@zleap-ai/sag-cli 0.2.1 → 0.3.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.en.md CHANGED
@@ -99,6 +99,20 @@ sag search "how to wire MCP" --source <source-id> --top-k 5
99
99
 
100
100
  A profile stores only `scheme://host[:port]`. The `/api/v1` prefix is added by the CLI — do not put it in the URL yourself.
101
101
 
102
+ ## Use with the Skill
103
+
104
+ The Skill files (`skill/`) ship alongside this CLI. Copy them into your Agent's skills directory:
105
+
106
+ ```bash
107
+ # Claude Code
108
+ SKILL_SRC="$(npm root -g)/@zleap-ai/sag-cli"
109
+ cp -r "$SKILL_SRC/skill" ~/.claude/skills/sag-knowledge
110
+
111
+ # Codex
112
+ SKILL_SRC="$(npm root -g)/@zleap-ai/sag-cli"
113
+ cp -r "$SKILL_SRC/skill" ~/.codex/skills/sag-knowledge
114
+ ```
115
+
102
116
  ## Command reference
103
117
 
104
118
  ```text
@@ -184,10 +198,17 @@ Tokens never appear in JSON, logs, or error output. Full error and exit codes li
184
198
 
185
199
  ## Further reading
186
200
 
201
+ ### Using the CLI
202
+
203
+ - [CHANGELOG.md](CHANGELOG.md) — release notes
187
204
  - Architecture and exit codes: [English](docs/architecture.md) / [简体中文](docs/architecture.zh-CN.md)
205
+ - [SKILL.md](SKILL.md) — teaches an Agent how to explore SAG efficiently
206
+
207
+ ### Developing and contributing
208
+
188
209
  - Local development and debugging: [English](docs/development.md) / [简体中文](docs/development.zh-CN.md)
189
210
  - Compatibility matrix: [English](docs/compatibility.md) / [简体中文](docs/compatibility.zh-CN.md)
190
211
  - OAuth and MCP authorization: [English](docs/oauth.md) / [简体中文](docs/oauth.zh-CN.md)
191
212
  - Release process: [English](docs/release.md) / [简体中文](docs/release.zh-CN.md)
192
- - Contributing: [CONTRIBUTING.md](CONTRIBUTING.md)
193
- - Changelog: [CHANGELOG.md](CHANGELOG.md)
213
+ - [CONTRIBUTING.md](CONTRIBUTING.md)
214
+ - [SECURITY.md](SECURITY.md)
package/README.md CHANGED
@@ -99,6 +99,20 @@ sag search "MCP 如何接入" --source <source-id> --top-k 5
99
99
 
100
100
  Profile URL 只保存 `scheme://host[:port]`。API 前缀 `/api/v1` 由 CLI 自动补齐,不要自己写。
101
101
 
102
+ ## 搭配 Skill 使用
103
+
104
+ Skill 文件(`skill/`)随本 CLI 一起发布。安装 CLI 后复制到 Agent 的 skills 目录:
105
+
106
+ ```bash
107
+ # Claude Code
108
+ SKILL_SRC="$(npm root -g)/@zleap-ai/sag-cli"
109
+ cp -r "$SKILL_SRC/skill" ~/.claude/skills/sag-knowledge
110
+
111
+ # Codex
112
+ SKILL_SRC="$(npm root -g)/@zleap-ai/sag-cli"
113
+ cp -r "$SKILL_SRC/skill" ~/.codex/skills/sag-knowledge
114
+ ```
115
+
102
116
  ## 命令一览
103
117
 
