@tomflow/proflow-platform-cli 0.1.38 → 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,11 @@
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
+
3
9
  ## 0.1.38
4
10
 
5
11
  ### 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.38";
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.38";
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.38";
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.38";
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.38";
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.38";
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.38";
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.38";
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.38",
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 }),
@@ -171,7 +172,7 @@ async function handleStatus(root, runtime) {
171
172
  });
172
173
  return outcome("status", "SUCCEEDED", root, { modules: output });
173
174
  }
174
- async function handleDocs(root, runtime) {
175
+ async function handleDocs(root, parsed, runtime) {
175
176
  reportProgress(runtime.onProgress, {
176
177
  command: "docs",
177
178
  phase: "docs",
@@ -179,7 +180,28 @@ async function handleDocs(root, runtime) {
179
180
  message: "正在整理模块文档",
180
181
  });
181
182
  const { catalog, modules: resolvedModules } = await buildContext(root);
182
- 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);
183
205
  const byRef = new Map(resolvedModules.map((module) => [module.moduleRef, module]));
184
206
  const modules = [];
185
207
  const errors = [];
@@ -270,7 +292,7 @@ async function handleInstall(root, runtime) {
270
292
  kind: "detail",
271
293
  status: "STARTED",
272
294
  total,
273
- message: `Registry 返回 ${total} 个候选模块,正在核验`,
295
+ message: `Registry 返回 ${total} 个候选模块,正在做安装前核验`,
274
296
  }),
275
297
  onPackageChecked: ({ current, total, packageName }) => reportProgress(runtime.onProgress, {
276
298
  command: "install",
@@ -280,7 +302,7 @@ async function handleInstall(root, runtime) {
280
302
  current,
281
303
  total,
282
304
  moduleRef: packageName.replace(PRO_FLOW_PACKAGE_PREFIX, ""),
283
- message: `核验 ${packageName.replace(PRO_FLOW_PACKAGE_PREFIX, "")}`,
305
+ message: `安装前核验 ${packageName.replace(PRO_FLOW_PACKAGE_PREFIX, "")}`,
284
306
  }),
285
307
  ...(runtime.registryRunner === undefined
286
308
  ? {}
@@ -335,7 +357,7 @@ async function handleInstall(root, runtime) {
335
357
  phase: "validation",
336
358
  kind: "phase",
337
359
  status: "SUCCEEDED",
338
- message: `已验证 ${modules.length} 个已安装模块`,
360
+ message: `安装完成,${modules.length} 个模块已从本地安装物验证`,
339
361
  });
340
362
  const moduleInstall = await installModulesThin(catalog, modules, root, runtime.onProgress);
341
363
  return outcome("install", batchStatus(moduleInstall), root, {
@@ -428,17 +450,78 @@ async function handleSetup(root, parsed, runtime) {
428
450
  message: "正在分析模块配置",
429
451
  });
430
452
  const { catalog, modules } = await buildContext(root);
431
- 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
432
468
  ? undefined
433
469
  : { moduleRef: parsed.moduleRef };
434
- 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("配置已完成");
435
513
  reportProgress(runtime.onProgress, {
436
514
  command: "setup",
437
515
  phase: "setup",
438
516
  status: result.completed ? "SUCCEEDED" : "ACTION_REQUIRED",
439
517
  message: result.completed ? "模块配置已就绪" : "配置清单已生成",
440
518
  });
441
- 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
+ });
442
525
  }
