@treeseed/sdk 0.4.7 → 0.4.9

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 (43) hide show
  1. package/README.md +1 -1
  2. package/dist/dispatch.d.ts +4 -0
  3. package/dist/dispatch.js +180 -0
  4. package/dist/index.d.ts +4 -2
  5. package/dist/index.js +25 -3
  6. package/dist/operations/providers/default.js +1 -0
  7. package/dist/operations/services/config-runtime.d.ts +121 -26
  8. package/dist/operations/services/config-runtime.js +332 -198
  9. package/dist/operations/services/deploy.d.ts +0 -3
  10. package/dist/operations/services/deploy.js +1 -1
  11. package/dist/operations/services/export-runtime.d.ts +18 -0
  12. package/dist/operations/services/export-runtime.js +136 -0
  13. package/dist/operations/services/railway-deploy.js +2 -2
  14. package/dist/operations/services/runtime-tools.d.ts +0 -1
  15. package/dist/operations/services/runtime-tools.js +1 -2
  16. package/dist/operations-registry.js +1 -0
  17. package/dist/operations-types.d.ts +1 -1
  18. package/dist/platform/book-export.d.ts +78 -0
  19. package/dist/platform/book-export.js +449 -0
  20. package/dist/platform/contracts.d.ts +6 -2
  21. package/dist/platform/deploy-config.d.ts +2 -0
  22. package/dist/platform/deploy-config.js +30 -2
  23. package/dist/platform/env.yaml +5 -0
  24. package/dist/platform/environment.d.ts +10 -1
  25. package/dist/platform/environment.js +82 -6
  26. package/dist/remote.d.ts +65 -9
  27. package/dist/remote.js +104 -28
  28. package/dist/scripts/aggregate-book.js +13 -118
  29. package/dist/scripts/config-treeseed.js +18 -27
  30. package/dist/sdk-dispatch.d.ts +12 -0
  31. package/dist/sdk-dispatch.js +142 -0
  32. package/dist/sdk-types.d.ts +137 -4
  33. package/dist/sdk-types.js +16 -0
  34. package/dist/sdk.d.ts +7 -1
  35. package/dist/sdk.js +69 -0
  36. package/dist/workflow/operations.d.ts +59 -15
  37. package/dist/workflow/operations.js +61 -81
  38. package/dist/workflow-state.js +2 -1
  39. package/dist/workflow-support.d.ts +2 -1
  40. package/dist/workflow-support.js +14 -6
  41. package/dist/workflow.d.ts +11 -1
  42. package/dist/workflow.js +6 -0
  43. package/package.json +6 -1
@@ -21,6 +21,7 @@ const TREESEED_ENVIRONMENT_TARGETS = [
21
21
  ];
22
22
  const TREESEED_ENVIRONMENT_PURPOSES = ["dev", "save", "deploy", "destroy", "config"];
23
23
  const TREESEED_ENVIRONMENT_SENSITIVITY = ["secret", "plain", "derived"];
24
+ const TREESEED_ENVIRONMENT_STORAGE = ["scoped", "shared"];
24
25
  const moduleDir = dirname(fileURLToPath(import.meta.url));
25
26
  const CORE_ENVIRONMENT_PATH = resolve(moduleDir, "env.yaml");
26
27
  const TENANT_ENVIRONMENT_OVERLAY_PATH = "src/env.yaml";
