@agent-native/core 0.92.4 → 0.92.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.
@@ -1,5 +1,11 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.92.5
4
+
5
+ ### Patch Changes
6
+
7
+ - eed1710: Fix `/visual-edit` live-edit 401s. The design server now mints the bridge token in `connect-localhost` and returns it from `open-visual-edit`, and `design connect` adopts it via a new `--bridge-token` flag (or `AGENT_NATIVE_BRIDGE_TOKEN`) instead of minting its own. The local bridge and the user's connection row now share the secret without the CLI needing its own auth to self-register — which was impossible under OAuth-based MCP and was the root cause of the 401s. The `visual-edit` skill is updated to call `open-visual-edit` first and start the bridge with the returned token.
8
+
3
9
  ## 0.92.4
4
10
 
5
11
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.92.4",
3
+ "version": "0.92.5",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -60,6 +60,10 @@ export interface DesignConnectArgs {
60
60
  * are present) the CLI POSTs to `/_agent-native/actions/connect-localhost`
61
61
  * with the real bridge token so the server can store it for grant minting. */
62
62
  appUrl?: string;
63
+ /** Server-minted bridge token to adopt instead of minting one, so the bridge
64
+ * matches the token already stored on the user's connection row (no
65
+ * self-registration). Also read from AGENT_NATIVE_BRIDGE_TOKEN. */
66
+ bridgeToken?: string;
63
67
  json: boolean;
64
68
  once: boolean;
65
69
  dryRun: boolean;
