@integrity-labs/agt-cli 0.27.0 → 0.27.1

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-YWXPSVI5.js";
29
+ } from "../chunk-UVPB6TSJ.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-BKLEFKUZ.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()) {
@@ -1556,8 +1556,8 @@ async function provisionCommand(codeName, options) {
1556
1556
  import chalk10 from "chalk";
1557
1557
  import ora10 from "ora";
1558
1558
  import { spawn } from "child_process";
1559
- import { writeFileSync as writeFileSync5 } from "fs";
1560
- import { join as join7 } from "path";
1559
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync6 } from "fs";
1560
+ import { join as join9 } from "path";
1561
1561
 
1562
1562
  // src/lib/impersonate-flag.ts
1563
1563
  var ENV_VAR = "AGT_IMPERSONATE_ENABLED";
@@ -1621,6 +1621,11 @@ function ensureCodeNameDir(codeName) {
1621
1621
  mkdirSync4(dir, { recursive: true });
1622
1622
  return dir;
1623
1623
  }
1624
+ function ensureImpersonateWorkdir(codeName) {
1625
+ const dir = join6(ensureCodeNameDir(codeName), "workdir");
1626
+ mkdirSync4(dir, { recursive: true });
1627
+ return dir;
1628
+ }
1624
1629
  function readActiveManifest() {
1625
1630
  if (!existsSync2(ACTIVE_MANIFEST_PATH)) return null;
1626
1631
  try {
@@ -1668,8 +1673,19 @@ import {
1668
1673
  unlinkSync
1669
1674
  } from "fs";
1670
1675
  import { homedir as homedir2 } from "os";
1671
- import { sep } from "path";
1676
+ import { dirname, join as join7, parse, sep } from "path";
1672
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
+ }
1673
1689
  function swapInPersonaFile(projectPath, personaPath) {
1674
1690
  let kind;
1675
1691
  if (!lstatSafe(projectPath)) {
@@ -1734,6 +1750,139 @@ function lstatSafe(path) {
1734
1750
  }
1735
1751
  }
1736
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
+
1737
1886
  // src/commands/impersonate.ts
1738
1887
  var SWAP_FILES = ["CLAUDE.md", ".mcp.json"];
1739
1888
  var AGENT_IMPERSONATION_HEADER = "X-Agent-Impersonation";
@@ -1757,6 +1906,16 @@ async function impersonateConnectCommand(token, options = {}) {
1757
1906
  process.exitCode = 1;
1758
1907
  return;
1759
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
+ }
1760
1919
  if (!json) console.log(chalk10.bold("\nAugmented \u2014 Impersonate\n"));
1761
1920
  const spinner = ora10({
1762
1921
  text: "Redeeming token\u2026",
@@ -1787,25 +1946,27 @@ async function impersonateConnectCommand(token, options = {}) {
1787
1946
  }
1788
1947
  spinner.text = "Writing persona files\u2026";
1789
1948
  const personaDir = ensureCodeNameDir(bundle.agent.code_name);
1790
- writeFileSync5(
1791
- join7(personaDir, "CLAUDE.md"),
1949
+ writeFileSync6(
1950
+ join9(personaDir, "CLAUDE.md"),
1792
1951
  bundle.artifacts["CLAUDE.md"]
1793
1952
  );
1794
- writeFileSync5(
1795
- join7(personaDir, ".mcp.json"),
1953
+ writeFileSync6(
1954
+ join9(personaDir, ".mcp.json"),
1796
1955
  bundle.artifacts[".mcp.json"]
1797
1956
  );
1798
1957
  spinner.text = "Swapping persona files\u2026";
1799
- const projectCwd = process.cwd();
1958
+ const projectCwd = options.workdir ? ensureImpersonateWorkdir(bundle.agent.code_name) : process.cwd();
1800
1959
  const backups = {};
1801
1960
  const swapped = [];
1961
+ let hookBackup;
1802
1962
  try {
1803
1963
  for (const file of SWAP_FILES) {
1804
- const projectPath = join7(projectCwd, file);
1805
- const kind = swapInPersonaFile(projectPath, join7(personaDir, file));
1964
+ const projectPath = join9(projectCwd, file);
1965
+ const kind = swapInPersonaFile(projectPath, join9(personaDir, file));
1806
1966
  backups[file] = kind;
1807
1967
  swapped.push({ file, kind });
1808
1968
  }
1969
+ hookBackup = registerIntroduceHook(projectCwd);
1809
1970
  const manifest = {
1810
1971
  code_name: bundle.agent.code_name,
1811
1972
  agent_id: bundle.agent.agent_id,
@@ -1816,13 +1977,20 @@ async function impersonateConnectCommand(token, options = {}) {
1816
1977
  minted_at: (/* @__PURE__ */ new Date()).toISOString(),
1817
1978
  project_cwd: projectCwd,
1818
1979
  host: host2,
1819
- backups
1980
+ backups,
1981
+ hook: hookBackup
1820
1982
  };
1821
1983
  writeActiveManifest(manifest);
1822
1984
  } catch (err) {
1985
+ if (hookBackup) {
1986
+ try {
1987
+ unregisterIntroduceHook(hookBackup);
1988
+ } catch {
1989
+ }
1990
+ }
1823
1991
  for (const { file, kind } of [...swapped].reverse()) {
1824
1992
  try {
1825
- restoreSwapped(join7(projectCwd, file), kind);
1993
+ restoreSwapped(join9(projectCwd, file), kind);
1826
1994
  } catch {
1827
1995
  }
1828
1996
  }
@@ -1856,6 +2024,9 @@ async function impersonateConnectCommand(token, options = {}) {
1856
2024
  console.log(` Swapped: ${chalk10.dim(SWAP_FILES.join(", "))}`);
1857
2025
  console.log();
1858
2026
  if (!shouldLaunchClaude) {
2027
+ if (options.workdir) {
2028
+ info(`Start Claude Code in the impersonation workdir: \`cd ${projectCwd}\``);
2029
+ }
1859
2030
  info(
1860
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)."
1861
2032
  );
@@ -1968,7 +2139,7 @@ async function impersonateExitCommand() {
1968
2139
  }
1969
2140
  const restored = [];
1970
2141
  for (const [file, kind] of Object.entries(manifest.backups)) {
1971
- const projectPath = join7(manifest.project_cwd, file);
2142
+ const projectPath = join9(manifest.project_cwd, file);
1972
2143
  try {
1973
2144
  restoreSwapped(projectPath, kind);
1974
2145
  restored.push(file);
@@ -1977,6 +2148,14 @@ async function impersonateExitCommand() {
1977
2148
  if (!json) error(`Failed to restore ${file}: ${msg}`);
1978
2149
  }
1979
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
+ }
1980
2159
  clearActiveManifest();
1981
2160
  if (json) {
1982
2161
  jsonOutput({
@@ -1998,6 +2177,37 @@ async function impersonateExitCommand() {
1998
2177
  }
1999
2178
  info("Restart Claude Code to pick up your original persona.");
2000
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
+ }
2001
2211
 
2002
2212
  // src/commands/drift.ts
2003
2213
  import chalk11 from "chalk";
@@ -2006,7 +2216,7 @@ import ora11 from "ora";
2006
2216
  // ../../packages/core/dist/drift/live-state-reader.js
2007
2217
  import { readFile } from "fs/promises";
2008
2218
  import { createHash } from "crypto";
2009
- import { join as join8 } from "path";
2219
+ import { join as join10 } from "path";
2010
2220
  import JSON5 from "json5";
2011
2221
  async function hashFile(filePath) {
2012
2222
  try {
@@ -2030,8 +2240,8 @@ async function readLiveState(options) {
2030
2240
  const toolsFile = trackedFiles.find((f) => f.includes("TOOLS")) ?? "TOOLS.md";
2031
2241
  const [frameworkConfig, charterHash, toolsHash] = await Promise.all([
2032
2242
  readJsonFile(options.configPath),
2033
- hashFile(join8(options.teamDir, charterFile)),
2034
- hashFile(join8(options.teamDir, toolsFile))
2243
+ hashFile(join10(options.teamDir, charterFile)),
2244
+ hashFile(join10(options.teamDir, toolsFile))
2035
2245
  ]);
2036
2246
  return {
2037
2247
  frameworkConfig,
@@ -2640,15 +2850,15 @@ function terminate(child) {
2640
2850
  // src/commands/manager-watch.tsx
2641
2851
  import { useEffect, useState, useMemo } from "react";
2642
2852
  import { render, Box, Text, useApp, useInput } from "ink";
2643
- 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";
2644
2854
  import { homedir as homedir3 } from "os";
2645
- import { join as join9 } from "path";
2855
+ import { join as join11 } from "path";
2646
2856
  import { jsx, jsxs } from "react/jsx-runtime";
2647
2857
  var REFRESH_MS = 1e3;
2648
2858
  var LOG_TAIL_LINES = 200;
2649
2859
  var DETAIL_RECENT_LINES = 50;
2650
2860
  function managerWatchCommand(opts = {}) {
2651
- const configDir = opts.configDir ?? join9(homedir3(), ".augmented");
2861
+ const configDir = opts.configDir ?? join11(homedir3(), ".augmented");
2652
2862
  const paths = getManagerPaths(configDir);
2653
2863
  const isTty = process.stdout.isTTY === true && process.stdin.isTTY === true;
2654
2864
  if (opts.noTui || !isTty) {
@@ -2672,15 +2882,15 @@ function managerWatchCommand(opts = {}) {
2672
2882
  }
2673
2883
  function readState(stateFile) {
2674
2884
  try {
2675
- if (!existsSync4(stateFile)) return null;
2676
- const raw = readFileSync3(stateFile, "utf-8");
2885
+ if (!existsSync5(stateFile)) return null;
2886
+ const raw = readFileSync5(stateFile, "utf-8");
2677
2887
  return JSON.parse(raw);
2678
2888
  } catch {
2679
2889
  return null;
2680
2890
  }
2681
2891
  }
2682
2892
  function tailLogFile(logFile, lines) {
2683
- if (!existsSync4(logFile)) return [];
2893
+ if (!existsSync5(logFile)) return [];
2684
2894
  try {
2685
2895
  const fileSize = statSync(logFile).size;
2686
2896
  if (fileSize === 0) return [];
@@ -2921,7 +3131,7 @@ function truncate(s, max) {
2921
3131
  return s.length > max ? `${s.slice(0, Math.max(0, max - 1))}\u2026` : s;
2922
3132
  }
2923
3133
  function streamLogFile(logFile) {
2924
- if (!existsSync4(logFile)) {
3134
+ if (!existsSync5(logFile)) {
2925
3135
  process.stderr.write(`No manager log found at ${logFile}.
2926
3136
  Start the manager first: agt manager start
2927
3137
  `);
@@ -2930,7 +3140,7 @@ Start the manager first: agt manager start
2930
3140
  }
2931
3141
  let lastSize = 0;
2932
3142
  try {
2933
- const initial = readFileSync3(logFile, "utf-8");
3143
+ const initial = readFileSync5(logFile, "utf-8");
2934
3144
  process.stdout.write(initial);
2935
3145
  lastSize = statSync(logFile).size;
2936
3146
  } catch (err) {
@@ -2967,14 +3177,14 @@ Start the manager first: agt manager start
2967
3177
  // src/commands/agent.ts
2968
3178
  import chalk14 from "chalk";
2969
3179
  import JSON52 from "json5";
2970
- import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
2971
- import { join as join10 } from "path";
3180
+ import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
3181
+ import { join as join12 } from "path";
2972
3182
  async function agentShowCommand(codeName, opts) {
2973
3183
  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);
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);
2978
3188
  let apiChannels = null;
2979
3189
  let apiAgent = null;
2980
3190
  if (getApiKey()) {
@@ -3007,34 +3217,34 @@ async function agentShowCommand(codeName, opts) {
3007
3217
  let openclawConfig = null;
3008
3218
  let agentState = null;
3009
3219
  if (hasLocalConfig) {
3010
- const charterPath = join10(agentDir, "CHARTER.md");
3011
- if (existsSync5(charterPath)) {
3012
- const raw = readFileSync4(charterPath, "utf-8");
3220
+ const charterPath = join12(agentDir, "CHARTER.md");
3221
+ if (existsSync6(charterPath)) {
3222
+ const raw = readFileSync6(charterPath, "utf-8");
3013
3223
  const parsed = extractFrontmatter(raw);
3014
3224
  if (parsed.frontmatter) {
3015
3225
  charter = parsed.frontmatter;
3016
3226
  }
3017
3227
  }
3018
- const toolsPath = join10(agentDir, "TOOLS.md");
3019
- if (existsSync5(toolsPath)) {
3020
- const raw = readFileSync4(toolsPath, "utf-8");
3228
+ const toolsPath = join12(agentDir, "TOOLS.md");
3229
+ if (existsSync6(toolsPath)) {
3230
+ const raw = readFileSync6(toolsPath, "utf-8");
3021
3231
  const parsed = extractFrontmatter(raw);
3022
3232
  if (parsed.frontmatter) {
3023
3233
  tools = parsed.frontmatter;
3024
3234
  }
3025
3235
  }
3026
- const openclawPath = join10(agentDir, "openclaw.json5");
3027
- if (existsSync5(openclawPath)) {
3236
+ const openclawPath = join12(agentDir, "openclaw.json5");
3237
+ if (existsSync6(openclawPath)) {
3028
3238
  try {
3029
- const raw = readFileSync4(openclawPath, "utf-8");
3239
+ const raw = readFileSync6(openclawPath, "utf-8");
3030
3240
  openclawConfig = JSON52.parse(raw);
3031
3241
  } catch {
3032
3242
  }
3033
3243
  }
3034
- const statePath = join10(opts.configDir, "manager-state.json");
3035
- if (existsSync5(statePath)) {
3244
+ const statePath = join12(opts.configDir, "manager-state.json");
3245
+ if (existsSync6(statePath)) {
3036
3246
  try {
3037
- const raw = readFileSync4(statePath, "utf-8");
3247
+ const raw = readFileSync6(statePath, "utf-8");
3038
3248
  const state = JSON.parse(raw);
3039
3249
  agentState = state.agents?.find((a) => a.codeName === codeName) ?? null;
3040
3250
  } catch {
@@ -3371,8 +3581,8 @@ async function kanbanRecurringDisableCommand(titleOrId, opts) {
3371
3581
  }
3372
3582
 
3373
3583
  // 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";
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";
3376
3586
  import { homedir as homedir4 } from "os";
3377
3587
  import chalk16 from "chalk";
3378
3588
  import ora14 from "ora";
@@ -3380,15 +3590,15 @@ function detectShellProfile() {
3380
3590
  const shell = process.env["SHELL"] ?? "";
3381
3591
  const home = homedir4();
3382
3592
  if (shell.includes("zsh")) {
3383
- return join11(home, ".zshrc");
3593
+ return join13(home, ".zshrc");
3384
3594
  }
3385
3595
  if (shell.includes("fish")) {
3386
- const fishConfig = join11(home, ".config", "fish", "config.fish");
3596
+ const fishConfig = join13(home, ".config", "fish", "config.fish");
3387
3597
  return fishConfig;
3388
3598
  }
3389
- const bashrc = join11(home, ".bashrc");
3390
- if (existsSync6(bashrc)) return bashrc;
3391
- return join11(home, ".bash_profile");
3599
+ const bashrc = join13(home, ".bashrc");
3600
+ if (existsSync7(bashrc)) return bashrc;
3601
+ return join13(home, ".bash_profile");
3392
3602
  }
3393
3603
  function maybeWriteSystemWideEnv(apiUrl, apiKey, consoleUrl) {
3394
3604
  const empty = { etcEnvironment: false, profileD: false, bashrc: false };
@@ -3414,14 +3624,14 @@ function quoteForFishShell(value) {
3414
3624
  }
3415
3625
  function writeEtcEnvironment(apiUrl, apiKey, consoleUrl) {
3416
3626
  const envPath = "/etc/environment";
3417
- if (!existsSync6(envPath)) return false;
3627
+ if (!existsSync7(envPath)) return false;
3418
3628
  try {
3419
3629
  accessSync(envPath, fsConstants.W_OK);
3420
3630
  } catch {
3421
3631
  return false;
3422
3632
  }
3423
3633
  try {
3424
- const current = readFileSync5(envPath, "utf-8");
3634
+ const current = readFileSync7(envPath, "utf-8");
3425
3635
  const stripped = current.split(/\r?\n/).filter((line) => !/^\s*(?:export\s+)?AGT_(?:HOST|API_KEY|CONSOLE_URL)=/.test(line)).join("\n");
3426
3636
  const base = stripped.endsWith("\n") || stripped.length === 0 ? stripped : `${stripped}
3427
3637
  `;
@@ -3432,7 +3642,7 @@ function writeEtcEnvironment(apiUrl, apiKey, consoleUrl) {
3432
3642
  if (consoleUrl) lines.push(`AGT_CONSOLE_URL=${quoteForEtcEnvironment(consoleUrl)}`);
3433
3643
  const appended = `${base}${lines.join("\n")}
3434
3644
  `;
3435
- writeFileSync6(envPath, appended, { mode: 420 });
3645
+ writeFileSync7(envPath, appended, { mode: 420 });
3436
3646
  return true;
3437
3647
  } catch {
3438
3648
  return false;
@@ -3440,7 +3650,7 @@ function writeEtcEnvironment(apiUrl, apiKey, consoleUrl) {
3440
3650
  }
3441
3651
  function writeProfileDScript(apiUrl, apiKey, consoleUrl) {
3442
3652
  const profileD = "/etc/profile.d";
3443
- if (!existsSync6(profileD)) return false;
3653
+ if (!existsSync7(profileD)) return false;
3444
3654
  try {
3445
3655
  accessSync(profileD, fsConstants.W_OK);
3446
3656
  } catch {
@@ -3455,7 +3665,7 @@ function writeProfileDScript(apiUrl, apiKey, consoleUrl) {
3455
3665
  ];
3456
3666
  if (consoleUrl) lines.push(`export AGT_CONSOLE_URL=${quoteForPosixShell(consoleUrl)}`);
3457
3667
  lines.push("");
3458
- writeFileSync6(scriptPath, lines.join("\n"), { mode: 420 });
3668
+ writeFileSync7(scriptPath, lines.join("\n"), { mode: 420 });
3459
3669
  return true;
3460
3670
  } catch {
3461
3671
  return false;
@@ -3463,14 +3673,14 @@ function writeProfileDScript(apiUrl, apiKey, consoleUrl) {
3463
3673
  }
3464
3674
  function ensureBashrcSourcesProfileD() {
3465
3675
  const bashrc = "/etc/bashrc";
3466
- if (!existsSync6(bashrc)) return false;
3676
+ if (!existsSync7(bashrc)) return false;
3467
3677
  try {
3468
3678
  accessSync(bashrc, fsConstants.W_OK);
3469
3679
  } catch {
3470
3680
  return false;
3471
3681
  }
3472
3682
  try {
3473
- const current = readFileSync5(bashrc, "utf-8");
3683
+ const current = readFileSync7(bashrc, "utf-8");
3474
3684
  const marker = "# Augmented (agt) \u2014 source system-wide AGT env";
3475
3685
  if (current.includes(marker)) return true;
3476
3686
  const snippet = [
@@ -3481,7 +3691,7 @@ function ensureBashrcSourcesProfileD() {
3481
3691
  "fi",
3482
3692
  ""
3483
3693
  ].join("\n");
3484
- writeFileSync6(bashrc, current + snippet);
3694
+ writeFileSync7(bashrc, current + snippet);
3485
3695
  return true;
3486
3696
  } catch {
3487
3697
  return false;
@@ -3596,12 +3806,12 @@ async function setupCommand(token) {
3596
3806
  );
3597
3807
  }
3598
3808
  const exportLines = buildExportLines(shell, finalApiUrl, setupResult.api_key, consoleUrl);
3599
- const profileDir = dirname(profilePath);
3600
- if (!existsSync6(profileDir)) {
3601
- mkdirSync5(profileDir, { recursive: true });
3809
+ const profileDir = dirname2(profilePath);
3810
+ if (!existsSync7(profileDir)) {
3811
+ mkdirSync6(profileDir, { recursive: true });
3602
3812
  }
3603
3813
  const marker = "# Augmented (agt) host configuration";
3604
- const current = existsSync6(profilePath) ? readFileSync5(profilePath, "utf-8") : "";
3814
+ const current = existsSync7(profilePath) ? readFileSync7(profilePath, "utf-8") : "";
3605
3815
  let updated;
3606
3816
  if (current.includes(marker)) {
3607
3817
  updated = current.replace(
@@ -3614,7 +3824,7 @@ async function setupCommand(token) {
3614
3824
  } else {
3615
3825
  updated = current + exportLines;
3616
3826
  }
3617
- writeFileSync6(profilePath, updated.endsWith("\n") ? updated : `${updated}
3827
+ writeFileSync7(profilePath, updated.endsWith("\n") ? updated : `${updated}
3618
3828
  `);
3619
3829
  if (!json) {
3620
3830
  success(`Environment variables written to ${chalk16.bold(profilePath)}`);
@@ -3632,7 +3842,7 @@ async function setupCommand(token) {
3632
3842
  const managerSpinner = ora14({ text: "Starting manager daemon\u2026", isSilent: json });
3633
3843
  managerSpinner.start();
3634
3844
  try {
3635
- const configDir = join11(homedir4(), ".augmented");
3845
+ const configDir = join13(homedir4(), ".augmented");
3636
3846
  const { pid } = startWatchdog({ intervalMs: 1e4, configDir, detached: true });
3637
3847
  managerSpinner.succeed(`Manager started (PID ${pid})`);
3638
3848
  } catch (err) {
@@ -4180,10 +4390,10 @@ async function acpxCloseCommand(agent2, _opts, cmd) {
4180
4390
 
4181
4391
  // src/commands/update.ts
4182
4392
  import { execFileSync, execSync } from "child_process";
4183
- import { existsSync as existsSync7, realpathSync as realpathSync2 } from "fs";
4393
+ import { existsSync as existsSync8, realpathSync as realpathSync2 } from "fs";
4184
4394
  import chalk18 from "chalk";
4185
4395
  import ora16 from "ora";
4186
- var cliVersion = true ? "0.27.0" : "dev";
4396
+ var cliVersion = true ? "0.27.1" : "dev";
4187
4397
  async function fetchLatestVersion() {
4188
4398
  const host2 = getHost();
4189
4399
  if (!host2) return null;
@@ -4203,9 +4413,9 @@ async function fetchLatestVersion() {
4203
4413
  }
4204
4414
  function isNewerVersion(local, remote) {
4205
4415
  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);
4416
+ const parse2 = (v) => v.replace(/^v/, "").split(".").map(Number);
4417
+ const lParts = parse2(local);
4418
+ const rParts = parse2(remote);
4209
4419
  const lMaj = lParts[0] ?? 0, lMin = lParts[1] ?? 0, lPat = lParts[2] ?? 0;
4210
4420
  const rMaj = rParts[0] ?? 0, rMin = rParts[1] ?? 0, rPat = rParts[2] ?? 0;
4211
4421
  if (rMaj !== lMaj) return rMaj > lMaj;
@@ -4287,7 +4497,7 @@ function performUpdate(version) {
4287
4497
  function detectBrewOwner() {
4288
4498
  for (const prefix of ["/home/linuxbrew/.linuxbrew", "/opt/homebrew", "/usr/local"]) {
4289
4499
  const cellar = `${prefix}/Cellar`;
4290
- if (!existsSync7(cellar)) continue;
4500
+ if (!existsSync8(cellar)) continue;
4291
4501
  try {
4292
4502
  return execSync(`stat -c %U "${cellar}" 2>/dev/null || stat -f %Su "${cellar}"`, { encoding: "utf-8" }).trim() || null;
4293
4503
  } catch {
@@ -4715,7 +4925,7 @@ function handleError(err) {
4715
4925
  }
4716
4926
 
4717
4927
  // src/bin/agt.ts
4718
- var cliVersion2 = true ? "0.27.0" : "dev";
4928
+ var cliVersion2 = true ? "0.27.1" : "dev";
4719
4929
  var program = new Command();
4720
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");
4721
4931
  program.hook("preAction", (thisCommand) => {
@@ -4738,16 +4948,23 @@ impersonate.command("connect <token>").description(
4738
4948
  ).option(
4739
4949
  "--no-launch",
4740
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"
4741
4954
  ).action(
4742
4955
  (token, opts) => (
4743
4956
  // Commander negates --no-FLAG into opts.flag, so --no-launch arrives
4744
4957
  // as opts.launch === false. Translate to the command's noLaunch
4745
4958
  // option, defaulting to launching when the flag isn't passed.
4746
- impersonateConnectCommand(token, { noLaunch: opts.launch === false })
4959
+ impersonateConnectCommand(token, {
4960
+ noLaunch: opts.launch === false,
4961
+ workdir: opts.workdir === true
4962
+ })
4747
4963
  )
4748
4964
  );
4749
4965
  impersonate.command("current").description('Print the active impersonation (or "none")').action(impersonateCurrentCommand);
4750
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);
4751
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);
4752
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);
4753
4970
  var channels = program.command("channels").description("Manage and inspect channel configuration");
@@ -4780,16 +4997,16 @@ host.command("pair <host-name>").description("Start an SSM port-forward + shell
4780
4997
  })
4781
4998
  );
4782
4999
  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);
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);
4788
5005
  manager.command("uninstall").description("Remove the OS-level supervisor (launchctl unload + delete plist on macOS). Idempotent.").action(managerUninstallCommand);
4789
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);
4790
5007
  manager.command("uninstall-system-unit").description("Remove the system-level systemd unit (Linux, root). Idempotent. (ENG-4706)").action(managerUninstallSystemUnitCommand);
4791
5008
  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);
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);
4793
5010
  var kanban = program.command("kanban").description("Manage agent kanban boards");
4794
5011
  kanban.command("list").description("List kanban board items for an agent").requiredOption("--agent <code-name>", "Agent code name").action(kanbanListCommand);
4795
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);