create-op-node 0.14.0 → 0.15.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 +5 -4
- package/dist/cli.js +28 -11
- 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
|
@@ -2223,7 +2223,7 @@ var MANAGED_END = "# <<< op-node managed <<<";
|
|
|
2223
2223
|
var MANAGED_KEYS = [
|
|
2224
2224
|
"LLM_MODEL",
|
|
2225
2225
|
"EMBEDDINGS_PROVIDER",
|
|
2226
|
-
"
|
|
2226
|
+
"EMBEDDINGS_OLLAMA_MODEL",
|
|
2227
2227
|
"NODE_ENV"
|
|
2228
2228
|
];
|
|
2229
2229
|
var ENV_VALUE_RE = /^[A-Za-z0-9][A-Za-z0-9._:/-]*$/;
|
|
@@ -2231,7 +2231,7 @@ function selectionToPairs(sel) {
|
|
|
2231
2231
|
return [
|
|
2232
2232
|
["LLM_MODEL", sel.llmModel],
|
|
2233
2233
|
["EMBEDDINGS_PROVIDER", sel.embeddingsProvider],
|
|
2234
|
-
["
|
|
2234
|
+
["EMBEDDINGS_OLLAMA_MODEL", sel.embeddingModel],
|
|
2235
2235
|
["NODE_ENV", sel.nodeEnv]
|
|
2236
2236
|
];
|
|
2237
2237
|
}
|
|
@@ -2336,7 +2336,7 @@ async function readEnvModelConfig(repoDir) {
|
|
|
2336
2336
|
const map = parseEnvContent(content);
|
|
2337
2337
|
const cfg = {};
|
|
2338
2338
|
const llm = map.get("LLM_MODEL");
|
|
2339
|
-
const emb = map.get("
|
|
2339
|
+
const emb = map.get("EMBEDDINGS_OLLAMA_MODEL");
|
|
2340
2340
|
const prov = map.get("EMBEDDINGS_PROVIDER");
|
|
2341
2341
|
const node = map.get("NODE_ENV");
|
|
2342
2342
|
if (llm !== void 0) cfg.llmModel = llm;
|
|
@@ -3029,7 +3029,7 @@ function buildComposeEnv(args) {
|
|
|
3029
3029
|
...secrets.promptServiceApiKey !== void 0 ? { PROMPT_SERVICE_API_KEY: secrets.promptServiceApiKey } : {},
|
|
3030
3030
|
...secrets.promptServiceApiKeys !== void 0 ? { PROMPT_SERVICE_API_KEYS: secrets.promptServiceApiKeys } : {},
|
|
3031
3031
|
...secrets.promptServiceAdminApiKeys !== void 0 ? { PROMPT_SERVICE_ADMIN_API_KEYS: secrets.promptServiceAdminApiKeys } : {}
|
|
3032
|
-
// Model config (LLM_MODEL /
|
|
3032
|
+
// Model config (LLM_MODEL / EMBEDDINGS_OLLAMA_MODEL / NODE_ENV) is intentionally
|
|
3033
3033
|
// NOT injected here — it lives in the node's `.env` (runEnvFilePhase),
|
|
3034
3034
|
// which docker compose auto-loads. Injecting it into the subprocess env
|
|
3035
3035
|
// would shadow `.env` (shell env > .env) and reintroduce the drift the
|
|
@@ -4014,7 +4014,12 @@ async function runVerify(input, deps = DEFAULT_DEPS2) {
|
|
|
4014
4014
|
await verifyCosignPhase(input, deps, push);
|
|
4015
4015
|
return { phases };
|
|
4016
4016
|
}
|
|
4017
|
+
function skipLocalOnly(name, detail, push) {
|
|
4018
|
+
push({ name, status: "skipped", detail });
|
|
4019
|
+
}
|
|
4020
|
+
var LOCAL_ONLY_NO_ENDPOINT = "local-only node \u2014 no public endpoint to probe";
|
|
4017
4021
|
async function verifyTlsPhase(input, deps, push) {
|
|
4022
|
+
if (input.localOnly) return skipLocalOnly("TLS handshake", LOCAL_ONLY_NO_ENDPOINT, push);
|
|
4018
4023
|
const tls2 = await deps.tls({ host: input.apiHost });
|
|
4019
4024
|
if (!tls2.ok) {
|
|
4020
4025
|
push({ name: "TLS handshake", status: "fail", detail: tls2.reason });
|
|
@@ -4032,6 +4037,7 @@ async function verifyTlsPhase(input, deps, push) {
|
|
|
4032
4037
|
push({ name: "TLS handshake", status: "ok", detail: line });
|
|
4033
4038
|
}
|
|
4034
4039
|
async function verifyHealthPhase(input, deps, push) {
|
|
4040
|
+
if (input.localOnly) return skipLocalOnly("GET /health", LOCAL_ONLY_NO_ENDPOINT, push);
|
|
4035
4041
|
const health = await deps.http({ url: `https://${input.apiHost}/health` });
|
|
4036
4042
|
if (health.ok) {
|
|
4037
4043
|
push({ name: "GET /health", status: "ok", detail: `HTTP ${health.status}` });
|
|
@@ -4040,6 +4046,7 @@ async function verifyHealthPhase(input, deps, push) {
|
|
|
4040
4046
|
}
|
|
4041
4047
|
}
|
|
4042
4048
|
async function verifyGraphqlPhase(input, deps, push) {
|
|
4049
|
+
if (input.localOnly) return skipLocalOnly("GraphQL { __typename }", LOCAL_ONLY_NO_ENDPOINT, push);
|
|
4043
4050
|
const gql = await deps.graphql({ url: `https://${input.apiHost}/api` });
|
|
4044
4051
|
if (gql.ok) {
|
|
4045
4052
|
push({ name: "GraphQL { __typename }", status: "ok", detail: `typename=${gql.typename}` });
|
|
@@ -4081,6 +4088,7 @@ async function verifyOllamaModelsPhase(input, deps, push) {
|
|
|
4081
4088
|
push({ name: "Ollama models", status: "ok", detail: `${required.join(", ")} present` });
|
|
4082
4089
|
}
|
|
4083
4090
|
async function verifyCloudflarePhase(input, deps, push) {
|
|
4091
|
+
if (input.localOnly) return skipLocalOnly("Cloudflare Tunnel", "local-only node \u2014 no tunnel", push);
|
|
4084
4092
|
const cfFields = [
|
|
4085
4093
|
["--cf-token", input.cf?.token],
|
|
4086
4094
|
["--cf-account-id", input.cf?.accountId],
|
|
@@ -4150,6 +4158,9 @@ function readTokenFile(path) {
|
|
|
4150
4158
|
if (!raw) throw new Error(`--cf-token-file ${path} was empty`);
|
|
4151
4159
|
return raw;
|
|
4152
4160
|
}
|
|
4161
|
+
function collectImage(value, previous) {
|
|
4162
|
+
return [...previous ?? [], value];
|
|
4163
|
+
}
|
|
4153
4164
|
var verifyCommand = new Command("verify").description(
|
|
4154
4165
|
"Off-LAN health probe of a live node \u2014 TLS, Apollo Federation reachability, GraphQL smoke. Run from anywhere with internet access."
|
|
4155
4166
|
).addOption(
|
|
@@ -4159,9 +4170,7 @@ var verifyCommand = new Command("verify").description(
|
|
|
4159
4170
|
).addOption(new Option("--cf-account-id <id>", "Cloudflare account ID (required with --cf-token)")).addOption(
|
|
4160
4171
|
new Option("--tunnel-id <id>", "Cloudflare Tunnel ID to query (required with --cf-token)")
|
|
4161
4172
|
).addOption(
|
|
4162
|
-
new Option("--image <ref>", "Repeatable. Image to cosign-verify (e.g. ghcr.io/opuspopuli/api:tag)").default(
|
|
4163
|
-
[]
|
|
4164
|
-
)
|
|
4173
|
+
new Option("--image <ref>", "Repeatable. Image to cosign-verify (e.g. ghcr.io/opuspopuli/api:tag)").default([]).argParser(collectImage)
|
|
4165
4174
|
).addOption(
|
|
4166
4175
|
new Option("--cert-warn-days <n>", "Warn if cert expires within N days").default("14")
|
|
4167
4176
|
).addOption(
|
|
@@ -4175,14 +4184,20 @@ var verifyCommand = new Command("verify").description(
|
|
|
4175
4184
|
new Option("--embeddings-provider <provider>", "Embeddings provider the node runs; the embedding model is only asserted for `ollama`.").choices(["xenova", "ollama"])
|
|
4176
4185
|
).addOption(
|
|
4177
4186
|
new Option("--repo-dir <path>", "Node repo dir whose .env supplies the model config when --llm-model is omitted (default: cwd).")
|
|
4187
|
+
).addOption(
|
|
4188
|
+
new Option(
|
|
4189
|
+
"--local-only",
|
|
4190
|
+
"Node has no public domain/tunnel \u2014 skip the TLS/health/GraphQL/tunnel probes (no domain prompt) and run only the node-local checks (Ollama model presence, cosign)."
|
|
4191
|
+
).default(false)
|
|
4178
4192
|
).addOption(
|
|
4179
4193
|
new Option("--show-skipped", "Include skipped phases in the summary").default(false)
|
|
4180
4194
|
).action(async (opts) => {
|
|
4181
4195
|
p3.intro(pc2.bgCyan(pc2.black(" create-op-node verify ")));
|
|
4182
|
-
const
|
|
4196
|
+
const localOnly = opts.localOnly ?? false;
|
|
4197
|
+
const domain = localOnly ? void 0 : await resolveDomain(opts);
|
|
4183
4198
|
const certWarnDays = resolveCertWarnDays(opts);
|
|
4184
4199
|
const cfToken = resolveCfToken(opts);
|
|
4185
|
-
const apiHost = opts.apiHost ?? `api.${domain}`;
|
|
4200
|
+
const apiHost = localOnly ? "" : opts.apiHost ?? `api.${domain}`;
|
|
4186
4201
|
const images = opts.image ?? [];
|
|
4187
4202
|
const ollama = await resolveOllamaVerifyInput(opts);
|
|
4188
4203
|
const totalPhases = 5 + (images.length === 0 ? 1 : images.length);
|
|
@@ -4193,6 +4208,7 @@ var verifyCommand = new Command("verify").description(
|
|
|
4193
4208
|
opts,
|
|
4194
4209
|
images,
|
|
4195
4210
|
totalPhases,
|
|
4211
|
+
localOnly,
|
|
4196
4212
|
...ollama ? { ollama } : {}
|
|
4197
4213
|
});
|
|
4198
4214
|
renderVerifySummary(report, { opts, totalPhases });
|
|
@@ -4291,7 +4307,7 @@ async function resolveOllamaVerifyInput(opts) {
|
|
|
4291
4307
|
return mergeOllamaModelConfig(opts, envCfg);
|
|
4292
4308
|
}
|
|
4293
4309
|
async function runVerifyWithSpinner(args) {
|
|
4294
|
-
const { apiHost, certWarnDays, cfToken, opts, images, totalPhases, ollama } = args;
|
|
4310
|
+
const { apiHost, certWarnDays, cfToken, opts, images, totalPhases, localOnly, ollama } = args;
|
|
4295
4311
|
let phaseIndex = 0;
|
|
4296
4312
|
let activeSpin = null;
|
|
4297
4313
|
const renderPhase = (ph) => {
|
|
@@ -4313,6 +4329,7 @@ async function runVerifyWithSpinner(args) {
|
|
|
4313
4329
|
...opts.tunnelId ? { tunnelId: opts.tunnelId } : {}
|
|
4314
4330
|
},
|
|
4315
4331
|
images,
|
|
4332
|
+
localOnly,
|
|
4316
4333
|
...ollama ? { ollama } : {}
|
|
4317
4334
|
},
|
|
4318
4335
|
deps
|
|
@@ -5428,7 +5445,7 @@ function withDefaultSubcommand(argv, known) {
|
|
|
5428
5445
|
}
|
|
5429
5446
|
|
|
5430
5447
|
// src/cli.ts
|
|
5431
|
-
var VERSION = "0.
|
|
5448
|
+
var VERSION = "0.15.1";
|
|
5432
5449
|
var program = new Command();
|
|
5433
5450
|
program.name("create-op-node").description(
|
|
5434
5451
|
"Interactive bootstrap for an Opus Populi federation node.\nCloudflare infrastructure \u2192 Mac Studio \u2192 live public API."
|