starbridge 0.1.0 → 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.
- package/README.md +32 -6
- package/dist/starbridge.js +873 -228
- package/package.json +1 -1
package/dist/starbridge.js
CHANGED
|
@@ -4056,7 +4056,7 @@ var require_png = __commonJS(function(exports) {
|
|
|
4056
4056
|
});
|
|
4057
4057
|
|
|
4058
4058
|
// src/cli.ts
|
|
4059
|
-
import { readFileSync as
|
|
4059
|
+
import { readFileSync as readFileSync15 } from "node:fs";
|
|
4060
4060
|
import { parseArgs } from "node:util";
|
|
4061
4061
|
|
|
4062
4062
|
// ../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/util.js
|
|
@@ -9651,7 +9651,7 @@ function duration2(params) {
|
|
|
9651
9651
|
}
|
|
9652
9652
|
// ../packages/protocol/src/client.ts
|
|
9653
9653
|
var CLIENT_HEADER = "starbridge-client";
|
|
9654
|
-
var CLIENT_NAMES = ["cli", "android", "web", "mod"];
|
|
9654
|
+
var CLIENT_NAMES = ["cli", "android", "web", "mod", "desktop"];
|
|
9655
9655
|
function clientHeader(name, version) {
|
|
9656
9656
|
return `${name}/${version}`;
|
|
9657
9657
|
}
|
|
@@ -13901,30 +13901,65 @@ function activeMembers(dir, role) {
|
|
|
13901
13901
|
return [...dir.members.values()].filter((e) => e.active && e.member.role === role).map((e) => e.member);
|
|
13902
13902
|
}
|
|
13903
13903
|
// ../packages/protocol/src/heads.ts
|
|
13904
|
+
function revocationOf(entries, id) {
|
|
13905
|
+
for (const raw of entries) {
|
|
13906
|
+
const env = SignedEnvelope.safeParse(raw);
|
|
13907
|
+
if (!env.success)
|
|
13908
|
+
continue;
|
|
13909
|
+
const body = JSON.parse(env.data.body);
|
|
13910
|
+
if (body.op === "revoke" && body.id === id)
|
|
13911
|
+
return { id, by: env.data.signer, at: body.at ?? "" };
|
|
13912
|
+
}
|
|
13913
|
+
return;
|
|
13914
|
+
}
|
|
13915
|
+
function removed(dir, entries, id) {
|
|
13916
|
+
if (dir.members.get(id)?.active !== false)
|
|
13917
|
+
return false;
|
|
13918
|
+
let revokedAt = -1;
|
|
13919
|
+
let recoveredAt = -1;
|
|
13920
|
+
entries.forEach((raw, i) => {
|
|
13921
|
+
const env = SignedEnvelope.safeParse(raw);
|
|
13922
|
+
if (!env.success)
|
|
13923
|
+
return;
|
|
13924
|
+
const body = JSON.parse(env.data.body);
|
|
13925
|
+
if (body.op === "revoke" && body.id === id)
|
|
13926
|
+
revokedAt = i;
|
|
13927
|
+
if (body.op === "recover")
|
|
13928
|
+
recoveredAt = i;
|
|
13929
|
+
});
|
|
13930
|
+
return recoveredAt > revokedAt;
|
|
13931
|
+
}
|
|
13904
13932
|
function noteHead(heads, signer, head, entries, dir) {
|
|
13905
13933
|
if (!head)
|
|
13906
13934
|
return false;
|
|
13907
13935
|
const by = head.by && head.by !== signer ? head.by : undefined;
|
|
13936
|
+
if (by && dir?.members.get(by)?.active === false)
|
|
13937
|
+
return false;
|
|
13908
13938
|
const key = !by ? signer : dir && !dir.members.has(by) ? `${signer}/?` : `${signer}/${by}`;
|
|
13909
13939
|
const known = heads[key];
|
|
13910
|
-
const dropped = !!known?.by && dir
|
|
13940
|
+
const dropped = !!known?.by && !!dir && removed(dir, entries, known.by);
|
|
13911
13941
|
const replace = !known || dropped || head.length > known.length || holdsHead(entries, known) && !holdsHead(entries, head);
|
|
13912
13942
|
if (replace)
|
|
13913
13943
|
heads[key] = head;
|
|
13914
13944
|
return replace;
|
|
13915
13945
|
}
|
|
13916
13946
|
function withheldBy(heads, dir, entries) {
|
|
13947
|
+
let fork;
|
|
13917
13948
|
for (const [key, head] of Object.entries(heads)) {
|
|
13918
13949
|
const id = key.split("/")[0];
|
|
13919
13950
|
const by = head.by && head.by !== id ? head.by : undefined;
|
|
13920
|
-
if (!dir.members.
|
|
13951
|
+
if (!dir.members.has(id) || removed(dir, entries, id))
|
|
13921
13952
|
continue;
|
|
13922
|
-
if (by && dir
|
|
13953
|
+
if (by && removed(dir, entries, by))
|
|
13923
13954
|
continue;
|
|
13924
|
-
if (
|
|
13955
|
+
if (holdsHead(entries, head))
|
|
13956
|
+
continue;
|
|
13957
|
+
const revoked = (dir.members.get(id)?.active ? undefined : revocationOf(entries, id)) ?? (by && dir.members.get(by)?.active === false ? revocationOf(entries, by) : undefined);
|
|
13958
|
+
if (!revoked)
|
|
13925
13959
|
return { id, ...by ? { by } : {}, head };
|
|
13960
|
+
fork ??= { id, ...by ? { by } : {}, head, revoked };
|
|
13926
13961
|
}
|
|
13927
|
-
return;
|
|
13962
|
+
return fork;
|
|
13928
13963
|
}
|
|
13929
13964
|
// ../packages/protocol/src/images.ts
|
|
13930
13965
|
var label = concat(utf8("starbridge/v1/image"), new Uint8Array([0]));
|
|
@@ -13966,7 +14001,19 @@ function formatPairingCode(code) {
|
|
|
13966
14001
|
return (code.rendezvous + code.secret).match(/.{4}/g)?.join("-") ?? "";
|
|
13967
14002
|
}
|
|
13968
14003
|
function pairingLink(server, code) {
|
|
13969
|
-
|
|
14004
|
+
let end = server.length;
|
|
14005
|
+
while (end > 0 && server[end - 1] === "/")
|
|
14006
|
+
end--;
|
|
14007
|
+
return `${server.slice(0, end)}/pair#${formatPairingCode(code)}`;
|
|
14008
|
+
}
|
|
14009
|
+
function appPairingLink(server, code, checkKey) {
|
|
14010
|
+
let end = server.length;
|
|
14011
|
+
while (end > 0 && server[end - 1] === "/")
|
|
14012
|
+
end--;
|
|
14013
|
+
return `starbridge://pair?server=${encodeURIComponent(server.slice(0, end))}&k=${checkKey}#${formatPairingCode(code)}`;
|
|
14014
|
+
}
|
|
14015
|
+
function newCheckKey() {
|
|
14016
|
+
return encodeCrockford(libsodium_wrappers_default.randombytes_buf(10));
|
|
13970
14017
|
}
|
|
13971
14018
|
function newClaimSecret() {
|
|
13972
14019
|
return toB64(libsodium_wrappers_default.randombytes_buf(32));
|
|
@@ -13993,7 +14040,8 @@ var PairingApprovalBody = object({
|
|
|
13993
14040
|
account: Id,
|
|
13994
14041
|
length: number2().int().positive(),
|
|
13995
14042
|
head: B64,
|
|
13996
|
-
approver: Id
|
|
14043
|
+
approver: Id,
|
|
14044
|
+
check: B64.optional()
|
|
13997
14045
|
});
|
|
13998
14046
|
var PairingMessage = object({ body: string2(), mac: B64 });
|
|
13999
14047
|
function macMessage(kind, body) {
|
|
@@ -14035,6 +14083,33 @@ function checkJoined(dir, me) {
|
|
|
14035
14083
|
if (!entry?.active || entry.member.role !== me.role || entry.member.boxPk !== me.boxPk || entry.member.signPk !== me.signPk)
|
|
14036
14084
|
throw new ProtocolError("unknown-member", "the directory does not hold my keys");
|
|
14037
14085
|
}
|
|
14086
|
+
function checkInput(entries, id) {
|
|
14087
|
+
const nul = new Uint8Array([0]);
|
|
14088
|
+
for (const e of entries) {
|
|
14089
|
+
const { body, sig } = e ?? {};
|
|
14090
|
+
if (typeof body !== "string" || typeof sig !== "string")
|
|
14091
|
+
continue;
|
|
14092
|
+
const parsed = JSON.parse(body);
|
|
14093
|
+
if (parsed.op !== "add" || parsed.member?.id !== id)
|
|
14094
|
+
continue;
|
|
14095
|
+
return concat(utf8("starbridge/v1/check"), nul, utf8(body), nul, utf8(sig));
|
|
14096
|
+
}
|
|
14097
|
+
throw new ProtocolError("unknown-member", `no add entry for ${id}`);
|
|
14098
|
+
}
|
|
14099
|
+
function checkCode(entries, id) {
|
|
14100
|
+
const hash = libsodium_wrappers_default.crypto_generichash(32, checkInput(entries, id), null);
|
|
14101
|
+
return encodeCrockford(hash.subarray(0, 10)).match(/.{4}/g).join("-");
|
|
14102
|
+
}
|
|
14103
|
+
function checkKeyBytes(key) {
|
|
14104
|
+
return libsodium_wrappers_default.crypto_generichash(32, concat(utf8("starbridge/v1/check-key"), new Uint8Array([0]), utf8(key)), null);
|
|
14105
|
+
}
|
|
14106
|
+
function verifyCheckProof(entries, id, key, proof) {
|
|
14107
|
+
try {
|
|
14108
|
+
return libsodium_wrappers_default.crypto_auth_verify(fromB64(proof), checkInput(entries, id), checkKeyBytes(key));
|
|
14109
|
+
} catch {
|
|
14110
|
+
return false;
|
|
14111
|
+
}
|
|
14112
|
+
}
|
|
14038
14113
|
|
|
14039
14114
|
// ../packages/protocol/src/join.ts
|
|
14040
14115
|
var JoinRequestBody = object({
|
|
@@ -14171,6 +14246,17 @@ function checkPermissionAnswer(asked, answer, device, now = Date.now()) {
|
|
|
14171
14246
|
if (now > Date.parse(asked.expiresAt))
|
|
14172
14247
|
throw new ProtocolError("expired");
|
|
14173
14248
|
}
|
|
14249
|
+
// ../packages/protocol/src/presence.ts
|
|
14250
|
+
var PRESENCE_INPUT_MS = 60000;
|
|
14251
|
+
var PRESENCE_BEAT_MS = 30000;
|
|
14252
|
+
var Presence = object({ present: boolean2() });
|
|
14253
|
+
var PUSH_HOLD_MAX = 300;
|
|
14254
|
+
var AccountSettings = object({
|
|
14255
|
+
pushHold: number2().int().min(0).max(PUSH_HOLD_MAX)
|
|
14256
|
+
});
|
|
14257
|
+
function isPresent(screen) {
|
|
14258
|
+
return !screen.locked && screen.idleMs >= 0 && screen.idleMs < PRESENCE_INPUT_MS;
|
|
14259
|
+
}
|
|
14174
14260
|
// src/agent/client.ts
|
|
14175
14261
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
14176
14262
|
import { existsSync as existsSync2 } from "node:fs";
|
|
@@ -14273,7 +14359,7 @@ function processAlive(pid) {
|
|
|
14273
14359
|
// package.json
|
|
14274
14360
|
var package_default = {
|
|
14275
14361
|
name: "starbridge",
|
|
14276
|
-
version: "0.1.
|
|
14362
|
+
version: "0.1.2",
|
|
14277
14363
|
description: "Post decisions to your Starbridge devices and upload quota windows from CodexBar",
|
|
14278
14364
|
license: "MIT",
|
|
14279
14365
|
repository: {
|
|
@@ -14344,8 +14430,9 @@ function configDir(env) {
|
|
|
14344
14430
|
}
|
|
14345
14431
|
var PROMPTS_OPEN = "permissions-open";
|
|
14346
14432
|
var CLI_PATH = "cli-path";
|
|
14433
|
+
var PICKER_MAX_MS = 86400000;
|
|
14347
14434
|
function promptsMark(s, now = Date.now()) {
|
|
14348
|
-
const open = Object.values(s.permissions ?? {}).some((p) => !p.settled && Date.parse(p.permission.expiresAt) > now);
|
|
14435
|
+
const open = Object.values(s.permissions ?? {}).some((p) => !p.settled && Date.parse(p.permission.expiresAt) > now) || Object.entries(s.asked).some(([id, a]) => a.picker && !a.settled && !s.answers[id] && Date.parse(a.askedAt) > now - PICKER_MAX_MS);
|
|
14349
14436
|
return open ? "open" : "";
|
|
14350
14437
|
}
|
|
14351
14438
|
var STATE_VERSION = 1;
|
|
@@ -14800,6 +14887,9 @@ class Api {
|
|
|
14800
14887
|
const r = await this.call("GET", "/quota/senders", { signal: AbortSignal.timeout(15000) });
|
|
14801
14888
|
return r.json.senders;
|
|
14802
14889
|
}
|
|
14890
|
+
async presence(present, signal) {
|
|
14891
|
+
await this.call("PUT", "/presence", { body: { present }, ...signal ? { signal } : {} });
|
|
14892
|
+
}
|
|
14803
14893
|
async postItem(item, signal) {
|
|
14804
14894
|
await this.call("POST", "/items", { body: item, ...signal ? { signal } : {} });
|
|
14805
14895
|
}
|
|
@@ -14865,20 +14955,12 @@ function signedHead(dir) {
|
|
|
14865
14955
|
return { length: dir.length, head: dir.head };
|
|
14866
14956
|
}
|
|
14867
14957
|
function withheld(st, dir, entries) {
|
|
14868
|
-
const
|
|
14869
|
-
|
|
14870
|
-
|
|
14871
|
-
|
|
14872
|
-
|
|
14873
|
-
|
|
14874
|
-
return `${id} signed a chain this machine does not hold (${head.length}), and this machine's chain revokes it: the server may be serving a fork. If you revoked ${id} because it was compromised, run \`starbridge pair --force\``;
|
|
14875
|
-
return;
|
|
14876
|
-
}
|
|
14877
|
-
function revokes(entries, id) {
|
|
14878
|
-
return entries.some((e) => {
|
|
14879
|
-
const body = JSON.parse(e.body);
|
|
14880
|
-
return body.op === "revoke" && body.id === id;
|
|
14881
|
-
});
|
|
14958
|
+
const held = withheldBy(st.heads ?? {}, dir, entries);
|
|
14959
|
+
if (!held)
|
|
14960
|
+
return;
|
|
14961
|
+
if (held.revoked)
|
|
14962
|
+
return `${held.id} signed a chain this machine does not hold (${held.head.length}), and this machine's chain revokes it: the server may be serving a fork. If you revoked ${held.id} because it was compromised, run \`starbridge pair --force\``;
|
|
14963
|
+
return `the server is holding back directory entries ${held.id} has seen (${held.head.length}, this machine has ${dir.length})`;
|
|
14882
14964
|
}
|
|
14883
14965
|
function devices(ctx, dir) {
|
|
14884
14966
|
const behind = withheld(ctx.store.state(), dir, ctx.store.directory().slice(0, dir.length));
|
|
@@ -15621,6 +15703,7 @@ function prune(st, now) {
|
|
|
15621
15703
|
if (Date.parse(p.permission.expiresAt) + KEEP_MS < now)
|
|
15622
15704
|
delete st.permissions?.[id];
|
|
15623
15705
|
}
|
|
15706
|
+
var LATE_REPORT_MS = 5000;
|
|
15624
15707
|
async function postPermission(ctx, s, hook, opts, signal) {
|
|
15625
15708
|
const dir = await refreshDirectory(ctx, s, signal);
|
|
15626
15709
|
const to = devices(ctx, dir);
|
|
@@ -15648,7 +15731,9 @@ async function postPermission(ctx, s, hook, opts, signal) {
|
|
|
15648
15731
|
await s.api.postItem(item, signal);
|
|
15649
15732
|
} catch (e) {
|
|
15650
15733
|
const keyboard = signal?.aborted && signal.reason?.name !== "TimeoutError";
|
|
15651
|
-
markSettled(ctx, permission.id, keyboard ? "keyboard" : "timeout");
|
|
15734
|
+
const how = markSettled(ctx, permission.id, keyboard ? "keyboard" : "timeout");
|
|
15735
|
+
if (how)
|
|
15736
|
+
await postSettled(ctx, s, permission.id, how, AbortSignal.timeout(LATE_REPORT_MS)).catch(() => {});
|
|
15652
15737
|
throw e;
|
|
15653
15738
|
}
|
|
15654
15739
|
return permission.id;
|
|
@@ -15871,7 +15956,8 @@ async function postDecision(ctx, s, input) {
|
|
|
15871
15956
|
...decision.done ? { done: true } : {},
|
|
15872
15957
|
...input.codex && decision.source.session ? { codex: input.codex } : {},
|
|
15873
15958
|
...input.extensionAnswers && decision.source.session ? { extensionAnswers: true } : {},
|
|
15874
|
-
...input.held ? { held: true } : {}
|
|
15959
|
+
...input.held ? { held: true } : {},
|
|
15960
|
+
...input.picker ? { picker: input.picker } : {}
|
|
15875
15961
|
};
|
|
15876
15962
|
});
|
|
15877
15963
|
if (input.waiting)
|
|
@@ -16860,12 +16946,12 @@ import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, rmSync as rmSyn
|
|
|
16860
16946
|
import { dirname as dirname2, join as join7 } from "node:path";
|
|
16861
16947
|
|
|
16862
16948
|
// ../plugin/hooks/rule.md
|
|
16863
|
-
var rule_default = "I am often away from this terminal and may not read your final message for hours. Reach me through the `starbridge` skill, not here
|
|
16949
|
+
var rule_default = "I am often away from this terminal and may not read your final message for hours. Reach me through the `starbridge` skill, not here, when you need a decision that is mine, and before you end a turn on work that waits on me, such as a PR to review or merge, or a failure only I can fix. In Claude Code, a quick question may use AskUserQuestion instead, since it reaches my devices too. Decide everything else yourself and keep working. Ask here only when `starbridge` fails.\n\nRun a command that blocks me (takes over the screen, keyboard or session, or holds a device I use), or that my instructions ask you to report, whole through `starbridge run`.\n";
|
|
16864
16950
|
|
|
16865
16951
|
// ../plugin/skills/starbridge/SKILL.md
|
|
16866
16952
|
var SKILL_default = `---
|
|
16867
16953
|
name: starbridge
|
|
16868
|
-
description: "Reach your user through Starbridge: a card on their phone that they answer with one tap, sent back into this session. Use it, instead of asking in chat
|
|
16954
|
+
description: "Reach your user through Starbridge: a card on their phone that they answer with one tap, sent back into this session. Use it, instead of asking in chat, whenever you need a decision that is theirs, and before ending a turn on work that waits on them (a PR to review or merge, a failure only they can fix). In Claude Code, a quick question may use AskUserQuestion, which reaches their devices too. Wrap in \`starbridge run\`, unasked, any command that blocks them or that their instructions ask you to report."
|
|
16869
16955
|
compatibility: The \`starbridge\` CLI on PATH, paired with \`starbridge pair\`.
|
|
16870
16956
|
---
|
|
16871
16957
|
|
|
@@ -16888,8 +16974,10 @@ their phone; they read it cold, between other things, and tap an option.
|
|
|
16888
16974
|
report: wrap it in \`starbridge run\` (below).
|
|
16889
16975
|
|
|
16890
16976
|
Decide everything else yourself and say what you did in your final message.
|
|
16891
|
-
Ask one question in one place
|
|
16892
|
-
\`AskUserQuestion
|
|
16977
|
+
Ask one question in one place, never also in the terminal. In Claude Code,
|
|
16978
|
+
\`AskUserQuestion\` reaches their devices too, so a quick question may use it;
|
|
16979
|
+
post a card for a decision they will read cold, or that needs links or
|
|
16980
|
+
images. Ask in the terminal only when \`starbridge\` fails (not
|
|
16893
16981
|
installed, not paired, an error), and say that it failed. A card cannot
|
|
16894
16982
|
approve an action your guidelines say needs the user's yes in this chat
|
|
16895
16983
|
(account settings, external messages, purchases, deleting data), since its
|
|
@@ -16993,6 +17081,11 @@ starbridge run --title 'Mac e2e' --reason 'takes over your screen and keyboard'
|
|
|
16993
17081
|
|
|
16994
17082
|
Wrap the whole command, chained or not. \`--reason\` is required: what it
|
|
16995
17083
|
blocks, or the rule it matches. Output and exit code are the command's own.
|
|
17084
|
+
|
|
17085
|
+
In Codex, ask to run \`starbridge run\` outside the sandbox on the first call
|
|
17086
|
+
(escalated permissions, justified by the network the report needs). In the
|
|
17087
|
+
sandbox the command runs but no device hears of it, and it runs again once
|
|
17088
|
+
the escalation is approved.
|
|
16996
17089
|
`;
|
|
16997
17090
|
|
|
16998
17091
|
// src/setup/marker.ts
|
|
@@ -17027,7 +17120,7 @@ import { configDir } from "./poller.ts";
|
|
|
17027
17120
|
|
|
17028
17121
|
/** The agent API revision this mod speaks, sent in the \`starbridge-api\` header. */
|
|
17029
17122
|
export const API = 1;
|
|
17030
|
-
export const VERSION = "0.1.
|
|
17123
|
+
export const VERSION = "0.1.2";
|
|
17031
17124
|
const CLIENT = \`starbridge-mod/\${VERSION}\`;
|
|
17032
17125
|
|
|
17033
17126
|
/** Event types this mod submits; others are skipped and left unconfirmed, as the API asks. */
|
|
@@ -17817,6 +17910,19 @@ var switch_default = `/**
|
|
|
17817
17910
|
import type { AgentLoop } from "./agent.ts";
|
|
17818
17911
|
import type { Poller } from "./poller.ts";
|
|
17819
17912
|
|
|
17913
|
+
/** Entry points of a run that ends with its last turn: \`claude -p\` and Agent SDK scripts. */
|
|
17914
|
+
const ONE_SHOT = new Set(["sdk-cli", "sdk-ts", "sdk-py"]);
|
|
17915
|
+
|
|
17916
|
+
/**
|
|
17917
|
+
* Whether a session gets an answer loop: one under the REPL, or one a host such as Claude
|
|
17918
|
+
* desktop's Code tab or an IDE keeps open for a person (#863). The engine calls those not
|
|
17919
|
+
* interactive, as it does a \`claude -p\` run, so the host's \`CLAUDE_CODE_ENTRYPOINT\` tells them
|
|
17920
|
+
* apart; a run with none set is \`claude -p\`'s own.
|
|
17921
|
+
*/
|
|
17922
|
+
export function wantsLoop(isInteractive: boolean, entrypoint: string | undefined): boolean {
|
|
17923
|
+
return isInteractive || (!!entrypoint && !ONE_SHOT.has(entrypoint));
|
|
17924
|
+
}
|
|
17925
|
+
|
|
17820
17926
|
export interface Paths {
|
|
17821
17927
|
/** True when an agent answers \`GET /v1/status\` with a 2xx. */
|
|
17822
17928
|
agentUp(): Promise<boolean>;
|
|
@@ -18524,6 +18630,7 @@ function run(sys, cmd, args, opts = {}) {
|
|
|
18524
18630
|
const child = spawn3(start.file, start.args, {
|
|
18525
18631
|
windowsVerbatimArguments: start.windowsVerbatimArguments,
|
|
18526
18632
|
env: { ...sys.ctx.env, ...opts.env },
|
|
18633
|
+
...opts.cwd ? { cwd: opts.cwd } : {},
|
|
18527
18634
|
stdio: [
|
|
18528
18635
|
opts.input === undefined ? "ignore" : "pipe",
|
|
18529
18636
|
opts.inherit ? "inherit" : "pipe",
|
|
@@ -18649,6 +18756,7 @@ var PI_PACKAGE = piSource(VERSION);
|
|
|
18649
18756
|
function hasPi(sys) {
|
|
18650
18757
|
return which(sys.ctx.env, "pi") !== undefined;
|
|
18651
18758
|
}
|
|
18759
|
+
var STARBRIDGE_PI = /github\.com[/:]T0mSIlver\/starbridge(?:\.git)?\/?(?:@.*)?$/i;
|
|
18652
18760
|
function piPackage(sys) {
|
|
18653
18761
|
const dir = sys.ctx.env.PI_CODING_AGENT_DIR || join7(sys.home, ".pi", "agent");
|
|
18654
18762
|
let packages;
|
|
@@ -18661,7 +18769,7 @@ function piPackage(sys) {
|
|
|
18661
18769
|
return;
|
|
18662
18770
|
for (const p of packages) {
|
|
18663
18771
|
const source = typeof p === "string" ? p : p?.source;
|
|
18664
|
-
if (typeof source === "string" &&
|
|
18772
|
+
if (typeof source === "string" && STARBRIDGE_PI.test(source))
|
|
18665
18773
|
return source;
|
|
18666
18774
|
}
|
|
18667
18775
|
return;
|
|
@@ -19561,16 +19669,234 @@ class Permissions {
|
|
|
19561
19669
|
}
|
|
19562
19670
|
}
|
|
19563
19671
|
|
|
19672
|
+
// src/screen.ts
|
|
19673
|
+
import { execFile, spawn as spawn4 } from "node:child_process";
|
|
19674
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
19675
|
+
var TIMEOUT_MS = 5000;
|
|
19676
|
+
var exec = (cmd, args) => new Promise((resolve) => {
|
|
19677
|
+
execFile(cmd, args, { timeout: TIMEOUT_MS, windowsHide: true }, (err, stdout) => resolve(err ? undefined : String(stdout)));
|
|
19678
|
+
});
|
|
19679
|
+
function macIdleMs(ioreg) {
|
|
19680
|
+
const m = /"HIDIdleTime"\s*=\s*(\d+)/.exec(ioreg);
|
|
19681
|
+
return m ? Number(m[1]) / 1e6 : undefined;
|
|
19682
|
+
}
|
|
19683
|
+
function macLocked(ioreg) {
|
|
19684
|
+
return /"CGSSessionScreenIsLocked"\s*=\s*Yes/.test(ioreg);
|
|
19685
|
+
}
|
|
19686
|
+
async function readMac(run) {
|
|
19687
|
+
const [hid, root] = await Promise.all([
|
|
19688
|
+
run("ioreg", ["-c", "IOHIDSystem", "-d", "4", "-r", "-k", "HIDIdleTime"]),
|
|
19689
|
+
run("ioreg", ["-n", "Root", "-d1"])
|
|
19690
|
+
]);
|
|
19691
|
+
const idleMs = hid === undefined ? undefined : macIdleMs(hid);
|
|
19692
|
+
if (idleMs === undefined || root === undefined)
|
|
19693
|
+
return;
|
|
19694
|
+
return { locked: macLocked(root), idleMs };
|
|
19695
|
+
}
|
|
19696
|
+
function properties(text) {
|
|
19697
|
+
const out = {};
|
|
19698
|
+
for (const line of text.split(`
|
|
19699
|
+
`)) {
|
|
19700
|
+
const i = line.indexOf("=");
|
|
19701
|
+
if (i > 0)
|
|
19702
|
+
out[line.slice(0, i)] = line.slice(i + 1).trim();
|
|
19703
|
+
}
|
|
19704
|
+
return out;
|
|
19705
|
+
}
|
|
19706
|
+
function graphical(sessions) {
|
|
19707
|
+
return sessions.map(properties).filter((p) => p.Active === "yes" && (p.Type === "x11" || p.Type === "wayland") && p.Class === "user").map((p) => ({ locked: p.LockedHint === "yes" }));
|
|
19708
|
+
}
|
|
19709
|
+
function linuxIdleMs(text) {
|
|
19710
|
+
const m = /^\(?(?:uint64\s+)?(\d+),?\)?\s*$/.exec(text.trim());
|
|
19711
|
+
return m ? Number(m[1]) : undefined;
|
|
19712
|
+
}
|
|
19713
|
+
async function readLinux(run) {
|
|
19714
|
+
const list = await run("loginctl", ["list-sessions", "--no-legend", "--no-pager"]);
|
|
19715
|
+
const ids = (list ?? "").split(`
|
|
19716
|
+
`).map((l) => l.trim().split(/\s+/)[0]).filter((id) => !!id);
|
|
19717
|
+
const shown = await Promise.all(ids.map((id) => run("loginctl", [
|
|
19718
|
+
"show-session",
|
|
19719
|
+
id,
|
|
19720
|
+
"-p",
|
|
19721
|
+
"Type",
|
|
19722
|
+
"-p",
|
|
19723
|
+
"Active",
|
|
19724
|
+
"-p",
|
|
19725
|
+
"Class",
|
|
19726
|
+
"-p",
|
|
19727
|
+
"LockedHint"
|
|
19728
|
+
])));
|
|
19729
|
+
const screens = graphical(shown.filter((s) => s !== undefined));
|
|
19730
|
+
if (screens.length === 0)
|
|
19731
|
+
return;
|
|
19732
|
+
const idle = await run("gdbus", [
|
|
19733
|
+
"call",
|
|
19734
|
+
"--session",
|
|
19735
|
+
"--dest",
|
|
19736
|
+
"org.gnome.Mutter.IdleMonitor",
|
|
19737
|
+
"--object-path",
|
|
19738
|
+
"/org/gnome/Mutter/IdleMonitor/Core",
|
|
19739
|
+
"--method",
|
|
19740
|
+
"org.gnome.Mutter.IdleMonitor.GetIdletime"
|
|
19741
|
+
]) ?? await run("xprintidle", []);
|
|
19742
|
+
const idleMs = idle === undefined ? undefined : linuxIdleMs(idle);
|
|
19743
|
+
if (idleMs === undefined)
|
|
19744
|
+
return;
|
|
19745
|
+
return { locked: screens.every((s) => s.locked), idleMs };
|
|
19746
|
+
}
|
|
19747
|
+
var WINDOWS_SCRIPT = `
|
|
19748
|
+
Add-Type @'
|
|
19749
|
+
using System; using System.Runtime.InteropServices;
|
|
19750
|
+
public static class Idle {
|
|
19751
|
+
[StructLayout(LayoutKind.Sequential)] struct LASTINPUTINFO { public uint cbSize; public uint dwTime; }
|
|
19752
|
+
[DllImport("user32.dll")] static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
|
|
19753
|
+
public static long Ms() {
|
|
19754
|
+
var i = new LASTINPUTINFO(); i.cbSize = (uint)Marshal.SizeOf(i);
|
|
19755
|
+
if (!GetLastInputInfo(ref i)) return -1;
|
|
19756
|
+
return (long)unchecked((uint)Environment.TickCount - i.dwTime);
|
|
19757
|
+
}
|
|
19758
|
+
}
|
|
19759
|
+
'@
|
|
19760
|
+
$session = (Get-Process -Id $PID).SessionId
|
|
19761
|
+
while ($null -ne [Console]::In.ReadLine()) {
|
|
19762
|
+
$locked = @(Get-Process LogonUI -ErrorAction SilentlyContinue | Where-Object { $_.SessionId -eq $session }).Count -gt 0
|
|
19763
|
+
[Console]::Out.WriteLine("$([Idle]::Ms()) $locked")
|
|
19764
|
+
}
|
|
19765
|
+
`;
|
|
19766
|
+
function windowsScreen(line) {
|
|
19767
|
+
const m = /^(-?\d+) (True|False)$/.exec(line.trim());
|
|
19768
|
+
if (!m || Number(m[1]) < 0)
|
|
19769
|
+
return;
|
|
19770
|
+
return { idleMs: Number(m[1]), locked: m[2] === "True" };
|
|
19771
|
+
}
|
|
19772
|
+
|
|
19773
|
+
class WindowsReader {
|
|
19774
|
+
child;
|
|
19775
|
+
pending = [];
|
|
19776
|
+
read() {
|
|
19777
|
+
const child = this.start();
|
|
19778
|
+
return new Promise((resolve) => {
|
|
19779
|
+
const timer = setTimeout(() => done(undefined), TIMEOUT_MS);
|
|
19780
|
+
const done = (line) => {
|
|
19781
|
+
clearTimeout(timer);
|
|
19782
|
+
resolve(line === undefined ? undefined : windowsScreen(line));
|
|
19783
|
+
};
|
|
19784
|
+
this.pending.push(done);
|
|
19785
|
+
child.stdin?.write(`
|
|
19786
|
+
`);
|
|
19787
|
+
});
|
|
19788
|
+
}
|
|
19789
|
+
start() {
|
|
19790
|
+
if (this.child && this.child.exitCode === null)
|
|
19791
|
+
return this.child;
|
|
19792
|
+
const child = spawn4("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", "-"], { stdio: ["pipe", "pipe", "ignore"], windowsHide: true });
|
|
19793
|
+
child.stdin?.write(`${WINDOWS_SCRIPT}
|
|
19794
|
+
`);
|
|
19795
|
+
createInterface2({ input: child.stdout }).on("line", (line) => this.pending.shift()?.(line));
|
|
19796
|
+
const gone = () => {
|
|
19797
|
+
if (this.child === child)
|
|
19798
|
+
this.child = undefined;
|
|
19799
|
+
for (const p of this.pending.splice(0))
|
|
19800
|
+
p(undefined);
|
|
19801
|
+
};
|
|
19802
|
+
child.on("exit", gone);
|
|
19803
|
+
child.on("error", gone);
|
|
19804
|
+
this.child = child;
|
|
19805
|
+
return child;
|
|
19806
|
+
}
|
|
19807
|
+
stop() {
|
|
19808
|
+
this.child?.kill();
|
|
19809
|
+
this.child = undefined;
|
|
19810
|
+
}
|
|
19811
|
+
}
|
|
19812
|
+
function screenReader(platform = process.platform, run = exec) {
|
|
19813
|
+
if (platform === "darwin")
|
|
19814
|
+
return { read: () => readMac(run), stop: () => {} };
|
|
19815
|
+
if (platform === "linux")
|
|
19816
|
+
return { read: () => readLinux(run), stop: () => {} };
|
|
19817
|
+
if (platform === "win32") {
|
|
19818
|
+
const reader = new WindowsReader;
|
|
19819
|
+
return { read: () => reader.read(), stop: () => reader.stop() };
|
|
19820
|
+
}
|
|
19821
|
+
return { read: async () => {
|
|
19822
|
+
return;
|
|
19823
|
+
}, stop: () => {} };
|
|
19824
|
+
}
|
|
19825
|
+
|
|
19826
|
+
// src/agent/presence.ts
|
|
19827
|
+
var READ_MS = 1e4;
|
|
19828
|
+
function presenceEnabled(hub) {
|
|
19829
|
+
return hub.ctx.store.agentConfig().presence?.enabled === true;
|
|
19830
|
+
}
|
|
19831
|
+
|
|
19832
|
+
class Presence2 {
|
|
19833
|
+
hub;
|
|
19834
|
+
reader;
|
|
19835
|
+
sent;
|
|
19836
|
+
lastError;
|
|
19837
|
+
constructor(hub, reader = screenReader()) {
|
|
19838
|
+
this.hub = hub;
|
|
19839
|
+
this.reader = reader;
|
|
19840
|
+
}
|
|
19841
|
+
status(into) {
|
|
19842
|
+
into.presence = {
|
|
19843
|
+
enabled: presenceEnabled(this.hub),
|
|
19844
|
+
present: this.sent?.present === true,
|
|
19845
|
+
...this.lastError ? { lastError: this.lastError } : {}
|
|
19846
|
+
};
|
|
19847
|
+
}
|
|
19848
|
+
async send(present, signal) {
|
|
19849
|
+
const ctx = this.hub.ctx;
|
|
19850
|
+
if (!ctx.store.machine())
|
|
19851
|
+
return;
|
|
19852
|
+
try {
|
|
19853
|
+
await session(ctx).api.presence(present, signal ?? AbortSignal.timeout(1e4));
|
|
19854
|
+
this.sent = { present, at: Date.now() };
|
|
19855
|
+
this.lastError = undefined;
|
|
19856
|
+
} catch (e) {
|
|
19857
|
+
this.lastError = e.message;
|
|
19858
|
+
}
|
|
19859
|
+
}
|
|
19860
|
+
async tick(now = Date.now()) {
|
|
19861
|
+
const screen = presenceEnabled(this.hub) ? await this.reader.read() : undefined;
|
|
19862
|
+
const present = screen !== undefined && isPresent(screen);
|
|
19863
|
+
if (present) {
|
|
19864
|
+
if (!this.sent?.present || now - this.sent.at >= PRESENCE_BEAT_MS)
|
|
19865
|
+
await this.send(true);
|
|
19866
|
+
} else if (this.sent?.present)
|
|
19867
|
+
await this.send(false);
|
|
19868
|
+
}
|
|
19869
|
+
async run(signal) {
|
|
19870
|
+
while (!signal.aborted) {
|
|
19871
|
+
await this.tick();
|
|
19872
|
+
await pause(READ_MS, signal);
|
|
19873
|
+
}
|
|
19874
|
+
if (this.sent?.present)
|
|
19875
|
+
await this.send(false, AbortSignal.timeout(3000));
|
|
19876
|
+
this.reader.stop();
|
|
19877
|
+
}
|
|
19878
|
+
}
|
|
19879
|
+
|
|
19564
19880
|
// src/quota.ts
|
|
19565
19881
|
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
19566
19882
|
|
|
19567
19883
|
// src/codexbar.ts
|
|
19568
|
-
import { spawn as
|
|
19884
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
19569
19885
|
var RUN_TIMEOUT_MS = 120000;
|
|
19570
19886
|
function runCodexbar(bin, provider, timeoutMs = RUN_TIMEOUT_MS) {
|
|
19571
19887
|
const args = ["usage", "--format", "json", ...provider ? ["--provider", provider] : []];
|
|
19572
19888
|
return new Promise((resolve) => {
|
|
19573
|
-
|
|
19889
|
+
let start;
|
|
19890
|
+
try {
|
|
19891
|
+
start = spawnable(bin, args);
|
|
19892
|
+
} catch (e) {
|
|
19893
|
+
return resolve({ code: null, stdout: "", stderr: e.message });
|
|
19894
|
+
}
|
|
19895
|
+
const child = spawn5(start.file, start.args, {
|
|
19896
|
+
windowsVerbatimArguments: start.windowsVerbatimArguments,
|
|
19897
|
+
windowsHide: true,
|
|
19898
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
19899
|
+
});
|
|
19574
19900
|
let stdout = "";
|
|
19575
19901
|
let stderr = "";
|
|
19576
19902
|
child.stdout.on("data", (d) => {
|
|
@@ -19579,7 +19905,7 @@ function runCodexbar(bin, provider, timeoutMs = RUN_TIMEOUT_MS) {
|
|
|
19579
19905
|
child.stderr.on("data", (d) => {
|
|
19580
19906
|
stderr += d;
|
|
19581
19907
|
});
|
|
19582
|
-
const timer = setTimeout(() => child
|
|
19908
|
+
const timer = setTimeout(() => killTree(child), timeoutMs);
|
|
19583
19909
|
child.on("error", (e) => {
|
|
19584
19910
|
clearTimeout(timer);
|
|
19585
19911
|
resolve({ code: null, stdout, stderr: e.message });
|
|
@@ -19599,6 +19925,11 @@ function isoOrNull(x) {
|
|
|
19599
19925
|
const t = Date.parse(x);
|
|
19600
19926
|
return Number.isNaN(t) ? null : `${new Date(t).toISOString().slice(0, 19)}Z`;
|
|
19601
19927
|
}
|
|
19928
|
+
function named(label, minutes) {
|
|
19929
|
+
if (label !== "Session" || minutes === null || minutes >= 24 * 60 || minutes % 60 !== 0)
|
|
19930
|
+
return label;
|
|
19931
|
+
return `${minutes / 60}-hour`;
|
|
19932
|
+
}
|
|
19602
19933
|
function window2(id, label, raw, now) {
|
|
19603
19934
|
if (!isObj2(raw))
|
|
19604
19935
|
return;
|
|
@@ -19606,11 +19937,12 @@ function window2(id, label, raw, now) {
|
|
|
19606
19937
|
if (typeof used !== "number" || !Number.isFinite(used) || used < 0)
|
|
19607
19938
|
return;
|
|
19608
19939
|
const minutes = raw.windowMinutes;
|
|
19940
|
+
const windowMinutes = typeof minutes === "number" && Number.isFinite(minutes) && minutes >= 1 ? Math.round(minutes) : null;
|
|
19609
19941
|
const w = {
|
|
19610
19942
|
id: clip(id, 100),
|
|
19611
|
-
label: clip(label, 100),
|
|
19943
|
+
label: clip(named(label, windowMinutes), 100),
|
|
19612
19944
|
usedPercent: used,
|
|
19613
|
-
windowMinutes
|
|
19945
|
+
windowMinutes,
|
|
19614
19946
|
resetsAt: isoOrNull(raw.resetsAt)
|
|
19615
19947
|
};
|
|
19616
19948
|
return { ...w, pace: computePace(w, now) };
|
|
@@ -19935,7 +20267,7 @@ class Quota {
|
|
|
19935
20267
|
}
|
|
19936
20268
|
|
|
19937
20269
|
// src/run.ts
|
|
19938
|
-
import { spawn as
|
|
20270
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
19939
20271
|
import { randomBytes as randomBytes7 } from "node:crypto";
|
|
19940
20272
|
import { constants as constants2 } from "node:os";
|
|
19941
20273
|
var PROGRESS_MS = 1e4;
|
|
@@ -20039,6 +20371,8 @@ class Reporter {
|
|
|
20039
20371
|
ctx;
|
|
20040
20372
|
timing;
|
|
20041
20373
|
progress;
|
|
20374
|
+
progressSent = false;
|
|
20375
|
+
dirty = false;
|
|
20042
20376
|
exit;
|
|
20043
20377
|
lastPost = Number.NEGATIVE_INFINITY;
|
|
20044
20378
|
inflight;
|
|
@@ -20060,7 +20394,8 @@ class Reporter {
|
|
|
20060
20394
|
if (JSON.stringify(next) === JSON.stringify(this.progress))
|
|
20061
20395
|
return;
|
|
20062
20396
|
this.progress = next;
|
|
20063
|
-
this.
|
|
20397
|
+
this.dirty = true;
|
|
20398
|
+
this.schedule(this.progressGap());
|
|
20064
20399
|
}
|
|
20065
20400
|
async finish(code) {
|
|
20066
20401
|
this.exit = { code, at: iso2(this.ctx.now()) };
|
|
@@ -20074,6 +20409,9 @@ class Reporter {
|
|
|
20074
20409
|
await Promise.race([settled, new Promise((r) => t = setTimeout(r, FINAL_MS))]);
|
|
20075
20410
|
clearTimeout(t);
|
|
20076
20411
|
}
|
|
20412
|
+
progressGap() {
|
|
20413
|
+
return this.progressSent ? this.timing.progressMs : 0;
|
|
20414
|
+
}
|
|
20077
20415
|
schedule(gap) {
|
|
20078
20416
|
if (this.off || this.exit)
|
|
20079
20417
|
return;
|
|
@@ -20091,11 +20429,14 @@ class Reporter {
|
|
|
20091
20429
|
}
|
|
20092
20430
|
const input = {
|
|
20093
20431
|
...this.base,
|
|
20094
|
-
at:
|
|
20432
|
+
at: this.ctx.now().toISOString(),
|
|
20095
20433
|
...this.progress ? { progress: this.progress } : {},
|
|
20096
20434
|
...this.exit ? { exit: this.exit } : {}
|
|
20097
20435
|
};
|
|
20098
20436
|
this.lastPost = this.ctx.now().getTime();
|
|
20437
|
+
if (this.progress)
|
|
20438
|
+
this.progressSent = true;
|
|
20439
|
+
this.dirty = false;
|
|
20099
20440
|
this.inflight = this.post(input).catch((e) => {
|
|
20100
20441
|
if (e instanceof UsageError && !(e instanceof WithheldError))
|
|
20101
20442
|
this.off = true;
|
|
@@ -20108,7 +20449,7 @@ class Reporter {
|
|
|
20108
20449
|
this.queued = false;
|
|
20109
20450
|
this.send();
|
|
20110
20451
|
} else
|
|
20111
|
-
this.schedule(this.timing.heartbeatMs);
|
|
20452
|
+
this.schedule(this.dirty ? this.progressGap() : this.timing.heartbeatMs);
|
|
20112
20453
|
});
|
|
20113
20454
|
}
|
|
20114
20455
|
}
|
|
@@ -20156,7 +20497,7 @@ async function runCommand(ctx, opts) {
|
|
|
20156
20497
|
}, opts.post ?? makePoster(ctx), ctx);
|
|
20157
20498
|
const stdout = opts.stdout ?? process.stdout;
|
|
20158
20499
|
const stderr = opts.stderr ?? process.stderr;
|
|
20159
|
-
const child =
|
|
20500
|
+
const child = spawn6(start.file, start.args, {
|
|
20160
20501
|
stdio: ["inherit", "pipe", "pipe"],
|
|
20161
20502
|
windowsVerbatimArguments: start.windowsVerbatimArguments
|
|
20162
20503
|
});
|
|
@@ -20218,7 +20559,13 @@ function makeAgent(ctx, opts = {}) {
|
|
|
20218
20559
|
const quota = { providers, intervalMs, ...codexbar ? { codexbar } : {} };
|
|
20219
20560
|
const agent = new Agent2(ctx, socket, (hub) => {
|
|
20220
20561
|
const q = new Quota(hub, quota);
|
|
20221
|
-
return [
|
|
20562
|
+
return [
|
|
20563
|
+
new Decisions(hub, (why) => q.now(why)),
|
|
20564
|
+
q,
|
|
20565
|
+
new Permissions(hub),
|
|
20566
|
+
new Runs(hub),
|
|
20567
|
+
new Presence2(hub)
|
|
20568
|
+
];
|
|
20222
20569
|
});
|
|
20223
20570
|
return agent;
|
|
20224
20571
|
}
|
|
@@ -20260,6 +20607,109 @@ async function runAgent(ctx, opts) {
|
|
|
20260
20607
|
|
|
20261
20608
|
// src/hook.ts
|
|
20262
20609
|
import { basename as basename3 } from "node:path";
|
|
20610
|
+
|
|
20611
|
+
// src/ran.ts
|
|
20612
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
20613
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync9 } from "node:fs";
|
|
20614
|
+
var RUN_CHECK_MS = 500;
|
|
20615
|
+
var SHELLS = new Set(["sh", "dash", "bash", "zsh", "ash", "ksh", "fish", "busybox"]);
|
|
20616
|
+
function evalForm(command) {
|
|
20617
|
+
return `eval '${command.replaceAll("'", `'"'"'`)}'`;
|
|
20618
|
+
}
|
|
20619
|
+
function program(p) {
|
|
20620
|
+
return (p.argv[0] ?? "").split(/\s/)[0]?.split("/").pop() ?? "";
|
|
20621
|
+
}
|
|
20622
|
+
function runs(p, command) {
|
|
20623
|
+
const quoted = evalForm(command);
|
|
20624
|
+
return SHELLS.has(program(p)) && p.argv.some((a) => a.includes(quoted));
|
|
20625
|
+
}
|
|
20626
|
+
function descends(pid, root, byPid) {
|
|
20627
|
+
const seen = new Set;
|
|
20628
|
+
for (let p = byPid.get(pid);p && !seen.has(p.pid); p = byPid.get(p.ppid)) {
|
|
20629
|
+
if (p.ppid === root)
|
|
20630
|
+
return true;
|
|
20631
|
+
seen.add(p.pid);
|
|
20632
|
+
}
|
|
20633
|
+
return false;
|
|
20634
|
+
}
|
|
20635
|
+
function procLinux() {
|
|
20636
|
+
const out = [];
|
|
20637
|
+
for (const name of readdirSync3("/proc")) {
|
|
20638
|
+
const pid = Number(name);
|
|
20639
|
+
if (!Number.isInteger(pid))
|
|
20640
|
+
continue;
|
|
20641
|
+
try {
|
|
20642
|
+
const stat = readFileSync9(`/proc/${pid}/stat`, "utf8");
|
|
20643
|
+
const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
|
|
20644
|
+
const argv = readFileSync9(`/proc/${pid}/cmdline`, "utf8").split("\x00").filter(Boolean);
|
|
20645
|
+
out.push({ pid, ppid, argv });
|
|
20646
|
+
} catch {}
|
|
20647
|
+
}
|
|
20648
|
+
return out;
|
|
20649
|
+
}
|
|
20650
|
+
function procPs() {
|
|
20651
|
+
return new Promise((resolve) => execFile2("ps", ["-A", "-ww", "-o", "pid=,ppid=,args="], { maxBuffer: 16 * 1024 * 1024, timeout: 5000 }, (e, stdout) => {
|
|
20652
|
+
if (e)
|
|
20653
|
+
return resolve([]);
|
|
20654
|
+
resolve(stdout.split(`
|
|
20655
|
+
`).flatMap((line) => {
|
|
20656
|
+
const m = /^\s*(\d+)\s+(\d+)\s(.*)$/.exec(line);
|
|
20657
|
+
return m ? [{ pid: Number(m[1]), ppid: Number(m[2]), argv: [m[3]] }] : [];
|
|
20658
|
+
}));
|
|
20659
|
+
}));
|
|
20660
|
+
}
|
|
20661
|
+
async function processes(platform = process.platform) {
|
|
20662
|
+
if (platform === "linux")
|
|
20663
|
+
return procLinux();
|
|
20664
|
+
if (platform === "win32")
|
|
20665
|
+
return [];
|
|
20666
|
+
return procPs();
|
|
20667
|
+
}
|
|
20668
|
+
function agentPid(procs, from = process.ppid) {
|
|
20669
|
+
const byPid = new Map(procs.map((p) => [p.pid, p]));
|
|
20670
|
+
for (let pid = from, hops = 0;hops < 4; hops++) {
|
|
20671
|
+
const p = byPid.get(pid);
|
|
20672
|
+
if (!p)
|
|
20673
|
+
return;
|
|
20674
|
+
if (!SHELLS.has(program(p)))
|
|
20675
|
+
return pid;
|
|
20676
|
+
pid = p.ppid;
|
|
20677
|
+
}
|
|
20678
|
+
return;
|
|
20679
|
+
}
|
|
20680
|
+
function untilRan(signal, command, list = processes, from = process.ppid) {
|
|
20681
|
+
const ran = new AbortController;
|
|
20682
|
+
let timer;
|
|
20683
|
+
let stopped = false;
|
|
20684
|
+
const watch = async () => {
|
|
20685
|
+
const first = await list().catch(() => []);
|
|
20686
|
+
const root = agentPid(first, from);
|
|
20687
|
+
if (root === undefined)
|
|
20688
|
+
return;
|
|
20689
|
+
const before = new Set(first.map((p) => p.pid));
|
|
20690
|
+
const tick = async () => {
|
|
20691
|
+
if (stopped)
|
|
20692
|
+
return;
|
|
20693
|
+
const now = await list().catch(() => []);
|
|
20694
|
+
const byPid = new Map(now.map((p) => [p.pid, p]));
|
|
20695
|
+
if (now.some((p) => !before.has(p.pid) && runs(p, command) && descends(p.pid, root, byPid)))
|
|
20696
|
+
return ran.abort();
|
|
20697
|
+
timer = setTimeout(tick, RUN_CHECK_MS);
|
|
20698
|
+
timer.unref?.();
|
|
20699
|
+
};
|
|
20700
|
+
await tick();
|
|
20701
|
+
};
|
|
20702
|
+
watch();
|
|
20703
|
+
return {
|
|
20704
|
+
signal: signal ? AbortSignal.any([signal, ran.signal]) : ran.signal,
|
|
20705
|
+
stop: () => {
|
|
20706
|
+
stopped = true;
|
|
20707
|
+
clearTimeout(timer);
|
|
20708
|
+
}
|
|
20709
|
+
};
|
|
20710
|
+
}
|
|
20711
|
+
|
|
20712
|
+
// src/hook.ts
|
|
20263
20713
|
var SLACK_MS2 = 15000;
|
|
20264
20714
|
var DIRECT_POLL_SECONDS = 5;
|
|
20265
20715
|
var REPORT_MS = 5000;
|
|
@@ -20288,14 +20738,22 @@ function untilOrphaned(signal, ppid = () => process.ppid) {
|
|
|
20288
20738
|
stop: () => clearInterval(timer)
|
|
20289
20739
|
};
|
|
20290
20740
|
}
|
|
20291
|
-
async function hookPermission(outer, stdin, opts) {
|
|
20741
|
+
async function hookPermission(outer, stdin, opts, ran = untilRan) {
|
|
20292
20742
|
const watch = untilOrphaned(outer.signal);
|
|
20293
|
-
|
|
20743
|
+
let ctx = { ...outer, signal: watch.signal };
|
|
20744
|
+
let started;
|
|
20294
20745
|
try {
|
|
20746
|
+
const hook = parseHook(stdin);
|
|
20747
|
+
if (hook.tool_name === "AskUserQuestion" && opts.agent === "claude-code")
|
|
20748
|
+
return await racePicker(ctx, outer, hook, opts.wait);
|
|
20295
20749
|
if (!permissionsEnabled(ctx))
|
|
20296
20750
|
return 0;
|
|
20297
20751
|
const agent = agentName(opts.agent);
|
|
20298
|
-
const
|
|
20752
|
+
const command = hook.tool_input?.command;
|
|
20753
|
+
if (agent === "claude-code" && hook.tool_name === "Bash" && typeof command === "string") {
|
|
20754
|
+
started = ran(ctx.signal, command);
|
|
20755
|
+
ctx = { ...ctx, signal: started.signal };
|
|
20756
|
+
}
|
|
20299
20757
|
const waitMs = opts.wait ? parseDuration(opts.wait) : DEFAULT_WAIT_MS;
|
|
20300
20758
|
const deadline = ctx.now().getTime() + waitMs;
|
|
20301
20759
|
const source = permissionSource(hook, ctx.env);
|
|
@@ -20305,6 +20763,7 @@ async function hookPermission(outer, stdin, opts) {
|
|
|
20305
20763
|
} catch (e) {
|
|
20306
20764
|
ctx.err(`starbridge: permission prompt not sent: ${e.message}`);
|
|
20307
20765
|
} finally {
|
|
20766
|
+
started?.stop();
|
|
20308
20767
|
watch.stop();
|
|
20309
20768
|
}
|
|
20310
20769
|
return 0;
|
|
@@ -20406,7 +20865,11 @@ async function hookSettle(ctx, stdin, opts) {
|
|
|
20406
20865
|
const st = ctx.store.state();
|
|
20407
20866
|
if (ctx.store.promptsMarkStale(st))
|
|
20408
20867
|
ctx.store.updateState(() => {});
|
|
20409
|
-
if (!sessionId
|
|
20868
|
+
if (!sessionId)
|
|
20869
|
+
return 0;
|
|
20870
|
+
if (hook.tool_name === "AskUserQuestion" || hook.tool_input === undefined)
|
|
20871
|
+
await settlePicker(ctx, sessionId, "elsewhere");
|
|
20872
|
+
if (waitingFor(ctx.store.state(), sessionId).length === 0)
|
|
20410
20873
|
return 0;
|
|
20411
20874
|
const inputHash = hook.tool_input !== undefined ? inputHashOf(session(ctx).keys, hook.tool_input) : undefined;
|
|
20412
20875
|
if (waitingFor(ctx.store.state(), sessionId, inputHash).length === 0)
|
|
@@ -20425,41 +20888,77 @@ async function hookSettle(ctx, stdin, opts) {
|
|
|
20425
20888
|
}
|
|
20426
20889
|
return 0;
|
|
20427
20890
|
}
|
|
20428
|
-
|
|
20429
|
-
|
|
20430
|
-
|
|
20431
|
-
|
|
20432
|
-
|
|
20433
|
-
|
|
20434
|
-
|
|
20435
|
-
|
|
20436
|
-
|
|
20437
|
-
|
|
20438
|
-
|
|
20439
|
-
|
|
20440
|
-
|
|
20891
|
+
function pickerQuestions(input) {
|
|
20892
|
+
const questions = input?.questions;
|
|
20893
|
+
if (!Array.isArray(questions) || questions.length === 0 || !questions.every((q) => typeof q?.question === "string"))
|
|
20894
|
+
return;
|
|
20895
|
+
return questions;
|
|
20896
|
+
}
|
|
20897
|
+
function pickerOpen(st, session) {
|
|
20898
|
+
return Object.entries(st.asked).filter(([id, a]) => a.picker === session && !a.settled && !st.answers[id]).map(([id]) => id);
|
|
20899
|
+
}
|
|
20900
|
+
async function settlePicker(ctx, session, outcome) {
|
|
20901
|
+
await Promise.all(pickerOpen(ctx.store.state(), session).map((id) => settle2({ ...ctx, signal: undefined }, { id, outcome }).catch((e) => ctx.err(`starbridge: could not settle ${id}: ${e.message}`))));
|
|
20902
|
+
}
|
|
20903
|
+
async function racePicker(ctx, outer, hook, waitText) {
|
|
20904
|
+
const questions = pickerQuestions(hook.tool_input);
|
|
20905
|
+
const sessionId = typeof hook.session_id === "string" ? hook.session_id : "";
|
|
20906
|
+
if (!questions || !sessionId)
|
|
20907
|
+
return 0;
|
|
20908
|
+
const s = session(ctx);
|
|
20909
|
+
const waitMs = waitText ? parseDuration(waitText) : DEFAULT_WAIT_MS;
|
|
20910
|
+
const cwd = typeof hook.cwd === "string" && hook.cwd ? hook.cwd : process.cwd();
|
|
20911
|
+
const deadline = AbortSignal.timeout(waitMs);
|
|
20912
|
+
const failed = new AbortController;
|
|
20913
|
+
const signal = AbortSignal.any([
|
|
20914
|
+
ctx.signal ?? new AbortController().signal,
|
|
20915
|
+
deadline,
|
|
20916
|
+
failed.signal
|
|
20917
|
+
]);
|
|
20918
|
+
const results = await Promise.allSettled(questions.map(async (q) => {
|
|
20919
|
+
const sub = { ...ctx, signal, err: () => {} };
|
|
20920
|
+
const input = {
|
|
20921
|
+
...questionInput({ ...q, multiple: q.multiSelect }),
|
|
20922
|
+
agent: "claude-code",
|
|
20923
|
+
session: sessionId,
|
|
20924
|
+
project: basename3(cwd),
|
|
20925
|
+
held: true,
|
|
20926
|
+
picker: sessionId,
|
|
20927
|
+
waiting: true
|
|
20928
|
+
};
|
|
20929
|
+
let id;
|
|
20930
|
+
try {
|
|
20931
|
+
({ id } = await postDecision(sub, s, resolveSource(input, ctx.env, cwd)));
|
|
20932
|
+
} catch (e) {
|
|
20933
|
+
failed.abort();
|
|
20934
|
+
throw e;
|
|
20935
|
+
}
|
|
20936
|
+
while (true) {
|
|
20937
|
+
const lines = [];
|
|
20938
|
+
const code = await wait({ ...sub, out: (l) => lines.push(l) }, { id, json: true, "no-mark": true }, s);
|
|
20939
|
+
if (code === 0 && lines[0])
|
|
20940
|
+
return opencodeAnswer(JSON.parse(lines[0]), { ...q, multiple: q.multiSelect });
|
|
20941
|
+
if (code !== EXIT_SNOOZED)
|
|
20942
|
+
return;
|
|
20943
|
+
}
|
|
20944
|
+
}));
|
|
20945
|
+
const picked = results.map((r) => r.status === "fulfilled" ? r.value : undefined);
|
|
20946
|
+
const error = results.find((r) => r.status === "rejected");
|
|
20947
|
+
if (error && !signal.aborted)
|
|
20948
|
+
ctx.err(`starbridge: question not sent: ${error.reason.message}`);
|
|
20949
|
+
if (picked.every((p) => p !== undefined) && !signal.aborted) {
|
|
20950
|
+
const answers = Object.fromEntries(questions.map((q, i) => [q.question, picked[i].join(", ")]));
|
|
20951
|
+
const decision = {
|
|
20952
|
+
behavior: "allow",
|
|
20953
|
+
updatedInput: { ...hook.tool_input, answers }
|
|
20954
|
+
};
|
|
20955
|
+
ctx.out(JSON.stringify({ hookSpecificOutput: { hookEventName: "PermissionRequest", decision } }));
|
|
20441
20956
|
return 0;
|
|
20442
20957
|
}
|
|
20443
|
-
|
|
20444
|
-
|
|
20445
|
-
|
|
20446
|
-
|
|
20447
|
-
if (Array.isArray(questions) && questions.length > 0 && questions.every((q) => typeof q?.question === "string"))
|
|
20448
|
-
input = parsed.tool_input;
|
|
20449
|
-
} catch {}
|
|
20450
|
-
const hookSpecificOutput = input ? {
|
|
20451
|
-
hookEventName: "PreToolUse",
|
|
20452
|
-
permissionDecision: "allow",
|
|
20453
|
-
updatedInput: {
|
|
20454
|
-
...input,
|
|
20455
|
-
answers: Object.fromEntries(input.questions.map((q) => [q.question, ASK_USER_REASON]))
|
|
20456
|
-
}
|
|
20457
|
-
} : {
|
|
20458
|
-
hookEventName: "PreToolUse",
|
|
20459
|
-
permissionDecision: "deny",
|
|
20460
|
-
permissionDecisionReason: ASK_USER_REASON
|
|
20461
|
-
};
|
|
20462
|
-
ctx.out(JSON.stringify({ hookSpecificOutput }));
|
|
20958
|
+
await settlePicker(ctx, sessionId, outer.signal?.aborted ? "elsewhere" : "withdrawn");
|
|
20959
|
+
return 0;
|
|
20960
|
+
}
|
|
20961
|
+
function hookAskUser() {
|
|
20463
20962
|
return 0;
|
|
20464
20963
|
}
|
|
20465
20964
|
var QUESTION_CHARS = 300;
|
|
@@ -20545,7 +21044,10 @@ async function hookQuestion(outer, stdin, opts) {
|
|
|
20545
21044
|
|
|
20546
21045
|
// src/pair.ts
|
|
20547
21046
|
import { randomBytes as randomBytes8 } from "node:crypto";
|
|
21047
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync10, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "node:fs";
|
|
20548
21048
|
import { hostname } from "node:os";
|
|
21049
|
+
import { join as join8 } from "node:path";
|
|
21050
|
+
import { createInterface as createInterface3 } from "node:readline";
|
|
20549
21051
|
|
|
20550
21052
|
// ../node_modules/.pnpm/uqr@0.1.3/node_modules/uqr/dist/index.mjs
|
|
20551
21053
|
var QrCodeDataType = /* @__PURE__ */ ((QrCodeDataType2) => {
|
|
@@ -21148,7 +21650,7 @@ function terminalQr(text) {
|
|
|
21148
21650
|
|
|
21149
21651
|
// src/settings.ts
|
|
21150
21652
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
21151
|
-
import { existsSync as existsSync4, readdirSync as
|
|
21653
|
+
import { existsSync as existsSync4, readdirSync as readdirSync4 } from "node:fs";
|
|
21152
21654
|
function detectMachineKind(env, platform = process.platform) {
|
|
21153
21655
|
if (env.CLAUDE_CODE_REMOTE === "true" || env.CODESPACES === "true" || env.GITPOD_WORKSPACE_ID)
|
|
21154
21656
|
return "cloud";
|
|
@@ -21162,7 +21664,7 @@ function hasBattery(platform) {
|
|
|
21162
21664
|
try {
|
|
21163
21665
|
if (platform === "linux") {
|
|
21164
21666
|
const dir = "/sys/class/power_supply";
|
|
21165
|
-
return existsSync4(dir) &&
|
|
21667
|
+
return existsSync4(dir) && readdirSync4(dir).some((n) => n.startsWith("BAT"));
|
|
21166
21668
|
}
|
|
21167
21669
|
if (platform === "darwin")
|
|
21168
21670
|
return spawnSync2("pmset", ["-g", "batt"], { encoding: "utf8" }).stdout.includes("InternalBattery");
|
|
@@ -21230,7 +21732,7 @@ async function offerPiAllow(ctx, prompt, quiet = false) {
|
|
|
21230
21732
|
} else
|
|
21231
21733
|
ctx.out(`Pi: pi-permission-system will ask before each \`starbridge ask\`; to allow them later, ${how}.`);
|
|
21232
21734
|
}
|
|
21233
|
-
var USAGE = "usage: starbridge config [permissions on|off] [machine-kind server|desktop|laptop|cloud]";
|
|
21735
|
+
var USAGE = "usage: starbridge config [permissions on|off] [presence on|off] [machine-kind server|desktop|laptop|cloud]";
|
|
21234
21736
|
async function configCommand(ctx, args, prompt) {
|
|
21235
21737
|
const [key, value, ...extra] = args;
|
|
21236
21738
|
if (extra.length > 0 || key !== undefined && value === undefined)
|
|
@@ -21243,6 +21745,11 @@ async function configCommand(ctx, args, prompt) {
|
|
|
21243
21745
|
await offerPiChain(ctx, prompt);
|
|
21244
21746
|
await offerPiAllow(ctx, prompt, true);
|
|
21245
21747
|
}
|
|
21748
|
+
} else if (key === "presence") {
|
|
21749
|
+
if (value !== "on" && value !== "off")
|
|
21750
|
+
throw new UsageError(USAGE);
|
|
21751
|
+
const config = ctx.store.agentConfig();
|
|
21752
|
+
ctx.store.saveAgentConfig({ ...config, presence: { enabled: value === "on" } });
|
|
21246
21753
|
} else if (key === "machine-kind") {
|
|
21247
21754
|
const kind = MachineKind.safeParse(value);
|
|
21248
21755
|
if (!kind.success)
|
|
@@ -21252,6 +21759,7 @@ async function configCommand(ctx, args, prompt) {
|
|
|
21252
21759
|
throw new UsageError(USAGE);
|
|
21253
21760
|
}
|
|
21254
21761
|
ctx.out(`permissions ${permissionsEnabled(ctx) ? "on" : "off"}`);
|
|
21762
|
+
ctx.out(`presence ${ctx.store.agentConfig().presence?.enabled ? "on" : "off"}`);
|
|
21255
21763
|
ctx.out(`machine-kind ${ctx.store.agentConfig().machineKind ?? "not set"}`);
|
|
21256
21764
|
return 0;
|
|
21257
21765
|
}
|
|
@@ -21270,6 +21778,7 @@ async function pair(ctx, opts) {
|
|
|
21270
21778
|
const id = `m_${randomBytes8(9).toString("base64url")}`;
|
|
21271
21779
|
const name = opts.name ?? previous?.name ?? hostname();
|
|
21272
21780
|
const claim = newClaimSecret();
|
|
21781
|
+
const checkKey = newCheckKey();
|
|
21273
21782
|
const deadline = ctx.now().getTime() + CODE_LIFETIME_MS;
|
|
21274
21783
|
const expired = () => new UsageError(`the pairing code expired; run \`${opts.again ?? "starbridge pair"}\` again`);
|
|
21275
21784
|
let code = newPairingCode();
|
|
@@ -21294,10 +21803,10 @@ async function pair(ctx, opts) {
|
|
|
21294
21803
|
}
|
|
21295
21804
|
const link = pairingLink(server, code);
|
|
21296
21805
|
ctx.out(`Pairing code: ${formatPairingCode(code)}`);
|
|
21297
|
-
ctx.out(" Scan this with your phone's camera
|
|
21298
|
-
for (const line of terminalQr(
|
|
21806
|
+
ctx.out(" Scan this with the Starbridge Android app or your phone's camera:");
|
|
21807
|
+
for (const line of terminalQr(appPairingLink(server, code, checkKey)))
|
|
21299
21808
|
ctx.out(line);
|
|
21300
|
-
ctx.out(`
|
|
21809
|
+
ctx.out(` No app? Open ${link}`);
|
|
21301
21810
|
ctx.out(" Or type the code under Devices in the app or web page. It expires in 10 minutes.");
|
|
21302
21811
|
let result;
|
|
21303
21812
|
while (!result) {
|
|
@@ -21325,6 +21834,20 @@ async function pair(ctx, opts) {
|
|
|
21325
21834
|
const pin = { length: approval.length, head: approval.head };
|
|
21326
21835
|
const dir = verifyDirectory(entries, { account: approval.account, pin });
|
|
21327
21836
|
checkJoined(dir, { id, role: "machine", ...publicKeys(keys) });
|
|
21837
|
+
const again = opts.again ?? "starbridge pair";
|
|
21838
|
+
const check = checkCode(entries, id);
|
|
21839
|
+
if (approval.check !== undefined) {
|
|
21840
|
+
if (!verifyCheckProof(entries, id, checkKey, approval.check))
|
|
21841
|
+
throw new UsageError(`the Android app that scanned the QR code saw another check code, so a server may have paired this machine into an account it controls. Nothing is saved: revoke "${name}" under Devices, then run \`${again}\` again`);
|
|
21842
|
+
} else {
|
|
21843
|
+
const answer = await confirmCheck(ctx, check, name);
|
|
21844
|
+
if (ctx.signal?.aborted)
|
|
21845
|
+
return 130;
|
|
21846
|
+
if (answer === "no")
|
|
21847
|
+
throw new UsageError(`the codes differ, so this machine is not paired. A server may have read the pairing code. Revoke "${name}" under Devices, then run \`${again}\` again`);
|
|
21848
|
+
if (answer === undefined)
|
|
21849
|
+
throw new UsageError(`the check code was not confirmed within 10 minutes, so this machine is not paired. Revoke "${name}" under Devices, then run \`${again}\` again`);
|
|
21850
|
+
}
|
|
21328
21851
|
const token = result.token;
|
|
21329
21852
|
ctx.store.locked(() => {
|
|
21330
21853
|
ctx.store.saveDirectory(entries);
|
|
@@ -21357,14 +21880,78 @@ async function pair(ctx, opts) {
|
|
|
21357
21880
|
function addedAt(entries, id) {
|
|
21358
21881
|
for (const e of entries) {
|
|
21359
21882
|
const body = JSON.parse(e.body);
|
|
21360
|
-
if (body.op === "add" && body.member?.id === id)
|
|
21361
|
-
|
|
21362
|
-
|
|
21363
|
-
|
|
21883
|
+
if (body.op === "add" && body.member?.id === id) {
|
|
21884
|
+
const at = new Date(body.at);
|
|
21885
|
+
const day = at.toLocaleDateString(undefined, { month: "short", day: "numeric" });
|
|
21886
|
+
const time = at.toLocaleTimeString(undefined, {
|
|
21364
21887
|
hour: "2-digit",
|
|
21365
21888
|
minute: "2-digit",
|
|
21366
21889
|
timeZoneName: "short"
|
|
21367
21890
|
});
|
|
21891
|
+
return `${day}, ${time}`;
|
|
21892
|
+
}
|
|
21893
|
+
}
|
|
21894
|
+
}
|
|
21895
|
+
var CONFIRM_MS = 10 * 60000;
|
|
21896
|
+
var waitingFile = (ctx) => join8(ctx.store.dir, "pair-waiting");
|
|
21897
|
+
var answerFile = (ctx, code) => join8(ctx.store.dir, `pair-answer.${code}`);
|
|
21898
|
+
function writeWhole(path, text) {
|
|
21899
|
+
const tmp = `${path}.${process.pid}`;
|
|
21900
|
+
rmSync3(tmp, { force: true });
|
|
21901
|
+
writeFileSync5(tmp, text, { mode: 384, flag: "wx" });
|
|
21902
|
+
renameSync3(tmp, path);
|
|
21903
|
+
}
|
|
21904
|
+
function sendConfirm(ctx, same) {
|
|
21905
|
+
let code;
|
|
21906
|
+
try {
|
|
21907
|
+
code = readFileSync10(waitingFile(ctx), "utf8").trim();
|
|
21908
|
+
} catch {
|
|
21909
|
+
throw new UsageError("no `starbridge pair` is waiting for its check code to be confirmed");
|
|
21910
|
+
}
|
|
21911
|
+
if (!/^[0-9A-Z]{4}(-[0-9A-Z]{4}){3}$/.test(code))
|
|
21912
|
+
throw new UsageError("no `starbridge pair` is waiting for its check code to be confirmed");
|
|
21913
|
+
writeWhole(answerFile(ctx, code), same ? "yes" : "no");
|
|
21914
|
+
ctx.out(`${same ? "Confirmed" : "Rejected"} check code ${code} for the waiting \`starbridge pair\`.`);
|
|
21915
|
+
return 0;
|
|
21916
|
+
}
|
|
21917
|
+
async function confirmCheck(ctx, code, name) {
|
|
21918
|
+
const file = answerFile(ctx, code);
|
|
21919
|
+
rmSync3(file, { force: true });
|
|
21920
|
+
mkdirSync6(ctx.store.dir, { recursive: true, mode: 448 });
|
|
21921
|
+
writeWhole(waitingFile(ctx), code);
|
|
21922
|
+
const tty = process.stdin.isTTY;
|
|
21923
|
+
ctx.out(`Check code: ${code}`);
|
|
21924
|
+
ctx.out(` The Starbridge Android app shows "${name}" under Devices with its check code. A browser shows what the server sends, so compare with the app if you have it. An app that shows no code needs updating.`);
|
|
21925
|
+
ctx.out(tty ? "Same code? [Y/n]" : " Same code? Run `starbridge pair --confirm` if so, `starbridge pair --reject` if not.");
|
|
21926
|
+
const typed = [];
|
|
21927
|
+
const shown = Date.now();
|
|
21928
|
+
const rl = tty ? createInterface3({ input: process.stdin }).on("line", (l) => {
|
|
21929
|
+
if (Date.now() - shown > 500)
|
|
21930
|
+
typed.push(l);
|
|
21931
|
+
}) : undefined;
|
|
21932
|
+
try {
|
|
21933
|
+
const until = ctx.now().getTime() + CONFIRM_MS;
|
|
21934
|
+
for (;; ) {
|
|
21935
|
+
if (ctx.signal?.aborted || ctx.now().getTime() > until)
|
|
21936
|
+
return;
|
|
21937
|
+
const line = typed.shift();
|
|
21938
|
+
if (line !== undefined)
|
|
21939
|
+
return /^\s*(y(es)?)?\s*$/i.test(line) ? "yes" : "no";
|
|
21940
|
+
try {
|
|
21941
|
+
const answer = readFileSync10(file, "utf8");
|
|
21942
|
+
rmSync3(file, { force: true });
|
|
21943
|
+
return answer === "yes" ? "yes" : "no";
|
|
21944
|
+
} catch {
|
|
21945
|
+
await ctx.sleep(500);
|
|
21946
|
+
}
|
|
21947
|
+
}
|
|
21948
|
+
} finally {
|
|
21949
|
+
rl?.close();
|
|
21950
|
+
rmSync3(file, { force: true });
|
|
21951
|
+
try {
|
|
21952
|
+
if (readFileSync10(waitingFile(ctx), "utf8") === code)
|
|
21953
|
+
rmSync3(waitingFile(ctx), { force: true });
|
|
21954
|
+
} catch {}
|
|
21368
21955
|
}
|
|
21369
21956
|
}
|
|
21370
21957
|
|
|
@@ -21484,8 +22071,8 @@ function installKind() {
|
|
|
21484
22071
|
}
|
|
21485
22072
|
|
|
21486
22073
|
// src/setup/plugins.ts
|
|
21487
|
-
import { existsSync as existsSync5, mkdirSync as
|
|
21488
|
-
import { join as
|
|
22074
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "node:fs";
|
|
22075
|
+
import { join as join9 } from "node:path";
|
|
21489
22076
|
var MARKETPLACE = "starbridge";
|
|
21490
22077
|
var MARKETPLACE_SOURCE = "T0mSIlver/starbridge";
|
|
21491
22078
|
var PLUGINS = [`starbridge@${MARKETPLACE}`, `starbridge-mod@${MARKETPLACE}`];
|
|
@@ -21493,9 +22080,9 @@ function hasClaude(sys) {
|
|
|
21493
22080
|
return which(sys.ctx.env, "claude") !== undefined;
|
|
21494
22081
|
}
|
|
21495
22082
|
function claudeDir(sys) {
|
|
21496
|
-
return sys.ctx.env.CLAUDE_CONFIG_DIR ||
|
|
22083
|
+
return sys.ctx.env.CLAUDE_CONFIG_DIR || join9(sys.home, ".claude");
|
|
21497
22084
|
}
|
|
21498
|
-
var claude = (sys, ...args) => run(sys, "claude", args, { timeoutMs: 180000 });
|
|
22085
|
+
var claude = (sys, ...args) => run(sys, "claude", args, { timeoutMs: 180000, cwd: sys.home });
|
|
21499
22086
|
async function listJson(sys, ...args) {
|
|
21500
22087
|
const r = await claude(sys, ...args, "--json");
|
|
21501
22088
|
const why = `\`claude ${args.join(" ")} --json\` failed`;
|
|
@@ -21565,7 +22152,7 @@ async function installPlugins(sys, state) {
|
|
|
21565
22152
|
throw new Error(foreign);
|
|
21566
22153
|
const done = [];
|
|
21567
22154
|
if (!state.marketplace) {
|
|
21568
|
-
const r = await claude(sys, "plugin", "marketplace", "add", MARKETPLACE_SOURCE);
|
|
22155
|
+
const r = await claude(sys, "plugin", "marketplace", "add", MARKETPLACE_SOURCE, "--scope", "user");
|
|
21569
22156
|
if (r?.code !== 0)
|
|
21570
22157
|
throw new Error(`claude plugin marketplace add ${MARKETPLACE_SOURCE}: ${failure2(r)}`);
|
|
21571
22158
|
done.push(`Added the ${MARKETPLACE} marketplace.`);
|
|
@@ -21581,6 +22168,8 @@ async function installPlugins(sys, state) {
|
|
|
21581
22168
|
return done;
|
|
21582
22169
|
}
|
|
21583
22170
|
async function removePlugins(sys, state) {
|
|
22171
|
+
if (state.foreign)
|
|
22172
|
+
return [`Left the ${MARKETPLACE} marketplace from ${state.foreign} and its plugins alone.`];
|
|
21584
22173
|
const done = [];
|
|
21585
22174
|
for (const id of PLUGINS) {
|
|
21586
22175
|
if (!state.plugins[id])
|
|
@@ -21595,17 +22184,17 @@ async function removePlugins(sys, state) {
|
|
|
21595
22184
|
return done;
|
|
21596
22185
|
}
|
|
21597
22186
|
function settingsPath(sys) {
|
|
21598
|
-
return
|
|
22187
|
+
return join9(claudeDir(sys), "settings.json");
|
|
21599
22188
|
}
|
|
21600
22189
|
function readSettings(sys) {
|
|
21601
22190
|
try {
|
|
21602
|
-
return JSON.parse(
|
|
22191
|
+
return JSON.parse(readFileSync11(settingsPath(sys), "utf8"));
|
|
21603
22192
|
} catch {
|
|
21604
22193
|
return;
|
|
21605
22194
|
}
|
|
21606
22195
|
}
|
|
21607
22196
|
function writeSettings(sys, s) {
|
|
21608
|
-
|
|
22197
|
+
writeFileSync6(settingsPath(sys), `${JSON.stringify(s, null, 2)}
|
|
21609
22198
|
`);
|
|
21610
22199
|
}
|
|
21611
22200
|
function autoUpdate(sys) {
|
|
@@ -21646,7 +22235,7 @@ function addAllowRules(sys) {
|
|
|
21646
22235
|
...s.permissions,
|
|
21647
22236
|
allow: [...allow, ...ALLOW_RULES.filter((r) => !allow.includes(r))]
|
|
21648
22237
|
};
|
|
21649
|
-
|
|
22238
|
+
mkdirSync7(claudeDir(sys), { recursive: true });
|
|
21650
22239
|
writeSettings(sys, s);
|
|
21651
22240
|
return true;
|
|
21652
22241
|
}
|
|
@@ -21858,24 +22447,39 @@ import {
|
|
|
21858
22447
|
closeSync as closeSync2,
|
|
21859
22448
|
existsSync as existsSync6,
|
|
21860
22449
|
lstatSync as lstatSync2,
|
|
21861
|
-
mkdirSync as
|
|
22450
|
+
mkdirSync as mkdirSync8,
|
|
21862
22451
|
openSync as openSync2,
|
|
21863
|
-
readdirSync as
|
|
21864
|
-
readFileSync as
|
|
22452
|
+
readdirSync as readdirSync5,
|
|
22453
|
+
readFileSync as readFileSync12,
|
|
21865
22454
|
realpathSync as realpathSync4,
|
|
21866
|
-
renameSync as
|
|
21867
|
-
rmSync as
|
|
22455
|
+
renameSync as renameSync4,
|
|
22456
|
+
rmSync as rmSync4,
|
|
21868
22457
|
symlinkSync,
|
|
21869
22458
|
unlinkSync as unlinkSync4,
|
|
21870
22459
|
writeSync
|
|
21871
22460
|
} from "node:fs";
|
|
21872
|
-
import { dirname as dirname5, join as
|
|
22461
|
+
import { dirname as dirname5, join as join10 } from "node:path";
|
|
22462
|
+
// src/setup/codexbar-pin.json
|
|
22463
|
+
var codexbar_pin_default = {
|
|
22464
|
+
version: "0.73.0",
|
|
22465
|
+
sha256: {
|
|
22466
|
+
"macos-arm64": "cd8f853779f8faec003f53d5fc2a1614aef577ec9f603cbd58a9559b54e159b9",
|
|
22467
|
+
"macos-x86_64": "19e7059de688e62be98790bc10198257f5d326e4a3f4191183c23106eb91780a",
|
|
22468
|
+
"linux-aarch64": "ae6f9be046b09dc9d17210b661c25e2ee8a0150edb047b917d9eb17c295c7094",
|
|
22469
|
+
"linux-x86_64": "a84f556c7ecebc8e606e0a34ee0d121626f72d7afc1e3adf5746410bc10f9611",
|
|
22470
|
+
"linux-musl-aarch64": "a4007dfa6577a436eaeb6b46601982c69548c6e20730b3eb9133d4266d5ea92b",
|
|
22471
|
+
"linux-musl-x86_64": "75fd9cfc00784eef8badd6e76f4f74cbdf7c13db0b7f9ca6e105ddb793e6e8f8"
|
|
22472
|
+
}
|
|
22473
|
+
};
|
|
22474
|
+
|
|
22475
|
+
// src/setup/codexbar.ts
|
|
22476
|
+
var PIN = codexbar_pin_default;
|
|
21873
22477
|
var RELEASES = "https://github.com/steipete/CodexBar/releases";
|
|
21874
22478
|
var PROBE_TIMEOUT_MS = 20000;
|
|
21875
22479
|
var PROGRESS_MS2 = 5000;
|
|
21876
22480
|
var APP_HELPERS = (home) => [
|
|
21877
22481
|
"/Applications/CodexBar.app/Contents/Helpers/CodexBarCLI",
|
|
21878
|
-
|
|
22482
|
+
join10(home, "Applications/CodexBar.app/Contents/Helpers/CodexBarCLI")
|
|
21879
22483
|
];
|
|
21880
22484
|
function findCodexbar(sys, configured) {
|
|
21881
22485
|
for (const p of [configured, sys.ctx.env.STARBRIDGE_CODEXBAR])
|
|
@@ -21884,7 +22488,7 @@ function findCodexbar(sys, configured) {
|
|
|
21884
22488
|
const onPath = which(sys.ctx.env, "codexbar");
|
|
21885
22489
|
if (onPath)
|
|
21886
22490
|
return { path: onPath };
|
|
21887
|
-
const opt =
|
|
22491
|
+
const opt = join10(sys.home, ".local/opt/codexbar/codexbar");
|
|
21888
22492
|
if (existsSync6(opt))
|
|
21889
22493
|
return { path: opt };
|
|
21890
22494
|
if (sys.platform === "darwin") {
|
|
@@ -21907,7 +22511,7 @@ function tarballKey(sys, musl = isMusl()) {
|
|
|
21907
22511
|
function isMusl() {
|
|
21908
22512
|
const has = (dir, prefix) => {
|
|
21909
22513
|
try {
|
|
21910
|
-
return
|
|
22514
|
+
return readdirSync5(dir).some((f) => f.startsWith(prefix));
|
|
21911
22515
|
} catch {
|
|
21912
22516
|
return false;
|
|
21913
22517
|
}
|
|
@@ -21924,9 +22528,9 @@ function isMusl() {
|
|
|
21924
22528
|
return !libs.some((d) => has(d, "libcurl.so.4"));
|
|
21925
22529
|
}
|
|
21926
22530
|
function linkIntoLocalBin(sys, target) {
|
|
21927
|
-
const bin =
|
|
21928
|
-
const link =
|
|
21929
|
-
|
|
22531
|
+
const bin = join10(sys.home, ".local/bin");
|
|
22532
|
+
const link = join10(bin, "codexbar");
|
|
22533
|
+
mkdirSync8(bin, { recursive: true });
|
|
21930
22534
|
try {
|
|
21931
22535
|
if (!lstatSync2(link).isSymbolicLink())
|
|
21932
22536
|
return;
|
|
@@ -21935,7 +22539,7 @@ function linkIntoLocalBin(sys, target) {
|
|
|
21935
22539
|
symlinkSync(target, link);
|
|
21936
22540
|
return link;
|
|
21937
22541
|
}
|
|
21938
|
-
var optDir = (sys) =>
|
|
22542
|
+
var optDir = (sys) => join10(sys.home, ".local/opt/codexbar");
|
|
21939
22543
|
function releaseVersion(v) {
|
|
21940
22544
|
const m = /^v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?)$/.exec(v.trim());
|
|
21941
22545
|
if (!m)
|
|
@@ -21953,19 +22557,28 @@ async function fetchRelease(url, init, what) {
|
|
|
21953
22557
|
throw new Error(`${what}: GitHub is limiting requests from this address (${res.status}); try again in an hour`);
|
|
21954
22558
|
return res;
|
|
21955
22559
|
}
|
|
21956
|
-
|
|
21957
|
-
const
|
|
21958
|
-
|
|
21959
|
-
|
|
21960
|
-
|
|
21961
|
-
|
|
21962
|
-
|
|
21963
|
-
|
|
22560
|
+
function compareCodexbar(a, b) {
|
|
22561
|
+
const parse = (v) => {
|
|
22562
|
+
const [main = "", pre] = releaseVersion(v).split(/-(.*)/);
|
|
22563
|
+
return { nums: main.split(".").map(Number), pre };
|
|
22564
|
+
};
|
|
22565
|
+
const x = parse(a);
|
|
22566
|
+
const y = parse(b);
|
|
22567
|
+
for (let i = 0;i < 3; i++)
|
|
22568
|
+
if (x.nums[i] !== y.nums[i])
|
|
22569
|
+
return x.nums[i] < y.nums[i] ? -1 : 1;
|
|
22570
|
+
if (x.pre === y.pre)
|
|
22571
|
+
return 0;
|
|
22572
|
+
if (x.pre === undefined)
|
|
22573
|
+
return 1;
|
|
22574
|
+
if (y.pre === undefined)
|
|
22575
|
+
return -1;
|
|
22576
|
+
return Math.sign(x.pre.localeCompare(y.pre, "en", { numeric: true }));
|
|
21964
22577
|
}
|
|
21965
22578
|
var size = (bytes) => bytes >= 1e6 ? `${Math.round(bytes / 1e6)} MB` : `${Math.max(1, Math.round(bytes / 1000))} kB`;
|
|
21966
22579
|
function installedVersion(sys) {
|
|
21967
22580
|
try {
|
|
21968
|
-
return
|
|
22581
|
+
return readFileSync12(join10(optDir(sys), "VERSION"), "utf8").trim() || undefined;
|
|
21969
22582
|
} catch {
|
|
21970
22583
|
return;
|
|
21971
22584
|
}
|
|
@@ -21973,40 +22586,43 @@ function installedVersion(sys) {
|
|
|
21973
22586
|
|
|
21974
22587
|
class NoChecksum extends Error {
|
|
21975
22588
|
}
|
|
21976
|
-
async function installTarball(sys, key, version) {
|
|
22589
|
+
async function installTarball(sys, key, version, sha256) {
|
|
21977
22590
|
const base = `${sys.ctx.env.STARBRIDGE_CODEXBAR_RELEASES ?? RELEASES}/download`;
|
|
21978
22591
|
const name = `CodexBarCLI-v${version}-${key}.tar.gz`;
|
|
21979
22592
|
const url = `${base}/v${version}/${name}`;
|
|
21980
22593
|
const signal = AbortSignal.timeout(10 * 60000);
|
|
21981
|
-
|
|
21982
|
-
if (!
|
|
21983
|
-
|
|
21984
|
-
|
|
21985
|
-
|
|
21986
|
-
|
|
22594
|
+
let want = sha256;
|
|
22595
|
+
if (!want) {
|
|
22596
|
+
const sums = await fetchRelease(`${url}.sha256`, { signal }, `downloading ${name}.sha256`);
|
|
22597
|
+
if (!sums.ok)
|
|
22598
|
+
throw new NoChecksum(`no checksum for ${name} (${sums.status}): not installed`);
|
|
22599
|
+
want = /^[0-9a-f]{64}\b/i.exec((await sums.text()).trim())?.[0].toLowerCase();
|
|
22600
|
+
if (!want)
|
|
22601
|
+
throw new Error(`${name}.sha256 holds no SHA-256: not installed`);
|
|
22602
|
+
}
|
|
21987
22603
|
const res = await fetchRelease(url, { signal }, `downloading ${name}`);
|
|
21988
22604
|
if (!res.ok || !res.body)
|
|
21989
22605
|
throw new Error(`downloading ${name}: ${res.status}`);
|
|
21990
22606
|
const dest = optDir(sys);
|
|
21991
22607
|
const opt = dirname5(dest);
|
|
21992
|
-
const fresh =
|
|
22608
|
+
const fresh = join10(opt, `.codexbar.${process.pid}`);
|
|
21993
22609
|
const file = `${fresh}.tar.gz`;
|
|
21994
22610
|
const old = `${fresh}.old`;
|
|
21995
|
-
|
|
22611
|
+
mkdirSync8(fresh, { recursive: true });
|
|
21996
22612
|
try {
|
|
21997
22613
|
await download(sys, res, file, want, name, `CodexBar ${version} (${key})`);
|
|
21998
22614
|
const r = await run(sys, "tar", ["-xzf", file, "-C", fresh]);
|
|
21999
22615
|
if (r?.code !== 0)
|
|
22000
22616
|
throw new Error(`could not unpack ${name} into ${opt}; is the disk full? (${failure2(r)})`);
|
|
22001
22617
|
if (existsSync6(dest))
|
|
22002
|
-
|
|
22003
|
-
|
|
22618
|
+
renameSync4(dest, old);
|
|
22619
|
+
renameSync4(fresh, dest);
|
|
22004
22620
|
} finally {
|
|
22005
|
-
|
|
22006
|
-
|
|
22007
|
-
|
|
22621
|
+
rmSync4(file, { force: true });
|
|
22622
|
+
rmSync4(fresh, { recursive: true, force: true });
|
|
22623
|
+
rmSync4(old, { recursive: true, force: true });
|
|
22008
22624
|
}
|
|
22009
|
-
return
|
|
22625
|
+
return join10(dest, "codexbar");
|
|
22010
22626
|
}
|
|
22011
22627
|
async function download(sys, res, file, want, name, label) {
|
|
22012
22628
|
const total = Number(res.headers.get("content-length")) || undefined;
|
|
@@ -22035,12 +22651,15 @@ async function download(sys, res, file, want, name, label) {
|
|
|
22035
22651
|
if (sha !== want)
|
|
22036
22652
|
throw new Error(`${name} has SHA-256 ${sha}, expected ${want}: not installed`);
|
|
22037
22653
|
}
|
|
22038
|
-
async function installRelease(sys, version) {
|
|
22654
|
+
async function installRelease(sys, version, pin = PIN) {
|
|
22039
22655
|
const key = tarballKey(sys);
|
|
22040
22656
|
if (!key)
|
|
22041
22657
|
throw new Error(`no CodexBar build for ${sys.platform} ${sys.arch}`);
|
|
22042
|
-
const v = version ? releaseVersion(version) :
|
|
22043
|
-
const
|
|
22658
|
+
const v = version ? releaseVersion(version) : pin.version;
|
|
22659
|
+
const sha = v === pin.version ? pin.sha256[key] : undefined;
|
|
22660
|
+
if (v === pin.version && !sha)
|
|
22661
|
+
throw new Error(`no checksum pinned for CodexBar ${v} (${key})`);
|
|
22662
|
+
const path = await installTarball(sys, key, v, sha);
|
|
22044
22663
|
const link = linkIntoLocalBin(sys, path);
|
|
22045
22664
|
sys.ctx.out(`Installed CodexBar ${v} to ${optDir(sys)}${link ? `, linked as ${link}` : ""}.`);
|
|
22046
22665
|
return path;
|
|
@@ -22087,10 +22706,10 @@ async function listProviders(sys, bin) {
|
|
|
22087
22706
|
}
|
|
22088
22707
|
function probeSet(sys, list, named) {
|
|
22089
22708
|
const set = new Set([...list.filter((p) => p.enabled).map((p) => p.provider), ...named]);
|
|
22090
|
-
const claude = existsSync6(
|
|
22709
|
+
const claude = existsSync6(join10(sys.home, ".claude/.credentials.json")) || which(sys.ctx.env, "claude");
|
|
22091
22710
|
if (claude)
|
|
22092
22711
|
set.add("claude");
|
|
22093
|
-
if (existsSync6(
|
|
22712
|
+
if (existsSync6(join10(sys.home, ".codex/auth.json")))
|
|
22094
22713
|
set.add("codex");
|
|
22095
22714
|
return [...set];
|
|
22096
22715
|
}
|
|
@@ -22121,7 +22740,7 @@ async function probe(bin, providers, list) {
|
|
|
22121
22740
|
}
|
|
22122
22741
|
}));
|
|
22123
22742
|
}
|
|
22124
|
-
async function updateCodexbar(sys, configured, version) {
|
|
22743
|
+
async function updateCodexbar(sys, configured, version, pin = PIN) {
|
|
22125
22744
|
const out = sys.ctx.out;
|
|
22126
22745
|
const found = findCodexbar(sys, configured);
|
|
22127
22746
|
if (!found) {
|
|
@@ -22141,20 +22760,19 @@ async function updateCodexbar(sys, configured, version) {
|
|
|
22141
22760
|
return version ? 1 : 0;
|
|
22142
22761
|
}
|
|
22143
22762
|
const have = installedVersion(sys);
|
|
22144
|
-
let want;
|
|
22145
22763
|
try {
|
|
22146
|
-
want = version ? releaseVersion(version) :
|
|
22764
|
+
const want = version ? releaseVersion(version) : pin.version;
|
|
22147
22765
|
if (have === want) {
|
|
22148
22766
|
out(`CodexBar ${have} is ${version ? "installed" : "up to date"}.`);
|
|
22149
22767
|
return 0;
|
|
22150
22768
|
}
|
|
22151
|
-
|
|
22152
|
-
|
|
22153
|
-
} catch (e) {
|
|
22154
|
-
if (!version && e instanceof NoChecksum) {
|
|
22155
|
-
out(`CodexBar ${want}'s build for this machine is not published yet: kept ${have ?? "the installed one"}.`);
|
|
22769
|
+
if (!version && have && compareCodexbar(have, want) > 0) {
|
|
22770
|
+
out(`CodexBar ${have} is newer than the ${want} this release installs: kept.`);
|
|
22156
22771
|
return 0;
|
|
22157
22772
|
}
|
|
22773
|
+
await installRelease(sys, want, pin);
|
|
22774
|
+
return 0;
|
|
22775
|
+
} catch (e) {
|
|
22158
22776
|
out(`Could not update CodexBar: ${e.message}`);
|
|
22159
22777
|
return 1;
|
|
22160
22778
|
}
|
|
@@ -22165,23 +22783,23 @@ import {
|
|
|
22165
22783
|
appendFileSync as appendFileSync2,
|
|
22166
22784
|
closeSync as closeSync3,
|
|
22167
22785
|
existsSync as existsSync7,
|
|
22168
|
-
mkdirSync as
|
|
22786
|
+
mkdirSync as mkdirSync9,
|
|
22169
22787
|
openSync as openSync3,
|
|
22170
|
-
readFileSync as
|
|
22788
|
+
readFileSync as readFileSync13,
|
|
22171
22789
|
readSync as readSync2,
|
|
22172
|
-
rmSync as
|
|
22173
|
-
writeFileSync as
|
|
22790
|
+
rmSync as rmSync5,
|
|
22791
|
+
writeFileSync as writeFileSync7
|
|
22174
22792
|
} from "node:fs";
|
|
22175
|
-
import {
|
|
22793
|
+
import { delimiter, dirname as dirname6, join as join11 } from "node:path";
|
|
22176
22794
|
function recordSelf(sys) {
|
|
22177
|
-
const file =
|
|
22795
|
+
const file = join11(sys.ctx.store.dir, CLI_PATH);
|
|
22178
22796
|
if (sys.self.length !== 1 || script(sys.self[0])) {
|
|
22179
|
-
|
|
22797
|
+
rmSync5(file, { force: true });
|
|
22180
22798
|
return;
|
|
22181
22799
|
}
|
|
22182
|
-
|
|
22800
|
+
mkdirSync9(dirname6(file), { recursive: true, mode: 448 });
|
|
22183
22801
|
const self2 = sys.self[0];
|
|
22184
|
-
|
|
22802
|
+
writeFileSync7(file, `${sys.platform === "win32" ? self2.replace(/\\/g, "/") : self2}
|
|
22185
22803
|
`);
|
|
22186
22804
|
}
|
|
22187
22805
|
function script(path) {
|
|
@@ -22198,7 +22816,8 @@ function script(path) {
|
|
|
22198
22816
|
function missingDir(sys) {
|
|
22199
22817
|
if (sys.self.length !== 1)
|
|
22200
22818
|
return;
|
|
22201
|
-
const
|
|
22819
|
+
const { dirname } = pathFor(sys.platform);
|
|
22820
|
+
const dir = dirname(sys.self[0]);
|
|
22202
22821
|
const norm = (p) => {
|
|
22203
22822
|
const bare = p.replace(/[/\\]+$/, "");
|
|
22204
22823
|
return sys.platform === "win32" ? bare.toLowerCase() : bare;
|
|
@@ -22207,20 +22826,21 @@ function missingDir(sys) {
|
|
|
22207
22826
|
return path.includes(norm(dir)) ? undefined : dir;
|
|
22208
22827
|
}
|
|
22209
22828
|
function shellProfile(sys, dir) {
|
|
22210
|
-
const
|
|
22829
|
+
const { basename, join } = pathFor(sys.platform);
|
|
22830
|
+
const shell = basename(sys.ctx.env.SHELL ?? "");
|
|
22211
22831
|
const shown = dir.startsWith(`${sys.home}/`) ? `$HOME${dir.slice(sys.home.length)}` : dir;
|
|
22212
22832
|
const exported = `export PATH="${shown}:$PATH"`;
|
|
22213
22833
|
if (shell === "fish")
|
|
22214
|
-
return { file:
|
|
22834
|
+
return { file: join(sys.home, ".config", "fish", "config.fish"), line: `fish_add_path ${dir}` };
|
|
22215
22835
|
if (shell === "zsh")
|
|
22216
|
-
return { file:
|
|
22836
|
+
return { file: join(sys.ctx.env.ZDOTDIR || sys.home, ".zshrc"), line: exported };
|
|
22217
22837
|
if (shell === "bash") {
|
|
22218
22838
|
if (sys.platform !== "darwin")
|
|
22219
|
-
return { file:
|
|
22220
|
-
const login = [".bash_profile", ".bash_login"].map((f) =>
|
|
22221
|
-
return { file: login ??
|
|
22839
|
+
return { file: join(sys.home, ".bashrc"), line: exported };
|
|
22840
|
+
const login = [".bash_profile", ".bash_login"].map((f) => join(sys.home, f)).find(existsSync7);
|
|
22841
|
+
return { file: login ?? join(sys.home, ".profile"), line: exported };
|
|
22222
22842
|
}
|
|
22223
|
-
return { file:
|
|
22843
|
+
return { file: join(sys.home, ".profile"), line: exported };
|
|
22224
22844
|
}
|
|
22225
22845
|
async function pathStep(sys) {
|
|
22226
22846
|
const dir = missingDir(sys);
|
|
@@ -22236,12 +22856,13 @@ async function pathStep(sys) {
|
|
|
22236
22856
|
];
|
|
22237
22857
|
}
|
|
22238
22858
|
const { file, line } = shellProfile(sys, dir);
|
|
22859
|
+
const { dirname } = pathFor(sys.platform);
|
|
22239
22860
|
const shown = file.startsWith(`${sys.home}/`) ? `~${file.slice(sys.home.length)}` : file;
|
|
22240
|
-
const text = existsSync7(file) ?
|
|
22861
|
+
const text = existsSync7(file) ? readFileSync13(file, "utf8") : "";
|
|
22241
22862
|
const rest = dir.startsWith(`${sys.home}/`) ? dir.slice(sys.home.length) : undefined;
|
|
22242
22863
|
const has = [dir, line, ...rest ? [`$HOME${rest}`, `\${HOME}${rest}`, `~${rest}`] : []].some((spelled) => text.includes(spelled));
|
|
22243
22864
|
if (!has && await prompt.confirm(`Add ${dir} to your PATH in ${shown}?`, true)) {
|
|
22244
|
-
|
|
22865
|
+
mkdirSync9(dirname(file), { recursive: true });
|
|
22245
22866
|
appendFileSync2(file, `${text === "" || text.endsWith(`
|
|
22246
22867
|
`) ? "" : `
|
|
22247
22868
|
`}
|
|
@@ -22262,9 +22883,9 @@ ${line}
|
|
|
22262
22883
|
}
|
|
22263
22884
|
|
|
22264
22885
|
// src/setup/service.ts
|
|
22265
|
-
import { existsSync as existsSync8, mkdirSync as
|
|
22886
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync10, readFileSync as readFileSync14, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "node:fs";
|
|
22266
22887
|
import { userInfo as userInfo2 } from "node:os";
|
|
22267
|
-
import { delimiter as delimiter2, dirname as dirname7, join as
|
|
22888
|
+
import { delimiter as delimiter2, dirname as dirname7, join as join12 } from "node:path";
|
|
22268
22889
|
var UNIT = "starbridge-agent.service";
|
|
22269
22890
|
var LABEL = "run.starbridge.agent";
|
|
22270
22891
|
var TASK = "starbridge-agent";
|
|
@@ -22272,25 +22893,25 @@ function kind(sys) {
|
|
|
22272
22893
|
return { linux: "systemd", darwin: "launchd", win32: "task" }[sys.platform];
|
|
22273
22894
|
}
|
|
22274
22895
|
function localDir(sys) {
|
|
22275
|
-
return
|
|
22896
|
+
return join12(sys.ctx.env.LOCALAPPDATA || join12(sys.home, "AppData", "Local"), "starbridge");
|
|
22276
22897
|
}
|
|
22277
|
-
var agentLog = (sys) => kind(sys) === "task" ?
|
|
22898
|
+
var agentLog = (sys) => kind(sys) === "task" ? join12(localDir(sys), "agent.log") : join12(sys.home, "Library/Logs/starbridge-agent.log");
|
|
22278
22899
|
function unitDir(sys) {
|
|
22279
|
-
return
|
|
22900
|
+
return join12(sys.home, ".config/systemd/user");
|
|
22280
22901
|
}
|
|
22281
22902
|
function servicePath(sys) {
|
|
22282
22903
|
const k = kind(sys);
|
|
22283
22904
|
if (k === "systemd")
|
|
22284
|
-
return
|
|
22905
|
+
return join12(unitDir(sys), UNIT);
|
|
22285
22906
|
if (k === "launchd")
|
|
22286
|
-
return
|
|
22907
|
+
return join12(sys.home, "Library/LaunchAgents", `${LABEL}.plist`);
|
|
22287
22908
|
if (k === "task")
|
|
22288
|
-
return
|
|
22909
|
+
return join12(localDir(sys), `${TASK}.xml`);
|
|
22289
22910
|
return;
|
|
22290
22911
|
}
|
|
22291
22912
|
function servicePathVar(sys) {
|
|
22292
22913
|
const extra = [
|
|
22293
|
-
|
|
22914
|
+
join12(sys.home, ".local/bin"),
|
|
22294
22915
|
"/opt/homebrew/bin",
|
|
22295
22916
|
"/usr/local/bin",
|
|
22296
22917
|
"/usr/bin",
|
|
@@ -22386,12 +23007,12 @@ ${env}
|
|
|
22386
23007
|
</plist>
|
|
22387
23008
|
`;
|
|
22388
23009
|
}
|
|
22389
|
-
var winArg = (s) => /[\s"]/.test(s) ? `"${s.replace(/"/g, "\\\"")}"` : s;
|
|
23010
|
+
var winArg = (s) => /[\s"]/.test(s) ? `"${s.replace(/(\\*)"/g, "$1$1\\\"").replace(/(\\+)$/, "$1$1")}"` : s;
|
|
22390
23011
|
function taskXml(sys) {
|
|
22391
23012
|
const env = sys.ctx.env;
|
|
22392
23013
|
const name = env.USERNAME || userInfo2().username;
|
|
22393
23014
|
const user = env.USERDOMAIN ? `${env.USERDOMAIN}\\${name}` : name;
|
|
22394
|
-
const conhost =
|
|
23015
|
+
const conhost = join12(env.SystemRoot || env.SYSTEMROOT || "C:\\Windows", "System32", "conhost.exe");
|
|
22395
23016
|
const args = ["--headless", ...sys.self, "agent", "--log", agentLog(sys)].map(winArg).join(" ");
|
|
22396
23017
|
return `<?xml version="1.0" encoding="UTF-16"?>
|
|
22397
23018
|
${marker("<!--", "-->")}
|
|
@@ -22443,7 +23064,7 @@ ${marker("<!--", "-->")}
|
|
|
22443
23064
|
</Task>
|
|
22444
23065
|
`;
|
|
22445
23066
|
}
|
|
22446
|
-
var powershell = (sys, script, env = {}) => run(sys,
|
|
23067
|
+
var powershell = (sys, script, env = {}) => run(sys, join12(sys.ctx.env.SystemRoot || sys.ctx.env.SYSTEMROOT || "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe"), ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], { timeoutMs: 60000, env });
|
|
22447
23068
|
async function stopTask(sys) {
|
|
22448
23069
|
let pid;
|
|
22449
23070
|
const socket = socketPath(sys.ctx.env, sys.ctx.store.dir, sys.platform);
|
|
@@ -22489,7 +23110,7 @@ async function unavailable(sys) {
|
|
|
22489
23110
|
}
|
|
22490
23111
|
function readText2(path) {
|
|
22491
23112
|
try {
|
|
22492
|
-
const bytes =
|
|
23113
|
+
const bytes = readFileSync14(path);
|
|
22493
23114
|
if (bytes[0] === 255 && bytes[1] === 254)
|
|
22494
23115
|
return bytes.toString("utf16le").slice(1);
|
|
22495
23116
|
return bytes.toString("utf8");
|
|
@@ -22507,12 +23128,12 @@ async function installService(sys, restart) {
|
|
|
22507
23128
|
const before = readText2(path);
|
|
22508
23129
|
if (before !== undefined && !ours(before))
|
|
22509
23130
|
throw new Error(`${path} was not written by setup: remove it to install the agent`);
|
|
22510
|
-
|
|
23131
|
+
mkdirSync10(dirname7(path), { recursive: true });
|
|
22511
23132
|
const k = kind(sys);
|
|
22512
23133
|
const text = k === "systemd" ? unitText(sys) : k === "launchd" ? plistText(sys) : taskXml(sys);
|
|
22513
23134
|
const changed = readText2(path) !== text;
|
|
22514
23135
|
if (changed)
|
|
22515
|
-
|
|
23136
|
+
writeFileSync8(path, k === "task" ? `\uFEFF${text}` : text, k === "task" ? "utf16le" : "utf8");
|
|
22516
23137
|
const running = /^(active|running)$/i.test((await serviceState(sys)).state);
|
|
22517
23138
|
const go = changed || restart || !running;
|
|
22518
23139
|
if (k === "task") {
|
|
@@ -22555,16 +23176,16 @@ async function removeService(sys) {
|
|
|
22555
23176
|
const r = await powershell(sys, `Unregister-ScheduledTask -TaskName ${TASK} -Confirm:$false -ErrorAction SilentlyContinue; if (Get-ScheduledTask -TaskName ${TASK} -ErrorAction SilentlyContinue) { exit 1 }`);
|
|
22556
23177
|
if (r?.code !== 0)
|
|
22557
23178
|
throw new Error(`Unregister-ScheduledTask: ${failure2(r)}`);
|
|
22558
|
-
|
|
23179
|
+
rmSync6(path, { force: true });
|
|
22559
23180
|
} else if (kind(sys) === "systemd") {
|
|
22560
23181
|
await stopUnit(sys, UNIT);
|
|
22561
|
-
|
|
23182
|
+
rmSync6(path, { force: true });
|
|
22562
23183
|
await systemctl(sys, "daemon-reload");
|
|
22563
23184
|
} else {
|
|
22564
23185
|
const r = await launchctl(sys, "bootout", `gui/${sys.uid}/${LABEL}`);
|
|
22565
23186
|
if (r?.code !== 0 && r?.code !== 3 && r?.code !== 113)
|
|
22566
23187
|
throw new Error(`launchctl bootout: ${failure2(r)}`);
|
|
22567
|
-
|
|
23188
|
+
rmSync6(path, { force: true });
|
|
22568
23189
|
}
|
|
22569
23190
|
return true;
|
|
22570
23191
|
}
|
|
@@ -22771,6 +23392,7 @@ async function setup(sys, opts) {
|
|
|
22771
23392
|
for (const line of commandTable([
|
|
22772
23393
|
["Check it", "starbridge status"],
|
|
22773
23394
|
...permissionsEnabled(ctx) ? [] : [["Send permission prompts", "starbridge config permissions on"]],
|
|
23395
|
+
...ctx.store.agentConfig().presence?.enabled || !["desktop", "laptop"].includes(ctx.store.agentConfig().machineKind ?? "") ? [] : [["Quiet the phone while here", "starbridge config presence on"]],
|
|
22774
23396
|
["Remove from one agent", `starbridge uninstall --agent ${one}`],
|
|
22775
23397
|
["Remove everything", "starbridge uninstall"]
|
|
22776
23398
|
]))
|
|
@@ -23137,6 +23759,9 @@ async function status(sys) {
|
|
|
23137
23759
|
out(`Sessions: ${agent.sessions.length}`);
|
|
23138
23760
|
for (const x of agent.sessions)
|
|
23139
23761
|
out(` ${x.id}${x.title ? ` "${x.title}"` : ""}${x.client ? ` ${x.client}` : ""}, last seen ${x.lastSeenAt}`);
|
|
23762
|
+
const own = ctx.env.CLAUDECODE === "1" ? ctx.env.CLAUDE_CODE_SESSION_ID : undefined;
|
|
23763
|
+
if (own && ctx.env.CLAUDE_CODE_SESSION_ATTENDED !== "0" && !agent.sessions.some((x) => x.id === own))
|
|
23764
|
+
out(` Not this session (${own}): no Starbridge mod runs in it, so answers come back only through \`starbridge wait\``);
|
|
23140
23765
|
}
|
|
23141
23766
|
const svc = await serviceState(sys);
|
|
23142
23767
|
out(svc.installed ? `Service: ${svc.state}${svc.enabled === undefined ? "" : svc.enabled ? ", enabled" : ", not enabled"}` : "Service: not installed");
|
|
@@ -23192,14 +23817,14 @@ async function status(sys) {
|
|
|
23192
23817
|
}
|
|
23193
23818
|
|
|
23194
23819
|
// src/setup/uninstall.ts
|
|
23195
|
-
import { existsSync as existsSync10, realpathSync as realpathSync5, rmSync as
|
|
23196
|
-
import { join as
|
|
23820
|
+
import { existsSync as existsSync10, realpathSync as realpathSync5, rmSync as rmSync8 } from "node:fs";
|
|
23821
|
+
import { join as join14 } from "node:path";
|
|
23197
23822
|
|
|
23198
23823
|
// src/update.ts
|
|
23199
|
-
import { spawn as
|
|
23200
|
-
import { chmodSync as chmodSync3, existsSync as existsSync9, readdirSync as
|
|
23824
|
+
import { spawn as spawn7, spawnSync as spawnSync3 } from "node:child_process";
|
|
23825
|
+
import { chmodSync as chmodSync3, existsSync as existsSync9, readdirSync as readdirSync6, renameSync as renameSync5, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
|
|
23201
23826
|
import { homedir as homedir5 } from "node:os";
|
|
23202
|
-
import { basename as
|
|
23827
|
+
import { basename as basename4, dirname as dirname8, join as join13 } from "node:path";
|
|
23203
23828
|
var MANAGED = {
|
|
23204
23829
|
brew: {
|
|
23205
23830
|
update: "brew upgrade starbridge, then starbridge setup --refresh",
|
|
@@ -23235,8 +23860,8 @@ async function restartAgent(ctx) {
|
|
|
23235
23860
|
return;
|
|
23236
23861
|
}
|
|
23237
23862
|
const home = ctx.env.HOME ?? homedir5();
|
|
23238
|
-
const linux =
|
|
23239
|
-
const mac =
|
|
23863
|
+
const linux = join13(home, ".config/systemd/user/starbridge-agent.service");
|
|
23864
|
+
const mac = join13(home, "Library/LaunchAgents/run.starbridge.agent.plist");
|
|
23240
23865
|
const r = existsSync9(linux) ? sh(ctx, "systemctl", ["--user", "try-restart", "starbridge-agent.service"]) : existsSync9(mac) ? sh(ctx, "launchctl", ["kickstart", "-k", `gui/${process.getuid?.()}/run.starbridge.agent`]) : undefined;
|
|
23241
23866
|
if (r === undefined)
|
|
23242
23867
|
return;
|
|
@@ -23272,7 +23897,14 @@ async function update(ctx, install, pubkey = RELEASE_KEY, codexbar) {
|
|
|
23272
23897
|
return updateCodexbar(sys, configured, codexbar);
|
|
23273
23898
|
let self2 = 0;
|
|
23274
23899
|
try {
|
|
23275
|
-
await updateSelf(ctx, install, pubkey)
|
|
23900
|
+
if (await updateSelf(ctx, install, pubkey) && install.kind === "binary") {
|
|
23901
|
+
const r = spawnSync3(install.path, ["update"], {
|
|
23902
|
+
env: ctx.env,
|
|
23903
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
23904
|
+
timeout: 15 * 60000
|
|
23905
|
+
});
|
|
23906
|
+
return r.status ?? 1;
|
|
23907
|
+
}
|
|
23276
23908
|
} catch (e) {
|
|
23277
23909
|
if (e instanceof ReleaseError && !(e instanceof DownloadError))
|
|
23278
23910
|
throw e;
|
|
@@ -23286,17 +23918,17 @@ async function update(ctx, install, pubkey = RELEASE_KEY, codexbar) {
|
|
|
23286
23918
|
async function updateSelf(ctx, install, pubkey) {
|
|
23287
23919
|
if (install.kind !== "binary") {
|
|
23288
23920
|
ctx.out(`starbridge was installed with ${install.kind}: run ${MANAGED[install.kind].update}`);
|
|
23289
|
-
return;
|
|
23921
|
+
return false;
|
|
23290
23922
|
}
|
|
23291
23923
|
const releases = ctx.env.STARBRIDGE_RELEASES_URL ?? RELEASES_URL;
|
|
23292
23924
|
const latest = await latestVersion(releases);
|
|
23293
23925
|
if (compareVersions(latest, VERSION) <= 0) {
|
|
23294
23926
|
ctx.out(`starbridge ${VERSION} is up to date.`);
|
|
23295
|
-
return;
|
|
23927
|
+
return false;
|
|
23296
23928
|
}
|
|
23297
23929
|
const bytes = await downloadVerified(latest, platformAsset(), { releases, pubkey });
|
|
23298
|
-
const next =
|
|
23299
|
-
|
|
23930
|
+
const next = join13(dirname8(install.path), ".starbridge.new");
|
|
23931
|
+
writeFileSync9(next, bytes, { mode: 493 });
|
|
23300
23932
|
chmodSync3(next, 493);
|
|
23301
23933
|
replaceBinary(next, install.path);
|
|
23302
23934
|
ctx.out(`Updated starbridge ${VERSION} to ${latest} in ${install.path}.`);
|
|
@@ -23315,28 +23947,29 @@ async function updateSelf(ctx, install, pubkey) {
|
|
|
23315
23947
|
}
|
|
23316
23948
|
updatePlugins(ctx);
|
|
23317
23949
|
movePiPackage(ctx, latest);
|
|
23950
|
+
return true;
|
|
23318
23951
|
}
|
|
23319
23952
|
function replaceBinary(next, target, platform = process.platform) {
|
|
23320
23953
|
if (platform !== "win32")
|
|
23321
|
-
return
|
|
23954
|
+
return renameSync5(next, target);
|
|
23322
23955
|
let aside = `${target}.old`;
|
|
23323
23956
|
try {
|
|
23324
|
-
|
|
23957
|
+
rmSync7(aside, { force: true });
|
|
23325
23958
|
} catch {
|
|
23326
23959
|
aside = `${target}.${process.pid}.old`;
|
|
23327
23960
|
}
|
|
23328
|
-
|
|
23961
|
+
renameSync5(target, aside);
|
|
23329
23962
|
try {
|
|
23330
|
-
|
|
23963
|
+
renameSync5(next, target);
|
|
23331
23964
|
} catch (e) {
|
|
23332
|
-
|
|
23965
|
+
renameSync5(aside, target);
|
|
23333
23966
|
throw e;
|
|
23334
23967
|
}
|
|
23335
23968
|
const dir = dirname8(target);
|
|
23336
|
-
const leftover = (f) => f.startsWith(`${
|
|
23337
|
-
for (const f of
|
|
23969
|
+
const leftover = (f) => f.startsWith(`${basename4(target)}.`) && f.endsWith(".old");
|
|
23970
|
+
for (const f of readdirSync6(dir).filter(leftover))
|
|
23338
23971
|
try {
|
|
23339
|
-
|
|
23972
|
+
rmSync7(join13(dir, f), { force: true });
|
|
23340
23973
|
} catch {}
|
|
23341
23974
|
}
|
|
23342
23975
|
function removeBinary(ctx, install) {
|
|
@@ -23346,7 +23979,7 @@ function removeBinary(ctx, install) {
|
|
|
23346
23979
|
}
|
|
23347
23980
|
if (process.platform === "win32")
|
|
23348
23981
|
return removeRunningExe(ctx, install.path);
|
|
23349
|
-
|
|
23982
|
+
rmSync7(install.path, { force: true });
|
|
23350
23983
|
ctx.out(`Removed ${install.path}.`);
|
|
23351
23984
|
return 0;
|
|
23352
23985
|
}
|
|
@@ -23356,7 +23989,7 @@ function removeRunningExe(ctx, path) {
|
|
|
23356
23989
|
return 0;
|
|
23357
23990
|
}
|
|
23358
23991
|
const del = `ping -n 3 127.0.0.1 >nul & del /f /q "${path}" "${path}.old" "${path}.*.old"`;
|
|
23359
|
-
|
|
23992
|
+
spawn7(ctx.env.ComSpec || "cmd.exe", ["/d", "/s", "/c", `"${del}"`], {
|
|
23360
23993
|
detached: true,
|
|
23361
23994
|
stdio: "ignore",
|
|
23362
23995
|
windowsHide: true,
|
|
@@ -23403,7 +24036,7 @@ async function uninstall(sys, opts) {
|
|
|
23403
24036
|
ctx.out(`Kept ${dir}: a service still uses it. Stop it, then rerun \`starbridge uninstall\`.`);
|
|
23404
24037
|
} else if (existsSync10(dir)) {
|
|
23405
24038
|
if (opts.purge || await prompt.confirm(`Delete ${dir} (this machine's keys and state)?`, false)) {
|
|
23406
|
-
|
|
24039
|
+
rmSync8(dir, { recursive: true, force: true });
|
|
23407
24040
|
ctx.out(`Deleted ${dir}.`);
|
|
23408
24041
|
} else
|
|
23409
24042
|
ctx.out(`Kept ${dir}: \`starbridge setup\` reuses it.`);
|
|
@@ -23412,8 +24045,8 @@ async function uninstall(sys, opts) {
|
|
|
23412
24045
|
ctx.out(`Revoke "${machine.name}" under Devices in the Starbridge app or web page.`);
|
|
23413
24046
|
const cb = findCodexbar(sys);
|
|
23414
24047
|
if (cb) {
|
|
23415
|
-
const opt =
|
|
23416
|
-
const how = cb.inApp ? "brew uninstall --cask codexbar (or drag CodexBar.app to the Bin)" : realpathSync5(cb.path).startsWith(opt) ? `rm -rf ${opt} ${
|
|
24048
|
+
const opt = join14(sys.home, ".local/opt/codexbar");
|
|
24049
|
+
const how = cb.inApp ? "brew uninstall --cask codexbar (or drag CodexBar.app to the Bin)" : realpathSync5(cb.path).startsWith(opt) ? `rm -rf ${opt} ${join14(sys.home, ".local/bin/codexbar")}` : "your package manager";
|
|
23417
24050
|
ctx.out(`CodexBar stays installed (${cb.path}); remove it with ${how}.`);
|
|
23418
24051
|
}
|
|
23419
24052
|
if (opts.install && stopped)
|
|
@@ -23468,7 +24101,10 @@ var HELP = `starbridge: post decisions to your devices, report runs, upload quot
|
|
|
23468
24101
|
setup leaves it out until \`setup --agent\` brings it back.
|
|
23469
24102
|
|
|
23470
24103
|
starbridge pair [--server <url>] [--name <name>] [--force]
|
|
23471
|
-
Make this machine's keys and print a pairing code to type on a device.
|
|
24104
|
+
Make this machine's keys and print a pairing code to type on a device. Unless the
|
|
24105
|
+
Starbridge Android app scanned the QR code, confirm that its check code matches.
|
|
24106
|
+
starbridge pair --confirm | --reject
|
|
24107
|
+
Answer that for a waiting \`pair\` or \`setup\` that has no terminal.
|
|
23472
24108
|
--server <url> a self-hosted server (default: $STARBRIDGE_SERVER, else
|
|
23473
24109
|
https://starbridge.run)
|
|
23474
24110
|
|
|
@@ -23559,24 +24195,29 @@ var HELP = `starbridge: post decisions to your devices, report runs, upload quot
|
|
|
23559
24195
|
The commands above go through it when it runs, and to the server directly when not
|
|
23560
24196
|
(or with STARBRIDGE_NO_AGENT=1).
|
|
23561
24197
|
|
|
23562
|
-
starbridge config [permissions on|off] [machine-kind server|desktop|laptop|cloud]
|
|
24198
|
+
starbridge config [permissions on|off] [presence on|off] [machine-kind server|desktop|laptop|cloud]
|
|
23563
24199
|
Print this machine's settings, or change one. permissions: send its Claude Code
|
|
23564
24200
|
permission prompts to your devices, where they can be allowed or denied; the prompt
|
|
23565
24201
|
stays open at the keyboard and the first answer wins. Off by default; while off, the
|
|
23566
|
-
starbridge plugin's permission hook exits at once.
|
|
23567
|
-
|
|
24202
|
+
starbridge plugin's permission hook exits at once. presence: while this machine's
|
|
24203
|
+
screen is unlocked and had keyboard or mouse input in the last minute, notifications
|
|
24204
|
+
on your other devices wait a few seconds, so a question you answer here doesn't buzz
|
|
24205
|
+
your phone; the server hears only yes or no. Off by default. machine-kind: the icon
|
|
24206
|
+
devices show, detected by setup.
|
|
23568
24207
|
|
|
23569
24208
|
starbridge hook permission --agent claude-code|pi|opencode [--wait 570s]
|
|
23570
24209
|
starbridge hook settle --agent claude-code
|
|
23571
|
-
For Claude Code's PermissionRequest hook, and for its PostToolUse,
|
|
23572
|
-
Stop and SessionEnd hooks: hook JSON on stdin; prints the hook's decision, or nothing
|
|
24210
|
+
For Claude Code's PermissionRequest hook, and for its PostToolUse, PostToolUseFailure,
|
|
24211
|
+
PermissionDenied, Stop and SessionEnd hooks: hook JSON on stdin; prints the hook's decision, or nothing
|
|
23573
24212
|
to leave the prompt to the keyboard. The Starbridge Pi extension runs it with --agent pi
|
|
23574
|
-
for pi-permission-system's prompts, the opencode plugin with --agent opencode.
|
|
24213
|
+
for pi-permission-system's prompts, the opencode plugin with --agent opencode. On
|
|
24214
|
+
Claude Code's AskUserQuestion it posts each question to your devices, whatever the
|
|
24215
|
+
permissions setting, and prints the first device answers as the picker's; an answer
|
|
24216
|
+
in the picker settles them.
|
|
23575
24217
|
|
|
23576
24218
|
starbridge hook ask-user
|
|
23577
|
-
For Claude Code's PreToolUse hook on AskUserQuestion
|
|
23578
|
-
|
|
23579
|
-
through, when this machine is not paired or the server does not answer.
|
|
24219
|
+
For Claude Code's PreToolUse hook on AskUserQuestion, from older plugins: prints
|
|
24220
|
+
nothing, so the picker opens and hook permission races it.
|
|
23580
24221
|
|
|
23581
24222
|
starbridge hook question --agent opencode
|
|
23582
24223
|
For the Starbridge opencode plugin, on each call of opencode's question tool: posts each
|
|
@@ -23586,9 +24227,9 @@ var HELP = `starbridge: post decisions to your devices, report runs, upload quot
|
|
|
23586
24227
|
|
|
23587
24228
|
starbridge update [--codexbar <version>]
|
|
23588
24229
|
Install the latest release once its signature checks out, then \`setup --refresh\` (brew
|
|
23589
|
-
and npm installs: use their manager, then \`starbridge setup --refresh\`); then
|
|
23590
|
-
|
|
23591
|
-
and only that.
|
|
24230
|
+
and npm installs: use their manager, then \`starbridge setup --refresh\`); then the
|
|
24231
|
+
CodexBar release this Starbridge release pins, if setup installed CodexBar. --codexbar
|
|
24232
|
+
installs that CodexBar release instead, and only that; update keeps it if it is newer.
|
|
23592
24233
|
|
|
23593
24234
|
starbridge --version
|
|
23594
24235
|
|
|
@@ -23610,7 +24251,7 @@ function parseSessionLink(text) {
|
|
|
23610
24251
|
}
|
|
23611
24252
|
function readText3(path) {
|
|
23612
24253
|
try {
|
|
23613
|
-
return
|
|
24254
|
+
return readFileSync15(path === "-" ? 0 : path, "utf8");
|
|
23614
24255
|
} catch (e) {
|
|
23615
24256
|
throw new UsageError(`cannot read ${path === "-" ? "stdin" : path}: ${e.message}`);
|
|
23616
24257
|
}
|
|
@@ -23644,9 +24285,13 @@ async function run2(argv, ctx) {
|
|
|
23644
24285
|
options: {
|
|
23645
24286
|
server: { type: "string" },
|
|
23646
24287
|
name: { type: "string" },
|
|
23647
|
-
force: { type: "boolean" }
|
|
24288
|
+
force: { type: "boolean" },
|
|
24289
|
+
confirm: { type: "boolean" },
|
|
24290
|
+
reject: { type: "boolean" }
|
|
23648
24291
|
}
|
|
23649
24292
|
});
|
|
24293
|
+
if (values.confirm || values.reject)
|
|
24294
|
+
return sendConfirm(ctx, !values.reject);
|
|
23650
24295
|
return await pair(ctx, values);
|
|
23651
24296
|
}
|
|
23652
24297
|
case "ask": {
|
|
@@ -23892,7 +24537,7 @@ async function run2(argv, ctx) {
|
|
|
23892
24537
|
if (sub === "settle")
|
|
23893
24538
|
return await hookSettle(ctx, readText3("-"), values);
|
|
23894
24539
|
if (sub === "ask-user")
|
|
23895
|
-
return
|
|
24540
|
+
return hookAskUser();
|
|
23896
24541
|
if (sub === "question")
|
|
23897
24542
|
return await hookQuestion(ctx, readText3("-"), values);
|
|
23898
24543
|
throw new UsageError("usage: starbridge hook permission|settle --agent claude-code, starbridge hook ask-user, or starbridge hook question --agent opencode");
|