driggsby 0.1.11 → 0.1.13

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.
Files changed (46) hide show
  1. package/.gitignore +2 -0
  2. package/README.md +16 -11
  3. package/binary-install.js +212 -0
  4. package/binary.js +128 -0
  5. package/install.js +4 -0
  6. package/npm-shrinkwrap.json +545 -0
  7. package/package.json +53 -41
  8. package/run-driggsby.js +4 -0
  9. package/dist/auth/browser.js +0 -31
  10. package/dist/auth/config.js +0 -23
  11. package/dist/auth/discovery.js +0 -42
  12. package/dist/auth/dpop.js +0 -44
  13. package/dist/auth/login.js +0 -126
  14. package/dist/auth/loopback.js +0 -157
  15. package/dist/auth/oauth.js +0 -136
  16. package/dist/auth/pkce.js +0 -12
  17. package/dist/auth/url-security.js +0 -16
  18. package/dist/broker/authentication.js +0 -49
  19. package/dist/broker/client.js +0 -148
  20. package/dist/broker/daemon.js +0 -65
  21. package/dist/broker/file-secret-store.js +0 -130
  22. package/dist/broker/installation.js +0 -154
  23. package/dist/broker/ipc.js +0 -12
  24. package/dist/broker/keyring-secret-store.js +0 -34
  25. package/dist/broker/launch.js +0 -35
  26. package/dist/broker/lock.js +0 -84
  27. package/dist/broker/remote-mcp.js +0 -129
  28. package/dist/broker/remote-session.js +0 -173
  29. package/dist/broker/resolve-secret-store.js +0 -52
  30. package/dist/broker/secret-store.js +0 -13
  31. package/dist/broker/server.js +0 -177
  32. package/dist/broker/session.js +0 -31
  33. package/dist/broker/test-support.js +0 -258
  34. package/dist/broker/types.js +0 -1
  35. package/dist/cli/commands/broker-daemon.js +0 -4
  36. package/dist/cli/commands/login.js +0 -35
  37. package/dist/cli/commands/logout.js +0 -20
  38. package/dist/cli/commands/status.js +0 -13
  39. package/dist/cli/format.js +0 -84
  40. package/dist/index.js +0 -39
  41. package/dist/lib/json-file.js +0 -36
  42. package/dist/lib/retry.js +0 -22
  43. package/dist/lib/runtime-paths.js +0 -62
  44. package/dist/lib/user-guidance.js +0 -19
  45. package/dist/shim/server.js +0 -143
  46. package/dist/shim/stdio-transport.js +0 -106
