agents-inc 0.170.1 → 0.171.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/dist/{chunk-52HDHJ7I.js → chunk-24OD7IP4.js} +32 -21
- package/dist/chunk-24OD7IP4.js.map +1 -0
- package/dist/{chunk-MQ6CGZNJ.js → chunk-3YYHL76L.js} +1879 -2019
- package/dist/chunk-3YYHL76L.js.map +1 -0
- package/dist/chunk-4RQKCV6I.js +103 -0
- package/dist/chunk-4RQKCV6I.js.map +1 -0
- package/dist/{chunk-USVBYQSQ.js → chunk-5FM77WKW.js} +16 -6
- package/dist/chunk-5FM77WKW.js.map +1 -0
- package/dist/{chunk-HED2DAXZ.js → chunk-7IVLVEHX.js} +5 -5
- package/dist/chunk-7IVLVEHX.js.map +1 -0
- package/dist/{chunk-PJWGTNOX.js → chunk-A6OWEJGC.js} +144 -186
- package/dist/chunk-A6OWEJGC.js.map +1 -0
- package/dist/{chunk-BMRUNMPV.js → chunk-AWWCRL5A.js} +35 -35
- package/dist/chunk-AWWCRL5A.js.map +1 -0
- package/dist/{chunk-F2BHT5C4.js → chunk-EOKNEGRD.js} +129 -151
- package/dist/chunk-EOKNEGRD.js.map +1 -0
- package/dist/{chunk-NQW25DCF.js → chunk-GHHJYNAN.js} +1 -7
- package/dist/chunk-GHHJYNAN.js.map +1 -0
- package/dist/chunk-JGPE4RRE.js +35 -0
- package/dist/chunk-JGPE4RRE.js.map +1 -0
- package/dist/{chunk-JFLVEFFP.js → chunk-KIK5EZYD.js} +1 -1
- package/dist/chunk-KIK5EZYD.js.map +1 -0
- package/dist/{chunk-NCWDK6L7.js → chunk-KMOCMVK5.js} +254 -379
- package/dist/chunk-KMOCMVK5.js.map +1 -0
- package/dist/{chunk-4AAOOAIN.js → chunk-P6CYS4JK.js} +2 -2
- package/dist/chunk-P6CYS4JK.js.map +1 -0
- package/dist/chunk-QIR56IDM.js +1169 -0
- package/dist/chunk-QIR56IDM.js.map +1 -0
- package/dist/{chunk-6ZRH5RHC.js → chunk-R572RLTI.js} +4 -4
- package/dist/chunk-R572RLTI.js.map +1 -0
- package/dist/{chunk-WTWMGH2C.js → chunk-RDXY4GRO.js} +2 -2
- package/dist/chunk-RDXY4GRO.js.map +1 -0
- package/dist/{chunk-7RDMTR64.js → chunk-S2SKVJT2.js} +785 -256
- package/dist/chunk-S2SKVJT2.js.map +1 -0
- package/dist/{chunk-SZYQISA2.js → chunk-TNFYBKVD.js} +43 -29
- package/dist/chunk-TNFYBKVD.js.map +1 -0
- package/dist/{chunk-RIPSGNLU.js → chunk-W4KAY26P.js} +3 -3
- package/dist/chunk-W4KAY26P.js.map +1 -0
- package/dist/{chunk-OYJQUA3D.js → chunk-WDH6HHA3.js} +31 -13
- package/dist/chunk-WDH6HHA3.js.map +1 -0
- package/dist/{chunk-3G7X2WKI.js → chunk-WII6GOPM.js} +18 -38
- package/dist/chunk-WII6GOPM.js.map +1 -0
- package/dist/{chunk-F5EBEATU.js → chunk-WM2N2D6Q.js} +34 -46
- package/dist/chunk-WM2N2D6Q.js.map +1 -0
- package/dist/{chunk-KOLDW6SF.js → chunk-YLUOGAW7.js} +2 -2
- package/dist/chunk-YLUOGAW7.js.map +1 -0
- package/dist/{chunk-44TE3RKH.js → chunk-ZC6GC66J.js} +5 -4
- package/dist/chunk-ZC6GC66J.js.map +1 -0
- package/dist/{chunk-NEZ4OAOL.js → chunk-ZEGM3MVR.js} +3 -5
- package/dist/chunk-ZEGM3MVR.js.map +1 -0
- package/dist/commands/build/marketplace.js +46 -46
- package/dist/commands/build/marketplace.js.map +1 -1
- package/dist/commands/build/plugins.js +25 -28
- package/dist/commands/build/plugins.js.map +1 -1
- package/dist/commands/compile.js +87 -178
- package/dist/commands/compile.js.map +1 -1
- package/dist/commands/doctor.js +510 -360
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/edit.js +451 -357
- package/dist/commands/edit.js.map +1 -1
- package/dist/commands/eject.js +144 -125
- package/dist/commands/eject.js.map +1 -1
- package/dist/commands/init.js +20 -27
- package/dist/commands/list.js +39 -55
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/new/marketplace.js +6 -11
- package/dist/commands/new/marketplace.js.map +1 -1
- package/dist/commands/search.js +35 -32
- package/dist/commands/search.js.map +1 -1
- package/dist/commands/share.js +17 -77
- package/dist/commands/share.js.map +1 -1
- package/dist/commands/uninstall.js +159 -222
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.js +9 -32
- package/dist/commands/update.js.map +1 -1
- package/dist/components/common/prompt-confirm.js +2 -2
- package/dist/components/common/removal-plan-confirm.js +2 -2
- package/dist/components/common/scroll-affordance.js +2 -2
- package/dist/components/common/select-list.js +2 -2
- package/dist/components/common/skill-agent-summary.js +4 -4
- package/dist/components/common/spinner.js +2 -4
- package/dist/components/common/summary-panel.js +5 -5
- package/dist/components/common/summary-panel.js.map +1 -1
- package/dist/config-exports.js +7 -3
- package/dist/config-exports.js.map +1 -1
- package/dist/hooks/init.js +18 -19
- package/dist/hooks/init.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/load-agent-defs-UBWITIEI.js +14 -0
- package/dist/recompile-project-agents-IQANWGOP.js +65 -0
- package/dist/recompile-project-agents-IQANWGOP.js.map +1 -0
- package/dist/{source-loader-C5SQXZXI.js → source-loader-YGG7DY2W.js} +5 -5
- package/dist/src/agents/developer/ai-developer/playbook.md +2 -5
- package/dist/src/agents/developer/api-developer/playbook.md +2 -5
- package/dist/src/agents/developer/cli-developer/playbook.md +2 -5
- package/dist/src/agents/developer/web-developer/playbook.md +2 -5
- package/dist/src/agents/meta/agent-summoner/output.md +2 -2
- package/dist/src/agents/meta/agent-summoner/playbook.md +1 -13
- package/dist/src/agents/meta/codex-keeper/critical-requirements.md +2 -5
- package/dist/src/agents/meta/convention-keeper/critical-requirements.md +2 -4
- package/dist/src/agents/tester/ai-tester/critical-requirements.md +1 -2
- package/dist/src/agents/tester/ai-tester/playbook.md +2 -0
- package/dist/src/agents/tester/api-tester/critical-requirements.md +1 -2
- package/dist/src/agents/tester/api-tester/playbook.md +2 -0
- package/dist/src/agents/tester/cli-tester/critical-requirements.md +1 -2
- package/dist/src/agents/tester/cli-tester/playbook.md +2 -0
- package/dist/src/agents/tester/web-tester/critical-requirements.md +1 -2
- package/dist/src/agents/tester/web-tester/playbook.md +2 -0
- package/package.json +9 -8
- package/src/agents/developer/ai-developer/playbook.md +2 -5
- package/src/agents/developer/api-developer/playbook.md +2 -5
- package/src/agents/developer/cli-developer/playbook.md +2 -5
- package/src/agents/developer/web-developer/playbook.md +2 -5
- package/src/agents/meta/agent-summoner/output.md +2 -2
- package/src/agents/meta/agent-summoner/playbook.md +1 -13
- package/src/agents/meta/codex-keeper/critical-requirements.md +2 -5
- package/src/agents/meta/convention-keeper/critical-requirements.md +2 -4
- package/src/agents/tester/ai-tester/critical-requirements.md +1 -2
- package/src/agents/tester/ai-tester/playbook.md +2 -0
- package/src/agents/tester/api-tester/critical-requirements.md +1 -2
- package/src/agents/tester/api-tester/playbook.md +2 -0
- package/src/agents/tester/cli-tester/critical-requirements.md +1 -2
- package/src/agents/tester/cli-tester/playbook.md +2 -0
- package/src/agents/tester/web-tester/critical-requirements.md +1 -2
- package/src/agents/tester/web-tester/playbook.md +2 -0
- package/src/schemas/project-config.schema.json +0 -3
- package/src/schemas/project-source-config.schema.json +0 -6
- package/dist/chunk-3G7X2WKI.js.map +0 -1
- package/dist/chunk-44TE3RKH.js.map +0 -1
- package/dist/chunk-4AAOOAIN.js.map +0 -1
- package/dist/chunk-52HDHJ7I.js.map +0 -1
- package/dist/chunk-6HNXYYVF.js +0 -373
- package/dist/chunk-6HNXYYVF.js.map +0 -1
- package/dist/chunk-6ZRH5RHC.js.map +0 -1
- package/dist/chunk-7RDMTR64.js.map +0 -1
- package/dist/chunk-BMRUNMPV.js.map +0 -1
- package/dist/chunk-CLPUWOSA.js +0 -46
- package/dist/chunk-CLPUWOSA.js.map +0 -1
- package/dist/chunk-F2BHT5C4.js.map +0 -1
- package/dist/chunk-F5EBEATU.js.map +0 -1
- package/dist/chunk-HED2DAXZ.js.map +0 -1
- package/dist/chunk-JFLVEFFP.js.map +0 -1
- package/dist/chunk-KOLDW6SF.js.map +0 -1
- package/dist/chunk-MQ6CGZNJ.js.map +0 -1
- package/dist/chunk-NCWDK6L7.js.map +0 -1
- package/dist/chunk-NEZ4OAOL.js.map +0 -1
- package/dist/chunk-NIHUOWE4.js +0 -106
- package/dist/chunk-NIHUOWE4.js.map +0 -1
- package/dist/chunk-NQW25DCF.js.map +0 -1
- package/dist/chunk-OYJQUA3D.js.map +0 -1
- package/dist/chunk-PJWGTNOX.js.map +0 -1
- package/dist/chunk-RIPSGNLU.js.map +0 -1
- package/dist/chunk-SZYQISA2.js.map +0 -1
- package/dist/chunk-USVBYQSQ.js.map +0 -1
- package/dist/chunk-V3KTSP3Q.js +0 -940
- package/dist/chunk-V3KTSP3Q.js.map +0 -1
- package/dist/chunk-WTWMGH2C.js.map +0 -1
- package/dist/load-agent-defs-YMDWBFDP.js +0 -14
- package/dist/recompile-project-agents-SZ76WNTW.js +0 -18
- package/dist/source-loader-C5SQXZXI.js.map +0 -1
- /package/dist/{load-agent-defs-YMDWBFDP.js.map → load-agent-defs-UBWITIEI.js.map} +0 -0
- /package/dist/{recompile-project-agents-SZ76WNTW.js.map → source-loader-YGG7DY2W.js.map} +0 -0
package/dist/chunk-V3KTSP3Q.js
DELETED
|
@@ -1,940 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
installPluginSkills,
|
|
4
|
-
pluginInstallFailureError,
|
|
5
|
-
unbackedPluginInstallError,
|
|
6
|
-
unbackedPluginSkillIds
|
|
7
|
-
} from "./chunk-CLPUWOSA.js";
|
|
8
|
-
import {
|
|
9
|
-
STATUS_MESSAGES,
|
|
10
|
-
claimSlug,
|
|
11
|
-
completedWithFailures,
|
|
12
|
-
computeFileHash,
|
|
13
|
-
copy,
|
|
14
|
-
defaultUsageGuidance,
|
|
15
|
-
directoryExists,
|
|
16
|
-
fetchMarketplace,
|
|
17
|
-
findConfigLoadFailures,
|
|
18
|
-
findUnusableSavedSkillMetadata,
|
|
19
|
-
getErrorMessage,
|
|
20
|
-
getStackSkillIds,
|
|
21
|
-
glob,
|
|
22
|
-
hostAt,
|
|
23
|
-
injectForkedFromMetadata,
|
|
24
|
-
installationConfigsUnreadable,
|
|
25
|
-
isHomeDirectory,
|
|
26
|
-
parseFrontmatter,
|
|
27
|
-
pluginsInstalled,
|
|
28
|
-
propagatedRecompileSummary,
|
|
29
|
-
readFile,
|
|
30
|
-
readForkedFromMetadata,
|
|
31
|
-
readLocalSkillMetadata,
|
|
32
|
-
readSkillMetadata,
|
|
33
|
-
refuseUnofferedPlacements,
|
|
34
|
-
refuseUnofferedStack,
|
|
35
|
-
refuseUnwritableStack,
|
|
36
|
-
registeredProjectConfigUnreadable,
|
|
37
|
-
registeredProjectGone,
|
|
38
|
-
registeredProjectOnAnotherProvider,
|
|
39
|
-
remove,
|
|
40
|
-
resolveBranding,
|
|
41
|
-
resolveInstallPaths,
|
|
42
|
-
savedSkillMetadataUnusableError,
|
|
43
|
-
scopeBlockedStackAssignment,
|
|
44
|
-
sharedConfigProjectScopeAtHome,
|
|
45
|
-
skillAssignedToNoAgent,
|
|
46
|
-
skillMetadataUnusableDetail,
|
|
47
|
-
validateSkillPath,
|
|
48
|
-
verbose,
|
|
49
|
-
warn,
|
|
50
|
-
writeFile,
|
|
51
|
-
writeMetadataYaml
|
|
52
|
-
} from "./chunk-MQ6CGZNJ.js";
|
|
53
|
-
import {
|
|
54
|
-
BUILT_IN_MATRIX,
|
|
55
|
-
getCategoryDomain,
|
|
56
|
-
typedEntries,
|
|
57
|
-
typedKeys
|
|
58
|
-
} from "./chunk-52HDHJ7I.js";
|
|
59
|
-
import {
|
|
60
|
-
CLI_COLORS,
|
|
61
|
-
DEFAULT_BRANDING,
|
|
62
|
-
GITHUB_SOURCE,
|
|
63
|
-
LOCAL_SKILLS_PATH,
|
|
64
|
-
MIN_TERMINAL_SIZE,
|
|
65
|
-
STANDARD_FILES,
|
|
66
|
-
isActiveAt,
|
|
67
|
-
seedExternalSkillSchema
|
|
68
|
-
} from "./chunk-PJWGTNOX.js";
|
|
69
|
-
|
|
70
|
-
// src/cli/utils/terminal.ts
|
|
71
|
-
function clearTerminalScreen() {
|
|
72
|
-
process.stdout.write("\x1B[H\x1B[2J\x1B[3J");
|
|
73
|
-
}
|
|
74
|
-
function isTerminalLargeEnough(columns, rows) {
|
|
75
|
-
return columns >= MIN_TERMINAL_SIZE.COLS && rows >= MIN_TERMINAL_SIZE.ROWS;
|
|
76
|
-
}
|
|
77
|
-
function formatTerminalTooSmallMessage(columns) {
|
|
78
|
-
const issue = columns < MIN_TERMINAL_SIZE.COLS ? `too narrow (need ${MIN_TERMINAL_SIZE.COLS})` : `too short (need ${MIN_TERMINAL_SIZE.ROWS})`;
|
|
79
|
-
return `Terminal ${issue}. Please resize.`;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// src/cli/lib/exit-codes.ts
|
|
83
|
-
var EXIT_CODES = {
|
|
84
|
-
SUCCESS: 0,
|
|
85
|
-
ERROR: 1,
|
|
86
|
-
INVALID_ARGS: 2,
|
|
87
|
-
NETWORK_ERROR: 3,
|
|
88
|
-
CANCELLED: 4,
|
|
89
|
-
/**
|
|
90
|
-
* The command ran to the end and part of what it set out to do did not happen.
|
|
91
|
-
*
|
|
92
|
-
* Distinct from {@link EXIT_CODES.ERROR} because the two ask opposite things of whoever reads
|
|
93
|
-
* them: an `ERROR` aborted, so nothing landed and the run can be repeated; this one landed and
|
|
94
|
-
* the repeat is the wrong move — what is owed is the remedy the output names, per failure.
|
|
95
|
-
* Collapsing it into `ERROR` would re-make the defect it exists to close one value along, and
|
|
96
|
-
* a spec asserting `not.toBe(0)` cannot tell a refusal from a partial apply either.
|
|
97
|
-
*
|
|
98
|
-
* It is raised at the END of a command, never mid-flight: the work completes first, which is
|
|
99
|
-
* the whole difference between this and `this.error`.
|
|
100
|
-
*/
|
|
101
|
-
COMPLETED_WITH_FAILURES: 5
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
// src/cli/lib/seed/external-skills.ts
|
|
105
|
-
import os from "os";
|
|
106
|
-
import path from "path";
|
|
107
|
-
import { mkdtemp } from "fs/promises";
|
|
108
|
-
import { isDeepEqual, omit } from "remeda";
|
|
109
|
-
|
|
110
|
-
// ../compile/src/carried-skill.ts
|
|
111
|
-
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
112
|
-
var FRONTMATTER_BLOCK = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
113
|
-
var NAME_FIELD = /^name:.*$/m;
|
|
114
|
-
function withInstalledName(manifest, { id, description }) {
|
|
115
|
-
const named = `name: ${id}`;
|
|
116
|
-
const block = FRONTMATTER_BLOCK.exec(manifest);
|
|
117
|
-
if (!block) {
|
|
118
|
-
return `---
|
|
119
|
-
${named}
|
|
120
|
-
description: ${description}
|
|
121
|
-
---
|
|
122
|
-
|
|
123
|
-
${manifest}`;
|
|
124
|
-
}
|
|
125
|
-
const [whole, fields = ""] = block;
|
|
126
|
-
const renamed = NAME_FIELD.test(fields) ? fields.replace(NAME_FIELD, named) : `${named}
|
|
127
|
-
${fields}`;
|
|
128
|
-
return `---
|
|
129
|
-
${renamed}
|
|
130
|
-
---${manifest.slice(whole.length)}`;
|
|
131
|
-
}
|
|
132
|
-
function carriedSkillAuthor(repo) {
|
|
133
|
-
const [owner = repo] = repo.split("/");
|
|
134
|
-
return `@${owner}`;
|
|
135
|
-
}
|
|
136
|
-
function carriedSkillMetadata({
|
|
137
|
-
id,
|
|
138
|
-
skill,
|
|
139
|
-
domain,
|
|
140
|
-
usageGuidance,
|
|
141
|
-
shipped
|
|
142
|
-
}) {
|
|
143
|
-
return {
|
|
144
|
-
author: carriedSkillAuthor(skill.repo),
|
|
145
|
-
usageGuidance,
|
|
146
|
-
...shipped,
|
|
147
|
-
displayName: skill.displayName,
|
|
148
|
-
slug: id,
|
|
149
|
-
category: skill.categoryId,
|
|
150
|
-
domain,
|
|
151
|
-
cliDescription: shortLabel(skill.description),
|
|
152
|
-
custom: true
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
var CLI_DESCRIPTION_MAX_LENGTH = 60;
|
|
156
|
-
var ELLIPSIS = "\u2026";
|
|
157
|
-
function shortLabel(description) {
|
|
158
|
-
if (description.length <= CLI_DESCRIPTION_MAX_LENGTH) return description;
|
|
159
|
-
const room = description.slice(
|
|
160
|
-
0,
|
|
161
|
-
CLI_DESCRIPTION_MAX_LENGTH - ELLIPSIS.length
|
|
162
|
-
);
|
|
163
|
-
return `${toLastWholeWord(room)}${ELLIPSIS}`;
|
|
164
|
-
}
|
|
165
|
-
function toLastWholeWord(text) {
|
|
166
|
-
const lastBreak = text.lastIndexOf(" ");
|
|
167
|
-
return (lastBreak > 0 ? text.slice(0, lastBreak) : text).trimEnd();
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// src/cli/lib/seed/external-skills.ts
|
|
171
|
-
function pluginInstallError(ids) {
|
|
172
|
-
return [
|
|
173
|
-
"This configuration cannot be installed: these skills travel inside it, so no marketplace serves them and they cannot be installed as plugins:",
|
|
174
|
-
...ids.map((id) => ` ${id}`),
|
|
175
|
-
"Re-share it with each skill above set to eject, which is the only way a carried skill installs."
|
|
176
|
-
].join("\n");
|
|
177
|
-
}
|
|
178
|
-
function selectedExternals(payload) {
|
|
179
|
-
return typedEntries(payload.external ?? {}).flatMap(([id, skill]) => {
|
|
180
|
-
const entry = payload.skills[id];
|
|
181
|
-
return entry ? [{ id, skill, entry }] : [];
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
function askedForAsPlugin(externals) {
|
|
185
|
-
return externals.filter(({ entry }) => entry.install !== "eject").map(({ id }) => id);
|
|
186
|
-
}
|
|
187
|
-
function claimingACatalogueId(externals, matrix) {
|
|
188
|
-
return externals.filter(({ id }) => heldByEitherCatalogue(id, matrix)).map(({ id }) => id);
|
|
189
|
-
}
|
|
190
|
-
var CATALOGUE_SKILL_IDS = new Set(typedKeys(BUILT_IN_MATRIX.skills));
|
|
191
|
-
function heldByEitherCatalogue(id, matrix) {
|
|
192
|
-
return heldByLoadedCatalogue(matrix.skills[id]) || CATALOGUE_SKILL_IDS.has(id);
|
|
193
|
-
}
|
|
194
|
-
function heldByLoadedCatalogue(incumbent) {
|
|
195
|
-
return incumbent !== void 0 && incumbent.local !== true;
|
|
196
|
-
}
|
|
197
|
-
function catalogueIdCollisionError(ids) {
|
|
198
|
-
return [
|
|
199
|
-
"This configuration cannot be installed: these skills travel inside it under ids a catalogue already owns:",
|
|
200
|
-
...ids.map((id) => ` ${id}`),
|
|
201
|
-
"A skill id is the directory the skill installs into, so each would be written over the catalogue's own copy of that skill. Re-share it with each skill above added again, which mints an id \u2014 'external-<category>-<name>' \u2014 that no catalogue owns."
|
|
202
|
-
].join("\n");
|
|
203
|
-
}
|
|
204
|
-
function externalSlug(id) {
|
|
205
|
-
return id;
|
|
206
|
-
}
|
|
207
|
-
function repoRef(repo) {
|
|
208
|
-
return `${GITHUB_SOURCE.GITHUB_PREFIX}${repo}`;
|
|
209
|
-
}
|
|
210
|
-
function externalSkillDir(id, scope, projectDir) {
|
|
211
|
-
return path.join(resolveInstallPaths(projectDir, scope).skillsDir, id);
|
|
212
|
-
}
|
|
213
|
-
function externalCatalogueEntry(install) {
|
|
214
|
-
return {
|
|
215
|
-
id: install.id,
|
|
216
|
-
slug: externalSlug(install.id),
|
|
217
|
-
displayName: install.skill.displayName,
|
|
218
|
-
description: install.skill.description,
|
|
219
|
-
// Boundary cast held by the guard above it: the category was confirmed against the catalogue
|
|
220
|
-
// the payload names, and this one has just been shown to declare it too.
|
|
221
|
-
category: install.skill.categoryId,
|
|
222
|
-
author: carriedSkillAuthor(install.skill.repo),
|
|
223
|
-
path: `${LOCAL_SKILLS_PATH}/${install.id}/`,
|
|
224
|
-
conflictsWith: [],
|
|
225
|
-
requires: [],
|
|
226
|
-
alternatives: [],
|
|
227
|
-
discourages: [],
|
|
228
|
-
// Local and custom for the same reason: the bytes are on this machine and nowhere else, and
|
|
229
|
-
// the skill was authored outside the vocabulary this catalogue ships.
|
|
230
|
-
local: true,
|
|
231
|
-
custom: true,
|
|
232
|
-
localPath: install.skillDir
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
function seatExternalSkill({ id, skill, entry }, matrix, projectDir) {
|
|
236
|
-
const domain = getCategoryDomain(skill.categoryId);
|
|
237
|
-
if (domain === void 0) return [];
|
|
238
|
-
const install = {
|
|
239
|
-
id,
|
|
240
|
-
skill,
|
|
241
|
-
domain,
|
|
242
|
-
scope: entry.scope,
|
|
243
|
-
skillDir: externalSkillDir(id, entry.scope, projectDir)
|
|
244
|
-
};
|
|
245
|
-
matrix.skills[id] = externalCatalogueEntry(install);
|
|
246
|
-
claimSlug(matrix.slugMap, externalSlug(id), id);
|
|
247
|
-
return [install];
|
|
248
|
-
}
|
|
249
|
-
function registerExternalSkills(payload, matrix, projectDir) {
|
|
250
|
-
const externals = selectedExternals(payload);
|
|
251
|
-
const asPlugin = askedForAsPlugin(externals);
|
|
252
|
-
const colliding = claimingACatalogueId(externals, matrix);
|
|
253
|
-
if (asPlugin.length > 0) throw new Error(pluginInstallError(asPlugin));
|
|
254
|
-
if (colliding.length > 0) throw new Error(catalogueIdCollisionError(colliding));
|
|
255
|
-
return externals.flatMap((external) => seatExternalSkill(external, matrix, projectDir));
|
|
256
|
-
}
|
|
257
|
-
async function writeExternalSkills(installs) {
|
|
258
|
-
await refuseUncarriedDestinations(installs);
|
|
259
|
-
const written = await Promise.all(installs.map(writeExternalSkill));
|
|
260
|
-
return changedIds(written);
|
|
261
|
-
}
|
|
262
|
-
var SCRATCH_DIR_PREFIX = "agents-inc-carried-";
|
|
263
|
-
async function carriedSkillsThatDiffer(installs) {
|
|
264
|
-
if (installs.length === 0) return [];
|
|
265
|
-
const scratch = await mkdtemp(path.join(os.tmpdir(), SCRATCH_DIR_PREFIX));
|
|
266
|
-
try {
|
|
267
|
-
const written = await Promise.all(
|
|
268
|
-
installs.map(async (install) => writeExternalSkill(await scratchCopyOf(install, scratch)))
|
|
269
|
-
);
|
|
270
|
-
return changedIds(written);
|
|
271
|
-
} finally {
|
|
272
|
-
await remove(scratch);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
async function scratchCopyOf(install, scratch) {
|
|
276
|
-
const skillDir = path.join(scratch, install.id);
|
|
277
|
-
if (await directoryExists(install.skillDir)) await copy(install.skillDir, skillDir);
|
|
278
|
-
return { ...install, skillDir };
|
|
279
|
-
}
|
|
280
|
-
function changedIds(written) {
|
|
281
|
-
return written.filter(({ changed }) => changed).map(({ id }) => id);
|
|
282
|
-
}
|
|
283
|
-
async function writeExternalSkill(install) {
|
|
284
|
-
const before = await readSkillDir(install.skillDir);
|
|
285
|
-
await writeSkillTree(install);
|
|
286
|
-
await writeSkillManifest(install);
|
|
287
|
-
await registerSkillOnDisk(install);
|
|
288
|
-
return { id: install.id, changed: !isDeepEqual(before, await readSkillDir(install.skillDir)) };
|
|
289
|
-
}
|
|
290
|
-
async function readSkillDir(skillDir) {
|
|
291
|
-
if (!await directoryExists(skillDir)) return null;
|
|
292
|
-
const files = omit(await readSkillTree(skillDir), [STANDARD_FILES.METADATA_YAML]);
|
|
293
|
-
return { files, metadata: undated(await readLocalSkillMetadata(skillDir)) };
|
|
294
|
-
}
|
|
295
|
-
function undated(metadata) {
|
|
296
|
-
if (metadata?.forkedFrom === void 0) return metadata;
|
|
297
|
-
return { ...metadata, forkedFrom: omit(metadata.forkedFrom, ["date"]) };
|
|
298
|
-
}
|
|
299
|
-
async function refuseUncarriedDestinations(installs) {
|
|
300
|
-
const judged = await Promise.all(installs.map(judgeDestination));
|
|
301
|
-
const blocked = judged.filter((destination) => destination !== null);
|
|
302
|
-
if (blocked.length > 0) throw new Error(uncarriedDestinationError(blocked));
|
|
303
|
-
}
|
|
304
|
-
async function judgeDestination(install) {
|
|
305
|
-
const { id, skillDir } = install;
|
|
306
|
-
if (!await directoryExists(skillDir)) return null;
|
|
307
|
-
if ((await readForkedFromMetadata(skillDir))?.path !== void 0) return null;
|
|
308
|
-
return { id, skillDir };
|
|
309
|
-
}
|
|
310
|
-
function uncarriedDestinationError(blocked) {
|
|
311
|
-
return [
|
|
312
|
-
"This configuration cannot be installed: these skills travel inside it under ids this installation already keeps a skill directory for, and no shared configuration put those directories there:",
|
|
313
|
-
...blocked.map(({ id, skillDir }) => ` ${id} -> ${skillDir}`),
|
|
314
|
-
"A skill id is the directory the skill installs into, so each would be written over a skill written here by hand or ejected from a marketplace. Move or rename the directory above, or re-share the configuration with each skill added again, which mints an id \u2014 'external-<category>-<name>' \u2014 that nothing here holds."
|
|
315
|
-
].join("\n");
|
|
316
|
-
}
|
|
317
|
-
async function writeSkillTree(install) {
|
|
318
|
-
for (const [relativePath, contents] of Object.entries(install.skill.files)) {
|
|
319
|
-
const filePath = path.join(install.skillDir, relativePath);
|
|
320
|
-
validateSkillPath(filePath, install.skillDir, relativePath);
|
|
321
|
-
await writeFile(filePath, contents);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
async function writeSkillManifest(install) {
|
|
325
|
-
const manifestPath = path.join(install.skillDir, STANDARD_FILES.SKILL_MD);
|
|
326
|
-
await writeFile(
|
|
327
|
-
manifestPath,
|
|
328
|
-
withInstalledName(await readFile(manifestPath), {
|
|
329
|
-
id: install.id,
|
|
330
|
-
description: install.skill.description
|
|
331
|
-
})
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
async function registerSkillOnDisk(install) {
|
|
335
|
-
const metadataPath = path.join(install.skillDir, STANDARD_FILES.METADATA_YAML);
|
|
336
|
-
const shipped = await readLocalSkillMetadata(install.skillDir);
|
|
337
|
-
await writeMetadataYaml(
|
|
338
|
-
metadataPath,
|
|
339
|
-
carriedSkillMetadata({
|
|
340
|
-
id: install.id,
|
|
341
|
-
skill: install.skill,
|
|
342
|
-
domain: install.domain,
|
|
343
|
-
usageGuidance: defaultUsageGuidance(install.skill.categoryId),
|
|
344
|
-
shipped
|
|
345
|
-
})
|
|
346
|
-
);
|
|
347
|
-
await injectForkedFromMetadata(
|
|
348
|
-
install.skillDir,
|
|
349
|
-
install.id,
|
|
350
|
-
await computeFileHash(path.join(install.skillDir, STANDARD_FILES.SKILL_MD)),
|
|
351
|
-
{ source: repoRef(install.skill.repo), path: install.skill.path }
|
|
352
|
-
);
|
|
353
|
-
}
|
|
354
|
-
function unnameableRepoMessage(id) {
|
|
355
|
-
return `${id} travels inside a shared configuration, and this installation does not record the repository its content came from`;
|
|
356
|
-
}
|
|
357
|
-
function uncarryableContentMessage(id, reason) {
|
|
358
|
-
return `${id} travels inside a shared configuration, and ${reason}`;
|
|
359
|
-
}
|
|
360
|
-
function repoFromRef(ref) {
|
|
361
|
-
if (ref === void 0 || !ref.startsWith(GITHUB_SOURCE.GITHUB_PREFIX)) return void 0;
|
|
362
|
-
return ref.slice(GITHUB_SOURCE.GITHUB_PREFIX.length);
|
|
363
|
-
}
|
|
364
|
-
var NO_MANIFEST_DESCRIPTION = `no description can be read from its ${STANDARD_FILES.SKILL_MD}`;
|
|
365
|
-
function manifestDescription(files, skillDir) {
|
|
366
|
-
const manifest = files[STANDARD_FILES.SKILL_MD];
|
|
367
|
-
if (manifest === void 0) return null;
|
|
368
|
-
const frontmatter = parseFrontmatter(manifest, path.join(skillDir, STANDARD_FILES.SKILL_MD));
|
|
369
|
-
return frontmatter === null ? null : frontmatter.description;
|
|
370
|
-
}
|
|
371
|
-
async function readSkillTree(skillDir) {
|
|
372
|
-
const files = await glob("**/*", skillDir, { dot: true });
|
|
373
|
-
const entries = await Promise.all(
|
|
374
|
-
files.map(async (file) => [file, await readFile(path.join(skillDir, file))])
|
|
375
|
-
);
|
|
376
|
-
return Object.fromEntries(entries);
|
|
377
|
-
}
|
|
378
|
-
async function readCarriedSkill({ id, skillDir, provenance }) {
|
|
379
|
-
if (provenance.path === void 0) return { carries: "nothing" };
|
|
380
|
-
const repo = repoFromRef(provenance.source);
|
|
381
|
-
if (repo === void 0) return { carries: "unshareable", line: unnameableRepoMessage(id) };
|
|
382
|
-
const described = await readSkillMetadata(path.join(skillDir, STANDARD_FILES.METADATA_YAML));
|
|
383
|
-
if (!described.usable) {
|
|
384
|
-
return { carries: "unshareable", line: uncarryableContentMessage(id, described.reason) };
|
|
385
|
-
}
|
|
386
|
-
const files = await readSkillTree(skillDir);
|
|
387
|
-
const description = manifestDescription(files, skillDir);
|
|
388
|
-
if (description === null) {
|
|
389
|
-
return { carries: "unshareable", line: uncarryableContentMessage(id, NO_MANIFEST_DESCRIPTION) };
|
|
390
|
-
}
|
|
391
|
-
const parsed = seedExternalSkillSchema.safeParse({
|
|
392
|
-
displayName: described.metadata.displayName,
|
|
393
|
-
description,
|
|
394
|
-
categoryId: described.metadata.category,
|
|
395
|
-
repo,
|
|
396
|
-
path: provenance.path,
|
|
397
|
-
files
|
|
398
|
-
});
|
|
399
|
-
if (!parsed.success) {
|
|
400
|
-
const reason = parsed.error.issues.map((issue) => issue.message).join("; ");
|
|
401
|
-
return { carries: "unshareable", line: uncarryableContentMessage(id, reason) };
|
|
402
|
-
}
|
|
403
|
-
return { carries: "content", id, skill: parsed.data };
|
|
404
|
-
}
|
|
405
|
-
async function readCarriedSkills(owned) {
|
|
406
|
-
const reads = await Promise.all(owned.map(readCarriedSkill));
|
|
407
|
-
return {
|
|
408
|
-
external: Object.fromEntries(
|
|
409
|
-
reads.flatMap((read) => read.carries === "content" ? [[read.id, read.skill]] : [])
|
|
410
|
-
),
|
|
411
|
-
uncarryable: reads.flatMap((read) => read.carries === "unshareable" ? [read.line] : [])
|
|
412
|
-
};
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// src/cli/base-command.ts
|
|
416
|
-
import { Command } from "@oclif/core";
|
|
417
|
-
import chalk from "chalk";
|
|
418
|
-
import { unique } from "remeda";
|
|
419
|
-
|
|
420
|
-
// src/cli/lib/operations/source/ensure-marketplace.ts
|
|
421
|
-
async function ensureMarketplace(sourceResult, host) {
|
|
422
|
-
const source = sourceResult.sourceConfig.source;
|
|
423
|
-
if (!sourceResult.marketplace) {
|
|
424
|
-
try {
|
|
425
|
-
const marketplaceResult = await fetchMarketplace(source);
|
|
426
|
-
sourceResult.marketplace = marketplaceResult.marketplace.name;
|
|
427
|
-
} catch (error) {
|
|
428
|
-
warn(`Could not resolve a marketplace from '${source}': ${getErrorMessage(error)}`);
|
|
429
|
-
return { marketplace: null, registered: false };
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
const marketplace = sourceResult.marketplace;
|
|
433
|
-
const exists = await host.marketplaceExists(marketplace);
|
|
434
|
-
if (!exists) {
|
|
435
|
-
const marketplaceSource = source.replace(/^github:/, "");
|
|
436
|
-
await host.addMarketplace(marketplaceSource);
|
|
437
|
-
return { marketplace, registered: true };
|
|
438
|
-
}
|
|
439
|
-
try {
|
|
440
|
-
await host.refreshMarketplace(marketplace);
|
|
441
|
-
} catch {
|
|
442
|
-
warn("Could not update marketplace \u2014 continuing with cached version");
|
|
443
|
-
}
|
|
444
|
-
return { marketplace, registered: false };
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// src/cli/lib/operations/source/require-marketplace.ts
|
|
448
|
-
async function requireMarketplace(sourceResult, purpose, host) {
|
|
449
|
-
const mpResult = await ensureMarketplace(sourceResult, host);
|
|
450
|
-
if (!mpResult.marketplace) {
|
|
451
|
-
return {
|
|
452
|
-
ok: false,
|
|
453
|
-
error: `Cannot ${purpose}: marketplace could not be resolved from '${sourceResult.sourceConfig.source}'. Plugin install mode requires a marketplace \u2014 fix the marketplace or switch the affected skills to eject mode.`
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
return { ok: true, marketplace: mpResult.marketplace, registered: mpResult.registered };
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// src/cli/base-command.ts
|
|
460
|
-
var TERMINAL_RESIZE_POLL_MS = 500;
|
|
461
|
-
var terminalColumns = () => process.stdout.columns ?? MIN_TERMINAL_SIZE.COLS;
|
|
462
|
-
var terminalRows = () => process.stdout.rows ?? MIN_TERMINAL_SIZE.ROWS;
|
|
463
|
-
function isTerminalBigEnough() {
|
|
464
|
-
return isTerminalLargeEnough(terminalColumns(), terminalRows());
|
|
465
|
-
}
|
|
466
|
-
function waitForTerminal(isSatisfied) {
|
|
467
|
-
return new Promise((resolve) => {
|
|
468
|
-
const check = () => {
|
|
469
|
-
if (!isSatisfied()) return;
|
|
470
|
-
clearInterval(interval);
|
|
471
|
-
process.stdout.off("resize", check);
|
|
472
|
-
resolve();
|
|
473
|
-
};
|
|
474
|
-
const interval = setInterval(check, TERMINAL_RESIZE_POLL_MS);
|
|
475
|
-
process.stdout.on("resize", check);
|
|
476
|
-
});
|
|
477
|
-
}
|
|
478
|
-
function findUnassignedSkills(config, selected) {
|
|
479
|
-
const assignedIds = new Set(config.stack ? getStackSkillIds(config.stack) : []);
|
|
480
|
-
const selectedIds = new Set(selected.map((skill) => skill.id));
|
|
481
|
-
const globalAgentNames = config.agents.filter((agent) => isActiveAt(agent, "global")).map((agent) => agent.name);
|
|
482
|
-
const unassignedIds = unique(
|
|
483
|
-
config.skills.filter((skill) => !skill.excluded && selectedIds.has(skill.id)).map((skill) => skill.id).filter((skillId) => !assignedIds.has(skillId))
|
|
484
|
-
);
|
|
485
|
-
return unassignedIds.map((skillId) => ({
|
|
486
|
-
skillId,
|
|
487
|
-
blockedBy: hasActiveProjectEntry(config.skills, skillId) ? globalAgentNames : []
|
|
488
|
-
}));
|
|
489
|
-
}
|
|
490
|
-
function hasActiveProjectEntry(skills, skillId) {
|
|
491
|
-
return skills.some((skill) => skill.id === skillId && isActiveAt(skill, "project"));
|
|
492
|
-
}
|
|
493
|
-
function projectScopedContent(result) {
|
|
494
|
-
return {
|
|
495
|
-
skillIds: result.skills.filter((skill) => isActiveAt(skill, "project")).map((skill) => skill.id),
|
|
496
|
-
agentNames: result.agentConfigs.filter((agent) => isActiveAt(agent, "project")).map((agent) => agent.name)
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
var BaseCommand = class extends Command {
|
|
500
|
-
/**
|
|
501
|
-
* Everything this run set out to do and did not, in the order it hit them.
|
|
502
|
-
*
|
|
503
|
-
* Non-empty is the whole of the exit-code decision — see {@link exitIfWorkIncomplete}. It is
|
|
504
|
-
* deliberately not a boolean: a non-zero exit whose output does not say what failed and what
|
|
505
|
-
* finishes it is worse than the silent success it replaces.
|
|
506
|
-
*
|
|
507
|
-
* Private, with {@link hasIncompleteWork} the only reading a subclass gets. A command records
|
|
508
|
-
* through the two methods below and asks whether anything is owed; the list itself is this
|
|
509
|
-
* class's, because it is this class that answers for it.
|
|
510
|
-
*/
|
|
511
|
-
incompleteWork = [];
|
|
512
|
-
async init() {
|
|
513
|
-
await super.init();
|
|
514
|
-
await this.ensureTerminalSize();
|
|
515
|
-
}
|
|
516
|
-
/**
|
|
517
|
-
* Blocks the command from launching in a terminal below {@link MIN_TERMINAL_SIZE}. This runs
|
|
518
|
-
* once, before Ink mounts.
|
|
519
|
-
*/
|
|
520
|
-
async ensureTerminalSize() {
|
|
521
|
-
if (isTerminalBigEnough()) return;
|
|
522
|
-
this.clearTerminal();
|
|
523
|
-
this.log(chalk.hex(CLI_COLORS.WARNING)(formatTerminalTooSmallMessage(terminalColumns())));
|
|
524
|
-
await waitForTerminal(isTerminalBigEnough);
|
|
525
|
-
}
|
|
526
|
-
handleError(error) {
|
|
527
|
-
const message = getErrorMessage(error);
|
|
528
|
-
this.error(message, { exit: EXIT_CODES.ERROR });
|
|
529
|
-
}
|
|
530
|
-
/**
|
|
531
|
-
* The name this run prints itself under: `branding.name` where a config supplies one, and
|
|
532
|
-
* {@link DEFAULT_BRANDING.NAME} everywhere else.
|
|
533
|
-
*
|
|
534
|
-
* **DEGRADE is the posture, and it is chosen here for every command that prints a name.**
|
|
535
|
-
* `loadSourceConfig` raises for a config that exists and cannot be evaluated, and two of the
|
|
536
|
-
* four callers must survive precisely that state: `doctor`'s whole job is naming it, and
|
|
537
|
-
* `uninstall` is the only way out of it. A heading is decoration — nothing about a display name
|
|
538
|
-
* is worth refusing a command over, and refusing here would turn the branding wiring into a new
|
|
539
|
-
* way for an unreadable config to abort two commands that survive it today. `init` and `eject`
|
|
540
|
-
* cannot reach the catch at all: {@link ensureConfigReadable} has already refused every config
|
|
541
|
-
* they would read.
|
|
542
|
-
*
|
|
543
|
-
* The cause is not swallowed: it goes to `verbose`, and the command's own config reader reports
|
|
544
|
-
* it in full where the user needs to act on it. Reporting it twice would put a warning about
|
|
545
|
-
* the configuration above a heading, which is the one line that has to be the first thing on
|
|
546
|
-
* screen.
|
|
547
|
-
*/
|
|
548
|
-
async resolveBrandingName(projectDir) {
|
|
549
|
-
try {
|
|
550
|
-
const branding = await resolveBranding(projectDir);
|
|
551
|
-
return branding.name;
|
|
552
|
-
} catch (error) {
|
|
553
|
-
verbose(`Could not read branding from the config: ${getErrorMessage(error)}`);
|
|
554
|
-
return DEFAULT_BRANDING.NAME;
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
logSuccess(message) {
|
|
558
|
-
this.log(`\u2713 ${message}`);
|
|
559
|
-
}
|
|
560
|
-
logWarning(message) {
|
|
561
|
-
this.warn(message);
|
|
562
|
-
}
|
|
563
|
-
logInfo(message) {
|
|
564
|
-
this.log(message);
|
|
565
|
-
}
|
|
566
|
-
clearTerminal() {
|
|
567
|
-
clearTerminalScreen();
|
|
568
|
-
}
|
|
569
|
-
/**
|
|
570
|
-
* Resolves the marketplace required for a plugin operation, or hard-exits with
|
|
571
|
-
* an actionable error when it cannot be resolved. `onRegistered` is invoked
|
|
572
|
-
* (when provided) if the marketplace had to be registered — init uses it to log.
|
|
573
|
-
*
|
|
574
|
-
* `host` is the caller's because a marketplace registration is handed no directory of its own:
|
|
575
|
-
* every caller here holds the install root the run is about, and `hostAt` turns one into the
|
|
576
|
-
* host whose registry the marketplace has to land in.
|
|
577
|
-
*/
|
|
578
|
-
async requireMarketplaceOrExit(sourceResult, purpose, host, onRegistered) {
|
|
579
|
-
const required = await requireMarketplace(sourceResult, purpose, host);
|
|
580
|
-
if (!required.ok) {
|
|
581
|
-
this.error(required.error, { exit: EXIT_CODES.ERROR });
|
|
582
|
-
}
|
|
583
|
-
if (required.registered) {
|
|
584
|
-
onRegistered?.(required.marketplace);
|
|
585
|
-
}
|
|
586
|
-
return required.marketplace;
|
|
587
|
-
}
|
|
588
|
-
/**
|
|
589
|
-
* Reports every constraint a selection breaks — a conflicting pair, an unmet
|
|
590
|
-
* requirement, an exclusive category with two picks.
|
|
591
|
-
*
|
|
592
|
-
* Shared by `init` and `edit` so one selection cannot be reported two ways. Both commands ask
|
|
593
|
-
* the same question of the same matrix, so a roster that draws a warning through one must draw
|
|
594
|
-
* the same warning through the other; `init` used to compute the answer and drop it, which is
|
|
595
|
-
* the whole reason this lives here rather than on either command.
|
|
596
|
-
*
|
|
597
|
-
* These are advisory (`ValidationError` in types/matrix.ts): the selection is installed either
|
|
598
|
-
* way and neither command's exit code turns on them, so they are warnings and not errors. The
|
|
599
|
-
* loop is its own guard — a selection that breaks none says nothing.
|
|
600
|
-
*/
|
|
601
|
-
reportValidationErrors(validation) {
|
|
602
|
-
for (const error of validation.errors) {
|
|
603
|
-
this.warn(error.message);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
/**
|
|
607
|
-
* Installs skill plugins and narrates the operation.
|
|
608
|
-
*
|
|
609
|
-
* Shared by `init` and `edit` for the same reason as {@link reportValidationErrors}:
|
|
610
|
-
* both run the SAME install against the same marketplace, so a line one prints about
|
|
611
|
-
* it the other owes. `edit` used to run it in silence — a real `claude plugin install`
|
|
612
|
-
* was indistinguishable in the output from nothing having happened.
|
|
613
|
-
*
|
|
614
|
-
* The skills nothing carries are refused FIRST, by name: an install no marketplace
|
|
615
|
-
* could serve is knowable before the host's CLI is asked, and the generic
|
|
616
|
-
* install-failure advice is unfollowable for a skill the user wrote themselves.
|
|
617
|
-
*/
|
|
618
|
-
async installPluginSkillsReported(skills, marketplace, projectDir, matrix) {
|
|
619
|
-
const unbacked = unbackedPluginSkillIds(skills, matrix);
|
|
620
|
-
if (unbacked.length > 0) {
|
|
621
|
-
this.error(unbackedPluginInstallError(unbacked), { exit: EXIT_CODES.ERROR });
|
|
622
|
-
}
|
|
623
|
-
this.announcePluginInstall();
|
|
624
|
-
const result = await installPluginSkills(skills, marketplace, projectDir);
|
|
625
|
-
this.reportPluginInstalls(result);
|
|
626
|
-
return result;
|
|
627
|
-
}
|
|
628
|
-
/**
|
|
629
|
-
* The opening half of the narration above, exposed for the one caller that cannot
|
|
630
|
-
* use the whole of it: an eject→plugin migration installs each plugin and deletes
|
|
631
|
-
* that skill's working copy in the same step (see `executeMigration`), so the
|
|
632
|
-
* install it performs is not this command's to run.
|
|
633
|
-
*/
|
|
634
|
-
announcePluginInstall() {
|
|
635
|
-
this.log(STATUS_MESSAGES.INSTALLING_PLUGINS);
|
|
636
|
-
}
|
|
637
|
-
/**
|
|
638
|
-
* The closing half: what was installed, what was not, and the refusal to continue
|
|
639
|
-
* past a failure.
|
|
640
|
-
*
|
|
641
|
-
* The hard-error is part of the report rather than each caller's own postscript
|
|
642
|
-
* because it is the same rule everywhere — plugin install intent is inviolable, so
|
|
643
|
-
* a run that could not honor it must stop BEFORE any config records a marketplace
|
|
644
|
-
* `origin` for a skill with no plugin registration.
|
|
645
|
-
*/
|
|
646
|
-
reportPluginInstalls(result) {
|
|
647
|
-
for (const item of result.installed) {
|
|
648
|
-
this.log(` Installed ${item.ref}`);
|
|
649
|
-
}
|
|
650
|
-
for (const item of result.failed) {
|
|
651
|
-
this.warn(`Failed to install plugin ${item.id}: ${item.error}`);
|
|
652
|
-
}
|
|
653
|
-
if (result.failed.length > 0) {
|
|
654
|
-
this.error(pluginInstallFailureError(result.failed.length), { exit: EXIT_CODES.ERROR });
|
|
655
|
-
}
|
|
656
|
-
this.log(`${pluginsInstalled(result.installed.length)}
|
|
657
|
-
`);
|
|
658
|
-
}
|
|
659
|
-
/**
|
|
660
|
-
* Renders what a gated write's fan-out did to the OTHER registered projects: one line for each
|
|
661
|
-
* it left alone because its config.ts cannot be loaded, then the recompile it performed.
|
|
662
|
-
*
|
|
663
|
-
* `uninstall` alone calls {@link reportPropagatedRecompile} directly, since it words the projects
|
|
664
|
-
* whose config it could not prune in its own terms.
|
|
665
|
-
*/
|
|
666
|
-
reportFanOut(report) {
|
|
667
|
-
for (const projectPath of report.propagated.unreadable) {
|
|
668
|
-
this.warn(registeredProjectConfigUnreadable(projectPath));
|
|
669
|
-
}
|
|
670
|
-
this.reportPropagatedRecompile(report);
|
|
671
|
-
}
|
|
672
|
-
/**
|
|
673
|
-
* One line for each registered project a fan-out left alone because nothing of this
|
|
674
|
-
* installation is there to rewrite: nothing is installed at its path any more, or it is another
|
|
675
|
-
* provider's installation. `compile` and `uninstall` print it — neither project's config can
|
|
676
|
-
* hold what either run changed.
|
|
677
|
-
*/
|
|
678
|
-
reportProjectsOutOfReach(propagated) {
|
|
679
|
-
for (const projectPath of propagated.gone) {
|
|
680
|
-
this.warn(registeredProjectGone(projectPath));
|
|
681
|
-
}
|
|
682
|
-
for (const projectPath of propagated.notOurs) {
|
|
683
|
-
this.warn(registeredProjectOnAnotherProvider(projectPath));
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
/**
|
|
687
|
-
* Renders the recompile a gated write already performed on every OTHER registered
|
|
688
|
-
* project this run's global change was propagated into.
|
|
689
|
-
*
|
|
690
|
-
* Shared by all four commands that fan out — the count and its wording are one
|
|
691
|
-
* statement about one operation, and four copies of it had already drifted into two
|
|
692
|
-
* spellings of the same sentence.
|
|
693
|
-
*/
|
|
694
|
-
reportPropagatedRecompile(report) {
|
|
695
|
-
if (report.propagated.updated.length === 0) return;
|
|
696
|
-
const { rewrittenCount, unchangedCount, failedCount, warnings } = report.recompile;
|
|
697
|
-
for (const warning of warnings) {
|
|
698
|
-
this.warn(warning);
|
|
699
|
-
}
|
|
700
|
-
this.log(propagatedRecompileSummary(rewrittenCount, unchangedCount, failedCount));
|
|
701
|
-
}
|
|
702
|
-
/**
|
|
703
|
-
* Names every selected skill this save left in no sub-agent's stack, and — where the
|
|
704
|
-
* scope rule is why — every sub-agent it was kept away from.
|
|
705
|
-
*
|
|
706
|
-
* Shared by `init` and `edit` for the reason {@link reportValidationErrors} is: both
|
|
707
|
-
* build the stack from the same rules over the same selection, so a skill that reaches
|
|
708
|
-
* nothing through one reaches nothing through the other. The rule being reported is
|
|
709
|
-
* CORRECT and nothing here changes it — a project-scoped skill installs, is written to
|
|
710
|
-
* `config.ts`, and is loaded by nobody, which every other surface reports as a clean
|
|
711
|
-
* install.
|
|
712
|
-
*
|
|
713
|
-
* `compile` says the same thing about the same shape one layer over, for a config
|
|
714
|
-
* hand-edited into it — see `Compile.warnScopeDroppedStackPairs`.
|
|
715
|
-
*/
|
|
716
|
-
reportUnassignedSkills(config, selected) {
|
|
717
|
-
for (const { skillId, blockedBy } of findUnassignedSkills(config, selected)) {
|
|
718
|
-
this.warn(skillAssignedToNoAgent(skillId));
|
|
719
|
-
if (blockedBy.length > 0) {
|
|
720
|
-
this.warn(scopeBlockedStackAssignment(blockedBy, skillId));
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
/**
|
|
725
|
-
* Refuses to run when a config file exists but cannot be loaded.
|
|
726
|
-
*
|
|
727
|
-
* Both configs this run would read are checked, because both are read on the way through: a
|
|
728
|
-
* project's own `config.ts` and the global one every project write inlines. Checking up
|
|
729
|
-
* front is the whole point — an unreadable GLOBAL config otherwise surfaced only at the
|
|
730
|
-
* config write, after the install had copied skills and installed plugins, as a warning that
|
|
731
|
-
* the write never happened.
|
|
732
|
-
*
|
|
733
|
-
* Every command that reads an installation's config calls it before anything else reads one:
|
|
734
|
-
* the writes (`init`, `edit`, `compile`, `update`, `share`, `eject`) and the two that only
|
|
735
|
-
* report (`search`, `list`). `doctor` reports the same file instead, and `uninstall` is the way
|
|
736
|
-
* out of it, so neither does.
|
|
737
|
-
*
|
|
738
|
-
* A MISSING config is not a corrupt one and passes here untouched: it is the legitimate
|
|
739
|
-
* state `init` exists for, and the state `edit` reports as "no installation".
|
|
740
|
-
*/
|
|
741
|
-
async ensureConfigReadable(projectDir) {
|
|
742
|
-
const failures = await findConfigLoadFailures(projectDir);
|
|
743
|
-
if (failures.length > 0) {
|
|
744
|
-
this.error(installationConfigsUnreadable(failures), { exit: EXIT_CODES.ERROR });
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
/**
|
|
748
|
-
* Refuses to run when a saved skill entry names a local skill that IS installed and whose
|
|
749
|
-
* metadata.yaml describes no skill.
|
|
750
|
-
*
|
|
751
|
-
* One layer below {@link ensureConfigReadable} and for the same reason: the config parses,
|
|
752
|
-
* and a file it points at does not.
|
|
753
|
-
*
|
|
754
|
-
* Only entries the catalogue does not carry are looked at, so a healthy install costs no
|
|
755
|
-
* filesystem read. An entry whose files are simply GONE is not refused — there
|
|
756
|
-
* is no file to repair.
|
|
757
|
-
*
|
|
758
|
-
* Runs before anything is asked or written, which is the whole point: past that, everything
|
|
759
|
-
* this protects has been rewritten.
|
|
760
|
-
*/
|
|
761
|
-
async ensureSavedSkillsReadable(savedSkills, matrix, projectDir) {
|
|
762
|
-
const unresolved = savedSkills.filter((saved) => !matrix.skills[saved.id]);
|
|
763
|
-
await this.refuseUnusableSavedSkillMetadata(
|
|
764
|
-
unresolved,
|
|
765
|
-
projectDir,
|
|
766
|
-
savedSkillMetadataUnusableError
|
|
767
|
-
);
|
|
768
|
-
}
|
|
769
|
-
/**
|
|
770
|
-
* Refuses to run when an installed entry of `savedSkills` has a metadata.yaml that describes no
|
|
771
|
-
* skill, with `refusal` saying what the file would cost the command that met it.
|
|
772
|
-
*/
|
|
773
|
-
async refuseUnusableSavedSkillMetadata(savedSkills, projectDir, refusal) {
|
|
774
|
-
const unusable = await findUnusableSavedSkillMetadata(savedSkills, projectDir);
|
|
775
|
-
if (unusable.length === 0) return;
|
|
776
|
-
for (const entry of unusable) {
|
|
777
|
-
this.log(skillMetadataUnusableDetail(entry));
|
|
778
|
-
}
|
|
779
|
-
this.error(refusal(unusable), { exit: EXIT_CODES.ERROR });
|
|
780
|
-
}
|
|
781
|
-
/**
|
|
782
|
-
* Refuses a shared configuration carrying project-scoped content at the home directory.
|
|
783
|
-
*
|
|
784
|
-
* The home directory IS the global scope, and a global installation holds only global-scoped
|
|
785
|
-
* content. Both scopes read one config file there, and on Claude one skills directory and one
|
|
786
|
-
* agents directory as well — so a project-scoped entry lands in the global config wearing a
|
|
787
|
-
* label that contradicts the file it is in, and on Claude `toClaudePluginScope` maps that
|
|
788
|
-
* declared scope onward, registering the skill against `$HOME` as a project. Nothing
|
|
789
|
-
* below this point reads the scope again, so whatever the boundary lets through becomes the
|
|
790
|
-
* truth.
|
|
791
|
-
*
|
|
792
|
-
* Shared by `init --from` and `edit --from` for the reason {@link reportValidationErrors} is
|
|
793
|
-
* shared: it is a fact about the payload and the directory, identical whichever command asked,
|
|
794
|
-
* and an invariant enforced on one producer and not the other is enforced nowhere. It matters
|
|
795
|
-
* more on the destructive one — `edit --from` reaches the same contradiction through the other
|
|
796
|
-
* door and can remove global entries on the way.
|
|
797
|
-
*
|
|
798
|
-
* It takes the DECODED selection, so it must run after the decode and can run no earlier: an
|
|
799
|
-
* all-global configuration is exactly what a global installation is for, and only the decode
|
|
800
|
-
* says which entries survived this catalogue and what scope each rests at. Every caller runs it
|
|
801
|
-
* before it reports a skip and before it writes anything, so a run about to be refused neither
|
|
802
|
-
* narrates nor mutates.
|
|
803
|
-
*/
|
|
804
|
-
refuseProjectScopedContentAtHome(result, projectDir) {
|
|
805
|
-
if (!isHomeDirectory(projectDir)) return;
|
|
806
|
-
const { skillIds, agentNames } = projectScopedContent(result);
|
|
807
|
-
if (skillIds.length === 0 && agentNames.length === 0) return;
|
|
808
|
-
this.error(sharedConfigProjectScopeAtHome(skillIds, agentNames), { exit: EXIT_CODES.ERROR });
|
|
809
|
-
}
|
|
810
|
-
/**
|
|
811
|
-
* The refusals a `--from` producer can decide only once it holds what it would install — the
|
|
812
|
-
* selection and the skills the configuration carries — asked before it lists any of it or asks
|
|
813
|
-
* whether to install it.
|
|
814
|
-
*
|
|
815
|
-
* A run that lists, asks, and is told yes has promised that a yes installs what was listed, so a
|
|
816
|
-
* refusal still to come after the question would ask a person to confirm an install that was
|
|
817
|
-
* never going to happen. Every other
|
|
818
|
-
* `--from` refusal is already decided above the lists — a carried skill asked for as a plugin or
|
|
819
|
-
* under a catalogue's id, project content at the home directory, an installation already here —
|
|
820
|
-
* and these four are the ones that need the decoded selection to be asked of:
|
|
821
|
-
*
|
|
822
|
-
* - **A placement the host does not offer.** Read off the selection and the host the run
|
|
823
|
-
* installs through, nothing else, so nothing makes it wait for the yes.
|
|
824
|
-
* - **A stack the marketplace does not ship.** The stack supplies only the configuration's
|
|
825
|
-
* description, yet the install refuses an id it cannot resolve — after the plugins are
|
|
826
|
-
* installed and the skills copied. So it is asked here, through the lookup the install makes,
|
|
827
|
-
* of the marketplace the install reads.
|
|
828
|
-
* - **A carried skill over a directory no shared configuration put there** — the user's own
|
|
829
|
-
* skill at the id the configuration minted for it. Judged on the same set the producer writes,
|
|
830
|
-
* so the skills already installed globally, which it does not write, are not asked about.
|
|
831
|
-
* - **A stack the config writer would refuse** — two skills in a category that holds one, as a
|
|
832
|
-
* hand-written row or the kept skills an apply puts back can make. Asked through the writer's
|
|
833
|
-
* own rule, because `edit --from` writes the config after its removals have landed.
|
|
834
|
-
*
|
|
835
|
-
* Shared by `init --from` and `edit --from` for the reason {@link refuseProjectScopedContentAtHome}
|
|
836
|
-
* is: an invariant enforced on one producer and not the other is enforced nowhere.
|
|
837
|
-
*/
|
|
838
|
-
async refuseSharedConfigBeforeAsking(selection, carried, root, sourceResult) {
|
|
839
|
-
this.refuseUnofferablePlacementsBeforeWriting(selection.skills, root);
|
|
840
|
-
try {
|
|
841
|
-
await refuseUnofferedStack(selection.selectedStackId, sourceResult);
|
|
842
|
-
await refuseUncarriedDestinations(carried);
|
|
843
|
-
refuseUnwritableStack(selection.assignedStack, sourceResult.matrix);
|
|
844
|
-
} catch (error) {
|
|
845
|
-
this.handleError(error);
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
/**
|
|
849
|
-
* Refuses a SELECTION asking for a mode/scope cell this host does not offer, before the run has
|
|
850
|
-
* touched anything.
|
|
851
|
-
*
|
|
852
|
-
* **`bindsItsOfferedPlacements` is not a pre-flight and cannot be made into one.** It refuses
|
|
853
|
-
* at `installPlugin`, which `installPluginSkills` calls once per skill inside a loop that
|
|
854
|
-
* collects per-skill failures — so a payload whose first plugin row is offerable and whose
|
|
855
|
-
* second is not installed a real plugin into the user's Codex registry and only then stopped,
|
|
856
|
-
* on a message ending "Nothing has been changed." Nothing removed it afterwards: no `config.ts`
|
|
857
|
-
* was written, so `uninstall` had no row naming it and `doctor` had no configuration to check
|
|
858
|
-
* it against — an orphan in the host's own registry that no command in this CLI can see. The
|
|
859
|
-
* single-row case cannot show it, because with one unofferable row the loop's first iteration
|
|
860
|
-
* IS the refusal. `edit --from` had no pre-flight at all until it came here, and met exactly
|
|
861
|
-
* that per-plugin guard after its own yes.
|
|
862
|
-
*
|
|
863
|
-
* It reads the whole selection — ejected rows included — because the roster is about CELLS
|
|
864
|
-
* rather than about plugins: a host that stopped offering `eject+project` would owe the same
|
|
865
|
-
* refusal, and a guard that only looked at plugin rows would go on writing files for it.
|
|
866
|
-
*
|
|
867
|
-
* The same function the read path calls (`refuseUnofferedPlacements`, off the same host
|
|
868
|
-
* roster), so `init`'s sentence and `compile`'s are one sentence rather than two that agree
|
|
869
|
-
* today. The per-call guard stays where it is: it is the backstop for a caller that did not
|
|
870
|
-
* come through here.
|
|
871
|
-
*/
|
|
872
|
-
refuseUnofferablePlacementsBeforeWriting(skills, root) {
|
|
873
|
-
try {
|
|
874
|
-
refuseUnofferedPlacements(skills, hostAt(root));
|
|
875
|
-
} catch (error) {
|
|
876
|
-
this.error(getErrorMessage(error), { exit: EXIT_CODES.ERROR });
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
/** Whether this run owes an account — the one thing a command may ask about the list. */
|
|
880
|
-
get hasIncompleteWork() {
|
|
881
|
-
return this.incompleteWork.length > 0;
|
|
882
|
-
}
|
|
883
|
-
/**
|
|
884
|
-
* Warns about work this run attempted and did not complete, and records it for the ending.
|
|
885
|
-
*
|
|
886
|
-
* Both halves, always. The warning stays at the point of failure because that is where it is
|
|
887
|
-
* explicable and where the surrounding output says what was being attempted; the record is
|
|
888
|
-
* what decides the exit code, and a failure the user is told about while the process reports
|
|
889
|
-
* success is exactly the state this closes.
|
|
890
|
-
*
|
|
891
|
-
* What it must never do is abort. Every caller sits past the point where the mutation it is
|
|
892
|
-
* reporting on has already happened, so stopping would relabel a state already committed to
|
|
893
|
-
* disk rather than prevent it — and would abandon the steps after it that can still land.
|
|
894
|
-
*
|
|
895
|
-
* The `recovery` sentences live in `INCOMPLETE_WORK_RECOVERY` in `utils/messages.ts`. One
|
|
896
|
-
* spelling per remedy is what matters; three spellings of "run compile" read as three
|
|
897
|
-
* different instructions.
|
|
898
|
-
*/
|
|
899
|
-
reportIncompleteWork(what, recovery) {
|
|
900
|
-
this.warn(what);
|
|
901
|
-
this.recordIncompleteWork(what, recovery);
|
|
902
|
-
}
|
|
903
|
-
/**
|
|
904
|
-
* The recording half alone, for a caller that has already said its piece: a recompile warns
|
|
905
|
-
* once per failed sub-agent with the compiler's own reason, so the ending names the roster
|
|
906
|
-
* those reasons belong to instead of repeating any of them.
|
|
907
|
-
*/
|
|
908
|
-
recordIncompleteWork(what, recovery) {
|
|
909
|
-
this.incompleteWork.push({ what, recovery });
|
|
910
|
-
}
|
|
911
|
-
/**
|
|
912
|
-
* The command's last word: what did not happen, what finishes it, and a non-zero exit.
|
|
913
|
-
*
|
|
914
|
-
* `this.exit` raises oclif's `ExitError`, which the framework carries to the process exit
|
|
915
|
-
* code and prints nothing for — so the account printed here is the whole of the output, with
|
|
916
|
-
* none of the `Error:` framing `this.error` would add to a command that in fact completed.
|
|
917
|
-
*
|
|
918
|
-
* A command that records must call this, or it files failures into a list nothing reads —
|
|
919
|
-
* which is the original silence wearing the mechanism built to close it, and is what
|
|
920
|
-
* `failure-reporting-classification.test.ts` holds every command in `src/cli/commands/` to.
|
|
921
|
-
*/
|
|
922
|
-
exitIfWorkIncomplete() {
|
|
923
|
-
if (!this.hasIncompleteWork) return;
|
|
924
|
-
this.log(`
|
|
925
|
-
${chalk.hex(CLI_COLORS.WARNING)(completedWithFailures(this.incompleteWork))}
|
|
926
|
-
`);
|
|
927
|
-
this.exit(EXIT_CODES.COMPLETED_WITH_FAILURES);
|
|
928
|
-
}
|
|
929
|
-
};
|
|
930
|
-
|
|
931
|
-
export {
|
|
932
|
-
clearTerminalScreen,
|
|
933
|
-
EXIT_CODES,
|
|
934
|
-
registerExternalSkills,
|
|
935
|
-
writeExternalSkills,
|
|
936
|
-
carriedSkillsThatDiffer,
|
|
937
|
-
readCarriedSkills,
|
|
938
|
-
BaseCommand
|
|
939
|
-
};
|
|
940
|
-
//# sourceMappingURL=chunk-V3KTSP3Q.js.map
|