@whooshbang/sdk 1.0.0-rc.14 → 1.0.0-rc.18

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 CHANGED
@@ -78,6 +78,54 @@ both the previous and the current secret so a rotation overlap has no gap. Use
78
78
  `verifyWhooshBangWebhook` (or `verifyWebhookSignature`) for signature-only
79
79
  verification of any signed body.
80
80
 
81
+ ## Channel connections
82
+
83
+ `listConnectionProviders` reports what a project can connect to, the setup
84
+ modes each provider supports, and the WhooshBang-owned identities an
85
+ environment may select. `listChannelConnections`, `createChannelConnection`,
86
+ `getChannelConnection`, `updateChannelConnection`, and
87
+ `archiveChannelConnection` cover the per-environment lifecycle. Credential
88
+ material, and any reference to it, is structurally absent from every one of
89
+ these results — including for the shared identities WhooshBang operates.
90
+
91
+ Create takes an explicit idempotency key and returns a `pending` connection.
92
+ Update carries the `row_version` that was read, so a rename and a disable made
93
+ at the same time cannot silently undo each other. Archiving is terminal:
94
+ reconnecting later is a new connection, so consent a subscriber gave to the
95
+ archived identity is never revived under it.
96
+
97
+ `createChannelConnection` is discriminated on `mode`. `whooshbang_shared`
98
+ names a disclosed WhooshBang-owned identity by `shared_identity_id`.
99
+ `customer_byok` supplies the customer's own provider credential — for Telegram,
100
+ the bot token BotFather issued — and names no identity at all. The absence is
101
+ deliberate: an endpoint that digested a client-supplied identity would answer
102
+ "already claimed" for a bot the caller does not own, which would turn create
103
+ into an oracle for whether any given bot is connected to WhooshBang by anyone.
104
+ The identity is derived server-side from a provider-validated `getMe`. Pass
105
+ `confirm_webhook_takeover` only when the customer accepts that an existing
106
+ webhook on that bot will be replaced; without it, an occupied bot is refused.
107
+
108
+ `repairChannelConnection`, `rotateChannelConnectionCredential`,
109
+ `checkChannelConnectionHealth`, and `purgeChannelConnection` operate one
110
+ connection after it exists. Repair reprovisions the delivery path with the
111
+ credential already in custody. Rotate replaces that credential while keeping
112
+ the identity, so subscribers stay bound to what they consented to. Purge
113
+ releases the provider-side delivery path, destroys custody, and archives; it is
114
+ repeat-safe, so a caller that lost the first response can confirm the outcome.
115
+
116
+ All four are `connections:write`, including `checkChannelConnectionHealth`. A
117
+ health check makes a live provider call and advances the connection's recorded
118
+ health, so it is not a safe read; read `health` from `getChannelConnection`
119
+ when the last recorded answer is enough.
120
+
121
+ The Problem vocabulary is closed, so these refusals reuse published codes and
122
+ differ by `field_errors[].code`: a rejected credential is `request_invalid`
123
+ with `provider_credential_invalid`, an occupied webhook is `request_invalid`
124
+ with `webhook_takeover_required`, an identity already connected by anyone is
125
+ `idempotency_conflict` with `provider_identity_claimed`, an unreachable
126
+ provider is `provider_outcome_unknown`, and a rate-limited one is
127
+ `provider_retryable` with `retryAt`.
128
+
81
129
  ## Machine clients
82
130
 
83
131
  `createMachineCredential` generates the C0-03 secret with Web Crypto and
@@ -116,8 +164,9 @@ then revoke a replacement. Product delivery methods continue to use project-
116
164
  or machine-scoped credentials; the client does not persist either credential
117
165
  kind.
118
166
 
119
- Delivery, project creation, endpoint create/rotate, machine administration, and
120
- acknowledgement side-effects require an explicit idempotency key. Credential
167
+ Delivery, project creation, endpoint create/rotate, connection creation,
168
+ machine administration, and acknowledgement side-effects require an explicit
169
+ idempotency key. Credential
121
170
  lifecycle create/rotate/revoke calls follow their one-time-reveal or intrinsic
122
171
  repeat-safe contract instead. The client sends each call exactly once. A
123
172
  transport failure on a side effect raises an error with
@@ -158,6 +207,6 @@ node packages/sdk-js/examples/send-interactive-message.mjs
158
207
  ## Compatibility
159
208
 
160
209
  The exact runtime dependency is
