aai-gateway 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,32 +4,32 @@
4
4
 
5
5
  AAI Gateway turns many apps, agents, skills, and MCP servers into one MCP server.
6
6
 
7
- ## 核心价值
7
+ ## Core Values
8
8
 
9
- ### 价值 1:自然语言驱动的工具接入
9
+ ### Value 1: Natural Language-Driven Tool Integration
10
10
 
11
- 安装 AAI Gateway MCP 之后,你可以通过自然语言描述快速接入其他任意 MCP、技能,并操控其他 AI Agent 工具(包括 Claude CodeCodexOpenCode 等)。
11
+ After installing the AAI Gateway MCP, you can quickly integrate any other MCP or skill through natural language descriptions, and control other AI Agent tools (including Claude Code, Codex, OpenCode, etc.).
12
12
 
13
- AAI Gateway 还集成搜索工具,可以帮助你从权威、主流的网站上搜索官方、安全的 MCP 和技能,并实现一句话安装。
13
+ AAI Gateway also integrates a search tool that helps you search for official and secure MCPs and skills from authoritative, mainstream websites, and install them with a single sentence. Control of other AI Agent tools is done via Agent Client Protocol (ACP), including session management.
14
14
 
15
- ### 价值 2:渐进式披露策略
15
+ ### Value 2: Progressive Disclosure Strategy
16
16
 
17
- AAI Gateway 不会一次性向大模型上下文中塞入所有工具的描述,而是采用渐进式披露策略:
17
+ AAI Gateway does not dump all tool descriptions into the LLM context at once. Instead, it employs a progressive disclosure strategy:
18
18
 
19
- **MCP Server 级别**:先只暴露 MCP Server 的整体描述。当大模型发现需要使用某个具体工具时,会先返回工具使用指导,然后 Agent 根据指导调用统一的 `aai:exec` 去执行。`aai:exec` 接受 `appId`、`tool`、`tool args` 作为参数。
19
+ **MCP Server Level**: Only the overall description of the MCP Server is exposed initially. When the LLM determines that a specific tool needs to be used, it returns tool usage guidance first. The Agent then calls the unified `aai:exec` to execute based on that guidance. `aai:exec` accepts `appId`, `tool`, and `tool args` as parameters.
20
20
 
21
- **MCP / 技能 描述级别**:提供两个层级的披露策略:
21
+ **MCP / Skill Description Level**: Two tiers of disclosure are provided:
22
22
 
23
- - `summary` — 自然语言描述,适合自动触发
24
- - `keywords` — 紧凑的关键词集合,进一步简化上下文占用
23
+ - `summary` — Natural language description; good for automatic triggering
24
+ - `keywords` — Compact keyword set; further reduces context overhead
25
25
 
26
- 这让 OpenCode 这种需要大量工具和技能的场景下依然能良好运行。
26
+ This allows OpenClaw (a popular personal assistant application) and similar tools that require many tools and skills to still run smoothly.
27
27
 
28
- ## 使用方案
28
+ ## How To Use
29
29
 
30
- ### 1. 安装 AAI Gateway MCP
30
+ ### 1. Install AAI Gateway MCP
31
31
 
32
- 你不需要预装 `aai-gateway`。只需将其注册为用户级 MCP 服务器,通过 `npx` 启动即可。
32
+ You do not need to preinstall `aai-gateway`. Simply register it as a user-level MCP server and launch it via `npx`.
33
33
 
34
34
  #### Claude Code
35
35
 
@@ -45,7 +45,7 @@ codex mcp add aai-gateway -- npx -y aai-gateway
45
45
 
46
46
  #### OpenCode
47
47
 
48
- `~/.config/opencode/opencode.json` 中添加:
48
+ Add to `~/.config/opencode/opencode.json`:
49
49
 
50
50
  ```json
51
51
  {
@@ -60,47 +60,46 @@ codex mcp add aai-gateway -- npx -y aai-gateway
60
60
  }
61
61
  ```
62
62
 
63
- ### 2. 搜索并安装 MCP 或技能
63
+ ### 2. Search and Install MCP or Skill
64
64
 
65
- 如果你不知道该安装哪个 MCP 或技能,可以让 AI 工具调用 `import:search`。
65
+ If you don't know which MCP or skill to install, just ask your AI tool to search for what you need using AAI Gateway (e.g., "please search for a filesystem MCP" or "find me a git commit skill").
66
66
 
67
- `import:search` 会:
67
+ The search will:
68
68
 
69
- - 将用户请求转换为搜索关键词
70
- - 推荐更安全的权威来源优先搜索
71
- - 将搜索结果规范化为候选列表
72
- - 为每个候选项生成临时 ID 供用户确认
73
- - 将确认的项目路由到 `mcp:import` 或 `skill:import` 流程
69
+ - Convert your request into search keywords
70
+ - Recommend safer authoritative sources to search first
71
+ - Normalize search results into a shortlist for your confirmation
72
+ - Route confirmed items into the import flow
74
73
 
75
- **推荐的搜索来源顺序**:
74
+ **Recommended Search Source Order**:
76
75
 
77
- 1. 官方目录:`modelcontextprotocol/registry`、`modelcontextprotocol/servers`、`openai/skills`
78
- 2. 社区精选列表:`punkpeye/awesome-mcp-servers`、`ComposioHQ/awesome-claude-skills`
79
- 3. 高审查来源:如 ClawHub 等开放市场(需额外谨慎)
76
+ 1. Official catalogs: `modelcontextprotocol/registry`, `modelcontextprotocol/servers`, `openai/skills`
77
+ 2. Community-curated lists: `punkpeye/awesome-mcp-servers`, `ComposioHQ/awesome-claude-skills`
78
+ 3. Higher-scrutiny sources: Open marketplaces like ClawHub (use with extra caution)
80
79
 
81
- > 注意:推荐列表是首选起点,而非硬性白名单。请勿随意推荐来自不知名小网站工具。对于市场平台,请额外检查维护者身份、仓库活跃度、README 质量和许可证是否可见。
80
+ > Note: The recommended list is a starting point, not a hard allowlist. Do not casually suggest tools from unknown websites. For marketplace platforms, also verify the maintainer's identity, repository activity, README quality, and license visibility.
82
81
 
83
- ### 3. 导入 MCP Server
82
+ ### 3. Import an MCP Server
84
83
 
85
- 主流程:复制主流 MCP 配置片段到 AI 工具,让它通过 AAI Gateway 导入。
84
+ Main workflow: Copy a mainstream MCP config snippet into your AI tool and ask it to import that server through AAI Gateway.
86
85
 
87
- AI 工具会:
86
+ The AI tool will:
88
87
 
89
- 1. 读取你粘贴的 MCP 配置
90
- 2. 询问你选择暴露模式
91
- 3. 调用 `mcp:import`
88
+ 1. Read the MCP config you pasted
89
+ 2. Ask you to choose an exposure mode
90
+ 3. Call `mcp:import`
92
91
 
93
- AAI Gateway 保持导入参数与标准 MCP 配置格式一致:
92
+ AAI Gateway keeps import parameters consistent with standard MCP config shapes:
94
93
 
95
- - stdio MCP:`command`、`args`、`env`、`cwd`
96
- - remote MCP:`url`、可选 `transport`、可选 `headers`
94
+ - stdio MCP: `command`, `args`, `env`, `cwd`
95
+ - remote MCP: `url`, optional `transport`, optional `headers`
97
96
 
