@tomflow/proflow-platform-cli 0.1.39 → 0.1.41

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.41
4
+
5
+ ### Patch Changes
6
+
7
+ - Harden Deployment Closeout: fail closed on partial installs and package-manager conflicts, normalize Git workspace isolation, make Dev Tunnel status fast and resumable without fabricating login failures, and align Browser Extension setup/conformance behavior.
8
+
9
+ ## 0.1.40
10
+
11
+ ### Patch Changes
12
+
13
+ - Ignore governed non-Module tool packages during Registry module discovery so Fresh install remains fixed at 23 active modules.
14
+
3
15
  ## 0.1.39
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.39";
9
+ readonly moduleVersion: "0.1.40";
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.39";
19
+ readonly moduleVersion: "0.1.40";
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.39";
29
+ moduleVersion: "0.1.40";
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.39";
43
+ readonly moduleVersion: "0.1.40";
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.39";
53
+ moduleVersion: "0.1.40";
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.39";
66
+ readonly moduleVersion: "0.1.40";
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.39";
76
+ readonly moduleVersion: "0.1.40";
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.39";
6
+ readonly moduleVersion: "0.1.40";
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.39",
6
+ moduleVersion: "0.1.40",
7
7
  kind: "cli",
8
8
  templateVersion: "1.0.0",
9
9
  platformCompatibility: ">=1.0.0 <2.0.0",
package/dist/src/cli.js CHANGED
@@ -1,14 +1,16 @@
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, readModuleSharedFacts, } from "@tomflow/proflow-module-contract";
4
+ import { moduleDocsDataSchema, moduleSetupPlanDataSchema, moduleStatusObservationSchema, } from "@tomflow/proflow-module-contract";
5
5
  import { descriptor as platformCliDescriptor } from "../deployment/descriptor.js";
6
+ import { FROZEN_DEPLOYMENT_INSTALL_ORDER } from "./deployment-order.js";
6
7
  import { AutoModuleCatalog, discoverModules } from "./discovery/discover.js";
7
8
  import { InstalledModuleCatalog } from "./discovery/installed.js";
8
9
  import { PlatformError } from "./errors.js";
9
- import { observeWorkspaceInstalledVersion, removeWorkspacePackages, syncWorkspacePackages, } from "./install/package-manager.js";
10
+ import { observeWorkspaceInstalledVersion, preflightWorkspacePackageManager, removeWorkspacePackages, syncWorkspacePackages, } from "./install/package-manager.js";
10
11
  import { cleanOwnedPnpmPolicy, observeMinimumReleaseAgeExclude, recordPnpmPolicyOwnership, } from "./install/pnpm-policy.js";
11
12
  import { installModulesThin, observeDocs, observeStatuses, setupModulesThin, startModulesThin, stopModulesThin, uninstallModulesThin, } from "./lifecycle/index.js";
13
+ import { ensureWorkspaceStateIsGitIgnored } from "./persistence/git-isolation.js";
12
14
  import { ensureWorkspaceMetadata } from "./persistence/workspace-metadata.js";
13
15
  import { reportProgress } from "./progress.js";
14
16
  import { discoverRegistryModules, PRO_FLOW_PACKAGE_PREFIX, } from "./registry/index.js";
@@ -23,6 +25,25 @@ const COMMANDS = [
23
25
  "start",
24
26
  "stop",
25
27
  ];
