@phnx-labs/agents-cli 1.20.37 → 1.20.39
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/commands/beta.js +1 -0
- package/dist/commands/computer-actions.d.ts +10 -0
- package/dist/commands/computer-actions.js +47 -17
- package/dist/commands/doctor.js +48 -1
- package/dist/commands/go.d.ts +28 -0
- package/dist/commands/go.js +238 -0
- package/dist/commands/sessions-picker.d.ts +2 -0
- package/dist/commands/sessions-picker.js +10 -1
- package/dist/commands/sessions-sync.js +23 -15
- package/dist/commands/sessions.d.ts +19 -1
- package/dist/commands/sessions.js +57 -10
- package/dist/index.js +59 -68
- package/dist/lib/beta.d.ts +1 -1
- package/dist/lib/beta.js +1 -1
- package/dist/lib/daemon.js +8 -4
- package/dist/lib/session/active.d.ts +6 -0
- package/dist/lib/session/active.js +1 -1
- package/dist/lib/session/sync/config.d.ts +0 -13
- package/dist/lib/session/sync/config.js +0 -56
- package/dist/lib/shims.d.ts +69 -2
- package/dist/lib/shims.js +260 -23
- package/dist/lib/sync-umbrella.d.ts +1 -1
- package/dist/lib/sync-umbrella.js +7 -5
- package/dist/lib/types.d.ts +1 -1
- package/package.json +1 -1
package/dist/lib/shims.js
CHANGED
|
@@ -14,7 +14,7 @@ import * as os from 'os';
|
|
|
14
14
|
import { fileURLToPath } from 'url';
|
|
15
15
|
import { confirm, select } from '@inquirer/prompts';
|
|
16
16
|
import { IS_WINDOWS, prependToWindowsUserPath } from './platform/index.js';
|
|
17
|
-
import { getShimsDir, getVersionsDir, getBackupsDir, ensureAgentsDir } from './state.js';
|
|
17
|
+
import { getShimsDir, getVersionsDir, getBackupsDir, getHistoryDir, ensureAgentsDir } from './state.js';
|
|
18
18
|
export { getShimsDir };
|
|
19
19
|
import { AGENTS, agentConfigDirName } from './agents.js';
|
|
20
20
|
/**
|
|
@@ -211,7 +211,7 @@ async function promptConflictStrategy(conflictInfos) {
|
|
|
211
211
|
// v22 — export DISABLE_AUTOUPDATER=1 for claude shims so a pinned per-version
|
|
212
212
|
// install can't self-mutate: Claude Code's background auto-updater would
|
|
213
213
|
// otherwise rewrite the pinned binary in place. Explicit user value wins.
|
|
214
|
-
export const SHIM_SCHEMA_VERSION =
|
|
214
|
+
export const SHIM_SCHEMA_VERSION = 23;
|
|
215
215
|
/** Internal marker string used to embed the schema version in shim scripts. */
|
|
216
216
|
const SHIM_VERSION_MARKER = 'agents-shim-version:';
|
|
217
217
|
function shellQuote(value) {
|
|
@@ -295,6 +295,33 @@ if [ -z "$AGENTS_BIN" ] || [ ! -x "$AGENTS_BIN" ]; then
|
|
|
295
295
|
exit 127
|
|
296
296
|
fi
|
|
297
297
|
|
|
298
|
+
# When agents-cli "adopts" a harness's own launcher (symlinks the native binary
|
|
299
|
+
# in ~/.local/bin to this dispatcher so version management wins regardless of
|
|
300
|
+
# PATH order), it records the real original here. Durable (.history, not the
|
|
301
|
+
# regenerable .cache) so the reverse pointer survives a cache wipe. Line 1 is
|
|
302
|
+
# the original binary (what we fall through to); line 2 is the launcher path
|
|
303
|
+
# (used by --release). It is the only safe fall-through target: exec it by
|
|
304
|
+
# ABSOLUTE PATH so we never re-resolve through PATH (which now points back at
|
|
305
|
+
# this dispatcher → infinite re-exec loop).
|
|
306
|
+
ADOPTED_ORIGINAL="$AGENTS_USER_DIR/.history/adopted-launchers/$CLI_COMMAND"
|
|
307
|
+
# Print the recorded original binary iff it is an executable file, else nothing.
|
|
308
|
+
adopted_original_bin() {
|
|
309
|
+
[ -f "$ADOPTED_ORIGINAL" ] || return 1
|
|
310
|
+
local orig
|
|
311
|
+
# First line only — line 2 (launcher path) is for --release, not exec.
|
|
312
|
+
IFS= read -r orig < "$ADOPTED_ORIGINAL" 2>/dev/null || return 1
|
|
313
|
+
[ -n "$orig" ] && [ -x "$orig" ] || return 1
|
|
314
|
+
printf '%s' "$orig"
|
|
315
|
+
}
|
|
316
|
+
# Last-resort fall-through: if a managed version can't be resolved but we've
|
|
317
|
+
# adopted this command's native launcher, run the original so the user's command
|
|
318
|
+
# never breaks. Replaces the process; returns non-zero only when no usable record.
|
|
319
|
+
exec_adopted_original() {
|
|
320
|
+
local orig
|
|
321
|
+
orig=$(adopted_original_bin) || return 1
|
|
322
|
+
exec "$orig" "$@"
|
|
323
|
+
}
|
|
324
|
+
|
|
298
325
|
# Find project agents.yaml walking up from cwd (skip $HOME/.agents/agents.yaml)
|
|
299
326
|
find_project_version() {
|
|
300
327
|
local dir="$PWD"
|
|
@@ -378,15 +405,20 @@ if [ -z "$VERSION" ]; then
|
|
|
378
405
|
VERSION_SOURCE="default"
|
|
379
406
|
;;
|
|
380
407
|
*)
|
|
408
|
+
exec_adopted_original "$@"
|
|
381
409
|
echo " Run: agents use $AGENT <version>" >&2
|
|
382
410
|
exit 1
|
|
383
411
|
;;
|
|
384
412
|
esac
|
|
385
413
|
else
|
|
414
|
+
exec_adopted_original "$@"
|
|
386
415
|
echo " Run: agents use $AGENT <version>" >&2
|
|
387
416
|
exit 1
|
|
388
417
|
fi
|
|
389
418
|
else
|
|
419
|
+
# No managed version at all. If we adopted this command's native launcher,
|
|
420
|
+
# run it so the command keeps working; otherwise report it's unconfigured.
|
|
421
|
+
exec_adopted_original "$@"
|
|
390
422
|
echo "agents: no version of $AGENT configured" >&2
|
|
391
423
|
echo " Run: agents add $AGENT@<version>" >&2
|
|
392
424
|
exit 1
|
|
@@ -414,16 +446,18 @@ if [ "$AGENT" = "grok" ]; then
|
|
|
414
446
|
fi
|
|
415
447
|
fi
|
|
416
448
|
if [ -z "$BINARY" ] || [ ! -x "$BINARY" ]; then
|
|
417
|
-
# Last resort:
|
|
418
|
-
#
|
|
419
|
-
#
|
|
420
|
-
# exec-ing it would re-enter and spin
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
"$
|
|
426
|
-
|
|
449
|
+
# Last resort: the adopted native launcher (recorded absolute path) if we
|
|
450
|
+
# adopted grok, else whatever is on PATH. Prefer the adopted record — after
|
|
451
|
+
# adoption, "command -v grok" resolves to the ~/.local/bin symlink that now
|
|
452
|
+
# points at THIS dispatcher, so exec-ing it would re-enter and spin forever.
|
|
453
|
+
BINARY=$(adopted_original_bin || echo "")
|
|
454
|
+
if [ -z "$BINARY" ]; then
|
|
455
|
+
BINARY=$(command -v grok 2>/dev/null || echo "")
|
|
456
|
+
# Refuse anything that resolves into our own shims dir (the dispatcher).
|
|
457
|
+
case "$(command -v "$BINARY" 2>/dev/null; readlink -f "$BINARY" 2>/dev/null)" in
|
|
458
|
+
*"$AGENTS_USER_DIR/.cache/shims/"*) BINARY="" ;;
|
|
459
|
+
esac
|
|
460
|
+
fi
|
|
427
461
|
fi
|
|
428
462
|
# Kimi is a normal npm agent: "agents add kimi" npm-installs
|
|
429
463
|
# @moonshot-ai/kimi-code into the version dir and the binary lands at
|
|
@@ -440,14 +474,20 @@ if [ "$AGENT" = "grok" ]; then
|
|
|
440
474
|
# IS this dispatcher, so exec'ing it would re-enter and spin in an infinite
|
|
441
475
|
# re-exec loop (the bug this branch fixes).
|
|
442
476
|
elif [ "$AGENT" = "droid" ]; then
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
477
|
+
# Prefer the adopted record first: if droid's ~/.local/bin/droid launcher was
|
|
478
|
+
# adopted, that fixed path now points at THIS dispatcher, so using it directly
|
|
479
|
+
# would infinite-loop. The record holds the real original binary.
|
|
480
|
+
BINARY=$(adopted_original_bin || echo "")
|
|
481
|
+
if [ -z "$BINARY" ]; then
|
|
482
|
+
DROID_BINARY="$HOME/.local/bin/droid"
|
|
483
|
+
if [ -x "$DROID_BINARY" ] && [ "$(readlink -f "$DROID_BINARY" 2>/dev/null)" != "$(readlink -f "$AGENTS_USER_DIR/.cache/shims/$CLI_COMMAND" 2>/dev/null)" ]; then
|
|
484
|
+
BINARY="$DROID_BINARY"
|
|
485
|
+
else
|
|
486
|
+
BINARY=$(command -v droid 2>/dev/null || echo "")
|
|
487
|
+
case "$(readlink -f "$BINARY" 2>/dev/null)" in
|
|
488
|
+
"$AGENTS_USER_DIR/.cache/shims/"*) BINARY="" ;;
|
|
489
|
+
esac
|
|
490
|
+
fi
|
|
451
491
|
fi
|
|
452
492
|
else
|
|
453
493
|
BINARY="$VERSION_DIR/node_modules/.bin/$CLI_COMMAND"
|
|
@@ -481,6 +521,7 @@ if [ ! -x "$BINARY" ]; then
|
|
|
481
521
|
echo " ✔ Installed $AGENT@$VERSION" >&2
|
|
482
522
|
else
|
|
483
523
|
echo " ✗ Failed to install $AGENT@$VERSION" >&2
|
|
524
|
+
exec_adopted_original "$@"
|
|
484
525
|
exit 1
|
|
485
526
|
fi
|
|
486
527
|
else
|
|
@@ -498,15 +539,18 @@ if [ ! -x "$BINARY" ]; then
|
|
|
498
539
|
BINARY="$VERSION_DIR/node_modules/.bin/$CLI_COMMAND"
|
|
499
540
|
;;
|
|
500
541
|
*)
|
|
542
|
+
exec_adopted_original "$@"
|
|
501
543
|
echo " Run: agents add $AGENT@$VERSION" >&2
|
|
502
544
|
exit 1
|
|
503
545
|
;;
|
|
504
546
|
esac
|
|
505
547
|
else
|
|
548
|
+
exec_adopted_original "$@"
|
|
506
549
|
echo " Run: agents add $AGENT@$VERSION" >&2
|
|
507
550
|
exit 1
|
|
508
551
|
fi
|
|
509
552
|
else
|
|
553
|
+
exec_adopted_original "$@"
|
|
510
554
|
echo "agents: $AGENT@$VERSION not installed" >&2
|
|
511
555
|
echo " Run: agents add $AGENT@$VERSION" >&2
|
|
512
556
|
exit 1
|
|
@@ -1572,12 +1616,16 @@ export function getShimPath(agent) {
|
|
|
1572
1616
|
* loop because addShimsToPath() only edits the rc file, never the legacy
|
|
1573
1617
|
* shim file itself.
|
|
1574
1618
|
*/
|
|
1575
|
-
export function getPathShadowingExecutable(agent) {
|
|
1576
|
-
const pathDirs = (process.env.PATH || '').split(path.delimiter).filter(Boolean);
|
|
1577
|
-
const shimPath = path.resolve(getShimPath(agent));
|
|
1619
|
+
export function getPathShadowingExecutable(agent, overrides) {
|
|
1620
|
+
const pathDirs = overrides?.pathDirs ?? (process.env.PATH || '').split(path.delimiter).filter(Boolean);
|
|
1621
|
+
const shimPath = path.resolve(overrides?.shimPath ?? getShimPath(agent));
|
|
1578
1622
|
const cliCommand = AGENTS[agent].cliCommand;
|
|
1579
1623
|
const legacyUserShim = path.resolve(path.join(os.homedir(), '.agents', 'shims', cliCommand));
|
|
1580
1624
|
const managedShimExists = fs.existsSync(shimPath);
|
|
1625
|
+
// The shim's own realpath — an adopted launcher is a symlink at a DIFFERENT
|
|
1626
|
+
// path that resolves here, so identity must be by resolved target, not the
|
|
1627
|
+
// literal path string.
|
|
1628
|
+
const shimReal = managedShimExists ? canonicalOrNull(shimPath) : null;
|
|
1581
1629
|
for (const dir of pathDirs) {
|
|
1582
1630
|
const candidate = path.resolve(dir, cliCommand);
|
|
1583
1631
|
if (!fs.existsSync(candidate)) {
|
|
@@ -1585,6 +1633,12 @@ export function getPathShadowingExecutable(agent) {
|
|
|
1585
1633
|
}
|
|
1586
1634
|
if (candidate === shimPath)
|
|
1587
1635
|
return null;
|
|
1636
|
+
// Adopted launcher: a symlink we repointed at our shim. Its path differs
|
|
1637
|
+
// from shimPath but it resolves to the same file, so it is NOT a shadow —
|
|
1638
|
+
// otherwise every adopted default would be re-flagged forever, resurfacing
|
|
1639
|
+
// the false "runs a native binary" note this whole feature set out to kill.
|
|
1640
|
+
if (shimReal && canonicalOrNull(candidate) === shimReal)
|
|
1641
|
+
return null;
|
|
1588
1642
|
if (candidate === legacyUserShim && managedShimExists) {
|
|
1589
1643
|
// Legacy file from the pre-split layout. Don't treat as shadow — the
|
|
1590
1644
|
// repair flow deletes it via removeLegacyUserShim instead. Continue
|
|
@@ -1630,6 +1684,189 @@ export function removeLegacyUserShim(agent, overrides) {
|
|
|
1630
1684
|
return false;
|
|
1631
1685
|
}
|
|
1632
1686
|
}
|
|
1687
|
+
/**
|
|
1688
|
+
* Where an adopted launcher's provenance is recorded. Lives under durable
|
|
1689
|
+
* `.history` (NOT the regenerable `.cache`) so the reverse pointer to the native
|
|
1690
|
+
* binary survives a cache wipe — the shim reads it to fall through to the native
|
|
1691
|
+
* binary by absolute path when no managed version resolves. Two lines:
|
|
1692
|
+
* line 1 = original binary, line 2 = launcher path (for `--release`).
|
|
1693
|
+
*/
|
|
1694
|
+
export function getAdoptedRecordPath(agent, historyDir = getHistoryDir()) {
|
|
1695
|
+
return path.join(historyDir, 'adopted-launchers', AGENTS[agent].cliCommand);
|
|
1696
|
+
}
|
|
1697
|
+
/**
|
|
1698
|
+
* The launcher a harness's own installer drops in an early-PATH dir. Detection
|
|
1699
|
+
* for adoption keys on the launcher *existing as a symlink resolving outside our
|
|
1700
|
+
* shims dir* — NOT on current PATH order. That's deliberate: the shim only loses
|
|
1701
|
+
* PATH races in non-interactive / GUI-launched shells, which an interactive
|
|
1702
|
+
* `agents` run can't observe via its own PATH. Keying on the durable symlink lets
|
|
1703
|
+
* auto-adoption fire for those users too. Returns the launcher path or null.
|
|
1704
|
+
*/
|
|
1705
|
+
export function findAdoptableLauncher(agent, overrides) {
|
|
1706
|
+
const cliCommand = AGENTS[agent].cliCommand;
|
|
1707
|
+
const homeDir = overrides?.homeDir ?? os.homedir();
|
|
1708
|
+
const shimsDirReal = canonical(overrides?.shimsDir ?? getShimsDir());
|
|
1709
|
+
// ~/.local/bin is where grok/kimi/antigravity/claude/codex/droid self-install.
|
|
1710
|
+
const candidate = path.join(homeDir, '.local', 'bin', cliCommand);
|
|
1711
|
+
let stat;
|
|
1712
|
+
try {
|
|
1713
|
+
stat = fs.lstatSync(candidate);
|
|
1714
|
+
}
|
|
1715
|
+
catch {
|
|
1716
|
+
return null;
|
|
1717
|
+
}
|
|
1718
|
+
if (!stat.isSymbolicLink())
|
|
1719
|
+
return null; // real binaries are never auto-adopted
|
|
1720
|
+
let resolved;
|
|
1721
|
+
try {
|
|
1722
|
+
resolved = fs.realpathSync(candidate); // broken symlink throws → skip
|
|
1723
|
+
}
|
|
1724
|
+
catch {
|
|
1725
|
+
return null;
|
|
1726
|
+
}
|
|
1727
|
+
// Already ours, or resolves into our shims dir → not adoptable.
|
|
1728
|
+
if (resolved === shimsDirReal || resolved.startsWith(shimsDirReal + path.sep))
|
|
1729
|
+
return null;
|
|
1730
|
+
return candidate;
|
|
1731
|
+
}
|
|
1732
|
+
/** Canonical path for identity comparison — realpath when it exists (resolves
|
|
1733
|
+
* symlinks AND platform aliases like macOS /var → /private/var), else resolve. */
|
|
1734
|
+
function canonical(p) {
|
|
1735
|
+
try {
|
|
1736
|
+
return fs.realpathSync(p);
|
|
1737
|
+
}
|
|
1738
|
+
catch {
|
|
1739
|
+
return path.resolve(p);
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
/** Like canonical(), but null when the path can't be resolved (broken/racy
|
|
1743
|
+
* symlink) — used where a failed resolve must NOT collapse to the input path. */
|
|
1744
|
+
function canonicalOrNull(p) {
|
|
1745
|
+
try {
|
|
1746
|
+
return fs.realpathSync(p);
|
|
1747
|
+
}
|
|
1748
|
+
catch {
|
|
1749
|
+
return null;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
/**
|
|
1753
|
+
* Adopt the harness's own launcher that shadows our shim on PATH.
|
|
1754
|
+
*
|
|
1755
|
+
* PATH-ordering fixes (editing rc files) can never reliably win: `~/.local/bin`
|
|
1756
|
+
* (where grok/droid/etc. self-install) is prepended in `.zshenv`/`.zprofile`
|
|
1757
|
+
* for *every* shell, while our shims prepend only lands in `.zshrc`
|
|
1758
|
+
* (interactive). No single rc file guarantees "last prepend wins" across zsh's
|
|
1759
|
+
* whole sourcing chain, so the shim loses in non-interactive / GUI-launched
|
|
1760
|
+
* contexts. Instead of fighting PATH order, we *become* the launcher: replace
|
|
1761
|
+
* the shadowing symlink with one pointing at our shim, and record the real
|
|
1762
|
+
* original so the shim falls through to it when no managed version is selected.
|
|
1763
|
+
*
|
|
1764
|
+
* Regression bounds:
|
|
1765
|
+
* - Only ever touches a **symlink** (never renames/deletes a real binary).
|
|
1766
|
+
* - Records the resolved original + launcher path for lossless restore
|
|
1767
|
+
* (`releaseAdoptedLauncher`), in durable `.history` so a cache wipe can't
|
|
1768
|
+
* orphan the reverse pointer.
|
|
1769
|
+
* - Idempotent: a no-op once the launcher already points at our shim.
|
|
1770
|
+
* - Never records our own shim as the "original" (would loop).
|
|
1771
|
+
*/
|
|
1772
|
+
export function adoptShadowingLauncher(agent, overrides) {
|
|
1773
|
+
const shimsDir = overrides?.shimsDir ?? getShimsDir();
|
|
1774
|
+
const shimPath = path.join(shimsDir, AGENTS[agent].cliCommand);
|
|
1775
|
+
const shimReal = canonical(shimPath);
|
|
1776
|
+
const shimsDirReal = canonical(shimsDir);
|
|
1777
|
+
const launcher = overrides?.shadowedBy ?? getPathShadowingExecutable(agent) ?? findAdoptableLauncher(agent, { shimsDir });
|
|
1778
|
+
if (!launcher)
|
|
1779
|
+
return { adopted: false, reason: 'no-shadow' };
|
|
1780
|
+
let stat;
|
|
1781
|
+
try {
|
|
1782
|
+
stat = fs.lstatSync(launcher);
|
|
1783
|
+
}
|
|
1784
|
+
catch {
|
|
1785
|
+
return { adopted: false, reason: 'error', launcher };
|
|
1786
|
+
}
|
|
1787
|
+
// Only adopt symlinks. A real binary in an early-PATH dir is left untouched —
|
|
1788
|
+
// renaming a multi-hundred-MB native binary is exactly the kind of surprise
|
|
1789
|
+
// this feature must avoid. (Its shim stays reachable via the versioned name.)
|
|
1790
|
+
if (!stat.isSymbolicLink()) {
|
|
1791
|
+
return { adopted: false, reason: 'not-a-symlink', launcher };
|
|
1792
|
+
}
|
|
1793
|
+
const resolved = canonical(launcher);
|
|
1794
|
+
// Already ours → nothing to do.
|
|
1795
|
+
if (resolved === shimReal) {
|
|
1796
|
+
return { adopted: false, reason: 'already-adopted', launcher };
|
|
1797
|
+
}
|
|
1798
|
+
// Never record a target that resolves back into our shims dir: exec-ing it
|
|
1799
|
+
// from the shim would re-enter this dispatcher and spin forever.
|
|
1800
|
+
if (resolved === shimsDirReal || resolved.startsWith(shimsDirReal + path.sep)) {
|
|
1801
|
+
return { adopted: false, reason: 'unsafe-target', launcher };
|
|
1802
|
+
}
|
|
1803
|
+
try {
|
|
1804
|
+
const recordPath = getAdoptedRecordPath(agent, overrides?.historyDir);
|
|
1805
|
+
fs.mkdirSync(path.dirname(recordPath), { recursive: true });
|
|
1806
|
+
// Line 1: original binary (shim fall-through target). Line 2: launcher path
|
|
1807
|
+
// (release restores this exact symlink, independent of PATH order at release
|
|
1808
|
+
// time — the M3 fix). Absolute launcher path so release never has to
|
|
1809
|
+
// re-derive it from a PATH scan that may miss.
|
|
1810
|
+
fs.writeFileSync(recordPath, `${resolved}\n${path.resolve(launcher)}\n`, 'utf-8');
|
|
1811
|
+
// Repoint the launcher at our shim. rm + symlink (not atomic rename) is fine
|
|
1812
|
+
// here: the record is already written, so a crash between the two leaves a
|
|
1813
|
+
// recoverable state and the next run re-adopts idempotently.
|
|
1814
|
+
fs.rmSync(launcher);
|
|
1815
|
+
fs.symlinkSync(shimPath, launcher);
|
|
1816
|
+
return { adopted: true, launcher, original: resolved };
|
|
1817
|
+
}
|
|
1818
|
+
catch {
|
|
1819
|
+
return { adopted: false, reason: 'error', launcher };
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
/**
|
|
1823
|
+
* Undo `adoptShadowingLauncher`: repoint the launcher back at the recorded
|
|
1824
|
+
* original and drop the record. Reversible escape hatch for users who want the
|
|
1825
|
+
* native launcher to win. Returns the restored original path, or null if there
|
|
1826
|
+
* was nothing to release.
|
|
1827
|
+
*/
|
|
1828
|
+
export function releaseAdoptedLauncher(agent, overrides) {
|
|
1829
|
+
const shimsDir = overrides?.shimsDir ?? getShimsDir();
|
|
1830
|
+
const recordPath = getAdoptedRecordPath(agent, overrides?.historyDir);
|
|
1831
|
+
let lines;
|
|
1832
|
+
try {
|
|
1833
|
+
lines = fs.readFileSync(recordPath, 'utf-8').split('\n').map((l) => l.trim());
|
|
1834
|
+
}
|
|
1835
|
+
catch {
|
|
1836
|
+
return null;
|
|
1837
|
+
}
|
|
1838
|
+
const original = lines[0] ?? '';
|
|
1839
|
+
if (!original)
|
|
1840
|
+
return null;
|
|
1841
|
+
// Line 2 is the exact launcher we rewrote at adopt time. Restoring it directly
|
|
1842
|
+
// (rather than re-deriving from PATH) means release works regardless of the
|
|
1843
|
+
// current shell's PATH order — the M3 fix. Fall back to a PATH scan only for
|
|
1844
|
+
// records written before this format existed.
|
|
1845
|
+
const launcher = lines[1] || getPathShadowingExecutable(agent) || original;
|
|
1846
|
+
const shimReal = canonical(path.join(shimsDir, AGENTS[agent].cliCommand));
|
|
1847
|
+
try {
|
|
1848
|
+
// Only rewrite the launcher if it currently points at our shim (i.e. we own
|
|
1849
|
+
// it). If the user has since replaced it themselves, leave it alone.
|
|
1850
|
+
let pointsAtShim = false;
|
|
1851
|
+
try {
|
|
1852
|
+
pointsAtShim = fs.lstatSync(launcher).isSymbolicLink()
|
|
1853
|
+
&& canonical(launcher) === shimReal;
|
|
1854
|
+
}
|
|
1855
|
+
catch { /* launcher gone — recreate below */ }
|
|
1856
|
+
if (pointsAtShim || !fs.existsSync(launcher)) {
|
|
1857
|
+
try {
|
|
1858
|
+
fs.rmSync(launcher);
|
|
1859
|
+
}
|
|
1860
|
+
catch { /* may not exist */ }
|
|
1861
|
+
fs.symlinkSync(original, launcher);
|
|
1862
|
+
}
|
|
1863
|
+
fs.rmSync(recordPath);
|
|
1864
|
+
return original;
|
|
1865
|
+
}
|
|
1866
|
+
catch {
|
|
1867
|
+
return null;
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1633
1870
|
/**
|
|
1634
1871
|
* Check if the agent's CLI command is shadowed by a shell alias.
|
|
1635
1872
|
*
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* secrets -> listRemoteBundles + pullBundle (needs a passphrase; skipped
|
|
14
14
|
* cleanly when none is available — tokenized non-interactive auth
|
|
15
15
|
* arrives with `agents login`, #366/#367)
|
|
16
|
-
* sessions -> syncSessions(), gated by isSyncConfigured()
|
|
16
|
+
* sessions -> syncSessions(), gated by the session-sync beta opt-in + isSyncConfigured(), like the daemon
|
|
17
17
|
* reconcile-> refresh({ skipPrompts }) — re-materialize resources into homes
|
|
18
18
|
*/
|
|
19
19
|
/** The five umbrella flags off `agents sync`. */
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* secrets -> listRemoteBundles + pullBundle (needs a passphrase; skipped
|
|
14
14
|
* cleanly when none is available — tokenized non-interactive auth
|
|
15
15
|
* arrives with `agents login`, #366/#367)
|
|
16
|
-
* sessions -> syncSessions(), gated by isSyncConfigured()
|
|
16
|
+
* sessions -> syncSessions(), gated by the session-sync beta opt-in + isSyncConfigured(), like the daemon
|
|
17
17
|
* reconcile-> refresh({ skipPrompts }) — re-materialize resources into homes
|
|
18
18
|
*/
|
|
19
19
|
import { pullRepo } from './git.js';
|
|
@@ -111,10 +111,12 @@ export async function runUmbrellaSync(args) {
|
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
if (plan.fetchSessions) {
|
|
114
|
-
// Gate exactly like the daemon:
|
|
115
|
-
// is a clean no-op, not an error that
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
// Gate exactly like the daemon: without the `session-sync` beta opt-in (or
|
|
115
|
+
// with a missing r2.backups bundle) this is a clean no-op, not an error that
|
|
116
|
+
// fails the whole sync.
|
|
117
|
+
const { isBetaEnabled } = await import('./beta.js');
|
|
118
|
+
const { isSyncConfigured } = await import('./session/sync/config.js');
|
|
119
|
+
if (isBetaEnabled('session-sync') && isSyncConfigured()) {
|
|
118
120
|
const { syncSessions } = await import('./session/sync/sync.js');
|
|
119
121
|
const r = await syncSessions();
|
|
120
122
|
result.sessions = { ran: true, pushed: r.pushed, pulled: r.pulled, merged: r.merged };
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -61,7 +61,7 @@ export interface BudgetConfig {
|
|
|
61
61
|
require_confirm_over?: number;
|
|
62
62
|
}
|
|
63
63
|
/** Preview features that users can opt into via `agents beta`. */
|
|
64
|
-
export type BetaFeatureName = 'drive' | 'factory';
|
|
64
|
+
export type BetaFeatureName = 'drive' | 'factory' | 'session-sync';
|
|
65
65
|
/** Subset of chalk color names used for agent-specific terminal output. */
|
|
66
66
|
export type ChalkColor = 'magenta' | 'green' | 'blue' | 'cyan' | 'yellowBright' | 'redBright' | 'whiteBright' | 'blueBright' | 'greenBright' | 'magentaBright' | 'cyanBright';
|
|
67
67
|
/** Static configuration for a single agent -- paths, capabilities, and format conventions. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.39",
|
|
4
4
|
"description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|