@agentmuxer/setup 0.1.1 → 0.1.2

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.
Files changed (3) hide show
  1. package/README.md +57 -6
  2. package/dist/bin.js +256 -61
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -14,15 +14,66 @@ automatic detection is unavailable. Repeat `--client` to select more than one.
14
14
 
15
15
  The installer registers `https://mcp.agentmuxer.com/mcp` in each selected client's global MCP
16
16
  configuration and maintains one marked instruction block in its global guidance file. Existing
17
- instructions are preserved. Repeat installation updates the block. Removal deletes only the standard
18
- AgentMuxer endpoint and marked block; a nonstandard or different MCP using the same name is reported
17
+ instructions are preserved. Repeat installation updates the block. Removal deletes the marked block and standard
18
+ AgentMuxer endpoint for Codex and Claude Code. OpenCode is disabled with its native `enabled: false`
19
+ setting so its OAuth registration survives alongside its retained credentials; reinstall re-enables it. A nonstandard or different MCP using the same name is reported
19
20
  as a conflict and left untouched. The guidance requires a free catalog search before external tooling, then leaves
20
21
  the native-versus-specialized choice to fit, simplicity, and cost. Search never requires invocation.
21
22
 
22
- After installation, run the sign-in command printed for your client every AgentMuxer call
23
- requires it; unauthenticated requests answer a standard 401 challenge that starts the host
24
- OAuth flow. Status verifies the MCP configuration and guidance only; OAuth state remains
25
- client-owned and is not inspected.
23
+ The standard install command requests `offline_access` for Claude Code and OpenCode so WorkOS can
24
+ issue refresh tokens. Reinstall upgrades the previous managed URL-only configuration, preserves
25
+ comments and unrelated settings, and leaves custom OAuth configurations untouched. Existing grants
26
+ issued without refresh tokens need one new sign-in after the scope upgrade. Codex retains its native
27
+ OAuth configuration; this change does not fix refresh persistence bugs in older Codex versions.
28
+
29
+ On first installation, setup starts each client's native browser sign-in sequentially. The native
30
+ client owns consent, credential storage, and refresh. Setup runs these commands outside the current
31
+ project so project-specific MCP configurations do not override the global installation. OpenCode
32
+ configuration override environment variables are excluded from these native setup commands; an
33
+ OpenCode session launched with those overrides can still use different configuration.
34
+ Repeat installation preserves existing credentials. Status reports the client's own sign-in status:
35
+ Claude Code checks the connection; Codex and OpenCode report stored credentials, which does not prove
36
+ that a live MCP request will succeed. OpenCode's `expired` status means its stored access token
37
+ has expired; setup preserves the credentials so OpenCode can attempt refresh on its next connection.
38
+ Restart open clients to load the configuration.
39
+
40
+ New OpenCode installations first check native status using a fixed in-memory configuration through
41
+ OpenCode's native `OPENCODE_CONFIG_CONTENT` support, before writing the AgentMuxer entry.
42
+ Retained credentials preserve that registration; only a signed-out new connection configures its
43
+ native `oauth.clientId` with `client_01M1TJJ3DKJR89RPXJ5XVWSKMH`, AgentMuxer's pre-registered public
44
+ WorkOS OAuth application for OpenCode. It uses PKCE without a client secret. OpenCode handles
45
+ browser consent, credential storage and refresh. The fixed ID survives explicit re-login and
46
+ represents the AgentMuxer OpenCode integration, not a device.
47
+ Setup may retry sign-in for this configuration when OpenCode reports that it is signed out.
48
+
49
+ Existing OpenCode DCR configurations keep their original registration and tokens. Setup adds the
50
+ refresh scope without switching their client ID. Explicit DCR re-login in OpenCode 1.18.29 can create
51
+ a new ID and therefore a new AgentMuxer principal. No automatic migration or principal relinking is
52
+ performed. Keep the disabled OpenCode entry when removing setup: manually deleting its configured
53
+ client ID while retaining tokens can require a new sign-in. Codex and Claude retained their CIMD IDs in the tested re-login paths, but setup does not
54
+ infer the registration type from their credential-presence status.
55
+
56
+ An unavailable status or network failure never triggers sign-in. Setup waits for native status
57
+ before writing the AgentMuxer entry, so a failed preflight cannot leave a scope-only registration. For existing connections without
58
+ the configured stable ID, setup prints the native reconnect command. An interrupted first sign-in
59
+ leaves configuration saved. No API-key fallback, local token store, OAuth broker, or Blueprint
60
+ integration is added. Owner/client principal bindings and spending restrictions remain unchanged.
61
+
62
+ The acceptance criterion is silent refresh/restart during healthy operation while authorization
63
+ remains valid. The recorded six-build matrix passed 78 ordinary forced-refresh/restart cycles.
64
+ Credential loss beyond an issuer's replay window is an accepted limitation. Older Codex builds also
65
+ have a reproduced persistence defect when a successful refresh is followed by an MCP 503; this is a
66
+ known client limitation, not something setup repairs. WorkOS Connect's idle and absolute grant
67
+ lifetimes remain unverified; do not promise that users will never need to sign in again. Full
68
+ production idle behavior and desktop onboarding are not certified by setup's packed smoke test.
69
+
70
+ The public WorkOS application must retain its client ID, PKCE requirement, and native redirect URI
71
+ `http://127.0.0.1:19876/mcp/oauth/callback`. OpenCode 1.18.29 passed native login and a live MCP request
72
+ against production with this application, followed by two refresh/restart cycles after actual
73
+ 300-second access-token expiration without another login or browser. The built setup CLI also
74
+ passed remove/reinstall and reconnection without sign-in; explicit native re-login and reconnection
75
+ passed separately. These checks do not establish long-idle behavior or desktop onboarding.
76
+ No AgentMuxer-hosted client metadata route is required.
26
77
 