98
- 导入前请选择暴露模式:
97
+ Choose an exposure mode before import:
99
98
 
100
- - `summary`:更容易自动触发
101
- - `keywords`:为更多工具留出空间,但通常需要更明确的关键词提及
99
+ - `summary`: Easier automatic triggering
100
+ - `keywords`: Leaves room for more tools, but usually needs more explicit keyword mentions
102
101
 
103
- **stdio MCP 示例**:
102
+ **stdio MCP Example**:
104
103
 
105
104
  ```json
106
105
  {
@@ -109,7 +108,7 @@ AAI Gateway 保持导入参数与标准 MCP 配置格式一致:
109
108
  }
110
109
  ```
111
110
 
112
- **Remote Streamable HTTP MCP 示例**:
111
+ **Remote Streamable HTTP MCP Example**:
113
112
 
114
113
  ```json
115
114
  {
@@ -117,7 +116,7 @@ AAI Gateway 保持导入参数与标准 MCP 配置格式一致:
117
116
  }
118
117
  ```
119
118
 
120
- **Remote SSE MCP 示例**:
119
+ **Remote SSE MCP Example**:
121
120
 
122
121
  ```json
123
122
  {
@@ -126,23 +125,23 @@ AAI Gateway 保持导入参数与标准 MCP 配置格式一致:
126
125
  }
127
126
  ```
128
127
 
129
- 导入完成后,AAI Gateway 返回:
128
+ After import, AAI Gateway returns:
130
129
 
131
- - 生成的 app id
132
- - 生成的 `keywords`
133
- - 生成的 `summary`
134
- - 引导工具名称:`app:<id>`
130
+ - The generated app id
131
+ - The generated `keywords`
132
+ - The generated `summary`
133
+ - The guide tool name: `app:<id>`
135
134
 
136
- > **重要**:导入后需重启 AI 工具才能使用新导入的工具。重启后,导入的应用将显示为 `app:<id>`,使用 `aai:exec` 执行实际操作。
135
+ > **Important**: Restart your AI tool before using the newly imported tool. After restart, the imported app will appear as `app:<id>`. Use `aai:exec` to actually run the imported app's operations.
137
136
 
138
- ### 4. 导入技能 (Skill)
137
+ ### 4. Import a Skill
139
138
 
140
- 技能导入同样通过 AI 工具完成。告诉 AI 工具调用 `skill:import`,然后提供:
139
+ Skills are imported through the AI tool as well. Just tell the AI tool to import a skill using AAI Gateway, then provide either:
141
140
 
142
- - 本地技能路径
143
- - 或暴露 `SKILL.md` 的远程技能根 URL
141
+ - A local skill path
142
+ - A remote skill root URL that exposes `SKILL.md`
144
143
 
145
- **本地技能示例**:
144
+ **Local Skill Example**:
146
145
 
147
146
  ```json
148
147
  {
@@ -150,7 +149,7 @@ AAI Gateway 保持导入参数与标准 MCP 配置格式一致:
150
149
  }
151
150
  ```
152
151
 
153
- **远程技能示例**:
152
+ **Remote Skill Example**:
154
153
 
155
154
  ```json
156
155
  {
@@ -158,23 +157,23 @@ AAI Gateway 保持导入参数与标准 MCP 配置格式一致:
158
157
  }
159
158
  ```
160
159
 
161
- MCP 导入一样,技能导入返回 `app id`、`keywords`、`summary` `app:<id>` 引导工具名称。
160
+ Like MCP import, skill import returns `app id`, `keywords`, `summary`, and the `app:<id>` guide tool name.
162
161
 
163
- 导入后需重启 AI 工具。
162
+ Restart your AI tool after import.
164
163
 
165
- ### 5. 支持的 ACP Agents
164
+ ### 5. Supported ACP Agents
166
165
 
167
- AAI Gateway 还能通过 ACP 控制类应用的 Agent。
166
+ AAI Gateway can also control app-like agents through ACP.
168
167
 
169
- 当前支持的 ACP Agent 类型:
168
+ Currently supported ACP agent types:
170
169
 
171
170
  - OpenCode
172
171
  - Claude Code
173
172
  - Codex
174
173
 
175
- ## 原理
174
+ ## How It Works
176
175
 
177
- ### 架构图
176
+ ### Architecture
178
177
 
179
178
  ```
180
179
  ┌─────────────────────────────────────────────────────────────┐
@@ -213,19 +212,19 @@ AAI Gateway 还能通过 ACP 控制类应用的 Agent。
213
212
  └─────────────────────────────────────────────────────────────┘
214
213
  ```
215
214
 
216
- ### 统一抽象:Agent App
215
+ ### Unified Abstraction: Agent App
217
216
 
218
- AAI Gateway MCP、技能、ACP Agent、CLI 工具统一抽象为 **Agent App**。
217
+ AAI Gateway unifies MCPs, Skills, ACP Agents, and CLI tools into **Agent Apps**.
219
218
 
220
- 只要提供一个 App 的描述文件 (`aai.json`),即可接入 AAI Gateway。描述文件告诉 AAI Gateway
219
+ To integrate an app with AAI Gateway, simply provide an app descriptor file (`aai.json`). The descriptor tells AAI Gateway:
221
220
 
222
- - App 是什么
223
- - 如何连接
224
- - 如何以低上下文成本暴露
221
+ - What the app is
222
+ - How to connect to it
223
+ - How to expose it at low context cost
225
224
 
226
- ### 描述文件示例
225
+ ### Descriptor Examples
227
226
 
228
- #### MCP Server 描述文件
227
+ #### MCP Server Descriptor
229
228
 
230
229
  ```json
231
230
  {
@@ -250,7 +249,7 @@ AAI Gateway 将 MCP、技能、ACP Agent、CLI 工具统一抽象为 **Agent App
250
249
  }
251
250
  ```
252
251
 
253
- #### Skill 描述文件
252
+ #### Skill Descriptor
254
253
 
255
254
  ```json
256
255
  {
@@ -274,7 +273,7 @@ AAI Gateway 将 MCP、技能、ACP Agent、CLI 工具统一抽象为 **Agent App
274
273
  }
275
274
  ```
276
275
 
277
- #### ACP Agent 描述文件
276
+ #### ACP Agent Descriptor
278
277
 
279
278
  ```json
280
279
  {
@@ -298,7 +297,7 @@ AAI Gateway 将 MCP、技能、ACP Agent、CLI 工具统一抽象为 **Agent App
298
297
  }
299
298
  ```
300
299
 
301
- #### CLI 工具描述文件
300
+ #### CLI Tool Descriptor
302
301
 
303
302
  ```json
304
303
  {
@@ -322,53 +321,53 @@ AAI Gateway 将 MCP、技能、ACP Agent、CLI 工具统一抽象为 **Agent App
322
321
  }
323
322
  ```
324
323
 
325
- ## 如何将更多 App 预置集成到 AAI Gateway
324
+ ## Pre-bundling Apps in AAI Gateway
326
325
 
327
- ### 提交 Pull Request
326
+ ### Submit a Pull Request
328
327
 