161
- `@whooshbang/contracts@1.0.0-rc.13`. Successful payloads are
210
+ `@whooshbang/contracts@1.0.0-rc.16`. Successful payloads are
162
211
  validated with its packaged validators, while additive response fields remain
163
212
  available on the returned object.
@@ -0,0 +1,22 @@
1
+ /**
2
+ * A very small argv reader.
3
+ *
4
+ * `node:util`'s `parseArgs` is here and capable, but it wants the option
5
+ * vocabulary declared up front and this CLI's options differ per command. The
6
+ * whole grammar is `wb <words…> [--flag value] [--switch]`, which is a dozen
7
+ * lines to read directly and no dependency to carry into a package a library
8
+ * consumer also installs.
9
+ */
10
+ export interface ParsedArguments {
11
+ /** The command path: `["project", "create"]`. */
12
+ readonly words: readonly string[];
13
+ readonly options: ReadonlyMap<string, string>;
14
+ readonly switches: ReadonlySet<string>;
15
+ /** Values repeated across several `--flag value` pairs, e.g. `--scope`. */
16
+ readonly repeated: ReadonlyMap<string, readonly string[]>;
17
+ }
18
+ export declare function parseArguments(argv: readonly string[]): ParsedArguments;
19
+ export declare class UsageError extends Error {
20
+ constructor(message: string);
21
+ }
22
+ export declare function required(parsed: ParsedArguments, name: string, what: string): string;
@@ -0,0 +1,59 @@
1
+ const SWITCHES = new Set([
2
+ "help",
3
+ "json",
4
+ "version",
5
+ "credential-stdin",
6
+ "yes",
7
+ ]);
8
+ export function parseArguments(argv) {
9
+ const words = [];
10
+ const options = new Map();
11
+ const switches = new Set();
12
+ const repeated = new Map();
13
+ for (let index = 0; index < argv.length; index += 1) {
14
+ const token = argv[index];
15
+ if (!token.startsWith("--")) {
16
+ words.push(token);
17
+ continue;
18
+ }
19
+ const body = token.slice(2);
20
+ const equals = body.indexOf("=");
21
+ const name = equals === -1 ? body : body.slice(0, equals);
22
+ if (equals !== -1) {
23
+ const value = body.slice(equals + 1);
24
+ options.set(name, value);
25
+ repeated.set(name, [...(repeated.get(name) ?? []), value]);
26
+ continue;
27
+ }
28
+ if (SWITCHES.has(name)) {
29
+ switches.add(name);
30
+ continue;
31
+ }
32
+ const next = argv[index + 1];
33
+ if (next === undefined || next.startsWith("--")) {
34
+ // A bare unknown flag is a switch rather than an error: the command
35
+ // decides what it does and does not understand, and it can say so in its
36
+ // own words.
37
+ switches.add(name);
38
+ continue;
39
+ }
40
+ options.set(name, next);
41
+ repeated.set(name, [...(repeated.get(name) ?? []), next]);
42
+ index += 1;
43
+ }
44
+ return { options, repeated, switches, words };
45
+ }
46
+ export class UsageError extends Error {
47
+ constructor(message) {
48
+ super(message);
49
+ this.name = "UsageError";
50
+ }
51
+ }
52
+ export function required(parsed, name, what) {
53
+ const value = parsed.options.get(name);
54
+ if (value === undefined || value.length === 0) {
55
+ throw new UsageError(`--${name} is required: ${what}.`);
56
+ }
57
+ return value;
58
+ }
59
+ //# sourceMappingURL=arguments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arguments.js","sourceRoot":"","sources":["../../src/cli/arguments.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { type ParsedArguments } from "./arguments.js";
2
+ import { type Writer } from "./output.js";
3
+ import { resolveOrigin } from "./session.js";
4
+ export interface CommandContext {
5
+ readonly parsed: ParsedArguments;
6
+ readonly writer: Writer;
7
+ readonly origin: string;
8
+ readonly openUrl: (url: string) => Promise<void>;
9
+ readonly readStdin: () => Promise<string>;
10
+ readonly credentialsFile?: string;
11
+ }
12
+ export declare function resolveCommand(words: readonly string[]): ((context: CommandContext) => Promise<void>) | null;
13
+ export declare const COMMAND_NAMES: string[];
14
+ export { resolveOrigin };
@@ -0,0 +1,257 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { required, UsageError } from "./arguments.js";
3
+ import { emit, emitSecret, table } from "./output.js";
4
+ import { authorizedClient, resolveOrigin, signIn, signOut } from "./session.js";
5
+ /**
6
+ * Every side-effecting call needs an idempotency key, and a person running a
7
+ * command by hand has no opinion about one. Generating it means a retried
8
+ * `wb message send` after a dropped connection is a replay rather than a
9
+ * second message — but only within the one invocation, which is why `--key`
10
+ * exists for anybody scripting a genuinely repeatable operation.
11
+ */
12
+ function idempotency(parsed) {
13
+ return { idempotencyKey: parsed.options.get("key") ?? randomUUID() };
14
+ }
15
+ /**
16
+ * `connection` unless asked otherwise, which matches what the API does for an
17
+ * ordinary subscriber id. Naming it on every send keeps a script auditable:
18
+ * somebody reading it can see which transport it meant without knowing the
19
+ * defaulting rule.
20
+ */
21
+ function deliveryTarget(parsed) {
22
+ const value = parsed.options.get("target") ?? "connection";
23
+ if (value !== "connection" && value !== "simulator") {
24
+ throw new UsageError("--target must be connection or simulator.");
25
+ }
26
+ return value;
27
+ }
28
+ function environmentArguments(parsed) {
29
+ return {
30
+ environmentId: required(parsed, "environment", "the environment id the operation acts in"),
31
+ projectId: required(parsed, "project", "the project id"),
32
+ };
33
+ }
34
+ async function client(context) {
35
+ return authorizedClient({
36
+ ...(context.credentialsFile === undefined
37
+ ? {}
38
+ : { credentialsFile: context.credentialsFile }),
39
+ origin: context.origin,
40
+ });
41
+ }
42
+ const commands = {
43
+ async login(context) {
44
+ const scopes = context.parsed.repeated.get("scope");
45
+ const session = await signIn({
46
+ ...(context.credentialsFile === undefined
47
+ ? {}
48
+ : { credentialsFile: context.credentialsFile }),
49
+ openUrl: context.openUrl,
50
+ origin: context.origin,
51
+ ...(scopes && scopes.length > 0 ? { scopes } : {}),
52
+ });
53
+ const api = await client(context);
54
+ const account = await api.getAccount();
55
+ emit(context.writer, { account: account.id, origin: context.origin, scopes: session.scopes }, [
56
+ `Signed in to ${context.origin}.`,
57
+ `Account ${account.id}.`,
58
+ `Permissions: ${session.scopes.join(", ")}`,
59
+ ]);
60
+ },
61
+ async logout(context) {
62
+ const removed = await signOut(context.origin, context.credentialsFile);
63
+ emit(context.writer, { origin: context.origin, signedOut: removed }, [
64
+ removed
65
+ ? `Signed out of ${context.origin}.`
66
+ : `Not signed in to ${context.origin}.`,
67
+ ]);
68
+ },
69
+ async whoami(context) {
70
+ const account = await (await client(context)).getAccount();
71
+ emit(context.writer, account, [
72
+ `Account ${account.id}`,
73
+ ...(account.name ? [account.name] : []),
74
+ ]);
75
+ },
76
+ async "project list"(context) {
77
+ const projects = await (await client(context)).listProjects();
78
+ emit(context.writer, projects, table([
79
+ ["ID", "SLUG", "NAME"],
80
+ ...projects.items.map((project) => [
81
+ project.id,
82
+ project.slug,
83
+ project.name,
84
+ ]),
85
+ ]));
86
+ },
87
+ async "project create"(context) {
88
+ const name = context.parsed.words[2];
89
+ if (!name)
90
+ throw new UsageError("wb project create <name> [--slug <slug>]");
91
+ const slug = context.parsed.options.get("slug") ??
92
+ name
93
+ .toLowerCase()
94
+ .replace(/[^a-z0-9]+/gu, "-")
95
+ .replace(/^-+|-+$/gu, "");
96
+ const project = await (await client(context)).createProject({ name, slug }, idempotency(context.parsed));
97
+ emit(context.writer, project, [
98
+ `Created project ${project.id} (${project.slug}).`,
99
+ ...table([
100
+ ["ENVIRONMENT", "KIND", "DEFAULT NOTIFIER"],
101
+ ...project.environments.map((environment) => [
102
+ environment.id,
103
+ environment.kind,
104
+ environment.default_notifier.id,
105
+ ]),
106
+ ]),
107
+ ]);
108
+ },
109
+ async "credential create"(context) {
110
+ const { environmentId, projectId } = environmentArguments(context.parsed);
111
+ const label = required(context.parsed, "label", "what this credential is for");
112
+ // The contract types the scope list as a bounded tuple, so a plain
113
+ // string[] cannot be passed. The values are validated by the API either
114
+ // way; the assertion buys the caller a shape the compiler accepts.
115
+ const scopes = (context.parsed.repeated.get("scope") ?? [
116
+ "messages:write",
117
+ "messages:read",
118
+ "subscriptions:write",
119
+ "subscriptions:read",
120
+ ]);
121
+ const created = await (await client(context)).createApiCredential(projectId, environmentId, { label, scopes });
122
+ if (context.writer.json) {
123
+ emit(context.writer, created, []);
124
+ return;
125
+ }
126
+ emitSecret(context.writer, created.secret, "The complete secret is shown once and is not retrievable later. Store it now.");
127
+ },
128
+ async "connection list"(context) {
129
+ const { environmentId, projectId } = environmentArguments(context.parsed);
130
+ const connections = await (await client(context)).listChannelConnections(projectId, environmentId);
131
+ emit(context.writer, connections, table([
132
+ ["ID", "MODE", "STATUS", "NAME"],
133
+ ...connections.items.map((connection) => [
134
+ connection.id,
135
+ connection.mode,
136
+ connection.status,
137
+ connection.display_name,
138
+ ]),
139
+ ]));
140
+ },
141
+ async "connection providers"(context) {
142
+ const { environmentId, projectId } = environmentArguments(context.parsed);
143
+ const providers = await (await client(context)).listConnectionProviders(projectId, environmentId);
144
+ emit(context.writer, providers, table([
145
+ ["PROVIDER", "SHARED IDENTITY", "DISPLAY NAME"],
146
+ ...providers.items.flatMap((provider) => provider.shared_identities.map((identity) => [
147
+ provider.provider,
148
+ identity.id,
149
+ identity.display_name,
150
+ ])),
151
+ ]));
152
+ },
153
+ async "connection create"(context) {
154
+ const { environmentId, projectId } = environmentArguments(context.parsed);
155
+ const mode = required(context.parsed, "mode", "whooshbang_shared, customer_managed, or customer_byok");
156
+ const displayName = required(context.parsed, "name", "what to call this connection");
157
+ let request = {
158
+ display_name: displayName,
159
+ mode,
160
+ };
161
+ if (mode === "whooshbang_shared") {
162
+ request["shared_identity_id"] = required(context.parsed, "shared-identity", "which shared identity to select — see `wb connection providers`");
163
+ }
164
+ else if (mode === "customer_byok") {
165
+ if (!context.parsed.switches.has("credential-stdin")) {
166
+ throw new UsageError("customer_byok needs --credential-stdin, and the token on standard input. A bot token passed as an argument is visible to every process on the machine and lands in your shell history.");
167
+ }
168
+ request = {
169
+ ...request,
170
+ confirm_webhook_takeover: true,
171
+ credential: (await context.readStdin()).trim(),
172
+ };
173
+ }
174
+ else if (mode === "customer_managed") {
175
+ const username = context.parsed.options.get("username");
176
+ if (username)
177
+ request["suggested_username"] = username;
178
+ }
179
+ const connection = await (await client(context)).createChannelConnection(projectId, environmentId, request, idempotency(context.parsed));
180
+ emit(context.writer, connection, [
181
+ `Created connection ${connection.id} (${connection.status}).`,
182
+ ...(connection.setup?.handoff_url
183
+ ? [`Finish setup in Telegram: ${connection.setup.handoff_url}`]
184
+ : []),
185
+ ]);
186
+ },
187
+ async "connection route"(context) {
188
+ const { environmentId, projectId } = environmentArguments(context.parsed);
189
+ const routes = await (await client(context)).attachNotifierConnection(projectId, environmentId, {
190
+ connection_id: required(context.parsed, "connection", "the connection to route through"),
191
+ notifier_id: required(context.parsed, "notifier", "the notifier to route"),
192
+ });
193
+ emit(context.writer, routes, table([
194
+ ["NOTIFIER", "CONNECTION", "DEFAULT"],
195
+ ...routes.items.map((route) => [
196
+ route.notifier_id,
197
+ route.connection_id,
198
+ route.is_default ? "yes" : "no",
199
+ ]),
200
+ ]));
201
+ },
202
+ async "link create"(context) {
203
+ const { environmentId, projectId } = environmentArguments(context.parsed);
204
+ const connectionId = context.parsed.options.get("connection");
205
+ const link = await (await client(context)).createSubscriptionLinkInEnvironment(projectId, environmentId, {
206
+ channels: ["telegram"],
207
+ ...(connectionId === undefined ? {} : { connection_id: connectionId }),
208
+ subscriber_id: required(context.parsed, "subscriber", "your own identifier for this subscriber"),
209
+ }, idempotency(context.parsed));
210
+ emit(context.writer, link, [
211
+ `Link ${link.id} (${link.status}).`,
212
+ // A link carries no authorization URL once it has been used, and an
213
+ // idempotent replay returns the used one. Printing "undefined" there
214
+ // would read as a defect rather than as the link having done its job.
215
+ ...(link.authorization_url
216
+ ? ["Send the subscriber here:", link.authorization_url]
217
+ : ["This link has already been used; issue another to invite again."]),
218
+ ]);
219
+ },
220
+ async "message send"(context) {
221
+ const { environmentId, projectId } = environmentArguments(context.parsed);
222
+ const message = await (await client(context)).createMessageInEnvironment(projectId, environmentId, {
223
+ content: {
224
+ text: required(context.parsed, "text", "the message body"),
225
+ type: "text",
226
+ },
227
+ delivery_target: deliveryTarget(context.parsed),
228
+ to: {
229
+ subscriber_id: required(context.parsed, "subscriber", "who to send to"),
230
+ },
231
+ }, idempotency(context.parsed));
232
+ emit(context.writer, message, [
233
+ `Accepted ${message.id} (${message.state}).`,
234
+ `Read it with: wb message get ${message.id} --project ${projectId} --environment ${environmentId}`,
235
+ ]);
236
+ },
237
+ async "message get"(context) {
238
+ const { environmentId, projectId } = environmentArguments(context.parsed);
239
+ const messageId = context.parsed.words[2];
240
+ if (!messageId)
241
+ throw new UsageError("wb message get <message-id>");
242
+ const message = await (await client(context)).getMessageInEnvironment(projectId, environmentId, messageId);
243
+ emit(context.writer, message, [
244
+ `${message.id} ${message.state}`,
245
+ `provider: ${message.delivery.highest_proven_provider_state}`,
246
+ `attempts: ${message.delivery.attempt_count}`,
247
+ ]);
248
+ },
249
+ };
250
+ export function resolveCommand(words) {
251
+ const two = words.slice(0, 2).join(" ");
252
+ const one = words[0] ?? "";
253
+ return commands[two] ?? commands[one] ?? null;
254
+ }
255
+ export const COMMAND_NAMES = Object.keys(commands);
256
+ export { resolveOrigin };
257
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Where the CLI keeps what it learned about a deployment.
3
+ *
4
+ * One file, holding one record per origin. A developer who works against
5
+ * staging and production is the ordinary case, not an exotic one, and a single
6
+ * unkeyed token would silently send staging work to production the first time
7
+ * somebody changed `--api-url`.
8
+ *
9
+ * `$XDG_CONFIG_HOME` is honoured where it is set, and `~/.config/whooshbang`
10
+ * otherwise, which is where this project already keeps operator files.
11
+ */
12
+ export interface StoredSession {
13
+ /** The client id dynamic registration returned. Reused on later logins. */
14
+ readonly clientId: string;
15
+ readonly accessToken: string;
16
+ readonly refreshToken?: string;
17
+ /** Epoch milliseconds, or absent when the server named no expiry. */
18
+ readonly expiresAt?: number;
19
+ readonly scopes: readonly string[];
20
+ }
21
+ export interface StoredCredentials {
22
+ readonly version: 1;
23
+ /** Keyed by API origin, exactly as the caller spelled it. */
24
+ readonly origins: Record<string, StoredSession>;
25
+ }
26
+ export declare function credentialsDirectory(environment?: NodeJS.ProcessEnv): string;
27
+ export declare function credentialsPath(environment?: NodeJS.ProcessEnv): string;
28
+ /**
29
+ * A missing or unreadable file is an empty store rather than an error: not
30
+ * being logged in is the ordinary first state, and a corrupted file should send
31
+ * somebody to `wb login` rather than to a stack trace.
32
+ */
33
+ export declare function readCredentials(file?: string): Promise<StoredCredentials>;
34
+ /**
35
+ * Written owner-only, in a directory created owner-only.
36
+ *
37
+ * `writeFile`'s mode is subject to the process umask, so the permissions are
38
+ * set explicitly afterwards rather than hoped for. The token is a bearer
39
+ * credential for the whole account; a group-readable home directory is common
40
+ * enough that leaving this to umask is not a defensible default.
41
+ */
42
+ export declare function writeCredentials(credentials: StoredCredentials, file?: string): Promise<void>;
43
+ export declare function storeSession(origin: string, session: StoredSession, file?: string): Promise<void>;
44
+ export declare function forgetSession(origin: string, file?: string): Promise<StoredSession | null>;
45
+ export declare function loadSession(origin: string, file?: string): Promise<StoredSession | null>;
@@ -0,0 +1,85 @@
1
+ import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ const EMPTY = { origins: {}, version: 1 };
5
+ export function credentialsDirectory(environment = process.env) {
6
+ const xdg = environment.XDG_CONFIG_HOME?.trim();
7
+ const base = xdg && xdg.length > 0 ? xdg : path.join(os.homedir(), ".config");
8
+ return path.join(base, "whooshbang");
9
+ }
10
+ export function credentialsPath(environment = process.env) {
11
+ return path.join(credentialsDirectory(environment), "cli-credentials.json");
12
+ }
13
+ /**
14
+ * A missing or unreadable file is an empty store rather than an error: not
15
+ * being logged in is the ordinary first state, and a corrupted file should send
16
+ * somebody to `wb login` rather than to a stack trace.
17
+ */
18
+ export async function readCredentials(file = credentialsPath()) {
19
+ let contents;
20
+ try {
21
+ contents = await readFile(file, "utf8");
22
+ }
23
+ catch {
24
+ return EMPTY;
25
+ }
26
+ try {
27
+ const parsed = JSON.parse(contents);
28
+ if (typeof parsed !== "object" ||
29
+ parsed === null ||
30
+ parsed.version !== 1 ||
31
+ typeof parsed.origins !== "object" ||
32
+ parsed.origins === null) {
33
+ return EMPTY;
34
+ }
35
+ return parsed;
36
+ }
37
+ catch {
38
+ return EMPTY;
39
+ }
40
+ }
41
+ /**
42
+ * Written owner-only, in a directory created owner-only.
43
+ *
44
+ * `writeFile`'s mode is subject to the process umask, so the permissions are
45
+ * set explicitly afterwards rather than hoped for. The token is a bearer
46
+ * credential for the whole account; a group-readable home directory is common
47
+ * enough that leaving this to umask is not a defensible default.
48
+ */
49
+ export async function writeCredentials(credentials, file = credentialsPath()) {
50
+ await mkdir(path.dirname(file), { mode: 0o700, recursive: true });
51
+ await chmod(path.dirname(file), 0o700).catch(() => undefined);
52
+ await writeFile(file, `${JSON.stringify(credentials, null, 2)}\n`, {
53
+ mode: 0o600,
54
+ });
55
+ await chmod(file, 0o600);
56
+ }
57
+ export async function storeSession(origin, session, file = credentialsPath()) {
58
+ const current = await readCredentials(file);
59
+ await writeCredentials({
60
+ origins: { ...current.origins, [origin]: session },
61
+ version: 1,
62
+ }, file);
63
+ }
64
+ export async function forgetSession(origin, file = credentialsPath()) {
65
+ const current = await readCredentials(file);
66
+ const session = current.origins[origin] ?? null;
67
+ if (!session)
68
+ return null;
69
+ const origins = { ...current.origins };
70
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
71
+ delete origins[origin];
72
+ if (Object.keys(origins).length === 0) {
73
+ // Nothing left to hold: remove the file rather than leave an empty husk
74
+ // that reads, to anybody auditing a home directory, like a live credential.
75
+ await rm(file, { force: true });
76
+ return session;
77
+ }
78
+ await writeCredentials({ origins, version: 1 }, file);
79
+ return session;
80
+ }
81
+ export async function loadSession(origin, file = credentialsPath()) {
82
+ const current = await readCredentials(file);
83
+ return current.origins[origin] ?? null;
84
+ }
85
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/cli/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ export declare function run(argv: readonly string[], overrides?: {
3
+ readonly credentialsFile?: string;
4
+ readonly openUrl?: (url: string) => Promise<void>;
5
+ }): Promise<number>;