443
526
  async function handleStart(root, runtime) {
444
527
  const { catalog, modules } = await buildContext(root);
@@ -485,7 +568,7 @@ export async function runCli(argv, runtime = {}) {
485
568
  case "setup":
486
569
  return await handleSetup(root, parsed, runtime);
487
570
  case "docs":
488
- return await handleDocs(root, runtime);
571
+ return await handleDocs(root, parsed, runtime);
489
572
  case "start":
490
573
  return await handleStart(root, runtime);
491
574
  case "stop":
@@ -658,6 +741,18 @@ function renderTerminalMarkdown(source, theme) {
658
741
  function renderDocs(data, theme) {
659
742
  if (!isRecord(data) || !Array.isArray(data.modules))
660
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
+ }
661
756
  const lines = [theme.title("ProFlow 模块文档")];
662
757
  for (const raw of data.modules)
663
758
  if (isRecord(raw))
@@ -668,36 +763,13 @@ function renderDocs(data, theme) {
668
763
  .map((item) => `- ${String(item.moduleRef)}:${String(item.reason)}`));
669
764
  return lines.join("\n");
670
765
  }
671
- const setupCommands = {
672
- "dev-tunnel": {
673
- ai: "platform setup --module dev-tunnel",
674
- inputs: "",
675
- },
676
- "model-provider-api": {
677
- ai: "platform setup --module model-provider-api",
678
- inputs: "无(等待 Deployment resolver 提供 endpoint)",
679
- },
680
- "model-runtime": {
681
- ai: "platform setup --module model-runtime",
682
- inputs: "无(仅等价合格候选歧义时选择)",
683
- },
684
- "execution-browser-extension": {
685
- ai: "platform setup --module execution-browser-extension",
686
- inputs: "无",
687
- },
688
- "agent-controller-dev": {
689
- ai: "platform setup --module agent-controller-dev",
690
- inputs: "无",
691
- },
692
- "agent-product": {
693
- ai: "platform setup --module agent-product",
694
- inputs: "无",
695
- },
696
- "agent-test-ops": {
697
- ai: "platform setup --module agent-test-ops",
698
- inputs: "无",
699
- },
766
+ const setupModuleLabels = {
767
+ "execution-browser-extension": "浏览器扩展",
768
+ "dev-tunnel": "远程连接",
769
+ "model-provider-api": "模型服务",
770
+ "model-runtime": "FAST / THINK 模型",
700
771
  };
772
+ const setupModuleLabel = (moduleRef) => setupModuleLabels[moduleRef] ?? moduleRef;
701
773
  function renderSetup(data, theme) {
702
774
  if (!isRecord(data) || !Array.isArray(data.results))
703
775
  return "没有需要执行的配置步骤。";
@@ -721,10 +793,14 @@ function renderSetup(data, theme) {
721
793
  blocked += 1;
722
794
  else
723
795
  needsAction += 1;
724
- lines.push(`${status === "FAILED" ? theme.failure("✕") : theme.warning("◆")} ${theme.section(moduleRef)}`);
796
+ lines.push(`${status === "FAILED" ? theme.failure("✕") : theme.warning("◆")} ${theme.section(setupModuleLabel(moduleRef))}`);
725
797
  const plan = moduleSetupPlanDataSchema.safeParse(raw.result.data);
726
798
  if (plan.success) {
799
+ const currentStep = plan.data.steps.find((step) => step.state === "BLOCKED") ??
800
+ plan.data.steps.find((step) => step.state === "TODO");
727
801
  for (const [index, step] of plan.data.steps.entries()) {
802
+ if (step !== currentStep && step.state !== "DONE")
803
+ continue;
728
804
  const marker = step.state === "DONE"
729
805
  ? theme.success("✓")
730
806
  : step.state === "BLOCKED"
@@ -736,13 +812,10 @@ function renderSetup(data, theme) {
736
812
  lines.push(` ${marker} ${String(index + 1).padStart(2, "0")} ${step.title}`);
737
813
  if (step.description)
738
814
  lines.push(` ${step.description}`);
739
- lines.push(` 运行:${theme.command(step.execution.interactive)}`);
740
- lines.push(` AI:${theme.command(step.execution.nonInteractive)}`);
741
815
  if (step.requiredInputs.length > 0)
742
816
  lines.push(` 需要:${step.requiredInputs.map((item) => item.description).join("、")}`);
743
817
  if (step.humanAction)
744
818
  lines.push(` 人工操作:${step.humanAction}`);
745
- lines.push(` 验证:${theme.command(step.verify)}`);
746
819
  lines.push(` 完成:${step.successCondition}`);
747
820
  if (step.blockedReason)
748
821
  lines.push(` 原因:${theme.failure(step.blockedReason)}`);
@@ -753,15 +826,7 @@ function renderSetup(data, theme) {
753
826
  lines.push(` 1. ${status === "FAILED" ? "等待上游服务信息" : "完成模块配置"}`);
754
827
  const error = raw.result.error;
755
828
  if (status !== "FAILED") {
756
- const commands = setupCommands[moduleRef] ?? {
757
- ai: `platform setup --module ${moduleRef}`,
758
- inputs: "按命令提示提供",
759
- };
760
- lines.push(` 人工执行:${commands.ai}`);
761
- lines.push(` AI 执行:${commands.ai}`);
762
- lines.push(` 需要输入:${commands.inputs}`);
763
- lines.push(" 验证:platform status");
764
- lines.push(" 完成条件:配置状态变为“已就绪”");
829
+ lines.push(" 请在当前 Platform 向导中完成这一项。完成后会自动验证。");
765
830
  }
766
831
  else if (isRecord(error)) {
767
832
  const code = error.code === undefined ? "FAILED" : String(error.code);
@@ -769,8 +834,7 @@ function renderSetup(data, theme) {
769
834
  ? "Module setup failed."
770
835
  : String(error.message);
771
836
  lines.push(` 原因:${code} — ${message}`);
772
- lines.push(` 执行:platform setup --module ${moduleRef}`);
773
- lines.push(" 完成条件:配置状态变为“已就绪”");
837
+ lines.push(" 修复当前问题后重新运行 platform setup。");
774
838
  }
775
839
  lines.push("");
776
840
  }
@@ -784,8 +848,11 @@ function renderSetup(data, theme) {
784
848
  blocked += 1;
785
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}`)}`, "");
786
850
  }
851
+ const targetModuleRef = typeof data.targetModuleRef === "string" ? data.targetModuleRef : undefined;
787
852
  if (needsAction === 0 && blocked === 0)
788
- lines.push("全部模块均已就绪。");
853
+ lines.push(targetModuleRef
854
+ ? `${setupModuleLabel(targetModuleRef)}配置已就绪。`
855
+ : "全部模块均已就绪。");
789
856
  lines.push(theme.section("汇总"), `汇总:${theme.success(`${ready} 个已就绪`)},${theme.warning(`${needsAction} 个需要操作`)},${blocked > 0 ? theme.failure(`${blocked} 个系统阻塞`) : `${blocked} 个系统阻塞`}`);
790
857
  return lines.join("\n").trimEnd();
791
858
  }
@@ -808,8 +875,8 @@ function renderHelp(theme, command) {
808
875
  description: "自动配置并列出全部剩余步骤",
809
876
  },
810
877
  docs: {
811
- usage: "platform docs [--workspace <路径>]",
812
- description: "阅读全部模块能力文档",
878
+ usage: "platform docs [--workspace <路径>] [--module <模块名>]",
879
+ description: "查看文档索引,或阅读一个模块的详细文档",
813
880
  },
814
881
  start: {
815
882
  usage: "platform start [--workspace <路径>]",
@@ -974,7 +1041,12 @@ export function renderHumanResult(result, options = {}) {
974
1041
  if (import.meta.main) {
975
1042
  const argv = process.argv.slice(2);
976
1043
  const reporter = createTerminalProgressReporter();
977
- 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
+ });
978
1050
  reporter.close();
979
1051
  const color = process.stdout.isTTY === true &&
980
1052
  process.env.NO_COLOR === undefined &&
@@ -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) {
@@ -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,7 +172,7 @@ 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 = [];
@@ -180,8 +180,40 @@ export async function setupModulesThin(catalog, modules, workspaceRoot, target,
180
180
  const byRef = new Map(modules.map((module) => [module.moduleRef, module]));
181
181
  if (target !== undefined && !byRef.has(target.moduleRef))
182
182
  throw new PlatformError("INVALID_REQUEST", `setup target module ${target.moduleRef} was not discovered`);
183
- const selectedRefs = new Set(target === undefined ? graph.order : [target.moduleRef]);
184
- const modulesInOrder = graph.order
183
+ const setupPriority = [
184
+ "execution-browser-extension",
185
+ "dev-tunnel",
186
+ "model-provider-api",
187
+ "model-runtime",
188
+ ];
189
+ const dependenciesByRef = new Map();
190
+ for (const edge of graph.edges) {
191
+ const dependencies = dependenciesByRef.get(edge.from) ?? [];
192
+ dependencies.push(edge.to);
193
+ dependenciesByRef.set(edge.from, dependencies);
194
+ }
195
+ const userOrder = [];
196
+ const included = new Set();
197
+ const appendWithDependencies = (moduleRef) => {
198
+ if (included.has(moduleRef) || !byRef.has(moduleRef))
199
+ return;
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);
205
+ };
206
+ if (target === undefined) {
207
+ for (const moduleRef of setupPriority)
208
+ appendWithDependencies(moduleRef);
209
+ for (const moduleRef of graph.order)
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
185
217
  .filter((moduleRef) => selectedRefs.has(moduleRef))
186
218
  .map((moduleRef) => byRef.get(moduleRef))
187
219
  .filter((module) => module !== undefined);
@@ -200,9 +232,7 @@ export async function setupModulesThin(catalog, modules, workspaceRoot, target,
200
232
  if (!succeeded(status.result)) {
201
233
  results.push(status);
202
234
  completed = false;
203
- if (target?.moduleRef === module.moduleRef)
204
- break;
205
- continue;
235
+ break;
206
236
  }
207
237
  const observed = moduleStatusObservationSchema.parse(status.result.data);
208
238
  if (observed.setupStatus === "READY" && target === undefined) {
@@ -218,6 +248,7 @@ export async function setupModulesThin(catalog, modules, workspaceRoot, target,
218
248
  });
219
249
  continue;
220
250
  }
251
+ await beforeSetup?.(module.moduleRef);
221
252
  const setup = await dispatchModuleCommand(catalog, module, "setup", context(workspaceRoot, target?.input));
222
253
  results.push(setup);
223
254
  reportProgress(reporter, {
@@ -243,8 +274,7 @@ export async function setupModulesThin(catalog, modules, workspaceRoot, target,
243
274
  }
244
275
  else {
245
276
  const reconciledStatus = moduleStatusObservationSchema.parse(reconciled.result.data);
246
- if (reconciledStatus.setupStatus === "BLOCKED" ||
247
- reconciledStatus.setupStatus === "FAILED") {
277
+ if (reconciledStatus.setupStatus !== "READY") {
248
278
  const issue = reconciledStatus.issues?.find((item) => item.scope === "SETUP");
249
279
  blockers.push({
250
280
  moduleRef: module.moduleRef,
@@ -256,8 +286,7 @@ export async function setupModulesThin(catalog, modules, workspaceRoot, target,
256
286
  completed = false;
257
287
  }
258
288
  }
259
- if (target !== undefined &&
260
- (!succeeded(setup.result) || blockers.length > 0))
289
+ if (!succeeded(setup.result) || blockers.length > 0)
261
290
  break;
262
291
  }
263
292
  return {
@@ -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.38",
3
+ "version": "0.1.39",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -24,10 +24,11 @@
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-deployment-conformance": "^0.1.12",
31
+ "@tomflow/proflow-deployment-conformance": "^0.1.13",
31
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.",
@@ -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.38",
6
+ "moduleVersion": "0.1.39",
7
7
  "kind": "cli",
8
8
  "templateVersion": "1.0.0",
9
9
  "platformCompatibility": ">=1.0.0 <2.0.0",