@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/assets/impersonate-statusline.sh +7 -2
- package/dist/bin/agt.js +798 -98
- package/dist/bin/agt.js.map +1 -1
- package/dist/{chunk-HT6EETEL.js → chunk-45I2X4M7.js} +361 -26
- package/dist/chunk-45I2X4M7.js.map +1 -0
- package/dist/{chunk-NFBRJGNU.js → chunk-N2EBL4PZ.js} +915 -239
- package/dist/chunk-N2EBL4PZ.js.map +1 -0
- package/dist/{chunk-F4NG4EXD.js → chunk-SJPAXXTW.js} +512 -80
- package/dist/chunk-SJPAXXTW.js.map +1 -0
- package/dist/{claude-pair-runtime-OBAJZDXK.js → claude-pair-runtime-OUGV74LY.js} +2 -2
- package/dist/lib/manager-worker.js +2058 -508
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/index.js +143 -67
- package/dist/mcp/slack-channel.js +1059 -297
- package/dist/mcp/teams-channel.js +16018 -0
- package/dist/mcp/telegram-channel.js +999 -140
- package/dist/{persistent-session-SBSOZG74.js → persistent-session-G3MR7JVI.js} +5 -3
- package/dist/responsiveness-probe-UHSBETBR.js +74 -0
- package/dist/responsiveness-probe-UHSBETBR.js.map +1 -0
- package/package.json +2 -2
- package/dist/chunk-F4NG4EXD.js.map +0 -1
- package/dist/chunk-HT6EETEL.js.map +0 -1
- package/dist/chunk-NFBRJGNU.js.map +0 -1
- package/dist/responsiveness-probe-DU4IJ2RZ.js +0 -33
- package/dist/responsiveness-probe-DU4IJ2RZ.js.map +0 -1
- /package/dist/{claude-pair-runtime-OBAJZDXK.js.map → claude-pair-runtime-OUGV74LY.js.map} +0 -0
- /package/dist/{persistent-session-SBSOZG74.js.map → persistent-session-G3MR7JVI.js.map} +0 -0
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-
|
|
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-
|
|
57
|
+
} from "../chunk-45I2X4M7.js";
|
|
57
58
|
|
|
58
59
|
// src/bin/agt.ts
|
|
59
|
-
import { join as
|
|
60
|
-
import { homedir as
|
|
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:
|
|
555
|
-
const entries =
|
|
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 (
|
|
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
|
|
1632
|
-
if (
|
|
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
|
-
|
|
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
|
|
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,
|
|
1738
|
+
renameSync(tmp, path);
|
|
1657
1739
|
}
|
|
1658
|
-
function clearActiveManifest() {
|
|
1659
|
-
|
|
1660
|
-
|
|
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) &&
|
|
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
|
|
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) &&
|
|
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
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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.
|
|
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(
|
|
5226
|
-
|
|
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",
|
|
5261
|
-
manager.command("stop").description("Stop the running manager daemon").option("--config-dir <dir>", "Config directory for agent files",
|
|
5262
|
-
manager.command("status").description("Show the current manager daemon status and discovered agents").option("--config-dir <dir>", "Config directory for agent files",
|
|
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",
|
|
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",
|
|
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",
|
|
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.
|
|
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
|