@phnx-labs/agents-cli 1.20.61 → 1.20.63
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/CHANGELOG.md +57 -0
- package/README.md +10 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/browser.js +13 -3
- package/dist/commands/exec.js +51 -1
- package/dist/commands/funnel.d.ts +5 -0
- package/dist/commands/funnel.js +62 -0
- package/dist/commands/hosts.js +42 -0
- package/dist/commands/repo.d.ts +4 -4
- package/dist/commands/repo.js +30 -19
- package/dist/commands/routines.js +73 -16
- package/dist/commands/sessions-sync.d.ts +1 -0
- package/dist/commands/sessions-sync.js +16 -2
- package/dist/commands/sessions.js +8 -1
- package/dist/commands/setup.js +9 -0
- package/dist/commands/ssh.js +72 -2
- package/dist/commands/sync-provision.d.ts +23 -0
- package/dist/commands/sync-provision.js +107 -0
- package/dist/commands/webhook.d.ts +9 -0
- package/dist/commands/webhook.js +93 -0
- package/dist/index.js +6 -2
- package/dist/lib/agents.d.ts +26 -0
- package/dist/lib/agents.js +100 -28
- package/dist/lib/browser/ipc.js +5 -4
- package/dist/lib/browser/profiles.d.ts +13 -0
- package/dist/lib/browser/profiles.js +17 -0
- package/dist/lib/browser/service.d.ts +12 -1
- package/dist/lib/browser/service.js +48 -13
- package/dist/lib/browser/sessions-list.d.ts +40 -0
- package/dist/lib/browser/sessions-list.js +190 -0
- package/dist/lib/commands.js +29 -0
- package/dist/lib/convert.d.ts +11 -0
- package/dist/lib/convert.js +22 -0
- package/dist/lib/daemon.js +2 -0
- package/dist/lib/devices/fleet.d.ts +62 -0
- package/dist/lib/devices/fleet.js +128 -0
- package/dist/lib/doctor-diff.js +17 -1
- package/dist/lib/funnel.d.ts +5 -0
- package/dist/lib/funnel.js +23 -0
- package/dist/lib/git.d.ts +21 -5
- package/dist/lib/git.js +64 -14
- package/dist/lib/goose-commands.d.ts +41 -0
- package/dist/lib/goose-commands.js +176 -0
- package/dist/lib/hooks.js +134 -0
- package/dist/lib/hosts/credentials.d.ts +28 -0
- package/dist/lib/hosts/credentials.js +48 -0
- package/dist/lib/hosts/dispatch.d.ts +25 -0
- package/dist/lib/hosts/dispatch.js +68 -2
- package/dist/lib/hosts/passthrough.d.ts +13 -10
- package/dist/lib/hosts/passthrough.js +119 -29
- package/dist/lib/mailbox-gc.js +4 -16
- package/dist/lib/migrate.d.ts +12 -0
- package/dist/lib/migrate.js +55 -1
- package/dist/lib/permissions.d.ts +15 -0
- package/dist/lib/permissions.js +99 -0
- package/dist/lib/plugins.d.ts +20 -0
- package/dist/lib/plugins.js +118 -0
- package/dist/lib/resources/permissions.js +2 -0
- package/dist/lib/routines.d.ts +29 -10
- package/dist/lib/routines.js +47 -15
- package/dist/lib/session/active.d.ts +8 -1
- package/dist/lib/session/active.js +1 -0
- package/dist/lib/session/parse.js +12 -0
- package/dist/lib/session/state.d.ts +33 -0
- package/dist/lib/session/state.js +40 -0
- package/dist/lib/session/sync/agents.d.ts +2 -0
- package/dist/lib/session/sync/agents.js +39 -1
- package/dist/lib/session/sync/config.d.ts +8 -0
- package/dist/lib/session/sync/config.js +6 -1
- package/dist/lib/session/sync/provision.d.ts +49 -0
- package/dist/lib/session/sync/provision.js +91 -0
- package/dist/lib/session/sync/sync.d.ts +3 -0
- package/dist/lib/session/sync/sync.js +26 -6
- package/dist/lib/session/sync/transcript-crypto.d.ts +77 -0
- package/dist/lib/session/sync/transcript-crypto.js +147 -0
- package/dist/lib/staleness/detectors/permissions.js +23 -0
- package/dist/lib/staleness/detectors/subagents.js +36 -0
- package/dist/lib/staleness/writers/commands.js +7 -0
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/staleness/writers/subagents.js +22 -3
- package/dist/lib/startup/command-registry.d.ts +2 -0
- package/dist/lib/startup/command-registry.js +11 -0
- package/dist/lib/state.d.ts +10 -2
- package/dist/lib/state.js +14 -2
- package/dist/lib/subagents.d.ts +32 -0
- package/dist/lib/subagents.js +238 -0
- package/dist/lib/triggers/webhook.d.ts +70 -27
- package/dist/lib/triggers/webhook.js +264 -43
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,62 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 1.20.63
|
|
6
|
+
|
|
7
|
+
- **Built-in routines: the daemon now fires routines shipped in the system repo.** Routine discovery (`listJobs`/`readJob`) unions a new system layer — `~/.agents/.system/routines/*.yml` (shipped via `gh:phnx-labs/.agents-system`, which every install pulls at `agents setup`) — under the existing project and user layers. Ordering is project > user > system with first-seen-wins, so a routine shipped as a built-in fires for every install, while a user routine of the same name overrides it and a user copy with `enabled: false` disables it. The daemon (which loads with no `cwd`) sees user + system routines; `writeJob` still only ever writes to the user layer, so built-ins are never mutated in place. This is what lets a routine like `check-updates` ship to all users centrally. Source: `apps/cli/src/lib/state.ts` (`getSystemRoutinesDir`), `apps/cli/src/lib/routines.ts`, `apps/cli/src/lib/routines.test.ts`.
|
|
8
|
+
|
|
9
|
+
- **`--host` / `--device` work across virtually all first-class subcommand groups (RUSH-1691).** Previously only a handful of groups accepted the flags; `agents repos list --host yosemite-s0` (and the same with `--device`) died with commander's raw `unknown option`. Remote routing now covers `repos`/`repo`, status/inspect groups, config/resource groups (`plugins`, `skills`, `hooks`, `sync`, …), `teams`, `routines`, and more via the central `maybeRunOnHost` allowlist. Commands with their own richer host handling (`run`, `sessions`, `feed`, `computer`, `secrets`, `logs`) still fall through to their actions. Groups with no remote semantics reject the flag with a clear message instead of `unknown option`. Self-host targets strip the routing flags before the local command parses so fall-through never trips an unregistered option. Source: `apps/cli/src/lib/hosts/passthrough.ts`, `apps/cli/src/commands/repo.ts`, `apps/cli/docs/{00-concepts,hosts}.md`.
|
|
10
|
+
- **Signed webhook ingress for routines via Tailscale Funnel (RUSH-1456, RUSH-1459, RUSH-1460, RUSH-1461).**
|
|
11
|
+
Routine triggers now understand both GitHub and Linear event sources, including
|
|
12
|
+
Linear action/team/label filters. `agents webhook serve --secrets-bundle <name>`
|
|
13
|
+
exposes signed localhost endpoints at `/hooks/github` and `/hooks/linear` with
|
|
14
|
+
raw-body HMAC verification, Linear timestamp checks, duplicate delivery
|
|
15
|
+
suppression, and rate limiting. `agents funnel status/up` wraps the allowed
|
|
16
|
+
Tailscale Funnel ports through the existing SSH/device path so a webhook
|
|
17
|
+
receiver can be exposed without hand-written SSH commands. Source:
|
|
18
|
+
`apps/cli/src/lib/routines.ts`, `apps/cli/src/lib/triggers/webhook.ts`,
|
|
19
|
+
`apps/cli/src/commands/routines.ts`, `apps/cli/src/commands/webhook.ts`,
|
|
20
|
+
`apps/cli/src/commands/funnel.ts`, `apps/cli/src/lib/funnel.ts`.
|
|
21
|
+
- **Guided session-sync provisioning in `agents setup` and `agents sessions sync --setup` (RUSH-1468).** Joining a machine to the cross-machine session-sync fabric no longer requires hand-running four `agents secrets add r2.backups …` commands. A new interactive step mints the `r2.backups` bundle (R2 account/bucket/access-key/secret + a generated `R2_SYNC_ENC_KEY`), probes read+write connectivity with a throwaway object, and opts the machine into the `session-sync` beta on success. The first machine mints and prints the shared encryption key; every other machine pastes it so the whole fabric shares one key (an existing key is reused, never overwritten — overwriting would orphan peers' encrypted transcripts). `agents setup` offers it opt-in (default No, never blocks setup); `agents sessions sync --setup` runs it explicitly and can re-show the shared key. Source: `apps/cli/src/lib/session/sync/provision.ts`, `apps/cli/src/lib/session/sync/provision.test.ts`, `apps/cli/src/commands/sync-provision.ts`, `apps/cli/src/commands/setup.ts`, `apps/cli/src/commands/sessions-sync.ts`.
|
|
22
|
+
- **`agents fleet` alias + fleet-wide rollout (RUSH-1632).** `fleet` is an alias
|
|
23
|
+
for `devices`. New subcommands `update [version]` and `run <cmd…>` roll out
|
|
24
|
+
across every online device with a per-device result table. Source:
|
|
25
|
+
`apps/cli/src/commands/ssh.ts`, `apps/cli/src/lib/devices/fleet.ts`.
|
|
26
|
+
- **`agents hosts stop <id>` (alias `kill`) terminates a detached host run from the origin machine (RUSH-1360).**
|
|
27
|
+
Sends SIGTERM to the remote process group, writes exit `143` only when a live
|
|
28
|
+
group was signaled (or no `.exit` existed), and keeps the remote log for
|
|
29
|
+
`agents hosts logs <id>`. Source: `apps/cli/src/lib/hosts/dispatch.ts`,
|
|
30
|
+
`apps/cli/src/commands/hosts.ts`.
|
|
31
|
+
- **Fix: mailbox GC actually archives expired messages on live boxes (RUSH-1611).**
|
|
32
|
+
The live-box branch of `gcMailbox` only incremented `messagesDroppedExpired`
|
|
33
|
+
without moving the file to `consumed/`, so `agents feed --dispatch` could report
|
|
34
|
+
drops while leaving expired messages in `inbox/`/`processing/`. GC now reuses
|
|
35
|
+
`sweepExpired` (the same path as drain/peek). Source: `apps/cli/src/lib/mailbox-gc.ts`.
|
|
36
|
+
- **Fix: Antigravity sign-in detection on Linux when the OAuth grant lives in Secret Service (RUSH-1329).** `agy` uses the Go keyring library, which prefers libsecret (gnome-keyring) over the file fallback whenever a Secret Service daemon is running — so `~/.gemini/antigravity-cli/antigravity-oauth-token` may be absent even when the user is signed in. After the file check, `getAccountInfo` now probes `secret-tool lookup service gemini username antigravity` (exit 0 = present; stdout discarded), mirroring the macOS `security find-generic-password` probe from #506. Missing `secret-tool`, locked collections, and timeouts all read as signed out. Opt out with `AGENTS_NO_KEYCHAIN_PROBE=1`. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/agents.test.ts`.
|
|
37
|
+
- **Client-side (zero-knowledge) encryption of session transcripts before R2 upload (RUSH-1463).** Transcripts carry secrets, tokens, and absolute file paths; R2's server-side encryption uses Cloudflare's key, so anyone with bucket-read access (or Cloudflare) could read them as plaintext NDJSON. `agents sessions sync` now seals each transcript BODY client-side with AES-256-GCM before upload and decrypts on pull, so R2 only ever stores ciphertext. The 32-byte key is a new `R2_SYNC_ENC_KEY` in the `r2.backups` bundle — shared across the sync fabric (every machine derives the identical key) and deliberately separate from the R2 access key so rotating the token never orphans encrypted objects. CRDT identity stays over plaintext (the manifest hash is cleartext; pull decrypts before the G-Set union), so cross-machine merge is unaffected. Pull transparently reads legacy plaintext objects (migration-safe); a push with no key configured still uploads but emits a loud per-cycle warning. A new `R2_ENDPOINT` override points sync at any S3-compatible store (MinIO/other providers), which is also how the flow is verified end-to-end without live R2. Source: `apps/cli/src/lib/session/sync/transcript-crypto.ts`, `apps/cli/src/lib/session/sync/transcript-crypto.test.ts`, `apps/cli/src/lib/session/sync/sync.ts`, `apps/cli/src/lib/session/sync/config.ts`, `apps/cli/src/commands/sessions-sync.ts`, `apps/cli/src/lib/daemon.ts`.
|
|
38
|
+
- **Extend session sync to Droid, Grok, Kimi, and OpenCode (RUSH-1467).** `agents sessions sync` now includes these four agents in its upload/download matrix. `SyncAgentSpec` gains an optional `ext` field so agents with non-`.jsonl` transcript files (e.g., Kimi `state.json`) are walked correctly. Droid `.jsonl` rollouts, Grok `events.jsonl` streams, and Kimi `state.json` metadata files round-trip through the R2 mirror; OpenCode is slotted in `SYNC_AGENTS` but remains a placeholder because its sessions live in a SQLite DB and still require an SQLite-to-JSONL export step. Source: `apps/cli/src/lib/session/sync/agents.ts`, `apps/cli/src/lib/session/sync/agents.test.ts`, `apps/cli/src/commands/sessions-sync.ts`.
|
|
39
|
+
- **`agents repos` is canonical (`repo` alias); push/pull echo the resolved target; push no longer no-ops when clean-but-ahead; pull rebases on diverge (RUSH-1454).** Help now prints `Usage: agents repos …`. Push/pull report `user (~/.agents → origin/main): …` instead of the bare alias. `commitAndPush` still `git push`es when the tree is clean but local is ahead of origin (previously returned success without pushing). `pullRepo` uses `git pull --rebase` so divergent branches reconcile instead of failing with a raw git error. Source: `apps/cli/src/commands/repo.ts`, `apps/cli/src/lib/git.ts`, `apps/cli/src/lib/startup/command-registry.ts`.
|
|
40
|
+
- **`agents run --host <name> --copy-creds` provisions runtime credentials on a persistent host (RUSH-1608).**
|
|
41
|
+
Reuses the `--lease` credential path (`resolveClaudeCredentialsBlob` + the
|
|
42
|
+
`~/.claude/.credentials.json` bootstrap) but makes copying tokens to a
|
|
43
|
+
persistent host strictly opt-in per run. The user picks runtimes, sees a
|
|
44
|
+
consent prompt naming accounts and the Claude OAuth token, and the files are
|
|
45
|
+
shredded after the run. Source: `apps/cli/src/commands/exec.ts`,
|
|
46
|
+
`apps/cli/src/lib/hosts/dispatch.ts`, `apps/cli/src/lib/hosts/credentials.ts`,
|
|
47
|
+
`apps/cli/src/lib/hosts/credentials.test.ts`.
|
|
48
|
+
|
|
49
|
+
## 1.20.62
|
|
50
|
+
|
|
51
|
+
- **Browser downloads land in a known per-profile dir; profile data is consolidated.** A browser profile is now one self-contained tree under `~/.agents/.cache/browser/<profile>/`: `chrome-data/`, `downloads/`, and `sessions/<task>/` (screenshots, PDFs, recordings). Previously downloads had no configured destination — the CLI only set the download dir when the agent explicitly ran `browser download --path`, so absent that call a download fell to Chromium's own default (for an attached user browser like comet, wherever *that* browser was last configured), which is how downloads escaped into random locations. The service now sets the profile's `downloads/` dir browser-global at connect time (both fresh launch and every attach path), so downloads always land somewhere agents-cli controls; `browser download --path` becomes an optional override (omit it to use the profile default) and reports the resolved path. Screenshots/PDFs/recordings moved from the old GLOBAL `browser/sessions/<task>/` root to the per-profile `browser/<profile>/sessions/<task>/`, with a one-shot migration that folds existing captures into the owning profile (attributed via each profile's `tasks.json`; unattributable captures go to a `_legacy` bucket). New `agents browser sessions [--profile <name>] [--open latest|<file>] [--json]` lists a profile's captures + downloads, aliased as `agents sessions --browser`. Source: `apps/cli/src/lib/browser/{profiles,service,ipc,sessions-list}.ts`, `apps/cli/src/lib/migrate.ts`, `apps/cli/src/commands/{browser,sessions}.ts`.
|
|
52
|
+
- **Wire Goose commands support (RUSH-1572).** `agents` now syncs slash commands to Goose as recipe YAML files under `~/.config/goose/commands/<name>.yaml`, each registered in `~/.config/goose/config.yaml` under a `slash_commands: [{ command, recipe_path }]` array (Goose has no native slash-command file format — a slash command IS a recipe). The command recipes live in a dir distinct from the workflow recipes dir (`~/.config/goose/recipes/`) so the workflow detector never treats a command recipe as a workflow. Registration is a read-modify-write that preserves every other `config.yaml` key (`mcp_servers`, `extensions`, …) and other `slash_commands` entries, and removal soft-deletes the recipe + unregisters the entry. Flip Goose's `commands` capability and add `goose` branches to install/list/match/remove, the staleness commands writer, and doctor-diff, backed by a new `goose-commands.ts` module + a `markdownToGooseRecipe` converter. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/goose-commands.ts`, `apps/cli/src/lib/commands.ts`, `apps/cli/src/lib/convert.ts`, `apps/cli/src/lib/staleness/writers/commands.ts`, `apps/cli/src/lib/doctor-diff.ts`.
|
|
53
|
+
- **Fix: Hermes plugin sync no longer disables a plugin the user explicitly enabled.** The plugin install path (RUSH-1688) unconditionally forced `plugins.enabled` to the exec-surface trust verdict on every sync. An ordinary un-flagged background re-sync computes `enable=false` for a plugin with hooks/tools, so it stripped that plugin from the `~/.hermes/config.yaml` allowlist — clobbering a plugin the user deliberately enabled with `--allow-exec-surfaces`. The install path now enables only when trusted and never down-toggles (matching the marketplace flow's add-if-trusted semantics); removal still unregisters explicitly. Source: `apps/cli/src/lib/plugins.ts`.
|
|
54
|
+
- **Wire Goose subagents support (RUSH-1573).** `agents` now syncs subagents to Goose as recipe YAML files under `~/.config/goose/agents/<name>.yaml` — Goose has no dedicated subagent format, so a named subagent IS a recipe (goose auto-discovers `~/.config/goose/agents/` and delegates to them by name in autonomous mode). `transformSubagentForGoose` emits the same recipe schema agents-cli already uses for Goose workflow recipes (`version`/`title`/`description`/`instructions`/`prompt`, plus optional `settings.goose_model`). Flip Goose's `subagents` capability and wire the install/remove/list/orphan/version-remove branches plus the staleness subagents writer and detector. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/subagents.ts`, `apps/cli/src/lib/staleness/{writers,detectors}/subagents.ts`.
|
|
55
|
+
|
|
56
|
+
- **Wire ForgeCode commands and subagents support (RUSH-1689, RUSH-1690).** `agents` now syncs slash commands to ForgeCode as Markdown files under `~/.forge/commands/<name>.md` (previously ForgeCode had `commands: false` and received commands only as skills), and named subagents as Markdown-with-frontmatter definitions under `~/.forge/agents/<name>.md` (same `color`-less shape as Droid/Copilot/Cursor, so `transformSubagentForForge` aliases `transformSubagentForDroid`). Flip ForgeCode's `commands`/`subagents` capabilities, set `commandsDir`, add the subagent transform plus install/remove/list/orphan/version-remove branches, and register the subagents writer + detector. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/subagents.ts`, `apps/cli/src/lib/staleness/{writers,detectors}/subagents.ts`.
|
|
57
|
+
- **Wire allowlist (permissions) support for OpenClaw (RUSH-1570).** OpenClaw gates at TOOL granularity only, so permission sync maps just **blanket** (whole-tool) rules into `~/.openclaw/openclaw.json` `tools.alsoAllow` (allow) / `tools.deny` (deny): `bash → exec`, `read → read`, `write`/`edit → write`, `webfetch → web_fetch`, `websearch → web_search`. Sub-command/path/domain rules (`Bash(git:*)`, `Write(secrets/**)`, `WebFetch(domain:x)`) have no tool-level equivalent and are skipped — coarse-mapping a specific deny to a whole tool would wrongly gate every use of that tool. The absolute `tools.allow` list is never touched, and all other keys (`mcp`, `exec`, `agents`, …) are preserved on read-modify-write. Flip OpenClaw's `allowlist: true`, add `convertToOpenClawFormat` + the `openclaw` branch in `applyPermissionsToVersion`, register the config path and staleness detector. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/permissions.ts`, `apps/cli/src/lib/resources/permissions.ts`, `apps/cli/src/lib/staleness/detectors/permissions.ts`.
|
|
58
|
+
- **Wire subagents support for Cursor CLI (RUSH-1388).** cursor-agent loads custom subagents as Markdown with YAML frontmatter under `~/.cursor/agents/*.md` (project-scoped `.cursor/agents/` also supported natively), same shape as Claude/Droid/Copilot minus the `color` field, gated at `>= 2026.1.22` (cursor-agent's CalVer build tag for Cursor 2.4). Flip Cursor's `subagents`, add `transformSubagentForCursor` (alias of `transformSubagentForDroid`), and wire the install/remove, list, orphan-detection, writer, and detector paths. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/subagents.ts`, `apps/cli/src/lib/staleness/{writers,detectors}/subagents.ts`.
|
|
59
|
+
- **Wire hooks support for Hermes (RUSH-1687).** `agents` now registers central hooks into Hermes Agent's `~/.hermes/config.yaml` under a `hooks:` block (YAML, ≥ 0.11.0). The registrar read-modify-writes that shared config so sibling keys like `mcp_servers` survive, maps canonical events to Hermes' snake_case lifecycle names (`SessionStart→on_session_start`, `SessionEnd→on_session_end`, `PreToolUse→pre_tool_call`, `PostToolUse→post_tool_call`, `SubagentStop→subagent_stop`, `UserPromptSubmit→pre_llm_call`, `Stop→on_session_finalize`), and clamps each hook's timeout to 300s (default 60s). Managed entries are re-synced idempotently while user-authored hooks are preserved. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/hooks.ts`, `apps/cli/src/lib/staleness/writers/hooks.ts`.
|
|
60
|
+
|
|
5
61
|
## 1.20.61
|
|
6
62
|
|
|
7
63
|
- **Detect OpenCode sign-in state so `agents view` stops mislabeling a logged-in install as "not signed in."** `getAccountInfo` had no `opencode` case, so it fell through to `signedIn: false` and every row printed "(not signed in — run opencode to log in)" even with a live login. It now reads OpenCode's `auth.json` (`$XDG_DATA_HOME/opencode/auth.json`, defaulting to `~/.local/share/opencode/auth.json` on every platform — `xdg-basedir` does not special-case macOS), validates each provider entry against its `oauth`/`api`/`wellknown` credential shape, and reports the account as signed in with the non-secret provider ids surfaced as the account label (e.g. `id:muse-spark`). Credential secrets (`access`/`refresh`/`key`/`token`) are only inspected for presence — never read into any display or JSON output. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/agents.test.ts`.
|
|
@@ -16,6 +72,7 @@
|
|
|
16
72
|
- **Wire Gemini permissions/allowlist support (RUSH-1567).** Gemini permission groups now sync Bash allow/deny rules into `.gemini/settings.json` as `tools.core` / `tools.exclude` entries with per-command `ShellTool(...)` patterns; non-Bash canonical permissions remain unsupported by Gemini's native tool grammar and are skipped. Flip `allowlist: true`, register the permission writer/detector through the capability table, and replace the dormant legacy `tools.allowed` serializer. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/permissions.ts`, `apps/cli/src/lib/resources/permissions.ts`, `apps/cli/src/lib/staleness/detectors/permissions.ts`.
|
|
17
73
|
- **Fix: `agents run <agent>@<version> --host <host>` now forwards the version pin and most run flags to the remote host.** Previously the `--host` branch stripped `@version` and ignored `--strategy`, `--effort`, `--add-dir`, `--json`, `--verbose`, `--timeout`, `--yes`, and `--acp`, so the remote host applied its own defaults. The local CLI now parses `agent@version` verbatim, normalizes `--strategy`/`--balanced`, makes `--add-dir` paths remote-portable, and forwards all of these flags to the remote `agents run` invocation. `--add-dir` portability uses the same `~`/`$HOME` re-rooting that `--cwd` already uses, so a Linux remote resolves home paths against its own `/home/<user>`. Source: `apps/cli/src/lib/hosts/dispatch.ts`, `apps/cli/src/commands/exec.ts`, `apps/cli/src/lib/hosts/dispatch.test.ts`.
|
|
18
74
|
- **Fix: routine edits no longer rewrite the whole YAML file, so `~/.agents` stays clean and `agents repo pull` can sync routines across the fleet.** `writeJob` previously re-emitted the entire document via `yaml.stringify` on every mutation (pause/resume, `routines devices --set`, add), restyling untouched scalars — unquoting `schedule`, re-wrapping the folded `prompt` block — which left the git-backed user repo perpetually dirty. That made cross-device `agents repo pull` refuse ("uncommitted changes"), so a `devices:` pin set on one machine never reached the others and `Devices: all` routines kept firing on every box. A new `serializeJob` edits only the changed keys via the YAML Document API, preserving byte-for-byte formatting of untouched nodes; new/unparseable/non-mapping files fall back to canonical stringify. Source: `apps/cli/src/lib/routines.ts`, `apps/cli/src/lib/__tests__/routines.serialize.test.ts`.
|
|
75
|
+
- **`agents sessions --active --json` now carries live plan progress (RUSH-1380).** The state engine parses the latest `TodoWrite` off the transcript tail into `ActiveSession.todos` (`{ items: [{ content, status, activeForm? }], done, total, activeForm }`), and the live preview verb reads `Plan N/M: <current step>` instead of a bare "TodoWrite". This lets consumers (the Factory Floor) show an N/M pill + checklist for every session — including remote / device-dispatched agents that have no local tool-call stream. Source: `apps/cli/src/lib/session/state.ts` (`extractTodoProgress`, `inferActivity`), `apps/cli/src/lib/session/active.ts` (`ActiveSession.todos`, `applyState`), `apps/cli/src/lib/session/parse.ts` (`summarizeToolUse`).
|
|
19
76
|
|
|
20
77
|
## 1.20.59
|
|
21
78
|
|
package/README.md
CHANGED
|
@@ -347,6 +347,9 @@ agents hosts check gpu-box # reachable? which agents-cli version?
|
|
|
347
347
|
# Run there instead of locally
|
|
348
348
|
agents run claude --host gpu-box "profile this build" # headless: follows live by default
|
|
349
349
|
agents run claude --host gpu-box # no prompt → interactive TTY over SSH (tmux-backed)
|
|
350
|
+
agents run claude --host gpu-box --copy-creds "fix auth" # copy local runtime creds + Claude token, shred after
|
|
351
|
+
agents hosts ps # list dispatched runs + terminal status
|
|
352
|
+
agents hosts stop <id> # terminate a hung/detached run (alias: kill)
|
|
350
353
|
agents logs --host gpu-box # pick a dispatched run — concise summary by default
|
|
351
354
|
agents logs <id> --full # the full raw transcript / stdout (token-heavy)
|
|
352
355
|
agents logs <id> -f # re-attach to a running one and follow
|
|
@@ -682,6 +685,13 @@ agents routines add nightly-drain --schedule "0 3 * * *" --agent claude \
|
|
|
682
685
|
|
|
683
686
|
agents routines devices nightly-drain --set yosemite-s0,mac-mini # update allowlist
|
|
684
687
|
agents routines list --host yosemite-s0 # query another device
|
|
688
|
+
|
|
689
|
+
# Signed webhook trigger: Linear issue labeled "agent" fires a routine
|
|
690
|
+
agents routines add agent-labeled-issue --on linear:Issue --action update \
|
|
691
|
+
--team-key RUSH --label agent --agent claude \
|
|
692
|
+
--prompt "Work the Linear issue that was just labeled agent"
|
|
693
|
+
agents webhook serve --secrets-bundle webhooks --port 8787 # /hooks/linear, /hooks/github
|
|
694
|
+
agents funnel up yosemite-s0 --local-port 8787 --port 443 # public HTTPS ingress
|
|
685
695
|
```
|
|
686
696
|
|
|
687
697
|
Jobs run sandboxed -- agents only see directories and tools you explicitly allow.
|
package/dist/bin/agents
CHANGED
|
Binary file
|
package/dist/commands/browser.js
CHANGED
|
@@ -8,6 +8,7 @@ import { readAndResolveBundleEnv, bundleExists, readBundle, describeBundle } fro
|
|
|
8
8
|
import { findBrowserPath, getPortOccupant, isLauncherScript } from '../lib/browser/chrome.js';
|
|
9
9
|
import { listProfileCacheDirs, removeProfileCache, listAllProfileSnapshots, } from '../lib/browser/runtime-state.js';
|
|
10
10
|
import { DEFAULT_VIEWPORT } from '../lib/browser/devices.js';
|
|
11
|
+
import { runBrowserSessions } from '../lib/browser/sessions-list.js';
|
|
11
12
|
import { discoverBrowserWsUrl, verifyBrowserIdentity } from '../lib/browser/cdp.js';
|
|
12
13
|
import { parseTargetFilter } from '../lib/browser/service.js';
|
|
13
14
|
import { BrowserDaemonNotRunningError, formatBrowserDaemonNotRunningError, sendIPCRequest, } from '../lib/browser/ipc.js';
|
|
@@ -1850,10 +1851,10 @@ function registerTaskCommands(browser) {
|
|
|
1850
1851
|
// ─── Downloads ───────────────────────────────────────────────────────────────
|
|
1851
1852
|
browser
|
|
1852
1853
|
.command('download')
|
|
1853
|
-
.description(
|
|
1854
|
+
.description("Set the download directory for a task (defaults to the profile's downloads dir)")
|
|
1854
1855
|
.option(TASK_OPTION_FLAG, TASK_OPTION_DESC)
|
|
1855
1856
|
.option('-t, --tab <tabId>', 'Tab ID (defaults to current)')
|
|
1856
|
-
.
|
|
1857
|
+
.option('-p, --path <dir>', "Download directory path (default: the profile's downloads dir)")
|
|
1857
1858
|
.action(async (opts) => {
|
|
1858
1859
|
const task = resolveTaskName(opts);
|
|
1859
1860
|
const response = await sendIPCRequest({
|
|
@@ -1866,7 +1867,16 @@ function registerTaskCommands(browser) {
|
|
|
1866
1867
|
console.error(response.error);
|
|
1867
1868
|
process.exit(1);
|
|
1868
1869
|
}
|
|
1869
|
-
console.log(`Download path set to ${
|
|
1870
|
+
console.log(response.downloadPath ? `Download path set to ${response.downloadPath}` : 'Download path set');
|
|
1871
|
+
});
|
|
1872
|
+
browser
|
|
1873
|
+
.command('sessions')
|
|
1874
|
+
.description('List a profile\'s captured screenshots, PDFs, recordings, and downloads')
|
|
1875
|
+
.option('--profile <name>', 'Only this profile (default: all profiles with captures)')
|
|
1876
|
+
.option('--open [selector]', "Open a capture in the OS default app: 'latest' or a filename")
|
|
1877
|
+
.option('--json', 'Emit machine-readable JSON')
|
|
1878
|
+
.action((opts) => {
|
|
1879
|
+
runBrowserSessions({ profile: opts.profile, open: opts.open, json: opts.json });
|
|
1870
1880
|
});
|
|
1871
1881
|
// ─── Recording ─────────────────────────────────────────────────────────────
|
|
1872
1882
|
const record = browser.command('record').description('Record a video of the page');
|
package/dist/commands/exec.js
CHANGED
|
@@ -242,6 +242,7 @@ export function registerRunCommand(program) {
|
|
|
242
242
|
.option('--remote-cwd <dir>', 'Explicit host working directory for --host runs (overrides --cwd; usually --cwd suffices).')
|
|
243
243
|
.option('--no-follow', 'With --host, dispatch detached and return immediately (track via `agents hosts ps/logs`).')
|
|
244
244
|
.option('--any', 'With --host <cap> (a capability tag), pick any matching host instead of erroring when several match.')
|
|
245
|
+
.option('--copy-creds', 'With --host, copy the picked runtime credentials (and Claude OAuth token) to the host, then shred them after the run. Opt-in per run.')
|
|
245
246
|
.option('--lease [backend]', 'Invent a disposable cloud box for this run and tear it down after (via crabbox). Optional backend selects the cloud (hetzner/aws/do). Unlike --host, no machine is registered.')
|
|
246
247
|
.option('--keep-box', 'With --lease, keep the box after the run instead of stopping it.');
|
|
247
248
|
// `--on` and `--computer` are hidden aliases of `--host` — same behavior.
|
|
@@ -536,6 +537,44 @@ export function registerRunCommand(program) {
|
|
|
536
537
|
// which can't run over a detached remote dispatch — only forward a
|
|
537
538
|
// concrete id.
|
|
538
539
|
const resumeId = typeof options.resume === 'string' ? options.resume : undefined;
|
|
540
|
+
// --copy-creds: provision runtime credentials (and the Claude OAuth token)
|
|
541
|
+
// on the remote host before the run, then shred them after. Unlike
|
|
542
|
+
// --lease, a host is persistent, so this is strictly opt-in per run.
|
|
543
|
+
let hostCopyCreds;
|
|
544
|
+
if (options.copyCreds) {
|
|
545
|
+
const { detectSignedInRuntimes, pickRuntimes, resolveClaudeCredentialsBlob } = await import('../lib/crabbox/runtimes.js');
|
|
546
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
547
|
+
const detected = await detectSignedInRuntimes();
|
|
548
|
+
const runtimes = await pickRuntimes(detected);
|
|
549
|
+
if (runtimes.length === 0) {
|
|
550
|
+
console.error(chalk.yellow('No runtimes selected. Sign into one locally then retry, or omit --copy-creds.'));
|
|
551
|
+
process.exit(1);
|
|
552
|
+
}
|
|
553
|
+
const names = runtimes
|
|
554
|
+
.map((id) => {
|
|
555
|
+
const d = detected.find((x) => x.id === id);
|
|
556
|
+
return `${d?.label ?? id}${d?.email ? ` (${d.email})` : ''}`;
|
|
557
|
+
})
|
|
558
|
+
.join(', ');
|
|
559
|
+
const whatShips = runtimes.includes('claude') ? 'credentials + Claude OAuth token' : 'credentials';
|
|
560
|
+
const ok = await confirm({
|
|
561
|
+
message: `Copy ${whatShips} for ${names} to host "${host.name}", run there, then shred them after?`,
|
|
562
|
+
default: false,
|
|
563
|
+
});
|
|
564
|
+
if (!ok) {
|
|
565
|
+
console.error(chalk.yellow('Aborted — no credentials pushed, no run dispatched.'));
|
|
566
|
+
process.exit(1);
|
|
567
|
+
}
|
|
568
|
+
let claudeCredentialsJson = null;
|
|
569
|
+
if (runtimes.includes('claude')) {
|
|
570
|
+
const claudeEmail = detected.find((d) => d.id === 'claude')?.email ?? null;
|
|
571
|
+
claudeCredentialsJson = await resolveClaudeCredentialsBlob({ preferEmail: claudeEmail });
|
|
572
|
+
if (!claudeCredentialsJson) {
|
|
573
|
+
console.error(chalk.yellow('Warning: could not read the local Claude OAuth token — the host may boot Claude "Not logged in".'));
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
hostCopyCreds = { runtimes, detected, claudeCredentialsJson };
|
|
577
|
+
}
|
|
539
578
|
// Decide whether this host run is interactive. No prompt always means
|
|
540
579
|
// interactive (matching local resolveInteractive); --interactive forces
|
|
541
580
|
// interactive even when a prompt is provided; --headless forces headless
|
|
@@ -587,6 +626,7 @@ export function registerRunCommand(program) {
|
|
|
587
626
|
passthroughArgs,
|
|
588
627
|
raw: options.raw || options.tmux === false || options.disableTmux === true,
|
|
589
628
|
forceInteractive: options.interactive,
|
|
629
|
+
copyCreds: hostCopyCreds,
|
|
590
630
|
});
|
|
591
631
|
process.exit(exitCode);
|
|
592
632
|
}
|
|
@@ -616,6 +656,7 @@ export function registerRunCommand(program) {
|
|
|
616
656
|
name: options.name,
|
|
617
657
|
resume: resumeId,
|
|
618
658
|
follow: options.follow !== false,
|
|
659
|
+
copyCreds: hostCopyCreds,
|
|
619
660
|
});
|
|
620
661
|
// Register the dispatched run in the LOCAL session index so it shows
|
|
621
662
|
// up in `agents sessions` and resolves by id/name, even though its
|
|
@@ -1081,8 +1122,17 @@ export function registerRunCommand(program) {
|
|
|
1081
1122
|
if (nativeResume(agent)) {
|
|
1082
1123
|
resumeNative = true;
|
|
1083
1124
|
resumeSessionId = session.id;
|
|
1125
|
+
// Native `--resume` (claude/codex) resolves the transcript relative to the
|
|
1126
|
+
// working directory (projects/<cwd-hash>/). The session may have been started
|
|
1127
|
+
// in a different directory than we're standing in now — most importantly when a
|
|
1128
|
+
// routine daemon fires `agents run --resume` from its own cwd. Spawn from the
|
|
1129
|
+
// session's ORIGIN cwd so the resume actually finds it; otherwise the agent
|
|
1130
|
+
// exits "No conversation found with session ID". Honor an explicit --cwd only if
|
|
1131
|
+
// the caller passed one (they're overriding on purpose).
|
|
1132
|
+
if (!options.cwd && session.cwd)
|
|
1133
|
+
options.cwd = session.cwd;
|
|
1084
1134
|
if (!options.quiet)
|
|
1085
|
-
process.stderr.write(chalk.gray(`Resuming ${agent} ${session.shortId} (native)${version ? ` @${version}` : ''}\n`));
|
|
1135
|
+
process.stderr.write(chalk.gray(`Resuming ${agent} ${session.shortId} (native)${version ? ` @${version}` : ''}${!options.cwd || options.cwd === session.cwd ? ` in ${session.cwd ?? cwd}` : ''}\n`));
|
|
1086
1136
|
}
|
|
1087
1137
|
else {
|
|
1088
1138
|
// Tier-2: launch fresh with a /continue <id> first message; the agent
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { buildFunnelStatusCommand, buildFunnelUpCommand, parseFunnelPort } from '../lib/funnel.js';
|
|
3
|
+
import { resolveHost } from '../lib/hosts/registry.js';
|
|
4
|
+
import { resolveRemoteOsSync } from '../lib/hosts/remote-os.js';
|
|
5
|
+
import { sshTargetFor } from '../lib/hosts/types.js';
|
|
6
|
+
import { sshExec } from '../lib/ssh-exec.js';
|
|
7
|
+
async function resolveIngressHost(name) {
|
|
8
|
+
const host = await resolveHost(name);
|
|
9
|
+
if (!host)
|
|
10
|
+
throw new Error(`Unknown host "${name}". Enroll it with agents hosts add, or sync devices first.`);
|
|
11
|
+
const os = host.os ?? resolveRemoteOsSync(host.name);
|
|
12
|
+
if (os === 'windows') {
|
|
13
|
+
throw new Error('Tailscale Funnel requires a host with the Tailscale CLI; Windows cannot host this ingress path.');
|
|
14
|
+
}
|
|
15
|
+
return host;
|
|
16
|
+
}
|
|
17
|
+
async function runOnHost(hostName, command) {
|
|
18
|
+
const host = await resolveIngressHost(hostName);
|
|
19
|
+
const target = sshTargetFor(host);
|
|
20
|
+
const result = sshExec(target, command, { timeoutMs: 30_000, multiplex: true });
|
|
21
|
+
if (result.code !== 0) {
|
|
22
|
+
throw new Error((result.stderr || result.stdout).trim() || `ssh exited ${result.code ?? 'without a code'}`);
|
|
23
|
+
}
|
|
24
|
+
const out = result.stdout.trim();
|
|
25
|
+
if (out)
|
|
26
|
+
console.log(out);
|
|
27
|
+
}
|
|
28
|
+
export function registerFunnelCommand(program) {
|
|
29
|
+
const funnel = program
|
|
30
|
+
.command('funnel')
|
|
31
|
+
.description('Manage Tailscale Funnel exposure for a fleet webhook receiver.');
|
|
32
|
+
funnel
|
|
33
|
+
.command('status <host>')
|
|
34
|
+
.description('Show Tailscale Funnel status on a fleet host.')
|
|
35
|
+
.action(async (host) => {
|
|
36
|
+
try {
|
|
37
|
+
await runOnHost(host, buildFunnelStatusCommand());
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
console.error(chalk.red(err.message));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
funnel
|
|
45
|
+
.command('up <host>')
|
|
46
|
+
.description('Expose a localhost webhook receiver through Tailscale Funnel.')
|
|
47
|
+
.requiredOption('--local-port <n>', 'Local receiver port on the remote host')
|
|
48
|
+
.option('--port <n>', 'Public Funnel port: 443, 8443, or 10000', '443')
|
|
49
|
+
.action(async (host, opts) => {
|
|
50
|
+
try {
|
|
51
|
+
const publicPort = parseFunnelPort(opts.port ?? '443');
|
|
52
|
+
const localPort = Number.parseInt(opts.localPort, 10);
|
|
53
|
+
const command = buildFunnelUpCommand(publicPort, localPort);
|
|
54
|
+
await runOnHost(host, command);
|
|
55
|
+
console.log(chalk.green(`Funnel enabled on ${host}: public :${publicPort} → localhost:${localPort}`));
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
console.error(chalk.red(err.message));
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
package/dist/commands/hosts.js
CHANGED
|
@@ -18,6 +18,7 @@ import { resolveRemoteOsSync } from '../lib/hosts/remote-os.js';
|
|
|
18
18
|
import { listTasks, loadTask, findTaskByName, findTaskBySessionId } from '../lib/hosts/tasks.js';
|
|
19
19
|
import { reconcileRunningTasks } from '../lib/hosts/reconcile.js';
|
|
20
20
|
import { showHostTaskLog } from '../lib/hosts/logs.js';
|
|
21
|
+
import { stopDispatchedTask } from '../lib/hosts/dispatch.js';
|
|
21
22
|
/** Parse `user@host` or `host` into its pieces. */
|
|
22
23
|
function parseTarget(target) {
|
|
23
24
|
const at = target.indexOf('@');
|
|
@@ -201,6 +202,42 @@ async function doLogs(ref, follow, full) {
|
|
|
201
202
|
if (res.exitCode !== undefined)
|
|
202
203
|
process.exitCode = res.exitCode;
|
|
203
204
|
}
|
|
205
|
+
/** Resolve a host-task ref (id, --name handle, or session id) or null. */
|
|
206
|
+
function resolveTaskRef(ref) {
|
|
207
|
+
return loadTask(ref) ?? findTaskByName(ref) ?? findTaskBySessionId(ref);
|
|
208
|
+
}
|
|
209
|
+
async function doStop(ref) {
|
|
210
|
+
// Heal first so we don't try to kill a process that already exited.
|
|
211
|
+
const current = resolveTaskRef(ref);
|
|
212
|
+
if (!current) {
|
|
213
|
+
console.log(chalk.red(`Unknown task "${ref}".`));
|
|
214
|
+
process.exitCode = 1;
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const task = reconcileRunningTasks([current])[0] ?? current;
|
|
218
|
+
if (task.status !== 'running') {
|
|
219
|
+
console.log(chalk.gray(`Task ${task.id} is already ${task.status}` + (task.exitCode !== undefined ? ` (exit ${task.exitCode})` : '') + '.'));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
const stopped = stopDispatchedTask(task);
|
|
224
|
+
const statusColor = stopped.status === 'completed' ? chalk.green : chalk.yellow;
|
|
225
|
+
const exitNote = stopped.exitCode === 143
|
|
226
|
+
? 'exit 143 / SIGTERM'
|
|
227
|
+
: stopped.exitCode !== undefined
|
|
228
|
+
? `exit ${stopped.exitCode}`
|
|
229
|
+
: stopped.status;
|
|
230
|
+
console.log(chalk.green(`Stopped ${stopped.id}`) +
|
|
231
|
+
chalk.gray(` on ${stopped.host}`) +
|
|
232
|
+
' ' + statusColor(stopped.status) +
|
|
233
|
+
chalk.gray(` (${exitNote})`));
|
|
234
|
+
console.log(chalk.gray(`Logs: agents hosts logs ${stopped.id}`));
|
|
235
|
+
}
|
|
236
|
+
catch (err) {
|
|
237
|
+
console.error(chalk.red(err?.message ?? err));
|
|
238
|
+
process.exitCode = 1;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
204
241
|
/** Register the `agents hosts` command tree. */
|
|
205
242
|
export function registerHostsCommand(program) {
|
|
206
243
|
const hosts = program
|
|
@@ -239,4 +276,9 @@ export function registerHostsCommand(program) {
|
|
|
239
276
|
.option('-f, --follow', 'Follow live output')
|
|
240
277
|
.option('-m, --full', 'Show the full raw combined-stdout log instead of the concise summary')
|
|
241
278
|
.action((id, opts) => doLogs(id, !!opts.follow, !!opts.full));
|
|
279
|
+
hosts
|
|
280
|
+
.command('stop <id>')
|
|
281
|
+
.alias('kill')
|
|
282
|
+
.description('Terminate a running host task from this machine (SIGTERM process group; marks failed/143).')
|
|
283
|
+
.action((id) => doStop(id));
|
|
242
284
|
}
|
package/dist/commands/repo.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Extra DotAgent repo management.
|
|
3
3
|
*
|
|
4
|
-
* Registers `agents
|
|
5
|
-
* additional DotAgent repos alongside the primary ~/.agents/.system/
|
|
6
|
-
* private, work, or team skills can ship separately from public ones.
|
|
4
|
+
* Registers `agents repos add|init|list|remove|enable|disable` (`repo` alias)
|
|
5
|
+
* which manage additional DotAgent repos alongside the primary ~/.agents/.system/
|
|
6
|
+
* repo so private, work, or team skills can ship separately from public ones.
|
|
7
7
|
*
|
|
8
8
|
* Extras are user-level config: managed clones live at ~/.agents-<alias>/ as
|
|
9
9
|
* peer dirs to ~/.agents/, and user-owned repos may live anywhere. All extras
|
|
@@ -73,6 +73,6 @@ export declare function formatResourceDelta(entries: {
|
|
|
73
73
|
export declare function repoSlug(url: string): string;
|
|
74
74
|
/** Pack colored phrases into `, `-joined lines no wider than `width`. */
|
|
75
75
|
export declare function wrapPhrases(parts: string[], width: number): string[];
|
|
76
|
-
/** Register the `agents
|
|
76
|
+
/** Register the `agents repos` command tree (`repo` is a convenience alias). */
|
|
77
77
|
export declare function registerRepoCommands(program: Command): void;
|
|
78
78
|
export {};
|
package/dist/commands/repo.js
CHANGED
|
@@ -9,6 +9,7 @@ import { confirm, input } from '@inquirer/prompts';
|
|
|
9
9
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
10
10
|
import { setHelpSections } from '../lib/help.js';
|
|
11
11
|
import { itemPicker } from '../lib/picker.js';
|
|
12
|
+
import { addHostOption } from '../lib/hosts/option.js';
|
|
12
13
|
import { inspectRepo, resolveRepoTarget, } from './inspect.js';
|
|
13
14
|
const HOME = os.homedir();
|
|
14
15
|
/**
|
|
@@ -27,7 +28,7 @@ function resolveRepoPath(target) {
|
|
|
27
28
|
return path.join(HOME, `.agents-${trimmed}`);
|
|
28
29
|
}
|
|
29
30
|
import { applyExtraAliasToVersions, ensureAgentsDir, getExtraRepoDir, getSystemAgentsDir, getUserAgentsDir, readMeta, resolveExtraRepoDir, updateMeta, } from '../lib/state.js';
|
|
30
|
-
import { parseSource, pullRepo, commitAndPush, isGitRepo, isSystemRepoOrigin, adoptRepo } from '../lib/git.js';
|
|
31
|
+
import { parseSource, pullRepo, commitAndPush, isGitRepo, isSystemRepoOrigin, adoptRepo, displayHomePath, } from '../lib/git.js';
|
|
31
32
|
import { DEFAULT_SYSTEM_REPO } from '../lib/types.js';
|
|
32
33
|
import { ALL_AGENT_IDS, isAgentName, resolveAgentName } from '../lib/agents.js';
|
|
33
34
|
import { refresh } from '../lib/refresh.js';
|
|
@@ -465,34 +466,44 @@ async function listRepos(alias, opts = {}) {
|
|
|
465
466
|
}
|
|
466
467
|
console.log('');
|
|
467
468
|
}
|
|
468
|
-
/**
|
|
469
|
+
/**
|
|
470
|
+
* Label for push/pull spinners and results: alias + resolved dir + tracking ref.
|
|
471
|
+
* e.g. `user (~/.agents → origin/main)`.
|
|
472
|
+
*/
|
|
473
|
+
function formatRepoTarget(alias, dir, branch) {
|
|
474
|
+
const ref = branch ? `origin/${branch}` : 'origin';
|
|
475
|
+
return `${alias} (${displayHomePath(dir)} → ${ref})`;
|
|
476
|
+
}
|
|
477
|
+
/** Register the `agents repos` command tree (`repo` is a convenience alias). */
|
|
469
478
|
export function registerRepoCommands(program) {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
.
|
|
479
|
+
// addHostOption on the group so --help documents --host/--device; remote
|
|
480
|
+
// routing is handled pre-parse by maybeRunOnHost (passthrough.ts).
|
|
481
|
+
const repoCmd = addHostOption(program
|
|
482
|
+
.command('repos')
|
|
483
|
+
.alias('repo')
|
|
484
|
+
.description('Manage extra DotAgent repos alongside ~/.agents/ (for private or team skills).'));
|
|
474
485
|
setHelpSections(repoCmd, {
|
|
475
486
|
examples: `
|
|
476
487
|
# Scaffold an editable repo (default: ~/.agents/)
|
|
477
|
-
agents
|
|
488
|
+
agents repos init
|
|
478
489
|
|
|
479
490
|
# Scaffold a named repo at ~/.agents-work/
|
|
480
|
-
agents
|
|
491
|
+
agents repos init work
|
|
481
492
|
|
|
482
493
|
# Register an existing repo (clones to ~/.agents-<alias>/)
|
|
483
|
-
agents
|
|
494
|
+
agents repos add gh:yourname/.agents-work
|
|
484
495
|
|
|
485
496
|
# Register with a custom alias
|
|
486
|
-
agents
|
|
497
|
+
agents repos add git@github.com:acme/team-skills.git --as acme
|
|
487
498
|
|
|
488
499
|
# See what's registered
|
|
489
|
-
agents
|
|
500
|
+
agents repos list
|
|
490
501
|
|
|
491
502
|
# View one repo's contents (git state + resource counts); omit the name for a picker
|
|
492
503
|
agents repos view system
|
|
493
504
|
|
|
494
505
|
# Temporarily disable without deleting
|
|
495
|
-
agents
|
|
506
|
+
agents repos disable acme
|
|
496
507
|
`,
|
|
497
508
|
notes: `
|
|
498
509
|
Managed extras live at ~/.agents-<alias>/ as peer dirs to ~/.agents/. User-owned
|
|
@@ -850,20 +861,20 @@ export function registerRepoCommands(program) {
|
|
|
850
861
|
}
|
|
851
862
|
continue;
|
|
852
863
|
}
|
|
853
|
-
const spinner = ora(`Pulling ${t.alias}...`).start();
|
|
864
|
+
const spinner = ora(`Pulling ${formatRepoTarget(t.alias, t.dir)}...`).start();
|
|
854
865
|
const result = await pullRepo(t.dir);
|
|
855
866
|
if (result.success) {
|
|
856
|
-
spinner.succeed(`${t.alias}
|
|
867
|
+
spinner.succeed(`${formatRepoTarget(t.alias, t.dir, result.branch)}: ${result.commit}`);
|
|
857
868
|
}
|
|
858
869
|
else {
|
|
859
|
-
spinner.fail(`${t.alias}: ${result.error}`);
|
|
870
|
+
spinner.fail(`${formatRepoTarget(t.alias, t.dir)}: ${result.error}`);
|
|
860
871
|
}
|
|
861
872
|
}
|
|
862
873
|
});
|
|
863
874
|
repoCmd
|
|
864
875
|
.command('push [alias]')
|
|
865
876
|
.description('Commit and push the user repo or a user-owned extra. Refuses to push the system repo.')
|
|
866
|
-
.option('-m, --message <msg>', 'Commit message', 'Update via agents
|
|
877
|
+
.option('-m, --message <msg>', 'Commit message', 'Update via agents repos push')
|
|
867
878
|
.action(async (alias, options) => {
|
|
868
879
|
const targets = collectRepoTargets(alias);
|
|
869
880
|
if (!targets) {
|
|
@@ -893,13 +904,13 @@ export function registerRepoCommands(program) {
|
|
|
893
904
|
return;
|
|
894
905
|
}
|
|
895
906
|
for (const t of pushable) {
|
|
896
|
-
const spinner = ora(`Pushing ${t.alias}...`).start();
|
|
907
|
+
const spinner = ora(`Pushing ${formatRepoTarget(t.alias, t.dir)}...`).start();
|
|
897
908
|
const result = await commitAndPush(t.dir, options.message);
|
|
898
909
|
if (result.success) {
|
|
899
|
-
spinner.succeed(`${t.alias} pushed`);
|
|
910
|
+
spinner.succeed(`${formatRepoTarget(t.alias, t.dir, result.branch)}: ${result.detail ?? 'pushed'}`);
|
|
900
911
|
}
|
|
901
912
|
else {
|
|
902
|
-
spinner.fail(`${t.alias}: ${result.error}`);
|
|
913
|
+
spinner.fail(`${formatRepoTarget(t.alias, t.dir)}: ${result.error}`);
|
|
903
914
|
}
|
|
904
915
|
}
|
|
905
916
|
});
|