@integrity-labs/agt-cli 0.27.8-test.9 → 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.
package/dist/bin/agt.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ApiError,
4
+ LITERAL_SECRET_PATTERNS,
4
5
  PROD_AGT_HOST,
5
6
  api,
6
7
  error,
@@ -27,7 +28,7 @@ import {
27
28
  success,
28
29
  table,
29
30
  warn
30
- } from "../chunk-NFBRJGNU.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-HT6EETEL.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:");
@@ -1558,7 +1586,7 @@ import chalk10 from "chalk";
1558
1586
  import ora10 from "ora";
1559
1587
  import { spawn } from "child_process";
1560
1588
  import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync7 } from "fs";
1561
- import { dirname as dirname3, join as join10 } from "path";
1589
+ import { delimiter, dirname as dirname3, join as join10 } from "path";
1562
1590
  import { fileURLToPath as fileURLToPath2 } from "url";
1563
1591
 
1564
1592
  // src/lib/impersonate-flag.ts
@@ -1591,10 +1619,12 @@ production.
1591
1619
  }
1592
1620
 
1593
1621
  // src/lib/impersonate-state.ts
1622
+ import { createHash } from "crypto";
1594
1623
  import {
1595
1624
  chmodSync,
1596
1625
  existsSync as existsSync2,
1597
1626
  mkdirSync as mkdirSync4,
1627
+ readdirSync,
1598
1628
  readFileSync as readFileSync2,
1599
1629
  renameSync,
1600
1630
  rmSync,
@@ -1605,6 +1635,12 @@ import { join as join6 } from "path";
1605
1635
  var IMPERSONATE_ROOT = join6(homedir(), ".augmented-impersonate");
1606
1636
  var ACTIVE_DIR = join6(IMPERSONATE_ROOT, "active");
1607
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
+ }
1608
1644
  var CODE_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
