flowdular 0.1.0 → 0.2.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.
package/dist/index.js CHANGED
@@ -6,7 +6,11 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
8
  var __commonJS = (cb, mod) => function __require() {
9
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
+ try {
10
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
+ } catch (e) {
12
+ throw mod = 0, e;
13
+ }
10
14
  };
11
15
  var __copyProps = (to, from, except, desc) => {
12
16
  if (from && typeof from === "object" || typeof from === "function") {
@@ -1995,15 +1999,42 @@ function isCapabilityList(values2) {
1995
1999
  }
1996
2000
  function renderOutput(envelope, json2) {
1997
2001
  if (json2) return JSON.stringify(envelope, null, 2);
1998
- if (!envelope.ok)
1999
- return `ERROR ${envelope.error?.code ?? "UNKNOWN"}
2002
+ if (!envelope.ok) {
2003
+ const heading = `ERROR ${envelope.error?.code ?? "UNKNOWN"}
2000
2004
  ${envelope.error?.message ?? "Command failed."}`;
2005
+ if (envelope.error?.code !== "DOCTOR_FAILED") return heading;
2006
+ const details = envelope.error.details;
2007
+ if (!Array.isArray(details?.checks)) return heading;
2008
+ const failures = details.checks.filter(
2009
+ (check) => check && check.status === "fail" && typeof check.id === "string" && typeof check.message === "string"
2010
+ );
2011
+ return [
2012
+ heading,
2013
+ ...failures.map((check) => `FAIL ${check.id} ${check.message}`)
2014
+ ].join("\n");
2015
+ }
2001
2016
  const decorate = (body) => [
2002
2017
  body,
2003
2018
  ...envelope.warnings.map((warning) => `WARNING ${warning}`),
2004
2019
  ...envelope.evidence.map((item) => `EVIDENCE ${item}`)
2005
2020
  ].join("\n");
2006
2021
  const data2 = envelope.data;
2022
+ if (data2?.cancelled === true) return decorate("Setup cancelled.");
2023
+ if (data2?.setup === "postgresql")
2024
+ return decorate(
2025
+ "PostgreSQL settings saved to .env.\nRun pnpm dev, then open http://localhost:4310 to create an account."
2026
+ );
2027
+ if (data2?.setup === "local") {
2028
+ const admin = data2.accounts?.admin;
2029
+ return decorate(
2030
+ [
2031
+ "Local demo is ready.",
2032
+ "Run pnpm dev, then open http://localhost:4310.",
2033
+ ...admin?.email ? [`Demo administrator: ${admin.email}`] : [],
2034
+ ...admin?.password ? [`Public demo password: ${admin.password}`] : []
2035
+ ].join("\n")
2036
+ );
2037
+ }
2007
2038
  if (data2 && Array.isArray(data2.checks)) {
2008
2039
  return decorate(
2009
2040
  data2.checks.map((check) => {
@@ -2022,143 +2053,53 @@ ${envelope.error?.message ?? "Command failed."}`;
2022
2053
  return decorate(JSON.stringify(data2, null, 2));
2023
2054
  }
2024
2055
 
2025
- // ../kernel/src/errors.ts
2026
- var RegistryError = class extends Error {
2027
- code;
2028
- constructor(code, message) {
2029
- super(message);
2030
- this.name = "RegistryError";
2031
- this.code = code;
2032
- }
2033
- };
2034
-
2035
- // ../kernel/src/module-compatibility.ts
2036
- var import_semver = __toESM(require_semver2(), 1);
2037
- var PLATFORM_API_VERSION = "0.1.0";
2038
- function satisfiesModuleVersion(version, range) {
2039
- return (0, import_semver.valid)(version) !== null && (0, import_semver.validRange)(range) !== null && (0, import_semver.satisfies)(version, range);
2040
- }
2041
- function compareModuleVersions(left, right) {
2042
- return (0, import_semver.rcompare)(left, right);
2043
- }
2044
- function assertModuleCompatibility(manifest2, platformVersion = PLATFORM_API_VERSION) {
2045
- if (!(0, import_semver.valid)(manifest2.version))
2046
- throw new RegistryError(
2047
- "MODULE_VERSION_INVALID",
2048
- `Invalid version for ${manifest2.id}: ${manifest2.version}`
2049
- );
2050
- if (manifest2.platformApi !== void 0 && (!(0, import_semver.validRange)(manifest2.platformApi) || platformVersion !== null && !satisfiesModuleVersion(platformVersion, manifest2.platformApi))) {
2051
- throw new RegistryError(
2052
- "MODULE_PLATFORM_INCOMPATIBLE",
2053
- `${manifest2.id} requires platform API ${manifest2.platformApi}; available ${PLATFORM_API_VERSION}.`
2054
- );
2055
- }
2056
- const ids = /* @__PURE__ */ new Set();
2057
- for (const dependency of manifest2.dependencies) {
2058
- if (!dependency.range.trim() || !(0, import_semver.validRange)(dependency.range))
2059
- throw new RegistryError(
2060
- "MODULE_RANGE_INVALID",
2061
- `${manifest2.id} declares an invalid range for ${dependency.id}: ${dependency.range}`
2062
- );
2063
- if (ids.has(dependency.id))
2064
- throw new RegistryError(
2065
- "MODULE_DEPENDENCY_DUPLICATE",
2066
- `${manifest2.id} declares ${dependency.id} more than once.`
2067
- );
2068
- ids.add(dependency.id);
2069
- }
2070
- }
2071
- function assertModuleDependency(consumer, dependency, version) {
2072
- if (!satisfiesModuleVersion(version, dependency.range)) {
2073
- throw new RegistryError(
2074
- "MODULE_DEPENDENCY_INCOMPATIBLE",
2075
- `${consumer} requires ${dependency.id} ${dependency.range}; available ${version}.`
2076
- );
2077
- }
2078
- }
2079
-
2080
- // ../kernel/src/module-registry.ts
2081
- function compareModules(left, right) {
2082
- return left.manifest.id.localeCompare(right.manifest.id);
2083
- }
2084
- function visitModule(id, modules, visiting, visited, ordered) {
2085
- if (visited.has(id)) return;
2086
- if (visiting.has(id)) {
2087
- throw new RegistryError(
2088
- "MODULE_DEPENDENCY_CYCLE",
2089
- `Dependency cycle detected at "${id}".`
2090
- );
2091
- }
2092
- const current = modules.get(id);
2093
- if (!current) {
2094
- throw new RegistryError(
2095
- "MODULE_NOT_FOUND",
2096
- `Module "${id}" is not registered.`
2097
- );
2098
- }
2099
- visiting.add(id);
2100
- for (const dependency of [...current.manifest.dependencies].sort(
2101
- (a, b) => a.id.localeCompare(b.id)
2102
- )) {
2103
- if (!modules.has(dependency.id)) {
2104
- throw new RegistryError(
2105
- "MODULE_DEPENDENCY_MISSING",
2106
- `Module "${id}" requires missing module "${dependency.id}".`
2056
+ // src/workspace.ts
2057
+ import { access, readFile, realpath } from "node:fs/promises";
2058
+ import { dirname, isAbsolute, relative, resolve } from "node:path";
2059
+ async function findWorkspace(start) {
2060
+ let directory = resolve(start);
2061
+ for (; ; ) {
2062
+ const configPath = resolve(directory, "flowdular.json");
2063
+ try {
2064
+ await access(configPath);
2065
+ const config = JSON.parse(await readFile(configPath, "utf8"));
2066
+ return { root: directory, configPath, config };
2067
+ } catch (error) {
2068
+ if (error instanceof SyntaxError) throw error;
2069
+ }
2070
+ const parent = dirname(directory);
2071
+ if (parent === directory) {
2072
+ throw new Error(
2073
+ "No flowdular.json was found in this directory or any parent."
2107
2074
  );
2108
2075
  }
2109
- assertModuleDependency(
2110
- id,
2111
- dependency,
2112
- modules.get(dependency.id).manifest.version
2113
- );
2114
- visitModule(dependency.id, modules, visiting, visited, ordered);
2076
+ directory = parent;
2115
2077
  }
2116
- visiting.delete(id);
2117
- visited.add(id);
2118
- ordered.push(current);
2119
2078
  }
2120
- function createModuleRegistry(input) {
2121
- const byId = /* @__PURE__ */ new Map();
2122
- const navigationIds = /* @__PURE__ */ new Set();
2123
- for (const module of input) {
2124
- assertModuleCompatibility(module.manifest);
2125
- if (byId.has(module.manifest.id)) {
2126
- throw new RegistryError(
2127
- "MODULE_DUPLICATE",
2128
- `Module "${module.manifest.id}" is registered more than once.`
2129
- );
2130
- }
2131
- byId.set(module.manifest.id, module);
2132
- for (const item of module.navigation ?? []) {
2133
- if (navigationIds.has(item.id)) {
2134
- throw new RegistryError(
2135
- "NAVIGATION_DUPLICATE",
2136
- `Navigation contribution "${item.id}" is registered more than once.`
2137
- );
2138
- }
2139
- navigationIds.add(item.id);
2140
- }
2079
+ function resolveInside(root, candidate) {
2080
+ const absolute = isAbsolute(candidate) ? resolve(candidate) : resolve(root, candidate);
2081
+ const pathFromRoot = relative(root, absolute);
2082
+ if (pathFromRoot.startsWith("..") || isAbsolute(pathFromRoot)) {
2083
+ throw new Error(`Path escapes the workspace: ${candidate}`);
2141
2084
  }
2142
- const ordered = [];
2143
- const visiting = /* @__PURE__ */ new Set();
2144
- const visited = /* @__PURE__ */ new Set();
2145
- for (const module of [...input].sort(compareModules)) {
2146
- visitModule(module.manifest.id, byId, visiting, visited, ordered);
2085
+ return absolute;
2086
+ }
2087
+ async function resolveExistingInside(root, candidate) {
2088
+ const canonicalRoot = await realpath(root);
2089
+ const canonicalCandidate = await realpath(resolveInside(root, candidate));
2090
+ const pathFromRoot = relative(canonicalRoot, canonicalCandidate);
2091
+ if (pathFromRoot.startsWith("..") || isAbsolute(pathFromRoot)) {
2092
+ throw new Error(`Path escapes the workspace through a link: ${candidate}`);
2147
2093
  }
2148
- return Object.freeze({
2149
- modules: Object.freeze(ordered),
2150
- get: (id) => byId.get(id),
2151
- has: (id) => byId.has(id),
2152
- navigation: (permissions) => Object.freeze(
2153
- ordered.flatMap((module) => module.navigation ?? []).filter(
2154
- (item) => !item.permission || permissions?.has(item.permission)
2155
- ).sort(
2156
- (left, right) => (left.order ?? 1e3) - (right.order ?? 1e3) || left.id.localeCompare(right.id)
2157
- )
2158
- )
2159
- });
2094
+ return canonicalCandidate;
2160
2095
  }
2161
2096
 
2097
+ // src/validation.ts
2098
+ import { readdir, readFile as readFile2 } from "node:fs/promises";
2099
+ import { basename, dirname as dirname2, join, relative as relative2 } from "node:path";
2100
+ import Ajv2020 from "ajv/dist/2020.js";
2101
+ import { parse as parseYaml } from "yaml";
2102
+
2162
2103
  // ../contracts/schemas/blueprint.schema.json
2163
2104
  var blueprint_schema_default = {
2164
2105
  $schema: "https://json-schema.org/draft/2020-12/schema",
@@ -2619,10 +2560,7 @@ var project_schema_default = {
2619
2560
  specs: {
2620
2561
  type: "object",
2621
2562
  additionalProperties: false,
2622
- required: [
2623
- "platformRoot",
2624
- "moduleDirectory"
2625
- ],
2563
+ required: ["platformRoot", "moduleDirectory"],
2626
2564
  properties: {
2627
2565
  platformRoot: {
2628
2566
  type: "string",
@@ -2637,10 +2575,7 @@ var project_schema_default = {
2637
2575
  modules: {
2638
2576
  type: "object",
2639
2577
  additionalProperties: false,
2640
- required: [
2641
- "roots",
2642
- "enabled"
2643
- ],
2578
+ required: ["roots", "enabled"],
2644
2579
  properties: {
2645
2580
  roots: {
2646
2581
  type: "array",
@@ -2673,11 +2608,7 @@ var project_schema_default = {
2673
2608
  agent: {
2674
2609
  type: "object",
2675
2610
  additionalProperties: false,
2676
- required: [
2677
- "policy",
2678
- "blueprints",
2679
- "modelRouting"
2680
- ],
2611
+ required: ["policy", "blueprints", "modelRouting"],
2681
2612
  properties: {
2682
2613
  policy: {
2683
2614
  type: "string",
@@ -2729,17 +2660,10 @@ var project_schema_default = {
2729
2660
  $ref: "#/$defs/gitName"
2730
2661
  },
2731
2662
  provider: {
2732
- enum: [
2733
- "github",
2734
- "none"
2735
- ]
2663
+ enum: ["github", "none"]
2736
2664
  },
2737
2665
  mode: {
2738
- enum: [
2739
- "auto",
2740
- "direct",
2741
- "fork"
2742
- ]
2666
+ enum: ["auto", "direct", "fork"]
2743
2667
  },
2744
2668
  forkOwner: {
2745
2669
  $ref: "#/$defs/githubAccount"
@@ -2765,9 +2689,7 @@ var project_schema_default = {
2765
2689
  web: {
2766
2690
  type: "object",
2767
2691
  additionalProperties: false,
2768
- required: [
2769
- "mounts"
2770
- ],
2692
+ required: ["mounts"],
2771
2693
  properties: {
2772
2694
  mounts: {
2773
2695
  type: "array",
@@ -2775,13 +2697,7 @@ var project_schema_default = {
2775
2697
  items: {
2776
2698
  type: "object",
2777
2699
  additionalProperties: false,
2778
- required: [
2779
- "id",
2780
- "moduleId",
2781
- "surfaceId",
2782
- "path",
2783
- "tenantId"
2784
- ],
2700
+ required: ["id", "moduleId", "surfaceId", "path", "tenantId"],
2785
2701
  properties: {
2786
2702
  id: {
2787
2703
  type: "string",
@@ -2816,9 +2732,7 @@ var project_schema_default = {
2816
2732
  application: {
2817
2733
  type: "object",
2818
2734
  additionalProperties: false,
2819
- required: [
2820
- "path"
2821
- ],
2735
+ required: ["path"],
2822
2736
  properties: {
2823
2737
  path: {
2824
2738
  type: "string",
@@ -2832,20 +2746,21 @@ var project_schema_default = {
2832
2746
  "/sign-up",
2833
2747
  "/forgot-password",
2834
2748
  "/reset-password",
2835
- "/accept-invitation"
2749
+ "/accept-invitation",
2750
+ "/setup",
2751
+ "/health",
2752
+ "/ready",
2753
+ "/assets"
2836
2754
  ]
2837
- }
2755
+ },
2756
+ maxLength: 64
2838
2757
  }
2839
2758
  }
2840
2759
  }
2841
2760
  },
2842
2761
  $defs: {
2843
2762
  deliveryTarget: {
2844
- enum: [
2845
- "workspace",
2846
- "git-pr",
2847
- "official-modules"
2848
- ]
2763
+ enum: ["workspace", "git-pr", "official-modules"]
2849
2764
  },
2850
2765
  gitName: {
2851
2766
  type: "string",
@@ -3007,19 +2922,7 @@ var projectSchema = project_schema_default;
3007
2922
  var moduleCatalogSchema = module_catalog_schema_default;
3008
2923
  var moduleArtifactSchema = module_artifact_schema_default;
3009
2924
 
3010
- // src/module-catalog.ts
3011
- import { readFile as readFile3, stat } from "node:fs/promises";
3012
- import { dirname as dirname3, resolve as resolve2 } from "node:path";
3013
-
3014
- // src/module-artifact.ts
3015
- import { parse as parseYaml2 } from "yaml";
3016
- import { createHash } from "node:crypto";
3017
-
3018
2925
  // src/validation.ts
3019
- import { readdir, readFile } from "node:fs/promises";
3020
- import { basename, dirname, join, relative } from "node:path";
3021
- import Ajv2020 from "ajv/dist/2020.js";
3022
- import { parse as parseYaml } from "yaml";
3023
2926
  var ajv = new Ajv2020({ allErrors: true, strict: true });
3024
2927
  var validators = {
3025
2928
  application: ajv.compile(projectSchema.properties.application),
@@ -3042,7 +2945,7 @@ function issuesFrom(errors) {
3042
2945
  }));
3043
2946
  }
3044
2947
  async function parseFile(path) {
3045
- const source = await readFile(path, "utf8");
2948
+ const source = await readFile2(path, "utf8");
3046
2949
  return path.endsWith(".yaml") || path.endsWith(".yml") ? parseYaml(source) : JSON.parse(source);
3047
2950
  }
3048
2951
  async function validateFile(path, validator) {
@@ -3106,7 +3009,7 @@ var blueprintFiles = [
3106
3009
  ];
3107
3010
  async function validateBlueprint(path) {
3108
3011
  const report = await validateFile(path, validators.blueprint);
3109
- const directory = dirname(path);
3012
+ const directory = dirname2(path);
3110
3013
  const entries = new Set(
3111
3014
  (await readdir(directory)).filter((name) => basename(name) === name)
3112
3015
  );
@@ -3117,14 +3020,177 @@ async function validateBlueprint(path) {
3117
3020
  ...missing.map((name) => ({
3118
3021
  code: "BLUEPRINT_FILE_MISSING",
3119
3022
  message: `Required blueprint file is missing: ${name}`,
3120
- path: relative(process.cwd(), join(directory, name)),
3023
+ path: relative2(process.cwd(), join(directory, name)),
3121
3024
  severity: "error"
3122
3025
  }))
3123
3026
  ];
3124
3027
  return { ...report, valid: false, issues };
3125
3028
  }
3126
3029
 
3030
+ // src/agent-resources.ts
3031
+ function agentResource(workspace, key) {
3032
+ const defaults = {
3033
+ policy: ".ai/policies/capabilities.yaml",
3034
+ modelRouting: ".ai/policies/model-routing.yaml",
3035
+ blueprints: ".ai/blueprints"
3036
+ };
3037
+ const agent = workspace.config.agent;
3038
+ const path = agent?.[key] ?? defaults[key];
3039
+ if (typeof path !== "string" || !path)
3040
+ throw new Error(`Invalid agent.${key} path.`);
3041
+ return resolveInside(workspace.root, path);
3042
+ }
3043
+ function findBlueprintFiles(workspace) {
3044
+ return findNamedFiles(
3045
+ agentResource(workspace, "blueprints"),
3046
+ "blueprint.json"
3047
+ );
3048
+ }
3049
+
3050
+ // ../kernel/src/errors.ts
3051
+ var RegistryError = class extends Error {
3052
+ code;
3053
+ constructor(code, message) {
3054
+ super(message);
3055
+ this.name = "RegistryError";
3056
+ this.code = code;
3057
+ }
3058
+ };
3059
+
3060
+ // ../kernel/src/module-compatibility.ts
3061
+ var import_semver = __toESM(require_semver2(), 1);
3062
+ var PLATFORM_API_VERSION = "0.1.0";
3063
+ function satisfiesModuleVersion(version, range) {
3064
+ return (0, import_semver.valid)(version) !== null && (0, import_semver.validRange)(range) !== null && (0, import_semver.satisfies)(version, range);
3065
+ }
3066
+ function compareModuleVersions(left, right) {
3067
+ return (0, import_semver.rcompare)(left, right);
3068
+ }
3069
+ function assertModuleCompatibility(manifest2, platformVersion = PLATFORM_API_VERSION) {
3070
+ if (!(0, import_semver.valid)(manifest2.version))
3071
+ throw new RegistryError(
3072
+ "MODULE_VERSION_INVALID",
3073
+ `Invalid version for ${manifest2.id}: ${manifest2.version}`
3074
+ );
3075
+ if (manifest2.platformApi !== void 0 && (!(0, import_semver.validRange)(manifest2.platformApi) || platformVersion !== null && !satisfiesModuleVersion(platformVersion, manifest2.platformApi))) {
3076
+ throw new RegistryError(
3077
+ "MODULE_PLATFORM_INCOMPATIBLE",
3078
+ `${manifest2.id} requires platform API ${manifest2.platformApi}; available ${PLATFORM_API_VERSION}.`
3079
+ );
3080
+ }
3081
+ const ids = /* @__PURE__ */ new Set();
3082
+ for (const dependency of manifest2.dependencies) {
3083
+ if (!dependency.range.trim() || !(0, import_semver.validRange)(dependency.range))
3084
+ throw new RegistryError(
3085
+ "MODULE_RANGE_INVALID",
3086
+ `${manifest2.id} declares an invalid range for ${dependency.id}: ${dependency.range}`
3087
+ );
3088
+ if (ids.has(dependency.id))
3089
+ throw new RegistryError(
3090
+ "MODULE_DEPENDENCY_DUPLICATE",
3091
+ `${manifest2.id} declares ${dependency.id} more than once.`
3092
+ );
3093
+ ids.add(dependency.id);
3094
+ }
3095
+ }
3096
+ function assertModuleDependency(consumer, dependency, version) {
3097
+ if (!satisfiesModuleVersion(version, dependency.range)) {
3098
+ throw new RegistryError(
3099
+ "MODULE_DEPENDENCY_INCOMPATIBLE",
3100
+ `${consumer} requires ${dependency.id} ${dependency.range}; available ${version}.`
3101
+ );
3102
+ }
3103
+ }
3104
+
3105
+ // ../kernel/src/module-registry.ts
3106
+ function compareModules(left, right) {
3107
+ return left.manifest.id.localeCompare(right.manifest.id);
3108
+ }
3109
+ function visitModule(id, modules, visiting, visited, ordered) {
3110
+ if (visited.has(id)) return;
3111
+ if (visiting.has(id)) {
3112
+ throw new RegistryError(
3113
+ "MODULE_DEPENDENCY_CYCLE",
3114
+ `Dependency cycle detected at "${id}".`
3115
+ );
3116
+ }
3117
+ const current = modules.get(id);
3118
+ if (!current) {
3119
+ throw new RegistryError(
3120
+ "MODULE_NOT_FOUND",
3121
+ `Module "${id}" is not registered.`
3122
+ );
3123
+ }
3124
+ visiting.add(id);
3125
+ for (const dependency of [...current.manifest.dependencies].sort(
3126
+ (a, b) => a.id.localeCompare(b.id)
3127
+ )) {
3128
+ if (!modules.has(dependency.id)) {
3129
+ throw new RegistryError(
3130
+ "MODULE_DEPENDENCY_MISSING",
3131
+ `Module "${id}" requires missing module "${dependency.id}".`
3132
+ );
3133
+ }
3134
+ assertModuleDependency(
3135
+ id,
3136
+ dependency,
3137
+ modules.get(dependency.id).manifest.version
3138
+ );
3139
+ visitModule(dependency.id, modules, visiting, visited, ordered);
3140
+ }
3141
+ visiting.delete(id);
3142
+ visited.add(id);
3143
+ ordered.push(current);
3144
+ }
3145
+ function createModuleRegistry(input) {
3146
+ const byId = /* @__PURE__ */ new Map();
3147
+ const navigationIds = /* @__PURE__ */ new Set();
3148
+ for (const module of input) {
3149
+ assertModuleCompatibility(module.manifest);
3150
+ if (byId.has(module.manifest.id)) {
3151
+ throw new RegistryError(
3152
+ "MODULE_DUPLICATE",
3153
+ `Module "${module.manifest.id}" is registered more than once.`
3154
+ );
3155
+ }
3156
+ byId.set(module.manifest.id, module);
3157
+ for (const item of module.navigation ?? []) {
3158
+ if (navigationIds.has(item.id)) {
3159
+ throw new RegistryError(
3160
+ "NAVIGATION_DUPLICATE",
3161
+ `Navigation contribution "${item.id}" is registered more than once.`
3162
+ );
3163
+ }
3164
+ navigationIds.add(item.id);
3165
+ }
3166
+ }
3167
+ const ordered = [];
3168
+ const visiting = /* @__PURE__ */ new Set();
3169
+ const visited = /* @__PURE__ */ new Set();
3170
+ for (const module of [...input].sort(compareModules)) {
3171
+ visitModule(module.manifest.id, byId, visiting, visited, ordered);
3172
+ }
3173
+ return Object.freeze({
3174
+ modules: Object.freeze(ordered),
3175
+ get: (id) => byId.get(id),
3176
+ has: (id) => byId.has(id),
3177
+ navigation: (permissions) => Object.freeze(
3178
+ ordered.flatMap((module) => module.navigation ?? []).filter(
3179
+ (item) => !item.permission || permissions?.has(item.permission)
3180
+ ).sort(
3181
+ (left, right) => (left.order ?? 1e3) - (right.order ?? 1e3) || left.id.localeCompare(right.id)
3182
+ )
3183
+ )
3184
+ });
3185
+ }
3186
+
3187
+ // src/module-catalog.ts
3188
+ import { readFile as readFile3, stat } from "node:fs/promises";
3189
+ import { dirname as dirname3, resolve as resolve2 } from "node:path";
3190
+
3127
3191
  // src/module-artifact.ts
3192
+ import { parse as parseYaml2 } from "yaml";
3193
+ import { createHash } from "node:crypto";
3128
3194
  var MAX_ARTIFACT_BYTES = 48 * 1024 * 1024;
3129
3195
  var MAX_SOURCE_BYTES = 32 * 1024 * 1024;
3130
3196
  var MAX_FILES = 4096;
@@ -3145,6 +3211,7 @@ var ModuleDistributionError = class extends Error {
3145
3211
  super(message);
3146
3212
  this.code = code;
3147
3213
  }
3214
+ code;
3148
3215
  };
3149
3216
  function distributionAssert(condition, code, message) {
3150
3217
  if (!condition) throw new ModuleDistributionError(code, message);
@@ -3282,47 +3349,6 @@ function validateModuleArtifact(value) {
3282
3349
  return artifact;
3283
3350
  }
3284
3351
 
3285
- // src/workspace.ts
3286
- import { access, readFile as readFile2, realpath } from "node:fs/promises";
3287
- import { dirname as dirname2, isAbsolute, relative as relative2, resolve } from "node:path";
3288
- async function findWorkspace(start) {
3289
- let directory = resolve(start);
3290
- for (; ; ) {
3291
- const configPath = resolve(directory, "flowdular.json");
3292
- try {
3293
- await access(configPath);
3294
- const config = JSON.parse(await readFile2(configPath, "utf8"));
3295
- return { root: directory, configPath, config };
3296
- } catch (error) {
3297
- if (error instanceof SyntaxError) throw error;
3298
- }
3299
- const parent = dirname2(directory);
3300
- if (parent === directory) {
3301
- throw new Error(
3302
- "No flowdular.json was found in this directory or any parent."
3303
- );
3304
- }
3305
- directory = parent;
3306
- }
3307
- }
3308
- function resolveInside(root, candidate) {
3309
- const absolute = isAbsolute(candidate) ? resolve(candidate) : resolve(root, candidate);
3310
- const pathFromRoot = relative2(root, absolute);
3311
- if (pathFromRoot.startsWith("..") || isAbsolute(pathFromRoot)) {
3312
- throw new Error(`Path escapes the workspace: ${candidate}`);
3313
- }
3314
- return absolute;
3315
- }
3316
- async function resolveExistingInside(root, candidate) {
3317
- const canonicalRoot = await realpath(root);
3318
- const canonicalCandidate = await realpath(resolveInside(root, candidate));
3319
- const pathFromRoot = relative2(canonicalRoot, canonicalCandidate);
3320
- if (pathFromRoot.startsWith("..") || isAbsolute(pathFromRoot)) {
3321
- throw new Error(`Path escapes the workspace through a link: ${candidate}`);
3322
- }
3323
- return canonicalCandidate;
3324
- }
3325
-
3326
3352
  // src/module-catalog.ts
3327
3353
  var OFFICIAL_CATALOG = "https://raw.githubusercontent.com/Flowdular/official-modules/main/registry/index.json";
3328
3354
  var OFFICIAL_PREFIX = "https://raw.githubusercontent.com/Flowdular/official-modules/";
@@ -3520,12 +3546,11 @@ import { dirname as dirname6, join as join5, relative as relative5 } from "node:
3520
3546
  import { createRequire } from "node:module";
3521
3547
  import { readFile as readFile4 } from "node:fs/promises";
3522
3548
  import { dirname as dirname4, join as join2 } from "node:path";
3523
- var SDK_VERSION = "0.1.0";
3549
+ var SDK_VERSION = "0.2.1";
3524
3550
  var LIBRARIES = /* @__PURE__ */ new Set([
3525
3551
  "ai-provider",
3526
3552
  "cli-protocol",
3527
3553
  "client",
3528
- "coding-agent",
3529
3554
  "contracts",
3530
3555
  "database",
3531
3556
  "database-pglite",
@@ -3533,7 +3558,6 @@ var LIBRARIES = /* @__PURE__ */ new Set([
3533
3558
  "dev-console",
3534
3559
  "harness",
3535
3560
  "kernel",
3536
- "sandbox",
3537
3561
  "server",
3538
3562
  "ui"
3539
3563
  ]);
@@ -3614,14 +3638,14 @@ import { createRequire as createRequire2 } from "node:module";
3614
3638
  import { lstat, readFile as readFile5 } from "node:fs/promises";
3615
3639
  import { join as join3, relative as relative3 } from "node:path";
3616
3640
  function moduleRoots(workspace) {
3617
- const roots = workspace.config.modules?.roots ?? ["modules"];
3618
- if (!Array.isArray(roots) || !roots.length || roots.some(
3641
+ const roots2 = workspace.config.modules?.roots ?? ["modules"];
3642
+ if (!Array.isArray(roots2) || !roots2.length || roots2.some(
3619
3643
  (root) => typeof root !== "string" || !root || root.startsWith(".") || root.includes("\\") || root.split("/").some((part) => part === ".." || !part)
3620
3644
  ))
3621
3645
  throw new Error(
3622
3646
  "Invalid modules.roots. Use relative workspace directories."
3623
3647
  );
3624
- return roots.map((root) => {
3648
+ return roots2.map((root) => {
3625
3649
  const path = resolveInside(workspace.root, root);
3626
3650
  if (!relative3(workspace.root, path))
3627
3651
  throw new Error("The workspace root cannot be a module root.");
@@ -4030,10 +4054,10 @@ async function validateModules(workspace, options = {}) {
4030
4054
  );
4031
4055
  }
4032
4056
  try {
4033
- const registered = manifests.map(
4057
+ const registered2 = manifests.map(
4034
4058
  (manifest2) => ({ manifest: manifest2 })
4035
4059
  );
4036
- const registry = createModuleRegistry(registered);
4060
+ const registry = createModuleRegistry(registered2);
4037
4061
  const enabled = config.modules?.enabled ?? [];
4038
4062
  const missingEnabled = enabled.filter((id) => !registry.has(id));
4039
4063
  if (missingEnabled.length > 0) {
@@ -4514,6 +4538,7 @@ var DatabaseError = class extends Error {
4514
4538
  this.code = code;
4515
4539
  this.name = "DatabaseError";
4516
4540
  }
4541
+ code;
4517
4542
  };
4518
4543
  function assertStatement(statement) {
4519
4544
  if (statement.text.trim().length === 0) {
@@ -4687,6 +4712,8 @@ var DatabaseMigrationError = class extends Error {
4687
4712
  this.migrationId = migrationId;
4688
4713
  this.name = "DatabaseMigrationError";
4689
4714
  }
4715
+ code;
4716
+ migrationId;
4690
4717
  };
4691
4718
  function assertMigrationId(id) {
4692
4719
  if (!/^[0-9]{4}_[a-z][a-z0-9_]{0,122}$/.test(id)) {
@@ -5030,6 +5057,9 @@ var PostgresSchemaIntrospector = class {
5030
5057
  this.baseSignal = baseSignal;
5031
5058
  this.ensureActive = ensureActive;
5032
5059
  }
5060
+ client;
5061
+ baseSignal;
5062
+ ensureActive;
5033
5063
  async hasTable(name, options = {}) {
5034
5064
  this.ensureActive();
5035
5065
  assertSchemaName(name);
@@ -5091,6 +5121,10 @@ var PostgresSession = class {
5091
5121
  ensureActive
5092
5122
  );
5093
5123
  }
5124
+ client;
5125
+ baseSignal;
5126
+ ensureActive;
5127
+ capabilities;
5094
5128
  adapterId = DATABASE_ADAPTER_IDS.postgresql;
5095
5129
  dialectId = DATABASE_DIALECT_IDS.postgresql;
5096
5130
  schema;
@@ -6017,19 +6051,35 @@ function createPgliteCluster(options = {}) {
6017
6051
  const close = () => {
6018
6052
  closePromise ??= (async () => {
6019
6053
  if (!databasePromise) return;
6020
- const instance = await databasePromise;
6054
+ const instance = await databasePromise.catch(() => void 0);
6021
6055
  databasePromise = void 0;
6022
- await instance.close();
6056
+ await instance?.close();
6023
6057
  })();
6024
6058
  return closePromise;
6025
6059
  };
6026
6060
  const database = async () => {
6027
6061
  databasePromise ??= (async () => {
6062
+ if (options.dataDirectory) {
6063
+ const { mkdir: mkdir6 } = await import("node:fs/promises");
6064
+ await mkdir6(options.dataDirectory, { recursive: true, mode: 448 });
6065
+ }
6028
6066
  const created = options.dataDirectory ? await PGlite.create({
6029
6067
  dataDir: options.dataDirectory,
6030
6068
  parsers: SERVER_PARSERS
6031
6069
  }) : await PGlite.create({ parsers: SERVER_PARSERS });
6032
- if (options.bootstrap) await created.exec(options.bootstrap);
6070
+ try {
6071
+ if (options.bootstrap) await created.exec(options.bootstrap);
6072
+ } catch (error) {
6073
+ try {
6074
+ await created.close();
6075
+ } catch (closeError) {
6076
+ throw new AggregateError(
6077
+ [error, closeError],
6078
+ "PGlite bootstrap failed and cleanup also failed."
6079
+ );
6080
+ }
6081
+ throw error;
6082
+ }
6033
6083
  return created;
6034
6084
  })();
6035
6085
  return databasePromise;
@@ -6352,9 +6402,10 @@ function generateServerComposition(modules, sdk = false, mounts = [], applicatio
6352
6402
  PlatformServerComposition,
6353
6403
  PlatformServerContext,
6354
6404
  } from '${sdk ? "@flowdular/sdk/modules/auth" : "@flowdular/module-auth"}/server';
6405
+ import type { WebMount } from '${sdk ? "@flowdular/sdk/server" : "@flowdular/server"}';
6355
6406
  `;
6356
6407
  if (server.length === 0) {
6357
- return GENERATED_HEADER + typeImport + "\nexport function composeModuleServer(\n _context: PlatformServerContext,\n): readonly PlatformServerComposition[] {\n return [];\n}\n\nexport const moduleWebMounts = " + JSON.stringify(mounts) + " as const;\nexport const applicationBasePath = " + JSON.stringify(applicationPath) + ";\n";
6408
+ return GENERATED_HEADER + typeImport + "\nexport function composeModuleServer(\n _context: PlatformServerContext,\n): readonly PlatformServerComposition[] {\n return [];\n}\n\nexport const moduleWebMounts: readonly WebMount[] = " + JSON.stringify(mounts) + " as const;\nexport const applicationBasePath = " + JSON.stringify(applicationPath) + ";\n";
6358
6409
  }
6359
6410
  const imports = server.map(
6360
6411
  (module) => platformImport(module.id, module.package, "createServerComposition")
@@ -6365,7 +6416,7 @@ function generateServerComposition(modules, sdk = false, mounts = [], applicatio
6365
6416
  agentDefinitions: context.agentDefinitions.forModule('${module.id}'),
6366
6417
  }), moduleId: '${module.id}' },`
6367
6418
  ).join("\n");
6368
- return GENERATED_HEADER + typeImport + imports + "\n\nexport function composeModuleServer(\n context: PlatformServerContext,\n): readonly PlatformServerComposition[] {\n return [\n" + calls + "\n ];\n}\n\nexport const moduleWebMounts = " + JSON.stringify(mounts) + " as const;\nexport const applicationBasePath = " + JSON.stringify(applicationPath) + ";\n";
6419
+ return GENERATED_HEADER + typeImport + imports + "\n\nexport function composeModuleServer(\n context: PlatformServerContext,\n): readonly PlatformServerComposition[] {\n return [\n" + calls + "\n ];\n}\n\nexport const moduleWebMounts: readonly WebMount[] = " + JSON.stringify(mounts) + " as const;\nexport const applicationBasePath = " + JSON.stringify(applicationPath) + ";\n";
6369
6420
  }
6370
6421
  function generateClientComposition(modules, sdk = false, applicationPath = "/app") {
6371
6422
  const client = modules.filter((module) => module.client);
@@ -6406,8 +6457,8 @@ async function loadManifests(workspace) {
6406
6457
  }
6407
6458
  return descriptors;
6408
6459
  }
6409
- function moduleOrder(manifests, roots, includeDependencies) {
6410
- const requested = new Set(roots);
6460
+ function moduleOrder(manifests, roots2, includeDependencies) {
6461
+ const requested = new Set(roots2);
6411
6462
  const visiting = [];
6412
6463
  const visited = /* @__PURE__ */ new Set();
6413
6464
  const ordered = [];
@@ -6492,7 +6543,8 @@ async function restoreSnapshots(snapshots) {
6492
6543
  async function syncPlatformModules(workspace, apply, enabledOverride) {
6493
6544
  if (workspace.config.web !== void 0 && !validators.web(workspace.config.web))
6494
6545
  throw new Error("Invalid web mount configuration in flowdular.json.");
6495
- if (workspace.config.application !== void 0 && !validators.application(workspace.config.application)) throw new Error("Invalid application path in flowdular.json.");
6546
+ if (workspace.config.application !== void 0 && !validators.application(workspace.config.application))
6547
+ throw new Error("Invalid application path in flowdular.json.");
6496
6548
  const applicationPath = workspace.config.application?.path ?? "/app";
6497
6549
  const enabled = enabledOverride ?? enabledModules(workspace);
6498
6550
  const sdk = (await sdkModules(workspace)).size > 0;
@@ -6505,7 +6557,8 @@ async function syncPlatformModules(workspace, apply, enabledOverride) {
6505
6557
  content: generateServerComposition(
6506
6558
  modules,
6507
6559
  sdk,
6508
- workspace.config.web?.mounts ?? []
6560
+ workspace.config.web?.mounts ?? [],
6561
+ applicationPath
6509
6562
  )
6510
6563
  },
6511
6564
  {
@@ -6749,14 +6802,15 @@ async function runDoctor(workspace) {
6749
6802
  message: project.valid ? "Project contract is valid." : project.issues.map((issue2) => issue2.message).join("; "),
6750
6803
  evidence: relative6(workspace.root, workspace.configPath)
6751
6804
  });
6752
- for (const [id, path] of [
6753
- ["policy.capabilities", ".ai/policies/capabilities.yaml"],
6754
- ["policy.model-routing", ".ai/policies/model-routing.yaml"],
6755
- ["blueprints.root", ".ai/blueprints"],
6756
- ["modules.root", "modules"],
6757
- ["packages.root", "packages"],
6758
- ["platform.root", "platform"]
6805
+ for (const [id, resource] of [
6806
+ ["policy.capabilities", agentResource(workspace, "policy")],
6807
+ ["policy.model-routing", agentResource(workspace, "modelRouting")],
6808
+ ["blueprints.root", agentResource(workspace, "blueprints")],
6809
+ ["modules.root", join9(workspace.root, "modules")],
6810
+ ...(await sdkModules(workspace)).size ? [] : [["packages.root", join9(workspace.root, "packages")]],
6811
+ ["platform.root", join9(workspace.root, "platform")]
6759
6812
  ]) {
6813
+ const path = relative6(workspace.root, resource);
6760
6814
  checks.push({
6761
6815
  id,
6762
6816
  status: await exists3(join9(workspace.root, path)) ? "pass" : "fail",
@@ -6773,7 +6827,7 @@ async function runDoctor(workspace) {
6773
6827
  message: packageJson2.packageManager ? `Pinned package manager: ${packageJson2.packageManager}` : "packageManager must pin pnpm.",
6774
6828
  evidence: "package.json"
6775
6829
  });
6776
- const blueprints = await findNamedFiles(workspace.root, "blueprint.json");
6830
+ const blueprints = await findBlueprintFiles(workspace);
6777
6831
  checks.push({
6778
6832
  id: "blueprints.discovered",
6779
6833
  status: blueprints.length > 0 ? "pass" : "fail",
@@ -6802,8 +6856,56 @@ async function runDoctor(workspace) {
6802
6856
 
6803
6857
  // src/extensions.ts
6804
6858
  import { readFile as readFile11 } from "node:fs/promises";
6805
- import { dirname as dirname9, relative as relative7 } from "node:path";
6806
- import { pathToFileURL as pathToFileURL2 } from "node:url";
6859
+ import { dirname as dirname10, relative as relative7 } from "node:path";
6860
+
6861
+ // src/module-import.ts
6862
+ import { readFileSync as readFileSync2, realpathSync } from "node:fs";
6863
+ import {
6864
+ createRequire as createRequire4,
6865
+ registerHooks,
6866
+ stripTypeScriptTypes
6867
+ } from "node:module";
6868
+ import { dirname as dirname9, sep as sep2 } from "node:path";
6869
+ import { fileURLToPath, pathToFileURL as pathToFileURL2 } from "node:url";
6870
+ var roots = /* @__PURE__ */ new Set();
6871
+ var registered = false;
6872
+ async function importModuleSource(entry, moduleRoot) {
6873
+ roots.add(realpathSync(moduleRoot));
6874
+ try {
6875
+ const require2 = createRequire4(pathToFileURL2(entry));
6876
+ roots.add(
6877
+ realpathSync(dirname9(require2.resolve("@flowdular/sdk/package.json")))
6878
+ );
6879
+ } catch (error) {
6880
+ if (!["MODULE_NOT_FOUND", "ERR_PACKAGE_PATH_NOT_EXPORTED"].includes(
6881
+ error.code ?? ""
6882
+ ))
6883
+ throw error;
6884
+ }
6885
+ if (!registered) {
6886
+ registerHooks({
6887
+ load(url, context, nextLoad) {
6888
+ if (!url.startsWith("file:") || !fileURLToPath(url).endsWith(".ts"))
6889
+ return nextLoad(url, context);
6890
+ const path = realpathSync(fileURLToPath(url));
6891
+ if (![...roots].some((root) => path.startsWith(root + sep2)))
6892
+ return nextLoad(url, context);
6893
+ return {
6894
+ format: "module",
6895
+ source: stripTypeScriptTypes(readFileSync2(path, "utf8"), {
6896
+ mode: "transform",
6897
+ sourceUrl: url
6898
+ }),
6899
+ shortCircuit: true
6900
+ };
6901
+ }
6902
+ });
6903
+ registered = true;
6904
+ }
6905
+ return import(pathToFileURL2(entry).href);
6906
+ }
6907
+
6908
+ // src/extensions.ts
6807
6909
  var reservedGroups = /* @__PURE__ */ new Set([
6808
6910
  "help",
6809
6911
  "doctor",
@@ -6859,7 +6961,10 @@ function commandKey(command) {
6859
6961
  return JSON.stringify({ path: command.path, capability: command.capability });
6860
6962
  }
6861
6963
  async function loadCliCommand(loaded) {
6862
- const imported = await import(pathToFileURL2(loaded.entry).href);
6964
+ const imported = await importModuleSource(
6965
+ loaded.entry,
6966
+ loaded.moduleRoot
6967
+ );
6863
6968
  const extension = imported.default ?? imported.cliExtension;
6864
6969
  if (!extension) {
6865
6970
  throw new Error(
@@ -6892,7 +6997,7 @@ async function loadCliExtensions(workspace) {
6892
6997
  await readFile11(manifestPath, "utf8")
6893
6998
  );
6894
6999
  if (!enabled.has(manifest2.id) || !manifest2.cli) continue;
6895
- const moduleRoot = dirname9(manifestPath);
7000
+ const moduleRoot = dirname10(manifestPath);
6896
7001
  const entry = await resolveExistingInside(moduleRoot, manifest2.cli.entry);
6897
7002
  const catalogPath = await resolveExistingInside(
6898
7003
  moduleRoot,
@@ -6929,9 +7034,8 @@ async function loadCliExtensions(workspace) {
6929
7034
  }
6930
7035
 
6931
7036
  // src/migration.ts
6932
- import { readFile as readFile13 } from "node:fs/promises";
6933
- import { dirname as dirname10, join as join11 } from "node:path";
6934
- import { pathToFileURL as pathToFileURL3 } from "node:url";
7037
+ import { readFile as readFile13, stat as stat2 } from "node:fs/promises";
7038
+ import { dirname as dirname11, join as join11 } from "node:path";
6935
7039
 
6936
7040
  // src/migration-audit.ts
6937
7041
  import { readdir as readdir5, readFile as readFile12 } from "node:fs/promises";
@@ -7095,13 +7199,17 @@ async function loadMigrationModules(workspace, moduleId) {
7095
7199
  );
7096
7200
  if (!enabled.has(manifest2.id)) continue;
7097
7201
  if (moduleId && manifest2.id !== moduleId) continue;
7098
- const entry = join11(dirname10(manifestPath), "src/services/migration.ts");
7099
- let databaseMigrations;
7202
+ const entry = join11(dirname11(manifestPath), "src/services/migration.ts");
7100
7203
  try {
7101
- ({ databaseMigrations } = await import(pathToFileURL3(entry).href));
7102
- } catch {
7103
- continue;
7204
+ await stat2(entry);
7205
+ } catch (error) {
7206
+ if (error.code === "ENOENT") continue;
7207
+ throw error;
7104
7208
  }
7209
+ const { databaseMigrations } = await importModuleSource(
7210
+ entry,
7211
+ dirname11(manifestPath)
7212
+ );
7105
7213
  if (!Array.isArray(databaseMigrations)) {
7106
7214
  unmanaged.push({
7107
7215
  moduleId: manifest2.id,
@@ -7111,7 +7219,7 @@ async function loadMigrationModules(workspace, moduleId) {
7111
7219
  }
7112
7220
  managed.push({
7113
7221
  moduleId: manifest2.id,
7114
- moduleDirectory: dirname10(manifestPath),
7222
+ moduleDirectory: dirname11(manifestPath),
7115
7223
  databaseMigrations
7116
7224
  });
7117
7225
  }
@@ -7338,7 +7446,7 @@ import {
7338
7446
  rm as rm3,
7339
7447
  writeFile as writeFile4
7340
7448
  } from "node:fs/promises";
7341
- import { dirname as dirname11, join as join12, relative as relative8, resolve as resolve5 } from "node:path";
7449
+ import { dirname as dirname12, join as join12, relative as relative8, resolve as resolve5 } from "node:path";
7342
7450
  import { parse as parseYaml4 } from "yaml";
7343
7451
 
7344
7452
  // src/module-templates.ts
@@ -7549,7 +7657,7 @@ function packageJson(model) {
7549
7657
  ...hasClient ? { "@tsrx/typescript-plugin": "0.3.120" } : {},
7550
7658
  "@types/node": "24.13.3",
7551
7659
  typescript: "5.9.3",
7552
- vitest: "4.1.10"
7660
+ vitest: "4.1.11"
7553
7661
  }
7554
7662
  });
7555
7663
  }
@@ -8707,7 +8815,7 @@ async function writeScaffold(moduleDirectory, files, target) {
8707
8815
  for (const [path, source] of files) {
8708
8816
  if (target.present.has(path)) continue;
8709
8817
  const absolute = join12(moduleDirectory, path);
8710
- const created = await mkdir4(dirname11(absolute), { recursive: true });
8818
+ const created = await mkdir4(dirname12(absolute), { recursive: true });
8711
8819
  if (created) createdDirectories.push(created);
8712
8820
  await writeFile4(absolute, source, { encoding: "utf8", flag: "wx" });
8713
8821
  written.push(absolute);
@@ -8807,9 +8915,9 @@ import {
8807
8915
  mkdir as mkdir5,
8808
8916
  readFile as readFile15,
8809
8917
  rm as rm4,
8810
- stat as stat2
8918
+ stat as stat3
8811
8919
  } from "node:fs/promises";
8812
- import { dirname as dirname12, join as join13, relative as relative9 } from "node:path";
8920
+ import { dirname as dirname13, join as join13, relative as relative9 } from "node:path";
8813
8921
  import { createHash as createHash3 } from "node:crypto";
8814
8922
 
8815
8923
  // ../kernel/src/legacy-local-state.ts
@@ -8933,7 +9041,7 @@ async function ensurePrivateDirectory(path) {
8933
9041
  await chmod(path, 448);
8934
9042
  }
8935
9043
  async function copyPlan(plan) {
8936
- await ensurePrivateDirectory(dirname12(plan.destinationDirectory));
9044
+ await ensurePrivateDirectory(dirname13(plan.destinationDirectory));
8937
9045
  await ensurePrivateDirectory(plan.destinationDirectory);
8938
9046
  const created = [];
8939
9047
  try {
@@ -8942,14 +9050,14 @@ async function copyPlan(plan) {
8942
9050
  if (sourceState.isSymbolicLink() || !sourceState.isFile()) {
8943
9051
  throw new Error(`${file.name} is no longer a regular file.`);
8944
9052
  }
8945
- const before = await stat2(file.source);
9053
+ const before = await stat3(file.source);
8946
9054
  await copyFile(file.source, file.destination, constants.COPYFILE_EXCL);
8947
9055
  created.push(file.destination);
8948
9056
  await chmod(file.destination, 384);
8949
9057
  const [sourceDigest2, destinationDigest, after] = await Promise.all([
8950
9058
  digest(file.source),
8951
9059
  digest(file.destination),
8952
- stat2(file.source)
9060
+ stat3(file.source)
8953
9061
  ]);
8954
9062
  if (sourceDigest2 !== destinationDigest || before.size !== after.size || before.mtimeMs !== after.mtimeMs) {
8955
9063
  throw new Error(`Source changed while copying ${file.name}.`);
@@ -8989,7 +9097,7 @@ function data(plan, applied) {
8989
9097
  ready: plan.blockers.length === 0,
8990
9098
  source: LEGACY_DATA_DIRECTORY,
8991
9099
  destination: relative9(
8992
- dirname12(dirname12(plan.destinationDirectory)),
9100
+ dirname13(dirname13(plan.destinationDirectory)),
8993
9101
  plan.destinationDirectory
8994
9102
  ),
8995
9103
  files: plan.files.map((file) => file.name),
@@ -9173,7 +9281,7 @@ async function runCommand(arguments_) {
9173
9281
  "module list|validate [--locked]|sync [--apply]|enable <id> [--apply]|disable <id> [--apply]|new <id> --spec <path> [--apply]",
9174
9282
  "migration status [--module <id>]|apply --module <id> [--apply]|verify|new <name> --module <id> [--apply]",
9175
9283
  "database reset [--module <id>] [--apply --confirm reset-database]",
9176
- "setup check|quick [--apply --confirm reset-local-auth]|migrate-state [--apply --confirm migrate-legacy-state]",
9284
+ "setup (interactive)|check|quick [--apply --confirm reset-local-auth]|migrate-state [--apply --confirm migrate-legacy-state]",
9177
9285
  ...extensionCommands.map((entry) => entry.command.path.join(" "))
9178
9286
  ],
9179
9287
  options: [
@@ -9291,13 +9399,13 @@ async function runCommand(arguments_) {
9291
9399
  );
9292
9400
  }
9293
9401
  if (group === "blueprint" && action === "list") {
9294
- const files = await findNamedFiles(workspace.root, "blueprint.json");
9402
+ const files = await findBlueprintFiles(workspace);
9295
9403
  return success({
9296
9404
  blueprints: files.map((file) => relative10(workspace.root, file))
9297
9405
  });
9298
9406
  }
9299
9407
  if (group === "blueprint" && action === "validate") {
9300
- const files = await findNamedFiles(workspace.root, "blueprint.json");
9408
+ const files = await findBlueprintFiles(workspace);
9301
9409
  const reports = await Promise.all(files.map(validateBlueprint));
9302
9410
  const data2 = relativeReports2(workspace.root, reports);
9303
9411
  return reports.every((report) => report.valid) ? success(
@@ -9594,11 +9702,213 @@ async function runProgram(arguments_) {
9594
9702
  }
9595
9703
  }
9596
9704
 
9705
+ // src/setup-wizard.ts
9706
+ import { stat as stat4 } from "node:fs/promises";
9707
+
9708
+ // src/setup-environment.ts
9709
+ import { randomUUID } from "node:crypto";
9710
+ import { lstat as lstat4, open, readFile as readFile17, rename as rename2, rm as rm5, writeFile as writeFile5 } from "node:fs/promises";
9711
+ import { join as join15 } from "node:path";
9712
+ async function readSetupEnvironment(root) {
9713
+ try {
9714
+ return await readFile17(join15(root, ".env"), "utf8");
9715
+ } catch (error) {
9716
+ if (error.code === "ENOENT") return "";
9717
+ throw error;
9718
+ }
9719
+ }
9720
+ async function saveSetupEnvironment(root, before, updates) {
9721
+ const path = join15(root, ".env");
9722
+ try {
9723
+ if (!(await lstat4(path)).isFile())
9724
+ throw new Error("Setup requires .env to be a regular file.");
9725
+ } catch (error) {
9726
+ if (error.code !== "ENOENT") throw error;
9727
+ }
9728
+ for (const [key, value] of Object.entries(updates)) {
9729
+ if (!/^FD_DATABASE_[A-Z_]+$/.test(key) || /[\r\n'\0]/.test(value))
9730
+ throw new Error("Invalid database setting.");
9731
+ }
9732
+ const lines = before.split(/\r?\n/).filter((line) => {
9733
+ const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(
9734
+ line
9735
+ );
9736
+ if (!match || !(match[1] in updates)) return true;
9737
+ const value = match[2].trim();
9738
+ if (value.startsWith('"') && !value.endsWith('"') || value.startsWith("'") && !value.endsWith("'"))
9739
+ throw new Error(
9740
+ "Edit multiline database settings in .env before using setup."
9741
+ );
9742
+ return false;
9743
+ });
9744
+ const source = lines.join("\n").trimEnd() + "\n" + Object.entries(updates).map(([key, value]) => `${key}='${value}'`).join("\n") + "\n";
9745
+ const temporary = join15(root, `.env.setup-${randomUUID()}.tmp`);
9746
+ const lockPath = join15(root, ".env.setup.lock");
9747
+ const lock = await open(lockPath, "wx", 384);
9748
+ try {
9749
+ await writeFile5(temporary, source, { mode: 384, flag: "wx" });
9750
+ if (await readSetupEnvironment(root) !== before)
9751
+ throw new Error("The .env file changed during setup. Run setup again.");
9752
+ await rename2(temporary, path);
9753
+ } finally {
9754
+ try {
9755
+ await rm5(temporary, { force: true });
9756
+ } finally {
9757
+ await lock.close();
9758
+ await rm5(lockPath, { force: true });
9759
+ }
9760
+ }
9761
+ }
9762
+
9763
+ // src/setup-wizard.ts
9764
+ var prompts = {
9765
+ async choose() {
9766
+ const { select } = await import("@inquirer/prompts");
9767
+ return select({
9768
+ message: "How would you like to set up this application?",
9769
+ choices: [
9770
+ {
9771
+ name: "Local demo with embedded PostgreSQL (PGlite)",
9772
+ value: "local"
9773
+ },
9774
+ {
9775
+ name: "Configure an existing PostgreSQL database",
9776
+ value: "postgresql"
9777
+ },
9778
+ { name: "Check configuration only", value: "check" }
9779
+ ]
9780
+ });
9781
+ },
9782
+ async confirm(message) {
9783
+ const { confirm } = await import("@inquirer/prompts");
9784
+ return confirm({ message, default: false });
9785
+ },
9786
+ async connection(message) {
9787
+ const { password } = await import("@inquirer/prompts");
9788
+ return password({
9789
+ message,
9790
+ mask: "*",
9791
+ validate: (value) => validConnection(value) ? true : "Use postgres://user:password@host/database without SSL query parameters."
9792
+ });
9793
+ }
9794
+ };
9795
+ function validConnection(value) {
9796
+ if (value.length > 4096 || /[\r\n'\0]/.test(value)) return false;
9797
+ try {
9798
+ const url = new URL(value);
9799
+ return ["postgres:", "postgresql:"].includes(url.protocol) && Boolean(url.hostname && url.username && url.pathname.length > 1) && !["sslmode", "sslcert", "sslkey", "sslrootcert"].some(
9800
+ (key) => url.searchParams.has(key)
9801
+ );
9802
+ } catch {
9803
+ return false;
9804
+ }
9805
+ }
9806
+ async function runSetupWizard(arguments_, io = prompts, execute = runProgram) {
9807
+ try {
9808
+ const flags = new Map(arguments_.flags);
9809
+ flags.delete("apply");
9810
+ flags.delete("confirm");
9811
+ const check = await execute({ positionals: ["setup", "check"], flags });
9812
+ if (!check.ok) return check;
9813
+ const workspace = await findWorkspace(
9814
+ stringFlag(arguments_, "root") ?? process.cwd()
9815
+ );
9816
+ const before = await readSetupEnvironment(workspace.root);
9817
+ const choice = await io.choose();
9818
+ if (choice === "check") return check;
9819
+ if (choice === "postgresql") {
9820
+ const urls = {
9821
+ FD_DATABASE_URL: await io.connection("Runtime role connection URL"),
9822
+ FD_DATABASE_MIGRATOR_URL: await io.connection(
9823
+ "Migration role connection URL"
9824
+ ),
9825
+ FD_DATABASE_BACKGROUND_URL: await io.connection(
9826
+ "Background role connection URL"
9827
+ )
9828
+ };
9829
+ if (!Object.values(urls).every(validConnection))
9830
+ return failure(
9831
+ "INVALID_DATABASE_CONFIGURATION",
9832
+ "Invalid PostgreSQL connection settings."
9833
+ );
9834
+ const updates = {
9835
+ ...urls,
9836
+ FD_DATABASE_ADAPTER: "postgresql",
9837
+ FD_DATABASE_TLS: "verify-full"
9838
+ };
9839
+ databaseProviderConfigFromEnvironment(
9840
+ { ...process.env, ...updates, NODE_ENV: "production" },
9841
+ workspace.root
9842
+ );
9843
+ if (!await io.confirm(
9844
+ "Save PostgreSQL settings in .env? This does not reset or modify the hosted database."
9845
+ ))
9846
+ return success({ cancelled: true });
9847
+ await saveSetupEnvironment(workspace.root, before, updates);
9848
+ return success(
9849
+ {
9850
+ setup: "postgresql",
9851
+ configured: true,
9852
+ adapter: "postgresql",
9853
+ next: "Run pnpm dev, then open http://localhost:4310 and create an account."
9854
+ },
9855
+ {
9856
+ warnings: [
9857
+ "TLS certificate verification is enabled. The database must provide separate migration, runtime and background roles."
9858
+ ]
9859
+ }
9860
+ );
9861
+ }
9862
+ const config = databaseProviderConfigFromEnvironment(
9863
+ process.env,
9864
+ workspace.root
9865
+ );
9866
+ if (config.adapter !== "pglite" || process.env.FD_DATABASE_PGLITE_DIRECTORY || process.env.CORELOOM_DATABASE_PGLITE_DIRECTORY || process.env.FD_DATABASE_URL || process.env.CORELOOM_DATABASE_URL)
9867
+ return failure(
9868
+ "LOCAL_DEMO_UNAVAILABLE",
9869
+ "Local demo setup requires the default embedded database. The configured database will not be reset."
9870
+ );
9871
+ const preview = await execute({ positionals: ["setup", "quick"], flags });
9872
+ if (!preview.ok) return preview;
9873
+ const directory = config.pglite?.dataDirectory;
9874
+ let existing = false;
9875
+ if (directory)
9876
+ try {
9877
+ await stat4(directory);
9878
+ existing = true;
9879
+ } catch (error) {
9880
+ if (error.code !== "ENOENT") throw error;
9881
+ }
9882
+ const message = existing ? `Reset the local demo database in ${directory}? All existing local data will be deleted. Confirm that the app is stopped.` : "Create a local demo with admin@example.com? Confirm that the app is stopped.";
9883
+ if (!await io.confirm(message)) return success({ cancelled: true });
9884
+ if (await readSetupEnvironment(workspace.root) !== before)
9885
+ return failure(
9886
+ "SETUP_CONFIGURATION_CHANGED",
9887
+ "The .env file changed during setup. Run setup again."
9888
+ );
9889
+ flags.set("apply", true);
9890
+ flags.set("confirm", "reset-local-auth");
9891
+ const applied = await execute({ positionals: ["setup", "quick"], flags });
9892
+ return applied.ok ? success(
9893
+ { ...applied.data, setup: "local" },
9894
+ { warnings: applied.warnings, evidence: applied.evidence }
9895
+ ) : applied;
9896
+ } catch (error) {
9897
+ if (error instanceof Error && ["ExitPromptError", "AbortPromptError"].includes(error.name))
9898
+ return success({ cancelled: true });
9899
+ return failure(
9900
+ "SETUP_FAILED",
9901
+ error instanceof Error ? error.message : "Setup failed."
9902
+ );
9903
+ }
9904
+ }
9905
+
9597
9906
  // src/index.ts
9598
9907
  var invokedAsProgram = process.argv[1]?.endsWith("/index.ts") || process.argv[1]?.endsWith("/index.js");
9599
9908
  if (invokedAsProgram) {
9600
9909
  const arguments_ = parseArguments(process.argv.slice(2));
9601
- const envelope = await runProgram(arguments_);
9910
+ const interactive = arguments_.positionals.length === 1 && arguments_.positionals[0] === "setup" && !arguments_.flags.has("json") && !arguments_.flags.has("help") && process.stdin.isTTY && process.stdout.isTTY;
9911
+ const envelope = await (interactive ? runSetupWizard(arguments_) : runProgram(arguments_));
9602
9912
  process.stdout.write(
9603
9913
  `${renderOutput(envelope, arguments_.flags.has("json"))}
9604
9914
  `