@phnx-labs/agents-cli 1.20.28 → 1.20.30

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.
Files changed (63) hide show
  1. package/dist/commands/computer-actions.js +6 -2
  2. package/dist/commands/computer.d.ts +12 -0
  3. package/dist/commands/computer.js +88 -13
  4. package/dist/commands/exec.js +22 -10
  5. package/dist/commands/inspect.js +1 -1
  6. package/dist/commands/models.js +8 -2
  7. package/dist/commands/secrets.js +93 -6
  8. package/dist/commands/sessions.js +157 -44
  9. package/dist/commands/ssh.d.ts +14 -0
  10. package/dist/commands/ssh.js +263 -0
  11. package/dist/commands/sync.js +70 -14
  12. package/dist/index.js +2 -1
  13. package/dist/lib/agents.d.ts +0 -4
  14. package/dist/lib/agents.js +54 -5
  15. package/dist/lib/browser/drivers/ssh.js +4 -35
  16. package/dist/lib/computer-rpc.d.ts +6 -1
  17. package/dist/lib/computer-rpc.js +86 -3
  18. package/dist/lib/devices/connect.d.ts +34 -0
  19. package/dist/lib/devices/connect.js +101 -0
  20. package/dist/lib/devices/registry.d.ts +78 -0
  21. package/dist/lib/devices/registry.js +168 -0
  22. package/dist/lib/devices/ssh-config.d.ts +21 -0
  23. package/dist/lib/devices/ssh-config.js +33 -0
  24. package/dist/lib/devices/tailscale.d.ts +31 -0
  25. package/dist/lib/devices/tailscale.js +126 -0
  26. package/dist/lib/exec.js +14 -0
  27. package/dist/lib/models.js +138 -5
  28. package/dist/lib/runner.js +7 -7
  29. package/dist/lib/secrets/remote.d.ts +67 -0
  30. package/dist/lib/secrets/remote.js +133 -0
  31. package/dist/lib/session/active.d.ts +13 -0
  32. package/dist/lib/session/active.js +79 -18
  33. package/dist/lib/session/cloud.js +2 -0
  34. package/dist/lib/session/db.d.ts +12 -0
  35. package/dist/lib/session/db.js +66 -9
  36. package/dist/lib/session/discover.d.ts +7 -0
  37. package/dist/lib/session/discover.js +309 -0
  38. package/dist/lib/session/parse.d.ts +22 -0
  39. package/dist/lib/session/parse.js +132 -2
  40. package/dist/lib/session/remote.d.ts +1 -1
  41. package/dist/lib/session/remote.js +8 -3
  42. package/dist/lib/session/state.d.ts +82 -0
  43. package/dist/lib/session/state.js +221 -0
  44. package/dist/lib/session/tail.d.ts +18 -0
  45. package/dist/lib/session/tail.js +57 -0
  46. package/dist/lib/session/types.d.ts +10 -1
  47. package/dist/lib/session/types.js +1 -1
  48. package/dist/lib/session/width.d.ts +29 -0
  49. package/dist/lib/session/width.js +91 -0
  50. package/dist/lib/shims.d.ts +17 -1
  51. package/dist/lib/shims.js +130 -6
  52. package/dist/lib/ssh-tunnel.d.ts +127 -0
  53. package/dist/lib/ssh-tunnel.js +346 -0
  54. package/dist/lib/startup/command-registry.d.ts +1 -0
  55. package/dist/lib/startup/command-registry.js +3 -0
  56. package/dist/lib/state.d.ts +4 -0
  57. package/dist/lib/state.js +19 -1
  58. package/dist/lib/teams/agents.d.ts +11 -1
  59. package/dist/lib/teams/agents.js +16 -2
  60. package/dist/lib/types.d.ts +1 -0
  61. package/dist/lib/versions.d.ts +19 -0
  62. package/dist/lib/versions.js +84 -24
  63. package/package.json +1 -1
