@proxy-checkout/cli 0.1.0-prx-128.104.1

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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +128 -0
  3. package/dist/cjs/auth.d.cts +38 -0
  4. package/dist/cjs/auth.d.ts +38 -0
  5. package/dist/cjs/auth.js +258 -0
  6. package/dist/cjs/bin.d.cts +2 -0
  7. package/dist/cjs/bin.d.ts +2 -0
  8. package/dist/cjs/bin.js +10 -0
  9. package/dist/cjs/cli.d.cts +10 -0
  10. package/dist/cjs/cli.d.ts +10 -0
  11. package/dist/cjs/cli.js +469 -0
  12. package/dist/cjs/command-schema.d.cts +23 -0
  13. package/dist/cjs/command-schema.d.ts +23 -0
  14. package/dist/cjs/command-schema.js +238 -0
  15. package/dist/cjs/config.d.cts +55 -0
  16. package/dist/cjs/config.d.ts +55 -0
  17. package/dist/cjs/config.js +339 -0
  18. package/dist/cjs/errors.d.cts +20 -0
  19. package/dist/cjs/errors.d.ts +20 -0
  20. package/dist/cjs/errors.js +52 -0
  21. package/dist/cjs/http-client.d.cts +25 -0
  22. package/dist/cjs/http-client.d.ts +25 -0
  23. package/dist/cjs/http-client.js +102 -0
  24. package/dist/cjs/index.d.cts +5 -0
  25. package/dist/cjs/index.d.ts +5 -0
  26. package/dist/cjs/index.js +17 -0
  27. package/dist/cjs/output.d.cts +19 -0
  28. package/dist/cjs/output.d.ts +19 -0
  29. package/dist/cjs/output.js +76 -0
  30. package/dist/cjs/package.json +3 -0
  31. package/dist/cjs/webhooks.d.cts +81 -0
  32. package/dist/cjs/webhooks.d.ts +81 -0
  33. package/dist/cjs/webhooks.js +343 -0
  34. package/dist/esm/auth.d.ts +38 -0
  35. package/dist/esm/auth.js +253 -0
  36. package/dist/esm/bin.d.ts +2 -0
  37. package/dist/esm/bin.js +8 -0
  38. package/dist/esm/cli.d.ts +10 -0
  39. package/dist/esm/cli.js +465 -0
  40. package/dist/esm/command-schema.d.ts +23 -0
  41. package/dist/esm/command-schema.js +232 -0
  42. package/dist/esm/config.d.ts +55 -0
  43. package/dist/esm/config.js +333 -0
  44. package/dist/esm/errors.d.ts +20 -0
  45. package/dist/esm/errors.js +46 -0
  46. package/dist/esm/http-client.d.ts +25 -0
  47. package/dist/esm/http-client.js +98 -0
  48. package/dist/esm/index.d.ts +5 -0
  49. package/dist/esm/index.js +5 -0
  50. package/dist/esm/output.d.ts +19 -0
  51. package/dist/esm/output.js +71 -0
  52. package/dist/esm/webhooks.d.ts +81 -0
  53. package/dist/esm/webhooks.js +337 -0
  54. package/package.json +68 -0
