@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/permissions.js
CHANGED
|
@@ -433,14 +433,27 @@ export function removePermissionSet(name) {
|
|
|
433
433
|
// ============================================================================
|
|
434
434
|
// Agent-specific converters
|
|
435
435
|
// ============================================================================
|
|
436
|
+
/**
|
|
437
|
+
* Map a canonical rule to Claude settings.json syntax.
|
|
438
|
+
* Claude's file-permission checks only match Edit(path) rules — an Edit rule
|
|
439
|
+
* covers every file-editing tool (Write, Edit, NotebookEdit) — and Claude
|
|
440
|
+
* warns at startup about unmatched Write(path) rules. Canonical Write(path)
|
|
441
|
+
* stays for other agents' converters; for Claude it is emitted as Edit(path).
|
|
442
|
+
*/
|
|
443
|
+
function canonicalToClaudeRule(perm) {
|
|
444
|
+
if (perm.startsWith('Write(')) {
|
|
445
|
+
return perm.replace(/^Write/, 'Edit');
|
|
446
|
+
}
|
|
447
|
+
return perm;
|
|
448
|
+
}
|
|
436
449
|
/**
|
|
437
450
|
* Convert canonical permission set to Claude format.
|
|
438
451
|
* Claude uses: { permissions: { allow: ["Bash(*)", "Read(**)"], deny: [] } }
|
|
439
452
|
*/
|
|
440
453
|
export function convertToClaudeFormat(set) {
|
|
441
454
|
const permissions = {
|
|
442
|
-
allow: [...set.allow],
|
|
443
|
-
deny: set.deny ? [...set.deny] : [],
|
|
455
|
+
allow: [...new Set(set.allow.map(canonicalToClaudeRule))],
|
|
456
|
+
deny: set.deny ? [...new Set(set.deny.map(canonicalToClaudeRule))] : [],
|
|
444
457
|
};
|
|
445
458
|
if (set.additionalDirectories?.length) {
|
|
446
459
|
permissions.additionalDirectories = [...set.additionalDirectories];
|
|
@@ -540,6 +553,89 @@ function normalizeBashPattern(pattern) {
|
|
|
540
553
|
return pattern.slice(0, -2) + ' *';
|
|
541
554
|
return pattern;
|
|
542
555
|
}
|
|
556
|
+
function canonicalBashToCopilotCommandIdentifier(pattern) {
|
|
557
|
+
if (pattern === '*' || pattern === '**')
|
|
558
|
+
return null;
|
|
559
|
+
if (pattern.endsWith(':*'))
|
|
560
|
+
return pattern;
|
|
561
|
+
if (pattern.endsWith(' *'))
|
|
562
|
+
return `${pattern.slice(0, -2)}:*`;
|
|
563
|
+
return pattern;
|
|
564
|
+
}
|
|
565
|
+
function canonicalToCopilotMcpApproval(parsed) {
|
|
566
|
+
const parts = parsed.pattern.split(/[.:/]/).filter(Boolean);
|
|
567
|
+
if (parts.length === 0)
|
|
568
|
+
return null;
|
|
569
|
+
const [serverName, toolName] = parts;
|
|
570
|
+
return { kind: 'mcp', serverName, toolName: toolName ?? null };
|
|
571
|
+
}
|
|
572
|
+
function canonicalToCopilotApproval(permission) {
|
|
573
|
+
const parsed = parseCanonicalPattern(permission);
|
|
574
|
+
if (!parsed)
|
|
575
|
+
return null;
|
|
576
|
+
if (parsed.tool === 'bash') {
|
|
577
|
+
const identifier = canonicalBashToCopilotCommandIdentifier(parsed.pattern);
|
|
578
|
+
return identifier ? { kind: 'commands', commandIdentifiers: [identifier] } : null;
|
|
579
|
+
}
|
|
580
|
+
if ((parsed.tool === 'write' || parsed.tool === 'edit') && (parsed.pattern === '*' || parsed.pattern === '**')) {
|
|
581
|
+
return { kind: 'write' };
|
|
582
|
+
}
|
|
583
|
+
if (parsed.tool === 'read' && (parsed.pattern === '*' || parsed.pattern === '**')) {
|
|
584
|
+
return { kind: 'read' };
|
|
585
|
+
}
|
|
586
|
+
if (parsed.tool === 'mcp') {
|
|
587
|
+
return canonicalToCopilotMcpApproval(parsed);
|
|
588
|
+
}
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
function mergeCopilotApprovals(existing, incoming) {
|
|
592
|
+
const byKey = new Map();
|
|
593
|
+
const add = (approval) => {
|
|
594
|
+
if (approval.kind === 'commands') {
|
|
595
|
+
const current = byKey.get('commands');
|
|
596
|
+
const commandIdentifiers = new Set([...(current?.commandIdentifiers ?? []), ...approval.commandIdentifiers]);
|
|
597
|
+
byKey.set('commands', { kind: 'commands', commandIdentifiers: Array.from(commandIdentifiers).sort() });
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
byKey.set(JSON.stringify(approval), approval);
|
|
601
|
+
};
|
|
602
|
+
for (const approval of existing) {
|
|
603
|
+
if (!approval || typeof approval !== 'object' || Array.isArray(approval))
|
|
604
|
+
continue;
|
|
605
|
+
const record = approval;
|
|
606
|
+
if (record.kind === 'commands' && Array.isArray(record.commandIdentifiers)) {
|
|
607
|
+
add({ kind: 'commands', commandIdentifiers: record.commandIdentifiers.filter((v) => typeof v === 'string') });
|
|
608
|
+
}
|
|
609
|
+
else if (record.kind === 'read' || record.kind === 'write') {
|
|
610
|
+
add({ kind: record.kind });
|
|
611
|
+
}
|
|
612
|
+
else if (record.kind === 'mcp' && typeof record.serverName === 'string' && (typeof record.toolName === 'string' || record.toolName === null)) {
|
|
613
|
+
add({ kind: 'mcp', serverName: record.serverName, toolName: record.toolName });
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
for (const approval of incoming)
|
|
617
|
+
add(approval);
|
|
618
|
+
return Array.from(byKey.values());
|
|
619
|
+
}
|
|
620
|
+
export function convertToCopilotFormat(set, location) {
|
|
621
|
+
const approvals = [];
|
|
622
|
+
for (const permission of set.allow) {
|
|
623
|
+
const approval = canonicalToCopilotApproval(permission);
|
|
624
|
+
if (approval)
|
|
625
|
+
approvals.push(approval);
|
|
626
|
+
}
|
|
627
|
+
const allowedDirectories = (set.additionalDirectories ?? [])
|
|
628
|
+
.filter((dir) => dir.trim().length > 0)
|
|
629
|
+
.map((dir) => path.isAbsolute(dir) ? dir : path.resolve(location, dir));
|
|
630
|
+
return {
|
|
631
|
+
locations: {
|
|
632
|
+
[location]: {
|
|
633
|
+
...(approvals.length > 0 ? { tool_approvals: mergeCopilotApprovals([], approvals) } : {}),
|
|
634
|
+
...(allowedDirectories.length > 0 ? { allowed_directories: Array.from(new Set(allowedDirectories)).sort() } : {}),
|
|
635
|
+
},
|
|
636
|
+
},
|
|
637
|
+
};
|
|
638
|
+
}
|
|
543
639
|
/** Convert canonical Bash rules into Droid's command arrays. */
|
|
544
640
|
export function convertToDroidFormat(set) {
|
|
545
641
|
const commands = (permissions) => {
|
|
@@ -614,6 +710,83 @@ export function convertToOpenClawFormat(set) {
|
|
|
614
710
|
deny: map(set.deny ?? []),
|
|
615
711
|
};
|
|
616
712
|
}
|
|
713
|
+
const FORGE_OPERATION_BY_CANONICAL = {
|
|
714
|
+
bash: 'command',
|
|
715
|
+
read: 'read',
|
|
716
|
+
grep: 'read',
|
|
717
|
+
glob: 'read',
|
|
718
|
+
write: 'write',
|
|
719
|
+
edit: 'write',
|
|
720
|
+
notebookedit: 'write',
|
|
721
|
+
webfetch: 'url',
|
|
722
|
+
websearch: 'url',
|
|
723
|
+
};
|
|
724
|
+
function canonicalToForgePolicy(perm, permission) {
|
|
725
|
+
if (BLANKET_BASH_FORMS.has(perm)) {
|
|
726
|
+
return { permission, rule: { command: '*' } };
|
|
727
|
+
}
|
|
728
|
+
const parsed = parseCanonicalPattern(perm);
|
|
729
|
+
if (!parsed)
|
|
730
|
+
return null;
|
|
731
|
+
const operation = FORGE_OPERATION_BY_CANONICAL[parsed.tool];
|
|
732
|
+
if (!operation)
|
|
733
|
+
return null;
|
|
734
|
+
const pattern = parsed.tool === 'bash'
|
|
735
|
+
? normalizeBashPattern(parsed.pattern)
|
|
736
|
+
: (parsed.tool === 'webfetch' || parsed.tool === 'websearch') && parsed.pattern.startsWith('domain:')
|
|
737
|
+
? `${parsed.pattern.slice('domain:'.length)}*`
|
|
738
|
+
: parsed.pattern === '**'
|
|
739
|
+
? '**/*'
|
|
740
|
+
: parsed.pattern;
|
|
741
|
+
return { permission, rule: { [operation]: pattern } };
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Convert canonical permissions to ForgeCode's permissions.yaml policy list.
|
|
745
|
+
*
|
|
746
|
+
* ForgeCode gates built-in tools by operation family (`read`, `write`,
|
|
747
|
+
* `command`, `url`) plus glob patterns, optionally scoped by `dir`. The policy
|
|
748
|
+
* file is ignored unless `.forge.toml` has `restricted = true`, and MCP tools
|
|
749
|
+
* bypass permissions.yaml entirely; agents-cli therefore maps only canonical
|
|
750
|
+
* built-in allow/deny rules and does not try to express per-named-MCP-tool
|
|
751
|
+
* grants.
|
|
752
|
+
*/
|
|
753
|
+
export function convertToForgeFormat(set) {
|
|
754
|
+
const policies = [];
|
|
755
|
+
const seen = new Set();
|
|
756
|
+
const add = (policy) => {
|
|
757
|
+
if (!policy)
|
|
758
|
+
return;
|
|
759
|
+
const key = `${policy.permission}|${JSON.stringify(policy.rule)}`;
|
|
760
|
+
if (seen.has(key))
|
|
761
|
+
return;
|
|
762
|
+
seen.add(key);
|
|
763
|
+
policies.push(policy);
|
|
764
|
+
};
|
|
765
|
+
for (const perm of set.allow)
|
|
766
|
+
add(canonicalToForgePolicy(perm, 'allow'));
|
|
767
|
+
for (const perm of set.deny ?? [])
|
|
768
|
+
add(canonicalToForgePolicy(perm, 'deny'));
|
|
769
|
+
return { policies };
|
|
770
|
+
}
|
|
771
|
+
export function convertToHermesFormat(set) {
|
|
772
|
+
const commands = (permissions) => {
|
|
773
|
+
const out = new Set();
|
|
774
|
+
for (const perm of permissions) {
|
|
775
|
+
if (BLANKET_BASH_FORMS.has(perm)) {
|
|
776
|
+
out.add('*');
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
const parsed = parseCanonicalPattern(perm);
|
|
780
|
+
if (parsed?.tool === 'bash')
|
|
781
|
+
out.add(normalizeBashPattern(parsed.pattern));
|
|
782
|
+
}
|
|
783
|
+
return Array.from(out).sort();
|
|
784
|
+
};
|
|
785
|
+
return {
|
|
786
|
+
command_allowlist: commands(set.allow),
|
|
787
|
+
approvals: { deny: commands(set.deny ?? []) },
|
|
788
|
+
};
|
|
789
|
+
}
|
|
617
790
|
/**
|
|
618
791
|
* Convert canonical permission set to Antigravity format.
|
|
619
792
|
* Antigravity reads ~/.gemini/antigravity-cli/settings.json with
|
|
@@ -1144,8 +1317,9 @@ export function applyClaudePermissions(set, scope = 'user', cwd, merge = true) {
|
|
|
1144
1317
|
const newPermissions = convertToClaudeFormat(set);
|
|
1145
1318
|
if (merge && config.permissions) {
|
|
1146
1319
|
const existing = config.permissions;
|
|
1147
|
-
|
|
1148
|
-
const
|
|
1320
|
+
// Rewrite stale Write(path) rules already installed in settings.json too.
|
|
1321
|
+
const mergedAllow = new Set([...(existing.allow || []).map(canonicalToClaudeRule), ...newPermissions.permissions.allow]);
|
|
1322
|
+
const mergedDeny = new Set([...(existing.deny || []).map(canonicalToClaudeRule), ...newPermissions.permissions.deny]);
|
|
1149
1323
|
config.permissions = {
|
|
1150
1324
|
allow: [...mergedAllow],
|
|
1151
1325
|
deny: [...mergedDeny],
|
|
@@ -1292,7 +1466,7 @@ function applyPermissionsToAgent(agentId, set, scope = 'user', cwd, merge = true
|
|
|
1292
1466
|
* Apply a permission set to a specific version's home directory.
|
|
1293
1467
|
* This writes to {versionHome}/.{agent}/settings.json (or equivalent).
|
|
1294
1468
|
*/
|
|
1295
|
-
export function applyPermissionsToVersion(agentId, set, versionHome, merge = true) {
|
|
1469
|
+
export function applyPermissionsToVersion(agentId, set, versionHome, merge = true, cwd) {
|
|
1296
1470
|
const configDir = path.join(versionHome, agentConfigDirName(agentId));
|
|
1297
1471
|
try {
|
|
1298
1472
|
fs.mkdirSync(configDir, { recursive: true });
|
|
@@ -1305,8 +1479,9 @@ export function applyPermissionsToVersion(agentId, set, versionHome, merge = tru
|
|
|
1305
1479
|
const newPermissions = convertToClaudeFormat(set);
|
|
1306
1480
|
if (merge && config.permissions) {
|
|
1307
1481
|
const existing = config.permissions;
|
|
1308
|
-
|
|
1309
|
-
const
|
|
1482
|
+
// Rewrite stale Write(path) rules already installed in settings.json too.
|
|
1483
|
+
const mergedAllow = new Set([...(existing.allow || []).map(canonicalToClaudeRule), ...newPermissions.permissions.allow]);
|
|
1484
|
+
const mergedDeny = new Set([...(existing.deny || []).map(canonicalToClaudeRule), ...newPermissions.permissions.deny]);
|
|
1310
1485
|
const mergedDirs = new Set([...(existing.additionalDirectories || []), ...(newPermissions.permissions.additionalDirectories || [])]);
|
|
1311
1486
|
const perms = {
|
|
1312
1487
|
allow: [...mergedAllow],
|
|
@@ -1593,6 +1768,50 @@ export function applyPermissionsToVersion(agentId, set, versionHome, merge = tru
|
|
|
1593
1768
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
1594
1769
|
return { success: true };
|
|
1595
1770
|
}
|
|
1771
|
+
if (agentId === 'copilot') {
|
|
1772
|
+
const configPath = path.join(versionHome, '.copilot', 'permissions-config.json');
|
|
1773
|
+
let config = { locations: {} };
|
|
1774
|
+
if (fs.existsSync(configPath)) {
|
|
1775
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
1776
|
+
}
|
|
1777
|
+
const location = path.resolve(cwd ?? process.cwd());
|
|
1778
|
+
const converted = convertToCopilotFormat(set, location);
|
|
1779
|
+
const incoming = converted.locations[location]?.tool_approvals ?? [];
|
|
1780
|
+
const incomingDirectories = converted.locations[location]?.allowed_directories ?? [];
|
|
1781
|
+
if (merge && incoming.length === 0 && incomingDirectories.length === 0)
|
|
1782
|
+
return { success: true };
|
|
1783
|
+
const locations = (typeof config.locations === 'object' && config.locations !== null && !Array.isArray(config.locations))
|
|
1784
|
+
? config.locations
|
|
1785
|
+
: {};
|
|
1786
|
+
const existingLocation = (typeof locations[location] === 'object' && locations[location] !== null && !Array.isArray(locations[location]))
|
|
1787
|
+
? locations[location]
|
|
1788
|
+
: {};
|
|
1789
|
+
const existingApprovals = Array.isArray(existingLocation.tool_approvals) ? existingLocation.tool_approvals : [];
|
|
1790
|
+
const nextApprovals = merge ? mergeCopilotApprovals(existingApprovals, incoming) : incoming;
|
|
1791
|
+
const existingDirectories = Array.isArray(existingLocation.allowed_directories)
|
|
1792
|
+
? existingLocation.allowed_directories.filter((v) => typeof v === 'string')
|
|
1793
|
+
: [];
|
|
1794
|
+
const nextDirectories = merge
|
|
1795
|
+
? Array.from(new Set([...existingDirectories, ...incomingDirectories])).sort()
|
|
1796
|
+
: incomingDirectories;
|
|
1797
|
+
const nextLocation = { ...existingLocation };
|
|
1798
|
+
if (nextApprovals.length > 0)
|
|
1799
|
+
nextLocation.tool_approvals = nextApprovals;
|
|
1800
|
+
else
|
|
1801
|
+
delete nextLocation.tool_approvals;
|
|
1802
|
+
if (nextDirectories.length > 0)
|
|
1803
|
+
nextLocation.allowed_directories = nextDirectories;
|
|
1804
|
+
else
|
|
1805
|
+
delete nextLocation.allowed_directories;
|
|
1806
|
+
if (Object.keys(nextLocation).length > 0)
|
|
1807
|
+
locations[location] = nextLocation;
|
|
1808
|
+
else
|
|
1809
|
+
delete locations[location];
|
|
1810
|
+
config.locations = locations;
|
|
1811
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
1812
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
1813
|
+
return { success: true };
|
|
1814
|
+
}
|
|
1596
1815
|
if (agentId === 'kiro') {
|
|
1597
1816
|
const permissionsPath = path.join(versionHome, '.kiro', 'settings', 'permissions.yaml');
|
|
1598
1817
|
let config = {};
|
|
@@ -1669,6 +1888,64 @@ export function applyPermissionsToVersion(agentId, set, versionHome, merge = tru
|
|
|
1669
1888
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
1670
1889
|
return { success: true };
|
|
1671
1890
|
}
|
|
1891
|
+
if (agentId === 'forge') {
|
|
1892
|
+
const permissionsPath = path.join(versionHome, '.forge', 'permissions.yaml');
|
|
1893
|
+
let config = {};
|
|
1894
|
+
if (fs.existsSync(permissionsPath)) {
|
|
1895
|
+
const parsed = yaml.parse(fs.readFileSync(permissionsPath, 'utf-8'));
|
|
1896
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
1897
|
+
config = parsed;
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
const newPolicies = convertToForgeFormat(set).policies;
|
|
1901
|
+
if (merge) {
|
|
1902
|
+
const existingPolicies = Array.isArray(config.policies) ? config.policies : [];
|
|
1903
|
+
const seen = new Set();
|
|
1904
|
+
config.policies = [...existingPolicies, ...newPolicies].filter((policy) => {
|
|
1905
|
+
if (!policy || typeof policy !== 'object' || Array.isArray(policy))
|
|
1906
|
+
return false;
|
|
1907
|
+
const key = JSON.stringify(policy);
|
|
1908
|
+
if (seen.has(key))
|
|
1909
|
+
return false;
|
|
1910
|
+
seen.add(key);
|
|
1911
|
+
return true;
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
else {
|
|
1915
|
+
config.policies = newPolicies;
|
|
1916
|
+
}
|
|
1917
|
+
fs.mkdirSync(path.dirname(permissionsPath), { recursive: true });
|
|
1918
|
+
fs.writeFileSync(permissionsPath, yaml.stringify(config), 'utf-8');
|
|
1919
|
+
return { success: true };
|
|
1920
|
+
}
|
|
1921
|
+
if (agentId === 'hermes') {
|
|
1922
|
+
const configPath = path.join(versionHome, '.hermes', 'config.yaml');
|
|
1923
|
+
let config = {};
|
|
1924
|
+
if (fs.existsSync(configPath)) {
|
|
1925
|
+
const parsed = yaml.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
1926
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
1927
|
+
config = parsed;
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
const converted = convertToHermesFormat(set);
|
|
1931
|
+
const approvals = (typeof config.approvals === 'object' && config.approvals !== null && !Array.isArray(config.approvals))
|
|
1932
|
+
? config.approvals
|
|
1933
|
+
: {};
|
|
1934
|
+
if (merge) {
|
|
1935
|
+
const existingAllow = Array.isArray(config.command_allowlist) ? config.command_allowlist : [];
|
|
1936
|
+
const existingDeny = Array.isArray(approvals.deny) ? approvals.deny : [];
|
|
1937
|
+
config.command_allowlist = Array.from(new Set([...existingAllow, ...converted.command_allowlist])).sort();
|
|
1938
|
+
approvals.deny = Array.from(new Set([...existingDeny, ...converted.approvals.deny])).sort();
|
|
1939
|
+
}
|
|
1940
|
+
else {
|
|
1941
|
+
config.command_allowlist = converted.command_allowlist;
|
|
1942
|
+
approvals.deny = converted.approvals.deny;
|
|
1943
|
+
}
|
|
1944
|
+
config.approvals = approvals;
|
|
1945
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
1946
|
+
fs.writeFileSync(configPath, yaml.stringify(config), 'utf-8');
|
|
1947
|
+
return { success: true };
|
|
1948
|
+
}
|
|
1672
1949
|
return { success: false, error: `Agent '${agentId}' does not support permissions` };
|
|
1673
1950
|
}
|
|
1674
1951
|
catch (err) {
|
|
@@ -8,18 +8,12 @@
|
|
|
8
8
|
* (+ per-agent symlinks). Delegates to compileRulesForProject, which is
|
|
9
9
|
* the same helper management-side `agents sync` uses.
|
|
10
10
|
*
|
|
11
|
-
* 2.
|
|
12
|
-
* into the agent's workspace-local discovery
|
|
13
|
-
* `<cwd>/.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* uses `~/.config/amp`, antigravity uses `~/.gemini/antigravity-cli`,
|
|
18
|
-
* codex/gemini/cursor lack subagent support entirely) — they're follow-up
|
|
19
|
-
* material. NOTE: `.mcp.json` is intentionally NOT auto-symlinked from
|
|
20
|
-
* the launch path — that's a supply-chain surface (cloning a hostile
|
|
21
|
-
* repo would auto-register an attacker MCP server). Belongs to full
|
|
22
|
-
* `agents sync` with explicit opt-in, not the hot path.
|
|
11
|
+
* 2. Copy project resources from `<cwd>/.agents/{commands,skills,subagents,workflows}`
|
|
12
|
+
* into the agent's workspace-local discovery dir (`<cwd>/.claude/`,
|
|
13
|
+
* `<cwd>/.codex/`, etc.) with an ownership manifest at
|
|
14
|
+
* `<cwd>/.{agent}/.agents-managed.json`. The manifest is the only clobber
|
|
15
|
+
* authority: paths it lists are removed and refreshed, pre-existing paths it
|
|
16
|
+
* does not list are skipped as user-owned.
|
|
23
17
|
*
|
|
24
18
|
* 3. Synthesize four scope-grouped plugin marketplaces under the version's
|
|
25
19
|
* `<versionHome>/.{agent}/plugins/marketplaces/` (for plugin-capable agents):
|
|
@@ -8,18 +8,12 @@
|
|
|
8
8
|
* (+ per-agent symlinks). Delegates to compileRulesForProject, which is
|
|
9
9
|
* the same helper management-side `agents sync` uses.
|
|
10
10
|
*
|
|
11
|
-
* 2.
|
|
12
|
-
* into the agent's workspace-local discovery
|
|
13
|
-
* `<cwd>/.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* uses `~/.config/amp`, antigravity uses `~/.gemini/antigravity-cli`,
|
|
18
|
-
* codex/gemini/cursor lack subagent support entirely) — they're follow-up
|
|
19
|
-
* material. NOTE: `.mcp.json` is intentionally NOT auto-symlinked from
|
|
20
|
-
* the launch path — that's a supply-chain surface (cloning a hostile
|
|
21
|
-
* repo would auto-register an attacker MCP server). Belongs to full
|
|
22
|
-
* `agents sync` with explicit opt-in, not the hot path.
|
|
11
|
+
* 2. Copy project resources from `<cwd>/.agents/{commands,skills,subagents,workflows}`
|
|
12
|
+
* into the agent's workspace-local discovery dir (`<cwd>/.claude/`,
|
|
13
|
+
* `<cwd>/.codex/`, etc.) with an ownership manifest at
|
|
14
|
+
* `<cwd>/.{agent}/.agents-managed.json`. The manifest is the only clobber
|
|
15
|
+
* authority: paths it lists are removed and refreshed, pre-existing paths it
|
|
16
|
+
* does not list are skipped as user-owned.
|
|
23
17
|
*
|
|
24
18
|
* 3. Synthesize four scope-grouped plugin marketplaces under the version's
|
|
25
19
|
* `<versionHome>/.{agent}/plugins/marketplaces/` (for plugin-capable agents):
|
|
@@ -48,8 +42,8 @@ import { supports } from './capabilities.js';
|
|
|
48
42
|
import { getEnabledExtraRepos, getExtraPluginsDir, getPluginsDir, getProjectAgentsDir, getProjectPluginsDir, getSystemPluginsDir, } from './state.js';
|
|
49
43
|
import { getVersionHomePath } from './versions.js';
|
|
50
44
|
import { toPortableKey } from './platform/index.js';
|
|
51
|
-
import { transformSubagentForClaude } from './subagents.js';
|
|
52
45
|
import { compileRulesForProject } from './rules/compile.js';
|
|
46
|
+
import { syncProjectResourcesToAgent } from './project-resources.js';
|
|
53
47
|
import { discoverPluginsInDir, hasPluginExecSurfaces, inspectPluginCapabilities } from './plugins.js';
|
|
54
48
|
import { MARKETPLACE_NAME, PROJECT_MARKETPLACE_NAME, SYSTEM_MARKETPLACE_NAME, addPluginToSettings, copyPluginToMarketplace, marketplaceNameFor, marketplaceRoot, pluginInstallDir, registerMarketplace, removePluginFromSettings, syncMarketplaceManifest, } from './plugin-marketplace.js';
|
|
55
49
|
/**
|
|
@@ -77,9 +71,12 @@ export function runLaunchSync(opts) {
|
|
|
77
71
|
// Don't fail launch on a malformed project rules.yaml.
|
|
78
72
|
}
|
|
79
73
|
// Step 2: workspace resource mirror
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
74
|
+
const projectAgentsDir = getProjectAgentsDir(opts.cwd);
|
|
75
|
+
if (projectAgentsDir) {
|
|
76
|
+
const projectResources = syncProjectResourcesToAgent(opts.agent, opts.version, projectAgentsDir);
|
|
77
|
+
result.workspaceLinks = projectResources.synced.length;
|
|
78
|
+
result.workspaceSkipped = projectResources.skipped;
|
|
79
|
+
}
|
|
83
80
|
// Step 3: scoped plugin marketplaces
|
|
84
81
|
result.marketplaces = synthesizeScopedMarketplaces(opts.agent, opts.version, opts.cwd);
|
|
85
82
|
// Touch the shim's skip-fast sentinel. Best-effort — if this fails the
|
|
@@ -117,218 +114,6 @@ function touchLaunchSentinel(agent, version, cwd) {
|
|
|
117
114
|
// best-effort
|
|
118
115
|
}
|
|
119
116
|
}
|
|
120
|
-
const CLAUDE_MIRROR_PLANS = [
|
|
121
|
-
{ srcSubdir: 'subagents', destSubdir: 'agents', mode: 'subagent-write' },
|
|
122
|
-
{ srcSubdir: 'commands', destSubdir: 'commands', mode: 'file-symlink' },
|
|
123
|
-
{ srcSubdir: 'skills', destSubdir: 'skills', mode: 'dir-symlink' },
|
|
124
|
-
];
|
|
125
|
-
/**
|
|
126
|
-
* Marker prepended-as-trailing-comment to every subagent file WE generate.
|
|
127
|
-
* It's an HTML comment — invisible to the markdown the agent reads — placed on
|
|
128
|
-
* the last line so it never disturbs the leading `---` frontmatter block.
|
|
129
|
-
*
|
|
130
|
-
* Ownership rule (the one don't-clobber decision for written, non-symlink
|
|
131
|
-
* files): we only overwrite a `.claude/agents/<name>.md` whose content carries
|
|
132
|
-
* this marker. A user-authored file (no marker) or a symlink at the dest is
|
|
133
|
-
* left untouched. A marker beats an mtime/sidecar sentinel because it travels
|
|
134
|
-
* with the file across copies and git, and needs no out-of-band state.
|
|
135
|
-
*/
|
|
136
|
-
const GENERATED_SUBAGENT_MARKER = '<!-- agents-cli:generated-subagent';
|
|
137
|
-
function mirrorWorkspaceResources(cwd, agent) {
|
|
138
|
-
// v1: claude-only. Other agents have workspace conventions we haven't
|
|
139
|
-
// mapped (amp: ~/.config/amp; antigravity: ~/.gemini/antigravity-cli;
|
|
140
|
-
// codex/gemini/cursor: no subagent support per capabilities). Adding them
|
|
141
|
-
// requires per-agent workspaceDirName + capability gates — follow-up.
|
|
142
|
-
if (agent !== 'claude')
|
|
143
|
-
return { links: 0, skipped: [] };
|
|
144
|
-
const projectAgentsDir = getProjectAgentsDir(cwd);
|
|
145
|
-
if (!projectAgentsDir)
|
|
146
|
-
return { links: 0, skipped: [] };
|
|
147
|
-
const agentWorkspaceDir = path.join(cwd, '.claude');
|
|
148
|
-
const projectAgentsResolved = (() => {
|
|
149
|
-
try {
|
|
150
|
-
return fs.realpathSync(projectAgentsDir);
|
|
151
|
-
}
|
|
152
|
-
catch {
|
|
153
|
-
return path.resolve(projectAgentsDir);
|
|
154
|
-
}
|
|
155
|
-
})();
|
|
156
|
-
let links = 0;
|
|
157
|
-
const skipped = [];
|
|
158
|
-
// Mirror subagents / commands / skills. mcp.json is intentionally excluded
|
|
159
|
-
// — see header doc, it's a supply-chain surface.
|
|
160
|
-
for (const plan of CLAUDE_MIRROR_PLANS) {
|
|
161
|
-
const srcDir = path.join(projectAgentsDir, plan.srcSubdir);
|
|
162
|
-
if (!fs.existsSync(srcDir))
|
|
163
|
-
continue;
|
|
164
|
-
const destDir = path.join(agentWorkspaceDir, plan.destSubdir);
|
|
165
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
166
|
-
// Subagents flatten N source files into one written .md — not a symlink.
|
|
167
|
-
if (plan.mode === 'subagent-write') {
|
|
168
|
-
const r = writeProjectSubagents(srcDir, destDir, cwd);
|
|
169
|
-
links += r.links;
|
|
170
|
-
skipped.push(...r.skipped);
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
174
|
-
for (const entry of entries) {
|
|
175
|
-
if (entry.name.startsWith('.'))
|
|
176
|
-
continue;
|
|
177
|
-
if (plan.mode === 'dir-symlink' && !entry.isDirectory())
|
|
178
|
-
continue;
|
|
179
|
-
if (plan.mode === 'file-symlink' && !entry.isFile() && !entry.isSymbolicLink())
|
|
180
|
-
continue;
|
|
181
|
-
const srcPath = path.join(srcDir, entry.name);
|
|
182
|
-
const destPath = path.join(destDir, entry.name);
|
|
183
|
-
if (replaceWithSymlinkIfOwned(srcPath, destPath, projectAgentsResolved)) {
|
|
184
|
-
links += 1;
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
skipped.push(path.relative(cwd, destPath));
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return { links, skipped };
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* Mirror project subagents into `<cwd>/.claude/agents/`. The canonical source
|
|
195
|
-
* shape is a DIRECTORY containing AGENT.md (e.g. `.agents/subagents/probe/AGENT.md`)
|
|
196
|
-
* — confirmed by the detector (versions.ts) and lister (subagents.ts). Each
|
|
197
|
-
* such directory is flattened via transformSubagentForClaude (the exact writer
|
|
198
|
-
* the version-home sync uses) into a single `<name>.md`, then written under an
|
|
199
|
-
* ownership marker so a re-launch refreshes our file but never clobbers a
|
|
200
|
-
* user-authored one.
|
|
201
|
-
*
|
|
202
|
-
* Returns the same {links, skipped} shape the symlink path reports, so the
|
|
203
|
-
* caller's accounting is uniform across resource kinds.
|
|
204
|
-
*/
|
|
205
|
-
function writeProjectSubagents(srcDir, destDir, cwd) {
|
|
206
|
-
let links = 0;
|
|
207
|
-
const skipped = [];
|
|
208
|
-
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
209
|
-
if (entry.name.startsWith('.'))
|
|
210
|
-
continue;
|
|
211
|
-
if (!entry.isDirectory())
|
|
212
|
-
continue;
|
|
213
|
-
const subagentDir = path.join(srcDir, entry.name);
|
|
214
|
-
if (!fs.existsSync(path.join(subagentDir, 'AGENT.md')))
|
|
215
|
-
continue;
|
|
216
|
-
const destPath = path.join(destDir, `${entry.name}.md`);
|
|
217
|
-
if (writeSubagentIfOwned(subagentDir, destPath)) {
|
|
218
|
-
links += 1;
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
skipped.push(path.relative(cwd, destPath));
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
return { links, skipped };
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* Write a flattened subagent file at `destPath`, refusing to clobber user state.
|
|
228
|
-
*
|
|
229
|
-
* - dest missing → write fresh.
|
|
230
|
-
* - dest is our generation → overwrite (refresh; carries GENERATED_SUBAGENT_MARKER).
|
|
231
|
-
* - dest is a symlink / any
|
|
232
|
-
* non-regular file → SKIP (user state we don't own).
|
|
233
|
-
* - dest is a regular file
|
|
234
|
-
* without our marker → SKIP (hand-authored .claude/agents/<name>.md).
|
|
235
|
-
*
|
|
236
|
-
* Returns true when our file is present (written now or already current),
|
|
237
|
-
* false when we left a user-owned dest alone.
|
|
238
|
-
*/
|
|
239
|
-
function writeSubagentIfOwned(subagentDir, destPath) {
|
|
240
|
-
let existing = null;
|
|
241
|
-
let destLstat = null;
|
|
242
|
-
try {
|
|
243
|
-
destLstat = fs.lstatSync(destPath);
|
|
244
|
-
}
|
|
245
|
-
catch { /* missing — write fresh */ }
|
|
246
|
-
if (destLstat) {
|
|
247
|
-
if (!destLstat.isFile())
|
|
248
|
-
return false; // symlink/dir/etc. — user state
|
|
249
|
-
try {
|
|
250
|
-
existing = fs.readFileSync(destPath, 'utf-8');
|
|
251
|
-
}
|
|
252
|
-
catch {
|
|
253
|
-
return false;
|
|
254
|
-
}
|
|
255
|
-
if (!existing.includes(GENERATED_SUBAGENT_MARKER))
|
|
256
|
-
return false; // hand-authored
|
|
257
|
-
}
|
|
258
|
-
let body;
|
|
259
|
-
try {
|
|
260
|
-
body = transformSubagentForClaude(subagentDir);
|
|
261
|
-
}
|
|
262
|
-
catch {
|
|
263
|
-
return false; // malformed AGENT.md — don't write a broken file
|
|
264
|
-
}
|
|
265
|
-
const content = `${body}\n\n${GENERATED_SUBAGENT_MARKER} — edit .agents/subagents/${path.basename(subagentDir)}/ instead -->\n`;
|
|
266
|
-
// Skip-fast: identical content already on disk → no write (keeps mtime stable).
|
|
267
|
-
if (existing === content)
|
|
268
|
-
return true;
|
|
269
|
-
try {
|
|
270
|
-
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
271
|
-
fs.writeFileSync(destPath, content);
|
|
272
|
-
}
|
|
273
|
-
catch {
|
|
274
|
-
return false;
|
|
275
|
-
}
|
|
276
|
-
return true;
|
|
277
|
-
}
|
|
278
|
-
/**
|
|
279
|
-
* Create or refresh a symlink at `destPath` pointing at `srcPath`. Returns
|
|
280
|
-
* true if we wrote (or already had) the link, false if we skipped because
|
|
281
|
-
* the destination is user-owned (regular file, directory, or symlink pointing
|
|
282
|
-
* outside the project's `.agents/` tree, or a dangling symlink — treated as
|
|
283
|
-
* user state we don't yet understand).
|
|
284
|
-
*
|
|
285
|
-
* Skip-fast: if the destination is already a symlink resolving to the
|
|
286
|
-
* project-agents tree AND its target matches srcPath, no write happens.
|
|
287
|
-
*/
|
|
288
|
-
function replaceWithSymlinkIfOwned(srcPath, destPath, projectAgentsResolved) {
|
|
289
|
-
let destLstat = null;
|
|
290
|
-
try {
|
|
291
|
-
destLstat = fs.lstatSync(destPath);
|
|
292
|
-
}
|
|
293
|
-
catch { /* missing — write fresh */ }
|
|
294
|
-
if (destLstat) {
|
|
295
|
-
if (!destLstat.isSymbolicLink()) {
|
|
296
|
-
return false;
|
|
297
|
-
}
|
|
298
|
-
let destTargetReal = null;
|
|
299
|
-
try {
|
|
300
|
-
destTargetReal = fs.realpathSync(destPath);
|
|
301
|
-
}
|
|
302
|
-
catch { /* dangling */ }
|
|
303
|
-
// Dangling symlink → user-owned in-progress state; do not clobber.
|
|
304
|
-
if (destTargetReal === null) {
|
|
305
|
-
return false;
|
|
306
|
-
}
|
|
307
|
-
if (destTargetReal !== projectAgentsResolved && !destTargetReal.startsWith(projectAgentsResolved + path.sep)) {
|
|
308
|
-
return false;
|
|
309
|
-
}
|
|
310
|
-
let srcReal = null;
|
|
311
|
-
try {
|
|
312
|
-
srcReal = fs.realpathSync(srcPath);
|
|
313
|
-
}
|
|
314
|
-
catch { /* src vanished mid-launch */ }
|
|
315
|
-
if (srcReal !== null && destTargetReal === srcReal) {
|
|
316
|
-
return true; // already correct
|
|
317
|
-
}
|
|
318
|
-
try {
|
|
319
|
-
fs.unlinkSync(destPath);
|
|
320
|
-
}
|
|
321
|
-
catch { /* ignore */ }
|
|
322
|
-
}
|
|
323
|
-
try {
|
|
324
|
-
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
325
|
-
fs.symlinkSync(srcPath, destPath);
|
|
326
|
-
}
|
|
327
|
-
catch {
|
|
328
|
-
return false;
|
|
329
|
-
}
|
|
330
|
-
return true;
|
|
331
|
-
}
|
|
332
117
|
function makeScope(spec, pluginsDir, autoEnableExecSurfaces, precedence) {
|
|
333
118
|
return { spec, marketplaceName: marketplaceNameFor(spec), pluginsDir, autoEnableExecSurfaces, precedence };
|
|
334
119
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AgentId } from './types.js';
|
|
2
|
+
export interface ProjectResourceSyncResult {
|
|
3
|
+
synced: string[];
|
|
4
|
+
skipped: string[];
|
|
5
|
+
}
|
|
6
|
+
export declare function projectAgentRoot(projectRoot: string, agent: AgentId): string;
|
|
7
|
+
export declare function syncProjectResourcesToAgent(agent: AgentId, version: string, projectAgentsDir: string): ProjectResourceSyncResult;
|