@phnx-labs/agents-cli 1.22.43 → 1.22.45
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 +85 -0
- package/README.md +14 -12
- package/dist/bootstrap.js +3 -6
- package/dist/cli/command-registry.d.ts +0 -7
- package/dist/cli/command-registry.js +1 -21
- package/dist/commands/accounts.d.ts +1 -9
- package/dist/commands/accounts.js +12 -90
- package/dist/commands/apply.d.ts +7 -10
- package/dist/commands/apply.js +15 -35
- package/dist/commands/artifacts.js +4 -5
- package/dist/commands/audit.d.ts +6 -6
- package/dist/commands/audit.js +12 -12
- package/dist/commands/beta.d.ts +7 -1
- package/dist/commands/beta.js +16 -9
- package/dist/commands/commands.js +1 -1
- package/dist/commands/doctor.js +1 -4
- package/dist/commands/events.d.ts +2 -2
- package/dist/commands/events.js +6 -5
- package/dist/commands/exec.d.ts +1 -1
- package/dist/commands/exec.js +8 -4
- package/dist/commands/factory.d.ts +1 -1
- package/dist/commands/fleet-capture.d.ts +1 -1
- package/dist/commands/fleet-capture.js +4 -5
- package/dist/commands/harness.js +1 -49
- package/dist/commands/hooks.js +1 -1
- package/dist/commands/insights.d.ts +1 -1
- package/dist/commands/insights.js +85 -156
- package/dist/commands/menubar.js +58 -1
- package/dist/commands/output.js +2 -2
- package/dist/commands/prune.js +5 -4
- package/dist/commands/routines.test-fixture.d.ts +86 -0
- package/dist/commands/routines.test-fixture.js +345 -0
- package/dist/commands/sessions.test-fixture.d.ts +24 -0
- package/dist/commands/sessions.test-fixture.js +224 -0
- package/dist/commands/setup.js +5 -1
- package/dist/commands/share.d.ts +9 -10
- package/dist/commands/share.js +68 -50
- package/dist/commands/skills.js +1 -1
- package/dist/commands/ssh.js +15 -62
- package/dist/commands/subagents.js +1 -1
- package/dist/commands/view.js +5 -2
- package/dist/lib/analytics/mix-commands.d.ts +6 -28
- package/dist/lib/analytics/mix-commands.js +14 -40
- package/dist/lib/audit/log.d.ts +2 -2
- package/dist/lib/audit/log.js +2 -2
- package/dist/lib/beta.js +1 -1
- package/dist/lib/browser/domain-skills.d.ts +26 -6
- package/dist/lib/browser/domain-skills.js +81 -43
- package/dist/lib/daemon/daemon.js +11 -0
- package/dist/lib/daemon/daemon.test-fixture.d.ts +24 -0
- package/dist/lib/daemon/daemon.test-fixture.js +71 -0
- package/dist/lib/daemon-ticks.d.ts +38 -1
- package/dist/lib/daemon-ticks.js +52 -5
- package/dist/lib/device-config.js +1 -2
- package/dist/lib/devices/discovery-policy.d.ts +3 -3
- package/dist/lib/devices/discovery-policy.js +3 -3
- package/dist/lib/devices/fleet.d.ts +4 -8
- package/dist/lib/devices/fleet.js +3 -15
- package/dist/lib/devices/health-report.js +3 -3
- package/dist/lib/devices/pool.d.ts +0 -6
- package/dist/lib/devices/pool.js +0 -6
- package/dist/lib/devices/registry.d.ts +0 -15
- package/dist/lib/devices/registry.js +0 -9
- package/dist/lib/devices/stats-cache.d.ts +12 -0
- package/dist/lib/devices/stats-cache.js +24 -5
- package/dist/lib/fleet/capture.d.ts +1 -1
- package/dist/lib/fleet/manifest.js +1 -1
- package/dist/lib/fleet/remote-login.d.ts +2 -2
- package/dist/lib/fleet/remote-login.js +3 -4
- package/dist/lib/hosts/dispatch.d.ts +2 -0
- package/dist/lib/hosts/dispatch.js +8 -1
- package/dist/lib/hosts/passthrough.js +0 -1
- package/dist/lib/hosts/providers/devices.js +1 -13
- package/dist/lib/hosts/ready.d.ts +12 -3
- package/dist/lib/hosts/ready.js +27 -12
- package/dist/lib/hosts/registry.d.ts +4 -5
- package/dist/lib/hosts/registry.js +3 -7
- package/dist/lib/installations/versions.js +21 -2
- package/dist/lib/menubar/install-menubar.d.ts +109 -0
- package/dist/lib/menubar/install-menubar.js +199 -4
- package/dist/lib/remote-agents-json.js +1 -7
- package/dist/lib/routines-placement.d.ts +1 -1
- package/dist/lib/routines-placement.js +1 -1
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -2
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -13
- package/dist/lib/session/active.d.ts +4 -16
- package/dist/lib/session/active.js +2 -6
- package/dist/lib/session/db.d.ts +13 -98
- package/dist/lib/session/db.js +4 -11
- package/dist/lib/session/discover.d.ts +5 -81
- package/dist/lib/session/discover.js +5 -5
- package/dist/lib/session/remote/remote-bundle.d.ts +12 -0
- package/dist/lib/session/remote/remote-bundle.js +61 -0
- package/dist/lib/session/remote/remote-list.d.ts +148 -0
- package/dist/lib/session/remote/remote-list.js +607 -0
- package/dist/lib/session/remote/remote.d.ts +132 -0
- package/dist/lib/session/remote/remote.js +314 -0
- package/dist/lib/session/remote/watch.d.ts +101 -0
- package/dist/lib/session/remote/watch.js +241 -0
- package/dist/lib/session/remote-bundle.d.ts +1 -12
- package/dist/lib/session/remote-bundle.js +3 -61
- package/dist/lib/session/remote-list.d.ts +1 -148
- package/dist/lib/session/remote-list.js +3 -607
- package/dist/lib/session/remote.d.ts +1 -132
- package/dist/lib/session/remote.js +3 -314
- package/dist/lib/session/watch.d.ts +1 -101
- package/dist/lib/session/watch.js +3 -242
- package/dist/lib/share/config.d.ts +12 -2
- package/dist/lib/share/config.js +47 -11
- package/dist/lib/share/delete.js +1 -1
- package/dist/lib/share/publish.js +1 -1
- package/dist/lib/share/worker-template.js +2 -2
- package/dist/lib/smart-launch.d.ts +4 -4
- package/dist/lib/smart-launch.js +8 -18
- package/dist/lib/staleness/index.d.ts +3 -1
- package/dist/lib/staleness/index.js +21 -1
- package/dist/lib/staleness/types.d.ts +11 -0
- package/dist/lib/staleness/writers/commands.js +13 -4
- package/dist/lib/staleness/writers/hooks.js +3 -1
- package/dist/lib/staleness/writers/mcp.d.ts +0 -7
- package/dist/lib/staleness/writers/mcp.js +16 -1
- package/dist/lib/staleness/writers/rules.js +1 -1
- package/dist/lib/staleness/writers/skills.js +3 -1
- package/dist/lib/staleness/writers/subagents.d.ts +0 -9
- package/dist/lib/staleness/writers/subagents.js +18 -1
- package/dist/lib/staleness/writers/types.d.ts +10 -0
- package/dist/lib/startup/command-registry.d.ts +8 -1
- package/dist/lib/startup/command-registry.js +21 -6
- package/dist/lib/startup/root-command.d.ts +18 -1
- package/dist/lib/startup/root-command.js +18 -1
- package/dist/lib/state.js +7 -0
- package/dist/lib/teams/placement-probe.js +27 -12
- package/dist/lib/teams/scheduler.d.ts +3 -1
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/view-types.d.ts +3 -0
- package/package.json +2 -3
- package/dist/bin/agents +0 -0
- package/dist/commands/auth.d.ts +0 -9
- package/dist/commands/auth.js +0 -108
- package/dist/commands/org.d.ts +0 -6
- package/dist/commands/org.js +0 -175
- package/dist/commands/serve.d.ts +0 -10
- package/dist/commands/serve.js +0 -68
- package/dist/commands/trends.d.ts +0 -10
- package/dist/commands/trends.js +0 -12
- package/dist/lib/entitlement.d.ts +0 -31
- package/dist/lib/entitlement.js +0 -137
- package/dist/lib/prix-account.d.ts +0 -158
- package/dist/lib/prix-account.js +0 -214
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/serve/control.d.ts +0 -95
- package/dist/lib/serve/control.js +0 -260
- package/dist/lib/serve/data.d.ts +0 -81
- package/dist/lib/serve/data.js +0 -91
- package/dist/lib/serve/page.d.ts +0 -7
- package/dist/lib/serve/page.js +0 -140
- package/dist/lib/serve/server.d.ts +0 -80
- package/dist/lib/serve/server.js +0 -145
- package/dist/lib/serve/stream.d.ts +0 -43
- package/dist/lib/serve/stream.js +0 -116
- package/dist/lib/serve/token.d.ts +0 -35
- package/dist/lib/serve/token.js +0 -85
|
@@ -1,242 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import * as fs from 'node:fs';
|
|
5
|
-
import * as path from 'node:path';
|
|
6
|
-
import { loadDevices, isControlDevice, isDialableDevice } from '../devices/registry.js';
|
|
7
|
-
import { deviceIdentityArgs, sshTargetFor } from '../devices/connect.js';
|
|
8
|
-
import { machineId, normalizeHost } from '../machine-id.js';
|
|
9
|
-
import { SSH_OPTS, controlOpts, shellQuote } from '../ssh-exec.js';
|
|
10
|
-
import { buildWindowsAgentsCommand, remoteShellFor } from '../hosts/remote-cmd.js';
|
|
11
|
-
import { activeSessionsJournalPath, activeSessionJournalIdentity, readActiveSessionsCache, } from './session-cache.js';
|
|
12
|
-
export const SESSION_WATCH_VERSION = 1;
|
|
13
|
-
export const SESSION_WATCH_HEARTBEAT_MS = 15_000;
|
|
14
|
-
/** Stable, opaque identity for one row within one device scope. */
|
|
15
|
-
export function sessionWatchRowKey(scope, row) {
|
|
16
|
-
const identity = activeSessionJournalIdentity(row);
|
|
17
|
-
return createHash('sha256').update(`${scope}\0${identity}`).digest('base64url').slice(0, 22);
|
|
18
|
-
}
|
|
19
|
-
export function toSessionWatchRow(scope, row) {
|
|
20
|
-
const rowKey = sessionWatchRowKey(scope, row);
|
|
21
|
-
const resumable = Boolean(row.sessionId);
|
|
22
|
-
const viewingIn = row.viewingIn
|
|
23
|
-
? [row.viewingIn.app, row.viewingIn.tab ? `tab ${row.viewingIn.tab}` : undefined].filter(Boolean).join(' ')
|
|
24
|
-
: null;
|
|
25
|
-
return {
|
|
26
|
-
...row,
|
|
27
|
-
rowKey,
|
|
28
|
-
sourceDevice: scope,
|
|
29
|
-
resumable,
|
|
30
|
-
unwatched: !viewingIn,
|
|
31
|
-
viewingIn,
|
|
32
|
-
recovery: resumable
|
|
33
|
-
? { command: 'agents', args: ['sessions', 'resume', row.sessionId, '--device', scope], ...(row.cwd ? { cwd: row.cwd } : {}) }
|
|
34
|
-
: null,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
function sameRow(a, b) {
|
|
38
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
39
|
-
}
|
|
40
|
-
/** Stream-local sequencer and convergent row diff. */
|
|
41
|
-
export class SessionWatchState {
|
|
42
|
-
streamId;
|
|
43
|
-
sequence = 0;
|
|
44
|
-
rows = new Map();
|
|
45
|
-
constructor(streamId = randomUUID()) { this.streamId = streamId; }
|
|
46
|
-
base(type) {
|
|
47
|
-
return { version: SESSION_WATCH_VERSION, type, streamId: this.streamId, sequence: ++this.sequence, capturedAt: Date.now() };
|
|
48
|
-
}
|
|
49
|
-
reset(scope, sourceRows) {
|
|
50
|
-
const rows = sourceRows.map((row) => toSessionWatchRow(scope, row));
|
|
51
|
-
this.rows.set(scope, new Map(rows.map((row) => [row.rowKey, row])));
|
|
52
|
-
return { ...this.base('reset'), scope, rows };
|
|
53
|
-
}
|
|
54
|
-
update(scope, sourceRows) {
|
|
55
|
-
const previous = this.rows.get(scope) ?? new Map();
|
|
56
|
-
const nextRows = sourceRows.map((row) => toSessionWatchRow(scope, row));
|
|
57
|
-
const next = new Map(nextRows.map((row) => [row.rowKey, row]));
|
|
58
|
-
const events = [];
|
|
59
|
-
for (const [rowKey, row] of next) {
|
|
60
|
-
if (!previous.has(rowKey) || !sameRow(previous.get(rowKey), row)) {
|
|
61
|
-
events.push({ ...this.base('upsert'), scope, rowKey, row });
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
for (const rowKey of previous.keys()) {
|
|
65
|
-
if (!next.has(rowKey))
|
|
66
|
-
events.push({ ...this.base('remove'), scope, rowKey });
|
|
67
|
-
}
|
|
68
|
-
this.rows.set(scope, next);
|
|
69
|
-
return events;
|
|
70
|
-
}
|
|
71
|
-
patch(scope, upserts, removes) {
|
|
72
|
-
const current = new Map(this.rows.get(scope) ?? []);
|
|
73
|
-
const events = [];
|
|
74
|
-
for (const source of upserts) {
|
|
75
|
-
const row = toSessionWatchRow(scope, source);
|
|
76
|
-
if (!current.has(row.rowKey) || !sameRow(current.get(row.rowKey), row)) {
|
|
77
|
-
current.set(row.rowKey, row);
|
|
78
|
-
events.push({ ...this.base('upsert'), scope, rowKey: row.rowKey, row });
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
for (const identity of removes) {
|
|
82
|
-
const rowKey = createHash('sha256').update(`${scope}\0${identity}`).digest('base64url').slice(0, 22);
|
|
83
|
-
if (current.delete(rowKey))
|
|
84
|
-
events.push({ ...this.base('remove'), scope, rowKey });
|
|
85
|
-
}
|
|
86
|
-
this.rows.set(scope, current);
|
|
87
|
-
return events;
|
|
88
|
-
}
|
|
89
|
-
scope(scope, status, reason) {
|
|
90
|
-
// Deliberately retain rows while unavailable. A reconnecting scope replaces
|
|
91
|
-
// them with a reset; transient fleet loss must not look like session death.
|
|
92
|
-
return { ...this.base('scope'), scope, status, ...(reason ? { reason } : {}) };
|
|
93
|
-
}
|
|
94
|
-
heartbeat(scope, capturedAt = Date.now()) {
|
|
95
|
-
return { ...this.base('heartbeat'), scope, capturedAt };
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Keep one local subscription alive. Startup reads one canonical reset snapshot;
|
|
100
|
-
* steady state tails the canonical writer journal and never invokes a gather.
|
|
101
|
-
*/
|
|
102
|
-
export async function watchLocalSessions(options) {
|
|
103
|
-
const state = new SessionWatchState();
|
|
104
|
-
const readCache = options.readCache ?? readActiveSessionsCache;
|
|
105
|
-
const journal = options.journalPath ?? activeSessionsJournalPath();
|
|
106
|
-
const heartbeatMs = options.heartbeatMs ?? SESSION_WATCH_HEARTBEAT_MS;
|
|
107
|
-
let offset = 0;
|
|
108
|
-
try {
|
|
109
|
-
offset = fs.statSync(journal).size;
|
|
110
|
-
}
|
|
111
|
-
catch { /* first publication */ }
|
|
112
|
-
const initial = readCache('local');
|
|
113
|
-
options.emit(state.reset(options.scope, initial?.sessions ?? []));
|
|
114
|
-
options.emit(state.scope(options.scope, initial ? 'available' : 'unavailable', initial ? undefined : 'awaiting publisher'));
|
|
115
|
-
if (options.signal.aborted)
|
|
116
|
-
return;
|
|
117
|
-
await new Promise((resolve) => {
|
|
118
|
-
let partial = '';
|
|
119
|
-
let reading = false;
|
|
120
|
-
const readAppended = () => {
|
|
121
|
-
if (reading || options.signal.aborted)
|
|
122
|
-
return;
|
|
123
|
-
reading = true;
|
|
124
|
-
try {
|
|
125
|
-
const size = fs.statSync(journal).size;
|
|
126
|
-
if (size < offset) {
|
|
127
|
-
offset = 0;
|
|
128
|
-
partial = '';
|
|
129
|
-
}
|
|
130
|
-
if (size === offset)
|
|
131
|
-
return;
|
|
132
|
-
const fd = fs.openSync(journal, 'r');
|
|
133
|
-
try {
|
|
134
|
-
const buffer = Buffer.alloc(size - offset);
|
|
135
|
-
fs.readSync(fd, buffer, 0, buffer.length, offset);
|
|
136
|
-
offset = size;
|
|
137
|
-
const lines = (partial + buffer.toString('utf8')).split('\n');
|
|
138
|
-
partial = lines.pop() ?? '';
|
|
139
|
-
for (const line of lines) {
|
|
140
|
-
if (!line)
|
|
141
|
-
continue;
|
|
142
|
-
try {
|
|
143
|
-
const record = JSON.parse(line);
|
|
144
|
-
if (record.version !== 1 || record.scope !== 'local' || !Array.isArray(record.upserts) || !Array.isArray(record.removes))
|
|
145
|
-
continue;
|
|
146
|
-
for (const event of state.patch(options.scope, record.upserts, record.removes))
|
|
147
|
-
options.emit(event);
|
|
148
|
-
options.emit(state.scope(options.scope, 'available'));
|
|
149
|
-
}
|
|
150
|
-
catch { /* partial/corrupt journal lines are not state */ }
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
finally {
|
|
154
|
-
fs.closeSync(fd);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
catch { /* journal may not exist until the first publisher write */ }
|
|
158
|
-
finally {
|
|
159
|
-
reading = false;
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
fs.mkdirSync(path.dirname(journal), { recursive: true });
|
|
163
|
-
const journalListener = () => readAppended();
|
|
164
|
-
fs.watchFile(journal, { interval: options.journalPollMs ?? 250 }, journalListener);
|
|
165
|
-
const heartbeatTimer = setInterval(() => options.emit(state.heartbeat(options.scope)), heartbeatMs);
|
|
166
|
-
const stop = () => { fs.unwatchFile(journal, journalListener); clearInterval(heartbeatTimer); resolve(); };
|
|
167
|
-
options.signal.addEventListener('abort', stop, { once: true });
|
|
168
|
-
// Close the offset/read/watch handoff: a publisher may append after the
|
|
169
|
-
// startup offset is captured but before watchFile is registered.
|
|
170
|
-
readAppended();
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
function remoteWatchCommand(os) {
|
|
174
|
-
const args = ['sessions', 'watch', '--json', '--local'];
|
|
175
|
-
return remoteShellFor(os) === 'powershell'
|
|
176
|
-
? buildWindowsAgentsCommand({ args })
|
|
177
|
-
: `bash -lc ${shellQuote(`agents ${args.map(shellQuote).join(' ')}`)}`;
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Subscribe to every dialable compute device with one persistent SSH process.
|
|
181
|
-
* A peer disconnect emits `scope: unavailable` but no removes; reconnecting
|
|
182
|
-
* peer resets only its own scope. There is no recurring fleet list command.
|
|
183
|
-
*/
|
|
184
|
-
export async function watchFleetSessions(options) {
|
|
185
|
-
const local = watchLocalSessions({ scope: machineId(), signal: options.signal, emit: options.emit });
|
|
186
|
-
let devices;
|
|
187
|
-
try {
|
|
188
|
-
devices = await loadDevices();
|
|
189
|
-
}
|
|
190
|
-
catch {
|
|
191
|
-
await local;
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
const self = machineId();
|
|
195
|
-
const peers = Object.values(devices).filter((device) => isDialableDevice(device)
|
|
196
|
-
&& !isControlDevice(device)
|
|
197
|
-
&& normalizeHost(device.name) !== self
|
|
198
|
-
&& ['windows', 'linux', 'macos'].includes(device.platform));
|
|
199
|
-
const reconnectMs = options.reconnectMs ?? 2_000;
|
|
200
|
-
const peerTasks = peers.map(async (device) => {
|
|
201
|
-
const scope = normalizeHost(device.name);
|
|
202
|
-
const state = new SessionWatchState();
|
|
203
|
-
while (!options.signal.aborted) {
|
|
204
|
-
let target;
|
|
205
|
-
try {
|
|
206
|
-
target = sshTargetFor(device);
|
|
207
|
-
}
|
|
208
|
-
catch (error) {
|
|
209
|
-
options.emit(state.scope(scope, 'unavailable', error instanceof Error ? error.message : String(error)));
|
|
210
|
-
break;
|
|
211
|
-
}
|
|
212
|
-
const child = spawn('ssh', [
|
|
213
|
-
...SSH_OPTS, ...controlOpts(), ...deviceIdentityArgs(device), target, remoteWatchCommand(device.platform),
|
|
214
|
-
], { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
215
|
-
const stop = () => child.kill('SIGTERM');
|
|
216
|
-
options.signal.addEventListener('abort', stop, { once: true });
|
|
217
|
-
const reader = createInterface({ input: child.stdout });
|
|
218
|
-
reader.on('line', (line) => {
|
|
219
|
-
try {
|
|
220
|
-
const event = JSON.parse(line);
|
|
221
|
-
if (event.version === SESSION_WATCH_VERSION && typeof event.sequence === 'number')
|
|
222
|
-
options.emit(event);
|
|
223
|
-
}
|
|
224
|
-
catch { /* incomplete/non-protocol peer output is not state */ }
|
|
225
|
-
});
|
|
226
|
-
const code = await new Promise((resolve) => {
|
|
227
|
-
child.once('error', () => resolve(null));
|
|
228
|
-
child.once('close', resolve);
|
|
229
|
-
});
|
|
230
|
-
reader.close();
|
|
231
|
-
options.signal.removeEventListener('abort', stop);
|
|
232
|
-
if (options.signal.aborted)
|
|
233
|
-
break;
|
|
234
|
-
options.emit(state.scope(scope, 'unavailable', code === null ? 'ssh failed' : `ssh exited ${code}`));
|
|
235
|
-
await new Promise((resolve) => {
|
|
236
|
-
const timer = setTimeout(resolve, reconnectMs);
|
|
237
|
-
options.signal.addEventListener('abort', () => { clearTimeout(timer); resolve(); }, { once: true });
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
await Promise.all([local, ...peerTasks]);
|
|
242
|
-
}
|
|
1
|
+
// Moved to ./remote/watch.ts (move 4 of the core-module refactor).
|
|
2
|
+
// Kept as a re-export shim so existing importers don't break.
|
|
3
|
+
export * from './remote/watch.js';
|
|
@@ -22,9 +22,19 @@ export declare const DEFAULT_BUCKET_NAME = "agents-share";
|
|
|
22
22
|
export declare const DEFAULT_SHARE_DOMAIN = "share.agents-cli.sh";
|
|
23
23
|
/** The write token may be injected ephemerally into fleet/cloud agents. */
|
|
24
24
|
export declare function readWriteTokenEnv(env?: NodeJS.ProcessEnv): string | null;
|
|
25
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Read the persisted endpoint config, or null if there is no `share.baseUrl`.
|
|
27
|
+
*
|
|
28
|
+
* Publish talks only to `baseUrl` with `WRITE_TOKEN`. `accountId` / worker /
|
|
29
|
+
* bucket are provisioning metadata for `setup`/`update`. Requiring all four
|
|
30
|
+
* (and treating `accountId: ""` as missing) made `status` and publish both
|
|
31
|
+
* report "not set up" on an endpoint that still worked — RUSH-2837.
|
|
32
|
+
*/
|
|
26
33
|
export declare function readShareConfig(): ShareConfig | null;
|
|
27
|
-
/** Persist the endpoint config to `agents.yaml` (syncs across the fleet).
|
|
34
|
+
/** Persist the endpoint config to `agents.yaml` (syncs across the fleet).
|
|
35
|
+
* Empty strings never overwrite a previously stored value and are not written
|
|
36
|
+
* back as `accountId: ""` (that form made {@link readShareConfig} return null
|
|
37
|
+
* under the old all-fields-required check, and it is still useless). */
|
|
28
38
|
export declare function writeShareConfig(cfg: ShareConfig): void;
|
|
29
39
|
/** A fresh 32-byte hex write token. */
|
|
30
40
|
export declare function generateWriteToken(): string;
|
package/dist/lib/share/config.js
CHANGED
|
@@ -23,24 +23,60 @@ export function readWriteTokenEnv(env = process.env) {
|
|
|
23
23
|
const token = env[SHARE_TOKEN_ENV_KEY]?.trim();
|
|
24
24
|
return token ? token : null;
|
|
25
25
|
}
|
|
26
|
-
/**
|
|
26
|
+
/** Trim; empty / whitespace-only strings are absent. */
|
|
27
|
+
function nonempty(v) {
|
|
28
|
+
const t = v?.trim();
|
|
29
|
+
return t ? t : undefined;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Read the persisted endpoint config, or null if there is no `share.baseUrl`.
|
|
33
|
+
*
|
|
34
|
+
* Publish talks only to `baseUrl` with `WRITE_TOKEN`. `accountId` / worker /
|
|
35
|
+
* bucket are provisioning metadata for `setup`/`update`. Requiring all four
|
|
36
|
+
* (and treating `accountId: ""` as missing) made `status` and publish both
|
|
37
|
+
* report "not set up" on an endpoint that still worked — RUSH-2837.
|
|
38
|
+
*/
|
|
27
39
|
export function readShareConfig() {
|
|
28
40
|
const s = readMeta().share;
|
|
29
|
-
|
|
41
|
+
const baseUrl = nonempty(s?.baseUrl)?.replace(/\/+$/, '');
|
|
42
|
+
if (!baseUrl)
|
|
30
43
|
return null;
|
|
31
44
|
return {
|
|
32
|
-
baseUrl
|
|
33
|
-
accountId: s
|
|
34
|
-
workerName: s
|
|
35
|
-
bucketName: s
|
|
36
|
-
domain: s
|
|
37
|
-
analyticsToken: s
|
|
38
|
-
templateHash: s
|
|
45
|
+
baseUrl,
|
|
46
|
+
accountId: nonempty(s?.accountId) ?? '',
|
|
47
|
+
workerName: nonempty(s?.workerName) ?? DEFAULT_WORKER_NAME,
|
|
48
|
+
bucketName: nonempty(s?.bucketName) ?? DEFAULT_BUCKET_NAME,
|
|
49
|
+
domain: nonempty(s?.domain),
|
|
50
|
+
analyticsToken: nonempty(s?.analyticsToken),
|
|
51
|
+
templateHash: nonempty(s?.templateHash),
|
|
39
52
|
};
|
|
40
53
|
}
|
|
41
|
-
/** Persist the endpoint config to `agents.yaml` (syncs across the fleet).
|
|
54
|
+
/** Persist the endpoint config to `agents.yaml` (syncs across the fleet).
|
|
55
|
+
* Empty strings never overwrite a previously stored value and are not written
|
|
56
|
+
* back as `accountId: ""` (that form made {@link readShareConfig} return null
|
|
57
|
+
* under the old all-fields-required check, and it is still useless). */
|
|
42
58
|
export function writeShareConfig(cfg) {
|
|
43
|
-
updateMeta((meta) =>
|
|
59
|
+
updateMeta((meta) => {
|
|
60
|
+
const prev = meta.share ?? {};
|
|
61
|
+
const next = { ...prev };
|
|
62
|
+
const assign = (key, incoming) => {
|
|
63
|
+
const v = nonempty(incoming);
|
|
64
|
+
if (v)
|
|
65
|
+
next[key] = v;
|
|
66
|
+
};
|
|
67
|
+
assign('baseUrl', cfg.baseUrl);
|
|
68
|
+
assign('accountId', cfg.accountId);
|
|
69
|
+
assign('workerName', cfg.workerName);
|
|
70
|
+
assign('bucketName', cfg.bucketName);
|
|
71
|
+
assign('domain', cfg.domain);
|
|
72
|
+
assign('analyticsToken', cfg.analyticsToken);
|
|
73
|
+
assign('templateHash', cfg.templateHash);
|
|
74
|
+
for (const key of Object.keys(next)) {
|
|
75
|
+
if (typeof next[key] === 'string' && !nonempty(next[key]))
|
|
76
|
+
delete next[key];
|
|
77
|
+
}
|
|
78
|
+
return { ...meta, share: next };
|
|
79
|
+
});
|
|
44
80
|
}
|
|
45
81
|
/** A fresh 32-byte hex write token. */
|
|
46
82
|
export function generateWriteToken() {
|
package/dist/lib/share/delete.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The delete path for `agents artifacts share delete` / `agents unshare` — an authed DELETE
|
|
1
|
+
// The delete path for `agents artifacts share delete` / `agents artifacts unshare` — an authed DELETE
|
|
2
2
|
// to the Worker, which already implements it (worker-template.ts). Mirrors
|
|
3
3
|
// publish.ts: pure target-resolution logic is exported for tests, the network
|
|
4
4
|
// calls (a status check + a delete) sit behind an injectable DI seam.
|
|
@@ -459,7 +459,7 @@ export async function publishToEndpoint(filePath, endpoint, opts = {}) {
|
|
|
459
459
|
// strings unless --force. Runs on the raw file body AND on every piece of
|
|
460
460
|
// free-text metadata that lands in public customMetadata — --label (explicit
|
|
461
461
|
// or derived) and every --meta value. Metadata is visible in the gallery,
|
|
462
|
-
// `share list --json`, and `share revisions` just like the page itself, so a
|
|
462
|
+
// `share list --list-json`, and `share revisions` just like the page itself, so a
|
|
463
463
|
// credential smuggled in there is exactly as exposed as one in the body; it
|
|
464
464
|
// must not have a free pass around this gate. Runs before analytics/cover
|
|
465
465
|
// mutation so a beacon injection never triggers a false positive on the body
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
// - GET /<slug> — backward-compat flat slug (legacy shares before
|
|
22
22
|
// per-user namespaces).
|
|
23
23
|
//
|
|
24
|
-
// Emitted as a string
|
|
25
|
-
//
|
|
24
|
+
// Emitted as a string, so it compiles into `dist/**` and ships with no
|
|
25
|
+
// package.json#files change. `provision.ts` uploads
|
|
26
26
|
// this verbatim as an ES-module Worker with a BUCKET (R2) binding + a WRITE_TOKEN secret.
|
|
27
27
|
/**
|
|
28
28
|
* Render the Worker source. Pure — the R2 binding + token are wired at deploy time.
|
|
@@ -34,9 +34,7 @@ export declare function sampleWeighted(candidates: WeightedCandidate[], rng?: ()
|
|
|
34
34
|
* paths (`resolveDeviceAuto`, `resolveDeviceAffinity`) get their candidates, so
|
|
35
35
|
* marking workers moves every `--device auto` at once instead of one surface.
|
|
36
36
|
*
|
|
37
|
-
*
|
|
38
|
-
* where the registry is already being read — they are control surfaces, not
|
|
39
|
-
* compute. It CAN return an empty list — a fleet where every marked worker is
|
|
37
|
+
* It CAN return an empty list — a fleet where every marked worker is
|
|
40
38
|
* offline, or where this box is the only candidate and is marked `personal`. That is a
|
|
41
39
|
* real answer, and both callers fail loud on it rather than falling back to the
|
|
42
40
|
* local machine (which would be the exact box the operator marked personal to
|
|
@@ -94,6 +92,8 @@ export declare function formatNoHealthyDeviceError(pool: string[], signals: Map<
|
|
|
94
92
|
export declare function resolveDeviceAuto(agent?: string, opts?: {
|
|
95
93
|
eligibleHosts?: string[];
|
|
96
94
|
localMachine?: string;
|
|
95
|
+
/** Route only to devices with a row the interactive account picker can launch. */
|
|
96
|
+
accountPicker?: boolean;
|
|
97
97
|
probe?: (pool: string[], agent?: AgentType) => Promise<Map<string, DevicePlacementSignal>>;
|
|
98
98
|
}): Promise<DeviceAutoPlan>;
|
|
99
99
|
/**
|
|
@@ -136,7 +136,7 @@ export type DeviceAutoApplyResult = {
|
|
|
136
136
|
* `auto`, so callers fail loud instead of silently launching locally.
|
|
137
137
|
*/
|
|
138
138
|
export declare function applyDeviceAutoToOptions(options: DeviceAutoHostOptions, deps?: {
|
|
139
|
-
resolve?: () => DeviceAutoPlan | Promise<DeviceAutoPlan>;
|
|
139
|
+
resolve?: (accountPicker: boolean) => DeviceAutoPlan | Promise<DeviceAutoPlan>;
|
|
140
140
|
agent?: string;
|
|
141
141
|
accountPickerRequested?: boolean;
|
|
142
142
|
}): Promise<DeviceAutoApplyResult>;
|
package/dist/lib/smart-launch.js
CHANGED
|
@@ -7,12 +7,11 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { queryAffinityRollup } from './session/db.js';
|
|
9
9
|
import { localMachineId } from './session/origin-machine.js';
|
|
10
|
-
import {
|
|
10
|
+
import { loadDevicesSync } from './devices/registry.js';
|
|
11
11
|
import { describeAutoPool, filterAutoPool, isAutoPoolMember } from './devices/pool.js';
|
|
12
12
|
import { normalizeHost } from './machine-id.js';
|
|
13
13
|
import { probePoolSignals } from './teams/placement-probe.js';
|
|
14
14
|
import { pickBestDevice } from './teams/scheduler.js';
|
|
15
|
-
import { getAccountInfo } from './agents.js';
|
|
16
15
|
/** Peakiness of usage weights; >1 amplifies the most-used option. */
|
|
17
16
|
export const DEFAULT_AFFINITY_ALPHA = 1.3;
|
|
18
17
|
/**
|
|
@@ -55,9 +54,7 @@ export function sampleWeighted(candidates, rng = Math.random) {
|
|
|
55
54
|
* paths (`resolveDeviceAuto`, `resolveDeviceAffinity`) get their candidates, so
|
|
56
55
|
* marking workers moves every `--device auto` at once instead of one surface.
|
|
57
56
|
*
|
|
58
|
-
*
|
|
59
|
-
* where the registry is already being read — they are control surfaces, not
|
|
60
|
-
* compute. It CAN return an empty list — a fleet where every marked worker is
|
|
57
|
+
* It CAN return an empty list — a fleet where every marked worker is
|
|
61
58
|
* offline, or where this box is the only candidate and is marked `personal`. That is a
|
|
62
59
|
* real answer, and both callers fail loud on it rather than falling back to the
|
|
63
60
|
* local machine (which would be the exact box the operator marked personal to
|
|
@@ -74,10 +71,6 @@ export function listOnlineDeviceNames(localName = localMachineId()) {
|
|
|
74
71
|
// No tailscale snapshot → treat as candidate (registry-only box).
|
|
75
72
|
if (online === false)
|
|
76
73
|
continue;
|
|
77
|
-
// A paired cockpit (iPhone/iPad) is a control surface, not compute — it
|
|
78
|
-
// is never dialed for a session, so it is never a placement candidate.
|
|
79
|
-
if (isControlDevice(d))
|
|
80
|
-
continue;
|
|
81
74
|
names.add(normalizeHost(name));
|
|
82
75
|
}
|
|
83
76
|
}
|
|
@@ -147,18 +140,15 @@ export async function resolveDeviceAuto(agent, opts = {}) {
|
|
|
147
140
|
signals.set(key, { ...current, installed: accountEligible, signedIn: accountEligible });
|
|
148
141
|
}
|
|
149
142
|
}
|
|
150
|
-
if (agent && !opts.probe && signals.has(local)) {
|
|
151
|
-
const info = await getAccountInfo(agent).catch(() => null);
|
|
152
|
-
const eligible = !!info?.signedIn && info.usageStatus !== 'rate_limited' && info.usageStatus !== 'out_of_credits';
|
|
153
|
-
signals.set(local, { ...signals.get(local), signedIn: eligible });
|
|
154
|
-
}
|
|
155
143
|
const eligiblePool = pool.filter((key) => {
|
|
156
144
|
const signal = signals.get(key);
|
|
157
145
|
if (signal?.reachable !== true || signal.headroom === 'loaded')
|
|
158
146
|
return false;
|
|
159
147
|
if (!agent)
|
|
160
148
|
return opts.probe ? true : signal.installed === true && signal.signedIn === true;
|
|
161
|
-
return signal.installed === true &&
|
|
149
|
+
return signal.installed === true && (opts.accountPicker === true
|
|
150
|
+
? signal.pickerEligible === true
|
|
151
|
+
: signal.signedIn === true);
|
|
162
152
|
});
|
|
163
153
|
if (eligiblePool.length === 0) {
|
|
164
154
|
throw new Error(formatNoHealthyDeviceError(pool, signals, agent));
|
|
@@ -260,15 +250,15 @@ export async function applyDeviceAutoToOptions(options, deps = {}) {
|
|
|
260
250
|
if (!hasAuto) {
|
|
261
251
|
return { attempted: false, deprecationSmart };
|
|
262
252
|
}
|
|
263
|
-
const
|
|
264
|
-
const
|
|
253
|
+
const accountPickerRequested = deps.accountPickerRequested ?? false;
|
|
254
|
+
const resolve = deps.resolve ?? ((accountPicker) => resolveDeviceAuto(deps.agent, { accountPicker }));
|
|
255
|
+
const plan = await resolve(accountPickerRequested);
|
|
265
256
|
const concrete = plan.host; // null = local
|
|
266
257
|
for (const k of HOST_SLOTS) {
|
|
267
258
|
if (isDeviceAuto(options[k])) {
|
|
268
259
|
options[k] = concrete ?? undefined;
|
|
269
260
|
}
|
|
270
261
|
}
|
|
271
|
-
const accountPickerRequested = deps.accountPickerRequested ?? false;
|
|
272
262
|
if (!accountPickerRequested && !options.strategy && !options.balanced) {
|
|
273
263
|
options.balanced = true;
|
|
274
264
|
}
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* - `buildManifest(agent, version, cwd)` — snapshot current state.
|
|
6
6
|
* - `isStale(manifest, agent, version, cwd)` — true when any tracked
|
|
7
|
-
* resource has drifted from its stored fingerprint
|
|
7
|
+
* resource has drifted from its stored fingerprint, or when an artifact
|
|
8
|
+
* the last full sync wrote (`writtenTargets`) has been deleted from the
|
|
9
|
+
* version home (#2398).
|
|
8
10
|
*
|
|
9
11
|
* `loadManifest` / `saveManifest` round-trip the on-disk JSON. The format
|
|
10
12
|
* version stays at 1; new optional fields (workflows, plugins) on old files
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* - `buildManifest(agent, version, cwd)` — snapshot current state.
|
|
6
6
|
* - `isStale(manifest, agent, version, cwd)` — true when any tracked
|
|
7
|
-
* resource has drifted from its stored fingerprint
|
|
7
|
+
* resource has drifted from its stored fingerprint, or when an artifact
|
|
8
|
+
* the last full sync wrote (`writtenTargets`) has been deleted from the
|
|
9
|
+
* version home (#2398).
|
|
8
10
|
*
|
|
9
11
|
* `loadManifest` / `saveManifest` round-trip the on-disk JSON. The format
|
|
10
12
|
* version stays at 1; new optional fields (workflows, plugins) on old files
|
|
@@ -91,6 +93,12 @@ export function buildManifest(agent, version, cwd, previous) {
|
|
|
91
93
|
subagents: {},
|
|
92
94
|
workflows: {},
|
|
93
95
|
plugins: {},
|
|
96
|
+
// Empty baseline ("nothing recorded to verify"). The full-sync
|
|
97
|
+
// orchestrator (versions.ts) replaces this with the writer-reported
|
|
98
|
+
// artifact paths right before saving; a manifest built outside that path
|
|
99
|
+
// simply verifies nothing. Only an ABSENT field (a pre-upgrade manifest
|
|
100
|
+
// on disk) forces the one-time migration re-sync in isStale.
|
|
101
|
+
writtenTargets: [],
|
|
94
102
|
};
|
|
95
103
|
for (const { checker, field } of STANDARD_CHECKERS) {
|
|
96
104
|
const target = manifest[field];
|
|
@@ -143,5 +151,17 @@ export function isStale(manifest, agent, version, cwd) {
|
|
|
143
151
|
return true;
|
|
144
152
|
if (isRulesStale(manifest.rules, agent, version, cwd))
|
|
145
153
|
return true;
|
|
154
|
+
// Home-side deletion check (#2398): every artifact the last full sync wrote
|
|
155
|
+
// must still exist, or the version home has rotted and needs a re-sync —
|
|
156
|
+
// regardless of source fingerprints. One existsSync per path, no content
|
|
157
|
+
// reads, so the fast-guard budget holds (RUSH-2320). A manifest that
|
|
158
|
+
// predates the field reads as stale once so the next full sync records the
|
|
159
|
+
// baseline (same precedent as the optional workflows/plugins maps).
|
|
160
|
+
if (manifest.writtenTargets === undefined)
|
|
161
|
+
return true;
|
|
162
|
+
for (const target of manifest.writtenTargets) {
|
|
163
|
+
if (!fs.existsSync(target))
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
146
166
|
return false;
|
|
147
167
|
}
|
|
@@ -54,4 +54,15 @@ export interface SyncManifest {
|
|
|
54
54
|
subagents: Record<string, DirEntry>;
|
|
55
55
|
workflows?: Record<string, DirEntry>;
|
|
56
56
|
plugins?: Record<string, PluginEntry>;
|
|
57
|
+
/**
|
|
58
|
+
* Absolute paths of the artifacts the last full sync materialized in the
|
|
59
|
+
* version home (writer-reported — see `WriteResult.paths`). `isStale`
|
|
60
|
+
* treats a missing path as stale so a deleted resource is restored by a
|
|
61
|
+
* plain `agents sync`, not only `--force` (#2398). An absent field means a
|
|
62
|
+
* pre-upgrade manifest: it reads as stale once, and the resulting full sync
|
|
63
|
+
* establishes the baseline — same migration precedent as the optional
|
|
64
|
+
* `workflows`/`plugins` maps above. An empty array is a valid baseline
|
|
65
|
+
* ("nothing recorded to verify"), never a migration trigger.
|
|
66
|
+
*/
|
|
67
|
+
writtenTargets?: string[];
|
|
57
68
|
}
|
|
@@ -25,7 +25,7 @@ import { supports } from '../../capabilities.js';
|
|
|
25
25
|
import { safeJoin } from '../../paths.js';
|
|
26
26
|
import { markdownToToml } from '../../convert.js';
|
|
27
27
|
import { commandAppliesTo, parseCommandMetadata } from '../../commands.js';
|
|
28
|
-
import { installCommandSkillToVersion, listCommandSkillsInVersion, removeCommandSkillFromVersion, shouldAlsoInstallCommandAsSkill, shouldInstallCommandAsSkill, } from '../../command-skills.js';
|
|
28
|
+
import { commandSkillName, installCommandSkillToVersion, listCommandSkillsInVersion, removeCommandSkillFromVersion, shouldAlsoInstallCommandAsSkill, shouldInstallCommandAsSkill, } from '../../command-skills.js';
|
|
29
29
|
import { installGooseCommandToVersion, listGooseCommandsInVersion, removeGooseCommandFromVersion, } from '../../goose-commands.js';
|
|
30
30
|
import { resolveCommandSource, trustedSkillRoots } from './sources.js';
|
|
31
31
|
import { lazyAgentMap } from './lazy-map.js';
|
|
@@ -50,6 +50,7 @@ function buildCommandsWriter(agent) {
|
|
|
50
50
|
fs.mkdirSync(commandsTarget, { recursive: true });
|
|
51
51
|
}
|
|
52
52
|
const synced = [];
|
|
53
|
+
const paths = [];
|
|
53
54
|
for (const cmd of selection) {
|
|
54
55
|
const srcFile = resolveCommandSource(cmd);
|
|
55
56
|
if (!srcFile)
|
|
@@ -63,9 +64,13 @@ function buildCommandsWriter(agent) {
|
|
|
63
64
|
// which is a deliberate no-op, not a failure — the native file is still written.
|
|
64
65
|
if (!installed.success)
|
|
65
66
|
continue;
|
|
67
|
+
if (!installed.skipped) {
|
|
68
|
+
paths.push(safeJoin(path.join(agentDir, 'skills'), commandSkillName(cmd)));
|
|
69
|
+
}
|
|
66
70
|
}
|
|
67
71
|
if (supportsCommands && agent === 'goose') {
|
|
68
72
|
// Goose: recipe YAML + config.yaml slash_commands entry, not a file copy.
|
|
73
|
+
// No artifact path recorded — the recipe layout lives in goose-commands.ts.
|
|
69
74
|
const installed = installGooseCommandToVersion(versionHome, cmd, srcFile);
|
|
70
75
|
if (!installed.success)
|
|
71
76
|
continue;
|
|
@@ -73,14 +78,18 @@ function buildCommandsWriter(agent) {
|
|
|
73
78
|
else if (supportsCommands && agentConfig.format === 'toml') {
|
|
74
79
|
const content = fs.readFileSync(srcFile, 'utf-8');
|
|
75
80
|
const tomlContent = markdownToToml(cmd, content);
|
|
76
|
-
|
|
81
|
+
const dest = safeJoin(commandsTarget, `${cmd}.toml`);
|
|
82
|
+
fs.writeFileSync(dest, tomlContent);
|
|
83
|
+
paths.push(dest);
|
|
77
84
|
}
|
|
78
85
|
else if (supportsCommands) {
|
|
79
|
-
|
|
86
|
+
const dest = safeJoin(commandsTarget, `${cmd}.md`);
|
|
87
|
+
fs.copyFileSync(srcFile, dest);
|
|
88
|
+
paths.push(dest);
|
|
80
89
|
}
|
|
81
90
|
synced.push(cmd);
|
|
82
91
|
}
|
|
83
|
-
return { synced };
|
|
92
|
+
return { synced, paths };
|
|
84
93
|
},
|
|
85
94
|
remove({ versionHome, name }) {
|
|
86
95
|
const agentConfig = AGENTS[agent];
|
|
@@ -75,6 +75,7 @@ function buildHooksWriter(agent) {
|
|
|
75
75
|
const hooksTarget = path.join(agentDir, 'hooks');
|
|
76
76
|
fs.mkdirSync(hooksTarget, { recursive: true });
|
|
77
77
|
const synced = [];
|
|
78
|
+
const paths = [];
|
|
78
79
|
for (const hook of selection) {
|
|
79
80
|
const srcFile = resolveHookSource(hook);
|
|
80
81
|
if (!srcFile)
|
|
@@ -82,6 +83,7 @@ function buildHooksWriter(agent) {
|
|
|
82
83
|
const destFile = safeJoin(hooksTarget, hook);
|
|
83
84
|
if (copyHookSource(srcFile, destFile)) {
|
|
84
85
|
synced.push(hook);
|
|
86
|
+
paths.push(destFile);
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
// Native hook registration in settings.json/hooks.json. Grok is included
|
|
@@ -93,7 +95,7 @@ function buildHooksWriter(agent) {
|
|
|
93
95
|
if (agent === 'claude' || agent === 'codex' || agent === 'antigravity' || agent === 'kimi' || agent === 'droid' || agent === 'copilot' || agent === 'kiro' || agent === 'goose' || agent === 'cursor' || agent === 'grok' || agent === 'hermes' || agent === 'muse') {
|
|
94
96
|
registerHooksToSettings(agent, versionHome);
|
|
95
97
|
}
|
|
96
|
-
return { synced };
|
|
98
|
+
return { synced, paths };
|
|
97
99
|
},
|
|
98
100
|
remove({ versionHome, name }) {
|
|
99
101
|
// Delete the copied hook artifact from `{agentDir}/hooks/<name>`. Native
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP writer — thin dispatcher into `installMcpServers` from `lib/mcp.ts`.
|
|
3
|
-
*
|
|
4
|
-
* The per-agent format handling (Claude CLI, Codex TOML, Cursor JSON, etc.)
|
|
5
|
-
* lives in lib/mcp.ts; we keep it there to avoid the import cycle with
|
|
6
|
-
* `versions.ts`. The writer simply hands the selection to that function.
|
|
7
|
-
*/
|
|
8
1
|
import type { AgentId } from '../../types.js';
|
|
9
2
|
import type { ResourceWriter } from './types.js';
|
|
10
3
|
export declare const mcpWriters: Partial<Record<AgentId, ResourceWriter<string[]>>>;
|