@phnx-labs/agents-cli 1.19.2 → 1.20.3
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 +140 -0
- package/README.md +72 -12
- package/dist/browser.js +0 -0
- package/dist/commands/browser.js +88 -16
- package/dist/commands/cli.d.ts +14 -0
- package/dist/commands/cli.js +244 -0
- package/dist/commands/cloud.js +1 -1
- package/dist/commands/commands.js +27 -10
- package/dist/commands/computer.js +18 -1
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.js +2 -2
- package/dist/commands/exec.js +38 -18
- package/dist/commands/factory.d.ts +3 -14
- package/dist/commands/factory.js +3 -3
- package/dist/commands/feedback.d.ts +7 -0
- package/dist/commands/feedback.js +89 -0
- package/dist/commands/helper.d.ts +12 -0
- package/dist/commands/helper.js +87 -0
- package/dist/commands/hooks.js +89 -10
- package/dist/commands/mcp.js +166 -10
- package/dist/commands/packages.js +196 -27
- package/dist/commands/permissions.js +21 -6
- package/dist/commands/plugins.js +11 -4
- package/dist/commands/profiles.d.ts +8 -0
- package/dist/commands/profiles.js +118 -5
- package/dist/commands/prune.js +39 -160
- package/dist/commands/pull.js +58 -5
- package/dist/commands/routines.js +107 -14
- package/dist/commands/rules.js +8 -4
- package/dist/commands/secrets-migrate.d.ts +24 -0
- package/dist/commands/secrets-migrate.js +198 -0
- package/dist/commands/secrets-sync.d.ts +11 -0
- package/dist/commands/secrets-sync.js +155 -0
- package/dist/commands/secrets.js +79 -46
- package/dist/commands/sessions.d.ts +28 -0
- package/dist/commands/sessions.js +98 -33
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +37 -28
- package/dist/commands/skills.js +25 -8
- package/dist/commands/subagents.js +69 -49
- package/dist/commands/teams.js +61 -10
- package/dist/commands/utils.d.ts +33 -0
- package/dist/commands/utils.js +139 -0
- package/dist/commands/versions.d.ts +4 -3
- package/dist/commands/versions.js +134 -130
- package/dist/commands/view.d.ts +6 -0
- package/dist/commands/view.js +175 -19
- package/dist/commands/workflows.js +29 -6
- package/dist/computer.js +0 -0
- package/dist/index.js +38 -6
- package/dist/lib/acp/client.js +6 -1
- package/dist/lib/acp/harnesses.js +8 -0
- package/dist/lib/agents.d.ts +4 -0
- package/dist/lib/agents.js +125 -34
- package/dist/lib/auto-pull-worker.js +18 -1
- package/dist/lib/browser/cdp.d.ts +8 -1
- package/dist/lib/browser/cdp.js +40 -3
- package/dist/lib/browser/chrome.d.ts +13 -0
- package/dist/lib/browser/chrome.js +46 -3
- package/dist/lib/browser/domain-skills.d.ts +51 -0
- package/dist/lib/browser/domain-skills.js +157 -0
- package/dist/lib/browser/drivers/local.js +45 -4
- package/dist/lib/browser/drivers/ssh.js +2 -2
- package/dist/lib/browser/ipc.d.ts +8 -1
- package/dist/lib/browser/ipc.js +37 -28
- package/dist/lib/browser/profiles.d.ts +16 -3
- package/dist/lib/browser/profiles.js +44 -4
- package/dist/lib/browser/service.d.ts +3 -0
- package/dist/lib/browser/service.js +40 -5
- package/dist/lib/browser/types.d.ts +11 -4
- package/dist/lib/cli-resources.d.ts +137 -0
- package/dist/lib/cli-resources.js +477 -0
- package/dist/lib/cloud/factory.d.ts +1 -1
- package/dist/lib/cloud/factory.js +1 -1
- package/dist/lib/cloud/rush.js +5 -5
- package/dist/lib/command-skills.js +0 -2
- package/dist/lib/computer-rpc.d.ts +3 -0
- package/dist/lib/computer-rpc.js +53 -0
- package/dist/lib/daemon.js +20 -0
- package/dist/lib/events.d.ts +16 -2
- package/dist/lib/events.js +33 -2
- package/dist/lib/exec.d.ts +42 -13
- package/dist/lib/exec.js +127 -33
- package/dist/lib/help.js +11 -5
- package/dist/lib/hooks/cache.d.ts +38 -0
- package/dist/lib/hooks/cache.js +242 -0
- package/dist/lib/hooks/profile.d.ts +33 -0
- package/dist/lib/hooks/profile.js +129 -0
- package/dist/lib/hooks.d.ts +0 -10
- package/dist/lib/hooks.js +246 -11
- package/dist/lib/mcp.d.ts +15 -0
- package/dist/lib/mcp.js +46 -0
- package/dist/lib/migrate.js +1 -1
- package/dist/lib/overdue.d.ts +26 -0
- package/dist/lib/overdue.js +101 -0
- package/dist/lib/permissions.d.ts +13 -0
- package/dist/lib/permissions.js +55 -1
- package/dist/lib/plugin-marketplace.js +1 -1
- package/dist/lib/plugins.js +15 -1
- package/dist/lib/profiles-presets.d.ts +26 -0
- package/dist/lib/profiles-presets.js +216 -0
- package/dist/lib/profiles.d.ts +34 -0
- package/dist/lib/profiles.js +112 -1
- package/dist/lib/resources/mcp.js +37 -0
- package/dist/lib/resources.d.ts +1 -1
- package/dist/lib/rotate.js +10 -4
- package/dist/lib/routines-format.d.ts +47 -0
- package/dist/lib/routines-format.js +194 -0
- package/dist/lib/routines.d.ts +8 -2
- package/dist/lib/routines.js +34 -14
- package/dist/lib/runner.js +83 -15
- package/dist/lib/scheduler.js +8 -1
- 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/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -9
- package/dist/lib/secrets/bundles.d.ts +34 -17
- package/dist/lib/secrets/bundles.js +210 -36
- package/dist/lib/secrets/index.d.ts +49 -30
- package/dist/lib/secrets/index.js +126 -115
- package/dist/lib/secrets/install-helper.d.ts +45 -0
- package/dist/lib/secrets/install-helper.js +165 -0
- package/dist/lib/secrets/linux.js +4 -4
- package/dist/lib/secrets/sync.d.ts +56 -0
- package/dist/lib/secrets/sync.js +180 -0
- package/dist/lib/session/active.d.ts +8 -0
- package/dist/lib/session/active.js +3 -2
- package/dist/lib/session/db.d.ts +0 -4
- package/dist/lib/session/db.js +0 -26
- package/dist/lib/session/parse.d.ts +1 -0
- package/dist/lib/session/parse.js +44 -0
- package/dist/lib/session/render.js +4 -4
- package/dist/lib/session/types.d.ts +2 -2
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/shims.d.ts +5 -2
- package/dist/lib/shims.js +70 -38
- package/dist/lib/state.d.ts +14 -2
- package/dist/lib/state.js +51 -20
- package/dist/lib/teams/agents.d.ts +5 -4
- package/dist/lib/teams/agents.js +48 -22
- package/dist/lib/teams/api.d.ts +2 -1
- package/dist/lib/teams/api.js +4 -3
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/teams/parsers.js +153 -3
- package/dist/lib/teams/summarizer.js +18 -2
- package/dist/lib/teams/worktree.js +14 -3
- package/dist/lib/types.d.ts +63 -4
- package/dist/lib/types.js +8 -3
- package/dist/lib/usage.d.ts +27 -2
- package/dist/lib/usage.js +100 -17
- package/dist/lib/versions.d.ts +45 -3
- package/dist/lib/versions.js +455 -60
- package/package.json +15 -14
- package/scripts/install-helper.js +97 -0
- package/scripts/postinstall.js +16 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
- package/npm-shrinkwrap.json +0 -3162
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure display helpers for `agents routines list`.
|
|
3
|
+
*
|
|
4
|
+
* No external dependencies. All functions are pure (no I/O, no side effects).
|
|
5
|
+
*/
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// humanizeCron
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
const DAY_NAMES = ['Sundays', 'Mondays', 'Tuesdays', 'Wednesdays', 'Thursdays', 'Fridays', 'Saturdays'];
|
|
10
|
+
/**
|
|
11
|
+
* Convert a cron expression to a human-readable phrase.
|
|
12
|
+
*
|
|
13
|
+
* Handles the common patterns. For anything unrecognized, returns the raw
|
|
14
|
+
* expression so the user still sees something useful. NEVER throws.
|
|
15
|
+
*/
|
|
16
|
+
export function humanizeCron(expr, _tz) {
|
|
17
|
+
try {
|
|
18
|
+
const parts = expr.trim().split(/\s+/);
|
|
19
|
+
if (parts.length !== 5)
|
|
20
|
+
return expr;
|
|
21
|
+
const [minute, hour, dom, month, dow] = parts;
|
|
22
|
+
// every minute: * * * * *
|
|
23
|
+
if (minute === '*' && hour === '*' && dom === '*' && month === '*' && dow === '*') {
|
|
24
|
+
return 'every minute';
|
|
25
|
+
}
|
|
26
|
+
// every N minutes: */N * * * *
|
|
27
|
+
const everyMinMatch = minute.match(/^\*\/(\d+)$/);
|
|
28
|
+
if (everyMinMatch && hour === '*' && dom === '*' && month === '*' && dow === '*') {
|
|
29
|
+
const n = parseInt(everyMinMatch[1], 10);
|
|
30
|
+
return `every ${n} minute${n === 1 ? '' : 's'}`;
|
|
31
|
+
}
|
|
32
|
+
// every N hours: 0 */N * * *
|
|
33
|
+
const everyHourMatch = hour.match(/^\*\/(\d+)$/);
|
|
34
|
+
if (everyHourMatch && minute === '0' && dom === '*' && month === '*' && dow === '*') {
|
|
35
|
+
const n = parseInt(everyHourMatch[1], 10);
|
|
36
|
+
return `every ${n} hour${n === 1 ? '' : 's'}`;
|
|
37
|
+
}
|
|
38
|
+
// Only proceed with time-of-day patterns when hour and minute are fixed integers
|
|
39
|
+
const hourNum = /^\d+$/.test(hour) ? parseInt(hour, 10) : null;
|
|
40
|
+
const minNum = /^\d+$/.test(minute) ? parseInt(minute, 10) : null;
|
|
41
|
+
if (hourNum === null || minNum === null)
|
|
42
|
+
return expr;
|
|
43
|
+
const timeStr = formatTime12(hourNum, minNum);
|
|
44
|
+
// daily at HH:MM: M H * * *
|
|
45
|
+
if (dom === '*' && month === '*' && dow === '*') {
|
|
46
|
+
return `daily at ${timeStr}`;
|
|
47
|
+
}
|
|
48
|
+
// weekdays: M H * * 1-5
|
|
49
|
+
if (dom === '*' && month === '*' && dow === '1-5') {
|
|
50
|
+
return `weekdays at ${timeStr}`;
|
|
51
|
+
}
|
|
52
|
+
// specific day of week: M H * * D (single digit 0-6)
|
|
53
|
+
if (dom === '*' && month === '*' && /^\d$/.test(dow)) {
|
|
54
|
+
const dayIdx = parseInt(dow, 10);
|
|
55
|
+
if (dayIdx >= 0 && dayIdx <= 6) {
|
|
56
|
+
return `${DAY_NAMES[dayIdx]} at ${timeStr}`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// specific day of month: M H D * *
|
|
60
|
+
if (/^\d+$/.test(dom) && month === '*' && dow === '*') {
|
|
61
|
+
const d = parseInt(dom, 10);
|
|
62
|
+
return `monthly on day ${d} at ${timeStr}`;
|
|
63
|
+
}
|
|
64
|
+
// every N hours with fixed minute: M */N * * * (already handled above for M=0; catch M != 0)
|
|
65
|
+
if (everyHourMatch && dom === '*' && month === '*' && dow === '*') {
|
|
66
|
+
const n = parseInt(everyHourMatch[1], 10);
|
|
67
|
+
return `every ${n} hour${n === 1 ? '' : 's'} at :${String(minNum).padStart(2, '0')}`;
|
|
68
|
+
}
|
|
69
|
+
return expr;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return expr;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** Format an hour (0-23) + minute (0-59) as "H:MM AM/PM". */
|
|
76
|
+
function formatTime12(hour, minute) {
|
|
77
|
+
const period = hour < 12 ? 'AM' : 'PM';
|
|
78
|
+
const h = hour % 12 === 0 ? 12 : hour % 12;
|
|
79
|
+
const m = String(minute).padStart(2, '0');
|
|
80
|
+
return `${h}:${m} ${period}`;
|
|
81
|
+
}
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// humanizeNextRun
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
const WEEKDAY_NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
86
|
+
const MONTH_NAMES = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
87
|
+
/**
|
|
88
|
+
* Convert a next-run Date into a human phrase relative to `now`.
|
|
89
|
+
*
|
|
90
|
+
* - null → '-'
|
|
91
|
+
* - same calendar day → 'today 9:00 AM'
|
|
92
|
+
* - next calendar day → 'tomorrow 9:00 AM'
|
|
93
|
+
* - within 7 days → 'Mon 9:00 AM'
|
|
94
|
+
* - further out → 'Jun 15, 9:00 AM'
|
|
95
|
+
*/
|
|
96
|
+
export function humanizeNextRun(date, now, tz) {
|
|
97
|
+
if (!date)
|
|
98
|
+
return '-';
|
|
99
|
+
try {
|
|
100
|
+
const locale = 'en-US';
|
|
101
|
+
const tzOpts = tz ? { timeZone: tz } : {};
|
|
102
|
+
// Extract calendar date components for both dates using the same timezone.
|
|
103
|
+
const toYMD = (d) => {
|
|
104
|
+
const fmt = new Intl.DateTimeFormat(locale, { year: 'numeric', month: 'numeric', day: 'numeric', ...tzOpts });
|
|
105
|
+
const parts = fmt.formatToParts(d);
|
|
106
|
+
const get = (type) => parseInt(parts.find((p) => p.type === type)?.value ?? '0', 10);
|
|
107
|
+
return { y: get('year'), m: get('month'), day: get('day') };
|
|
108
|
+
};
|
|
109
|
+
const nowYMD = toYMD(now);
|
|
110
|
+
const dateYMD = toYMD(date);
|
|
111
|
+
// Diff in whole calendar days (ignoring time-of-day)
|
|
112
|
+
const nowMidnight = Date.UTC(nowYMD.y, nowYMD.m - 1, nowYMD.day);
|
|
113
|
+
const dateMidnight = Date.UTC(dateYMD.y, dateYMD.m - 1, dateYMD.day);
|
|
114
|
+
const diffDays = Math.round((dateMidnight - nowMidnight) / 86400000);
|
|
115
|
+
// Time string for the date
|
|
116
|
+
const timeFmt = new Intl.DateTimeFormat(locale, { hour: 'numeric', minute: '2-digit', hour12: true, ...tzOpts });
|
|
117
|
+
const timeStr = timeFmt.format(date);
|
|
118
|
+
if (diffDays === 0)
|
|
119
|
+
return `today ${timeStr}`;
|
|
120
|
+
if (diffDays === 1)
|
|
121
|
+
return `tomorrow ${timeStr}`;
|
|
122
|
+
if (diffDays < 7) {
|
|
123
|
+
const weekdayIdx = new Intl.DateTimeFormat(locale, { weekday: 'short', ...tzOpts })
|
|
124
|
+
.formatToParts(date)
|
|
125
|
+
.find((p) => p.type === 'weekday')?.value;
|
|
126
|
+
return `${weekdayIdx ?? WEEKDAY_NAMES[date.getDay()]} ${timeStr}`;
|
|
127
|
+
}
|
|
128
|
+
// Further out: "Jun 15, 9:00 AM"
|
|
129
|
+
const monthName = MONTH_NAMES[dateYMD.m - 1];
|
|
130
|
+
return `${monthName} ${dateYMD.day}, ${timeStr}`;
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return date.toLocaleString();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// formatRepoLink
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
/**
|
|
140
|
+
* Maximum display length for a repo cell. Display strings longer than this
|
|
141
|
+
* are truncated with an ellipsis so column alignment is preserved.
|
|
142
|
+
* Consumers that render the column should use this constant as the column width.
|
|
143
|
+
*/
|
|
144
|
+
export const REPO_DISPLAY_MAX = 24;
|
|
145
|
+
/**
|
|
146
|
+
* Parse a repo string into a display label and an optional hyperlink target.
|
|
147
|
+
*
|
|
148
|
+
* Rules:
|
|
149
|
+
* - null / undefined / empty / non-string → display '-', href null
|
|
150
|
+
* - 'owner/name' (one slash) → display 'owner/name', href 'https://github.com/owner/name/pulls'
|
|
151
|
+
* - 'https://...' or 'http://...' → display hostname+path, href the URL verbatim
|
|
152
|
+
* - anything else → display raw string, href null
|
|
153
|
+
*
|
|
154
|
+
* The display string is truncated to REPO_DISPLAY_MAX characters (with a
|
|
155
|
+
* trailing '…') when it would otherwise exceed the column width. The href
|
|
156
|
+
* is always the full untruncated URL so hyperlinks remain functional.
|
|
157
|
+
*
|
|
158
|
+
* NEVER throws — mirrors the contract of humanizeCron.
|
|
159
|
+
*/
|
|
160
|
+
export function formatRepoLink(repo) {
|
|
161
|
+
if (repo == null || typeof repo !== 'string' || repo.trim() === '') {
|
|
162
|
+
return { display: '-', href: null };
|
|
163
|
+
}
|
|
164
|
+
const trimmed = repo.trim();
|
|
165
|
+
let display;
|
|
166
|
+
let href;
|
|
167
|
+
// Absolute URL
|
|
168
|
+
if (trimmed.startsWith('https://') || trimmed.startsWith('http://')) {
|
|
169
|
+
try {
|
|
170
|
+
const url = new URL(trimmed);
|
|
171
|
+
display = url.hostname + url.pathname.replace(/\/$/, '');
|
|
172
|
+
href = trimmed;
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
display = trimmed;
|
|
176
|
+
href = null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else if (/^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/.test(trimmed)) {
|
|
180
|
+
// GitHub shorthand: owner/name (exactly one slash, no scheme, no extra slashes)
|
|
181
|
+
display = trimmed;
|
|
182
|
+
href = `https://github.com/${trimmed}/pulls`;
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
// Anything else: plain text, no link
|
|
186
|
+
display = trimmed;
|
|
187
|
+
href = null;
|
|
188
|
+
}
|
|
189
|
+
// Truncate display to column width; href stays untruncated.
|
|
190
|
+
if (display.length > REPO_DISPLAY_MAX) {
|
|
191
|
+
display = display.slice(0, REPO_DISPLAY_MAX - 1) + '…';
|
|
192
|
+
}
|
|
193
|
+
return { display, href };
|
|
194
|
+
}
|
package/dist/lib/routines.d.ts
CHANGED
|
@@ -18,12 +18,14 @@ export interface JobConfig {
|
|
|
18
18
|
name: string;
|
|
19
19
|
schedule: string;
|
|
20
20
|
agent: AgentId;
|
|
21
|
-
|
|
21
|
+
workflow?: string;
|
|
22
|
+
mode: 'plan' | 'edit' | 'auto' | 'skip' | 'full';
|
|
22
23
|
effort: 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'auto';
|
|
23
24
|
timeout: string;
|
|
24
25
|
enabled: boolean;
|
|
25
26
|
prompt: string;
|
|
26
27
|
timezone?: string;
|
|
28
|
+
repo?: string;
|
|
27
29
|
variables?: Record<string, string>;
|
|
28
30
|
sandbox?: boolean;
|
|
29
31
|
allow?: JobAllowConfig;
|
|
@@ -36,6 +38,7 @@ export interface RunMeta {
|
|
|
36
38
|
jobName: string;
|
|
37
39
|
runId: string;
|
|
38
40
|
agent: AgentId;
|
|
41
|
+
workflow?: string;
|
|
39
42
|
pid: number | null;
|
|
40
43
|
status: 'running' | 'completed' | 'failed' | 'timeout';
|
|
41
44
|
startedAt: string;
|
|
@@ -56,7 +59,10 @@ export declare function setJobEnabled(name: string, enabled: boolean): void;
|
|
|
56
59
|
export declare function validateJob(config: Partial<JobConfig>): string[];
|
|
57
60
|
/** Expand built-in and user-defined template variables in a job's prompt string. */
|
|
58
61
|
export declare function resolveJobPrompt(config: JobConfig): string;
|
|
59
|
-
/** Parse a human-readable timeout string (e.g. "
|
|
62
|
+
/** Parse a human-readable timeout string (e.g. "10m", "2h", "1h30m", "3d", "1w") into milliseconds.
|
|
63
|
+
* Accepts combinations of w (weeks), d (days), h (hours), m (minutes).
|
|
64
|
+
* Returns null if the string is empty, matches nothing, totals zero, or exceeds 1 week.
|
|
65
|
+
*/
|
|
60
66
|
export declare function parseTimeout(timeout: string): number | null;
|
|
61
67
|
/** List all run metadata entries for a job, sorted chronologically. */
|
|
62
68
|
export declare function listRuns(jobName: string): RunMeta[];
|
package/dist/lib/routines.js
CHANGED
|
@@ -17,7 +17,7 @@ import { ALL_AGENT_IDS } from './agents.js';
|
|
|
17
17
|
const JOB_DEFAULTS = {
|
|
18
18
|
mode: 'plan',
|
|
19
19
|
effort: 'auto',
|
|
20
|
-
timeout: '
|
|
20
|
+
timeout: '10m',
|
|
21
21
|
enabled: true,
|
|
22
22
|
};
|
|
23
23
|
/** List all job configs from ~/.agents/routines/. */
|
|
@@ -73,7 +73,7 @@ export function writeJob(config) {
|
|
|
73
73
|
delete output.mode;
|
|
74
74
|
if (output.effort === 'auto')
|
|
75
75
|
delete output.effort;
|
|
76
|
-
if (output.timeout === '
|
|
76
|
+
if (output.timeout === '10m')
|
|
77
77
|
delete output.timeout;
|
|
78
78
|
if (output.enabled === true)
|
|
79
79
|
delete output.enabled;
|
|
@@ -119,14 +119,24 @@ export function validateJob(config) {
|
|
|
119
119
|
errors.push(`invalid cron expression: "${config.schedule}"`);
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
const hasAgent = Boolean(config.agent && typeof config.agent === 'string');
|
|
123
|
+
const hasWorkflow = Boolean(config.workflow && typeof config.workflow === 'string');
|
|
124
|
+
if (!hasAgent && !hasWorkflow) {
|
|
125
|
+
errors.push('exactly one of agent or workflow is required');
|
|
124
126
|
}
|
|
125
|
-
if (
|
|
127
|
+
else if (hasAgent && hasWorkflow) {
|
|
128
|
+
errors.push('exactly one of agent or workflow must be set (not both)');
|
|
129
|
+
}
|
|
130
|
+
if (hasAgent && config.agent && !ALL_AGENT_IDS.includes(config.agent)) {
|
|
126
131
|
errors.push(`agent must be one of: ${ALL_AGENT_IDS.join(', ')}`);
|
|
127
132
|
}
|
|
128
|
-
if (
|
|
129
|
-
|
|
133
|
+
if (hasWorkflow && config.workflow) {
|
|
134
|
+
if (!/^[a-z0-9][a-z0-9_-]*$/.test(config.workflow)) {
|
|
135
|
+
errors.push('workflow must be a lowercase alphanumeric name (hyphens and underscores allowed, e.g. autodev)');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (config.mode && !['plan', 'edit', 'auto', 'skip', 'full'].includes(config.mode)) {
|
|
139
|
+
errors.push("mode must be plan, edit, auto, or skip ('full' accepted as alias for skip)");
|
|
130
140
|
}
|
|
131
141
|
if (config.effort && !['low', 'medium', 'high', 'xhigh', 'max', 'auto'].includes(config.effort)) {
|
|
132
142
|
errors.push('effort must be low, medium, high, xhigh, max, or auto');
|
|
@@ -135,7 +145,7 @@ export function validateJob(config) {
|
|
|
135
145
|
errors.push('prompt is required');
|
|
136
146
|
}
|
|
137
147
|
if (config.timeout && !parseTimeout(config.timeout)) {
|
|
138
|
-
errors.push('timeout must be like
|
|
148
|
+
errors.push('timeout must be like 10m, 2h, 3d, 1w (max 1w)');
|
|
139
149
|
}
|
|
140
150
|
return errors;
|
|
141
151
|
}
|
|
@@ -179,15 +189,25 @@ export function resolveJobPrompt(config) {
|
|
|
179
189
|
}
|
|
180
190
|
return prompt;
|
|
181
191
|
}
|
|
182
|
-
/** Parse a human-readable timeout string (e.g. "
|
|
192
|
+
/** Parse a human-readable timeout string (e.g. "10m", "2h", "1h30m", "3d", "1w") into milliseconds.
|
|
193
|
+
* Accepts combinations of w (weeks), d (days), h (hours), m (minutes).
|
|
194
|
+
* Returns null if the string is empty, matches nothing, totals zero, or exceeds 1 week.
|
|
195
|
+
*/
|
|
183
196
|
export function parseTimeout(timeout) {
|
|
184
|
-
const match = timeout.match(/^(?:(\d+)h)?(?:(\d+)m)?$/);
|
|
197
|
+
const match = timeout.match(/^(?:(\d+)w)?(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?$/);
|
|
185
198
|
if (!match)
|
|
186
199
|
return null;
|
|
187
|
-
const
|
|
188
|
-
const
|
|
189
|
-
const
|
|
190
|
-
|
|
200
|
+
const weeks = parseInt(match[1] || '0', 10);
|
|
201
|
+
const days = parseInt(match[2] || '0', 10);
|
|
202
|
+
const hours = parseInt(match[3] || '0', 10);
|
|
203
|
+
const minutes = parseInt(match[4] || '0', 10);
|
|
204
|
+
const ms = ((weeks * 7 + days) * 24 * 60 + hours * 60 + minutes) * 60 * 1000;
|
|
205
|
+
if (ms <= 0)
|
|
206
|
+
return null;
|
|
207
|
+
const ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000; // 604800000
|
|
208
|
+
if (ms > ONE_WEEK_MS)
|
|
209
|
+
return null;
|
|
210
|
+
return ms;
|
|
191
211
|
}
|
|
192
212
|
/** List all run metadata entries for a job, sorted chronologically. */
|
|
193
213
|
export function listRuns(jobName) {
|
package/dist/lib/runner.js
CHANGED
|
@@ -14,7 +14,8 @@ import { resolveJobPrompt, parseTimeout, writeRunMeta, getRunDir, } from './rout
|
|
|
14
14
|
import { getRunsDir } from './state.js';
|
|
15
15
|
import { prepareJobHome, buildSpawnEnv } from './sandbox.js';
|
|
16
16
|
import { resolveModel, buildReasoningFlags } from './models.js';
|
|
17
|
-
import { createTimer, maybeRotate,
|
|
17
|
+
import { createTimer, maybeRotate, redactPrompt } from './events.js';
|
|
18
|
+
import { normalizeMode } from './exec.js';
|
|
18
19
|
/** CLI command templates per agent, with {prompt} as a placeholder. */
|
|
19
20
|
const AGENT_COMMANDS = {
|
|
20
21
|
claude: ['claude', '-p', '--verbose', '{prompt}', '--output-format', 'stream-json', '--permission-mode', 'plan'],
|
|
@@ -23,18 +24,33 @@ const AGENT_COMMANDS = {
|
|
|
23
24
|
};
|
|
24
25
|
/** Build the full CLI argv for executing a job, applying mode, model, and permission flags. */
|
|
25
26
|
export function buildJobCommand(config, resolvedPrompt) {
|
|
27
|
+
// Workflow branch: delegate to `agents run <workflow>` which handles subagent
|
|
28
|
+
// injection, WORKFLOW.md orchestration, and model selection via frontmatter.
|
|
29
|
+
// appendModelAndReasoning is intentionally skipped — the workflow frontmatter
|
|
30
|
+
// owns model selection. No --timeout flag: the runner enforces its own SIGTERM/SIGKILL.
|
|
31
|
+
if (config.workflow) {
|
|
32
|
+
const cmd = ['agents', 'run', config.workflow, resolvedPrompt, '--mode', config.mode];
|
|
33
|
+
return cmd;
|
|
34
|
+
}
|
|
26
35
|
const template = AGENT_COMMANDS[config.agent];
|
|
27
36
|
if (!template) {
|
|
28
37
|
throw new Error(`Unsupported agent for daemon jobs: ${config.agent}`);
|
|
29
38
|
}
|
|
30
39
|
let cmd = template.map((part) => part.replace('{prompt}', resolvedPrompt));
|
|
40
|
+
// Canonicalize mode (accepts legacy `full` as alias for `skip`).
|
|
41
|
+
const mode = normalizeMode(config.mode);
|
|
31
42
|
if (config.agent === 'claude') {
|
|
32
|
-
if (
|
|
43
|
+
if (mode === 'edit') {
|
|
33
44
|
const planIndex = cmd.indexOf('plan');
|
|
34
45
|
if (planIndex !== -1)
|
|
35
46
|
cmd[planIndex] = 'acceptEdits';
|
|
36
47
|
}
|
|
37
|
-
else if (
|
|
48
|
+
else if (mode === 'auto') {
|
|
49
|
+
const planIndex = cmd.indexOf('plan');
|
|
50
|
+
if (planIndex !== -1)
|
|
51
|
+
cmd[planIndex] = 'auto';
|
|
52
|
+
}
|
|
53
|
+
else if (mode === 'skip') {
|
|
38
54
|
// Replace --permission-mode plan with --dangerously-skip-permissions
|
|
39
55
|
const pmIndex = cmd.indexOf('--permission-mode');
|
|
40
56
|
if (pmIndex !== -1)
|
|
@@ -55,10 +71,10 @@ export function buildJobCommand(config, resolvedPrompt) {
|
|
|
55
71
|
appendModelAndReasoning(cmd, config);
|
|
56
72
|
}
|
|
57
73
|
if (config.agent === 'codex') {
|
|
58
|
-
if (
|
|
74
|
+
if (mode === 'edit') {
|
|
59
75
|
cmd.push('--full-auto');
|
|
60
76
|
}
|
|
61
|
-
else if (
|
|
77
|
+
else if (mode === 'skip') {
|
|
62
78
|
// Remove sandbox restriction, just --full-auto
|
|
63
79
|
const sbIndex = cmd.indexOf('--sandbox');
|
|
64
80
|
if (sbIndex !== -1)
|
|
@@ -68,7 +84,10 @@ export function buildJobCommand(config, resolvedPrompt) {
|
|
|
68
84
|
appendModelAndReasoning(cmd, config);
|
|
69
85
|
}
|
|
70
86
|
if (config.agent === 'gemini') {
|
|
71
|
-
if (
|
|
87
|
+
if (mode === 'edit') {
|
|
88
|
+
cmd.push('--approval-mode', 'auto_edit');
|
|
89
|
+
}
|
|
90
|
+
else if (mode === 'skip') {
|
|
72
91
|
cmd.push('--yolo');
|
|
73
92
|
}
|
|
74
93
|
appendModelAndReasoning(cmd, config);
|
|
@@ -114,7 +133,7 @@ export async function executeJob(config) {
|
|
|
114
133
|
version: config.version,
|
|
115
134
|
jobName: config.name,
|
|
116
135
|
mode: config.mode,
|
|
117
|
-
|
|
136
|
+
...redactPrompt(config.prompt),
|
|
118
137
|
schedule: config.schedule,
|
|
119
138
|
});
|
|
120
139
|
const resolvedPrompt = resolveJobPrompt(config);
|
|
@@ -130,10 +149,14 @@ export async function executeJob(config) {
|
|
|
130
149
|
if (config.timezone) {
|
|
131
150
|
spawnEnv.TZ = config.timezone;
|
|
132
151
|
}
|
|
152
|
+
// Workflows run via `agents run <workflow>` which delegates to claude under the hood.
|
|
153
|
+
// Use 'claude' as the effective agent for report extraction and metadata when workflow is set.
|
|
154
|
+
const effectiveAgent = config.workflow ? 'claude' : config.agent;
|
|
133
155
|
const meta = {
|
|
134
156
|
jobName: config.name,
|
|
135
157
|
runId,
|
|
136
|
-
agent:
|
|
158
|
+
agent: effectiveAgent,
|
|
159
|
+
...(config.workflow ? { workflow: config.workflow } : {}),
|
|
137
160
|
pid: null,
|
|
138
161
|
status: 'running',
|
|
139
162
|
startedAt: new Date().toISOString(),
|
|
@@ -141,7 +164,7 @@ export async function executeJob(config) {
|
|
|
141
164
|
exitCode: null,
|
|
142
165
|
};
|
|
143
166
|
writeRunMeta(meta);
|
|
144
|
-
const timeoutMs = parseTimeout(config.timeout) ||
|
|
167
|
+
const timeoutMs = parseTimeout(config.timeout) || 10 * 60 * 1000;
|
|
145
168
|
return new Promise((resolve) => {
|
|
146
169
|
const child = spawn(cmd[0], cmd.slice(1), {
|
|
147
170
|
stdio: ['ignore', stdoutFd, stdoutFd],
|
|
@@ -173,7 +196,7 @@ export async function executeJob(config) {
|
|
|
173
196
|
meta.completedAt = new Date().toISOString();
|
|
174
197
|
writeRunMeta(meta);
|
|
175
198
|
timer.end({ status: 'timeout', runId });
|
|
176
|
-
const reportPath = extractAndSaveReport(stdoutPath,
|
|
199
|
+
const reportPath = extractAndSaveReport(stdoutPath, effectiveAgent, runDir);
|
|
177
200
|
resolve({ meta, reportPath });
|
|
178
201
|
}, timeoutMs);
|
|
179
202
|
child.on('exit', (code) => {
|
|
@@ -190,7 +213,7 @@ export async function executeJob(config) {
|
|
|
190
213
|
meta.completedAt = new Date().toISOString();
|
|
191
214
|
writeRunMeta(meta);
|
|
192
215
|
timer.end({ status: meta.status, exitCode: code ?? undefined, runId });
|
|
193
|
-
const reportPath = extractAndSaveReport(stdoutPath,
|
|
216
|
+
const reportPath = extractAndSaveReport(stdoutPath, effectiveAgent, runDir);
|
|
194
217
|
resolve({ meta, reportPath });
|
|
195
218
|
});
|
|
196
219
|
child.on('error', (err) => {
|
|
@@ -226,10 +249,12 @@ export async function executeJobDetached(config) {
|
|
|
226
249
|
if (config.timezone) {
|
|
227
250
|
spawnEnv.TZ = config.timezone;
|
|
228
251
|
}
|
|
252
|
+
const effectiveAgent = config.workflow ? 'claude' : config.agent;
|
|
229
253
|
const meta = {
|
|
230
254
|
jobName: config.name,
|
|
231
255
|
runId,
|
|
232
|
-
agent:
|
|
256
|
+
agent: effectiveAgent,
|
|
257
|
+
...(config.workflow ? { workflow: config.workflow } : {}),
|
|
233
258
|
pid: null,
|
|
234
259
|
status: 'running',
|
|
235
260
|
startedAt: new Date().toISOString(),
|
|
@@ -307,6 +332,39 @@ export function extractReport(stdoutPath, agentType) {
|
|
|
307
332
|
return null;
|
|
308
333
|
}
|
|
309
334
|
}
|
|
335
|
+
/** Derive the final status of a detached run by reading the agent's stream-json
|
|
336
|
+
* tail. Detached children fire-and-forget, so we never see their exit code
|
|
337
|
+
* directly — but Claude's stream-json terminates with a `type: result` line
|
|
338
|
+
* that carries `is_error`. If we find it, the run completed cleanly (modulo
|
|
339
|
+
* agent-reported error). If not, the process likely died mid-stream and the
|
|
340
|
+
* caller should treat the run as failed. */
|
|
341
|
+
function inferFinalStatusFromLog(stdoutPath, agent) {
|
|
342
|
+
if (!fs.existsSync(stdoutPath))
|
|
343
|
+
return null;
|
|
344
|
+
try {
|
|
345
|
+
const content = fs.readFileSync(stdoutPath, 'utf-8');
|
|
346
|
+
const lines = content.split('\n').filter((l) => l.trim());
|
|
347
|
+
// Walk backwards over the last few lines — the result marker is always
|
|
348
|
+
// at the tail. Cap the scan so a huge stdout doesn't iterate forever.
|
|
349
|
+
for (let i = lines.length - 1, scanned = 0; i >= 0 && scanned < 20; i--, scanned++) {
|
|
350
|
+
try {
|
|
351
|
+
const parsed = JSON.parse(lines[i]);
|
|
352
|
+
if (agent === 'claude' && parsed.type === 'result') {
|
|
353
|
+
return parsed.is_error
|
|
354
|
+
? { status: 'failed', exitCode: 1 }
|
|
355
|
+
: { status: 'completed', exitCode: 0 };
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
// malformed JSONL line — keep scanning
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
310
368
|
/** Scan all runs marked "running" and finalize any whose process has exited. */
|
|
311
369
|
export function monitorRunningJobs() {
|
|
312
370
|
const runsDir = getRunsDir();
|
|
@@ -332,11 +390,21 @@ export function monitorRunningJobs() {
|
|
|
332
390
|
process.kill(meta.pid, 0);
|
|
333
391
|
}
|
|
334
392
|
catch { /* process no longer running */
|
|
335
|
-
|
|
393
|
+
const runDirPath = path.join(jobRunsPath, runDirEntry.name);
|
|
394
|
+
const stdoutPath = path.join(runDirPath, 'stdout.log');
|
|
395
|
+
// Prefer the agent's own success/error marker; fall back to "failed"
|
|
396
|
+
// only when the stream ended without one (process killed mid-run).
|
|
397
|
+
const inferred = inferFinalStatusFromLog(stdoutPath, meta.agent);
|
|
398
|
+
if (inferred) {
|
|
399
|
+
meta.status = inferred.status;
|
|
400
|
+
meta.exitCode = inferred.exitCode;
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
meta.status = 'failed';
|
|
404
|
+
}
|
|
336
405
|
meta.completedAt = new Date().toISOString();
|
|
337
406
|
writeRunMeta(meta);
|
|
338
|
-
|
|
339
|
-
extractAndSaveReport(stdoutPath, meta.agent, path.join(jobRunsPath, runDirEntry.name));
|
|
407
|
+
extractAndSaveReport(stdoutPath, meta.agent, runDirPath);
|
|
340
408
|
}
|
|
341
409
|
}
|
|
342
410
|
catch { /* corrupt or unreadable meta.json */ }
|
package/dist/lib/scheduler.js
CHANGED
|
@@ -24,7 +24,14 @@ export class JobScheduler {
|
|
|
24
24
|
}
|
|
25
25
|
schedule(config) {
|
|
26
26
|
this.unschedule(config.name);
|
|
27
|
-
|
|
27
|
+
// catch: true — a throw from one job's callback should not kill the
|
|
28
|
+
// whole cron loop. Each invocation of onTrigger is already wrapped in
|
|
29
|
+
// try/catch, but a synchronous throw before the await would otherwise
|
|
30
|
+
// bubble up; defense in depth.
|
|
31
|
+
const cronOptions = { catch: true };
|
|
32
|
+
if (config.timezone)
|
|
33
|
+
cronOptions.timezone = config.timezone;
|
|
34
|
+
const cron = new Cron(config.schedule, cronOptions, async () => {
|
|
28
35
|
try {
|
|
29
36
|
await this.onTrigger(config);
|
|
30
37
|
}
|
|
Binary file
|
|
Binary file
|
|
@@ -5,15 +5,7 @@
|
|
|
5
5
|
<key>files</key>
|
|
6
6
|
<dict/>
|
|
7
7
|
<key>files2</key>
|
|
8
|
-
<dict
|
|
9
|
-
<key>embedded.provisionprofile</key>
|
|
10
|
-
<dict>
|
|
11
|
-
<key>hash2</key>
|
|
12
|
-
<data>
|
|
13
|
-
2vfA/eR3dTYgNc/fXhdADUPkp5tRIepPzE3FCLfDx4w=
|
|
14
|
-
</data>
|
|
15
|
-
</dict>
|
|
16
|
-
</dict>
|
|
8
|
+
<dict/>
|
|
17
9
|
<key>rules</key>
|
|
18
10
|
<dict>
|
|
19
11
|
<key>^Resources/</key>
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Secret bundles
|
|
2
|
+
* Secret bundles — named sets of keychain-backed environment variables.
|
|
3
3
|
*
|
|
4
4
|
* Bundle metadata (name, description, vars map) is stored in the macOS
|
|
5
|
-
* Keychain as a JSON blob under `agents-cli.bundles.<name>`.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* the
|
|
5
|
+
* Keychain as a JSON blob under `agents-cli.bundles.<name>`. Secret values
|
|
6
|
+
* live one per keychain item under `agents-cli.secrets.<bundle>.<key>`.
|
|
7
|
+
* Every item is device-local and gated by Touch ID / device passcode — see
|
|
8
|
+
* src/lib/secrets/index.ts for the access-control story. Nothing about
|
|
9
|
+
* secrets ever lives in plaintext on disk.
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* `icloud_sync` flag.
|
|
11
|
+
* Cross-machine sync is handled by src/lib/secrets/sync.ts via an explicit
|
|
12
|
+
* encrypted export/import flow; the bundle layer is sync-agnostic.
|
|
13
13
|
*/
|
|
14
14
|
import { type BundleValue, type SecretRef } from './index.js';
|
|
15
15
|
/** Allowed values for a secret's `type` metadata field. */
|
|
@@ -28,8 +28,6 @@ export interface SecretsBundle {
|
|
|
28
28
|
name: string;
|
|
29
29
|
description?: string;
|
|
30
30
|
allow_exec?: boolean;
|
|
31
|
-
/** When true, keychain-backed values and bundle metadata sync via iCloud Keychain. */
|
|
32
|
-
icloud_sync?: boolean;
|
|
33
31
|
/** ISO 8601 UTC timestamp. Set once on the first writeBundle() for a bundle. */
|
|
34
32
|
created_at?: string;
|
|
35
33
|
/** ISO 8601 UTC timestamp. Refreshed on every writeBundle(). */
|
|
@@ -49,6 +47,7 @@ export declare const RESERVED_ENV_NAMES: Set<string>;
|
|
|
49
47
|
export declare function bundleToEnvPrefix(name: string): string;
|
|
50
48
|
export declare function isReservedEnvName(key: string): boolean;
|
|
51
49
|
export declare function isLoaderOrInterpreterEnv(name: string): boolean;
|
|
50
|
+
export declare function sanitizeProcessEnv(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
52
51
|
/** Validate a bundle name against the allowed pattern. Throws on invalid input. */
|
|
53
52
|
export declare function validateBundleName(name: string): void;
|
|
54
53
|
export declare function validateEnvKey(key: string): void;
|
|
@@ -74,14 +73,32 @@ export declare function describeBundle(bundle: SecretsBundle): BundleEntryInfo[]
|
|
|
74
73
|
/** Options for resolveBundleEnv. */
|
|
75
74
|
export interface ResolveBundleOptions {
|
|
76
75
|
/**
|
|
77
|
-
* Human-readable label for who is requesting the secrets.
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
76
|
+
* Human-readable label for who is requesting the secrets. Currently
|
|
77
|
+
* informational only — the helper's Touch ID prompt is set by the OS and
|
|
78
|
+
* cannot be reliably customized once we drop the per-batch reason path,
|
|
79
|
+
* but we keep this in the API so call sites stay explicit about who's
|
|
80
|
+
* about to read the bundle.
|
|
81
81
|
*/
|
|
82
82
|
caller?: string;
|
|
83
83
|
}
|
|
84
|
-
export declare function resolveBundleEnv(bundle: SecretsBundle,
|
|
84
|
+
export declare function resolveBundleEnv(bundle: SecretsBundle, _opts?: ResolveBundleOptions): Record<string, string>;
|
|
85
|
+
/**
|
|
86
|
+
* Read a bundle's metadata AND resolve its env in a single Touch ID prompt.
|
|
87
|
+
*
|
|
88
|
+
* `readBundle` + `resolveBundleEnv` issued two separate `LAContext` calls
|
|
89
|
+
* (metadata read via `get-auth`, then secret values via `get-batch`) which
|
|
90
|
+
* surfaced as two consecutive Touch ID prompts. macOS does not honor
|
|
91
|
+
* "Always Allow" for items protected with `kSecAttrAccessControl`+biometry,
|
|
92
|
+
* so caching at the OS level was never an option. This collapses both reads
|
|
93
|
+
* into one `get-batch` call: we enumerate the bundle's secret items first
|
|
94
|
+
* (silent — `list` returns attrs only and does not trigger biometry) and
|
|
95
|
+
* include the metadata item in the same batch. One prompt, correctly scoped
|
|
96
|
+
* to the bundle name and caller.
|
|
97
|
+
*/
|
|
98
|
+
export declare function readAndResolveBundleEnv(name: string, opts?: ResolveBundleOptions): {
|
|
99
|
+
bundle: SecretsBundle;
|
|
100
|
+
env: Record<string, string>;
|
|
101
|
+
};
|
|
85
102
|
export declare function keychainRef(key: string): string;
|
|
86
103
|
/** Options for rotateBundleSecret. */
|
|
87
104
|
export interface RotateOptions {
|
|
@@ -114,8 +131,8 @@ export interface RenameOptions {
|
|
|
114
131
|
* 4) write new bundle metadata
|
|
115
132
|
* 5) delete the old per-key keychain items + old metadata
|
|
116
133
|
*
|
|
117
|
-
* Steps 1-4 are reversible. If 5 partially fails
|
|
118
|
-
*
|
|
134
|
+
* Steps 1-4 are reversible. If 5 partially fails, running `rename` again is
|
|
135
|
+
* a safe no-op for the source items.
|
|
119
136
|
*/
|
|
120
137
|
export declare function renameBundle(oldName: string, newName: string, opts?: RenameOptions): void;
|
|
121
138
|
export declare function keychainItemsForBundle(bundle: SecretsBundle): Array<{
|