@cabane/companion 0.6.171 → 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.
- package/dist/cli.js +6178 -6407
- package/dist/pairing-config.js +38 -100
- package/dist/runtime.js +3 -2
- package/package.json +1 -1
package/dist/pairing-config.js
CHANGED
|
@@ -432,114 +432,53 @@ function saveConfig(cfg) {
|
|
|
432
432
|
}
|
|
433
433
|
|
|
434
434
|
// src/enrollment.ts
|
|
435
|
-
|
|
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(`${
|
|
438
|
+
res = await fetch(`${baseUrl.replace(/\/$/, "")}/api/device-enrollment/claim`, {
|
|
454
439
|
method: "POST",
|
|
455
|
-
headers: {
|
|
456
|
-
|
|
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
|
|
452
|
+
} catch {
|
|
459
453
|
throw new CompanionError(
|
|
460
|
-
`
|
|
454
|
+
`Couldn't reach Cabane at ${baseUrl}. Check the server URL and your connection.`
|
|
461
455
|
);
|
|
462
456
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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
|
@@ -3487,9 +3487,9 @@ var TURN_CONTROL_TOOL_DESCRIPTIONS = {
|
|
|
3487
3487
|
- Recorded when you call this; delivered when your turn ends, or after 3 minutes if it is still running. Ask as soon as you know. The answer reaches you on a later turn.
|
|
3488
3488
|
- The person sends one response; unanswered questions end as Unanswered. A person can dismiss an ask; you aren't told and nothing wakes you. Check an ask's status before raising it again.
|
|
3489
3489
|
- To replace a stale ask (yours or anyone's here), pass \`replaces: { askId, reason }\`: both land in one write and the person sees the reason. To withdraw without a replacement, use \`withdraw_ask\`.`,
|
|
3490
|
-
cancel_wake: `Stand down
|
|
3490
|
+
cancel_wake: `Stand down the wake you have armed in this conversation, now, when you call it. No arguments. Use it when what the wake was to check has already happened or the work it watched is over; an unneeded wake fires into a turn with nothing to do. Your turn context prints the armed wake ("Wake armed: \u2026"); one no longer printed has fired or been dismissed. Nothing armed is a clean no-op. Calls act in order: \`cancel_wake\` then \`wake_me\` leaves the new wake armed. It reaches only your own wake here, never a peer's and never a recurring schedule (\`schedules.*\`).`,
|
|
3491
3491
|
skip_turn: `End your current turn WITHOUT posting a reply. Call this when you've been woken to read a message that genuinely doesn't need a response from you \u2014 a thanks/aside, a question already answered, chatter outside your lane, or something another participant clearly owns. Your turn ends silently: no message bubble is posted. The \`reason\` is a short free-text note for telemetry (e.g. "already answered by cabane", "thanks, nothing to add"). Prefer this over posting a low-value "ok!"/"got it" reply. Don't also write a reply when you skip \u2014 skipping IS the whole turn.`,
|
|
3492
|
-
wake_me: `Wake yourself later
|
|
3492
|
+
wake_me: `Wake yourself later: end this turn now and be re-dispatched at a time you pick, with a note to yourself. Use it to wait for a condition you can name (a PR merging, a session limit resetting, an answer landing): arm, end the turn, and you're woken to CHECK and either act or re-arm. Most wakes are short, seconds to a couple of hours. Reach past a few hours only when a person asked for that timing or the wait is pinned to a real external event (a report that runs Mondays); if no one asked and you can't name both what clears the wait and why it takes that long, don't arm it: finish now, or raise an \`ask\`. Pass EXACTLY ONE of \`afterSeconds\` (\`300\` for five minutes) or \`at\` (an ISO-8601 timestamp WITH a zone, e.g. \`2026-07-16T09:00:00-07:00\`, which YOU compute; nothing parses natural-language time). \`note\` becomes the body of the message that re-dispatches you: write the condition to re-check. The wake arms NOW, when you call this; a later \`wake_me\` in the same turn replaces it, \`cancel_wake\` stands it down, and one that comes due mid-turn is delivered after your reply. This is the ONLY way to schedule your own continuation; never a host cron or scheduler. Floor 60s, horizon 14 days, enforced here so you can recompute. Widen the interval as a loop ages (5m \u2192 15m \u2192 1h\u2026); after many re-arms with no other activity you'll be steered to an \`ask\` instead.`,
|
|
3493
3493
|
withdraw_ask: `Withdraw an open ask \u2014 yours or anyone's in this conversation \u2014 because the question is moot, was wrong, or has been overtaken. \`reason\` is required and is shown to the person and, if it wasn't your ask, to the agent who asked it. If there is a current question to put in its place, don't call this \u2014 call \`ask\` with \`replaces\` so the two land as one and the person never sees neither or both. Never withdraw to hurry an answer; to act on a deadline, arm a \`wake_me\` and decide then.`
|
|
3494
3494
|
};
|
|
3495
3495
|
var TURN_CONTROL_FIELD_DESCRIPTIONS = {
|
|
@@ -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.
|
|
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",
|