@integrity-labs/agt-cli 0.27.0 → 0.27.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/agt.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ApiError,
4
+ DEFAULT_AGT_HOST,
4
5
  api,
5
6
  error,
6
7
  exchangeApiKey,
@@ -26,7 +27,7 @@ import {
26
27
  success,
27
28
  table,
28
29
  warn
29
- } from "../chunk-YWXPSVI5.js";
30
+ } from "../chunk-5N3JKXMU.js";
30
31
  import {
31
32
  CHANNEL_REGISTRY,
32
33
  DEPLOYMENT_TEMPLATES,
@@ -52,10 +53,10 @@ import {
52
53
  renderTemplate,
53
54
  resolveChannels,
54
55
  serializeManifestForSlackCli
55
- } from "../chunk-BKLEFKUZ.js";
56
+ } from "../chunk-JTZZ6YB2.js";
56
57
 
57
58
  // src/bin/agt.ts
58
- import { join as join12 } from "path";
59
+ import { join as join14 } from "path";
59
60
  import { homedir as homedir5 } from "os";
60
61
  import { Command } from "commander";
61
62
 
@@ -550,8 +551,8 @@ async function lintCommand(path) {
550
551
  process.exitCode = 1;
551
552
  return;
552
553
  }
553
- const { readdirSync, statSync: statSync2 } = await import("fs");
554
- const entries = readdirSync(augmentedDir);
554
+ const { readdirSync: readdirSync2, statSync: statSync2 } = await import("fs");
555
+ const entries = readdirSync2(augmentedDir);
555
556
  for (const entry of entries) {
556
557
  const entryPath = join2(augmentedDir, entry);
557
558
  if (statSync2(entryPath).isDirectory()) {
@@ -1556,8 +1557,8 @@ async function provisionCommand(codeName, options) {
1556
1557
  import chalk10 from "chalk";
1557
1558
  import ora10 from "ora";
1558
1559
  import { spawn } from "child_process";
1559
- import { writeFileSync as writeFileSync5 } from "fs";
1560
- import { join as join7 } from "path";
1560
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync6 } from "fs";
1561
+ import { join as join9 } from "path";
1561
1562
 
1562
1563
  // src/lib/impersonate-flag.ts
1563
1564
  var ENV_VAR = "AGT_IMPERSONATE_ENABLED";
@@ -1621,6 +1622,11 @@ function ensureCodeNameDir(codeName) {
1621
1622
  mkdirSync4(dir, { recursive: true });
1622
1623
  return dir;
1623
1624
  }
1625
+ function ensureImpersonateWorkdir(codeName) {
1626
+ const dir = join6(ensureCodeNameDir(codeName), "workdir");
1627
+ mkdirSync4(dir, { recursive: true });
1628
+ return dir;
1629
+ }
1624
1630
  function readActiveManifest() {
1625
1631
  if (!existsSync2(ACTIVE_MANIFEST_PATH)) return null;
1626
1632
  try {
@@ -1668,8 +1674,19 @@ import {
1668
1674
  unlinkSync
1669
1675
  } from "fs";
1670
1676
  import { homedir as homedir2 } from "os";
1671
- import { sep } from "path";
1677
+ import { dirname, join as join7, parse, sep } from "path";
1672
1678
  var BACKUP_SUFFIX = ".pre-impersonate-backup";
1679
+ function findGitWorkTreeRoot(startDir) {
1680
+ const { root } = parse(startDir);
1681
+ let dir = startDir;
1682
+ for (; ; ) {
1683
+ if (existsSync3(join7(dir, ".git"))) return dir;
1684
+ if (dir === root) return null;
1685
+ const parent = dirname(dir);
1686
+ if (parent === dir) return null;
1687
+ dir = parent;
1688
+ }
1689
+ }
1673
1690
  function swapInPersonaFile(projectPath, personaPath) {
1674
1691
  let kind;
1675
1692
  if (!lstatSafe(projectPath)) {
@@ -1734,6 +1751,139 @@ function lstatSafe(path) {
1734
1751
  }
1735
1752
  }
1736
1753
 
1754
+ // src/lib/impersonate-hook.ts
1755
+ import {
1756
+ copyFileSync,
1757
+ existsSync as existsSync4,
1758
+ mkdirSync as mkdirSync5,
1759
+ readdirSync,
1760
+ readFileSync as readFileSync3,
1761
+ renameSync as renameSync3,
1762
+ rmdirSync,
1763
+ unlinkSync as unlinkSync2,
1764
+ writeFileSync as writeFileSync5
1765
+ } from "fs";
1766
+ import { join as join8 } from "path";
1767
+ var INTRODUCE_HOOK_COMMAND = "agt impersonate introduce";
1768
+ var SESSION_START_MATCHER = "startup";
1769
+ function registerIntroduceHook(projectCwd, command = INTRODUCE_HOOK_COMMAND) {
1770
+ const claudeDir = join8(projectCwd, ".claude");
1771
+ const createdClaudeDir = !existsSync4(claudeDir);
1772
+ mkdirSync5(claudeDir, { recursive: true });
1773
+ const settingsPath = join8(claudeDir, "settings.local.json");
1774
+ const settingsExisted = existsSync4(settingsPath);
1775
+ const backupPath = settingsPath + BACKUP_SUFFIX;
1776
+ let settings = {};
1777
+ if (settingsExisted) {
1778
+ if (!existsSync4(backupPath)) {
1779
+ copyFileSync(settingsPath, backupPath);
1780
+ }
1781
+ settings = asPlainObject(safeParseJson(readFileSync3(settingsPath, "utf-8")));
1782
+ }
1783
+ const hooks = asPlainObject(settings["hooks"]);
1784
+ const existing = Array.isArray(hooks[SESSION_START_KEY]) ? [...hooks[SESSION_START_KEY]] : [];
1785
+ const alreadyRegistered = existing.some(
1786
+ (entry) => entryMatchesCommand(entry, command)
1787
+ );
1788
+ if (!alreadyRegistered) {
1789
+ existing.push({
1790
+ matcher: SESSION_START_MATCHER,
1791
+ hooks: [{ type: "command", command }]
1792
+ });
1793
+ }
1794
+ hooks[SESSION_START_KEY] = existing;
1795
+ settings["hooks"] = hooks;
1796
+ writeFileSync5(settingsPath, JSON.stringify(settings, null, 2) + "\n");
1797
+ return {
1798
+ settings_path: settingsPath,
1799
+ settings_backup: settingsExisted ? "had-file" : "didnt-exist",
1800
+ created_claude_dir: createdClaudeDir
1801
+ };
1802
+ }
1803
+ function unregisterIntroduceHook(backup) {
1804
+ const { settings_path, settings_backup, created_claude_dir } = backup;
1805
+ const backupPath = settings_path + BACKUP_SUFFIX;
1806
+ if (settings_backup === "had-file") {
1807
+ if (existsSync4(backupPath)) {
1808
+ renameSync3(backupPath, settings_path);
1809
+ }
1810
+ } else {
1811
+ if (existsSync4(settings_path)) unlinkSync2(settings_path);
1812
+ if (existsSync4(backupPath)) unlinkSync2(backupPath);
1813
+ }
1814
+ if (created_claude_dir) {
1815
+ const claudeDir = dirOf(settings_path);
1816
+ try {
1817
+ if (existsSync4(claudeDir) && readdirSync(claudeDir).length === 0) {
1818
+ rmdirSync(claudeDir);
1819
+ }
1820
+ } catch {
1821
+ }
1822
+ }
1823
+ }
1824
+ var SESSION_START_KEY = "SessionStart";
1825
+ function entryMatchesCommand(entry, command) {
1826
+ if (entry.matcher !== SESSION_START_MATCHER) return false;
1827
+ const entryHooks = entry.hooks;
1828
+ return Array.isArray(entryHooks) && entryHooks.some(
1829
+ (h) => typeof h === "object" && h !== null && h.type === "command" && h.command === command
1830
+ );
1831
+ }
1832
+ function dirOf(filePath) {
1833
+ const idx = filePath.lastIndexOf("/");
1834
+ return idx === -1 ? filePath : filePath.slice(0, idx);
1835
+ }
1836
+ function safeParseJson(text) {
1837
+ try {
1838
+ return JSON.parse(text);
1839
+ } catch {
1840
+ return null;
1841
+ }
1842
+ }
1843
+ function asPlainObject(value) {
1844
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
1845
+ }
1846
+
1847
+ // src/lib/impersonate-introduce.ts
1848
+ function parseIdentityFromClaudeMd(content) {
1849
+ const headingMatch = content.match(/^#\s+(.+?)\s*$/m);
1850
+ const displayName = headingMatch?.[1]?.trim() || null;
1851
+ const roleMatch = content.match(
1852
+ /^You are \*\*[^*]+\*\*,\s*\*\*([^*]+)\*\*/m
1853
+ );
1854
+ const role = roleMatch?.[1]?.trim() || null;
1855
+ return { displayName, role };
1856
+ }
1857
+ function parseIntegrationsFromMcpJson(content) {
1858
+ let parsed;
1859
+ try {
1860
+ parsed = JSON.parse(content);
1861
+ } catch {
1862
+ return [];
1863
+ }
1864
+ const servers = parsed?.mcpServers;
1865
+ if (!servers || typeof servers !== "object" || Array.isArray(servers)) {
1866
+ return [];
1867
+ }
1868
+ return Object.keys(servers).map(prettifyServerName);
1869
+ }
1870
+ function prettifyServerName(key) {
1871
+ return key.split(/[-_]/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
1872
+ }
1873
+ function buildIntroduction(input4) {
1874
+ const { displayName, codeName, role, integrations } = input4;
1875
+ const spokenName = displayName || codeName;
1876
+ const codeSuffix = displayName && displayName !== codeName ? ` (${codeName})` : "";
1877
+ const roleClause = role ? `, ${role}` : "";
1878
+ const lines = [`I'm ${spokenName}${codeSuffix}${roleClause}.`, ""];
1879
+ if (integrations.length > 0) {
1880
+ lines.push(`Connected integrations: ${integrations.join(", ")}.`);
1881
+ } else {
1882
+ lines.push("No integrations are connected.");
1883
+ }
1884
+ return lines.join("\n");
1885
+ }
1886
+
1737
1887
  // src/commands/impersonate.ts
1738
1888
  var SWAP_FILES = ["CLAUDE.md", ".mcp.json"];
1739
1889
  var AGENT_IMPERSONATION_HEADER = "X-Agent-Impersonation";
@@ -1757,13 +1907,23 @@ async function impersonateConnectCommand(token, options = {}) {
1757
1907
  process.exitCode = 1;
1758
1908
  return;
1759
1909
  }
1910
+ if (!options.workdir) {
1911
+ const workTreeRoot = findGitWorkTreeRoot(process.cwd());
1912
+ if (workTreeRoot) {
1913
+ 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.`;
1914
+ if (json) jsonOutput({ ok: false, error: msg });
1915
+ else error(msg);
1916
+ process.exitCode = 1;
1917
+ return;
1918
+ }
1919
+ }
1760
1920
  if (!json) console.log(chalk10.bold("\nAugmented \u2014 Impersonate\n"));
1761
1921
  const spinner = ora10({
1762
1922
  text: "Redeeming token\u2026",
1763
1923
  isSilent: json
1764
1924
  });
1765
1925
  spinner.start();
1766
- const host2 = getHost();
1926
+ const host2 = (options.apiHost ?? DEFAULT_AGT_HOST).replace(/\/+$/, "");
1767
1927
  let bundle;
1768
1928
  try {
1769
1929
  const res = await fetch(`${host2}/impersonate/agent/redeem`, {
@@ -1787,25 +1947,27 @@ async function impersonateConnectCommand(token, options = {}) {
1787
1947
  }
1788
1948
  spinner.text = "Writing persona files\u2026";
1789
1949
  const personaDir = ensureCodeNameDir(bundle.agent.code_name);
1790
- writeFileSync5(
1791
- join7(personaDir, "CLAUDE.md"),
1950
+ writeFileSync6(
1951
+ join9(personaDir, "CLAUDE.md"),
1792
1952
  bundle.artifacts["CLAUDE.md"]
1793
1953
  );
1794
- writeFileSync5(
1795
- join7(personaDir, ".mcp.json"),
1954
+ writeFileSync6(
1955
+ join9(personaDir, ".mcp.json"),
1796
1956
  bundle.artifacts[".mcp.json"]
1797
1957
  );
1798
1958
  spinner.text = "Swapping persona files\u2026";
1799
- const projectCwd = process.cwd();
1959
+ const projectCwd = options.workdir ? ensureImpersonateWorkdir(bundle.agent.code_name) : process.cwd();
1800
1960
  const backups = {};
1801
1961
  const swapped = [];
1962
+ let hookBackup;
1802
1963
  try {
1803
1964
  for (const file of SWAP_FILES) {
1804
- const projectPath = join7(projectCwd, file);
1805
- const kind = swapInPersonaFile(projectPath, join7(personaDir, file));
1965
+ const projectPath = join9(projectCwd, file);
1966
+ const kind = swapInPersonaFile(projectPath, join9(personaDir, file));
1806
1967
  backups[file] = kind;
1807
1968
  swapped.push({ file, kind });
1808
1969
  }
1970
+ hookBackup = registerIntroduceHook(projectCwd);
1809
1971
  const manifest = {
1810
1972
  code_name: bundle.agent.code_name,
1811
1973
  agent_id: bundle.agent.agent_id,
@@ -1816,13 +1978,20 @@ async function impersonateConnectCommand(token, options = {}) {
1816
1978
  minted_at: (/* @__PURE__ */ new Date()).toISOString(),
1817
1979
  project_cwd: projectCwd,
1818
1980
  host: host2,
1819
- backups
1981
+ backups,
1982
+ hook: hookBackup
1820
1983
  };
1821
1984
  writeActiveManifest(manifest);
1822
1985
  } catch (err) {
1986
+ if (hookBackup) {
1987
+ try {
1988
+ unregisterIntroduceHook(hookBackup);
1989
+ } catch {
1990
+ }
1991
+ }
1823
1992
  for (const { file, kind } of [...swapped].reverse()) {
1824
1993
  try {
1825
- restoreSwapped(join7(projectCwd, file), kind);
1994
+ restoreSwapped(join9(projectCwd, file), kind);
1826
1995
  } catch {
1827
1996
  }
1828
1997
  }
@@ -1856,6 +2025,9 @@ async function impersonateConnectCommand(token, options = {}) {
1856
2025
  console.log(` Swapped: ${chalk10.dim(SWAP_FILES.join(", "))}`);
1857
2026
  console.log();
1858
2027
  if (!shouldLaunchClaude) {
2028
+ if (options.workdir) {
2029
+ info(`Start Claude Code in the impersonation workdir: \`cd ${projectCwd}\``);
2030
+ }
1859
2031
  info(
1860
2032
  "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)."
1861
2033
  );
@@ -1968,7 +2140,7 @@ async function impersonateExitCommand() {
1968
2140
  }
1969
2141
  const restored = [];
1970
2142
  for (const [file, kind] of Object.entries(manifest.backups)) {
1971
- const projectPath = join7(manifest.project_cwd, file);
2143
+ const projectPath = join9(manifest.project_cwd, file);
1972
2144
  try {
1973
2145
  restoreSwapped(projectPath, kind);
1974
2146
  restored.push(file);
@@ -1977,6 +2149,14 @@ async function impersonateExitCommand() {
1977
2149
  if (!json) error(`Failed to restore ${file}: ${msg}`);
1978
2150
  }
1979
2151
  }
2152
+ if (manifest.hook) {
2153
+ try {
2154
+ unregisterIntroduceHook(manifest.hook);
2155
+ } catch (err) {
2156
+ const msg = err instanceof Error ? err.message : String(err);
2157
+ if (!json) error(`Failed to remove introduce hook: ${msg}`);
2158
+ }
2159
+ }
1980
2160
  clearActiveManifest();
1981
2161
  if (json) {
1982
2162
  jsonOutput({
@@ -1998,6 +2178,37 @@ async function impersonateExitCommand() {
1998
2178
  }
1999
2179
  info("Restart Claude Code to pick up your original persona.");
2000
2180
  }
2181
+ async function impersonateIntroduceCommand() {
2182
+ try {
2183
+ const manifest = readActiveManifest();
2184
+ if (!manifest || isExpired(manifest)) return;
2185
+ const cwd = manifest.project_cwd;
2186
+ const identity = readClaudeMdIdentity(join9(cwd, "CLAUDE.md"));
2187
+ const integrations = readMcpIntegrations(join9(cwd, ".mcp.json"));
2188
+ const intro = buildIntroduction({
2189
+ displayName: identity.displayName,
2190
+ codeName: manifest.code_name,
2191
+ role: identity.role,
2192
+ integrations
2193
+ });
2194
+ process.stdout.write(intro + "\n");
2195
+ } catch {
2196
+ }
2197
+ }
2198
+ function readClaudeMdIdentity(path) {
2199
+ try {
2200
+ return parseIdentityFromClaudeMd(readFileSync4(path, "utf-8"));
2201
+ } catch {
2202
+ return { displayName: null, role: null };
2203
+ }
2204
+ }
2205
+ function readMcpIntegrations(path) {
2206
+ try {
2207
+ return parseIntegrationsFromMcpJson(readFileSync4(path, "utf-8"));
2208
+ } catch {
2209
+ return [];
2210
+ }
2211
+ }
2001
2212
 
2002
2213
  // src/commands/drift.ts
2003
2214
  import chalk11 from "chalk";
@@ -2006,7 +2217,7 @@ import ora11 from "ora";
2006
2217
  // ../../packages/core/dist/drift/live-state-reader.js
2007
2218
  import { readFile } from "fs/promises";
2008
2219
  import { createHash } from "crypto";
2009
- import { join as join8 } from "path";
2220
+ import { join as join10 } from "path";
2010
2221
  import JSON5 from "json5";
2011
2222
  async function hashFile(filePath) {
2012
2223
  try {
@@ -2030,8 +2241,8 @@ async function readLiveState(options) {
2030
2241
  const toolsFile = trackedFiles.find((f) => f.includes("TOOLS")) ?? "TOOLS.md";
2031
2242
  const [frameworkConfig, charterHash, toolsHash] = await Promise.all([
2032
2243
  readJsonFile(options.configPath),
2033
- hashFile(join8(options.teamDir, charterFile)),
2034
- hashFile(join8(options.teamDir, toolsFile))
2244
+ hashFile(join10(options.teamDir, charterFile)),
2245
+ hashFile(join10(options.teamDir, toolsFile))
2035
2246
  ]);
2036
2247
  return {
2037
2248
  frameworkConfig,
@@ -2640,15 +2851,15 @@ function terminate(child) {
2640
2851
  // src/commands/manager-watch.tsx
2641
2852
  import { useEffect, useState, useMemo } from "react";
2642
2853
  import { render, Box, Text, useApp, useInput } from "ink";
2643
- import { existsSync as existsSync4, readFileSync as readFileSync3, statSync, openSync, readSync, closeSync } from "fs";
2854
+ import { existsSync as existsSync5, readFileSync as readFileSync5, statSync, openSync, readSync, closeSync } from "fs";
2644
2855
  import { homedir as homedir3 } from "os";
2645
- import { join as join9 } from "path";
2856
+ import { join as join11 } from "path";
2646
2857
  import { jsx, jsxs } from "react/jsx-runtime";
2647
2858
  var REFRESH_MS = 1e3;
2648
2859
  var LOG_TAIL_LINES = 200;
2649
2860
  var DETAIL_RECENT_LINES = 50;
2650
2861
  function managerWatchCommand(opts = {}) {
2651
- const configDir = opts.configDir ?? join9(homedir3(), ".augmented");
2862
+ const configDir = opts.configDir ?? join11(homedir3(), ".augmented");
2652
2863
  const paths = getManagerPaths(configDir);
2653
2864
  const isTty = process.stdout.isTTY === true && process.stdin.isTTY === true;
2654
2865
  if (opts.noTui || !isTty) {
@@ -2672,15 +2883,15 @@ function managerWatchCommand(opts = {}) {
2672
2883
  }
2673
2884
  function readState(stateFile) {
2674
2885
  try {
2675
- if (!existsSync4(stateFile)) return null;
2676
- const raw = readFileSync3(stateFile, "utf-8");
2886
+ if (!existsSync5(stateFile)) return null;
2887
+ const raw = readFileSync5(stateFile, "utf-8");
2677
2888
  return JSON.parse(raw);
2678
2889
  } catch {
2679
2890
  return null;
2680
2891
  }
2681
2892
  }
2682
2893
  function tailLogFile(logFile, lines) {
2683
- if (!existsSync4(logFile)) return [];
2894
+ if (!existsSync5(logFile)) return [];
2684
2895
  try {
2685
2896
  const fileSize = statSync(logFile).size;
2686
2897
  if (fileSize === 0) return [];
@@ -2921,7 +3132,7 @@ function truncate(s, max) {
2921
3132
  return s.length > max ? `${s.slice(0, Math.max(0, max - 1))}\u2026` : s;
2922
3133
  }
2923
3134
  function streamLogFile(logFile) {
2924
- if (!existsSync4(logFile)) {
3135
+ if (!existsSync5(logFile)) {
2925
3136
  process.stderr.write(`No manager log found at ${logFile}.
2926
3137
  Start the manager first: agt manager start
2927
3138
  `);
@@ -2930,7 +3141,7 @@ Start the manager first: agt manager start
2930
3141
  }
2931
3142
  let lastSize = 0;
2932
3143
  try {
2933
- const initial = readFileSync3(logFile, "utf-8");
3144
+ const initial = readFileSync5(logFile, "utf-8");
2934
3145
  process.stdout.write(initial);
2935
3146
  lastSize = statSync(logFile).size;
2936
3147
  } catch (err) {
@@ -2967,14 +3178,14 @@ Start the manager first: agt manager start
2967
3178
  // src/commands/agent.ts
2968
3179
  import chalk14 from "chalk";
2969
3180
  import JSON52 from "json5";
2970
- import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
2971
- import { join as join10 } from "path";
3181
+ import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
3182
+ import { join as join12 } from "path";
2972
3183
  async function agentShowCommand(codeName, opts) {
2973
3184
  const json = isJsonMode();
2974
- const unifiedDir = join10(opts.configDir, codeName, "provision");
2975
- const legacyDir = join10(opts.configDir, codeName, "claudecode", "provision");
2976
- const agentDir = existsSync5(unifiedDir) ? unifiedDir : legacyDir;
2977
- const hasLocalConfig = existsSync5(agentDir);
3185
+ const unifiedDir = join12(opts.configDir, codeName, "provision");
3186
+ const legacyDir = join12(opts.configDir, codeName, "claudecode", "provision");
3187
+ const agentDir = existsSync6(unifiedDir) ? unifiedDir : legacyDir;
3188
+ const hasLocalConfig = existsSync6(agentDir);
2978
3189
  let apiChannels = null;
2979
3190
  let apiAgent = null;
2980
3191
  if (getApiKey()) {
@@ -3007,34 +3218,34 @@ async function agentShowCommand(codeName, opts) {
3007
3218
  let openclawConfig = null;
3008
3219
  let agentState = null;
3009
3220
  if (hasLocalConfig) {
3010
- const charterPath = join10(agentDir, "CHARTER.md");
3011
- if (existsSync5(charterPath)) {
3012
- const raw = readFileSync4(charterPath, "utf-8");
3221
+ const charterPath = join12(agentDir, "CHARTER.md");
3222
+ if (existsSync6(charterPath)) {
3223
+ const raw = readFileSync6(charterPath, "utf-8");
3013
3224
  const parsed = extractFrontmatter(raw);
3014
3225
  if (parsed.frontmatter) {
3015
3226
  charter = parsed.frontmatter;
3016
3227
  }
3017
3228
  }
3018
- const toolsPath = join10(agentDir, "TOOLS.md");
3019
- if (existsSync5(toolsPath)) {
3020
- const raw = readFileSync4(toolsPath, "utf-8");
3229
+ const toolsPath = join12(agentDir, "TOOLS.md");
3230
+ if (existsSync6(toolsPath)) {
3231
+ const raw = readFileSync6(toolsPath, "utf-8");
3021
3232
  const parsed = extractFrontmatter(raw);
3022
3233
  if (parsed.frontmatter) {
3023
3234
  tools = parsed.frontmatter;
3024
3235
  }
3025
3236
  }
3026
- const openclawPath = join10(agentDir, "openclaw.json5");
3027
- if (existsSync5(openclawPath)) {
3237
+ const openclawPath = join12(agentDir, "openclaw.json5");
3238
+ if (existsSync6(openclawPath)) {
3028
3239
  try {
3029
- const raw = readFileSync4(openclawPath, "utf-8");
3240
+ const raw = readFileSync6(openclawPath, "utf-8");
3030
3241
  openclawConfig = JSON52.parse(raw);
3031
3242
  } catch {
3032
3243
  }
3033
3244
  }
3034
- const statePath = join10(opts.configDir, "manager-state.json");
3035
- if (existsSync5(statePath)) {
3245
+ const statePath = join12(opts.configDir, "manager-state.json");
3246
+ if (existsSync6(statePath)) {
3036
3247
  try {
3037
- const raw = readFileSync4(statePath, "utf-8");
3248
+ const raw = readFileSync6(statePath, "utf-8");
3038
3249
  const state = JSON.parse(raw);
3039
3250
  agentState = state.agents?.find((a) => a.codeName === codeName) ?? null;
3040
3251
  } catch {
@@ -3371,8 +3582,8 @@ async function kanbanRecurringDisableCommand(titleOrId, opts) {
3371
3582
  }
3372
3583
 
3373
3584
  // src/commands/setup.ts
3374
- import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, accessSync, constants as fsConstants } from "fs";
3375
- import { join as join11, dirname } from "path";
3585
+ import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, accessSync, constants as fsConstants } from "fs";
3586
+ import { join as join13, dirname as dirname2 } from "path";
3376
3587
  import { homedir as homedir4 } from "os";
3377
3588
  import chalk16 from "chalk";
3378
3589
  import ora14 from "ora";
@@ -3380,15 +3591,15 @@ function detectShellProfile() {
3380
3591
  const shell = process.env["SHELL"] ?? "";
3381
3592
  const home = homedir4();
3382
3593
  if (shell.includes("zsh")) {
3383
- return join11(home, ".zshrc");
3594
+ return join13(home, ".zshrc");
3384
3595
  }
3385
3596
  if (shell.includes("fish")) {
3386
- const fishConfig = join11(home, ".config", "fish", "config.fish");
3597
+ const fishConfig = join13(home, ".config", "fish", "config.fish");
3387
3598
  return fishConfig;
3388
3599
  }
3389
- const bashrc = join11(home, ".bashrc");
3390
- if (existsSync6(bashrc)) return bashrc;
3391
- return join11(home, ".bash_profile");
3600
+ const bashrc = join13(home, ".bashrc");
3601
+ if (existsSync7(bashrc)) return bashrc;
3602
+ return join13(home, ".bash_profile");
3392
3603
  }
3393
3604
  function maybeWriteSystemWideEnv(apiUrl, apiKey, consoleUrl) {
3394
3605
  const empty = { etcEnvironment: false, profileD: false, bashrc: false };
@@ -3414,14 +3625,14 @@ function quoteForFishShell(value) {
3414
3625
  }
3415
3626
  function writeEtcEnvironment(apiUrl, apiKey, consoleUrl) {
3416
3627
  const envPath = "/etc/environment";
3417
- if (!existsSync6(envPath)) return false;
3628
+ if (!existsSync7(envPath)) return false;
3418
3629
  try {
3419
3630
  accessSync(envPath, fsConstants.W_OK);
3420
3631
  } catch {
3421
3632
  return false;
3422
3633
  }
3423
3634
  try {
3424
- const current = readFileSync5(envPath, "utf-8");
3635
+ const current = readFileSync7(envPath, "utf-8");
3425
3636
  const stripped = current.split(/\r?\n/).filter((line) => !/^\s*(?:export\s+)?AGT_(?:HOST|API_KEY|CONSOLE_URL)=/.test(line)).join("\n");
3426
3637
  const base = stripped.endsWith("\n") || stripped.length === 0 ? stripped : `${stripped}
3427
3638
  `;
@@ -3432,7 +3643,7 @@ function writeEtcEnvironment(apiUrl, apiKey, consoleUrl) {
3432
3643
  if (consoleUrl) lines.push(`AGT_CONSOLE_URL=${quoteForEtcEnvironment(consoleUrl)}`);
3433
3644
  const appended = `${base}${lines.join("\n")}
3434
3645
  `;
3435
- writeFileSync6(envPath, appended, { mode: 420 });
3646
+ writeFileSync7(envPath, appended, { mode: 420 });
3436
3647
  return true;
3437
3648
  } catch {
3438
3649
  return false;
@@ -3440,7 +3651,7 @@ function writeEtcEnvironment(apiUrl, apiKey, consoleUrl) {
3440
3651
  }
3441
3652
  function writeProfileDScript(apiUrl, apiKey, consoleUrl) {
3442
3653
  const profileD = "/etc/profile.d";
3443
- if (!existsSync6(profileD)) return false;
3654
+ if (!existsSync7(profileD)) return false;
3444
3655
  try {
3445
3656
  accessSync(profileD, fsConstants.W_OK);
3446
3657
  } catch {
@@ -3455,7 +3666,7 @@ function writeProfileDScript(apiUrl, apiKey, consoleUrl) {
3455
3666
  ];
3456
3667
  if (consoleUrl) lines.push(`export AGT_CONSOLE_URL=${quoteForPosixShell(consoleUrl)}`);
3457
3668
  lines.push("");
3458
- writeFileSync6(scriptPath, lines.join("\n"), { mode: 420 });
3669
+ writeFileSync7(scriptPath, lines.join("\n"), { mode: 420 });
3459
3670
  return true;
3460
3671
  } catch {
3461
3672
  return false;
@@ -3463,14 +3674,14 @@ function writeProfileDScript(apiUrl, apiKey, consoleUrl) {
3463
3674
  }
3464
3675
  function ensureBashrcSourcesProfileD() {
3465
3676
  const bashrc = "/etc/bashrc";
3466
- if (!existsSync6(bashrc)) return false;
3677
+ if (!existsSync7(bashrc)) return false;
3467
3678
  try {
3468
3679
  accessSync(bashrc, fsConstants.W_OK);
3469
3680
  } catch {
3470
3681
  return false;
3471
3682
  }
3472
3683
  try {
3473
- const current = readFileSync5(bashrc, "utf-8");
3684
+ const current = readFileSync7(bashrc, "utf-8");
3474
3685
  const marker = "# Augmented (agt) \u2014 source system-wide AGT env";
3475
3686
  if (current.includes(marker)) return true;
3476
3687
  const snippet = [
@@ -3481,7 +3692,7 @@ function ensureBashrcSourcesProfileD() {
3481
3692
  "fi",
3482
3693
  ""
3483
3694
  ].join("\n");
3484
- writeFileSync6(bashrc, current + snippet);
3695
+ writeFileSync7(bashrc, current + snippet);
3485
3696
  return true;
3486
3697
  } catch {
3487
3698
  return false;
@@ -3596,12 +3807,12 @@ async function setupCommand(token) {
3596
3807
  );
3597
3808
  }
3598
3809
  const exportLines = buildExportLines(shell, finalApiUrl, setupResult.api_key, consoleUrl);
3599
- const profileDir = dirname(profilePath);
3600
- if (!existsSync6(profileDir)) {
3601
- mkdirSync5(profileDir, { recursive: true });
3810
+ const profileDir = dirname2(profilePath);
3811
+ if (!existsSync7(profileDir)) {
3812
+ mkdirSync6(profileDir, { recursive: true });
3602
3813
  }
3603
3814
  const marker = "# Augmented (agt) host configuration";
3604
- const current = existsSync6(profilePath) ? readFileSync5(profilePath, "utf-8") : "";
3815
+ const current = existsSync7(profilePath) ? readFileSync7(profilePath, "utf-8") : "";
3605
3816
  let updated;
3606
3817
  if (current.includes(marker)) {
3607
3818
  updated = current.replace(
@@ -3614,7 +3825,7 @@ async function setupCommand(token) {
3614
3825
  } else {
3615
3826
  updated = current + exportLines;
3616
3827
  }
3617
- writeFileSync6(profilePath, updated.endsWith("\n") ? updated : `${updated}
3828
+ writeFileSync7(profilePath, updated.endsWith("\n") ? updated : `${updated}
3618
3829
  `);
3619
3830
  if (!json) {
3620
3831
  success(`Environment variables written to ${chalk16.bold(profilePath)}`);
@@ -3632,7 +3843,7 @@ async function setupCommand(token) {
3632
3843
  const managerSpinner = ora14({ text: "Starting manager daemon\u2026", isSilent: json });
3633
3844
  managerSpinner.start();
3634
3845
  try {
3635
- const configDir = join11(homedir4(), ".augmented");
3846
+ const configDir = join13(homedir4(), ".augmented");
3636
3847
  const { pid } = startWatchdog({ intervalMs: 1e4, configDir, detached: true });
3637
3848
  managerSpinner.succeed(`Manager started (PID ${pid})`);
3638
3849
  } catch (err) {
@@ -4180,10 +4391,10 @@ async function acpxCloseCommand(agent2, _opts, cmd) {
4180
4391
 
4181
4392
  // src/commands/update.ts
4182
4393
  import { execFileSync, execSync } from "child_process";
4183
- import { existsSync as existsSync7, realpathSync as realpathSync2 } from "fs";
4394
+ import { existsSync as existsSync8, realpathSync as realpathSync2 } from "fs";
4184
4395
  import chalk18 from "chalk";
4185
4396
  import ora16 from "ora";
4186
- var cliVersion = true ? "0.27.0" : "dev";
4397
+ var cliVersion = true ? "0.27.2" : "dev";
4187
4398
  async function fetchLatestVersion() {
4188
4399
  const host2 = getHost();
4189
4400
  if (!host2) return null;
@@ -4203,9 +4414,9 @@ async function fetchLatestVersion() {
4203
4414
  }
4204
4415
  function isNewerVersion(local, remote) {
4205
4416
  if (local === "dev") return false;
4206
- const parse = (v) => v.replace(/^v/, "").split(".").map(Number);
4207
- const lParts = parse(local);
4208
- const rParts = parse(remote);
4417
+ const parse2 = (v) => v.replace(/^v/, "").split(".").map(Number);
4418
+ const lParts = parse2(local);
4419
+ const rParts = parse2(remote);
4209
4420
  const lMaj = lParts[0] ?? 0, lMin = lParts[1] ?? 0, lPat = lParts[2] ?? 0;
4210
4421
  const rMaj = rParts[0] ?? 0, rMin = rParts[1] ?? 0, rPat = rParts[2] ?? 0;
4211
4422
  if (rMaj !== lMaj) return rMaj > lMaj;
@@ -4287,7 +4498,7 @@ function performUpdate(version) {
4287
4498
  function detectBrewOwner() {
4288
4499
  for (const prefix of ["/home/linuxbrew/.linuxbrew", "/opt/homebrew", "/usr/local"]) {
4289
4500
  const cellar = `${prefix}/Cellar`;
4290
- if (!existsSync7(cellar)) continue;
4501
+ if (!existsSync8(cellar)) continue;
4291
4502
  try {
4292
4503
  return execSync(`stat -c %U "${cellar}" 2>/dev/null || stat -f %Su "${cellar}"`, { encoding: "utf-8" }).trim() || null;
4293
4504
  } catch {
@@ -4715,7 +4926,7 @@ function handleError(err) {
4715
4926
  }
4716
4927
 
4717
4928
  // src/bin/agt.ts
4718
- var cliVersion2 = true ? "0.27.0" : "dev";
4929
+ var cliVersion2 = true ? "0.27.2" : "dev";
4719
4930
  var program = new Command();
4720
4931
  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");
4721
4932
  program.hook("preAction", (thisCommand) => {
@@ -4738,16 +4949,27 @@ impersonate.command("connect <token>").description(
4738
4949
  ).option(
4739
4950
  "--no-launch",
4740
4951
  "Skip launching Claude Code after the swap (operator manages their own session)"
4952
+ ).option(
4953
+ "--workdir",
4954
+ "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"
4955
+ ).option(
4956
+ "--api-host <url>",
4957
+ "Override the API host used to redeem the token. Defaults to https://api.augmented.team \u2014 AGT_HOST is intentionally ignored so a dev/Tailscale AGT_HOST doesn't accidentally 502 against a token minted on prod."
4741
4958
  ).action(
4742
4959
  (token, opts) => (
4743
4960
  // Commander negates --no-FLAG into opts.flag, so --no-launch arrives
4744
4961
  // as opts.launch === false. Translate to the command's noLaunch
4745
4962
  // option, defaulting to launching when the flag isn't passed.
4746
- impersonateConnectCommand(token, { noLaunch: opts.launch === false })
4963
+ impersonateConnectCommand(token, {
4964
+ noLaunch: opts.launch === false,
4965
+ workdir: opts.workdir === true,
4966
+ apiHost: opts.apiHost
4967
+ })
4747
4968
  )
4748
4969
  );
4749
4970
  impersonate.command("current").description('Print the active impersonation (or "none")').action(impersonateCurrentCommand);
4750
4971
  impersonate.command("exit").description("End the active impersonation, restore project files, and notify the server").action(impersonateExitCommand);
4972
+ impersonate.command("introduce", { hidden: true }).description("Print the impersonated agent's self-introduction (SessionStart hook target)").action(impersonateIntroduceCommand);
4751
4973
  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);
4752
4974
  program.command("lint").argument("[path]", "Path to agent directory (default: all agents in .augmented/)").description("Lint CHARTER.md and TOOLS.md files").action(lintCommand);
4753
4975
  var channels = program.command("channels").description("Manage and inspect channel configuration");
@@ -4780,16 +5002,16 @@ host.command("pair <host-name>").description("Start an SSM port-forward + shell
4780
5002
  })
4781
5003
  );
4782
5004
  var manager = program.command("manager").description("Host config sync daemon \u2014 keeps local agent files in sync with API");
4783
- 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);
4784
- manager.command("stop").description("Stop the running manager daemon").option("--config-dir <dir>", "Config directory for agent files", join12(homedir5(), ".augmented")).action(managerStopCommand);
4785
- 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);
4786
- 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);
4787
- 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);
5005
+ 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);
5006
+ manager.command("stop").description("Stop the running manager daemon").option("--config-dir <dir>", "Config directory for agent files", join14(homedir5(), ".augmented")).action(managerStopCommand);
5007
+ 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);
5008
+ 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);
5009
+ 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);
4788
5010
  manager.command("uninstall").description("Remove the OS-level supervisor (launchctl unload + delete plist on macOS). Idempotent.").action(managerUninstallCommand);
4789
5011
  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);
4790
5012
  manager.command("uninstall-system-unit").description("Remove the system-level systemd unit (Linux, root). Idempotent. (ENG-4706)").action(managerUninstallSystemUnitCommand);
4791
5013
  var agent = program.command("agent").description("Inspect and manage agents");
4792
- 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);
5014
+ 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);
4793
5015
  var kanban = program.command("kanban").description("Manage agent kanban boards");
4794
5016
  kanban.command("list").description("List kanban board items for an agent").requiredOption("--agent <code-name>", "Agent code name").action(kanbanListCommand);
4795
5017
  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);