@@ -45,9 +46,69 @@ function railwayManagedEnabled(context) {
45
46
  function generatedSecret(bytes = 24) {
46
47
  return randomBytes(bytes).toString("hex");
47
48
  }
49
+ function normalizeUrl(value) {
50
+ return value.trim().replace(/\/$/u, "");
51
+ }
52
+ function primaryHostFromUrl(value) {
53
+ if (!value || value.trim().length === 0) {
54
+ return void 0;
55
+ }
56
+ try {
57
+ return new URL(value).host;
58
+ } catch {
59
+ return void 0;
60
+ }
61
+ }
62
+ function parseDomainList(value) {
63
+ return String(value ?? "").split(",").map((entry) => entry.trim()).filter(Boolean);
64
+ }
65
+ function deriveApiDomainFromProjectDomain(domain) {
66
+ if (!domain) {
67
+ return void 0;
68
+ }
69
+ if (domain.startsWith("api.")) {
70
+ return domain;
71
+ }
72
+ const segments = domain.split(".").filter(Boolean);
73
+ if (segments.length <= 2) {
74
+ return `api.${domain}`;
75
+ }
76
+ return `api.${segments.slice(1).join(".")}`;
77
+ }
78
+ function resolveConfiguredApiBaseUrl(context, scope, values = {}) {
79
+ const localBaseUrl = context.deployConfig.services?.api?.environments?.local?.baseUrl ?? context.deployConfig.surfaces?.api?.localBaseUrl ?? "http://127.0.0.1:3000";
80
+ if (scope === "local") {
81
+ return normalizeUrl(localBaseUrl);
82
+ }
83
+ const scopedBaseUrl = context.deployConfig.services?.api?.environments?.[scope]?.baseUrl ?? context.deployConfig.services?.api?.publicBaseUrl ?? context.deployConfig.surfaces?.api?.publicBaseUrl;
84
+ if (scopedBaseUrl) {
85
+ return normalizeUrl(scopedBaseUrl);
86
+ }
87
+ const projectDomains = [
88
+ ...parseDomainList(values.TREESEED_PROJECT_DOMAINS),
89
+ primaryHostFromUrl(context.deployConfig.siteUrl)
90
+ ].filter(Boolean);
91
+ for (const domain of projectDomains) {
92
+ const apiDomain = deriveApiDomainFromProjectDomain(domain);
93
+ if (apiDomain) {
94
+ return `https://${apiDomain}`;
95
+ }
96
+ }
97
+ return void 0;
98
+ }
99
+ function resolveWebServiceId(_values = {}) {
100
+ return "web";
101
+ }
102
+ function resolveApiWebServiceId(values = {}) {
103
+ return values.TREESEED_WEB_SERVICE_ID?.trim() || "web";
104
+ }
48
105
  const VALUE_RESOLVERS = {
49
106
  generatedSecret: () => generatedSecret(),
50
- localFormsBypassDefault: () => "true"
107
+ localFormsBypassDefault: () => "true",
108
+ projectDomainsDefault: (context) => primaryHostFromUrl(context.deployConfig.siteUrl),
109
+ apiBaseUrlDefault: (context, scope, values) => resolveConfiguredApiBaseUrl(context, scope, values),
110
+ webServiceIdDefault: (_context, _scope, values) => resolveWebServiceId(values),
111
+ apiWebServiceIdDefault: (_context, _scope, values) => resolveApiWebServiceId(values)
51
112
  };
52
113
  const PREDICATES = {
53
114
  turnstileEnabled: (context) => turnstileEnabled(context),
@@ -135,6 +196,7 @@ function materializeEntry(id, entry) {
135
196
  return {
136
197
  ...entry,
137
198
  id,
199
+ storage: entry.storage ?? "scoped",
138
200
  defaultValue: resolveNamedValueResolver(entry.defaultValueRef),
139
201
  localDefaultValue: resolveNamedValueResolver(entry.localDefaultValueRef),
140
202
  isRelevant: resolveNamedPredicate(entry.relevanceRef),
@@ -233,18 +295,30 @@ function isEntryRequired(entry, context, scope, purpose) {
233
295
  }
234
296
  return false;
235
297
  }
236
- function materializeDefaultValue(entry, context, scope) {
298
+ function materializeDefaultValue(entry, context, scope, values = {}) {
237
299
  const source = scope === "local" && entry.localDefaultValue !== void 0 ? entry.localDefaultValue : entry.defaultValue;
238
300
  if (source === void 0) {
239
301
  return void 0;
240
302
  }
241
- return typeof source === "function" ? source(context, scope) : source;
303
+ return typeof source === "function" ? source(context, scope, values) : source;
242
304
  }
243
305
  function getTreeseedEnvironmentSuggestedValues(options) {
244
306
  const registry = resolveTreeseedEnvironmentRegistry(options);
245
- return Object.fromEntries(
246
- registry.entries.filter((entry) => isTreeseedEnvironmentEntryRelevant(entry, registry.context, options.scope, options.purpose)).map((entry) => [entry.id, materializeDefaultValue(entry, registry.context, options.scope)]).filter(([, value]) => value !== void 0)
247
- );
307
+ const suggestedValues = {};
308
+ const seedValues = { ...options.values ?? {} };
309
+ for (const entry of registry.entries.filter(
310
+ (candidate) => isTreeseedEnvironmentEntryRelevant(candidate, registry.context, options.scope, options.purpose)
311
+ )) {
312
+ const value = materializeDefaultValue(entry, registry.context, options.scope, { ...suggestedValues, ...seedValues });
313
+ if (value === void 0) {
314
+ continue;
315
+ }
316
+ suggestedValues[entry.id] = value;
317
+ }
318
+ return suggestedValues;
319
+ }
320
+ function isTreeseedEnvironmentEntryRequired(entry, context, scope, purpose) {
321
+ return isEntryRequired(entry, context, scope, purpose);
248
322
  }
249
323
  function valuePresent(value) {
250
324
  return typeof value === "string" && value.trim().length > 0;
@@ -322,9 +396,11 @@ export {
322
396
  TREESEED_ENVIRONMENT_REQUIREMENTS,
323
397
  TREESEED_ENVIRONMENT_SCOPES,
324
398
  TREESEED_ENVIRONMENT_SENSITIVITY,
399
+ TREESEED_ENVIRONMENT_STORAGE,
325
400
  TREESEED_ENVIRONMENT_TARGETS,
326
401
  getTreeseedEnvironmentSuggestedValues,
327
402
  isTreeseedEnvironmentEntryRelevant,
403
+ isTreeseedEnvironmentEntryRequired,
328
404
  loadTreeseedEnvironmentOverlay,
329
405
  resolveTreeseedEnvironmentContext,
330
406
  resolveTreeseedEnvironmentRegistry,
package/dist/remote.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { RemoteJob, RemoteJobEvent, SdkDispatchRequest, SdkDispatchResult } from './sdk-types.ts';
1
2
  export declare const TREESEED_REMOTE_CONTRACT_VERSION = 1;
2
3
  export declare const TREESEED_REMOTE_CONTRACT_HEADER = "x-treeseed-remote-contract-version";
3
4
  export type ApiScope = string;
@@ -111,6 +112,25 @@ export interface RemoteWorkflowOperationResponse {
111
112
  payload?: Record<string, unknown> | null;
112
113
  nextSteps?: RemoteWorkflowNextStep[];
113
114
  }
115
+ export interface RemoteJobPullRequest {
116
+ limit?: number;
117
+ runnerId?: string;
118
+ }
119
+ export interface RemoteJobPullResponse {
120
+ ok: true;
121
+ payload: RemoteJob[];
122
+ }
123
+ export interface RemoteJobProgressRequest {
124
+ summary?: string;
125
+ data?: Record<string, unknown>;
126
+ }
127
+ export interface RemoteJobCompletionRequest {
128
+ output?: unknown;
129
+ }
130
+ export interface RemoteJobFailureRequest {
131
+ code?: string;
132
+ message: string;
133
+ }
114
134
  export declare class RemoteTreeseedClient {
115
135
  readonly config: RemoteTreeseedConfig;
116
136
  private readonly fetchImpl;
@@ -141,15 +161,6 @@ export declare class RemoteTreeseedSdkClient {
141
161
  constructor(client: RemoteTreeseedClient);
142
162
  execute<T = unknown>(operation: string, request: RemoteSdkOperationRequest): Promise<T>;
143
163
  }
144
- export declare class TreeseedGatewayClient {
145
- private readonly baseUrl;
146
- private readonly token;
147
- private readonly fetchImpl;
148
- constructor(config: import('./sdk-types.ts').SdkGatewayClientConfig);
149
- requestJson<T>(path: string, options?: RemoteGatewayRequest): Promise<T & {
150
- error?: string;
151
- }>;
152
- }
153
164
  export declare class CloudflareQueuePullClient {
154
165
  private readonly baseUrl;
155
166
  private readonly token;
@@ -170,8 +181,53 @@ export declare class CloudflareQueuePullClient {
170
181
  delaySeconds?: number;
171
182
  }>): Promise<unknown>;
172
183
  }
184
+ export declare class CloudflareQueuePushClient {
185
+ private readonly baseUrl;
186
+ private readonly token;
187
+ private readonly fetchImpl;
188
+ constructor(config: import('./sdk-types.ts').SdkQueuePushClientConfig);
189
+ enqueue(request: import('./sdk-types.ts').SdkQueuePushRequest): Promise<void>;
190
+ }
173
191
  export declare class RemoteTreeseedOperationsClient {
174
192
  private readonly client;
175
193
  constructor(client: RemoteTreeseedClient);
176
194
  execute(operation: string, request: RemoteWorkflowOperationRequest): Promise<RemoteWorkflowOperationResponse>;
177
195
  }
196
+ export declare class RemoteTreeseedDispatchClient {
197
+ private readonly client;
198
+ constructor(client: RemoteTreeseedClient);
199
+ dispatch(projectId: string, request: SdkDispatchRequest): Promise<SdkDispatchResult>;
200
+ }
201
+ export declare class RemoteTreeseedJobsClient {
202
+ private readonly client;
203
+ constructor(client: RemoteTreeseedClient);
204
+ get(jobId: string): Promise<{
205
+ ok: true;
206
+ payload: RemoteJob;
207
+ }>;
208
+ cancel(jobId: string): Promise<{
209
+ ok: true;
210
+ payload: RemoteJob;
211
+ }>;
212
+ events(jobId: string): Promise<{
213
+ ok: true;
214
+ payload: RemoteJobEvent[];
215
+ }>;
216
+ }
217
+ export declare class RemoteTreeseedRunnerClient {
218
+ private readonly client;
219
+ constructor(client: RemoteTreeseedClient);
220
+ pull(projectId: string, request?: RemoteJobPullRequest): Promise<RemoteJobPullResponse>;
221
+ progress(jobId: string, request?: RemoteJobProgressRequest): Promise<{
222
+ ok: true;
223
+ payload: RemoteJob;
224
+ }>;
225
+ complete(jobId: string, request?: RemoteJobCompletionRequest): Promise<{
226
+ ok: true;
227
+ payload: RemoteJob;
228
+ }>;
229
+ fail(jobId: string, request: RemoteJobFailureRequest): Promise<{
230
+ ok: true;
231
+ payload: RemoteJob;
232
+ }>;
233
+ }
package/dist/remote.js CHANGED
@@ -100,32 +100,6 @@ class RemoteTreeseedSdkClient {
100
100
  function normalizeExternalBaseUrl(baseUrl) {
101
101
  return baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
102
102
  }
103
- class TreeseedGatewayClient {
104
- baseUrl;
105
- token;
106
- fetchImpl;
107
- constructor(config) {
108
- this.baseUrl = normalizeExternalBaseUrl(config.baseUrl);
109
- this.token = config.bearerToken;
110
- this.fetchImpl = config.fetchImpl ?? fetch;
111
- }
112
- async requestJson(path, options = {}) {
113
- const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
114
- method: options.method ?? "POST",
115
- headers: {
116
- accept: "application/json",
117
- authorization: `Bearer ${this.token}`,
118
- ...options.body === void 0 ? {} : { "content-type": "application/json" }
119
- },
120
- body: options.body === void 0 ? void 0 : JSON.stringify(options.body)
121
- });
122
- const payload = await response.json().catch(() => ({}));
123
- if (!response.ok) {
124
- throw new Error(typeof payload.error === "string" ? payload.error : `Gateway request failed with ${response.status}.`);
125
- }
126
- return payload;
127
- }
128
- }
129
103
  class CloudflareQueuePullClient {
130
104
  baseUrl;
131
105
  token;
@@ -177,6 +151,36 @@ class CloudflareQueuePullClient {
177
151
  });
178
152
  }
179
153
  }
154
+ class CloudflareQueuePushClient {
155
+ baseUrl;
156
+ token;
157
+ fetchImpl;
158
+ constructor(config) {
159
+ const apiBaseUrl = config.apiBaseUrl ?? "https://api.cloudflare.com/client/v4/accounts";
160
+ this.baseUrl = `${normalizeExternalBaseUrl(apiBaseUrl)}/${config.accountId}/queues/${config.queueId}`;
161
+ this.token = config.token;
162
+ this.fetchImpl = config.fetchImpl ?? fetch;
163
+ }
164
+ async enqueue(request) {
165
+ const response = await this.fetchImpl(`${this.baseUrl}/messages`, {
166
+ method: "POST",
167
+ headers: {
168
+ accept: "application/json",
169
+ authorization: `Bearer ${this.token}`,
170
+ "content-type": "application/json"
171
+ },
172
+ body: JSON.stringify({
173
+ body: request.message,
174
+ content_type: "json",
175
+ delay_seconds: request.delaySeconds ?? 0
176
+ })
177
+ });
178
+ const payload = await response.json().catch(() => ({}));
179
+ if (!response.ok || payload.success === false) {
180
+ throw new Error(payload.errors?.[0]?.message ?? `Queue request failed with ${response.status}.`);
181
+ }
182
+ }
183
+ }
180
184
  class RemoteTreeseedOperationsClient {
181
185
  constructor(client) {
182
186
  this.client = client;
@@ -190,13 +194,85 @@ class RemoteTreeseedOperationsClient {
190
194
  });
191
195
  }
192
196
  }
197
+ class RemoteTreeseedDispatchClient {
198
+ constructor(client) {
199
+ this.client = client;
200
+ }
201
+ client;
202
+ dispatch(projectId, request) {
203
+ return this.client.requestJson(`/v1/projects/${encodeURIComponent(projectId)}/dispatch`, {
204
+ method: "POST",
205
+ body: request,
206
+ requireAuth: true
207
+ });
208
+ }
209
+ }
210
+ class RemoteTreeseedJobsClient {
211
+ constructor(client) {
212
+ this.client = client;
213
+ }
214
+ client;
215
+ get(jobId) {
216
+ return this.client.requestJson(`/v1/jobs/${encodeURIComponent(jobId)}`, {
217
+ requireAuth: true
218
+ });
219
+ }
220
+ cancel(jobId) {
221
+ return this.client.requestJson(`/v1/jobs/${encodeURIComponent(jobId)}/cancel`, {
222
+ method: "POST",
223
+ requireAuth: true
224
+ });
225
+ }
226
+ events(jobId) {
227
+ return this.client.requestJson(`/v1/jobs/${encodeURIComponent(jobId)}/events`, {
228
+ requireAuth: true
229
+ });
230
+ }
231
+ }
232
+ class RemoteTreeseedRunnerClient {
233
+ constructor(client) {
234
+ this.client = client;
235
+ }
236
+ client;
237
+ pull(projectId, request = {}) {
238
+ return this.client.requestJson(`/v1/projects/${encodeURIComponent(projectId)}/runner/jobs/pull`, {
239
+ method: "POST",
240
+ body: request,
241
+ requireAuth: true
242
+ });
243
+ }
244
+ progress(jobId, request = {}) {
245
+ return this.client.requestJson(`/v1/jobs/${encodeURIComponent(jobId)}/progress`, {
246
+ method: "POST",
247
+ body: request,
248
+ requireAuth: true
249
+ });
250
+ }
251
+ complete(jobId, request = {}) {
252
+ return this.client.requestJson(`/v1/jobs/${encodeURIComponent(jobId)}/complete`, {
253
+ method: "POST",
254
+ body: request,
255
+ requireAuth: true
256
+ });
257
+ }
258
+ fail(jobId, request) {
259
+ return this.client.requestJson(`/v1/jobs/${encodeURIComponent(jobId)}/fail`, {
260
+ method: "POST",
261
+ body: request,
262
+ requireAuth: true
263
+ });
264
+ }
265
+ }
193
266
  export {
194
267
  CloudflareQueuePullClient,
268
+ CloudflareQueuePushClient,
195
269
  RemoteTreeseedAuthClient,
196
270
  RemoteTreeseedClient,
271
+ RemoteTreeseedDispatchClient,
272
+ RemoteTreeseedJobsClient,
197
273
  RemoteTreeseedOperationsClient,
274
+ RemoteTreeseedRunnerClient,
198
275
  RemoteTreeseedSdkClient,
199
276
  TREESEED_REMOTE_CONTRACT_HEADER,
200
- TREESEED_REMOTE_CONTRACT_VERSION,
201
- TreeseedGatewayClient
277
+ TREESEED_REMOTE_CONTRACT_VERSION
202
278
  };
@@ -1,121 +1,16 @@
1
- import fs from 'node:fs';
2
1
  import path from 'node:path';
3
- import { buildTenantBookRuntime } from '../platform/books-data.js';
4
- import { loadTreeseedManifest } from '../platform/tenant-config.js';
5
- const PROJECT_TENANT = loadTreeseedManifest();
6
- const { BOOKS, TREESEED_LIBRARY_DOWNLOAD } = buildTenantBookRuntime(PROJECT_TENANT, {
7
- projectRoot: PROJECT_TENANT.__tenantRoot ?? process.cwd(),
8
- docsLibraryDownload: {
9
- downloadFileName: 'karyon-knowledge.md',
10
- downloadHref: '/books/karyon-knowledge.md',
11
- downloadTitle: 'Karyon Knowledge Library',
12
- },
13
- });
14
- const projectRoot = PROJECT_TENANT.__tenantRoot ?? process.cwd();
15
- const outputDir = path.join(projectRoot, 'public', 'books');
16
- const legacyOutputFile = path.join(projectRoot, 'public', 'book.md');
17
- function sortPaths(paths) {
18
- return [...paths].sort((left, right) => left.localeCompare(right, undefined, { numeric: true, sensitivity: 'base' }));
19
- }
20
- function getSidebarOrder(filePath) {
21
- const rawContent = fs.readFileSync(filePath, 'utf8');
22
- const frontmatterMatch = rawContent.match(/^---\r?\n([\s\S]*?)\r?\n---/);
23
- if (!frontmatterMatch)
24
- return Number.POSITIVE_INFINITY;
25
- const orderMatch = frontmatterMatch[1].match(/^\s{2}order:\s*(\d+)\s*$/m);
26
- return orderMatch ? Number.parseInt(orderMatch[1], 10) : Number.POSITIVE_INFINITY;
27
- }
28
- function collectMarkdownFiles(rootPath) {
29
- if (!fs.existsSync(rootPath)) {
30
- throw new Error(`Book export root not found: ${rootPath}`);
31
- }
32
- const stats = fs.statSync(rootPath);
33
- if (stats.isFile()) {
34
- return [rootPath];
35
- }
36
- const entries = fs.readdirSync(rootPath, { withFileTypes: true });
37
- return sortPaths(entries.flatMap((entry) => {
38
- const fullPath = path.join(rootPath, entry.name);
39
- if (entry.isDirectory()) {
40
- return collectMarkdownFiles(fullPath);
41
- }
42
- if (entry.isFile() && (entry.name.endsWith('.md') || entry.name.endsWith('.mdx'))) {
43
- return [fullPath];
44
- }
45
- return [];
46
- }));
47
- }
48
- function stripFrontmatter(content) {
49
- return content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '').trim();
50
- }
51
- function stripMdxOnlySyntax(content) {
52
- return content
53
- .replace(/^import\s.+$/gm, '')
54
- .replace(/^\s*<\/?[A-Z][^>]*>\s*$/gm, '')
55
- .replace(/\n{3,}/g, '\n\n')
56
- .trim();
57
- }
58
- function inferExportRootFromBasePath(book) {
59
- const normalizedBasePath = String(book.basePath || '').trim();
60
- const knowledgePrefix = '/knowledge/';
61
- if (!normalizedBasePath.startsWith(knowledgePrefix)) {
62
- throw new Error(`Book basePath must start with "${knowledgePrefix}" to infer exports: ${book.basePath}`);
63
- }
64
- const relativeKnowledgePath = normalizedBasePath
65
- .slice(knowledgePrefix.length)
66
- .replace(/^\/+|\/+$/g, '');
67
- if (!relativeKnowledgePath) {
68
- throw new Error(`Book basePath must identify a knowledge directory: ${book.basePath}`);
2
+ import { exportTenantBookPackages } from '../platform/book-export.js';
3
+ async function main() {
4
+ console.log('Generating Treeseed AI book packages...');
5
+ const result = await exportTenantBookPackages({ projectRoot: process.cwd() });
6
+ for (const entry of result.bookPackages) {
7
+ console.log(`Generated ${path.relative(result.projectRoot, entry.markdownPath)}`);
8
+ console.log(`Generated ${path.relative(result.projectRoot, entry.indexPath)}`);
69
9
  }
70
- return path.join(PROJECT_TENANT.content.docs, relativeKnowledgePath);
10
+ console.log(`Generated ${path.relative(result.projectRoot, result.libraryPackage.markdownPath)}`);
11
+ console.log(`Generated ${path.relative(result.projectRoot, result.libraryPackage.indexPath)}`);
71
12
  }
72
- function resolveExportRoots(book) {
73
- if (Array.isArray(book.exportRoots) && book.exportRoots.length > 0) {
74
- return book.exportRoots;
75
- }
76
- return [inferExportRootFromBasePath(book)];
77
- }
78
- function resolveBookFiles(book) {
79
- const files = resolveExportRoots(book).flatMap((root) => collectMarkdownFiles(path.resolve(projectRoot, root)).sort((left, right) => {
80
- const orderDelta = getSidebarOrder(left) - getSidebarOrder(right);
81
- if (orderDelta !== 0)
82
- return orderDelta;
83
- return left.localeCompare(right, undefined, { numeric: true, sensitivity: 'base' });
84
- }));
85
- return Array.from(new Set(files));
86
- }
87
- function buildBookMarkdown(book) {
88
- const sections = resolveBookFiles(book).map((filePath) => {
89
- const rawContent = fs.readFileSync(filePath, 'utf8');
90
- return stripMdxOnlySyntax(stripFrontmatter(rawContent));
91
- });
92
- return `# ${book.downloadTitle}\n\n> This document is auto-generated from the Karyon knowledge source.\n\n${sections.join('\n\n---\n\n')}\n`;
93
- }
94
- function ensureOutputDir() {
95
- fs.mkdirSync(outputDir, { recursive: true });
96
- }
97
- function writeBookOutput(fileName, content) {
98
- const outputPath = path.join(outputDir, fileName);
99
- fs.writeFileSync(outputPath, content);
100
- return outputPath;
101
- }
102
- function main() {
103
- console.log('Generating contextual Karyon knowledge exports...');
104
- ensureOutputDir();
105
- const bookOutputs = BOOKS.map((book) => {
106
- const content = buildBookMarkdown(book);
107
- const outputPath = writeBookOutput(book.downloadFileName, content);
108
- console.log(`Generated ${path.relative(projectRoot, outputPath)}`);
109
- return { book, content };
110
- });
111
- const compositeContent = `# ${TREESEED_LIBRARY_DOWNLOAD.downloadTitle}\n\n> This document is auto-generated from the Karyon knowledge source.\n\n${bookOutputs
112
- .map(({ content }) => content.trim())
113
- .join('\n\n---\n\n')}\n`;
114
- const compositeOutputPath = writeBookOutput(TREESEED_LIBRARY_DOWNLOAD.downloadFileName, compositeContent);
115
- console.log(`Generated ${path.relative(projectRoot, compositeOutputPath)}`);
116
- if (fs.existsSync(legacyOutputFile)) {
117
- fs.rmSync(legacyOutputFile);
118
- console.log(`Removed legacy export ${path.relative(projectRoot, legacyOutputFile)}`);
119
- }
120
- }
121
- main();
13
+ main().catch((error) => {
14
+ console.error(error instanceof Error ? error.message : String(error));
15
+ process.exitCode = 1;
16
+ });
@@ -1,8 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import readline from 'node:readline/promises';
3
- import { stdin as input, stdout as output } from 'node:process';
4
- import { collectCliPreflight } from '../operations/services/workspace-preflight.js';
5
- import { applyTreeseedEnvironmentToProcess, ensureTreeseedGitignoreEntries, getTreeseedMachineConfigPaths, rotateTreeseedMachineKey, runTreeseedConfigWizard, writeTreeseedLocalEnvironmentFiles, } from '../operations/services/config-runtime.js';
2
+ import { applyTreeseedConfigValues, applyTreeseedSafeRepairs, collectTreeseedConfigContext, ensureTreeseedGitignoreEntries, finalizeTreeseedConfig, getTreeseedMachineConfigPaths, rotateTreeseedMachineKey, } from '../operations/services/config-runtime.js';
6
3
  const tenantRoot = process.cwd();
7
4
  function parseArgs(argv) {
8
5
  const parsed = {
@@ -45,42 +42,37 @@ const scopes = options.scopes.length === 0 || options.scopes.includes('all')
45
42
  ? ['local', 'staging', 'prod']
46
43
  : ['local', 'staging', 'prod'].filter((scope) => options.scopes.includes(scope));
47
44
  ensureTreeseedGitignoreEntries(tenantRoot);
48
- const preflight = collectCliPreflight({ cwd: tenantRoot, requireAuth: false });
49
- const rl = readline.createInterface({ input, output });
50
45
  try {
51
- console.log('Treeseed configuration wizard');
52
- console.log('This command writes a local machine config, generates .env.local and .dev.vars, and can sync GitHub or Cloudflare settings.');
53
- console.log('Enter a value to set it, press Enter to keep the current/default value, or enter "-" to clear a value.\n');
54
46
  if (options.rotateMachineKey) {
55
47
  const result = rotateTreeseedMachineKey(tenantRoot);
56
48
  console.log('Treeseed machine key rotated.');
57
49
  console.log(`Machine key: ${result.keyPath}`);
58
50
  }
59
51
  else {
60
- const result = await runTreeseedConfigWizard({
52
+ applyTreeseedSafeRepairs(tenantRoot);
53
+ const context = collectTreeseedConfigContext({
54
+ tenantRoot,
55
+ scopes,
56
+ env: process.env,
57
+ });
58
+ const updates = scopes.flatMap((scope) => context.entriesByScope[scope].map((entry) => ({
59
+ scope,
60
+ entryId: entry.id,
61
+ value: entry.effectiveValue,
62
+ reused: entry.currentValue.length > 0 || entry.suggestedValue.length > 0,
63
+ })));
64
+ const applyResult = applyTreeseedConfigValues({ tenantRoot, updates });
65
+ const result = finalizeTreeseedConfig({
61
66
  tenantRoot,
62
67
  scopes,
63
68
  sync: options.sync,
64
- authStatus: preflight.checks.auth,
65
- prompt: async (message) => {
66
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
67
- return '';
68
- }
69
- try {
70
- return await rl.question(message);
71
- }
72
- catch {
73
- return '';
74
- }
75
- },
69
+ env: process.env,
76
70
  });
77
- writeTreeseedLocalEnvironmentFiles(tenantRoot);
78
- applyTreeseedEnvironmentToProcess({ tenantRoot, scope: 'local', override: true });
79
71
  const { configPath, keyPath } = getTreeseedMachineConfigPaths(tenantRoot);
80
- console.log('\nTreeseed config completed.');
72
+ console.log('Treeseed config completed.');
81
73
  console.log(`Machine config: ${configPath}`);
82
74
  console.log(`Machine key: ${keyPath}`);
83
- console.log(`Updated values: ${result.updated.length}`);
75
+ console.log(`Updated values: ${applyResult.updated.length}`);
84
76
  console.log(`Initialized environments: ${result.initialized.length}`);
85
77
  if (result.synced.github) {
86
78
  console.log(`GitHub sync: ${result.synced.github.secrets.length} secrets, ${result.synced.github.variables.length} variables (${result.synced.github.repository})`);
@@ -91,5 +83,4 @@ try {
91
83
  }
92
84
  }
93
85
  finally {
94
- rl.close();
95
86
  }
@@ -0,0 +1,12 @@
1
+ import type { AgentSdk } from './sdk.ts';
2
+ type JsonRecord = Record<string, unknown>;
3
+ type SdkOperationHandler = (sdk: AgentSdk, input: JsonRecord) => Promise<unknown> | unknown;
4
+ interface SdkOperationSpec {
5
+ name: string;
6
+ aliases?: string[];
7
+ handler: SdkOperationHandler;
8
+ }
9
+ export declare function listSdkOperationNames(): string[];
10
+ export declare function findSdkOperation(name: string): SdkOperationSpec | null;
11
+ export declare function executeSdkOperation(sdk: AgentSdk, operationName: string, input: JsonRecord): Promise<unknown>;
12
+ export {};