@@ -174,6 +178,11 @@ export function parseDesignConnectArgs(argv: string[]): DesignConnectArgs {
174
178
  index += 1;
175
179
  } else if (arg.startsWith("--app-url=")) {
176
180
  parsed.appUrl = arg.slice("--app-url=".length);
181
+ } else if (arg === "--bridge-token") {
182
+ parsed.bridgeToken = stringFlagValue(args, index, arg);
183
+ index += 1;
184
+ } else if (arg.startsWith("--bridge-token=")) {
185
+ parsed.bridgeToken = arg.slice("--bridge-token=".length);
177
186
  } else if (arg === "--json") {
178
187
  parsed.json = true;
179
188
  parsed.once = true;
@@ -1215,12 +1224,16 @@ async function walkBridgeFiles(rootPath: string): Promise<ListFilesResult> {
1215
1224
 
1216
1225
  export async function startDesignConnectBridge(
1217
1226
  manifest: DesignConnectManifest,
1227
+ seedToken?: string,
1218
1228
  ): Promise<DesignConnectBridge> {
1219
- // Mint a cryptographically random per-rootPath bridge token. This token is
1220
- // kept in-process only and is never emitted via the public GET routes so that
1221
- // an unauthenticated caller cannot read it. The server-side grant action
1222
- // obtains it out-of-band (via the exported bridge reference).
1223
- const bridgeToken = crypto.randomBytes(32).toString("hex");
1229
+ // Shared secret the browser sends (x-bridge-token) to unlock live-edit/read/
1230
+ // write. Bridge and the user's connection row must agree on it. Adopt a
1231
+ // server-minted seed when given (MCP flow); otherwise mint one and rely on
1232
+ // --app-url self-registration to push it up. Kept in-process, never served.
1233
+ const bridgeToken =
1234
+ seedToken ||
1235
+ process.env["AGENT_NATIVE_BRIDGE_TOKEN"] ||
1236
+ crypto.randomBytes(32).toString("hex");
1224
1237
  let liveEditBridgeScript = "";
1225
1238
 
1226
1239
  const server = http.createServer(
@@ -1795,6 +1808,12 @@ Options:
1795
1808
  --route-manifest <path> Non-destructive route manifest output path
1796
1809
  --app-url <url> Deployed design app URL for self-registration
1797
1810
  (also reads AGENT_NATIVE_URL / DESIGN_APP_URL env)
1811
+ --bridge-token <token> Adopt a bridge token minted server-side by the
1812
+ authenticated connect-localhost / open-visual-edit
1813
+ action instead of minting one. Used by the remote-MCP
1814
+ /visual-edit flow so the bridge and the user's stored
1815
+ connection token match with no self-registration.
1816
+ (also reads AGENT_NATIVE_BRIDGE_TOKEN env)
1798
1817
  --daemon Start the bridge detached, wait for /health, then exit
1799
1818
  --json Print the manifest JSON and exit
1800
1819
  --once Prepare/scaffold the manifest and exit
@@ -1931,25 +1950,33 @@ export async function runDesign(argv: string[]) {
1931
1950
  return 0;
1932
1951
  }
1933
1952
 
1934
- const bridge = await startDesignConnectBridge(manifest);
1953
+ const seedToken =
1954
+ parsed.bridgeToken || process.env["AGENT_NATIVE_BRIDGE_TOKEN"] || undefined;
1955
+ const bridge = await startDesignConnectBridge(manifest, seedToken);
1935
1956
  console.error("Design localhost bridge running");
1936
1957
  console.error(`Bridge: ${manifest.bridgeUrl}`);
1937
1958
  console.error(`Manifest: ${manifest.bridgeUrl}/manifest.json`);
1938
1959
  console.error(`Routes: ${manifest.routeCount}`);
1939
1960
  console.error(`Dev URL: ${manifest.devServerUrl}`);
1940
1961
 
1941
- // Self-register with the design app server (best-effort): POST the bridge
1942
- // token to connect-localhost so the server row stores the real token. Without
1943
- // it, the bridge and server tokens diverge and every write returns 401.
1944
- const appUrl = resolveAppUrl(parsed.appUrl);
1945
- if (appUrl) {
1946
- void registerConnectionWithServer(appUrl, bridge, resolveAuthToken());
1947
- } else {
1948
- // No app URL means the token never reaches the DB — surface it instead of
1949
- // silently skipping, since live-edit will then 401.
1962
+ if (seedToken) {
1963
+ // Server already stored this token on the row; bridge matches it, so no
1964
+ // self-registration needed. Zero-config path for the remote-MCP flow.
1950
1965
  console.error(
1951
- "[design connect] No app URL resolved (pass --app-url or set AGENT_NATIVE_URL); skipping self-registration — live-edit will fail to authorize.",
1966
+ "[design connect] Using server-provided bridge token; skipping self-registration.",
1952
1967
  );
1968
+ } else {
1969
+ // No seed: fall back to self-registration — POST the minted token to
1970
+ // connect-localhost. Needs an auth token in env or it 401s (the old gap).
1971
+ const appUrl = resolveAppUrl(parsed.appUrl);
1972
+ if (appUrl) {
1973
+ void registerConnectionWithServer(appUrl, bridge, resolveAuthToken());
1974
+ } else {
1975
+ // No token source at all — warn rather than 401 silently at edit time.
1976
+ console.error(
1977
+ "[design connect] No bridge token or app URL resolved (pass --bridge-token, or --app-url / AGENT_NATIVE_URL); skipping self-registration — live-edit will fail to authorize.",
1978
+ );
1979
+ }
1953
1980
  }
1954
1981
 
1955
1982
  return await new Promise<number>((resolve) => {
@@ -486,34 +486,66 @@ iframe-backed screens on the infinite canvas.
486
486
 
487
487
  ## Required Local Bridge
488
488
 
489
- From the target app repo, make sure its dev server is running, then run:
489
+ The live-edit bridge is unlocked by a shared secret (the "bridge token") that
490
+ must match on two sides: the local bridge process, and the user's connection row
491
+ in Design (which the browser reads to authorize \`/live-edit-bridge\`,
492
+ \`/read-file\`, \`/write-file\`). Get them to match by letting the
493
+ **authenticated** \`open-visual-edit\` action mint the token, then starting the
494
+ bridge with it. This is the only ordering that works for the remote-MCP flow —
495
+ the bridge cannot push its own token to the server without a CLI auth token, so
496
+ the server mints instead and the bridge adopts.
497
+
498
+ From the target app repo, make sure its dev server is running, then:
499
+
500
+ **1. Discover routes without starting a durable bridge** (one-shot, exits):
490
501
 
491
502
  \`\`\`bash
492
- npx @agent-native/core@latest design connect --url http://localhost:5173 --root . --daemon
503
+ npx @agent-native/core@latest design connect --url http://localhost:5173 --root . --json
493
504
  \`\`\`
494
505
 
495
- Use the app's real port. The command starts a detached local bridge on
496
- \`http://127.0.0.1:7331\` by default, waits for \`/health\`, prints the
497
- manifest JSON, and keeps the bridge alive after the agent command exits.
506
+ This prints the manifest (routes + capabilities). Parse it to build
507
+ \`routeManifest\` for the next step. (Skip this if the user already gave explicit
508
+ paths/URLs to place.)
509
+
510
+ **2. Call \`open-visual-edit\`** (see Action Flow below) with NO \`bridgeToken\`.
511
+ The server mints one, stores it on the user's connection row, copies it into the
512
+ placed screens' metadata, and returns it to you as \`bridgeToken\`. Capture it.
498
513
 
499
- For a manual health/manifest check:
514
+ **3. Start the persistent bridge adopting that token** (single line; prefer the
515
+ env var so the secret does not appear in \`ps\`):
516
+
517
+ \`\`\`bash
518
+ AGENT_NATIVE_BRIDGE_TOKEN="<bridgeToken from step 2>" npx @agent-native/core@latest design connect --url http://localhost:5173 --root . --daemon
519
+ \`\`\`
520
+
521
+ (Equivalently, pass \`--bridge-token <token>\`.) This starts a detached bridge on
522
+ \`http://127.0.0.1:7331\`, adopts the server-minted token — so bridge and row
523
+ agree and live-edit authorizes with no self-registration — and stays alive after
524
+ the command exits.
525
+
526
+ For a manual health/manifest check on the running bridge:
500
527
 
501
528
  \`\`\`bash
502
529
  curl http://127.0.0.1:7331/manifest.json
503
530
  \`\`\`
504
531
 
505
- Do not use \`--json\` for an editable session. \`--json\`, \`--once\`, and
506
- \`--dry-run\` print the manifest and exit, so Design will fall back to a
507
- non-editable live iframe as soon as it tries to refresh the snapshot.
532
+ Only use \`--json\` for the step-1 route probe. Never use \`--json\`, \`--once\`,
533
+ or \`--dry-run\` for the durable step-3 bridge: they print the manifest and exit,
534
+ so Design falls back to a non-editable live iframe.
508
535
 
509
536
  ## Action Flow
510
537
 
511
538
  Prefer the single authenticated \`open-visual-edit\` action. It registers or
512
- refreshes the localhost bridge, creates or reuses a Design project, places
513
- URL-backed screens, stores the active visual-edit context, and navigates to
514
- overview mode in one call. This avoids creating a private design under a
515
- synthetic CLI user and then handing the browser a tokenized URL that may be
516
- shadowed by an existing session.
539
+ refreshes the localhost bridge connection, mints and stores the bridge token,
540
+ creates or reuses a Design project, places URL-backed screens, stores the active
541
+ visual-edit context, and navigates to overview mode in one call. This avoids
542
+ creating a private design under a synthetic CLI user and then handing the browser
543
+ a tokenized URL that may be shadowed by an existing session.
544
+
545
+ Call it BEFORE starting the durable bridge (step 3 above): it does not contact
546
+ the bridge, so the bridge need not be running yet, and you need its returned
547
+ \`bridgeToken\` to start the bridge with a matching secret. Omit \`bridgeToken\`
548
+ on the call so the server mints one.
517
549
 
518
550
  \`\`\`bash
519
551
  pnpm action open-visual-edit '{
@@ -526,8 +558,12 @@ pnpm action open-visual-edit '{
526
558
  }'
527
559
  \`\`\`
528
560
 
529
- The action returns \`designId\`, \`connectionId\`, \`screens\`, \`urlPath\`, and
530
- \`openUrl\`. Keep those IDs in the chat context for follow-ups.
561
+ The action returns \`designId\`, \`connectionId\`, \`bridgeToken\`, \`screens\`,
562
+ \`urlPath\`, and \`openUrl\`. Keep \`designId\`/\`connectionId\` in the chat context
563
+ for follow-ups, and pass \`bridgeToken\` to \`design connect\` (step 3) to start
564
+ the bridge. On follow-up calls reusing an existing \`connectionId\`, the same
565
+ token is returned (it is minted once and reused), so the running bridge stays
566
+ valid.
531
567
 
532
568
  For a numbered flow the user describes in chat, keep the labels and order:
533
569
 
@@ -60,31 +60,43 @@ iframe-backed screens on the infinite canvas.
60
60
 
61
61
  ## Required Local Bridge
62
62
 
63
- From the target app repo, make sure its dev server is running, then run the
64
- Design bridge with `npx`:
63
+ The live-edit bridge is unlocked by a shared secret (the "bridge token") that
64
+ must match on two sides: the local bridge process and the user's connection row
65
+ (which the browser reads to authorize `/live-edit-bridge`, `/read-file`,
66
+ `/write-file`). Let the authenticated `connect-localhost` / `open-visual-edit`
67
+ action mint the token, then start the bridge adopting it — the bridge cannot
68
+ push its own token to the server without a CLI auth token, so the server mints.
69
+
70
+ From the target app repo, make sure its dev server is running, then:
71
+
72
+ **1. Discover routes without a durable bridge** (one-shot, exits):
65
73
 
66
74
  ```bash
67
- npx @agent-native/core@latest design connect --url http://localhost:5173 --root .
75
+ npx @agent-native/core@latest design connect --url http://localhost:5173 --root . --json
68
76
  ```
69
77
 
70
- Use the app's real port. The command starts a local bridge on
71
- `http://127.0.0.1:7331` by default and exposes:
78
+ Prints the manifest (routes + capabilities). Use it to build `routeManifest` for
79
+ the action call. Skip if the user already gave explicit paths/URLs.
72
80
 
73
- - `GET /manifest.json` dev server URL, bridge URL, discovered routes, root.
74
- - `GET /routes.json` route manifest only.
75
- - `GET /health` bridge liveness.
81
+ **2. Register the connection** via `connect-localhost` / `open-visual-edit`
82
+ (Action Flow below) with NO `bridgeToken`. The server mints one, stores it on the
83
+ connection row, and returns it as `bridgeToken`. Capture it.
76
84
 
77
- For one-shot agent setup, ask for JSON and keep the long-running bridge open in
78
- a second terminal if the user needs live updates:
85
+ **3. Start the persistent bridge adopting that token:**
79
86
 
80
87
  ```bash
81
- npx @agent-native/core@latest design connect --url http://localhost:5173 --root .
82
- curl http://127.0.0.1:7331/manifest.json
88
+ AGENT_NATIVE_BRIDGE_TOKEN="<bridgeToken from step 2>" \
89
+ npx @agent-native/core@latest design connect \
90
+ --url http://localhost:5173 --root . --daemon
83
91
  ```
84
92
 
85
- Do not use `--json` for an editable session. `--json`, `--once`, and
86
- `--dry-run` print the manifest and exit, so Design will fall back to a
87
- non-editable live iframe as soon as it tries to refresh the snapshot.
93
+ (Or pass `--bridge-token <token>`; prefer the env var to keep the secret out of
94
+ `ps`.) The bridge exposes `GET /manifest.json`, `GET /routes.json`,
95
+ `GET /health`, and now authorizes live-edit because its token matches the row.
96
+
97
+ Only use `--json` for the step-1 route probe. Never use `--json`, `--once`, or
98
+ `--dry-run` for the durable step-3 bridge: they print the manifest and exit, so
99
+ Design falls back to a non-editable live iframe.
88
100
 
89
101
  ## Action Flow
90
102
 
@@ -5,7 +5,7 @@ import {
5
5
  getRequestOrgId,
6
6
  getRequestUserEmail,
7
7
  } from "@agent-native/core/server/request-context";
8
- import { eq } from "drizzle-orm";
8
+ import { and, eq, sql } from "drizzle-orm";
9
9
  import { z } from "zod";
10
10
 
11
11
  import { getDb, schema } from "../server/db/index.js";
@@ -202,8 +202,14 @@ export default defineAction({
202
202
  );
203
203
  }
204
204
 
205
- const nextBridgeToken = args.bridgeToken?.trim() || undefined;
206
- const values = {
205
+ // Token for a new row: explicit, else existing, else mint. The authenticated
206
+ // action owning the mint is what lets the CLI skip its own auth (the 401 gap).
207
+ const explicitToken = args.bridgeToken?.trim() || undefined;
208
+ const nextBridgeToken =
209
+ explicitToken ||
210
+ existing[0]?.bridgeToken ||
211
+ crypto.randomBytes(32).toString("hex");
212
+ const baseValues = {
207
213
  id,
208
214
  name: args.name ?? new URL(devServerUrl).host,
209
215
  sourceType: "localhost" as const,
@@ -212,7 +218,6 @@ export default defineAction({
212
218
  rootPath: routeManifest.rootPath ?? null,
213
219
  routeManifest: JSON.stringify(routeManifest),
214
220
  capabilities: JSON.stringify(capabilities),
215
- bridgeToken: nextBridgeToken ?? existing[0]?.bridgeToken ?? null,
216
221
  status: args.status,
217
222
  lastSeenAt: now,
218
223
  ownerEmail,
@@ -220,23 +225,44 @@ export default defineAction({
220
225
  updatedAt: now,
221
226
  };
222
227
 
223
- // Atomic upsert keyed on the id primary key no check-then-insert race.
224
- // setWhere keeps a concurrent insert by another user (TOCTOU on the
225
- // ownership check above) from being overwritten: on a cross-user conflict
226
- // the update filters to a no-op instead of hijacking the other row.
228
+ // On conflict, an explicit token overwrites; a server-minted one uses
229
+ // coalesce(existing, minted) evaluated at write time it fills a null token
230
+ // but never clobbers one, so concurrent first-time callers converge on the
231
+ // first writer (read->mint->write isn't atomic). setWhere keeps a cross-user
232
+ // conflict a no-op.
227
233
  await db
228
234
  .insert(schema.designLocalhostConnections)
229
- .values({ ...values, createdAt: now })
235
+ .values({ ...baseValues, bridgeToken: nextBridgeToken, createdAt: now })
230
236
  .onConflictDoUpdate({
231
237
  target: schema.designLocalhostConnections.id,
232
- set: values,
238
+ set: {
239
+ ...baseValues,
240
+ bridgeToken: explicitToken
241
+ ? nextBridgeToken
242
+ : sql`coalesce(${schema.designLocalhostConnections.bridgeToken}, excluded.bridge_token)`,
243
+ },
233
244
  setWhere: eq(schema.designLocalhostConnections.ownerEmail, ownerEmail),
234
245
  });
235
246
 
247
+ // Return the token the row actually holds (owner-scoped, so a cross-user
248
+ // no-op never leaks another user's token), not the one we minted — so
249
+ // concurrent callers converge on the winner (no 401 on a lost race).
250
+ const [stored] = await db
251
+ .select({ bridgeToken: schema.designLocalhostConnections.bridgeToken })
252
+ .from(schema.designLocalhostConnections)
253
+ .where(
254
+ and(
255
+ eq(schema.designLocalhostConnections.id, id),
256
+ eq(schema.designLocalhostConnections.ownerEmail, ownerEmail),
257
+ ),
258
+ )
259
+ .limit(1);
260
+ const effectiveBridgeToken = stored?.bridgeToken ?? nextBridgeToken;
261
+
236
262
  return {
237
263
  id,
238
264
  sourceType: "localhost",
239
- name: values.name,
265
+ name: baseValues.name,
240
266
  devServerUrl,
241
267
  bridgeUrl: bridgeUrl ?? null,
242
268
  rootPath: routeManifest.rootPath ?? null,
@@ -245,6 +271,9 @@ export default defineAction({
245
271
  capabilities,
246
272
  status: args.status,
247
273
  lastSeenAt: now,
274
+ // Returned so the caller can start the bridge with
275
+ // `design connect --bridge-token <this>`, matching this row.
276
+ bridgeToken: effectiveBridgeToken,
248
277
  };
249
278
  },
250
279
  });
@@ -162,7 +162,10 @@ export default defineAction({
162
162
  .string()
163
163
  .optional()
164
164
  .describe(
165
- "Real bridge token from the running bridge. Stored server-side only; never returned.",
165
+ "Optional bridge token to store on the connection (e.g. one a CLI " +
166
+ "self-registered). Omit it and the server mints one, stores it, and " +
167
+ "returns it as `bridgeToken` so the caller can start the local bridge " +
168
+ "with `design connect --bridge-token <token>`.",
166
169
  ),
167
170
  routes: jsonArray(z.array(screenRouteSchema))
168
171
  .optional()
@@ -315,6 +318,9 @@ export default defineAction({
315
318
  overview: true,
316
319
  urlPath,
317
320
  openUrl: designOverviewDeepLink(designId),
321
+ // Minted/stored by connect-localhost; the skill starts the bridge with
322
+ // `design connect --bridge-token <this>` so bridge and row agree.
323
+ bridgeToken: connection.bridgeToken,
318
324
  };
319
325
  },
320
326
  link: ({ result }) => {
@@ -17,6 +17,10 @@ export interface DesignConnectArgs {
17
17
  * are present) the CLI POSTs to `/_agent-native/actions/connect-localhost`
18
18
  * with the real bridge token so the server can store it for grant minting. */
19
19
  appUrl?: string;
20
+ /** Server-minted bridge token to adopt instead of minting one, so the bridge
21
+ * matches the token already stored on the user's connection row (no
22
+ * self-registration). Also read from AGENT_NATIVE_BRIDGE_TOKEN. */
23
+ bridgeToken?: string;
20
24
  json: boolean;
21
25
  once: boolean;
22
26
  dryRun: boolean;
@@ -105,7 +109,7 @@ export interface ListFilesResult {
105
109
  files: ListedBridgeFile[];
106
110
  truncated: boolean;
107
111
  }
108
- export declare function startDesignConnectBridge(manifest: DesignConnectManifest): Promise<DesignConnectBridge>;
112
+ export declare function startDesignConnectBridge(manifest: DesignConnectManifest, seedToken?: string): Promise<DesignConnectBridge>;
109
113
  /**
110
114
  * Resolve the design app URL from an explicit value or environment variables.
111
115
  * Returns undefined when no URL is configured (registration is optional).
@@ -1 +1 @@
1
- {"version":3,"file":"design-connect.d.ts","sourceRoot":"","sources":["../../src/cli/design-connect.ts"],"names":[],"mappings":"AAIA,OAAa,EAEX,KAAK,MAAM,EAEZ,MAAM,WAAW,CAAC;AAcnB,QAAA,MAAM,iBAAiB,YACrB,QAAQ,EACR,mBAAmB,EACnB,UAAU,EACV,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,WAAW,CACH,CAAC;AAEX,KAAK,eAAe,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAY1D,kFAAkF;AAClF,QAAA,MAAM,qBAAqB;aACzB,SAAS;aACT,aAAa;aACb,cAAc;CACN,CAAC;AAEX,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;mFAG+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,cAAc,GAAG,MAAM,GAAG,QAAQ,CAAC;CAChD;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,6BAA6B,CAAC;IACtC,UAAU,EAAE,WAAW,CAAC;IACxB,SAAS,EAAE,IAAI,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,KAAK,CAAC;QAClB,SAAS,EAAE,eAAe,CAAC;QAC3B,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,UAAU,CAAC;QAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;IACH;;;;OAIG;IACH,oBAAoB,EAAE,OAAO,qBAAqB,CAAC;CACpD;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,qBAAqB,CAAC;IAChC;;kFAE8E;IAC9E,WAAW,EAAE,MAAM,CAAC;CACrB;AAyBD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,iBAAiB,CAqExE;AAiED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,EAAE,CA+CvE;AAgFD,wBAAgB,mCAAmC,CACjD,OAAO,EAAE,IAAI,CAAC,qBAAqB,EAAE,cAAc,GAAG,UAAU,CAAC,EACjE,SAAS,EAAE,IAAI,CAAC,qBAAqB,EAAE,cAAc,GAAG,UAAU,CAAC,GAClE,OAAO,CAaT;AA6CD,wBAAsB,4BAA4B,CAChD,OAAO,GAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAC3D,OAAO,CAAC,qBAAqB,CAAC,CA2ChC;AAydD,MAAM,WAAW,aAAa;IAC5B,oFAAoF;IACpF,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,QAAQ,EAAE,OAAO,CAAC;IAClB,qEAAqE;IACrE,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,EAAE,CAchE;AAgCD,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,aAAa,EAAE,EACtB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,OAAO,GACb,OAAO,CAET;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;IAAE,SAAS,EAAE,aAAa,EAAE,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1D,OAAO,CA4BT;AAqBD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;CACpB;AAuGD,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,qBAAqB,GAC9B,OAAO,CAAC,mBAAmB,CAAC,CAuc9B;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAmBnE;AAiBD;;;;;;;;;GASG;AACH,wBAAsB,4BAA4B,CAChD,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,mBAAmB,EAC3B,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAwDf;AAuHD,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,mBA2D7C"}
1
+ {"version":3,"file":"design-connect.d.ts","sourceRoot":"","sources":["../../src/cli/design-connect.ts"],"names":[],"mappings":"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"}
@@ -110,6 +110,13 @@ export function parseDesignConnectArgs(argv) {
110
110
  else if (arg.startsWith("--app-url=")) {
111
111
  parsed.appUrl = arg.slice("--app-url=".length);
112
112
  }
113
+ else if (arg === "--bridge-token") {
114
+ parsed.bridgeToken = stringFlagValue(args, index, arg);
115
+ index += 1;
116
+ }
117
+ else if (arg.startsWith("--bridge-token=")) {
118
+ parsed.bridgeToken = arg.slice("--bridge-token=".length);
119
+ }
113
120
  else if (arg === "--json") {
114
121
  parsed.json = true;
115
122
  parsed.once = true;
@@ -1004,12 +1011,14 @@ async function walkBridgeFiles(rootPath) {
1004
1011
  await walk(resolvedRoot, "");
1005
1012
  return { files, truncated };
1006
1013
  }
1007
- export async function startDesignConnectBridge(manifest) {
1008
- // Mint a cryptographically random per-rootPath bridge token. This token is
1009
- // kept in-process only and is never emitted via the public GET routes so that
1010
- // an unauthenticated caller cannot read it. The server-side grant action
1011
- // obtains it out-of-band (via the exported bridge reference).
1012
- const bridgeToken = crypto.randomBytes(32).toString("hex");
1014
+ export async function startDesignConnectBridge(manifest, seedToken) {
1015
+ // Shared secret the browser sends (x-bridge-token) to unlock live-edit/read/
1016
+ // write. Bridge and the user's connection row must agree on it. Adopt a
1017
+ // server-minted seed when given (MCP flow); otherwise mint one and rely on
1018
+ // --app-url self-registration to push it up. Kept in-process, never served.
1019
+ const bridgeToken = seedToken ||
1020
+ process.env["AGENT_NATIVE_BRIDGE_TOKEN"] ||
1021
+ crypto.randomBytes(32).toString("hex");
1013
1022
  let liveEditBridgeScript = "";
1014
1023
  const server = http.createServer((req, res) => {
1015
1024
  if (req.method === "OPTIONS") {
@@ -1492,6 +1501,12 @@ Options:
1492
1501
  --route-manifest <path> Non-destructive route manifest output path
1493
1502
  --app-url <url> Deployed design app URL for self-registration
1494
1503
  (also reads AGENT_NATIVE_URL / DESIGN_APP_URL env)
1504
+ --bridge-token <token> Adopt a bridge token minted server-side by the
1505
+ authenticated connect-localhost / open-visual-edit
1506
+ action instead of minting one. Used by the remote-MCP
1507
+ /visual-edit flow so the bridge and the user's stored
1508
+ connection token match with no self-registration.
1509
+ (also reads AGENT_NATIVE_BRIDGE_TOKEN env)
1495
1510
  --daemon Start the bridge detached, wait for /health, then exit
1496
1511
  --json Print the manifest JSON and exit
1497
1512
  --once Prepare/scaffold the manifest and exit
@@ -1599,23 +1614,29 @@ export async function runDesign(argv) {
1599
1614
  console.log(JSON.stringify(manifest, null, 2));
1600
1615
  return 0;
1601
1616
  }
1602
- const bridge = await startDesignConnectBridge(manifest);
1617
+ const seedToken = parsed.bridgeToken || process.env["AGENT_NATIVE_BRIDGE_TOKEN"] || undefined;
1618
+ const bridge = await startDesignConnectBridge(manifest, seedToken);
1603
1619
  console.error("Design localhost bridge running");
1604
1620
  console.error(`Bridge: ${manifest.bridgeUrl}`);
1605
1621
  console.error(`Manifest: ${manifest.bridgeUrl}/manifest.json`);
1606
1622
  console.error(`Routes: ${manifest.routeCount}`);
1607
1623
  console.error(`Dev URL: ${manifest.devServerUrl}`);
1608
- // Self-register with the design app server (best-effort): POST the bridge
1609
- // token to connect-localhost so the server row stores the real token. Without
1610
- // it, the bridge and server tokens diverge and every write returns 401.
1611
- const appUrl = resolveAppUrl(parsed.appUrl);
1612
- if (appUrl) {
1613
- void registerConnectionWithServer(appUrl, bridge, resolveAuthToken());
1624
+ if (seedToken) {
1625
+ // Server already stored this token on the row; bridge matches it, so no
1626
+ // self-registration needed. Zero-config path for the remote-MCP flow.
1627
+ console.error("[design connect] Using server-provided bridge token; skipping self-registration.");
1614
1628
  }
1615
1629
  else {
1616
- // No app URL means the token never reaches the DB surface it instead of
1617
- // silently skipping, since live-edit will then 401.
1618
- console.error("[design connect] No app URL resolved (pass --app-url or set AGENT_NATIVE_URL); skipping self-registration — live-edit will fail to authorize.");
1630
+ // No seed: fall back to self-registration POST the minted token to
1631
+ // connect-localhost. Needs an auth token in env or it 401s (the old gap).
1632
+ const appUrl = resolveAppUrl(parsed.appUrl);
1633
+ if (appUrl) {
1634
+ void registerConnectionWithServer(appUrl, bridge, resolveAuthToken());
1635
+ }
1636
+ else {
1637
+ // No token source at all — warn rather than 401 silently at edit time.
1638
+ console.error("[design connect] No bridge token or app URL resolved (pass --bridge-token, or --app-url / AGENT_NATIVE_URL); skipping self-registration — live-edit will fail to authorize.");
1639
+ }
1619
1640
  }
1620
1641
  return await new Promise((resolve) => {
1621
1642
  const stop = () => {