@phnx-labs/agents-cli 1.20.71 → 1.20.73
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 +325 -1
- 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/commands.js +2 -0
- package/dist/commands/doctor.js +8 -4
- 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/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/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 +39 -10
- package/dist/commands/mcp.js +2 -0
- 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/resource-view.d.ts +2 -0
- package/dist/commands/resource-view.js +8 -0
- 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.d.ts +6 -0
- package/dist/commands/sessions.js +26 -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 +3 -1
- package/dist/commands/share.d.ts +8 -1
- package/dist/commands/share.js +122 -37
- package/dist/commands/skills.js +2 -0
- package/dist/commands/ssh.d.ts +9 -0
- package/dist/commands/ssh.js +196 -26
- package/dist/commands/status.js +8 -2
- package/dist/commands/subagents.js +3 -1
- package/dist/commands/sync.js +1 -1
- package/dist/commands/teams.js +11 -2
- package/dist/commands/uninstall.d.ts +11 -0
- package/dist/commands/uninstall.js +158 -0
- package/dist/commands/utils.d.ts +29 -0
- package/dist/commands/utils.js +24 -0
- package/dist/commands/versions.js +120 -11
- package/dist/index.js +83 -12
- package/dist/lib/activity.d.ts +124 -0
- package/dist/lib/activity.js +542 -0
- package/dist/lib/agents.js +16 -5
- 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/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 +14 -5
- package/dist/lib/daemon.js +52 -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/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/logs.d.ts +12 -0
- package/dist/lib/hosts/logs.js +18 -0
- 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/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 +291 -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 +36 -0
- package/dist/lib/shims.js +120 -16
- 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 +3 -0
- 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 +73 -0
- package/dist/lib/uninstall.d.ts +84 -0
- package/dist/lib/uninstall.js +347 -0
- package/dist/lib/usage.d.ts +13 -1
- package/dist/lib/usage.js +32 -14
- package/dist/lib/version.d.ts +40 -0
- package/dist/lib/version.js +94 -16
- package/dist/lib/versions.d.ts +27 -0
- package/dist/lib/versions.js +248 -79
- 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
|
@@ -14,6 +14,14 @@ import { isInteractiveTerminal, isPromptCancelled, printWithPager } from './util
|
|
|
14
14
|
import { terminalWidth, truncateToWidth, padToWidth, stringWidth, stripAnsi } from '../lib/session/width.js';
|
|
15
15
|
/** Display a resource list: interactive picker in TTY mode, plain table otherwise. */
|
|
16
16
|
export async function showResourceList(opts) {
|
|
17
|
+
if (opts.json) {
|
|
18
|
+
// Strip the non-serializable buildDetail thunk; emit the row metadata plus
|
|
19
|
+
// each resource's per-agent-version sync targets. One shared JSON contract
|
|
20
|
+
// for every resource `list` built on this helper.
|
|
21
|
+
const rows = opts.rows.map(({ buildDetail, ...row }) => row);
|
|
22
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
17
25
|
if (opts.rows.length === 0) {
|
|
18
26
|
console.log(chalk.gray(opts.emptyMessage));
|
|
19
27
|
return;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents resources` — show the merged DotAgents resource surface.
|
|
3
|
+
*/
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { listResources } from '../lib/resources.js';
|
|
7
|
+
import { terminalWidth, truncateToWidth, stringWidth } from '../lib/session/width.js';
|
|
8
|
+
const DRILLABLE_KINDS = [
|
|
9
|
+
'skills',
|
|
10
|
+
'commands',
|
|
11
|
+
'mcp',
|
|
12
|
+
'hooks',
|
|
13
|
+
'rules',
|
|
14
|
+
'plugins',
|
|
15
|
+
'workflows',
|
|
16
|
+
'subagents',
|
|
17
|
+
];
|
|
18
|
+
const KIND_LABELS = {
|
|
19
|
+
skills: 'Skills',
|
|
20
|
+
commands: 'Commands',
|
|
21
|
+
mcp: 'MCP',
|
|
22
|
+
hooks: 'Hooks',
|
|
23
|
+
rules: 'Rules',
|
|
24
|
+
plugins: 'Plugins',
|
|
25
|
+
workflows: 'Workflows',
|
|
26
|
+
subagents: 'Subagents',
|
|
27
|
+
};
|
|
28
|
+
export function registerResourcesCommand(program) {
|
|
29
|
+
program
|
|
30
|
+
.command('resources')
|
|
31
|
+
.description('Show the merged DotAgents resources resolved across project, user, system, and extras')
|
|
32
|
+
.option('--merged', 'Show the merged first-wins resource surface (default)')
|
|
33
|
+
.addHelpText('after', `
|
|
34
|
+
Examples:
|
|
35
|
+
agents resources
|
|
36
|
+
agents resources --merged
|
|
37
|
+
`)
|
|
38
|
+
.action((options) => {
|
|
39
|
+
void options;
|
|
40
|
+
renderMergedResources();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function renderMergedResources() {
|
|
44
|
+
const groups = DRILLABLE_KINDS.map((kind) => ({
|
|
45
|
+
kind,
|
|
46
|
+
rows: listResources(kind),
|
|
47
|
+
}));
|
|
48
|
+
const total = groups.reduce((sum, group) => sum + group.rows.length, 0);
|
|
49
|
+
if (total === 0) {
|
|
50
|
+
console.log(chalk.gray('No merged resources found.'));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
console.log(chalk.bold(`Resources (${total} merged)`));
|
|
54
|
+
for (const group of groups) {
|
|
55
|
+
console.log();
|
|
56
|
+
console.log(chalk.bold(`${KIND_LABELS[group.kind]} (${group.rows.length})`));
|
|
57
|
+
if (group.rows.length === 0) {
|
|
58
|
+
console.log(chalk.gray(' none'));
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
for (const line of renderResourceRows(group.rows))
|
|
62
|
+
console.log(line);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function renderResourceRows(rows) {
|
|
66
|
+
const nameW = Math.min(28, Math.max('Name'.length, ...rows.map((row) => stringWidth(row.name))));
|
|
67
|
+
const layerW = Math.min(16, Math.max('Layer'.length, ...rows.map((row) => stringWidth(row.source))));
|
|
68
|
+
const prefixW = 2 + nameW + 2 + layerW + 2;
|
|
69
|
+
const pathW = Math.max(12, terminalWidth() - prefixW);
|
|
70
|
+
const lines = [
|
|
71
|
+
` ${chalk.bold(pad(row('Name', nameW), nameW))} ${chalk.bold(pad(row('Layer', layerW), layerW))} ${chalk.bold('Path')}`,
|
|
72
|
+
` ${chalk.gray(`${'-'.repeat(nameW)} ${'-'.repeat(layerW)} ${'-'.repeat(Math.min(pathW, 40))}`)}`,
|
|
73
|
+
];
|
|
74
|
+
for (const resource of rows) {
|
|
75
|
+
lines.push(` ${chalk.cyan(pad(truncateToWidth(resource.name, nameW), nameW))} ${pad(resource.source, layerW)} ${chalk.gray(truncateToWidth(formatPath(resource.path), pathW))}`);
|
|
76
|
+
}
|
|
77
|
+
return lines;
|
|
78
|
+
}
|
|
79
|
+
function row(value, width) {
|
|
80
|
+
return truncateToWidth(value, width);
|
|
81
|
+
}
|
|
82
|
+
function pad(value, width) {
|
|
83
|
+
return value + ' '.repeat(Math.max(0, width - stringWidth(value)));
|
|
84
|
+
}
|
|
85
|
+
function formatPath(value) {
|
|
86
|
+
const home = process.env.HOME;
|
|
87
|
+
if (home) {
|
|
88
|
+
const rel = path.relative(home, value);
|
|
89
|
+
if (rel === '')
|
|
90
|
+
return '~';
|
|
91
|
+
if (!rel.startsWith('..') && !path.isAbsolute(rel))
|
|
92
|
+
return `~/${rel}`;
|
|
93
|
+
}
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
* Also exposes scheduler lifecycle controls (start/stop/status/logs).
|
|
7
7
|
*/
|
|
8
8
|
import type { Command } from 'commander';
|
|
9
|
+
import type { RunMeta } from '../lib/routines.js';
|
|
9
10
|
/**
|
|
10
11
|
* Human-friendly wall-clock a run took (e.g. " · 3 min", " · 45 sec"), or ""
|
|
11
12
|
* when it hasn't completed or timestamps are unparseable. Leading separator lets
|
|
12
13
|
* callers drop it straight into a status line.
|
|
13
14
|
*/
|
|
14
15
|
export declare function formatRunDuration(startedAt: string, completedAt: string | null): string;
|
|
16
|
+
export declare function buildRunsJson(runs: RunMeta[]): Record<string, unknown>[];
|
|
15
17
|
/** Register the `agents routines` command tree. */
|
|
16
18
|
export declare function registerRoutinesCommands(program: Command): void;
|
|
@@ -58,7 +58,11 @@ function fireConditionLabel(job) {
|
|
|
58
58
|
const scope = job.trigger.repo
|
|
59
59
|
? ` (${job.trigger.repo}${job.trigger.branch ? `@${job.trigger.branch}` : ''})`
|
|
60
60
|
: '';
|
|
61
|
-
|
|
61
|
+
const filters = [
|
|
62
|
+
job.trigger.action ? `action=${job.trigger.action}` : null,
|
|
63
|
+
job.trigger.label ? `label=${job.trigger.label}` : null,
|
|
64
|
+
].filter(Boolean).join(', ');
|
|
65
|
+
return `on github:${job.trigger.event}${scope}${filters ? ` (${filters})` : ''}`;
|
|
62
66
|
}
|
|
63
67
|
const filters = [
|
|
64
68
|
job.trigger.action ? `action=${job.trigger.action}` : null,
|
|
@@ -83,6 +87,10 @@ function parseRoutineTrigger(options) {
|
|
|
83
87
|
trigger.repo = options.repo;
|
|
84
88
|
if (typeof options.branch === 'string')
|
|
85
89
|
trigger.branch = options.branch;
|
|
90
|
+
if (typeof options.action === 'string')
|
|
91
|
+
trigger.action = options.action;
|
|
92
|
+
if (typeof options.label === 'string')
|
|
93
|
+
trigger.label = options.label;
|
|
86
94
|
return trigger;
|
|
87
95
|
}
|
|
88
96
|
if (sourceMaybe === 'linear') {
|
|
@@ -97,22 +105,47 @@ function parseRoutineTrigger(options) {
|
|
|
97
105
|
}
|
|
98
106
|
throw new Error('--on source must be github or linear');
|
|
99
107
|
}
|
|
100
|
-
/**
|
|
101
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Start or reload the background scheduler so newly-added jobs fire on time.
|
|
110
|
+
* `quiet` suppresses human status lines for JSON callers.
|
|
111
|
+
*/
|
|
112
|
+
function ensureSchedulerRunning(opts = {}) {
|
|
113
|
+
const log = opts.stderr ? console.error : console.log;
|
|
102
114
|
if (isDaemonRunning()) {
|
|
103
115
|
signalDaemonReload();
|
|
104
|
-
|
|
116
|
+
if (!opts.quiet)
|
|
117
|
+
log(chalk.gray('Scheduler reloaded'));
|
|
105
118
|
return;
|
|
106
119
|
}
|
|
107
120
|
const result = startDaemon();
|
|
121
|
+
if (opts.quiet)
|
|
122
|
+
return;
|
|
108
123
|
if (result.pid) {
|
|
109
|
-
|
|
110
|
-
|
|
124
|
+
log(chalk.green(`Scheduler started (PID: ${result.pid}). It will run in the background and fire routines on schedule.`));
|
|
125
|
+
log(chalk.gray(`Stop anytime with: agents routines stop`));
|
|
111
126
|
}
|
|
112
127
|
else {
|
|
113
|
-
|
|
128
|
+
log(chalk.yellow('Could not start the scheduler. Start it manually with: agents routines start'));
|
|
114
129
|
}
|
|
115
130
|
}
|
|
131
|
+
function writeJson(payload) {
|
|
132
|
+
process.stdout.write(JSON.stringify(payload) + '\n');
|
|
133
|
+
}
|
|
134
|
+
function runMetaJson(run) {
|
|
135
|
+
return {
|
|
136
|
+
jobId: run.jobName,
|
|
137
|
+
jobName: run.jobName,
|
|
138
|
+
runId: run.runId,
|
|
139
|
+
status: run.status,
|
|
140
|
+
startedAt: run.startedAt,
|
|
141
|
+
completedAt: run.completedAt,
|
|
142
|
+
exitCode: run.exitCode,
|
|
143
|
+
errorMessage: run.errorMessage ?? null,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
export function buildRunsJson(runs) {
|
|
147
|
+
return runs.map(runMetaJson);
|
|
148
|
+
}
|
|
116
149
|
/** Detect Ctrl+C or premature stream close during an interactive prompt. */
|
|
117
150
|
function isPromptCancelled(err) {
|
|
118
151
|
return (err instanceof Error &&
|
|
@@ -296,6 +329,8 @@ export function registerRoutinesCommands(program) {
|
|
|
296
329
|
nextRun: nextRun ? nextRun.toISOString() : null,
|
|
297
330
|
nextRunHuman: humanizeNextRun(nextRun ?? null, nowJson, job.timezone),
|
|
298
331
|
lastStatus: latestRun?.status ?? null,
|
|
332
|
+
exitCode: latestRun?.exitCode ?? null,
|
|
333
|
+
failureReason: latestRun?.errorMessage ?? null,
|
|
299
334
|
lastRunStartedAt: latestRun?.startedAt ?? null,
|
|
300
335
|
lastRunCompletedAt: latestRun?.completedAt ?? null,
|
|
301
336
|
};
|
|
@@ -394,25 +429,26 @@ export function registerRoutinesCommands(program) {
|
|
|
394
429
|
.option('--on <source:event>', 'Webhook trigger instead of/in addition to a schedule: github:pull_request or linear:Issue')
|
|
395
430
|
.option('--repo <owner/name>', 'GitHub repo filter for --on github:<event>')
|
|
396
431
|
.option('--branch <name>', 'GitHub branch filter for --on github:<event>')
|
|
397
|
-
.option('--action <name>', '
|
|
432
|
+
.option('--action <name>', 'Webhook action filter for --on triggers (GitHub: labeled/opened; Linear: update)')
|
|
398
433
|
.option('--team-key <key>', 'Linear team key filter for --on linear:<event> (e.g. RUSH)')
|
|
399
|
-
.option('--label <name>', '
|
|
434
|
+
.option('--label <name>', 'Label filter for --on triggers (GitHub label name or Linear issue label)')
|
|
400
435
|
.option('--end-at <iso>', 'Stop firing on or after this ISO 8601 timestamp (e.g., "2026-12-31T23:59:00Z"); routine auto-disables.')
|
|
401
436
|
.option('--disabled', 'Create the routine but keep it paused (enable later with resume)')
|
|
402
437
|
.option('--resume <sessionId>', 'At fire time, resume this existing session id (via `agents run <agent> --resume`) instead of starting fresh — the actual session reopens with full context and the prompt becomes its next turn. Powers self-scheduled wake-ups (e.g. /hibernate). Requires --agent claude or codex; runs un-sandboxed (the session store lives in the real home, not the job overlay).')
|
|
438
|
+
.option('--json', 'Emit machine-readable JSON with the created routine id and status')
|
|
403
439
|
.action(async (nameOrPath, options) => {
|
|
404
440
|
// Check if inline mode (has flags) or file mode
|
|
405
441
|
const hasInlineFlags = options.schedule || options.agent || options.workflow || options.command || options.prompt || options.at || options.on;
|
|
406
442
|
if (hasInlineFlags) {
|
|
407
443
|
// Inline mode: create job from flags
|
|
408
444
|
if (!nameOrPath) {
|
|
409
|
-
console.
|
|
410
|
-
console.
|
|
445
|
+
console.error(chalk.red('Job name is required'));
|
|
446
|
+
console.error(chalk.gray('Usage: agents routines add <name> --schedule "..." --agent <agent> --prompt "..."'));
|
|
411
447
|
process.exit(1);
|
|
412
448
|
}
|
|
413
449
|
// Validate mutually exclusive --agent / --workflow / --command
|
|
414
450
|
if ([options.agent, options.workflow, options.command].filter(Boolean).length > 1) {
|
|
415
|
-
console.
|
|
451
|
+
console.error(chalk.red('--agent, --workflow, and --command are mutually exclusive; specify exactly one'));
|
|
416
452
|
process.exit(1);
|
|
417
453
|
}
|
|
418
454
|
let schedule = options.schedule;
|
|
@@ -422,31 +458,31 @@ export function registerRoutinesCommands(program) {
|
|
|
422
458
|
trigger = parseRoutineTrigger(options);
|
|
423
459
|
}
|
|
424
460
|
catch (err) {
|
|
425
|
-
console.
|
|
461
|
+
console.error(chalk.red(err.message));
|
|
426
462
|
process.exit(1);
|
|
427
463
|
}
|
|
428
464
|
// Handle --at for one-shot jobs
|
|
429
465
|
if (options.at) {
|
|
430
466
|
const parsed = parseAtTime(options.at);
|
|
431
467
|
if (!parsed) {
|
|
432
|
-
console.
|
|
433
|
-
console.
|
|
468
|
+
console.error(chalk.red(`Invalid --at format: ${options.at}`));
|
|
469
|
+
console.error(chalk.gray('Supported formats: "14:30" or "2026-02-24 09:00"'));
|
|
434
470
|
process.exit(1);
|
|
435
471
|
}
|
|
436
472
|
schedule = parsed.schedule;
|
|
437
473
|
runOnce = parsed.runOnce;
|
|
438
474
|
}
|
|
439
475
|
if (!schedule && !trigger) {
|
|
440
|
-
console.
|
|
476
|
+
console.error(chalk.red('Schedule or trigger is required (use --schedule, --at, or --on)'));
|
|
441
477
|
process.exit(1);
|
|
442
478
|
}
|
|
443
479
|
if (!options.agent && !options.workflow && !options.command) {
|
|
444
|
-
console.
|
|
480
|
+
console.error(chalk.red('An agent, workflow, or command is required (use --agent, --workflow, or --command)'));
|
|
445
481
|
process.exit(1);
|
|
446
482
|
}
|
|
447
483
|
// Command routines run a plain shell and take no prompt; agent/workflow routines require one.
|
|
448
484
|
if (!options.command && !options.prompt) {
|
|
449
|
-
console.
|
|
485
|
+
console.error(chalk.red('Prompt is required (use --prompt)'));
|
|
450
486
|
process.exit(1);
|
|
451
487
|
}
|
|
452
488
|
// Parse and validate --devices against the fleet registry.
|
|
@@ -459,7 +495,7 @@ export function registerRoutinesCommands(program) {
|
|
|
459
495
|
// this machine unless the user chose an explicit eligibility set.
|
|
460
496
|
if (options.runOn && !devices) {
|
|
461
497
|
devices = [machineId()];
|
|
462
|
-
console.
|
|
498
|
+
console.error(chalk.gray(`--run-on set with no --devices: pinned firing to this machine (${devices[0]}).`));
|
|
463
499
|
}
|
|
464
500
|
const config = {
|
|
465
501
|
name: nameOrPath,
|
|
@@ -483,13 +519,28 @@ export function registerRoutinesCommands(program) {
|
|
|
483
519
|
};
|
|
484
520
|
const errors = validateJob(config);
|
|
485
521
|
if (errors.length > 0) {
|
|
486
|
-
console.
|
|
522
|
+
console.error(chalk.red('Validation errors:'));
|
|
487
523
|
for (const err of errors) {
|
|
488
|
-
console.
|
|
524
|
+
console.error(chalk.red(` - ${err}`));
|
|
489
525
|
}
|
|
490
526
|
process.exit(1);
|
|
491
527
|
}
|
|
492
528
|
writeJob(config);
|
|
529
|
+
if (options.json) {
|
|
530
|
+
writeJson({
|
|
531
|
+
ok: true,
|
|
532
|
+
added: nameOrPath,
|
|
533
|
+
job: config,
|
|
534
|
+
jobId: config.name,
|
|
535
|
+
name: config.name,
|
|
536
|
+
status: 'added',
|
|
537
|
+
enabled: config.enabled,
|
|
538
|
+
schedule: config.schedule ?? null,
|
|
539
|
+
trigger: config.trigger ?? null,
|
|
540
|
+
});
|
|
541
|
+
ensureSchedulerRunning({ quiet: true });
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
493
544
|
console.log(chalk.green(`Job '${nameOrPath}' added`));
|
|
494
545
|
if (runOnce) {
|
|
495
546
|
console.log(chalk.gray(`One-shot job scheduled for: ${options.at}`));
|
|
@@ -499,14 +550,14 @@ export function registerRoutinesCommands(program) {
|
|
|
499
550
|
else {
|
|
500
551
|
// File mode: load from YAML file
|
|
501
552
|
if (!nameOrPath) {
|
|
502
|
-
console.
|
|
503
|
-
console.
|
|
504
|
-
console.
|
|
553
|
+
console.error(chalk.red('File path or job name with flags is required'));
|
|
554
|
+
console.error(chalk.gray('Usage: agents routines add <path-to-job.yml>'));
|
|
555
|
+
console.error(chalk.gray(' or: agents routines add <name> --schedule "..." --agent <agent> --prompt "..."'));
|
|
505
556
|
process.exit(1);
|
|
506
557
|
}
|
|
507
558
|
const resolved = path.resolve(nameOrPath);
|
|
508
559
|
if (!fs.existsSync(resolved)) {
|
|
509
|
-
console.
|
|
560
|
+
console.error(chalk.red(`File not found: ${resolved}`));
|
|
510
561
|
process.exit(1);
|
|
511
562
|
}
|
|
512
563
|
const content = fs.readFileSync(resolved, 'utf-8');
|
|
@@ -515,16 +566,16 @@ export function registerRoutinesCommands(program) {
|
|
|
515
566
|
parsed = yaml.parse(content);
|
|
516
567
|
}
|
|
517
568
|
catch (err) {
|
|
518
|
-
console.
|
|
569
|
+
console.error(chalk.red(`Invalid YAML: ${err.message}`));
|
|
519
570
|
process.exit(1);
|
|
520
571
|
}
|
|
521
572
|
const name = parsed.name || path.basename(resolved).replace(/\.ya?ml$/, '');
|
|
522
573
|
parsed.name = name;
|
|
523
574
|
const errors = validateJob(parsed);
|
|
524
575
|
if (errors.length > 0) {
|
|
525
|
-
console.
|
|
576
|
+
console.error(chalk.red('Validation errors:'));
|
|
526
577
|
for (const err of errors) {
|
|
527
|
-
console.
|
|
578
|
+
console.error(chalk.red(` - ${err}`));
|
|
528
579
|
}
|
|
529
580
|
process.exit(1);
|
|
530
581
|
}
|
|
@@ -539,9 +590,24 @@ export function registerRoutinesCommands(program) {
|
|
|
539
590
|
// with no eligibility pin would fire from every daemon in the fleet.
|
|
540
591
|
if (config.host && (!config.devices || config.devices.length === 0)) {
|
|
541
592
|
config.devices = [machineId()];
|
|
542
|
-
console.
|
|
593
|
+
console.error(chalk.gray(`host: set with no devices pin: pinned firing to this machine (${config.devices[0]}).`));
|
|
543
594
|
}
|
|
544
595
|
writeJob(config);
|
|
596
|
+
if (options.json) {
|
|
597
|
+
writeJson({
|
|
598
|
+
ok: true,
|
|
599
|
+
added: name,
|
|
600
|
+
job: config,
|
|
601
|
+
jobId: config.name,
|
|
602
|
+
name: config.name,
|
|
603
|
+
status: 'added',
|
|
604
|
+
enabled: config.enabled,
|
|
605
|
+
schedule: config.schedule ?? null,
|
|
606
|
+
trigger: config.trigger ?? null,
|
|
607
|
+
});
|
|
608
|
+
ensureSchedulerRunning({ quiet: true });
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
545
611
|
console.log(chalk.green(`Job '${name}' added`));
|
|
546
612
|
ensureSchedulerRunning();
|
|
547
613
|
}
|
|
@@ -644,13 +710,22 @@ export function registerRoutinesCommands(program) {
|
|
|
644
710
|
routinesCmd
|
|
645
711
|
.command('runs [name]')
|
|
646
712
|
.description('See execution history: run IDs, completion status, and start times (up to last 10 runs)')
|
|
647
|
-
.
|
|
713
|
+
.option('--json', 'Emit machine-readable JSON with run ids and statuses')
|
|
714
|
+
.action(async (name, options) => {
|
|
648
715
|
if (!name) {
|
|
649
716
|
name = await pickJob('Select job to view runs', undefined, ['agents routines runs <name>']) ?? undefined;
|
|
650
717
|
if (!name)
|
|
651
718
|
return;
|
|
652
719
|
}
|
|
653
720
|
const runs = listRuns(name);
|
|
721
|
+
if (options.json) {
|
|
722
|
+
writeJson({
|
|
723
|
+
jobId: name,
|
|
724
|
+
name,
|
|
725
|
+
runs: buildRunsJson(runs.slice(-10)),
|
|
726
|
+
});
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
654
729
|
if (runs.length === 0) {
|
|
655
730
|
console.log(chalk.yellow(`No runs found for job '${name}'`));
|
|
656
731
|
return;
|
|
@@ -668,7 +743,8 @@ export function registerRoutinesCommands(program) {
|
|
|
668
743
|
routinesCmd
|
|
669
744
|
.command('run [name]')
|
|
670
745
|
.description('Execute a routine right now in the foreground. Ignores the schedule; useful for testing before enabling.')
|
|
671
|
-
.
|
|
746
|
+
.option('--json', 'Emit machine-readable JSON with the run id and status')
|
|
747
|
+
.action(async (name, options) => {
|
|
672
748
|
if (!name) {
|
|
673
749
|
name = await pickJob('Select job to run', undefined, ['agents routines run <name>']) ?? undefined;
|
|
674
750
|
if (!name)
|
|
@@ -682,20 +758,42 @@ export function registerRoutinesCommands(program) {
|
|
|
682
758
|
// the trusted user layer.
|
|
683
759
|
const job = readJob(name);
|
|
684
760
|
if (!job) {
|
|
685
|
-
|
|
761
|
+
if (options.json) {
|
|
762
|
+
writeJson({ error: `Job '${name}' not found` });
|
|
763
|
+
process.exit(1);
|
|
764
|
+
}
|
|
765
|
+
console.error(chalk.red(`Job '${name}' not found`));
|
|
686
766
|
process.exit(1);
|
|
687
767
|
}
|
|
688
768
|
const eligibility = checkJobDeviceEligibility(job);
|
|
689
769
|
if (eligibility) {
|
|
690
|
-
|
|
691
|
-
|
|
770
|
+
if (options.json) {
|
|
771
|
+
writeJson({ error: eligibility.message, hint: eligibility.suggestion });
|
|
772
|
+
process.exit(1);
|
|
773
|
+
}
|
|
774
|
+
console.error(chalk.red(eligibility.message));
|
|
775
|
+
console.error(chalk.gray(` ${eligibility.suggestion}`));
|
|
692
776
|
process.exit(1);
|
|
693
777
|
}
|
|
694
778
|
const runLabel = job.command ? 'command' : job.workflow ? `workflow: ${job.workflow}` : `agent: ${job.agent}`;
|
|
695
|
-
|
|
696
|
-
const spinner = ora('Executing...').start();
|
|
779
|
+
// A spinner writes to stderr but its human framing is noise for a JSON consumer.
|
|
780
|
+
const spinner = options.json ? null : ora('Executing...').start();
|
|
781
|
+
if (!options.json)
|
|
782
|
+
console.log(chalk.bold(`Running job '${name}' (${runLabel}, mode: ${job.mode})\n`));
|
|
697
783
|
try {
|
|
698
784
|
const result = await executeJob(job);
|
|
785
|
+
const logPath = `${getRunDir(name, result.meta.runId)}/stdout.log`;
|
|
786
|
+
if (options.json) {
|
|
787
|
+
writeJson({
|
|
788
|
+
ok: true,
|
|
789
|
+
job: name,
|
|
790
|
+
logDir: getRunDir(name, result.meta.runId),
|
|
791
|
+
...runMetaJson(result.meta),
|
|
792
|
+
logPath,
|
|
793
|
+
reportPath: result.reportPath ?? null,
|
|
794
|
+
});
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
699
797
|
if (result.meta.status === 'completed') {
|
|
700
798
|
spinner.succeed(`Job completed (exit code: ${result.meta.exitCode})`);
|
|
701
799
|
}
|
|
@@ -706,13 +804,17 @@ export function registerRoutinesCommands(program) {
|
|
|
706
804
|
spinner.fail(`Job failed (exit code: ${result.meta.exitCode})`);
|
|
707
805
|
}
|
|
708
806
|
console.log(chalk.gray(` Run: ${result.meta.runId}`));
|
|
709
|
-
console.log(chalk.gray(` Log: ${
|
|
807
|
+
console.log(chalk.gray(` Log: ${logPath}`));
|
|
710
808
|
if (result.reportPath) {
|
|
711
809
|
console.log(chalk.bold('\nReport:\n'));
|
|
712
810
|
console.log(fs.readFileSync(result.reportPath, 'utf-8'));
|
|
713
811
|
}
|
|
714
812
|
}
|
|
715
813
|
catch (err) {
|
|
814
|
+
if (options.json) {
|
|
815
|
+
writeJson({ error: err.message });
|
|
816
|
+
process.exit(1);
|
|
817
|
+
}
|
|
716
818
|
spinner.fail('Execution failed');
|
|
717
819
|
console.error(chalk.red(err.message));
|
|
718
820
|
process.exit(1);
|