ai-project-manage-cli 4.0.23 → 5.0.1

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/dist/index.js CHANGED
@@ -40,12 +40,6 @@ function defaultApmConfig() {
40
40
  email: ""
41
41
  };
42
42
  }
43
- function httpBaseToWsOrigin(httpBase) {
44
- const u = httpBase.trim().replace(/\/+$/, "");
45
- if (u.startsWith("https://")) return "wss://" + u.slice("https://".length);
46
- if (u.startsWith("http://")) return "ws://" + u.slice("http://".length);
47
- throw new Error("baseUrl \u5FC5\u987B\u4EE5 http:// \u6216 https:// \u5F00\u5934");
48
- }
49
43
  async function ensureApmConfig() {
50
44
  const existing = await readApmConfig();
51
45
  if (existing) return existing;
@@ -65,183 +59,47 @@ async function writeApmConfig(cfg) {
65
59
  mode: 384
66
60
  });
67
61
  }
68
- function buildAgentWsUrl(httpBase, token) {
69
- const origin = httpBaseToWsOrigin(httpBase);
70
- const q = new URLSearchParams({ token });
71
- return `${origin}/ws/agent?${q.toString()}`;
72
- }
73
-
74
- // src/commands/comment.ts
75
- import { readFileSync as readFileSync2 } from "fs";
76
-
77
- // src/api/client.ts
78
- import { createApiClient } from "listpage-http";
79
-
80
- // src/api/request-config.ts
81
- import { defineEndpoint } from "listpage-http";
82
- var requestConfig = {
83
- auth: {
84
- login: defineEndpoint({
85
- method: "POST",
86
- path: "/auth/login",
87
- authRequired: false
88
- })
89
- },
90
- cliRequirements: {
91
- pull: defineEndpoint({
92
- method: "GET",
93
- path: "/cli/requirements/pull"
94
- }),
95
- branchBaseline: defineEndpoint({
96
- method: "GET",
97
- path: "/cli/requirements/branch-baseline"
98
- }),
99
- comment: defineEndpoint({
100
- method: "POST",
101
- path: "/cli/requirements/comment"
102
- }),
103
- commentStructured: defineEndpoint({
104
- method: "POST",
105
- path: "/cli/requirements/comment-structured"
106
- }),
107
- refine: defineEndpoint({
108
- method: "POST",
109
- path: "/cli/requirements/refine"
110
- }),
111
- updateStatus: defineEndpoint({
112
- method: "POST",
113
- path: "/cli/requirements/update-status"
114
- }),
115
- updateDevStatus: defineEndpoint({
116
- method: "POST",
117
- path: "/cli/requirements/update-dev-status"
118
- })
119
- },
120
- requirementAttachment: {
121
- list: defineEndpoint({
122
- method: "GET",
123
- path: "/requirement-attachments/list"
124
- }),
125
- download: defineEndpoint({
126
- method: "GET",
127
- path: "/requirement-attachments/download",
128
- mode: "download"
129
- })
130
- },
131
- requirementArtifact: {
132
- list: defineEndpoint({
133
- method: "GET",
134
- path: "/requirement-artifacts/list"
135
- }),
136
- get: defineEndpoint({
137
- method: "GET",
138
- path: "/requirement-artifacts/get"
139
- }),
140
- create: defineEndpoint({
141
- method: "POST",
142
- path: "/requirement-artifacts/create"
143
- }),
144
- upsert: defineEndpoint({
145
- method: "POST",
146
- path: "/requirement-artifacts/upsert"
147
- }),
148
- update: defineEndpoint({
149
- method: "POST",
150
- path: "/requirement-artifacts/update"
151
- }),
152
- delete: defineEndpoint({
153
- method: "POST",
154
- path: "/requirement-artifacts/delete"
155
- })
156
- },
157
- theaterSessionArtifact: {
158
- upsert: defineEndpoint({
159
- method: "POST",
160
- path: "/theater-session-artifacts/upsert"
161
- }),
162
- create: defineEndpoint({
163
- method: "POST",
164
- path: "/theater-session-artifacts/create"
165
- }),
166
- update: defineEndpoint({
167
- method: "POST",
168
- path: "/theater-session-artifacts/update"
169
- }),
170
- list: defineEndpoint({
171
- method: "GET",
172
- path: "/theater-session-artifacts/list"
173
- }),
174
- get: defineEndpoint({
175
- method: "GET",
176
- path: "/theater-session-artifacts/get"
177
- })
178
- },
179
- theater: {
180
- sessionBranchBaseline: defineEndpoint({
181
- method: "GET",
182
- path: "/theater/sessions/branch-baseline"
183
- }),
184
- reportMemberAgentProgress: defineEndpoint({
185
- method: "POST",
186
- path: "/theater/sessions/report-member-agent-progress"
187
- }),
188
- submitMemberResponse: defineEndpoint({
189
- method: "POST",
190
- path: "/theater/sessions/submit-member-response"
191
- })
192
- },
193
- spaceWorkflow: {
194
- aiStartNode: defineEndpoint({
195
- method: "POST",
196
- path: "/space-workflows/ai-start-node"
197
- }),
198
- aiConfirmNode: defineEndpoint(
199
- {
200
- method: "POST",
201
- path: "/space-workflows/ai-confirm-node"
202
- }
203
- ),
204
- aiFailNode: defineEndpoint({
205
- method: "POST",
206
- path: "/space-workflows/ai-fail-node"
207
- })
208
- }
209
- };
210
62
 
211
- // src/api/client.ts
212
- function createApmApiClient(cfg) {
213
- const baseURL = `${cfg.baseUrl.trim().replace(/\/+$/, "")}/api/v1`;
214
- return createApiClient(requestConfig, {
215
- baseURL,
216
- getToken: () => cfg.token || void 0,
217
- successCodes: [200, 201],
218
- unauthorizedCodes: [401]
219
- });
220
- }
63
+ // src/commands/init.ts
64
+ import { join as join3 } from "path";
65
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
221
66
 
222
67
  // src/command-utils.ts
223
68
  import { cpSync, existsSync, mkdirSync as mkdirSync2, readdirSync, statSync } from "fs";
224
- import { dirname, join as join2, resolve } from "path";
69
+ import { basename, dirname, extname, join as join2, resolve } from "path";
225
70
  import { fileURLToPath } from "url";
226
71
  var __dirname = dirname(fileURLToPath(import.meta.url));
227
72
  var CLI_TEMPLATE_DIR = resolve(__dirname, "../template");
