<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: TangGee</title>
    <description>The latest articles on DEV Community by TangGee (@tanggee).</description>
    <link>https://dev.to/tanggee</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4176614%2F5ee4eb2d-8d07-4a09-b079-288a97342943.png</url>
      <title>DEV Community: TangGee</title>
      <link>https://dev.to/tanggee</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tanggee"/>
    <language>en</language>
    <item>
      <title>Develop aiopsterm Plugins</title>
      <dc:creator>TangGee</dc:creator>
      <pubDate>Sun, 25 Oct 2026 08:10:45 +0000</pubDate>
      <link>https://dev.to/tanggee/develop-aiopsterm-plugins-20p7</link>
      <guid>https://dev.to/tanggee/develop-aiopsterm-plugins-20p7</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1syrbrwherqhatyf7yac.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1syrbrwherqhatyf7yac.png" alt="aiopsterm" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An aiopsterm plugin is a declarative capability package validated and hosted by aiopsterm. It does not execute a plugin-provided JavaScript entry point.&lt;/p&gt;

&lt;p&gt;The current plugin kinds are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;content&lt;/code&gt;, which contributes terminal commands.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;provider&lt;/code&gt;, which contributes a controlled data form processed by a host-owned adapter.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Package Layout
&lt;/h2&gt;

&lt;p&gt;An external plugin is a ZIP archive with the &lt;code&gt;.aiopsterm-plugin&lt;/code&gt; extension. Its manifest must be at the archive root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-plugin.aiopsterm-plugin
└── aiopsterm.plugin.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Built-in plugins remain unpacked below &lt;code&gt;resources/builtin-plugins/&amp;lt;plugin-name&amp;gt;/aiopsterm.plugin.json&lt;/code&gt;. Packaging copies that directory into the application resources.&lt;/p&gt;

&lt;p&gt;Unsupported package extensions and legacy manifests are rejected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manifest
&lt;/h2&gt;

&lt;p&gt;Required common fields are &lt;code&gt;manifestVersion: 1&lt;/code&gt;, a unique &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;displayName&lt;/code&gt;, &lt;code&gt;version&lt;/code&gt;, &lt;code&gt;kind&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;engines.aiopsterm&lt;/code&gt;, and matching &lt;code&gt;contributes&lt;/code&gt;. The engine range accepts &lt;code&gt;*&lt;/code&gt;, an exact version, or &lt;code&gt;&amp;gt;=x.y.z&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;content&lt;/code&gt; plugin must contribute at least one command:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy65vp1usjjszbmxa83qw.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy65vp1usjjszbmxa83qw.webp" alt="aiopsterm workspace" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"manifestVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example.disk-check"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"displayName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Disk Check"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Disk inspection commands."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"aiopsterm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;=0.1.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"commands"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"disk-check.usage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Disk usage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Show filesystem capacity."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"df -h"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commands run through the existing terminal execution and security controller. A plugin cannot access terminal session objects or bypass that policy.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;provider&lt;/code&gt; plugin must contribute at least one asset provider. The current adapter is &lt;code&gt;json-assets&lt;/code&gt;, and the current field type is &lt;code&gt;textarea&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"manifestVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example.cmdb"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"displayName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Example CMDB"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Import JSON assets."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"aiopsterm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;=0.1.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"assetProviders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cmdb-json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CMDB JSON"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Paste an asset array."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"adapter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"json-assets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"fields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CMDB JSON"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"textarea"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Input may be an array or an object containing an &lt;code&gt;assets&lt;/code&gt; array. Every asset requires &lt;code&gt;externalId&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, and &lt;code&gt;host&lt;/code&gt;. The host limits a request to 2 MiB and 1000 assets, validates all rows before persistence, owns stable IDs, and normalizes fields.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification
&lt;/h2&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run typecheck
npx vitest run tests/extensions-backend.test.ts tests/extensions-client.test.ts
npm run audit:i18n
npm run audit:package-config
npm run audit:client-mocks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding a contribution type requires coordinated updates to shared contracts, manifest parsing, the host adapter, IPC and preload contracts, renderer guards and UI, packaging audits, and boundary tests. Capabilities that execute arbitrary code require a separate isolation and permission design.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/TangGee/aiopsterm" rel="noopener noreferrer"&gt;https://github.com/TangGee/aiopsterm&lt;/a&gt;&lt;/p&gt;

