@lumenflow/cli 3.13.2 → 3.15.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/dist/chunk-2D2VOCA4.js +37 -0
- package/dist/chunk-2D5KFYGX.js +284 -0
- package/dist/chunk-2GXVIN57.js +14072 -0
- package/dist/chunk-2MQ7HZWZ.js +26 -0
- package/dist/chunk-2UFQ3A3C.js +643 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-4N74J3UT.js +15 -0
- package/dist/chunk-5GTOXFYR.js +392 -0
- package/dist/chunk-5VY6MQMC.js +240 -0
- package/dist/chunk-67XVPMRY.js +1297 -0
- package/dist/chunk-6HO4GWJE.js +164 -0
- package/dist/chunk-6W5XHWYV.js +1890 -0
- package/dist/chunk-6X4EMYJQ.js +64 -0
- package/dist/chunk-6XYXI2NQ.js +772 -0
- package/dist/chunk-7ANSOV6Q.js +285 -0
- package/dist/chunk-A624LFLB.js +1380 -0
- package/dist/chunk-ADN5NHG4.js +126 -0
- package/dist/chunk-B7YJYJKG.js +33 -0
- package/dist/chunk-CCLHCPKG.js +210 -0
- package/dist/chunk-CK36VROC.js +1584 -0
- package/dist/chunk-D3UOFRSB.js +81 -0
- package/dist/chunk-DFR4DJBM.js +230 -0
- package/dist/chunk-DSYBDHYH.js +79 -0
- package/dist/chunk-DWMLTXKQ.js +1176 -0
- package/dist/chunk-E3REJTAJ.js +28 -0
- package/dist/chunk-EA3IVO64.js +633 -0
- package/dist/chunk-EK2AKZKD.js +55 -0
- package/dist/chunk-ELD7JTTT.js +343 -0
- package/dist/chunk-EX6TT2XI.js +195 -0
- package/dist/chunk-EXINSFZE.js +82 -0
- package/dist/chunk-EZ6ZBYBM.js +510 -0
- package/dist/chunk-FBKAPTJ2.js +16 -0
- package/dist/chunk-FVLV5RYH.js +1118 -0
- package/dist/chunk-GDNSBQVK.js +2485 -0
- package/dist/chunk-GPQHMBNN.js +278 -0
- package/dist/chunk-GTFJB67L.js +68 -0
- package/dist/chunk-HANJXVKW.js +1127 -0
- package/dist/chunk-HEVS5YLD.js +269 -0
- package/dist/chunk-HMEVZKPQ.js +9 -0
- package/dist/chunk-HRGSYNLM.js +3511 -0
- package/dist/chunk-ISZR5N4K.js +60 -0
- package/dist/chunk-J6SUPR2C.js +226 -0
- package/dist/chunk-JERYVEIZ.js +244 -0
- package/dist/chunk-JHHWGL2N.js +87 -0
- package/dist/chunk-JONWQUB5.js +775 -0
- package/dist/chunk-K2DIWWDM.js +1766 -0
- package/dist/chunk-KY4PGL5V.js +969 -0
- package/dist/chunk-L737LQ4C.js +1285 -0
- package/dist/chunk-LFTWYIB2.js +497 -0
- package/dist/chunk-LV47RFNJ.js +41 -0
- package/dist/chunk-MKSAITI7.js +15 -0
- package/dist/chunk-MZ7RKIX4.js +212 -0
- package/dist/chunk-NAP6CFSO.js +84 -0
- package/dist/chunk-ND6MY37M.js +16 -0
- package/dist/chunk-NMG736UR.js +683 -0
- package/dist/chunk-NRAXROED.js +32 -0
- package/dist/chunk-NRIZR3A7.js +690 -0
- package/dist/chunk-NX43BG3M.js +233 -0
- package/dist/chunk-O645XLSI.js +297 -0
- package/dist/chunk-OMJD6A3S.js +235 -0
- package/dist/chunk-QB6SJD4T.js +430 -0
- package/dist/chunk-QFSTL4J3.js +276 -0
- package/dist/chunk-QLGDFMFX.js +212 -0
- package/dist/chunk-RIAAGL2E.js +13 -0
- package/dist/chunk-RWO5XMZ6.js +86 -0
- package/dist/chunk-RXRKBBSM.js +149 -0
- package/dist/chunk-RZOZMML6.js +363 -0
- package/dist/chunk-U7I7FS7T.js +113 -0
- package/dist/chunk-UI42RODY.js +717 -0
- package/dist/chunk-UTVMVSCO.js +519 -0
- package/dist/chunk-V6OJGLBA.js +1746 -0
- package/dist/chunk-W2JHVH7D.js +152 -0
- package/dist/chunk-WD3Y7VQN.js +280 -0
- package/dist/chunk-WOCTQ5MS.js +303 -0
- package/dist/chunk-WZR3ZUNN.js +696 -0
- package/dist/chunk-XGI665H7.js +150 -0
- package/dist/chunk-XKY65P2T.js +304 -0
- package/dist/chunk-Y4CQZY65.js +57 -0
- package/dist/chunk-YFEXKLVE.js +194 -0
- package/dist/chunk-YHO3HS5X.js +287 -0
- package/dist/chunk-YLS7AZSX.js +738 -0
- package/dist/chunk-ZE473AO6.js +49 -0
- package/dist/chunk-ZF747T3O.js +644 -0
- package/dist/chunk-ZHCZHZH3.js +43 -0
- package/dist/chunk-ZZNZX2XY.js +87 -0
- package/dist/constants-7QAP3VQ4.js +23 -0
- package/dist/dist-IY3UUMWK.js +33 -0
- package/dist/docs-sync.js +60 -25
- package/dist/docs-sync.js.map +1 -1
- package/dist/gates-runners.js +43 -2
- package/dist/gates-runners.js.map +1 -1
- package/dist/init-templates.js +26 -219
- package/dist/init-templates.js.map +1 -1
- package/dist/invariants-runner-W5RGHCSU.js +27 -0
- package/dist/lane-lock-6J36HD5O.js +35 -0
- package/dist/lumenflow-upgrade.js +60 -0
- package/dist/lumenflow-upgrade.js.map +1 -1
- package/dist/mem-checkpoint-core-EANG2GVN.js +14 -0
- package/dist/mem-signal-core-2LZ2WYHW.js +19 -0
- package/dist/memory-store-OLB5FO7K.js +18 -0
- package/dist/plan-edit.js +19 -24
- package/dist/plan-edit.js.map +1 -1
- package/dist/plan-promote.js +15 -23
- package/dist/plan-promote.js.map +1 -1
- package/dist/plan-resolve.js +111 -0
- package/dist/plan-resolve.js.map +1 -0
- package/dist/public-manifest.js +2 -2
- package/dist/public-manifest.js.map +1 -1
- package/dist/service-6BYCOCO5.js +13 -0
- package/dist/spawn-policy-resolver-NTSZYQ6R.js +17 -0
- package/dist/spawn-task-builder-R4E2BHSW.js +22 -0
- package/dist/sync-templates.js +12 -0
- package/dist/sync-templates.js.map +1 -1
- package/dist/wu-brief.js +1 -1
- package/dist/wu-brief.js.map +1 -1
- package/dist/wu-claim-validation.js +9 -1
- package/dist/wu-claim-validation.js.map +1 -1
- package/dist/wu-done-policies.js +78 -33
- package/dist/wu-done-policies.js.map +1 -1
- package/dist/wu-done-pr-WLFFFEPJ.js +25 -0
- package/dist/wu-done-validation-3J5E36FE.js +30 -0
- package/dist/wu-done.js +42 -1
- package/dist/wu-done.js.map +1 -1
- package/dist/wu-duplicate-id-detector-5S7JHELK.js +232 -0
- package/dist/wu-edit-operations.js +7 -6
- package/dist/wu-edit-operations.js.map +1 -1
- package/dist/wu-edit.js +23 -3
- package/dist/wu-edit.js.map +1 -1
- package/dist/wu-spawn-prompt-builders.js +38 -1
- package/dist/wu-spawn-prompt-builders.js.map +1 -1
- package/dist/wu-spawn-strategy-resolver.js +30 -11
- package/dist/wu-spawn-strategy-resolver.js.map +1 -1
- package/package.json +8 -8
- package/packs/sidekick/.turbo/turbo-build.log +1 -1
- package/packs/sidekick/.turbo/turbo-typecheck.log +4 -0
- package/packs/sidekick/package.json +1 -1
- package/packs/software-delivery/.turbo/turbo-build.log +1 -1
- package/packs/software-delivery/.turbo/turbo-typecheck.log +4 -0
- package/packs/software-delivery/package.json +1 -1
- package/templates/core/AGENTS.md.template +19 -0
- package/templates/core/LUMENFLOW.md.template +13 -2
- package/templates/core/UPGRADING.md.template +6 -6
- package/templates/core/ai/onboarding/agent-invocation-guide.md.template +6 -5
- package/templates/core/ai/onboarding/first-15-mins.md.template +1 -1
- package/templates/core/ai/onboarding/first-wu-mistakes.md.template +10 -0
- package/templates/core/ai/onboarding/initiative-orchestration.md.template +5 -7
- package/templates/core/ai/onboarding/quick-ref-commands.md.template +11 -9
- package/templates/core/ai/onboarding/release-process.md.template +1 -1
- package/templates/core/ai/onboarding/starting-prompt.md.template +5 -6
- package/templates/core/ai/onboarding/wu-sizing-guide.md.template +11 -2
- package/templates/vendors/claude/.claude/skills/initiative-management/SKILL.md.template +2 -2
- package/templates/vendors/claude/.claude/skills/multi-agent-coordination/SKILL.md.template +2 -2
- package/templates/vendors/claude/.claude/skills/orchestration/SKILL.md.template +4 -4
- package/templates/vendors/cursor/.cursor/rules/lumenflow.md.template +9 -1
- package/templates/vendors/windsurf/.windsurf/rules/lumenflow.md.template +9 -1
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import {
|
|
2
|
+
validatePack
|
|
3
|
+
} from "./chunk-2D5KFYGX.js";
|
|
4
|
+
import {
|
|
5
|
+
computePackHash
|
|
6
|
+
} from "./chunk-EXINSFZE.js";
|
|
7
|
+
import {
|
|
8
|
+
WORKSPACE_FILE_NAME
|
|
9
|
+
} from "./chunk-HANJXVKW.js";
|
|
10
|
+
import {
|
|
11
|
+
WU_OPTIONS,
|
|
12
|
+
createWUParser,
|
|
13
|
+
runCLI
|
|
14
|
+
} from "./chunk-2GXVIN57.js";
|
|
15
|
+
import {
|
|
16
|
+
ErrorCodes,
|
|
17
|
+
createError
|
|
18
|
+
} from "./chunk-RXRKBBSM.js";
|
|
19
|
+
|
|
20
|
+
// src/pack-install.ts
|
|
21
|
+
import { createHash } from "crypto";
|
|
22
|
+
import { existsSync, mkdirSync } from "fs";
|
|
23
|
+
import { readFile, writeFile } from "fs/promises";
|
|
24
|
+
import { join, resolve } from "path";
|
|
25
|
+
import { execFile } from "child_process";
|
|
26
|
+
import { tmpdir } from "os";
|
|
27
|
+
import { promisify } from "util";
|
|
28
|
+
import YAML from "yaml";
|
|
29
|
+
var execFileAsync = promisify(execFile);
|
|
30
|
+
var LOG_PREFIX = "[pack:install]";
|
|
31
|
+
var PACK_SOURCE_VALUES = ["local", "git", "registry"];
|
|
32
|
+
var UTF8 = "utf-8";
|
|
33
|
+
var DEFAULT_REGISTRY_URL = "https://registry.lumenflow.dev";
|
|
34
|
+
var REGISTRY_PACKS_API_PATH = "/api/registry/packs";
|
|
35
|
+
var SHA256_INTEGRITY_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
36
|
+
async function readWorkspaceFile(workspaceRoot) {
|
|
37
|
+
const workspacePath = join(workspaceRoot, WORKSPACE_FILE_NAME);
|
|
38
|
+
if (!existsSync(workspacePath)) {
|
|
39
|
+
throw createError(
|
|
40
|
+
ErrorCodes.WORKSPACE_NOT_FOUND,
|
|
41
|
+
`${WORKSPACE_FILE_NAME} not found at ${workspacePath}. Run "lumenflow init" to create a workspace first.`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const content = await readFile(workspacePath, UTF8);
|
|
45
|
+
const parsed = YAML.parse(content);
|
|
46
|
+
if (!parsed || typeof parsed !== "object") {
|
|
47
|
+
throw createError(
|
|
48
|
+
ErrorCodes.WORKSPACE_MALFORMED,
|
|
49
|
+
`${WORKSPACE_FILE_NAME} is empty or malformed.`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (!Array.isArray(parsed.packs)) {
|
|
53
|
+
parsed.packs = [];
|
|
54
|
+
}
|
|
55
|
+
return parsed;
|
|
56
|
+
}
|
|
57
|
+
async function writeWorkspaceFile(workspaceRoot, data) {
|
|
58
|
+
const workspacePath = join(workspaceRoot, WORKSPACE_FILE_NAME);
|
|
59
|
+
const content = YAML.stringify(data, { lineWidth: 120 });
|
|
60
|
+
await writeFile(workspacePath, content, UTF8);
|
|
61
|
+
}
|
|
62
|
+
function buildPackPin(options) {
|
|
63
|
+
const pin = {
|
|
64
|
+
id: options.packId,
|
|
65
|
+
version: options.version,
|
|
66
|
+
integrity: options.integrity,
|
|
67
|
+
source: options.source
|
|
68
|
+
};
|
|
69
|
+
if (options.url) {
|
|
70
|
+
pin.url = options.url;
|
|
71
|
+
}
|
|
72
|
+
if (options.registryUrl) {
|
|
73
|
+
pin.registry_url = options.registryUrl;
|
|
74
|
+
}
|
|
75
|
+
return pin;
|
|
76
|
+
}
|
|
77
|
+
function upsertPackPin(packs, newPin) {
|
|
78
|
+
const existingIndex = packs.findIndex((p) => p.id === newPin.id);
|
|
79
|
+
if (existingIndex >= 0) {
|
|
80
|
+
const updated = [...packs];
|
|
81
|
+
updated[existingIndex] = newPin;
|
|
82
|
+
return updated;
|
|
83
|
+
}
|
|
84
|
+
return [...packs, newPin];
|
|
85
|
+
}
|
|
86
|
+
async function installPack(options) {
|
|
87
|
+
const { workspaceRoot, packId, source, version, url, registryUrl, packRoot } = options;
|
|
88
|
+
const absolutePackRoot = resolve(packRoot);
|
|
89
|
+
let workspace;
|
|
90
|
+
try {
|
|
91
|
+
workspace = await readWorkspaceFile(workspaceRoot);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
94
|
+
return { success: false, error: message };
|
|
95
|
+
}
|
|
96
|
+
let validation;
|
|
97
|
+
try {
|
|
98
|
+
validation = await validatePack({ packRoot: absolutePackRoot });
|
|
99
|
+
} catch (err) {
|
|
100
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
101
|
+
return { success: false, error: `Pack validation failed: ${message}` };
|
|
102
|
+
}
|
|
103
|
+
if (!validation.allPassed) {
|
|
104
|
+
return {
|
|
105
|
+
success: false,
|
|
106
|
+
error: `Pack validation failed for "${packId}". Run pack:validate --pack-root ${absolutePackRoot} for details.`,
|
|
107
|
+
validation
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
let integrity;
|
|
111
|
+
try {
|
|
112
|
+
integrity = await computePackHash({ packRoot: absolutePackRoot });
|
|
113
|
+
} catch (err) {
|
|
114
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
115
|
+
return { success: false, error: `Integrity hash computation failed: ${message}` };
|
|
116
|
+
}
|
|
117
|
+
const pin = buildPackPin({ packId, version, source, integrity, url, registryUrl });
|
|
118
|
+
workspace.packs = upsertPackPin(workspace.packs, pin);
|
|
119
|
+
try {
|
|
120
|
+
await writeWorkspaceFile(workspaceRoot, workspace);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
123
|
+
return { success: false, error: `Failed to write ${WORKSPACE_FILE_NAME}: ${message}` };
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
success: true,
|
|
127
|
+
integrity,
|
|
128
|
+
validation
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function buildRegistryTarballUrl(registryUrl, packId, version) {
|
|
132
|
+
const base = registryUrl.replace(/\/+$/, "");
|
|
133
|
+
return `${base}${REGISTRY_PACKS_API_PATH}/${encodeURIComponent(packId)}/versions/${encodeURIComponent(version)}/tarball`;
|
|
134
|
+
}
|
|
135
|
+
function buildRegistryPackDetailUrl(registryUrl, packId) {
|
|
136
|
+
const base = registryUrl.replace(/\/+$/, "");
|
|
137
|
+
return `${base}${REGISTRY_PACKS_API_PATH}/${encodeURIComponent(packId)}`;
|
|
138
|
+
}
|
|
139
|
+
function isLocalRegistryHost(hostname) {
|
|
140
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
|
|
141
|
+
}
|
|
142
|
+
function validateRegistryUrl(registryUrl) {
|
|
143
|
+
let parsed;
|
|
144
|
+
try {
|
|
145
|
+
parsed = new URL(registryUrl);
|
|
146
|
+
} catch {
|
|
147
|
+
return { valid: false, error: `Invalid --registry-url: "${registryUrl}"` };
|
|
148
|
+
}
|
|
149
|
+
if (parsed.username || parsed.password) {
|
|
150
|
+
return {
|
|
151
|
+
valid: false,
|
|
152
|
+
error: "Registry URL must not include username/password credentials"
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const usesHttps = parsed.protocol === "https:";
|
|
156
|
+
const allowsInsecureLocalhost = parsed.protocol === "http:" && isLocalRegistryHost(parsed.hostname);
|
|
157
|
+
if (!usesHttps && !allowsInsecureLocalhost) {
|
|
158
|
+
return {
|
|
159
|
+
valid: false,
|
|
160
|
+
error: "Registry URL must use HTTPS unless host is localhost, 127.0.0.1, or ::1"
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return { valid: true, normalizedUrl: parsed.toString().replace(/\/+$/, "") };
|
|
164
|
+
}
|
|
165
|
+
function parsePackDetailResponse(data) {
|
|
166
|
+
if (!data || typeof data !== "object") {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
const maybeRecord = data;
|
|
170
|
+
const packRaw = "pack" in maybeRecord && maybeRecord.pack && typeof maybeRecord.pack === "object" ? maybeRecord.pack : maybeRecord;
|
|
171
|
+
const latestVersion = typeof packRaw.latestVersion === "string" ? packRaw.latestVersion : void 0;
|
|
172
|
+
const versionsRaw = packRaw.versions;
|
|
173
|
+
if (!Array.isArray(versionsRaw)) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
const versions = [];
|
|
177
|
+
for (const entry of versionsRaw) {
|
|
178
|
+
if (!entry || typeof entry !== "object") {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const maybeVersion = entry.version;
|
|
182
|
+
const maybeIntegrity = entry.integrity;
|
|
183
|
+
if (typeof maybeVersion === "string" && typeof maybeIntegrity === "string") {
|
|
184
|
+
versions.push({ version: maybeVersion, integrity: maybeIntegrity });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return { latestVersion, versions };
|
|
188
|
+
}
|
|
189
|
+
async function resolveRegistryIntegrity(options) {
|
|
190
|
+
const registryValidation = validateRegistryUrl(options.registryUrl);
|
|
191
|
+
if (!registryValidation.valid || !registryValidation.normalizedUrl) {
|
|
192
|
+
return {
|
|
193
|
+
success: false,
|
|
194
|
+
error: registryValidation.error ?? `Invalid registry URL: ${options.registryUrl}`
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
const detailUrl = buildRegistryPackDetailUrl(registryValidation.normalizedUrl, options.packId);
|
|
198
|
+
let detailResponse;
|
|
199
|
+
try {
|
|
200
|
+
detailResponse = await options.fetchFn(detailUrl);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
203
|
+
return {
|
|
204
|
+
success: false,
|
|
205
|
+
error: `Failed to fetch registry metadata for "${options.packId}": ${message}`
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
if (!detailResponse.ok) {
|
|
209
|
+
const body = await detailResponse.text().catch(() => "No response body");
|
|
210
|
+
return {
|
|
211
|
+
success: false,
|
|
212
|
+
error: `Registry metadata lookup returned ${String(detailResponse.status)} ${detailResponse.statusText} for "${options.packId}": ${body}`
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
let detailJson;
|
|
216
|
+
try {
|
|
217
|
+
detailJson = await detailResponse.json();
|
|
218
|
+
} catch {
|
|
219
|
+
return {
|
|
220
|
+
success: false,
|
|
221
|
+
error: `Failed to parse registry metadata JSON for "${options.packId}"`
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
const parsedDetail = parsePackDetailResponse(detailJson);
|
|
225
|
+
if (!parsedDetail || parsedDetail.versions.length === 0) {
|
|
226
|
+
return {
|
|
227
|
+
success: false,
|
|
228
|
+
error: `Registry metadata for "${options.packId}" did not include any versions`
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
const targetVersion = options.version === "latest" ? parsedDetail.latestVersion ?? parsedDetail.versions[parsedDetail.versions.length - 1]?.version : options.version;
|
|
232
|
+
if (!targetVersion) {
|
|
233
|
+
return {
|
|
234
|
+
success: false,
|
|
235
|
+
error: `Registry metadata for "${options.packId}" is missing latestVersion`
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
const versionEntry = parsedDetail.versions.find((entry) => entry.version === targetVersion);
|
|
239
|
+
if (!versionEntry) {
|
|
240
|
+
return {
|
|
241
|
+
success: false,
|
|
242
|
+
error: `Version "${targetVersion}" not found for pack "${options.packId}"`
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
if (!SHA256_INTEGRITY_PATTERN.test(versionEntry.integrity)) {
|
|
246
|
+
return {
|
|
247
|
+
success: false,
|
|
248
|
+
error: `Registry returned invalid integrity for "${options.packId}@${targetVersion}"`
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
success: true,
|
|
253
|
+
resolvedVersion: versionEntry.version,
|
|
254
|
+
resolvedIntegrity: versionEntry.integrity
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function verifySha256Integrity(buffer, expectedIntegrity) {
|
|
258
|
+
const data = Buffer.from(buffer);
|
|
259
|
+
const actualHex = createHash("sha256").update(data).digest("hex");
|
|
260
|
+
const actualIntegrity = `sha256:${actualHex}`;
|
|
261
|
+
return {
|
|
262
|
+
valid: actualIntegrity === expectedIntegrity,
|
|
263
|
+
actual: actualIntegrity,
|
|
264
|
+
expected: expectedIntegrity
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
async function extractTarball(tarballPath, targetDir) {
|
|
268
|
+
mkdirSync(targetDir, { recursive: true });
|
|
269
|
+
await execFileAsync("tar", ["-xzf", tarballPath, "-C", targetDir]);
|
|
270
|
+
}
|
|
271
|
+
async function installPackFromRegistry(options) {
|
|
272
|
+
const { workspaceRoot, packId, version, registryUrl, integrity, fetchFn } = options;
|
|
273
|
+
const registryValidation = validateRegistryUrl(registryUrl);
|
|
274
|
+
if (!registryValidation.valid || !registryValidation.normalizedUrl) {
|
|
275
|
+
return {
|
|
276
|
+
success: false,
|
|
277
|
+
error: registryValidation.error ?? `Invalid registry URL: ${registryUrl}`
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
const normalizedRegistryUrl = registryValidation.normalizedUrl;
|
|
281
|
+
let resolvedVersion = version;
|
|
282
|
+
let resolvedIntegrity = integrity?.trim() ?? "";
|
|
283
|
+
if (resolvedIntegrity.length > 0 && !SHA256_INTEGRITY_PATTERN.test(resolvedIntegrity)) {
|
|
284
|
+
return {
|
|
285
|
+
success: false,
|
|
286
|
+
error: `Invalid integrity format for "${packId}@${resolvedVersion}". Expected sha256:<hex>`
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
if (resolvedIntegrity.length === 0) {
|
|
290
|
+
const integrityResult = await resolveRegistryIntegrity({
|
|
291
|
+
packId,
|
|
292
|
+
version: resolvedVersion,
|
|
293
|
+
registryUrl: normalizedRegistryUrl,
|
|
294
|
+
fetchFn
|
|
295
|
+
});
|
|
296
|
+
if (!integrityResult.success || !integrityResult.resolvedIntegrity || !integrityResult.resolvedVersion) {
|
|
297
|
+
return {
|
|
298
|
+
success: false,
|
|
299
|
+
error: integrityResult.error ?? `Failed to resolve integrity for "${packId}@${resolvedVersion}"`
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
resolvedVersion = integrityResult.resolvedVersion;
|
|
303
|
+
resolvedIntegrity = integrityResult.resolvedIntegrity;
|
|
304
|
+
}
|
|
305
|
+
const tarballUrl = buildRegistryTarballUrl(normalizedRegistryUrl, packId, resolvedVersion);
|
|
306
|
+
let response;
|
|
307
|
+
try {
|
|
308
|
+
response = await fetchFn(tarballUrl);
|
|
309
|
+
} catch (err) {
|
|
310
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
311
|
+
return {
|
|
312
|
+
success: false,
|
|
313
|
+
error: `Registry fetch failed for "${packId}@${resolvedVersion}": ${message}`
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
if (!response.ok) {
|
|
317
|
+
const body = await response.text().catch(() => "No response body");
|
|
318
|
+
return {
|
|
319
|
+
success: false,
|
|
320
|
+
error: `Registry returned ${String(response.status)} ${response.statusText} for "${packId}@${resolvedVersion}": ${body}`
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
let tarballBuffer;
|
|
324
|
+
try {
|
|
325
|
+
tarballBuffer = await response.arrayBuffer();
|
|
326
|
+
} catch (err) {
|
|
327
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
328
|
+
return {
|
|
329
|
+
success: false,
|
|
330
|
+
error: `Failed to read tarball response body: ${message}`
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
const integrityCheck = verifySha256Integrity(tarballBuffer, resolvedIntegrity);
|
|
334
|
+
if (!integrityCheck.valid) {
|
|
335
|
+
return {
|
|
336
|
+
success: false,
|
|
337
|
+
error: `Integrity mismatch for "${packId}@${resolvedVersion}". Expected: ${integrityCheck.expected}, Got: ${integrityCheck.actual}`
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
const tempBase = join(tmpdir(), `lumenflow-registry-install-${packId}-${Date.now()}`);
|
|
341
|
+
const tarballPath = join(tempBase, `${packId}-${resolvedVersion}.tar.gz`);
|
|
342
|
+
const extractDir = join(tempBase, "pack");
|
|
343
|
+
try {
|
|
344
|
+
mkdirSync(tempBase, { recursive: true });
|
|
345
|
+
await writeFile(tarballPath, Buffer.from(tarballBuffer));
|
|
346
|
+
await extractTarball(tarballPath, extractDir);
|
|
347
|
+
} catch (err) {
|
|
348
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
349
|
+
return {
|
|
350
|
+
success: false,
|
|
351
|
+
error: `Failed to extract tarball for "${packId}@${resolvedVersion}": ${message}`
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
return installPack({
|
|
355
|
+
workspaceRoot,
|
|
356
|
+
packId,
|
|
357
|
+
source: "registry",
|
|
358
|
+
version: resolvedVersion,
|
|
359
|
+
registryUrl: normalizedRegistryUrl,
|
|
360
|
+
packRoot: extractDir
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
var DEFAULT_PACKS_ROOT = "packages/@lumenflow/packs";
|
|
364
|
+
var PACK_INSTALL_OPTIONS = {
|
|
365
|
+
packId: {
|
|
366
|
+
name: "id",
|
|
367
|
+
flags: "--id <packId>",
|
|
368
|
+
description: "Pack ID to install"
|
|
369
|
+
},
|
|
370
|
+
source: {
|
|
371
|
+
name: "source",
|
|
372
|
+
flags: "--source <source>",
|
|
373
|
+
description: "Pack source: local, git, or registry"
|
|
374
|
+
},
|
|
375
|
+
version: {
|
|
376
|
+
name: "version",
|
|
377
|
+
flags: "--version <version>",
|
|
378
|
+
description: "Pack version in semver format"
|
|
379
|
+
},
|
|
380
|
+
url: {
|
|
381
|
+
name: "url",
|
|
382
|
+
flags: "--url <url>",
|
|
383
|
+
description: "Git repository URL (required for source: git)"
|
|
384
|
+
},
|
|
385
|
+
registryUrl: {
|
|
386
|
+
name: "registryUrl",
|
|
387
|
+
flags: "--registry-url <url>",
|
|
388
|
+
description: "Registry base URL (for source: registry)"
|
|
389
|
+
},
|
|
390
|
+
packRoot: {
|
|
391
|
+
name: "packRoot",
|
|
392
|
+
flags: "--pack-root <dir>",
|
|
393
|
+
description: "Direct path to resolved pack directory on disk (overrides default resolution)"
|
|
394
|
+
},
|
|
395
|
+
packsRoot: {
|
|
396
|
+
name: "packsRoot",
|
|
397
|
+
flags: "--packs-root <dir>",
|
|
398
|
+
description: `Root directory containing packs (default: "${DEFAULT_PACKS_ROOT}")`
|
|
399
|
+
},
|
|
400
|
+
integrity: {
|
|
401
|
+
name: "integrity",
|
|
402
|
+
flags: "--integrity <hash>",
|
|
403
|
+
description: 'Expected SHA-256 integrity hash in "sha256:<hex>" format (optional)'
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
function resolvePackRootFromCli(options) {
|
|
407
|
+
if (options.directPackRoot) {
|
|
408
|
+
return resolve(options.directPackRoot);
|
|
409
|
+
}
|
|
410
|
+
return resolve(options.packsRoot, options.packId);
|
|
411
|
+
}
|
|
412
|
+
async function main() {
|
|
413
|
+
const opts = createWUParser({
|
|
414
|
+
name: "pack-install",
|
|
415
|
+
description: "Install a LumenFlow domain pack into workspace.yaml",
|
|
416
|
+
options: [
|
|
417
|
+
PACK_INSTALL_OPTIONS.packId,
|
|
418
|
+
PACK_INSTALL_OPTIONS.source,
|
|
419
|
+
PACK_INSTALL_OPTIONS.version,
|
|
420
|
+
PACK_INSTALL_OPTIONS.url,
|
|
421
|
+
PACK_INSTALL_OPTIONS.registryUrl,
|
|
422
|
+
PACK_INSTALL_OPTIONS.packRoot,
|
|
423
|
+
PACK_INSTALL_OPTIONS.packsRoot,
|
|
424
|
+
PACK_INSTALL_OPTIONS.integrity,
|
|
425
|
+
WU_OPTIONS.force
|
|
426
|
+
],
|
|
427
|
+
required: ["id", "source", "version"]
|
|
428
|
+
});
|
|
429
|
+
const packId = opts.id;
|
|
430
|
+
const source = opts.source;
|
|
431
|
+
const version = opts.version;
|
|
432
|
+
const url = opts.url;
|
|
433
|
+
const registryUrl = opts.registryUrl;
|
|
434
|
+
const directPackRoot = opts.packRoot;
|
|
435
|
+
const packsRoot = opts.packsRoot ?? DEFAULT_PACKS_ROOT;
|
|
436
|
+
if (!PACK_SOURCE_VALUES.includes(source)) {
|
|
437
|
+
console.error(
|
|
438
|
+
`${LOG_PREFIX} Error: Invalid source "${source}". Must be one of: ${PACK_SOURCE_VALUES.join(", ")}`
|
|
439
|
+
);
|
|
440
|
+
process.exit(1);
|
|
441
|
+
}
|
|
442
|
+
if (source === "git" && !url) {
|
|
443
|
+
console.error(`${LOG_PREFIX} Error: --url is required when --source is "git"`);
|
|
444
|
+
process.exit(1);
|
|
445
|
+
}
|
|
446
|
+
const workspaceRoot = resolve(".");
|
|
447
|
+
console.log(`${LOG_PREFIX} Installing pack "${packId}" v${version} (source: ${source})...`);
|
|
448
|
+
let result;
|
|
449
|
+
if (source === "registry") {
|
|
450
|
+
const resolvedRegistryUrl = registryUrl ?? DEFAULT_REGISTRY_URL;
|
|
451
|
+
const integrityFlag = opts.integrity;
|
|
452
|
+
const registryValidation = validateRegistryUrl(resolvedRegistryUrl);
|
|
453
|
+
if (!registryValidation.valid || !registryValidation.normalizedUrl) {
|
|
454
|
+
console.error(`${LOG_PREFIX} Error: ${registryValidation.error}`);
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
result = await installPackFromRegistry({
|
|
458
|
+
workspaceRoot,
|
|
459
|
+
packId,
|
|
460
|
+
version,
|
|
461
|
+
registryUrl: registryValidation.normalizedUrl,
|
|
462
|
+
integrity: integrityFlag,
|
|
463
|
+
fetchFn: globalThis.fetch
|
|
464
|
+
});
|
|
465
|
+
} else {
|
|
466
|
+
const packRoot = resolvePackRootFromCli({ directPackRoot, packId, packsRoot });
|
|
467
|
+
result = await installPack({
|
|
468
|
+
workspaceRoot,
|
|
469
|
+
packId,
|
|
470
|
+
source,
|
|
471
|
+
version,
|
|
472
|
+
url,
|
|
473
|
+
registryUrl,
|
|
474
|
+
packRoot
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
if (!result.success) {
|
|
478
|
+
console.error(`${LOG_PREFIX} Installation failed: ${result.error}`);
|
|
479
|
+
process.exit(1);
|
|
480
|
+
}
|
|
481
|
+
console.log(`${LOG_PREFIX} Pack "${packId}" v${version} installed successfully.`);
|
|
482
|
+
console.log(`${LOG_PREFIX} Integrity: ${result.integrity}`);
|
|
483
|
+
console.log(`${LOG_PREFIX} workspace.yaml updated.`);
|
|
484
|
+
}
|
|
485
|
+
if (import.meta.main) {
|
|
486
|
+
void runCLI(main);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export {
|
|
490
|
+
LOG_PREFIX,
|
|
491
|
+
DEFAULT_REGISTRY_URL,
|
|
492
|
+
installPack,
|
|
493
|
+
validateRegistryUrl,
|
|
494
|
+
resolveRegistryIntegrity,
|
|
495
|
+
installPackFromRegistry,
|
|
496
|
+
main
|
|
497
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createGitForPath
|
|
3
|
+
} from "./chunk-2UFQ3A3C.js";
|
|
4
|
+
|
|
5
|
+
// ../core/dist/stamp-tracking.js
|
|
6
|
+
import path from "path";
|
|
7
|
+
function toPosixPath(value) {
|
|
8
|
+
return value.split(path.sep).join("/");
|
|
9
|
+
}
|
|
10
|
+
function stampIdFromFilePath(filePath) {
|
|
11
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
12
|
+
const fileName = path.posix.basename(normalized);
|
|
13
|
+
if (!fileName.startsWith("WU-") || !fileName.endsWith(".done")) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return fileName.slice(0, -".done".length);
|
|
17
|
+
}
|
|
18
|
+
async function listTrackedWUStampIds({ projectRoot, stampsDir, gitRaw }) {
|
|
19
|
+
try {
|
|
20
|
+
const raw = gitRaw ?? ((args) => createGitForPath(projectRoot).raw(args));
|
|
21
|
+
const stampsDirRelative = path.isAbsolute(stampsDir) ? path.relative(projectRoot, stampsDir) : stampsDir;
|
|
22
|
+
const pathspec = `${toPosixPath(stampsDirRelative)}/WU-*.done`;
|
|
23
|
+
const output = await raw(["ls-files", "--", pathspec]);
|
|
24
|
+
const tracked = /* @__PURE__ */ new Set();
|
|
25
|
+
for (const line of output.split("\n")) {
|
|
26
|
+
const value = line.trim();
|
|
27
|
+
if (!value)
|
|
28
|
+
continue;
|
|
29
|
+
const id = stampIdFromFilePath(value);
|
|
30
|
+
if (id)
|
|
31
|
+
tracked.add(id);
|
|
32
|
+
}
|
|
33
|
+
return tracked;
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
listTrackedWUStampIds
|
|
41
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
runCLI
|
|
3
|
+
} from "./chunk-2GXVIN57.js";
|
|
4
|
+
import {
|
|
5
|
+
die
|
|
6
|
+
} from "./chunk-RXRKBBSM.js";
|
|
7
|
+
|
|
8
|
+
// src/wu-spawn.ts
|
|
9
|
+
async function main() {
|
|
10
|
+
const removalGuidance = "wu:spawn has been removed. Use wu:brief for config-aware prompt generation or wu:delegate for explicit delegation lineage.";
|
|
11
|
+
die(removalGuidance);
|
|
12
|
+
}
|
|
13
|
+
if (import.meta.main) {
|
|
14
|
+
void runCLI(main);
|
|
15
|
+
}
|