@factiii/runner 0.12.1 → 0.12.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.
Files changed (2) hide show
  1. package/dist/cli.js +257 -113
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -3984,7 +3984,7 @@ var require_has_flag = __commonJS({
3984
3984
  var require_supports_color = __commonJS({
3985
3985
  "../../node_modules/debug/node_modules/supports-color/index.js"(exports2, module2) {
3986
3986
  "use strict";
3987
- var os7 = require("os");
3987
+ var os8 = require("os");
3988
3988
  var hasFlag = require_has_flag();
3989
3989
  var env = process.env;
3990
3990
  var forceColor;
@@ -4022,7 +4022,7 @@ var require_supports_color = __commonJS({
4022
4022
  }
4023
4023
  const min = forceColor ? 1 : 0;
4024
4024
  if (process.platform === "win32") {
4025
- const osRelease = os7.release().split(".");
4025
+ const osRelease = os8.release().split(".");
4026
4026
  if (Number(process.versions.node.split(".")[0]) >= 8 && Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
4027
4027
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
4028
4028
  }
@@ -14161,6 +14161,18 @@ var import_promises5 = __toESM(require("fs/promises"));
14161
14161
  var import_os2 = __toESM(require("os"));
14162
14162
  var import_path7 = __toESM(require("path"));
14163
14163
 
14164
+ // ../../shared/all/domains/products.ts
14165
+ var TRONS_PER_USD = 1e6;
14166
+ var TRONS_PER_CENT = TRONS_PER_USD / 100;
14167
+ var FOUNDERS_REFUND_WINDOW_DAYS = 60;
14168
+ var FOUNDERS_REFUND_POLICY = `Refundable within ${FOUNDERS_REFUND_WINDOW_DAYS} days of purchase \u2014 after that, all sales are final.`;
14169
+
14170
+ // ../../shared/all/domains/autoTopUp.ts
14171
+ var DAY_MS = 24 * 60 * 60 * 1e3;
14172
+ var MONTH_MS = 30 * DAY_MS;
14173
+ var CLAIM_STALE_MS = 5 * 60 * 1e3;
14174
+ var MIN_THRESHOLD_TRONS = TRONS_PER_CENT;
14175
+
14164
14176
  // ../../shared/all/domains/posts.ts
14165
14177
  var postDataSort = /* @__PURE__ */ ((postDataSort2) => {
14166
14178
  postDataSort2["DAILY"] = "DAILY";
@@ -14363,7 +14375,11 @@ var checkPurchaseSchema = external_exports.object({
14363
14375
  productId: external_exports.string(),
14364
14376
  type: external_exports.enum(["STRIPE", "GOOGLE", "APPLE"]),
14365
14377
  receivedPrice: external_exports.number().int().min(0, "Received price must be a positive integer in cents"),
14366
- currency: external_exports.string().length(3).default("USD")
14378
+ currency: external_exports.string().length(3).default("USD"),
14379
+ // Explicit consent to keep this card for auto top-up. Card-network rules
14380
+ // require the buyer to agree before a card may be charged while they are not
14381
+ // present, so this must come from a checkbox they ticked — never a default.
14382
+ saveCardForAutoTopUp: external_exports.boolean().default(false)
14367
14383
  });
14368
14384
  var creditPurchaseSchema = external_exports.object({
14369
14385
  orderId: external_exports.string(),
@@ -14666,6 +14682,19 @@ var skillFrontmatterSchema = external_exports.object({
14666
14682
  }).strict();
14667
14683
  var skillAgentSchema = external_exports.enum(["claude", "codex"]);
14668
14684
  var skillScopeSchema = external_exports.enum(["global", "repo"]);
14685
+ var autoTopUpSchema = external_exports.object({
14686
+ enabled: external_exports.boolean(),
14687
+ productId: external_exports.number().int().positive(),
14688
+ thresholdTrons: external_exports.number().int().min(MIN_THRESHOLD_TRONS),
14689
+ dailyCapCents: external_exports.number().int().min(0),
14690
+ monthlyCapCents: external_exports.number().int().min(0),
14691
+ // Alerts. `alertBalanceTrons` is null when the low-balance warning is off;
14692
+ // it works independently of `enabled`, since with auto top-up off it is the
14693
+ // only safety net there is.
14694
+ alertOnTopUp: external_exports.boolean(),
14695
+ alertBalanceTrons: external_exports.number().int().min(MIN_THRESHOLD_TRONS).nullable(),
14696
+ alertNearCap: external_exports.boolean()
14697
+ });
14669
14698
 
14670
14699
  // ../../shared/all/helpers/board-agent-core/host-deploy.ts
14671
14700
  var import_child_process3 = require("child_process");
@@ -22329,11 +22358,58 @@ ${c.content.trim() || "(no description)"}`
22329
22358
  // ../../shared/all/helpers/board-agent-core/index-service.ts
22330
22359
  var import_child_process5 = require("child_process");
22331
22360
  var import_crypto7 = require("crypto");
22332
- var import_fs5 = require("fs");
22361
+ var import_fs6 = require("fs");
22333
22362
  var import_promises11 = __toESM(require("fs/promises"));
22334
22363
  var import_net3 = require("net");
22335
- var import_path16 = require("path");
22364
+ var import_path17 = require("path");
22336
22365
  var import_util7 = require("util");
22366
+
22367
+ // ../../shared/all/helpers/board-agent-core/node-version.ts
22368
+ var import_fs5 = require("fs");
22369
+ var import_os4 = __toESM(require("os"));
22370
+ var import_path16 = require("path");
22371
+ var MIN_INDEX_NODE_MAJOR = 20;
22372
+ var MAX_INDEX_NODE_MAJOR = 24;
22373
+ var NODE_OVERRIDE_ENV = "FACTIII_INDEX_NODE";
22374
+ function nodeMajor(version) {
22375
+ return Number(version.replace(/^v/, "").split(".")[0]);
22376
+ }
22377
+ function indexNodeSupported(version = process.versions.node) {
22378
+ const major = nodeMajor(version);
22379
+ return !Number.isNaN(major) && major >= MIN_INDEX_NODE_MAJOR && major <= MAX_INDEX_NODE_MAJOR;
22380
+ }
22381
+ function candidateNodes() {
22382
+ const paths = [];
22383
+ const home = import_os4.default.homedir();
22384
+ const nvmRoot = (0, import_path16.join)(home, ".nvm", "versions", "node");
22385
+ try {
22386
+ const versions = (0, import_fs5.readdirSync)(nvmRoot).filter((v) => indexNodeSupported(v)).sort((a, b) => nodeMajor(b) - nodeMajor(a));
22387
+ for (const v of versions) paths.push((0, import_path16.join)(nvmRoot, v, "bin", "node"));
22388
+ } catch {
22389
+ }
22390
+ for (const prefix of ["/opt/homebrew/opt", "/usr/local/opt"]) {
22391
+ for (let major = MAX_INDEX_NODE_MAJOR; major >= MIN_INDEX_NODE_MAJOR; major -= 1) {
22392
+ paths.push((0, import_path16.join)(prefix, `node@${major}`, "bin", "node"));
22393
+ }
22394
+ }
22395
+ return paths;
22396
+ }
22397
+ function findIndexNode() {
22398
+ const override = process.env[NODE_OVERRIDE_ENV];
22399
+ if (override && (0, import_fs5.existsSync)(override)) return override;
22400
+ if (indexNodeSupported()) return process.execPath;
22401
+ return candidateNodes().find((p) => (0, import_fs5.existsSync)(p)) ?? null;
22402
+ }
22403
+ function unsupportedNodeMessage() {
22404
+ return `The OneDrive index needs Node ${MIN_INDEX_NODE_MAJOR}-${MAX_INDEX_NODE_MAJOR} (pinned 24.11.1); this host runs Node ${process.versions.node} and has no supported Node installed. It compiles better-sqlite3 from source, and that build fails on Node ${MAX_INDEX_NODE_MAJOR + 1}+.
22405
+
22406
+ Install one: nvm install 24.11.1
22407
+ or point the runner at an existing one: export ${NODE_OVERRIDE_ENV}=/path/to/node
22408
+
22409
+ Everything else on this runner works meanwhile \u2014 only OneDrive search is off.`;
22410
+ }
22411
+
22412
+ // ../../shared/all/helpers/board-agent-core/index-service.ts
22337
22413
  var execFileAsync4 = (0, import_util7.promisify)(import_child_process5.execFile);
22338
22414
  var IndexService = class {
22339
22415
  constructor(opts) {
@@ -22386,27 +22462,25 @@ var IndexService = class {
22386
22462
  await this.waitReady(this.port);
22387
22463
  return;
22388
22464
  }
22389
- await this.installDeps();
22465
+ const nodeBin = findIndexNode();
22466
+ if (!nodeBin) throw new Error(unsupportedNodeMessage());
22467
+ await this.installDeps(nodeBin);
22390
22468
  const port = await this.stablePort();
22391
22469
  const log = await this.openLog();
22392
- this.proc = (0, import_child_process5.spawn)(
22393
- process.execPath,
22394
- [(0, import_path16.join)(this.opts.indexDir, "index.js")],
22395
- {
22396
- cwd: this.opts.indexDir,
22397
- env: {
22398
- ...process.env,
22399
- NODE_ENV: "production",
22400
- PORT: String(port),
22401
- CONTROL_TOKEN: this.opts.controlToken,
22402
- EMBED_URL: this.opts.embedUrl(),
22403
- INDEX_DATA_DIR: this.opts.dataDir
22404
- },
22405
- stdio: log ? ["ignore", log, log] : "ignore",
22406
- // Own process group so stop() reaps it; nothing else reaps orphans here.
22407
- detached: true
22408
- }
22409
- );
22470
+ this.proc = (0, import_child_process5.spawn)(nodeBin, [(0, import_path17.join)(this.opts.indexDir, "index.js")], {
22471
+ cwd: this.opts.indexDir,
22472
+ env: {
22473
+ ...process.env,
22474
+ NODE_ENV: "production",
22475
+ PORT: String(port),
22476
+ CONTROL_TOKEN: this.opts.controlToken,
22477
+ EMBED_URL: this.opts.embedUrl(),
22478
+ INDEX_DATA_DIR: this.opts.dataDir
22479
+ },
22480
+ stdio: log ? ["ignore", log, log] : "ignore",
22481
+ // Own process group so stop() reaps it; nothing else reaps orphans here.
22482
+ detached: true
22483
+ });
22410
22484
  this.proc.on("exit", () => {
22411
22485
  this.proc = null;
22412
22486
  this.ready = null;
@@ -22420,31 +22494,58 @@ var IndexService = class {
22420
22494
  async openLog() {
22421
22495
  try {
22422
22496
  await import_promises11.default.mkdir(this.opts.dataDir, { recursive: true });
22423
- return (0, import_fs5.openSync)((0, import_path16.join)(this.opts.dataDir, "index.log"), "a");
22497
+ return (0, import_fs6.openSync)((0, import_path17.join)(this.opts.dataDir, "index.log"), "a");
22424
22498
  } catch {
22425
22499
  return null;
22426
22500
  }
22427
22501
  }
22428
- /** Install the index's native deps against THIS node on first start, so a
22429
- * platform needing node-gyp fails here rather than at query time. */
22430
- async installDeps() {
22502
+ /** Install the index's native deps against the node that will RUN it, so a
22503
+ * platform needing node-gyp fails here rather than at query time.
22504
+ *
22505
+ * The deps are compiled per ABI, so they are only reusable under the node
22506
+ * that built them. The stamp records which ABI is on disk and forces a
22507
+ * rebuild when the interpreter changes — otherwise a host that switches
22508
+ * node versions loads a binding built for the old one and dies with
22509
+ * "compiled against a different Node.js version". */
22510
+ async installDeps(nodeBin) {
22431
22511
  const dir = this.opts.indexDir;
22432
- if ((0, import_fs5.existsSync)((0, import_path16.join)(dir, "node_modules"))) return;
22512
+ const { stdout } = await execFileAsync4(nodeBin, [
22513
+ "-p",
22514
+ "process.versions.modules"
22515
+ ]);
22516
+ const abi = stdout.trim();
22517
+ const stamp = (0, import_path17.join)(dir, ".node-abi");
22518
+ if ((0, import_fs6.existsSync)((0, import_path17.join)(dir, "node_modules"))) {
22519
+ const built = await import_promises11.default.readFile(stamp, "utf-8").catch(() => "");
22520
+ if (built.trim() === abi) return;
22521
+ await import_promises11.default.rm((0, import_path17.join)(dir, "node_modules"), { recursive: true, force: true });
22522
+ }
22433
22523
  await import_promises11.default.copyFile(
22434
- (0, import_path16.join)(dir, "image-package.json"),
22435
- (0, import_path16.join)(dir, "package.json")
22524
+ (0, import_path17.join)(dir, "image-package.json"),
22525
+ (0, import_path17.join)(dir, "package.json")
22436
22526
  );
22437
22527
  await execFileAsync4(
22438
22528
  "npm",
22439
22529
  ["install", "--omit=dev", "--no-audit", "--no-fund"],
22440
- { cwd: dir }
22530
+ {
22531
+ cwd: dir,
22532
+ // npm resolves node-gyp's target from the node running it, so the
22533
+ // chosen interpreter has to lead PATH or the build targets the wrong ABI.
22534
+ env: {
22535
+ ...process.env,
22536
+ PATH: `${(0, import_path17.dirname)(nodeBin)}:${process.env.PATH ?? ""}`
22537
+ }
22538
+ }
22441
22539
  );
22540
+ await import_promises11.default.writeFile(stamp, `${abi}
22541
+ `, "utf-8").catch(() => {
22542
+ });
22442
22543
  }
22443
22544
  /** The port to listen on, kept stable across restarts. The URL goes into the
22444
22545
  * CLIs' persistent MCP config, so a fresh port each start would leave every
22445
22546
  * registration pointing at a dead one. */
22446
22547
  async stablePort() {
22447
- const file = (0, import_path16.join)(this.opts.dataDir, ".port");
22548
+ const file = (0, import_path17.join)(this.opts.dataDir, ".port");
22448
22549
  const saved = Number(await import_promises11.default.readFile(file, "utf-8").catch(() => ""));
22449
22550
  if (Number.isInteger(saved) && saved > 0 && await isFree(saved)) {
22450
22551
  return saved;
@@ -22619,25 +22720,25 @@ async function pairWithBrowser(serverUrl) {
22619
22720
  }
22620
22721
 
22621
22722
  // src/config.ts
22622
- var import_fs9 = __toESM(require("fs"));
22623
- var import_os6 = __toESM(require("os"));
22624
- var import_path21 = __toESM(require("path"));
22723
+ var import_fs10 = __toESM(require("fs"));
22724
+ var import_os7 = __toESM(require("os"));
22725
+ var import_path22 = __toESM(require("path"));
22625
22726
 
22626
22727
  // src/secureFile.ts
22627
22728
  var import_crypto10 = __toESM(require("crypto"));
22628
- var import_fs8 = __toESM(require("fs"));
22629
- var import_path20 = __toESM(require("path"));
22729
+ var import_fs9 = __toESM(require("fs"));
22730
+ var import_path21 = __toESM(require("path"));
22630
22731
 
22631
22732
  // src/keychain.ts
22632
22733
  var import_child_process7 = require("child_process");
22633
22734
  var import_crypto8 = __toESM(require("crypto"));
22634
- var import_fs6 = __toESM(require("fs"));
22635
- var import_os4 = __toESM(require("os"));
22636
- var import_path17 = __toESM(require("path"));
22735
+ var import_fs7 = __toESM(require("fs"));
22736
+ var import_os5 = __toESM(require("os"));
22737
+ var import_path18 = __toESM(require("path"));
22637
22738
  var SERVICE = "factiii-runner";
22638
22739
  var ACCOUNT = "config-encryption-key";
22639
- var DPAPI_KEY_FILE = import_path17.default.join(
22640
- import_os4.default.homedir(),
22740
+ var DPAPI_KEY_FILE = import_path18.default.join(
22741
+ import_os5.default.homedir(),
22641
22742
  ".factiii-runner",
22642
22743
  "config-key.dpapi"
22643
22744
  );
@@ -22717,7 +22818,7 @@ function linuxWrite(key) {
22717
22818
  function winRead() {
22718
22819
  let wrapped;
22719
22820
  try {
22720
- wrapped = import_fs6.default.readFileSync(DPAPI_KEY_FILE, "utf-8").trim();
22821
+ wrapped = import_fs7.default.readFileSync(DPAPI_KEY_FILE, "utf-8").trim();
22721
22822
  } catch {
22722
22823
  return { state: "missing" };
22723
22824
  }
@@ -22742,8 +22843,8 @@ function winWrite(key) {
22742
22843
  "-Command",
22743
22844
  `ConvertTo-SecureString -String '${key.toString("base64")}' -AsPlainText -Force | ConvertFrom-SecureString`
22744
22845
  ]);
22745
- import_fs6.default.mkdirSync(import_path17.default.dirname(DPAPI_KEY_FILE), { recursive: true });
22746
- import_fs6.default.writeFileSync(DPAPI_KEY_FILE, `${out.trim()}
22846
+ import_fs7.default.mkdirSync(import_path18.default.dirname(DPAPI_KEY_FILE), { recursive: true });
22847
+ import_fs7.default.writeFileSync(DPAPI_KEY_FILE, `${out.trim()}
22747
22848
  `, { mode: 384 });
22748
22849
  }
22749
22850
  function keychainBackend() {
@@ -22905,22 +23006,22 @@ function keychainStatus() {
22905
23006
 
22906
23007
  // src/secureStore.ts
22907
23008
  var import_crypto9 = __toESM(require("crypto"));
22908
- var import_fs7 = __toESM(require("fs"));
22909
- var import_path19 = __toESM(require("path"));
23009
+ var import_fs8 = __toESM(require("fs"));
23010
+ var import_path20 = __toESM(require("path"));
22910
23011
 
22911
23012
  // src/paths.ts
22912
- var import_os5 = __toESM(require("os"));
22913
- var import_path18 = __toESM(require("path"));
22914
- var CONFIG_DIR = import_path18.default.join(import_os5.default.homedir(), ".factiii-runner");
23013
+ var import_os6 = __toESM(require("os"));
23014
+ var import_path19 = __toESM(require("path"));
23015
+ var CONFIG_DIR = import_path19.default.join(import_os6.default.homedir(), ".factiii-runner");
22915
23016
  function safeSlug(spaceSlug) {
22916
23017
  return spaceSlug.replace(/[^a-zA-Z0-9_.-]/g, "_");
22917
23018
  }
22918
23019
  function spaceDirPath(spaceSlug) {
22919
- return import_path18.default.join(CONFIG_DIR, safeSlug(spaceSlug));
23020
+ return import_path19.default.join(CONFIG_DIR, safeSlug(spaceSlug));
22920
23021
  }
22921
23022
 
22922
23023
  // src/secureStore.ts
22923
- var VAULT_KEY_FILE = import_path19.default.join(CONFIG_DIR, "vault-key.json");
23024
+ var VAULT_KEY_FILE = import_path20.default.join(CONFIG_DIR, "vault-key.json");
22924
23025
  var SCRYPT_N2 = 1 << 15;
22925
23026
  var MIN_PASSWORD_LENGTH = 8;
22926
23027
  var SecureStoreError = class extends Error {
@@ -22993,7 +23094,7 @@ function unseal(box, key) {
22993
23094
  function readVaultFile() {
22994
23095
  try {
22995
23096
  const parsed = JSON.parse(
22996
- import_fs7.default.readFileSync(VAULT_KEY_FILE, "utf-8")
23097
+ import_fs8.default.readFileSync(VAULT_KEY_FILE, "utf-8")
22997
23098
  );
22998
23099
  return parsed.v === 1 ? parsed : null;
22999
23100
  } catch {
@@ -23001,11 +23102,11 @@ function readVaultFile() {
23001
23102
  }
23002
23103
  }
23003
23104
  function writeVaultFile(file) {
23004
- import_fs7.default.mkdirSync(CONFIG_DIR, { recursive: true });
23105
+ import_fs8.default.mkdirSync(CONFIG_DIR, { recursive: true });
23005
23106
  const tmp = `${VAULT_KEY_FILE}.${process.pid}.tmp`;
23006
- import_fs7.default.writeFileSync(tmp, `${JSON.stringify(file, null, 2)}
23107
+ import_fs8.default.writeFileSync(tmp, `${JSON.stringify(file, null, 2)}
23007
23108
  `, { mode: 384 });
23008
- import_fs7.default.renameSync(tmp, VAULT_KEY_FILE);
23109
+ import_fs8.default.renameSync(tmp, VAULT_KEY_FILE);
23009
23110
  }
23010
23111
  var unlockedDek = null;
23011
23112
  function createVault(machineKey) {
@@ -23242,15 +23343,15 @@ function parseEnvelope(raw) {
23242
23343
  }
23243
23344
  }
23244
23345
  function atomicWrite(filePath, body) {
23245
- import_fs8.default.mkdirSync(import_path20.default.dirname(filePath), { recursive: true });
23346
+ import_fs9.default.mkdirSync(import_path21.default.dirname(filePath), { recursive: true });
23246
23347
  const tmp = `${filePath}.${process.pid}.tmp`;
23247
- import_fs8.default.writeFileSync(tmp, body, { mode: 384 });
23248
- import_fs8.default.renameSync(tmp, filePath);
23348
+ import_fs9.default.writeFileSync(tmp, body, { mode: 384 });
23349
+ import_fs9.default.renameSync(tmp, filePath);
23249
23350
  }
23250
23351
  function readSecureBuffer(filePath) {
23251
23352
  let raw;
23252
23353
  try {
23253
- raw = import_fs8.default.readFileSync(filePath, "utf-8");
23354
+ raw = import_fs9.default.readFileSync(filePath, "utf-8");
23254
23355
  } catch {
23255
23356
  return null;
23256
23357
  }
@@ -23307,7 +23408,7 @@ function writeSecureJson(filePath, value2) {
23307
23408
  function readBootJson(filePath) {
23308
23409
  let raw;
23309
23410
  try {
23310
- raw = import_fs8.default.readFileSync(filePath, "utf-8");
23411
+ raw = import_fs9.default.readFileSync(filePath, "utf-8");
23311
23412
  } catch {
23312
23413
  return null;
23313
23414
  }
@@ -23357,8 +23458,8 @@ function writeBootJson(filePath, value2) {
23357
23458
  }
23358
23459
 
23359
23460
  // src/config.ts
23360
- var CONFIG_DIR2 = import_path21.default.join(import_os6.default.homedir(), ".factiii-runner");
23361
- var CONFIG_FILE = import_path21.default.join(CONFIG_DIR2, "config.json");
23461
+ var CONFIG_DIR2 = import_path22.default.join(import_os7.default.homedir(), ".factiii-runner");
23462
+ var CONFIG_FILE = import_path22.default.join(CONFIG_DIR2, "config.json");
23362
23463
  function readRunnerConfig() {
23363
23464
  return readBootJson(CONFIG_FILE);
23364
23465
  }
@@ -23366,7 +23467,7 @@ function writeRunnerConfig(config) {
23366
23467
  writeBootJson(CONFIG_FILE, config);
23367
23468
  }
23368
23469
  function configExists() {
23369
- return import_fs9.default.existsSync(CONFIG_FILE);
23470
+ return import_fs10.default.existsSync(CONFIG_FILE);
23370
23471
  }
23371
23472
 
23372
23473
  // src/promptPassword.ts
@@ -23459,7 +23560,54 @@ function currentVersion() {
23459
23560
  return "0.0.0";
23460
23561
  }
23461
23562
  }
23563
+ function hostSatisfiesTarget() {
23564
+ let engines = "";
23565
+ try {
23566
+ const res = (0, import_node_child_process.spawnSync)(
23567
+ "npm",
23568
+ ["view", `${PACKAGE_NAME}@latest`, "engines.node"],
23569
+ { encoding: "utf-8", shell: process.platform === "win32" }
23570
+ );
23571
+ if (res.status !== 0) return { ok: true, reason: "" };
23572
+ engines = (res.stdout ?? "").trim();
23573
+ } catch {
23574
+ return { ok: true, reason: "" };
23575
+ }
23576
+ if (!engines) return { ok: true, reason: "" };
23577
+ const major = Number(process.versions.node.split(".")[0]);
23578
+ if (Number.isNaN(major)) return { ok: true, reason: "" };
23579
+ const min = /(?:>=|\^|~)\s*v?(\d+)/.exec(engines);
23580
+ if (min && major < Number(min[1])) {
23581
+ return {
23582
+ ok: false,
23583
+ reason: `it needs Node ${engines} and this host runs Node ${process.versions.node}`
23584
+ };
23585
+ }
23586
+ const lessThan = /<\s*v?(\d+)/.exec(engines);
23587
+ if (lessThan && major >= Number(lessThan[1])) {
23588
+ return {
23589
+ ok: false,
23590
+ reason: `it needs Node ${engines} and this host runs Node ${process.versions.node}`
23591
+ };
23592
+ }
23593
+ const atMost = /<=\s*v?(\d+)/.exec(engines);
23594
+ if (atMost && major > Number(atMost[1])) {
23595
+ return {
23596
+ ok: false,
23597
+ reason: `it needs Node ${engines} and this host runs Node ${process.versions.node}`
23598
+ };
23599
+ }
23600
+ return { ok: true, reason: "" };
23601
+ }
23462
23602
  function installLatest() {
23603
+ const fit = hostSatisfiesTarget();
23604
+ if (!fit.ok) {
23605
+ console.error(
23606
+ `[update] refusing to install ${PACKAGE_NAME}@latest: ${fit.reason}.
23607
+ [update] Staying on ${currentVersion()}. Install a supported Node (nvm install 24.11.1) and restart the runner to update.`
23608
+ );
23609
+ return false;
23610
+ }
23463
23611
  console.log(`[update] installing ${PACKAGE_NAME}@latest...`);
23464
23612
  const res = (0, import_node_child_process.spawnSync)("npm", ["install", "-g", `${PACKAGE_NAME}@latest`], {
23465
23613
  stdio: "inherit",
@@ -23480,9 +23628,9 @@ var import_node_child_process2 = require("node:child_process");
23480
23628
 
23481
23629
  // src/daemon.ts
23482
23630
  var import_crypto11 = require("crypto");
23483
- var import_fs11 = __toESM(require("fs"));
23631
+ var import_fs12 = __toESM(require("fs"));
23484
23632
  var import_node_datachannel = require("node-datachannel");
23485
- var import_path23 = __toESM(require("path"));
23633
+ var import_path24 = __toESM(require("path"));
23486
23634
 
23487
23635
  // ../../node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.node.js
23488
23636
  var XMLHttpRequestModule = __toESM(require_XMLHttpRequest(), 1);
@@ -26981,18 +27129,18 @@ Object.assign(lookup, {
26981
27129
  });
26982
27130
 
26983
27131
  // src/agent-adapter.ts
26984
- var import_fs10 = __toESM(require("fs"));
26985
- var import_path22 = __toESM(require("path"));
27132
+ var import_fs11 = __toESM(require("fs"));
27133
+ var import_path23 = __toESM(require("path"));
26986
27134
  function credsDir(spaceSlug) {
26987
- return import_path22.default.join(spaceDirPath(spaceSlug), ".creds");
27135
+ return import_path23.default.join(spaceDirPath(spaceSlug), ".creds");
26988
27136
  }
26989
27137
  function githubPath(spaceSlug) {
26990
- return import_path22.default.join(credsDir(spaceSlug), "github.json");
27138
+ return import_path23.default.join(credsDir(spaceSlug), "github.json");
26991
27139
  }
26992
27140
  function connectionPath(spaceSlug) {
26993
- return import_path22.default.join(credsDir(spaceSlug), "onedrive.json");
27141
+ return import_path23.default.join(credsDir(spaceSlug), "onedrive.json");
26994
27142
  }
26995
- var RUNNER_CONFIG_PATH = import_path22.default.join(CONFIG_DIR, "runner-config.json");
27143
+ var RUNNER_CONFIG_PATH = import_path23.default.join(CONFIG_DIR, "runner-config.json");
26996
27144
  function writeJson(filePath, value2) {
26997
27145
  writeSecureJson(filePath, value2);
26998
27146
  }
@@ -27008,14 +27156,14 @@ function fillDefaults(config) {
27008
27156
  }
27009
27157
  function configDirNames() {
27010
27158
  try {
27011
- return import_fs10.default.readdirSync(CONFIG_DIR, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
27159
+ return import_fs11.default.readdirSync(CONFIG_DIR, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
27012
27160
  } catch {
27013
27161
  return [];
27014
27162
  }
27015
27163
  }
27016
27164
  function provisionedSpaceSlugs() {
27017
27165
  return configDirNames().filter(
27018
- (name) => import_fs10.default.existsSync(import_path22.default.join(CONFIG_DIR, name, "bin"))
27166
+ (name) => import_fs11.default.existsSync(import_path23.default.join(CONFIG_DIR, name, "bin"))
27019
27167
  );
27020
27168
  }
27021
27169
  var migrated = false;
@@ -27029,7 +27177,7 @@ function migrateToSpaceFolders() {
27029
27177
  }
27030
27178
  }
27031
27179
  function readLegacy(filePath) {
27032
- if (!import_fs10.default.existsSync(filePath)) return { readable: true, value: null };
27180
+ if (!import_fs11.default.existsSync(filePath)) return { readable: true, value: null };
27033
27181
  const value2 = readSecureJson(filePath);
27034
27182
  return { readable: value2 !== null, value: value2 };
27035
27183
  }
@@ -27047,14 +27195,14 @@ function migrateOnce() {
27047
27195
  const consumed = [];
27048
27196
  let strayFiles = [];
27049
27197
  try {
27050
- strayFiles = import_fs10.default.readdirSync(CONFIG_DIR).filter((f) => f.startsWith("space-") && f.endsWith(".json")).filter((f) => !f.endsWith(".onedrive.json"));
27198
+ strayFiles = import_fs11.default.readdirSync(CONFIG_DIR).filter((f) => f.startsWith("space-") && f.endsWith(".json")).filter((f) => !f.endsWith(".onedrive.json"));
27051
27199
  } catch {
27052
27200
  strayFiles = [];
27053
27201
  }
27054
27202
  for (const name of strayFiles) {
27055
27203
  const slug = name.slice("space-".length, -".json".length);
27056
27204
  const parsed = readLegacy(
27057
- import_path22.default.join(CONFIG_DIR, name)
27205
+ import_path23.default.join(CONFIG_DIR, name)
27058
27206
  );
27059
27207
  if (!parsed.readable) continue;
27060
27208
  if (parsed.value) strays[slug] = parsed.value;
@@ -27067,7 +27215,7 @@ function migrateOnce() {
27067
27215
  ...provisionedSpaceSlugs()
27068
27216
  ]);
27069
27217
  for (const slug of slugs) {
27070
- if (import_fs10.default.existsSync(githubPath(slug))) continue;
27218
+ if (import_fs11.default.existsSync(githubPath(slug))) continue;
27071
27219
  const over = boards[slug] ?? strays[slug] ?? {};
27072
27220
  if (!Object.keys(over).length && !Object.keys(base).length) continue;
27073
27221
  const merged = fillDefaults({
@@ -27078,32 +27226,32 @@ function migrateOnce() {
27078
27226
  githubToken: over.githubToken || base.githubToken,
27079
27227
  provider: over.provider || base.provider
27080
27228
  });
27081
- import_fs10.default.mkdirSync(credsDir(slug), { recursive: true });
27229
+ import_fs11.default.mkdirSync(credsDir(slug), { recursive: true });
27082
27230
  writeJson(githubPath(slug), merged);
27083
27231
  }
27084
27232
  let odFiles = [];
27085
27233
  try {
27086
- odFiles = import_fs10.default.readdirSync(CONFIG_DIR).filter((f) => f.startsWith("space-") && f.endsWith(".onedrive.json"));
27234
+ odFiles = import_fs11.default.readdirSync(CONFIG_DIR).filter((f) => f.startsWith("space-") && f.endsWith(".onedrive.json"));
27087
27235
  } catch {
27088
27236
  odFiles = [];
27089
27237
  }
27090
27238
  for (const name of odFiles) {
27091
27239
  const slug = name.slice("space-".length, -".onedrive.json".length);
27092
- if (import_fs10.default.existsSync(connectionPath(slug))) {
27240
+ if (import_fs11.default.existsSync(connectionPath(slug))) {
27093
27241
  consumed.push(name);
27094
27242
  continue;
27095
27243
  }
27096
- const conn = readLegacy(import_path22.default.join(CONFIG_DIR, name));
27244
+ const conn = readLegacy(import_path23.default.join(CONFIG_DIR, name));
27097
27245
  if (!conn.readable) continue;
27098
27246
  if (conn.value) {
27099
- import_fs10.default.mkdirSync(credsDir(slug), { recursive: true });
27247
+ import_fs11.default.mkdirSync(credsDir(slug), { recursive: true });
27100
27248
  writeJson(connectionPath(slug), conn.value);
27101
27249
  }
27102
27250
  consumed.push(name);
27103
27251
  }
27104
27252
  for (const name of [...consumed, "runner-config.json"]) {
27105
27253
  try {
27106
- import_fs10.default.rmSync(import_path22.default.join(CONFIG_DIR, name), { force: true });
27254
+ import_fs11.default.rmSync(import_path23.default.join(CONFIG_DIR, name), { force: true });
27107
27255
  } catch {
27108
27256
  }
27109
27257
  }
@@ -27115,14 +27263,14 @@ function encryptStrayPlaintextConfigs() {
27115
27263
  const OURS = /^(config|runner-config|space-.*)\.json(\.bak-\d+)?$/;
27116
27264
  let names = [];
27117
27265
  try {
27118
- names = import_fs10.default.readdirSync(CONFIG_DIR).filter((f) => OURS.test(f));
27266
+ names = import_fs11.default.readdirSync(CONFIG_DIR).filter((f) => OURS.test(f));
27119
27267
  } catch {
27120
27268
  return;
27121
27269
  }
27122
27270
  for (const name of names) {
27123
- const filePath = import_path22.default.join(CONFIG_DIR, name);
27271
+ const filePath = import_path23.default.join(CONFIG_DIR, name);
27124
27272
  try {
27125
- const raw = import_fs10.default.readFileSync(filePath, "utf-8");
27273
+ const raw = import_fs11.default.readFileSync(filePath, "utf-8");
27126
27274
  if (!raw.startsWith("{")) continue;
27127
27275
  writeSecureJson(filePath, JSON.parse(raw));
27128
27276
  } catch {
@@ -27145,7 +27293,7 @@ async function getConfiguredConnections() {
27145
27293
  for (const slug of listConfiguredSlugs()) {
27146
27294
  if (kinds.has("git") && kinds.has("onedrive")) break;
27147
27295
  if (readBoard(slug).githubToken) kinds.add("git");
27148
- if (import_fs10.default.existsSync(connectionPath(slug))) kinds.add("onedrive");
27296
+ if (import_fs11.default.existsSync(connectionPath(slug))) kinds.add("onedrive");
27149
27297
  }
27150
27298
  } catch {
27151
27299
  return [];
@@ -27157,7 +27305,7 @@ function listConfiguredSlugs() {
27157
27305
  try {
27158
27306
  migrateToSpaceFolders();
27159
27307
  return configDirNames().filter(
27160
- (slug) => import_fs10.default.existsSync(githubPath(slug)) || import_fs10.default.existsSync(connectionPath(slug))
27308
+ (slug) => import_fs11.default.existsSync(githubPath(slug)) || import_fs11.default.existsSync(connectionPath(slug))
27161
27309
  );
27162
27310
  } catch {
27163
27311
  return [];
@@ -27176,7 +27324,7 @@ var localConfigProvider = {
27176
27324
  // which is what makes disconnect stick.
27177
27325
  writeConfig(spaceSlug, config) {
27178
27326
  migrateToSpaceFolders();
27179
- import_fs10.default.mkdirSync(credsDir(spaceSlug), { recursive: true });
27327
+ import_fs11.default.mkdirSync(credsDir(spaceSlug), { recursive: true });
27180
27328
  writeJson(githubPath(spaceSlug), config);
27181
27329
  },
27182
27330
  readOneDriveConnection(spaceSlug) {
@@ -27190,7 +27338,7 @@ var localConfigProvider = {
27190
27338
  writeOneDriveConnection(spaceSlug, connection) {
27191
27339
  const filePath = connectionPath(spaceSlug);
27192
27340
  if (!connection) {
27193
- import_fs10.default.rmSync(filePath, { force: true });
27341
+ import_fs11.default.rmSync(filePath, { force: true });
27194
27342
  return;
27195
27343
  }
27196
27344
  writeJson(filePath, connection);
@@ -27215,7 +27363,7 @@ var localConfigProvider = {
27215
27363
  "claude",
27216
27364
  "codex"
27217
27365
  ]) {
27218
- import_fs10.default.mkdirSync(import_path22.default.join(dir, sub), { recursive: true });
27366
+ import_fs11.default.mkdirSync(import_path23.default.join(dir, sub), { recursive: true });
27219
27367
  }
27220
27368
  return dir;
27221
27369
  }
@@ -27389,15 +27537,15 @@ async function startDaemon(config) {
27389
27537
  }
27390
27538
  );
27391
27539
  const indexCandidates = [
27392
- import_path23.default.join(__dirname, "..", "index"),
27393
- import_path23.default.join(__dirname, "..", "..", "index", "image")
27540
+ import_path24.default.join(__dirname, "..", "index"),
27541
+ import_path24.default.join(__dirname, "..", "..", "index", "image")
27394
27542
  ];
27395
- const indexDir = indexCandidates.find((p) => import_fs11.default.existsSync(p)) ?? indexCandidates[0];
27543
+ const indexDir = indexCandidates.find((p) => import_fs12.default.existsSync(p)) ?? indexCandidates[0];
27396
27544
  const odIndex = new IndexService({
27397
27545
  controlToken: (0, import_crypto11.createHash)("sha256").update(config.authToken).digest("hex"),
27398
27546
  embedUrl: () => config.serverUrl,
27399
27547
  indexDir,
27400
- dataDir: import_path23.default.join(CONFIG_DIR, "index-data")
27548
+ dataDir: import_path24.default.join(CONFIG_DIR, "index-data")
27401
27549
  });
27402
27550
  void odIndex.ensureRunning().catch((err) => {
27403
27551
  console.error(
@@ -28013,27 +28161,24 @@ ${err.fix}`);
28013
28161
  console.error("");
28014
28162
  process.exit(1);
28015
28163
  }
28016
- var MAX_SUPPORTED_NODE_MAJOR = 24;
28017
- function assertSupportedNode() {
28018
- const major = Number(process.versions.node.split(".")[0]);
28019
- if (Number.isNaN(major) || major <= MAX_SUPPORTED_NODE_MAJOR) return;
28020
- console.error(
28164
+ function warnUnsupportedNode() {
28165
+ if (indexNodeSupported()) return;
28166
+ const alternative = findIndexNode();
28167
+ console.warn(
28021
28168
  `
28022
- factiii-runner needs Node 20\u2013${MAX_SUPPORTED_NODE_MAJOR} (pinned 24.11.1); you are on Node ${process.versions.node}.
28023
- The bundled OneDrive index compiles better-sqlite3 from source, and that build fails on Node 25+.
28024
-
28025
- Switch with nvm: nvm install 24.11.1 && nvm use 24.11.1
28026
- then relaunch: factiii-runner start
28027
- `
28169
+ Node ${process.versions.node} is outside the range the OneDrive index supports (${MIN_INDEX_NODE_MAJOR}-${MAX_INDEX_NODE_MAJOR}, pinned 24.11.1).` + (alternative ? `
28170
+ The index will run under ${alternative}. Everything else is unaffected.
28171
+ ` : `
28172
+ OneDrive search will be unavailable until a supported Node is installed (nvm install 24.11.1). Everything else works.
28173
+ `)
28028
28174
  );
28029
- process.exit(1);
28030
28175
  }
28031
28176
  import_node_dns.default.setDefaultResultOrder("ipv4first");
28032
28177
  var program2 = new Command();
28033
28178
  program2.name("factiii-runner").description("Factiii Runner - run Board AI agents on remote servers").version(currentVersion());
28034
28179
  program2.command("setup").description("Verify the host toolchain and pair this runner").option("-s, --server <url>", "API server URL", "https://api.factiii.com").action(async (opts) => {
28035
28180
  try {
28036
- assertSupportedNode();
28181
+ warnUnsupportedNode();
28037
28182
  await setup(opts.server);
28038
28183
  } catch (err) {
28039
28184
  console.error(
@@ -28062,7 +28207,7 @@ program2.command("connect").description("Pair this runner with a Factiii account
28062
28207
  }
28063
28208
  });
28064
28209
  program2.command("start").description("Start the runner daemon").action(async () => {
28065
- assertSupportedNode();
28210
+ warnUnsupportedNode();
28066
28211
  if (!configExists()) {
28067
28212
  console.error('No config found. Run "factiii-runner setup" first.');
28068
28213
  process.exit(1);
@@ -28108,8 +28253,7 @@ program2.command("status").description("Show current runner configuration").acti
28108
28253
  console.log(`Token: \xB7\xB7\xB7\xB7${config.authToken.slice(-4)}`);
28109
28254
  });
28110
28255
  program2.command("doctor").description("Show where each host tool resolves, for a failed environment").action(async () => {
28111
- const nodeMajor = Number(process.versions.node.split(".")[0]);
28112
- const nodeNote = nodeMajor > MAX_SUPPORTED_NODE_MAJOR ? " (UNSUPPORTED \u2014 use 24.11.1)" : "";
28256
+ const nodeNote = indexNodeSupported() ? "" : ` (OneDrive index needs ${MIN_INDEX_NODE_MAJOR}-${MAX_INDEX_NODE_MAJOR}; ${findIndexNode() ?? "no supported node found"})`;
28113
28257
  console.log(`${"node".padEnd(14)} ${process.execPath} v${process.versions.node}${nodeNote}`);
28114
28258
  const report = await reportToolchain(hostTarget(CONFIG_DIR));
28115
28259
  for (const tool of report.tools) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@factiii/runner",
3
- "version": "0.12.1",
3
+ "version": "0.12.2",
4
4
  "description": "Factiii Runner, run Board AI agents on a machine you control. Pairs with the Factiii web/mobile clients over WebRTC.",
5
5
  "license": "ISC",
6
6
  "keywords": [