@treeseed/core 0.4.8 → 0.4.10

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 (42) hide show
  1. package/README.md +1 -2
  2. package/dist/agent.d.ts +0 -1
  3. package/dist/agent.js +0 -2
  4. package/dist/agents/spec-types.d.ts +10 -10
  5. package/dist/api/agent-routes.d.ts +2 -2
  6. package/dist/api/agent-routes.js +51 -125
  7. package/dist/api/app.js +56 -4
  8. package/dist/api/auth/d1-store.d.ts +1 -0
  9. package/dist/api/auth/d1-store.js +21 -1
  10. package/dist/api/config.js +4 -0
  11. package/dist/api/http.d.ts +4 -0
  12. package/dist/api/http.js +7 -0
  13. package/dist/api/index.d.ts +1 -1
  14. package/dist/api/index.js +2 -2
  15. package/dist/api/operations-routes.d.ts +1 -0
  16. package/dist/api/operations-routes.js +6 -1
  17. package/dist/api/railway.d.ts +4 -0
  18. package/dist/api/sdk-dispatch.d.ts +2 -11
  19. package/dist/api/sdk-dispatch.js +1 -133
  20. package/dist/api/sdk-routes.d.ts +1 -0
  21. package/dist/api/sdk-routes.js +5 -1
  22. package/dist/api/types.d.ts +32 -16
  23. package/dist/dev.js +24 -1
  24. package/dist/index.d.ts +0 -1
  25. package/dist/index.js +0 -2
  26. package/dist/scripts/aggregate-book.js +13 -108
  27. package/dist/scripts/test-smoke.js +80 -24
  28. package/dist/services/common.d.ts +37 -4
  29. package/dist/services/common.js +135 -17
  30. package/dist/services/index.d.ts +1 -1
  31. package/dist/services/index.js +3 -2
  32. package/dist/services/remote-runner.d.ts +23 -0
  33. package/dist/services/remote-runner.js +105 -0
  34. package/dist/services/workday-report.js +13 -17
  35. package/dist/services/workday-start.d.ts +5 -1
  36. package/dist/services/workday-start.js +7 -13
  37. package/dist/services/worker.js +38 -57
  38. package/package.json +7 -11
  39. package/dist/api/gateway.d.ts +0 -5
  40. package/dist/api/gateway.js +0 -35
  41. package/dist/services/manager.d.ts +0 -4
  42. package/dist/services/manager.js +0 -199
@@ -2,5 +2,6 @@ import type { Hono } from 'hono';
2
2
  import { executeHttpWorkflowOperation } from './operations.ts';
3
3
  export declare function registerOperationRoutes(app: Hono<any>, options: {
4
4
  scope: string;
5
+ prefix?: string;
5
6
  executeOperation?: typeof executeHttpWorkflowOperation;
6
7
  }): void;
@@ -3,7 +3,12 @@ import { executeHttpWorkflowOperation, isHttpWorkflowOperationAllowed } from "./
3
3
  import { jsonError, requireScope } from "./http.js";
