@agent-native/core 0.20.8 → 0.21.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/action.d.ts +61 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +14 -0
- package/dist/action.js.map +1 -1
- package/dist/agent/production-agent.d.ts +4 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +19 -7
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/types.d.ts +2 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/cli/code-agent-executor.d.ts.map +1 -1
- package/dist/cli/code-agent-executor.js +1 -0
- package/dist/cli/code-agent-executor.js.map +1 -1
- package/dist/cli/connect.d.ts +26 -4
- package/dist/cli/connect.d.ts.map +1 -1
- package/dist/cli/connect.js +578 -10
- package/dist/cli/connect.js.map +1 -1
- package/dist/cli/templates-meta.d.ts.map +1 -1
- package/dist/cli/templates-meta.js +1 -0
- package/dist/cli/templates-meta.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +6 -5
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +25 -4
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/code-agent-chat-adapter.js +1 -0
- package/dist/client/code-agent-chat-adapter.js.map +1 -1
- package/dist/client/composer/TiptapComposer.js +1 -1
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/conversation/AgentConversation.d.ts.map +1 -1
- package/dist/client/conversation/AgentConversation.js +3 -2
- package/dist/client/conversation/AgentConversation.js.map +1 -1
- package/dist/client/conversation/code-agent-transcript.js +1 -0
- package/dist/client/conversation/code-agent-transcript.js.map +1 -1
- package/dist/client/conversation/types.d.ts +2 -0
- package/dist/client/conversation/types.d.ts.map +1 -1
- package/dist/client/conversation/types.js.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.js +2 -1
- package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
- package/dist/client/extensions/ExtensionEditor.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionEditor.js +6 -3
- package/dist/client/extensions/ExtensionEditor.js.map +1 -1
- package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionViewer.js +66 -2
- package/dist/client/extensions/ExtensionViewer.js.map +1 -1
- package/dist/client/extensions/ExtensionsListPage.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionsListPage.js +2 -1
- package/dist/client/extensions/ExtensionsListPage.js.map +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.js +5 -7
- package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
- package/dist/client/extensions/index.d.ts +1 -0
- package/dist/client/extensions/index.d.ts.map +1 -1
- package/dist/client/extensions/index.js +1 -0
- package/dist/client/extensions/index.js.map +1 -1
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/mcp-apps/McpAppRenderer.d.ts +10 -0
- package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -0
- package/dist/client/mcp-apps/McpAppRenderer.js +296 -0
- package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -0
- package/dist/client/sse-event-processor.d.ts +3 -0
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +2 -0
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/code-agents/transcript-normalizer.d.ts +2 -0
- package/dist/code-agents/transcript-normalizer.d.ts.map +1 -1
- package/dist/code-agents/transcript-normalizer.js +17 -0
- package/dist/code-agents/transcript-normalizer.js.map +1 -1
- package/dist/db/client.d.ts +19 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +139 -6
- package/dist/db/client.js.map +1 -1
- package/dist/extensions/actions.d.ts.map +1 -1
- package/dist/extensions/actions.js +6 -2
- package/dist/extensions/actions.js.map +1 -1
- package/dist/extensions/path.d.ts +6 -0
- package/dist/extensions/path.d.ts.map +1 -0
- package/dist/extensions/path.js +38 -0
- package/dist/extensions/path.js.map +1 -0
- package/dist/index.browser.d.ts +1 -1
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +154 -4
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/connect-store.d.ts +1 -1
- package/dist/mcp/connect-store.d.ts.map +1 -1
- package/dist/mcp/connect-store.js +1 -1
- package/dist/mcp/connect-store.js.map +1 -1
- package/dist/mcp/stdio.d.ts +2 -2
- package/dist/mcp/stdio.d.ts.map +1 -1
- package/dist/mcp/stdio.js +26 -8
- package/dist/mcp/stdio.js.map +1 -1
- package/dist/mcp-client/app-result.d.ts +40 -0
- package/dist/mcp-client/app-result.d.ts.map +1 -0
- package/dist/mcp-client/app-result.js +19 -0
- package/dist/mcp-client/app-result.js.map +1 -0
- package/dist/mcp-client/index.d.ts +5 -2
- package/dist/mcp-client/index.d.ts.map +1 -1
- package/dist/mcp-client/index.js +185 -23
- package/dist/mcp-client/index.js.map +1 -1
- package/dist/mcp-client/manager.d.ts +16 -0
- package/dist/mcp-client/manager.d.ts.map +1 -1
- package/dist/mcp-client/manager.js +58 -1
- package/dist/mcp-client/manager.js.map +1 -1
- package/dist/mcp-client/routes.d.ts +4 -1
- package/dist/mcp-client/routes.d.ts.map +1 -1
- package/dist/mcp-client/routes.js +146 -0
- package/dist/mcp-client/routes.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +8 -1
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/auth.d.ts +2 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +2 -2
- package/dist/server/auth.js.map +1 -1
- package/dist/server/framework-request-handler.d.ts +4 -2
- package/dist/server/framework-request-handler.d.ts.map +1 -1
- package/dist/server/framework-request-handler.js +25 -11
- package/dist/server/framework-request-handler.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/styles/agent-conversation.css +53 -0
- package/docs/content/actions.md +25 -2
- package/docs/content/external-agents.md +62 -8
- package/docs/content/key-concepts.md +1 -1
- package/docs/content/mcp-clients.md +1 -1
- package/docs/content/mcp-protocol.md +16 -11
- package/package.json +2 -1
package/dist/cli/connect.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* codex|cowork] [--scope user|project]
|
|
9
9
|
* [--name <serverName>]
|
|
10
10
|
* agent-native connect <url> --token <token> (no-browser fallback)
|
|
11
|
+
* agent-native connect [--client ...] (pick first-party apps)
|
|
11
12
|
* agent-native connect --all [--client ...] (every first-party app)
|
|
12
13
|
*
|
|
13
14
|
* Server contract (implemented by another agent on `<url>`):
|
|
@@ -30,12 +31,14 @@ import os from "node:os";
|
|
|
30
31
|
import { spawn } from "node:child_process";
|
|
31
32
|
import path from "node:path";
|
|
32
33
|
import { findWorkspaceRoot } from "../mcp/workspace-resolve.js";
|
|
33
|
-
import { CLIENTS, writeHttpEntryForClient, } from "./mcp-config-writers.js";
|
|
34
|
-
import { visibleTemplates } from "./templates-meta.js";
|
|
34
|
+
import { CLIENTS, configPathFor, writeCodexBlock, writeHttpEntryForClient, writeJsonMcpEntry, } from "./mcp-config-writers.js";
|
|
35
|
+
import { TEMPLATES, visibleTemplates } from "./templates-meta.js";
|
|
35
36
|
const DEVICE_START_PATH = "/_agent-native/mcp/connect/device/start";
|
|
36
37
|
const DEVICE_POLL_PATH = "/_agent-native/mcp/connect/device/poll";
|
|
37
38
|
const SERVER_NAME_PREFIX = "agent-native";
|
|
38
39
|
const CONNECT_PREFERENCES_VERSION = 1;
|
|
40
|
+
const CONNECT_PROFILES_VERSION = 1;
|
|
41
|
+
const DEFAULT_DEV_GATEWAY = "http://127.0.0.1:8080";
|
|
39
42
|
const CLIENT_LABELS = {
|
|
40
43
|
"claude-code": "Claude Code",
|
|
41
44
|
"claude-code-cli": "Claude Code CLI",
|
|
@@ -73,6 +76,16 @@ export function parseConnectArgs(argv) {
|
|
|
73
76
|
let v;
|
|
74
77
|
if (a === "--all")
|
|
75
78
|
out.all = true;
|
|
79
|
+
else if ((v = eat("--apps")) !== undefined)
|
|
80
|
+
out.apps = v;
|
|
81
|
+
else if ((v = eat("--gateway")) !== undefined)
|
|
82
|
+
out.gateway = v;
|
|
83
|
+
else if ((v = eat("--gateway-url")) !== undefined)
|
|
84
|
+
out.gateway = v;
|
|
85
|
+
else if ((v = eat("--port")) !== undefined)
|
|
86
|
+
out.port = Number(v);
|
|
87
|
+
else if ((v = eat("--owner-email")) !== undefined)
|
|
88
|
+
out.ownerEmail = v;
|
|
76
89
|
else if ((v = eat("--client")) !== undefined) {
|
|
77
90
|
out.client = v;
|
|
78
91
|
out.clientExplicit = true;
|
|
@@ -83,8 +96,12 @@ export function parseConnectArgs(argv) {
|
|
|
83
96
|
out.name = v;
|
|
84
97
|
else if ((v = eat("--token")) !== undefined)
|
|
85
98
|
out.token = v;
|
|
86
|
-
else if (!a.startsWith("-") && !out.url)
|
|
87
|
-
out.
|
|
99
|
+
else if (!a.startsWith("-") && !out.url) {
|
|
100
|
+
if (!out.mode && (a === "dev" || a === "prod"))
|
|
101
|
+
out.mode = a;
|
|
102
|
+
else
|
|
103
|
+
out.url = a;
|
|
104
|
+
}
|
|
88
105
|
}
|
|
89
106
|
return out;
|
|
90
107
|
}
|
|
@@ -181,7 +198,7 @@ function clientPromptOptions() {
|
|
|
181
198
|
hint: CLIENT_HINTS[client],
|
|
182
199
|
}));
|
|
183
200
|
}
|
|
184
|
-
function
|
|
201
|
+
function shouldPrompt(deps) {
|
|
185
202
|
if (deps.isInteractive)
|
|
186
203
|
return deps.isInteractive();
|
|
187
204
|
if (process.env.AGENT_NATIVE_NO_PROMPT === "1")
|
|
@@ -190,6 +207,9 @@ function shouldPromptForClients(deps) {
|
|
|
190
207
|
return false;
|
|
191
208
|
return !!process.stdin.isTTY && !!process.stdout.isTTY;
|
|
192
209
|
}
|
|
210
|
+
function shouldPromptForClients(deps) {
|
|
211
|
+
return shouldPrompt(deps);
|
|
212
|
+
}
|
|
193
213
|
async function promptForClients(context) {
|
|
194
214
|
const clack = await import("@clack/prompts");
|
|
195
215
|
const result = await clack.multiselect({
|
|
@@ -205,6 +225,42 @@ async function promptForClients(context) {
|
|
|
205
225
|
}
|
|
206
226
|
return normalizeClientIds(result);
|
|
207
227
|
}
|
|
228
|
+
function normalizeHostedAppNames(values, apps) {
|
|
229
|
+
if (!Array.isArray(values))
|
|
230
|
+
return [];
|
|
231
|
+
const byName = new Map(apps.map((app) => [app.name, app]));
|
|
232
|
+
const seen = new Set();
|
|
233
|
+
const out = [];
|
|
234
|
+
for (const value of values) {
|
|
235
|
+
if (typeof value !== "string")
|
|
236
|
+
continue;
|
|
237
|
+
const app = byName.get(value);
|
|
238
|
+
if (!app || seen.has(app.name))
|
|
239
|
+
continue;
|
|
240
|
+
seen.add(app.name);
|
|
241
|
+
out.push(app.name);
|
|
242
|
+
}
|
|
243
|
+
return out;
|
|
244
|
+
}
|
|
245
|
+
async function promptForHostedApps(context) {
|
|
246
|
+
const clack = await import("@clack/prompts");
|
|
247
|
+
const result = await clack.multiselect({
|
|
248
|
+
message: "Which Agent Native apps do you want to connect?\n" +
|
|
249
|
+
" (all are selected by default; space toggles, enter confirms)",
|
|
250
|
+
options: context.apps.map((app) => ({
|
|
251
|
+
value: app.name,
|
|
252
|
+
label: app.label,
|
|
253
|
+
hint: app.url,
|
|
254
|
+
})),
|
|
255
|
+
initialValues: context.initialApps,
|
|
256
|
+
required: true,
|
|
257
|
+
});
|
|
258
|
+
if (clack.isCancel(result)) {
|
|
259
|
+
clack.cancel("Cancelled.");
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
return normalizeHostedAppNames(result, context.apps);
|
|
263
|
+
}
|
|
208
264
|
async function resolveConnectClients(parsed, deps) {
|
|
209
265
|
if (parsed.clientExplicit)
|
|
210
266
|
return resolveClients(parsed.client);
|
|
@@ -229,6 +285,24 @@ async function resolveConnectClients(parsed, deps) {
|
|
|
229
285
|
}
|
|
230
286
|
return selected;
|
|
231
287
|
}
|
|
288
|
+
async function resolveHostedAppsFromPrompt(deps) {
|
|
289
|
+
const apps = hostedApps();
|
|
290
|
+
if (apps.length === 0) {
|
|
291
|
+
logErr(" No hosted first-party apps found in the template registry.");
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
if (!shouldPrompt(deps))
|
|
295
|
+
return null;
|
|
296
|
+
const prompt = deps.promptHostedApps ?? promptForHostedApps;
|
|
297
|
+
const selectedNames = normalizeHostedAppNames(await prompt({
|
|
298
|
+
apps,
|
|
299
|
+
initialApps: apps.map((app) => app.name),
|
|
300
|
+
}), apps);
|
|
301
|
+
if (selectedNames.length === 0)
|
|
302
|
+
return [];
|
|
303
|
+
const selected = new Set(selectedNames);
|
|
304
|
+
return apps.filter((app) => selected.has(app.name));
|
|
305
|
+
}
|
|
232
306
|
function clientArgForDeviceFlow(clients) {
|
|
233
307
|
return clients.length === 1 ? clients[0] : "all";
|
|
234
308
|
}
|
|
@@ -425,6 +499,459 @@ export function writeConfigs(clients, serverName, mcpUrl, token, scope, baseDir
|
|
|
425
499
|
}
|
|
426
500
|
return written;
|
|
427
501
|
}
|
|
502
|
+
export function connectProfilesPath() {
|
|
503
|
+
return path.join(os.homedir(), ".agent-native", "connect-profiles.json");
|
|
504
|
+
}
|
|
505
|
+
function readConnectProfiles(file) {
|
|
506
|
+
try {
|
|
507
|
+
const parsed = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
508
|
+
if (parsed && typeof parsed === "object") {
|
|
509
|
+
return {
|
|
510
|
+
version: Number(parsed.version) || CONNECT_PROFILES_VERSION,
|
|
511
|
+
updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : undefined,
|
|
512
|
+
prodEntries: parsed.prodEntries && typeof parsed.prodEntries === "object"
|
|
513
|
+
? parsed.prodEntries
|
|
514
|
+
: {},
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
catch {
|
|
519
|
+
// no saved profiles yet
|
|
520
|
+
}
|
|
521
|
+
return { version: CONNECT_PROFILES_VERSION, prodEntries: {} };
|
|
522
|
+
}
|
|
523
|
+
function writeConnectProfiles(file, profiles) {
|
|
524
|
+
profiles.version = CONNECT_PROFILES_VERSION;
|
|
525
|
+
profiles.updatedAt = new Date().toISOString();
|
|
526
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
527
|
+
fs.writeFileSync(file, JSON.stringify(profiles, null, 2) + "\n", "utf-8");
|
|
528
|
+
}
|
|
529
|
+
function savedProfileEntry(profiles, serverName, client, file) {
|
|
530
|
+
return profiles.prodEntries?.[serverName]?.[client]?.[file];
|
|
531
|
+
}
|
|
532
|
+
function setSavedProfileEntry(profiles, serverName, client, file, entry) {
|
|
533
|
+
profiles.prodEntries ??= {};
|
|
534
|
+
profiles.prodEntries[serverName] ??= {};
|
|
535
|
+
profiles.prodEntries[serverName][client] ??= {};
|
|
536
|
+
profiles.prodEntries[serverName][client][file] = entry;
|
|
537
|
+
}
|
|
538
|
+
function readJsonMcpServerEntry(file, serverName) {
|
|
539
|
+
try {
|
|
540
|
+
const parsed = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
541
|
+
const entry = parsed?.mcpServers?.[serverName];
|
|
542
|
+
return entry && typeof entry === "object" ? entry : undefined;
|
|
543
|
+
}
|
|
544
|
+
catch {
|
|
545
|
+
return undefined;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
function tomlQuoteForRead(s) {
|
|
549
|
+
return `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
550
|
+
}
|
|
551
|
+
function codexHeadersForRead(name) {
|
|
552
|
+
const headers = [`[mcp_servers.${tomlQuoteForRead(name)}]`];
|
|
553
|
+
if (/^[A-Za-z0-9_-]+$/.test(name))
|
|
554
|
+
headers.push(`[mcp_servers.${name}]`);
|
|
555
|
+
return headers;
|
|
556
|
+
}
|
|
557
|
+
function readCodexMcpBlock(file, serverName) {
|
|
558
|
+
let content = "";
|
|
559
|
+
try {
|
|
560
|
+
content = fs.readFileSync(file, "utf-8");
|
|
561
|
+
}
|
|
562
|
+
catch {
|
|
563
|
+
return undefined;
|
|
564
|
+
}
|
|
565
|
+
const headers = new Set(codexHeadersForRead(serverName));
|
|
566
|
+
const lines = content.split(/\r?\n/);
|
|
567
|
+
for (let i = 0; i < lines.length; i++) {
|
|
568
|
+
if (!headers.has(lines[i].trim()))
|
|
569
|
+
continue;
|
|
570
|
+
const block = [lines[i]];
|
|
571
|
+
i++;
|
|
572
|
+
while (i < lines.length && !/^\s*\[/.test(lines[i])) {
|
|
573
|
+
block.push(lines[i]);
|
|
574
|
+
i++;
|
|
575
|
+
}
|
|
576
|
+
return block.join("\n").replace(/\n*$/, "") + "\n";
|
|
577
|
+
}
|
|
578
|
+
return undefined;
|
|
579
|
+
}
|
|
580
|
+
function readCurrentMcpEntry(client, serverName, baseDir, scope) {
|
|
581
|
+
const file = configPathFor(client, baseDir, scope);
|
|
582
|
+
if (client === "codex") {
|
|
583
|
+
const block = readCodexMcpBlock(file, serverName);
|
|
584
|
+
return {
|
|
585
|
+
file,
|
|
586
|
+
saved: block
|
|
587
|
+
? { kind: "codex", block, savedAt: new Date().toISOString() }
|
|
588
|
+
: undefined,
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
const entry = readJsonMcpServerEntry(file, serverName);
|
|
592
|
+
return {
|
|
593
|
+
file,
|
|
594
|
+
saved: entry
|
|
595
|
+
? { kind: "json", entry, savedAt: new Date().toISOString() }
|
|
596
|
+
: undefined,
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
function writeSavedMcpEntry(client, file, serverName, saved) {
|
|
600
|
+
if (client === "codex") {
|
|
601
|
+
if (saved.kind !== "codex")
|
|
602
|
+
return;
|
|
603
|
+
writeCodexBlock(file, serverName, saved.block);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
if (saved.kind !== "json")
|
|
607
|
+
return;
|
|
608
|
+
writeJsonMcpEntry(file, serverName, saved.entry);
|
|
609
|
+
}
|
|
610
|
+
function unescapeTomlString(value) {
|
|
611
|
+
return value.replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
612
|
+
}
|
|
613
|
+
function parseCodexHeaders(block) {
|
|
614
|
+
const line = block
|
|
615
|
+
.split(/\r?\n/)
|
|
616
|
+
.find((candidate) => /^\s*http_headers\s*=/.test(candidate));
|
|
617
|
+
if (!line)
|
|
618
|
+
return {};
|
|
619
|
+
const match = line.match(/\{(.*)\}/);
|
|
620
|
+
if (!match)
|
|
621
|
+
return {};
|
|
622
|
+
const headers = {};
|
|
623
|
+
const pairRe = /"((?:\\.|[^"])*)"\s*=\s*"((?:\\.|[^"])*)"/g;
|
|
624
|
+
let pair;
|
|
625
|
+
while ((pair = pairRe.exec(match[1]))) {
|
|
626
|
+
headers[unescapeTomlString(pair[1])] = unescapeTomlString(pair[2]);
|
|
627
|
+
}
|
|
628
|
+
return headers;
|
|
629
|
+
}
|
|
630
|
+
function savedEntryUrl(saved) {
|
|
631
|
+
if (!saved)
|
|
632
|
+
return undefined;
|
|
633
|
+
if (saved.kind === "json") {
|
|
634
|
+
return typeof saved.entry.url === "string" ? saved.entry.url : undefined;
|
|
635
|
+
}
|
|
636
|
+
const match = saved.block.match(/^\s*url\s*=\s*"((?:\\.|[^"])*)"/m);
|
|
637
|
+
return match ? unescapeTomlString(match[1]) : undefined;
|
|
638
|
+
}
|
|
639
|
+
function savedEntryHeaders(saved) {
|
|
640
|
+
if (!saved)
|
|
641
|
+
return {};
|
|
642
|
+
if (saved.kind === "json") {
|
|
643
|
+
const headers = saved.entry.headers;
|
|
644
|
+
return headers && typeof headers === "object"
|
|
645
|
+
? Object.fromEntries(Object.entries(headers)
|
|
646
|
+
.filter((entry) => {
|
|
647
|
+
return typeof entry[1] === "string";
|
|
648
|
+
})
|
|
649
|
+
.map(([key, value]) => [key, value]))
|
|
650
|
+
: {};
|
|
651
|
+
}
|
|
652
|
+
return parseCodexHeaders(saved.block);
|
|
653
|
+
}
|
|
654
|
+
function isLoopbackMcpUrl(value) {
|
|
655
|
+
if (!value)
|
|
656
|
+
return false;
|
|
657
|
+
try {
|
|
658
|
+
const url = new URL(value);
|
|
659
|
+
return (url.hostname === "localhost" ||
|
|
660
|
+
url.hostname === "127.0.0.1" ||
|
|
661
|
+
url.hostname === "::1" ||
|
|
662
|
+
url.hostname.startsWith("127."));
|
|
663
|
+
}
|
|
664
|
+
catch {
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
function decodeJwtSub(authHeader) {
|
|
669
|
+
if (!authHeader?.startsWith("Bearer "))
|
|
670
|
+
return undefined;
|
|
671
|
+
const token = authHeader.slice("Bearer ".length);
|
|
672
|
+
const [, payload] = token.split(".");
|
|
673
|
+
if (!payload)
|
|
674
|
+
return undefined;
|
|
675
|
+
try {
|
|
676
|
+
const normalized = payload.replace(/-/g, "+").replace(/_/g, "/");
|
|
677
|
+
const padded = normalized.padEnd(normalized.length + ((4 - (normalized.length % 4)) % 4), "=");
|
|
678
|
+
const parsed = JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
|
|
679
|
+
return typeof parsed.sub === "string" && parsed.sub.includes("@")
|
|
680
|
+
? parsed.sub
|
|
681
|
+
: undefined;
|
|
682
|
+
}
|
|
683
|
+
catch {
|
|
684
|
+
return undefined;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
function ownerEmailFromEntry(saved) {
|
|
688
|
+
const headers = savedEntryHeaders(saved);
|
|
689
|
+
return (headers["X-Agent-Native-Owner-Email"] || decodeJwtSub(headers.Authorization));
|
|
690
|
+
}
|
|
691
|
+
function readEnvFile(file) {
|
|
692
|
+
try {
|
|
693
|
+
return fs.readFileSync(file, "utf-8");
|
|
694
|
+
}
|
|
695
|
+
catch {
|
|
696
|
+
return "";
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
function readEnvValue(content, key) {
|
|
700
|
+
let found;
|
|
701
|
+
for (const line of content.split(/\r?\n/)) {
|
|
702
|
+
const match = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)\s*$/i);
|
|
703
|
+
if (match?.[1] === key) {
|
|
704
|
+
found = match[2].replace(/^["']|["']$/g, "");
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
return found;
|
|
708
|
+
}
|
|
709
|
+
function workspaceEnvContent(baseDir) {
|
|
710
|
+
return (readEnvFile(path.join(baseDir, ".env.local")) +
|
|
711
|
+
"\n" +
|
|
712
|
+
readEnvFile(path.join(baseDir, ".env")));
|
|
713
|
+
}
|
|
714
|
+
function localAccessToken(baseDir) {
|
|
715
|
+
const content = workspaceEnvContent(baseDir);
|
|
716
|
+
const single = readEnvValue(content, "ACCESS_TOKEN");
|
|
717
|
+
if (single)
|
|
718
|
+
return single;
|
|
719
|
+
const multi = readEnvValue(content, "ACCESS_TOKENS");
|
|
720
|
+
return multi
|
|
721
|
+
?.split(",")
|
|
722
|
+
.map((token) => token.trim())
|
|
723
|
+
.find(Boolean);
|
|
724
|
+
}
|
|
725
|
+
function localA2ASecret(baseDir) {
|
|
726
|
+
return (process.env.A2A_SECRET ||
|
|
727
|
+
readEnvValue(workspaceEnvContent(baseDir), "A2A_SECRET"));
|
|
728
|
+
}
|
|
729
|
+
async function mintLocalA2AToken(ownerEmail, baseDir) {
|
|
730
|
+
const secret = ownerEmail ? localA2ASecret(baseDir) : undefined;
|
|
731
|
+
if (!secret)
|
|
732
|
+
return undefined;
|
|
733
|
+
const jose = await import("jose");
|
|
734
|
+
return new jose.SignJWT({ sub: ownerEmail })
|
|
735
|
+
.setProtectedHeader({ alg: "HS256" })
|
|
736
|
+
.setIssuer("agent-native-connect-dev")
|
|
737
|
+
.setIssuedAt()
|
|
738
|
+
.setExpirationTime("30d")
|
|
739
|
+
.sign(new TextEncoder().encode(secret));
|
|
740
|
+
}
|
|
741
|
+
async function devHeadersForApp(params) {
|
|
742
|
+
const ownerEmail = params.ownerEmail ||
|
|
743
|
+
process.env.AGENT_NATIVE_OWNER_EMAIL ||
|
|
744
|
+
ownerEmailFromEntry(params.sourceEntry);
|
|
745
|
+
const headers = {};
|
|
746
|
+
const accessToken = localAccessToken(params.baseDir);
|
|
747
|
+
const a2aToken = accessToken
|
|
748
|
+
? undefined
|
|
749
|
+
: await mintLocalA2AToken(ownerEmail, params.baseDir);
|
|
750
|
+
if (accessToken || a2aToken) {
|
|
751
|
+
headers.Authorization = `Bearer ${accessToken || a2aToken}`;
|
|
752
|
+
}
|
|
753
|
+
if (ownerEmail) {
|
|
754
|
+
headers["X-Agent-Native-Owner-Email"] = ownerEmail;
|
|
755
|
+
}
|
|
756
|
+
return Object.keys(headers).length ? headers : undefined;
|
|
757
|
+
}
|
|
758
|
+
function connectableApps(includeHidden = false) {
|
|
759
|
+
const source = includeHidden ? TEMPLATES : visibleTemplates();
|
|
760
|
+
return source
|
|
761
|
+
.filter((template) => typeof template.prodUrl === "string")
|
|
762
|
+
.map((template) => ({
|
|
763
|
+
name: template.name,
|
|
764
|
+
label: template.label,
|
|
765
|
+
url: template.prodUrl,
|
|
766
|
+
core: !!template.core,
|
|
767
|
+
}));
|
|
768
|
+
}
|
|
769
|
+
function profileDefaultApps() {
|
|
770
|
+
const core = connectableApps(false).filter((app) => app.core);
|
|
771
|
+
return core.length ? core : connectableApps(false);
|
|
772
|
+
}
|
|
773
|
+
function parseAppsList(value) {
|
|
774
|
+
return (value ?? "")
|
|
775
|
+
.split(",")
|
|
776
|
+
.map((app) => app.trim())
|
|
777
|
+
.filter(Boolean);
|
|
778
|
+
}
|
|
779
|
+
async function resolveProfileApps(parsed, deps) {
|
|
780
|
+
const allVisible = connectableApps(false);
|
|
781
|
+
const allIncludingHidden = connectableApps(true);
|
|
782
|
+
if (parsed.apps) {
|
|
783
|
+
const requested = parseAppsList(parsed.apps);
|
|
784
|
+
if (requested.includes("all"))
|
|
785
|
+
return allVisible;
|
|
786
|
+
const byName = new Map(allIncludingHidden.map((app) => [app.name, app]));
|
|
787
|
+
const unknown = requested.filter((name) => !byName.has(name));
|
|
788
|
+
if (unknown.length) {
|
|
789
|
+
throw new Error(`Unknown app(s): ${unknown.join(", ")}. Known apps: ${allIncludingHidden
|
|
790
|
+
.map((app) => app.name)
|
|
791
|
+
.join(", ")}`);
|
|
792
|
+
}
|
|
793
|
+
return requested.map((name) => byName.get(name));
|
|
794
|
+
}
|
|
795
|
+
if (parsed.all)
|
|
796
|
+
return allVisible;
|
|
797
|
+
if (shouldPrompt(deps)) {
|
|
798
|
+
const prompt = deps.promptHostedApps ?? promptForHostedApps;
|
|
799
|
+
const initialApps = profileDefaultApps().map((app) => app.name);
|
|
800
|
+
const selectedNames = normalizeHostedAppNames(await prompt({ apps: allVisible, initialApps }), allVisible);
|
|
801
|
+
if (selectedNames.length === 0)
|
|
802
|
+
return [];
|
|
803
|
+
const selected = new Set(selectedNames);
|
|
804
|
+
return allVisible.filter((app) => selected.has(app.name));
|
|
805
|
+
}
|
|
806
|
+
return profileDefaultApps();
|
|
807
|
+
}
|
|
808
|
+
function defaultDevGateway() {
|
|
809
|
+
if (process.env.WORKSPACE_GATEWAY_URL)
|
|
810
|
+
return process.env.WORKSPACE_GATEWAY_URL;
|
|
811
|
+
const port = process.env.WORKSPACE_PORT || process.env.PORT;
|
|
812
|
+
return port ? `http://127.0.0.1:${port}` : DEFAULT_DEV_GATEWAY;
|
|
813
|
+
}
|
|
814
|
+
function normalizeDevGateway(parsed) {
|
|
815
|
+
const raw = parsed.gateway ||
|
|
816
|
+
(Number.isFinite(parsed.port) && parsed.port
|
|
817
|
+
? `http://127.0.0.1:${parsed.port}`
|
|
818
|
+
: defaultDevGateway());
|
|
819
|
+
const normalized = normalizeUrl(raw);
|
|
820
|
+
return normalized.replace(/\/+$/, "");
|
|
821
|
+
}
|
|
822
|
+
async function gatewayAppUrls(gatewayUrl, deps) {
|
|
823
|
+
const out = new Map();
|
|
824
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
825
|
+
try {
|
|
826
|
+
const response = await fetchImpl(`${gatewayUrl}/_workspace/apps`, {
|
|
827
|
+
signal: AbortSignal.timeout(1200),
|
|
828
|
+
});
|
|
829
|
+
if (!response.ok)
|
|
830
|
+
return out;
|
|
831
|
+
const apps = (await response.json());
|
|
832
|
+
if (!Array.isArray(apps))
|
|
833
|
+
return out;
|
|
834
|
+
for (const app of apps) {
|
|
835
|
+
if (!app || typeof app !== "object")
|
|
836
|
+
continue;
|
|
837
|
+
const id = app.id;
|
|
838
|
+
const url = app.url;
|
|
839
|
+
if (typeof id === "string" && typeof url === "string") {
|
|
840
|
+
out.set(id, normalizeUrl(url));
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
catch {
|
|
845
|
+
// The gateway may not be running yet; still write deterministic dev URLs.
|
|
846
|
+
}
|
|
847
|
+
return out;
|
|
848
|
+
}
|
|
849
|
+
function devMcpUrl(app, gatewayUrl, gatewayUrls) {
|
|
850
|
+
const base = gatewayUrls.get(app.name) ?? `${gatewayUrl}/${app.name}`;
|
|
851
|
+
return `${base.replace(/\/+$/, "")}/_agent-native/mcp`;
|
|
852
|
+
}
|
|
853
|
+
function serverNameForApp(app) {
|
|
854
|
+
return `${SERVER_NAME_PREFIX}-${app.name}`;
|
|
855
|
+
}
|
|
856
|
+
async function connectDevProfile(parsed, clients, deps) {
|
|
857
|
+
const apps = await resolveProfileApps(parsed, deps);
|
|
858
|
+
if (!apps || apps.length === 0)
|
|
859
|
+
return true;
|
|
860
|
+
const baseDir = projectBaseDir();
|
|
861
|
+
const scope = parsed.scope === "project" ? "project" : "user";
|
|
862
|
+
const gatewayUrl = normalizeDevGateway(parsed);
|
|
863
|
+
const gatewayUrls = await gatewayAppUrls(gatewayUrl, deps);
|
|
864
|
+
const profilesFile = deps.profilesFile ?? connectProfilesPath();
|
|
865
|
+
const profiles = readConnectProfiles(profilesFile);
|
|
866
|
+
const rows = [];
|
|
867
|
+
const ownerWarnings = new Set();
|
|
868
|
+
for (const app of apps) {
|
|
869
|
+
const serverName = serverNameForApp(app);
|
|
870
|
+
const mcpUrl = devMcpUrl(app, gatewayUrl, gatewayUrls);
|
|
871
|
+
for (const client of clients) {
|
|
872
|
+
const current = readCurrentMcpEntry(client, serverName, baseDir, scope);
|
|
873
|
+
const backup = savedProfileEntry(profiles, serverName, client, current.file);
|
|
874
|
+
if (current.saved && !isLoopbackMcpUrl(savedEntryUrl(current.saved))) {
|
|
875
|
+
setSavedProfileEntry(profiles, serverName, client, current.file, current.saved);
|
|
876
|
+
}
|
|
877
|
+
const sourceEntry = current.saved && !isLoopbackMcpUrl(savedEntryUrl(current.saved))
|
|
878
|
+
? current.saved
|
|
879
|
+
: backup;
|
|
880
|
+
const headers = await devHeadersForApp({
|
|
881
|
+
ownerEmail: parsed.ownerEmail,
|
|
882
|
+
sourceEntry,
|
|
883
|
+
baseDir,
|
|
884
|
+
});
|
|
885
|
+
if (!headers?.["X-Agent-Native-Owner-Email"]) {
|
|
886
|
+
ownerWarnings.add(app.name);
|
|
887
|
+
}
|
|
888
|
+
const file = writeHttpEntryForClient(client, serverName, mcpUrl, undefined, baseDir, scope, headers);
|
|
889
|
+
rows.push({
|
|
890
|
+
app: app.name,
|
|
891
|
+
client,
|
|
892
|
+
status: "dev",
|
|
893
|
+
file,
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
writeConnectProfiles(profilesFile, profiles);
|
|
898
|
+
logOut("");
|
|
899
|
+
logOut(` Switched ${apps.length} app(s) to dev via ${gatewayUrl}`);
|
|
900
|
+
for (const row of rows) {
|
|
901
|
+
logOut(` ${row.app.padEnd(12)} ${row.client.padEnd(18)} ${row.file}`);
|
|
902
|
+
}
|
|
903
|
+
if (ownerWarnings.size) {
|
|
904
|
+
logOut("");
|
|
905
|
+
logOut(` Tip: pass --owner-email <you@example.com> if local tools look sparse ` +
|
|
906
|
+
`for ${Array.from(ownerWarnings).join(", ")}.`);
|
|
907
|
+
}
|
|
908
|
+
logOut("");
|
|
909
|
+
logOut(" Restart your coding agent to pick up the dev MCP servers.");
|
|
910
|
+
return true;
|
|
911
|
+
}
|
|
912
|
+
async function connectProdProfile(parsed, clients, deps) {
|
|
913
|
+
const apps = await resolveProfileApps(parsed, deps);
|
|
914
|
+
if (!apps || apps.length === 0)
|
|
915
|
+
return true;
|
|
916
|
+
const baseDir = projectBaseDir();
|
|
917
|
+
const scope = parsed.scope === "project" ? "project" : "user";
|
|
918
|
+
const profilesFile = deps.profilesFile ?? connectProfilesPath();
|
|
919
|
+
const profiles = readConnectProfiles(profilesFile);
|
|
920
|
+
const restored = [];
|
|
921
|
+
const missing = [];
|
|
922
|
+
for (const app of apps) {
|
|
923
|
+
const serverName = serverNameForApp(app);
|
|
924
|
+
for (const client of clients) {
|
|
925
|
+
const file = configPathFor(client, baseDir, scope);
|
|
926
|
+
const saved = savedProfileEntry(profiles, serverName, client, file);
|
|
927
|
+
if (!saved) {
|
|
928
|
+
missing.push({ app: app.name, client });
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
writeSavedMcpEntry(client, file, serverName, saved);
|
|
932
|
+
restored.push({ app: app.name, client, file });
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
logOut("");
|
|
936
|
+
if (restored.length) {
|
|
937
|
+
logOut(` Restored ${restored.length} production MCP entr${restored.length === 1 ? "y" : "ies"}.`);
|
|
938
|
+
for (const row of restored) {
|
|
939
|
+
logOut(` ${row.app.padEnd(12)} ${row.client.padEnd(18)} ${row.file}`);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
if (missing.length) {
|
|
943
|
+
logOut("");
|
|
944
|
+
logOut(" No saved production entry for:");
|
|
945
|
+
for (const row of missing) {
|
|
946
|
+
const app = apps.find((candidate) => candidate.name === row.app);
|
|
947
|
+
logOut(` ${row.app.padEnd(12)} ${row.client.padEnd(18)} ` +
|
|
948
|
+
`run: agent-native connect ${app?.url ?? "<url>"} --client ${row.client}`);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
logOut("");
|
|
952
|
+
logOut(" Restart your coding agent to pick up the production MCP servers.");
|
|
953
|
+
return missing.length === 0;
|
|
954
|
+
}
|
|
428
955
|
// ---------------------------------------------------------------------------
|
|
429
956
|
// Single-app connect
|
|
430
957
|
// ---------------------------------------------------------------------------
|
|
@@ -470,10 +997,13 @@ async function connectOne(rawUrl, parsed, clients, deps) {
|
|
|
470
997
|
export function hostedApps() {
|
|
471
998
|
return visibleTemplates()
|
|
472
999
|
.filter((t) => typeof t.prodUrl === "string" && t.prodUrl.length > 0)
|
|
473
|
-
.map((t) => ({
|
|
1000
|
+
.map((t) => ({
|
|
1001
|
+
name: t.name,
|
|
1002
|
+
label: t.label,
|
|
1003
|
+
url: t.prodUrl,
|
|
1004
|
+
}));
|
|
474
1005
|
}
|
|
475
|
-
async function
|
|
476
|
-
const apps = hostedApps();
|
|
1006
|
+
async function connectApps(apps, parsed, clients, deps) {
|
|
477
1007
|
if (apps.length === 0) {
|
|
478
1008
|
logErr(" No hosted first-party apps found in the template registry.");
|
|
479
1009
|
return false;
|
|
@@ -483,11 +1013,11 @@ async function connectAll(parsed, clients, deps) {
|
|
|
483
1013
|
const results = [];
|
|
484
1014
|
for (const app of apps) {
|
|
485
1015
|
logOut("");
|
|
486
|
-
logOut(` ── ${app.
|
|
1016
|
+
logOut(` ── ${app.label} (${app.url}) ──`);
|
|
487
1017
|
try {
|
|
488
1018
|
const res = await connectOne(app.url, parsed, clients, deps);
|
|
489
1019
|
results.push({
|
|
490
|
-
name: app.
|
|
1020
|
+
name: app.label,
|
|
491
1021
|
status: res.ok ? "connected" : "skipped",
|
|
492
1022
|
files: res.files ?? [],
|
|
493
1023
|
});
|
|
@@ -505,12 +1035,19 @@ async function connectAll(parsed, clients, deps) {
|
|
|
505
1035
|
}
|
|
506
1036
|
return results.every((r) => r.status === "connected");
|
|
507
1037
|
}
|
|
1038
|
+
async function connectAll(parsed, clients, deps) {
|
|
1039
|
+
return connectApps(hostedApps(), parsed, clients, deps);
|
|
1040
|
+
}
|
|
508
1041
|
// ---------------------------------------------------------------------------
|
|
509
1042
|
// Entry point
|
|
510
1043
|
// ---------------------------------------------------------------------------
|
|
511
1044
|
const HELP = `agent-native connect — wire your coding agent to a deployed app
|
|
512
1045
|
|
|
513
1046
|
Usage:
|
|
1047
|
+
agent-native connect [--client <c>] [--scope user|project]
|
|
1048
|
+
With no URL, opens a picker for the built-in hosted apps
|
|
1049
|
+
(mail.agent-native.com, calendar.agent-native.com, and friends).
|
|
1050
|
+
|
|
514
1051
|
agent-native connect <url> [--client <c>] [--scope user|project] [--name <n>]
|
|
515
1052
|
Browser device-code flow. Prints a code, opens the verification URL,
|
|
516
1053
|
polls until approved, then writes the HTTP MCP entry into your
|
|
@@ -525,6 +1062,14 @@ Usage:
|
|
|
525
1062
|
agent-native connect --all [--client <c>] [--scope user|project]
|
|
526
1063
|
Connect every first-party hosted app at once.
|
|
527
1064
|
|
|
1065
|
+
Developer:
|
|
1066
|
+
agent-native connect dev [--apps mail,calendar] [--client <c>]
|
|
1067
|
+
Switch selected first-party MCP entries to a local dev-lazy gateway.
|
|
1068
|
+
Defaults to ${DEFAULT_DEV_GATEWAY}; override with --gateway or --port.
|
|
1069
|
+
|
|
1070
|
+
agent-native connect prod [--apps mail,calendar] [--client <c>]
|
|
1071
|
+
Restore production MCP entries saved before the dev switch.
|
|
1072
|
+
|
|
528
1073
|
Clients: all (default), claude-code, claude-code-cli, codex, cowork
|
|
529
1074
|
Scope: user (default, ~/.claude.json) or project (.mcp.json)`;
|
|
530
1075
|
/**
|
|
@@ -542,6 +1087,17 @@ export async function runConnect(args, deps = {}) {
|
|
|
542
1087
|
}
|
|
543
1088
|
const parsed = parseConnectArgs(args);
|
|
544
1089
|
try {
|
|
1090
|
+
if (parsed.mode) {
|
|
1091
|
+
const clients = await resolveConnectClients(parsed, deps);
|
|
1092
|
+
if (!clients)
|
|
1093
|
+
return;
|
|
1094
|
+
const ok = parsed.mode === "dev"
|
|
1095
|
+
? await connectDevProfile(parsed, clients, deps)
|
|
1096
|
+
: await connectProdProfile(parsed, clients, deps);
|
|
1097
|
+
if (!ok)
|
|
1098
|
+
process.exitCode = 1;
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
545
1101
|
if (parsed.all) {
|
|
546
1102
|
const clients = await resolveConnectClients(parsed, deps);
|
|
547
1103
|
if (!clients)
|
|
@@ -552,6 +1108,18 @@ export async function runConnect(args, deps = {}) {
|
|
|
552
1108
|
return;
|
|
553
1109
|
}
|
|
554
1110
|
if (!parsed.url) {
|
|
1111
|
+
const apps = await resolveHostedAppsFromPrompt(deps);
|
|
1112
|
+
if (apps) {
|
|
1113
|
+
if (apps.length === 0)
|
|
1114
|
+
return;
|
|
1115
|
+
const clients = await resolveConnectClients(parsed, deps);
|
|
1116
|
+
if (!clients)
|
|
1117
|
+
return;
|
|
1118
|
+
const ok = await connectApps(apps, parsed, clients, deps);
|
|
1119
|
+
if (!ok)
|
|
1120
|
+
process.exitCode = 1;
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
555
1123
|
logErr(" Missing app URL.");
|
|
556
1124
|
logErr("");
|
|
557
1125
|
logOut(HELP);
|