@agent-native/core 0.92.3 → 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.
- package/corpus/core/CHANGELOG.md +12 -0
- package/corpus/core/package.json +2 -2
- package/corpus/core/src/cli/design-connect.ts +43 -16
- package/corpus/core/src/cli/skills.ts +52 -16
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +27 -15
- package/corpus/templates/design/actions/connect-localhost.ts +40 -11
- package/corpus/templates/design/actions/open-visual-edit.ts +7 -1
- package/dist/cli/design-connect.d.ts +5 -1
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +37 -16
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +52 -16
- package/dist/cli/skills.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/package.json +2 -2
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
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
|
+
|
|
9
|
+
## 0.92.4
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 86697e9: Depend on `@agent-native/toolkit` via `workspace:^` instead of `workspace:*`. Publishing now pins a caret range (e.g. `^0.4.3`) rather than an exact version, so an app that scaffolds `@agent-native/toolkit@latest` separately can dedupe against it through normal semver resolution instead of installing two mismatched toolkit copies side by side (which crashed Vite with `"./collab-ui" is not exported`).
|
|
14
|
+
|
|
3
15
|
## 0.92.3
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.92.
|
|
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": {
|
|
@@ -235,7 +235,7 @@
|
|
|
235
235
|
"prepublishOnly": "npm run build"
|
|
236
236
|
},
|
|
237
237
|
"dependencies": {
|
|
238
|
-
"@agent-native/toolkit": "workspace
|
|
238
|
+
"@agent-native/toolkit": "workspace:^",
|
|
239
239
|
"@amplitude/analytics-browser": "^2.41.1",
|
|
240
240
|
"@anthropic-ai/sdk": "^0.90.0",
|
|
241
241
|
"@anthropic-ai/tokenizer": "0.0.4",
|
|
@@ -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
|
-
//
|
|
1220
|
-
//
|
|
1221
|
-
//
|
|
1222
|
-
//
|
|
1223
|
-
const bridgeToken =
|
|
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
|
|
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
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
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]
|
|
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
|
-
|
|
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 . --
|
|
503
|
+
npx @agent-native/core@latest design connect --url http://localhost:5173 --root . --json
|
|
493
504
|
\`\`\`
|
|
494
505
|
|
|
495
|
-
|
|
496
|
-
\`
|
|
497
|
-
|
|
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
|
-
|
|
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
|
-
|
|
506
|
-
\`--dry-run\`
|
|
507
|
-
|
|
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,
|
|
513
|
-
URL-backed screens, stores the active
|
|
514
|
-
overview mode in one call. This avoids
|
|
515
|
-
synthetic CLI user and then handing the browser
|
|
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\`, \`
|
|
530
|
-
\`openUrl\`. Keep
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
71
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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
|
-
|
|
82
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
206
|
-
|
|
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
|
-
//
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
//
|
|
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({ ...
|
|
235
|
+
.values({ ...baseValues, bridgeToken: nextBridgeToken, createdAt: now })
|
|
230
236
|
.onConflictDoUpdate({
|
|
231
237
|
target: schema.designLocalhostConnections.id,
|
|
232
|
-
set:
|
|
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:
|
|
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
|
-
"
|
|
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,
|
|
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
|
-
//
|
|
1009
|
-
//
|
|
1010
|
-
//
|
|
1011
|
-
//
|
|
1012
|
-
const bridgeToken =
|
|
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
|
|
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
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
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
|
|
1617
|
-
//
|
|
1618
|
-
|
|
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 = () => {
|