@integrity-labs/agt-cli 0.26.2 → 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 CHANGED
@@ -26,7 +26,7 @@ import {
26
26
  success,
27
27
  table,
28
28
  warn
29
- } from "../chunk-4CESBZPM.js";
29
+ } from "../chunk-CPZIFISH.js";
30
30
  import {
31
31
  CHANNEL_REGISTRY,
32
32
  DEPLOYMENT_TEMPLATES,
@@ -52,10 +52,10 @@ import {
52
52
  renderTemplate,
53
53
  resolveChannels,
54
54
  serializeManifestForSlackCli
55
- } from "../chunk-U3HCB23E.js";
55
+ } from "../chunk-CTWY2X4S.js";
56
56
 
57
57
  // src/bin/agt.ts
58
- import { join as join12 } from "path";
58
+ import { join as join14 } from "path";
59
59
  import { homedir as homedir5 } from "os";
60
60
  import { Command } from "commander";
61
61
 
@@ -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 = readdirSync(augmentedDir);
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()) {
@@ -1555,8 +1555,9 @@ async function provisionCommand(codeName, options) {
1555
1555
  // src/commands/impersonate.ts
1556
1556
  import chalk10 from "chalk";
1557
1557
  import ora10 from "ora";
1558
- import { writeFileSync as writeFileSync5 } from "fs";
1559
- import { join as join7 } from "path";
1558
+ import { spawn } from "child_process";
1559
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync6 } from "fs";
1560
+ import { join as join9 } from "path";
1560
1561
 
1561
1562
  // src/lib/impersonate-flag.ts
1562
1563
  var ENV_VAR = "AGT_IMPERSONATE_ENABLED";
@@ -1620,6 +1621,11 @@ function ensureCodeNameDir(codeName) {
1620
1621
  mkdirSync4(dir, { recursive: true });
1621
1622
  return dir;
1622
1623
  }
1624
+ function ensureImpersonateWorkdir(codeName) {
1625
+ const dir = join6(ensureCodeNameDir(codeName), "workdir");
1626
+ mkdirSync4(dir, { recursive: true });
1627
+ return dir;
1628
+ }
1623
1629
  function readActiveManifest() {
1624
1630
  if (!existsSync2(ACTIVE_MANIFEST_PATH)) return null;
1625
1631
  try {
@@ -1667,8 +1673,19 @@ import {
1667
1673
  unlinkSync
1668
1674
  } from "fs";
1669
1675
  import { homedir as homedir2 } from "os";
1670
- import { sep } from "path";
1676
+ import { dirname, join as join7, parse, sep } from "path";
1671
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
+ }
1672
1689
  function swapInPersonaFile(projectPath, personaPath) {
1673
1690
  let kind;
1674
1691
  if (!lstatSafe(projectPath)) {
@@ -1733,13 +1750,147 @@ function lstatSafe(path) {
1733
1750
  }
1734
1751
  }
1735
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
+
1736
1886
  // src/commands/impersonate.ts
1737
1887
  var SWAP_FILES = ["CLAUDE.md", ".mcp.json"];
1738
1888
  var AGENT_IMPERSONATION_HEADER = "X-Agent-Impersonation";
1739
1889
  var REDEEM_TOKEN_RE = /^[A-HJ-NP-Z2-9]{4}-[A-HJ-NP-Z2-9]{4}$/i;
1740
- async function impersonateConnectCommand(token) {
1890
+ async function impersonateConnectCommand(token, options = {}) {
1741
1891
  requireImpersonateEnabled();
1742
1892
  const json = isJsonMode();
1893
+ const shouldLaunchClaude = !options.noLaunch && !json;
1743
1894
  if (!REDEEM_TOKEN_RE.test(token)) {
1744
1895
  const msg = "Token is not in the expected XXXX-XXXX format. Copy the full `agt impersonate connect <token>` command from the webapp.";
1745
1896
  if (json) jsonOutput({ ok: false, error: msg });
@@ -1755,6 +1906,16 @@ async function impersonateConnectCommand(token) {
1755
1906
  process.exitCode = 1;
1756
1907
  return;
1757
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
+ }
1758
1919
  if (!json) console.log(chalk10.bold("\nAugmented \u2014 Impersonate\n"));
1759
1920
  const spinner = ora10({
1760
1921
  text: "Redeeming token\u2026",
@@ -1785,25 +1946,27 @@ async function impersonateConnectCommand(token) {
1785
1946
  }
1786
1947
  spinner.text = "Writing persona files\u2026";
1787
1948
  const personaDir = ensureCodeNameDir(bundle.agent.code_name);
1788
- writeFileSync5(
1789
- join7(personaDir, "CLAUDE.md"),
1949
+ writeFileSync6(
1950
+ join9(personaDir, "CLAUDE.md"),
1790
1951
  bundle.artifacts["CLAUDE.md"]
1791
1952
  );
1792
- writeFileSync5(
1793
- join7(personaDir, ".mcp.json"),
1953
+ writeFileSync6(
1954
+ join9(personaDir, ".mcp.json"),
1794
1955
  bundle.artifacts[".mcp.json"]
1795
1956
  );
1796
1957
  spinner.text = "Swapping persona files\u2026";
1797
- const projectCwd = process.cwd();
1958
+ const projectCwd = options.workdir ? ensureImpersonateWorkdir(bundle.agent.code_name) : process.cwd();
1798
1959
  const backups = {};
1799
1960
  const swapped = [];
1961
+ let hookBackup;
1800
1962
  try {
1801
1963
  for (const file of SWAP_FILES) {
1802
- const projectPath = join7(projectCwd, file);
1803
- const kind = swapInPersonaFile(projectPath, join7(personaDir, file));
1964
+ const projectPath = join9(projectCwd, file);
1965
+ const kind = swapInPersonaFile(projectPath, join9(personaDir, file));
1804
1966
  backups[file] = kind;
1805
1967
  swapped.push({ file, kind });
1806
1968
  }
1969
+ hookBackup = registerIntroduceHook(projectCwd);
1807
1970
  const manifest = {
1808
1971
  code_name: bundle.agent.code_name,
1809
1972
  agent_id: bundle.agent.agent_id,
@@ -1814,13 +1977,20 @@ async function impersonateConnectCommand(token) {
1814
1977
  minted_at: (/* @__PURE__ */ new Date()).toISOString(),
1815
1978
  project_cwd: projectCwd,
1816
1979
  host: host2,
1817
- backups
1980
+ backups,
1981
+ hook: hookBackup
1818
1982
  };
1819
1983
  writeActiveManifest(manifest);
1820
1984
  } catch (err) {
1985
+ if (hookBackup) {
1986
+ try {
1987
+ unregisterIntroduceHook(hookBackup);
1988
+ } catch {
1989
+ }
1990
+ }
1821
1991
  for (const { file, kind } of [...swapped].reverse()) {
1822
1992
  try {
1823
- restoreSwapped(join7(projectCwd, file), kind);
1993
+ restoreSwapped(join9(projectCwd, file), kind);
1824
1994
  } catch {
1825
1995
  }
1826
1996
  }
@@ -1853,11 +2023,44 @@ async function impersonateConnectCommand(token) {
1853
2023
  console.log(` Host: ${chalk10.dim(host2)}`);
1854
2024
  console.log(` Swapped: ${chalk10.dim(SWAP_FILES.join(", "))}`);
1855
2025
  console.log();
1856
- info(
1857
- "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)."
1858
- );
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
+ });
1859
2062
  console.log();
1860
- info("When done: `agt impersonate exit`");
2063
+ info("When done: `agt impersonate exit` (still active).");
1861
2064
  }
1862
2065
  async function impersonateCurrentCommand() {
1863
2066
  requireImpersonateEnabled();
@@ -1936,7 +2139,7 @@ async function impersonateExitCommand() {
1936
2139
  }
1937
2140
  const restored = [];
1938
2141
  for (const [file, kind] of Object.entries(manifest.backups)) {
1939
- const projectPath = join7(manifest.project_cwd, file);
2142
+ const projectPath = join9(manifest.project_cwd, file);
1940
2143
  try {
1941
2144
  restoreSwapped(projectPath, kind);
1942
2145
  restored.push(file);
@@ -1945,6 +2148,14 @@ async function impersonateExitCommand() {
1945
2148
  if (!json) error(`Failed to restore ${file}: ${msg}`);
1946
2149
  }
1947
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
+ }
1948
2159
  clearActiveManifest();
1949
2160
  if (json) {
1950
2161
  jsonOutput({
@@ -1966,6 +2177,37 @@ async function impersonateExitCommand() {
1966
2177
  }
1967
2178
  info("Restart Claude Code to pick up your original persona.");
1968
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
+ }
1969
2211
 
1970
2212
  // src/commands/drift.ts
1971
2213
  import chalk11 from "chalk";
@@ -1974,7 +2216,7 @@ import ora11 from "ora";
1974
2216
  // ../../packages/core/dist/drift/live-state-reader.js
1975
2217
  import { readFile } from "fs/promises";
1976
2218
  import { createHash } from "crypto";
1977
- import { join as join8 } from "path";
2219
+ import { join as join10 } from "path";
1978
2220
  import JSON5 from "json5";
1979
2221
  async function hashFile(filePath) {
1980
2222
  try {
@@ -1998,8 +2240,8 @@ async function readLiveState(options) {
1998
2240
  const toolsFile = trackedFiles.find((f) => f.includes("TOOLS")) ?? "TOOLS.md";
1999
2241
  const [frameworkConfig, charterHash, toolsHash] = await Promise.all([
2000
2242
  readJsonFile(options.configPath),
2001
- hashFile(join8(options.teamDir, charterFile)),
2002
- hashFile(join8(options.teamDir, toolsFile))
2243
+ hashFile(join10(options.teamDir, charterFile)),
2244
+ hashFile(join10(options.teamDir, toolsFile))
2003
2245
  ]);
2004
2246
  return {
2005
2247
  frameworkConfig,
@@ -2454,7 +2696,7 @@ async function hostDecommissionCommand(hostName) {
2454
2696
  }
2455
2697
 
2456
2698
  // src/commands/host-pair.ts
2457
- import { spawn, spawnSync } from "child_process";
2699
+ import { spawn as spawn2, spawnSync } from "child_process";
2458
2700
  import chalk13 from "chalk";
2459
2701
  async function hostPairCommand(name, opts) {
2460
2702
  const teamSlug = requireTeam();
@@ -2535,7 +2777,7 @@ async function hostPairCommand(name, opts) {
2535
2777
  console.log();
2536
2778
  console.log(chalk13.dim("Exit the shell (Ctrl+D) when Claude Code reports successful login."));
2537
2779
  console.log();
2538
- const tunnel = spawn(
2780
+ const tunnel = spawn2(
2539
2781
  "aws",
2540
2782
  [
2541
2783
  "ssm",
@@ -2592,7 +2834,7 @@ function preflightAws() {
2592
2834
  }
2593
2835
  function runInteractive(cmd, args) {
2594
2836
  return new Promise((resolve2) => {
2595
- const child = spawn(cmd, args, { stdio: "inherit" });
2837
+ const child = spawn2(cmd, args, { stdio: "inherit" });
2596
2838
  child.on("exit", (code) => resolve2(code ?? 0));
2597
2839
  child.on("error", () => resolve2(1));
2598
2840
  });
@@ -2608,15 +2850,15 @@ function terminate(child) {
2608
2850
  // src/commands/manager-watch.tsx
2609
2851
  import { useEffect, useState, useMemo } from "react";
2610
2852
  import { render, Box, Text, useApp, useInput } from "ink";
2611
- import { existsSync as existsSync4, readFileSync as readFileSync3, statSync, openSync, readSync, closeSync } from "fs";
2853
+ import { existsSync as existsSync5, readFileSync as readFileSync5, statSync, openSync, readSync, closeSync } from "fs";
2612
2854
  import { homedir as homedir3 } from "os";
2613
- import { join as join9 } from "path";
2855
+ import { join as join11 } from "path";
2614
2856
  import { jsx, jsxs } from "react/jsx-runtime";
2615
2857
  var REFRESH_MS = 1e3;
2616
2858
  var LOG_TAIL_LINES = 200;
2617
2859
  var DETAIL_RECENT_LINES = 50;
2618
2860
  function managerWatchCommand(opts = {}) {
2619
- const configDir = opts.configDir ?? join9(homedir3(), ".augmented");
2861
+ const configDir = opts.configDir ?? join11(homedir3(), ".augmented");
2620
2862
  const paths = getManagerPaths(configDir);
2621
2863
  const isTty = process.stdout.isTTY === true && process.stdin.isTTY === true;
2622
2864
  if (opts.noTui || !isTty) {
@@ -2640,15 +2882,15 @@ function managerWatchCommand(opts = {}) {
2640
2882
  }
2641
2883
  function readState(stateFile) {
2642
2884
  try {
2643
- if (!existsSync4(stateFile)) return null;
2644
- const raw = readFileSync3(stateFile, "utf-8");
2885
+ if (!existsSync5(stateFile)) return null;
2886
+ const raw = readFileSync5(stateFile, "utf-8");
2645
2887
  return JSON.parse(raw);
2646
2888
  } catch {
2647
2889
  return null;
2648
2890
  }
2649
2891
  }
2650
2892
  function tailLogFile(logFile, lines) {
2651
- if (!existsSync4(logFile)) return [];
2893
+ if (!existsSync5(logFile)) return [];
2652
2894
  try {
2653
2895
  const fileSize = statSync(logFile).size;
2654
2896
  if (fileSize === 0) return [];
@@ -2889,7 +3131,7 @@ function truncate(s, max) {
2889
3131
  return s.length > max ? `${s.slice(0, Math.max(0, max - 1))}\u2026` : s;
2890
3132
  }
2891
3133
  function streamLogFile(logFile) {
2892
- if (!existsSync4(logFile)) {
3134
+ if (!existsSync5(logFile)) {
2893
3135
  process.stderr.write(`No manager log found at ${logFile}.
2894
3136
  Start the manager first: agt manager start
2895
3137
  `);
@@ -2898,7 +3140,7 @@ Start the manager first: agt manager start
2898
3140
  }
2899
3141
  let lastSize = 0;
2900
3142
  try {
2901
- const initial = readFileSync3(logFile, "utf-8");
3143
+ const initial = readFileSync5(logFile, "utf-8");
2902
3144
  process.stdout.write(initial);
2903
3145
  lastSize = statSync(logFile).size;
2904
3146
  } catch (err) {
@@ -2935,14 +3177,14 @@ Start the manager first: agt manager start
2935
3177
  // src/commands/agent.ts
2936
3178
  import chalk14 from "chalk";
2937
3179
  import JSON52 from "json5";
2938
- import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
2939
- import { join as join10 } from "path";
3180
+ import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
3181
+ import { join as join12 } from "path";
2940
3182
  async function agentShowCommand(codeName, opts) {
2941
3183
  const json = isJsonMode();
2942
- const unifiedDir = join10(opts.configDir, codeName, "provision");
2943
- const legacyDir = join10(opts.configDir, codeName, "claudecode", "provision");
2944
- const agentDir = existsSync5(unifiedDir) ? unifiedDir : legacyDir;
2945
- const hasLocalConfig = existsSync5(agentDir);
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);
2946
3188
  let apiChannels = null;
2947
3189
  let apiAgent = null;
2948
3190
  if (getApiKey()) {
@@ -2975,34 +3217,34 @@ async function agentShowCommand(codeName, opts) {
2975
3217
  let openclawConfig = null;
2976
3218
  let agentState = null;
2977
3219
  if (hasLocalConfig) {
2978
- const charterPath = join10(agentDir, "CHARTER.md");
2979
- if (existsSync5(charterPath)) {
2980
- const raw = readFileSync4(charterPath, "utf-8");
3220
+ const charterPath = join12(agentDir, "CHARTER.md");
3221
+ if (existsSync6(charterPath)) {
3222
+ const raw = readFileSync6(charterPath, "utf-8");
2981
3223
  const parsed = extractFrontmatter(raw);
2982
3224
  if (parsed.frontmatter) {
2983
3225
  charter = parsed.frontmatter;
2984
3226
  }
2985
3227
  }
2986
- const toolsPath = join10(agentDir, "TOOLS.md");
2987
- if (existsSync5(toolsPath)) {
2988
- const raw = readFileSync4(toolsPath, "utf-8");
3228
+ const toolsPath = join12(agentDir, "TOOLS.md");
3229
+ if (existsSync6(toolsPath)) {
3230
+ const raw = readFileSync6(toolsPath, "utf-8");
2989
3231
  const parsed = extractFrontmatter(raw);
2990
3232
  if (parsed.frontmatter) {
2991
3233
  tools = parsed.frontmatter;
2992
3234
  }
2993
3235
  }
2994
- const openclawPath = join10(agentDir, "openclaw.json5");
2995
- if (existsSync5(openclawPath)) {
3236
+ const openclawPath = join12(agentDir, "openclaw.json5");
3237
+ if (existsSync6(openclawPath)) {
2996
3238
  try {
2997
- const raw = readFileSync4(openclawPath, "utf-8");
3239
+ const raw = readFileSync6(openclawPath, "utf-8");
2998
3240
  openclawConfig = JSON52.parse(raw);
2999
3241
  } catch {
3000
3242
  }
3001
3243
  }
3002
- const statePath = join10(opts.configDir, "manager-state.json");
3003
- if (existsSync5(statePath)) {
3244
+ const statePath = join12(opts.configDir, "manager-state.json");
3245
+ if (existsSync6(statePath)) {
3004
3246
  try {
3005
- const raw = readFileSync4(statePath, "utf-8");
3247
+ const raw = readFileSync6(statePath, "utf-8");
3006
3248
  const state = JSON.parse(raw);
3007
3249
  agentState = state.agents?.find((a) => a.codeName === codeName) ?? null;
3008
3250
  } catch {
@@ -3339,8 +3581,8 @@ async function kanbanRecurringDisableCommand(titleOrId, opts) {
3339
3581
  }
3340
3582
 
3341
3583
  // src/commands/setup.ts
3342
- import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, accessSync, constants as fsConstants } from "fs";
3343
- import { join as join11, dirname } from "path";
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";
3344
3586
  import { homedir as homedir4 } from "os";
3345
3587
  import chalk16 from "chalk";
3346
3588
  import ora14 from "ora";
@@ -3348,15 +3590,15 @@ function detectShellProfile() {
3348
3590
  const shell = process.env["SHELL"] ?? "";
3349
3591
  const home = homedir4();
3350
3592
  if (shell.includes("zsh")) {
3351
- return join11(home, ".zshrc");
3593
+ return join13(home, ".zshrc");
3352
3594
  }
3353
3595
  if (shell.includes("fish")) {
3354
- const fishConfig = join11(home, ".config", "fish", "config.fish");
3596
+ const fishConfig = join13(home, ".config", "fish", "config.fish");
3355
3597
  return fishConfig;
3356
3598
  }
3357
- const bashrc = join11(home, ".bashrc");
3358
- if (existsSync6(bashrc)) return bashrc;
3359
- return join11(home, ".bash_profile");
3599
+ const bashrc = join13(home, ".bashrc");
3600
+ if (existsSync7(bashrc)) return bashrc;
3601
+ return join13(home, ".bash_profile");
3360
3602
  }
3361
3603
  function maybeWriteSystemWideEnv(apiUrl, apiKey, consoleUrl) {
3362
3604
  const empty = { etcEnvironment: false, profileD: false, bashrc: false };
@@ -3382,14 +3624,14 @@ function quoteForFishShell(value) {
3382
3624
  }
3383
3625
  function writeEtcEnvironment(apiUrl, apiKey, consoleUrl) {
3384
3626
  const envPath = "/etc/environment";
3385
- if (!existsSync6(envPath)) return false;
3627
+ if (!existsSync7(envPath)) return false;
3386
3628
  try {
3387
3629
  accessSync(envPath, fsConstants.W_OK);
3388
3630
  } catch {
3389
3631
  return false;
3390
3632
  }
3391
3633
  try {
3392
- const current = readFileSync5(envPath, "utf-8");
3634
+ const current = readFileSync7(envPath, "utf-8");
3393
3635
  const stripped = current.split(/\r?\n/).filter((line) => !/^\s*(?:export\s+)?AGT_(?:HOST|API_KEY|CONSOLE_URL)=/.test(line)).join("\n");
3394
3636
  const base = stripped.endsWith("\n") || stripped.length === 0 ? stripped : `${stripped}
3395
3637
  `;
@@ -3400,7 +3642,7 @@ function writeEtcEnvironment(apiUrl, apiKey, consoleUrl) {
3400
3642
  if (consoleUrl) lines.push(`AGT_CONSOLE_URL=${quoteForEtcEnvironment(consoleUrl)}`);
3401
3643
  const appended = `${base}${lines.join("\n")}
3402
3644
  `;
3403
- writeFileSync6(envPath, appended, { mode: 420 });
3645
+ writeFileSync7(envPath, appended, { mode: 420 });
3404
3646
  return true;
3405
3647
  } catch {
3406
3648
  return false;
@@ -3408,7 +3650,7 @@ function writeEtcEnvironment(apiUrl, apiKey, consoleUrl) {
3408
3650
  }
3409
3651
  function writeProfileDScript(apiUrl, apiKey, consoleUrl) {
3410
3652
  const profileD = "/etc/profile.d";
3411
- if (!existsSync6(profileD)) return false;
3653
+ if (!existsSync7(profileD)) return false;
3412
3654
  try {
3413
3655
  accessSync(profileD, fsConstants.W_OK);
3414
3656
  } catch {
@@ -3423,7 +3665,7 @@ function writeProfileDScript(apiUrl, apiKey, consoleUrl) {
3423
3665
  ];
3424
3666
  if (consoleUrl) lines.push(`export AGT_CONSOLE_URL=${quoteForPosixShell(consoleUrl)}`);
3425
3667
  lines.push("");
3426
- writeFileSync6(scriptPath, lines.join("\n"), { mode: 420 });
3668
+ writeFileSync7(scriptPath, lines.join("\n"), { mode: 420 });
3427
3669
  return true;
3428
3670
  } catch {
3429
3671
  return false;
@@ -3431,14 +3673,14 @@ function writeProfileDScript(apiUrl, apiKey, consoleUrl) {
3431
3673
  }
3432
3674
  function ensureBashrcSourcesProfileD() {
3433
3675
  const bashrc = "/etc/bashrc";
3434
- if (!existsSync6(bashrc)) return false;
3676
+ if (!existsSync7(bashrc)) return false;
3435
3677
  try {
3436
3678
  accessSync(bashrc, fsConstants.W_OK);
3437
3679
  } catch {
3438
3680
  return false;
3439
3681
  }
3440
3682
  try {
3441
- const current = readFileSync5(bashrc, "utf-8");
3683
+ const current = readFileSync7(bashrc, "utf-8");
3442
3684
  const marker = "# Augmented (agt) \u2014 source system-wide AGT env";
3443
3685
  if (current.includes(marker)) return true;
3444
3686
  const snippet = [
@@ -3449,7 +3691,7 @@ function ensureBashrcSourcesProfileD() {
3449
3691
  "fi",
3450
3692
  ""
3451
3693
  ].join("\n");
3452
- writeFileSync6(bashrc, current + snippet);
3694
+ writeFileSync7(bashrc, current + snippet);
3453
3695
  return true;
3454
3696
  } catch {
3455
3697
  return false;
@@ -3564,12 +3806,12 @@ async function setupCommand(token) {
3564
3806
  );
3565
3807
  }
3566
3808
  const exportLines = buildExportLines(shell, finalApiUrl, setupResult.api_key, consoleUrl);
3567
- const profileDir = dirname(profilePath);
3568
- if (!existsSync6(profileDir)) {
3569
- mkdirSync5(profileDir, { recursive: true });
3809
+ const profileDir = dirname2(profilePath);
3810
+ if (!existsSync7(profileDir)) {
3811
+ mkdirSync6(profileDir, { recursive: true });
3570
3812
  }
3571
3813
  const marker = "# Augmented (agt) host configuration";
3572
- const current = existsSync6(profilePath) ? readFileSync5(profilePath, "utf-8") : "";
3814
+ const current = existsSync7(profilePath) ? readFileSync7(profilePath, "utf-8") : "";
3573
3815
  let updated;
3574
3816
  if (current.includes(marker)) {
3575
3817
  updated = current.replace(
@@ -3582,7 +3824,7 @@ async function setupCommand(token) {
3582
3824
  } else {
3583
3825
  updated = current + exportLines;
3584
3826
  }
3585
- writeFileSync6(profilePath, updated.endsWith("\n") ? updated : `${updated}
3827
+ writeFileSync7(profilePath, updated.endsWith("\n") ? updated : `${updated}
3586
3828
  `);
3587
3829
  if (!json) {
3588
3830
  success(`Environment variables written to ${chalk16.bold(profilePath)}`);
@@ -3600,7 +3842,7 @@ async function setupCommand(token) {
3600
3842
  const managerSpinner = ora14({ text: "Starting manager daemon\u2026", isSilent: json });
3601
3843
  managerSpinner.start();
3602
3844
  try {
3603
- const configDir = join11(homedir4(), ".augmented");
3845
+ const configDir = join13(homedir4(), ".augmented");
3604
3846
  const { pid } = startWatchdog({ intervalMs: 1e4, configDir, detached: true });
3605
3847
  managerSpinner.succeed(`Manager started (PID ${pid})`);
3606
3848
  } catch (err) {
@@ -3932,7 +4174,7 @@ function getAcpAgent(name) {
3932
4174
  }
3933
4175
 
3934
4176
  // ../../packages/core/dist/acp/client.js
3935
- import { spawn as spawn2 } from "child_process";
4177
+ import { spawn as spawn3 } from "child_process";
3936
4178
  function resolveAgentCommand(agentName) {
3937
4179
  const adapter = getAcpAgent(agentName);
3938
4180
  if (adapter) {
@@ -3948,7 +4190,7 @@ function resolveAgentCommand(agentName) {
3948
4190
  }
3949
4191
  async function runAcpx(args, options = {}) {
3950
4192
  return new Promise((resolve2) => {
3951
- const child = spawn2("acpx", args, {
4193
+ const child = spawn3("acpx", args, {
3952
4194
  cwd: options.cwd,
3953
4195
  stdio: ["pipe", "pipe", "pipe"],
3954
4196
  env: { ...process.env }
@@ -4148,10 +4390,10 @@ async function acpxCloseCommand(agent2, _opts, cmd) {
4148
4390
 
4149
4391
  // src/commands/update.ts
4150
4392
  import { execFileSync, execSync } from "child_process";
4151
- import { existsSync as existsSync7, realpathSync as realpathSync2 } from "fs";
4393
+ import { existsSync as existsSync8, realpathSync as realpathSync2 } from "fs";
4152
4394
  import chalk18 from "chalk";
4153
4395
  import ora16 from "ora";
4154
- var cliVersion = true ? "0.26.2" : "dev";
4396
+ var cliVersion = true ? "0.27.0-test.4" : "dev";
4155
4397
  async function fetchLatestVersion() {
4156
4398
  const host2 = getHost();
4157
4399
  if (!host2) return null;
@@ -4171,9 +4413,9 @@ async function fetchLatestVersion() {
4171
4413
  }
4172
4414
  function isNewerVersion(local, remote) {
4173
4415
  if (local === "dev") return false;
4174
- const parse = (v) => v.replace(/^v/, "").split(".").map(Number);
4175
- const lParts = parse(local);
4176
- const rParts = parse(remote);
4416
+ const parse2 = (v) => v.replace(/^v/, "").split(".").map(Number);
4417
+ const lParts = parse2(local);
4418
+ const rParts = parse2(remote);
4177
4419
  const lMaj = lParts[0] ?? 0, lMin = lParts[1] ?? 0, lPat = lParts[2] ?? 0;
4178
4420
  const rMaj = rParts[0] ?? 0, rMin = rParts[1] ?? 0, rPat = rParts[2] ?? 0;
4179
4421
  if (rMaj !== lMaj) return rMaj > lMaj;
@@ -4255,7 +4497,7 @@ function performUpdate(version) {
4255
4497
  function detectBrewOwner() {
4256
4498
  for (const prefix of ["/home/linuxbrew/.linuxbrew", "/opt/homebrew", "/usr/local"]) {
4257
4499
  const cellar = `${prefix}/Cellar`;
4258
- if (!existsSync7(cellar)) continue;
4500
+ if (!existsSync8(cellar)) continue;
4259
4501
  try {
4260
4502
  return execSync(`stat -c %U "${cellar}" 2>/dev/null || stat -f %Su "${cellar}"`, { encoding: "utf-8" }).trim() || null;
4261
4503
  } catch {
@@ -4683,7 +4925,7 @@ function handleError(err) {
4683
4925
  }
4684
4926
 
4685
4927
  // src/bin/agt.ts
4686
- var cliVersion2 = true ? "0.26.2" : "dev";
4928
+ var cliVersion2 = true ? "0.27.0-test.4" : "dev";
4687
4929
  var program = new Command();
4688
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");
4689
4931
  program.hook("preAction", (thisCommand) => {
@@ -4702,10 +4944,27 @@ var impersonate = program.command("impersonate").description(
4702
4944
  "Operate as a managed agent locally (experimental, gated by AGT_IMPERSONATE_ENABLED)"
4703
4945
  );
4704
4946
  impersonate.command("connect <token>").description(
4705
- "Redeem a XXXX-XXXX token from the webapp, render the agent's CLAUDE.md + .mcp.json, and symlink them into the current project"
4706
- ).action(impersonateConnectCommand);
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
+ );
4707
4965
  impersonate.command("current").description('Print the active impersonation (or "none")').action(impersonateCurrentCommand);
4708
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);
4709
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);
4710
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);
4711
4970
  var channels = program.command("channels").description("Manage and inspect channel configuration");
@@ -4738,16 +4997,16 @@ host.command("pair <host-name>").description("Start an SSM port-forward + shell
4738
4997
  })
4739
4998
  );
4740
4999
  var manager = program.command("manager").description("Host config sync daemon \u2014 keeps local agent files in sync with API");
4741
- 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", join12(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);
4742
- manager.command("stop").description("Stop the running manager daemon").option("--config-dir <dir>", "Config directory for agent files", join12(homedir5(), ".augmented")).action(managerStopCommand);
4743
- manager.command("status").description("Show the current manager daemon status and discovered agents").option("--config-dir <dir>", "Config directory for agent files", join12(homedir5(), ".augmented")).action(managerStatusCommand);
4744
- 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", join12(homedir5(), ".augmented")).option("--no-tui", "Skip the TUI and stream the manager log to stdout instead (CI / scripts)").action(managerWatchCommand);
4745
- 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", join12(homedir5(), ".augmented")).action(managerInstallCommand);
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);
4746
5005
  manager.command("uninstall").description("Remove the OS-level supervisor (launchctl unload + delete plist on macOS). Idempotent.").action(managerUninstallCommand);
4747
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);
4748
5007
  manager.command("uninstall-system-unit").description("Remove the system-level systemd unit (Linux, root). Idempotent. (ENG-4706)").action(managerUninstallSystemUnitCommand);
4749
5008
  var agent = program.command("agent").description("Inspect and manage agents");
4750
- agent.command("show <code-name>").description("Display an agent's provisioned OpenClaw configuration").option("--config-dir <dir>", "Config directory", join12(homedir5(), ".augmented")).option("--all-channels", "Show all channels (including disabled)").action(agentShowCommand);
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);
4751
5010
  var kanban = program.command("kanban").description("Manage agent kanban boards");
4752
5011
  kanban.command("list").description("List kanban board items for an agent").requiredOption("--agent <code-name>", "Agent code name").action(kanbanListCommand);
4753
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);