@cimplify/cli 0.15.3 → 0.17.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.
Files changed (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -2
  3. package/dist/add-C3DPTVH3.mjs +140 -0
  4. package/dist/{add-KRIIKAS4.mjs → add-JX2GXWXU.mjs} +4 -4
  5. package/dist/ai-HUZTVJIQ.mjs +17 -0
  6. package/dist/{assets-CWB72PZG.mjs → assets-MPESJSUV.mjs} +12 -7
  7. package/dist/{auth-step-up-Z6KOTTV7.mjs → auth-step-up-VJAFGSW6.mjs} +5 -5
  8. package/dist/build-E7RRQWVF.mjs +616 -0
  9. package/dist/{cancel-ZHVVILOU.mjs → cancel-UMKR274A.mjs} +4 -4
  10. package/dist/capture-TK4NIUYM.mjs +69 -0
  11. package/dist/chunk-25ZPFHLF.mjs +51 -0
  12. package/dist/{chunk-OLZKNUMK.mjs → chunk-3LJG22FS.mjs} +6 -6
  13. package/dist/chunk-454RQ6NR.mjs +10614 -0
  14. package/dist/{chunk-O2TO2J4I.mjs → chunk-6CQMRZ2W.mjs} +2 -2
  15. package/dist/chunk-6KKLF5KG.mjs +32 -0
  16. package/dist/chunk-74QZQSCL.mjs +84 -0
  17. package/dist/chunk-BGM7CH5G.mjs +81 -0
  18. package/dist/chunk-BTNVYAC4.mjs +5557 -0
  19. package/dist/{chunk-I2UP7YTG.mjs → chunk-ECGDRU56.mjs} +1 -1
  20. package/dist/{chunk-UTD34C2H.mjs → chunk-EOOOXVHR.mjs} +2 -4
  21. package/dist/chunk-EYEMPIMP.mjs +74 -0
  22. package/dist/{chunk-3RPR33MO.mjs → chunk-FV273ECI.mjs} +11 -7
  23. package/dist/{chunk-MLE4MVYC.mjs → chunk-GGPN4QYQ.mjs} +2 -2
  24. package/dist/chunk-HQPZQIWW.mjs +1456 -0
  25. package/dist/{chunk-3QUK5BPY.mjs → chunk-I7GEHLDD.mjs} +16 -1
  26. package/dist/chunk-ITDAICBY.mjs +363 -0
  27. package/dist/chunk-JKECUAAV.mjs +2169 -0
  28. package/dist/chunk-JNCEKW2Y.mjs +41 -0
  29. package/dist/chunk-JTYVGORV.mjs +101 -0
  30. package/dist/chunk-KQNZKV6E.mjs +535 -0
  31. package/dist/chunk-LJDIQ4PF.mjs +6463 -0
  32. package/dist/{chunk-2MKZKUVH.mjs → chunk-LLRUOIMU.mjs} +1 -2
  33. package/dist/chunk-MJMRIMR7.mjs +86 -0
  34. package/dist/chunk-N2WZJX5P.mjs +1989 -0
  35. package/dist/chunk-NEB372WD.mjs +1152 -0
  36. package/dist/chunk-OPX7KPOU.mjs +68 -0
  37. package/dist/chunk-PHUBMRAS.mjs +623 -0
  38. package/dist/chunk-PQBALZUH.mjs +1941 -0
  39. package/dist/chunk-PVW5D5AW.mjs +42 -0
  40. package/dist/{chunk-LZEG3FEY.mjs → chunk-QQWYQ2HS.mjs} +7 -7
  41. package/dist/chunk-QVR6V6QP.mjs +46 -0
  42. package/dist/chunk-SGT5IJVW.mjs +28 -0
  43. package/dist/chunk-SZXO6YSX.mjs +502 -0
  44. package/dist/chunk-THATLMEX.mjs +1227 -0
  45. package/dist/chunk-TYR4Q37L.mjs +391 -0
  46. package/dist/{chunk-DMLQBAGC.mjs → chunk-V3R2NYQR.mjs} +1 -1
  47. package/dist/chunk-VHK7WBO3.mjs +189 -0
  48. package/dist/{chunk-TH4XENKH.mjs → chunk-XASDB7YS.mjs} +1 -1
  49. package/dist/{chunk-NYXA6X56.mjs → chunk-XBQ4LO2N.mjs} +31 -11
  50. package/dist/chunk-XF3MGADK.mjs +2946 -0
  51. package/dist/{chunk-5KYR6WJB.mjs → chunk-XQAHREKO.mjs} +17 -1
  52. package/dist/chunk-XZG3DTXV.mjs +696 -0
  53. package/dist/chunk-Y4YGR2NY.mjs +52 -0
  54. package/dist/{chunk-LJEKKFC7.mjs → chunk-YS2JUNSQ.mjs} +19 -11
  55. package/dist/chunk-ZG7NJLXN.mjs +104 -0
  56. package/dist/create-3TGG7B3X.mjs +10 -0
  57. package/dist/credits-MEG7V5DI.mjs +33 -0
  58. package/dist/{deploy-STWZ3OSM.mjs → deploy-CNRORHJ7.mjs} +11 -11
  59. package/dist/dev-RXSBY7U4.mjs +8 -0
  60. package/dist/dev-YLYC6B7R.mjs +401 -0
  61. package/dist/dispatcher.mjs +131 -30
  62. package/dist/{doctor-3RW3OGZJ.mjs → doctor-XRTNG2IT.mjs} +13 -21
  63. package/dist/{domains-FTNK4IKY.mjs → domains-JIHFKDWR.mjs} +6 -6
  64. package/dist/engine-runtime-fetch-QVKJVC3W.mjs +10 -0
  65. package/dist/{env-TNCYUAMT.mjs → env-LJ3DCAA5.mjs} +7 -7
  66. package/dist/env-R72AHBT2.mjs +19 -0
  67. package/dist/explain-32RSXEXC.mjs +251 -0
  68. package/dist/inspect-PYLYN45K.mjs +86 -0
  69. package/dist/{inspect-X3M7WSTL.mjs → inspect-ZOLZHNSZ.mjs} +28 -13
  70. package/dist/introspect-27MMTACH.mjs +9 -0
  71. package/dist/kernel-IC4ZC54Z.mjs +662 -0
  72. package/dist/kernel-plugin.js +6862 -0
  73. package/dist/keys-WKOQ4KQE.mjs +71 -0
  74. package/dist/lines-I6ZB2IOU.mjs +56 -0
  75. package/dist/{link-76Z6V6CJ.mjs → link-TE2FUX74.mjs} +6 -6
  76. package/dist/{lint-DDZ7HUQN.mjs → lint-735IPMC6.mjs} +2 -2
  77. package/dist/{list-2MFOQKKQ.mjs → list-CHYY2P4T.mjs} +7 -11
  78. package/dist/lock-XVR3RRZ6.mjs +57 -0
  79. package/dist/{login-RLMDEWAL.mjs → login-LW7CHZDZ.mjs} +10 -8
  80. package/dist/{logout-Z57X6HPE.mjs → logout-6U6OP5DM.mjs} +2 -2
  81. package/dist/{logs-TNDM4RFE.mjs → logs-HKH4H6PT.mjs} +5 -5
  82. package/dist/logs-SCQC5HNO.mjs +44 -0
  83. package/dist/mcp-VRHQOEGG.mjs +1077 -0
  84. package/dist/{mock-EVER34IX.mjs → mock-5H3CVPHC.mjs} +2 -2
  85. package/dist/outdated-T7QW6F3Z.mjs +58 -0
  86. package/dist/port-24R7ZNP4.mjs +2408 -0
  87. package/dist/{projects-YL4T24VV.mjs → projects-HYITVEXS.mjs} +6 -6
  88. package/dist/publish-DZRR5A5X.mjs +20 -0
  89. package/dist/remix-4HVXRIEB.mjs +125 -0
  90. package/dist/repo-FG3CMZQS.mjs +9 -0
  91. package/dist/report-M7F4ZYP7.mjs +2 -0
  92. package/dist/rollback-H7Q4XYGX.mjs +43 -0
  93. package/dist/{rollback-47VQ4HJK.mjs → rollback-OM64O67F.mjs} +6 -6
  94. package/dist/scene-QTHT3ZPL.mjs +324 -0
  95. package/dist/{status-TWPOMWNA.mjs → status-5D4RR3MM.mjs} +5 -5
  96. package/dist/status-AKI6E3JK.mjs +18 -0
  97. package/dist/test-W6MURUGZ.mjs +233 -0
  98. package/dist/testers-EI32JB5F.mjs +49 -0
  99. package/dist/{unlink-H3BO3DOQ.mjs → unlink-BND47IWW.mjs} +2 -2
  100. package/dist/{update-FLY46XI3.mjs → update-XKKFREKH.mjs} +4 -4
  101. package/dist/{whoami-CQPPARRS.mjs → whoami-WHLGCVRC.mjs} +3 -3
  102. package/dist/workshop-F2T4IDOU.mjs +1703 -0
  103. package/package.json +11 -7
  104. package/templates/storefront-auto/app/products/[slug]/page.tsx +1 -1
  105. package/templates/storefront-auto/package.json +1 -1
  106. package/templates/storefront-bakery/package.json +1 -1
  107. package/templates/storefront-fashion/app/products/[slug]/page.tsx +1 -1
  108. package/templates/storefront-fashion/package.json +1 -1
  109. package/templates/storefront-grocery/package.json +1 -1
  110. package/templates/storefront-pharmacy/app/products/[slug]/page.tsx +1 -1
  111. package/templates/storefront-pharmacy/package.json +1 -1
  112. package/templates/storefront-restaurant/package.json +1 -1
  113. package/templates/storefront-retail/app/products/[slug]/page.tsx +1 -1
  114. package/templates/storefront-retail/package.json +1 -1
  115. package/templates/storefront-services/package.json +1 -1
  116. package/dist/chunk-ARIDYKLO.mjs +0 -6463
  117. package/dist/dev-7O65HY6C.mjs +0 -8
  118. package/dist/explain-DLRUUCNS.mjs +0 -251
  119. package/dist/introspect-RC265ACF.mjs +0 -9
  120. package/dist/repo-UUT3AZ3L.mjs +0 -9
@@ -0,0 +1,662 @@
1
+ #!/usr/bin/env node
2
+ import { kernelProgram, findSites, tunableConsts, tunableLiteral, compileSite, KernelError, metaOf } from './chunk-454RQ6NR.mjs';
3
+ import { ENGINE_RUNTIME_ROUTE, ENGINE_RUNTIME_FILES_ROUTE } from './chunk-QVR6V6QP.mjs';
4
+ import { locateToolchain, engineCacheRoot, validManifest, canonicalBytes } from './chunk-N2WZJX5P.mjs';
5
+ import './chunk-EYEMPIMP.mjs';
6
+ import './chunk-BGM7CH5G.mjs';
7
+ import { isDigest, sha256 } from './chunk-25ZPFHLF.mjs';
8
+ import './chunk-SGT5IJVW.mjs';
9
+ import { parseArgs, flagString, flagBool } from './chunk-XQAHREKO.mjs';
10
+ import { ApiClient } from './chunk-XBQ4LO2N.mjs';
11
+ import { readAuthOrNull } from './chunk-GGPN4QYQ.mjs';
12
+ import { CliError, CLI_ERROR_CODE, info, dim, bold, warn, success, result, isCliError } from './chunk-I7GEHLDD.mjs';
13
+ import { promises, existsSync, readFileSync } from 'fs';
14
+ import path5 from 'path';
15
+ import { spawnSync } from 'child_process';
16
+ import { trainMlp, trainMoe, importMoe, importMlp, kernelIsa, readOnnx, plainNet } from '@cimplify/sdk/game';
17
+ import ts from 'typescript';
18
+ import { randomBytes, createPublicKey, verify } from 'crypto';
19
+
20
+ var TUNER_ENV = "CIMPLIFY_KERNEL_TUNER";
21
+ var OBJECTIVE_NAME = "objective";
22
+ var TUNER_CRATE = path5.join("crates", "engine_kernel_grad", "Cargo.toml");
23
+ var MAX_F32_DIGITS = 9;
24
+ var DIFF_CONTEXT = 2;
25
+ var TUNER_BUFFER = 1 << 28;
26
+ function fileOf(source) {
27
+ const bytes = Buffer.from(source.text, "utf8");
28
+ return { path: source.path, bytes, digest: "", size: bytes.length };
29
+ }
30
+ function pickSite(sites, name, what) {
31
+ const kernels = sites.filter((s) => s.kind === "kernel");
32
+ if (name !== null) {
33
+ const hit = kernels.find((s) => s.name === name);
34
+ if (hit === void 0) throw new Error(`no kernel named ${name} in the ${what} file; kernels there: ${kernels.map((s) => s.name).join(", ") || "none"}`);
35
+ return hit;
36
+ }
37
+ if (kernels.length === 1) return kernels[0];
38
+ throw new Error(`the ${what} file declares ${kernels.length} kernels; name one (${kernels.map((s) => s.name).join(", ") || "none"})`);
39
+ }
40
+ function literalValue(node) {
41
+ const lit = tunableLiteral(node);
42
+ if (lit === null) throw new Error("not a number literal");
43
+ if (ts.isNumericLiteral(lit)) return Number(lit.text);
44
+ const inner = lit.operand;
45
+ let e = inner;
46
+ while (ts.isParenthesizedExpression(e)) e = e.expression;
47
+ const value = Number(e.text);
48
+ return lit.operator === ts.SyntaxKind.MinusToken ? -value : value;
49
+ }
50
+ function compileOne(site, checker, tunable) {
51
+ try {
52
+ return compileSite(site, checker, tunable);
53
+ } catch (error) {
54
+ if (error instanceof KernelError) {
55
+ const sf = error.node.getSourceFile();
56
+ const lc = sf.getLineAndCharacterOfPosition(error.node.getStart(sf));
57
+ throw new Error(`${site.path}:${lc.line + 1}:${lc.character + 1}: ${error.message}; ${error.fix}`);
58
+ }
59
+ throw error;
60
+ }
61
+ }
62
+ function compileForTuning(kernel, kernelName, objective, objectiveName, params, toolchain) {
63
+ if (params.length === 0) throw new Error("name at least one constant to tune");
64
+ const { program, checker, toSource } = kernelProgram([fileOf(kernel), fileOf(objective)], "canvas", toolchain);
65
+ let kernelSf = null;
66
+ let objectiveSf = null;
67
+ for (const sf of program.getSourceFiles()) {
68
+ const p = toSource.get(sf.fileName);
69
+ if (p === kernel.path) kernelSf = sf;
70
+ if (p === objective.path) objectiveSf = sf;
71
+ }
72
+ if (kernelSf === null || objectiveSf === null) throw new Error("the kernel or objective file is not part of the program");
73
+ const site = pickSite(findSites(kernelSf, kernel.path), kernelName, "kernel");
74
+ const objectiveSites = findSites(objectiveSf, objective.path);
75
+ const objectiveSite = pickSite(objectiveSites, objectiveName ?? (objectiveSites.some((s) => s.name === OBJECTIVE_NAME) ? OBJECTIVE_NAME : null), "objective");
76
+ const consts = tunableConsts(kernelSf);
77
+ const values = params.map((name) => {
78
+ const decl = consts.get(name);
79
+ if (decl?.initializer === void 0 || tunableLiteral(decl.initializer) === null) throw new Error(`${name} is not a top-level const with a number literal in ${kernel.path}`);
80
+ return { name, value: Math.fround(literalValue(decl.initializer)) };
81
+ });
82
+ return {
83
+ tunable: compileOne(site, checker, new Set(params)),
84
+ shipped: compileOne(site, checker),
85
+ objective: compileOne(objectiveSite, checker),
86
+ params: values
87
+ };
88
+ }
89
+ function specOf(kernel) {
90
+ const meta = metaOf(kernel, 0);
91
+ return {
92
+ program: Buffer.from(kernel.bytes).toString("base64"),
93
+ slots: meta.slots.map((s) => ({ field: s.field, component: s.component, kind: s.kind })),
94
+ args: meta.args.map((a) => ({ name: a.name, kind: a.kind, width: a.width }))
95
+ };
96
+ }
97
+ function tuneJob(compiled, inputs, options, bounds2 = /* @__PURE__ */ new Map()) {
98
+ return {
99
+ kernel: specOf(compiled.tunable),
100
+ objective: specOf(compiled.objective),
101
+ params: compiled.params.map((p) => ({ ...p, ...bounds2.get(p.name) ?? {} })),
102
+ inputs,
103
+ options
104
+ };
105
+ }
106
+ function shortestF32(x) {
107
+ const f = Math.fround(x);
108
+ if (!Number.isFinite(f)) throw new Error(`${x} is not a finite f32`);
109
+ for (let p = 1; p <= MAX_F32_DIGITS; p++) {
110
+ const candidate = Number(f.toPrecision(p));
111
+ if (Math.fround(candidate) === f) return String(candidate);
112
+ }
113
+ return String(f);
114
+ }
115
+ function writeBack(source, values) {
116
+ const sf = ts.createSourceFile(source.path, source.text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
117
+ const consts = tunableConsts(sf);
118
+ const replacements = [];
119
+ for (const [name, value] of values) {
120
+ const decl = consts.get(name);
121
+ const lit = decl?.initializer === void 0 ? null : tunableLiteral(decl.initializer);
122
+ if (lit === null) throw new Error(`${name} is not a top-level const with a number literal in ${source.path}`);
123
+ const start = lit.getStart(sf);
124
+ const before = source.text.slice(start, lit.end);
125
+ const after = shortestF32(value);
126
+ const line = sf.getLineAndCharacterOfPosition(start).line + 1;
127
+ replacements.push({ start, end: lit.end, text: after, edit: { name, line, before, after } });
128
+ }
129
+ replacements.sort((a, b) => b.start - a.start);
130
+ let text = source.text;
131
+ for (const r of replacements) text = text.slice(0, r.start) + r.text + text.slice(r.end);
132
+ return { text, edits: replacements.map((r) => r.edit).sort((a, b) => a.line - b.line) };
133
+ }
134
+ function unifiedDiff(file, before, after) {
135
+ const a = before.split("\n");
136
+ const b = after.split("\n");
137
+ if (a.length !== b.length) throw new Error("constant edits never add or remove lines");
138
+ const changed = [];
139
+ for (let k = 0; k < a.length; k++) if (a[k] !== b[k]) changed.push(k);
140
+ if (changed.length === 0) return "";
141
+ const hunks = [];
142
+ for (const k of changed) {
143
+ const lo = Math.max(0, k - DIFF_CONTEXT);
144
+ const hi = Math.min(a.length - 1, k + DIFF_CONTEXT);
145
+ const last = hunks[hunks.length - 1];
146
+ if (last !== void 0 && lo <= last[1] + 1) last[1] = hi;
147
+ else hunks.push([lo, hi]);
148
+ }
149
+ const out = [`--- a/${file}`, `+++ b/${file}`];
150
+ for (const [lo, hi] of hunks) {
151
+ const n = hi - lo + 1;
152
+ out.push(`@@ -${lo + 1},${n} +${lo + 1},${n} @@`);
153
+ for (let k = lo; k <= hi; k++) {
154
+ if (a[k] === b[k]) out.push(` ${a[k]}`);
155
+ else out.push(`-${a[k]}`, `+${b[k]}`);
156
+ }
157
+ }
158
+ return `${out.join("\n")}
159
+ `;
160
+ }
161
+ function words(bytes) {
162
+ return new Uint32Array(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength));
163
+ }
164
+ function sameButConstants(before, after) {
165
+ const x = words(before);
166
+ const y = words(after);
167
+ if (x.length !== y.length) return false;
168
+ for (let k = 0; k < kernelIsa.PROGRAM_HEADER_WORDS; k++) if (x[k] !== y[k]) return false;
169
+ const start = kernelIsa.PROGRAM_HEADER_WORDS + x[kernelIsa.H_SLOTS] + x[kernelIsa.H_ARGS];
170
+ const end = start + x[kernelIsa.H_CONSTANTS];
171
+ for (let k = 0; k < x.length; k++) if ((k < start || k >= end) && x[k] !== y[k]) return false;
172
+ return true;
173
+ }
174
+ function upward(from, relative2) {
175
+ let dir = path5.resolve(from);
176
+ for (; ; ) {
177
+ if (existsSync(path5.join(dir, relative2))) return dir;
178
+ const parent = path5.dirname(dir);
179
+ if (parent === dir) return null;
180
+ dir = parent;
181
+ }
182
+ }
183
+ function tunerCommand(explicit, cwd) {
184
+ const chosen = explicit ?? process.env[TUNER_ENV] ?? null;
185
+ if (chosen !== null && chosen !== "") return [chosen];
186
+ const root = upward(cwd, TUNER_CRATE);
187
+ if (root === null) return null;
188
+ return ["cargo", "run", "--release", "--quiet", "--manifest-path", path5.join(root, "Cargo.toml"), "-p", "engine_kernel_grad", "--bin", "kernel-tune", "--"];
189
+ }
190
+ function runTuner(command, job) {
191
+ const [program, ...rest] = command;
192
+ const child = spawnSync(program, [...rest, "-"], { input: JSON.stringify(job), encoding: "utf8", maxBuffer: TUNER_BUFFER });
193
+ if (child.error !== void 0) throw new Error(`the tuner did not start: ${child.error.message}`);
194
+ if (child.status !== 0) throw new Error(`the tuner failed: ${child.stderr.trim() || `exit ${child.status}`}`);
195
+ return JSON.parse(child.stdout);
196
+ }
197
+ var KERNEL_TUNE = "kernel-tune";
198
+ var TOOLS_DIR = "tools";
199
+ var TOOL_INDEX_PATH = `${TOOLS_DIR}/tools.json`;
200
+ var TOOL_SIGNATURE_CONTEXT = "cimplify.tools.v1\n";
201
+ var TOOL_PLATFORMS = ["darwin-arm64", "darwin-x64", "linux-x64", "linux-arm64", "windows-x64"];
202
+ var TRUSTED_TOOL_KEYS = {
203
+ "86fa65e460043f11": "xsFQOcIapIbz1I02WRMczcaWAtPhcGAGDVtf6NlKIL4="
204
+ };
205
+ var WINDOWS_PLATFORM = "windows-x64";
206
+ var EXE = ".exe";
207
+ var ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
208
+ var RAW_PUBLIC_KEY_BYTES = 32;
209
+ var PLATFORM_OF = {
210
+ "darwin-arm64": "darwin-arm64",
211
+ "darwin-x64": "darwin-x64",
212
+ "linux-x64": "linux-x64",
213
+ "linux-arm64": "linux-arm64",
214
+ "win32-x64": "windows-x64"
215
+ };
216
+ function toolPlatform(platform = process.platform, arch = process.arch) {
217
+ return PLATFORM_OF[`${platform}-${arch}`] ?? null;
218
+ }
219
+ function toolPath(platform) {
220
+ return `${TOOLS_DIR}/${platform}/${KERNEL_TUNE}${platform === WINDOWS_PLATFORM ? EXE : ""}`;
221
+ }
222
+ function toolMessage(index) {
223
+ const entries = [...index.entries].sort((a, b) => a.platform < b.platform ? -1 : a.platform > b.platform ? 1 : 0);
224
+ const lines = [`tool=${index.tool}`, `version=${index.version}`, ...entries.map((e) => `${e.platform} ${e.path} ${e.sha256} ${e.bytes}`)];
225
+ return Buffer.concat([Buffer.from(TOOL_SIGNATURE_CONTEXT, "ascii"), Buffer.from(`${lines.join("\n")}
226
+ `, "utf8")]);
227
+ }
228
+ function verifyRawEd25519(rawPublicKey, message, signature) {
229
+ if (rawPublicKey.length !== RAW_PUBLIC_KEY_BYTES) return false;
230
+ try {
231
+ const key = createPublicKey({ key: Buffer.concat([ED25519_SPKI_PREFIX, rawPublicKey]), format: "der", type: "spki" });
232
+ return verify(null, message, key, signature);
233
+ } catch {
234
+ return false;
235
+ }
236
+ }
237
+ function verifyToolIndex(index, keys = TRUSTED_TOOL_KEYS) {
238
+ const raw = keys[index.keyId];
239
+ if (raw === void 0) return false;
240
+ return verifyRawEd25519(Buffer.from(raw, "base64"), toolMessage(index), Buffer.from(index.signature, "base64"));
241
+ }
242
+ function validToolIndex(value) {
243
+ const v = value;
244
+ return typeof v === "object" && v !== null && typeof v.tool === "string" && typeof v.version === "string" && typeof v.keyId === "string" && typeof v.signature === "string" && Array.isArray(v.entries) && v.entries.every((e) => TOOL_PLATFORMS.includes(e.platform) && e.path === toolPath(e.platform) && isDigest(e.sha256) && Number.isInteger(e.bytes) && e.bytes > 0);
245
+ }
246
+
247
+ // src/interactives/kernels/tuner.ts
248
+ var EXECUTABLE_MODE = 493;
249
+ var MAX_INDEX_BYTES = 64 * 1024;
250
+ var DOWNLOAD_TIMEOUT_MS = 5 * 60 * 1e3;
251
+ var TEMP_SUFFIX_BYTES = 6;
252
+ var POINTER_SUFFIX = ".json";
253
+ function binaryPath(root, platform, digest) {
254
+ return path5.join(root, TOOLS_DIR, digest, path5.basename(toolPath(platform)));
255
+ }
256
+ function pointerPath(root, platform) {
257
+ return path5.join(root, TOOLS_DIR, `${KERNEL_TUNE}-${platform}${POINTER_SUFFIX}`);
258
+ }
259
+ function intact(file, digest) {
260
+ return existsSync(file) && sha256(new Uint8Array(readFileSync(file))) === digest;
261
+ }
262
+ async function writeAtomically(file, bytes, mode) {
263
+ await promises.mkdir(path5.dirname(file), { recursive: true });
264
+ const staged = path5.join(path5.dirname(file), `.tmp-${process.pid}-${randomBytes(TEMP_SUFFIX_BYTES).toString("hex")}`);
265
+ await promises.writeFile(staged, bytes, mode === void 0 ? void 0 : { mode });
266
+ await promises.rename(staged, file);
267
+ }
268
+ function cachedTuner(root, platform) {
269
+ const pointer = pointerPath(root, platform);
270
+ if (!existsSync(pointer)) return null;
271
+ let parsed;
272
+ try {
273
+ parsed = JSON.parse(readFileSync(pointer, "utf8"));
274
+ } catch {
275
+ return null;
276
+ }
277
+ if (typeof parsed.sha256 !== "string" || !isDigest(parsed.sha256)) return null;
278
+ const file = binaryPath(root, platform, parsed.sha256);
279
+ return intact(file, parsed.sha256) ? { command: [file], source: "cached", version: parsed.version } : null;
280
+ }
281
+ function entryAt(manifest, wanted) {
282
+ return manifest.files.find((f) => f.path === wanted) ?? null;
283
+ }
284
+ async function fetchTuner(client, root, platform, keys) {
285
+ let manifest;
286
+ try {
287
+ manifest = await client.get(ENGINE_RUNTIME_ROUTE);
288
+ } catch (error) {
289
+ if (isCliError(error) && error.code === CLI_ERROR_CODE.NOT_FOUND) return null;
290
+ throw error;
291
+ }
292
+ if (!validManifest(manifest)) throw new Error("the registry returned an engine runtime manifest this CLI cannot use");
293
+ const indexFile = entryAt(manifest, TOOL_INDEX_PATH);
294
+ const binary = entryAt(manifest, toolPath(platform));
295
+ if (indexFile === null || binary === null) return null;
296
+ if (indexFile.bytes > MAX_INDEX_BYTES) throw new Error("the published tool index is too large");
297
+ const indexBytes = canonicalBytes(indexFile, await client.getBytes(`${ENGINE_RUNTIME_FILES_ROUTE}/${indexFile.sha256}`, { timeoutMs: DOWNLOAD_TIMEOUT_MS }));
298
+ const index = JSON.parse(Buffer.from(indexBytes).toString("utf8"));
299
+ if (!validToolIndex(index)) throw new Error("the published tool index is malformed");
300
+ if (!verifyToolIndex(index, keys)) throw new Error(`the published ${KERNEL_TUNE} index is not signed by a key this CLI trusts`);
301
+ const signed = index.entries.find((e) => e.platform === platform);
302
+ if (index.tool !== KERNEL_TUNE || signed === void 0 || signed.sha256 !== binary.sha256 || signed.bytes !== binary.bytes) {
303
+ throw new Error(`the published ${KERNEL_TUNE} for ${platform} does not match its signed index`);
304
+ }
305
+ const file = binaryPath(root, platform, binary.sha256);
306
+ let fetched = false;
307
+ if (!intact(file, binary.sha256)) {
308
+ const bytes = canonicalBytes(binary, await client.getBytes(`${ENGINE_RUNTIME_FILES_ROUTE}/${binary.sha256}`, { timeoutMs: DOWNLOAD_TIMEOUT_MS }));
309
+ await writeAtomically(file, bytes, EXECUTABLE_MODE);
310
+ fetched = true;
311
+ }
312
+ await writeAtomically(pointerPath(root, platform), `${JSON.stringify({ sha256: binary.sha256, version: index.version })}
313
+ `);
314
+ return { command: [file], source: fetched ? "fetched" : "cached", version: index.version };
315
+ }
316
+ async function signedInClient() {
317
+ const auth = await readAuthOrNull();
318
+ return auth === null ? null : ApiClient.fromAuth(auth);
319
+ }
320
+ async function resolveTuner(options) {
321
+ if (options.explicit !== null && options.explicit !== "") return { command: [options.explicit], source: "flag", version: null };
322
+ const fromEnv = process.env[TUNER_ENV];
323
+ if (fromEnv !== void 0 && fromEnv !== "") return { command: [fromEnv], source: "env", version: null };
324
+ const monorepo = tunerCommand(null, options.cwd);
325
+ if (monorepo !== null) return { command: monorepo, source: "monorepo", version: null };
326
+ const platform = options.platform === void 0 ? toolPlatform() : options.platform;
327
+ if (platform === null) return null;
328
+ const root = options.root ?? engineCacheRoot();
329
+ const client = options.client === void 0 ? await signedInClient() : options.client;
330
+ if (client !== null) {
331
+ try {
332
+ const fetched = await fetchTuner(client, root, platform, options.keys ?? TRUSTED_TOOL_KEYS);
333
+ if (fetched !== null) return fetched;
334
+ } catch (error) {
335
+ const cached = cachedTuner(root, platform);
336
+ if (cached !== null) return cached;
337
+ throw error;
338
+ }
339
+ }
340
+ return cachedTuner(root, platform);
341
+ }
342
+ var IMPORT_USAGE = "cimplify kernel import <model.onnx|model.json> [expert.onnx ...] [--router <router.onnx|router.json>] [--samples <samples.json>] [--max-drift <d>] [--memory <n>] [--out <net.mlp>] (ONNX graphs of Gemm, MatMul, Add, Relu, Tanh and Clip, or plain JSON { layers: [{ weights, bias, activation }] } or { router, experts }; with --router every model is an expert)";
343
+ var OUT_SUFFIX = ".mlp";
344
+ var WORD_BYTES = 4;
345
+ var ONNX_SUFFIX = ".onnx";
346
+ function invalid(message) {
347
+ throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, message, { remediation: `usage: ${IMPORT_USAGE}` });
348
+ }
349
+ async function bytesOf(file) {
350
+ try {
351
+ return await promises.readFile(path5.resolve(file));
352
+ } catch {
353
+ invalid(`cannot read ${file}`);
354
+ }
355
+ }
356
+ function layersOf(json, what) {
357
+ const layers = json?.layers;
358
+ if (!Array.isArray(layers)) invalid(`${what} needs a "layers" list of { weights, bias, activation }`);
359
+ return plainNet(layers);
360
+ }
361
+ async function netOf(file) {
362
+ const bytes = await bytesOf(file);
363
+ if (file.endsWith(ONNX_SUFFIX)) return readOnnx(new Uint8Array(bytes));
364
+ let json;
365
+ try {
366
+ json = JSON.parse(bytes.toString("utf8"));
367
+ } catch {
368
+ invalid(`${file} is neither an .onnx model nor readable JSON`);
369
+ }
370
+ const mixture = json;
371
+ if (mixture.router !== void 0) {
372
+ if (!Array.isArray(mixture.experts)) invalid(`${file} names a router but no "experts" list`);
373
+ return { router: layersOf(mixture.router, `${file} router`), experts: mixture.experts.map((e, k) => layersOf(e, `${file} expert ${k}`)) };
374
+ }
375
+ return layersOf(json, file);
376
+ }
377
+ function single(net, file) {
378
+ if ("router" in net) invalid(`${file} is a whole mixture; pass it alone, without --router`);
379
+ return net;
380
+ }
381
+ async function samplesOf(file) {
382
+ if (file === void 0) return void 0;
383
+ let json;
384
+ try {
385
+ json = JSON.parse((await bytesOf(file)).toString("utf8"));
386
+ } catch {
387
+ invalid(`${file} is not readable JSON`);
388
+ }
389
+ const rows = json?.inputs;
390
+ if (!Array.isArray(rows) || rows.length === 0 || rows.some((r) => !Array.isArray(r) || r.some((v) => typeof v !== "number"))) invalid(`${file} needs an "inputs" list of rows of numbers`);
391
+ return Float32Array.from(rows.flat());
392
+ }
393
+ function number(raw, name) {
394
+ if (raw === void 0) return void 0;
395
+ const value = Number(raw);
396
+ if (!Number.isFinite(value) || value < 0) invalid(`--${name} must be a non-negative number`);
397
+ return value;
398
+ }
399
+ async function importNet(file, argv) {
400
+ if (file === void 0 || file.startsWith("--")) invalid(`usage: ${IMPORT_USAGE}`);
401
+ const args = parseArgs(argv);
402
+ const files = [file, ...args.positional];
403
+ const router = flagString(args, "router");
404
+ const maxDrift = number(flagString(args, "max-drift"), "max-drift");
405
+ const memory = number(flagString(args, "memory"), "memory");
406
+ const samples = await samplesOf(flagString(args, "samples"));
407
+ const options = { ...samples === void 0 ? {} : { samples }, ...maxDrift === void 0 ? {} : { maxDrift }, ...memory === void 0 ? {} : { memory: Math.round(memory) } };
408
+ let imported;
409
+ try {
410
+ const first = await netOf(file);
411
+ if (router !== void 0) {
412
+ const experts = await Promise.all(files.map(async (f) => single(await netOf(f), f)));
413
+ imported = importMoe(single(await netOf(router), router), experts, options);
414
+ } else if ("router" in first) {
415
+ if (files.length > 1) invalid("a mixture file stands alone");
416
+ imported = importMoe(first.router, first.experts, options);
417
+ } else {
418
+ if (files.length > 1) invalid("several models make a mixture only with --router");
419
+ imported = importMlp(first, options);
420
+ }
421
+ } catch (error) {
422
+ if (error instanceof CliError) throw error;
423
+ invalid(error.message);
424
+ }
425
+ const out = path5.resolve(flagString(args, "out") ?? `${file.replace(/\.(onnx|json)$/, "")}${OUT_SUFFIX}`);
426
+ const bytes = new Uint8Array(imported.blob.length * WORD_BYTES);
427
+ const view = new DataView(bytes.buffer);
428
+ imported.blob.forEach((w, k) => view.setUint32(k * WORD_BYTES, w, true));
429
+ await promises.writeFile(out, bytes);
430
+ for (const l of imported.layers) info(dim(`net ${l.net} layer ${l.layer} ${l.inputs}->${l.outputs} ${l.activation === 1 ? "relu" : "linear"}: weight scale ${l.weightScale.toPrecision(4)}, activation scale ${l.activationScale.toPrecision(4)}, requantize ${l.multiplier} >> ${l.shift}`));
431
+ const d = imported.drift;
432
+ info(`drift over ${d.samples} samples: largest error ${d.maxError.toPrecision(4)} on outputs up to ${d.peak.toPrecision(4)} (${(d.relative * 100).toFixed(2)}%, bound ${(d.bound * 100).toFixed(2)}%)`);
433
+ success(`wrote ${path5.relative(process.cwd(), out)}; load it with loadMlp(net, url) or setMlp(net, words)`);
434
+ result({ out, words: imported.blob.length, inputScale: imported.inputScale, layers: imported.layers, drift: imported.drift, memory: imported.memory });
435
+ }
436
+ var TRAIN_USAGE = "cimplify kernel train <samples.json> --widths <in,hidden,...,out> [--epochs <n>] [--batch <n>] [--rate <r>] [--momentum <m>] [--seed <n>] [--experts <n> [--router <hidden,...>] [--balance <b>]] [--out <net.mlp>] (with --experts the widths are each expert's and the file is a mixture of experts; samples: inputs, targets and optional memory.previous and memory.next rows; the widths count the memory in and out)";
437
+ var OUT_SUFFIX2 = ".mlp";
438
+ var WORD_BYTES2 = 4;
439
+ function memoryWidth(json) {
440
+ const memory = json?.memory;
441
+ if (memory === void 0) return 0;
442
+ const previous = memory.previous;
443
+ if (!Array.isArray(previous) || !Array.isArray(previous[0])) invalid2('"memory" needs "previous" and "next" arrays with one row of numbers per sample');
444
+ return previous[0].length;
445
+ }
446
+ function invalid2(message) {
447
+ throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, message, { remediation: `usage: ${TRAIN_USAGE}` });
448
+ }
449
+ function count(raw, name) {
450
+ if (raw === void 0) return void 0;
451
+ const value = Number(raw);
452
+ if (!Number.isFinite(value) || value < 0) invalid2(`--${name} must be a non-negative number`);
453
+ return value;
454
+ }
455
+ function samplesOf2(json, inputs, outputs) {
456
+ const rows = (key) => {
457
+ const value = json?.[key];
458
+ if (!Array.isArray(value)) invalid2(`the samples file needs an "${key}" array with one row of numbers per sample`);
459
+ return value;
460
+ };
461
+ const xs = rows("inputs");
462
+ const ys = rows("targets");
463
+ if (xs.length !== ys.length || xs.length === 0) invalid2("inputs and targets need the same number of rows, at least one");
464
+ const flat = (list2, width2, key) => {
465
+ const out = new Float32Array(list2.length * width2);
466
+ list2.forEach((row, n) => {
467
+ if (!Array.isArray(row) || row.length !== width2 || row.some((v) => typeof v !== "number")) invalid2(`${key} row ${n} needs ${width2} numbers`);
468
+ out.set(row, n * width2);
469
+ });
470
+ return out;
471
+ };
472
+ const width = memoryWidth(json);
473
+ const base = { inputs: flat(xs, inputs - width, "inputs"), targets: flat(ys, outputs - width, "targets"), samples: xs.length };
474
+ if (width === 0) return base;
475
+ const memory = json.memory;
476
+ const list = (key) => {
477
+ const value = memory[key];
478
+ if (!Array.isArray(value) || value.length !== xs.length) invalid2(`memory.${key} needs one row per sample`);
479
+ return value;
480
+ };
481
+ return { ...base, memory: { width, previous: flat(list("previous"), width, "memory.previous"), next: flat(list("next"), width, "memory.next") } };
482
+ }
483
+ async function trainNet(file, argv) {
484
+ if (file === void 0 || file.startsWith("--")) invalid2(`usage: ${TRAIN_USAGE}`);
485
+ const args = parseArgs(argv);
486
+ const widthsFlag = flagString(args, "widths");
487
+ if (widthsFlag === void 0) invalid2("--widths is required, for example --widths 32,64,64,8");
488
+ const widths = widthsFlag.split(",").map((w) => Number(w.trim()));
489
+ if (widths.length < 2 || widths.some((w) => !Number.isInteger(w) || w < 1)) invalid2("--widths lists whole numbers: inputs, hidden layers, outputs");
490
+ let json;
491
+ try {
492
+ json = JSON.parse(await promises.readFile(path5.resolve(file), "utf8"));
493
+ } catch {
494
+ invalid2(`${file} is not a readable JSON file`);
495
+ }
496
+ const data = samplesOf2(json, widths[0], widths[widths.length - 1]);
497
+ const epochs = count(flagString(args, "epochs"), "epochs");
498
+ const batch = count(flagString(args, "batch"), "batch");
499
+ const learningRate = count(flagString(args, "rate"), "rate");
500
+ const momentum = count(flagString(args, "momentum"), "momentum");
501
+ const seed = count(flagString(args, "seed"), "seed");
502
+ const experts = count(flagString(args, "experts"), "experts");
503
+ const balance = count(flagString(args, "balance"), "balance");
504
+ const routerFlag = flagString(args, "router");
505
+ const router = routerFlag === void 0 || routerFlag === "" ? [] : routerFlag.split(",").map((w) => Number(w.trim()));
506
+ if (router.some((w) => !Number.isInteger(w) || w < 1)) invalid2("--router lists whole hidden widths, for example --router 16");
507
+ const started = performance.now();
508
+ const common = {
509
+ ...epochs === void 0 ? {} : { epochs: Math.round(epochs) },
510
+ ...batch === void 0 ? {} : { batch: Math.round(batch) },
511
+ ...learningRate === void 0 ? {} : { learningRate },
512
+ ...momentum === void 0 ? {} : { momentum },
513
+ ...seed === void 0 ? {} : { seed: Math.round(seed) }
514
+ };
515
+ let trained;
516
+ try {
517
+ trained = experts === void 0 ? trainMlp(data, { widths, ...common }) : trainMoe(data, { experts: Math.round(experts), router, expert: widths.slice(1, -1), ...balance === void 0 ? {} : { balance }, ...common });
518
+ } catch (error) {
519
+ invalid2(error.message);
520
+ }
521
+ const seconds = (performance.now() - started) / 1e3;
522
+ const out = path5.resolve(flagString(args, "out") ?? `${file.replace(/\.json$/, "")}${OUT_SUFFIX2}`);
523
+ const bytes = new Uint8Array(trained.blob.length * WORD_BYTES2);
524
+ const view = new DataView(bytes.buffer);
525
+ trained.blob.forEach((w, k) => view.setUint32(k * WORD_BYTES2, w, true));
526
+ await promises.writeFile(out, bytes);
527
+ const loss = Array.from(trained.loss);
528
+ info(dim(`loss by epoch ${loss.map((l) => l.toPrecision(4)).join(" ")}`));
529
+ info(`trained ${widths.join("-")} on ${data.samples} samples in ${seconds.toFixed(2)} s; error of the shipped network ${trained.blobLoss.toPrecision(4)}`);
530
+ success(`wrote ${path5.relative(process.cwd(), out)}; load it with loadMlp(net, url) or setMlp(net, words)`);
531
+ const usage = trained.usage === void 0 ? null : Array.from(trained.usage);
532
+ if (usage !== null) info(dim(`rows per expert ${usage.join(" ")}`));
533
+ result({ out, widths, samples: data.samples, seconds, loss, shippedLoss: trained.blobLoss, words: trained.blob.length, ...usage === null ? {} : { experts: usage.length, usage } });
534
+ }
535
+
536
+ // src/interactives/commands/kernel.ts
537
+ var USAGE = `cimplify kernel tune <file>[#kernel] --objective <file>[#kernel] --params <A,B> --inputs <recording.json> [--iterations <n>] [--rate <r>] [--band <b>] [--logistic] [--range <A=lo:hi>] [--diff <path>] [--apply] [--tuner <path>]
538
+ ${TRAIN_USAGE}
539
+ ${IMPORT_USAGE}`;
540
+ var TUNE = "tune";
541
+ var TRAIN = "train";
542
+ var IMPORT = "import";
543
+ var DIFF_SUFFIX = ".tune.diff";
544
+ function split(spec) {
545
+ const hash = spec.lastIndexOf("#");
546
+ return hash < 0 ? { file: spec, name: null } : { file: spec.slice(0, hash), name: spec.slice(hash + 1) };
547
+ }
548
+ function numberFlag(args, name) {
549
+ const raw = flagString(args, name);
550
+ if (raw === void 0) return void 0;
551
+ const value = Number(raw);
552
+ if (!Number.isFinite(value) || value < 0) throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, `--${name} must be a non-negative number`);
553
+ return value;
554
+ }
555
+ function bounds(raw) {
556
+ const out = /* @__PURE__ */ new Map();
557
+ if (raw === void 0) return out;
558
+ for (const part of raw.split(",")) {
559
+ const match = /^([A-Za-z_$][\w$]*)=(-?[\d.eE+-]*):(-?[\d.eE+-]*)$/.exec(part.trim());
560
+ if (match === null) throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, `--range ${part} is not NAME=lo:hi`);
561
+ out.set(match[1], { ...match[2] === "" ? {} : { min: Number(match[2]) }, ...match[3] === "" ? {} : { max: Number(match[3]) } });
562
+ }
563
+ return out;
564
+ }
565
+ async function read(file) {
566
+ try {
567
+ return await promises.readFile(file, "utf8");
568
+ } catch {
569
+ throw new CliError(CLI_ERROR_CODE.NOT_FOUND, `cannot read ${file}`);
570
+ }
571
+ }
572
+ function relative(root, file) {
573
+ return path5.relative(root, file).split(path5.sep).join("/");
574
+ }
575
+ async function run(argv) {
576
+ const [action, target, ...rest] = argv;
577
+ if (action === TRAIN) return trainNet(target, rest);
578
+ if (action === IMPORT) return importNet(target, rest);
579
+ if (action !== TUNE || target === void 0 || target.startsWith("--")) throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, `usage: ${USAGE}`);
580
+ const args = parseArgs(rest);
581
+ const objectiveFlag = flagString(args, "objective");
582
+ const paramsFlag = flagString(args, "params");
583
+ const inputsFlag = flagString(args, "inputs");
584
+ if (objectiveFlag === void 0 || paramsFlag === void 0 || inputsFlag === void 0) throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, `usage: ${USAGE}`);
585
+ const cwd = process.cwd();
586
+ const kernelSpec = split(target);
587
+ const objectiveSpec = split(objectiveFlag);
588
+ const kernelFile = path5.resolve(kernelSpec.file);
589
+ const objectiveFile = path5.resolve(objectiveSpec.file);
590
+ const params = paramsFlag.split(",").map((p) => p.trim()).filter((p) => p !== "");
591
+ const iterations = numberFlag(args, "iterations");
592
+ const rate = numberFlag(args, "rate");
593
+ const band = numberFlag(args, "band");
594
+ const options = {
595
+ ...iterations === void 0 ? {} : { iterations: Math.round(iterations) },
596
+ ...rate === void 0 ? {} : { rate },
597
+ ...band === void 0 ? {} : { band },
598
+ ...flagBool(args, "logistic") ? { logistic: true } : {}
599
+ };
600
+ const kernelText = await read(kernelFile);
601
+ const objectiveText = await read(objectiveFile);
602
+ let inputs;
603
+ try {
604
+ inputs = JSON.parse(await read(path5.resolve(inputsFlag)));
605
+ } catch (error) {
606
+ if (error instanceof CliError) throw error;
607
+ throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, `${inputsFlag} is not JSON`);
608
+ }
609
+ const toolchain = locateToolchain(cwd);
610
+ if (toolchain === null) throw new CliError(CLI_ERROR_CODE.NOT_FOUND, "the kernel compiler toolchain is not installed", { remediation: "run cimplify build once in this project" });
611
+ const root = path5.dirname(kernelFile) === path5.dirname(objectiveFile) ? path5.dirname(kernelFile) : cwd;
612
+ const kernelSource = { path: relative(root, kernelFile), text: kernelText };
613
+ const objectiveSource = { path: relative(root, objectiveFile), text: objectiveText };
614
+ let compiled;
615
+ try {
616
+ compiled = compileForTuning(kernelSource, kernelSpec.name, objectiveSource, objectiveSpec.name, params, toolchain);
617
+ } catch (error) {
618
+ throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, error.message);
619
+ }
620
+ let resolved;
621
+ try {
622
+ resolved = await resolveTuner({ explicit: flagString(args, "tuner") ?? null, cwd });
623
+ } catch (error) {
624
+ throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, `the kernel tuner could not be fetched: ${error.message}`, { remediation: "pass --tuner <path to kernel-tune> or set CIMPLIFY_KERNEL_TUNER" });
625
+ }
626
+ if (resolved === null) throw new CliError(CLI_ERROR_CODE.NOT_FOUND, "the kernel tuner was not found", { remediation: "sign in with cimplify login so the CLI can fetch it, or pass --tuner <path to kernel-tune>, or set CIMPLIFY_KERNEL_TUNER" });
627
+ if (resolved.source === "fetched") info(dim(`Downloaded ${KERNEL_TUNE} ${resolved.version ?? ""}`));
628
+ const command = resolved.command;
629
+ info(dim(`tuning ${params.join(", ")} of ${compiled.tunable.site.name} against ${compiled.objective.site.name}`));
630
+ let report;
631
+ try {
632
+ report = runTuner(command, tuneJob(compiled, inputs, options, bounds(flagString(args, "range"))));
633
+ } catch (error) {
634
+ throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, error.message);
635
+ }
636
+ for (const p of report.params) info(`${bold(p.name.padEnd(20))} ${String(p.before).padStart(12)} -> ${String(p.after).padEnd(12)} ${dim(`d loss / d ${p.name} at start ${p.gradient.toExponential(3)}`)}`);
637
+ info(`loss (shipping kernel) ${report.exact.before.toExponential(4)} -> ${report.exact.after.toExponential(4)}`);
638
+ info(dim(`loss (surrogate) ${report.surrogate.before.toExponential(4)} -> ${report.surrogate.after.toExponential(4)}, best of ${report.history.length - 1} iterations at ${report.best}`));
639
+ info(dim(`surrogate ops: ${report.kernel.exact} exact, ${report.kernel.smooth} smooth, ${report.kernel.straight_through} straight-through, ${report.kernel.frozen} without gradient`));
640
+ const values = new Map(report.params.map((p) => [p.name, p.after]));
641
+ const { text, edits } = writeBack(kernelSource, values);
642
+ const diff = unifiedDiff(relative(cwd, kernelFile), kernelText, text);
643
+ const retuned = compileForTuning({ ...kernelSource, text }, kernelSpec.name, objectiveSource, objectiveSpec.name, params, toolchain);
644
+ const same = sameButConstants(compiled.shipped.bytes, retuned.shipped.bytes);
645
+ if (!same) warn("the retuned kernel's bytecode differs beyond its constants (constant pooling or folding changed); review it before shipping");
646
+ const diffPath = path5.resolve(flagString(args, "diff") ?? `${kernelFile}${DIFF_SUFFIX}`);
647
+ if (diff === "") {
648
+ info("no constant changed");
649
+ } else {
650
+ await promises.writeFile(diffPath, diff);
651
+ info(diff.trimEnd());
652
+ if (flagBool(args, "apply")) {
653
+ await promises.writeFile(kernelFile, text);
654
+ success(`applied to ${kernelSource.path}`);
655
+ } else {
656
+ success(`review ${path5.relative(cwd, diffPath)}; rerun with --apply to write it`);
657
+ }
658
+ }
659
+ result({ params: report.params, exact: report.exact, surrogate: report.surrogate, edits, diff: diff === "" ? null : diffPath, applied: diff !== "" && flagBool(args, "apply"), sameButConstants: same, kernel: report.kernel });
660
+ }
661
+
662
+ export { USAGE, run as default };