@integrity-labs/agt-cli 0.27.8 → 0.27.9-test.11

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 (28) hide show
  1. package/dist/assets/impersonate-statusline.sh +7 -2
  2. package/dist/bin/agt.js +946 -129
  3. package/dist/bin/agt.js.map +1 -1
  4. package/dist/{chunk-YSBGIXJG.js → chunk-45I2X4M7.js} +361 -26
  5. package/dist/chunk-45I2X4M7.js.map +1 -0
  6. package/dist/{chunk-IB655E5U.js → chunk-N2EBL4PZ.js} +1086 -285
  7. package/dist/chunk-N2EBL4PZ.js.map +1 -0
  8. package/dist/{chunk-GN4XPQWJ.js → chunk-SJPAXXTW.js} +535 -94
  9. package/dist/chunk-SJPAXXTW.js.map +1 -0
  10. package/dist/{claude-pair-runtime-ZBQKBBMT.js → claude-pair-runtime-OUGV74LY.js} +2 -2
  11. package/dist/lib/manager-worker.js +2071 -517
  12. package/dist/lib/manager-worker.js.map +1 -1
  13. package/dist/mcp/direct-chat-channel.js +49 -455
  14. package/dist/mcp/index.js +232 -137
  15. package/dist/mcp/slack-channel.js +1379 -856
  16. package/dist/mcp/teams-channel.js +16018 -0
  17. package/dist/mcp/telegram-channel.js +1264 -618
  18. package/dist/{persistent-session-ICYFLUAM.js → persistent-session-G3MR7JVI.js} +5 -3
  19. package/dist/responsiveness-probe-UHSBETBR.js +74 -0
  20. package/dist/responsiveness-probe-UHSBETBR.js.map +1 -0
  21. package/package.json +2 -2
  22. package/dist/chunk-GN4XPQWJ.js.map +0 -1
  23. package/dist/chunk-IB655E5U.js.map +0 -1
  24. package/dist/chunk-YSBGIXJG.js.map +0 -1
  25. package/dist/responsiveness-probe-WZNQ2762.js +0 -33
  26. package/dist/responsiveness-probe-WZNQ2762.js.map +0 -1
  27. /package/dist/{claude-pair-runtime-ZBQKBBMT.js.map → claude-pair-runtime-OUGV74LY.js.map} +0 -0
  28. /package/dist/{persistent-session-ICYFLUAM.js.map → persistent-session-G3MR7JVI.js.map} +0 -0
package/dist/bin/agt.js CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ApiError,
4
- DEFAULT_AGT_HOST,
4
+ LITERAL_SECRET_PATTERNS,
5
+ PROD_AGT_HOST,
5
6
  api,
6
7
  error,
7
8
  exchangeApiKey,
@@ -27,7 +28,7 @@ import {
27
28
  success,
28
29
  table,
29
30
  warn
30
- } from "../chunk-IB655E5U.js";
31
+ } from "../chunk-N2EBL4PZ.js";
31
32
  import {
32
33
  CHANNEL_REGISTRY,
33
34
  DEPLOYMENT_TEMPLATES,
@@ -53,11 +54,11 @@ import {
53
54
  renderTemplate,
54
55
  resolveChannels,
55
56
  serializeManifestForSlackCli
56
- } from "../chunk-YSBGIXJG.js";
57
+ } from "../chunk-45I2X4M7.js";
57
58
 
58
59
  // src/bin/agt.ts
59
- import { join as join15 } from "path";
60
- import { homedir as homedir6 } from "os";
60
+ import { join as join20 } from "path";
61
+ import { homedir as homedir9 } from "os";
61
62
  import { Command } from "commander";
62
63
 
63
64
  // src/commands/whoami.ts
@@ -551,11 +552,11 @@ async function lintCommand(path) {
551
552
  process.exitCode = 1;
552
553
  return;
553
554
  }
