@phnx-labs/agents-cli 1.20.72 → 1.20.74
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 +395 -0
- package/README.md +40 -4
- package/dist/bin/agents +0 -0
- package/dist/commands/activity.d.ts +12 -0
- package/dist/commands/activity.js +68 -0
- package/dist/commands/apply.d.ts +13 -0
- package/dist/commands/apply.js +28 -3
- package/dist/commands/browser.js +1 -1
- package/dist/commands/cloud.js +21 -11
- package/dist/commands/doctor.js +21 -5
- package/dist/commands/events.d.ts +11 -10
- package/dist/commands/events.js +30 -21
- package/dist/commands/exec.d.ts +34 -0
- package/dist/commands/exec.js +337 -39
- package/dist/commands/export.d.ts +11 -0
- package/dist/commands/export.js +215 -0
- package/dist/commands/feed.d.ts +13 -0
- package/dist/commands/feed.js +130 -29
- package/dist/commands/hq.d.ts +2 -0
- package/dist/commands/hq.js +58 -0
- package/dist/commands/import.js +51 -3
- package/dist/commands/lease.d.ts +19 -0
- package/dist/commands/lease.js +138 -11
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +123 -0
- package/dist/commands/logs.js +1 -1
- package/dist/commands/mine.d.ts +27 -0
- package/dist/commands/mine.js +207 -0
- package/dist/commands/monitors.js +79 -49
- package/dist/commands/plugins.js +1 -0
- package/dist/commands/profiles.js +158 -0
- package/dist/commands/repo.js +2 -60
- package/dist/commands/resources.d.ts +5 -0
- package/dist/commands/resources.js +95 -0
- package/dist/commands/routines.d.ts +2 -0
- package/dist/commands/routines.js +139 -37
- package/dist/commands/secrets.js +234 -47
- package/dist/commands/send.d.ts +15 -0
- package/dist/commands/send.js +62 -0
- package/dist/commands/sessions-tail.d.ts +1 -0
- package/dist/commands/sessions-tail.js +20 -9
- package/dist/commands/sessions.js +18 -3
- package/dist/commands/setup-mine.d.ts +18 -0
- package/dist/commands/setup-mine.js +106 -0
- package/dist/commands/setup-share.js +2 -2
- package/dist/commands/setup.js +9 -2
- package/dist/commands/share.d.ts +8 -1
- package/dist/commands/share.js +122 -37
- package/dist/commands/ssh.d.ts +9 -0
- package/dist/commands/ssh.js +196 -26
- package/dist/commands/sync.js +1 -1
- package/dist/commands/teams.js +11 -2
- package/dist/commands/versions.js +36 -5
- package/dist/commands/view.js +75 -27
- package/dist/index.js +80 -10
- package/dist/lib/activity.d.ts +124 -0
- package/dist/lib/activity.js +542 -0
- package/dist/lib/agents.d.ts +35 -0
- package/dist/lib/agents.js +79 -6
- package/dist/lib/artifact-actions.d.ts +1 -1
- package/dist/lib/artifact-actions.js +1 -1
- package/dist/lib/ask-classifier.d.ts +2 -1
- package/dist/lib/ask-classifier.js +3 -3
- package/dist/lib/auth-health.d.ts +32 -4
- package/dist/lib/auth-health.js +40 -1
- package/dist/lib/auto-pull.js +8 -1
- package/dist/lib/brand.d.ts +40 -0
- package/dist/lib/brand.js +122 -0
- package/dist/lib/browser/drivers/ssh.js +4 -7
- package/dist/lib/browser/service.js +6 -8
- package/dist/lib/channels/providers/index.d.ts +2 -0
- package/dist/lib/channels/providers/index.js +20 -0
- package/dist/lib/channels/providers/mailbox.d.ts +2 -0
- package/dist/lib/channels/providers/mailbox.js +26 -0
- package/dist/lib/channels/providers/openclaw-telegram.d.ts +2 -0
- package/dist/lib/channels/providers/openclaw-telegram.js +34 -0
- package/dist/lib/channels/providers/rush.d.ts +6 -0
- package/dist/lib/channels/providers/rush.js +55 -0
- package/dist/lib/channels/registry.d.ts +42 -0
- package/dist/lib/channels/registry.js +20 -0
- package/dist/lib/channels/resolve.d.ts +11 -0
- package/dist/lib/channels/resolve.js +12 -0
- package/dist/lib/cli-resources.d.ts +18 -0
- package/dist/lib/cli-resources.js +53 -4
- package/dist/lib/cloud/codex.js +6 -3
- package/dist/lib/cloud/factory.d.ts +1 -0
- package/dist/lib/cloud/factory.js +10 -4
- package/dist/lib/cloud/rush.d.ts +3 -1
- package/dist/lib/cloud/rush.js +6 -1
- package/dist/lib/cloud/types.d.ts +2 -0
- package/dist/lib/codex-home.d.ts +35 -0
- package/dist/lib/codex-home.js +136 -0
- package/dist/lib/config-transfer.d.ts +16 -0
- package/dist/lib/config-transfer.js +56 -0
- package/dist/lib/crabbox/cli.d.ts +44 -0
- package/dist/lib/crabbox/cli.js +120 -20
- package/dist/lib/crabbox/lease.d.ts +52 -1
- package/dist/lib/crabbox/lease.js +117 -16
- package/dist/lib/crabbox/progress.d.ts +31 -0
- package/dist/lib/crabbox/progress.js +76 -0
- package/dist/lib/crabbox/runtimes.d.ts +4 -0
- package/dist/lib/crabbox/runtimes.js +33 -8
- package/dist/lib/crabbox/setup-copy.d.ts +87 -0
- package/dist/lib/crabbox/setup-copy.js +127 -0
- package/dist/lib/daemon.d.ts +5 -5
- package/dist/lib/daemon.js +9 -9
- package/dist/lib/drive-sync.js +6 -3
- package/dist/lib/event-stream.d.ts +36 -0
- package/dist/lib/event-stream.js +68 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +11 -0
- package/dist/lib/exec.js +23 -2
- package/dist/lib/export.d.ts +72 -0
- package/dist/lib/export.js +269 -0
- package/dist/lib/feed-policy.d.ts +1 -1
- package/dist/lib/feed-policy.js +14 -9
- package/dist/lib/feed-ranking.d.ts +32 -0
- package/dist/lib/feed-ranking.js +224 -0
- package/dist/lib/feed.d.ts +35 -2
- package/dist/lib/feed.js +66 -3
- package/dist/lib/fleet/auth-sync.d.ts +53 -0
- package/dist/lib/fleet/auth-sync.js +92 -0
- package/dist/lib/fleet/remote-login.d.ts +187 -0
- package/dist/lib/fleet/remote-login.js +556 -0
- package/dist/lib/format.d.ts +30 -3
- package/dist/lib/format.js +34 -5
- package/dist/lib/fs-atomic.d.ts +7 -2
- package/dist/lib/fs-atomic.js +8 -12
- package/dist/lib/git.d.ts +16 -0
- package/dist/lib/git.js +79 -2
- package/dist/lib/heal.js +11 -3
- package/dist/lib/hosts/progress.d.ts +28 -10
- package/dist/lib/hosts/progress.js +79 -22
- package/dist/lib/hosts/remote-cmd.js +4 -0
- package/dist/lib/hq/floor.d.ts +87 -0
- package/dist/lib/hq/floor.js +226 -0
- package/dist/lib/import.d.ts +23 -0
- package/dist/lib/import.js +74 -2
- package/dist/lib/isolation-boundary-report.d.ts +10 -0
- package/dist/lib/isolation-boundary-report.js +35 -0
- package/dist/lib/menubar/install-menubar.js +14 -20
- package/dist/lib/notify.d.ts +1 -0
- package/dist/lib/notify.js +3 -3
- package/dist/lib/open-url.d.ts +2 -0
- package/dist/lib/open-url.js +19 -0
- package/dist/lib/openclaw-keychain.d.ts +56 -0
- package/dist/lib/openclaw-keychain.js +236 -0
- package/dist/lib/overdue.js +10 -0
- package/dist/lib/permissions.d.ts +44 -1
- package/dist/lib/permissions.js +284 -7
- package/dist/lib/project-launch.d.ts +6 -12
- package/dist/lib/project-launch.js +13 -228
- package/dist/lib/project-resources.d.ts +7 -0
- package/dist/lib/project-resources.js +306 -0
- package/dist/lib/pty-client.d.ts +27 -0
- package/dist/lib/pty-client.js +136 -10
- package/dist/lib/refresh.js +14 -10
- package/dist/lib/resource-profiles.d.ts +26 -0
- package/dist/lib/resource-profiles.js +157 -0
- package/dist/lib/resources/permissions.js +7 -1
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/resources.d.ts +1 -1
- package/dist/lib/resources.js +32 -3
- package/dist/lib/routines.d.ts +11 -0
- package/dist/lib/routines.js +56 -15
- package/dist/lib/runner.d.ts +1 -0
- package/dist/lib/runner.js +66 -5
- package/dist/lib/secrets/bundles.d.ts +16 -3
- package/dist/lib/secrets/bundles.js +206 -37
- package/dist/lib/secrets/icloud-import.d.ts +2 -2
- package/dist/lib/secrets/icloud-import.js +9 -6
- package/dist/lib/secrets/mcp.js +1 -1
- package/dist/lib/secrets/vault-age-helper.d.ts +1 -0
- package/dist/lib/secrets/vault-age-helper.js +34 -0
- package/dist/lib/secrets/vault.d.ts +49 -0
- package/dist/lib/secrets/vault.js +397 -0
- package/dist/lib/self-heal/checks/path.js +3 -1
- package/dist/lib/self-heal/checks/shadowing.js +10 -2
- package/dist/lib/self-heal/checks/shims.js +19 -11
- package/dist/lib/session/cloud.d.ts +2 -2
- package/dist/lib/session/cloud.js +2 -2
- package/dist/lib/session/db.d.ts +4 -0
- package/dist/lib/session/db.js +44 -7
- package/dist/lib/session/discover.d.ts +2 -0
- package/dist/lib/session/discover.js +114 -3
- package/dist/lib/session/stream-render.d.ts +3 -0
- package/dist/lib/session/stream-render.js +130 -0
- package/dist/lib/session/types.d.ts +6 -0
- package/dist/lib/share/analytics.d.ts +13 -0
- package/dist/lib/share/analytics.js +45 -0
- package/dist/lib/share/config.d.ts +19 -5
- package/dist/lib/share/config.js +44 -8
- package/dist/lib/share/provision.d.ts +58 -6
- package/dist/lib/share/provision.js +97 -22
- package/dist/lib/share/publish.d.ts +36 -13
- package/dist/lib/share/publish.js +55 -8
- package/dist/lib/share/worker-template.js +83 -17
- package/dist/lib/shims.d.ts +35 -0
- package/dist/lib/shims.js +159 -13
- package/dist/lib/ssh-exec.d.ts +14 -0
- package/dist/lib/ssh-exec.js +57 -0
- package/dist/lib/staleness/detectors/hooks.js +25 -1
- package/dist/lib/staleness/detectors/permissions.js +66 -0
- package/dist/lib/staleness/detectors/workflows.js +20 -0
- package/dist/lib/staleness/writers/hooks.js +58 -4
- package/dist/lib/staleness/writers/permissions.js +2 -2
- package/dist/lib/staleness/writers/skills.js +1 -1
- package/dist/lib/staleness/writers/sources.d.ts +10 -1
- package/dist/lib/staleness/writers/sources.js +68 -1
- package/dist/lib/star-nudge.d.ts +45 -0
- package/dist/lib/star-nudge.js +91 -0
- package/dist/lib/startup/command-registry.d.ts +7 -1
- package/dist/lib/startup/command-registry.js +19 -3
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.js +17 -4
- package/dist/lib/subagents-registry.d.ts +2 -0
- package/dist/lib/subagents-registry.js +3 -0
- package/dist/lib/teams/parsers.js +214 -6
- package/dist/lib/teams/supervisor.d.ts +7 -0
- package/dist/lib/teams/supervisor.js +2 -1
- package/dist/lib/template.d.ts +1 -1
- package/dist/lib/template.js +1 -1
- package/dist/lib/triggers/webhook.js +36 -3
- package/dist/lib/types.d.ts +84 -0
- package/dist/lib/uninstall.js +1 -45
- package/dist/lib/version.d.ts +40 -0
- package/dist/lib/version.js +94 -16
- package/dist/lib/versions.d.ts +24 -0
- package/dist/lib/versions.js +269 -78
- package/dist/lib/workflows.d.ts +2 -0
- package/dist/lib/workflows.js +88 -0
- package/package.json +2 -1
- package/dist/commands/daemon.d.ts +0 -10
- package/dist/commands/daemon.js +0 -121
package/dist/index.js
CHANGED
|
@@ -26,6 +26,11 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
26
26
|
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
|
27
27
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
28
28
|
const VERSION = packageJson.version;
|
|
29
|
+
if (process.argv[2] === '__vault-age-helper') {
|
|
30
|
+
const { runVaultAgeHelperCli } = await import('./lib/secrets/vault-age-helper.js');
|
|
31
|
+
await runVaultAgeHelperCli();
|
|
32
|
+
process.exit(process.exitCode ?? 0);
|
|
33
|
+
}
|
|
29
34
|
import { NPM_PACKAGE_NAME, deriveGlobalPrefix, detectPackageManager, installPackageIntoPrefix, installPackageWithBun, verifyInstalledVersion, refreshAliasShims, downloadVerifiedTarball, } from './lib/self-update.js';
|
|
30
35
|
// Detect dev/working-tree builds and default the noisy startup steps off.
|
|
31
36
|
// Three cases trip this:
|
|
@@ -50,7 +55,7 @@ if (IS_DEV_BUILD) {
|
|
|
50
55
|
// module on each invocation (which loaded the whole ~50-module tree before the
|
|
51
56
|
// first byte of output), the registry maps a command name to a thunk that
|
|
52
57
|
// imports only what that command needs. See src/lib/startup/command-registry.ts.
|
|
53
|
-
import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadMemory, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport,
|
|
58
|
+
import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadResources, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadMemory, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadExport, loadPackages, loadRoutines, loadMonitors, loadRun, loadFork, loadDefaults, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadApply, loadCheck, loadStatus, loadProfiles, loadSecrets, loadLogin, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadLock, loadRefreshRules, loadDrive, loadFactory, loadUsage, loadCost, loadOutput, loadBudget, loadAlias, loadMine, loadPty, loadTmux, loadWatchdog, loadBrowser, loadComputer, loadHosts, loadLogs, loadEvents, loadAudit, loadWebhook, loadFunnel, loadSsh, loadPull, loadPush, loadRepo, loadSetup, loadUninstall, loadShare, loadSend, loadHq, loadFeed, loadActivity, loadMailboxes, } from './lib/startup/command-registry.js';
|
|
54
59
|
import { applyGlobalHelpConventions } from './lib/help.js';
|
|
55
60
|
import { renderWhatsNew } from './lib/whats-new.js';
|
|
56
61
|
import { emit, redactArgs } from './lib/events.js';
|
|
@@ -68,9 +73,18 @@ if (process.argv[2] === '__shim') {
|
|
|
68
73
|
const code = await execShimPassthrough(agent, rawArgs, process.cwd(), pinned || undefined);
|
|
69
74
|
process.exit(code);
|
|
70
75
|
}
|
|
76
|
+
if (process.argv[2] === '__daemon-run') {
|
|
77
|
+
const { runDaemon } = await import('./lib/daemon.js');
|
|
78
|
+
await runDaemon();
|
|
79
|
+
process.exit(process.exitCode ?? 0);
|
|
80
|
+
}
|
|
81
|
+
// White-label: the shim for a brand (e.g. `jack`) exports AGENTS_BRAND, so the
|
|
82
|
+
// CLI presents its own name/help/errors as the brand. Unbranded (AGENTS_BRAND
|
|
83
|
+
// unset) resolves to 'agents' and everything below is byte-identical to before.
|
|
84
|
+
const BRAND = resolveBrandName();
|
|
71
85
|
const program = new Command();
|
|
72
86
|
program
|
|
73
|
-
.name(
|
|
87
|
+
.name(BRAND)
|
|
74
88
|
.description('Environment manager for AI agents')
|
|
75
89
|
.version(VERSION)
|
|
76
90
|
.option('--verbose', 'Show startup self-heal details on stderr')
|
|
@@ -86,7 +100,7 @@ program
|
|
|
86
100
|
function auditCommandPath(cmd) {
|
|
87
101
|
const parts = [];
|
|
88
102
|
let c = cmd;
|
|
89
|
-
while (c && c.name() && c.name() !==
|
|
103
|
+
while (c && c.name() && c.name() !== BRAND) {
|
|
90
104
|
parts.unshift(c.name());
|
|
91
105
|
c = c.parent;
|
|
92
106
|
}
|
|
@@ -129,11 +143,36 @@ program.hook('postAction', (_thisCommand, actionCommand) => {
|
|
|
129
143
|
// Best-effort completion record; the start line is the durable audit fact.
|
|
130
144
|
}
|
|
131
145
|
});
|
|
146
|
+
/**
|
|
147
|
+
* Skin the static root help for a brand: rewrite the visible `agents` command
|
|
148
|
+
* examples to the brand name and drop lines for commands this brand disabled.
|
|
149
|
+
* A no-op for the unbranded `agents` CLI with nothing disabled.
|
|
150
|
+
*/
|
|
151
|
+
function brandRootHelp(raw) {
|
|
152
|
+
let text = raw;
|
|
153
|
+
if (BRAND !== 'agents') {
|
|
154
|
+
text = text
|
|
155
|
+
.replace(/Usage: agents /g, `Usage: ${BRAND} `)
|
|
156
|
+
.replace(/^ {2}agents /gm, ` ${BRAND} `)
|
|
157
|
+
.replace(/Run 'agents /g, `Run '${BRAND} `);
|
|
158
|
+
}
|
|
159
|
+
const disabled = disabledCommandsForActiveBrand();
|
|
160
|
+
if (disabled.size > 0) {
|
|
161
|
+
text = text
|
|
162
|
+
.split('\n')
|
|
163
|
+
.filter((line) => {
|
|
164
|
+
const m = line.match(/^ {2}([a-z][\w-]*)/);
|
|
165
|
+
return !(m && disabled.has(m[1]));
|
|
166
|
+
})
|
|
167
|
+
.join('\n');
|
|
168
|
+
}
|
|
169
|
+
return text;
|
|
170
|
+
}
|
|
132
171
|
// Custom help for the main program only
|
|
133
172
|
const originalHelpInformation = program.helpInformation.bind(program);
|
|
134
173
|
program.helpInformation = function () {
|
|
135
|
-
if (this.name() ===
|
|
136
|
-
return `Usage: agents [command] [options]
|
|
174
|
+
if (this.name() === BRAND && !this.parent) {
|
|
175
|
+
return brandRootHelp(`Usage: agents [command] [options]
|
|
137
176
|
|
|
138
177
|
Install, configure, run, and dispatch AI coding agents from one place.
|
|
139
178
|
Works with Claude, Codex, Gemini, Cursor, OpenCode, OpenClaw, and Droid.
|
|
@@ -154,6 +193,7 @@ Agent versions:
|
|
|
154
193
|
trash Inspect and restore soft-deleted version directories
|
|
155
194
|
view [agent[@version]] List versions, or inspect one in detail
|
|
156
195
|
inspect <target> Deep details for one agent+version, or a DotAgents repo (user|system|project|alias|path)
|
|
196
|
+
resources Show merged DotAgents resources with their winning layer
|
|
157
197
|
|
|
158
198
|
Agent configuration (synced across versions):
|
|
159
199
|
rules Instructions given to agents (CLAUDE.md, etc.)
|
|
@@ -173,6 +213,7 @@ Run and dispatch:
|
|
|
173
213
|
run <agent|profile> [prompt] Run an agent. Omit prompt for interactive mode.
|
|
174
214
|
defaults Configure run defaults by agent/version selector
|
|
175
215
|
teams Coordinate multiple agents on shared work
|
|
216
|
+
hq JSON bridge for the interactive Agents HQ floor
|
|
176
217
|
routines Run agents on a cron schedule (scheduler auto-starts)
|
|
177
218
|
webhook Receive signed GitHub/Linear webhooks for trigger routines
|
|
178
219
|
funnel Expose a webhook receiver through Tailscale Funnel
|
|
@@ -182,6 +223,7 @@ Run and dispatch:
|
|
|
182
223
|
pty Drive interactive terminal programs (REPLs, TUIs) via a persistent PTY session
|
|
183
224
|
|
|
184
225
|
Credentials and profiles:
|
|
226
|
+
profile Activate resource profiles across skills, MCP, permissions, and secrets
|
|
185
227
|
profiles Bundles of (host CLI, endpoint, model, auth)
|
|
186
228
|
secrets Keychain-backed env bundles; use 'secrets exec <bundle> -- <cmd>' to inject into a subprocess
|
|
187
229
|
|
|
@@ -213,7 +255,7 @@ Options:
|
|
|
213
255
|
--verbose Show startup self-heal details on stderr
|
|
214
256
|
|
|
215
257
|
System config lives in ~/.agents/.system/. Run 'agents <command> --help' for details.
|
|
216
|
-
|
|
258
|
+
`);
|
|
217
259
|
}
|
|
218
260
|
return originalHelpInformation();
|
|
219
261
|
};
|
|
@@ -251,6 +293,7 @@ async function showWhatsNew(fromVersion, toVersion) {
|
|
|
251
293
|
}
|
|
252
294
|
const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
253
295
|
import { getUpdateCheckPath, getMigratedSentinelPath, getUserAgentsDir, getRuntimeStateDir } from './lib/state.js';
|
|
296
|
+
import { resolveBrandName, disabledCommandsForActiveBrand } from './lib/brand.js';
|
|
254
297
|
import { readUpdateCache, saveUpdateCheck, dismissUpdateVersion, shouldPromptUpgrade, findAgentsCliInstalls, } from './lib/self-update.js';
|
|
255
298
|
const UPDATE_CHECK_FILE = getUpdateCheckPath();
|
|
256
299
|
/**
|
|
@@ -668,7 +711,9 @@ async function registerEagerForRequest(name) {
|
|
|
668
711
|
async function registerAllEagerCommands() {
|
|
669
712
|
await reg(loadView);
|
|
670
713
|
await reg(loadShare);
|
|
714
|
+
await reg(loadSend);
|
|
671
715
|
await reg(loadInspect);
|
|
716
|
+
await reg(loadResources);
|
|
672
717
|
await reg(loadFeedback);
|
|
673
718
|
await reg(loadCommands);
|
|
674
719
|
await reg(loadHooks);
|
|
@@ -685,8 +730,8 @@ async function registerAllEagerCommands() {
|
|
|
685
730
|
await reg(loadWorktree);
|
|
686
731
|
await reg(loadVersions);
|
|
687
732
|
await reg(loadImport);
|
|
733
|
+
await reg(loadExport);
|
|
688
734
|
await reg(loadPackages);
|
|
689
|
-
await reg(loadDaemon);
|
|
690
735
|
await reg(loadRoutines);
|
|
691
736
|
await reg(loadMonitors);
|
|
692
737
|
await reg(loadRun);
|
|
@@ -703,6 +748,7 @@ async function registerAllEagerCommands() {
|
|
|
703
748
|
registerExecAliasCommand(program);
|
|
704
749
|
await reg(loadProfiles);
|
|
705
750
|
await reg(loadSecrets);
|
|
751
|
+
await reg(loadLogin);
|
|
706
752
|
await reg(loadWallet);
|
|
707
753
|
await reg(loadHelper);
|
|
708
754
|
await reg(loadMenubar);
|
|
@@ -717,6 +763,7 @@ async function registerAllEagerCommands() {
|
|
|
717
763
|
await reg(loadOutput);
|
|
718
764
|
await reg(loadBudget);
|
|
719
765
|
await reg(loadAlias);
|
|
766
|
+
await reg(loadMine);
|
|
720
767
|
await reg(loadPty);
|
|
721
768
|
await reg(loadTmux);
|
|
722
769
|
await reg(loadWatchdog);
|
|
@@ -728,7 +775,9 @@ async function registerAllEagerCommands() {
|
|
|
728
775
|
await reg(loadAudit);
|
|
729
776
|
await reg(loadWebhook);
|
|
730
777
|
await reg(loadFunnel);
|
|
778
|
+
await reg(loadHq);
|
|
731
779
|
await reg(loadFeed);
|
|
780
|
+
await reg(loadActivity);
|
|
732
781
|
await reg(loadMailboxes);
|
|
733
782
|
await reg(loadSsh);
|
|
734
783
|
registerJobsCronAliasCommand(program, 'jobs');
|
|
@@ -795,13 +844,19 @@ const verboseStartup = passedArgs.includes('--verbose');
|
|
|
795
844
|
// Help and version output are pure documentation — they must never gate on
|
|
796
845
|
// setup, otherwise `agents <cmd> --help` becomes useless on a fresh box.
|
|
797
846
|
const helpOrVersionRequested = passedArgs.some((arg) => arg === '--help' || arg === '-h' || arg === '--version' || arg === '-V');
|
|
847
|
+
// White-label: a brand can hide built-in top-level commands. A hidden command
|
|
848
|
+
// must behave as if it doesn't exist under this brand (unknown-command +
|
|
849
|
+
// spellcheck), while `agents` itself is unaffected. `brandDisabled` is empty for
|
|
850
|
+
// the unbranded CLI, so all of this is a no-op there.
|
|
851
|
+
const brandDisabled = disabledCommandsForActiveBrand();
|
|
852
|
+
const requestedIsDisabled = requestedCommand !== undefined && brandDisabled.has(requestedCommand);
|
|
798
853
|
// `--host` passthrough: run this invocation on a remote machine over SSH instead
|
|
799
854
|
// of locally. Handled before any local command registration / update check /
|
|
800
855
|
// background sync — a remote run needs none of that. Only the allowlisted
|
|
801
856
|
// read-only + config + teams commands route here; `run`/`sessions` are absent
|
|
802
857
|
// from the table and fall through to their own richer `--host` handling below.
|
|
803
858
|
// `--help`/`--version` stay local (docs must work without a reachable host).
|
|
804
|
-
if (requestedCommand !== undefined && !helpOrVersionRequested) {
|
|
859
|
+
if (requestedCommand !== undefined && !helpOrVersionRequested && !requestedIsDisabled) {
|
|
805
860
|
const { maybeRunOnHost } = await import('./lib/hosts/passthrough.js');
|
|
806
861
|
if (await maybeRunOnHost(requestedCommand, passedArgs)) {
|
|
807
862
|
process.exit(process.exitCode ?? 0);
|
|
@@ -810,7 +865,13 @@ if (requestedCommand !== undefined && !helpOrVersionRequested) {
|
|
|
810
865
|
// Register only the command(s) this invocation actually uses. Lazy commands
|
|
811
866
|
// (sessions/teams/cloud) are handled after applyGlobalHelpConventions below.
|
|
812
867
|
const isLazyRequest = requestedCommand !== undefined && LAZY_COMMAND_NAMES.has(requestedCommand);
|
|
813
|
-
if (
|
|
868
|
+
if (requestedIsDisabled) {
|
|
869
|
+
// The brand turned this command off: register the full tree so the "did you
|
|
870
|
+
// mean" picker still works, then strip the disabled commands below so the
|
|
871
|
+
// request resolves as unknown.
|
|
872
|
+
await registerAllEagerCommands();
|
|
873
|
+
}
|
|
874
|
+
else if (requestedCommand !== undefined && !isLazyRequest) {
|
|
814
875
|
const known = await registerEagerForRequest(requestedCommand);
|
|
815
876
|
if (!known) {
|
|
816
877
|
// Unknown top-level command: register the full tree so the "did you mean"
|
|
@@ -828,10 +889,19 @@ if (requestedCommand !== undefined && !isLazyRequest) {
|
|
|
828
889
|
applyGlobalHelpConventions(program);
|
|
829
890
|
// Lazy commands pull in the SQLite-backed session/cloud stack; register them
|
|
830
891
|
// only when explicitly requested, keeping lightweight commands off that path.
|
|
831
|
-
if (isLazyRequest) {
|
|
892
|
+
if (isLazyRequest && !requestedIsDisabled) {
|
|
832
893
|
for (const loader of COMMAND_LOADERS[requestedCommand])
|
|
833
894
|
await reg(loader);
|
|
834
895
|
}
|
|
896
|
+
// White-label: remove any commands this brand disabled so they resolve as
|
|
897
|
+
// unknown (the command:* handler then reports "unknown command"). Unbranded or
|
|
898
|
+
// nothing-disabled → no-op. Done after all registration paths above.
|
|
899
|
+
if (brandDisabled.size > 0) {
|
|
900
|
+
const kept = program.commands.filter((c) => !brandDisabled.has(c.name()));
|
|
901
|
+
if (kept.length !== program.commands.length) {
|
|
902
|
+
program.commands = kept;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
835
905
|
// Pure documentation paths (--version / --help / -h) return immediately: skip
|
|
836
906
|
// the update check (PATH scan + cache read) and the detached background sync
|
|
837
907
|
// (spawns a child process) that every other invocation runs.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { EventRecord } from './events.js';
|
|
2
|
+
/** Recognizable milestone events, ordered first in any activity lane. */
|
|
3
|
+
export type MilestoneEvent = 'plan.created' | 'pr.opened' | 'pr.merged' | 'worktree.created' | 'worktree.removed' | 'commit.created' | 'pushed' | 'subagent.spawned' | 'artifact.created';
|
|
4
|
+
/** Routine activity events, collapsed to counts by readers. */
|
|
5
|
+
export type ActivityKind = 'file.edited';
|
|
6
|
+
export type ActivityEventKind = MilestoneEvent | ActivityKind;
|
|
7
|
+
export type ActivityTier = 'milestone' | 'activity';
|
|
8
|
+
/** The set of milestone events, for tier classification and ordering. */
|
|
9
|
+
export declare const MILESTONE_EVENTS: readonly MilestoneEvent[];
|
|
10
|
+
export declare function tierForEvent(event: string): ActivityTier;
|
|
11
|
+
export interface ActivityEvent {
|
|
12
|
+
/** Schema version, for forward-compatible readers. */
|
|
13
|
+
v: number;
|
|
14
|
+
/** ISO-8601 timestamp of the event. */
|
|
15
|
+
ts: string;
|
|
16
|
+
/** Event kind (see {@link ActivityEventKind}). */
|
|
17
|
+
event: ActivityEventKind | string;
|
|
18
|
+
/** Coarse importance tier -- stamped by the writer, recomputed if absent. */
|
|
19
|
+
tier: ActivityTier;
|
|
20
|
+
/** Owning session id. */
|
|
21
|
+
sessionId: string;
|
|
22
|
+
/** Mailbox id for routing/joins (falls back to sessionId). */
|
|
23
|
+
mailboxId: string;
|
|
24
|
+
/** Short, normalized hostname the event fired on. */
|
|
25
|
+
host: string;
|
|
26
|
+
/** Runtime label (headless, tmux, cloud, ...). */
|
|
27
|
+
runtime: string;
|
|
28
|
+
/** Working directory at event time -- the join key to a project/git repo. */
|
|
29
|
+
cwd?: string;
|
|
30
|
+
/** Agent that produced the event (claude, codex, ...). */
|
|
31
|
+
agent?: string;
|
|
32
|
+
/** Tool that triggered the event (Bash, Task, ExitPlanMode, ...). */
|
|
33
|
+
tool?: string;
|
|
34
|
+
/** One-line human summary (plan title, PR command, sub-agent role). */
|
|
35
|
+
detail?: string;
|
|
36
|
+
/** Extracted URL when the event has one (e.g. the opened PR). */
|
|
37
|
+
url?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Append one event to a session's activity log. Primarily the Python hook
|
|
41
|
+
* writes these at runtime; this TS writer exists for tests and any in-process
|
|
42
|
+
* emitter. Uses O_APPEND so concurrent appends never interleave a line.
|
|
43
|
+
*/
|
|
44
|
+
export declare function appendActivityEvent(event: Omit<ActivityEvent, 'v' | 'tier'> & {
|
|
45
|
+
v?: number;
|
|
46
|
+
tier?: ActivityTier;
|
|
47
|
+
}, root?: string): void;
|
|
48
|
+
/** Read all events for one session (bounded tail). */
|
|
49
|
+
export declare function readSessionActivity(sessionId: string, root?: string, maxBytes?: number): ActivityEvent[];
|
|
50
|
+
/** List session ids that have an activity log. */
|
|
51
|
+
export declare function listActivitySessions(root?: string): string[];
|
|
52
|
+
export interface RecentActivityOptions {
|
|
53
|
+
/** Only include events at or after this epoch-ms. */
|
|
54
|
+
sinceMs?: number;
|
|
55
|
+
/** Cap the number of returned events (most recent first). */
|
|
56
|
+
limit?: number;
|
|
57
|
+
/** Override the activity dir (tests). */
|
|
58
|
+
root?: string;
|
|
59
|
+
/** Per-session tail budget in bytes. */
|
|
60
|
+
maxBytesPerSession?: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Merge recent events across every session's log, newest first. Reads only the
|
|
64
|
+
* tail of each file, so cost scales with active sessions, not transcript size.
|
|
65
|
+
*/
|
|
66
|
+
export declare function readRecentActivity(opts?: RecentActivityOptions): ActivityEvent[];
|
|
67
|
+
export interface CollapsedActivity {
|
|
68
|
+
/** Milestone events, individually preserved, newest first. */
|
|
69
|
+
milestones: ActivityEvent[];
|
|
70
|
+
/** Routine events rolled up to counts, e.g. { 'file.edited': 12 }. */
|
|
71
|
+
counts: Record<string, number>;
|
|
72
|
+
/** Number of sub-agents spawned across the collapsed set. */
|
|
73
|
+
subagentCount: number;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Split a chronological event list into individual milestones plus a
|
|
77
|
+
* count map for the routine (tier-2) events, so a reader shows recognizable
|
|
78
|
+
* deliverables in full and collapses the noise.
|
|
79
|
+
*/
|
|
80
|
+
export declare function collapseActivity(events: ActivityEvent[]): CollapsedActivity;
|
|
81
|
+
/**
|
|
82
|
+
* Normalize one activity event into the shared {@link EventRecord} shape so the
|
|
83
|
+
* agent-semantic stream reads through the same reader as operational events.
|
|
84
|
+
* `module` is stamped `activity` so `--module` filters partition the two cleanly.
|
|
85
|
+
*/
|
|
86
|
+
export declare function activityEventToRecord(ev: ActivityEvent): EventRecord;
|
|
87
|
+
/** Read recent activity across sessions as unified {@link EventRecord}s. */
|
|
88
|
+
export declare function readActivityAsEventRecords(opts?: RecentActivityOptions): EventRecord[];
|
|
89
|
+
/** Glyph + color + human label per event, so the lane reads at a glance. */
|
|
90
|
+
export declare const EVENT_STYLE: Record<string, {
|
|
91
|
+
glyph: string;
|
|
92
|
+
color: (s: string) => string;
|
|
93
|
+
label: string;
|
|
94
|
+
}>;
|
|
95
|
+
export declare function styleForEvent(event: string): {
|
|
96
|
+
glyph: string;
|
|
97
|
+
color: (s: string) => string;
|
|
98
|
+
label: string;
|
|
99
|
+
};
|
|
100
|
+
/** One rendered activity line: ` <rel> [host] <glyph label> detail url`. */
|
|
101
|
+
export declare function formatActivityLine(ev: ActivityEvent, opts?: {
|
|
102
|
+
showHost?: boolean;
|
|
103
|
+
}): string;
|
|
104
|
+
/**
|
|
105
|
+
* The activity-log hook (Python), sibling to 10-feed-publish.py. Classifies
|
|
106
|
+
* PreToolUse/PostToolUse payloads into activity events and appends one JSONL
|
|
107
|
+
* line per event to ~/.agents/.history/activity/<sessionId>.jsonl.
|
|
108
|
+
*
|
|
109
|
+
* Matcher-gated to mutating/milestone tools (Bash|Task|ExitPlanMode|Write|
|
|
110
|
+
* Edit|MultiEdit) so read-only tools never pay the hook cost. Fail-open: any
|
|
111
|
+
* error is swallowed so a logging hiccup never blocks a tool call.
|
|
112
|
+
*/
|
|
113
|
+
export declare const ACTIVITY_LOG_HOOK_SCRIPT = "#!/usr/bin/env python3\n\"\"\"Append agent-activity events for `agents feed` / `agents activity`.\n\nBound to PreToolUse (ExitPlanMode, Task) and PostToolUse (Bash, Write, Edit,\nMultiEdit). One append-only file per session; read-only tools never trigger it\nbecause the manifest matcher excludes them.\n\nSub-agent gate: when the payload carries `agent_type`, this is a Task/Agent\nsub-agent -- skip so only the top-level agent logs its own activity.\n\nFail-open: ANY error is swallowed so a logging hiccup never blocks a tool call.\n\"\"\"\nimport os\nimport re\nimport sys\nimport json\nimport shlex\nimport socket\nfrom datetime import datetime, timezone\n\nMAX_LOG_BYTES = 5 * 1024 * 1024 # cap a pathological session's log\nMILESTONE_EVENTS = {\n \"plan.created\", \"pr.opened\", \"pr.merged\", \"worktree.created\",\n \"worktree.removed\", \"commit.created\", \"pushed\", \"subagent.spawned\",\n \"artifact.created\",\n}\n\n# Deliverable file types + locations -- a Write here is a recognizable artifact\n# (an HTML plan, a PDF report, a rendered image), not a routine code edit.\nARTIFACT_EXTS = {\n \".html\", \".htm\", \".pdf\", \".png\", \".jpg\", \".jpeg\", \".gif\", \".svg\",\n \".webp\", \".mp4\", \".mov\", \".webm\", \".csv\", \".xlsx\", \".pptx\", \".docx\",\n}\nARTIFACT_DIR_HINTS = (\"/tmp/\", \"/downloads/\", \"/.agents/artifacts/\")\n\n\ndef is_artifact(file_path):\n low = (file_path or \"\").lower()\n if os.path.splitext(low)[1] in ARTIFACT_EXTS:\n return True\n return any(hint in low for hint in ARTIFACT_DIR_HINTS)\n\n\ndef first_line(text, limit=140):\n for raw in (text or \"\").splitlines():\n s = raw.strip().lstrip(\"#\").strip()\n if s:\n return s[:limit]\n return \"\"\n\n\ndef _subcommand(tokens, tool):\n \"\"\"First non-flag token after `tool`, i.e. its subcommand (skips -C <path>,\n -c <cfg>, and other leading flags). None if `tool` isn't the invoked command.\"\"\"\n try:\n i = tokens.index(tool) + 1\n except ValueError:\n return None, []\n while i < len(tokens):\n t = tokens[i]\n if t in (\"-C\", \"-c\", \"--git-dir\", \"--work-tree\"):\n i += 2 # flag that consumes the next token\n continue\n if t.startswith(\"-\"):\n i += 1\n continue\n return t, tokens[i + 1:]\n return None, []\n\n\ndef classify_bash(command):\n \"\"\"Return the milestone event for a git/gh command, else None. Tokenizes so a\n path like `git diff -- src/commit.ts` is not mistaken for a commit.\"\"\"\n try:\n tokens = shlex.split(command or \"\")\n except Exception:\n tokens = (command or \"\").split()\n\n verb, rest = _subcommand(tokens, \"git\")\n if verb == \"worktree\":\n sub = rest[0] if rest else \"\"\n if sub == \"add\":\n return \"worktree.created\"\n if sub == \"remove\":\n return \"worktree.removed\"\n elif verb == \"commit\":\n return \"commit.created\"\n elif verb == \"push\":\n return \"pushed\"\n\n verb, rest = _subcommand(tokens, \"gh\")\n if verb == \"pr\" and rest:\n if rest[0] == \"create\":\n return \"pr.opened\"\n if rest[0] == \"merge\":\n return \"pr.merged\"\n return None\n\n\ndef extract_url(tool_response):\n \"\"\"Pull the first https URL out of a Bash tool response (stdout).\"\"\"\n text = \"\"\n if isinstance(tool_response, dict):\n text = str(tool_response.get(\"stdout\") or tool_response.get(\"output\") or \"\")\n elif isinstance(tool_response, str):\n text = tool_response\n m = re.search(r\"https?://\\S+\", text)\n return m.group(0).rstrip(\").,\") if m else None\n\n\ndef build_event(payload, hook_event):\n tool_name = payload.get(\"tool_name\", \"\")\n tool_input = payload.get(\"tool_input\", {}) or {}\n tool_response = payload.get(\"tool_response\", {})\n\n event = None\n detail = None\n url = None\n\n if hook_event == \"PreToolUse\":\n if tool_name == \"ExitPlanMode\":\n event = \"plan.created\"\n detail = first_line(tool_input.get(\"plan\", \"\")) or \"plan presented\"\n elif tool_name == \"Task\":\n event = \"subagent.spawned\"\n role = tool_input.get(\"subagent_type\") or \"agent\"\n desc = tool_input.get(\"description\") or tool_input.get(\"prompt\") or \"\"\n detail = (role + \": \" + first_line(desc)).strip(\": \").strip()\n elif hook_event == \"PostToolUse\":\n if tool_name == \"Bash\":\n event = classify_bash(tool_input.get(\"command\", \"\"))\n if event:\n detail = first_line(tool_input.get(\"command\", \"\"))\n url = extract_url(tool_response)\n elif tool_name in (\"Write\", \"Edit\", \"MultiEdit\"):\n fp = tool_input.get(\"file_path\") or tool_input.get(\"path\") or \"\"\n # A freshly-written deliverable is a milestone; edits and code\n # writes stay routine and collapse to a count.\n if tool_name == \"Write\" and is_artifact(fp):\n event = \"artifact.created\"\n else:\n event = \"file.edited\"\n detail = os.path.basename(fp) if fp else tool_name\n\n if not event:\n return None, tool_name\n\n tier = \"milestone\" if event in MILESTONE_EVENTS else \"activity\"\n record = {\n \"v\": 1,\n \"ts\": datetime.now(timezone.utc).isoformat(),\n \"event\": event,\n \"tier\": tier,\n }\n if detail:\n record[\"detail\"] = detail\n if url:\n record[\"url\"] = url\n record[\"tool\"] = tool_name\n return record, tool_name\n\n\ndef main():\n raw = sys.stdin.read()\n try:\n payload = json.loads(raw) if raw.strip() else {}\n except Exception:\n return\n\n # Sub-agent gate -- only the top-level agent logs.\n if payload.get(\"agent_type\"):\n return\n\n session_id = payload.get(\"session_id\", \"\")\n if not session_id:\n return\n\n hook_event = payload.get(\"hook_event_name\", \"\")\n record, tool_name = build_event(payload, hook_event)\n if not record:\n return\n\n safe_session = re.sub(r\"[^A-Za-z0-9._-]\", \"-\", session_id) or \"unknown\"\n home = os.environ.get(\"HOME\") or os.path.expanduser(\"~\")\n activity_dir = os.path.join(home, \".agents\", \".history\", \"activity\")\n target = os.path.join(activity_dir, safe_session + \".jsonl\")\n\n # Identity (mirrors 10-feed-publish.py).\n mailbox_id = os.path.basename(\n os.environ.get(\"AGENTS_MAILBOX_DIR\", \"\").rstrip(\"/\")\n ) or session_id\n hostname = os.environ.get(\"AGENTS_SYNC_MACHINE_ID\") or socket.gethostname()\n host = re.sub(r\"[^a-z0-9_-]\", \"-\", hostname.split(\".\")[0].strip().lower()) or \"unknown\"\n\n record[\"sessionId\"] = session_id\n record[\"mailboxId\"] = mailbox_id\n record[\"host\"] = host\n record[\"runtime\"] = os.environ.get(\"AGENTS_RUNTIME\", \"headless\")\n cwd = payload.get(\"cwd\") or os.environ.get(\"AGENTS_CWD\")\n if cwd:\n record[\"cwd\"] = cwd\n agent = os.environ.get(\"AGENTS_AGENT_NAME\") or \"claude\"\n record[\"agent\"] = agent\n\n try:\n os.makedirs(activity_dir, exist_ok=True)\n # Cap growth: once over the size limit, keep only milestones.\n if record[\"tier\"] != \"milestone\":\n try:\n if os.path.getsize(target) > MAX_LOG_BYTES:\n return\n except OSError:\n pass\n with open(target, \"a\") as f:\n f.write(json.dumps(record) + \"\\n\")\n except Exception:\n pass # fail open\n\n\nif __name__ == \"__main__\":\n try:\n main()\n except Exception:\n pass # fail open\n";
|
|
114
|
+
/** Hook manifest entries (agents.yaml shape) for the activity-log hook. */
|
|
115
|
+
export declare const ACTIVITY_HOOK_DEFINITIONS: Record<string, Record<string, unknown>>;
|
|
116
|
+
/**
|
|
117
|
+
* Install the activity-log hook script into the user hooks dir and add its
|
|
118
|
+
* manifest entries to the user agents.yaml. Mirrors {@link ensureFeedPublishHook}
|
|
119
|
+
* in feed.ts -- idempotent, and never writes the read-only system repo.
|
|
120
|
+
*/
|
|
121
|
+
export declare function ensureActivityLogHook(userAgentsDir?: string): {
|
|
122
|
+
installed: boolean;
|
|
123
|
+
error?: string;
|
|
124
|
+
};
|