104
118
  ```text
@@ -184,10 +198,17 @@ Token 不会出现在 JSON、日志或错误输出里。完整错误码与退出
184
198
 
185
199
  ## 深入阅读
186
200
 
201
+ ### 使用指南
202
+
203
+ - [CHANGELOG.md](CHANGELOG.md) — 版本变更记录
187
204
  - 架构与退出码:[中文](docs/architecture.zh-CN.md) / [English](docs/architecture.md)
205
+ - [SKILL.md](SKILL.md) — 教 Agent 怎么高效探索 SAG 知识库
206
+
207
+ ### 开发与贡献
208
+
188
209
  - 本地开发与调试:[中文](docs/development.zh-CN.md) / [English](docs/development.md)
189
210
  - 兼容性矩阵:[中文](docs/compatibility.zh-CN.md) / [English](docs/compatibility.md)
190
211
  - OAuth 与 MCP 授权:[中文](docs/oauth.zh-CN.md) / [English](docs/oauth.md)
191
212
  - 发布流程:[中文](docs/release.zh-CN.md) / [English](docs/release.md)
192
- - 贡献指南:[CONTRIBUTING.md](CONTRIBUTING.md)
193
- - 变更记录:[CHANGELOG.md](CHANGELOG.md)
213
+ - [CONTRIBUTING.md](CONTRIBUTING.md)
214
+ - [SECURITY.md](SECURITY.md)
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import { confirm, password, select } from "@inquirer/prompts";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@zleap-ai/sag-cli",
9
- version: "0.2.1",
9
+ version: "0.3.0",
10
10
  description: "Command-line client and diagnostics for SAG knowledge bases",
11
11
  type: "module",
12
12
  bin: {
@@ -16,7 +16,8 @@ var package_default = {
16
16
  "dist",
17
17
  "README.md",
18
18
  "README.en.md",
19
- "LICENSE"
19
+ "LICENSE",
20
+ "skill"
20
21
  ],
21
22
  scripts: {
22
23
  build: "tsup",
@@ -1354,6 +1355,43 @@ var mcpDescriptorSchema = z6.object({
1354
1355
  headers: z6.record(z6.string(), z6.string())
1355
1356
  }).passthrough()
1356
1357
  }).passthrough();
1358
+ var outlineItemSchema = z6.object({
1359
+ rank: z6.number().int(),
1360
+ heading: z6.string(),
1361
+ chunk_id: z6.string()
1362
+ }).passthrough();
1363
+ var outlineSchema = z6.object({
1364
+ document_id: z6.string(),
1365
+ filename: z6.string(),
1366
+ outline: z6.array(outlineItemSchema)
1367
+ }).passthrough();
1368
+ var grepMatchSchema = z6.object({
1369
+ chunk_id: z6.string(),
1370
+ heading: z6.string(),
1371
+ snippet: z6.string()
1372
+ }).passthrough();
1373
+ var grepResponseSchema = z6.object({
1374
+ pattern: z6.string(),
1375
+ matches: z6.array(grepMatchSchema),
1376
+ count: z6.number().int().nonnegative()
1377
+ }).passthrough();
1378
+ var readResponseSchema = z6.object({
1379
+ document_id: z6.string(),
1380
+ filename: z6.string(),
1381
+ total_lines: z6.number().int().nonnegative(),
1382
+ offset: z6.number().int().positive(),
1383
+ limit: z6.number().int().positive(),
1384
+ lines: z6.array(z6.string())
1385
+ }).passthrough();
1386
+ var entityContextSchema = z6.object({
1387
+ entity_id: z6.string(),
1388
+ name: z6.string(),
1389
+ type: z6.string(),
1390
+ description: z6.string(),
1391
+ context: z6.string(),
1392
+ source_id: z6.string(),
1393
+ source_name: z6.string()
1394
+ }).passthrough();
1357
1395
 
1358
1396
  // src/api/client.ts
1359
1397
  var SagClient = class {
@@ -1415,6 +1453,42 @@ var SagClient = class {
1415
1453
  body: input
1416
1454
  });
1417
1455
  }
1456
+ outline(sourceId, documentId) {
1457
+ const params = new URLSearchParams({ document_id: documentId });
1458
+ return this.#request(
1459
+ `/api/v1/sources/${encodeURIComponent(sourceId)}/outline?${params}`,
1460
+ outlineSchema
1461
+ );
1462
+ }
1463
+ grep(sourceId, pattern, limit) {
1464
+ const params = new URLSearchParams({ pattern });
1465
+ if (limit !== void 0) {
1466
+ params.set("limit", String(limit));
1467
+ }
1468
+ return this.#request(
1469
+ `/api/v1/sources/${encodeURIComponent(sourceId)}/grep?${params}`,
1470
+ grepResponseSchema
1471
+ );
1472
+ }
1473
+ readDocument(sourceId, documentId, offset, limit) {
1474
+ const params = new URLSearchParams();
1475
+ if (offset !== void 0) {
1476
+ params.set("offset", String(offset));
1477
+ }
1478
+ if (limit !== void 0) {
1479
+ params.set("limit", String(limit));
1480
+ }
1481
+ return this.#request(
1482
+ `/api/v1/sources/${encodeURIComponent(sourceId)}/documents/${encodeURIComponent(documentId)}/read?${params}`,
1483
+ readResponseSchema
1484
+ );
1485
+ }
1486
+ getEntityContext(sourceId, name) {
1487
+ return this.#request(
1488
+ `/api/v1/sources/${encodeURIComponent(sourceId)}/entities/${encodeURIComponent(name)}/context`,
1489
+ entityContextSchema
1490
+ );
1491
+ }
1418
1492
  async #request(path5, schema, options = {}) {
1419
1493
  const authenticated = options.authenticated ?? true;
1420
1494
  if (authenticated && !this.#token) {
@@ -2133,6 +2207,44 @@ async function documentStatus(client, sourceId, documentId) {
2133
2207
  };
2134
2208
  }
2135
2209
 
2210
+ // src/commands/knowledge.ts
2211
+ function requiredIdentifier2(value, label) {
2212
+ const normalized = value.trim();
2213
+ if (!normalized) {
2214
+ throw new CliError("INVALID_ARGUMENT", `${label} is required`, {
2215
+ exitCode: exitCodes.invalidArgument
2216
+ });
2217
+ }
2218
+ return normalized;
2219
+ }
2220
+ async function outline(client, sourceId, documentId) {
2221
+ return client.outline(
2222
+ requiredIdentifier2(sourceId, "Source ID"),
2223
+ requiredIdentifier2(documentId, "Document ID")
2224
+ );
2225
+ }
2226
+ async function grep(client, sourceId, pattern, limit) {
2227
+ return client.grep(
2228
+ requiredIdentifier2(sourceId, "Source ID"),
2229
+ requiredIdentifier2(pattern, "Pattern"),
2230
+ limit
2231
+ );
2232
+ }
2233
+ async function read(client, sourceId, documentId, offset, limit) {
2234
+ return client.readDocument(
2235
+ requiredIdentifier2(sourceId, "Source ID"),
2236
+ requiredIdentifier2(documentId, "Document ID"),
2237
+ offset,
2238
+ limit
2239
+ );
2240
+ }
2241
+ async function getEntityContext(client, sourceId, name) {
2242
+ return client.getEntityContext(
2243
+ requiredIdentifier2(sourceId, "Source ID"),
2244
+ requiredIdentifier2(name, "Entity name")
2245
+ );
2246
+ }
2247
+
2136
2248
  // src/commands/mcp.ts
2137
2249
  async function testLocalMcp(runtime2, input) {
2138
2250
  const container = await discoverSagContainer({
@@ -2681,6 +2793,45 @@ function renderLocalMcp(value) {
2681
2793
  ].join("\n")}
2682
2794
  `;
