jinzd-ai-cli 0.4.208 → 0.4.210
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 +10 -8
- package/README.zh-CN.md +5 -5
- package/dist/{batch-3T44NFLG.js → batch-MFPYHOIL.js} +4 -4
- package/dist/{chunk-TD7JEHCR.js → chunk-7JWT3KXO.js} +390 -23
- package/dist/{chunk-SQB66GP6.js → chunk-BNERFXVO.js} +12 -9
- package/dist/{chunk-J22B3OSQ.js → chunk-DJ35G5VO.js} +1 -1
- package/dist/{chunk-VWYUB22Y.js → chunk-E2ZWWE6Q.js} +1 -1
- package/dist/{chunk-KV4GYNEA.js → chunk-GRJNQJA5.js} +5 -3
- package/dist/{chunk-QDY72ABW.js → chunk-HZQVX7VF.js} +5 -3
- package/dist/{chunk-O6UFCEUZ.js → chunk-K3CF65QH.js} +12 -9
- package/dist/{chunk-2OVMMSGQ.js → chunk-NEPFADHX.js} +10 -9
- package/dist/{chunk-P7JY3SWA.js → chunk-NWP7C6CC.js} +1 -1
- package/dist/{chunk-TM4MS63K.js → chunk-P6MTFCXB.js} +14 -6
- package/dist/{ci-6QWBVRJX.js → ci-QLJUDLMY.js} +3 -2
- package/dist/{constants-SZTQNN7K.js → constants-47OR72MV.js} +3 -1
- package/dist/{doctor-cli-I2Y2YR4V.js → doctor-cli-32COMA5K.js} +4 -4
- package/dist/electron-server.js +652 -457
- package/dist/{hub-XVPFBODB.js → hub-3NWJUCLK.js} +1 -1
- package/dist/index.js +128 -376
- package/dist/{indexer-AKWMYNJI.js → indexer-2AG4G6B5.js} +1 -1
- package/dist/{indexer-BMYUUDLH.js → indexer-4WWS3VIL.js} +1 -1
- package/dist/{run-tests-HZ2CRCTZ.js → run-tests-NT2UIUVB.js} +1 -1
- package/dist/{run-tests-D5SNUBYX.js → run-tests-UT7RZ7Y3.js} +2 -2
- package/dist/{server-PVKLPRUP.js → server-AIHVMLNU.js} +5 -5
- package/dist/{server-4EFTRFTN.js → server-E5D54DIZ.js} +87 -306
- package/dist/{task-orchestrator-2A5VCFLS.js → task-orchestrator-BFDSTSOH.js} +5 -5
- package/dist/{usage-Q24E5636.js → usage-P4B2RZKL.js} +2 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://www.npmjs.com/package/jinzd-ai-cli)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](https://nodejs.org)
|
|
10
|
-
[]()
|
|
11
11
|
[](https://github.com/jinzhengdong/ai-cli/releases)
|
|
12
12
|
[](https://github.com/jinzhengdong/ai-cli/actions/workflows/ci.yml)
|
|
13
13
|
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
- **MCP Protocol** — Connect external MCP servers for dynamic tool discovery
|
|
44
44
|
- **Multi-User Auth** — Web UI supports multiple users with password authentication
|
|
45
45
|
- **PWA Support** — Install Web UI as a desktop/mobile app, accessible over LAN
|
|
46
|
-
- **Hierarchical Context** — 3-layer context files (global / project / subdirectory) auto-injected
|
|
46
|
+
- **Hierarchical Context** — 3-layer context files (global / project / subdirectory) auto-injected; supports native AICLI.md, Claude-compatible CLAUDE.md, and Codex-compatible AGENTS.md with override files
|
|
47
47
|
- **Headless Mode** — `ai-cli -p "prompt"` for CI/CD pipelines and scripting
|
|
48
48
|
- **44 REPL Commands** — Session management, checkpointing, code review, security review/scan, rewind, scaffolding, cross-session history search, chat-memory recall, smart model routing (`/route`), and more
|
|
49
49
|
- **GitHub Actions CI/CD** — Automated testing on Node 20/22 + npm publish on release tags
|
|
@@ -370,11 +370,11 @@ ai-cli automatically discovers and injects context files into the system prompt:
|
|
|
370
370
|
|
|
371
371
|
| Layer | Path | Purpose |
|
|
372
372
|
|-------|------|---------|
|
|
373
|
-
| Global | `~/.aicli
|
|
374
|
-
| Project | `<git-root
|
|
375
|
-
| Subdirectory | `<cwd
|
|
373
|
+
| Global | `~/.aicli/<context-file>` | Personal preferences across all projects |
|
|
374
|
+
| Project | `<git-root>/<context-file>` | Project rules (commit to git for team sharing) |
|
|
375
|
+
| Subdirectory | `<cwd>/<context-file>` | Directory-specific instructions |
|
|
376
376
|
|
|
377
|
-
|
|
377
|
+
At each layer, ai-cli loads the first non-empty file in this priority order: `AICLI.override.md`, `AGENTS.override.md`, `AICLI.md`, `CLAUDE.md`, `AGENTS.md`. `AICLI.md` is the native filename; `CLAUDE.md` and `AGENTS.md` are compatibility filenames for Claude Code and Codex-style projects.
|
|
378
378
|
|
|
379
379
|
### MCP Integration
|
|
380
380
|
|
|
@@ -414,11 +414,13 @@ The Web UI (`aicli web`) provides a full-featured browser interface:
|
|
|
414
414
|
## Testing
|
|
415
415
|
|
|
416
416
|
```bash
|
|
417
|
-
npm test #
|
|
417
|
+
npm test # Offline regression suite: 79 files, 1393 passed, 1 skipped
|
|
418
|
+
npm run test:providers # Real-provider smoke tests (network/API-key dependent)
|
|
419
|
+
npm run test:e2e-web # Playwright browser smoke; may need browser-process permission in sandboxes
|
|
418
420
|
npm run test:watch # Watch mode
|
|
419
421
|
```
|
|
420
422
|
|
|
421
|
-
|
|
423
|
+
79 offline test files covering: authentication, sessions, tool types & danger levels, permissions, output truncation, diff rendering, edit-file similarity, error hierarchy, config management, env loading, provider registry, web-fetch, grep-files, hub renderer, hub discussion, hub presets, dev-state, token estimator, tool registry budget, parallel tool execution, cost tracker, session tool history.
|
|
422
424
|
|
|
423
425
|
## Documentation
|
|
424
426
|
|
package/README.zh-CN.md
CHANGED
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
- **MCP 协议** — 接入外部 MCP 服务器,动态发现工具
|
|
36
36
|
- **多用户认证** — Web UI 支持多用户密码登录
|
|
37
37
|
- **PWA 支持** — Web UI 可安装为桌面/移动应用,支持局域网访问
|
|
38
|
-
- **三层级上下文** — 全局 / 项目 /
|
|
38
|
+
- **三层级上下文** — 全局 / 项目 / 子目录上下文文件自动注入;支持原生 `AICLI.md`、Claude 兼容 `CLAUDE.md`、Codex 兼容 `AGENTS.md` 及 override 文件
|
|
39
39
|
- **无头模式** — `aicli -p "提示词"` 用于 CI/CD 管道和脚本
|
|
40
40
|
- **44 个 REPL 命令** — 会话管理、检查点、代码审查、安全审查/扫描、对话回退、脚手架、聊天记忆召回、智能模型路由(`/route`)等
|
|
41
41
|
- **GitHub Actions CI/CD** — Node 20/22 自动测试 + Release tag 自动发布 npm
|
|
@@ -391,11 +391,11 @@ ai-cli 自动发现并注入上下文文件到 system prompt:
|
|
|
391
391
|
|
|
392
392
|
| 层级 | 路径 | 用途 |
|
|
393
393
|
|------|------|------|
|
|
394
|
-
| 全局层 | `~/.aicli
|
|
395
|
-
| 项目层 | `<git-root
|
|
396
|
-
| 子目录层 | `<cwd
|
|
394
|
+
| 全局层 | `~/.aicli/<context-file>` | 所有项目通用的个人偏好 |
|
|
395
|
+
| 项目层 | `<git-root>/<context-file>` | 项目级规则(提交到 git 供团队共享) |
|
|
396
|
+
| 子目录层 | `<cwd>/<context-file>` | 当前子目录的特定指令 |
|
|
397
397
|
|
|
398
|
-
|
|
398
|
+
每层按以下优先级加载第一个非空文件:`AICLI.override.md` → `AGENTS.override.md` → `AICLI.md` → `CLAUDE.md` → `AGENTS.md`。`AICLI.md` 是 ai-cli 原生文件名;`CLAUDE.md` 和 `AGENTS.md` 分别用于兼容 Claude Code 与 Codex 风格项目。
|
|
399
399
|
|
|
400
400
|
### MCP 集成
|
|
401
401
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ConfigManager
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-P6MTFCXB.js";
|
|
5
5
|
import "./chunk-TZQHYZKT.js";
|
|
6
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-GRJNQJA5.js";
|
|
7
7
|
import {
|
|
8
8
|
atomicWriteFileSync
|
|
9
9
|
} from "./chunk-IW3Q7AE5.js";
|
|
10
10
|
|
|
11
11
|
// src/cli/batch.ts
|
|
12
12
|
import Anthropic from "@anthropic-ai/sdk";
|
|
13
|
-
import { readFileSync,
|
|
13
|
+
import { readFileSync, existsSync, mkdirSync } from "fs";
|
|
14
14
|
import { join } from "path";
|
|
15
15
|
function parseBatchInput(jsonl) {
|
|
16
16
|
const lines = jsonl.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
@@ -195,7 +195,7 @@ async function cmdResults(batchId, outPath) {
|
|
|
195
195
|
}
|
|
196
196
|
const body = lines.join("\n") + "\n";
|
|
197
197
|
if (outPath) {
|
|
198
|
-
|
|
198
|
+
atomicWriteFileSync(outPath, body);
|
|
199
199
|
console.log(`Saved ${lines.length} result(s) to ${outPath} (${ok} succeeded, ${err} failed/errored).`);
|
|
200
200
|
} else {
|
|
201
201
|
process.stdout.write(body);
|
|
@@ -1,17 +1,179 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
truncateForPersist
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-NEPFADHX.js";
|
|
5
5
|
import {
|
|
6
6
|
APP_NAME,
|
|
7
7
|
CONFIG_DIR_NAME,
|
|
8
|
+
CONTEXT_FILE_CANDIDATES,
|
|
9
|
+
CONTEXT_FILE_MAX_BYTES,
|
|
8
10
|
DEV_STATE_FILE_NAME,
|
|
9
11
|
MCP_CALL_TIMEOUT,
|
|
10
12
|
MCP_CONNECT_TIMEOUT,
|
|
11
13
|
MCP_PROTOCOL_VERSION,
|
|
12
14
|
MCP_TOOL_PREFIX,
|
|
13
15
|
VERSION
|
|
14
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-GRJNQJA5.js";
|
|
17
|
+
import {
|
|
18
|
+
atomicWriteFileSync
|
|
19
|
+
} from "./chunk-IW3Q7AE5.js";
|
|
20
|
+
|
|
21
|
+
// src/core/context-files.ts
|
|
22
|
+
import { existsSync, readFileSync } from "fs";
|
|
23
|
+
import { join, relative, resolve } from "path";
|
|
24
|
+
function uniqueNonEmpty(values) {
|
|
25
|
+
const seen = /* @__PURE__ */ new Set();
|
|
26
|
+
const out = [];
|
|
27
|
+
for (const value of values) {
|
|
28
|
+
const trimmed = value.trim();
|
|
29
|
+
if (!trimmed || seen.has(trimmed)) continue;
|
|
30
|
+
seen.add(trimmed);
|
|
31
|
+
out.push(trimmed);
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
function buildContextFileCandidates(fallbackFilenames = []) {
|
|
36
|
+
return uniqueNonEmpty([...CONTEXT_FILE_CANDIDATES, ...fallbackFilenames]);
|
|
37
|
+
}
|
|
38
|
+
function displayPath(filePath, cwd) {
|
|
39
|
+
const rel = relative(cwd, filePath);
|
|
40
|
+
if (rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\")) {
|
|
41
|
+
return rel;
|
|
42
|
+
}
|
|
43
|
+
return filePath;
|
|
44
|
+
}
|
|
45
|
+
function isInsideOrEqual(parent, child) {
|
|
46
|
+
const rel = relative(resolve(parent), resolve(child));
|
|
47
|
+
return rel === "" || !!rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
|
|
48
|
+
}
|
|
49
|
+
function readContextFile(level, filePath, cwd, maxBytes) {
|
|
50
|
+
const name = filePath.split(/[\\/]/).pop() ?? filePath;
|
|
51
|
+
const shown = displayPath(filePath, cwd);
|
|
52
|
+
try {
|
|
53
|
+
const raw = readFileSync(filePath);
|
|
54
|
+
const byteCount = raw.byteLength;
|
|
55
|
+
if (byteCount === 0) {
|
|
56
|
+
return {
|
|
57
|
+
layer: null,
|
|
58
|
+
skipped: { level, filePath, displayPath: shown, fileName: name, reason: "empty" }
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const truncated = byteCount > maxBytes;
|
|
62
|
+
const bytes = truncated ? raw.subarray(0, maxBytes) : raw;
|
|
63
|
+
const content = bytes.toString("utf-8").trim();
|
|
64
|
+
if (!content) {
|
|
65
|
+
return {
|
|
66
|
+
layer: null,
|
|
67
|
+
skipped: { level, filePath, displayPath: shown, fileName: name, reason: "empty" }
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
layer: {
|
|
72
|
+
level: level === "single" ? "project" : level,
|
|
73
|
+
filePath,
|
|
74
|
+
displayPath: shown,
|
|
75
|
+
fileName: name,
|
|
76
|
+
content,
|
|
77
|
+
charCount: content.length,
|
|
78
|
+
byteCount,
|
|
79
|
+
truncated
|
|
80
|
+
},
|
|
81
|
+
skipped: truncated ? { level, filePath, displayPath: shown, fileName: name, reason: "too-large", message: `truncated to ${maxBytes} bytes` } : null
|
|
82
|
+
};
|
|
83
|
+
} catch (err) {
|
|
84
|
+
return {
|
|
85
|
+
layer: null,
|
|
86
|
+
skipped: {
|
|
87
|
+
level,
|
|
88
|
+
filePath,
|
|
89
|
+
displayPath: shown,
|
|
90
|
+
fileName: name,
|
|
91
|
+
reason: "read-error",
|
|
92
|
+
message: err instanceof Error ? err.message : String(err)
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
98
|
+
const skipped = [];
|
|
99
|
+
for (const candidate of candidates) {
|
|
100
|
+
const filePath = join(dir, candidate);
|
|
101
|
+
if (!existsSync(filePath)) continue;
|
|
102
|
+
const result = readContextFile(level, filePath, cwd, maxBytes);
|
|
103
|
+
if (result.skipped) skipped.push(result.skipped);
|
|
104
|
+
if (result.layer) return { layer: result.layer, skipped };
|
|
105
|
+
}
|
|
106
|
+
return { layer: null, skipped };
|
|
107
|
+
}
|
|
108
|
+
function loadContextFiles(options) {
|
|
109
|
+
const cwd = resolve(options.cwd);
|
|
110
|
+
const maxBytes = options.maxBytes ?? CONTEXT_FILE_MAX_BYTES;
|
|
111
|
+
const candidates = options.candidates ?? buildContextFileCandidates(options.fallbackFilenames);
|
|
112
|
+
const skipped = [];
|
|
113
|
+
if (options.setting === false) {
|
|
114
|
+
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
115
|
+
}
|
|
116
|
+
if (options.setting && options.setting !== "auto") {
|
|
117
|
+
const filePath = resolve(cwd, String(options.setting));
|
|
118
|
+
if (!isInsideOrEqual(cwd, filePath)) {
|
|
119
|
+
skipped.push({
|
|
120
|
+
level: "single",
|
|
121
|
+
filePath,
|
|
122
|
+
displayPath: filePath,
|
|
123
|
+
fileName: String(options.setting),
|
|
124
|
+
reason: "outside-cwd"
|
|
125
|
+
});
|
|
126
|
+
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
127
|
+
}
|
|
128
|
+
if (!existsSync(filePath)) {
|
|
129
|
+
skipped.push({
|
|
130
|
+
level: "single",
|
|
131
|
+
filePath,
|
|
132
|
+
displayPath: displayPath(filePath, cwd),
|
|
133
|
+
fileName: String(options.setting),
|
|
134
|
+
reason: "missing"
|
|
135
|
+
});
|
|
136
|
+
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
137
|
+
}
|
|
138
|
+
const result = readContextFile("single", filePath, cwd, maxBytes);
|
|
139
|
+
if (result.skipped) skipped.push(result.skipped);
|
|
140
|
+
const layers2 = result.layer ? [result.layer] : [];
|
|
141
|
+
const mergedContent2 = layers2.map((l) => l.content).join("\n\n---\n\n");
|
|
142
|
+
return {
|
|
143
|
+
layers: layers2,
|
|
144
|
+
mergedContent: mergedContent2,
|
|
145
|
+
skipped,
|
|
146
|
+
totalChars: layers2.reduce((sum, l) => sum + l.charCount, 0),
|
|
147
|
+
totalBytes: layers2.reduce((sum, l) => sum + l.byteCount, 0),
|
|
148
|
+
maxBytes,
|
|
149
|
+
candidates
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const layers = [];
|
|
153
|
+
for (const [level, dir] of [
|
|
154
|
+
["global", options.configDir],
|
|
155
|
+
["project", options.projectRoot]
|
|
156
|
+
]) {
|
|
157
|
+
const result = findFirstContextFile(level, dir, cwd, candidates, maxBytes);
|
|
158
|
+
skipped.push(...result.skipped);
|
|
159
|
+
if (result.layer) layers.push(result.layer);
|
|
160
|
+
}
|
|
161
|
+
if (resolve(options.cwd) !== resolve(options.projectRoot)) {
|
|
162
|
+
const result = findFirstContextFile("local", options.cwd, cwd, candidates, maxBytes);
|
|
163
|
+
skipped.push(...result.skipped);
|
|
164
|
+
if (result.layer) layers.push(result.layer);
|
|
165
|
+
}
|
|
166
|
+
const mergedContent = layers.map((l) => l.content).join("\n\n---\n\n");
|
|
167
|
+
return {
|
|
168
|
+
layers,
|
|
169
|
+
mergedContent,
|
|
170
|
+
skipped,
|
|
171
|
+
totalChars: layers.reduce((sum, l) => sum + l.charCount, 0),
|
|
172
|
+
totalBytes: layers.reduce((sum, l) => sum + l.byteCount, 0),
|
|
173
|
+
maxBytes,
|
|
174
|
+
candidates
|
|
175
|
+
};
|
|
176
|
+
}
|
|
15
177
|
|
|
16
178
|
// src/mcp/client.ts
|
|
17
179
|
import { spawn } from "child_process";
|
|
@@ -148,7 +310,7 @@ var McpClient = class {
|
|
|
148
310
|
// 内部方法:JSON-RPC 通信
|
|
149
311
|
// ══════════════════════════════════════════════════════════════════
|
|
150
312
|
sendRequest(method, params) {
|
|
151
|
-
return new Promise((
|
|
313
|
+
return new Promise((resolve2, reject) => {
|
|
152
314
|
if (!this.process?.stdin?.writable) {
|
|
153
315
|
return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
|
|
154
316
|
}
|
|
@@ -172,7 +334,7 @@ var McpClient = class {
|
|
|
172
334
|
this.pendingRequests.set(id, {
|
|
173
335
|
resolve: (result) => {
|
|
174
336
|
cleanup();
|
|
175
|
-
|
|
337
|
+
resolve2(result);
|
|
176
338
|
},
|
|
177
339
|
reject: (error) => {
|
|
178
340
|
cleanup();
|
|
@@ -249,13 +411,13 @@ var McpClient = class {
|
|
|
249
411
|
}
|
|
250
412
|
/** Promise 超时包装 */
|
|
251
413
|
withTimeout(promise, ms, label) {
|
|
252
|
-
return new Promise((
|
|
414
|
+
return new Promise((resolve2, reject) => {
|
|
253
415
|
const timer = setTimeout(() => {
|
|
254
416
|
reject(new Error(`MCP [${this.serverId}] ${label} timed out after ${ms}ms`));
|
|
255
417
|
}, ms);
|
|
256
418
|
promise.then((val) => {
|
|
257
419
|
clearTimeout(timer);
|
|
258
|
-
|
|
420
|
+
resolve2(val);
|
|
259
421
|
}).catch((err) => {
|
|
260
422
|
clearTimeout(timer);
|
|
261
423
|
reject(err);
|
|
@@ -532,11 +694,11 @@ var McpManager = class {
|
|
|
532
694
|
};
|
|
533
695
|
|
|
534
696
|
// src/skills/manager.ts
|
|
535
|
-
import { existsSync, readdirSync, mkdirSync, statSync } from "fs";
|
|
536
|
-
import { join } from "path";
|
|
697
|
+
import { existsSync as existsSync2, readdirSync, mkdirSync, statSync } from "fs";
|
|
698
|
+
import { join as join2 } from "path";
|
|
537
699
|
|
|
538
700
|
// src/skills/types.ts
|
|
539
|
-
import { readFileSync } from "fs";
|
|
701
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
540
702
|
import { basename } from "path";
|
|
541
703
|
function parseSimpleYaml(yaml) {
|
|
542
704
|
const result = {};
|
|
@@ -558,7 +720,7 @@ function parseYamlArray(value) {
|
|
|
558
720
|
function parseSkillFile(filePath) {
|
|
559
721
|
let raw;
|
|
560
722
|
try {
|
|
561
|
-
raw =
|
|
723
|
+
raw = readFileSync2(filePath, "utf-8");
|
|
562
724
|
} catch {
|
|
563
725
|
return null;
|
|
564
726
|
}
|
|
@@ -601,7 +763,7 @@ var SkillManager = class {
|
|
|
601
763
|
/** 发现并加载 skillsDir 下所有 .md 文件,返回加载数量 */
|
|
602
764
|
loadSkills() {
|
|
603
765
|
this.skills.clear();
|
|
604
|
-
if (!
|
|
766
|
+
if (!existsSync2(this.skillsDir)) {
|
|
605
767
|
try {
|
|
606
768
|
mkdirSync(this.skillsDir, { recursive: true });
|
|
607
769
|
} catch {
|
|
@@ -616,14 +778,14 @@ var SkillManager = class {
|
|
|
616
778
|
}
|
|
617
779
|
for (const entry of entries) {
|
|
618
780
|
let filePath;
|
|
619
|
-
const fullPath =
|
|
781
|
+
const fullPath = join2(this.skillsDir, entry);
|
|
620
782
|
if (entry.endsWith(".md")) {
|
|
621
783
|
filePath = fullPath;
|
|
622
784
|
} else {
|
|
623
785
|
try {
|
|
624
786
|
if (statSync(fullPath).isDirectory()) {
|
|
625
|
-
const skillMd =
|
|
626
|
-
if (
|
|
787
|
+
const skillMd = join2(fullPath, "SKILL.md");
|
|
788
|
+
if (existsSync2(skillMd)) {
|
|
627
789
|
filePath = skillMd;
|
|
628
790
|
} else {
|
|
629
791
|
continue;
|
|
@@ -696,6 +858,206 @@ async function setupProxy(configProxy) {
|
|
|
696
858
|
}
|
|
697
859
|
}
|
|
698
860
|
|
|
861
|
+
// src/repl/commands/project-init.ts
|
|
862
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
863
|
+
import { join as join3 } from "path";
|
|
864
|
+
var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
865
|
+
"node_modules",
|
|
866
|
+
".git",
|
|
867
|
+
"dist",
|
|
868
|
+
"build",
|
|
869
|
+
"out",
|
|
870
|
+
"target",
|
|
871
|
+
".next",
|
|
872
|
+
".nuxt",
|
|
873
|
+
"__pycache__",
|
|
874
|
+
".venv",
|
|
875
|
+
"venv",
|
|
876
|
+
".tox",
|
|
877
|
+
".mypy_cache",
|
|
878
|
+
".pytest_cache",
|
|
879
|
+
".gradle",
|
|
880
|
+
".idea",
|
|
881
|
+
".vscode",
|
|
882
|
+
".vs",
|
|
883
|
+
"coverage",
|
|
884
|
+
".cache",
|
|
885
|
+
".parcel-cache",
|
|
886
|
+
"dist-cjs",
|
|
887
|
+
"release",
|
|
888
|
+
".output",
|
|
889
|
+
".turbo",
|
|
890
|
+
"vendor"
|
|
891
|
+
]);
|
|
892
|
+
function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
893
|
+
const lines = [];
|
|
894
|
+
let count = 0;
|
|
895
|
+
const walk = (d, prefix, depth) => {
|
|
896
|
+
if (depth > maxDepth || count >= maxEntries) return;
|
|
897
|
+
let entries;
|
|
898
|
+
try {
|
|
899
|
+
entries = readdirSync2(d);
|
|
900
|
+
} catch {
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
const filtered = entries.filter((e) => !e.startsWith(".") && !SCAN_SKIP_DIRS.has(e));
|
|
904
|
+
const sorted = filtered.sort((a, b) => {
|
|
905
|
+
let aIsDir = false, bIsDir = false;
|
|
906
|
+
try {
|
|
907
|
+
aIsDir = statSync2(join3(d, a)).isDirectory();
|
|
908
|
+
} catch {
|
|
909
|
+
}
|
|
910
|
+
try {
|
|
911
|
+
bIsDir = statSync2(join3(d, b)).isDirectory();
|
|
912
|
+
} catch {
|
|
913
|
+
}
|
|
914
|
+
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
|
|
915
|
+
return a.localeCompare(b);
|
|
916
|
+
});
|
|
917
|
+
for (let i = 0; i < sorted.length && count < maxEntries; i++) {
|
|
918
|
+
const name = sorted[i];
|
|
919
|
+
const fullPath = join3(d, name);
|
|
920
|
+
const isLast = i === sorted.length - 1;
|
|
921
|
+
const connector = isLast ? "+-- " : "|-- ";
|
|
922
|
+
let isDir;
|
|
923
|
+
try {
|
|
924
|
+
isDir = statSync2(fullPath).isDirectory();
|
|
925
|
+
} catch {
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
lines.push(prefix + connector + name + (isDir ? "/" : ""));
|
|
929
|
+
count++;
|
|
930
|
+
if (isDir) {
|
|
931
|
+
walk(fullPath, prefix + (isLast ? " " : "| "), depth + 1);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
walk(dir, "", 0);
|
|
936
|
+
if (count >= maxEntries) lines.push("... (truncated)");
|
|
937
|
+
return lines.join("\n");
|
|
938
|
+
}
|
|
939
|
+
function scanProject(cwd) {
|
|
940
|
+
const info = {
|
|
941
|
+
type: "unknown",
|
|
942
|
+
language: "unknown",
|
|
943
|
+
configFiles: [],
|
|
944
|
+
directoryStructure: ""
|
|
945
|
+
};
|
|
946
|
+
const check = (file) => existsSync3(join3(cwd, file));
|
|
947
|
+
const configCandidates = [
|
|
948
|
+
"package.json",
|
|
949
|
+
"tsconfig.json",
|
|
950
|
+
"Cargo.toml",
|
|
951
|
+
"pyproject.toml",
|
|
952
|
+
"setup.py",
|
|
953
|
+
"requirements.txt",
|
|
954
|
+
"go.mod",
|
|
955
|
+
"pom.xml",
|
|
956
|
+
"build.gradle",
|
|
957
|
+
"build.gradle.kts",
|
|
958
|
+
"CMakeLists.txt",
|
|
959
|
+
"Makefile",
|
|
960
|
+
".csproj",
|
|
961
|
+
".sln",
|
|
962
|
+
"composer.json",
|
|
963
|
+
"Gemfile",
|
|
964
|
+
"mix.exs",
|
|
965
|
+
"deno.json",
|
|
966
|
+
"bun.lockb"
|
|
967
|
+
];
|
|
968
|
+
info.configFiles = configCandidates.filter(check);
|
|
969
|
+
if (check("package.json")) {
|
|
970
|
+
info.type = "node";
|
|
971
|
+
info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
|
|
972
|
+
try {
|
|
973
|
+
const pkg = JSON.parse(readFileSync3(join3(cwd, "package.json"), "utf-8"));
|
|
974
|
+
const scripts = pkg.scripts ?? {};
|
|
975
|
+
info.buildCommand = scripts.build ? "npm run build" : void 0;
|
|
976
|
+
info.testCommand = scripts.test ? "npm test" : void 0;
|
|
977
|
+
info.devCommand = scripts.dev ? "npm run dev" : void 0;
|
|
978
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
979
|
+
if (allDeps["react"]) info.framework = "React";
|
|
980
|
+
else if (allDeps["vue"]) info.framework = "Vue";
|
|
981
|
+
else if (allDeps["@angular/core"]) info.framework = "Angular";
|
|
982
|
+
else if (allDeps["next"]) info.framework = "Next.js";
|
|
983
|
+
else if (allDeps["nuxt"]) info.framework = "Nuxt";
|
|
984
|
+
else if (allDeps["express"]) info.framework = "Express";
|
|
985
|
+
else if (allDeps["fastify"]) info.framework = "Fastify";
|
|
986
|
+
else if (allDeps["svelte"]) info.framework = "Svelte";
|
|
987
|
+
} catch {
|
|
988
|
+
}
|
|
989
|
+
} else if (check("Cargo.toml")) {
|
|
990
|
+
info.type = "rust";
|
|
991
|
+
info.language = "Rust";
|
|
992
|
+
info.buildCommand = "cargo build";
|
|
993
|
+
info.testCommand = "cargo test";
|
|
994
|
+
} else if (check("pyproject.toml") || check("setup.py") || check("requirements.txt")) {
|
|
995
|
+
info.type = "python";
|
|
996
|
+
info.language = "Python";
|
|
997
|
+
info.testCommand = check("pytest.ini") || check("pyproject.toml") ? "pytest" : "python -m unittest";
|
|
998
|
+
} else if (check("go.mod")) {
|
|
999
|
+
info.type = "go";
|
|
1000
|
+
info.language = "Go";
|
|
1001
|
+
info.buildCommand = "go build ./...";
|
|
1002
|
+
info.testCommand = "go test ./...";
|
|
1003
|
+
} else if (check("pom.xml")) {
|
|
1004
|
+
info.type = "java";
|
|
1005
|
+
info.language = "Java";
|
|
1006
|
+
info.buildCommand = "mvn package";
|
|
1007
|
+
info.testCommand = "mvn test";
|
|
1008
|
+
} else if (check("build.gradle") || check("build.gradle.kts")) {
|
|
1009
|
+
info.type = "java";
|
|
1010
|
+
info.language = "Java/Kotlin";
|
|
1011
|
+
info.buildCommand = "./gradlew build";
|
|
1012
|
+
info.testCommand = "./gradlew test";
|
|
1013
|
+
}
|
|
1014
|
+
info.directoryStructure = scanDirTree(cwd);
|
|
1015
|
+
return info;
|
|
1016
|
+
}
|
|
1017
|
+
function buildInitPrompt(info, cwd) {
|
|
1018
|
+
const parts = [
|
|
1019
|
+
"Please generate an AICLI.md context file (Markdown format) for the following project. This file will be injected into the AI conversation system prompt to help the AI understand the project structure and coding conventions.",
|
|
1020
|
+
"\n## Project Info\n",
|
|
1021
|
+
`- Working directory: ${cwd}`,
|
|
1022
|
+
`- Type: ${info.type}`,
|
|
1023
|
+
`- Language: ${info.language}`
|
|
1024
|
+
];
|
|
1025
|
+
if (info.framework) parts.push(`- Framework: ${info.framework}`);
|
|
1026
|
+
if (info.buildCommand) parts.push(`- Build command: ${info.buildCommand}`);
|
|
1027
|
+
if (info.testCommand) parts.push(`- Test command: ${info.testCommand}`);
|
|
1028
|
+
if (info.devCommand) parts.push(`- Dev command: ${info.devCommand}`);
|
|
1029
|
+
parts.push(`
|
|
1030
|
+
## Detected Config Files
|
|
1031
|
+
${info.configFiles.map((f) => `- ${f}`).join("\n")}`);
|
|
1032
|
+
parts.push(`
|
|
1033
|
+
## Directory Structure
|
|
1034
|
+
\`\`\`
|
|
1035
|
+
${info.directoryStructure}
|
|
1036
|
+
\`\`\``);
|
|
1037
|
+
parts.push(`
|
|
1038
|
+
## Requirements
|
|
1039
|
+
Please generate a structured Markdown file containing:
|
|
1040
|
+
1. Project overview (one-sentence summary)
|
|
1041
|
+
2. Tech stack
|
|
1042
|
+
3. Project structure description (based on the directory structure above)
|
|
1043
|
+
4. Common commands (build, test, dev, etc.)
|
|
1044
|
+
5. Code style and conventions (inferred from config files)
|
|
1045
|
+
|
|
1046
|
+
Output the Markdown content directly, do not wrap the entire file in a code block. Keep it concise, within 200 lines.`);
|
|
1047
|
+
return parts.join("\n");
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// src/core/git-diff.ts
|
|
1051
|
+
import { execFileSync } from "child_process";
|
|
1052
|
+
function readGitDiff(options = {}) {
|
|
1053
|
+
const args = ["diff"];
|
|
1054
|
+
if (options.staged) args.push("--staged");
|
|
1055
|
+
return execFileSync("git", args, {
|
|
1056
|
+
encoding: "utf-8",
|
|
1057
|
+
timeout: options.timeoutMs ?? 1e4
|
|
1058
|
+
}).trim();
|
|
1059
|
+
}
|
|
1060
|
+
|
|
699
1061
|
// src/session/tool-history.ts
|
|
700
1062
|
var SESSION_SIZE_LIMIT = 2 * 1024 * 1024;
|
|
701
1063
|
function persistToolRound(session, toolCalls, toolResults, opts) {
|
|
@@ -784,8 +1146,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
|
|
|
784
1146
|
}
|
|
785
1147
|
|
|
786
1148
|
// src/repl/dev-state.ts
|
|
787
|
-
import { existsSync as
|
|
788
|
-
import { join as
|
|
1149
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
|
|
1150
|
+
import { join as join4 } from "path";
|
|
789
1151
|
import { homedir } from "os";
|
|
790
1152
|
var DEV_STATE_MAX_CHARS = 6e3;
|
|
791
1153
|
var SNAPSHOT_PROMPT = `You are about to be replaced by a different AI model. Please generate a structured development state snapshot so the next model can continue seamlessly.
|
|
@@ -839,11 +1201,11 @@ function sessionHasMeaningfulContent(messages) {
|
|
|
839
1201
|
return hasUser && hasAssistant;
|
|
840
1202
|
}
|
|
841
1203
|
function getDevStatePath() {
|
|
842
|
-
return
|
|
1204
|
+
return join4(homedir(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
|
|
843
1205
|
}
|
|
844
1206
|
function saveDevState(content) {
|
|
845
|
-
const configDir =
|
|
846
|
-
if (!
|
|
1207
|
+
const configDir = join4(homedir(), CONFIG_DIR_NAME);
|
|
1208
|
+
if (!existsSync4(configDir)) {
|
|
847
1209
|
mkdirSync2(configDir, { recursive: true });
|
|
848
1210
|
}
|
|
849
1211
|
let trimmed = content.trim();
|
|
@@ -855,17 +1217,17 @@ function saveDevState(content) {
|
|
|
855
1217
|
}
|
|
856
1218
|
trimmed += "\n\n[...truncated]";
|
|
857
1219
|
}
|
|
858
|
-
|
|
1220
|
+
atomicWriteFileSync(getDevStatePath(), trimmed);
|
|
859
1221
|
}
|
|
860
1222
|
function loadDevState() {
|
|
861
1223
|
const path = getDevStatePath();
|
|
862
|
-
if (!
|
|
863
|
-
const content =
|
|
1224
|
+
if (!existsSync4(path)) return null;
|
|
1225
|
+
const content = readFileSync4(path, "utf-8").trim();
|
|
864
1226
|
return content || null;
|
|
865
1227
|
}
|
|
866
1228
|
function clearDevState() {
|
|
867
1229
|
const path = getDevStatePath();
|
|
868
|
-
if (
|
|
1230
|
+
if (existsSync4(path)) {
|
|
869
1231
|
try {
|
|
870
1232
|
unlinkSync(path);
|
|
871
1233
|
} catch {
|
|
@@ -874,9 +1236,14 @@ function clearDevState() {
|
|
|
874
1236
|
}
|
|
875
1237
|
|
|
876
1238
|
export {
|
|
1239
|
+
scanDirTree,
|
|
1240
|
+
scanProject,
|
|
1241
|
+
buildInitPrompt,
|
|
1242
|
+
readGitDiff,
|
|
877
1243
|
parseSimpleYaml,
|
|
878
1244
|
persistToolRound,
|
|
879
1245
|
autoTrimSessionIfNeeded,
|
|
1246
|
+
loadContextFiles,
|
|
880
1247
|
SNAPSHOT_PROMPT,
|
|
881
1248
|
sessionHasMeaningfulContent,
|
|
882
1249
|
saveDevState,
|
|
@@ -19,12 +19,13 @@ import { Parser, Language } from "web-tree-sitter";
|
|
|
19
19
|
import { createRequire } from "module";
|
|
20
20
|
import path from "path";
|
|
21
21
|
import fs from "fs";
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
var
|
|
22
|
+
var entryCandidates = [
|
|
23
|
+
process.pkg?.entrypoint,
|
|
24
|
+
process.argv[1],
|
|
25
|
+
process.execPath
|
|
26
|
+
].filter((p) => !!p);
|
|
27
|
+
var bundleDirs = [...new Set(entryCandidates.map((p) => path.dirname(path.resolve(p))))];
|
|
28
|
+
var require_ = createRequire(path.join(process.cwd(), "package.json"));
|
|
28
29
|
var GRAMMAR_FILE = {
|
|
29
30
|
typescript: "tree-sitter-typescript.wasm",
|
|
30
31
|
tsx: "tree-sitter-tsx.wasm",
|
|
@@ -41,9 +42,11 @@ var languageCache = /* @__PURE__ */ new Map();
|
|
|
41
42
|
var parserCache = /* @__PURE__ */ new Map();
|
|
42
43
|
function resolveWasmPath(filename) {
|
|
43
44
|
const candidates = [];
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
for (const dir of bundleDirs) {
|
|
46
|
+
candidates.push(path.join(dir, "wasm", filename));
|
|
47
|
+
candidates.push(path.join(dir, "..", "wasm", filename));
|
|
48
|
+
candidates.push(path.join(dir, "..", "dist", "wasm", filename));
|
|
49
|
+
}
|
|
47
50
|
if (process.pkg) {
|
|
48
51
|
candidates.push(path.join(path.dirname(process.execPath), "dist", "wasm", filename));
|
|
49
52
|
candidates.push(path.join("/snapshot", "ai-cli", "dist", "wasm", filename));
|