28
+ function editDistance(left, right) {
29
+ const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
30
+ for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
31
+ const current = [leftIndex];
32
+ for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
33
+ current[rightIndex] = Math.min((current[rightIndex - 1] ?? 0) + 1, (previous[rightIndex] ?? 0) + 1, (previous[rightIndex - 1] ?? 0) +
34
+ (left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1));
35
+ }
36
+ previous.splice(0, previous.length, ...current);
37
+ }
38
+ return previous[right.length] ?? Number.POSITIVE_INFINITY;
39
+ }
40
+ function commandSuggestion(raw) {
41
+ const ranked = COMMANDS.map((command) => ({
42
+ command,
43
+ distance: editDistance(raw.toLowerCase(), command),
44
+ })).sort((left, right) => left.distance - right.distance);
45
+ return ranked[0] && ranked[0].distance <= 2 ? ranked[0].command : undefined;
46
+ }
26
47
  function parseArgs(argv) {
27
48
  let workspace, moduleRef;
28
49
  let special;
@@ -68,8 +89,12 @@ function parseArgs(argv) {
68
89
  if (positional.length !== 1)
69
90
  throw new PlatformError("INVALID_REQUEST", "commands accept no positional arguments");
70
91
  const raw = positional[0] ?? "";
71
- if (!COMMANDS.includes(raw))
72
- throw new PlatformError("INVALID_REQUEST", `unknown command ${raw}`);
92
+ if (!COMMANDS.includes(raw)) {
93
+ const suggestion = commandSuggestion(raw);
94
+ throw new PlatformError("INVALID_REQUEST", suggestion
95
+ ? `未知命令 ${raw}。你是否想运行 platform ${suggestion}?`
96
+ : `未知命令 ${raw}。运行 platform --help 查看可用命令。`);
97
+ }
73
98
  const command = raw;
74
99
  if (moduleRef !== undefined && command !== "setup" && command !== "docs")
75
100
  throw new PlatformError("INVALID_REQUEST", "--module is only valid with setup or docs");
@@ -132,17 +157,24 @@ function batchStatus(result) {
132
157
  return "ACTION_REQUIRED";
133
158
  return "FAILED";
134
159
  }
135
- async function handleStatus(root, runtime) {
136
- reportProgress(runtime.onProgress, {
137
- command: "status",
138
- phase: "status",
139
- status: "STARTED",
140
- message: "正在读取模块状态",
141
- });
142
- const { catalog, modules } = await buildContext(root);
160
+ function missingFrozenModules(modules) {
161
+ const observed = new Set(modules.map((module) => module.moduleRef));
162
+ const hasCurrentPlatformSurface = FROZEN_DEPLOYMENT_INSTALL_ORDER.some((moduleRef) => observed.has(moduleRef));
163
+ if (!hasCurrentPlatformSurface)
164
+ return [];
165
+ return FROZEN_DEPLOYMENT_INSTALL_ORDER.filter((moduleRef) => !observed.has(moduleRef));
166
+ }
167
+ function assertInstalledModules(modules) {
168
+ if (modules.length === 0)
169
+ throw new PlatformError("PLATFORM_NOT_INSTALLED", "ProFlow 尚未安装。请先运行 platform install。");
170
+ const missing = missingFrozenModules(modules);
171
+ if (missing.length > 0)
172
+ throw new PlatformError("PLATFORM_INSTALL_INCOMPLETE", `ProFlow 安装不完整,缺少 ${missing.length} 个核心模块。请重新运行 platform install。`);
173
+ }
174
+ async function collectModuleStatuses(catalog, modules, root) {
143
175
  const observed = await observeStatuses(catalog, modules, root);
144
176
  const byRef = new Map(modules.map((module) => [module.moduleRef, module]));
145
- const output = observed.map((item) => {
177
+ return observed.map((item) => {
146
178
  if (item.result.status !== "SUCCEEDED")
147
179
  throw new PlatformError("COMMAND_FAILED", `module ${item.moduleRef} status observation did not return SUCCEEDED`);
148
180
  const parsed = moduleStatusObservationSchema.safeParse(item.result.data);
@@ -164,13 +196,54 @@ async function handleStatus(root, runtime) {
164
196
  : { externalAvailabilityClaim: item.externalAvailabilityClaim }),
165
197
  };
166
198
  });
199
+ }
200
+ async function handleStatus(root, runtime) {
201
+ reportProgress(runtime.onProgress, {
202
+ command: "status",
203
+ phase: "status",
204
+ status: "STARTED",
205
+ message: "正在读取模块状态",
206
+ });
207
+ const { catalog, modules } = await buildContext(root);
208
+ if (modules.length === 0) {
209
+ reportProgress(runtime.onProgress, {
210
+ command: "status",
211
+ phase: "status",
212
+ status: "SUCCEEDED",
213
+ message: "ProFlow 尚未安装",
214
+ });
215
+ return outcome("status", "SUCCEEDED", root, {
216
+ installed: false,
217
+ installState: "NOT_INSTALLED",
218
+ modules: [],
219
+ });
220
+ }
221
+ const missing = missingFrozenModules(modules);
222
+ if (missing.length > 0) {
223
+ reportProgress(runtime.onProgress, {
224
+ command: "status",
225
+ phase: "status",
226
+ status: "SUCCEEDED",
227
+ message: `ProFlow 安装不完整,缺少 ${missing.length} 个核心模块`,
228
+ });
229
+ return outcome("status", "SUCCEEDED", root, {
230
+ installed: true,
231
+ installState: "INCOMPLETE",
232
+ missingModules: missing,
233
+ modules: [],
234
+ });
235
+ }
236
+ const output = await collectModuleStatuses(catalog, modules, root);
167
237
  reportProgress(runtime.onProgress, {
168
238
  command: "status",
169
239
  phase: "status",
170
240
  status: "SUCCEEDED",
171
241
  message: `已读取 ${output.length} 个模块状态`,
172
242
  });
173
- return outcome("status", "SUCCEEDED", root, { modules: output });
243
+ return outcome("status", "SUCCEEDED", root, {
244
+ installed: true,
245
+ modules: output,
246
+ });
174
247
  }
175
248
  async function handleDocs(root, parsed, runtime) {
176
249
  reportProgress(runtime.onProgress, {
@@ -268,6 +341,7 @@ async function handleInstall(root, runtime) {
268
341
  status: "STARTED",
269
342
  message: "正在校验 Workspace",
270
343
  });
344
+ const gitIsolation = await ensureWorkspaceStateIsGitIgnored(root);
271
345
  const metadata = await ensureWorkspaceMetadata(root);
272
346
  reportProgress(runtime.onProgress, {
273
347
  command: "install",
@@ -298,6 +372,7 @@ async function handleInstall(root, runtime) {
298
372
  command: "install",
299
373
  phase: "registry",
300
374
  kind: "detail",
375
+ retention: "REPLACE",
301
376
  status: "SUCCEEDED",
302
377
  current,
303
378
  total,
@@ -352,6 +427,7 @@ async function handleInstall(root, runtime) {
352
427
  await recordPnpmPolicyOwnership(root, pnpmPolicyBefore);
353
428
  await validateInstalledPackageSet(root, discovered.candidates, previousManaged);
354
429
  const { catalog, modules } = await buildContext(root);
430
+ assertInstalledModules(modules);
355
431
  reportProgress(runtime.onProgress, {
356
432
  command: "install",
357
433
  phase: "validation",
@@ -368,11 +444,12 @@ async function handleInstall(root, runtime) {
368
444
  version: item.moduleVersion,
369
445
  })),
370
446
  workspace: metadata,
447
+ gitIsolation,
371
448
  modules: moduleInstall,
372
449
  next: "platform status",
373
450
  });
374
451
  }
375
- const usefulPackageOutput = /(?:resolved|downloaded|reused|linked|added|removed|packages:|progress:|warn|warning|error|ERR_|done|completed|audited|changed)/i;
452
+ const usefulPackageOutput = /(?:warn|warning|error|ERR_)/i;
376
453
  function reportPackageManagerOutput(runtime, command, output) {
377
454
  if (!usefulPackageOutput.test(output.line))
378
455
  return;
@@ -415,6 +492,8 @@ async function workspaceProFlowDependencies(root) {
415
492
  }
416
493
  async function handleUninstall(root, runtime) {
417
494
  const packageNames = await workspaceProFlowDependencies(root);
495
+ if (packageNames.length > 0)
496
+ await preflightWorkspacePackageManager(root, runtime.executableAvailable);
418
497
  const { catalog, modules } = await buildContext(root);
419
498
  const moduleUninstall = await uninstallModulesThin(catalog, modules, root, runtime.onProgress);
420
499
  if (!moduleUninstall.completed)
@@ -450,6 +529,7 @@ async function handleSetup(root, parsed, runtime) {
450
529
  message: "正在分析模块配置",
451
530
  });
452
531
  const { catalog, modules } = await buildContext(root);
532
+ assertInstalledModules(modules);
453
533
  const interaction = runtime.setupInteraction;
454
534
  const inputByModule = new Map();
455
535
  const approvedSteps = new Set();
@@ -475,24 +555,17 @@ async function handleSetup(root, parsed, runtime) {
475
555
  const action = actionable?.result.actionRequired;
476
556
  if (!actionable || !action)
477
557
  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
- }
558
+ const actionData = isRecord(actionable.result.data)
559
+ ? actionable.result.data
560
+ : undefined;
561
+ const options = Array.isArray(actionData?.candidates)
562
+ ? actionData.candidates.filter((item) => typeof item === "string")
563
+ : undefined;
491
564
  const collected = await interaction.collect({
492
565
  moduleRef: actionable.moduleRef,
493
566
  action: action.action,
494
567
  description: action.description,
495
- ...(modelIds?.length ? { modelIds } : {}),
568
+ ...(options?.length ? { options } : {}),
496
569
  });
497
570
  if (!collected)
498
571
  break;
@@ -516,8 +589,10 @@ async function handleSetup(root, parsed, runtime) {
516
589
  status: result.completed ? "SUCCEEDED" : "ACTION_REQUIRED",
517
590
  message: result.completed ? "模块配置已就绪" : "配置清单已生成",
518
591
  });
592
+ const moduleStatuses = await collectModuleStatuses(catalog, modules, root);
519
593
  return outcome("setup", batchStatus(result), root, {
520
594
  ...result,
595
+ moduleStatuses,
521
596
  ...(parsed.moduleRef === undefined
522
597
  ? {}
523
598
  : { targetModuleRef: parsed.moduleRef }),
@@ -525,6 +600,7 @@ async function handleSetup(root, parsed, runtime) {
525
600
  }
526
601
  async function handleStart(root, runtime) {
527
602
  const { catalog, modules } = await buildContext(root);
603
+ assertInstalledModules(modules);
528
604
  const result = await startModulesThin(catalog, modules, root, runtime.onProgress);
529
605
  return outcome("start", batchStatus(result), root, result);
530
606
  }
@@ -628,87 +704,112 @@ function humanTheme(options = {}) {
628
704
  failure: (value) => paint("red", value),
629
705
  };
630
706
  }
707
+ function journeyState(entries) {
708
+ if (entries.length === 0 ||
709
+ entries.some((item) => item.setupStatus === "FAILED" || item.runtimeStatus === "FAILED"))
710
+ return "FAILED";
711
+ if (entries.some((item) => item.setupStatus === "ACTION_REQUIRED"))
712
+ return "ACTION_REQUIRED";
713
+ if (entries.some((item) => item.setupStatus === "BLOCKED"))
714
+ return "BLOCKED";
715
+ return entries.every((item) => item.setupStatus === "READY")
716
+ ? "READY"
717
+ : "BLOCKED";
718
+ }
719
+ function setupJourney(modules) {
720
+ const byRef = new Map(modules.map((item) => [String(item.moduleRef), item]));
721
+ return [
722
+ { label: "浏览器扩展", refs: ["execution-browser-extension"] },
723
+ { label: "远程连接", refs: ["dev-tunnel"] },
724
+ {
725
+ label: "模型服务与 FAST / THINK",
726
+ refs: ["model-provider-api", "model-runtime"],
727
+ },
728
+ ].map(({ label, refs }) => {
729
+ const entries = refs.flatMap((ref) => {
730
+ const item = byRef.get(ref);
731
+ return item ? [item] : [];
732
+ });
733
+ const issues = entries.flatMap((item) => Array.isArray(item.issues) ? item.issues.filter(isRecord) : []);
734
+ return {
735
+ label,
736
+ state: journeyState(entries),
737
+ modules: entries,
738
+ ...(issues[0] ? { issue: issues[0] } : {}),
739
+ };
740
+ });
741
+ }
631
742
  function renderStatus(data, theme) {
632
743
  if (!isRecord(data) || !Array.isArray(data.modules))
633
- return "未发现模块。";
634
- const setupLabels = {
635
- READY: "已就绪",
636
- ACTION_REQUIRED: "需要操作",
637
- BLOCKED: "等待依赖",
638
- FAILED: "失败",
639
- };
640
- const runtimeLabels = {
641
- RUNNING: "运行中",
642
- STOPPED: "已停止",
643
- FAILED: "运行失败",
644
- NOT_APPLICABLE: "无独立进程",
645
- };
744
+ return "无法读取状态。";
646
745
  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));
654
- const failedModules = modules.filter((item) => item.setupStatus === "FAILED" || 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"));
658
- const running = modules.filter((item) => item.setupStatus === "READY" && item.runtimeStatus === "RUNNING");
659
- const healthy = modules.filter((item) => item.setupStatus === "READY" &&
660
- item.runtimeStatus !== "RUNNING" &&
661
- item.runtimeStatus !== "FAILED");
662
- const lines = [theme.title("ProFlow 状态"), ""];
663
- const renderProblems = (title, entries, tone, icon) => {
664
- if (entries.length === 0)
665
- return;
666
- lines.push(theme.section(title), "");
667
- for (const raw of entries) {
668
- const moduleRef = String(raw.moduleRef);
669
- const setupStatus = String(raw.setupStatus);
670
- const runtimeStatus = String(raw.runtimeStatus);
671
- const runtimeLabel = raw.externalAvailabilityClaim === "AVAILABLE"
672
- ? "外部资源可用"
673
- : (runtimeLabels[runtimeStatus] ?? "未知");
674
- lines.push(`${tone(icon)} ${theme.section(moduleRef)} ${theme.muted(String(raw.version))}`);
675
- lines.push(` ${tone(setupLabels[setupStatus] ?? "未知")} · ${runtimeLabel}`);
676
- const issues = Array.isArray(raw.issues)
677
- ? raw.issues.filter(isRecord)
678
- : [];
679
- for (const issue of issues) {
680
- lines.push(` 原因:${String(issue.message)}`);
681
- if (Array.isArray(issue.relatedModuleRefs) &&
682
- issue.relatedModuleRefs.length > 0)
683
- lines.push(` 依赖:${issue.relatedModuleRefs.map(String).join("、")}`);
684
- if (setupStatus !== "BLOCKED")
685
- lines.push(` 下一步:${theme.command(String(issue.nextCommand))}`);
686
- }
687
- lines.push("");
688
- }
689
- };
690
- renderProblems("失败", failedModules, theme.failure, "✕");
691
- renderProblems("需要操作", actionModules, theme.warning, "");
692
- renderProblems("等待依赖", blockedModules, theme.info, "");
693
- const compact = (title, entries, symbol) => {
694
- if (entries.length === 0)
695
- return;
696
- lines.push(theme.section(title), "");
697
- for (const item of entries) {
698
- const runtime = item.externalAvailabilityClaim === "AVAILABLE"
699
- ? "外部资源可用"
700
- : (runtimeLabels[String(item.runtimeStatus)] ?? "未知");
701
- lines.push(`${theme.success(symbol)} ${String(item.moduleRef).padEnd(32)} ${theme.muted(String(item.version).padEnd(9))} ${runtime}`);
746
+ if (data.installState === "INCOMPLETE") {
747
+ const missing = Array.isArray(data.missingModules)
748
+ ? data.missingModules.filter((item) => typeof item === "string")
749
+ : [];
750
+ return [
751
+ theme.title("ProFlow 状态"),
752
+ "",
753
+ theme.warning("ProFlow 安装不完整。"),
754
+ ...(missing.length > 0 ? [`缺少 ${missing.length} 个核心模块。`] : []),
755
+ `下一步:${theme.command("platform install")}`,
756
+ "",
757
+ "PLATFORM_READY=NO",
758
+ ].join("\n");
759
+ }
760
+ if (data.installed === false || modules.length === 0)
761
+ return [
762
+ theme.title("ProFlow 状态"),
763
+ "",
764
+ theme.warning("ProFlow 尚未安装。"),
765
+ `下一步:${theme.command("platform install")}`,
766
+ "",
767
+ "PLATFORM_READY=NO",
768
+ ].join("\n");
769
+ const journey = setupJourney(modules);
770
+ const completed = journey.filter((step) => step.state === "READY").length;
771
+ const current = journey.find((step) => step.state !== "READY");
772
+ const running = modules.filter((item) => item.runtimeStatus === "RUNNING");
773
+ const platformReady = modules.every((item) => item.setupStatus === "READY" && item.runtimeStatus !== "FAILED");
774
+ const internalProblem = modules.find((item) => item.setupStatus !== "READY" || item.runtimeStatus === "FAILED");
775
+ const lines = [
776
+ theme.title("ProFlow 状态"),
777
+ "",
778
+ `${theme.section("配置进度")} ${completed}/3`,
779
+ ...journey.map((step, index) => {
780
+ const marker = step.state === "READY"
781
+ ? theme.success("✓")
782
+ : step === current
783
+ ? step.state === "FAILED"
784
+ ? theme.failure("✕")
785
+ : theme.warning("→")
786
+ : theme.muted("");
787
+ const label = step.state === "READY"
788
+ ? "已完成"
789
+ : step === current
790
+ ? step.state === "FAILED"
791
+ ? "验证失败"
792
+ : "当前步骤"
793
+ : "后续步骤";
794
+ return `${marker} ${index + 1}/3 ${step.label} · ${label}`;
795
+ }),
796
+ ];
797
+ if (current) {
798
+ lines.push("", theme.section(`当前处理:${current.label}`));
799
+ if (current.issue?.message)
800
+ lines.push(`原因:${String(current.issue.message)}`);
801
+ lines.push(`下一步:${theme.command("platform setup")}`);
802
+ }
803
+ else {
804
+ lines.push("", theme.success("三个核心配置步骤均已完成。"));
805
+ if (internalProblem) {
806
+ const issue = Array.isArray(internalProblem.issues)
807
+ ? internalProblem.issues.filter(isRecord)[0]
808
+ : undefined;
809
+ lines.push(theme.warning("仍有内部能力尚未闭环。"), ...(issue?.message ? [`原因:${String(issue.message)}`] : []), `下一步:${theme.command("platform setup")}`);
702
810
  }
703
- lines.push("");
704
- };
705
- compact("运行中", running, "●");
706
- compact("已就绪", healthy, "○");
707
- const ready = healthy.length + running.length;
708
- const action = modules.filter((item) => item.setupStatus === "ACTION_REQUIRED" && item.runtimeStatus !== "FAILED").length;
709
- const failed = failedModules.length;
710
- const blocked = modules.filter((item) => item.setupStatus === "BLOCKED" && item.runtimeStatus !== "FAILED").length;
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"}`);
811
+ }
812
+ lines.push("", `${running.length} 个真实服务进程运行中`, `PLATFORM_READY=${platformReady ? "YES" : "NO"}`);
712
813
  return lines.join("\n");
713
814
  }
714
815
  function renderTerminalMarkdown(source, theme) {
@@ -742,15 +843,19 @@ function renderDocs(data, theme) {
742
843
  if (!isRecord(data) || !Array.isArray(data.modules))
743
844
  return "未发现模块文档。";
744
845
  if (data.indexOnly === true) {
745
- const modules = data.modules.filter(isRecord);
746
846
  return [
747
847
  theme.title("ProFlow 帮助"),
748
848
  "",
749
- "先运行 platform status 查看当前状态;需要某项详细说明时使用:",
750
- ` ${theme.command("platform docs --module <模块名>")}`,
849
+ "ProFlow 会自动完成内部模块的安装、依赖准备和验证。",
751
850
  "",
752
- theme.section(`可用文档(${modules.length})`),
753
- ...modules.map((item) => ` ${String(item.moduleRef).padEnd(34)} ${theme.muted(String(item.version ?? ""))}`),
851
+ theme.section("推荐流程"),
852
+ ` ${theme.command("platform install")}`,
853
+ ` ${theme.command("platform setup")}`,
854
+ ` ${theme.command("platform start")}`,
855
+ "",
856
+ theme.section("当前需要帮助?"),
857
+ ` 运行 ${theme.command("platform status")} 查看唯一的下一步。`,
858
+ ` 配置未完成时运行 ${theme.command("platform setup")} 继续向导。`,
754
859
  ].join("\n");
755
860
  }
756
861
  const lines = [theme.title("ProFlow 模块文档")];
@@ -794,7 +899,10 @@ function renderSetup(data, theme) {
794
899
  else
795
900
  needsAction += 1;
796
901
  lines.push(`${status === "FAILED" ? theme.failure("✕") : theme.warning("◆")} ${theme.section(setupModuleLabel(moduleRef))}`);
797
- const plan = moduleSetupPlanDataSchema.safeParse(raw.result.data);
902
+ const planData = isRecord(raw.result.data) && Array.isArray(raw.result.data.steps)
903
+ ? { steps: raw.result.data.steps }
904
+ : raw.result.data;
905
+ const plan = moduleSetupPlanDataSchema.safeParse(planData);
798
906
  if (plan.success) {
799
907
  const currentStep = plan.data.steps.find((step) => step.state === "BLOCKED") ??
800
908
  plan.data.steps.find((step) => step.state === "TODO");
@@ -841,19 +949,38 @@ function renderSetup(data, theme) {
841
949
  const dependencyBlockers = Array.isArray(data.blockers)
842
950
  ? data.blockers.filter(isRecord)
843
951
  : [];
844
- for (const item of dependencyBlockers) {
952
+ for (const item of dependencyBlockers.slice(0, 1)) {
845
953
  const moduleRef = String(item.moduleRef ?? "unknown");
846
954
  if (renderedModuleRefs.has(moduleRef))
847
955
  continue;
848
956
  blocked += 1;
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}`)}`, "");
957
+ lines.push(`${theme.info("◇")} ${theme.section(setupModuleLabel(moduleRef))}`, ` 原因:${typeof item.reason === "string" ? item.reason : "等待上游 Module 就绪"}`, ` 下一步:${theme.command("platform setup")}`, "");
850
958
  }
851
959
  const targetModuleRef = typeof data.targetModuleRef === "string" ? data.targetModuleRef : undefined;
852
960
  if (needsAction === 0 && blocked === 0)
853
961
  lines.push(targetModuleRef
854
962
  ? `${setupModuleLabel(targetModuleRef)}配置已就绪。`
855
963
  : "全部模块均已就绪。");
856
- lines.push(theme.section("汇总"), `汇总:${theme.success(`${ready} 个已就绪`)},${theme.warning(`${needsAction} 个需要操作`)},${blocked > 0 ? theme.failure(`${blocked} 个系统阻塞`) : `${blocked} 个系统阻塞`}`);
964
+ const snapshot = Array.isArray(data.moduleStatuses)
965
+ ? data.moduleStatuses.filter(isRecord)
966
+ : [];
967
+ if (snapshot.length > 0) {
968
+ const journey = setupJourney(snapshot);
969
+ const completed = journey.filter((step) => step.state === "READY").length;
970
+ const current = journey.find((step) => step.state !== "READY");
971
+ lines.push(theme.section("配置进度"), `${completed}/3 个核心步骤已完成`, ...(current
972
+ ? [
973
+ `当前处理:${current.label}`,
974
+ ...(current.issue?.message
975
+ ? [`原因:${String(current.issue.message)}`]
976
+ : []),
977
+ `下一步:${theme.command("platform setup")}`,
978
+ ]
979
+ : [theme.success("全部核心配置已完成。")]));
980
+ }
981
+ else {
982
+ lines.push(theme.section("汇总"), `汇总:${theme.success(`${ready} 个已就绪`)},${theme.warning(`${needsAction} 个需要操作`)},${blocked > 0 ? theme.failure(`${blocked} 个系统阻塞`) : `${blocked} 个系统阻塞`}`);
983
+ }
857
984
  return lines.join("\n").trimEnd();
858
985
  }
859
986
  function renderHelp(theme, command) {
@@ -871,12 +998,12 @@ function renderHelp(theme, command) {
871
998
  description: "查看模块配置与运行状态",
872
999
  },
873
1000
  setup: {
874
- usage: "platform setup [--workspace <路径>] [--module <模块名>]",
875
- description: "自动配置并列出全部剩余步骤",
1001
+ usage: "platform setup [--workspace <路径>]",
1002
+ description: "按顺序完成当前唯一配置步骤",
876
1003
  },
877
1004
  docs: {
878
- usage: "platform docs [--workspace <路径>] [--module <模块名>]",
879
- description: "查看文档索引,或阅读一个模块的详细文档",
1005
+ usage: "platform docs [--workspace <路径>]",
1006
+ description: "查看当前使用帮助",
880
1007
  },
881
1008
  start: {
882
1009
  usage: "platform start [--workspace <路径>]",
@@ -912,12 +1039,11 @@ function renderHelp(theme, command) {
912
1039
  "",
913
1040
  theme.section("公共参数"),
914
1041
  " --workspace <路径> 指定 ProFlow Workspace",
915
- " setup --module <模块名> 只查看指定模块的配置步骤",
916
1042
  " -h, --help 显示帮助",
917
1043
  " -v, --version 显示版本",
918
1044
  "",
919
1045
  theme.section("推荐流程"),
920
- ` ${theme.command("install → status → docs → setup → start → status → stop")}`,
1046
+ ` ${theme.command("install → setup → start")}`,
921
1047
  "",
922
1048
  theme.section("配置入口"),
923
1049
  ` ${theme.command("platform setup")}`,
@@ -951,38 +1077,21 @@ export function renderHumanResult(result, options = {}) {
951
1077
  ["dev-tunnel", 1],
952
1078
  ["model-provider-api", 2],
953
1079
  ]);
954
- const groups = [
955
- { status: "FAILED", title: "失败", icon: "✕", tone: theme.failure },
956
- {
957
- status: "ACTION_REQUIRED",
958
- title: "当前根阻塞",
959
- icon: "◆",
960
- tone: theme.warning,
961
- },
962
- { status: "BLOCKED", title: "下游等待", icon: "◇", tone: theme.info },
963
- ];
964
- const lines = [theme.failure("平台未启动:存在未就绪模块"), ""];
965
- for (const group of groups) {
966
- const entries = blockers
967
- .filter((item) => item.setupStatus === group.status)
1080
+ const lines = [theme.failure("平台未启动:配置尚未完成"), ""];
1081
+ let current;
1082
+ for (const status of ["FAILED", "ACTION_REQUIRED", "BLOCKED"]) {
1083
+ current = blockers
1084
+ .filter((item) => item.setupStatus === status)
968
1085
  .sort((a, b) => (productOrder.get(String(a.moduleRef)) ?? 100) -
969
- (productOrder.get(String(b.moduleRef)) ?? 100));
970
- if (entries.length === 0)
971
- continue;
972
- lines.push(theme.section(group.title));
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
- }
978
- lines.push("");
1086
+ (productOrder.get(String(b.moduleRef)) ?? 100))[0];
1087
+ if (current)
1088
+ break;
1089
+ }
1090
+ if (current) {
1091
+ const moduleRef = String(current.moduleRef);
1092
+ lines.push(`${current.setupStatus === "FAILED" ? theme.failure("✕") : theme.warning("◆")} ${theme.section(setupModuleLabel(moduleRef))}`, `原因:${typeof current.reason === "string" ? current.reason : "当前配置尚未完成"}`, "");
979
1093
  }
980
- const readyCount = Array.isArray(result.data.results)
981
- ? result.data.results.filter((item) => isRecord(item) &&
982
- isRecord(item.result) &&
983
- item.result.status === "SUCCEEDED").length - blockers.length
984
- : 0;
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}"` : ""}`)}`);
1094
+ lines.push("PLATFORM_READY=NO", `处理方式:${theme.command(`platform setup${result.workspaceRoot ? ` --workspace "${result.workspaceRoot}"` : ""}`)}`);
986
1095
  return lines.join("\n");
987
1096
  }
988
1097
  }
@@ -992,7 +1101,8 @@ export function renderHumanResult(result, options = {}) {
992
1101
  `${theme.failure("│")} ${theme.section(result.error.code)}`,
993
1102
  `${theme.failure("│")} ${result.error.message}`,
994
1103
  theme.failure("╰─ 请根据提示修复后重试"),
995
- ...(result.error.category === "USAGE"
1104
+ ...(result.error.category === "USAGE" &&
1105
+ !result.error.message.includes("你是否想运行")
996
1106
  ? ["", renderHelp(theme, result.error.helpCommand)]
997
1107
  : []),
998
1108
  ].join("\n");
@@ -1,4 +1,4 @@
1
- export declare const platformErrorCodes: readonly ["INVALID_REQUEST", "DESCRIPTOR_INVALID", "DUPLICATE_IDENTITY", "DEPENDENCY_UNRESOLVED", "DEPENDENCY_INCOMPATIBLE", "DEPENDENCY_CYCLE", "WORKSPACE_NOT_FOUND", "WORKSPACE_INSTANCE_INVALID", "PACKAGE_MANAGER_UNSUPPORTED", "PACKAGE_MANAGER_CONFLICT", "PACKAGE_MANAGER_UNAVAILABLE", "UNINSTALL_FAILED", "REGISTRY_UNAVAILABLE", "REGISTRY_AUTH_REQUIRED", "REGISTRY_RESPONSE_INVALID", "PACKAGE_NOT_FOUND", "PACKAGE_NOT_PROFLOW", "COMMAND_FAILED"];
1
+ export declare const platformErrorCodes: readonly ["INVALID_REQUEST", "DESCRIPTOR_INVALID", "DUPLICATE_IDENTITY", "DEPENDENCY_UNRESOLVED", "DEPENDENCY_INCOMPATIBLE", "DEPENDENCY_CYCLE", "WORKSPACE_NOT_FOUND", "WORKSPACE_INSTANCE_INVALID", "WORKSPACE_STATE_NOT_ISOLATED", "PACKAGE_MANAGER_UNSUPPORTED", "PACKAGE_MANAGER_CONFLICT", "PACKAGE_MANAGER_UNAVAILABLE", "UNINSTALL_FAILED", "REGISTRY_UNAVAILABLE", "REGISTRY_AUTH_REQUIRED", "REGISTRY_RESPONSE_INVALID", "PACKAGE_NOT_FOUND", "PACKAGE_NOT_PROFLOW", "PLATFORM_NOT_INSTALLED", "PLATFORM_INSTALL_INCOMPLETE", "COMMAND_FAILED"];
2
2
  export type PlatformErrorCode = (typeof platformErrorCodes)[number];
3
3
  export declare class PlatformError extends Error {
4
4
  readonly code: PlatformErrorCode;
@@ -7,6 +7,7 @@ export const platformErrorCodes = [
7
7
  "DEPENDENCY_CYCLE",
8
8
  "WORKSPACE_NOT_FOUND",
9
9
  "WORKSPACE_INSTANCE_INVALID",
10
+ "WORKSPACE_STATE_NOT_ISOLATED",
10
11
  "PACKAGE_MANAGER_UNSUPPORTED",
11
12
  "PACKAGE_MANAGER_CONFLICT",
12
13
  "PACKAGE_MANAGER_UNAVAILABLE",
@@ -16,6 +17,8 @@ export const platformErrorCodes = [
16
17
  "REGISTRY_RESPONSE_INVALID",
17
18
  "PACKAGE_NOT_FOUND",
18
19
  "PACKAGE_NOT_PROFLOW",
20
+ "PLATFORM_NOT_INSTALLED",
21
+ "PLATFORM_INSTALL_INCOMPLETE",
19
22
  "COMMAND_FAILED",
20
23
  ];
21
24
  export class PlatformError extends Error {
@@ -38,3 +38,4 @@ export declare function removeWorkspacePackages(options: {
38
38
  onOutput?: (line: PackageCommandOutput) => void;
39
39
  }): Promise<WorkspacePackageMutationResult>;
40
40
  export declare function observeWorkspaceInstalledVersion(workspaceRoot: string, packageName: string): Promise<string | undefined>;
41
+ export declare function preflightWorkspacePackageManager(workspaceRoot: string, executableAvailable?: (command: string) => boolean): Promise<WorkspacePackageManagerSelection>;
@@ -157,7 +157,7 @@ export async function syncWorkspacePackages(options) {
157
157
  }
158
158
  await ensureWorkspaceManifest(options.workspaceRoot);
159
159
  const runner = options.runner ?? systemPackageCommandRunner();
160
- const manager = await requireWorkspacePackageManager(options.workspaceRoot, options.executableAvailable ?? findExecutable);
160
+ const manager = await preflightWorkspacePackageManager(options.workspaceRoot, options.executableAvailable ?? findExecutable);
161
161
  const targets = [...options.packages].sort((a, b) => a.packageName.localeCompare(b.packageName));
162
162
  const specs = targets.map((item) => `${item.packageName}@${item.version}`);
163
163
  await writeManagedPackageSet(options.workspaceRoot, targets);
@@ -212,7 +212,7 @@ export async function removeWorkspacePackages(options) {
212
212
  return { packageManager: "npm", packages: [] };
213
213
  }
214
214
  const runner = options.runner ?? systemPackageCommandRunner();
215
- const manager = await requireWorkspacePackageManager(options.workspaceRoot, options.executableAvailable ?? findExecutable);
215
+ const manager = await preflightWorkspacePackageManager(options.workspaceRoot, options.executableAvailable ?? findExecutable);
216
216
  const args = await batchPackageManagerArgs(runner, manager, options.workspaceRoot, "remove", packageNames);
217
217
  try {
218
218
  await runner.run(manager.name, args, options.workspaceRoot, options.onOutput);
@@ -253,7 +253,7 @@ async function ensureWorkspaceManifest(workspaceRoot) {
253
253
  }
254
254
  await atomicWrite(path, `${JSON.stringify({ private: true }, null, 2)}\n`);
255
255
  }
256
- async function requireWorkspacePackageManager(workspaceRoot, executableAvailable) {
256
+ export async function preflightWorkspacePackageManager(workspaceRoot, executableAvailable = findExecutable) {
257
257
  const manager = await readWorkspacePackageManagerSelection(workspaceRoot);
258
258
  if (!executableAvailable(manager.name)) {
259
259
  throw new PlatformError("PACKAGE_MANAGER_UNAVAILABLE", `workspace package manager ${manager.name} is not available on PATH`);
@@ -87,6 +87,8 @@ async function runOrdered(catalog, modules, workspaceRoot, command, reverse, rep
87
87
  reportProgress(reporter, {
88
88
  command,
89
89
  phase: command,
90
+ kind: "detail",
91
+ retention: "REPLACE",
90
92
  current: index + 1,
91
93
  total: modulesInOrder.length,
92
94
  moduleRef: module.moduleRef,
@@ -100,6 +102,8 @@ async function runOrdered(catalog, modules, workspaceRoot, command, reverse, rep
100
102
  reportProgress(reporter, {
101
103
  command,
102
104
  phase: command,
105
+ kind: "detail",
106
+ retention: "REPLACE",
103
107
  current: index + 1,
104
108
  total: modulesInOrder.length,
105
109
  moduleRef: module.moduleRef,
@@ -222,6 +226,8 @@ export async function setupModulesThin(catalog, modules, workspaceRoot, target,
222
226
  reportProgress(reporter, {
223
227
  command: "setup",
224
228
  phase: "setup",
229
+ kind: "detail",
230
+ retention: "REPLACE",
225
231
  current: index + 1,
226
232
  total: modulesInOrder.length,
227
233
  moduleRef: module.moduleRef,
@@ -240,6 +246,8 @@ export async function setupModulesThin(catalog, modules, workspaceRoot, target,
240
246
  reportProgress(reporter, {
241
247
  command: "setup",
242
248
  phase: "setup",
249
+ kind: "detail",
250
+ retention: "REPLACE",
243
251
  current: index + 1,
244
252
  total: modulesInOrder.length,
245
253
  moduleRef: module.moduleRef,
@@ -254,6 +262,8 @@ export async function setupModulesThin(catalog, modules, workspaceRoot, target,
254
262
  reportProgress(reporter, {
255
263
  command: "setup",
256
264
  phase: "setup",
265
+ kind: "detail",
266
+ retention: "REPLACE",
257
267
  current: index + 1,
258
268
  total: modulesInOrder.length,
259
269
  moduleRef: module.moduleRef,
@@ -0,0 +1,4 @@
1
+ export declare function ensureWorkspaceStateIsGitIgnored(workspaceRoot: string): Promise<{
2
+ gitRepository: boolean;
3
+ changed: boolean;
4
+ }>;
@@ -0,0 +1,52 @@
1
+ import { execFile } from "node:child_process";
2
+ import { mkdir, readFile, realpath, writeFile } from "node:fs/promises";
3
+ import { dirname, relative, resolve } from "node:path";
4
+ import { promisify } from "node:util";
5
+ import { PlatformError } from "../errors.js";
6
+ const execute = promisify(execFile);
7
+ async function git(root, args) {
8
+ try {
9
+ const result = await execute("git", ["-C", root, ...args], {
10
+ encoding: "utf8",
11
+ });
12
+ return result.stdout.trim();
13
+ }
14
+ catch {
15
+ return undefined;
16
+ }
17
+ }
18
+ export async function ensureWorkspaceStateIsGitIgnored(workspaceRoot) {
19
+ const topLevel = await git(workspaceRoot, ["rev-parse", "--show-toplevel"]);
20
+ const gitDirectoryValue = await git(workspaceRoot, [
21
+ "rev-parse",
22
+ "--absolute-git-dir",
23
+ ]);
24
+ if (!topLevel || !gitDirectoryValue)
25
+ return { gitRepository: false, changed: false };
26
+ if (await git(workspaceRoot, ["check-ignore", ".proflow/workspace.json"]))
27
+ return { gitRepository: true, changed: false };
28
+ const [canonicalTopLevel, canonicalWorkspaceRoot] = await Promise.all([
29
+ realpath(topLevel).catch(() => topLevel),
30
+ realpath(workspaceRoot).catch(() => workspaceRoot),
31
+ ]);
32
+ const excludePath = resolve(gitDirectoryValue, "info", "exclude");
33
+ const workspaceRelative = relative(canonicalTopLevel, canonicalWorkspaceRoot).replaceAll("\\", "/");
34
+ const pattern = `/${workspaceRelative ? `${workspaceRelative}/` : ""}.proflow/`;
35
+ let existing = "";
36
+ try {
37
+ existing = await readFile(excludePath, "utf8");
38
+ }
39
+ catch {
40
+ // A new repository may not have an info/exclude file yet.
41
+ }
42
+ if (!existing.split(/\r?\n/).includes(pattern)) {
43
+ await mkdir(dirname(excludePath), { recursive: true });
44
+ const prefix = existing.length === 0 || existing.endsWith("\n")
45
+ ? existing
46
+ : `${existing}\n`;
47
+ await writeFile(excludePath, `${prefix}# ProFlow managed workspace state\n${pattern}\n`, "utf8");
48
+ }
49
+ if (!(await git(workspaceRoot, ["check-ignore", ".proflow/workspace.json"])))
50
+ throw new PlatformError("WORKSPACE_STATE_NOT_ISOLATED", "无法确保 .proflow 工作区状态被 Git 忽略;安装已安全停止。");
51
+ return { gitRepository: true, changed: true };
52
+ }
@@ -1,6 +1,7 @@
1
1
  export declare const PRO_FLOW_SCOPE = "@tomflow";
2
2
  export declare const PRO_FLOW_PACKAGE_PREFIX = "@tomflow/proflow-";
3
3
  export declare const RETIRED_PRO_FLOW_PACKAGES: Set<string>;
4
+ export declare const PRO_FLOW_TOOL_PACKAGES: Set<string>;
4
5
  export interface NpmCommandResult {
5
6
  stdout: string;
6
7
  stderr: string;
@@ -28,6 +29,7 @@ export interface RegistryDiscoveryResult {
28
29
  candidates: RegistryModuleCandidate[];
29
30
  rejected: RegistryRejectedPackage[];
30
31
  retired: string[];
32
+ tools: string[];
31
33
  }
32
34
  export declare function systemNpmRunner(): NpmCommandRunner;
33
35
  export declare function resolveScopeRegistry(workspaceRoot: string, runner?: NpmCommandRunner): Promise<string>;
@@ -9,6 +9,9 @@ export const PRO_FLOW_PACKAGE_PREFIX = "@tomflow/proflow-";
9
9
  export const RETIRED_PRO_FLOW_PACKAGES = new Set([
10
10
  "@tomflow/proflow-chatgpt-carrier",
11
11
  ]);
12
+ export const PRO_FLOW_TOOL_PACKAGES = new Set([
13
+ "@tomflow/proflow-devtunnel-cli",
14
+ ]);
12
15
  export function systemNpmRunner() {
13
16
  return {
14
17
  async run(args, cwd, timeoutMs) {
@@ -46,7 +49,8 @@ export async function discoverRegistryModules(options) {
46
49
  ? await searchPackageNames(options.workspaceRoot, registry, runner)
47
50
  : [validateRequestedPackageName(options.packageName)];
48
51
  const retired = names.filter((name) => RETIRED_PRO_FLOW_PACKAGES.has(name));
49
- const activeNames = names.filter((name) => !RETIRED_PRO_FLOW_PACKAGES.has(name));
52
+ const tools = names.filter((name) => PRO_FLOW_TOOL_PACKAGES.has(name));
53
+ const activeNames = names.filter((name) => !RETIRED_PRO_FLOW_PACKAGES.has(name) && !PRO_FLOW_TOOL_PACKAGES.has(name));
50
54
  options.onSearchComplete?.(activeNames.length);
51
55
  const candidates = [];
52
56
  const rejected = [];
@@ -75,7 +79,7 @@ export async function discoverRegistryModules(options) {
75
79
  await Promise.all(Array.from({ length: Math.min(4, activeNames.length) }, () => worker()));
76
80
  candidates.sort((left, right) => left.packageName.localeCompare(right.packageName));
77
81
  rejected.sort((left, right) => left.packageName.localeCompare(right.packageName));
78
- return { registry, candidates, rejected, retired };
82
+ return { registry, candidates, rejected, retired, tools };
79
83
  }
80
84
  async function searchPackageNames(workspaceRoot, registry, runner) {
81
85
  const result = await runner.run([
@@ -2,7 +2,7 @@ export type SetupActionRequest = {
2
2
  moduleRef: string;
3
3
  action: string;
4
4
  description: string;
5
- modelIds?: readonly string[];
5
+ options?: readonly string[];
6
6
  };
7
7
  export interface SetupInteraction {
8
8
  begin(): void;
@@ -1,9 +1,8 @@
1
- import { autocomplete, cancel, confirm, intro, isCancel, outro, password, text, } from "@clack/prompts";
1
+ import { cancel, confirm, intro, isCancel, outro, password, select, text, } from "@clack/prompts";
2
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 继续。",
3
+ "dev-tunnel": "下一步将检查远程连接;如需登录,会打开 GitHub 授权。是否继续?",
4
+ "model-provider-api": "下一步配置 OpenAI-compatible 模型服务地址。是否继续?",
5
+ "model-runtime": "下一步验证模型能力并配置 FAST THINK。是否继续?",
7
6
  };
8
7
  function cancelled(value) {
9
8
  if (!isCancel(value))
@@ -71,29 +70,26 @@ async function providerCredential() {
71
70
  return undefined;
72
71
  return { providerCredential: String(value) };
73
72
  }
74
- async function modelRoles(modelIds) {
73
+ async function modelRole(role, modelIds) {
75
74
  const options = modelIds.map((id) => ({ value: id, label: id }));
76
- const fastModel = await autocomplete({
77
- message: "选择 FAST 模型(快速、低延迟)",
78
- placeholder: "输入关键字搜索模型",
75
+ const selected = await select({
76
+ message: role === "fast"
77
+ ? "选择 FAST 模型(快速、低延迟)"
78
+ : "选择 THINK 模型(复杂分析与推理)",
79
79
  options,
80
+ showInstructions: false,
80
81
  });
81
- if (cancelled(fastModel))
82
- return undefined;
83
- const reasonModel = await autocomplete({
84
- message: "选择 THINK 模型(复杂分析与推理)",
85
- placeholder: "输入关键字搜索模型",
86
- options,
87
- });
88
- if (cancelled(reasonModel))
82
+ if (cancelled(selected))
89
83
  return undefined;
90
84
  const approved = await confirm({
91
- message: `确认 FAST=${String(fastModel)}THINK=${String(reasonModel)}?`,
85
+ message: `确认将 ${String(selected)} 用作 ${role === "fast" ? "FAST" : "THINK"} 模型?`,
92
86
  initialValue: true,
93
87
  });
94
88
  if (cancelled(approved) || approved !== true)
95
89
  return undefined;
96
- return { fastModel: String(fastModel), reasonModel: String(reasonModel) };
90
+ return role === "fast"
91
+ ? { fastModel: String(selected) }
92
+ : { reasonModel: String(selected) };
97
93
  }
98
94
  export function createClackSetupInteraction() {
99
95
  return {
@@ -111,14 +107,26 @@ export function createClackSetupInteraction() {
111
107
  return !cancelled(approved) && approved === true;
112
108
  },
113
109
  async collect(request) {
110
+ if (request.action === "confirm-browser-developer-mode") {
111
+ const approved = await confirm({
112
+ message: "步骤 1/2:已经在 Chrome 中开启“开发者模式”了吗?",
113
+ initialValue: true,
114
+ });
115
+ if (cancelled(approved) || approved !== true)
116
+ return undefined;
117
+ return { developerModeConfirmed: true };
118
+ }
114
119
  if (request.action === "provide-provider-endpoint") {
115
120
  return providerEndpoint();
116
121
  }
117
122
  if (request.action === "provide-provider-credential") {
118
123
  return providerCredential();
119
124
  }
120
- if (request.action.startsWith("select-") && request.modelIds?.length) {
121
- return modelRoles(request.modelIds);
125
+ if (request.action === "select-fast-model" && request.options?.length) {
126
+ return modelRole("fast", request.options);
127
+ }
128
+ if (request.action === "select-reason-model" && request.options?.length) {
129
+ return modelRole("reason", request.options);
122
130
  }
123
131
  return undefined;
124
132
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomflow/proflow-platform-cli",
3
- "version": "0.1.39",
3
+ "version": "0.1.41",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -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.39",
6
+ "moduleVersion": "0.1.41",
7
7
  "kind": "cli",
8
8
  "templateVersion": "1.0.0",
9
9
  "platformCompatibility": ">=1.0.0 <2.0.0",