329
- 如果你希望 AAI Gateway 默认打包某个 App 的描述文件,可以提交 PR
328
+ If you want AAI Gateway to ship with a descriptor for an app by default, open a PR.
330
329
 
331
- PR 需要包含:
330
+ A PR should include:
332
331
 
333
- 1. 描述文件本身
334
- 2. 安全的发现规则(证明 App 确实已安装)
335
- 3. 连接配置
336
- 4. 说明为什么这个集成应该被捆绑
332
+ 1. The descriptor itself
333
+ 2. A safe discovery rule that proves the app is actually installed
334
+ 3. The connection config
335
+ 4. An explanation of why the integration should be bundled
337
336
 
338
- 内置 ACP Agent 描述文件位于:
337
+ Built-in ACP agent descriptors live in:
339
338
 
340
339
  - `src/discovery/descriptors/`
341
340
 
342
- 它们在以下文件中注册:
341
+ They are registered in:
343
342
 
344
343
  - `src/discovery/agent-registry.ts`
345
344
 
346
- 标准 PR 流程:
345
+ Standard PR workflow:
347
346
 
348
- 1. 添加描述文件
349
- 2. 添加或更新发现检查
350
- 3. 在适当的发现源中注册
351
- 4. 如果新集成面向用户,更新 README
347
+ 1. Add the descriptor file
348
+ 2. Add or update discovery checks
349
+ 3. Register it in the appropriate discovery source
350
+ 4. Update the README if the new integration is user-facing
352
351
 
353
- 如果你不确定某个集成是否应该被捆绑,请先提交 Issue 讨论。
352
+ If you're unsure whether an integration should be bundled, open an issue first to discuss.
354
353
 
355
- ### 描述文件放置位置
354
+ ### Where to Place Descriptors
356
355
 
357
- AAI Gateway 从以下位置发现 App:
356
+ AAI Gateway discovers apps from the following locations:
358
357
 
359
358
  #### Web Apps
360
359
 
361
- 发布到:
360
+ Publish at:
362
361
 
363
362
  ```
364
363
  https://<your-host>/.well-known/aai.json
365
364
  ```
366
365
 
367
- 用户调用 `remote:discover` 时,AAI Gateway 会获取该路径。
366
+ AAI Gateway fetches this path when the user calls `remote:discover`.
368
367
 
369
368
  #### macOS Apps
370
369
 
371
- 推荐位置:
370
+ Recommended locations:
372
371
 
373
372
  - `<YourApp>.app/Contents/Resources/aai.json`
374
373
  - `~/Library/Containers/<container>/Data/Library/Application Support/aai.json`
@@ -376,7 +375,7 @@ https://<your-host>/.well-known/aai.json
376
375
 
377
376
  #### Linux Apps
378
377
 
379
- 扫描以下位置:
378
+ Scanned locations:
380
379
 
381
380
  - `/usr/share`
382
381
  - `/usr/local/share`
@@ -384,20 +383,20 @@ https://<your-host>/.well-known/aai.json
384
383
 
385
384
  #### Windows Apps
386
385
 
387
- 扫描以下位置:
386
+ Scanned locations:
388
387
 
389
388
  - `C:\Program Files`
390
389
  - `C:\Program Files (x86)`
391
390
  - `%LOCALAPPDATA%`
392
391
 
393
- #### 描述文件编写建议
392
+ #### Descriptor Guidelines
394
393
 
395
- - 保持描述文件小而实用
396
- - `app.name.default` 要清晰
397
- - `keywords` 要短且高信号
398
- - `summary` 要解释何时应该使用该 App
399
- - 详细能力数据放在下游协议中,而非描述文件中
400
- - 如果你的 App 已使用 MCP,保持描述文件最小化,让 MCP 提供惰性工具详情
394
+ - Keep descriptors small and practical
395
+ - Make `app.name.default` clear
396
+ - Keep `keywords` short and high-signal
397
+ - Make `summary` explain when the app should be used
398
+ - Put detailed capability data in the downstream protocol, not in the descriptor
399
+ - If your app already speaks MCP, keep the descriptor minimal and let MCP provide lazy tool details
401
400
 
402
401
  ## Disclaimer
403
402
 
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { A as logger, D as createDesktopDiscovery, O as getManagedAppDir, T as AAI_GATEWAY_VERSION, _ as isMcpAccess, a as buildMcpImportConfig, b as getMcpExecutor, c as importSkill, d as upsertSkillRegistryEntry, f as upsertMcpRegistryEntry, i as IMPORT_LIMITS, l as normalizeExposureInput, m as createSecureStorage, n as createGatewayServer, o as buildSkillImportSource, r as EXPOSURE_LIMITS, s as importMcpServer, u as validateImportHeaders, v as isSkillAccess } from "./server-DQ0nbmQB.js";
2
+ import { A as logger, D as createDesktopDiscovery, O as getManagedAppDir, T as AAI_GATEWAY_VERSION, _ as isMcpAccess, a as buildMcpImportConfig, b as getMcpExecutor, c as importSkill, d as upsertSkillRegistryEntry, f as upsertMcpRegistryEntry, i as IMPORT_LIMITS, l as normalizeExposureInput, m as createSecureStorage, n as createGatewayServer, o as buildSkillImportSource, r as EXPOSURE_LIMITS, s as importMcpServer, u as validateImportHeaders, v as isSkillAccess } from "./server-C-9LuKWE.js";
3
3
  import { join } from "node:path";
4
4
  import { existsSync, readFileSync } from "node:fs";
5
5
  //#region src/cli.ts
@@ -154,8 +154,8 @@ function parseArgs(args) {
154
154
  };
155
155
  }