27
78
  Release maintainers must run `vp run @agentmuxer/setup#release:smoke` and publish the exact tested
28
79
  tarball it writes to `.context/release`, never publish this directory with npm.
package/dist/bin.js CHANGED
@@ -1,14 +1,17 @@
1
1
  #!/usr/bin/env node
2
- import { isDeepStrictEqual, parseArgs } from "node:util";
3
- import { existsSync } from "node:fs";
4
- import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
2
+ import { isDeepStrictEqual, parseArgs, promisify, stripVTControlCharacters } from "node:util";
3
+ import { execFile, spawn } from "node:child_process";
4
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
5
+ import { tmpdir } from "node:os";
5
6
  import { dirname, join } from "node:path";
6
- import { agents, detectGlobalAgents, listInstalledServers, removeServer, upsertServer } from "add-mcp";
7
- import { parse } from "jsonc-parser";
7
+ import { existsSync } from "node:fs";
8
+ import { agents, detectGlobalAgents, listInstalledServers, removeServer } from "add-mcp";
9
+ import { applyEdits, modify, parse } from "jsonc-parser";
8
10
  import { parseTOML } from "toml-eslint-parser";
9
11
  //#region src/management.ts
10
12
  const agentmuxerServerName = "agentmuxer";
11
13
  const agentmuxerMcpUrl = "https://mcp.agentmuxer.com/mcp";
14
+ const openCodeClientId = "client_01M1TJJ3DKJR89RPXJ5XVWSKMH";
12
15
  const agentmuxerConfig = {
13
16
  type: "http",
14
17
  url: agentmuxerMcpUrl
@@ -223,15 +226,47 @@ const agentmuxerServer = async (client) => {
223
226
  };
224
227
  };
225
228
  const conflictError = (client) => `${clientLabel(client)} already has an ${agentmuxerServerName} configuration that setup cannot safely manage; leave it unchanged or remove it manually`;
