@lumi.ai/runner 0.3.1 → 0.3.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 +46 -36
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -532,7 +532,7 @@ function mcpUrl(config2) {
532
532
  }
533
533
 
534
534
  // src/version.ts
535
- var RUNNER_VERSION = true ? "0.3.1" : "0.0.0-dev";
535
+ var RUNNER_VERSION = true ? "0.3.2" : "0.0.0-dev";
536
536
 
537
537
  // src/auth.ts
538
538
  import { signInWithCustomToken } from "firebase/auth";
@@ -2639,10 +2639,10 @@ function serviceStatus() {
2639
2639
  };
2640
2640
  }
2641
2641
  if (process.platform === "win32") {
2642
- const query6 = run("schtasks", ["/Query", "/TN", WINDOWS_TASK]);
2643
- if (!query6.ok) return { state: "not-installed", detail: "No scheduled task installed." };
2642
+ const query5 = run("schtasks", ["/Query", "/TN", WINDOWS_TASK]);
2643
+ if (!query5.ok) return { state: "not-installed", detail: "No scheduled task installed." };
2644
2644
  return {
2645
- state: /\bRunning\b/i.test(query6.out) ? "running" : "installed",
2645
+ state: /\bRunning\b/i.test(query5.out) ? "running" : "installed",
2646
2646
  detail: "Scheduled task installed (runs at logon).",
2647
2647
  unitPath: WINDOWS_TASK
2648
2648
  };
@@ -2751,21 +2751,6 @@ async function openShipSession(shipId) {
2751
2751
  const user = await signInToShip(fb, config2, shipId);
2752
2752
  return { shipId, config: config2, fb, user };
2753
2753
  }
2754
- async function openSession() {
2755
- const config2 = requireConfig();
2756
- if (config2.ships.length === 0) {
2757
- throw new AuthError("No Ships assigned. Run: lumi-runner ship add <shipId>");
2758
- }
2759
- let last;
2760
- for (const shipId of config2.ships) {
2761
- try {
2762
- return await openShipSession(shipId);
2763
- } catch (e) {
2764
- last = e;
2765
- }
2766
- }
2767
- throw last instanceof Error ? last : new AuthError("Could not sign in to any Ship.");
2768
- }
2769
2754
 
2770
2755
  // src/cli/commands/doctor.ts
2771
2756
  var ok = (id, label, detail) => ({ id, label, level: "ok", detail });
@@ -3280,17 +3265,19 @@ async function runUninstall(options) {
3280
3265
  }
3281
3266
 
3282
3267
  // src/cli/commands/ship.ts
3283
- import { collectionGroup, doc as doc9, getDoc as getDoc7, getDocs as getDocs5, query as query4, where as where4 } from "firebase/firestore";
3268
+ import { doc as doc9, getDoc as getDoc7 } from "firebase/firestore";
3284
3269
  async function listMyShips() {
3285
- const { fb, user } = await openSession();
3286
- const memberships = await getDocs5(
3287
- query4(collectionGroup(fb.db, COLLECTIONS.members), where4("uid", "==", user.uid))
3288
- );
3289
- const shipIds = memberships.docs.map((d) => d.ref.parent.parent?.id).filter((id) => !!id);
3270
+ const config2 = requireConfig();
3271
+ const ids = Object.keys(config2.shipKeys ?? {});
3290
3272
  const ships = await Promise.all(
3291
- shipIds.map(async (id) => {
3292
- const snap = await getDoc7(doc9(fb.db, COLLECTIONS.ships, id));
3293
- return snap.exists() ? { id: snap.id, ...snap.data() } : null;
3273
+ ids.map(async (id) => {
3274
+ try {
3275
+ const { fb } = await openShipSession(id);
3276
+ const snap = await getDoc7(doc9(fb.db, COLLECTIONS.ships, id));
3277
+ return { id, ...snap.data() };
3278
+ } catch {
3279
+ return null;
3280
+ }
3294
3281
  })
3295
3282
  );
3296
3283
  return ships.filter((s) => s !== null).sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0));
@@ -3298,7 +3285,9 @@ async function listMyShips() {
3298
3285
  async function pickShips(current) {
3299
3286
  const ships = await listMyShips();
3300
3287
  if (ships.length === 0) {
3301
- throw new CliError("You are not a member of any Ship yet \u2014 create one in the web app first.");
3288
+ throw new CliError(
3289
+ "This machine holds no Ship keys. Run `lumi-runner login` and have a captain approve it \u2014 a daemon can only serve Ships it was approved for."
3290
+ );
3302
3291
  }
3303
3292
  return promptMultiSelect({
3304
3293
  message: "Which Ships should this machine serve?",
@@ -3408,9 +3397,9 @@ import {
3408
3397
  doc as doc10,
3409
3398
  getCountFromServer as getCountFromServer2,
3410
3399
  getDoc as getDoc8,
3411
- getDocs as getDocs6,
3412
- query as query5,
3413
- where as where5
3400
+ getDocs as getDocs5,
3401
+ query as query4,
3402
+ where as where4
3414
3403
  } from "firebase/firestore";
3415
3404
  async function runStatus() {
3416
3405
  const config2 = loadConfig();
@@ -3424,16 +3413,33 @@ async function runStatus() {
3424
3413
  }
3425
3414
  const progress = spinner2();
3426
3415
  progress.start("Reading Ship state\u2026");
3427
- const { fb } = await openSession();
3428
3416
  const ships = [];
3429
3417
  for (const shipId of config2.ships) {
3418
+ let fb;
3419
+ try {
3420
+ ({ fb } = await openShipSession(shipId));
3421
+ } catch {
3422
+ ships.push({
3423
+ shipId,
3424
+ enrolled: false,
3425
+ approved: false,
3426
+ online: false,
3427
+ lastSeenAt: null,
3428
+ currentJob: null,
3429
+ queued: 0,
3430
+ today: { ...EMPTY_USAGE_TOTALS },
3431
+ engineLimits: [],
3432
+ unreachable: true
3433
+ });
3434
+ continue;
3435
+ }
3430
3436
  const shipRef = doc10(fb.db, COLLECTIONS.ships, shipId);
3431
3437
  const mirrorSnap = await getDoc8(doc10(shipRef, COLLECTIONS.runners, config2.runnerId));
3432
3438
  const mirror = mirrorSnap.data();
3433
3439
  let queued = 0;
3434
3440
  try {
3435
3441
  const counted = await getCountFromServer2(
3436
- query5(collection7(shipRef, COLLECTIONS.jobs), where5("status", "==", "queued"))
3442
+ query4(collection7(shipRef, COLLECTIONS.jobs), where4("status", "==", "queued"))
3437
3443
  );
3438
3444
  queued = counted.data().count;
3439
3445
  } catch {
@@ -3442,7 +3448,7 @@ async function runStatus() {
3442
3448
  const usageSnap = await getDoc8(doc10(shipRef, COLLECTIONS.usageDaily, utcDay(Date.now())));
3443
3449
  const today = { ...EMPTY_USAGE_TOTALS, ...usageSnap.data()?.totals ?? {} };
3444
3450
  const now = Date.now();
3445
- const limitsSnap = await getDocs6(collection7(shipRef, COLLECTIONS.engineLimits));
3451
+ const limitsSnap = await getDocs5(collection7(shipRef, COLLECTIONS.engineLimits));
3446
3452
  const engineLimits = limitsSnap.docs.map((d) => ({ id: d.id, ...d.data() })).filter((l) => isEngineLimited(l, now));
3447
3453
  ships.push({
3448
3454
  shipId,
@@ -3479,8 +3485,12 @@ async function runStatus() {
3479
3485
  return 0;
3480
3486
  }
3481
3487
  for (const ship2 of ships) {
3482
- const state = !ship2.enrolled ? pc.dim("not enrolled") : !ship2.approved ? pc.yellow("awaiting approval") : ship2.online ? pc.green("online") : pc.red("offline");
3488
+ const state = ship2.unreachable ? pc.red("no access") : !ship2.enrolled ? pc.dim("not enrolled") : !ship2.approved ? pc.yellow("awaiting approval") : ship2.online ? pc.green("online") : pc.red("offline");
3483
3489
  say.line(` ${pc.bold(ship2.shipId)} ${state}`);
3490
+ if (ship2.unreachable) {
3491
+ say.line(` ${pc.dim("this machine\u2019s key for that Ship was revoked \u2014 re-approve it on the Daemons page")}`);
3492
+ continue;
3493
+ }
3484
3494
  if (ship2.lastSeenAt) say.line(` last seen ${new Date(ship2.lastSeenAt).toLocaleString()}`);
3485
3495
  say.line(
3486
3496
  ship2.currentJob ? ` running job ${ship2.currentJob.jobId} on task ${ship2.currentJob.taskId} (agent ${ship2.currentJob.agentId})` : " running nothing"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumi.ai/runner",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "type": "module",
5
5
  "description": "Lumi Crew runner daemon — claims jobs from your Ships and executes them as headless Claude sessions on your own machine.",
6
6
  "//name": "The ONLY package in this monorepo published to the public registry, so it is the one that does not follow the internal @lumi/crew-* convention: `@lumi` is not a scope we own, `@lumi.ai` is (the npm org). The workspace DIRECTORY stays packages/crew/runner — renaming the package is not renaming the folder.",