</description>
      <category>development</category>
      <category>plugin</category>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Integrate JumpServer with aiopsterm: Synchronize Your Bastion Host Catalog</title>
      <dc:creator>TangGee</dc:creator>
      <pubDate>Sat, 24 Oct 2026 06:32:15 +0000</pubDate>
      <link>https://dev.to/tanggee/integrate-jumpserver-with-aiopsterm-synchronize-your-bastion-host-catalog-52kg</link>
      <guid>https://dev.to/tanggee/integrate-jumpserver-with-aiopsterm-synchronize-your-bastion-host-catalog-52kg</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1syrbrwherqhatyf7yac.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1syrbrwherqhatyf7yac.png" alt="aiopsterm" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This guide is for JumpServer administrators and integration developers. It explains how aiopsterm synchronizes one organization's host catalog while keeping the API datasource and bastion SSH login as separate security boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario: Synchronize One Organization Catalog
&lt;/h2&gt;

&lt;p&gt;One JumpServer bastion record contains two configuration sets:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Configuration&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JumpServer root URL, Private Token, organization id&lt;/td&gt;
&lt;td&gt;Call the asset API and refresh the organization catalog&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bastion address, SSH port, username, and password or key&lt;/td&gt;
&lt;td&gt;Log in to the bastion and use it as a jump path&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The API token does not replace SSH credentials, and SSH credentials cannot read the asset API. Grant least privilege independently.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy65vp1usjjszbmxa83qw.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy65vp1usjjszbmxa83qw.webp" alt="aiopsterm terminal" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  JumpServer API Contract
&lt;/h2&gt;

&lt;p&gt;aiopsterm constructs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/v1/assets/hosts/?limit=100&amp;amp;offset=0
Authorization: PrivateToken &amp;lt;token&amp;gt;
X-JMS-ORG: &amp;lt;organization-id&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;X-JMS-ORG&lt;/code&gt; is sent only when an organization id is configured. Enter a root such as &lt;code&gt;https://jumpserver.example.com&lt;/code&gt;; do not include the hosts API path in the root URL.&lt;/p&gt;

&lt;p&gt;Client behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;30-second timeout per page.&lt;/li&gt;
&lt;li&gt;100-page safety limit.&lt;/li&gt;
&lt;li&gt;Follows only same-origin next-page URLs.&lt;/li&gt;
&lt;li&gt;Accepts a JumpServer list or a paged response with &lt;code&gt;results&lt;/code&gt; and &lt;code&gt;next&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Network, authentication, non-success HTTP, non-JSON, cross-origin pagination, and malformed-host errors fail the whole refresh.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Give the token account read-only host-asset access in the intended organization. Validate it from an administrative environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: PrivateToken &lt;/span&gt;&lt;span class="nv"&gt;$JMS_PRIVATE_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-JMS-ORG: &lt;/span&gt;&lt;span class="nv"&gt;$JMS_ORG_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://jumpserver.example.com/api/v1/assets/hosts/?limit=100&amp;amp;offset=0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not place the token in source code, logs, or issue screenshots.&lt;/p&gt;

&lt;h2&gt;
  
  
  Field Mapping And Stable Updates
&lt;/h2&gt;