229
+ const nativeServerConfig = (client) => {
230
+ const config = agents[client].transformConfig(agentmuxerServerName, agentmuxerConfig);
231
+ if (typeof config !== "object" || config === null || Array.isArray(config)) throw new Error("Invalid client configuration transform");
232
+ const base = Object.fromEntries(Object.entries(config).filter(([, value]) => value !== void 0));
233
+ return client === "codex" ? base : {
234
+ ...base,
235
+ oauth: client === "claude-code" ? { scopes: "offline_access" } : {
236
+ clientId: openCodeClientId,
237
+ scope: "offline_access"
238
+ }
239
+ };
240
+ };
226
241
  const isManagedServer = (client, server) => {
227
242
  const transformed = agents[client].transformConfig(agentmuxerServerName, agentmuxerConfig);
228
243
  if (typeof transformed !== "object" || transformed === null || Array.isArray(transformed)) return false;
229
244
  const expected = Object.fromEntries(Object.entries(transformed).filter(([, value]) => value !== void 0));
230
- return isDeepStrictEqual(server.config, expected) || client === "codex" && isDeepStrictEqual(server.config, { url: "https://mcp.agentmuxer.com/mcp" });
245
+ const config = client === "opencode" && server.config["enabled"] === false ? {
246
+ ...server.config,
247
+ enabled: true
248
+ } : server.config;
249
+ return isDeepStrictEqual(config, expected) || isDeepStrictEqual(config, nativeServerConfig(client)) || client === "opencode" && isDeepStrictEqual(config, {
250
+ ...nativeServerConfig(client),
251
+ oauth: { scope: "offline_access" }
252
+ }) || client === "codex" && isDeepStrictEqual(config, { url: "https://mcp.agentmuxer.com/mcp" });
231
253
  };
232
254
  const isManagedServerAtPath = async (client, server, path) => isManagedServer(client, server) && (client !== "codex" || await readCodexLayout(path) !== void 0);
233
255
  const uninspectableConfigError = (client) => `${clientLabel(client)} configuration exists but the client was not detected; refusing to modify it`;
