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 +5 -4
- package/dist/cli.js +59 -19
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
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`, `
|
|
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
|
-
> `
|
|
95
|
-
> daemon with `--embedding-model` — the value is written to `.env` as
|
|
96
|
-
> single source of
|
|
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
|
|
1801
|
-
#
|
|
1802
|
-
|
|
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
|
-
"
|
|
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
|
-
["
|
|
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
|
|
2284
|
-
"# read by docker compose via `.env` auto-load.
|
|
2285
|
-
"# change
|
|
2286
|
-
"# edits unless you explicitly re-select
|
|
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("
|
|
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 /
|
|
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
|
|
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.
|
|
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."
|