0xray 4.0.12 → 4.0.14
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/.grok-plugin/plugin.json +1 -1
- package/.xray/features.json +1 -1
- package/.xray/features.schema.json +1 -1
- package/AGENTS-consumer.md +4 -2
- package/AGENTS.md +8 -1
- package/README.md +1 -1
- package/SKILLS.md +1 -1
- package/dist/AGENTS.md +8 -1
- package/dist/CHANGELOG.md +25 -1
- package/dist/README.md +1 -1
- package/dist/SKILLS.md +1 -1
- package/dist/integrations/cursor/README.md +61 -0
- package/dist/integrations/cursor/hooks/after-file-edit.js +38 -0
- package/dist/integrations/cursor/hooks/cursor-hook-utils.js +170 -0
- package/dist/integrations/cursor/hooks/cursor-usage-receipt.js +298 -0
- package/dist/integrations/cursor/hooks/hooks.json +20 -0
- package/dist/integrations/cursor/hooks/pre-compact.js +88 -0
- package/dist/integrations/cursor/hooks/pre-tool-use.js +105 -0
- package/dist/integrations/hooks/station-hook-runtime.cjs +89 -1
- package/dist/integrations/hooks/station-hook-runtime.mjs +2 -0
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/dist/nucleus/suit-temperament.d.ts +2 -2
- package/dist/nucleus/suit-temperament.js +3 -1
- package/llms.txt +6 -4
- package/package.json +4 -2
- package/scripts/foundry/CHANGELOG.md +37 -1
- package/scripts/foundry/README.md +13 -1
- package/scripts/foundry/assert-packed-dist-cli.mjs +97 -0
- package/scripts/foundry/blip-render.cjs +1237 -0
- package/scripts/foundry/blip-rippel.cjs +1963 -0
- package/scripts/foundry/blip.mjs +109 -0
- package/scripts/foundry/cli.mjs +8 -4
- package/scripts/foundry/inspect.mjs +117 -7
- package/scripts/foundry/mint-suit.cjs +215 -14
- package/scripts/foundry/package.json +7 -1
- package/scripts/foundry/plant/agents/blip-inspect.yml +5 -0
- package/scripts/foundry/plant/agents/blip.yml +5 -0
- package/scripts/foundry/plant/agents/inspect.yml +1 -1
- package/scripts/foundry/plant/agents/mill.yml +1 -1
- package/scripts/foundry/plant/agents/sound-inspect.yml +5 -0
- package/scripts/foundry/plant/agents/sound.yml +5 -0
- package/scripts/foundry/plant/motions/MOTION-REGISTRY.md +35 -0
- package/scripts/foundry/plant/motions/registry.json +81 -0
- package/scripts/foundry/plant/skills/blip/SKILL.md +39 -0
- package/scripts/foundry/plant/skills/blip-inspect/SKILL.md +32 -0
- package/scripts/foundry/plant/skills/inspect/SKILL.md +2 -2
- package/scripts/foundry/plant/skills/sound/SKILL.md +29 -0
- package/scripts/foundry/plant/skills/sound-inspect/SKILL.md +28 -0
- package/scripts/foundry/release-gate.mjs +18 -10
- package/scripts/foundry/release.mjs +3 -0
- package/scripts/foundry/sound-bed.cjs +454 -0
- package/scripts/foundry/sound-rippel.cjs +1434 -0
- package/scripts/foundry/sound.mjs +93 -0
- package/scripts/node/assert-packed-dist-cli.mjs +4 -0
- package/scripts/node/consumer-install-smoke.mjs +20 -1
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/xray/features.json +1 -1
- package/xray/features.schema.json +1 -1
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Factory-blip CLI — sibling plant to mill + sound, not a mill bolt-on.
|
|
4
|
+
* npx @0xray/foundry blip render --brief TEXT --mode still|motion:<id> [--look focus|cage] [--bed PATH] [--engine rippel|wireframe] [--out FILE]
|
|
5
|
+
* npx @0xray/foundry blip inspect
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createRequire } from "node:module";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { resolveMillRoot } from "./mill-root.mjs";
|
|
12
|
+
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
const blip = require("./blip-render.cjs");
|
|
15
|
+
|
|
16
|
+
const HELP =
|
|
17
|
+
"Usage: npx @0xray/foundry blip <render|inspect> [...args]\n" +
|
|
18
|
+
"\n" +
|
|
19
|
+
"Factory-blip plant, sibling to mill + sound. FOUNDRY_ROOT overrides cwd.\n" +
|
|
20
|
+
"render: brief → checksum seed → still|motion:<id> → 4.44s mp4 + audio bed → receipt.\n" +
|
|
21
|
+
"v0 ids: still, orb, swirl, snap, waves, spark (Rippel five are imports).\n" +
|
|
22
|
+
"Motions: Rippel VisualConfig.circles at ≥720p (default). --look focus|cage (seed picks if omitted).\n" +
|
|
23
|
+
"--engine wireframe is emergency only.\n" +
|
|
24
|
+
"Every mp4 muxes a 4.44s bed (--bed PATH or auto sound mill). Silent = inspect FAIL.\n" +
|
|
25
|
+
"kapow is a growth stub (FAIL until a renderer ships). Unknown id FAIL.\n" +
|
|
26
|
+
"still: Power Plant ident — plate dissolves over 4.44s at ≥720p, not a frozen poster.\n" +
|
|
27
|
+
"inspect: last .xray/blip/receipt.json PASS/FAIL (missing is FAIL). Lists live registry ids.\n";
|
|
28
|
+
|
|
29
|
+
function argValue(argv, name, fallback) {
|
|
30
|
+
const i = argv.indexOf(name);
|
|
31
|
+
if (i < 0 || i + 1 >= argv.length) return fallback;
|
|
32
|
+
return argv[i + 1];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function writeJson(value) {
|
|
36
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function inspectBlip(root) {
|
|
40
|
+
const motions = blip.listMotionIds();
|
|
41
|
+
const receipt = blip.readReceipt(root);
|
|
42
|
+
if (!receipt) {
|
|
43
|
+
return {
|
|
44
|
+
ok: false,
|
|
45
|
+
status: "FAIL",
|
|
46
|
+
failClosed: true,
|
|
47
|
+
plant: "blip",
|
|
48
|
+
motions,
|
|
49
|
+
detail: "missing .xray/blip/receipt.json",
|
|
50
|
+
receipt: null,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const evaluated = blip.evaluateReceipt(root, receipt);
|
|
54
|
+
const status = evaluated.status === "PASS" ? "PASS" : "FAIL";
|
|
55
|
+
return {
|
|
56
|
+
ok: status === "PASS",
|
|
57
|
+
status,
|
|
58
|
+
failClosed: true,
|
|
59
|
+
plant: "blip",
|
|
60
|
+
motions,
|
|
61
|
+
mode: evaluated.mode || receipt.motionId || receipt.mode || null,
|
|
62
|
+
pictureMode: evaluated.pictureMode || receipt.pictureMode || null,
|
|
63
|
+
durationSec: evaluated.durationSec ?? receipt.durationSec ?? null,
|
|
64
|
+
receipt,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function main() {
|
|
69
|
+
const argv = process.argv.slice(2);
|
|
70
|
+
const cmd = argv[0];
|
|
71
|
+
const rest = argv.slice(1);
|
|
72
|
+
const root = resolveMillRoot();
|
|
73
|
+
|
|
74
|
+
if (!cmd || cmd === "--help" || cmd === "-h" || cmd === "help") {
|
|
75
|
+
process.stdout.write(HELP);
|
|
76
|
+
process.exit(cmd ? 0 : 1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (cmd === "inspect") {
|
|
80
|
+
const report = inspectBlip(root);
|
|
81
|
+
writeJson(report);
|
|
82
|
+
process.exit(report.ok ? 0 : 1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (cmd === "render") {
|
|
86
|
+
const result = blip.renderBlip({
|
|
87
|
+
root,
|
|
88
|
+
brief: argValue(rest, "--brief", "factory-blip"),
|
|
89
|
+
pictureMode: argValue(rest, "--picture-mode", argValue(rest, "--mode", "still")),
|
|
90
|
+
lookKind: argValue(rest, "--look", null),
|
|
91
|
+
bed: argValue(rest, "--bed", null),
|
|
92
|
+
engine: argValue(rest, "--engine", "rippel"),
|
|
93
|
+
out: argValue(rest, "--out", null),
|
|
94
|
+
});
|
|
95
|
+
writeJson(result.receipt);
|
|
96
|
+
process.exit(result.receipt.status === "PASS" ? 0 : 1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
process.stderr.write(HELP);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export { inspectBlip };
|
|
104
|
+
|
|
105
|
+
const isMain =
|
|
106
|
+
process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
107
|
+
if (isMain) {
|
|
108
|
+
main();
|
|
109
|
+
}
|
package/scripts/foundry/cli.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @0xray/foundry CLI — mill, not exo. No fifth MCP.
|
|
4
4
|
*
|
|
5
|
-
* npx @0xray/foundry <reconcile|stamp|gate|release|docs-check|docs-build|mint|inspect|ci|hooks> [...args]
|
|
5
|
+
* npx @0xray/foundry <reconcile|stamp|gate|release|docs-check|docs-build|mint|inspect|sound|blip|ci|hooks> [...args]
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { spawnSync } from "node:child_process";
|
|
@@ -17,17 +17,21 @@ const COMMANDS = {
|
|
|
17
17
|
"docs-build": { script: "docs-build.mjs", preset: [] },
|
|
18
18
|
mint: { script: "mint.mjs", preset: [] },
|
|
19
19
|
inspect: { script: "inspect.mjs", preset: [] },
|
|
20
|
+
sound: { script: "sound.mjs", preset: [] },
|
|
21
|
+
blip: { script: "blip.mjs", preset: [] },
|
|
20
22
|
ci: { script: "ci-monitor.mjs", preset: [] },
|
|
21
23
|
hooks: { script: "hooks.mjs", preset: [] },
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
const HELP =
|
|
25
|
-
"Usage: npx @0xray/foundry <reconcile|stamp|gate|release|docs-check|docs-build|mint|inspect|ci|hooks> [...args]\n" +
|
|
27
|
+
"Usage: npx @0xray/foundry <reconcile|stamp|gate|release|docs-check|docs-build|mint|inspect|sound|blip|ci|hooks> [...args]\n" +
|
|
26
28
|
"\n" +
|
|
27
29
|
"Mill, not exo. FOUNDRY_ROOT overrides cwd (the repo being milled).\n" +
|
|
28
30
|
"release bumps/commits/pushes/publishes the milled cwd — pass --dry-run or --i-mean-it (or FOUNDRY_RELEASE=1).\n" +
|
|
29
|
-
"Mint fastens
|
|
30
|
-
"inspect runs
|
|
31
|
+
"Mint fastens the requested factory plant (default mill; foundry.json plant: sound|blip turns mill off), then inspect.\n" +
|
|
32
|
+
"inspect runs mill checks plus, on a sound seat, last bed receipt PASS/FAIL; on a blip seat, last blip receipt.\n" +
|
|
33
|
+
"sound render|inspect is the factory-sound plant (brief → seed → genre → wav → metrics). Sibling to mill, not a mill copy.\n" +
|
|
34
|
+
"blip render|inspect is the factory-blip plant (brief → seed → still|motion:<id> → 4.44s mp4). Sibling to mill + sound.\n" +
|
|
31
35
|
"gate is build+test. docs-build is Docusaurus on the 0xray exo. ci reports GitHub Actions (no auto-push). hooks installs git pre/post hooks.\n";
|
|
32
36
|
|
|
33
37
|
const cmd = process.argv[2];
|
|
@@ -82,15 +82,25 @@ function checkDiff(root) {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
function checkPlantVsWorn(root, millRoot) {
|
|
85
|
-
const
|
|
85
|
+
const kinds = mint.loadFactoryPlantKinds(root);
|
|
86
|
+
const allow = mint.factoryPlantAllowlist(millRoot, root);
|
|
86
87
|
const millPlant = {
|
|
87
|
-
skills:
|
|
88
|
-
agents:
|
|
88
|
+
skills: kinds.includes("mill") ? mint.catalogPlant("mill").skills : [],
|
|
89
|
+
agents: kinds.includes("mill") ? mint.catalogPlant("mill").agents : [],
|
|
90
|
+
sound: kinds.includes("sound") ? mint.catalogPlant("sound") : { skills: [], agents: [] },
|
|
91
|
+
blip: kinds.includes("blip") ? mint.catalogPlant("blip") : { skills: [], agents: [] },
|
|
89
92
|
};
|
|
90
93
|
const params = mint.loadFoundryParams(root);
|
|
91
94
|
const tree = {
|
|
92
95
|
skills: mint.listConsumerSkillNames(root, params.skills),
|
|
93
96
|
agents: mint.listConsumerAgentFiles(root, params.agents),
|
|
97
|
+
plantKinds: kinds,
|
|
98
|
+
soundPlantSkills: millPlant.sound.skills,
|
|
99
|
+
soundPlantAgents: millPlant.sound.agents,
|
|
100
|
+
sound: millPlant.sound,
|
|
101
|
+
blipPlantSkills: millPlant.blip.skills,
|
|
102
|
+
blipPlantAgents: millPlant.blip.agents,
|
|
103
|
+
blip: millPlant.blip,
|
|
94
104
|
};
|
|
95
105
|
const shopPlant = mint.loadShopPlant(root);
|
|
96
106
|
try {
|
|
@@ -98,10 +108,14 @@ function checkPlantVsWorn(root, millRoot) {
|
|
|
98
108
|
return {
|
|
99
109
|
id: "plant-vs-worn",
|
|
100
110
|
ok: true,
|
|
111
|
+
plant: kinds,
|
|
101
112
|
millPlant: millPlant.skills,
|
|
113
|
+
soundPlant: millPlant.sound.skills,
|
|
114
|
+
blipPlant: millPlant.blip.skills,
|
|
102
115
|
shopPlant,
|
|
103
116
|
tree: tree.skills,
|
|
104
117
|
worn: mint.wornSkillNames(root),
|
|
118
|
+
allow: allow.skills,
|
|
105
119
|
};
|
|
106
120
|
} catch (err) {
|
|
107
121
|
return {
|
|
@@ -110,6 +124,9 @@ function checkPlantVsWorn(root, millRoot) {
|
|
|
110
124
|
detail: err instanceof Error ? err.message : String(err),
|
|
111
125
|
extraSkills: err.extraSkills || [],
|
|
112
126
|
extraAgents: err.extraAgents || [],
|
|
127
|
+
plant: kinds,
|
|
128
|
+
soundPlant: millPlant.sound.skills,
|
|
129
|
+
blipPlant: millPlant.blip.skills,
|
|
113
130
|
shopPlant,
|
|
114
131
|
};
|
|
115
132
|
}
|
|
@@ -125,18 +142,107 @@ function checkReceipt(root) {
|
|
|
125
142
|
return { id: "receipt", ok: false, detail: "missing .xray/foundry-inventory.json" };
|
|
126
143
|
}
|
|
127
144
|
const millPlantSkills = inventory.millPlant?.skills || [];
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
const
|
|
145
|
+
const soundPlantSkills = inventory.soundPlant?.skills || [];
|
|
146
|
+
const blipPlantSkills = inventory.blipPlant?.skills || [];
|
|
147
|
+
const kinds = mint.inventoryPlantKinds(inventory);
|
|
148
|
+
const needMill = kinds.includes("mill");
|
|
149
|
+
const needSound = kinds.includes("sound");
|
|
150
|
+
const needBlip = kinds.includes("blip");
|
|
151
|
+
const millOk = !needMill || (millPlantSkills.includes("mill") && millPlantSkills.includes("inspect"));
|
|
152
|
+
const soundOk =
|
|
153
|
+
!needSound || (soundPlantSkills.includes("sound") && soundPlantSkills.includes("sound-inspect"));
|
|
154
|
+
const blipOk =
|
|
155
|
+
!needBlip || (blipPlantSkills.includes("blip") && blipPlantSkills.includes("blip-inspect"));
|
|
156
|
+
const ok = millOk && soundOk && blipOk && (needMill || needSound || needBlip);
|
|
157
|
+
let detail = null;
|
|
158
|
+
if (!ok && needMill && !millOk) detail = "millPlant.skills must include mill and inspect";
|
|
159
|
+
else if (!ok && needSound && !soundOk) detail = "soundPlant.skills must include sound and sound-inspect";
|
|
160
|
+
else if (!ok && needBlip && !blipOk) detail = "blipPlant.skills must include blip and blip-inspect";
|
|
161
|
+
else if (!ok) detail = "inventory plant is empty";
|
|
131
162
|
return {
|
|
132
163
|
id: "receipt",
|
|
133
164
|
ok,
|
|
134
165
|
suit: inventory.suit,
|
|
135
166
|
mill: inventory.mill,
|
|
167
|
+
plant: kinds,
|
|
136
168
|
millPlant: millPlantSkills,
|
|
169
|
+
soundPlant: soundPlantSkills,
|
|
170
|
+
blipPlant: blipPlantSkills,
|
|
137
171
|
dna: typeof inventory.dna === "string" ? inventory.dna : null,
|
|
138
172
|
pack: "0xray-suit",
|
|
139
|
-
detail
|
|
173
|
+
detail,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function checkSoundBed(root) {
|
|
178
|
+
const inventory = readJson(path.join(root, ".xray", "foundry-inventory.json"));
|
|
179
|
+
const kinds = inventory ? mint.inventoryPlantKinds(inventory) : mint.loadFactoryPlantKinds(root);
|
|
180
|
+
if (!kinds.includes("sound")) return null;
|
|
181
|
+
const bed = require("./sound-bed.cjs");
|
|
182
|
+
const receipt = bed.readReceipt(root);
|
|
183
|
+
if (!receipt) {
|
|
184
|
+
return {
|
|
185
|
+
id: "sound-bed",
|
|
186
|
+
ok: true,
|
|
187
|
+
skipped: true,
|
|
188
|
+
status: "NONE",
|
|
189
|
+
plant: kinds,
|
|
190
|
+
detail: "no bed receipt yet — npx @0xray/foundry sound render",
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
const status = receipt.status === "PASS" ? "PASS" : "FAIL";
|
|
194
|
+
const unreadable = receipt.reason === "receipt-unreadable";
|
|
195
|
+
return {
|
|
196
|
+
id: "sound-bed",
|
|
197
|
+
ok: status === "PASS" && !unreadable,
|
|
198
|
+
status,
|
|
199
|
+
failClosed: true,
|
|
200
|
+
plant: kinds,
|
|
201
|
+
metrics: receipt.metrics || null,
|
|
202
|
+
gates: receipt.gates || null,
|
|
203
|
+
wav: receipt.wav || null,
|
|
204
|
+
seed: receipt.seed || null,
|
|
205
|
+
detail: status === "PASS" ? null : receipt.reason || "bed receipt FAIL",
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function checkBlip(root) {
|
|
210
|
+
const inventory = readJson(path.join(root, ".xray", "foundry-inventory.json"));
|
|
211
|
+
const kinds = inventory ? mint.inventoryPlantKinds(inventory) : mint.loadFactoryPlantKinds(root);
|
|
212
|
+
if (!kinds.includes("blip")) return null;
|
|
213
|
+
const blip = require("./blip-render.cjs");
|
|
214
|
+
const receipt = blip.readReceipt(root);
|
|
215
|
+
const motions = blip.listMotionIds();
|
|
216
|
+
if (!receipt) {
|
|
217
|
+
return {
|
|
218
|
+
id: "blip",
|
|
219
|
+
ok: true,
|
|
220
|
+
skipped: true,
|
|
221
|
+
status: "NONE",
|
|
222
|
+
plant: kinds,
|
|
223
|
+
motions,
|
|
224
|
+
detail: "no blip receipt yet — npx @0xray/foundry blip render --brief \"...\" --mode still",
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const evaluated = blip.evaluateReceipt(root, receipt);
|
|
228
|
+
const status = evaluated.status === "PASS" ? "PASS" : "FAIL";
|
|
229
|
+
const unreadable = receipt.reason === "receipt-unreadable";
|
|
230
|
+
return {
|
|
231
|
+
id: "blip",
|
|
232
|
+
ok: status === "PASS" && !unreadable,
|
|
233
|
+
status,
|
|
234
|
+
failClosed: true,
|
|
235
|
+
plant: kinds,
|
|
236
|
+
motions,
|
|
237
|
+
mode: evaluated.mode || receipt.motionId || receipt.mode || null,
|
|
238
|
+
pictureMode: evaluated.pictureMode || receipt.pictureMode || null,
|
|
239
|
+
palette: receipt.palette || null,
|
|
240
|
+
plate: receipt.plate || null,
|
|
241
|
+
durationSec: evaluated.durationSec ?? receipt.durationSec ?? null,
|
|
242
|
+
mp4: evaluated.mp4 || receipt.mp4 || null,
|
|
243
|
+
hasVideo: evaluated.hasVideo ?? receipt.hasVideo ?? null,
|
|
244
|
+
hasAudio: evaluated.hasAudio ?? receipt.hasAudio ?? null,
|
|
245
|
+
detail: status === "PASS" ? null : evaluated.reason || receipt.reason || "blip receipt FAIL",
|
|
140
246
|
};
|
|
141
247
|
}
|
|
142
248
|
|
|
@@ -268,6 +374,10 @@ export async function inspectSuit(root, opts = {}) {
|
|
|
268
374
|
checks.push(checkDiff(root));
|
|
269
375
|
checks.push(checkPlantVsWorn(root, millRoot));
|
|
270
376
|
checks.push(checkReceipt(root));
|
|
377
|
+
const soundBed = checkSoundBed(root);
|
|
378
|
+
if (soundBed) checks.push(soundBed);
|
|
379
|
+
const blip = checkBlip(root);
|
|
380
|
+
if (blip) checks.push(blip);
|
|
271
381
|
checks.push(checkCi(root));
|
|
272
382
|
|
|
273
383
|
if (skipLive) {
|
|
@@ -24,6 +24,17 @@ const DEFAULT_PARAMS = {
|
|
|
24
24
|
/** Factory shop plant. First-class with mill plant. Not 45/42 costume. */
|
|
25
25
|
const FACTORY_SHOP_SKILLS = ["shop-extract", "shop-witness", "shop-pin"];
|
|
26
26
|
|
|
27
|
+
/** Factory plant kinds. Mill is default. Sound is a bed factory. Blip is a tiny-video factory. */
|
|
28
|
+
const FACTORY_PLANT_CATALOG = {
|
|
29
|
+
mill: { skills: ["mill", "inspect"], agents: ["mill.yml", "inspect.yml"] },
|
|
30
|
+
sound: { skills: ["sound", "sound-inspect"], agents: ["sound.yml", "sound-inspect.yml"] },
|
|
31
|
+
blip: { skills: ["blip", "blip-inspect"], agents: ["blip.yml", "blip-inspect.yml"] },
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function isFactoryPlantKind(kind) {
|
|
35
|
+
return Boolean(kind && FACTORY_PLANT_CATALOG[kind]);
|
|
36
|
+
}
|
|
37
|
+
|
|
27
38
|
function deepMerge(src, dest) {
|
|
28
39
|
if (typeof src !== "object" || src === null) return dest !== undefined ? dest : src;
|
|
29
40
|
if (Array.isArray(src)) return Array.isArray(dest) ? dest : src;
|
|
@@ -118,6 +129,90 @@ function millPlantDir(millPackageRoot) {
|
|
|
118
129
|
return null;
|
|
119
130
|
}
|
|
120
131
|
|
|
132
|
+
function emptyPlantFiles() {
|
|
133
|
+
return { skills: [], agents: [] };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function catalogPlant(kind) {
|
|
137
|
+
const spec = FACTORY_PLANT_CATALOG[kind];
|
|
138
|
+
return spec ? { skills: [...spec.skills], agents: [...spec.agents] } : emptyPlantFiles();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** foundry.json plant: "sound" | "blip" | "mill" | ["mill","sound","blip"]. millPlant:false turns mill off. */
|
|
142
|
+
function loadFactoryPlantKinds(targetDir) {
|
|
143
|
+
const extra = loadFoundryExtra(targetDir);
|
|
144
|
+
const kinds = [];
|
|
145
|
+
const raw = extra.plant;
|
|
146
|
+
if (Array.isArray(raw)) {
|
|
147
|
+
for (const item of raw) {
|
|
148
|
+
if (isFactoryPlantKind(item)) kinds.push(item);
|
|
149
|
+
}
|
|
150
|
+
} else if (isFactoryPlantKind(raw)) {
|
|
151
|
+
kinds.push(raw);
|
|
152
|
+
} else if (extra.soundPlant === true || extra.blipPlant === true) {
|
|
153
|
+
if (extra.millPlant !== false) kinds.push("mill");
|
|
154
|
+
if (extra.soundPlant === true) kinds.push("sound");
|
|
155
|
+
if (extra.blipPlant === true) kinds.push("blip");
|
|
156
|
+
} else {
|
|
157
|
+
kinds.push("mill");
|
|
158
|
+
}
|
|
159
|
+
if (extra.millPlant === false) {
|
|
160
|
+
return [...new Set(kinds.filter((kind) => kind !== "mill"))];
|
|
161
|
+
}
|
|
162
|
+
return [...new Set(kinds)];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function unionPlantFiles(kinds) {
|
|
166
|
+
const skills = [];
|
|
167
|
+
const agents = [];
|
|
168
|
+
for (const kind of kinds) {
|
|
169
|
+
const spec = catalogPlant(kind);
|
|
170
|
+
for (const name of spec.skills) {
|
|
171
|
+
if (!skills.includes(name)) skills.push(name);
|
|
172
|
+
}
|
|
173
|
+
for (const file of spec.agents) {
|
|
174
|
+
if (!agents.includes(file)) agents.push(file);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return { skills, agents };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function existingPlantFiles(plantDir, spec) {
|
|
181
|
+
if (!plantDir || !spec) return emptyPlantFiles();
|
|
182
|
+
const skillsSrc = path.join(plantDir, "skills");
|
|
183
|
+
const agentsSrc = path.join(plantDir, "agents");
|
|
184
|
+
const haveSkills = new Set(listSkillNamesAt(skillsSrc));
|
|
185
|
+
const haveAgents = new Set(listAgentFilesAt(agentsSrc));
|
|
186
|
+
return {
|
|
187
|
+
skills: spec.skills.filter((name) => haveSkills.has(name)),
|
|
188
|
+
agents: spec.agents.filter((file) => haveAgents.has(file)),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function factoryPlantAllowlist(millPackageRoot, targetDir) {
|
|
193
|
+
const kinds = loadFactoryPlantKinds(targetDir);
|
|
194
|
+
return {
|
|
195
|
+
kinds,
|
|
196
|
+
...existingPlantFiles(millPlantDir(millPackageRoot), unionPlantFiles(kinds)),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function inventoryPlantKinds(inventory) {
|
|
201
|
+
if (!inventory || typeof inventory !== "object") return ["mill"];
|
|
202
|
+
if (Array.isArray(inventory.plant) && inventory.plant.length > 0) {
|
|
203
|
+
return inventory.plant.filter((kind) => isFactoryPlantKind(kind));
|
|
204
|
+
}
|
|
205
|
+
if (isFactoryPlantKind(inventory.plant)) return [inventory.plant];
|
|
206
|
+
const hasSound = Array.isArray(inventory.soundPlant?.skills) && inventory.soundPlant.skills.length > 0;
|
|
207
|
+
const hasBlip = Array.isArray(inventory.blipPlant?.skills) && inventory.blipPlant.skills.length > 0;
|
|
208
|
+
const hasMill = Array.isArray(inventory.millPlant?.skills) && inventory.millPlant.skills.length > 0;
|
|
209
|
+
const kinds = [];
|
|
210
|
+
if (hasMill) kinds.push("mill");
|
|
211
|
+
if (hasSound) kinds.push("sound");
|
|
212
|
+
if (hasBlip) kinds.push("blip");
|
|
213
|
+
return kinds.length > 0 ? kinds : ["mill"];
|
|
214
|
+
}
|
|
215
|
+
|
|
121
216
|
function listSkillNamesAt(skillsSrc) {
|
|
122
217
|
if (!isDirectory(skillsSrc)) return [];
|
|
123
218
|
const names = [];
|
|
@@ -141,20 +236,18 @@ function listAgentFilesAt(agentsSrc) {
|
|
|
141
236
|
.map((entry) => entry.name);
|
|
142
237
|
}
|
|
143
238
|
|
|
144
|
-
|
|
145
|
-
function fastenMillPlant(millPackageRoot, targetDir, log) {
|
|
146
|
-
const plant = millPlantDir(millPackageRoot);
|
|
147
|
-
if (!plant) return { skills: [], agents: [] };
|
|
239
|
+
function copyPlantFiles(plant, spec, targetDir) {
|
|
148
240
|
const skillsSrc = path.join(plant, "skills");
|
|
149
241
|
const agentsSrc = path.join(plant, "agents");
|
|
150
|
-
const skills =
|
|
151
|
-
const agents =
|
|
242
|
+
const skills = spec.skills || [];
|
|
243
|
+
const agents = spec.agents || [];
|
|
152
244
|
const skillDirs = listProjectSkillDirs(targetDir);
|
|
153
245
|
const agentsDest = path.join(targetDir, ".opencode", "agents");
|
|
154
246
|
for (const dir of skillDirs) {
|
|
155
247
|
for (const name of skills) {
|
|
156
248
|
const src = path.join(skillsSrc, name, "SKILL.md");
|
|
157
249
|
const destMd = path.join(dir, name, "SKILL.md");
|
|
250
|
+
if (!fs.existsSync(src)) continue;
|
|
158
251
|
if (path.resolve(src) === path.resolve(destMd)) continue;
|
|
159
252
|
if (fs.existsSync(destMd)) continue;
|
|
160
253
|
fs.mkdirSync(path.dirname(destMd), { recursive: true });
|
|
@@ -164,18 +257,44 @@ function fastenMillPlant(millPackageRoot, targetDir, log) {
|
|
|
164
257
|
for (const file of agents) {
|
|
165
258
|
const src = path.join(agentsSrc, file);
|
|
166
259
|
const dest = path.join(agentsDest, file);
|
|
260
|
+
if (!fs.existsSync(src)) continue;
|
|
167
261
|
if (path.resolve(src) === path.resolve(dest)) continue;
|
|
168
262
|
if (fs.existsSync(dest)) continue;
|
|
169
263
|
fs.mkdirSync(agentsDest, { recursive: true });
|
|
170
264
|
fs.copyFileSync(src, dest);
|
|
171
265
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
266
|
+
return { skills, agents };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** Fasten requested factory plants. Default mill only. Sound/blip seats skip mill unless asked. */
|
|
270
|
+
function fastenMillPlant(millPackageRoot, targetDir, log) {
|
|
271
|
+
const plant = millPlantDir(millPackageRoot);
|
|
272
|
+
const kinds = loadFactoryPlantKinds(targetDir);
|
|
273
|
+
if (!plant) {
|
|
274
|
+
return { skills: [], agents: [], kinds, sound: emptyPlantFiles(), blip: emptyPlantFiles() };
|
|
275
|
+
}
|
|
276
|
+
const mill = kinds.includes("mill")
|
|
277
|
+
? existingPlantFiles(plant, catalogPlant("mill"))
|
|
278
|
+
: emptyPlantFiles();
|
|
279
|
+
const sound = kinds.includes("sound")
|
|
280
|
+
? existingPlantFiles(plant, catalogPlant("sound"))
|
|
281
|
+
: emptyPlantFiles();
|
|
282
|
+
const blip = kinds.includes("blip")
|
|
283
|
+
? existingPlantFiles(plant, catalogPlant("blip"))
|
|
284
|
+
: emptyPlantFiles();
|
|
285
|
+
const fastened = copyPlantFiles(
|
|
286
|
+
plant,
|
|
287
|
+
unionPlantFiles(kinds.filter((kind) => isFactoryPlantKind(kind))),
|
|
288
|
+
targetDir,
|
|
289
|
+
);
|
|
290
|
+
if (log && (fastened.skills.length > 0 || fastened.agents.length > 0)) {
|
|
291
|
+
log("foundry-mint", "Fastened factory plant", "info", {
|
|
292
|
+
plant: kinds,
|
|
293
|
+
skills: fastened.skills.length,
|
|
294
|
+
agents: fastened.agents.length,
|
|
176
295
|
});
|
|
177
296
|
}
|
|
178
|
-
return { skills, agents };
|
|
297
|
+
return { skills: mill.skills, agents: mill.agents, kinds, sound, blip };
|
|
179
298
|
}
|
|
180
299
|
|
|
181
300
|
function isDirectory(p) {
|
|
@@ -371,30 +490,80 @@ function previousTreeAllowlist(targetDir) {
|
|
|
371
490
|
const shopSkills = Array.isArray(shopFromInv)
|
|
372
491
|
? normalizeNameList(shopFromInv)
|
|
373
492
|
: normalizeNameList(shopFromInv?.skills);
|
|
493
|
+
const soundFromInv = inventory?.soundPlant;
|
|
494
|
+
const soundSkills = Array.isArray(soundFromInv)
|
|
495
|
+
? normalizeNameList(soundFromInv)
|
|
496
|
+
: normalizeNameList(soundFromInv?.skills);
|
|
497
|
+
const soundAgents = normalizeNameList(soundFromInv?.agents);
|
|
498
|
+
const blipFromInv = inventory?.blipPlant;
|
|
499
|
+
const blipSkills = Array.isArray(blipFromInv)
|
|
500
|
+
? normalizeNameList(blipFromInv)
|
|
501
|
+
: normalizeNameList(blipFromInv?.skills);
|
|
502
|
+
const blipAgents = normalizeNameList(blipFromInv?.agents);
|
|
374
503
|
return {
|
|
375
504
|
skills: [
|
|
376
505
|
...(Array.isArray(inventory?.tree?.skills) ? inventory.tree.skills : []),
|
|
506
|
+
...(Array.isArray(inventory?.millPlant?.skills) ? inventory.millPlant.skills : []),
|
|
377
507
|
...shopSkills,
|
|
508
|
+
...soundSkills,
|
|
509
|
+
...blipSkills,
|
|
510
|
+
],
|
|
511
|
+
agents: [
|
|
512
|
+
...(Array.isArray(inventory?.tree?.agents) ? inventory.tree.agents : []),
|
|
513
|
+
...(Array.isArray(inventory?.millPlant?.agents) ? inventory.millPlant.agents : []),
|
|
514
|
+
...soundAgents,
|
|
515
|
+
...blipAgents,
|
|
378
516
|
],
|
|
379
|
-
agents: Array.isArray(inventory?.tree?.agents) ? inventory.tree.agents : [],
|
|
380
517
|
};
|
|
381
518
|
} catch {
|
|
382
519
|
return { skills: [], agents: [] };
|
|
383
520
|
}
|
|
384
521
|
}
|
|
385
522
|
|
|
386
|
-
|
|
523
|
+
function soundPlantFromTree(targetDir, tree) {
|
|
524
|
+
if (Array.isArray(tree?.soundPlantSkills) || Array.isArray(tree?.sound?.skills)) {
|
|
525
|
+
return {
|
|
526
|
+
skills: normalizeNameList(tree.soundPlantSkills || tree.sound?.skills),
|
|
527
|
+
agents: normalizeNameList(tree.soundPlantAgents || tree.sound?.agents),
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
const kinds = Array.isArray(tree?.plantKinds) ? tree.plantKinds : loadFactoryPlantKinds(targetDir);
|
|
531
|
+
return kinds.includes("sound") ? catalogPlant("sound") : emptyPlantFiles();
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function blipPlantFromTree(targetDir, tree) {
|
|
535
|
+
if (Array.isArray(tree?.blipPlantSkills) || Array.isArray(tree?.blip?.skills)) {
|
|
536
|
+
return {
|
|
537
|
+
skills: normalizeNameList(tree.blipPlantSkills || tree.blip?.skills),
|
|
538
|
+
agents: normalizeNameList(tree.blipPlantAgents || tree.blip?.agents),
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
const kinds = Array.isArray(tree?.plantKinds) ? tree.plantKinds : loadFactoryPlantKinds(targetDir);
|
|
542
|
+
return kinds.includes("blip") ? catalogPlant("blip") : emptyPlantFiles();
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/** Extra worn names that are neither mill/sound/blip plant, their plant, shop plant, nor a prior overlay. */
|
|
387
546
|
function costumeDumpExtras(targetDir, millPlant, tree) {
|
|
388
547
|
const prior = previousTreeAllowlist(targetDir);
|
|
389
548
|
const shopPlant = loadShopPlant(targetDir);
|
|
549
|
+
const soundPlant = soundPlantFromTree(targetDir, tree);
|
|
550
|
+
const blipPlant = blipPlantFromTree(targetDir, tree);
|
|
390
551
|
const allowedSkills = new Set([
|
|
391
552
|
...(Array.isArray(millPlant?.skills) ? millPlant.skills : []),
|
|
553
|
+
...(Array.isArray(millPlant?.sound?.skills) ? millPlant.sound.skills : []),
|
|
554
|
+
...(Array.isArray(millPlant?.blip?.skills) ? millPlant.blip.skills : []),
|
|
555
|
+
...soundPlant.skills,
|
|
556
|
+
...blipPlant.skills,
|
|
392
557
|
...(Array.isArray(tree?.skills) ? tree.skills : []),
|
|
393
558
|
...prior.skills,
|
|
394
559
|
...shopPlant,
|
|
395
560
|
]);
|
|
396
561
|
const allowedAgents = new Set([
|
|
397
562
|
...(Array.isArray(millPlant?.agents) ? millPlant.agents : []),
|
|
563
|
+
...(Array.isArray(millPlant?.sound?.agents) ? millPlant.sound.agents : []),
|
|
564
|
+
...(Array.isArray(millPlant?.blip?.agents) ? millPlant.blip.agents : []),
|
|
565
|
+
...soundPlant.agents,
|
|
566
|
+
...blipPlant.agents,
|
|
398
567
|
...(Array.isArray(tree?.agents) ? tree.agents : []),
|
|
399
568
|
...prior.agents,
|
|
400
569
|
]);
|
|
@@ -482,10 +651,19 @@ function mintConsumerFromSsot(packageRoot, targetDir, log, tree) {
|
|
|
482
651
|
const millPlanted =
|
|
483
652
|
(Array.isArray(tree?.millPlantSkills) && tree.millPlantSkills.length > 0) ||
|
|
484
653
|
(Array.isArray(tree?.millPlantAgents) && tree.millPlantAgents.length > 0);
|
|
654
|
+
const soundPlanted =
|
|
655
|
+
(Array.isArray(tree?.soundPlantSkills) && tree.soundPlantSkills.length > 0) ||
|
|
656
|
+
(Array.isArray(tree?.soundPlantAgents) && tree.soundPlantAgents.length > 0);
|
|
657
|
+
const blipPlanted =
|
|
658
|
+
(Array.isArray(tree?.blipPlantSkills) && tree.blipPlantSkills.length > 0) ||
|
|
659
|
+
(Array.isArray(tree?.blipPlantAgents) && tree.blipPlantAgents.length > 0);
|
|
660
|
+
const plantKinds = Array.isArray(tree?.plantKinds) && tree.plantKinds.length > 0
|
|
661
|
+
? tree.plantKinds
|
|
662
|
+
: loadFactoryPlantKinds(targetDir);
|
|
485
663
|
const costume = wantsCostume(targetDir);
|
|
486
664
|
const suit = overlayed
|
|
487
665
|
? "overlay"
|
|
488
|
-
: millPlanted
|
|
666
|
+
: millPlanted || soundPlanted || blipPlanted
|
|
489
667
|
? "fastened"
|
|
490
668
|
: costume
|
|
491
669
|
? "costume"
|
|
@@ -494,12 +672,21 @@ function mintConsumerFromSsot(packageRoot, targetDir, log, tree) {
|
|
|
494
672
|
mill: { name: mill.name || "@0xray/foundry", version: mill.version },
|
|
495
673
|
consumer: { name: consumer.name, version: consumer.version },
|
|
496
674
|
suit,
|
|
675
|
+
plant: plantKinds,
|
|
497
676
|
params: tree?.params || loadFoundryParams(targetDir),
|
|
498
677
|
tree: { skills, agents },
|
|
499
678
|
millPlant: {
|
|
500
679
|
skills: Array.isArray(tree?.millPlantSkills) ? tree.millPlantSkills : [],
|
|
501
680
|
agents: Array.isArray(tree?.millPlantAgents) ? tree.millPlantAgents : [],
|
|
502
681
|
},
|
|
682
|
+
soundPlant: {
|
|
683
|
+
skills: Array.isArray(tree?.soundPlantSkills) ? tree.soundPlantSkills : [],
|
|
684
|
+
agents: Array.isArray(tree?.soundPlantAgents) ? tree.soundPlantAgents : [],
|
|
685
|
+
},
|
|
686
|
+
blipPlant: {
|
|
687
|
+
skills: Array.isArray(tree?.blipPlantSkills) ? tree.blipPlantSkills : [],
|
|
688
|
+
agents: Array.isArray(tree?.blipPlantAgents) ? tree.blipPlantAgents : [],
|
|
689
|
+
},
|
|
503
690
|
shopPlant: {
|
|
504
691
|
skills: Array.isArray(tree?.shopPlantSkills) ? tree.shopPlantSkills : loadShopPlant(targetDir),
|
|
505
692
|
},
|
|
@@ -539,8 +726,15 @@ function mintConsumerSuit(millPackageRoot, targetDir, log) {
|
|
|
539
726
|
const params = loadFoundryParams(targetDir);
|
|
540
727
|
const millPlant = fastenMillPlant(millPackageRoot, targetDir, log);
|
|
541
728
|
const tree = overlayConsumerTree(targetDir, log, params);
|
|
729
|
+
tree.plantKinds = millPlant.kinds || loadFactoryPlantKinds(targetDir);
|
|
542
730
|
tree.millPlantSkills = millPlant.skills;
|
|
543
731
|
tree.millPlantAgents = millPlant.agents;
|
|
732
|
+
tree.soundPlantSkills = millPlant.sound?.skills || [];
|
|
733
|
+
tree.soundPlantAgents = millPlant.sound?.agents || [];
|
|
734
|
+
tree.sound = millPlant.sound || emptyPlantFiles();
|
|
735
|
+
tree.blipPlantSkills = millPlant.blip?.skills || [];
|
|
736
|
+
tree.blipPlantAgents = millPlant.blip?.agents || [];
|
|
737
|
+
tree.blip = millPlant.blip || emptyPlantFiles();
|
|
544
738
|
tree.shopPlantSkills = loadShopPlant(targetDir);
|
|
545
739
|
tree.codex = overlayJsonFacet(
|
|
546
740
|
resolveInside(targetDir, params.codex),
|
|
@@ -569,6 +763,8 @@ function mintConsumerSuit(millPackageRoot, targetDir, log) {
|
|
|
569
763
|
module.exports = {
|
|
570
764
|
DEFAULT_PARAMS,
|
|
571
765
|
FACTORY_SHOP_SKILLS,
|
|
766
|
+
FACTORY_PLANT_CATALOG,
|
|
767
|
+
isFactoryPlantKind,
|
|
572
768
|
deepMerge,
|
|
573
769
|
loadFoundryParams,
|
|
574
770
|
loadFoundryExtra,
|
|
@@ -576,6 +772,11 @@ module.exports = {
|
|
|
576
772
|
declaredShopPlant,
|
|
577
773
|
wantsCostume,
|
|
578
774
|
millPlantDir,
|
|
775
|
+
loadFactoryPlantKinds,
|
|
776
|
+
factoryPlantAllowlist,
|
|
777
|
+
inventoryPlantKinds,
|
|
778
|
+
catalogPlant,
|
|
779
|
+
unionPlantFiles,
|
|
579
780
|
listSkillNamesAt,
|
|
580
781
|
listAgentFilesAt,
|
|
581
782
|
fastenMillPlant,
|