@@ -46,6 +46,7 @@ export const loadTmux = async () => (await import('../../commands/tmux.js')).reg
46
46
  export const loadBrowser = async () => (await import('../../commands/browser.js')).registerBrowserCommand;
47
47
  export const loadComputer = async () => (await import('../../commands/computer.js')).registerComputerCommand;
48
48
  export const loadHosts = async () => (await import('../../commands/hosts.js')).registerHostsCommand;
49
+ export const loadSsh = async () => (await import('../../commands/ssh.js')).registerSshCommands;
49
50
  export const loadPull = async () => (await import('../../commands/pull.js')).registerPullCommand;
50
51
  export const loadPush = async () => (await import('../../commands/push.js')).registerPushCommand;
51
52
  export const loadRepo = async () => (await import('../../commands/repo.js')).registerRepoCommands;
@@ -128,6 +129,8 @@ export const COMMAND_LOADERS = {
128
129
  browser: [loadBrowser],
129
130
  computer: [loadComputer],
130
131
  hosts: [loadHosts],
132
+ ssh: [loadSsh],
133
+ devices: [loadSsh],
131
134
  pull: [loadPull],
132
135
  push: [loadPush],
133
136
  repo: [loadRepo],
@@ -99,6 +99,8 @@ export declare function getSystemWorkflowsDir(): string;
99
99
  export declare function getUserWorkflowsDir(): string;
100
100
  export declare function getUserSecretsDir(): string;
101
101
  export declare function getUserPromptcutsPath(): string;
102
+ /** Canonical home anchor (HOME env override or os.homedir()). */
103
+ export declare function getHomeDir(): string;
102
104
  /** Bucket root for durable runtime data (~/.agents/.history/). */
103
105
  export declare function getHistoryDir(): string;
104
106
  /** Bucket root for regenerable runtime data (~/.agents/.cache/). */
@@ -152,6 +154,8 @@ export declare function getTeamsDir(): string;
152
154
  export declare function getTeamsAgentsDir(): string;
153
155
  /** Path to the team registry — list of named teams with timestamps. Durable runtime, per-machine. */
154
156
  export declare function getTeamsRegistryPath(): string;
157
+ /** Path to the device registry — SSH device profiles with platform/auth metadata. Durable runtime, per-machine (host list + addresses are NOT pulled by `agents repo push`). */
158
+ export declare function getDevicesRegistryPath(): string;
155
159
  /** Path to cloud dispatch cache (~/.agents/.cache/cloud/). */
156
160
  export declare function getCloudDir(): string;
157
161
  /** Path to terminal session metadata (~/.agents/.cache/terminals/). */
package/dist/lib/state.js CHANGED
@@ -29,6 +29,20 @@ import * as yaml from 'yaml';
29
29
  import { ensureLockTarget, atomicWriteFileSync, withFileLock } from './fs-atomic.js';
30
30
  import { SEEDED_REGISTRIES } from './types.js';
31
31
  const HOME = process.env.HOME ?? os.homedir();
32
+ /**
33
+ * Compare two filesystem paths for identity, resolving symlinks and (on
34
+ * Windows) 8.3 short-name vs long-name divergence via the OS realpath.
35
+ * Falls back to a case-folded normalize when a path doesn't exist on disk.
36
+ */
37
+ function isSamePath(a, b) {
38
+ try {
39
+ return fs.realpathSync.native(a) === fs.realpathSync.native(b);
40
+ }
41
+ catch {
42
+ const norm = (p) => process.platform === 'win32' ? path.resolve(p).toLowerCase() : path.resolve(p);
43
+ return norm(a) === norm(b);
44
+ }
45
+ }
32
46
  // ─── Root directories ─────────────────────────────────────────────────────────
33
47
  /** User repo — user-authored resources and agents.yaml. Always-on. */
34
48
  const USER_AGENTS_DIR = path.join(HOME, '.agents');
@@ -161,7 +175,7 @@ export function getProjectAgentsDir(startPath = process.cwd()) {
161
175
  while (true) {
162
176
  const agentsPath = path.join(dir, '.agents');
163
177
  if (fs.existsSync(agentsPath) && fs.statSync(agentsPath).isDirectory()) {
164
- if (agentsPath !== SYSTEM_AGENTS_DIR && agentsPath !== USER_AGENTS_DIR) {
178
+ if (!isSamePath(agentsPath, SYSTEM_AGENTS_DIR) && !isSamePath(agentsPath, USER_AGENTS_DIR)) {
165
179
  return agentsPath;
166
180
  }
167
181
  }
@@ -269,6 +283,8 @@ export function getUserPromptcutsPath() { return USER_PROMPTCUTS_FILE; }
269
283
  //
270
284
  // Top-level dirs hold definitions/configs only; runtime data lives under
271
285
  // .history/ (durable) or .cache/ (regenerable). See file header.
286
+ /** Canonical home anchor (HOME env override or os.homedir()). */
287
+ export function getHomeDir() { return HOME; }
272
288
  /** Bucket root for durable runtime data (~/.agents/.history/). */
273
289
  export function getHistoryDir() { return HISTORY_DIR; }
274
290
  /** Bucket root for regenerable runtime data (~/.agents/.cache/). */
@@ -334,6 +350,8 @@ export function getTeamsDir() { return TEAMS_DIR; }
334
350
  export function getTeamsAgentsDir() { return TEAMS_AGENTS_DIR; }
335
351
  /** Path to the team registry — list of named teams with timestamps. Durable runtime, per-machine. */
336
352
  export function getTeamsRegistryPath() { return path.join(HISTORY_DIR, 'teams', 'registry.json'); }
353
+ /** Path to the device registry — SSH device profiles with platform/auth metadata. Durable runtime, per-machine (host list + addresses are NOT pulled by `agents repo push`). */
354
+ export function getDevicesRegistryPath() { return path.join(HISTORY_DIR, 'devices', 'registry.json'); }
337
355
  /** Path to cloud dispatch cache (~/.agents/.cache/cloud/). */
338
356
  export function getCloudDir() { return CLOUD_DIR; }
339
357
  /** Path to terminal session metadata (~/.agents/.cache/terminals/). */
@@ -50,7 +50,17 @@ type Mode = 'plan' | 'edit' | 'auto' | 'skip';
50
50
  export declare function resolveMode(requestedMode: string | null | undefined, defaultMode?: Mode): Mode;
51
51
  /** Ensure Gemini's settings.json has experimental.plan enabled for headless plan mode. */
52
52
  export declare function ensureGeminiPlanMode(): Promise<void>;
53
- /** Check whether the CLI binary for a given agent type exists in PATH. Returns [available, pathOrError]. */
53
+ /**
54
+ * Check whether the CLI binary for a given agent type is installed.
55
+ * Returns [available, pathOrError].
56
+ *
57
+ * The agents-managed shims dir (`~/.agents/.cache/shims`) is the canonical
58
+ * install location, so a shim there means installed regardless of the caller's
59
+ * PATH. Non-interactive callers — the menu-bar helper, cron, CI — run with a
60
+ * minimal launchd PATH that omits the shims dir; a bare PATH lookup false-flags
61
+ * every shim-based CLI as "not installed". Check the shim first, PATH second
62
+ * (for CLIs the user installed outside agents-cli).
63
+ */
54
64
  export declare function checkCliAvailable(agentType: AgentType): [boolean, string | null];
55
65
  /** Check availability of all known agent CLIs. Returns a map of agent type to install status. */
56
66
  export declare function checkAllClis(): Record<string, {
@@ -18,7 +18,7 @@ import { findExecutable } from '../platform/index.js';
18
18
  import { normalizeEvents } from './parsers.js';
19
19
  import { debug } from './debug.js';
20
20
  import { setGeminiAutoUpdateDisabled, updateGeminiSettings } from '../gemini-settings.js';
21
- import { getAgentsDir as getSystemAgentsDir } from '../state.js';
21
+ import { getAgentsDir as getSystemAgentsDir, getShimsDir } from '../state.js';
22
22
  import { AGENTS } from '../agents.js';
23
23
  import { sanitizeProcessEnv } from '../secrets/bundles.js';
24
24
  let lastMemoryWarnAt = 0;
@@ -314,12 +314,26 @@ export async function ensureGeminiPlanMode() {
314
314
  console.warn('[Swarm] Could not enable Gemini plan mode:', err);
315
315
  }
316
316
  }
317
- /** Check whether the CLI binary for a given agent type exists in PATH. Returns [available, pathOrError]. */
317
+ /**
318
+ * Check whether the CLI binary for a given agent type is installed.
319
+ * Returns [available, pathOrError].
320
+ *
321
+ * The agents-managed shims dir (`~/.agents/.cache/shims`) is the canonical
322
+ * install location, so a shim there means installed regardless of the caller's
323
+ * PATH. Non-interactive callers — the menu-bar helper, cron, CI — run with a
324
+ * minimal launchd PATH that omits the shims dir; a bare PATH lookup false-flags
325
+ * every shim-based CLI as "not installed". Check the shim first, PATH second
326
+ * (for CLIs the user installed outside agents-cli).
327
+ */
318
328
  export function checkCliAvailable(agentType) {
319
329
  const executable = AGENTS[agentType]?.cliCommand;
320
330
  if (!executable) {
321
331
  return [false, `Unknown agent type: ${agentType}`];
322
332
  }
333
+ const shimPath = path.join(getShimsDir(), executable);
334
+ if (fsSync.existsSync(shimPath)) {
335
+ return [true, shimPath];
336
+ }
323
337
  const resolved = findExecutable(executable);
324
338
  return resolved
325
339
  ? [true, resolved]
@@ -74,6 +74,7 @@ export interface AgentConfig {
74
74
  installScript?: string;
75
75
  configDir: string;
76
76
  homeFiles?: string[];
77
+ authFiles?: string[];
77
78
  commandsDir: string;
78
79
  commandsSubdir: string;
79
80
  skillsDir: string;
@@ -314,6 +314,25 @@ export interface ResourceDiff {
314
314
  * Uses filesystem state - no tracking needed.
315
315
  */
316
316
  export declare function getResourceDiff(agent: AgentId, version: string): ResourceDiff;
317
+ /**
318
+ * Enumerate the DotAgent repo names that resources can be scoped to:
319
+ * the fixed `project` / `user` / `system` layers plus every enabled extra
320
+ * repo alias. Used to validate `agents sync <agent> --repo <name>`.
321
+ */
322
+ export declare function listRepoNames(): string[];
323
+ /**
324
+ * Build a ResourceSelection scoped to a single DotAgent repo (`system`,
325
+ * `user`, `project`, or an extra-repo alias). Every resource kind is filtered
326
+ * to the entries whose source layer matches `repo`, reusing the same
327
+ * name→source maps and `source:*` pattern expansion the persisted-pattern
328
+ * sync path uses. Passing the result as an explicit `selection` means the sync
329
+ * touches only that repo's resources — no orphan-sweep of the other layers.
330
+ *
331
+ * `memory` is set to `[]` (not omitted): that empty-array sentinel is what
332
+ * `syncResourcesToVersion`'s `skipMemory` gate keys on to leave the memory
333
+ * file untouched — it's a merge of all layers, not a per-repo artifact.
334
+ */
335
+ export declare function buildRepoScopedSelection(repo: string, cwd?: string): ResourceSelection;
317
336
  /**
318
337
  * Sync central resources (~/.agents/) into a specific version's config directory.
319
338
  * Copies selected resources from central storage into {versionHome}/.{agent}/.
@@ -16,13 +16,12 @@
16
16
  */
17
17
  import * as fs from 'fs';
18
18
  import * as path from 'path';
19
- import * as os from 'os';
20
19
  import * as yaml from 'yaml';
21
20
  import { exec, execFile } from 'child_process';
22
21
  import { promisify } from 'util';
23
22
  import chalk from 'chalk';
24
23
  import { checkbox, select } from '@inquirer/prompts';
25
- import { getVersionsDir, ensureAgentsDir, readMeta, writeMeta, getCommandsDir, getSkillsDir, getHooksDir, getResolvedRulesDir, getUserRulesDir, getVersionResources, ensureVersionResourcePatterns, getProjectAgentsDir, getPromptcutsPath, getUserPromptcutsPath, getEnabledExtraRepos, getAgentsDir, getUserAgentsDir, getTrashVersionsDir, getActiveRulesPreset } from './state.js';
24
+ import { getVersionsDir, ensureAgentsDir, readMeta, writeMeta, getCommandsDir, getSkillsDir, getHooksDir, getResolvedRulesDir, getUserRulesDir, getVersionResources, ensureVersionResourcePatterns, getProjectAgentsDir, getPromptcutsPath, getUserPromptcutsPath, getEnabledExtraRepos, getAgentsDir, getUserAgentsDir, getTrashVersionsDir, getActiveRulesPreset, getHomeDir } from './state.js';
26
25
  import { defaultPatterns, expandPatterns } from './resource-patterns.js';
27
26
  import { listResources } from './resources.js';
28
27
  import { AGENTS, agentConfigDirName, getAccountEmail, resolveAgentName, formatAgentError, findInPath } from './agents.js';
@@ -30,6 +29,7 @@ import { discoverPermissionGroups, getActivePermissionPresetName, readPermission
30
29
  import { parseMcpServerConfig } from './mcp.js';
31
30
  import { createVersionedAlias, removeVersionedAlias, getConfigSymlinkVersion, ensureClaudeInsideSymlink } from './shims.js';
32
31
  import { importInstallScriptBinary } from './import.js';
32
+ import { IS_WINDOWS } from './platform/index.js';
33
33
  import { supports, explainSkip } from './capabilities.js';
34
34
  import { discoverPlugins } from './plugins.js';
35
35
  import { loadManifest, saveManifest, buildManifest as buildSyncManifest, isStale } from './staleness/index.js';
@@ -819,12 +819,17 @@ export function getBinaryPath(agent, version) {
819
819
  return path.join(grokDownloads, `grok-${version}`);
820
820
  }
821
821
  if (agent === 'droid') {
822
- // Factory's installer drops a standalone native binary at ~/.local/bin/droid
823
- // (no npm package, nothing in node_modules/.bin). The binary is global, not
824
- // per-version — config isolation rides the ~/.factory symlink switch, not a
825
- // separate binary per version. Mirror the shim's `droid` branch so
826
- // isVersionInstalled/`agents view` agree with what actually executes.
827
- return path.join(os.homedir(), '.local', 'bin', 'droid');
822
+ // Factory's installer drops a standalone native binary (no npm package,
823
+ // nothing in node_modules/.bin). The binary is global, not per-version —
824
+ // config isolation rides the ~/.factory symlink switch, not a separate
825
+ // binary per version. Install location is platform-specific:
826
+ // macOS/Linux: ~/.local/bin/droid (curl app.factory.ai/cli | sh)
827
+ // Windows: %USERPROFILE%\bin\droid.exe (irm app.factory.ai/cli/windows | iex)
828
+ // Mirror the shim's `droid` branch so isVersionInstalled/`agents view`
829
+ // agree with what actually executes.
830
+ return IS_WINDOWS
831
+ ? path.join(getHomeDir(), 'bin', 'droid.exe')
832
+ : path.join(getHomeDir(), '.local', 'bin', 'droid');
828
833
  }
829
834
  const versionDir = getVersionDir(agent, version);
830
835
  return path.join(versionDir, 'node_modules', '.bin', agentConfig.cliCommand);
@@ -1302,7 +1307,7 @@ export function removeVersion(agent, version) {
1302
1307
  // Clean up dangling config symlink if it pointed to the removed version
1303
1308
  const symlinkVersion = getConfigSymlinkVersion(agent);
1304
1309
  if (symlinkVersion === version) {
1305
- const configPath = path.join(os.homedir(), agentConfigDirName(agent));
1310
+ const configPath = path.join(getHomeDir(), agentConfigDirName(agent));
1306
1311
  try {
1307
1312
  fs.unlinkSync(configPath);
1308
1313
  }
@@ -1636,6 +1641,68 @@ export function getResourceDiff(agent, version) {
1636
1641
  diff.hooks.dangling.length + diff.memory.dangling.length;
1637
1642
  return diff;
1638
1643
  }
1644
+ /**
1645
+ * Enumerate the DotAgent repo names that resources can be scoped to:
1646
+ * the fixed `project` / `user` / `system` layers plus every enabled extra
1647
+ * repo alias. Used to validate `agents sync <agent> --repo <name>`.
1648
+ */
1649
+ export function listRepoNames() {
1650
+ return ['project', 'user', 'system', ...getEnabledExtraRepos().map(e => e.alias)];
1651
+ }
1652
+ /**
1653
+ * Build the name→source-layer map for one resource kind, the input
1654
+ * `expandPatterns` matches `source:*` patterns against. This is the single
1655
+ * source of truth for how each kind attributes its source layer:
1656
+ * - commands/skills/hooks/subagents → real layer from `listResources`
1657
+ * - permissions → always the system repo
1658
+ * - mcp → project vs user scope preserved
1659
+ * - plugins/workflows → user repo
1660
+ * Both the persisted-pattern sync path and `buildRepoScopedSelection` use it
1661
+ * so the attribution can't drift between the two.
1662
+ */
1663
+ function resourceSourceMap(kind, cwd, available) {
1664
+ switch (kind) {
1665
+ case 'commands':
1666
+ case 'skills':
1667
+ case 'hooks':
1668
+ case 'subagents':
1669
+ return new Map(listResources(kind, cwd).map(r => [r.name, r.source]));
1670
+ case 'permissions':
1671
+ return new Map(available.permissions.map(n => [n, 'system']));
1672
+ case 'mcp':
1673
+ return new Map(getScopedMcpResources(cwd).map(r => [r.name, r.scope]));
1674
+ case 'plugins':
1675
+ return new Map(available.plugins.map(n => [n, 'user']));
1676
+ case 'workflows':
1677
+ return new Map(available.workflows.map(n => [n, 'user']));
1678
+ }
1679
+ }
1680
+ /**
1681
+ * Build a ResourceSelection scoped to a single DotAgent repo (`system`,
1682
+ * `user`, `project`, or an extra-repo alias). Every resource kind is filtered
1683
+ * to the entries whose source layer matches `repo`, reusing the same
1684
+ * name→source maps and `source:*` pattern expansion the persisted-pattern
1685
+ * sync path uses. Passing the result as an explicit `selection` means the sync
1686
+ * touches only that repo's resources — no orphan-sweep of the other layers.
1687
+ *
1688
+ * `memory` is set to `[]` (not omitted): that empty-array sentinel is what
1689
+ * `syncResourcesToVersion`'s `skipMemory` gate keys on to leave the memory
1690
+ * file untouched — it's a merge of all layers, not a per-repo artifact.
1691
+ */
1692
+ export function buildRepoScopedSelection(repo, cwd = process.cwd()) {
1693
+ const patterns = [`${repo}:*`];
1694
+ const available = getAvailableResources(cwd);
1695
+ const selection = {};
1696
+ const kinds = ['commands', 'skills', 'hooks', 'subagents', 'permissions', 'mcp', 'plugins', 'workflows'];
1697
+ for (const kind of kinds) {
1698
+ const names = expandPatterns(patterns, resourceSourceMap(kind, cwd, available));
1699
+ if (names.length > 0)
1700
+ selection[kind] = names;
1701
+ }
1702
+ // Empty-array sentinel → skip the memory writer (see skipMemory below).
1703
+ selection.memory = [];
1704
+ return selection;
1705
+ }
1639
1706
  /**
1640
1707
  * Sync central resources (~/.agents/) into a specific version's config directory.
1641
1708
  * Copies selected resources from central storage into {versionHome}/.{agent}/.
@@ -1701,28 +1768,21 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1701
1768
  const patterns = vr[type];
1702
1769
  if (!Array.isArray(patterns) || patterns.length === 0)
1703
1770
  continue;
1704
- const sourceMap = new Map(listResources(kind, cwd).map(r => [r.name, r.source]));
1705
- patternSelection[type] = expandPatterns(patterns, sourceMap);
1771
+ patternSelection[type] = expandPatterns(patterns, resourceSourceMap(kind, cwd, available));
1706
1772
  }
1707
- // permissions: all groups are 'system' source.
1773
+ // permissions / mcp / plugins / workflows: source attribution lives in
1774
+ // resourceSourceMap so it can't drift from buildRepoScopedSelection.
1708
1775
  if (Array.isArray(vr.permissions) && vr.permissions.length > 0) {
1709
- const permMap = new Map(available.permissions.map(n => [n, 'system']));
1710
- patternSelection.permissions = expandPatterns(vr.permissions, permMap);
1776
+ patternSelection.permissions = expandPatterns(vr.permissions, resourceSourceMap('permissions', cwd, available));
1711
1777
  }
1712
- // mcp: pattern matching must preserve project vs user scope.
1713
1778
  if (Array.isArray(vr.mcp) && vr.mcp.length > 0) {
1714
- const mcpMap = new Map(getScopedMcpResources(cwd).map(resource => [resource.name, resource.scope]));
1715
- patternSelection.mcp = expandPatterns(vr.mcp, mcpMap);
1779
+ patternSelection.mcp = expandPatterns(vr.mcp, resourceSourceMap('mcp', cwd, available));
1716
1780
  }
1717
- // plugins: treat all as 'user' source for now.
1718
1781
  if (Array.isArray(vr.plugins) && vr.plugins.length > 0) {
1719
- const pluginMap = new Map(available.plugins.map(n => [n, 'user']));
1720
- patternSelection.plugins = expandPatterns(vr.plugins, pluginMap);
1782
+ patternSelection.plugins = expandPatterns(vr.plugins, resourceSourceMap('plugins', cwd, available));
1721
1783
  }
1722
- // workflows: treat all as 'user' source.
1723
1784
  if (Array.isArray(vr.workflows) && vr.workflows.length > 0) {
1724
- const workflowMap = new Map(available.workflows.map(n => [n, 'user']));
1725
- patternSelection.workflows = expandPatterns(vr.workflows, workflowMap);
1785
+ patternSelection.workflows = expandPatterns(vr.workflows, resourceSourceMap('workflows', cwd, available));
1726
1786
  }
1727
1787
  // memory is not pattern-controlled (rulesPreset handles it) — always sync.
1728
1788
  patternSelection.memory = 'all';
@@ -2071,7 +2131,7 @@ export function getEffectiveHome(agentId) {
2071
2131
  if (resolved && isVersionInstalled(agentId, resolved)) {
2072
2132
  return getVersionHomePath(agentId, resolved);
2073
2133
  }
2074
- return os.homedir();
2134
+ return getHomeDir();
2075
2135
  }
2076
2136
  /**
2077
2137
  * Thrown when the user references an agent@version that is not installed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.20.28",
3
+ "version": "1.20.30",
4
4
  "description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",