@tomflow/proflow-platform-cli 0.1.37 → 0.1.39

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @tomflow/proflow-platform-cli
2
2
 
3
+ ## 0.1.39
4
+
5
+ ### Patch Changes
6
+
7
+ - Add professional setup orchestration, Provider FAST/THINK input, recoverable managed Dev Tunnel provisioning, Browser installation guidance, and precise status diagnostics.
8
+
9
+ ## 0.1.38
10
+
11
+ ### Patch Changes
12
+
13
+ - Retire the obsolete Carrier package and Browser standalone management CLI, make Platform setup/status preserve owner and dependency semantics, and enforce read-only status observation across the final Real-3 deployment closeout.
14
+
3
15
  ## 0.1.37
4
16
 
5
17
  ### Patch Changes
@@ -6,7 +6,7 @@ export declare const behaviorAdapter: {
6
6
  readonly ok: true;
7
7
  readonly status: "SUCCEEDED";
8
8
  readonly moduleRef: "platform-cli";
9
- readonly moduleVersion: "0.1.37";
9
+ readonly moduleVersion: "0.1.39";
10
10
  };
11
11
  observedEffects: never[];
12
12
  }>;
@@ -16,7 +16,7 @@ export declare const behaviorAdapter: {
16
16
  readonly ok: true;
17
17
  readonly status: "SUCCEEDED";
18
18
  readonly moduleRef: "platform-cli";
19
- readonly moduleVersion: "0.1.37";
19
+ readonly moduleVersion: "0.1.39";
20
20
  };
21
21
  observedEffects: never[];
22
22
  }>;
@@ -26,7 +26,7 @@ export declare const behaviorAdapter: {
26
26
  ok: true;
27
27
  status: "SUCCEEDED";
28
28
  moduleRef: "platform-cli";
29
- moduleVersion: "0.1.37";
29
+ moduleVersion: "0.1.39";
30
30
  data: {
31
31
  readonly setupStatus: "READY";
32
32
  readonly runtimeStatus: "NOT_APPLICABLE";
@@ -40,7 +40,7 @@ export declare const behaviorAdapter: {
40
40
  readonly ok: true;
41
41
  readonly status: "SUCCEEDED";
42
42
  readonly moduleRef: "platform-cli";
43
- readonly moduleVersion: "0.1.37";
43
+ readonly moduleVersion: "0.1.39";
44
44
  };
45
45
  observedEffects: never[];
46
46
  }>;
@@ -50,7 +50,7 @@ export declare const behaviorAdapter: {
50
50
  ok: true;
51
51
  status: "SUCCEEDED";
52
52
  moduleRef: "platform-cli";
53
- moduleVersion: "0.1.37";
53
+ moduleVersion: "0.1.39";
54
54
  data: {
55
55
  docs: string;
56
56
  };
@@ -63,7 +63,7 @@ export declare const behaviorAdapter: {
63
63
  readonly ok: true;
64
64
  readonly status: "SUCCEEDED";
65
65
  readonly moduleRef: "platform-cli";
66
- readonly moduleVersion: "0.1.37";
66
+ readonly moduleVersion: "0.1.39";
67
67
  };
68
68
  observedEffects: never[];
69
69
  }>;
@@ -73,7 +73,7 @@ export declare const behaviorAdapter: {
73
73
  readonly ok: true;
74
74
  readonly status: "SUCCEEDED";
75
75
  readonly moduleRef: "platform-cli";
76
- readonly moduleVersion: "0.1.37";
76
+ readonly moduleVersion: "0.1.39";
77
77
  };
78
78
  observedEffects: never[];
79
79
  }>;