&lt;p&gt;Valid remote hosts map:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JumpServer host id.&lt;/li&gt;
&lt;li&gt;Name and address.&lt;/li&gt;
&lt;li&gt;SSH port.&lt;/li&gt;
&lt;li&gt;Node path.&lt;/li&gt;
&lt;li&gt;Status, type, and category.&lt;/li&gt;
&lt;li&gt;Comment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Local rows are stably updated by remote identity within the selected bastion and organization scope. A successful refresh:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Updates existing synchronized assets.&lt;/li&gt;
&lt;li&gt;Creates new assets.&lt;/li&gt;
&lt;li&gt;Deletes remote-missing rows explicitly marked as JumpServer-synchronized for that organization.&lt;/li&gt;
&lt;li&gt;Preserves manually created hosts and other organizations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Updates apply only after the complete remote result validates. Authentication, pagination, or payload failures leave the existing synchronized catalog unchanged, preventing a transient outage from causing mass deletion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Credential And Data Protection
&lt;/h2&gt;

&lt;p&gt;The backend credential store encrypts the Private Token before SQLite or fallback persistence. Normal asset snapshots expose only &lt;code&gt;hasJumpserverToken&lt;/code&gt;, and asset exports omit the token. Plaintext is returned for editing only through the dedicated editable-secret boundary.&lt;/p&gt;

&lt;p&gt;Integrators should also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a separate token per environment.&lt;/li&gt;
&lt;li&gt;Rotate tokens and immediately run a refresh test.&lt;/li&gt;
&lt;li&gt;Prevent reverse proxies from logging &lt;code&gt;Authorization&lt;/code&gt; or &lt;code&gt;X-JMS-ORG&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use a valid TLS certificate for the root URL; do not leave verification disabled after testing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  SSH, Secondary Login, And File Management
&lt;/h2&gt;

&lt;p&gt;Synchronization discovers assets. Real target connections still use aiopsterm's SSH runtime:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Standard SSH TCP forwarding through the bastion is preferred for secondary SSH.&lt;/li&gt;
&lt;li&gt;Password, OTP, and keyboard-interactive requests stay in aiopsterm's authentication dialog.&lt;/li&gt;
&lt;li&gt;If the bastion rejects TCP forwarding, a visible terminal may fall back to relay-shell and run nested SSH.&lt;/li&gt;
&lt;li&gt;Relay-shell is not a structured SSH channel and cannot support SFTP. Use a TCP-forward-capable path, or run &lt;code&gt;scp&lt;/code&gt; or &lt;code&gt;rsync&lt;/code&gt; in the terminal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqqs3xx94u8zpeftgkff1.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqqs3xx94u8zpeftgkff1.webp" alt="aiopsterm AI conversation" width="799" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Kubernetes Boundary
&lt;/h2&gt;

&lt;p&gt;Synchronized assets can be mapped into the Kubernetes catalog, but JumpServer Kubernetes command streaming is not implemented. Connect, resource refresh, terminal, and resource actions for this source fail closed. Import a directly usable kubeconfig for real cluster operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integration Checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Validate the token, organization header, and first page with curl.&lt;/li&gt;
&lt;li&gt;Confirm every pagination &lt;code&gt;next&lt;/code&gt; URL has the same origin as the configured root.&lt;/li&gt;
&lt;li&gt;Save the datasource in aiopsterm and verify that normal snapshots and exports contain no token.&lt;/li&gt;
&lt;li&gt;Run the first refresh and check create count, names, addresses, ports, and node paths.&lt;/li&gt;
&lt;li&gt;Change one remote asset and refresh; confirm a stable update rather than a duplicate.&lt;/li&gt;
&lt;li&gt;Delete one remote synchronized asset and create one manual local host; confirm only the synchronized row is removed.&lt;/li&gt;
&lt;li&gt;Test with a bad token and confirm the existing catalog remains intact.&lt;/li&gt;
&lt;li&gt;Test bastion SSH, secondary SSH, OTP, and the SFTP boundary separately.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/TangGee/aiopsterm" rel="noopener noreferrer"&gt;https://github.com/TangGee/aiopsterm&lt;/a&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>automation</category>
      <category>devops</category>
      <category>security</category>
    </item>
    <item>
      <title>MCP Settings in aiopsterm</title>
      <dc:creator>TangGee</dc:creator>
      <pubDate>Sun, 18 Oct 2026 06:34:59 +0000</pubDate>
      <link>https://dev.to/tanggee/mcp-settings-in-aiopsterm-24nm</link>
      <guid>https://dev.to/tanggee/mcp-settings-in-aiopsterm-24nm</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1syrbrwherqhatyf7yac.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1syrbrwherqhatyf7yac.png" alt="aiopsterm" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  MCP Settings
