@integrity-labs/agt-cli 0.26.2-eng5706.1 → 0.27.0-test.4
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 +874 -192
- package/dist/bin/agt.js.map +1 -1
- package/dist/{chunk-LBYU24PW.js → chunk-CPZIFISH.js} +22 -8
- package/dist/{chunk-LBYU24PW.js.map → chunk-CPZIFISH.js.map} +1 -1
- package/dist/{chunk-U3HCB23E.js → chunk-CTWY2X4S.js} +41 -8
- package/dist/chunk-CTWY2X4S.js.map +1 -0
- package/dist/{chunk-QSDDP7JN.js → chunk-WDCSMDQ4.js} +52 -8
- package/dist/chunk-WDCSMDQ4.js.map +1 -0
- package/dist/{claude-pair-runtime-2NNAUBP3.js → claude-pair-runtime-YRPKT45R.js} +2 -2
- package/dist/lib/manager-worker.js +7 -7
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/direct-chat-channel.js +168 -0
- package/dist/mcp/slack-channel.js +158 -19
- package/dist/mcp/telegram-channel.js +212 -40
- package/dist/{persistent-session-K4X76LLB.js → persistent-session-QJT7JE5P.js} +3 -3
- package/dist/{responsiveness-probe-QBV43SJ7.js → responsiveness-probe-ZERSZOZ2.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-QSDDP7JN.js.map +0 -1
- package/dist/chunk-U3HCB23E.js.map +0 -1
- /package/dist/{claude-pair-runtime-2NNAUBP3.js.map → claude-pair-runtime-YRPKT45R.js.map} +0 -0
- /package/dist/{persistent-session-K4X76LLB.js.map → persistent-session-QJT7JE5P.js.map} +0 -0
- /package/dist/{responsiveness-probe-QBV43SJ7.js.map → responsiveness-probe-ZERSZOZ2.js.map} +0 -0
package/dist/bin/agt.js
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
success,
|
|
27
27
|
table,
|
|
28
28
|
warn
|
|
29
|
-
} from "../chunk-
|
|
29
|
+
} from "../chunk-CPZIFISH.js";
|
|
30
30
|
import {
|
|
31
31
|
CHANNEL_REGISTRY,
|
|
32
32
|
DEPLOYMENT_TEMPLATES,
|
|
@@ -52,11 +52,11 @@ import {
|
|
|
52
52
|
renderTemplate,
|
|
53
53
|
resolveChannels,
|
|
54
54
|
serializeManifestForSlackCli
|
|
55
|
-
} from "../chunk-
|
|
55
|
+
} from "../chunk-CTWY2X4S.js";
|
|
56
56
|
|
|
57
57
|
// src/bin/agt.ts
|
|
58
|
-
import { join as
|
|
59
|
-
import { homedir as
|
|
58
|
+
import { join as join14 } from "path";
|
|
59
|
+
import { homedir as homedir5 } from "os";
|
|
60
60
|
import { Command } from "commander";
|
|
61
61
|
|
|
62
62
|
// src/commands/whoami.ts
|
|
@@ -550,8 +550,8 @@ async function lintCommand(path) {
|
|
|
550
550
|
process.exitCode = 1;
|
|
551
551
|
return;
|
|
552
552
|
}
|
|
553
|
-
const { readdirSync, statSync: statSync2 } = await import("fs");
|
|
554
|
-
const entries =
|
|
553
|
+
const { readdirSync: readdirSync2, statSync: statSync2 } = await import("fs");
|
|
554
|
+
const entries = readdirSync2(augmentedDir);
|
|
555
555
|
for (const entry of entries) {
|
|
556
556
|
const entryPath = join2(augmentedDir, entry);
|
|
557
557
|
if (statSync2(entryPath).isDirectory()) {
|
|
@@ -1552,14 +1552,671 @@ async function provisionCommand(codeName, options) {
|
|
|
1552
1552
|
info(` agt drift check ${agentData.code_name}`);
|
|
1553
1553
|
}
|
|
1554
1554
|
|
|
1555
|
-
// src/commands/
|
|
1555
|
+
// src/commands/impersonate.ts
|
|
1556
1556
|
import chalk10 from "chalk";
|
|
1557
1557
|
import ora10 from "ora";
|
|
1558
|
+
import { spawn } from "child_process";
|
|
1559
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
1560
|
+
import { join as join9 } from "path";
|
|
1561
|
+
|
|
1562
|
+
// src/lib/impersonate-flag.ts
|
|
1563
|
+
var ENV_VAR = "AGT_IMPERSONATE_ENABLED";
|
|
1564
|
+
function impersonateEnabled() {
|
|
1565
|
+
const v = process.env[ENV_VAR];
|
|
1566
|
+
if (!v) return false;
|
|
1567
|
+
const trimmed = v.trim().toLowerCase();
|
|
1568
|
+
return !(trimmed === "" || trimmed === "0" || trimmed === "false" || trimmed === "no");
|
|
1569
|
+
}
|
|
1570
|
+
function requireImpersonateEnabled() {
|
|
1571
|
+
if (impersonateEnabled()) return;
|
|
1572
|
+
process.stderr.write(
|
|
1573
|
+
`
|
|
1574
|
+
The \`agt impersonate\` commands are gated behind a feature flag.
|
|
1575
|
+
|
|
1576
|
+
To enable them, set ${ENV_VAR}=1 in your environment:
|
|
1577
|
+
|
|
1578
|
+
export ${ENV_VAR}=1
|
|
1579
|
+
|
|
1580
|
+
These commands are part of the operator-impersonates-agent v0 flow
|
|
1581
|
+
(ENG-5687 / ENG-5688). The gating will be removed once the
|
|
1582
|
+
surrounding pieces (ENG-5689 channel-MCP refusal, ENG-5690 slash
|
|
1583
|
+
commands) land and the end-to-end smoke test has been run against
|
|
1584
|
+
production.
|
|
1585
|
+
|
|
1586
|
+
`
|
|
1587
|
+
);
|
|
1588
|
+
process.exit(2);
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
// src/lib/impersonate-state.ts
|
|
1592
|
+
import {
|
|
1593
|
+
chmodSync,
|
|
1594
|
+
existsSync as existsSync2,
|
|
1595
|
+
mkdirSync as mkdirSync4,
|
|
1596
|
+
readFileSync as readFileSync2,
|
|
1597
|
+
renameSync,
|
|
1598
|
+
rmSync,
|
|
1599
|
+
writeFileSync as writeFileSync4
|
|
1600
|
+
} from "fs";
|
|
1601
|
+
import { homedir } from "os";
|
|
1602
|
+
import { join as join6 } from "path";
|
|
1603
|
+
var IMPERSONATE_ROOT = join6(homedir(), ".augmented-impersonate");
|
|
1604
|
+
var ACTIVE_DIR = join6(IMPERSONATE_ROOT, "active");
|
|
1605
|
+
var ACTIVE_MANIFEST_PATH = join6(ACTIVE_DIR, "manifest.json");
|
|
1606
|
+
var CODE_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
1607
|
+
function assertValidCodeName(codeName) {
|
|
1608
|
+
if (!CODE_NAME_RE.test(codeName)) {
|
|
1609
|
+
throw new Error(
|
|
1610
|
+
`Invalid agent code_name "${codeName}" \u2014 must be lowercase kebab-case (a-z, 0-9, hyphens; no leading/trailing/consecutive hyphens).`
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
function getImpersonateCodeNameDir(codeName) {
|
|
1615
|
+
assertValidCodeName(codeName);
|
|
1616
|
+
return join6(IMPERSONATE_ROOT, codeName);
|
|
1617
|
+
}
|
|
1618
|
+
function ensureCodeNameDir(codeName) {
|
|
1619
|
+
assertValidCodeName(codeName);
|
|
1620
|
+
const dir = getImpersonateCodeNameDir(codeName);
|
|
1621
|
+
mkdirSync4(dir, { recursive: true });
|
|
1622
|
+
return dir;
|
|
1623
|
+
}
|
|
1624
|
+
function ensureImpersonateWorkdir(codeName) {
|
|
1625
|
+
const dir = join6(ensureCodeNameDir(codeName), "workdir");
|
|
1626
|
+
mkdirSync4(dir, { recursive: true });
|
|
1627
|
+
return dir;
|
|
1628
|
+
}
|
|
1629
|
+
function readActiveManifest() {
|
|
1630
|
+
if (!existsSync2(ACTIVE_MANIFEST_PATH)) return null;
|
|
1631
|
+
try {
|
|
1632
|
+
const raw = readFileSync2(ACTIVE_MANIFEST_PATH, "utf-8");
|
|
1633
|
+
const parsed = JSON.parse(raw);
|
|
1634
|
+
const backupsOk = typeof parsed.backups === "object" && parsed.backups !== null && Object.values(parsed.backups).every(
|
|
1635
|
+
(v) => v === "had-file" || v === "was-symlink" || v === "didnt-exist"
|
|
1636
|
+
);
|
|
1637
|
+
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
|
|
1638
|
+
// the right environment. Required, not optional, because writing the
|
|
1639
|
+
// wrong host on an existing session would point exit at a server
|
|
1640
|
+
// that has no record of the session — silently misleading.
|
|
1641
|
+
typeof parsed.host !== "string" || parsed.host.length === 0 || !backupsOk) {
|
|
1642
|
+
return null;
|
|
1643
|
+
}
|
|
1644
|
+
return parsed;
|
|
1645
|
+
} catch {
|
|
1646
|
+
return null;
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
function writeActiveManifest(manifest) {
|
|
1650
|
+
mkdirSync4(ACTIVE_DIR, { recursive: true, mode: 448 });
|
|
1651
|
+
const tmp = ACTIVE_MANIFEST_PATH + ".tmp";
|
|
1652
|
+
writeFileSync4(tmp, JSON.stringify(manifest, null, 2), { mode: 384 });
|
|
1653
|
+
chmodSync(tmp, 384);
|
|
1654
|
+
renameSync(tmp, ACTIVE_MANIFEST_PATH);
|
|
1655
|
+
}
|
|
1656
|
+
function clearActiveManifest() {
|
|
1657
|
+
if (existsSync2(ACTIVE_MANIFEST_PATH)) {
|
|
1658
|
+
rmSync(ACTIVE_MANIFEST_PATH);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
function isExpired(manifest, now = Date.now()) {
|
|
1662
|
+
return manifest.expires_at * 1e3 <= now;
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
// src/lib/impersonate-fs-ops.ts
|
|
1666
|
+
import {
|
|
1667
|
+
existsSync as existsSync3,
|
|
1668
|
+
lstatSync,
|
|
1669
|
+
readlinkSync,
|
|
1670
|
+
realpathSync,
|
|
1671
|
+
renameSync as renameSync2,
|
|
1672
|
+
symlinkSync,
|
|
1673
|
+
unlinkSync
|
|
1674
|
+
} from "fs";
|
|
1675
|
+
import { homedir as homedir2 } from "os";
|
|
1676
|
+
import { dirname, join as join7, parse, sep } from "path";
|
|
1677
|
+
var BACKUP_SUFFIX = ".pre-impersonate-backup";
|
|
1678
|
+
function findGitWorkTreeRoot(startDir) {
|
|
1679
|
+
const { root } = parse(startDir);
|
|
1680
|
+
let dir = startDir;
|
|
1681
|
+
for (; ; ) {
|
|
1682
|
+
if (existsSync3(join7(dir, ".git"))) return dir;
|
|
1683
|
+
if (dir === root) return null;
|
|
1684
|
+
const parent = dirname(dir);
|
|
1685
|
+
if (parent === dir) return null;
|
|
1686
|
+
dir = parent;
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
function swapInPersonaFile(projectPath, personaPath) {
|
|
1690
|
+
let kind;
|
|
1691
|
+
if (!lstatSafe(projectPath)) {
|
|
1692
|
+
kind = "didnt-exist";
|
|
1693
|
+
} else {
|
|
1694
|
+
const stat = lstatSync(projectPath);
|
|
1695
|
+
if (stat.isSymbolicLink()) {
|
|
1696
|
+
const target = readlinkSync(projectPath);
|
|
1697
|
+
const ourRoot = realpathSync(homedir2()) + sep + ".augmented-impersonate" + sep;
|
|
1698
|
+
let resolvedTarget;
|
|
1699
|
+
try {
|
|
1700
|
+
resolvedTarget = realpathSync(projectPath);
|
|
1701
|
+
} catch {
|
|
1702
|
+
throw new Error(
|
|
1703
|
+
`${projectPath} is a broken symlink (target ${target}). Resolve manually and retry.`
|
|
1704
|
+
);
|
|
1705
|
+
}
|
|
1706
|
+
if (resolvedTarget.startsWith(ourRoot)) {
|
|
1707
|
+
unlinkSync(projectPath);
|
|
1708
|
+
kind = "was-symlink";
|
|
1709
|
+
} else {
|
|
1710
|
+
throw new Error(
|
|
1711
|
+
`${projectPath} is a symlink to ${target} (resolved: ${resolvedTarget}). Refusing to replace it \u2014 it doesn't live under ${ourRoot}. Resolve manually and retry.`
|
|
1712
|
+
);
|
|
1713
|
+
}
|
|
1714
|
+
} else {
|
|
1715
|
+
const backupPath = projectPath + BACKUP_SUFFIX;
|
|
1716
|
+
if (existsSync3(backupPath)) {
|
|
1717
|
+
throw new Error(
|
|
1718
|
+
`${backupPath} already exists from a previous run. Remove it manually if you're sure it's stale, then retry.`
|
|
1719
|
+
);
|
|
1720
|
+
}
|
|
1721
|
+
renameSync2(projectPath, backupPath);
|
|
1722
|
+
kind = "had-file";
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
symlinkSync(personaPath, projectPath, "file");
|
|
1726
|
+
return kind;
|
|
1727
|
+
}
|
|
1728
|
+
function restoreSwapped(projectPath, kind) {
|
|
1729
|
+
if (lstatSafe(projectPath)) {
|
|
1730
|
+
const stat = lstatSync(projectPath);
|
|
1731
|
+
if (stat.isSymbolicLink()) {
|
|
1732
|
+
unlinkSync(projectPath);
|
|
1733
|
+
} else {
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
if (kind === "had-file") {
|
|
1738
|
+
const backupPath = projectPath + BACKUP_SUFFIX;
|
|
1739
|
+
if (existsSync3(backupPath)) {
|
|
1740
|
+
renameSync2(backupPath, projectPath);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
function lstatSafe(path) {
|
|
1745
|
+
try {
|
|
1746
|
+
lstatSync(path);
|
|
1747
|
+
return true;
|
|
1748
|
+
} catch {
|
|
1749
|
+
return false;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
// src/lib/impersonate-hook.ts
|
|
1754
|
+
import {
|
|
1755
|
+
copyFileSync,
|
|
1756
|
+
existsSync as existsSync4,
|
|
1757
|
+
mkdirSync as mkdirSync5,
|
|
1758
|
+
readdirSync,
|
|
1759
|
+
readFileSync as readFileSync3,
|
|
1760
|
+
renameSync as renameSync3,
|
|
1761
|
+
rmdirSync,
|
|
1762
|
+
unlinkSync as unlinkSync2,
|
|
1763
|
+
writeFileSync as writeFileSync5
|
|
1764
|
+
} from "fs";
|
|
1765
|
+
import { join as join8 } from "path";
|
|
1766
|
+
var INTRODUCE_HOOK_COMMAND = "agt impersonate introduce";
|
|
1767
|
+
var SESSION_START_MATCHER = "startup";
|
|
1768
|
+
function registerIntroduceHook(projectCwd, command = INTRODUCE_HOOK_COMMAND) {
|
|
1769
|
+
const claudeDir = join8(projectCwd, ".claude");
|
|
1770
|
+
const createdClaudeDir = !existsSync4(claudeDir);
|
|
1771
|
+
mkdirSync5(claudeDir, { recursive: true });
|
|
1772
|
+
const settingsPath = join8(claudeDir, "settings.local.json");
|
|
1773
|
+
const settingsExisted = existsSync4(settingsPath);
|
|
1774
|
+
const backupPath = settingsPath + BACKUP_SUFFIX;
|
|
1775
|
+
let settings = {};
|
|
1776
|
+
if (settingsExisted) {
|
|
1777
|
+
if (!existsSync4(backupPath)) {
|
|
1778
|
+
copyFileSync(settingsPath, backupPath);
|
|
1779
|
+
}
|
|
1780
|
+
settings = asPlainObject(safeParseJson(readFileSync3(settingsPath, "utf-8")));
|
|
1781
|
+
}
|
|
1782
|
+
const hooks = asPlainObject(settings["hooks"]);
|
|
1783
|
+
const existing = Array.isArray(hooks[SESSION_START_KEY]) ? [...hooks[SESSION_START_KEY]] : [];
|
|
1784
|
+
const alreadyRegistered = existing.some(
|
|
1785
|
+
(entry) => entryMatchesCommand(entry, command)
|
|
1786
|
+
);
|
|
1787
|
+
if (!alreadyRegistered) {
|
|
1788
|
+
existing.push({
|
|
1789
|
+
matcher: SESSION_START_MATCHER,
|
|
1790
|
+
hooks: [{ type: "command", command }]
|
|
1791
|
+
});
|
|
1792
|
+
}
|
|
1793
|
+
hooks[SESSION_START_KEY] = existing;
|
|
1794
|
+
settings["hooks"] = hooks;
|
|
1795
|
+
writeFileSync5(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
1796
|
+
return {
|
|
1797
|
+
settings_path: settingsPath,
|
|
1798
|
+
settings_backup: settingsExisted ? "had-file" : "didnt-exist",
|
|
1799
|
+
created_claude_dir: createdClaudeDir
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
function unregisterIntroduceHook(backup) {
|
|
1803
|
+
const { settings_path, settings_backup, created_claude_dir } = backup;
|
|
1804
|
+
const backupPath = settings_path + BACKUP_SUFFIX;
|
|
1805
|
+
if (settings_backup === "had-file") {
|
|
1806
|
+
if (existsSync4(backupPath)) {
|
|
1807
|
+
renameSync3(backupPath, settings_path);
|
|
1808
|
+
}
|
|
1809
|
+
} else {
|
|
1810
|
+
if (existsSync4(settings_path)) unlinkSync2(settings_path);
|
|
1811
|
+
if (existsSync4(backupPath)) unlinkSync2(backupPath);
|
|
1812
|
+
}
|
|
1813
|
+
if (created_claude_dir) {
|
|
1814
|
+
const claudeDir = dirOf(settings_path);
|
|
1815
|
+
try {
|
|
1816
|
+
if (existsSync4(claudeDir) && readdirSync(claudeDir).length === 0) {
|
|
1817
|
+
rmdirSync(claudeDir);
|
|
1818
|
+
}
|
|
1819
|
+
} catch {
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
var SESSION_START_KEY = "SessionStart";
|
|
1824
|
+
function entryMatchesCommand(entry, command) {
|
|
1825
|
+
if (entry.matcher !== SESSION_START_MATCHER) return false;
|
|
1826
|
+
const entryHooks = entry.hooks;
|
|
1827
|
+
return Array.isArray(entryHooks) && entryHooks.some(
|
|
1828
|
+
(h) => typeof h === "object" && h !== null && h.type === "command" && h.command === command
|
|
1829
|
+
);
|
|
1830
|
+
}
|
|
1831
|
+
function dirOf(filePath) {
|
|
1832
|
+
const idx = filePath.lastIndexOf("/");
|
|
1833
|
+
return idx === -1 ? filePath : filePath.slice(0, idx);
|
|
1834
|
+
}
|
|
1835
|
+
function safeParseJson(text) {
|
|
1836
|
+
try {
|
|
1837
|
+
return JSON.parse(text);
|
|
1838
|
+
} catch {
|
|
1839
|
+
return null;
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
function asPlainObject(value) {
|
|
1843
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
// src/lib/impersonate-introduce.ts
|
|
1847
|
+
function parseIdentityFromClaudeMd(content) {
|
|
1848
|
+
const headingMatch = content.match(/^#\s+(.+?)\s*$/m);
|
|
1849
|
+
const displayName = headingMatch?.[1]?.trim() || null;
|
|
1850
|
+
const roleMatch = content.match(
|
|
1851
|
+
/^You are \*\*[^*]+\*\*,\s*\*\*([^*]+)\*\*/m
|
|
1852
|
+
);
|
|
1853
|
+
const role = roleMatch?.[1]?.trim() || null;
|
|
1854
|
+
return { displayName, role };
|
|
1855
|
+
}
|
|
1856
|
+
function parseIntegrationsFromMcpJson(content) {
|
|
1857
|
+
let parsed;
|
|
1858
|
+
try {
|
|
1859
|
+
parsed = JSON.parse(content);
|
|
1860
|
+
} catch {
|
|
1861
|
+
return [];
|
|
1862
|
+
}
|
|
1863
|
+
const servers = parsed?.mcpServers;
|
|
1864
|
+
if (!servers || typeof servers !== "object" || Array.isArray(servers)) {
|
|
1865
|
+
return [];
|
|
1866
|
+
}
|
|
1867
|
+
return Object.keys(servers).map(prettifyServerName);
|
|
1868
|
+
}
|
|
1869
|
+
function prettifyServerName(key) {
|
|
1870
|
+
return key.split(/[-_]/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
1871
|
+
}
|
|
1872
|
+
function buildIntroduction(input4) {
|
|
1873
|
+
const { displayName, codeName, role, integrations } = input4;
|
|
1874
|
+
const spokenName = displayName || codeName;
|
|
1875
|
+
const codeSuffix = displayName && displayName !== codeName ? ` (${codeName})` : "";
|
|
1876
|
+
const roleClause = role ? `, ${role}` : "";
|
|
1877
|
+
const lines = [`I'm ${spokenName}${codeSuffix}${roleClause}.`, ""];
|
|
1878
|
+
if (integrations.length > 0) {
|
|
1879
|
+
lines.push(`Connected integrations: ${integrations.join(", ")}.`);
|
|
1880
|
+
} else {
|
|
1881
|
+
lines.push("No integrations are connected.");
|
|
1882
|
+
}
|
|
1883
|
+
return lines.join("\n");
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
// src/commands/impersonate.ts
|
|
1887
|
+
var SWAP_FILES = ["CLAUDE.md", ".mcp.json"];
|
|
1888
|
+
var AGENT_IMPERSONATION_HEADER = "X-Agent-Impersonation";
|
|
1889
|
+
var REDEEM_TOKEN_RE = /^[A-HJ-NP-Z2-9]{4}-[A-HJ-NP-Z2-9]{4}$/i;
|
|
1890
|
+
async function impersonateConnectCommand(token, options = {}) {
|
|
1891
|
+
requireImpersonateEnabled();
|
|
1892
|
+
const json = isJsonMode();
|
|
1893
|
+
const shouldLaunchClaude = !options.noLaunch && !json;
|
|
1894
|
+
if (!REDEEM_TOKEN_RE.test(token)) {
|
|
1895
|
+
const msg = "Token is not in the expected XXXX-XXXX format. Copy the full `agt impersonate connect <token>` command from the webapp.";
|
|
1896
|
+
if (json) jsonOutput({ ok: false, error: msg });
|
|
1897
|
+
else error(msg);
|
|
1898
|
+
process.exitCode = 1;
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
const existing = readActiveManifest();
|
|
1902
|
+
if (existing) {
|
|
1903
|
+
const msg = `An impersonation session is already active (${existing.code_name}). Run \`agt impersonate exit\` first.`;
|
|
1904
|
+
if (json) jsonOutput({ ok: false, error: msg });
|
|
1905
|
+
else error(msg);
|
|
1906
|
+
process.exitCode = 1;
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
if (!options.workdir) {
|
|
1910
|
+
const workTreeRoot = findGitWorkTreeRoot(process.cwd());
|
|
1911
|
+
if (workTreeRoot) {
|
|
1912
|
+
const msg = `Refusing to impersonate inside a git working tree (${workTreeRoot}). connect swaps the agent's ${SWAP_FILES.join(" + ")} into the current directory, which would overwrite the repo's own files. Re-run with \`--workdir\` to use an auto-provisioned dedicated directory, or run from a standalone empty directory.`;
|
|
1913
|
+
if (json) jsonOutput({ ok: false, error: msg });
|
|
1914
|
+
else error(msg);
|
|
1915
|
+
process.exitCode = 1;
|
|
1916
|
+
return;
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
if (!json) console.log(chalk10.bold("\nAugmented \u2014 Impersonate\n"));
|
|
1920
|
+
const spinner = ora10({
|
|
1921
|
+
text: "Redeeming token\u2026",
|
|
1922
|
+
isSilent: json
|
|
1923
|
+
});
|
|
1924
|
+
spinner.start();
|
|
1925
|
+
const host2 = getHost();
|
|
1926
|
+
let bundle;
|
|
1927
|
+
try {
|
|
1928
|
+
const res = await fetch(`${host2}/impersonate/agent/redeem`, {
|
|
1929
|
+
method: "POST",
|
|
1930
|
+
headers: { "Content-Type": "application/json" },
|
|
1931
|
+
body: JSON.stringify({ redeem_token: token })
|
|
1932
|
+
});
|
|
1933
|
+
if (!res.ok) {
|
|
1934
|
+
const body = await res.json().catch(() => ({}));
|
|
1935
|
+
const detail = body["error"] ?? `HTTP ${res.status}`;
|
|
1936
|
+
throw new Error(`${detail} (HTTP ${res.status})`);
|
|
1937
|
+
}
|
|
1938
|
+
bundle = await res.json();
|
|
1939
|
+
} catch (err) {
|
|
1940
|
+
spinner.fail("Failed to redeem token.");
|
|
1941
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1942
|
+
if (json) jsonOutput({ ok: false, error: msg });
|
|
1943
|
+
else error(msg);
|
|
1944
|
+
process.exitCode = 1;
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
spinner.text = "Writing persona files\u2026";
|
|
1948
|
+
const personaDir = ensureCodeNameDir(bundle.agent.code_name);
|
|
1949
|
+
writeFileSync6(
|
|
1950
|
+
join9(personaDir, "CLAUDE.md"),
|
|
1951
|
+
bundle.artifacts["CLAUDE.md"]
|
|
1952
|
+
);
|
|
1953
|
+
writeFileSync6(
|
|
1954
|
+
join9(personaDir, ".mcp.json"),
|
|
1955
|
+
bundle.artifacts[".mcp.json"]
|
|
1956
|
+
);
|
|
1957
|
+
spinner.text = "Swapping persona files\u2026";
|
|
1958
|
+
const projectCwd = options.workdir ? ensureImpersonateWorkdir(bundle.agent.code_name) : process.cwd();
|
|
1959
|
+
const backups = {};
|
|
1960
|
+
const swapped = [];
|
|
1961
|
+
let hookBackup;
|
|
1962
|
+
try {
|
|
1963
|
+
for (const file of SWAP_FILES) {
|
|
1964
|
+
const projectPath = join9(projectCwd, file);
|
|
1965
|
+
const kind = swapInPersonaFile(projectPath, join9(personaDir, file));
|
|
1966
|
+
backups[file] = kind;
|
|
1967
|
+
swapped.push({ file, kind });
|
|
1968
|
+
}
|
|
1969
|
+
hookBackup = registerIntroduceHook(projectCwd);
|
|
1970
|
+
const manifest = {
|
|
1971
|
+
code_name: bundle.agent.code_name,
|
|
1972
|
+
agent_id: bundle.agent.agent_id,
|
|
1973
|
+
team_id: bundle.agent.team_id,
|
|
1974
|
+
token: bundle.token,
|
|
1975
|
+
expires_at: bundle.expires_at,
|
|
1976
|
+
session_id: bundle.session_id,
|
|
1977
|
+
minted_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1978
|
+
project_cwd: projectCwd,
|
|
1979
|
+
host: host2,
|
|
1980
|
+
backups,
|
|
1981
|
+
hook: hookBackup
|
|
1982
|
+
};
|
|
1983
|
+
writeActiveManifest(manifest);
|
|
1984
|
+
} catch (err) {
|
|
1985
|
+
if (hookBackup) {
|
|
1986
|
+
try {
|
|
1987
|
+
unregisterIntroduceHook(hookBackup);
|
|
1988
|
+
} catch {
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
for (const { file, kind } of [...swapped].reverse()) {
|
|
1992
|
+
try {
|
|
1993
|
+
restoreSwapped(join9(projectCwd, file), kind);
|
|
1994
|
+
} catch {
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
spinner.fail("Failed to enter impersonation; rolled back changes.");
|
|
1998
|
+
throw err;
|
|
1999
|
+
}
|
|
2000
|
+
spinner.stop();
|
|
2001
|
+
if (json) {
|
|
2002
|
+
jsonOutput({
|
|
2003
|
+
ok: true,
|
|
2004
|
+
code_name: bundle.agent.code_name,
|
|
2005
|
+
agent_id: bundle.agent.agent_id,
|
|
2006
|
+
session_id: bundle.session_id,
|
|
2007
|
+
expires_at: bundle.expires_at,
|
|
2008
|
+
project_cwd: projectCwd,
|
|
2009
|
+
host: host2,
|
|
2010
|
+
swapped_files: SWAP_FILES
|
|
2011
|
+
});
|
|
2012
|
+
return;
|
|
2013
|
+
}
|
|
2014
|
+
success(`Impersonating ${chalk10.bold(bundle.agent.code_name)}`);
|
|
2015
|
+
console.log();
|
|
2016
|
+
console.log(` Agent ID: ${chalk10.dim(bundle.agent.agent_id)}`);
|
|
2017
|
+
console.log(` Team: ${chalk10.dim(bundle.agent.team_id)}`);
|
|
2018
|
+
console.log(` Session: ${chalk10.dim(bundle.session_id)}`);
|
|
2019
|
+
console.log(
|
|
2020
|
+
` Expires: ${chalk10.dim(new Date(bundle.expires_at * 1e3).toISOString())}`
|
|
2021
|
+
);
|
|
2022
|
+
console.log(` Project: ${chalk10.dim(projectCwd)}`);
|
|
2023
|
+
console.log(` Host: ${chalk10.dim(host2)}`);
|
|
2024
|
+
console.log(` Swapped: ${chalk10.dim(SWAP_FILES.join(", "))}`);
|
|
2025
|
+
console.log();
|
|
2026
|
+
if (!shouldLaunchClaude) {
|
|
2027
|
+
if (options.workdir) {
|
|
2028
|
+
info(`Start Claude Code in the impersonation workdir: \`cd ${projectCwd}\``);
|
|
2029
|
+
}
|
|
2030
|
+
info(
|
|
2031
|
+
"Restart Claude Code to pick up the agent's persona + MCP server set. Claude Code 2.1.152 does not honour `tools/list_changed` in-session (spike PoC, ENG-4733)."
|
|
2032
|
+
);
|
|
2033
|
+
console.log();
|
|
2034
|
+
info("When done: `agt impersonate exit`");
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
info("Launching Claude Code\u2026 (use `--no-launch` next time to skip)");
|
|
2038
|
+
console.log();
|
|
2039
|
+
await new Promise((resolve2) => {
|
|
2040
|
+
const child = spawn("claude", {
|
|
2041
|
+
stdio: "inherit",
|
|
2042
|
+
cwd: projectCwd
|
|
2043
|
+
});
|
|
2044
|
+
child.on("error", (err) => {
|
|
2045
|
+
if (err.code === "ENOENT") {
|
|
2046
|
+
error(
|
|
2047
|
+
`\`claude\` binary not found on PATH. Start Claude Code yourself from ${projectCwd}, or install the CLI from claude.ai/code.`
|
|
2048
|
+
);
|
|
2049
|
+
process.exitCode = 1;
|
|
2050
|
+
} else {
|
|
2051
|
+
error(`Failed to launch Claude Code: ${err.message}`);
|
|
2052
|
+
process.exitCode = 1;
|
|
2053
|
+
}
|
|
2054
|
+
resolve2();
|
|
2055
|
+
});
|
|
2056
|
+
child.on("exit", (code, signal) => {
|
|
2057
|
+
if (code !== null && code !== 0) process.exitCode = code;
|
|
2058
|
+
if (signal) process.exitCode = 130;
|
|
2059
|
+
resolve2();
|
|
2060
|
+
});
|
|
2061
|
+
});
|
|
2062
|
+
console.log();
|
|
2063
|
+
info("When done: `agt impersonate exit` (still active).");
|
|
2064
|
+
}
|
|
2065
|
+
async function impersonateCurrentCommand() {
|
|
2066
|
+
requireImpersonateEnabled();
|
|
2067
|
+
const json = isJsonMode();
|
|
2068
|
+
const manifest = readActiveManifest();
|
|
2069
|
+
if (!manifest) {
|
|
2070
|
+
if (json) jsonOutput({ ok: true, active: null });
|
|
2071
|
+
else info("No active impersonation.");
|
|
2072
|
+
return;
|
|
2073
|
+
}
|
|
2074
|
+
const expired = isExpired(manifest);
|
|
2075
|
+
if (json) {
|
|
2076
|
+
jsonOutput({
|
|
2077
|
+
ok: true,
|
|
2078
|
+
active: {
|
|
2079
|
+
code_name: manifest.code_name,
|
|
2080
|
+
agent_id: manifest.agent_id,
|
|
2081
|
+
team_id: manifest.team_id,
|
|
2082
|
+
session_id: manifest.session_id,
|
|
2083
|
+
minted_at: manifest.minted_at,
|
|
2084
|
+
expires_at: manifest.expires_at,
|
|
2085
|
+
expired,
|
|
2086
|
+
project_cwd: manifest.project_cwd,
|
|
2087
|
+
host: manifest.host
|
|
2088
|
+
}
|
|
2089
|
+
});
|
|
2090
|
+
return;
|
|
2091
|
+
}
|
|
2092
|
+
console.log();
|
|
2093
|
+
console.log(chalk10.bold(`Impersonating: ${manifest.code_name}`));
|
|
2094
|
+
console.log(` Agent ID: ${chalk10.dim(manifest.agent_id)}`);
|
|
2095
|
+
console.log(` Team: ${chalk10.dim(manifest.team_id)}`);
|
|
2096
|
+
console.log(` Session: ${chalk10.dim(manifest.session_id)}`);
|
|
2097
|
+
console.log(` Minted at: ${chalk10.dim(manifest.minted_at)}`);
|
|
2098
|
+
console.log(
|
|
2099
|
+
` Expires at: ${chalk10.dim(new Date(manifest.expires_at * 1e3).toISOString())} ` + (expired ? chalk10.red("(EXPIRED \u2014 run `agt impersonate exit`)") : "")
|
|
2100
|
+
);
|
|
2101
|
+
console.log(` Project: ${chalk10.dim(manifest.project_cwd)}`);
|
|
2102
|
+
console.log(` Host: ${chalk10.dim(manifest.host)}`);
|
|
2103
|
+
console.log();
|
|
2104
|
+
}
|
|
2105
|
+
async function impersonateExitCommand() {
|
|
2106
|
+
requireImpersonateEnabled();
|
|
2107
|
+
const json = isJsonMode();
|
|
2108
|
+
const manifest = readActiveManifest();
|
|
2109
|
+
if (!manifest) {
|
|
2110
|
+
if (json) jsonOutput({ ok: true, was_active: false });
|
|
2111
|
+
else info("No active impersonation to exit.");
|
|
2112
|
+
return;
|
|
2113
|
+
}
|
|
2114
|
+
let stopOk = true;
|
|
2115
|
+
let stopDetail = null;
|
|
2116
|
+
try {
|
|
2117
|
+
if (process.env["AGT_API_KEY"]) {
|
|
2118
|
+
await api.post("/impersonate/agent/stop", void 0, {
|
|
2119
|
+
[AGENT_IMPERSONATION_HEADER]: manifest.token
|
|
2120
|
+
});
|
|
2121
|
+
} else {
|
|
2122
|
+
const res = await fetch(`${manifest.host}/impersonate/agent/stop`, {
|
|
2123
|
+
method: "POST",
|
|
2124
|
+
headers: {
|
|
2125
|
+
"Content-Type": "application/json",
|
|
2126
|
+
[AGENT_IMPERSONATION_HEADER]: manifest.token
|
|
2127
|
+
}
|
|
2128
|
+
});
|
|
2129
|
+
if (!res.ok) {
|
|
2130
|
+
const body = await res.json().catch(() => ({}));
|
|
2131
|
+
const detail = body["error"] ?? `HTTP ${res.status}`;
|
|
2132
|
+
stopOk = false;
|
|
2133
|
+
stopDetail = `${detail} (HTTP ${res.status})`;
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
} catch (err) {
|
|
2137
|
+
stopOk = false;
|
|
2138
|
+
stopDetail = err instanceof ApiError ? `${err.message} (HTTP ${err.status})` : err instanceof Error ? err.message : String(err);
|
|
2139
|
+
}
|
|
2140
|
+
const restored = [];
|
|
2141
|
+
for (const [file, kind] of Object.entries(manifest.backups)) {
|
|
2142
|
+
const projectPath = join9(manifest.project_cwd, file);
|
|
2143
|
+
try {
|
|
2144
|
+
restoreSwapped(projectPath, kind);
|
|
2145
|
+
restored.push(file);
|
|
2146
|
+
} catch (err) {
|
|
2147
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2148
|
+
if (!json) error(`Failed to restore ${file}: ${msg}`);
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
if (manifest.hook) {
|
|
2152
|
+
try {
|
|
2153
|
+
unregisterIntroduceHook(manifest.hook);
|
|
2154
|
+
} catch (err) {
|
|
2155
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2156
|
+
if (!json) error(`Failed to remove introduce hook: ${msg}`);
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
clearActiveManifest();
|
|
2160
|
+
if (json) {
|
|
2161
|
+
jsonOutput({
|
|
2162
|
+
ok: true,
|
|
2163
|
+
was_active: true,
|
|
2164
|
+
session_id: manifest.session_id,
|
|
2165
|
+
stop_acknowledged: stopOk,
|
|
2166
|
+
stop_detail: stopDetail,
|
|
2167
|
+
restored
|
|
2168
|
+
});
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
success(`Exited impersonation: ${manifest.code_name}`);
|
|
2172
|
+
if (restored.length > 0) {
|
|
2173
|
+
info(`Restored: ${restored.join(", ")}`);
|
|
2174
|
+
}
|
|
2175
|
+
if (!stopOk) {
|
|
2176
|
+
info(`Note: server-side stop not acknowledged (${stopDetail}). Token will expire naturally.`);
|
|
2177
|
+
}
|
|
2178
|
+
info("Restart Claude Code to pick up your original persona.");
|
|
2179
|
+
}
|
|
2180
|
+
async function impersonateIntroduceCommand() {
|
|
2181
|
+
try {
|
|
2182
|
+
const manifest = readActiveManifest();
|
|
2183
|
+
if (!manifest || isExpired(manifest)) return;
|
|
2184
|
+
const cwd = manifest.project_cwd;
|
|
2185
|
+
const identity = readClaudeMdIdentity(join9(cwd, "CLAUDE.md"));
|
|
2186
|
+
const integrations = readMcpIntegrations(join9(cwd, ".mcp.json"));
|
|
2187
|
+
const intro = buildIntroduction({
|
|
2188
|
+
displayName: identity.displayName,
|
|
2189
|
+
codeName: manifest.code_name,
|
|
2190
|
+
role: identity.role,
|
|
2191
|
+
integrations
|
|
2192
|
+
});
|
|
2193
|
+
process.stdout.write(intro + "\n");
|
|
2194
|
+
} catch {
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
function readClaudeMdIdentity(path) {
|
|
2198
|
+
try {
|
|
2199
|
+
return parseIdentityFromClaudeMd(readFileSync4(path, "utf-8"));
|
|
2200
|
+
} catch {
|
|
2201
|
+
return { displayName: null, role: null };
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
function readMcpIntegrations(path) {
|
|
2205
|
+
try {
|
|
2206
|
+
return parseIntegrationsFromMcpJson(readFileSync4(path, "utf-8"));
|
|
2207
|
+
} catch {
|
|
2208
|
+
return [];
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
// src/commands/drift.ts
|
|
2213
|
+
import chalk11 from "chalk";
|
|
2214
|
+
import ora11 from "ora";
|
|
1558
2215
|
|
|
1559
2216
|
// ../../packages/core/dist/drift/live-state-reader.js
|
|
1560
2217
|
import { readFile } from "fs/promises";
|
|
1561
2218
|
import { createHash } from "crypto";
|
|
1562
|
-
import { join as
|
|
2219
|
+
import { join as join10 } from "path";
|
|
1563
2220
|
import JSON5 from "json5";
|
|
1564
2221
|
async function hashFile(filePath) {
|
|
1565
2222
|
try {
|
|
@@ -1583,8 +2240,8 @@ async function readLiveState(options) {
|
|
|
1583
2240
|
const toolsFile = trackedFiles.find((f) => f.includes("TOOLS")) ?? "TOOLS.md";
|
|
1584
2241
|
const [frameworkConfig, charterHash, toolsHash] = await Promise.all([
|
|
1585
2242
|
readJsonFile(options.configPath),
|
|
1586
|
-
hashFile(
|
|
1587
|
-
hashFile(
|
|
2243
|
+
hashFile(join10(options.teamDir, charterFile)),
|
|
2244
|
+
hashFile(join10(options.teamDir, toolsFile))
|
|
1588
2245
|
]);
|
|
1589
2246
|
return {
|
|
1590
2247
|
frameworkConfig,
|
|
@@ -1599,15 +2256,15 @@ var KEBAB_CASE_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
|
|
|
1599
2256
|
function severityColor(severity) {
|
|
1600
2257
|
switch (severity) {
|
|
1601
2258
|
case "critical":
|
|
1602
|
-
return
|
|
2259
|
+
return chalk11.red(severity);
|
|
1603
2260
|
case "warning":
|
|
1604
|
-
return
|
|
2261
|
+
return chalk11.yellow(severity);
|
|
1605
2262
|
default:
|
|
1606
|
-
return
|
|
2263
|
+
return chalk11.dim(severity);
|
|
1607
2264
|
}
|
|
1608
2265
|
}
|
|
1609
2266
|
function printDriftReport(report) {
|
|
1610
|
-
console.log(
|
|
2267
|
+
console.log(chalk11.bold(`
|
|
1611
2268
|
Drift Report: ${report.codeName}
|
|
1612
2269
|
`));
|
|
1613
2270
|
info(`Agent ID: ${report.agentId}`);
|
|
@@ -1636,7 +2293,7 @@ async function driftCheckCommand(codeName, opts) {
|
|
|
1636
2293
|
return;
|
|
1637
2294
|
}
|
|
1638
2295
|
const useJson = opts.json || isJsonMode();
|
|
1639
|
-
const spinner =
|
|
2296
|
+
const spinner = ora11({ text: `Checking drift for "${codeName}"\u2026`, isSilent: useJson });
|
|
1640
2297
|
spinner.start();
|
|
1641
2298
|
try {
|
|
1642
2299
|
const driftData = await api.get(`/agents/${encodeURIComponent(codeName)}/drift-data`);
|
|
@@ -1718,14 +2375,14 @@ async function driftWatchCommand(codeName, opts) {
|
|
|
1718
2375
|
const trackedFiles = adapter.driftTrackedFiles();
|
|
1719
2376
|
if (!useJson) {
|
|
1720
2377
|
console.log(
|
|
1721
|
-
|
|
2378
|
+
chalk11.bold(`
|
|
1722
2379
|
Watching drift for "${codeName}" every ${intervalSec}s. Press Ctrl+C to stop.
|
|
1723
2380
|
`)
|
|
1724
2381
|
);
|
|
1725
2382
|
}
|
|
1726
2383
|
let previousFindingCount = -1;
|
|
1727
2384
|
const runCheck = async () => {
|
|
1728
|
-
const spinner =
|
|
2385
|
+
const spinner = ora11({ text: `Checking drift for "${codeName}"\u2026`, isSilent: useJson });
|
|
1729
2386
|
spinner.start();
|
|
1730
2387
|
try {
|
|
1731
2388
|
const driftData = await api.get(`/agents/${encodeURIComponent(codeName)}/drift-data`);
|
|
@@ -1789,19 +2446,19 @@ Watching drift for "${codeName}" every ${intervalSec}s. Press Ctrl+C to stop.
|
|
|
1789
2446
|
}, intervalSec * 1e3);
|
|
1790
2447
|
process.on("SIGINT", () => {
|
|
1791
2448
|
clearInterval(timer);
|
|
1792
|
-
if (!useJson) console.log(
|
|
2449
|
+
if (!useJson) console.log(chalk11.dim("\nStopped watching."));
|
|
1793
2450
|
process.exit(0);
|
|
1794
2451
|
});
|
|
1795
2452
|
}
|
|
1796
2453
|
|
|
1797
2454
|
// src/commands/host.ts
|
|
1798
|
-
import
|
|
1799
|
-
import
|
|
2455
|
+
import chalk12 from "chalk";
|
|
2456
|
+
import ora12 from "ora";
|
|
1800
2457
|
async function hostListCommand() {
|
|
1801
2458
|
const teamSlug = requireTeam();
|
|
1802
2459
|
if (!teamSlug) return;
|
|
1803
2460
|
const json = isJsonMode();
|
|
1804
|
-
const spinner =
|
|
2461
|
+
const spinner = ora12({ text: "Fetching hosts\u2026", isSilent: json });
|
|
1805
2462
|
spinner.start();
|
|
1806
2463
|
try {
|
|
1807
2464
|
const data = await api.get("/hosts");
|
|
@@ -1819,10 +2476,10 @@ async function hostListCommand() {
|
|
|
1819
2476
|
return;
|
|
1820
2477
|
}
|
|
1821
2478
|
const rows = data.hosts.map((h) => {
|
|
1822
|
-
const status = h.status === "active" ?
|
|
2479
|
+
const status = h.status === "active" ? chalk12.green("active") : chalk12.red("decommissioned");
|
|
1823
2480
|
const agents = String(h.agents);
|
|
1824
|
-
const lastSeen = h.last_seen_at ? new Date(h.last_seen_at).toLocaleDateString() :
|
|
1825
|
-
const prefix = h.key_prefix ? `tlk_${h.key_prefix}\u2026` :
|
|
2481
|
+
const lastSeen = h.last_seen_at ? new Date(h.last_seen_at).toLocaleDateString() : chalk12.dim("never");
|
|
2482
|
+
const prefix = h.key_prefix ? `tlk_${h.key_prefix}\u2026` : chalk12.dim("none");
|
|
1826
2483
|
return [h.name, status, agents, lastSeen, prefix];
|
|
1827
2484
|
});
|
|
1828
2485
|
table(["Name", "Status", "Agents", "Last Seen", "Key"], rows);
|
|
@@ -1849,14 +2506,14 @@ async function hostAssignCommand(hostName, agentCodeNames, opts) {
|
|
|
1849
2506
|
process.exitCode = 1;
|
|
1850
2507
|
return;
|
|
1851
2508
|
}
|
|
1852
|
-
const spinner =
|
|
2509
|
+
const spinner = ora12({ text: "Assigning agents\u2026", isSilent: json });
|
|
1853
2510
|
spinner.start();
|
|
1854
2511
|
try {
|
|
1855
2512
|
await api.post(`/hosts/${encodeURIComponent(hostName)}/assign`, {
|
|
1856
2513
|
agents: agentCodeNames,
|
|
1857
2514
|
force: opts.force
|
|
1858
2515
|
});
|
|
1859
|
-
spinner.succeed(`Agents assigned to ${
|
|
2516
|
+
spinner.succeed(`Agents assigned to ${chalk12.bold(hostName)}.`);
|
|
1860
2517
|
if (json) {
|
|
1861
2518
|
jsonOutput({ ok: true, host: hostName, assigned: agentCodeNames });
|
|
1862
2519
|
return;
|
|
@@ -1887,13 +2544,13 @@ async function hostUnassignCommand(hostName, agentCodeNames) {
|
|
|
1887
2544
|
process.exitCode = 1;
|
|
1888
2545
|
return;
|
|
1889
2546
|
}
|
|
1890
|
-
const spinner =
|
|
2547
|
+
const spinner = ora12({ text: "Unassigning agents\u2026", isSilent: json });
|
|
1891
2548
|
spinner.start();
|
|
1892
2549
|
try {
|
|
1893
2550
|
await api.post(`/hosts/${encodeURIComponent(hostName)}/unassign`, {
|
|
1894
2551
|
agents: agentCodeNames
|
|
1895
2552
|
});
|
|
1896
|
-
spinner.succeed(`Agents unassigned from ${
|
|
2553
|
+
spinner.succeed(`Agents unassigned from ${chalk12.bold(hostName)}.`);
|
|
1897
2554
|
if (json) {
|
|
1898
2555
|
jsonOutput({ ok: true, host: hostName, unassigned: agentCodeNames });
|
|
1899
2556
|
return;
|
|
@@ -1913,7 +2570,7 @@ async function hostUnassignCommand(hostName, agentCodeNames) {
|
|
|
1913
2570
|
}
|
|
1914
2571
|
async function hostAgentsCommand(hostName) {
|
|
1915
2572
|
const json = isJsonMode();
|
|
1916
|
-
const spinner =
|
|
2573
|
+
const spinner = ora12({ text: "Fetching host agents\u2026", isSilent: json });
|
|
1917
2574
|
spinner.start();
|
|
1918
2575
|
try {
|
|
1919
2576
|
const hostId = await getHostId();
|
|
@@ -1952,7 +2609,7 @@ async function hostAgentsCommand(hostName) {
|
|
|
1952
2609
|
return;
|
|
1953
2610
|
}
|
|
1954
2611
|
const rows = agents.map((a) => {
|
|
1955
|
-
const statusColor = a.status === "active" ?
|
|
2612
|
+
const statusColor = a.status === "active" ? chalk12.green : chalk12.yellow;
|
|
1956
2613
|
return [
|
|
1957
2614
|
a.code_name,
|
|
1958
2615
|
a.display_name,
|
|
@@ -1975,11 +2632,11 @@ async function hostRotateKeyCommand(hostName) {
|
|
|
1975
2632
|
const teamSlug = requireTeam();
|
|
1976
2633
|
if (!teamSlug) return;
|
|
1977
2634
|
const json = isJsonMode();
|
|
1978
|
-
const spinner =
|
|
2635
|
+
const spinner = ora12({ text: "Rotating host key\u2026", isSilent: json });
|
|
1979
2636
|
spinner.start();
|
|
1980
2637
|
try {
|
|
1981
2638
|
const data = await api.post(`/hosts/${encodeURIComponent(hostName)}/rotate-key`);
|
|
1982
|
-
spinner.succeed(`Key rotated for ${
|
|
2639
|
+
spinner.succeed(`Key rotated for ${chalk12.bold(hostName)}.`);
|
|
1983
2640
|
if (json) {
|
|
1984
2641
|
jsonOutput({
|
|
1985
2642
|
ok: true,
|
|
@@ -1989,12 +2646,12 @@ async function hostRotateKeyCommand(hostName) {
|
|
|
1989
2646
|
return;
|
|
1990
2647
|
}
|
|
1991
2648
|
console.log();
|
|
1992
|
-
info(`Host: ${
|
|
2649
|
+
info(`Host: ${chalk12.bold(hostName)}`);
|
|
1993
2650
|
info(`Prefix: ${data.key.prefix}`);
|
|
1994
2651
|
console.log();
|
|
1995
|
-
console.log(
|
|
2652
|
+
console.log(chalk12.yellow.bold(" Save this key now \u2014 it will not be shown again:"));
|
|
1996
2653
|
console.log();
|
|
1997
|
-
console.log(` ${
|
|
2654
|
+
console.log(` ${chalk12.green.bold(data.key.raw_key)}`);
|
|
1998
2655
|
console.log();
|
|
1999
2656
|
} catch (err) {
|
|
2000
2657
|
spinner.fail("Failed to rotate key.");
|
|
@@ -2010,11 +2667,11 @@ async function hostDecommissionCommand(hostName) {
|
|
|
2010
2667
|
const teamSlug = requireTeam();
|
|
2011
2668
|
if (!teamSlug) return;
|
|
2012
2669
|
const json = isJsonMode();
|
|
2013
|
-
const spinner =
|
|
2670
|
+
const spinner = ora12({ text: "Decommissioning host\u2026", isSilent: json });
|
|
2014
2671
|
spinner.start();
|
|
2015
2672
|
try {
|
|
2016
2673
|
await api.post(`/hosts/${encodeURIComponent(hostName)}/decommission`);
|
|
2017
|
-
spinner.succeed(`Host "${
|
|
2674
|
+
spinner.succeed(`Host "${chalk12.bold(hostName)}" decommissioned.`);
|
|
2018
2675
|
if (json) {
|
|
2019
2676
|
jsonOutput({
|
|
2020
2677
|
ok: true,
|
|
@@ -2024,7 +2681,7 @@ async function hostDecommissionCommand(hostName) {
|
|
|
2024
2681
|
return;
|
|
2025
2682
|
}
|
|
2026
2683
|
info(`Host: ${hostName}`);
|
|
2027
|
-
info(`Status: ${
|
|
2684
|
+
info(`Status: ${chalk12.red("decommissioned")}`);
|
|
2028
2685
|
info("API key revoked. Agents remain assigned for audit visibility.");
|
|
2029
2686
|
info("Reassign agents to another host with `agt host assign --force`.");
|
|
2030
2687
|
} catch (err) {
|
|
@@ -2039,8 +2696,8 @@ async function hostDecommissionCommand(hostName) {
|
|
|
2039
2696
|
}
|
|
2040
2697
|
|
|
2041
2698
|
// src/commands/host-pair.ts
|
|
2042
|
-
import { spawn, spawnSync } from "child_process";
|
|
2043
|
-
import
|
|
2699
|
+
import { spawn as spawn2, spawnSync } from "child_process";
|
|
2700
|
+
import chalk13 from "chalk";
|
|
2044
2701
|
async function hostPairCommand(name, opts) {
|
|
2045
2702
|
const teamSlug = requireTeam();
|
|
2046
2703
|
if (!teamSlug) return;
|
|
@@ -2110,17 +2767,17 @@ async function hostPairCommand(name, opts) {
|
|
|
2110
2767
|
});
|
|
2111
2768
|
return;
|
|
2112
2769
|
}
|
|
2113
|
-
info(`Pairing Claude Code on ${
|
|
2114
|
-
info(`Local port ${
|
|
2770
|
+
info(`Pairing Claude Code on ${chalk13.bold(name)} (${instanceId}, ${region})`);
|
|
2771
|
+
info(`Local port ${chalk13.cyan(String(localPort))} will be forwarded to the host.`);
|
|
2115
2772
|
console.log();
|
|
2116
|
-
console.log(
|
|
2117
|
-
console.log(` ${
|
|
2118
|
-
console.log(
|
|
2119
|
-
console.log(
|
|
2773
|
+
console.log(chalk13.dim("In the shell that opens, run:"));
|
|
2774
|
+
console.log(` ${chalk13.cyan(`CLAUDE_CODE_OAUTH_PORT=${localPort} claude /login`)}`);
|
|
2775
|
+
console.log(chalk13.dim("Then click the printed URL on this machine. The OAuth callback"));
|
|
2776
|
+
console.log(chalk13.dim(`will reach Claude Code via the port-forward tunnel.`));
|
|
2120
2777
|
console.log();
|
|
2121
|
-
console.log(
|
|
2778
|
+
console.log(chalk13.dim("Exit the shell (Ctrl+D) when Claude Code reports successful login."));
|
|
2122
2779
|
console.log();
|
|
2123
|
-
const tunnel =
|
|
2780
|
+
const tunnel = spawn2(
|
|
2124
2781
|
"aws",
|
|
2125
2782
|
[
|
|
2126
2783
|
"ssm",
|
|
@@ -2139,7 +2796,7 @@ async function hostPairCommand(name, opts) {
|
|
|
2139
2796
|
tunnel.stderr?.on("data", (buf) => {
|
|
2140
2797
|
const line = buf.toString().trim();
|
|
2141
2798
|
if (line.startsWith("An error occurred")) {
|
|
2142
|
-
console.error(
|
|
2799
|
+
console.error(chalk13.red(`[tunnel] ${line}`));
|
|
2143
2800
|
}
|
|
2144
2801
|
});
|
|
2145
2802
|
await new Promise((r) => setTimeout(r, 1500));
|
|
@@ -2149,7 +2806,7 @@ async function hostPairCommand(name, opts) {
|
|
|
2149
2806
|
return;
|
|
2150
2807
|
}
|
|
2151
2808
|
if (opts.noShell) {
|
|
2152
|
-
console.log(
|
|
2809
|
+
console.log(chalk13.dim("--no-shell set; tunnel is running. Press Ctrl+C to exit."));
|
|
2153
2810
|
await new Promise((resolve2) => {
|
|
2154
2811
|
const onSignal = () => {
|
|
2155
2812
|
terminate(tunnel);
|
|
@@ -2177,7 +2834,7 @@ function preflightAws() {
|
|
|
2177
2834
|
}
|
|
2178
2835
|
function runInteractive(cmd, args) {
|
|
2179
2836
|
return new Promise((resolve2) => {
|
|
2180
|
-
const child =
|
|
2837
|
+
const child = spawn2(cmd, args, { stdio: "inherit" });
|
|
2181
2838
|
child.on("exit", (code) => resolve2(code ?? 0));
|
|
2182
2839
|
child.on("error", () => resolve2(1));
|
|
2183
2840
|
});
|
|
@@ -2193,15 +2850,15 @@ function terminate(child) {
|
|
|
2193
2850
|
// src/commands/manager-watch.tsx
|
|
2194
2851
|
import { useEffect, useState, useMemo } from "react";
|
|
2195
2852
|
import { render, Box, Text, useApp, useInput } from "ink";
|
|
2196
|
-
import { existsSync as
|
|
2197
|
-
import { homedir } from "os";
|
|
2198
|
-
import { join as
|
|
2853
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, statSync, openSync, readSync, closeSync } from "fs";
|
|
2854
|
+
import { homedir as homedir3 } from "os";
|
|
2855
|
+
import { join as join11 } from "path";
|
|
2199
2856
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2200
2857
|
var REFRESH_MS = 1e3;
|
|
2201
2858
|
var LOG_TAIL_LINES = 200;
|
|
2202
2859
|
var DETAIL_RECENT_LINES = 50;
|
|
2203
2860
|
function managerWatchCommand(opts = {}) {
|
|
2204
|
-
const configDir = opts.configDir ??
|
|
2861
|
+
const configDir = opts.configDir ?? join11(homedir3(), ".augmented");
|
|
2205
2862
|
const paths = getManagerPaths(configDir);
|
|
2206
2863
|
const isTty = process.stdout.isTTY === true && process.stdin.isTTY === true;
|
|
2207
2864
|
if (opts.noTui || !isTty) {
|
|
@@ -2225,15 +2882,15 @@ function managerWatchCommand(opts = {}) {
|
|
|
2225
2882
|
}
|
|
2226
2883
|
function readState(stateFile) {
|
|
2227
2884
|
try {
|
|
2228
|
-
if (!
|
|
2229
|
-
const raw =
|
|
2885
|
+
if (!existsSync5(stateFile)) return null;
|
|
2886
|
+
const raw = readFileSync5(stateFile, "utf-8");
|
|
2230
2887
|
return JSON.parse(raw);
|
|
2231
2888
|
} catch {
|
|
2232
2889
|
return null;
|
|
2233
2890
|
}
|
|
2234
2891
|
}
|
|
2235
2892
|
function tailLogFile(logFile, lines) {
|
|
2236
|
-
if (!
|
|
2893
|
+
if (!existsSync5(logFile)) return [];
|
|
2237
2894
|
try {
|
|
2238
2895
|
const fileSize = statSync(logFile).size;
|
|
2239
2896
|
if (fileSize === 0) return [];
|
|
@@ -2474,7 +3131,7 @@ function truncate(s, max) {
|
|
|
2474
3131
|
return s.length > max ? `${s.slice(0, Math.max(0, max - 1))}\u2026` : s;
|
|
2475
3132
|
}
|
|
2476
3133
|
function streamLogFile(logFile) {
|
|
2477
|
-
if (!
|
|
3134
|
+
if (!existsSync5(logFile)) {
|
|
2478
3135
|
process.stderr.write(`No manager log found at ${logFile}.
|
|
2479
3136
|
Start the manager first: agt manager start
|
|
2480
3137
|
`);
|
|
@@ -2483,7 +3140,7 @@ Start the manager first: agt manager start
|
|
|
2483
3140
|
}
|
|
2484
3141
|
let lastSize = 0;
|
|
2485
3142
|
try {
|
|
2486
|
-
const initial =
|
|
3143
|
+
const initial = readFileSync5(logFile, "utf-8");
|
|
2487
3144
|
process.stdout.write(initial);
|
|
2488
3145
|
lastSize = statSync(logFile).size;
|
|
2489
3146
|
} catch (err) {
|
|
@@ -2518,16 +3175,16 @@ Start the manager first: agt manager start
|
|
|
2518
3175
|
}
|
|
2519
3176
|
|
|
2520
3177
|
// src/commands/agent.ts
|
|
2521
|
-
import
|
|
3178
|
+
import chalk14 from "chalk";
|
|
2522
3179
|
import JSON52 from "json5";
|
|
2523
|
-
import { readFileSync as
|
|
2524
|
-
import { join as
|
|
3180
|
+
import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
|
|
3181
|
+
import { join as join12 } from "path";
|
|
2525
3182
|
async function agentShowCommand(codeName, opts) {
|
|
2526
3183
|
const json = isJsonMode();
|
|
2527
|
-
const unifiedDir =
|
|
2528
|
-
const legacyDir =
|
|
2529
|
-
const agentDir =
|
|
2530
|
-
const hasLocalConfig =
|
|
3184
|
+
const unifiedDir = join12(opts.configDir, codeName, "provision");
|
|
3185
|
+
const legacyDir = join12(opts.configDir, codeName, "claudecode", "provision");
|
|
3186
|
+
const agentDir = existsSync6(unifiedDir) ? unifiedDir : legacyDir;
|
|
3187
|
+
const hasLocalConfig = existsSync6(agentDir);
|
|
2531
3188
|
let apiChannels = null;
|
|
2532
3189
|
let apiAgent = null;
|
|
2533
3190
|
if (getApiKey()) {
|
|
@@ -2560,34 +3217,34 @@ async function agentShowCommand(codeName, opts) {
|
|
|
2560
3217
|
let openclawConfig = null;
|
|
2561
3218
|
let agentState = null;
|
|
2562
3219
|
if (hasLocalConfig) {
|
|
2563
|
-
const charterPath =
|
|
2564
|
-
if (
|
|
2565
|
-
const raw =
|
|
3220
|
+
const charterPath = join12(agentDir, "CHARTER.md");
|
|
3221
|
+
if (existsSync6(charterPath)) {
|
|
3222
|
+
const raw = readFileSync6(charterPath, "utf-8");
|
|
2566
3223
|
const parsed = extractFrontmatter(raw);
|
|
2567
3224
|
if (parsed.frontmatter) {
|
|
2568
3225
|
charter = parsed.frontmatter;
|
|
2569
3226
|
}
|
|
2570
3227
|
}
|
|
2571
|
-
const toolsPath =
|
|
2572
|
-
if (
|
|
2573
|
-
const raw =
|
|
3228
|
+
const toolsPath = join12(agentDir, "TOOLS.md");
|
|
3229
|
+
if (existsSync6(toolsPath)) {
|
|
3230
|
+
const raw = readFileSync6(toolsPath, "utf-8");
|
|
2574
3231
|
const parsed = extractFrontmatter(raw);
|
|
2575
3232
|
if (parsed.frontmatter) {
|
|
2576
3233
|
tools = parsed.frontmatter;
|
|
2577
3234
|
}
|
|
2578
3235
|
}
|
|
2579
|
-
const openclawPath =
|
|
2580
|
-
if (
|
|
3236
|
+
const openclawPath = join12(agentDir, "openclaw.json5");
|
|
3237
|
+
if (existsSync6(openclawPath)) {
|
|
2581
3238
|
try {
|
|
2582
|
-
const raw =
|
|
3239
|
+
const raw = readFileSync6(openclawPath, "utf-8");
|
|
2583
3240
|
openclawConfig = JSON52.parse(raw);
|
|
2584
3241
|
} catch {
|
|
2585
3242
|
}
|
|
2586
3243
|
}
|
|
2587
|
-
const statePath =
|
|
2588
|
-
if (
|
|
3244
|
+
const statePath = join12(opts.configDir, "manager-state.json");
|
|
3245
|
+
if (existsSync6(statePath)) {
|
|
2589
3246
|
try {
|
|
2590
|
-
const raw =
|
|
3247
|
+
const raw = readFileSync6(statePath, "utf-8");
|
|
2591
3248
|
const state = JSON.parse(raw);
|
|
2592
3249
|
agentState = state.agents?.find((a) => a.codeName === codeName) ?? null;
|
|
2593
3250
|
} catch {
|
|
@@ -2654,7 +3311,7 @@ async function agentShowCommand(codeName, opts) {
|
|
|
2654
3311
|
return;
|
|
2655
3312
|
}
|
|
2656
3313
|
const displayName = apiAgent?.display_name ?? charter?.display_name ?? codeName;
|
|
2657
|
-
console.log(
|
|
3314
|
+
console.log(chalk14.bold(`
|
|
2658
3315
|
Agent: ${codeName}`) + (displayName !== codeName ? ` (${displayName})` : "") + "\n");
|
|
2659
3316
|
if (charter || apiAgent) {
|
|
2660
3317
|
const agentId = apiAgent?.agent_id ?? charter?.agent_id;
|
|
@@ -2686,7 +3343,7 @@ Agent: ${codeName}`) + (displayName !== codeName ? ` (${displayName})` : "") + "
|
|
|
2686
3343
|
info("No agent metadata available");
|
|
2687
3344
|
}
|
|
2688
3345
|
const filtered = opts.allChannels ? channelRows : channelRows.filter((c) => c.enabled);
|
|
2689
|
-
console.log(
|
|
3346
|
+
console.log(chalk14.bold("\nChannels:"));
|
|
2690
3347
|
if (filtered.length === 0) {
|
|
2691
3348
|
info("(none enabled)");
|
|
2692
3349
|
} else {
|
|
@@ -2702,7 +3359,7 @@ Agent: ${codeName}`) + (displayName !== codeName ? ` (${displayName})` : "") + "
|
|
|
2702
3359
|
);
|
|
2703
3360
|
}
|
|
2704
3361
|
if (tools) {
|
|
2705
|
-
console.log(
|
|
3362
|
+
console.log(chalk14.bold("\nTools:"));
|
|
2706
3363
|
if (tools.tools.length === 0) {
|
|
2707
3364
|
info("(none configured)");
|
|
2708
3365
|
} else {
|
|
@@ -2713,7 +3370,7 @@ Agent: ${codeName}`) + (displayName !== codeName ? ` (${displayName})` : "") + "
|
|
|
2713
3370
|
}
|
|
2714
3371
|
if (tools.global_controls) {
|
|
2715
3372
|
const gc = tools.global_controls;
|
|
2716
|
-
console.log(
|
|
3373
|
+
console.log(chalk14.bold("\nGlobal Controls:"));
|
|
2717
3374
|
info(`Network: ${gc.default_network_policy === "deny" ? "deny-by-default" : "allow-by-default"}`);
|
|
2718
3375
|
info(`Timeout: ${gc.default_timeout_ms}ms`);
|
|
2719
3376
|
info(`Rate: ${gc.default_rate_limit_rpm} rpm`);
|
|
@@ -2729,8 +3386,8 @@ function formatTimestamp(iso) {
|
|
|
2729
3386
|
}
|
|
2730
3387
|
|
|
2731
3388
|
// src/commands/kanban-recurring.ts
|
|
2732
|
-
import
|
|
2733
|
-
import
|
|
3389
|
+
import chalk15 from "chalk";
|
|
3390
|
+
import ora13 from "ora";
|
|
2734
3391
|
async function resolveAgentId(codeName) {
|
|
2735
3392
|
try {
|
|
2736
3393
|
const data = await api.get(`/agents/${codeName}`);
|
|
@@ -2741,10 +3398,10 @@ async function resolveAgentId(codeName) {
|
|
|
2741
3398
|
}
|
|
2742
3399
|
}
|
|
2743
3400
|
function priorityLabel(p) {
|
|
2744
|
-
return p === 1 ?
|
|
3401
|
+
return p === 1 ? chalk15.red("HIGH") : p === 3 ? chalk15.dim("LOW") : chalk15.yellow("MED");
|
|
2745
3402
|
}
|
|
2746
3403
|
function formatSpawnTime(isoDate, timezone) {
|
|
2747
|
-
if (!isoDate) return
|
|
3404
|
+
if (!isoDate) return chalk15.dim("\u2014");
|
|
2748
3405
|
try {
|
|
2749
3406
|
return new Intl.DateTimeFormat("en-AU", {
|
|
2750
3407
|
dateStyle: "medium",
|
|
@@ -2785,7 +3442,7 @@ async function kanbanRecurringAddCommand(title, opts) {
|
|
|
2785
3442
|
return;
|
|
2786
3443
|
}
|
|
2787
3444
|
}
|
|
2788
|
-
const spinner =
|
|
3445
|
+
const spinner = ora13({ text: "Creating recurring template\u2026", isSilent: json });
|
|
2789
3446
|
spinner.start();
|
|
2790
3447
|
const agentId = await resolveAgentId(opts.agent);
|
|
2791
3448
|
if (!agentId) {
|
|
@@ -2809,8 +3466,8 @@ async function kanbanRecurringAddCommand(title, opts) {
|
|
|
2809
3466
|
jsonOutput({ ok: true, template: data.template });
|
|
2810
3467
|
return;
|
|
2811
3468
|
}
|
|
2812
|
-
success(`Recurring task created: ${
|
|
2813
|
-
info(`Schedule: ${
|
|
3469
|
+
success(`Recurring task created: ${chalk15.bold(title)}`);
|
|
3470
|
+
info(`Schedule: ${chalk15.cyan(data.template.natural_language ?? data.template.expression ?? data.template.every_interval ?? "")}`);
|
|
2814
3471
|
info(`Next spawn: ${formatSpawnTime(data.template.next_spawn_at, data.template.timezone)}`);
|
|
2815
3472
|
info(`Timezone: ${data.template.timezone}`);
|
|
2816
3473
|
} catch (err) {
|
|
@@ -2827,7 +3484,7 @@ async function kanbanRecurringListCommand(opts) {
|
|
|
2827
3484
|
const teamSlug = requireTeam();
|
|
2828
3485
|
if (!teamSlug) return;
|
|
2829
3486
|
const json = isJsonMode();
|
|
2830
|
-
const spinner =
|
|
3487
|
+
const spinner = ora13({ text: "Fetching recurring templates\u2026", isSilent: json });
|
|
2831
3488
|
spinner.start();
|
|
2832
3489
|
const agentId = await resolveAgentId(opts.agent);
|
|
2833
3490
|
if (!agentId) {
|
|
@@ -2853,7 +3510,7 @@ async function kanbanRecurringListCommand(opts) {
|
|
|
2853
3510
|
t.title,
|
|
2854
3511
|
t.natural_language ?? t.expression ?? t.every_interval ?? "\u2014",
|
|
2855
3512
|
priorityLabel(t.priority),
|
|
2856
|
-
t.enabled ?
|
|
3513
|
+
t.enabled ? chalk15.green("Active") : chalk15.dim("Disabled"),
|
|
2857
3514
|
formatSpawnTime(t.next_spawn_at, t.timezone),
|
|
2858
3515
|
String(t.spawn_count)
|
|
2859
3516
|
]);
|
|
@@ -2875,7 +3532,7 @@ async function kanbanRecurringDisableCommand(titleOrId, opts) {
|
|
|
2875
3532
|
const teamSlug = requireTeam();
|
|
2876
3533
|
if (!teamSlug) return;
|
|
2877
3534
|
const json = isJsonMode();
|
|
2878
|
-
const spinner =
|
|
3535
|
+
const spinner = ora13({ text: "Disabling recurring template\u2026", isSilent: json });
|
|
2879
3536
|
spinner.start();
|
|
2880
3537
|
const agentId = await resolveAgentId(opts.agent);
|
|
2881
3538
|
if (!agentId) {
|
|
@@ -2910,7 +3567,7 @@ async function kanbanRecurringDisableCommand(titleOrId, opts) {
|
|
|
2910
3567
|
if (json) {
|
|
2911
3568
|
jsonOutput({ ok: true, id: match.id, title: match.title, enabled: false });
|
|
2912
3569
|
} else {
|
|
2913
|
-
success(`Disabled: ${
|
|
3570
|
+
success(`Disabled: ${chalk15.bold(match.title)}`);
|
|
2914
3571
|
}
|
|
2915
3572
|
} catch (err) {
|
|
2916
3573
|
spinner.fail("Failed to disable recurring template");
|
|
@@ -2924,24 +3581,24 @@ async function kanbanRecurringDisableCommand(titleOrId, opts) {
|
|
|
2924
3581
|
}
|
|
2925
3582
|
|
|
2926
3583
|
// src/commands/setup.ts
|
|
2927
|
-
import { existsSync as
|
|
2928
|
-
import { join as
|
|
2929
|
-
import { homedir as
|
|
2930
|
-
import
|
|
2931
|
-
import
|
|
3584
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, accessSync, constants as fsConstants } from "fs";
|
|
3585
|
+
import { join as join13, dirname as dirname2 } from "path";
|
|
3586
|
+
import { homedir as homedir4 } from "os";
|
|
3587
|
+
import chalk16 from "chalk";
|
|
3588
|
+
import ora14 from "ora";
|
|
2932
3589
|
function detectShellProfile() {
|
|
2933
3590
|
const shell = process.env["SHELL"] ?? "";
|
|
2934
|
-
const home =
|
|
3591
|
+
const home = homedir4();
|
|
2935
3592
|
if (shell.includes("zsh")) {
|
|
2936
|
-
return
|
|
3593
|
+
return join13(home, ".zshrc");
|
|
2937
3594
|
}
|
|
2938
3595
|
if (shell.includes("fish")) {
|
|
2939
|
-
const fishConfig =
|
|
3596
|
+
const fishConfig = join13(home, ".config", "fish", "config.fish");
|
|
2940
3597
|
return fishConfig;
|
|
2941
3598
|
}
|
|
2942
|
-
const bashrc =
|
|
2943
|
-
if (
|
|
2944
|
-
return
|
|
3599
|
+
const bashrc = join13(home, ".bashrc");
|
|
3600
|
+
if (existsSync7(bashrc)) return bashrc;
|
|
3601
|
+
return join13(home, ".bash_profile");
|
|
2945
3602
|
}
|
|
2946
3603
|
function maybeWriteSystemWideEnv(apiUrl, apiKey, consoleUrl) {
|
|
2947
3604
|
const empty = { etcEnvironment: false, profileD: false, bashrc: false };
|
|
@@ -2967,14 +3624,14 @@ function quoteForFishShell(value) {
|
|
|
2967
3624
|
}
|
|
2968
3625
|
function writeEtcEnvironment(apiUrl, apiKey, consoleUrl) {
|
|
2969
3626
|
const envPath = "/etc/environment";
|
|
2970
|
-
if (!
|
|
3627
|
+
if (!existsSync7(envPath)) return false;
|
|
2971
3628
|
try {
|
|
2972
3629
|
accessSync(envPath, fsConstants.W_OK);
|
|
2973
3630
|
} catch {
|
|
2974
3631
|
return false;
|
|
2975
3632
|
}
|
|
2976
3633
|
try {
|
|
2977
|
-
const current =
|
|
3634
|
+
const current = readFileSync7(envPath, "utf-8");
|
|
2978
3635
|
const stripped = current.split(/\r?\n/).filter((line) => !/^\s*(?:export\s+)?AGT_(?:HOST|API_KEY|CONSOLE_URL)=/.test(line)).join("\n");
|
|
2979
3636
|
const base = stripped.endsWith("\n") || stripped.length === 0 ? stripped : `${stripped}
|
|
2980
3637
|
`;
|
|
@@ -2985,7 +3642,7 @@ function writeEtcEnvironment(apiUrl, apiKey, consoleUrl) {
|
|
|
2985
3642
|
if (consoleUrl) lines.push(`AGT_CONSOLE_URL=${quoteForEtcEnvironment(consoleUrl)}`);
|
|
2986
3643
|
const appended = `${base}${lines.join("\n")}
|
|
2987
3644
|
`;
|
|
2988
|
-
|
|
3645
|
+
writeFileSync7(envPath, appended, { mode: 420 });
|
|
2989
3646
|
return true;
|
|
2990
3647
|
} catch {
|
|
2991
3648
|
return false;
|
|
@@ -2993,7 +3650,7 @@ function writeEtcEnvironment(apiUrl, apiKey, consoleUrl) {
|
|
|
2993
3650
|
}
|
|
2994
3651
|
function writeProfileDScript(apiUrl, apiKey, consoleUrl) {
|
|
2995
3652
|
const profileD = "/etc/profile.d";
|
|
2996
|
-
if (!
|
|
3653
|
+
if (!existsSync7(profileD)) return false;
|
|
2997
3654
|
try {
|
|
2998
3655
|
accessSync(profileD, fsConstants.W_OK);
|
|
2999
3656
|
} catch {
|
|
@@ -3008,7 +3665,7 @@ function writeProfileDScript(apiUrl, apiKey, consoleUrl) {
|
|
|
3008
3665
|
];
|
|
3009
3666
|
if (consoleUrl) lines.push(`export AGT_CONSOLE_URL=${quoteForPosixShell(consoleUrl)}`);
|
|
3010
3667
|
lines.push("");
|
|
3011
|
-
|
|
3668
|
+
writeFileSync7(scriptPath, lines.join("\n"), { mode: 420 });
|
|
3012
3669
|
return true;
|
|
3013
3670
|
} catch {
|
|
3014
3671
|
return false;
|
|
@@ -3016,14 +3673,14 @@ function writeProfileDScript(apiUrl, apiKey, consoleUrl) {
|
|
|
3016
3673
|
}
|
|
3017
3674
|
function ensureBashrcSourcesProfileD() {
|
|
3018
3675
|
const bashrc = "/etc/bashrc";
|
|
3019
|
-
if (!
|
|
3676
|
+
if (!existsSync7(bashrc)) return false;
|
|
3020
3677
|
try {
|
|
3021
3678
|
accessSync(bashrc, fsConstants.W_OK);
|
|
3022
3679
|
} catch {
|
|
3023
3680
|
return false;
|
|
3024
3681
|
}
|
|
3025
3682
|
try {
|
|
3026
|
-
const current =
|
|
3683
|
+
const current = readFileSync7(bashrc, "utf-8");
|
|
3027
3684
|
const marker = "# Augmented (agt) \u2014 source system-wide AGT env";
|
|
3028
3685
|
if (current.includes(marker)) return true;
|
|
3029
3686
|
const snippet = [
|
|
@@ -3034,7 +3691,7 @@ function ensureBashrcSourcesProfileD() {
|
|
|
3034
3691
|
"fi",
|
|
3035
3692
|
""
|
|
3036
3693
|
].join("\n");
|
|
3037
|
-
|
|
3694
|
+
writeFileSync7(bashrc, current + snippet);
|
|
3038
3695
|
return true;
|
|
3039
3696
|
} catch {
|
|
3040
3697
|
return false;
|
|
@@ -3081,10 +3738,10 @@ async function setupCommand(token) {
|
|
|
3081
3738
|
if (!apiUrl) {
|
|
3082
3739
|
apiUrl = "https://api.augmented.team";
|
|
3083
3740
|
if (!json) {
|
|
3084
|
-
info(`No AGT_HOST set \u2014 using default: ${
|
|
3741
|
+
info(`No AGT_HOST set \u2014 using default: ${chalk16.bold(apiUrl)}`);
|
|
3085
3742
|
}
|
|
3086
3743
|
}
|
|
3087
|
-
const spinner =
|
|
3744
|
+
const spinner = ora14({ text: "Exchanging provisioning token\u2026", isSilent: json });
|
|
3088
3745
|
spinner.start();
|
|
3089
3746
|
let setupResult;
|
|
3090
3747
|
try {
|
|
@@ -3112,17 +3769,17 @@ async function setupCommand(token) {
|
|
|
3112
3769
|
const finalApiUrl = setupResult.api_url || apiUrl;
|
|
3113
3770
|
process.env["AGT_HOST"] = finalApiUrl;
|
|
3114
3771
|
process.env["AGT_API_KEY"] = setupResult.api_key;
|
|
3115
|
-
const verifySpinner =
|
|
3772
|
+
const verifySpinner = ora14({ text: "Verifying connection\u2026", isSilent: json });
|
|
3116
3773
|
verifySpinner.start();
|
|
3117
3774
|
try {
|
|
3118
3775
|
const exchange = await exchangeApiKey(setupResult.api_key);
|
|
3119
3776
|
verifySpinner.succeed("Connection verified");
|
|
3120
3777
|
if (!json) {
|
|
3121
|
-
info(`Host: ${
|
|
3778
|
+
info(`Host: ${chalk16.bold(setupResult.host_name)}`);
|
|
3122
3779
|
info(`Host ID: ${exchange.hostId}`);
|
|
3123
|
-
info(`Team: ${
|
|
3780
|
+
info(`Team: ${chalk16.bold(exchange.teamSlug ?? setupResult.team_slug ?? "unknown")}`);
|
|
3124
3781
|
if (exchange.userEmail) {
|
|
3125
|
-
info(`User: ${
|
|
3782
|
+
info(`User: ${chalk16.bold(exchange.userEmail)}`);
|
|
3126
3783
|
}
|
|
3127
3784
|
}
|
|
3128
3785
|
} catch (err) {
|
|
@@ -3149,12 +3806,12 @@ async function setupCommand(token) {
|
|
|
3149
3806
|
);
|
|
3150
3807
|
}
|
|
3151
3808
|
const exportLines = buildExportLines(shell, finalApiUrl, setupResult.api_key, consoleUrl);
|
|
3152
|
-
const profileDir =
|
|
3153
|
-
if (!
|
|
3154
|
-
|
|
3809
|
+
const profileDir = dirname2(profilePath);
|
|
3810
|
+
if (!existsSync7(profileDir)) {
|
|
3811
|
+
mkdirSync6(profileDir, { recursive: true });
|
|
3155
3812
|
}
|
|
3156
3813
|
const marker = "# Augmented (agt) host configuration";
|
|
3157
|
-
const current =
|
|
3814
|
+
const current = existsSync7(profilePath) ? readFileSync7(profilePath, "utf-8") : "";
|
|
3158
3815
|
let updated;
|
|
3159
3816
|
if (current.includes(marker)) {
|
|
3160
3817
|
updated = current.replace(
|
|
@@ -3167,10 +3824,10 @@ async function setupCommand(token) {
|
|
|
3167
3824
|
} else {
|
|
3168
3825
|
updated = current + exportLines;
|
|
3169
3826
|
}
|
|
3170
|
-
|
|
3827
|
+
writeFileSync7(profilePath, updated.endsWith("\n") ? updated : `${updated}
|
|
3171
3828
|
`);
|
|
3172
3829
|
if (!json) {
|
|
3173
|
-
success(`Environment variables written to ${
|
|
3830
|
+
success(`Environment variables written to ${chalk16.bold(profilePath)}`);
|
|
3174
3831
|
}
|
|
3175
3832
|
const sys = maybeWriteSystemWideEnv(finalApiUrl, setupResult.api_key, consoleUrl);
|
|
3176
3833
|
const valueChannelsWritten = sys.etcEnvironment || sys.profileD;
|
|
@@ -3182,10 +3839,10 @@ async function setupCommand(token) {
|
|
|
3182
3839
|
].filter(Boolean).join(" + ");
|
|
3183
3840
|
success(`System-wide env updated: ${channels2}`);
|
|
3184
3841
|
}
|
|
3185
|
-
const managerSpinner =
|
|
3842
|
+
const managerSpinner = ora14({ text: "Starting manager daemon\u2026", isSilent: json });
|
|
3186
3843
|
managerSpinner.start();
|
|
3187
3844
|
try {
|
|
3188
|
-
const configDir =
|
|
3845
|
+
const configDir = join13(homedir4(), ".augmented");
|
|
3189
3846
|
const { pid } = startWatchdog({ intervalMs: 1e4, configDir, detached: true });
|
|
3190
3847
|
managerSpinner.succeed(`Manager started (PID ${pid})`);
|
|
3191
3848
|
} catch (err) {
|
|
@@ -3210,21 +3867,21 @@ async function setupCommand(token) {
|
|
|
3210
3867
|
process.exit(0);
|
|
3211
3868
|
} else {
|
|
3212
3869
|
console.log();
|
|
3213
|
-
console.log(
|
|
3870
|
+
console.log(chalk16.green.bold(" Setup complete!"));
|
|
3214
3871
|
console.log();
|
|
3215
3872
|
if (setupResult.agents.length > 0) {
|
|
3216
|
-
info(`Agents: ${setupResult.agents.map((a) =>
|
|
3873
|
+
info(`Agents: ${setupResult.agents.map((a) => chalk16.cyan(a)).join(", ")}`);
|
|
3217
3874
|
} else {
|
|
3218
3875
|
info("No agents assigned yet. Assign agents in the dashboard or with: agt host assign");
|
|
3219
3876
|
}
|
|
3220
3877
|
console.log();
|
|
3221
|
-
info(`Restart your shell or run: ${
|
|
3878
|
+
info(`Restart your shell or run: ${chalk16.bold(`source ${profilePath}`)}`);
|
|
3222
3879
|
}
|
|
3223
3880
|
}
|
|
3224
3881
|
|
|
3225
3882
|
// src/commands/kanban.ts
|
|
3226
|
-
import
|
|
3227
|
-
import
|
|
3883
|
+
import chalk17 from "chalk";
|
|
3884
|
+
import ora15 from "ora";
|
|
3228
3885
|
async function resolveAgentId2(codeName) {
|
|
3229
3886
|
try {
|
|
3230
3887
|
const data = await api.get(`/agents/${codeName}`);
|
|
@@ -3234,14 +3891,14 @@ async function resolveAgentId2(codeName) {
|
|
|
3234
3891
|
}
|
|
3235
3892
|
}
|
|
3236
3893
|
function priorityLabel2(p) {
|
|
3237
|
-
return p === 1 ?
|
|
3894
|
+
return p === 1 ? chalk17.red("HIGH") : p === 3 ? chalk17.dim("LOW") : chalk17.yellow("MED");
|
|
3238
3895
|
}
|
|
3239
3896
|
function statusLabel(s) {
|
|
3240
3897
|
const map = {
|
|
3241
|
-
in_progress:
|
|
3242
|
-
todo:
|
|
3243
|
-
backlog:
|
|
3244
|
-
done:
|
|
3898
|
+
in_progress: chalk17.blue("In Progress"),
|
|
3899
|
+
todo: chalk17.green("To Do"),
|
|
3900
|
+
backlog: chalk17.dim("Backlog"),
|
|
3901
|
+
done: chalk17.gray("Done")
|
|
3245
3902
|
};
|
|
3246
3903
|
return map[s] ?? s;
|
|
3247
3904
|
}
|
|
@@ -3249,7 +3906,7 @@ async function kanbanListCommand(opts) {
|
|
|
3249
3906
|
const teamSlug = requireTeam();
|
|
3250
3907
|
if (!teamSlug) return;
|
|
3251
3908
|
const json = isJsonMode();
|
|
3252
|
-
const spinner =
|
|
3909
|
+
const spinner = ora15({ text: "Fetching kanban board\u2026", isSilent: json });
|
|
3253
3910
|
spinner.start();
|
|
3254
3911
|
const agentId = await resolveAgentId2(opts.agent);
|
|
3255
3912
|
if (!agentId) {
|
|
@@ -3277,7 +3934,7 @@ async function kanbanListCommand(opts) {
|
|
|
3277
3934
|
item.estimated_minutes ? `~${item.estimated_minutes}min` : "",
|
|
3278
3935
|
item.id.slice(0, 8)
|
|
3279
3936
|
]);
|
|
3280
|
-
console.log(
|
|
3937
|
+
console.log(chalk17.bold(`
|
|
3281
3938
|
Kanban: ${opts.agent}
|
|
3282
3939
|
`));
|
|
3283
3940
|
table(["Pri", "Status", "Title", "Est", "ID"], rows);
|
|
@@ -3517,7 +4174,7 @@ function getAcpAgent(name) {
|
|
|
3517
4174
|
}
|
|
3518
4175
|
|
|
3519
4176
|
// ../../packages/core/dist/acp/client.js
|
|
3520
|
-
import { spawn as
|
|
4177
|
+
import { spawn as spawn3 } from "child_process";
|
|
3521
4178
|
function resolveAgentCommand(agentName) {
|
|
3522
4179
|
const adapter = getAcpAgent(agentName);
|
|
3523
4180
|
if (adapter) {
|
|
@@ -3533,7 +4190,7 @@ function resolveAgentCommand(agentName) {
|
|
|
3533
4190
|
}
|
|
3534
4191
|
async function runAcpx(args, options = {}) {
|
|
3535
4192
|
return new Promise((resolve2) => {
|
|
3536
|
-
const child =
|
|
4193
|
+
const child = spawn3("acpx", args, {
|
|
3537
4194
|
cwd: options.cwd,
|
|
3538
4195
|
stdio: ["pipe", "pipe", "pipe"],
|
|
3539
4196
|
env: { ...process.env }
|
|
@@ -3733,10 +4390,10 @@ async function acpxCloseCommand(agent2, _opts, cmd) {
|
|
|
3733
4390
|
|
|
3734
4391
|
// src/commands/update.ts
|
|
3735
4392
|
import { execFileSync, execSync } from "child_process";
|
|
3736
|
-
import { existsSync as
|
|
3737
|
-
import
|
|
3738
|
-
import
|
|
3739
|
-
var cliVersion = true ? "0.
|
|
4393
|
+
import { existsSync as existsSync8, realpathSync as realpathSync2 } from "fs";
|
|
4394
|
+
import chalk18 from "chalk";
|
|
4395
|
+
import ora16 from "ora";
|
|
4396
|
+
var cliVersion = true ? "0.27.0-test.4" : "dev";
|
|
3740
4397
|
async function fetchLatestVersion() {
|
|
3741
4398
|
const host2 = getHost();
|
|
3742
4399
|
if (!host2) return null;
|
|
@@ -3756,9 +4413,9 @@ async function fetchLatestVersion() {
|
|
|
3756
4413
|
}
|
|
3757
4414
|
function isNewerVersion(local, remote) {
|
|
3758
4415
|
if (local === "dev") return false;
|
|
3759
|
-
const
|
|
3760
|
-
const lParts =
|
|
3761
|
-
const rParts =
|
|
4416
|
+
const parse2 = (v) => v.replace(/^v/, "").split(".").map(Number);
|
|
4417
|
+
const lParts = parse2(local);
|
|
4418
|
+
const rParts = parse2(remote);
|
|
3762
4419
|
const lMaj = lParts[0] ?? 0, lMin = lParts[1] ?? 0, lPat = lParts[2] ?? 0;
|
|
3763
4420
|
const rMaj = rParts[0] ?? 0, rMin = rParts[1] ?? 0, rPat = rParts[2] ?? 0;
|
|
3764
4421
|
if (rMaj !== lMaj) return rMaj > lMaj;
|
|
@@ -3800,7 +4457,7 @@ function detectActiveSessions() {
|
|
|
3800
4457
|
}
|
|
3801
4458
|
function detectInstallSource() {
|
|
3802
4459
|
try {
|
|
3803
|
-
const resolved =
|
|
4460
|
+
const resolved = realpathSync2(process.argv[1] ?? "");
|
|
3804
4461
|
if (/\/Cellar\/[^/]+\//.test(resolved)) {
|
|
3805
4462
|
return "brew";
|
|
3806
4463
|
}
|
|
@@ -3840,7 +4497,7 @@ function performUpdate(version) {
|
|
|
3840
4497
|
function detectBrewOwner() {
|
|
3841
4498
|
for (const prefix of ["/home/linuxbrew/.linuxbrew", "/opt/homebrew", "/usr/local"]) {
|
|
3842
4499
|
const cellar = `${prefix}/Cellar`;
|
|
3843
|
-
if (!
|
|
4500
|
+
if (!existsSync8(cellar)) continue;
|
|
3844
4501
|
try {
|
|
3845
4502
|
return execSync(`stat -c %U "${cellar}" 2>/dev/null || stat -f %Su "${cellar}"`, { encoding: "utf-8" }).trim() || null;
|
|
3846
4503
|
} catch {
|
|
@@ -3850,7 +4507,7 @@ function detectBrewOwner() {
|
|
|
3850
4507
|
}
|
|
3851
4508
|
async function updateCommand(opts = {}) {
|
|
3852
4509
|
const json = isJsonMode();
|
|
3853
|
-
const spinner =
|
|
4510
|
+
const spinner = ora16({ text: "Checking for updates\u2026", isSilent: json });
|
|
3854
4511
|
spinner.start();
|
|
3855
4512
|
const versionInfo = await fetchLatestVersion();
|
|
3856
4513
|
if (!versionInfo) {
|
|
@@ -3869,13 +4526,13 @@ async function updateCommand(opts = {}) {
|
|
|
3869
4526
|
if (json) {
|
|
3870
4527
|
jsonOutput({ ok: true, current: cliVersion, latest: versionInfo.latest, update_available: false });
|
|
3871
4528
|
} else {
|
|
3872
|
-
success(`You're on the latest version (${
|
|
4529
|
+
success(`You're on the latest version (${chalk18.bold(cliVersion)})`);
|
|
3873
4530
|
}
|
|
3874
4531
|
return;
|
|
3875
4532
|
}
|
|
3876
4533
|
spinner.stop();
|
|
3877
4534
|
if (!json) {
|
|
3878
|
-
info(`Update available: ${
|
|
4535
|
+
info(`Update available: ${chalk18.dim(cliVersion)} \u2192 ${chalk18.bold.green(versionInfo.latest)}`);
|
|
3879
4536
|
if (versionInfo.changelog_url) {
|
|
3880
4537
|
info(`Changelog: ${versionInfo.changelog_url}`);
|
|
3881
4538
|
}
|
|
@@ -3886,16 +4543,16 @@ async function updateCommand(opts = {}) {
|
|
|
3886
4543
|
if (!json) {
|
|
3887
4544
|
warn("Active processes detected:");
|
|
3888
4545
|
if (managerPid) {
|
|
3889
|
-
console.error(
|
|
4546
|
+
console.error(chalk18.yellow(` \u2022 Manager process running (PID ${managerPid})`));
|
|
3890
4547
|
}
|
|
3891
4548
|
if (tmuxSessions.length > 0) {
|
|
3892
|
-
console.error(
|
|
4549
|
+
console.error(chalk18.yellow(` \u2022 ${tmuxSessions.length} active agent session(s): ${tmuxSessions.join(", ")}`));
|
|
3893
4550
|
}
|
|
3894
4551
|
console.error();
|
|
3895
4552
|
warn("Updating while the manager is running will leave it on the old version until restarted.");
|
|
3896
4553
|
warn("Active agent sessions will continue with stale MCP servers.");
|
|
3897
4554
|
console.error();
|
|
3898
|
-
info(`Run ${
|
|
4555
|
+
info(`Run ${chalk18.bold("agt update --force")} to update anyway, then restart the manager.`);
|
|
3899
4556
|
} else {
|
|
3900
4557
|
jsonOutput({
|
|
3901
4558
|
ok: false,
|
|
@@ -3913,7 +4570,7 @@ async function updateCommand(opts = {}) {
|
|
|
3913
4570
|
return;
|
|
3914
4571
|
}
|
|
3915
4572
|
}
|
|
3916
|
-
const updateSpinner =
|
|
4573
|
+
const updateSpinner = ora16({ text: "Installing update\u2026", isSilent: json });
|
|
3917
4574
|
updateSpinner.start();
|
|
3918
4575
|
try {
|
|
3919
4576
|
performUpdate(versionInfo.latest);
|
|
@@ -3927,8 +4584,8 @@ async function updateCommand(opts = {}) {
|
|
|
3927
4584
|
updated: true
|
|
3928
4585
|
});
|
|
3929
4586
|
} else {
|
|
3930
|
-
success(`Updated to ${
|
|
3931
|
-
info(`If you have a running manager, restart it: ${
|
|
4587
|
+
success(`Updated to ${chalk18.bold(versionInfo.latest)}`);
|
|
4588
|
+
info(`If you have a running manager, restart it: ${chalk18.bold("agt manager stop && agt manager start")}`);
|
|
3932
4589
|
}
|
|
3933
4590
|
} catch (err) {
|
|
3934
4591
|
updateSpinner.fail("Update failed");
|
|
@@ -3959,8 +4616,8 @@ async function checkForUpdateOnStartup() {
|
|
|
3959
4616
|
if (!versionInfo) return;
|
|
3960
4617
|
if (isNewerVersion(cliVersion, versionInfo.latest)) {
|
|
3961
4618
|
console.error(
|
|
3962
|
-
|
|
3963
|
-
Update available: ${cliVersion} \u2192 ${versionInfo.latest}. Run ${
|
|
4619
|
+
chalk18.yellow(`
|
|
4620
|
+
Update available: ${cliVersion} \u2192 ${versionInfo.latest}. Run ${chalk18.bold("agt update")} to install.`) + chalk18.dim("\n Note: Restart the manager after updating.\n")
|
|
3964
4621
|
);
|
|
3965
4622
|
}
|
|
3966
4623
|
} catch {
|
|
@@ -3968,11 +4625,11 @@ async function checkForUpdateOnStartup() {
|
|
|
3968
4625
|
}
|
|
3969
4626
|
|
|
3970
4627
|
// src/commands/integration.ts
|
|
3971
|
-
import
|
|
3972
|
-
import
|
|
4628
|
+
import chalk19 from "chalk";
|
|
4629
|
+
import ora17 from "ora";
|
|
3973
4630
|
async function integrationListCommand() {
|
|
3974
4631
|
const json = isJsonMode();
|
|
3975
|
-
const spinner =
|
|
4632
|
+
const spinner = ora17({ text: "Fetching integrations\u2026", isSilent: json }).start();
|
|
3976
4633
|
try {
|
|
3977
4634
|
const data = await api.get("/integrations/bindings");
|
|
3978
4635
|
spinner.stop();
|
|
@@ -3984,11 +4641,11 @@ async function integrationListCommand() {
|
|
|
3984
4641
|
info("No integrations found for this team.");
|
|
3985
4642
|
return;
|
|
3986
4643
|
}
|
|
3987
|
-
console.log(
|
|
4644
|
+
console.log(chalk19.bold("\nAvailable Integrations:\n"));
|
|
3988
4645
|
for (const p of data) {
|
|
3989
|
-
const statusColor = p.status === "published" ?
|
|
4646
|
+
const statusColor = p.status === "published" ? chalk19.green : p.status === "archived" ? chalk19.gray : chalk19.yellow;
|
|
3990
4647
|
console.log(
|
|
3991
|
-
` ${
|
|
4648
|
+
` ${chalk19.bold(p.name)} ${chalk19.dim(`(${p.slug})`)} ${statusColor(p.status)} v${p.version} ${chalk19.dim(`${p.skills?.length ?? 0} skills, ${p.defined_scopes?.length ?? 0} scopes`)}`
|
|
3992
4649
|
);
|
|
3993
4650
|
}
|
|
3994
4651
|
console.log();
|
|
@@ -3999,7 +4656,7 @@ async function integrationListCommand() {
|
|
|
3999
4656
|
}
|
|
4000
4657
|
async function integrationShowCommand(slug) {
|
|
4001
4658
|
const json = isJsonMode();
|
|
4002
|
-
const spinner =
|
|
4659
|
+
const spinner = ora17({ text: "Fetching integration\u2026", isSilent: json }).start();
|
|
4003
4660
|
try {
|
|
4004
4661
|
const all = await api.get("/integrations/bindings");
|
|
4005
4662
|
const integration2 = all.find((p) => p.slug === slug);
|
|
@@ -4015,21 +4672,21 @@ async function integrationShowCommand(slug) {
|
|
|
4015
4672
|
jsonOutput({ integration: integration2, scopes });
|
|
4016
4673
|
return;
|
|
4017
4674
|
}
|
|
4018
|
-
console.log(
|
|
4019
|
-
${integration2.name}`),
|
|
4020
|
-
if (integration2.description) console.log(
|
|
4675
|
+
console.log(chalk19.bold(`
|
|
4676
|
+
${integration2.name}`), chalk19.dim(`(${integration2.slug})`));
|
|
4677
|
+
if (integration2.description) console.log(chalk19.dim(integration2.description));
|
|
4021
4678
|
console.log();
|
|
4022
4679
|
console.log(` Status: ${integration2.status} v${integration2.version}`);
|
|
4023
4680
|
console.log(` Category: ${integration2.category}`);
|
|
4024
4681
|
console.log(` Toolkits: ${integration2.required_toolkits.join(", ") || "none"}`);
|
|
4025
4682
|
console.log(` Skills: ${integration2.skills?.length ?? 0}`);
|
|
4026
4683
|
if (scopes.length > 0) {
|
|
4027
|
-
console.log(
|
|
4684
|
+
console.log(chalk19.bold("\n Permission Scopes:\n"));
|
|
4028
4685
|
for (const s of scopes) {
|
|
4029
|
-
const roleColor = s.can_grant ?
|
|
4030
|
-
const overrideTag = s.is_overridden ?
|
|
4686
|
+
const roleColor = s.can_grant ? chalk19.green : chalk19.red;
|
|
4687
|
+
const overrideTag = s.is_overridden ? chalk19.cyan(" [team override]") : "";
|
|
4031
4688
|
console.log(
|
|
4032
|
-
` ${
|
|
4689
|
+
` ${chalk19.bold(s.id)} ${s.name} min: ${roleColor(s.effective_min_role)}${overrideTag} ${s.can_grant ? chalk19.green("\u2713 grantable") : chalk19.red("\u2717 needs approval")}`
|
|
4033
4690
|
);
|
|
4034
4691
|
}
|
|
4035
4692
|
}
|
|
@@ -4041,7 +4698,7 @@ ${integration2.name}`), chalk18.dim(`(${integration2.slug})`));
|
|
|
4041
4698
|
}
|
|
4042
4699
|
async function integrationInstallCommand(slug, options) {
|
|
4043
4700
|
const json = isJsonMode();
|
|
4044
|
-
const spinner =
|
|
4701
|
+
const spinner = ora17({ text: "Installing integration\u2026", isSilent: json }).start();
|
|
4045
4702
|
try {
|
|
4046
4703
|
const all = await api.get("/integrations/bindings");
|
|
4047
4704
|
const integration2 = all.find((p) => p.slug === slug);
|
|
@@ -4080,7 +4737,7 @@ async function integrationInstallCommand(slug, options) {
|
|
|
4080
4737
|
const needsApproval = err.body["needs_approval"];
|
|
4081
4738
|
error("Some scopes exceed your role ceiling:");
|
|
4082
4739
|
for (const s of needsApproval) {
|
|
4083
|
-
console.log(
|
|
4740
|
+
console.log(chalk19.yellow(` - ${s}`));
|
|
4084
4741
|
}
|
|
4085
4742
|
info("To request elevated scopes, use the web dashboard or contact your team admin.");
|
|
4086
4743
|
process.exitCode = 1;
|
|
@@ -4091,7 +4748,7 @@ async function integrationInstallCommand(slug, options) {
|
|
|
4091
4748
|
}
|
|
4092
4749
|
async function integrationUninstallCommand(slug, options) {
|
|
4093
4750
|
const json = isJsonMode();
|
|
4094
|
-
const spinner =
|
|
4751
|
+
const spinner = ora17({ text: "Uninstalling integration\u2026", isSilent: json }).start();
|
|
4095
4752
|
try {
|
|
4096
4753
|
const all = await api.get("/integrations/bindings");
|
|
4097
4754
|
const integration2 = all.find((p) => p.slug === slug);
|
|
@@ -4122,7 +4779,7 @@ async function integrationUninstallCommand(slug, options) {
|
|
|
4122
4779
|
}
|
|
4123
4780
|
async function integrationRequestsCommand() {
|
|
4124
4781
|
const json = isJsonMode();
|
|
4125
|
-
const spinner =
|
|
4782
|
+
const spinner = ora17({ text: "Fetching scope requests\u2026", isSilent: json }).start();
|
|
4126
4783
|
try {
|
|
4127
4784
|
const requests = await api.get("/integrations/bindings/scope-requests");
|
|
4128
4785
|
spinner.stop();
|
|
@@ -4134,10 +4791,10 @@ async function integrationRequestsCommand() {
|
|
|
4134
4791
|
info("No pending scope requests.");
|
|
4135
4792
|
return;
|
|
4136
4793
|
}
|
|
4137
|
-
console.log(
|
|
4794
|
+
console.log(chalk19.bold("\nPending Scope Requests:\n"));
|
|
4138
4795
|
for (const r of requests) {
|
|
4139
|
-
console.log(` ${
|
|
4140
|
-
if (r.reason) console.log(` reason: ${
|
|
4796
|
+
console.log(` ${chalk19.bold(r.id)} scopes: ${r.requested_scopes.join(", ")}`);
|
|
4797
|
+
if (r.reason) console.log(` reason: ${chalk19.dim(r.reason)}`);
|
|
4141
4798
|
console.log(` status: ${r.status} requested: ${r.created_at}`);
|
|
4142
4799
|
console.log();
|
|
4143
4800
|
}
|
|
@@ -4148,7 +4805,7 @@ async function integrationRequestsCommand() {
|
|
|
4148
4805
|
}
|
|
4149
4806
|
async function integrationApproveCommand(requestId, options) {
|
|
4150
4807
|
const json = isJsonMode();
|
|
4151
|
-
const spinner =
|
|
4808
|
+
const spinner = ora17({ text: "Approving request\u2026", isSilent: json }).start();
|
|
4152
4809
|
try {
|
|
4153
4810
|
const result = await api.put(`/integrations/bindings/scope-requests/${requestId}`, {
|
|
4154
4811
|
status: "approved",
|
|
@@ -4167,7 +4824,7 @@ async function integrationApproveCommand(requestId, options) {
|
|
|
4167
4824
|
}
|
|
4168
4825
|
async function integrationDenyCommand(requestId, options) {
|
|
4169
4826
|
const json = isJsonMode();
|
|
4170
|
-
const spinner =
|
|
4827
|
+
const spinner = ora17({ text: "Denying request\u2026", isSilent: json }).start();
|
|
4171
4828
|
try {
|
|
4172
4829
|
const result = await api.put(`/integrations/bindings/scope-requests/${requestId}`, {
|
|
4173
4830
|
status: "denied",
|
|
@@ -4209,7 +4866,7 @@ async function integrationEnrolCommand(options) {
|
|
|
4209
4866
|
const [secretKey, secretValue] = secrets[0];
|
|
4210
4867
|
const credentials = { [secretKey]: secretValue };
|
|
4211
4868
|
const authType = options.authType ?? (options.webhookSecret ? "webhook" : options.apiKey ? "api_key" : "oauth2");
|
|
4212
|
-
const spinner =
|
|
4869
|
+
const spinner = ora17({ text: `Enrolling ${options.def} at ${apiScope} scope\u2026`, isSilent: json }).start();
|
|
4213
4870
|
try {
|
|
4214
4871
|
let agentId;
|
|
4215
4872
|
if (apiScope === "agent") {
|
|
@@ -4268,7 +4925,7 @@ function handleError(err) {
|
|
|
4268
4925
|
}
|
|
4269
4926
|
|
|
4270
4927
|
// src/bin/agt.ts
|
|
4271
|
-
var cliVersion2 = true ? "0.
|
|
4928
|
+
var cliVersion2 = true ? "0.27.0-test.4" : "dev";
|
|
4272
4929
|
var program = new Command();
|
|
4273
4930
|
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");
|
|
4274
4931
|
program.hook("preAction", (thisCommand) => {
|
|
@@ -4283,6 +4940,31 @@ var team = program.command("team").description("Manage teams");
|
|
|
4283
4940
|
team.command("list").description("List teams you belong to").action(teamListCommand);
|
|
4284
4941
|
team.command("create <name>").description("Create a new team and set it as active").action(teamCreateCommand);
|
|
4285
4942
|
team.command("switch <slug>").description("Switch the active team").action(teamSwitchCommand);
|
|
4943
|
+
var impersonate = program.command("impersonate").description(
|
|
4944
|
+
"Operate as a managed agent locally (experimental, gated by AGT_IMPERSONATE_ENABLED)"
|
|
4945
|
+
);
|
|
4946
|
+
impersonate.command("connect <token>").description(
|
|
4947
|
+
"Redeem a XXXX-XXXX token from the webapp, render the agent's CLAUDE.md + .mcp.json, swap them into the current project, and launch Claude Code (use --no-launch to skip the launch)"
|
|
4948
|
+
).option(
|
|
4949
|
+
"--no-launch",
|
|
4950
|
+
"Skip launching Claude Code after the swap (operator manages their own session)"
|
|
4951
|
+
).option(
|
|
4952
|
+
"--workdir",
|
|
4953
|
+
"Swap into an auto-provisioned dedicated directory (~/.augmented-impersonate/<agent>/workdir) instead of the current directory \u2014 required when the current directory is inside a git repo"
|
|
4954
|
+
).action(
|
|
4955
|
+
(token, opts) => (
|
|
4956
|
+
// Commander negates --no-FLAG into opts.flag, so --no-launch arrives
|
|
4957
|
+
// as opts.launch === false. Translate to the command's noLaunch
|
|
4958
|
+
// option, defaulting to launching when the flag isn't passed.
|
|
4959
|
+
impersonateConnectCommand(token, {
|
|
4960
|
+
noLaunch: opts.launch === false,
|
|
4961
|
+
workdir: opts.workdir === true
|
|
4962
|
+
})
|
|
4963
|
+
)
|
|
4964
|
+
);
|
|
4965
|
+
impersonate.command("current").description('Print the active impersonation (or "none")').action(impersonateCurrentCommand);
|
|
4966
|
+
impersonate.command("exit").description("End the active impersonation, restore project files, and notify the server").action(impersonateExitCommand);
|
|
4967
|
+
impersonate.command("introduce", { hidden: true }).description("Print the impersonated agent's self-introduction (SessionStart hook target)").action(impersonateIntroduceCommand);
|
|
4286
4968
|
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);
|
|
4287
4969
|
program.command("lint").argument("[path]", "Path to agent directory (default: all agents in .augmented/)").description("Lint CHARTER.md and TOOLS.md files").action(lintCommand);
|
|
4288
4970
|
var channels = program.command("channels").description("Manage and inspect channel configuration");
|
|
@@ -4315,16 +4997,16 @@ host.command("pair <host-name>").description("Start an SSM port-forward + shell
|
|
|
4315
4997
|
})
|
|
4316
4998
|
);
|
|
4317
4999
|
var manager = program.command("manager").description("Host config sync daemon \u2014 keeps local agent files in sync with API");
|
|
4318
|
-
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",
|
|
4319
|
-
manager.command("stop").description("Stop the running manager daemon").option("--config-dir <dir>", "Config directory for agent files",
|
|
4320
|
-
manager.command("status").description("Show the current manager daemon status and discovered agents").option("--config-dir <dir>", "Config directory for agent files",
|
|
4321
|
-
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",
|
|
4322
|
-
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",
|
|
5000
|
+
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", join14(homedir5(), ".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);
|
|
5001
|
+
manager.command("stop").description("Stop the running manager daemon").option("--config-dir <dir>", "Config directory for agent files", join14(homedir5(), ".augmented")).action(managerStopCommand);
|
|
5002
|
+
manager.command("status").description("Show the current manager daemon status and discovered agents").option("--config-dir <dir>", "Config directory for agent files", join14(homedir5(), ".augmented")).action(managerStatusCommand);
|
|
5003
|
+
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", join14(homedir5(), ".augmented")).option("--no-tui", "Skip the TUI and stream the manager log to stdout instead (CI / scripts)").action(managerWatchCommand);
|
|
5004
|
+
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", join14(homedir5(), ".augmented")).action(managerInstallCommand);
|
|
4323
5005
|
manager.command("uninstall").description("Remove the OS-level supervisor (launchctl unload + delete plist on macOS). Idempotent.").action(managerUninstallCommand);
|
|
4324
5006
|
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);
|
|
4325
5007
|
manager.command("uninstall-system-unit").description("Remove the system-level systemd unit (Linux, root). Idempotent. (ENG-4706)").action(managerUninstallSystemUnitCommand);
|
|
4326
5008
|
var agent = program.command("agent").description("Inspect and manage agents");
|
|
4327
|
-
agent.command("show <code-name>").description("Display an agent's provisioned OpenClaw configuration").option("--config-dir <dir>", "Config directory",
|
|
5009
|
+
agent.command("show <code-name>").description("Display an agent's provisioned OpenClaw configuration").option("--config-dir <dir>", "Config directory", join14(homedir5(), ".augmented")).option("--all-channels", "Show all channels (including disabled)").action(agentShowCommand);
|
|
4328
5010
|
var kanban = program.command("kanban").description("Manage agent kanban boards");
|
|
4329
5011
|
kanban.command("list").description("List kanban board items for an agent").requiredOption("--agent <code-name>", "Agent code name").action(kanbanListCommand);
|
|
4330
5012
|
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);
|