@treeseed/sdk 0.4.13 → 0.5.1

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 (83) 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/verification.js +90 -2
  74. package/dist/workflow/operations.d.ts +98 -0
  75. package/dist/workflow/operations.js +229 -7
  76. package/dist/workflow-state.d.ts +54 -2
  77. package/dist/workflow-state.js +170 -24
  78. package/dist/workflow-support.d.ts +1 -1
  79. package/dist/workflow-support.js +32 -2
  80. package/dist/workflow.d.ts +29 -0
  81. package/package.json +1 -1
  82. package/templates/github/deploy.workflow.yml +11 -1
  83. package/dist/scripts/sync-dev-vars.js +0 -6
@@ -2,18 +2,23 @@ import type { TreeseedDeployConfig, TreeseedTenantConfig } from './contracts.ts'
2
2
  import { type LoadedTreeseedPluginEntry } from './plugins.ts';
3
3
  export declare const TREESEED_ENVIRONMENT_SCOPES: readonly ["local", "staging", "prod"];
4
4
  export declare const TREESEED_ENVIRONMENT_REQUIREMENTS: readonly ["required", "conditional", "optional"];
5
- export declare const TREESEED_ENVIRONMENT_TARGETS: readonly ["local-file", "wrangler-dev-vars", "github-secret", "github-variable", "cloudflare-secret", "cloudflare-var", "railway-secret", "railway-var", "config-file"];
5
+ export declare const TREESEED_ENVIRONMENT_TARGETS: readonly ["local-runtime", "local-cloudflare", "github-secret", "github-variable", "cloudflare-secret", "cloudflare-var", "railway-secret", "railway-var", "config-file"];
6
6
  export declare const TREESEED_ENVIRONMENT_PURPOSES: readonly ["dev", "save", "deploy", "destroy", "config"];
7
7
  export declare const TREESEED_ENVIRONMENT_SENSITIVITY: readonly ["secret", "plain", "derived"];
8
8
  export declare const TREESEED_ENVIRONMENT_STORAGE: readonly ["scoped", "shared"];
9
+ export declare const TREESEED_CONFIG_STARTUP_PROFILES: readonly ["core", "optional", "advanced"];
9
10
  export type TreeseedEnvironmentScope = (typeof TREESEED_ENVIRONMENT_SCOPES)[number];
10
11
  export type TreeseedEnvironmentRequirement = (typeof TREESEED_ENVIRONMENT_REQUIREMENTS)[number];
11
12
  export type TreeseedEnvironmentTarget = (typeof TREESEED_ENVIRONMENT_TARGETS)[number];
12
13
  export type TreeseedEnvironmentPurpose = (typeof TREESEED_ENVIRONMENT_PURPOSES)[number];
13
14
  export type TreeseedEnvironmentSensitivity = (typeof TREESEED_ENVIRONMENT_SENSITIVITY)[number];
14
15
  export type TreeseedEnvironmentStorage = (typeof TREESEED_ENVIRONMENT_STORAGE)[number];
