@phnx-labs/agents-cli 1.14.7 → 1.16.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 +78 -39
- package/README.md +74 -7
- package/dist/commands/alias.js +2 -2
- package/dist/commands/beta.js +6 -1
- package/dist/commands/browser-picker.d.ts +21 -0
- package/dist/commands/browser-picker.js +114 -0
- package/dist/commands/browser.js +546 -75
- package/dist/commands/commands.js +72 -22
- package/dist/commands/daemon.js +2 -2
- package/dist/commands/exec.js +9 -2
- package/dist/commands/fork.js +2 -2
- package/dist/commands/hooks.js +71 -26
- package/dist/commands/mcp.js +85 -43
- package/dist/commands/plugins.js +48 -15
- package/dist/commands/prune.d.ts +0 -20
- package/dist/commands/prune.js +291 -16
- package/dist/commands/pull.js +3 -3
- package/dist/commands/repo.js +1 -1
- package/dist/commands/routines.js +2 -2
- package/dist/commands/secrets.js +37 -1
- package/dist/commands/sessions.js +62 -19
- package/dist/commands/{init.d.ts → setup.d.ts} +7 -6
- package/dist/commands/{init.js → setup.js} +32 -21
- package/dist/commands/skills.js +60 -19
- package/dist/commands/subagents.js +41 -13
- package/dist/commands/teams.js +2 -3
- package/dist/commands/usage.js +6 -0
- package/dist/commands/utils.d.ts +16 -0
- package/dist/commands/utils.js +32 -0
- package/dist/commands/versions.js +8 -6
- package/dist/commands/view.js +61 -16
- package/dist/index.d.ts +1 -1
- package/dist/index.js +17 -20
- package/dist/lib/agents.js +2 -2
- package/dist/lib/auto-pull-worker.js +2 -3
- package/dist/lib/auto-pull.js +2 -2
- package/dist/lib/browser/cdp.d.ts +7 -1
- package/dist/lib/browser/cdp.js +29 -1
- package/dist/lib/browser/chrome.js +6 -3
- package/dist/lib/browser/devices.d.ts +4 -0
- package/dist/lib/browser/devices.js +27 -0
- package/dist/lib/browser/drivers/local.js +9 -4
- package/dist/lib/browser/drivers/ssh.d.ts +1 -0
- package/dist/lib/browser/drivers/ssh.js +32 -4
- package/dist/lib/browser/ipc.js +145 -23
- package/dist/lib/browser/profiles.d.ts +5 -2
- package/dist/lib/browser/profiles.js +77 -37
- package/dist/lib/browser/service.d.ts +84 -13
- package/dist/lib/browser/service.js +806 -122
- package/dist/lib/browser/types.d.ts +81 -3
- package/dist/lib/browser/types.js +16 -0
- package/dist/lib/cloud/rush.js +2 -2
- package/dist/lib/cloud/store.js +2 -2
- package/dist/lib/commands.d.ts +1 -0
- package/dist/lib/commands.js +6 -2
- package/dist/lib/daemon.js +6 -7
- package/dist/lib/doctor-diff.js +4 -4
- package/dist/lib/events.d.ts +94 -1
- package/dist/lib/events.js +264 -6
- package/dist/lib/exec.js +16 -10
- package/dist/lib/hooks.d.ts +11 -7
- package/dist/lib/hooks.js +125 -49
- package/dist/lib/migrate.d.ts +1 -1
- package/dist/lib/migrate.js +1178 -21
- package/dist/lib/models.js +2 -2
- package/dist/lib/permissions.d.ts +14 -11
- package/dist/lib/permissions.js +46 -42
- package/dist/lib/plugins.d.ts +30 -1
- package/dist/lib/plugins.js +75 -3
- package/dist/lib/pty-server.js +9 -10
- package/dist/lib/resources/hooks.d.ts +5 -1
- package/dist/lib/resources/hooks.js +21 -4
- package/dist/lib/rotate.js +3 -4
- package/dist/lib/routines.d.ts +15 -0
- package/dist/lib/routines.js +68 -0
- package/dist/lib/runner.js +9 -5
- package/dist/lib/secrets/index.d.ts +14 -11
- package/dist/lib/secrets/index.js +49 -21
- package/dist/lib/secrets/linux.d.ts +27 -0
- package/dist/lib/secrets/linux.js +161 -0
- package/dist/lib/session/active.d.ts +3 -0
- package/dist/lib/session/active.js +92 -6
- package/dist/lib/session/cloud.js +2 -2
- package/dist/lib/session/db.d.ts +4 -0
- package/dist/lib/session/db.js +34 -3
- package/dist/lib/session/discover.js +30 -15
- package/dist/lib/session/team-filter.js +2 -2
- package/dist/lib/shims.d.ts +2 -2
- package/dist/lib/shims.js +6 -6
- package/dist/lib/skills.js +6 -2
- package/dist/lib/state.d.ts +86 -14
- package/dist/lib/state.js +150 -23
- package/dist/lib/subagents.d.ts +28 -0
- package/dist/lib/subagents.js +98 -1
- package/dist/lib/sync-manifest.d.ts +1 -1
- package/dist/lib/sync-manifest.js +3 -3
- package/dist/lib/teams/persistence.js +15 -5
- package/dist/lib/teams/registry.js +2 -2
- package/dist/lib/types.d.ts +32 -3
- package/dist/lib/types.js +3 -3
- package/dist/lib/usage.d.ts +1 -1
- package/dist/lib/usage.js +15 -48
- package/dist/lib/versions.js +31 -21
- package/package.json +1 -1
- package/scripts/postinstall.js +37 -9
package/dist/commands/prune.js
CHANGED
|
@@ -1,14 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Top-level `agents prune` — destructive cleanup across the install.
|
|
3
|
+
*
|
|
4
|
+
* Cleanup targets:
|
|
5
|
+
* - Resource orphans: command/skill/hook files inside a version home that no
|
|
6
|
+
* longer come from any source (deleted from ~/.agents/ but never reconciled
|
|
7
|
+
* into the version install).
|
|
8
|
+
* - Version duplicates: older installed versions of an agent that share an
|
|
9
|
+
* account with a newer installed version of the same agent.
|
|
10
|
+
* - Trash: soft-deleted resources in ~/.agents/.trash/ older than N days.
|
|
11
|
+
* - Sessions: session records in sessions.db older than N days.
|
|
12
|
+
* - Runs: routine execution logs, keeping only the last N per job.
|
|
13
|
+
*
|
|
14
|
+
* Sync (additive: copy missing/changed files into version homes) is no longer
|
|
15
|
+
* a user-facing verb — `syncResourcesToVersion` runs at agent launch and
|
|
16
|
+
* applies adds/updates automatically. Pruning, however, is destructive, so it
|
|
17
|
+
* stays explicit.
|
|
18
|
+
*
|
|
19
|
+
* Default scope: each agent's currently-pinned default version for orphan
|
|
20
|
+
* cleanup, plus the standard cross-agent version-dedup pass. Pass `--all`
|
|
21
|
+
* to widen orphan cleanup to every installed version.
|
|
22
|
+
*/
|
|
23
|
+
import * as fs from 'fs';
|
|
24
|
+
import * as path from 'path';
|
|
1
25
|
import chalk from 'chalk';
|
|
2
26
|
import { confirm } from '@inquirer/prompts';
|
|
3
27
|
import { diffVersionCommands, iterCommandsCapableVersions, removeCommandFromVersion, } from '../lib/commands.js';
|
|
4
28
|
import { diffVersionSkills, iterSkillsCapableVersions, removeSkillFromVersion, } from '../lib/skills.js';
|
|
5
29
|
import { diffVersionHooks, iterHooksCapableVersions, removeHookFromVersion, } from '../lib/hooks.js';
|
|
30
|
+
import { diffVersionPlugins, iterPluginsCapableVersions, removePluginSkillFromVersion, } from '../lib/plugins.js';
|
|
31
|
+
import { diffVersionSubagents, iterSubagentsCapableVersions, removeSubagentFromVersion, } from '../lib/subagents.js';
|
|
6
32
|
import { getGlobalDefault } from '../lib/versions.js';
|
|
7
33
|
import { resolveAgentName, formatAgentError } from '../lib/agents.js';
|
|
8
34
|
import { pruneDuplicates } from './view.js';
|
|
9
35
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
10
|
-
|
|
11
|
-
|
|
36
|
+
import { getTrashDir } from '../lib/state.js';
|
|
37
|
+
import { countSessionsOlderThan, deleteSessionsOlderThan } from '../lib/session/db.js';
|
|
38
|
+
import { previewRunsPrune, pruneRuns, countAllRuns } from '../lib/routines.js';
|
|
39
|
+
const RESOURCE_TYPES = ['commands', 'skills', 'hooks', 'plugins', 'subagents'];
|
|
40
|
+
const STATE_TYPES = ['trash', 'sessions', 'runs'];
|
|
41
|
+
const ALL_TYPES = [...RESOURCE_TYPES, 'versions', ...STATE_TYPES];
|
|
12
42
|
function scopePairs(pairs, all) {
|
|
13
43
|
if (all)
|
|
14
44
|
return pairs;
|
|
@@ -40,6 +70,22 @@ function collectOrphans(types, all) {
|
|
|
40
70
|
}
|
|
41
71
|
}
|
|
42
72
|
}
|
|
73
|
+
if (types.includes('plugins')) {
|
|
74
|
+
for (const { agent, version } of scopePairs(iterPluginsCapableVersions(), all)) {
|
|
75
|
+
const diff = diffVersionPlugins(agent, version);
|
|
76
|
+
if (diff.orphans.length > 0) {
|
|
77
|
+
groups.push({ type: 'plugins', agent, version, orphans: diff.orphans });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (types.includes('subagents')) {
|
|
82
|
+
for (const { agent, version } of scopePairs(iterSubagentsCapableVersions(), all)) {
|
|
83
|
+
const diff = diffVersionSubagents(agent, version);
|
|
84
|
+
if (diff.orphans.length > 0) {
|
|
85
|
+
groups.push({ type: 'subagents', agent, version, orphans: diff.orphans });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
43
89
|
return groups;
|
|
44
90
|
}
|
|
45
91
|
function removeOne(group, name) {
|
|
@@ -50,6 +96,10 @@ function removeOne(group, name) {
|
|
|
50
96
|
return removeSkillFromVersion(group.agent, group.version, name);
|
|
51
97
|
case 'hooks':
|
|
52
98
|
return removeHookFromVersion(group.agent, group.version, name);
|
|
99
|
+
case 'plugins':
|
|
100
|
+
return removePluginSkillFromVersion(group.agent, group.version, name);
|
|
101
|
+
case 'subagents':
|
|
102
|
+
return removeSubagentFromVersion(group.agent, group.version, name);
|
|
53
103
|
}
|
|
54
104
|
}
|
|
55
105
|
function parseTarget(arg) {
|
|
@@ -59,6 +109,9 @@ function parseTarget(arg) {
|
|
|
59
109
|
if (RESOURCE_TYPES.includes(arg)) {
|
|
60
110
|
return { resourceTypes: [arg], includeVersions: false };
|
|
61
111
|
}
|
|
112
|
+
if (STATE_TYPES.includes(arg)) {
|
|
113
|
+
return { resourceTypes: [], includeVersions: false, stateType: arg };
|
|
114
|
+
}
|
|
62
115
|
if (arg === 'versions') {
|
|
63
116
|
return { resourceTypes: [], includeVersions: true };
|
|
64
117
|
}
|
|
@@ -72,6 +125,202 @@ function parseTarget(arg) {
|
|
|
72
125
|
console.log(chalk.gray(formatAgentError(arg)));
|
|
73
126
|
process.exit(1);
|
|
74
127
|
}
|
|
128
|
+
function parseDays(value, defaultDays) {
|
|
129
|
+
const match = value.match(/^(\d+)d?$/);
|
|
130
|
+
if (match)
|
|
131
|
+
return parseInt(match[1], 10);
|
|
132
|
+
return defaultDays;
|
|
133
|
+
}
|
|
134
|
+
function formatBytes(bytes) {
|
|
135
|
+
if (bytes < 1024)
|
|
136
|
+
return `${bytes} B`;
|
|
137
|
+
if (bytes < 1024 * 1024)
|
|
138
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
139
|
+
if (bytes < 1024 * 1024 * 1024)
|
|
140
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
141
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
142
|
+
}
|
|
143
|
+
function getDirSize(dirPath) {
|
|
144
|
+
if (!fs.existsSync(dirPath))
|
|
145
|
+
return 0;
|
|
146
|
+
let size = 0;
|
|
147
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
148
|
+
for (const entry of entries) {
|
|
149
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
150
|
+
if (entry.isDirectory()) {
|
|
151
|
+
size += getDirSize(fullPath);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
try {
|
|
155
|
+
size += fs.statSync(fullPath).size;
|
|
156
|
+
}
|
|
157
|
+
catch { /* ignore */ }
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return size;
|
|
161
|
+
}
|
|
162
|
+
async function runTrashPrune(options) {
|
|
163
|
+
const trashDir = getTrashDir();
|
|
164
|
+
if (!fs.existsSync(trashDir)) {
|
|
165
|
+
console.log(chalk.green('Trash is empty.'));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const days = parseDays(options.olderThan || '30d', 30);
|
|
169
|
+
const cutoffMs = Date.now() - days * 24 * 60 * 60 * 1000;
|
|
170
|
+
const toPrune = [];
|
|
171
|
+
function scanDir(dir) {
|
|
172
|
+
if (!fs.existsSync(dir))
|
|
173
|
+
return;
|
|
174
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
175
|
+
const fullPath = path.join(dir, entry.name);
|
|
176
|
+
try {
|
|
177
|
+
const stat = fs.statSync(fullPath);
|
|
178
|
+
if (stat.mtimeMs < cutoffMs) {
|
|
179
|
+
toPrune.push({ path: fullPath, mtime: stat.mtimeMs, size: entry.isDirectory() ? getDirSize(fullPath) : stat.size });
|
|
180
|
+
}
|
|
181
|
+
else if (entry.isDirectory()) {
|
|
182
|
+
scanDir(fullPath);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch { /* skip inaccessible */ }
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
scanDir(trashDir);
|
|
189
|
+
if (toPrune.length === 0) {
|
|
190
|
+
console.log(chalk.green(`No trash entries older than ${days} days.`));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const totalSize = toPrune.reduce((sum, e) => sum + e.size, 0);
|
|
194
|
+
console.log(chalk.bold(`Trash entries older than ${days} days\n`));
|
|
195
|
+
for (const entry of toPrune.slice(0, 20)) {
|
|
196
|
+
const age = Math.floor((Date.now() - entry.mtime) / (24 * 60 * 60 * 1000));
|
|
197
|
+
console.log(` ${chalk.gray(`${age}d ago`)} ${path.relative(trashDir, entry.path)}`);
|
|
198
|
+
}
|
|
199
|
+
if (toPrune.length > 20) {
|
|
200
|
+
console.log(chalk.gray(` ... and ${toPrune.length - 20} more`));
|
|
201
|
+
}
|
|
202
|
+
console.log();
|
|
203
|
+
if (options.dryRun) {
|
|
204
|
+
console.log(chalk.gray(`${toPrune.length} entries (${formatBytes(totalSize)}). Run without --dry-run to delete.`));
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (!options.yes) {
|
|
208
|
+
if (!isInteractiveTerminal()) {
|
|
209
|
+
console.log(chalk.yellow('Non-interactive shell: pass -y to confirm, or --dry-run to preview.'));
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
let ok = false;
|
|
213
|
+
try {
|
|
214
|
+
ok = await confirm({ message: `Delete ${toPrune.length} entries (${formatBytes(totalSize)})?`, default: false });
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
if (isPromptCancelled(err)) {
|
|
218
|
+
console.log(chalk.gray('Cancelled'));
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
throw err;
|
|
222
|
+
}
|
|
223
|
+
if (!ok) {
|
|
224
|
+
console.log(chalk.gray('Cancelled'));
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
let deleted = 0;
|
|
229
|
+
for (const entry of toPrune) {
|
|
230
|
+
try {
|
|
231
|
+
fs.rmSync(entry.path, { recursive: true, force: true });
|
|
232
|
+
deleted++;
|
|
233
|
+
}
|
|
234
|
+
catch { /* ignore */ }
|
|
235
|
+
}
|
|
236
|
+
console.log(chalk.green(`Pruned ${deleted} trash entries (${formatBytes(totalSize)}).`));
|
|
237
|
+
}
|
|
238
|
+
async function runSessionsPrune(options) {
|
|
239
|
+
const days = parseDays(options.olderThan || '90d', 90);
|
|
240
|
+
const cutoffMs = Date.now() - days * 24 * 60 * 60 * 1000;
|
|
241
|
+
const count = countSessionsOlderThan(cutoffMs);
|
|
242
|
+
if (count === 0) {
|
|
243
|
+
console.log(chalk.green(`No sessions older than ${days} days.`));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
console.log(chalk.bold(`Sessions older than ${days} days: ${count}\n`));
|
|
247
|
+
if (options.dryRun) {
|
|
248
|
+
console.log(chalk.gray(`${count} session(s). Run without --dry-run to delete.`));
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (!options.yes) {
|
|
252
|
+
if (!isInteractiveTerminal()) {
|
|
253
|
+
console.log(chalk.yellow('Non-interactive shell: pass -y to confirm, or --dry-run to preview.'));
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
let ok = false;
|
|
257
|
+
try {
|
|
258
|
+
ok = await confirm({ message: `Delete ${count} session records?`, default: false });
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
if (isPromptCancelled(err)) {
|
|
262
|
+
console.log(chalk.gray('Cancelled'));
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
throw err;
|
|
266
|
+
}
|
|
267
|
+
if (!ok) {
|
|
268
|
+
console.log(chalk.gray('Cancelled'));
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const deleted = deleteSessionsOlderThan(cutoffMs);
|
|
273
|
+
console.log(chalk.green(`Pruned ${deleted} session records.`));
|
|
274
|
+
}
|
|
275
|
+
async function runRunsPrune(options) {
|
|
276
|
+
const keep = options.keep ? parseInt(options.keep, 10) : 10;
|
|
277
|
+
if (isNaN(keep) || keep < 0) {
|
|
278
|
+
console.log(chalk.red('--keep must be a non-negative integer'));
|
|
279
|
+
process.exit(1);
|
|
280
|
+
}
|
|
281
|
+
const preview = previewRunsPrune(keep);
|
|
282
|
+
const total = countAllRuns();
|
|
283
|
+
if (preview.length === 0) {
|
|
284
|
+
console.log(chalk.green(`All jobs have ${keep} or fewer runs. Nothing to prune.`));
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
console.log(chalk.bold(`Routine runs to prune (keeping last ${keep} per job)\n`));
|
|
288
|
+
const byJob = new Map();
|
|
289
|
+
for (const run of preview) {
|
|
290
|
+
byJob.set(run.jobName, (byJob.get(run.jobName) || 0) + 1);
|
|
291
|
+
}
|
|
292
|
+
for (const [job, count] of byJob) {
|
|
293
|
+
console.log(` ${chalk.cyan(job)}: ${count} old runs`);
|
|
294
|
+
}
|
|
295
|
+
console.log();
|
|
296
|
+
if (options.dryRun) {
|
|
297
|
+
console.log(chalk.gray(`${preview.length} of ${total} runs would be deleted. Run without --dry-run to delete.`));
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (!options.yes) {
|
|
301
|
+
if (!isInteractiveTerminal()) {
|
|
302
|
+
console.log(chalk.yellow('Non-interactive shell: pass -y to confirm, or --dry-run to preview.'));
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
let ok = false;
|
|
306
|
+
try {
|
|
307
|
+
ok = await confirm({ message: `Delete ${preview.length} old runs?`, default: false });
|
|
308
|
+
}
|
|
309
|
+
catch (err) {
|
|
310
|
+
if (isPromptCancelled(err)) {
|
|
311
|
+
console.log(chalk.gray('Cancelled'));
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
throw err;
|
|
315
|
+
}
|
|
316
|
+
if (!ok) {
|
|
317
|
+
console.log(chalk.gray('Cancelled'));
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const { deleted, bytesFreed } = pruneRuns(keep);
|
|
322
|
+
console.log(chalk.green(`Pruned ${deleted} runs (${formatBytes(bytesFreed)}).`));
|
|
323
|
+
}
|
|
75
324
|
async function runOrphanPrune(resourceTypes, options) {
|
|
76
325
|
const groups = collectOrphans(resourceTypes, options.all === true);
|
|
77
326
|
if (groups.length === 0) {
|
|
@@ -133,10 +382,12 @@ async function runOrphanPrune(resourceTypes, options) {
|
|
|
133
382
|
export function registerPruneCommand(program) {
|
|
134
383
|
program
|
|
135
384
|
.command('prune [target]')
|
|
136
|
-
.description('Remove orphan resources
|
|
385
|
+
.description('Remove orphan resources, old versions, trash, sessions, or routine runs')
|
|
137
386
|
.option('--all', 'For orphan cleanup: sweep every installed version (default: current default version per agent)')
|
|
138
|
-
.option('--dry-run', 'Show what would be removed without deleting')
|
|
387
|
+
.option('--dry-run', 'Show what would be removed without deleting (default for state targets)')
|
|
139
388
|
.option('-y, --yes', 'Skip confirmation prompt')
|
|
389
|
+
.option('--older-than <days>', 'For trash/sessions: delete entries older than N days (default: 30d for trash, 90d for sessions)')
|
|
390
|
+
.option('--keep <n>', 'For runs: keep the last N runs per job (default: 10)')
|
|
140
391
|
.addHelpText('after', `
|
|
141
392
|
Targets:
|
|
142
393
|
(none) Orphans across commands, skills, hooks + duplicate versions
|
|
@@ -145,6 +396,9 @@ Targets:
|
|
|
145
396
|
hooks Orphan hook scripts only
|
|
146
397
|
versions Older duplicate version installs only
|
|
147
398
|
<agent> Older duplicate versions for one agent (e.g. 'claude')
|
|
399
|
+
trash Soft-deleted resources older than --older-than days (default 30)
|
|
400
|
+
sessions Session records in sessions.db older than --older-than days (default 90)
|
|
401
|
+
runs Routine execution logs, keeping only --keep per job (default 10)
|
|
148
402
|
|
|
149
403
|
Examples:
|
|
150
404
|
# Full sweep: orphan resources + duplicate versions for current defaults
|
|
@@ -156,14 +410,26 @@ Examples:
|
|
|
156
410
|
# Just version dedup
|
|
157
411
|
agents prune versions
|
|
158
412
|
|
|
159
|
-
# Just version dedup for one agent
|
|
160
|
-
agents prune claude
|
|
161
|
-
|
|
162
413
|
# Sweep every installed version's orphans, not only the defaults
|
|
163
414
|
agents prune --all
|
|
164
415
|
|
|
165
|
-
# Preview
|
|
166
|
-
agents prune --dry-run
|
|
416
|
+
# Preview trash entries older than 30 days
|
|
417
|
+
agents prune trash --dry-run
|
|
418
|
+
|
|
419
|
+
# Delete trash entries older than 60 days
|
|
420
|
+
agents prune trash --older-than 60 -y
|
|
421
|
+
|
|
422
|
+
# Preview session cleanup (90+ days old)
|
|
423
|
+
agents prune sessions --dry-run
|
|
424
|
+
|
|
425
|
+
# Delete sessions older than 180 days
|
|
426
|
+
agents prune sessions --older-than 180 -y
|
|
427
|
+
|
|
428
|
+
# Preview runs cleanup (keeping last 10)
|
|
429
|
+
agents prune runs --dry-run
|
|
430
|
+
|
|
431
|
+
# Keep only the last 5 runs per job
|
|
432
|
+
agents prune runs --keep 5 -y
|
|
167
433
|
|
|
168
434
|
What's an orphan?
|
|
169
435
|
A command, skill, or hook present inside a version home but missing from every
|
|
@@ -171,18 +437,27 @@ What's an orphan?
|
|
|
171
437
|
repos). Usually leftovers from a resource that was deleted or moved but never
|
|
172
438
|
reconciled into the version install.
|
|
173
439
|
|
|
174
|
-
What this does NOT do:
|
|
175
|
-
Adds and updates flow through the auto-sync that runs when you launch the
|
|
176
|
-
agent — there is no manual sync verb.
|
|
177
|
-
|
|
178
440
|
Soft-delete:
|
|
179
441
|
Version directories are NEVER hard-deleted. \`prune\` moves them to
|
|
180
|
-
~/.agents
|
|
181
|
-
|
|
182
|
-
The trash never auto-expires; \`rm -rf\` it manually when you're sure.
|
|
442
|
+
~/.agents/.trash/versions/<agent>/<version>/<timestamp>/. Use
|
|
443
|
+
\`agents prune trash\` to expire old trash entries.
|
|
183
444
|
`)
|
|
184
445
|
.action(async (target, options) => {
|
|
185
446
|
const parsed = parseTarget(target);
|
|
447
|
+
if (parsed.stateType) {
|
|
448
|
+
switch (parsed.stateType) {
|
|
449
|
+
case 'trash':
|
|
450
|
+
await runTrashPrune(options);
|
|
451
|
+
break;
|
|
452
|
+
case 'sessions':
|
|
453
|
+
await runSessionsPrune(options);
|
|
454
|
+
break;
|
|
455
|
+
case 'runs':
|
|
456
|
+
await runRunsPrune(options);
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
186
461
|
if (parsed.resourceTypes.length > 0) {
|
|
187
462
|
await runOrphanPrune(parsed.resourceTypes, options);
|
|
188
463
|
if (parsed.includeVersions)
|
package/dist/commands/pull.js
CHANGED
|
@@ -80,12 +80,12 @@ Skip CLI installs with --skip-clis when you only want config updates, not versio
|
|
|
80
80
|
// auto-syncs the system repo in the background and surfaces upstream
|
|
81
81
|
// changes for user/extra repos as one-line notices. Repo lifecycle is
|
|
82
82
|
// managed under `agents repo`. We keep this command functional today
|
|
83
|
-
// because `agents
|
|
84
|
-
//
|
|
83
|
+
// because `agents setup` still invokes it for first-time setup; once
|
|
84
|
+
// setup is refactored to call the bootstrap helpers directly, this
|
|
85
85
|
// command will hard-error like `agents memory` does.
|
|
86
86
|
if (!options.yes && process.argv[2] === 'pull') {
|
|
87
87
|
process.stderr.write('agents-cli: "agents pull" is deprecated.\n' +
|
|
88
|
-
' First-time setup: agents
|
|
88
|
+
' First-time setup: agents setup\n' +
|
|
89
89
|
' Force a sync now: agents repo pull\n' +
|
|
90
90
|
' Push your repo: agents repo push\n\n');
|
|
91
91
|
}
|
package/dist/commands/repo.js
CHANGED
|
@@ -257,7 +257,7 @@ Examples:
|
|
|
257
257
|
const systemStatus = !systemOnDisk
|
|
258
258
|
? chalk.red('missing')
|
|
259
259
|
: !systemIsGit
|
|
260
|
-
? chalk.yellow('not a git repo — run: agents
|
|
260
|
+
? chalk.yellow('not a git repo — run: agents setup')
|
|
261
261
|
: chalk.green('cloned');
|
|
262
262
|
const systemCommitLabel = systemCommit ? chalk.gray(`(${systemCommit})`) : '';
|
|
263
263
|
console.log(chalk.bold('System (~/.agents-system/)'));
|
|
@@ -571,8 +571,8 @@ Examples:
|
|
|
571
571
|
.action(async (options) => {
|
|
572
572
|
if (options.follow) {
|
|
573
573
|
const { exec: execCb } = await import('child_process');
|
|
574
|
-
const {
|
|
575
|
-
const logPath = path.join(
|
|
574
|
+
const { getDaemonDir } = await import('../lib/state.js');
|
|
575
|
+
const logPath = path.join(getDaemonDir(), 'logs.jsonl');
|
|
576
576
|
const child = execCb(`tail -f "${logPath}"`);
|
|
577
577
|
child.stdout?.pipe(process.stdout);
|
|
578
578
|
child.stderr?.pipe(process.stderr);
|
package/dist/commands/secrets.js
CHANGED
|
@@ -280,6 +280,10 @@ Examples:
|
|
|
280
280
|
# Eval the bundle into your current shell
|
|
281
281
|
eval "$(agents secrets export prod --plaintext)"
|
|
282
282
|
|
|
283
|
+
# Run a command with secrets injected
|
|
284
|
+
agents secrets exec prod -- ./deploy.sh
|
|
285
|
+
agents secrets exec hetzner.com -- crabbox list
|
|
286
|
+
|
|
283
287
|
# Remove one key (purges the keychain item by default)
|
|
284
288
|
agents secrets remove prod STRIPE_API_KEY
|
|
285
289
|
|
|
@@ -297,7 +301,7 @@ Examples:
|
|
|
297
301
|
registerCommandGroups(cmd, [
|
|
298
302
|
{ title: 'Bundle commands', names: ['list', 'view', 'create', 'delete'] },
|
|
299
303
|
{ title: 'Secret commands', names: ['add', 'rotate', 'remove', 'import', 'export'] },
|
|
300
|
-
{ title: 'Utilities', names: ['generate'] },
|
|
304
|
+
{ title: 'Utilities', names: ['exec', 'generate'] },
|
|
301
305
|
]);
|
|
302
306
|
cmd
|
|
303
307
|
.command('list')
|
|
@@ -711,6 +715,38 @@ Examples:
|
|
|
711
715
|
process.exit(1);
|
|
712
716
|
}
|
|
713
717
|
});
|
|
718
|
+
cmd
|
|
719
|
+
.command('exec <bundle> [command...]')
|
|
720
|
+
.description('Run a command with the bundle\'s secrets injected into the environment')
|
|
721
|
+
.allowUnknownOption()
|
|
722
|
+
.action(async (bundleName, commandParts) => {
|
|
723
|
+
try {
|
|
724
|
+
if (commandParts.length === 0) {
|
|
725
|
+
console.error(chalk.red('Usage: agents secrets exec <bundle> -- <command...>'));
|
|
726
|
+
process.exit(1);
|
|
727
|
+
}
|
|
728
|
+
const { resolveBundleEnv } = await import('../lib/secrets/bundles.js');
|
|
729
|
+
const bundle = readBundle(bundleName);
|
|
730
|
+
const secretEnv = resolveBundleEnv(bundle);
|
|
731
|
+
const { spawn } = await import('child_process');
|
|
732
|
+
const [cmd, ...args] = commandParts;
|
|
733
|
+
const proc = spawn(cmd, args, {
|
|
734
|
+
stdio: 'inherit',
|
|
735
|
+
env: { ...process.env, ...secretEnv },
|
|
736
|
+
});
|
|
737
|
+
proc.on('close', (code) => process.exit(code ?? 0));
|
|
738
|
+
proc.on('error', (err) => {
|
|
739
|
+
console.error(chalk.red(`Failed to run '${cmd}': ${err.message}`));
|
|
740
|
+
process.exit(1);
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
catch (err) {
|
|
744
|
+
if (isPromptCancelled(err))
|
|
745
|
+
return;
|
|
746
|
+
console.error(chalk.red(err.message));
|
|
747
|
+
process.exit(1);
|
|
748
|
+
}
|
|
749
|
+
});
|
|
714
750
|
cmd
|
|
715
751
|
.command('generate [length]')
|
|
716
752
|
.description('Generate a random password')
|
|
@@ -160,6 +160,26 @@ function formatStartedAt(startedAtMs) {
|
|
|
160
160
|
return '-';
|
|
161
161
|
return formatRelativeTime(new Date(startedAtMs).toISOString());
|
|
162
162
|
}
|
|
163
|
+
/** Build a display-friendly description for an active session (label or topic). */
|
|
164
|
+
function buildSessionDescription(s) {
|
|
165
|
+
if (s.context === 'cloud') {
|
|
166
|
+
return `${s.cloudProvider ?? ''}${s.cloudTaskId ? ` · ${s.cloudTaskId.slice(0, 12)}` : ''}`;
|
|
167
|
+
}
|
|
168
|
+
if (s.context === 'teams') {
|
|
169
|
+
const parts = [s.teamName];
|
|
170
|
+
if (s.label)
|
|
171
|
+
parts.push(s.label);
|
|
172
|
+
else if (s.topic)
|
|
173
|
+
parts.push(s.topic);
|
|
174
|
+
return parts.filter(Boolean).join(' · ');
|
|
175
|
+
}
|
|
176
|
+
// Terminal or headless: prefer label, then topic
|
|
177
|
+
if (s.label)
|
|
178
|
+
return s.label;
|
|
179
|
+
if (s.topic)
|
|
180
|
+
return s.topic;
|
|
181
|
+
return '';
|
|
182
|
+
}
|
|
163
183
|
/** Render the unified active-session view. */
|
|
164
184
|
async function renderActiveSessions(asJson) {
|
|
165
185
|
const sessions = await getActiveSessions();
|
|
@@ -171,26 +191,49 @@ async function renderActiveSessions(asJson) {
|
|
|
171
191
|
console.log(chalk.gray('No active agent sessions.'));
|
|
172
192
|
return;
|
|
173
193
|
}
|
|
194
|
+
// Group sessions by workspace (cwd), with cloud/undefined grouped separately
|
|
195
|
+
const byWorkspace = new Map();
|
|
174
196
|
for (const s of sessions) {
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
197
|
+
const key = s.cwd ?? (s.context === 'cloud' ? '__cloud__' : '__unknown__');
|
|
198
|
+
const list = byWorkspace.get(key) || [];
|
|
199
|
+
list.push(s);
|
|
200
|
+
byWorkspace.set(key, list);
|
|
201
|
+
}
|
|
202
|
+
// Sort workspaces: most sessions first, then alphabetically
|
|
203
|
+
const sortedKeys = Array.from(byWorkspace.keys()).sort((a, b) => {
|
|
204
|
+
const aCount = byWorkspace.get(a).length;
|
|
205
|
+
const bCount = byWorkspace.get(b).length;
|
|
206
|
+
if (aCount !== bCount)
|
|
207
|
+
return bCount - aCount;
|
|
208
|
+
return a.localeCompare(b);
|
|
209
|
+
});
|
|
210
|
+
let first = true;
|
|
211
|
+
for (const key of sortedKeys) {
|
|
212
|
+
const group = byWorkspace.get(key);
|
|
213
|
+
if (!first)
|
|
214
|
+
console.log();
|
|
215
|
+
first = false;
|
|
216
|
+
// Print workspace header
|
|
217
|
+
const header = key === '__cloud__'
|
|
218
|
+
? chalk.magenta.bold('cloud')
|
|
219
|
+
: key === '__unknown__'
|
|
220
|
+
? chalk.gray.bold('unknown')
|
|
221
|
+
: chalk.cyan.bold(shortCwd(key));
|
|
222
|
+
console.log(`${header} ${chalk.gray(`(${group.length})`)}`);
|
|
223
|
+
// Print each session in this workspace
|
|
224
|
+
for (const s of group) {
|
|
225
|
+
const kindCol = colorAgent(s.kind)(padRight(truncate(s.kind, 8), 9));
|
|
226
|
+
const hostCol = chalk.gray(padRight(truncate(s.host ?? '-', 8), 9));
|
|
227
|
+
const statusCol = statusColor(s.status)(padRight(truncate(s.status, 7), 8));
|
|
228
|
+
const pidCol = chalk.yellow(padRight(s.pid ? String(s.pid) : '-', 7));
|
|
229
|
+
const desc = buildSessionDescription(s);
|
|
230
|
+
console.log(' ' +
|
|
231
|
+
pidCol +
|
|
232
|
+
kindCol +
|
|
233
|
+
hostCol +
|
|
234
|
+
statusCol +
|
|
235
|
+
chalk.white(truncate(desc || '-', 50)));
|
|
236
|
+
}
|
|
194
237
|
}
|
|
195
238
|
const runningCount = sessions.filter(s => s.status === 'running').length;
|
|
196
239
|
const idleCount = sessions.filter(s => s.status === 'idle').length;
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* First-run
|
|
2
|
+
* First-run setup command.
|
|
3
3
|
*
|
|
4
|
-
* Registers the `agents
|
|
4
|
+
* Registers the `agents setup` command which clones the system repo into
|
|
5
5
|
* ~/.agents-system/ and installs agent CLIs with resource syncing.
|
|
6
6
|
*/
|
|
7
7
|
import type { Command } from 'commander';
|
|
8
8
|
/** First-run setup. Clones ~/.agents-system/ from the system repo if needed. */
|
|
9
|
-
export declare function
|
|
9
|
+
export declare function runSetup(program: Command, options?: {
|
|
10
10
|
force?: boolean;
|
|
11
|
+
suppressFooter?: boolean;
|
|
11
12
|
}): Promise<void>;
|
|
12
13
|
/**
|
|
13
14
|
* Ensure the system repo exists before running a command that needs it.
|
|
14
15
|
* If ~/.agents-system/ is not a git repo AND we're in an interactive TTY,
|
|
15
|
-
* prompt the user to run
|
|
16
|
+
* prompt the user to run setup now. In non-interactive mode, print a clear
|
|
16
17
|
* error and exit.
|
|
17
18
|
*/
|
|
18
19
|
export declare function ensureInitialized(program: Command): Promise<void>;
|
|
19
|
-
/** Register the `agents
|
|
20
|
-
export declare function
|
|
20
|
+
/** Register the `agents setup` command. */
|
|
21
|
+
export declare function registerSetupCommand(program: Command): void;
|