flowdular 0.2.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/README.md CHANGED
@@ -71,3 +71,16 @@ Release tooling can import the source distribution API through `flowdular/distri
71
71
  - [Documentation](https://github.com/Flowdular/flowdular/tree/main/docs) · [Source and issues](https://github.com/Flowdular/flowdular) · [Website](https://flowdular.com)
72
72
 
73
73
  Licensed under MIT. Repository documentation and source require repository access while the repositories are private.
74
+
75
+ ## Interactive setup
76
+
77
+ Run `pnpm flowdular setup` in a terminal. The wizard offers a local demo, existing
78
+ PostgreSQL connection settings, or a configuration check. Local resets require
79
+ confirmation and a stopped application; custom or hosted databases are refused.
80
+ PostgreSQL URLs are masked and saved to `.env` with TLS verification; this does
81
+ not initialize, reset or connect to the hosted database.
82
+
83
+ Without an interactive terminal, `setup` remains a configuration check. Use
84
+ `setup check --json` for machine-readable results and
85
+ `setup quick --apply --confirm reset-local-auth` for an explicitly authorized
86
+ local reset in a script. Ctrl+C or declining confirmation leaves data unchanged.
@@ -3405,7 +3405,7 @@ import { dirname as dirname6, join as join6, relative as relative5 } from "node:
3405
3405
  import { createRequire } from "node:module";
3406
3406
  import { readFile as readFile5 } from "node:fs/promises";
3407
3407
  import { dirname as dirname4, join as join3 } from "node:path";
3408
- var SDK_VERSION = "0.2.0";
3408
+ var SDK_VERSION = "0.2.1";
3409
3409
  function npmPackage(specifier) {
3410
3410
  return specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
3411
3411
  }
package/dist/index.js CHANGED
@@ -1999,15 +1999,42 @@ function isCapabilityList(values2) {
1999
1999
  }
2000
2000
  function renderOutput(envelope, json2) {
2001
2001
  if (json2) return JSON.stringify(envelope, null, 2);
2002
- if (!envelope.ok)
2003
- return `ERROR ${envelope.error?.code ?? "UNKNOWN"}
2002
+ if (!envelope.ok) {
2003
+ const heading = `ERROR ${envelope.error?.code ?? "UNKNOWN"}
2004
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
+ }
2005
2016
  const decorate = (body) => [
2006
2017
  body,
2007
2018
  ...envelope.warnings.map((warning) => `WARNING ${warning}`),
2008
2019
  ...envelope.evidence.map((item) => `EVIDENCE ${item}`)
2009
2020
  ].join("\n");
2010
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
+ }
2011
2038
  if (data2 && Array.isArray(data2.checks)) {
2012
2039
  return decorate(
2013
2040
  data2.checks.map((check) => {
@@ -2026,143 +2053,53 @@ ${envelope.error?.message ?? "Command failed."}`;
2026
2053
  return decorate(JSON.stringify(data2, null, 2));
2027
2054
  }
2028
2055
 
