@bivy/bivy 0.10.1-staging.355 → 0.10.1-staging.356
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controllers/credential-commands.js +131 -0
- package/dist/server.js +10 -115
- package/package.json +1 -1
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (c) 2026 Petter André Sjulstad
|
|
3
|
+
//
|
|
4
|
+
// The credential CRUD + presets command cluster, extracted from server.ts's
|
|
5
|
+
// RELAY_COMMANDS (server.ts decomposition; same shape as controllers/
|
|
6
|
+
// session-control.ts and fork-commands.ts). The credential vault operations
|
|
7
|
+
// already live in credentials/api.ts + runtime/credentials.ts and take the node's
|
|
8
|
+
// credsDir explicitly, so this controller imports them directly and only injects
|
|
9
|
+
// the handful of daemon bits (credsDir, event emit, and the post-auth refresh /
|
|
10
|
+
// provider-list helpers) as composition deps.
|
|
11
|
+
import { testProviderCredential } from "../runtime/credentials.js";
|
|
12
|
+
import { exportAccountApiKeys, exportRecordTombstones, listCredentialRecords, removeProviderCredential, setProviderApiKeyLabeled, setProviderReferenceLabeled, setCredentialSync, setCredentialUnattended, getCredentialPresets, setActiveCredentialPreset, setCredentialPresetMapping, } from "../credentials/api.js";
|
|
13
|
+
export function createCredentialCommands(deps) {
|
|
14
|
+
const { credsDir } = deps;
|
|
15
|
+
return {
|
|
16
|
+
async "credentials.list"() {
|
|
17
|
+
deps.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
18
|
+
},
|
|
19
|
+
async "credentials.account.export"(_msg, ctx) {
|
|
20
|
+
// This reply travels inside the already-paired E2E node channel. It contains
|
|
21
|
+
// API keys only; OAuth/ref/node-local material is excluded by the API.
|
|
22
|
+
ctx.reply({ type: "credentials.account.export", requestId: _msg.requestId, entries: await exportAccountApiKeys(credsDir), records: await listCredentialRecords(credsDir), deletedAt: await exportRecordTombstones(credsDir) });
|
|
23
|
+
},
|
|
24
|
+
async "credential.set"(msg, ctx) {
|
|
25
|
+
try {
|
|
26
|
+
const provider = String(msg.provider ?? "").trim().toLowerCase();
|
|
27
|
+
const label = String(msg.label ?? "");
|
|
28
|
+
const ref = typeof msg.ref === "string" ? msg.ref.trim() : "";
|
|
29
|
+
const requestedSync = msg.sync === "account" || msg.sync === "node" ? msg.sync : undefined;
|
|
30
|
+
// Persist the secret and its requested tier atomically. Creating a
|
|
31
|
+
// machine-only credential must never leave an account-tier crash window.
|
|
32
|
+
if (ref)
|
|
33
|
+
await setProviderReferenceLabeled(credsDir, provider, label, ref, requestedSync);
|
|
34
|
+
else
|
|
35
|
+
await setProviderApiKeyLabeled(credsDir, provider, label, String(msg.key ?? ""), requestedSync);
|
|
36
|
+
await deps.pushModelAuthToControlPlane();
|
|
37
|
+
await deps.refreshSessionAfterAuth();
|
|
38
|
+
deps.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
39
|
+
deps.broadcast({ type: "providers.list", providers: await deps.listProvidersUnified() });
|
|
40
|
+
ctx.reply({ type: "credential.set.ok", requestId: msg.requestId });
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
44
|
+
deps.sendEvent({ type: "session.error", error: message });
|
|
45
|
+
ctx.reply({ type: "credential.set.error", requestId: msg.requestId, error: message });
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
async "credential.remove"(msg, ctx) {
|
|
49
|
+
try {
|
|
50
|
+
await removeProviderCredential(credsDir, String(msg.provider ?? ""), String(msg.label ?? ""));
|
|
51
|
+
await deps.pushModelAuthToControlPlane();
|
|
52
|
+
await deps.refreshSessionAfterAuth();
|
|
53
|
+
deps.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
54
|
+
deps.broadcast({ type: "providers.list", providers: await deps.listProvidersUnified() });
|
|
55
|
+
ctx.reply({ type: "credential.remove.ok", requestId: msg.requestId });
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
59
|
+
deps.sendEvent({ type: "session.error", error: message });
|
|
60
|
+
ctx.reply({ type: "credential.remove.error", requestId: msg.requestId, error: message });
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
async "credential.sync.set"(msg, ctx) {
|
|
64
|
+
try {
|
|
65
|
+
const sync = msg.sync === "node" ? "node" : "account";
|
|
66
|
+
await setCredentialSync(credsDir, String(msg.provider ?? ""), String(msg.label ?? ""), sync);
|
|
67
|
+
await deps.pushModelAuthToControlPlane();
|
|
68
|
+
deps.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
69
|
+
ctx.reply({ type: "credential.sync.set.ok", requestId: msg.requestId });
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
73
|
+
deps.sendEvent({ type: "session.error", error: message });
|
|
74
|
+
ctx.reply({ type: "credential.sync.set.error", requestId: msg.requestId, error: message });
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
async "credential.unattended.set"(msg, ctx) {
|
|
78
|
+
try {
|
|
79
|
+
await setCredentialUnattended(credsDir, String(msg.provider ?? ""), String(msg.label ?? ""), msg.unattended === true);
|
|
80
|
+
await deps.pushModelAuthToControlPlane();
|
|
81
|
+
deps.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
82
|
+
ctx.reply({ type: "credential.unattended.set.ok", requestId: msg.requestId });
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
86
|
+
ctx.reply({ type: "credential.unattended.set.error", requestId: msg.requestId, error: message });
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
// "Test connection": a bounded, non-secret liveness probe for one credential
|
|
90
|
+
// record (see credentials/api.ts testCredential). The reply carries only
|
|
91
|
+
// ok/at/reason — the credential's own token never leaves this handler.
|
|
92
|
+
async "credential.test"(msg, ctx) {
|
|
93
|
+
const provider = String(msg.provider ?? "").trim().toLowerCase();
|
|
94
|
+
const label = String(msg.label ?? "");
|
|
95
|
+
try {
|
|
96
|
+
const result = await testProviderCredential(credsDir, provider, label);
|
|
97
|
+
ctx.reply({ type: "credential.test.result", requestId: msg.requestId, provider, label, ...result });
|
|
98
|
+
deps.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
ctx.reply({ type: "credential.test.result", requestId: msg.requestId, provider, label, ok: false, at: Date.now(), reason: "network_error" });
|
|
102
|
+
deps.sendEvent({ type: "session.error", error: error instanceof Error ? error.message : String(error) });
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
async "credentials.presets.get"() {
|
|
106
|
+
deps.sendEvent({ type: "credentials.presets", presets: getCredentialPresets(credsDir) });
|
|
107
|
+
},
|
|
108
|
+
async "credentials.presets.setActive"(msg) {
|
|
109
|
+
try {
|
|
110
|
+
setActiveCredentialPreset(credsDir, String(msg.active ?? ""));
|
|
111
|
+
await deps.refreshSessionAfterAuth();
|
|
112
|
+
deps.sendEvent({ type: "credentials.presets", presets: getCredentialPresets(credsDir) });
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
deps.sendEvent({ type: "session.error", error: error instanceof Error ? error.message : String(error) });
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
async "credentials.presets.setMapping"(msg, ctx) {
|
|
119
|
+
try {
|
|
120
|
+
setCredentialPresetMapping(credsDir, String(msg.preset ?? ""), String(msg.provider ?? ""), String(msg.label ?? ""));
|
|
121
|
+
await deps.refreshSessionAfterAuth();
|
|
122
|
+
deps.sendEvent({ type: "credentials.presets", presets: getCredentialPresets(credsDir) });
|
|
123
|
+
ctx.reply({ type: "credentials.presets.setMapping.ok", requestId: msg.requestId });
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
127
|
+
ctx.reply({ type: "credentials.presets.setMapping.error", requestId: msg.requestId, error: message });
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -98,6 +98,7 @@ import { createAccessDeviceController, createLinkedDeviceController } from "./co
|
|
|
98
98
|
import { createSessionControlCommands } from "./controllers/session-control.js";
|
|
99
99
|
import { createForkCommands } from "./controllers/fork-commands.js";
|
|
100
100
|
import { createGithubCommands } from "./controllers/github-commands.js";
|
|
101
|
+
import { createCredentialCommands } from "./controllers/credential-commands.js";
|
|
101
102
|
import { MetadataStore } from "./metadata.js";
|
|
102
103
|
import { resolveResumeRef, resumeRefFor } from "./session-ref.js";
|
|
103
104
|
import { materializeFork } from "./session/fork.js";
|
|
@@ -1952,6 +1953,15 @@ const RELAY_COMMANDS = {
|
|
|
1952
1953
|
connectExistingApp,
|
|
1953
1954
|
disconnectGithubApp,
|
|
1954
1955
|
}),
|
|
1956
|
+
// Credential CRUD + presets cluster, extracted to controllers/credential-commands.ts.
|
|
1957
|
+
...createCredentialCommands({
|
|
1958
|
+
credsDir,
|
|
1959
|
+
sendEvent: (event) => relay?.sendEvent(event),
|
|
1960
|
+
broadcast,
|
|
1961
|
+
pushModelAuthToControlPlane,
|
|
1962
|
+
refreshSessionAfterAuth,
|
|
1963
|
+
listProvidersUnified,
|
|
1964
|
+
}),
|
|
1955
1965
|
// Live-stream gap recovery: replay the session.events a client missed after the
|
|
1956
1966
|
// last seq it holds, or tell it to full-resync (mode:"reset") when the ring has
|
|
1957
1967
|
// evicted past that point. Answers only the caller (ctx.reply); other clients
|
|
@@ -2581,121 +2591,6 @@ const RELAY_COMMANDS = {
|
|
|
2581
2591
|
relay?.sendEvent({ type: "session.error", error: error instanceof Error ? error.message : String(error) });
|
|
2582
2592
|
}
|
|
2583
2593
|
},
|
|
2584
|
-
// --- Multiple credentials per provider (labeled). ----------------------
|
|
2585
|
-
async "credentials.list"() {
|
|
2586
|
-
relay?.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
2587
|
-
},
|
|
2588
|
-
async "credentials.account.export"(_msg, ctx) {
|
|
2589
|
-
// This reply travels inside the already-paired E2E node channel. It contains
|
|
2590
|
-
// API keys only; OAuth/ref/node-local material is excluded by the API.
|
|
2591
|
-
ctx.reply({ type: "credentials.account.export", requestId: _msg.requestId, entries: await exportAccountApiKeys(credsDir), records: await listCredentialRecords(credsDir), deletedAt: await exportRecordTombstones(credsDir) });
|
|
2592
|
-
},
|
|
2593
|
-
async "credential.set"(msg, ctx) {
|
|
2594
|
-
try {
|
|
2595
|
-
const provider = String(msg.provider ?? "").trim().toLowerCase();
|
|
2596
|
-
const label = String(msg.label ?? "");
|
|
2597
|
-
const ref = typeof msg.ref === "string" ? msg.ref.trim() : "";
|
|
2598
|
-
const requestedSync = msg.sync === "account" || msg.sync === "node" ? msg.sync : undefined;
|
|
2599
|
-
// Persist the secret and its requested tier atomically. Creating a
|
|
2600
|
-
// machine-only credential must never leave an account-tier crash window.
|
|
2601
|
-
if (ref)
|
|
2602
|
-
await setProviderReferenceLabeled(credsDir, provider, label, ref, requestedSync);
|
|
2603
|
-
else
|
|
2604
|
-
await setProviderApiKeyLabeled(credsDir, provider, label, String(msg.key ?? ""), requestedSync);
|
|
2605
|
-
await pushModelAuthToControlPlane();
|
|
2606
|
-
await refreshSessionAfterAuth();
|
|
2607
|
-
relay?.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
2608
|
-
broadcast({ type: "providers.list", providers: await listProvidersUnified() });
|
|
2609
|
-
ctx.reply({ type: "credential.set.ok", requestId: msg.requestId });
|
|
2610
|
-
}
|
|
2611
|
-
catch (error) {
|
|
2612
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2613
|
-
relay?.sendEvent({ type: "session.error", error: message });
|
|
2614
|
-
ctx.reply({ type: "credential.set.error", requestId: msg.requestId, error: message });
|
|
2615
|
-
}
|
|
2616
|
-
},
|
|
2617
|
-
async "credential.remove"(msg, ctx) {
|
|
2618
|
-
try {
|
|
2619
|
-
await removeProviderCredential(credsDir, String(msg.provider ?? ""), String(msg.label ?? ""));
|
|
2620
|
-
await pushModelAuthToControlPlane();
|
|
2621
|
-
await refreshSessionAfterAuth();
|
|
2622
|
-
relay?.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
2623
|
-
broadcast({ type: "providers.list", providers: await listProvidersUnified() });
|
|
2624
|
-
ctx.reply({ type: "credential.remove.ok", requestId: msg.requestId });
|
|
2625
|
-
}
|
|
2626
|
-
catch (error) {
|
|
2627
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2628
|
-
relay?.sendEvent({ type: "session.error", error: message });
|
|
2629
|
-
ctx.reply({ type: "credential.remove.error", requestId: msg.requestId, error: message });
|
|
2630
|
-
}
|
|
2631
|
-
},
|
|
2632
|
-
async "credential.sync.set"(msg, ctx) {
|
|
2633
|
-
try {
|
|
2634
|
-
const sync = msg.sync === "node" ? "node" : "account";
|
|
2635
|
-
await setCredentialSync(credsDir, String(msg.provider ?? ""), String(msg.label ?? ""), sync);
|
|
2636
|
-
await pushModelAuthToControlPlane();
|
|
2637
|
-
relay?.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
2638
|
-
ctx.reply({ type: "credential.sync.set.ok", requestId: msg.requestId });
|
|
2639
|
-
}
|
|
2640
|
-
catch (error) {
|
|
2641
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2642
|
-
relay?.sendEvent({ type: "session.error", error: message });
|
|
2643
|
-
ctx.reply({ type: "credential.sync.set.error", requestId: msg.requestId, error: message });
|
|
2644
|
-
}
|
|
2645
|
-
},
|
|
2646
|
-
async "credential.unattended.set"(msg, ctx) {
|
|
2647
|
-
try {
|
|
2648
|
-
await setCredentialUnattended(credsDir, String(msg.provider ?? ""), String(msg.label ?? ""), msg.unattended === true);
|
|
2649
|
-
await pushModelAuthToControlPlane();
|
|
2650
|
-
relay?.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
2651
|
-
ctx.reply({ type: "credential.unattended.set.ok", requestId: msg.requestId });
|
|
2652
|
-
}
|
|
2653
|
-
catch (error) {
|
|
2654
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2655
|
-
ctx.reply({ type: "credential.unattended.set.error", requestId: msg.requestId, error: message });
|
|
2656
|
-
}
|
|
2657
|
-
},
|
|
2658
|
-
// "Test connection": a bounded, non-secret liveness probe for one credential
|
|
2659
|
-
// record (see credentials/api.ts testCredential). The reply carries only
|
|
2660
|
-
// ok/at/reason — the credential's own token never leaves this handler.
|
|
2661
|
-
async "credential.test"(msg, ctx) {
|
|
2662
|
-
const provider = String(msg.provider ?? "").trim().toLowerCase();
|
|
2663
|
-
const label = String(msg.label ?? "");
|
|
2664
|
-
try {
|
|
2665
|
-
const result = await testProviderCredential(credsDir, provider, label);
|
|
2666
|
-
ctx.reply({ type: "credential.test.result", requestId: msg.requestId, provider, label, ...result });
|
|
2667
|
-
relay?.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
2668
|
-
}
|
|
2669
|
-
catch (error) {
|
|
2670
|
-
ctx.reply({ type: "credential.test.result", requestId: msg.requestId, provider, label, ok: false, at: Date.now(), reason: "network_error" });
|
|
2671
|
-
relay?.sendEvent({ type: "session.error", error: error instanceof Error ? error.message : String(error) });
|
|
2672
|
-
}
|
|
2673
|
-
},
|
|
2674
|
-
async "credentials.presets.get"() {
|
|
2675
|
-
relay?.sendEvent({ type: "credentials.presets", presets: getCredentialPresets(credsDir) });
|
|
2676
|
-
},
|
|
2677
|
-
async "credentials.presets.setActive"(msg) {
|
|
2678
|
-
try {
|
|
2679
|
-
setActiveCredentialPreset(credsDir, String(msg.active ?? ""));
|
|
2680
|
-
await refreshSessionAfterAuth();
|
|
2681
|
-
relay?.sendEvent({ type: "credentials.presets", presets: getCredentialPresets(credsDir) });
|
|
2682
|
-
}
|
|
2683
|
-
catch (error) {
|
|
2684
|
-
relay?.sendEvent({ type: "session.error", error: error instanceof Error ? error.message : String(error) });
|
|
2685
|
-
}
|
|
2686
|
-
},
|
|
2687
|
-
async "credentials.presets.setMapping"(msg, ctx) {
|
|
2688
|
-
try {
|
|
2689
|
-
setCredentialPresetMapping(credsDir, String(msg.preset ?? ""), String(msg.provider ?? ""), String(msg.label ?? ""));
|
|
2690
|
-
await refreshSessionAfterAuth();
|
|
2691
|
-
relay?.sendEvent({ type: "credentials.presets", presets: getCredentialPresets(credsDir) });
|
|
2692
|
-
ctx.reply({ type: "credentials.presets.setMapping.ok", requestId: msg.requestId });
|
|
2693
|
-
}
|
|
2694
|
-
catch (error) {
|
|
2695
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2696
|
-
ctx.reply({ type: "credentials.presets.setMapping.error", requestId: msg.requestId, error: message });
|
|
2697
|
-
}
|
|
2698
|
-
},
|
|
2699
2594
|
// --- Local / custom models (Bivy-owned registry; Pi is a projection). ---
|
|
2700
2595
|
async "models.custom.list"() {
|
|
2701
2596
|
relay?.sendEvent({ type: "models.custom.list", providers: await localModelSummaries() });
|
package/package.json
CHANGED