@phnx-labs/agents-cli 1.22.24 → 1.22.25
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 +167 -1
- package/README.md +14 -4
- package/dist/bin/agents +0 -0
- package/dist/commands/cloud.js +9 -5
- package/dist/commands/doctor.d.ts +24 -0
- package/dist/commands/doctor.js +100 -9
- package/dist/commands/exec.js +19 -16
- package/dist/commands/feed.d.ts +5 -0
- package/dist/commands/feed.js +21 -1
- package/dist/commands/focus.js +2 -2
- package/dist/commands/menubar.js +8 -0
- package/dist/commands/routines.d.ts +3 -0
- package/dist/commands/routines.js +70 -47
- package/dist/commands/run-cloud.js +1 -1
- package/dist/commands/sessions-browser.d.ts +1 -1
- package/dist/commands/sessions-browser.js +27 -7
- package/dist/commands/sessions-resume.js +3 -2
- package/dist/commands/sessions.d.ts +13 -1
- package/dist/commands/sessions.js +24 -2
- package/dist/commands/setup-watchdog.js +5 -10
- package/dist/commands/setup.js +1 -1
- package/dist/commands/teams.js +3 -2
- package/dist/commands/watchdog.d.ts +3 -4
- package/dist/commands/watchdog.js +26 -66
- package/dist/index.js +36 -1
- package/dist/lib/agents.js +126 -21
- package/dist/lib/cloud/cursor.d.ts +79 -0
- package/dist/lib/cloud/cursor.js +228 -0
- package/dist/lib/cloud/registry.js +2 -0
- package/dist/lib/cloud/types.d.ts +7 -2
- package/dist/lib/cloud/types.js +14 -0
- package/dist/lib/crabbox/cli.d.ts +2 -2
- package/dist/lib/crabbox/config.d.ts +7 -8
- package/dist/lib/crabbox/config.js +14 -14
- package/dist/lib/crabbox/lease.d.ts +11 -4
- package/dist/lib/crabbox/lease.js +40 -8
- package/dist/lib/crabbox/setup-copy.d.ts +5 -0
- package/dist/lib/crabbox/setup-copy.js +17 -1
- package/dist/lib/daemon.js +27 -1
- package/dist/lib/device-config.js +7 -0
- package/dist/lib/devices/doctor-findings.d.ts +7 -1
- package/dist/lib/devices/doctor-findings.js +40 -1
- package/dist/lib/events.d.ts +9 -0
- package/dist/lib/events.js +58 -0
- package/dist/lib/exec.d.ts +3 -3
- package/dist/lib/exec.js +24 -10
- package/dist/lib/feed-outcome.d.ts +3 -0
- package/dist/lib/feed-outcome.js +18 -10
- package/dist/lib/feed.d.ts +4 -0
- package/dist/lib/hosts/passthrough.d.ts +21 -0
- package/dist/lib/hosts/passthrough.js +39 -12
- package/dist/lib/mcp.js +5 -1
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/snapshot.d.ts +15 -0
- package/dist/lib/menubar/snapshot.js +40 -0
- package/dist/lib/plugins.js +13 -1
- package/dist/lib/resources/mcp.js +3 -0
- package/dist/lib/routine-process-cleanup.d.ts +9 -0
- package/dist/lib/routine-process-cleanup.js +73 -0
- package/dist/lib/runner.js +5 -5
- 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/bundles.js +1 -20
- package/dist/lib/secrets/filestore.d.ts +2 -0
- package/dist/lib/secrets/filestore.js +13 -0
- package/dist/lib/secrets/rc-hygiene.d.ts +14 -0
- package/dist/lib/secrets/rc-hygiene.js +14 -1
- package/dist/lib/session/active.d.ts +4 -0
- package/dist/lib/session/db.js +8 -2
- package/dist/lib/session/remote-list.d.ts +2 -0
- package/dist/lib/session/remote-list.js +1 -0
- package/dist/lib/session/session-cache.d.ts +4 -4
- package/dist/lib/session/session-cache.js +4 -4
- package/dist/lib/shims.js +21 -1
- package/dist/lib/signin-badge.js +2 -0
- package/dist/lib/startup/command-registry.d.ts +15 -0
- package/dist/lib/startup/command-registry.js +42 -0
- package/dist/lib/teams/agents.js +1 -1
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/versions.js +16 -1
- package/dist/lib/watchdog/service.d.ts +17 -0
- package/dist/lib/watchdog/service.js +39 -0
- package/package.json +1 -1
|
@@ -10,27 +10,21 @@
|
|
|
10
10
|
* agents watchdog --nudge one tick, actually injects (explicit opt-in)
|
|
11
11
|
* agents watchdog --watch manual poll loop (dry unless --nudge)
|
|
12
12
|
* agents watchdog --json machine-readable tick output (for the menu-bar)
|
|
13
|
-
* agents watchdog on|off turn the
|
|
13
|
+
* agents watchdog on|off turn the device-local daemon pass on/off
|
|
14
14
|
* agents watchdog policy <id> <p> per-session policy: off | keep | handsoff
|
|
15
15
|
*
|
|
16
|
-
* The
|
|
17
|
-
*
|
|
18
|
-
* repo; on/off only changes this device's routine membership.
|
|
16
|
+
* The agents daemon is the sole automatic watchdog scheduler. The menu bar reads
|
|
17
|
+
* persisted state; it never runs a tick.
|
|
19
18
|
*/
|
|
20
19
|
import chalk from 'chalk';
|
|
21
20
|
import * as path from 'path';
|
|
22
21
|
import { setHelpSections } from '../lib/help.js';
|
|
23
22
|
import { parseDuration } from '../lib/hooks/cache.js';
|
|
24
23
|
import { getRuntimeStateDir } from '../lib/state.js';
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import { loadLocalActiveSessions } from '../lib/session/session-cache.js';
|
|
28
|
-
import { mailboxIdForActiveSession } from '../lib/mailbox-target.js';
|
|
29
|
-
import { gcMailbox } from '../lib/mailbox-gc.js';
|
|
30
|
-
import { devicesWithRoutineEnabled, routineEnabledOnThisDevice } from '../lib/routine-activation.js';
|
|
31
|
-
import { runWatchdogTick, writePolicySentinel, DEFAULT_THRESHOLDS, } from '../lib/watchdog/runner.js';
|
|
24
|
+
import { getConfigValue, setConfigValue } from '../lib/device-config.js';
|
|
25
|
+
import { writePolicySentinel, DEFAULT_THRESHOLDS, } from '../lib/watchdog/runner.js';
|
|
32
26
|
import { isWatchdogRotateEnabled, listRotateStates, setWatchdogRotateEnabled } from '../lib/watchdog/rotate.js';
|
|
33
|
-
|
|
27
|
+
import { loadWatchdogSessions, runWatchdogPass } from '../lib/watchdog/service.js';
|
|
34
28
|
/** Default state dir the runner and these subcommands share. */
|
|
35
29
|
function stateDir() {
|
|
36
30
|
return path.join(getRuntimeStateDir(), 'watchdog');
|
|
@@ -63,21 +57,6 @@ function humanMs(ms) {
|
|
|
63
57
|
return `${Math.round(ms / 60_000)}m`;
|
|
64
58
|
return `${Math.round(ms / 1000)}s`;
|
|
65
59
|
}
|
|
66
|
-
/**
|
|
67
|
-
* Run the mailbox liveness sweep against the same session set the tick just used.
|
|
68
|
-
* Idempotent and cheap: archiving a message twice is a no-op, and GC only scans
|
|
69
|
-
* directories. Failures are swallowed so a mailbox-root problem cannot break the
|
|
70
|
-
* watchdog tick.
|
|
71
|
-
*/
|
|
72
|
-
async function runMailboxGc(sessions) {
|
|
73
|
-
const activeBoxIds = new Set(sessions.map(mailboxIdForActiveSession).filter((id) => !!id));
|
|
74
|
-
try {
|
|
75
|
-
gcMailbox(activeBoxIds);
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
// GC is best-effort housekeeping; the next tick will retry.
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
60
|
function colorForOutcome(o) {
|
|
82
61
|
if (o.injected)
|
|
83
62
|
return chalk.green;
|
|
@@ -139,45 +118,35 @@ export function registerWatchdogCommand(program) {
|
|
|
139
118
|
};
|
|
140
119
|
// Injection gate: --nudge is the explicit opt-in to actually inject. Bare
|
|
141
120
|
// `agents watchdog` (and `--watch` without `--nudge`) is dry. The always-on
|
|
142
|
-
// path is the daemon
|
|
143
|
-
//
|
|
121
|
+
// path is the daemon-owned pass. Device config is its on/off switch; this
|
|
122
|
+
// explicit command still requires --nudge before it can inject.
|
|
144
123
|
const computeWillInject = () => opts.nudge === true;
|
|
145
|
-
const tickOnce = async (willInject, sessions) =>
|
|
124
|
+
const tickOnce = async (willInject, sessions) => runWatchdogPass({
|
|
146
125
|
nudge: willInject,
|
|
147
126
|
nudgeText: opts.text,
|
|
148
127
|
smart: opts.smart === true,
|
|
149
128
|
smartAgent: opts.smartAgent,
|
|
150
129
|
thresholds,
|
|
151
130
|
allowGhosttyFocus: opts.allowGhosttyFocus === true,
|
|
152
|
-
stateDir: stateDir(),
|
|
153
131
|
sessions,
|
|
154
132
|
});
|
|
155
133
|
// RUSH-2062: share the daemon-warmed local active-session snapshot with
|
|
156
134
|
// menubar/CLI/Factory instead of re-running a full gather every tick.
|
|
157
|
-
const loadWatchdogSessions = async () => {
|
|
158
|
-
const loaded = await loadLocalActiveSessions({
|
|
159
|
-
// Force a live gather only when the warm path is unavailable; the
|
|
160
|
-
// cache layer already re-gathers past DEFAULT_ACTIVE_CACHE_MAX_AGE_MS.
|
|
161
|
-
gather: () => getActiveSessions({ localOnly: true }),
|
|
162
|
-
});
|
|
163
|
-
return loaded.sessions;
|
|
164
|
-
};
|
|
165
135
|
if (!opts.watch) {
|
|
166
136
|
const willInject = computeWillInject();
|
|
167
137
|
const sessions = await loadWatchdogSessions();
|
|
168
138
|
const result = await tickOnce(willInject, sessions);
|
|
169
|
-
await runMailboxGc(sessions);
|
|
170
139
|
if (opts.json)
|
|
171
140
|
console.log(JSON.stringify(result, null, 2));
|
|
172
141
|
else
|
|
173
142
|
printTick(result, willInject);
|
|
174
143
|
return;
|
|
175
144
|
}
|
|
176
|
-
// Manual poll loop
|
|
145
|
+
// Manual poll loop for ad-hoc use; the daemon owns the automatic cadence.
|
|
177
146
|
const intervalMs = durationMsOr(opts.interval, 30_000);
|
|
178
147
|
if (!computeWillInject() && !opts.json) {
|
|
179
148
|
console.log(chalk.yellow(`watchdog --watch is DETECT-ONLY. Pass --nudge to inject, ` +
|
|
180
|
-
`or run 'agents watchdog on' for the
|
|
149
|
+
`or run 'agents watchdog on' for the daemon-owned pass.`));
|
|
181
150
|
}
|
|
182
151
|
// eslint-disable-next-line no-constant-condition
|
|
183
152
|
while (true) {
|
|
@@ -185,7 +154,6 @@ export function registerWatchdogCommand(program) {
|
|
|
185
154
|
const willInject = computeWillInject();
|
|
186
155
|
const sessions = await loadWatchdogSessions();
|
|
187
156
|
const result = await tickOnce(willInject, sessions);
|
|
188
|
-
await runMailboxGc(sessions);
|
|
189
157
|
if (opts.json)
|
|
190
158
|
console.log(JSON.stringify(result));
|
|
191
159
|
else
|
|
@@ -207,10 +175,10 @@ export function registerWatchdogCommand(program) {
|
|
|
207
175
|
# Machine-readable for the menu-bar
|
|
208
176
|
agents watchdog --json
|
|
209
177
|
|
|
210
|
-
# Turn on the
|
|
178
|
+
# Turn on the daemon-owned watchdog pass (every three minutes)
|
|
211
179
|
agents watchdog on
|
|
212
180
|
|
|
213
|
-
# Show
|
|
181
|
+
# Show device enablement, rotate config, and in-flight rotates
|
|
214
182
|
agents watchdog status
|
|
215
183
|
|
|
216
184
|
# Leave one session detected-but-untouched
|
|
@@ -253,37 +221,32 @@ export function registerWatchdogCommand(program) {
|
|
|
253
221
|
'watchdog.rotate: off' to ~/.agents/agents.yaml; nudging stays on).
|
|
254
222
|
State machine: ~/.agents/.cache/state/watchdog/rotate/<sessionId>.json.
|
|
255
223
|
|
|
256
|
-
Always-on: 'agents watchdog on' enables
|
|
257
|
-
on this device and reloads the daemon; 'off' disables it here.
|
|
258
|
-
|
|
259
|
-
|
|
224
|
+
Always-on: 'agents watchdog on' enables one daemon-owned pass every three
|
|
225
|
+
minutes on this device and reloads the daemon; 'off' disables it here.
|
|
226
|
+
Defaults OFF. Per-session policy: off (ignore), keep (default), handsoff
|
|
227
|
+
(detect + flag).
|
|
260
228
|
|
|
261
229
|
State (tray-readable): ${path.join('~/.agents/.cache/state/watchdog', '{nudges,flags,last-tick}.json')}
|
|
262
230
|
`,
|
|
263
231
|
});
|
|
264
232
|
// --- always-on enable/disable/status (backed by the daemon routine) --------
|
|
265
233
|
const turnOn = async () => {
|
|
266
|
-
|
|
267
|
-
if (!routine)
|
|
268
|
-
throw new Error("Built-in routine 'watchdog' is missing. Run: agents repo pull system");
|
|
269
|
-
setJobEnabled(WATCHDOG_ROUTINE_NAME, true);
|
|
234
|
+
setConfigValue('watchdog.enabled', true);
|
|
270
235
|
await reloadDaemonForRoutine(true);
|
|
271
|
-
console.log(chalk.green(
|
|
236
|
+
console.log(chalk.green('watchdog: ON on this device (every 3 minutes)'));
|
|
272
237
|
};
|
|
273
238
|
const turnOff = async () => {
|
|
274
|
-
|
|
275
|
-
throw new Error("Built-in routine 'watchdog' is missing. Run: agents repo pull system");
|
|
276
|
-
setJobEnabled(WATCHDOG_ROUTINE_NAME, false);
|
|
239
|
+
setConfigValue('watchdog.enabled', false);
|
|
277
240
|
await reloadDaemonForRoutine(false);
|
|
278
241
|
console.log(chalk.yellow('watchdog: OFF on this device'));
|
|
279
242
|
};
|
|
280
243
|
cmd.command('on')
|
|
281
|
-
.description('Enable the
|
|
244
|
+
.description('Enable the daemon watchdog pass on this device.')
|
|
282
245
|
.action(async () => {
|
|
283
246
|
await turnOn();
|
|
284
247
|
});
|
|
285
248
|
cmd.command('off')
|
|
286
|
-
.description('Disable the
|
|
249
|
+
.description('Disable the daemon watchdog pass on this device.')
|
|
287
250
|
.action(async () => {
|
|
288
251
|
await turnOff();
|
|
289
252
|
});
|
|
@@ -304,7 +267,7 @@ export function registerWatchdogCommand(program) {
|
|
|
304
267
|
chalk.dim(`(watchdog.rotate: ${s} in agents.yaml)`));
|
|
305
268
|
});
|
|
306
269
|
cmd.command('status')
|
|
307
|
-
.description('Show whether the
|
|
270
|
+
.description('Show whether the daemon watchdog pass is enabled and where state is written.')
|
|
308
271
|
.option('--json', 'Emit status as JSON (for the menu-bar / scripts)')
|
|
309
272
|
.action((_opts, command) => {
|
|
310
273
|
// The parent `watchdog` command also declares --json and greedily parses it
|
|
@@ -312,16 +275,14 @@ export function registerWatchdogCommand(program) {
|
|
|
312
275
|
// parent, not this subcommand. optsWithGlobals() merges both levels, so we
|
|
313
276
|
// read it correctly regardless of which command commander bound it to.
|
|
314
277
|
const json = command.optsWithGlobals().json === true;
|
|
315
|
-
const on =
|
|
316
|
-
const enabledDevices = devicesWithRoutineEnabled(WATCHDOG_ROUTINE_NAME);
|
|
278
|
+
const on = getConfigValue('watchdog.enabled').value === true;
|
|
317
279
|
const rotate = isWatchdogRotateEnabled() ? 'on' : 'off';
|
|
318
280
|
const rotates = listRotateStates(stateDir());
|
|
319
281
|
const inflight = rotates.filter((r) => r.phase !== 'done' && r.phase !== 'failed');
|
|
320
282
|
if (json) {
|
|
321
283
|
console.log(JSON.stringify({
|
|
322
284
|
enabled: on,
|
|
323
|
-
|
|
324
|
-
routine: WATCHDOG_ROUTINE_NAME,
|
|
285
|
+
cadenceMs: 180_000,
|
|
325
286
|
stateDir: stateDir(),
|
|
326
287
|
rotate,
|
|
327
288
|
rotates: rotates.map((r) => ({
|
|
@@ -335,8 +296,7 @@ export function registerWatchdogCommand(program) {
|
|
|
335
296
|
}));
|
|
336
297
|
return;
|
|
337
298
|
}
|
|
338
|
-
console.log(`always-on watchdog: ${on ? chalk.green('ON') : chalk.dim('off')} (
|
|
339
|
-
console.log(`enabled devices: ${enabledDevices.length > 0 ? enabledDevices.join(', ') : chalk.dim('none')}`);
|
|
299
|
+
console.log(`always-on watchdog: ${on ? chalk.green('ON') : chalk.dim('off')} (every 3 minutes on this device)`);
|
|
340
300
|
console.log(`rotate: ${rotate === 'on' ? chalk.green('on') : chalk.yellow('off')} (watchdog.rotate in agents.yaml) · ${inflight.length} in-flight`);
|
|
341
301
|
for (const r of inflight) {
|
|
342
302
|
console.log(` ${chalk.magenta(r.phase.padEnd(12))} ${chalk.bold(r.sessionId.slice(0, 8))} → ${r.newSessionId.slice(0, 8)}${r.error ? chalk.red(` ${r.error}`) : ''}`);
|
package/dist/index.js
CHANGED
|
@@ -1035,7 +1035,23 @@ program.on('command:*', (operands) => {
|
|
|
1035
1035
|
if (minDist === 1 && closest) {
|
|
1036
1036
|
const args = process.argv.slice(2);
|
|
1037
1037
|
args[0] = closest;
|
|
1038
|
-
|
|
1038
|
+
// The typo'd name was unknown, so the top-level --host router (which ran
|
|
1039
|
+
// before commander parsing, against the ORIGINAL name) could not have
|
|
1040
|
+
// routed it - it correctly fell through to reach this handler at all
|
|
1041
|
+
// (that fallthrough is this ticket's own fix). But falling through to a
|
|
1042
|
+
// plain local re-parse means a routing flag on a corrected REAL
|
|
1043
|
+
// host-routable command (e.g. `docto --host box`, corrected to `doctor`)
|
|
1044
|
+
// silently ran LOCALLY instead of remotely, with no error - worse than
|
|
1045
|
+
// the loud "does not support --host" this ticket replaced. Re-run the
|
|
1046
|
+
// router with the CORRECTED name before falling through to local parse;
|
|
1047
|
+
// it already no-ops when no routing flag is present. RUSH-2022 review r2.
|
|
1048
|
+
void (async () => {
|
|
1049
|
+
const { maybeRunOnHost } = await import('./lib/hosts/passthrough.js');
|
|
1050
|
+
if (await maybeRunOnHost(closest, args)) {
|
|
1051
|
+
process.exit(process.exitCode ?? 0);
|
|
1052
|
+
}
|
|
1053
|
+
program.parse(['node', 'agents', ...args]);
|
|
1054
|
+
})();
|
|
1039
1055
|
return;
|
|
1040
1056
|
}
|
|
1041
1057
|
console.error(`error: unknown command '${unknown}'`);
|
|
@@ -1074,6 +1090,9 @@ if (requestedCommand !== undefined && !helpOrVersionRequested && !requestedIsDis
|
|
|
1074
1090
|
// Register only the command(s) this invocation actually uses. Lazy commands
|
|
1075
1091
|
// (sessions/teams/cloud) are handled after applyGlobalHelpConventions below.
|
|
1076
1092
|
const isLazyRequest = requestedCommand !== undefined && LAZY_COMMAND_NAMES.has(requestedCommand);
|
|
1093
|
+
// Set when the requested name maps to no command: the lazy tree is then also
|
|
1094
|
+
// registered below so the spellcheck can suggest `sessions`/`teams`/`cloud`.
|
|
1095
|
+
let requestedIsUnknown = false;
|
|
1077
1096
|
if (requestedIsDisabled) {
|
|
1078
1097
|
// The brand turned this command off: register the full tree so the "did you
|
|
1079
1098
|
// mean" picker still works, then strip the disabled commands below so the
|
|
@@ -1087,6 +1106,7 @@ else if (requestedCommand !== undefined && !isLazyRequest) {
|
|
|
1087
1106
|
// spellcheck and edit-distance-1 auto-correct (the command:* handler above)
|
|
1088
1107
|
// see the same candidate set — and ordering — as main.
|
|
1089
1108
|
await registerAllEagerCommands();
|
|
1109
|
+
requestedIsUnknown = true;
|
|
1090
1110
|
}
|
|
1091
1111
|
}
|
|
1092
1112
|
// When requestedCommand is undefined (bare invocation, --version, --help, -h) no
|
|
@@ -1102,6 +1122,21 @@ if (isLazyRequest && !requestedIsDisabled) {
|
|
|
1102
1122
|
for (const loader of COMMAND_LOADERS[requestedCommand])
|
|
1103
1123
|
await reg(loader);
|
|
1104
1124
|
}
|
|
1125
|
+
else if (requestedIsUnknown) {
|
|
1126
|
+
// Unknown command: the lazy names must be candidates too, or `agents session`
|
|
1127
|
+
// (a typo for the very much lazy `sessions`) gets no "did you mean" at all —
|
|
1128
|
+
// the miss the RUSH-2022 report walked into. Registration order still mirrors
|
|
1129
|
+
// main: lazy commands come after applyGlobalHelpConventions.
|
|
1130
|
+
const seen = new Set();
|
|
1131
|
+
for (const name of LAZY_COMMAND_NAMES) {
|
|
1132
|
+
for (const loader of COMMAND_LOADERS[name] ?? []) {
|
|
1133
|
+
if (seen.has(loader))
|
|
1134
|
+
continue;
|
|
1135
|
+
seen.add(loader);
|
|
1136
|
+
await reg(loader);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1105
1140
|
// White-label: remove any commands this brand disabled so they resolve as
|
|
1106
1141
|
// unknown (the command:* handler then reports "unknown command"). Unbranded or
|
|
1107
1142
|
// nothing-disabled → no-op. Done after all registration paths above.
|
package/dist/lib/agents.js
CHANGED
|
@@ -279,6 +279,7 @@ export const AGENTS = {
|
|
|
279
279
|
color: 'cyan',
|
|
280
280
|
cliCommand: 'cursor-agent',
|
|
281
281
|
npmPackage: '',
|
|
282
|
+
cloudProvider: 'cursor',
|
|
282
283
|
installScript: 'curl https://cursor.com/install -fsS | bash && mv ~/.local/bin/agent ~/.local/bin/cursor-agent && grep -q "/.local/bin" ~/.zshrc || echo \'export PATH="$HOME/.local/bin:$PATH"\' >> ~/.zshrc',
|
|
283
284
|
configDir: path.join(HOME, '.cursor'),
|
|
284
285
|
commandsDir: path.join(HOME, '.cursor', 'commands'),
|
|
@@ -306,7 +307,7 @@ export const AGENTS = {
|
|
|
306
307
|
// See transformSubagentForCursor / https://cursor.com/docs/subagents.
|
|
307
308
|
// interactiveRepl: false — cursor-agent exits immediately with no argv. It requires a
|
|
308
309
|
// prompt to do anything useful; a bare invocation is not a REPL (RUSH-2185, EXEC-23a).
|
|
309
|
-
capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: true, commands: true, plugins: true, subagents: { since: '2026.1.22' }, rules: { file: '.cursorrules' }, workflows: false, memory: false, modes: ['edit', 'skip'], interactiveRepl: false }, // allowlist: ~/.cursor/cli-config.json
|
|
310
|
+
capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: true, commands: true, plugins: true, subagents: { since: '2026.1.22' }, rules: { file: '.cursorrules' }, workflows: false, memory: false, modes: ['plan', 'edit', 'skip'], interactiveRepl: false }, // allowlist: ~/.cursor/cli-config.json
|
|
310
311
|
},
|
|
311
312
|
opencode: {
|
|
312
313
|
id: 'opencode',
|
|
@@ -811,6 +812,75 @@ export const AGENTS = {
|
|
|
811
812
|
interactiveRepl: true,
|
|
812
813
|
},
|
|
813
814
|
},
|
|
815
|
+
// Warp Agent CLI (`oz`) — the standalone coding-agent CLI on Warp's "Oz"
|
|
816
|
+
// platform (the same shared Warp binary invoked via the `oz` symlink). Native
|
|
817
|
+
// binary via `brew install --cask oz` (macOS) / `oz-stable` apt|yum|pacman
|
|
818
|
+
// package (Linux); self-updating, no npm package. Config lives under `~/.warp/`
|
|
819
|
+
// (`.mcp.json`, `skills/`, `tab_configs/`, `worktrees/`, `remote-server`).
|
|
820
|
+
// Headless: `oz agent run --prompt "<task>" [--model <id>]` (local) /
|
|
821
|
+
// `oz agent run-cloud` (cloud). Auth: `oz login` (browser OAuth) or a
|
|
822
|
+
// `WARP_API_KEY` env token for headless/CI (`oz api-key create`). Rules/context
|
|
823
|
+
// file is `AGENTS.md` (also reads WARP.md / CLAUDE.md). Autonomy is governed by
|
|
824
|
+
// the selected agent profile (`--profile`), not a per-run permission flag.
|
|
825
|
+
// Sessions/conversations are stored SERVER-SIDE (retrieved with auth via
|
|
826
|
+
// `oz run conversation get <id>`), so there is no local transcript for
|
|
827
|
+
// `agents sessions` to index — warp is intentionally absent from SESSION_AGENTS.
|
|
828
|
+
// Docs: https://docs.warp.dev/reference/cli
|
|
829
|
+
warp: {
|
|
830
|
+
id: 'warp',
|
|
831
|
+
name: 'Warp',
|
|
832
|
+
color: 'blueBright',
|
|
833
|
+
cliCommand: 'oz',
|
|
834
|
+
npmPackage: '',
|
|
835
|
+
installScript: 'brew install --cask oz',
|
|
836
|
+
configDir: path.join(HOME, '.warp'),
|
|
837
|
+
commandsDir: '',
|
|
838
|
+
commandsSubdir: '',
|
|
839
|
+
skillsDir: path.join(HOME, '.warp', 'skills'),
|
|
840
|
+
hooksDir: 'hooks',
|
|
841
|
+
instructionsFile: 'AGENTS.md',
|
|
842
|
+
format: 'markdown',
|
|
843
|
+
variableSyntax: '$ARGUMENTS',
|
|
844
|
+
// Oz has no event->shell-command hook registration surface (its documented
|
|
845
|
+
// capabilities are agent-profiles-permissions, codebase-context,
|
|
846
|
+
// full-terminal-use, mcp, planning, rules, slash-commands — no hooks).
|
|
847
|
+
supportsHooks: false,
|
|
848
|
+
capabilities: {
|
|
849
|
+
hooks: false,
|
|
850
|
+
// MCP: Oz reads the Claude `{ "mcpServers": {...} }` schema from
|
|
851
|
+
// `.warp/.mcp.json` (user `~/.warp/.mcp.json`, project `<root>/.warp/.mcp.json`)
|
|
852
|
+
// and accepts `--mcp <path|json>` at run time; `oz mcp list` reads them
|
|
853
|
+
// back. stdio + http with headers, same schema as Claude's .mcp.json.
|
|
854
|
+
mcp: true,
|
|
855
|
+
mcpHttp: true,
|
|
856
|
+
mcpHeaders: true,
|
|
857
|
+
// Autonomy is governed by the selected agent profile
|
|
858
|
+
// (agent-profiles-permissions), not a Claude-style tool-name allow/deny
|
|
859
|
+
// list agents-cli can write — so no allowlist writer (mirrors muse).
|
|
860
|
+
allowlist: false,
|
|
861
|
+
// Skills: `--skill <spec>` + `oz agent skills`; searched in
|
|
862
|
+
// `.agents/skills/`, `.warp/skills/`, `.claude/skills/`, `.codex/skills/`.
|
|
863
|
+
skills: true,
|
|
864
|
+
// Slash-commands are native/server-managed (no droppable markdown
|
|
865
|
+
// command-file directory for agents-cli to sync into).
|
|
866
|
+
commands: false,
|
|
867
|
+
// No Claude marketplace / plugin manifest support.
|
|
868
|
+
plugins: false,
|
|
869
|
+
// Cloud agents + agent profiles are server-side; no installable
|
|
870
|
+
// subagent-definition directory to sync into (keeps the table truthful).
|
|
871
|
+
subagents: false,
|
|
872
|
+
rules: { file: 'AGENTS.md' },
|
|
873
|
+
workflows: false,
|
|
874
|
+
memory: false,
|
|
875
|
+
// Oz has no per-run permission flag; a single autonomous mode maps to no
|
|
876
|
+
// flags (the `--profile` selection governs autonomy). Mirrors hermes.
|
|
877
|
+
modes: ['edit'],
|
|
878
|
+
rulesImports: false,
|
|
879
|
+
// `oz agent run` requires a prompt (--prompt/--saved-prompt/--task-id/
|
|
880
|
+
// --skill), so a bare invocation opens no REPL.
|
|
881
|
+
interactiveRepl: false,
|
|
882
|
+
},
|
|
883
|
+
},
|
|
814
884
|
};
|
|
815
885
|
/** All current and legacy agent IDs derived from the AGENTS registry. */
|
|
816
886
|
export const ALL_AGENT_IDS = Object.keys(AGENTS);
|
|
@@ -1507,11 +1577,16 @@ function isValidOpenCodeCredential(value) {
|
|
|
1507
1577
|
}
|
|
1508
1578
|
/**
|
|
1509
1579
|
* Whether a Muse Code `~/.config/muse/auth.json` document holds any usable
|
|
1510
|
-
* access token.
|
|
1511
|
-
*
|
|
1512
|
-
*
|
|
1580
|
+
* access token. Live shape from `muse login` (device OAuth, Muse Code 0.1.0):
|
|
1581
|
+
* { schema_version: 1, providers: { meta: { access_token, api_key, user_email, … } } }
|
|
1582
|
+
* Also accept top-level or one-level-nested tokens for older/alternate writers.
|
|
1583
|
+
* Recurses into objects so `providers.meta.access_token` is found — a flat
|
|
1584
|
+
* one-level walk only saw `providers` and reported signed-out after a successful
|
|
1585
|
+
* login (balanced then excluded the account). Never returns the secret itself.
|
|
1513
1586
|
*/
|
|
1514
|
-
function museAuthHasToken(value) {
|
|
1587
|
+
function museAuthHasToken(value, depth = 0) {
|
|
1588
|
+
if (depth > 4)
|
|
1589
|
+
return false;
|
|
1515
1590
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
1516
1591
|
return false;
|
|
1517
1592
|
const root = value;
|
|
@@ -1520,16 +1595,31 @@ function museAuthHasToken(value) {
|
|
|
1520
1595
|
if (typeof root.api_key === 'string' && root.api_key.length > 0)
|
|
1521
1596
|
return true;
|
|
1522
1597
|
for (const slot of Object.values(root)) {
|
|
1523
|
-
if (
|
|
1524
|
-
continue;
|
|
1525
|
-
const entry = slot;
|
|
1526
|
-
if (typeof entry.access_token === 'string' && entry.access_token.length > 0)
|
|
1527
|
-
return true;
|
|
1528
|
-
if (typeof entry.api_key === 'string' && entry.api_key.length > 0)
|
|
1598
|
+
if (museAuthHasToken(slot, depth + 1))
|
|
1529
1599
|
return true;
|
|
1530
1600
|
}
|
|
1531
1601
|
return false;
|
|
1532
1602
|
}
|
|
1603
|
+
/** Best-effort email from a Muse auth.json (providers.meta.user_email, etc.). */
|
|
1604
|
+
function museAuthEmail(value, depth = 0) {
|
|
1605
|
+
if (depth > 4)
|
|
1606
|
+
return null;
|
|
1607
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
1608
|
+
return null;
|
|
1609
|
+
const root = value;
|
|
1610
|
+
if (typeof root.user_email === 'string' && root.user_email.includes('@')) {
|
|
1611
|
+
return root.user_email;
|
|
1612
|
+
}
|
|
1613
|
+
if (typeof root.email === 'string' && root.email.includes('@')) {
|
|
1614
|
+
return root.email;
|
|
1615
|
+
}
|
|
1616
|
+
for (const slot of Object.values(root)) {
|
|
1617
|
+
const found = museAuthEmail(slot, depth + 1);
|
|
1618
|
+
if (found)
|
|
1619
|
+
return found;
|
|
1620
|
+
}
|
|
1621
|
+
return null;
|
|
1622
|
+
}
|
|
1533
1623
|
/**
|
|
1534
1624
|
* Whether a Claude version home's credential file is present but carries no
|
|
1535
1625
|
* token — the "must have a real credential" floor (see
|
|
@@ -1924,10 +2014,8 @@ export async function getAccountInfo(agentId, home) {
|
|
|
1924
2014
|
}
|
|
1925
2015
|
case 'muse': {
|
|
1926
2016
|
// Muse Code authenticates with META_API_KEY (env, highest priority) or
|
|
1927
|
-
// a stored OAuth/API credential at ~/.config/muse/auth.json.
|
|
1928
|
-
//
|
|
1929
|
-
// access_token; there is no email claim, so we report signed-in + a
|
|
1930
|
-
// stable identity key (env-key vs provider slots) like kimi/opencode.
|
|
2017
|
+
// a stored OAuth/API credential at ~/.config/muse/auth.json. Live file
|
|
2018
|
+
// shape nests under providers.meta (access_token + optional user_email).
|
|
1931
2019
|
if (process.env.META_API_KEY?.trim() || process.env.MODEL_API_KEY?.trim()) {
|
|
1932
2020
|
const accountKey = buildIdentityKey(agentId, [['auth', 'env']]);
|
|
1933
2021
|
return { ...empty, signedIn: true, accountId: 'env', accountKey, lastActive };
|
|
@@ -1938,13 +2026,18 @@ export async function getAccountInfo(agentId, home) {
|
|
|
1938
2026
|
const data = JSON.parse(await fs.promises.readFile(authPath, 'utf-8'));
|
|
1939
2027
|
if (!data || typeof data !== 'object')
|
|
1940
2028
|
return { ...empty, lastActive };
|
|
1941
|
-
|
|
1942
|
-
// Any object with a nested access_token (or a top-level one) counts.
|
|
1943
|
-
const hasToken = museAuthHasToken(data);
|
|
1944
|
-
if (!hasToken)
|
|
2029
|
+
if (!museAuthHasToken(data))
|
|
1945
2030
|
return { ...empty, lastActive };
|
|
1946
|
-
const
|
|
1947
|
-
|
|
2031
|
+
const email = museAuthEmail(data);
|
|
2032
|
+
const accountKey = buildIdentityKey(agentId, email ? [['email', email]] : [['auth', 'file']]);
|
|
2033
|
+
return {
|
|
2034
|
+
...empty,
|
|
2035
|
+
signedIn: true,
|
|
2036
|
+
email,
|
|
2037
|
+
accountId: email ?? 'file',
|
|
2038
|
+
accountKey,
|
|
2039
|
+
lastActive,
|
|
2040
|
+
};
|
|
1948
2041
|
}
|
|
1949
2042
|
default:
|
|
1950
2043
|
return { ...empty, lastActive };
|
|
@@ -2563,6 +2656,9 @@ export function getUserMcpConfigPath(agentId) {
|
|
|
2563
2656
|
case 'muse':
|
|
2564
2657
|
// Muse Code: MCP lives in ~/.config/muse/settings.json under mcp_servers.
|
|
2565
2658
|
return path.join(agent.configDir, 'settings.json');
|
|
2659
|
+
case 'warp':
|
|
2660
|
+
// Oz reads user-scope MCP from ~/.warp/.mcp.json (Claude schema).
|
|
2661
|
+
return path.join(agent.configDir, '.mcp.json');
|
|
2566
2662
|
default:
|
|
2567
2663
|
// Gemini and others use settings.json
|
|
2568
2664
|
return path.join(agent.configDir, 'settings.json');
|
|
@@ -2603,6 +2699,8 @@ export function getMcpConfigPathForHome(agentId, home) {
|
|
|
2603
2699
|
return path.join(home, '.omp', 'agent', '.mcp.json');
|
|
2604
2700
|
case 'muse':
|
|
2605
2701
|
return path.join(home, '.config', 'muse', 'settings.json');
|
|
2702
|
+
case 'warp':
|
|
2703
|
+
return path.join(home, '.warp', '.mcp.json');
|
|
2606
2704
|
default:
|
|
2607
2705
|
return path.join(home, agentConfigDirName(agentId), 'settings.json');
|
|
2608
2706
|
}
|
|
@@ -2649,6 +2747,9 @@ export function getProjectMcpConfigPath(agentId, cwd = process.cwd()) {
|
|
|
2649
2747
|
// Muse project MCP rides the same settings schema under .muse/settings.json
|
|
2650
2748
|
// when present; otherwise fall back to the user settings path.
|
|
2651
2749
|
return path.join(cwd, '.muse', 'settings.json');
|
|
2750
|
+
case 'warp':
|
|
2751
|
+
// Oz reads project MCP from <root>/.warp/.mcp.json (Claude schema).
|
|
2752
|
+
return path.join(cwd, '.warp', '.mcp.json');
|
|
2652
2753
|
default:
|
|
2653
2754
|
return path.join(cwd, `.${agentId}`, 'settings.json');
|
|
2654
2755
|
}
|
|
@@ -2796,6 +2897,10 @@ export const AGENT_NAME_ALIASES = {
|
|
|
2796
2897
|
'muse-code': 'muse',
|
|
2797
2898
|
'muse-spark': 'muse',
|
|
2798
2899
|
'meta-muse': 'muse',
|
|
2900
|
+
warp: 'warp',
|
|
2901
|
+
oz: 'warp',
|
|
2902
|
+
'warp-agent': 'warp',
|
|
2903
|
+
'warp-cli': 'warp',
|
|
2799
2904
|
};
|
|
2800
2905
|
/**
|
|
2801
2906
|
* Resolve a user-provided agent name (alias, shorthand, or canonical) to its AgentId.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** Cursor Cloud Agents provider backed by the public v1 REST API. */
|
|
2
|
+
import type { CloudEvent, CloudProvider, CloudTask, CloudTaskStatus, DispatchOptions, ProviderCapabilities } from './types.js';
|
|
3
|
+
export declare const CURSOR_API_BASE_URL = "https://api.cursor.com/v1";
|
|
4
|
+
interface CursorAgent {
|
|
5
|
+
id: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
status: 'ACTIVE' | 'ARCHIVED';
|
|
8
|
+
createdAt: string;
|
|
9
|
+
updatedAt: string;
|
|
10
|
+
latestRunId?: string;
|
|
11
|
+
repos?: Array<{
|
|
12
|
+
url: string;
|
|
13
|
+
startingRef?: string;
|
|
14
|
+
prUrl?: string;
|
|
15
|
+
}>;
|
|
16
|
+
}
|
|
17
|
+
interface CursorRun {
|
|
18
|
+
id: string;
|
|
19
|
+
agentId: string;
|
|
20
|
+
status: string;
|
|
21
|
+
createdAt: string;
|
|
22
|
+
updatedAt: string;
|
|
23
|
+
result?: string;
|
|
24
|
+
git?: {
|
|
25
|
+
branches?: Array<{
|
|
26
|
+
repoUrl: string;
|
|
27
|
+
branch?: string;
|
|
28
|
+
prUrl?: string;
|
|
29
|
+
}>;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export interface CursorCreateBody {
|
|
33
|
+
prompt: {
|
|
34
|
+
text: string;
|
|
35
|
+
images?: Array<{
|
|
36
|
+
data: string;
|
|
37
|
+
mimeType: string;
|
|
38
|
+
}>;
|
|
39
|
+
};
|
|
40
|
+
model?: {
|
|
41
|
+
id: string;
|
|
42
|
+
};
|
|
43
|
+
repos?: Array<{
|
|
44
|
+
url: string;
|
|
45
|
+
startingRef?: string;
|
|
46
|
+
}>;
|
|
47
|
+
autoCreatePR?: boolean;
|
|
48
|
+
envVars?: Record<string, string>;
|
|
49
|
+
}
|
|
50
|
+
/** Translate unified dispatch options into Cursor's documented v1 create shape. */
|
|
51
|
+
export declare function buildCursorCreateBody(options: DispatchOptions): CursorCreateBody;
|
|
52
|
+
/** Map one Cursor agent/run pair into the provider-neutral task shape. */
|
|
53
|
+
export declare function parseCursorTask(agent: CursorAgent, run: CursorRun, prompt?: string): CloudTask;
|
|
54
|
+
/** Parse one complete SSE frame into a shared cloud event. */
|
|
55
|
+
export declare function parseCursorSseFrame(frame: string): CloudEvent | undefined;
|
|
56
|
+
/** Turn Cursor's structured API errors into actionable provider errors. */
|
|
57
|
+
export declare function cursorApiError(action: string, status: number, text: string): Error;
|
|
58
|
+
export declare class CursorCloudProvider implements CloudProvider {
|
|
59
|
+
id: "cursor";
|
|
60
|
+
name: string;
|
|
61
|
+
private secretsBundle?;
|
|
62
|
+
constructor(config?: {
|
|
63
|
+
secretsBundle?: string;
|
|
64
|
+
});
|
|
65
|
+
private hasKeySource;
|
|
66
|
+
private resolveApiKey;
|
|
67
|
+
capabilities(): ProviderCapabilities;
|
|
68
|
+
private request;
|
|
69
|
+
private latestRun;
|
|
70
|
+
dispatch(options: DispatchOptions): Promise<CloudTask>;
|
|
71
|
+
status(taskId: string): Promise<CloudTask>;
|
|
72
|
+
list(filter?: {
|
|
73
|
+
status?: CloudTaskStatus;
|
|
74
|
+
}): Promise<CloudTask[]>;
|
|
75
|
+
stream(taskId: string): AsyncIterable<CloudEvent>;
|
|
76
|
+
cancel(taskId: string): Promise<void>;
|
|
77
|
+
message(taskId: string, content: string): Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
export {};
|