@treeseed/sdk 0.4.13 → 0.5.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 (82) hide show
  1. package/dist/control-plane-client.d.ts +60 -1
  2. package/dist/control-plane-client.js +59 -0
  3. package/dist/control-plane.d.ts +1 -1
  4. package/dist/control-plane.js +11 -4
  5. package/dist/d1-store.d.ts +58 -0
  6. package/dist/d1-store.js +64 -0
  7. package/dist/dispatch.js +6 -0
  8. package/dist/graph/schema.js +4 -0
  9. package/dist/index.d.ts +5 -1
  10. package/dist/index.js +32 -0
  11. package/dist/knowledge-coop.d.ts +223 -0
  12. package/dist/knowledge-coop.js +82 -0
  13. package/dist/model-registry.js +79 -0
  14. package/dist/operations/providers/default.js +126 -7
  15. package/dist/operations/services/config-runtime.d.ts +102 -24
  16. package/dist/operations/services/config-runtime.js +896 -160
  17. package/dist/operations/services/deploy.d.ts +223 -15
  18. package/dist/operations/services/deploy.js +626 -55
  19. package/dist/operations/services/github-automation.d.ts +60 -0
  20. package/dist/operations/services/github-automation.js +138 -0
  21. package/dist/operations/services/key-agent.d.ts +118 -0
  22. package/dist/operations/services/key-agent.js +476 -0
  23. package/dist/operations/services/knowledge-coop-launch.d.ts +90 -0
  24. package/dist/operations/services/knowledge-coop-launch.js +753 -0
  25. package/dist/operations/services/knowledge-coop-packaging.d.ts +59 -0
  26. package/dist/operations/services/knowledge-coop-packaging.js +234 -0
  27. package/dist/operations/services/local-dev.d.ts +0 -1
  28. package/dist/operations/services/local-dev.js +1 -14
  29. package/dist/operations/services/project-platform.d.ts +42 -182
  30. package/dist/operations/services/project-platform.js +162 -59
  31. package/dist/operations/services/railway-deploy.d.ts +1 -0
  32. package/dist/operations/services/railway-deploy.js +31 -13
  33. package/dist/operations/services/runtime-tools.d.ts +52 -5
  34. package/dist/operations/services/runtime-tools.js +186 -26
  35. package/dist/operations/services/watch-dev.js +2 -4
  36. package/dist/operations/services/workspace-preflight.d.ts +4 -4
  37. package/dist/operations/services/workspace-preflight.js +22 -20
  38. package/dist/operations-registry.js +7 -2
  39. package/dist/platform/contracts.d.ts +39 -3
  40. package/dist/platform/deploy-config.d.ts +12 -1
  41. package/dist/platform/deploy-config.js +214 -15
  42. package/dist/platform/deploy-runtime.d.ts +1 -0
  43. package/dist/platform/deploy-runtime.js +10 -2
  44. package/dist/platform/env.yaml +93 -61
  45. package/dist/platform/environment.d.ts +13 -2
  46. package/dist/platform/environment.js +90 -20
  47. package/dist/platform/plugins/constants.d.ts +1 -0
  48. package/dist/platform/plugins/constants.js +7 -6
  49. package/dist/platform/tenant/runtime-config.js +8 -1
  50. package/dist/platform/tenant-config.js +4 -0
  51. package/dist/platform/utils/site-config-schema.js +18 -0
  52. package/dist/plugin-default.js +2 -2
  53. package/dist/scripts/key-agent.js +165 -0
  54. package/dist/scripts/tenant-build.js +4 -1
  55. package/dist/scripts/tenant-check.js +4 -1
  56. package/dist/scripts/tenant-deploy.js +43 -4
  57. package/dist/scripts/tenant-dev.js +0 -1
  58. package/dist/sdk-types.d.ts +2 -2
  59. package/dist/sdk-types.js +2 -0
  60. package/dist/sdk.d.ts +13 -0
  61. package/dist/sdk.js +40 -0
  62. package/dist/stores/knowledge-coop-store.d.ts +56 -0
  63. package/dist/stores/knowledge-coop-store.js +482 -0
  64. package/dist/treeseed/template-catalog/templates/starter-basic/template/package.json +6 -2
  65. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/api/server.js +4 -0
  66. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/config.yaml +25 -0
  67. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/decisions/adopt-initial-proposal-loop.mdx +22 -0
  68. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/people/starter-steward.mdx +11 -0
  69. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/proposals/establish-initial-proposal-loop.mdx +17 -0
  70. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/manifest.yaml +17 -10
  71. package/dist/treeseed/template-catalog/templates/starter-basic/template/treeseed.site.yaml +69 -7
  72. package/dist/treeseed/template-catalog/templates/starter-basic/template.config.json +1 -0
  73. package/dist/workflow/operations.d.ts +98 -0
  74. package/dist/workflow/operations.js +229 -7
  75. package/dist/workflow-state.d.ts +54 -2
  76. package/dist/workflow-state.js +170 -24
  77. package/dist/workflow-support.d.ts +1 -1
  78. package/dist/workflow-support.js +32 -2
  79. package/dist/workflow.d.ts +29 -0
  80. package/package.json +1 -1
  81. package/templates/github/deploy.workflow.yml +11 -1
  82. package/dist/scripts/sync-dev-vars.js +0 -6
