create-op-node 0.7.0 → 0.8.0

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
@@ -202,6 +202,11 @@ npx create-op-node reset --region us-ca --dry-run
202
202
 
203
203
  # Nuke from orbit: containers + volumes + LaunchAgent + ghcr credentials.
204
204
  npx create-op-node reset --region us-ca --wipe-data
205
+
206
+ # Even more: ALSO drop all docker images (forces re-pull on next bootstrap).
207
+ # Use this for the "wipe everything and start over" iteration loop while
208
+ # debugging a fresh bootstrap. Implies --wipe-data.
209
+ npx create-op-node reset --region us-ca --wipe-images
205
210
  ```
206
211
 
207
212
  ## Verifying a live node
package/dist/cli.js CHANGED
@@ -1151,6 +1151,13 @@ async function enableAutoRestartOnPowerFailure() {
1151
1151
  async function disableDiskSleep() {
1152
1152
  return runSudoPmset(["disksleep", "0"]);
1153
1153
  }
1154
+ async function detectUnifiedMemoryGB() {
1155
+ const res = await safeExeca("sysctl", ["-n", "hw.memsize"]);
1156
+ if (res === null || res.exitCode !== 0) return null;
1157
+ const bytes = Number.parseInt(res.stdout.trim(), 10);
1158
+ if (!Number.isFinite(bytes) || bytes <= 0) return null;
1159
+ return Math.round(bytes / 2 ** 30);
1160
+ }
1154
1161
  async function runSudoPmset(args) {
1155
1162
  const res = await safeExeca("sudo", ["pmset", "-a", ...args]);
1156
1163
  if (res === null) {
@@ -1372,8 +1379,14 @@ async function composeDown(opts) {
1372
1379
  const flags = ["down"];
1373
1380
  if (opts.wipeVolumes) flags.push("-v");
1374
1381
  if (opts.removeOrphans) flags.push("--remove-orphans");
1382
+ if (opts.removeImages) flags.push("--rmi", opts.removeImages);
1383
+ const label = [
1384
+ "compose down",
1385
+ opts.wipeVolumes ? "-v" : "",
1386
+ opts.removeImages ? `--rmi ${opts.removeImages}` : ""
1387
+ ].filter(Boolean).join(" ");
1375
1388
  const res = await safeExeca("docker", composeArgs(opts, flags));
1376
- return result(res, opts.wipeVolumes ? "compose down -v" : "compose down");
1389
+ return result(res, label);
1377
1390
  }
1378
1391
  async function dockerLogout(registry = GHCR_REGISTRY) {
1379
1392
  const res = await safeExeca("docker", ["logout", registry]);
@@ -2023,12 +2036,22 @@ var LLM_MODEL_CHOICES = [
2023
2036
  }
2024
2037
  ];
2025
2038
  var OTHER_SENTINEL = "__OTHER__";
2039
+ function recommendLlmModel(ramGB) {
2040
+ if (ramGB === null) return null;
2041
+ if (ramGB >= 96) return "qwen2.5:72b";
2042
+ if (ramGB >= 48) return "qwen2.5:32b";
2043
+ return "qwen3.5:9b";
2044
+ }
2026
2045
  async function selectLlmModel(opts) {
2027
2046
  if (opts.yes) return DEFAULT_LLM_MODEL;
2047
+ const ramGB = await detectUnifiedMemoryGB();
2048
+ const recommended = recommendLlmModel(ramGB) ?? "qwen2.5:72b";
2049
+ const ramNote = ramGB !== null ? `Detected ${ramGB} GB unified memory \u2014 pre-selecting ${recommended}.` : `Couldn't detect Studio memory \u2014 defaulting to ${recommended} (override if your config differs).`;
2050
+ p3.note(ramNote, "Hardware");
2028
2051
  const choice = unwrap(
2029
2052
  await p3.select({
2030
2053
  message: "Choose the LLM model to pull + run",
2031
- initialValue: "qwen2.5:72b",
2054
+ initialValue: recommended,
2032
2055
  options: [
2033
2056
  ...LLM_MODEL_CHOICES.map((c) => ({ value: c.value, label: c.label, hint: c.hint })),
2034
2057
  {
@@ -2220,7 +2243,7 @@ async function runReset(input, deps = DEFAULT_DEPS) {
2220
2243
  push({
2221
2244
  name: RESET_PHASES.STOP_STACK,
2222
2245
  status: "dry-run",
2223
- detail: `would run: docker compose -f ${input.stack.composeFiles.join(" -f ")} down${input.stack.wipeVolumes ? " -v" : ""}${input.stack.removeOrphans ? " --remove-orphans" : ""}`
2246
+ detail: `would run: docker compose -f ${input.stack.composeFiles.join(" -f ")} down${input.stack.wipeVolumes ? " -v" : ""}${input.stack.removeOrphans ? " --remove-orphans" : ""}${input.stack.wipeImages ? " --rmi all" : ""}`
2224
2247
  });
2225
2248
  } else {
2226
2249
  const result2 = await deps.composeDown({
@@ -2228,13 +2251,18 @@ async function runReset(input, deps = DEFAULT_DEPS) {
2228
2251
  cwd: input.stack.repoPath,
2229
2252
  ...input.stack.envFile ? { envFile: input.stack.envFile } : {},
2230
2253
  wipeVolumes: input.stack.wipeVolumes,
2231
- removeOrphans: input.stack.removeOrphans
2254
+ removeOrphans: input.stack.removeOrphans,
2255
+ ...input.stack.wipeImages ? { removeImages: "all" } : {}
2232
2256
  });
2233
2257
  if (result2.ok) {
2258
+ const bits = [
2259
+ input.stack.wipeVolumes ? "volumes destroyed" : "containers stopped, volumes preserved",
2260
+ input.stack.wipeImages ? "images removed (next bootstrap will re-pull)" : null
2261
+ ].filter(Boolean);
2234
2262
  push({
2235
2263
  name: RESET_PHASES.STOP_STACK,
2236
2264
  status: "ok",
2237
- detail: input.stack.wipeVolumes ? "volumes destroyed" : "containers stopped, volumes preserved"
2265
+ detail: bits.join("; ")
2238
2266
  });
2239
2267
  } else {
2240
2268
  push({ name: RESET_PHASES.STOP_STACK, status: "fail", detail: result2.reason ?? "unknown failure" });
@@ -2321,6 +2349,11 @@ var resetCommand = new Command("reset").description(
2321
2349
  "--wipe-data",
2322
2350
  "DESTROYS docker volumes (adds -v to `compose down`). Requires retyping the region label as confirmation. Default off \u2014 volumes preserved."
2323
2351
  ).default(false)
2352
+ ).addOption(
2353
+ new Option(
2354
+ "--wipe-images",
2355
+ 'ALSO remove all docker images referenced by the compose file (adds --rmi all). Forces a fresh pull on next bootstrap. Implies --wipe-data semantics for the iteration loop ("wipe everything and start over"). Default off.'
2356
+ ).default(false)
2324
2357
  ).addOption(
2325
2358
  new Option(
2326
2359
  "--no-remove-orphans",
@@ -2333,7 +2366,8 @@ var resetCommand = new Command("reset").description(
2333
2366
  ).default(false)
2334
2367
  ).addOption(new Option("--skip-docker-logout", "Don't run `docker logout`").default(false)).addOption(new Option("--registry <registry>", "Registry to log out of").default(GHCR_REGISTRY)).addOption(new Option("--dry-run", "Show what would happen without acting").default(false)).addOption(new Option("-y, --yes", "Skip non-destructive confirmations (wipe still confirms)").default(false)).action(async (opts) => {
2335
2368
  p3.intro(pc2.bgCyan(pc2.black(" create-op-node reset ")));
2336
- const wipeData = opts.wipeData ?? false;
2369
+ const wipeImages = opts.wipeImages ?? false;
2370
+ const wipeData = (opts.wipeData ?? false) || wipeImages;
2337
2371
  const removeOrphans = opts.removeOrphans ?? true;
2338
2372
  const region = opts.region ? opts.region : unwrap(
2339
2373
  await p3.text({
@@ -2399,6 +2433,7 @@ var resetCommand = new Command("reset").description(
2399
2433
  `pgsodium key file: ${keyFileExists ? pc2.cyan(launchAgentPaths.keyFile) : pc2.dim("not present")}`,
2400
2434
  `Registry to log out: ${pc2.cyan(opts.registry ?? GHCR_REGISTRY)}`,
2401
2435
  `Volume policy: ${wipeData ? pc2.red("WIPE") : pc2.green("preserve (default)")}`,
2436
+ `Image policy: ${wipeImages ? pc2.red("WIPE (re-pull on next bootstrap)") : pc2.green("keep (default)")}`,
2402
2437
  `Dry run: ${opts.dryRun ? pc2.yellow("yes") : pc2.dim("no")}`
2403
2438
  ].join("\n"),
2404
2439
  "Snapshot"
@@ -2426,6 +2461,7 @@ var resetCommand = new Command("reset").description(
2426
2461
  repoPath,
2427
2462
  composeFiles: resolveComposeFiles(repoPath, opts.composeFile),
2428
2463
  wipeVolumes: wipeData,
2464
+ wipeImages,
2429
2465
  removeOrphans,
2430
2466
  ...opts.envFile ? { envFile: opts.envFile } : {}
2431
2467
  } : void 0;
@@ -3900,7 +3936,7 @@ async function looksLikeRegionsRepo(dir) {
3900
3936
  }
3901
3937
 
3902
3938
  // src/cli.ts
3903
- var VERSION = "0.7.0";
3939
+ var VERSION = "0.8.0";
3904
3940
  var program = new Command();
3905
3941
  program.name("create-op-node").description(
3906
3942
  "Interactive bootstrap for an Opus Populi federation node.\nCloudflare infrastructure \u2192 Mac Studio \u2192 live public API."