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.
- package/README.md +89 -0
- package/bin/run.js +16 -0
- package/dist/cli-config-B5hrwe8q.js +1330 -0
- package/dist/oclif/index.js +22646 -0
- package/dist/oclif/proxy-auto-detect.js +71 -0
- package/dist/oclif/root-signup-hint.js +136 -0
- package/man/primitive.1 +111 -0
- package/package.json +143 -0
|
@@ -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 };
|