&lt;/h1&gt;

&lt;p&gt;This page lives under &lt;code&gt;Settings -&amp;gt; Host Agent -&amp;gt; MCP&lt;/code&gt;. It manages MCP Servers, Tools, and Resources available to aiopsterm's internal Agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Top Actions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Add Server: Opens the MCP config editor for &lt;code&gt;mcp_settings.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Save: Saves the MCP JSON in the editor.&lt;/li&gt;
&lt;li&gt;Close: Closes the MCP config editor.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  MCP Server List
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Expand/collapse: Click a server name to show its tools and resources.&lt;/li&gt;
&lt;li&gt;Status badge: Shows server state, such as connected, disabled, or error.&lt;/li&gt;
&lt;li&gt;Edit: Opens the MCP config editor.&lt;/li&gt;
&lt;li&gt;Delete: Removes the server configuration.&lt;/li&gt;
&lt;li&gt;Enable switch: Enables or disables the server. Disabled servers do not provide tools or resources to the Agent.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Transport Config
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;mcp_settings.json&lt;/code&gt; supports &lt;code&gt;stdio&lt;/code&gt;, &lt;code&gt;streamableHttp&lt;/code&gt;, and legacy &lt;code&gt;sse&lt;/code&gt;. Codex-style configs may omit the transport type: when a server has &lt;code&gt;url&lt;/code&gt; and no &lt;code&gt;command&lt;/code&gt;, aiopsterm saves it as &lt;code&gt;streamableHttp&lt;/code&gt;. Common aliases &lt;code&gt;http&lt;/code&gt;, &lt;code&gt;streamable_http&lt;/code&gt;, and &lt;code&gt;streamable-http&lt;/code&gt; are also normalized to &lt;code&gt;streamableHttp&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Tool name: Click to enable or disable the tool.&lt;/li&gt;
&lt;li&gt;Auto Approve: Allows the tool to be auto-approved when policy permits it. High-risk calls still require approval.&lt;/li&gt;
&lt;li&gt;PARAMETERS: Shows parameter names, required markers, and descriptions.&lt;/li&gt;
&lt;li&gt;Argument editor: JSON arguments used for a direct tool test.&lt;/li&gt;
&lt;li&gt;Run: Calls the tool directly and shows result or error.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbs971s1qpl1iumapsp87.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbs971s1qpl1iumapsp87.webp" alt="MCP bridge" width="799" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Resources tab: Shows resources exposed by the server.&lt;/li&gt;
&lt;li&gt;Read: Reads the selected resource and shows returned content or error.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Filesystem Scope
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@modelcontextprotocol/server-filesystem&lt;/code&gt; only accesses the local filesystem of the process that starts the MCP server. For example, when the configured argument is &lt;code&gt;/home/tester&lt;/code&gt;, it accesses &lt;code&gt;/home/tester&lt;/code&gt; on the machine running aiopsterm.&lt;/p&gt;

&lt;p&gt;It does not use aiopsterm SSH/SFTP asset connections and does not bind to the active terminal's remote shell. After a manual &lt;code&gt;relay ssh -&amp;gt; ssh target&lt;/code&gt; hop, filesystem MCP still cannot see files on the final target. Use shell commands in that terminal, or a purpose-built aiopsterm MCP server that explicitly bridges to an asset SFTP channel or terminal session.&lt;/p&gt;

