create-op-node 0.15.0 → 0.15.2

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
@@ -72,7 +72,7 @@ The chosen models flow two places:
72
72
  1. **Ollama**: bootstrap pulls + warms them so the daemon has them
73
73
  resident before the stack comes up. The embedding model pulls first
74
74
  (small, fast feedback); the LLM pulls second (can be tens of GB).
75
- 2. **The node `.env`**: bootstrap writes `LLM_MODEL`, `EMBEDDINGS_MODEL`,
75
+ 2. **The node `.env`**: bootstrap writes `LLM_MODEL`, `EMBEDDINGS_OLLAMA_MODEL`,
76
76
  and `EMBEDDINGS_PROVIDER` into a managed block of the region repo's
77
77
  `.env`, which docker compose auto-loads. This is the **single source of
78
78
  truth** — every service (and every future partial recreate) resolves
@@ -91,9 +91,10 @@ The chosen models flow two places:
91
91
 
92
92
  > **`--embeddings-provider` picks where embeddings run.** The default is
93
93
  > `xenova` (in-process), which bundles its own embedding model and ignores
94
- > `EMBEDDINGS_MODEL`. Pass `--embeddings-provider ollama` to use the host
95
- > daemon with `--embedding-model` — the value is written to `.env` as the
96
- > single source of truth. See `docs/provider-pattern.md`.
94
+ > `EMBEDDINGS_OLLAMA_MODEL`. Pass `--embeddings-provider ollama` to use the host
95
+ > daemon with `--embedding-model` — the value is written to `.env` (as
96
+ > `EMBEDDINGS_OLLAMA_MODEL`, the key the backend reads) as the single source of
97
+ > truth. See `docs/provider-pattern.md`.
97
98
 
98
99
  > **Template contract**: for `LLM_MODEL` to actually change the running
99
100
  > model, the region repo's `docker-compose-prod.yml` must use
package/dist/cli.js CHANGED
@@ -1797,9 +1797,10 @@ if [ -n "$TUNNEL_TOKEN_VAL" ]; then
1797
1797
  export TUNNEL_TOKEN="$TUNNEL_TOKEN_VAL"
1798
1798
  fi
1799
1799
 
1800
- # SUPABASE_URL is not a secret \u2014 read from env if the operator set one,
1801
- # default to localhost for --local-only.
1802
- export SUPABASE_URL="\${SUPABASE_URL:-http://localhost:8000}"
1800
+ # SUPABASE_URL is NOT exported here \u2014 it's non-secret config that lives in the
1801
+ # node .env managed block (create-op-node writes it), which docker compose
1802
+ # auto-loads. Exporting it would override that .env value with a stale default
1803
+ # and silently break browser-facing auth URLs. See opuspopuli-node#43.
1803
1804
 
1804
1805
  ${promptServiceUrlExport}# prompt-service credentials. Always exported \u2014 harmless when the operator
1805
1806
  # is NOT colocating prompt-service (the main compose doesn't read these
@@ -2223,16 +2224,18 @@ var MANAGED_END = "# <<< op-node managed <<<";
2223
2224
  var MANAGED_KEYS = [
2224
2225
  "LLM_MODEL",
2225
2226
  "EMBEDDINGS_PROVIDER",
2226
- "EMBEDDINGS_MODEL",
2227
- "NODE_ENV"
2227
+ "EMBEDDINGS_OLLAMA_MODEL",
2228
+ "NODE_ENV",
2229
+ "SUPABASE_URL"
2228
2230
  ];
2229
2231
  var ENV_VALUE_RE = /^[A-Za-z0-9][A-Za-z0-9._:/-]*$/;
2230
2232
  function selectionToPairs(sel) {
2231
2233
  return [
2232
2234
  ["LLM_MODEL", sel.llmModel],
2233
2235
  ["EMBEDDINGS_PROVIDER", sel.embeddingsProvider],
2234
- ["EMBEDDINGS_MODEL", sel.embeddingModel],
2235
- ["NODE_ENV", sel.nodeEnv]
2236
+ ["EMBEDDINGS_OLLAMA_MODEL", sel.embeddingModel],
2237
+ ["NODE_ENV", sel.nodeEnv],
2238
+ ["SUPABASE_URL", sel.supabaseUrl]
2236
2239
  ];
2237
2240
  }