156
156
  if (args[0] === "app" && args[1] === "config") {
157
- const localId = args[2];
158
- if (!localId) throw new Error("Usage: aai-gateway app config <app-id>");
157
+ const appId = args[2];
158
+ if (!appId) throw new Error("Usage: aai-gateway app config <app-id>");
159
159
  let exposure;
160
160
  let summary;
161
161
  const keywords = [];
@@ -188,7 +188,7 @@ function parseArgs(args) {
188
188
  return {
189
189
  command: "app-config",
190
190
  dev,
191
- localId,
191
+ appId,
192
192
  ...exposure ? { exposure } : {},
193
193
  ...summary ? { summary } : {},
194
194
  ...keywords.length > 0 ? { keywords } : {}
@@ -283,7 +283,7 @@ async function runScan(dev) {
283
283
  return;
284
284
  }
285
285
  for (const app of apps) {
286
- console.log(`${app.localId}`);
286
+ console.log(`${app.appId}`);
287
287
  console.log(` Name: ${app.descriptor.app.name.default}`);
288
288
  console.log(` Location: ${app.location ?? "(unknown)"}`);
289
289
  console.log(` Protocol: ${app.descriptor.access.protocol}`);
@@ -312,10 +312,10 @@ async function runMcpImport(options) {
312
312
  headers: options.headers
313
313
  });
314
314
  console.log(`Imported MCP app: ${result.descriptor.app.name.default}`);
315
- console.log(`App ID: ${result.entry.localId}`);
315
+ console.log(`App ID: ${result.entry.appId}`);
316
316
  console.log(`Descriptor: ${result.entry.descriptorPath}`);
317
- console.log(`Managed directory: ${getManagedAppDir(result.entry.localId)}`);
318
- console.log(`Tool name after restart: app:${result.entry.localId}`);
317
+ console.log(`Managed directory: ${getManagedAppDir(result.entry.appId)}`);
318
+ console.log(`Tool name after restart: app:${result.entry.appId}`);
319
319
  console.log(`Keywords: ${result.descriptor.exposure.keywords.join(", ")}`);
320
320
  console.log(`Summary: ${result.descriptor.exposure.summary}`);
321
321
  console.log(`Exposure mode: ${options.exposure}`);
@@ -333,16 +333,16 @@ async function runSkillImport(options) {
333
333
  url: source.url
334
334
  });
335
335
  console.log(`Imported skill: ${result.descriptor.app.name.default}`);
336
- console.log(`App ID: ${result.localId}`);
337
- console.log(`Descriptor: ${join(getManagedAppDir(result.localId), "aai.json")}`);
336
+ console.log(`App ID: ${result.appId}`);
337
+ console.log(`Descriptor: ${join(getManagedAppDir(result.appId), "aai.json")}`);
338
338
  console.log(`Skill directory: ${result.managedPath}`);
339
- console.log(`Tool name after restart: app:${result.localId}`);
339
+ console.log(`Tool name after restart: app:${result.appId}`);
340
340
  console.log(`Keywords: ${result.descriptor.exposure.keywords.join(", ")}`);
341
341
  console.log(`Summary: ${result.descriptor.exposure.summary}`);
342
342
  console.log(`Exposure mode: ${options.exposure}`);
343
343
  }
344
344
  async function runAppConfig(options) {
345
- const descriptorPath = resolveManagedDescriptorPath(options.localId);
345
+ const descriptorPath = resolveManagedDescriptorPath(options.appId);
346
346
  const descriptor = JSON.parse(readFileSync(descriptorPath, "utf-8"));
347
347
  const nextExposure = normalizeAppConfigExposure(options, descriptor.exposure);
348
348
  const nextDescriptor = {
@@ -350,17 +350,17 @@ async function runAppConfig(options) {
350
350
  exposure: nextExposure
351
351
  };
352
352
  if (isMcpAccess(nextDescriptor.access)) await upsertMcpRegistryEntry({
353
- localId: options.localId,
353
+ appId: options.appId,
354
354
  protocol: "mcp",
355
355
  config: nextDescriptor.access.config
356
356
  }, nextDescriptor);
357
357
  else if (isSkillAccess(nextDescriptor.access)) await upsertSkillRegistryEntry({
358
- localId: options.localId,
358
+ appId: options.appId,
359
359
  protocol: "skill",
360
360
  config: nextDescriptor.access.config
361
361
  }, nextDescriptor);
362
- else throw new Error(`App '${options.localId}' is not an imported MCP app or imported skill`);
363
- console.log(`Updated app: ${options.localId}`);
362
+ else throw new Error(`App '${options.appId}' is not an imported MCP app or imported skill`);
363
+ console.log(`Updated app: ${options.appId}`);
364
364
  if (options.exposure) console.log(`Exposure mode: ${options.exposure}`);
365
365
  console.log(`Keywords: ${nextDescriptor.exposure.keywords.join(", ")}`);
366
366
  console.log(`Summary: ${nextDescriptor.exposure.summary}`);
@@ -372,8 +372,8 @@ function normalizeAppConfigExposure(options, current) {
372
372
  summary
373
373
  });
374
374
  }
375
- function resolveManagedDescriptorPath(localId) {
376
- const descriptorPath = join(getManagedAppDir(localId), "aai.json");
375
+ function resolveManagedDescriptorPath(appId) {
376
+ const descriptorPath = join(getManagedAppDir(appId), "aai.json");
377
377
  if (existsSync(descriptorPath)) return descriptorPath;
378
378
  return descriptorPath;
379
379
  }
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { createDesktopDiscovery } from './discovery/index.js';\nimport { getMcpExecutor } from './executors/mcp.js';\nimport {\n buildMcpImportConfig,\n buildSkillImportSource,\n type ExposureMode,\n EXPOSURE_LIMITS,\n IMPORT_LIMITS,\n importMcpServer,\n importSkill,\n normalizeExposureInput,\n validateImportHeaders,\n} from './mcp/importer.js';\nimport { createGatewayServer } from './mcp/server.js';\nimport { upsertMcpRegistryEntry } from './storage/mcp-registry.js';\nimport { getManagedAppDir } from './storage/paths.js';\nimport { createSecureStorage } from './storage/secure-storage/index.js';\nimport { upsertSkillRegistryEntry } from './storage/skill-registry.js';\nimport { isMcpAccess, isSkillAccess, type AaiJson } from './types/aai-json.js';\nimport { logger } from './utils/logger.js';\nimport { AAI_GATEWAY_VERSION } from './version.js';\n\ninterface CommonOptions {\n dev: boolean;\n}\n\ninterface ServeOptions extends CommonOptions {\n command: 'serve';\n}\n\ninterface ScanOptions extends CommonOptions {\n command: 'scan';\n}\n\ninterface ExposureOptions {\n exposure: ExposureMode;\n summary: string;\n keywords: string[];\n}\n\ninterface McpImportOptions extends CommonOptions, ExposureOptions {\n command: 'mcp-import';\n name?: string;\n transport?: 'streamable-http' | 'sse';\n url?: string;\n launchCommand?: string;\n timeout?: number;\n launchArgs: string[];\n launchEnv: Record<string, string>;\n launchCwd?: string;\n headers: Record<string, string>;\n}\n\ninterface SkillImportOptions extends CommonOptions, ExposureOptions {\n command: 'skill-import';\n path?: string;\n url?: string;\n}\n\ninterface AppConfigOptions extends CommonOptions {\n command: 'app-config';\n localId: string;\n exposure?: ExposureMode;\n keywords?: string[];\n summary?: string;\n}\n\ntype CliOptions = ServeOptions | ScanOptions | McpImportOptions | SkillImportOptions | AppConfigOptions;\n\nfunction parseKeyValue(value: string, flag: string): [string, string] {\n const index = value.indexOf('=');\n if (index === -1) {\n throw new Error(`${flag} expects KEY=VALUE`);\n }\n return [value.slice(0, index), value.slice(index + 1)];\n}\n\nfunction parsePositiveInteger(value: string, flag: string): number {\n const parsed = Number(value);\n if (!Number.isInteger(parsed) || parsed <= 0) {\n throw new Error(`${flag} expects a positive integer`);\n }\n return parsed;\n}\n\nfunction parseArgs(args: string[]): CliOptions {\n const dev = args.includes('--dev');\n if (args.includes('--scan')) {\n return { command: 'scan', dev };\n }\n\n if (args[0] === 'mcp' && args[1] === 'import') {\n const exposure = parseRequiredExposureArgs(args.slice(2));\n let name: string | undefined;\n let transport: 'streamable-http' | 'sse' | undefined;\n let url: string | undefined;\n let launchCommand: string | undefined;\n let timeout: number | undefined;\n let launchCwd: string | undefined;\n const launchArgs: string[] = [];\n const launchEnv: Record<string, string> = {};\n const headers: Record<string, string> = {};\n\n for (let i = 2; i < args.length; i += 1) {\n const arg = args[i];\n const next = args[i + 1];\n switch (arg) {\n case '--dev':\n break;\n case '--exposure':\n case '--summary':\n case '--keyword':\n i += 1;\n break;\n case '--name':\n name = next;\n i += 1;\n break;\n case '--transport':\n if (next !== 'streamable-http' && next !== 'sse') {\n throw new Error('--transport must be streamable-http or sse');\n }\n transport = next;\n i += 1;\n break;\n case '--url':\n url = next;\n i += 1;\n break;\n case '--command':\n launchCommand = next;\n i += 1;\n break;\n case '--timeout':\n timeout = parsePositiveInteger(next, '--timeout');\n i += 1;\n break;\n case '--arg':\n launchArgs.push(next);\n i += 1;\n break;\n case '--env': {\n const [key, value] = parseKeyValue(next, '--env');\n launchEnv[key] = value;\n i += 1;\n break;\n }\n case '--cwd':\n launchCwd = next;\n i += 1;\n break;\n case '--header': {\n const [key, value] = parseKeyValue(next, '--header');\n headers[key] = value;\n i += 1;\n break;\n }\n default:\n if (\n arg.startsWith('--') &&\n ![\n '--dev',\n '--exposure',\n '--summary',\n '--keyword',\n '--name',\n '--transport',\n '--url',\n '--command',\n '--timeout',\n '--arg',\n '--env',\n '--cwd',\n '--header',\n ].includes(arg)\n ) {\n throw new Error(`Unknown argument: ${arg}`);\n }\n }\n }\n\n return {\n command: 'mcp-import',\n dev,\n ...exposure,\n ...(name ? { name } : {}),\n transport,\n url,\n launchCommand,\n ...(timeout !== undefined ? { timeout } : {}),\n launchArgs,\n launchEnv,\n launchCwd,\n headers,\n };\n }\n\n if (args[0] === 'skill' && args[1] === 'import') {\n const exposure = parseRequiredExposureArgs(args.slice(2));\n let path: string | undefined;\n let url: string | undefined;\n for (let i = 2; i < args.length; i += 1) {\n const arg = args[i];\n const next = args[i + 1];\n switch (arg) {\n case '--dev':\n break;\n case '--exposure':\n case '--summary':\n case '--keyword':\n i += 1;\n break;\n case '--path':\n path = next;\n i += 1;\n break;\n case '--url':\n url = next;\n i += 1;\n break;\n default:\n if (arg.startsWith('--') && !['--path', '--url', '--dev', '--exposure', '--summary', '--keyword'].includes(arg)) {\n throw new Error(`Unknown argument: ${arg}`);\n }\n }\n }\n return { command: 'skill-import', dev, ...exposure, path, url };\n }\n\n if (args[0] === 'app' && args[1] === 'config') {\n const localId = args[2];\n if (!localId) {\n throw new Error('Usage: aai-gateway app config <app-id>');\n }\n\n let exposure: ExposureMode | undefined;\n let summary: string | undefined;\n const keywords: string[] = [];\n\n for (let i = 3; i < args.length; i += 1) {\n const arg = args[i];\n const next = args[i + 1];\n switch (arg) {\n case '--dev':\n break;\n case '--exposure':\n if (next !== 'summary' && next !== 'keywords') {\n throw new Error('--exposure must be summary or keywords');\n }\n exposure = next;\n i += 1;\n break;\n case '--summary':\n summary = next;\n i += 1;\n break;\n case '--keyword':\n keywords.push(next);\n i += 1;\n break;\n default:\n if (arg.startsWith('--') && !['--dev', '--exposure', '--summary', '--keyword'].includes(arg)) {\n throw new Error(`Unknown argument: ${arg}`);\n }\n }\n }\n\n return {\n command: 'app-config',\n dev,\n localId,\n ...(exposure ? { exposure } : {}),\n ...(summary ? { summary } : {}),\n ...(keywords.length > 0 ? { keywords } : {}),\n };\n }\n\n return { command: 'serve', dev };\n}\n\nfunction parseRequiredExposureArgs(args: string[]): ExposureOptions {\n let exposure: ExposureMode | undefined;\n let summary: string | undefined;\n const keywords: string[] = [];\n\n for (let i = 0; i < args.length; i += 1) {\n const arg = args[i];\n const next = args[i + 1];\n switch (arg) {\n case '--exposure':\n if (next !== 'summary' && next !== 'keywords') {\n throw new Error('--exposure must be summary or keywords');\n }\n exposure = next;\n i += 1;\n break;\n case '--summary':\n summary = next;\n i += 1;\n break;\n case '--keyword':\n keywords.push(next);\n i += 1;\n break;\n default:\n break;\n }\n }\n\n if (!exposure) {\n throw new Error('Import requires --exposure summary|keywords');\n }\n\n if (!summary) {\n throw new Error(`Import requires --summary (maximum ${EXPOSURE_LIMITS.summaryLength} characters)`);\n }\n\n if (keywords.length === 0) {\n throw new Error(`Import requires at least one --keyword (maximum ${EXPOSURE_LIMITS.keywordCount} total)`);\n }\n\n return {\n exposure,\n ...normalizeExposureInput({ keywords, summary }),\n };\n}\n\nfunction printHelp(): void {\n console.log(`\nAAI Gateway\n\nUsage:\n aai-gateway [options]\n aai-gateway mcp import [options]\n aai-gateway skill import [options]\n aai-gateway app config <app-id> [options]\n\nOptions:\n --scan Scan for desktop descriptors and exit\n --dev Enable development mode\n --version Show version\n --help, -h Show help\n\nShared metadata options:\n --exposure MODE Required for import. One of: summary, keywords\n --summary TEXT Required for import, max ${EXPOSURE_LIMITS.summaryLength} characters\n --keyword VALUE Required for import and repeatable, max ${EXPOSURE_LIMITS.keywordCount} items, each max ${EXPOSURE_LIMITS.keywordLength} characters\n\nMCP import options:\n --name TEXT Optional app name used for display and app id generation, max ${IMPORT_LIMITS.nameLength} chars\n --command CMD Import a local stdio MCP server, max ${IMPORT_LIMITS.commandLength} chars\n --timeout MS Optional MCP downstream inactivity timeout in milliseconds, default 60000\n --arg VALUE Repeatable stdio argument, max ${IMPORT_LIMITS.argCount} items, each max ${IMPORT_LIMITS.argLength} chars\n --env KEY=VALUE Repeatable stdio environment variable, max ${IMPORT_LIMITS.envCount} entries\n --cwd DIR Working directory for stdio launch, max ${IMPORT_LIMITS.cwdLength} chars\n --url URL Import a remote MCP server, max ${IMPORT_LIMITS.urlLength} chars\n --transport TYPE Remote transport: streamable-http or sse\n --header KEY=VALUE Repeatable remote header stored in secure storage, max ${IMPORT_LIMITS.headerCount} entries\n\nSkill import options:\n --path DIR Import a local skill directory, max ${IMPORT_LIMITS.pathLength} chars\n --url URL Import a remote skill root URL, max ${IMPORT_LIMITS.urlLength} chars\n\nApp config options:\n --exposure MODE Optional. Update the recorded exposure mode\n --summary TEXT Optional. Override the current summary\n --keyword VALUE Optional and repeatable. Replace the current keywords\n`);\n}\n\nasync function runScan(dev: boolean): Promise<void> {\n const discovery = createDesktopDiscovery();\n const apps = await discovery.scan({ devMode: dev });\n\n if (apps.length === 0) {\n console.log('No desktop descriptors found.');\n return;\n }\n\n for (const app of apps) {\n console.log(`${app.localId}`);\n console.log(` Name: ${app.descriptor.app.name.default}`);\n console.log(` Location: ${app.location ?? '(unknown)'}`);\n console.log(` Protocol: ${app.descriptor.access.protocol}`);\n console.log(` Summary: ${app.descriptor.exposure.summary}`);\n }\n}\n\nasync function runMcpImport(options: McpImportOptions): Promise<void> {\n const storage = createSecureStorage();\n const executor = getMcpExecutor();\n validateImportHeaders(options.headers);\n const config = buildMcpImportConfig({\n transport: options.transport,\n url: options.url,\n command: options.launchCommand,\n timeout: options.timeout,\n args: options.launchArgs,\n env: options.launchEnv,\n cwd: options.launchCwd,\n });\n\n const result = await importMcpServer(executor, storage, {\n name: options.name,\n exposureMode: options.exposure,\n keywords: options.keywords,\n summary: options.summary,\n config,\n headers: options.headers,\n });\n\n console.log(`Imported MCP app: ${result.descriptor.app.name.default}`);\n console.log(`App ID: ${result.entry.localId}`);\n console.log(`Descriptor: ${result.entry.descriptorPath}`);\n console.log(`Managed directory: ${getManagedAppDir(result.entry.localId)}`);\n console.log(`Tool name after restart: app:${result.entry.localId}`);\n console.log(`Keywords: ${result.descriptor.exposure.keywords.join(', ')}`);\n console.log(`Summary: ${result.descriptor.exposure.summary}`);\n console.log(`Exposure mode: ${options.exposure}`);\n}\n\nasync function runSkillImport(options: SkillImportOptions): Promise<void> {\n const source = buildSkillImportSource({\n path: options.path,\n url: options.url,\n });\n\n const result = await importSkill({\n exposureMode: options.exposure,\n keywords: options.keywords,\n summary: options.summary,\n path: source.path,\n url: source.url,\n });\n\n console.log(`Imported skill: ${result.descriptor.app.name.default}`);\n console.log(`App ID: ${result.localId}`);\n console.log(`Descriptor: ${join(getManagedAppDir(result.localId), 'aai.json')}`);\n console.log(`Skill directory: ${result.managedPath}`);\n console.log(`Tool name after restart: app:${result.localId}`);\n console.log(`Keywords: ${result.descriptor.exposure.keywords.join(', ')}`);\n console.log(`Summary: ${result.descriptor.exposure.summary}`);\n console.log(`Exposure mode: ${options.exposure}`);\n}\n\nasync function runAppConfig(options: AppConfigOptions): Promise<void> {\n const descriptorPath = resolveManagedDescriptorPath(options.localId);\n const descriptor = JSON.parse(readFileSync(descriptorPath, 'utf-8')) as AaiJson;\n\n const nextExposure = normalizeAppConfigExposure(options, descriptor.exposure);\n const nextDescriptor: AaiJson = {\n ...descriptor,\n exposure: nextExposure,\n };\n\n if (isMcpAccess(nextDescriptor.access)) {\n await upsertMcpRegistryEntry(\n {\n localId: options.localId,\n protocol: 'mcp',\n config: nextDescriptor.access.config,\n },\n nextDescriptor\n );\n } else if (isSkillAccess(nextDescriptor.access)) {\n await upsertSkillRegistryEntry(\n {\n localId: options.localId,\n protocol: 'skill',\n config: nextDescriptor.access.config,\n },\n nextDescriptor\n );\n } else {\n throw new Error(`App '${options.localId}' is not an imported MCP app or imported skill`);\n }\n\n console.log(`Updated app: ${options.localId}`);\n if (options.exposure) {\n console.log(`Exposure mode: ${options.exposure}`);\n }\n console.log(`Keywords: ${nextDescriptor.exposure.keywords.join(', ')}`);\n console.log(`Summary: ${nextDescriptor.exposure.summary}`);\n}\n\nfunction normalizeAppConfigExposure(\n options: AppConfigOptions,\n current: AaiJson['exposure']\n): AaiJson['exposure'] {\n const summary = options.summary ?? current.summary;\n const keywords = options.keywords ?? current.keywords;\n return normalizeExposureInput({ keywords, summary });\n}\n\nfunction resolveManagedDescriptorPath(localId: string): string {\n const descriptorPath = join(getManagedAppDir(localId), 'aai.json');\n if (existsSync(descriptorPath)) {\n return descriptorPath;\n }\n\n return descriptorPath;\n}\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2);\n\n if (args.includes('--version')) {\n console.log(AAI_GATEWAY_VERSION);\n return;\n }\n\n if (args.includes('--help') || args.includes('-h')) {\n printHelp();\n return;\n }\n\n const options = parseArgs(args);\n\n switch (options.command) {\n case 'scan':\n await runScan(options.dev);\n return;\n case 'mcp-import':\n await runMcpImport(options);\n return;\n case 'skill-import':\n await runSkillImport(options);\n return;\n case 'app-config':\n await runAppConfig(options);\n return;\n case 'serve': {\n const server = await createGatewayServer({ devMode: options.dev });\n await server.start();\n return;\n }\n }\n}\n\nmain().catch((err) => {\n logger.fatal({ err }, 'Fatal error');\n process.exit(1);\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { createDesktopDiscovery } from './discovery/index.js';\nimport { getMcpExecutor } from './executors/mcp.js';\nimport {\n buildMcpImportConfig,\n buildSkillImportSource,\n type ExposureMode,\n EXPOSURE_LIMITS,\n IMPORT_LIMITS,\n importMcpServer,\n importSkill,\n normalizeExposureInput,\n validateImportHeaders,\n} from './mcp/importer.js';\nimport { createGatewayServer } from './mcp/server.js';\nimport { upsertMcpRegistryEntry } from './storage/mcp-registry.js';\nimport { getManagedAppDir } from './storage/paths.js';\nimport { createSecureStorage } from './storage/secure-storage/index.js';\nimport { upsertSkillRegistryEntry } from './storage/skill-registry.js';\nimport { isMcpAccess, isSkillAccess, type AaiJson } from './types/aai-json.js';\nimport { logger } from './utils/logger.js';\nimport { AAI_GATEWAY_VERSION } from './version.js';\n\ninterface CommonOptions {\n dev: boolean;\n}\n\ninterface ServeOptions extends CommonOptions {\n command: 'serve';\n}\n\ninterface ScanOptions extends CommonOptions {\n command: 'scan';\n}\n\ninterface ExposureOptions {\n exposure: ExposureMode;\n summary: string;\n keywords: string[];\n}\n\ninterface McpImportOptions extends CommonOptions, ExposureOptions {\n command: 'mcp-import';\n name?: string;\n transport?: 'streamable-http' | 'sse';\n url?: string;\n launchCommand?: string;\n timeout?: number;\n launchArgs: string[];\n launchEnv: Record<string, string>;\n launchCwd?: string;\n headers: Record<string, string>;\n}\n\ninterface SkillImportOptions extends CommonOptions, ExposureOptions {\n command: 'skill-import';\n path?: string;\n url?: string;\n}\n\ninterface AppConfigOptions extends CommonOptions {\n command: 'app-config';\n appId: string;\n exposure?: ExposureMode;\n keywords?: string[];\n summary?: string;\n}\n\ntype CliOptions = ServeOptions | ScanOptions | McpImportOptions | SkillImportOptions | AppConfigOptions;\n\nfunction parseKeyValue(value: string, flag: string): [string, string] {\n const index = value.indexOf('=');\n if (index === -1) {\n throw new Error(`${flag} expects KEY=VALUE`);\n }\n return [value.slice(0, index), value.slice(index + 1)];\n}\n\nfunction parsePositiveInteger(value: string, flag: string): number {\n const parsed = Number(value);\n if (!Number.isInteger(parsed) || parsed <= 0) {\n throw new Error(`${flag} expects a positive integer`);\n }\n return parsed;\n}\n\nfunction parseArgs(args: string[]): CliOptions {\n const dev = args.includes('--dev');\n if (args.includes('--scan')) {\n return { command: 'scan', dev };\n }\n\n if (args[0] === 'mcp' && args[1] === 'import') {\n const exposure = parseRequiredExposureArgs(args.slice(2));\n let name: string | undefined;\n let transport: 'streamable-http' | 'sse' | undefined;\n let url: string | undefined;\n let launchCommand: string | undefined;\n let timeout: number | undefined;\n let launchCwd: string | undefined;\n const launchArgs: string[] = [];\n const launchEnv: Record<string, string> = {};\n const headers: Record<string, string> = {};\n\n for (let i = 2; i < args.length; i += 1) {\n const arg = args[i];\n const next = args[i + 1];\n switch (arg) {\n case '--dev':\n break;\n case '--exposure':\n case '--summary':\n case '--keyword':\n i += 1;\n break;\n case '--name':\n name = next;\n i += 1;\n break;\n case '--transport':\n if (next !== 'streamable-http' && next !== 'sse') {\n throw new Error('--transport must be streamable-http or sse');\n }\n transport = next;\n i += 1;\n break;\n case '--url':\n url = next;\n i += 1;\n break;\n case '--command':\n launchCommand = next;\n i += 1;\n break;\n case '--timeout':\n timeout = parsePositiveInteger(next, '--timeout');\n i += 1;\n break;\n case '--arg':\n launchArgs.push(next);\n i += 1;\n break;\n case '--env': {\n const [key, value] = parseKeyValue(next, '--env');\n launchEnv[key] = value;\n i += 1;\n break;\n }\n case '--cwd':\n launchCwd = next;\n i += 1;\n break;\n case '--header': {\n const [key, value] = parseKeyValue(next, '--header');\n headers[key] = value;\n i += 1;\n break;\n }\n default:\n if (\n arg.startsWith('--') &&\n ![\n '--dev',\n '--exposure',\n '--summary',\n '--keyword',\n '--name',\n '--transport',\n '--url',\n '--command',\n '--timeout',\n '--arg',\n '--env',\n '--cwd',\n '--header',\n ].includes(arg)\n ) {\n throw new Error(`Unknown argument: ${arg}`);\n }\n }\n }\n\n return {\n command: 'mcp-import',\n dev,\n ...exposure,\n ...(name ? { name } : {}),\n transport,\n url,\n launchCommand,\n ...(timeout !== undefined ? { timeout } : {}),\n launchArgs,\n launchEnv,\n launchCwd,\n headers,\n };\n }\n\n if (args[0] === 'skill' && args[1] === 'import') {\n const exposure = parseRequiredExposureArgs(args.slice(2));\n let path: string | undefined;\n let url: string | undefined;\n for (let i = 2; i < args.length; i += 1) {\n const arg = args[i];\n const next = args[i + 1];\n switch (arg) {\n case '--dev':\n break;\n case '--exposure':\n case '--summary':\n case '--keyword':\n i += 1;\n break;\n case '--path':\n path = next;\n i += 1;\n break;\n case '--url':\n url = next;\n i += 1;\n break;\n default:\n if (arg.startsWith('--') && !['--path', '--url', '--dev', '--exposure', '--summary', '--keyword'].includes(arg)) {\n throw new Error(`Unknown argument: ${arg}`);\n }\n }\n }\n return { command: 'skill-import', dev, ...exposure, path, url };\n }\n\n if (args[0] === 'app' && args[1] === 'config') {\n const appId = args[2];\n if (!appId) {\n throw new Error('Usage: aai-gateway app config <app-id>');\n }\n\n let exposure: ExposureMode | undefined;\n let summary: string | undefined;\n const keywords: string[] = [];\n\n for (let i = 3; i < args.length; i += 1) {\n const arg = args[i];\n const next = args[i + 1];\n switch (arg) {\n case '--dev':\n break;\n case '--exposure':\n if (next !== 'summary' && next !== 'keywords') {\n throw new Error('--exposure must be summary or keywords');\n }\n exposure = next;\n i += 1;\n break;\n case '--summary':\n summary = next;\n i += 1;\n break;\n case '--keyword':\n keywords.push(next);\n i += 1;\n break;\n default:\n if (arg.startsWith('--') && !['--dev', '--exposure', '--summary', '--keyword'].includes(arg)) {\n throw new Error(`Unknown argument: ${arg}`);\n }\n }\n }\n\n return {\n command: 'app-config',\n dev,\n appId,\n ...(exposure ? { exposure } : {}),\n ...(summary ? { summary } : {}),\n ...(keywords.length > 0 ? { keywords } : {}),\n };\n }\n\n return { command: 'serve', dev };\n}\n\nfunction parseRequiredExposureArgs(args: string[]): ExposureOptions {\n let exposure: ExposureMode | undefined;\n let summary: string | undefined;\n const keywords: string[] = [];\n\n for (let i = 0; i < args.length; i += 1) {\n const arg = args[i];\n const next = args[i + 1];\n switch (arg) {\n case '--exposure':\n if (next !== 'summary' && next !== 'keywords') {\n throw new Error('--exposure must be summary or keywords');\n }\n exposure = next;\n i += 1;\n break;\n case '--summary':\n summary = next;\n i += 1;\n break;\n case '--keyword':\n keywords.push(next);\n i += 1;\n break;\n default:\n break;\n }\n }\n\n if (!exposure) {\n throw new Error('Import requires --exposure summary|keywords');\n }\n\n if (!summary) {\n throw new Error(`Import requires --summary (maximum ${EXPOSURE_LIMITS.summaryLength} characters)`);\n }\n\n if (keywords.length === 0) {\n throw new Error(`Import requires at least one --keyword (maximum ${EXPOSURE_LIMITS.keywordCount} total)`);\n }\n\n return {\n exposure,\n ...normalizeExposureInput({ keywords, summary }),\n };\n}\n\nfunction printHelp(): void {\n console.log(`\nAAI Gateway\n\nUsage:\n aai-gateway [options]\n aai-gateway mcp import [options]\n aai-gateway skill import [options]\n aai-gateway app config <app-id> [options]\n\nOptions:\n --scan Scan for desktop descriptors and exit\n --dev Enable development mode\n --version Show version\n --help, -h Show help\n\nShared metadata options:\n --exposure MODE Required for import. One of: summary, keywords\n --summary TEXT Required for import, max ${EXPOSURE_LIMITS.summaryLength} characters\n --keyword VALUE Required for import and repeatable, max ${EXPOSURE_LIMITS.keywordCount} items, each max ${EXPOSURE_LIMITS.keywordLength} characters\n\nMCP import options:\n --name TEXT Optional app name used for display and app id generation, max ${IMPORT_LIMITS.nameLength} chars\n --command CMD Import a local stdio MCP server, max ${IMPORT_LIMITS.commandLength} chars\n --timeout MS Optional MCP downstream inactivity timeout in milliseconds, default 60000\n --arg VALUE Repeatable stdio argument, max ${IMPORT_LIMITS.argCount} items, each max ${IMPORT_LIMITS.argLength} chars\n --env KEY=VALUE Repeatable stdio environment variable, max ${IMPORT_LIMITS.envCount} entries\n --cwd DIR Working directory for stdio launch, max ${IMPORT_LIMITS.cwdLength} chars\n --url URL Import a remote MCP server, max ${IMPORT_LIMITS.urlLength} chars\n --transport TYPE Remote transport: streamable-http or sse\n --header KEY=VALUE Repeatable remote header stored in secure storage, max ${IMPORT_LIMITS.headerCount} entries\n\nSkill import options:\n --path DIR Import a local skill directory, max ${IMPORT_LIMITS.pathLength} chars\n --url URL Import a remote skill root URL, max ${IMPORT_LIMITS.urlLength} chars\n\nApp config options:\n --exposure MODE Optional. Update the recorded exposure mode\n --summary TEXT Optional. Override the current summary\n --keyword VALUE Optional and repeatable. Replace the current keywords\n`);\n}\n\nasync function runScan(dev: boolean): Promise<void> {\n const discovery = createDesktopDiscovery();\n const apps = await discovery.scan({ devMode: dev });\n\n if (apps.length === 0) {\n console.log('No desktop descriptors found.');\n return;\n }\n\n for (const app of apps) {\n console.log(`${app.appId}`);\n console.log(` Name: ${app.descriptor.app.name.default}`);\n console.log(` Location: ${app.location ?? '(unknown)'}`);\n console.log(` Protocol: ${app.descriptor.access.protocol}`);\n console.log(` Summary: ${app.descriptor.exposure.summary}`);\n }\n}\n\nasync function runMcpImport(options: McpImportOptions): Promise<void> {\n const storage = createSecureStorage();\n const executor = getMcpExecutor();\n validateImportHeaders(options.headers);\n const config = buildMcpImportConfig({\n transport: options.transport,\n url: options.url,\n command: options.launchCommand,\n timeout: options.timeout,\n args: options.launchArgs,\n env: options.launchEnv,\n cwd: options.launchCwd,\n });\n\n const result = await importMcpServer(executor, storage, {\n name: options.name,\n exposureMode: options.exposure,\n keywords: options.keywords,\n summary: options.summary,\n config,\n headers: options.headers,\n });\n\n console.log(`Imported MCP app: ${result.descriptor.app.name.default}`);\n console.log(`App ID: ${result.entry.appId}`);\n console.log(`Descriptor: ${result.entry.descriptorPath}`);\n console.log(`Managed directory: ${getManagedAppDir(result.entry.appId)}`);\n console.log(`Tool name after restart: app:${result.entry.appId}`);\n console.log(`Keywords: ${result.descriptor.exposure.keywords.join(', ')}`);\n console.log(`Summary: ${result.descriptor.exposure.summary}`);\n console.log(`Exposure mode: ${options.exposure}`);\n}\n\nasync function runSkillImport(options: SkillImportOptions): Promise<void> {\n const source = buildSkillImportSource({\n path: options.path,\n url: options.url,\n });\n\n const result = await importSkill({\n exposureMode: options.exposure,\n keywords: options.keywords,\n summary: options.summary,\n path: source.path,\n url: source.url,\n });\n\n console.log(`Imported skill: ${result.descriptor.app.name.default}`);\n console.log(`App ID: ${result.appId}`);\n console.log(`Descriptor: ${join(getManagedAppDir(result.appId), 'aai.json')}`);\n console.log(`Skill directory: ${result.managedPath}`);\n console.log(`Tool name after restart: app:${result.appId}`);\n console.log(`Keywords: ${result.descriptor.exposure.keywords.join(', ')}`);\n console.log(`Summary: ${result.descriptor.exposure.summary}`);\n console.log(`Exposure mode: ${options.exposure}`);\n}\n\nasync function runAppConfig(options: AppConfigOptions): Promise<void> {\n const descriptorPath = resolveManagedDescriptorPath(options.appId);\n const descriptor = JSON.parse(readFileSync(descriptorPath, 'utf-8')) as AaiJson;\n\n const nextExposure = normalizeAppConfigExposure(options, descriptor.exposure);\n const nextDescriptor: AaiJson = {\n ...descriptor,\n exposure: nextExposure,\n };\n\n if (isMcpAccess(nextDescriptor.access)) {\n await upsertMcpRegistryEntry(\n {\n appId: options.appId,\n protocol: 'mcp',\n config: nextDescriptor.access.config,\n },\n nextDescriptor\n );\n } else if (isSkillAccess(nextDescriptor.access)) {\n await upsertSkillRegistryEntry(\n {\n appId: options.appId,\n protocol: 'skill',\n config: nextDescriptor.access.config,\n },\n nextDescriptor\n );\n } else {\n throw new Error(`App '${options.appId}' is not an imported MCP app or imported skill`);\n }\n\n console.log(`Updated app: ${options.appId}`);\n if (options.exposure) {\n console.log(`Exposure mode: ${options.exposure}`);\n }\n console.log(`Keywords: ${nextDescriptor.exposure.keywords.join(', ')}`);\n console.log(`Summary: ${nextDescriptor.exposure.summary}`);\n}\n\nfunction normalizeAppConfigExposure(\n options: AppConfigOptions,\n current: AaiJson['exposure']\n): AaiJson['exposure'] {\n const summary = options.summary ?? current.summary;\n const keywords = options.keywords ?? current.keywords;\n return normalizeExposureInput({ keywords, summary });\n}\n\nfunction resolveManagedDescriptorPath(appId: string): string {\n const descriptorPath = join(getManagedAppDir(appId), 'aai.json');\n if (existsSync(descriptorPath)) {\n return descriptorPath;\n }\n\n return descriptorPath;\n}\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2);\n\n if (args.includes('--version')) {\n console.log(AAI_GATEWAY_VERSION);\n return;\n }\n\n if (args.includes('--help') || args.includes('-h')) {\n printHelp();\n return;\n }\n\n const options = parseArgs(args);\n\n switch (options.command) {\n case 'scan':\n await runScan(options.dev);\n return;\n case 'mcp-import':\n await runMcpImport(options);\n return;\n case 'skill-import':\n await runSkillImport(options);\n return;\n case 'app-config':\n await runAppConfig(options);\n return;\n case 'serve': {\n const server = await createGatewayServer({ devMode: options.dev });\n await server.start();\n return;\n }\n }\n}\n\nmain().catch((err) => {\n logger.fatal({ err }, 'Fatal error');\n process.exit(1);\n});\n"],"mappings":"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"}