@@ -3,7 +3,7 @@ export declare const descriptor: {
3
3
  readonly contractVersion: "1.0.0";
4
4
  readonly moduleRef: "platform-cli";
5
5
  readonly packageName: "@tomflow/proflow-platform-cli";
6
- readonly moduleVersion: "0.1.37";
6
+ readonly moduleVersion: "0.1.39";
7
7
  readonly kind: "cli";
8
8
  readonly templateVersion: "1.0.0";
9
9
  readonly platformCompatibility: ">=1.0.0 <2.0.0";
@@ -3,7 +3,7 @@ export const descriptor = {
3
3
  contractVersion: "1.0.0",
4
4
  moduleRef: "platform-cli",
5
5
  packageName: "@tomflow/proflow-platform-cli",
6
- moduleVersion: "0.1.37",
6
+ moduleVersion: "0.1.39",
7
7
  kind: "cli",
8
8
  templateVersion: "1.0.0",
9
9
  platformCompatibility: ">=1.0.0 <2.0.0",
package/dist/src/cli.d.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  import { type PackageCommandRunner } from "./install/package-manager.ts";
3
3
  import { type PlatformProgressReporter } from "./progress.ts";
4
4
  import { type NpmCommandRunner } from "./registry/index.ts";
5
+ import { type SetupInteraction } from "./setup/interaction.ts";
5
6
  declare const COMMANDS: readonly ["install", "uninstall", "status", "setup", "docs", "start", "stop"];
6
7
  type Command = (typeof COMMANDS)[number];
7
8
  export type CliStatus = "SUCCEEDED" | "ACTION_REQUIRED" | "BLOCKED" | "FAILED";
@@ -23,6 +24,7 @@ export interface CliRuntimeOptions {
23
24
  packageRunner?: PackageCommandRunner;
24
25
  executableAvailable?: (command: string) => boolean;
25
26
  onProgress?: PlatformProgressReporter;
27
+ setupInteraction?: SetupInteraction;
26
28
  }
27
29
  export declare function runCli(argv: readonly string[], runtime?: CliRuntimeOptions): Promise<CliOutcome>;
28
30
  export interface HumanRenderOptions {
package/dist/src/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFile, realpath, stat } from "node:fs/promises";
3
3
  import { resolve } from "node:path";
4
- import { moduleDocsDataSchema, moduleSetupPlanDataSchema, moduleStatusObservationSchema, } from "@tomflow/proflow-module-contract";
4
+ import { moduleDocsDataSchema, moduleSetupPlanDataSchema, moduleStatusObservationSchema, readModuleSharedFacts, } from "@tomflow/proflow-module-contract";
5
5
  import { descriptor as platformCliDescriptor } from "../deployment/descriptor.js";
6
6
  import { AutoModuleCatalog, discoverModules } from "./discovery/discover.js";
7
7
  import { InstalledModuleCatalog } from "./discovery/installed.js";
@@ -12,6 +12,7 @@ import { installModulesThin, observeDocs, observeStatuses, setupModulesThin, sta
12
12
  import { ensureWorkspaceMetadata } from "./persistence/workspace-metadata.js";
13
13
  import { reportProgress } from "./progress.js";
14
14
  import { discoverRegistryModules, PRO_FLOW_PACKAGE_PREFIX, } from "./registry/index.js";
15
+ import { createClackSetupInteraction, } from "./setup/interaction.js";
15
16
  import { createTerminalProgressReporter } from "./terminal.js";
16
17
  const COMMANDS = [
17
18
  "install",
@@ -70,8 +71,8 @@ function parseArgs(argv) {
70
71
  if (!COMMANDS.includes(raw))
71
72
  throw new PlatformError("INVALID_REQUEST", `unknown command ${raw}`);
72
73
  const command = raw;
73
- if (moduleRef !== undefined && command !== "setup")
74
- throw new PlatformError("INVALID_REQUEST", "--module is only valid with setup");
74
+ if (moduleRef !== undefined && command !== "setup" && command !== "docs")
75
+ throw new PlatformError("INVALID_REQUEST", "--module is only valid with setup or docs");
75
76
  return {
76
77
  command,
77
78
  ...(workspace === undefined ? {} : { workspace }),
@@ -158,6 +159,9 @@ async function handleStatus(root, runtime) {
158
159
  ...(parsed.data.issues === undefined
159
160
  ? {}
160
161
  : { issues: parsed.data.issues }),
162
+ ...(item.externalAvailabilityClaim === undefined
163
+ ? {}
164
+ : { externalAvailabilityClaim: item.externalAvailabilityClaim }),
161
165
  };
162
166
  });
163
167
  reportProgress(runtime.onProgress, {
@@ -168,7 +172,7 @@ async function handleStatus(root, runtime) {
168
172
  });
169
173
  return outcome("status", "SUCCEEDED", root, { modules: output });
170
174
  }
171
- async function handleDocs(root, runtime) {
175
+ async function handleDocs(root, parsed, runtime) {
172
176
  reportProgress(runtime.onProgress, {
173
177
  command: "docs",
174
178
  phase: "docs",
@@ -176,7 +180,28 @@ async function handleDocs(root, runtime) {
176
180
  message: "正在整理模块文档",
177
181
  });
178
182
  const { catalog, modules: resolvedModules } = await buildContext(root);
179
- const docs = await observeDocs(catalog, resolvedModules, root);
183
+ const selectedModules = parsed.moduleRef === undefined
184
+ ? resolvedModules
185
+ : resolvedModules.filter((item) => item.moduleRef === parsed.moduleRef);
186
+ if (parsed.moduleRef !== undefined && selectedModules.length === 0)
187
+ throw new PlatformError("INVALID_REQUEST", `docs target module ${parsed.moduleRef} was not discovered`);
188
+ if (parsed.moduleRef === undefined) {
189
+ reportProgress(runtime.onProgress, {
190
+ command: "docs",
191
+ phase: "docs",
192
+ status: "SUCCEEDED",
193
+ message: `已整理 ${selectedModules.length} 个模块的文档索引`,
194
+ });
195
+ return outcome("docs", "SUCCEEDED", root, {
196
+ indexOnly: true,
197
+ modules: selectedModules.map((module) => ({
198
+ moduleRef: module.moduleRef,
199
+ version: module.moduleVersion,
200
+ })),
201
+ errors: [],
202
+ });
203
+ }
204
+ const docs = await observeDocs(catalog, selectedModules, root);
180
205
  const byRef = new Map(resolvedModules.map((module) => [module.moduleRef, module]));
181
206
  const modules = [];
182
207
  const errors = [];
@@ -267,7 +292,7 @@ async function handleInstall(root, runtime) {
267
292
  kind: "detail",
268
293
  status: "STARTED",
269
294
  total,
270
- message: `Registry 返回 ${total} 个候选模块,正在核验`,
295
+ message: `Registry 返回 ${total} 个候选模块,正在做安装前核验`,
271
296
  }),
272
297
  onPackageChecked: ({ current, total, packageName }) => reportProgress(runtime.onProgress, {
273
298
  command: "install",
@@ -277,7 +302,7 @@ async function handleInstall(root, runtime) {
277
302
  current,
278
303
  total,
279
304
  moduleRef: packageName.replace(PRO_FLOW_PACKAGE_PREFIX, ""),
280
- message: `核验 ${packageName.replace(PRO_FLOW_PACKAGE_PREFIX, "")}`,
305
+ message: `安装前核验 ${packageName.replace(PRO_FLOW_PACKAGE_PREFIX, "")}`,
281
306
  }),
282
307
  ...(runtime.registryRunner === undefined
283
308
  ? {}
@@ -332,7 +357,7 @@ async function handleInstall(root, runtime) {
332
357
  phase: "validation",
333
358
  kind: "phase",
334
359
  status: "SUCCEEDED",
335
- message: `已验证 ${modules.length} 个已安装模块`,
360
+ message: `安装完成,${modules.length} 个模块已从本地安装物验证`,
336
361
  });
337
362
  const moduleInstall = await installModulesThin(catalog, modules, root, runtime.onProgress);
338
363
  return outcome("install", batchStatus(moduleInstall), root, {
@@ -425,17 +450,78 @@ async function handleSetup(root, parsed, runtime) {
425
450
  message: "正在分析模块配置",
426
451
  });
427
452
  const { catalog, modules } = await buildContext(root);
428
- const target = parsed.moduleRef === undefined
453
+ const interaction = runtime.setupInteraction;
454
+ const inputByModule = new Map();
455
+ const approvedSteps = new Set();
456
+ const confirmSetupStep = interaction?.confirmStep
457
+ ? async (moduleRef) => {
458
+ if (approvedSteps.has(moduleRef))
459
+ return;
460
+ if (!(await interaction.confirmStep?.(moduleRef)))
461
+ throw new PlatformError("INVALID_REQUEST", "配置已取消");
462
+ approvedSteps.add(moduleRef);
463
+ }
464
+ : undefined;
465
+ if (interaction)
466
+ interaction.begin();
467
+ let target = parsed.moduleRef === undefined
429
468
  ? undefined
430
469
  : { moduleRef: parsed.moduleRef };
431
- const result = await setupModulesThin(catalog, modules, root, target, undefined);
470
+ let result = await setupModulesThin(catalog, modules, root, target, runtime.onProgress, confirmSetupStep);
471
+ for (let attempt = 0; interaction && !result.completed && attempt < 8; attempt += 1) {
472
+ const actionable = [...result.results]
473
+ .reverse()
474
+ .find((item) => item.result.status === "ACTION_REQUIRED");
475
+ const action = actionable?.result.actionRequired;
476
+ if (!actionable || !action)
477
+ break;
478
+ let modelIds;
479
+ if (actionable.moduleRef === "model-runtime") {
480
+ const facts = await readModuleSharedFacts({ workspaceRoot: root }, "model-provider-api");
481
+ const inventory = facts?.models;
482
+ if (Array.isArray(inventory))
483
+ modelIds = inventory.flatMap((item) => {
484
+ if (typeof item === "string")
485
+ return [item];
486
+ if (isRecord(item) && typeof item.id === "string")
487
+ return [item.id];
488
+ return [];
489
+ });
490
+ }
491
+ const collected = await interaction.collect({
492
+ moduleRef: actionable.moduleRef,
493
+ action: action.action,
494
+ description: action.description,
495
+ ...(modelIds?.length ? { modelIds } : {}),
496
+ });
497
+ if (!collected)
498
+ break;
499
+ const previous = inputByModule.get(actionable.moduleRef) ?? {};
500
+ const input = { ...previous, ...collected };
501
+ inputByModule.set(actionable.moduleRef, input);
502
+ const targeted = await setupModulesThin(catalog, modules, root, { moduleRef: actionable.moduleRef, input }, runtime.onProgress, confirmSetupStep);
503
+ if (parsed.moduleRef !== undefined || !targeted.completed) {
504
+ result = targeted;
505
+ target = { moduleRef: actionable.moduleRef, input };
506
+ continue;
507
+ }
508
+ target = undefined;
509
+ result = await setupModulesThin(catalog, modules, root, undefined, runtime.onProgress, confirmSetupStep);
510
+ }
511
+ if (interaction && result.completed)
512
+ interaction.finish("配置已完成");
432
513
  reportProgress(runtime.onProgress, {
433
514
  command: "setup",
434
515
  phase: "setup",
435
516
  status: result.completed ? "SUCCEEDED" : "ACTION_REQUIRED",
436
517
  message: result.completed ? "模块配置已就绪" : "配置清单已生成",
437
518
  });
438
- return outcome("setup", batchStatus(result), root, result);
519
+ return outcome("setup", batchStatus(result), root, {
520
+ ...result,
521
+ ...(parsed.moduleRef === undefined
522
+ ? {}
523
+ : { targetModuleRef: parsed.moduleRef }),
524
+ });
439
525
  }
440
526
  async function handleStart(root, runtime) {
441
527
  const { catalog, modules } = await buildContext(root);
@@ -482,7 +568,7 @@ export async function runCli(argv, runtime = {}) {
482
568
  case "setup":
483
569
  return await handleSetup(root, parsed, runtime);
484
570
  case "docs":
485
- return await handleDocs(root, runtime);
571
+ return await handleDocs(root, parsed, runtime);
486
572
  case "start":
487
573
  return await handleStart(root, runtime);
488
574
  case "stop":
@@ -558,9 +644,17 @@ function renderStatus(data, theme) {
558
644
  NOT_APPLICABLE: "无独立进程",
559
645
  };
560
646
  const modules = data.modules.filter(isRecord);
647
+ const productOrder = new Map([
648
+ ["execution-browser-extension", 0],
649
+ ["dev-tunnel", 1],
650
+ ["model-provider-api", 2],
651
+ ]);
652
+ const orderedProblems = (entries) => [...entries].sort((a, b) => (productOrder.get(String(a.moduleRef)) ?? 100) -
653
+ (productOrder.get(String(b.moduleRef)) ?? 100));
561
654
  const failedModules = modules.filter((item) => item.setupStatus === "FAILED" || item.runtimeStatus === "FAILED");
562
- const actionModules = modules.filter((item) => item.setupStatus === "ACTION_REQUIRED" && item.runtimeStatus !== "FAILED");
563
- const blockedModules = modules.filter((item) => item.setupStatus === "BLOCKED" && item.runtimeStatus !== "FAILED");
655
+ const actionModules = orderedProblems(modules.filter((item) => item.setupStatus === "ACTION_REQUIRED" &&
656
+ item.runtimeStatus !== "FAILED"));
657
+ const blockedModules = orderedProblems(modules.filter((item) => item.setupStatus === "BLOCKED" && item.runtimeStatus !== "FAILED"));
564
658
  const running = modules.filter((item) => item.setupStatus === "READY" && item.runtimeStatus === "RUNNING");
565
659
  const healthy = modules.filter((item) => item.setupStatus === "READY" &&
566
660
  item.runtimeStatus !== "RUNNING" &&
@@ -574,8 +668,11 @@ function renderStatus(data, theme) {
574
668
  const moduleRef = String(raw.moduleRef);
575
669
  const setupStatus = String(raw.setupStatus);
576
670
  const runtimeStatus = String(raw.runtimeStatus);
671
+ const runtimeLabel = raw.externalAvailabilityClaim === "AVAILABLE"
672
+ ? "外部资源可用"
673
+ : (runtimeLabels[runtimeStatus] ?? "未知");
577
674
  lines.push(`${tone(icon)} ${theme.section(moduleRef)} ${theme.muted(String(raw.version))}`);
578
- lines.push(` ${tone(setupLabels[setupStatus] ?? "未知")} · ${runtimeLabels[runtimeStatus] ?? "未知"}`);
675
+ lines.push(` ${tone(setupLabels[setupStatus] ?? "未知")} · ${runtimeLabel}`);
579
676
  const issues = Array.isArray(raw.issues)
580
677
  ? raw.issues.filter(isRecord)
581
678
  : [];
@@ -584,7 +681,8 @@ function renderStatus(data, theme) {
584
681
  if (Array.isArray(issue.relatedModuleRefs) &&
585
682
  issue.relatedModuleRefs.length > 0)
586
683
  lines.push(` 依赖:${issue.relatedModuleRefs.map(String).join("、")}`);
587
- lines.push(` 下一步:${theme.command(String(issue.nextCommand))}`);
684
+ if (setupStatus !== "BLOCKED")
685
+ lines.push(` 下一步:${theme.command(String(issue.nextCommand))}`);
588
686
  }
589
687
  lines.push("");
590
688
  }
@@ -597,7 +695,9 @@ function renderStatus(data, theme) {
597
695
  return;
598
696
  lines.push(theme.section(title), "");
599
697
  for (const item of entries) {
600
- const runtime = runtimeLabels[String(item.runtimeStatus)] ?? "未知";
698
+ const runtime = item.externalAvailabilityClaim === "AVAILABLE"
699
+ ? "外部资源可用"
700
+ : (runtimeLabels[String(item.runtimeStatus)] ?? "未知");
601
701
  lines.push(`${theme.success(symbol)} ${String(item.moduleRef).padEnd(32)} ${theme.muted(String(item.version).padEnd(9))} ${runtime}`);
602
702
  }
603
703
  lines.push("");
@@ -608,7 +708,7 @@ function renderStatus(data, theme) {
608
708
  const action = modules.filter((item) => item.setupStatus === "ACTION_REQUIRED" && item.runtimeStatus !== "FAILED").length;
609
709
  const failed = failedModules.length;
610
710
  const blocked = modules.filter((item) => item.setupStatus === "BLOCKED" && item.runtimeStatus !== "FAILED").length;
611
- lines.push(theme.section("汇总"), `${theme.success(`${ready} 已就绪`)} · ${theme.warning(`${action} 需要操作`)} · ${theme.info(`${blocked} 等待依赖`)} · ${failed > 0 ? theme.failure(`${failed} 失败`) : `${failed} 失败`}`);
711
+ lines.push(theme.section("汇总"), `${theme.success(`${ready} 配置已完成`)} · ${theme.warning(`${action} 根阻塞`)} · ${theme.info(`${blocked} 下游等待`)} · ${failed > 0 ? theme.failure(`${failed} 失败`) : `${failed} 失败`} · ${running.length} 个真实进程运行中`, `PLATFORM_READY=${action === 0 && blocked === 0 && failed === 0 ? "YES" : "NO"}`);
612
712
  return lines.join("\n");
613
713
  }
614
714
  function renderTerminalMarkdown(source, theme) {
@@ -641,6 +741,18 @@ function renderTerminalMarkdown(source, theme) {
641
741
  function renderDocs(data, theme) {
642
742
  if (!isRecord(data) || !Array.isArray(data.modules))
643
743
  return "未发现模块文档。";
744
+ if (data.indexOnly === true) {
745
+ const modules = data.modules.filter(isRecord);
746
+ return [
747
+ theme.title("ProFlow 帮助"),
748
+ "",
749
+ "先运行 platform status 查看当前状态;需要某项详细说明时使用:",
750
+ ` ${theme.command("platform docs --module <模块名>")}`,
751
+ "",
752
+ theme.section(`可用文档(${modules.length})`),
753
+ ...modules.map((item) => ` ${String(item.moduleRef).padEnd(34)} ${theme.muted(String(item.version ?? ""))}`),
754
+ ].join("\n");
755
+ }
644
756
  const lines = [theme.title("ProFlow 模块文档")];
645
757
  for (const raw of data.modules)
646
758
  if (isRecord(raw))
@@ -651,40 +763,13 @@ function renderDocs(data, theme) {
651
763
  .map((item) => `- ${String(item.moduleRef)}:${String(item.reason)}`));
652
764
  return lines.join("\n");
653
765
  }
654
- const setupCommands = {
655
- "chatgpt-carrier": {
656
- ai: "pnpm exec -- proflow-chatgpt-carrier setup",
657
- inputs: "",
658
- },
659
- "dev-tunnel": {
660
- ai: "pnpm exec -- proflow-dev-tunnel setup",
661
- inputs: "无",
662
- },
663
- "model-provider-api": {
664
- ai: "pnpm exec -- proflow-model-provider-api setup",
665
- inputs: "无(等待 Deployment resolver 提供 endpoint)",
666
- },
667
- "model-runtime": {
668
- ai: "pnpm exec -- proflow-model-runtime setup",
669
- inputs: "无(仅等价合格候选歧义时选择)",
670
- },
671
- "execution-browser-extension": {
672
- ai: "pnpm exec -- proflow-execution-browser-extension setup",
673
- inputs: "无",
674
- },
675
- "agent-controller-dev": {
676
- ai: "pnpm exec -- proflow-agent-controller-dev setup",
677
- inputs: "无",
678
- },
679
- "agent-product": {
680
- ai: "pnpm exec -- proflow-agent-product setup",
681
- inputs: "无",
682
- },
683
- "agent-test-ops": {
684
- ai: "pnpm exec -- proflow-agent-test-ops setup",
685
- inputs: "无",
686
- },
766
+ const setupModuleLabels = {
767
+ "execution-browser-extension": "浏览器扩展",
768
+ "dev-tunnel": "远程连接",
769
+ "model-provider-api": "模型服务",
770
+ "model-runtime": "FAST / THINK 模型",
687
771
  };
772
+ const setupModuleLabel = (moduleRef) => setupModuleLabels[moduleRef] ?? moduleRef;
688
773
  function renderSetup(data, theme) {
689
774
  if (!isRecord(data) || !Array.isArray(data.results))
690
775
  return "没有需要执行的配置步骤。";
@@ -708,10 +793,14 @@ function renderSetup(data, theme) {
708
793
  blocked += 1;
709
794
  else
710
795
  needsAction += 1;
711
- lines.push(`${status === "FAILED" ? theme.failure("✕") : theme.warning("◆")} ${theme.section(moduleRef)}`);
796
+ lines.push(`${status === "FAILED" ? theme.failure("✕") : theme.warning("◆")} ${theme.section(setupModuleLabel(moduleRef))}`);
712
797
  const plan = moduleSetupPlanDataSchema.safeParse(raw.result.data);
713
798
  if (plan.success) {
799
+ const currentStep = plan.data.steps.find((step) => step.state === "BLOCKED") ??
800
+ plan.data.steps.find((step) => step.state === "TODO");
714
801
  for (const [index, step] of plan.data.steps.entries()) {
802
+ if (step !== currentStep && step.state !== "DONE")
803
+ continue;
715
804
  const marker = step.state === "DONE"
716
805
  ? theme.success("✓")
717
806
  : step.state === "BLOCKED"
@@ -723,13 +812,10 @@ function renderSetup(data, theme) {
723
812
  lines.push(` ${marker} ${String(index + 1).padStart(2, "0")} ${step.title}`);
724
813
  if (step.description)
725
814
  lines.push(` ${step.description}`);
726
- lines.push(` 运行:${theme.command(step.execution.interactive)}`);
727
- lines.push(` AI:${theme.command(step.execution.nonInteractive)}`);
728
815
  if (step.requiredInputs.length > 0)
729
816
  lines.push(` 需要:${step.requiredInputs.map((item) => item.description).join("、")}`);
730
817
  if (step.humanAction)
731
818
  lines.push(` 人工操作:${step.humanAction}`);
732
- lines.push(` 验证:${theme.command(step.verify)}`);
733
819
  lines.push(` 完成:${step.successCondition}`);
734
820
  if (step.blockedReason)
735
821
  lines.push(` 原因:${theme.failure(step.blockedReason)}`);
@@ -740,15 +826,7 @@ function renderSetup(data, theme) {
740
826
  lines.push(` 1. ${status === "FAILED" ? "等待上游服务信息" : "完成模块配置"}`);
741
827
  const error = raw.result.error;
742
828
  if (status !== "FAILED") {
743
- const commands = setupCommands[moduleRef] ?? {
744
- ai: `proflow-${moduleRef} setup`,
745
- inputs: "按命令提示提供",
746
- };
747
- lines.push(` 人工执行:pnpm exec -- proflow-${moduleRef} setup`);
748
- lines.push(` AI 执行:${commands.ai}`);
749
- lines.push(` 需要输入:${commands.inputs}`);
750
- lines.push(` 验证:proflow-${moduleRef} verify`);
751
- lines.push(" 完成条件:配置状态变为“已就绪”");
829
+ lines.push(" 请在当前 Platform 向导中完成这一项。完成后会自动验证。");
752
830
  }
753
831
  else if (isRecord(error)) {
754
832
  const code = error.code === undefined ? "FAILED" : String(error.code);
@@ -756,8 +834,7 @@ function renderSetup(data, theme) {
756
834
  ? "Module setup failed."
757
835
  : String(error.message);
758
836
  lines.push(` 原因:${code} — ${message}`);
759
- lines.push(` 执行:platform setup --module ${moduleRef}`);
760
- lines.push(" 完成条件:配置状态变为“已就绪”");
837
+ lines.push(" 修复当前问题后重新运行 platform setup。");
761
838
  }
762
839
  lines.push("");
763
840
  }
@@ -771,8 +848,11 @@ function renderSetup(data, theme) {
771
848
  blocked += 1;
772
849
  lines.push(`${theme.info("◇")} ${theme.section(moduleRef)}`, ` 原因:${typeof item.reason === "string" ? item.reason : "等待上游 Module 就绪"}`, ` 下一步:${theme.command(typeof item.nextCommand === "string" ? item.nextCommand : `platform setup --module ${moduleRef}`)}`, "");
773
850
  }
851
+ const targetModuleRef = typeof data.targetModuleRef === "string" ? data.targetModuleRef : undefined;
774
852
  if (needsAction === 0 && blocked === 0)
775
- lines.push("全部模块均已就绪。");
853
+ lines.push(targetModuleRef
854
+ ? `${setupModuleLabel(targetModuleRef)}配置已就绪。`
855
+ : "全部模块均已就绪。");
776
856
  lines.push(theme.section("汇总"), `汇总:${theme.success(`${ready} 个已就绪`)},${theme.warning(`${needsAction} 个需要操作`)},${blocked > 0 ? theme.failure(`${blocked} 个系统阻塞`) : `${blocked} 个系统阻塞`}`);
777
857
  return lines.join("\n").trimEnd();
778
858
  }
@@ -795,8 +875,8 @@ function renderHelp(theme, command) {
795
875
  description: "自动配置并列出全部剩余步骤",
796
876
  },
797
877
  docs: {
798
- usage: "platform docs [--workspace <路径>]",
799
- description: "阅读全部模块能力文档",
878
+ usage: "platform docs [--workspace <路径>] [--module <模块名>]",
879
+ description: "查看文档索引,或阅读一个模块的详细文档",
800
880
  },
801
881
  start: {
802
882
  usage: "platform start [--workspace <路径>]",
@@ -839,17 +919,17 @@ function renderHelp(theme, command) {
839
919
  theme.section("推荐流程"),
840
920
  ` ${theme.command("install → status → docs → setup → start → status → stop")}`,
841
921
  "",
842
- theme.section("人工配置示例"),
843
- ` 人工:${theme.command("pnpm exec -- proflow-chatgpt-carrier setup")}`,
844
- ` AI: ${theme.command("pnpm exec -- proflow-chatgpt-carrier setup")}`,
922
+ theme.section("配置入口"),
923
+ ` ${theme.command("platform setup")}`,
845
924
  "",
846
925
  theme.section("状态图例"),
847
926
  ` ${theme.success("已就绪")} 配置与验证完成`,
848
927
  ` ${theme.warning("需要操作")} 需要执行 setup 步骤`,
849
928
  ` ${theme.info("等待依赖")} 等待上游 Module 或外部服务`,
850
929
  ` ${theme.failure("失败")} 已确认配置或运行故障`,
851
- " 运行中 服务进程正在运行",
852
- " 无独立进程 该模块无需启动",
930
+ " 运行中 服务进程正在运行",
931
+ " 外部资源可用 Chrome 等外部资源已通过真实探测",
932
+ " 无独立进程 该模块无需启动",
853
933
  "",
854
934
  theme.section("遇到问题"),
855
935
  ` 先运行 ${theme.command("platform status")};配置问题运行 ${theme.command("platform setup")}。`,
@@ -866,24 +946,35 @@ export function renderHumanResult(result, options = {}) {
866
946
  ? result.data.blockers.filter(isRecord)
867
947
  : [];
868
948
  if (blockers.length > 0) {
949
+ const productOrder = new Map([
950
+ ["execution-browser-extension", 0],
951
+ ["dev-tunnel", 1],
952
+ ["model-provider-api", 2],
953
+ ]);
869
954
  const groups = [
870
955
  { status: "FAILED", title: "失败", icon: "✕", tone: theme.failure },
871
956
  {
872
957
  status: "ACTION_REQUIRED",
873
- title: "需要操作",
958
+ title: "当前根阻塞",
874
959
  icon: "◆",
875
960
  tone: theme.warning,
876
961
  },
877
- { status: "BLOCKED", title: "等待依赖", icon: "◇", tone: theme.info },
962
+ { status: "BLOCKED", title: "下游等待", icon: "◇", tone: theme.info },
878
963
  ];
879
964
  const lines = [theme.failure("平台未启动:存在未就绪模块"), ""];
880
965
  for (const group of groups) {
881
- const entries = blockers.filter((item) => item.setupStatus === group.status);
966
+ const entries = blockers
967
+ .filter((item) => item.setupStatus === group.status)
968
+ .sort((a, b) => (productOrder.get(String(a.moduleRef)) ?? 100) -
969
+ (productOrder.get(String(b.moduleRef)) ?? 100));
882
970
  if (entries.length === 0)
883
971
  continue;
884
972
  lines.push(theme.section(group.title));
885
- for (const item of entries)
886
- lines.push(`${group.tone(group.icon)} ${theme.section(String(item.moduleRef))}`, ` 原因:${typeof item.reason === "string" ? item.reason : "模块尚未就绪"}`, ` 下一步:${theme.command(typeof item.nextCommand === "string" ? item.nextCommand : `platform setup --module ${String(item.moduleRef)}`)}`);
973
+ for (const item of entries) {
974
+ lines.push(`${group.tone(group.icon)} ${theme.section(String(item.moduleRef))}`, ` 原因:${typeof item.reason === "string" ? item.reason : "模块尚未就绪"}`);
975
+ if (group.status !== "BLOCKED")
976
+ lines.push(` 下一步:${theme.command(typeof item.nextCommand === "string" ? item.nextCommand : `platform setup --module ${String(item.moduleRef)}`)}`);
977
+ }
887
978
  lines.push("");
888
979
  }
889
980
  const readyCount = Array.isArray(result.data.results)
@@ -891,7 +982,7 @@ export function renderHumanResult(result, options = {}) {
891
982
  isRecord(item.result) &&
892
983
  item.result.status === "SUCCEEDED").length - blockers.length
893
984
  : 0;
894
- lines.push(theme.section("检查汇总"), `${Math.max(0, readyCount)} 已就绪 · ${blockers.length} 未就绪 · 0 个模块已启动`, `处理方式:${theme.command(`platform setup${result.workspaceRoot ? ` --workspace "${result.workspaceRoot}"` : ""}`)}`);
985
+ lines.push(theme.section("检查汇总"), `${Math.max(0, readyCount)} 配置已完成 · ${blockers.filter((item) => item.setupStatus === "ACTION_REQUIRED").length} 根阻塞 · ${blockers.filter((item) => item.setupStatus === "BLOCKED").length} 下游等待 · 0 个真实进程启动`, "PLATFORM_READY=NO", `处理方式:${theme.command(`platform setup${result.workspaceRoot ? ` --workspace "${result.workspaceRoot}"` : ""}`)}`);
895
986
  return lines.join("\n");
896
987
  }
897
988
  }
@@ -950,7 +1041,12 @@ export function renderHumanResult(result, options = {}) {
950
1041
  if (import.meta.main) {
951
1042
  const argv = process.argv.slice(2);
952
1043
  const reporter = createTerminalProgressReporter();
953
- const result = await runCli(argv, { onProgress: reporter });
1044
+ const result = await runCli(argv, {
1045
+ onProgress: reporter,
1046
+ ...(process.stdin.isTTY && process.stdout.isTTY
1047
+ ? { setupInteraction: createClackSetupInteraction() }
1048
+ : {}),
1049
+ });
954
1050
  reporter.close();
955
1051
  const color = process.stdout.isTTY === true &&
956
1052
  process.env.NO_COLOR === undefined &&
@@ -1,2 +1,2 @@
1
- export declare const FROZEN_DEPLOYMENT_INSTALL_ORDER: readonly ["chrome-runtime", "execution-browser-extension", "agent-controller-dev", "agent-product", "agent-test-ops", "agent-gateway", "dev-tunnel", "agent-runtime", "platform-host", "execution-runtime", "execution-local", "model-provider-api", "model-runtime", "task-orchestration", "task-store-sqlite", "task-migration-runner", "execution-contracts", "model-contracts", "module-contract", "module-skill", "module-template", "deployment-conformance", "platform-cli", "chatgpt-carrier"];
1
+ export declare const FROZEN_DEPLOYMENT_INSTALL_ORDER: readonly ["chrome-runtime", "execution-browser-extension", "agent-controller-dev", "agent-product", "agent-test-ops", "agent-gateway", "dev-tunnel", "agent-runtime", "platform-host", "execution-runtime", "execution-local", "model-provider-api", "model-runtime", "task-orchestration", "task-store-sqlite", "task-migration-runner", "execution-contracts", "model-contracts", "module-contract", "module-skill", "module-template", "deployment-conformance", "platform-cli"];
2
2
  export declare function orderModuleRefsForInstall(moduleRefs: readonly string[]): string[];
@@ -22,7 +22,6 @@ export const FROZEN_DEPLOYMENT_INSTALL_ORDER = [
22
22
  "module-template",
23
23
  "deployment-conformance",
24
24
  "platform-cli",
25
- "chatgpt-carrier",
26
25
  ];
27
26
  const installRank = new Map(FROZEN_DEPLOYMENT_INSTALL_ORDER.map((moduleRef, index) => [moduleRef, index]));
28
27
  export function orderModuleRefsForInstall(moduleRefs) {
@@ -71,7 +71,7 @@ export async function readWorkspacePackageManagerSelection(workspaceRoot) {
71
71
  return { name, source: "declared", declared };
72
72
  }
73
73
  if (lockManagers.size > 1) {
74
- throw new PlatformError("PACKAGE_MANAGER_CONFLICT", `multiple package-manager lockfiles are present: ${[...lockManagers].sort().join(", ")}`);
74
+ throw new PlatformError("PACKAGE_MANAGER_CONFLICT", `检测到多个包管理器的 lockfile(${[...lockManagers].sort().join(", ")})。Platform 不会自动删除或替你选择;请先保留现场并明确工作区应使用的唯一包管理器。`);
75
75
  }
76
76
  const [fromLockfile] = lockManagers;
77
77
  if (fromLockfile !== undefined) {
@@ -6,5 +6,6 @@ export interface ModuleDispatchResult {
6
6
  command: ModuleManagementCommand;
7
7
  result: ModuleOperationResult;
8
8
  observedEffects: string[];
9
+ externalAvailabilityClaim?: "AVAILABLE" | "UNAVAILABLE";
9
10
  }
10
11
  export declare function dispatchModuleCommand(catalog: ModuleCatalog, module: ResolvedModule, command: ModuleManagementCommand, context: ModuleCommandContext): Promise<ModuleDispatchResult>;
@@ -24,6 +24,10 @@ function normalizeInvocation(raw) {
24
24
  observedEffects: Array.isArray(raw.observedEffects)
25
25
  ? raw.observedEffects.filter((item) => typeof item === "string")
26
26
  : [],
27
+ ...(raw.externalAvailabilityClaim === "AVAILABLE" ||
28
+ raw.externalAvailabilityClaim === "UNAVAILABLE"
29
+ ? { externalAvailabilityClaim: raw.externalAvailabilityClaim }
30
+ : {}),
27
31
  };
28
32
  }
29
33
  export async function dispatchModuleCommand(catalog, module, command, context) {
@@ -32,7 +36,7 @@ export async function dispatchModuleCommand(catalog, module, command, context) {
32
36
  const invoke = adapter[command];
33
37
  if (!isCommandFn(invoke))
34
38
  throw new PlatformError("COMMAND_FAILED", `module ${module.moduleRef} does not implement standard command "${command}"`);
35
- const { result, observedEffects } = normalizeInvocation(await invoke(context));
39
+ const { result, observedEffects, externalAvailabilityClaim } = normalizeInvocation(await invoke(context));
36
40
  const parsed = moduleOperationResultSchema.safeParse(result);
37
41
  if (!parsed.success)
38
42
  throw new PlatformError("COMMAND_FAILED", `module ${module.moduleRef} "${command}" returned an invalid result: ${parsed.error.message}`);
@@ -41,5 +45,8 @@ export async function dispatchModuleCommand(catalog, module, command, context) {
41
45
  command,
42
46
  result: parsed.data,
43
47
  observedEffects,
48
+ ...(externalAvailabilityClaim === undefined
49
+ ? {}
50
+ : { externalAvailabilityClaim }),
44
51
  };
45
52
  }
@@ -30,5 +30,5 @@ export declare function stopModulesThin(catalog: ModuleCatalog, modules: readonl
30
30
  export declare function setupModulesThin(catalog: ModuleCatalog, modules: readonly ResolvedModule[], workspaceRoot: string, target?: {
31
31
  moduleRef: string;
32
32
  input?: unknown;
33
- }, reporter?: PlatformProgressReporter): Promise<ModuleBatchResult>;
33
+ }, reporter?: PlatformProgressReporter, beforeSetup?: (moduleRef: string) => Promise<void>): Promise<ModuleBatchResult>;
34
34
  export declare function startModulesThin(catalog: ModuleCatalog, modules: readonly ResolvedModule[], workspaceRoot: string, reporter?: PlatformProgressReporter): Promise<ModuleBatchResult>;
@@ -172,38 +172,51 @@ export async function stopModulesThin(catalog, modules, workspaceRoot, reporter)
172
172
  }
173
173
  return { phase: "stop", results, completed: true, skipped };
174
174
  }
175
- export async function setupModulesThin(catalog, modules, workspaceRoot, target, reporter) {
175
+ export async function setupModulesThin(catalog, modules, workspaceRoot, target, reporter, beforeSetup) {
176
176
  const results = [];
177
177
  const skipped = [];
178
178
  const blockers = [];
179
179
  const graph = buildDependencyGraph(modules);
180
180
  const byRef = new Map(modules.map((module) => [module.moduleRef, module]));
181
+ if (target !== undefined && !byRef.has(target.moduleRef))
182
+ throw new PlatformError("INVALID_REQUEST", `setup target module ${target.moduleRef} was not discovered`);
183
+ const setupPriority = [
184
+ "execution-browser-extension",
185
+ "dev-tunnel",
186
+ "model-provider-api",
187
+ "model-runtime",
188
+ ];
181
189
  const dependenciesByRef = new Map();
182
190
  for (const edge of graph.edges) {
183
191
  const dependencies = dependenciesByRef.get(edge.from) ?? [];
184
192
  dependencies.push(edge.to);
185
193
  dependenciesByRef.set(edge.from, dependencies);
186
194
  }
187
- if (target !== undefined && !byRef.has(target.moduleRef))
188
- throw new PlatformError("INVALID_REQUEST", `setup target module ${target.moduleRef} was not discovered`);
189
- const visitedRefs = new Set();
190
- const includeWithDependencies = (moduleRef) => {
191
- if (visitedRefs.has(moduleRef))
195
+ const userOrder = [];
196
+ const included = new Set();
197
+ const appendWithDependencies = (moduleRef) => {
198
+ if (included.has(moduleRef) || !byRef.has(moduleRef))
192
199
  return;
193
- visitedRefs.add(moduleRef);
194
- for (const dependency of dependenciesByRef.get(moduleRef) ?? [])
195
- includeWithDependencies(dependency);
200
+ for (const dependency of graph.order)
201
+ if (dependenciesByRef.get(moduleRef)?.includes(dependency))
202
+ appendWithDependencies(dependency);
203
+ included.add(moduleRef);
204
+ userOrder.push(moduleRef);
196
205
  };
197
- if (target !== undefined)
198
- includeWithDependencies(target.moduleRef);
199
- else
206
+ if (target === undefined) {
207
+ for (const moduleRef of setupPriority)
208
+ appendWithDependencies(moduleRef);
200
209
  for (const moduleRef of graph.order)
201
- visitedRefs.add(moduleRef);
202
- const modulesInOrder = graph.order
203
- .filter((moduleRef) => visitedRefs.has(moduleRef))
210
+ appendWithDependencies(moduleRef);
211
+ }
212
+ else {
213
+ appendWithDependencies(target.moduleRef);
214
+ }
215
+ const selectedRefs = new Set(target === undefined ? userOrder : [target.moduleRef]);
216
+ const modulesInOrder = userOrder
217
+ .filter((moduleRef) => selectedRefs.has(moduleRef))
204
218
  .map((moduleRef) => byRef.get(moduleRef))
205
219
  .filter((module) => module !== undefined);
206
- const setupStatusByRef = new Map();
207
220
  let completed = true;
208
221
  for (const [index, module] of modulesInOrder.entries()) {
209
222
  reportProgress(reporter, {
@@ -218,16 +231,10 @@ export async function setupModulesThin(catalog, modules, workspaceRoot, target,
218
231
  const status = await dispatchModuleCommand(catalog, module, "status", context(workspaceRoot));
219
232
  if (!succeeded(status.result)) {
220
233
  results.push(status);
221
- setupStatusByRef.set(module.moduleRef, "FAILED");
222
234
  completed = false;
223
- if (target?.moduleRef === module.moduleRef)
224
- break;
225
- continue;
235
+ break;
226
236
  }
227
237
  const observed = moduleStatusObservationSchema.parse(status.result.data);
228
- setupStatusByRef.set(module.moduleRef, observed.setupStatus);
229
- if (target !== undefined && module.moduleRef !== target.moduleRef)
230
- continue;
231
238
  if (observed.setupStatus === "READY" && target === undefined) {
232
239
  skipped.push({ moduleRef: module.moduleRef, reason: "READY" });
233
240
  reportProgress(reporter, {
@@ -241,29 +248,7 @@ export async function setupModulesThin(catalog, modules, workspaceRoot, target,
241
248
  });
242
249
  continue;
243
250
  }
244
- const unavailableDependencies = (dependenciesByRef.get(module.moduleRef) ?? []).filter((moduleRef) => setupStatusByRef.get(moduleRef) !== "READY");
245
- if (unavailableDependencies.length > 0) {
246
- const first = unavailableDependencies[0];
247
- blockers.push({
248
- moduleRef: module.moduleRef,
249
- setupStatus: "BLOCKED",
250
- reason: `等待依赖模块就绪:${unavailableDependencies.join(", ")}`,
251
- nextCommand: `platform setup --module ${first}`,
252
- });
253
- completed = false;
254
- reportProgress(reporter, {
255
- command: "setup",
256
- phase: "setup",
257
- current: index + 1,
258
- total: modulesInOrder.length,
259
- moduleRef: module.moduleRef,
260
- status: "SKIPPED",
261
- message: `${module.moduleRef} 等待 ${unavailableDependencies.join(", ")}`,
262
- });
263
- if (target !== undefined)
264
- break;
265
- continue;
266
- }
251
+ await beforeSetup?.(module.moduleRef);
267
252
  const setup = await dispatchModuleCommand(catalog, module, "setup", context(workspaceRoot, target?.input));
268
253
  results.push(setup);
269
254
  reportProgress(reporter, {
@@ -283,14 +268,25 @@ export async function setupModulesThin(catalog, modules, workspaceRoot, target,
283
268
  completed = false;
284
269
  }
285
270
  const reconciled = await dispatchModuleCommand(catalog, module, "status", context(workspaceRoot));
286
- if (succeeded(reconciled.result))
287
- setupStatusByRef.set(module.moduleRef, moduleStatusObservationSchema.parse(reconciled.result.data).setupStatus);
288
- else {
271
+ if (!succeeded(reconciled.result)) {
289
272
  results.push(reconciled);
290
- setupStatusByRef.set(module.moduleRef, "FAILED");
291
273
  completed = false;
292
274
  }
293
- if (target !== undefined && !succeeded(setup.result))
275
+ else {
276
+ const reconciledStatus = moduleStatusObservationSchema.parse(reconciled.result.data);
277
+ if (reconciledStatus.setupStatus !== "READY") {
278
+ const issue = reconciledStatus.issues?.find((item) => item.scope === "SETUP");
279
+ blockers.push({
280
+ moduleRef: module.moduleRef,
281
+ setupStatus: reconciledStatus.setupStatus,
282
+ ...(issue === undefined
283
+ ? {}
284
+ : { reason: issue.message, nextCommand: issue.nextCommand }),
285
+ });
286
+ completed = false;
287
+ }
288
+ }
289
+ if (!succeeded(setup.result) || blockers.length > 0)
294
290
  break;
295
291
  }
296
292
  return {
@@ -1,2 +1,2 @@
1
1
  export type { NpmCommandResult, NpmCommandRunner, RegistryDiscoveryResult, RegistryModuleCandidate, RegistryRejectedPackage, } from "./npm-registry.ts";
2
- export { discoverRegistryModules, PRO_FLOW_PACKAGE_PREFIX, PRO_FLOW_SCOPE, resolveScopeRegistry, systemNpmRunner, } from "./npm-registry.ts";
2
+ export { discoverRegistryModules, PRO_FLOW_PACKAGE_PREFIX, PRO_FLOW_SCOPE, RETIRED_PRO_FLOW_PACKAGES, resolveScopeRegistry, systemNpmRunner, } from "./npm-registry.ts";
@@ -1 +1 @@
1
- export { discoverRegistryModules, PRO_FLOW_PACKAGE_PREFIX, PRO_FLOW_SCOPE, resolveScopeRegistry, systemNpmRunner, } from "./npm-registry.js";
1
+ export { discoverRegistryModules, PRO_FLOW_PACKAGE_PREFIX, PRO_FLOW_SCOPE, RETIRED_PRO_FLOW_PACKAGES, resolveScopeRegistry, systemNpmRunner, } from "./npm-registry.js";
@@ -1,5 +1,6 @@
1
1
  export declare const PRO_FLOW_SCOPE = "@tomflow";
2
2
  export declare const PRO_FLOW_PACKAGE_PREFIX = "@tomflow/proflow-";
3
+ export declare const RETIRED_PRO_FLOW_PACKAGES: Set<string>;
3
4
  export interface NpmCommandResult {
4
5
  stdout: string;
5
6
  stderr: string;
@@ -26,6 +27,7 @@ export interface RegistryDiscoveryResult {
26
27
  registry: string;
27
28
  candidates: RegistryModuleCandidate[];
28
29
  rejected: RegistryRejectedPackage[];
30
+ retired: string[];
29
31
  }
30
32
  export declare function systemNpmRunner(): NpmCommandRunner;
31
33
  export declare function resolveScopeRegistry(workspaceRoot: string, runner?: NpmCommandRunner): Promise<string>;
@@ -6,6 +6,9 @@ import { versionSatisfies } from "../modules.js";
6
6
  const execFileAsync = promisify(execFile);
7
7
  export const PRO_FLOW_SCOPE = "@tomflow";
8
8
  export const PRO_FLOW_PACKAGE_PREFIX = "@tomflow/proflow-";
9
+ export const RETIRED_PRO_FLOW_PACKAGES = new Set([
10
+ "@tomflow/proflow-chatgpt-carrier",
11
+ ]);
9
12
  export function systemNpmRunner() {
10
13
  return {
11
14
  async run(args, cwd, timeoutMs) {
@@ -42,15 +45,17 @@ export async function discoverRegistryModules(options) {
42
45
  const names = options.packageName === undefined
43
46
  ? await searchPackageNames(options.workspaceRoot, registry, runner)
44
47
  : [validateRequestedPackageName(options.packageName)];
45
- options.onSearchComplete?.(names.length);
48
+ const retired = names.filter((name) => RETIRED_PRO_FLOW_PACKAGES.has(name));
49
+ const activeNames = names.filter((name) => !RETIRED_PRO_FLOW_PACKAGES.has(name));
50
+ options.onSearchComplete?.(activeNames.length);
46
51
  const candidates = [];
47
52
  const rejected = [];
48
53
  let nextIndex = 0;
49
54
  let completed = 0;
50
55
  const worker = async () => {
51
- while (nextIndex < names.length) {
56
+ while (nextIndex < activeNames.length) {
52
57
  const index = nextIndex++;
53
- const packageName = names[index];
58
+ const packageName = activeNames[index];
54
59
  if (packageName === undefined)
55
60
  return;
56
61
  const manifest = await viewManifest(options.workspaceRoot, registry, packageName, runner);
@@ -62,15 +67,15 @@ export async function discoverRegistryModules(options) {
62
67
  completed += 1;
63
68
  options.onPackageChecked?.({
64
69
  current: completed,
65
- total: names.length,
70
+ total: activeNames.length,
66
71
  packageName,
67
72
  });
68
73
  }
69
74
  };
70
- await Promise.all(Array.from({ length: Math.min(4, names.length) }, () => worker()));
75
+ await Promise.all(Array.from({ length: Math.min(4, activeNames.length) }, () => worker()));
71
76
  candidates.sort((left, right) => left.packageName.localeCompare(right.packageName));
72
77
  rejected.sort((left, right) => left.packageName.localeCompare(right.packageName));
73
- return { registry, candidates, rejected };
78
+ return { registry, candidates, rejected, retired };
74
79
  }
75
80
  async function searchPackageNames(workspaceRoot, registry, runner) {
76
81
  const result = await runner.run([
@@ -0,0 +1,14 @@
1
+ export type SetupActionRequest = {
2
+ moduleRef: string;
3
+ action: string;
4
+ description: string;
5
+ modelIds?: readonly string[];
6
+ };
7
+ export interface SetupInteraction {
8
+ begin(): void;
9
+ finish(message: string): void;
10
+ confirmStep?(moduleRef: string): Promise<boolean>;
11
+ collect(request: SetupActionRequest): Promise<Record<string, unknown> | undefined>;
12
+ }
13
+ export declare function validateProviderBaseUrl(value: string): string | undefined;
14
+ export declare function createClackSetupInteraction(): SetupInteraction;
@@ -0,0 +1,126 @@
1
+ import { autocomplete, cancel, confirm, intro, isCancel, outro, password, text, } from "@clack/prompts";
2
+ const stepPrompts = {
3
+ "execution-browser-extension": "下一步将打开 Chrome,并引导你加载浏览器扩展。按 Enter 继续。",
4
+ "dev-tunnel": "下一步将检查远程连接;如需登录,会打开 GitHub 授权。按 Enter 继续。",
5
+ "model-provider-api": "下一步配置 OpenAI-compatible 模型服务地址。按 Enter 继续。",
6
+ "model-runtime": "下一步从真实模型列表选择 FAST 与 THINK。按 Enter 继续。",
7
+ };
8
+ function cancelled(value) {
9
+ if (!isCancel(value))
10
+ return false;
11
+ cancel("已取消配置,没有继续执行后续步骤。");
12
+ return true;
13
+ }
14
+ function privateIpv4(hostname) {
15
+ const parts = hostname.split(".").map(Number);
16
+ if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part)))
17
+ return false;
18
+ const [first = -1, second = -1] = parts;
19
+ return (first === 10 ||
20
+ first === 127 ||
21
+ (first === 192 && second === 168) ||
22
+ (first === 172 && second >= 16 && second <= 31));
23
+ }
24
+ export function validateProviderBaseUrl(value) {
25
+ let url;
26
+ try {
27
+ url = new URL(value.trim());
28
+ }
29
+ catch {
30
+ return "请输入包含 http:// 或 https:// 的完整 API Base URL。";
31
+ }
32
+ if (url.protocol !== "http:" && url.protocol !== "https:")
33
+ return "只支持 HTTP(S) OpenAI-compatible 服务。";
34
+ if (/\/chat\/completions\/?$/i.test(url.pathname))
35
+ return "请填写服务 Base URL,不要包含 /chat/completions。";
36
+ const local = url.hostname === "localhost" ||
37
+ url.hostname.endsWith(".local") ||
38
+ privateIpv4(url.hostname);
39
+ if (!local && url.protocol !== "https:")
40
+ return "公网模型服务必须使用 https://。";
41
+ return undefined;
42
+ }
43
+ async function providerEndpoint() {
44
+ const value = await text({
45
+ message: "OpenAI-compatible 服务 Base URL",
46
+ placeholder: "例如 https://api.deepseek.com 或 http://192.168.0.108:8080/v1",
47
+ validate: (candidate) => candidate === undefined || candidate.trim().length === 0
48
+ ? "API Base URL 不能为空。"
49
+ : validateProviderBaseUrl(candidate),
50
+ });
51
+ if (cancelled(value))
52
+ return undefined;
53
+ const providerBaseUrl = String(value).trim().replace(/\/$/, "");
54
+ const approved = await confirm({
55
+ message: `使用 ${providerBaseUrl} 连接整个模型服务?`,
56
+ initialValue: true,
57
+ });
58
+ if (cancelled(approved) || approved !== true)
59
+ return undefined;
60
+ return { providerBaseUrl };
61
+ }
62
+ async function providerCredential() {
63
+ const value = await password({
64
+ message: "模型服务 API Key(输入不会显示)",
65
+ mask: "•",
66
+ validate: (candidate) => candidate === undefined || candidate.trim().length === 0
67
+ ? "API Key 不能为空。"
68
+ : undefined,
69
+ });
70
+ if (cancelled(value))
71
+ return undefined;
72
+ return { providerCredential: String(value) };
73
+ }
74
+ async function modelRoles(modelIds) {
75
+ const options = modelIds.map((id) => ({ value: id, label: id }));
76
+ const fastModel = await autocomplete({
77
+ message: "选择 FAST 模型(快速、低延迟)",
78
+ placeholder: "输入关键字搜索模型",
79
+ options,
80
+ });
81
+ if (cancelled(fastModel))
82
+ return undefined;
83
+ const reasonModel = await autocomplete({
84
+ message: "选择 THINK 模型(复杂分析与推理)",
85
+ placeholder: "输入关键字搜索模型",
86
+ options,
87
+ });
88
+ if (cancelled(reasonModel))
89
+ return undefined;
90
+ const approved = await confirm({
91
+ message: `确认 FAST=${String(fastModel)},THINK=${String(reasonModel)}?`,
92
+ initialValue: true,
93
+ });
94
+ if (cancelled(approved) || approved !== true)
95
+ return undefined;
96
+ return { fastModel: String(fastModel), reasonModel: String(reasonModel) };
97
+ }
98
+ export function createClackSetupInteraction() {
99
+ return {
100
+ begin() {
101
+ intro("ProFlow 配置向导");
102
+ },
103
+ finish(message) {
104
+ outro(message);
105
+ },
106
+ async confirmStep(moduleRef) {
107
+ const message = stepPrompts[moduleRef];
108
+ if (!message)
109
+ return true;
110
+ const approved = await confirm({ message, initialValue: true });
111
+ return !cancelled(approved) && approved === true;
112
+ },
113
+ async collect(request) {
114
+ if (request.action === "provide-provider-endpoint") {
115
+ return providerEndpoint();
116
+ }
117
+ if (request.action === "provide-provider-credential") {
118
+ return providerCredential();
119
+ }
120
+ if (request.action.startsWith("select-") && request.modelIds?.length) {
121
+ return modelRoles(request.modelIds);
122
+ }
123
+ return undefined;
124
+ },
125
+ };
126
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomflow/proflow-platform-cli",
3
- "version": "0.1.37",
3
+ "version": "0.1.39",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -24,11 +24,12 @@
24
24
  "SETUP.md"
25
25
  ],
26
26
  "dependencies": {
27
+ "@clack/prompts": "1.7.0",
27
28
  "@tomflow/proflow-module-contract": "^0.1.13"
28
29
  },
29
30
  "devDependencies": {
30
- "@tomflow/proflow-module-template": "^0.1.10",
31
- "@tomflow/proflow-deployment-conformance": "^0.1.11"
31
+ "@tomflow/proflow-deployment-conformance": "^0.1.13",
32
+ "@tomflow/proflow-module-template": "^0.1.10"
32
33
  },
33
34
  "description": "Thin Platform CLI for Module discovery, documentation, package synchronization and lifecycle orchestration.",
34
35
  "keywords": [
@@ -3,7 +3,7 @@
3
3
  "contractVersion": "1.0.0",
4
4
  "moduleRef": "platform-cli",
5
5
  "packageName": "@tomflow/proflow-platform-cli",
6
- "moduleVersion": "0.1.37",
6
+ "moduleVersion": "0.1.39",
7
7
  "kind": "cli",
8
8
  "templateVersion": "1.0.0",
9
9
  "platformCompatibility": ">=1.0.0 <2.0.0",