@dev.fast/whiteboard 0.0.0-stage → 0.2.0
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/LICENSE +21 -0
- package/README.md +146 -2
- package/THIRD_PARTY_NOTICES.md +93 -0
- package/dist/account-alias-DOHN1RSH.js +973 -0
- package/dist/agent-cli-ChOq0ZuO.js +117 -0
- package/dist/agent-cli-DzWfZr6G.js +2 -0
- package/dist/agent-client-CPQo7iTI.js +305 -0
- package/dist/authoring-tools-Cr6Kpjsx.js +234 -0
- package/dist/build-info.json +1 -0
- package/dist/cli-hVgHcdsv.js +47 -0
- package/dist/cli-runner-D8y2_luU.js +5095 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +128 -0
- package/dist/client-DFoh50W3.js +950 -0
- package/dist/desktop-discovery-DBO-PM2V.js +164 -0
- package/dist/error-message-OtiDonty.js +6 -0
- package/dist/fs-utils-BMPLt0cr.js +22 -0
- package/dist/fuzzy-match-BoAmcyak.js +30 -0
- package/dist/headless-host-BJxf4dUU.js +94 -0
- package/dist/input-error-OLgB_h31.js +11 -0
- package/dist/local-data-CxLk75rx.js +4491 -0
- package/dist/mcp-QeF8vkdf.js +140 -0
- package/dist/package-paths-B6-zxvIO.js +47 -0
- package/dist/process-error-telemetry-v9e5h6D7.js +3952 -0
- package/dist/profile-4ry2f3AM.js +76 -0
- package/dist/profile-Bh5FIrmf.js +2 -0
- package/dist/request-origin-D_4QIdoN.js +811 -0
- package/dist/review-agent-traces-DNJFYzc8.js +3866 -0
- package/dist/review-home-paths-6zZH-1c9.js +2560 -0
- package/dist/review-telemetry-DEFnQsQ2.js +1195 -0
- package/dist/runtime-CKiWhGrf.js +52 -0
- package/dist/runtime.d.ts +9 -0
- package/dist/runtime.js +2 -0
- package/dist/s3-SzLpeCG6.js +345 -0
- package/dist/s3-config-BgxkSoOy.js +2 -0
- package/dist/s3-config-CAxhO9u_.js +676 -0
- package/dist/server/desktop-host.d.ts +4 -0
- package/dist/server/desktop-host.js +1702 -0
- package/dist/server-discovery-Dbzn3w6Z.js +66 -0
- package/dist/sharing/index.d.ts +1977 -0
- package/dist/sharing/index.js +2 -0
- package/dist/src-CGP5ytbV.js +283 -0
- package/dist/src-CmdiBL20.js +3676 -0
- package/dist/src-DnwdaQ2r.js +865 -0
- package/dist/src-X9phtB2j.js +68 -0
- package/dist/stored-document-migration-DHeMpLMb.js +2671 -0
- package/dist/tool-failure-C8zB73HV.js +98 -0
- package/dist/tutorial-trace-DrBvkaeL.js +43 -0
- package/instructions/authoring.md +36 -0
- package/instructions/file-lenses.md +13 -0
- package/instructions/scratchpad.md +23 -0
- package/instructions/trace-archaeology.md +113 -0
- package/onboarding.md +8 -0
- package/package.json +106 -3
- package/src/agent-selection.ts +100 -0
- package/src/agent-session-ref.ts +80 -0
- package/src/ask/agents.ts +351 -0
- package/src/ask/checkout-files.ts +47 -0
- package/src/ask/file-refs.ts +111 -0
- package/src/ask/pi-mcp.ts +154 -0
- package/src/ask/protocol.ts +244 -0
- package/src/ask/thread-state.ts +350 -0
- package/src/ask/thread.ts +1400 -0
- package/src/ask/threads.ts +219 -0
- package/src/ask/watch.ts +79 -0
- package/src/cli-install.ts +1041 -0
- package/src/cli-runner.ts +1461 -0
- package/src/cli-runtime-info.ts +35 -0
- package/src/cli.ts +222 -0
- package/src/connect-prompts.ts +248 -0
- package/src/cursor-deeplink.ts +11 -0
- package/src/desktop-discovery.ts +355 -0
- package/src/diff-selection-migration.ts +63 -0
- package/src/embedded-posthog-key.ts +6 -0
- package/src/error-telemetry.ts +247 -0
- package/src/evidence.ts +14 -0
- package/src/exception-telemetry.ts +126 -0
- package/src/fixtures/blocks/call_stack_diff.json +27 -0
- package/src/fixtures/blocks/callout.json +11 -0
- package/src/fixtures/blocks/code.json +9 -0
- package/src/fixtures/blocks/code_peek.json +8 -0
- package/src/fixtures/blocks/database_lens.json +51 -0
- package/src/fixtures/blocks/divider.json +1 -0
- package/src/fixtures/blocks/fixtures.ts +33 -0
- package/src/fixtures/blocks/flow_diagram.json +36 -0
- package/src/fixtures/blocks/ids.ts +8 -0
- package/src/fixtures/blocks/image.json +9 -0
- package/src/fixtures/blocks/markdown.json +7 -0
- package/src/fixtures/blocks/section.json +11 -0
- package/src/fixtures/blocks/sequence.json +31 -0
- package/src/fixtures/blocks/software_map.json +7 -0
- package/src/fixtures/blocks/trace_quote.json +9 -0
- package/src/fixtures/blocks/tutorial.json +51 -0
- package/src/fs-utils.ts +32 -0
- package/src/fuzzy-match.ts +94 -0
- package/src/install.ts +29 -0
- package/src/legacy-skills.ts +133 -0
- package/src/lens-selection.ts +230 -0
- package/src/markdown-latex-math.ts +230 -0
- package/src/markdown.ts +81 -0
- package/src/package-paths.ts +53 -0
- package/src/posthog-capture-client.ts +610 -0
- package/src/review-api/README.md +295 -0
- package/src/review-api/activity.ts +340 -0
- package/src/review-api/agent-cli.ts +219 -0
- package/src/review-api/agent-client.ts +188 -0
- package/src/review-api/anchor-quotes.ts +88 -0
- package/src/review-api/ask-history.ts +242 -0
- package/src/review-api/authoring-tools.ts +211 -0
- package/src/review-api/blocks/call_stack_diff.ts +75 -0
- package/src/review-api/blocks/callout.ts +24 -0
- package/src/review-api/blocks/code.ts +9 -0
- package/src/review-api/blocks/code_peek.ts +13 -0
- package/src/review-api/blocks/database_lens.ts +150 -0
- package/src/review-api/blocks/definition.ts +40 -0
- package/src/review-api/blocks/divider.ts +6 -0
- package/src/review-api/blocks/flow_diagram.ts +111 -0
- package/src/review-api/blocks/image.ts +10 -0
- package/src/review-api/blocks/index.ts +90 -0
- package/src/review-api/blocks/markdown.ts +17 -0
- package/src/review-api/blocks/section.ts +24 -0
- package/src/review-api/blocks/sequence.ts +56 -0
- package/src/review-api/blocks/software_map.ts +9 -0
- package/src/review-api/blocks/trace_quote.ts +10 -0
- package/src/review-api/blocks/tutorial.ts +39 -0
- package/src/review-api/checkout-fs.ts +14 -0
- package/src/review-api/client.ts +1 -0
- package/src/review-api/comparison-coverage.ts +304 -0
- package/src/review-api/component-reference.ts +30 -0
- package/src/review-api/diff-lenses.ts +175 -0
- package/src/review-api/document-headings.ts +51 -0
- package/src/review-api/document-text.ts +200 -0
- package/src/review-api/document.ts +906 -0
- package/src/review-api/file-lenses.ts +100 -0
- package/src/review-api/http.ts +1877 -0
- package/src/review-api/image-decode.ts +29 -0
- package/src/review-api/input-error.ts +9 -0
- package/src/review-api/instructions.ts +89 -0
- package/src/review-api/lens-alignment.ts +52 -0
- package/src/review-api/local-data.ts +1837 -0
- package/src/review-api/map-input.ts +154 -0
- package/src/review-api/mcp-client-agent.ts +32 -0
- package/src/review-api/mcp.ts +240 -0
- package/src/review-api/origin.ts +43 -0
- package/src/review-api/profile.ts +146 -0
- package/src/review-api/public-tools.ts +102 -0
- package/src/review-api/pull-request.ts +389 -0
- package/src/review-api/read-schemas.ts +85 -0
- package/src/review-api/recovery.ts +2 -0
- package/src/review-api/request-origin.ts +33 -0
- package/src/review-api/review-progress.ts +382 -0
- package/src/review-api/status-tool.ts +10 -0
- package/src/review-api/store-schema.ts +22 -0
- package/src/review-api/store.ts +1647 -0
- package/src/review-api/tool-failure.ts +64 -0
- package/src/review-api/trace-schema.ts +13 -0
- package/src/review-api/traces.ts +119 -0
- package/src/review-api/unsupported-files.integration.ts +91 -0
- package/src/review-api/workspaces.ts +692 -0
- package/src/review-api/worktree-source.ts +170 -0
- package/src/review-api/worktree-structural.integration.ts +320 -0
- package/src/review-app-launcher.ts +432 -0
- package/src/review-app-picker.ts +168 -0
- package/src/review-app.ts +134 -0
- package/src/review-bundled-tools.ts +249 -0
- package/src/review-checkout-paths.ts +37 -0
- package/src/review-diff-files.ts +89 -0
- package/src/review-head-checkout.ts +300 -0
- package/src/review-home-paths.ts +73 -0
- package/src/review-info.ts +59 -0
- package/src/review-instances.ts +113 -0
- package/src/review-logger.ts +183 -0
- package/src/review-preferences.ts +88 -0
- package/src/review-prepare.ts +291 -0
- package/src/review-stack.ts +112 -0
- package/src/review-telemetry.ts +1066 -0
- package/src/runtime.ts +74 -0
- package/src/server/account-alias.ts +30 -0
- package/src/server/bounded-stream.ts +34 -0
- package/src/server/bug-report.ts +301 -0
- package/src/server/client-error-budget.ts +45 -0
- package/src/server/crash-report.ts +273 -0
- package/src/server/desktop-host-shutdown.ts +64 -0
- package/src/server/desktop-host.ts +190 -0
- package/src/server/desktop-server.ts +703 -0
- package/src/server/diffr-config.ts +509 -0
- package/src/server/diffr-languages.ts +97 -0
- package/src/server/global-verb-relay.ts +190 -0
- package/src/server/headless-host.ts +149 -0
- package/src/server/hono-http.ts +162 -0
- package/src/server/http-json.ts +24 -0
- package/src/server/json-review-reporting.ts +174 -0
- package/src/server/process-error-telemetry.ts +174 -0
- package/src/server/review-api-parsers.ts +90 -0
- package/src/server/review-lifecycle-telemetry.ts +73 -0
- package/src/server/review-open-watchdog.ts +46 -0
- package/src/server/review-server-core.ts +290 -0
- package/src/server/structural-comparisons.ts +153 -0
- package/src/server/structural-diff.ts +186 -0
- package/src/server/tutorial-service.ts +241 -0
- package/src/server/ui-telemetry.ts +198 -0
- package/src/server-discovery.ts +95 -0
- package/src/session-markers.ts +132 -0
- package/src/sharing/auth.ts +41 -0
- package/src/sharing/cli.ts +83 -0
- package/src/sharing/client.ts +334 -0
- package/src/sharing/export.ts +195 -0
- package/src/sharing/host.ts +316 -0
- package/src/sharing/import.ts +787 -0
- package/src/sharing/index.ts +16 -0
- package/src/sharing/repository.ts +145 -0
- package/src/sharing/routes.ts +34 -0
- package/src/slug.ts +23 -0
- package/src/software-map-diff-counts.ts +517 -0
- package/src/software-map-model.ts +1147 -0
- package/src/software-map-topology-diff.ts +260 -0
- package/src/source.ts +92 -0
- package/src/startup-trace.ts +232 -0
- package/src/stored-document-migration.ts +151 -0
- package/src/telemetry-clean-text.ts +257 -0
- package/src/telemetry-config.ts +298 -0
- package/src/telemetry-debug-sink.ts +38 -0
- package/src/telemetry.ts +13 -0
- package/src/trace-cli.ts +156 -0
- package/src/trace-storage-cli.ts +509 -0
- package/src/tutorial-conversation.ts +18 -0
- package/src/ui-telemetry-events.ts +765 -0
- package/src/unified-diff.ts +71 -0
- package/src/viewed-coverage.ts +259 -0
- package/src/windows-cli.ts +125 -0
- package/tutorial/document.json +275 -0
- package/tutorial/runtime-manifest.json +12 -0
- package/tutorial/sample-service/package.json +9 -0
- package/tutorial/sample-service/src/api/checkout-api.ts +17 -0
- package/tutorial/sample-service/src/app.ts +26 -0
- package/tutorial/sample-service/src/database/schema.ts +10 -0
- package/tutorial/sample-service/src/fulfillment/fulfillment-queue.ts +15 -0
- package/tutorial/sample-service/src/fulfillment/fulfillment-worker.ts +24 -0
- package/tutorial/sample-service/src/inventory/inventory-service.ts +11 -0
- package/tutorial/sample-service/src/orders/order-service.ts +34 -0
- package/tutorial/sample-service/src/orders/order.ts +21 -0
- package/tutorial/sample-service/src/orders/orders-repository.ts +24 -0
- package/tutorial/sample-service/src/payments/payment-gateway.ts +12 -0
- package/tutorial/sample-service/src/shipping/shipping-gateway.ts +15 -0
- package/tutorial/sample-service/tsconfig.json +12 -0
- package/tutorial/software-map.json +139 -0
- package/tutorial/trace.json +20 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import { jsonString } from "@dev.fast/review-protocol";
|
|
4
|
+
|
|
5
|
+
import { readBuildInfo } from "./package-paths";
|
|
6
|
+
|
|
7
|
+
/** Build identity belongs to the executable, never the current checkout. */
|
|
8
|
+
export function cliRuntimeInfo(
|
|
9
|
+
requestedPath: string,
|
|
10
|
+
effectivePath = requestedPath,
|
|
11
|
+
) {
|
|
12
|
+
const metadata = readBuildInfo(path.dirname(effectivePath));
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
event: "version" as const,
|
|
16
|
+
requestedPath,
|
|
17
|
+
effectivePath,
|
|
18
|
+
delegated: requestedPath !== effectivePath,
|
|
19
|
+
version: jsonString(metadata?.version) ?? null,
|
|
20
|
+
commit: jsonString(metadata?.commit) ?? null,
|
|
21
|
+
dirty:
|
|
22
|
+
metadata?.dirty === true
|
|
23
|
+
? true
|
|
24
|
+
: metadata?.dirty === false
|
|
25
|
+
? false
|
|
26
|
+
: null,
|
|
27
|
+
builtAt: jsonString(metadata?.builtAt) ?? null,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function describeCliRuntime(
|
|
32
|
+
info: ReturnType<typeof cliRuntimeInfo>,
|
|
33
|
+
): string {
|
|
34
|
+
return `CLI: ${info.effectivePath}${info.delegated ? ` (delegated from ${info.requestedPath})` : " (direct)"}\nBuild: ${info.version ?? "unknown"}, commit ${info.commit ?? "unknown"}, dirty ${info.dirty ?? "unknown"}, built ${info.builtAt ?? "unknown"}\n`;
|
|
35
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
jsonNumber,
|
|
11
|
+
jsonObject,
|
|
12
|
+
jsonString,
|
|
13
|
+
parseJsonText,
|
|
14
|
+
} from "@dev.fast/review-protocol";
|
|
15
|
+
|
|
16
|
+
// A standalone build (npx/global install) defers to the CLI bundled with a
|
|
17
|
+
// running Review Desktop so the CLI can never skew from the server it talks
|
|
18
|
+
// to. Checkout runs execute src/cli.ts via tsx and therefore never delegate.
|
|
19
|
+
// Delegation runs before the Node floor check on purpose: the app's
|
|
20
|
+
// Electron-as-Node runtime can rescue a machine whose system Node is too old.
|
|
21
|
+
const argv = process.argv.slice(2);
|
|
22
|
+
|
|
23
|
+
const ownCliPath = fileURLToPath(import.meta.url);
|
|
24
|
+
|
|
25
|
+
const delegatedExitCode = await maybeDelegateToDesktopCli(argv);
|
|
26
|
+
|
|
27
|
+
process.exitCode = delegatedExitCode ?? (await runCli(ownCliPath));
|
|
28
|
+
|
|
29
|
+
async function runCli(effectivePath: string): Promise<number> {
|
|
30
|
+
if (!supportedNodeRuntime()) {
|
|
31
|
+
process.stderr.write(
|
|
32
|
+
`Whiteboard needs Node.js 24 or newer; found ${process.versions.node}. ` +
|
|
33
|
+
"Update Node, or use the whiteboard command installed by Whiteboard Desktop.\n",
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
return 1;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const { runReviewCli } = await import("./cli-runner.js");
|
|
40
|
+
|
|
41
|
+
return runReviewCli({
|
|
42
|
+
argv,
|
|
43
|
+
cliPaths: { requestedPath: ownCliPath, effectivePath },
|
|
44
|
+
stdin: process.stdin,
|
|
45
|
+
stdout: process.stdout,
|
|
46
|
+
stderr: process.stderr,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// An Electron-as-Node runtime (the app's) is trusted as-is: it is the same
|
|
51
|
+
// runtime the Review server runs on. Only a system Node gets the floor check,
|
|
52
|
+
// so an old `node` fails with an instruction instead of a syntax or
|
|
53
|
+
// missing-builtin crash deeper in.
|
|
54
|
+
function supportedNodeRuntime(): boolean {
|
|
55
|
+
if (process.versions.electron) return true;
|
|
56
|
+
|
|
57
|
+
return Number(process.versions.node.split(".")[0]) >= 24;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function maybeDelegateToDesktopCli(
|
|
61
|
+
argv: string[],
|
|
62
|
+
): Promise<number | null> {
|
|
63
|
+
const env = process.env;
|
|
64
|
+
|
|
65
|
+
if (env.DEV_FAST_REVIEW_CLI_NO_DELEGATE || env.DEV_FAST_REVIEW_CLI_DELEGATED)
|
|
66
|
+
return null;
|
|
67
|
+
|
|
68
|
+
// api and mcp are thin HTTP clients whose tool catalog comes from the
|
|
69
|
+
// server, so they cannot skew from it and must not be handed to a bundled
|
|
70
|
+
// CLI that predates them.
|
|
71
|
+
if (
|
|
72
|
+
argv.some(
|
|
73
|
+
(argument) =>
|
|
74
|
+
["api", "mcp", "server", "instances"].includes(argument) ||
|
|
75
|
+
/^--state-dir(?:=|$)/.test(argument),
|
|
76
|
+
) ||
|
|
77
|
+
env.DEV_REVIEW_SERVER_DIR?.trim()
|
|
78
|
+
)
|
|
79
|
+
return null;
|
|
80
|
+
const ownPath = fileURLToPath(import.meta.url);
|
|
81
|
+
|
|
82
|
+
if (!/[\\/]dist[\\/]cli\.js$/.test(ownPath)) return null;
|
|
83
|
+
|
|
84
|
+
// This bootstrap runs before the Node floor check, so it cannot import
|
|
85
|
+
// devReviewHome() from @dev.fast/trace-core: that module graph needs a modern
|
|
86
|
+
// Node. Keep this copy in step with devReviewHome().
|
|
87
|
+
const devHome = env.DEV_REVIEW_HOME?.trim()
|
|
88
|
+
? path.resolve(env.DEV_REVIEW_HOME.trim())
|
|
89
|
+
: path.join(os.homedir(), ".dev");
|
|
90
|
+
|
|
91
|
+
let cliPath: string;
|
|
92
|
+
let runtimePath: string | undefined;
|
|
93
|
+
const discoveryFile = selectedDiscoveryFile(devHome, env);
|
|
94
|
+
|
|
95
|
+
if (!discoveryFile) return null;
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const discovery = jsonObject(
|
|
99
|
+
parseJsonText(readFileSync(discoveryFile, "utf8")),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const discoveredCliPath = jsonString(discovery?.cliPath);
|
|
103
|
+
|
|
104
|
+
if (!discoveredCliPath) return null;
|
|
105
|
+
cliPath = discoveredCliPath;
|
|
106
|
+
const discoveredRuntimePath = jsonString(discovery?.cliRuntimePath);
|
|
107
|
+
|
|
108
|
+
if (discoveredRuntimePath && existsSync(discoveredRuntimePath)) {
|
|
109
|
+
runtimePath = discoveredRuntimePath;
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
if (realpathSync(cliPath) === realpathSync(ownPath)) return null;
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Prefer the app's Electron-as-Node runtime: it matches the server exactly
|
|
122
|
+
// and works even when this process runs on an older system Node.
|
|
123
|
+
const childEnv: NodeJS.ProcessEnv = {
|
|
124
|
+
...env,
|
|
125
|
+
DEV_FAST_REVIEW_CLI_DELEGATED: "1",
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// The current parser owns verbose diagnostics, including usage errors and
|
|
129
|
+
// help, even when the selected Desktop CLI predates this option.
|
|
130
|
+
const command = argv.filter((argument) => argument !== "--json");
|
|
131
|
+
|
|
132
|
+
if (command[0] === "version" && command.includes("--verbose")) {
|
|
133
|
+
return runCli(cliPath);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (runtimePath) childEnv.ELECTRON_RUN_AS_NODE = "1";
|
|
137
|
+
|
|
138
|
+
const result = spawnSync(
|
|
139
|
+
runtimePath ?? process.execPath,
|
|
140
|
+
[cliPath, ...argv],
|
|
141
|
+
{ stdio: "inherit", env: childEnv },
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
if (result.signal) {
|
|
145
|
+
process.kill(process.pid, result.signal);
|
|
146
|
+
|
|
147
|
+
return 1;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return result.status ?? 1;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* The shim's selection, kept in step with it and with selectReviewInstance:
|
|
155
|
+
* DEV_REVIEW_INSTANCE, the machine default, the only live Desktop, then
|
|
156
|
+
* stable, then the pre-instance server.json.
|
|
157
|
+
*/
|
|
158
|
+
function selectedDiscoveryFile(devHome: string, env: NodeJS.ProcessEnv) {
|
|
159
|
+
const desktop = path.join(devHome, "review-desktop");
|
|
160
|
+
const instances = path.join(desktop, "instances");
|
|
161
|
+
let key = env.DEV_REVIEW_INSTANCE?.trim();
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
key ||= readFileSync(path.join(desktop, "default-instance"), "utf8").trim();
|
|
165
|
+
} catch {
|
|
166
|
+
// No machine default.
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Keys name files; the CLI rejects anything else.
|
|
170
|
+
if (key && !/^[A-Za-z0-9_.-]+$/.test(key)) return undefined;
|
|
171
|
+
|
|
172
|
+
// A stable Desktop that predates instances wrote only server.json; it never
|
|
173
|
+
// stands in for any other key.
|
|
174
|
+
const legacy = path.join(desktop, "server.json");
|
|
175
|
+
let stable = path.join(instances, "stable.json");
|
|
176
|
+
|
|
177
|
+
if (!existsSync(stable)) stable = legacy;
|
|
178
|
+
|
|
179
|
+
let selected =
|
|
180
|
+
key === "stable" ? stable : key && path.join(instances, `${key}.json`);
|
|
181
|
+
|
|
182
|
+
if (!selected) {
|
|
183
|
+
let names: string[] = [];
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
names = readdirSync(instances).filter((name) => name.endsWith(".json"));
|
|
187
|
+
} catch {
|
|
188
|
+
// No instance has started yet.
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const live = [
|
|
192
|
+
...names.map((name) => path.join(instances, name)),
|
|
193
|
+
...(stable === legacy ? [legacy] : []),
|
|
194
|
+
].filter(recordIsLive);
|
|
195
|
+
|
|
196
|
+
selected = live.length === 1 ? live[0]! : stable;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return recordIsLive(selected) ? selected : undefined;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function recordIsLive(filePath: string) {
|
|
203
|
+
try {
|
|
204
|
+
const serverPid = jsonNumber(
|
|
205
|
+
jsonObject(parseJsonText(readFileSync(filePath, "utf8")))?.serverPid,
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
return serverPid !== undefined && processIsAlive(serverPid);
|
|
209
|
+
} catch {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function processIsAlive(pid: number) {
|
|
215
|
+
try {
|
|
216
|
+
process.kill(pid, 0);
|
|
217
|
+
|
|
218
|
+
return true;
|
|
219
|
+
} catch (error) {
|
|
220
|
+
return error instanceof Error && "code" in error && error.code === "EPERM";
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { cursorInstallDeeplink } from "./cursor-deeplink";
|
|
2
|
+
import type { InstallTarget } from "./install";
|
|
3
|
+
|
|
4
|
+
/** The macOS and Linux launch form; the Claude and Cursor plugins must match it byte for byte, and the Codex plugin's bin/whiteboard-mcp runs the same command. */
|
|
5
|
+
export const REVIEW_MCP_LAUNCH = {
|
|
6
|
+
command: "sh",
|
|
7
|
+
args: ["-c", 'exec "$HOME/.local/bin/whiteboard" mcp'],
|
|
8
|
+
} as const;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Windows has no sh, and Node-based harnesses cannot start a .cmd file
|
|
12
|
+
* directly. cmd finds whiteboard.cmd on PATH, whether the first-run step or
|
|
13
|
+
* the installer put it there.
|
|
14
|
+
*/
|
|
15
|
+
export const WINDOWS_MCP_LAUNCH = {
|
|
16
|
+
command: "cmd",
|
|
17
|
+
args: ["/d", "/c", "whiteboard", "mcp"],
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
export interface ConnectPromptInput {
|
|
21
|
+
/** False when the serving package has no built CLI (Desktop from source): prompts fall back to a bare `whiteboard`. */
|
|
22
|
+
hasShim: boolean;
|
|
23
|
+
legacyPaths: string[];
|
|
24
|
+
traceEnabled: boolean;
|
|
25
|
+
fffBinaryPath: string;
|
|
26
|
+
fffCorpusRoot: string;
|
|
27
|
+
/** Defaults to this process's platform. */
|
|
28
|
+
platform?: NodeJS.Platform;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const FFF_INSTALL_URL =
|
|
32
|
+
"https://raw.githubusercontent.com/dmtrKovalenko/fff/v0.11.0/install-mcp.sh";
|
|
33
|
+
|
|
34
|
+
export const PI_FFF_PACKAGE = "npm:@ff-labs/pi-fff";
|
|
35
|
+
|
|
36
|
+
export const PI_WHITEBOARD_PACKAGE = "npm:@dev.fast/pi-whiteboard";
|
|
37
|
+
|
|
38
|
+
/** A stdio MCP server launch, as harness configs spell it. */
|
|
39
|
+
export interface McpLaunch {
|
|
40
|
+
command: string;
|
|
41
|
+
args: string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function reviewMcpLaunch(
|
|
45
|
+
hasShim: boolean,
|
|
46
|
+
platform: NodeJS.Platform = process.platform,
|
|
47
|
+
): McpLaunch {
|
|
48
|
+
if (platform === "win32")
|
|
49
|
+
return {
|
|
50
|
+
command: WINDOWS_MCP_LAUNCH.command,
|
|
51
|
+
args: [...WINDOWS_MCP_LAUNCH.args],
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return hasShim
|
|
55
|
+
? {
|
|
56
|
+
command: REVIEW_MCP_LAUNCH.command,
|
|
57
|
+
args: [...REVIEW_MCP_LAUNCH.args],
|
|
58
|
+
}
|
|
59
|
+
: { command: "whiteboard", args: ["mcp"] };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function shellQuote(value: string): string {
|
|
63
|
+
return "'" + value.replaceAll("'", "'\"'\"'") + "'";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function numbered(steps: string[]): string[] {
|
|
67
|
+
return steps.map((step, index) => `${index + 1}. ${step}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function fffSteps(input: ConnectPromptInput, target: InstallTarget): string[] {
|
|
71
|
+
if (!input.traceEnabled) return [];
|
|
72
|
+
|
|
73
|
+
if (target === "pi" || target === "omp") {
|
|
74
|
+
return [`Run: ${target} install ${PI_FFF_PACKAGE}`];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const binary = shellQuote(input.fffBinaryPath);
|
|
78
|
+
const root = shellQuote(input.fffCorpusRoot);
|
|
79
|
+
const add = target === "claude" ? "claude mcp add -s user" : "codex mcp add";
|
|
80
|
+
|
|
81
|
+
return [
|
|
82
|
+
`Unless \`${target} mcp get fff\` finds an existing server, run:\n\n\`\`\`sh\n[ -x ${binary} ] || curl -fsSL ${FFF_INSTALL_URL} | bash\n${add} fff -- ${binary} ${root}\n\`\`\``,
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The Claude plugin's sh launch cannot start on Windows, so register the server directly. */
|
|
87
|
+
export const CLAUDE_WINDOWS_MCP_ADD = `claude mcp add -s user whiteboard -- ${WINDOWS_MCP_LAUNCH.command} ${WINDOWS_MCP_LAUNCH.args.join(" ")}`;
|
|
88
|
+
|
|
89
|
+
/** Copilot CLI loads the Claude plugin, so on Windows it too registers the server directly. */
|
|
90
|
+
export const COPILOT_WINDOWS_MCP_ADD = `copilot mcp add whiteboard -- ${WINDOWS_MCP_LAUNCH.command} ${WINDOWS_MCP_LAUNCH.args.join(" ")}`;
|
|
91
|
+
|
|
92
|
+
function pluginSteps(
|
|
93
|
+
target: Exclude<InstallTarget, "cursor">,
|
|
94
|
+
platform: NodeJS.Platform,
|
|
95
|
+
): string[] {
|
|
96
|
+
switch (target) {
|
|
97
|
+
case "claude":
|
|
98
|
+
if (platform === "win32")
|
|
99
|
+
return [
|
|
100
|
+
`Run:\n\n\`\`\`sh\nclaude plugin uninstall whiteboard@devfast # its launch cannot start on Windows, if installed\nclaude mcp remove -s user whiteboard # old registration, if any\n${CLAUDE_WINDOWS_MCP_ADD}\n\`\`\``,
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
return [
|
|
104
|
+
"Run:\n\n```sh\nclaude plugin marketplace add devdotfast/whiteboard\nclaude plugin install whiteboard@devfast --scope user\nclaude mcp remove -s user whiteboard # old manual registration, if any\n```",
|
|
105
|
+
];
|
|
106
|
+
case "codex":
|
|
107
|
+
return [
|
|
108
|
+
"Run:\n\n```sh\ncodex plugin marketplace add devdotfast/whiteboard\ncodex plugin add whiteboard@devfast\ncodex mcp remove whiteboard # old manual registration, if any\n```",
|
|
109
|
+
];
|
|
110
|
+
case "opencode": {
|
|
111
|
+
const launch = reviewMcpLaunch(true, platform);
|
|
112
|
+
|
|
113
|
+
const entry = JSON.stringify(
|
|
114
|
+
{
|
|
115
|
+
whiteboard: {
|
|
116
|
+
type: "local",
|
|
117
|
+
command: [launch.command, ...launch.args],
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
null,
|
|
121
|
+
2,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
return [
|
|
125
|
+
`On OpenCode 2.0 or later, run:\n\n\`\`\`sh\nopencode mcp add --global whiteboard -- ${launchCommand(launch)}\n\`\`\`\n\nOn older OpenCode, add this entry to "mcp" in ~/.config/opencode/opencode.json, keeping the rest of the file:\n\n\`\`\`json\n${entry}\n\`\`\``,
|
|
126
|
+
`Remove "@dev.fast/opencode-whiteboard" from "plugin" in ~/.config/opencode/opencode.json if it is there.`,
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
case "copilot":
|
|
131
|
+
if (platform === "win32")
|
|
132
|
+
return [
|
|
133
|
+
`Run:\n\n\`\`\`sh\ncopilot plugin uninstall whiteboard@devfast # its launch cannot start on Windows, if installed\ncopilot mcp remove whiteboard # old registration, if any\n${COPILOT_WINDOWS_MCP_ADD}\n\`\`\``,
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
return [
|
|
137
|
+
"Run:\n\n```sh\ncopilot plugin marketplace add devdotfast/whiteboard\ncopilot plugin install whiteboard@devfast\ncopilot mcp remove whiteboard # old manual registration, if any\n```",
|
|
138
|
+
];
|
|
139
|
+
case "pi":
|
|
140
|
+
return [
|
|
141
|
+
`On Pi 0.99.0 or later, run:\n\n\`\`\`sh\npi mcp add whiteboard -- ${launchCommand(reviewMcpLaunch(true, platform))}\npi remove ${PI_WHITEBOARD_PACKAGE} # if installed\n\`\`\`\n\nOn older Pi, run \`pi install ${PI_WHITEBOARD_PACKAGE}\`.`,
|
|
142
|
+
];
|
|
143
|
+
case "omp": {
|
|
144
|
+
const entry = JSON.stringify(
|
|
145
|
+
{ whiteboard: reviewMcpLaunch(true, platform) },
|
|
146
|
+
null,
|
|
147
|
+
2,
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
return [
|
|
151
|
+
`Add this entry to mcpServers in ~/.omp/agent/mcp.json, keeping the rest of the file:\n\n\`\`\`json\n${entry}\n\`\`\``,
|
|
152
|
+
`Run \`omp plugin uninstall @dev.fast/pi-whiteboard\` if installed.`,
|
|
153
|
+
];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function launchCommand(launch: McpLaunch): string {
|
|
159
|
+
return [launch.command, ...launch.args]
|
|
160
|
+
.map((arg) => (/^[\w./-]+$/.test(arg) ? arg : shellQuote(arg)))
|
|
161
|
+
.join(" ");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function cursorSteps(): string[] {
|
|
165
|
+
const launch = reviewMcpLaunch(true);
|
|
166
|
+
const deeplink = cursorInstallDeeplink(launch);
|
|
167
|
+
const entry = JSON.stringify({ whiteboard: launch }, null, 2);
|
|
168
|
+
|
|
169
|
+
return [
|
|
170
|
+
`Do not paste the install link in chat. Chat clients do not open cursor:// links. Open Cursor's MCP install deeplink with the OS URL handler using the command for this operating system, then stop and let me confirm the install:\n\nmacOS: open ${shellQuote(deeplink)}\nLinux: xdg-open ${shellQuote(deeplink)}\nWindows: cmd /c start "" "${deeplink}"`,
|
|
171
|
+
`After I confirm, check for a whiteboard server in ~/.cursor/mcp.json. If the deeplink did not add it, merge this entry into the file's mcpServers object without removing other servers or settings (create the file and object if missing). Do not use this fallback if I declined the install:\n\n\`\`\`json\n${entry}\n\`\`\``,
|
|
172
|
+
];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function connectPrompt(
|
|
176
|
+
target: InstallTarget,
|
|
177
|
+
input: ConnectPromptInput,
|
|
178
|
+
): string {
|
|
179
|
+
if (!input.hasShim) {
|
|
180
|
+
return "Connect this agent to dev.fast Whiteboard.\n\nStop and ask me to install the whiteboard command in Whiteboard Desktop (Settings → Command line), then run this connect command again. The connection requires ~/.local/bin/whiteboard.";
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const extra =
|
|
184
|
+
target === "claude" ||
|
|
185
|
+
target === "codex" ||
|
|
186
|
+
target === "pi" ||
|
|
187
|
+
target === "omp"
|
|
188
|
+
? fffSteps(input, target)
|
|
189
|
+
: [];
|
|
190
|
+
|
|
191
|
+
const verify =
|
|
192
|
+
target === "pi"
|
|
193
|
+
? "Ask me to run /reload, then call `session_get_instructions` on the Whiteboard server (older Pi: `whiteboard api session_get_instructions '{}'`). Do not author anything yet."
|
|
194
|
+
: target === "omp"
|
|
195
|
+
? "Ask me to run /mcp reload, then call `session_get_instructions` on the Whiteboard server. Do not author anything yet."
|
|
196
|
+
: target === "opencode"
|
|
197
|
+
? "Stop and tell me to quit and reopen OpenCode: it loads MCP servers only at startup. After I reopen it, call `session_get_instructions` on the Whiteboard server to confirm the connection. Do not author anything yet."
|
|
198
|
+
: "Reload your MCP tools and call `session_get_instructions` on the Whiteboard server. If a restart is needed, tell me and verify after it. Do not author anything yet.";
|
|
199
|
+
|
|
200
|
+
return [
|
|
201
|
+
"Connect this agent to dev.fast Whiteboard.",
|
|
202
|
+
"",
|
|
203
|
+
...numbered([
|
|
204
|
+
...(input.legacyPaths.length
|
|
205
|
+
? [
|
|
206
|
+
`Delete these old Whiteboard skill folders and plugin files:\n${input.legacyPaths.map((item) => `- ${JSON.stringify(item)}`).join("\n")}`,
|
|
207
|
+
]
|
|
208
|
+
: []),
|
|
209
|
+
...(target === "cursor"
|
|
210
|
+
? cursorSteps()
|
|
211
|
+
: [
|
|
212
|
+
...pluginSteps(target, input.platform ?? process.platform),
|
|
213
|
+
...extra,
|
|
214
|
+
]),
|
|
215
|
+
verify,
|
|
216
|
+
]),
|
|
217
|
+
].join("\n");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function connectSetupPrompt(target: InstallTarget): string {
|
|
221
|
+
return `Run \`whiteboard connect ${target}\` and follow the instructions to connect this agent to Whiteboard.`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function connectSetupPrompts(): Record<InstallTarget, string> {
|
|
225
|
+
return {
|
|
226
|
+
claude: connectSetupPrompt("claude"),
|
|
227
|
+
codex: connectSetupPrompt("codex"),
|
|
228
|
+
cursor: connectSetupPrompt("cursor"),
|
|
229
|
+
opencode: connectSetupPrompt("opencode"),
|
|
230
|
+
pi: connectSetupPrompt("pi"),
|
|
231
|
+
omp: connectSetupPrompt("omp"),
|
|
232
|
+
copilot: connectSetupPrompt("copilot"),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function connectPrompts(
|
|
237
|
+
input: ConnectPromptInput,
|
|
238
|
+
): Record<InstallTarget, string> {
|
|
239
|
+
return {
|
|
240
|
+
claude: connectPrompt("claude", input),
|
|
241
|
+
codex: connectPrompt("codex", input),
|
|
242
|
+
cursor: connectPrompt("cursor", input),
|
|
243
|
+
opencode: connectPrompt("opencode", input),
|
|
244
|
+
pi: connectPrompt("pi", input),
|
|
245
|
+
omp: connectPrompt("omp", input),
|
|
246
|
+
copilot: connectPrompt("copilot", input),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Cursor's one-click MCP install link for Whiteboard's server. */
|
|
2
|
+
export function cursorInstallDeeplink(launch: {
|
|
3
|
+
command: string;
|
|
4
|
+
args: string[];
|
|
5
|
+
}): string {
|
|
6
|
+
const config = Buffer.from(
|
|
7
|
+
JSON.stringify({ command: launch.command, args: launch.args }),
|
|
8
|
+
).toString("base64");
|
|
9
|
+
|
|
10
|
+
return `cursor://anysphere.cursor-deeplink/mcp/install?name=whiteboard&config=${encodeURIComponent(config)}`;
|
|
11
|
+
}
|