byollm 0.1.1 → 0.1.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.
@@ -613,11 +613,41 @@ var DaemonConfig = z.object({
613
613
  * The trade this switch represents is written down rather than
614
614
  * discovered: an auto-updating daemon means whoever controls the npm
615
615
  * publish controls the fleet. The controls on that are 2FA at publish,
616
- * exact-version installs (never a tag), the canary and rollback in
617
- * `update.ts`, and staged cohorts. It is the Chrome trade, taken
618
- * deliberately.
616
+ * exact-version installs (never a tag), forward-only versions, offers
617
+ * from {@link updateAuthority} alone, SLSA provenance checked before the
618
+ * new binary runs, and the canary and rollback in `update.ts`. Staged
619
+ * cohorts are NOT among them yet: nothing in this file or on the wire
620
+ * spreads an offer over time, so every machine the authority offers a
621
+ * version to takes it on its next heartbeat. It is the Chrome trade,
622
+ * taken deliberately.
619
623
  */
620
624
  autoUpdate: z.boolean().default(false),
625
+ /**
626
+ * The one origin whose update offers this daemon takes — B360.
627
+ *
628
+ * Unset means the reference hub (`DEFAULT_ORIGIN` in `cli.ts`), and it is
629
+ * resolved there rather than defaulted here on purpose: `byollm offer`
630
+ * writes the PARSED config back, so a schema default would be baked into
631
+ * every owner's file — and an older daemon, which is exactly what a
632
+ * rollback installs, refuses a key its strict schema has never seen.
633
+ *
634
+ * Every other paired origin's offer is ignored and said once. The
635
+ * authority's own offers count only over a pairing that pinned a control
636
+ * plane; a direct-mode pairing never updates the daemon.
637
+ */
638
+ updateAuthority: z.string().refine(
639
+ (value) => {
640
+ try {
641
+ normalizeOrigin(value);
642
+ return true;
643
+ } catch {
644
+ return false;
645
+ }
646
+ },
647
+ {
648
+ message: "an origin byollm can reach, the way `byollm connect` takes one (https://hub.byollm.cloud)"
649
+ }
650
+ ).optional(),
621
651
  /**
622
652
  * The memory floor this device refuses model work below — B090.
623
653
  *
@@ -1152,37 +1182,58 @@ ${plan.say}
1152
1182
  }
1153
1183
 
1154
1184
  // src/update.ts
1185
+ import { compareVersions } from "@byollm/protocol";
1155
1186
  function exactVersion(value) {
1156
1187
  return /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(value) ? value : void 0;
1157
1188
  }
1158
1189
  async function update(from, to, deps) {
1159
1190
  const target = exactVersion(to);
1160
1191
  if (target === void 0) {
1161
- const why2 = `refusing to install "${to}" \u2014 the updater installs exact versions only`;
1162
- deps.report(why2);
1163
- return { kind: "refused", why: why2 };
1192
+ const why = `refusing to install "${to}" \u2014 the updater installs exact versions only`;
1193
+ deps.report(why);
1194
+ return { kind: "refused", why };
1164
1195
  }
1165
- if (target === from) {
1196
+ if (exactVersion(from) === void 0) {
1197
+ const why = `refusing to update from "${from}" \u2014 no version to roll back to`;
1198
+ deps.report(why);
1199
+ return { kind: "refused", why };
1200
+ }
1201
+ const order = compareVersions(target, from);
1202
+ if (order === 0) {
1166
1203
  return { kind: "refused", why: `already on ${from}` };
1167
1204
  }
1168
- if (exactVersion(from) === void 0) {
1169
- const why2 = `refusing to update from "${from}" \u2014 no version to roll back to`;
1170
- deps.report(why2);
1171
- return { kind: "refused", why: why2 };
1205
+ if (order === void 0 || order < 0) {
1206
+ const why = order === void 0 ? `refusing to install ${target} \u2014 cannot tell whether it is newer than ${from}` : `refusing to install ${target} \u2014 it is older than ${from}, and the updater never downgrades`;
1207
+ deps.report(why);
1208
+ return { kind: "refused", why };
1172
1209
  }
1173
1210
  await deps.drain();
1174
1211
  if (!await deps.install(target)) {
1175
1212
  await deps.reregister();
1176
- const why2 = `could not install ${target} \u2014 staying on ${from}`;
1177
- deps.report(why2);
1178
- return { kind: "refused", why: why2 };
1213
+ const why = `could not install ${target} \u2014 staying on ${from}`;
1214
+ deps.report(why);
1215
+ return { kind: "refused", why };
1216
+ }
1217
+ const proof = await deps.verify(target);
1218
+ if (!proof.verified) {
1219
+ return rollBack(
1220
+ from,
1221
+ `${target} failed its provenance check: ${proof.why}`,
1222
+ deps
1223
+ );
1179
1224
  }
1180
1225
  await deps.reregister();
1181
1226
  const reported = await deps.installedVersion();
1182
1227
  if (reported === target) {
1183
1228
  return { kind: "updated", to: target };
1184
1229
  }
1185
- const why = reported === void 0 ? `${target} did not answer after installing` : `installed ${target} but the binary reports ${reported}`;
1230
+ return rollBack(
1231
+ from,
1232
+ reported === void 0 ? `${target} did not answer after installing` : `installed ${target} but the binary reports ${reported}`,
1233
+ deps
1234
+ );
1235
+ }
1236
+ async function rollBack(from, why, deps) {
1186
1237
  if (!await deps.install(from)) {
1187
1238
  const stranded = `${why}; rolling back to ${from} also failed`;
1188
1239
  deps.report(stranded);
@@ -1198,6 +1249,115 @@ async function update(from, to, deps) {
1198
1249
  deps.report(`${why}; rolled back to ${from}`);
1199
1250
  return { kind: "rolled-back", to: from, why };
1200
1251
  }
1252
+ function offerInbox(input) {
1253
+ let offered;
1254
+ const refused = /* @__PURE__ */ new Set();
1255
+ return {
1256
+ receive: (origin, version) => {
1257
+ const why = origin !== input.authority ? `updates are taken only from ${input.authority}` : input.hasControlPlane(origin) ? void 0 : "it is a direct-mode pairing, and those never update this daemon";
1258
+ if (why === void 0) {
1259
+ offered = version;
1260
+ return;
1261
+ }
1262
+ if (refused.has(origin)) return;
1263
+ refused.add(origin);
1264
+ input.report(`ignoring an offer of ${version} from ${origin} \u2014 ${why}`);
1265
+ },
1266
+ current: () => offered
1267
+ };
1268
+ }
1269
+
1270
+ // src/provenance.ts
1271
+ var NPM_REGISTRY = "https://registry.npmjs.org";
1272
+ var RELEASE_REPOSITORY = "https://github.com/oftomorrowinc/byollm";
1273
+ var SLSA_V1 = "https://slsa.dev/provenance/v1";
1274
+ async function verifyProvenance(version, options = {}) {
1275
+ const get = options.fetch ?? ((url) => fetch(url));
1276
+ const registry = options.registry ?? NPM_REGISTRY;
1277
+ const no = (why) => ({ verified: false, why });
1278
+ const integrity = await readJson(get, `${registry}/byollm/${version}`);
1279
+ if (!integrity.ok)
1280
+ return no(`could not read the registry entry: ${integrity.why}`);
1281
+ const expected = sha512Hex(
1282
+ integrity.body?.dist?.integrity
1283
+ );
1284
+ if (expected === void 0) {
1285
+ return no("the registry entry has no sha512 integrity to check against");
1286
+ }
1287
+ const listed = await readJson(
1288
+ get,
1289
+ `${registry}/-/npm/v1/attestations/byollm@${version}`
1290
+ );
1291
+ if (!listed.ok) return no(`could not read the attestations: ${listed.why}`);
1292
+ const attestations = listed.body?.attestations;
1293
+ if (!Array.isArray(attestations))
1294
+ return no("the registry lists no attestations");
1295
+ const slsa = attestations.filter(
1296
+ (entry) => entry?.predicateType === SLSA_V1
1297
+ );
1298
+ if (slsa.length === 0) return no("there is no SLSA provenance for it");
1299
+ for (const entry of slsa) {
1300
+ const statement = decodeStatement(entry);
1301
+ if (statement === void 0) return no("its provenance could not be read");
1302
+ const why = checkStatement(statement, version, expected);
1303
+ if (why !== void 0) return no(why);
1304
+ }
1305
+ return { verified: true };
1306
+ }
1307
+ function checkStatement(statement, version, expected) {
1308
+ const workflow = statement.predicate?.buildDefinition?.externalParameters?.workflow;
1309
+ if (workflow?.repository !== RELEASE_REPOSITORY) {
1310
+ return `it was built by ${named(workflow?.repository)}, not ${RELEASE_REPOSITORY}`;
1311
+ }
1312
+ if (workflow.ref !== `refs/tags/v${version}`) {
1313
+ return `it was built from ${named(workflow.ref)}, not refs/tags/v${version}`;
1314
+ }
1315
+ const subjects = Array.isArray(
1316
+ statement.subject
1317
+ ) ? statement.subject : [];
1318
+ const ours = subjects.find((s) => s.name === `pkg:npm/byollm@${version}`);
1319
+ if (ours === void 0)
1320
+ return `its provenance does not name byollm@${version}`;
1321
+ if (ours.digest?.sha512 !== expected) {
1322
+ return "its provenance names a different tarball than the one npm installed";
1323
+ }
1324
+ return void 0;
1325
+ }
1326
+ async function readJson(get, url) {
1327
+ try {
1328
+ const response = await get(url);
1329
+ if (!response.ok)
1330
+ return { ok: false, why: `HTTP ${String(response.status)}` };
1331
+ return { ok: true, body: await response.json() };
1332
+ } catch (error) {
1333
+ return {
1334
+ ok: false,
1335
+ why: error instanceof Error ? error.message : "unknown error"
1336
+ };
1337
+ }
1338
+ }
1339
+ function decodeStatement(entry) {
1340
+ const payload = entry?.bundle?.dsseEnvelope?.payload;
1341
+ if (typeof payload !== "string") return void 0;
1342
+ try {
1343
+ const parsed = JSON.parse(
1344
+ Buffer.from(payload, "base64").toString("utf8")
1345
+ );
1346
+ return typeof parsed === "object" && parsed !== null ? parsed : void 0;
1347
+ } catch {
1348
+ return void 0;
1349
+ }
1350
+ }
1351
+ function sha512Hex(integrity) {
1352
+ if (typeof integrity !== "string") return void 0;
1353
+ const match = /^sha512-([A-Za-z0-9+/]+={0,2})$/.exec(integrity.trim());
1354
+ if (match?.[1] === void 0) return void 0;
1355
+ const bytes2 = Buffer.from(match[1], "base64");
1356
+ return bytes2.length === 64 ? bytes2.toString("hex") : void 0;
1357
+ }
1358
+ function named(value) {
1359
+ return typeof value === "string" ? value : "an unnamed workflow";
1360
+ }
1201
1361
 
1202
1362
  // src/update-deps.ts
1203
1363
  function realUpdateDeps(input) {
@@ -1209,6 +1369,8 @@ function realUpdateDeps(input) {
1209
1369
  "npm",
1210
1370
  "install",
1211
1371
  "--global",
1372
+ "--ignore-scripts",
1373
+ `--registry=${NPM_REGISTRY}`,
1212
1374
  `byollm@${version}`
1213
1375
  ]);
1214
1376
  if (result.code !== 0) {
@@ -1218,6 +1380,7 @@ function realUpdateDeps(input) {
1218
1380
  }
1219
1381
  return result.code === 0;
1220
1382
  },
1383
+ verify: input.verify ?? ((version) => verifyProvenance(version)),
1221
1384
  reregister: async () => (await input.run([binary, "start"])).code === 0,
1222
1385
  installedVersion: async () => {
1223
1386
  const result = await input.run([binary, "--version"]);
@@ -1548,6 +1711,7 @@ var FIXED_ARGV = Object.freeze([
1548
1711
  "--strict-mcp-config",
1549
1712
  "--mcp-config",
1550
1713
  '{"mcpServers":{}}',
1714
+ "--restricted",
1551
1715
  "--no-session-persistence"
1552
1716
  ]);
1553
1717
  var ENV_ALLOWLIST = Object.freeze([
@@ -2346,7 +2510,7 @@ var CLAUDE_ALIASES = [
2346
2510
  { id: "sonnet", note: "balanced" },
2347
2511
  { id: "fable", note: "fastest" }
2348
2512
  ];
2349
- async function readJson(path) {
2513
+ async function readJson2(path) {
2350
2514
  try {
2351
2515
  return JSON.parse(await readFile6(path, "utf8"));
2352
2516
  } catch {
@@ -2371,7 +2535,7 @@ function codexSuggestions(cache) {
2371
2535
  }));
2372
2536
  }
2373
2537
  async function claudeSuggestions(home) {
2374
- const settings = await readJson(join3(home, ".claude", "settings.json"));
2538
+ const settings = await readJson2(join3(home, ".claude", "settings.json"));
2375
2539
  const configured = typeof settings === "object" && settings !== null ? settings.model : void 0;
2376
2540
  const out = [];
2377
2541
  if (typeof configured === "string" && configured !== "") {
@@ -2388,7 +2552,7 @@ async function claudeSuggestions(home) {
2388
2552
  async function modelSuggestions(cli, home = homedir()) {
2389
2553
  if (cli === "codex-cli") {
2390
2554
  return codexSuggestions(
2391
- await readJson(join3(home, ".codex", "models_cache.json"))
2555
+ await readJson2(join3(home, ".codex", "models_cache.json"))
2392
2556
  );
2393
2557
  }
2394
2558
  if (cli === "claude-cli") return claudeSuggestions(home);
@@ -3076,14 +3240,14 @@ async function candidates(input) {
3076
3240
  let model = cli.model;
3077
3241
  let namedByOwner = false;
3078
3242
  if (model === void 0) {
3079
- const named = await modelByHand({
3243
+ const named2 = await modelByHand({
3080
3244
  cli,
3081
3245
  io: input.io,
3082
3246
  verifier: input.verifier,
3083
3247
  ...input.suggest === void 0 ? {} : { suggest: input.suggest }
3084
3248
  });
3085
- if (named === void 0) continue;
3086
- model = named;
3249
+ if (named2 === void 0) continue;
3250
+ model = named2;
3087
3251
  namedByOwner = true;
3088
3252
  }
3089
3253
  const proof = await input.verifier(cli.id, model);
@@ -4813,7 +4977,7 @@ function isNotFound2(error) {
4813
4977
  }
4814
4978
 
4815
4979
  // src/console-agent.ts
4816
- import { Buffer } from "buffer";
4980
+ import { Buffer as Buffer2 } from "buffer";
4817
4981
  import {
4818
4982
  ConsoleFrame,
4819
4983
  CONSOLE_FRAME_VERSION,
@@ -4973,7 +5137,7 @@ function consoleSession(deps) {
4973
5137
  case "stdin":
4974
5138
  if (!started) return;
4975
5139
  fromBrowser += 1;
4976
- deps.shell.write(Buffer.from(consoleDataBytes(frame.data)));
5140
+ deps.shell.write(Buffer2.from(consoleDataBytes(frame.data)));
4977
5141
  return;
4978
5142
  case "resize":
4979
5143
  if (!started) return;
@@ -4992,7 +5156,7 @@ function consoleSession(deps) {
4992
5156
  }
4993
5157
 
4994
5158
  // src/pty-shell.ts
4995
- import { Buffer as Buffer2 } from "buffer";
5159
+ import { Buffer as Buffer3 } from "buffer";
4996
5160
  var NO_PTY_MESSAGE = "byollm console-agent needs node-pty, which is a hosted-box feature and is not installed here.\nIf you are self-hosting a box, install it beside byollm: npm i -g node-pty";
4997
5161
  var NoPtyError = class extends Error {
4998
5162
  constructor() {
@@ -5032,7 +5196,7 @@ async function openPtyShell(options) {
5032
5196
  let exited;
5033
5197
  let announce;
5034
5198
  child.onData((data) => {
5035
- const chunk = Buffer2.from(data, "utf8");
5199
+ const chunk = Buffer3.from(data, "utf8");
5036
5200
  if (deliver !== void 0) {
5037
5201
  deliver(chunk);
5038
5202
  return;
@@ -7550,11 +7714,14 @@ var Runner = class {
7550
7714
  jobId: job.id,
7551
7715
  senderKeyId: keyId2(publicIdentityOf2(keys).identity),
7552
7716
  recipientKeyId: keyId2(this.#pinFor(job).identity),
7553
- // This runner's clock, not the process's — cloud_008 §31. The only
7554
- // `Date.now()` left in this class, in the one place that stamps a
7555
- // deadline somebody else enforces: a test that moves time moved every
7556
- // other clock here and not this one, so a sealed result carried an
7557
- // expiry from a different timeline than the lease it answered.
7717
+ // This runner's clock, not the process's — cloud_008 §31. This was a
7718
+ // `Date.now()`, in the one place that stamps a deadline somebody else
7719
+ // enforces: a test that moves time moved every other clock here and
7720
+ // not this one, so a sealed result carried an expiry from a different
7721
+ // timeline than the lease it answered. The retirement window had the
7722
+ // same two clocks until it was found the same way; `drain` is the one
7723
+ // read left on the process clock, because it waits out real time
7724
+ // beside a real sleep.
7558
7725
  deadlineAt: this.#now() + ENVELOPE_MAX_AGE_MS,
7559
7726
  direction: "result"
7560
7727
  }
@@ -7861,7 +8028,7 @@ var Runner = class {
7861
8028
  for (const id of [...this.#sites.keys()]) {
7862
8029
  if (id in sites) continue;
7863
8030
  const until2 = this.#retiring.get(id);
7864
- if (until2 !== void 0 && Date.now() < until2) continue;
8031
+ if (until2 !== void 0 && this.#now() < until2) continue;
7865
8032
  this.#retiring.delete(id);
7866
8033
  this.#sites.delete(id);
7867
8034
  for (const [leaseId, held] of this.#active) {
@@ -7943,7 +8110,7 @@ var Runner = class {
7943
8110
  if (!previous) return false;
7944
8111
  this.#known.set(id, site);
7945
8112
  this.#sites.set(id, site);
7946
- const ceiling = Date.now() + RETIREMENT_WINDOW_MS;
8113
+ const ceiling = this.#now() + RETIREMENT_WINDOW_MS;
7947
8114
  const proposed = offered.retiringUntil ?? ceiling;
7948
8115
  this.#retiring.set(walk.from, Math.min(proposed, ceiling));
7949
8116
  this.#options.onEvent?.({
@@ -8975,18 +9142,18 @@ function consoleSpecArg(args) {
8975
9142
  var UNRESTRICTED = "--unrestricted-shell";
8976
9143
  function consoleShellChoice(args) {
8977
9144
  const at = args.indexOf("--shell");
8978
- const named = at === -1 ? void 0 : args[at + 1];
9145
+ const named2 = at === -1 ? void 0 : args[at + 1];
8979
9146
  const escaped = args.includes(UNRESTRICTED);
8980
- if (at !== -1 && (named === void 0 || named.startsWith("--"))) {
9147
+ if (at !== -1 && (named2 === void 0 || named2.startsWith("--"))) {
8981
9148
  return { ok: false, why: "`--shell` needs a path to the shell to run." };
8982
9149
  }
8983
- if (named !== void 0 && escaped) {
9150
+ if (named2 !== void 0 && escaped) {
8984
9151
  return {
8985
9152
  ok: false,
8986
9153
  why: `\`--shell\` and \`${UNRESTRICTED}\` disagree about what to run.`
8987
9154
  };
8988
9155
  }
8989
- if (named !== void 0) return { ok: true, command: named, fenced: true };
9156
+ if (named2 !== void 0) return { ok: true, command: named2, fenced: true };
8990
9157
  if (escaped) return { ok: true, command: "/bin/sh", fenced: false };
8991
9158
  return {
8992
9159
  ok: false,
@@ -9120,9 +9287,9 @@ async function commandConsoleAgent(paths, args, io) {
9120
9287
  }
9121
9288
  var DEFAULT_ORIGIN = "https://hub.byollm.cloud";
9122
9289
  function connectTarget(args) {
9123
- const named = args.indexOf("--name");
9290
+ const named2 = args.indexOf("--name");
9124
9291
  const positional = args.filter(
9125
- (arg, at) => !arg.startsWith("-") && (named === -1 || at !== named && at !== named + 1)
9292
+ (arg, at) => !arg.startsWith("-") && (named2 === -1 || at !== named2 && at !== named2 + 1)
9126
9293
  );
9127
9294
  return positional[0] ?? DEFAULT_ORIGIN;
9128
9295
  }
@@ -9191,10 +9358,14 @@ async function commandModel(paths, args, io) {
9191
9358
  const shown = await showModel(paths.config, service, io);
9192
9359
  return shown.code;
9193
9360
  }
9361
+ const loaded = await loadConfig(paths.config).catch(() => void 0);
9362
+ const configured = loaded?.config.services[service];
9194
9363
  const set = await setModel(
9195
9364
  { configPath: paths.config, service, model },
9196
9365
  io,
9197
- backendVerifier((id) => createBackend(id, {})),
9366
+ backendVerifier(
9367
+ (id) => backendFor({ type: id, baseUrl: configured?.baseUrl })
9368
+ ),
9198
9369
  /**
9199
9370
  * The memory guard on the owner's own command — B106.
9200
9371
  *
@@ -9206,12 +9377,11 @@ async function commandModel(paths, args, io) {
9206
9377
  * job — `minAvailableMemoryBytes`, one value, one meaning, whoever asked.
9207
9378
  */
9208
9379
  async (backendId) => {
9209
- const loaded = await loadConfig(paths.config).catch(() => void 0);
9210
9380
  if (loaded === void 0) return { ask: false };
9211
9381
  const { memory, pressure } = await readHostMemory();
9212
9382
  return modelLoadQuestion({
9213
9383
  backendId,
9214
- baseUrl: loaded.config.services[service]?.baseUrl,
9384
+ baseUrl: configured?.baseUrl,
9215
9385
  model,
9216
9386
  memory,
9217
9387
  pressure,
@@ -9223,9 +9393,9 @@ async function commandModel(paths, args, io) {
9223
9393
  return set.code;
9224
9394
  }
9225
9395
  async function commandConnect(paths, args, io, signal) {
9226
- const named = args.indexOf("--name");
9227
- const name = named === -1 ? void 0 : args[named + 1];
9228
- if (named !== -1 && (name === void 0 || name.startsWith("-"))) {
9396
+ const named2 = args.indexOf("--name");
9397
+ const name = named2 === -1 ? void 0 : args[named2 + 1];
9398
+ if (named2 !== -1 && (name === void 0 || name.startsWith("-"))) {
9229
9399
  io.err("usage: byollm connect [<url>] [--name <name>]\n");
9230
9400
  return 2;
9231
9401
  }
@@ -9563,7 +9733,16 @@ async function runLoop(paths, origins, io, signal, supervised = !process.stdout.
9563
9733
  );
9564
9734
  if (signal?.aborted === true) controller.abort();
9565
9735
  const runners = [];
9566
- let offered;
9736
+ const inbox = loaded.config.autoUpdate ? offerInbox({
9737
+ authority: normalizeOrigin(
9738
+ loaded.config.updateAuthority ?? DEFAULT_ORIGIN
9739
+ ),
9740
+ hasControlPlane: (origin) => pairings.get(origin)?.controlPlanePublic !== void 0,
9741
+ report: (line) => {
9742
+ io.err(`update: ${line}
9743
+ `);
9744
+ }
9745
+ }) : void 0;
9567
9746
  for (const origin of origins) {
9568
9747
  const pairing = pairings.get(origin);
9569
9748
  if (!pairing) {
@@ -9662,7 +9841,8 @@ async function runLoop(paths, origins, io, signal, supervised = !process.stdout.
9662
9841
  },
9663
9842
  onEvent: (event) => {
9664
9843
  report(origin, event, io);
9665
- if (event.type === "update-offered") offered = event.version;
9844
+ if (event.type === "update-offered")
9845
+ inbox?.receive(origin, event.version);
9666
9846
  if (event.type === "heartbeat") {
9667
9847
  void recordSites(pairings, origin, runner.sites, {
9668
9848
  known: runner.known
@@ -9717,11 +9897,11 @@ Which sites may send work is enabled from your dashboard. Ctrl-C to stop.
9717
9897
  `
9718
9898
  );
9719
9899
  await ingress.applyRetention(Date.now());
9720
- const updated = loaded.config.autoUpdate ? watchForUpdate({
9900
+ const updated = inbox ? watchForUpdate({
9721
9901
  runners,
9722
9902
  io,
9723
9903
  signal: controller.signal,
9724
- offered: () => offered
9904
+ offered: inbox.current
9725
9905
  }) : new Promise(() => void 0);
9726
9906
  const tookUpdate = await Promise.race([
9727
9907
  Promise.all(runners.map((runner) => runner.run(controller.signal))).then(
@@ -9747,6 +9927,7 @@ async function watchForUpdate(input) {
9747
9927
  const outcome = await update(DAEMON_VERSION, version, {
9748
9928
  ...realUpdateDeps({
9749
9929
  run: input.run ?? spawnCommand,
9930
+ ...input.verify === void 0 ? {} : { verify: input.verify },
9750
9931
  drain: async () => {
9751
9932
  await Promise.all(
9752
9933
  input.runners.map(
@@ -9910,7 +10091,7 @@ function describeAge(ms) {
9910
10091
  return seconds < 120 ? `${String(seconds)} seconds` : `${String(Math.round(seconds / 60))} minutes`;
9911
10092
  }
9912
10093
  async function commandStatus(paths, io, service) {
9913
- const { loaded, ingress, budgets, spend, spentGrants } = await context(paths);
10094
+ const { loaded, configured, ingress, budgets, spend, spentGrants } = await context(paths);
9914
10095
  const pairings = new Pairings(paths.pairings);
9915
10096
  await pairings.load();
9916
10097
  reportSkipped(pairings, io);
@@ -9931,10 +10112,17 @@ async function commandStatus(paths, io, service) {
9931
10112
  const plan = servicePlan(serviceTarget(paths, service));
9932
10113
  const supervision = await serviceState(plan, service.run);
9933
10114
  const revoked = await revokedMark(paths.health);
10115
+ const notSetUp = !configured && beat === void 0;
9934
10116
  io.out(
9935
- `state: ${revoked !== void 0 ? "REVOKED" : supervision.state === "installed" || stale ? "NOT RUNNING" : failing ? "NOT REPORTING" : "running"}
10117
+ `state: ${revoked !== void 0 ? "REVOKED" : supervision.state === "installed" || stale ? "NOT RUNNING" : failing ? "NOT REPORTING" : notSetUp ? "NOT SET UP" : "running"}
9936
10118
  `
9937
10119
  );
10120
+ if (revoked === void 0 && !stale && !failing && notSetUp) {
10121
+ io.out(
10122
+ ` no config at ${paths.config}, and nothing has run here yet.
10123
+ ${NOT_SET_UP_HINT}`
10124
+ );
10125
+ }
9938
10126
  if (revoked !== void 0) {
9939
10127
  io.out(
9940
10128
  ` ${REVOKED_SENTENCE}.
@@ -10000,8 +10188,10 @@ async function commandStatus(paths, io, service) {
10000
10188
  io.out("\nservices\n");
10001
10189
  const recorded = await readServiceStates(paths.serviceStates);
10002
10190
  const deviceName = await labelFor(paths, void 0);
10003
- const declared = Object.entries(loaded.config.services);
10004
- if (declared.length === 0) {
10191
+ const declared = configured ? Object.entries(loaded.config.services) : [];
10192
+ if (!configured) {
10193
+ io.out(" (none \u2014 run `byollm setup`)\n");
10194
+ } else if (declared.length === 0) {
10005
10195
  io.out(" (none configured)\n");
10006
10196
  }
10007
10197
  for (const [name, service2] of declared) {
@@ -10506,7 +10696,16 @@ ${summarise(outcome).join("\n")}
10506
10696
  return signal?.aborted === true ? 1 : 0;
10507
10697
  }
10508
10698
  async function commandServices(paths, io, service) {
10509
- const { loaded, ingress, budgets, spend, spentGrants } = await context(paths);
10699
+ const { loaded, configured, ingress, budgets, spend, spentGrants } = await context(paths);
10700
+ if (!configured) {
10701
+ io.out(
10702
+ `services
10703
+ (none \u2014 no config at ${paths.config})
10704
+
10705
+ ` + NOT_SET_UP_HINT
10706
+ );
10707
+ return 1;
10708
+ }
10510
10709
  const pairings = new Pairings(paths.pairings);
10511
10710
  await pairings.load();
10512
10711
  const hasRelay = pairings.list().some((pairing) => pairing.controlPlanePublic !== void 0);
@@ -10602,6 +10801,10 @@ with the device's page, and after installing a new CLI run
10602
10801
  }
10603
10802
  async function context(paths) {
10604
10803
  const loaded = await loadConfig(paths.config);
10804
+ const configured = await access(paths.config).then(
10805
+ () => true,
10806
+ () => false
10807
+ );
10605
10808
  const ingress = new IngressLog({
10606
10809
  path: paths.ingressLog,
10607
10810
  communityPromptDays: loaded.config.ingress.communityPromptDays,
@@ -10613,8 +10816,9 @@ async function context(paths) {
10613
10816
  const spentGrants = new SpentGrants(paths.spentGrants);
10614
10817
  spentGrants.load(Date.now());
10615
10818
  await spend.load(Date.now());
10616
- return { loaded, ingress, budgets, spend, spentGrants };
10819
+ return { loaded, configured, ingress, budgets, spend, spentGrants };
10617
10820
  }
10821
+ var NOT_SET_UP_HINT = "Run `byollm setup` to find what this computer already has.\n";
10618
10822
  async function confirmInteractively(question) {
10619
10823
  if (!process.stdin.isTTY) {
10620
10824
  process.stderr.write(
@@ -10680,7 +10884,7 @@ async function exists(path) {
10680
10884
  }
10681
10885
 
10682
10886
  // src/index.ts
10683
- var DAEMON_VERSION = "0.1.1";
10887
+ var DAEMON_VERSION = "0.1.2";
10684
10888
  function daemonVersion() {
10685
10889
  return {
10686
10890
  daemon: DAEMON_VERSION,
@@ -10697,6 +10901,8 @@ ${v.platform}-${v.arch}, node ${v.node}
10697
10901
  }
10698
10902
 
10699
10903
  export {
10904
+ UnusableOrigin,
10905
+ normalizeOrigin,
10700
10906
  checkBaseUrl,
10701
10907
  BASE_URL_REFUSAL_MESSAGES,
10702
10908
  DaemonConfig,
@@ -10710,8 +10916,6 @@ export {
10710
10916
  createBackend,
10711
10917
  SpendLedger,
10712
10918
  estimateCents,
10713
- UnusableOrigin,
10714
- normalizeOrigin,
10715
10919
  Budgets,
10716
10920
  ClientError,
10717
10921
  ProtocolClient,
@@ -10739,4 +10943,4 @@ export {
10739
10943
  runCli,
10740
10944
  main
10741
10945
  };
10742
- //# sourceMappingURL=chunk-OOC3ASS6.js.map
10946
+ //# sourceMappingURL=chunk-K7QYAF7V.js.map