@nudge-ui/mcp 0.2.4 → 0.2.5

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/cli.js CHANGED
@@ -1,48 +1,17 @@
1
- import { createHash } from "node:crypto";
2
- import { spawn } from "node:child_process";
3
- import { basename, resolve } from "node:path";
4
- import { defaultBridgePort } from "./internal/agent-protocol.js";
1
+ import { resolve } from "node:path";
5
2
  import { diagnoseProjectSessions, discoverProjectSessions } from "./discovery.js";
6
- import { createAgentCompanion, createDiscoveredAgentAdapter, } from "./server.js";
7
- const DEFAULT_PORT = 0;
8
- const DEFAULT_COMMAND_TIMEOUT_MS = 5_000;
9
- const PAGE_PROBE_TIMEOUT_MS = 1_500;
10
- const PAGE_LAUNCH_DEPENDENCIES = {
11
- fetch: globalThis.fetch,
12
- platform: process.platform,
13
- spawn: (command, args, options) => spawn(command, args, options),
14
- warn: (message) => process.stderr.write(`${message}\n`),
15
- };
16
- /** Opens a reachable paired project page using the platform URL handler. */
17
- export async function openPairedPage(pageUrl, dependencies = PAGE_LAUNCH_DEPENDENCIES) {
18
- try {
19
- const response = await dependencies.fetch(pageUrl, {
20
- method: "GET",
21
- signal: AbortSignal.timeout(PAGE_PROBE_TIMEOUT_MS),
22
- });
23
- if (!response.ok)
24
- throw new Error(`page returned ${response.status}`);
25
- const command = dependencies.platform === "darwin"
26
- ? { executable: "open", args: [pageUrl] }
27
- : dependencies.platform === "win32"
28
- ? { executable: "cmd", args: ["/c", "start", "", pageUrl] }
29
- : { executable: "xdg-open", args: [pageUrl] };
30
- const child = dependencies.spawn(command.executable, command.args, {
31
- detached: true,
32
- stdio: "ignore",
33
- });
34
- child.unref();
35
- return true;
36
- }
37
- catch (error) {
38
- const detail = error instanceof Error ? ` (${error.message})` : "";
39
- dependencies.warn(`[nudge-ui] paired page is unavailable${detail}; open ${pageUrl} manually`);
40
- return false;
41
- }
42
- }
43
- function projectIdFor(root) {
44
- const name = basename(root);
45
- return name.length > 0 ? name : `project-${createHash("sha256").update(root).digest("hex").slice(0, 24)}`;
3
+ import { createDiscoveredAgentAdapter } from "./server.js";
4
+ const REMOVED_OPTIONS = new Set(["--origin", "--project-id", "--host", "--port", "--command-timeout-ms", "--workspace-root"]);
5
+ const REMOVED_ENVIRONMENT = [
6
+ "NUDGE_UI_PROJECT_ID",
7
+ "NUDGE_UI_ORIGIN",
8
+ "NUDGE_UI_WORKSPACE_ROOT",
9
+ "NUDGE_UI_BRIDGE_HOST",
10
+ "NUDGE_UI_BRIDGE_PORT",
11
+ "NUDGE_UI_COMMAND_TIMEOUT_MS",
12
+ ];
13
+ function removedConfiguration(name) {
14
+ return new TypeError(`${name} configured the removed coupled or startup-workspace mode. Rerun \`npx nudge-ui agent setup\` in the project to register the reusable adapter.`);
46
15
  }
47
16
  function valueAfter(argv, index, flag) {
48
17
  const value = argv[index + 1];
@@ -50,93 +19,42 @@ function valueAfter(argv, index, flag) {
50
19
  throw new TypeError(`${flag} requires a value`);
51
20
  return value;
52
21
  }
53
- function parseInteger(value, flag, maximum) {
54
- if (!/^\d+$/.test(value))
55
- throw new TypeError(`${flag} must be a non-negative integer`);
56
- const parsed = Number(value);
57
- if (!Number.isSafeInteger(parsed) || parsed > maximum)
58
- throw new TypeError(`${flag} is out of range`);
59
- return parsed;
60
- }
61
- /** Parses only project-scoped, host-neutral CLI options. */
22
+ /** Serving takes no options; `doctor` accepts a diagnostic scope. */
62
23
  export function parseCliArguments(argv = process.argv.slice(2), environment = process.env) {
63
24
  const command = argv[0] === "doctor" ? "doctor" : "serve";
64
25
  const argumentsToParse = command === "doctor" ? argv.slice(1) : argv;
65
- const root = resolve(environment.NUDGE_UI_WORKSPACE_ROOT ?? environment.INIT_CWD ?? process.cwd());
66
- let projectId = environment.NUDGE_UI_PROJECT_ID ?? projectIdFor(root);
67
- let origin = environment.NUDGE_UI_ORIGIN;
68
- // SAFETY: NUDGE_UI_BRIDGE_HOST is trusted operator config for a loopback-bound dev bridge; CliOptions["host"] is its declared address union.
69
- let host = (environment.NUDGE_UI_BRIDGE_HOST ?? "127.0.0.1");
70
- let port = environment.NUDGE_UI_BRIDGE_PORT === undefined
71
- ? DEFAULT_PORT
72
- : parseInteger(environment.NUDGE_UI_BRIDGE_PORT, "NUDGE_UI_BRIDGE_PORT", 65_535);
73
- let commandTimeoutMs = environment.NUDGE_UI_COMMAND_TIMEOUT_MS === undefined
74
- ? DEFAULT_COMMAND_TIMEOUT_MS
75
- : parseInteger(environment.NUDGE_UI_COMMAND_TIMEOUT_MS, "NUDGE_UI_COMMAND_TIMEOUT_MS", 60_000);
76
- let workspaceRoot = environment.NUDGE_UI_WORKSPACE_ROOT === undefined ? undefined : root;
26
+ let workspaceRoot;
77
27
  let help = false;
78
- let explicitPort = environment.NUDGE_UI_BRIDGE_PORT !== undefined;
79
28
  for (let index = 0; index < argumentsToParse.length; index += 1) {
80
29
  const argument = argumentsToParse[index];
81
- switch (argument) {
82
- case "--help":
83
- case "-h":
84
- help = true;
85
- break;
86
- case "--project-id":
87
- projectId = valueAfter(argumentsToParse, index, argument);
88
- index += 1;
89
- break;
90
- case "--origin":
91
- origin = valueAfter(argumentsToParse, index, argument);
92
- index += 1;
93
- break;
94
- case "--workspace-root":
95
- workspaceRoot = resolve(valueAfter(argumentsToParse, index, argument));
96
- index += 1;
97
- break;
98
- case "--host": {
99
- const candidate = valueAfter(argumentsToParse, index, argument);
100
- if (candidate !== "127.0.0.1" && candidate !== "::1" && candidate !== "localhost") {
101
- throw new TypeError("--host must be 127.0.0.1, ::1, or localhost");
102
- }
103
- host = candidate;
104
- index += 1;
105
- break;
106
- }
107
- case "--port":
108
- port = parseInteger(valueAfter(argumentsToParse, index, argument), argument, 65_535);
109
- explicitPort = true;
110
- index += 1;
111
- break;
112
- case "--command-timeout-ms":
113
- commandTimeoutMs = parseInteger(valueAfter(argumentsToParse, index, argument), argument, 60_000);
114
- if (commandTimeoutMs === 0)
115
- throw new TypeError("--command-timeout-ms must be greater than zero");
116
- index += 1;
117
- break;
118
- default:
119
- throw new TypeError(`Unknown option ${argument}`);
30
+ if (argument === "--help" || argument === "-h") {
31
+ help = true;
32
+ }
33
+ else if (command === "doctor" && argument === "--workspace-root") {
34
+ workspaceRoot = resolve(valueAfter(argumentsToParse, index, argument));
35
+ index += 1;
36
+ }
37
+ else if (REMOVED_OPTIONS.has(argument)) {
38
+ throw removedConfiguration(argument);
39
+ }
40
+ else {
41
+ throw new TypeError(`Unknown option ${argument}`);
120
42
  }
121
43
  }
122
- if (projectId.length === 0 || projectId.length > 256)
123
- throw new TypeError("--project-id must be a bounded non-empty string");
124
- if (!explicitPort)
125
- port = defaultBridgePort(projectId);
126
- return { command, projectId, origin, workspaceRoot, host, port, commandTimeoutMs, help };
44
+ if (command === "serve") {
45
+ const removed = REMOVED_ENVIRONMENT.find((name) => environment[name] !== undefined);
46
+ if (removed)
47
+ throw removedConfiguration(removed);
48
+ }
49
+ return { command, ...(workspaceRoot === undefined ? {} : { workspaceRoot }), help };
127
50
  }
128
51
  export const CLI_USAGE = `Usage: nudge-mcp [doctor] [options]
129
52
 
130
- Starts a standard MCP stdio adapter. It discovers the bridge owned by the
131
- running project unless --origin selects the legacy coupled mode.
53
+ Starts a standard MCP stdio adapter that discovers the bridges owned by running
54
+ projects. Every tool call names the checkout or application it targets.
132
55
 
133
56
  Options:
134
- --project-id <id> Stable project pairing identity
135
- --origin <origin> Exact app origin required for browser pairing
136
- --workspace-root <path> Optional legacy project restriction; omitted for reusable adapters
137
- --host <loopback> 127.0.0.1, ::1, or localhost
138
- --port <port> Bridge port; default is deterministic per project, 0 is ephemeral
139
- --command-timeout-ms <ms> Canvas acknowledgement timeout
57
+ --workspace-root <path> doctor only: limit diagnostics to one checkout or application
140
58
  --help Show this message
141
59
  `;
142
60
  /** Runs the CLI until the stdio MCP connection closes or the process stops. */
@@ -162,74 +80,42 @@ export async function runCli(argv = process.argv.slice(2), environment = process
162
80
  ...(matches.length === 0
163
81
  ? ["[nudge-ui] no reachable compatible bridge matches; inspect the diagnostics above or start the development server"]
164
82
  : matches.map((session) => {
165
- const state = session.status.connection === "working"
166
- ? "working"
167
- : session.status.listenerActive ? "listening" : "available";
83
+ const state = session.status.connection === "delivered"
84
+ ? "awaiting acknowledgment"
85
+ : session.status.connection === "working"
86
+ ? "working"
87
+ : session.status.listenerActive ? "listening" : "available";
168
88
  return `[nudge-ui] ${state}: ${session.projectId} (${session.origin})${session.claimed ? " [claimed]" : ""}`;
169
89
  })),
170
90
  ].join("\n") + "\n");
171
91
  return;
172
92
  }
173
- if (!options.origin) {
174
- const adapter = await createDiscoveredAgentAdapter({ workspaceRoot: options.workspaceRoot });
175
- let stopping = false;
176
- let resolveAdapterWait = null;
177
- const stop = () => {
178
- if (stopping)
179
- return;
180
- stopping = true;
181
- void adapter.close();
182
- resolveAdapterWait?.();
183
- };
184
- process.once("SIGINT", stop);
185
- process.once("SIGTERM", stop);
186
- try {
187
- await adapter.start();
188
- await new Promise((resolveAdapter) => {
189
- const finish = () => resolveAdapter();
190
- // Keep a direct resolver so signal handlers terminate even though
191
- // installing a signal handler suppresses Node's default process exit.
192
- resolveAdapterWait = finish;
193
- process.stdin.once("end", finish);
194
- process.once("exit", finish);
195
- });
196
- }
197
- finally {
198
- process.off("SIGINT", stop);
199
- process.off("SIGTERM", stop);
200
- await adapter.close();
201
- }
202
- return;
203
- }
204
- process.stderr.write("[nudge-ui] --origin uses deprecated coupled mode. Rerun agent setup to use project-owned bridges and avoid port collisions.\n");
205
- const companion = createAgentCompanion({
206
- ...options,
207
- workspaceRoot: options.workspaceRoot ?? resolve(environment.INIT_CWD ?? process.cwd()),
208
- onControllerUnavailable: (pageUrl) => openPairedPage(pageUrl).then(() => undefined),
209
- });
93
+ const adapter = createDiscoveredAgentAdapter();
210
94
  let stopping = false;
95
+ let resolveAdapterWait = null;
211
96
  const stop = () => {
212
97
  if (stopping)
213
98
  return;
214
99
  stopping = true;
215
- void companion.close();
100
+ void adapter.close();
101
+ resolveAdapterWait?.();
216
102
  };
217
103
  process.once("SIGINT", stop);
218
104
  process.once("SIGTERM", stop);
219
105
  try {
220
- const started = await companion.start();
221
- // stdout belongs to MCP stdio. Keep discovery information on stderr so it
222
- // can be observed by a host without corrupting JSON-RPC framing.
223
- process.stderr.write(`[nudge-ui] browser bridge listening at ${started.address.url}\n`);
224
- await new Promise((resolveWait) => {
225
- const close = () => resolveWait();
226
- companion.bridge.httpServer.once("close", close);
227
- process.once("exit", close);
106
+ await adapter.start();
107
+ await new Promise((resolveAdapter) => {
108
+ const finish = () => resolveAdapter();
109
+ // Keep a direct resolver so signal handlers terminate even though
110
+ // installing a signal handler suppresses Node's default process exit.
111
+ resolveAdapterWait = finish;
112
+ process.stdin.once("end", finish);
113
+ process.once("exit", finish);
228
114
  });
229
115
  }
230
116
  finally {
231
117
  process.off("SIGINT", stop);
232
118
  process.off("SIGTERM", stop);
233
- await companion.close();
119
+ await adapter.close();
234
120
  }
235
121
  }