@phnx-labs/agents-cli 1.20.72 → 1.20.74
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 +395 -0
- package/README.md +40 -4
- package/dist/bin/agents +0 -0
- package/dist/commands/activity.d.ts +12 -0
- package/dist/commands/activity.js +68 -0
- package/dist/commands/apply.d.ts +13 -0
- package/dist/commands/apply.js +28 -3
- package/dist/commands/browser.js +1 -1
- package/dist/commands/cloud.js +21 -11
- package/dist/commands/doctor.js +21 -5
- package/dist/commands/events.d.ts +11 -10
- package/dist/commands/events.js +30 -21
- package/dist/commands/exec.d.ts +34 -0
- package/dist/commands/exec.js +337 -39
- package/dist/commands/export.d.ts +11 -0
- package/dist/commands/export.js +215 -0
- package/dist/commands/feed.d.ts +13 -0
- package/dist/commands/feed.js +130 -29
- package/dist/commands/hq.d.ts +2 -0
- package/dist/commands/hq.js +58 -0
- package/dist/commands/import.js +51 -3
- package/dist/commands/lease.d.ts +19 -0
- package/dist/commands/lease.js +138 -11
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +123 -0
- package/dist/commands/logs.js +1 -1
- package/dist/commands/mine.d.ts +27 -0
- package/dist/commands/mine.js +207 -0
- package/dist/commands/monitors.js +79 -49
- package/dist/commands/plugins.js +1 -0
- package/dist/commands/profiles.js +158 -0
- package/dist/commands/repo.js +2 -60
- package/dist/commands/resources.d.ts +5 -0
- package/dist/commands/resources.js +95 -0
- package/dist/commands/routines.d.ts +2 -0
- package/dist/commands/routines.js +139 -37
- package/dist/commands/secrets.js +234 -47
- package/dist/commands/send.d.ts +15 -0
- package/dist/commands/send.js +62 -0
- package/dist/commands/sessions-tail.d.ts +1 -0
- package/dist/commands/sessions-tail.js +20 -9
- package/dist/commands/sessions.js +18 -3
- package/dist/commands/setup-mine.d.ts +18 -0
- package/dist/commands/setup-mine.js +106 -0
- package/dist/commands/setup-share.js +2 -2
- package/dist/commands/setup.js +9 -2
- package/dist/commands/share.d.ts +8 -1
- package/dist/commands/share.js +122 -37
- package/dist/commands/ssh.d.ts +9 -0
- package/dist/commands/ssh.js +196 -26
- package/dist/commands/sync.js +1 -1
- package/dist/commands/teams.js +11 -2
- package/dist/commands/versions.js +36 -5
- package/dist/commands/view.js +75 -27
- package/dist/index.js +80 -10
- package/dist/lib/activity.d.ts +124 -0
- package/dist/lib/activity.js +542 -0
- package/dist/lib/agents.d.ts +35 -0
- package/dist/lib/agents.js +79 -6
- package/dist/lib/artifact-actions.d.ts +1 -1
- package/dist/lib/artifact-actions.js +1 -1
- package/dist/lib/ask-classifier.d.ts +2 -1
- package/dist/lib/ask-classifier.js +3 -3
- package/dist/lib/auth-health.d.ts +32 -4
- package/dist/lib/auth-health.js +40 -1
- package/dist/lib/auto-pull.js +8 -1
- package/dist/lib/brand.d.ts +40 -0
- package/dist/lib/brand.js +122 -0
- package/dist/lib/browser/drivers/ssh.js +4 -7
- package/dist/lib/browser/service.js +6 -8
- package/dist/lib/channels/providers/index.d.ts +2 -0
- package/dist/lib/channels/providers/index.js +20 -0
- package/dist/lib/channels/providers/mailbox.d.ts +2 -0
- package/dist/lib/channels/providers/mailbox.js +26 -0
- package/dist/lib/channels/providers/openclaw-telegram.d.ts +2 -0
- package/dist/lib/channels/providers/openclaw-telegram.js +34 -0
- package/dist/lib/channels/providers/rush.d.ts +6 -0
- package/dist/lib/channels/providers/rush.js +55 -0
- package/dist/lib/channels/registry.d.ts +42 -0
- package/dist/lib/channels/registry.js +20 -0
- package/dist/lib/channels/resolve.d.ts +11 -0
- package/dist/lib/channels/resolve.js +12 -0
- package/dist/lib/cli-resources.d.ts +18 -0
- package/dist/lib/cli-resources.js +53 -4
- package/dist/lib/cloud/codex.js +6 -3
- package/dist/lib/cloud/factory.d.ts +1 -0
- package/dist/lib/cloud/factory.js +10 -4
- package/dist/lib/cloud/rush.d.ts +3 -1
- package/dist/lib/cloud/rush.js +6 -1
- package/dist/lib/cloud/types.d.ts +2 -0
- package/dist/lib/codex-home.d.ts +35 -0
- package/dist/lib/codex-home.js +136 -0
- package/dist/lib/config-transfer.d.ts +16 -0
- package/dist/lib/config-transfer.js +56 -0
- package/dist/lib/crabbox/cli.d.ts +44 -0
- package/dist/lib/crabbox/cli.js +120 -20
- package/dist/lib/crabbox/lease.d.ts +52 -1
- package/dist/lib/crabbox/lease.js +117 -16
- package/dist/lib/crabbox/progress.d.ts +31 -0
- package/dist/lib/crabbox/progress.js +76 -0
- package/dist/lib/crabbox/runtimes.d.ts +4 -0
- package/dist/lib/crabbox/runtimes.js +33 -8
- package/dist/lib/crabbox/setup-copy.d.ts +87 -0
- package/dist/lib/crabbox/setup-copy.js +127 -0
- package/dist/lib/daemon.d.ts +5 -5
- package/dist/lib/daemon.js +9 -9
- package/dist/lib/drive-sync.js +6 -3
- package/dist/lib/event-stream.d.ts +36 -0
- package/dist/lib/event-stream.js +68 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +11 -0
- package/dist/lib/exec.js +23 -2
- package/dist/lib/export.d.ts +72 -0
- package/dist/lib/export.js +269 -0
- package/dist/lib/feed-policy.d.ts +1 -1
- package/dist/lib/feed-policy.js +14 -9
- package/dist/lib/feed-ranking.d.ts +32 -0
- package/dist/lib/feed-ranking.js +224 -0
- package/dist/lib/feed.d.ts +35 -2
- package/dist/lib/feed.js +66 -3
- package/dist/lib/fleet/auth-sync.d.ts +53 -0
- package/dist/lib/fleet/auth-sync.js +92 -0
- package/dist/lib/fleet/remote-login.d.ts +187 -0
- package/dist/lib/fleet/remote-login.js +556 -0
- package/dist/lib/format.d.ts +30 -3
- package/dist/lib/format.js +34 -5
- package/dist/lib/fs-atomic.d.ts +7 -2
- package/dist/lib/fs-atomic.js +8 -12
- package/dist/lib/git.d.ts +16 -0
- package/dist/lib/git.js +79 -2
- package/dist/lib/heal.js +11 -3
- package/dist/lib/hosts/progress.d.ts +28 -10
- package/dist/lib/hosts/progress.js +79 -22
- package/dist/lib/hosts/remote-cmd.js +4 -0
- package/dist/lib/hq/floor.d.ts +87 -0
- package/dist/lib/hq/floor.js +226 -0
- package/dist/lib/import.d.ts +23 -0
- package/dist/lib/import.js +74 -2
- package/dist/lib/isolation-boundary-report.d.ts +10 -0
- package/dist/lib/isolation-boundary-report.js +35 -0
- package/dist/lib/menubar/install-menubar.js +14 -20
- package/dist/lib/notify.d.ts +1 -0
- package/dist/lib/notify.js +3 -3
- package/dist/lib/open-url.d.ts +2 -0
- package/dist/lib/open-url.js +19 -0
- package/dist/lib/openclaw-keychain.d.ts +56 -0
- package/dist/lib/openclaw-keychain.js +236 -0
- package/dist/lib/overdue.js +10 -0
- package/dist/lib/permissions.d.ts +44 -1
- package/dist/lib/permissions.js +284 -7
- package/dist/lib/project-launch.d.ts +6 -12
- package/dist/lib/project-launch.js +13 -228
- package/dist/lib/project-resources.d.ts +7 -0
- package/dist/lib/project-resources.js +306 -0
- package/dist/lib/pty-client.d.ts +27 -0
- package/dist/lib/pty-client.js +136 -10
- package/dist/lib/refresh.js +14 -10
- package/dist/lib/resource-profiles.d.ts +26 -0
- package/dist/lib/resource-profiles.js +157 -0
- package/dist/lib/resources/permissions.js +7 -1
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/resources.d.ts +1 -1
- package/dist/lib/resources.js +32 -3
- package/dist/lib/routines.d.ts +11 -0
- package/dist/lib/routines.js +56 -15
- package/dist/lib/runner.d.ts +1 -0
- package/dist/lib/runner.js +66 -5
- package/dist/lib/secrets/bundles.d.ts +16 -3
- package/dist/lib/secrets/bundles.js +206 -37
- package/dist/lib/secrets/icloud-import.d.ts +2 -2
- package/dist/lib/secrets/icloud-import.js +9 -6
- package/dist/lib/secrets/mcp.js +1 -1
- package/dist/lib/secrets/vault-age-helper.d.ts +1 -0
- package/dist/lib/secrets/vault-age-helper.js +34 -0
- package/dist/lib/secrets/vault.d.ts +49 -0
- package/dist/lib/secrets/vault.js +397 -0
- package/dist/lib/self-heal/checks/path.js +3 -1
- package/dist/lib/self-heal/checks/shadowing.js +10 -2
- package/dist/lib/self-heal/checks/shims.js +19 -11
- package/dist/lib/session/cloud.d.ts +2 -2
- package/dist/lib/session/cloud.js +2 -2
- package/dist/lib/session/db.d.ts +4 -0
- package/dist/lib/session/db.js +44 -7
- package/dist/lib/session/discover.d.ts +2 -0
- package/dist/lib/session/discover.js +114 -3
- package/dist/lib/session/stream-render.d.ts +3 -0
- package/dist/lib/session/stream-render.js +130 -0
- package/dist/lib/session/types.d.ts +6 -0
- package/dist/lib/share/analytics.d.ts +13 -0
- package/dist/lib/share/analytics.js +45 -0
- package/dist/lib/share/config.d.ts +19 -5
- package/dist/lib/share/config.js +44 -8
- package/dist/lib/share/provision.d.ts +58 -6
- package/dist/lib/share/provision.js +97 -22
- package/dist/lib/share/publish.d.ts +36 -13
- package/dist/lib/share/publish.js +55 -8
- package/dist/lib/share/worker-template.js +83 -17
- package/dist/lib/shims.d.ts +35 -0
- package/dist/lib/shims.js +159 -13
- package/dist/lib/ssh-exec.d.ts +14 -0
- package/dist/lib/ssh-exec.js +57 -0
- package/dist/lib/staleness/detectors/hooks.js +25 -1
- package/dist/lib/staleness/detectors/permissions.js +66 -0
- package/dist/lib/staleness/detectors/workflows.js +20 -0
- package/dist/lib/staleness/writers/hooks.js +58 -4
- package/dist/lib/staleness/writers/permissions.js +2 -2
- package/dist/lib/staleness/writers/skills.js +1 -1
- package/dist/lib/staleness/writers/sources.d.ts +10 -1
- package/dist/lib/staleness/writers/sources.js +68 -1
- package/dist/lib/star-nudge.d.ts +45 -0
- package/dist/lib/star-nudge.js +91 -0
- package/dist/lib/startup/command-registry.d.ts +7 -1
- package/dist/lib/startup/command-registry.js +19 -3
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.js +17 -4
- package/dist/lib/subagents-registry.d.ts +2 -0
- package/dist/lib/subagents-registry.js +3 -0
- package/dist/lib/teams/parsers.js +214 -6
- package/dist/lib/teams/supervisor.d.ts +7 -0
- package/dist/lib/teams/supervisor.js +2 -1
- package/dist/lib/template.d.ts +1 -1
- package/dist/lib/template.js +1 -1
- package/dist/lib/triggers/webhook.js +36 -3
- package/dist/lib/types.d.ts +84 -0
- package/dist/lib/uninstall.js +1 -45
- package/dist/lib/version.d.ts +40 -0
- package/dist/lib/version.js +94 -16
- package/dist/lib/versions.d.ts +24 -0
- package/dist/lib/versions.js +269 -78
- package/dist/lib/workflows.d.ts +2 -0
- package/dist/lib/workflows.js +88 -0
- package/package.json +2 -1
- package/dist/commands/daemon.d.ts +0 -10
- package/dist/commands/daemon.js +0 -121
package/dist/lib/shims.js
CHANGED
|
@@ -17,6 +17,7 @@ import { IS_WINDOWS, prependToWindowsUserPath } from './platform/index.js';
|
|
|
17
17
|
import { getShimsDir, getVersionsDir, getBackupsDir, getHistoryDir, ensureAgentsDir } from './state.js';
|
|
18
18
|
export { getShimsDir };
|
|
19
19
|
import { AGENTS, agentConfigDirName, readAuthAccountIdentity } from './agents.js';
|
|
20
|
+
import { codexHomeShimBash } from './codex-home.js';
|
|
20
21
|
/**
|
|
21
22
|
* Files and directories to always skip during conflict detection and migration.
|
|
22
23
|
* These are never user config that should be migrated.
|
|
@@ -261,12 +262,7 @@ if [ "\$(uname -s)" = "Linux" ] && [ -z "\${CLAUDE_CODE_OAUTH_TOKEN:-}" ] && [ -
|
|
|
261
262
|
fi
|
|
262
263
|
`
|
|
263
264
|
: agent === 'codex'
|
|
264
|
-
? `
|
|
265
|
-
# Codex reads its config (approval_policy, sandbox_mode, MCP servers, rules)
|
|
266
|
-
# from CODEX_HOME. Point it at the versioned home so permissions/rules
|
|
267
|
-
# written by agents-cli actually take effect.
|
|
268
|
-
export CODEX_HOME="$VERSION_DIR/home/${configDirName}"
|
|
269
|
-
`
|
|
265
|
+
? codexHomeShimBash(`$VERSION_DIR/home/${configDirName}`, `$AGENTS_USER_DIR/.codex-homes/$VERSION`)
|
|
270
266
|
: agent === 'copilot'
|
|
271
267
|
? `
|
|
272
268
|
# GitHub Copilot CLI honors COPILOT_HOME to relocate its config and state
|
|
@@ -665,6 +661,9 @@ export function shimTargetsFor(platform) {
|
|
|
665
661
|
* Create a shim for an agent.
|
|
666
662
|
*/
|
|
667
663
|
export function createShim(agent) {
|
|
664
|
+
// A bare shim puts agents-cli first on PATH for this agent — the opposite of what
|
|
665
|
+
// an isolated-only install promises.
|
|
666
|
+
assertIsolationBoundary(agent, 'create the bare shim');
|
|
668
667
|
ensureAgentsDir();
|
|
669
668
|
const shimsDir = getShimsDir();
|
|
670
669
|
const agentConfig = AGENTS[agent];
|
|
@@ -682,6 +681,75 @@ export function createShim(agent) {
|
|
|
682
681
|
}
|
|
683
682
|
return shimPath;
|
|
684
683
|
}
|
|
684
|
+
// ─── White-label brand shims ──────────────────────────────────────────────────
|
|
685
|
+
// A brand shim is a PURE pass-through: unlike an agent shim (which resolves a
|
|
686
|
+
// version and execs the agent binary) or an alias shim (which injects a fixed
|
|
687
|
+
// subcommand), it forwards argv verbatim to the agents-cli entrypoint with
|
|
688
|
+
// `AGENTS_BRAND` set, so `<brand> <any-verb>` behaves exactly like
|
|
689
|
+
// `agents <any-verb>` but presents under the brand's name. See lib/brand.ts.
|
|
690
|
+
const BRAND_SHIM_MARKER = '# Brand shim:';
|
|
691
|
+
/** The POSIX pass-through shim for a brand. */
|
|
692
|
+
export function generateBrandShim(name) {
|
|
693
|
+
const agentsBin = shellQuote(getAgentsBinForGeneratedShim());
|
|
694
|
+
return `#!/bin/sh
|
|
695
|
+
# Auto-generated by agents-cli - do not edit
|
|
696
|
+
${BRAND_SHIM_MARKER} ${name} -> agents-cli (white-label)
|
|
697
|
+
AGENTS_BIN=${agentsBin}
|
|
698
|
+
if [ -z "$AGENTS_BIN" ] || [ ! -x "$AGENTS_BIN" ]; then
|
|
699
|
+
echo "${name}: agents-cli entrypoint missing or not executable: $AGENTS_BIN" >&2
|
|
700
|
+
exit 127
|
|
701
|
+
fi
|
|
702
|
+
export AGENTS_BRAND=${name}
|
|
703
|
+
exec "$AGENTS_BIN" "$@"
|
|
704
|
+
`;
|
|
705
|
+
}
|
|
706
|
+
/** Write a brand's pass-through shim(s) onto PATH; returns the shim path. */
|
|
707
|
+
export function createBrandShim(name) {
|
|
708
|
+
ensureAgentsDir();
|
|
709
|
+
const shimsDir = getShimsDir();
|
|
710
|
+
const shimPath = path.join(shimsDir, name);
|
|
711
|
+
const targets = shimTargetsFor(process.platform);
|
|
712
|
+
if (targets.bash) {
|
|
713
|
+
fs.writeFileSync(shimPath, generateBrandShim(name), { mode: 0o755 });
|
|
714
|
+
}
|
|
715
|
+
if (targets.cmd) {
|
|
716
|
+
writeWindowsBrandShim(shimPath + '.cmd', name);
|
|
717
|
+
}
|
|
718
|
+
return shimPath;
|
|
719
|
+
}
|
|
720
|
+
/** Windows `.cmd` pass-through: set AGENTS_BRAND then forward argv to the entrypoint. */
|
|
721
|
+
function writeWindowsBrandShim(cmdPath, name) {
|
|
722
|
+
const indexJs = getAgentsBinForGeneratedShim();
|
|
723
|
+
const content = `@echo off\r\n` +
|
|
724
|
+
`rem Auto-generated by agents-cli - do not edit\r\n` +
|
|
725
|
+
`rem Brand shim: ${name}\r\n` +
|
|
726
|
+
`set AGENTS_BRAND=${name}\r\n` +
|
|
727
|
+
`node "${indexJs}" %*\r\n`;
|
|
728
|
+
fs.writeFileSync(cmdPath, content);
|
|
729
|
+
}
|
|
730
|
+
/** True when the file at the given path is an agents-cli brand shim. */
|
|
731
|
+
export function isBrandShim(filePath) {
|
|
732
|
+
try {
|
|
733
|
+
const head = fs.readFileSync(filePath, 'utf-8').slice(0, 300);
|
|
734
|
+
return head.includes(BRAND_SHIM_MARKER) || head.includes('rem Brand shim:');
|
|
735
|
+
}
|
|
736
|
+
catch {
|
|
737
|
+
return false;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
/** Remove a brand's shim companions. Returns true if anything was removed. */
|
|
741
|
+
export function removeBrandShim(name) {
|
|
742
|
+
const shimsDir = getShimsDir();
|
|
743
|
+
const shimPath = path.join(shimsDir, name);
|
|
744
|
+
let removed = false;
|
|
745
|
+
for (const p of [shimPath, shimPath + '.cmd']) {
|
|
746
|
+
if (fs.existsSync(p) && isBrandShim(p)) {
|
|
747
|
+
fs.unlinkSync(p);
|
|
748
|
+
removed = true;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return removed;
|
|
752
|
+
}
|
|
685
753
|
/**
|
|
686
754
|
* Generate a Windows `.cmd` launcher that delegates to `agents __shim <spec>`.
|
|
687
755
|
* `spec` is the agent's cliCommand for the default-version shim, or
|
|
@@ -829,12 +897,7 @@ export CLAUDE_CONFIG_DIR="$HOME/.agents/.history/versions/${agent}/${version}/ho
|
|
|
829
897
|
export DISABLE_AUTOUPDATER="\${DISABLE_AUTOUPDATER:-1}"
|
|
830
898
|
`
|
|
831
899
|
: agent === 'codex'
|
|
832
|
-
? `
|
|
833
|
-
# Codex reads its config (approval_policy, sandbox_mode, MCP servers, rules)
|
|
834
|
-
# from CODEX_HOME. Point direct aliases at the versioned home so permissions
|
|
835
|
-
# and rules written by agents-cli actually take effect.
|
|
836
|
-
export CODEX_HOME="$HOME/.agents/.history/versions/${agent}/${version}/home/${configDirName}"
|
|
837
|
-
`
|
|
900
|
+
? codexHomeShimBash(`$HOME/.agents/.history/versions/${agent}/${version}/home/${configDirName}`, `$HOME/.agents/.codex-homes/${version}`)
|
|
838
901
|
: agent === 'copilot'
|
|
839
902
|
? `
|
|
840
903
|
# Copilot honors COPILOT_HOME to relocate ~/.copilot (settings, mcp-config.json,
|
|
@@ -1262,6 +1325,8 @@ export function carryForwardAuthFiles(agent, toConfigDir) {
|
|
|
1262
1325
|
}
|
|
1263
1326
|
}
|
|
1264
1327
|
export async function switchConfigSymlink(agent, version) {
|
|
1328
|
+
// Moves the user's real ~/.<agent> aside and symlinks it into a version home.
|
|
1329
|
+
assertIsolationBoundary(agent, 'repoint your real config directory');
|
|
1265
1330
|
const configPath = getAgentConfigPath(agent);
|
|
1266
1331
|
const versionConfigPath = getVersionConfigPath(agent, version);
|
|
1267
1332
|
// Ensure version config directory exists
|
|
@@ -1366,6 +1431,8 @@ export async function switchConfigSymlink(agent, version) {
|
|
|
1366
1431
|
* ALL installed versions so they inherit the current account.
|
|
1367
1432
|
*/
|
|
1368
1433
|
export function switchHomeFileSymlinks(agent, version) {
|
|
1434
|
+
// Same, for home-level files such as ~/.claude.json.
|
|
1435
|
+
assertIsolationBoundary(agent, 'repoint your home-level config files');
|
|
1369
1436
|
const agentConfig = AGENTS[agent];
|
|
1370
1437
|
const homeFiles = agentConfig.homeFiles;
|
|
1371
1438
|
if (!homeFiles || homeFiles.length === 0)
|
|
@@ -2016,6 +2083,9 @@ function canonicalOrNull(p) {
|
|
|
2016
2083
|
* - Never records our own shim as the "original" (would loop).
|
|
2017
2084
|
*/
|
|
2018
2085
|
export function adoptShadowingLauncher(agent, overrides) {
|
|
2086
|
+
// Repoints the user's OWN launcher symlink at our shim — the most invasive thing
|
|
2087
|
+
// in the codebase, and the one with no isolated-scoped equivalent at all.
|
|
2088
|
+
assertIsolationBoundary(agent, 'adopt your launcher');
|
|
2019
2089
|
const shimsDir = overrides?.shimsDir ?? getShimsDir();
|
|
2020
2090
|
const shimPath = path.join(shimsDir, AGENTS[agent].cliCommand);
|
|
2021
2091
|
const shimReal = canonical(shimPath);
|
|
@@ -2368,6 +2438,82 @@ export function listAgentsWithInstalledVersions() {
|
|
|
2368
2438
|
.map((entry) => entry.name)
|
|
2369
2439
|
.filter((agent) => fs.readdirSync(path.join(versionsDir, agent), { withFileTypes: true }).some((entry) => entry.isDirectory()));
|
|
2370
2440
|
}
|
|
2441
|
+
function isInstalledVersionIsolated(agent, version) {
|
|
2442
|
+
return fs.existsSync(path.join(getVersionsDir(), agent, version, '.isolated'));
|
|
2443
|
+
}
|
|
2444
|
+
/**
|
|
2445
|
+
* Thrown when an operation would carry an isolated-only agent across the isolation
|
|
2446
|
+
* boundary. Callers that can explain the situation catch it and print guidance; the
|
|
2447
|
+
* throw is what makes the boundary a property of the code rather than a convention
|
|
2448
|
+
* every future call site has to remember.
|
|
2449
|
+
*/
|
|
2450
|
+
export class IsolationBoundaryError extends Error {
|
|
2451
|
+
agent;
|
|
2452
|
+
operation;
|
|
2453
|
+
constructor(agent, operation) {
|
|
2454
|
+
super(`${agent} is installed only as isolated copies; "${operation}" would adopt it into your local setup.`);
|
|
2455
|
+
this.agent = agent;
|
|
2456
|
+
this.operation = operation;
|
|
2457
|
+
this.name = 'IsolationBoundaryError';
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
/**
|
|
2461
|
+
* True when EVERY installed version of `agent` is isolated (and at least one is).
|
|
2462
|
+
*
|
|
2463
|
+
* This is the switch: installing with `--isolated` is itself the act of opting in,
|
|
2464
|
+
* so there is no mode to set and none to forget. It is per-agent, so an isolated
|
|
2465
|
+
* codex constrains nothing about claude. And the escape hatch is inherent — remove
|
|
2466
|
+
* the isolated copies and the agent is ordinary again, which means the state that
|
|
2467
|
+
* grants protection is the same state you delete to drop it.
|
|
2468
|
+
*
|
|
2469
|
+
* An agent with any NORMAL version is not protected: that install already owns the
|
|
2470
|
+
* launcher and the real `~/.<agent>`, so there is no boundary left to defend.
|
|
2471
|
+
*/
|
|
2472
|
+
export function isIsolationProtected(agent) {
|
|
2473
|
+
const agentVersionsDir = path.join(getVersionsDir(), agent);
|
|
2474
|
+
let dirs;
|
|
2475
|
+
try {
|
|
2476
|
+
dirs = fs.readdirSync(agentVersionsDir, { withFileTypes: true })
|
|
2477
|
+
.filter((e) => e.isDirectory())
|
|
2478
|
+
.map((e) => e.name);
|
|
2479
|
+
}
|
|
2480
|
+
catch {
|
|
2481
|
+
return false;
|
|
2482
|
+
}
|
|
2483
|
+
// Count only directories that are actually an install. A bare version dir is
|
|
2484
|
+
// scaffolding, not a non-isolated version — and treating it as one would disable
|
|
2485
|
+
// protection at exactly the wrong moment: an adopting path that does
|
|
2486
|
+
// `mkdirSync(<version>/home)` before adopting would flip this to false with its own
|
|
2487
|
+
// first line and then walk straight through the gates. Ignoring scaffolding biases
|
|
2488
|
+
// the predicate toward protecting, which is the safe direction to fail in.
|
|
2489
|
+
const installed = dirs.filter((v) => {
|
|
2490
|
+
const dir = path.join(agentVersionsDir, v);
|
|
2491
|
+
return fs.existsSync(path.join(dir, 'node_modules')) || fs.existsSync(path.join(dir, 'package.json'));
|
|
2492
|
+
});
|
|
2493
|
+
if (installed.length === 0)
|
|
2494
|
+
return false;
|
|
2495
|
+
return installed.every((v) => isInstalledVersionIsolated(agent, v));
|
|
2496
|
+
}
|
|
2497
|
+
/** Refuse `operation` when `agent` is isolated-only. */
|
|
2498
|
+
export function assertIsolationBoundary(agent, operation) {
|
|
2499
|
+
if (isIsolationProtected(agent))
|
|
2500
|
+
throw new IsolationBoundaryError(agent, operation);
|
|
2501
|
+
}
|
|
2502
|
+
export function listAgentsWithNonIsolatedInstalledVersions() {
|
|
2503
|
+
const versionsDir = getVersionsDir();
|
|
2504
|
+
if (!fs.existsSync(versionsDir)) {
|
|
2505
|
+
return [];
|
|
2506
|
+
}
|
|
2507
|
+
const entries = fs.readdirSync(versionsDir, { withFileTypes: true });
|
|
2508
|
+
return entries
|
|
2509
|
+
.filter((entry) => entry.isDirectory() && AGENTS[entry.name])
|
|
2510
|
+
.map((entry) => entry.name)
|
|
2511
|
+
.filter((agent) => {
|
|
2512
|
+
const agentVersionsDir = path.join(versionsDir, agent);
|
|
2513
|
+
return fs.readdirSync(agentVersionsDir, { withFileTypes: true })
|
|
2514
|
+
.some((entry) => entry.isDirectory() && !isInstalledVersionIsolated(agent, entry.name));
|
|
2515
|
+
});
|
|
2516
|
+
}
|
|
2371
2517
|
/**
|
|
2372
2518
|
* Create shims for all installed agents.
|
|
2373
2519
|
*/
|
|
@@ -2382,7 +2528,7 @@ function ensureAllShims() {
|
|
|
2382
2528
|
const agent = entry.name;
|
|
2383
2529
|
const agentVersionsDir = path.join(versionsDir, agent);
|
|
2384
2530
|
const versions = fs.readdirSync(agentVersionsDir, { withFileTypes: true })
|
|
2385
|
-
.filter((e) => e.isDirectory());
|
|
2531
|
+
.filter((e) => e.isDirectory() && !isInstalledVersionIsolated(agent, e.name));
|
|
2386
2532
|
if (versions.length > 0 && !shimExists(agent)) {
|
|
2387
2533
|
createShim(agent);
|
|
2388
2534
|
}
|
package/dist/lib/ssh-exec.d.ts
CHANGED
|
@@ -89,6 +89,20 @@ export interface SshExecRawResult {
|
|
|
89
89
|
* U+FFFD replacement char (which would desync a byte offset from the wire).
|
|
90
90
|
*/
|
|
91
91
|
export declare function sshExecRaw(target: string, remoteCmd: string, opts?: SshExecOptions): SshExecRawResult;
|
|
92
|
+
export interface SshExecRawStreamOptions extends SshExecOptions {
|
|
93
|
+
onStdout: (chunk: Buffer) => void;
|
|
94
|
+
onStderr?: (chunk: Buffer) => void;
|
|
95
|
+
signal?: AbortSignal;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Stream raw stdout from a long-lived remote command over ssh.
|
|
99
|
+
*
|
|
100
|
+
* Unlike {@link sshStream}, this does not inherit local stdio: callers receive
|
|
101
|
+
* byte-exact stdout chunks and decide how to account for them. That matters for
|
|
102
|
+
* offset-resumed log following where a UTF-8 decode boundary must not shift the
|
|
103
|
+
* byte cursor.
|
|
104
|
+
*/
|
|
105
|
+
export declare function sshExecRawStream(target: string, remoteCmd: string, opts: SshExecRawStreamOptions): Promise<Omit<SshExecRawResult, 'stdout'>>;
|
|
92
106
|
/** True if `target` is reachable over ssh (a passwordless `true` succeeds quickly). */
|
|
93
107
|
export declare function sshReachable(target: string, timeoutMs?: number): boolean;
|
|
94
108
|
export interface SshStreamOptions {
|
package/dist/lib/ssh-exec.js
CHANGED
|
@@ -205,6 +205,63 @@ export function sshExecRaw(target, remoteCmd, opts = {}) {
|
|
|
205
205
|
timedOut,
|
|
206
206
|
};
|
|
207
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Stream raw stdout from a long-lived remote command over ssh.
|
|
210
|
+
*
|
|
211
|
+
* Unlike {@link sshStream}, this does not inherit local stdio: callers receive
|
|
212
|
+
* byte-exact stdout chunks and decide how to account for them. That matters for
|
|
213
|
+
* offset-resumed log following where a UTF-8 decode boundary must not shift the
|
|
214
|
+
* byte cursor.
|
|
215
|
+
*/
|
|
216
|
+
export function sshExecRawStream(target, remoteCmd, opts) {
|
|
217
|
+
assertValidSshTarget(target);
|
|
218
|
+
const mux = opts.multiplex === false ? [] : controlOpts();
|
|
219
|
+
const args = [...sshConnectOpts(mux, opts.hostKeyOpts), ...(opts.extraSshArgs ?? []), target, remoteCmd];
|
|
220
|
+
return new Promise((resolve) => {
|
|
221
|
+
const child = spawn('ssh', args, {
|
|
222
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
223
|
+
windowsHide: true,
|
|
224
|
+
});
|
|
225
|
+
const stderr = [];
|
|
226
|
+
let settled = false;
|
|
227
|
+
let timedOut = false;
|
|
228
|
+
let aborted = false;
|
|
229
|
+
const finish = (code) => {
|
|
230
|
+
if (settled)
|
|
231
|
+
return;
|
|
232
|
+
settled = true;
|
|
233
|
+
if (timer)
|
|
234
|
+
clearTimeout(timer);
|
|
235
|
+
opts.signal?.removeEventListener('abort', abort);
|
|
236
|
+
resolve({ code, stderr: Buffer.concat(stderr), timedOut });
|
|
237
|
+
};
|
|
238
|
+
const abort = () => {
|
|
239
|
+
aborted = true;
|
|
240
|
+
child.kill('SIGTERM');
|
|
241
|
+
};
|
|
242
|
+
const timer = opts.timeoutMs
|
|
243
|
+
? setTimeout(() => {
|
|
244
|
+
timedOut = true;
|
|
245
|
+
child.kill('SIGTERM');
|
|
246
|
+
}, opts.timeoutMs)
|
|
247
|
+
: null;
|
|
248
|
+
child.stdout.on('data', (chunk) => { opts.onStdout(chunk); });
|
|
249
|
+
child.stderr.on('data', (chunk) => {
|
|
250
|
+
stderr.push(chunk);
|
|
251
|
+
opts.onStderr?.(chunk);
|
|
252
|
+
});
|
|
253
|
+
child.stdin.on('error', () => { });
|
|
254
|
+
child.stdin.end(opts.input ?? '');
|
|
255
|
+
opts.signal?.addEventListener('abort', abort, { once: true });
|
|
256
|
+
child.on('error', (err) => {
|
|
257
|
+
stderr.push(Buffer.from(err.message));
|
|
258
|
+
finish(null);
|
|
259
|
+
});
|
|
260
|
+
child.on('close', (code) => {
|
|
261
|
+
finish(aborted ? null : code);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
}
|
|
208
265
|
/** True if `target` is reachable over ssh (a passwordless `true` succeeds quickly). */
|
|
209
266
|
export function sshReachable(target, timeoutMs = 10000) {
|
|
210
267
|
return sshExec(target, 'true', { timeoutMs, multiplex: true }).code === 0;
|
|
@@ -8,6 +8,30 @@ import * as path from 'path';
|
|
|
8
8
|
import { capableAgents } from '../../capabilities.js';
|
|
9
9
|
import { resolveHookSource } from '../writers/sources.js';
|
|
10
10
|
import { lazyAgentMap } from '../writers/lazy-map.js';
|
|
11
|
+
function hookSourcesMatch(src, dest) {
|
|
12
|
+
const srcStat = fs.lstatSync(src);
|
|
13
|
+
if (srcStat.isSymbolicLink())
|
|
14
|
+
return false;
|
|
15
|
+
if (srcStat.isDirectory()) {
|
|
16
|
+
if (!fs.existsSync(dest) || !fs.lstatSync(dest).isDirectory())
|
|
17
|
+
return false;
|
|
18
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
if (entry.isSymbolicLink())
|
|
21
|
+
continue;
|
|
22
|
+
const srcPath = path.join(src, entry.name);
|
|
23
|
+
const destPath = path.join(dest, entry.name);
|
|
24
|
+
if (!hookSourcesMatch(srcPath, destPath))
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
if (!srcStat.isFile())
|
|
30
|
+
return false;
|
|
31
|
+
if (!fs.existsSync(dest) || !fs.lstatSync(dest).isFile())
|
|
32
|
+
return false;
|
|
33
|
+
return fs.readFileSync(src, 'utf-8') === fs.readFileSync(dest, 'utf-8');
|
|
34
|
+
}
|
|
11
35
|
function buildHooksDetector(agent) {
|
|
12
36
|
return {
|
|
13
37
|
kind: 'hooks',
|
|
@@ -26,7 +50,7 @@ function buildHooksDetector(agent) {
|
|
|
26
50
|
continue;
|
|
27
51
|
}
|
|
28
52
|
try {
|
|
29
|
-
if (
|
|
53
|
+
if (hookSourcesMatch(src, path.join(hooksDir, hook))) {
|
|
30
54
|
synced.push(hook);
|
|
31
55
|
}
|
|
32
56
|
}
|
|
@@ -288,6 +288,69 @@ function buildOpenClawDetector() {
|
|
|
288
288
|
},
|
|
289
289
|
};
|
|
290
290
|
}
|
|
291
|
+
function buildCopilotDetector() {
|
|
292
|
+
return {
|
|
293
|
+
kind: 'permissions',
|
|
294
|
+
agent: 'copilot',
|
|
295
|
+
list({ versionHome, cwd }) {
|
|
296
|
+
const configPath = path.join(versionHome, '.copilot', 'permissions-config.json');
|
|
297
|
+
if (!fs.existsSync(configPath))
|
|
298
|
+
return [];
|
|
299
|
+
try {
|
|
300
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
301
|
+
const locations = config.locations && typeof config.locations === 'object' && !Array.isArray(config.locations)
|
|
302
|
+
? config.locations
|
|
303
|
+
: {};
|
|
304
|
+
const location = locations[path.resolve(cwd)];
|
|
305
|
+
const hasApprovals = Array.isArray(location?.tool_approvals) && location.tool_approvals.length > 0;
|
|
306
|
+
const hasDirectories = Array.isArray(location?.allowed_directories) && location.allowed_directories.length > 0;
|
|
307
|
+
if (hasApprovals || hasDirectories)
|
|
308
|
+
return discoverPermissionGroups().map(g => g.name);
|
|
309
|
+
}
|
|
310
|
+
catch { /* parse fail */ }
|
|
311
|
+
return [];
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function buildForgeDetector() {
|
|
316
|
+
return {
|
|
317
|
+
kind: 'permissions',
|
|
318
|
+
agent: 'forge',
|
|
319
|
+
list({ versionHome }) {
|
|
320
|
+
const permissionsPath = path.join(versionHome, '.forge', 'permissions.yaml');
|
|
321
|
+
if (!fs.existsSync(permissionsPath))
|
|
322
|
+
return [];
|
|
323
|
+
try {
|
|
324
|
+
const config = yaml.parse(fs.readFileSync(permissionsPath, 'utf-8'));
|
|
325
|
+
if (config && Array.isArray(config.policies) && config.policies.length > 0) {
|
|
326
|
+
return discoverPermissionGroups().map(g => g.name);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
catch { /* parse fail */ }
|
|
330
|
+
return [];
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function buildHermesDetector() {
|
|
335
|
+
return {
|
|
336
|
+
kind: 'permissions',
|
|
337
|
+
agent: 'hermes',
|
|
338
|
+
list({ versionHome }) {
|
|
339
|
+
const configPath = path.join(versionHome, '.hermes', 'config.yaml');
|
|
340
|
+
if (!fs.existsSync(configPath))
|
|
341
|
+
return [];
|
|
342
|
+
try {
|
|
343
|
+
const config = yaml.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
344
|
+
const hasAllow = Array.isArray(config?.command_allowlist) && config.command_allowlist.length > 0;
|
|
345
|
+
const hasDeny = Array.isArray(config?.approvals?.deny) && config.approvals.deny.length > 0;
|
|
346
|
+
if (hasAllow || hasDeny)
|
|
347
|
+
return discoverPermissionGroups().map(g => g.name);
|
|
348
|
+
}
|
|
349
|
+
catch { /* parse fail */ }
|
|
350
|
+
return [];
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
}
|
|
291
354
|
const handlers = {
|
|
292
355
|
claude: buildClaudeDetector,
|
|
293
356
|
codex: buildCodexDetector,
|
|
@@ -301,6 +364,9 @@ const handlers = {
|
|
|
301
364
|
droid: buildDroidDetector,
|
|
302
365
|
kiro: buildKiroDetector,
|
|
303
366
|
openclaw: buildOpenClawDetector,
|
|
367
|
+
copilot: buildCopilotDetector,
|
|
368
|
+
forge: buildForgeDetector,
|
|
369
|
+
hermes: buildHermesDetector,
|
|
304
370
|
};
|
|
305
371
|
export const permissionsDetectors = lazyAgentMap(() => {
|
|
306
372
|
const m = {};
|
|
@@ -73,6 +73,26 @@ function buildWorkflowsDetector(agent) {
|
|
|
73
73
|
.filter(d => d.isFile() && d.name.endsWith('.yaml') && !d.name.startsWith('.'))
|
|
74
74
|
.map(d => d.name.slice(0, -'.yaml'.length));
|
|
75
75
|
}
|
|
76
|
+
if (agent === 'openclaw') {
|
|
77
|
+
const dir = path.join(versionHome, '.openclaw', 'workflows');
|
|
78
|
+
if (!fs.existsSync(dir))
|
|
79
|
+
return [];
|
|
80
|
+
return fs.readdirSync(dir, { withFileTypes: true })
|
|
81
|
+
.filter(d => d.isFile() && d.name.endsWith('.lobster') && !d.name.startsWith('.'))
|
|
82
|
+
.filter(d => {
|
|
83
|
+
try {
|
|
84
|
+
const parsed = yaml.parse(fs.readFileSync(path.join(dir, d.name), 'utf-8'));
|
|
85
|
+
const env = parsed && typeof parsed === 'object' && parsed.env && typeof parsed.env === 'object' && !Array.isArray(parsed.env)
|
|
86
|
+
? parsed.env
|
|
87
|
+
: {};
|
|
88
|
+
return env.AGENTS_CLI_WORKFLOW === d.name.slice(0, -'.lobster'.length);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
.map(d => d.name.slice(0, -'.lobster'.length));
|
|
95
|
+
}
|
|
76
96
|
const workflowsDir = path.join(versionHome, 'workflows');
|
|
77
97
|
if (!fs.existsSync(workflowsDir))
|
|
78
98
|
return [];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Hooks writer — copies trusted hook
|
|
2
|
+
* Hooks writer — copies trusted hook sources into `{agentDir}/hooks/`.
|
|
3
3
|
* Caller filters by `supports(agent, 'hooks', version)` before invoking.
|
|
4
4
|
* Orphan sweep (delete hooks in version-home that aren't in `availableNames`)
|
|
5
5
|
* stays in the orchestrator since it depends on the broader available set.
|
|
@@ -12,6 +12,60 @@ import { safeJoin } from '../../paths.js';
|
|
|
12
12
|
import { registerHooksToSettings } from '../../hooks.js';
|
|
13
13
|
import { resolveHookSource } from './sources.js';
|
|
14
14
|
import { lazyAgentMap } from './lazy-map.js';
|
|
15
|
+
function removePath(target) {
|
|
16
|
+
try {
|
|
17
|
+
const stat = fs.lstatSync(target);
|
|
18
|
+
if (stat.isSymbolicLink() || stat.isFile()) {
|
|
19
|
+
fs.unlinkSync(target);
|
|
20
|
+
}
|
|
21
|
+
else if (stat.isDirectory()) {
|
|
22
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
/* already gone */
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function copyDir(src, dest) {
|
|
30
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
31
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
if (entry.isSymbolicLink())
|
|
34
|
+
continue;
|
|
35
|
+
const srcPath = safeJoin(src, entry.name);
|
|
36
|
+
const destPath = safeJoin(dest, entry.name);
|
|
37
|
+
if (entry.isDirectory()) {
|
|
38
|
+
copyDir(srcPath, destPath);
|
|
39
|
+
}
|
|
40
|
+
else if (entry.isFile()) {
|
|
41
|
+
fs.copyFileSync(srcPath, destPath);
|
|
42
|
+
fs.chmodSync(destPath, fs.statSync(srcPath).mode);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function copyHookSource(src, dest) {
|
|
47
|
+
let stat;
|
|
48
|
+
try {
|
|
49
|
+
stat = fs.lstatSync(src);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
if (stat.isSymbolicLink())
|
|
55
|
+
return false;
|
|
56
|
+
removePath(dest);
|
|
57
|
+
if (stat.isDirectory()) {
|
|
58
|
+
copyDir(src, dest);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
if (stat.isFile()) {
|
|
62
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
63
|
+
fs.copyFileSync(src, dest);
|
|
64
|
+
fs.chmodSync(dest, 0o755);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
15
69
|
function buildHooksWriter(agent) {
|
|
16
70
|
return {
|
|
17
71
|
kind: 'hooks',
|
|
@@ -26,9 +80,9 @@ function buildHooksWriter(agent) {
|
|
|
26
80
|
if (!srcFile)
|
|
27
81
|
continue;
|
|
28
82
|
const destFile = safeJoin(hooksTarget, hook);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
83
|
+
if (copyHookSource(srcFile, destFile)) {
|
|
84
|
+
synced.push(hook);
|
|
85
|
+
}
|
|
32
86
|
}
|
|
33
87
|
// Native hook registration in settings.json/hooks.json. Grok is included
|
|
34
88
|
// so subrule-bundled guards (absolute paths outside the central hooks/
|
|
@@ -5,7 +5,7 @@ function buildPermissionsWriter(agent) {
|
|
|
5
5
|
return {
|
|
6
6
|
kind: 'permissions',
|
|
7
7
|
agent,
|
|
8
|
-
write({ versionHome, selection }) {
|
|
8
|
+
write({ versionHome, selection, cwd }) {
|
|
9
9
|
if (selection.length === 0)
|
|
10
10
|
return { synced: [] };
|
|
11
11
|
const built = buildPermissionsFromGroups(selection);
|
|
@@ -13,7 +13,7 @@ function buildPermissionsWriter(agent) {
|
|
|
13
13
|
const hasDeny = (built.deny?.length ?? 0) > 0;
|
|
14
14
|
if (!hasAllow && !hasDeny)
|
|
15
15
|
return { synced: [] };
|
|
16
|
-
const r = applyPermsToVersion(agent, built, versionHome, true);
|
|
16
|
+
const r = applyPermsToVersion(agent, built, versionHome, true, cwd);
|
|
17
17
|
return { synced: r.success ? selection : [] };
|
|
18
18
|
},
|
|
19
19
|
};
|
|
@@ -56,7 +56,7 @@ function buildSkillsWriter(agent) {
|
|
|
56
56
|
fs.mkdirSync(skillsTarget, { recursive: true });
|
|
57
57
|
const synced = [];
|
|
58
58
|
for (const skill of selection) {
|
|
59
|
-
const srcDir = resolveSkillSource(skill);
|
|
59
|
+
const srcDir = resolveSkillSource(skill, { agent });
|
|
60
60
|
if (!srcDir)
|
|
61
61
|
continue;
|
|
62
62
|
const destDir = safeJoin(skillsTarget, skill);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AgentId } from '../../types.js';
|
|
1
2
|
export type EnabledExtra = {
|
|
2
3
|
alias: string;
|
|
3
4
|
dir: string;
|
|
@@ -9,7 +10,15 @@ export declare function trustedSourceBases(): {
|
|
|
9
10
|
/** Find the trusted source for a command markdown by name. */
|
|
10
11
|
export declare function resolveCommandSource(name: string): string | null;
|
|
11
12
|
/** Find the trusted source directory for a skill by name. */
|
|
12
|
-
export declare function resolveSkillSource(name: string
|
|
13
|
+
export declare function resolveSkillSource(name: string, options?: {
|
|
14
|
+
agent?: AgentId;
|
|
15
|
+
plugins?: Set<string>;
|
|
16
|
+
}): string | null;
|
|
17
|
+
/** List trusted plugin-bundled skill names, filtered to an optional plugin/agent scope. */
|
|
18
|
+
export declare function listPluginSkillNames(options?: {
|
|
19
|
+
agent?: AgentId;
|
|
20
|
+
plugins?: Set<string>;
|
|
21
|
+
}): string[];
|
|
13
22
|
/** Find the trusted source file for a hook by name. */
|
|
14
23
|
export declare function resolveHookSource(name: string): string | null;
|
|
15
24
|
/** All trusted command-skill source roots, used to dedup name collisions for commands-as-skills writes. */
|