@irogane/kaji 0.2.0-beta.11 → 0.3.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 (137) hide show
  1. package/README.md +112 -551
  2. package/dist/index.d.mts +253 -0
  3. package/dist/index.mjs +440 -0
  4. package/package.json +28 -166
  5. package/contracts/README.md +0 -14
  6. package/contracts/beta-core-v1.json +0 -52
  7. package/contracts/cli/init-cases-v1.json +0 -27
  8. package/contracts/errors/error-codes.json +0 -48
  9. package/contracts/errors/integration-recovery-v1.json +0 -127
  10. package/contracts/errors/provider-normalization.json +0 -111
  11. package/contracts/events/conformance-invalid.json +0 -20
  12. package/contracts/events/conformance.json +0 -511
  13. package/contracts/events/new-kaji-event-v1.schema.json +0 -1021
  14. package/contracts/events/stored-kaji-event-v1.schema.json +0 -1025
  15. package/contracts/feature-tiers-v1.json +0 -488
  16. package/contracts/integrations/abi-index-v1.json +0 -8
  17. package/contracts/integrations/conformance-invalid.json +0 -443
  18. package/contracts/integrations/conformance-valid.json +0 -121
  19. package/contracts/integrations/copy-provenance-v1.schema.json +0 -61
  20. package/contracts/integrations/echo-tool-abi-v1.json +0 -37
  21. package/contracts/integrations/github-api-conformance-v1.json +0 -644
  22. package/contracts/integrations/github-tool-abi-typescript-v1.json +0 -369
  23. package/contracts/integrations/github-tool-abi-v1.json +0 -146
  24. package/contracts/integrations/gmail-api-conformance-v1.json +0 -750
  25. package/contracts/integrations/gmail-tool-abi-v1.json +0 -62
  26. package/contracts/integrations/index.schema.json +0 -37
  27. package/contracts/integrations/manifest.schema.json +0 -119
  28. package/contracts/parity/expected-normalized.json +0 -4906
  29. package/contracts/parity/scenarios.json +0 -100
  30. package/contracts/parity/scenarios.schema.json +0 -214
  31. package/contracts/providers/cost-conformance.json +0 -112
  32. package/contracts/release/github-proof-v1.schema.json +0 -138
  33. package/contracts/release/gmail-proof-v1.schema.json +0 -138
  34. package/contracts/release/kaji-ts-consumer-handoff-v1.schema.json +0 -1289
  35. package/contracts/release/publisher-identity-receipt-v1.schema.json +0 -319
  36. package/contracts/release/typescript-onboarding-evidence-v1.schema.json +0 -740
  37. package/contracts/tools/conformance-invalid.json +0 -271
  38. package/contracts/tools/conformance-valid.json +0 -78
  39. package/contracts/tools/tool-schema-v1.schema.json +0 -18
  40. package/dist/anthropic.cjs +0 -1231
  41. package/dist/anthropic.cjs.map +0 -1
  42. package/dist/anthropic.d.cts +0 -26
  43. package/dist/anthropic.d.ts +0 -26
  44. package/dist/anthropic.js +0 -270
  45. package/dist/anthropic.js.map +0 -1
  46. package/dist/auth.cjs +0 -1507
  47. package/dist/auth.cjs.map +0 -1
  48. package/dist/auth.d.cts +0 -129
  49. package/dist/auth.d.ts +0 -129
  50. package/dist/auth.js +0 -1039
  51. package/dist/auth.js.map +0 -1
  52. package/dist/base-B9FRMcP8.d.cts +0 -140
  53. package/dist/base-nHQd1VtS.d.ts +0 -140
  54. package/dist/chunk-AAM33KAO.js +0 -4367
  55. package/dist/chunk-AAM33KAO.js.map +0 -1
  56. package/dist/chunk-KAJ6BM64.js +0 -153
  57. package/dist/chunk-KAJ6BM64.js.map +0 -1
  58. package/dist/chunk-KCAXIOZS.js +0 -308
  59. package/dist/chunk-KCAXIOZS.js.map +0 -1
  60. package/dist/chunk-LSJ4AVO2.js +0 -243
  61. package/dist/chunk-LSJ4AVO2.js.map +0 -1
  62. package/dist/chunk-TM7ZGOJX.js +0 -716
  63. package/dist/chunk-TM7ZGOJX.js.map +0 -1
  64. package/dist/cli/bin.d.ts +0 -2
  65. package/dist/cli/bin.js +0 -13
  66. package/dist/cli/bin.js.map +0 -1
  67. package/dist/cli/chunk-2RCWPRVY.js +0 -6277
  68. package/dist/cli/chunk-2RCWPRVY.js.map +0 -1
  69. package/dist/cli/chunk-SEBX54TR.js +0 -681
  70. package/dist/cli/chunk-SEBX54TR.js.map +0 -1
  71. package/dist/cli/index.d.ts +0 -232
  72. package/dist/cli/index.js +0 -11
  73. package/dist/cli/index.js.map +0 -1
  74. package/dist/cli/init-worker.d.ts +0 -2
  75. package/dist/cli/init-worker.js +0 -18
  76. package/dist/cli/init-worker.js.map +0 -1
  77. package/dist/cli/integration-copy-worker.js +0 -48
  78. package/dist/cli/integration-copy-worker.js.map +0 -1
  79. package/dist/cli/package-entry-cjs.cjs +0 -21
  80. package/dist/cli/package-entry-cjs.cjs.map +0 -1
  81. package/dist/cli/package-entry-cjs.d.cts +0 -2
  82. package/dist/cli/package-entry.d.ts +0 -2
  83. package/dist/cli/package-entry.js +0 -13
  84. package/dist/cli/package-entry.js.map +0 -1
  85. package/dist/context-BaFHrQHv.d.cts +0 -21
  86. package/dist/context-BaFHrQHv.d.ts +0 -21
  87. package/dist/context-C-YPY-GS.d.cts +0 -1538
  88. package/dist/context-C-YPY-GS.d.ts +0 -1538
  89. package/dist/index.cjs +0 -12346
  90. package/dist/index.cjs.map +0 -1
  91. package/dist/index.d.cts +0 -1560
  92. package/dist/index.d.ts +0 -1560
  93. package/dist/index.js +0 -7852
  94. package/dist/index.js.map +0 -1
  95. package/dist/integrations/github.cjs +0 -2092
  96. package/dist/integrations/github.cjs.map +0 -1
  97. package/dist/integrations/github.d.cts +0 -21
  98. package/dist/integrations/github.d.ts +0 -21
  99. package/dist/integrations/github.js +0 -2088
  100. package/dist/integrations/github.js.map +0 -1
  101. package/dist/integrations.cjs +0 -3370
  102. package/dist/integrations.cjs.map +0 -1
  103. package/dist/integrations.d.cts +0 -202
  104. package/dist/integrations.d.ts +0 -202
  105. package/dist/integrations.js +0 -2650
  106. package/dist/integrations.js.map +0 -1
  107. package/dist/observability-Cj--OkME.d.cts +0 -96
  108. package/dist/observability-Cj--OkME.d.ts +0 -96
  109. package/dist/openai.cjs +0 -1235
  110. package/dist/openai.cjs.map +0 -1
  111. package/dist/openai.d.cts +0 -32
  112. package/dist/openai.d.ts +0 -32
  113. package/dist/openai.js +0 -272
  114. package/dist/openai.js.map +0 -1
  115. package/dist/testing.cjs +0 -554
  116. package/dist/testing.cjs.map +0 -1
  117. package/dist/testing.d.cts +0 -43
  118. package/dist/testing.d.ts +0 -43
  119. package/dist/testing.js +0 -118
  120. package/dist/testing.js.map +0 -1
  121. package/registry/echo/index.ts +0 -53
  122. package/registry/echo/manifest.json +0 -53
  123. package/registry/github/LICENSE +0 -105
  124. package/registry/github/client.ts +0 -1727
  125. package/registry/github/index.ts +0 -263
  126. package/registry/github/manifest.json +0 -227
  127. package/registry/github/owner-fixtures.json +0 -10
  128. package/registry/github/tests/github.test.ts +0 -32
  129. package/registry/gmail/LICENSE +0 -105
  130. package/registry/gmail/client.ts +0 -548
  131. package/registry/gmail/index.ts +0 -165
  132. package/registry/gmail/manifest.json +0 -102
  133. package/registry/gmail/owner-fixtures.json +0 -10
  134. package/registry/gmail/tests/gmail.test.ts +0 -32
  135. package/registry/index.json +0 -21
  136. package/registry/index.schema.json +0 -37
  137. package/registry/schema.json +0 -119