@@ -0,0 +1,238 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.commandSchemas = void 0;
4
+ exports.findCommandSchema = findCommandSchema;
5
+ exports.commandSchemaDocument = commandSchemaDocument;
6
+ exports.llmsFullDocument = llmsFullDocument;
7
+ const profileOption = {
8
+ description: "Credential profile name.",
9
+ env: "PROXY_PROFILE",
10
+ name: "--profile",
11
+ type: "string",
12
+ };
13
+ const apiBaseOption = {
14
+ description: "Proxy API base URL.",
15
+ env: "PROXY_API_BASE_URL",
16
+ name: "--api-base",
17
+ type: "string",
18
+ };
19
+ const merchantOption = {
20
+ description: "Assert the credential's target merchant ID.",
21
+ name: "--merchant",
22
+ type: "string",
23
+ };
24
+ const modeOption = {
25
+ description: "Assert the command mode (test or live).",
26
+ name: "--mode",
27
+ type: "string",
28
+ };
29
+ const commonOptions = [profileOption, apiBaseOption, merchantOption, modeOption];
30
+ function withCommonOptions(...options) {
31
+ return [...commonOptions, ...options];
32
+ }
33
+ exports.commandSchemas = [
34
+ {
35
+ capabilities: [],
36
+ description: "Authorize a revocable, test-merchant Proxy CLI credential in the browser.",
37
+ interactive: true,
38
+ modes: ["test"],
39
+ options: withCommonOptions({
40
+ description: "Print the approval URL without opening a browser.",
41
+ name: "--no-browser",
42
+ type: "boolean",
43
+ }, { description: "Human-readable profile label.", name: "--name", type: "string" }),
44
+ output: "The non-secret profile and credential metadata.",
45
+ path: "auth login",
46
+ streaming: true,
47
+ },
48
+ {
49
+ capabilities: ["cliAuth:read"],
50
+ description: "Show the active profile and server-verified credential status.",
51
+ interactive: false,
52
+ modes: ["test", "live"],
53
+ options: withCommonOptions(),
54
+ output: "The active non-secret profile.",
55
+ path: "auth status",
56
+ },
57
+ {
58
+ capabilities: ["cliAuth:read", "cliAuth:revoke"],
59
+ description: "Close owned listeners, revoke the CLI credential, and remove it locally.",
60
+ interactive: false,
61
+ modes: ["test", "live"],
62
+ options: withCommonOptions(),
63
+ output: "Revocation status.",
64
+ path: "auth logout",
65
+ },
66
+ {
67
+ description: "Relay signed Proxy test webhooks to a loopback development server.",
68
+ interactive: true,
69
+ modes: ["test"],
70
+ options: withCommonOptions({
71
+ description: "Loopback HTTP(S) destination.",
72
+ name: "--forward-to",
73
+ required: true,
74
+ type: "string",
75
+ }, { description: "Source webhook endpoint ID.", name: "--endpoint", type: "string" }, {
76
+ description: "Orchestrate Stripe CLI test webhooks through Proxy.",
77
+ name: "--stripe",
78
+ type: "boolean",
79
+ }, {
80
+ description: "Explicit active Stripe PSP configuration ID.",
81
+ name: "--stripe-psp-config",
82
+ type: "string",
83
+ }, {
84
+ description: "Stop after this many forwarded events; zero means run until interrupted.",
85
+ name: "--max-events",
86
+ type: "integer",
87
+ }),
88
+ output: "A stream of listener, delivery, retry, and cleanup events.",
89
+ path: "listen",
90
+ streaming: true,
91
+ capabilities: [
92
+ "cliAuth:read",
93
+ "localWebhookListeners:read",
94
+ "localWebhookListeners:create",
95
+ "localWebhookListeners:heartbeat",
96
+ "localWebhookListeners:close",
97
+ "localWebhookDeliveries:claim",
98
+ "localWebhookDeliveries:complete",
99
+ "localPspIngresses:create",
100
+ ],
101
+ },
102
+ {
103
+ capabilities: ["cliAuth:read", "localWebhookListeners:read"],
104
+ description: "List active HTTPS webhook endpoints eligible as relay sources.",
105
+ interactive: false,
106
+ modes: ["test"],
107
+ options: withCommonOptions(),
108
+ output: "Source endpoint records.",
109
+ path: "webhooks endpoints list",
110
+ },
111
+ {
112
+ capabilities: ["cliAuth:read", "localWebhookListeners:create"],
113
+ description: "Create a temporary local relay listener.",
114
+ interactive: false,
115
+ modes: ["test"],
116
+ options: withCommonOptions({
117
+ description: "Source webhook endpoint ID.",
118
+ name: "--endpoint",
119
+ required: true,
120
+ type: "string",
121
+ }),
122
+ output: "Listener record and lease.",
123
+ path: "webhooks listeners create",
124
+ },
125
+ {
126
+ capabilities: ["cliAuth:read", "localWebhookListeners:read"],
127
+ description: "Read an owned relay listener.",
128
+ interactive: false,
129
+ modes: ["test"],
130
+ options: withCommonOptions({
131
+ description: "Listener ID.",
132
+ name: "--listener",
133
+ required: true,
134
+ type: "string",
135
+ }),
136
+ output: "Listener record and lease.",
137
+ path: "webhooks listeners status",
138
+ },
139
+ {
140
+ capabilities: ["cliAuth:read", "localWebhookListeners:heartbeat"],
141
+ description: "Extend an owned relay listener lease.",
142
+ interactive: false,
143
+ modes: ["test"],
144
+ options: withCommonOptions({
145
+ description: "Listener ID.",
146
+ name: "--listener",
147
+ required: true,
148
+ type: "string",
149
+ }),
150
+ output: "Updated listener lease.",
151
+ path: "webhooks listeners heartbeat",
152
+ },
153
+ {
154
+ capabilities: ["cliAuth:read", "localWebhookListeners:close"],
155
+ description: "Close an owned relay listener and temporary ingress.",
156
+ interactive: false,
157
+ modes: ["test"],
158
+ options: withCommonOptions({
159
+ description: "Listener ID.",
160
+ name: "--listener",
161
+ required: true,
162
+ type: "string",
163
+ }),
164
+ output: "Archived listener record.",
165
+ path: "webhooks listeners close",
166
+ },
167
+ {
168
+ capabilities: ["cliAuth:read", "localWebhookDeliveries:read"],
169
+ description: "List delivery state for an owned relay listener.",
170
+ interactive: false,
171
+ modes: ["test"],
172
+ options: withCommonOptions({ description: "Listener ID.", name: "--listener", required: true, type: "string" }, { description: "Maximum records (1-100).", name: "--limit", type: "integer" }),
173
+ output: "Delivery records in server order.",
174
+ path: "webhooks deliveries list",
175
+ },
176
+ {
177
+ capabilities: ["cliAuth:read", "localWebhookDeliveries:replay"],
178
+ description: "Schedule an explicit replay of a completed local delivery.",
179
+ interactive: false,
180
+ modes: ["test"],
181
+ options: withCommonOptions({ description: "Listener ID.", name: "--listener", required: true, type: "string" }, { description: "Delivery ID.", name: "--delivery", required: true, type: "string" }, { description: "Audit reason.", name: "--reason", required: true, type: "string" }),
182
+ output: "Replay outbox identifier and state.",
183
+ path: "webhooks deliveries replay",
184
+ },
185
+ {
186
+ description: "Claim and forward one selected delivery or replay, enabling explicit processing order.",
187
+ interactive: false,
188
+ modes: ["test"],
189
+ options: withCommonOptions({ description: "Listener ID.", name: "--listener", required: true, type: "string" }, {
190
+ description: "Pending delivery ID (exclusive with --replay-outbox).",
191
+ name: "--delivery",
192
+ type: "string",
193
+ }, {
194
+ description: "Replay outbox ID (exclusive with --delivery).",
195
+ name: "--replay-outbox",
196
+ type: "string",
197
+ }, {
198
+ description: "Loopback HTTP(S) destination.",
199
+ name: "--forward-to",
200
+ required: true,
201
+ type: "string",
202
+ }),
203
+ output: "Non-sensitive claim metadata and forwarding completion.",
204
+ path: "webhooks deliveries forward",
205
+ capabilities: [
206
+ "cliAuth:read",
207
+ "localWebhookDeliveries:claim",
208
+ "localWebhookDeliveries:complete",
209
+ ],
210
+ },
211
+ ];
212
+ function findCommandSchema(path) {
213
+ return exports.commandSchemas.find((command) => command.path === path);
214
+ }
215
+ function commandSchemaDocument(path) {
216
+ if (!path) {
217
+ return { commands: exports.commandSchemas, schema_version: "2026-07-14" };
218
+ }
219
+ return findCommandSchema(path);
220
+ }
221
+ function llmsFullDocument() {
222
+ return [
223
+ "# Proxy CLI command reference",
224
+ "",
225
+ "All operational resource commands currently accept test-mode credentials only. Auth status and logout are mode-neutral safety controls for future separately issued grants. Use `--format json` for one-shot commands and `--format jsonl` for streaming commands. Use `--no-input` to prohibit prompts.",
226
+ "",
227
+ ...exports.commandSchemas.flatMap((command) => [
228
+ `## proxy ${command.path}`,
229
+ "",
230
+ command.description,
231
+ "",
232
+ `Capabilities: ${command.capabilities.join(", ") || "none"}`,
233
+ `Modes: ${command.modes.join(", ")}`,
234
+ ...command.options.map((option) => `- ${option.name} (${option.type}${option.required ? ", required" : ""}): ${option.description}`),
235
+ "",
236
+ ]),
237
+ ].join("\n");
238
+ }
@@ -0,0 +1,55 @@
1
+ export interface CliProfile {
2
+ apiBaseUrl: string;
3
+ apiVersion: string;
4
+ capabilityVersion: number;
5
+ credentialId: string;
6
+ credentialStore: "file" | "keychain" | "secret-service";
7
+ expiresAt: string | null;
8
+ merchantId: string;
9
+ mode: "test" | "live";
10
+ name: string;
11
+ organizationId: string;
12
+ }
13
+ export interface ResolvedCredential {
14
+ apiBaseUrl: string;
15
+ profile?: CliProfile;
16
+ profileName: string;
17
+ source: "environment" | "file" | "keychain" | "secret-service";
18
+ token: string;
19
+ }
20
+ export interface ConfigEnvironment {
21
+ HOME?: string;
22
+ PATH?: string;
23
+ PROXY_API_BASE_URL?: string;
24
+ PROXY_CLI_TOKEN?: string;
25
+ PROXY_CONFIG_HOME?: string;
26
+ PROXY_PROFILE?: string;
27
+ XDG_CONFIG_HOME?: string;
28
+ XDG_DATA_HOME?: string;
29
+ }
30
+ export declare class CliConfigStore {
31
+ private readonly configFile;
32
+ private readonly credentialsFile;
33
+ private readonly environment;
34
+ private readonly operatingSystem;
35
+ constructor(options?: {
36
+ environment?: ConfigEnvironment;
37
+ operatingSystem?: NodeJS.Platform;
38
+ });
39
+ profileName(requested?: string): string;
40
+ apiBaseUrl(requested?: string, profile?: CliProfile): string;
41
+ readProfile(profileName: string): Promise<CliProfile | undefined>;
42
+ saveProfile(profileName: string, profile: Omit<CliProfile, "credentialStore">, token: string): Promise<CliProfile>;
43
+ resolveCredential(input: {
44
+ apiBaseUrl?: string;
45
+ profileName?: string;
46
+ }): Promise<ResolvedCredential>;
47
+ removeProfile(profileName: string): Promise<void>;
48
+ private readConfiguration;
49
+ private storeToken;
50
+ private readToken;
51
+ private deleteToken;
52
+ private readFileCredentials;
53
+ }
54
+ export declare function normalizeApiBaseUrl(value: string): string;
55
+ export declare function writePrivateJson(path: string, value: unknown): Promise<void>;
@@ -0,0 +1,55 @@
1
+ export interface CliProfile {
2
+ apiBaseUrl: string;
3
+ apiVersion: string;
4
+ capabilityVersion: number;
5
+ credentialId: string;
6
+ credentialStore: "file" | "keychain" | "secret-service";
7
+ expiresAt: string | null;
8
+ merchantId: string;
9
+ mode: "test" | "live";
10
+ name: string;
11
+ organizationId: string;
12
+ }
13
+ export interface ResolvedCredential {
14
+ apiBaseUrl: string;
15
+ profile?: CliProfile;
16
+ profileName: string;
17
+ source: "environment" | "file" | "keychain" | "secret-service";
18
+ token: string;
19
+ }
20
+ export interface ConfigEnvironment {
21
+ HOME?: string;
22
+ PATH?: string;
23
+ PROXY_API_BASE_URL?: string;
24
+ PROXY_CLI_TOKEN?: string;
25
+ PROXY_CONFIG_HOME?: string;
26
+ PROXY_PROFILE?: string;
27
+ XDG_CONFIG_HOME?: string;
28
+ XDG_DATA_HOME?: string;
29
+ }
30
+ export declare class CliConfigStore {
31
+ private readonly configFile;
32
+ private readonly credentialsFile;
33
+ private readonly environment;
34
+ private readonly operatingSystem;
35
+ constructor(options?: {
36
+ environment?: ConfigEnvironment;
37
+ operatingSystem?: NodeJS.Platform;
38
+ });
39
+ profileName(requested?: string): string;
40
+ apiBaseUrl(requested?: string, profile?: CliProfile): string;
41
+ readProfile(profileName: string): Promise<CliProfile | undefined>;
42
+ saveProfile(profileName: string, profile: Omit<CliProfile, "credentialStore">, token: string): Promise<CliProfile>;
43
+ resolveCredential(input: {
44
+ apiBaseUrl?: string;
45
+ profileName?: string;
46
+ }): Promise<ResolvedCredential>;
47
+ removeProfile(profileName: string): Promise<void>;
48
+ private readConfiguration;
49
+ private storeToken;
50
+ private readToken;
51
+ private deleteToken;
52
+ private readFileCredentials;
53
+ }
54
+ export declare function normalizeApiBaseUrl(value: string): string;
55
+ export declare function writePrivateJson(path: string, value: unknown): Promise<void>;
@@ -0,0 +1,339 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CliConfigStore = void 0;
4
+ exports.normalizeApiBaseUrl = normalizeApiBaseUrl;
5
+ exports.writePrivateJson = writePrivateJson;
6
+ const node_child_process_1 = require("node:child_process");
7
+ const node_fs_1 = require("node:fs");
8
+ const promises_1 = require("node:fs/promises");
9
+ const node_os_1 = require("node:os");
10
+ const node_path_1 = require("node:path");
11
+ const errors_js_1 = require("./errors.js");
12
+ const configurationVersion = 1;
13
+ const keychainService = "com.proxycheckout.cli";
14
+ const defaultApiBaseUrl = "https://api.proxycheckout.com";
15
+ const profileNamePattern = /^[A-Za-z0-9._-]{1,64}$/;
16
+ const macosKeychainScript = String.raw `
17
+ ObjC.import("Foundation");
18
+ ObjC.import("Security");
19
+
20
+ function run() {
21
+ const stdin = $.NSFileHandle.fileHandleWithStandardInput.readDataToEndOfFile;
22
+ const raw = ObjC.unwrap(
23
+ $.NSString.alloc.initWithDataEncoding(stdin, $.NSUTF8StringEncoding),
24
+ );
25
+ const [operation, serviceValue, accountValue, credential = ""] = raw.split("\n");
26
+ if (!operation || !serviceValue || !accountValue) throw new Error("invalid input");
27
+
28
+ const query = $.NSMutableDictionary.alloc.init;
29
+ query.setObjectForKey($("genp"), $("class"));
30
+ query.setObjectForKey($(serviceValue), $("svce"));
31
+ query.setObjectForKey($(accountValue), $("acct"));
32
+
33
+ if (operation === "write") {
34
+ const data = $(credential).dataUsingEncoding($.NSUTF8StringEncoding);
35
+ const update = $.NSMutableDictionary.alloc.init;
36
+ update.setObjectForKey(data, $("v_Data"));
37
+ const updateStatus = $.SecItemUpdate(query, update);
38
+ if (updateStatus === 0) return "stored";
39
+ if (updateStatus !== -25300) throw new Error("keychain update failed: " + updateStatus);
40
+ query.setObjectForKey(data, $("v_Data"));
41
+ const addStatus = $.SecItemAdd(query, $());
42
+ if (addStatus !== 0) throw new Error("keychain add failed: " + addStatus);
43
+ return "stored";
44
+ }
45
+
46
+ if (operation === "read") {
47
+ query.setObjectForKey(true, $("r_Data"));
48
+ query.setObjectForKey($("m_LimitOne"), $("m_Limit"));
49
+ const result = $();
50
+ const status = $.SecItemCopyMatching(query, result);
51
+ if (status === -25300) return "missing";
52
+ if (status !== 0) throw new Error("keychain read failed: " + status);
53
+ $.NSFileHandle.fileHandleWithStandardOutput.writeData(result);
54
+ return "";
55
+ }
56
+
57
+ if (operation === "delete") {
58
+ const status = $.SecItemDelete(query);
59
+ if (status !== 0 && status !== -25300) {
60
+ throw new Error("keychain delete failed: " + status);
61
+ }
62
+ return "deleted";
63
+ }
64
+
65
+ throw new Error("unknown operation");
66
+ }
67
+ `;
68
+ class CliConfigStore {
69
+ configFile;
70
+ credentialsFile;
71
+ environment;
72
+ operatingSystem;
73
+ constructor(options = {}) {
74
+ this.environment = options.environment ?? process.env;
75
+ this.operatingSystem = options.operatingSystem ?? (0, node_os_1.platform)();
76
+ const home = this.environment.HOME ?? (0, node_os_1.homedir)();
77
+ const configHome = this.environment.PROXY_CONFIG_HOME ??
78
+ this.environment.XDG_CONFIG_HOME ??
79
+ (0, node_path_1.join)(home, ".config");
80
+ const dataHome = this.environment.XDG_DATA_HOME ?? (0, node_path_1.join)(home, ".local", "share");
81
+ this.configFile = (0, node_path_1.join)(configHome, "proxy", "config.json");
82
+ this.credentialsFile = (0, node_path_1.join)(dataHome, "proxy", "credentials.json");
83
+ }
84
+ profileName(requested) {
85
+ const value = requested ?? this.environment.PROXY_PROFILE ?? "default";
86
+ if (!profileNamePattern.test(value)) {
87
+ throw (0, errors_js_1.usageError)("Profile names may contain only letters, digits, dot, underscore, and dash");
88
+ }
89
+ return value;
90
+ }
91
+ apiBaseUrl(requested, profile) {
92
+ return normalizeApiBaseUrl(requested ?? this.environment.PROXY_API_BASE_URL ?? profile?.apiBaseUrl ?? defaultApiBaseUrl);
93
+ }
94
+ async readProfile(profileName) {
95
+ return (await this.readConfiguration()).profiles[profileName];
96
+ }
97
+ async saveProfile(profileName, profile, token) {
98
+ validateToken(token);
99
+ const credentialStore = await this.storeToken(profileName, token);
100
+ const configuration = await this.readConfiguration();
101
+ const storedProfile = { ...profile, credentialStore };
102
+ configuration.profiles[profileName] = storedProfile;
103
+ try {
104
+ await writePrivateJson(this.configFile, configuration);
105
+ }
106
+ catch (error) {
107
+ await this.deleteToken(profileName, credentialStore).catch(() => undefined);
108
+ throw error;
109
+ }
110
+ return storedProfile;
111
+ }
112
+ async resolveCredential(input) {
113
+ const profileName = this.profileName(input.profileName);
114
+ const profile = await this.readProfile(profileName);
115
+ const environmentToken = this.environment.PROXY_CLI_TOKEN;
116
+ const apiBaseOverride = input.apiBaseUrl ?? this.environment.PROXY_API_BASE_URL;
117
+ if (profile &&
118
+ !environmentToken &&
119
+ apiBaseOverride &&
120
+ normalizeApiBaseUrl(apiBaseOverride) !== normalizeApiBaseUrl(profile.apiBaseUrl)) {
121
+ throw (0, errors_js_1.usageError)("A stored credential may be sent only to the API origin saved with its profile; use PROXY_CLI_TOKEN for an explicit override");
122
+ }
123
+ const apiBaseUrl = this.apiBaseUrl(input.apiBaseUrl, profile);
124
+ if (environmentToken) {
125
+ validateToken(environmentToken);
126
+ return {
127
+ apiBaseUrl,
128
+ ...(profile ? { profile } : {}),
129
+ profileName,
130
+ source: "environment",
131
+ token: environmentToken,
132
+ };
133
+ }
134
+ if (!profile) {
135
+ throw new errors_js_1.CliError(`Profile ${profileName} is not authenticated. Run proxy auth login.`, "cli_not_authenticated", errors_js_1.cliExitCodes.authentication);
136
+ }
137
+ const token = await this.readToken(profileName, profile.credentialStore);
138
+ if (!token) {
139
+ throw new errors_js_1.CliError(`Credential for profile ${profileName} is missing. Run proxy auth login again.`, "cli_credential_missing", errors_js_1.cliExitCodes.authentication);
140
+ }
141
+ validateToken(token);
142
+ return { apiBaseUrl, profile, profileName, source: profile.credentialStore, token };
143
+ }
144
+ async removeProfile(profileName) {
145
+ const configuration = await this.readConfiguration();
146
+ const profile = configuration.profiles[profileName];
147
+ if (profile) {
148
+ await this.deleteToken(profileName, profile.credentialStore);
149
+ delete configuration.profiles[profileName];
150
+ await writePrivateJson(this.configFile, configuration);
151
+ }
152
+ }
153
+ async readConfiguration() {
154
+ const raw = await readOptionalJson(this.configFile);
155
+ if (raw === undefined) {
156
+ return { profiles: {}, version: configurationVersion };
157
+ }
158
+ if (!isRecord(raw) || raw.version !== configurationVersion || !isRecord(raw.profiles)) {
159
+ throw new errors_js_1.CliError("Proxy CLI configuration is invalid", "cli_config_invalid", errors_js_1.cliExitCodes.internal);
160
+ }
161
+ return raw;
162
+ }
163
+ async storeToken(profileName, token) {
164
+ if (this.operatingSystem === "darwin" &&
165
+ (await executableExists("osascript", this.environment))) {
166
+ const result = await runProcess("osascript", ["-l", "JavaScript", "-e", macosKeychainScript], keychainInput("write", profileName, token));
167
+ if (result.code !== 0) {
168
+ throw credentialStoreError("macOS Keychain", result.stderr);
169
+ }
170
+ return "keychain";
171
+ }
172
+ if (this.operatingSystem === "linux" &&
173
+ (await executableExists("secret-tool", this.environment))) {
174
+ const result = await runProcess("secret-tool", ["store", "--label=Proxy CLI", "service", keychainService, "profile", profileName], token);
175
+ if (result.code !== 0) {
176
+ throw credentialStoreError("Secret Service", result.stderr);
177
+ }
178
+ return "secret-service";
179
+ }
180
+ const credentials = await this.readFileCredentials();
181
+ credentials.tokens[profileName] = token;
182
+ await writePrivateJson(this.credentialsFile, credentials);
183
+ return "file";
184
+ }
185
+ async readToken(profileName, store) {
186
+ if (store === "keychain") {
187
+ const result = await runProcess("osascript", ["-l", "JavaScript", "-e", macosKeychainScript], keychainInput("read", profileName));
188
+ if (result.code !== 0) {
189
+ throw credentialStoreError("macOS Keychain", result.stderr);
190
+ }
191
+ return result.stdout.trim() !== "missing" ? result.stdout.trim() : undefined;
192
+ }
193
+ if (store === "secret-service") {
194
+ const result = await runProcess("secret-tool", [
195
+ "lookup",
196
+ "service",
197
+ keychainService,
198
+ "profile",
199
+ profileName,
200
+ ]);
201
+ return result.code === 0 ? result.stdout.trim() : undefined;
202
+ }
203
+ return (await this.readFileCredentials()).tokens[profileName];
204
+ }
205
+ async deleteToken(profileName, store) {
206
+ if (store === "keychain") {
207
+ const result = await runProcess("osascript", ["-l", "JavaScript", "-e", macosKeychainScript], keychainInput("delete", profileName));
208
+ if (result.code !== 0) {
209
+ throw credentialStoreError("macOS Keychain", result.stderr);
210
+ }
211
+ return;
212
+ }
213
+ if (store === "secret-service") {
214
+ await runProcess("secret-tool", [
215
+ "clear",
216
+ "service",
217
+ keychainService,
218
+ "profile",
219
+ profileName,
220
+ ]);
221
+ return;
222
+ }
223
+ const credentials = await this.readFileCredentials();
224
+ delete credentials.tokens[profileName];
225
+ await writePrivateJson(this.credentialsFile, credentials);
226
+ }
227
+ async readFileCredentials() {
228
+ const raw = await readOptionalJson(this.credentialsFile);
229
+ if (raw === undefined) {
230
+ return { tokens: {}, version: configurationVersion };
231
+ }
232
+ if (!isRecord(raw) || raw.version !== configurationVersion || !isRecord(raw.tokens)) {
233
+ throw new errors_js_1.CliError("Proxy CLI credential file is invalid", "cli_credential_file_invalid", errors_js_1.cliExitCodes.internal);
234
+ }
235
+ return raw;
236
+ }
237
+ }
238
+ exports.CliConfigStore = CliConfigStore;
239
+ function keychainInput(operation, profileName, token = "") {
240
+ return `${operation}\n${keychainService}\n${profileName}\n${token}\n`;
241
+ }
242
+ function normalizeApiBaseUrl(value) {
243
+ let url;
244
+ try {
245
+ url = new URL(value);
246
+ }
247
+ catch {
248
+ throw (0, errors_js_1.usageError)("Proxy API base URL must be a valid URL");
249
+ }
250
+ const hostname = url.hostname.replace(/^\[|\]$/g, "");
251
+ const isLoopback = ["127.0.0.1", "::1", "localhost"].includes(hostname);
252
+ if ((url.protocol !== "https:" && !(url.protocol === "http:" && isLoopback)) ||
253
+ url.username ||
254
+ url.password) {
255
+ throw (0, errors_js_1.usageError)("Proxy API base URL must use HTTPS, except for loopback development");
256
+ }
257
+ if (url.pathname !== "/" || url.search || url.hash) {
258
+ throw (0, errors_js_1.usageError)("Proxy API base URL must be an origin without a path, query, or fragment");
259
+ }
260
+ return url.toString().replace(/\/$/, "");
261
+ }
262
+ async function executableExists(command, environment) {
263
+ for (const path of (environment.PATH ?? process.env.PATH ?? "").split(node_path_1.delimiter)) {
264
+ if (!path)
265
+ continue;
266
+ try {
267
+ await (0, promises_1.access)((0, node_path_1.join)(path, command), node_fs_1.constants.X_OK);
268
+ return true;
269
+ }
270
+ catch {
271
+ // Continue through PATH entries.
272
+ }
273
+ }
274
+ return false;
275
+ }
276
+ async function runProcess(command, args, input) {
277
+ return new Promise((resolve, reject) => {
278
+ const child = (0, node_child_process_1.spawn)(command, args, { shell: false, stdio: ["pipe", "pipe", "pipe"] });
279
+ let stdout = "";
280
+ let stderr = "";
281
+ child.stdout.setEncoding("utf8").on("data", (chunk) => {
282
+ stdout += chunk;
283
+ });
284
+ child.stderr.setEncoding("utf8").on("data", (chunk) => {
285
+ stderr += chunk;
286
+ });
287
+ child.on("error", reject);
288
+ child.on("close", (code) => resolve({ code: code ?? 1, stderr, stdout }));
289
+ child.stdin.on("error", (error) => {
290
+ if (!isNodeError(error, "EPIPE"))
291
+ reject(error);
292
+ });
293
+ child.stdin.end(input);
294
+ });
295
+ }
296
+ async function readOptionalJson(path) {
297
+ try {
298
+ return JSON.parse(await (0, promises_1.readFile)(path, "utf8"));
299
+ }
300
+ catch (error) {
301
+ if (isNodeError(error, "ENOENT")) {
302
+ return undefined;
303
+ }
304
+ if (error instanceof SyntaxError) {
305
+ throw new errors_js_1.CliError(`Invalid JSON in ${path}`, "cli_config_invalid", errors_js_1.cliExitCodes.internal);
306
+ }
307
+ throw error;
308
+ }
309
+ }
310
+ async function writePrivateJson(path, value) {
311
+ await (0, promises_1.mkdir)((0, node_path_1.dirname)(path), { mode: 0o700, recursive: true });
312
+ await (0, promises_1.chmod)((0, node_path_1.dirname)(path), 0o700);
313
+ const temporaryPath = `${path}.${process.pid}.tmp`;
314
+ try {
315
+ await (0, promises_1.writeFile)(temporaryPath, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
316
+ await (0, promises_1.rename)(temporaryPath, path);
317
+ await (0, promises_1.chmod)(path, 0o600);
318
+ }
319
+ finally {
320
+ await (0, promises_1.unlink)(temporaryPath).catch((error) => {
321
+ if (!isNodeError(error, "ENOENT"))
322
+ throw error;
323
+ });
324
+ }
325
+ }
326
+ function validateToken(token) {
327
+ if (!/^pcli_(?:test|live)_[A-Za-z0-9_-]{20,}$/.test(token)) {
328
+ throw new errors_js_1.CliError("Proxy CLI credential has an invalid format", "cli_credential_invalid", errors_js_1.cliExitCodes.authentication);
329
+ }
330
+ }
331
+ function credentialStoreError(store, stderr) {
332
+ return new errors_js_1.CliError(`${store} could not access the Proxy CLI credential`, "cli_credential_store_failed", errors_js_1.cliExitCodes.internal, { diagnostic: stderr.trim().slice(0, 200) });
333
+ }
334
+ function isRecord(value) {
335
+ return typeof value === "object" && value !== null && !Array.isArray(value);
336
+ }
337
+ function isNodeError(error, code) {
338
+ return error instanceof Error && "code" in error && error.code === code;
339
+ }