From b80de1414bf4629dfc033e3aee1acd75e67c407d Mon Sep 17 00:00:00 2001 From: wuyangfan <1102042793@qq.com> Date: Wed, 27 May 2026 00:34:28 +0800 Subject: [PATCH] fix(tools-dev): print source-install od CLI hints after start After tools-dev starts the daemon on an ephemeral port, print how to run `pnpm exec od` and export OD_DAEMON_URL so source installs do not hit the system BSD `od` binary or the packaged Docker default port. Related to #2801 --- tools/dev/src/cli-hints.ts | 16 ++++++++++++++++ tools/dev/src/index.ts | 32 ++++++++++++++++++++++++++----- tools/dev/tests/cli-hints.test.ts | 27 ++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 tools/dev/src/cli-hints.ts create mode 100644 tools/dev/tests/cli-hints.test.ts diff --git a/tools/dev/src/cli-hints.ts b/tools/dev/src/cli-hints.ts new file mode 100644 index 000000000..7c06faf2d --- /dev/null +++ b/tools/dev/src/cli-hints.ts @@ -0,0 +1,16 @@ +export function formatSourceInstallCliHints(args: { + daemonUrl: string | null; + odBinPath: string; +}): string[] { + if (args.daemonUrl == null) return []; + + return [ + "", + " Source install CLI", + "", + " ➜ Run: pnpm exec od skills list", + ` ➜ URL: export OD_DAEMON_URL=${args.daemonUrl}`, + ` ➜ Note: system 'od' is not Open Design — use pnpm exec od or ${args.odBinPath}`, + "", + ]; +} diff --git a/tools/dev/src/index.ts b/tools/dev/src/index.ts index c2241acb4..585b28ec8 100644 --- a/tools/dev/src/index.ts +++ b/tools/dev/src/index.ts @@ -64,6 +64,7 @@ import { waitForWebRuntime, } from "./sidecar-client.js"; import { ensureDaemonGateForDesktop } from "./desktop-auth-gate.js"; +import { formatSourceInstallCliHints } from "./cli-hints.js"; import { resolveSharedPortsFromRunningState } from "./shared-ports.js"; type CliOptions = ToolDevOptions & { @@ -183,12 +184,28 @@ function printStartSection(result: Partial>, hea } } -function printStartResult(result: Partial>, options: CliOptions, heading = "tools-dev start"): void { +function printSourceInstallCliHints(config: ToolDevConfig, daemonUrl: string | null): void { + for (const line of formatSourceInstallCliHints({ + daemonUrl, + odBinPath: path.join(config.workspaceRoot, "node_modules/.bin/od"), + })) { + process.stdout.write(`${line}\n`); + } +} + +function printStartResult( + result: Partial>, + options: CliOptions, + config: ToolDevConfig, + heading = "tools-dev start", +): void { if (options.json === true) { printJson(result); return; } printStartSection(result, heading); + const daemonStatus = asRecord(asRecord(result.daemon)?.status); + printSourceInstallCliHints(config, stringField(daemonStatus ?? {}, "url")); } function printStopSection(result: Partial>, heading: string): void { @@ -254,7 +271,11 @@ function printStatusResult(result: unknown, options: CliOptions, appName: string process.stdout.write(`- ${appName ?? ALL_APPS.join("/")}: ${formatStatusSummary(result)}\n`); } -function printRunForegroundResult(started: Partial>, options: CliOptions): void { +function printRunForegroundResult( + started: Partial>, + options: CliOptions, + config: ToolDevConfig, +): void { if (options.json === true) { printJson({ mode: "foreground", started }); return; @@ -269,7 +290,8 @@ function printRunForegroundResult(started: Partial (await inspectWebRuntime(runtimeLookup(config)))?.url, }); const started = await runSequential(targets, (target) => startApp(config, target, foregroundOptions, { targets })); - printRunForegroundResult(started, options); + printRunForegroundResult(started, options, config); let shuttingDown = false; const keepAlive = setInterval(() => undefined, 60_000); @@ -1094,7 +1116,7 @@ addPortOptions(addSharedOptions(cli.command("start [app]", "Start daemon, web, d webUrl: async () => (await inspectWebRuntime(runtimeLookup(config)))?.url, }); const result = await runSequential(targets, (target) => startApp(config, target, options, { targets })); - printStartResult(result, options); + printStartResult(result, options, config); }, ); diff --git a/tools/dev/tests/cli-hints.test.ts b/tools/dev/tests/cli-hints.test.ts new file mode 100644 index 000000000..6777643aa --- /dev/null +++ b/tools/dev/tests/cli-hints.test.ts @@ -0,0 +1,27 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; + +import { formatSourceInstallCliHints } from "../src/cli-hints.js"; + +describe("formatSourceInstallCliHints", () => { + it("returns empty output when the daemon URL is unavailable", () => { + assert.deepEqual( + formatSourceInstallCliHints({ + daemonUrl: null, + odBinPath: "/repo/node_modules/.bin/od", + }), + [], + ); + }); + + it("prints pnpm exec od guidance and OD_DAEMON_URL export (#2801)", () => { + const text = formatSourceInstallCliHints({ + daemonUrl: "http://127.0.0.1:49980", + odBinPath: "/repo/node_modules/.bin/od", + }).join("\n"); + + assert.match(text, /pnpm exec od skills list/); + assert.match(text, /export OD_DAEMON_URL=http:\/\/127\.0\.0\.1:49980/); + assert.match(text, /\/repo\/node_modules\/\.bin\/od/); + }); +});