4
4
  function registerOperationRoutes(app, options) {
5
5
  const executeOperation = options.executeOperation ?? executeHttpWorkflowOperation;
6
- app.post("/operations/:operation", async (c) => {
6
+ const prefix = options.prefix ?? "";
7
+ function withPrefix(path) {
8
+ if (!prefix) return path;
9
+ return `${prefix}${path}`.replace(/\/{2,}/g, "/");
10
+ }
11
+ app.post(withPrefix("/operations/:operation"), async (c) => {
7
12
  const unauthorized = requireScope(c, options.scope);
8
13
  if (unauthorized) return unauthorized;
9
14
  const requestedOperation = c.req.param("operation");
@@ -22,7 +22,11 @@ export declare function createRailwayTreeseedApiServer(options?: ApiServerOption
22
22
  baseUrl: string;
23
23
  issuer: string;
24
24
  repoRoot: string;
25
+ projectId: string;
25
26
  authSecret: string;
27
+ projectApiKey?: string;
28
+ projectApiLabel: string;
29
+ projectApiPermissions: string[];
26
30
  cloudflareAccountId?: string;
27
31
  cloudflareApiToken?: string;
28
32
  d1DatabaseId?: string;
@@ -1,14 +1,5 @@
1
1
  import type { AgentSdk, RemoteSdkOperationRequest } from '@treeseed/sdk';
2
+ import { executeSdkOperation, findSdkOperation, listSdkOperationNames } from '@treeseed/sdk';
2
3
  import type { ApiConfig } from './types.ts';
3
- type JsonRecord = Record<string, unknown>;
4
- type SdkOperationHandler = (sdk: AgentSdk, input: JsonRecord) => Promise<unknown> | unknown;
5
- interface SdkOperationSpec {
6
- name: string;
7
- aliases?: string[];
8
- handler: SdkOperationHandler;
9
- }
10
- export declare function listSdkOperationNames(): string[];
11
- export declare function findSdkOperation(name: string): SdkOperationSpec;
4
+ export { executeSdkOperation, findSdkOperation, listSdkOperationNames, };
12
5
  export declare function resolveSdkInstance(sharedSdk: AgentSdk | undefined, config: ApiConfig, request: RemoteSdkOperationRequest): AgentSdk;
13
- export declare function executeSdkOperation(sdk: AgentSdk, operationName: string, input: JsonRecord): Promise<unknown>;
14
- export {};
@@ -1,142 +1,10 @@
1
- import { AgentSdk as AgentSdkClass } from "@treeseed/sdk";
2
- function passthrough(methodName) {
3
- return (sdk, input) => sdk[methodName](input);
4
- }
5
- const SDK_OPERATION_SPECS = [
6
- { name: "get", handler: passthrough("get") },
7
- { name: "read", handler: passthrough("read") },
8
- { name: "search", handler: passthrough("search") },
9
- { name: "follow", handler: passthrough("follow") },
10
- { name: "pick", handler: passthrough("pick") },
11
- { name: "create", handler: passthrough("create") },
12
- { name: "update", handler: passthrough("update") },
13
- { name: "claimMessage", aliases: ["claim-message"], handler: passthrough("claimMessage") },
14
- { name: "ackMessage", aliases: ["ack-message"], handler: passthrough("ackMessage") },
15
- { name: "createMessage", aliases: ["create-message"], handler: passthrough("createMessage") },
16
- { name: "recordRun", aliases: ["record-run"], handler: passthrough("recordRun") },
17
- { name: "getCursor", aliases: ["get-cursor"], handler: passthrough("getCursor") },
18
- { name: "upsertCursor", aliases: ["upsert-cursor"], handler: passthrough("upsertCursor") },
19
- { name: "releaseLease", aliases: ["release-lease"], handler: passthrough("releaseLease") },
20
- {
21
- name: "releaseAllLeases",
22
- aliases: ["release-all-leases"],
23
- handler: (sdk) => sdk.releaseAllLeases()
24
- },
25
- { name: "startWorkDay", aliases: ["start-work-day"], handler: passthrough("startWorkDay") },
26
- { name: "closeWorkDay", aliases: ["close-work-day"], handler: passthrough("closeWorkDay") },
27
- { name: "createTask", aliases: ["create-task"], handler: passthrough("createTask") },
28
- { name: "claimTask", aliases: ["claim-task"], handler: passthrough("claimTask") },
29
- {
30
- name: "recordTaskProgress",
31
- aliases: ["record-task-progress"],
32
- handler: passthrough("recordTaskProgress")
33
- },
34
- { name: "completeTask", aliases: ["complete-task"], handler: passthrough("completeTask") },
35
- { name: "failTask", aliases: ["fail-task"], handler: passthrough("failTask") },
36
- { name: "appendTaskEvent", aliases: ["append-task-event"], handler: passthrough("appendTaskEvent") },
37
- { name: "searchTasks", aliases: ["search-tasks"], handler: passthrough("searchTasks") },
38
- {
39
- name: "getManagerContext",
40
- aliases: ["get-manager-context"],
41
- handler: (sdk, input) => sdk.getManagerContext(String(input.taskId ?? input.id ?? ""))
42
- },
43
- { name: "createReport", aliases: ["create-report"], handler: passthrough("createReport") },
44
- {
45
- name: "listAgentSpecs",
46
- aliases: ["list-agent-specs"],
47
- handler: (sdk, input) => sdk.listAgentSpecs(input)
48
- },
49
- { name: "refreshGraph", aliases: ["refresh-graph"], handler: passthrough("refreshGraph") },
50
- {
51
- name: "searchFiles",
52
- aliases: ["search-files"],
53
- handler: (sdk, input) => sdk.searchFiles(String(input.query ?? ""), input.options)
54
- },
55
- {
56
- name: "searchSections",
57
- aliases: ["search-sections"],
58
- handler: (sdk, input) => sdk.searchSections(String(input.query ?? ""), input.options)
59
- },
60
- {
61
- name: "searchEntities",
62
- aliases: ["search-entities"],
63
- handler: (sdk, input) => sdk.searchEntities(String(input.query ?? ""), input.options)
64
- },
65
- {
66
- name: "getGraphNode",
67
- aliases: ["get-graph-node"],
68
- handler: (sdk, input) => sdk.getGraphNode(String(input.id ?? ""))
69
- },
70
- {
71
- name: "getNeighbors",
72
- aliases: ["get-neighbors"],
73
- handler: (sdk, input) => sdk.getNeighbors(String(input.id ?? ""), input.options)
74
- },
75
- {
76
- name: "followReferences",
77
- aliases: ["follow-references"],
78
- handler: (sdk, input) => sdk.followReferences(String(input.id ?? ""), input.options)
79
- },
80
- {
81
- name: "getBacklinks",
82
- aliases: ["get-backlinks"],
83
- handler: (sdk, input) => sdk.getBacklinks(String(input.id ?? ""), input.options)
84
- },
85
- {
86
- name: "getRelated",
87
- aliases: ["get-related"],
88
- handler: (sdk, input) => sdk.getRelated(String(input.id ?? ""), input.options)
89
- },
90
- {
91
- name: "getSubgraph",
92
- aliases: ["get-subgraph"],
93
- handler: (sdk, input) => sdk.getSubgraph(Array.isArray(input.seedIds) ? input.seedIds.map(String) : [], input.options)
94
- },
95
- { name: "resolveSeeds", aliases: ["resolve-seeds"], handler: passthrough("resolveSeeds") },
96
- { name: "queryGraph", aliases: ["query-graph"], handler: passthrough("queryGraph") },
97
- { name: "buildContextPack", aliases: ["build-context-pack"], handler: passthrough("buildContextPack") },
98
- {
99
- name: "parseGraphDsl",
100
- aliases: ["parse-graph-dsl"],
101
- handler: (sdk, input) => sdk.parseGraphDsl(String(input.source ?? input.query ?? ""))
102
- },
103
- {
104
- name: "resolveReference",
105
- aliases: ["resolve-reference"],
106
- handler: (sdk, input) => sdk.resolveReference(String(input.reference ?? ""), input.options)
107
- },
108
- {
109
- name: "explainReferenceChain",
110
- aliases: ["explain-reference-chain"],
111
- handler: (sdk, input) => sdk.explainReferenceChain(String(input.fromId ?? ""), String(input.toId ?? ""))
112
- }
113
- ];
114
- const SDK_OPERATION_INDEX = /* @__PURE__ */ new Map();
115
- for (const spec of SDK_OPERATION_SPECS) {
116
- SDK_OPERATION_INDEX.set(spec.name, spec);
117
- for (const alias of spec.aliases ?? []) {
118
- SDK_OPERATION_INDEX.set(alias, spec);
119
- }
120
- }
121
- function listSdkOperationNames() {
122
- return [...new Set(SDK_OPERATION_SPECS.map((entry) => entry.name))];
123
- }
124
- function findSdkOperation(name) {
125
- return SDK_OPERATION_INDEX.get(name) ?? null;
126
- }
1
+ import { AgentSdk as AgentSdkClass, executeSdkOperation, findSdkOperation, listSdkOperationNames } from "@treeseed/sdk";
127
2
  function resolveSdkInstance(sharedSdk, config, request) {
128
3
  if (!request.repoRoot || request.repoRoot === config.repoRoot) {
129
4
  return sharedSdk ?? new AgentSdkClass({ repoRoot: config.repoRoot });
130
5
  }
131
6
  return new AgentSdkClass({ repoRoot: request.repoRoot });
132
7
  }
133
- async function executeSdkOperation(sdk, operationName, input) {
134
- const spec = findSdkOperation(operationName);
135
- if (!spec) {
136
- throw new Error(`Unknown SDK operation "${operationName}".`);
137
- }
138
- return await spec.handler(sdk, input);
139
- }
140
8
  export {
141
9
  executeSdkOperation,
142
10
  findSdkOperation,
@@ -5,6 +5,7 @@ interface RegisterSdkRoutesOptions {
5
5
  config: ApiConfig;
6
6
  sharedSdk?: AgentSdk;
7
7
  scope: string;
8
+ prefix?: string;
8
9
  }
9
10
  export declare function registerSdkRoutes(app: Hono<any>, options: RegisterSdkRoutesOptions): void;
10
11
  export {};
@@ -1,7 +1,11 @@
1
1
  import { executeSdkOperation, resolveSdkInstance } from "./sdk-dispatch.js";
2
2
  import { jsonError, requireScope } from "./http.js";
3
+ function withPrefix(prefix, path) {
4
+ if (!prefix) return path;
5
+ return `${prefix}${path}`.replace(/\/{2,}/g, "/");
6
+ }
3
7
  function registerSdkRoutes(app, options) {
4
- app.post("/sdk/:operation", async (c) => {
8
+ app.post(withPrefix(options.prefix ?? "", "/sdk/:operation"), async (c) => {
5
9
  const unauthorized = requireScope(c, options.scope);
6
10
  if (unauthorized) return unauthorized;
7
11
  const operation = c.req.param("operation");
@@ -1,4 +1,5 @@
1
- import type { AgentSdk, SdkQueueMessageEnvelope } from '@treeseed/sdk';
1
+ import type { Hono } from 'hono';
2
+ import type { AgentSdk } from '@treeseed/sdk';
2
3
  import type { ApiPrincipal, ApiScope, DeviceCodeApproveRequest as SdkDeviceCodeApproveRequest, DeviceCodePollRequest, DeviceCodePollResponse, DeviceCodeStartRequest, DeviceCodeStartResponse, RemoteWorkflowOperationRequest as WorkflowHttpOperationRequest, RemoteWorkflowOperationResponse as ApiWorkflowOperationResponse, RemoteSdkOperationRequest as SdkHttpOperationRequest, TokenRefreshRequest, TokenRefreshResponse } from '@treeseed/sdk/remote';
3
4
  export type { ApiPrincipal, ApiScope, DeviceCodePollRequest, DeviceCodePollResponse, DeviceCodeStartRequest, DeviceCodeStartResponse, WorkflowHttpOperationRequest, ApiWorkflowOperationResponse, SdkHttpOperationRequest, TokenRefreshRequest, TokenRefreshResponse, };
4
5
  export type DeviceCodeApproveRequest = SdkDeviceCodeApproveRequest;
@@ -87,7 +88,11 @@ export interface ApiConfig {
87
88
  baseUrl: string;
88
89
  issuer: string;
89
90
  repoRoot: string;
91
+ projectId: string;
90
92
  authSecret: string;
93
+ projectApiKey?: string;
94
+ projectApiLabel: string;
95
+ projectApiPermissions: string[];
91
96
  cloudflareAccountId?: string;
92
97
  cloudflareApiToken?: string;
93
98
  d1DatabaseId?: string;
@@ -111,11 +116,11 @@ export interface AppVariables {
111
116
  principal: ApiPrincipal | null;
112
117
  actingUser: ApiPrincipal | null;
113
118
  credential: ApiCredential | null;
114
- actorType: 'anonymous' | 'user' | 'service';
119
+ actorType: 'anonymous' | 'user' | 'service' | 'project';
115
120
  permissionGrants: string[];
116
121
  }
117
122
  export interface ApiCredential {
118
- type: 'access_token' | 'personal_access_token' | 'service_secret' | 'service_token';
123
+ type: 'access_token' | 'personal_access_token' | 'service_secret' | 'service_token' | 'project_api_key' | 'team_api_key';
119
124
  id: string;
120
125
  label?: string;
121
126
  }
@@ -158,6 +163,28 @@ export interface ResolvedApiRuntimeProviders {
158
163
  };
159
164
  selections: ApiRuntimeProviderSelections;
160
165
  }
166
+ export interface ApiResolvedSettings {
167
+ config: ApiConfig;
168
+ surfaces: {
169
+ auth: boolean;
170
+ templates: boolean;
171
+ sdk: boolean;
172
+ agent: boolean;
173
+ operations: boolean;
174
+ };
175
+ scopes: {
176
+ authMe: ApiScope;
177
+ sdk: ApiScope;
178
+ agent: ApiScope;
179
+ operations: ApiScope;
180
+ };
181
+ }
182
+ export interface ApiAppRuntime {
183
+ resolved: ApiResolvedSettings;
184
+ runtimeProviders: ResolvedApiRuntimeProviders;
185
+ sharedSdk: AgentSdk;
186
+ internalPrefix: string;
187
+ }
161
188
  export interface ApiServerOptions {
162
189
  config?: Partial<ApiConfig>;
163
190
  runtimeProviders?: ApiRuntimeProviders;
@@ -176,18 +203,7 @@ export interface ApiServerOptions {
176
203
  agent: ApiScope;
177
204
  operations: ApiScope;
178
205
  }>;
206
+ internalPrefix?: string;
207
+ extendApp?: (app: Hono<any>, runtime: ApiAppRuntime) => void;
179
208
  log?: (message: string, details?: Record<string, unknown>) => void;
180
209
  }
181
- export interface GatewayQueueProducer {
182
- enqueue(request: {
183
- queueName?: string;
184
- message: SdkQueueMessageEnvelope;
185
- delaySeconds?: number;
186
- }): Promise<void>;
187
- }
188
- export interface GatewayServerOptions {
189
- sdk: AgentSdk;
190
- bearerToken: string;
191
- queueProducer?: GatewayQueueProducer;
192
- projectId?: string;
193
- }
package/dist/dev.js CHANGED
@@ -38,6 +38,28 @@ function resolveNodeEntrypoint(packageDir, sourceRelativePath, distRelativePath)
38
38
  args: [resolve(packageDir, distRelativePath)]
39
39
  };
40
40
  }
41
+ function resolveTenantApiEntrypoint(tenantRoot, runTsPath) {
42
+ const javascriptCandidates = [
43
+ resolve(tenantRoot, "src", "api", "server.js"),
44
+ resolve(tenantRoot, "src", "api", "server.mjs")
45
+ ];
46
+ for (const candidate of javascriptCandidates) {
47
+ if (existsSync(candidate)) {
48
+ return {
49
+ command: process.execPath,
50
+ args: [candidate]
51
+ };
52
+ }
53
+ }
54
+ const typescriptCandidate = resolve(tenantRoot, "src", "api", "server.ts");
55
+ if (existsSync(typescriptCandidate) && existsSync(runTsPath)) {
56
+ return {
57
+ command: process.execPath,
58
+ args: [runTsPath, typescriptCandidate]
59
+ };
60
+ }
61
+ return null;
62
+ }
41
63
  function withWatchArgs(args, watchPaths) {
42
64
  return watchPaths.flatMap((watchPath) => ["--watch-path", watchPath]).concat(args);
43
65
  }
@@ -55,12 +77,13 @@ function createTreeseedIntegratedDevPlan(options = {}) {
55
77
  const mergedEnv = { ...process.env, ...options.env ?? {} };
56
78
  const apiBaseUrl = mergedEnv.TREESEED_API_BASE_URL?.trim() || `http://${apiHost}:${apiPort}`;
57
79
  const sdkPackageRoot = resolvePackageRoot("@treeseed/sdk", tenantRoot);
80
+ const coreRunTsPath = resolve(packageRoot, "scripts", "run-ts.mjs");
58
81
  const webEntrypoint = resolveNodeEntrypoint(
59
82
  sdkPackageRoot,
60
83
  "scripts/tenant-astro-command.ts",
61
84
  "dist/scripts/tenant-astro-command.js"
62
85
  );
63
- const apiEntrypoint = resolveNodeEntrypoint(
86
+ const apiEntrypoint = resolveTenantApiEntrypoint(tenantRoot, coreRunTsPath) ?? resolveNodeEntrypoint(
64
87
  packageRoot,
65
88
  "src/api/server.ts",
66
89
  "dist/api/server.js"
package/dist/index.d.ts CHANGED
@@ -2,7 +2,6 @@ export { buildTreeseedSiteLayers, resolveTreeseedPageEntrypoint, resolveTreeseed
2
2
  export { buildTreeseedPlatformLayers, resolveTreeseedPlatformResource, TREESEED_PLATFORM_RESOURCE_KINDS, } from './platform-resources';
3
3
  export { parseSiteConfig } from './utils/site-config-schema.js';
4
4
  export { createTreeseedApiApp } from './api/app';
5
- export { createTreeseedGatewayApp } from './api/gateway';
6
5
  export { createRailwayTreeseedApiServer } from './api/railway';
7
6
  export { resolveApiConfig } from './api/config';
8
7
  export { createTreeseedIntegratedDevPlan, runTreeseedIntegratedDev, type TreeseedIntegratedDevCommand, type TreeseedIntegratedDevOptions, type TreeseedIntegratedDevPlan, type TreeseedIntegratedDevSurface, } from './dev';
package/dist/index.js CHANGED
@@ -12,7 +12,6 @@ import {
12
12
  } from "./platform-resources.js";
13
13
  import { parseSiteConfig } from "./utils/site-config-schema.js";
14
14
  import { createTreeseedApiApp } from "./api/app.js";
15
- import { createTreeseedGatewayApp } from "./api/gateway.js";
16
15
  import { createRailwayTreeseedApiServer } from "./api/railway.js";
17
16
  import { resolveApiConfig } from "./api/config.js";
18
17
  import {
@@ -26,7 +25,6 @@ export {
26
25
  buildTreeseedSiteLayers,
27
26
  createRailwayTreeseedApiServer,
28
27
  createTreeseedApiApp,
29
- createTreeseedGatewayApp,
30
28
  createTreeseedIntegratedDevPlan,
31
29
  parseSiteConfig,
32
30
  resolveApiConfig,
@@ -1,112 +1,17 @@
1
- import fs from 'node:fs';
2
1
  import path from 'node:path';
3
- import { BOOKS, TREESEED_LIBRARY_DOWNLOAD } from '@treeseed/sdk/platform/books-data';
2
+ import { exportTenantBookPackages } from '@treeseed/sdk/platform/book-export';
4
3
  import { PROJECT_TENANT } from '../tenant/bridge.js';
5
- const projectRoot = PROJECT_TENANT.__tenantRoot ?? process.cwd();
6
- const outputDir = path.join(projectRoot, 'public', 'books');
7
- const legacyOutputFile = path.join(projectRoot, 'public', 'book.md');
8
- function sortPaths(paths) {
9
- return [...paths].sort((left, right) => left.localeCompare(right, undefined, { numeric: true, sensitivity: 'base' }));
10
- }
11
- function getSidebarOrder(filePath) {
12
- const rawContent = fs.readFileSync(filePath, 'utf8');
13
- const frontmatterMatch = rawContent.match(/^---\r?\n([\s\S]*?)\r?\n---/);
14
- if (!frontmatterMatch)
15
- return Number.POSITIVE_INFINITY;
16
- const orderMatch = frontmatterMatch[1].match(/^\s{2}order:\s*(\d+)\s*$/m);
17
- return orderMatch ? Number.parseInt(orderMatch[1], 10) : Number.POSITIVE_INFINITY;
18
- }
19
- function collectMarkdownFiles(rootPath) {
20
- if (!fs.existsSync(rootPath)) {
21
- throw new Error(`Book export root not found: ${rootPath}`);
22
- }
23
- const stats = fs.statSync(rootPath);
24
- if (stats.isFile()) {
25
- return [rootPath];
26
- }
27
- const entries = fs.readdirSync(rootPath, { withFileTypes: true });
28
- return sortPaths(entries.flatMap((entry) => {
29
- const fullPath = path.join(rootPath, entry.name);
30
- if (entry.isDirectory()) {
31
- return collectMarkdownFiles(fullPath);
32
- }
33
- if (entry.isFile() && (entry.name.endsWith('.md') || entry.name.endsWith('.mdx'))) {
34
- return [fullPath];
35
- }
36
- return [];
37
- }));
38
- }
39
- function stripFrontmatter(content) {
40
- return content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '').trim();
41
- }
42
- function stripMdxOnlySyntax(content) {
43
- return content
44
- .replace(/^import\s.+$/gm, '')
45
- .replace(/^\s*<\/?[A-Z][^>]*>\s*$/gm, '')
46
- .replace(/\n{3,}/g, '\n\n')
47
- .trim();
48
- }
49
- function inferExportRootFromBasePath(book) {
50
- const normalizedBasePath = String(book.basePath || '').trim();
51
- const knowledgePrefix = '/knowledge/';
52
- if (!normalizedBasePath.startsWith(knowledgePrefix)) {
53
- throw new Error(`Book basePath must start with "${knowledgePrefix}" to infer exports: ${book.basePath}`);
54
- }
55
- const relativeKnowledgePath = normalizedBasePath
56
- .slice(knowledgePrefix.length)
57
- .replace(/^\/+|\/+$/g, '');
58
- if (!relativeKnowledgePath) {
59
- throw new Error(`Book basePath must identify a knowledge directory: ${book.basePath}`);
60
- }
61
- return path.join(PROJECT_TENANT.content.docs, relativeKnowledgePath);
62
- }
63
- function resolveExportRoots(book) {
64
- if (Array.isArray(book.exportRoots) && book.exportRoots.length > 0) {
65
- return book.exportRoots;
66
- }
67
- return [inferExportRootFromBasePath(book)];
68
- }
69
- function resolveBookFiles(book) {
70
- const files = resolveExportRoots(book).flatMap((root) => collectMarkdownFiles(path.resolve(projectRoot, root)).sort((left, right) => {
71
- const orderDelta = getSidebarOrder(left) - getSidebarOrder(right);
72
- if (orderDelta !== 0)
73
- return orderDelta;
74
- return left.localeCompare(right, undefined, { numeric: true, sensitivity: 'base' });
75
- }));
76
- return Array.from(new Set(files));
77
- }
78
- function buildBookMarkdown(book) {
79
- const sections = resolveBookFiles(book).map((filePath) => {
80
- const rawContent = fs.readFileSync(filePath, 'utf8');
81
- return stripMdxOnlySyntax(stripFrontmatter(rawContent));
82
- });
83
- return `# ${book.downloadTitle}\n\n> This document is auto-generated from the TreeSeed knowledge source.\n\n${sections.join('\n\n---\n\n')}\n`;
84
- }
85
- function ensureOutputDir() {
86
- fs.mkdirSync(outputDir, { recursive: true });
87
- }
88
- function writeBookOutput(fileName, content) {
89
- const outputPath = path.join(outputDir, fileName);
90
- fs.writeFileSync(outputPath, content);
91
- return outputPath;
92
- }
93
- function main() {
94
- console.log('Generating TreeSeed knowledge exports...');
95
- ensureOutputDir();
96
- const bookOutputs = BOOKS.map((book) => {
97
- const content = buildBookMarkdown(book);
98
- const outputPath = writeBookOutput(book.downloadFileName, content);
99
- console.log(`Generated ${path.relative(projectRoot, outputPath)}`);
100
- return { book, content };
101
- });
102
- const compositeContent = `# ${TREESEED_LIBRARY_DOWNLOAD.downloadTitle}\n\n> This document is auto-generated from the TreeSeed knowledge source.\n\n${bookOutputs
103
- .map(({ content }) => content.trim())
104
- .join('\n\n---\n\n')}\n`;
105
- const compositeOutputPath = writeBookOutput(TREESEED_LIBRARY_DOWNLOAD.downloadFileName, compositeContent);
106
- console.log(`Generated ${path.relative(projectRoot, compositeOutputPath)}`);
107
- if (fs.existsSync(legacyOutputFile)) {
108
- fs.rmSync(legacyOutputFile);
109
- console.log(`Removed legacy export ${path.relative(projectRoot, legacyOutputFile)}`);
4
+ async function main() {
5
+ console.log('Generating Treeseed AI book packages...');
6
+ const result = await exportTenantBookPackages({ projectRoot: PROJECT_TENANT.__tenantRoot ?? process.cwd() });
7
+ for (const entry of result.bookPackages) {
8
+ console.log(`Generated ${path.relative(result.projectRoot, entry.markdownPath)}`);
9
+ console.log(`Generated ${path.relative(result.projectRoot, entry.indexPath)}`);
110
10
  }
11
+ console.log(`Generated ${path.relative(result.projectRoot, result.libraryPackage.markdownPath)}`);
12
+ console.log(`Generated ${path.relative(result.projectRoot, result.libraryPackage.indexPath)}`);
111
13
  }
112
- main();
14
+ main().catch((error) => {
15
+ console.error(error instanceof Error ? error.message : String(error));
16
+ process.exitCode = 1;
17
+ });
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readdirSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
1
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
2
2
  import { mkdtempSync } from 'node:fs';
3
3
  import { tmpdir } from 'node:os';
4
4
  import { dirname, join, resolve } from 'node:path';
@@ -24,36 +24,92 @@ function run(command, args, cwd = packageRoot, capture = false) {
24
24
  }
25
25
  return (result.stdout ?? '').trim();
26
26
  }
27
- function resolveNodeModulesRoot() {
28
- let lastCandidate = null;
29
- let current = packageRoot;
30
- while (true) {
31
- const candidate = resolve(current, 'node_modules');
32
- try {
33
- readdirSync(candidate);
34
- lastCandidate = candidate;
27
+ function runtimeDependencyNamesFor(root) {
28
+ const packageJson = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8'));
29
+ return Object.keys(packageJson.dependencies ?? {});
30
+ }
31
+ function resolveWorkspaceRuntimePackageRoots() {
32
+ const roots = new Map();
33
+ for (const dependencyName of runtimeDependencyNamesFor(packageRoot)) {
34
+ const dependencyRoot = resolveTreeseedRuntimeDependencyRoot(dependencyName);
35
+ if (dependencyRoot) {
36
+ roots.set(dependencyName, dependencyRoot);
37
+ }
38
+ }
39
+ return roots;
40
+ }
41
+ function collectRuntimeDependenciesForPackaging() {
42
+ const dependencyNames = new Set(runtimeDependencyNamesFor(packageRoot));
43
+ const searchRoots = [
44
+ packageRoot,
45
+ sdkPackageRoot,
46
+ ];
47
+ const queue = [...resolveWorkspaceRuntimePackageRoots().values()];
48
+ const visited = new Set();
49
+ while (queue.length > 0) {
50
+ const nextRoot = queue.shift();
51
+ if (!nextRoot || visited.has(nextRoot)) {
52
+ continue;
35
53
  }
36
- catch {
54
+ visited.add(nextRoot);
55
+ for (const dependencyName of runtimeDependencyNamesFor(nextRoot)) {
56
+ dependencyNames.add(dependencyName);
57
+ const dependencyRoot = resolveTreeseedRuntimeDependencyRoot(dependencyName, searchRoots);
58
+ if (dependencyRoot) {
59
+ queue.push(dependencyRoot);
60
+ }
61
+ }
62
+ }
63
+ return dependencyNames;
64
+ }
65
+ function resolveTreeseedRuntimeDependencyRoot(packageName, searchRoots = [packageRoot, sdkPackageRoot]) {
66
+ if (!packageName.startsWith('@treeseed/')) {
67
+ return null;
68
+ }
69
+ const folderName = packageName.slice('@treeseed/'.length);
70
+ const workspaceCandidateRoot = resolve(packageRoot, '..', folderName);
71
+ if (existsSync(resolve(workspaceCandidateRoot, 'package.json'))) {
72
+ return workspaceCandidateRoot;
73
+ }
74
+ try {
75
+ return resolveInstalledPackageRoot(packageName, searchRoots);
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ }
81
+ function resolveInstalledPackageRoot(packageName, searchRoots) {
82
+ let resolvedEntry = require.resolve(packageName, { paths: searchRoots });
83
+ let current = dirname(resolvedEntry);
84
+ while (true) {
85
+ const packageJsonPath = resolve(current, 'package.json');
86
+ if (existsSync(packageJsonPath)) {
87
+ return current;
37
88
  }
38
89
  const parent = resolve(current, '..');
39
- if (parent === current)
40
- break;
90
+ if (parent === current) {
91
+ throw new Error(`Unable to resolve installed package root for ${packageName}.`);
92
+ }
41
93
  current = parent;
42
94
  }
43
- if (lastCandidate) {
44
- return lastCandidate;
45
- }
46
- throw new Error(`Unable to locate node_modules for ${packageRoot}.`);
47
95
  }
48
- function mirrorDependencies(tempRoot) {
49
- const sharedNodeModules = resolveNodeModulesRoot();
50
- for (const entry of readdirSync(sharedNodeModules, { withFileTypes: true })) {
51
- if (entry.name === '.bin' || entry.name === '@treeseed') {
96
+ function mirrorDependencies(tempRoot, excludedPackages = new Set()) {
97
+ const runtimeDependencies = collectRuntimeDependenciesForPackaging();
98
+ const searchRoots = [
99
+ packageRoot,
100
+ ...resolveWorkspaceRuntimePackageRoots().values(),
101
+ ];
102
+ for (const packageName of runtimeDependencies) {
103
+ if (excludedPackages.has(packageName) || packageName === '@treeseed/core') {
104
+ continue;
105
+ }
106
+ const sourcePath = resolveInstalledPackageRoot(packageName, searchRoots);
107
+ const targetPath = resolve(tempRoot, 'node_modules', ...packageName.split('/'));
108
+ if (existsSync(targetPath)) {
52
109
  continue;
53
110
  }
54
- const targetPath = resolve(tempRoot, 'node_modules', entry.name);
55
111
  mkdirSync(dirname(targetPath), { recursive: true });
56
- symlinkSync(resolve(sharedNodeModules, entry.name), targetPath, 'dir');
112
+ symlinkSync(sourcePath, targetPath, 'dir');
57
113
  }
58
114
  }
59
115
  function pack(root, outputRoot, fallbackName) {
@@ -98,6 +154,7 @@ try {
98
154
  mkdirSync(packRoot, { recursive: true });
99
155
  mkdirSync(extractRoot, { recursive: true });
100
156
  const coreTarball = pack(packageRoot, packRoot, 'treeseed-core.tgz');
157
+ const workspaceRuntimePackageRoots = resolveWorkspaceRuntimePackageRoots();
101
158
  if (existsSync(resolve(sdkPackageRoot, 'scripts', 'run-ts.mjs'))) {
102
159
  const sdkTarball = pack(sdkPackageRoot, packRoot, 'treeseed-sdk.tgz');
103
160
  installPackagedPackage(extractRoot, installRoot, sdkTarball, 'sdk');
@@ -106,7 +163,7 @@ try {
106
163
  installPackageDirectory(installRoot, sdkPackageRoot, 'sdk');
107
164
  }
108
165
  installPackagedPackage(extractRoot, installRoot, coreTarball, 'core');
109
- mirrorDependencies(installRoot);
166
+ mirrorDependencies(installRoot, new Set(workspaceRuntimePackageRoots.keys()));
110
167
  writeFileSync(resolve(installRoot, 'package.json'), `${JSON.stringify({ name: 'treeseed-core-smoke', private: true, type: 'module' }, null, 2)}\n`, 'utf8');
111
168
  run(process.execPath, [
112
169
  '--input-type=module',
@@ -117,7 +174,6 @@ try {
117
174
  'await import("@treeseed/core/runtime-types");',
118
175
  'await import("@treeseed/core/contracts/messages");',
119
176
  'await import("@treeseed/core/contracts/run");',
120
- 'await import("@treeseed/core/services/manager");',
121
177
  'await import("@treeseed/core/services/worker");',
122
178
  'await import("@treeseed/core/services/workday-start");',
123
179
  'await import("@treeseed/core/services/workday-report");',