@phnx-labs/agents-cli 1.14.3 → 1.14.5
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 +10 -0
- package/dist/browser.d.ts +2 -0
- package/dist/browser.js +7 -0
- package/dist/commands/browser.d.ts +1 -0
- package/dist/commands/browser.js +4 -0
- package/dist/commands/teams.js +25 -3
- package/dist/commands/view.js +1 -1
- package/dist/index.js +0 -0
- package/dist/lib/browser/chrome.d.ts +2 -2
- package/dist/lib/browser/chrome.js +15 -3
- package/dist/lib/browser/drivers/local.js +1 -1
- package/dist/lib/browser/drivers/ssh.js +14 -5
- package/dist/lib/browser/service.js +24 -3
- package/dist/lib/browser/types.d.ts +3 -1
- package/dist/lib/migrate.js +46 -0
- package/dist/lib/resources/commands.d.ts +46 -0
- package/dist/lib/resources/commands.js +208 -0
- package/dist/lib/resources/hooks.d.ts +12 -0
- package/dist/lib/resources/hooks.js +136 -0
- package/dist/lib/resources/index.d.ts +36 -0
- package/dist/lib/resources/index.js +69 -0
- package/dist/lib/resources/mcp.d.ts +34 -0
- package/dist/lib/resources/mcp.js +483 -0
- package/dist/lib/resources/permissions.d.ts +13 -0
- package/dist/lib/resources/permissions.js +184 -0
- package/dist/lib/resources/rules.d.ts +43 -0
- package/dist/lib/resources/rules.js +146 -0
- package/dist/lib/resources/skills.d.ts +37 -0
- package/dist/lib/resources/skills.js +238 -0
- package/dist/lib/resources/subagents.d.ts +46 -0
- package/dist/lib/resources/subagents.js +198 -0
- package/dist/lib/resources/types.d.ts +82 -0
- package/dist/lib/resources/types.js +8 -0
- package/dist/lib/state.js +3 -5
- package/dist/lib/teams/registry.d.ts +4 -0
- package/dist/lib/teams/registry.js +4 -0
- package/dist/lib/versions.d.ts +2 -1
- package/dist/lib/versions.js +20 -14
- package/package.json +3 -2
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subagents resource handler.
|
|
3
|
+
*
|
|
4
|
+
* Subagents are YAML files stored in subagents/ directories across layers.
|
|
5
|
+
* Format is the same for all agents. Resolution order: project > user > system.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import * as yaml from 'yaml';
|
|
10
|
+
import { getProjectAgentsDir, getUserSubagentsDir, getSystemSubagentsDir, getEnabledExtraRepos, } from '../state.js';
|
|
11
|
+
/** Get layer directories for subagent resolution. */
|
|
12
|
+
function getLayerDirs(cwd) {
|
|
13
|
+
const projectDir = getProjectAgentsDir(cwd);
|
|
14
|
+
const extraRepos = getEnabledExtraRepos();
|
|
15
|
+
return {
|
|
16
|
+
system: path.join(getSystemSubagentsDir()),
|
|
17
|
+
user: getUserSubagentsDir(),
|
|
18
|
+
project: projectDir ? path.join(projectDir, 'subagents') : null,
|
|
19
|
+
extra: extraRepos.map((e) => path.join(e.dir, 'subagents')),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Map source directory to layer name. */
|
|
23
|
+
function dirToLayer(dir, dirs) {
|
|
24
|
+
if (dirs.project && dir.startsWith(dirs.project))
|
|
25
|
+
return 'project';
|
|
26
|
+
if (dir.startsWith(dirs.user))
|
|
27
|
+
return 'user';
|
|
28
|
+
return 'system';
|
|
29
|
+
}
|
|
30
|
+
/** Parse a subagent YAML file and return parsed item. */
|
|
31
|
+
function parseSubagentYaml(filePath) {
|
|
32
|
+
if (!fs.existsSync(filePath)) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
37
|
+
const parsed = yaml.parse(content);
|
|
38
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
name: parsed.name || '',
|
|
43
|
+
description: parsed.description || '',
|
|
44
|
+
model: parsed.model,
|
|
45
|
+
color: parsed.color,
|
|
46
|
+
config: parsed.config,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Extract name from filename (removes .yaml/.yml extension). */
|
|
54
|
+
function nameFromFile(filename) {
|
|
55
|
+
return filename.replace(/\.ya?ml$/, '');
|
|
56
|
+
}
|
|
57
|
+
export class SubagentsHandler {
|
|
58
|
+
kind = 'subagent';
|
|
59
|
+
/**
|
|
60
|
+
* List all subagents across layers, with higher layer winning on name conflict.
|
|
61
|
+
* Returns a union of all subagents, deduplicated by name.
|
|
62
|
+
*/
|
|
63
|
+
listAll(_agent, cwd) {
|
|
64
|
+
const dirs = getLayerDirs(cwd);
|
|
65
|
+
const seen = new Set();
|
|
66
|
+
const results = [];
|
|
67
|
+
// Order: project > user > system > extra (extra comes last after system)
|
|
68
|
+
const layerDirs = [];
|
|
69
|
+
if (dirs.project && fs.existsSync(dirs.project)) {
|
|
70
|
+
layerDirs.push({ dir: dirs.project, layer: 'project' });
|
|
71
|
+
}
|
|
72
|
+
if (fs.existsSync(dirs.user)) {
|
|
73
|
+
layerDirs.push({ dir: dirs.user, layer: 'user' });
|
|
74
|
+
}
|
|
75
|
+
if (fs.existsSync(dirs.system)) {
|
|
76
|
+
layerDirs.push({ dir: dirs.system, layer: 'system' });
|
|
77
|
+
}
|
|
78
|
+
for (const extraDir of dirs.extra) {
|
|
79
|
+
if (fs.existsSync(extraDir)) {
|
|
80
|
+
layerDirs.push({ dir: extraDir, layer: 'system' });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (const { dir, layer } of layerDirs) {
|
|
84
|
+
let entries;
|
|
85
|
+
try {
|
|
86
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
for (const entry of entries) {
|
|
92
|
+
if (!entry.isFile())
|
|
93
|
+
continue;
|
|
94
|
+
if (!entry.name.endsWith('.yaml') && !entry.name.endsWith('.yml'))
|
|
95
|
+
continue;
|
|
96
|
+
if (entry.name.startsWith('.'))
|
|
97
|
+
continue;
|
|
98
|
+
const name = nameFromFile(entry.name);
|
|
99
|
+
if (seen.has(name))
|
|
100
|
+
continue;
|
|
101
|
+
const filePath = path.join(dir, entry.name);
|
|
102
|
+
const item = parseSubagentYaml(filePath);
|
|
103
|
+
if (!item)
|
|
104
|
+
continue;
|
|
105
|
+
seen.add(name);
|
|
106
|
+
results.push({
|
|
107
|
+
name,
|
|
108
|
+
item,
|
|
109
|
+
layer,
|
|
110
|
+
path: filePath,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return results;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Resolve a single subagent by name.
|
|
118
|
+
* Returns the winning layer's version, or null if not found.
|
|
119
|
+
*/
|
|
120
|
+
resolve(_agent, name, cwd) {
|
|
121
|
+
const dirs = getLayerDirs(cwd);
|
|
122
|
+
// Order: project > user > system > extra
|
|
123
|
+
const searchDirs = [];
|
|
124
|
+
if (dirs.project) {
|
|
125
|
+
searchDirs.push({ dir: dirs.project, layer: 'project' });
|
|
126
|
+
}
|
|
127
|
+
searchDirs.push({ dir: dirs.user, layer: 'user' });
|
|
128
|
+
searchDirs.push({ dir: dirs.system, layer: 'system' });
|
|
129
|
+
for (const extraDir of dirs.extra) {
|
|
130
|
+
searchDirs.push({ dir: extraDir, layer: 'system' });
|
|
131
|
+
}
|
|
132
|
+
for (const { dir, layer } of searchDirs) {
|
|
133
|
+
if (!fs.existsSync(dir))
|
|
134
|
+
continue;
|
|
135
|
+
// Try .yaml first, then .yml
|
|
136
|
+
for (const ext of ['.yaml', '.yml']) {
|
|
137
|
+
const filePath = path.join(dir, name + ext);
|
|
138
|
+
if (fs.existsSync(filePath)) {
|
|
139
|
+
const item = parseSubagentYaml(filePath);
|
|
140
|
+
if (item) {
|
|
141
|
+
return {
|
|
142
|
+
name,
|
|
143
|
+
item,
|
|
144
|
+
layer,
|
|
145
|
+
path: filePath,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Sync resolved subagents to the agent's version home directory.
|
|
155
|
+
* Copies YAML files to the target directory.
|
|
156
|
+
*/
|
|
157
|
+
sync(agent, versionHome, cwd) {
|
|
158
|
+
const targetDir = path.join(versionHome, this.targetDir(agent));
|
|
159
|
+
// Ensure target directory exists
|
|
160
|
+
if (!fs.existsSync(targetDir)) {
|
|
161
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
162
|
+
}
|
|
163
|
+
// Clear existing subagents in target
|
|
164
|
+
try {
|
|
165
|
+
const existing = fs.readdirSync(targetDir);
|
|
166
|
+
for (const file of existing) {
|
|
167
|
+
if (file.endsWith('.yaml') || file.endsWith('.yml')) {
|
|
168
|
+
fs.unlinkSync(path.join(targetDir, file));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// Ignore errors during cleanup
|
|
174
|
+
}
|
|
175
|
+
// Copy all resolved subagents
|
|
176
|
+
const resolved = this.listAll(agent, cwd);
|
|
177
|
+
for (const { name, path: sourcePath } of resolved) {
|
|
178
|
+
const ext = sourcePath.endsWith('.yml') ? '.yml' : '.yaml';
|
|
179
|
+
const targetPath = path.join(targetDir, name + ext);
|
|
180
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Get the file format this resource uses for a given agent.
|
|
185
|
+
* Subagents always use YAML format.
|
|
186
|
+
*/
|
|
187
|
+
format(_agent) {
|
|
188
|
+
return 'yaml';
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Get the target directory name in the agent's version home.
|
|
192
|
+
*/
|
|
193
|
+
targetDir(_agent) {
|
|
194
|
+
return 'subagents';
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/** Singleton instance of the SubagentsHandler. */
|
|
198
|
+
export const subagentsHandler = new SubagentsHandler();
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified resource system types.
|
|
3
|
+
*
|
|
4
|
+
* Resources merge from three layers: system → user → project
|
|
5
|
+
* - Union: All resources from all layers are combined
|
|
6
|
+
* - Override on name conflict: Higher layer wins (project > user > system)
|
|
7
|
+
*/
|
|
8
|
+
export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw';
|
|
9
|
+
export type Layer = 'system' | 'user' | 'project';
|
|
10
|
+
export type ResourceKind = 'command' | 'hook' | 'skill' | 'rule' | 'mcp' | 'permission' | 'subagent';
|
|
11
|
+
/** A resolved resource with its origin layer. */
|
|
12
|
+
export interface ResolvedItem<T> {
|
|
13
|
+
name: string;
|
|
14
|
+
item: T;
|
|
15
|
+
layer: Layer;
|
|
16
|
+
path: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Resource handler interface.
|
|
20
|
+
*
|
|
21
|
+
* Each resource type (commands, hooks, skills, etc.) implements this interface
|
|
22
|
+
* to provide consistent list/resolve/sync behavior across all agent types.
|
|
23
|
+
*/
|
|
24
|
+
export interface ResourceHandler<T> {
|
|
25
|
+
readonly kind: ResourceKind;
|
|
26
|
+
/**
|
|
27
|
+
* List all resources across layers, with higher layer winning on name conflict.
|
|
28
|
+
* Returns a union of all resources, deduplicated by name.
|
|
29
|
+
*/
|
|
30
|
+
listAll(agent: AgentId, cwd?: string): ResolvedItem<T>[];
|
|
31
|
+
/**
|
|
32
|
+
* Resolve a single resource by name.
|
|
33
|
+
* Returns the winning layer's version, or null if not found.
|
|
34
|
+
*/
|
|
35
|
+
resolve(agent: AgentId, name: string, cwd?: string): ResolvedItem<T> | null;
|
|
36
|
+
/**
|
|
37
|
+
* Sync resolved resources to the agent's version home directory.
|
|
38
|
+
* Copies/transforms resources as needed for the agent's expected format.
|
|
39
|
+
*/
|
|
40
|
+
sync(agent: AgentId, versionHome: string, cwd?: string): void;
|
|
41
|
+
/**
|
|
42
|
+
* Get the file format this resource uses for a given agent.
|
|
43
|
+
*/
|
|
44
|
+
format(agent: AgentId): 'md' | 'toml' | 'json' | 'yaml';
|
|
45
|
+
/**
|
|
46
|
+
* Get the target directory name in the agent's version home.
|
|
47
|
+
*/
|
|
48
|
+
targetDir(agent: AgentId): string;
|
|
49
|
+
/**
|
|
50
|
+
* For resources that modify config files (MCP, permissions),
|
|
51
|
+
* return the config file path. Returns null if not applicable.
|
|
52
|
+
*/
|
|
53
|
+
configPath?(agent: AgentId, versionHome: string): string | null;
|
|
54
|
+
/**
|
|
55
|
+
* Compute content hash for a resource item (for change detection).
|
|
56
|
+
* Used by diff() to detect modifications without full content comparison.
|
|
57
|
+
* Optional — handlers that don't implement this fall back to full sync.
|
|
58
|
+
*/
|
|
59
|
+
hash?(item: T): string;
|
|
60
|
+
/**
|
|
61
|
+
* Compare source layers vs synced target to detect drift.
|
|
62
|
+
* Returns list of resources that differ (added, modified, removed).
|
|
63
|
+
* Enables incremental sync and "X resources out of sync" status.
|
|
64
|
+
* Optional — handlers that don't implement this always report "unknown".
|
|
65
|
+
*/
|
|
66
|
+
diff?(agent: AgentId, versionHome: string, cwd?: string): ResourceDiff[];
|
|
67
|
+
}
|
|
68
|
+
/** Result of comparing source vs target for a single resource. */
|
|
69
|
+
export interface ResourceDiff {
|
|
70
|
+
name: string;
|
|
71
|
+
status: 'added' | 'modified' | 'removed';
|
|
72
|
+
sourceLayer: Layer | null;
|
|
73
|
+
sourceHash: string | null;
|
|
74
|
+
targetHash: string | null;
|
|
75
|
+
}
|
|
76
|
+
/** Helper to get layer directories for resource resolution. */
|
|
77
|
+
export interface LayerDirs {
|
|
78
|
+
system: string;
|
|
79
|
+
user: string;
|
|
80
|
+
project: string | null;
|
|
81
|
+
extra: string[];
|
|
82
|
+
}
|
package/dist/lib/state.js
CHANGED
|
@@ -40,13 +40,13 @@ const SYSTEM_INSTRUCTIONS_FILE = path.join(SYSTEM_AGENTS_DIR, 'instructions.md')
|
|
|
40
40
|
// User-level operational state
|
|
41
41
|
const PACKAGES_DIR = path.join(USER_AGENTS_DIR, 'packages');
|
|
42
42
|
const ROUTINES_DIR = path.join(USER_AGENTS_DIR, 'routines');
|
|
43
|
-
const RUNS_DIR = path.join(
|
|
43
|
+
const RUNS_DIR = path.join(ROUTINES_DIR, 'runs');
|
|
44
44
|
const VERSIONS_DIR = path.join(USER_AGENTS_DIR, 'versions');
|
|
45
45
|
const SHIMS_DIR = path.join(USER_AGENTS_DIR, 'shims');
|
|
46
|
-
const BACKUPS_DIR = path.join(USER_AGENTS_DIR, 'backups');
|
|
46
|
+
const BACKUPS_DIR = path.join(USER_AGENTS_DIR, '.backups');
|
|
47
47
|
const PLUGINS_DIR = path.join(USER_AGENTS_DIR, 'plugins');
|
|
48
48
|
const DRIVE_DIR = path.join(USER_AGENTS_DIR, 'drive');
|
|
49
|
-
const TRASH_DIR = path.join(USER_AGENTS_DIR, 'trash');
|
|
49
|
+
const TRASH_DIR = path.join(USER_AGENTS_DIR, '.trash');
|
|
50
50
|
// ─── User resource dirs ───────────────────────────────────────────────────────
|
|
51
51
|
const USER_COMMANDS_DIR = path.join(USER_AGENTS_DIR, 'commands');
|
|
52
52
|
const USER_HOOKS_DIR = path.join(USER_AGENTS_DIR, 'hooks');
|
|
@@ -264,8 +264,6 @@ export function ensureAgentsDir() {
|
|
|
264
264
|
fs.mkdirSync(SYSTEM_PERMISSIONS_DIR, opts);
|
|
265
265
|
if (!fs.existsSync(SYSTEM_SUBAGENTS_DIR))
|
|
266
266
|
fs.mkdirSync(SYSTEM_SUBAGENTS_DIR, opts);
|
|
267
|
-
if (!fs.existsSync(DRIVE_DIR))
|
|
268
|
-
fs.mkdirSync(DRIVE_DIR, opts);
|
|
269
267
|
try {
|
|
270
268
|
fs.chmodSync(SYSTEM_AGENTS_DIR, 0o700);
|
|
271
269
|
}
|
|
@@ -3,6 +3,8 @@ export interface TeamMeta {
|
|
|
3
3
|
created_at: string;
|
|
4
4
|
description?: string;
|
|
5
5
|
enable_worktrees?: boolean;
|
|
6
|
+
/** Shared worktree path for all teammates (mutually exclusive with enable_worktrees). */
|
|
7
|
+
use_worktree?: string;
|
|
6
8
|
}
|
|
7
9
|
/** Map of team name to team metadata. */
|
|
8
10
|
export type TeamRegistry = Record<string, TeamMeta>;
|
|
@@ -16,6 +18,8 @@ export declare function loadTeams(): Promise<TeamRegistry>;
|
|
|
16
18
|
export interface CreateTeamOptions {
|
|
17
19
|
description?: string;
|
|
18
20
|
enableWorktrees?: boolean;
|
|
21
|
+
/** Path to an existing worktree for all teammates to share. */
|
|
22
|
+
useWorktree?: string;
|
|
19
23
|
}
|
|
20
24
|
/** Create a new team. Throws if a team with the same name already exists. */
|
|
21
25
|
export declare function createTeam(name: string, options?: CreateTeamOptions): Promise<TeamMeta>;
|
|
@@ -92,6 +92,9 @@ async function saveTeams(reg) {
|
|
|
92
92
|
}
|
|
93
93
|
/** Create a new team. Throws if a team with the same name already exists. */
|
|
94
94
|
export async function createTeam(name, options) {
|
|
95
|
+
if (options?.enableWorktrees && options?.useWorktree) {
|
|
96
|
+
throw new Error('Cannot use both --enable-worktrees and --use-worktree. Pick one.');
|
|
97
|
+
}
|
|
95
98
|
const p = await registryPath();
|
|
96
99
|
return withRegistryLock(p, async () => {
|
|
97
100
|
const reg = await loadTeams();
|
|
@@ -102,6 +105,7 @@ export async function createTeam(name, options) {
|
|
|
102
105
|
created_at: new Date().toISOString(),
|
|
103
106
|
...(options?.description ? { description: options.description } : {}),
|
|
104
107
|
...(options?.enableWorktrees ? { enable_worktrees: true } : {}),
|
|
108
|
+
...(options?.useWorktree ? { use_worktree: options.useWorktree } : {}),
|
|
105
109
|
};
|
|
106
110
|
reg[name] = meta;
|
|
107
111
|
await saveTeams(reg);
|
package/dist/lib/versions.d.ts
CHANGED
|
@@ -54,8 +54,9 @@ export declare function getActuallySyncedResources(agent: AgentId, version: stri
|
|
|
54
54
|
export declare function getNewResources(available: AvailableResources, actuallySynced: AvailableResources): AvailableResources;
|
|
55
55
|
/**
|
|
56
56
|
* Check if there are any new resources to sync.
|
|
57
|
+
* When version is provided, uses version-specific capability checks.
|
|
57
58
|
*/
|
|
58
|
-
export declare function hasNewResources(diff: AvailableResources, agent?: AgentId): boolean;
|
|
59
|
+
export declare function hasNewResources(diff: AvailableResources, agent?: AgentId, version?: string): boolean;
|
|
59
60
|
/**
|
|
60
61
|
* Prompt user to select which NEW resources to sync.
|
|
61
62
|
* Only shows resources that haven't been synced yet.
|
package/dist/lib/versions.js
CHANGED
|
@@ -27,7 +27,7 @@ import { getVersionsDir, ensureAgentsDir, readMeta, writeMeta, getCommandsDir, g
|
|
|
27
27
|
import { resolveResource } from './resources.js';
|
|
28
28
|
import { AGENTS, getAccountEmail, MCP_CAPABLE_AGENTS, COMMANDS_CAPABLE_AGENTS, getMcpConfigPathForHome, parseMcpConfig, resolveAgentName, formatAgentError } from './agents.js';
|
|
29
29
|
import { applyPermissionsToVersion as applyPermsToVersion, PERMISSIONS_CAPABLE_AGENTS, discoverPermissionGroups, buildPermissionsFromGroups, CODEX_RULES_FILENAME, getActivePermissionSetName, readPermissionSetRecipe, PERMISSION_SET_ENV_VAR } from './permissions.js';
|
|
30
|
-
import { installMcpServers } from './mcp.js';
|
|
30
|
+
import { installMcpServers, parseMcpServerConfig } from './mcp.js';
|
|
31
31
|
import { markdownToToml } from './convert.js';
|
|
32
32
|
import { createVersionedAlias, removeVersionedAlias, getConfigSymlinkVersion, ensureClaudeInsideSymlink } from './shims.js';
|
|
33
33
|
import { listInstalledSubagents, transformSubagentForClaude, syncSubagentToOpenclaw, SUBAGENT_CAPABLE_AGENTS } from './subagents.js';
|
|
@@ -145,17 +145,18 @@ export function getAvailableResources(cwd = process.cwd()) {
|
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
result.memory = Array.from(presetNames);
|
|
148
|
-
// MCP servers (*.yaml files)
|
|
148
|
+
// MCP servers (*.yaml files) — use the `name:` field inside, not filename
|
|
149
149
|
const mcpNames = new Set();
|
|
150
150
|
for (const { base } of resourceBases) {
|
|
151
151
|
const mcpDir = path.join(base, 'mcp');
|
|
152
152
|
if (!fs.existsSync(mcpDir))
|
|
153
153
|
continue;
|
|
154
|
-
const
|
|
155
|
-
.filter(f => f.endsWith('.yaml') || f.endsWith('.yml'))
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
154
|
+
const files = fs.readdirSync(mcpDir)
|
|
155
|
+
.filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
|
|
156
|
+
for (const file of files) {
|
|
157
|
+
const config = parseMcpServerConfig(path.join(mcpDir, file));
|
|
158
|
+
if (config?.name)
|
|
159
|
+
mcpNames.add(config.name);
|
|
159
160
|
}
|
|
160
161
|
}
|
|
161
162
|
result.mcp = Array.from(mcpNames);
|
|
@@ -459,7 +460,11 @@ export function getNewResources(available, actuallySynced) {
|
|
|
459
460
|
commands: available.commands.filter(c => !actuallySynced.commands.includes(c)),
|
|
460
461
|
skills: available.skills.filter(s => !actuallySynced.skills.includes(s)),
|
|
461
462
|
hooks: available.hooks.filter(h => !actuallySynced.hooks.includes(h)),
|
|
462
|
-
|
|
463
|
+
// Memory/rules presets are mutually exclusive — only one can be active.
|
|
464
|
+
// If any preset is synced, don't report others as "new".
|
|
465
|
+
memory: actuallySynced.memory.length > 0
|
|
466
|
+
? []
|
|
467
|
+
: available.memory.filter(m => !actuallySynced.memory.includes(m)),
|
|
463
468
|
mcp: available.mcp.filter(m => !actuallySynced.mcp.includes(m)),
|
|
464
469
|
permissions: available.permissions.filter(p => !actuallySynced.permissions.includes(p)),
|
|
465
470
|
subagents: available.subagents.filter(s => !actuallySynced.subagents.includes(s)),
|
|
@@ -471,14 +476,15 @@ export function getNewResources(available, actuallySynced) {
|
|
|
471
476
|
}
|
|
472
477
|
/**
|
|
473
478
|
* Check if there are any new resources to sync.
|
|
479
|
+
* When version is provided, uses version-specific capability checks.
|
|
474
480
|
*/
|
|
475
|
-
export function hasNewResources(diff, agent) {
|
|
476
|
-
const commandsApply = agent ?
|
|
477
|
-
const hooksApply = agent ?
|
|
478
|
-
const mcpApply = agent ?
|
|
479
|
-
const permsApply = agent ?
|
|
481
|
+
export function hasNewResources(diff, agent, version) {
|
|
482
|
+
const commandsApply = agent ? supports(agent, 'commands', version).ok : true;
|
|
483
|
+
const hooksApply = agent ? supports(agent, 'hooks', version).ok : true;
|
|
484
|
+
const mcpApply = agent ? supports(agent, 'mcp', version).ok : true;
|
|
485
|
+
const permsApply = agent ? supports(agent, 'allowlist', version).ok : true;
|
|
480
486
|
const subagentsApply = agent ? SUBAGENT_CAPABLE_AGENTS.includes(agent) : true;
|
|
481
|
-
const pluginsApply = agent ?
|
|
487
|
+
const pluginsApply = agent ? supports(agent, 'plugins', version).ok : true;
|
|
482
488
|
return ((diff.commands.length > 0 && commandsApply) ||
|
|
483
489
|
diff.skills.length > 0 ||
|
|
484
490
|
(diff.hooks.length > 0 && hooksApply) ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.14.
|
|
3
|
+
"version": "1.14.5",
|
|
4
4
|
"description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
},
|
|
17
17
|
"bin": {
|
|
18
18
|
"agents": "dist/index.js",
|
|
19
|
-
"ag": "dist/index.js"
|
|
19
|
+
"ag": "dist/index.js",
|
|
20
|
+
"browser": "dist/browser.js"
|
|
20
21
|
},
|
|
21
22
|
"files": [
|
|
22
23
|
"dist/**/*.js",
|