diffprism 0.34.1 → 0.35.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/dist/bin.js CHANGED
@@ -1,19 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createGitHubClient,
4
+ ensureServer,
4
5
  fetchPullRequest,
5
6
  fetchPullRequestDiff,
6
7
  isServerAlive,
7
8
  normalizePr,
8
9
  parsePrRef,
9
10
  readServerFile,
10
- readWatchFile,
11
11
  resolveGitHubToken,
12
12
  startGlobalServer,
13
- startReview,
14
- startWatch,
15
- submitGitHubReview
16
- } from "./chunk-VASCXEMN.js";
13
+ submitGitHubReview,
14
+ submitReviewToServer
15
+ } from "./chunk-LUUR6LNP.js";
16
+ import "./chunk-QGWYCEJN.js";
17
+ import "./chunk-DHCVZGHE.js";
18
+ import "./chunk-JSBRDJBE.js";
17
19
 
18
20
  // cli/src/index.ts
19
21
  import { Command } from "commander";
@@ -31,11 +33,11 @@ async function review(ref, flags) {
31
33
  diffRef = "working-copy";
32
34
  }
33
35
  try {
34
- const result = await startReview({
35
- diffRef,
36
+ const serverInfo = await ensureServer({ dev: flags.dev });
37
+ const { result } = await submitReviewToServer(serverInfo, diffRef, {
36
38
  title: flags.title,
37
39
  cwd: process.cwd(),
38
- dev: flags.dev
40
+ diffRef
39
41
  });
40
42
  console.log(JSON.stringify(result, null, 2));
41
43
  process.exit(0);
@@ -62,46 +64,19 @@ async function reviewPr(pr, flags) {
62
64
  console.log("PR has no changes to review.");
63
65
  return;
64
66
  }
65
- const { payload, diffSet, briefing, metadata } = normalizePr(rawDiff, prMetadata, {
67
+ const { payload, diffSet } = normalizePr(rawDiff, prMetadata, {
66
68
  title: flags.title,
67
69
  reasoning: flags.reasoning
68
70
  });
69
71
  console.log(
70
72
  `${diffSet.files.length} files, +${diffSet.files.reduce((s, f) => s + f.additions, 0)} -${diffSet.files.reduce((s, f) => s + f.deletions, 0)}`
71
73
  );
72
- let result;
73
- const serverInfo = await isServerAlive();
74
- if (serverInfo) {
75
- const createResponse = await fetch(
76
- `http://localhost:${serverInfo.httpPort}/api/reviews`,
77
- {
78
- method: "POST",
79
- headers: { "Content-Type": "application/json" },
80
- body: JSON.stringify({
81
- payload,
82
- projectPath: `github:${owner}/${repo}`,
83
- diffRef: `PR #${number}`
84
- })
85
- }
86
- );
87
- if (!createResponse.ok) {
88
- throw new Error(`Global server returned ${createResponse.status}`);
89
- }
90
- const { sessionId } = await createResponse.json();
91
- console.log(`Review session created: ${sessionId}`);
92
- console.log("Waiting for review submission...");
93
- result = await pollForResult(serverInfo.httpPort, sessionId);
94
- } else {
95
- result = await startReview({
96
- diffRef: `PR #${number}`,
97
- title: metadata.title,
98
- description: metadata.description,
99
- reasoning: metadata.reasoning,
100
- cwd: process.cwd(),
101
- dev: flags.dev,
102
- injectedPayload: payload
103
- });
104
- }
74
+ const serverInfo = await ensureServer({ dev: flags.dev });
75
+ const { result } = await submitReviewToServer(serverInfo, `PR #${number}`, {
76
+ injectedPayload: payload,
77
+ projectPath: `github:${owner}/${repo}`,
78
+ diffRef: `PR #${number}`
79
+ });
105
80
  console.log(JSON.stringify(result, null, 2));
106
81
  if (flags.postToGithub || result.decision !== "dismissed" && await promptPostToGithub()) {
107
82
  console.log("Posting review to GitHub...");
@@ -117,24 +92,6 @@ async function reviewPr(pr, flags) {
117
92
  process.exit(1);
118
93
  }
119
94
  }
120
- async function pollForResult(httpPort, sessionId) {
121
- const pollIntervalMs = 2e3;
122
- const maxWaitMs = 600 * 1e3;
123
- const start2 = Date.now();
124
- while (Date.now() - start2 < maxWaitMs) {
125
- const response = await fetch(
126
- `http://localhost:${httpPort}/api/reviews/${sessionId}/result`
127
- );
128
- if (response.ok) {
129
- const data = await response.json();
130
- if (data.result) {
131
- return data.result;
132
- }
133
- }
134
- await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
135
- }
136
- throw new Error("Review timed out waiting for submission.");
137
- }
138
95
  async function promptPostToGithub() {
139
96
  if (!process.stdin.isTTY) {
140
97
  return false;
@@ -169,130 +126,30 @@ name: review
169
126
  description: Open current code changes in DiffPrism's browser-based review UI for human review.
170
127
  ---
171
128
 
172
- # DiffPrism Review Skill
173
-
174
- When the user invokes \`/review\`, open the current code changes in DiffPrism for browser-based human review.
175
-
176
- ## Steps
177
-
178
- ### 1. Check for Watch Mode
179
-
180
- Before opening a new review, check if \`diffprism watch\` is already running. Look for \`.diffprism/watch.json\` at the git root. If it exists and the process is alive:
181
-
182
- - **Do NOT call \`open_review\`** (the browser is already open with live-updating diffs)
183
- - Instead, call \`mcp__diffprism__update_review_context\` to push your reasoning to the existing watch session
184
- - Then **immediately** call \`mcp__diffprism__get_review_result\` with \`wait: true\` to block until the developer submits their review
185
- - Tell the user: "DiffPrism watch is running \u2014 pushed reasoning to the live review. Waiting for your feedback..."
186
- - When the result comes back, handle it per step 5 below
187
- - Skip steps 2-4
188
-
189
- ### 2. Load Configuration
190
-
191
- Look for \`diffprism.config.json\` at the project root. If it exists, read it for preferences. If it doesn't exist, use defaults silently \u2014 do not prompt or create the file.
192
-
193
- \`\`\`json
194
- {
195
- "defaultDiffScope": "staged | unstaged | working-copy",
196
- "includeReasoning": true | false
197
- }
198
- \`\`\`
199
-
200
- **Defaults** (when fields are missing or file doesn't exist):
201
- - \`defaultDiffScope\`: \`"working-copy"\`
202
- - \`includeReasoning\`: \`true\`
203
-
204
- ### 3. Open the Review
205
-
206
- Call \`mcp__diffprism__open_review\` with:
207
-
208
- - \`diff_ref\`: Use the \`defaultDiffScope\` from config. If the user specified a scope in their message (e.g., "/review staged"), use that instead.
209
- - \`title\`: A short summary of the changes (generate from git status or the user's message).
210
- - \`description\`: A brief description of what changed and why.
211
- - \`reasoning\`: If \`includeReasoning\` is \`true\`, include your reasoning about the implementation decisions.
129
+ # DiffPrism Review
212
130
 
213
- ### 4. Handle the Result
131
+ When the user invokes \`/review\`, call \`mcp__diffprism__open_review\` with:
214
132
 
215
- The tool blocks until the user submits their review in the browser. When it returns:
133
+ - \`diff_ref\`: \`"working-copy"\` (or what the user specified, e.g. \`"staged"\`)
134
+ - \`title\`: Brief summary of the changes
135
+ - \`reasoning\`: Your reasoning about the implementation decisions
216
136
 
217
- - **\`approved\`** \u2014 Acknowledge and proceed with whatever task was in progress.
218
- - **\`approved_with_comments\`** \u2014 Note the comments, address any actionable feedback.
219
- - **\`changes_requested\`** \u2014 Read the comments carefully, make the requested changes, and offer to open another review.
137
+ The tool blocks until the human submits their review. Handle the result:
220
138
 
221
- #### Post-Review Actions
139
+ - **\`approved\`** \u2014 Proceed with the task.
140
+ - **\`changes_requested\`** \u2014 Read comments, make fixes, offer to re-review.
141
+ - If \`postReviewAction\` is \`"commit"\` \u2014 commit the changes.
142
+ - If \`postReviewAction\` is \`"commit_and_pr"\` \u2014 commit and open a PR.
222
143
 
223
- The result may include a \`postReviewAction\` field. If present, **execute the action immediately without asking for confirmation** \u2014 the user already chose this action in the review UI:
144
+ ## Headless Tools
224
145
 
225
- - **\`"commit"\`** \u2014 Commit the reviewed changes (stage relevant files, create a commit with an appropriate message).
226
- - **\`"commit_and_pr"\`** \u2014 Commit the changes and open a pull request.
146
+ - \`mcp__diffprism__analyze_diff\` \u2014 Returns analysis JSON (patterns, complexity, test gaps) without opening a browser. Use proactively to self-check before requesting review.
147
+ - \`mcp__diffprism__get_diff\` \u2014 Returns structured diff JSON.
227
148
 
228
- If \`postReviewAction\` is not present or is empty, do nothing extra \u2014 just report the result.
149
+ ## Rules
229
150
 
230
- ### 5. Error Handling
231
-
232
- If the \`mcp__diffprism__open_review\` tool is not available:
233
- - Tell the user: "The DiffPrism MCP server isn't configured. Run \`npx diffprism setup\` to set it up, then restart Claude Code."
234
-
235
- ## Global Server Mode
236
-
237
- When a global DiffPrism server is running (\`diffprism server\`), the MCP tools automatically detect it and route reviews there instead of opening a new browser tab each time. The review appears in the server's multi-session UI at the existing browser tab.
238
-
239
- This is transparent \u2014 the same \`open_review\`, \`update_review_context\`, and \`get_review_result\` tools work the same way. No changes to the workflow are needed.
240
-
241
- ## Watch Mode: Waiting for Review Feedback
242
-
243
- When \`diffprism watch\` is active (detected via \`.diffprism/watch.json\`), the developer can submit reviews at any time in the browser.
244
-
245
- **After pushing context to a watch session**, call \`mcp__diffprism__get_review_result\` with \`wait: true\` to block until the developer submits their review. This polls the result file every 2 seconds and returns as soon as feedback is available (up to 5 minutes by default).
246
-
247
- Use this pattern:
248
- 1. Push context via \`update_review_context\`
249
- 2. Call \`get_review_result\` with \`wait: true\` \u2014 this blocks until the developer submits
250
- 3. Handle the result (approved, changes_requested, etc.)
251
- 4. If changes were requested, make fixes, push updated context, and call \`get_review_result\` with \`wait: true\` again
252
-
253
- You can also check for feedback without blocking by calling \`get_review_result\` without \`wait\` at natural breakpoints (between tasks, before committing, etc.).
254
-
255
- ## Self-Review: Headless Analysis Tools
256
-
257
- DiffPrism provides two headless tools that return analysis data as JSON without opening a browser. Use these to check your own work before requesting human review.
258
-
259
- ### Available Headless Tools
260
-
261
- - **\`mcp__diffprism__get_diff\`** \u2014 Returns a structured \`DiffSet\` (files, hunks, additions/deletions) for a given diff ref. Use this to inspect exactly what changed.
262
- - **\`mcp__diffprism__analyze_diff\`** \u2014 Returns a \`ReviewBriefing\` with summary, file triage, impact detection, complexity scores, test coverage gaps, and pattern flags (security issues, TODOs, console.logs left in, etc.).
263
-
264
- Both accept a \`diff_ref\` parameter: \`"staged"\`, \`"unstaged"\`, \`"working-copy"\`, or a git range like \`"HEAD~3..HEAD"\`.
265
-
266
- ### Self-Review Loop
267
-
268
- When you've finished writing code and before requesting human review, use this pattern:
269
-
270
- 1. **Analyze your changes:** Call \`mcp__diffprism__analyze_diff\` with \`diff_ref: "working-copy"\`
271
- 2. **Check the briefing for issues:**
272
- - \`patterns\` \u2014 Look for console.logs, TODOs, security flags, disabled tests
273
- - \`testCoverage\` \u2014 Check if changed source files have corresponding test changes
274
- - \`complexity\` \u2014 Review high-complexity scores
275
- - \`impact.newDependencies\` \u2014 Verify any new deps are intentional
276
- - \`impact.breakingChanges\` \u2014 Confirm breaking changes are expected
277
- 3. **Fix any issues found** \u2014 Remove debug statements, add missing tests, address security flags
278
- 4. **Re-analyze** \u2014 Run \`analyze_diff\` again to confirm the issues are resolved
279
- 5. **Open for human review** \u2014 Once clean, use \`/review\` or \`open_review\` for final human sign-off
280
-
281
- This loop catches common issues (leftover console.logs, missing tests, security anti-patterns) before the human reviewer sees them, making reviews faster and more focused.
282
-
283
- ### When to Use Headless Tools
284
-
285
- - **After completing a coding task** \u2014 Self-check before requesting review
286
- - **During implementation** \u2014 Periodically check for patterns and issues as you work
287
- - **Before committing** \u2014 Quick sanity check on what's about to be committed
288
- - **Do NOT use these as a replacement for human review** \u2014 They complement, not replace, \`/review\`
289
-
290
- ## Behavior Rules
291
-
292
- - **IMPORTANT: Do NOT open reviews automatically.** Only open a review when the user explicitly invokes \`/review\` or directly asks for a review.
293
- - Do NOT open reviews before commits, after code changes, or at any other time unless the user requests it.
294
- - Headless tools (\`get_diff\`, \`analyze_diff\`) can be used proactively during development without explicit user request \u2014 they don't open a browser or interrupt the user.
295
- - Power users can create \`diffprism.config.json\` manually to customize defaults (diff scope, reasoning).
151
+ - Only open a review when the user explicitly asks (\`/review\` or "review my changes").
152
+ - Headless tools can be used proactively without user request.
296
153
  `;
297
154
 
298
155
  // cli/src/commands/setup.ts
@@ -808,7 +665,7 @@ async function start(ref, flags) {
808
665
  });
809
666
  const hasChanges = outcome.created.length > 0 || outcome.updated.length > 0;
810
667
  if (hasChanges) {
811
- console.log("\u2713 DiffPrism configured for Claude Code.");
668
+ console.log("DiffPrism configured for Claude Code.");
812
669
  }
813
670
  let diffRef;
814
671
  if (flags.staged) {
@@ -820,30 +677,23 @@ async function start(ref, flags) {
820
677
  } else {
821
678
  diffRef = "working-copy";
822
679
  }
823
- const pollInterval = flags.interval ? parseInt(flags.interval, 10) : 1e3;
824
680
  try {
825
- const handle = await startWatch({
826
- diffRef,
827
- title: flags.title,
828
- cwd: process.cwd(),
829
- dev: flags.dev,
830
- pollInterval
831
- });
832
- console.log("Use /review in Claude Code to send changes for review.");
681
+ const serverInfo = await ensureServer({ dev: flags.dev });
682
+ console.log(
683
+ `DiffPrism server at http://localhost:${serverInfo.httpPort}`
684
+ );
833
685
  if (hasChanges) {
834
686
  console.log(
835
687
  "If this is your first time, restart Claude Code first to load the MCP server."
836
688
  );
837
689
  }
838
- const shutdown = async () => {
839
- console.log("\nStopping DiffPrism...");
840
- await handle.stop();
841
- process.exit(0);
842
- };
843
- process.on("SIGINT", shutdown);
844
- process.on("SIGTERM", shutdown);
845
- await new Promise(() => {
690
+ const { result } = await submitReviewToServer(serverInfo, diffRef, {
691
+ title: flags.title,
692
+ cwd: process.cwd(),
693
+ diffRef
846
694
  });
695
+ console.log(JSON.stringify(result, null, 2));
696
+ process.exit(0);
847
697
  } catch (err) {
848
698
  const message = err instanceof Error ? err.message : String(err);
849
699
  console.error(`Error: ${message}`);
@@ -863,24 +713,19 @@ async function watch(ref, flags) {
863
713
  } else {
864
714
  diffRef = "working-copy";
865
715
  }
866
- const pollInterval = flags.interval ? parseInt(flags.interval, 10) : 1e3;
867
716
  try {
868
- const handle = await startWatch({
869
- diffRef,
717
+ const serverInfo = await ensureServer({ dev: flags.dev });
718
+ console.log(
719
+ `DiffPrism server at http://localhost:${serverInfo.httpPort}`
720
+ );
721
+ console.log("Submitting review session...");
722
+ const { result } = await submitReviewToServer(serverInfo, diffRef, {
870
723
  title: flags.title,
871
724
  cwd: process.cwd(),
872
- dev: flags.dev,
873
- pollInterval
874
- });
875
- const shutdown = async () => {
876
- console.log("\nStopping watch...");
877
- await handle.stop();
878
- process.exit(0);
879
- };
880
- process.on("SIGINT", shutdown);
881
- process.on("SIGTERM", shutdown);
882
- await new Promise(() => {
725
+ diffRef
883
726
  });
727
+ console.log(JSON.stringify(result, null, 2));
728
+ process.exit(0);
884
729
  } catch (err) {
885
730
  const message = err instanceof Error ? err.message : String(err);
886
731
  console.error(`Error: ${message}`);
@@ -891,15 +736,15 @@ async function watch(ref, flags) {
891
736
  // cli/src/commands/notify-stop.ts
892
737
  async function notifyStop() {
893
738
  try {
894
- const watchInfo = readWatchFile();
895
- if (!watchInfo) {
739
+ const serverInfo = await isServerAlive();
740
+ if (!serverInfo) {
896
741
  process.exit(0);
897
742
  return;
898
743
  }
899
744
  const controller = new AbortController();
900
745
  const timeout = setTimeout(() => controller.abort(), 2e3);
901
746
  try {
902
- await fetch(`http://localhost:${watchInfo.wsPort}/api/refresh`, {
747
+ await fetch(`http://localhost:${serverInfo.httpPort}/api/refresh`, {
903
748
  method: "POST",
904
749
  signal: controller.signal
905
750
  });
@@ -912,18 +757,33 @@ async function notifyStop() {
912
757
  }
913
758
 
914
759
  // cli/src/commands/server.ts
760
+ import { spawn } from "child_process";
761
+ import fs3 from "fs";
762
+ import path3 from "path";
763
+ import os3 from "os";
915
764
  async function server(flags) {
916
- const existing = await isServerAlive();
917
- if (existing) {
918
- console.log(`DiffPrism server is already running on port ${existing.httpPort} (PID ${existing.pid})`);
919
- console.log(`Use 'diffprism server stop' to stop it first.`);
920
- process.exit(1);
765
+ if (flags.background) {
766
+ await spawnDaemon(flags);
921
767
  return;
922
768
  }
769
+ const isDaemon = !!flags._daemon;
770
+ if (!isDaemon) {
771
+ const existing = await isServerAlive();
772
+ if (existing) {
773
+ console.log(`DiffPrism server is already running on port ${existing.httpPort} (PID ${existing.pid})`);
774
+ console.log(`Use 'diffprism server stop' to stop it first.`);
775
+ process.exit(1);
776
+ return;
777
+ }
778
+ }
923
779
  if (!isGlobalSetupDone()) {
924
- console.log("Running global setup...\n");
925
- await setup({ global: true, quiet: false });
926
- console.log("");
780
+ if (!isDaemon) {
781
+ console.log("Running global setup...\n");
782
+ }
783
+ await setup({ global: true, quiet: isDaemon });
784
+ if (!isDaemon) {
785
+ console.log("");
786
+ }
927
787
  }
928
788
  const httpPort = flags.port ? parseInt(flags.port, 10) : void 0;
929
789
  const wsPort = flags.wsPort ? parseInt(flags.wsPort, 10) : void 0;
@@ -931,10 +791,14 @@ async function server(flags) {
931
791
  const handle = await startGlobalServer({
932
792
  httpPort,
933
793
  wsPort,
934
- dev: flags.dev
794
+ dev: flags.dev,
795
+ silent: isDaemon,
796
+ openBrowser: !isDaemon
935
797
  });
936
798
  const shutdown = async () => {
937
- console.log("\nStopping server...");
799
+ if (!isDaemon) {
800
+ console.log("\nStopping server...");
801
+ }
938
802
  await handle.stop();
939
803
  process.exit(0);
940
804
  };
@@ -944,10 +808,48 @@ async function server(flags) {
944
808
  });
945
809
  } catch (err) {
946
810
  const message = err instanceof Error ? err.message : String(err);
947
- console.error(`Error starting server: ${message}`);
811
+ if (!isDaemon) {
812
+ console.error(`Error starting server: ${message}`);
813
+ }
948
814
  process.exit(1);
949
815
  }
950
816
  }
817
+ async function spawnDaemon(flags) {
818
+ const existing = await isServerAlive();
819
+ if (existing) {
820
+ console.log(`DiffPrism server is already running on port ${existing.httpPort} (PID ${existing.pid})`);
821
+ return;
822
+ }
823
+ const args = process.argv.slice(1).filter((a) => a !== "--background");
824
+ args.push("--_daemon");
825
+ const logDir = path3.join(os3.homedir(), ".diffprism");
826
+ if (!fs3.existsSync(logDir)) {
827
+ fs3.mkdirSync(logDir, { recursive: true });
828
+ }
829
+ const logPath = path3.join(logDir, "server.log");
830
+ const logFd = fs3.openSync(logPath, "a");
831
+ const child = spawn(process.execPath, args, {
832
+ detached: true,
833
+ stdio: ["ignore", logFd, logFd],
834
+ env: { ...process.env }
835
+ });
836
+ child.unref();
837
+ fs3.closeSync(logFd);
838
+ console.log("Starting DiffPrism server in background...");
839
+ const startTime = Date.now();
840
+ const timeoutMs = 15e3;
841
+ while (Date.now() - startTime < timeoutMs) {
842
+ await new Promise((resolve) => setTimeout(resolve, 500));
843
+ const info = await isServerAlive();
844
+ if (info) {
845
+ console.log(`DiffPrism server started (PID ${info.pid}, port ${info.httpPort})`);
846
+ console.log(`Logs: ${logPath}`);
847
+ return;
848
+ }
849
+ }
850
+ console.error("Timed out waiting for server to start. Check logs at:", logPath);
851
+ process.exit(1);
852
+ }
951
853
  async function serverStatus() {
952
854
  const info = await isServerAlive();
953
855
  if (!info) {
@@ -1001,7 +903,7 @@ async function serverStop() {
1001
903
 
1002
904
  // cli/src/index.ts
1003
905
  var program = new Command();
1004
- program.name("diffprism").description("Local-first code review tool for agent-generated changes").version(true ? "0.34.1" : "0.0.0-dev");
906
+ program.name("diffprism").description("Local-first code review tool for agent-generated changes").version(true ? "0.35.0" : "0.0.0-dev");
1005
907
  program.command("review [ref]").description("Open a browser-based diff review").option("--staged", "Review staged changes").option("--unstaged", "Review unstaged changes").option("-t, --title <title>", "Review title").option("--dev", "Use Vite dev server with HMR instead of static files").action(review);
1006
908
  program.command("review-pr <pr>").description("Review a GitHub pull request in DiffPrism").option("-t, --title <title>", "Override review title").option("--reasoning <text>", "Agent reasoning about the PR").option("--dev", "Use Vite dev server with HMR instead of static files").option("--post-to-github", "Automatically post review back to GitHub without prompting").action(reviewPr);
1007
909
  program.command("start [ref]").description("Set up DiffPrism and start watching for changes").option("--staged", "Watch staged changes").option("--unstaged", "Watch unstaged changes").option("-t, --title <title>", "Review title").option("--interval <ms>", "Poll interval in milliseconds (default: 1000)").option("--dev", "Use Vite dev server with HMR instead of static files").option("--global", "Install skill globally (~/.claude/skills/)").option("--force", "Overwrite existing configuration files").action(start);
@@ -1014,7 +916,7 @@ program.command("setup").description("Configure DiffPrism for Claude Code integr
1014
916
  program.command("teardown").description("Remove DiffPrism configuration from the current project").option("--global", "Remove global configuration (skill + permissions at ~/.claude/)").option("-q, --quiet", "Suppress output").action((flags) => {
1015
917
  teardown(flags);
1016
918
  });
1017
- var serverCmd = program.command("server").description("Start the global DiffPrism server for multi-session reviews").option("-p, --port <port>", "HTTP API port (default: 24680)").option("--ws-port <port>", "WebSocket port (default: 24681)").option("--dev", "Use Vite dev server with HMR instead of static files").action(server);
919
+ var serverCmd = program.command("server").description("Start the global DiffPrism server for multi-session reviews").option("-p, --port <port>", "HTTP API port (default: 24680)").option("--ws-port <port>", "WebSocket port (default: 24681)").option("--dev", "Use Vite dev server with HMR instead of static files").option("--background", "Start server as a background daemon").option("--_daemon", "Internal: run as spawned daemon (do not use directly)").action(server);
1018
920
  serverCmd.command("status").description("Check if the global server is running and list active sessions").action(serverStatus);
1019
921
  serverCmd.command("stop").description("Stop the running global server").action(serverStop);
1020
922
  program.parse();