554
- const { readdirSync: readdirSync3, statSync: statSync2 } = await import("fs");
555
- const entries = readdirSync3(augmentedDir);
555
+ const { readdirSync: readdirSync7, statSync: statSync5 } = await import("fs");
556
+ const entries = readdirSync7(augmentedDir);
556
557
  for (const entry of entries) {
557
558
  const entryPath = join2(augmentedDir, entry);
558
- if (statSync2(entryPath).isDirectory()) {
559
+ if (statSync5(entryPath).isDirectory()) {
559
560
  dirs.push({ name: entry, dir: entryPath });
560
561
  }
561
562
  }
@@ -828,7 +829,11 @@ async function channelSlackSetupCommand(agentCodeName, options) {
828
829
  const manifest = generateSlackAppManifest({
829
830
  agent_name: agent2.display_name ?? agentCodeName,
830
831
  description: `Augmented-managed Slack bot for agent ${agentCodeName}`,
831
- scopes: selectedScopes
832
+ scopes: selectedScopes,
833
+ // ENG-6044: per-agent slash commands carry the code-name suffix.
834
+ // No-op until a slash_command_url is supplied, but keeps this
835
+ // manifest consistent with the API-generated ones.
836
+ agent_code_name: agent2.code_name ?? agentCodeName
832
837
  });
833
838
  const manifestObj = serializeManifestForSlackCli(manifest);
834
839
  const manifestJson = JSON.stringify(manifestObj, null, 2);
@@ -1435,6 +1440,23 @@ async function provisionCommand(codeName, options) {
1435
1440
  };
1436
1441
  }
1437
1442
  const resolvedChannels = resolveChannels(agentChannelPolicy, orgChannelPolicy);
1443
+ spinner.text = "Fetching integrations\u2026";
1444
+ const agentId = agentData.agent_id;
1445
+ let integrations = [];
1446
+ let integrationsError = null;
1447
+ try {
1448
+ const integrationsData = await api.post("/host/agent-integrations", { agent_id: agentId });
1449
+ integrations = integrationsData.integrations ?? [];
1450
+ } catch (err) {
1451
+ integrationsError = err.message;
1452
+ if (!json) {
1453
+ spinner.stop();
1454
+ warn(
1455
+ `Could not fetch integrations: ${integrationsError}. .mcp.json may be missing integration servers.`
1456
+ );
1457
+ spinner.start("Provisioning\u2026");
1458
+ }
1459
+ }
1438
1460
  const frameworkId = agentData.framework ?? "openclaw";
1439
1461
  const adapter = getFramework(frameworkId);
1440
1462
  spinner.text = `Building ${adapter.label} config\u2026`;
@@ -1445,6 +1467,7 @@ async function provisionCommand(codeName, options) {
1445
1467
  toolsFrontmatter,
1446
1468
  toolsContent,
1447
1469
  resolvedChannels,
1470
+ integrations,
1448
1471
  deploymentTarget: target,
1449
1472
  gatewayPort: 9e3
1450
1473
  };
@@ -1478,6 +1501,8 @@ async function provisionCommand(codeName, options) {
1478
1501
  charter_hash: provisionOutput.charterHash,
1479
1502
  tools_hash: provisionOutput.toolsHash,
1480
1503
  channels: resolvedChannels.length,
1504
+ integrations: integrations.length,
1505
+ integrations_error: integrationsError,
1481
1506
  artifacts: provisionOutput.artifacts.map((a) => ({
1482
1507
  path: a.relativePath,
1483
1508
  size: a.content.length
@@ -1534,6 +1559,8 @@ async function provisionCommand(codeName, options) {
1534
1559
  charter_hash: provisionOutput.charterHash,
1535
1560
  tools_hash: provisionOutput.toolsHash,
1536
1561
  channels: resolvedChannels.length,
1562
+ integrations: integrations.length,
1563
+ integrations_error: integrationsError,
1537
1564
  artifacts: provisionOutput.artifacts.map((a) => a.relativePath)
1538
1565
  });
1539
1566
  return;
@@ -1546,6 +1573,7 @@ async function provisionCommand(codeName, options) {
1546
1573
  info(`CHARTER hash: ${provisionOutput.charterHash.slice(0, 12)}\u2026`);
1547
1574
  info(`TOOLS hash: ${provisionOutput.toolsHash.slice(0, 12)}\u2026`);
1548
1575
  info(`Channels: ${resolvedChannels.length} active`);
1576
+ info(`Integrations: ${integrations.length}${integrationsError ? " (fetch failed)" : ""}`);
1549
1577
  info(`Artifacts: ${provisionOutput.artifacts.map((a) => a.relativePath).join(", ")}`);
1550
1578
  console.log();
1551
1579
  info("Next steps:");
@@ -1557,8 +1585,9 @@ async function provisionCommand(codeName, options) {
1557
1585
  import chalk10 from "chalk";
1558
1586
  import ora10 from "ora";
1559
1587
  import { spawn } from "child_process";
1560
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync7 } from "fs";
1561
- import { join as join10 } from "path";
1588
+ import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync7 } from "fs";
1589
+ import { delimiter, dirname as dirname3, join as join10 } from "path";
1590
+ import { fileURLToPath as fileURLToPath2 } from "url";
1562
1591
 
1563
1592
  // src/lib/impersonate-flag.ts
1564
1593
  var ENV_VAR = "AGT_IMPERSONATE_ENABLED";
@@ -1590,10 +1619,12 @@ production.
1590
1619
  }
1591
1620
 
1592
1621
  // src/lib/impersonate-state.ts
1622
+ import { createHash } from "crypto";
1593
1623
  import {
1594
1624
  chmodSync,
1595
1625
  existsSync as existsSync2,
1596
1626
  mkdirSync as mkdirSync4,
1627
+ readdirSync,
1597
1628
  readFileSync as readFileSync2,
1598
1629
  renameSync,
1599
1630
  rmSync,
@@ -1604,6 +1635,12 @@ import { join as join6 } from "path";
1604
1635
  var IMPERSONATE_ROOT = join6(homedir(), ".augmented-impersonate");
1605
1636
  var ACTIVE_DIR = join6(IMPERSONATE_ROOT, "active");
1606
1637
  var ACTIVE_MANIFEST_PATH = join6(ACTIVE_DIR, "manifest.json");
1638
+ var SESSION_FILE_PREFIX = "session-";
1639
+ var SESSION_FILE_SUFFIX = ".json";
1640
+ function sessionManifestPath(projectCwd) {
1641
+ const hash = createHash("sha256").update(projectCwd).digest("hex").slice(0, 16);
1642
+ return join6(ACTIVE_DIR, `${SESSION_FILE_PREFIX}${hash}${SESSION_FILE_SUFFIX}`);
1643
+ }
1607
1644
  var CODE_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
1608
1645
  function assertValidCodeName(codeName) {
1609
1646
  if (!CODE_NAME_RE.test(codeName)) {
@@ -1627,36 +1664,84 @@ function ensureImpersonateWorkdir(codeName) {
1627
1664
  mkdirSync4(dir, { recursive: true });
1628
1665
  return dir;
1629
1666
  }
1630
- function readActiveManifest() {
1631
- if (!existsSync2(ACTIVE_MANIFEST_PATH)) return null;
1667
+ function validateManifest(parsed) {
1668
+ if (typeof parsed !== "object" || parsed === null) return null;
1669
+ const m = parsed;
1670
+ const backupsOk = typeof m.backups === "object" && m.backups !== null && Object.values(m.backups).every(
1671
+ (v) => v === "had-file" || v === "was-symlink" || v === "didnt-exist"
1672
+ );
1673
+ if (typeof m.code_name !== "string" || typeof m.agent_id !== "string" || typeof m.team_id !== "string" || typeof m.token !== "string" || typeof m.expires_at !== "number" || typeof m.session_id !== "string" || typeof m.minted_at !== "string" || typeof m.project_cwd !== "string" || // ENG-5688 (redesigned): host required so `exit` calls /stop against
1674
+ // the right environment. Required, not optional, because writing the
1675
+ // wrong host on an existing session would point exit at a server
1676
+ // that has no record of the session — silently misleading.
1677
+ typeof m.host !== "string" || m.host.length === 0 || !backupsOk) {
1678
+ return null;
1679
+ }
1680
+ return parsed;
1681
+ }
1682
+ function readManifestFile(path) {
1683
+ if (!existsSync2(path)) return null;
1632
1684
  try {
1633
- const raw = readFileSync2(ACTIVE_MANIFEST_PATH, "utf-8");
1634
- const parsed = JSON.parse(raw);
1635
- const backupsOk = typeof parsed.backups === "object" && parsed.backups !== null && Object.values(parsed.backups).every(
1636
- (v) => v === "had-file" || v === "was-symlink" || v === "didnt-exist"
1637
- );
1638
- if (typeof parsed.code_name !== "string" || typeof parsed.agent_id !== "string" || typeof parsed.team_id !== "string" || typeof parsed.token !== "string" || typeof parsed.expires_at !== "number" || typeof parsed.session_id !== "string" || typeof parsed.minted_at !== "string" || typeof parsed.project_cwd !== "string" || // ENG-5688 (redesigned): host required so `exit` calls /stop against
1639
- // the right environment. Required, not optional, because writing the
1640
- // wrong host on an existing session would point exit at a server
1641
- // that has no record of the session — silently misleading.
1642
- typeof parsed.host !== "string" || parsed.host.length === 0 || !backupsOk) {
1643
- return null;
1644
- }
1645
- return parsed;
1685
+ return validateManifest(JSON.parse(readFileSync2(path, "utf-8")));
1646
1686
  } catch {
1647
1687
  return null;
1648
1688
  }
1649
1689
  }
1690
+ function migrateLegacyManifest() {
1691
+ if (!existsSync2(ACTIVE_MANIFEST_PATH)) return;
1692
+ const legacy = readManifestFile(ACTIVE_MANIFEST_PATH);
1693
+ try {
1694
+ if (!legacy) {
1695
+ rmSync(ACTIVE_MANIFEST_PATH, { force: true });
1696
+ return;
1697
+ }
1698
+ const target = sessionManifestPath(legacy.project_cwd);
1699
+ if (!existsSync2(target)) {
1700
+ mkdirSync4(ACTIVE_DIR, { recursive: true, mode: 448 });
1701
+ renameSync(ACTIVE_MANIFEST_PATH, target);
1702
+ chmodSync(target, 384);
1703
+ } else {
1704
+ rmSync(ACTIVE_MANIFEST_PATH, { force: true });
1705
+ }
1706
+ } catch {
1707
+ }
1708
+ }
1709
+ function readActiveManifest(projectCwd) {
1710
+ migrateLegacyManifest();
1711
+ return readManifestFile(sessionManifestPath(projectCwd));
1712
+ }
1713
+ function listActiveManifests() {
1714
+ migrateLegacyManifest();
1715
+ let entries;
1716
+ try {
1717
+ entries = readdirSync(ACTIVE_DIR);
1718
+ } catch {
1719
+ return [];
1720
+ }
1721
+ const out = [];
1722
+ for (const entry of entries) {
1723
+ if (!entry.startsWith(SESSION_FILE_PREFIX) || !entry.endsWith(SESSION_FILE_SUFFIX)) {
1724
+ continue;
1725
+ }
1726
+ const manifest = readManifestFile(join6(ACTIVE_DIR, entry));
1727
+ if (manifest) out.push(manifest);
1728
+ }
1729
+ return out;
1730
+ }
1650
1731
  function writeActiveManifest(manifest) {
1732
+ migrateLegacyManifest();
1651
1733
  mkdirSync4(ACTIVE_DIR, { recursive: true, mode: 448 });
1652
- const tmp = ACTIVE_MANIFEST_PATH + ".tmp";
1734
+ const path = sessionManifestPath(manifest.project_cwd);
1735
+ const tmp = path + ".tmp";
1653
1736
  writeFileSync4(tmp, JSON.stringify(manifest, null, 2), { mode: 384 });
1654
1737
  chmodSync(tmp, 384);
1655
- renameSync(tmp, ACTIVE_MANIFEST_PATH);
1738
+ renameSync(tmp, path);
1656
1739
  }
1657
- function clearActiveManifest() {
1658
- if (existsSync2(ACTIVE_MANIFEST_PATH)) {
1659
- rmSync(ACTIVE_MANIFEST_PATH);
1740
+ function clearActiveManifest(projectCwd) {
1741
+ migrateLegacyManifest();
1742
+ const path = sessionManifestPath(projectCwd);
1743
+ if (existsSync2(path)) {
1744
+ rmSync(path);
1660
1745
  }
1661
1746
  }
1662
1747
  function isExpired(manifest, now = Date.now()) {
@@ -1756,7 +1841,7 @@ import {
1756
1841
  copyFileSync,
1757
1842
  existsSync as existsSync4,
1758
1843
  mkdirSync as mkdirSync5,
1759
- readdirSync,
1844
+ readdirSync as readdirSync2,
1760
1845
  readFileSync as readFileSync3,
1761
1846
  renameSync as renameSync3,
1762
1847
  rmdirSync,
@@ -1814,7 +1899,7 @@ function unregisterIntroduceHook(backup) {
1814
1899
  if (created_claude_dir) {
1815
1900
  const claudeDir = dirOf(settings_path);
1816
1901
  try {
1817
- if (existsSync4(claudeDir) && readdirSync(claudeDir).length === 0) {
1902
+ if (existsSync4(claudeDir) && readdirSync2(claudeDir).length === 0) {
1818
1903
  rmdirSync(claudeDir);
1819
1904
  }
1820
1905
  } catch {
@@ -1850,7 +1935,7 @@ import {
1850
1935
  copyFileSync as copyFileSync2,
1851
1936
  existsSync as existsSync5,
1852
1937
  mkdirSync as mkdirSync6,
1853
- readdirSync as readdirSync2,
1938
+ readdirSync as readdirSync3,
1854
1939
  readFileSync as readFileSync4,
1855
1940
  renameSync as renameSync4,
1856
1941
  rmdirSync as rmdirSync2,
@@ -1920,7 +2005,7 @@ function unregisterStatusLine(backup) {
1920
2005
  if (created_claude_dir) {
1921
2006
  const claudeDir = dirname2(settings_path);
1922
2007
  try {
1923
- if (existsSync5(claudeDir) && readdirSync2(claudeDir).length === 0) {
2008
+ if (existsSync5(claudeDir) && readdirSync3(claudeDir).length === 0) {
1924
2009
  rmdirSync2(claudeDir);
1925
2010
  }
1926
2011
  } catch {
@@ -1996,12 +2081,86 @@ function buildIntroduction(input4) {
1996
2081
  const lines = [`I'm ${spokenName}${codeSuffix}${roleClause}.`, ""];
1997
2082
  if (integrations.length > 0) {
1998
2083
  lines.push(`Connected integrations: ${integrations.join(", ")}.`);
2084
+ lines.push("");
2085
+ lines.push(
2086
+ "If the operator asks what you can do, call each MCP server's `tools/list` and report the actual tool inventory. Do not narrate capabilities from CHARTER.md \u2014 it describes intent, not runtime state. Tools that fail to enumerate (server failed to spawn, env missing, etc.) should be reported as such, not silently omitted."
2087
+ );
1999
2088
  } else {
2000
2089
  lines.push("No integrations are connected.");
2001
2090
  }
2002
2091
  return lines.join("\n");
2003
2092
  }
2004
2093
 
2094
+ // src/lib/impersonate-mcp-rewrite.ts
2095
+ var SERVER_HOME_MCP_PATH_RE = /^\/home\/[^/]+\/\.augmented\/_mcp\/([^/]+)$/;
2096
+ var CHANNEL_SERVER_KEYS = /* @__PURE__ */ new Set([
2097
+ "slack",
2098
+ "telegram",
2099
+ "msteams",
2100
+ "direct-chat",
2101
+ "discord"
2102
+ ]);
2103
+ function rewriteMcpJsonForImpersonation(mcpJson, ctx) {
2104
+ const parsed = JSON.parse(mcpJson);
2105
+ if (!isPlainObject(parsed)) {
2106
+ throw new Error(
2107
+ `rewriteMcpJsonForImpersonation: expected an object at the JSON root, got ${typeof parsed}`
2108
+ );
2109
+ }
2110
+ const servers = parsed["mcpServers"];
2111
+ if (!isPlainObject(servers)) {
2112
+ throw new Error(
2113
+ "rewriteMcpJsonForImpersonation: expected an object-typed `mcpServers` field"
2114
+ );
2115
+ }
2116
+ const rewrittenServers = {};
2117
+ for (const [serverName, raw] of Object.entries(servers)) {
2118
+ rewrittenServers[serverName] = rewriteServerEntry(serverName, raw, ctx);
2119
+ }
2120
+ const out = { ...parsed, mcpServers: rewrittenServers };
2121
+ return JSON.stringify(out, null, 2);
2122
+ }
2123
+ function rewriteServerEntry(serverName, raw, ctx) {
2124
+ if (!isPlainObject(raw)) return raw;
2125
+ let args = raw["args"];
2126
+ if (Array.isArray(args)) {
2127
+ args = args.map((arg) => {
2128
+ if (typeof arg !== "string") return arg;
2129
+ const m = SERVER_HOME_MCP_PATH_RE.exec(arg);
2130
+ if (!m) return arg;
2131
+ return `${ctx.cliMcpBundleDir}/${m[1]}`;
2132
+ });
2133
+ }
2134
+ const env = isPlainObject(raw["env"]) ? { ...raw["env"] } : {};
2135
+ fillIfEmpty(env, "AGT_HOST", ctx.apiHost);
2136
+ fillIfEmpty(env, "AGT_API_KEY", ctx.impersonationToken);
2137
+ if (ctx.agentSessionToken) {
2138
+ fillIfEmpty(env, "AGT_AGENT_SESSION_TOKEN", ctx.agentSessionToken);
2139
+ }
2140
+ fillIfEmpty(env, "HOME", ctx.operatorHome);
2141
+ fillIfEmpty(env, "AGT_AGENT_ID", ctx.agentId);
2142
+ fillIfEmpty(env, "AGT_AGENT_CODE_NAME", ctx.agentCodeName);
2143
+ if ("PATH" in env) {
2144
+ env["PATH"] = ctx.operatorPath;
2145
+ }
2146
+ if (env["AGT_RUN_ID"] === "${AGT_RUN_ID}") {
2147
+ delete env["AGT_RUN_ID"];
2148
+ }
2149
+ if (CHANNEL_SERVER_KEYS.has(serverName)) {
2150
+ env["AGT_ACT_AS_AGENT_ID"] = ctx.agentId;
2151
+ }
2152
+ return { ...raw, args, env };
2153
+ }
2154
+ function fillIfEmpty(env, key, value) {
2155
+ const current = env[key];
2156
+ if (typeof current !== "string" || current.length === 0 || current === `\${${key}}`) {
2157
+ env[key] = value;
2158
+ }
2159
+ }
2160
+ function isPlainObject(v) {
2161
+ return typeof v === "object" && v !== null && !Array.isArray(v);
2162
+ }
2163
+
2005
2164
  // src/commands/impersonate.ts
2006
2165
  var SWAP_FILES = ["CLAUDE.md", ".mcp.json"];
2007
2166
  var AGENT_IMPERSONATION_HEADER = "X-Agent-Impersonation";
@@ -2017,13 +2176,15 @@ async function impersonateConnectCommand(token, options = {}) {
2017
2176
  process.exitCode = 1;
2018
2177
  return;
2019
2178
  }
2020
- const existing = readActiveManifest();
2021
- if (existing) {
2022
- const msg = `An impersonation session is already active (${existing.code_name}). Run \`agt impersonate exit\` first.`;
2023
- if (json) jsonOutput({ ok: false, error: msg });
2024
- else error(msg);
2025
- process.exitCode = 1;
2026
- return;
2179
+ if (!options.workdir) {
2180
+ const existing = readActiveManifest(process.cwd());
2181
+ if (existing) {
2182
+ const msg = `An impersonation session is already active in this directory (${existing.code_name}). Run \`agt impersonate exit\` here first, or connect from a different directory.`;
2183
+ if (json) jsonOutput({ ok: false, error: msg });
2184
+ else error(msg);
2185
+ process.exitCode = 1;
2186
+ return;
2187
+ }
2027
2188
  }
2028
2189
  if (!options.workdir) {
2029
2190
  const workTreeRoot = findGitWorkTreeRoot(process.cwd());
@@ -2041,7 +2202,7 @@ async function impersonateConnectCommand(token, options = {}) {
2041
2202
  isSilent: json
2042
2203
  });
2043
2204
  spinner.start();
2044
- const host2 = (options.apiHost ?? DEFAULT_AGT_HOST).replace(/\/+$/, "");
2205
+ const host2 = (options.apiHost ?? PROD_AGT_HOST).replace(/\/+$/, "");
2045
2206
  let bundle;
2046
2207
  try {
2047
2208
  const res = await fetch(`${host2}/impersonate/agent/redeem`, {
@@ -2071,10 +2232,28 @@ async function impersonateConnectCommand(token, options = {}) {
2071
2232
  );
2072
2233
  writeFileSync7(
2073
2234
  join10(personaDir, ".mcp.json"),
2074
- bundle.artifacts[".mcp.json"]
2235
+ rewriteMcpJsonForImpersonation(bundle.artifacts[".mcp.json"], {
2236
+ operatorHome: process.env.HOME ?? "",
2237
+ operatorPath: resolveOperatorPath(),
2238
+ cliMcpBundleDir: resolveCliMcpBundleDir(),
2239
+ impersonationToken: bundle.token,
2240
+ agentSessionToken: bundle.agent_session_token ?? null,
2241
+ apiHost: host2,
2242
+ agentId: bundle.agent.agent_id,
2243
+ agentCodeName: bundle.agent.code_name
2244
+ })
2075
2245
  );
2076
2246
  spinner.text = "Swapping persona files\u2026";
2077
2247
  const projectCwd = options.workdir ? ensureImpersonateWorkdir(bundle.agent.code_name) : process.cwd();
2248
+ const existingForTarget = readActiveManifest(projectCwd);
2249
+ if (existingForTarget) {
2250
+ spinner.fail("Impersonation already active for this agent.");
2251
+ const msg = `An impersonation session is already active in ${projectCwd} (${existingForTarget.code_name}). Run \`agt impersonate exit\` there first.`;
2252
+ if (json) jsonOutput({ ok: false, error: msg });
2253
+ else error(msg);
2254
+ process.exitCode = 1;
2255
+ return;
2256
+ }
2078
2257
  const backups = {};
2079
2258
  const swapped = [];
2080
2259
  let hookBackup;
@@ -2164,10 +2343,16 @@ async function impersonateConnectCommand(token, options = {}) {
2164
2343
  }
2165
2344
  info("Launching Claude Code\u2026 (use `--no-launch` next time to skip)");
2166
2345
  console.log();
2346
+ const launch = buildImpersonateClaudeLaunch(
2347
+ projectCwd,
2348
+ process.env,
2349
+ bundle.agent.agent_id
2350
+ );
2167
2351
  await new Promise((resolve2) => {
2168
- const child = spawn("claude", {
2352
+ const child = spawn("claude", launch.args, {
2169
2353
  stdio: "inherit",
2170
- cwd: projectCwd
2354
+ cwd: projectCwd,
2355
+ env: launch.env
2171
2356
  });
2172
2357
  child.on("error", (err) => {
2173
2358
  if (err.code === "ENOENT") {
@@ -2190,33 +2375,8 @@ async function impersonateConnectCommand(token, options = {}) {
2190
2375
  console.log();
2191
2376
  info("When done: `agt impersonate exit` (still active).");
2192
2377
  }
2193
- async function impersonateCurrentCommand() {
2194
- requireImpersonateEnabled();
2195
- const json = isJsonMode();
2196
- const manifest = readActiveManifest();
2197
- if (!manifest) {
2198
- if (json) jsonOutput({ ok: true, active: null });
2199
- else info("No active impersonation.");
2200
- return;
2201
- }
2378
+ function printManifestText(manifest) {
2202
2379
  const expired = isExpired(manifest);
2203
- if (json) {
2204
- jsonOutput({
2205
- ok: true,
2206
- active: {
2207
- code_name: manifest.code_name,
2208
- agent_id: manifest.agent_id,
2209
- team_id: manifest.team_id,
2210
- session_id: manifest.session_id,
2211
- minted_at: manifest.minted_at,
2212
- expires_at: manifest.expires_at,
2213
- expired,
2214
- project_cwd: manifest.project_cwd,
2215
- host: manifest.host
2216
- }
2217
- });
2218
- return;
2219
- }
2220
2380
  console.log();
2221
2381
  console.log(chalk10.bold(`Impersonating: ${manifest.code_name}`));
2222
2382
  console.log(` Agent ID: ${chalk10.dim(manifest.agent_id)}`);
@@ -2230,15 +2390,92 @@ async function impersonateCurrentCommand() {
2230
2390
  console.log(` Host: ${chalk10.dim(manifest.host)}`);
2231
2391
  console.log();
2232
2392
  }
2233
- async function impersonateExitCommand() {
2393
+ function manifestToJson(manifest) {
2394
+ return {
2395
+ code_name: manifest.code_name,
2396
+ agent_id: manifest.agent_id,
2397
+ team_id: manifest.team_id,
2398
+ session_id: manifest.session_id,
2399
+ minted_at: manifest.minted_at,
2400
+ expires_at: manifest.expires_at,
2401
+ expired: isExpired(manifest),
2402
+ project_cwd: manifest.project_cwd,
2403
+ host: manifest.host
2404
+ };
2405
+ }
2406
+ async function impersonateCurrentCommand(opts = {}) {
2407
+ requireImpersonateEnabled();
2408
+ const json = isJsonMode();
2409
+ if (opts.all) {
2410
+ const manifests = listActiveManifests();
2411
+ if (json) {
2412
+ jsonOutput({ ok: true, active: manifests.map(manifestToJson) });
2413
+ return;
2414
+ }
2415
+ if (manifests.length === 0) {
2416
+ info("No active impersonations.");
2417
+ return;
2418
+ }
2419
+ console.log();
2420
+ console.log(chalk10.bold(`Active impersonations (${manifests.length}):`));
2421
+ for (const m of manifests) printManifestText(m);
2422
+ return;
2423
+ }
2424
+ const manifest = readActiveManifest(process.cwd());
2425
+ if (!manifest) {
2426
+ const others = listActiveManifests();
2427
+ if (json) {
2428
+ jsonOutput({ ok: true, active: null, others_active: others.length });
2429
+ return;
2430
+ }
2431
+ if (others.length > 0) {
2432
+ info(
2433
+ `No active impersonation in this directory \u2014 ${others.length} active elsewhere. Run \`agt impersonate current --all\` to list them.`
2434
+ );
2435
+ } else {
2436
+ info("No active impersonation.");
2437
+ }
2438
+ return;
2439
+ }
2440
+ if (json) {
2441
+ jsonOutput({ ok: true, active: manifestToJson(manifest) });
2442
+ return;
2443
+ }
2444
+ printManifestText(manifest);
2445
+ }
2446
+ async function impersonateExitCommand(opts = {}) {
2234
2447
  requireImpersonateEnabled();
2235
2448
  const json = isJsonMode();
2236
- const manifest = readActiveManifest();
2449
+ if (opts.all) {
2450
+ const manifests = listActiveManifests();
2451
+ if (manifests.length === 0) {
2452
+ if (json) jsonOutput({ ok: true, was_active: false, exited: [] });
2453
+ else info("No active impersonation to exit.");
2454
+ return;
2455
+ }
2456
+ const exited = [];
2457
+ for (const manifest2 of manifests) {
2458
+ await performImpersonateExit(manifest2, { json, silent: true });
2459
+ exited.push(manifest2.code_name);
2460
+ }
2461
+ if (json) {
2462
+ jsonOutput({ ok: true, was_active: true, exited });
2463
+ } else {
2464
+ success(`Exited ${exited.length} impersonation session(s): ${exited.join(", ")}`);
2465
+ info("Restart Claude Code to pick up your original persona.");
2466
+ }
2467
+ return;
2468
+ }
2469
+ const manifest = readActiveManifest(process.cwd());
2237
2470
  if (!manifest) {
2238
2471
  if (json) jsonOutput({ ok: true, was_active: false });
2239
2472
  else info("No active impersonation to exit.");
2240
2473
  return;
2241
2474
  }
2475
+ await performImpersonateExit(manifest, { json, silent: false });
2476
+ }
2477
+ async function performImpersonateExit(manifest, opts) {
2478
+ const { json, silent } = opts;
2242
2479
  let stopOk = true;
2243
2480
  let stopDetail = null;
2244
2481
  try {
@@ -2295,7 +2532,8 @@ async function impersonateExitCommand() {
2295
2532
  if (!json) error(`Failed to remove introduce hook: ${msg}`);
2296
2533
  }
2297
2534
  }
2298
- clearActiveManifest();
2535
+ clearActiveManifest(manifest.project_cwd);
2536
+ if (silent) return;
2299
2537
  if (json) {
2300
2538
  jsonOutput({
2301
2539
  ok: true,
@@ -2316,9 +2554,32 @@ async function impersonateExitCommand() {
2316
2554
  }
2317
2555
  info("Restart Claude Code to pick up your original persona.");
2318
2556
  }
2557
+ async function autoExitExpiredImpersonation() {
2558
+ let manifests;
2559
+ try {
2560
+ manifests = listActiveManifests();
2561
+ } catch {
2562
+ return;
2563
+ }
2564
+ const expired = manifests.filter((m) => isExpired(m));
2565
+ if (expired.length === 0) return;
2566
+ for (const manifest of expired) {
2567
+ try {
2568
+ if (!isJsonMode()) {
2569
+ console.error(
2570
+ chalk10.cyan(
2571
+ `\u2139 Impersonation session for ${manifest.code_name} expired \u2014 auto-exiting and restoring your files.`
2572
+ )
2573
+ );
2574
+ }
2575
+ await performImpersonateExit(manifest, { json: isJsonMode(), silent: true });
2576
+ } catch {
2577
+ }
2578
+ }
2579
+ }
2319
2580
  async function impersonateIntroduceCommand() {
2320
2581
  try {
2321
- const manifest = readActiveManifest();
2582
+ const manifest = readActiveManifest(process.cwd());
2322
2583
  if (!manifest || isExpired(manifest)) return;
2323
2584
  const cwd = manifest.project_cwd;
2324
2585
  const identity = readClaudeMdIdentity(join10(cwd, "CLAUDE.md"));
@@ -2347,6 +2608,43 @@ function readMcpIntegrations(path) {
2347
2608
  return [];
2348
2609
  }
2349
2610
  }
2611
+ function resolveCliMcpBundleDir() {
2612
+ const moduleDir = dirname3(fileURLToPath2(import.meta.url));
2613
+ const candidates = [
2614
+ join10(moduleDir, "mcp"),
2615
+ join10(moduleDir, "..", "mcp"),
2616
+ join10(moduleDir, "..", "..", "mcp")
2617
+ ];
2618
+ for (const candidate of candidates) {
2619
+ if (existsSync6(join10(candidate, "index.js"))) return candidate;
2620
+ }
2621
+ return candidates[candidates.length - 1];
2622
+ }
2623
+ function resolveOperatorPath(execPath = process.execPath, inheritedPath = process.env.PATH ?? "") {
2624
+ const nodeBinDir = dirname3(execPath);
2625
+ const segments = inheritedPath.split(delimiter).filter((s) => s.length > 0);
2626
+ if (!segments.includes(nodeBinDir)) {
2627
+ segments.unshift(nodeBinDir);
2628
+ }
2629
+ return segments.join(delimiter);
2630
+ }
2631
+ function buildImpersonateClaudeLaunch(projectCwd, inheritEnv = process.env, agentId = null) {
2632
+ const env = {
2633
+ ...inheritEnv,
2634
+ ENABLE_CLAUDEAI_MCP_SERVERS: "false"
2635
+ };
2636
+ if (agentId !== null && agentId.length > 0) {
2637
+ env.AGT_ACT_AS_AGENT_ID = agentId;
2638
+ }
2639
+ return {
2640
+ args: [
2641
+ "--strict-mcp-config",
2642
+ "--mcp-config",
2643
+ join10(projectCwd, ".mcp.json")
2644
+ ],
2645
+ env
2646
+ };
2647
+ }
2350
2648
 
2351
2649
  // src/commands/drift.ts
2352
2650
  import chalk11 from "chalk";
@@ -2354,13 +2652,13 @@ import ora11 from "ora";
2354
2652
 
2355
2653
  // ../../packages/core/dist/drift/live-state-reader.js
2356
2654
  import { readFile } from "fs/promises";
2357
- import { createHash } from "crypto";
2655
+ import { createHash as createHash2 } from "crypto";
2358
2656
  import { join as join11 } from "path";
2359
2657
  import JSON5 from "json5";
2360
2658
  async function hashFile(filePath) {
2361
2659
  try {
2362
2660
  const content = await readFile(filePath);
2363
- return createHash("sha256").update(content).digest("hex");
2661
+ return createHash2("sha256").update(content).digest("hex");
2364
2662
  } catch {
2365
2663
  return null;
2366
2664
  }
@@ -2802,6 +3100,97 @@ async function hostRotateKeyCommand(hostName) {
2802
3100
  process.exitCode = 1;
2803
3101
  }
2804
3102
  }
3103
+ async function hostMaintenanceWindowCommand(hostName, opts) {
3104
+ const teamSlug = requireTeam();
3105
+ if (!teamSlug) return;
3106
+ const json = isJsonMode();
3107
+ const path = `/hosts/${encodeURIComponent(hostName)}/maintenance-window`;
3108
+ const setting = opts.clear || opts.start !== void 0 || opts.end !== void 0 || opts.tz !== void 0;
3109
+ if (!setting) {
3110
+ const spinner2 = ora12({ text: "Fetching maintenance window\u2026", isSilent: json });
3111
+ spinner2.start();
3112
+ try {
3113
+ const data = await api.get(path);
3114
+ spinner2.stop();
3115
+ if (json) {
3116
+ jsonOutput({ ok: true, ...data });
3117
+ return;
3118
+ }
3119
+ const ov = data.override;
3120
+ const hasOverride = ov.maintenance_window_start !== null || ov.maintenance_window_end !== null || ov.maintenance_timezone !== null;
3121
+ info(`Host: ${chalk12.bold(hostName)}`);
3122
+ info(
3123
+ `Effective: ${chalk12.green(`${data.effective.start}\u2013${data.effective.end}`)} ${data.effective.timezone}`
3124
+ );
3125
+ info(
3126
+ `Override: ${hasOverride ? chalk12.cyan("set") : chalk12.dim("none (default 01:00\u201302:00)")}`
3127
+ );
3128
+ info("Set with --start/--end/--tz, or --clear to follow the default.");
3129
+ } catch (err) {
3130
+ spinner2.fail("Failed to fetch maintenance window.");
3131
+ if (json) jsonOutput({ ok: false, error: err.message });
3132
+ else error(err.message);
3133
+ process.exitCode = 1;
3134
+ }
3135
+ return;
3136
+ }
3137
+ if (opts.clear && (opts.start !== void 0 || opts.end !== void 0 || opts.tz !== void 0)) {
3138
+ const msg = "Use --clear on its own \u2014 it cannot be combined with --start/--end/--tz.";
3139
+ if (json) jsonOutput({ ok: false, error: msg });
3140
+ else error(msg);
3141
+ process.exitCode = 1;
3142
+ return;
3143
+ }
3144
+ if (!opts.clear && opts.start === void 0 !== (opts.end === void 0)) {
3145
+ const msg = "Provide both --start and --end (or use --clear).";
3146
+ if (json) jsonOutput({ ok: false, error: msg });
3147
+ else error(msg);
3148
+ process.exitCode = 1;
3149
+ return;
3150
+ }
3151
+ if (!opts.clear && opts.tz !== void 0 && opts.start === void 0) {
3152
+ const msg = "Provide --start and --end when setting --tz (or use --clear).";
3153
+ if (json) jsonOutput({ ok: false, error: msg });
3154
+ else error(msg);
3155
+ process.exitCode = 1;
3156
+ return;
3157
+ }
3158
+ let resolvedTz = opts.tz ?? null;
3159
+ if (!opts.clear && opts.tz === void 0) {
3160
+ try {
3161
+ const cur = await api.get(path);
3162
+ resolvedTz = cur.override.maintenance_timezone;
3163
+ } catch (err) {
3164
+ const spinner0 = ora12({ isSilent: json });
3165
+ spinner0.fail("Failed to read current maintenance window.");
3166
+ if (json) jsonOutput({ ok: false, error: err.message });
3167
+ else error(err.message);
3168
+ process.exitCode = 1;
3169
+ return;
3170
+ }
3171
+ }
3172
+ const payload = opts.clear ? { maintenance_window_start: null, maintenance_window_end: null, maintenance_timezone: null } : {
3173
+ maintenance_window_start: opts.start ?? null,
3174
+ maintenance_window_end: opts.end ?? null,
3175
+ maintenance_timezone: resolvedTz
3176
+ };
3177
+ const spinner = ora12({ text: "Updating maintenance window\u2026", isSilent: json });
3178
+ spinner.start();
3179
+ try {
3180
+ const data = await api.post(path, payload);
3181
+ spinner.succeed(`Maintenance window updated for ${chalk12.bold(hostName)}.`);
3182
+ if (json) {
3183
+ jsonOutput({ ok: true, ...data });
3184
+ return;
3185
+ }
3186
+ if (data.note) info(data.note);
3187
+ } catch (err) {
3188
+ spinner.fail("Failed to update maintenance window.");
3189
+ if (json) jsonOutput({ ok: false, error: err.message });
3190
+ else error(err.message);
3191
+ process.exitCode = 1;
3192
+ }
3193
+ }
2805
3194
  async function hostDecommissionCommand(hostName) {
2806
3195
  const teamSlug = requireTeam();
2807
3196
  if (!teamSlug) return;
@@ -2989,7 +3378,7 @@ function terminate(child) {
2989
3378
  // src/commands/manager-watch.tsx
2990
3379
  import { useEffect, useState, useMemo } from "react";
2991
3380
  import { render, Box, Text, useApp, useInput } from "ink";
2992
- import { existsSync as existsSync6, readFileSync as readFileSync6, statSync, openSync, readSync, closeSync } from "fs";
3381
+ import { existsSync as existsSync7, readFileSync as readFileSync6, statSync, openSync, readSync, closeSync } from "fs";
2993
3382
  import { homedir as homedir4 } from "os";
2994
3383
  import { join as join12 } from "path";
2995
3384
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -3021,7 +3410,7 @@ function managerWatchCommand(opts = {}) {
3021
3410
  }
3022
3411
  function readState(stateFile) {
3023
3412
  try {
3024
- if (!existsSync6(stateFile)) return null;
3413
+ if (!existsSync7(stateFile)) return null;
3025
3414
  const raw = readFileSync6(stateFile, "utf-8");
3026
3415
  return JSON.parse(raw);
3027
3416
  } catch {
@@ -3029,7 +3418,7 @@ function readState(stateFile) {
3029
3418
  }
3030
3419
  }
3031
3420
  function tailLogFile(logFile, lines) {
3032
- if (!existsSync6(logFile)) return [];
3421
+ if (!existsSync7(logFile)) return [];
3033
3422
  try {
3034
3423
  const fileSize = statSync(logFile).size;
3035
3424
  if (fileSize === 0) return [];
@@ -3270,7 +3659,7 @@ function truncate(s, max) {
3270
3659
  return s.length > max ? `${s.slice(0, Math.max(0, max - 1))}\u2026` : s;
3271
3660
  }
3272
3661
  function streamLogFile(logFile) {
3273
- if (!existsSync6(logFile)) {
3662
+ if (!existsSync7(logFile)) {
3274
3663
  process.stderr.write(`No manager log found at ${logFile}.
3275
3664
  Start the manager first: agt manager start
3276
3665
  `);
@@ -3316,14 +3705,14 @@ Start the manager first: agt manager start
3316
3705
  // src/commands/agent.ts
3317
3706
  import chalk14 from "chalk";
3318
3707
  import JSON52 from "json5";
3319
- import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
3708
+ import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
3320
3709
  import { join as join13 } from "path";
3321
3710
  async function agentShowCommand(codeName, opts) {
3322
3711
  const json = isJsonMode();
3323
3712
  const unifiedDir = join13(opts.configDir, codeName, "provision");
3324
3713
  const legacyDir = join13(opts.configDir, codeName, "claudecode", "provision");
3325
- const agentDir = existsSync7(unifiedDir) ? unifiedDir : legacyDir;
3326
- const hasLocalConfig = existsSync7(agentDir);
3714
+ const agentDir = existsSync8(unifiedDir) ? unifiedDir : legacyDir;
3715
+ const hasLocalConfig = existsSync8(agentDir);
3327
3716
  let apiChannels = null;
3328
3717
  let apiAgent = null;
3329
3718
  if (getApiKey()) {
@@ -3357,7 +3746,7 @@ async function agentShowCommand(codeName, opts) {
3357
3746
  let agentState = null;
3358
3747
  if (hasLocalConfig) {
3359
3748
  const charterPath = join13(agentDir, "CHARTER.md");
3360
- if (existsSync7(charterPath)) {
3749
+ if (existsSync8(charterPath)) {
3361
3750
  const raw = readFileSync7(charterPath, "utf-8");
3362
3751
  const parsed = extractFrontmatter(raw);
3363
3752
  if (parsed.frontmatter) {
@@ -3365,7 +3754,7 @@ async function agentShowCommand(codeName, opts) {
3365
3754
  }
3366
3755
  }
3367
3756
  const toolsPath = join13(agentDir, "TOOLS.md");
3368
- if (existsSync7(toolsPath)) {
3757
+ if (existsSync8(toolsPath)) {
3369
3758
  const raw = readFileSync7(toolsPath, "utf-8");
3370
3759
  const parsed = extractFrontmatter(raw);
3371
3760
  if (parsed.frontmatter) {
@@ -3373,7 +3762,7 @@ async function agentShowCommand(codeName, opts) {
3373
3762
  }
3374
3763
  }
3375
3764
  const openclawPath = join13(agentDir, "openclaw.json5");
3376
- if (existsSync7(openclawPath)) {
3765
+ if (existsSync8(openclawPath)) {
3377
3766
  try {
3378
3767
  const raw = readFileSync7(openclawPath, "utf-8");
3379
3768
  openclawConfig = JSON52.parse(raw);
@@ -3381,7 +3770,7 @@ async function agentShowCommand(codeName, opts) {
3381
3770
  }
3382
3771
  }
3383
3772
  const statePath = join13(opts.configDir, "manager-state.json");
3384
- if (existsSync7(statePath)) {
3773
+ if (existsSync8(statePath)) {
3385
3774
  try {
3386
3775
  const raw = readFileSync7(statePath, "utf-8");
3387
3776
  const state = JSON.parse(raw);
@@ -3720,8 +4109,8 @@ async function kanbanRecurringDisableCommand(titleOrId, opts) {
3720
4109
  }
3721
4110
 
3722
4111
  // src/commands/setup.ts
3723
- import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, accessSync, constants as fsConstants } from "fs";
3724
- import { join as join14, dirname as dirname3 } from "path";
4112
+ import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, accessSync, constants as fsConstants } from "fs";
4113
+ import { join as join14, dirname as dirname4 } from "path";
3725
4114
  import { homedir as homedir5 } from "os";
3726
4115
  import chalk16 from "chalk";
3727
4116
  import ora14 from "ora";
@@ -3736,7 +4125,7 @@ function detectShellProfile() {
3736
4125
  return fishConfig;
3737
4126
  }
3738
4127
  const bashrc = join14(home, ".bashrc");
3739
- if (existsSync8(bashrc)) return bashrc;
4128
+ if (existsSync9(bashrc)) return bashrc;
3740
4129
  return join14(home, ".bash_profile");
3741
4130
  }
3742
4131
  function maybeWriteSystemWideEnv(apiUrl, apiKey, consoleUrl) {
@@ -3763,7 +4152,7 @@ function quoteForFishShell(value) {
3763
4152
  }
3764
4153
  function writeEtcEnvironment(apiUrl, apiKey, consoleUrl) {
3765
4154
  const envPath = "/etc/environment";
3766
- if (!existsSync8(envPath)) return false;
4155
+ if (!existsSync9(envPath)) return false;
3767
4156
  try {
3768
4157
  accessSync(envPath, fsConstants.W_OK);
3769
4158
  } catch {
@@ -3789,7 +4178,7 @@ function writeEtcEnvironment(apiUrl, apiKey, consoleUrl) {
3789
4178
  }
3790
4179
  function writeProfileDScript(apiUrl, apiKey, consoleUrl) {
3791
4180
  const profileD = "/etc/profile.d";
3792
- if (!existsSync8(profileD)) return false;
4181
+ if (!existsSync9(profileD)) return false;
3793
4182
  try {
3794
4183
  accessSync(profileD, fsConstants.W_OK);
3795
4184
  } catch {
@@ -3812,7 +4201,7 @@ function writeProfileDScript(apiUrl, apiKey, consoleUrl) {
3812
4201
  }
3813
4202
  function ensureBashrcSourcesProfileD() {
3814
4203
  const bashrc = "/etc/bashrc";
3815
- if (!existsSync8(bashrc)) return false;
4204
+ if (!existsSync9(bashrc)) return false;
3816
4205
  try {
3817
4206
  accessSync(bashrc, fsConstants.W_OK);
3818
4207
  } catch {
@@ -3858,7 +4247,7 @@ function buildExportLines(shell, apiUrl, apiKey, consoleUrl) {
3858
4247
  lines.push("");
3859
4248
  return lines.join("\n");
3860
4249
  }
3861
- async function setupCommand(token) {
4250
+ async function setupCommand(token, options = {}) {
3862
4251
  const json = isJsonMode();
3863
4252
  const shortTokenPattern = /^[A-HJ-NP-Z2-9]{4}-[A-HJ-NP-Z2-9]{4}$/i;
3864
4253
  const legacyTokenPattern = /^prov_[a-f0-9]{64}$/i;
@@ -3873,11 +4262,24 @@ async function setupCommand(token) {
3873
4262
  return;
3874
4263
  }
3875
4264
  const normalizedToken = shortTokenPattern.test(token) ? token.toUpperCase() : token.toLowerCase();
3876
- let apiUrl = process.env["AGT_HOST"];
4265
+ const apiHostFlag = options.apiHost?.trim();
4266
+ const envHost = process.env["AGT_HOST"]?.trim();
4267
+ const apiUrl = apiHostFlag || envHost;
3877
4268
  if (!apiUrl) {
3878
- apiUrl = "https://api.augmented.team";
3879
- if (!json) {
3880
- info(`No AGT_HOST set \u2014 using default: ${chalk16.bold(apiUrl)}`);
4269
+ const msg = "Cannot determine API host for token exchange. Pass --api-host <url> or set AGT_HOST before running `agt setup`.\n Production: --api-host https://api.augmented.team\n Non-prod stage: --api-host https://<stage>.api.staging.augmented.team\n Local development: --api-host http://api.agt.localhost:1355";
4270
+ if (json) {
4271
+ jsonOutput({ ok: false, error: msg });
4272
+ } else {
4273
+ error(msg);
4274
+ }
4275
+ process.exitCode = 1;
4276
+ return;
4277
+ }
4278
+ if (!json) {
4279
+ if (apiHostFlag) {
4280
+ info(`Exchanging token against ${chalk16.bold(apiUrl)} (from --api-host)`);
4281
+ } else {
4282
+ info(`Exchanging token against ${chalk16.bold(apiUrl)} (from AGT_HOST)`);
3881
4283
  }
3882
4284
  }
3883
4285
  const spinner = ora14({ text: "Exchanging provisioning token\u2026", isSilent: json });
@@ -3945,12 +4347,12 @@ async function setupCommand(token) {
3945
4347
  );
3946
4348
  }
3947
4349
  const exportLines = buildExportLines(shell, finalApiUrl, setupResult.api_key, consoleUrl);
3948
- const profileDir = dirname3(profilePath);
3949
- if (!existsSync8(profileDir)) {
4350
+ const profileDir = dirname4(profilePath);
4351
+ if (!existsSync9(profileDir)) {
3950
4352
  mkdirSync7(profileDir, { recursive: true });
3951
4353
  }
3952
4354
  const marker = "# Augmented (agt) host configuration";
3953
- const current = existsSync8(profilePath) ? readFileSync8(profilePath, "utf-8") : "";
4355
+ const current = existsSync9(profilePath) ? readFileSync8(profilePath, "utf-8") : "";
3954
4356
  let updated;
3955
4357
  if (current.includes(marker)) {
3956
4358
  updated = current.replace(
@@ -4529,10 +4931,10 @@ async function acpxCloseCommand(agent2, _opts, cmd) {
4529
4931
 
4530
4932
  // src/commands/update.ts
4531
4933
  import { execFileSync, execSync } from "child_process";
4532
- import { existsSync as existsSync9, realpathSync as realpathSync2 } from "fs";
4934
+ import { existsSync as existsSync10, realpathSync as realpathSync2 } from "fs";
4533
4935
  import chalk18 from "chalk";
4534
4936
  import ora16 from "ora";
4535
- var cliVersion = true ? "0.27.8" : "dev";
4937
+ var cliVersion = true ? "0.27.9-test.11" : "dev";
4536
4938
  async function fetchLatestVersion() {
4537
4939
  const host2 = getHost();
4538
4940
  if (!host2) return null;
@@ -4636,7 +5038,7 @@ function performUpdate(version) {
4636
5038
  function detectBrewOwner() {
4637
5039
  for (const prefix of ["/home/linuxbrew/.linuxbrew", "/opt/homebrew", "/usr/local"]) {
4638
5040
  const cellar = `${prefix}/Cellar`;
4639
- if (!existsSync9(cellar)) continue;
5041
+ if (!existsSync10(cellar)) continue;
4640
5042
  try {
4641
5043
  return execSync(`stat -c %U "${cellar}" 2>/dev/null || stat -f %Su "${cellar}"`, { encoding: "utf-8" }).trim() || null;
4642
5044
  } catch {
@@ -4763,6 +5165,397 @@ async function checkForUpdateOnStartup() {
4763
5165
  }
4764
5166
  }
4765
5167
 
5168
+ // src/commands/audit-subagents.ts
5169
+ import { homedir as homedir6 } from "os";
5170
+ import { join as join16 } from "path";
5171
+
5172
+ // src/lib/subagent-mcp-audit.ts
5173
+ import { readdirSync as readdirSync4, readFileSync as readFileSync9, statSync as statSync2 } from "fs";
5174
+ import { join as join15 } from "path";
5175
+ function parseFrontmatter(content) {
5176
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
5177
+ if (!match) return null;
5178
+ const out = {};
5179
+ const lines = match[1].split(/\r?\n/);
5180
+ for (let i = 0; i < lines.length; i++) {
5181
+ const line = lines[i];
5182
+ const m = line.match(/^(name|tools|background):\s*(.*)$/);
5183
+ if (!m) continue;
5184
+ const [, key, rawValue] = m;
5185
+ const trimmed = rawValue.trim();
5186
+ if (key === "tools" && trimmed === "") {
5187
+ const items = [];
5188
+ let j = i + 1;
5189
+ while (j < lines.length) {
5190
+ const nextRaw = lines[j];
5191
+ const itemMatch = nextRaw.match(/^(\s+)-\s*(.+?)\s*$/);
5192
+ if (!itemMatch) break;
5193
+ items.push(itemMatch[2].replace(/^['"](.*)['"]$/, "$1"));
5194
+ j++;
5195
+ }
5196
+ if (items.length > 0) {
5197
+ out.tools = items.join(", ");
5198
+ i = j - 1;
5199
+ continue;
5200
+ }
5201
+ }
5202
+ out[key] = trimmed;
5203
+ }
5204
+ return out;
5205
+ }
5206
+ function toolsExcludeMcp(tools) {
5207
+ if (tools.length === 0) return false;
5208
+ if (/\bmcp__/.test(tools)) return false;
5209
+ if (/\*/.test(tools)) return false;
5210
+ if (/\bAll tools\b/i.test(tools)) return false;
5211
+ return true;
5212
+ }
5213
+ function findSubagentFiles(dir, depth = 0) {
5214
+ if (depth > 6) return [];
5215
+ let entries;
5216
+ try {
5217
+ entries = readdirSync4(dir);
5218
+ } catch {
5219
+ return [];
5220
+ }
5221
+ const out = [];
5222
+ const isAgentsDir = dir.endsWith("/agents") || dir.endsWith("\\agents");
5223
+ for (const entry of entries) {
5224
+ const full = join15(dir, entry);
5225
+ let s;
5226
+ try {
5227
+ s = statSync2(full);
5228
+ } catch {
5229
+ continue;
5230
+ }
5231
+ if (s.isDirectory()) {
5232
+ out.push(...findSubagentFiles(full, depth + 1));
5233
+ } else if (s.isFile() && isAgentsDir && entry.endsWith(".md")) {
5234
+ out.push(full);
5235
+ }
5236
+ }
5237
+ return out;
5238
+ }
5239
+ function auditSubagentMcpBindings(rootDirs) {
5240
+ const findings = [];
5241
+ const seen = /* @__PURE__ */ new Set();
5242
+ for (const root of rootDirs) {
5243
+ for (const file of findSubagentFiles(root)) {
5244
+ if (seen.has(file)) continue;
5245
+ seen.add(file);
5246
+ let content;
5247
+ try {
5248
+ content = readFileSync9(file, "utf-8");
5249
+ } catch {
5250
+ continue;
5251
+ }
5252
+ const fm = parseFrontmatter(content);
5253
+ if (!fm || typeof fm.tools !== "string") continue;
5254
+ if (!toolsExcludeMcp(fm.tools)) continue;
5255
+ findings.push({
5256
+ file,
5257
+ name: fm.name ?? "<unknown>",
5258
+ tools: fm.tools,
5259
+ reason: "restrictive-tools-no-mcp"
5260
+ });
5261
+ }
5262
+ }
5263
+ return findings;
5264
+ }
5265
+ function formatFinding(f) {
5266
+ return `[manager-worker] [subagent-mcp-audit] file=${f.file} name=${f.name} reason=${f.reason} tools=${JSON.stringify(f.tools)}`;
5267
+ }
5268
+
5269
+ // src/commands/audit-subagents.ts
5270
+ function defaultRoots() {
5271
+ const home = homedir6();
5272
+ return [
5273
+ join16(home, ".claude", "agents"),
5274
+ join16(home, ".claude", "plugins"),
5275
+ join16(process.cwd(), ".claude", "agents")
5276
+ ];
5277
+ }
5278
+ async function auditSubagentsCommand(options = {}) {
5279
+ const roots = defaultRoots();
5280
+ const findings = auditSubagentMcpBindings(roots);
5281
+ if (options.json) {
5282
+ process.stdout.write(`${JSON.stringify({ findings, roots }, null, 2)}
5283
+ `);
5284
+ } else if (findings.length === 0) {
5285
+ process.stdout.write("No restrictive sub-agents found \u2014 every sub-agent under the scanned roots either inherits the parent toolset or explicitly includes mcp__* wildcards.\n");
5286
+ } else {
5287
+ process.stdout.write(`Found ${findings.length} sub-agent${findings.length === 1 ? "" : "s"} whose tools allowlist excludes mcp__*:
5288
+
5289
+ `);
5290
+ for (const f of findings) {
5291
+ process.stdout.write(` \u2022 ${f.name} (${f.file})
5292
+ `);
5293
+ process.stdout.write(` tools: ${f.tools}
5294
+ `);
5295
+ }
5296
+ process.stdout.write(
5297
+ "\nThese sub-agents will return \"No such tool available.\" for every mcp__* call when dispatched from a managed agent. If the parent needs to do MCP-tool work, dispatch the Augmented plugin's `augmented-worker` sub-agent instead \u2014 it omits `tools:` so it inherits the parent's full toolset including every MCP server. See ENG-5897.\n"
5298
+ );
5299
+ for (const f of findings) {
5300
+ process.stderr.write(`${formatFinding(f)}
5301
+ `);
5302
+ }
5303
+ }
5304
+ if (options.strict && findings.length > 0) {
5305
+ process.exit(2);
5306
+ }
5307
+ }
5308
+
5309
+ // src/lib/mcp-render-allowlist-audit.ts
5310
+ import { readdirSync as readdirSync5, readFileSync as readFileSync10, statSync as statSync3 } from "fs";
5311
+ import { homedir as homedir7 } from "os";
5312
+ import { join as join17 } from "path";
5313
+ function expectedWildcard(serverKey) {
5314
+ return `mcp__${serverKey.replace(/-/g, "_")}__*`;
5315
+ }
5316
+ function parseMcpWildcardsFromToolsLine(content) {
5317
+ const toolsLine = content.split("\n").find((l) => l.startsWith("tools:"));
5318
+ if (!toolsLine) return null;
5319
+ const value = toolsLine.replace(/^tools:\s*/, "").trim();
5320
+ if (value.length === 0) return [];
5321
+ return value.split(/\s*,\s*/).map((t) => t.trim()).filter((t) => t.startsWith("mcp__"));
5322
+ }
5323
+ function readMcpServerKeys(mcpJsonPath) {
5324
+ try {
5325
+ const config = JSON.parse(readFileSync10(mcpJsonPath, "utf-8"));
5326
+ return Object.keys(config.mcpServers ?? {});
5327
+ } catch {
5328
+ return null;
5329
+ }
5330
+ }
5331
+ function auditMcpRenderAllowlist(rootDir = join17(homedir7(), ".augmented")) {
5332
+ const findings = [];
5333
+ let entries;
5334
+ try {
5335
+ entries = readdirSync5(rootDir);
5336
+ } catch {
5337
+ return findings;
5338
+ }
5339
+ for (const entry of entries) {
5340
+ const agentDir = join17(rootDir, entry);
5341
+ let s;
5342
+ try {
5343
+ s = statSync3(agentDir);
5344
+ } catch {
5345
+ continue;
5346
+ }
5347
+ if (!s.isDirectory() || entry.startsWith("_") || entry.startsWith(".")) continue;
5348
+ for (const scope of ["provision", "project"]) {
5349
+ const mcpJsonPath = scope === "provision" ? join17(agentDir, "provision", ".mcp.json") : join17(agentDir, "project", ".mcp.json");
5350
+ const keys = readMcpServerKeys(mcpJsonPath);
5351
+ if (keys === null || keys.length === 0) continue;
5352
+ const expected = new Set(keys.map(expectedWildcard));
5353
+ for (const subagent of ["channel-message-handler", "augmented-worker"]) {
5354
+ const mdPath = scope === "provision" ? join17(agentDir, ".claude", "agents", `${subagent}.md`) : join17(agentDir, "project", ".claude", "agents", `${subagent}.md`);
5355
+ let content;
5356
+ try {
5357
+ content = readFileSync10(mdPath, "utf-8");
5358
+ } catch {
5359
+ continue;
5360
+ }
5361
+ const declared = parseMcpWildcardsFromToolsLine(content);
5362
+ if (declared === null) continue;
5363
+ const declaredSet = new Set(declared);
5364
+ const missing = [...expected].filter((w) => !declaredSet.has(w));
5365
+ if (missing.length > 0) {
5366
+ findings.push({ agent: entry, subagent, scope, missing, file: mdPath });
5367
+ }
5368
+ }
5369
+ }
5370
+ }
5371
+ return findings;
5372
+ }
5373
+ function formatRenderDriftFinding(f) {
5374
+ return `[manager-worker] [mcp-render-drift] agent=${f.agent} subagent=${f.subagent} scope=${f.scope} missing=${JSON.stringify(f.missing)} file=${f.file}`;
5375
+ }
5376
+
5377
+ // src/commands/audit-mcp-render.ts
5378
+ async function auditMcpRenderCommand(options = {}) {
5379
+ const findings = auditMcpRenderAllowlist();
5380
+ if (options.json) {
5381
+ process.stdout.write(`${JSON.stringify({ findings }, null, 2)}
5382
+ `);
5383
+ } else if (findings.length === 0) {
5384
+ process.stdout.write(
5385
+ "No render drift found. Every managed agent's sub-agent .md `tools:` allowlist covers every server in its `.mcp.json`.\n"
5386
+ );
5387
+ } else {
5388
+ process.stdout.write(
5389
+ `Found ${findings.length} sub-agent .md file${findings.length === 1 ? "" : "s"} with a stale tools allowlist:
5390
+
5391
+ `
5392
+ );
5393
+ for (const f of findings) {
5394
+ process.stdout.write(` \u2022 ${f.agent}/${f.scope}/${f.subagent}.md
5395
+ `);
5396
+ process.stdout.write(` missing: ${f.missing.join(", ")}
5397
+ `);
5398
+ process.stdout.write(` file: ${f.file}
5399
+ `);
5400
+ }
5401
+ process.stdout.write(
5402
+ '\nA missing wildcard means the sub-agent will get "No such tool available." on any call to that MCP server. The fix is to force a re-render (e.g. by adding/removing any MCP server, which routes through syncMcpToProject), OR restart the manager so the next poll picks up the live `.mcp.json` state. The root-cause write path that bypassed the render chokepoint is tracked in ENG-5922.\n'
5403
+ );
5404
+ for (const f of findings) {
5405
+ process.stderr.write(`${formatRenderDriftFinding(f)}
5406
+ `);
5407
+ }
5408
+ }
5409
+ if (options.strict && findings.length > 0) {
5410
+ process.exit(2);
5411
+ }
5412
+ }
5413
+
5414
+ // src/commands/audit-secrets.ts
5415
+ import { homedir as homedir8 } from "os";
5416
+ import { join as join19 } from "path";
5417
+
5418
+ // src/lib/secret-leak-audit.ts
5419
+ import { readdirSync as readdirSync6, readFileSync as readFileSync11, statSync as statSync4 } from "fs";
5420
+ import { join as join18 } from "path";
5421
+ var SECRET_KEY_NAME_RE = /TOKEN|KEY|SECRET|BEARER|PASSWORD/i;
5422
+ function isTemplated(value) {
5423
+ return value.includes("${");
5424
+ }
5425
+ function matchesKnownSecretShape(value) {
5426
+ return LITERAL_SECRET_PATTERNS.some(({ re }) => re.test(value));
5427
+ }
5428
+ function isWorldReadable(mode) {
5429
+ return (mode & 63) !== 0;
5430
+ }
5431
+ function octal(mode) {
5432
+ return `0${(mode & 511).toString(8)}`;
5433
+ }
5434
+ function scanMcpFile(file, agent2, findings) {
5435
+ let mode = null;
5436
+ try {
5437
+ mode = statSync4(file).mode;
5438
+ } catch {
5439
+ return;
5440
+ }
5441
+ if (mode !== null && isWorldReadable(mode)) {
5442
+ findings.push({ kind: "world-readable-secret-file", file, agent: agent2, mode: octal(mode) });
5443
+ }
5444
+ let parsed;
5445
+ try {
5446
+ parsed = JSON.parse(readFileSync11(file, "utf-8"));
5447
+ } catch {
5448
+ return;
5449
+ }
5450
+ const servers = parsed?.mcpServers;
5451
+ if (typeof servers !== "object" || servers === null) return;
5452
+ for (const [server, raw] of Object.entries(servers)) {
5453
+ if (typeof raw !== "object" || raw === null) continue;
5454
+ const entry = raw;
5455
+ const blocks = [
5456
+ [entry.env, "env"],
5457
+ [entry.headers, "header"]
5458
+ ];
5459
+ for (const [block, location] of blocks) {
5460
+ if (!block) continue;
5461
+ for (const [field, value] of Object.entries(block)) {
5462
+ if (typeof value !== "string" || value.length === 0) continue;
5463
+ if (isTemplated(value)) continue;
5464
+ if (!SECRET_KEY_NAME_RE.test(field) && !matchesKnownSecretShape(value)) continue;
5465
+ findings.push({ kind: "literal-secret-in-mcp", file, agent: agent2, server, field, location });
5466
+ }
5467
+ }
5468
+ }
5469
+ }
5470
+ function scanEnvIntegrations(file, agent2, findings) {
5471
+ let mode;
5472
+ try {
5473
+ mode = statSync4(file).mode;
5474
+ } catch {
5475
+ return;
5476
+ }
5477
+ if (isWorldReadable(mode)) {
5478
+ findings.push({ kind: "world-readable-secret-file", file, agent: agent2, mode: octal(mode) });
5479
+ }
5480
+ }
5481
+ function auditSecretLeaks(augmentedRoot) {
5482
+ const findings = [];
5483
+ let agents;
5484
+ try {
5485
+ agents = readdirSync6(augmentedRoot);
5486
+ } catch {
5487
+ return findings;
5488
+ }
5489
+ for (const agent2 of agents) {
5490
+ const agentDir = join18(augmentedRoot, agent2);
5491
+ try {
5492
+ if (!statSync4(agentDir).isDirectory()) continue;
5493
+ } catch {
5494
+ continue;
5495
+ }
5496
+ scanMcpFile(join18(agentDir, "provision", ".mcp.json"), agent2, findings);
5497
+ scanMcpFile(join18(agentDir, "project", ".mcp.json"), agent2, findings);
5498
+ scanEnvIntegrations(join18(agentDir, ".env.integrations"), agent2, findings);
5499
+ }
5500
+ return findings;
5501
+ }
5502
+ function formatSecretFinding(f) {
5503
+ const parts = [
5504
+ "[audit-secrets]",
5505
+ `kind=${f.kind}`,
5506
+ `agent=${f.agent}`,
5507
+ `file=${f.file}`
5508
+ ];
5509
+ if (f.server) parts.push(`server=${f.server}`);
5510
+ if (f.field) parts.push(`field=${f.field}`);
5511
+ if (f.location) parts.push(`location=${f.location}`);
5512
+ if (f.mode) parts.push(`mode=${f.mode}`);
5513
+ return parts.join(" ");
5514
+ }
5515
+
5516
+ // src/commands/audit-secrets.ts
5517
+ function describe(f) {
5518
+ if (f.kind === "literal-secret-in-mcp") {
5519
+ return `literal secret in ${f.location} field '${f.field}' of server '${f.server}'`;
5520
+ }
5521
+ return `secret file is mode ${f.mode} (expected 0600)`;
5522
+ }
5523
+ async function auditSecretsCommand(options = {}) {
5524
+ const root = options.root ?? join19(homedir8(), ".augmented");
5525
+ const findings = auditSecretLeaks(root);
5526
+ if (options.json) {
5527
+ process.stdout.write(`${JSON.stringify({ findings, root }, null, 2)}
5528
+ `);
5529
+ } else if (findings.length === 0) {
5530
+ process.stdout.write(
5531
+ `No literal secrets or world-readable secret files found under ${root}.
5532
+ `
5533
+ );
5534
+ } else {
5535
+ process.stdout.write(
5536
+ `Found ${findings.length} secret-handling issue${findings.length === 1 ? "" : "s"} under ${root}:
5537
+
5538
+ `
5539
+ );
5540
+ for (const f of findings) {
5541
+ process.stdout.write(` \u2022 [${f.agent}] ${describe(f)}
5542
+ `);
5543
+ process.stdout.write(` ${f.file}
5544
+ `);
5545
+ }
5546
+ process.stdout.write(
5547
+ "\nLiteral secrets in .mcp.json should be `${VAR}` placeholders with the raw value in .env.integrations (mode 0600). See docs/operator/credential-migration-eng5898.md.\n"
5548
+ );
5549
+ for (const f of findings) {
5550
+ process.stderr.write(`${formatSecretFinding(f)}
5551
+ `);
5552
+ }
5553
+ }
5554
+ if (options.strict && findings.length > 0) {
5555
+ process.exit(2);
5556
+ }
5557
+ }
5558
+
4766
5559
  // src/commands/integration.ts
4767
5560
  import chalk19 from "chalk";
4768
5561
  import ora17 from "ora";
@@ -4770,7 +5563,7 @@ async function integrationListCommand() {
4770
5563
  const json = isJsonMode();
4771
5564
  const spinner = ora17({ text: "Fetching integrations\u2026", isSilent: json }).start();
4772
5565
  try {
4773
- const data = await api.get("/integrations/bindings");
5566
+ const data = await api.get("/integrations/installs");
4774
5567
  spinner.stop();
4775
5568
  if (json) {
4776
5569
  jsonOutput({ integrations: data });
@@ -4797,7 +5590,7 @@ async function integrationShowCommand(slug) {
4797
5590
  const json = isJsonMode();
4798
5591
  const spinner = ora17({ text: "Fetching integration\u2026", isSilent: json }).start();
4799
5592
  try {
4800
- const all = await api.get("/integrations/bindings");
5593
+ const all = await api.get("/integrations/installs");
4801
5594
  const integration2 = all.find((p) => p.slug === slug);
4802
5595
  if (!integration2) {
4803
5596
  spinner.stop();
@@ -4805,7 +5598,7 @@ async function integrationShowCommand(slug) {
4805
5598
  process.exitCode = 1;
4806
5599
  return;
4807
5600
  }
4808
- const scopes = await api.get(`/integrations/bindings/${integration2.id}/scopes`);
5601
+ const scopes = await api.get(`/integrations/installs/${integration2.id}/scopes`);
4809
5602
  spinner.stop();
4810
5603
  if (json) {
4811
5604
  jsonOutput({ integration: integration2, scopes });
@@ -4839,7 +5632,7 @@ async function integrationInstallCommand(slug, options) {
4839
5632
  const json = isJsonMode();
4840
5633
  const spinner = ora17({ text: "Installing integration\u2026", isSilent: json }).start();
4841
5634
  try {
4842
- const all = await api.get("/integrations/bindings");
5635
+ const all = await api.get("/integrations/installs");
4843
5636
  const integration2 = all.find((p) => p.slug === slug);
4844
5637
  if (!integration2) {
4845
5638
  spinner.stop();
@@ -4856,7 +5649,7 @@ async function integrationInstallCommand(slug, options) {
4856
5649
  return;
4857
5650
  }
4858
5651
  const scopes = options.scopes?.split(",").map((s) => s.trim()).filter(Boolean) ?? [];
4859
- const result = await api.post("/integrations/bindings/install", {
5652
+ const result = await api.post("/integrations/installs/install", {
4860
5653
  agent_id: agent2.agent_id,
4861
5654
  plugin_id: integration2.id,
4862
5655
  scopes
@@ -4889,7 +5682,7 @@ async function integrationUninstallCommand(slug, options) {
4889
5682
  const json = isJsonMode();
4890
5683
  const spinner = ora17({ text: "Uninstalling integration\u2026", isSilent: json }).start();
4891
5684
  try {
4892
- const all = await api.get("/integrations/bindings");
5685
+ const all = await api.get("/integrations/installs");
4893
5686
  const integration2 = all.find((p) => p.slug === slug);
4894
5687
  if (!integration2) {
4895
5688
  spinner.stop();
@@ -4905,7 +5698,7 @@ async function integrationUninstallCommand(slug, options) {
4905
5698
  process.exitCode = 1;
4906
5699
  return;
4907
5700
  }
4908
- await api.post("/integrations/bindings/uninstall", {
5701
+ await api.post("/integrations/installs/uninstall", {
4909
5702
  agent_id: agent2.agent_id,
4910
5703
  plugin_id: integration2.id
4911
5704
  });
@@ -4920,7 +5713,7 @@ async function integrationRequestsCommand() {
4920
5713
  const json = isJsonMode();
4921
5714
  const spinner = ora17({ text: "Fetching scope requests\u2026", isSilent: json }).start();
4922
5715
  try {
4923
- const requests = await api.get("/integrations/bindings/scope-requests");
5716
+ const requests = await api.get("/integrations/installs/scope-requests");
4924
5717
  spinner.stop();
4925
5718
  if (json) {
4926
5719
  jsonOutput({ requests });
@@ -4946,7 +5739,7 @@ async function integrationApproveCommand(requestId, options) {
4946
5739
  const json = isJsonMode();
4947
5740
  const spinner = ora17({ text: "Approving request\u2026", isSilent: json }).start();
4948
5741
  try {
4949
- const result = await api.put(`/integrations/bindings/scope-requests/${requestId}`, {
5742
+ const result = await api.put(`/integrations/installs/scope-requests/${requestId}`, {
4950
5743
  status: "approved",
4951
5744
  review_notes: options.notes
4952
5745
  });
@@ -4965,7 +5758,7 @@ async function integrationDenyCommand(requestId, options) {
4965
5758
  const json = isJsonMode();
4966
5759
  const spinner = ora17({ text: "Denying request\u2026", isSilent: json }).start();
4967
5760
  try {
4968
- const result = await api.put(`/integrations/bindings/scope-requests/${requestId}`, {
5761
+ const result = await api.put(`/integrations/installs/scope-requests/${requestId}`, {
4969
5762
  status: "denied",
4970
5763
  review_notes: options.reason
4971
5764
  });
@@ -5027,7 +5820,7 @@ async function integrationEnrolCommand(options) {
5027
5820
  }
5028
5821
  let displayName = options.displayName;
5029
5822
  if (!displayName) {
5030
- const defs = await api.get("/integrations/bindings");
5823
+ const defs = await api.get("/integrations/installs");
5031
5824
  const def = defs.find((d) => d.slug === options.def);
5032
5825
  displayName = def?.name ?? options.def;
5033
5826
  }
@@ -5064,17 +5857,29 @@ function handleError(err) {
5064
5857
  }
5065
5858
 
5066
5859
  // src/bin/agt.ts
5067
- var cliVersion2 = true ? "0.27.8" : "dev";
5860
+ var cliVersion2 = true ? "0.27.9-test.11" : "dev";
5068
5861
  var program = new Command();
5069
5862
  program.name("agt").description("Augmented CLI \u2014 agent provisioning and management").version(cliVersion2).option("--json", "Emit machine-readable JSON output (suppress spinners and colors)").option("--skip-update-check", "Skip the automatic update check on startup");
5070
- program.hook("preAction", (thisCommand) => {
5863
+ program.hook("preAction", async (thisCommand, actionCommand) => {
5071
5864
  const root = thisCommand.optsWithGlobals();
5072
5865
  if (root.json) {
5073
5866
  setJsonMode(true);
5074
5867
  }
5868
+ const segs = [];
5869
+ for (let c = actionCommand; c && c.parent; c = c.parent) segs.unshift(c.name());
5870
+ const cmdPath = segs.join(" ");
5871
+ if (cmdPath !== "impersonate introduce" && cmdPath !== "impersonate exit") {
5872
+ await autoExitExpiredImpersonation();
5873
+ }
5075
5874
  });
5076
5875
  program.command("whoami").description("Show the authenticated host, team, and user from AGT_API_KEY").action(whoamiCommand);
5077
- program.command("setup <token>").description("One-command host setup: exchange provisioning token, configure env vars, verify, and start manager").action(setupCommand);
5876
+ program.command("setup <token>").description("One-command host setup: exchange provisioning token, configure env vars, verify, and start manager").option(
5877
+ "--api-host <url>",
5878
+ // ENG-5831: required when AGT_HOST is not set in the shell — the setup
5879
+ // command no longer silently defaults to prod. Mirrors `agt impersonate
5880
+ // connect --api-host` (ENG-5773).
5881
+ "API host to exchange the provisioning token against. Takes precedence over AGT_HOST. Required when AGT_HOST is unset (e.g. https://test.api.staging.augmented.team for the test stage; https://api.augmented.team for prod)."
5882
+ ).action(setupCommand);
5078
5883
  var team = program.command("team").description("Manage teams");
5079
5884
  team.command("list").description("List teams you belong to").action(teamListCommand);
5080
5885
  team.command("create <name>").description("Create a new team and set it as active").action(teamCreateCommand);
@@ -5105,8 +5910,12 @@ impersonate.command("connect <token>").description(
5105
5910
  })
5106
5911
  )
5107
5912
  );
5108
- impersonate.command("current").description('Print the active impersonation (or "none")').action(impersonateCurrentCommand);
5109
- impersonate.command("exit").description("End the active impersonation, restore project files, and notify the server").action(impersonateExitCommand);
5913
+ impersonate.command("current").description('Print the active impersonation for this directory (or "none")').option("--all", "List every active impersonation session on this machine").action(
5914
+ (opts) => impersonateCurrentCommand({ all: opts.all === true })
5915
+ );
5916
+ impersonate.command("exit").description("End this directory's impersonation, restore project files, and notify the server").option("--all", "Exit every active impersonation session on this machine").action(
5917
+ (opts) => impersonateExitCommand({ all: opts.all === true })
5918
+ );
5110
5919
  impersonate.command("introduce", { hidden: true }).description("Print the impersonated agent's self-introduction (SessionStart hook target)").action(impersonateIntroduceCommand);
5111
5920
  program.command("init").description("Create a new agent (interactive wizard, or non-interactive with --name)").option("--name <display-name>", "Agent display name (triggers non-interactive mode)").option("--code-name <code-name>", "Agent code name (kebab-case, derived from --name if omitted)").option("--description <text>", "Short agent description").option("--env <environment>", "Environment: dev | stage | prod", "dev").option("--risk-tier <tier>", "Risk tier: Low | Medium | High", "Low").option("--budget-type <type>", "Budget type: tokens | dollars | both", "tokens").option("--budget-tokens <number>", "Token budget limit", "10000").option("--budget-dollars <number>", "Dollar budget limit", "10").option("--budget-window <window>", "Budget window: daily | weekly | monthly", "daily").option("--budget-enforcement <mode>", "Budget enforcement: block | throttle | alert | degrade", "block").option("--channels <list>", "Comma-separated channel IDs").option("--logging <mode>", "Logging mode: redacted | hash-only | full-local", "redacted").action(initCommand);
5112
5921
  program.command("lint").argument("[path]", "Path to agent directory (default: all agents in .augmented/)").description("Lint CHARTER.md and TOOLS.md files").action(lintCommand);
@@ -5133,6 +5942,7 @@ host.command("unassign <host-name> <agent-code-names...>").description("Unassign
5133
5942
  host.command("agents [host-name]").description("List agents assigned to a host (omit name to auto-resolve from AGT_API_KEY)").action(hostAgentsCommand);
5134
5943
  host.command("rotate-key <host-name>").description("Rotate the API key for a host (revokes the old key)").action(hostRotateKeyCommand);
5135
5944
  host.command("decommission <host-name>").description("Decommission a host (revokes key, marks inactive)").action(hostDecommissionCommand);
5945
+ host.command("maintenance-window <host-name>").description("View or set the host maintenance window for restart-causing updates").option("--start <HH:MM>", "Window start, 24h local time (e.g. 01:00)").option("--end <HH:MM>", "Window end, 24h local time (e.g. 02:00)").option("--tz <IANA>", "Window timezone (e.g. Australia/Sydney)").option("--clear", "Clear the override and follow the default 01:00\u201302:00").action(hostMaintenanceWindowCommand);
5136
5946
  host.command("pair <host-name>").description("Start an SSM port-forward + shell to re-authenticate Claude Code on an EC2 host").option("--port <port>", "Local port to forward for the OAuth callback", "54545").option("--no-shell", "Start the tunnel only; do not open an interactive shell").action(
5137
5947
  (hostName, opts) => hostPairCommand(hostName, {
5138
5948
  port: opts.port,
@@ -5140,16 +5950,16 @@ host.command("pair <host-name>").description("Start an SSM port-forward + shell
5140
5950
  })
5141
5951
  );
5142
5952
  var manager = program.command("manager").description("Host config sync daemon \u2014 keeps local agent files in sync with API");
5143
- manager.command("start").description("Start the manager daemon (polls API for config changes and detects local drift)").option("--interval <seconds>", "Poll interval in seconds (min 5)", "10").option("--config-dir <dir>", "Config directory for agent files", join15(homedir6(), ".augmented")).option("--supervise", "Wrap the manager in a respawn-on-clean-exit loop so auto-upgrades can restart it transparently (ENG-4488)", false).action(managerStartCommand);
5144
- manager.command("stop").description("Stop the running manager daemon").option("--config-dir <dir>", "Config directory for agent files", join15(homedir6(), ".augmented")).action(managerStopCommand);
5145
- manager.command("status").description("Show the current manager daemon status and discovered agents").option("--config-dir <dir>", "Config directory for agent files", join15(homedir6(), ".augmented")).action(managerStatusCommand);
5146
- manager.command("watch").description("Live TUI dashboard \u2014 per-agent boxes, drill-in, log tail. Read-only (ENG-4555).").option("--config-dir <dir>", "Config directory for agent files", join15(homedir6(), ".augmented")).option("--no-tui", "Skip the TUI and stream the manager log to stdout instead (CI / scripts)").action(managerWatchCommand);
5147
- manager.command("install").description("Install OS-level supervisor (launchd LaunchAgent on macOS) so the manager auto-restarts after crash, reboot, or self-update (ENG-4593)").option("--interval <seconds>", "Poll interval in seconds (min 5)", "10").option("--config-dir <dir>", "Config directory for agent files", join15(homedir6(), ".augmented")).action(managerInstallCommand);
5953
+ manager.command("start").description("Start the manager daemon (polls API for config changes and detects local drift)").option("--interval <seconds>", "Poll interval in seconds (min 5)", "10").option("--config-dir <dir>", "Config directory for agent files", join20(homedir9(), ".augmented")).option("--supervise", "Wrap the manager in a respawn-on-clean-exit loop so auto-upgrades can restart it transparently (ENG-4488)", false).action(managerStartCommand);
5954
+ manager.command("stop").description("Stop the running manager daemon").option("--config-dir <dir>", "Config directory for agent files", join20(homedir9(), ".augmented")).action(managerStopCommand);
5955
+ manager.command("status").description("Show the current manager daemon status and discovered agents").option("--config-dir <dir>", "Config directory for agent files", join20(homedir9(), ".augmented")).action(managerStatusCommand);
5956
+ manager.command("watch").description("Live TUI dashboard \u2014 per-agent boxes, drill-in, log tail. Read-only (ENG-4555).").option("--config-dir <dir>", "Config directory for agent files", join20(homedir9(), ".augmented")).option("--no-tui", "Skip the TUI and stream the manager log to stdout instead (CI / scripts)").action(managerWatchCommand);
5957
+ manager.command("install").description("Install OS-level supervisor (launchd LaunchAgent on macOS) so the manager auto-restarts after crash, reboot, or self-update (ENG-4593)").option("--interval <seconds>", "Poll interval in seconds (min 5)", "10").option("--config-dir <dir>", "Config directory for agent files", join20(homedir9(), ".augmented")).action(managerInstallCommand);
5148
5958
  manager.command("uninstall").description("Remove the OS-level supervisor (launchctl unload + delete plist on macOS). Idempotent.").action(managerUninstallCommand);
5149
5959
  manager.command("install-system-unit").description("Install a system-level systemd unit (Linux, root) so the manager auto-starts on every boot. For headless EC2 hosts \u2014 survives reboot without `loginctl enable-linger`. (ENG-4706)").option("--interval <seconds>", "Poll interval in seconds (min 5)", "10").option("--config-dir <dir>", "Config directory for agent files (defaults to /root/.augmented or /home/<user>/.augmented)").option("--user <name>", "Unix user the manager runs as", "root").action(managerInstallSystemUnitCommand);
5150
5960
  manager.command("uninstall-system-unit").description("Remove the system-level systemd unit (Linux, root). Idempotent. (ENG-4706)").action(managerUninstallSystemUnitCommand);
5151
5961
  var agent = program.command("agent").description("Inspect and manage agents");
5152
- agent.command("show <code-name>").description("Display an agent's provisioned OpenClaw configuration").option("--config-dir <dir>", "Config directory", join15(homedir6(), ".augmented")).option("--all-channels", "Show all channels (including disabled)").action(agentShowCommand);
5962
+ agent.command("show <code-name>").description("Display an agent's provisioned OpenClaw configuration").option("--config-dir <dir>", "Config directory", join20(homedir9(), ".augmented")).option("--all-channels", "Show all channels (including disabled)").action(agentShowCommand);
5153
5963
  var kanban = program.command("kanban").description("Manage agent kanban boards");
5154
5964
  kanban.command("list").description("List kanban board items for an agent").requiredOption("--agent <code-name>", "Agent code name").action(kanbanListCommand);
5155
5965
  kanban.command("add <title>").description("Add a new item to an agent kanban board").requiredOption("--agent <code-name>", "Agent code name").option("--priority <1|2|3>", "Priority: 1=high, 2=medium, 3=low", "2").option("--status <status>", "Initial status: backlog | todo | in_progress", "todo").option("--description <text>", "Item description").option("--estimate <minutes>", "Estimated time in minutes").option("--deliverable <text>", "Expected output/deliverable").action(kanbanAddCommand);
@@ -5177,10 +5987,17 @@ integration.command("approve <request-id>").description("Approve a scope request
5177
5987
  integration.command("deny <request-id>").description("Deny a scope request").option("--reason <text>", "Denial reason").action(integrationDenyCommand);
5178
5988
  integration.command("enrol").description("Enrol an integration with an API key or webhook secret (no OAuth flow)").requiredOption("--def <code-name>", "Integration definition code_name (e.g. resend, xero)").requiredOption("--scope <org|team|agent>", "Install scope").option("--api-key <value>", "API key (sets auth_type=api_key)").option("--access-token <value>", "Bearer access token (sets auth_type=oauth2)").option("--webhook-secret <value>", "Webhook signing secret (sets auth_type=webhook)").option("--display-name <name>", "Override display name (defaults to the definition\u2019s display_name)").option("--agent <code-name>", "Agent code name (required for agent scope)").action(integrationEnrolCommand);
5179
5989
  program.command("update").description("Check for and install CLI updates").option("--force", "Update even if manager or agent sessions are running").action(updateCommand);
5990
+ var audit = program.command("audit").description("Operator audits \u2014 sub-agent MCP bindings, etc.");
5991
+ audit.command("subagents").description("Find sub-agents whose tools allowlist would block mcp__* calls on dispatch (ENG-5897)").option("--strict", "Exit with code 2 if any findings (suitable for CI gating)").option("--json", "Emit findings as JSON to stdout").action(auditSubagentsCommand);
5992
+ audit.command("mcp-render").description("Find managed agents whose subagent .md tools allowlist is stale vs .mcp.json (ENG-5922)").option("--strict", "Exit with code 2 if any findings (suitable for CI gating)").option("--json", "Emit findings as JSON to stdout").action(auditMcpRenderCommand);
5993
+ audit.command("secrets").description("Find literal secrets in .mcp.json and world-readable secret files (ENG-5901)").option("--strict", "Exit with code 2 if any findings (suitable for CI gating)").option("--json", "Emit findings as JSON to stdout").action(auditSecretsCommand);
5180
5994
  var skipUpdateCheck = process.argv.includes("--skip-update-check") || process.argv.includes("--json") || process.argv[2] === "update";
5181
5995
  if (!skipUpdateCheck) {
5182
5996
  checkForUpdateOnStartup().catch(() => {
5183
5997
  });
5184
5998
  }
5185
- program.parse();
5999
+ program.parseAsync().catch((err) => {
6000
+ console.error(err instanceof Error ? err.message : String(err));
6001
+ process.exitCode = 1;
6002
+ });
5186
6003
  //# sourceMappingURL=agt.js.map