2683
2795
  }
2796
+ function renderOutline(value) {
2797
+ const filename = typeof value.filename === "string" ? value.filename : "Unknown";
2798
+ const items = Array.isArray(value.outline) ? value.outline.filter(isRecord) : [];
2799
+ const lines = items.map(
2800
+ (item) => `${String(item.rank ?? 0).padStart(3)}. ${item.heading || "(untitled)"} (chunk_id=${item.chunk_id})`
2801
+ );
2802
+ return `${filename}
2803
+ ${lines.join("\n")}
2804
+ `;
2805
+ }
2806
+ function renderGrep(value) {
2807
+ const matches = Array.isArray(value.matches) ? value.matches.filter(isRecord) : [];
2808
+ const lines = matches.map(
2809
+ (match, index) => `[${index + 1}] ${match.heading || "Match"}
2810
+ ${match.snippet}`
2811
+ );
2812
+ return lines.length ? `${lines.join("\n\n")}
2813
+ ` : "No matches.\n";
2814
+ }
2815
+ function renderRead(value) {
2816
+ const filename = typeof value.filename === "string" ? value.filename : "Unknown";
2817
+ const total = typeof value.total_lines === "number" ? value.total_lines : 0;
2818
+ const offset = typeof value.offset === "number" ? value.offset : 1;
2819
+ const lines = Array.isArray(value.lines) ? value.lines.map(String) : [];
2820
+ const header = `${filename} \xB7 lines ${offset}-${offset + lines.length - 1} / ${total}`;
2821
+ return `${header}
2822
+ ${lines.join("")}`;
2823
+ }
2824
+ function renderEntityContext(value) {
2825
+ const name = typeof value.name === "string" ? value.name : "Unknown";
2826
+ const type = typeof value.type === "string" ? value.type : "";
2827
+ const source = typeof value.source_name === "string" ? value.source_name : "";
2828
+ const context = typeof value.context === "string" ? value.context : "";
2829
+ return `Entity: ${name}${type ? ` (${type})` : ""}
2830
+ Source: ${source}
2831
+
2832
+ ${context}
2833
+ `;
2834
+ }
2684
2835
  function renderHuman(value, quiet = false) {
2685
2836
  if (quiet) {
2686
2837
  if (Array.isArray(value)) {
@@ -2703,6 +2854,18 @@ function renderHuman(value, quiet = false) {
2703
2854
  if (isRecord(value) && Array.isArray(value.sections) && "query" in value) {
2704
2855
  return renderSearch(value);
2705
2856
  }
2857
+ if (isRecord(value) && Array.isArray(value.outline) && "filename" in value) {
2858
+ return renderOutline(value);
2859
+ }
2860
+ if (isRecord(value) && Array.isArray(value.matches) && "pattern" in value) {
2861
+ return renderGrep(value);
2862
+ }
2863
+ if (isRecord(value) && Array.isArray(value.lines) && "total_lines" in value) {
2864
+ return renderRead(value);
2865
+ }
2866
+ if (isRecord(value) && "entity_id" in value && "context" in value) {
2867
+ return renderEntityContext(value);
2868
+ }
2706
2869
  if (isRecord(value) && value.provider === "docker-stdio" && "protocolVersion" in value && "container" in value) {
2707
2870
  return renderLocalMcp(value);
2708
2871
  }
@@ -2946,6 +3109,46 @@ function addKnowledgeCommands(program, dependencies2) {
2946
3109
  );
2947
3110
  }
2948
3111
  );
3112
+ program.command("outline").argument("<document-id>").requiredOption("--source <source-id>").action(async (documentId, options) => {
3113
+ const context = await runtime(program, dependencies2);
3114
+ emit(
3115
+ program,
3116
+ dependencies2,
3117
+ await outline(context.client, options.source, documentId)
3118
+ );
3119
+ });
3120
+ program.command("grep").argument("<pattern>").requiredOption("--source <source-id>").option("--limit <number>", "Maximum result count", parseInteger).action(async (pattern, options) => {
3121
+ const context = await runtime(program, dependencies2);
3122
+ emit(
3123
+ program,
3124
+ dependencies2,
3125
+ await grep(context.client, options.source, pattern, options.limit)
3126
+ );
3127
+ });
3128
+ program.command("read").argument("<document-id>").requiredOption("--source <source-id>").option("--offset <number>", "Starting line (1-based)", parseInteger).option("--limit <number>", "Lines to read", parseInteger).action(
3129
+ async (documentId, options) => {
3130
+ const context = await runtime(program, dependencies2);
3131
+ emit(
3132
+ program,
3133
+ dependencies2,
3134
+ await read(
3135
+ context.client,
3136
+ options.source,
3137
+ documentId,
3138
+ options.offset,
3139
+ options.limit
3140
+ )
3141
+ );
3142
+ }
3143
+ );
3144
+ program.command("get-entity").argument("<name>").requiredOption("--source <source-id>").action(async (name, options) => {
3145
+ const context = await runtime(program, dependencies2);
3146
+ emit(
3147
+ program,
3148
+ dependencies2,
3149
+ await getEntityContext(context.client, options.source, name)
3150
+ );
3151
+ });
2949
3152
  }
2950
3153
  function addLocalMcpCommands(program, dependencies2) {
2951
3154
  const mcp = program.command("mcp").description("Verify local SAG MCP");