ai-project-manage-cli 4.0.23 → 5.0.2

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,148 @@ 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}`);
257
+ console.error("[apm] \u8BF7\u6C42\u5931\u8D25:", raw instanceof Error ? raw.message : raw);
258
+ process.exit(1);
862
259
  }
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`
260
+ const userId = data?.user?.id;
261
+ const token = data?.token;
262
+ if (!userId || !token) {
263
+ console.error(
264
+ "[apm] \u54CD\u5E94\u7F3A\u5C11 user.id / token\uFF08\u8BF7\u786E\u8BA4\u670D\u52A1\u7AEF\u4E3A /api/auth/login\uFF09"
883
265
  );
266
+ process.exit(1);
884
267
  }
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");
888
- }
889
- return name;
268
+ const cfg = {
269
+ baseUrl,
270
+ userId,
271
+ token,
272
+ email: opts.email
273
+ };
274
+ await writeApmConfig(cfg);
275
+ console.error(`[apm] \u5DF2\u4FDD\u5B58\u767B\u5F55\u4FE1\u606F: ${APM_CONFIG_PATH}`);
276
+ console.log(
277
+ JSON.stringify({ userId: cfg.userId, baseUrl: cfg.baseUrl }, null, 2)
278
+ );
890
279
  }
891
- async function fetchTheaterBaselineBranchFromApi(sessionId, cwd) {
280
+
281
+ // src/commands/branch.ts
282
+ import { execFile } from "child_process";
283
+ import { resolve as resolve2 } from "path";
284
+ import { promisify } from "util";
285
+ var execFileAsync = promisify(execFile);
286
+ async function fetchBaselineBranchFromApi(sessionId, cwd) {
892
287
  const cfg = await ensureLoggedConfig();
893
288
  const api = createApmApiClient(cfg);
894
- const workdirPath = resolve3(cwd);
895
- const res = await api.theater.sessionBranchBaseline({
289
+ const workdirPath = resolve2(cwd);
290
+ const res = await api.cli.branchBaseline({
896
291
  sessionId,
897
292
  workdirPath
898
293
  });
@@ -920,7 +315,6 @@ function branchNameForRequirement(requirementId) {
920
315
  }
921
316
  return `feat/req-${id}`;
922
317
  }
923
- var branchNameForTheaterSession = branchNameForRequirement;
924
318
  async function execGit(cwd, args, quiet) {
925
319
  try {
926
320
  const { stdout, stderr } = await execFileAsync("git", args, {
@@ -971,11 +365,6 @@ async function localBranchExists(cwd, branch) {
971
365
  return false;
972
366
  }
973
367
  }
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
368
  async function commitWorkingTreeIfDirty(cwd, message) {
980
369
  await ensureGitRepo(cwd);
981
370
  if (!await isWorkingTreeDirty(cwd)) {
@@ -1038,538 +427,129 @@ async function ensureFeatureBranch(branch, options, fetchBaseline) {
1038
427
  console.log(`[apm] \u5DF2\u5C31\u7EEA\u5206\u652F ${branch}`);
1039
428
  return branch;
1040
429
  }
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 = {}) {
430
+ async function runBranch(sessionId, options = {}) {
431
+ const trimmedSessionId = sessionId.trim();
432
+ if (!trimmedSessionId) {
433
+ console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
434
+ process.exit(1);
435
+ }
436
+ const cfg = await ensureLoggedConfig();
437
+ const api = createApmApiClient(cfg);
1051
438
  const cwd = options.cwd ?? process.cwd();
1052
- const branch = branchNameForTheaterSession(sessionId);
439
+ const baseline = await api.cli.branchBaseline({
440
+ sessionId: trimmedSessionId,
441
+ workdirPath: resolve2(cwd)
442
+ });
443
+ const branch = branchNameForRequirement(baseline.requirementId);
1053
444
  return ensureFeatureBranch(
1054
445
  branch,
1055
446
  options,
1056
- () => fetchTheaterBaselineBranchFromApi(sessionId, cwd)
447
+ () => fetchBaselineBranchFromApi(trimmedSessionId, cwd)
1057
448
  );
1058
449
  }
1059
450
 
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
- );
451
+ // src/commands/pull.ts
452
+ import { writeFileSync as writeFileSync4 } from "fs";
453
+ import { join as join5 } from "path";
454
+ import { stringify as yamlStringify } from "yaml";
455
+
456
+ // src/commands/sync-session-attachments.ts
457
+ import { writeFileSync as writeFileSync3 } from "fs";
458
+ import { join as join4 } from "path";
459
+ async function downloadAttachment(cfg, sessionId, attachmentId) {
460
+ const base = cfg.baseUrl.trim().replace(/\/+$/, "");
461
+ const q = new URLSearchParams({ sessionId, attachmentId });
462
+ const url = `${base}/api/cli/attachments/file?${q.toString()}`;
463
+ const res = await fetch(url, {
464
+ headers: { Authorization: `Bearer ${cfg.token}` }
1117
465
  });
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)
466
+ if (!res.ok) {
467
+ throw new Error(
468
+ `[apm] \u4E0B\u8F7D\u9644\u4EF6\u5931\u8D25 (${res.status}): attachmentId=${attachmentId}`
1175
469
  );
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
470
  }
1254
- return { agentId: run.agentId, failed, failReason };
471
+ return Buffer.from(await res.arrayBuffer());
1255
472
  }
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
- });
473
+ async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
474
+ if (attachments.length === 0) return;
475
+ const dir = join4(sessionDir(sessionId, apmRoot), SESSION_ATTACHMENTS_SUBDIR);
476
+ await ensureDirExists(dir);
477
+ for (const item of attachments) {
478
+ const dest = join4(dir, item.name);
479
+ const buffer = await downloadAttachment(cfg, sessionId, item.id);
480
+ writeFileSync3(dest, buffer);
1343
481
  console.log(
1344
- "[apm] \u5267\u573A\u6210\u5458\u56DE\u590D\u5DF2\u63D0\u4EA4",
1345
- runResult.failed ? "(\u6267\u884C\u5931\u8D25)" : ""
482
+ `[apm] \u5DF2\u4E0B\u8F7D\u9644\u4EF6: ${SESSION_ATTACHMENTS_SUBDIR}/${item.name}`
1346
483
  );
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
484
  }
1501
485
  }
1502
486
 
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");
487
+ // src/commands/pull.ts
488
+ async function runPull(sessionId, apmRoot) {
489
+ const trimmedId = sessionId.trim();
490
+ if (!trimmedId) {
491
+ console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
1535
492
  process.exit(1);
1536
493
  }
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
494
  const cfg = await ensureLoggedConfig();
1553
- const filePath = join9(WORKSPACE_APM_DIR, "workitems", requirementId, "prd.md");
1554
- const content = readFileSync5(filePath, "utf8");
1555
495
  const api = createApmApiClient(cfg);
1556
- const data = await api.cliRequirements.refine({ requirementId, content });
1557
- console.log(JSON.stringify(data, null, 2));
496
+ const [detail, members, documents, attachments] = await Promise.all([
497
+ api.cli.sessionDetail({ sessionId: trimmedId }),
498
+ api.cli.sessionMembers({ sessionId: trimmedId }),
499
+ api.cli.listDocuments({ sessionId: trimmedId }),
500
+ api.cli.listAttachments({ sessionId: trimmedId })
501
+ ]);
502
+ const dir = sessionDir(trimmedId, apmRoot);
503
+ const docsDir = sessionDocsDir(trimmedId, apmRoot);
504
+ await ensureDirExists(docsDir);
505
+ writeFileSync4(
506
+ sessionRulePath(trimmedId, apmRoot),
507
+ detail.description ?? "",
508
+ "utf8"
509
+ );
510
+ for (const doc of documents) {
511
+ const fileName = documentLocalFileName(doc.name);
512
+ writeFileSync4(join5(docsDir, fileName), doc.content ?? "", "utf8");
513
+ }
514
+ const sessionYaml = yamlStringify(
515
+ {
516
+ name: detail.title,
517
+ description: "./RULE.md",
518
+ members: members.map((m) => ({
519
+ name: m.displayName,
520
+ position: m.position,
521
+ system_persona: m.systemPersona,
522
+ skills: []
523
+ })),
524
+ skills: [],
525
+ attachments: attachments.map((a) => ({ name: a.name }))
526
+ },
527
+ { lineWidth: 0 }
528
+ );
529
+ writeFileSync4(
530
+ sessionYamlPath(trimmedId, apmRoot),
531
+ sessionYaml.endsWith("\n") ? sessionYaml : `${sessionYaml}
532
+ `,
533
+ "utf8"
534
+ );
535
+ await syncSessionAttachments(cfg, trimmedId, attachments, apmRoot);
536
+ console.log(`[apm] \u5DF2\u540C\u6B65\u4F1A\u8BDD\u5DE5\u4F5C\u533A: ${dir}`);
537
+ return dir;
1558
538
  }
1559
539
 
1560
540
  // src/commands/update.ts
1561
541
  import { spawnSync } from "child_process";
1562
542
 
1563
543
  // src/version.ts
1564
- import { readFileSync as readFileSync6 } from "fs";
1565
- import { dirname as dirname3, join as join10 } from "path";
544
+ import { readFileSync as readFileSync3 } from "fs";
545
+ import { dirname as dirname2, join as join6 } from "path";
1566
546
  import { fileURLToPath as fileURLToPath2 } from "url";
1567
547
  var CLI_PACKAGE_NAME = "ai-project-manage-cli";
1568
548
  function readCliVersion() {
1569
549
  try {
1570
- const dir = dirname3(fileURLToPath2(import.meta.url));
1571
- const pkgPath = join10(dir, "..", "package.json");
1572
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
550
+ const dir = dirname2(fileURLToPath2(import.meta.url));
551
+ const pkgPath = join6(dir, "..", "package.json");
552
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
1573
553
  return pkg.version ?? "0.0.0";
1574
554
  } catch {
1575
555
  return "0.0.0";
@@ -1636,147 +616,139 @@ async function runUpdate() {
1636
616
  }
1637
617
 
1638
618
  // 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");
619
+ import { existsSync as existsSync2, mkdirSync as mkdirSync3, rmSync, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
620
+ import { join as join7 } from "path";
621
+ function sanitizeDirName(name) {
622
+ const trimmed = name.trim();
623
+ if (!trimmed) return "skill";
624
+ return trimmed.replace(/[/\\:*?"<>|]/g, "_");
625
+ }
1642
626
  async function runUpdateSkills() {
1643
- const apmDir = WORKSPACE_APM_DIR;
1644
- if (!existsSync4(apmDir)) {
627
+ const apmDir = workspaceApmDir();
628
+ if (!existsSync2(apmDir)) {
1645
629
  console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
1646
630
  process.exit(1);
1647
631
  }
1648
- const apmStat = statSync3(apmDir);
632
+ const apmStat = statSync2(apmDir);
1649
633
  if (!apmStat.isDirectory()) {
1650
634
  throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
1651
635
  }
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)) {
636
+ const cfg = await ensureLoggedConfig();
637
+ const api = createApmApiClient(cfg);
638
+ const { list } = await api.cli.listSkills({});
639
+ const skillsDir = join7(apmDir, "skills");
640
+ if (existsSync2(skillsDir)) {
1663
641
  rmSync(skillsDir, { recursive: true, force: true });
1664
642
  }
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 });
643
+ mkdirSync3(skillsDir, { recursive: true });
644
+ for (const skill of list) {
645
+ const dirName = sanitizeDirName(skill.name);
646
+ const skillDir = join7(skillsDir, dirName);
647
+ mkdirSync3(skillDir, { recursive: true });
648
+ writeFileSync5(join7(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
649
+ console.log(`[apm] \u5DF2\u5199\u5165\u6280\u80FD: skills/${dirName}/SKILL.md`);
1700
650
  }
1701
- await ensureDirExists(apmDir);
1702
- cpSync3(TEMPLATE_THEATER_SKILLS_DIR, theaterSkillsDir, { recursive: true });
1703
- console.log("[apm] \u5DF2\u66F4\u65B0 .apm/theater-skills");
651
+ console.log(`[apm] \u5DF2\u66F4\u65B0 ${list.length} \u4E2A\u6280\u80FD\u5230 .apm/skills`);
1704
652
  }
1705
653
 
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) {
654
+ // src/commands/sync-document.ts
655
+ import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
656
+ async function runSyncDocument(sessionId, options) {
1710
657
  const trimmedSessionId = sessionId.trim();
1711
658
  if (!trimmedSessionId) {
1712
659
  console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
1713
660
  process.exit(1);
1714
661
  }
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`);
662
+ const fileArg = options.file?.trim();
663
+ if (!fileArg) {
664
+ console.error("[apm] \u8BF7\u6307\u5B9A --file <\u672C\u5730\u8DEF\u5F84>");
665
+ process.exit(1);
666
+ }
667
+ const absPath = resolveCwdPath(fileArg);
668
+ if (!existsSync3(absPath)) {
669
+ console.error(`[apm] \u6587\u6863\u4E0D\u5B58\u5728: ${absPath}`);
1726
670
  process.exit(1);
1727
671
  }
1728
- const content = readFileSync7(absPath, "utf8");
672
+ const content = readFileSync4(absPath, "utf8");
673
+ const name = documentPlatformName(absPath);
1729
674
  const cfg = await ensureLoggedConfig();
1730
675
  const api = createApmApiClient(cfg);
1731
- const data = await api.theaterSessionArtifact.upsert({
676
+ const doc = await api.cli.upsertDocument({
1732
677
  sessionId: trimmedSessionId,
1733
- fileName: normalizeTheaterArtifactFileName(fileArg),
678
+ name,
1734
679
  content
1735
680
  });
1736
- console.log(
1737
- `[apm] \u5DF2\u540C\u6B65\u4F1A\u8BDD\u6587\u6863: ${localRelativePath} \u2192 ${data.fileName} (artifactId=${data.id})`
1738
- );
681
+ console.log(`[apm] \u5DF2\u540C\u6B65\u6587\u6863: ${name} (id=${doc.id})`);
1739
682
  }
1740
683
 
1741
- // src/commands/update-dev-status.ts
1742
- async function runUpdateDevStatus(requirementId, status) {
684
+ // src/commands/append-message.ts
685
+ import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
686
+ async function runAppendMessage(options) {
687
+ const messageId = options.id?.trim();
688
+ if (!messageId) {
689
+ console.error("[apm] \u8BF7\u6307\u5B9A --id <messageId>");
690
+ process.exit(1);
691
+ }
692
+ const fileArg = options.file?.trim();
693
+ if (!fileArg) {
694
+ console.error("[apm] \u8BF7\u6307\u5B9A --file <\u672C\u5730\u8DEF\u5F84>");
695
+ process.exit(1);
696
+ }
697
+ const absPath = resolveCwdPath(fileArg);
698
+ if (!existsSync4(absPath)) {
699
+ console.error(`[apm] \u6587\u4EF6\u4E0D\u5B58\u5728: ${absPath}`);
700
+ process.exit(1);
701
+ }
702
+ const content = readFileSync5(absPath, "utf8");
1743
703
  const cfg = await ensureLoggedConfig();
1744
704
  const api = createApmApiClient(cfg);
1745
- const data = await api.cliRequirements.updateDevStatus({
1746
- requirementId,
1747
- status
1748
- });
1749
- console.log(JSON.stringify(data, null, 2));
705
+ await api.cli.appendMessageContent({ id: messageId, content });
706
+ console.log(`[apm] \u5DF2\u8FFD\u52A0\u6D88\u606F\u5185\u5BB9: ${messageId}`);
1750
707
  }
1751
708
 
1752
- // src/commands/update-status.ts
1753
- async function runUpdateStatus(requirementId, status) {
709
+ // src/commands/update-message-status.ts
710
+ var VALID_STATUSES = [
711
+ "CREATED",
712
+ "TYPING",
713
+ "SUCCESS",
714
+ "FAILED"
715
+ ];
716
+ async function runUpdateMessageStatus(options) {
717
+ const messageId = options.id?.trim();
718
+ const status = options.status?.trim().toUpperCase();
719
+ if (!messageId) {
720
+ console.error("[apm] \u8BF7\u6307\u5B9A --id <messageId>");
721
+ process.exit(1);
722
+ }
723
+ if (!VALID_STATUSES.includes(status)) {
724
+ console.error(
725
+ `[apm] \u65E0\u6548\u72B6\u6001: ${options.status}\uFF0C\u53EF\u9009: ${VALID_STATUSES.join(", ")}`
726
+ );
727
+ process.exit(1);
728
+ }
1754
729
  const cfg = await ensureLoggedConfig();
1755
730
  const api = createApmApiClient(cfg);
1756
- const data = await api.cliRequirements.updateStatus({
1757
- requirementId,
1758
- status
1759
- });
1760
- console.log(JSON.stringify(data, null, 2));
731
+ await api.cli.updateMessageStatus({ id: messageId, status });
732
+ console.log(`[apm] \u5DF2\u66F4\u65B0\u6D88\u606F\u72B6\u6001: ${messageId} \u2192 ${status}`);
1761
733
  }
1762
734
 
1763
735
  // src/commands/deploy/backend.ts
1764
736
  import path5 from "node:path";
1765
737
 
1766
738
  // 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";
739
+ import { existsSync as existsSync5, readFileSync as readFileSync6 } from "node:fs";
740
+ import { resolve as resolve3 } from "node:path";
1769
741
  function loadApmConfig(options) {
1770
- const p = resolve4(
742
+ const p = resolve3(
1771
743
  process.cwd(),
1772
- options?.configPath ?? resolve4(WORKSPACE_APM_DIR, "apm.config.json")
744
+ options?.configPath ?? resolve3(workspaceApmDir(), "apm.config.json")
1773
745
  );
1774
- if (!existsSync7(p)) {
746
+ if (!existsSync5(p)) {
1775
747
  console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
1776
748
  process.exit(1);
1777
749
  }
1778
750
  try {
1779
- const raw = readFileSync8(p, "utf8");
751
+ const raw = readFileSync6(p, "utf8");
1780
752
  return JSON.parse(raw);
1781
753
  } catch (e) {
1782
754
  console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
@@ -1803,9 +775,7 @@ function reqTopLevelName(cfg) {
1803
775
  function reqBackendPositiveInt(v, field) {
1804
776
  const n = Number(v);
1805
777
  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
- );
778
+ console.error(`apm.config.json \u4E2D backendDeploy.${field} \u987B\u4E3A\u6B63\u6574\u6570`);
1809
779
  process.exit(1);
1810
780
  }
1811
781
  return n;
@@ -1875,7 +845,7 @@ import path4 from "node:path";
1875
845
  import Docker from "dockerode";
1876
846
 
1877
847
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
1878
- import { existsSync as existsSync8, readFileSync as readFileSync9 } from "node:fs";
848
+ import { existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs";
1879
849
  import path from "node:path";
1880
850
  function asOptionalTlsBuffer(value) {
1881
851
  if (typeof value !== "string") {
@@ -1887,8 +857,8 @@ function asOptionalTlsBuffer(value) {
1887
857
  if (normalized === "") {
1888
858
  return void 0;
1889
859
  }
1890
- if (existsSync8(normalized)) {
1891
- return readFileSync9(normalized);
860
+ if (existsSync6(normalized)) {
861
+ return readFileSync7(normalized);
1892
862
  }
1893
863
  const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
1894
864
  if (looksLikePath) {
@@ -2013,17 +983,17 @@ var DockerodeClient = class {
2013
983
  await this.client.getImage(image).remove({ force: true });
2014
984
  }
2015
985
  async pullImage(image, auth) {
2016
- const stream = await new Promise((resolve5, reject) => {
986
+ const stream = await new Promise((resolve4, reject) => {
2017
987
  const pullOptions = auth ? { authconfig: auth } : void 0;
2018
988
  this.client.pull(image, pullOptions, (err, output) => {
2019
989
  if (err || !output) {
2020
990
  reject(err ?? new Error("docker pull \u8FD4\u56DE\u7A7A\u8F93\u51FA"));
2021
991
  return;
2022
992
  }
2023
- resolve5(output);
993
+ resolve4(output);
2024
994
  });
2025
995
  });
2026
- await new Promise((resolve5, reject) => {
996
+ await new Promise((resolve4, reject) => {
2027
997
  this.client.modem.followProgress(
2028
998
  stream,
2029
999
  (err) => {
@@ -2031,7 +1001,7 @@ var DockerodeClient = class {
2031
1001
  reject(err);
2032
1002
  return;
2033
1003
  }
2034
- resolve5();
1004
+ resolve4();
2035
1005
  },
2036
1006
  () => void 0
2037
1007
  );
@@ -2098,7 +1068,7 @@ var DockerodeClient = class {
2098
1068
  var createDockerodeClient = (config) => new DockerodeClient(config);
2099
1069
 
2100
1070
  // src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
2101
- import { existsSync as existsSync9, readFileSync as readFileSync10, statSync as statSync5 } from "node:fs";
1071
+ import { existsSync as existsSync7, readFileSync as readFileSync8, statSync as statSync3 } from "node:fs";
2102
1072
  import path2 from "node:path";
2103
1073
  function stripSurroundingQuotes(value) {
2104
1074
  const t = value.trim();
@@ -2115,10 +1085,10 @@ function loadEnvFromFile(envFilePath) {
2115
1085
  return {};
2116
1086
  }
2117
1087
  const targetPath = path2.resolve(envFilePath);
2118
- if (!existsSync9(targetPath) || !statSync5(targetPath).isFile()) {
1088
+ if (!existsSync7(targetPath) || !statSync3(targetPath).isFile()) {
2119
1089
  return {};
2120
1090
  }
2121
- const raw = readFileSync10(targetPath, "utf-8");
1091
+ const raw = readFileSync8(targetPath, "utf-8");
2122
1092
  const result = {};
2123
1093
  for (const line of raw.split(/\r?\n/)) {
2124
1094
  const normalized = line.trim();
@@ -2289,12 +1259,12 @@ function dockerPushImage(params, cwd) {
2289
1259
  }
2290
1260
 
2291
1261
  // src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
2292
- import { existsSync as existsSync10 } from "node:fs";
1262
+ import { existsSync as existsSync8 } from "node:fs";
2293
1263
  import path3 from "node:path";
2294
1264
  function resolveDockerBuildPaths(cwd) {
2295
1265
  const dockerfilePath = path3.join(cwd, "Dockerfile");
2296
1266
  Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
2297
- if (!existsSync10(dockerfilePath)) {
1267
+ if (!existsSync8(dockerfilePath)) {
2298
1268
  throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
2299
1269
  }
2300
1270
  Logger.info("\u2713 Dockerfile \u5B58\u5728");
@@ -2423,16 +1393,16 @@ import { copyFile, readdir as readdir2, stat } from "node:fs/promises";
2423
1393
  import path7 from "node:path";
2424
1394
 
2425
1395
  // 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";
1396
+ import { existsSync as existsSync9, readFileSync as readFileSync9 } from "node:fs";
1397
+ import { join as join8 } from "node:path";
2428
1398
  function loadApmDotEnvIfPresent() {
2429
- const p = join13(WORKSPACE_APM_DIR, ".env");
2430
- if (!existsSync11(p)) {
1399
+ const p = join8(workspaceApmDir(), ".env");
1400
+ if (!existsSync9(p)) {
2431
1401
  return;
2432
1402
  }
2433
1403
  let text;
2434
1404
  try {
2435
- text = readFileSync11(p, "utf8");
1405
+ text = readFileSync9(p, "utf8");
2436
1406
  } catch {
2437
1407
  return;
2438
1408
  }
@@ -2457,14 +1427,14 @@ function loadApmDotEnvIfPresent() {
2457
1427
  }
2458
1428
 
2459
1429
  // src/commands/deploy/internal/minio.ts
2460
- import { statSync as statSync6 } from "node:fs";
1430
+ import { statSync as statSync4 } from "node:fs";
2461
1431
  import { readdir, readFile } from "node:fs/promises";
2462
1432
  import path6 from "node:path";
2463
1433
  import * as Minio from "minio";
2464
1434
  var DEFAULT_MAX_FILE_SIZE_MB = 50;
2465
1435
  async function isDirectoryPath(dir) {
2466
1436
  try {
2467
- const st = statSync6(dir);
1437
+ const st = statSync4(dir);
2468
1438
  return st.isDirectory();
2469
1439
  } catch {
2470
1440
  return false;
@@ -2494,7 +1464,7 @@ async function collectFiles(root) {
2494
1464
  if (e.isDirectory()) {
2495
1465
  await walk(abs, rel);
2496
1466
  } else if (e.isFile()) {
2497
- const st = statSync6(abs);
1467
+ const st = statSync4(abs);
2498
1468
  out.push({
2499
1469
  absPath: abs,
2500
1470
  relativePath: rel.replace(/\\/g, "/"),
@@ -2556,14 +1526,14 @@ var MinioClient = class {
2556
1526
  async deleteObjectsByPrefix(bucket, prefix) {
2557
1527
  const objectsStream = this.inner.listObjectsV2(bucket, prefix, true);
2558
1528
  const keys = [];
2559
- await new Promise((resolve5, reject) => {
1529
+ await new Promise((resolve4, reject) => {
2560
1530
  objectsStream.on("data", (obj) => {
2561
1531
  if (obj.name) {
2562
1532
  keys.push(obj.name);
2563
1533
  }
2564
1534
  });
2565
1535
  objectsStream.on("error", reject);
2566
- objectsStream.on("end", resolve5);
1536
+ objectsStream.on("end", resolve4);
2567
1537
  });
2568
1538
  const chunkSize = 500;
2569
1539
  for (let i = 0; i < keys.length; i += chunkSize) {
@@ -2790,7 +1760,7 @@ function registerDeployCommands(program) {
2790
1760
  function buildProgram() {
2791
1761
  const program = new Command();
2792
1762
  program.name("apm").description(
2793
- `\u5E73\u53F0\u547D\u4EE4\u884C\uFF08Agent \u901A\u9053\u4E0E\u9700\u6C42 CLI\uFF09\u3002
1763
+ `OPC \u5E73\u53F0\u547D\u4EE4\u884C\uFF08\u4F1A\u8BDD\u5DE5\u4F5C\u533A\u4E0E\u7814\u53D1\u81EA\u52A8\u5316\uFF09\u3002
2794
1764
  \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
1765
  ).version(readCliVersion(), "-V, --version", "\u663E\u793A\u7248\u672C\u53F7").helpOption("-h, --help", "\u663E\u793A\u5E2E\u52A9").showHelpAfterError(true);
2796
1766
  program.command("login").description(
@@ -2800,9 +1770,6 @@ function buildProgram() {
2800
1770
  await runLogin(opts);
2801
1771
  }
2802
1772
  );
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
1773
  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
1774
  await runInit(opts.name);
2808
1775
  });
@@ -2811,72 +1778,30 @@ function buildProgram() {
2811
1778
  ).action(async () => {
2812
1779
  await runUpdate();
2813
1780
  });
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 () => {
1781
+ 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
1782
  await runUpdateSkills();
2818
1783
  });
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}`);
1784
+ program.command("pull").description(
1785
+ "\u62C9\u53D6\u6C9F\u901A\u7FA4\u6570\u636E\u5230 .apm/sessions/<sessionId>/\uFF08session.yaml\u3001RULE.md\u3001docs\u3001attachments\uFF09"
1786
+ ).argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").action(async (sessionId) => {
1787
+ await runPull(sessionId);
2829
1788
  });
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 });
1789
+ 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) => {
1790
+ await runSyncDocument(sessionId, { file: opts.file });
2837
1791
  });
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);
1792
+ 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) => {
1793
+ await runAppendMessage(opts);
2842
1794
  });
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);
1795
+ 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) => {
1796
+ await runUpdateMessageStatus(opts);
2847
1797
  });
2848
1798
  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(
1799
+ "\u5207\u6362\u6216\u521B\u5EFA\u9700\u6C42\u5206\u652F feat/req-<requirementId>\uFF08\u7531 sessionId \u89E3\u6790\u9700\u6C42\uFF09"
1800
+ ).argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").option(
2851
1801
  "-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);
1802
+ "\u5DF2\u5728\u76EE\u6807\u5206\u652F\u4E14\u9700\u63D0\u4EA4\u672C\u5730\u6539\u52A8\u65F6\u4F7F\u7528\u7684\u63D0\u4EA4\u8BF4\u660E"
1803
+ ).action(async (sessionId, opts) => {
1804
+ await runBranch(sessionId, { message: opts.message });
2880
1805
  });
2881
1806
  registerDeployCommands(program);
2882
1807
  return program;