@@ -1,681 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/cli/init.ts
4
- import { randomUUID } from "crypto";
5
- import { spawn } from "child_process";
6
- import { once } from "events";
7
- import { constants, existsSync, lstatSync, mkdirSync, readFileSync } from "fs";
8
- import { link, lstat as lstatAsync, mkdtemp, open, rename, rm, writeFile } from "fs/promises";
9
- import { basename, isAbsolute, join, resolve } from "path";
10
- import { fileURLToPath } from "url";
11
- var PROVIDERS = ["mock", "openai", "anthropic"];
12
- var CliArgError = class extends Error {
13
- };
14
- var WORKER_INPUT_LIMIT_BYTES = 256 * 1024;
15
- var WORKER_OUTPUT_LIMIT_BYTES = 8 * 1024;
16
- var WORKER_TIMEOUT_MS = 15e3;
17
- var WORKER_COMMIT = "commit";
18
- var WORKER_CANCEL = "cancel";
19
- var WORKER_PREPARED = '{"prepared":true}';
20
- var WORKER_SUCCESS = '{"ok":true}';
21
- var RECOVERY_DIRECTORY = /^\.kaji-scaffold-backup-[A-Za-z0-9]{6}$/;
22
- function safeRecoveryDirectory(value) {
23
- return typeof value === "string" && RECOVERY_DIRECTORY.test(value) ? value : void 0;
24
- }
25
- var ScaffoldRollbackError = class extends Error {
26
- recoveryDirectory;
27
- constructor(recoveryDirectory, cause) {
28
- const safeDirectory = safeRecoveryDirectory(recoveryDirectory);
29
- if (safeDirectory === void 0) throw new Error("invalid scaffold recovery directory");
30
- super("scaffold publication rollback failed", { cause });
31
- this.name = "ScaffoldRollbackError";
32
- this.recoveryDirectory = safeDirectory;
33
- }
34
- };
35
- function scaffoldRecoveryDirectory(error) {
36
- return error instanceof ScaffoldRollbackError ? error.recoveryDirectory : void 0;
37
- }
38
- function throwIfCancelled(signal) {
39
- if (signal?.aborted) throw new Error("scaffold worker cancelled");
40
- }
41
- function parseArgs(rest) {
42
- let positionalPath;
43
- let provider = "mock";
44
- let force = false;
45
- for (let index = 0; index < rest.length; index++) {
46
- const arg = rest[index];
47
- if (arg === "--provider") {
48
- const next = rest[index + 1];
49
- if (next === void 0 || next.startsWith("--")) {
50
- throw new CliArgError("--provider requires a value");
51
- }
52
- if (!PROVIDERS.includes(next)) {
53
- throw new CliArgError("--provider must be mock, openai, or anthropic");
54
- }
55
- provider = next;
56
- index++;
57
- } else if (arg === "--force") {
58
- force = true;
59
- } else if (arg === "--yes") {
60
- } else if (arg.startsWith("--")) {
61
- throw new CliArgError(`unknown argument: ${arg}`);
62
- } else if (positionalPath === void 0) {
63
- positionalPath = arg;
64
- } else {
65
- throw new CliArgError(`unexpected path argument: ${arg}`);
66
- }
67
- }
68
- return {
69
- out: resolve(positionalPath ?? "."),
70
- provider,
71
- force
72
- };
73
- }
74
- function installedMetadata() {
75
- const path = new URL("../../package.json", import.meta.url);
76
- const value = JSON.parse(readFileSync(path, "utf8"));
77
- if (typeof value.version !== "string" || value.version.length === 0 || value.peerDependencies === void 0 || value.devDependencies === void 0) {
78
- throw new Error("installed kaji package metadata is incomplete");
79
- }
80
- return value;
81
- }
82
- function agentSource(provider) {
83
- const providerImports = {
84
- mock: 'import { MockProvider } from "@irogane/kaji/testing";',
85
- openai: 'import { OpenAIProvider } from "@irogane/kaji/openai";',
86
- anthropic: 'import { AnthropicProvider } from "@irogane/kaji/anthropic";'
87
- };
88
- const providerSetup = {
89
- mock: "const provider = new MockProvider();",
90
- openai: `const environment = (globalThis as unknown as { process: { env: Record<string, string | undefined> } }).process.env;
91
- const apiKey = environment.OPENAI_API_KEY;
92
- if (!apiKey) throw new Error("OPENAI_API_KEY is required for the openai scaffold");
93
- const provider = new OpenAIProvider({ apiKey });`,
94
- anthropic: `const environment = (globalThis as unknown as { process: { env: Record<string, string | undefined> } }).process.env;
95
- const apiKey = environment.ANTHROPIC_API_KEY;
96
- if (!apiKey) throw new Error("ANTHROPIC_API_KEY is required for the anthropic scaffold");
97
- const provider = new AnthropicProvider({ apiKey });`
98
- };
99
- return `import { AgentBuilder } from "@irogane/kaji";
100
- ${providerImports[provider]}
101
-
102
- ${providerSetup[provider]}
103
- const runtime = new AgentBuilder().provider(provider).build();
104
- const result = await runtime.turn("Say hello.");
105
- const finalSequence = Math.max(...result.events.map((event) => event.sequence));
106
- console.log(\`text=\${result.text}\`);
107
- console.log(\`turn_id=\${result.turnId}\`);
108
- console.log(\`final_sequence=\${finalSequence}\`);
109
- `;
110
- }
111
- function scaffoldFiles(provider) {
112
- const metadata = installedMetadata();
113
- const zodRange = metadata.peerDependencies.zod;
114
- if (zodRange === void 0) throw new Error("installed kaji has no Zod peer range");
115
- const nodeTypesRange = metadata.devDependencies["@types/node"];
116
- if (nodeTypesRange === void 0) {
117
- throw new Error("installed kaji has no supported @types/node range");
118
- }
119
- const dotenvxVersion = metadata.devDependencies["@dotenvx/dotenvx"];
120
- if (dotenvxVersion === void 0) {
121
- throw new Error("installed kaji has no supported dotenvx version");
122
- }
123
- const dependencies = {
124
- "@irogane/kaji": metadata.version,
125
- zod: zodRange
126
- };
127
- if (provider === "openai") {
128
- const range = metadata.peerDependencies.openai;
129
- if (range === void 0) throw new Error("installed kaji has no OpenAI peer range");
130
- dependencies.openai = range;
131
- } else if (provider === "anthropic") {
132
- const range = metadata.peerDependencies["@anthropic-ai/sdk"];
133
- if (range === void 0) throw new Error("installed kaji has no Anthropic peer range");
134
- dependencies["@anthropic-ai/sdk"] = range;
135
- }
136
- return {
137
- "package.json": JSON.stringify(
138
- {
139
- name: "my-kaji-agent",
140
- version: "0.1.0",
141
- private: true,
142
- type: "module",
143
- scripts: {
144
- start: "dotenvx run --ignore=MISSING_ENV_FILE -- tsx agent.ts",
145
- typecheck: "tsc --noEmit"
146
- },
147
- dependencies,
148
- devDependencies: {
149
- "@dotenvx/dotenvx": dotenvxVersion,
150
- "@types/node": nodeTypesRange,
151
- tsx: "^4.0.0",
152
- typescript57: metadata.devDependencies.typescript57 ?? "npm:typescript@5.7.3",
153
- typescript: metadata.devDependencies.typescript ?? "^6.0.0"
154
- }
155
- },
156
- null,
157
- 2
158
- ),
159
- "tsconfig.json": JSON.stringify(
160
- {
161
- compilerOptions: {
162
- target: "ES2022",
163
- module: "ESNext",
164
- moduleResolution: "Bundler",
165
- strict: true,
166
- esModuleInterop: true,
167
- skipLibCheck: false,
168
- types: ["node"]
169
- },
170
- include: ["*.ts"]
171
- },
172
- null,
173
- 2
174
- ),
175
- "agent.ts": agentSource(provider),
176
- ".env.example": provider === "mock" ? "# No provider credentials required.\n" : provider === "openai" ? "OPENAI_API_KEY=\n" : "ANTHROPIC_API_KEY=\n"
177
- };
178
- }
179
- function destinationKind(path) {
180
- try {
181
- const stat = lstatSync(path);
182
- if (stat.isSymbolicLink()) return "symlink";
183
- return stat.isFile() ? "file" : "other";
184
- } catch (error) {
185
- if (error.code === "ENOENT") return "missing";
186
- throw error;
187
- }
188
- }
189
- function validateWorkerRequest(value) {
190
- if (typeof value !== "object" || value === null) {
191
- throw new Error("invalid scaffold worker request");
192
- }
193
- const request = value;
194
- if (request.version !== 1 || typeof request.expectedDev !== "string" || !/^\d+$/.test(request.expectedDev) || typeof request.expectedIno !== "string" || !/^\d+$/.test(request.expectedIno) || typeof request.targetAbsolute !== "string" || !isAbsolute(request.targetAbsolute) || typeof request.files !== "object" || request.files === null || Array.isArray(request.files) || typeof request.force !== "boolean") {
195
- throw new Error("invalid scaffold worker request");
196
- }
197
- const entries = Object.entries(request.files);
198
- if (entries.length === 0 || entries.length > 32 || entries.some(
199
- ([name, body]) => name.length === 0 || name === "." || name === ".." || basename(name) !== name || name.includes("\0") || typeof body !== "string"
200
- )) {
201
- throw new Error("invalid scaffold worker request");
202
- }
203
- return request;
204
- }
205
- async function assertTargetIdentity(targetAbsolute, expectedDev, expectedIno) {
206
- let targetStat;
207
- try {
208
- targetStat = await lstatAsync(targetAbsolute, { bigint: true });
209
- } catch {
210
- throw new Error("target directory changed during scaffold publication");
211
- }
212
- if (!targetStat.isDirectory() || targetStat.isSymbolicLink() || targetStat.dev !== expectedDev || targetStat.ino !== expectedIno) {
213
- throw new Error("target directory changed during scaffold publication");
214
- }
215
- }
216
- async function runInitWorkerRequest(requestValue, options = {}) {
217
- const request = validateWorkerRequest(requestValue);
218
- const expectedDev = BigInt(request.expectedDev);
219
- const expectedIno = BigInt(request.expectedIno);
220
- throwIfCancelled(options.signal);
221
- const cwdStat = await lstatAsync(".", { bigint: true });
222
- throwIfCancelled(options.signal);
223
- if (!cwdStat.isDirectory() || cwdStat.dev !== expectedDev || cwdStat.ino !== expectedIno) {
224
- throw new Error("pinned scaffold directory identity mismatch");
225
- }
226
- await options.afterHandshake?.();
227
- throwIfCancelled(options.signal);
228
- await writeScaffoldFiles(".", request.files, request.force, {
229
- beforePublish: options.beforePublish,
230
- publish: options.publish,
231
- signal: options.signal,
232
- verifyTarget: () => assertTargetIdentity(request.targetAbsolute, expectedDev, expectedIno)
233
- });
234
- }
235
- var WorkerInput = class {
236
- buffer = Buffer.alloc(0);
237
- bytes = 0;
238
- async readLine(signal) {
239
- for (; ; ) {
240
- const newline = this.buffer.indexOf(10);
241
- if (newline >= 0) {
242
- const line = this.buffer.subarray(0, newline);
243
- this.buffer = this.buffer.subarray(newline + 1);
244
- return line.at(-1) === 13 ? line.subarray(0, line.byteLength - 1).toString("utf8") : line.toString("utf8");
245
- }
246
- const value = process.stdin.read();
247
- if (value !== null) {
248
- const chunk = Buffer.isBuffer(value) ? value : Buffer.from(value);
249
- this.bytes += chunk.byteLength;
250
- if (this.bytes > WORKER_INPUT_LIMIT_BYTES) {
251
- throw new Error("scaffold worker input exceeded limit");
252
- }
253
- this.buffer = Buffer.concat([this.buffer, chunk]);
254
- continue;
255
- }
256
- if (process.stdin.readableEnded) {
257
- if (this.buffer.byteLength === 0) throw new Error("scaffold worker input ended early");
258
- const line = this.buffer.toString("utf8");
259
- this.buffer = Buffer.alloc(0);
260
- return line;
261
- }
262
- await once(process.stdin, "readable", { signal });
263
- }
264
- }
265
- };
266
- async function runInitWorkerMain() {
267
- const controller = new AbortController();
268
- const cancel = () => controller.abort();
269
- let timeout = setTimeout(cancel, WORKER_TIMEOUT_MS);
270
- process.once("SIGTERM", cancel);
271
- const input = new WorkerInput();
272
- try {
273
- const request = JSON.parse(await input.readLine(controller.signal));
274
- await runInitWorkerRequest(request, {
275
- signal: controller.signal,
276
- beforePublish: async () => {
277
- throwIfCancelled(controller.signal);
278
- clearTimeout(timeout);
279
- timeout = setTimeout(cancel, WORKER_TIMEOUT_MS);
280
- process.stdout.write(`${WORKER_PREPARED}
281
- `);
282
- const command = await input.readLine(controller.signal);
283
- if (command !== WORKER_COMMIT) throw new Error("scaffold worker cancelled");
284
- throwIfCancelled(controller.signal);
285
- clearTimeout(timeout);
286
- }
287
- });
288
- } finally {
289
- clearTimeout(timeout);
290
- process.removeListener("SIGTERM", cancel);
291
- }
292
- }
293
- function workerRecoveryLine(line) {
294
- try {
295
- const value = JSON.parse(line);
296
- if (typeof value !== "object" || value === null) return void 0;
297
- const record = value;
298
- if (record.ok !== false || Object.keys(record).sort().join(",") !== "ok,recovery") {
299
- return void 0;
300
- }
301
- return safeRecoveryDirectory(record.recovery);
302
- } catch {
303
- return void 0;
304
- }
305
- }
306
- function safeTimeout(value) {
307
- return value !== void 0 && Number.isSafeInteger(value) && value > 0 ? value : WORKER_TIMEOUT_MS;
308
- }
309
- async function invokeInitWorkerProcess(request, options = {}) {
310
- const payload = JSON.stringify(request);
311
- if (Buffer.byteLength(`${payload}
312
- ${WORKER_COMMIT}
313
- `) > WORKER_INPUT_LIMIT_BYTES) {
314
- throw new Error("pinned scaffold worker failed");
315
- }
316
- const workerPath = fileURLToPath(new URL("./init-worker.js", import.meta.url));
317
- const workerExecutable = typeof process.versions.bun === "string" ? process.env.NODE_BINARY ?? "node" : process.execPath;
318
- const workerEnvironment = { ...process.env };
319
- delete workerEnvironment.NODE_OPTIONS;
320
- delete workerEnvironment.NODE_PATH;
321
- const command = options.command ?? [workerExecutable, workerPath];
322
- const startupTimeoutMs = safeTimeout(options.startupTimeoutMs);
323
- const transactionTimeoutMs = safeTimeout(options.transactionTimeoutMs);
324
- await new Promise((resolveWorker, rejectWorker) => {
325
- const child = spawn(command[0], command.slice(1), {
326
- cwd: request.targetAbsolute,
327
- env: workerEnvironment,
328
- stdio: ["pipe", "pipe", "pipe"],
329
- windowsHide: true
330
- });
331
- let stdoutBuffer = "";
332
- let stdoutBytes = 0;
333
- let stderrBytes = 0;
334
- let failed = false;
335
- let settled = false;
336
- let prepared = false;
337
- let succeeded = false;
338
- let recoveryDirectory;
339
- let timer;
340
- const finish = (error) => {
341
- if (settled) return;
342
- settled = true;
343
- clearTimeout(timer);
344
- if (error === void 0) resolveWorker();
345
- else rejectWorker(error);
346
- };
347
- const cancelWorker = () => {
348
- if (settled) return;
349
- failed = true;
350
- if (!child.stdin.destroyed && child.stdin.writable) child.stdin.end(`${WORKER_CANCEL}
351
- `);
352
- child.kill("SIGTERM");
353
- };
354
- const armTimeout = (durationMs) => {
355
- clearTimeout(timer);
356
- timer = setTimeout(cancelWorker, durationMs);
357
- };
358
- const authorizeCommit = async () => {
359
- try {
360
- await options.beforeCommit?.();
361
- } catch {
362
- cancelWorker();
363
- return;
364
- }
365
- if (!failed && !settled && !child.stdin.destroyed && child.stdin.writable) {
366
- clearTimeout(timer);
367
- child.stdin.end(`${WORKER_COMMIT}
368
- `);
369
- }
370
- };
371
- const consumeLine = (line) => {
372
- if (!prepared && line === WORKER_PREPARED) {
373
- prepared = true;
374
- armTimeout(transactionTimeoutMs);
375
- void authorizeCommit();
376
- return;
377
- }
378
- if (prepared && !succeeded && line === WORKER_SUCCESS) {
379
- succeeded = true;
380
- return;
381
- }
382
- const recovery = workerRecoveryLine(line);
383
- if (recovery !== void 0 && recoveryDirectory === void 0) {
384
- recoveryDirectory = recovery;
385
- return;
386
- }
387
- cancelWorker();
388
- };
389
- const consume = (chunk, stream) => {
390
- if (stream === "stdout") {
391
- stdoutBytes += chunk.byteLength;
392
- if (stdoutBytes <= WORKER_OUTPUT_LIMIT_BYTES) {
393
- stdoutBuffer += chunk.toString("utf8");
394
- for (; ; ) {
395
- const newline = stdoutBuffer.indexOf("\n");
396
- if (newline < 0) break;
397
- const line = stdoutBuffer.slice(0, newline);
398
- stdoutBuffer = stdoutBuffer.slice(newline + 1);
399
- consumeLine(line);
400
- }
401
- }
402
- } else {
403
- stderrBytes += chunk.byteLength;
404
- }
405
- if (stdoutBytes > WORKER_OUTPUT_LIMIT_BYTES || stderrBytes > WORKER_OUTPUT_LIMIT_BYTES) {
406
- cancelWorker();
407
- }
408
- };
409
- armTimeout(startupTimeoutMs);
410
- child.stdout.on("data", (chunk) => consume(chunk, "stdout"));
411
- child.stderr.on("data", (chunk) => consume(chunk, "stderr"));
412
- child.stdin.on("error", cancelWorker);
413
- child.on("error", () => finish(new Error("pinned scaffold worker failed")));
414
- child.on("close", (code) => {
415
- if (recoveryDirectory !== void 0) {
416
- finish(new ScaffoldRollbackError(recoveryDirectory));
417
- } else if (!failed && code === 0 && prepared && succeeded && stdoutBuffer.length === 0) {
418
- finish();
419
- } else {
420
- finish(new Error("pinned scaffold worker failed"));
421
- }
422
- });
423
- child.stdin.write(`${payload}
424
- `);
425
- });
426
- }
427
- async function runPinnedInitWorker(out, files, force, options = {}) {
428
- const targetAbsolute = resolve(out);
429
- let directory;
430
- try {
431
- directory = await open(
432
- targetAbsolute,
433
- constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW
434
- );
435
- } catch (error) {
436
- if (["ELOOP", "ENOTDIR"].includes(error.code ?? "")) {
437
- throw new Error("unsafe scaffold destination: symbolic links are not allowed");
438
- }
439
- throw error;
440
- }
441
- try {
442
- const targetStat = await directory.stat({ bigint: true });
443
- if (!targetStat.isDirectory()) {
444
- throw new Error("unsafe scaffold destination: target is not a directory");
445
- }
446
- const request = {
447
- version: 1,
448
- expectedDev: targetStat.dev.toString(),
449
- expectedIno: targetStat.ino.toString(),
450
- targetAbsolute,
451
- files,
452
- force
453
- };
454
- try {
455
- await (options.invokeWorker ?? invokeInitWorkerProcess)(request);
456
- } catch (error) {
457
- if (error instanceof ScaffoldRollbackError) throw error;
458
- throw new Error("pinned scaffold worker failed");
459
- }
460
- } finally {
461
- await directory.close();
462
- }
463
- }
464
- async function writeScaffoldFiles(out, files, force, options = {}) {
465
- const entries = Object.entries(files).map(([name, body]) => ({
466
- body,
467
- destination: join(out, name),
468
- temporary: join(out, `.${name}.kaji-${randomUUID()}.tmp`)
469
- }));
470
- const committed = [];
471
- const backups = [];
472
- const published = [];
473
- let backupDirectory;
474
- try {
475
- throwIfCancelled(options.signal);
476
- for (const entry of entries) {
477
- await writeFile(entry.temporary, entry.body, { encoding: "utf8", flag: "wx" });
478
- throwIfCancelled(options.signal);
479
- }
480
- if (entries.some(({ destination }) => destinationKind(destination) === "symlink")) {
481
- throw new Error("unsafe scaffold destination: symbolic links are not allowed");
482
- }
483
- if (!force && entries.some(({ destination }) => destinationKind(destination) !== "missing")) {
484
- throw Object.assign(new Error("scaffold destination appeared during write"), {
485
- code: "EEXIST"
486
- });
487
- }
488
- if (force) {
489
- const kinds = entries.map((entry) => [entry, destinationKind(entry.destination)]);
490
- if (kinds.some(([, kind]) => kind === "symlink")) {
491
- throw new Error("unsafe scaffold destination: symbolic links are not allowed");
492
- }
493
- if (kinds.some(([, kind]) => kind === "other")) {
494
- throw new Error("unsafe scaffold destination: generated paths must be regular files");
495
- }
496
- backupDirectory = await mkdtemp(join(out, ".kaji-scaffold-backup-"));
497
- for (const [entry] of kinds) {
498
- const currentKind = destinationKind(entry.destination);
499
- if (currentKind === "symlink" || currentKind === "other") {
500
- throw new Error("unsafe scaffold destination: generated paths must be regular files");
501
- }
502
- if (currentKind === "missing") continue;
503
- const sourceStat = await lstatAsync(entry.destination, { bigint: true });
504
- if (!sourceStat.isFile() || sourceStat.isSymbolicLink()) {
505
- throw new Error("unsafe scaffold destination: generated paths must be regular files");
506
- }
507
- const backup = join(backupDirectory, basename(entry.destination));
508
- await rename(entry.destination, backup);
509
- backups.push({ destination: entry.destination, backup });
510
- throwIfCancelled(options.signal);
511
- const backupStat = await lstatAsync(backup, { bigint: true });
512
- throwIfCancelled(options.signal);
513
- if (!backupStat.isFile() || backupStat.isSymbolicLink() || backupStat.dev !== sourceStat.dev || backupStat.ino !== sourceStat.ino) {
514
- throw new Error("unsafe scaffold destination: destination changed during backup");
515
- }
516
- }
517
- await options.beforePublish?.();
518
- throwIfCancelled(options.signal);
519
- const publish = options.publish ?? rename;
520
- for (const entry of entries) {
521
- const temporaryStat = await lstatAsync(entry.temporary, { bigint: true });
522
- throwIfCancelled(options.signal);
523
- published.push({
524
- destination: entry.destination,
525
- dev: temporaryStat.dev,
526
- ino: temporaryStat.ino
527
- });
528
- await publish(entry.temporary, entry.destination);
529
- throwIfCancelled(options.signal);
530
- const destinationStat = await lstatAsync(entry.destination, { bigint: true });
531
- throwIfCancelled(options.signal);
532
- if (!destinationStat.isFile() || destinationStat.isSymbolicLink() || destinationStat.dev !== temporaryStat.dev || destinationStat.ino !== temporaryStat.ino) {
533
- throw new Error("unsafe scaffold destination: destination changed during publication");
534
- }
535
- }
536
- await options.verifyTarget?.();
537
- throwIfCancelled(options.signal);
538
- await rm(backupDirectory, { recursive: true, force: true });
539
- backupDirectory = void 0;
540
- } else {
541
- await options.beforePublish?.();
542
- throwIfCancelled(options.signal);
543
- for (const entry of entries) {
544
- const sourceStat = await lstatAsync(entry.temporary, { bigint: true });
545
- throwIfCancelled(options.signal);
546
- await link(entry.temporary, entry.destination);
547
- committed.push({
548
- destination: entry.destination,
549
- dev: sourceStat.dev,
550
- ino: sourceStat.ino
551
- });
552
- throwIfCancelled(options.signal);
553
- }
554
- await options.verifyTarget?.();
555
- throwIfCancelled(options.signal);
556
- }
557
- } catch (error) {
558
- const rollbackErrors = [];
559
- if (force) {
560
- for (const item of published.reverse()) {
561
- try {
562
- if (destinationKind(item.destination) === "missing") continue;
563
- const destinationStat = await lstatAsync(item.destination, { bigint: true });
564
- if (!destinationStat.isFile() || destinationStat.isSymbolicLink() || destinationStat.dev !== item.dev || destinationStat.ino !== item.ino) {
565
- throw new Error("scaffold destination changed during rollback");
566
- }
567
- await rm(item.destination, { force: true });
568
- } catch (rollbackError) {
569
- rollbackErrors.push(rollbackError);
570
- }
571
- }
572
- for (const item of backups.reverse()) {
573
- try {
574
- if (destinationKind(item.destination) !== "missing") {
575
- throw new Error("scaffold destination changed before backup restore");
576
- }
577
- await rename(item.backup, item.destination);
578
- } catch (rollbackError) {
579
- rollbackErrors.push(rollbackError);
580
- }
581
- }
582
- if (rollbackErrors.length === 0 && backupDirectory !== void 0) {
583
- try {
584
- await rm(backupDirectory, { recursive: true, force: true });
585
- backupDirectory = void 0;
586
- } catch (rollbackError) {
587
- rollbackErrors.push(rollbackError);
588
- }
589
- }
590
- } else {
591
- for (const item of committed) {
592
- try {
593
- const destinationStat = await lstatAsync(item.destination, { bigint: true });
594
- if (destinationStat.isFile() && !destinationStat.isSymbolicLink() && destinationStat.dev === item.dev && destinationStat.ino === item.ino) {
595
- await rm(item.destination, { force: true });
596
- }
597
- } catch (cleanupError) {
598
- if (cleanupError.code !== "ENOENT") {
599
- rollbackErrors.push(cleanupError);
600
- }
601
- }
602
- }
603
- }
604
- if (rollbackErrors.length > 0) {
605
- if (backupDirectory !== void 0) {
606
- throw new ScaffoldRollbackError(
607
- basename(backupDirectory),
608
- new AggregateError([error, ...rollbackErrors])
609
- );
610
- }
611
- throw new AggregateError([error, ...rollbackErrors], "scaffold publication rollback failed");
612
- }
613
- throw error;
614
- } finally {
615
- for (const entry of entries) await rm(entry.temporary, { force: true }).catch(() => void 0);
616
- }
617
- }
618
- async function init(rest, opts) {
619
- const log = opts.log ?? ((message) => console.log(message));
620
- const err = opts.err ?? ((message) => console.error(message));
621
- let args;
622
- try {
623
- args = parseArgs(rest);
624
- } catch (error) {
625
- if (error instanceof CliArgError) {
626
- err(`Error: ${error.message}`);
627
- err("usage: kaji init [path] [--provider mock|openai|anthropic] [--yes] [--force]");
628
- return 2;
629
- }
630
- throw error;
631
- }
632
- let files;
633
- try {
634
- files = scaffoldFiles(args.provider);
635
- } catch {
636
- err("kaji init could not read installed package metadata");
637
- return 1;
638
- }
639
- const destinations = Object.keys(files).map((name) => join(args.out, name));
640
- const targetKind = destinationKind(args.out);
641
- const kinds = destinations.map((path) => [path, destinationKind(path)]);
642
- if (targetKind === "symlink" || kinds.some(([, kind]) => kind === "symlink")) {
643
- err("unsafe scaffold destination: symbolic links are not allowed");
644
- return 1;
645
- }
646
- const conflicts = kinds.filter(([, kind]) => kind !== "missing").map(([path]) => path);
647
- if (conflicts.length > 0 && !args.force) {
648
- err(
649
- `refusing to overwrite without --force: ${conflicts.map((path) => path.split("/").at(-1)).join(", ")}`
650
- );
651
- return 1;
652
- }
653
- try {
654
- if (!existsSync(args.out)) mkdirSync(args.out, { recursive: true });
655
- await (opts.initWorkerRunner ?? runPinnedInitWorker)(args.out, files, args.force);
656
- for (const name of Object.keys(files)) log(`wrote ${join(args.out, name)}`);
657
- } catch (error) {
658
- if (error instanceof ScaffoldRollbackError) {
659
- err(
660
- `kaji init failed while writing the scaffold; original preserved in target directory as ${error.recoveryDirectory}`
661
- );
662
- } else if (error instanceof Error && error.message.includes("unsafe scaffold destination")) {
663
- err(error.message);
664
- } else if (error.code === "EEXIST") {
665
- err("refusing to overwrite without --force: destination changed during write");
666
- } else {
667
- err("kaji init failed while writing the scaffold");
668
- }
669
- return 1;
670
- }
671
- log("");
672
- log(`Next: cd ${args.out} && npm install && npm start (or bun install && bun start)`);
673
- return 0;
674
- }
675
-
676
- export {
677
- scaffoldRecoveryDirectory,
678
- runInitWorkerMain,
679
- init
680
- };
681
- //# sourceMappingURL=chunk-SEBX54TR.js.map