@bivy/bivy 0.12.0-staging.405 → 0.12.0-staging.407
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/bin/bivy.mjs +24 -19
- package/dist/relay-setup.js +17 -7
- package/dist/server.js +14 -9
- package/package.json +1 -1
package/bin/bivy.mjs
CHANGED
|
@@ -3621,8 +3621,9 @@ async function cmdSetup(args = []) {
|
|
|
3621
3621
|
// If self-host endpoints are already provided via the environment, default to
|
|
3622
3622
|
// self-hosted so a scripted or self-hosted install doesn't have to re-pick it
|
|
3623
3623
|
// (BIVY_CONTROL_PLANE_URL / BIVY_RELAY_URL then pre-fill the URL prompts below).
|
|
3624
|
-
const
|
|
3625
|
-
const
|
|
3624
|
+
const nodeClaimCode = process.env.BIVY_NODE_CLAIM_CODE?.trim();
|
|
3625
|
+
const selfHostEnv = !nodeClaimCode && Boolean((process.env.BIVY_CONTROL_PLANE_URL || "").trim() || (process.env.BIVY_RELAY_URL || "").trim());
|
|
3626
|
+
const syncChoice = nodeClaimCode ? "h" : await askChoice(
|
|
3626
3627
|
"Remote access",
|
|
3627
3628
|
[
|
|
3628
3629
|
{ key: "h", label: "hosted (recommended — sign in with GitHub or email; nothing caps your local usage)" },
|
|
@@ -3644,29 +3645,33 @@ async function cmdSetup(args = []) {
|
|
|
3644
3645
|
}
|
|
3645
3646
|
|
|
3646
3647
|
if (syncChoice !== "l") {
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3648
|
+
if (!nodeClaimCode) {
|
|
3649
|
+
const loginChoice = await askChoice(
|
|
3650
|
+
"Remote login",
|
|
3651
|
+
[
|
|
3652
|
+
{ key: "g", label: "GitHub" },
|
|
3653
|
+
{ key: "e", label: "email sign-in link (open or scan on any device)" },
|
|
3654
|
+
],
|
|
3655
|
+
"g",
|
|
3656
|
+
);
|
|
3657
|
+
if (loginChoice === "e") {
|
|
3658
|
+
const email = await ask(" Your account email:", config.env.BIVY_EMAIL || "");
|
|
3659
|
+
if (email.trim()) relayArgs.push("--email", email.trim());
|
|
3660
|
+
else relayArgs.push("--github");
|
|
3661
|
+
} else {
|
|
3662
|
+
relayArgs.push("--github");
|
|
3663
|
+
}
|
|
3661
3664
|
}
|
|
3662
3665
|
|
|
3663
3666
|
const useGithub = relayArgs.includes("--github");
|
|
3664
3667
|
try { fs.rmSync(setupSessionPath, { force: true }); } catch { /* best effort */ }
|
|
3665
3668
|
let relayOk;
|
|
3666
3669
|
for (;;) {
|
|
3667
|
-
console.log(c.dim(
|
|
3668
|
-
? "
|
|
3669
|
-
:
|
|
3670
|
+
console.log(c.dim(nodeClaimCode
|
|
3671
|
+
? " Using the one-time machine claim from your Bivy account; no additional sign-in is required."
|
|
3672
|
+
: useGithub
|
|
3673
|
+
? " We'll open GitHub in your browser (or print the URL on a headless server). Authorize, and setup continues automatically."
|
|
3674
|
+
: " We'll email you a sign-in link. Open it in any browser and setup continues automatically."));
|
|
3670
3675
|
rl.pause();
|
|
3671
3676
|
const code = await run(nodeBin, [...nodeScriptArgs(relaySetupEntry), ...relayArgs, "--emit-session", setupSessionPath], {
|
|
3672
3677
|
cwd: repoRoot,
|
package/dist/relay-setup.js
CHANGED
|
@@ -197,19 +197,29 @@ async function main() {
|
|
|
197
197
|
const clientBaseUrl = (arg("client", process.env.BIVY_CLIENT_BASE_URL) ?? controlPlaneUrl).replace(/\/$/, "") || controlPlaneUrl;
|
|
198
198
|
const email = arg("email", process.env.BIVY_EMAIL);
|
|
199
199
|
const sessionToken = arg("session-token", process.env.BIVY_SESSION_TOKEN);
|
|
200
|
+
const nodeClaimCode = process.env.BIVY_NODE_CLAIM_CODE?.trim();
|
|
200
201
|
// GitHub is the primary sign-in: used when --github is passed, or by default
|
|
201
|
-
// when neither an email
|
|
202
|
-
|
|
202
|
+
// when neither an email, an existing session token, nor a one-time machine
|
|
203
|
+
// claim is supplied. Claims authorize enrollment only and never mint a user
|
|
204
|
+
// session token.
|
|
205
|
+
const useGithub = process.argv.includes("--github") || process.env.BIVY_AUTH === "github" || (!email && !sessionToken && !nodeClaimCode);
|
|
203
206
|
const identity = NodeIdentity.load(appDir);
|
|
204
207
|
console.log(`Node: ${identity.name} (${identity.nodeId})`);
|
|
205
208
|
console.log(`Control plane: ${controlPlaneUrl}`);
|
|
206
209
|
console.log(`Relay: ${relayUrl}`);
|
|
207
210
|
await checkControlPlane(controlPlaneUrl);
|
|
208
|
-
const token =
|
|
211
|
+
const token = nodeClaimCode ? undefined
|
|
212
|
+
: sessionToken ?? (useGithub ? await githubDeviceLogin(controlPlaneUrl) : await deviceLogin(controlPlaneUrl, email));
|
|
209
213
|
async function enrollNode() {
|
|
210
|
-
|
|
214
|
+
const url = nodeClaimCode
|
|
215
|
+
? `${controlPlaneUrl}/claim/${encodeURIComponent(nodeClaimCode)}/enroll`
|
|
216
|
+
: `${controlPlaneUrl}/nodes/enroll`;
|
|
217
|
+
return fetchJson(url, {
|
|
211
218
|
method: "POST",
|
|
212
|
-
headers: {
|
|
219
|
+
headers: {
|
|
220
|
+
"content-type": "application/json",
|
|
221
|
+
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
222
|
+
},
|
|
213
223
|
body: JSON.stringify({ nodeId: identity.nodeId, name: identity.name }),
|
|
214
224
|
});
|
|
215
225
|
}
|
|
@@ -247,7 +257,7 @@ async function main() {
|
|
|
247
257
|
// read once and deleted by setup. We skip it when the caller supplied a session
|
|
248
258
|
// token only via --session-token/env with no --emit-session, i.e. non-setup use.
|
|
249
259
|
const emitSession = arg("emit-session");
|
|
250
|
-
if (emitSession) {
|
|
260
|
+
if (emitSession && token) {
|
|
251
261
|
try {
|
|
252
262
|
const handoff = JSON.stringify({ session: token, nodeId: identity.nodeId });
|
|
253
263
|
fs.writeFileSync(emitSession, `${handoff}\n`, { mode: 0o600 });
|
|
@@ -257,7 +267,7 @@ async function main() {
|
|
|
257
267
|
// best effort — setup falls back to opening the plain remote app URL
|
|
258
268
|
}
|
|
259
269
|
}
|
|
260
|
-
console.log(`\n✓ Signed in and enrolled this node. Wrote ${filePath}`);
|
|
270
|
+
console.log(`\n✓ ${nodeClaimCode ? "Claimed and enrolled" : "Signed in and enrolled"} this node. Wrote ${filePath}`);
|
|
261
271
|
console.log('Run "bivy link" to pair a phone, or use "Link remote device" in the app (bivy open).');
|
|
262
272
|
}
|
|
263
273
|
main().catch((error) => {
|
package/dist/server.js
CHANGED
|
@@ -4625,18 +4625,23 @@ function invalidateGitHubApps() {
|
|
|
4625
4625
|
// token nor an app key. When BIVY_HOSTED_MINT is set they mint a fresh,
|
|
4626
4626
|
// short-lived token from the control plane per git op — cached until ~5 min
|
|
4627
4627
|
// before expiry so a burst of git ops is a single round trip. This is the final
|
|
4628
|
-
// fallback rung after local apps and BIVY_GITHUB_TOKEN.
|
|
4629
|
-
|
|
4630
|
-
|
|
4628
|
+
// fallback rung after local apps and BIVY_GITHUB_TOKEN. Passing the repo lets
|
|
4629
|
+
// the control plane pick the matching central-app installation and scope the
|
|
4630
|
+
// token down to that repo, so the cache is keyed per repo.
|
|
4631
|
+
const hostedMintCache = new Map();
|
|
4632
|
+
async function hostedMintToken(repo) {
|
|
4631
4633
|
if (!process.env.BIVY_HOSTED_MINT || !sessionAdvertiseTarget)
|
|
4632
4634
|
return undefined;
|
|
4635
|
+
const cacheKey = repo ?? "";
|
|
4633
4636
|
const now = Date.now();
|
|
4634
|
-
|
|
4635
|
-
|
|
4637
|
+
const cached = hostedMintCache.get(cacheKey);
|
|
4638
|
+
if (cached && cached.expiresAt - now > 5 * 60 * 1000)
|
|
4639
|
+
return cached.token;
|
|
4636
4640
|
try {
|
|
4637
4641
|
const res = await fetch(`${sessionAdvertiseTarget.controlPlaneUrl.replace(/\/$/, "")}/node/hosted-git-credential`, {
|
|
4638
4642
|
method: "POST",
|
|
4639
|
-
headers: { authorization: `Bearer ${sessionAdvertiseTarget.enrollmentToken}
|
|
4643
|
+
headers: { authorization: `Bearer ${sessionAdvertiseTarget.enrollmentToken}`, "content-type": "application/json" },
|
|
4644
|
+
body: JSON.stringify(repo ? { repo } : {}),
|
|
4640
4645
|
});
|
|
4641
4646
|
if (!res.ok)
|
|
4642
4647
|
return undefined;
|
|
@@ -4644,7 +4649,7 @@ async function hostedMintToken() {
|
|
|
4644
4649
|
if (!data.token)
|
|
4645
4650
|
return undefined;
|
|
4646
4651
|
const parsed = data.expiresAt ? Date.parse(data.expiresAt) : NaN;
|
|
4647
|
-
hostedMintCache
|
|
4652
|
+
hostedMintCache.set(cacheKey, { token: data.token, expiresAt: Number.isFinite(parsed) ? parsed : now + 55 * 60 * 1000 });
|
|
4648
4653
|
return data.token;
|
|
4649
4654
|
}
|
|
4650
4655
|
catch {
|
|
@@ -4668,7 +4673,7 @@ async function resolveTokenForWorkItem(item) {
|
|
|
4668
4673
|
}
|
|
4669
4674
|
}
|
|
4670
4675
|
}
|
|
4671
|
-
return (await resolveGitHubToken()) ?? (await hostedMintToken());
|
|
4676
|
+
return (await resolveGitHubToken()) ?? (await hostedMintToken(item.repo));
|
|
4672
4677
|
}
|
|
4673
4678
|
/**
|
|
4674
4679
|
* The token for interactive repo operations (clone/fetch/push/PR) on `owner/repo`.
|
|
@@ -4716,7 +4721,7 @@ async function resolveTokenForRepo(owner, repo) {
|
|
|
4716
4721
|
}
|
|
4717
4722
|
}
|
|
4718
4723
|
}
|
|
4719
|
-
return (await resolveGitHubToken()) ?? (await hostedMintToken());
|
|
4724
|
+
return (await resolveGitHubToken()) ?? (await hostedMintToken(`${owner}/${repo}`));
|
|
4720
4725
|
}
|
|
4721
4726
|
/** The session source a Linear-issue pickup advertises, keyed by the issue's
|
|
4722
4727
|
* provider-native id so the control plane can correlate a re-dispatch to it
|
package/package.json
CHANGED