&lt;p&gt;MCP settings change the tool set that later Agent requests can use.&lt;/p&gt;




&lt;p&gt;GitHub: &lt;a href="https://github.com/TangGee/aiopsterm" rel="noopener noreferrer"&gt;https://github.com/TangGee/aiopsterm&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>mcp</category>
      <category>software</category>
    </item>
    <item>
      <title>I unified my terminal, SSH, Kubernetes, databases — and all my AI agents — into one open-source app</title>
      <dc:creator>TangGee</dc:creator>
      <pubDate>Sun, 11 Oct 2026 08:19:39 +0000</pubDate>
      <link>https://dev.to/tanggee/i-unified-my-terminal-ssh-kubernetes-databases-and-all-my-ai-agents-into-one-open-source-app-4aie</link>
      <guid>https://dev.to/tanggee/i-unified-my-terminal-ssh-kubernetes-databases-and-all-my-ai-agents-into-one-open-source-app-4aie</guid>
      <description>&lt;p&gt;If you run AI coding agents (Codex, Claude Code, Cursor, Kimi Code) you've probably had this moment: a session finished 40 minutes ago and nobody noticed. Or it got stuck waiting for an approval. Or you can't remember which terminal window was running what.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I built an open-source AI operations terminal&lt;/strong&gt; that gives every AI coding session an inbox — plus SSH, Kubernetes and databases in the same window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your AI coding sessions, in one inbox
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw5me60ryyod6hqk104t9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw5me60ryyod6hqk104t9.png" alt="AI session inbox" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All external coding-agent sessions appear in one list with live status:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Desktop notifications&lt;/strong&gt; — when a session needs you (approval, feedback), it calls you. You don't have to watch AI work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approvals in your hands&lt;/strong&gt; — AI wants to write a file or run a command? Approve right from the sidebar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File previews on the right&lt;/strong&gt; — see what the agent changed, without leaving the terminal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full transcripts&lt;/strong&gt; — view, search and revise the entire session later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Demo: AI writes a Snake game → two approvals → preview + transcript.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI on remote hosts — zero install
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk36g8cm6zn1d0ztu9sp5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk36g8cm6zn1d0ztu9sp5.png" alt="Remote troubleshooting" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bind an AI agent to a bound SSH terminal and ask: "Check the system status of this remote host — CPU, memory, disk, load, and whether key services are healthy."&lt;/p&gt;

&lt;p&gt;The agent runs diagnostics by itself (&lt;code&gt;df&lt;/code&gt;, &lt;code&gt;free&lt;/code&gt;, &lt;code&gt;top&lt;/code&gt;, &lt;code&gt;systemctl&lt;/code&gt;, &lt;code&gt;uptime&lt;/code&gt;), every command visible in the terminal. When it wants to run something system-changing, you approve. It can not only inspect — it can fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero install on the remote host&lt;/strong&gt;: the AI drives commands through the terminal you already connected, so it works behind jump hosts and bastions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rest of the ops terminal
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;SSH with bastion sync (JumpServer) — double-click to connect from the workspace&lt;/li&gt;
&lt;li&gt;Kubernetes workspace — work with clusters from the terminal&lt;/li&gt;
&lt;li&gt;DB AI — natural language → SQL&lt;/li&gt;
&lt;li&gt;Local and remote file management&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Open source, Apache 2.0
&lt;/h2&gt;

&lt;p&gt;Downloads for macOS / Windows / Linux: &lt;a href="https://aiopsterm.com" rel="noopener noreferrer"&gt;https://aiopsterm.com&lt;/a&gt;&lt;br&gt;
Source: &lt;a href="https://github.com/TangGee/aiopsterm" rel="noopener noreferrer"&gt;https://github.com/TangGee/aiopsterm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Star it if useful; issues and feedback are very welcome. It's 0.x — I want to hear from people who run multiple agents or manage fleets.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>opensource</category>
      <category>devops</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
