@thinkingai/ae-cli 1.0.24 → 1.0.27
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 +17 -2
- package/README.zh.md +18 -2
- package/dist/{auth-4OPL4YWG.js → auth-TXFJHPXU.js} +3 -2
- package/dist/{auth-PBPAZQWP.js → auth-W2JZ3DKA.js} +7 -5
- package/dist/{chunk-OVHL4JAC.js → chunk-2NGWYMLB.js} +5 -3
- package/dist/chunk-6T7245YE.js +478 -0
- package/dist/{chunk-JJCRURTR.js → chunk-B4UL2VIJ.js} +1 -1
- package/dist/{chunk-DC66L5AM.js → chunk-BI5ZVD6T.js} +17 -90
- package/dist/chunk-MNHE6SSI.js +79 -0
- package/dist/chunk-MWWYGZ76.js +130 -0
- package/dist/{chunk-47MTN54I.js → chunk-SRRUNQIM.js} +29 -2
- package/dist/chunk-TG64AQV4.js +262 -0
- package/dist/{chunk-MZW6NOVS.js → chunk-VT6VC4PA.js} +9 -3
- package/dist/{client-TDH6MUWM.js → client-IU2E5IOL.js} +6 -3
- package/dist/{config-5ELRYAIH.js → config-WH3R3IRY.js} +3 -2
- package/dist/index.js +45 -17
- package/dist/model-CC7U5XOJ.js +131 -0
- package/dist/{raw-EM7AP3RT.js → raw-TUHZQ4UB.js} +5 -4
- package/dist/sync-EVEDS7YE.js +383 -0
- package/dist/te-agent-FLTDYV3L.js +522 -0
- package/dist/{te-analysis-3KFSNUCJ.js → te-analysis-CMXADIJM.js} +81 -11
- package/dist/{te-audience-GMYJFCSC.js → te-audience-LPZFFSXM.js} +4 -3
- package/dist/{te-common-YLERCMQI.js → te-common-BXQFKTB3.js} +4 -3
- package/dist/{te-community-PYDN5A6B.js → te-community-K5S3VOZY.js} +4 -3
- package/dist/{te-dataops-ETWS5O3X.js → te-dataops-567IMCQG.js} +4 -3
- package/dist/{te-engage-ROJVZHDA.js → te-engage-TBUTGW5F.js} +105 -49
- package/dist/{te-kb-VUNS2CC7.js → te-kb-XHVBWP5E.js} +225 -29
- package/dist/{te-meta-3EFWLM3A.js → te-meta-NQ4U3VJW.js} +4 -3
- package/dist/te-team-556APRIU.js +529 -0
- package/package.json +5 -1
- package/skills/ae-agent/SKILL.md +133 -0
- package/skills/ae-analysis/references/drilldown_user_events.md +1 -1
- package/skills/ae-analysis/references/drilldown_users.md +2 -2
- package/skills/ae-analysis/references/query_adhoc.md +3 -2
- package/skills/ae-analysis/references/query_entity_details.md +1 -1
- package/skills/ae-engage/SKILL.md +25 -2
- package/skills/ae-engage/references/build-task-save-guide.md +283 -0
- package/skills/ae-engage/references/save-task.md +304 -0
- package/skills/ae-kb/SKILL.md +283 -0
- package/skills/ae-team/SKILL.md +164 -0
- package/skills/ae-team/references/ai-generate.md +39 -0
- package/skills/ae-team/references/create.md +94 -0
- package/skills/ae-team/references/delete.md +39 -0
- package/skills/ae-team/references/list-projects.md +45 -0
- package/skills/ae-team/references/list-templates.md +38 -0
- package/skills/ae-team/references/list.md +39 -0
- package/skills/ae-team/references/run-artifacts.md +51 -0
- package/skills/ae-team/references/run-cancel.md +38 -0
- package/skills/ae-team/references/run-chat.md +57 -0
- package/skills/ae-team/references/run-reply.md +41 -0
- package/skills/ae-team/references/run-result.md +75 -0
- package/skills/ae-team/references/run-start.md +73 -0
- package/skills/ae-team/references/run-watch.md +82 -0
- package/skills/ae-team/references/update.md +47 -0
package/README.md
CHANGED
|
@@ -68,6 +68,13 @@ ae-cli kb +schema --name engineering-handbook
|
|
|
68
68
|
ae-cli kb +compile --name engineering-handbook
|
|
69
69
|
ae-cli kb +query -q "How to configure sandbox?" \
|
|
70
70
|
--sources '[{"scope":"company","name":"engineering-handbook"}]'
|
|
71
|
+
|
|
72
|
+
# Retrieval primitives for external agents (read index → grep → read page)
|
|
73
|
+
ae-cli kb +index --sources '[{"scope":"company","name":"engineering-handbook"}]'
|
|
74
|
+
ae-cli kb +grep -q "sandbox config" \
|
|
75
|
+
--sources '[{"scope":"company","name":"engineering-handbook"}]'
|
|
76
|
+
ae-cli kb +read --source '{"scope":"company","name":"engineering-handbook"}' \
|
|
77
|
+
--path "wiki/sandbox.md"
|
|
71
78
|
```
|
|
72
79
|
|
|
73
80
|
## Authentication
|
|
@@ -105,21 +112,29 @@ ae-cli auth logout
|
|
|
105
112
|
| `dataops_integration` | 20+ | Data integration: datasource management, sync solutions, data synchronization |
|
|
106
113
|
| `community` | 10+ | Community analysis: posts search, sentiment analysis, topic trends, livestream data |
|
|
107
114
|
| `analysis_common` | 2 | Cross-module common constraints: resource link completion, project ID gate |
|
|
108
|
-
| `
|
|
115
|
+
| `team` | 14 | AI Agent Team: manage teams (list/create/update/delete/ai-generate/templates/projects) and execute TeamRuns (start/watch/chat/reply/cancel/result/artifacts) |
|
|
116
|
+
| `kb` | 10 | Knowledge base lifecycle: query / new / add (md/dir/url) / schema / compile / rm-source / remove; retrieval primitives: index / grep / read |
|
|
109
117
|
| `auth` / `config` | 2 | Authentication and host configuration |
|
|
110
118
|
|
|
111
119
|
### kb
|
|
112
120
|
|
|
113
|
-
Knowledge base lifecycle (`+new` → `+add` → `+schema` → `+compile` → `+query`):
|
|
121
|
+
Knowledge base lifecycle (`+new` → `+add` → `+schema` → `+compile` → `+status` → `+query`):
|
|
114
122
|
|
|
115
123
|
- **Create** (`+new`): `--scope personal|company`, `--name`, optional `--description`, `--tags`, `--project-id`, `--project-name`
|
|
116
124
|
- **Upload** (`+add`): `--name`, `--files` JSON array (`.md` file, directory non-recursive scan, or http(s) URL with HTML-to-Markdown conversion)
|
|
117
125
|
- **Schema** (`+schema`): `--name`, optional `--force`, `--model`
|
|
118
126
|
- **Compile** (`+compile`): `--name`, `--mode incremental|full` (default: incremental)
|
|
127
|
+
- **Status** (`+status`): `--name`
|
|
119
128
|
- **Query** (`+query`): `--query` / `-q`, `--sources` JSON refs e.g. `[{"scope":"company","name":"engineering-handbook"}]`
|
|
120
129
|
- **Remove source** (`+rm-source`): `--name`, `--display-name`
|
|
121
130
|
- **Remove KB** (`+remove`): `--name`
|
|
122
131
|
|
|
132
|
+
Retrieval primitives for external agents (`+index` → `+grep` → `+read`); these are deterministic file-search endpoints with no server-side LLM, designed for agents (Claude Code / Codex / Cursor) to explore a KB the way they explore a codebase:
|
|
133
|
+
|
|
134
|
+
- **Index** (`+index`): optional `--sources` JSON refs (omit to list all accessible KBs), optional `--locale`. Returns each KB's metadata plus its `index.md` navigation map.
|
|
135
|
+
- **Grep** (`+grep`): `--query` / `-q`, optional `--sources`, `--top-k` (1-50, default 10), `--locale`. Returns matched lines with path, line number, breadcrumb and context snippet.
|
|
136
|
+
- **Read** (`+read`): `--source` JSON ref (exactly one KB), `--path` (page path relative to the KB root), optional `--offset` / `--limit` (line window), `--locale`. Returns the full page or a line window.
|
|
137
|
+
|
|
123
138
|
### Global Options
|
|
124
139
|
|
|
125
140
|
| Option | Description | Default |
|
package/README.zh.md
CHANGED
|
@@ -68,6 +68,13 @@ ae-cli kb +schema --name engineering-handbook
|
|
|
68
68
|
ae-cli kb +compile --name engineering-handbook
|
|
69
69
|
ae-cli kb +query -q "如何配置 sandbox 容器?" \
|
|
70
70
|
--sources '[{"scope":"company","name":"engineering-handbook"}]'
|
|
71
|
+
|
|
72
|
+
# 面向外部 agent 的检索原语(读 index → grep 定位 → read 翻页)
|
|
73
|
+
ae-cli kb +index --sources '[{"scope":"company","name":"engineering-handbook"}]'
|
|
74
|
+
ae-cli kb +grep -q "sandbox 配置" \
|
|
75
|
+
--sources '[{"scope":"company","name":"engineering-handbook"}]'
|
|
76
|
+
ae-cli kb +read --source '{"scope":"company","name":"engineering-handbook"}' \
|
|
77
|
+
--path "wiki/sandbox.md"
|
|
71
78
|
```
|
|
72
79
|
|
|
73
80
|
## 身份验证
|
|
@@ -105,21 +112,30 @@ ae-cli auth logout
|
|
|
105
112
|
| `dataops_integration` | 20+ | 数据集成:数据源管理、同步方案、数据同步 |
|
|
106
113
|
| `community` | 10+ | 社区分析:帖子搜索、情感分析、话题趋势、直播数据 |
|
|
107
114
|
| `analysis_common` | 2 | 跨模块通用约束:资源链接补全、项目 ID 门控 |
|
|
108
|
-
| `
|
|
115
|
+
| `agent` | 15 | Agent 平台资源管理:模型、MCP 服务、Skill、附件库的增删查和启停 |
|
|
116
|
+
| `team` | 14 | AI Agent Team:团队管理(列表/创建/更新/删除/AI生成/模板/项目)和 TeamRun 执行(启动/监听/对话/回复/取消/结果/产物) |
|
|
117
|
+
| `kb` | 10 | 知识库生命周期:查询 / 新建 / 添加源(md/目录/url)/ schema / 编译 / 删除源 / 删除;检索原语:index / grep / read |
|
|
109
118
|
| `auth` / `config` | 2 | 身份验证和主机配置 |
|
|
110
119
|
|
|
111
120
|
### kb(知识库)
|
|
112
121
|
|
|
113
|
-
知识库生命周期(`+new` → `+add` → `+schema` → `+compile` → `+query`):
|
|
122
|
+
知识库生命周期(`+new` → `+add` → `+schema` → `+compile` → `+status` → `+query`):
|
|
114
123
|
|
|
115
124
|
- **新建** (`+new`):`--scope personal|company`,`--name`,可选 `--description`、`--tags`、`--project-id`、`--project-name`
|
|
116
125
|
- **上传源** (`+add`):`--name`,`--files` JSON 数组(`.md` 文件、目录非递归扫描、或 http(s) URL 自动转 Markdown)
|
|
117
126
|
- **生成 schema** (`+schema`):`--name`,可选 `--force`、`--model`
|
|
118
127
|
- **编译** (`+compile`):`--name`,`--mode incremental|full`(默认 incremental)
|
|
128
|
+
- **查询状态** (`+status`):`--name`
|
|
119
129
|
- **查询** (`+query`):`--query` / `-q`,`--sources` JSON 引用,如 `[{"scope":"company","name":"engineering-handbook"}]`
|
|
120
130
|
- **删除源** (`+rm-source`):`--name`,`--display-name`
|
|
121
131
|
- **删除知识库** (`+remove`):`--name`
|
|
122
132
|
|
|
133
|
+
面向外部 agent 的检索原语(`+index` → `+grep` → `+read`);均为确定性文件检索端点、服务端零 LLM 调用,让 agent(Claude Code / Codex / Cursor)像翻代码库一样探索知识库:
|
|
134
|
+
|
|
135
|
+
- **索引** (`+index`):可选 `--sources` JSON 引用(省略则列出全部可访问知识库),可选 `--locale`。返回每个知识库的元信息及其 `index.md` 导航地图。
|
|
136
|
+
- **检索** (`+grep`):`--query` / `-q`,可选 `--sources`、`--top-k`(1-50,默认 10)、`--locale`。返回命中行的路径、行号、面包屑与上下文片段。
|
|
137
|
+
- **读取** (`+read`):`--source` JSON 引用(精确指向一个知识库),`--path`(相对知识库根目录的页面路径),可选 `--offset` / `--limit`(行窗口)、`--locale`。返回页面全文或行窗口。
|
|
138
|
+
|
|
123
139
|
### 全局选项
|
|
124
140
|
|
|
125
141
|
| 选项 | 描述 | 默认值 |
|
|
@@ -7,8 +7,9 @@ import {
|
|
|
7
7
|
saveToken,
|
|
8
8
|
setTokenManual,
|
|
9
9
|
validateToken
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-SRRUNQIM.js";
|
|
11
|
+
import "./chunk-BI5ZVD6T.js";
|
|
12
|
+
import "./chunk-MNHE6SSI.js";
|
|
12
13
|
export {
|
|
13
14
|
clearToken,
|
|
14
15
|
getAuthStatus,
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
printError,
|
|
3
3
|
printOutput
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-B4UL2VIJ.js";
|
|
5
5
|
import {
|
|
6
6
|
clearMcpToken,
|
|
7
7
|
loadMcpTokenStore,
|
|
8
8
|
setMcpTokenManual,
|
|
9
9
|
validateMcpToken
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-2NGWYMLB.js";
|
|
11
11
|
import {
|
|
12
12
|
clearToken,
|
|
13
13
|
getAuthStatus,
|
|
@@ -15,12 +15,14 @@ import {
|
|
|
15
15
|
resolveHost,
|
|
16
16
|
setTokenManual,
|
|
17
17
|
validateToken
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-SRRUNQIM.js";
|
|
19
19
|
import {
|
|
20
20
|
loadConfig,
|
|
21
|
-
logger,
|
|
22
21
|
saveConfig
|
|
23
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-BI5ZVD6T.js";
|
|
23
|
+
import {
|
|
24
|
+
logger
|
|
25
|
+
} from "./chunk-MNHE6SSI.js";
|
|
24
26
|
|
|
25
27
|
// src/commands/auth.ts
|
|
26
28
|
function registerAuth(program) {
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getToken
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-SRRUNQIM.js";
|
|
4
4
|
import {
|
|
5
5
|
forceMigrateFromFallback,
|
|
6
6
|
getActiveHost,
|
|
7
7
|
getConfigDir,
|
|
8
|
-
logger,
|
|
9
8
|
safeJsonParse,
|
|
10
9
|
safeReadJsonFile
|
|
11
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-BI5ZVD6T.js";
|
|
11
|
+
import {
|
|
12
|
+
logger
|
|
13
|
+
} from "./chunk-MNHE6SSI.js";
|
|
12
14
|
|
|
13
15
|
// src/core/mcp.ts
|
|
14
16
|
import fs from "fs";
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
// src/core/multiselect.ts
|
|
2
|
+
var MultiselectCancelled = class extends Error {
|
|
3
|
+
constructor() {
|
|
4
|
+
super("\u7528\u6237\u53D6\u6D88\u9009\u62E9");
|
|
5
|
+
this.name = "MultiselectCancelled";
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
function buildRows(items) {
|
|
9
|
+
const rows = [];
|
|
10
|
+
let lastGroup;
|
|
11
|
+
for (let i = 0; i < items.length; i++) {
|
|
12
|
+
const it = items[i];
|
|
13
|
+
if (it.group && it.group !== lastGroup) {
|
|
14
|
+
rows.push({ kind: "header", groupLabel: it.group });
|
|
15
|
+
lastGroup = it.group;
|
|
16
|
+
}
|
|
17
|
+
rows.push({ kind: "item", itemIndex: i, item: it });
|
|
18
|
+
}
|
|
19
|
+
return rows;
|
|
20
|
+
}
|
|
21
|
+
function promptMultiselect(opts) {
|
|
22
|
+
const { title, items } = opts;
|
|
23
|
+
if (items.length === 0) {
|
|
24
|
+
return Promise.resolve([]);
|
|
25
|
+
}
|
|
26
|
+
const stderr = process.stderr;
|
|
27
|
+
const stdin = process.stdin;
|
|
28
|
+
if (!stdin.isTTY) {
|
|
29
|
+
return Promise.reject(
|
|
30
|
+
new Error("ae-cli sync \u9700\u8981 TTY \u624D\u80FD\u8FDB\u884C\u591A\u9009\uFF1B\u8BF7\u5728\u4EA4\u4E92\u5F0F\u7EC8\u7AEF\u4E2D\u8FD0\u884C")
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
const rows = buildRows(items);
|
|
34
|
+
const itemRowIndices = [];
|
|
35
|
+
rows.forEach((r, idx) => {
|
|
36
|
+
if (r.kind === "item") itemRowIndices.push(idx);
|
|
37
|
+
});
|
|
38
|
+
const selected = /* @__PURE__ */ new Set();
|
|
39
|
+
items.forEach((it, idx) => {
|
|
40
|
+
if (it.preselected) selected.add(idx);
|
|
41
|
+
});
|
|
42
|
+
let cursorRowIdx = itemRowIndices[0];
|
|
43
|
+
let renderCount = 0;
|
|
44
|
+
function totalLines() {
|
|
45
|
+
return rows.length + 2;
|
|
46
|
+
}
|
|
47
|
+
function render() {
|
|
48
|
+
if (renderCount > 0) {
|
|
49
|
+
stderr.write(`\x1B[${totalLines()}A`);
|
|
50
|
+
}
|
|
51
|
+
stderr.write(`${title} (space \u9009\u62E9 \xB7 a \u5168\u9009/\u5168\u4E0D\u9009 \xB7 enter \u786E\u8BA4 \xB7 q \u53D6\u6D88)\x1B[K
|
|
52
|
+
`);
|
|
53
|
+
stderr.write(`\x1B[K
|
|
54
|
+
`);
|
|
55
|
+
rows.forEach((row, ridx) => {
|
|
56
|
+
if (row.kind === "header") {
|
|
57
|
+
stderr.write(` \x1B[90m\u2500\u2500 ${row.groupLabel} \u2500\u2500\x1B[0m\x1B[K
|
|
58
|
+
`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const isCursor = ridx === cursorRowIdx;
|
|
62
|
+
const isSelected = selected.has(row.itemIndex);
|
|
63
|
+
const pointer = isCursor ? "\x1B[36m\u276F\x1B[0m" : " ";
|
|
64
|
+
const checkbox = isSelected ? "\x1B[32m[x]\x1B[0m" : "[ ]";
|
|
65
|
+
const label = row.item.label;
|
|
66
|
+
const hint = row.item.hint ? ` \x1B[90m${row.item.hint}\x1B[0m` : "";
|
|
67
|
+
stderr.write(`${pointer} ${checkbox} ${label}${hint}\x1B[K
|
|
68
|
+
`);
|
|
69
|
+
});
|
|
70
|
+
renderCount++;
|
|
71
|
+
}
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
stderr.write("\x1B[?25l");
|
|
74
|
+
stdin.setRawMode(true);
|
|
75
|
+
stdin.resume();
|
|
76
|
+
stdin.setEncoding("utf8");
|
|
77
|
+
function cleanup() {
|
|
78
|
+
stdin.setRawMode(false);
|
|
79
|
+
stdin.pause();
|
|
80
|
+
stdin.removeListener("data", onData);
|
|
81
|
+
stderr.write("\x1B[?25h");
|
|
82
|
+
}
|
|
83
|
+
function moveCursor(direction) {
|
|
84
|
+
const cur = itemRowIndices.indexOf(cursorRowIdx);
|
|
85
|
+
const next = (cur + direction + itemRowIndices.length) % itemRowIndices.length;
|
|
86
|
+
cursorRowIdx = itemRowIndices[next];
|
|
87
|
+
render();
|
|
88
|
+
}
|
|
89
|
+
function toggleAll() {
|
|
90
|
+
if (selected.size === items.length) {
|
|
91
|
+
selected.clear();
|
|
92
|
+
} else {
|
|
93
|
+
items.forEach((_, idx) => selected.add(idx));
|
|
94
|
+
}
|
|
95
|
+
render();
|
|
96
|
+
}
|
|
97
|
+
function onData(key) {
|
|
98
|
+
if (key === "\x1B[A" || key === "k") {
|
|
99
|
+
moveCursor(-1);
|
|
100
|
+
} else if (key === "\x1B[B" || key === "j") {
|
|
101
|
+
moveCursor(1);
|
|
102
|
+
} else if (key === " ") {
|
|
103
|
+
const row = rows[cursorRowIdx];
|
|
104
|
+
if (row.kind === "item") {
|
|
105
|
+
const idx = row.itemIndex;
|
|
106
|
+
if (selected.has(idx)) selected.delete(idx);
|
|
107
|
+
else selected.add(idx);
|
|
108
|
+
render();
|
|
109
|
+
}
|
|
110
|
+
} else if (key === "a" || key === "A") {
|
|
111
|
+
toggleAll();
|
|
112
|
+
} else if (key === "\r" || key === "\n") {
|
|
113
|
+
if (selected.size === 0) {
|
|
114
|
+
stderr.write("\x07");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
cleanup();
|
|
118
|
+
const result = Array.from(selected).map((i) => items[i].value);
|
|
119
|
+
resolve(result);
|
|
120
|
+
} else if (key === "q" || key === "Q" || key === "\x1B" || key === "") {
|
|
121
|
+
cleanup();
|
|
122
|
+
reject(new MultiselectCancelled());
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
stdin.on("data", onData);
|
|
126
|
+
render();
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
function promptSingleCheckboxSelect(opts) {
|
|
130
|
+
const { title, items } = opts;
|
|
131
|
+
if (items.length === 0) return Promise.reject(new Error("\u65E0\u53EF\u9009\u9879"));
|
|
132
|
+
const stderr = process.stderr;
|
|
133
|
+
const stdin = process.stdin;
|
|
134
|
+
if (!stdin.isTTY) return Promise.reject(new Error("\u9700\u8981 TTY \u624D\u80FD\u8FDB\u884C\u5355\u9009"));
|
|
135
|
+
const preselectedIndex = items.findIndex((item) => item.preselected);
|
|
136
|
+
let cursor = preselectedIndex >= 0 ? preselectedIndex : 0;
|
|
137
|
+
let selected = preselectedIndex >= 0 ? preselectedIndex : null;
|
|
138
|
+
let renderCount = 0;
|
|
139
|
+
const lines = items.length + 2;
|
|
140
|
+
function render() {
|
|
141
|
+
if (renderCount > 0) stderr.write(`\x1B[${lines}A`);
|
|
142
|
+
stderr.write(`${title} (space \u9009\u62E9 \xB7 enter \u786E\u8BA4 \xB7 q \u53D6\u6D88)\x1B[K
|
|
143
|
+
`);
|
|
144
|
+
stderr.write(`\x1B[K
|
|
145
|
+
`);
|
|
146
|
+
items.forEach((it, idx) => {
|
|
147
|
+
const pointer = idx === cursor ? "\x1B[36m\u276F\x1B[0m" : " ";
|
|
148
|
+
const checkbox = selected === idx ? "\x1B[32m[x]\x1B[0m" : "[ ]";
|
|
149
|
+
const hint = it.hint ? ` \x1B[90m${it.hint}\x1B[0m` : "";
|
|
150
|
+
stderr.write(`${pointer} ${checkbox} ${it.label}${hint}\x1B[K
|
|
151
|
+
`);
|
|
152
|
+
});
|
|
153
|
+
renderCount++;
|
|
154
|
+
}
|
|
155
|
+
return new Promise((resolve, reject) => {
|
|
156
|
+
stderr.write("\x1B[?25l");
|
|
157
|
+
stdin.setRawMode(true);
|
|
158
|
+
stdin.resume();
|
|
159
|
+
stdin.setEncoding("utf8");
|
|
160
|
+
function cleanup() {
|
|
161
|
+
stdin.setRawMode(false);
|
|
162
|
+
stdin.pause();
|
|
163
|
+
stdin.removeListener("data", onData);
|
|
164
|
+
stderr.write("\x1B[?25h");
|
|
165
|
+
}
|
|
166
|
+
function moveCursor(direction) {
|
|
167
|
+
cursor = (cursor + direction + items.length) % items.length;
|
|
168
|
+
render();
|
|
169
|
+
}
|
|
170
|
+
function onData(key) {
|
|
171
|
+
if (key === "\x1B[A" || key === "k") {
|
|
172
|
+
moveCursor(-1);
|
|
173
|
+
} else if (key === "\x1B[B" || key === "j") {
|
|
174
|
+
moveCursor(1);
|
|
175
|
+
} else if (key === " ") {
|
|
176
|
+
selected = cursor;
|
|
177
|
+
render();
|
|
178
|
+
} else if (key === "\r" || key === "\n") {
|
|
179
|
+
if (selected === null) {
|
|
180
|
+
stderr.write("\x07");
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
cleanup();
|
|
184
|
+
resolve(items[selected].value);
|
|
185
|
+
} else if (key === "q" || key === "Q" || key === "\x1B" || key === "") {
|
|
186
|
+
cleanup();
|
|
187
|
+
reject(new MultiselectCancelled());
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
stdin.on("data", onData);
|
|
191
|
+
render();
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
function promptSingleSelect(opts) {
|
|
195
|
+
const { title, items } = opts;
|
|
196
|
+
if (items.length === 0) return Promise.reject(new Error("\u65E0\u53EF\u9009\u9879"));
|
|
197
|
+
const stderr = process.stderr;
|
|
198
|
+
const stdin = process.stdin;
|
|
199
|
+
if (!stdin.isTTY) return Promise.reject(new Error("\u9700\u8981 TTY \u624D\u80FD\u8FDB\u884C\u5355\u9009"));
|
|
200
|
+
let cursor = 0;
|
|
201
|
+
let renderCount = 0;
|
|
202
|
+
const lines = items.length + 1;
|
|
203
|
+
function render() {
|
|
204
|
+
if (renderCount > 0) stderr.write(`\x1B[${lines}A`);
|
|
205
|
+
stderr.write(`${title}\x1B[K
|
|
206
|
+
`);
|
|
207
|
+
items.forEach((it, idx) => {
|
|
208
|
+
const pointer = idx === cursor ? "\x1B[36m\u276F\x1B[0m" : " ";
|
|
209
|
+
stderr.write(`${pointer} ${it.label}\x1B[K
|
|
210
|
+
`);
|
|
211
|
+
});
|
|
212
|
+
renderCount++;
|
|
213
|
+
}
|
|
214
|
+
return new Promise((resolve, reject) => {
|
|
215
|
+
stderr.write("\x1B[?25l");
|
|
216
|
+
stdin.setRawMode(true);
|
|
217
|
+
stdin.resume();
|
|
218
|
+
stdin.setEncoding("utf8");
|
|
219
|
+
function cleanup() {
|
|
220
|
+
stdin.setRawMode(false);
|
|
221
|
+
stdin.pause();
|
|
222
|
+
stdin.removeListener("data", onData);
|
|
223
|
+
stderr.write("\x1B[?25h");
|
|
224
|
+
}
|
|
225
|
+
function onData(key) {
|
|
226
|
+
if (key === "\x1B[A" || key === "k") {
|
|
227
|
+
cursor = (cursor - 1 + items.length) % items.length;
|
|
228
|
+
render();
|
|
229
|
+
} else if (key === "\x1B[B" || key === "j") {
|
|
230
|
+
cursor = (cursor + 1) % items.length;
|
|
231
|
+
render();
|
|
232
|
+
} else if (key === "\r" || key === "\n") {
|
|
233
|
+
cleanup();
|
|
234
|
+
resolve(items[cursor].value);
|
|
235
|
+
} else if (key === "q" || key === "Q" || key === "\x1B" || key === "") {
|
|
236
|
+
cleanup();
|
|
237
|
+
reject(new MultiselectCancelled());
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
stdin.on("data", onData);
|
|
241
|
+
render();
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// src/commands/sync/scanners.ts
|
|
246
|
+
import { createHash } from "crypto";
|
|
247
|
+
import { readdirSync, readFileSync as readFileSync2, statSync, lstatSync, existsSync as existsSync2, realpathSync } from "fs";
|
|
248
|
+
import { homedir } from "os";
|
|
249
|
+
import { basename, join, relative, sep } from "path";
|
|
250
|
+
|
|
251
|
+
// src/commands/sync/skill-manifest.ts
|
|
252
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
253
|
+
var SKILL_MANIFEST_FILE = ".skill-manifest.json";
|
|
254
|
+
function isSkillScope(value) {
|
|
255
|
+
return value === "personal" || value === "company" || value === "system";
|
|
256
|
+
}
|
|
257
|
+
function uniqueEntries(entries) {
|
|
258
|
+
const seen = /* @__PURE__ */ new Set();
|
|
259
|
+
const out = [];
|
|
260
|
+
for (const entry of entries) {
|
|
261
|
+
if (seen.has(entry.dirName)) continue;
|
|
262
|
+
seen.add(entry.dirName);
|
|
263
|
+
out.push(entry);
|
|
264
|
+
}
|
|
265
|
+
return out;
|
|
266
|
+
}
|
|
267
|
+
function readSkillManifestEntries(manifestPath) {
|
|
268
|
+
if (!existsSync(manifestPath)) return [];
|
|
269
|
+
let parsed;
|
|
270
|
+
try {
|
|
271
|
+
parsed = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
272
|
+
} catch {
|
|
273
|
+
return [];
|
|
274
|
+
}
|
|
275
|
+
if (Array.isArray(parsed)) {
|
|
276
|
+
const entries = [];
|
|
277
|
+
for (const item of parsed) {
|
|
278
|
+
if (!item || typeof item !== "object") continue;
|
|
279
|
+
const entry = item;
|
|
280
|
+
if (typeof entry.dirName !== "string" || !isSkillScope(entry.scope))
|
|
281
|
+
continue;
|
|
282
|
+
entries.push({ dirName: entry.dirName, scope: entry.scope });
|
|
283
|
+
}
|
|
284
|
+
return uniqueEntries(entries);
|
|
285
|
+
}
|
|
286
|
+
return [];
|
|
287
|
+
}
|
|
288
|
+
function writeSkillManifestEntries(manifestPath, entries) {
|
|
289
|
+
writeFileSync(
|
|
290
|
+
manifestPath,
|
|
291
|
+
JSON.stringify(uniqueEntries(entries), null, 2) + "\n",
|
|
292
|
+
"utf8"
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/commands/sync/scanners.ts
|
|
297
|
+
var SECRET_PATTERN = /TOKEN|SECRET|KEY|PASSWORD/i;
|
|
298
|
+
function safeLstat(p) {
|
|
299
|
+
try {
|
|
300
|
+
return lstatSync(p);
|
|
301
|
+
} catch {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function safeReaddir(p) {
|
|
306
|
+
try {
|
|
307
|
+
return readdirSync(p);
|
|
308
|
+
} catch {
|
|
309
|
+
return [];
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function readSkillFile(filePath) {
|
|
313
|
+
try {
|
|
314
|
+
const content = readFileSync2(filePath, "utf8");
|
|
315
|
+
const stat = statSync(filePath);
|
|
316
|
+
return { content, mtime: stat.mtime };
|
|
317
|
+
} catch {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function sha256(content) {
|
|
322
|
+
return createHash("sha256").update(content).digest("hex");
|
|
323
|
+
}
|
|
324
|
+
function getCurrentWorkspace(home = process.env.HOME || homedir()) {
|
|
325
|
+
const cwd = process.cwd();
|
|
326
|
+
const wsRoot = join(home, "workspaces");
|
|
327
|
+
const rel = relative(wsRoot, cwd);
|
|
328
|
+
if (rel !== "" && !rel.startsWith("..") && !rel.startsWith(sep)) {
|
|
329
|
+
const name = rel.split(sep)[0];
|
|
330
|
+
return { dir: join(wsRoot, name), name };
|
|
331
|
+
}
|
|
332
|
+
if (existsSync2(join(cwd, ".claude"))) {
|
|
333
|
+
return { dir: cwd, name: basename(cwd) };
|
|
334
|
+
}
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
function readSkillScopeIndex(skillsRoot) {
|
|
338
|
+
const entries = readSkillManifestEntries(join(skillsRoot, SKILL_MANIFEST_FILE));
|
|
339
|
+
return new Map(entries.map((entry) => [entry.dirName, entry.scope]));
|
|
340
|
+
}
|
|
341
|
+
function scanSkillsInDir(dir, source, workspacePath) {
|
|
342
|
+
if (!existsSync2(dir)) return [];
|
|
343
|
+
const out = [];
|
|
344
|
+
const scopeIndex = readSkillScopeIndex(dir);
|
|
345
|
+
for (const slug of safeReaddir(dir)) {
|
|
346
|
+
const scope = scopeIndex.get(slug);
|
|
347
|
+
if (scope === "system" || scope === "company") continue;
|
|
348
|
+
const slugDir = join(dir, slug);
|
|
349
|
+
const lst = safeLstat(slugDir);
|
|
350
|
+
if (!lst) continue;
|
|
351
|
+
const isSymlink = lst.isSymbolicLink();
|
|
352
|
+
let isDir = lst.isDirectory();
|
|
353
|
+
if (isSymlink) {
|
|
354
|
+
try {
|
|
355
|
+
isDir = statSync(slugDir).isDirectory();
|
|
356
|
+
} catch {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (!isDir) continue;
|
|
361
|
+
const filePath = join(slugDir, "SKILL.md");
|
|
362
|
+
if (!existsSync2(filePath)) continue;
|
|
363
|
+
const file = readSkillFile(filePath);
|
|
364
|
+
if (!file) continue;
|
|
365
|
+
out.push({
|
|
366
|
+
slug,
|
|
367
|
+
source,
|
|
368
|
+
workspacePath,
|
|
369
|
+
dirPath: slugDir,
|
|
370
|
+
filePath,
|
|
371
|
+
content: file.content,
|
|
372
|
+
checksum: sha256(file.content),
|
|
373
|
+
mtime: file.mtime.toISOString(),
|
|
374
|
+
isSymlink
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
return out;
|
|
378
|
+
}
|
|
379
|
+
function scanSkills() {
|
|
380
|
+
const home = process.env.HOME || homedir();
|
|
381
|
+
const workspace = getCurrentWorkspace(home);
|
|
382
|
+
if (!workspace) return [];
|
|
383
|
+
return scanSkillsInDir(join(workspace.dir, ".claude", "skills"), "workspace", workspace.name);
|
|
384
|
+
}
|
|
385
|
+
function readJsonSafe(p) {
|
|
386
|
+
try {
|
|
387
|
+
return JSON.parse(readFileSync2(p, "utf8"));
|
|
388
|
+
} catch {
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function detectSecrets(env) {
|
|
393
|
+
if (!env) return false;
|
|
394
|
+
return Object.keys(env).some((k) => SECRET_PATTERN.test(k));
|
|
395
|
+
}
|
|
396
|
+
function parseMcpServers(servers, source, workspacePath, options = { filterScope: true }) {
|
|
397
|
+
if (!servers || typeof servers !== "object") return [];
|
|
398
|
+
const out = [];
|
|
399
|
+
for (const [slug, raw] of Object.entries(servers)) {
|
|
400
|
+
if (!raw || typeof raw !== "object") continue;
|
|
401
|
+
if (options.filterScope && (raw._scope === "system" || raw._scope === "company")) continue;
|
|
402
|
+
const transport = raw.type === "http" || raw.transport === "http" || typeof raw.url === "string" ? "http" : "stdio";
|
|
403
|
+
const env = raw.env && typeof raw.env === "object" ? raw.env : void 0;
|
|
404
|
+
const headers = raw.headers && typeof raw.headers === "object" ? raw.headers : void 0;
|
|
405
|
+
out.push({
|
|
406
|
+
slug,
|
|
407
|
+
source,
|
|
408
|
+
workspacePath,
|
|
409
|
+
transport,
|
|
410
|
+
url: typeof raw.url === "string" ? raw.url : void 0,
|
|
411
|
+
command: typeof raw.command === "string" ? raw.command : void 0,
|
|
412
|
+
args: Array.isArray(raw.args) ? raw.args.map(String) : void 0,
|
|
413
|
+
env,
|
|
414
|
+
headers,
|
|
415
|
+
hasSecrets: detectSecrets(env)
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
return out;
|
|
419
|
+
}
|
|
420
|
+
function normalizeProjectPath(p) {
|
|
421
|
+
const trimmed = p.replace(/\/+$/, "");
|
|
422
|
+
try {
|
|
423
|
+
return statSync(trimmed).isDirectory() ? realpathSync(trimmed).replace(/\/+$/, "") : trimmed;
|
|
424
|
+
} catch {
|
|
425
|
+
return trimmed;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function projectMcpServers(data, workspaceDir) {
|
|
429
|
+
const projects = data?.projects;
|
|
430
|
+
if (!projects || typeof projects !== "object") return void 0;
|
|
431
|
+
const normalizedWorkspaceDir = workspaceDir.replace(/\/+$/, "");
|
|
432
|
+
const project = projects[normalizedWorkspaceDir] || projects[`${normalizedWorkspaceDir}/`] || projects[process.cwd()] || projects[process.cwd().replace(/\/+$/, "")];
|
|
433
|
+
if (project && typeof project === "object") return project.mcpServers;
|
|
434
|
+
const realWorkspaceDir = normalizeProjectPath(workspaceDir);
|
|
435
|
+
for (const [projectPath, projectConfig] of Object.entries(projects)) {
|
|
436
|
+
if (normalizeProjectPath(projectPath) !== realWorkspaceDir) continue;
|
|
437
|
+
return projectConfig && typeof projectConfig === "object" ? projectConfig.mcpServers : void 0;
|
|
438
|
+
}
|
|
439
|
+
return void 0;
|
|
440
|
+
}
|
|
441
|
+
function scanMcps() {
|
|
442
|
+
const home = process.env.HOME || homedir();
|
|
443
|
+
const workspace = getCurrentWorkspace(home);
|
|
444
|
+
if (!workspace) return [];
|
|
445
|
+
const out = [];
|
|
446
|
+
const scopedMcpJson = readJsonSafe(join(workspace.dir, ".mcp.json"));
|
|
447
|
+
out.push(...parseMcpServers(scopedMcpJson?.mcpServers, "workspace", workspace.name, { filterScope: true }));
|
|
448
|
+
const workspaceClaudeJson = readJsonSafe(join(workspace.dir, ".claude", ".claude.json"));
|
|
449
|
+
out.push(
|
|
450
|
+
...parseMcpServers(projectMcpServers(workspaceClaudeJson, workspace.dir), "workspace", workspace.name, {
|
|
451
|
+
filterScope: false
|
|
452
|
+
})
|
|
453
|
+
);
|
|
454
|
+
const globalClaudeJson = readJsonSafe(join(home, ".claude.json"));
|
|
455
|
+
out.push(
|
|
456
|
+
...parseMcpServers(projectMcpServers(globalClaudeJson, workspace.dir), "global", void 0, {
|
|
457
|
+
filterScope: false
|
|
458
|
+
})
|
|
459
|
+
);
|
|
460
|
+
return out;
|
|
461
|
+
}
|
|
462
|
+
function describeSource(_c) {
|
|
463
|
+
return void 0;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export {
|
|
467
|
+
MultiselectCancelled,
|
|
468
|
+
promptMultiselect,
|
|
469
|
+
promptSingleCheckboxSelect,
|
|
470
|
+
promptSingleSelect,
|
|
471
|
+
SKILL_MANIFEST_FILE,
|
|
472
|
+
readSkillManifestEntries,
|
|
473
|
+
writeSkillManifestEntries,
|
|
474
|
+
getCurrentWorkspace,
|
|
475
|
+
scanSkills,
|
|
476
|
+
scanMcps,
|
|
477
|
+
describeSource
|
|
478
|
+
};
|