2238
2241
  function parseEnvContent(content) {
@@ -2280,10 +2283,10 @@ function stripManagedKeyLines(region) {
2280
2283
  function renderBlock(values) {
2281
2284
  return [
2282
2285
  MANAGED_BEGIN,
2283
- "# Auto-generated by `create-op-node bootstrap`. Non-secret model config,",
2284
- "# read by docker compose via `.env` auto-load. Edit LLM_MODEL here to",
2285
- "# change the model for every service; re-running bootstrap preserves your",
2286
- "# edits unless you explicitly re-select a model. Secrets do NOT live here.",
2286
+ "# Auto-generated by `create-op-node bootstrap`. Non-secret config (model",
2287
+ "# selection + SUPABASE_URL), read by docker compose via `.env` auto-load.",
2288
+ "# Edit a value here to change it for every service; re-running bootstrap",
2289
+ "# preserves your edits unless you explicitly re-select. Secrets do NOT live here.",
2287
2290
  ...values.map(([k, v]) => `${k}=${v}`),
2288
2291
  MANAGED_END
2289
2292
  ];
@@ -2336,13 +2339,15 @@ async function readEnvModelConfig(repoDir) {
2336
2339
  const map = parseEnvContent(content);
2337
2340
  const cfg = {};
2338
2341
  const llm = map.get("LLM_MODEL");
2339
- const emb = map.get("EMBEDDINGS_MODEL");
2342
+ const emb = map.get("EMBEDDINGS_OLLAMA_MODEL");
2340
2343
  const prov = map.get("EMBEDDINGS_PROVIDER");
2341
2344
  const node = map.get("NODE_ENV");
2345
+ const supa = map.get("SUPABASE_URL");
2342
2346
  if (llm !== void 0) cfg.llmModel = llm;
2343
2347
  if (emb !== void 0) cfg.embeddingModel = emb;
2344
2348
  if (prov !== void 0) cfg.embeddingsProvider = prov;
2345
2349
  if (node !== void 0) cfg.nodeEnv = node;
2350
+ if (supa !== void 0) cfg.supabaseUrl = supa;
2346
2351
  return cfg;
2347
2352
  }
2348
2353
  async function writeManagedEnv(repoDir, selection, opts = {}) {
@@ -2506,7 +2511,8 @@ var bootstrapCommand = new Command("bootstrap").description(
2506
2511
  llmModel,
2507
2512
  embeddingModel,
2508
2513
  embeddingsProvider,
2509
- localOnly: Boolean(opts.localOnly)
2514
+ localOnly: Boolean(opts.localOnly),
2515
+ supabaseUrl: secrets.supabaseUrl
2510
2516
  });
2511
2517
  await runStackPhase({ opts, repoPath, region, composeFile, secrets });
2512
2518
  });
@@ -2975,7 +2981,7 @@ async function runOllamaPhase(args) {
2975
2981
  }
2976
2982
  }
