@phnx-labs/agents-cli 1.15.0 → 1.17.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 +143 -39
- package/README.md +6 -6
- package/dist/commands/alias.js +2 -2
- package/dist/commands/browser-picker.d.ts +21 -0
- package/dist/commands/browser-picker.js +114 -0
- package/dist/commands/browser.js +793 -83
- package/dist/commands/cloud.js +8 -0
- package/dist/commands/commands.js +72 -22
- package/dist/commands/daemon.js +2 -2
- package/dist/commands/exec.js +70 -1
- package/dist/commands/hooks.js +71 -26
- package/dist/commands/mcp.js +81 -39
- package/dist/commands/plugins.js +224 -17
- package/dist/commands/prune.js +29 -1
- 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 +154 -20
- 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} +22 -21
- package/dist/commands/skills.js +60 -19
- package/dist/commands/subagents.js +41 -13
- package/dist/commands/utils.d.ts +16 -0
- package/dist/commands/utils.js +32 -0
- package/dist/commands/view.js +78 -20
- package/dist/commands/workflows.d.ts +10 -0
- package/dist/commands/workflows.js +457 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +48 -36
- 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 +32 -1
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +41 -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 +22 -6
- package/dist/lib/browser/drivers/ssh.js +9 -2
- package/dist/lib/browser/input.d.ts +1 -0
- package/dist/lib/browser/input.js +3 -0
- package/dist/lib/browser/ipc.js +158 -23
- package/dist/lib/browser/profiles.d.ts +10 -2
- package/dist/lib/browser/profiles.js +122 -37
- package/dist/lib/browser/service.d.ts +91 -13
- package/dist/lib/browser/service.js +767 -132
- package/dist/lib/browser/types.d.ts +91 -3
- package/dist/lib/browser/types.js +16 -0
- package/dist/lib/cloud/rush.d.ts +28 -1
- package/dist/lib/cloud/rush.js +69 -14
- package/dist/lib/cloud/store.js +2 -2
- package/dist/lib/commands.d.ts +1 -15
- package/dist/lib/commands.js +11 -7
- package/dist/lib/daemon.js +2 -3
- package/dist/lib/doctor-diff.js +4 -4
- package/dist/lib/events.js +2 -2
- package/dist/lib/hooks.d.ts +11 -7
- package/dist/lib/hooks.js +138 -49
- package/dist/lib/migrate.d.ts +1 -1
- package/dist/lib/migrate.js +1237 -22
- package/dist/lib/models.js +2 -2
- package/dist/lib/permissions.d.ts +8 -66
- package/dist/lib/permissions.js +18 -18
- package/dist/lib/plugins.d.ts +94 -24
- package/dist/lib/plugins.js +702 -123
- package/dist/lib/pty-server.js +9 -10
- package/dist/lib/resource-patterns.d.ts +41 -0
- package/dist/lib/resource-patterns.js +82 -0
- package/dist/lib/resources/hooks.d.ts +5 -1
- package/dist/lib/resources/hooks.js +21 -4
- package/dist/lib/resources/index.d.ts +17 -0
- package/dist/lib/resources/index.js +7 -0
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/resources/workflows.d.ts +24 -0
- package/dist/lib/resources/workflows.js +110 -0
- package/dist/lib/resources.d.ts +6 -1
- package/dist/lib/resources.js +12 -2
- package/dist/lib/rotate.js +3 -4
- 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 +18 -0
- package/dist/lib/session/db.js +109 -5
- package/dist/lib/session/discover.d.ts +6 -0
- package/dist/lib/session/discover.js +55 -29
- package/dist/lib/session/team-filter.js +2 -2
- package/dist/lib/shims.d.ts +4 -52
- package/dist/lib/shims.js +23 -15
- package/dist/lib/skills.js +6 -2
- package/dist/lib/sqlite.js +10 -4
- package/dist/lib/state.d.ts +101 -16
- package/dist/lib/state.js +179 -31
- 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 +75 -17
- package/dist/lib/types.js +3 -3
- package/dist/lib/usage.js +2 -2
- package/dist/lib/versions.d.ts +3 -0
- package/dist/lib/versions.js +158 -47
- package/dist/lib/workflows.d.ts +79 -0
- package/dist/lib/workflows.js +233 -0
- package/package.json +1 -5
- package/scripts/postinstall.js +60 -59
- package/dist/commands/fork.d.ts +0 -10
- package/dist/commands/fork.js +0 -146
package/dist/commands/plugins.js
CHANGED
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
import * as fs from 'fs';
|
|
9
9
|
import * as path from 'path';
|
|
10
10
|
import chalk from 'chalk';
|
|
11
|
+
import { input } from '@inquirer/prompts';
|
|
11
12
|
import { PLUGINS_CAPABLE_AGENTS, agentLabel } from '../lib/agents.js';
|
|
12
|
-
import { discoverPlugins, getPlugin, pluginSupportsAgent, removePluginFromVersion } from '../lib/plugins.js';
|
|
13
|
+
import { discoverPlugins, getPlugin, pluginSupportsAgent, removePluginFromVersion, isPluginSynced, installPlugin, updatePlugin, loadUserConfig, saveUserConfig, checkPluginDependencies, } from '../lib/plugins.js';
|
|
13
14
|
import { listInstalledVersions, syncResourcesToVersion, getGlobalDefault, getVersionHomePath, } from '../lib/versions.js';
|
|
14
|
-
import {
|
|
15
|
-
import { isPromptCancelled, isInteractiveTerminal, requireDestructiveArg, requireInteractiveSelection, } from './utils.js';
|
|
15
|
+
import { isPromptCancelled, isInteractiveTerminal, requireDestructiveArg, requireInteractiveSelection, promptRemovalTargets, } from './utils.js';
|
|
16
16
|
import { itemPicker } from '../lib/picker.js';
|
|
17
17
|
import { showResourceList, buildTargetsSection, } from './resource-view.js';
|
|
18
18
|
import { getPluginsDir } from '../lib/state.js';
|
|
@@ -255,7 +255,7 @@ Examples:
|
|
|
255
255
|
# Unsync but keep source directory
|
|
256
256
|
agents plugins remove rush-toolkit --keep-source
|
|
257
257
|
`)
|
|
258
|
-
.action((nameArg, options) => {
|
|
258
|
+
.action(async (nameArg, options) => {
|
|
259
259
|
if (!nameArg) {
|
|
260
260
|
requireDestructiveArg({
|
|
261
261
|
argName: 'name',
|
|
@@ -275,35 +275,242 @@ Examples:
|
|
|
275
275
|
console.log(chalk.red(`Plugin '${name}' not found`));
|
|
276
276
|
process.exit(1);
|
|
277
277
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
let totalPerms = 0;
|
|
281
|
-
let versionsTouched = 0;
|
|
278
|
+
// Build list of targets that have this plugin synced
|
|
279
|
+
const availableTargets = [];
|
|
282
280
|
for (const agentId of PLUGINS_CAPABLE_AGENTS) {
|
|
281
|
+
if (plugin && !pluginSupportsAgent(plugin, agentId))
|
|
282
|
+
continue;
|
|
283
283
|
const versions = listInstalledVersions(agentId);
|
|
284
284
|
for (const version of versions) {
|
|
285
285
|
const versionHome = getVersionHomePath(agentId, version);
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
versionsTouched += 1;
|
|
289
|
-
totalSkills += r.skills.length;
|
|
290
|
-
totalHooks += r.hooks.length;
|
|
291
|
-
totalPerms += r.permissions;
|
|
292
|
-
console.log(chalk.gray(` ${agentLabel(agentId)}@${version}: ${r.skills.length} skill(s), ${r.hooks.length} hook(s), ${r.permissions} perm(s)`));
|
|
286
|
+
if (plugin && isPluginSynced(plugin, agentId, versionHome)) {
|
|
287
|
+
availableTargets.push({ agent: agentId, version });
|
|
293
288
|
}
|
|
294
289
|
}
|
|
295
290
|
}
|
|
296
|
-
|
|
297
|
-
|
|
291
|
+
if (availableTargets.length === 0) {
|
|
292
|
+
console.log(chalk.yellow(`Plugin '${name}' not synced to any version.`));
|
|
293
|
+
if (!options.keepSource && fs.existsSync(pluginRoot)) {
|
|
294
|
+
fs.rmSync(pluginRoot, { recursive: true, force: true });
|
|
295
|
+
console.log(chalk.green(`Deleted ${formatPath(pluginRoot)}`));
|
|
296
|
+
}
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
// Show multi-select picker for targets
|
|
300
|
+
const removalTargets = availableTargets.map((t) => ({
|
|
301
|
+
agent: t.agent,
|
|
302
|
+
version: t.version,
|
|
303
|
+
label: `${agentLabel(t.agent)}@${t.version}`,
|
|
304
|
+
}));
|
|
305
|
+
const selectedTargets = await promptRemovalTargets(name, removalTargets);
|
|
306
|
+
if (selectedTargets.length === 0) {
|
|
307
|
+
console.log(chalk.gray('Cancelled.'));
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
let totalSkills = 0;
|
|
311
|
+
let totalCommands = 0;
|
|
312
|
+
let totalAgentDefs = 0;
|
|
313
|
+
let totalHooks = 0;
|
|
314
|
+
let totalPerms = 0;
|
|
315
|
+
let totalMcp = 0;
|
|
316
|
+
let versionsTouched = 0;
|
|
317
|
+
for (const target of selectedTargets) {
|
|
318
|
+
const versionHome = getVersionHomePath(target.agent, target.version);
|
|
319
|
+
const r = removePluginFromVersion(name, resolvedRoot, target.agent, versionHome);
|
|
320
|
+
const anyRemoved = r.skills.length > 0 || r.commands.length > 0 || r.agentDefs.length > 0 ||
|
|
321
|
+
r.bin.length > 0 || r.hooks.length > 0 || r.permissions > 0 || r.mcp > 0;
|
|
322
|
+
if (anyRemoved) {
|
|
323
|
+
versionsTouched += 1;
|
|
324
|
+
totalSkills += r.skills.length;
|
|
325
|
+
totalCommands += r.commands.length;
|
|
326
|
+
totalAgentDefs += r.agentDefs.length;
|
|
327
|
+
totalHooks += r.hooks.length;
|
|
328
|
+
totalPerms += r.permissions;
|
|
329
|
+
totalMcp += r.mcp;
|
|
330
|
+
const parts = [
|
|
331
|
+
r.skills.length > 0 ? `${r.skills.length} skill(s)` : null,
|
|
332
|
+
r.commands.length > 0 ? `${r.commands.length} command(s)` : null,
|
|
333
|
+
r.agentDefs.length > 0 ? `${r.agentDefs.length} agent def(s)` : null,
|
|
334
|
+
r.hooks.length > 0 ? `${r.hooks.length} hook(s)` : null,
|
|
335
|
+
r.permissions > 0 ? `${r.permissions} perm(s)` : null,
|
|
336
|
+
r.mcp > 0 ? `${r.mcp} MCP server(s)` : null,
|
|
337
|
+
].filter(Boolean);
|
|
338
|
+
console.log(` ${chalk.red('-')} ${target.label}: ${parts.join(', ')}`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const summary = [
|
|
342
|
+
totalSkills > 0 ? `${totalSkills} skills` : null,
|
|
343
|
+
totalCommands > 0 ? `${totalCommands} commands` : null,
|
|
344
|
+
totalAgentDefs > 0 ? `${totalAgentDefs} agent defs` : null,
|
|
345
|
+
totalHooks > 0 ? `${totalHooks} hooks` : null,
|
|
346
|
+
totalPerms > 0 ? `${totalPerms} permissions` : null,
|
|
347
|
+
totalMcp > 0 ? `${totalMcp} MCP servers` : null,
|
|
348
|
+
].filter(Boolean).join(', ') || 'nothing';
|
|
349
|
+
console.log(chalk.green(`\nUnsynced ${name} from ${versionsTouched} version(s) — ${summary}`));
|
|
350
|
+
// Only delete source if ALL targets were selected
|
|
351
|
+
if (!options.keepSource && selectedTargets.length === availableTargets.length) {
|
|
298
352
|
if (fs.existsSync(pluginRoot)) {
|
|
299
353
|
fs.rmSync(pluginRoot, { recursive: true, force: true });
|
|
300
354
|
console.log(chalk.green(`Deleted ${formatPath(pluginRoot)}`));
|
|
301
355
|
}
|
|
302
356
|
}
|
|
357
|
+
else if (!options.keepSource && selectedTargets.length < availableTargets.length) {
|
|
358
|
+
console.log(chalk.gray(`Source kept — plugin still synced to other versions.`));
|
|
359
|
+
}
|
|
303
360
|
else {
|
|
304
361
|
console.log(chalk.gray(`Kept source at ${formatPath(pluginRoot)}`));
|
|
305
362
|
}
|
|
306
363
|
});
|
|
364
|
+
// agents plugins install <spec>
|
|
365
|
+
pluginsCmd
|
|
366
|
+
.command('install <spec>')
|
|
367
|
+
.description('Install a plugin from a git URL or local path (format: name@source or source)')
|
|
368
|
+
.addHelpText('after', `
|
|
369
|
+
Examples:
|
|
370
|
+
# Install from a git URL
|
|
371
|
+
agents plugins install my-plugin@https://github.com/user/my-plugin.git
|
|
372
|
+
|
|
373
|
+
# Install from a local path
|
|
374
|
+
agents plugins install /path/to/plugin
|
|
375
|
+
|
|
376
|
+
# Named install from a local path
|
|
377
|
+
agents plugins install rush-toolkit@~/Projects/rush-toolkit
|
|
378
|
+
`)
|
|
379
|
+
.action(async (spec) => {
|
|
380
|
+
console.log(chalk.gray(`Installing plugin from: ${spec}`));
|
|
381
|
+
let name;
|
|
382
|
+
let root;
|
|
383
|
+
try {
|
|
384
|
+
const result = await installPlugin(spec);
|
|
385
|
+
name = result.name;
|
|
386
|
+
root = result.root;
|
|
387
|
+
}
|
|
388
|
+
catch (err) {
|
|
389
|
+
console.log(chalk.red(`Install failed: ${err.message}`));
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
const plugin = getPlugin(name);
|
|
393
|
+
if (!plugin) {
|
|
394
|
+
console.log(chalk.red(`Installed but could not load plugin '${name}'`));
|
|
395
|
+
process.exit(1);
|
|
396
|
+
}
|
|
397
|
+
// Check dependencies
|
|
398
|
+
const missingDeps = checkPluginDependencies(plugin.manifest);
|
|
399
|
+
if (missingDeps.length > 0) {
|
|
400
|
+
console.log(chalk.yellow(`Warning: missing dependencies: ${missingDeps.join(', ')}`));
|
|
401
|
+
console.log(chalk.gray('Install them with: agents plugins install <name>@<source>'));
|
|
402
|
+
}
|
|
403
|
+
// Prompt for userConfig fields
|
|
404
|
+
if (plugin.manifest.userConfig && plugin.manifest.userConfig.length > 0 && isInteractiveTerminal()) {
|
|
405
|
+
const existingConfig = loadUserConfig(name);
|
|
406
|
+
const newConfig = await promptUserConfig(plugin.manifest, existingConfig);
|
|
407
|
+
if (Object.keys(newConfig).length > 0) {
|
|
408
|
+
saveUserConfig(name, { ...existingConfig, ...newConfig });
|
|
409
|
+
console.log(chalk.gray('User config saved.'));
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
// Sync to all supported installed versions
|
|
413
|
+
console.log();
|
|
414
|
+
let synced = 0;
|
|
415
|
+
for (const agentId of PLUGINS_CAPABLE_AGENTS) {
|
|
416
|
+
if (!pluginSupportsAgent(plugin, agentId))
|
|
417
|
+
continue;
|
|
418
|
+
const versions = listInstalledVersions(agentId);
|
|
419
|
+
if (versions.length === 0)
|
|
420
|
+
continue;
|
|
421
|
+
const defaultVer = getGlobalDefault(agentId);
|
|
422
|
+
const targetVersions = defaultVer ? [defaultVer] : [versions[versions.length - 1]];
|
|
423
|
+
for (const version of targetVersions) {
|
|
424
|
+
const syncResult = syncResourcesToVersion(agentId, version, { plugins: [name] });
|
|
425
|
+
if (syncResult.plugins.length > 0) {
|
|
426
|
+
console.log(chalk.green(` Synced to ${agentLabel(agentId)}@${version}`));
|
|
427
|
+
synced++;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (synced === 0) {
|
|
432
|
+
console.log(chalk.gray(' No supported agent versions installed — run "agents use <agent>@<version>" to sync.'));
|
|
433
|
+
}
|
|
434
|
+
console.log(chalk.bold(`\nInstalled ${plugin.name} v${plugin.manifest.version} to ${formatPath(root)}`));
|
|
435
|
+
});
|
|
436
|
+
// agents plugins update [name]
|
|
437
|
+
pluginsCmd
|
|
438
|
+
.command('update [name]')
|
|
439
|
+
.description('Re-pull a plugin from its original source and re-sync to all versions')
|
|
440
|
+
.addHelpText('after', `
|
|
441
|
+
Examples:
|
|
442
|
+
# Update a specific plugin
|
|
443
|
+
agents plugins update rush-toolkit
|
|
444
|
+
|
|
445
|
+
# Update all plugins
|
|
446
|
+
agents plugins update
|
|
447
|
+
`)
|
|
448
|
+
.action(async (nameArg) => {
|
|
449
|
+
const plugins = nameArg ? [getPlugin(nameArg)].filter(Boolean) : discoverPlugins();
|
|
450
|
+
if (nameArg && plugins.length === 0) {
|
|
451
|
+
console.log(chalk.red(`Plugin '${nameArg}' not found`));
|
|
452
|
+
process.exit(1);
|
|
453
|
+
}
|
|
454
|
+
if (plugins.length === 0) {
|
|
455
|
+
console.log(chalk.gray('No plugins installed.'));
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
for (const plugin of plugins) {
|
|
459
|
+
process.stdout.write(`Updating ${plugin.name}... `);
|
|
460
|
+
const result = await updatePlugin(plugin.name);
|
|
461
|
+
if (!result.success) {
|
|
462
|
+
console.log(chalk.red(`failed — ${result.error || 'unknown error'}`));
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
console.log(chalk.green('done'));
|
|
466
|
+
// Re-sync to all supported installed versions
|
|
467
|
+
for (const agentId of PLUGINS_CAPABLE_AGENTS) {
|
|
468
|
+
if (!pluginSupportsAgent(plugin, agentId))
|
|
469
|
+
continue;
|
|
470
|
+
const versions = listInstalledVersions(agentId);
|
|
471
|
+
const defaultVer = getGlobalDefault(agentId);
|
|
472
|
+
const targetVersions = defaultVer ? [defaultVer] : versions.slice(-1);
|
|
473
|
+
for (const version of targetVersions) {
|
|
474
|
+
const syncResult = syncResourcesToVersion(agentId, version, { plugins: [plugin.name] });
|
|
475
|
+
if (syncResult.plugins.length > 0) {
|
|
476
|
+
console.log(chalk.gray(` Re-synced to ${agentLabel(agentId)}@${version}`));
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Prompt for missing or empty userConfig fields interactively.
|
|
485
|
+
* Only prompts for fields not already present in existingConfig.
|
|
486
|
+
*/
|
|
487
|
+
async function promptUserConfig(manifest, existingConfig = {}) {
|
|
488
|
+
const result = {};
|
|
489
|
+
const fields = manifest.userConfig || [];
|
|
490
|
+
for (const field of fields) {
|
|
491
|
+
if (existingConfig[field.key] !== undefined)
|
|
492
|
+
continue;
|
|
493
|
+
const defaultValue = field.default ?? '';
|
|
494
|
+
try {
|
|
495
|
+
const value = await input({
|
|
496
|
+
message: field.description + (field.required ? ' (required)' : ' (optional)'),
|
|
497
|
+
default: defaultValue || undefined,
|
|
498
|
+
required: field.required ?? false,
|
|
499
|
+
});
|
|
500
|
+
if (value) {
|
|
501
|
+
result[field.key] = value;
|
|
502
|
+
}
|
|
503
|
+
else if (defaultValue) {
|
|
504
|
+
result[field.key] = defaultValue;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
catch (err) {
|
|
508
|
+
if (isPromptCancelled(err))
|
|
509
|
+
break;
|
|
510
|
+
throw err;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return result;
|
|
307
514
|
}
|
|
308
515
|
/** Convert discovered plugins into rows suitable for the resource list view. */
|
|
309
516
|
function buildPluginRows(plugins) {
|
package/dist/commands/prune.js
CHANGED
|
@@ -27,6 +27,8 @@ import { confirm } from '@inquirer/prompts';
|
|
|
27
27
|
import { diffVersionCommands, iterCommandsCapableVersions, removeCommandFromVersion, } from '../lib/commands.js';
|
|
28
28
|
import { diffVersionSkills, iterSkillsCapableVersions, removeSkillFromVersion, } from '../lib/skills.js';
|
|
29
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';
|
|
30
32
|
import { getGlobalDefault } from '../lib/versions.js';
|
|
31
33
|
import { resolveAgentName, formatAgentError } from '../lib/agents.js';
|
|
32
34
|
import { pruneDuplicates } from './view.js';
|
|
@@ -34,7 +36,7 @@ import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
|
34
36
|
import { getTrashDir } from '../lib/state.js';
|
|
35
37
|
import { countSessionsOlderThan, deleteSessionsOlderThan } from '../lib/session/db.js';
|
|
36
38
|
import { previewRunsPrune, pruneRuns, countAllRuns } from '../lib/routines.js';
|
|
37
|
-
const RESOURCE_TYPES = ['commands', 'skills', 'hooks'];
|
|
39
|
+
const RESOURCE_TYPES = ['commands', 'skills', 'hooks', 'plugins', 'subagents'];
|
|
38
40
|
const STATE_TYPES = ['trash', 'sessions', 'runs'];
|
|
39
41
|
const ALL_TYPES = [...RESOURCE_TYPES, 'versions', ...STATE_TYPES];
|
|
40
42
|
function scopePairs(pairs, all) {
|
|
@@ -68,6 +70,22 @@ function collectOrphans(types, all) {
|
|
|
68
70
|
}
|
|
69
71
|
}
|
|
70
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
|
+
}
|
|
71
89
|
return groups;
|
|
72
90
|
}
|
|
73
91
|
function removeOne(group, name) {
|
|
@@ -78,6 +96,10 @@ function removeOne(group, name) {
|
|
|
78
96
|
return removeSkillFromVersion(group.agent, group.version, name);
|
|
79
97
|
case 'hooks':
|
|
80
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);
|
|
81
103
|
}
|
|
82
104
|
}
|
|
83
105
|
function parseTarget(arg) {
|
|
@@ -382,12 +404,18 @@ Examples:
|
|
|
382
404
|
# Full sweep: orphan resources + duplicate versions for current defaults
|
|
383
405
|
agents prune
|
|
384
406
|
|
|
407
|
+
# Preview what a full sweep would remove
|
|
408
|
+
agents prune --dry-run
|
|
409
|
+
|
|
385
410
|
# Just orphan skills
|
|
386
411
|
agents prune skills
|
|
387
412
|
|
|
388
413
|
# Just version dedup
|
|
389
414
|
agents prune versions
|
|
390
415
|
|
|
416
|
+
# Deduplicate versions for one agent only
|
|
417
|
+
agents prune claude
|
|
418
|
+
|
|
391
419
|
# Sweep every installed version's orphans, not only the defaults
|
|
392
420
|
agents prune --all
|
|
393
421
|
|
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
|
@@ -9,6 +9,7 @@ import chalk from 'chalk';
|
|
|
9
9
|
import * as fs from 'fs';
|
|
10
10
|
import { bundleExists, deleteBundle, describeBundle, keychainItemsForBundle, keychainRef, listBundles, migrateLegacyBundles, parseDotenv, readBundle, rotateBundleSecret, validateBundleName, validateEnvKey, validateExpiresFutureDated, validateSecretType, writeBundle, } from '../lib/secrets/bundles.js';
|
|
11
11
|
import { deleteKeychainToken, getKeychainToken, hasKeychainToken, secretsKeychainItem, setKeychainToken, } from '../lib/secrets/index.js';
|
|
12
|
+
import { assertOpAvailable, createPasswordItem, deleteItemByTitle, extractSecrets, itemExistsByTitle, listItems, listVaults, } from '../lib/onepassword.js';
|
|
12
13
|
import { registerCommandGroups } from '../lib/help.js';
|
|
13
14
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
14
15
|
/** Prompt the user for a secret value with masked input. Requires an interactive TTY. */
|
|
@@ -91,6 +92,24 @@ async function promptKeyName(bundleName) {
|
|
|
91
92
|
},
|
|
92
93
|
});
|
|
93
94
|
}
|
|
95
|
+
/** Resolve a 1Password vault name — use the provided value, or prompt interactively. */
|
|
96
|
+
async function resolveVault(vaultOpt) {
|
|
97
|
+
if (vaultOpt)
|
|
98
|
+
return vaultOpt;
|
|
99
|
+
const vaults = listVaults();
|
|
100
|
+
if (vaults.length === 0)
|
|
101
|
+
throw new Error('No 1Password vaults found. Make sure you are signed in: op signin');
|
|
102
|
+
if (vaults.length === 1)
|
|
103
|
+
return vaults[0].name;
|
|
104
|
+
if (!isInteractiveTerminal()) {
|
|
105
|
+
throw new Error(`Multiple vaults found. Pass --vault <name> (available: ${vaults.map((v) => v.name).join(', ')})`);
|
|
106
|
+
}
|
|
107
|
+
const { select } = await import('@inquirer/prompts');
|
|
108
|
+
return await select({
|
|
109
|
+
message: 'Which 1Password vault?',
|
|
110
|
+
choices: vaults.map((v) => ({ name: v.name, value: v.name })),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
94
113
|
/** Read all available data from stdin synchronously, trimmed. */
|
|
95
114
|
function readStdinSync() {
|
|
96
115
|
const chunks = [];
|
|
@@ -268,6 +287,13 @@ Examples:
|
|
|
268
287
|
# Import an entire .env file straight into keychain
|
|
269
288
|
agents secrets import prod --from .env.prod
|
|
270
289
|
|
|
290
|
+
# Import secrets from a 1Password vault
|
|
291
|
+
agents secrets import prod --from-1password --vault "Rush Prod"
|
|
292
|
+
|
|
293
|
+
# Push a bundle back to 1Password (vault migration, backup)
|
|
294
|
+
agents secrets export prod --to-1password --vault "Rush Prod"
|
|
295
|
+
agents secrets export prod --to-1password --vault "Rush Prod" --force
|
|
296
|
+
|
|
271
297
|
# See what's in a bundle (values masked)
|
|
272
298
|
agents secrets view prod
|
|
273
299
|
|
|
@@ -280,6 +306,10 @@ Examples:
|
|
|
280
306
|
# Eval the bundle into your current shell
|
|
281
307
|
eval "$(agents secrets export prod --plaintext)"
|
|
282
308
|
|
|
309
|
+
# Run a command with secrets injected
|
|
310
|
+
agents secrets exec prod -- ./deploy.sh
|
|
311
|
+
agents secrets exec hetzner.com -- crabbox list
|
|
312
|
+
|
|
283
313
|
# Remove one key (purges the keychain item by default)
|
|
284
314
|
agents secrets remove prod STRIPE_API_KEY
|
|
285
315
|
|
|
@@ -297,7 +327,7 @@ Examples:
|
|
|
297
327
|
registerCommandGroups(cmd, [
|
|
298
328
|
{ title: 'Bundle commands', names: ['list', 'view', 'create', 'delete'] },
|
|
299
329
|
{ title: 'Secret commands', names: ['add', 'rotate', 'remove', 'import', 'export'] },
|
|
300
|
-
{ title: 'Utilities', names: ['generate'] },
|
|
330
|
+
{ title: 'Utilities', names: ['exec', 'generate'] },
|
|
301
331
|
]);
|
|
302
332
|
cmd
|
|
303
333
|
.command('list')
|
|
@@ -645,35 +675,71 @@ Examples:
|
|
|
645
675
|
});
|
|
646
676
|
cmd
|
|
647
677
|
.command('import [bundle]')
|
|
648
|
-
.description('Import keys from a .env file into a bundle. By default every key is stored in keychain.')
|
|
649
|
-
.
|
|
678
|
+
.description('Import keys from a .env file or a 1Password vault into a bundle. By default every key is stored in keychain.')
|
|
679
|
+
.option('--from <path>', 'Path to a .env file')
|
|
680
|
+
.option('--from-1password', 'Import secrets from a 1Password vault (requires the op CLI)')
|
|
681
|
+
.option('--vault <name>', '1Password vault name (used with --from-1password)')
|
|
650
682
|
.option('--all-plaintext', 'Store every imported value as a literal in the bundle metadata (skip keychain item creation)')
|
|
651
683
|
.option('--force', 'Overwrite an existing key in the bundle')
|
|
652
684
|
.action(async (bundleName, opts) => {
|
|
653
685
|
try {
|
|
686
|
+
if (!opts.from && !opts.from1password) {
|
|
687
|
+
throw new Error('Pass --from <path> to import a .env file, or --from-1password to import from a 1Password vault.');
|
|
688
|
+
}
|
|
689
|
+
if (opts.from && opts.from1password) {
|
|
690
|
+
throw new Error('--from and --from-1password are mutually exclusive.');
|
|
691
|
+
}
|
|
654
692
|
const resolvedBundleName = bundleName ?? (await pickBundleName('import into'));
|
|
655
693
|
const bundle = readBundle(resolvedBundleName);
|
|
656
|
-
const raw = fs.readFileSync(opts.from, 'utf-8');
|
|
657
|
-
const pairs = parseDotenv(raw);
|
|
658
694
|
let added = 0;
|
|
659
695
|
let skipped = 0;
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
696
|
+
if (opts.from1password) {
|
|
697
|
+
assertOpAvailable();
|
|
698
|
+
const vault = await resolveVault(opts.vault);
|
|
699
|
+
const items = listItems(vault);
|
|
700
|
+
const { secrets, skipped: opSkipped } = extractSecrets(items, vault);
|
|
701
|
+
for (const { envKey, value } of secrets) {
|
|
702
|
+
if (!opts.force && envKey in bundle.vars) {
|
|
703
|
+
skipped++;
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
if (opts.allPlaintext) {
|
|
707
|
+
bundle.vars[envKey] = { value };
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
const item = secretsKeychainItem(resolvedBundleName, envKey);
|
|
711
|
+
setKeychainToken(item, value, bundle.icloud_sync);
|
|
712
|
+
bundle.vars[envKey] = keychainRef(envKey);
|
|
713
|
+
}
|
|
714
|
+
added++;
|
|
664
715
|
}
|
|
665
|
-
|
|
666
|
-
|
|
716
|
+
writeBundle(bundle);
|
|
717
|
+
if (opSkipped.length) {
|
|
718
|
+
console.log(chalk.yellow(`Skipped ${opSkipped.length} item(s) with no importable fields.`));
|
|
667
719
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
720
|
+
console.log(chalk.green(`Imported ${added} key(s) from 1Password vault '${vault}'${skipped ? `, skipped ${skipped} (already set, pass --force)` : ''}.`));
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
const raw = fs.readFileSync(opts.from, 'utf-8');
|
|
724
|
+
const pairs = parseDotenv(raw);
|
|
725
|
+
for (const [key, value] of Object.entries(pairs)) {
|
|
726
|
+
if (!opts.force && key in bundle.vars) {
|
|
727
|
+
skipped++;
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
if (opts.allPlaintext) {
|
|
731
|
+
bundle.vars[key] = { value };
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
const item = secretsKeychainItem(resolvedBundleName, key);
|
|
735
|
+
setKeychainToken(item, value, bundle.icloud_sync);
|
|
736
|
+
bundle.vars[key] = keychainRef(key);
|
|
737
|
+
}
|
|
738
|
+
added++;
|
|
672
739
|
}
|
|
673
|
-
|
|
740
|
+
writeBundle(bundle);
|
|
741
|
+
console.log(chalk.green(`Imported ${added} key(s)${skipped ? `, skipped ${skipped} (already set, pass --force)` : ''}.`));
|
|
674
742
|
}
|
|
675
|
-
writeBundle(bundle);
|
|
676
|
-
console.log(chalk.green(`Imported ${added} key(s)${skipped ? `, skipped ${skipped} (already set, pass --force)` : ''}.`));
|
|
677
743
|
}
|
|
678
744
|
catch (err) {
|
|
679
745
|
if (isPromptCancelled(err))
|
|
@@ -684,13 +750,49 @@ Examples:
|
|
|
684
750
|
});
|
|
685
751
|
cmd
|
|
686
752
|
.command('export [bundle]')
|
|
687
|
-
.description('Resolve a bundle and print KEY=VALUE lines
|
|
688
|
-
.option('--plaintext', 'Acknowledge that the resolved values will be printed in the clear')
|
|
753
|
+
.description('Resolve a bundle and print KEY=VALUE lines, or push it to a 1Password vault with --to-1password.')
|
|
754
|
+
.option('--plaintext', 'Acknowledge that the resolved values will be printed in the clear (shell export mode)')
|
|
755
|
+
.option('--to-1password', 'Push every key in the bundle as a PASSWORD item in a 1Password vault')
|
|
756
|
+
.option('--vault <name>', '1Password vault name (used with --to-1password)')
|
|
757
|
+
.option('--force', 'Overwrite existing 1Password items (used with --to-1password)')
|
|
689
758
|
.action(async (bundleName, opts) => {
|
|
690
759
|
try {
|
|
691
760
|
const { resolveBundleEnv, bundleToEnvPrefix, isReservedEnvName } = await import('../lib/secrets/bundles.js');
|
|
692
761
|
const resolvedBundleName = bundleName ?? (await pickBundleName('export'));
|
|
693
762
|
const bundle = readBundle(resolvedBundleName);
|
|
763
|
+
if (opts.to1password) {
|
|
764
|
+
assertOpAvailable();
|
|
765
|
+
const vault = await resolveVault(opts.vault);
|
|
766
|
+
const env = resolveBundleEnv(bundle);
|
|
767
|
+
let created = 0;
|
|
768
|
+
let overwritten = 0;
|
|
769
|
+
let skipped = 0;
|
|
770
|
+
for (const [key, value] of Object.entries(env)) {
|
|
771
|
+
const exists = itemExistsByTitle(key, vault);
|
|
772
|
+
if (exists) {
|
|
773
|
+
if (!opts.force) {
|
|
774
|
+
skipped++;
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
deleteItemByTitle(key, vault);
|
|
778
|
+
createPasswordItem(key, value, vault);
|
|
779
|
+
overwritten++;
|
|
780
|
+
}
|
|
781
|
+
else {
|
|
782
|
+
createPasswordItem(key, value, vault);
|
|
783
|
+
created++;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
const parts = [];
|
|
787
|
+
if (created)
|
|
788
|
+
parts.push(`${created} created`);
|
|
789
|
+
if (overwritten)
|
|
790
|
+
parts.push(`${overwritten} overwritten`);
|
|
791
|
+
if (skipped)
|
|
792
|
+
parts.push(`${skipped} skipped (already exist, pass --force)`);
|
|
793
|
+
console.log(chalk.green(`Exported to 1Password vault '${vault}': ${parts.join(', ')}.`));
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
694
796
|
if (isInteractiveTerminal() && !opts.plaintext) {
|
|
695
797
|
console.error(chalk.red('export to a TTY requires --plaintext (prevents shoulder-surfing).'));
|
|
696
798
|
process.exit(1);
|
|
@@ -711,6 +813,38 @@ Examples:
|
|
|
711
813
|
process.exit(1);
|
|
712
814
|
}
|
|
713
815
|
});
|
|
816
|
+
cmd
|
|
817
|
+
.command('exec <bundle> [command...]')
|
|
818
|
+
.description('Run a command with the bundle\'s secrets injected into the environment')
|
|
819
|
+
.allowUnknownOption()
|
|
820
|
+
.action(async (bundleName, commandParts) => {
|
|
821
|
+
try {
|
|
822
|
+
if (commandParts.length === 0) {
|
|
823
|
+
console.error(chalk.red('Usage: agents secrets exec <bundle> -- <command...>'));
|
|
824
|
+
process.exit(1);
|
|
825
|
+
}
|
|
826
|
+
const { resolveBundleEnv } = await import('../lib/secrets/bundles.js');
|
|
827
|
+
const bundle = readBundle(bundleName);
|
|
828
|
+
const secretEnv = resolveBundleEnv(bundle);
|
|
829
|
+
const { spawn } = await import('child_process');
|
|
830
|
+
const [cmd, ...args] = commandParts;
|
|
831
|
+
const proc = spawn(cmd, args, {
|
|
832
|
+
stdio: 'inherit',
|
|
833
|
+
env: { ...process.env, ...secretEnv },
|
|
834
|
+
});
|
|
835
|
+
proc.on('close', (code) => process.exit(code ?? 0));
|
|
836
|
+
proc.on('error', (err) => {
|
|
837
|
+
console.error(chalk.red(`Failed to run '${cmd}': ${err.message}`));
|
|
838
|
+
process.exit(1);
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
catch (err) {
|
|
842
|
+
if (isPromptCancelled(err))
|
|
843
|
+
return;
|
|
844
|
+
console.error(chalk.red(err.message));
|
|
845
|
+
process.exit(1);
|
|
846
|
+
}
|
|
847
|
+
});
|
|
714
848
|
cmd
|
|
715
849
|
.command('generate [length]')
|
|
716
850
|
.description('Generate a random password')
|