@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/pty-server.js
CHANGED
|
@@ -14,7 +14,7 @@ import * as path from 'path';
|
|
|
14
14
|
import * as crypto from 'crypto';
|
|
15
15
|
import { execFileSync } from 'child_process';
|
|
16
16
|
import { fileURLToPath } from 'url';
|
|
17
|
-
import {
|
|
17
|
+
import { getPtyDir as getPtyDirRoot } from './state.js';
|
|
18
18
|
/**
|
|
19
19
|
* Capture a stable identifier for a process at the moment it was started.
|
|
20
20
|
* Used to defeat PID reuse: a kill(pid, ...) is only safe when the process
|
|
@@ -53,7 +53,6 @@ export function captureProcessStartTime(pid) {
|
|
|
53
53
|
}
|
|
54
54
|
// --- Constants ---
|
|
55
55
|
const SENTINEL = '__AGENTS_PTY_DONE__';
|
|
56
|
-
const PTY_DIR = 'helpers/pty';
|
|
57
56
|
const SOCKET_NAME = 'pty.sock';
|
|
58
57
|
const PID_FILE = 'pty.pid';
|
|
59
58
|
const LOG_FILE = 'logs.jsonl';
|
|
@@ -84,7 +83,7 @@ function buildPtyEnv() {
|
|
|
84
83
|
}
|
|
85
84
|
/** Get the PTY helper directory, creating it if needed. */
|
|
86
85
|
function getPtyDir() {
|
|
87
|
-
const dir =
|
|
86
|
+
const dir = getPtyDirRoot();
|
|
88
87
|
fs.mkdirSync(dir, { recursive: true });
|
|
89
88
|
return dir;
|
|
90
89
|
}
|
|
@@ -178,7 +177,7 @@ export async function runPtyServer() {
|
|
|
178
177
|
}
|
|
179
178
|
catch (err) {
|
|
180
179
|
console.error('node-pty is required for PTY support.');
|
|
181
|
-
console.error('Install: cd ' +
|
|
180
|
+
console.error('Install: cd ' + '~/agents-cli && bun add node-pty');
|
|
182
181
|
process.exit(1);
|
|
183
182
|
}
|
|
184
183
|
try {
|
|
@@ -188,7 +187,7 @@ export async function runPtyServer() {
|
|
|
188
187
|
}
|
|
189
188
|
catch {
|
|
190
189
|
console.error('@xterm/headless is required for PTY support.');
|
|
191
|
-
console.error('Install: cd ' +
|
|
190
|
+
console.error('Install: cd ' + '~/agents-cli && bun add @xterm/headless');
|
|
192
191
|
process.exit(1);
|
|
193
192
|
}
|
|
194
193
|
const sessions = new Map();
|
|
@@ -493,11 +492,11 @@ export async function runPtyServer() {
|
|
|
493
492
|
});
|
|
494
493
|
conn.on('error', () => { });
|
|
495
494
|
});
|
|
496
|
-
// Lock down
|
|
497
|
-
// user with execute on the parent dir could connect to the socket
|
|
498
|
-
// the listen()-to-chmod() window. macOS BSD AF_UNIX semantics make
|
|
499
|
-
// mode advisory only, so the parent dir is the real boundary.
|
|
500
|
-
const agentsDir =
|
|
495
|
+
// Lock down the PTY scratch dir before opening the socket — without this,
|
|
496
|
+
// any local user with execute on the parent dir could connect to the socket
|
|
497
|
+
// during the listen()-to-chmod() window. macOS BSD AF_UNIX semantics make
|
|
498
|
+
// socket mode advisory only, so the parent dir is the real boundary.
|
|
499
|
+
const agentsDir = getPtyDirRoot();
|
|
501
500
|
fs.mkdirSync(agentsDir, { recursive: true });
|
|
502
501
|
fs.chmodSync(agentsDir, 0o700);
|
|
503
502
|
// umask covers any inherited group/other bits while listen() is creating
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource selection patterns for agents.yaml versions: entries.
|
|
3
|
+
*
|
|
4
|
+
* Pattern syntax: [!]source:name
|
|
5
|
+
* "system:*" — all resources from ~/.agents-system/
|
|
6
|
+
* "user:*" — all resources from ~/.agents/
|
|
7
|
+
* "rush:*" — all resources from ~/.agents-rush/ (extra repo alias)
|
|
8
|
+
* "project:*" — all resources from .agents/ in the project root
|
|
9
|
+
* "user:foo" — specifically the resource named "foo" from ~/.agents/
|
|
10
|
+
* "!user:temp" — exclude "temp" from the user repo
|
|
11
|
+
*
|
|
12
|
+
* Evaluation rule: union all inclusions, then subtract all exclusions.
|
|
13
|
+
*/
|
|
14
|
+
export interface ParsedPattern {
|
|
15
|
+
negate: boolean;
|
|
16
|
+
source: string;
|
|
17
|
+
name: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function parsePattern(p: string): ParsedPattern;
|
|
20
|
+
/** Returns true if the string is a legacy plain name with no source: prefix. */
|
|
21
|
+
export declare function isLegacyName(p: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Expand a list of patterns against an available name→source map.
|
|
24
|
+
* Returns the union of matching names with exclusions subtracted.
|
|
25
|
+
*
|
|
26
|
+
* Supports comma-grouped names to avoid repeating the source prefix:
|
|
27
|
+
* "system:brain-scan,mq" → includes brain-scan and mq from system
|
|
28
|
+
* "!user:temp,draft" → excludes temp and draft from user
|
|
29
|
+
*
|
|
30
|
+
* Note: in YAML flow sequences ([...]) a comma inside a pattern requires
|
|
31
|
+
* quoting ("system:brain-scan,mq"). Block-style items and yaml.stringify
|
|
32
|
+
* output handle this automatically.
|
|
33
|
+
*/
|
|
34
|
+
export declare function expandPatterns(patterns: string[], available: Map<string, string>): string[];
|
|
35
|
+
/**
|
|
36
|
+
* Build the default pattern list for a resource type.
|
|
37
|
+
* Order: system → user → alias1 → alias2 → ... → project (base-to-override).
|
|
38
|
+
* @param extraAliases Alias names of enabled extra repos, in insertion order.
|
|
39
|
+
* @param includeProject Whether to append "project:*". False for hooks (security).
|
|
40
|
+
*/
|
|
41
|
+
export declare function defaultPatterns(extraAliases?: string[], includeProject?: boolean): string[];
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource selection patterns for agents.yaml versions: entries.
|
|
3
|
+
*
|
|
4
|
+
* Pattern syntax: [!]source:name
|
|
5
|
+
* "system:*" — all resources from ~/.agents-system/
|
|
6
|
+
* "user:*" — all resources from ~/.agents/
|
|
7
|
+
* "rush:*" — all resources from ~/.agents-rush/ (extra repo alias)
|
|
8
|
+
* "project:*" — all resources from .agents/ in the project root
|
|
9
|
+
* "user:foo" — specifically the resource named "foo" from ~/.agents/
|
|
10
|
+
* "!user:temp" — exclude "temp" from the user repo
|
|
11
|
+
*
|
|
12
|
+
* Evaluation rule: union all inclusions, then subtract all exclusions.
|
|
13
|
+
*/
|
|
14
|
+
export function parsePattern(p) {
|
|
15
|
+
const negate = p.startsWith('!');
|
|
16
|
+
const raw = negate ? p.slice(1) : p;
|
|
17
|
+
const colon = raw.indexOf(':');
|
|
18
|
+
if (colon === -1) {
|
|
19
|
+
throw new Error(`Invalid resource pattern "${p}": expected "source:name" format`);
|
|
20
|
+
}
|
|
21
|
+
return { negate, source: raw.slice(0, colon), name: raw.slice(colon + 1) };
|
|
22
|
+
}
|
|
23
|
+
/** Returns true if the string is a legacy plain name with no source: prefix. */
|
|
24
|
+
export function isLegacyName(p) {
|
|
25
|
+
return !p.startsWith('!') && !p.includes(':');
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Expand a list of patterns against an available name→source map.
|
|
29
|
+
* Returns the union of matching names with exclusions subtracted.
|
|
30
|
+
*
|
|
31
|
+
* Supports comma-grouped names to avoid repeating the source prefix:
|
|
32
|
+
* "system:brain-scan,mq" → includes brain-scan and mq from system
|
|
33
|
+
* "!user:temp,draft" → excludes temp and draft from user
|
|
34
|
+
*
|
|
35
|
+
* Note: in YAML flow sequences ([...]) a comma inside a pattern requires
|
|
36
|
+
* quoting ("system:brain-scan,mq"). Block-style items and yaml.stringify
|
|
37
|
+
* output handle this automatically.
|
|
38
|
+
*/
|
|
39
|
+
export function expandPatterns(patterns, available) {
|
|
40
|
+
const included = new Set();
|
|
41
|
+
const excluded = new Set();
|
|
42
|
+
for (const p of patterns) {
|
|
43
|
+
try {
|
|
44
|
+
const { negate, source, name } = parsePattern(p);
|
|
45
|
+
const target = negate ? excluded : included;
|
|
46
|
+
// Comma-grouped names: "system:brain-scan,mq" → ['brain-scan', 'mq']
|
|
47
|
+
const names = name === '*' ? ['*'] : name.split(',').map(n => n.trim()).filter(Boolean);
|
|
48
|
+
for (const n of names) {
|
|
49
|
+
if (n === '*') {
|
|
50
|
+
for (const [rn, rs] of available) {
|
|
51
|
+
if (rs === source)
|
|
52
|
+
target.add(rn);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
if (available.has(n))
|
|
57
|
+
target.add(n);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Skip malformed patterns
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return [...included].filter(n => !excluded.has(n));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Build the default pattern list for a resource type.
|
|
69
|
+
* Order: system → user → alias1 → alias2 → ... → project (base-to-override).
|
|
70
|
+
* @param extraAliases Alias names of enabled extra repos, in insertion order.
|
|
71
|
+
* @param includeProject Whether to append "project:*". False for hooks (security).
|
|
72
|
+
*/
|
|
73
|
+
export function defaultPatterns(extraAliases = [], includeProject = true) {
|
|
74
|
+
const patterns = ['system:*', 'user:*'];
|
|
75
|
+
for (const alias of extraAliases) {
|
|
76
|
+
patterns.push(`${alias}:*`);
|
|
77
|
+
}
|
|
78
|
+
if (includeProject) {
|
|
79
|
+
patterns.push('project:*');
|
|
80
|
+
}
|
|
81
|
+
return patterns;
|
|
82
|
+
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HooksHandler - ResourceHandler implementation for hooks.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Hook declarations live in:
|
|
5
|
+
* - System: ~/.agents-system/hooks.yaml (npm-shipped defaults)
|
|
6
|
+
* - User: `hooks:` section of ~/.agents/agents.yaml
|
|
7
|
+
* - Project: <project>/.agents/hooks.yaml
|
|
8
|
+
*
|
|
5
9
|
* Resolution: project > user > system (higher layer wins on name conflict).
|
|
6
10
|
* Non-conflicting hooks from all layers are unioned together.
|
|
7
11
|
*/
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HooksHandler - ResourceHandler implementation for hooks.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Hook declarations live in:
|
|
5
|
+
* - System: ~/.agents-system/hooks.yaml (npm-shipped defaults)
|
|
6
|
+
* - User: `hooks:` section of ~/.agents/agents.yaml
|
|
7
|
+
* - Project: <project>/.agents/hooks.yaml
|
|
8
|
+
*
|
|
5
9
|
* Resolution: project > user > system (higher layer wins on name conflict).
|
|
6
10
|
* Non-conflicting hooks from all layers are unioned together.
|
|
7
11
|
*/
|
|
@@ -10,13 +14,20 @@ import * as path from 'path';
|
|
|
10
14
|
import * as yaml from 'yaml';
|
|
11
15
|
import { getSystemAgentsDir, getUserAgentsDir, getProjectAgentsDir, } from '../state.js';
|
|
12
16
|
/**
|
|
13
|
-
* Get the
|
|
17
|
+
* Get the hook manifest path for a layer dir. The user layer reads from
|
|
18
|
+
* agents.yaml (hooks: section) since that's where user hooks now live.
|
|
19
|
+
* Other layers continue to use a standalone hooks.yaml.
|
|
14
20
|
*/
|
|
15
21
|
function getHooksYamlPath(layerDir) {
|
|
22
|
+
if (layerDir === getUserAgentsDir()) {
|
|
23
|
+
return path.join(layerDir, 'agents.yaml');
|
|
24
|
+
}
|
|
16
25
|
return path.join(layerDir, 'hooks.yaml');
|
|
17
26
|
}
|
|
18
27
|
/**
|
|
19
|
-
* Parse hooks
|
|
28
|
+
* Parse hooks for a layer directory.
|
|
29
|
+
* - User layer: read `hooks:` section from agents.yaml.
|
|
30
|
+
* - System / project layer: read top-level map from hooks.yaml.
|
|
20
31
|
* Returns empty object if file doesn't exist or is invalid.
|
|
21
32
|
*/
|
|
22
33
|
function parseHooksYaml(dir) {
|
|
@@ -27,7 +38,13 @@ function parseHooksYaml(dir) {
|
|
|
27
38
|
try {
|
|
28
39
|
const content = fs.readFileSync(manifestPath, 'utf-8');
|
|
29
40
|
const parsed = yaml.parse(content);
|
|
30
|
-
|
|
41
|
+
if (!parsed)
|
|
42
|
+
return {};
|
|
43
|
+
if (dir === getUserAgentsDir()) {
|
|
44
|
+
const hooks = parsed.hooks;
|
|
45
|
+
return (hooks && typeof hooks === 'object') ? hooks : {};
|
|
46
|
+
}
|
|
47
|
+
return parsed;
|
|
31
48
|
}
|
|
32
49
|
catch {
|
|
33
50
|
return {};
|
|
@@ -13,6 +13,7 @@ export { RulesHandler, type RuleItem } from './rules.js';
|
|
|
13
13
|
export { McpHandler, getMcpConfigPath, type McpItem } from './mcp.js';
|
|
14
14
|
export { PermissionsHandler, type PermissionItem } from './permissions.js';
|
|
15
15
|
export { SubagentsHandler, subagentsHandler, type SubagentItem } from './subagents.js';
|
|
16
|
+
export { WorkflowsHandler, type WorkflowItem } from './workflows.js';
|
|
16
17
|
import type { ResourceKind, ResourceHandler } from './types.js';
|
|
17
18
|
/** All resource handlers keyed by kind. */
|
|
18
19
|
export declare const handlers: {
|
|
@@ -29,6 +30,22 @@ export declare const handlers: {
|
|
|
29
30
|
readonly permissions: ResourceHandler<import("../types.js").PermissionSet>;
|
|
30
31
|
readonly subagent: import("./subagents.js").SubagentsHandler;
|
|
31
32
|
readonly subagents: import("./subagents.js").SubagentsHandler;
|
|
33
|
+
readonly workflow: {
|
|
34
|
+
readonly kind: "workflow";
|
|
35
|
+
listAll(_agent: import("./types.js").AgentId, cwd?: string): import("./types.js").ResolvedItem<import("./workflows.js").WorkflowItem>[];
|
|
36
|
+
resolve(_agent: import("./types.js").AgentId, name: string, cwd?: string): import("./types.js").ResolvedItem<import("./workflows.js").WorkflowItem> | null;
|
|
37
|
+
sync(_agent: import("./types.js").AgentId, _versionHome: string, _cwd?: string): void;
|
|
38
|
+
format(_agent: import("./types.js").AgentId): "md";
|
|
39
|
+
targetDir(_agent: import("./types.js").AgentId): string;
|
|
40
|
+
};
|
|
41
|
+
readonly workflows: {
|
|
42
|
+
readonly kind: "workflow";
|
|
43
|
+
listAll(_agent: import("./types.js").AgentId, cwd?: string): import("./types.js").ResolvedItem<import("./workflows.js").WorkflowItem>[];
|
|
44
|
+
resolve(_agent: import("./types.js").AgentId, name: string, cwd?: string): import("./types.js").ResolvedItem<import("./workflows.js").WorkflowItem> | null;
|
|
45
|
+
sync(_agent: import("./types.js").AgentId, _versionHome: string, _cwd?: string): void;
|
|
46
|
+
format(_agent: import("./types.js").AgentId): "md";
|
|
47
|
+
targetDir(_agent: import("./types.js").AgentId): string;
|
|
48
|
+
};
|
|
32
49
|
};
|
|
33
50
|
/** Get a handler by resource kind. */
|
|
34
51
|
export declare function getHandler(kind: ResourceKind): ResourceHandler<unknown> | null;
|
|
@@ -13,6 +13,7 @@ export { RulesHandler } from './rules.js';
|
|
|
13
13
|
export { McpHandler, getMcpConfigPath } from './mcp.js';
|
|
14
14
|
export { PermissionsHandler } from './permissions.js';
|
|
15
15
|
export { SubagentsHandler, subagentsHandler } from './subagents.js';
|
|
16
|
+
export { WorkflowsHandler } from './workflows.js';
|
|
16
17
|
import { commandsHandler } from './commands.js';
|
|
17
18
|
import { HooksHandler } from './hooks.js';
|
|
18
19
|
import { SkillsHandler } from './skills.js';
|
|
@@ -20,6 +21,7 @@ import { RulesHandler } from './rules.js';
|
|
|
20
21
|
import { McpHandler } from './mcp.js';
|
|
21
22
|
import { PermissionsHandler } from './permissions.js';
|
|
22
23
|
import { subagentsHandler } from './subagents.js';
|
|
24
|
+
import { WorkflowsHandler } from './workflows.js';
|
|
23
25
|
/** All resource handlers keyed by kind. */
|
|
24
26
|
export const handlers = {
|
|
25
27
|
command: commandsHandler,
|
|
@@ -35,6 +37,8 @@ export const handlers = {
|
|
|
35
37
|
permissions: PermissionsHandler,
|
|
36
38
|
subagent: subagentsHandler,
|
|
37
39
|
subagents: subagentsHandler,
|
|
40
|
+
workflow: WorkflowsHandler,
|
|
41
|
+
workflows: WorkflowsHandler,
|
|
38
42
|
};
|
|
39
43
|
/** Get a handler by resource kind. */
|
|
40
44
|
export function getHandler(kind) {
|
|
@@ -53,6 +57,8 @@ export function getHandler(kind) {
|
|
|
53
57
|
return PermissionsHandler;
|
|
54
58
|
case 'subagent':
|
|
55
59
|
return subagentsHandler;
|
|
60
|
+
case 'workflow':
|
|
61
|
+
return WorkflowsHandler;
|
|
56
62
|
default:
|
|
57
63
|
return null;
|
|
58
64
|
}
|
|
@@ -66,4 +72,5 @@ export const RESOURCE_KINDS = [
|
|
|
66
72
|
'mcp',
|
|
67
73
|
'permission',
|
|
68
74
|
'subagent',
|
|
75
|
+
'workflow',
|
|
69
76
|
];
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw';
|
|
9
9
|
export type Layer = 'system' | 'user' | 'project';
|
|
10
|
-
export type ResourceKind = 'command' | 'hook' | 'skill' | 'rule' | 'mcp' | 'permission' | 'subagent';
|
|
10
|
+
export type ResourceKind = 'command' | 'hook' | 'skill' | 'rule' | 'mcp' | 'permission' | 'subagent' | 'workflow';
|
|
11
11
|
/** A resolved resource with its origin layer. */
|
|
12
12
|
export interface ResolvedItem<T> {
|
|
13
13
|
name: string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflows resource handler.
|
|
3
|
+
*
|
|
4
|
+
* Workflows are directory bundles with a WORKFLOW.md containing YAML frontmatter.
|
|
5
|
+
* They optionally contain subagents/, skills/, and plugins/ subdirectories.
|
|
6
|
+
* Resolution order: project > user > system.
|
|
7
|
+
*/
|
|
8
|
+
import type { AgentId, ResolvedItem, ResourceHandler } from './types.js';
|
|
9
|
+
export interface WorkflowItem {
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
model?: string;
|
|
13
|
+
subagentCount: number;
|
|
14
|
+
}
|
|
15
|
+
declare class WorkflowsHandlerImpl implements ResourceHandler<WorkflowItem> {
|
|
16
|
+
readonly kind: "workflow";
|
|
17
|
+
listAll(_agent: AgentId, cwd?: string): ResolvedItem<WorkflowItem>[];
|
|
18
|
+
resolve(_agent: AgentId, name: string, cwd?: string): ResolvedItem<WorkflowItem> | null;
|
|
19
|
+
sync(_agent: AgentId, _versionHome: string, _cwd?: string): void;
|
|
20
|
+
format(_agent: AgentId): 'md';
|
|
21
|
+
targetDir(_agent: AgentId): string;
|
|
22
|
+
}
|
|
23
|
+
export declare const WorkflowsHandler: WorkflowsHandlerImpl;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflows resource handler.
|
|
3
|
+
*
|
|
4
|
+
* Workflows are directory bundles with a WORKFLOW.md containing YAML frontmatter.
|
|
5
|
+
* They optionally contain subagents/, skills/, and plugins/ subdirectories.
|
|
6
|
+
* Resolution order: project > user > system.
|
|
7
|
+
*/
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import { getProjectAgentsDir, getUserWorkflowsDir, getSystemWorkflowsDir, getEnabledExtraRepos, } from '../state.js';
|
|
11
|
+
import { parseWorkflowFrontmatter, countWorkflowSubagents } from '../workflows.js';
|
|
12
|
+
function getLayerDirs(cwd) {
|
|
13
|
+
const projectDir = getProjectAgentsDir(cwd);
|
|
14
|
+
const extraRepos = getEnabledExtraRepos();
|
|
15
|
+
return {
|
|
16
|
+
system: getSystemWorkflowsDir(),
|
|
17
|
+
user: getUserWorkflowsDir(),
|
|
18
|
+
project: projectDir ? path.join(projectDir, 'workflows') : null,
|
|
19
|
+
extra: extraRepos.map(e => path.join(e.dir, 'workflows')),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function listWorkflowsInDir(dir) {
|
|
23
|
+
if (!fs.existsSync(dir))
|
|
24
|
+
return [];
|
|
25
|
+
try {
|
|
26
|
+
return fs.readdirSync(dir, { withFileTypes: true })
|
|
27
|
+
.filter(e => e.isDirectory() && !e.name.startsWith('.') &&
|
|
28
|
+
fs.existsSync(path.join(dir, e.name, 'WORKFLOW.md')))
|
|
29
|
+
.map(e => ({ name: e.name, path: path.join(dir, e.name) }));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
class WorkflowsHandlerImpl {
|
|
36
|
+
kind = 'workflow';
|
|
37
|
+
listAll(_agent, cwd) {
|
|
38
|
+
const dirs = getLayerDirs(cwd);
|
|
39
|
+
const seen = new Set();
|
|
40
|
+
const results = [];
|
|
41
|
+
const layerDirs = [];
|
|
42
|
+
if (dirs.project)
|
|
43
|
+
layerDirs.push({ dir: dirs.project, layer: 'project' });
|
|
44
|
+
layerDirs.push({ dir: dirs.user, layer: 'user' });
|
|
45
|
+
layerDirs.push({ dir: dirs.system, layer: 'system' });
|
|
46
|
+
for (const extraDir of dirs.extra)
|
|
47
|
+
layerDirs.push({ dir: extraDir, layer: 'system' });
|
|
48
|
+
for (const { dir, layer } of layerDirs) {
|
|
49
|
+
for (const { name, path: workflowPath } of listWorkflowsInDir(dir)) {
|
|
50
|
+
if (seen.has(name))
|
|
51
|
+
continue;
|
|
52
|
+
const fm = parseWorkflowFrontmatter(workflowPath);
|
|
53
|
+
if (!fm)
|
|
54
|
+
continue;
|
|
55
|
+
seen.add(name);
|
|
56
|
+
results.push({
|
|
57
|
+
name,
|
|
58
|
+
item: {
|
|
59
|
+
name: fm.name || name,
|
|
60
|
+
description: fm.description,
|
|
61
|
+
model: fm.model,
|
|
62
|
+
subagentCount: countWorkflowSubagents(workflowPath),
|
|
63
|
+
},
|
|
64
|
+
layer,
|
|
65
|
+
path: workflowPath,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return results.sort((a, b) => a.name.localeCompare(b.name));
|
|
70
|
+
}
|
|
71
|
+
resolve(_agent, name, cwd) {
|
|
72
|
+
const dirs = getLayerDirs(cwd);
|
|
73
|
+
const searchDirs = [];
|
|
74
|
+
if (dirs.project)
|
|
75
|
+
searchDirs.push({ dir: dirs.project, layer: 'project' });
|
|
76
|
+
searchDirs.push({ dir: dirs.user, layer: 'user' });
|
|
77
|
+
searchDirs.push({ dir: dirs.system, layer: 'system' });
|
|
78
|
+
for (const extraDir of dirs.extra)
|
|
79
|
+
searchDirs.push({ dir: extraDir, layer: 'system' });
|
|
80
|
+
for (const { dir, layer } of searchDirs) {
|
|
81
|
+
const workflowPath = path.join(dir, name);
|
|
82
|
+
const fm = parseWorkflowFrontmatter(workflowPath);
|
|
83
|
+
if (fm) {
|
|
84
|
+
return {
|
|
85
|
+
name,
|
|
86
|
+
item: {
|
|
87
|
+
name: fm.name || name,
|
|
88
|
+
description: fm.description,
|
|
89
|
+
model: fm.model,
|
|
90
|
+
subagentCount: countWorkflowSubagents(workflowPath),
|
|
91
|
+
},
|
|
92
|
+
layer,
|
|
93
|
+
path: workflowPath,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
sync(_agent, _versionHome, _cwd) {
|
|
100
|
+
// Version-home copies are written by syncResourcesToVersion in versions.ts.
|
|
101
|
+
// exec.ts resolves workflows at run time from source dirs directly.
|
|
102
|
+
}
|
|
103
|
+
format(_agent) {
|
|
104
|
+
return 'md';
|
|
105
|
+
}
|
|
106
|
+
targetDir(_agent) {
|
|
107
|
+
return 'workflows';
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export const WorkflowsHandler = new WorkflowsHandlerImpl();
|
package/dist/lib/resources.d.ts
CHANGED
|
@@ -11,7 +11,11 @@ export interface ResolvedResource {
|
|
|
11
11
|
name: string;
|
|
12
12
|
/** Absolute path to the resource file or directory. */
|
|
13
13
|
path: string;
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Source layer: 'project' | 'user' | 'system' for built-in layers,
|
|
16
|
+
* or the alias name (e.g. 'rush') for extra repos registered in agents.yaml.
|
|
17
|
+
*/
|
|
18
|
+
source: string;
|
|
15
19
|
}
|
|
16
20
|
/**
|
|
17
21
|
* Resolve a single resource by kind + name using project > user > system precedence.
|
|
@@ -52,6 +56,7 @@ export interface AgentResources {
|
|
|
52
56
|
mcp: McpResourceEntry[];
|
|
53
57
|
memory: ResourceEntry[];
|
|
54
58
|
hooks: ResourceEntry[];
|
|
59
|
+
workflows: ResourceEntry[];
|
|
55
60
|
}
|
|
56
61
|
/** Options for resource discovery. */
|
|
57
62
|
export interface GetAgentResourcesOptions {
|
package/dist/lib/resources.js
CHANGED
|
@@ -11,6 +11,8 @@ import { listInstalledHooksWithScope } from './hooks.js';
|
|
|
11
11
|
import { listInstalledInstructionsWithScope } from './rules/rules.js';
|
|
12
12
|
import { getEffectiveHome } from './versions.js';
|
|
13
13
|
import { listMcpServerConfigs } from './mcp.js';
|
|
14
|
+
import { WorkflowsHandler } from './resources/workflows.js';
|
|
15
|
+
import { WORKFLOW_CAPABLE_AGENTS } from './workflows.js';
|
|
14
16
|
import { getProjectAgentsDir, getUserAgentsDir, getSystemAgentsDir, getEnabledExtraRepos, } from './state.js';
|
|
15
17
|
/**
|
|
16
18
|
* Resolve a single resource by kind + name using project > user > system precedence.
|
|
@@ -26,7 +28,7 @@ export function resolveResource(kind, name, cwd) {
|
|
|
26
28
|
...(projectDir ? [[path.join(projectDir, kind), 'project']] : []),
|
|
27
29
|
[path.join(getUserAgentsDir(), kind), 'user'],
|
|
28
30
|
[path.join(getSystemAgentsDir(), kind), 'system'],
|
|
29
|
-
...extraRepos.map((e) => [path.join(e.dir, kind),
|
|
31
|
+
...extraRepos.map((e) => [path.join(e.dir, kind), e.alias]),
|
|
30
32
|
];
|
|
31
33
|
for (const [dir, source] of candidates) {
|
|
32
34
|
if (!fs.existsSync(dir))
|
|
@@ -60,7 +62,7 @@ export function listResources(kind, cwd) {
|
|
|
60
62
|
...(projectDir ? [[path.join(projectDir, kind), 'project']] : []),
|
|
61
63
|
[path.join(getUserAgentsDir(), kind), 'user'],
|
|
62
64
|
[path.join(getSystemAgentsDir(), kind), 'system'],
|
|
63
|
-
...extraRepos.map((e) => [path.join(e.dir, kind),
|
|
65
|
+
...extraRepos.map((e) => [path.join(e.dir, kind), e.alias]),
|
|
64
66
|
];
|
|
65
67
|
for (const [dir, source] of roots) {
|
|
66
68
|
if (!fs.existsSync(dir))
|
|
@@ -159,6 +161,13 @@ export function getAgentResources(agentId, options = {}) {
|
|
|
159
161
|
hooks.push({ name: hook.name, path: hook.path, scope: hook.scope });
|
|
160
162
|
}
|
|
161
163
|
}
|
|
164
|
+
// Workflows (claude only)
|
|
165
|
+
const workflows = [];
|
|
166
|
+
if (WORKFLOW_CAPABLE_AGENTS.includes(agentId)) {
|
|
167
|
+
for (const w of WorkflowsHandler.listAll(agentId, cwd)) {
|
|
168
|
+
workflows.push({ name: w.name, path: w.path, scope: w.layer === 'project' ? 'project' : 'user' });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
162
171
|
return {
|
|
163
172
|
agentId,
|
|
164
173
|
commands,
|
|
@@ -167,6 +176,7 @@ export function getAgentResources(agentId, options = {}) {
|
|
|
167
176
|
mcp,
|
|
168
177
|
memory,
|
|
169
178
|
hooks,
|
|
179
|
+
workflows,
|
|
170
180
|
};
|
|
171
181
|
}
|
|
172
182
|
/**
|
package/dist/lib/rotate.js
CHANGED
|
@@ -8,12 +8,11 @@ import * as fs from 'fs';
|
|
|
8
8
|
import * as path from 'path';
|
|
9
9
|
import * as yaml from 'yaml';
|
|
10
10
|
import { getAccountInfo } from './agents.js';
|
|
11
|
-
import { readMeta, writeMeta,
|
|
11
|
+
import { readMeta, writeMeta, getHelpersDir, getUserAgentsDir } from './state.js';
|
|
12
12
|
import { listInstalledVersions, getVersionHomePath, resolveVersion } from './versions.js';
|
|
13
13
|
import { getUsageInfoByIdentity, getUsageLookupKey, isClaudeAuthValid, } from './usage.js';
|
|
14
|
-
const ROTATE_DIR = 'helpers/rotate';
|
|
15
14
|
function getRotateDir() {
|
|
16
|
-
const dir = path.join(
|
|
15
|
+
const dir = path.join(getHelpersDir(), 'rotate');
|
|
17
16
|
fs.mkdirSync(dir, { recursive: true });
|
|
18
17
|
return dir;
|
|
19
18
|
}
|
|
@@ -35,7 +34,7 @@ export function normalizeRunStrategy(value) {
|
|
|
35
34
|
/** Read project-local run strategy from the nearest agents.yaml, if present. */
|
|
36
35
|
export function getProjectRunStrategy(agent, startPath) {
|
|
37
36
|
let dir = path.resolve(startPath);
|
|
38
|
-
const userAgentsYaml = path.join(
|
|
37
|
+
const userAgentsYaml = path.join(getUserAgentsDir(), 'agents.yaml');
|
|
39
38
|
while (dir !== path.dirname(dir)) {
|
|
40
39
|
const manifestPath = path.join(dir, 'agents.yaml');
|
|
41
40
|
if (manifestPath !== userAgentsYaml && fs.existsSync(manifestPath)) {
|
|
@@ -8,7 +8,10 @@ export interface ActiveSession {
|
|
|
8
8
|
pid?: number;
|
|
9
9
|
sessionId?: string;
|
|
10
10
|
cwd?: string;
|
|
11
|
+
/** User-given name from /rename command. */
|
|
11
12
|
label?: string;
|
|
13
|
+
/** First meaningful line of the initial prompt (extracted topic). */
|
|
14
|
+
topic?: string;
|
|
12
15
|
sessionFile?: string;
|
|
13
16
|
startedAtMs?: number;
|
|
14
17
|
status: ActiveStatus;
|