2977
2983
  async function runEnvFilePhase(args) {
2978
- const { repoPath, llmModel, embeddingModel, embeddingsProvider, localOnly } = args;
2984
+ const { repoPath, llmModel, embeddingModel, embeddingsProvider, localOnly, supabaseUrl } = args;
2979
2985
  const envSpin = p3.spinner();
2980
2986
  envSpin.start("Writing model config to the node .env\u2026");
2981
2987
  const res = await writeManagedEnv(
@@ -2986,7 +2992,10 @@ async function runEnvFilePhase(args) {
2986
2992
  embeddingsProvider,
2987
2993
  // NODE_ENV=development only for local-dev nodes; production nodes leave
2988
2994
  // it unset (the compose default / container images decide).
2989
- ...localOnly ? { nodeEnv: "development" } : {}
2995
+ ...localOnly ? { nodeEnv: "development" } : {},
2996
+ // SUPABASE_URL lives in the managed block (not an op-compose export) so
2997
+ // docker compose reads the node's real public URL from .env durably (#43).
2998
+ supabaseUrl
2990
2999
  },
2991
3000
  { overwrite: true }
2992
3001
  );
@@ -3029,7 +3038,7 @@ function buildComposeEnv(args) {
3029
3038
  ...secrets.promptServiceApiKey !== void 0 ? { PROMPT_SERVICE_API_KEY: secrets.promptServiceApiKey } : {},
3030
3039
  ...secrets.promptServiceApiKeys !== void 0 ? { PROMPT_SERVICE_API_KEYS: secrets.promptServiceApiKeys } : {},
3031
3040
  ...secrets.promptServiceAdminApiKeys !== void 0 ? { PROMPT_SERVICE_ADMIN_API_KEYS: secrets.promptServiceAdminApiKeys } : {}
3032
- // Model config (LLM_MODEL / EMBEDDINGS_MODEL / NODE_ENV) is intentionally
3041
+ // Model config (LLM_MODEL / EMBEDDINGS_OLLAMA_MODEL / NODE_ENV) is intentionally
3033
3042
  // NOT injected here — it lives in the node's `.env` (runEnvFilePhase),
3034
3043
  // which docker compose auto-loads. Injecting it into the subprocess env
3035
3044
  // would shadow `.env` (shell env > .env) and reintroduce the drift the
@@ -4010,6 +4019,7 @@ async function runVerify(input, deps = DEFAULT_DEPS2) {
4010
4019
  await verifyHealthPhase(input, deps, push);
4011
4020
  await verifyGraphqlPhase(input, deps, push);
4012
4021
  await verifyOllamaModelsPhase(input, deps, push);
4022
+ verifySupabaseUrlPhase(input, push);
4013
4023
  await verifyCloudflarePhase(input, deps, push);
4014
4024
  await verifyCosignPhase(input, deps, push);
4015
4025
  return { phases };
@@ -4087,6 +4097,28 @@ async function verifyOllamaModelsPhase(input, deps, push) {
4087
4097
  }
4088
4098
  push({ name: "Ollama models", status: "ok", detail: `${required.join(", ")} present` });
4089
4099
  }
4100
+ function verifySupabaseUrlPhase(input, push) {
4101
+ if (input.localOnly) {
4102
+ return skipLocalOnly("SUPABASE_URL", "local-only node \u2014 localhost:8000 is expected", push);
4103
+ }
4104
+ if (input.supabaseUrl === void 0) {
4105
+ push({
4106
+ name: "SUPABASE_URL",
4107
+ status: "skipped",
4108
+ detail: "run on the node (or pass --repo-dir) so its .env is read"
4109
+ });
4110
+ return;
4111
+ }
4112
+ if (input.supabaseUrl === "http://localhost:8000") {
4113
+ push({
4114
+ name: "SUPABASE_URL",
4115
+ status: "warn",
4116
+ detail: "still http://localhost:8000 on a non-local node \u2014 browser-facing auth URLs will point at localhost; set SUPABASE_URL in the node .env to the public URL"
4117
+ });
4118
+ return;
4119
+ }
4120
+ push({ name: "SUPABASE_URL", status: "ok", detail: input.supabaseUrl });
4121
+ }
4090
4122
  async function verifyCloudflarePhase(input, deps, push) {
4091
4123
  if (input.localOnly) return skipLocalOnly("Cloudflare Tunnel", "local-only node \u2014 no tunnel", push);
4092
4124
  const cfFields = [
@@ -4200,7 +4232,8 @@ var verifyCommand = new Command("verify").description(
4200
4232
  const apiHost = localOnly ? "" : opts.apiHost ?? `api.${domain}`;
4201
4233
  const images = opts.image ?? [];
4202
4234
  const ollama = await resolveOllamaVerifyInput(opts);
4203
- const totalPhases = 5 + (images.length === 0 ? 1 : images.length);
4235
+ const supabaseUrl = await resolveSupabaseUrlVerifyInput(opts);
4236
+ const totalPhases = 6 + (images.length === 0 ? 1 : images.length);
4204
4237
  const report = await runVerifyWithSpinner({
4205
4238
  apiHost,
4206
4239
  certWarnDays,
@@ -4209,7 +4242,8 @@ var verifyCommand = new Command("verify").description(
4209
4242
  images,
4210
4243
  totalPhases,
4211
4244
  localOnly,
4212
- ...ollama ? { ollama } : {}
4245
+ ...ollama ? { ollama } : {},
4246
+ ...supabaseUrl !== void 0 ? { supabaseUrl } : {}
4213
4247
  });
4214
4248
  renderVerifySummary(report, { opts, totalPhases });
4215
4249
  reportVerifyOutcome(report);
@@ -4306,6 +4340,12 @@ async function resolveOllamaVerifyInput(opts) {
4306
4340
  }
4307
4341
  return mergeOllamaModelConfig(opts, envCfg);
4308
4342
  }
4343
+ async function resolveSupabaseUrlVerifyInput(opts) {
4344
+ const explicit = opts.repoDir !== void 0;
4345
+ const dir = opts.repoDir ?? process.cwd();
4346
+ if (!explicit && !await looksLikeNodeRepo(dir)) return void 0;
4347
+ return (await readEnvModelConfig(dir)).supabaseUrl;
4348
+ }
4309
4349
  async function runVerifyWithSpinner(args) {
4310
4350
  const { apiHost, certWarnDays, cfToken, opts, images, totalPhases, localOnly, ollama } = args;
4311
4351
  let phaseIndex = 0;
@@ -5445,7 +5485,7 @@ function withDefaultSubcommand(argv, known) {
5445
5485
  }
5446
5486
 
5447
5487
  // src/cli.ts
5448
- var VERSION = "0.15.0";
5488
+ var VERSION = "0.15.2";
5449
5489
  var program = new Command();
5450
5490
  program.name("create-op-node").description(
5451
5491
  "Interactive bootstrap for an Opus Populi federation node.\nCloudflare infrastructure \u2192 Mac Studio \u2192 live public API."