228
- var WORKSPACE_APM_DIR = resolve(process.cwd(), ".apm");
229
- function requirementWorkitemsDir(requirementId, workspaceDir = WORKSPACE_APM_DIR) {
230
- return join2(workspaceDir, "workitems", requirementId);
73
+ function workspaceApmDir(cwd = process.cwd()) {
74
+ return resolve(cwd, ".apm");
75
+ }
76
+ var SESSIONS_SUBDIR = "sessions";
77
+ var SESSION_DOCS_SUBDIR = "docs";
78
+ var SESSION_ATTACHMENTS_SUBDIR = "attachments";
79
+ function sessionDir(sessionId, apmRoot) {
80
+ return join2(apmRoot ?? workspaceApmDir(), SESSIONS_SUBDIR, sessionId);
231
81
  }
232
- function theaterSessionDir(sessionId, apmRoot = WORKSPACE_APM_DIR) {
233
- return join2(apmRoot, "theater-sessions", sessionId);
82
+ function sessionDocsDir(sessionId, apmRoot) {
83
+ return join2(sessionDir(sessionId, apmRoot), SESSION_DOCS_SUBDIR);
234
84
  }
235
- function normalizeTheaterArtifactFileName(fileName) {
236
- return fileName.trim().replace(/\\/g, "/").replace(/^\/+/, "").replace(/^docs\//, "");
85
+ function sessionRulePath(sessionId, apmRoot) {
86
+ return join2(sessionDir(sessionId, apmRoot), "RULE.md");
237
87
  }
238
- function theaterArtifactLocalRelativePath(fileName) {
239
- const base = normalizeTheaterArtifactFileName(fileName);
240
- return `docs/${base}`;
88
+ function sessionYamlPath(sessionId, apmRoot) {
89
+ return join2(sessionDir(sessionId, apmRoot), "session.yaml");
241
90
  }
242
- function theaterSessionDocumentPath(sessionId, fileName = "prd.md", apmRoot = WORKSPACE_APM_DIR) {
243
- const localRelative = theaterArtifactLocalRelativePath(fileName);
244
- return join2(theaterSessionDir(sessionId, apmRoot), localRelative);
91
+ function documentLocalFileName(platformName) {
92
+ const trimmed = platformName.trim();
93
+ if (!trimmed) return "document.md";
94
+ if (extname(trimmed).toLowerCase() === ".md") return trimmed;
95
+ return `${trimmed}.md`;
96
+ }
97
+ function documentPlatformName(filePath) {
98
+ const base = basename(filePath.trim().replace(/\\/g, "/"));
99
+ if (base.toLowerCase().endsWith(".md")) {
100
+ return base.slice(0, -3);
101
+ }
102
+ return base;
245
103
  }
246
104
  async function ensureLoggedConfig() {
247
105
  const cfg = await ensureApmConfig();
@@ -257,7 +115,7 @@ async function ensureDirExists(dir) {
257
115
  mkdirSync2(dir, { recursive: true });
258
116
  }
259
117
  async function ensureWorkspaceApmDirForInit() {
260
- const dir = WORKSPACE_APM_DIR;
118
+ const dir = workspaceApmDir();
261
119
  if (!existsSync(dir)) {
262
120
  mkdirSync2(dir, { recursive: true });
263
121
  return;
@@ -288,611 +146,145 @@ async function copyTemplateFiles(targetDir) {
288
146
  })
289
147
  );
290
148
  }
291
- function xmlEscape(v) {
292
- return v.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
293
- }
294
149
  function resolveCwdPath(file) {
295
150
  return resolve(process.cwd(), file);
296
151
  }
297
152
 
298
- // src/structured-review-yaml.ts
299
- import { parse } from "yaml";
300
- var KIND_MAP = {
301
- clarify: "CLARIFY",
302
- difficulty: "DIFFICULTY",
303
- business: "BUSINESS",
304
- CLARIFY: "CLARIFY",
305
- DIFFICULTY: "DIFFICULTY",
306
- BUSINESS: "BUSINESS"
307
- };
308
- var STANCE_VALUES = /* @__PURE__ */ new Set(["frontend", "backend", "fullstack"]);
309
- function asRecord(value) {
310
- if (value && typeof value === "object" && !Array.isArray(value)) {
311
- return value;
312
- }
313
- throw new Error("YAML \u7ED3\u6784\u65E0\u6548\uFF1A\u9700\u8981\u5BF9\u8C61\u6839\u8282\u70B9");
314
- }
315
- function parseAnchor(anchor, index) {
316
- const rec = asRecord(anchor);
317
- const start = Number(rec.start ?? rec.startLine);
318
- const end = Number(rec.end ?? rec.endLine);
319
- if (!Number.isInteger(start) || !Number.isInteger(end) || start < 1 || end < start) {
320
- throw new Error(`items[${index}].anchor \u884C\u53F7\u65E0\u6548\uFF08\u9700 1 \u2264 start \u2264 end\uFF09`);
321
- }
322
- return { start, end };
323
- }
324
- function parseKind(raw, index) {
325
- const key = String(raw ?? "").trim();
326
- const kind = KIND_MAP[key] ?? KIND_MAP[key.toLowerCase()];
327
- if (!kind) {
328
- if (key.toLowerCase() === "coordination" || key === "COORDINATION") {
329
- throw new Error(
330
- `items[${index}].kind \u4E0D\u518D\u652F\u6301 coordination\uFF08\u8054\u8C03\u4F9D\u8D56\uFF09\uFF1B\u8BF7\u52FF\u8F93\u51FA\u63A5\u53E3/\u8054\u8C03\u7C7B\u8BC4\u5BA1\u6761\u76EE`
331
- );
332
- }
333
- throw new Error(
334
- `items[${index}].kind \u65E0\u6548\uFF0C\u5E94\u4E3A clarify | difficulty | business`
335
- );
336
- }
337
- return kind;
338
- }
339
- function parseStructuredReviewYaml(raw, cliModel) {
340
- let doc;
341
- try {
342
- doc = parse(raw);
343
- } catch (e) {
344
- const msg = e instanceof Error ? e.message : String(e);
345
- throw new Error(`YAML \u89E3\u6790\u5931\u8D25\uFF1A${msg}`);
346
- }
347
- const root = asRecord(doc);
348
- const reviewer = asRecord(root.reviewer ?? {});
349
- const stance = String(reviewer.stance ?? root.stance ?? "").trim();
350
- if (!STANCE_VALUES.has(stance)) {
351
- throw new Error("reviewer.stance \u5FC5\u987B\u4E3A frontend\u3001backend \u6216 fullstack");
352
- }
353
- const modelFromYaml = reviewer.model != null ? String(reviewer.model).trim() : "";
354
- const model = cliModel?.trim() || modelFromYaml || null;
355
- const itemsRaw = root.items;
356
- if (!Array.isArray(itemsRaw) || itemsRaw.length === 0) {
357
- throw new Error("items \u4E0D\u80FD\u4E3A\u7A7A");
358
- }
359
- const items = itemsRaw.map((entry, index) => {
360
- const item = asRecord(entry);
361
- const anchor = parseAnchor(item.anchor ?? item, index);
362
- const kind = parseKind(item.kind, index);
363
- const body = typeof item.body === "string" ? item.body.trim() : String(item.body ?? "").trim();
364
- if (!body) {
365
- throw new Error(`items[${index}].body \u4E0D\u80FD\u4E3A\u7A7A`);
366
- }
367
- return {
368
- startLine: anchor.start,
369
- endLine: anchor.end,
370
- kind,
371
- body
372
- };
373
- });
374
- return { stance, model, items };
375
- }
376
- function inferCommentFormat(filePath, explicit) {
377
- if (explicit === "structured" || explicit === "legacy") {
378
- return explicit;
379
- }
380
- const lower = filePath.toLowerCase();
381
- if (lower.endsWith(".yaml") || lower.endsWith(".yml")) {
382
- return "structured";
383
- }
384
- return "legacy";
385
- }
386
-
387
- // src/commands/comment.ts
388
- async function runComment(requirementId, file, options) {
389
- const cfg = await ensureLoggedConfig();
390
- const resolvedPath = resolveCwdPath(file);
391
- const raw = readFileSync2(resolvedPath, "utf8");
392
- const format = inferCommentFormat(resolvedPath, options?.format);
393
- const api = createApmApiClient(cfg);
394
- if (format === "structured") {
395
- const parsed = parseStructuredReviewYaml(raw, options?.model);
396
- const data2 = await api.cliRequirements.commentStructured({
397
- requirementId,
398
- stance: parsed.stance,
399
- model: parsed.model,
400
- items: parsed.items
401
- });
402
- console.log(JSON.stringify(data2, null, 2));
403
- return;
404
- }
405
- if (!raw.trim()) {
406
- throw new Error("\u8BC4\u8BBA\u6B63\u6587\u4E0D\u80FD\u4E3A\u7A7A");
153
+ // src/commands/init.ts
154
+ async function runInit(name) {
155
+ await ensureWorkspaceApmDirForInit();
156
+ const apmDir = workspaceApmDir();
157
+ await copyTemplateFiles(apmDir);
158
+ if (name) {
159
+ const apmConfigPath = join3(apmDir, "apm.config.json");
160
+ const config = readFileSync2(apmConfigPath, "utf8");
161
+ const configJson = JSON.parse(config);
162
+ configJson.name = name;
163
+ writeFileSync2(apmConfigPath, JSON.stringify(configJson, null, 2), "utf8");
407
164
  }
408
- const data = await api.cliRequirements.comment({
409
- requirementId,
410
- content: raw,
411
- model: options?.model
412
- });
413
- console.log(JSON.stringify(data, null, 2));
414
165
  }
415
166
 
416
- // src/commands/connect.ts
417
- import { randomUUID as randomUUID2 } from "crypto";
418
- import WebSocket from "ws";
419
- import { Agent } from "@cursor/sdk";
420
- import { join as join7 } from "path";
421
-
422
- // src/session-utils.ts
423
- import { appendFileSync } from "fs";
424
- import { resolve as resolve2 } from "path";
425
- var EventSession = class {
426
- events = [];
427
- constructor(prompt) {
428
- this.events.push({
429
- type: "input",
430
- content: prompt
431
- });
432
- }
433
- addEvent(event) {
434
- const latestEvent = this.events[this.events.length - 1];
435
- const formatedEvent = this.formatEvent(event);
436
- if (!formatedEvent) {
437
- return;
438
- }
439
- if (formatedEvent.type === "tool_call") {
440
- const existingToolCall = this.events.find(
441
- (e) => e.type === "tool_call" && e.call_id === formatedEvent.call_id
442
- );
443
- if (existingToolCall) {
444
- existingToolCall.args = formatedEvent.args;
445
- existingToolCall.result = formatedEvent.result;
446
- existingToolCall.status = formatedEvent.status;
447
- return;
448
- }
449
- this.events.push(formatedEvent);
450
- return;
451
- }
452
- if (formatedEvent.type === "status") {
453
- return;
454
- }
455
- if (formatedEvent.type === "request") {
456
- this.events.push(formatedEvent);
457
- return;
458
- }
459
- if (latestEvent?.type === formatedEvent.type) {
460
- switch (formatedEvent.type) {
461
- case "assistant":
462
- latestEvent.content += formatedEvent.content;
463
- break;
464
- case "thinking":
465
- latestEvent.content += formatedEvent.content;
466
- break;
467
- case "task":
468
- latestEvent.status = formatedEvent.status;
469
- latestEvent.text = formatedEvent.text;
470
- break;
471
- }
472
- return;
473
- }
474
- this.events.push(formatedEvent);
475
- }
476
- formatEvent(event) {
477
- switch (event.type) {
478
- case "assistant":
479
- return {
480
- type: "assistant",
481
- content: event.message.content[0].text || event.content || ""
482
- };
483
- case "thinking":
484
- return {
485
- type: "thinking",
486
- content: event.text || event.content || ""
487
- };
488
- case "tool_call":
489
- return {
490
- type: "tool_call",
491
- args: event.args,
492
- result: event.result,
493
- status: event.status,
494
- call_id: event.call_id,
495
- name: event.name
496
- };
497
- case "task":
498
- return {
499
- type: "task",
500
- status: event.status,
501
- text: event.text
502
- };
503
- case "request":
504
- return {
505
- ...event,
506
- type: "request"
507
- };
508
- case "status":
509
- return { type: "status", status: event.status, message: event.message };
510
- }
511
- }
512
- /** 合并所有 assistant 片段,供剧场成员回传等场景使用 */
513
- getAssistantText() {
514
- return this.events.filter((e) => e.type === "assistant").map((e) => String(e.content ?? "")).join("\n").trim();
515
- }
516
- writeToFile(cwd, requirementId, agentId) {
517
- const sessionsDir = resolve2(
518
- cwd,
519
- `.apm/workitems/${requirementId}/sessions`
520
- );
521
- ensureDirExists(sessionsDir);
522
- const sessionFile = resolve2(sessionsDir, `${agentId}.md`);
523
- this.events.forEach((event) => {
524
- const type = event.type;
525
- const content = (function(type2) {
526
- if (type2 === "input") {
527
- return `## \u7528\u6237\u8F93\u5165
528
-
529
- ${event.content}
530
- `;
531
- }
532
- if (type2 === "assistant") {
533
- return `## \u6A21\u578B\u8F93\u51FA
534
-
535
- ${event.content}
536
- `;
537
- }
538
- if (type2 === "thinking") {
539
- return `## \u6A21\u578B\u601D\u8003
167
+ // src/commands/login.ts
168
+ import { ApiError } from "listpage-http";
540
169
 
541
- ${event.content}
542
- `;
543
- }
544
- if (type2 === "tool_call") {
545
- return "````toolcall\n" + JSON.stringify(event, null, 2) + "\n````\n";
546
- }
547
- return `## \u672A\u77E5\u4E8B\u4EF6\uFF1A${type2}
170
+ // src/api/client.ts
171
+ import { createApiClient } from "listpage-http";
548
172
 
549
- \`\`\`json
550
- ${JSON.stringify(event, null, 2)}
551
- \`\`\``;
552
- })(type);
553
- appendFileSync(sessionFile, content + "\n");
554
- });
173
+ // src/api/request-config.ts
174
+ import { defineEndpoint } from "listpage-http";
175
+ var requestConfig = {
176
+ auth: {
177
+ login: defineEndpoint({
178
+ method: "POST",
179
+ path: "/auth/login",
180
+ authRequired: false
181
+ })
182
+ },
183
+ cli: {
184
+ sessionDetail: defineEndpoint({
185
+ method: "GET",
186
+ path: "/cli/sessions/detail"
187
+ }),
188
+ sessionMembers: defineEndpoint({
189
+ method: "GET",
190
+ path: "/cli/sessions/members"
191
+ }),
192
+ listDocuments: defineEndpoint({
193
+ method: "GET",
194
+ path: "/cli/documents"
195
+ }),
196
+ listAttachments: defineEndpoint({
197
+ method: "GET",
198
+ path: "/cli/attachments"
199
+ }),
200
+ upsertDocument: defineEndpoint({
201
+ method: "PUT",
202
+ path: "/cli/documents/upsert"
203
+ }),
204
+ appendMessageContent: defineEndpoint({
205
+ method: "PUT",
206
+ path: "/cli/messages/content"
207
+ }),
208
+ updateMessageStatus: defineEndpoint({
209
+ method: "PUT",
210
+ path: "/cli/messages/status"
211
+ }),
212
+ branchBaseline: defineEndpoint({
213
+ method: "GET",
214
+ path: "/cli/requirements/branch-baseline"
215
+ }),
216
+ listSkills: defineEndpoint({
217
+ method: "GET",
218
+ path: "/cli/skills"
219
+ })
555
220
  }
556
221
  };
557
222
 
558
- // src/commands/upload-artifact.ts
559
- import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
560
- import { join as join4, relative, sep } from "path";
561
-
562
- // src/commands/sync-requirement-attachments.ts
563
- import { existsSync as existsSync2, writeFileSync as writeFileSync2 } from "fs";
564
- import { join as join3 } from "path";
565
- var REQUIREMENT_ATTACHMENTS_SUBDIR = "attachments";
566
- async function syncRequirementAttachments(api, requirementId, workitemsDir) {
567
- const dir = join3(workitemsDir, REQUIREMENT_ATTACHMENTS_SUBDIR);
568
- await ensureDirExists(dir);
569
- const { items } = await api.requirementAttachment.list({ requirementId });
570
- if (items.length === 0) return;
571
- for (const item of items) {
572
- const dest = join3(dir, item.fileName);
573
- if (existsSync2(dest)) continue;
574
- const { blob } = await api.requirementAttachment.download({
575
- attachmentId: item.id
576
- });
577
- const buffer = Buffer.from(await blob.arrayBuffer());
578
- writeFileSync2(dest, buffer);
579
- console.log(
580
- `[apm] \u5DF2\u4E0B\u8F7D\u9644\u4EF6: ${REQUIREMENT_ATTACHMENTS_SUBDIR}/${item.fileName}`
581
- );
582
- }
583
- }
584
-
585
- // src/commands/upload-artifact.ts
586
- var EXCLUDED_RELATIVE_PATHS = /* @__PURE__ */ new Set([
587
- "defect.xml",
588
- "prd.md",
589
- "requirement-status.yaml",
590
- "reviews.xml",
591
- "testcase.xml"
592
- ]);
593
- function toPosixRelative(root, absoluteFile) {
594
- return relative(root, absoluteFile).split(sep).join("/");
595
- }
596
- function artifactTagFromRelPath(relPosix) {
597
- const parts = relPosix.split("/");
598
- const fileName = parts[parts.length - 1] ?? relPosix;
599
- if (parts.length > 1) {
600
- return parts[0] ?? fileName;
601
- }
602
- const dot = fileName.lastIndexOf(".");
603
- return dot > 0 ? fileName.slice(0, dot) : fileName;
604
- }
605
- function* walkMarkdownFiles(dir) {
606
- const names = readdirSync2(dir);
607
- for (const name of names) {
608
- if (name.startsWith(".")) continue;
609
- if (name === REQUIREMENT_ATTACHMENTS_SUBDIR) continue;
610
- const full = join4(dir, name);
611
- const st = statSync2(full);
612
- if (st.isDirectory()) {
613
- yield* walkMarkdownFiles(full);
614
- continue;
615
- }
616
- if (!st.isFile()) continue;
617
- if (!name.toLowerCase().endsWith(".md")) continue;
618
- yield full;
619
- }
620
- }
621
- async function runUploadArtifact(requirementId, workspaceDir) {
622
- const cfg = await ensureLoggedConfig();
623
- const api = createApmApiClient(cfg);
624
- const root = requirementWorkitemsDir(requirementId, workspaceDir);
625
- if (!existsSync3(root)) {
626
- console.error(
627
- `[apm] \u76EE\u5F55\u4E0D\u5B58\u5728: ${root}
628
- \u8BF7\u5148\u6267\u884C: apm pull ${requirementId}`
629
- );
630
- process.exit(1);
631
- }
632
- for (const abs of walkMarkdownFiles(root)) {
633
- const relPosix = toPosixRelative(root, abs);
634
- if (EXCLUDED_RELATIVE_PATHS.has(relPosix)) continue;
635
- const content = readFileSync3(abs, "utf8");
636
- const tag = artifactTagFromRelPath(relPosix);
637
- await api.requirementArtifact.upsert({
638
- requirementId,
639
- tag,
640
- fileName: relPosix,
641
- content
642
- });
643
- console.log(`[apm] \u5DF2\u540C\u6B65\u4EA7\u7269: ${relPosix}`);
644
- }
645
- }
646
-
647
- // src/commands/pull.ts
648
- import { writeFileSync as writeFileSync3 } from "fs";
649
- import { join as join5 } from "path";
650
- import { stringify as yamlStringify } from "yaml";
651
-
652
- // src/pull-reviews-xml.ts
653
- function escapeForCdata(text) {
654
- return text.replace(/\]\]>/g, "]]]]><![CDATA[>");
655
- }
656
- function buildReviewsXml(reviews) {
657
- const lines = ["<reviews>"];
658
- for (const review of reviews) {
659
- lines.push(
660
- ` <review id="${xmlEscape(review.id)}" model="${xmlEscape(
661
- review.model ?? ""
662
- )}" stance="${xmlEscape(review.stance ?? "")}" memberRole="${xmlEscape(
663
- review.memberRole
664
- )}">`
665
- );
666
- for (const item of review.items) {
667
- const kind = item.kind.toLowerCase();
668
- lines.push(
669
- ` <item id="${xmlEscape(item.id)}" start="${item.startLine}" end="${item.endLine}" kind="${xmlEscape(kind)}" status="open">`
670
- );
671
- lines.push(
672
- ` <body><![CDATA[${escapeForCdata(item.body ?? "")}]]></body>`
673
- );
674
- const reply = item.reply?.trim() ?? "";
675
- lines.push(` <reply><![CDATA[${escapeForCdata(reply)}]]></reply>`);
676
- lines.push(" </item>");
677
- }
678
- lines.push(" </review>");
679
- }
680
- lines.push("</reviews>", "");
681
- return lines.join("\n");
223
+ // src/api/client.ts
224
+ function createApmApiClient(cfg) {
225
+ const baseURL = `${cfg.baseUrl.trim().replace(/\/+$/, "")}/api`;
226
+ return createApiClient(requestConfig, {
227
+ baseURL,
228
+ getToken: () => cfg.token || void 0,
229
+ successCodes: [0],
230
+ unauthorizedCodes: [401]
231
+ });
682
232
  }
683
233
 
684
- // src/commands/pull.ts
685
- var PULL_ARTIFACT_FILE_NAMES = ["api.md", "backend.md"];
686
- function normalizeArtifactPath(fileName) {
687
- return fileName.trim().replace(/\\/g, "/").replace(/^\/+/, "");
688
- }
689
- function artifactFileNameMatches(itemFileName, targetFileName) {
690
- const norm = normalizeArtifactPath(itemFileName);
691
- const target = normalizeArtifactPath(targetFileName);
692
- if (norm === target) return true;
693
- const base = norm.split("/").filter(Boolean).pop() ?? norm;
694
- return base === target;
695
- }
696
- async function fetchArtifactContentByFileName(api, requirementId, fileName) {
697
- const pageSize = 500;
698
- let page = 1;
699
- const items = [];
700
- while (true) {
701
- const batch = await api.requirementArtifact.list({
702
- requirementId,
703
- page,
704
- pageSize
234
+ // src/commands/login.ts
235
+ async function runLogin(opts) {
236
+ const baseUrl = (opts.server?.trim() || process.env.AI_PM_SERVER?.trim() || DEFAULT_BASE_URL).replace(/\/+$/, "");
237
+ const api = createApmApiClient({
238
+ baseUrl,
239
+ userId: "",
240
+ token: ""
241
+ });
242
+ let data;
243
+ try {
244
+ data = await api.auth.login({
245
+ email: opts.email,
246
+ password: opts.password
705
247
  });
706
- items.push(...batch.items);
707
- if (batch.total === 0 || items.length >= batch.total) break;
708
- page += 1;
709
- }
710
- const hit = items.find(
711
- (item) => artifactFileNameMatches(item.fileName, fileName)
712
- );
713
- if (!hit) return null;
714
- const art = await api.requirementArtifact.get({ artifactId: hit.id });
715
- return art.content;
716
- }
717
- function valueToXmlContent(value) {
718
- if (value === null || value === void 0) return "";
719
- if (typeof value === "string") return xmlEscape(value);
720
- if (typeof value === "number" || typeof value === "boolean") {
721
- return xmlEscape(String(value));
722
- }
723
- return `<![CDATA[${JSON.stringify(value)}]]>`;
724
- }
725
- function recordToXmlLines(record, indent) {
726
- const lines = [];
727
- for (const [rawKey, val] of Object.entries(record)) {
728
- const key = /^[a-zA-Z_][\w.-]*$/.test(rawKey) ? rawKey : "field";
729
- lines.push(`${indent}<${key}>${valueToXmlContent(val)}</${key}>`);
730
- }
731
- return lines;
732
- }
733
- function unknownArrayToXml(rootName, itemName, items) {
734
- const lines = [`<${rootName}>`];
735
- items.forEach((item, index) => {
736
- if (item !== null && typeof item === "object" && !Array.isArray(item)) {
737
- lines.push(` <${itemName} index="${index}">`);
738
- lines.push(...recordToXmlLines(item, " "));
739
- lines.push(` </${itemName}>`);
740
- } else {
741
- lines.push(
742
- ` <${itemName} index="${index}">${valueToXmlContent(
743
- item
744
- )}</${itemName}>`
248
+ } catch (raw) {
249
+ if (raw instanceof ApiError) {
250
+ const err = raw;
251
+ console.error(
252
+ `[apm] \u767B\u5F55\u5931\u8D25 (${err.httpStatus ?? err.code}):`,
253
+ err.message
745
254
  );
255
+ process.exit(1);
746
256
  }
747
- });
748
- lines.push(`</${rootName}>`, "");
749
- return lines.join("\n");
750
- }
751
- function escapeForCdata2(text) {
752
- return text.replace(/\]\]>/g, "]]]]><![CDATA[>");
753
- }
754
- function defectsToXml(defects) {
755
- const sorted = [...defects].sort(
756
- (a, b) => new Date(a.createdAt ?? 0).getTime() - new Date(b.createdAt ?? 0).getTime()
757
- );
758
- const lines = ["<defects>"];
759
- for (const d of sorted) {
760
- lines.push(` <defect id="${xmlEscape(d.id)}">`);
761
- lines.push(` <status>${xmlEscape(d.status)}</status>`);
762
- lines.push(
763
- ` <current><![CDATA[${escapeForCdata2(
764
- d.currentState ?? ""
765
- )}]]></current>`
766
- );
767
- lines.push(
768
- ` <expected><![CDATA[${escapeForCdata2(
769
- d.expectedEffect ?? ""
770
- )}]]></expected>`
771
- );
772
- lines.push(` </defect>`);
773
- }
774
- lines.push("</defects>", "");
775
- return lines.join("\n");
776
- }
777
- async function runPull(requirementId, workspaceDir) {
778
- const cfg = await ensureLoggedConfig();
779
- const api = createApmApiClient(cfg);
780
- const data = await api.cliRequirements.pull({ requirementId });
781
- const WORKITEMS_DIR = requirementWorkitemsDir(requirementId, workspaceDir);
782
- await ensureDirExists(WORKITEMS_DIR);
783
- const req2 = data.requirement;
784
- const statusYaml = yamlStringify(
785
- {
786
- id: req2.id,
787
- status: req2.status,
788
- title: req2.title
789
- },
790
- { lineWidth: 0 }
791
- );
792
- writeFileSync3(
793
- join5(WORKITEMS_DIR, "requirement-status.yaml"),
794
- statusYaml.endsWith("\n") ? statusYaml : `${statusYaml}
795
- `,
796
- "utf8"
797
- );
798
- writeFileSync3(join5(WORKITEMS_DIR, "prd.md"), req2.content || "", "utf8");
799
- const reviewsXml = buildReviewsXml(data.reviews ?? []);
800
- writeFileSync3(join5(WORKITEMS_DIR, "reviews.xml"), reviewsXml, "utf8");
801
- const defectsXml = defectsToXml(data.defects ?? []);
802
- writeFileSync3(join5(WORKITEMS_DIR, "defect.xml"), defectsXml, "utf8");
803
- const testCasesXml = unknownArrayToXml(
804
- "testcases",
805
- "case",
806
- data.testCases ?? []
807
- );
808
- writeFileSync3(join5(WORKITEMS_DIR, "testcase.xml"), testCasesXml, "utf8");
809
- for (const fileName of PULL_ARTIFACT_FILE_NAMES) {
810
- const content = await fetchArtifactContentByFileName(
811
- api,
812
- requirementId,
813
- fileName
814
- );
815
- if (content !== null) {
816
- writeFileSync3(join5(WORKITEMS_DIR, fileName), content, "utf8");
817
- }
818
- }
819
- await syncRequirementAttachments(api, requirementId, WORKITEMS_DIR);
820
- return WORKITEMS_DIR;
821
- }
822
-
823
- // src/commands/pull-theater-session.ts
824
- import { writeFileSync as writeFileSync4 } from "fs";
825
- import { dirname as dirname2, join as join6 } from "path";
826
- function normalizeRelativePath(fileName) {
827
- return theaterArtifactLocalRelativePath(fileName);
828
- }
829
- async function runPullTheaterSession(sessionId, apmRoot = WORKSPACE_APM_DIR) {
830
- const trimmedSessionId = sessionId.trim();
831
- if (!trimmedSessionId) {
832
- throw new Error("sessionId \u4E0D\u80FD\u4E3A\u7A7A");
833
- }
834
- const cfg = await ensureLoggedConfig();
835
- const api = createApmApiClient(cfg);
836
- const sessionDir = theaterSessionDir(trimmedSessionId, apmRoot);
837
- await ensureDirExists(sessionDir);
838
- await ensureDirExists(join6(sessionDir, "docs"));
839
- const pageSize = 500;
840
- let page = 1;
841
- const items = [];
842
- while (true) {
843
- const batch = await api.theaterSessionArtifact.list({
844
- sessionId: trimmedSessionId,
845
- page,
846
- pageSize
847
- });
848
- items.push(...batch.items);
849
- if (batch.total === 0 || items.length >= batch.total) break;
850
- page += 1;
851
- }
852
- for (const item of items) {
853
- const art = await api.theaterSessionArtifact.get({
854
- sessionId: trimmedSessionId,
855
- artifactId: item.id
856
- });
857
- const relativePath = normalizeRelativePath(art.fileName);
858
- const destPath = join6(sessionDir, relativePath);
859
- await ensureDirExists(dirname2(destPath));
860
- writeFileSync4(destPath, art.content, "utf8");
861
- console.log(`[apm] \u5DF2\u8986\u76D6\u4F1A\u8BDD\u6587\u6863: ${relativePath}`);
862
- }
863
- return sessionDir;
864
- }
865
-
866
- // src/commands/branch.ts
867
- import { execFile } from "child_process";
868
- import { resolve as resolve3 } from "path";
869
- import { promisify } from "util";
870
- var execFileAsync = promisify(execFile);
871
- async function fetchBaselineBranchFromApi(requirementId, cwd) {
872
- const cfg = await ensureLoggedConfig();
873
- const api = createApmApiClient(cfg);
874
- const workdirPath = resolve3(cwd);
875
- const res = await api.cliRequirements.branchBaseline({
876
- requirementId,
877
- workdirPath
878
- });
879
- if (!res.repositoryId) {
880
- throw new Error(
881
- `[apm] \u672A\u5728\u5E73\u53F0\u627E\u5230\u4E0E\u5F53\u524D\u76EE\u5F55\u5339\u914D\u7684\u5DE5\u4F5C\u76EE\u5F55\u767B\u8BB0\uFF1A${workdirPath}
882
- \u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`
257
+ console.error(
258
+ "[apm] \u8BF7\u6C42\u5931\u8D25:",
259
+ raw instanceof Error ? raw.message : raw
883
260
  );
261
+ process.exit(1);
884
262
  }
885
- const name = (res.defaultBranch ?? "").trim();
886
- if (!name) {
887
- throw new Error("[apm] \u5E73\u53F0\u8FD4\u56DE\u7684\u57FA\u7EBF\u5206\u652F\u540D\u4E3A\u7A7A");
263
+ if (!data?.id || !data?.token) {
264
+ console.error("[apm] \u54CD\u5E94\u7F3A\u5C11 id / token\uFF08\u8BF7\u786E\u8BA4\u670D\u52A1\u7AEF\u4E3A /api/auth/login\uFF09");
265
+ process.exit(1);
888
266
  }
889
- return name;
267
+ const cfg = {
268
+ baseUrl,
269
+ userId: data.id,
270
+ token: data.token,
271
+ email: opts.email
272
+ };
273
+ await writeApmConfig(cfg);
274
+ console.error(`[apm] \u5DF2\u4FDD\u5B58\u767B\u5F55\u4FE1\u606F: ${APM_CONFIG_PATH}`);
275
+ console.log(JSON.stringify({ userId: cfg.userId, baseUrl: cfg.baseUrl }, null, 2));
890
276
  }
891
- async function fetchTheaterBaselineBranchFromApi(sessionId, cwd) {
277
+
278
+ // src/commands/branch.ts
279
+ import { execFile } from "child_process";
280
+ import { resolve as resolve2 } from "path";
281
+ import { promisify } from "util";
282
+ var execFileAsync = promisify(execFile);
283
+ async function fetchBaselineBranchFromApi(sessionId, cwd) {
892
284
  const cfg = await ensureLoggedConfig();
893
285
  const api = createApmApiClient(cfg);
894
- const workdirPath = resolve3(cwd);
895
- const res = await api.theater.sessionBranchBaseline({
286
+ const workdirPath = resolve2(cwd);
287
+ const res = await api.cli.branchBaseline({
896
288
  sessionId,
897
289
  workdirPath
898
290
  });
@@ -920,7 +312,6 @@ function branchNameForRequirement(requirementId) {
920
312
  }
921
313
  return `feat/req-${id}`;
922
314
  }
923
- var branchNameForTheaterSession = branchNameForRequirement;
924
315
  async function execGit(cwd, args, quiet) {
925
316
  try {
926
317
  const { stdout, stderr } = await execFileAsync("git", args, {
@@ -971,11 +362,6 @@ async function localBranchExists(cwd, branch) {
971
362
  return false;
972
363
  }
973
364
  }
974
- function theaterSessionCommitMessage(sessionId, memberKey) {
975
- const id = sessionId.trim();
976
- const key = memberKey?.trim();
977
- return key ? `feat(req-${id}): ${key} theater agent` : `feat(req-${id}): theater agent`;
978
- }
979
365
  async function commitWorkingTreeIfDirty(cwd, message) {
980
366
  await ensureGitRepo(cwd);
981
367
  if (!await isWorkingTreeDirty(cwd)) {
@@ -1038,538 +424,129 @@ async function ensureFeatureBranch(branch, options, fetchBaseline) {
1038
424
  console.log(`[apm] \u5DF2\u5C31\u7EEA\u5206\u652F ${branch}`);
1039
425
  return branch;
1040
426
  }
1041
- async function runBranch(requirementId, options = {}) {
1042
- const cwd = options.cwd ?? process.cwd();
1043
- const branch = branchNameForRequirement(requirementId);
1044
- return ensureFeatureBranch(
1045
- branch,
1046
- options,
1047
- () => fetchBaselineBranchFromApi(requirementId, cwd)
1048
- );
1049
- }
1050
- async function runTheaterSessionBranch(sessionId, options = {}) {
427
+ async function runBranch(sessionId, options = {}) {
428
+ const trimmedSessionId = sessionId.trim();
429
+ if (!trimmedSessionId) {
430
+ console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
431
+ process.exit(1);
432
+ }
433
+ const cfg = await ensureLoggedConfig();
434
+ const api = createApmApiClient(cfg);
1051
435
  const cwd = options.cwd ?? process.cwd();
1052
- const branch = branchNameForTheaterSession(sessionId);
436
+ const baseline = await api.cli.branchBaseline({
437
+ sessionId: trimmedSessionId,
438
+ workdirPath: resolve2(cwd)
439
+ });
440
+ const branch = branchNameForRequirement(baseline.requirementId);
1053
441
  return ensureFeatureBranch(
1054
442
  branch,
1055
443
  options,
1056
- () => fetchTheaterBaselineBranchFromApi(sessionId, cwd)
444
+ () => fetchBaselineBranchFromApi(trimmedSessionId, cwd)
1057
445
  );
1058
446
  }
1059
447
 
1060
- // src/theater-job-report.ts
1061
- import { randomUUID } from "crypto";
1062
- function extractErrorMessage(err) {
1063
- if (err instanceof Error && err.message.trim()) {
1064
- return err.message.trim();
1065
- }
1066
- if (typeof err === "string" && err.trim()) {
1067
- return err.trim();
1068
- }
1069
- if (typeof err === "object" && err && "message" in err) {
1070
- const msg = err.message;
1071
- if (typeof msg === "string" && msg.trim()) return msg.trim();
1072
- if (Array.isArray(msg)) return msg.map(String).join("\uFF1B");
1073
- }
1074
- return "\u672A\u77E5\u9519\u8BEF";
1075
- }
1076
- function isUnauthorizedMessage(msg) {
1077
- return msg.includes("\u672A\u767B\u5F55") || /401/.test(msg);
1078
- }
1079
- function sendTheaterReportWs(ws, payload) {
1080
- return new Promise((resolve5, reject) => {
1081
- const msg_id = randomUUID();
1082
- const timer = setTimeout(() => {
1083
- ws.off("message", onMessage);
1084
- reject(new Error("WebSocket \u4E0A\u62A5\u8D85\u65F6"));
1085
- }, 3e4);
1086
- const onMessage = (data) => {
1087
- const text = typeof data === "string" ? data : data.toString();
1088
- let frame;
1089
- try {
1090
- frame = JSON.parse(text);
1091
- } catch {
1092
- return;
1093
- }
1094
- if (frame.msg_id !== msg_id) return;
1095
- clearTimeout(timer);
1096
- ws.off("message", onMessage);
1097
- if (frame.type === "ERROR") {
1098
- reject(
1099
- new Error(frame.payload?.message?.trim() || "WebSocket \u4E0A\u62A5\u5931\u8D25")
1100
- );
1101
- return;
1102
- }
1103
- if (frame.type === "ACK") {
1104
- resolve5();
1105
- return;
1106
- }
1107
- };
1108
- ws.on("message", onMessage);
1109
- ws.send(
1110
- JSON.stringify({
1111
- type: "THEATER_REPORT",
1112
- msg_id,
1113
- ts: (/* @__PURE__ */ new Date()).toISOString(),
1114
- payload
1115
- })
1116
- );
448
+ // src/commands/pull.ts
449
+ import { writeFileSync as writeFileSync4 } from "fs";
450
+ import { join as join5 } from "path";
451
+ import { stringify as yamlStringify } from "yaml";
452
+
453
+ // src/commands/sync-session-attachments.ts
454
+ import { writeFileSync as writeFileSync3 } from "fs";
455
+ import { join as join4 } from "path";
456
+ async function downloadAttachment(cfg, sessionId, attachmentId) {
457
+ const base = cfg.baseUrl.trim().replace(/\/+$/, "");
458
+ const q = new URLSearchParams({ sessionId, attachmentId });
459
+ const url = `${base}/api/cli/attachments/file?${q.toString()}`;
460
+ const res = await fetch(url, {
461
+ headers: { Authorization: `Bearer ${cfg.token}` }
1117
462
  });
1118
- }
1119
- async function reportTheaterProgress(api, ws, params) {
1120
- const body = {
1121
- sessionId: params.sessionId,
1122
- memberKey: params.memberKey,
1123
- status: "IN_PROGRESS",
1124
- logId: params.logId
1125
- };
1126
- try {
1127
- await api.theater.reportMemberAgentProgress(body);
1128
- } catch (err) {
1129
- const msg = extractErrorMessage(err);
1130
- if (!isUnauthorizedMessage(msg)) throw err;
1131
- await sendTheaterReportWs(ws, { action: "progress", ...body });
1132
- }
1133
- }
1134
- async function submitTheaterResult(api, ws, params) {
1135
- const body = {
1136
- sessionId: params.sessionId,
1137
- memberKey: params.memberKey,
1138
- content: params.content,
1139
- agentId: params.agentId ?? "",
1140
- agentStatus: params.agentStatus,
1141
- logId: params.logId
1142
- };
1143
- try {
1144
- await api.theater.submitMemberResponse(body);
1145
- } catch (err) {
1146
- const msg = extractErrorMessage(err);
1147
- if (!isUnauthorizedMessage(msg)) throw err;
1148
- await sendTheaterReportWs(ws, {
1149
- action: "submit",
1150
- sessionId: params.sessionId,
1151
- memberKey: params.memberKey,
1152
- content: params.content,
1153
- agentId: params.agentId,
1154
- agentStatus: params.agentStatus,
1155
- logId: params.logId
1156
- });
1157
- }
1158
- }
1159
- async function submitTheaterFailure(api, ws, params) {
1160
- const content = `[${params.stage}] ${params.error}`;
1161
- try {
1162
- await submitTheaterResult(api, ws, {
1163
- sessionId: params.sessionId,
1164
- memberKey: params.memberKey,
1165
- content,
1166
- agentId: params.agentId,
1167
- agentStatus: "FAILED",
1168
- logId: params.logId
1169
- });
1170
- return true;
1171
- } catch (submitErr) {
1172
- console.error(
1173
- "[apm] \u65E0\u6CD5\u5C06\u5931\u8D25\u72B6\u6001\u5199\u5165\u5267\u573A\u4F1A\u8BDD:",
1174
- extractErrorMessage(submitErr)
463
+ if (!res.ok) {
464
+ throw new Error(
465
+ `[apm] \u4E0B\u8F7D\u9644\u4EF6\u5931\u8D25 (${res.status}): attachmentId=${attachmentId}`
1175
466
  );
1176
- return false;
1177
- }
1178
- }
1179
- function logTheaterJobError(stage, err, meta) {
1180
- const msg = extractErrorMessage(err);
1181
- const ctx = [
1182
- meta?.sessionId ? `session=${meta.sessionId}` : "",
1183
- meta?.memberKey ? `member=${meta.memberKey}` : ""
1184
- ].filter(Boolean).join(" ");
1185
- console.error(
1186
- `[apm] \u5267\u573A THEATER_JOB \u5931\u8D25 \xB7 \u9636\u6BB5=${stage}${ctx ? ` \xB7 ${ctx}` : ""} \xB7 ${msg}`
1187
- );
1188
- if (isUnauthorizedMessage(msg)) {
1189
- console.error("[apm] \u63D0\u793A: \u8BF7\u6267\u884C apm login \u91CD\u65B0\u767B\u5F55\u540E\u518D\u8BD5");
1190
- }
1191
- }
1192
-
1193
- // src/commands/connect.ts
1194
- var DEFAULT_AGENT_MODEL_ID = "default";
1195
- function resolveAgentModelId(model) {
1196
- const id = model?.trim();
1197
- return id || DEFAULT_AGENT_MODEL_ID;
1198
- }
1199
- async function createAgentForJob(payload) {
1200
- const options = {
1201
- apiKey: payload.apiKey,
1202
- model: { id: resolveAgentModelId(payload.model) },
1203
- local: { cwd: payload.cwd }
1204
- };
1205
- if (payload.agentId) {
1206
- return Agent.resume(payload.agentId, options);
1207
- }
1208
- return Agent.create(options);
1209
- }
1210
- async function runAgentStream(payload, session, agent) {
1211
- const run = await agent.send(payload.prompt);
1212
- let failed = false;
1213
- let failReason = "";
1214
- for await (const event of run.stream()) {
1215
- if (event.type === "status") {
1216
- console.log(`[Status]`, event.message || event.status);
1217
- if (event.status === "ERROR") {
1218
- failed = true;
1219
- failReason = event.message?.trim() ?? "";
1220
- console.error("[apm] Agent \u6267\u884C\u5931\u8D25:", failReason || "(\u65E0\u5931\u8D25\u539F\u56E0)");
1221
- break;
1222
- }
1223
- continue;
1224
- }
1225
- if (event.type === "assistant") {
1226
- const output = event.message.content[0].text;
1227
- console.log(`[Output]`, output);
1228
- session.addEvent(event);
1229
- continue;
1230
- }
1231
- if (event.type === "thinking") {
1232
- console.log(`[Thinking]`, event.text);
1233
- continue;
1234
- }
1235
- if (event.type === "tool_call") {
1236
- if (event.status === "completed" || event.status === "error") {
1237
- console.log(`[ToolCall(${event.call_id}) Result]`);
1238
- }
1239
- session.addEvent(event);
1240
- continue;
1241
- }
1242
- if (event.type === "task") {
1243
- console.log(`[Task:${event.status}]`);
1244
- session.addEvent(event);
1245
- continue;
1246
- }
1247
- if (event.type === "request") {
1248
- console.log(JSON.stringify(event, null, 2));
1249
- session.addEvent(event);
1250
- continue;
1251
- }
1252
- console.log("\u672A\u77E5\u4E8B\u4EF6:", JSON.stringify(event, null, 2));
1253
467
  }
1254
- return { agentId: run.agentId, failed, failReason };
468
+ return Buffer.from(await res.arrayBuffer());
1255
469
  }
1256
- async function handleTheaterJob(api, ws, payload) {
1257
- const sessionId = payload.theaterSessionId.trim();
1258
- const memberKey = payload.memberKey.trim();
1259
- const logId = payload.logId?.trim() || void 0;
1260
- if (!sessionId) {
1261
- throw new Error("THEATER_JOB \u7F3A\u5C11 theaterSessionId");
1262
- }
1263
- if (!memberKey) {
1264
- throw new Error("THEATER_JOB \u7F3A\u5C11 memberKey");
1265
- }
1266
- console.log("[apm] \u5267\u573A\u4F1A\u8BDD:", sessionId, "\u6210\u5458:", memberKey);
1267
- const apmRoot = join7(payload.cwd, ".apm");
1268
- let agentId = payload.agentId?.trim() || void 0;
1269
- try {
1270
- await reportTheaterProgress(api, ws, { sessionId, memberKey, logId });
1271
- } catch (err) {
1272
- logTheaterJobError("\u4E0A\u62A5\u6267\u884C\u8FDB\u5EA6", err, { sessionId, memberKey });
1273
- await submitTheaterFailure(api, ws, {
1274
- sessionId,
1275
- memberKey,
1276
- logId,
1277
- agentId,
1278
- stage: "\u4E0A\u62A5\u8FDB\u5EA6",
1279
- error: extractErrorMessage(err)
1280
- });
1281
- return;
1282
- }
1283
- try {
1284
- await runTheaterSessionBranch(sessionId, { cwd: payload.cwd });
1285
- } catch (branchErr) {
1286
- logTheaterJobError("\u5207\u6362\u5206\u652F", branchErr, { sessionId, memberKey });
1287
- await submitTheaterFailure(api, ws, {
1288
- sessionId,
1289
- memberKey,
1290
- logId,
1291
- agentId,
1292
- stage: "\u5207\u6362\u5206\u652F",
1293
- error: extractErrorMessage(branchErr)
1294
- });
1295
- return;
1296
- }
1297
- try {
1298
- await runPullTheaterSession(sessionId, apmRoot);
1299
- } catch (pullErr) {
1300
- logTheaterJobError("\u62C9\u53D6\u4F1A\u8BDD\u6587\u6863", pullErr, { sessionId, memberKey });
1301
- await submitTheaterFailure(api, ws, {
1302
- sessionId,
1303
- memberKey,
1304
- logId,
1305
- agentId,
1306
- stage: "\u62C9\u53D6\u4F1A\u8BDD\u6587\u6863",
1307
- error: extractErrorMessage(pullErr)
1308
- });
1309
- return;
1310
- }
1311
- try {
1312
- const eventSession = new EventSession(payload.prompt);
1313
- const agent = await createAgentForJob(payload);
1314
- const runResult = await runAgentStream(payload, eventSession, agent);
1315
- agentId = runResult.agentId;
1316
- try {
1317
- await commitWorkingTreeIfDirty(
1318
- payload.cwd,
1319
- theaterSessionCommitMessage(sessionId, memberKey)
1320
- );
1321
- } catch (commitErr) {
1322
- logTheaterJobError("\u63D0\u4EA4\u5DE5\u4F5C\u533A", commitErr, { sessionId, memberKey });
1323
- await submitTheaterFailure(api, ws, {
1324
- sessionId,
1325
- memberKey,
1326
- logId,
1327
- agentId,
1328
- stage: "\u63D0\u4EA4\u5DE5\u4F5C\u533A",
1329
- error: extractErrorMessage(commitErr)
1330
- });
1331
- return;
1332
- }
1333
- const assistantText = eventSession.getAssistantText();
1334
- const content = runResult.failed ? runResult.failReason || assistantText || "\u672A\u77E5\u9519\u8BEF" : assistantText || "[Agent \u672A\u4EA7\u751F\u6587\u672C\u8F93\u51FA]";
1335
- await submitTheaterResult(api, ws, {
1336
- sessionId,
1337
- memberKey,
1338
- content,
1339
- agentId,
1340
- agentStatus: runResult.failed ? "FAILED" : "SUCCESS",
1341
- logId
1342
- });
470
+ async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
471
+ if (attachments.length === 0) return;
472
+ const dir = join4(sessionDir(sessionId, apmRoot), SESSION_ATTACHMENTS_SUBDIR);
473
+ await ensureDirExists(dir);
474
+ for (const item of attachments) {
475
+ const dest = join4(dir, item.name);
476
+ const buffer = await downloadAttachment(cfg, sessionId, item.id);
477
+ writeFileSync3(dest, buffer);
1343
478
  console.log(
1344
- "[apm] \u5267\u573A\u6210\u5458\u56DE\u590D\u5DF2\u63D0\u4EA4",
1345
- runResult.failed ? "(\u6267\u884C\u5931\u8D25)" : ""
479
+ `[apm] \u5DF2\u4E0B\u8F7D\u9644\u4EF6: ${SESSION_ATTACHMENTS_SUBDIR}/${item.name}`
1346
480
  );
1347
- } catch (err) {
1348
- logTheaterJobError("\u6267\u884C\u6216\u63D0\u4EA4\u56DE\u590D", err, { sessionId, memberKey });
1349
- const reported = await submitTheaterFailure(api, ws, {
1350
- sessionId,
1351
- memberKey,
1352
- logId,
1353
- agentId,
1354
- stage: "\u6267\u884C\u4EFB\u52A1",
1355
- error: extractErrorMessage(err)
1356
- });
1357
- if (reported) {
1358
- console.error("[apm] \u5DF2\u5C06\u5931\u8D25\u539F\u56E0\u5199\u5165\u5267\u573A\u4F1A\u8BDD\uFF0C\u53EF\u5728\u7F51\u9875\u67E5\u770B");
1359
- }
1360
- }
1361
- }
1362
- async function handleWorkflowJob(api, payload) {
1363
- const apmRoot = join7(payload.cwd, ".apm");
1364
- console.log("[apm] ROOT:", apmRoot);
1365
- console.log("[apm] workflow node:", payload.nodeId);
1366
- try {
1367
- await runPull(payload.requirementId, apmRoot);
1368
- } catch (pullErr) {
1369
- console.error("[apm] apm pull \u5931\u8D25:", pullErr);
1370
- throw pullErr;
1371
- }
1372
- const agent = await createAgentForJob(payload);
1373
- await api.cliRequirements.updateDevStatus({
1374
- requirementId: payload.requirementId,
1375
- status: "WORKING"
1376
- });
1377
- await api.spaceWorkflow.aiStartNode({
1378
- requirementId: payload.requirementId,
1379
- nodeId: payload.nodeId,
1380
- agentId: agent.agentId
1381
- });
1382
- const session = new EventSession(payload.prompt);
1383
- const { agentId, failed, failReason } = await runAgentStream(
1384
- payload,
1385
- session,
1386
- agent
1387
- );
1388
- await api.cliRequirements.updateDevStatus({
1389
- requirementId: payload.requirementId,
1390
- status: "IDLE"
1391
- });
1392
- session.writeToFile(payload.cwd, payload.requirementId, agentId);
1393
- if (failed) {
1394
- const failResult = await api.spaceWorkflow.aiFailNode({
1395
- requirementId: payload.requirementId,
1396
- nodeId: payload.nodeId,
1397
- error: failReason
1398
- });
1399
- console.log("[apm] \u5DE5\u4F5C\u6D41\u8282\u70B9\u72B6\u6001:", failResult.nodeStatus);
1400
- return;
1401
- }
1402
- console.log("[Done]");
1403
- try {
1404
- await runUploadArtifact(payload.requirementId, apmRoot);
1405
- } catch (pullErr) {
1406
- console.error("[apm] apm upload-artifact \u5931\u8D25:", pullErr);
1407
- throw pullErr;
1408
- }
1409
- const confirmResult = await api.spaceWorkflow.aiConfirmNode({
1410
- requirementId: payload.requirementId,
1411
- nodeId: payload.nodeId
1412
- });
1413
- console.log("[apm] \u5DE5\u4F5C\u6D41\u8282\u70B9\u72B6\u6001:", confirmResult.nodeStatus);
1414
- }
1415
- function runConnect(opts) {
1416
- void (async () => {
1417
- const cfg = await ensureApmConfig();
1418
- const server = opts.server?.trim();
1419
- if (server) {
1420
- cfg.baseUrl = server.replace(/\/+$/, "");
1421
- await writeApmConfig(cfg);
1422
- }
1423
- if (!cfg.token) {
1424
- console.error(
1425
- "[apm] \u672A\u68C0\u6D4B\u5230 token\uFF0C\u8BF7\u5148\u6267\u884C: apm login --email ... --password ..."
1426
- );
1427
- console.error(`[apm] \u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84: ${APM_CONFIG_PATH}`);
1428
- process.exit(1);
1429
- }
1430
- const api = createApmApiClient(cfg);
1431
- const url = buildAgentWsUrl(cfg.baseUrl, cfg.token);
1432
- console.error(`[apm] \u8FDE\u63A5 ${url.replace(cfg.token, "<token>")} \u2026`);
1433
- const ws = new WebSocket(url);
1434
- ws.on("open", () => {
1435
- console.error("[apm] \u5DF2\u8FDE\u63A5");
1436
- const sendHeartbeat = () => {
1437
- const frame = {
1438
- type: "HEARTBEAT",
1439
- msg_id: randomUUID2(),
1440
- ts: (/* @__PURE__ */ new Date()).toISOString(),
1441
- payload: {}
1442
- };
1443
- ws.send(JSON.stringify(frame));
1444
- };
1445
- sendHeartbeat();
1446
- const interval = setInterval(sendHeartbeat, 15e3);
1447
- ws.on("close", () => clearInterval(interval));
1448
- });
1449
- ws.on("message", async (data) => {
1450
- const text = typeof data === "string" ? data : data.toString();
1451
- let msg;
1452
- try {
1453
- msg = JSON.parse(text);
1454
- } catch (err) {
1455
- console.error(
1456
- "[apm] \u65E0\u6CD5\u89E3\u6790 WebSocket \u6D88\u606F:",
1457
- text,
1458
- err.message
1459
- );
1460
- return;
1461
- }
1462
- try {
1463
- const jobType = msg.type;
1464
- if (jobType === "THEATER_JOB") {
1465
- await handleTheaterJob(api, ws, msg.payload);
1466
- return;
1467
- }
1468
- if (jobType === "JOB") {
1469
- await handleWorkflowJob(api, msg.payload);
1470
- return;
1471
- }
1472
- } catch (err) {
1473
- const jobType = msg.type;
1474
- const label = jobType === "THEATER_JOB" ? "\u5267\u573A THEATER_JOB" : jobType === "JOB" ? "\u5DE5\u4F5C\u6D41 JOB" : String(jobType);
1475
- console.error(`[apm] ${label} \u5904\u7406\u5931\u8D25:`, extractErrorMessage(err));
1476
- }
1477
- });
1478
- ws.on("error", (err) => {
1479
- console.error("[apm] WebSocket \u9519\u8BEF:", err.message);
1480
- });
1481
- ws.on("close", (code, reason) => {
1482
- console.error(`[apm] \u8FDE\u63A5\u5173\u95ED code=${code} reason=${reason.toString()}`);
1483
- process.exit(code === 1e3 ? 0 : 1);
1484
- });
1485
- })();
1486
- }
1487
-
1488
- // src/commands/init.ts
1489
- import { join as join8 } from "path";
1490
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
1491
- async function runInit(name) {
1492
- await ensureWorkspaceApmDirForInit();
1493
- await copyTemplateFiles(WORKSPACE_APM_DIR);
1494
- if (name) {
1495
- const apmConfigPath = join8(WORKSPACE_APM_DIR, "apm.config.json");
1496
- const config = readFileSync4(apmConfigPath, "utf8");
1497
- const configJson = JSON.parse(config);
1498
- configJson.name = name;
1499
- writeFileSync5(apmConfigPath, JSON.stringify(configJson, null, 2), "utf8");
1500
481
  }
1501
482
  }
1502
483
 
1503
- // src/commands/login.ts
1504
- import { ApiError } from "listpage-http";
1505
- async function runLogin(opts) {
1506
- const baseUrl = (opts.server?.trim() || process.env.AI_PM_SERVER?.trim() || DEFAULT_BASE_URL).replace(/\/+$/, "");
1507
- const api = createApmApiClient({
1508
- baseUrl,
1509
- userId: "",
1510
- token: ""
1511
- });
1512
- let data;
1513
- try {
1514
- data = await api.auth.login({
1515
- email: opts.email,
1516
- password: opts.password
1517
- });
1518
- } catch (raw) {
1519
- if (raw instanceof ApiError) {
1520
- const err = raw;
1521
- console.error(
1522
- `[apm] \u767B\u5F55\u5931\u8D25 (${err.httpStatus ?? err.code}):`,
1523
- err.message
1524
- );
1525
- process.exit(1);
1526
- }
1527
- console.error(
1528
- "[apm] \u8BF7\u6C42\u5931\u8D25:",
1529
- raw instanceof Error ? raw.message : raw
1530
- );
1531
- process.exit(1);
1532
- }
1533
- if (!data?.id || !data?.token) {
1534
- console.error("[apm] \u54CD\u5E94\u7F3A\u5C11 id / token\uFF08\u8BF7\u786E\u8BA4\u670D\u52A1\u7AEF\u4E3A /api/auth/login\uFF09");
484
+ // src/commands/pull.ts
485
+ async function runPull(sessionId, apmRoot) {
486
+ const trimmedId = sessionId.trim();
487
+ if (!trimmedId) {
488
+ console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
1535
489
  process.exit(1);
1536
490
  }
1537
- const cfg = {
1538
- baseUrl,
1539
- userId: data.id,
1540
- token: data.token,
1541
- email: opts.email
1542
- };
1543
- await writeApmConfig(cfg);
1544
- console.error(`[apm] \u5DF2\u4FDD\u5B58\u767B\u5F55\u4FE1\u606F: ${APM_CONFIG_PATH}`);
1545
- console.log(JSON.stringify({ userId: cfg.userId, baseUrl: cfg.baseUrl }, null, 2));
1546
- }
1547
-
1548
- // src/commands/refine.ts
1549
- import { readFileSync as readFileSync5 } from "fs";
1550
- import { join as join9 } from "path";
1551
- async function runRefine(requirementId) {
1552
491
  const cfg = await ensureLoggedConfig();
1553
- const filePath = join9(WORKSPACE_APM_DIR, "workitems", requirementId, "prd.md");
1554
- const content = readFileSync5(filePath, "utf8");
1555
492
  const api = createApmApiClient(cfg);
1556
- const data = await api.cliRequirements.refine({ requirementId, content });
1557
- console.log(JSON.stringify(data, null, 2));
493
+ const [detail, members, documents, attachments] = await Promise.all([
494
+ api.cli.sessionDetail({ sessionId: trimmedId }),
495
+ api.cli.sessionMembers({ sessionId: trimmedId }),
496
+ api.cli.listDocuments({ sessionId: trimmedId }),
497
+ api.cli.listAttachments({ sessionId: trimmedId })
498
+ ]);
499
+ const dir = sessionDir(trimmedId, apmRoot);
500
+ const docsDir = sessionDocsDir(trimmedId, apmRoot);
501
+ await ensureDirExists(docsDir);
502
+ writeFileSync4(
503
+ sessionRulePath(trimmedId, apmRoot),
504
+ detail.description ?? "",
505
+ "utf8"
506
+ );
507
+ for (const doc of documents) {
508
+ const fileName = documentLocalFileName(doc.name);
509
+ writeFileSync4(join5(docsDir, fileName), doc.content ?? "", "utf8");
510
+ }
511
+ const sessionYaml = yamlStringify(
512
+ {
513
+ name: detail.title,
514
+ description: "./RULE.md",
515
+ members: members.map((m) => ({
516
+ name: m.displayName,
517
+ position: m.position,
518
+ system_persona: m.systemPersona,
519
+ skills: []
520
+ })),
521
+ skills: [],
522
+ attachments: attachments.map((a) => ({ name: a.name }))
523
+ },
524
+ { lineWidth: 0 }
525
+ );
526
+ writeFileSync4(
527
+ sessionYamlPath(trimmedId, apmRoot),
528
+ sessionYaml.endsWith("\n") ? sessionYaml : `${sessionYaml}
529
+ `,
530
+ "utf8"
531
+ );
532
+ await syncSessionAttachments(cfg, trimmedId, attachments, apmRoot);
533
+ console.log(`[apm] \u5DF2\u540C\u6B65\u4F1A\u8BDD\u5DE5\u4F5C\u533A: ${dir}`);
534
+ return dir;
1558
535
  }
1559
536
 
1560
537
  // src/commands/update.ts
1561
538
  import { spawnSync } from "child_process";
1562
539
 
1563
540
  // src/version.ts
1564
- import { readFileSync as readFileSync6 } from "fs";
1565
- import { dirname as dirname3, join as join10 } from "path";
541
+ import { readFileSync as readFileSync3 } from "fs";
542
+ import { dirname as dirname2, join as join6 } from "path";
1566
543
  import { fileURLToPath as fileURLToPath2 } from "url";
1567
544
  var CLI_PACKAGE_NAME = "ai-project-manage-cli";
1568
545
  function readCliVersion() {
1569
546
  try {
1570
- const dir = dirname3(fileURLToPath2(import.meta.url));
1571
- const pkgPath = join10(dir, "..", "package.json");
1572
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
547
+ const dir = dirname2(fileURLToPath2(import.meta.url));
548
+ const pkgPath = join6(dir, "..", "package.json");
549
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
1573
550
  return pkg.version ?? "0.0.0";
1574
551
  } catch {
1575
552
  return "0.0.0";
@@ -1636,147 +613,139 @@ async function runUpdate() {
1636
613
  }
1637
614
 
1638
615
  // src/commands/update-skills.ts
1639
- import { cpSync as cpSync2, existsSync as existsSync4, rmSync, statSync as statSync3 } from "fs";
1640
- import { join as join11 } from "path";
1641
- var TEMPLATE_SKILLS_DIR = join11(CLI_TEMPLATE_DIR, "skills");
616
+ import { existsSync as existsSync2, mkdirSync as mkdirSync3, rmSync, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
617
+ import { join as join7 } from "path";
618
+ function sanitizeDirName(name) {
619
+ const trimmed = name.trim();
620
+ if (!trimmed) return "skill";
621
+ return trimmed.replace(/[/\\:*?"<>|]/g, "_");
622
+ }
1642
623
  async function runUpdateSkills() {
1643
- const apmDir = WORKSPACE_APM_DIR;
1644
- if (!existsSync4(apmDir)) {
624
+ const apmDir = workspaceApmDir();
625
+ if (!existsSync2(apmDir)) {
1645
626
  console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
1646
627
  process.exit(1);
1647
628
  }
1648
- const apmStat = statSync3(apmDir);
629
+ const apmStat = statSync2(apmDir);
1649
630
  if (!apmStat.isDirectory()) {
1650
631
  throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
1651
632
  }
1652
- let templateStat;
1653
- try {
1654
- templateStat = statSync3(TEMPLATE_SKILLS_DIR);
1655
- } catch {
1656
- throw new Error(`[apm] \u5185\u7F6E\u6280\u80FD\u6A21\u677F\u4E0D\u5B58\u5728: ${TEMPLATE_SKILLS_DIR}`);
1657
- }
1658
- if (!templateStat.isDirectory()) {
1659
- throw new Error(`[apm] \u5185\u7F6E\u6280\u80FD\u6A21\u677F\u4E0D\u662F\u76EE\u5F55: ${TEMPLATE_SKILLS_DIR}`);
1660
- }
1661
- const skillsDir = join11(apmDir, "skills");
1662
- if (existsSync4(skillsDir)) {
633
+ const cfg = await ensureLoggedConfig();
634
+ const api = createApmApiClient(cfg);
635
+ const { list } = await api.cli.listSkills({});
636
+ const skillsDir = join7(apmDir, "skills");
637
+ if (existsSync2(skillsDir)) {
1663
638
  rmSync(skillsDir, { recursive: true, force: true });
1664
639
  }
1665
- await ensureDirExists(apmDir);
1666
- cpSync2(TEMPLATE_SKILLS_DIR, skillsDir, { recursive: true });
1667
- console.log("[apm] \u5DF2\u66F4\u65B0 .apm/skills");
1668
- }
1669
-
1670
- // src/commands/update-theater-skills.ts
1671
- import { cpSync as cpSync3, existsSync as existsSync5, rmSync as rmSync2, statSync as statSync4 } from "fs";
1672
- import { join as join12 } from "path";
1673
- var TEMPLATE_THEATER_SKILLS_DIR = join12(CLI_TEMPLATE_DIR, "theater-skills");
1674
- async function runUpdateTheaterSkills() {
1675
- const apmDir = WORKSPACE_APM_DIR;
1676
- if (!existsSync5(apmDir)) {
1677
- console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
1678
- process.exit(1);
1679
- }
1680
- const apmStat = statSync4(apmDir);
1681
- if (!apmStat.isDirectory()) {
1682
- throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
1683
- }
1684
- let templateStat;
1685
- try {
1686
- templateStat = statSync4(TEMPLATE_THEATER_SKILLS_DIR);
1687
- } catch {
1688
- throw new Error(
1689
- `[apm] \u5185\u7F6E\u5267\u573A\u6280\u80FD\u6A21\u677F\u4E0D\u5B58\u5728: ${TEMPLATE_THEATER_SKILLS_DIR}`
1690
- );
1691
- }
1692
- if (!templateStat.isDirectory()) {
1693
- throw new Error(
1694
- `[apm] \u5185\u7F6E\u5267\u573A\u6280\u80FD\u6A21\u677F\u4E0D\u662F\u76EE\u5F55: ${TEMPLATE_THEATER_SKILLS_DIR}`
1695
- );
1696
- }
1697
- const theaterSkillsDir = join12(apmDir, "theater-skills");
1698
- if (existsSync5(theaterSkillsDir)) {
1699
- rmSync2(theaterSkillsDir, { recursive: true, force: true });
640
+ mkdirSync3(skillsDir, { recursive: true });
641
+ for (const skill of list) {
642
+ const dirName = sanitizeDirName(skill.name);
643
+ const skillDir = join7(skillsDir, dirName);
644
+ mkdirSync3(skillDir, { recursive: true });
645
+ writeFileSync5(join7(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
646
+ console.log(`[apm] \u5DF2\u5199\u5165\u6280\u80FD: skills/${dirName}/SKILL.md`);
1700
647
  }
1701
- await ensureDirExists(apmDir);
1702
- cpSync3(TEMPLATE_THEATER_SKILLS_DIR, theaterSkillsDir, { recursive: true });
1703
- console.log("[apm] \u5DF2\u66F4\u65B0 .apm/theater-skills");
648
+ console.log(`[apm] \u5DF2\u66F4\u65B0 ${list.length} \u4E2A\u6280\u80FD\u5230 .apm/skills`);
1704
649
  }
1705
650
 
1706
- // src/commands/sync-session-document.ts
1707
- import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
1708
- var DEFAULT_FILE = "prd.md";
1709
- async function runSyncSessionDocument(sessionId, options) {
651
+ // src/commands/sync-document.ts
652
+ import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
653
+ async function runSyncDocument(sessionId, options) {
1710
654
  const trimmedSessionId = sessionId.trim();
1711
655
  if (!trimmedSessionId) {
1712
656
  console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
1713
657
  process.exit(1);
1714
658
  }
1715
- const fileArg = (options?.file?.trim() || DEFAULT_FILE).replace(/\\/g, "/");
1716
- const apmRoot = options?.apmRoot ?? WORKSPACE_APM_DIR;
1717
- const localRelativePath = theaterArtifactLocalRelativePath(fileArg);
1718
- const absPath = theaterSessionDocumentPath(
1719
- trimmedSessionId,
1720
- fileArg,
1721
- apmRoot
1722
- );
1723
- if (!existsSync6(absPath)) {
1724
- console.error(`[apm] \u6587\u6863\u4E0D\u5B58\u5728: ${absPath}
1725
- \u8BF7\u5148\u521B\u5EFA\u8BE5\u6587\u4EF6\u540E\u518D\u540C\u6B65`);
659
+ const fileArg = options.file?.trim();
660
+ if (!fileArg) {
661
+ console.error("[apm] \u8BF7\u6307\u5B9A --file <\u672C\u5730\u8DEF\u5F84>");
662
+ process.exit(1);
663
+ }
664
+ const absPath = resolveCwdPath(fileArg);
665
+ if (!existsSync3(absPath)) {
666
+ console.error(`[apm] \u6587\u6863\u4E0D\u5B58\u5728: ${absPath}`);
1726
667
  process.exit(1);
1727
668
  }
1728
- const content = readFileSync7(absPath, "utf8");
669
+ const content = readFileSync4(absPath, "utf8");
670
+ const name = documentPlatformName(absPath);
1729
671
  const cfg = await ensureLoggedConfig();
1730
672
  const api = createApmApiClient(cfg);
1731
- const data = await api.theaterSessionArtifact.upsert({
673
+ const doc = await api.cli.upsertDocument({
1732
674
  sessionId: trimmedSessionId,
1733
- fileName: normalizeTheaterArtifactFileName(fileArg),
675
+ name,
1734
676
  content
1735
677
  });
1736
- console.log(
1737
- `[apm] \u5DF2\u540C\u6B65\u4F1A\u8BDD\u6587\u6863: ${localRelativePath} \u2192 ${data.fileName} (artifactId=${data.id})`
1738
- );
678
+ console.log(`[apm] \u5DF2\u540C\u6B65\u6587\u6863: ${name} (id=${doc.id})`);
1739
679
  }
1740
680
 
1741
- // src/commands/update-dev-status.ts
1742
- async function runUpdateDevStatus(requirementId, status) {
681
+ // src/commands/append-message.ts
682
+ import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
683
+ async function runAppendMessage(options) {
684
+ const messageId = options.id?.trim();
685
+ if (!messageId) {
686
+ console.error("[apm] \u8BF7\u6307\u5B9A --id <messageId>");
687
+ process.exit(1);
688
+ }
689
+ const fileArg = options.file?.trim();
690
+ if (!fileArg) {
691
+ console.error("[apm] \u8BF7\u6307\u5B9A --file <\u672C\u5730\u8DEF\u5F84>");
692
+ process.exit(1);
693
+ }
694
+ const absPath = resolveCwdPath(fileArg);
695
+ if (!existsSync4(absPath)) {
696
+ console.error(`[apm] \u6587\u4EF6\u4E0D\u5B58\u5728: ${absPath}`);
697
+ process.exit(1);
698
+ }
699
+ const content = readFileSync5(absPath, "utf8");
1743
700
  const cfg = await ensureLoggedConfig();
1744
701
  const api = createApmApiClient(cfg);
1745
- const data = await api.cliRequirements.updateDevStatus({
1746
- requirementId,
1747
- status
1748
- });
1749
- console.log(JSON.stringify(data, null, 2));
702
+ await api.cli.appendMessageContent({ id: messageId, content });
703
+ console.log(`[apm] \u5DF2\u8FFD\u52A0\u6D88\u606F\u5185\u5BB9: ${messageId}`);
1750
704
  }
1751
705
 
1752
- // src/commands/update-status.ts
1753
- async function runUpdateStatus(requirementId, status) {
706
+ // src/commands/update-message-status.ts
707
+ var VALID_STATUSES = [
708
+ "CREATED",
709
+ "TYPING",
710
+ "SUCCESS",
711
+ "FAILED"
712
+ ];
713
+ async function runUpdateMessageStatus(options) {
714
+ const messageId = options.id?.trim();
715
+ const status = options.status?.trim().toUpperCase();
716
+ if (!messageId) {
717
+ console.error("[apm] \u8BF7\u6307\u5B9A --id <messageId>");
718
+ process.exit(1);
719
+ }
720
+ if (!VALID_STATUSES.includes(status)) {
721
+ console.error(
722
+ `[apm] \u65E0\u6548\u72B6\u6001: ${options.status}\uFF0C\u53EF\u9009: ${VALID_STATUSES.join(", ")}`
723
+ );
724
+ process.exit(1);
725
+ }
1754
726
  const cfg = await ensureLoggedConfig();
1755
727
  const api = createApmApiClient(cfg);
1756
- const data = await api.cliRequirements.updateStatus({
1757
- requirementId,
1758
- status
1759
- });
1760
- console.log(JSON.stringify(data, null, 2));
728
+ await api.cli.updateMessageStatus({ id: messageId, status });
729
+ console.log(`[apm] \u5DF2\u66F4\u65B0\u6D88\u606F\u72B6\u6001: ${messageId} \u2192 ${status}`);
1761
730
  }
1762
731
 
1763
732
  // src/commands/deploy/backend.ts
1764
733
  import path5 from "node:path";
1765
734
 
1766
735
  // src/commands/deploy/internal/apm-config.ts
1767
- import { existsSync as existsSync7, readFileSync as readFileSync8 } from "node:fs";
1768
- import { resolve as resolve4 } from "node:path";
736
+ import { existsSync as existsSync5, readFileSync as readFileSync6 } from "node:fs";
737
+ import { resolve as resolve3 } from "node:path";
1769
738
  function loadApmConfig(options) {
1770
- const p = resolve4(
739
+ const p = resolve3(
1771
740
  process.cwd(),
1772
- options?.configPath ?? resolve4(WORKSPACE_APM_DIR, "apm.config.json")
741
+ options?.configPath ?? resolve3(workspaceApmDir(), "apm.config.json")
1773
742
  );
1774
- if (!existsSync7(p)) {
743
+ if (!existsSync5(p)) {
1775
744
  console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
1776
745
  process.exit(1);
1777
746
  }
1778
747
  try {
1779
- const raw = readFileSync8(p, "utf8");
748
+ const raw = readFileSync6(p, "utf8");
1780
749
  return JSON.parse(raw);
1781
750
  } catch (e) {
1782
751
  console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
@@ -1803,9 +772,7 @@ function reqTopLevelName(cfg) {
1803
772
  function reqBackendPositiveInt(v, field) {
1804
773
  const n = Number(v);
1805
774
  if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
1806
- console.error(
1807
- `apm.config.json \u4E2D backendDeploy.${field} \u987B\u4E3A\u6B63\u6574\u6570`
1808
- );
775
+ console.error(`apm.config.json \u4E2D backendDeploy.${field} \u987B\u4E3A\u6B63\u6574\u6570`);
1809
776
  process.exit(1);
1810
777
  }
1811
778
  return n;
@@ -1875,7 +842,7 @@ import path4 from "node:path";
1875
842
  import Docker from "dockerode";
1876
843
 
1877
844
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
1878
- import { existsSync as existsSync8, readFileSync as readFileSync9 } from "node:fs";
845
+ import { existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs";
1879
846
  import path from "node:path";
1880
847
  function asOptionalTlsBuffer(value) {
1881
848
  if (typeof value !== "string") {
@@ -1887,8 +854,8 @@ function asOptionalTlsBuffer(value) {
1887
854
  if (normalized === "") {
1888
855
  return void 0;
1889
856
  }
1890
- if (existsSync8(normalized)) {
1891
- return readFileSync9(normalized);
857
+ if (existsSync6(normalized)) {
858
+ return readFileSync7(normalized);
1892
859
  }
1893
860
  const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
1894
861
  if (looksLikePath) {
@@ -2013,17 +980,17 @@ var DockerodeClient = class {
2013
980
  await this.client.getImage(image).remove({ force: true });
2014
981
  }
2015
982
  async pullImage(image, auth) {
2016
- const stream = await new Promise((resolve5, reject) => {
983
+ const stream = await new Promise((resolve4, reject) => {
2017
984
  const pullOptions = auth ? { authconfig: auth } : void 0;
2018
985
  this.client.pull(image, pullOptions, (err, output) => {
2019
986
  if (err || !output) {
2020
987
  reject(err ?? new Error("docker pull \u8FD4\u56DE\u7A7A\u8F93\u51FA"));
2021
988
  return;
2022
989
  }
2023
- resolve5(output);
990
+ resolve4(output);
2024
991
  });
2025
992
  });
2026
- await new Promise((resolve5, reject) => {
993
+ await new Promise((resolve4, reject) => {
2027
994
  this.client.modem.followProgress(
2028
995
  stream,
2029
996
  (err) => {
@@ -2031,7 +998,7 @@ var DockerodeClient = class {
2031
998
  reject(err);
2032
999
  return;
2033
1000
  }
2034
- resolve5();
1001
+ resolve4();
2035
1002
  },
2036
1003
  () => void 0
2037
1004
  );
@@ -2098,7 +1065,7 @@ var DockerodeClient = class {
2098
1065
  var createDockerodeClient = (config) => new DockerodeClient(config);
2099
1066
 
2100
1067
  // src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
2101
- import { existsSync as existsSync9, readFileSync as readFileSync10, statSync as statSync5 } from "node:fs";
1068
+ import { existsSync as existsSync7, readFileSync as readFileSync8, statSync as statSync3 } from "node:fs";
2102
1069
  import path2 from "node:path";
2103
1070
  function stripSurroundingQuotes(value) {
2104
1071
  const t = value.trim();
@@ -2115,10 +1082,10 @@ function loadEnvFromFile(envFilePath) {
2115
1082
  return {};
2116
1083
  }
2117
1084
  const targetPath = path2.resolve(envFilePath);
2118
- if (!existsSync9(targetPath) || !statSync5(targetPath).isFile()) {
1085
+ if (!existsSync7(targetPath) || !statSync3(targetPath).isFile()) {
2119
1086
  return {};
2120
1087
  }
2121
- const raw = readFileSync10(targetPath, "utf-8");
1088
+ const raw = readFileSync8(targetPath, "utf-8");
2122
1089
  const result = {};
2123
1090
  for (const line of raw.split(/\r?\n/)) {
2124
1091
  const normalized = line.trim();
@@ -2289,12 +1256,12 @@ function dockerPushImage(params, cwd) {
2289
1256
  }
2290
1257
 
2291
1258
  // src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
2292
- import { existsSync as existsSync10 } from "node:fs";
1259
+ import { existsSync as existsSync8 } from "node:fs";
2293
1260
  import path3 from "node:path";
2294
1261
  function resolveDockerBuildPaths(cwd) {
2295
1262
  const dockerfilePath = path3.join(cwd, "Dockerfile");
2296
1263
  Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
2297
- if (!existsSync10(dockerfilePath)) {
1264
+ if (!existsSync8(dockerfilePath)) {
2298
1265
  throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
2299
1266
  }
2300
1267
  Logger.info("\u2713 Dockerfile \u5B58\u5728");
@@ -2423,16 +1390,16 @@ import { copyFile, readdir as readdir2, stat } from "node:fs/promises";
2423
1390
  import path7 from "node:path";
2424
1391
 
2425
1392
  // src/commands/deploy/internal/load-apm-dotenv.ts
2426
- import { existsSync as existsSync11, readFileSync as readFileSync11 } from "node:fs";
2427
- import { join as join13 } from "node:path";
1393
+ import { existsSync as existsSync9, readFileSync as readFileSync9 } from "node:fs";
1394
+ import { join as join8 } from "node:path";
2428
1395
  function loadApmDotEnvIfPresent() {
2429
- const p = join13(WORKSPACE_APM_DIR, ".env");
2430
- if (!existsSync11(p)) {
1396
+ const p = join8(workspaceApmDir(), ".env");
1397
+ if (!existsSync9(p)) {
2431
1398
  return;
2432
1399
  }
2433
1400
  let text;
2434
1401
  try {
2435
- text = readFileSync11(p, "utf8");
1402
+ text = readFileSync9(p, "utf8");
2436
1403
  } catch {
2437
1404
  return;
2438
1405
  }
@@ -2457,14 +1424,14 @@ function loadApmDotEnvIfPresent() {
2457
1424
  }
2458
1425
 
2459
1426
  // src/commands/deploy/internal/minio.ts
2460
- import { statSync as statSync6 } from "node:fs";
1427
+ import { statSync as statSync4 } from "node:fs";
2461
1428
  import { readdir, readFile } from "node:fs/promises";
2462
1429
  import path6 from "node:path";
2463
1430
  import * as Minio from "minio";
2464
1431
  var DEFAULT_MAX_FILE_SIZE_MB = 50;
2465
1432
  async function isDirectoryPath(dir) {
2466
1433
  try {
2467
- const st = statSync6(dir);
1434
+ const st = statSync4(dir);
2468
1435
  return st.isDirectory();
2469
1436
  } catch {
2470
1437
  return false;
@@ -2494,7 +1461,7 @@ async function collectFiles(root) {
2494
1461
  if (e.isDirectory()) {
2495
1462
  await walk(abs, rel);
2496
1463
  } else if (e.isFile()) {
2497
- const st = statSync6(abs);
1464
+ const st = statSync4(abs);
2498
1465
  out.push({
2499
1466
  absPath: abs,
2500
1467
  relativePath: rel.replace(/\\/g, "/"),
@@ -2556,14 +1523,14 @@ var MinioClient = class {
2556
1523
  async deleteObjectsByPrefix(bucket, prefix) {
2557
1524
  const objectsStream = this.inner.listObjectsV2(bucket, prefix, true);
2558
1525
  const keys = [];
2559
- await new Promise((resolve5, reject) => {
1526
+ await new Promise((resolve4, reject) => {
2560
1527
  objectsStream.on("data", (obj) => {
2561
1528
  if (obj.name) {
2562
1529
  keys.push(obj.name);
2563
1530
  }
2564
1531
  });
2565
1532
  objectsStream.on("error", reject);
2566
- objectsStream.on("end", resolve5);
1533
+ objectsStream.on("end", resolve4);
2567
1534
  });
2568
1535
  const chunkSize = 500;
2569
1536
  for (let i = 0; i < keys.length; i += chunkSize) {
@@ -2790,7 +1757,7 @@ function registerDeployCommands(program) {
2790
1757
  function buildProgram() {
2791
1758
  const program = new Command();
2792
1759
  program.name("apm").description(
2793
- `\u5E73\u53F0\u547D\u4EE4\u884C\uFF08Agent \u901A\u9053\u4E0E\u9700\u6C42 CLI\uFF09\u3002
1760
+ `OPC \u5E73\u53F0\u547D\u4EE4\u884C\uFF08\u4F1A\u8BDD\u5DE5\u4F5C\u533A\u4E0E\u7814\u53D1\u81EA\u52A8\u5316\uFF09\u3002
2794
1761
  \u672A\u4F20 --server \u65F6\u4F18\u5148\u4F7F\u7528\u73AF\u5883\u53D8\u91CF AI_PM_SERVER\uFF0C\u5426\u5219\u9ED8\u8BA4 ${DEFAULT_BASE_URL}\u3002`
2795
1762
  ).version(readCliVersion(), "-V, --version", "\u663E\u793A\u7248\u672C\u53F7").helpOption("-h, --help", "\u663E\u793A\u5E2E\u52A9").showHelpAfterError(true);
2796
1763
  program.command("login").description(
@@ -2800,9 +1767,6 @@ function buildProgram() {
2800
1767
  await runLogin(opts);
2801
1768
  }
2802
1769
  );
2803
- program.command("connect").description("\u8FDE\u63A5 WebSocket /ws/agent\uFF0C\u5E76\u6309\u56FA\u5B9A\u95F4\u9694\u53D1\u9001 HEARTBEAT").option("--server <url>", "API \u6839\u5730\u5740\uFF08\u5199\u5165\u672C\u5730\u914D\u7F6E\uFF09").action((opts) => {
2804
- runConnect(opts);
2805
- });
2806
1770
  program.command("init").description("\u5728\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u521D\u59CB\u5316 .apm \u6A21\u677F\uFF08\u82E5 .apm \u5DF2\u5B58\u5728\u4E14\u975E\u7A7A\u5219\u62A5\u9519\uFF09").option("--name <name>", "\u5DE5\u4F5C\u76EE\u5F55\u540D\u79F0").action(async (opts) => {
2807
1771
  await runInit(opts.name);
2808
1772
  });
@@ -2811,72 +1775,30 @@ function buildProgram() {
2811
1775
  ).action(async () => {
2812
1776
  await runUpdate();
2813
1777
  });
2814
- program.command("update-skills").description(
2815
- "\u5220\u9664\u5DE5\u4F5C\u533A .apm/skills \u540E\uFF0C\u4ECE\u5F53\u524D CLI \u5185\u7F6E\u6A21\u677F\u91CD\u65B0\u590D\u5236\u6280\u80FD\u76EE\u5F55"
2816
- ).action(async () => {
1778
+ program.command("update-skills").description("\u4ECE\u5E73\u53F0\u62C9\u53D6\u5F53\u524D\u7528\u6237\u53EF\u8BBF\u95EE\u6280\u80FD\u5230 .apm/skills/").action(async () => {
2817
1779
  await runUpdateSkills();
2818
1780
  });
2819
- program.command("update-theater-skills").description(
2820
- "\u5220\u9664\u5DE5\u4F5C\u533A .apm/theater-skills \u540E\uFF0C\u4ECE\u5F53\u524D CLI \u5185\u7F6E\u6A21\u677F\u91CD\u65B0\u590D\u5236\u5267\u573A\u6280\u80FD\u76EE\u5F55"
2821
- ).action(async () => {
2822
- await runUpdateTheaterSkills();
2823
- });
2824
- program.command("pull-session").description(
2825
- "\u5C06\u5E73\u53F0\u5267\u573A\u4F1A\u8BDD\u4EA7\u7269\u6587\u6863\u4E0B\u8F7D\u5230 .apm/theater-sessions/<sessionId>/\uFF08\u5982 docs/prd.md\uFF09\uFF1B\u672C\u5730\u5DF2\u6709\u540C\u540D\u6587\u4EF6\u65F6\u8986\u76D6"
2826
- ).argument("<sessionId>", "\u5267\u573A\u4F1A\u8BDD ID").action(async (sessionId) => {
2827
- const dir = await runPullTheaterSession(sessionId);
2828
- console.log(`[apm] \u4F1A\u8BDD\u6587\u6863\u76EE\u5F55: ${dir}`);
1781
+ program.command("pull").description(
1782
+ "\u62C9\u53D6\u6C9F\u901A\u7FA4\u6570\u636E\u5230 .apm/sessions/<sessionId>/\uFF08session.yaml\u3001RULE.md\u3001docs\u3001attachments\uFF09"
1783
+ ).argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").action(async (sessionId) => {
1784
+ await runPull(sessionId);
2829
1785
  });
2830
- program.command("sync-session-document").description(
2831
- "\u5C06 .apm/theater-sessions/<sessionId>/docs/ \u4E0B Markdown \u6587\u6863 upsert \u5230\u5E73\u53F0\u4F1A\u8BDD\u4EA7\u7269\uFF08\u4E0D\u542B\u5BF9\u8BDD\u65E5\u5FD7\uFF09"
2832
- ).argument("<sessionId>", "\u5267\u573A\u4F1A\u8BDD ID").option(
2833
- "--file <path>",
2834
- "\u6587\u6863\u540D\u6216\u76F8\u5BF9\u8DEF\u5F84\uFF08\u5982 prd.md\u3001docs/backend.md\uFF1B\u672C\u5730\u56FA\u5B9A\u843D\u5728 docs/ \u4E0B\uFF09"
2835
- ).action(async (sessionId, opts) => {
2836
- await runSyncSessionDocument(sessionId, { file: opts.file });
1786
+ program.command("sync-document").description("\u5C06\u672C\u5730 Markdown \u8986\u76D6\u5F0F upsert \u5230\u5E73\u53F0\u9700\u6C42\u6587\u6863").argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").requiredOption("--file <path>", "\u672C\u5730\u6587\u4EF6\u8DEF\u5F84").action(async (sessionId, opts) => {
1787
+ await runSyncDocument(sessionId, { file: opts.file });
2837
1788
  });
2838
- program.command("pull").description(
2839
- "GET /api/cli/requirements/pull\uFF0C\u540C\u6B65\u6570\u636E\u4E0E\u9644\u4EF6\u5230 .apm/workitems/<\u9700\u6C42ID>"
2840
- ).argument("<requirementId>", "\u9700\u6C42 ID").action(async (requirementId) => {
2841
- await runPull(requirementId);
1789
+ program.command("append-message").description("\u5411\u5E73\u53F0\u4F1A\u8BDD\u6D88\u606F\u8FFD\u52A0\u5185\u5BB9\uFF08PUT /api/cli/messages/content\uFF09").requiredOption("--id <messageId>", "\u6D88\u606F ID").requiredOption("--file <path>", "\u672C\u5730\u6587\u4EF6\u8DEF\u5F84").action(async (opts) => {
1790
+ await runAppendMessage(opts);
2842
1791
  });
2843
- program.command("upload-artifact").description(
2844
- "\u5148\u6E05\u7A7A\u8BE5\u9700\u6C42\u5728\u5E73\u53F0\u4E0A\u7684\u4EA7\u7269\u6587\u6863\uFF0C\u518D\u5C06 .apm/workitems/<\u9700\u6C42ID> \u4E0B Markdown \u540C\u6B65\u4E0A\u53BB\uFF08\u6392\u9664 pull \u7CFB\u7EDF\u6587\u4EF6\u4E0E attachments \u76EE\u5F55\uFF09"
2845
- ).argument("<requirementId>", "\u9700\u6C42 ID").action(async (requirementId) => {
2846
- await runUploadArtifact(requirementId);
1792
+ program.command("update-message-status").description("\u66F4\u65B0\u5E73\u53F0\u4F1A\u8BDD\u6D88\u606F\u72B6\u6001").requiredOption("--id <messageId>", "\u6D88\u606F ID").requiredOption("--status <status>", "CREATED | TYPING | SUCCESS | FAILED").action(async (opts) => {
1793
+ await runUpdateMessageStatus(opts);
2847
1794
  });
2848
1795
  program.command("branch").description(
2849
- "\u5207\u6362\u6216\u521B\u5EFA\u9700\u6C42\u5206\u652F feat/req-<ID>\uFF1A\u8FDC\u7AEF\u5B58\u5728\u5219\u62C9\u53D6\u6700\u65B0\uFF1B\u8FDC\u7AEF\u5C1A\u65E0\u8BE5\u5206\u652F\u4E14\u672C\u5730\u4E5F\u65E0\u540C\u540D\u5206\u652F\u65F6\uFF0C\u9700\u5DF2 login\uFF0C\u5E76\u7531\u5E73\u53F0\u6839\u636E\u5F53\u524D\u76EE\u5F55\u8DEF\u5F84\u89E3\u6790\u4ED3\u5E93\u57FA\u7EBF\u5206\u652F\u540E\u4ECE origin \u68C0\u51FA\u518D\u63A8\u9001\uFF1B\u6709\u672C\u5730\u672A\u63D0\u4EA4\u6539\u52A8\u65F6\u5728\u975E\u76EE\u6807\u5206\u652F\u5148 stash\uFF08\u4E0D\u81EA\u52A8\u6062\u590D\uFF09\uFF0C\u5728\u76EE\u6807\u5206\u652F\u5219\u5148 commit"
2850
- ).argument("<requirementId>", "\u9700\u6C42 ID").option(
1796
+ "\u5207\u6362\u6216\u521B\u5EFA\u9700\u6C42\u5206\u652F feat/req-<requirementId>\uFF08\u7531 sessionId \u89E3\u6790\u9700\u6C42\uFF09"
1797
+ ).argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").option(
2851
1798
  "-m, --message <text>",
2852
- "\u5DF2\u5728\u76EE\u6807\u5206\u652F\u4E14\u9700\u63D0\u4EA4\u672C\u5730\u6539\u52A8\u65F6\u4F7F\u7528\u7684\u63D0\u4EA4\u8BF4\u660E\uFF08\u9ED8\u8BA4\u81EA\u52A8\u751F\u6210\uFF09"
2853
- ).action(async (requirementId, opts) => {
2854
- await runBranch(requirementId, { message: opts.message });
2855
- });
2856
- program.command("comment").description(
2857
- "\u63D0\u4EA4\u9700\u6C42\u8BC4\u5BA1\uFF1Alegacy \u4E3A\u6574\u7BC7 Markdown\uFF08POST \u2026/comment\uFF09\uFF1Bstructured \u4E3A\u884C\u7EA7 YAML\uFF08POST \u2026/comment-structured\uFF09"
2858
- ).argument("<requirementId>", "\u9700\u6C42 ID").requiredOption(
2859
- "--file <path>",
2860
- "\u8BC4\u8BBA\u6587\u4EF6\u8DEF\u5F84\uFF08.yaml/.yml \u9ED8\u8BA4 structured\uFF09"
2861
- ).option(
2862
- "--format <format>",
2863
- "structured | legacy\uFF08\u9ED8\u8BA4 legacy\uFF1B.yaml/.yml \u672A\u6307\u5B9A\u65F6\u89C6\u4E3A structured\uFF09"
2864
- ).option("--model <model>", "\u8BC4\u8BBA\u6A21\u578B\uFF08\u8986\u76D6 YAML reviewer.model\uFF09").action(
2865
- async (requirementId, options) => {
2866
- await runComment(requirementId, options.file, {
2867
- model: options.model,
2868
- format: options.format
2869
- });
2870
- }
2871
- );
2872
- program.command("refine").description("POST /api/cli/requirements/refine\uFF08\u6B63\u6587\u6765\u81EA\u6587\u4EF6\uFF09").argument("<requirementId>", "\u9700\u6C42 ID").action(async (requirementId) => {
2873
- await runRefine(requirementId);
2874
- });
2875
- program.command("update-status").description("POST /api/cli/requirements/update-status").argument("<requirementId>", "\u9700\u6C42 ID").requiredOption("--status <RequirementStatus>", "\u9700\u6C42\u72B6\u6001\u679A\u4E3E\u503C").action(async (requirementId, options) => {
2876
- await runUpdateStatus(requirementId, options.status);
2877
- });
2878
- program.command("update-dev-status").description("POST /api/cli/requirements/update-dev-status").argument("<requirementId>", "\u9700\u6C42 ID").requiredOption("--status <status>", "\u6210\u5458\u5F00\u53D1\u72B6\u6001\uFF08\u81EA\u7531\u6587\u672C\uFF09").action(async (requirementId, options) => {
2879
- await runUpdateDevStatus(requirementId, options.status);
1799
+ "\u5DF2\u5728\u76EE\u6807\u5206\u652F\u4E14\u9700\u63D0\u4EA4\u672C\u5730\u6539\u52A8\u65F6\u4F7F\u7528\u7684\u63D0\u4EA4\u8BF4\u660E"
1800
+ ).action(async (sessionId, opts) => {
1801
+ await runBranch(sessionId, { message: opts.message });
2880
1802
  });
2881
1803
  registerDeployCommands(program);
2882
1804
  return program;