@@ -1,148 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import net from "node:net";
3
- import { readBrokerMetadata, readBrokerLocalAuthToken } from "./installation.js";
4
- import { decodeResponse, encodeRequest } from "./ipc.js";
5
- import { hashBrokerPayload, verifyBrokerProof } from "./authentication.js";
6
- export async function pingBroker(options) {
7
- const response = await sendBrokerRequest(options, "ping");
8
- if (response?.ok !== true) {
9
- return null;
10
- }
11
- return response.result;
12
- }
13
- export async function getBrokerStatus(options) {
14
- const response = await sendBrokerRequest(options, "get_status");
15
- if (response?.ok !== true) {
16
- return null;
17
- }
18
- return response.result.status;
19
- }
20
- export async function shutdownBroker(options) {
21
- const response = await sendBrokerRequest(options, "shutdown");
22
- return response?.ok === true && response.result.stopped;
23
- }
24
- export async function listBrokerTools(options) {
25
- const response = await sendBrokerRequest(options, "list_tools");
26
- if (response === null) {
27
- return null;
28
- }
29
- if (!response.ok) {
30
- throw new Error(response.error);
31
- }
32
- return response.result;
33
- }
34
- export async function callBrokerTool(options, toolName, args) {
35
- const response = await sendBrokerRequest(options, "call_tool", {
36
- toolName,
37
- ...(args === undefined ? {} : { args }),
38
- });
39
- if (response === null) {
40
- return null;
41
- }
42
- if (!response.ok) {
43
- throw new Error(response.error);
44
- }
45
- return response.result;
46
- }
47
- async function sendBrokerRequest(options, method, extraFields) {
48
- const metadata = await readBrokerMetadata(options.runtimePaths);
49
- if (metadata === null) {
50
- return null;
51
- }
52
- const authToken = await readBrokerLocalAuthToken(options.secretStore, metadata.brokerId);
53
- if (authToken === null) {
54
- return null;
55
- }
56
- const challenge = randomUUID();
57
- const request = buildBrokerRequest(method, authToken, challenge, extraFields);
58
- const response = await new Promise((resolve) => {
59
- const socket = net.createConnection(options.runtimePaths.socketPath);
60
- let buffer = "";
61
- let settled = false;
62
- const settle = (value) => {
63
- if (settled) {
64
- return;
65
- }
66
- settled = true;
67
- resolve(value);
68
- };
69
- socket.setEncoding("utf8");
70
- socket.setTimeout(1_500);
71
- socket.once("connect", () => {
72
- socket.write(encodeRequest(request));
73
- });
74
- socket.on("data", (chunk) => {
75
- buffer += chunk;
76
- const newlineIndex = buffer.indexOf("\n");
77
- if (newlineIndex < 0) {
78
- return;
79
- }
80
- const message = buffer.slice(0, newlineIndex);
81
- try {
82
- settle(decodeResponse(message));
83
- }
84
- catch {
85
- settle(null);
86
- }
87
- finally {
88
- socket.end();
89
- }
90
- });
91
- socket.once("timeout", () => {
92
- settle(null);
93
- socket.destroy();
94
- });
95
- socket.once("error", () => {
96
- settle(null);
97
- });
98
- socket.once("end", () => {
99
- if (buffer.length === 0) {
100
- settle(null);
101
- }
102
- });
103
- });
104
- if (response === null) {
105
- return null;
106
- }
107
- const expectedClaims = buildExpectedClaims(request, response, metadata.brokerId);
108
- const proofValid = await verifyBrokerProof(response.brokerProof, metadata.dpop.publicJwk, expectedClaims);
109
- return proofValid ? response : null;
110
- }
111
- function buildBrokerRequest(method, authToken, challenge, extraFields) {
112
- const baseRequest = {
113
- authToken,
114
- challenge,
115
- id: randomUUID(),
116
- };
117
- if (method === "call_tool") {
118
- const toolRequest = {
119
- ...baseRequest,
120
- method,
121
- toolName: extraFields?.toolName ?? "",
122
- };
123
- if (extraFields?.args !== undefined) {
124
- return {
125
- ...toolRequest,
126
- args: extraFields.args,
127
- };
128
- }
129
- return toolRequest;
130
- }
131
- return {
132
- ...baseRequest,
133
- method,
134
- };
135
- }
136
- function buildExpectedClaims(request, response, brokerId) {
137
- const payload = response.ok
138
- ? { ok: true, result: response.result }
139
- : { ok: false, error: response.error };
140
- return {
141
- aud: "driggsby-local-shim",
142
- challenge: request.challenge,
143
- payloadSha256: hashBrokerPayload(payload),
144
- requestId: request.id,
145
- requestMethod: request.method,
146
- sub: brokerId,
147
- };
148
- }
@@ -1,65 +0,0 @@
1
- import { buildBrokerStatus, ensureBrokerInstallation, readBrokerDpopKeyPair, readBrokerLocalAuthToken, readBrokerPrivateJwk, } from "./installation.js";
2
- import { BrokerRemoteSessionManager } from "./remote-session.js";
3
- import { callRemoteTool, listRemoteTools } from "./remote-mcp.js";
4
- import { resolveSecretStore } from "./resolve-secret-store.js";
5
- import { LocalBrokerServer } from "./server.js";
6
- import { buildReauthenticationRequiredMessage } from "../lib/user-guidance.js";
7
- export async function runBrokerDaemon(runtimePaths) {
8
- const secretStore = (await resolveSecretStore(runtimePaths)).store;
9
- const metadata = await ensureBrokerInstallation(runtimePaths, secretStore);
10
- const localAuthToken = await readRequiredLocalAuthToken(secretStore, metadata.brokerId);
11
- const privateJwk = await readRequiredPrivateJwk(secretStore, metadata.brokerId);
12
- const dpopKeyPair = await readRequiredDpopKeyPair(runtimePaths, secretStore, metadata.brokerId);
13
- const remoteSessionManager = new BrokerRemoteSessionManager({
14
- brokerId: metadata.brokerId,
15
- runtimePaths,
16
- secretStore,
17
- });
18
- const server = new LocalBrokerServer({
19
- brokerId: metadata.brokerId,
20
- callTool: async (toolName, args) => {
21
- const session = await remoteSessionManager.ensureFreshSession();
22
- return await callRemoteTool(session, dpopKeyPair, toolName, args);
23
- },
24
- localAuthToken,
25
- listTools: async () => {
26
- const session = await remoteSessionManager.ensureFreshSession();
27
- return await listRemoteTools(session, dpopKeyPair);
28
- },
29
- privateJwk,
30
- runtimePaths,
31
- statusProvider: async () => await buildBrokerStatus(runtimePaths, secretStore, true),
32
- });
33
- await server.listen();
34
- await new Promise((resolve, reject) => {
35
- const shutdown = () => {
36
- void server
37
- .close()
38
- .then(resolve)
39
- .catch(reject);
40
- };
41
- process.once("SIGINT", shutdown);
42
- process.once("SIGTERM", shutdown);
43
- });
44
- }
45
- async function readRequiredLocalAuthToken(secretStore, brokerId) {
46
- const localAuthToken = await readBrokerLocalAuthToken(secretStore, brokerId);
47
- if (localAuthToken === null) {
48
- throw new Error(buildReauthenticationRequiredMessage("The local broker auth state is incomplete"));
49
- }
50
- return localAuthToken;
51
- }
52
- async function readRequiredPrivateJwk(secretStore, brokerId) {
53
- const privateJwkRaw = await readBrokerPrivateJwk(secretStore, brokerId);
54
- if (privateJwkRaw === null) {
55
- throw new Error(buildReauthenticationRequiredMessage("The local broker signing key is missing"));
56
- }
57
- return JSON.parse(privateJwkRaw);
58
- }
59
- async function readRequiredDpopKeyPair(runtimePaths, secretStore, brokerId) {
60
- const dpopKeyPair = await readBrokerDpopKeyPair(runtimePaths, secretStore, brokerId);
61
- if (dpopKeyPair === null) {
62
- throw new Error(buildReauthenticationRequiredMessage("The local broker DPoP key is missing"));
63
- }
64
- return dpopKeyPair;
65
- }
@@ -1,130 +0,0 @@
1
- import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
2
- import { promises as fs } from "node:fs";
3
- import path from "node:path";
4
- import { readJsonFile, removeFileIfPresent, writeJsonFile, } from "../lib/json-file.js";
5
- const FILE_SECRET_KEY_BYTES = 32;
6
- const FILE_SECRET_STORE_INCOMPLETE_MESSAGE = "The local Driggsby file-backed secret store is incomplete. Run `driggsby logout` and then `driggsby login`.";
7
- const FILE_SECRET_STORE_INVALID_MESSAGE = "The local Driggsby file-backed secret store is invalid. Run `driggsby logout` and then `driggsby login`.";
8
- const FILE_SECRET_STORE_SCHEMA_VERSION = 1;
9
- const FILE_SECRET_IV_BYTES = 12;
10
- export class FileSecretStore {
11
- encryptionKeyPath;
12
- secretsPath;
13
- constructor(runtimePaths) {
14
- this.encryptionKeyPath = path.join(runtimePaths.stateDir, "broker-secrets.key");
15
- this.secretsPath = path.join(runtimePaths.configDir, "broker-secrets.json");
16
- }
17
- async hasStoredSecrets() {
18
- const storedSecrets = await this.readStoredSecretsFile();
19
- return Object.keys(storedSecrets?.secrets ?? {}).length > 0;
20
- }
21
- async setSecret(account, secret) {
22
- const encryptionKey = await this.readOrCreateEncryptionKey();
23
- const storedSecrets = (await this.readStoredSecretsFile()) ?? {
24
- schemaVersion: FILE_SECRET_STORE_SCHEMA_VERSION,
25
- secrets: {},
26
- };
27
- storedSecrets.secrets[account] = encryptSecret(secret, encryptionKey);
28
- await writeJsonFile(this.secretsPath, storedSecrets);
29
- }
30
- async getSecret(account) {
31
- const storedSecrets = await this.readStoredSecretsFile();
32
- const encryptedSecret = storedSecrets?.secrets[account];
33
- if (encryptedSecret === undefined) {
34
- return null;
35
- }
36
- const encryptionKey = await this.readEncryptionKey(true);
37
- if (encryptionKey === null) {
38
- throw new Error(FILE_SECRET_STORE_INCOMPLETE_MESSAGE);
39
- }
40
- return decryptSecret(encryptedSecret, encryptionKey);
41
- }
42
- async deleteSecret(account) {
43
- const storedSecrets = await this.readStoredSecretsFile();
44
- if (storedSecrets?.secrets[account] === undefined) {
45
- return false;
46
- }
47
- const remainingSecrets = Object.fromEntries(Object.entries(storedSecrets.secrets).filter(([storedAccount]) => storedAccount !== account));
48
- if (Object.keys(remainingSecrets).length === 0) {
49
- await Promise.all([
50
- removeFileIfPresent(this.secretsPath),
51
- removeFileIfPresent(this.encryptionKeyPath),
52
- ]);
53
- return true;
54
- }
55
- await writeJsonFile(this.secretsPath, {
56
- ...storedSecrets,
57
- secrets: remainingSecrets,
58
- });
59
- return true;
60
- }
61
- async readStoredSecretsFile() {
62
- return await readJsonFile(this.secretsPath);
63
- }
64
- async readEncryptionKey(errorIfMissing) {
65
- try {
66
- const encodedKey = await fs.readFile(this.encryptionKeyPath, "utf8");
67
- const encryptionKey = Buffer.from(encodedKey.trim(), "base64");
68
- if (encryptionKey.length !== FILE_SECRET_KEY_BYTES) {
69
- throw new Error(FILE_SECRET_STORE_INVALID_MESSAGE);
70
- }
71
- return encryptionKey;
72
- }
73
- catch (error) {
74
- if (!errorIfMissing &&
75
- error instanceof Error &&
76
- "code" in error &&
77
- error.code === "ENOENT") {
78
- return null;
79
- }
80
- if (errorIfMissing &&
81
- error instanceof Error &&
82
- "code" in error &&
83
- error.code === "ENOENT") {
84
- throw new Error(FILE_SECRET_STORE_INCOMPLETE_MESSAGE);
85
- }
86
- throw error;
87
- }
88
- }
89
- async readOrCreateEncryptionKey() {
90
- const existingKey = await this.readEncryptionKey(false);
91
- if (existingKey !== null) {
92
- return existingKey;
93
- }
94
- await fs.mkdir(path.dirname(this.secretsPath), {
95
- recursive: true,
96
- mode: 0o700,
97
- });
98
- await fs.mkdir(path.dirname(this.encryptionKeyPath), {
99
- recursive: true,
100
- mode: 0o700,
101
- });
102
- const encryptionKey = randomBytes(FILE_SECRET_KEY_BYTES);
103
- await fs.writeFile(this.encryptionKeyPath, encryptionKey.toString("base64"), {
104
- encoding: "utf8",
105
- mode: 0o600,
106
- });
107
- return encryptionKey;
108
- }
109
- }
110
- function encryptSecret(secret, encryptionKey) {
111
- const iv = randomBytes(FILE_SECRET_IV_BYTES);
112
- const cipher = createCipheriv("aes-256-gcm", encryptionKey, iv);
113
- const ciphertext = Buffer.concat([
114
- cipher.update(secret, "utf8"),
115
- cipher.final(),
116
- ]);
117
- return {
118
- authTagBase64: cipher.getAuthTag().toString("base64"),
119
- ciphertextBase64: ciphertext.toString("base64"),
120
- ivBase64: iv.toString("base64"),
121
- };
122
- }
123
- function decryptSecret(encryptedSecret, encryptionKey) {
124
- const decipher = createDecipheriv("aes-256-gcm", encryptionKey, Buffer.from(encryptedSecret.ivBase64, "base64"));
125
- decipher.setAuthTag(Buffer.from(encryptedSecret.authTagBase64, "base64"));
126
- return Buffer.concat([
127
- decipher.update(Buffer.from(encryptedSecret.ciphertextBase64, "base64")),
128
- decipher.final(),
129
- ]).toString("utf8");
130
- }
@@ -1,154 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import { promises as fs } from "node:fs";
3
- import { generateDpopKeyMaterial } from "../auth/dpop.js";
4
- import { readJsonFile, removeFileIfPresent, writeJsonFile } from "../lib/json-file.js";
5
- import { generateLocalAuthToken } from "./authentication.js";
6
- import { inspectRemoteSessionReadiness } from "./remote-session.js";
7
- import { buildNotConnectedReadiness } from "./remote-session.js";
8
- import { clearBrokerRemoteSession, readBrokerRemoteSession, } from "./session.js";
9
- const LOCAL_AUTH_TOKEN_ACCOUNT_SUFFIX = "local-auth-token";
10
- const PRIVATE_KEY_ACCOUNT_SUFFIX = "dpop-private-jwk";
11
- export async function ensureBrokerInstallation(runtimePaths, secretStore) {
12
- const readiness = await inspectBrokerReadiness(runtimePaths, secretStore);
13
- if (readiness.installed && readiness.privateKeyPresent) {
14
- const metadata = await readBrokerMetadata(runtimePaths);
15
- if (metadata !== null) {
16
- return metadata;
17
- }
18
- }
19
- const brokerId = randomUUID();
20
- const dpop = await generateDpopKeyMaterial();
21
- const metadata = {
22
- schemaVersion: 1,
23
- brokerId,
24
- createdAt: new Date().toISOString(),
25
- dpop: {
26
- algorithm: dpop.algorithm,
27
- publicJwk: dpop.publicJwk,
28
- thumbprint: dpop.thumbprint,
29
- },
30
- };
31
- await secretStore.setSecret(localAuthTokenAccountName(brokerId), generateLocalAuthToken());
32
- await secretStore.setSecret(privateKeyAccountName(brokerId), JSON.stringify(dpop.privateJwk));
33
- await writeJsonFile(runtimePaths.metadataPath, metadata);
34
- return metadata;
35
- }
36
- export async function inspectBrokerReadiness(runtimePaths, secretStore) {
37
- const metadata = await readBrokerMetadata(runtimePaths);
38
- if (metadata === null) {
39
- return {
40
- installed: false,
41
- localAuthTokenPresent: false,
42
- privateKeyPresent: false,
43
- remoteSessionPresent: false,
44
- };
45
- }
46
- const localAuthTokenPresent = (await secretStore.getSecret(localAuthTokenAccountName(metadata.brokerId))) !== null;
47
- const privateKeyPresent = (await secretStore.getSecret(privateKeyAccountName(metadata.brokerId))) !== null;
48
- const remoteSessionPresent = (await readBrokerRemoteSession(secretStore, metadata.brokerId)) !== null;
49
- return {
50
- installed: localAuthTokenPresent && privateKeyPresent,
51
- brokerId: metadata.brokerId,
52
- dpopThumbprint: metadata.dpop.thumbprint,
53
- localAuthTokenPresent,
54
- privateKeyPresent,
55
- remoteSessionPresent,
56
- };
57
- }
58
- export async function buildBrokerStatus(runtimePaths, secretStore, brokerRunning) {
59
- const readiness = await inspectBrokerReadiness(runtimePaths, secretStore);
60
- const remoteReadiness = readiness.brokerId === undefined
61
- ? buildNotConnectedReadiness()
62
- : await inspectRemoteSessionReadiness({
63
- brokerId: readiness.brokerId,
64
- refreshIfNeeded: true,
65
- runtimePaths,
66
- secretStore,
67
- });
68
- const status = {
69
- installed: readiness.installed && readiness.privateKeyPresent,
70
- brokerRunning,
71
- ...(remoteReadiness.nextStepCommand === undefined
72
- ? {}
73
- : { nextStepCommand: remoteReadiness.nextStepCommand }),
74
- remoteAccessDetail: remoteReadiness.detail,
75
- remoteAccessState: remoteReadiness.state,
76
- remoteMcpReady: remoteReadiness.ready,
77
- socketPath: runtimePaths.socketPath,
78
- ...(readiness.brokerId === undefined ? {} : { brokerId: readiness.brokerId }),
79
- ...(readiness.dpopThumbprint === undefined
80
- ? {}
81
- : { dpopThumbprint: readiness.dpopThumbprint }),
82
- ...(remoteReadiness.session === undefined
83
- ? {}
84
- : { remoteSession: remoteReadiness.session }),
85
- };
86
- return status;
87
- }
88
- export async function resolveBrokerStatusForDisplay(runtimePaths, secretStore, liveStatus) {
89
- const brokerRunning = liveStatus !== null;
90
- if (hasActionableRemoteStatus(liveStatus)) {
91
- return liveStatus;
92
- }
93
- return await buildBrokerStatus(runtimePaths, secretStore, brokerRunning);
94
- }
95
- export async function clearBrokerInstallation(runtimePaths, secretStore) {
96
- const metadata = await readBrokerMetadata(runtimePaths);
97
- if (metadata !== null) {
98
- await clearBrokerRemoteSession(secretStore, metadata.brokerId);
99
- await secretStore.deleteSecret(localAuthTokenAccountName(metadata.brokerId));
100
- await secretStore.deleteSecret(privateKeyAccountName(metadata.brokerId));
101
- }
102
- await removeFileIfPresent(runtimePaths.metadataPath);
103
- if (process.platform !== "win32") {
104
- await removeFileIfPresent(runtimePaths.socketPath);
105
- }
106
- await removeEmptyDirectory(runtimePaths.configDir);
107
- await removeEmptyDirectory(runtimePaths.stateDir);
108
- }
109
- export async function readBrokerMetadata(runtimePaths) {
110
- return await readJsonFile(runtimePaths.metadataPath);
111
- }
112
- export async function readBrokerLocalAuthToken(secretStore, brokerId) {
113
- return await secretStore.getSecret(localAuthTokenAccountName(brokerId));
114
- }
115
- export async function readBrokerPrivateJwk(secretStore, brokerId) {
116
- return await secretStore.getSecret(privateKeyAccountName(brokerId));
117
- }
118
- export async function readBrokerDpopKeyPair(runtimePaths, secretStore, brokerId) {
119
- const metadata = await readBrokerMetadata(runtimePaths);
120
- if (metadata?.brokerId !== brokerId) {
121
- return null;
122
- }
123
- const privateJwkRaw = await readBrokerPrivateJwk(secretStore, brokerId);
124
- if (privateJwkRaw === null) {
125
- return null;
126
- }
127
- return {
128
- privateJwk: JSON.parse(privateJwkRaw),
129
- publicJwk: metadata.dpop.publicJwk,
130
- };
131
- }
132
- function localAuthTokenAccountName(brokerId) {
133
- return `${brokerId}:${LOCAL_AUTH_TOKEN_ACCOUNT_SUFFIX}`;
134
- }
135
- function privateKeyAccountName(brokerId) {
136
- return `${brokerId}:${PRIVATE_KEY_ACCOUNT_SUFFIX}`;
137
- }
138
- async function removeEmptyDirectory(directoryPath) {
139
- try {
140
- await fs.rmdir(directoryPath);
141
- }
142
- catch (error) {
143
- if (!(error instanceof Error) ||
144
- !("code" in error) ||
145
- (error.code !== "ENOENT" && error.code !== "ENOTEMPTY")) {
146
- throw error;
147
- }
148
- }
149
- }
150
- function hasActionableRemoteStatus(status) {
151
- return (status !== null &&
152
- typeof status.remoteAccessDetail === "string" &&
153
- typeof status.remoteAccessState === "string");
154
- }
@@ -1,12 +0,0 @@
1
- export function encodeRequest(request) {
2
- return `${JSON.stringify(request)}\n`;
3
- }
4
- export function decodeResponse(raw) {
5
- return JSON.parse(raw);
6
- }
7
- export function decodeRequest(raw) {
8
- return JSON.parse(raw);
9
- }
10
- export function encodeResponse(response) {
11
- return `${JSON.stringify(response)}\n`;
12
- }
@@ -1,34 +0,0 @@
1
- import { randomBytes } from "node:crypto";
2
- export class KeyringSecretStore {
3
- serviceName;
4
- constructor(serviceName = "driggsby.local-broker") {
5
- this.serviceName = serviceName;
6
- }
7
- async isAvailable() {
8
- try {
9
- const AsyncEntry = await loadAsyncEntry();
10
- await new AsyncEntry(this.serviceName, `driggsby-probe-${randomBytes(12).toString("hex")}`).getPassword();
11
- return true;
12
- }
13
- catch {
14
- return false;
15
- }
16
- }
17
- async setSecret(account, secret) {
18
- const AsyncEntry = await loadAsyncEntry();
19
- await new AsyncEntry(this.serviceName, account).setPassword(secret);
20
- }
21
- async getSecret(account) {
22
- const AsyncEntry = await loadAsyncEntry();
23
- const secret = await new AsyncEntry(this.serviceName, account).getPassword();
24
- return secret ?? null;
25
- }
26
- async deleteSecret(account) {
27
- const AsyncEntry = await loadAsyncEntry();
28
- return await new AsyncEntry(this.serviceName, account).deleteCredential();
29
- }
30
- }
31
- async function loadAsyncEntry() {
32
- const keyringModule = await import("@napi-rs/keyring");
33
- return keyringModule.AsyncEntry;
34
- }
@@ -1,35 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { setTimeout as sleep } from "node:timers/promises";
3
- import { buildBrokerInvestigationMessage } from "../lib/user-guidance.js";
4
- import { getBrokerStatus, pingBroker } from "./client.js";
5
- export async function ensureBrokerRunning(options) {
6
- if ((await pingBroker({
7
- runtimePaths: options.runtimePaths,
8
- secretStore: options.secretStore,
9
- })) !== null) {
10
- return;
11
- }
12
- spawnBrokerDaemon(options.entrypointPath);
13
- const started = await waitForBroker(options.runtimePaths, options.secretStore, 4_000);
14
- if (!started) {
15
- throw new Error(buildBrokerInvestigationMessage("The local Driggsby broker did not start cleanly"));
16
- }
17
- }
18
- export async function waitForBroker(runtimePaths, secretStore, timeoutMs) {
19
- const deadline = Date.now() + timeoutMs;
20
- while (Date.now() < deadline) {
21
- if ((await getBrokerStatus({ runtimePaths, secretStore })) !== null) {
22
- return true;
23
- }
24
- await sleep(100);
25
- }
26
- return false;
27
- }
28
- function spawnBrokerDaemon(entrypointPath) {
29
- const child = spawn(process.execPath, [...process.execArgv, entrypointPath, "broker-daemon"], {
30
- detached: true,
31
- stdio: "ignore",
32
- windowsHide: true,
33
- });
34
- child.unref();
35
- }
@@ -1,84 +0,0 @@
1
- import { promises as fs } from "node:fs";
2
- export async function acquireBrokerLock(lockPath) {
3
- const initialHandle = await tryOpenExclusive(lockPath);
4
- if (initialHandle !== null) {
5
- return await createLease(lockPath, initialHandle);
6
- }
7
- if (!(await removeStaleLock(lockPath))) {
8
- return null;
9
- }
10
- const recoveredHandle = await tryOpenExclusive(lockPath);
11
- if (recoveredHandle === null) {
12
- return null;
13
- }
14
- return await createLease(lockPath, recoveredHandle);
15
- }
16
- async function createLease(lockPath, handle) {
17
- let released = false;
18
- await handle.truncate(0);
19
- await handle.writeFile(JSON.stringify({ pid: process.pid }));
20
- return {
21
- async release() {
22
- if (released) {
23
- return;
24
- }
25
- released = true;
26
- await handle.close();
27
- await removeFileIfPresent(lockPath);
28
- },
29
- };
30
- }
31
- async function tryOpenExclusive(lockPath) {
32
- try {
33
- return await fs.open(lockPath, "wx", 0o600);
34
- }
35
- catch (error) {
36
- if (isErrnoException(error) && error.code === "EEXIST") {
37
- return null;
38
- }
39
- throw error;
40
- }
41
- }
42
- async function removeStaleLock(lockPath) {
43
- const stalePid = await readLockPid(lockPath);
44
- if (stalePid === null || processAlive(stalePid)) {
45
- return false;
46
- }
47
- await removeFileIfPresent(lockPath);
48
- return true;
49
- }
50
- async function readLockPid(lockPath) {
51
- try {
52
- const contents = await fs.readFile(lockPath, "utf8");
53
- const parsed = JSON.parse(contents);
54
- return typeof parsed.pid === "number" ? parsed.pid : null;
55
- }
56
- catch (error) {
57
- if (isErrnoException(error) && error.code === "ENOENT") {
58
- return null;
59
- }
60
- return null;
61
- }
62
- }
63
- function processAlive(pid) {
64
- try {
65
- process.kill(pid, 0);
66
- return true;
67
- }
68
- catch {
69
- return false;
70
- }
71
- }
72
- async function removeFileIfPresent(filePath) {
73
- try {
74
- await fs.rm(filePath);
75
- }
76
- catch (error) {
77
- if (!(isErrnoException(error) && error.code === "ENOENT")) {
78
- throw error;
79
- }
80
- }
81
- }
82
- function isErrnoException(error) {
83
- return error instanceof Error && "code" in error;
84
- }