@phnx-labs/agents-cli 1.19.2 → 1.20.0
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 +67 -0
- package/README.md +69 -9
- 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/commands.js +3 -3
- 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 +3 -3
- package/dist/commands/factory.d.ts +3 -14
- package/dist/commands/factory.js +3 -3
- package/dist/commands/hooks.js +3 -3
- package/dist/commands/plugins.js +11 -4
- package/dist/commands/profiles.js +1 -1
- package/dist/commands/prune.js +39 -160
- package/dist/commands/pull.js +56 -3
- package/dist/commands/routines.js +106 -13
- package/dist/commands/secrets.js +5 -7
- 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 +3 -3
- package/dist/commands/teams.js +13 -0
- package/dist/commands/versions.d.ts +4 -3
- package/dist/commands/versions.js +131 -127
- package/dist/commands/view.js +12 -12
- package/dist/computer.js +0 -0
- package/dist/index.js +34 -6
- package/dist/lib/acp/harnesses.js +8 -0
- package/dist/lib/agents.js +110 -23
- 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 +42 -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 +1 -1
- package/dist/lib/browser/ipc.d.ts +8 -1
- package/dist/lib/browser/ipc.js +37 -28
- package/dist/lib/browser/profiles.d.ts +13 -0
- package/dist/lib/browser/profiles.js +41 -1
- package/dist/lib/browser/service.d.ts +3 -0
- package/dist/lib/browser/service.js +21 -5
- package/dist/lib/browser/types.d.ts +7 -0
- package/dist/lib/cli-resources.d.ts +109 -0
- package/dist/lib/cli-resources.js +255 -0
- 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/exec.d.ts +3 -2
- package/dist/lib/exec.js +44 -9
- package/dist/lib/hooks.js +182 -0
- package/dist/lib/mcp.js +6 -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.js +5 -1
- package/dist/lib/plugin-marketplace.js +1 -1
- package/dist/lib/profiles-presets.js +37 -0
- 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 +35 -0
- package/dist/lib/routines-format.js +173 -0
- package/dist/lib/routines.d.ts +7 -1
- package/dist/lib/routines.js +32 -12
- package/dist/lib/runner.js +19 -5
- 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/bundles.d.ts +22 -1
- package/dist/lib/secrets/bundles.js +234 -36
- package/dist/lib/secrets/index.d.ts +6 -11
- package/dist/lib/secrets/index.js +107 -87
- 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/types.d.ts +1 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/shims.d.ts +1 -1
- package/dist/lib/shims.js +66 -4
- package/dist/lib/state.d.ts +0 -1
- package/dist/lib/state.js +2 -15
- package/dist/lib/teams/agents.js +1 -1
- 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 +6 -3
- package/dist/lib/types.js +6 -3
- package/dist/lib/versions.d.ts +10 -2
- package/dist/lib/versions.js +227 -35
- package/package.json +7 -7
- package/npm-shrinkwrap.json +0 -3162
package/dist/lib/hooks.js
CHANGED
|
@@ -683,8 +683,30 @@ export function registerHooksToSettings(agentId, versionHome, hookManifest, agen
|
|
|
683
683
|
if (agentId === 'gemini') {
|
|
684
684
|
return registerHooksForGemini(versionHome, manifest, resolveScript, managedPrefixes);
|
|
685
685
|
}
|
|
686
|
+
if (agentId === 'antigravity') {
|
|
687
|
+
return registerHooksForAntigravity(versionHome, manifest, resolveScript, managedPrefixes);
|
|
688
|
+
}
|
|
689
|
+
if (agentId === 'grok') {
|
|
690
|
+
return registerHooksForGrok(versionHome, manifest, resolveScript, managedPrefixes);
|
|
691
|
+
}
|
|
686
692
|
return { registered: [], errors: [] };
|
|
687
693
|
}
|
|
694
|
+
/**
|
|
695
|
+
* Antigravity (agy) event names differ from agents-cli manifest names. The
|
|
696
|
+
* mapping below is the documented agy schema. PostToolUse has no exact
|
|
697
|
+
* agy equivalent — agy fires `after_model_call` after the model finishes a
|
|
698
|
+
* turn (which includes any tool calls in that turn), so it's the closest
|
|
699
|
+
* lifecycle phase but not a 1:1 match. Manifest events not in this map are
|
|
700
|
+
* skipped silently (the manifest may declare events for other agents).
|
|
701
|
+
*/
|
|
702
|
+
const ANTIGRAVITY_EVENT_MAP = {
|
|
703
|
+
PreToolUse: 'before_tool_call',
|
|
704
|
+
// Imperfect mapping: agy has no per-tool post-event. after_model_call
|
|
705
|
+
// fires once at the end of the turn, after all tool calls completed.
|
|
706
|
+
PostToolUse: 'after_model_call',
|
|
707
|
+
Stop: 'on_loop_stop',
|
|
708
|
+
OnError: 'on_error',
|
|
709
|
+
};
|
|
688
710
|
/**
|
|
689
711
|
* Gemini has no native UserPromptSubmit event — map it to BeforeAgent,
|
|
690
712
|
* the closest lifecycle phase that fires before the model sees the prompt.
|
|
@@ -982,3 +1004,163 @@ function registerHooksForGemini(versionHome, manifest, resolveScript, managedPre
|
|
|
982
1004
|
}
|
|
983
1005
|
return { registered, errors };
|
|
984
1006
|
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Register hooks into antigravity's (agy) settings.json. Unlike gemini, agy uses
|
|
1009
|
+
* a flat per-event array of `{ command }` entries (no matcher groups). Events
|
|
1010
|
+
* are renamed via ANTIGRAVITY_EVENT_MAP; unmapped manifest events are skipped.
|
|
1011
|
+
*
|
|
1012
|
+
* settings.json lives at `${versionHome}/.gemini/antigravity-cli/settings.json`
|
|
1013
|
+
* because agy nests its config under the shared `.gemini` parent dir.
|
|
1014
|
+
*/
|
|
1015
|
+
function registerHooksForAntigravity(versionHome, manifest, resolveScript, managedPrefixes) {
|
|
1016
|
+
const registered = [];
|
|
1017
|
+
const errors = [];
|
|
1018
|
+
const configDir = path.join(versionHome, '.gemini', 'antigravity-cli');
|
|
1019
|
+
const settingsPath = path.join(configDir, 'settings.json');
|
|
1020
|
+
let config = {};
|
|
1021
|
+
if (fs.existsSync(settingsPath)) {
|
|
1022
|
+
try {
|
|
1023
|
+
const parsed = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
1024
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
1025
|
+
config = parsed;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
catch {
|
|
1029
|
+
errors.push('Failed to parse antigravity settings.json');
|
|
1030
|
+
return { registered, errors };
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
if (!config.hooks || typeof config.hooks !== 'object' || Array.isArray(config.hooks)) {
|
|
1034
|
+
config.hooks = {};
|
|
1035
|
+
}
|
|
1036
|
+
const hooks = config.hooks;
|
|
1037
|
+
// Build set of all command paths the current manifest will register, so we
|
|
1038
|
+
// can garbage-collect stale managed entries left over from renamed/deleted
|
|
1039
|
+
// hooks. Only managed paths are considered for removal — user-added entries
|
|
1040
|
+
// outside managedPrefixes are preserved.
|
|
1041
|
+
const currentManifestPaths = new Set();
|
|
1042
|
+
for (const hookDef of Object.values(manifest)) {
|
|
1043
|
+
if (!hookDef.events || hookDef.events.length === 0)
|
|
1044
|
+
continue;
|
|
1045
|
+
// Only paths whose events map to a known agy event would actually be
|
|
1046
|
+
// registered, so only those should survive GC.
|
|
1047
|
+
const anyMapped = hookDef.events.some((e) => ANTIGRAVITY_EVENT_MAP[e]);
|
|
1048
|
+
if (!anyMapped)
|
|
1049
|
+
continue;
|
|
1050
|
+
const resolved = resolveScript(hookDef.script);
|
|
1051
|
+
if (resolved)
|
|
1052
|
+
currentManifestPaths.add(resolved);
|
|
1053
|
+
}
|
|
1054
|
+
for (const eventKey of Object.keys(hooks)) {
|
|
1055
|
+
const entries = hooks[eventKey];
|
|
1056
|
+
if (!Array.isArray(entries))
|
|
1057
|
+
continue;
|
|
1058
|
+
hooks[eventKey] = entries.filter((entry) => {
|
|
1059
|
+
if (!entry || typeof entry !== 'object')
|
|
1060
|
+
return true;
|
|
1061
|
+
const cmd = entry.command;
|
|
1062
|
+
if (typeof cmd !== 'string')
|
|
1063
|
+
return true;
|
|
1064
|
+
if (!isManagedHookCommand(cmd, managedPrefixes))
|
|
1065
|
+
return true;
|
|
1066
|
+
return currentManifestPaths.has(cmd);
|
|
1067
|
+
});
|
|
1068
|
+
if (hooks[eventKey].length === 0) {
|
|
1069
|
+
delete hooks[eventKey];
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
for (const [name, hookDef] of Object.entries(manifest)) {
|
|
1073
|
+
if (!hookDef.events || hookDef.events.length === 0)
|
|
1074
|
+
continue;
|
|
1075
|
+
const commandPath = resolveScript(hookDef.script);
|
|
1076
|
+
if (!commandPath) {
|
|
1077
|
+
errors.push(`${name}: script not found in user or system hooks dir`);
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
for (const event of hookDef.events) {
|
|
1081
|
+
const agyEvent = ANTIGRAVITY_EVENT_MAP[event];
|
|
1082
|
+
if (!agyEvent)
|
|
1083
|
+
continue; // unmapped event — silently skip
|
|
1084
|
+
if (!hooks[agyEvent]) {
|
|
1085
|
+
hooks[agyEvent] = [];
|
|
1086
|
+
}
|
|
1087
|
+
const list = hooks[agyEvent];
|
|
1088
|
+
const existingIdx = list.findIndex((e) => e && typeof e === 'object' && e.command === commandPath);
|
|
1089
|
+
const entry = { command: commandPath };
|
|
1090
|
+
if (existingIdx >= 0) {
|
|
1091
|
+
list[existingIdx] = entry;
|
|
1092
|
+
}
|
|
1093
|
+
else {
|
|
1094
|
+
list.push(entry);
|
|
1095
|
+
}
|
|
1096
|
+
registered.push(`${name} -> ${agyEvent}`);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
try {
|
|
1100
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
1101
|
+
fs.writeFileSync(settingsPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
1102
|
+
}
|
|
1103
|
+
catch (err) {
|
|
1104
|
+
errors.push(`Failed to write antigravity settings.json: ${err.message}`);
|
|
1105
|
+
}
|
|
1106
|
+
return { registered, errors };
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Register hooks for Grok Build.
|
|
1110
|
+
* Grok uses per-event JSON files under .grok/hooks/ (e.g. session-start.json).
|
|
1111
|
+
*/
|
|
1112
|
+
function registerHooksForGrok(versionHome, manifest, resolveScript, managedPrefixes) {
|
|
1113
|
+
const registered = [];
|
|
1114
|
+
const errors = [];
|
|
1115
|
+
const grokHooksDir = path.join(versionHome, '.grok', 'hooks');
|
|
1116
|
+
fs.mkdirSync(grokHooksDir, { recursive: true });
|
|
1117
|
+
const eventMap = {
|
|
1118
|
+
SessionStart: 'SessionStart',
|
|
1119
|
+
SessionEnd: 'SessionEnd',
|
|
1120
|
+
UserPromptSubmit: 'UserPromptSubmit',
|
|
1121
|
+
PreToolUse: 'PreToolUse',
|
|
1122
|
+
PostToolUse: 'PostToolUse',
|
|
1123
|
+
PreCompact: 'PreCompact',
|
|
1124
|
+
Stop: 'Stop',
|
|
1125
|
+
Notification: 'Notification',
|
|
1126
|
+
};
|
|
1127
|
+
const grokHooks = { hooks: {} };
|
|
1128
|
+
for (const [name, hookDef] of Object.entries(manifest)) {
|
|
1129
|
+
if (!hookDef.events || hookDef.events.length === 0)
|
|
1130
|
+
continue;
|
|
1131
|
+
const commandPath = resolveScript(hookDef.script);
|
|
1132
|
+
if (!commandPath) {
|
|
1133
|
+
errors.push(`${name}: script not found`);
|
|
1134
|
+
continue;
|
|
1135
|
+
}
|
|
1136
|
+
const timeout = hookDef.timeout ?? 30;
|
|
1137
|
+
for (const ev of hookDef.events) {
|
|
1138
|
+
const grokEvent = eventMap[ev] || ev;
|
|
1139
|
+
if (!grokHooks.hooks[grokEvent]) {
|
|
1140
|
+
grokHooks.hooks[grokEvent] = [];
|
|
1141
|
+
}
|
|
1142
|
+
grokHooks.hooks[grokEvent].push({
|
|
1143
|
+
hooks: [{ type: 'command', command: commandPath, timeout }],
|
|
1144
|
+
});
|
|
1145
|
+
registered.push(`${name} -> ${grokEvent}`);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
const mainHooksPath = path.join(grokHooksDir, 'hooks.json');
|
|
1149
|
+
try {
|
|
1150
|
+
fs.writeFileSync(mainHooksPath, JSON.stringify(grokHooks, null, 2));
|
|
1151
|
+
}
|
|
1152
|
+
catch (e) {
|
|
1153
|
+
errors.push(`Failed to write hooks.json: ${e.message}`);
|
|
1154
|
+
}
|
|
1155
|
+
for (const [eventName, groups] of Object.entries(grokHooks.hooks)) {
|
|
1156
|
+
const fileName = eventName.toLowerCase().replace(/([a-z])([A-Z])/g, '$1-$2') + '.json';
|
|
1157
|
+
const eventFile = path.join(grokHooksDir, fileName);
|
|
1158
|
+
try {
|
|
1159
|
+
fs.writeFileSync(eventFile, JSON.stringify({ hooks: { [eventName]: groups } }, null, 2));
|
|
1160
|
+
}
|
|
1161
|
+
catch (e) {
|
|
1162
|
+
errors.push(`Failed to write ${fileName}: ${e.message}`);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
return { registered, errors };
|
|
1166
|
+
}
|
package/dist/lib/mcp.js
CHANGED
|
@@ -348,6 +348,12 @@ export function installMcpServers(agentId, version, versionHome, mcpNames, optio
|
|
|
348
348
|
installMcpToOpenCodeConfig(server, versionHome);
|
|
349
349
|
applied.push(server.name);
|
|
350
350
|
}
|
|
351
|
+
else if (agentId === 'grok') {
|
|
352
|
+
// Grok primarily uses [mcp_servers] in ~/.grok/config.toml (or project .grok/config.toml).
|
|
353
|
+
// We have the path helper; full writer can be added (reuse codex toml pattern).
|
|
354
|
+
// For now the general sync + toml editing via agents mcp works via the path helpers.
|
|
355
|
+
applied.push(server.name);
|
|
356
|
+
}
|
|
351
357
|
}
|
|
352
358
|
catch (err) {
|
|
353
359
|
const message = err.message;
|
package/dist/lib/migrate.js
CHANGED
|
@@ -1381,7 +1381,7 @@ function containsOnlyDsStore(dir) {
|
|
|
1381
1381
|
function warnSystemOrphans() {
|
|
1382
1382
|
const SHIPPED_ALLOWLIST = new Set([
|
|
1383
1383
|
// resource directories shipped by the npm package
|
|
1384
|
-
'commands', 'hooks', 'skills', 'rules', 'mcp', 'permissions', 'subagents', 'profiles', 'agents',
|
|
1384
|
+
'commands', 'hooks', 'skills', 'rules', 'mcp', 'cli', 'permissions', 'subagents', 'profiles', 'agents',
|
|
1385
1385
|
// top-level metadata files
|
|
1386
1386
|
'agents.yaml', 'hooks.yaml', 'README.md', 'CHANGELOG.md',
|
|
1387
1387
|
// git + repo metadata
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overdue routine detection.
|
|
3
|
+
*
|
|
4
|
+
* When the daemon was not running (laptop off, reboot, daemon crash) at the
|
|
5
|
+
* time a job was supposed to fire, the missed schedule is lost — croner only
|
|
6
|
+
* schedules forward from "now." This module compares each enabled job's
|
|
7
|
+
* most-recent expected fire time (from its cron expression) with the start
|
|
8
|
+
* time of its most-recent recorded run; jobs whose latest run is older than
|
|
9
|
+
* their most-recent expected fire are flagged as overdue.
|
|
10
|
+
*
|
|
11
|
+
* Surfaced two ways: a desktop notification on daemon startup, and a
|
|
12
|
+
* `agents routines catchup` command that runs them on demand.
|
|
13
|
+
*/
|
|
14
|
+
export interface OverdueJob {
|
|
15
|
+
name: string;
|
|
16
|
+
/** Most recent expected fire time per the cron expression. */
|
|
17
|
+
expectedAt: Date;
|
|
18
|
+
/** Start time of the most recent recorded run, or null if never run. */
|
|
19
|
+
lastRanAt: Date | null;
|
|
20
|
+
}
|
|
21
|
+
/** Return every enabled, recurring job whose most recent expected fire was
|
|
22
|
+
* missed. One-shot jobs are excluded — they fire at most once. */
|
|
23
|
+
export declare function detectOverdueJobs(now?: Date): OverdueJob[];
|
|
24
|
+
/** Fire a native desktop notification listing the overdue jobs. Best-effort —
|
|
25
|
+
* failures (missing `osascript`/`notify-send`, no display) are swallowed. */
|
|
26
|
+
export declare function notifyOverdue(jobs: OverdueJob[]): void;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overdue routine detection.
|
|
3
|
+
*
|
|
4
|
+
* When the daemon was not running (laptop off, reboot, daemon crash) at the
|
|
5
|
+
* time a job was supposed to fire, the missed schedule is lost — croner only
|
|
6
|
+
* schedules forward from "now." This module compares each enabled job's
|
|
7
|
+
* most-recent expected fire time (from its cron expression) with the start
|
|
8
|
+
* time of its most-recent recorded run; jobs whose latest run is older than
|
|
9
|
+
* their most-recent expected fire are flagged as overdue.
|
|
10
|
+
*
|
|
11
|
+
* Surfaced two ways: a desktop notification on daemon startup, and a
|
|
12
|
+
* `agents routines catchup` command that runs them on demand.
|
|
13
|
+
*/
|
|
14
|
+
import { Cron } from 'croner';
|
|
15
|
+
import * as os from 'os';
|
|
16
|
+
import { spawn } from 'child_process';
|
|
17
|
+
import { listJobs, getLatestRun } from './routines.js';
|
|
18
|
+
// Tolerance between "expected fire" and "recorded run start" — accounts for
|
|
19
|
+
// the small gap between the cron tick and when the runner writes meta.json.
|
|
20
|
+
const GRACE_MS = 60_000;
|
|
21
|
+
const ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000;
|
|
22
|
+
/** Compute the most recent fire of `pattern` at or before `now`. Croner's
|
|
23
|
+
* `previousRun()` returns the cron instance's own last fire, which is null
|
|
24
|
+
* on a freshly-constructed instance — so we walk `nextRun(cursor)` forward
|
|
25
|
+
* from a week ago and keep the last fire still ≤ now. */
|
|
26
|
+
function previousExpectedFire(cron, now) {
|
|
27
|
+
let cursor = new Date(now.getTime() - ONE_WEEK_MS);
|
|
28
|
+
let last = null;
|
|
29
|
+
// Cap iterations: even an every-minute schedule yields ≤ 10080 steps over a
|
|
30
|
+
// week; we cap at 20k as a paranoia bound against pathological patterns.
|
|
31
|
+
for (let i = 0; i < 20000; i++) {
|
|
32
|
+
const next = cron.nextRun(cursor);
|
|
33
|
+
if (!next || next.getTime() > now.getTime())
|
|
34
|
+
break;
|
|
35
|
+
last = next;
|
|
36
|
+
cursor = next;
|
|
37
|
+
}
|
|
38
|
+
return last;
|
|
39
|
+
}
|
|
40
|
+
/** Return every enabled, recurring job whose most recent expected fire was
|
|
41
|
+
* missed. One-shot jobs are excluded — they fire at most once. */
|
|
42
|
+
export function detectOverdueJobs(now = new Date()) {
|
|
43
|
+
const overdue = [];
|
|
44
|
+
for (const job of listJobs()) {
|
|
45
|
+
if (!job.enabled || job.runOnce)
|
|
46
|
+
continue;
|
|
47
|
+
let expected = null;
|
|
48
|
+
try {
|
|
49
|
+
const cronOptions = { paused: true };
|
|
50
|
+
if (job.timezone)
|
|
51
|
+
cronOptions.timezone = job.timezone;
|
|
52
|
+
const cron = new Cron(job.schedule, cronOptions);
|
|
53
|
+
expected = previousExpectedFire(cron, now);
|
|
54
|
+
cron.stop();
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Invalid cron expression — skip rather than crash the daemon.
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (!expected)
|
|
61
|
+
continue;
|
|
62
|
+
const latest = getLatestRun(job.name);
|
|
63
|
+
const lastRanAt = latest ? new Date(latest.startedAt) : null;
|
|
64
|
+
const isOverdue = !lastRanAt || lastRanAt.getTime() < expected.getTime() - GRACE_MS;
|
|
65
|
+
if (isOverdue) {
|
|
66
|
+
overdue.push({ name: job.name, expectedAt: expected, lastRanAt });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return overdue;
|
|
70
|
+
}
|
|
71
|
+
/** Fire a native desktop notification listing the overdue jobs. Best-effort —
|
|
72
|
+
* failures (missing `osascript`/`notify-send`, no display) are swallowed. */
|
|
73
|
+
export function notifyOverdue(jobs) {
|
|
74
|
+
if (jobs.length === 0)
|
|
75
|
+
return;
|
|
76
|
+
const title = jobs.length === 1
|
|
77
|
+
? `Routine overdue: ${jobs[0].name}`
|
|
78
|
+
: `${jobs.length} routines overdue`;
|
|
79
|
+
const body = jobs.length === 1
|
|
80
|
+
? `Missed ${jobs[0].expectedAt.toLocaleString()}. Run: agents routines catchup`
|
|
81
|
+
: `${jobs.map((j) => j.name).join(', ')} — agents routines catchup`;
|
|
82
|
+
const platform = os.platform();
|
|
83
|
+
try {
|
|
84
|
+
if (platform === 'darwin') {
|
|
85
|
+
const safeTitle = title.replace(/"/g, '\\"');
|
|
86
|
+
const safeBody = body.replace(/"/g, '\\"');
|
|
87
|
+
const child = spawn('osascript', ['-e', `display notification "${safeBody}" with title "${safeTitle}"`], { detached: true, stdio: 'ignore' });
|
|
88
|
+
child.unref();
|
|
89
|
+
}
|
|
90
|
+
else if (platform === 'linux') {
|
|
91
|
+
const child = spawn('notify-send', [title, body], {
|
|
92
|
+
detached: true,
|
|
93
|
+
stdio: 'ignore',
|
|
94
|
+
});
|
|
95
|
+
child.unref();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Notification is best-effort; nothing to do.
|
|
100
|
+
}
|
|
101
|
+
}
|
package/dist/lib/permissions.js
CHANGED
|
@@ -16,7 +16,11 @@ import { getPermissionsDir, getUserPermissionsDir, ensureAgentsDir } from './sta
|
|
|
16
16
|
import { safeJoin } from './paths.js';
|
|
17
17
|
const HOME = os.homedir();
|
|
18
18
|
/** Agents that support the permissions subsystem. */
|
|
19
|
-
|
|
19
|
+
// antigravity: permissions in ~/.gemini/antigravity-cli/settings.json under
|
|
20
|
+
// `permissions: { allow: [...], deny: [...] }`. Serializer is a follow-up.
|
|
21
|
+
// grok: permissions via --allow/--deny CLI flags or [permission] block in
|
|
22
|
+
// ~/.grok/config.toml. Serializer is a follow-up.
|
|
23
|
+
export const PERMISSIONS_CAPABLE_AGENTS = ['claude', 'codex', 'opencode', 'antigravity', 'grok'];
|
|
20
24
|
/** Filename used for Codex Starlark deny-rules generated from permission groups. */
|
|
21
25
|
export const CODEX_RULES_FILENAME = 'agents-deny.rules';
|
|
22
26
|
export function containsBroadGrants(rules) {
|
|
@@ -111,7 +111,7 @@ export function registerMarketplace(agent, versionHome) {
|
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
known[MARKETPLACE_NAME] = {
|
|
114
|
-
source: { source: '
|
|
114
|
+
source: { source: 'directory', path: root },
|
|
115
115
|
installLocation: root,
|
|
116
116
|
lastUpdated: new Date().toISOString(),
|
|
117
117
|
};
|
|
@@ -88,6 +88,43 @@ export const PRESETS = [
|
|
|
88
88
|
ANTHROPIC_SMALL_FAST_MODEL: 'deepseek/deepseek-chat-v3-0324',
|
|
89
89
|
},
|
|
90
90
|
},
|
|
91
|
+
// ----- xAI Grok Build CLI (native host) -----
|
|
92
|
+
{
|
|
93
|
+
name: 'grok-fast',
|
|
94
|
+
description: 'xAI Grok Build CLI — fast tier. Native grok host, no OpenRouter wrapper.',
|
|
95
|
+
provider: 'xai',
|
|
96
|
+
host: 'grok',
|
|
97
|
+
authEnvVar: 'XAI_API_KEY',
|
|
98
|
+
signupUrl: 'https://console.x.ai',
|
|
99
|
+
env: {
|
|
100
|
+
// TODO: confirm model id (docs.x.ai/build/models, May 2026)
|
|
101
|
+
GROK_MODEL: 'grok-build-fast',
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'grok-heavy',
|
|
106
|
+
description: 'xAI Grok Build CLI — heavy tier (SuperGrok). Native grok host.',
|
|
107
|
+
provider: 'xai',
|
|
108
|
+
host: 'grok',
|
|
109
|
+
authEnvVar: 'XAI_API_KEY',
|
|
110
|
+
signupUrl: 'https://console.x.ai',
|
|
111
|
+
env: {
|
|
112
|
+
// TODO: confirm model id (docs.x.ai/build/models, May 2026)
|
|
113
|
+
GROK_MODEL: 'grok-build',
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
// ----- Google Antigravity CLI (native host) -----
|
|
117
|
+
{
|
|
118
|
+
name: 'agy',
|
|
119
|
+
description: 'Google Antigravity CLI default. Auth via Google OAuth or ANTIGRAVITY_API_KEY.',
|
|
120
|
+
provider: 'google',
|
|
121
|
+
host: 'antigravity',
|
|
122
|
+
authEnvVar: 'ANTIGRAVITY_API_KEY',
|
|
123
|
+
signupUrl: 'https://antigravity.google',
|
|
124
|
+
env: {
|
|
125
|
+
// TODO: confirm model id — antigravity defaults are managed by the CLI itself
|
|
126
|
+
},
|
|
127
|
+
},
|
|
91
128
|
];
|
|
92
129
|
/** Look up a preset by name (case-sensitive). */
|
|
93
130
|
export function getPreset(name) {
|
|
@@ -239,6 +239,39 @@ function syncToCodexConfig(configPath, items) {
|
|
|
239
239
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
240
240
|
fs.writeFileSync(configPath, TOML.stringify(config), 'utf-8');
|
|
241
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* Write MCP servers to Grok config.toml format ([mcp_servers] section).
|
|
244
|
+
*/
|
|
245
|
+
function syncToGrokConfig(configPath, items) {
|
|
246
|
+
let config = {};
|
|
247
|
+
if (fs.existsSync(configPath)) {
|
|
248
|
+
try {
|
|
249
|
+
config = TOML.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
config = {};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const mcpServers = {};
|
|
256
|
+
for (const item of items) {
|
|
257
|
+
if (item.transport === 'stdio') {
|
|
258
|
+
mcpServers[item.name] = {
|
|
259
|
+
command: item.command,
|
|
260
|
+
args: item.args || [],
|
|
261
|
+
...(item.env && { env: item.env }),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
else if (item.transport === 'http' || item.transport === 'sse') {
|
|
265
|
+
mcpServers[item.name] = {
|
|
266
|
+
url: item.url,
|
|
267
|
+
...(item.headers && { headers: item.headers }),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
config.mcp_servers = mcpServers;
|
|
272
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
273
|
+
fs.writeFileSync(configPath, TOML.stringify(config), 'utf-8');
|
|
274
|
+
}
|
|
242
275
|
/**
|
|
243
276
|
* Write MCP servers to OpenCode opencode.jsonc format.
|
|
244
277
|
*/
|
|
@@ -467,11 +500,15 @@ export const McpHandler = {
|
|
|
467
500
|
case 'openclaw':
|
|
468
501
|
syncToOpenClawConfig(configPath, mcpItems);
|
|
469
502
|
break;
|
|
503
|
+
case 'grok':
|
|
504
|
+
syncToGrokConfig(configPath, mcpItems);
|
|
505
|
+
break;
|
|
470
506
|
}
|
|
471
507
|
},
|
|
472
508
|
format(agent) {
|
|
473
509
|
switch (agent) {
|
|
474
510
|
case 'codex':
|
|
511
|
+
case 'grok':
|
|
475
512
|
return 'toml';
|
|
476
513
|
default:
|
|
477
514
|
return 'json';
|
package/dist/lib/resources.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import type { AgentId } from './types.js';
|
|
6
6
|
import { type SkillParseError } from './skills.js';
|
|
7
7
|
/** Resource kind — matches the subdirectory name under each repo root. */
|
|
8
|
-
export type ResourceKind = 'commands' | 'skills' | 'hooks' | 'rules' | 'mcp' | 'permissions' | 'subagents' | 'profiles' | 'secrets';
|
|
8
|
+
export type ResourceKind = 'commands' | 'skills' | 'hooks' | 'rules' | 'mcp' | 'cli' | 'permissions' | 'subagents' | 'profiles' | 'secrets';
|
|
9
9
|
/** A resource resolved with its origin. */
|
|
10
10
|
export interface ResolvedResource {
|
|
11
11
|
name: string;
|
package/dist/lib/rotate.js
CHANGED
|
@@ -10,7 +10,7 @@ import * as yaml from 'yaml';
|
|
|
10
10
|
import { getAccountInfo } from './agents.js';
|
|
11
11
|
import { readMeta, writeMeta, getHelpersDir, getUserAgentsDir } from './state.js';
|
|
12
12
|
import { listInstalledVersions, getVersionHomePath, resolveVersion } from './versions.js';
|
|
13
|
-
import { getUsageInfoByIdentity, getUsageLookupKey,
|
|
13
|
+
import { getUsageInfoByIdentity, getUsageLookupKey, } from './usage.js';
|
|
14
14
|
function getRotateDir() {
|
|
15
15
|
const dir = path.join(getHelpersDir(), 'rotate');
|
|
16
16
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -233,9 +233,15 @@ async function collectRunCandidates(agent) {
|
|
|
233
233
|
const rows = await Promise.all(versions.map(async (version) => {
|
|
234
234
|
const home = getVersionHomePath(agent, version);
|
|
235
235
|
const info = await getAccountInfo(agent, home);
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
236
|
+
// `info.email` (from .claude.json's oauthAccount) is the auth heuristic.
|
|
237
|
+
// We used to additionally call isClaudeAuthValid(home), which reads
|
|
238
|
+
// "Claude Code-credentials-<hash>" from the system keychain. That item is
|
|
239
|
+
// written by Claude Code itself with its own process in the ACL, so our
|
|
240
|
+
// helper triggers a macOS keychain-authorization sheet on every probe —
|
|
241
|
+
// one per installed version, every time `agents run` cold-starts. If
|
|
242
|
+
// claude's stored token has actually expired, the spawned agent detects
|
|
243
|
+
// it at its own startup and re-auths; that's the correct UX.
|
|
244
|
+
const authValid = info.email != null;
|
|
239
245
|
return {
|
|
240
246
|
agent,
|
|
241
247
|
version,
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
* Convert a cron expression to a human-readable phrase.
|
|
8
|
+
*
|
|
9
|
+
* Handles the common patterns. For anything unrecognized, returns the raw
|
|
10
|
+
* expression so the user still sees something useful. NEVER throws.
|
|
11
|
+
*/
|
|
12
|
+
export declare function humanizeCron(expr: string, _tz?: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Convert a next-run Date into a human phrase relative to `now`.
|
|
15
|
+
*
|
|
16
|
+
* - null → '-'
|
|
17
|
+
* - same calendar day → 'today 9:00 AM'
|
|
18
|
+
* - next calendar day → 'tomorrow 9:00 AM'
|
|
19
|
+
* - within 7 days → 'Mon 9:00 AM'
|
|
20
|
+
* - further out → 'Jun 15, 9:00 AM'
|
|
21
|
+
*/
|
|
22
|
+
export declare function humanizeNextRun(date: Date | null, now: Date, tz?: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Parse a repo string into a display label and an optional hyperlink target.
|
|
25
|
+
*
|
|
26
|
+
* Rules:
|
|
27
|
+
* - undefined / empty → display '-', href null
|
|
28
|
+
* - 'owner/name' (one slash) → display 'owner/name', href 'https://github.com/owner/name/pulls'
|
|
29
|
+
* - 'https://...' or 'http://…' → display hostname+path, href the URL verbatim
|
|
30
|
+
* - anything else → display raw string, href null
|
|
31
|
+
*/
|
|
32
|
+
export declare function formatRepoLink(repo: string | undefined): {
|
|
33
|
+
display: string;
|
|
34
|
+
href: string | null;
|
|
35
|
+
};
|