@dev.fast/whiteboard 0.2.5-preview.20261013.113 → 0.2.5-preview.20261015.116
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/THIRD_PARTY_NOTICES.md +29 -0
- package/dist/{account-alias-D0hF0sKk.js → account-alias-ChZR3RIU.js} +94 -8
- package/dist/{agent-cli-04xbDj23.js → agent-cli-CR2PnA9G.js} +9 -12
- package/dist/agent-cli-CRPPmS3l.js +2 -0
- package/dist/{agent-client-P3J4y8Zn.js → agent-client-DVd-prfp.js} +31 -14
- package/dist/{authoring-tools-fdA0RPIT.js → authoring-tools-BXKEwhfi.js} +5 -5
- package/dist/background-server-Cw7CcfaZ.js +156 -0
- package/dist/background-server-D2Rrr_e2.js +2 -0
- package/dist/build-info.json +1 -1
- package/dist/{cli-C0ziP6hg.js → cli-CSrz_wGk.js} +1 -1
- package/dist/{cli-runner-C3men22A.js → cli-runner-PlGqBeMw.js} +368 -252
- package/dist/cli.js +3 -2
- package/dist/{client-DzcSt6pV.js → client-DhZzPePs.js} +5 -3
- package/dist/{process-error-telemetry-BCBYp2t4.js → headless-host-CVXTSIlx.js} +3285 -2585
- package/dist/headless-host-Cp37C-v0.js +2 -0
- package/dist/input-error-OLgB_h31.js +11 -0
- package/dist/{local-data-Dy1pNtLF.js → local-data-yfLp5PiJ.js} +113 -62
- package/dist/{mcp-B8kuRSXM.js → mcp-DBbDwG4y.js} +4 -4
- package/dist/{package-paths-B9PDoAl6.js → package-paths-BsuI4WUf.js} +1 -1
- package/dist/profile-BSOkA6A-.js +2 -0
- package/dist/{profile-tbGWuWHu.js → profile-pAzg7fxC.js} +3 -3
- package/dist/remote-attach-BAt2eZx3.js +137 -0
- package/dist/remote-extensions-2Cxf3hMo.js +2 -0
- package/dist/remote-extensions-BiNx6NQK.js +323 -0
- package/dist/remote-language-server-DQgWUNeV.js +252 -0
- package/dist/remote-uninstall-DLtDG0o7.js +153 -0
- package/dist/{request-origin-DA4K1qWY.js → request-origin-DaRIV3cB.js} +5 -2
- package/dist/review-app-launcher-D7I4DGoH.js +222 -0
- package/dist/{review-home-paths-Y9mYWkCI.js → review-home-paths-zwevIq4I.js} +7 -7
- package/dist/{review-telemetry-CmM6tCbj.js → review-telemetry-BqB4CiTD.js} +5 -5
- package/dist/{s3-SzLpeCG6.js → s3-77-qkEhs.js} +1 -1
- package/dist/{s3-config-BgxkSoOy.js → s3-config-0xuCh-F_.js} +1 -1
- package/dist/{s3-config-CAxhO9u_.js → s3-config-DDWWMh5x.js} +5 -1
- package/dist/server/desktop-host.js +1663 -266
- package/dist/{desktop-discovery-Ba3dD5Cn.js → server-discovery-CifQ3yQS.js} +88 -3
- package/dist/sharing/index.d.ts +88 -32
- package/dist/sharing/index.js +1 -1
- package/dist/{src-DiHP11l3.js → src-BMPuxWv4.js} +6 -11
- package/dist/{src-WCbabvdS.js → src-Bgvf1sEN.js} +128 -8
- package/dist/{stored-document-migration-u4L_1VZa.js → stored-document-migration-CFOvjRwW.js} +5 -18
- package/dist/{tool-failure-CJHBCHMh.js → tool-failure-BXWzQG8T.js} +2 -2
- package/instructions/authoring.md +16 -12
- package/instructions/file-lenses.md +4 -2
- package/package.json +8 -13
- package/src/agent-connect.ts +507 -0
- package/src/agent-selection.ts +4 -0
- package/src/ask/agents.ts +59 -13
- package/src/ask/pi-mcp.ts +7 -3
- package/src/ask/thread-state.ts +9 -4
- package/src/ask/thread.ts +88 -50
- package/src/ask/threads.ts +53 -7
- package/src/cli-install.ts +3 -1
- package/src/cli-runner.ts +472 -81
- package/src/cli.ts +1 -1
- package/src/connect-agents.ts +285 -0
- package/src/connect-prompts.ts +60 -6
- package/src/desktop-discovery.ts +24 -0
- package/src/pinned-download.ts +118 -0
- package/src/remote-attach.ts +130 -0
- package/src/remote-extensions.ts +359 -0
- package/src/remote-language-server.ts +428 -0
- package/src/remote-toolchains.ts +107 -0
- package/src/remote-uninstall.ts +280 -0
- package/src/review-api/README.md +1 -3
- package/src/review-api/agent-cli.ts +4 -4
- package/src/review-api/agent-client.ts +23 -0
- package/src/review-api/ask-history.ts +31 -16
- package/src/review-api/authoring-tools.ts +1 -1
- package/src/review-api/blocks/index.ts +2 -4
- package/src/review-api/document.ts +1 -4
- package/src/review-api/http.ts +265 -237
- package/src/review-api/local-data.ts +18 -3
- package/src/review-api/onboarding.ts +478 -0
- package/src/review-api/origin.ts +7 -0
- package/src/review-api/recent-folders.ts +173 -0
- package/src/review-api/store.ts +56 -10
- package/src/review-api/workspaces.ts +56 -11
- package/src/review-app-launcher.ts +61 -3
- package/src/review-stack.ts +162 -36
- package/src/server/background-server.ts +250 -0
- package/src/server/desktop-host-shutdown.ts +24 -0
- package/src/server/desktop-host.ts +39 -24
- package/src/server/desktop-server.ts +68 -118
- package/src/server/global-verb-relay.ts +16 -0
- package/src/server/headless-host.ts +128 -3
- package/src/server/hono-http.ts +6 -1
- package/src/server/legacy-tutorial-cleanup.ts +38 -0
- package/src/server/review-gateway-hosts.ts +379 -0
- package/src/server/review-gateway-list.ts +109 -0
- package/src/server/review-gateway-memory.ts +130 -0
- package/src/server/review-gateway-pushes.ts +142 -0
- package/src/server/review-gateway-streams.ts +415 -0
- package/src/server/review-gateway-transport.ts +204 -0
- package/src/server/review-gateway.ts +433 -0
- package/src/server/review-server-core.ts +36 -6
- package/src/server-discovery.ts +11 -0
- package/src/sharing/import.ts +1 -0
- package/src/ui-telemetry-events.ts +5 -1
- package/src/vsix.ts +122 -0
- package/dist/agent-cli-BR_Jw5BL.js +0 -2
- package/dist/headless-host-BLLJ7Dyu.js +0 -95
- package/dist/profile-aMSw1oCM.js +0 -2
- package/dist/server-discovery-ZVi8Ay8J.js +0 -66
- package/src/fixtures/blocks/tutorial.json +0 -14
- package/src/review-api/blocks/tutorial.ts +0 -23
- package/src/server/tutorial-service.ts +0 -217
- package/tutorial/document.json +0 -216
- package/tutorial/lenses.json +0 -10
- package/tutorial/runtime-manifest.json +0 -11
- package/tutorial/sample-service/package.json +0 -9
- package/tutorial/sample-service/src/api/checkout-api.ts +0 -17
- package/tutorial/sample-service/src/app.ts +0 -26
- package/tutorial/sample-service/src/database/drizzle-orm.d.ts +0 -16
- package/tutorial/sample-service/src/database/schema.ts +0 -9
- package/tutorial/sample-service/src/fulfillment/fulfillment-queue.ts +0 -15
- package/tutorial/sample-service/src/fulfillment/fulfillment-worker.ts +0 -24
- package/tutorial/sample-service/src/inventory/forklift-mood.ts +0 -4
- package/tutorial/sample-service/src/inventory/inventory-service.ts +0 -28
- package/tutorial/sample-service/src/inventory/warehouse-lore.ts +0 -11
- package/tutorial/sample-service/src/orders/order-service.ts +0 -35
- package/tutorial/sample-service/src/orders/order.ts +0 -43
- package/tutorial/sample-service/src/orders/orders-repository.ts +0 -24
- package/tutorial/sample-service/src/payments/fraud-vibes.ts +0 -4
- package/tutorial/sample-service/src/payments/payment-gateway.ts +0 -29
- package/tutorial/sample-service/src/payments/raccoon-ledger.ts +0 -9
- package/tutorial/sample-service/src/shipping/shipping-gateway.ts +0 -15
- package/tutorial/sample-service/tsconfig.json +0 -12
|
@@ -1,27 +1,31 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as setLocalVcsCommandObserver, E as __toESM, T as __require, w as __commonJSMin } from "./src-BMPuxWv4.js";
|
|
2
2
|
import { c as jsonString, n as isJsonObject, u as parseJsonText } from "./src-X9phtB2j.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { o as ReviewApiClient, s as ReviewApiError } from "./agent-client-
|
|
3
|
+
import { _ as REVIEW_REMOTE_AGENT_IDS, p as REVIEW_CLI_INSTALL_TARGET_LABELS, v as REVIEW_REMOTE_ATTACH_BEGIN, y as REVIEW_REMOTE_ATTACH_END } from "./src-Bgvf1sEN.js";
|
|
4
|
+
import { A as runTraceDeny, E as traceMachineStatus, F as describeSelection, H as resolveTraceRepositoryTarget, J as HOSTED_CAPTURE_SCOPE_DESCRIPTION, K as traceRepositoryStatus, L as loadS3TraceStorage, M as runTraceSessions, N as runTraceStoreDelete, P as runTraceStoreInfo, T as traceMachineEnabled, V as requireTraceConsent, X as agentTraceHomeDirectory, _ as runTraceRepair, _t as jsonRequestedInArgv, b as runTraceGitHook, c as registerTraceCommands, ct as runStoreLogin, d as runTraceList$1, dt as StoreApiError, et as resolveTraceCommand, f as runTracePull$1, ft as StoreClient, g as runTraceInstallMachine, gt as humanStream, h as runTraceEnable, ht as failWithJsonError, it as DEFAULT_STORE_ORIGIN, j as runTraceOnboard, k as runTraceAllow, l as resolveTraceReadStorage, lt as runStoreLogout, m as runTraceDisable, mt as emitJsonEvent, nt as traceHomeDir, p as runTraceShow, pt as withStoreAuthorization, q as readTraceUserConfig, rt as traceScope, st as readStoreAuth, u as runTraceBlame, ut as runStoreWhoami, v as runTraceStatus, w as readLegacyCaptureSettings, x as runTraceHook, y as runTraceSync, z as selectTraceStorage } from "./review-home-paths-zwevIq4I.js";
|
|
5
|
+
import { o as ReviewApiClient, s as ReviewApiError } from "./agent-client-DVd-prfp.js";
|
|
6
6
|
import { t as resolveReviewRoot } from "./runtime-CKiWhGrf.js";
|
|
7
|
-
import {
|
|
7
|
+
import { C as s3ProfileSchema, E as writeTraceConfigFile, M as devReviewHome, N as normalizeStoreOrigin, S as readTraceConfigFile, T as sameS3Profile, _ as emptyTraceConfig, c as resolveS3Setup, g as currentStore, h as TraceConfigurationError, i as isS3MockMode, n as clearTraceEnvCache, o as readTraceEnvFile, p as traceSettingsPath, w as s3Store } from "./s3-config-DDWWMh5x.js";
|
|
8
8
|
import { t as errorMessage } from "./error-message-OtiDonty.js";
|
|
9
|
-
import { i as readReviewPackageVersion, r as readBuildInfo, t as findReviewPackageRoot } from "./package-paths-
|
|
10
|
-
import { a as
|
|
11
|
-
import { t as
|
|
12
|
-
import {
|
|
9
|
+
import { i as readReviewPackageVersion, r as readBuildInfo, t as findReviewPackageRoot } from "./package-paths-BsuI4WUf.js";
|
|
10
|
+
import { a as readReviewServerHealth, b as selectReviewInstance, c as reviewServerStateDir, d as ReviewInstanceUnavailableError, f as clearDefaultReviewInstance, g as requireHealthyReviewDesktop, h as readDefaultReviewInstance, i as readReviewServerDiscovery, l as serverNotReady, m as healthyReviewInstance, p as desktopUsesHome, t as DESKTOP_TAKEOVER_SIGNAL, u as REVIEW_INSTANCE_ENV, x as writeDefaultReviewInstance, y as reviewInstanceUnavailable } from "./server-discovery-CifQ3yQS.js";
|
|
11
|
+
import { t as isDirectory } from "./fs-utils-BMPLt0cr.js";
|
|
12
|
+
import { t as ReviewTelemetry } from "./review-telemetry-BqB4CiTD.js";
|
|
13
|
+
import { _ as connectPrompts, a as isOwnedShim, f as windowsInstallerCommand, g as CONNECT_COMMANDS, h as isInstallTarget, m as ALL_INSTALL_TARGETS, o as pathShimPath, p as scanLegacySkills, t as aliasInstallationToAccount } from "./account-alias-ChZR3RIU.js";
|
|
13
14
|
import { t as fuzzyRank } from "./fuzzy-match-BoAmcyak.js";
|
|
14
|
-
import { n as
|
|
15
|
-
import { t as reviewAgentCliHelp } from "./agent-cli-
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
15
|
+
import { n as focusReviewDesktop, r as runReviewAppLaunch } from "./review-app-launcher-D7I4DGoH.js";
|
|
16
|
+
import { t as reviewAgentCliHelp } from "./agent-cli-CR2PnA9G.js";
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
import { constants, existsSync, mkdirSync, renameSync, writeFileSync } from "node:fs";
|
|
19
|
+
import { access, readFile, realpath } from "node:fs/promises";
|
|
18
20
|
import path from "node:path";
|
|
19
|
-
import os
|
|
21
|
+
import os from "node:os";
|
|
20
22
|
import { spawn } from "node:child_process";
|
|
21
23
|
import { fileURLToPath } from "node:url";
|
|
24
|
+
import { Writable } from "node:stream";
|
|
22
25
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
23
26
|
import { Argument, Command, CommanderError, Option } from "commander";
|
|
24
27
|
import { emitKeypressEvents } from "node:readline";
|
|
28
|
+
import { parse } from "smol-toml";
|
|
25
29
|
import { createInterface as createInterface$1 } from "node:readline/promises";
|
|
26
30
|
import pino from "pino";
|
|
27
31
|
import { performance } from "node:perf_hooks";
|
|
@@ -44,213 +48,155 @@ function describeCliRuntime(info) {
|
|
|
44
48
|
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`;
|
|
45
49
|
}
|
|
46
50
|
//#endregion
|
|
47
|
-
//#region src/
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
]
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
//#endregion
|
|
61
|
-
//#region src/review-app-launcher.ts
|
|
62
|
-
const RELEASE_APPS = {
|
|
63
|
-
stable: {
|
|
64
|
-
bundleId: "dev.fast.review",
|
|
65
|
-
linuxLauncher: "/usr/bin/review-desktop"
|
|
66
|
-
},
|
|
67
|
-
preview: {
|
|
68
|
-
bundleId: "dev.fast.review.preview",
|
|
69
|
-
linuxLauncher: "/usr/bin/review-preview-desktop"
|
|
51
|
+
//#region src/connect-agents.ts
|
|
52
|
+
const AGENT_CONNECT_TARGETS = REVIEW_REMOTE_AGENT_IDS;
|
|
53
|
+
async function detectAgents(scope) {
|
|
54
|
+
const found = [];
|
|
55
|
+
for (const id of AGENT_CONNECT_TARGETS) {
|
|
56
|
+
if (!await isDirectory(agentTraceHomeDirectory(id, scope.homeDir, scope.env))) continue;
|
|
57
|
+
const manual = !await onPath(CONNECT_COMMANDS[id][0].argv[0], scope.env);
|
|
58
|
+
found.push({
|
|
59
|
+
id,
|
|
60
|
+
name: REVIEW_CLI_INSTALL_TARGET_LABELS[id],
|
|
61
|
+
connected: await connected(id, scope),
|
|
62
|
+
...manual && { manual: true }
|
|
63
|
+
});
|
|
70
64
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const selection = await runtime.selectInstance();
|
|
89
|
-
const current = healthyReviewInstance(selection);
|
|
90
|
-
if (current) {
|
|
91
|
-
if (input.focus) await runtime.focusDesktop(current);
|
|
92
|
-
return launchEvent("running", current.instanceId);
|
|
93
|
-
}
|
|
94
|
-
const running = selection.instances.filter((instance) => instance.healthy);
|
|
95
|
-
if (selection.key !== "stable" && selection.key !== "preview") throw new Error(`${reviewInstanceStartHint(selection)}; \`whiteboard app launch\` starts only installed apps.`);
|
|
96
|
-
if (selection.source === "fallback" && running.length > 1) throw reviewInstanceUnavailable(selection);
|
|
97
|
-
const launch = {
|
|
98
|
-
focus: input.focus,
|
|
99
|
-
noSandbox: input.noSandbox
|
|
100
|
-
};
|
|
101
|
-
if (selection.source !== "fallback") launch.instance = {
|
|
102
|
-
key: selection.key,
|
|
103
|
-
appPath: selection.instance?.discovery.appPath
|
|
104
|
-
};
|
|
105
|
-
const attempt = runtime.launchDesktop(launch);
|
|
106
|
-
let completion = observedCompletion(attempt);
|
|
107
|
-
completion.catch(() => void 0);
|
|
108
|
-
const deadline = runtime.now() + (input.timeoutMs ?? DEFAULT_LAUNCH_TIMEOUT_MS);
|
|
109
|
-
let unexpectedSuccessfulExitAt;
|
|
110
|
-
while (runtime.now() < deadline) {
|
|
111
|
-
const ready = healthyReviewInstance(await runtime.selectInstance());
|
|
112
|
-
if (ready) return launchEvent("launched", ready.instanceId);
|
|
113
|
-
if (unexpectedSuccessfulExitAt !== void 0 && runtime.now() - unexpectedSuccessfulExitAt >= EARLY_EXIT_GRACE_MS) throw launchFailure(attempt, /* @__PURE__ */ new Error("the launch process exited before Desktop became ready"));
|
|
114
|
-
const remaining = Math.max(0, deadline - runtime.now());
|
|
115
|
-
const outcome = completion ? await Promise.race([completion.then((result) => ({ completion: result })), runtime.wait(Math.min(POLL_INTERVAL_MS, remaining)).then(() => ({ completion: null }))]) : await runtime.wait(Math.min(POLL_INTERVAL_MS, remaining)).then(() => ({ completion: null }));
|
|
116
|
-
if (outcome.completion) {
|
|
117
|
-
assertSuccessfulLaunchCompletion(attempt, outcome.completion);
|
|
118
|
-
unexpectedSuccessfulExitAt = runtime.now();
|
|
119
|
-
completion = void 0;
|
|
65
|
+
return found;
|
|
66
|
+
}
|
|
67
|
+
async function connectAgents(input) {
|
|
68
|
+
const results = [];
|
|
69
|
+
for (const id of input.agents) {
|
|
70
|
+
const deadline = Date.now() + (input.timeoutMs ?? 12e4);
|
|
71
|
+
let output = "";
|
|
72
|
+
const commands = CONNECT_COMMANDS[id];
|
|
73
|
+
for (const { argv, ifAny } of commands) {
|
|
74
|
+
const left = deadline - Date.now();
|
|
75
|
+
if (left <= 0) break;
|
|
76
|
+
const step = await run(argv, input.env, left);
|
|
77
|
+
output += `$ ${argv.join(" ")}\n${step.output}`;
|
|
78
|
+
if (!step.ok && !ifAny) {
|
|
79
|
+
output += `Stopped: ${argv.join(" ")} failed.\n`;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
120
82
|
}
|
|
83
|
+
results.push({
|
|
84
|
+
id,
|
|
85
|
+
name: REVIEW_CLI_INSTALL_TARGET_LABELS[id],
|
|
86
|
+
connected: await connected(id, input),
|
|
87
|
+
output: output.slice(-4e3)
|
|
88
|
+
});
|
|
121
89
|
}
|
|
122
|
-
|
|
123
|
-
throw new Error(`Whiteboard Desktop did not become ready within ${Math.ceil((input.timeoutMs ?? DEFAULT_LAUNCH_TIMEOUT_MS) / 1e3)} seconds after ${attempt.method}. Open Whiteboard Desktop once, then run \`whiteboard app launch\` again.${launchDiagnostics(attempt.logPath)}`);
|
|
124
|
-
}
|
|
125
|
-
async function focusReviewDesktop(discovery, fetch) {
|
|
126
|
-
const response = await fetch(`${discovery.url}/app/focus`, {
|
|
127
|
-
method: "POST",
|
|
128
|
-
headers: { "x-review-token": discovery.token },
|
|
129
|
-
signal: AbortSignal.timeout(5e3)
|
|
130
|
-
});
|
|
131
|
-
const result = parseReviewVerbResponse(await response.json());
|
|
132
|
-
if (!response.ok || !result.ok) throw new Error(result.ok ? `Whiteboard Desktop focus returned ${response.status}.` : result.error);
|
|
90
|
+
return results;
|
|
133
91
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const profileArgs = stateRoot ? [`--user-data-dir=${path.resolve(stateRoot, "user-data")}`, `--extensions-dir=${path.resolve(stateRoot, "extensions")}`] : [];
|
|
155
|
-
let command = "/usr/bin/open";
|
|
156
|
-
let method;
|
|
157
|
-
let args;
|
|
158
|
-
if (platform === "linux") {
|
|
159
|
-
delete env.VSCODE_DEV;
|
|
160
|
-
delete env.VSCODE_CLI;
|
|
161
|
-
command = (input.instance ? "" : env.DEV_FAST_REVIEW_DESKTOP_COMMAND?.trim()) || release.linuxLauncher;
|
|
162
|
-
method = `the installed Linux launcher at "${command}"`;
|
|
163
|
-
if (electron && !env.DEV_FAST_REVIEW_DESKTOP_COMMAND?.trim()) {
|
|
164
|
-
command = execPath;
|
|
165
|
-
method = `the Desktop-managed bundle at "${command}"`;
|
|
166
|
-
}
|
|
167
|
-
args = profileArgs;
|
|
168
|
-
if (input.noSandbox) args.push("--no-sandbox");
|
|
169
|
-
} else {
|
|
170
|
-
const appPath = input.instance ? input.instance.appPath : electron ? execPath.match(/^(.*?\.app)\/Contents\/MacOS\//)?.[1] : void 0;
|
|
171
|
-
const target = appPath?.endsWith(".app") ? ["-a", appPath] : ["-b", release.bundleId];
|
|
172
|
-
method = appPath?.endsWith(".app") ? `the macOS application at "${appPath}"` : `the macOS bundle identifier "${release.bundleId}"`;
|
|
173
|
-
args = [
|
|
174
|
-
"-n",
|
|
175
|
-
"-W",
|
|
176
|
-
...focus ? [] : ["-g"],
|
|
177
|
-
...target
|
|
178
|
-
];
|
|
179
|
-
for (const [key, value] of Object.entries(env)) if (value !== void 0 && /^DEV_(REVIEW|FAST)_/.test(key)) args.push("--env", `${key}=${value}`);
|
|
180
|
-
if (profileArgs.length > 0) args.push("--args", ...profileArgs);
|
|
92
|
+
const WHITEBOARD_PLUGIN = "whiteboard@devfast";
|
|
93
|
+
const ClaudePluginsSchema = z.object({ plugins: z.record(z.string(), z.unknown()) });
|
|
94
|
+
const CodexConfigSchema = z.object({ plugins: z.record(z.string(), z.unknown()).optional() });
|
|
95
|
+
const CodexPluginSchema = z.object({ enabled: z.boolean().optional() });
|
|
96
|
+
const PiSettingsSchema = z.object({ packages: z.array(z.union([z.string(), z.object({ source: z.string() }).transform((entry) => entry.source)])) });
|
|
97
|
+
const PI_PACKAGE = /^npm:@dev\.fast\/pi-whiteboard(@.*)?$/;
|
|
98
|
+
const OPENCODE_PLUGIN = /"plugin"\s*:\s*\[[^\]]*"@dev\.fast\/opencode-whiteboard(@[^"]*)?"/;
|
|
99
|
+
const OpenCodeConfigSchema = z.object({ mcp: z.object({
|
|
100
|
+
whiteboard: z.unknown().optional(),
|
|
101
|
+
servers: z.object({ whiteboard: z.unknown().optional() }).optional()
|
|
102
|
+
}) });
|
|
103
|
+
/** OpenCode's config is JSONC: drop comments and trailing commas, keep strings. */
|
|
104
|
+
const JSONC_EXTRAS = /("(?:\\.|[^"\\])*")|\/\/[^\n]*|\/\*[\s\S]*?\*\/|,(?=\s*[}\]])/g;
|
|
105
|
+
function openCodeConnected(text) {
|
|
106
|
+
if (OPENCODE_PLUGIN.test(text)) return true;
|
|
107
|
+
try {
|
|
108
|
+
const { mcp } = OpenCodeConfigSchema.parse(JSON.parse(text.replace(JSONC_EXTRAS, "$1")));
|
|
109
|
+
return mcp.whiteboard !== void 0 || mcp.servers?.whiteboard !== void 0;
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
181
112
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
113
|
+
}
|
|
114
|
+
async function connected(id, scope) {
|
|
115
|
+
const home = agentTraceHomeDirectory(id, scope.homeDir, scope.env);
|
|
116
|
+
const read = (...parts) => readFile(path.join(home, ...parts), "utf8");
|
|
185
117
|
try {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
118
|
+
switch (id) {
|
|
119
|
+
case "claude": {
|
|
120
|
+
const installs = ClaudePluginsSchema.parse(JSON.parse(await read("plugins", "installed_plugins.json"))).plugins[WHITEBOARD_PLUGIN];
|
|
121
|
+
return Array.isArray(installs) ? installs.length > 0 : Boolean(installs);
|
|
122
|
+
}
|
|
123
|
+
case "codex": {
|
|
124
|
+
const entry = CodexConfigSchema.parse(parse(await read("config.toml"))).plugins?.[WHITEBOARD_PLUGIN];
|
|
125
|
+
return entry !== void 0 && CodexPluginSchema.parse(entry).enabled !== false;
|
|
126
|
+
}
|
|
127
|
+
case "opencode":
|
|
128
|
+
for (const name of [
|
|
129
|
+
"opencode.json",
|
|
130
|
+
"opencode.jsonc",
|
|
131
|
+
"config.json"
|
|
132
|
+
]) if (openCodeConnected(await read(name).catch(() => ""))) return true;
|
|
133
|
+
return false;
|
|
134
|
+
case "pi": return PiSettingsSchema.parse(JSON.parse(await read("agent", "settings.json"))).packages.some((entry) => PI_PACKAGE.test(entry));
|
|
189
135
|
}
|
|
190
|
-
|
|
191
|
-
|
|
136
|
+
} catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function onPath(command, env) {
|
|
141
|
+
for (const directory of (env.PATH ?? "").split(path.delimiter)) {
|
|
142
|
+
if (!path.isAbsolute(directory)) continue;
|
|
143
|
+
const candidate = path.join(directory, command);
|
|
144
|
+
try {
|
|
145
|
+
await access(candidate, constants.X_OK);
|
|
146
|
+
return candidate;
|
|
147
|
+
} catch {}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async function run(argv, env, timeoutMs) {
|
|
151
|
+
const [command = "", ...args] = argv;
|
|
152
|
+
const file = await onPath(command, env);
|
|
153
|
+
if (!file) return {
|
|
154
|
+
output: `${command} was not found on PATH.\n`,
|
|
155
|
+
ok: false
|
|
156
|
+
};
|
|
157
|
+
return new Promise((resolve) => {
|
|
158
|
+
let output = "";
|
|
159
|
+
const keep = (chunk) => {
|
|
160
|
+
output = (output + chunk.toString("utf8")).slice(-4e3);
|
|
161
|
+
};
|
|
162
|
+
const child = spawn(file, args, {
|
|
192
163
|
env,
|
|
193
|
-
stdio:
|
|
164
|
+
stdio: [
|
|
194
165
|
"ignore",
|
|
195
|
-
"
|
|
196
|
-
|
|
197
|
-
]
|
|
166
|
+
"pipe",
|
|
167
|
+
"pipe"
|
|
168
|
+
],
|
|
169
|
+
detached: true
|
|
198
170
|
});
|
|
199
|
-
|
|
200
|
-
|
|
171
|
+
const timer = setTimeout(() => {
|
|
172
|
+
output += `\n${command} did not finish within ${Math.round(timeoutMs / 1e3)} seconds.\n`;
|
|
173
|
+
try {
|
|
174
|
+
process.kill(-child.pid, "SIGKILL");
|
|
175
|
+
} catch {
|
|
176
|
+
child.kill("SIGKILL");
|
|
177
|
+
}
|
|
178
|
+
}, timeoutMs);
|
|
179
|
+
child.stdout.on("data", keep);
|
|
180
|
+
child.stderr.on("data", keep);
|
|
181
|
+
child.once("error", (error) => {
|
|
182
|
+
clearTimeout(timer);
|
|
201
183
|
resolve({
|
|
202
|
-
|
|
203
|
-
|
|
184
|
+
output: `${output}${error.message}\n`,
|
|
185
|
+
ok: false
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
child.once("close", (code) => {
|
|
189
|
+
clearTimeout(timer);
|
|
190
|
+
resolve(code === 0 ? {
|
|
191
|
+
output,
|
|
192
|
+
ok: true
|
|
193
|
+
} : {
|
|
194
|
+
output: `${output}${output.endsWith("\n") || !output ? "" : "\n"}(exit ${code ?? "signal"})\n`,
|
|
195
|
+
ok: false
|
|
204
196
|
});
|
|
205
197
|
});
|
|
206
|
-
child.unref();
|
|
207
|
-
} catch (error) {
|
|
208
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
209
|
-
} finally {
|
|
210
|
-
if (logFd !== void 0) closeSync(logFd);
|
|
211
|
-
}
|
|
212
|
-
return {
|
|
213
|
-
method,
|
|
214
|
-
logPath,
|
|
215
|
-
completion
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
function launchEvent(state, instanceId) {
|
|
219
|
-
return {
|
|
220
|
-
event: "app",
|
|
221
|
-
action: "launch",
|
|
222
|
-
state,
|
|
223
|
-
instanceId
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
function launchFailure(attempt, error) {
|
|
227
|
-
return /* @__PURE__ */ new Error(`Could not launch Whiteboard with ${attempt.method}: ${error.message}. Open Whiteboard once, then run \`whiteboard app launch\` again. A sandboxed agent must run it outside the sandbox.${launchDiagnostics(attempt.logPath)}`);
|
|
228
|
-
}
|
|
229
|
-
function assertSuccessfulLaunchCompletion(attempt, completion) {
|
|
230
|
-
if (completion.code === 0 && !completion.signal) return;
|
|
231
|
-
throw launchFailure(attempt, /* @__PURE__ */ new Error(completion.signal ? `the launch process exited on ${completion.signal}` : `the launch process exited with code ${completion.code ?? "unknown"}`));
|
|
232
|
-
}
|
|
233
|
-
function observedCompletion(attempt) {
|
|
234
|
-
return attempt.completion.catch((error) => {
|
|
235
|
-
throw launchFailure(attempt, error instanceof Error ? error : new Error(String(error)));
|
|
236
198
|
});
|
|
237
199
|
}
|
|
238
|
-
function launchDiagnostics(logPath) {
|
|
239
|
-
if (!logPath) return "";
|
|
240
|
-
let tail = "";
|
|
241
|
-
try {
|
|
242
|
-
const fd = openSync(logPath, "r");
|
|
243
|
-
try {
|
|
244
|
-
const size = fstatSync(fd).size;
|
|
245
|
-
const buffer = Buffer.alloc(Math.min(size, 16384));
|
|
246
|
-
const read = readSync(fd, buffer, 0, buffer.length, size - buffer.length);
|
|
247
|
-
tail = buffer.subarray(0, read).toString("utf8").trim().split("\n").slice(-20).join("\n");
|
|
248
|
-
} finally {
|
|
249
|
-
closeSync(fd);
|
|
250
|
-
}
|
|
251
|
-
} catch {}
|
|
252
|
-
return `\nDesktop log: ${logPath}${tail ? `\n${tail}` : ""}`;
|
|
253
|
-
}
|
|
254
200
|
//#endregion
|
|
255
201
|
//#region src/review-app-picker.ts
|
|
256
202
|
const MAX_VISIBLE_ROWS = 10;
|
|
@@ -858,7 +804,7 @@ var require_pump = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
858
804
|
//#endregion
|
|
859
805
|
//#region ../../node_modules/split2/index.js
|
|
860
806
|
var require_split2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
861
|
-
const { Transform: Transform$
|
|
807
|
+
const { Transform: Transform$2 } = __require("stream");
|
|
862
808
|
const { StringDecoder } = __require("string_decoder");
|
|
863
809
|
const kLast = Symbol("last");
|
|
864
810
|
const kDecoder = Symbol("decoder");
|
|
@@ -929,7 +875,7 @@ var require_split2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
929
875
|
options.transform = transform;
|
|
930
876
|
options.flush = flush;
|
|
931
877
|
options.readableObjectMode = true;
|
|
932
|
-
const stream = new Transform$
|
|
878
|
+
const stream = new Transform$2(options);
|
|
933
879
|
stream[kLast] = "";
|
|
934
880
|
stream[kDecoder] = new StringDecoder("utf8");
|
|
935
881
|
stream.matcher = matcher;
|
|
@@ -3781,7 +3727,7 @@ var require_pretty = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
3781
3727
|
var import_pino_pretty = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
3782
3728
|
const { isColorSupported } = require_colorette();
|
|
3783
3729
|
const pump = require_pump();
|
|
3784
|
-
const { Transform } = __require("node:stream");
|
|
3730
|
+
const { Transform: Transform$1 } = __require("node:stream");
|
|
3785
3731
|
const abstractTransport = require_pino_abstract_transport();
|
|
3786
3732
|
const colors = require_colors();
|
|
3787
3733
|
const { ERROR_LIKE_KEYS, LEVEL_KEY, LEVEL_LABEL, MESSAGE_KEY, TIMESTAMP_KEY } = require_constants();
|
|
@@ -3912,7 +3858,7 @@ var import_pino_pretty = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(
|
|
|
3912
3858
|
pretty = prettyFactory(opts);
|
|
3913
3859
|
source.off("message", pinoConfigListener);
|
|
3914
3860
|
});
|
|
3915
|
-
const stream = new Transform({
|
|
3861
|
+
const stream = new Transform$1({
|
|
3916
3862
|
objectMode: true,
|
|
3917
3863
|
autoDestroy: true,
|
|
3918
3864
|
transform(chunk, enc, cb) {
|
|
@@ -4482,23 +4428,58 @@ async function runReviewCli(input) {
|
|
|
4482
4428
|
...env,
|
|
4483
4429
|
DEV_REVIEW_SERVER_DIR: path.resolve(cwd, stateDir)
|
|
4484
4430
|
} : env;
|
|
4431
|
+
const refuse = (message, json) => {
|
|
4432
|
+
if (json) emitReviewEvent(input.stdout, {
|
|
4433
|
+
event: "error",
|
|
4434
|
+
error: {
|
|
4435
|
+
name: "Error",
|
|
4436
|
+
message
|
|
4437
|
+
}
|
|
4438
|
+
});
|
|
4439
|
+
else input.stderr.write(`${message}\n`);
|
|
4440
|
+
state.exitCode = 1;
|
|
4441
|
+
};
|
|
4485
4442
|
const serverCommand = configureOutput(program.command("server").description("Run review authoring without Desktop"), "plain");
|
|
4486
|
-
configureJsonOutput(serverCommand.command("start").description("Start the foreground authoring server; stop with Ctrl-C or SIGTERM").option("--state-dir <path>", "directory for saved reviews and server discovery").option("--port <port>", "loopback port (0 chooses an available port)", "0").option("--software-maps", "allow the authoring skill to generate optional software maps").addOption(new Option("--
|
|
4443
|
+
configureJsonOutput(serverCommand.command("start").description("Start the foreground authoring server; stop with Ctrl-C or SIGTERM").option("--state-dir <path>", "directory for saved reviews and server discovery").option("--port <port>", "loopback port (0 chooses an available port)", "0").option("--software-maps", "allow the authoring skill to generate optional software maps").option("--detach", "start the server in the background, logging to review-server/server.log").addOption(new Option("--started-by <who>").choices([
|
|
4444
|
+
"user",
|
|
4445
|
+
"cli",
|
|
4446
|
+
"desktop"
|
|
4447
|
+
]).default("user").hideHelp()).addOption(new Option("--authoring-mode <mode>").hideHelp()), "plain").action(async (_options, command) => {
|
|
4487
4448
|
const options = command.optsWithGlobals();
|
|
4488
4449
|
if (options.authoringMode !== void 0) throw new ReviewCliUsageError("--authoring-mode was removed with batch authoring; the server always authors interactively. Drop the option.");
|
|
4489
4450
|
const port = Number(options.port);
|
|
4490
4451
|
if (!Number.isInteger(port) || port < 0 || port > 65535) throw new ReviewCliUsageError("--port must be an integer between 0 and 65535.");
|
|
4491
4452
|
const stateDir = reviewServerStateDir(authoringEnv(options.stateDir));
|
|
4453
|
+
if (options.detach) {
|
|
4454
|
+
const { ensureBackgroundServer } = await import("./background-server-D2Rrr_e2.js");
|
|
4455
|
+
const { discovery, started } = await ensureBackgroundServer({
|
|
4456
|
+
stateDir,
|
|
4457
|
+
env,
|
|
4458
|
+
args: [
|
|
4459
|
+
"--port",
|
|
4460
|
+
`${port}`,
|
|
4461
|
+
...options.softwareMaps ? ["--software-maps"] : []
|
|
4462
|
+
]
|
|
4463
|
+
});
|
|
4464
|
+
await writeServerStatus(discovery, stateDir, options.json, { started });
|
|
4465
|
+
return;
|
|
4466
|
+
}
|
|
4492
4467
|
const controller = new AbortController();
|
|
4493
4468
|
const stop = () => controller.abort();
|
|
4469
|
+
const takeover = () => {
|
|
4470
|
+
input.stderr.write(`Whiteboard Desktop took over ${stateDir}; stopping this server.\n`);
|
|
4471
|
+
stop();
|
|
4472
|
+
};
|
|
4494
4473
|
process.once("SIGINT", stop);
|
|
4495
4474
|
process.once("SIGTERM", stop);
|
|
4475
|
+
process.once(DESKTOP_TAKEOVER_SIGNAL, takeover);
|
|
4476
|
+
const { HeadlessServerBusyError, runHeadlessServer } = await import("./headless-host-Cp37C-v0.js");
|
|
4496
4477
|
try {
|
|
4497
|
-
const { runHeadlessServer } = await import("./headless-host-BLLJ7Dyu.js");
|
|
4498
4478
|
await runHeadlessServer({
|
|
4499
4479
|
stateDir,
|
|
4500
4480
|
port,
|
|
4501
4481
|
softwareMapEnabled: options.softwareMaps,
|
|
4482
|
+
startedBy: options.startedBy,
|
|
4502
4483
|
signal: controller.signal,
|
|
4503
4484
|
telemetry,
|
|
4504
4485
|
onReady: ({ url, serverPid }) => {
|
|
@@ -4510,42 +4491,54 @@ async function runReviewCli(input) {
|
|
|
4510
4491
|
})}\n` : `Whiteboard server ready at ${url}\nSaved reviews: ${stateDir}\n`);
|
|
4511
4492
|
}
|
|
4512
4493
|
});
|
|
4494
|
+
} catch (error) {
|
|
4495
|
+
if (!(error instanceof HeadlessServerBusyError)) throw error;
|
|
4496
|
+
refuse(error.message, options.json);
|
|
4513
4497
|
} finally {
|
|
4514
4498
|
process.off("SIGINT", stop);
|
|
4515
4499
|
process.off("SIGTERM", stop);
|
|
4500
|
+
process.off(DESKTOP_TAKEOVER_SIGNAL, takeover);
|
|
4516
4501
|
}
|
|
4517
4502
|
});
|
|
4518
4503
|
configureJsonOutput(serverCommand.command("status").description("Check whether the headless server is ready").option("--state-dir <path>", "directory selected when starting the server"), "plain").action(async (_options, command) => {
|
|
4519
4504
|
const options = command.optsWithGlobals();
|
|
4520
4505
|
const stateDir = reviewServerStateDir(authoringEnv(options.stateDir));
|
|
4521
4506
|
const discovery = await readReviewServerDiscovery(stateDir);
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4507
|
+
if (!discovery) throw serverNotReady(stateDir);
|
|
4508
|
+
await writeServerStatus(discovery, stateDir, options.json);
|
|
4509
|
+
});
|
|
4510
|
+
configureJsonOutput(serverCommand.command("stop").description("Stop a background server started by --detach, the CLI, or Desktop").option("--state-dir <path>", "directory selected when starting the server"), "plain").action(async (_options, command) => {
|
|
4511
|
+
const options = command.optsWithGlobals();
|
|
4512
|
+
const stateDir = reviewServerStateDir(authoringEnv(options.stateDir));
|
|
4513
|
+
const { headlessServerOwner, stopBackgroundServer } = await import("./background-server-D2Rrr_e2.js");
|
|
4514
|
+
const owner = await headlessServerOwner(stateDir);
|
|
4515
|
+
const discovery = await readReviewServerDiscovery(stateDir);
|
|
4516
|
+
if (owner === void 0) {
|
|
4517
|
+
input.stdout.write(options.json ? `${JSON.stringify({
|
|
4518
|
+
event: "server.stop",
|
|
4519
|
+
stopped: false,
|
|
4520
|
+
stateDir
|
|
4521
|
+
})}\n` : `No Whiteboard server is running in ${stateDir}.\n`);
|
|
4522
|
+
return;
|
|
4523
|
+
}
|
|
4524
|
+
const serverPid = owner;
|
|
4525
|
+
if (discovery?.serverPid !== owner) throw new Error(`Process ${owner} holds the Whiteboard server's lock in ${stateDir} but has published no server; it may be starting. Try again, or end that process.`);
|
|
4526
|
+
if (discovery.startedBy === "user") throw new Error(`The Whiteboard server in ${stateDir} (process ${serverPid}) runs in the foreground of \`whiteboard server start\`. Stop it there with Ctrl-C.`);
|
|
4527
|
+
await stopBackgroundServer(discovery);
|
|
4526
4528
|
input.stdout.write(options.json ? `${JSON.stringify({
|
|
4527
|
-
event: "server.
|
|
4528
|
-
|
|
4529
|
-
url,
|
|
4529
|
+
event: "server.stop",
|
|
4530
|
+
stopped: true,
|
|
4530
4531
|
serverPid,
|
|
4531
|
-
stateDir
|
|
4532
|
-
|
|
4533
|
-
serverId
|
|
4534
|
-
})}\n` : `Whiteboard server ready at ${url}\nSaved reviews: ${stateDir}\n`);
|
|
4532
|
+
stateDir
|
|
4533
|
+
})}\n` : `Stopped the Whiteboard server (process ${serverPid}).\n`);
|
|
4535
4534
|
});
|
|
4536
4535
|
configureJsonOutput(serverCommand.command("reset-id").description("Give this machine's saved reviews a new server id; stop the server first").option("--state-dir <path>", "directory selected when starting the server"), "plain").action(async (_options, command) => {
|
|
4537
4536
|
const options = command.optsWithGlobals();
|
|
4538
4537
|
const stateDir = reviewServerStateDir(authoringEnv(options.stateDir));
|
|
4539
4538
|
if (!existsSync(path.join(stateDir, "review-api.db"))) throw new Error(`No saved reviews in ${stateDir}, so there is no server id to reset. Check --state-dir.`);
|
|
4540
|
-
const
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
} });
|
|
4544
|
-
const [problem] = desktops.broken.values();
|
|
4545
|
-
if (problem) throw new Error(`Cannot tell whether a Whiteboard Desktop is using ${stateDir}: ${problem.message}`);
|
|
4546
|
-
const inUse = () => /* @__PURE__ */ new Error(`A Whiteboard server is using ${stateDir}. Stop it first, then run whiteboard server reset-id again.`);
|
|
4547
|
-
if (desktops.instances.some(({ discovery }) => [discovery.appPid, discovery.serverPid].some((pid) => processIsAlive(pid)))) throw inUse();
|
|
4548
|
-
const [{ openReviewProfile }, { withHeadlessServerLock }] = await Promise.all([import("./profile-aMSw1oCM.js"), import("./headless-host-BLLJ7Dyu.js")]);
|
|
4539
|
+
const inUse = `A Whiteboard server is using ${stateDir}. Stop it first, then run whiteboard server reset-id again.`;
|
|
4540
|
+
if (await desktopUsesHome(stateDir, env)) return refuse(inUse, options.json);
|
|
4541
|
+
const [{ openReviewProfile }, { withHeadlessServerLock }] = await Promise.all([import("./profile-BSOkA6A-.js"), import("./headless-host-Cp37C-v0.js")]);
|
|
4549
4542
|
const reset = await withHeadlessServerLock(await realpath(stateDir), async () => {
|
|
4550
4543
|
const local = await openReviewProfile(stateDir, { manageWorkspaces: false });
|
|
4551
4544
|
try {
|
|
@@ -4555,7 +4548,7 @@ async function runReviewCli(input) {
|
|
|
4555
4548
|
await local.store.close();
|
|
4556
4549
|
}
|
|
4557
4550
|
});
|
|
4558
|
-
if (!reset.acquired)
|
|
4551
|
+
if (!reset.acquired) return refuse(inUse, options.json);
|
|
4559
4552
|
const serverId = reset.result;
|
|
4560
4553
|
input.stdout.write(options.json ? `${JSON.stringify({
|
|
4561
4554
|
event: "server.reset-id",
|
|
@@ -4563,6 +4556,104 @@ async function runReviewCli(input) {
|
|
|
4563
4556
|
stateDir
|
|
4564
4557
|
})}\n` : `New server id ${serverId}\nSaved reviews: ${stateDir}\n`);
|
|
4565
4558
|
});
|
|
4559
|
+
const remote = configureOutput(program.command("remote").description("Serve reviews to Whiteboard Desktop over SSH"), "plain");
|
|
4560
|
+
configureJsonOutput(remote.command("attach").description("Start a background server if none is healthy and report how to reach it").option("--state-dir <path>", "directory for saved reviews and server discovery").option("--groups <groups>", "comma-separated optional extension groups the Desktop has enabled, such as go").option("--replace", "stop a running server of another version that the CLI or Desktop started, and start this one"), "plain").action(async (_options, command) => {
|
|
4561
|
+
const options = command.optsWithGlobals();
|
|
4562
|
+
const stateDir = reviewServerStateDir(authoringEnv(options.stateDir));
|
|
4563
|
+
const { remoteAttach } = await import("./remote-attach-BAt2eZx3.js");
|
|
4564
|
+
let attach;
|
|
4565
|
+
try {
|
|
4566
|
+
attach = await remoteAttach({
|
|
4567
|
+
stateDir,
|
|
4568
|
+
env,
|
|
4569
|
+
groups: options.groups?.split(",").map((group) => group.trim()),
|
|
4570
|
+
replace: options.replace
|
|
4571
|
+
});
|
|
4572
|
+
} catch (error) {
|
|
4573
|
+
if (!options.json) throw error;
|
|
4574
|
+
let line = "";
|
|
4575
|
+
emitReviewEvent(new Writable({ write(chunk, _encoding, done) {
|
|
4576
|
+
line += chunk;
|
|
4577
|
+
done();
|
|
4578
|
+
} }), {
|
|
4579
|
+
event: "error",
|
|
4580
|
+
error: serializeReviewError(error)
|
|
4581
|
+
});
|
|
4582
|
+
input.stdout.write(`${REVIEW_REMOTE_ATTACH_BEGIN}\n${ensureTrailingNewline(line)}${REVIEW_REMOTE_ATTACH_END}\n`);
|
|
4583
|
+
state.exitCode = 1;
|
|
4584
|
+
return;
|
|
4585
|
+
}
|
|
4586
|
+
input.stdout.write(options.json ? `${REVIEW_REMOTE_ATTACH_BEGIN}\n${JSON.stringify(attach)}\n${REVIEW_REMOTE_ATTACH_END}\n` : `Whiteboard server ${attach.startedServer ? "started" : "already running"} at ${attach.url}\nLanguage features: ${attach.languageServer ? `VS Code server on port ${attach.languageServer.port}` : `unavailable (${attach.languageServerDetail})`}\n`);
|
|
4587
|
+
});
|
|
4588
|
+
configureJsonOutput(remote.command("extensions").description("Language extensions for the VS Code server on a remote").command("ensure").description("Download the curated extensions for this machine and list them for the VS Code server").option("--groups <groups>", "comma-separated optional extension groups the Desktop has enabled, such as go"), "plain").action(async (_options, command) => {
|
|
4589
|
+
const { json, groups } = command.optsWithGlobals();
|
|
4590
|
+
const { ensureRemoteExtensions } = await import("./remote-extensions-2Cxf3hMo.js");
|
|
4591
|
+
const result = await ensureRemoteExtensions({
|
|
4592
|
+
env,
|
|
4593
|
+
groups: groups?.split(",").map((group) => group.trim())
|
|
4594
|
+
});
|
|
4595
|
+
input.stdout.write(json ? `${JSON.stringify(result)}\n` : [
|
|
4596
|
+
...result.installed.map((id) => `Installed ${id}`),
|
|
4597
|
+
...result.skipped.map(({ id, reason }) => `Skipped ${id}: ${reason}`),
|
|
4598
|
+
...result.failed.map(({ id, error }) => `Failed ${id}: ${error}`)
|
|
4599
|
+
].map((line) => `${line}\n`).join(""));
|
|
4600
|
+
state.exitCode = result.failed.length > 0 ? 1 : 0;
|
|
4601
|
+
});
|
|
4602
|
+
configureJsonOutput(remote.command("uninstall").description("Remove what Whiteboard Desktop installed on this host, stopping a server it started").addOption(new Option("--keep-reviews", "leave the saved reviews in place").conflicts("deleteReviews")).addOption(new Option("--delete-reviews", "also delete the saved reviews")).option("--state-dir <path>", "directory for saved reviews and server discovery"), "plain").action(async (_options, command) => {
|
|
4603
|
+
const options = command.optsWithGlobals();
|
|
4604
|
+
const stateDir = reviewServerStateDir(authoringEnv(options.stateDir));
|
|
4605
|
+
const { remoteUninstall } = await import("./remote-uninstall-DLtDG0o7.js");
|
|
4606
|
+
let deleteReviews = options.deleteReviews ? true : options.keepReviews ? false : void 0;
|
|
4607
|
+
if (deleteReviews === void 0 && !options.json && input.stdin?.isTTY) {
|
|
4608
|
+
const prompt = createInterface$1({
|
|
4609
|
+
input: input.stdin,
|
|
4610
|
+
output: input.stderr
|
|
4611
|
+
});
|
|
4612
|
+
try {
|
|
4613
|
+
deleteReviews = /^y(es)?$/i.test((await prompt.question(`Also delete the saved reviews in ${stateDir}? [y/N] `)).trim());
|
|
4614
|
+
} finally {
|
|
4615
|
+
prompt.close();
|
|
4616
|
+
}
|
|
4617
|
+
}
|
|
4618
|
+
const result = deleteReviews === void 0 ? {
|
|
4619
|
+
event: "remote.uninstall",
|
|
4620
|
+
ok: false,
|
|
4621
|
+
reason: "Say what happens to the saved reviews: pass --keep-reviews or --delete-reviews."
|
|
4622
|
+
} : await remoteUninstall({
|
|
4623
|
+
home: env.HOME ?? "",
|
|
4624
|
+
env,
|
|
4625
|
+
stateDir,
|
|
4626
|
+
deleteReviews
|
|
4627
|
+
});
|
|
4628
|
+
if (!result.ok) {
|
|
4629
|
+
if (!options.json) throw new Error(result.reason);
|
|
4630
|
+
input.stdout.write(`${JSON.stringify(result)}\n`);
|
|
4631
|
+
state.exitCode = 1;
|
|
4632
|
+
return;
|
|
4633
|
+
}
|
|
4634
|
+
input.stdout.write(options.json ? `${JSON.stringify(result)}\n` : [
|
|
4635
|
+
...result.stoppedServer ? [`Stopped the Whiteboard server (process ${result.stoppedServer.pid}).`] : [],
|
|
4636
|
+
...result.removed.map((removed) => `Removed ${removed}`),
|
|
4637
|
+
result.keptReviews ? `Saved reviews kept in ${stateDir}` : "Saved reviews deleted.",
|
|
4638
|
+
""
|
|
4639
|
+
].join("\n"));
|
|
4640
|
+
});
|
|
4641
|
+
async function writeServerStatus(discovery, stateDir, json, extra = {}) {
|
|
4642
|
+
const health = await readReviewServerHealth(discovery);
|
|
4643
|
+
if (!health) throw serverNotReady(stateDir);
|
|
4644
|
+
const { url, serverPid } = discovery;
|
|
4645
|
+
const { version, serverId } = health;
|
|
4646
|
+
input.stdout.write(json ? `${JSON.stringify({
|
|
4647
|
+
event: "server.status",
|
|
4648
|
+
ready: true,
|
|
4649
|
+
url,
|
|
4650
|
+
serverPid,
|
|
4651
|
+
stateDir,
|
|
4652
|
+
version,
|
|
4653
|
+
serverId,
|
|
4654
|
+
...extra
|
|
4655
|
+
})}\n` : `Whiteboard server ${extra.started === false ? "already running" : "ready"} at ${url}\nSaved reviews: ${stateDir}\n`);
|
|
4656
|
+
}
|
|
4566
4657
|
configureJsonOutput(program.command("version").description("Print Whiteboard package version").option("--verbose", "Show executing CLI paths and build identity"), "plain").action(async (options, command) => {
|
|
4567
4658
|
const { json } = command.optsWithGlobals();
|
|
4568
4659
|
if (options.verbose) {
|
|
@@ -4653,7 +4744,46 @@ async function runReviewCli(input) {
|
|
|
4653
4744
|
"omp",
|
|
4654
4745
|
"copilot",
|
|
4655
4746
|
"all"
|
|
4656
|
-
])), "plain").action(async (targets, options) => {
|
|
4747
|
+
])).addOption(new Option("--detect", "list the agents on this machine and whether each is connected; changes nothing").conflicts("yes")).option("--yes", "run the commands the prompt gives, without an agent: claude, codex, opencode or pi"), "plain").action(async (targets, options) => {
|
|
4748
|
+
const output = {
|
|
4749
|
+
json: options.json,
|
|
4750
|
+
stdout: input.stdout,
|
|
4751
|
+
stderr: input.stderr
|
|
4752
|
+
};
|
|
4753
|
+
if (options.detect) {
|
|
4754
|
+
const agents = await detectAgents({
|
|
4755
|
+
homeDir: scope.homeDir,
|
|
4756
|
+
env
|
|
4757
|
+
});
|
|
4758
|
+
if (options.json) {
|
|
4759
|
+
emitJsonEvent(output, {
|
|
4760
|
+
event: "connect.detect",
|
|
4761
|
+
agents
|
|
4762
|
+
});
|
|
4763
|
+
return;
|
|
4764
|
+
}
|
|
4765
|
+
humanStream(output).write(agents.length ? `${agents.map((agent) => `${agent.name}: ${agent.connected ? "connected" : "not connected"}${agent.manual ? " (its command is not on PATH)" : ""}`).join("\n")}\n` : "No agents found.\n");
|
|
4766
|
+
return;
|
|
4767
|
+
}
|
|
4768
|
+
if (options.yes) {
|
|
4769
|
+
const agents = parseTargets(targets);
|
|
4770
|
+
if (targets.length === 0 || !agents.every((target) => AGENT_CONNECT_TARGETS.some((known) => known === target))) throw new Error("connect --yes takes one or more of claude, codex, opencode or pi.");
|
|
4771
|
+
const results = await connectAgents({
|
|
4772
|
+
agents,
|
|
4773
|
+
homeDir: scope.homeDir,
|
|
4774
|
+
env
|
|
4775
|
+
});
|
|
4776
|
+
state.exitCode = results.every((result) => result.connected) ? 0 : 1;
|
|
4777
|
+
if (options.json) {
|
|
4778
|
+
emitJsonEvent(output, {
|
|
4779
|
+
event: "connect.run",
|
|
4780
|
+
agents: results
|
|
4781
|
+
});
|
|
4782
|
+
return;
|
|
4783
|
+
}
|
|
4784
|
+
humanStream(output).write(results.map((result) => `${result.output}${result.name}: ${result.connected ? "connected" : "not connected"}\n`).join(""));
|
|
4785
|
+
return;
|
|
4786
|
+
}
|
|
4657
4787
|
const selected = parseTargets(targets);
|
|
4658
4788
|
const { homeDir, devHome } = scope;
|
|
4659
4789
|
const prompts = connectPrompts({
|
|
@@ -4666,11 +4796,6 @@ async function runReviewCli(input) {
|
|
|
4666
4796
|
fffBinaryPath: path.join(homeDir, ".local", "bin", "fff-mcp"),
|
|
4667
4797
|
fffCorpusRoot: path.join(devHome, "trace-search")
|
|
4668
4798
|
});
|
|
4669
|
-
const output = {
|
|
4670
|
-
json: options.json,
|
|
4671
|
-
stdout: input.stdout,
|
|
4672
|
-
stderr: input.stderr
|
|
4673
|
-
};
|
|
4674
4799
|
if (options.json) {
|
|
4675
4800
|
emitJsonEvent(output, {
|
|
4676
4801
|
event: "connect",
|
|
@@ -4678,21 +4803,21 @@ async function runReviewCli(input) {
|
|
|
4678
4803
|
});
|
|
4679
4804
|
return;
|
|
4680
4805
|
}
|
|
4681
|
-
const sections = selected.map((target) => selected.length > 1 ? `## ${
|
|
4806
|
+
const sections = selected.map((target) => selected.length > 1 ? `## ${REVIEW_CLI_INSTALL_TARGET_LABELS[target]}\n\n${prompts[target]}` : prompts[target]);
|
|
4682
4807
|
humanStream(output).write(`${sections.join("\n\n")}\n`);
|
|
4683
4808
|
});
|
|
4684
4809
|
configureJsonOutput(configureOutput(program.command("migrate", { hidden: true }), "plain").command("apply").option("--force"), "plain").action((_options, command) => {
|
|
4685
4810
|
command.showHelpAfterError(false).error("Markdown review migration is no longer supported. Ask your agent to migrate your old Whiteboard reviews.");
|
|
4686
4811
|
});
|
|
4687
4812
|
configureJsonOutput(configureJsonOutput(program.command("share").description("Upload an immutable review snapshot and return its share link"), "plain").option("--review <id>", "review ID").option("--version <number>", "Saved version to share").option("--preview", "Open the share link in Whiteboard Preview by default").option("--request-id <uuid>", "Reuse this ID when retrying the same immutable upload").action(async (options) => {
|
|
4688
|
-
const { runShareCli } = await import("./cli-
|
|
4813
|
+
const { runShareCli } = await import("./cli-CSrz_wGk.js");
|
|
4689
4814
|
state.exitCode = await runShareCli({
|
|
4690
4815
|
...input,
|
|
4691
4816
|
...options,
|
|
4692
4817
|
env: authoringEnv()
|
|
4693
4818
|
});
|
|
4694
4819
|
}).command("revoke <share-id>").description("Revoke future downloads of a share"), "plain").action(async (shareId, options) => {
|
|
4695
|
-
const { runShareCli } = await import("./cli-
|
|
4820
|
+
const { runShareCli } = await import("./cli-CSrz_wGk.js");
|
|
4696
4821
|
state.exitCode = await runShareCli({
|
|
4697
4822
|
...input,
|
|
4698
4823
|
...options,
|
|
@@ -4773,7 +4898,7 @@ async function runReviewCli(input) {
|
|
|
4773
4898
|
return commands;
|
|
4774
4899
|
} });
|
|
4775
4900
|
for (const [name, description] of [["api", "Call a JSON review authoring tool on the running server"], ["mcp", "Serve the JSON review authoring tools over stdio MCP"]]) configureOutput(program.command(name).description(description).argument("[args...]", "tool name and JSON input").allowUnknownOption().allowExcessArguments().helpOption(false).passThroughOptions().addHelpText("after", `\n${reviewAgentCliHelp}`), "plain").action(async (args) => {
|
|
4776
|
-
const { runReviewAgentCli } = await import("./agent-cli-
|
|
4901
|
+
const { runReviewAgentCli } = await import("./agent-cli-CRPPmS3l.js");
|
|
4777
4902
|
state.exitCode = await runReviewAgentCli({
|
|
4778
4903
|
...input,
|
|
4779
4904
|
env: authoringEnv(),
|
|
@@ -4867,15 +4992,6 @@ async function runReviewCli(input) {
|
|
|
4867
4992
|
await attemptTelemetry(() => telemetry.shutdown(1e3));
|
|
4868
4993
|
}
|
|
4869
4994
|
}
|
|
4870
|
-
const TARGET_LABELS = {
|
|
4871
|
-
claude: "Claude Code",
|
|
4872
|
-
codex: "Codex",
|
|
4873
|
-
cursor: "Cursor",
|
|
4874
|
-
opencode: "OpenCode",
|
|
4875
|
-
pi: "Pi",
|
|
4876
|
-
omp: "oh-my-pi",
|
|
4877
|
-
copilot: "Copilot CLI"
|
|
4878
|
-
};
|
|
4879
4995
|
function parseTargets(targets) {
|
|
4880
4996
|
if (targets.length === 0 || targets.includes("all")) return [...ALL_INSTALL_TARGETS];
|
|
4881
4997
|
return [...new Set(targets.values().map((target) => target === "claude-code" ? "claude" : target).filter(isInstallTarget))];
|