@phnx-labs/agents-cli 1.20.78 → 1.20.83
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 +595 -0
- package/README.md +26 -26
- package/dist/bin/agents +0 -0
- package/dist/commands/activity.d.ts +9 -0
- package/dist/commands/activity.js +153 -24
- package/dist/commands/apply.js +11 -4
- package/dist/commands/cli.js +1 -1
- package/dist/commands/commands.d.ts +1 -1
- package/dist/commands/commands.js +7 -6
- package/dist/commands/defaults.js +3 -11
- package/dist/commands/doctor.d.ts +66 -0
- package/dist/commands/doctor.js +596 -33
- package/dist/commands/events.js +1 -1
- package/dist/commands/exec.js +24 -5
- package/dist/commands/feed.d.ts +4 -0
- package/dist/commands/feed.js +87 -11
- package/dist/commands/harness.js +2 -2
- package/dist/commands/hooks.js +1 -1
- package/dist/commands/import.js +8 -4
- package/dist/commands/logs.js +6 -0
- package/dist/commands/menubar.js +12 -0
- package/dist/commands/models.js +1 -1
- package/dist/commands/packages.js +6 -6
- package/dist/commands/permissions.js +1 -1
- package/dist/commands/profiles.js +1 -1
- package/dist/commands/pull.d.ts +1 -1
- package/dist/commands/pull.js +4 -4
- package/dist/commands/repo.d.ts +78 -0
- package/dist/commands/repo.js +187 -11
- package/dist/commands/routines.d.ts +8 -1
- package/dist/commands/routines.js +241 -77
- package/dist/commands/rules.js +13 -12
- package/dist/commands/secrets.d.ts +3 -1
- package/dist/commands/secrets.js +100 -32
- package/dist/commands/sessions-inject.d.ts +7 -4
- package/dist/commands/sessions-inject.js +18 -18
- package/dist/commands/sessions-picker.js +32 -3
- package/dist/commands/sessions-resume.d.ts +16 -0
- package/dist/commands/sessions-resume.js +14 -13
- package/dist/commands/sessions.d.ts +115 -0
- package/dist/commands/sessions.js +189 -35
- package/dist/commands/set.d.ts +15 -0
- package/dist/commands/set.js +79 -0
- package/dist/commands/setup-fleet.d.ts +21 -0
- package/dist/commands/setup-fleet.js +201 -0
- package/dist/commands/setup-secrets.d.ts +20 -0
- package/dist/commands/setup-secrets.js +223 -0
- package/dist/commands/setup.js +17 -3
- package/dist/commands/skills.js +1 -1
- package/dist/commands/sync.js +7 -2
- package/dist/commands/teams-picker.js +0 -1
- package/dist/commands/teams.d.ts +33 -1
- package/dist/commands/teams.js +208 -44
- package/dist/commands/versions.js +10 -5
- package/dist/commands/view.d.ts +1 -0
- package/dist/commands/view.js +11 -0
- package/dist/index.js +52 -8
- package/dist/lib/acp/harnesses.js +0 -7
- package/dist/lib/activity.d.ts +156 -0
- package/dist/lib/activity.js +282 -0
- package/dist/lib/agents.d.ts +9 -2
- package/dist/lib/agents.js +72 -63
- package/dist/lib/browser/service.js +3 -0
- package/dist/lib/browser/types.d.ts +7 -0
- package/dist/lib/capabilities.js +6 -2
- package/dist/lib/crabbox/lease.js +2 -2
- package/dist/lib/crabbox/setup-copy.d.ts +4 -4
- package/dist/lib/crabbox/setup-copy.js +4 -4
- package/dist/lib/daemon.d.ts +18 -0
- package/dist/lib/daemon.js +47 -6
- package/dist/lib/devices/fleet.d.ts +1 -1
- package/dist/lib/devices/fleet.js +1 -1
- package/dist/lib/doctor-diff.d.ts +20 -0
- package/dist/lib/doctor-diff.js +6 -1
- package/dist/lib/drift.d.ts +31 -11
- package/dist/lib/drift.js +58 -7
- package/dist/lib/events.d.ts +8 -1
- package/dist/lib/events.js +10 -1
- package/dist/lib/exec.d.ts +7 -0
- package/dist/lib/exec.js +69 -11
- package/dist/lib/feed-post.d.ts +28 -1
- package/dist/lib/feed-post.js +110 -2
- package/dist/lib/fleet/apply.js +8 -1
- package/dist/lib/fleet/auth-sync.d.ts +18 -1
- package/dist/lib/fleet/auth-sync.js +25 -11
- package/dist/lib/fleet/remote-login.js +5 -0
- package/dist/lib/git.d.ts +14 -14
- package/dist/lib/git.js +41 -44
- package/dist/lib/hooks.d.ts +71 -0
- package/dist/lib/hooks.js +245 -95
- package/dist/lib/hosts/dispatch.d.ts +28 -1
- package/dist/lib/hosts/dispatch.js +33 -5
- package/dist/lib/hosts/option.js +2 -2
- package/dist/lib/hosts/passthrough.d.ts +22 -1
- package/dist/lib/hosts/passthrough.js +238 -13
- package/dist/lib/hosts/run-target.d.ts +2 -0
- package/dist/lib/hosts/run-target.js +1 -0
- package/dist/lib/hq/floor.d.ts +1 -1
- package/dist/lib/hq/floor.js +6 -1
- package/dist/lib/mcp.js +3 -71
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.d.ts +24 -0
- package/dist/lib/menubar/install-menubar.js +52 -2
- package/dist/lib/menubar/notify-desktop.d.ts +15 -0
- package/dist/lib/menubar/notify-desktop.js +41 -5
- package/dist/lib/merged-resources.d.ts +11 -0
- package/dist/{commands/resources.js → lib/merged-resources.js} +11 -19
- package/dist/lib/permissions.d.ts +0 -32
- package/dist/lib/permissions.js +4 -157
- package/dist/lib/picker.js +1 -1
- package/dist/lib/platform/process.d.ts +16 -5
- package/dist/lib/platform/process.js +54 -0
- package/dist/lib/plugins.d.ts +0 -8
- package/dist/lib/plugins.js +4 -110
- package/dist/lib/project-resources.js +4 -1
- package/dist/lib/refresh.d.ts +5 -3
- package/dist/lib/refresh.js +7 -5
- package/dist/lib/resources/commands.js +4 -1
- package/dist/lib/resources/mcp.js +0 -4
- package/dist/lib/resources/permissions.d.ts +1 -1
- package/dist/lib/resources/permissions.js +1 -5
- package/dist/lib/resources/rules.js +4 -1
- package/dist/lib/resources/skills.js +4 -1
- package/dist/lib/resources/subagents.js +4 -0
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/routines.d.ts +33 -0
- package/dist/lib/routines.js +122 -0
- package/dist/lib/run-defaults.d.ts +1 -0
- package/dist/lib/run-defaults.js +9 -0
- package/dist/lib/runner.d.ts +0 -12
- package/dist/lib/runner.js +49 -10
- package/dist/lib/scheduler.js +16 -4
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/agent.d.ts +16 -0
- package/dist/lib/secrets/agent.js +105 -3
- package/dist/lib/secrets/audit.d.ts +46 -0
- package/dist/lib/secrets/audit.js +56 -0
- package/dist/lib/secrets/bundles.d.ts +11 -8
- package/dist/lib/secrets/bundles.js +31 -23
- package/dist/lib/secrets/index.d.ts +2 -1
- package/dist/lib/secrets/index.js +8 -2
- package/dist/lib/secrets/remote.d.ts +75 -2
- package/dist/lib/secrets/remote.js +139 -5
- package/dist/lib/secrets/sync.js +3 -3
- package/dist/lib/session/active.d.ts +128 -26
- package/dist/lib/session/active.js +192 -53
- package/dist/lib/session/actor-sidecar.d.ts +23 -0
- package/dist/lib/session/actor-sidecar.js +101 -0
- package/dist/lib/session/db.d.ts +4 -1
- package/dist/lib/session/db.js +65 -8
- package/dist/lib/session/discover.d.ts +4 -0
- package/dist/lib/session/discover.js +13 -0
- package/dist/lib/session/hook-sessions.d.ts +9 -0
- package/dist/lib/session/hook-sessions.js +57 -8
- package/dist/lib/session/pid-registry.d.ts +13 -0
- package/dist/lib/session/render.d.ts +2 -0
- package/dist/lib/session/render.js +1 -1
- package/dist/lib/session/types.d.ts +16 -0
- package/dist/lib/staleness/detectors/commands.js +2 -2
- package/dist/lib/staleness/detectors/permissions.js +1 -45
- package/dist/lib/staleness/writers/commands.js +4 -5
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +1 -2
- package/dist/lib/startup/command-registry.js +2 -4
- package/dist/lib/state.js +57 -1
- package/dist/lib/subagents-registry.js +5 -12
- package/dist/lib/subagents.d.ts +0 -10
- package/dist/lib/subagents.js +0 -12
- package/dist/lib/teams/agents.d.ts +0 -2
- package/dist/lib/teams/agents.js +10 -27
- package/dist/lib/teams/index.d.ts +1 -1
- package/dist/lib/teams/index.js +1 -1
- package/dist/lib/types.d.ts +17 -9
- package/dist/lib/usage.d.ts +28 -5
- package/dist/lib/usage.js +271 -8
- package/dist/lib/versions.js +7 -1
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdog.js +25 -13
- package/package.json +1 -2
- package/dist/commands/check.d.ts +0 -15
- package/dist/commands/check.js +0 -180
- package/dist/commands/resources.d.ts +0 -5
|
@@ -11,8 +11,9 @@ import * as fs from 'fs';
|
|
|
11
11
|
import * as path from 'path';
|
|
12
12
|
import * as yaml from 'yaml';
|
|
13
13
|
import { isDaemonRunning, signalDaemonReload, startDaemon, stopDaemon, readDaemonLog, getDaemonStatus, } from '../lib/daemon.js';
|
|
14
|
+
import { resolveAgentName, isAgentHardDeprecated, hardDeprecationError } from '../lib/agents.js';
|
|
14
15
|
import { humanizeCron, humanizeNextRun, formatRepoLink, REPO_DISPLAY_MAX } from '../lib/routines-format.js';
|
|
15
|
-
import { listJobs as listAllJobs, deleteJob, readJob, validateJob, writeJob, setJobEnabled, listRuns, getLatestRun, getRunDir, getJobPath, parseAtTime, jobRunsOnThisDevice, checkJobDeviceEligibility, normalizeTriggerEvent, parseHostStrategy, resolveHostStrategy, placementRequiresFiringPin, HOST_STRATEGIES, } from '../lib/routines.js';
|
|
16
|
+
import { listJobs as listAllJobs, deleteJob, readJob, validateJob, writeJob, setJobEnabled, listRuns, getLatestRun, getRunDir, getJobPath, parseAtTime, hasCompletedOneShotRun, isOneShotLikeSchedule, isOneShotRoutine, isPastOneShotRoutine, jobRunsOnThisDevice, checkJobDeviceEligibility, normalizeTriggerEvent, parseHostStrategy, resolveHostStrategy, placementRequiresFiringPin, HOST_STRATEGIES, } from '../lib/routines.js';
|
|
16
17
|
import { discoverProjectRoutinesAt, enableProjectRoutines, disableProjectRoutines, syncProjectRoutines, syncAllProjectRoutines, listEnabledProjectRoots, resolveProjectRoot, displayProjectPath, listProjectRoutineFiles, } from '../lib/routines-project.js';
|
|
17
18
|
import { fireWebhookJobs, matchJobsToWebhook } from '../lib/triggers/webhook.js';
|
|
18
19
|
import { getRoutinesDir } from '../lib/state.js';
|
|
@@ -23,7 +24,7 @@ import { JobScheduler } from '../lib/scheduler.js';
|
|
|
23
24
|
import { detectOverdueJobs } from '../lib/overdue.js';
|
|
24
25
|
import { isInteractiveTerminal, requireInteractiveSelection } from './utils.js';
|
|
25
26
|
import { setHelpSections } from '../lib/help.js';
|
|
26
|
-
import { loadDevices } from '../lib/devices/registry.js';
|
|
27
|
+
import { loadDevices, loadDevicesSync } from '../lib/devices/registry.js';
|
|
27
28
|
import { machineId, normalizeHost } from '../lib/machine-id.js';
|
|
28
29
|
import { addHostOption } from '../lib/hosts/option.js';
|
|
29
30
|
/**
|
|
@@ -74,6 +75,164 @@ function fireConditionLabel(job) {
|
|
|
74
75
|
}
|
|
75
76
|
return '-';
|
|
76
77
|
}
|
|
78
|
+
function scheduleLabel(job) {
|
|
79
|
+
let label = fireConditionLabel(job);
|
|
80
|
+
if (isOneShotRoutine(job))
|
|
81
|
+
label = `${label} (one-shot)`;
|
|
82
|
+
if (job.endAt) {
|
|
83
|
+
const end = new Date(job.endAt);
|
|
84
|
+
const endLabel = Number.isFinite(end.getTime())
|
|
85
|
+
? end.toLocaleDateString()
|
|
86
|
+
: job.endAt;
|
|
87
|
+
label = `${label} (until ${endLabel})`;
|
|
88
|
+
}
|
|
89
|
+
return label;
|
|
90
|
+
}
|
|
91
|
+
function nextRunForDisplay(job, scheduler) {
|
|
92
|
+
if (isPastOneShotRoutine(job))
|
|
93
|
+
return null;
|
|
94
|
+
return scheduler.getNextRun(job.name);
|
|
95
|
+
}
|
|
96
|
+
function nextRunLabel(job, scheduler, now) {
|
|
97
|
+
if (isPastOneShotRoutine(job))
|
|
98
|
+
return 'expired';
|
|
99
|
+
return humanizeNextRun(scheduler.getNextRun(job.name) ?? null, now, job.timezone);
|
|
100
|
+
}
|
|
101
|
+
function deviceStateLabel(name, registry) {
|
|
102
|
+
const profile = registry[normalizeHost(name)] ?? registry[name];
|
|
103
|
+
if (!profile)
|
|
104
|
+
return 'unknown';
|
|
105
|
+
if (profile.reachability?.reachable === false)
|
|
106
|
+
return 'offline';
|
|
107
|
+
if (profile.reachability?.reachable === true)
|
|
108
|
+
return 'online';
|
|
109
|
+
if (profile.tailscale?.online === false)
|
|
110
|
+
return 'offline';
|
|
111
|
+
if (profile.tailscale?.online === true)
|
|
112
|
+
return 'online';
|
|
113
|
+
return 'unknown';
|
|
114
|
+
}
|
|
115
|
+
function titleWithDeviceState(title, name, registry) {
|
|
116
|
+
const state = deviceStateLabel(name, registry);
|
|
117
|
+
return state && state !== 'online' ? `${title} (${state})` : title;
|
|
118
|
+
}
|
|
119
|
+
function placementTag(job) {
|
|
120
|
+
const strategy = resolveHostStrategy(job);
|
|
121
|
+
if (strategy === 'local')
|
|
122
|
+
return job.host ? `->${job.host}` : '';
|
|
123
|
+
if (strategy === 'host')
|
|
124
|
+
return `->${job.host ?? '?'}`;
|
|
125
|
+
if (strategy === 'fleet')
|
|
126
|
+
return '->fleet';
|
|
127
|
+
return '->cloud';
|
|
128
|
+
}
|
|
129
|
+
function deviceLabel(job, width) {
|
|
130
|
+
const full = [job.devices?.join(',') ?? '', placementTag(job)]
|
|
131
|
+
.filter(Boolean)
|
|
132
|
+
.join(' ');
|
|
133
|
+
const raw = full.length === 0 ? 'all' : full;
|
|
134
|
+
const display = width && raw.length > width
|
|
135
|
+
? raw.slice(0, width - 1) + '…'
|
|
136
|
+
: raw;
|
|
137
|
+
return { raw, display, dim: full.length === 0 || !jobRunsOnThisDevice(job) };
|
|
138
|
+
}
|
|
139
|
+
export function groupRoutineJobsByDevice(jobs, registry, self = machineId()) {
|
|
140
|
+
const groups = new Map();
|
|
141
|
+
const add = (key, title, job) => {
|
|
142
|
+
const existing = groups.get(key);
|
|
143
|
+
if (existing) {
|
|
144
|
+
existing.jobs.push(job);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
groups.set(key, { key, title, jobs: [job] });
|
|
148
|
+
};
|
|
149
|
+
for (const job of jobs) {
|
|
150
|
+
const strategy = resolveHostStrategy(job);
|
|
151
|
+
if (strategy === 'cloud') {
|
|
152
|
+
add('cloud', 'Cloud', job);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (strategy === 'fleet') {
|
|
156
|
+
add('fleet', 'Fleet-wide', job);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (strategy === 'host') {
|
|
160
|
+
const host = job.host ?? 'unknown-host';
|
|
161
|
+
add(`host:${normalizeHost(host)}`, titleWithDeviceState(`Host: ${host}`, host, registry), job);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const devices = job.devices ?? [];
|
|
165
|
+
if (devices.length === 0) {
|
|
166
|
+
add('fleet', 'Fleet-wide', job);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
for (const device of devices) {
|
|
170
|
+
const normalized = normalizeHost(device);
|
|
171
|
+
if (normalized === self) {
|
|
172
|
+
add('this-machine', `This machine (${self})`, job);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
add(`device:${normalized}`, titleWithDeviceState(`Device: ${normalized}`, normalized, registry), job);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const order = (group) => {
|
|
180
|
+
if (group.key === 'this-machine')
|
|
181
|
+
return 0;
|
|
182
|
+
if (group.key === 'fleet')
|
|
183
|
+
return 1;
|
|
184
|
+
if (group.key === 'cloud')
|
|
185
|
+
return 2;
|
|
186
|
+
if (group.key.startsWith('device:'))
|
|
187
|
+
return 3;
|
|
188
|
+
if (group.key.startsWith('host:'))
|
|
189
|
+
return 4;
|
|
190
|
+
return 5;
|
|
191
|
+
};
|
|
192
|
+
return [...groups.values()].sort((a, b) => order(a) - order(b) || a.title.localeCompare(b.title));
|
|
193
|
+
}
|
|
194
|
+
function renderRoutineRows({ jobs, scheduler, overdueSet, link, now }) {
|
|
195
|
+
const NAME_W = 24;
|
|
196
|
+
const AGENT_W = 10;
|
|
197
|
+
const REPO_W = REPO_DISPLAY_MAX;
|
|
198
|
+
const DEVICE_W = 22;
|
|
199
|
+
const SCHED_W = 34;
|
|
200
|
+
const ENABLED_W = 10;
|
|
201
|
+
const NEXT_W = 22;
|
|
202
|
+
const header = ` ${'Name'.padEnd(NAME_W)} ${'Agent'.padEnd(AGENT_W)} ${'Repo'.padEnd(REPO_W)} ${'Devices'.padEnd(DEVICE_W)} ${'Schedule'.padEnd(SCHED_W)} ${'Enabled'.padEnd(ENABLED_W)} ${'Next Run'.padEnd(NEXT_W)} Last Status`;
|
|
203
|
+
console.log(chalk.gray(header));
|
|
204
|
+
console.log(chalk.gray(' ' + '-'.repeat(NAME_W + AGENT_W + REPO_W + DEVICE_W + SCHED_W + ENABLED_W + NEXT_W + 20)));
|
|
205
|
+
for (const job of jobs) {
|
|
206
|
+
const nextStr = nextRunLabel(job, scheduler, now);
|
|
207
|
+
const schedStr = scheduleLabel(job);
|
|
208
|
+
const latestRun = getLatestRun(job.name);
|
|
209
|
+
const lastStatus = latestRun?.status || '-';
|
|
210
|
+
const sourceRepo = job.source?.repo ?? job.repo;
|
|
211
|
+
const sourceLabel = sourceRepo
|
|
212
|
+
? (job.source?.branch ? `${sourceRepo}@${job.source.branch}` : sourceRepo)
|
|
213
|
+
: null;
|
|
214
|
+
const repoInfo = formatRepoLink(sourceLabel ?? job.repo);
|
|
215
|
+
const repoCell = link(repoInfo.display, repoInfo.href);
|
|
216
|
+
const repoPadding = Math.max(0, REPO_W - repoInfo.display.length);
|
|
217
|
+
const enabledStr = job.enabled ? chalk.green('yes') : chalk.gray('no');
|
|
218
|
+
const enabledWord = job.enabled ? 'yes' : 'no';
|
|
219
|
+
const enabledPad = Math.max(0, ENABLED_W - enabledWord.length);
|
|
220
|
+
const device = deviceLabel(job, DEVICE_W);
|
|
221
|
+
const deviceCell = device.dim ? chalk.gray(device.display) : device.display;
|
|
222
|
+
const devicePad = Math.max(0, DEVICE_W - device.display.length);
|
|
223
|
+
const statusColor = lastStatus === 'completed' ? chalk.green
|
|
224
|
+
: lastStatus === 'failed' ? chalk.red
|
|
225
|
+
: lastStatus === 'timeout' ? chalk.yellow
|
|
226
|
+
: chalk.gray;
|
|
227
|
+
const overdueTag = overdueSet.has(job.name) ? chalk.yellow(' (overdue)') : '';
|
|
228
|
+
const agentLabelPadded = job.command
|
|
229
|
+
? chalk.magenta('command'.padEnd(10))
|
|
230
|
+
: job.workflow
|
|
231
|
+
? chalk.magenta(`wf:${job.workflow}`.padEnd(10))
|
|
232
|
+
: (job.agent || '').padEnd(10);
|
|
233
|
+
console.log(` ${chalk.cyan(job.name.padEnd(NAME_W))} ${agentLabelPadded} ${repoCell}${' '.repeat(repoPadding)} ${deviceCell}${' '.repeat(devicePad)} ${schedStr.padEnd(SCHED_W)} ${enabledStr}${' '.repeat(enabledPad)} ${chalk.gray(nextStr.padEnd(NEXT_W))} ${statusColor(lastStatus)}${overdueTag}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
77
236
|
function parseRoutineTrigger(options) {
|
|
78
237
|
const raw = typeof options.on === 'string' ? options.on : undefined;
|
|
79
238
|
if (!raw)
|
|
@@ -280,7 +439,7 @@ export function registerRoutinesCommands(program) {
|
|
|
280
439
|
`,
|
|
281
440
|
notes: `
|
|
282
441
|
A routine is a YAML file that schedules an agent invocation. It specifies:
|
|
283
|
-
- which agent to run (claude, codex,
|
|
442
|
+
- which agent to run (claude, codex, antigravity, ...)
|
|
284
443
|
- when to run (cron schedule or one-shot time)
|
|
285
444
|
- what task to give the agent (the prompt)
|
|
286
445
|
- execution constraints (mode, effort, timeout)
|
|
@@ -307,7 +466,13 @@ export function registerRoutinesCommands(program) {
|
|
|
307
466
|
.command('list')
|
|
308
467
|
.description('See all scheduled jobs, when they run next, and their last execution status')
|
|
309
468
|
.option('--json', 'Emit machine-readable JSON instead of the table (used by the menu bar helper)')
|
|
469
|
+
.option('--group-by <field>', 'Group table output by device (default for terminal output)')
|
|
470
|
+
.option('--flat', 'Print the legacy flat table instead of grouped sections')
|
|
310
471
|
.action((options) => {
|
|
472
|
+
if (options.groupBy && options.groupBy !== 'device') {
|
|
473
|
+
console.error(chalk.red(`Unsupported --group-by '${options.groupBy}'. Use: device`));
|
|
474
|
+
process.exit(1);
|
|
475
|
+
}
|
|
311
476
|
try {
|
|
312
477
|
monitorRunningJobs();
|
|
313
478
|
}
|
|
@@ -338,7 +503,6 @@ export function registerRoutinesCommands(program) {
|
|
|
338
503
|
if (options.json) {
|
|
339
504
|
const nowJson = new Date();
|
|
340
505
|
const payload = jobs.map((job) => {
|
|
341
|
-
const nextRun = scheduler.getNextRun(job.name);
|
|
342
506
|
const latestRun = getLatestRun(job.name);
|
|
343
507
|
return {
|
|
344
508
|
name: job.name,
|
|
@@ -356,11 +520,14 @@ export function registerRoutinesCommands(program) {
|
|
|
356
520
|
source: job.source ?? null,
|
|
357
521
|
sourceRepo: job.source?.repo ?? job.repo ?? null,
|
|
358
522
|
sourceBranch: job.source?.branch ?? null,
|
|
523
|
+
runOnce: Boolean(job.runOnce),
|
|
524
|
+
oneShot: isOneShotRoutine(job),
|
|
525
|
+
expired: isPastOneShotRoutine(job, nowJson),
|
|
359
526
|
runsHere: jobRunsOnThisDevice(job),
|
|
360
527
|
enabled: job.enabled,
|
|
361
528
|
overdue: overdueSet.has(job.name),
|
|
362
|
-
nextRun:
|
|
363
|
-
nextRunHuman:
|
|
529
|
+
nextRun: nextRunForDisplay(job, scheduler)?.toISOString() ?? null,
|
|
530
|
+
nextRunHuman: nextRunLabel(job, scheduler, nowJson),
|
|
364
531
|
lastStatus: latestRun?.status ?? null,
|
|
365
532
|
exitCode: latestRun?.exitCode ?? null,
|
|
366
533
|
failureReason: latestRun?.errorMessage ?? null,
|
|
@@ -378,76 +545,21 @@ export function registerRoutinesCommands(program) {
|
|
|
378
545
|
// contains raw ESC ] 8 ;; ... BEL escape sequences.
|
|
379
546
|
const link = (label, url) => url && process.stdout.isTTY ? `\x1b]8;;${url}\x07${label}\x1b]8;;\x07` : label;
|
|
380
547
|
const now = new Date();
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
const end = new Date(job.endAt);
|
|
397
|
-
const endLabel = Number.isFinite(end.getTime())
|
|
398
|
-
? end.toLocaleDateString()
|
|
399
|
-
: job.endAt;
|
|
400
|
-
schedStr = `${schedStr} (until ${endLabel})`;
|
|
401
|
-
}
|
|
402
|
-
const latestRun = getLatestRun(job.name);
|
|
403
|
-
const lastStatus = latestRun?.status || '-';
|
|
404
|
-
// Prefer project-source repo (with optional @branch) over bare job.repo.
|
|
405
|
-
const sourceRepo = job.source?.repo ?? job.repo;
|
|
406
|
-
const sourceLabel = sourceRepo
|
|
407
|
-
? (job.source?.branch ? `${sourceRepo}@${job.source.branch}` : sourceRepo)
|
|
408
|
-
: null;
|
|
409
|
-
const repoInfo = formatRepoLink(sourceLabel ?? job.repo);
|
|
410
|
-
const repoCell = link(repoInfo.display, repoInfo.href);
|
|
411
|
-
// Pad based on the display string, not the raw cell (which may include escape codes).
|
|
412
|
-
const repoPadding = Math.max(0, REPO_W - repoInfo.display.length);
|
|
413
|
-
const enabledStr = job.enabled ? chalk.green('yes') : chalk.gray('no');
|
|
414
|
-
// chalk adds escape codes; pad the raw word and let chalk wrap it.
|
|
415
|
-
const enabledWord = job.enabled ? 'yes' : 'no';
|
|
416
|
-
const enabledPad = Math.max(0, ENABLED_W - enabledWord.length);
|
|
417
|
-
// Placement rides in the Devices cell: eligibility →execution strategy.
|
|
418
|
-
const strategy = resolveHostStrategy(job);
|
|
419
|
-
const placementTag = strategy === 'local'
|
|
420
|
-
? (job.host ? `→${job.host}` : '')
|
|
421
|
-
: strategy === 'host'
|
|
422
|
-
? `→${job.host ?? '?'}`
|
|
423
|
-
: strategy === 'fleet'
|
|
424
|
-
? '→fleet'
|
|
425
|
-
: '→cloud';
|
|
426
|
-
const deviceFull = [job.devices?.join(',') ?? '', placementTag]
|
|
427
|
-
.filter(Boolean)
|
|
428
|
-
.join(' ');
|
|
429
|
-
const deviceWord = deviceFull.length === 0
|
|
430
|
-
? 'all'
|
|
431
|
-
: deviceFull.length > DEVICE_W
|
|
432
|
-
? deviceFull.slice(0, DEVICE_W - 1) + '…'
|
|
433
|
-
: deviceFull;
|
|
434
|
-
const deviceCell = deviceFull.length === 0
|
|
435
|
-
? chalk.gray('all')
|
|
436
|
-
: jobRunsOnThisDevice(job)
|
|
437
|
-
? deviceWord
|
|
438
|
-
: chalk.gray(deviceWord);
|
|
439
|
-
const devicePad = Math.max(0, DEVICE_W - deviceWord.length);
|
|
440
|
-
const statusColor = lastStatus === 'completed' ? chalk.green
|
|
441
|
-
: lastStatus === 'failed' ? chalk.red
|
|
442
|
-
: lastStatus === 'timeout' ? chalk.yellow
|
|
443
|
-
: chalk.gray;
|
|
444
|
-
const overdueTag = overdueSet.has(job.name) ? chalk.yellow(' (overdue)') : '';
|
|
445
|
-
const agentLabelPadded = job.command
|
|
446
|
-
? chalk.magenta('command'.padEnd(10))
|
|
447
|
-
: job.workflow
|
|
448
|
-
? chalk.magenta(`wf:${job.workflow}`.padEnd(10))
|
|
449
|
-
: (job.agent || '').padEnd(10);
|
|
450
|
-
console.log(` ${chalk.cyan(job.name.padEnd(NAME_W))} ${agentLabelPadded} ${repoCell}${' '.repeat(repoPadding)} ${deviceCell}${' '.repeat(devicePad)} ${schedStr.padEnd(SCHED_W)} ${enabledStr}${' '.repeat(enabledPad)} ${chalk.gray(nextStr.padEnd(NEXT_W))} ${statusColor(lastStatus)}${overdueTag}`);
|
|
548
|
+
if (options.flat) {
|
|
549
|
+
renderRoutineRows({ jobs, scheduler, overdueSet, link, now });
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
let registry = {};
|
|
553
|
+
try {
|
|
554
|
+
registry = loadDevicesSync();
|
|
555
|
+
}
|
|
556
|
+
catch (err) {
|
|
557
|
+
console.error(chalk.yellow(`Could not read device registry: ${err.message}`));
|
|
558
|
+
}
|
|
559
|
+
for (const group of groupRoutineJobsByDevice(jobs, registry)) {
|
|
560
|
+
console.log(chalk.bold(`\n${group.title}`));
|
|
561
|
+
renderRoutineRows({ jobs: group.jobs, scheduler, overdueSet, link, now });
|
|
562
|
+
}
|
|
451
563
|
}
|
|
452
564
|
if (overdueSet.size > 0) {
|
|
453
565
|
console.log();
|
|
@@ -460,7 +572,7 @@ export function registerRoutinesCommands(program) {
|
|
|
460
572
|
.command('add [nameOrPath]')
|
|
461
573
|
.description('Create a new routine from a YAML file or inline flags. Starts the scheduler automatically if it is not already running.')
|
|
462
574
|
.option('-s, --schedule <cron>', 'Cron schedule in standard format (5 fields: minute hour day month weekday)')
|
|
463
|
-
.option('-a, --agent <agent>', 'Which agent runs this routine: claude, codex,
|
|
575
|
+
.option('-a, --agent <agent>', 'Which agent runs this routine: claude, codex, antigravity, cursor, or opencode')
|
|
464
576
|
.option('--workflow <name>', 'Run an installed workflow (~/.agents/workflows/<name>) via `agents run`. Mutually exclusive with --agent.')
|
|
465
577
|
.option('--command <sh>', 'Run a plain shell command directly (no agent, no auth, no sandbox) — for deterministic housekeeping routines. Mutually exclusive with --agent and --workflow; --prompt is not used.')
|
|
466
578
|
.option('-p, --prompt <prompt>', 'Task instruction for the agent')
|
|
@@ -519,6 +631,10 @@ export function registerRoutinesCommands(program) {
|
|
|
519
631
|
schedule = parsed.schedule;
|
|
520
632
|
runOnce = parsed.runOnce;
|
|
521
633
|
}
|
|
634
|
+
if (!options.at && isOneShotLikeSchedule(schedule)) {
|
|
635
|
+
runOnce = true;
|
|
636
|
+
console.error(chalk.yellow(`Schedule "${schedule}" pins minute, hour, day, and month; treating it as one-shot. Prefer --at for one-time routines.`));
|
|
637
|
+
}
|
|
522
638
|
if (!schedule && !trigger) {
|
|
523
639
|
console.error(chalk.red('Schedule or trigger is required (use --schedule, --at, or --on)'));
|
|
524
640
|
process.exit(1);
|
|
@@ -527,6 +643,15 @@ export function registerRoutinesCommands(program) {
|
|
|
527
643
|
console.error(chalk.red('An agent, workflow, or command is required (use --agent, --workflow, or --command)'));
|
|
528
644
|
process.exit(1);
|
|
529
645
|
}
|
|
646
|
+
// Hard-deprecated harnesses cannot be scheduled — refuse at create time so
|
|
647
|
+
// no new recurring job silently fails against a retired backend.
|
|
648
|
+
if (options.agent) {
|
|
649
|
+
const routineAgentId = resolveAgentName(options.agent);
|
|
650
|
+
if (routineAgentId && isAgentHardDeprecated(routineAgentId)) {
|
|
651
|
+
console.error(chalk.red(hardDeprecationError(routineAgentId)));
|
|
652
|
+
process.exit(1);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
530
655
|
// Command routines run a plain shell and take no prompt; agent/workflow routines require one.
|
|
531
656
|
if (!options.command && !options.prompt) {
|
|
532
657
|
console.error(chalk.red('Prompt is required (use --prompt)'));
|
|
@@ -651,6 +776,10 @@ export function registerRoutinesCommands(program) {
|
|
|
651
776
|
enabled: true,
|
|
652
777
|
...parsed,
|
|
653
778
|
};
|
|
779
|
+
if (isOneShotLikeSchedule(config.schedule)) {
|
|
780
|
+
config.runOnce = true;
|
|
781
|
+
console.error(chalk.yellow(`Schedule "${config.schedule}" pins minute, hour, day, and month; treating it as one-shot. Prefer --at for one-time routines.`));
|
|
782
|
+
}
|
|
654
783
|
// Same duplicate-fire guard as --placement/--run-on: off-box placement
|
|
655
784
|
// with no eligibility pin would fire from every daemon in the fleet.
|
|
656
785
|
const fileStrategy = resolveHostStrategy(config);
|
|
@@ -678,6 +807,41 @@ export function registerRoutinesCommands(program) {
|
|
|
678
807
|
ensureSchedulerRunning();
|
|
679
808
|
}
|
|
680
809
|
});
|
|
810
|
+
routinesCmd
|
|
811
|
+
.command('cleanup')
|
|
812
|
+
.description('Remove expired one-shot routines that already fired and still have a user-layer YAML file.')
|
|
813
|
+
.option('--dry-run', 'Show routines that would be removed without deleting files')
|
|
814
|
+
.action((options) => {
|
|
815
|
+
const jobs = listAllJobs()
|
|
816
|
+
.filter((job) => getJobPath(job.name) !== null)
|
|
817
|
+
.filter((job) => hasCompletedOneShotRun(job));
|
|
818
|
+
if (jobs.length === 0) {
|
|
819
|
+
console.log(chalk.gray('No completed expired one-shot routines to clean up.'));
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
if (options.dryRun) {
|
|
823
|
+
console.log(chalk.bold('Expired one-shot routines eligible for cleanup\n'));
|
|
824
|
+
for (const job of jobs) {
|
|
825
|
+
console.log(` ${chalk.cyan(job.name)} ${chalk.gray(scheduleLabel(job))}`);
|
|
826
|
+
}
|
|
827
|
+
console.log(chalk.gray(`\nDry run. Remove with: agents routines cleanup`));
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
let removed = 0;
|
|
831
|
+
for (const job of jobs) {
|
|
832
|
+
if (deleteJob(job.name)) {
|
|
833
|
+
removed++;
|
|
834
|
+
console.log(chalk.green(`Removed ${job.name}`));
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
console.log(chalk.gray(`Cleaned up ${removed} expired one-shot routine(s).`));
|
|
838
|
+
try {
|
|
839
|
+
signalDaemonReload();
|
|
840
|
+
}
|
|
841
|
+
catch {
|
|
842
|
+
// The daemon may not be running; the next start will read the cleaned directory.
|
|
843
|
+
}
|
|
844
|
+
});
|
|
681
845
|
routinesCmd
|
|
682
846
|
.command('remove [name]')
|
|
683
847
|
.description('Delete a routine. Stops scheduling future runs; past execution logs remain on disk.')
|
package/dist/commands/rules.js
CHANGED
|
@@ -4,7 +4,8 @@ import * as fs from 'fs';
|
|
|
4
4
|
import * as os from 'os';
|
|
5
5
|
import * as path from 'path';
|
|
6
6
|
import { select, checkbox } from '@inquirer/prompts';
|
|
7
|
-
import { AGENTS, agentConfigDirName,
|
|
7
|
+
import { AGENTS, agentConfigDirName, MANAGED_AGENT_IDS, resolveAgentName, formatAgentError, agentLabel, } from '../lib/agents.js';
|
|
8
|
+
import { capableAgents } from '../lib/capabilities.js';
|
|
8
9
|
import { cloneRepo } from '../lib/git.js';
|
|
9
10
|
import { discoverInstructionsFromRepo, discoverRuleFilesFromRepo, installInstructionsCentrally, uninstallInstructions, listInstalledInstructionsWithScope, instructionsExists, getInstructionsContent, listCentralRules, } from '../lib/rules/rules.js';
|
|
10
11
|
import { listInstalledVersions, getGlobalDefault, resolveVersionAlias, syncResourcesToVersion, promptAgentVersionSelection, getVersionHomePath, } from '../lib/versions.js';
|
|
@@ -28,7 +29,7 @@ Examples:
|
|
|
28
29
|
agents rules list claude@2.1.112
|
|
29
30
|
|
|
30
31
|
# Install rules from your .agents repo to multiple agents
|
|
31
|
-
agents rules add --agents claude,codex,
|
|
32
|
+
agents rules add --agents claude,codex,cursor
|
|
32
33
|
|
|
33
34
|
# Install a shared rule file from GitHub
|
|
34
35
|
agents rules add gh:anthropics/agent-rules --agents codex@0.116.0
|
|
@@ -44,13 +45,13 @@ When to use:
|
|
|
44
45
|
|
|
45
46
|
Project rules & @-imports:
|
|
46
47
|
Project rules live in <repo>/.agents/rules/. On every agent launch, the shim compiles
|
|
47
|
-
them into <repo>/AGENTS.md (with CLAUDE.md
|
|
48
|
+
them into <repo>/AGENTS.md (with CLAUDE.md and .cursorrules symlinked to it).
|
|
48
49
|
A hand-authored AGENTS.md is preserved — the compile pipeline only overwrites files
|
|
49
50
|
it owns (those starting with the auto-compile header). Delete the file to migrate.
|
|
50
51
|
|
|
51
52
|
@path imports inside AGENTS.md/CLAUDE.md are resolved at session start by the agent
|
|
52
53
|
itself, not by agents-cli. Support is per-agent:
|
|
53
|
-
Inlined natively: claude
|
|
54
|
+
Inlined natively: claude
|
|
54
55
|
Literal text: codex, cursor, opencode, copilot, amp, kiro, goose
|
|
55
56
|
|
|
56
57
|
For rules that need to work across all agents, inline the content rather than using
|
|
@@ -161,7 +162,7 @@ Project rules & @-imports:
|
|
|
161
162
|
return;
|
|
162
163
|
}
|
|
163
164
|
console.log(chalk.bold('Installed Rules\n'));
|
|
164
|
-
for (const aid of
|
|
165
|
+
for (const aid of MANAGED_AGENT_IDS) {
|
|
165
166
|
const agent = AGENTS[aid];
|
|
166
167
|
const installedVersions = listInstalledVersions(aid);
|
|
167
168
|
const defaultVer = getGlobalDefault(aid);
|
|
@@ -200,7 +201,7 @@ Project rules & @-imports:
|
|
|
200
201
|
rulesCmd
|
|
201
202
|
.command('add [source]')
|
|
202
203
|
.description('Install rule files from a source (GitHub, local path) or pick from central storage')
|
|
203
|
-
.option('-a, --agents <list>', 'Targets: claude, codex@0.116.0, or
|
|
204
|
+
.option('-a, --agents <list>', 'Targets: claude, codex@0.116.0, or cursor@default')
|
|
204
205
|
.option('--names <list>', 'Rule file names from ~/.agents/rules/ (comma-separated)')
|
|
205
206
|
.option('-y, --yes', 'Skip all prompts and use defaults')
|
|
206
207
|
.addHelpText('after', `
|
|
@@ -212,7 +213,7 @@ Examples:
|
|
|
212
213
|
agents rules add --names AGENTS.md --agents codex@0.116.0
|
|
213
214
|
|
|
214
215
|
# Install all rules from a GitHub repo to multiple agents
|
|
215
|
-
agents rules add gh:team/agent-rules --agents claude,
|
|
216
|
+
agents rules add gh:team/agent-rules --agents claude,codex
|
|
216
217
|
|
|
217
218
|
# Sync a local rules directory to your active agents
|
|
218
219
|
agents rules add ~/projects/my-rules --agents claude@default
|
|
@@ -317,7 +318,7 @@ Examples:
|
|
|
317
318
|
let selectedAgents;
|
|
318
319
|
let versionSelections;
|
|
319
320
|
if (options.agents) {
|
|
320
|
-
const result = await resolveAgentTargetsAutoInstalling(options.agents,
|
|
321
|
+
const result = await resolveAgentTargetsAutoInstalling(options.agents, capableAgents('rules'), { yes: options.yes });
|
|
321
322
|
if (!result) {
|
|
322
323
|
console.log(chalk.gray('Cancelled.'));
|
|
323
324
|
return;
|
|
@@ -326,7 +327,7 @@ Examples:
|
|
|
326
327
|
versionSelections = result.versionSelections;
|
|
327
328
|
}
|
|
328
329
|
else {
|
|
329
|
-
const result = await promptAgentVersionSelection(
|
|
330
|
+
const result = await promptAgentVersionSelection(capableAgents('rules'), {
|
|
330
331
|
skipPrompts: options.yes,
|
|
331
332
|
});
|
|
332
333
|
selectedAgents = result.selectedAgents;
|
|
@@ -376,7 +377,7 @@ Examples:
|
|
|
376
377
|
agents rules view codex --scope project
|
|
377
378
|
|
|
378
379
|
# View rules for a specific installed version
|
|
379
|
-
agents rules view
|
|
380
|
+
agents rules view codex@0.116.0
|
|
380
381
|
`)
|
|
381
382
|
.action(async (agentArg, options) => {
|
|
382
383
|
const cwd = process.cwd();
|
|
@@ -393,7 +394,7 @@ Examples:
|
|
|
393
394
|
requestedVersion = resolveVersionAlias(agentId, parts[1]) ?? null;
|
|
394
395
|
}
|
|
395
396
|
else {
|
|
396
|
-
const choices =
|
|
397
|
+
const choices = MANAGED_AGENT_IDS.filter((id) => instructionsExists(id, 'user', cwd) || instructionsExists(id, 'project', cwd));
|
|
397
398
|
if (choices.length === 0) {
|
|
398
399
|
console.log(chalk.yellow('No rule files found.'));
|
|
399
400
|
return;
|
|
@@ -460,7 +461,7 @@ Examples:
|
|
|
460
461
|
if (!agentArg) {
|
|
461
462
|
// Only list agents that actually have a rules file installed — avoids
|
|
462
463
|
// suggesting agents the user hasn't touched.
|
|
463
|
-
const candidates =
|
|
464
|
+
const candidates = MANAGED_AGENT_IDS.filter((id) => instructionsExists(id));
|
|
464
465
|
requireDestructiveArg({
|
|
465
466
|
argName: 'agent',
|
|
466
467
|
command: 'agents rules remove',
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { type Command } from 'commander';
|
|
9
9
|
import { SSH_TARGET_RE, assertValidSshTarget } from '../lib/ssh-exec.js';
|
|
10
10
|
import { quoteWin32ExecArg } from '../lib/platform/index.js';
|
|
11
|
-
import { type SecretsBundle, type SecretsPolicy } from '../lib/secrets/bundles.js';
|
|
11
|
+
import { type SecretsBackend, type SecretsBundle, type SecretsPolicy } from '../lib/secrets/bundles.js';
|
|
12
12
|
/** Read all available data from stdin synchronously, trimmed. */
|
|
13
13
|
/**
|
|
14
14
|
* Read the raw `.env` text for `import --from <path|->`. A `-` reads the .env
|
|
@@ -109,4 +109,6 @@ export declare function assertNeverPolicyAcknowledged(policy: SecretsPolicy | un
|
|
|
109
109
|
iUnderstand?: boolean;
|
|
110
110
|
interactive: boolean;
|
|
111
111
|
}): 'ok' | 'prompt';
|
|
112
|
+
/** Validate a --backend value, exiting with a clear message on a bad one. */
|
|
113
|
+
export declare function secretsDefaultBackend(): SecretsBackend;
|
|
112
114
|
export { quoteWin32ExecArg };
|