1609
1645
  function assertValidCodeName(codeName) {
1610
1646
  if (!CODE_NAME_RE.test(codeName)) {
@@ -1628,36 +1664,84 @@ function ensureImpersonateWorkdir(codeName) {
1628
1664
  mkdirSync4(dir, { recursive: true });
1629
1665
  return dir;
1630
1666
  }
1631
- function readActiveManifest() {
1632
- 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;
1633
1684
  try {
1634
- const raw = readFileSync2(ACTIVE_MANIFEST_PATH, "utf-8");
1635
- const parsed = JSON.parse(raw);
1636
- const backupsOk = typeof parsed.backups === "object" && parsed.backups !== null && Object.values(parsed.backups).every(
1637
- (v) => v === "had-file" || v === "was-symlink" || v === "didnt-exist"
1638
- );
1639
- 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
1640
- // the right environment. Required, not optional, because writing the
1641
- // wrong host on an existing session would point exit at a server
1642
- // that has no record of the session — silently misleading.
1643
- typeof parsed.host !== "string" || parsed.host.length === 0 || !backupsOk) {
1644
- return null;
1645
- }
1646
- return parsed;
1685
+ return validateManifest(JSON.parse(readFileSync2(path, "utf-8")));
1647
1686
  } catch {
1648
1687
  return null;
1649
1688
  }
1650
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
+ }
1651
1731
  function writeActiveManifest(manifest) {
1732
+ migrateLegacyManifest();
1652
1733
  mkdirSync4(ACTIVE_DIR, { recursive: true, mode: 448 });
1653
- const tmp = ACTIVE_MANIFEST_PATH + ".tmp";
1734
+ const path = sessionManifestPath(manifest.project_cwd);
1735
+ const tmp = path + ".tmp";
1654
1736
  writeFileSync4(tmp, JSON.stringify(manifest, null, 2), { mode: 384 });
1655
1737
  chmodSync(tmp, 384);
1656
- renameSync(tmp, ACTIVE_MANIFEST_PATH);
1738
+ renameSync(tmp, path);
1657
1739
  }
1658
- function clearActiveManifest() {
1659
- if (existsSync2(ACTIVE_MANIFEST_PATH)) {
1660
- rmSync(ACTIVE_MANIFEST_PATH);
1740
+ function clearActiveManifest(projectCwd) {
1741
+ migrateLegacyManifest();
1742
+ const path = sessionManifestPath(projectCwd);
1743
+ if (existsSync2(path)) {
1744
+ rmSync(path);
1661
1745
  }
1662
1746
  }
1663
1747
  function isExpired(manifest, now = Date.now()) {
@@ -1757,7 +1841,7 @@ import {
1757
1841
  copyFileSync,
1758
1842
  existsSync as existsSync4,
1759
1843
  mkdirSync as mkdirSync5,
1760
- readdirSync,
1844
+ readdirSync as readdirSync2,
1761
1845
  readFileSync as readFileSync3,
1762
1846
  renameSync as renameSync3,
1763
1847
  rmdirSync,
@@ -1815,7 +1899,7 @@ function unregisterIntroduceHook(backup) {
1815
1899
  if (created_claude_dir) {
1816
1900
  const claudeDir = dirOf(settings_path);
1817
1901
  try {
1818
- if (existsSync4(claudeDir) && readdirSync(claudeDir).length === 0) {
1902
+ if (existsSync4(claudeDir) && readdirSync2(claudeDir).length === 0) {
1819
1903
  rmdirSync(claudeDir);
1820
1904
  }
1821
1905
  } catch {
@@ -1851,7 +1935,7 @@ import {
1851
1935
  copyFileSync as copyFileSync2,
1852
1936
  existsSync as existsSync5,
1853
1937
  mkdirSync as mkdirSync6,
1854
- readdirSync as readdirSync2,
1938
+ readdirSync as readdirSync3,
1855
1939
  readFileSync as readFileSync4,
1856
1940
  renameSync as renameSync4,
1857
1941
  rmdirSync as rmdirSync2,
@@ -1921,7 +2005,7 @@ function unregisterStatusLine(backup) {
1921
2005
  if (created_claude_dir) {
1922
2006
  const claudeDir = dirname2(settings_path);
1923
2007
  try {
1924
- if (existsSync5(claudeDir) && readdirSync2(claudeDir).length === 0) {
2008
+ if (existsSync5(claudeDir) && readdirSync3(claudeDir).length === 0) {
1925
2009
  rmdirSync2(claudeDir);
1926
2010
  }
1927
2011
  } catch {
@@ -2009,6 +2093,13 @@ function buildIntroduction(input4) {
2009
2093
 
2010
2094
  // src/lib/impersonate-mcp-rewrite.ts
2011
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
+ ]);
2012
2103
  function rewriteMcpJsonForImpersonation(mcpJson, ctx) {
2013
2104
  const parsed = JSON.parse(mcpJson);
2014
2105
  if (!isPlainObject(parsed)) {
@@ -2024,12 +2115,12 @@ function rewriteMcpJsonForImpersonation(mcpJson, ctx) {
2024
2115
  }
2025
2116
  const rewrittenServers = {};
2026
2117
  for (const [serverName, raw] of Object.entries(servers)) {
2027
- rewrittenServers[serverName] = rewriteServerEntry(raw, ctx);
2118
+ rewrittenServers[serverName] = rewriteServerEntry(serverName, raw, ctx);
2028
2119
  }
2029
2120
  const out = { ...parsed, mcpServers: rewrittenServers };
2030
2121
  return JSON.stringify(out, null, 2);
2031
2122
  }
2032
- function rewriteServerEntry(raw, ctx) {
2123
+ function rewriteServerEntry(serverName, raw, ctx) {
2033
2124
  if (!isPlainObject(raw)) return raw;
2034
2125
  let args = raw["args"];
2035
2126
  if (Array.isArray(args)) {
@@ -2043,14 +2134,26 @@ function rewriteServerEntry(raw, ctx) {
2043
2134
  const env = isPlainObject(raw["env"]) ? { ...raw["env"] } : {};
2044
2135
  fillIfEmpty(env, "AGT_HOST", ctx.apiHost);
2045
2136
  fillIfEmpty(env, "AGT_API_KEY", ctx.impersonationToken);
2137
+ if (ctx.agentSessionToken) {
2138
+ fillIfEmpty(env, "AGT_AGENT_SESSION_TOKEN", ctx.agentSessionToken);
2139
+ }
2046
2140
  fillIfEmpty(env, "HOME", ctx.operatorHome);
2047
2141
  fillIfEmpty(env, "AGT_AGENT_ID", ctx.agentId);
2048
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
+ }
2049
2152
  return { ...raw, args, env };
2050
2153
  }
2051
2154
  function fillIfEmpty(env, key, value) {
2052
2155
  const current = env[key];
2053
- if (typeof current !== "string" || current.length === 0) {
2156
+ if (typeof current !== "string" || current.length === 0 || current === `\${${key}}`) {
2054
2157
  env[key] = value;
2055
2158
  }
2056
2159
  }
@@ -2073,13 +2176,15 @@ async function impersonateConnectCommand(token, options = {}) {
2073
2176
  process.exitCode = 1;
2074
2177
  return;
2075
2178
  }
2076
- const existing = readActiveManifest();
2077
- if (existing) {
2078
- const msg = `An impersonation session is already active (${existing.code_name}). Run \`agt impersonate exit\` first.`;
2079
- if (json) jsonOutput({ ok: false, error: msg });
2080
- else error(msg);
2081
- process.exitCode = 1;
2082
- 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
+ }
2083
2188
  }
2084
2189
  if (!options.workdir) {
2085
2190
  const workTreeRoot = findGitWorkTreeRoot(process.cwd());
@@ -2129,8 +2234,10 @@ async function impersonateConnectCommand(token, options = {}) {
2129
2234
  join10(personaDir, ".mcp.json"),
2130
2235
  rewriteMcpJsonForImpersonation(bundle.artifacts[".mcp.json"], {
2131
2236
  operatorHome: process.env.HOME ?? "",
2237
+ operatorPath: resolveOperatorPath(),
2132
2238
  cliMcpBundleDir: resolveCliMcpBundleDir(),
2133
2239
  impersonationToken: bundle.token,
2240
+ agentSessionToken: bundle.agent_session_token ?? null,
2134
2241
  apiHost: host2,
2135
2242
  agentId: bundle.agent.agent_id,
2136
2243
  agentCodeName: bundle.agent.code_name
@@ -2138,6 +2245,15 @@ async function impersonateConnectCommand(token, options = {}) {
2138
2245
  );
2139
2246
  spinner.text = "Swapping persona files\u2026";
2140
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
+ }
2141
2257
  const backups = {};
2142
2258
  const swapped = [];
2143
2259
  let hookBackup;
@@ -2259,33 +2375,8 @@ async function impersonateConnectCommand(token, options = {}) {
2259
2375
  console.log();
2260
2376
  info("When done: `agt impersonate exit` (still active).");
2261
2377
  }
2262
- async function impersonateCurrentCommand() {
2263
- requireImpersonateEnabled();
2264
- const json = isJsonMode();
2265
- const manifest = readActiveManifest();
2266
- if (!manifest) {
2267
- if (json) jsonOutput({ ok: true, active: null });
2268
- else info("No active impersonation.");
2269
- return;
2270
- }
2378
+ function printManifestText(manifest) {
2271
2379
  const expired = isExpired(manifest);
2272
- if (json) {
2273
- jsonOutput({
2274
- ok: true,
2275
- active: {
2276
- code_name: manifest.code_name,
2277
- agent_id: manifest.agent_id,
2278
- team_id: manifest.team_id,
2279
- session_id: manifest.session_id,
2280
- minted_at: manifest.minted_at,
2281
- expires_at: manifest.expires_at,
2282
- expired,
2283
- project_cwd: manifest.project_cwd,
2284
- host: manifest.host
2285
- }
2286
- });
2287
- return;
2288
- }
2289
2380
  console.log();
2290
2381
  console.log(chalk10.bold(`Impersonating: ${manifest.code_name}`));
2291
2382
  console.log(` Agent ID: ${chalk10.dim(manifest.agent_id)}`);
@@ -2299,15 +2390,92 @@ async function impersonateCurrentCommand() {
2299
2390
  console.log(` Host: ${chalk10.dim(manifest.host)}`);
2300
2391
  console.log();
2301
2392
  }
2302
- 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 = {}) {
2303
2447
  requireImpersonateEnabled();
2304
2448
  const json = isJsonMode();
2305
- 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());
2306
2470
  if (!manifest) {
2307
2471
  if (json) jsonOutput({ ok: true, was_active: false });
2308
2472
  else info("No active impersonation to exit.");
2309
2473
  return;
2310
2474
  }
2475
+ await performImpersonateExit(manifest, { json, silent: false });
2476
+ }
2477
+ async function performImpersonateExit(manifest, opts) {
2478
+ const { json, silent } = opts;
2311
2479
  let stopOk = true;
2312
2480
  let stopDetail = null;
2313
2481
  try {
@@ -2364,7 +2532,8 @@ async function impersonateExitCommand() {
2364
2532
  if (!json) error(`Failed to remove introduce hook: ${msg}`);
2365
2533
  }
2366
2534
  }
2367
- clearActiveManifest();
2535
+ clearActiveManifest(manifest.project_cwd);
2536
+ if (silent) return;
2368
2537
  if (json) {
2369
2538
  jsonOutput({
2370
2539
  ok: true,
@@ -2385,9 +2554,32 @@ async function impersonateExitCommand() {
2385
2554
  }
2386
2555
  info("Restart Claude Code to pick up your original persona.");
2387
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
+ }
2388
2580
  async function impersonateIntroduceCommand() {
2389
2581
  try {
2390
- const manifest = readActiveManifest();
2582
+ const manifest = readActiveManifest(process.cwd());
2391
2583
  if (!manifest || isExpired(manifest)) return;
2392
2584
  const cwd = manifest.project_cwd;
2393
2585
  const identity = readClaudeMdIdentity(join10(cwd, "CLAUDE.md"));
@@ -2428,6 +2620,14 @@ function resolveCliMcpBundleDir() {
2428
2620
  }
2429
2621
  return candidates[candidates.length - 1];
2430
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
+ }
2431
2631
  function buildImpersonateClaudeLaunch(projectCwd, inheritEnv = process.env, agentId = null) {
2432
2632
  const env = {
2433
2633
  ...inheritEnv,
@@ -2452,13 +2652,13 @@ import ora11 from "ora";
2452
2652
 
2453
2653
  // ../../packages/core/dist/drift/live-state-reader.js
2454
2654
  import { readFile } from "fs/promises";
2455
- import { createHash } from "crypto";
2655
+ import { createHash as createHash2 } from "crypto";
2456
2656
  import { join as join11 } from "path";
2457
2657
  import JSON5 from "json5";
2458
2658
  async function hashFile(filePath) {
2459
2659
  try {
2460
2660
  const content = await readFile(filePath);
2461
- return createHash("sha256").update(content).digest("hex");
2661
+ return createHash2("sha256").update(content).digest("hex");
2462
2662
  } catch {
2463
2663
  return null;
2464
2664
  }
@@ -2900,6 +3100,97 @@ async function hostRotateKeyCommand(hostName) {
2900
3100
  process.exitCode = 1;
2901
3101
  }
2902
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
+ }
2903
3194
  async function hostDecommissionCommand(hostName) {
2904
3195
  const teamSlug = requireTeam();
2905
3196
  if (!teamSlug) return;
@@ -4643,7 +4934,7 @@ import { execFileSync, execSync } from "child_process";
4643
4934
  import { existsSync as existsSync10, realpathSync as realpathSync2 } from "fs";
4644
4935
  import chalk18 from "chalk";
4645
4936
  import ora16 from "ora";
4646
- var cliVersion = true ? "0.27.8-test.9" : "dev";
4937
+ var cliVersion = true ? "0.27.9-test.11" : "dev";
4647
4938
  async function fetchLatestVersion() {
4648
4939
  const host2 = getHost();
4649
4940
  if (!host2) return null;
@@ -4874,6 +5165,397 @@ async function checkForUpdateOnStartup() {
4874
5165
  }
4875
5166
  }
4876
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
+
4877
5559
  // src/commands/integration.ts
4878
5560
  import chalk19 from "chalk";
4879
5561
  import ora17 from "ora";
@@ -4881,7 +5563,7 @@ async function integrationListCommand() {
4881
5563
  const json = isJsonMode();
4882
5564
  const spinner = ora17({ text: "Fetching integrations\u2026", isSilent: json }).start();
4883
5565
  try {
4884
- const data = await api.get("/integrations/bindings");
5566
+ const data = await api.get("/integrations/installs");
4885
5567
  spinner.stop();
4886
5568
  if (json) {
4887
5569
  jsonOutput({ integrations: data });
@@ -4908,7 +5590,7 @@ async function integrationShowCommand(slug) {
4908
5590
  const json = isJsonMode();
4909
5591
  const spinner = ora17({ text: "Fetching integration\u2026", isSilent: json }).start();
4910
5592
  try {
4911
- const all = await api.get("/integrations/bindings");
5593
+ const all = await api.get("/integrations/installs");
4912
5594
  const integration2 = all.find((p) => p.slug === slug);
4913
5595
  if (!integration2) {
4914
5596
  spinner.stop();
@@ -4916,7 +5598,7 @@ async function integrationShowCommand(slug) {
4916
5598
  process.exitCode = 1;
4917
5599
  return;
4918
5600
  }
4919
- const scopes = await api.get(`/integrations/bindings/${integration2.id}/scopes`);
5601
+ const scopes = await api.get(`/integrations/installs/${integration2.id}/scopes`);
4920
5602
  spinner.stop();
4921
5603
  if (json) {
4922
5604
  jsonOutput({ integration: integration2, scopes });
@@ -4950,7 +5632,7 @@ async function integrationInstallCommand(slug, options) {
4950
5632
  const json = isJsonMode();
4951
5633
  const spinner = ora17({ text: "Installing integration\u2026", isSilent: json }).start();
4952
5634
  try {
4953
- const all = await api.get("/integrations/bindings");
5635
+ const all = await api.get("/integrations/installs");
4954
5636
  const integration2 = all.find((p) => p.slug === slug);
4955
5637
  if (!integration2) {
4956
5638
  spinner.stop();
@@ -4967,7 +5649,7 @@ async function integrationInstallCommand(slug, options) {
4967
5649
  return;
4968
5650
  }
4969
5651
  const scopes = options.scopes?.split(",").map((s) => s.trim()).filter(Boolean) ?? [];
4970
- const result = await api.post("/integrations/bindings/install", {
5652
+ const result = await api.post("/integrations/installs/install", {
4971
5653
  agent_id: agent2.agent_id,
4972
5654
  plugin_id: integration2.id,
4973
5655
  scopes
@@ -5000,7 +5682,7 @@ async function integrationUninstallCommand(slug, options) {
5000
5682
  const json = isJsonMode();
5001
5683
  const spinner = ora17({ text: "Uninstalling integration\u2026", isSilent: json }).start();
5002
5684
  try {
5003
- const all = await api.get("/integrations/bindings");
5685
+ const all = await api.get("/integrations/installs");
5004
5686
  const integration2 = all.find((p) => p.slug === slug);
5005
5687
  if (!integration2) {
5006
5688
  spinner.stop();
@@ -5016,7 +5698,7 @@ async function integrationUninstallCommand(slug, options) {
5016
5698
  process.exitCode = 1;
5017
5699
  return;
5018
5700
  }
5019
- await api.post("/integrations/bindings/uninstall", {
5701
+ await api.post("/integrations/installs/uninstall", {
5020
5702
  agent_id: agent2.agent_id,
5021
5703
  plugin_id: integration2.id
5022
5704
  });
@@ -5031,7 +5713,7 @@ async function integrationRequestsCommand() {
5031
5713
  const json = isJsonMode();
5032
5714
  const spinner = ora17({ text: "Fetching scope requests\u2026", isSilent: json }).start();
5033
5715
  try {
5034
- const requests = await api.get("/integrations/bindings/scope-requests");
5716
+ const requests = await api.get("/integrations/installs/scope-requests");
5035
5717
  spinner.stop();
5036
5718
  if (json) {
5037
5719
  jsonOutput({ requests });
@@ -5057,7 +5739,7 @@ async function integrationApproveCommand(requestId, options) {
5057
5739
  const json = isJsonMode();
5058
5740
  const spinner = ora17({ text: "Approving request\u2026", isSilent: json }).start();
5059
5741
  try {
5060
- const result = await api.put(`/integrations/bindings/scope-requests/${requestId}`, {
5742
+ const result = await api.put(`/integrations/installs/scope-requests/${requestId}`, {
5061
5743
  status: "approved",
5062
5744
  review_notes: options.notes
5063
5745
  });
@@ -5076,7 +5758,7 @@ async function integrationDenyCommand(requestId, options) {
5076
5758
  const json = isJsonMode();
5077
5759
  const spinner = ora17({ text: "Denying request\u2026", isSilent: json }).start();
5078
5760
  try {
5079
- const result = await api.put(`/integrations/bindings/scope-requests/${requestId}`, {
5761
+ const result = await api.put(`/integrations/installs/scope-requests/${requestId}`, {
5080
5762
  status: "denied",
5081
5763
  review_notes: options.reason
5082
5764
  });
@@ -5138,7 +5820,7 @@ async function integrationEnrolCommand(options) {
5138
5820
  }
5139
5821
  let displayName = options.displayName;
5140
5822
  if (!displayName) {
5141
- const defs = await api.get("/integrations/bindings");
5823
+ const defs = await api.get("/integrations/installs");
5142
5824
  const def = defs.find((d) => d.slug === options.def);
5143
5825
  displayName = def?.name ?? options.def;
5144
5826
  }
@@ -5175,14 +5857,20 @@ function handleError(err) {
5175
5857
  }
5176
5858
 
5177
5859
  // src/bin/agt.ts
5178
- var cliVersion2 = true ? "0.27.8-test.9" : "dev";
5860
+ var cliVersion2 = true ? "0.27.9-test.11" : "dev";
5179
5861
  var program = new Command();
5180
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");
5181
- program.hook("preAction", (thisCommand) => {
5863
+ program.hook("preAction", async (thisCommand, actionCommand) => {
5182
5864
  const root = thisCommand.optsWithGlobals();
5183
5865
  if (root.json) {
5184
5866
  setJsonMode(true);
5185
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
+ }
5186
5874
  });
5187
5875
  program.command("whoami").description("Show the authenticated host, team, and user from AGT_API_KEY").action(whoamiCommand);
5188
5876
  program.command("setup <token>").description("One-command host setup: exchange provisioning token, configure env vars, verify, and start manager").option(
@@ -5222,8 +5910,12 @@ impersonate.command("connect <token>").description(
5222
5910
  })
5223
5911
  )
5224
5912
  );
5225
- impersonate.command("current").description('Print the active impersonation (or "none")').action(impersonateCurrentCommand);
5226
- 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
+ );
5227
5919
  impersonate.command("introduce", { hidden: true }).description("Print the impersonated agent's self-introduction (SessionStart hook target)").action(impersonateIntroduceCommand);
5228
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);
5229
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);
@@ -5250,6 +5942,7 @@ host.command("unassign <host-name> <agent-code-names...>").description("Unassign
5250
5942
  host.command("agents [host-name]").description("List agents assigned to a host (omit name to auto-resolve from AGT_API_KEY)").action(hostAgentsCommand);
5251
5943
  host.command("rotate-key <host-name>").description("Rotate the API key for a host (revokes the old key)").action(hostRotateKeyCommand);
5252
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);
5253
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(
5254
5947
  (hostName, opts) => hostPairCommand(hostName, {
5255
5948
  port: opts.port,
@@ -5257,16 +5950,16 @@ host.command("pair <host-name>").description("Start an SSM port-forward + shell
5257
5950
  })
5258
5951
  );
5259
5952
  var manager = program.command("manager").description("Host config sync daemon \u2014 keeps local agent files in sync with API");
5260
- 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);
5261
- manager.command("stop").description("Stop the running manager daemon").option("--config-dir <dir>", "Config directory for agent files", join15(homedir6(), ".augmented")).action(managerStopCommand);
5262
- 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);
5263
- 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);
5264
- 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);
5265
5958
  manager.command("uninstall").description("Remove the OS-level supervisor (launchctl unload + delete plist on macOS). Idempotent.").action(managerUninstallCommand);
5266
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);
5267
5960
  manager.command("uninstall-system-unit").description("Remove the system-level systemd unit (Linux, root). Idempotent. (ENG-4706)").action(managerUninstallSystemUnitCommand);
5268
5961
  var agent = program.command("agent").description("Inspect and manage agents");
5269
- 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);
5270
5963
  var kanban = program.command("kanban").description("Manage agent kanban boards");
5271
5964
  kanban.command("list").description("List kanban board items for an agent").requiredOption("--agent <code-name>", "Agent code name").action(kanbanListCommand);
5272
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);
@@ -5294,10 +5987,17 @@ integration.command("approve <request-id>").description("Approve a scope request
5294
5987
  integration.command("deny <request-id>").description("Deny a scope request").option("--reason <text>", "Denial reason").action(integrationDenyCommand);
5295
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);
5296
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);
5297
5994
  var skipUpdateCheck = process.argv.includes("--skip-update-check") || process.argv.includes("--json") || process.argv[2] === "update";
5298
5995
  if (!skipUpdateCheck) {
5299
5996
  checkForUpdateOnStartup().catch(() => {
5300
5997
  });
5301
5998
  }
5302
- program.parse();
5999
+ program.parseAsync().catch((err) => {
6000
+ console.error(err instanceof Error ? err.message : String(err));
6001
+ process.exitCode = 1;
6002
+ });
5303
6003
  //# sourceMappingURL=agt.js.map