@wallbreakerno4/opencode-commandcode 0.1.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 (76) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +54 -0
  3. package/dist/disguise/backoff.d.ts +33 -0
  4. package/dist/disguise/backoff.js +43 -0
  5. package/dist/disguise/config-block.d.ts +71 -0
  6. package/dist/disguise/config-block.js +166 -0
  7. package/dist/disguise/config-freeze.d.ts +27 -0
  8. package/dist/disguise/config-freeze.js +87 -0
  9. package/dist/disguise/fingerprint.d.ts +44 -0
  10. package/dist/disguise/fingerprint.js +127 -0
  11. package/dist/disguise/hash.d.ts +8 -0
  12. package/dist/disguise/hash.js +13 -0
  13. package/dist/disguise/headers.d.ts +25 -0
  14. package/dist/disguise/headers.js +25 -0
  15. package/dist/disguise/logger.d.ts +14 -0
  16. package/dist/disguise/logger.js +18 -0
  17. package/dist/disguise/preflight.d.ts +74 -0
  18. package/dist/disguise/preflight.js +139 -0
  19. package/dist/disguise/redact.d.ts +12 -0
  20. package/dist/disguise/redact.js +17 -0
  21. package/dist/disguise/session.d.ts +11 -0
  22. package/dist/disguise/session.js +24 -0
  23. package/dist/disguise/slug.d.ts +12 -0
  24. package/dist/disguise/slug.js +47 -0
  25. package/dist/disguise/state.d.ts +62 -0
  26. package/dist/disguise/state.js +157 -0
  27. package/dist/disguise/traceparent.d.ts +7 -0
  28. package/dist/disguise/traceparent.js +10 -0
  29. package/dist/disguise/version-cache.d.ts +27 -0
  30. package/dist/disguise/version-cache.js +69 -0
  31. package/dist/disguise/version-runtime.d.ts +42 -0
  32. package/dist/disguise/version-runtime.js +137 -0
  33. package/dist/disguise/version.d.ts +21 -0
  34. package/dist/disguise/version.js +17 -0
  35. package/dist/host/constants.d.ts +14 -0
  36. package/dist/host/constants.js +14 -0
  37. package/dist/host/v1.d.ts +85 -0
  38. package/dist/host/v1.js +118 -0
  39. package/dist/host/v2.d.ts +33 -0
  40. package/dist/host/v2.js +110 -0
  41. package/dist/index.d.ts +47 -0
  42. package/dist/index.js +33 -0
  43. package/dist/models/api.d.ts +23 -0
  44. package/dist/models/api.js +44 -0
  45. package/dist/models/artifact.d.ts +45 -0
  46. package/dist/models/artifact.js +108 -0
  47. package/dist/models/cascade.d.ts +47 -0
  48. package/dist/models/cascade.js +40 -0
  49. package/dist/models/mapping.d.ts +66 -0
  50. package/dist/models/mapping.js +47 -0
  51. package/dist/models/pipeline.d.ts +89 -0
  52. package/dist/models/pipeline.js +331 -0
  53. package/dist/models/signature.d.ts +20 -0
  54. package/dist/models/signature.js +29 -0
  55. package/dist/models/snapshot.d.ts +15 -0
  56. package/dist/models/snapshot.js +16 -0
  57. package/dist/models/snapshot.json +535 -0
  58. package/dist/models/urls.d.ts +39 -0
  59. package/dist/models/urls.js +96 -0
  60. package/dist/protocol/envelope.d.ts +118 -0
  61. package/dist/protocol/envelope.js +331 -0
  62. package/dist/protocol/errors.d.ts +97 -0
  63. package/dist/protocol/errors.js +281 -0
  64. package/dist/protocol/generate.d.ts +53 -0
  65. package/dist/protocol/generate.js +173 -0
  66. package/dist/protocol/images.d.ts +45 -0
  67. package/dist/protocol/images.js +115 -0
  68. package/dist/protocol/json.d.ts +11 -0
  69. package/dist/protocol/json.js +8 -0
  70. package/dist/protocol/ndjson.d.ts +41 -0
  71. package/dist/protocol/ndjson.js +321 -0
  72. package/dist/protocol/watchdog.d.ts +37 -0
  73. package/dist/protocol/watchdog.js +61 -0
  74. package/dist/provider/model.d.ts +95 -0
  75. package/dist/provider/model.js +323 -0
  76. package/package.json +46 -0
