primcli 1.2.0

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.
@@ -0,0 +1,1330 @@
1
+ import { Errors } from "@oclif/core";
2
+ import { chmodSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
3
+ import { randomUUID } from "node:crypto";
4
+ import { join } from "node:path";
5
+ //#region ../packages/api-core/src/api/core/bodySerializer.gen.ts
6
+ const jsonBodySerializer = { bodySerializer: (body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value) };
7
+ //#endregion
8
+ //#region ../packages/api-core/src/api/core/serverSentEvents.gen.ts
9
+ function createSseClient({ onRequest, onSseError, onSseEvent, responseTransformer, responseValidator, sseDefaultRetryDelay, sseMaxRetryAttempts, sseMaxRetryDelay, sseSleepFn, url, ...options }) {
10
+ let lastEventId;
11
+ const sleep = sseSleepFn ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
12
+ const createStream = async function* () {
13
+ let retryDelay = sseDefaultRetryDelay ?? 3e3;
14
+ let attempt = 0;
15
+ const signal = options.signal ?? new AbortController().signal;
16
+ while (true) {
17
+ if (signal.aborted) break;
18
+ attempt++;
19
+ const headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers);
20
+ if (lastEventId !== void 0) headers.set("Last-Event-ID", lastEventId);
21
+ try {
22
+ const requestInit = {
23
+ redirect: "follow",
24
+ ...options,
25
+ body: options.serializedBody,
26
+ headers,
27
+ signal
28
+ };
29
+ let request = new Request(url, requestInit);
30
+ if (onRequest) request = await onRequest(url, requestInit);
31
+ const response = await (options.fetch ?? globalThis.fetch)(request);
32
+ if (!response.ok) throw new Error(`SSE failed: ${response.status} ${response.statusText}`);
33
+ if (!response.body) throw new Error("No body in SSE response");
34
+ const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
35
+ let buffer = "";
36
+ const abortHandler = () => {
37
+ try {
38
+ reader.cancel();
39
+ } catch {}
40
+ };
41
+ signal.addEventListener("abort", abortHandler);
42
+ try {
43
+ while (true) {
44
+ const { done, value } = await reader.read();
45
+ if (done) break;
46
+ buffer += value;
47
+ buffer = buffer.replace(/\r\n?/g, "\n");
48
+ const chunks = buffer.split("\n\n");
49
+ buffer = chunks.pop() ?? "";
50
+ for (const chunk of chunks) {
51
+ const lines = chunk.split("\n");
52
+ const dataLines = [];
53
+ let eventName;
54
+ for (const line of lines) if (line.startsWith("data:")) dataLines.push(line.replace(/^data:\s*/, ""));
55
+ else if (line.startsWith("event:")) eventName = line.replace(/^event:\s*/, "");
56
+ else if (line.startsWith("id:")) lastEventId = line.replace(/^id:\s*/, "");
57
+ else if (line.startsWith("retry:")) {
58
+ const parsed = Number.parseInt(line.replace(/^retry:\s*/, ""), 10);
59
+ if (!Number.isNaN(parsed)) retryDelay = parsed;
60
+ }
61
+ let data;
62
+ let parsedJson = false;
63
+ if (dataLines.length) {
64
+ const rawData = dataLines.join("\n");
65
+ try {
66
+ data = JSON.parse(rawData);
67
+ parsedJson = true;
68
+ } catch {
69
+ data = rawData;
70
+ }
71
+ }
72
+ if (parsedJson) {
73
+ if (responseValidator) await responseValidator(data);
74
+ if (responseTransformer) data = await responseTransformer(data);
75
+ }
76
+ onSseEvent?.({
77
+ data,
78
+ event: eventName,
79
+ id: lastEventId,
80
+ retry: retryDelay
81
+ });
82
+ if (dataLines.length) yield data;
83
+ }
84
+ }
85
+ } finally {
86
+ signal.removeEventListener("abort", abortHandler);
87
+ reader.releaseLock();
88
+ }
89
+ break;
90
+ } catch (error) {
91
+ onSseError?.(error);
92
+ if (sseMaxRetryAttempts !== void 0 && attempt >= sseMaxRetryAttempts) break;
93
+ await sleep(Math.min(retryDelay * 2 ** (attempt - 1), sseMaxRetryDelay ?? 3e4));
94
+ }
95
+ }
96
+ };
97
+ return { stream: createStream() };
98
+ }
99
+ //#endregion
100
+ //#region ../packages/api-core/src/api/core/pathSerializer.gen.ts
101
+ const separatorArrayExplode = (style) => {
102
+ switch (style) {
103
+ case "label": return ".";
104
+ case "matrix": return ";";
105
+ case "simple": return ",";
106
+ default: return "&";
107
+ }
108
+ };
109
+ const separatorArrayNoExplode = (style) => {
110
+ switch (style) {
111
+ case "form": return ",";
112
+ case "pipeDelimited": return "|";
113
+ case "spaceDelimited": return "%20";
114
+ default: return ",";
115
+ }
116
+ };
117
+ const separatorObjectExplode = (style) => {
118
+ switch (style) {
119
+ case "label": return ".";
120
+ case "matrix": return ";";
121
+ case "simple": return ",";
122
+ default: return "&";
123
+ }
124
+ };
125
+ const serializeArrayParam = ({ allowReserved, explode, name, style, value }) => {
126
+ if (!explode) {
127
+ const joinedValues = (allowReserved ? value : value.map((v) => encodeURIComponent(v))).join(separatorArrayNoExplode(style));
128
+ switch (style) {
129
+ case "label": return `.${joinedValues}`;
130
+ case "matrix": return `;${name}=${joinedValues}`;
131
+ case "simple": return joinedValues;
132
+ default: return `${name}=${joinedValues}`;
133
+ }
134
+ }
135
+ const separator = separatorArrayExplode(style);
136
+ const joinedValues = value.map((v) => {
137
+ if (style === "label" || style === "simple") return allowReserved ? v : encodeURIComponent(v);
138
+ return serializePrimitiveParam({
139
+ allowReserved,
140
+ name,
141
+ value: v
142
+ });
143
+ }).join(separator);
144
+ return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
145
+ };
146
+ const serializePrimitiveParam = ({ allowReserved, name, value }) => {
147
+ if (value === void 0 || value === null) return "";
148
+ if (typeof value === "object") throw new Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");
149
+ return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;
150
+ };
151
+ const serializeObjectParam = ({ allowReserved, explode, name, style, value, valueOnly }) => {
152
+ if (value instanceof Date) return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`;
153
+ if (style !== "deepObject" && !explode) {
154
+ let values = [];
155
+ Object.entries(value).forEach(([key, v]) => {
156
+ values = [
157
+ ...values,
158
+ key,
159
+ allowReserved ? v : encodeURIComponent(v)
160
+ ];
161
+ });
162
+ const joinedValues = values.join(",");
163
+ switch (style) {
164
+ case "form": return `${name}=${joinedValues}`;
165
+ case "label": return `.${joinedValues}`;
166
+ case "matrix": return `;${name}=${joinedValues}`;
167
+ default: return joinedValues;
168
+ }
169
+ }
170
+ const separator = separatorObjectExplode(style);
171
+ const joinedValues = Object.entries(value).map(([key, v]) => serializePrimitiveParam({
172
+ allowReserved,
173
+ name: style === "deepObject" ? `${name}[${key}]` : key,
174
+ value: v
175
+ })).join(separator);
176
+ return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
177
+ };
178
+ //#endregion
179
+ //#region ../packages/api-core/src/api/core/utils.gen.ts
180
+ const PATH_PARAM_RE = /\{[^{}]+\}/g;
181
+ const defaultPathSerializer = ({ path, url: _url }) => {
182
+ let url = _url;
183
+ const matches = _url.match(PATH_PARAM_RE);
184
+ if (matches) for (const match of matches) {
185
+ let explode = false;
186
+ let name = match.substring(1, match.length - 1);
187
+ let style = "simple";
188
+ if (name.endsWith("*")) {
189
+ explode = true;
190
+ name = name.substring(0, name.length - 1);
191
+ }
192
+ if (name.startsWith(".")) {
193
+ name = name.substring(1);
194
+ style = "label";
195
+ } else if (name.startsWith(";")) {
196
+ name = name.substring(1);
197
+ style = "matrix";
198
+ }
199
+ const value = path[name];
200
+ if (value === void 0 || value === null) continue;
201
+ if (Array.isArray(value)) {
202
+ url = url.replace(match, serializeArrayParam({
203
+ explode,
204
+ name,
205
+ style,
206
+ value
207
+ }));
208
+ continue;
209
+ }
210
+ if (typeof value === "object") {
211
+ url = url.replace(match, serializeObjectParam({
212
+ explode,
213
+ name,
214
+ style,
215
+ value,
216
+ valueOnly: true
217
+ }));
218
+ continue;
219
+ }
220
+ if (style === "matrix") {
221
+ url = url.replace(match, `;${serializePrimitiveParam({
222
+ name,
223
+ value
224
+ })}`);
225
+ continue;
226
+ }
227
+ const replaceValue = encodeURIComponent(style === "label" ? `.${value}` : value);
228
+ url = url.replace(match, replaceValue);
229
+ }
230
+ return url;
231
+ };
232
+ const getUrl = ({ baseUrl, path, query, querySerializer, url: _url }) => {
233
+ const pathUrl = _url.startsWith("/") ? _url : `/${_url}`;
234
+ let url = (baseUrl ?? "") + pathUrl;
235
+ if (path) url = defaultPathSerializer({
236
+ path,
237
+ url
238
+ });
239
+ let search = query ? querySerializer(query) : "";
240
+ if (search.startsWith("?")) search = search.substring(1);
241
+ if (search) url += `?${search}`;
242
+ return url;
243
+ };
244
+ function getValidRequestBody(options) {
245
+ const hasBody = options.body !== void 0;
246
+ if (hasBody && options.bodySerializer) {
247
+ if ("serializedBody" in options) return options.serializedBody !== void 0 && options.serializedBody !== "" ? options.serializedBody : null;
248
+ return options.body !== "" ? options.body : null;
249
+ }
250
+ if (hasBody) return options.body;
251
+ }
252
+ //#endregion
253
+ //#region ../packages/api-core/src/api/core/auth.gen.ts
254
+ const getAuthToken = async (auth, callback) => {
255
+ const token = typeof callback === "function" ? await callback(auth) : callback;
256
+ if (!token) return;
257
+ if (auth.scheme === "bearer") return `Bearer ${token}`;
258
+ if (auth.scheme === "basic") return `Basic ${btoa(token)}`;
259
+ return token;
260
+ };
261
+ //#endregion
262
+ //#region ../packages/api-core/src/api/client/utils.gen.ts
263
+ const createQuerySerializer = ({ parameters = {}, ...args } = {}) => {
264
+ const querySerializer = (queryParams) => {
265
+ const search = [];
266
+ if (queryParams && typeof queryParams === "object") for (const name in queryParams) {
267
+ const value = queryParams[name];
268
+ if (value === void 0 || value === null) continue;
269
+ const options = parameters[name] || args;
270
+ if (Array.isArray(value)) {
271
+ const serializedArray = serializeArrayParam({
272
+ allowReserved: options.allowReserved,
273
+ explode: true,
274
+ name,
275
+ style: "form",
276
+ value,
277
+ ...options.array
278
+ });
279
+ if (serializedArray) search.push(serializedArray);
280
+ } else if (typeof value === "object") {
281
+ const serializedObject = serializeObjectParam({
282
+ allowReserved: options.allowReserved,
283
+ explode: true,
284
+ name,
285
+ style: "deepObject",
286
+ value,
287
+ ...options.object
288
+ });
289
+ if (serializedObject) search.push(serializedObject);
290
+ } else {
291
+ const serializedPrimitive = serializePrimitiveParam({
292
+ allowReserved: options.allowReserved,
293
+ name,
294
+ value
295
+ });
296
+ if (serializedPrimitive) search.push(serializedPrimitive);
297
+ }
298
+ }
299
+ return search.join("&");
300
+ };
301
+ return querySerializer;
302
+ };
303
+ /**
304
+ * Infers parseAs value from provided Content-Type header.
305
+ */
306
+ const getParseAs = (contentType) => {
307
+ if (!contentType) return "stream";
308
+ const cleanContent = contentType.split(";")[0]?.trim();
309
+ if (!cleanContent) return;
310
+ if (cleanContent.startsWith("application/json") || cleanContent.endsWith("+json")) return "json";
311
+ if (cleanContent === "multipart/form-data") return "formData";
312
+ if ([
313
+ "application/",
314
+ "audio/",
315
+ "image/",
316
+ "video/"
317
+ ].some((type) => cleanContent.startsWith(type))) return "blob";
318
+ if (cleanContent.startsWith("text/")) return "text";
319
+ };
320
+ const checkForExistence = (options, name) => {
321
+ if (!name) return false;
322
+ if (options.headers.has(name) || options.query?.[name] || options.headers.get("Cookie")?.includes(`${name}=`)) return true;
323
+ return false;
324
+ };
325
+ const setAuthParams = async ({ security, ...options }) => {
326
+ for (const auth of security) {
327
+ if (checkForExistence(options, auth.name)) continue;
328
+ const token = await getAuthToken(auth, options.auth);
329
+ if (!token) continue;
330
+ const name = auth.name ?? "Authorization";
331
+ switch (auth.in) {
332
+ case "query":
333
+ if (!options.query) options.query = {};
334
+ options.query[name] = token;
335
+ break;
336
+ case "cookie":
337
+ options.headers.append("Cookie", `${name}=${token}`);
338
+ break;
339
+ default:
340
+ options.headers.set(name, token);
341
+ break;
342
+ }
343
+ }
344
+ };
345
+ const buildUrl = (options) => getUrl({
346
+ baseUrl: options.baseUrl,
347
+ path: options.path,
348
+ query: options.query,
349
+ querySerializer: typeof options.querySerializer === "function" ? options.querySerializer : createQuerySerializer(options.querySerializer),
350
+ url: options.url
351
+ });
352
+ const mergeConfigs = (a, b) => {
353
+ const config = {
354
+ ...a,
355
+ ...b
356
+ };
357
+ if (config.baseUrl?.endsWith("/")) config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1);
358
+ config.headers = mergeHeaders(a.headers, b.headers);
359
+ return config;
360
+ };
361
+ const headersEntries = (headers) => {
362
+ const entries = [];
363
+ headers.forEach((value, key) => {
364
+ entries.push([key, value]);
365
+ });
366
+ return entries;
367
+ };
368
+ const mergeHeaders = (...headers) => {
369
+ const mergedHeaders = new Headers();
370
+ for (const header of headers) {
371
+ if (!header) continue;
372
+ const iterator = header instanceof Headers ? headersEntries(header) : Object.entries(header);
373
+ for (const [key, value] of iterator) if (value === null) mergedHeaders.delete(key);
374
+ else if (Array.isArray(value)) for (const v of value) mergedHeaders.append(key, v);
375
+ else if (value !== void 0) mergedHeaders.set(key, typeof value === "object" ? JSON.stringify(value) : value);
376
+ }
377
+ return mergedHeaders;
378
+ };
379
+ var Interceptors = class {
380
+ fns = [];
381
+ clear() {
382
+ this.fns = [];
383
+ }
384
+ eject(id) {
385
+ const index = this.getInterceptorIndex(id);
386
+ if (this.fns[index]) this.fns[index] = null;
387
+ }
388
+ exists(id) {
389
+ const index = this.getInterceptorIndex(id);
390
+ return Boolean(this.fns[index]);
391
+ }
392
+ getInterceptorIndex(id) {
393
+ if (typeof id === "number") return this.fns[id] ? id : -1;
394
+ return this.fns.indexOf(id);
395
+ }
396
+ update(id, fn) {
397
+ const index = this.getInterceptorIndex(id);
398
+ if (this.fns[index]) {
399
+ this.fns[index] = fn;
400
+ return id;
401
+ }
402
+ return false;
403
+ }
404
+ use(fn) {
405
+ this.fns.push(fn);
406
+ return this.fns.length - 1;
407
+ }
408
+ };
409
+ const createInterceptors = () => ({
410
+ error: new Interceptors(),
411
+ request: new Interceptors(),
412
+ response: new Interceptors()
413
+ });
414
+ const defaultQuerySerializer = createQuerySerializer({
415
+ allowReserved: false,
416
+ array: {
417
+ explode: true,
418
+ style: "form"
419
+ },
420
+ object: {
421
+ explode: true,
422
+ style: "deepObject"
423
+ }
424
+ });
425
+ const defaultHeaders = { "Content-Type": "application/json" };
426
+ const createConfig = (override = {}) => ({
427
+ ...jsonBodySerializer,
428
+ headers: defaultHeaders,
429
+ parseAs: "auto",
430
+ querySerializer: defaultQuerySerializer,
431
+ ...override
432
+ });
433
+ //#endregion
434
+ //#region ../packages/api-core/src/api/client/client.gen.ts
435
+ const createClient = (config = {}) => {
436
+ let _config = mergeConfigs(createConfig(), config);
437
+ const getConfig = () => ({ ..._config });
438
+ const setConfig = (config) => {
439
+ _config = mergeConfigs(_config, config);
440
+ return getConfig();
441
+ };
442
+ const interceptors = createInterceptors();
443
+ const beforeRequest = async (options) => {
444
+ const opts = {
445
+ ..._config,
446
+ ...options,
447
+ fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
448
+ headers: mergeHeaders(_config.headers, options.headers),
449
+ serializedBody: void 0
450
+ };
451
+ if (opts.security) await setAuthParams({
452
+ ...opts,
453
+ security: opts.security
454
+ });
455
+ if (opts.requestValidator) await opts.requestValidator(opts);
456
+ if (opts.body !== void 0 && opts.bodySerializer) opts.serializedBody = opts.bodySerializer(opts.body);
457
+ if (opts.body === void 0 || opts.serializedBody === "") opts.headers.delete("Content-Type");
458
+ const resolvedOpts = opts;
459
+ return {
460
+ opts: resolvedOpts,
461
+ url: buildUrl(resolvedOpts)
462
+ };
463
+ };
464
+ const request = async (options) => {
465
+ const { opts, url } = await beforeRequest(options);
466
+ const requestInit = {
467
+ redirect: "follow",
468
+ ...opts,
469
+ body: getValidRequestBody(opts)
470
+ };
471
+ let request = new Request(url, requestInit);
472
+ for (const fn of interceptors.request.fns) if (fn) request = await fn(request, opts);
473
+ const _fetch = opts.fetch;
474
+ let response;
475
+ try {
476
+ response = await _fetch(request);
477
+ } catch (error) {
478
+ let finalError = error;
479
+ for (const fn of interceptors.error.fns) if (fn) finalError = await fn(error, void 0, request, opts);
480
+ finalError = finalError || {};
481
+ if (opts.throwOnError) throw finalError;
482
+ return opts.responseStyle === "data" ? void 0 : {
483
+ error: finalError,
484
+ request,
485
+ response: void 0
486
+ };
487
+ }
488
+ for (const fn of interceptors.response.fns) if (fn) response = await fn(response, request, opts);
489
+ const result = {
490
+ request,
491
+ response
492
+ };
493
+ if (response.ok) {
494
+ const parseAs = (opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json";
495
+ if (response.status === 204 || response.headers.get("Content-Length") === "0") {
496
+ let emptyData;
497
+ switch (parseAs) {
498
+ case "arrayBuffer":
499
+ case "blob":
500
+ case "text":
501
+ emptyData = await response[parseAs]();
502
+ break;
503
+ case "formData":
504
+ emptyData = new FormData();
505
+ break;
506
+ case "stream":
507
+ emptyData = response.body;
508
+ break;
509
+ default:
510
+ emptyData = {};
511
+ break;
512
+ }
513
+ return opts.responseStyle === "data" ? emptyData : {
514
+ data: emptyData,
515
+ ...result
516
+ };
517
+ }
518
+ let data;
519
+ switch (parseAs) {
520
+ case "arrayBuffer":
521
+ case "blob":
522
+ case "formData":
523
+ case "text":
524
+ data = await response[parseAs]();
525
+ break;
526
+ case "json": {
527
+ const text = await response.text();
528
+ data = text ? JSON.parse(text) : {};
529
+ break;
530
+ }
531
+ case "stream": return opts.responseStyle === "data" ? response.body : {
532
+ data: response.body,
533
+ ...result
534
+ };
535
+ }
536
+ if (parseAs === "json") {
537
+ if (opts.responseValidator) await opts.responseValidator(data);
538
+ if (opts.responseTransformer) data = await opts.responseTransformer(data);
539
+ }
540
+ return opts.responseStyle === "data" ? data : {
541
+ data,
542
+ ...result
543
+ };
544
+ }
545
+ const textError = await response.text();
546
+ let jsonError;
547
+ try {
548
+ jsonError = JSON.parse(textError);
549
+ } catch {}
550
+ const error = jsonError ?? textError;
551
+ let finalError = error;
552
+ for (const fn of interceptors.error.fns) if (fn) finalError = await fn(error, response, request, opts);
553
+ finalError = finalError || {};
554
+ if (opts.throwOnError) throw finalError;
555
+ return opts.responseStyle === "data" ? void 0 : {
556
+ error: finalError,
557
+ ...result
558
+ };
559
+ };
560
+ const makeMethodFn = (method) => (options) => request({
561
+ ...options,
562
+ method
563
+ });
564
+ const makeSseFn = (method) => async (options) => {
565
+ const { opts, url } = await beforeRequest(options);
566
+ return createSseClient({
567
+ ...opts,
568
+ body: opts.body,
569
+ headers: opts.headers,
570
+ method,
571
+ onRequest: async (url, init) => {
572
+ let request = new Request(url, init);
573
+ for (const fn of interceptors.request.fns) if (fn) request = await fn(request, opts);
574
+ return request;
575
+ },
576
+ serializedBody: getValidRequestBody(opts),
577
+ url
578
+ });
579
+ };
580
+ const _buildUrl = (options) => buildUrl({
581
+ ..._config,
582
+ ...options
583
+ });
584
+ return {
585
+ buildUrl: _buildUrl,
586
+ connect: makeMethodFn("CONNECT"),
587
+ delete: makeMethodFn("DELETE"),
588
+ get: makeMethodFn("GET"),
589
+ getConfig,
590
+ head: makeMethodFn("HEAD"),
591
+ interceptors,
592
+ options: makeMethodFn("OPTIONS"),
593
+ patch: makeMethodFn("PATCH"),
594
+ post: makeMethodFn("POST"),
595
+ put: makeMethodFn("PUT"),
596
+ request,
597
+ setConfig,
598
+ sse: {
599
+ connect: makeSseFn("CONNECT"),
600
+ delete: makeSseFn("DELETE"),
601
+ get: makeSseFn("GET"),
602
+ head: makeSseFn("HEAD"),
603
+ options: makeSseFn("OPTIONS"),
604
+ patch: makeSseFn("PATCH"),
605
+ post: makeSseFn("POST"),
606
+ put: makeSseFn("PUT"),
607
+ trace: makeSseFn("TRACE")
608
+ },
609
+ trace: makeMethodFn("TRACE")
610
+ };
611
+ };
612
+ //#endregion
613
+ //#region ../packages/api-core/src/client.ts
614
+ /**
615
+ * Host-aware Primitive API client and shared error type.
616
+ *
617
+ * Lives in api-core (instead of sdk-node) so the CLI can build a
618
+ * configured request client without taking a dependency on sdk-node.
619
+ * The higher-level `PrimitiveClient` (with `.send`, `.reply`,
620
+ * `.forward`) still lives in sdk-node because it needs the
621
+ * `ReceivedEmail` type from the webhook parsing surface.
622
+ */
623
+ const DEFAULT_API_BASE_URL = "https://api.primitive.dev/v1";
624
+ function createDefaultAuth(apiKey) {
625
+ return (security) => {
626
+ if (security.type === "http" && security.scheme === "bearer") return apiKey;
627
+ };
628
+ }
629
+ var PrimitiveApiClient = class {
630
+ client;
631
+ constructor(options = {}) {
632
+ const { apiKey, auth, apiBaseUrl = DEFAULT_API_BASE_URL, ...config } = options;
633
+ const resolvedAuth = auth ?? createDefaultAuth(apiKey);
634
+ this.client = createClient(createConfig({
635
+ ...config,
636
+ auth: resolvedAuth,
637
+ baseUrl: apiBaseUrl
638
+ }));
639
+ }
640
+ getConfig() {
641
+ return this.client.getConfig();
642
+ }
643
+ setConfig(config) {
644
+ return this.client.setConfig(config);
645
+ }
646
+ };
647
+ //#endregion
648
+ //#region src/oclif/chat-state.ts
649
+ const CHAT_STATE_FILE = "chat-state.json";
650
+ const CHAT_STATE_VERSION = 2;
651
+ const MAX_CONVERSATIONS = 50;
652
+ function isRecord$2(value) {
653
+ return value !== null && typeof value === "object" && !Array.isArray(value);
654
+ }
655
+ function nonEmptyString(value) {
656
+ return typeof value === "string" && value.trim().length > 0 ? value : null;
657
+ }
658
+ function positiveInteger(value) {
659
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : null;
660
+ }
661
+ function nonNegativeInteger(value) {
662
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : null;
663
+ }
664
+ function parseLocalId(value) {
665
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= Number.MAX_SAFE_INTEGER ? value : null;
666
+ }
667
+ function chatStatePath(configDir) {
668
+ return join(configDir, CHAT_STATE_FILE);
669
+ }
670
+ function deleteChatState(configDir) {
671
+ rmSync(chatStatePath(configDir), { force: true });
672
+ }
673
+ function chatConversationState(params) {
674
+ return {
675
+ ...params.input,
676
+ local_id: params.localId,
677
+ updated_at: params.input.updated_at ?? (/* @__PURE__ */ new Date()).toISOString()
678
+ };
679
+ }
680
+ function parseConversation(raw) {
681
+ if (!isRecord$2(raw)) return null;
682
+ const localId = parseLocalId(raw.local_id);
683
+ const recipient = nonEmptyString(raw.recipient);
684
+ const from = nonEmptyString(raw.from);
685
+ const lastReplyEmailId = nonEmptyString(raw.last_reply_email_id);
686
+ const lastSentEmailId = nonEmptyString(raw.last_sent_email_id);
687
+ const lastReplyReceivedAt = nonEmptyString(raw.last_reply_received_at);
688
+ const updatedAt = nonEmptyString(raw.updated_at);
689
+ const timeoutSeconds = nonNegativeInteger(raw.timeout_seconds);
690
+ const strictPhaseSeconds = positiveInteger(raw.strict_phase_seconds);
691
+ if (localId === null || !recipient || !from || !lastReplyEmailId || !lastSentEmailId || !lastReplyReceivedAt || !updatedAt || timeoutSeconds === null || strictPhaseSeconds === null || typeof raw.strict_only !== "boolean") return null;
692
+ const threadId = raw.thread_id !== null && raw.thread_id !== void 0 ? nonEmptyString(raw.thread_id) : null;
693
+ if (raw.thread_id !== null && raw.thread_id !== void 0 && !threadId) return null;
694
+ return {
695
+ from,
696
+ last_reply_email_id: lastReplyEmailId,
697
+ last_reply_received_at: lastReplyReceivedAt,
698
+ last_sent_email_id: lastSentEmailId,
699
+ local_id: localId,
700
+ recipient,
701
+ strict_only: raw.strict_only,
702
+ strict_phase_seconds: strictPhaseSeconds,
703
+ thread_id: threadId,
704
+ timeout_seconds: timeoutSeconds,
705
+ updated_at: updatedAt
706
+ };
707
+ }
708
+ function parseLegacyActiveState(raw) {
709
+ if (!isRecord$2(raw) || raw.version !== 1) return null;
710
+ const legacy = parseConversation({
711
+ ...raw,
712
+ local_id: 0
713
+ });
714
+ if (!legacy) return null;
715
+ return {
716
+ active_local_id: 0,
717
+ conversations: [legacy],
718
+ next_local_id: 1,
719
+ version: CHAT_STATE_VERSION
720
+ };
721
+ }
722
+ function parseChatState(raw) {
723
+ if (!isRecord$2(raw)) return null;
724
+ if (raw.version === 1) return parseLegacyActiveState(raw);
725
+ if (raw.version !== CHAT_STATE_VERSION) return null;
726
+ if (!Array.isArray(raw.conversations)) return null;
727
+ const conversations = raw.conversations.map((entry) => parseConversation(entry)).filter((entry) => entry !== null);
728
+ if (conversations.length !== raw.conversations.length) return null;
729
+ const ids = /* @__PURE__ */ new Set();
730
+ for (const conversation of conversations) {
731
+ if (ids.has(conversation.local_id)) return null;
732
+ ids.add(conversation.local_id);
733
+ }
734
+ const activeLocalId = raw.active_local_id === null ? null : parseLocalId(raw.active_local_id);
735
+ if (activeLocalId === null && raw.active_local_id !== null) return null;
736
+ if (activeLocalId !== null && !ids.has(activeLocalId)) return null;
737
+ const nextLocalId = nonNegativeInteger(raw.next_local_id) ?? 0;
738
+ return {
739
+ active_local_id: activeLocalId,
740
+ conversations,
741
+ next_local_id: Math.max(nextLocalId, ...conversations.map((conversation) => conversation.local_id + 1), 0),
742
+ version: CHAT_STATE_VERSION
743
+ };
744
+ }
745
+ function loadChatState(configDir) {
746
+ let contents;
747
+ try {
748
+ contents = readFileSync(chatStatePath(configDir), "utf8");
749
+ } catch {
750
+ return null;
751
+ }
752
+ try {
753
+ return parseChatState(JSON.parse(contents));
754
+ } catch {
755
+ return null;
756
+ }
757
+ }
758
+ function loadActiveChatState(configDir) {
759
+ const state = loadChatState(configDir);
760
+ if (state?.active_local_id === null || state === null) return null;
761
+ return state.conversations.find((conversation) => conversation.local_id === state.active_local_id) ?? null;
762
+ }
763
+ function loadChatConversationByLocalId(configDir, localId) {
764
+ return loadChatState(configDir)?.conversations.find((conversation) => conversation.local_id === localId) ?? null;
765
+ }
766
+ function saveChatState(configDir, state) {
767
+ mkdirSync(configDir, {
768
+ mode: 448,
769
+ recursive: true
770
+ });
771
+ const path = chatStatePath(configDir);
772
+ const tempPath = join(configDir, `${CHAT_STATE_FILE}.${process.pid}.${randomUUID()}.tmp`);
773
+ try {
774
+ writeFileSync(tempPath, `${JSON.stringify(state, null, 2)}\n`, { mode: 384 });
775
+ renameSync(tempPath, path);
776
+ } catch (error) {
777
+ rmSync(tempPath, { force: true });
778
+ throw error;
779
+ }
780
+ }
781
+ function compareConversationUpdatedAt(left, right) {
782
+ return right.updated_at.localeCompare(left.updated_at);
783
+ }
784
+ function saveActiveChatState(configDir, input, options = {}) {
785
+ const existing = loadChatState(configDir) ?? {
786
+ active_local_id: null,
787
+ conversations: [],
788
+ next_local_id: 0,
789
+ version: CHAT_STATE_VERSION
790
+ };
791
+ const existingByPreferredId = options.preferredLocalId === void 0 ? void 0 : existing.conversations.find((conversation) => conversation.local_id === options.preferredLocalId);
792
+ const existingByThread = input.thread_id === null ? void 0 : existing.conversations.find((conversation) => conversation.thread_id === input.thread_id);
793
+ const localId = existingByPreferredId?.local_id ?? existingByThread?.local_id ?? existing.next_local_id;
794
+ const conversation = chatConversationState({
795
+ input,
796
+ localId
797
+ });
798
+ const conversations = [conversation, ...existing.conversations.filter((item) => item.local_id !== localId)].sort(compareConversationUpdatedAt).slice(0, MAX_CONVERSATIONS);
799
+ const activeStillPresent = conversations.some((item) => item.local_id === localId);
800
+ const nextLocalId = Math.max(existing.next_local_id, localId + 1, ...conversations.map((item) => item.local_id + 1));
801
+ saveChatState(configDir, {
802
+ active_local_id: activeStillPresent ? localId : null,
803
+ conversations,
804
+ next_local_id: nextLocalId,
805
+ version: CHAT_STATE_VERSION
806
+ });
807
+ return conversation;
808
+ }
809
+ //#endregion
810
+ //#region src/oclif/auth.ts
811
+ const CREDENTIALS_FILE = "credentials.json";
812
+ const CREDENTIALS_LOCK_DIR = "credentials.lock";
813
+ const CREDENTIALS_LOCK_OWNER_FILE = "owner.json";
814
+ const CREDENTIALS_LOCK_STALE_MS = 1800 * 1e3;
815
+ const MALFORMED_CREDENTIALS_HINT = "Run `primitive logout` and then `primitive signin`.";
816
+ const CREDENTIALS_LOCK_CLEANUP_SIGNALS = [
817
+ "SIGINT",
818
+ "SIGTERM",
819
+ "SIGHUP"
820
+ ];
821
+ function isRecord$1(value) {
822
+ return value !== null && typeof value === "object" && !Array.isArray(value);
823
+ }
824
+ function requireString(value, key) {
825
+ const raw = value[key];
826
+ if (typeof raw !== "string" || raw.trim().length === 0) throw new Error(`Stored Primitive CLI credentials are malformed: ${key} must be a non-empty string. ${MALFORMED_CREDENTIALS_HINT}`);
827
+ return raw;
828
+ }
829
+ function readStoredApiBaseUrl(raw) {
830
+ const value = raw.api_base_url ?? raw.api_base_url_2 ?? raw.api_base_url_1;
831
+ if (typeof value !== "string" || value.trim().length === 0) throw new Error(`Stored Primitive CLI credentials are malformed: api_base_url must be a non-empty string. ${MALFORMED_CREDENTIALS_HINT}`);
832
+ return normalizeApiBaseUrl(value);
833
+ }
834
+ /**
835
+ * Sentinel returned by parseCredentials when the on-disk credentials were
836
+ * written by an API-key-based CLI. The caller treats this as "not logged in"
837
+ * after clearing the local file. The backing API key is intentionally not
838
+ * revoked; API keys still work when passed explicitly via --api-key/env.
839
+ */
840
+ var LegacyApiKeyCredentialFormatError = class extends Error {
841
+ constructor() {
842
+ super("legacy_api_key_credential_format");
843
+ this.name = "LegacyApiKeyCredentialFormatError";
844
+ }
845
+ };
846
+ function parseCredentials(raw) {
847
+ if (!isRecord$1(raw)) throw new Error(`Stored Primitive CLI credentials are malformed: expected a JSON object. ${MALFORMED_CREDENTIALS_HINT}`);
848
+ if (raw.auth_method !== "oauth") {
849
+ if (typeof raw.api_key === "string" || typeof raw.key_id === "string" || typeof raw.base_url === "string") throw new LegacyApiKeyCredentialFormatError();
850
+ throw new Error(`Stored Primitive CLI credentials are malformed: auth_method must be oauth. ${MALFORMED_CREDENTIALS_HINT}`);
851
+ }
852
+ const orgName = raw.org_name;
853
+ if (orgName !== null && typeof orgName !== "string") throw new Error(`Stored Primitive CLI credentials are malformed: org_name must be a string or null. ${MALFORMED_CREDENTIALS_HINT}`);
854
+ if (requireString(raw, "token_type") !== "Bearer") throw new Error(`Stored Primitive CLI credentials are malformed: token_type must be Bearer. ${MALFORMED_CREDENTIALS_HINT}`);
855
+ return {
856
+ auth_method: "oauth",
857
+ access_token: requireString(raw, "access_token"),
858
+ refresh_token: requireString(raw, "refresh_token"),
859
+ token_type: "Bearer",
860
+ expires_at: requireString(raw, "expires_at"),
861
+ oauth_grant_id: requireString(raw, "oauth_grant_id"),
862
+ oauth_client_id: requireString(raw, "oauth_client_id"),
863
+ org_id: requireString(raw, "org_id"),
864
+ org_name: orgName,
865
+ api_base_url: readStoredApiBaseUrl(raw),
866
+ created_at: requireString(raw, "created_at")
867
+ };
868
+ }
869
+ function credentialsPath(configDir) {
870
+ return join(configDir, CREDENTIALS_FILE);
871
+ }
872
+ function credentialsLockPath(configDir) {
873
+ return join(configDir, CREDENTIALS_LOCK_DIR);
874
+ }
875
+ function normalize(url, fallback) {
876
+ const trimmed = url?.trim();
877
+ if (!trimmed) return fallback;
878
+ return trimmed.replace(/\/+$/, "");
879
+ }
880
+ function canonicalizeKnownApiBaseUrl(url) {
881
+ let parsed;
882
+ try {
883
+ parsed = new URL(url);
884
+ } catch {
885
+ return url;
886
+ }
887
+ if (parsed.pathname.replace(/\/+$/, "") !== "/api/v1") return url;
888
+ if (parsed.hostname === "primitive.dev" || parsed.hostname === "www.primitive.dev") {
889
+ parsed.hostname = "api.primitive.dev";
890
+ parsed.pathname = "/v1";
891
+ return parsed.toString().replace(/\/+$/, "");
892
+ }
893
+ if (parsed.hostname === "primitive-staging-1.com") {
894
+ parsed.hostname = "api.primitive-staging-1.com";
895
+ parsed.pathname = "/v1";
896
+ return parsed.toString().replace(/\/+$/, "");
897
+ }
898
+ return url;
899
+ }
900
+ function normalizeApiBaseUrl(url) {
901
+ return canonicalizeKnownApiBaseUrl(normalize(url, DEFAULT_API_BASE_URL));
902
+ }
903
+ function cliAccessTokenExpiresAt(expiresInSeconds, now = Date.now) {
904
+ return new Date(now() + expiresInSeconds * 1e3).toISOString();
905
+ }
906
+ function loadCliCredentials(configDir) {
907
+ const path = credentialsPath(configDir);
908
+ let contents;
909
+ try {
910
+ contents = readFileSync(path, "utf8");
911
+ } catch (error) {
912
+ if (error && typeof error === "object" && error.code === "ENOENT") return null;
913
+ const detail = error instanceof Error ? error.message : String(error);
914
+ throw new Error(`Could not read Primitive CLI credentials: ${detail}`);
915
+ }
916
+ try {
917
+ return parseCredentials(JSON.parse(contents));
918
+ } catch (error) {
919
+ if (error instanceof LegacyApiKeyCredentialFormatError) {
920
+ try {
921
+ rmSync(path, { force: true });
922
+ deleteChatState(configDir);
923
+ } catch {}
924
+ process.stderr.write("Removed local Primitive CLI API-key login state. API keys are still valid when passed explicitly, but saved CLI auth now uses OAuth. Run `primitive signin` to create an OAuth session. No API key was revoked.\n");
925
+ return null;
926
+ }
927
+ if (error instanceof SyntaxError) throw new Error("Stored Primitive CLI credentials are not valid JSON. Run `primitive logout` and then `primitive signin`.");
928
+ throw error;
929
+ }
930
+ }
931
+ function saveCliCredentials(configDir, credentials) {
932
+ mkdirSync(configDir, {
933
+ mode: 448,
934
+ recursive: true
935
+ });
936
+ const path = credentialsPath(configDir);
937
+ const tempPath = join(configDir, `${CREDENTIALS_FILE}.${process.pid}.${randomUUID()}.tmp`);
938
+ try {
939
+ writeFileSync(tempPath, `${JSON.stringify(credentials, null, 2)}\n`, { mode: 384 });
940
+ chmodSync(tempPath, 384);
941
+ renameSync(tempPath, path);
942
+ chmodSync(path, 384);
943
+ } catch (error) {
944
+ rmSync(tempPath, { force: true });
945
+ throw error;
946
+ }
947
+ }
948
+ function deleteCliCredentials(configDir) {
949
+ rmSync(credentialsPath(configDir), { force: true });
950
+ deleteChatState(configDir);
951
+ }
952
+ function saveSignupCredentials(params) {
953
+ deleteChatState(params.configDir);
954
+ saveCliCredentials(params.configDir, {
955
+ access_token: params.signup.access_token,
956
+ api_base_url: params.apiBaseUrl,
957
+ auth_method: "oauth",
958
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
959
+ expires_at: cliAccessTokenExpiresAt(params.signup.expires_in),
960
+ oauth_client_id: params.signup.oauth_client_id,
961
+ oauth_grant_id: params.signup.oauth_grant_id,
962
+ org_id: params.signup.org_id,
963
+ org_name: params.signup.org_name,
964
+ refresh_token: params.signup.refresh_token,
965
+ token_type: params.signup.token_type
966
+ });
967
+ }
968
+ function deleteCliCredentialsLock(configDir) {
969
+ rmSync(credentialsLockPath(configDir), {
970
+ force: true,
971
+ recursive: true
972
+ });
973
+ }
974
+ function errorCode(error) {
975
+ return error && typeof error === "object" ? error.code : void 0;
976
+ }
977
+ function removeStaleCliCredentialsLock(lockPath, staleMs, now) {
978
+ try {
979
+ const stats = statSync(lockPath);
980
+ if (now() - stats.mtimeMs < staleMs) return false;
981
+ } catch (error) {
982
+ if (errorCode(error) === "ENOENT") return true;
983
+ throw error;
984
+ }
985
+ rmSync(lockPath, {
986
+ force: true,
987
+ recursive: true
988
+ });
989
+ return true;
990
+ }
991
+ function readCliCredentialsLockOwner(lockPath) {
992
+ let raw;
993
+ try {
994
+ raw = readFileSync(join(lockPath, CREDENTIALS_LOCK_OWNER_FILE), "utf8");
995
+ } catch (error) {
996
+ if (errorCode(error) === "ENOENT") return null;
997
+ throw error;
998
+ }
999
+ try {
1000
+ const pid = JSON.parse(raw)?.pid;
1001
+ return Number.isInteger(pid) && pid > 0 ? { pid } : null;
1002
+ } catch {
1003
+ return null;
1004
+ }
1005
+ }
1006
+ function processIsRunning(pid) {
1007
+ try {
1008
+ process.kill(pid, 0);
1009
+ return true;
1010
+ } catch (error) {
1011
+ if (errorCode(error) === "ESRCH") return false;
1012
+ return true;
1013
+ }
1014
+ }
1015
+ function removeRecoverableCliCredentialsLock(params) {
1016
+ const owner = readCliCredentialsLockOwner(params.lockPath);
1017
+ if (owner && params.isRunning(owner.pid)) return false;
1018
+ if (owner) {
1019
+ rmSync(params.lockPath, {
1020
+ force: true,
1021
+ recursive: true
1022
+ });
1023
+ return true;
1024
+ }
1025
+ return removeStaleCliCredentialsLock(params.lockPath, params.staleMs, params.now);
1026
+ }
1027
+ function writeCliCredentialsLockOwner(lockPath) {
1028
+ const ownerPath = join(lockPath, CREDENTIALS_LOCK_OWNER_FILE);
1029
+ writeFileSync(ownerPath, `${JSON.stringify({
1030
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
1031
+ pid: process.pid
1032
+ })}\n`, { mode: 384 });
1033
+ chmodSync(ownerPath, 384);
1034
+ }
1035
+ function installCredentialsLockSignalCleanup(lockPath) {
1036
+ let active = true;
1037
+ const listeners = CREDENTIALS_LOCK_CLEANUP_SIGNALS.map((signal) => {
1038
+ const listener = () => {
1039
+ if (!active) return;
1040
+ active = false;
1041
+ rmSync(lockPath, {
1042
+ force: true,
1043
+ recursive: true
1044
+ });
1045
+ process.exit(signal === "SIGINT" ? 130 : signal === "SIGTERM" ? 143 : 129);
1046
+ };
1047
+ process.once(signal, listener);
1048
+ return {
1049
+ listener,
1050
+ signal
1051
+ };
1052
+ });
1053
+ return () => {
1054
+ if (!active) return;
1055
+ active = false;
1056
+ for (const { listener, signal } of listeners) process.removeListener(signal, listener);
1057
+ };
1058
+ }
1059
+ function credentialsLockInProgressMessage(lockPath) {
1060
+ return `Another Primitive CLI credential operation is already in progress. Wait for it to finish, then retry. If no Primitive auth command is still running, run \`primitive logout --force\` to clear local CLI auth state and remove ${lockPath}.`;
1061
+ }
1062
+ function acquireCliCredentialsLock(configDir, options = {}) {
1063
+ mkdirSync(configDir, {
1064
+ mode: 448,
1065
+ recursive: true
1066
+ });
1067
+ const lockPath = credentialsLockPath(configDir);
1068
+ const installSignalHandlers = options.installSignalHandlers ?? true;
1069
+ const isRunning = options.isProcessRunning ?? processIsRunning;
1070
+ const now = options.now ?? Date.now;
1071
+ const staleMs = options.staleMs ?? CREDENTIALS_LOCK_STALE_MS;
1072
+ let acquired = false;
1073
+ for (let attempt = 0; attempt < 2; attempt += 1) try {
1074
+ mkdirSync(lockPath, { mode: 448 });
1075
+ acquired = true;
1076
+ break;
1077
+ } catch (error) {
1078
+ if (errorCode(error) !== "EEXIST") throw error;
1079
+ if (removeRecoverableCliCredentialsLock({
1080
+ isRunning,
1081
+ lockPath,
1082
+ now,
1083
+ staleMs
1084
+ })) continue;
1085
+ throw new Error(credentialsLockInProgressMessage(lockPath));
1086
+ }
1087
+ if (!acquired) throw new Error(credentialsLockInProgressMessage(lockPath));
1088
+ try {
1089
+ writeCliCredentialsLockOwner(lockPath);
1090
+ } catch (error) {
1091
+ rmSync(lockPath, {
1092
+ force: true,
1093
+ recursive: true
1094
+ });
1095
+ throw error;
1096
+ }
1097
+ const removeSignalCleanup = installSignalHandlers ? installCredentialsLockSignalCleanup(lockPath) : () => void 0;
1098
+ let released = false;
1099
+ return () => {
1100
+ if (released) return;
1101
+ released = true;
1102
+ removeSignalCleanup();
1103
+ rmSync(lockPath, {
1104
+ force: true,
1105
+ recursive: true
1106
+ });
1107
+ };
1108
+ }
1109
+ /**
1110
+ * Detect the PRIMITIVE_KEY vs PRIMITIVE_API_KEY rename trap.
1111
+ *
1112
+ * AGX feedback: users on older docs (or coming from other tools) set
1113
+ * `PRIMITIVE_KEY` and then cannot figure out why the CLI reports no
1114
+ * API key. The CLI reads `PRIMITIVE_API_KEY` only. Returns a stderr
1115
+ * hint when `PRIMITIVE_KEY` is set but `PRIMITIVE_API_KEY` is not,
1116
+ * otherwise null. Exported as a helper so both `doctor` and the
1117
+ * general auth-resolution path surface the same guidance from the
1118
+ * first command the user runs, instead of forcing them to discover
1119
+ * the rename via `doctor` after the fact.
1120
+ */
1121
+ function detectPrimitiveKeyEnvMisname(env = process.env) {
1122
+ const primitiveKey = env.PRIMITIVE_KEY;
1123
+ const primitiveApiKey = env.PRIMITIVE_API_KEY;
1124
+ if ((primitiveKey?.length ?? 0) > 0 && (primitiveApiKey?.length ?? 0) === 0) return "PRIMITIVE_KEY is set but the CLI reads PRIMITIVE_API_KEY. Rename your env var, or re-run with PRIMITIVE_API_KEY=$PRIMITIVE_KEY.";
1125
+ return null;
1126
+ }
1127
+ function resolveCliAuth(params) {
1128
+ const apiKey = params.apiKey?.trim();
1129
+ const apiBaseUrl = normalizeApiBaseUrl(params.apiBaseUrl);
1130
+ if (apiKey) return {
1131
+ apiKey,
1132
+ apiBaseUrl,
1133
+ credentials: null,
1134
+ source: "flag-or-env"
1135
+ };
1136
+ const credentials = loadCliCredentials(params.configDir);
1137
+ if (credentials) return {
1138
+ apiKey: credentials.access_token,
1139
+ apiBaseUrl: credentials.api_base_url,
1140
+ credentials,
1141
+ source: "stored"
1142
+ };
1143
+ return {
1144
+ apiKey: void 0,
1145
+ apiBaseUrl,
1146
+ credentials: null,
1147
+ source: "none"
1148
+ };
1149
+ }
1150
+ //#endregion
1151
+ //#region src/oclif/cli-config.ts
1152
+ const CONFIG_FILE = "config.json";
1153
+ const CONFIG_VERSION = 1;
1154
+ const DEFAULT_ENVIRONMENT = "default";
1155
+ function cliConfigPath(configDir) {
1156
+ return join(configDir, CONFIG_FILE);
1157
+ }
1158
+ function cliConfigError(message) {
1159
+ return new Errors.CLIError(`${message} Run \`primitive config reset\` to clear the local CLI config.`, { exit: 1 });
1160
+ }
1161
+ function isRecord(value) {
1162
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1163
+ }
1164
+ function normalizeCliEnvironmentName(name) {
1165
+ const trimmed = name?.trim();
1166
+ if (!trimmed) throw new Errors.CLIError("Environment name must be a non-empty string.", { exit: 1 });
1167
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,62}$/.test(trimmed)) throw new Errors.CLIError("Environment name must start with a letter or number and may only contain letters, numbers, '.', '_', or '-'.", { exit: 1 });
1168
+ return trimmed;
1169
+ }
1170
+ function validateCliHeaderName(name) {
1171
+ const trimmed = name.trim();
1172
+ if (!trimmed) throw new Errors.CLIError("Header name must be a non-empty string.", { exit: 1 });
1173
+ if (!/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(trimmed)) throw new Errors.CLIError(`Invalid header name: ${name}`, { exit: 1 });
1174
+ if (trimmed.toLowerCase() === "authorization") throw new Errors.CLIError("The Authorization header is managed by PRIMITIVE_API_KEY or saved OAuth CLI credentials.", { exit: 1 });
1175
+ return trimmed;
1176
+ }
1177
+ function validateCliHeaderValue(value, name) {
1178
+ if (value.length === 0) throw new Errors.CLIError(`Header ${name} value must not be empty.`, { exit: 1 });
1179
+ if (/[\r\n\0]/.test(value)) throw new Errors.CLIError(`Header ${name} value must not contain CR, LF, or NUL characters.`, { exit: 1 });
1180
+ return value;
1181
+ }
1182
+ function parseHeaderAssignment(assignment) {
1183
+ const separator = assignment.indexOf("=");
1184
+ if (separator <= 0) throw new Errors.CLIError("Header values must use name=value syntax, for example `x-custom=secret`.", { exit: 1 });
1185
+ const name = validateCliHeaderName(assignment.slice(0, separator));
1186
+ return [name, validateCliHeaderValue(assignment.slice(separator + 1), name)];
1187
+ }
1188
+ function parseHeaders(raw, context) {
1189
+ if (raw === void 0) return {};
1190
+ if (!isRecord(raw)) throw cliConfigError(`${context} headers must be a JSON object.`);
1191
+ const headers = {};
1192
+ for (const [rawName, rawValue] of Object.entries(raw)) {
1193
+ const name = validateCliHeaderName(rawName);
1194
+ if (typeof rawValue !== "string") throw cliConfigError(`${context} header ${name} must be a string.`);
1195
+ headers[name] = validateCliHeaderValue(rawValue, name);
1196
+ }
1197
+ return headers;
1198
+ }
1199
+ function parseEnvironmentConfig(raw, context) {
1200
+ if (!isRecord(raw)) throw cliConfigError(`${context} must be a JSON object.`);
1201
+ const env = {};
1202
+ const rawApiBaseUrl = raw.api_base_url ?? raw.api_base_url_2 ?? raw.api_base_url_1;
1203
+ if (rawApiBaseUrl !== void 0) {
1204
+ if (typeof rawApiBaseUrl !== "string") throw cliConfigError(`${context}.api_base_url must be a string.`);
1205
+ env.api_base_url = normalizeApiBaseUrl(rawApiBaseUrl);
1206
+ }
1207
+ const headers = parseHeaders(raw.headers, context);
1208
+ if (Object.keys(headers).length > 0) env.headers = headers;
1209
+ return env;
1210
+ }
1211
+ function parseStoredCliConfig(raw) {
1212
+ if (!isRecord(raw)) throw cliConfigError("Primitive CLI config must be a JSON object.");
1213
+ if (raw.version !== CONFIG_VERSION) throw cliConfigError(`Primitive CLI config version must be ${CONFIG_VERSION}.`);
1214
+ const currentRaw = raw.current_environment;
1215
+ const current_environment = currentRaw === null || currentRaw === void 0 ? null : typeof currentRaw === "string" ? normalizeCliEnvironmentName(currentRaw) : (() => {
1216
+ throw cliConfigError("Primitive CLI config current_environment must be a string or null.");
1217
+ })();
1218
+ if (!isRecord(raw.environments)) throw cliConfigError("Primitive CLI config environments must be an object.");
1219
+ const environments = {};
1220
+ for (const [rawName, rawEnv] of Object.entries(raw.environments)) {
1221
+ const name = normalizeCliEnvironmentName(rawName);
1222
+ environments[name] = parseEnvironmentConfig(rawEnv, `Primitive CLI config environment ${name}`);
1223
+ }
1224
+ if (current_environment && !environments[current_environment]) throw cliConfigError(`Primitive CLI config current environment ${current_environment} does not exist.`);
1225
+ return {
1226
+ version: CONFIG_VERSION,
1227
+ current_environment,
1228
+ environments
1229
+ };
1230
+ }
1231
+ function emptyCliConfig() {
1232
+ return {
1233
+ version: CONFIG_VERSION,
1234
+ current_environment: null,
1235
+ environments: {}
1236
+ };
1237
+ }
1238
+ function loadCliConfig(configDir) {
1239
+ const path = cliConfigPath(configDir);
1240
+ let contents;
1241
+ try {
1242
+ contents = readFileSync(path, "utf8");
1243
+ } catch (error) {
1244
+ if (error && typeof error === "object" && error.code === "ENOENT") return null;
1245
+ throw cliConfigError(`Could not read Primitive CLI config: ${error instanceof Error ? error.message : String(error)}.`);
1246
+ }
1247
+ try {
1248
+ return parseStoredCliConfig(JSON.parse(contents));
1249
+ } catch (error) {
1250
+ if (error instanceof SyntaxError) throw cliConfigError("Primitive CLI config is not valid JSON.");
1251
+ throw error;
1252
+ }
1253
+ }
1254
+ function saveCliConfig(configDir, config) {
1255
+ mkdirSync(configDir, {
1256
+ mode: 448,
1257
+ recursive: true
1258
+ });
1259
+ const path = cliConfigPath(configDir);
1260
+ const tempPath = join(configDir, `${CONFIG_FILE}.${process.pid}.${randomUUID()}.tmp`);
1261
+ try {
1262
+ writeFileSync(tempPath, `${JSON.stringify(config, null, 2)}\n`, { mode: 384 });
1263
+ renameSync(tempPath, path);
1264
+ } catch (error) {
1265
+ rmSync(tempPath, { force: true });
1266
+ throw error;
1267
+ }
1268
+ }
1269
+ function deleteCliConfig(configDir) {
1270
+ rmSync(cliConfigPath(configDir), { force: true });
1271
+ }
1272
+ function resolveConfigEnvironment(config) {
1273
+ if (!config) return null;
1274
+ const current = config.current_environment;
1275
+ if (current) {
1276
+ const environment = config.environments[current];
1277
+ return environment ? {
1278
+ name: current,
1279
+ config: environment
1280
+ } : null;
1281
+ }
1282
+ const defaultEnvironment = config.environments[DEFAULT_ENVIRONMENT];
1283
+ return defaultEnvironment ? {
1284
+ name: DEFAULT_ENVIRONMENT,
1285
+ config: defaultEnvironment
1286
+ } : null;
1287
+ }
1288
+ function upsertCliEnvironment(params) {
1289
+ const name = normalizeCliEnvironmentName(params.environmentName ?? resolveConfigEnvironment(params.config)?.name ?? "default");
1290
+ const existing = params.config.environments[name] ?? {};
1291
+ const nextHeaders = { ...existing.headers ?? {} };
1292
+ for (const assignment of params.headers ?? []) {
1293
+ const [headerName, value] = parseHeaderAssignment(assignment);
1294
+ nextHeaders[headerName] = value;
1295
+ }
1296
+ for (const rawName of params.unsetHeaders ?? []) delete nextHeaders[validateCliHeaderName(rawName)];
1297
+ const nextEnvironment = {
1298
+ ...existing,
1299
+ ...params.apiBaseUrl !== void 0 ? { api_base_url: normalizeApiBaseUrl(params.apiBaseUrl) } : {},
1300
+ ...Object.keys(nextHeaders).length > 0 ? { headers: nextHeaders } : {}
1301
+ };
1302
+ if (Object.keys(nextHeaders).length === 0) delete nextEnvironment.headers;
1303
+ return {
1304
+ ...params.config,
1305
+ current_environment: params.use === false ? params.config.current_environment : name,
1306
+ environments: {
1307
+ ...params.config.environments,
1308
+ [name]: nextEnvironment
1309
+ }
1310
+ };
1311
+ }
1312
+ function removeCliEnvironment(config, environmentName) {
1313
+ const name = normalizeCliEnvironmentName(environmentName);
1314
+ const environments = { ...config.environments };
1315
+ delete environments[name];
1316
+ return {
1317
+ ...config,
1318
+ current_environment: config.current_environment === name ? null : config.current_environment,
1319
+ environments
1320
+ };
1321
+ }
1322
+ function redactCliEnvironment(environment) {
1323
+ const headers = environment.headers && Object.keys(environment.headers).length > 0 ? Object.fromEntries(Object.keys(environment.headers).map((name) => [name, "***"])) : void 0;
1324
+ return {
1325
+ ...environment,
1326
+ ...headers ? { headers } : {}
1327
+ };
1328
+ }
1329
+ //#endregion
1330
+ export { PrimitiveApiClient as A, saveCliCredentials as C, loadActiveChatState as D, deleteChatState as E, createConfig as M, loadChatConversationByLocalId as O, resolveCliAuth as S, chatStatePath as T, deleteCliCredentials as _, normalizeCliEnvironmentName as a, loadCliCredentials as b, resolveConfigEnvironment as c, validateCliHeaderName as d, validateCliHeaderValue as f, credentialsPath as g, credentialsLockPath as h, loadCliConfig as i, createClient as j, saveActiveChatState as k, saveCliConfig as l, cliAccessTokenExpiresAt as m, deleteCliConfig as n, redactCliEnvironment as o, acquireCliCredentialsLock as p, emptyCliConfig as r, removeCliEnvironment as s, DEFAULT_ENVIRONMENT as t, upsertCliEnvironment as u, deleteCliCredentialsLock as v, saveSignupCredentials as w, normalizeApiBaseUrl as x, detectPrimitiveKeyEnvMisname as y };