@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/lib/versions.js
CHANGED
|
@@ -23,14 +23,16 @@ import { promisify } from 'util';
|
|
|
23
23
|
import chalk from 'chalk';
|
|
24
24
|
import * as TOML from 'smol-toml';
|
|
25
25
|
import { checkbox, select } from '@inquirer/prompts';
|
|
26
|
-
import { getVersionsDir, ensureAgentsDir, readMeta, writeMeta, getCommandsDir, getSkillsDir, getHooksDir, getResolvedRulesDir, getUserRulesDir, clearVersionResources,
|
|
27
|
-
import {
|
|
26
|
+
import { getVersionsDir, ensureAgentsDir, readMeta, writeMeta, getCommandsDir, getSkillsDir, getHooksDir, getResolvedRulesDir, getUserRulesDir, clearVersionResources, getVersionResources, ensureVersionResourcePatterns, getProjectAgentsDir, getPromptcutsPath, getUserPromptcutsPath, getEnabledExtraRepos, getAgentsDir, getUserAgentsDir, getTrashVersionsDir, getActiveRulesPreset } from './state.js';
|
|
27
|
+
import { defaultPatterns, expandPatterns } from './resource-patterns.js';
|
|
28
|
+
import { resolveResource, listResources } from './resources.js';
|
|
28
29
|
import { AGENTS, getAccountEmail, MCP_CAPABLE_AGENTS, COMMANDS_CAPABLE_AGENTS, getMcpConfigPathForHome, parseMcpConfig, resolveAgentName, formatAgentError } from './agents.js';
|
|
29
|
-
import { applyPermissionsToVersion as applyPermsToVersion, PERMISSIONS_CAPABLE_AGENTS, discoverPermissionGroups, buildPermissionsFromGroups, CODEX_RULES_FILENAME,
|
|
30
|
+
import { applyPermissionsToVersion as applyPermsToVersion, PERMISSIONS_CAPABLE_AGENTS, discoverPermissionGroups, buildPermissionsFromGroups, CODEX_RULES_FILENAME, getActivePermissionPresetName, readPermissionPresetRecipe, PERMISSION_PRESET_ENV_VAR } from './permissions.js';
|
|
30
31
|
import { installMcpServers, parseMcpServerConfig } from './mcp.js';
|
|
31
32
|
import { markdownToToml } from './convert.js';
|
|
32
33
|
import { createVersionedAlias, removeVersionedAlias, getConfigSymlinkVersion, ensureClaudeInsideSymlink } from './shims.js';
|
|
33
34
|
import { listInstalledSubagents, transformSubagentForClaude, syncSubagentToOpenclaw, SUBAGENT_CAPABLE_AGENTS } from './subagents.js';
|
|
35
|
+
import { WORKFLOW_CAPABLE_AGENTS, listInstalledWorkflows, syncWorkflowToVersion } from './workflows.js';
|
|
34
36
|
import { registerHooksToSettings } from './hooks.js';
|
|
35
37
|
import { supports, explainSkip } from './capabilities.js';
|
|
36
38
|
import { discoverPlugins, syncPluginToVersion, isPluginSynced, pluginSupportsAgent, cleanOrphanedPluginSkills } from './plugins.js';
|
|
@@ -62,6 +64,7 @@ export function getAvailableResources(cwd = process.cwd()) {
|
|
|
62
64
|
permissions: [],
|
|
63
65
|
subagents: [],
|
|
64
66
|
plugins: [],
|
|
67
|
+
workflows: [],
|
|
65
68
|
promptcuts: false,
|
|
66
69
|
};
|
|
67
70
|
const projectAgentsDir = getProjectAgentsDir(cwd);
|
|
@@ -189,13 +192,26 @@ export function getAvailableResources(cwd = process.cwd()) {
|
|
|
189
192
|
}
|
|
190
193
|
}
|
|
191
194
|
result.subagents = Array.from(subagentNames);
|
|
195
|
+
// Workflows (directories with WORKFLOW.md)
|
|
196
|
+
const workflowNames = new Set();
|
|
197
|
+
for (const { base } of resourceBases) {
|
|
198
|
+
const workflowsDir = path.join(base, 'workflows');
|
|
199
|
+
if (!fs.existsSync(workflowsDir))
|
|
200
|
+
continue;
|
|
201
|
+
const names = fs.readdirSync(workflowsDir, { withFileTypes: true })
|
|
202
|
+
.filter(d => d.isDirectory() && fs.existsSync(path.join(workflowsDir, d.name, 'WORKFLOW.md')))
|
|
203
|
+
.map(d => d.name);
|
|
204
|
+
for (const name of names) {
|
|
205
|
+
workflowNames.add(name);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
result.workflows = Array.from(workflowNames);
|
|
192
209
|
// Plugins (directories with .claude-plugin/plugin.json)
|
|
193
210
|
const allPlugins = discoverPlugins();
|
|
194
211
|
result.plugins = allPlugins.map(p => p.name);
|
|
195
|
-
// Promptcuts —
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
result.promptcuts = fs.existsSync(getPromptcutsPath());
|
|
212
|
+
// Promptcuts — present if either layer exists. Reads merge user + system
|
|
213
|
+
// with user precedence (see readMergedPromptcuts); writes always go to user.
|
|
214
|
+
result.promptcuts = fs.existsSync(getUserPromptcutsPath()) || fs.existsSync(getPromptcutsPath());
|
|
199
215
|
return result;
|
|
200
216
|
}
|
|
201
217
|
// Files/dirs that are never synced into a version home (OS metadata, local tooling).
|
|
@@ -251,6 +267,7 @@ export function getActuallySyncedResources(agent, version, options = {}) {
|
|
|
251
267
|
permissions: [],
|
|
252
268
|
subagents: [],
|
|
253
269
|
plugins: [],
|
|
270
|
+
workflows: [],
|
|
254
271
|
promptcuts: false,
|
|
255
272
|
};
|
|
256
273
|
// Commands - check what files exist in version home
|
|
@@ -449,6 +466,15 @@ export function getActuallySyncedResources(agent, version, options = {}) {
|
|
|
449
466
|
}
|
|
450
467
|
}
|
|
451
468
|
}
|
|
469
|
+
// Workflows - check {versionHome}/workflows/ for synced workflow directories
|
|
470
|
+
if (WORKFLOW_CAPABLE_AGENTS.includes(agent)) {
|
|
471
|
+
const workflowsDir = path.join(versionHome, 'workflows');
|
|
472
|
+
if (fs.existsSync(workflowsDir)) {
|
|
473
|
+
result.workflows = fs.readdirSync(workflowsDir, { withFileTypes: true })
|
|
474
|
+
.filter(d => d.isDirectory() && fs.existsSync(path.join(workflowsDir, d.name, 'WORKFLOW.md')))
|
|
475
|
+
.map(d => d.name);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
452
478
|
return result;
|
|
453
479
|
}
|
|
454
480
|
/**
|
|
@@ -470,6 +496,7 @@ export function getNewResources(available, actuallySynced) {
|
|
|
470
496
|
permissions: available.permissions.filter(p => !actuallySynced.permissions.includes(p)),
|
|
471
497
|
subagents: available.subagents.filter(s => !actuallySynced.subagents.includes(s)),
|
|
472
498
|
plugins: available.plugins.filter(p => !actuallySynced.plugins.includes(p)),
|
|
499
|
+
workflows: available.workflows.filter(w => !actuallySynced.workflows.includes(w)),
|
|
473
500
|
// Promptcuts aren't version-scoped — the hook reads ~/.agents/promptcuts.yaml
|
|
474
501
|
// directly, so there is never a "new" per-version state to reconcile.
|
|
475
502
|
promptcuts: false,
|
|
@@ -486,6 +513,7 @@ export function hasNewResources(diff, agent, version) {
|
|
|
486
513
|
const permsApply = agent ? supports(agent, 'allowlist', version).ok : true;
|
|
487
514
|
const subagentsApply = agent ? SUBAGENT_CAPABLE_AGENTS.includes(agent) : true;
|
|
488
515
|
const pluginsApply = agent ? supports(agent, 'plugins', version).ok : true;
|
|
516
|
+
const workflowsApply = agent ? WORKFLOW_CAPABLE_AGENTS.includes(agent) : true;
|
|
489
517
|
return ((diff.commands.length > 0 && commandsApply) ||
|
|
490
518
|
diff.skills.length > 0 ||
|
|
491
519
|
(diff.hooks.length > 0 && hooksApply) ||
|
|
@@ -493,7 +521,8 @@ export function hasNewResources(diff, agent, version) {
|
|
|
493
521
|
(diff.mcp.length > 0 && mcpApply) ||
|
|
494
522
|
(diff.permissions.length > 0 && permsApply) ||
|
|
495
523
|
(diff.subagents.length > 0 && subagentsApply) ||
|
|
496
|
-
(diff.plugins.length > 0 && pluginsApply)
|
|
524
|
+
(diff.plugins.length > 0 && pluginsApply) ||
|
|
525
|
+
(diff.workflows.length > 0 && workflowsApply));
|
|
497
526
|
}
|
|
498
527
|
/**
|
|
499
528
|
* Build a summary string of new resources.
|
|
@@ -526,6 +555,9 @@ function buildNewResourcesSummary(newResources, agent) {
|
|
|
526
555
|
if (newResources.plugins.length > 0 && PLUGINS_CAPABLE_AGENTS.includes(agent)) {
|
|
527
556
|
parts.push(`${newResources.plugins.length} plugin${newResources.plugins.length === 1 ? '' : 's'}`);
|
|
528
557
|
}
|
|
558
|
+
if (newResources.workflows.length > 0 && WORKFLOW_CAPABLE_AGENTS.includes(agent)) {
|
|
559
|
+
parts.push(`${newResources.workflows.length} workflow${newResources.workflows.length === 1 ? '' : 's'}`);
|
|
560
|
+
}
|
|
529
561
|
return parts.join(', ');
|
|
530
562
|
}
|
|
531
563
|
/**
|
|
@@ -574,6 +606,8 @@ export async function promptNewResourceSelection(agent, newResources) {
|
|
|
574
606
|
selection.subagents = newResources.subagents;
|
|
575
607
|
if (newResources.plugins.length > 0 && PLUGINS_CAPABLE_AGENTS.includes(agent))
|
|
576
608
|
selection.plugins = newResources.plugins;
|
|
609
|
+
if (newResources.workflows.length > 0 && WORKFLOW_CAPABLE_AGENTS.includes(agent))
|
|
610
|
+
selection.workflows = newResources.workflows;
|
|
577
611
|
return selection;
|
|
578
612
|
}
|
|
579
613
|
// Select specific items for each category
|
|
@@ -651,6 +685,14 @@ export async function promptNewResourceSelection(agent, newResources) {
|
|
|
651
685
|
if (selected.length > 0)
|
|
652
686
|
selection.plugins = selected;
|
|
653
687
|
}
|
|
688
|
+
if (newResources.workflows.length > 0 && WORKFLOW_CAPABLE_AGENTS.includes(agent)) {
|
|
689
|
+
const selected = await checkbox({
|
|
690
|
+
message: 'Select new workflows to sync:',
|
|
691
|
+
choices: newResources.workflows.map(w => ({ name: w, value: w, checked: true })),
|
|
692
|
+
});
|
|
693
|
+
if (selected.length > 0)
|
|
694
|
+
selection.workflows = selected;
|
|
695
|
+
}
|
|
654
696
|
return selection;
|
|
655
697
|
}
|
|
656
698
|
/**
|
|
@@ -676,14 +718,14 @@ export async function promptResourceSelection(agent) {
|
|
|
676
718
|
];
|
|
677
719
|
const availableCategories = categories.filter(c => c.available);
|
|
678
720
|
if (availableCategories.length === 0) {
|
|
679
|
-
console.log(chalk.gray('No resources available
|
|
721
|
+
console.log(chalk.gray('No resources available to sync.'));
|
|
680
722
|
return {};
|
|
681
723
|
}
|
|
682
724
|
// Step 1: Select categories (with "Select All" shortcut at the top)
|
|
683
725
|
console.log();
|
|
684
726
|
const SELECT_ALL_KEY = '__select_all__';
|
|
685
727
|
const selectedCategories = await checkbox({
|
|
686
|
-
message: 'Which resources
|
|
728
|
+
message: 'Which resources would you like to sync?',
|
|
687
729
|
choices: [
|
|
688
730
|
{ name: chalk.bold('Select All (sync everything)'), value: SELECT_ALL_KEY, checked: false },
|
|
689
731
|
...availableCategories.map(c => ({
|
|
@@ -1189,7 +1231,7 @@ export function resolveVersionAliasLoose(agent, raw) {
|
|
|
1189
1231
|
* Get version specified in a project-root agents.yaml (not the user ~/.agents-system/agents.yaml).
|
|
1190
1232
|
*/
|
|
1191
1233
|
export function getProjectVersion(agent, startPath) {
|
|
1192
|
-
const userAgentsYaml = path.join(
|
|
1234
|
+
const userAgentsYaml = path.join(getUserAgentsDir(), 'agents.yaml');
|
|
1193
1235
|
let dir = path.resolve(startPath);
|
|
1194
1236
|
while (dir !== path.dirname(dir)) {
|
|
1195
1237
|
const manifestPath = path.join(dir, 'agents.yaml');
|
|
@@ -1400,7 +1442,7 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
1400
1442
|
const versionHome = getVersionHomePath(agent, version);
|
|
1401
1443
|
const agentDir = path.join(versionHome, `.${agent}`);
|
|
1402
1444
|
fs.mkdirSync(agentDir, { recursive: true });
|
|
1403
|
-
const result = { commands: false, skills: false, hooks: false, memory: [], permissions: false, mcp: [], subagents: [], plugins: [] };
|
|
1445
|
+
const result = { commands: false, skills: false, hooks: false, memory: [], permissions: false, mcp: [], subagents: [], plugins: [], workflows: [] };
|
|
1404
1446
|
const cwd = options.cwd || process.cwd();
|
|
1405
1447
|
const projectAgentsDir = options.projectDir || getProjectAgentsDir(cwd);
|
|
1406
1448
|
const userAgentsDir = getUserAgentsDir();
|
|
@@ -1408,13 +1450,80 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
1408
1450
|
// project/user/system repos win on name collisions.
|
|
1409
1451
|
const extraRepos = getEnabledExtraRepos();
|
|
1410
1452
|
const available = getAvailableResources(cwd);
|
|
1453
|
+
// Write default resource selection patterns for this version (idempotent —
|
|
1454
|
+
// only sets fields that aren't already present, preserving user edits).
|
|
1455
|
+
{
|
|
1456
|
+
const extraAliases = extraRepos.map(e => e.alias);
|
|
1457
|
+
const allLayers = defaultPatterns(extraAliases);
|
|
1458
|
+
const noProject = defaultPatterns(extraAliases, false);
|
|
1459
|
+
ensureVersionResourcePatterns(agent, version, {
|
|
1460
|
+
commands: allLayers,
|
|
1461
|
+
skills: allLayers,
|
|
1462
|
+
hooks: noProject, // hooks: no project layer (security)
|
|
1463
|
+
subagents: noProject,
|
|
1464
|
+
plugins: noProject,
|
|
1465
|
+
workflows: noProject,
|
|
1466
|
+
permissions: ['system:*'],
|
|
1467
|
+
mcp: ['user:*'],
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
// If no explicit selection was passed, build one from the persisted resource
|
|
1471
|
+
// patterns. This lets users customize agents.yaml to control which resources
|
|
1472
|
+
// are synced (e.g. "skills: [system:brain-scan user:creative]").
|
|
1473
|
+
// When patterns are the default (every layer wildcard), the expanded result
|
|
1474
|
+
// equals the full available set — identical to the old behavior.
|
|
1475
|
+
if (!selection) {
|
|
1476
|
+
const vr = getVersionResources(agent, version);
|
|
1477
|
+
if (vr) {
|
|
1478
|
+
const patternSelection = {};
|
|
1479
|
+
// Listable resource types: use listResources to get name→source maps.
|
|
1480
|
+
const listableTypes = [
|
|
1481
|
+
['commands', 'commands'],
|
|
1482
|
+
['skills', 'skills'],
|
|
1483
|
+
['hooks', 'hooks'],
|
|
1484
|
+
['subagents', 'subagents'],
|
|
1485
|
+
];
|
|
1486
|
+
for (const [type, kind] of listableTypes) {
|
|
1487
|
+
const patterns = vr[type];
|
|
1488
|
+
if (!Array.isArray(patterns) || patterns.length === 0)
|
|
1489
|
+
continue;
|
|
1490
|
+
const sourceMap = new Map(listResources(kind, cwd).map(r => [r.name, r.source]));
|
|
1491
|
+
patternSelection[type] = expandPatterns(patterns, sourceMap);
|
|
1492
|
+
}
|
|
1493
|
+
// permissions: all groups are 'system' source.
|
|
1494
|
+
if (Array.isArray(vr.permissions) && vr.permissions.length > 0) {
|
|
1495
|
+
const permMap = new Map(available.permissions.map(n => [n, 'system']));
|
|
1496
|
+
patternSelection.permissions = expandPatterns(vr.permissions, permMap);
|
|
1497
|
+
}
|
|
1498
|
+
// mcp: all declared servers are 'user' source.
|
|
1499
|
+
if (Array.isArray(vr.mcp) && vr.mcp.length > 0) {
|
|
1500
|
+
const mcpMap = new Map(available.mcp.map(n => [n, 'user']));
|
|
1501
|
+
patternSelection.mcp = expandPatterns(vr.mcp, mcpMap);
|
|
1502
|
+
}
|
|
1503
|
+
// plugins: treat all as 'user' source for now.
|
|
1504
|
+
if (Array.isArray(vr.plugins) && vr.plugins.length > 0) {
|
|
1505
|
+
const pluginMap = new Map(available.plugins.map(n => [n, 'user']));
|
|
1506
|
+
patternSelection.plugins = expandPatterns(vr.plugins, pluginMap);
|
|
1507
|
+
}
|
|
1508
|
+
// workflows: treat all as 'user' source.
|
|
1509
|
+
if (Array.isArray(vr.workflows) && vr.workflows.length > 0) {
|
|
1510
|
+
const workflowMap = new Map(available.workflows.map(n => [n, 'user']));
|
|
1511
|
+
patternSelection.workflows = expandPatterns(vr.workflows, workflowMap);
|
|
1512
|
+
}
|
|
1513
|
+
// memory is not pattern-controlled (rulesPreset handles it) — always sync.
|
|
1514
|
+
patternSelection.memory = 'all';
|
|
1515
|
+
if (Object.keys(patternSelection).length > 0) {
|
|
1516
|
+
selection = patternSelection;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1411
1520
|
// Fast guard: skip the entire sync when no selection is active and nothing
|
|
1412
1521
|
// has changed since the last full sync. Drops steady-state cost from ~16s
|
|
1413
1522
|
// (unconditional file copies) to ~2ms (stat calls + manifest read).
|
|
1414
1523
|
if (!selection && !options.force) {
|
|
1415
1524
|
const manifest = loadSyncManifest(agent, version);
|
|
1416
1525
|
if (manifest && !isSyncStale(manifest, available, agent, version, cwd)) {
|
|
1417
|
-
return { commands: false, skills: false, hooks: false, memory: [], permissions: false, mcp: [], subagents: [], plugins: [] };
|
|
1526
|
+
return { commands: false, skills: false, hooks: false, memory: [], permissions: false, mcp: [], subagents: [], plugins: [], workflows: [] };
|
|
1418
1527
|
}
|
|
1419
1528
|
}
|
|
1420
1529
|
// Helper: remove a path (symlink or real) if it exists
|
|
@@ -1500,9 +1609,6 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
1500
1609
|
syncedCommands.push(cmd);
|
|
1501
1610
|
}
|
|
1502
1611
|
result.commands = syncedCommands.length > 0;
|
|
1503
|
-
if (syncedCommands.length > 0) {
|
|
1504
|
-
recordVersionResources(agent, version, 'commands', syncedCommands);
|
|
1505
|
-
}
|
|
1506
1612
|
}
|
|
1507
1613
|
// Sync skills (skip if agent natively reads ~/.agents/skills/)
|
|
1508
1614
|
if (agentConfig.nativeAgentsSkillsDir) {
|
|
@@ -1531,9 +1637,6 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
1531
1637
|
syncedSkills.push(skill);
|
|
1532
1638
|
}
|
|
1533
1639
|
result.skills = syncedSkills.length > 0;
|
|
1534
|
-
if (syncedSkills.length > 0) {
|
|
1535
|
-
recordVersionResources(agent, version, 'skills', syncedSkills);
|
|
1536
|
-
}
|
|
1537
1640
|
}
|
|
1538
1641
|
}
|
|
1539
1642
|
// Sync hooks (if agent supports them at this version)
|
|
@@ -1599,9 +1702,6 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
1599
1702
|
}
|
|
1600
1703
|
}
|
|
1601
1704
|
result.hooks = syncedHooks.length > 0;
|
|
1602
|
-
if (syncedHooks.length > 0) {
|
|
1603
|
-
recordVersionResources(agent, version, 'hooks', syncedHooks);
|
|
1604
|
-
}
|
|
1605
1705
|
// Register hooks into agent-native settings.json/hooks.json. Gemini
|
|
1606
1706
|
// shipped hooks in 0.26.0; gate already passed above so this is safe.
|
|
1607
1707
|
if (agent === 'claude' || agent === 'codex' || agent === 'gemini') {
|
|
@@ -1630,8 +1730,7 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
1630
1730
|
removePath(destFile);
|
|
1631
1731
|
fs.writeFileSync(destFile, composed.content);
|
|
1632
1732
|
result.memory.push(targetName);
|
|
1633
|
-
//
|
|
1634
|
-
recordVersionResources(agent, version, 'memory', [composed.preset]);
|
|
1733
|
+
// rulesPreset is tracked separately via setActiveRulesPreset.
|
|
1635
1734
|
}
|
|
1636
1735
|
catch (err) {
|
|
1637
1736
|
// No rules.yaml yet, or a typo'd preset name. Don't fail the whole sync —
|
|
@@ -1641,39 +1740,39 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
1641
1740
|
}
|
|
1642
1741
|
// Apply permissions (if agent supports them).
|
|
1643
1742
|
// Groups live in ~/.agents/permissions/groups/. Optional recipes in
|
|
1644
|
-
// ~/.agents/permissions/
|
|
1645
|
-
// If
|
|
1743
|
+
// ~/.agents/permissions/presets/<name>.yaml pick a subset via `includes:`.
|
|
1744
|
+
// If AGENTS_PERMISSION_PRESET is set, we resolve that recipe and use its
|
|
1646
1745
|
// includes list as the group filter (intersected with groups on disk).
|
|
1647
1746
|
const permissionGroups = discoverPermissionGroups();
|
|
1648
1747
|
const allGroupNames = permissionGroups.map(g => g.name);
|
|
1649
|
-
const
|
|
1650
|
-
let
|
|
1651
|
-
if (
|
|
1652
|
-
const recipe =
|
|
1748
|
+
const activePresetName = getActivePermissionPresetName();
|
|
1749
|
+
let presetFilteredGroups = null;
|
|
1750
|
+
if (activePresetName) {
|
|
1751
|
+
const recipe = readPermissionPresetRecipe(activePresetName);
|
|
1653
1752
|
if (recipe) {
|
|
1654
1753
|
const available = new Set(allGroupNames);
|
|
1655
|
-
|
|
1754
|
+
presetFilteredGroups = recipe.includes.filter(g => available.has(g));
|
|
1656
1755
|
}
|
|
1657
1756
|
else {
|
|
1658
|
-
console.warn(`${
|
|
1757
|
+
console.warn(`${PERMISSION_PRESET_ENV_VAR}=${activePresetName} but no recipe at ~/.agents/permissions/presets/${activePresetName}.yaml — falling back to all groups`);
|
|
1659
1758
|
}
|
|
1660
1759
|
}
|
|
1661
1760
|
let permsToSync;
|
|
1662
1761
|
if (selection) {
|
|
1663
1762
|
permsToSync = resolveSelection(selection.permissions, allGroupNames);
|
|
1664
|
-
// If a
|
|
1763
|
+
// If a preset recipe is active, the recipe's includes list always wins —
|
|
1665
1764
|
// even when the caller passed an explicit array via selection. Without
|
|
1666
1765
|
// this intersection, `agents add`'s buildAutomaticSelection would pass
|
|
1667
1766
|
// every group name discovered on disk (including 99-deny), bypassing
|
|
1668
1767
|
// the sandbox filter.
|
|
1669
|
-
if (
|
|
1670
|
-
const filterSet = new Set(
|
|
1768
|
+
if (presetFilteredGroups) {
|
|
1769
|
+
const filterSet = new Set(presetFilteredGroups);
|
|
1671
1770
|
permsToSync = permsToSync.filter(g => filterSet.has(g));
|
|
1672
1771
|
}
|
|
1673
1772
|
}
|
|
1674
1773
|
else {
|
|
1675
1774
|
permsToSync = PERMISSIONS_CAPABLE_AGENTS.includes(agent)
|
|
1676
|
-
? (
|
|
1775
|
+
? (presetFilteredGroups ?? allGroupNames)
|
|
1677
1776
|
: [];
|
|
1678
1777
|
}
|
|
1679
1778
|
if (permsToSync.length > 0 && PERMISSIONS_CAPABLE_AGENTS.includes(agent)) {
|
|
@@ -1682,9 +1781,7 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
1682
1781
|
if (builtPerms.allow.length > 0 || (builtPerms.deny && builtPerms.deny.length > 0)) {
|
|
1683
1782
|
const permResult = applyPermsToVersion(agent, builtPerms, versionHome, true);
|
|
1684
1783
|
result.permissions = permResult.success;
|
|
1685
|
-
|
|
1686
|
-
recordVersionResources(agent, version, 'permissions', permsToSync);
|
|
1687
|
-
}
|
|
1784
|
+
// permissions patterns already written via ensureVersionResourcePatterns above.
|
|
1688
1785
|
}
|
|
1689
1786
|
}
|
|
1690
1787
|
// Install MCP servers (if agent supports them)
|
|
@@ -1696,9 +1793,7 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
1696
1793
|
if (mcpToSync.length > 0 && MCP_CAPABLE_AGENTS.includes(agent)) {
|
|
1697
1794
|
const mcpResult = installMcpServers(agent, version, versionHome, mcpToSync, { cwd });
|
|
1698
1795
|
result.mcp = mcpResult.applied;
|
|
1699
|
-
|
|
1700
|
-
recordVersionResources(agent, version, 'mcp', mcpResult.applied);
|
|
1701
|
-
}
|
|
1796
|
+
// mcp patterns already written via ensureVersionResourcePatterns above.
|
|
1702
1797
|
}
|
|
1703
1798
|
// Sync subagents (claude and openclaw only)
|
|
1704
1799
|
const subagentsToSync = selection
|
|
@@ -1731,9 +1826,7 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
1731
1826
|
}
|
|
1732
1827
|
catch { /* resource sync failed for this item */ }
|
|
1733
1828
|
}
|
|
1734
|
-
|
|
1735
|
-
recordVersionResources(agent, version, 'subagents', result.subagents);
|
|
1736
|
-
}
|
|
1829
|
+
// subagent patterns already written via ensureVersionResourcePatterns above.
|
|
1737
1830
|
}
|
|
1738
1831
|
// Sync plugins (claude and openclaw)
|
|
1739
1832
|
const pluginsToSync = selection
|
|
@@ -1754,9 +1847,27 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
1754
1847
|
result.plugins.push(name);
|
|
1755
1848
|
}
|
|
1756
1849
|
}
|
|
1757
|
-
|
|
1758
|
-
|
|
1850
|
+
// plugin patterns already written via ensureVersionResourcePatterns above.
|
|
1851
|
+
}
|
|
1852
|
+
// Sync workflows (claude only)
|
|
1853
|
+
const workflowsToSync = selection
|
|
1854
|
+
? resolveSelection(selection.workflows, available.workflows)
|
|
1855
|
+
: (WORKFLOW_CAPABLE_AGENTS.includes(agent) ? available.workflows : []);
|
|
1856
|
+
if (workflowsToSync.length > 0 && WORKFLOW_CAPABLE_AGENTS.includes(agent)) {
|
|
1857
|
+
const allWorkflows = listInstalledWorkflows();
|
|
1858
|
+
for (const name of workflowsToSync) {
|
|
1859
|
+
const workflow = allWorkflows.get(name);
|
|
1860
|
+
if (!workflow)
|
|
1861
|
+
continue;
|
|
1862
|
+
try {
|
|
1863
|
+
const syncResult = syncWorkflowToVersion(workflow.path, name, agent, versionHome);
|
|
1864
|
+
if (syncResult.success) {
|
|
1865
|
+
result.workflows.push(name);
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
catch { /* resource sync failed for this item */ }
|
|
1759
1869
|
}
|
|
1870
|
+
// workflow patterns already written via ensureVersionResourcePatterns above.
|
|
1760
1871
|
}
|
|
1761
1872
|
// Write manifest after a successful full sync so the next launch can skip this work.
|
|
1762
1873
|
if (!selection) {
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow management library.
|
|
3
|
+
*
|
|
4
|
+
* Workflows are directory bundles with a WORKFLOW.md containing YAML frontmatter.
|
|
5
|
+
* They optionally contain subagents/, skills/, and plugins/ subdirectories that
|
|
6
|
+
* are composed at runtime by `agents run <workflow>`.
|
|
7
|
+
*/
|
|
8
|
+
import type { AgentId } from './types.js';
|
|
9
|
+
/** Agents that support running workflows via `agents run`. */
|
|
10
|
+
export declare const WORKFLOW_CAPABLE_AGENTS: AgentId[];
|
|
11
|
+
/** Parsed WORKFLOW.md frontmatter. */
|
|
12
|
+
export interface WorkflowFrontmatter {
|
|
13
|
+
name: string;
|
|
14
|
+
description: string;
|
|
15
|
+
model?: string;
|
|
16
|
+
tools?: string[];
|
|
17
|
+
skills?: string[];
|
|
18
|
+
mcpServers?: string[];
|
|
19
|
+
allowedAgents?: string[];
|
|
20
|
+
}
|
|
21
|
+
/** A workflow found during repo discovery. */
|
|
22
|
+
export interface DiscoveredWorkflow {
|
|
23
|
+
name: string;
|
|
24
|
+
path: string;
|
|
25
|
+
frontmatter: WorkflowFrontmatter;
|
|
26
|
+
subagentCount: number;
|
|
27
|
+
}
|
|
28
|
+
/** A workflow in central storage (~/.agents/workflows/ or ~/.agents-system/workflows/). */
|
|
29
|
+
export interface InstalledWorkflow {
|
|
30
|
+
name: string;
|
|
31
|
+
path: string;
|
|
32
|
+
frontmatter: WorkflowFrontmatter;
|
|
33
|
+
subagentCount: number;
|
|
34
|
+
}
|
|
35
|
+
/** Parse WORKFLOW.md frontmatter from a workflow directory. Returns null if invalid. */
|
|
36
|
+
export declare function parseWorkflowFrontmatter(workflowDir: string): WorkflowFrontmatter | null;
|
|
37
|
+
/** Count subagent .md files in a workflow's subagents/ directory. */
|
|
38
|
+
export declare function countWorkflowSubagents(workflowDir: string): number;
|
|
39
|
+
/**
|
|
40
|
+
* Discover all workflow directories (those containing WORKFLOW.md) in a local path.
|
|
41
|
+
* Checks if the path itself is a workflow, then scans a top-level workflows/ subdirectory,
|
|
42
|
+
* then falls back to scanning all immediate subdirectories.
|
|
43
|
+
*/
|
|
44
|
+
export declare function discoverWorkflowsFromRepo(repoPath: string): DiscoveredWorkflow[];
|
|
45
|
+
/**
|
|
46
|
+
* List all workflows in central storage.
|
|
47
|
+
* User layer (~/.agents/workflows/) wins over system (~/.agents-system/workflows/).
|
|
48
|
+
*/
|
|
49
|
+
export declare function listInstalledWorkflows(): Map<string, InstalledWorkflow>;
|
|
50
|
+
/** Copy a workflow directory into user central storage (~/.agents/workflows/<name>/). */
|
|
51
|
+
export declare function installWorkflowCentrally(sourcePath: string, name: string): {
|
|
52
|
+
success: boolean;
|
|
53
|
+
error?: string;
|
|
54
|
+
};
|
|
55
|
+
/** Move a workflow from user central storage to trash. */
|
|
56
|
+
export declare function removeWorkflow(name: string): {
|
|
57
|
+
success: boolean;
|
|
58
|
+
error?: string;
|
|
59
|
+
};
|
|
60
|
+
/** List workflow names synced into a specific agent version home (at {versionHome}/workflows/). */
|
|
61
|
+
export declare function listWorkflowsForAgent(_agent: AgentId, versionHome: string): string[];
|
|
62
|
+
/** Copy a workflow directory into a version home at {versionHome}/workflows/<name>/. */
|
|
63
|
+
export declare function syncWorkflowToVersion(workflowPath: string, name: string, _agent: AgentId, versionHome: string): {
|
|
64
|
+
success: boolean;
|
|
65
|
+
error?: string;
|
|
66
|
+
};
|
|
67
|
+
/** Remove a workflow from a specific agent version home. */
|
|
68
|
+
export declare function removeWorkflowFromVersion(agent: AgentId, version: string, name: string): {
|
|
69
|
+
success: boolean;
|
|
70
|
+
error?: string;
|
|
71
|
+
};
|
|
72
|
+
/** Iterate all installed (agent, version) pairs that support workflows. */
|
|
73
|
+
export declare function iterWorkflowsCapableVersions(filter?: {
|
|
74
|
+
agent?: AgentId;
|
|
75
|
+
version?: string;
|
|
76
|
+
}): Array<{
|
|
77
|
+
agent: AgentId;
|
|
78
|
+
version: string;
|
|
79
|
+
}>;
|