@@ -0,0 +1,323 @@
1
+ /**
2
+ * 工厂装配(#35 弹道合拢点):协议核心(执行内核 generate.ts)× 伪装模块
3
+ * (会话身份 / config 块 / 版本头 / 伪装头)× 模型管线(级联 limit 数据)组装成
4
+ * 包导出的 `createCommandCode({name, apiKey, headers, fetch})` 工厂,内部实现
5
+ * LanguageModelV3(契约形状:docs/research/v2-provider-contract.md §2 实测——
6
+ * `finishReason` 为 `{unified, raw}` 对象、`usage` 嵌套结构、上游恒为流式 NDJSON、
7
+ * `doGenerate` = `doStream` 聚合)。
8
+ *
9
+ * 工厂签名四键无 body;宿主按「模块第一个 `create*` 前缀导出」判据发现(v1/v2
10
+ * 共用),返回 `{ languageModel(modelID) → LanguageModelV3 }`。模型 reference 按
11
+ * 首个 `/` 切分 provider 与 wire id——wire id 自身可含 `/`(如
12
+ * `deepseek/deepseek-v4-flash`),故仅 provider 段与本插件同名时才剥前缀,无前缀
13
+ * reference 原样透传。
14
+ *
15
+ * doStream 主路径:预请求惰性检查(到点后台发射,主请求零等待)→ 会话身份派生
16
+ * (从调用 headers 的 OpenCode 会话头确定性派生,缺失落 per-key 回退轮换)→ 图片
17
+ * 预处理(模态过滤 + 外链下载)→ config 块与版本头并行就绪 → 信封构造(伪装字段
18
+ * 填充点)→ 伪装头组装(`threadId` 与 `x-session-id` 同值)→ 300s 看门狗执行内核。
19
+ * HTTP 层错误(非 2xx / 响应前网络错误 / 看门狗连接期到点)在 doStream await 时
20
+ * 抛出(AI SDK 惯例,宿主 catch 分类);流内错误(截断 / error 事件 / 零输出 /
21
+ * 流中看门狗到点)以 error part 浮现(protocol.md §3)。
22
+ *
23
+ * 运行时共享:宿主每次模型请求都会重新调用工厂(v2 实测两遍/请求),预请求节奏
24
+ * (D2 每进程一次)与模型管线刷新节奏(model-pipeline.md §4 TTL)必须跨工厂调用
25
+ * 存活——伪装状态与管线为模块级单例;宿主逐次注入的 fetch / 自定义头 / logger 经
26
+ * 接缝重指向最新值;key 相关状态(回退会话、预请求退避)由 DisguiseState 内部
27
+ * per-key Map 承载。调用 headers 只作会话派生种子、不透传上游(`X-Session-Id` 等
28
+ * 是宿主内部头,非 CLI 指纹);工厂 options.headers(用户显式配置)透传且被伪装
29
+ * 键覆盖防冲突。
30
+ */
31
+ import { PERMISSION_MODE } from "../disguise/config-block.js";
32
+ import { buildGenerateHeaders } from "../disguise/headers.js";
33
+ import { consoleLogger } from "../disguise/logger.js";
34
+ import { createDisguiseState } from "../disguise/state.js";
35
+ import { parseArtifact } from "../models/artifact.js";
36
+ import { mergeModelLayers } from "../models/cascade.js";
37
+ import { createModelPipeline } from "../models/pipeline.js";
38
+ import { PACKAGE_SNAPSHOT_JSON } from "../models/snapshot.js";
39
+ import { buildEnvelope, PROVIDER_ID, splitModelReference } from "../protocol/envelope.js";
40
+ import { generateStream } from "../protocol/generate.js";
41
+ import { preparePromptImages } from "../protocol/images.js";
42
+ let runtime;
43
+ /**
44
+ * 模型数据变更分发器:管线构造时只订阅这个稳定跳板,真实回调经
45
+ * `ensureProviderRuntime` 注册(最后写入者生效)——v2 glue 接 `catalog.reload()`;
46
+ * 插件热重载(setup 重跑)时旧 ctx 失效,重设让 reload 打到活 ctx 上。运行时由
47
+ * 工厂先行构造(独立调用 / v1 形态)时无人注册,分发为 no-op。
48
+ */
49
+ let onModelDataChange;
50
+ function loadPackageSnapshot(logger) {
51
+ const parsed = parseArtifact(PACKAGE_SNAPSHOT_JSON);
52
+ if (parsed.ok)
53
+ return parsed.artifact;
54
+ // 常量形状在编译期已定,此分支仅防御快照常量被改坏;兜底空清单保「永远可用」
55
+ logger.warn(`包内快照解析失败(${JSON.stringify(parsed.error)}),以空清单兜底`);
56
+ return { schemaVersion: 1, generatedAt: "", sourceCliVersion: "", models: [] };
57
+ }
58
+ /** 快照解析记忆化:latestCascade 的「运行时未构造」路径与运行时构造共用同一份;
59
+ * 解析异常走 console warn(§7 生产退化通道——此路径无宿主 logger 可注入) */
60
+ let snapshotMemo;
61
+ function packageSnapshot() {
62
+ snapshotMemo ??= loadPackageSnapshot(consoleLogger());
63
+ return snapshotMemo;
64
+ }
65
+ function getRuntime(init, startup = "start") {
66
+ if (runtime !== undefined)
67
+ return runtime;
68
+ const seam = {
69
+ fetch: init.fetch ?? globalThis.fetch,
70
+ headers: init.headers ?? {},
71
+ logger: init.logger ?? consoleLogger(),
72
+ };
73
+ // 稳定出网跳板:伪装状态与管线构造时捕获本函数,全部出网路径(预请求 / models /
74
+ // 产物 / 版本查询 / 图片下载 / generate)经它逐次转发到 seam.fetch 最新注入值
75
+ const trampoline = (input, requestInit) => seam.fetch(input, requestInit);
76
+ const trampolineLogger = {
77
+ debug: (message) => seam.logger.debug(message),
78
+ warn: (message) => seam.logger.warn(message),
79
+ };
80
+ const snapshot = packageSnapshot();
81
+ const state = createDisguiseState({
82
+ fetch: trampoline,
83
+ logger: trampolineLogger,
84
+ // 版本头兜底链 ③/④ 接线(disguise.md §6):产物层读管线内存(getter 调用时
85
+ // 单例必已赋值),快照层读本包快照
86
+ artifactSourceCliVersion: () => runtime?.pipeline.artifactSourceCliVersion(),
87
+ snapshotSourceCliVersion: () => snapshot.sourceCliVersion,
88
+ });
89
+ const pipeline = createModelPipeline({
90
+ snapshot,
91
+ modelsUrls: init.modelsUrls,
92
+ fetch: trampoline,
93
+ logger: trampolineLogger,
94
+ // 变更分发走模块级跳板:真实回调(catalog.reload)由 glue 注册、可随热重载重设
95
+ onChange: (cascade) => onModelDataChange?.(cascade),
96
+ });
97
+ // 启动协商(#37):"start" = v2 节奏(零阻塞,首轮拉取转后台;v2 setup 与工厂
98
+ // 独立调用都走它);"defer" = v1 glue 的 config hook 构造后紧接 initializeOnce
99
+ //(15s 预算拉一次、无后台刷新)。构造本身不指定形态——先后到达者定模式。
100
+ if (startup === "start")
101
+ pipeline.start();
102
+ runtime = { state, pipeline, snapshot, seam, trampoline };
103
+ return runtime;
104
+ }
105
+ /**
106
+ * v2 glue(#36)的运行时入口:幂等构造 + 变更回调注册。setup 语义下 modelsUrls
107
+ * 的 config 通道(settings.modelsUrls)由 glue 在 transform 内捕获后传入;工厂侧
108
+ * 宿主透传值只在工厂先行构造(独立调用 / v1 形态)时作构造初值。
109
+ */
110
+ export function ensureProviderRuntime(init) {
111
+ if (init.onModelDataChange !== undefined)
112
+ onModelDataChange = init.onModelDataChange;
113
+ getRuntime(init);
114
+ }
115
+ /**
116
+ * v1 glue(#37)的运行时入口:v1 启动协商——幂等构造(不预启动)+ 启动拉取一次
117
+ *(15s 总预算、失败用快照、此后无后台刷新,model-pipeline.md §4)。v1 config hook
118
+ * 在注入模型清单前 await 本函数;运行时已被先行构造时(v2 setup / 工厂独立调用),
119
+ * initializeOnce 的幂等守卫使本调用退化为「返回当前级联」。
120
+ */
121
+ export async function ensureV1ProviderRuntime(init) {
122
+ const rt = getRuntime(init, "defer");
123
+ await rt.pipeline.initializeOnce();
124
+ return rt.pipeline.getModels();
125
+ }
126
+ /**
127
+ * 当前级联(v2 glue 的 transform 回放数据源):运行时已构造时读管线实时值(读时
128
+ * 惰性检查 TTL,reload 回放顺带充当显式到期触发点);未构造时为纯快照层——启动
129
+ * 零阻塞的注册序:transform 先注册快照,后台拉取的变更经 reload 回放本函数更新。
130
+ */
131
+ export function latestCascade() {
132
+ if (runtime !== undefined)
133
+ return runtime.pipeline.getModels();
134
+ return mergeModelLayers({ snapshot: packageSnapshot() });
135
+ }
136
+ // ---------------------------------------------------------------------------
137
+ // 工厂与 LanguageModelV3
138
+ // ---------------------------------------------------------------------------
139
+ export function createCommandCode(options) {
140
+ const rt = getRuntime(options);
141
+ // 逐次调用重指向最新注入:宿主包装 fetch、provider 级自定义头、logger
142
+ rt.seam.fetch = options.fetch ?? globalThis.fetch;
143
+ rt.seam.headers = options.headers ?? {};
144
+ rt.seam.logger = options.logger ?? consoleLogger();
145
+ // modelsUrls config 通道接驳(model-pipeline.md §1.3):v2 宿主把 settings.modelsUrls
146
+ // 合并进工厂 options(首次工厂调用前插件侧不可见——管线构造时按 env/默认列表启动,
147
+ // 这里逐次重绑定,原值不变零开销);v1 宿主的 options.modelsUrls 已由 glue 的
148
+ // config hook 在构造时应用,管线在 v1 形态下忽略重绑定(无后台刷新,无需补拉)
149
+ if (options.modelsUrls !== undefined)
150
+ rt.pipeline.rebindModelsUrls(options.modelsUrls);
151
+ const apiKey = options.apiKey ?? "";
152
+ return {
153
+ languageModel(modelID) {
154
+ return {
155
+ specificationVersion: "v3",
156
+ provider: PROVIDER_ID,
157
+ modelId: modelID,
158
+ supportedUrls: {},
159
+ doStream: (callOptions) => doStream(rt, apiKey, modelID, callOptions),
160
+ doGenerate: (callOptions) => doGenerate(rt, apiKey, modelID, callOptions),
161
+ };
162
+ },
163
+ };
164
+ }
165
+ /**
166
+ * reference 解析:`splitModelReference` 按首个 `/` 切分;wire id 自身可含 `/`,
167
+ * 仅 provider 段与本插件同名(或无 `/` 的裸 id,其默认段即本插件)时才取切分结果,
168
+ * 否则整串是无前缀 reference,原样透传。
169
+ */
170
+ function resolveWireId(reference) {
171
+ const { providerId, wireId } = splitModelReference(reference);
172
+ return providerId === PROVIDER_ID ? wireId : reference;
173
+ }
174
+ /** 未注册模型的防御路径(级联查无此 id——注册与级联同源,正常路径永不触发):
175
+ * `max_tokens` 是信封必填字段,无级联裁剪参考可用时以调用方值为准,缺省兜构建侧同款
176
+ * 上限常量(model-pipeline.md §1.1 maxOutput 补全链末级「常量 32000」——非本模块
177
+ * 发明值)并打 warn 指明未注册;网关墙 200000 在协议核心统一钳制(ADR 0002)。 */
178
+ const FALLBACK_MAX_OUTPUT = 32_000;
179
+ function fallbackMaxOutput(logger, wireId, callerValue) {
180
+ logger.warn(`模型 ${wireId} 不在级联清单中(未注册或已下架),max_tokens 无级联上限可用` +
181
+ `,按调用方值处理${callerValue === undefined ? `,缺省兜 ${FALLBACK_MAX_OUTPUT}` : ""}`);
182
+ return callerValue ?? FALLBACK_MAX_OUTPUT;
183
+ }
184
+ /**
185
+ * 一次调用的完整装配:产出执行内核的 stream part 生成器(doStream/doGenerate 共用,
186
+ * 看门狗天然共享同一内核)。
187
+ */
188
+ async function* callIterator(rt, apiKey, modelID, options) {
189
+ const wireId = resolveWireId(modelID);
190
+ // 预请求惰性检查(D5):到点后台发射,主请求永不等待
191
+ rt.state.maybeRunPreflight(apiKey);
192
+ // 会话身份主路径(D3):调用 headers 中 OpenCode 会话头任一存在即确定性派生;
193
+ // 全部缺失落 per-key 回退轮换。threadId 与 x-session-id 同值由此一处保证。
194
+ const sessionId = rt.state.resolveSessionId(apiKey, options.headers);
195
+ const lastUserMessage = [...options.prompt].reverse().find((message) => message.role === "user");
196
+ const turnKey = lastUserMessage === undefined ? "no-user-message" : JSON.stringify(lastUserMessage);
197
+ const traceparent = rt.state.resolveTraceparent(apiKey, sessionId, turnKey);
198
+ // 模型 limit 数据(管线级联,stale-while-revalidate):maxOutput 裁剪与图片模态
199
+ const resolved = rt.pipeline.getModels().models.find((model) => model.id === wireId);
200
+ const maxOutput = resolved?.maxOutput ?? fallbackMaxOutput(rt.seam.logger, wireId, options.maxOutputTokens);
201
+ // 图片预处理(§5):模态过滤 + 外链下载,不可投递者丢弃 + warn(绝不静默)
202
+ const prompt = await preparePromptImages(options.prompt, {
203
+ inputModalities: resolved?.inputModalities ?? [],
204
+ modelId: wireId,
205
+ fetch: rt.trampoline,
206
+ logger: rt.seam.logger,
207
+ });
208
+ // 伪装填充点就绪:config 块(进程级冻结 + 24h 过期重采)与版本头(1.5s 竞速上限)并行
209
+ const [configBlock, ccVersion] = await Promise.all([rt.state.getConfigBlock(), rt.state.getCommandCodeVersion()]);
210
+ const { body, warnings } = buildEnvelope({ ...options, prompt }, {
211
+ modelId: wireId,
212
+ maxOutput,
213
+ threadId: sessionId,
214
+ // ConfigBlock → 信封填充点的零知识搬运:接口无索引签名,结构上仅此一处适配
215
+ disguise: { config: configBlock, permissionMode: PERMISSION_MODE },
216
+ });
217
+ // 伪装头组装;用户显式配置头垫底(被伪装键覆盖防冲突);调用 headers 不透传
218
+ const headers = {
219
+ ...rt.seam.headers,
220
+ ...buildGenerateHeaders({ apiKey, ccVersion, sessionId, workingDir: configBlock.workingDir, traceparent }),
221
+ };
222
+ yield* generateStream({
223
+ modelId: wireId,
224
+ body,
225
+ headers,
226
+ fetchImpl: rt.trampoline,
227
+ signal: options.abortSignal,
228
+ warnings,
229
+ });
230
+ }
231
+ /** doStream:主动推进首块(fetch 发起与 HTTP 层错误抛出在 await 时完成),
232
+ * 其余部分包成 ReadableStream 交宿主。 */
233
+ async function doStream(rt, apiKey, modelID, options) {
234
+ const iterator = callIterator(rt, apiKey, modelID, options);
235
+ const first = await iterator.next();
236
+ let pending = first.done ? undefined : first.value;
237
+ const stream = new ReadableStream({
238
+ async pull(controller) {
239
+ if (pending !== undefined) {
240
+ controller.enqueue(pending);
241
+ pending = undefined;
242
+ return;
243
+ }
244
+ try {
245
+ const result = await iterator.next();
246
+ if (result.done)
247
+ controller.close();
248
+ else
249
+ controller.enqueue(result.value);
250
+ }
251
+ catch (error) {
252
+ controller.error(error);
253
+ }
254
+ },
255
+ cancel() {
256
+ // 宿主弃流:走生成器 finally(看门狗定时器释放),上游 fetch 由宿主接缝管理
257
+ void iterator.return(undefined);
258
+ },
259
+ });
260
+ return { stream };
261
+ }
262
+ /** doGenerate = doStream 聚合(上游恒为流式):text / reasoning 块按 id 归并、
263
+ * tool-call 收拢增量、finish 收账;error part 无聚合通道,以 throw 浮出。 */
264
+ async function doGenerate(rt, apiKey, modelID, options) {
265
+ const iterator = callIterator(rt, apiKey, modelID, options);
266
+ const content = [];
267
+ const warnings = [];
268
+ /** 进行中的块(id → 块对象;对象引用已入 content,追加即聚合) */
269
+ const open = new Map();
270
+ let finishReason = { unified: "other", raw: undefined };
271
+ let usage = {
272
+ inputTokens: { total: undefined, noCache: undefined, cacheRead: undefined, cacheWrite: undefined },
273
+ outputTokens: { total: undefined, text: undefined, reasoning: undefined },
274
+ };
275
+ let providerMetadata;
276
+ for await (const part of iterator) {
277
+ switch (part.type) {
278
+ case "stream-start":
279
+ warnings.push(...part.warnings);
280
+ break;
281
+ case "text-start":
282
+ case "reasoning-start": {
283
+ const block = { type: part.type === "text-start" ? "text" : "reasoning", text: "" };
284
+ open.set(part.id, block);
285
+ content.push(block);
286
+ break;
287
+ }
288
+ case "text-delta":
289
+ case "reasoning-delta": {
290
+ const block = open.get(part.id);
291
+ if (block !== undefined)
292
+ block.text += part.delta;
293
+ break;
294
+ }
295
+ case "text-end":
296
+ case "reasoning-end":
297
+ open.delete(part.id);
298
+ break;
299
+ case "tool-call":
300
+ content.push({ type: "tool-call", toolCallId: part.toolCallId, toolName: part.toolName, input: part.input });
301
+ break;
302
+ case "finish":
303
+ finishReason = part.finishReason;
304
+ usage = part.usage;
305
+ if (part.providerMetadata !== undefined)
306
+ providerMetadata = part.providerMetadata;
307
+ break;
308
+ case "error":
309
+ // doGenerate 无流内错误通道:error part 一律抛出(宿主按 APICallError 分类)
310
+ throw part.error;
311
+ default:
312
+ // tool-input-* 增量已由一次性 tool-call 收拢;response-metadata 等不进 content
313
+ break;
314
+ }
315
+ }
316
+ return {
317
+ content,
318
+ finishReason,
319
+ usage,
320
+ warnings,
321
+ ...(providerMetadata !== undefined ? { providerMetadata } : {}),
322
+ };
323
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/package.json",
3
+ "name": "@wallbreakerno4/opencode-commandcode",
4
+ "version": "0.1.0",
5
+ "description": "OpenCode provider plugin for the Command Code Go plan: an in-process bridge to the /alpha/generate gateway.",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/WallBreakerNO4/opencode-commandcode-provider.git"
22
+ },
23
+ "bugs": {
24
+ "url": "https://github.com/WallBreakerNO4/opencode-commandcode-provider/issues"
25
+ },
26
+ "homepage": "https://github.com/WallBreakerNO4/opencode-commandcode-provider#readme",
27
+ "keywords": [
28
+ "opencode",
29
+ "opencode-plugin",
30
+ "provider",
31
+ "commandcode"
32
+ ],
33
+ "devDependencies": {
34
+ "@ai-sdk/provider": "3.0.8",
35
+ "@opencode-ai/plugin": "beta",
36
+ "@types/bun": "^1.4.0",
37
+ "typescript": "^5.9.3"
38
+ },
39
+ "scripts": {
40
+ "test": "bun test",
41
+ "typecheck": "tsc --noEmit",
42
+ "build": "tsc -p tsconfig.build.json && cp src/models/snapshot.json dist/models/snapshot.json",
43
+ "build:models": "bun scripts/build-models/main.ts",
44
+ "embed-snapshot": "bun scripts/embed-snapshot.ts"
45
+ }
46
+ }