@@ -0,0 +1,476 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { createCipheriv, createDecipheriv, randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
3
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import { dirname, join, resolve } from "node:path";
6
+ const TRESEED_MACHINE_KEY_PASSPHRASE_ENV = "TREESEED_KEY_PASSPHRASE";
7
+ const TRESEED_KEY_AGENT_IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
8
+ const TREESEED_KEY_AGENT_IDLE_TIMEOUT_MS = TRESEED_KEY_AGENT_IDLE_TIMEOUT_MS;
9
+ const TRESEED_WRAPPED_MACHINE_KEY_VERSION = 2;
10
+ const TRESEED_WRAPPED_MACHINE_KEY_KIND = "treeseed-wrapped-machine-key";
11
+ const TREESEED_MACHINE_KEY_PASSPHRASE_ENV = TRESEED_MACHINE_KEY_PASSPHRASE_ENV;
12
+ const KEY_AGENT_SOCKET_RELATIVE_PATH = ".treeseed/run/key-agent.sock";
13
+ const WRAPPED_KEY_KDF_PARAMS = {
14
+ N: 1 << 14,
15
+ r: 8,
16
+ p: 1,
17
+ keyLength: 32
18
+ };
19
+ class TreeseedKeyAgentError extends Error {
20
+ code;
21
+ details;
22
+ constructor(code, message, details) {
23
+ super(message);
24
+ this.name = "TreeseedKeyAgentError";
25
+ this.code = code;
26
+ this.details = details;
27
+ }
28
+ }
29
+ function nowIso() {
30
+ return (/* @__PURE__ */ new Date()).toISOString();
31
+ }
32
+ function ensureParent(filePath) {
33
+ mkdirSync(dirname(filePath), { recursive: true });
34
+ }
35
+ function ensureFifo(filePath) {
36
+ ensureParent(filePath);
37
+ if (existsSync(filePath)) {
38
+ const stats = statSync(filePath);
39
+ if (stats.isFIFO()) {
40
+ return;
41
+ }
42
+ rmSync(filePath, { force: true });
43
+ }
44
+ const result = spawnSync("mkfifo", [filePath], { stdio: "pipe", encoding: "utf8" });
45
+ if (result.status !== 0) {
46
+ throw new Error(result.stderr?.trim() || `Unable to create FIFO ${filePath}.`);
47
+ }
48
+ }
49
+ function pidFilePath(socketPath) {
50
+ return `${socketPath}.pid`;
51
+ }
52
+ function responseFifoPath(socketPath) {
53
+ return join(mkdtempSync(resolve(dirname(socketPath), "key-agent-response-")), "response.fifo");
54
+ }
55
+ function writePidFile(socketPath) {
56
+ writeFileSync(pidFilePath(socketPath), `${process.pid}
57
+ `, { mode: 384 });
58
+ }
59
+ function clearPidFile(socketPath) {
60
+ rmSync(pidFilePath(socketPath), { force: true });
61
+ }
62
+ function readAgentPid(socketPath) {
63
+ const pidPath = pidFilePath(socketPath);
64
+ if (!existsSync(pidPath)) {
65
+ return null;
66
+ }
67
+ const raw = readFileSync(pidPath, "utf8").trim();
68
+ const pid = Number.parseInt(raw, 10);
69
+ return Number.isFinite(pid) ? pid : null;
70
+ }
71
+ function agentProcessAlive(socketPath) {
72
+ const pid = readAgentPid(socketPath);
73
+ if (!pid) {
74
+ return false;
75
+ }
76
+ try {
77
+ process.kill(pid, 0);
78
+ return true;
79
+ } catch {
80
+ return false;
81
+ }
82
+ }
83
+ function createFingerprint(machineKey) {
84
+ return machineKey.subarray(0, 6).toString("base64");
85
+ }
86
+ function deriveWrappingKey(passphrase, salt, keyLength) {
87
+ return scryptSync(passphrase.normalize("NFKC"), salt, keyLength, {
88
+ N: WRAPPED_KEY_KDF_PARAMS.N,
89
+ r: WRAPPED_KEY_KDF_PARAMS.r,
90
+ p: WRAPPED_KEY_KDF_PARAMS.p
91
+ });
92
+ }
93
+ function wrapMachineKey(machineKey, passphrase) {
94
+ const salt = randomBytes(16);
95
+ const iv = randomBytes(12);
96
+ const wrappingKey = deriveWrappingKey(passphrase, salt, WRAPPED_KEY_KDF_PARAMS.keyLength);
97
+ const cipher = createCipheriv("aes-256-gcm", wrappingKey, iv);
98
+ const ciphertext = Buffer.concat([cipher.update(machineKey), cipher.final()]);
99
+ const tag = cipher.getAuthTag();
100
+ return {
101
+ version: TRESEED_WRAPPED_MACHINE_KEY_VERSION,
102
+ kind: TRESEED_WRAPPED_MACHINE_KEY_KIND,
103
+ createdAt: nowIso(),
104
+ updatedAt: nowIso(),
105
+ kdf: {
106
+ algorithm: "scrypt",
107
+ salt: salt.toString("base64"),
108
+ N: WRAPPED_KEY_KDF_PARAMS.N,
109
+ r: WRAPPED_KEY_KDF_PARAMS.r,
110
+ p: WRAPPED_KEY_KDF_PARAMS.p,
111
+ keyLength: WRAPPED_KEY_KDF_PARAMS.keyLength
112
+ },
113
+ wrappedKey: {
114
+ algorithm: "aes-256-gcm",
115
+ iv: iv.toString("base64"),
116
+ tag: tag.toString("base64"),
117
+ ciphertext: ciphertext.toString("base64")
118
+ },
119
+ fingerprint: createFingerprint(machineKey)
120
+ };
121
+ }
122
+ function unwrapMachineKey(payload, passphrase) {
123
+ try {
124
+ const salt = Buffer.from(payload.kdf.salt, "base64");
125
+ const wrappingKey = deriveWrappingKey(passphrase, salt, payload.kdf.keyLength);
126
+ const decipher = createDecipheriv("aes-256-gcm", wrappingKey, Buffer.from(payload.wrappedKey.iv, "base64"));
127
+ decipher.setAuthTag(Buffer.from(payload.wrappedKey.tag, "base64"));
128
+ return Buffer.concat([
129
+ decipher.update(Buffer.from(payload.wrappedKey.ciphertext, "base64")),
130
+ decipher.final()
131
+ ]);
132
+ } catch (error) {
133
+ throw new TreeseedKeyAgentError(
134
+ "unlock_failed",
135
+ "Unable to unlock the Treeseed machine key. The passphrase is incorrect or the wrapped key file is corrupt.",
136
+ { cause: error instanceof Error ? error.message : String(error) }
137
+ );
138
+ }
139
+ }
140
+ function isWrappedMachineKeyPayload(value) {
141
+ if (!value || typeof value !== "object") {
142
+ return false;
143
+ }
144
+ const record = value;
145
+ return record.kind === TRESEED_WRAPPED_MACHINE_KEY_KIND && record.version === TRESEED_WRAPPED_MACHINE_KEY_VERSION;
146
+ }
147
+ function readWrappedMachineKeyFile(keyPath) {
148
+ if (!existsSync(keyPath)) {
149
+ return {
150
+ exists: false,
151
+ wrapped: null,
152
+ plaintextLegacy: null,
153
+ migrationRequired: false
154
+ };
155
+ }
156
+ const raw = readFileSync(keyPath, "utf8").trim();
157
+ if (!raw) {
158
+ return {
159
+ exists: false,
160
+ wrapped: null,
161
+ plaintextLegacy: null,
162
+ migrationRequired: false
163
+ };
164
+ }
165
+ try {
166
+ const parsed = JSON.parse(raw);
167
+ if (isWrappedMachineKeyPayload(parsed)) {
168
+ return {
169
+ exists: true,
170
+ wrapped: parsed,
171
+ plaintextLegacy: null,
172
+ migrationRequired: false
173
+ };
174
+ }
175
+ } catch {
176
+ }
177
+ try {
178
+ return {
179
+ exists: true,
180
+ wrapped: null,
181
+ plaintextLegacy: Buffer.from(raw, "base64"),
182
+ migrationRequired: true
183
+ };
184
+ } catch {
185
+ throw new TreeseedKeyAgentError(
186
+ "corrupt_wrapped_key",
187
+ "Unable to parse the Treeseed machine key file.",
188
+ { keyPath }
189
+ );
190
+ }
191
+ }
192
+ function writeWrappedMachineKeyFile(keyPath, payload) {
193
+ ensureParent(keyPath);
194
+ writeFileSync(keyPath, `${JSON.stringify(payload, null, 2)}
195
+ `, { mode: 384 });
196
+ }
197
+ function replaceWrappedMachineKey(keyPath, machineKey, passphrase) {
198
+ const payload = wrapMachineKey(machineKey, passphrase);
199
+ writeWrappedMachineKeyFile(keyPath, payload);
200
+ return payload;
201
+ }
202
+ function getTreeseedKeyAgentPaths() {
203
+ const homeRoot = process.env.HOME && process.env.HOME.trim().length > 0 ? process.env.HOME : homedir();
204
+ return {
205
+ homeRoot,
206
+ socketPath: resolve(homeRoot, KEY_AGENT_SOCKET_RELATIVE_PATH)
207
+ };
208
+ }
209
+ function createStatus(command, session) {
210
+ const wrapped = readWrappedMachineKeyFile(command.keyPath);
211
+ const idleRemainingMs = session.machineKey ? Math.max(0, session.idleTimeoutMs - (Date.now() - session.lastTouchedAt)) : 0;
212
+ return {
213
+ running: true,
214
+ unlocked: Boolean(session.machineKey) && idleRemainingMs > 0,
215
+ wrappedKeyPresent: wrapped.exists && Boolean(wrapped.wrapped),
216
+ migrationRequired: wrapped.migrationRequired,
217
+ keyPath: command.keyPath,
218
+ socketPath: command.socketPath,
219
+ idleTimeoutMs: session.idleTimeoutMs,
220
+ idleRemainingMs
221
+ };
222
+ }
223
+ function maybeExpireSession(session) {
224
+ if (!session.machineKey) {
225
+ return;
226
+ }
227
+ if (Date.now() - session.lastTouchedAt >= session.idleTimeoutMs) {
228
+ session.machineKey = null;
229
+ }
230
+ }
231
+ function readLegacyProjectMachineKey(legacyKeyPath) {
232
+ if (!existsSync(legacyKeyPath)) {
233
+ return null;
234
+ }
235
+ try {
236
+ return Buffer.from(readFileSync(legacyKeyPath, "utf8").trim(), "base64");
237
+ } catch {
238
+ return null;
239
+ }
240
+ }
241
+ function unwrapOrProvisionMachineKey(command) {
242
+ const wrapped = readWrappedMachineKeyFile(command.keyPath);
243
+ if (wrapped.wrapped) {
244
+ return unwrapMachineKey(wrapped.wrapped, command.passphrase);
245
+ }
246
+ if (wrapped.plaintextLegacy) {
247
+ if (!command.allowMigration) {
248
+ throw new TreeseedKeyAgentError(
249
+ "wrapped_key_migration_required",
250
+ "The Treeseed machine key is still stored in the legacy plaintext format. Run a migration or unlock interactively to wrap it first.",
251
+ { keyPath: command.keyPath }
252
+ );
253
+ }
254
+ replaceWrappedMachineKey(command.keyPath, wrapped.plaintextLegacy, command.passphrase);
255
+ return wrapped.plaintextLegacy;
256
+ }
257
+ if (!command.createIfMissing) {
258
+ throw new TreeseedKeyAgentError(
259
+ "wrapped_key_missing",
260
+ "No wrapped Treeseed machine key exists yet. Create one by unlocking interactively or with a startup passphrase.",
261
+ { keyPath: command.keyPath }
262
+ );
263
+ }
264
+ const machineKey = randomBytes(32);
265
+ replaceWrappedMachineKey(command.keyPath, machineKey, command.passphrase);
266
+ return machineKey;
267
+ }
268
+ function ok(response = {}) {
269
+ return { ok: true, ...response };
270
+ }
271
+ function fail(error, command) {
272
+ if (error instanceof TreeseedKeyAgentError) {
273
+ return {
274
+ ok: false,
275
+ code: error.code,
276
+ message: error.message,
277
+ status: {
278
+ running: true,
279
+ unlocked: false,
280
+ wrappedKeyPresent: readWrappedMachineKeyFile(command.keyPath).wrapped !== null,
281
+ migrationRequired: readWrappedMachineKeyFile(command.keyPath).migrationRequired,
282
+ keyPath: command.keyPath,
283
+ socketPath: command.socketPath,
284
+ idleTimeoutMs: command.idleTimeoutMs,
285
+ idleRemainingMs: 0
286
+ }
287
+ };
288
+ }
289
+ return {
290
+ ok: false,
291
+ code: "unlock_failed",
292
+ message: error instanceof Error ? error.message : String(error),
293
+ status: {
294
+ running: true,
295
+ unlocked: false,
296
+ wrappedKeyPresent: readWrappedMachineKeyFile(command.keyPath).wrapped !== null,
297
+ migrationRequired: readWrappedMachineKeyFile(command.keyPath).migrationRequired,
298
+ keyPath: command.keyPath,
299
+ socketPath: command.socketPath,
300
+ idleTimeoutMs: command.idleTimeoutMs,
301
+ idleRemainingMs: 0
302
+ }
303
+ };
304
+ }
305
+ function handleTreeseedKeyAgentCommand(command, session) {
306
+ maybeExpireSession(session);
307
+ if (command.command === "status") {
308
+ return ok({ status: createStatus(command, session) });
309
+ }
310
+ if (command.command === "lock") {
311
+ session.machineKey = null;
312
+ return ok({ status: createStatus(command, session) });
313
+ }
314
+ if (command.command === "touch") {
315
+ if (!session.machineKey) {
316
+ return fail(new TreeseedKeyAgentError("locked", "Treeseed secret session is locked."), command);
317
+ }
318
+ session.lastTouchedAt = Date.now();
319
+ return ok({ status: createStatus(command, session) });
320
+ }
321
+ if (command.command === "unlock") {
322
+ try {
323
+ session.machineKey = unwrapOrProvisionMachineKey(command);
324
+ session.lastTouchedAt = Date.now();
325
+ session.idleTimeoutMs = command.idleTimeoutMs;
326
+ return ok({ status: createStatus(command, session) });
327
+ } catch (error) {
328
+ return fail(error, command);
329
+ }
330
+ }
331
+ if (!session.machineKey) {
332
+ return fail(new TreeseedKeyAgentError("locked", "Treeseed secret session is locked."), command);
333
+ }
334
+ session.lastTouchedAt = Date.now();
335
+ return ok({
336
+ status: createStatus(command, session),
337
+ machineKey: session.machineKey.toString("base64")
338
+ });
339
+ }
340
+ async function requestTreeseedKeyAgent(command) {
341
+ if (!agentProcessAlive(command.socketPath) || !existsSync(command.socketPath)) {
342
+ throw new TreeseedKeyAgentError("daemon_unavailable", "Treeseed key-agent is not running.");
343
+ }
344
+ const responsePath = responseFifoPath(command.socketPath);
345
+ ensureFifo(responsePath);
346
+ try {
347
+ const responsePromise = Promise.resolve().then(() => readFileSync(responsePath, "utf8"));
348
+ writeFileSync(command.socketPath, `${JSON.stringify({ ...command, responsePath })}
349
+ `, "utf8");
350
+ return JSON.parse((await responsePromise).trim() || "{}");
351
+ } finally {
352
+ rmSync(dirname(responsePath), { recursive: true, force: true });
353
+ }
354
+ }
355
+ async function socketAlreadyServed(socketPath) {
356
+ return agentProcessAlive(socketPath) && existsSync(socketPath);
357
+ }
358
+ async function removeStaleSocket(socketPath) {
359
+ if (!existsSync(socketPath)) {
360
+ return true;
361
+ }
362
+ try {
363
+ if (await socketAlreadyServed(socketPath)) {
364
+ return false;
365
+ }
366
+ rmSync(socketPath, { force: true });
367
+ clearPidFile(socketPath);
368
+ return true;
369
+ } catch {
370
+ rmSync(socketPath, { force: true });
371
+ clearPidFile(socketPath);
372
+ return true;
373
+ }
374
+ }
375
+ async function startTreeseedKeyAgentServer(options) {
376
+ const socketPath = options.socketPath ?? getTreeseedKeyAgentPaths().socketPath;
377
+ const canStart = await removeStaleSocket(socketPath);
378
+ if (!canStart) {
379
+ return;
380
+ }
381
+ ensureFifo(socketPath);
382
+ writePidFile(socketPath);
383
+ const session = {
384
+ machineKey: null,
385
+ lastTouchedAt: 0,
386
+ idleTimeoutMs: options.idleTimeoutMs ?? TRESEED_KEY_AGENT_IDLE_TIMEOUT_MS
387
+ };
388
+ process.on("exit", () => {
389
+ try {
390
+ rmSync(socketPath, { force: true });
391
+ clearPidFile(socketPath);
392
+ } catch {
393
+ }
394
+ });
395
+ for (; ; ) {
396
+ const line = readFileSync(socketPath, "utf8").trim();
397
+ if (!line) {
398
+ continue;
399
+ }
400
+ try {
401
+ const parsed = JSON.parse(line);
402
+ const response = handleTreeseedKeyAgentCommand(parsed, session);
403
+ if (parsed.responsePath) {
404
+ writeFileSync(parsed.responsePath, `${JSON.stringify(response)}
405
+ `, "utf8");
406
+ }
407
+ } catch (error) {
408
+ const fallback = fail(error, {
409
+ command: "status",
410
+ keyPath: options.keyPath,
411
+ socketPath,
412
+ idleTimeoutMs: options.idleTimeoutMs ?? TRESEED_KEY_AGENT_IDLE_TIMEOUT_MS
413
+ });
414
+ try {
415
+ const parsed = JSON.parse(line);
416
+ if (parsed.responsePath) {
417
+ writeFileSync(parsed.responsePath, `${JSON.stringify(fallback)}
418
+ `, "utf8");
419
+ }
420
+ } catch {
421
+ }
422
+ }
423
+ }
424
+ }
425
+ function assertTreeseedKeyAgentResponse(response, fallback = "Treeseed secret session request failed.") {
426
+ if (response.ok) {
427
+ return response;
428
+ }
429
+ throw new TreeseedKeyAgentError(
430
+ response.code ?? "unlock_failed",
431
+ response.message ?? fallback,
432
+ response.status ? { status: response.status } : void 0
433
+ );
434
+ }
435
+ function rotateWrappedMachineKeyPassphrase(keyPath, machineKey, passphrase) {
436
+ return replaceWrappedMachineKey(keyPath, machineKey, passphrase);
437
+ }
438
+ function migrateLegacyProjectMachineKeyToWrapped(keyPath, legacyKeyPath, passphrase) {
439
+ const legacyProjectKey = readLegacyProjectMachineKey(legacyKeyPath);
440
+ if (!legacyProjectKey) {
441
+ throw new TreeseedKeyAgentError(
442
+ "wrapped_key_migration_required",
443
+ "No legacy project machine key is available to migrate.",
444
+ { legacyKeyPath }
445
+ );
446
+ }
447
+ const wrapped = replaceWrappedMachineKey(keyPath, legacyProjectKey, passphrase);
448
+ if (legacyKeyPath !== keyPath) {
449
+ rmSync(legacyKeyPath, { force: true });
450
+ }
451
+ return wrapped;
452
+ }
453
+ function machineKeysEqual(left, right) {
454
+ return left.length === right.length && timingSafeEqual(left, right);
455
+ }
456
+ export {
457
+ TREESEED_KEY_AGENT_IDLE_TIMEOUT_MS,
458
+ TREESEED_MACHINE_KEY_PASSPHRASE_ENV,
459
+ TRESEED_KEY_AGENT_IDLE_TIMEOUT_MS,
460
+ TRESEED_MACHINE_KEY_PASSPHRASE_ENV,
461
+ TRESEED_WRAPPED_MACHINE_KEY_KIND,
462
+ TRESEED_WRAPPED_MACHINE_KEY_VERSION,
463
+ TreeseedKeyAgentError,
464
+ assertTreeseedKeyAgentResponse,
465
+ getTreeseedKeyAgentPaths,
466
+ handleTreeseedKeyAgentCommand,
467
+ machineKeysEqual,
468
+ migrateLegacyProjectMachineKeyToWrapped,
469
+ readWrappedMachineKeyFile,
470
+ replaceWrappedMachineKey,
471
+ requestTreeseedKeyAgent,
472
+ rotateWrappedMachineKeyPassphrase,
473
+ startTreeseedKeyAgentServer,
474
+ unwrapMachineKey,
475
+ writeWrappedMachineKeyFile
476
+ };
@@ -0,0 +1,90 @@
1
+ import { checkTreeseedProviderConnections } from './config-runtime.ts';
2
+ import { provisionCloudflareResources, verifyProvisionedCloudflareResources } from './deploy.ts';
3
+ import { configuredRailwayServices, deployRailwayService, ensureRailwayScheduledJobs, verifyRailwayScheduledJobs } from './railway-deploy.ts';
4
+ import { buildKnowledgeCoopKnowledgePackPackage, buildKnowledgeCoopTemplatePackage } from './knowledge-coop-packaging.ts';
5
+ export type KnowledgeCoopLaunchFailurePhase = 'repo_provision_failed' | 'content_bootstrap_failed' | 'workflow_bootstrap_failed' | 'hosting_registration_failed' | 'runtime_connection_failed';
6
+ export interface KnowledgeCoopManagedLaunchInput {
7
+ projectId: string;
8
+ teamId: string;
9
+ teamSlug?: string | null;
10
+ projectSlug: string;
11
+ projectName: string;
12
+ summary?: string | null;
13
+ sourceKind: 'blank' | 'template' | 'knowledge_pack';
14
+ sourceRef?: string | null;
15
+ hostingMode?: 'managed' | 'hybrid' | 'self_hosted';
16
+ publicSite?: boolean;
17
+ repoOwner?: string | null;
18
+ repoVisibility?: 'private' | 'public' | 'internal';
19
+ marketBaseUrl?: string | null;
20
+ projectApiBaseUrl?: string | null;
21
+ contactEmail?: string | null;
22
+ enableDefaultAgents?: boolean;
23
+ preserveWorkingTree?: boolean;
24
+ }
25
+ export interface KnowledgeCoopLaunchPhaseRecord {
26
+ phase: string;
27
+ status: 'running' | 'completed' | 'failed';
28
+ detail: string;
29
+ timestamp: string;
30
+ }
31
+ export interface KnowledgeCoopManagedLaunchResult {
32
+ workingRoot: string;
33
+ repository: {
34
+ slug: string;
35
+ owner: string;
36
+ name: string;
37
+ url: string;
38
+ defaultBranch: string;
39
+ stagingBranch: string | null;
40
+ visibility: 'private' | 'public' | 'internal';
41
+ };
42
+ workflows: {
43
+ repository: string | null;
44
+ workflows: Array<{
45
+ workflowPath: string;
46
+ changed: boolean;
47
+ workingDirectory?: string;
48
+ mode?: string;
49
+ }>;
50
+ secrets: {
51
+ existing: string[];
52
+ created: string[];
53
+ };
54
+ variables: {
55
+ existing: string[];
56
+ created: string[];
57
+ };
58
+ };
59
+ cloudflare: {
60
+ staging: ReturnType<typeof provisionCloudflareResources>;
61
+ prod: ReturnType<typeof provisionCloudflareResources>;
62
+ verification: ReturnType<typeof verifyProvisionedCloudflareResources>;
63
+ };
64
+ railway: {
65
+ services: ReturnType<typeof configuredRailwayServices>;
66
+ deployments: ReturnType<typeof deployRailwayService>[];
67
+ schedules: Awaited<ReturnType<typeof ensureRailwayScheduledJobs>>;
68
+ verification: Awaited<ReturnType<typeof verifyRailwayScheduledJobs>>;
69
+ };
70
+ projectApiBaseUrl: string;
71
+ projectSiteUrl: string;
72
+ projectMetadata: Record<string, unknown>;
73
+ defaultWorkstream: Record<string, unknown>;
74
+ phases: KnowledgeCoopLaunchPhaseRecord[];
75
+ templatePackage: ReturnType<typeof buildKnowledgeCoopTemplatePackage>;
76
+ knowledgePackPackage: ReturnType<typeof buildKnowledgeCoopKnowledgePackPackage>;
77
+ }
78
+ export interface KnowledgeCoopLaunchPreflightReport {
79
+ ok: boolean;
80
+ missingConfig: string[];
81
+ providerChecks: ReturnType<typeof checkTreeseedProviderConnections>;
82
+ commands: {
83
+ git: boolean;
84
+ gh: boolean;
85
+ wrangler: boolean;
86
+ railway: boolean;
87
+ };
88
+ }
89
+ export declare function validateKnowledgeCoopManagedLaunchPrerequisites(tenantRoot?: string): KnowledgeCoopLaunchPreflightReport;
90
+ export declare function executeKnowledgeCoopManagedLaunch(input: KnowledgeCoopManagedLaunchInput): Promise<KnowledgeCoopManagedLaunchResult>;