@cabane/companion 0.6.172 → 0.6.173

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.
@@ -432,114 +432,53 @@ function saveConfig(cfg) {
432
432
  }
433
433
 
434
434
  // src/enrollment.ts
435
- var EnrollmentExpiredError = class extends CompanionError {
436
- constructor() {
437
- super("this pairing code expired before it was confirmed.");
438
- this.name = "EnrollmentExpiredError";
439
- }
440
- };
441
- var EnrollmentCancelledError = class extends CompanionError {
442
- constructor() {
443
- super("pairing was cancelled.");
444
- this.name = "EnrollmentCancelledError";
445
- }
446
- };
447
- function trimBase(baseUrl) {
448
- return baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
449
- }
450
- async function postJson(baseUrl, path, body) {
435
+ async function claimSetupCode(baseUrl, input) {
451
436
  let res;
452
437
  try {
453
- res = await fetch(`${trimBase(baseUrl)}${path}`, {
438
+ res = await fetch(`${baseUrl.replace(/\/$/, "")}/api/device-enrollment/claim`, {
454
439
  method: "POST",
455
- headers: { "content-type": "application/json", accept: "application/json" },
456
- body: JSON.stringify(body)
440
+ headers: {
441
+ "content-type": "application/json",
442
+ ...input.deviceToken ? { authorization: `Bearer ${input.deviceToken}` } : {}
443
+ },
444
+ body: JSON.stringify({
445
+ userCode: input.userCode,
446
+ label: input.label,
447
+ platform: process.platform,
448
+ arch: process.arch,
449
+ nodeMajor: Number.parseInt(process.versions.node.split(".")[0], 10)
450
+ })
457
451
  });
458
- } catch (err) {
452
+ } catch {
459
453
  throw new CompanionError(
460
- `couldn't reach cabane at ${baseUrl}: ${err instanceof Error ? err.message : String(err)}. Check the server URL (pass --server <url>) and your connection.`
454
+ `Couldn't reach Cabane at ${baseUrl}. Check the server URL and your connection.`
461
455
  );
462
456
  }
463
- const raw = await res.text();
464
- let parsed = void 0;
465
- if (raw.length > 0) {
466
- try {
467
- parsed = JSON.parse(raw);
468
- } catch {
469
- parsed = raw;
470
- }
471
- }
472
- if (res.status >= 400) {
473
- if (res.status === 404 && path.endsWith("/code")) {
474
- throw new CompanionError(
475
- `no device-flow pairing endpoint at ${baseUrl}. Either that server predates \`${cabane("pair")}\`, or it isn't a Cabane API origin \u2014 the hosted app is https://app.cabane.ai (pass \`--server <url>\` for your own instance).`
476
- );
477
- }
478
- const msg = parsed && typeof parsed === "object" && "error" in parsed ? String(parsed.error) : `${res.status}`;
479
- throw new ApiError(res.status, `${res.status} ${msg}`, parsed);
480
- }
481
- return parsed;
482
- }
483
- function requestEnrollmentCode(baseUrl, opts = {}) {
484
- return postJson(baseUrl, "/api/device-enrollment/code", {
485
- ...opts.label ? { label: opts.label } : {},
486
- ...hostFacts()
487
- });
488
- }
489
- function hostFacts() {
490
- const nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "", 10);
457
+ if (res.status === 429)
458
+ throw new CompanionError(
459
+ "Too many attempts from this network. Wait a few minutes and paste the setup line again."
460
+ );
461
+ if (res.status === 404 && input.deviceToken)
462
+ throw new CompanionError(
463
+ "The setup code or saved device credential is unavailable. If this device was removed, run cabane logout, then take a fresh setup line from your workspace\u2019s Settings \u2192 Devices."
464
+ );
465
+ if (res.status === 404 || res.status === 410)
466
+ throw new CompanionError(
467
+ "That setup code is unavailable. Take a fresh setup line from your workspace\u2019s Settings \u2192 Devices."
468
+ );
469
+ if (!res.ok) throw new ApiError(res.status, `Setup failed (${res.status}).`);
470
+ const body = await res.json();
471
+ if (!body || typeof body !== "object" || !("baseUrl" in body) || typeof body.baseUrl !== "string" || !("deviceId" in body) || typeof body.deviceId !== "string")
472
+ throw new CompanionError("Cabane returned an incomplete setup response.");
473
+ const token = "deviceToken" in body && typeof body.deviceToken === "string" ? body.deviceToken : input.deviceToken;
474
+ if (!token) throw new CompanionError("Cabane returned no device credential.");
491
475
  return {
492
- platform: process.platform,
493
- arch: process.arch,
494
- ...Number.isInteger(nodeMajor) ? { nodeMajor } : {}
476
+ baseUrl: body.baseUrl,
477
+ deviceToken: token,
478
+ deviceId: body.deviceId,
479
+ ..."deviceLabel" in body && typeof body.deviceLabel === "string" ? { deviceLabel: body.deviceLabel } : {}
495
480
  };
496
481
  }
497
- function pollOnce(baseUrl, deviceCode) {
498
- return postJson(baseUrl, "/api/device-enrollment/poll", { deviceCode });
499
- }
500
- var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
501
- async function runDeviceFlow(baseUrl, print, opts = {}) {
502
- if (opts.signal?.aborted) throw new EnrollmentCancelledError();
503
- const code = await requestEnrollmentCode(baseUrl, {
504
- ...opts.label ? { label: opts.label } : {}
505
- });
506
- if (opts.onCode) await opts.onCode(code);
507
- print("");
508
- print(` Enter this code at ${code.verificationUri}`);
509
- print("");
510
- print(` ${code.userCode}`);
511
- print("");
512
- print(` or open: ${code.verificationUriComplete}`);
513
- print("");
514
- print("Waiting for you to confirm it in Cabane\u2026");
515
- return awaitEnrollment(baseUrl, code, opts);
516
- }
517
- async function awaitEnrollment(baseUrl, code, opts = {}) {
518
- const now = opts.now ?? (() => Date.now());
519
- const wait = opts.sleepMs ?? sleep;
520
- const deadline = now() + code.expiresIn * 1e3;
521
- let intervalMs = Math.max(1, code.interval) * 1e3;
522
- while (now() < deadline) {
523
- await wait(intervalMs);
524
- if (opts.signal?.aborted) throw new EnrollmentCancelledError();
525
- const res = await pollOnce(baseUrl, code.deviceCode);
526
- if (res.status === "complete") {
527
- if (!res.deviceToken || !res.baseUrl) {
528
- throw new CompanionError("the server reported the pairing complete but returned no token.");
529
- }
530
- return {
531
- baseUrl: res.baseUrl,
532
- deviceToken: res.deviceToken,
533
- ...res.deviceId ? { deviceId: res.deviceId } : {},
534
- ...res.deviceLabel ? { deviceLabel: res.deviceLabel } : {},
535
- ...res.ownerName ? { ownerName: res.ownerName } : {}
536
- };
537
- }
538
- if (res.status === "expired") throw new EnrollmentExpiredError();
539
- if (res.status === "slow_down") intervalMs += 1e3;
540
- }
541
- throw new EnrollmentExpiredError();
542
- }
543
482
 
544
483
  // src/pairing-config.ts
545
484
  var DEFAULT_BASE_URL = "https://app.cabane.ai";
@@ -574,9 +513,8 @@ function isDevicePaired() {
574
513
  export {
575
514
  ApiError,
576
515
  CompanionError,
516
+ claimSetupCode,
577
517
  isDevicePaired,
578
- requestEnrollmentCode,
579
518
  resolvePairBaseUrl,
580
- runDeviceFlow,
581
519
  writePairedConfig
582
520
  };
package/dist/runtime.js CHANGED
@@ -14270,6 +14270,7 @@ var CompanionSupervisor = class {
14270
14270
  async sendHeartbeat() {
14271
14271
  if (!this.deviceApi) return;
14272
14272
  try {
14273
+ if (!this.detection || advertisedRuntimes(this.detection).length === 0) await this.detect();
14273
14274
  const store = loadSecretStoreTolerant(
14274
14275
  (m) => this.log.warn({ err: m }, "Could not read the secrets file. Check ~/.cabane/secrets.json.")
14275
14276
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabane/companion",
3
- "version": "0.6.172",
3
+ "version": "0.6.173",
4
4
  "type": "module",
5
5
  "description": "The Cabane Companion (headless): connect an agent on your machine to your Cabane workspace as a responder — drive work against your own codebase, files, and MCP servers without putting any of it in Cabane.",
6
6
  "license": "UNLICENSED",