@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/view.js
CHANGED
|
@@ -2,19 +2,24 @@ import chalk from 'chalk';
|
|
|
2
2
|
import ora from 'ora';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import * as path from 'path';
|
|
5
|
-
import * as yaml from 'yaml';
|
|
6
5
|
import { AGENTS, ALL_AGENT_IDS, getAllCliStates, getAccountInfo, resolveAgentName, formatAgentError, agentLabel, colorAgent, } from '../lib/agents.js';
|
|
7
6
|
import { formatUsageSection, formatUsageSummary, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, } from '../lib/usage.js';
|
|
8
7
|
import { readManifest } from '../lib/manifest.js';
|
|
9
8
|
import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, resolveVersionAlias, getAvailableResources, getActuallySyncedResources, getNewResources, hasNewResources, promptNewResourceSelection, syncResourcesToVersion, removeVersion, } from '../lib/versions.js';
|
|
10
9
|
import { getShimsDir, isShimsInPath, ensureVersionedAliasCurrent, removeShim, } from '../lib/shims.js';
|
|
11
10
|
import { getAgentResources } from '../lib/resources.js';
|
|
12
|
-
import {
|
|
11
|
+
import { WORKFLOW_CAPABLE_AGENTS } from '../lib/workflows.js';
|
|
12
|
+
import { getAgentsDir, getUserAgentsDir, getEffectivePromptcutsPath, readMergedPromptcuts } from '../lib/state.js';
|
|
13
13
|
import { isGitRepo, getGitSyncStatus } from '../lib/git.js';
|
|
14
14
|
import { getCentralRulesFileName } from '../lib/rules/rules.js';
|
|
15
|
+
import { composeRulesFromState } from '../lib/rules/compose.js';
|
|
15
16
|
import { getConfiguredRunStrategy } from '../lib/rotate.js';
|
|
16
17
|
import { confirm } from '@inquirer/prompts';
|
|
17
18
|
import { formatPath, isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
19
|
+
function termLink(text, filePath) {
|
|
20
|
+
const url = `file://${filePath}`;
|
|
21
|
+
return `\x1b]8;;${url}\x1b\\${text}\x1b]8;;\x1b\\`;
|
|
22
|
+
}
|
|
18
23
|
function formatLastActive(date) {
|
|
19
24
|
if (!date)
|
|
20
25
|
return '';
|
|
@@ -356,6 +361,8 @@ async function showInstalledVersions(filterAgentId) {
|
|
|
356
361
|
synced.push('mcp');
|
|
357
362
|
if (result.plugins.length > 0)
|
|
358
363
|
synced.push('plugins');
|
|
364
|
+
if (result.workflows.length > 0)
|
|
365
|
+
synced.push('workflows');
|
|
359
366
|
if (synced.length > 0) {
|
|
360
367
|
console.log(chalk.green(`\nSynced to ${agentLabel(filterAgentId)}@${defaultVersion}: ${synced.join(', ')}`));
|
|
361
368
|
}
|
|
@@ -478,6 +485,7 @@ async function showAgentResources(agentId, requestedVersion) {
|
|
|
478
485
|
...r,
|
|
479
486
|
syncState: r.scope === 'project' ? undefined : getSyncState(r.name, 'hooks', hooksSync),
|
|
480
487
|
})),
|
|
488
|
+
workflows: resources.workflows.map(r => ({ name: r.name, path: r.path, scope: r.scope })),
|
|
481
489
|
};
|
|
482
490
|
spinner.stop();
|
|
483
491
|
// Render helper for resources
|
|
@@ -488,7 +496,8 @@ async function showAgentResources(agentId, requestedVersion) {
|
|
|
488
496
|
return;
|
|
489
497
|
}
|
|
490
498
|
const versionStr = agentData.version ? ` (${agentData.version})` : '';
|
|
491
|
-
|
|
499
|
+
const agentHeader = home ? termLink(agentData.agentName, home) : agentData.agentName;
|
|
500
|
+
console.log(` ${chalk.bold(agentHeader)}${chalk.gray(versionStr)}:`);
|
|
492
501
|
for (const r of items) {
|
|
493
502
|
let nameColor = chalk.cyan;
|
|
494
503
|
if (r.syncState === 'synced')
|
|
@@ -499,7 +508,8 @@ async function showAgentResources(agentId, requestedVersion) {
|
|
|
499
508
|
nameColor = chalk.yellow;
|
|
500
509
|
else if (r.syncState === 'deleted')
|
|
501
510
|
nameColor = chalk.red;
|
|
502
|
-
|
|
511
|
+
const linkedName = r.path ? termLink(r.name, r.path) : r.name;
|
|
512
|
+
let display = nameColor(linkedName);
|
|
503
513
|
if (r.ruleCount !== undefined)
|
|
504
514
|
display += chalk.gray(` (${r.ruleCount} rules)`);
|
|
505
515
|
// Source annotation: project overrides user, user overrides system
|
|
@@ -507,30 +517,23 @@ async function showAgentResources(agentId, requestedVersion) {
|
|
|
507
517
|
: r.scope === 'user' ? chalk.cyan('[user]')
|
|
508
518
|
: chalk.gray('[system]');
|
|
509
519
|
display += ` ${sourceTag}`;
|
|
510
|
-
const pathStr = r.path ? chalk.gray(formatPath(r.path, cwd)) : '';
|
|
511
520
|
const syncStr = r.syncState ? chalk.gray(` [${r.syncState}]`) : '';
|
|
512
|
-
console.log(` ${display
|
|
521
|
+
console.log(` ${display}${syncStr}`);
|
|
513
522
|
}
|
|
514
523
|
}
|
|
515
|
-
// Render
|
|
516
|
-
//
|
|
524
|
+
// Render promptcuts (cross-agent, not per-version). Shortcuts are layered
|
|
525
|
+
// across system + user files with user precedence; the displayed file path
|
|
526
|
+
// is whichever is "live" — user if it exists, else system.
|
|
517
527
|
function renderPromptcuts() {
|
|
518
528
|
console.log(chalk.bold(`\nPromptcuts\n`));
|
|
519
|
-
const
|
|
520
|
-
|
|
529
|
+
const merged = readMergedPromptcuts();
|
|
530
|
+
const count = Object.keys(merged).length;
|
|
531
|
+
if (count === 0) {
|
|
521
532
|
console.log(` ${chalk.gray('none')}`);
|
|
522
533
|
return;
|
|
523
534
|
}
|
|
524
|
-
let count = 0;
|
|
525
|
-
try {
|
|
526
|
-
const parsed = yaml.parse(fs.readFileSync(promptcutsPath, 'utf-8'));
|
|
527
|
-
count = parsed?.shortcuts ? Object.keys(parsed.shortcuts).length : 0;
|
|
528
|
-
}
|
|
529
|
-
catch {
|
|
530
|
-
count = 0;
|
|
531
|
-
}
|
|
532
535
|
const label = `${count} shortcut${count === 1 ? '' : 's'}`;
|
|
533
|
-
console.log(` ${chalk.green(label).padEnd(24)} ${chalk.gray(formatPath(
|
|
536
|
+
console.log(` ${chalk.green(label).padEnd(24)} ${chalk.gray(formatPath(getEffectivePromptcutsPath(), cwd))}`);
|
|
534
537
|
}
|
|
535
538
|
// 1. Agent CLI info
|
|
536
539
|
console.log(chalk.bold('Agent CLIs\n'));
|
|
@@ -565,7 +568,62 @@ async function showAgentResources(agentId, requestedVersion) {
|
|
|
565
568
|
}
|
|
566
569
|
}
|
|
567
570
|
renderSection('MCP Servers', agentData.mcp);
|
|
568
|
-
|
|
571
|
+
if (WORKFLOW_CAPABLE_AGENTS.includes(agentId)) {
|
|
572
|
+
renderSection('Workflows', agentData.workflows);
|
|
573
|
+
}
|
|
574
|
+
// Rules section with subrules breakdown
|
|
575
|
+
function renderRulesSection() {
|
|
576
|
+
console.log(chalk.bold('\nRules\n'));
|
|
577
|
+
const items = agentData.memory;
|
|
578
|
+
if (items.length === 0) {
|
|
579
|
+
console.log(` ${chalk.gray('none')}`);
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
const versionStr = agentData.version ? ` (${agentData.version})` : '';
|
|
583
|
+
console.log(` ${chalk.bold(agentData.agentName)}${chalk.gray(versionStr)}:`);
|
|
584
|
+
// Get composed subrules for the user scope
|
|
585
|
+
let composedSubrules = [];
|
|
586
|
+
try {
|
|
587
|
+
const composed = composeRulesFromState({ cwd });
|
|
588
|
+
composedSubrules = composed.subrules;
|
|
589
|
+
}
|
|
590
|
+
catch {
|
|
591
|
+
// No preset configured or rules.yaml missing — show rules without subrule breakdown
|
|
592
|
+
}
|
|
593
|
+
for (const r of items) {
|
|
594
|
+
let nameColor = chalk.cyan;
|
|
595
|
+
if (r.syncState === 'synced')
|
|
596
|
+
nameColor = chalk.green;
|
|
597
|
+
else if (r.syncState === 'new')
|
|
598
|
+
nameColor = chalk.blue;
|
|
599
|
+
else if (r.syncState === 'modified')
|
|
600
|
+
nameColor = chalk.yellow;
|
|
601
|
+
else if (r.syncState === 'deleted')
|
|
602
|
+
nameColor = chalk.red;
|
|
603
|
+
const linkedName = r.path ? termLink(r.name, r.path) : r.name;
|
|
604
|
+
let display = nameColor(linkedName);
|
|
605
|
+
if (r.ruleCount !== undefined)
|
|
606
|
+
display += chalk.gray(` (${r.ruleCount} rules)`);
|
|
607
|
+
const sourceTag = r.scope === 'project' ? chalk.blue('[project]')
|
|
608
|
+
: r.scope === 'user' ? chalk.cyan('[user]')
|
|
609
|
+
: chalk.gray('[system]');
|
|
610
|
+
display += ` ${sourceTag}`;
|
|
611
|
+
const syncStr = r.syncState ? chalk.gray(` [${r.syncState}]`) : '';
|
|
612
|
+
console.log(` ${display}${syncStr}`);
|
|
613
|
+
// Show subrules for user-scope rules (the compiled CLAUDE.md)
|
|
614
|
+
if (r.scope === 'user' && composedSubrules.length > 0) {
|
|
615
|
+
for (const sub of composedSubrules) {
|
|
616
|
+
const scopeLabel = sub.layerScope === 'project' ? chalk.blue('[project]')
|
|
617
|
+
: sub.layerScope === 'user' ? chalk.cyan('[user]')
|
|
618
|
+
: sub.layerScope === 'extra' ? chalk.magenta(`[${sub.layerAlias || 'extra'}]`)
|
|
619
|
+
: chalk.gray('[system]');
|
|
620
|
+
const linkedSubName = termLink(sub.name, sub.sourcePath);
|
|
621
|
+
console.log(` ${chalk.gray('-')} ${linkedSubName} ${scopeLabel}`);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
renderRulesSection();
|
|
569
627
|
renderSection('Hooks', agentData.hooks);
|
|
570
628
|
renderPromptcuts();
|
|
571
629
|
// Show legend at the end if git repo exists
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow management commands.
|
|
3
|
+
*
|
|
4
|
+
* Implements `agents workflows` — list, view, add, remove pipeline workflows
|
|
5
|
+
* (WORKFLOW.md bundles with optional subagents/, skills/, plugins/ subdirs).
|
|
6
|
+
* Run a workflow with: agents run <workflow-name>
|
|
7
|
+
*/
|
|
8
|
+
import type { Command } from 'commander';
|
|
9
|
+
/** Register the `agents workflows` command tree (list, view, add, remove). */
|
|
10
|
+
export declare function registerWorkflowsCommands(program: Command): void;
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { select, checkbox } from '@inquirer/prompts';
|
|
7
|
+
import { resolveAgentName, agentLabel } from '../lib/agents.js';
|
|
8
|
+
import { cloneRepo } from '../lib/git.js';
|
|
9
|
+
import { WORKFLOW_CAPABLE_AGENTS, discoverWorkflowsFromRepo, installWorkflowCentrally, removeWorkflow, listInstalledWorkflows, listWorkflowsForAgent, removeWorkflowFromVersion, iterWorkflowsCapableVersions, } from '../lib/workflows.js';
|
|
10
|
+
import { getVersionHomePath, getGlobalDefault, resolveVersionAlias, syncResourcesToVersion, promptAgentVersionSelection, resolveAgentVersionTargets, } from '../lib/versions.js';
|
|
11
|
+
import { recordVersionResources, getUserWorkflowsDir } from '../lib/state.js';
|
|
12
|
+
import { isPromptCancelled, isInteractiveTerminal, requireInteractiveSelection, printWithPager, promptRemovalTargets, } from './utils.js';
|
|
13
|
+
import { showResourceList, buildTargetsSection, } from './resource-view.js';
|
|
14
|
+
/** Register the `agents workflows` command tree (list, view, add, remove). */
|
|
15
|
+
export function registerWorkflowsCommands(program) {
|
|
16
|
+
const workflowsCmd = program
|
|
17
|
+
.command('workflows')
|
|
18
|
+
.description('Manage multi-agent pipeline workflows (WORKFLOW.md bundles)')
|
|
19
|
+
.addHelpText('after', `
|
|
20
|
+
Workflows are directory bundles (WORKFLOW.md + subagents/) that define multi-agent pipelines run via:
|
|
21
|
+
agents run <workflow-name>
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
# See what workflows are available
|
|
25
|
+
agents workflows list
|
|
26
|
+
|
|
27
|
+
# Install from GitHub
|
|
28
|
+
agents workflows add gh:user/workflows
|
|
29
|
+
|
|
30
|
+
# Install a local workflow directory
|
|
31
|
+
agents workflows add ./rdev
|
|
32
|
+
|
|
33
|
+
# Remove a workflow
|
|
34
|
+
agents workflows remove rdev
|
|
35
|
+
`);
|
|
36
|
+
workflowsCmd
|
|
37
|
+
.command('list [agent]')
|
|
38
|
+
.description('Show installed workflows and which agent versions they are synced to')
|
|
39
|
+
.option('-a, --agent <agent>', 'Filter to a specific agent')
|
|
40
|
+
.action(async (agentArg, options) => {
|
|
41
|
+
const spinner = ora({ text: 'Loading...', isSilent: !process.stdout.isTTY }).start();
|
|
42
|
+
const agentInput = agentArg || options.agent;
|
|
43
|
+
let filterAgent;
|
|
44
|
+
let filterVersion;
|
|
45
|
+
if (agentInput) {
|
|
46
|
+
const parts = agentInput.split('@');
|
|
47
|
+
const resolved = resolveAgentName(parts[0]);
|
|
48
|
+
if (!resolved) {
|
|
49
|
+
spinner.stop();
|
|
50
|
+
console.log(chalk.red(`Unknown agent: ${parts[0]}`));
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
filterAgent = resolved;
|
|
54
|
+
filterVersion = parts[1] ? resolveVersionAlias(resolved, parts[1]) : undefined;
|
|
55
|
+
}
|
|
56
|
+
const rows = buildWorkflowRows({ filterAgent, filterVersion });
|
|
57
|
+
spinner.stop();
|
|
58
|
+
await showResourceList({
|
|
59
|
+
resourcePlural: 'workflows',
|
|
60
|
+
resourceSingular: 'workflow',
|
|
61
|
+
extraLabel: 'Agents',
|
|
62
|
+
rows,
|
|
63
|
+
emptyMessage: filterAgent
|
|
64
|
+
? `No workflows in central storage for ${agentLabel(filterAgent)}.`
|
|
65
|
+
: 'No workflows in ~/.agents/workflows/. Add one with: agents workflows add gh:user/repo',
|
|
66
|
+
centralPath: getUserWorkflowsDir(),
|
|
67
|
+
filterAgent,
|
|
68
|
+
filterVersion,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
workflowsCmd
|
|
72
|
+
.command('add [source]')
|
|
73
|
+
.description('Install workflows from a source (GitHub, local) or pick from central storage')
|
|
74
|
+
.option('-a, --agents <list>', 'Targets: claude, claude@2.1.138')
|
|
75
|
+
.option('-y, --yes', 'Skip confirmation prompts')
|
|
76
|
+
.addHelpText('after', `
|
|
77
|
+
Examples:
|
|
78
|
+
# Install from GitHub
|
|
79
|
+
agents workflows add gh:user/workflows
|
|
80
|
+
|
|
81
|
+
# Install a local workflow directory (must contain WORKFLOW.md)
|
|
82
|
+
agents workflows add ./rdev
|
|
83
|
+
|
|
84
|
+
# Install and sync to a specific version
|
|
85
|
+
agents workflows add gh:user/workflows --agents claude@2.1.138
|
|
86
|
+
`)
|
|
87
|
+
.action(async (source, options) => {
|
|
88
|
+
try {
|
|
89
|
+
let workflows;
|
|
90
|
+
if (!source) {
|
|
91
|
+
// Interactive: pick from central storage
|
|
92
|
+
const installed = listInstalledWorkflows();
|
|
93
|
+
if (installed.size === 0) {
|
|
94
|
+
console.log(chalk.yellow('No workflows in ~/.agents/workflows/'));
|
|
95
|
+
console.log(chalk.gray('\nTo add workflows from a repo:'));
|
|
96
|
+
console.log(chalk.cyan(' agents workflows add gh:user/repo'));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (!isInteractiveTerminal()) {
|
|
100
|
+
requireInteractiveSelection('Selecting workflows', [
|
|
101
|
+
'agents workflows add gh:user/repo',
|
|
102
|
+
]);
|
|
103
|
+
}
|
|
104
|
+
const choices = Array.from(installed.values()).map(w => ({
|
|
105
|
+
value: w.name,
|
|
106
|
+
name: w.frontmatter.description
|
|
107
|
+
? `${w.name} ${chalk.gray(w.frontmatter.description.slice(0, 50))}`
|
|
108
|
+
: w.name,
|
|
109
|
+
}));
|
|
110
|
+
const selected = await checkbox({ message: 'Select workflows to sync', choices });
|
|
111
|
+
if (selected.length === 0) {
|
|
112
|
+
console.log(chalk.gray('No workflows selected.'));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
workflows = selected.map(name => ({ name, path: installed.get(name).path }));
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// Fetch from repo or local path
|
|
119
|
+
const spinner = ora('Fetching workflows...').start();
|
|
120
|
+
const isGitRepo = source.startsWith('gh:') || source.startsWith('git:') ||
|
|
121
|
+
source.startsWith('https://') || source.startsWith('http://');
|
|
122
|
+
let localPath;
|
|
123
|
+
if (isGitRepo) {
|
|
124
|
+
const result = await cloneRepo(source);
|
|
125
|
+
localPath = result.localPath;
|
|
126
|
+
spinner.succeed('Repository cloned');
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
localPath = source.startsWith('~')
|
|
130
|
+
? path.join(os.homedir(), source.slice(1))
|
|
131
|
+
: path.resolve(source);
|
|
132
|
+
if (!fs.existsSync(localPath)) {
|
|
133
|
+
spinner.fail(`Path not found: ${localPath}`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
spinner.succeed('Using local path');
|
|
137
|
+
}
|
|
138
|
+
const discovered = discoverWorkflowsFromRepo(localPath);
|
|
139
|
+
if (discovered.length === 0) {
|
|
140
|
+
console.log(chalk.yellow('No workflows found (looking for WORKFLOW.md files)'));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
console.log(chalk.bold(`\nFound ${discovered.length} workflow(s):`));
|
|
144
|
+
for (const w of discovered) {
|
|
145
|
+
console.log(`\n ${chalk.cyan(w.name)}: ${w.frontmatter.description || 'no description'}`);
|
|
146
|
+
if (w.subagentCount > 0) {
|
|
147
|
+
console.log(` ${chalk.gray(`${w.subagentCount} subagent${w.subagentCount === 1 ? '' : 's'}`)}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const installSpinner = ora('Installing to central storage...').start();
|
|
151
|
+
let installed = 0;
|
|
152
|
+
for (const w of discovered) {
|
|
153
|
+
const result = installWorkflowCentrally(w.path, w.name);
|
|
154
|
+
if (result.success) {
|
|
155
|
+
installed++;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
installSpinner.stop();
|
|
159
|
+
console.log(chalk.red(`\n Failed to install ${w.name}: ${result.error}`));
|
|
160
|
+
installSpinner.start();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
installSpinner.succeed(`Installed ${installed} workflow(s) to ~/.agents/workflows/`);
|
|
164
|
+
workflows = discovered.map(w => ({
|
|
165
|
+
name: w.name,
|
|
166
|
+
path: path.join(getUserWorkflowsDir(), w.name),
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
// Agent/version selection
|
|
170
|
+
let selectedAgents;
|
|
171
|
+
let versionSelections;
|
|
172
|
+
if (options.agents) {
|
|
173
|
+
const result = resolveAgentVersionTargets(options.agents, WORKFLOW_CAPABLE_AGENTS);
|
|
174
|
+
selectedAgents = result.selectedAgents;
|
|
175
|
+
versionSelections = result.versionSelections;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
const result = await promptAgentVersionSelection(WORKFLOW_CAPABLE_AGENTS, {
|
|
179
|
+
skipPrompts: options.yes || !isInteractiveTerminal(),
|
|
180
|
+
});
|
|
181
|
+
selectedAgents = result.selectedAgents;
|
|
182
|
+
versionSelections = result.versionSelections;
|
|
183
|
+
}
|
|
184
|
+
if (selectedAgents.length === 0) {
|
|
185
|
+
console.log(chalk.yellow('\nNo agents selected.'));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const syncSpinner = ora('Syncing to agent versions...').start();
|
|
189
|
+
let synced = 0;
|
|
190
|
+
const workflowNames = workflows.map(w => w.name);
|
|
191
|
+
for (const [agentId, versions] of versionSelections) {
|
|
192
|
+
for (const version of versions) {
|
|
193
|
+
syncResourcesToVersion(agentId, version);
|
|
194
|
+
recordVersionResources(agentId, version, 'workflows', workflowNames);
|
|
195
|
+
synced++;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (synced > 0) {
|
|
199
|
+
syncSpinner.succeed(`Synced to ${synced} agent version(s)`);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
syncSpinner.info('No version-managed agents to sync');
|
|
203
|
+
}
|
|
204
|
+
console.log(chalk.green('\nWorkflows installed.'));
|
|
205
|
+
console.log(chalk.gray('Run with: agents run <workflow-name>'));
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
if (isPromptCancelled(err)) {
|
|
209
|
+
console.log(chalk.gray('\nCancelled'));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
console.error(chalk.red('Failed to add workflows'));
|
|
213
|
+
console.error(chalk.red(err.message));
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
workflowsCmd
|
|
218
|
+
.command('remove [name]')
|
|
219
|
+
.description('Remove a workflow from version homes (interactive picker if no name given)')
|
|
220
|
+
.addHelpText('after', `
|
|
221
|
+
Examples:
|
|
222
|
+
# Remove a workflow by name
|
|
223
|
+
agents workflows remove rdev
|
|
224
|
+
|
|
225
|
+
# Interactive picker
|
|
226
|
+
agents workflows remove
|
|
227
|
+
`)
|
|
228
|
+
.action(async (name) => {
|
|
229
|
+
const workflowTargetMap = new Map();
|
|
230
|
+
for (const { agent, version } of iterWorkflowsCapableVersions()) {
|
|
231
|
+
const home = getVersionHomePath(agent, version);
|
|
232
|
+
for (const n of listWorkflowsForAgent(agent, home)) {
|
|
233
|
+
const existing = workflowTargetMap.get(n);
|
|
234
|
+
if (existing) {
|
|
235
|
+
existing.targets.push({ agent, version });
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
workflowTargetMap.set(n, { name: n, targets: [{ agent, version }] });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
let toRemove;
|
|
243
|
+
if (name) {
|
|
244
|
+
toRemove = [name];
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
if (workflowTargetMap.size === 0) {
|
|
248
|
+
console.log(chalk.yellow('No workflows synced to any version.'));
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (!isInteractiveTerminal()) {
|
|
252
|
+
requireInteractiveSelection('Selecting workflows to remove', [
|
|
253
|
+
'agents workflows remove rdev',
|
|
254
|
+
]);
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
const selected = await checkbox({
|
|
258
|
+
message: 'Select workflows to remove',
|
|
259
|
+
choices: Array.from(workflowTargetMap.values()).map(w => ({
|
|
260
|
+
value: w.name,
|
|
261
|
+
name: w.name,
|
|
262
|
+
})),
|
|
263
|
+
});
|
|
264
|
+
if (selected.length === 0) {
|
|
265
|
+
console.log(chalk.gray('No workflows selected.'));
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
toRemove = selected;
|
|
269
|
+
}
|
|
270
|
+
catch (err) {
|
|
271
|
+
if (isPromptCancelled(err)) {
|
|
272
|
+
console.log(chalk.gray('Cancelled'));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
throw err;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
let removed = 0;
|
|
279
|
+
for (const workflowName of toRemove) {
|
|
280
|
+
const info = workflowTargetMap.get(workflowName);
|
|
281
|
+
if (!info || info.targets.length === 0) {
|
|
282
|
+
// Not synced to any version — try removing from central storage directly
|
|
283
|
+
const result = removeWorkflow(workflowName);
|
|
284
|
+
if (result.success) {
|
|
285
|
+
console.log(` ${chalk.red('-')} ${workflowName}: removed from central storage`);
|
|
286
|
+
removed++;
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
console.log(chalk.yellow(` Workflow '${workflowName}' not found.`));
|
|
290
|
+
}
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
const removalTargets = info.targets.map(t => ({
|
|
294
|
+
agent: t.agent,
|
|
295
|
+
version: t.version,
|
|
296
|
+
label: `${agentLabel(t.agent)}@${t.version}`,
|
|
297
|
+
}));
|
|
298
|
+
const selectedTargets = await promptRemovalTargets(workflowName, removalTargets);
|
|
299
|
+
if (selectedTargets.length === 0) {
|
|
300
|
+
console.log(chalk.gray(` Skipped '${workflowName}'.`));
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
for (const target of selectedTargets) {
|
|
304
|
+
const result = removeWorkflowFromVersion(target.agent, target.version, workflowName);
|
|
305
|
+
if (result.success) {
|
|
306
|
+
console.log(` ${chalk.red('-')} ${target.label}: ${workflowName}`);
|
|
307
|
+
removed++;
|
|
308
|
+
}
|
|
309
|
+
else if (result.error) {
|
|
310
|
+
console.log(` ${chalk.yellow('!')} ${target.label}: ${result.error}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (removed === 0) {
|
|
315
|
+
console.log(chalk.yellow('No workflows removed.'));
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
console.log(chalk.green(`\nRemoved ${removed} workflow(s) from version homes.`));
|
|
319
|
+
console.log(chalk.gray('Central source unchanged. Use "agents workflows remove <name>" again to remove from ~/.agents/workflows/.'));
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
workflowsCmd
|
|
323
|
+
.command('view [name]')
|
|
324
|
+
.description('Read workflow details (description, subagents, model, MCP)')
|
|
325
|
+
.addHelpText('after', `
|
|
326
|
+
Examples:
|
|
327
|
+
# View a specific workflow
|
|
328
|
+
agents workflows view rdev
|
|
329
|
+
|
|
330
|
+
# Interactive picker
|
|
331
|
+
agents workflows view
|
|
332
|
+
`)
|
|
333
|
+
.action(async (name) => {
|
|
334
|
+
const installed = listInstalledWorkflows();
|
|
335
|
+
if (!name) {
|
|
336
|
+
if (installed.size === 0) {
|
|
337
|
+
console.log(chalk.yellow('No workflows installed'));
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (!isInteractiveTerminal()) {
|
|
341
|
+
requireInteractiveSelection('Selecting a workflow to view', [
|
|
342
|
+
'agents workflows view rdev',
|
|
343
|
+
]);
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
name = await select({
|
|
347
|
+
message: 'Select a workflow to view',
|
|
348
|
+
choices: Array.from(installed.values()).map(w => ({
|
|
349
|
+
value: w.name,
|
|
350
|
+
name: w.frontmatter.description
|
|
351
|
+
? `${w.name} - ${w.frontmatter.description}`
|
|
352
|
+
: w.name,
|
|
353
|
+
})),
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
catch (err) {
|
|
357
|
+
if (isPromptCancelled(err)) {
|
|
358
|
+
console.log(chalk.gray('Cancelled'));
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
throw err;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
const workflow = installed.get(name);
|
|
365
|
+
if (!workflow) {
|
|
366
|
+
console.log(chalk.yellow(`Workflow '${name}' not found`));
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const lines = [];
|
|
370
|
+
const fm = workflow.frontmatter;
|
|
371
|
+
lines.push(chalk.bold(`\n${fm.name || workflow.name}\n`));
|
|
372
|
+
if (fm.description)
|
|
373
|
+
lines.push(` ${fm.description}`);
|
|
374
|
+
lines.push('');
|
|
375
|
+
if (fm.model)
|
|
376
|
+
lines.push(` Model: ${fm.model}`);
|
|
377
|
+
if (fm.tools?.length)
|
|
378
|
+
lines.push(` Tools: ${fm.tools.join(', ')}`);
|
|
379
|
+
if (fm.mcpServers?.length)
|
|
380
|
+
lines.push(` MCP: ${fm.mcpServers.join(', ')}`);
|
|
381
|
+
if (fm.skills?.length)
|
|
382
|
+
lines.push(` Skills: ${fm.skills.join(', ')}`);
|
|
383
|
+
lines.push(` Path: ${workflow.path}`);
|
|
384
|
+
if (fm.allowedAgents?.length) {
|
|
385
|
+
lines.push(chalk.bold(`\n Subagents (${workflow.subagentCount}):`));
|
|
386
|
+
for (const a of fm.allowedAgents) {
|
|
387
|
+
lines.push(` ${chalk.cyan(a)}`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
lines.push('');
|
|
391
|
+
printWithPager(lines.join('\n'), lines.length);
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
function buildWorkflowRows(opts) {
|
|
395
|
+
const central = listInstalledWorkflows();
|
|
396
|
+
if (central.size === 0)
|
|
397
|
+
return [];
|
|
398
|
+
const targetPairs = iterWorkflowsCapableVersions({
|
|
399
|
+
agent: opts.filterAgent,
|
|
400
|
+
version: opts.filterVersion,
|
|
401
|
+
});
|
|
402
|
+
const syncedByTarget = new Map();
|
|
403
|
+
const defaultByAgent = new Map();
|
|
404
|
+
for (const { agent, version } of targetPairs) {
|
|
405
|
+
if (!defaultByAgent.has(agent))
|
|
406
|
+
defaultByAgent.set(agent, getGlobalDefault(agent));
|
|
407
|
+
const home = getVersionHomePath(agent, version);
|
|
408
|
+
syncedByTarget.set(`${agent}@${version}`, new Set(listWorkflowsForAgent(agent, home)));
|
|
409
|
+
}
|
|
410
|
+
const rows = [];
|
|
411
|
+
for (const [name, workflow] of central) {
|
|
412
|
+
const targets = targetPairs.map(({ agent, version }) => ({
|
|
413
|
+
agent,
|
|
414
|
+
version,
|
|
415
|
+
isDefault: defaultByAgent.get(agent) === version,
|
|
416
|
+
status: syncedByTarget.get(`${agent}@${version}`)?.has(name) ? 'synced' : 'missing',
|
|
417
|
+
}));
|
|
418
|
+
rows.push({
|
|
419
|
+
name,
|
|
420
|
+
description: workflow.frontmatter.description,
|
|
421
|
+
extra: workflow.subagentCount > 0 ? `${workflow.subagentCount}` : '-',
|
|
422
|
+
targets,
|
|
423
|
+
buildDetail: () => formatWorkflowDetail(workflow, targets),
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
rows.sort((a, b) => {
|
|
427
|
+
const aSynced = a.targets.filter(t => t.status === 'synced').length;
|
|
428
|
+
const bSynced = b.targets.filter(t => t.status === 'synced').length;
|
|
429
|
+
if (aSynced !== bSynced)
|
|
430
|
+
return bSynced - aSynced;
|
|
431
|
+
return a.name.localeCompare(b.name);
|
|
432
|
+
});
|
|
433
|
+
return rows;
|
|
434
|
+
}
|
|
435
|
+
function formatWorkflowDetail(workflow, targets) {
|
|
436
|
+
const lines = [];
|
|
437
|
+
const fm = workflow.frontmatter;
|
|
438
|
+
lines.push(chalk.bold.cyan(workflow.name));
|
|
439
|
+
if (fm.description)
|
|
440
|
+
lines.push(chalk.gray(fm.description));
|
|
441
|
+
lines.push('');
|
|
442
|
+
const meta = [];
|
|
443
|
+
if (fm.model)
|
|
444
|
+
meta.push(`model ${chalk.white(fm.model)}`);
|
|
445
|
+
if (fm.mcpServers?.length)
|
|
446
|
+
meta.push(`${chalk.white(fm.mcpServers.length)} MCP`);
|
|
447
|
+
if (fm.skills?.length)
|
|
448
|
+
meta.push(`${chalk.white(fm.skills.length)} skill${fm.skills.length === 1 ? '' : 's'}`);
|
|
449
|
+
meta.push(`${chalk.white(workflow.subagentCount)} subagent${workflow.subagentCount === 1 ? '' : 's'}`);
|
|
450
|
+
if (meta.length)
|
|
451
|
+
lines.push(' ' + meta.join(chalk.gray(' · ')));
|
|
452
|
+
lines.push(' ' + chalk.gray(workflow.path));
|
|
453
|
+
lines.push('');
|
|
454
|
+
lines.push(chalk.bold(' Synced to'));
|
|
455
|
+
lines.push(buildTargetsSection(targets));
|
|
456
|
+
return lines.join('\n');
|
|
457
|
+
}
|
package/dist/index.d.ts
CHANGED