234
- const installAgentmuxer = async (client) => {
256
+ const writeNativeServerConfig = async (client, path, config) => {
257
+ const original = await readOptionalFile(path) ?? "{}";
258
+ const next = applyEdits(original, modify(original, [client === "claude-code" ? "mcpServers" : "mcp", agentmuxerServerName], config, { formattingOptions: {
259
+ insertSpaces: true,
260
+ tabSize: 2,
261
+ eol: original.includes("\r\n") ? "\r\n" : "\n"
262
+ } }));
263
+ await mkdir(dirname(path), { recursive: true });
264
+ await writeFile(path, next, {
265
+ encoding: "utf8",
266
+ mode: 384
267
+ });
268
+ };
269
+ const installAgentmuxer = async (client, newOpenCodeRegistration = false) => {
235
270
  const existing = await agentmuxerServer(client);
236
271
  if (!existing.detected && existsSync(existing.configPath)) return {
237
272
  success: false,
@@ -243,26 +278,37 @@ const installAgentmuxer = async (client) => {
243
278
  path: existing.configPath,
244
279
  error: conflictError(client)
245
280
  };
246
- if (existing.server !== void 0) return {
281
+ if (existing.server !== void 0 && (client === "codex" || isDeepStrictEqual(existing.server.config, nativeServerConfig(client)))) return {
247
282
  success: true,
248
- path: existing.configPath
283
+ path: existing.configPath,
284
+ stableClientId: client === "opencode"
249
285
  };
250
286
  if (client === "codex") {
251
287
  await installCodexServer(existing.configPath);
252
288
  return {
253
289
  success: true,
254
- path: existing.configPath
290
+ path: existing.configPath,
291
+ created: true
255
292
  };
256
293
  }
257
- const configExisted = existsSync(existing.configPath);
258
- const result = upsertServer(client, agentmuxerServerName, agentmuxerConfig);
259
- if (result.success && client === "claude-code" && !configExisted) try {
260
- await chmod(result.path, 384);
261
- } catch (error) {
262
- await rm(result.path, { force: true });
263
- throw error;
264
- }
265
- return result;
294
+ const config = client === "opencode" && !(existing.server === void 0 && newOpenCodeRegistration) && !isDeepStrictEqual({
295
+ ...existing.server?.config,
296
+ enabled: true
297
+ }, nativeServerConfig(client)) ? {
298
+ ...nativeServerConfig(client),
299
+ oauth: { scope: "offline_access" }
300
+ } : nativeServerConfig(client);
301
+ if (existing.server !== void 0 && isDeepStrictEqual(existing.server.config, config)) return {
302
+ success: true,
303
+ path: existing.configPath
304
+ };
305
+ await writeNativeServerConfig(client, existing.configPath, config);
306
+ return {
307
+ success: true,
308
+ path: existing.configPath,
309
+ created: existing.server === void 0,
310
+ stableClientId: client === "opencode" && isDeepStrictEqual(config, nativeServerConfig(client))
311
+ };
266
312
  };
267
313
  const listAgentmuxerInstallations = async (clients = supportedClients) => {
268
314
  configureClientPaths();
@@ -271,7 +317,7 @@ const listAgentmuxerInstallations = async (clients = supportedClients) => {
271
317
  agents: [...clients]
272
318
  });
273
319
  await Promise.all(installations.flatMap(({ agentType, configPath }) => isSupportedClient(agentType) ? [validateConfigSyntax(agentType, configPath)] : []));
274
- return (await Promise.all(installations.flatMap(({ agentType, displayName, servers }) => isSupportedClient(agentType) ? servers.filter(({ serverName }) => serverName === agentmuxerServerName).map(async (server) => await isManagedServerAtPath(agentType, server, server.configPath) ? {
320
+ return (await Promise.all(installations.flatMap(({ agentType, displayName, servers }) => isSupportedClient(agentType) ? servers.filter(({ serverName, config }) => serverName === "agentmuxer" && !(agentType === "opencode" && config["enabled"] === false)).map(async (server) => await isManagedServerAtPath(agentType, server, server.configPath) ? {
275
321
  client: agentType,
276
322
  displayName,
277
323
  configPath: server.configPath
@@ -296,18 +342,51 @@ const removeAgentmuxer = async (client) => {
296
342
  removed: false,
297
343
  error: conflictError(client)
298
344
  };
299
- if (client === "codex") try {
300
- const layout = await readCodexLayout(existing.configPath);
301
- if (layout === void 0) return {
302
- success: false,
303
- path: existing.configPath,
304
- removed: false
305
- };
306
- await writeFile(existing.configPath, layout, "utf8");
345
+ try {
346
+ const original = await readFile(existing.configPath, "utf8");
347
+ let result;
348
+ if (client === "codex") {
349
+ const layout = await readCodexLayout(existing.configPath);
350
+ if (layout === void 0) return {
351
+ success: false,
352
+ path: existing.configPath,
353
+ removed: false
354
+ };
355
+ await writeFile(existing.configPath, layout, "utf8");
356
+ result = {
357
+ success: true,
358
+ path: existing.configPath,
359
+ removed: true
360
+ };
361
+ } else if (client === "opencode") {
362
+ if (existing.server.config["enabled"] === false) return {
363
+ success: true,
364
+ path: existing.configPath,
365
+ removed: false,
366
+ disabled: true
367
+ };
368
+ await writeNativeServerConfig(client, existing.configPath, {
369
+ ...existing.server.config,
370
+ enabled: false
371
+ });
372
+ result = {
373
+ success: true,
374
+ path: existing.configPath,
375
+ removed: true,
376
+ disabled: true
377
+ };
378
+ } else result = removeServer(client, agentmuxerServerName);
379
+ if (!result.success || !result.removed) return result;
380
+ const next = await readOptionalFile(existing.configPath);
307
381
  return {
308
- success: true,
309
- path: existing.configPath,
310
- removed: true
382
+ ...result,
383
+ rollback: async () => {
384
+ if (await readOptionalFile(existing.configPath) !== next) throw new Error("MCP configuration changed during removal; refusing to overwrite it");
385
+ await writeFile(existing.configPath, original, {
386
+ encoding: "utf8",
387
+ mode: 384
388
+ });
389
+ }
311
390
  };
312
391
  } catch (error) {
313
392
  return {
@@ -317,7 +396,6 @@ const removeAgentmuxer = async (client) => {
317
396
  error: error instanceof Error ? error.message : "removal failed"
318
397
  };
319
398
  }
320
- return removeServer(client, agentmuxerServerName);
321
399
  };
322
400
  const authenticationCommands = {
323
401
  codex: `codex mcp login ${agentmuxerServerName}`,
@@ -326,14 +404,112 @@ const authenticationCommands = {
326
404
  };
327
405
  const authenticationCommand = (client) => authenticationCommands[client];
328
406
  //#endregion
407
+ //#region src/authentication.ts
408
+ const execute = promisify(execFile);
409
+ const statusCommands = {
410
+ codex: [
411
+ "codex",
412
+ "mcp",
413
+ "list",
414
+ "--json"
415
+ ],
416
+ "claude-code": [
417
+ "claude",
418
+ "mcp",
419
+ "list"
420
+ ],
421
+ opencode: [
422
+ "opencode",
423
+ "mcp",
424
+ "auth",
425
+ "list"
426
+ ]
427
+ };
428
+ const nativeEnvironment = (client) => client === "opencode" ? Object.fromEntries(Object.entries(process.env).filter(([name]) => ![
429
+ "OPENCODE_CONFIG",
430
+ "OPENCODE_CONFIG_CONTENT",
431
+ "OPENCODE_CONFIG_DIR"
432
+ ].includes(name.toUpperCase()))) : process.env;
433
+ /** Read supported native CLI output, never the client's private credential store. */
434
+ const authenticationState = async (client) => {
435
+ const [command, ...args] = statusCommands[client];
436
+ const cwd = await mkdtemp(join(tmpdir(), "agentmuxer-auth-"));
437
+ try {
438
+ const env = nativeEnvironment(client);
439
+ if (client === "opencode") env["OPENCODE_CONFIG_CONTENT"] = JSON.stringify({ mcp: { agentmuxer: {
440
+ type: "remote",
441
+ url: agentmuxerMcpUrl,
442
+ enabled: true,
443
+ oauth: { scope: "offline_access" }
444
+ } } });
445
+ const { stdout } = await execute(command, args, {
446
+ cwd,
447
+ env,
448
+ shell: process.platform === "win32",
449
+ timeout: 3e4,
450
+ maxBuffer: 1048576
451
+ });
452
+ if (client === "codex") {
453
+ const entries = JSON.parse(stdout);
454
+ if (!Array.isArray(entries)) return "unavailable";
455
+ const entry = entries.find((value) => typeof value === "object" && value !== null && "name" in value && value.name === "agentmuxer");
456
+ if (typeof entry !== "object" || entry === null || !("auth_status" in entry)) return "unavailable";
457
+ return entry.auth_status === "o_auth" ? "credentials-present" : entry.auth_status === "not_logged_in" ? "signed-out" : "unavailable";
458
+ }
459
+ const line = stripVTControlCharacters(stdout).split(/\r?\n/).find((value) => client === "claude-code" ? /^agentmuxer:\s/.test(value.trim()) : /^[\s│●✓✗✘✔✖○⚠]*agentmuxer\s/.test(value));
460
+ if (line === void 0) return "unavailable";
461
+ if (/\b(?:needs authentication|not authenticated)\b/i.test(line)) return "signed-out";
462
+ if (client === "claude-code" && /✓ Connected\s*$/i.test(line)) return "connected";
463
+ return client === "opencode" && /\b(?:authenticated|expired)\s*$/i.test(line) ? "credentials-present" : "unavailable";
464
+ } catch {
465
+ return "unavailable";
466
+ } finally {
467
+ await rm(cwd, {
468
+ recursive: true,
469
+ force: true
470
+ });
471
+ }
472
+ };
473
+ /** The native client owns browser consent, credential storage and token refresh. */
474
+ const signIn = async (client) => {
475
+ const cwd = await mkdtemp(join(tmpdir(), "agentmuxer-auth-"));
476
+ try {
477
+ await new Promise((resolve, reject) => {
478
+ const [command, ...args] = authenticationCommand(client).split(" ");
479
+ if (command === void 0) {
480
+ reject(/* @__PURE__ */ new Error("Missing native sign-in command"));
481
+ return;
482
+ }
483
+ const child = spawn(command, args, {
484
+ cwd,
485
+ env: nativeEnvironment(client),
486
+ stdio: "inherit",
487
+ shell: process.platform === "win32"
488
+ });
489
+ child.once("error", () => {
490
+ reject(/* @__PURE__ */ new Error(`${clientLabel(client)} sign-in could not start. Configuration is saved; run ${authenticationCommand(client)} from a terminal where the client is installed.`));
491
+ });
492
+ child.once("close", (code) => {
493
+ if (code === 0) resolve();
494
+ else reject(/* @__PURE__ */ new Error(`${clientLabel(client)} sign-in did not complete. Configuration is saved; retry ${authenticationCommand(client)}.`));
495
+ });
496
+ });
497
+ } finally {
498
+ await rm(cwd, {
499
+ recursive: true,
500
+ force: true
501
+ });
502
+ }
503
+ };
504
+ //#endregion
329
505
  //#region src/cli.ts
330
- const version = "0.1.1";
506
+ const version = "0.1.2";
331
507
  const help = `AgentMuxer setup ${version}
332
508
 
333
509
  Usage:
334
- agentmuxer-setup install Install AgentMuxer globally
335
- agentmuxer-setup status Verify client configuration and guidance
336
- agentmuxer-setup remove Remove AgentMuxer-managed configuration
510
+ npx -y @agentmuxer/setup@latest install Install AgentMuxer globally and start native sign-in
511
+ npx -y @agentmuxer/setup@latest status Check configuration and native sign-in status
512
+ npx -y @agentmuxer/setup@latest remove Remove AgentMuxer-managed configuration
337
513
 
338
514
  Options:
339
515
  --client <name> Target codex, claude-code, or opencode; repeatable
@@ -343,13 +519,6 @@ Options:
343
519
  const fail = (message) => {
344
520
  throw new Error(message);
345
521
  };
346
- const clientSetupState = async (client) => {
347
- const [installations, guidance] = await Promise.all([listAgentmuxerInstallations([client]), clientGuidanceStatus(client)]);
348
- return {
349
- guidance: guidance.installed,
350
- mcp: installations.length > 0
351
- };
352
- };
353
522
  const withClientSetupRollback = async (client, operation) => {
354
523
  const actions = [];
355
524
  try {
@@ -382,21 +551,25 @@ const install = async (args) => {
382
551
  const outcomes = await Promise.all(clients.map(async (client) => {
383
552
  try {
384
553
  return await withClientSetupRollback(client, async (defer) => {
554
+ const state = client === "opencode" ? await authenticationState(client) : void 0;
555
+ if (state === "unavailable") throw new Error("Native sign-in status is unavailable; retry setup. Existing configuration and credentials were preserved.");
385
556
  const guidance = await installClientGuidance(client);
386
557
  defer(guidance.rollback);
387
- const result = await installAgentmuxer(client);
558
+ const result = await installAgentmuxer(client, state === "signed-out");
388
559
  if (!result.success) throw new Error(result.error ?? "installation failed");
389
560
  return {
390
- lines: [
391
- `${clientLabel(client)} MCP: ${result.path}`,
392
- `${clientLabel(client)} guidance: ${guidance.path}`,
393
- `${clientLabel(client)} sign-in (required): ${authenticationCommand(client)}`
394
- ],
561
+ client,
562
+ created: result.created === true,
563
+ state,
564
+ stableClientId: result.stableClientId === true,
565
+ lines: [`${clientLabel(client)} MCP: ${result.path}`, `${clientLabel(client)} guidance: ${guidance.path}`],
395
566
  failure: void 0
396
567
  };
397
568
  });
398
569
  } catch (error) {
399
570
  return {
571
+ client,
572
+ created: false,
400
573
  lines: [],
401
574
  failure: `${clientLabel(client)}: ${error instanceof Error ? error.message : "installation failed"}`
402
575
  };
@@ -404,8 +577,27 @@ const install = async (args) => {
404
577
  }));
405
578
  for (const line of outcomes.flatMap((outcome) => outcome.lines ?? [])) process.stdout.write(`${line}\n`);
406
579
  const failures = outcomes.flatMap((outcome) => outcome.failure ?? []);
580
+ for (const outcome of outcomes) {
581
+ if (outcome.failure !== void 0) continue;
582
+ try {
583
+ const state = outcome.state ?? await authenticationState(outcome.client);
584
+ if (state === "connected" || state === "credentials-present") {
585
+ process.stdout.write(`${clientLabel(outcome.client)}: ${state === "connected" ? "connected" : "native credentials present; live connection not verified"}.\n`);
586
+ continue;
587
+ }
588
+ if (state === "unavailable") throw new Error(`${clientLabel(outcome.client)} sign-in status is unavailable. Check that the client is installed and reachable, then retry setup. Existing configuration and credentials were preserved.`);
589
+ if (!outcome.created && !outcome.stableClientId) throw new Error(`${clientLabel(outcome.client)} requires sign-in: ${authenticationCommand(outcome.client)}. Setup preserved the existing registration. Clients using dynamic registration may create a new agent identity on re-login.`);
590
+ process.stdout.write(`${clientLabel(outcome.client)}: starting native sign-in.\n`);
591
+ await signIn(outcome.client);
592
+ const after = await authenticationState(outcome.client);
593
+ if (after !== "connected" && after !== "credentials-present") throw new Error(`${clientLabel(outcome.client)} did not confirm native sign-in.`);
594
+ process.stdout.write(`${clientLabel(outcome.client)}: ${after === "connected" ? "connected" : "native sign-in completed; live connection not verified"}.\n`);
595
+ } catch (error) {
596
+ failures.push(error instanceof Error ? error.message : "Native sign-in failed");
597
+ }
598
+ }
407
599
  if (failures.length > 0) fail(failures.join("\n"));
408
- process.stdout.write("Restart open clients after setup, then run the sign-in command above every AgentMuxer call requires it (unauthenticated requests answer a 401 challenge).\n");
600
+ process.stdout.write("Restart open clients to load the configuration. Native sign-in does not verify a live MCP request; check the connection in your client.\n");
409
601
  };
410
602
  const status = async (args) => {
411
603
  const clients = parseClients(args) ?? await detectedClients();
@@ -419,30 +611,33 @@ const status = async (args) => {
419
611
  process.stdout.write(`${clientLabel(client)} MCP: ${installation?.configPath ?? "not installed"}\n`);
420
612
  process.stdout.write(`${clientLabel(client)} guidance: ${guidance.installed ? guidance.path : "not installed"}\n`);
421
613
  return installation !== void 0 && guidance.installed;
422
- }).every(Boolean)) fail("AgentMuxer setup is incomplete or conflicting; run agentmuxer-setup install, or remove a custom same-name MCP manually");
614
+ }).every(Boolean)) fail("AgentMuxer setup is incomplete or conflicting; run npx -y @agentmuxer/setup@latest install, or remove a custom same-name MCP manually");
423
615
  process.stdout.write("AgentMuxer configuration and guidance verified.\n");
424
- process.stdout.write("Not verified: sign-in. If calls answer 401, sign in:\n");
425
- for (const client of clients) process.stdout.write(` ${clientLabel(client)}: ${authenticationCommand(client)}\n`);
616
+ const failures = [];
617
+ const authStates = await Promise.all(clients.map(authenticationState));
618
+ for (const [index, client] of clients.entries()) {
619
+ const state = authStates[index] ?? "unavailable";
620
+ process.stdout.write(`${clientLabel(client)}: ${state === "credentials-present" ? "native credentials present; live connection not verified" : state}.\n`);
621
+ if (state === "signed-out" || state === "unavailable") failures.push(clientLabel(client));
622
+ }
623
+ if (failures.length > 0) fail(`Native sign-in could not be confirmed for: ${failures.join(", ")}. Check the connection in your client; existing credentials were preserved.`);
426
624
  };
427
625
  const remove = async (args) => {
428
626
  const clients = parseClients(args) ?? supportedClients;
429
627
  await Promise.all(clients.map(validateClientGuidance));
430
628
  const outcomes = await Promise.all(clients.map(async (client) => {
431
629
  try {
432
- const before = await clientSetupState(client);
630
+ const before = await clientGuidanceStatus(client);
433
631
  return await withClientSetupRollback(client, async (defer) => {
434
632
  const result = await removeAgentmuxer(client);
435
- if (!result.success) throw new Error(result.error ?? "removal failed");
436
- if (result.removed && before.mcp) defer(async () => {
437
- const rollback = await installAgentmuxer(client);
438
- if (!rollback.success) throw new Error(rollback.error ?? "MCP rollback failed");
439
- });
440
- if (before.guidance) defer(async () => {
633
+ if (!result.success) throw new Error("error" in result && result.error || "removal failed");
634
+ if ("rollback" in result) defer(result.rollback);
635
+ if (before.installed) defer(async () => {
441
636
  await installClientGuidance(client);
442
637
  });
443
638
  const guidanceRemoved = await removeClientGuidance(client);
444
639
  return {
445
- lines: [`${clientLabel(client)} MCP: ${result.removed ? "removed" : "not installed"}`, ...guidanceRemoved ? [`${clientLabel(client)} guidance: removed`] : []],
640
+ lines: [`${clientLabel(client)} MCP: ${"disabled" in result && result.disabled ? "disabled; registration retained" : result.removed ? "removed" : "not installed"}`, ...guidanceRemoved ? [`${clientLabel(client)} guidance: removed`] : []],
446
641
  failure: void 0
447
642
  };
448
643
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentmuxer/setup",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Install AgentMuxer's hosted MCP in supported coding agents",
5
5
  "keywords": [
6
6
  "agentmuxer",