ai-project-manage-cli 8.1.6 → 8.1.8
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 +3 -1
- package/dist/index.js +274 -220
- package/dist/worker-script.js +315 -344
- package/package.json +1 -1
- package/template/AGENTS.webide.md +0 -28
- package/template/rules/webide_git_commit.md +0 -16
- package/template/rules/webide_merge.md +0 -16
- package/template/rules/webide_package.md +0 -70
- package/template/rules/webide_reply.md +0 -18
- package/template/rules/webide_sql_change.md +0 -28
- package/template/rules/webide_sql_execute.md +0 -29
- package/template/rules/webide_terminal.md +0 -45
- package/template/rules/webide_testcase.md +0 -47
package/dist/index.js
CHANGED
|
@@ -79,179 +79,9 @@ function buildAgentWsUrl(httpBase, apiKey) {
|
|
|
79
79
|
return `${origin}/ws/agent?${q.toString()}`;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
// src/
|
|
83
|
-
import {
|
|
84
|
-
import {
|
|
85
|
-
import { fileURLToPath } from "url";
|
|
86
|
-
|
|
87
|
-
// src/utils/path.ts
|
|
88
|
-
import { realpathSync } from "fs";
|
|
89
|
-
import { platform } from "os";
|
|
90
|
-
import { resolve } from "path";
|
|
91
|
-
function toFsPath(inputPath) {
|
|
92
|
-
const absolute = resolve(inputPath);
|
|
93
|
-
if (platform() !== "win32") return absolute;
|
|
94
|
-
if (absolute.startsWith("\\\\?\\")) return absolute;
|
|
95
|
-
const normalized = absolute.replace(/\//g, "\\");
|
|
96
|
-
if (normalized.startsWith("\\\\")) {
|
|
97
|
-
return `\\\\?\\UNC\\${normalized.slice(2)}`;
|
|
98
|
-
}
|
|
99
|
-
return `\\\\?\\${normalized}`;
|
|
100
|
-
}
|
|
101
|
-
function normalizeWorkdirPath(path) {
|
|
102
|
-
let normalized = path.trim().replace(/\\/g, "/").normalize("NFC");
|
|
103
|
-
if (normalized.startsWith("//?/")) {
|
|
104
|
-
normalized = normalized.slice(4);
|
|
105
|
-
}
|
|
106
|
-
const windowsDrive = /^([A-Za-z]:)\/*(.*)$/.exec(normalized);
|
|
107
|
-
if (windowsDrive) {
|
|
108
|
-
const drive = windowsDrive[1].toLowerCase();
|
|
109
|
-
const rest = windowsDrive[2].replace(/\/+/g, "/").replace(/\/$/, "");
|
|
110
|
-
return rest ? `${drive}/${rest}` : drive;
|
|
111
|
-
}
|
|
112
|
-
normalized = normalized.replace(/\/+/g, "/");
|
|
113
|
-
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
114
|
-
normalized = normalized.slice(0, -1);
|
|
115
|
-
}
|
|
116
|
-
return normalized;
|
|
117
|
-
}
|
|
118
|
-
function resolveWorkdirPath(cwd = process.cwd()) {
|
|
119
|
-
const absolute = resolve(cwd);
|
|
120
|
-
try {
|
|
121
|
-
return normalizeWorkdirPath(realpathSync.native(absolute));
|
|
122
|
-
} catch {
|
|
123
|
-
return normalizeWorkdirPath(absolute);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// src/utils/workspace.ts
|
|
128
|
-
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
129
|
-
var CLI_TEMPLATE_DIR = resolve2(__dirname, "../template");
|
|
130
|
-
function workspaceApmDir(cwd = resolveWorkdirPath()) {
|
|
131
|
-
return resolve2(resolve2(cwd), ".apm");
|
|
132
|
-
}
|
|
133
|
-
async function ensureLoggedConfig() {
|
|
134
|
-
const cfg = await ensureApmConfig();
|
|
135
|
-
if (!resolveApiKey(cfg)) {
|
|
136
|
-
console.error(
|
|
137
|
-
"[apm] \u672A\u68C0\u6D4B\u5230 API Key\uFF0C\u8BF7\u5148\u6267\u884C: apm login --api-key cm_xxx"
|
|
138
|
-
);
|
|
139
|
-
process.exit(1);
|
|
140
|
-
}
|
|
141
|
-
return cfg;
|
|
142
|
-
}
|
|
143
|
-
async function ensureDirExists(dir) {
|
|
144
|
-
mkdirSync2(dir, { recursive: true });
|
|
145
|
-
}
|
|
146
|
-
async function ensureWorkspaceApmDirForInit(cwd = resolveWorkdirPath()) {
|
|
147
|
-
const dir = workspaceApmDir(cwd);
|
|
148
|
-
const fsDir = toFsPath(dir);
|
|
149
|
-
if (existsSync(fsDir) && !statSync(fsDir).isDirectory()) {
|
|
150
|
-
throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${dir}`);
|
|
151
|
-
}
|
|
152
|
-
mkdirSync2(fsDir, { recursive: true });
|
|
153
|
-
}
|
|
154
|
-
var WEBIDE_AGENTS_TEMPLATE_NAME = "AGENTS.webide.md";
|
|
155
|
-
function shouldSkipTemplateEntry(name) {
|
|
156
|
-
return name === ".DS_Store" || name === "Thumbs.db";
|
|
157
|
-
}
|
|
158
|
-
function isWebIdeRuleFileName(name) {
|
|
159
|
-
return name.startsWith("webide");
|
|
160
|
-
}
|
|
161
|
-
function copyTemplateEntry(src, dest) {
|
|
162
|
-
const fsSrc = toFsPath(src);
|
|
163
|
-
const fsDest = toFsPath(dest);
|
|
164
|
-
const st = statSync(fsSrc);
|
|
165
|
-
if (st.isDirectory()) {
|
|
166
|
-
mkdirSync2(fsDest, { recursive: true });
|
|
167
|
-
for (const name of readdirSync(fsSrc)) {
|
|
168
|
-
if (shouldSkipTemplateEntry(name)) continue;
|
|
169
|
-
copyTemplateEntry(join2(src, name), join2(dest, name));
|
|
170
|
-
}
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
if (!st.isFile()) return;
|
|
174
|
-
mkdirSync2(toFsPath(dirname(dest)), { recursive: true });
|
|
175
|
-
copyFileSync(fsSrc, fsDest);
|
|
176
|
-
}
|
|
177
|
-
function listWebIdeRuleFileNames(rulesDir) {
|
|
178
|
-
const fsRulesDir = toFsPath(rulesDir);
|
|
179
|
-
if (!existsSync(fsRulesDir) || !statSync(fsRulesDir).isDirectory()) {
|
|
180
|
-
return [];
|
|
181
|
-
}
|
|
182
|
-
return readdirSync(fsRulesDir).filter((name) => {
|
|
183
|
-
if (shouldSkipTemplateEntry(name) || !isWebIdeRuleFileName(name)) {
|
|
184
|
-
return false;
|
|
185
|
-
}
|
|
186
|
-
return statSync(toFsPath(join2(rulesDir, name))).isFile();
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
function assertWebIdeTemplateCopiedToApm(apmDir, workdir) {
|
|
190
|
-
const required = ["AGENTS.md", "rules"];
|
|
191
|
-
for (const item of required) {
|
|
192
|
-
const path = join2(apmDir, item);
|
|
193
|
-
if (!existsSync(toFsPath(path))) {
|
|
194
|
-
throw new Error(`[apm] WebIDE \u521D\u59CB\u5316\u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5C11: ${path}`);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
const rulesDir = join2(apmDir, "rules");
|
|
198
|
-
const ruleNames = listWebIdeRuleFileNames(rulesDir);
|
|
199
|
-
if (ruleNames.length === 0) {
|
|
200
|
-
throw new Error(`[apm] WebIDE \u521D\u59CB\u5316\u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5C11 webide \u89C4\u5219: ${rulesDir}`);
|
|
201
|
-
}
|
|
202
|
-
const leakedRules = join2(workdir, "rules");
|
|
203
|
-
const apmRules = join2(apmDir, "rules");
|
|
204
|
-
if (existsSync(toFsPath(leakedRules)) && !existsSync(toFsPath(join2(apmRules, "webide_reply.md")))) {
|
|
205
|
-
throw new Error(
|
|
206
|
-
`[apm] \u6A21\u677F\u88AB\u590D\u5236\u5230\u9519\u8BEF\u4F4D\u7F6E: ${leakedRules}\uFF08\u5E94\u5728 ${apmRules}\uFF09`
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
async function copyWebIdeTemplateFiles(targetDir, workdir = resolveWorkdirPath()) {
|
|
211
|
-
const resolvedTarget = resolve2(targetDir);
|
|
212
|
-
const templateDir = resolve2(CLI_TEMPLATE_DIR);
|
|
213
|
-
const fsTemplateDir = toFsPath(templateDir);
|
|
214
|
-
if (!existsSync(fsTemplateDir) || !statSync(fsTemplateDir).isDirectory()) {
|
|
215
|
-
throw new Error(`[apm] \u672A\u627E\u5230 CLI \u6A21\u677F\u76EE\u5F55: ${templateDir}`);
|
|
216
|
-
}
|
|
217
|
-
const agentsSrc = join2(templateDir, WEBIDE_AGENTS_TEMPLATE_NAME);
|
|
218
|
-
if (!existsSync(toFsPath(agentsSrc))) {
|
|
219
|
-
throw new Error(`[apm] \u672A\u627E\u5230 WebIDE \u6307\u5357: ${agentsSrc}`);
|
|
220
|
-
}
|
|
221
|
-
const rulesSrc = join2(templateDir, "rules");
|
|
222
|
-
const ruleNames = listWebIdeRuleFileNames(rulesSrc);
|
|
223
|
-
if (ruleNames.length === 0) {
|
|
224
|
-
throw new Error(`[apm] CLI \u6A21\u677F\u4E2D\u6CA1\u6709 webide \u89C4\u5219: ${rulesSrc}`);
|
|
225
|
-
}
|
|
226
|
-
mkdirSync2(toFsPath(resolvedTarget), { recursive: true });
|
|
227
|
-
copyFileSync(
|
|
228
|
-
toFsPath(agentsSrc),
|
|
229
|
-
toFsPath(join2(resolvedTarget, "AGENTS.md"))
|
|
230
|
-
);
|
|
231
|
-
const rulesDest = join2(resolvedTarget, "rules");
|
|
232
|
-
mkdirSync2(toFsPath(rulesDest), { recursive: true });
|
|
233
|
-
for (const name of ruleNames) {
|
|
234
|
-
copyTemplateEntry(join2(rulesSrc, name), join2(rulesDest, name));
|
|
235
|
-
}
|
|
236
|
-
assertWebIdeTemplateCopiedToApm(resolvedTarget, resolve2(workdir));
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// src/commands/init.ts
|
|
240
|
-
async function initializeWorkspace(workdir) {
|
|
241
|
-
await ensureWorkspaceApmDirForInit(workdir);
|
|
242
|
-
const apmDir = workspaceApmDir(workdir);
|
|
243
|
-
await copyWebIdeTemplateFiles(apmDir, workdir);
|
|
244
|
-
console.log(`[apm] \u5DF2\u6309 WebIDE \u521D\u59CB\u5316\u5DE5\u4F5C\u533A\uFF1A${apmDir}`);
|
|
245
|
-
return { didInit: true };
|
|
246
|
-
}
|
|
247
|
-
async function runInit() {
|
|
248
|
-
const workdir = resolveWorkdirPath();
|
|
249
|
-
await initializeWorkspace(workdir);
|
|
250
|
-
console.log(`[apm] \u5DE5\u4F5C\u76EE\u5F55\u8DEF\u5F84\uFF1A${workdir}`);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// src/commands/login.ts
|
|
254
|
-
import { ApiError } from "listpage-http";
|
|
82
|
+
// src/commands/sync-webide-agent.ts
|
|
83
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
84
|
+
import { join as join3 } from "path";
|
|
255
85
|
|
|
256
86
|
// src/api/client.ts
|
|
257
87
|
import { createApiClient } from "listpage-http";
|
|
@@ -266,6 +96,10 @@ var requestConfig = {
|
|
|
266
96
|
path: "/cli/me"
|
|
267
97
|
}
|
|
268
98
|
),
|
|
99
|
+
webideAgentConfig: defineEndpoint({
|
|
100
|
+
method: "GET",
|
|
101
|
+
path: "/cli/webide/agent-config"
|
|
102
|
+
}),
|
|
269
103
|
webideAppendMessageContent: defineEndpoint({
|
|
270
104
|
method: "PUT",
|
|
271
105
|
path: "/cli/webide/messages/content"
|
|
@@ -324,6 +158,14 @@ var requestConfig = {
|
|
|
324
158
|
method: "PUT",
|
|
325
159
|
path: "/cli/webide/plan"
|
|
326
160
|
}),
|
|
161
|
+
webideUpsertChecklist: defineEndpoint({
|
|
162
|
+
method: "PUT",
|
|
163
|
+
path: "/cli/webide/checklist"
|
|
164
|
+
}),
|
|
165
|
+
webideGetChecklist: defineEndpoint({
|
|
166
|
+
method: "GET",
|
|
167
|
+
path: "/cli/webide/checklist"
|
|
168
|
+
}),
|
|
327
169
|
webideReplaceTestCases: defineEndpoint({
|
|
328
170
|
method: "PUT",
|
|
329
171
|
path: "/cli/webide/test-cases"
|
|
@@ -456,7 +298,157 @@ function createApmApiClient(cfg) {
|
|
|
456
298
|
});
|
|
457
299
|
}
|
|
458
300
|
|
|
301
|
+
// src/utils/path.ts
|
|
302
|
+
import { realpathSync } from "fs";
|
|
303
|
+
import { platform } from "os";
|
|
304
|
+
import { resolve } from "path";
|
|
305
|
+
function toFsPath(inputPath) {
|
|
306
|
+
const absolute = resolve(inputPath);
|
|
307
|
+
if (platform() !== "win32") return absolute;
|
|
308
|
+
if (absolute.startsWith("\\\\?\\")) return absolute;
|
|
309
|
+
const normalized = absolute.replace(/\//g, "\\");
|
|
310
|
+
if (normalized.startsWith("\\\\")) {
|
|
311
|
+
return `\\\\?\\UNC\\${normalized.slice(2)}`;
|
|
312
|
+
}
|
|
313
|
+
return `\\\\?\\${normalized}`;
|
|
314
|
+
}
|
|
315
|
+
function normalizeWorkdirPath(path) {
|
|
316
|
+
let normalized = path.trim().replace(/\\/g, "/").normalize("NFC");
|
|
317
|
+
if (normalized.startsWith("//?/")) {
|
|
318
|
+
normalized = normalized.slice(4);
|
|
319
|
+
}
|
|
320
|
+
const windowsDrive = /^([A-Za-z]:)\/*(.*)$/.exec(normalized);
|
|
321
|
+
if (windowsDrive) {
|
|
322
|
+
const drive = windowsDrive[1].toLowerCase();
|
|
323
|
+
const rest = windowsDrive[2].replace(/\/+/g, "/").replace(/\/$/, "");
|
|
324
|
+
return rest ? `${drive}/${rest}` : drive;
|
|
325
|
+
}
|
|
326
|
+
normalized = normalized.replace(/\/+/g, "/");
|
|
327
|
+
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
328
|
+
normalized = normalized.slice(0, -1);
|
|
329
|
+
}
|
|
330
|
+
return normalized;
|
|
331
|
+
}
|
|
332
|
+
function resolveWorkdirPath(cwd = process.cwd()) {
|
|
333
|
+
const absolute = resolve(cwd);
|
|
334
|
+
try {
|
|
335
|
+
return normalizeWorkdirPath(realpathSync.native(absolute));
|
|
336
|
+
} catch {
|
|
337
|
+
return normalizeWorkdirPath(absolute);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// src/utils/workspace.ts
|
|
342
|
+
import { existsSync, mkdirSync as mkdirSync2, readdirSync, statSync } from "fs";
|
|
343
|
+
import { join as join2, resolve as resolve2 } from "path";
|
|
344
|
+
function workspaceApmDir(cwd = resolveWorkdirPath()) {
|
|
345
|
+
return resolve2(resolve2(cwd), ".apm");
|
|
346
|
+
}
|
|
347
|
+
async function ensureLoggedConfig() {
|
|
348
|
+
const cfg = await ensureApmConfig();
|
|
349
|
+
if (!resolveApiKey(cfg)) {
|
|
350
|
+
console.error(
|
|
351
|
+
"[apm] \u672A\u68C0\u6D4B\u5230 API Key\uFF0C\u8BF7\u5148\u6267\u884C: apm login --api-key cm_xxx"
|
|
352
|
+
);
|
|
353
|
+
process.exit(1);
|
|
354
|
+
}
|
|
355
|
+
return cfg;
|
|
356
|
+
}
|
|
357
|
+
async function ensureDirExists(dir) {
|
|
358
|
+
mkdirSync2(dir, { recursive: true });
|
|
359
|
+
}
|
|
360
|
+
async function ensureWorkspaceApmDirForInit(cwd = resolveWorkdirPath()) {
|
|
361
|
+
const dir = workspaceApmDir(cwd);
|
|
362
|
+
const fsDir = toFsPath(dir);
|
|
363
|
+
if (existsSync(fsDir) && !statSync(fsDir).isDirectory()) {
|
|
364
|
+
throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${dir}`);
|
|
365
|
+
}
|
|
366
|
+
mkdirSync2(fsDir, { recursive: true });
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// src/commands/sync-webide-agent.ts
|
|
370
|
+
var REVISION_FILE = ".webide-agent.json";
|
|
371
|
+
var RULE_KEY = /^webide_[a-z0-9_]+$/;
|
|
372
|
+
function withTrailingNewline(text) {
|
|
373
|
+
return text.endsWith("\n") ? text : `${text}
|
|
374
|
+
`;
|
|
375
|
+
}
|
|
376
|
+
function readRevision(apmDir) {
|
|
377
|
+
const path = toFsPath(join3(apmDir, REVISION_FILE));
|
|
378
|
+
if (!existsSync2(path)) return null;
|
|
379
|
+
try {
|
|
380
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
381
|
+
if (parsed?.version === 1 && parsed.revision?.trim()) {
|
|
382
|
+
return parsed.revision.trim();
|
|
383
|
+
}
|
|
384
|
+
} catch {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
function writeRevision(apmDir, revision) {
|
|
390
|
+
writeFileSync2(
|
|
391
|
+
toFsPath(join3(apmDir, REVISION_FILE)),
|
|
392
|
+
`${JSON.stringify({ version: 1, revision }, null, 2)}
|
|
393
|
+
`,
|
|
394
|
+
"utf8"
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
async function syncWebIdeAgentFiles(workdir, cfg) {
|
|
398
|
+
const api = createApmApiClient(cfg);
|
|
399
|
+
const bundle = await api.cli.webideAgentConfig();
|
|
400
|
+
const revision = bundle.revision?.trim();
|
|
401
|
+
if (!revision) {
|
|
402
|
+
throw new Error("[apm] \u5E73\u53F0\u672A\u8FD4\u56DE WebIDE \u89C4\u5219\u7248\u672C");
|
|
403
|
+
}
|
|
404
|
+
const apmDir = workspaceApmDir(workdir);
|
|
405
|
+
mkdirSync3(toFsPath(apmDir), { recursive: true });
|
|
406
|
+
if (readRevision(apmDir) === revision) {
|
|
407
|
+
return { revision, wrote: false, ruleCount: bundle.rules?.length ?? 0 };
|
|
408
|
+
}
|
|
409
|
+
const rulesDir = join3(apmDir, "rules");
|
|
410
|
+
mkdirSync3(toFsPath(rulesDir), { recursive: true });
|
|
411
|
+
writeFileSync2(
|
|
412
|
+
toFsPath(join3(apmDir, "AGENTS.md")),
|
|
413
|
+
withTrailingNewline(bundle.guide ?? ""),
|
|
414
|
+
"utf8"
|
|
415
|
+
);
|
|
416
|
+
for (const rule of bundle.rules ?? []) {
|
|
417
|
+
const key = rule.key?.trim() ?? "";
|
|
418
|
+
if (!RULE_KEY.test(key)) {
|
|
419
|
+
throw new Error(`[apm] \u975E\u6CD5\u89C4\u5219\u540D: ${key || "(\u7A7A)"}`);
|
|
420
|
+
}
|
|
421
|
+
writeFileSync2(
|
|
422
|
+
toFsPath(join3(rulesDir, `${key}.md`)),
|
|
423
|
+
withTrailingNewline(rule.body ?? ""),
|
|
424
|
+
"utf8"
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
writeRevision(apmDir, revision);
|
|
428
|
+
return {
|
|
429
|
+
revision,
|
|
430
|
+
wrote: true,
|
|
431
|
+
ruleCount: bundle.rules?.length ?? 0
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// src/commands/init.ts
|
|
436
|
+
async function initializeWorkspace(workdir, cfg) {
|
|
437
|
+
await ensureWorkspaceApmDirForInit(workdir);
|
|
438
|
+
await syncWebIdeAgentFiles(workdir, cfg);
|
|
439
|
+
const apmDir = workspaceApmDir(workdir);
|
|
440
|
+
console.log(`[apm] \u5DF2\u6309 WebIDE \u521D\u59CB\u5316\u5DE5\u4F5C\u533A\uFF1A${apmDir}`);
|
|
441
|
+
return { didInit: true };
|
|
442
|
+
}
|
|
443
|
+
async function runInit() {
|
|
444
|
+
const workdir = resolveWorkdirPath();
|
|
445
|
+
const cfg = await ensureLoggedConfig();
|
|
446
|
+
await initializeWorkspace(workdir, cfg);
|
|
447
|
+
console.log(`[apm] \u5DE5\u4F5C\u76EE\u5F55\u8DEF\u5F84\uFF1A${workdir}`);
|
|
448
|
+
}
|
|
449
|
+
|
|
459
450
|
// src/commands/login.ts
|
|
451
|
+
import { ApiError } from "listpage-http";
|
|
460
452
|
async function runLogin(opts) {
|
|
461
453
|
const baseUrl = (opts.server?.trim() || process.env.AI_PM_SERVER?.trim() || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
462
454
|
const apiKey = (opts.apiKey?.trim() || process.env.APM_API_KEY?.trim() || "").replace(/^Bearer\s+/i, "");
|
|
@@ -512,17 +504,19 @@ async function runLogin(opts) {
|
|
|
512
504
|
|
|
513
505
|
// src/commands/update.ts
|
|
514
506
|
import { spawnSync } from "child_process";
|
|
507
|
+
import { existsSync as existsSync3 } from "fs";
|
|
508
|
+
import { join as join5 } from "path";
|
|
515
509
|
|
|
516
510
|
// src/version.ts
|
|
517
|
-
import { readFileSync as
|
|
518
|
-
import { dirname
|
|
519
|
-
import { fileURLToPath
|
|
511
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
512
|
+
import { dirname, join as join4 } from "path";
|
|
513
|
+
import { fileURLToPath } from "url";
|
|
520
514
|
var CLI_PACKAGE_NAME = "ai-project-manage-cli";
|
|
521
515
|
function readCliVersion() {
|
|
522
516
|
try {
|
|
523
|
-
const dir =
|
|
524
|
-
const pkgPath =
|
|
525
|
-
const pkg = JSON.parse(
|
|
517
|
+
const dir = dirname(fileURLToPath(import.meta.url));
|
|
518
|
+
const pkgPath = join4(dir, "..", "package.json");
|
|
519
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
|
|
526
520
|
return pkg.version ?? "0.0.0";
|
|
527
521
|
} catch {
|
|
528
522
|
return "0.0.0";
|
|
@@ -530,6 +524,7 @@ function readCliVersion() {
|
|
|
530
524
|
}
|
|
531
525
|
|
|
532
526
|
// src/commands/update.ts
|
|
527
|
+
var APM_SKIP_SELF_UPDATE_ENV = "APM_SKIP_SELF_UPDATE";
|
|
533
528
|
var useNpmShell = process.platform === "win32";
|
|
534
529
|
function runNpm(args, options = {}) {
|
|
535
530
|
return spawnSync(useNpmShell ? "npm.cmd" : "npm", args, {
|
|
@@ -622,10 +617,17 @@ function npmAvailable() {
|
|
|
622
617
|
return !r.error && r.status === 0;
|
|
623
618
|
}
|
|
624
619
|
async function runUpdate(options = {}) {
|
|
620
|
+
if (process.env[APM_SKIP_SELF_UPDATE_ENV] === "1") {
|
|
621
|
+
return { didUpdate: false };
|
|
622
|
+
}
|
|
625
623
|
const current = readCliVersion();
|
|
626
624
|
const currentMajor = parseMajorVersion(current);
|
|
627
625
|
const globalLatest = await fetchLatestPublishedVersion();
|
|
628
626
|
const latestInMajor = options.allowMajorUpgrade ? null : await fetchLatestPublishedVersionInMajor(currentMajor);
|
|
627
|
+
if (options.quietIfCurrent && !options.allowMajorUpgrade && !latestInMajor && !globalLatest) {
|
|
628
|
+
console.warn(`[apm] \u65E0\u6CD5\u68C0\u6D4B\u6700\u65B0\u7248\u672C\uFF0C\u7EE7\u7EED\u4F7F\u7528\u5F53\u524D ${current}`);
|
|
629
|
+
return { didUpdate: false };
|
|
630
|
+
}
|
|
629
631
|
const resolved = resolveUpdateTarget({
|
|
630
632
|
current,
|
|
631
633
|
allowMajorUpgrade: options.allowMajorUpgrade,
|
|
@@ -633,8 +635,10 @@ async function runUpdate(options = {}) {
|
|
|
633
635
|
globalLatest
|
|
634
636
|
});
|
|
635
637
|
if (resolved.action === "skip") {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
+
if (!options.quietIfCurrent) {
|
|
639
|
+
console.log(`[apm] \u5DF2\u662F\u6700\u65B0\u7248\u672C ${current}`);
|
|
640
|
+
}
|
|
641
|
+
return { didUpdate: false };
|
|
638
642
|
}
|
|
639
643
|
if (resolved.majorUpgradeAvailable) {
|
|
640
644
|
console.log(
|
|
@@ -671,26 +675,72 @@ async function runUpdate(options = {}) {
|
|
|
671
675
|
} else {
|
|
672
676
|
console.log(`[apm] \u5DF2\u662F\u6700\u65B0\u7248\u672C ${current}`);
|
|
673
677
|
}
|
|
678
|
+
return { didUpdate: expectedBump };
|
|
679
|
+
}
|
|
680
|
+
function resolveApmEntryPath(entryArg = process.argv[1]) {
|
|
681
|
+
const npmResult = runNpm(["root", "-g"], {
|
|
682
|
+
encoding: "utf8",
|
|
683
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
684
|
+
});
|
|
685
|
+
if (npmResult.status === 0) {
|
|
686
|
+
const globalRoot = npmResult.stdout?.toString().trim();
|
|
687
|
+
if (globalRoot) {
|
|
688
|
+
const candidate = join5(globalRoot, CLI_PACKAGE_NAME, "dist", "index.js");
|
|
689
|
+
if (existsSync3(candidate)) {
|
|
690
|
+
return candidate;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
const fromArgv = entryArg?.trim();
|
|
695
|
+
if (fromArgv && existsSync3(fromArgv)) {
|
|
696
|
+
return fromArgv;
|
|
697
|
+
}
|
|
698
|
+
if (fromArgv) {
|
|
699
|
+
return fromArgv;
|
|
700
|
+
}
|
|
701
|
+
console.error("[apm] \u65E0\u6CD5\u89E3\u6790 apm \u5165\u53E3\u8DEF\u5F84");
|
|
702
|
+
process.exit(1);
|
|
703
|
+
}
|
|
704
|
+
function reexecConnectAfterUpdate(options) {
|
|
705
|
+
console.log("[apm] \u66F4\u65B0\u5B8C\u6210\uFF0C\u6B63\u5728\u4EE5\u65B0\u7248\u672C\u91CD\u65B0\u8FDE\u63A5\u2026");
|
|
706
|
+
const apmScript = resolveApmEntryPath();
|
|
707
|
+
const args = [apmScript, "connect"];
|
|
708
|
+
const server = options.server?.trim();
|
|
709
|
+
if (server) {
|
|
710
|
+
args.push("--server", server);
|
|
711
|
+
}
|
|
712
|
+
const result = spawnSync(process.execPath, args, {
|
|
713
|
+
stdio: "inherit",
|
|
714
|
+
env: { ...process.env, [APM_SKIP_SELF_UPDATE_ENV]: "1" }
|
|
715
|
+
});
|
|
716
|
+
if (result.error) {
|
|
717
|
+
console.error("[apm] \u91CD\u542F connect \u5931\u8D25:", result.error.message);
|
|
718
|
+
process.exit(1);
|
|
719
|
+
}
|
|
720
|
+
process.exit(result.status ?? 0);
|
|
674
721
|
}
|
|
675
722
|
|
|
676
723
|
// src/commands/update-skills.ts
|
|
677
|
-
import { existsSync as
|
|
724
|
+
import { existsSync as existsSync4, statSync as statSync2 } from "fs";
|
|
678
725
|
async function syncWorkspaceSkills(workdir) {
|
|
679
726
|
const apmDir = workspaceApmDir(workdir);
|
|
680
727
|
const fsApmDir = toFsPath(apmDir);
|
|
681
|
-
if (!
|
|
728
|
+
if (!existsSync4(fsApmDir)) {
|
|
682
729
|
throw new Error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
|
|
683
730
|
}
|
|
684
731
|
const apmStat = statSync2(fsApmDir);
|
|
685
732
|
if (!apmStat.isDirectory()) {
|
|
686
733
|
throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
|
|
687
734
|
}
|
|
688
|
-
await
|
|
689
|
-
|
|
735
|
+
const cfg = await ensureLoggedConfig();
|
|
736
|
+
const result = await syncWebIdeAgentFiles(workdir, cfg);
|
|
737
|
+
console.log(
|
|
738
|
+
result.wrote ? `[apm] \u5DF2\u540C\u6B65 WebIDE \u6307\u5357\u4E0E ${result.ruleCount} \u6761\u89C4\u5219` : "[apm] WebIDE \u6307\u5357\u4E0E\u89C4\u5219\u672A\u53D8\u5316"
|
|
739
|
+
);
|
|
690
740
|
}
|
|
691
741
|
async function runUpdateSkills() {
|
|
692
742
|
const apmDir = workspaceApmDir();
|
|
693
|
-
if (!
|
|
743
|
+
if (!existsSync4(apmDir)) {
|
|
694
744
|
console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
|
|
695
745
|
process.exit(1);
|
|
696
746
|
}
|
|
@@ -703,14 +753,14 @@ async function runUpdateSkills() {
|
|
|
703
753
|
|
|
704
754
|
// src/utils/project-documents.ts
|
|
705
755
|
import {
|
|
706
|
-
existsSync as
|
|
756
|
+
existsSync as existsSync5,
|
|
707
757
|
readdirSync as readdirSync2,
|
|
708
|
-
readFileSync as
|
|
758
|
+
readFileSync as readFileSync4,
|
|
709
759
|
rmSync,
|
|
710
|
-
writeFileSync as
|
|
760
|
+
writeFileSync as writeFileSync3
|
|
711
761
|
} from "fs";
|
|
712
762
|
import { createHash } from "crypto";
|
|
713
|
-
import { dirname as
|
|
763
|
+
import { dirname as dirname2, join as join6, relative, sep } from "path";
|
|
714
764
|
|
|
715
765
|
// src/utils/git.ts
|
|
716
766
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -784,11 +834,11 @@ function normalizeProjectIdForPath(projectId) {
|
|
|
784
834
|
}
|
|
785
835
|
function projectDocumentsDir(apmRoot, projectId) {
|
|
786
836
|
const id = normalizeProjectIdForPath(projectId);
|
|
787
|
-
return
|
|
837
|
+
return join6(apmRoot ?? workspaceApmDir(), "project", id);
|
|
788
838
|
}
|
|
789
839
|
function projectDocumentLocalPath(apmRoot, projectId, documentPath) {
|
|
790
840
|
const normalized = normalizeLocalDocumentPath(documentPath);
|
|
791
|
-
return
|
|
841
|
+
return join6(
|
|
792
842
|
projectDocumentsDir(apmRoot, projectId),
|
|
793
843
|
...normalized.split("/")
|
|
794
844
|
);
|
|
@@ -808,16 +858,16 @@ function hashLocalFileContent(content) {
|
|
|
808
858
|
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
809
859
|
}
|
|
810
860
|
function readLocalManifest(apmRoot, projectId) {
|
|
811
|
-
const manifestPath =
|
|
861
|
+
const manifestPath = join6(
|
|
812
862
|
projectDocumentsDir(apmRoot, projectId),
|
|
813
863
|
MANIFEST_FILE
|
|
814
864
|
);
|
|
815
|
-
if (!
|
|
865
|
+
if (!existsSync5(manifestPath)) {
|
|
816
866
|
return null;
|
|
817
867
|
}
|
|
818
868
|
try {
|
|
819
869
|
return JSON.parse(
|
|
820
|
-
|
|
870
|
+
readFileSync4(manifestPath, "utf8")
|
|
821
871
|
);
|
|
822
872
|
} catch {
|
|
823
873
|
return null;
|
|
@@ -825,13 +875,13 @@ function readLocalManifest(apmRoot, projectId) {
|
|
|
825
875
|
}
|
|
826
876
|
function listLocalDocumentPaths(apmRoot, projectId) {
|
|
827
877
|
const root = projectDocumentsDir(apmRoot, projectId);
|
|
828
|
-
if (!
|
|
878
|
+
if (!existsSync5(root)) {
|
|
829
879
|
return [];
|
|
830
880
|
}
|
|
831
881
|
const paths = [];
|
|
832
882
|
const walk = (dir) => {
|
|
833
883
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
834
|
-
const abs =
|
|
884
|
+
const abs = join6(dir, entry.name);
|
|
835
885
|
if (entry.isDirectory()) {
|
|
836
886
|
walk(abs);
|
|
837
887
|
continue;
|
|
@@ -950,8 +1000,8 @@ ${diagnostic ?? ""}`);
|
|
|
950
1000
|
const absPath = toFsPath(
|
|
951
1001
|
projectDocumentLocalPath(targetApmDir, projectId, doc.path)
|
|
952
1002
|
);
|
|
953
|
-
await ensureDirExists(
|
|
954
|
-
|
|
1003
|
+
await ensureDirExists(dirname2(absPath));
|
|
1004
|
+
writeFileSync3(absPath, doc.content, "utf8");
|
|
955
1005
|
downloaded += 1;
|
|
956
1006
|
}
|
|
957
1007
|
}
|
|
@@ -960,13 +1010,13 @@ ${diagnostic ?? ""}`);
|
|
|
960
1010
|
const absPath = toFsPath(
|
|
961
1011
|
projectDocumentLocalPath(targetApmDir, projectId, path)
|
|
962
1012
|
);
|
|
963
|
-
if (
|
|
1013
|
+
if (existsSync5(absPath)) {
|
|
964
1014
|
rmSync(absPath, { force: true });
|
|
965
1015
|
deleted += 1;
|
|
966
1016
|
}
|
|
967
1017
|
}
|
|
968
|
-
|
|
969
|
-
toFsPath(
|
|
1018
|
+
writeFileSync3(
|
|
1019
|
+
toFsPath(join6(docsDir, MANIFEST_FILE)),
|
|
970
1020
|
`${JSON.stringify(remoteManifest, null, 2)}
|
|
971
1021
|
`,
|
|
972
1022
|
"utf8"
|
|
@@ -1009,7 +1059,7 @@ async function syncProjectDocumentsPush(cfg, workdirPath, apmRoot, options) {
|
|
|
1009
1059
|
const absPath = toFsPath(
|
|
1010
1060
|
projectDocumentLocalPath(targetApmDir, projectId, path)
|
|
1011
1061
|
);
|
|
1012
|
-
const content =
|
|
1062
|
+
const content = readFileSync4(absPath, "utf8");
|
|
1013
1063
|
const contentHash = hashLocalFileContent(content);
|
|
1014
1064
|
if (remoteHashByPath.get(path) === contentHash) {
|
|
1015
1065
|
continue;
|
|
@@ -1284,8 +1334,8 @@ function handleCancelAction(payload, ctx) {
|
|
|
1284
1334
|
}
|
|
1285
1335
|
|
|
1286
1336
|
// src/commands/connect/core/worker/worker.ts
|
|
1287
|
-
import { dirname as
|
|
1288
|
-
import { fileURLToPath as
|
|
1337
|
+
import { dirname as dirname3, join as join7 } from "path";
|
|
1338
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1289
1339
|
import { Worker } from "node:worker_threads";
|
|
1290
1340
|
|
|
1291
1341
|
// src/commands/connect/core/worker/config.ts
|
|
@@ -1310,8 +1360,8 @@ function resolveWebIdeWorkerHeapMb(env = process.env, totalMemoryBytes = totalme
|
|
|
1310
1360
|
}
|
|
1311
1361
|
|
|
1312
1362
|
// src/commands/connect/core/worker/worker.ts
|
|
1313
|
-
var workerFile =
|
|
1314
|
-
|
|
1363
|
+
var workerFile = join7(
|
|
1364
|
+
dirname3(fileURLToPath2(import.meta.url)),
|
|
1315
1365
|
"worker-script.js"
|
|
1316
1366
|
);
|
|
1317
1367
|
var ActionWorker = class {
|
|
@@ -1663,14 +1713,14 @@ function dispatchConnectAction(payload, ctx) {
|
|
|
1663
1713
|
|
|
1664
1714
|
// src/commands/connect-lock.ts
|
|
1665
1715
|
import {
|
|
1666
|
-
existsSync as
|
|
1667
|
-
mkdirSync as
|
|
1668
|
-
readFileSync as
|
|
1716
|
+
existsSync as existsSync6,
|
|
1717
|
+
mkdirSync as mkdirSync4,
|
|
1718
|
+
readFileSync as readFileSync5,
|
|
1669
1719
|
unlinkSync,
|
|
1670
|
-
writeFileSync as
|
|
1720
|
+
writeFileSync as writeFileSync4
|
|
1671
1721
|
} from "fs";
|
|
1672
|
-
import { join as
|
|
1673
|
-
var CONNECT_LOCK_PATH =
|
|
1722
|
+
import { join as join8 } from "path";
|
|
1723
|
+
var CONNECT_LOCK_PATH = join8(APM_CONFIG_DIR, "connect.lock");
|
|
1674
1724
|
function isProcessAlive(pid) {
|
|
1675
1725
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
1676
1726
|
try {
|
|
@@ -1681,9 +1731,9 @@ function isProcessAlive(pid) {
|
|
|
1681
1731
|
}
|
|
1682
1732
|
}
|
|
1683
1733
|
function readConnectLock() {
|
|
1684
|
-
if (!
|
|
1734
|
+
if (!existsSync6(CONNECT_LOCK_PATH)) return null;
|
|
1685
1735
|
try {
|
|
1686
|
-
const raw =
|
|
1736
|
+
const raw = readFileSync5(CONNECT_LOCK_PATH, "utf8");
|
|
1687
1737
|
const parsed = JSON.parse(raw);
|
|
1688
1738
|
if (typeof parsed.pid !== "number" || typeof parsed.startedAt !== "string") {
|
|
1689
1739
|
return null;
|
|
@@ -1715,12 +1765,12 @@ function acquireConnectLock() {
|
|
|
1715
1765
|
console.error(`[apm] \u5DF2\u6709 apm connect \u5728\u8FD0\u884C (pid=${existing.pid})`);
|
|
1716
1766
|
process.exit(1);
|
|
1717
1767
|
}
|
|
1718
|
-
|
|
1768
|
+
mkdirSync4(APM_CONFIG_DIR, { recursive: true });
|
|
1719
1769
|
const lock = {
|
|
1720
1770
|
pid: process.pid,
|
|
1721
1771
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1722
1772
|
};
|
|
1723
|
-
|
|
1773
|
+
writeFileSync4(
|
|
1724
1774
|
CONNECT_LOCK_PATH,
|
|
1725
1775
|
JSON.stringify(lock, null, 2) + "\n",
|
|
1726
1776
|
"utf8"
|
|
@@ -1847,6 +1897,10 @@ function resolveConnectServerOption(options) {
|
|
|
1847
1897
|
return void 0;
|
|
1848
1898
|
}
|
|
1849
1899
|
async function runConnect(options) {
|
|
1900
|
+
const { didUpdate } = await runUpdate({ quietIfCurrent: true });
|
|
1901
|
+
if (didUpdate) {
|
|
1902
|
+
reexecConnectAfterUpdate(options);
|
|
1903
|
+
}
|
|
1850
1904
|
const cfg = await ensureLoggedConfig();
|
|
1851
1905
|
const serverOverride = resolveConnectServerOption(options);
|
|
1852
1906
|
if (serverOverride) {
|
|
@@ -1951,7 +2005,7 @@ function buildProgram() {
|
|
|
1951
2005
|
).option("--api-key <key>", "\u8FDC\u7A0B\u4E3B\u673A API Key\uFF08cm_ \u5F00\u5934\uFF09").option("--server <url>", "API \u6839\u5730\u5740\uFF0C\u4F8B\u5982 http://127.0.0.1:3000").action(async (opts) => {
|
|
1952
2006
|
await runLogin(opts);
|
|
1953
2007
|
});
|
|
1954
|
-
program.command("init").description("\u5728\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u6309 WebIDE \u5DE5\u4F5C\u533A\u521D\u59CB\u5316\uFF1A\u5199\u5165\u6307\u5357\u4E0E
|
|
2008
|
+
program.command("init").description("\u5728\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u6309 WebIDE \u5DE5\u4F5C\u533A\u521D\u59CB\u5316\uFF1A\u4ECE\u5E73\u53F0\u5199\u5165\u6307\u5357\u4E0E\u89C4\u5219").action(async () => {
|
|
1955
2009
|
await runInit();
|
|
1956
2010
|
});
|
|
1957
2011
|
program.command("update").description(
|
|
@@ -1964,7 +2018,7 @@ function buildProgram() {
|
|
|
1964
2018
|
).action(async (opts) => {
|
|
1965
2019
|
await runUpdate({ allowMajorUpgrade: opts.major === true });
|
|
1966
2020
|
});
|
|
1967
|
-
program.command("update-skills").description("\u540C\u6B65 .apm/ \u4E0B\u7684 WebIDE \u6307\u5357\u4E0E\u89C4\u5219").action(async () => {
|
|
2021
|
+
program.command("update-skills").description("\u4ECE\u5E73\u53F0\u540C\u6B65 .apm/ \u4E0B\u7684 WebIDE \u6307\u5357\u4E0E\u89C4\u5219").action(async () => {
|
|
1968
2022
|
await runUpdateSkills();
|
|
1969
2023
|
});
|
|
1970
2024
|
program.command("sync-project-documents").description(
|
|
@@ -1973,7 +2027,7 @@ function buildProgram() {
|
|
|
1973
2027
|
await runSyncProjectDocuments(opts);
|
|
1974
2028
|
});
|
|
1975
2029
|
program.command("connect").description(
|
|
1976
|
-
"\u8FDE\u63A5\
|
|
2030
|
+
"\u8FDE\u63A5\u524D\u68C0\u6D4B\u5E76\u66F4\u65B0\u5230\u5F53\u524D\u5927\u7248\u672C\u6700\u65B0 CLI\uFF0C\u518D\u8FDE\u63A5\u5E73\u53F0 WebSocket\uFF08/ws/agent\uFF09"
|
|
1977
2031
|
).option("--server <url>", "API \u6839\u5730\u5740\uFF0C\u8986\u76D6 config \u4E2D\u7684 baseUrl").action(async (opts) => {
|
|
1978
2032
|
await runConnect({ server: opts.server });
|
|
1979
2033
|
});
|