2029
- // ../kernel/src/errors.ts
2030
- var RegistryError = class extends Error {
2031
- code;
2032
- constructor(code, message) {
2033
- super(message);
2034
- this.name = "RegistryError";
2035
- this.code = code;
2036
- }
2037
- };
2038
-
2039
- // ../kernel/src/module-compatibility.ts
2040
- var import_semver = __toESM(require_semver2(), 1);
2041
- var PLATFORM_API_VERSION = "0.1.0";
2042
- function satisfiesModuleVersion(version, range) {
2043
- return (0, import_semver.valid)(version) !== null && (0, import_semver.validRange)(range) !== null && (0, import_semver.satisfies)(version, range);
2044
- }
2045
- function compareModuleVersions(left, right) {
2046
- return (0, import_semver.rcompare)(left, right);
2047
- }
2048
- function assertModuleCompatibility(manifest2, platformVersion = PLATFORM_API_VERSION) {
2049
- if (!(0, import_semver.valid)(manifest2.version))
2050
- throw new RegistryError(
2051
- "MODULE_VERSION_INVALID",
2052
- `Invalid version for ${manifest2.id}: ${manifest2.version}`
2053
- );
2054
- if (manifest2.platformApi !== void 0 && (!(0, import_semver.validRange)(manifest2.platformApi) || platformVersion !== null && !satisfiesModuleVersion(platformVersion, manifest2.platformApi))) {
2055
- throw new RegistryError(
2056
- "MODULE_PLATFORM_INCOMPATIBLE",
2057
- `${manifest2.id} requires platform API ${manifest2.platformApi}; available ${PLATFORM_API_VERSION}.`
2058
- );
2059
- }
2060
- const ids = /* @__PURE__ */ new Set();
2061
- for (const dependency of manifest2.dependencies) {
2062
- if (!dependency.range.trim() || !(0, import_semver.validRange)(dependency.range))
2063
- throw new RegistryError(
2064
- "MODULE_RANGE_INVALID",
2065
- `${manifest2.id} declares an invalid range for ${dependency.id}: ${dependency.range}`
2066
- );
2067
- if (ids.has(dependency.id))
2068
- throw new RegistryError(
2069
- "MODULE_DEPENDENCY_DUPLICATE",
2070
- `${manifest2.id} declares ${dependency.id} more than once.`
2071
- );
2072
- ids.add(dependency.id);
2073
- }
2074
- }
2075
- function assertModuleDependency(consumer, dependency, version) {
2076
- if (!satisfiesModuleVersion(version, dependency.range)) {
2077
- throw new RegistryError(
2078
- "MODULE_DEPENDENCY_INCOMPATIBLE",
2079
- `${consumer} requires ${dependency.id} ${dependency.range}; available ${version}.`
2080
- );
2081
- }
2082
- }
2083
-
2084
- // ../kernel/src/module-registry.ts
2085
- function compareModules(left, right) {
2086
- return left.manifest.id.localeCompare(right.manifest.id);
2087
- }
2088
- function visitModule(id, modules, visiting, visited, ordered) {
2089
- if (visited.has(id)) return;
2090
- if (visiting.has(id)) {
2091
- throw new RegistryError(
2092
- "MODULE_DEPENDENCY_CYCLE",
2093
- `Dependency cycle detected at "${id}".`
2094
- );
2095
- }
2096
- const current = modules.get(id);
2097
- if (!current) {
2098
- throw new RegistryError(
2099
- "MODULE_NOT_FOUND",
2100
- `Module "${id}" is not registered.`
2101
- );
2102
- }
2103
- visiting.add(id);
2104
- for (const dependency of [...current.manifest.dependencies].sort(
2105
- (a, b) => a.id.localeCompare(b.id)
2106
- )) {
2107
- if (!modules.has(dependency.id)) {
2108
- throw new RegistryError(
2109
- "MODULE_DEPENDENCY_MISSING",
2110
- `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."
2111
2074
  );
2112
2075
  }
2113
- assertModuleDependency(
2114
- id,
2115
- dependency,
2116
- modules.get(dependency.id).manifest.version
2117
- );
2118
- visitModule(dependency.id, modules, visiting, visited, ordered);
2076
+ directory = parent;
2119
2077
  }
2120
- visiting.delete(id);
2121
- visited.add(id);
2122
- ordered.push(current);
2123
2078
  }
2124
- function createModuleRegistry(input) {
2125
- const byId = /* @__PURE__ */ new Map();
2126
- const navigationIds = /* @__PURE__ */ new Set();
2127
- for (const module of input) {
2128
- assertModuleCompatibility(module.manifest);
2129
- if (byId.has(module.manifest.id)) {
2130
- throw new RegistryError(
2131
- "MODULE_DUPLICATE",
2132
- `Module "${module.manifest.id}" is registered more than once.`
2133
- );
2134
- }
2135
- byId.set(module.manifest.id, module);
2136
- for (const item of module.navigation ?? []) {
2137
- if (navigationIds.has(item.id)) {
2138
- throw new RegistryError(
2139
- "NAVIGATION_DUPLICATE",
2140
- `Navigation contribution "${item.id}" is registered more than once.`
2141
- );
2142
- }
2143
- navigationIds.add(item.id);
2144
- }
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}`);
2145
2084
  }
2146
- const ordered = [];
2147
- const visiting = /* @__PURE__ */ new Set();
2148
- const visited = /* @__PURE__ */ new Set();
2149
- for (const module of [...input].sort(compareModules)) {
2150
- 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}`);
2151
2093
  }
2152
- return Object.freeze({
2153
- modules: Object.freeze(ordered),
2154
- get: (id) => byId.get(id),
2155
- has: (id) => byId.has(id),
2156
- navigation: (permissions) => Object.freeze(
2157
- ordered.flatMap((module) => module.navigation ?? []).filter(
2158
- (item) => !item.permission || permissions?.has(item.permission)
2159
- ).sort(
2160
- (left, right) => (left.order ?? 1e3) - (right.order ?? 1e3) || left.id.localeCompare(right.id)
2161
- )
2162
- )
2163
- });
2094
+ return canonicalCandidate;
2164
2095
  }
2165
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
+
2166
2103
  // ../contracts/schemas/blueprint.schema.json
2167
2104
  var blueprint_schema_default = {
2168
2105
  $schema: "https://json-schema.org/draft/2020-12/schema",
@@ -2985,19 +2922,7 @@ var projectSchema = project_schema_default;
2985
2922
  var moduleCatalogSchema = module_catalog_schema_default;
2986
2923
  var moduleArtifactSchema = module_artifact_schema_default;
2987
2924
 
2988
- // src/module-catalog.ts
2989
- import { readFile as readFile3, stat } from "node:fs/promises";
2990
- import { dirname as dirname3, resolve as resolve2 } from "node:path";
2991
-
2992
- // src/module-artifact.ts
2993
- import { parse as parseYaml2 } from "yaml";
2994
- import { createHash } from "node:crypto";
2995
-
2996
2925
  // src/validation.ts
2997
- import { readdir, readFile } from "node:fs/promises";
2998
- import { basename, dirname, join, relative } from "node:path";
2999
- import Ajv2020 from "ajv/dist/2020.js";
3000
- import { parse as parseYaml } from "yaml";
3001
2926
  var ajv = new Ajv2020({ allErrors: true, strict: true });
3002
2927
  var validators = {
3003
2928
  application: ajv.compile(projectSchema.properties.application),
@@ -3020,7 +2945,7 @@ function issuesFrom(errors) {
3020
2945
  }));
3021
2946
  }
3022
2947
  async function parseFile(path) {
3023
- const source = await readFile(path, "utf8");
2948
+ const source = await readFile2(path, "utf8");
3024
2949
  return path.endsWith(".yaml") || path.endsWith(".yml") ? parseYaml(source) : JSON.parse(source);
3025
2950
  }
3026
2951
  async function validateFile(path, validator) {
@@ -3084,7 +3009,7 @@ var blueprintFiles = [
3084
3009
  ];
3085
3010
  async function validateBlueprint(path) {
3086
3011
  const report = await validateFile(path, validators.blueprint);
3087
- const directory = dirname(path);
3012
+ const directory = dirname2(path);
3088
3013
  const entries = new Set(
3089
3014
  (await readdir(directory)).filter((name) => basename(name) === name)
3090
3015
  );
@@ -3095,14 +3020,177 @@ async function validateBlueprint(path) {
3095
3020
  ...missing.map((name) => ({
3096
3021
  code: "BLUEPRINT_FILE_MISSING",
3097
3022
  message: `Required blueprint file is missing: ${name}`,
3098
- path: relative(process.cwd(), join(directory, name)),
3023
+ path: relative2(process.cwd(), join(directory, name)),
3099
3024
  severity: "error"
3100
3025
  }))
3101
3026
  ];
3102
3027
  return { ...report, valid: false, issues };
3103
3028
  }
3104
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
+
3105
3191
  // src/module-artifact.ts
3192
+ import { parse as parseYaml2 } from "yaml";
3193
+ import { createHash } from "node:crypto";
3106
3194
  var MAX_ARTIFACT_BYTES = 48 * 1024 * 1024;
3107
3195
  var MAX_SOURCE_BYTES = 32 * 1024 * 1024;
3108
3196
  var MAX_FILES = 4096;
@@ -3261,47 +3349,6 @@ function validateModuleArtifact(value) {
3261
3349
  return artifact;
3262
3350
  }
3263
3351
 
3264
- // src/workspace.ts
3265
- import { access, readFile as readFile2, realpath } from "node:fs/promises";
3266
- import { dirname as dirname2, isAbsolute, relative as relative2, resolve } from "node:path";
3267
- async function findWorkspace(start) {
3268
- let directory = resolve(start);
3269
- for (; ; ) {
3270
- const configPath = resolve(directory, "flowdular.json");
3271
- try {
3272
- await access(configPath);
3273
- const config = JSON.parse(await readFile2(configPath, "utf8"));
3274
- return { root: directory, configPath, config };
3275
- } catch (error) {
3276
- if (error instanceof SyntaxError) throw error;
3277
- }
3278
- const parent = dirname2(directory);
3279
- if (parent === directory) {
3280
- throw new Error(
3281
- "No flowdular.json was found in this directory or any parent."
3282
- );
3283
- }
3284
- directory = parent;
3285
- }
3286
- }
3287
- function resolveInside(root, candidate) {
3288
- const absolute = isAbsolute(candidate) ? resolve(candidate) : resolve(root, candidate);
3289
- const pathFromRoot = relative2(root, absolute);
3290
- if (pathFromRoot.startsWith("..") || isAbsolute(pathFromRoot)) {
3291
- throw new Error(`Path escapes the workspace: ${candidate}`);
3292
- }
3293
- return absolute;
3294
- }
3295
- async function resolveExistingInside(root, candidate) {
3296
- const canonicalRoot = await realpath(root);
3297
- const canonicalCandidate = await realpath(resolveInside(root, candidate));
3298
- const pathFromRoot = relative2(canonicalRoot, canonicalCandidate);
3299
- if (pathFromRoot.startsWith("..") || isAbsolute(pathFromRoot)) {
3300
- throw new Error(`Path escapes the workspace through a link: ${candidate}`);
3301
- }
3302
- return canonicalCandidate;
3303
- }
3304
-
3305
3352
  // src/module-catalog.ts
3306
3353
  var OFFICIAL_CATALOG = "https://raw.githubusercontent.com/Flowdular/official-modules/main/registry/index.json";
3307
3354
  var OFFICIAL_PREFIX = "https://raw.githubusercontent.com/Flowdular/official-modules/";
@@ -3499,7 +3546,7 @@ import { dirname as dirname6, join as join5, relative as relative5 } from "node:
3499
3546
  import { createRequire } from "node:module";
3500
3547
  import { readFile as readFile4 } from "node:fs/promises";
3501
3548
  import { dirname as dirname4, join as join2 } from "node:path";
3502
- var SDK_VERSION = "0.2.0";
3549
+ var SDK_VERSION = "0.2.1";
3503
3550
  var LIBRARIES = /* @__PURE__ */ new Set([
3504
3551
  "ai-provider",
3505
3552
  "cli-protocol",
@@ -3591,14 +3638,14 @@ import { createRequire as createRequire2 } from "node:module";
3591
3638
  import { lstat, readFile as readFile5 } from "node:fs/promises";
3592
3639
  import { join as join3, relative as relative3 } from "node:path";
3593
3640
  function moduleRoots(workspace) {
3594
- const roots = workspace.config.modules?.roots ?? ["modules"];
3595
- 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(
3596
3643
  (root) => typeof root !== "string" || !root || root.startsWith(".") || root.includes("\\") || root.split("/").some((part) => part === ".." || !part)
3597
3644
  ))
3598
3645
  throw new Error(
3599
3646
  "Invalid modules.roots. Use relative workspace directories."
3600
3647
  );
3601
- return roots.map((root) => {
3648
+ return roots2.map((root) => {
3602
3649
  const path = resolveInside(workspace.root, root);
3603
3650
  if (!relative3(workspace.root, path))
3604
3651
  throw new Error("The workspace root cannot be a module root.");
@@ -4007,10 +4054,10 @@ async function validateModules(workspace, options = {}) {
4007
4054
  );
4008
4055
  }
4009
4056
  try {
4010
- const registered = manifests.map(
4057
+ const registered2 = manifests.map(
4011
4058
  (manifest2) => ({ manifest: manifest2 })
4012
4059
  );
4013
- const registry = createModuleRegistry(registered);
4060
+ const registry = createModuleRegistry(registered2);
4014
4061
  const enabled = config.modules?.enabled ?? [];
4015
4062
  const missingEnabled = enabled.filter((id) => !registry.has(id));
4016
4063
  if (missingEnabled.length > 0) {
@@ -6004,19 +6051,35 @@ function createPgliteCluster(options = {}) {
6004
6051
  const close = () => {
6005
6052
  closePromise ??= (async () => {
6006
6053
  if (!databasePromise) return;
6007
- const instance = await databasePromise;
6054
+ const instance = await databasePromise.catch(() => void 0);
6008
6055
  databasePromise = void 0;
6009
- await instance.close();
6056
+ await instance?.close();
6010
6057
  })();
6011
6058
  return closePromise;
6012
6059
  };
6013
6060
  const database = async () => {
6014
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
+ }
6015
6066
  const created = options.dataDirectory ? await PGlite.create({
6016
6067
  dataDir: options.dataDirectory,
6017
6068
  parsers: SERVER_PARSERS
6018
6069
  }) : await PGlite.create({ parsers: SERVER_PARSERS });
6019
- 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
+ }
6020
6083
  return created;
6021
6084
  })();
6022
6085
  return databasePromise;
@@ -6394,8 +6457,8 @@ async function loadManifests(workspace) {
6394
6457
  }
6395
6458
  return descriptors;
6396
6459
  }
6397
- function moduleOrder(manifests, roots, includeDependencies) {
6398
- const requested = new Set(roots);
6460
+ function moduleOrder(manifests, roots2, includeDependencies) {
6461
+ const requested = new Set(roots2);
6399
6462
  const visiting = [];
6400
6463
  const visited = /* @__PURE__ */ new Set();
6401
6464
  const ordered = [];
@@ -6739,14 +6802,15 @@ async function runDoctor(workspace) {
6739
6802
  message: project.valid ? "Project contract is valid." : project.issues.map((issue2) => issue2.message).join("; "),
6740
6803
  evidence: relative6(workspace.root, workspace.configPath)
6741
6804
  });
6742
- for (const [id, path] of [
6743
- ["policy.capabilities", ".ai/policies/capabilities.yaml"],
6744
- ["policy.model-routing", ".ai/policies/model-routing.yaml"],
6745
- ["blueprints.root", ".ai/blueprints"],
6746
- ["modules.root", "modules"],
6747
- ["packages.root", "packages"],
6748
- ["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")]
6749
6812
  ]) {
6813
+ const path = relative6(workspace.root, resource);
6750
6814
  checks.push({
6751
6815
  id,
6752
6816
  status: await exists3(join9(workspace.root, path)) ? "pass" : "fail",
@@ -6763,7 +6827,7 @@ async function runDoctor(workspace) {
6763
6827
  message: packageJson2.packageManager ? `Pinned package manager: ${packageJson2.packageManager}` : "packageManager must pin pnpm.",
6764
6828
  evidence: "package.json"
6765
6829
  });
6766
- const blueprints = await findNamedFiles(workspace.root, "blueprint.json");
6830
+ const blueprints = await findBlueprintFiles(workspace);
6767
6831
  checks.push({
6768
6832
  id: "blueprints.discovered",
6769
6833
  status: blueprints.length > 0 ? "pass" : "fail",
@@ -6792,8 +6856,56 @@ async function runDoctor(workspace) {
6792
6856
 
6793
6857
  // src/extensions.ts
6794
6858
  import { readFile as readFile11 } from "node:fs/promises";
6795
- import { dirname as dirname9, relative as relative7 } from "node:path";
6796
- 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
6797
6909
  var reservedGroups = /* @__PURE__ */ new Set([
6798
6910
  "help",
6799
6911
  "doctor",
@@ -6849,7 +6961,10 @@ function commandKey(command) {
6849
6961
  return JSON.stringify({ path: command.path, capability: command.capability });
6850
6962
  }
6851
6963
  async function loadCliCommand(loaded) {
6852
- const imported = await import(pathToFileURL2(loaded.entry).href);
6964
+ const imported = await importModuleSource(
6965
+ loaded.entry,
6966
+ loaded.moduleRoot
6967
+ );
6853
6968
  const extension = imported.default ?? imported.cliExtension;
6854
6969
  if (!extension) {
6855
6970
  throw new Error(
@@ -6882,7 +6997,7 @@ async function loadCliExtensions(workspace) {
6882
6997
  await readFile11(manifestPath, "utf8")
6883
6998
  );
6884
6999
  if (!enabled.has(manifest2.id) || !manifest2.cli) continue;
6885
- const moduleRoot = dirname9(manifestPath);
7000
+ const moduleRoot = dirname10(manifestPath);
6886
7001
  const entry = await resolveExistingInside(moduleRoot, manifest2.cli.entry);
6887
7002
  const catalogPath = await resolveExistingInside(
6888
7003
  moduleRoot,
@@ -6919,9 +7034,8 @@ async function loadCliExtensions(workspace) {
6919
7034
  }
6920
7035
 
6921
7036
  // src/migration.ts
6922
- import { readFile as readFile13 } from "node:fs/promises";
6923
- import { dirname as dirname10, join as join11 } from "node:path";
6924
- 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";
6925
7039
 
6926
7040
  // src/migration-audit.ts
6927
7041
  import { readdir as readdir5, readFile as readFile12 } from "node:fs/promises";
@@ -7085,13 +7199,17 @@ async function loadMigrationModules(workspace, moduleId) {
7085
7199
  );
7086
7200
  if (!enabled.has(manifest2.id)) continue;
7087
7201
  if (moduleId && manifest2.id !== moduleId) continue;
7088
- const entry = join11(dirname10(manifestPath), "src/services/migration.ts");
7089
- let databaseMigrations;
7202
+ const entry = join11(dirname11(manifestPath), "src/services/migration.ts");
7090
7203
  try {
7091
- ({ databaseMigrations } = await import(pathToFileURL3(entry).href));
7092
- } catch {
7093
- continue;
7204
+ await stat2(entry);
7205
+ } catch (error) {
7206
+ if (error.code === "ENOENT") continue;
7207
+ throw error;
7094
7208
  }
7209
+ const { databaseMigrations } = await importModuleSource(
7210
+ entry,
7211
+ dirname11(manifestPath)
7212
+ );
7095
7213
  if (!Array.isArray(databaseMigrations)) {
7096
7214
  unmanaged.push({
7097
7215
  moduleId: manifest2.id,
@@ -7101,7 +7219,7 @@ async function loadMigrationModules(workspace, moduleId) {
7101
7219
  }
7102
7220
  managed.push({
7103
7221
  moduleId: manifest2.id,
7104
- moduleDirectory: dirname10(manifestPath),
7222
+ moduleDirectory: dirname11(manifestPath),
7105
7223
  databaseMigrations
7106
7224
  });
7107
7225
  }
@@ -7328,7 +7446,7 @@ import {
7328
7446
  rm as rm3,
7329
7447
  writeFile as writeFile4
7330
7448
  } from "node:fs/promises";
7331
- 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";
7332
7450
  import { parse as parseYaml4 } from "yaml";
7333
7451
 
7334
7452
  // src/module-templates.ts
@@ -8697,7 +8815,7 @@ async function writeScaffold(moduleDirectory, files, target) {
8697
8815
  for (const [path, source] of files) {
8698
8816
  if (target.present.has(path)) continue;
8699
8817
  const absolute = join12(moduleDirectory, path);
8700
- const created = await mkdir4(dirname11(absolute), { recursive: true });
8818
+ const created = await mkdir4(dirname12(absolute), { recursive: true });
8701
8819
  if (created) createdDirectories.push(created);
8702
8820
  await writeFile4(absolute, source, { encoding: "utf8", flag: "wx" });
8703
8821
  written.push(absolute);
@@ -8797,9 +8915,9 @@ import {
8797
8915
  mkdir as mkdir5,
8798
8916
  readFile as readFile15,
8799
8917
  rm as rm4,
8800
- stat as stat2
8918
+ stat as stat3
8801
8919
  } from "node:fs/promises";
8802
- 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";
8803
8921
  import { createHash as createHash3 } from "node:crypto";
8804
8922
 
8805
8923
  // ../kernel/src/legacy-local-state.ts
@@ -8923,7 +9041,7 @@ async function ensurePrivateDirectory(path) {
8923
9041
  await chmod(path, 448);
8924
9042
  }
8925
9043
  async function copyPlan(plan) {
8926
- await ensurePrivateDirectory(dirname12(plan.destinationDirectory));
9044
+ await ensurePrivateDirectory(dirname13(plan.destinationDirectory));
8927
9045
  await ensurePrivateDirectory(plan.destinationDirectory);
8928
9046
  const created = [];
8929
9047
  try {
@@ -8932,14 +9050,14 @@ async function copyPlan(plan) {
8932
9050
  if (sourceState.isSymbolicLink() || !sourceState.isFile()) {
8933
9051
  throw new Error(`${file.name} is no longer a regular file.`);
8934
9052
  }
8935
- const before = await stat2(file.source);
9053
+ const before = await stat3(file.source);
8936
9054
  await copyFile(file.source, file.destination, constants.COPYFILE_EXCL);
8937
9055
  created.push(file.destination);
8938
9056
  await chmod(file.destination, 384);
8939
9057
  const [sourceDigest2, destinationDigest, after] = await Promise.all([
8940
9058
  digest(file.source),
8941
9059
  digest(file.destination),
8942
- stat2(file.source)
9060
+ stat3(file.source)
8943
9061
  ]);
8944
9062
  if (sourceDigest2 !== destinationDigest || before.size !== after.size || before.mtimeMs !== after.mtimeMs) {
8945
9063
  throw new Error(`Source changed while copying ${file.name}.`);
@@ -8979,7 +9097,7 @@ function data(plan, applied) {
8979
9097
  ready: plan.blockers.length === 0,
8980
9098
  source: LEGACY_DATA_DIRECTORY,
8981
9099
  destination: relative9(
8982
- dirname12(dirname12(plan.destinationDirectory)),
9100
+ dirname13(dirname13(plan.destinationDirectory)),
8983
9101
  plan.destinationDirectory
8984
9102
  ),
8985
9103
  files: plan.files.map((file) => file.name),
@@ -9163,7 +9281,7 @@ async function runCommand(arguments_) {
9163
9281
  "module list|validate [--locked]|sync [--apply]|enable <id> [--apply]|disable <id> [--apply]|new <id> --spec <path> [--apply]",
9164
9282
  "migration status [--module <id>]|apply --module <id> [--apply]|verify|new <name> --module <id> [--apply]",
9165
9283
  "database reset [--module <id>] [--apply --confirm reset-database]",
9166
- "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]",
9167
9285
  ...extensionCommands.map((entry) => entry.command.path.join(" "))
9168
9286
  ],
9169
9287
  options: [
@@ -9281,13 +9399,13 @@ async function runCommand(arguments_) {
9281
9399
  );
9282
9400
  }
9283
9401
  if (group === "blueprint" && action === "list") {
9284
- const files = await findNamedFiles(workspace.root, "blueprint.json");
9402
+ const files = await findBlueprintFiles(workspace);
9285
9403
  return success({
9286
9404
  blueprints: files.map((file) => relative10(workspace.root, file))
9287
9405
  });
9288
9406
  }
9289
9407
  if (group === "blueprint" && action === "validate") {
9290
- const files = await findNamedFiles(workspace.root, "blueprint.json");
9408
+ const files = await findBlueprintFiles(workspace);
9291
9409
  const reports = await Promise.all(files.map(validateBlueprint));
9292
9410
  const data2 = relativeReports2(workspace.root, reports);
9293
9411
  return reports.every((report) => report.valid) ? success(
@@ -9584,11 +9702,213 @@ async function runProgram(arguments_) {
9584
9702
  }
9585
9703
  }
9586
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
+
9587
9906
  // src/index.ts
9588
9907
  var invokedAsProgram = process.argv[1]?.endsWith("/index.ts") || process.argv[1]?.endsWith("/index.js");
9589
9908
  if (invokedAsProgram) {
9590
9909
  const arguments_ = parseArguments(process.argv.slice(2));
9591
- 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_));
9592
9912
  process.stdout.write(
9593
9913
  `${renderOutput(envelope, arguments_.flags.has("json"))}
9594
9914
  `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowdular",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "flowdular": "./dist/index.js",
@@ -12,10 +12,11 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@electric-sql/pglite": "0.5.8",
15
+ "@inquirer/prompts": "8.7.2",
15
16
  "ajv": "8.20.0",
16
17
  "pg": "8.23.0",
17
18
  "yaml": "2.9.0",
18
- "@flowdular/sdk": "0.2.0"
19
+ "@flowdular/sdk": "0.2.1"
19
20
  },
20
21
  "license": "MIT",
21
22
  "repository": {