16
+ export type TreeseedConfigStartupProfile = (typeof TREESEED_CONFIG_STARTUP_PROFILES)[number];
15
17
  export type TreeseedEnvironmentValidation = {
16
- kind: 'string' | 'nonempty' | 'boolean' | 'number' | 'url' | 'email';
18
+ kind: 'string' | 'nonempty' | 'url' | 'email';
19
+ minLength?: number;
20
+ } | {
21
+ kind: 'boolean' | 'number';
17
22
  } | {
18
23
  kind: 'enum';
19
24
  values: string[];
@@ -60,6 +65,9 @@ export type TreeseedEnvironmentEntry = {
60
65
  id: string;
61
66
  label: string;
62
67
  group: string;
68
+ cluster?: string;
69
+ onboardingFeature?: string;
70
+ startupProfile?: TreeseedConfigStartupProfile;
63
71
  description: string;
64
72
  howToGet: string;
65
73
  sensitivity: TreeseedEnvironmentSensitivity;
@@ -76,6 +84,9 @@ export type TreeseedEnvironmentEntry = {
76
84
  requiredWhen?: (context: TreeseedEnvironmentContext, scope: TreeseedEnvironmentScope, purpose?: TreeseedEnvironmentPurpose) => boolean;
77
85
  };
78
86
  export type TreeseedEnvironmentEntryYaml = Omit<TreeseedEnvironmentEntry, 'id' | 'defaultValue' | 'localDefaultValue' | 'isRelevant' | 'requiredWhen'> & {
87
+ cluster?: string;
88
+ onboardingFeature?: string;
89
+ startupProfile?: TreeseedConfigStartupProfile;
79
90
  defaultValueRef?: string;
80
91
  localDefaultValueRef?: string;
81
92
  relevanceRef?: string;
@@ -9,8 +9,8 @@ import { loadTreeseedManifest } from "./tenant-config.js";
9
9
  const TREESEED_ENVIRONMENT_SCOPES = ["local", "staging", "prod"];
10
10
  const TREESEED_ENVIRONMENT_REQUIREMENTS = ["required", "conditional", "optional"];
11
11
  const TREESEED_ENVIRONMENT_TARGETS = [
12
- "local-file",
13
- "wrangler-dev-vars",
12
+ "local-runtime",
13
+ "local-cloudflare",
14
14
  "github-secret",
15
15
  "github-variable",
16
16
  "cloudflare-secret",
@@ -22,8 +22,17 @@ const TREESEED_ENVIRONMENT_TARGETS = [
22
22
  const TREESEED_ENVIRONMENT_PURPOSES = ["dev", "save", "deploy", "destroy", "config"];
23
23
  const TREESEED_ENVIRONMENT_SENSITIVITY = ["secret", "plain", "derived"];
24
24
  const TREESEED_ENVIRONMENT_STORAGE = ["scoped", "shared"];
25
+ const TREESEED_CONFIG_STARTUP_PROFILES = ["core", "optional", "advanced"];
25
26
  const moduleDir = dirname(fileURLToPath(import.meta.url));
26
- const CORE_ENVIRONMENT_PATH = resolve(moduleDir, "env.yaml");
27
+ function resolveCoreEnvironmentPath() {
28
+ const candidates = [
29
+ resolve(moduleDir, "env.yaml"),
30
+ resolve(moduleDir, "../src/platform/env.yaml"),
31
+ resolve(moduleDir, "../dist/platform/env.yaml")
32
+ ];
33
+ return candidates.find((candidate) => existsSync(candidate)) ?? candidates[0];
34
+ }
35
+ const CORE_ENVIRONMENT_PATH = resolveCoreEnvironmentPath();
27
36
  const TENANT_ENVIRONMENT_OVERLAY_PATH = "src/env.yaml";
28
37
  function loadOptionalTenantConfig() {
29
38
  try {
@@ -33,16 +42,31 @@ function loadOptionalTenantConfig() {
33
42
  }
34
43
  }
35
44
  function turnstileEnabled(context) {
36
- return context.deployConfig.turnstile?.enabled !== false;
45
+ return context.deployConfig.turnstile?.enabled === true;
37
46
  }
38
47
  function smtpEnabled(context) {
39
48
  return context.deployConfig.smtp?.enabled === true;
40
49
  }
41
50
  function railwayManagedEnabled(context) {
51
+ if (context.deployConfig.runtime?.mode === "treeseed_managed") {
52
+ return true;
53
+ }
54
+ if (context.deployConfig.runtime?.mode && context.deployConfig.runtime.mode !== "treeseed_managed") {
55
+ return false;
56
+ }
42
57
  return Object.values(context.deployConfig.services ?? {}).some(
43
58
  (service) => service && service.enabled !== false && (service.provider ?? "railway") === "railway"
44
59
  );
45
60
  }
61
+ function resolveHubMode(context) {
62
+ return context.deployConfig.hub?.mode ?? "treeseed_hosted";
63
+ }
64
+ function resolveRuntimeMode(context) {
65
+ return context.deployConfig.runtime?.mode ?? "none";
66
+ }
67
+ function resolveRuntimeRegistration(context) {
68
+ return context.deployConfig.runtime?.registration ?? "none";
69
+ }
46
70
  function resolveHostingKind(context) {
47
71
  return context.deployConfig.hosting?.kind ?? "self_hosted_project";
48
72
  }
@@ -59,14 +83,26 @@ function selfHostedProjectEnabled(context) {
59
83
  return resolveHostingKind(context) === "self_hosted_project";
60
84
  }
61
85
  function projectRegistrationEnabled(context) {
62
- if (hostedProjectEnabled(context)) {
63
- return true;
64
- }
65
- return selfHostedProjectEnabled(context) && resolveHostingRegistration(context) === "optional";
86
+ return resolveRuntimeRegistration(context) === "optional" || resolveRuntimeRegistration(context) === "required";
66
87
  }
67
88
  function generatedSecret(bytes = 24) {
68
89
  return randomBytes(bytes).toString("hex");
69
90
  }
91
+ function localTimezoneDefault() {
92
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
93
+ }
94
+ function localMailpitHostDefault() {
95
+ return "127.0.0.1";
96
+ }
97
+ function localMailpitPortDefault() {
98
+ return "1025";
99
+ }
100
+ function contactEmailDefault(context) {
101
+ return context.deployConfig.contactEmail?.trim() || "contact@example.com";
102
+ }
103
+ function workdayWindowsDefault() {
104
+ return JSON.stringify([{ days: [1, 2, 3, 4, 5], startTime: "09:00", endTime: "17:00" }]);
105
+ }
70
106
  function normalizeUrl(value) {
71
107
  return value.trim().replace(/\/$/u, "");
72
108
  }
@@ -124,29 +160,32 @@ function resolveApiWebServiceId(values = {}) {
124
160
  return values.TREESEED_WEB_SERVICE_ID?.trim() || "web";
125
161
  }
126
162
  function resolvePagesProjectName(context) {
127
- return context.deployConfig.cloudflare.pages?.projectName?.trim() || context.deployConfig.slug;
163
+ return context.deployConfig.slug;
128
164
  }
129
- function resolvePagesPreviewProjectName(context) {
130
- return context.deployConfig.cloudflare.pages?.previewProjectName?.trim() || `${context.deployConfig.cloudflare.pages?.projectName?.trim() || context.deployConfig.slug}-staging`;
165
+ function resolvePagesPreviewProjectName(context, _scope, values = {}) {
166
+ return values.TREESEED_CLOUDFLARE_PAGES_PROJECT_NAME?.trim() || `${context.deployConfig.slug}-staging`;
131
167
  }
132
168
  function resolveContentBucketName(context) {
133
- return context.deployConfig.cloudflare.r2?.bucketName?.trim() || `${context.deployConfig.slug}-content`;
169
+ return `${context.deployConfig.slug}-content`;
134
170
  }
135
171
  function resolveContentBucketBinding(context) {
136
172
  return context.deployConfig.cloudflare.r2?.binding?.trim() || "TREESEED_CONTENT_BUCKET";
137
173
  }
138
- function resolveMarketBaseUrl(context) {
139
- return context.deployConfig.hosting?.marketBaseUrl?.trim() || context.deployConfig.services?.api?.environments?.prod?.baseUrl?.trim() || context.deployConfig.services?.api?.publicBaseUrl?.trim() || "https://api.treeseed.ai";
174
+ function resolveMarketBaseUrl(context, _scope, values = {}) {
175
+ return values.TREESEED_API_BASE_URL?.trim() || context.deployConfig.services?.api?.environments?.prod?.baseUrl?.trim() || "https://api.treeseed.ai";
140
176
  }
141
177
  function resolveHostedTeamId(context) {
142
- return context.deployConfig.hosting?.teamId?.trim() || context.deployConfig.slug;
178
+ return context.deployConfig.slug;
143
179
  }
144
180
  function resolveHostedProjectId(context) {
145
- return context.deployConfig.hosting?.projectId?.trim() || context.deployConfig.slug;
181
+ return context.deployConfig.slug;
146
182
  }
147
183
  const VALUE_RESOLVERS = {
148
184
  generatedSecret: () => generatedSecret(),
149
185
  localFormsBypassDefault: () => "true",
186
+ localMailpitHostDefault: () => localMailpitHostDefault(),
187
+ localMailpitPortDefault: () => localMailpitPortDefault(),
188
+ contactEmailDefault: (context) => contactEmailDefault(context),
150
189
  projectDomainsDefault: (context) => primaryHostFromUrl(context.deployConfig.siteUrl),
151
190
  apiBaseUrlDefault: (context, scope, values) => resolveConfiguredApiBaseUrl(context, scope, values),
152
191
  webServiceIdDefault: (_context, _scope, values) => resolveWebServiceId(values),
@@ -157,9 +196,24 @@ const VALUE_RESOLVERS = {
157
196
  contentBucketBindingDefault: (context) => resolveContentBucketBinding(context),
158
197
  hostingKindDefault: (context) => resolveHostingKind(context),
159
198
  hostingRegistrationDefault: (context) => resolveHostingRegistration(context),
199
+ hubModeDefault: (context) => resolveHubMode(context),
200
+ runtimeModeDefault: (context) => resolveRuntimeMode(context),
201
+ runtimeRegistrationDefault: (context) => resolveRuntimeRegistration(context),
160
202
  marketBaseUrlDefault: (context) => resolveMarketBaseUrl(context),
161
203
  hostingTeamIdDefault: (context) => resolveHostedTeamId(context),
162
- hostingProjectIdDefault: (context) => resolveHostedProjectId(context)
204
+ hostingProjectIdDefault: (context) => resolveHostedProjectId(context),
205
+ agentPoolMinWorkersDefault: () => "0",
206
+ agentPoolMaxWorkersDefault: () => "2",
207
+ agentPoolTargetQueueDepthDefault: () => "1",
208
+ agentPoolCooldownSecondsDefault: () => "60",
209
+ workdayTimezoneDefault: () => localTimezoneDefault(),
210
+ workdayWindowsDefault: () => workdayWindowsDefault(),
211
+ workdayTaskCreditBudgetDefault: () => "20",
212
+ managerMaxQueuedTasksDefault: () => "5",
213
+ managerMaxQueuedCreditsDefault: () => "20",
214
+ managerPriorityModelsDefault: () => "objective,question,note,page,book,knowledge",
215
+ taskCreditWeightsDefault: () => "[]",
216
+ workerPoolScalerDefault: (context) => railwayManagedEnabled(context) ? "railway" : "noop"
163
217
  };
164
218
  const PREDICATES = {
165
219
  turnstileEnabled: (context) => turnstileEnabled(context),
@@ -167,6 +221,11 @@ const PREDICATES = {
167
221
  smtpEnabled: (context) => smtpEnabled(context),
168
222
  smtpNonLocal: (context, scope) => smtpEnabled(context) && scope !== "local",
169
223
  railwayManagedEnabled: (context) => railwayManagedEnabled(context),
224
+ hubTreeseedHosted: (context) => resolveHubMode(context) === "treeseed_hosted",
225
+ hubCustomerHosted: (context) => resolveHubMode(context) === "customer_hosted",
226
+ runtimeNone: (context) => resolveRuntimeMode(context) === "none",
227
+ runtimeByoAttached: (context) => resolveRuntimeMode(context) === "byo_attached",
228
+ runtimeTreeseedManaged: (context) => resolveRuntimeMode(context) === "treeseed_managed",
170
229
  marketControlPlaneEnabled: (context) => marketControlPlaneEnabled(context),
171
230
  hostedProjectEnabled: (context) => hostedProjectEnabled(context),
172
231
  selfHostedProjectEnabled: (context) => selfHostedProjectEnabled(context),
@@ -251,6 +310,9 @@ function materializeEntry(id, entry) {
251
310
  return {
252
311
  ...entry,
253
312
  id,
313
+ cluster: entry.cluster ?? `${entry.group}:${id}`,
314
+ onboardingFeature: entry.onboardingFeature,
315
+ startupProfile: entry.startupProfile ?? (entry.onboardingFeature ? "optional" : entry.group === "auth" || entry.id === "TREESEED_FORM_TOKEN_SECRET" || entry.group === "local-development" ? "core" : "advanced"),
254
316
  storage: entry.storage ?? "scoped",
255
317
  defaultValue: resolveNamedValueResolver(entry.defaultValueRef),
256
318
  localDefaultValue: resolveNamedValueResolver(entry.localDefaultValueRef),
@@ -260,7 +322,7 @@ function materializeEntry(id, entry) {
260
322
  }
261
323
  function mergeEntryYaml(baseEntry, id, override) {
262
324
  const merged = baseEntry ? deepMerge(baseEntry, override) : override;
263
- if (typeof merged.label !== "string" || typeof merged.group !== "string" || typeof merged.description !== "string" || typeof merged.howToGet !== "string" || !Array.isArray(merged.targets) || !Array.isArray(merged.scopes) || typeof merged.requirement !== "string" || !Array.isArray(merged.purposes) || typeof merged.sensitivity !== "string") {
325
+ if (typeof merged.label !== "string" || typeof merged.group !== "string" || merged.cluster !== void 0 && typeof merged.cluster !== "string" || merged.onboardingFeature !== void 0 && typeof merged.onboardingFeature !== "string" || merged.startupProfile !== void 0 && !TREESEED_CONFIG_STARTUP_PROFILES.includes(merged.startupProfile) || typeof merged.description !== "string" || typeof merged.howToGet !== "string" || !Array.isArray(merged.targets) || !Array.isArray(merged.scopes) || typeof merged.requirement !== "string" || !Array.isArray(merged.purposes) || typeof merged.sensitivity !== "string") {
264
326
  throw new Error(`Treeseed environment registry entry "${id}" is missing required metadata after merge.`);
265
327
  }
266
328
  return merged;
@@ -384,8 +446,15 @@ function validateValue(entry, value) {
384
446
  }
385
447
  switch (entry.validation.kind) {
386
448
  case "string":
387
- case "nonempty":
388
- return valuePresent(value) ? null : `${entry.id} must be a non-empty string.`;
449
+ case "nonempty": {
450
+ if (!valuePresent(value)) {
451
+ return `${entry.id} must be a non-empty string.`;
452
+ }
453
+ if (typeof entry.validation.minLength === "number" && value.trim().length < entry.validation.minLength) {
454
+ return `${entry.id} must be at least ${entry.validation.minLength} characters.`;
455
+ }
456
+ return null;
457
+ }
389
458
  case "boolean":
390
459
  return /^(true|false|1|0)$/i.test(value) ? null : `${entry.id} must be true or false.`;
391
460
  case "number":
@@ -447,6 +516,7 @@ function validateTreeseedEnvironmentValues(options) {
447
516
  };
448
517
  }
449
518
  export {
519
+ TREESEED_CONFIG_STARTUP_PROFILES,
450
520
  TREESEED_ENVIRONMENT_PURPOSES,
451
521
  TREESEED_ENVIRONMENT_REQUIREMENTS,
452
522
  TREESEED_ENVIRONMENT_SCOPES,
@@ -15,6 +15,7 @@ export declare const TREESEED_DEFAULT_PROVIDER_SELECTIONS: {
15
15
  runtime: string;
16
16
  publish: string;
17
17
  docs: string;
18
+ serving: string;
18
19
  };
19
20
  site: string;
20
21
  };
@@ -3,18 +3,19 @@ const TREESEED_DEFAULT_PROVIDER_SELECTIONS = {
3
3
  forms: "store_only",
4
4
  operations: "default",
5
5
  agents: {
6
- execution: "stub",
6
+ execution: "copilot",
7
7
  mutation: "local_branch",
8
- repository: "stub",
9
- verification: "stub",
10
- notification: "stub",
11
- research: "stub"
8
+ repository: "git",
9
+ verification: "local",
10
+ notification: "sdk_message",
11
+ research: "project_graph"
12
12
  },
13
13
  deploy: "cloudflare",
14
14
  content: {
15
15
  runtime: "team_scoped_r2_overlay",
16
16
  publish: "team_scoped_r2_overlay",
17
- docs: "default"
17
+ docs: "default",
18
+ serving: "local_collections"
18
19
  },
19
20
  site: "default"
20
21
  };
@@ -14,6 +14,8 @@ function fallbackTenantConfig(projectRoot) {
14
14
  notes: resolve(projectRoot, "src/content/notes"),
15
15
  questions: resolve(projectRoot, "src/content/questions"),
16
16
  objectives: resolve(projectRoot, "src/content/objectives"),
17
+ proposals: resolve(projectRoot, "src/content/proposals"),
18
+ decisions: resolve(projectRoot, "src/content/decisions"),
17
19
  people: resolve(projectRoot, "src/content/people"),
18
20
  agents: resolve(projectRoot, "src/content/agents"),
19
21
  books: resolve(projectRoot, "src/content/books"),
@@ -24,7 +26,12 @@ function fallbackTenantConfig(projectRoot) {
24
26
  },
25
27
  features: {
26
28
  docs: true,
27
- books: true
29
+ books: true,
30
+ notes: true,
31
+ questions: true,
32
+ objectives: true,
33
+ proposals: true,
34
+ decisions: true
28
35
  }
29
36
  };
30
37
  }
@@ -21,6 +21,8 @@ const manifestContentFieldAliases = {
21
21
  notes: { key: "notes", aliases: ["notes_root"] },
22
22
  questions: { key: "questions", aliases: ["questions_root"] },
23
23
  objectives: { key: "objectives", aliases: ["objectives_root"] },
24
+ proposals: { key: "proposals", aliases: ["proposals_root"] },
25
+ decisions: { key: "decisions", aliases: ["decisions_root"] },
24
26
  people: { key: "people", aliases: ["people_root"] },
25
27
  agents: { key: "agents", aliases: ["agents_root"] },
26
28
  books: { key: "books", aliases: ["books_root"] },
@@ -183,6 +185,8 @@ const MODEL_FEATURE_MAP = {
183
185
  notes: "notes",
184
186
  questions: "questions",
185
187
  objectives: "objectives",
188
+ proposals: "proposals",
189
+ decisions: "decisions",
186
190
  agents: "agents"
187
191
  };
188
192
  function tenantModelRendered(tenantConfig, modelName) {
@@ -312,6 +312,8 @@ export function parseSiteConfig(source) {
312
312
  const noteModel = expectRecord(models.notes ?? {}, 'models.notes');
313
313
  const questionModel = expectRecord(models.questions ?? {}, 'models.questions');
314
314
  const objectiveModel = expectRecord(models.objectives ?? {}, 'models.objectives');
315
+ const proposalModel = expectRecord(models.proposals ?? {}, 'models.proposals');
316
+ const decisionModel = expectRecord(models.decisions ?? {}, 'models.decisions');
315
317
  const peopleModel = expectRecord(models.people ?? {}, 'models.people');
316
318
  const agentModel = expectRecord(models.agents ?? {}, 'models.agents');
317
319
  const bookModel = expectRecord(models.books ?? {}, 'models.books');
@@ -320,6 +322,8 @@ export function parseSiteConfig(source) {
320
322
  const noteDefaults = expectRecord(noteModel.defaults ?? {}, 'models.notes.defaults');
321
323
  const questionDefaults = expectRecord(questionModel.defaults ?? {}, 'models.questions.defaults');
322
324
  const objectiveDefaults = expectRecord(objectiveModel.defaults ?? {}, 'models.objectives.defaults');
325
+ const proposalDefaults = expectRecord(proposalModel.defaults ?? {}, 'models.proposals.defaults');
326
+ const decisionDefaults = expectRecord(decisionModel.defaults ?? {}, 'models.decisions.defaults');
323
327
  const peopleDefaults = expectRecord(peopleModel.defaults ?? {}, 'models.people.defaults');
324
328
  const agentDefaults = normalizeAliasedRecord(agentDefaultsFieldAliases, expectRecord(agentModel.defaults ?? {}, 'models.agents.defaults'));
325
329
  const bookDefaults = expectRecord(bookModel.defaults ?? {}, 'models.books.defaults');
@@ -394,6 +398,20 @@ export function parseSiteConfig(source) {
394
398
  status: optionalString(objectiveDefaults.status, 'models.objectives.defaults.status'),
395
399
  },
396
400
  },
401
+ proposals: {
402
+ defaults: {
403
+ draft: optionalBoolean(proposalDefaults.draft, 'models.proposals.defaults.draft'),
404
+ tags: stringArray(proposalDefaults.tags, 'models.proposals.defaults.tags'),
405
+ status: optionalString(proposalDefaults.status, 'models.proposals.defaults.status'),
406
+ },
407
+ },
408
+ decisions: {
409
+ defaults: {
410
+ draft: optionalBoolean(decisionDefaults.draft, 'models.decisions.defaults.draft'),
411
+ tags: stringArray(decisionDefaults.tags, 'models.decisions.defaults.tags'),
412
+ status: optionalString(decisionDefaults.status, 'models.decisions.defaults.status'),
413
+ },
414
+ },
397
415
  people: {
398
416
  defaults: {
399
417
  status: optionalString(peopleDefaults.status, 'models.people.defaults.status'),
@@ -10,8 +10,8 @@ var plugin_default_default = defineTreeseedPlugin({
10
10
  mutation: ["local_branch"],
11
11
  repository: ["stub", "git"],
12
12
  verification: ["stub", "local"],
13
- notification: ["stub"],
14
- research: ["stub"],
13
+ notification: ["stub", "sdk_message"],
14
+ research: ["stub", "project_graph"],
15
15
  handlers: [
16
16
  "planner",
17
17
  "architect",
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env node
2
+ import { createInterface } from 'node:readline';
3
+ import { stdin as input, stdout as output } from 'node:process';
4
+ import { assertTreeseedKeyAgentResponse, getTreeseedKeyAgentPaths, readWrappedMachineKeyFile, requestTreeseedKeyAgent, startTreeseedKeyAgentServer, TREESEED_KEY_AGENT_IDLE_TIMEOUT_MS, TreeseedKeyAgentError, TRESEED_MACHINE_KEY_PASSPHRASE_ENV, } from '../operations/services/key-agent.js';
5
+ function parseArgs(argv) {
6
+ const [mode = 'request', ...rest] = argv;
7
+ const parsed = {
8
+ mode,
9
+ payload: '',
10
+ keyPath: '',
11
+ socketPath: '',
12
+ idleTimeoutMs: TREESEED_KEY_AGENT_IDLE_TIMEOUT_MS,
13
+ allowMigration: false,
14
+ createIfMissing: false,
15
+ };
16
+ while (rest.length > 0) {
17
+ const current = rest.shift();
18
+ if (!current) {
19
+ continue;
20
+ }
21
+ if (!parsed.payload && !current.startsWith('--')) {
22
+ parsed.payload = current;
23
+ continue;
24
+ }
25
+ if (current === '--key-path') {
26
+ parsed.keyPath = rest.shift() ?? '';
27
+ continue;
28
+ }
29
+ if (current === '--socket-path') {
30
+ parsed.socketPath = rest.shift() ?? '';
31
+ continue;
32
+ }
33
+ if (current === '--idle-timeout-ms') {
34
+ parsed.idleTimeoutMs = Number.parseInt(rest.shift() ?? String(TREESEED_KEY_AGENT_IDLE_TIMEOUT_MS), 10);
35
+ continue;
36
+ }
37
+ if (current === '--allow-migration') {
38
+ parsed.allowMigration = true;
39
+ continue;
40
+ }
41
+ if (current === '--create-if-missing') {
42
+ parsed.createIfMissing = true;
43
+ continue;
44
+ }
45
+ }
46
+ return parsed;
47
+ }
48
+ function writeJson(payload) {
49
+ process.stdout.write(`${JSON.stringify(payload)}\n`);
50
+ }
51
+ function prompt(question, secret = false) {
52
+ return new Promise((resolvePromise) => {
53
+ if (!secret) {
54
+ const rl = createInterface({ input, output });
55
+ rl.question(question, (answer) => {
56
+ rl.close();
57
+ resolvePromise(answer);
58
+ });
59
+ return;
60
+ }
61
+ const rl = createInterface({ input, output, terminal: true });
62
+ const previousWrite = rl._writeToOutput;
63
+ rl._writeToOutput = function _writeToOutput(text) {
64
+ if (text.trim()) {
65
+ output.write('*');
66
+ }
67
+ };
68
+ rl.question(question, (answer) => {
69
+ output.write('\n');
70
+ rl.close();
71
+ rl._writeToOutput = previousWrite;
72
+ resolvePromise(answer);
73
+ });
74
+ });
75
+ }
76
+ async function runInteractiveUnlock(parsed) {
77
+ const wrapped = readWrappedMachineKeyFile(parsed.keyPath);
78
+ if (wrapped.exists && !wrapped.wrapped && !wrapped.migrationRequired) {
79
+ throw new TreeseedKeyAgentError('corrupt_wrapped_key', 'Unable to read the existing Treeseed machine key file.');
80
+ }
81
+ const needsCreation = !wrapped.exists;
82
+ const needsMigration = wrapped.migrationRequired;
83
+ let passphrase = '';
84
+ if (needsCreation || needsMigration) {
85
+ const action = needsCreation ? 'Create a new Treeseed passphrase: ' : 'Create a new Treeseed passphrase to wrap the existing machine key: ';
86
+ const first = (await prompt(action, true)).trim();
87
+ if (!first) {
88
+ throw new TreeseedKeyAgentError('interactive_required', 'A non-empty passphrase is required.');
89
+ }
90
+ const second = (await prompt('Confirm passphrase: ', true)).trim();
91
+ if (first !== second) {
92
+ throw new TreeseedKeyAgentError('unlock_failed', 'The passphrase confirmation did not match.');
93
+ }
94
+ passphrase = first;
95
+ }
96
+ else {
97
+ passphrase = (await prompt('Treeseed passphrase: ', true)).trim();
98
+ if (!passphrase) {
99
+ throw new TreeseedKeyAgentError('interactive_required', 'A passphrase is required to unlock the Treeseed machine key.');
100
+ }
101
+ }
102
+ const response = await requestTreeseedKeyAgent({
103
+ command: 'unlock',
104
+ keyPath: parsed.keyPath,
105
+ socketPath: parsed.socketPath,
106
+ idleTimeoutMs: parsed.idleTimeoutMs,
107
+ passphrase,
108
+ createIfMissing: needsCreation || parsed.createIfMissing,
109
+ allowMigration: needsMigration || parsed.allowMigration,
110
+ });
111
+ assertTreeseedKeyAgentResponse(response);
112
+ writeJson(response);
113
+ }
114
+ async function main() {
115
+ const parsed = parseArgs(process.argv.slice(2));
116
+ const defaults = getTreeseedKeyAgentPaths();
117
+ parsed.socketPath ||= defaults.socketPath;
118
+ if (parsed.mode === 'serve') {
119
+ if (!parsed.keyPath) {
120
+ throw new Error('Missing --key-path for key-agent serve mode.');
121
+ }
122
+ await startTreeseedKeyAgentServer({
123
+ keyPath: parsed.keyPath,
124
+ socketPath: parsed.socketPath,
125
+ idleTimeoutMs: parsed.idleTimeoutMs,
126
+ });
127
+ await new Promise(() => { });
128
+ return;
129
+ }
130
+ if (parsed.mode === 'unlock-interactive') {
131
+ await runInteractiveUnlock(parsed);
132
+ return;
133
+ }
134
+ if (parsed.mode === 'unlock-from-env') {
135
+ const passphrase = String(process.env[TRESEED_MACHINE_KEY_PASSPHRASE_ENV] ?? '').trim();
136
+ if (!passphrase) {
137
+ throw new TreeseedKeyAgentError('interactive_required', `Set ${TRESEED_MACHINE_KEY_PASSPHRASE_ENV} before using unlock-from-env.`);
138
+ }
139
+ const response = await requestTreeseedKeyAgent({
140
+ command: 'unlock',
141
+ keyPath: parsed.keyPath,
142
+ socketPath: parsed.socketPath,
143
+ idleTimeoutMs: parsed.idleTimeoutMs,
144
+ passphrase,
145
+ createIfMissing: parsed.createIfMissing,
146
+ allowMigration: parsed.allowMigration,
147
+ });
148
+ assertTreeseedKeyAgentResponse(response);
149
+ writeJson(response);
150
+ return;
151
+ }
152
+ const payload = JSON.parse(parsed.payload);
153
+ const response = await requestTreeseedKeyAgent(payload);
154
+ writeJson(response);
155
+ }
156
+ main().catch((error) => {
157
+ const message = error instanceof Error ? error.message : String(error);
158
+ const code = error instanceof TreeseedKeyAgentError ? error.code : 'unlock_failed';
159
+ writeJson({
160
+ ok: false,
161
+ code,
162
+ message,
163
+ });
164
+ process.exit(1);
165
+ });
@@ -1,7 +1,10 @@
1
1
  import { resolveAstroBin, createProductionBuildEnv, packageScriptPath, runNodeBinary, runNodeScript } from '../operations/services/runtime-tools.js';
2
2
  process.env.TREESEED_LOCAL_DEV_MODE = process.env.TREESEED_LOCAL_DEV_MODE ?? 'cloudflare';
3
+ const publishedRuntime = process.env.TREESEED_CONTENT_SERVING_MODE === 'published_runtime';
3
4
  runNodeScript(packageScriptPath('patch-starlight-content-path'), [], { cwd: process.cwd() });
4
- runNodeScript(packageScriptPath('aggregate-book'), [], { cwd: process.cwd() });
5
+ if (!publishedRuntime) {
6
+ runNodeScript(packageScriptPath('aggregate-book'), [], { cwd: process.cwd() });
7
+ }
5
8
  runNodeBinary(resolveAstroBin(), ['build'], {
6
9
  cwd: process.cwd(),
7
10
  env: createProductionBuildEnv({
@@ -1,6 +1,9 @@
1
1
  import { resolveAstroBin, createProductionBuildEnv, packageScriptPath, runNodeBinary, runNodeScript } from '../operations/services/runtime-tools.js';
2
+ const publishedRuntime = process.env.TREESEED_CONTENT_SERVING_MODE === 'published_runtime';
2
3
  runNodeScript(packageScriptPath('patch-starlight-content-path'), [], { cwd: process.cwd() });
3
- runNodeScript(packageScriptPath('aggregate-book'), [], { cwd: process.cwd() });
4
+ if (!publishedRuntime) {
5
+ runNodeScript(packageScriptPath('aggregate-book'), [], { cwd: process.cwd() });
6
+ }
4
7
  runNodeBinary(resolveAstroBin(), ['check'], {
5
8
  cwd: process.cwd(),
6
9
  env: createProductionBuildEnv(),
@@ -3,9 +3,9 @@ import { spawnSync } from 'node:child_process';
3
3
  import { mkdirSync, writeFileSync } from 'node:fs';
4
4
  import { dirname, resolve } from 'node:path';
5
5
  import { applyTreeseedEnvironmentToProcess } from '../operations/services/config-runtime.js';
6
- import { assertDeploymentInitialized, createBranchPreviewDeployTarget, createPersistentDeployTarget, deployTargetLabel, ensureGeneratedWranglerConfig, finalizeDeploymentState, runRemoteD1Migrations, } from '../operations/services/deploy.js';
6
+ import { assertDeploymentInitialized, createBranchPreviewDeployTarget, createPersistentDeployTarget, deployTargetLabel, ensureGeneratedWranglerConfig, finalizeDeploymentState, loadDeployState, runRemoteD1Migrations, } from '../operations/services/deploy.js';
7
7
  import { currentManagedBranch, PRODUCTION_BRANCH, STAGING_BRANCH } from '../operations/services/git-workflow.js';
8
- import { packageScriptPath, resolveWranglerBin } from '../operations/services/runtime-tools.js';
8
+ import { loadCliDeployConfig, packageScriptPath, resolveWranglerBin } from '../operations/services/runtime-tools.js';
9
9
  import { runTenantDeployPreflight } from '../operations/services/save-deploy-preflight.js';
10
10
  const tenantRoot = process.cwd();
11
11
  const args = process.argv.slice(2);
@@ -109,6 +109,27 @@ function runWranglerDeploy(configPath) {
109
109
  process.exit(result.status ?? 1);
110
110
  }
111
111
  }
112
+ function runWranglerPagesDeploy(projectName, branchName, outputDir = 'dist') {
113
+ const args = [
114
+ resolveWranglerBin(),
115
+ 'pages',
116
+ 'deploy',
117
+ outputDir,
118
+ '--project-name',
119
+ projectName,
120
+ ];
121
+ if (branchName) {
122
+ args.push('--branch', branchName);
123
+ }
124
+ const result = spawnSync(process.execPath, args, {
125
+ stdio: 'inherit',
126
+ cwd: tenantRoot,
127
+ env: { ...process.env },
128
+ });
129
+ if (result.status !== 0) {
130
+ process.exit(result.status ?? 1);
131
+ }
132
+ }
112
133
  async function main() {
113
134
  const options = parseArgs(args);
114
135
  const { target, scope } = resolveTarget(options);
@@ -142,6 +163,14 @@ async function main() {
142
163
  throw error;
143
164
  }
144
165
  const { wranglerPath } = ensureGeneratedWranglerConfig(tenantRoot, { target });
166
+ const deployConfig = loadCliDeployConfig(tenantRoot);
167
+ const deployState = loadDeployState(tenantRoot, deployConfig, { target });
168
+ const pagesProjectName = target.kind === 'persistent' ? deployState.pages?.projectName ?? null : null;
169
+ const pagesBranchName = target.kind === 'persistent'
170
+ ? (target.scope === 'prod'
171
+ ? deployState.pages?.productionBranch ?? 'main'
172
+ : deployState.pages?.stagingBranch ?? 'staging')
173
+ : null;
145
174
  if (shouldRun('migrate')) {
146
175
  const result = runRemoteD1Migrations(tenantRoot, { dryRun: options.dryRun, target });
147
176
  console.log(`${options.dryRun ? 'Planned' : 'Applied'} remote migrations for ${result.databaseName}.`);
@@ -156,10 +185,20 @@ async function main() {
156
185
  }
157
186
  if (shouldRun('publish')) {
158
187
  if (options.dryRun) {
159
- console.log(`Dry run: would deploy ${deployTargetLabel(target)} with generated Wrangler config at ${resolve(wranglerPath)}.`);
188
+ if (pagesProjectName) {
189
+ console.log(`Dry run: would deploy ${deployTargetLabel(target)} to Pages project ${pagesProjectName} from ${resolve(tenantRoot, 'dist')}.`);
190
+ }
191
+ else {
192
+ console.log(`Dry run: would deploy ${deployTargetLabel(target)} with generated Wrangler config at ${resolve(wranglerPath)}.`);
193
+ }
160
194
  }
161
195
  else {
162
- runWranglerDeploy(wranglerPath);
196
+ if (pagesProjectName) {
197
+ runWranglerPagesDeploy(pagesProjectName, pagesBranchName, resolve(tenantRoot, 'dist'));
198
+ }
199
+ else {
200
+ runWranglerDeploy(wranglerPath);
201
+ }
163
202
  finalizeDeploymentState(tenantRoot, { target });
164
203
  }
165
204
  }