@chainpatrol/cli 0.1.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 +224 -0
- package/dist/breakdown-JVN66HY3.js +69 -0
- package/dist/chunk-D2QGXYXZ.js +126 -0
- package/dist/chunk-E2LAMILJ.js +48 -0
- package/dist/chunk-EEG7T6WT.js +287 -0
- package/dist/chunk-H7UKKLCV.js +6572 -0
- package/dist/chunk-IUZB3DQW.js +237 -0
- package/dist/chunk-JCMWDZYY.js +39 -0
- package/dist/chunk-U73SABXK.js +46 -0
- package/dist/chunk-VFT3TD3E.js +82 -0
- package/dist/cli.js +895 -0
- package/dist/completions-EGQIARFC.js +12 -0
- package/dist/config-Z3TASRME.js +10 -0
- package/dist/configs-update-BK2S6AZ6.js +101 -0
- package/dist/create-4SQUBQI7.js +128 -0
- package/dist/drift-VRZKQC4P.js +80 -0
- package/dist/found-4O3AISNI.js +93 -0
- package/dist/healthcheck-7DR5MGEQ.js +94 -0
- package/dist/list-6L7XR4SZ.js +94 -0
- package/dist/list-HZAHEHDM.js +40 -0
- package/dist/list-IBMM562A.js +139 -0
- package/dist/list-json-TPBLJBD3.js +24 -0
- package/dist/login-G7LPHKDR.js +162 -0
- package/dist/login-json-LKB72OFY.js +71 -0
- package/dist/logout-LA7VEKON.js +25 -0
- package/dist/logout-json-4GIJZJ46.js +18 -0
- package/dist/run-PABQKATZ.js +112 -0
- package/dist/run-U62KVNTH.js +34 -0
- package/dist/setup-skill-U24CJZ6T.js +211 -0
- package/dist/snapshot-JEVDTE74.js +88 -0
- package/dist/summary-YG5NYIOA.js +61 -0
- package/dist/validate-PI7GPT5I.js +79 -0
- package/package.json +50 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getConfig,
|
|
3
|
+
getConfigDir
|
|
4
|
+
} from "./chunk-U73SABXK.js";
|
|
5
|
+
|
|
6
|
+
// src/lib/auth.ts
|
|
7
|
+
import {
|
|
8
|
+
appendFileSync,
|
|
9
|
+
chmodSync,
|
|
10
|
+
closeSync,
|
|
11
|
+
existsSync,
|
|
12
|
+
mkdirSync,
|
|
13
|
+
openSync,
|
|
14
|
+
readFileSync,
|
|
15
|
+
unlinkSync,
|
|
16
|
+
writeFileSync
|
|
17
|
+
} from "fs";
|
|
18
|
+
import { join } from "path";
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
var CLIENT_ID = "chainpatrol-cli";
|
|
21
|
+
var AUTH_TIMEOUT_MS = 3e4;
|
|
22
|
+
var credentialsSchema = z.object({
|
|
23
|
+
accessToken: z.string().min(1),
|
|
24
|
+
expiresAt: z.string().datetime({ offset: true }),
|
|
25
|
+
email: z.string().email().optional()
|
|
26
|
+
});
|
|
27
|
+
var getSessionResponseSchema = z.object({
|
|
28
|
+
user: z.object({
|
|
29
|
+
email: z.string().email().optional()
|
|
30
|
+
}).optional()
|
|
31
|
+
});
|
|
32
|
+
var deviceCodeResponseSchema = z.object({
|
|
33
|
+
device_code: z.string().min(1),
|
|
34
|
+
user_code: z.string().min(1),
|
|
35
|
+
verification_uri: z.string().url(),
|
|
36
|
+
verification_uri_complete: z.string().url().optional(),
|
|
37
|
+
expires_in: z.number().int().positive(),
|
|
38
|
+
interval: z.number().int().positive()
|
|
39
|
+
});
|
|
40
|
+
var pollSuccessResponseSchema = z.object({
|
|
41
|
+
access_token: z.string().min(1),
|
|
42
|
+
token_type: z.string().min(1),
|
|
43
|
+
expires_in: z.number().int().positive(),
|
|
44
|
+
scope: z.string()
|
|
45
|
+
});
|
|
46
|
+
var pollErrorResponseSchema = z.object({
|
|
47
|
+
error: z.string().optional()
|
|
48
|
+
});
|
|
49
|
+
function credsFile() {
|
|
50
|
+
return join(getConfigDir(), "credentials.json");
|
|
51
|
+
}
|
|
52
|
+
function debugFile() {
|
|
53
|
+
return join(getConfigDir(), "auth-debug.log");
|
|
54
|
+
}
|
|
55
|
+
function sanitizeDebugValue(value) {
|
|
56
|
+
if (Array.isArray(value)) {
|
|
57
|
+
return value.map((item) => sanitizeDebugValue(item));
|
|
58
|
+
}
|
|
59
|
+
if (value && typeof value === "object") {
|
|
60
|
+
const output = {};
|
|
61
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
62
|
+
const isSensitive = /token|secret|password|authorization|key|session/i.test(key);
|
|
63
|
+
output[key] = isSensitive ? "[redacted]" : sanitizeDebugValue(entry);
|
|
64
|
+
}
|
|
65
|
+
return output;
|
|
66
|
+
}
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
function writeDebugLog(context, payload) {
|
|
70
|
+
try {
|
|
71
|
+
mkdirSync(getConfigDir(), { recursive: true });
|
|
72
|
+
const fileDescriptor = openSync(debugFile(), "a", 384);
|
|
73
|
+
chmodSync(debugFile(), 384);
|
|
74
|
+
try {
|
|
75
|
+
appendFileSync(
|
|
76
|
+
fileDescriptor,
|
|
77
|
+
`${(/* @__PURE__ */ new Date()).toISOString()} ${context}
|
|
78
|
+
${JSON.stringify(sanitizeDebugValue(payload), null, 2)}
|
|
79
|
+
|
|
80
|
+
`
|
|
81
|
+
);
|
|
82
|
+
} finally {
|
|
83
|
+
closeSync(fileDescriptor);
|
|
84
|
+
}
|
|
85
|
+
chmodSync(debugFile(), 384);
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
var AuthNotLoggedInError = class extends Error {
|
|
90
|
+
constructor() {
|
|
91
|
+
super("Not logged in. Run `chainpatrol login` first.");
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
var AuthExpiredError = class extends Error {
|
|
95
|
+
constructor() {
|
|
96
|
+
super("Session expired. Run `chainpatrol login` to re-authenticate.");
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
var AuthCorruptedError = class extends Error {
|
|
100
|
+
constructor() {
|
|
101
|
+
super("Stored credentials are invalid. Run `chainpatrol login` to re-authenticate.");
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
function getCredentials() {
|
|
105
|
+
if (!existsSync(credsFile())) {
|
|
106
|
+
throw new AuthNotLoggedInError();
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
const raw = readFileSync(credsFile(), "utf-8");
|
|
110
|
+
const parsed = JSON.parse(raw);
|
|
111
|
+
const validation = credentialsSchema.safeParse(parsed);
|
|
112
|
+
if (!validation.success) {
|
|
113
|
+
writeDebugLog("credentials-validation-failed", {
|
|
114
|
+
path: credsFile(),
|
|
115
|
+
errors: validation.error.issues,
|
|
116
|
+
parsed
|
|
117
|
+
});
|
|
118
|
+
throw new AuthCorruptedError();
|
|
119
|
+
}
|
|
120
|
+
return validation.data;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (error instanceof AuthCorruptedError) {
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
writeDebugLog("credentials-read-failed", {
|
|
126
|
+
path: credsFile(),
|
|
127
|
+
message: error instanceof Error ? error.message : String(error)
|
|
128
|
+
});
|
|
129
|
+
throw new AuthCorruptedError();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function storeCredentials(creds) {
|
|
133
|
+
mkdirSync(getConfigDir(), { recursive: true });
|
|
134
|
+
writeFileSync(credsFile(), JSON.stringify(creds, null, 2), { mode: 384 });
|
|
135
|
+
}
|
|
136
|
+
function clearCredentials() {
|
|
137
|
+
if (existsSync(credsFile())) {
|
|
138
|
+
unlinkSync(credsFile());
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function isLoggedIn() {
|
|
142
|
+
try {
|
|
143
|
+
getValidCredentials();
|
|
144
|
+
return true;
|
|
145
|
+
} catch {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function getValidCredentials() {
|
|
150
|
+
const creds = getCredentials();
|
|
151
|
+
const expiresMs = new Date(creds.expiresAt).getTime();
|
|
152
|
+
if (Number.isNaN(expiresMs) || expiresMs < Date.now()) {
|
|
153
|
+
clearCredentials();
|
|
154
|
+
throw new AuthExpiredError();
|
|
155
|
+
}
|
|
156
|
+
return creds;
|
|
157
|
+
}
|
|
158
|
+
async function fetchUserEmail(accessToken) {
|
|
159
|
+
const config = getConfig();
|
|
160
|
+
try {
|
|
161
|
+
const res = await fetch(`${config.apiUrl}/api/better-auth/get-session`, {
|
|
162
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
163
|
+
signal: AbortSignal.timeout(AUTH_TIMEOUT_MS)
|
|
164
|
+
});
|
|
165
|
+
if (!res.ok) return null;
|
|
166
|
+
const rawData = await res.json();
|
|
167
|
+
const data = getSessionResponseSchema.safeParse(rawData);
|
|
168
|
+
if (!data.success) {
|
|
169
|
+
writeDebugLog("fetch-user-email-response-invalid", {
|
|
170
|
+
status: res.status,
|
|
171
|
+
errors: data.error.issues,
|
|
172
|
+
rawData
|
|
173
|
+
});
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
return data.data.user?.email ?? null;
|
|
177
|
+
} catch {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async function authFetch(url, body) {
|
|
182
|
+
try {
|
|
183
|
+
return await fetch(url, {
|
|
184
|
+
method: "POST",
|
|
185
|
+
headers: { "Content-Type": "application/json" },
|
|
186
|
+
body: JSON.stringify(body),
|
|
187
|
+
signal: AbortSignal.timeout(AUTH_TIMEOUT_MS)
|
|
188
|
+
});
|
|
189
|
+
} catch (err) {
|
|
190
|
+
if (err instanceof DOMException && err.name === "TimeoutError") {
|
|
191
|
+
throw new Error("Request timed out. Check your network connection and try again.");
|
|
192
|
+
}
|
|
193
|
+
throw new Error("Network error. Check your internet connection and try again.");
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
async function requestDeviceCode() {
|
|
197
|
+
const config = getConfig();
|
|
198
|
+
const res = await authFetch(`${config.apiUrl}/api/better-auth/device/code`, {
|
|
199
|
+
client_id: CLIENT_ID
|
|
200
|
+
});
|
|
201
|
+
if (!res.ok) {
|
|
202
|
+
if (res.status >= 500) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
"ChainPatrol API is temporarily unavailable. Please try again later."
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
throw new Error(`Authentication failed (${res.status}). Please try again.`);
|
|
208
|
+
}
|
|
209
|
+
const rawData = await res.json();
|
|
210
|
+
const data = deviceCodeResponseSchema.safeParse(rawData);
|
|
211
|
+
if (!data.success) {
|
|
212
|
+
writeDebugLog("request-device-code-response-invalid", {
|
|
213
|
+
status: res.status,
|
|
214
|
+
errors: data.error.issues,
|
|
215
|
+
rawData
|
|
216
|
+
});
|
|
217
|
+
throw new Error("Received an invalid authentication response. Please try again.");
|
|
218
|
+
}
|
|
219
|
+
return data.data;
|
|
220
|
+
}
|
|
221
|
+
async function pollForToken(deviceCode) {
|
|
222
|
+
const config = getConfig();
|
|
223
|
+
const res = await authFetch(`${config.apiUrl}/api/better-auth/device/token`, {
|
|
224
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
225
|
+
device_code: deviceCode,
|
|
226
|
+
client_id: CLIENT_ID
|
|
227
|
+
});
|
|
228
|
+
if (res.ok) {
|
|
229
|
+
const rawData = await res.json();
|
|
230
|
+
const data = pollSuccessResponseSchema.safeParse(rawData);
|
|
231
|
+
if (!data.success) {
|
|
232
|
+
writeDebugLog("poll-for-token-success-response-invalid", {
|
|
233
|
+
status: res.status,
|
|
234
|
+
errors: data.error.issues,
|
|
235
|
+
rawData
|
|
236
|
+
});
|
|
237
|
+
return {
|
|
238
|
+
status: "error",
|
|
239
|
+
message: "Received an invalid authentication response. Please run `chainpatrol login` again."
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
status: "success",
|
|
244
|
+
accessToken: data.data.access_token,
|
|
245
|
+
expiresIn: data.data.expires_in
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
const rawError = await res.json();
|
|
249
|
+
const error = pollErrorResponseSchema.safeParse(rawError);
|
|
250
|
+
if (!error.success) {
|
|
251
|
+
writeDebugLog("poll-for-token-error-response-invalid", {
|
|
252
|
+
status: res.status,
|
|
253
|
+
errors: error.error.issues,
|
|
254
|
+
rawError
|
|
255
|
+
});
|
|
256
|
+
return {
|
|
257
|
+
status: "error",
|
|
258
|
+
message: "Received an invalid authentication response. Please run `chainpatrol login` again."
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
switch (error.data.error) {
|
|
262
|
+
case "authorization_pending":
|
|
263
|
+
return { status: "pending" };
|
|
264
|
+
case "slow_down":
|
|
265
|
+
return { status: "slow_down", addSeconds: 5 };
|
|
266
|
+
case "expired_token":
|
|
267
|
+
return { status: "expired" };
|
|
268
|
+
case "access_denied":
|
|
269
|
+
return { status: "denied" };
|
|
270
|
+
default:
|
|
271
|
+
return { status: "error", message: error.data.error ?? "Unknown error" };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export {
|
|
276
|
+
AuthNotLoggedInError,
|
|
277
|
+
AuthExpiredError,
|
|
278
|
+
AuthCorruptedError,
|
|
279
|
+
getCredentials,
|
|
280
|
+
storeCredentials,
|
|
281
|
+
clearCredentials,
|
|
282
|
+
isLoggedIn,
|
|
283
|
+
getValidCredentials,
|
|
284
|
+
fetchUserEmail,
|
|
285
|
+
requestDeviceCode,
|
|
286
|
+
pollForToken
|
|
287
|
+
};
|