@manan_joshi/atelier 0.1.1 → 0.1.2
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/dist/index.js +13 -2340
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,2342 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { execFileSync as
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import { Command } from "commander";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import { dirname, join } from "path";
|
|
17
|
-
var CONFIG_DIR = join(homedir(), ".config", "atelier");
|
|
18
|
-
var SESSION_PATH = join(CONFIG_DIR, "session.json");
|
|
19
|
-
var CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
20
|
-
var KEYCHAIN_SERVICE = "dev.atelier.session";
|
|
21
|
-
var DEFAULT_API_URL = "https://atelier.mananjoshi.me/api";
|
|
22
|
-
var DEFAULT_GITHUB_CLIENT_ID = "Ov23liiscZlMXcJ2RLnd";
|
|
23
|
-
function getConfiguredApiUrl() {
|
|
24
|
-
return process.env.ATELIER_API_URL ?? DEFAULT_API_URL;
|
|
25
|
-
}
|
|
26
|
-
function getConfiguredGithubClientId() {
|
|
27
|
-
return process.env.ATELIER_GITHUB_CLIENT_ID ?? DEFAULT_GITHUB_CLIENT_ID;
|
|
28
|
-
}
|
|
29
|
-
async function loginWithGitHub(options) {
|
|
30
|
-
const apiUrl = options.apiUrl ?? getConfiguredApiUrl();
|
|
31
|
-
const clientId = options.clientId ?? getConfiguredGithubClientId();
|
|
32
|
-
const device = await requestDeviceCode(clientId);
|
|
33
|
-
await options.onPrompt({
|
|
34
|
-
verificationUri: device.verification_uri,
|
|
35
|
-
userCode: device.user_code,
|
|
36
|
-
expiresIn: device.expires_in
|
|
37
|
-
});
|
|
38
|
-
const githubAccessToken = await pollForGitHubAccessToken(clientId, device, options.onPoll);
|
|
39
|
-
const exchange = await fetch(`${apiUrl}/auth/github/exchange`, {
|
|
40
|
-
method: "POST",
|
|
41
|
-
headers: { "content-type": "application/json" },
|
|
42
|
-
body: JSON.stringify({ githubAccessToken })
|
|
43
|
-
});
|
|
44
|
-
if (!exchange.ok) {
|
|
45
|
-
throw new Error(`Atelier API rejected GitHub login (${exchange.status})`);
|
|
46
|
-
}
|
|
47
|
-
const body = await exchange.json();
|
|
48
|
-
saveSession({ apiUrl, user: body.user, token: body.token });
|
|
49
|
-
return { apiUrl, user: body.user };
|
|
50
|
-
}
|
|
51
|
-
async function getCurrentUser() {
|
|
52
|
-
const authenticated = getAuthenticatedSession();
|
|
53
|
-
if (!authenticated)
|
|
54
|
-
return;
|
|
55
|
-
const response = await fetch(`${authenticated.apiUrl}/me`, {
|
|
56
|
-
headers: { authorization: `Bearer ${authenticated.token}` }
|
|
57
|
-
});
|
|
58
|
-
if (!response.ok)
|
|
59
|
-
return;
|
|
60
|
-
const body = await response.json();
|
|
61
|
-
return { user: body.user, apiUrl: authenticated.apiUrl };
|
|
62
|
-
}
|
|
63
|
-
function getAuthenticatedSession() {
|
|
64
|
-
const session = readSession();
|
|
65
|
-
if (!session)
|
|
66
|
-
return;
|
|
67
|
-
const token = readSessionToken(session);
|
|
68
|
-
if (!token)
|
|
69
|
-
return;
|
|
70
|
-
return { apiUrl: session.apiUrl, user: session.user, token };
|
|
71
|
-
}
|
|
72
|
-
function getActiveProfile() {
|
|
73
|
-
return readConfig().activeProfile ?? "personal";
|
|
74
|
-
}
|
|
75
|
-
function setActiveProfile(profile) {
|
|
76
|
-
writeConfig({ ...readConfig(), activeProfile: profile });
|
|
77
|
-
}
|
|
78
|
-
function logout() {
|
|
79
|
-
const session = readSession();
|
|
80
|
-
if (session?.tokenStorage === "keychain")
|
|
81
|
-
deleteKeychainToken(session.user.id);
|
|
82
|
-
if (existsSync(SESSION_PATH))
|
|
83
|
-
rmSync(SESSION_PATH);
|
|
84
|
-
}
|
|
85
|
-
function readSession() {
|
|
86
|
-
if (!existsSync(SESSION_PATH))
|
|
87
|
-
return;
|
|
88
|
-
return JSON.parse(readFileSync(SESSION_PATH, "utf-8"));
|
|
89
|
-
}
|
|
90
|
-
function readConfig() {
|
|
91
|
-
if (!existsSync(CONFIG_PATH))
|
|
92
|
-
return {};
|
|
93
|
-
return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
94
|
-
}
|
|
95
|
-
function writeConfig(config) {
|
|
96
|
-
mkdirSync(dirname(CONFIG_PATH), { recursive: true });
|
|
97
|
-
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + `
|
|
98
|
-
`, "utf-8");
|
|
99
|
-
chmodSync(CONFIG_PATH, 384);
|
|
100
|
-
}
|
|
101
|
-
function saveSession(input) {
|
|
102
|
-
mkdirSync(dirname(SESSION_PATH), { recursive: true });
|
|
103
|
-
const canUseKeychain = platform() === "darwin";
|
|
104
|
-
const session = {
|
|
105
|
-
apiUrl: input.apiUrl,
|
|
106
|
-
user: input.user,
|
|
107
|
-
tokenStorage: canUseKeychain ? "keychain" : "file",
|
|
108
|
-
createdAt: new Date().toISOString()
|
|
109
|
-
};
|
|
110
|
-
if (canUseKeychain) {
|
|
111
|
-
saveKeychainToken(input.user.id, input.token);
|
|
112
|
-
} else {
|
|
113
|
-
session.token = input.token;
|
|
114
|
-
}
|
|
115
|
-
writeFileSync(SESSION_PATH, JSON.stringify(session, null, 2) + `
|
|
116
|
-
`, "utf-8");
|
|
117
|
-
chmodSync(SESSION_PATH, 384);
|
|
118
|
-
}
|
|
119
|
-
function readSessionToken(session) {
|
|
120
|
-
if (session.tokenStorage === "file")
|
|
121
|
-
return session.token;
|
|
122
|
-
return readKeychainToken(session.user.id);
|
|
123
|
-
}
|
|
124
|
-
async function requestDeviceCode(clientId) {
|
|
125
|
-
const response = await fetch("https://github.com/login/device/code", {
|
|
126
|
-
method: "POST",
|
|
127
|
-
headers: { accept: "application/json", "content-type": "application/json" },
|
|
128
|
-
body: JSON.stringify({ client_id: clientId, scope: "read:user user:email" })
|
|
129
|
-
});
|
|
130
|
-
if (!response.ok)
|
|
131
|
-
throw new Error(`GitHub device-code request failed (${response.status})`);
|
|
132
|
-
return response.json();
|
|
133
|
-
}
|
|
134
|
-
async function pollForGitHubAccessToken(clientId, device, onPoll) {
|
|
135
|
-
let interval = device.interval;
|
|
136
|
-
const expiresAt = Date.now() + device.expires_in * 1000;
|
|
137
|
-
while (Date.now() < expiresAt) {
|
|
138
|
-
await sleep(interval * 1000);
|
|
139
|
-
await onPoll?.("Waiting for GitHub authorization\u2026");
|
|
140
|
-
const response = await fetch("https://github.com/login/oauth/access_token", {
|
|
141
|
-
method: "POST",
|
|
142
|
-
headers: { accept: "application/json", "content-type": "application/json" },
|
|
143
|
-
body: JSON.stringify({ client_id: clientId, device_code: device.device_code, grant_type: "urn:ietf:params:oauth:grant-type:device_code" })
|
|
144
|
-
});
|
|
145
|
-
if (!response.ok)
|
|
146
|
-
throw new Error(`GitHub token polling failed (${response.status})`);
|
|
147
|
-
const body = await response.json();
|
|
148
|
-
if (body.access_token)
|
|
149
|
-
return body.access_token;
|
|
150
|
-
if (body.error === "authorization_pending")
|
|
151
|
-
continue;
|
|
152
|
-
if (body.error === "slow_down") {
|
|
153
|
-
interval += 5;
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
if (body.error === "access_denied")
|
|
157
|
-
throw new Error("GitHub login was denied");
|
|
158
|
-
if (body.error === "expired_token")
|
|
159
|
-
throw new Error("GitHub login code expired");
|
|
160
|
-
throw new Error(body.error_description ?? "GitHub login failed");
|
|
161
|
-
}
|
|
162
|
-
throw new Error("GitHub login timed out");
|
|
163
|
-
}
|
|
164
|
-
function saveKeychainToken(account, token) {
|
|
165
|
-
execFileSync("security", ["add-generic-password", "-a", account, "-s", KEYCHAIN_SERVICE, "-w", token, "-U"], { stdio: "ignore" });
|
|
166
|
-
}
|
|
167
|
-
function readKeychainToken(account) {
|
|
168
|
-
try {
|
|
169
|
-
return execFileSync("security", ["find-generic-password", "-a", account, "-s", KEYCHAIN_SERVICE, "-w"], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
170
|
-
} catch {
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
function deleteKeychainToken(account) {
|
|
175
|
-
try {
|
|
176
|
-
execFileSync("security", ["delete-generic-password", "-a", account, "-s", KEYCHAIN_SERVICE], { stdio: "ignore" });
|
|
177
|
-
} catch {}
|
|
178
|
-
}
|
|
179
|
-
function sleep(ms) {
|
|
180
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// ../../packages/api-client/src/index.ts
|
|
184
|
-
async function listProfiles() {
|
|
185
|
-
const response = await authedFetch("/profiles");
|
|
186
|
-
const body = await response.json();
|
|
187
|
-
return body.profiles;
|
|
188
|
-
}
|
|
189
|
-
async function createProfile(name) {
|
|
190
|
-
const response = await authedFetch("/profiles", {
|
|
191
|
-
method: "POST",
|
|
192
|
-
headers: { "content-type": "application/json" },
|
|
193
|
-
body: JSON.stringify({ name })
|
|
194
|
-
});
|
|
195
|
-
const body = await response.json();
|
|
196
|
-
return body.profile;
|
|
197
|
-
}
|
|
198
|
-
async function getProfile(name) {
|
|
199
|
-
const response = await authedFetch(`/profiles/${encodeURIComponent(name)}`);
|
|
200
|
-
if (response.status === 404)
|
|
201
|
-
return;
|
|
202
|
-
const body = await response.json();
|
|
203
|
-
return body.profile;
|
|
204
|
-
}
|
|
205
|
-
async function getVault() {
|
|
206
|
-
return await (await authedFetch("/vault")).json();
|
|
207
|
-
}
|
|
208
|
-
async function saveVault(input) {
|
|
209
|
-
await authedFetch("/vault", {
|
|
210
|
-
method: "PUT",
|
|
211
|
-
headers: { "content-type": "application/json" },
|
|
212
|
-
body: JSON.stringify(input)
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
async function getProfileKey(profileName) {
|
|
216
|
-
const response = await authedFetch(`/profiles/${encodeURIComponent(profileName)}/key`);
|
|
217
|
-
if (response.status === 404)
|
|
218
|
-
return;
|
|
219
|
-
const body = await response.json();
|
|
220
|
-
return body.profileKey;
|
|
221
|
-
}
|
|
222
|
-
async function saveProfileKey(profileName, input) {
|
|
223
|
-
const response = await authedFetch(`/profiles/${encodeURIComponent(profileName)}/key`, {
|
|
224
|
-
method: "PUT",
|
|
225
|
-
headers: { "content-type": "application/json" },
|
|
226
|
-
body: JSON.stringify(input)
|
|
227
|
-
});
|
|
228
|
-
const body = await response.json();
|
|
229
|
-
return body.profileKey;
|
|
230
|
-
}
|
|
231
|
-
async function listSavedConfigs(profileName) {
|
|
232
|
-
const response = await authedFetch(`/profiles/${encodeURIComponent(profileName)}/configs`);
|
|
233
|
-
const body = await response.json();
|
|
234
|
-
return body.configs;
|
|
235
|
-
}
|
|
236
|
-
async function getSavedConfig(profileName, stableId) {
|
|
237
|
-
const response = await authedFetch(`/profiles/${encodeURIComponent(profileName)}/configs/${encodeURIComponent(stableId)}`);
|
|
238
|
-
if (response.status === 404)
|
|
239
|
-
return;
|
|
240
|
-
const body = await response.json();
|
|
241
|
-
return body.config;
|
|
242
|
-
}
|
|
243
|
-
async function createConfigVersion(profileName, stableId, input) {
|
|
244
|
-
const response = await authedFetch(`/profiles/${encodeURIComponent(profileName)}/configs/${encodeURIComponent(stableId)}/versions`, {
|
|
245
|
-
method: "POST",
|
|
246
|
-
headers: { "content-type": "application/json" },
|
|
247
|
-
body: JSON.stringify(input)
|
|
248
|
-
});
|
|
249
|
-
return await response.json();
|
|
250
|
-
}
|
|
251
|
-
async function authedFetch(path, init = {}) {
|
|
252
|
-
const session = getAuthenticatedSession();
|
|
253
|
-
if (!session)
|
|
254
|
-
throw new Error("Not logged in. Run `atl login`.");
|
|
255
|
-
const response = await fetch(`${session.apiUrl}${path}`, {
|
|
256
|
-
...init,
|
|
257
|
-
headers: {
|
|
258
|
-
...init.headers,
|
|
259
|
-
authorization: `Bearer ${session.token}`
|
|
260
|
-
}
|
|
261
|
-
});
|
|
262
|
-
if (!response.ok && response.status !== 404) {
|
|
263
|
-
const body = await response.json().catch(() => {
|
|
264
|
-
return;
|
|
265
|
-
});
|
|
266
|
-
throw new Error(body?.error ?? `Atelier API request failed (${response.status})`);
|
|
267
|
-
}
|
|
268
|
-
return response;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// ../../packages/save-client/src/index.ts
|
|
272
|
-
import { createCipheriv as createCipheriv2, createHash, randomBytes as randomBytes2 } from "crypto";
|
|
273
|
-
import { readFile } from "fs/promises";
|
|
274
|
-
|
|
275
|
-
// ../../packages/vault-client/src/index.ts
|
|
276
|
-
import { execFileSync as execFileSync2 } from "child_process";
|
|
277
|
-
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
|
|
278
|
-
import { chmodSync as chmodSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
279
|
-
import { homedir as homedir2, platform as platform2 } from "os";
|
|
280
|
-
import { dirname as dirname2, join as join2 } from "path";
|
|
281
|
-
var VAULT_KEYCHAIN_SERVICE = "dev.atelier.vault";
|
|
282
|
-
var LOCAL_VAULT_DIR = join2(homedir2(), ".config", "atelier");
|
|
283
|
-
var LOCAL_VAULT_KEY_PATH = join2(LOCAL_VAULT_DIR, "vault-key.json");
|
|
284
|
-
var KDF_PARAMS = { N: 32768, r: 8, p: 1 };
|
|
285
|
-
var KDF_MAXMEM = 64 * 1024 * 1024;
|
|
286
|
-
var KEY_ALGORITHM = "aes-256-gcm";
|
|
287
|
-
async function getVaultState() {
|
|
288
|
-
const session = requireSession();
|
|
289
|
-
const vault = await getVault();
|
|
290
|
-
return {
|
|
291
|
-
initialized: vault.initialized,
|
|
292
|
-
unlocked: Boolean(readLocalVaultKey(session.user.id)),
|
|
293
|
-
activeProfile: getActiveProfile()
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
async function initializeVault(passphrase, profileName = getActiveProfile()) {
|
|
297
|
-
assertStrongPassphrase(passphrase);
|
|
298
|
-
const session = requireSession();
|
|
299
|
-
const existing = await getVault();
|
|
300
|
-
if (existing.initialized)
|
|
301
|
-
throw new Error("Atelier vault already exists. Run `atl vault unlock` if this machine is locked.");
|
|
302
|
-
const profile = await getProfile(profileName);
|
|
303
|
-
if (!profile)
|
|
304
|
-
throw new Error(`Profile not found: ${profileName}`);
|
|
305
|
-
const vaultKey = randomBytes(32);
|
|
306
|
-
const profileKey = randomBytes(32);
|
|
307
|
-
const salt = randomBytes(16);
|
|
308
|
-
const wrappingKey = deriveWrappingKey(passphrase, salt, KDF_PARAMS);
|
|
309
|
-
const encryptedVaultKey = encryptBytes(vaultKey, wrappingKey);
|
|
310
|
-
const encryptedProfileKey = encryptBytes(profileKey, vaultKey);
|
|
311
|
-
await saveVault({
|
|
312
|
-
kdf: {
|
|
313
|
-
algorithm: "scrypt",
|
|
314
|
-
salt: base64urlEncode(salt),
|
|
315
|
-
params: KDF_PARAMS
|
|
316
|
-
},
|
|
317
|
-
encryptedVaultKey
|
|
318
|
-
});
|
|
319
|
-
await saveProfileKey(profileName, {
|
|
320
|
-
version: 1,
|
|
321
|
-
algorithm: encryptedProfileKey.algorithm,
|
|
322
|
-
nonce: encryptedProfileKey.nonce,
|
|
323
|
-
encryptedKey: encryptedProfileKey.ciphertext
|
|
324
|
-
});
|
|
325
|
-
saveLocalVaultKey(session.user.id, vaultKey);
|
|
326
|
-
return { profileName: profile.name };
|
|
327
|
-
}
|
|
328
|
-
async function unlockVault(passphrase) {
|
|
329
|
-
const session = requireSession();
|
|
330
|
-
const vault = await getVault();
|
|
331
|
-
if (!vault.initialized)
|
|
332
|
-
throw new Error("Atelier vault is not initialized. Run `atl save <id>` or `atl vault init`.");
|
|
333
|
-
const vaultKey = decryptVaultKey(vault, passphrase);
|
|
334
|
-
saveLocalVaultKey(session.user.id, vaultKey);
|
|
335
|
-
return { activeProfile: getActiveProfile() };
|
|
336
|
-
}
|
|
337
|
-
function lockVault() {
|
|
338
|
-
const session = requireSession();
|
|
339
|
-
deleteLocalVaultKey(session.user.id);
|
|
340
|
-
}
|
|
341
|
-
async function ensureLocalVaultKey(passphrase, profileName = getActiveProfile()) {
|
|
342
|
-
const session = requireSession();
|
|
343
|
-
const localVaultKey = readLocalVaultKey(session.user.id);
|
|
344
|
-
if (localVaultKey)
|
|
345
|
-
return localVaultKey;
|
|
346
|
-
if (!passphrase)
|
|
347
|
-
throw new Error("Vault passphrase is required");
|
|
348
|
-
const vault = await getVault();
|
|
349
|
-
if (!vault.initialized) {
|
|
350
|
-
await initializeVault(passphrase, profileName);
|
|
351
|
-
const initializedKey = readLocalVaultKey(session.user.id);
|
|
352
|
-
if (!initializedKey)
|
|
353
|
-
throw new Error("Vault initialized but local key was not stored");
|
|
354
|
-
return initializedKey;
|
|
355
|
-
}
|
|
356
|
-
return decryptVaultKey(vault, passphrase);
|
|
357
|
-
}
|
|
358
|
-
async function ensureProfileKey(passphrase, profileName = getActiveProfile()) {
|
|
359
|
-
const vaultKey = await ensureLocalVaultKey(passphrase, profileName);
|
|
360
|
-
const existing = await getProfileKey(profileName);
|
|
361
|
-
if (existing)
|
|
362
|
-
return decryptEnvelope(existing.encryptedKey, existing.nonce, vaultKey);
|
|
363
|
-
const profileKey = randomBytes(32);
|
|
364
|
-
const encryptedProfileKey = encryptBytes(profileKey, vaultKey);
|
|
365
|
-
await saveProfileKey(profileName, {
|
|
366
|
-
version: 1,
|
|
367
|
-
algorithm: encryptedProfileKey.algorithm,
|
|
368
|
-
nonce: encryptedProfileKey.nonce,
|
|
369
|
-
encryptedKey: encryptedProfileKey.ciphertext
|
|
370
|
-
});
|
|
371
|
-
return profileKey;
|
|
372
|
-
}
|
|
373
|
-
function decryptVaultKey(vault, passphrase) {
|
|
374
|
-
if (!vault.kdf || !vault.encryptedVaultKey)
|
|
375
|
-
throw new Error("Vault payload is incomplete");
|
|
376
|
-
if (vault.kdf.algorithm !== "scrypt")
|
|
377
|
-
throw new Error(`Unsupported vault KDF: ${vault.kdf.algorithm}`);
|
|
378
|
-
const salt = base64urlDecode(vault.kdf.salt);
|
|
379
|
-
const wrappingKey = deriveWrappingKey(passphrase, salt, vault.kdf.params);
|
|
380
|
-
const vaultKey = decryptEnvelope(vault.encryptedVaultKey.ciphertext, vault.encryptedVaultKey.nonce, wrappingKey);
|
|
381
|
-
saveLocalVaultKey(requireSession().user.id, vaultKey);
|
|
382
|
-
return vaultKey;
|
|
383
|
-
}
|
|
384
|
-
function deriveWrappingKey(passphrase, salt, params) {
|
|
385
|
-
return scryptSync(passphrase, salt, 32, {
|
|
386
|
-
N: params.N,
|
|
387
|
-
r: params.r,
|
|
388
|
-
p: params.p,
|
|
389
|
-
maxmem: KDF_MAXMEM
|
|
390
|
-
});
|
|
391
|
-
}
|
|
392
|
-
function encryptBytes(plaintext, key) {
|
|
393
|
-
const nonce = randomBytes(12);
|
|
394
|
-
const cipher = createCipheriv(KEY_ALGORITHM, key, nonce);
|
|
395
|
-
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
396
|
-
const tag = cipher.getAuthTag();
|
|
397
|
-
return {
|
|
398
|
-
algorithm: KEY_ALGORITHM,
|
|
399
|
-
nonce: base64urlEncode(nonce),
|
|
400
|
-
ciphertext: base64urlEncode(Buffer.concat([ciphertext, tag]))
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
function decryptEnvelope(ciphertextWithTag, nonceValue, key) {
|
|
404
|
-
const payload = Buffer.from(base64urlDecode(ciphertextWithTag));
|
|
405
|
-
const nonce = Buffer.from(base64urlDecode(nonceValue));
|
|
406
|
-
const ciphertext = payload.subarray(0, -16);
|
|
407
|
-
const tag = payload.subarray(-16);
|
|
408
|
-
const decipher = createDecipheriv(KEY_ALGORITHM, key, nonce);
|
|
409
|
-
decipher.setAuthTag(tag);
|
|
410
|
-
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
411
|
-
}
|
|
412
|
-
function assertStrongPassphrase(passphrase) {
|
|
413
|
-
if (passphrase.length < 12)
|
|
414
|
-
throw new Error("Vault passphrase must be at least 12 characters.");
|
|
415
|
-
}
|
|
416
|
-
function requireSession() {
|
|
417
|
-
const session = getAuthenticatedSession();
|
|
418
|
-
if (!session)
|
|
419
|
-
throw new Error("Not logged in. Run `atl login`.");
|
|
420
|
-
return session;
|
|
421
|
-
}
|
|
422
|
-
function saveLocalVaultKey(userId, key) {
|
|
423
|
-
const encoded = base64urlEncode(key);
|
|
424
|
-
if (platform2() === "darwin") {
|
|
425
|
-
execFileSync2("security", ["add-generic-password", "-a", userId, "-s", VAULT_KEYCHAIN_SERVICE, "-w", encoded, "-U"], { stdio: "ignore" });
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
|
-
mkdirSync2(dirname2(LOCAL_VAULT_KEY_PATH), { recursive: true });
|
|
429
|
-
writeFileSync2(LOCAL_VAULT_KEY_PATH, JSON.stringify({ userId, key: encoded }, null, 2) + `
|
|
430
|
-
`, "utf-8");
|
|
431
|
-
chmodSync2(LOCAL_VAULT_KEY_PATH, 384);
|
|
432
|
-
}
|
|
433
|
-
function readLocalVaultKey(userId) {
|
|
434
|
-
const encoded = platform2() === "darwin" ? readKeychainVaultKey(userId) : readFileVaultKey(userId);
|
|
435
|
-
return encoded ? Buffer.from(base64urlDecode(encoded)) : undefined;
|
|
436
|
-
}
|
|
437
|
-
function deleteLocalVaultKey(userId) {
|
|
438
|
-
if (platform2() === "darwin") {
|
|
439
|
-
try {
|
|
440
|
-
execFileSync2("security", ["delete-generic-password", "-a", userId, "-s", VAULT_KEYCHAIN_SERVICE], { stdio: "ignore" });
|
|
441
|
-
} catch {}
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
if (existsSync2(LOCAL_VAULT_KEY_PATH))
|
|
445
|
-
rmSync2(LOCAL_VAULT_KEY_PATH);
|
|
446
|
-
}
|
|
447
|
-
function readKeychainVaultKey(userId) {
|
|
448
|
-
try {
|
|
449
|
-
return execFileSync2("security", ["find-generic-password", "-a", userId, "-s", VAULT_KEYCHAIN_SERVICE, "-w"], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
450
|
-
} catch {
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
function readFileVaultKey(userId) {
|
|
455
|
-
if (!existsSync2(LOCAL_VAULT_KEY_PATH))
|
|
456
|
-
return;
|
|
457
|
-
const body = JSON.parse(readFileSync2(LOCAL_VAULT_KEY_PATH, "utf-8"));
|
|
458
|
-
return body.userId === userId ? body.key : undefined;
|
|
459
|
-
}
|
|
460
|
-
function base64urlEncode(bytes) {
|
|
461
|
-
return Buffer.from(bytes).toString("base64url");
|
|
462
|
-
}
|
|
463
|
-
function base64urlDecode(value) {
|
|
464
|
-
return new Uint8Array(Buffer.from(value, "base64url"));
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// ../../packages/save-client/src/validation.ts
|
|
468
|
-
var MAX_SAVE_SIZE_BYTES = 1024 * 1024;
|
|
469
|
-
function validateSaveCandidate(item) {
|
|
470
|
-
const reasons = [];
|
|
471
|
-
if (!item.exists)
|
|
472
|
-
reasons.push("file does not exist");
|
|
473
|
-
if (item.isDirectory || item.kind === "directory" || item.format === "directory")
|
|
474
|
-
reasons.push("directory saving is not supported yet");
|
|
475
|
-
if (item.kind === "generated")
|
|
476
|
-
reasons.push("generated/cache files are not saved");
|
|
477
|
-
if (item.kind === "private" || item.shareability === "private")
|
|
478
|
-
reasons.push("private/auth files are blocked");
|
|
479
|
-
if (item.secretFindings.length > 0)
|
|
480
|
-
reasons.push("detected secret material");
|
|
481
|
-
if (item.size !== undefined && item.size > MAX_SAVE_SIZE_BYTES)
|
|
482
|
-
reasons.push(`file is larger than ${formatBytes(MAX_SAVE_SIZE_BYTES)}`);
|
|
483
|
-
if (isUnsupportedBinary(item))
|
|
484
|
-
reasons.push("binary files are not supported yet");
|
|
485
|
-
return { ok: reasons.length === 0, reasons };
|
|
486
|
-
}
|
|
487
|
-
function assertSaveCandidate(item) {
|
|
488
|
-
const result = validateSaveCandidate(item);
|
|
489
|
-
if (!result.ok)
|
|
490
|
-
throw new Error(`Cannot save ${item.id}: ${result.reasons.join("; ")}`);
|
|
491
|
-
}
|
|
492
|
-
function isUnsupportedBinary(item) {
|
|
493
|
-
if (item.previewSuppressedReason?.toLowerCase().includes("binary"))
|
|
494
|
-
return true;
|
|
495
|
-
return ["binary", "sqlite", "db"].includes(item.format.toLowerCase());
|
|
496
|
-
}
|
|
497
|
-
function formatBytes(bytes) {
|
|
498
|
-
if (bytes < 1024)
|
|
499
|
-
return `${bytes} B`;
|
|
500
|
-
const kib = bytes / 1024;
|
|
501
|
-
if (kib < 1024)
|
|
502
|
-
return `${Math.round(kib)} KiB`;
|
|
503
|
-
return `${Math.round(kib / 1024)} MiB`;
|
|
504
|
-
}
|
|
505
|
-
// ../../packages/save-client/src/index.ts
|
|
506
|
-
var CONTENT_ALGORITHM = "aes-256-gcm";
|
|
507
|
-
async function saveConfigItem(item, options = {}) {
|
|
508
|
-
assertSaveCandidate(item);
|
|
509
|
-
const profileName = getActiveProfile();
|
|
510
|
-
const profileKey = await ensureProfileKey(options.passphrase, profileName);
|
|
511
|
-
const plaintext = await readFile(item.path);
|
|
512
|
-
const encrypted = encryptContent(plaintext, profileKey);
|
|
513
|
-
const response = await createConfigVersion(profileName, item.id, {
|
|
514
|
-
kind: "file",
|
|
515
|
-
pathHint: item.displayPath,
|
|
516
|
-
contentSha256: sha256Hex(plaintext),
|
|
517
|
-
ciphertextSha256: sha256Hex(encrypted.ciphertextBytes),
|
|
518
|
-
sizeBytes: plaintext.byteLength,
|
|
519
|
-
algorithm: encrypted.algorithm,
|
|
520
|
-
profileKeyVersion: 1,
|
|
521
|
-
nonce: encrypted.nonce,
|
|
522
|
-
ciphertext: encrypted.ciphertext
|
|
523
|
-
});
|
|
524
|
-
return {
|
|
525
|
-
profileName,
|
|
526
|
-
stableId: item.id,
|
|
527
|
-
version: response.version,
|
|
528
|
-
reused: response.reused
|
|
529
|
-
};
|
|
530
|
-
}
|
|
531
|
-
function encryptContent(plaintext, key) {
|
|
532
|
-
const nonce = randomBytes2(12);
|
|
533
|
-
const cipher = createCipheriv2(CONTENT_ALGORITHM, key, nonce);
|
|
534
|
-
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
535
|
-
const tag = cipher.getAuthTag();
|
|
536
|
-
const ciphertextBytes = Buffer.concat([ciphertext, tag]);
|
|
537
|
-
return {
|
|
538
|
-
algorithm: CONTENT_ALGORITHM,
|
|
539
|
-
nonce: base64urlEncode2(nonce),
|
|
540
|
-
ciphertext: base64urlEncode2(ciphertextBytes),
|
|
541
|
-
ciphertextBytes
|
|
542
|
-
};
|
|
543
|
-
}
|
|
544
|
-
function sha256Hex(bytes) {
|
|
545
|
-
return createHash("sha256").update(bytes).digest("hex");
|
|
546
|
-
}
|
|
547
|
-
function base64urlEncode2(bytes) {
|
|
548
|
-
return Buffer.from(bytes).toString("base64url");
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
// ../../packages/scanner/src/index.ts
|
|
552
|
-
import { execFileSync as execFileSync3 } from "child_process";
|
|
553
|
-
import { existsSync as existsSync3, lstatSync, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync3 } from "fs";
|
|
554
|
-
import { homedir as homedir3, userInfo } from "os";
|
|
555
|
-
import { dirname as dirname3, join as join3, relative, resolve } from "path";
|
|
556
|
-
|
|
557
|
-
// ../../packages/registry/src/definitions.ts
|
|
558
|
-
var registryDefinitions = [
|
|
559
|
-
{
|
|
560
|
-
domain: "AI Tools",
|
|
561
|
-
app: "Pi / Pi Next",
|
|
562
|
-
items: [
|
|
563
|
-
{
|
|
564
|
-
id: "pi.dir",
|
|
565
|
-
path: "~/.pi",
|
|
566
|
-
kind: "directory",
|
|
567
|
-
format: "directory",
|
|
568
|
-
shareability: "machine-specific"
|
|
569
|
-
},
|
|
570
|
-
{
|
|
571
|
-
id: "pi-next.dir",
|
|
572
|
-
path: "~/.pi-next",
|
|
573
|
-
kind: "directory",
|
|
574
|
-
format: "directory",
|
|
575
|
-
shareability: "shareable"
|
|
576
|
-
},
|
|
577
|
-
{
|
|
578
|
-
id: "pi-next.design",
|
|
579
|
-
path: "~/.pi-next/DESIGN.md",
|
|
580
|
-
kind: "config",
|
|
581
|
-
format: "markdown",
|
|
582
|
-
shareability: "shareable"
|
|
583
|
-
},
|
|
584
|
-
{
|
|
585
|
-
id: "pi-next.cmux-config",
|
|
586
|
-
path: "~/.pi-next/cmux-config",
|
|
587
|
-
kind: "config",
|
|
588
|
-
format: "directory",
|
|
589
|
-
shareability: "shareable"
|
|
590
|
-
},
|
|
591
|
-
{
|
|
592
|
-
id: "pi-next.zed-config",
|
|
593
|
-
path: "~/.pi-next/zed-config",
|
|
594
|
-
kind: "config",
|
|
595
|
-
format: "directory",
|
|
596
|
-
shareability: "shareable"
|
|
597
|
-
},
|
|
598
|
-
{
|
|
599
|
-
id: "pi-next.bin",
|
|
600
|
-
path: "~/.pi-next/bin",
|
|
601
|
-
kind: "config",
|
|
602
|
-
format: "directory",
|
|
603
|
-
shareability: "shareable"
|
|
604
|
-
}
|
|
605
|
-
]
|
|
606
|
-
},
|
|
607
|
-
{
|
|
608
|
-
domain: "Shell",
|
|
609
|
-
app: "Starship",
|
|
610
|
-
items: [
|
|
611
|
-
{
|
|
612
|
-
id: "starship.config",
|
|
613
|
-
path: "~/.config/starship.toml",
|
|
614
|
-
kind: "config",
|
|
615
|
-
format: "toml",
|
|
616
|
-
shareability: "shareable"
|
|
617
|
-
}
|
|
618
|
-
]
|
|
619
|
-
},
|
|
620
|
-
{
|
|
621
|
-
domain: "Editors",
|
|
622
|
-
app: "Zed",
|
|
623
|
-
items: [
|
|
624
|
-
{
|
|
625
|
-
id: "zed.dir",
|
|
626
|
-
path: "~/.config/zed",
|
|
627
|
-
kind: "directory",
|
|
628
|
-
format: "directory",
|
|
629
|
-
shareability: "shareable"
|
|
630
|
-
},
|
|
631
|
-
{
|
|
632
|
-
id: "zed.settings",
|
|
633
|
-
path: "~/.config/zed/settings.json",
|
|
634
|
-
kind: "config",
|
|
635
|
-
format: "jsonc",
|
|
636
|
-
shareability: "shareable",
|
|
637
|
-
mirrors: [
|
|
638
|
-
"~/.pi-next/zed-config/settings.json"
|
|
639
|
-
]
|
|
640
|
-
},
|
|
641
|
-
{
|
|
642
|
-
id: "zed.themes",
|
|
643
|
-
path: "~/.config/zed/themes",
|
|
644
|
-
kind: "config",
|
|
645
|
-
format: "directory",
|
|
646
|
-
shareability: "shareable"
|
|
647
|
-
},
|
|
648
|
-
{
|
|
649
|
-
id: "zed.prompts",
|
|
650
|
-
path: "~/.config/zed/prompts",
|
|
651
|
-
kind: "generated",
|
|
652
|
-
format: "directory",
|
|
653
|
-
shareability: "machine-specific"
|
|
654
|
-
}
|
|
655
|
-
]
|
|
656
|
-
},
|
|
657
|
-
{
|
|
658
|
-
domain: "Git",
|
|
659
|
-
app: "Git",
|
|
660
|
-
items: [
|
|
661
|
-
{
|
|
662
|
-
id: "git.config",
|
|
663
|
-
path: "~/.gitconfig",
|
|
664
|
-
kind: "config",
|
|
665
|
-
format: "gitconfig",
|
|
666
|
-
shareability: "machine-specific"
|
|
667
|
-
},
|
|
668
|
-
{
|
|
669
|
-
id: "git.config-dir",
|
|
670
|
-
path: "~/.config/git",
|
|
671
|
-
kind: "directory",
|
|
672
|
-
format: "directory",
|
|
673
|
-
shareability: "machine-specific"
|
|
674
|
-
},
|
|
675
|
-
{
|
|
676
|
-
id: "git.ignore",
|
|
677
|
-
path: "~/.gitignore",
|
|
678
|
-
kind: "config",
|
|
679
|
-
format: "gitignore",
|
|
680
|
-
shareability: "machine-specific"
|
|
681
|
-
}
|
|
682
|
-
]
|
|
683
|
-
},
|
|
684
|
-
{
|
|
685
|
-
domain: "Private/Auth",
|
|
686
|
-
app: "Credentials",
|
|
687
|
-
privateByDefault: true,
|
|
688
|
-
items: [
|
|
689
|
-
{
|
|
690
|
-
id: "auth.ssh",
|
|
691
|
-
path: "~/.ssh",
|
|
692
|
-
kind: "private",
|
|
693
|
-
format: "directory",
|
|
694
|
-
shareability: "private"
|
|
695
|
-
},
|
|
696
|
-
{
|
|
697
|
-
id: "auth.aws",
|
|
698
|
-
path: "~/.aws",
|
|
699
|
-
kind: "private",
|
|
700
|
-
format: "directory",
|
|
701
|
-
shareability: "private"
|
|
702
|
-
},
|
|
703
|
-
{
|
|
704
|
-
id: "auth.gh",
|
|
705
|
-
path: "~/.config/gh",
|
|
706
|
-
kind: "private",
|
|
707
|
-
format: "directory",
|
|
708
|
-
shareability: "private"
|
|
709
|
-
},
|
|
710
|
-
{
|
|
711
|
-
id: "auth.gcloud-adc",
|
|
712
|
-
path: "~/.config/gcloud/application_default_credentials.json",
|
|
713
|
-
kind: "private",
|
|
714
|
-
format: "json",
|
|
715
|
-
shareability: "private"
|
|
716
|
-
},
|
|
717
|
-
{
|
|
718
|
-
id: "auth.docker",
|
|
719
|
-
path: "~/.docker/config.json",
|
|
720
|
-
kind: "private",
|
|
721
|
-
format: "json",
|
|
722
|
-
shareability: "private"
|
|
723
|
-
},
|
|
724
|
-
{
|
|
725
|
-
id: "auth.pi",
|
|
726
|
-
path: "~/.config/pi/auth.json",
|
|
727
|
-
kind: "private",
|
|
728
|
-
format: "json",
|
|
729
|
-
shareability: "private"
|
|
730
|
-
}
|
|
731
|
-
]
|
|
732
|
-
},
|
|
733
|
-
{
|
|
734
|
-
domain: "Shell",
|
|
735
|
-
app: "Zsh",
|
|
736
|
-
items: [
|
|
737
|
-
{
|
|
738
|
-
id: "zsh.home-rc",
|
|
739
|
-
path: "~/.zshrc",
|
|
740
|
-
kind: "config",
|
|
741
|
-
format: "shell",
|
|
742
|
-
shareability: "shareable"
|
|
743
|
-
},
|
|
744
|
-
{
|
|
745
|
-
id: "zsh.home-env",
|
|
746
|
-
path: "~/.zshenv",
|
|
747
|
-
kind: "config",
|
|
748
|
-
format: "shell",
|
|
749
|
-
shareability: "private"
|
|
750
|
-
},
|
|
751
|
-
{
|
|
752
|
-
id: "zsh.dir",
|
|
753
|
-
path: "~/.config/zsh",
|
|
754
|
-
kind: "directory",
|
|
755
|
-
format: "directory",
|
|
756
|
-
shareability: "machine-specific"
|
|
757
|
-
},
|
|
758
|
-
{
|
|
759
|
-
id: "zsh.env",
|
|
760
|
-
path: "~/.config/zsh/.zshenv",
|
|
761
|
-
kind: "config",
|
|
762
|
-
format: "shell",
|
|
763
|
-
shareability: "private"
|
|
764
|
-
},
|
|
765
|
-
{
|
|
766
|
-
id: "zsh.rc",
|
|
767
|
-
path: "~/.config/zsh/.zshrc",
|
|
768
|
-
kind: "config",
|
|
769
|
-
format: "shell",
|
|
770
|
-
shareability: "shareable"
|
|
771
|
-
},
|
|
772
|
-
{
|
|
773
|
-
id: "zsh.conf",
|
|
774
|
-
path: "~/.config/zsh/conf.d",
|
|
775
|
-
kind: "directory",
|
|
776
|
-
format: "directory",
|
|
777
|
-
shareability: "shareable"
|
|
778
|
-
},
|
|
779
|
-
{
|
|
780
|
-
id: "zsh.zcompdump",
|
|
781
|
-
path: "~/.config/zsh/.zcompdump",
|
|
782
|
-
kind: "generated",
|
|
783
|
-
format: "text",
|
|
784
|
-
shareability: "machine-specific"
|
|
785
|
-
},
|
|
786
|
-
{
|
|
787
|
-
id: "zsh.sessions",
|
|
788
|
-
path: "~/.config/zsh/.zsh_sessions",
|
|
789
|
-
kind: "generated",
|
|
790
|
-
format: "directory",
|
|
791
|
-
shareability: "machine-specific"
|
|
792
|
-
}
|
|
793
|
-
]
|
|
794
|
-
},
|
|
795
|
-
{
|
|
796
|
-
domain: "Terminals",
|
|
797
|
-
app: "cmux",
|
|
798
|
-
items: [
|
|
799
|
-
{
|
|
800
|
-
id: "cmux.dir",
|
|
801
|
-
path: "~/.config/cmux",
|
|
802
|
-
kind: "directory",
|
|
803
|
-
format: "directory",
|
|
804
|
-
shareability: "shareable"
|
|
805
|
-
},
|
|
806
|
-
{
|
|
807
|
-
id: "cmux.config",
|
|
808
|
-
path: "~/.config/cmux/cmux.json",
|
|
809
|
-
kind: "config",
|
|
810
|
-
format: "jsonc",
|
|
811
|
-
shareability: "shareable",
|
|
812
|
-
mirrors: [
|
|
813
|
-
"~/.pi-next/cmux-config/cmux.json"
|
|
814
|
-
]
|
|
815
|
-
},
|
|
816
|
-
{
|
|
817
|
-
id: "cmux.ghostty",
|
|
818
|
-
path: "~/Library/Application Support/com.cmuxterm.app/config.ghostty",
|
|
819
|
-
kind: "config",
|
|
820
|
-
format: "ghostty",
|
|
821
|
-
shareability: "shareable",
|
|
822
|
-
mirrors: [
|
|
823
|
-
"~/.pi-next/cmux-config/config.ghostty"
|
|
824
|
-
]
|
|
825
|
-
},
|
|
826
|
-
{
|
|
827
|
-
id: "cmux.browser-history",
|
|
828
|
-
path: "~/Library/Application Support/com.cmuxterm.app/browser_history.json",
|
|
829
|
-
kind: "generated",
|
|
830
|
-
format: "json",
|
|
831
|
-
shareability: "machine-specific"
|
|
832
|
-
}
|
|
833
|
-
]
|
|
834
|
-
}
|
|
835
|
-
];
|
|
836
|
-
|
|
837
|
-
// ../../packages/secrets/src/index.ts
|
|
838
|
-
var SECRET_PATTERNS = [
|
|
839
|
-
["GitHub token", /gh[pousr]_[A-Za-z0-9_]{20,}/g],
|
|
840
|
-
["Slack token", /xox[baprs]-[A-Za-z0-9-]{20,}/g],
|
|
841
|
-
["AWS access key", /AKIA[0-9A-Z]{16}/g],
|
|
842
|
-
["Private key", /-----BEGIN (?:RSA |OPENSSH |EC |DSA )?PRIVATE KEY-----/g],
|
|
843
|
-
["Generic token assignment", /(?:token|api[_-]?key|secret|webhook|password)\s*[=:]\s*["']?[^"'\s]{12,}/gi]
|
|
844
|
-
];
|
|
845
|
-
function detectSecrets(text) {
|
|
846
|
-
const findings = [];
|
|
847
|
-
const lines = text.split(/\r?\n/);
|
|
848
|
-
for (const [type, pattern] of SECRET_PATTERNS) {
|
|
849
|
-
for (const match of text.matchAll(pattern)) {
|
|
850
|
-
const index = match.index ?? 0;
|
|
851
|
-
const line = text.slice(0, index).split(/\r?\n/).length;
|
|
852
|
-
findings.push({ type, line, preview: redact(match[0]) });
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
lines.forEach((lineText, index) => {
|
|
856
|
-
for (const candidate of lineText.matchAll(/[A-Za-z0-9_+\/=.-]{32,}/g)) {
|
|
857
|
-
const value = candidate[0];
|
|
858
|
-
if (!isCoveredByExplicitPattern(value, lineText) && isHighEntropySecretCandidate(value, lineText)) {
|
|
859
|
-
findings.push({ type: "High-entropy string", line: index + 1, preview: redact(value) });
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
});
|
|
863
|
-
return dedupe(findings);
|
|
864
|
-
}
|
|
865
|
-
function redact(text) {
|
|
866
|
-
if (text.length <= 8)
|
|
867
|
-
return "[redacted]";
|
|
868
|
-
return `${text.slice(0, 4)}\u2026${text.slice(-4)}`;
|
|
869
|
-
}
|
|
870
|
-
function redactText(text) {
|
|
871
|
-
let redacted = text;
|
|
872
|
-
for (const [, pattern] of SECRET_PATTERNS) {
|
|
873
|
-
redacted = redacted.replace(pattern, (match) => redact(match));
|
|
874
|
-
}
|
|
875
|
-
redacted = redacted.replace(/[A-Za-z0-9_+\/=.-]{32,}/g, (match) => {
|
|
876
|
-
if (isHighEntropySecretCandidate(match, text))
|
|
877
|
-
return redact(match);
|
|
878
|
-
return match;
|
|
879
|
-
});
|
|
880
|
-
return redacted;
|
|
881
|
-
}
|
|
882
|
-
function isCoveredByExplicitPattern(value, context) {
|
|
883
|
-
return SECRET_PATTERNS.some(([, pattern]) => {
|
|
884
|
-
pattern.lastIndex = 0;
|
|
885
|
-
return Array.from(context.matchAll(pattern)).some((match) => match[0].includes(value) || value.includes(match[0]));
|
|
886
|
-
});
|
|
887
|
-
}
|
|
888
|
-
function isHighEntropySecretCandidate(value, context) {
|
|
889
|
-
if (value.startsWith("amazon."))
|
|
890
|
-
return false;
|
|
891
|
-
if (value.includes("anthropic.claude"))
|
|
892
|
-
return false;
|
|
893
|
-
if (/^[A-Za-z0-9.-]+@[A-Za-z0-9.-]+$/.test(value))
|
|
894
|
-
return false;
|
|
895
|
-
if (/model/i.test(context) && /^[A-Za-z0-9_.-]+$/.test(value))
|
|
896
|
-
return false;
|
|
897
|
-
return entropy(value) > 4.2 && /[A-Za-z]/.test(value) && /[0-9]/.test(value);
|
|
898
|
-
}
|
|
899
|
-
function entropy(value) {
|
|
900
|
-
const counts = new Map;
|
|
901
|
-
for (const char of value)
|
|
902
|
-
counts.set(char, (counts.get(char) ?? 0) + 1);
|
|
903
|
-
let result = 0;
|
|
904
|
-
for (const count of counts.values()) {
|
|
905
|
-
const p = count / value.length;
|
|
906
|
-
result -= p * Math.log2(p);
|
|
907
|
-
}
|
|
908
|
-
return result;
|
|
909
|
-
}
|
|
910
|
-
function dedupe(findings) {
|
|
911
|
-
const seen = new Set;
|
|
912
|
-
return findings.filter((finding) => {
|
|
913
|
-
const key = `${finding.type}:${finding.line}:${finding.preview}`;
|
|
914
|
-
if (seen.has(key))
|
|
915
|
-
return false;
|
|
916
|
-
seen.add(key);
|
|
917
|
-
return true;
|
|
918
|
-
});
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
// ../../packages/scanner/src/jsonc.ts
|
|
922
|
-
function parseJsonc(input) {
|
|
923
|
-
const withoutBlockComments = input.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
924
|
-
const withoutLineComments = withoutBlockComments.replace(/(^|[^:])\/\/.*$/gm, "$1");
|
|
925
|
-
const withoutTrailingCommas = withoutLineComments.replace(/,\s*([}\]])/g, "$1");
|
|
926
|
-
return JSON.parse(withoutTrailingCommas);
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
// ../../packages/scanner/src/index.ts
|
|
930
|
-
var HOME = homedir3();
|
|
931
|
-
var TEXT_EXTENSIONS = new Set([".zsh", ".zshrc", ".zshenv", ".sh", ".json", ".jsonc", ".toml", ".md", ".yml", ".yaml", ".gitconfig", ".gitignore", ".ghostty", ""]);
|
|
932
|
-
var GENERATED_NAMES = [/cache/i, /history/i, /session/i, /state/i, /\.mdb$/i, /\.sqlite/i, /\.db$/i, /zcompdump/i, /logs?/i];
|
|
933
|
-
var PRIVATE_NAMES = [/credential/i, /secret/i, /token/i, /auth\.json$/i, /hosts\.yml$/i, /config\.json$/i];
|
|
934
|
-
var yadmCache;
|
|
935
|
-
async function scan(options) {
|
|
936
|
-
yadmCache = options.includeLegacyManagers ? loadYadmCache() : undefined;
|
|
937
|
-
const definitions = loadRegistry(options.repoRoot);
|
|
938
|
-
const registryItems = definitions.flatMap((definition) => definition.items.map((item) => ({ definition, item })));
|
|
939
|
-
const seen = new Set;
|
|
940
|
-
const items = [];
|
|
941
|
-
for (const { definition, item } of registryItems) {
|
|
942
|
-
const path = expandHome(item.path);
|
|
943
|
-
seen.add(path);
|
|
944
|
-
items.push(inspectRegistryItem(definition, item, path));
|
|
945
|
-
}
|
|
946
|
-
for (const candidate of discoverConfigCandidates()) {
|
|
947
|
-
if (seen.has(candidate.path))
|
|
948
|
-
continue;
|
|
949
|
-
seen.add(candidate.path);
|
|
950
|
-
items.push(inspectHeuristicItem(candidate));
|
|
951
|
-
}
|
|
952
|
-
for (const manual of options.manualPaths ?? []) {
|
|
953
|
-
const path = expandHome(manual);
|
|
954
|
-
if (seen.has(path))
|
|
955
|
-
continue;
|
|
956
|
-
seen.add(path);
|
|
957
|
-
items.push(inspectHeuristicItem({ path, reason: "manual path" }));
|
|
958
|
-
}
|
|
959
|
-
items.sort((a, b) => `${a.domain}:${a.app}:${a.path}`.localeCompare(`${b.domain}:${b.app}:${b.path}`));
|
|
960
|
-
return {
|
|
961
|
-
version: 1,
|
|
962
|
-
generatedAt: new Date().toISOString(),
|
|
963
|
-
repoRoot: options.repoRoot,
|
|
964
|
-
home: HOME,
|
|
965
|
-
items,
|
|
966
|
-
summary: {
|
|
967
|
-
total: items.length,
|
|
968
|
-
existing: items.filter((item) => item.exists).length,
|
|
969
|
-
secrets: items.filter((item) => item.secretFindings.length > 0).length,
|
|
970
|
-
drift: items.filter((item) => item.mirrors.some((mirror) => mirror.exists && mirror.identical === false)).length,
|
|
971
|
-
generated: items.filter((item) => item.kind === "generated").length,
|
|
972
|
-
private: items.filter((item) => item.shareability === "private").length
|
|
973
|
-
}
|
|
974
|
-
};
|
|
975
|
-
}
|
|
976
|
-
function writeInventory(inventory, options = {}) {
|
|
977
|
-
const path = options.path ?? join3(inventory.repoRoot, ".atelier", "state", "inventory.json");
|
|
978
|
-
mkdirSync3(dirname3(path), { recursive: true });
|
|
979
|
-
writeFileSync3(path, JSON.stringify(inventory, null, 2) + `
|
|
980
|
-
`, "utf-8");
|
|
981
|
-
return path;
|
|
982
|
-
}
|
|
983
|
-
function loadRegistry(repoRoot) {
|
|
984
|
-
const dir = join3(repoRoot, "packages", "registry", "definitions");
|
|
985
|
-
if (!existsSync3(dir))
|
|
986
|
-
return registryDefinitions;
|
|
987
|
-
return readdirSync(dir).filter((file) => file.endsWith(".jsonc")).map((file) => parseJsonc(readFileSync3(join3(dir, file), "utf-8")));
|
|
988
|
-
}
|
|
989
|
-
function inspectRegistryItem(definition, item, path) {
|
|
990
|
-
return inspectPath({
|
|
991
|
-
path,
|
|
992
|
-
domain: definition.domain,
|
|
993
|
-
app: definition.app,
|
|
994
|
-
kind: item.kind,
|
|
995
|
-
format: item.format,
|
|
996
|
-
shareability: item.shareability,
|
|
997
|
-
reason: "registry match",
|
|
998
|
-
mirrors: item.mirrors ?? [],
|
|
999
|
-
privateByDefault: definition.privateByDefault,
|
|
1000
|
-
id: item.id
|
|
1001
|
-
});
|
|
1002
|
-
}
|
|
1003
|
-
function inspectHeuristicItem(candidate) {
|
|
1004
|
-
const app = appNameFromPath(candidate.path);
|
|
1005
|
-
const kind = heuristicKind(candidate.path);
|
|
1006
|
-
const shareability = heuristicShareability(candidate.path, kind);
|
|
1007
|
-
return inspectPath({
|
|
1008
|
-
path: candidate.path,
|
|
1009
|
-
domain: heuristicDomain(candidate.path),
|
|
1010
|
-
app,
|
|
1011
|
-
kind,
|
|
1012
|
-
format: detectFormat(candidate.path),
|
|
1013
|
-
shareability,
|
|
1014
|
-
reason: candidate.reason,
|
|
1015
|
-
mirrors: [],
|
|
1016
|
-
privateByDefault: shareability === "private"
|
|
1017
|
-
});
|
|
1018
|
-
}
|
|
1019
|
-
function inspectPath(input) {
|
|
1020
|
-
const exists = existsSync3(input.path);
|
|
1021
|
-
const lst = exists ? lstatSync(input.path) : undefined;
|
|
1022
|
-
const stat = exists ? statSync(input.path) : undefined;
|
|
1023
|
-
const isDirectory = !!stat?.isDirectory();
|
|
1024
|
-
const isSymlink = !!lst?.isSymbolicLink();
|
|
1025
|
-
const secretFindings = exists && !isDirectory && isSafeTextPath(input.path) ? detectSecrets(readFileSync3(input.path, "utf-8")) : [];
|
|
1026
|
-
const mirrors = input.mirrors.map((mirror) => inspectMirror(input.path, expandHome(mirror)));
|
|
1027
|
-
const recommendation = recommend(input.kind, input.shareability, secretFindings.length, mirrors);
|
|
1028
|
-
const previewInfo = preview(input.path, exists, isDirectory, input.shareability, input.privateByDefault, input.kind);
|
|
1029
|
-
return {
|
|
1030
|
-
id: input.id ?? stableId(input.path),
|
|
1031
|
-
domain: input.domain,
|
|
1032
|
-
app: input.app,
|
|
1033
|
-
path: input.path,
|
|
1034
|
-
displayPath: displayPath(input.path),
|
|
1035
|
-
kind: input.kind,
|
|
1036
|
-
format: input.format,
|
|
1037
|
-
shareability: input.shareability,
|
|
1038
|
-
exists,
|
|
1039
|
-
isDirectory,
|
|
1040
|
-
isSymlink,
|
|
1041
|
-
mode: stat ? `0${(stat.mode & 511).toString(8)}` : undefined,
|
|
1042
|
-
owner: exists ? userInfo().username : undefined,
|
|
1043
|
-
size: stat?.size,
|
|
1044
|
-
git: exists ? gitInfo(input.path) : undefined,
|
|
1045
|
-
legacyManagers: exists ? legacyManagerInfo(input.path) : {},
|
|
1046
|
-
mirrors,
|
|
1047
|
-
secretFindings,
|
|
1048
|
-
...previewInfo,
|
|
1049
|
-
recommendation,
|
|
1050
|
-
reason: input.reason
|
|
1051
|
-
};
|
|
1052
|
-
}
|
|
1053
|
-
function discoverConfigCandidates() {
|
|
1054
|
-
const candidates = [];
|
|
1055
|
-
const configRoot = join3(HOME, ".config");
|
|
1056
|
-
if (!existsSync3(configRoot))
|
|
1057
|
-
return candidates;
|
|
1058
|
-
for (const entry of readdirSync(configRoot, { withFileTypes: true })) {
|
|
1059
|
-
if (entry.name.startsWith("."))
|
|
1060
|
-
continue;
|
|
1061
|
-
const appDir = join3(configRoot, entry.name);
|
|
1062
|
-
candidates.push({ path: appDir, reason: "~/.config app directory" });
|
|
1063
|
-
if (!entry.isDirectory())
|
|
1064
|
-
continue;
|
|
1065
|
-
for (const child of safeReadDir(appDir).slice(0, 80)) {
|
|
1066
|
-
const childPath = join3(appDir, child.name);
|
|
1067
|
-
if (isGeneratedPath(childPath)) {
|
|
1068
|
-
candidates.push({ path: childPath, reason: "generated/app-state candidate" });
|
|
1069
|
-
continue;
|
|
1070
|
-
}
|
|
1071
|
-
if (child.isFile() && looksLikeConfigFile(child.name)) {
|
|
1072
|
-
candidates.push({ path: childPath, reason: "shallow ~/.config config candidate" });
|
|
1073
|
-
}
|
|
1074
|
-
if (child.isDirectory() && ["conf.d", "themes", "snippets", "plugins"].includes(child.name)) {
|
|
1075
|
-
candidates.push({ path: childPath, reason: "shallow ~/.config config directory" });
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
return candidates;
|
|
1080
|
-
}
|
|
1081
|
-
function safeReadDir(path) {
|
|
1082
|
-
try {
|
|
1083
|
-
return readdirSync(path, { withFileTypes: true });
|
|
1084
|
-
} catch {
|
|
1085
|
-
return [];
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
function inspectMirror(livePath, mirrorPath) {
|
|
1089
|
-
const exists = existsSync3(mirrorPath);
|
|
1090
|
-
if (!exists || !existsSync3(livePath) || statSync(livePath).isDirectory() || statSync(mirrorPath).isDirectory()) {
|
|
1091
|
-
return { path: mirrorPath, displayPath: displayPath(mirrorPath), exists };
|
|
1092
|
-
}
|
|
1093
|
-
const live = readFileSync3(livePath, "utf-8");
|
|
1094
|
-
const mirror = readFileSync3(mirrorPath, "utf-8");
|
|
1095
|
-
return {
|
|
1096
|
-
path: mirrorPath,
|
|
1097
|
-
displayPath: displayPath(mirrorPath),
|
|
1098
|
-
exists,
|
|
1099
|
-
identical: live === mirror,
|
|
1100
|
-
diff: live === mirror ? undefined : simpleDiff(mirror, live)
|
|
1101
|
-
};
|
|
1102
|
-
}
|
|
1103
|
-
function simpleDiff(before, after) {
|
|
1104
|
-
const beforeLines = before.split(/\r?\n/);
|
|
1105
|
-
const afterLines = after.split(/\r?\n/);
|
|
1106
|
-
const max = Math.max(beforeLines.length, afterLines.length);
|
|
1107
|
-
const output = [];
|
|
1108
|
-
for (let index = 0;index < max; index++) {
|
|
1109
|
-
if (beforeLines[index] === afterLines[index])
|
|
1110
|
-
continue;
|
|
1111
|
-
if (beforeLines[index] !== undefined)
|
|
1112
|
-
output.push(`-${beforeLines[index]}`);
|
|
1113
|
-
if (afterLines[index] !== undefined)
|
|
1114
|
-
output.push(`+${afterLines[index]}`);
|
|
1115
|
-
}
|
|
1116
|
-
return output.slice(0, 200).join(`
|
|
1117
|
-
`);
|
|
1118
|
-
}
|
|
1119
|
-
function preview(path, exists, isDirectory, shareability, privateByDefault, kind) {
|
|
1120
|
-
if (!exists)
|
|
1121
|
-
return { previewSuppressedReason: "missing" };
|
|
1122
|
-
if (isDirectory)
|
|
1123
|
-
return { previewSuppressedReason: "directory" };
|
|
1124
|
-
if (shareability === "private" || privateByDefault || kind === "private")
|
|
1125
|
-
return { previewSuppressedReason: "private/auth metadata-only" };
|
|
1126
|
-
if (!isSafeTextPath(path))
|
|
1127
|
-
return { previewSuppressedReason: "binary or unsupported file type" };
|
|
1128
|
-
const text = readFileSync3(path, "utf-8");
|
|
1129
|
-
return { preview: redactText(text).slice(0, 20000) };
|
|
1130
|
-
}
|
|
1131
|
-
function recommend(kind, shareability, secretCount, mirrors) {
|
|
1132
|
-
if (secretCount > 0)
|
|
1133
|
-
return "rotate-secret";
|
|
1134
|
-
if (kind === "generated")
|
|
1135
|
-
return "ignore-generated";
|
|
1136
|
-
if (shareability === "private")
|
|
1137
|
-
return "mark-private";
|
|
1138
|
-
if (mirrors.some((mirror) => mirror.exists && mirror.identical === false))
|
|
1139
|
-
return "resolve-drift";
|
|
1140
|
-
if (shareability === "machine-specific")
|
|
1141
|
-
return "review-machine-specific";
|
|
1142
|
-
if (kind === "config" || kind === "directory")
|
|
1143
|
-
return "adopt-candidate";
|
|
1144
|
-
return "none";
|
|
1145
|
-
}
|
|
1146
|
-
function gitInfo(path) {
|
|
1147
|
-
const cwd = statSync(path).isDirectory() ? path : dirname3(path);
|
|
1148
|
-
const root = git(["rev-parse", "--show-toplevel"], cwd);
|
|
1149
|
-
if (!root)
|
|
1150
|
-
return;
|
|
1151
|
-
const rel = relative(root, path);
|
|
1152
|
-
const ignored = !!git(["check-ignore", "-q", rel], root, true);
|
|
1153
|
-
const tracked = !!git(["ls-files", "--error-unmatch", rel], root, true);
|
|
1154
|
-
const modified = tracked && !!git(["status", "--porcelain", "--", rel], root);
|
|
1155
|
-
return { root, tracked, modified, ignored };
|
|
1156
|
-
}
|
|
1157
|
-
function legacyManagerInfo(path) {
|
|
1158
|
-
if (!yadmCache)
|
|
1159
|
-
return {};
|
|
1160
|
-
return { yadm: yadmInfo(path) };
|
|
1161
|
-
}
|
|
1162
|
-
function yadmInfo(path) {
|
|
1163
|
-
const rel = displayPath(path).replace(/^~\//, "");
|
|
1164
|
-
if (yadmCache?.modified.has(rel))
|
|
1165
|
-
return "modified";
|
|
1166
|
-
if (yadmCache?.tracked.has(rel))
|
|
1167
|
-
return "tracked";
|
|
1168
|
-
return "unknown";
|
|
1169
|
-
}
|
|
1170
|
-
function loadYadmCache() {
|
|
1171
|
-
const tracked = new Set((gitLike("yadm", ["ls-files"]) ?? "").split(`
|
|
1172
|
-
`).filter(Boolean));
|
|
1173
|
-
const modified = new Set((gitLike("yadm", ["status", "--porcelain"]) ?? "").split(`
|
|
1174
|
-
`).map((line) => line.slice(3).trim()).filter(Boolean));
|
|
1175
|
-
return { tracked, modified };
|
|
1176
|
-
}
|
|
1177
|
-
function git(args, cwd, allowEmpty = false) {
|
|
1178
|
-
try {
|
|
1179
|
-
const out = execFileSync3("git", args, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
1180
|
-
return out || (allowEmpty ? "ok" : undefined);
|
|
1181
|
-
} catch {
|
|
1182
|
-
return;
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
function gitLike(command, args) {
|
|
1186
|
-
try {
|
|
1187
|
-
return execFileSync3(command, args, { cwd: HOME, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
|
|
1188
|
-
} catch {
|
|
1189
|
-
return;
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
function expandHome(path) {
|
|
1193
|
-
return resolve(path.replace(/^~(?=\/|$)/, HOME));
|
|
1194
|
-
}
|
|
1195
|
-
function displayPath(path) {
|
|
1196
|
-
return path.startsWith(HOME) ? `~${path.slice(HOME.length)}` : path;
|
|
1197
|
-
}
|
|
1198
|
-
function stableId(path) {
|
|
1199
|
-
return displayPath(path).replace(/[^A-Za-z0-9_.-]+/g, ":");
|
|
1200
|
-
}
|
|
1201
|
-
function appNameFromPath(path) {
|
|
1202
|
-
const rel = relative(join3(HOME, ".config"), path);
|
|
1203
|
-
if (!rel.startsWith(".."))
|
|
1204
|
-
return rel.split(/[\\/]/)[0] || "Unknown";
|
|
1205
|
-
return "Unknown";
|
|
1206
|
-
}
|
|
1207
|
-
function heuristicDomain(path) {
|
|
1208
|
-
if (path.includes("/.config/"))
|
|
1209
|
-
return "~/.config";
|
|
1210
|
-
if (path.includes("/.ssh") || path.includes("/.aws"))
|
|
1211
|
-
return "Private/Auth";
|
|
1212
|
-
return "Unknown";
|
|
1213
|
-
}
|
|
1214
|
-
function heuristicKind(path) {
|
|
1215
|
-
if (isGeneratedPath(path))
|
|
1216
|
-
return "generated";
|
|
1217
|
-
if (PRIVATE_NAMES.some((pattern) => pattern.test(path)))
|
|
1218
|
-
return "private";
|
|
1219
|
-
if (existsSync3(path) && statSync(path).isDirectory())
|
|
1220
|
-
return "directory";
|
|
1221
|
-
if (looksLikeConfigFile(path))
|
|
1222
|
-
return "config";
|
|
1223
|
-
return "unknown";
|
|
1224
|
-
}
|
|
1225
|
-
function heuristicShareability(path, kind) {
|
|
1226
|
-
if (kind === "private")
|
|
1227
|
-
return "private";
|
|
1228
|
-
if (kind === "generated")
|
|
1229
|
-
return "machine-specific";
|
|
1230
|
-
if (path.includes("/credentials") || path.includes("/.ssh") || path.includes("/.aws"))
|
|
1231
|
-
return "private";
|
|
1232
|
-
return "machine-specific";
|
|
1233
|
-
}
|
|
1234
|
-
function detectFormat(path) {
|
|
1235
|
-
if (existsSync3(path) && statSync(path).isDirectory())
|
|
1236
|
-
return "directory";
|
|
1237
|
-
if (path.endsWith(".jsonc"))
|
|
1238
|
-
return "jsonc";
|
|
1239
|
-
if (path.endsWith(".json"))
|
|
1240
|
-
return "json";
|
|
1241
|
-
if (path.endsWith(".toml"))
|
|
1242
|
-
return "toml";
|
|
1243
|
-
if (path.endsWith(".zsh"))
|
|
1244
|
-
return "shell";
|
|
1245
|
-
if (path.endsWith(".md"))
|
|
1246
|
-
return "markdown";
|
|
1247
|
-
return "text";
|
|
1248
|
-
}
|
|
1249
|
-
function isGeneratedPath(path) {
|
|
1250
|
-
return GENERATED_NAMES.some((pattern) => pattern.test(path));
|
|
1251
|
-
}
|
|
1252
|
-
function looksLikeConfigFile(name) {
|
|
1253
|
-
return /(^config\.|settings\.|rc$|\.rc$|\.zshrc$|\.zshenv$|\.jsonc?$|\.toml$|\.ya?ml$|\.zsh$|\.conf$|\.ini$)/i.test(name);
|
|
1254
|
-
}
|
|
1255
|
-
function isSafeTextPath(path) {
|
|
1256
|
-
const name = path.split("/").pop() ?? "";
|
|
1257
|
-
if (isGeneratedPath(path))
|
|
1258
|
-
return false;
|
|
1259
|
-
if (name.includes("lock") || name.endsWith(".mdb") || name.endsWith(".sqlite") || name.endsWith(".db"))
|
|
1260
|
-
return false;
|
|
1261
|
-
const ext = name.includes(".") ? name.slice(name.lastIndexOf(".")) : "";
|
|
1262
|
-
return TEXT_EXTENSIONS.has(ext) || looksLikeConfigFile(name);
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
// src/ui.tsx
|
|
1266
|
-
import { Box, Text, render } from "ink";
|
|
1267
|
-
import { jsxDEV, Fragment } from "react/jsx-dev-runtime";
|
|
1268
|
-
function renderInk(element) {
|
|
1269
|
-
const app = render(/* @__PURE__ */ jsxDEV(Fragment, {
|
|
1270
|
-
children: element
|
|
1271
|
-
}, undefined, false, undefined, this));
|
|
1272
|
-
return app.waitUntilExit();
|
|
1273
|
-
}
|
|
1274
|
-
function ScanSummary({ inventory, path }) {
|
|
1275
|
-
return /* @__PURE__ */ jsxDEV(Box, {
|
|
1276
|
-
flexDirection: "column",
|
|
1277
|
-
gap: 1,
|
|
1278
|
-
children: [
|
|
1279
|
-
/* @__PURE__ */ jsxDEV(Header, {
|
|
1280
|
-
title: "Scan complete",
|
|
1281
|
-
subtitle: path
|
|
1282
|
-
}, undefined, false, undefined, this),
|
|
1283
|
-
/* @__PURE__ */ jsxDEV(Box, {
|
|
1284
|
-
gap: 2,
|
|
1285
|
-
children: [
|
|
1286
|
-
/* @__PURE__ */ jsxDEV(Stat, {
|
|
1287
|
-
label: "total",
|
|
1288
|
-
value: inventory.summary.total,
|
|
1289
|
-
color: "cyan"
|
|
1290
|
-
}, undefined, false, undefined, this),
|
|
1291
|
-
/* @__PURE__ */ jsxDEV(Stat, {
|
|
1292
|
-
label: "existing",
|
|
1293
|
-
value: inventory.summary.existing,
|
|
1294
|
-
color: "green"
|
|
1295
|
-
}, undefined, false, undefined, this),
|
|
1296
|
-
/* @__PURE__ */ jsxDEV(Stat, {
|
|
1297
|
-
label: "private",
|
|
1298
|
-
value: inventory.summary.private,
|
|
1299
|
-
color: "red"
|
|
1300
|
-
}, undefined, false, undefined, this),
|
|
1301
|
-
/* @__PURE__ */ jsxDEV(Stat, {
|
|
1302
|
-
label: "generated",
|
|
1303
|
-
value: inventory.summary.generated,
|
|
1304
|
-
color: "magenta"
|
|
1305
|
-
}, undefined, false, undefined, this),
|
|
1306
|
-
/* @__PURE__ */ jsxDEV(Stat, {
|
|
1307
|
-
label: "secrets",
|
|
1308
|
-
value: inventory.summary.secrets,
|
|
1309
|
-
color: "red"
|
|
1310
|
-
}, undefined, false, undefined, this),
|
|
1311
|
-
/* @__PURE__ */ jsxDEV(Stat, {
|
|
1312
|
-
label: "drift",
|
|
1313
|
-
value: inventory.summary.drift,
|
|
1314
|
-
color: "blue"
|
|
1315
|
-
}, undefined, false, undefined, this)
|
|
1316
|
-
]
|
|
1317
|
-
}, undefined, true, undefined, this)
|
|
1318
|
-
]
|
|
1319
|
-
}, undefined, true, undefined, this);
|
|
1320
|
-
}
|
|
1321
|
-
function ItemList({ items, title }) {
|
|
1322
|
-
return /* @__PURE__ */ jsxDEV(Box, {
|
|
1323
|
-
flexDirection: "column",
|
|
1324
|
-
children: [
|
|
1325
|
-
/* @__PURE__ */ jsxDEV(Header, {
|
|
1326
|
-
title,
|
|
1327
|
-
subtitle: `${items.length} item${items.length === 1 ? "" : "s"}`
|
|
1328
|
-
}, undefined, false, undefined, this),
|
|
1329
|
-
/* @__PURE__ */ jsxDEV(Box, {
|
|
1330
|
-
flexDirection: "column",
|
|
1331
|
-
marginTop: 1,
|
|
1332
|
-
children: items.map((item) => /* @__PURE__ */ jsxDEV(Box, {
|
|
1333
|
-
gap: 1,
|
|
1334
|
-
children: [
|
|
1335
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1336
|
-
color: item.exists ? "white" : "gray",
|
|
1337
|
-
children: item.exists ? "\u25CF" : "\u25CB"
|
|
1338
|
-
}, undefined, false, undefined, this),
|
|
1339
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1340
|
-
color: kindColor(item),
|
|
1341
|
-
children: item.domain
|
|
1342
|
-
}, undefined, false, undefined, this),
|
|
1343
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1344
|
-
color: "gray",
|
|
1345
|
-
children: "/"
|
|
1346
|
-
}, undefined, false, undefined, this),
|
|
1347
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1348
|
-
color: "cyan",
|
|
1349
|
-
children: item.app
|
|
1350
|
-
}, undefined, false, undefined, this),
|
|
1351
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1352
|
-
children: item.displayPath
|
|
1353
|
-
}, undefined, false, undefined, this),
|
|
1354
|
-
/* @__PURE__ */ jsxDEV(Badge, {
|
|
1355
|
-
label: item.shareability,
|
|
1356
|
-
color: shareabilityColor(item.shareability)
|
|
1357
|
-
}, undefined, false, undefined, this),
|
|
1358
|
-
item.secretFindings.length > 0 ? /* @__PURE__ */ jsxDEV(Badge, {
|
|
1359
|
-
label: "secret",
|
|
1360
|
-
color: "red"
|
|
1361
|
-
}, undefined, false, undefined, this) : null,
|
|
1362
|
-
item.mirrors.some((mirror) => mirror.exists && mirror.identical === false) ? /* @__PURE__ */ jsxDEV(Badge, {
|
|
1363
|
-
label: "drift",
|
|
1364
|
-
color: "blue"
|
|
1365
|
-
}, undefined, false, undefined, this) : null
|
|
1366
|
-
]
|
|
1367
|
-
}, item.id, true, undefined, this))
|
|
1368
|
-
}, undefined, false, undefined, this)
|
|
1369
|
-
]
|
|
1370
|
-
}, undefined, true, undefined, this);
|
|
1371
|
-
}
|
|
1372
|
-
function ItemDetail({ item }) {
|
|
1373
|
-
return /* @__PURE__ */ jsxDEV(Box, {
|
|
1374
|
-
flexDirection: "column",
|
|
1375
|
-
gap: 1,
|
|
1376
|
-
children: [
|
|
1377
|
-
/* @__PURE__ */ jsxDEV(Header, {
|
|
1378
|
-
title: item.app,
|
|
1379
|
-
subtitle: item.displayPath
|
|
1380
|
-
}, undefined, false, undefined, this),
|
|
1381
|
-
/* @__PURE__ */ jsxDEV(Box, {
|
|
1382
|
-
gap: 1,
|
|
1383
|
-
children: [
|
|
1384
|
-
/* @__PURE__ */ jsxDEV(Badge, {
|
|
1385
|
-
label: item.domain,
|
|
1386
|
-
color: "cyan"
|
|
1387
|
-
}, undefined, false, undefined, this),
|
|
1388
|
-
/* @__PURE__ */ jsxDEV(Badge, {
|
|
1389
|
-
label: item.kind,
|
|
1390
|
-
color: kindColor(item)
|
|
1391
|
-
}, undefined, false, undefined, this),
|
|
1392
|
-
/* @__PURE__ */ jsxDEV(Badge, {
|
|
1393
|
-
label: item.shareability,
|
|
1394
|
-
color: shareabilityColor(item.shareability)
|
|
1395
|
-
}, undefined, false, undefined, this),
|
|
1396
|
-
/* @__PURE__ */ jsxDEV(Badge, {
|
|
1397
|
-
label: item.recommendation,
|
|
1398
|
-
color: "yellow"
|
|
1399
|
-
}, undefined, false, undefined, this)
|
|
1400
|
-
]
|
|
1401
|
-
}, undefined, true, undefined, this),
|
|
1402
|
-
/* @__PURE__ */ jsxDEV(KeyValues, {
|
|
1403
|
-
rows: [
|
|
1404
|
-
["reason", item.reason],
|
|
1405
|
-
["exists", String(item.exists)],
|
|
1406
|
-
["mode", item.mode ?? "\u2014"],
|
|
1407
|
-
["symlink", String(item.isSymlink)],
|
|
1408
|
-
["git", gitText(item)],
|
|
1409
|
-
["legacy", legacyText(item)]
|
|
1410
|
-
]
|
|
1411
|
-
}, undefined, false, undefined, this),
|
|
1412
|
-
item.secretFindings.length > 0 ? /* @__PURE__ */ jsxDEV(Box, {
|
|
1413
|
-
flexDirection: "column",
|
|
1414
|
-
children: [
|
|
1415
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1416
|
-
color: "red",
|
|
1417
|
-
bold: true,
|
|
1418
|
-
children: "Secret warnings"
|
|
1419
|
-
}, undefined, false, undefined, this),
|
|
1420
|
-
item.secretFindings.map((finding, index) => /* @__PURE__ */ jsxDEV(Text, {
|
|
1421
|
-
color: "red",
|
|
1422
|
-
children: [
|
|
1423
|
-
" ",
|
|
1424
|
-
finding.type,
|
|
1425
|
-
finding.line ? ` line ${finding.line}` : "",
|
|
1426
|
-
": ",
|
|
1427
|
-
finding.preview
|
|
1428
|
-
]
|
|
1429
|
-
}, `${finding.type}-${index}`, true, undefined, this))
|
|
1430
|
-
]
|
|
1431
|
-
}, undefined, true, undefined, this) : null,
|
|
1432
|
-
item.mirrors.length > 0 ? /* @__PURE__ */ jsxDEV(Box, {
|
|
1433
|
-
flexDirection: "column",
|
|
1434
|
-
children: [
|
|
1435
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1436
|
-
color: "blue",
|
|
1437
|
-
bold: true,
|
|
1438
|
-
children: "Mirrors"
|
|
1439
|
-
}, undefined, false, undefined, this),
|
|
1440
|
-
item.mirrors.map((mirror) => /* @__PURE__ */ jsxDEV(Text, {
|
|
1441
|
-
children: [
|
|
1442
|
-
" ",
|
|
1443
|
-
mirror.displayPath,
|
|
1444
|
-
" \u2014 ",
|
|
1445
|
-
mirror.exists ? mirror.identical === false ? "differs" : "identical" : "missing"
|
|
1446
|
-
]
|
|
1447
|
-
}, mirror.path, true, undefined, this))
|
|
1448
|
-
]
|
|
1449
|
-
}, undefined, true, undefined, this) : null,
|
|
1450
|
-
/* @__PURE__ */ jsxDEV(Box, {
|
|
1451
|
-
flexDirection: "column",
|
|
1452
|
-
children: [
|
|
1453
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1454
|
-
bold: true,
|
|
1455
|
-
children: item.preview ? "Safe preview" : "Preview"
|
|
1456
|
-
}, undefined, false, undefined, this),
|
|
1457
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1458
|
-
color: item.preview ? "white" : "gray",
|
|
1459
|
-
children: item.preview ? item.preview.slice(0, 3000) : `Suppressed: ${item.previewSuppressedReason ?? "not available"}`
|
|
1460
|
-
}, undefined, false, undefined, this)
|
|
1461
|
-
]
|
|
1462
|
-
}, undefined, true, undefined, this)
|
|
1463
|
-
]
|
|
1464
|
-
}, undefined, true, undefined, this);
|
|
1465
|
-
}
|
|
1466
|
-
function DecisionsView({ decisions }) {
|
|
1467
|
-
return /* @__PURE__ */ jsxDEV(Box, {
|
|
1468
|
-
flexDirection: "column",
|
|
1469
|
-
children: [
|
|
1470
|
-
/* @__PURE__ */ jsxDEV(Header, {
|
|
1471
|
-
title: "Decisions",
|
|
1472
|
-
subtitle: `${decisions.length} saved`
|
|
1473
|
-
}, undefined, false, undefined, this),
|
|
1474
|
-
/* @__PURE__ */ jsxDEV(Box, {
|
|
1475
|
-
flexDirection: "column",
|
|
1476
|
-
marginTop: 1,
|
|
1477
|
-
children: decisions.length === 0 ? /* @__PURE__ */ jsxDEV(Text, {
|
|
1478
|
-
color: "gray",
|
|
1479
|
-
children: "No decisions recorded yet."
|
|
1480
|
-
}, undefined, false, undefined, this) : decisions.map((decision) => /* @__PURE__ */ jsxDEV(Box, {
|
|
1481
|
-
gap: 1,
|
|
1482
|
-
children: [
|
|
1483
|
-
/* @__PURE__ */ jsxDEV(Badge, {
|
|
1484
|
-
label: decision.status,
|
|
1485
|
-
color: "cyan"
|
|
1486
|
-
}, undefined, false, undefined, this),
|
|
1487
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1488
|
-
children: decision.itemId
|
|
1489
|
-
}, undefined, false, undefined, this),
|
|
1490
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1491
|
-
color: "gray",
|
|
1492
|
-
children: decision.updatedAt
|
|
1493
|
-
}, undefined, false, undefined, this)
|
|
1494
|
-
]
|
|
1495
|
-
}, `${decision.itemId}-${decision.updatedAt}`, true, undefined, this))
|
|
1496
|
-
}, undefined, false, undefined, this)
|
|
1497
|
-
]
|
|
1498
|
-
}, undefined, true, undefined, this);
|
|
1499
|
-
}
|
|
1500
|
-
function LoginPrompt({ verificationUri, userCode, expiresIn }) {
|
|
1501
|
-
return /* @__PURE__ */ jsxDEV(Box, {
|
|
1502
|
-
flexDirection: "column",
|
|
1503
|
-
gap: 1,
|
|
1504
|
-
children: [
|
|
1505
|
-
/* @__PURE__ */ jsxDEV(Header, {
|
|
1506
|
-
title: "Login with GitHub",
|
|
1507
|
-
subtitle: `code expires in ${Math.round(expiresIn / 60)} minutes`
|
|
1508
|
-
}, undefined, false, undefined, this),
|
|
1509
|
-
/* @__PURE__ */ jsxDEV(Box, {
|
|
1510
|
-
flexDirection: "column",
|
|
1511
|
-
children: [
|
|
1512
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1513
|
-
children: "Open:"
|
|
1514
|
-
}, undefined, false, undefined, this),
|
|
1515
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1516
|
-
color: "blue",
|
|
1517
|
-
underline: true,
|
|
1518
|
-
children: verificationUri
|
|
1519
|
-
}, undefined, false, undefined, this)
|
|
1520
|
-
]
|
|
1521
|
-
}, undefined, true, undefined, this),
|
|
1522
|
-
/* @__PURE__ */ jsxDEV(Box, {
|
|
1523
|
-
gap: 1,
|
|
1524
|
-
children: [
|
|
1525
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1526
|
-
children: "Enter code:"
|
|
1527
|
-
}, undefined, false, undefined, this),
|
|
1528
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1529
|
-
color: "green",
|
|
1530
|
-
bold: true,
|
|
1531
|
-
children: userCode
|
|
1532
|
-
}, undefined, false, undefined, this)
|
|
1533
|
-
]
|
|
1534
|
-
}, undefined, true, undefined, this)
|
|
1535
|
-
]
|
|
1536
|
-
}, undefined, true, undefined, this);
|
|
1537
|
-
}
|
|
1538
|
-
function LoginSuccess({ login, apiUrl }) {
|
|
1539
|
-
return /* @__PURE__ */ jsxDEV(Box, {
|
|
1540
|
-
flexDirection: "column",
|
|
1541
|
-
children: [
|
|
1542
|
-
/* @__PURE__ */ jsxDEV(Header, {
|
|
1543
|
-
title: "Logged in",
|
|
1544
|
-
subtitle: apiUrl
|
|
1545
|
-
}, undefined, false, undefined, this),
|
|
1546
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1547
|
-
color: "green",
|
|
1548
|
-
children: [
|
|
1549
|
-
"\u2713 ",
|
|
1550
|
-
login
|
|
1551
|
-
]
|
|
1552
|
-
}, undefined, true, undefined, this)
|
|
1553
|
-
]
|
|
1554
|
-
}, undefined, true, undefined, this);
|
|
1555
|
-
}
|
|
1556
|
-
function WhoamiView({ login, apiUrl }) {
|
|
1557
|
-
if (!login)
|
|
1558
|
-
return /* @__PURE__ */ jsxDEV(Text, {
|
|
1559
|
-
color: "yellow",
|
|
1560
|
-
children: "Not logged in. Run `atl login`."
|
|
1561
|
-
}, undefined, false, undefined, this);
|
|
1562
|
-
return /* @__PURE__ */ jsxDEV(Box, {
|
|
1563
|
-
flexDirection: "column",
|
|
1564
|
-
children: [
|
|
1565
|
-
/* @__PURE__ */ jsxDEV(Header, {
|
|
1566
|
-
title: "Current account",
|
|
1567
|
-
subtitle: apiUrl
|
|
1568
|
-
}, undefined, false, undefined, this),
|
|
1569
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1570
|
-
color: "green",
|
|
1571
|
-
children: login
|
|
1572
|
-
}, undefined, false, undefined, this)
|
|
1573
|
-
]
|
|
1574
|
-
}, undefined, true, undefined, this);
|
|
1575
|
-
}
|
|
1576
|
-
function LogoutView() {
|
|
1577
|
-
return /* @__PURE__ */ jsxDEV(Text, {
|
|
1578
|
-
color: "green",
|
|
1579
|
-
children: "\u2713 Logged out"
|
|
1580
|
-
}, undefined, false, undefined, this);
|
|
1581
|
-
}
|
|
1582
|
-
function ProfileListView({ profiles, activeProfile }) {
|
|
1583
|
-
return /* @__PURE__ */ jsxDEV(Box, {
|
|
1584
|
-
flexDirection: "column",
|
|
1585
|
-
children: [
|
|
1586
|
-
/* @__PURE__ */ jsxDEV(Header, {
|
|
1587
|
-
title: "Profiles",
|
|
1588
|
-
subtitle: `active: ${activeProfile}`
|
|
1589
|
-
}, undefined, false, undefined, this),
|
|
1590
|
-
/* @__PURE__ */ jsxDEV(Box, {
|
|
1591
|
-
flexDirection: "column",
|
|
1592
|
-
marginTop: 1,
|
|
1593
|
-
children: profiles.map((profile) => /* @__PURE__ */ jsxDEV(Box, {
|
|
1594
|
-
gap: 1,
|
|
1595
|
-
children: [
|
|
1596
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1597
|
-
color: profile.name === activeProfile ? "green" : "gray",
|
|
1598
|
-
children: profile.name === activeProfile ? "\u25CF" : "\u25CB"
|
|
1599
|
-
}, undefined, false, undefined, this),
|
|
1600
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1601
|
-
bold: profile.name === activeProfile,
|
|
1602
|
-
children: profile.name
|
|
1603
|
-
}, undefined, false, undefined, this),
|
|
1604
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1605
|
-
color: "gray",
|
|
1606
|
-
children: profile.createdAt
|
|
1607
|
-
}, undefined, false, undefined, this)
|
|
1608
|
-
]
|
|
1609
|
-
}, profile.id, true, undefined, this))
|
|
1610
|
-
}, undefined, false, undefined, this)
|
|
1611
|
-
]
|
|
1612
|
-
}, undefined, true, undefined, this);
|
|
1613
|
-
}
|
|
1614
|
-
function ProfileSavedView({ title, name }) {
|
|
1615
|
-
return /* @__PURE__ */ jsxDEV(Box, {
|
|
1616
|
-
flexDirection: "column",
|
|
1617
|
-
children: [
|
|
1618
|
-
/* @__PURE__ */ jsxDEV(Header, {
|
|
1619
|
-
title
|
|
1620
|
-
}, undefined, false, undefined, this),
|
|
1621
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1622
|
-
color: "green",
|
|
1623
|
-
children: [
|
|
1624
|
-
"\u2713 ",
|
|
1625
|
-
name
|
|
1626
|
-
]
|
|
1627
|
-
}, undefined, true, undefined, this)
|
|
1628
|
-
]
|
|
1629
|
-
}, undefined, true, undefined, this);
|
|
1630
|
-
}
|
|
1631
|
-
function SavedConfigListView({ configs, profileName }) {
|
|
1632
|
-
return /* @__PURE__ */ jsxDEV(Box, {
|
|
1633
|
-
flexDirection: "column",
|
|
1634
|
-
children: [
|
|
1635
|
-
/* @__PURE__ */ jsxDEV(Header, {
|
|
1636
|
-
title: "Saved configs",
|
|
1637
|
-
subtitle: profileName
|
|
1638
|
-
}, undefined, false, undefined, this),
|
|
1639
|
-
configs.length === 0 ? /* @__PURE__ */ jsxDEV(Text, {
|
|
1640
|
-
color: "gray",
|
|
1641
|
-
children: "No saved configs yet."
|
|
1642
|
-
}, undefined, false, undefined, this) : configs.map((config) => /* @__PURE__ */ jsxDEV(Box, {
|
|
1643
|
-
flexDirection: "column",
|
|
1644
|
-
marginTop: 1,
|
|
1645
|
-
children: [
|
|
1646
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1647
|
-
bold: true,
|
|
1648
|
-
children: config.stableId
|
|
1649
|
-
}, undefined, false, undefined, this),
|
|
1650
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1651
|
-
color: "gray",
|
|
1652
|
-
children: config.pathHint ?? "no path hint"
|
|
1653
|
-
}, undefined, false, undefined, this),
|
|
1654
|
-
config.latestVersion ? /* @__PURE__ */ jsxDEV(Text, {
|
|
1655
|
-
color: "green",
|
|
1656
|
-
children: [
|
|
1657
|
-
config.latestVersion.contentSha256.slice(0, 12),
|
|
1658
|
-
" \xB7 ",
|
|
1659
|
-
config.latestVersion.sizeBytes,
|
|
1660
|
-
" bytes \xB7 ",
|
|
1661
|
-
config.latestVersion.createdAt
|
|
1662
|
-
]
|
|
1663
|
-
}, undefined, true, undefined, this) : null
|
|
1664
|
-
]
|
|
1665
|
-
}, config.id, true, undefined, this))
|
|
1666
|
-
]
|
|
1667
|
-
}, undefined, true, undefined, this);
|
|
1668
|
-
}
|
|
1669
|
-
function SavedConfigDetailView({ config, profileName }) {
|
|
1670
|
-
return /* @__PURE__ */ jsxDEV(Box, {
|
|
1671
|
-
flexDirection: "column",
|
|
1672
|
-
children: [
|
|
1673
|
-
/* @__PURE__ */ jsxDEV(Header, {
|
|
1674
|
-
title: config.stableId,
|
|
1675
|
-
subtitle: profileName
|
|
1676
|
-
}, undefined, false, undefined, this),
|
|
1677
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1678
|
-
color: "gray",
|
|
1679
|
-
children: config.pathHint ?? "no path hint"
|
|
1680
|
-
}, undefined, false, undefined, this),
|
|
1681
|
-
/* @__PURE__ */ jsxDEV(Box, {
|
|
1682
|
-
flexDirection: "column",
|
|
1683
|
-
marginTop: 1,
|
|
1684
|
-
children: (config.versions ?? []).map((version) => /* @__PURE__ */ jsxDEV(Text, {
|
|
1685
|
-
children: [
|
|
1686
|
-
version.contentSha256.slice(0, 12),
|
|
1687
|
-
" \xB7 ",
|
|
1688
|
-
version.sizeBytes,
|
|
1689
|
-
" bytes \xB7 ",
|
|
1690
|
-
version.createdAt
|
|
1691
|
-
]
|
|
1692
|
-
}, version.id, true, undefined, this))
|
|
1693
|
-
}, undefined, false, undefined, this)
|
|
1694
|
-
]
|
|
1695
|
-
}, undefined, true, undefined, this);
|
|
1696
|
-
}
|
|
1697
|
-
function ConfigSavedView({ stableId: stableId2, profileName, versionHash, sizeBytes, reused }) {
|
|
1698
|
-
return /* @__PURE__ */ jsxDEV(Box, {
|
|
1699
|
-
flexDirection: "column",
|
|
1700
|
-
children: [
|
|
1701
|
-
/* @__PURE__ */ jsxDEV(Header, {
|
|
1702
|
-
title: reused ? "Config already saved" : "Config saved",
|
|
1703
|
-
subtitle: profileName
|
|
1704
|
-
}, undefined, false, undefined, this),
|
|
1705
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1706
|
-
color: "green",
|
|
1707
|
-
children: [
|
|
1708
|
-
"\u2713 ",
|
|
1709
|
-
stableId2
|
|
1710
|
-
]
|
|
1711
|
-
}, undefined, true, undefined, this),
|
|
1712
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1713
|
-
color: "gray",
|
|
1714
|
-
children: [
|
|
1715
|
-
"Version ",
|
|
1716
|
-
versionHash.slice(0, 12),
|
|
1717
|
-
" \xB7 ",
|
|
1718
|
-
sizeBytes,
|
|
1719
|
-
" bytes \xB7 encrypted"
|
|
1720
|
-
]
|
|
1721
|
-
}, undefined, true, undefined, this)
|
|
1722
|
-
]
|
|
1723
|
-
}, undefined, true, undefined, this);
|
|
1724
|
-
}
|
|
1725
|
-
function DecisionSaved({ item, status }) {
|
|
1726
|
-
return /* @__PURE__ */ jsxDEV(Box, {
|
|
1727
|
-
flexDirection: "column",
|
|
1728
|
-
children: [
|
|
1729
|
-
/* @__PURE__ */ jsxDEV(Header, {
|
|
1730
|
-
title: "Decision saved",
|
|
1731
|
-
subtitle: item.displayPath
|
|
1732
|
-
}, undefined, false, undefined, this),
|
|
1733
|
-
/* @__PURE__ */ jsxDEV(Box, {
|
|
1734
|
-
gap: 1,
|
|
1735
|
-
children: [
|
|
1736
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1737
|
-
children: "Status:"
|
|
1738
|
-
}, undefined, false, undefined, this),
|
|
1739
|
-
/* @__PURE__ */ jsxDEV(Badge, {
|
|
1740
|
-
label: status,
|
|
1741
|
-
color: "green"
|
|
1742
|
-
}, undefined, false, undefined, this)
|
|
1743
|
-
]
|
|
1744
|
-
}, undefined, true, undefined, this)
|
|
1745
|
-
]
|
|
1746
|
-
}, undefined, true, undefined, this);
|
|
1747
|
-
}
|
|
1748
|
-
function DoctorView({ checks }) {
|
|
1749
|
-
const failing = checks.filter((check) => check.status === "fail").length;
|
|
1750
|
-
const warnings = checks.filter((check) => check.status === "warn").length;
|
|
1751
|
-
return /* @__PURE__ */ jsxDEV(Box, {
|
|
1752
|
-
flexDirection: "column",
|
|
1753
|
-
gap: 1,
|
|
1754
|
-
children: [
|
|
1755
|
-
/* @__PURE__ */ jsxDEV(Header, {
|
|
1756
|
-
title: "Doctor",
|
|
1757
|
-
subtitle: failing ? `${failing} failing` : warnings ? `${warnings} warning${warnings === 1 ? "" : "s"}` : "all checks passed"
|
|
1758
|
-
}, undefined, false, undefined, this),
|
|
1759
|
-
/* @__PURE__ */ jsxDEV(Box, {
|
|
1760
|
-
flexDirection: "column",
|
|
1761
|
-
children: checks.map((check) => /* @__PURE__ */ jsxDEV(Box, {
|
|
1762
|
-
gap: 1,
|
|
1763
|
-
children: [
|
|
1764
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1765
|
-
color: doctorColor(check.status),
|
|
1766
|
-
children: doctorIcon(check.status)
|
|
1767
|
-
}, undefined, false, undefined, this),
|
|
1768
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1769
|
-
bold: true,
|
|
1770
|
-
children: check.label
|
|
1771
|
-
}, undefined, false, undefined, this),
|
|
1772
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1773
|
-
color: "gray",
|
|
1774
|
-
children: check.detail
|
|
1775
|
-
}, undefined, false, undefined, this)
|
|
1776
|
-
]
|
|
1777
|
-
}, check.label, true, undefined, this))
|
|
1778
|
-
}, undefined, false, undefined, this)
|
|
1779
|
-
]
|
|
1780
|
-
}, undefined, true, undefined, this);
|
|
1781
|
-
}
|
|
1782
|
-
function NotFound({ query }) {
|
|
1783
|
-
return /* @__PURE__ */ jsxDEV(Text, {
|
|
1784
|
-
color: "red",
|
|
1785
|
-
children: [
|
|
1786
|
-
"No inventory item matched: ",
|
|
1787
|
-
query
|
|
1788
|
-
]
|
|
1789
|
-
}, undefined, true, undefined, this);
|
|
1790
|
-
}
|
|
1791
|
-
function Header({ title, subtitle }) {
|
|
1792
|
-
return /* @__PURE__ */ jsxDEV(Box, {
|
|
1793
|
-
flexDirection: "column",
|
|
1794
|
-
children: [
|
|
1795
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1796
|
-
bold: true,
|
|
1797
|
-
color: "cyan",
|
|
1798
|
-
children: "\u25C6 Atelier"
|
|
1799
|
-
}, undefined, false, undefined, this),
|
|
1800
|
-
/* @__PURE__ */ jsxDEV(Box, {
|
|
1801
|
-
gap: 1,
|
|
1802
|
-
children: [
|
|
1803
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1804
|
-
bold: true,
|
|
1805
|
-
children: title
|
|
1806
|
-
}, undefined, false, undefined, this),
|
|
1807
|
-
subtitle ? /* @__PURE__ */ jsxDEV(Text, {
|
|
1808
|
-
color: "gray",
|
|
1809
|
-
children: subtitle
|
|
1810
|
-
}, undefined, false, undefined, this) : null
|
|
1811
|
-
]
|
|
1812
|
-
}, undefined, true, undefined, this)
|
|
1813
|
-
]
|
|
1814
|
-
}, undefined, true, undefined, this);
|
|
1815
|
-
}
|
|
1816
|
-
function Stat({ label, value, color }) {
|
|
1817
|
-
return /* @__PURE__ */ jsxDEV(Box, {
|
|
1818
|
-
flexDirection: "column",
|
|
1819
|
-
borderStyle: "round",
|
|
1820
|
-
borderColor: color,
|
|
1821
|
-
paddingX: 1,
|
|
1822
|
-
children: [
|
|
1823
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1824
|
-
color,
|
|
1825
|
-
bold: true,
|
|
1826
|
-
children: value
|
|
1827
|
-
}, undefined, false, undefined, this),
|
|
1828
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1829
|
-
color: "gray",
|
|
1830
|
-
children: label
|
|
1831
|
-
}, undefined, false, undefined, this)
|
|
1832
|
-
]
|
|
1833
|
-
}, undefined, true, undefined, this);
|
|
1834
|
-
}
|
|
1835
|
-
function Badge({ label, color }) {
|
|
1836
|
-
return /* @__PURE__ */ jsxDEV(Text, {
|
|
1837
|
-
color,
|
|
1838
|
-
children: [
|
|
1839
|
-
"[",
|
|
1840
|
-
label,
|
|
1841
|
-
"]"
|
|
1842
|
-
]
|
|
1843
|
-
}, undefined, true, undefined, this);
|
|
1844
|
-
}
|
|
1845
|
-
function KeyValues({ rows }) {
|
|
1846
|
-
return /* @__PURE__ */ jsxDEV(Box, {
|
|
1847
|
-
flexDirection: "column",
|
|
1848
|
-
children: rows.map(([key, value]) => /* @__PURE__ */ jsxDEV(Box, {
|
|
1849
|
-
gap: 1,
|
|
1850
|
-
children: [
|
|
1851
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1852
|
-
color: "gray",
|
|
1853
|
-
children: key.padEnd(10)
|
|
1854
|
-
}, undefined, false, undefined, this),
|
|
1855
|
-
/* @__PURE__ */ jsxDEV(Text, {
|
|
1856
|
-
children: value
|
|
1857
|
-
}, undefined, false, undefined, this)
|
|
1858
|
-
]
|
|
1859
|
-
}, key, true, undefined, this))
|
|
1860
|
-
}, undefined, false, undefined, this);
|
|
1861
|
-
}
|
|
1862
|
-
function kindColor(item) {
|
|
1863
|
-
const kind = typeof item === "string" ? item : item.kind;
|
|
1864
|
-
if (kind === "private")
|
|
1865
|
-
return "red";
|
|
1866
|
-
if (kind === "generated")
|
|
1867
|
-
return "magenta";
|
|
1868
|
-
if (kind === "config")
|
|
1869
|
-
return "green";
|
|
1870
|
-
return "yellow";
|
|
1871
|
-
}
|
|
1872
|
-
function shareabilityColor(shareability) {
|
|
1873
|
-
if (shareability === "private")
|
|
1874
|
-
return "red";
|
|
1875
|
-
if (shareability === "shareable")
|
|
1876
|
-
return "green";
|
|
1877
|
-
return "yellow";
|
|
1878
|
-
}
|
|
1879
|
-
function doctorIcon(status) {
|
|
1880
|
-
if (status === "pass")
|
|
1881
|
-
return "\u2713";
|
|
1882
|
-
if (status === "warn")
|
|
1883
|
-
return "!";
|
|
1884
|
-
return "\u2717";
|
|
1885
|
-
}
|
|
1886
|
-
function doctorColor(status) {
|
|
1887
|
-
if (status === "pass")
|
|
1888
|
-
return "green";
|
|
1889
|
-
if (status === "warn")
|
|
1890
|
-
return "yellow";
|
|
1891
|
-
return "red";
|
|
1892
|
-
}
|
|
1893
|
-
function legacyText(item) {
|
|
1894
|
-
const entries = Object.entries(item.legacyManagers ?? {});
|
|
1895
|
-
if (entries.length === 0)
|
|
1896
|
-
return "not scanned";
|
|
1897
|
-
return entries.map(([manager, status]) => `${manager}: ${status}`).join(", ");
|
|
1898
|
-
}
|
|
1899
|
-
function gitText(item) {
|
|
1900
|
-
if (!item.git)
|
|
1901
|
-
return "not in repo";
|
|
1902
|
-
return `${item.git.tracked ? "tracked" : "untracked"}${item.git.modified ? ", modified" : ""} @ ${item.git.root}`;
|
|
1903
|
-
}
|
|
1904
|
-
|
|
1905
|
-
// src/index.tsx
|
|
1906
|
-
import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
|
|
1907
|
-
var repoRoot = resolve2(import.meta.dir, "../../..");
|
|
1908
|
-
var stateDir = getStateDir();
|
|
1909
|
-
var inventoryPath = join4(stateDir, "inventory.json");
|
|
1910
|
-
var decisionsPath = join4(stateDir, "decisions.json");
|
|
1911
|
-
var decisionStatuses = new Set(["undecided", "candidate", "shareable", "machine-specific", "private", "ignored"]);
|
|
1912
|
-
var pipedInputLines;
|
|
1913
|
-
var program = new Command;
|
|
1914
|
-
program.name("atl").description("Atelier config inventory cockpit").version("0.1.1");
|
|
1915
|
-
program.command("login").description("Login to Atelier with GitHub device auth").option("--api-url <url>", "Atelier API URL", getConfiguredApiUrl()).action(async (options) => {
|
|
1916
|
-
const result = await loginWithGitHub({
|
|
1917
|
-
apiUrl: options.apiUrl,
|
|
1918
|
-
onPrompt: async (prompt) => {
|
|
1919
|
-
await renderInk(/* @__PURE__ */ jsxDEV2(LoginPrompt, {
|
|
1920
|
-
verificationUri: prompt.verificationUri,
|
|
1921
|
-
userCode: prompt.userCode,
|
|
1922
|
-
expiresIn: prompt.expiresIn
|
|
1923
|
-
}, undefined, false, undefined, this));
|
|
1924
|
-
}
|
|
1925
|
-
});
|
|
1926
|
-
await renderInk(/* @__PURE__ */ jsxDEV2(LoginSuccess, {
|
|
1927
|
-
login: result.user.login,
|
|
1928
|
-
apiUrl: result.apiUrl
|
|
1929
|
-
}, undefined, false, undefined, this));
|
|
1930
|
-
});
|
|
1931
|
-
program.command("whoami").description("Show the current Atelier account").action(async () => {
|
|
1932
|
-
const current = await getCurrentUser();
|
|
1933
|
-
await renderInk(/* @__PURE__ */ jsxDEV2(WhoamiView, {
|
|
1934
|
-
login: current?.user.login,
|
|
1935
|
-
apiUrl: current?.apiUrl
|
|
1936
|
-
}, undefined, false, undefined, this));
|
|
1937
|
-
if (!current)
|
|
1938
|
-
process.exitCode = 1;
|
|
1939
|
-
});
|
|
1940
|
-
program.command("logout").description("Remove the local Atelier session").action(async () => {
|
|
1941
|
-
logout();
|
|
1942
|
-
await renderInk(/* @__PURE__ */ jsxDEV2(LogoutView, {}, undefined, false, undefined, this));
|
|
1943
|
-
});
|
|
1944
|
-
program.command("api").description("Run the local Atelier API server for development").option("--port <port>", "port to bind", "8787").action(async (options) => {
|
|
1945
|
-
await startApi(Number(options.port));
|
|
1946
|
-
});
|
|
1947
|
-
var profile = program.command("profile").description("Manage account-backed config profiles");
|
|
1948
|
-
profile.command("list").description("List remote profiles").action(async () => {
|
|
1949
|
-
await renderInk(/* @__PURE__ */ jsxDEV2(ProfileListView, {
|
|
1950
|
-
profiles: await listProfiles(),
|
|
1951
|
-
activeProfile: getActiveProfile()
|
|
1952
|
-
}, undefined, false, undefined, this));
|
|
1953
|
-
});
|
|
1954
|
-
profile.command("create").description("Create a remote profile").argument("<name>").action(async (name) => {
|
|
1955
|
-
const profile2 = await createProfile(name);
|
|
1956
|
-
await renderInk(/* @__PURE__ */ jsxDEV2(ProfileSavedView, {
|
|
1957
|
-
title: "Profile created",
|
|
1958
|
-
name: profile2.name
|
|
1959
|
-
}, undefined, false, undefined, this));
|
|
1960
|
-
});
|
|
1961
|
-
profile.command("switch").description("Set the active local profile after verifying it exists remotely").argument("<name>").action(async (name) => {
|
|
1962
|
-
const profile2 = await getProfile(name);
|
|
1963
|
-
if (!profile2)
|
|
1964
|
-
throw new Error(`Profile not found: ${name}`);
|
|
1965
|
-
setActiveProfile(profile2.name);
|
|
1966
|
-
await renderInk(/* @__PURE__ */ jsxDEV2(ProfileSavedView, {
|
|
1967
|
-
title: "Active profile",
|
|
1968
|
-
name: profile2.name
|
|
1969
|
-
}, undefined, false, undefined, this));
|
|
1970
|
-
});
|
|
1971
|
-
profile.command("current").description("Show the active local profile").action(async () => {
|
|
1972
|
-
await renderInk(/* @__PURE__ */ jsxDEV2(ProfileSavedView, {
|
|
1973
|
-
title: "Active profile",
|
|
1974
|
-
name: getActiveProfile()
|
|
1975
|
-
}, undefined, false, undefined, this));
|
|
1976
|
-
});
|
|
1977
|
-
program.command("checkout").description("Alias for `atl profile switch`").argument("<name>").action(async (name) => {
|
|
1978
|
-
const profile2 = await getProfile(name);
|
|
1979
|
-
if (!profile2)
|
|
1980
|
-
throw new Error(`Profile not found: ${name}`);
|
|
1981
|
-
setActiveProfile(profile2.name);
|
|
1982
|
-
await renderInk(/* @__PURE__ */ jsxDEV2(ProfileSavedView, {
|
|
1983
|
-
title: "Active profile",
|
|
1984
|
-
name: profile2.name
|
|
1985
|
-
}, undefined, false, undefined, this));
|
|
1986
|
-
});
|
|
1987
|
-
var vault = program.command("vault").description("Advanced vault controls");
|
|
1988
|
-
vault.command("status").description("Show vault initialization and local unlock state").action(async () => {
|
|
1989
|
-
const state = await getVaultState();
|
|
1990
|
-
console.log(`Vault: ${state.initialized ? "initialized" : "not initialized"}`);
|
|
1991
|
-
console.log(`Local state: ${state.unlocked ? "unlocked" : "locked"}`);
|
|
1992
|
-
console.log(`Active profile: ${state.activeProfile}`);
|
|
1993
|
-
});
|
|
1994
|
-
vault.command("init").description("Initialize the encrypted vault now instead of waiting for first save").action(async () => {
|
|
1995
|
-
const passphrase = await readConfirmedPassphrase("Create an Atelier vault passphrase: ");
|
|
1996
|
-
const result = await initializeVault(passphrase);
|
|
1997
|
-
console.log(`Vault initialized for profile ${result.profileName}`);
|
|
1998
|
-
});
|
|
1999
|
-
vault.command("unlock").description("Unlock this machine with the vault passphrase").action(async () => {
|
|
2000
|
-
const passphrase = await readHiddenLine("Vault passphrase: ");
|
|
2001
|
-
const result = await unlockVault(passphrase);
|
|
2002
|
-
console.log(`Vault unlocked for profile ${result.activeProfile}`);
|
|
2003
|
-
});
|
|
2004
|
-
vault.command("lock").description("Remove local unlocked vault material from this machine").action(async () => {
|
|
2005
|
-
lockVault();
|
|
2006
|
-
console.log("Vault locked");
|
|
2007
|
-
});
|
|
2008
|
-
program.command("save").description("Save a config snapshot to the active encrypted profile").argument("<id-or-path>").action(async (query) => {
|
|
2009
|
-
await saveInventoryItem(query);
|
|
2010
|
-
});
|
|
2011
|
-
program.command("adopt").description("Alias for `atl save`").argument("<id-or-path>").action(async (query) => {
|
|
2012
|
-
await saveInventoryItem(query);
|
|
2013
|
-
});
|
|
2014
|
-
var saved = program.command("saved").description("Inspect encrypted configs saved remotely");
|
|
2015
|
-
saved.command("list").description("List saved configs for the active profile").action(async () => {
|
|
2016
|
-
const profileName = getActiveProfile();
|
|
2017
|
-
await renderInk(/* @__PURE__ */ jsxDEV2(SavedConfigListView, {
|
|
2018
|
-
configs: await listSavedConfigs(profileName),
|
|
2019
|
-
profileName
|
|
2020
|
-
}, undefined, false, undefined, this));
|
|
2021
|
-
});
|
|
2022
|
-
saved.command("show").description("Show saved metadata and version history").argument("<stable-id>").action(async (stableId2) => {
|
|
2023
|
-
const profileName = getActiveProfile();
|
|
2024
|
-
const config = await getSavedConfig(profileName, stableId2);
|
|
2025
|
-
if (!config)
|
|
2026
|
-
throw new Error(`Saved config not found: ${stableId2}`);
|
|
2027
|
-
await renderInk(/* @__PURE__ */ jsxDEV2(SavedConfigDetailView, {
|
|
2028
|
-
config,
|
|
2029
|
-
profileName
|
|
2030
|
-
}, undefined, false, undefined, this));
|
|
2031
|
-
});
|
|
2032
|
-
program.command("scan").description("Scan this machine and write local inventory").option("--path <path...>", "additional manual path(s) to scan").option("--legacy", "include legacy manager signals like yadm").action(async (options) => {
|
|
2033
|
-
const inventory = await scan({ repoRoot, manualPaths: options.path ?? [], includeLegacyManagers: options.legacy });
|
|
2034
|
-
const path = writeInventory(inventory, { path: inventoryPath });
|
|
2035
|
-
await renderInk(/* @__PURE__ */ jsxDEV2(ScanSummary, {
|
|
2036
|
-
inventory,
|
|
2037
|
-
path
|
|
2038
|
-
}, undefined, false, undefined, this));
|
|
2039
|
-
});
|
|
2040
|
-
program.command("ui").description("Open the local TanStack Start inventory UI").option("--scan", "rescan before opening the UI").option("--path <path...>", "additional manual path(s) to scan").option("--legacy", "include legacy manager signals like yadm during scan").option("--port <port>", "port to bind", "4141").action(async (options) => {
|
|
2041
|
-
if (!existsSync4(inventoryPath) || options.scan) {
|
|
2042
|
-
console.log("Atelier: scanning configs\u2026");
|
|
2043
|
-
const inventory = await scan({ repoRoot, manualPaths: options.path ?? [], includeLegacyManagers: options.legacy });
|
|
2044
|
-
writeInventory(inventory, { path: inventoryPath });
|
|
2045
|
-
console.log(`Atelier: scan complete (${inventory.summary.existing}/${inventory.summary.total} existing)`);
|
|
2046
|
-
}
|
|
2047
|
-
console.log("Atelier: starting UI\u2026");
|
|
2048
|
-
await startUi(Number(options.port));
|
|
2049
|
-
});
|
|
2050
|
-
program.command("list").description("List inventory items with polished terminal output").option("--domain <domain>", "filter by domain").option("--app <app>", "filter by app").option("--secrets", "only items with secret warnings").option("--drift", "only items with mirror drift").option("--generated", "only generated/app-state items").option("--private", "only private items").option("--missing", "only missing registry items").option("--json", "print JSON instead of Ink output").action(async (options) => {
|
|
2051
|
-
const inventory = readInventory();
|
|
2052
|
-
const items = filterItems(inventory.items, options);
|
|
2053
|
-
if (options.json)
|
|
2054
|
-
return printJson(items);
|
|
2055
|
-
await renderInk(/* @__PURE__ */ jsxDEV2(ItemList, {
|
|
2056
|
-
items,
|
|
2057
|
-
title: "Inventory"
|
|
2058
|
-
}, undefined, false, undefined, this));
|
|
2059
|
-
});
|
|
2060
|
-
program.command("inspect").description("Inspect one inventory item by id or path").argument("<id-or-path>").option("--json", "print JSON instead of Ink output").action(async (query, options) => {
|
|
2061
|
-
const inventory = readInventory();
|
|
2062
|
-
const item = findItem(inventory, query);
|
|
2063
|
-
if (options.json)
|
|
2064
|
-
return printJson(item ?? null);
|
|
2065
|
-
await renderInk(item ? /* @__PURE__ */ jsxDEV2(ItemDetail, {
|
|
2066
|
-
item
|
|
2067
|
-
}, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV2(NotFound, {
|
|
2068
|
-
query
|
|
2069
|
-
}, undefined, false, undefined, this));
|
|
2070
|
-
});
|
|
2071
|
-
program.command("decisions").description("Show saved item decisions").option("--json", "print JSON instead of Ink output").action(async (options) => {
|
|
2072
|
-
const decisions = readDecisions().decisions;
|
|
2073
|
-
if (options.json)
|
|
2074
|
-
return printJson(decisions);
|
|
2075
|
-
await renderInk(/* @__PURE__ */ jsxDEV2(DecisionsView, {
|
|
2076
|
-
decisions
|
|
2077
|
-
}, undefined, false, undefined, this));
|
|
2078
|
-
});
|
|
2079
|
-
program.command("decide").description("Save a decision for one inventory item").argument("<id-or-path>").argument("<status>", `one of: ${Array.from(decisionStatuses).join(", ")}`).action(async (query, status) => {
|
|
2080
|
-
if (!decisionStatuses.has(status)) {
|
|
2081
|
-
program.error(`invalid status '${status}'. Valid: ${Array.from(decisionStatuses).join(", ")}`);
|
|
2082
|
-
}
|
|
2083
|
-
const inventory = readInventory();
|
|
2084
|
-
const item = findItem(inventory, query);
|
|
2085
|
-
if (!item) {
|
|
2086
|
-
await renderInk(/* @__PURE__ */ jsxDEV2(NotFound, {
|
|
2087
|
-
query
|
|
2088
|
-
}, undefined, false, undefined, this));
|
|
2089
|
-
process.exitCode = 1;
|
|
2090
|
-
return;
|
|
2091
|
-
}
|
|
2092
|
-
const decisions = readDecisions();
|
|
2093
|
-
decisions.decisions = decisions.decisions.filter((decision) => decision.itemId !== item.id);
|
|
2094
|
-
decisions.decisions.push({ itemId: item.id, status, updatedAt: new Date().toISOString() });
|
|
2095
|
-
mkdirSync4(dirname4(decisionsPath), { recursive: true });
|
|
2096
|
-
writeFileSync4(decisionsPath, JSON.stringify(decisions, null, 2) + `
|
|
2097
|
-
`, "utf-8");
|
|
2098
|
-
await renderInk(/* @__PURE__ */ jsxDEV2(DecisionSaved, {
|
|
2099
|
-
item,
|
|
2100
|
-
status
|
|
2101
|
-
}, undefined, false, undefined, this));
|
|
2102
|
-
});
|
|
2103
|
-
program.command("summary").description("Print the last scan summary").option("--json", "print JSON instead of Ink output").action(async (options) => {
|
|
2104
|
-
const inventory = readInventory();
|
|
2105
|
-
if (options.json)
|
|
2106
|
-
return printJson(inventory.summary);
|
|
2107
|
-
await renderInk(/* @__PURE__ */ jsxDEV2(ScanSummary, {
|
|
2108
|
-
inventory,
|
|
2109
|
-
path: inventoryPath
|
|
2110
|
-
}, undefined, false, undefined, this));
|
|
2111
|
-
});
|
|
2112
|
-
program.command("doctor").description("Check Atelier CLI, UI, inventory, and local environment health").option("--json", "print JSON instead of Ink output").action(async (options) => {
|
|
2113
|
-
const checks = runDoctorChecks();
|
|
2114
|
-
if (options.json)
|
|
2115
|
-
printJson(checks);
|
|
2116
|
-
else
|
|
2117
|
-
await renderInk(/* @__PURE__ */ jsxDEV2(DoctorView, {
|
|
2118
|
-
checks
|
|
2119
|
-
}, undefined, false, undefined, this));
|
|
2120
|
-
if (checks.some((check) => check.status === "fail"))
|
|
2121
|
-
process.exitCode = 1;
|
|
2122
|
-
});
|
|
2123
|
-
program.parseAsync(process.argv).catch((error) => {
|
|
2124
|
-
console.error(error instanceof Error ? error.message : error);
|
|
2125
|
-
process.exit(1);
|
|
2126
|
-
});
|
|
2127
|
-
function printJson(value) {
|
|
2128
|
-
console.log(JSON.stringify(value, null, 2));
|
|
2129
|
-
}
|
|
2130
|
-
function readInventory() {
|
|
2131
|
-
if (!existsSync4(inventoryPath)) {
|
|
2132
|
-
throw new Error("No inventory found. Run `atl scan` first.");
|
|
2133
|
-
}
|
|
2134
|
-
return JSON.parse(readFileSync4(inventoryPath, "utf-8"));
|
|
2135
|
-
}
|
|
2136
|
-
function readDecisions() {
|
|
2137
|
-
if (!existsSync4(decisionsPath))
|
|
2138
|
-
return { version: 1, decisions: [] };
|
|
2139
|
-
return JSON.parse(readFileSync4(decisionsPath, "utf-8"));
|
|
2140
|
-
}
|
|
2141
|
-
function getStateDir() {
|
|
2142
|
-
if (process.env.ATELIER_STATE_DIR)
|
|
2143
|
-
return process.env.ATELIER_STATE_DIR;
|
|
2144
|
-
if (process.env.XDG_STATE_HOME)
|
|
2145
|
-
return join4(process.env.XDG_STATE_HOME, "atelier");
|
|
2146
|
-
if (platform3() === "win32" && process.env.LOCALAPPDATA)
|
|
2147
|
-
return join4(process.env.LOCALAPPDATA, "Atelier");
|
|
2148
|
-
return join4(homedir4(), ".local", "state", "atelier");
|
|
2149
|
-
}
|
|
2150
|
-
async function saveInventoryItem(query) {
|
|
2151
|
-
const inventory = readInventory();
|
|
2152
|
-
const item = findItem(inventory, query);
|
|
2153
|
-
if (!item) {
|
|
2154
|
-
await renderInk(/* @__PURE__ */ jsxDEV2(NotFound, {
|
|
2155
|
-
query
|
|
2156
|
-
}, undefined, false, undefined, this));
|
|
2157
|
-
process.exitCode = 1;
|
|
2158
|
-
return;
|
|
2159
|
-
}
|
|
2160
|
-
const vault2 = await getVaultState();
|
|
2161
|
-
let passphrase;
|
|
2162
|
-
if (!vault2.initialized) {
|
|
2163
|
-
passphrase = await readConfirmedPassphrase("Create an Atelier vault passphrase: ");
|
|
2164
|
-
} else if (!vault2.unlocked) {
|
|
2165
|
-
passphrase = await readHiddenLine("Vault passphrase: ");
|
|
2166
|
-
}
|
|
2167
|
-
const result = await saveConfigItem(item, { passphrase });
|
|
2168
|
-
await renderInk(/* @__PURE__ */ jsxDEV2(ConfigSavedView, {
|
|
2169
|
-
stableId: result.stableId,
|
|
2170
|
-
profileName: result.profileName,
|
|
2171
|
-
versionHash: result.version.contentSha256,
|
|
2172
|
-
sizeBytes: result.version.sizeBytes,
|
|
2173
|
-
reused: result.reused
|
|
2174
|
-
}, undefined, false, undefined, this));
|
|
2175
|
-
}
|
|
2176
|
-
async function readConfirmedPassphrase(prompt) {
|
|
2177
|
-
const passphrase = await readHiddenLine(prompt);
|
|
2178
|
-
const confirmation = await readHiddenLine("Confirm vault passphrase: ");
|
|
2179
|
-
if (passphrase !== confirmation)
|
|
2180
|
-
throw new Error("Vault passphrases do not match");
|
|
2181
|
-
return passphrase;
|
|
2182
|
-
}
|
|
2183
|
-
async function readHiddenLine(prompt) {
|
|
2184
|
-
if (!input.isTTY) {
|
|
2185
|
-
output.write(prompt);
|
|
2186
|
-
pipedInputLines ??= readFileSync4(0, "utf-8").split(/\r?\n/);
|
|
2187
|
-
return pipedInputLines.shift() ?? "";
|
|
2188
|
-
}
|
|
2189
|
-
output.write(prompt);
|
|
2190
|
-
input.setRawMode(true);
|
|
2191
|
-
input.resume();
|
|
2192
|
-
input.setEncoding("utf-8");
|
|
2193
|
-
let value = "";
|
|
2194
|
-
try {
|
|
2195
|
-
for await (const chunk of input) {
|
|
2196
|
-
const text = String(chunk);
|
|
2197
|
-
for (const char of text) {
|
|
2198
|
-
if (char === "\x03") {
|
|
2199
|
-
output.write(`
|
|
2200
|
-
`);
|
|
2201
|
-
process.exit(130);
|
|
2202
|
-
}
|
|
2203
|
-
if (char === "\r" || char === `
|
|
2204
|
-
`) {
|
|
2205
|
-
output.write(`
|
|
2206
|
-
`);
|
|
2207
|
-
return value;
|
|
2208
|
-
}
|
|
2209
|
-
if (char === "\x7F") {
|
|
2210
|
-
value = value.slice(0, -1);
|
|
2211
|
-
continue;
|
|
2212
|
-
}
|
|
2213
|
-
value += char;
|
|
2214
|
-
}
|
|
2215
|
-
}
|
|
2216
|
-
} finally {
|
|
2217
|
-
input.setRawMode(false);
|
|
2218
|
-
input.pause();
|
|
2219
|
-
}
|
|
2220
|
-
return value;
|
|
2221
|
-
}
|
|
2222
|
-
function filterItems(items, options) {
|
|
2223
|
-
return items.filter((item) => {
|
|
2224
|
-
if (options.domain && item.domain.toLowerCase() !== options.domain.toLowerCase())
|
|
2225
|
-
return false;
|
|
2226
|
-
if (options.app && item.app.toLowerCase() !== options.app.toLowerCase())
|
|
2227
|
-
return false;
|
|
2228
|
-
if (options.secrets && item.secretFindings.length === 0)
|
|
2229
|
-
return false;
|
|
2230
|
-
if (options.drift && !item.mirrors.some((mirror) => mirror.exists && mirror.identical === false))
|
|
2231
|
-
return false;
|
|
2232
|
-
if (options.generated && item.kind !== "generated")
|
|
2233
|
-
return false;
|
|
2234
|
-
if (options.private && item.shareability !== "private")
|
|
2235
|
-
return false;
|
|
2236
|
-
if (options.missing && item.exists)
|
|
2237
|
-
return false;
|
|
2238
|
-
return true;
|
|
2239
|
-
});
|
|
2240
|
-
}
|
|
2241
|
-
function findItem(inventory, query) {
|
|
2242
|
-
const normalized = normalizeQuery(query);
|
|
2243
|
-
return inventory.items.find((item) => item.id === query || normalizeQuery(item.path) === normalized || normalizeQuery(item.displayPath) === normalized) ?? inventory.items.find((item) => item.displayPath.includes(query) || item.path.includes(query));
|
|
2244
|
-
}
|
|
2245
|
-
function normalizeQuery(query) {
|
|
2246
|
-
return query.replace(/^~(?=\/|$)/, process.env.HOME ?? "").toLowerCase();
|
|
2247
|
-
}
|
|
2248
|
-
function runDoctorChecks() {
|
|
2249
|
-
const checks = [];
|
|
2250
|
-
const inventory = existsSync4(inventoryPath) ? readInventory() : undefined;
|
|
2251
|
-
const bunVersion = commandOutput("bun", ["--version"]);
|
|
2252
|
-
const nodeVersion = commandOutput("node", ["--version"]);
|
|
2253
|
-
const atlPath = commandOutput("which", ["atl"]);
|
|
2254
|
-
const gitDirty = commandOutput("git", ["status", "--porcelain"], repoRoot);
|
|
2255
|
-
checks.push({
|
|
2256
|
-
label: "repo",
|
|
2257
|
-
status: existsSync4(join4(repoRoot, "package.json")) ? "pass" : "fail",
|
|
2258
|
-
detail: repoRoot
|
|
2259
|
-
});
|
|
2260
|
-
checks.push({
|
|
2261
|
-
label: "bun",
|
|
2262
|
-
status: bunVersion ? "pass" : "fail",
|
|
2263
|
-
detail: bunVersion ? `v${bunVersion}` : "not found"
|
|
2264
|
-
});
|
|
2265
|
-
checks.push({
|
|
2266
|
-
label: "node for Vite",
|
|
2267
|
-
status: nodeVersion && isViteSupportedNode(nodeVersion) ? "pass" : "warn",
|
|
2268
|
-
detail: nodeVersion ? `${nodeVersion}${isViteSupportedNode(nodeVersion) ? "" : " \u2014 Vite wants 20.19+ or 22.12+"}` : "not found"
|
|
2269
|
-
});
|
|
2270
|
-
checks.push({
|
|
2271
|
-
label: "global atl",
|
|
2272
|
-
status: atlPath ? atlPath.includes(".bun") || atlPath.includes(repoRoot) ? "pass" : "warn" : "fail",
|
|
2273
|
-
detail: atlPath ?? "not linked; run `bun link` from the repo"
|
|
2274
|
-
});
|
|
2275
|
-
checks.push({
|
|
2276
|
-
label: "inventory",
|
|
2277
|
-
status: inventory ? "pass" : "warn",
|
|
2278
|
-
detail: inventory ? `${inventory.summary.existing}/${inventory.summary.total} existing, generated ${inventory.generatedAt}` : "missing; run `atl scan`"
|
|
2279
|
-
});
|
|
2280
|
-
checks.push({
|
|
2281
|
-
label: "secret warnings",
|
|
2282
|
-
status: inventory && inventory.summary.secrets > 0 ? "warn" : "pass",
|
|
2283
|
-
detail: inventory ? `${inventory.summary.secrets} item${inventory.summary.secrets === 1 ? "" : "s"} flagged` : "no inventory"
|
|
2284
|
-
});
|
|
2285
|
-
checks.push({
|
|
2286
|
-
label: "ui dependencies",
|
|
2287
|
-
status: existsSync4(join4(repoRoot, "node_modules", "@tanstack", "react-start")) ? "pass" : "fail",
|
|
2288
|
-
detail: existsSync4(join4(repoRoot, "node_modules", "@tanstack", "react-start")) ? "installed" : "missing; run `bun install`"
|
|
2289
|
-
});
|
|
2290
|
-
checks.push({
|
|
2291
|
-
label: "git state",
|
|
2292
|
-
status: gitDirty === undefined ? "warn" : gitDirty.trim() ? "warn" : "pass",
|
|
2293
|
-
detail: gitDirty === undefined ? "not a git repo" : gitDirty.trim() ? `${gitDirty.trim().split(`
|
|
2294
|
-
`).length} changed file(s)` : "clean"
|
|
2295
|
-
});
|
|
2296
|
-
return checks;
|
|
2297
|
-
}
|
|
2298
|
-
function commandOutput(command, args, cwd = repoRoot) {
|
|
2299
|
-
try {
|
|
2300
|
-
return execFileSync4(command, args, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
2301
|
-
} catch {
|
|
2302
|
-
return;
|
|
2303
|
-
}
|
|
2304
|
-
}
|
|
2305
|
-
function isViteSupportedNode(version) {
|
|
2306
|
-
const match = version.match(/^v?(\d+)\.(\d+)\.(\d+)/);
|
|
2307
|
-
if (!match)
|
|
2308
|
-
return false;
|
|
2309
|
-
const [, majorRaw, minorRaw] = match;
|
|
2310
|
-
const major = Number(majorRaw);
|
|
2311
|
-
const minor = Number(minorRaw);
|
|
2312
|
-
if (major === 20)
|
|
2313
|
-
return minor >= 19;
|
|
2314
|
-
if (major === 22)
|
|
2315
|
-
return minor >= 12;
|
|
2316
|
-
return major > 22;
|
|
2317
|
-
}
|
|
2318
|
-
async function startApi(port) {
|
|
2319
|
-
const apiSecret = process.env.ATELIER_API_SECRET ?? crypto.randomUUID();
|
|
2320
|
-
const proc = Bun.spawn(["bun", "src/index.ts"], {
|
|
2321
|
-
cwd: join4(repoRoot, "apps", "api"),
|
|
2322
|
-
env: { ...process.env, PORT: String(port), ATELIER_API_SECRET: apiSecret },
|
|
2323
|
-
stdin: "inherit",
|
|
2324
|
-
stdout: "inherit",
|
|
2325
|
-
stderr: "inherit"
|
|
2326
|
-
});
|
|
2327
|
-
console.log(`Atelier API: http://localhost:${port}`);
|
|
2328
|
-
const code = await proc.exited;
|
|
2329
|
-
process.exit(code);
|
|
2330
|
-
}
|
|
2331
|
-
async function startUi(port) {
|
|
2332
|
-
const proc = Bun.spawn(["bun", "--bun", "vite", "dev", "--host", "127.0.0.1", "--port", String(port)], {
|
|
2333
|
-
cwd: join4(repoRoot, "apps", "ui"),
|
|
2334
|
-
env: { ...process.env, ATELIER_REPO_ROOT: repoRoot },
|
|
2335
|
-
stdin: "inherit",
|
|
2336
|
-
stdout: "inherit",
|
|
2337
|
-
stderr: "inherit"
|
|
2338
|
-
});
|
|
2339
|
-
console.log(`Atelier UI: http://localhost:${port}`);
|
|
2340
|
-
const code = await proc.exited;
|
|
2341
|
-
process.exit(code);
|
|
2342
|
-
}
|
|
3
|
+
import{execFileSync as un}from"child_process";import{stdin as C,stdout as F}from"process";import{existsSync as x,mkdirSync as dn,readFileSync as ke,writeFileSync as yn}from"fs";import{homedir as bn,platform as wn}from"os";import{dirname as hn,join as p,resolve as pn}from"path";import{Command as Bn}from"commander";import{execFileSync as te}from"child_process";import{chmodSync as Me,existsSync as ne,mkdirSync as Re,readFileSync as Se,rmSync as Gr,writeFileSync as _e}from"fs";import{homedir as Or,platform as Ir}from"os";import{dirname as me,join as oe}from"path";var Xe=oe(Or(),".config","atelier"),z=oe(Xe,"session.json"),S=oe(Xe,"config.json"),ie="dev.atelier.session",Hr="https://atelier.mananjoshi.me/api",qr="Ov23liiscZlMXcJ2RLnd";function le(){return process.env.ATELIER_API_URL??Hr}function Fr(){return process.env.ATELIER_GITHUB_CLIENT_ID??qr}async function Ye(e){let r=e.apiUrl??le(),t=e.clientId??Fr(),n=await Dr(t);await e.onPrompt({verificationUri:n.verification_uri,userCode:n.user_code,expiresIn:n.expires_in});let i=await jr(t,n,e.onPoll),l=await fetch(`${r}/auth/github/exchange`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({githubAccessToken:i})});if(!l.ok)throw new Error(`Atelier API rejected GitHub login (${l.status})`);let a=await l.json();return Vr({apiUrl:r,user:a.user,token:a.token}),{apiUrl:r,user:a.user}}async function Je(){let e=_();if(!e)return;let r=await fetch(`${e.apiUrl}/me`,{headers:{authorization:`Bearer ${e.token}`}});if(!r.ok)return;return{user:(await r.json()).user,apiUrl:e.apiUrl}}function _(){let e=Le();if(!e)return;let r=Er(e);if(!r)return;return{apiUrl:e.apiUrl,user:e.user,token:r}}function b(){return Qe().activeProfile??"personal"}function ae(e){Pr({...Qe(),activeProfile:e})}function We(){let e=Le();if(e?.tokenStorage==="keychain")tt(e.user.id);if(ne(z))Gr(z)}function Le(){if(!ne(z))return;return JSON.parse(Se(z,"utf-8"))}function Qe(){if(!ne(S))return{};return JSON.parse(Se(S,"utf-8"))}function Pr(e){Re(me(S),{recursive:!0}),_e(S,JSON.stringify(e,null,2)+`
|
|
4
|
+
`,"utf-8"),Me(S,384)}function Vr(e){Re(me(z),{recursive:!0});let r=Ir()==="darwin",t={apiUrl:e.apiUrl,user:e.user,tokenStorage:r?"keychain":"file",createdAt:new Date().toISOString()};if(r)et(e.user.id,e.token);else t.token=e.token;_e(z,JSON.stringify(t,null,2)+`
|
|
5
|
+
`,"utf-8"),Me(z,384)}function Er(e){if(e.tokenStorage==="file")return e.token;return rt(e.user.id)}async function Dr(e){let r=await fetch("https://github.com/login/device/code",{method:"POST",headers:{accept:"application/json","content-type":"application/json"},body:JSON.stringify({client_id:e,scope:"read:user user:email"})});if(!r.ok)throw new Error(`GitHub device-code request failed (${r.status})`);return r.json()}async function jr(e,r,t){let n=r.interval,i=Date.now()+r.expires_in*1000;while(Date.now()<i){await nt(n*1000),await t?.("Waiting for GitHub authorization\u2026");let l=await fetch("https://github.com/login/oauth/access_token",{method:"POST",headers:{accept:"application/json","content-type":"application/json"},body:JSON.stringify({client_id:e,device_code:r.device_code,grant_type:"urn:ietf:params:oauth:grant-type:device_code"})});if(!l.ok)throw new Error(`GitHub token polling failed (${l.status})`);let a=await l.json();if(a.access_token)return a.access_token;if(a.error==="authorization_pending")continue;if(a.error==="slow_down"){n+=5;continue}if(a.error==="access_denied")throw new Error("GitHub login was denied");if(a.error==="expired_token")throw new Error("GitHub login code expired");throw new Error(a.error_description??"GitHub login failed")}throw new Error("GitHub login timed out")}function et(e,r){te("security",["add-generic-password","-a",e,"-s",ie,"-w",r,"-U"],{stdio:"ignore"})}function rt(e){try{return te("security",["find-generic-password","-a",e,"-s",ie,"-w"],{encoding:"utf-8",stdio:["ignore","pipe","ignore"]}).trim()}catch{return}}function tt(e){try{te("security",["delete-generic-password","-a",e,"-s",ie],{stdio:"ignore"})}catch{}}function nt(e){return new Promise((r)=>setTimeout(r,e))}async function Ne(){return(await(await h("/profiles")).json()).profiles}async function Ge(e){return(await(await h("/profiles",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({name:e})})).json()).profile}async function m(e){let r=await h(`/profiles/${encodeURIComponent(e)}`);if(r.status===404)return;return(await r.json()).profile}async function X(){return await(await h("/vault")).json()}async function Oe(e){await h("/vault",{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify(e)})}async function Ie(e){let r=await h(`/profiles/${encodeURIComponent(e)}/key`);if(r.status===404)return;return(await r.json()).profileKey}async function ce(e,r){return(await(await h(`/profiles/${encodeURIComponent(e)}/key`,{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify(r)})).json()).profileKey}async function He(e){return(await(await h(`/profiles/${encodeURIComponent(e)}/configs`)).json()).configs}async function qe(e,r){let t=await h(`/profiles/${encodeURIComponent(e)}/configs/${encodeURIComponent(r)}`);if(t.status===404)return;return(await t.json()).config}async function Fe(e,r,t){return await(await h(`/profiles/${encodeURIComponent(e)}/configs/${encodeURIComponent(r)}/versions`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t)})).json()}async function h(e,r={}){let t=_();if(!t)throw new Error("Not logged in. Run `atl login`.");let n=await fetch(`${t.apiUrl}${e}`,{...r,headers:{...r.headers,authorization:`Bearer ${t.token}`}});if(!n.ok&&n.status!==404){let i=await n.json().catch(()=>{return});throw new Error(i?.error??`Atelier API request failed (${n.status})`)}return n}import{createCipheriv as $t,createHash as At,randomBytes as Ct}from"crypto";import{readFile as xt}from"fs/promises";import{execFileSync as ue}from"child_process";import{createCipheriv as ot,createDecipheriv as it,randomBytes as Y,scryptSync as lt}from"crypto";import{chmodSync as at,existsSync as Ve,mkdirSync as ct,readFileSync as ft,rmSync as gt,writeFileSync as st}from"fs";import{homedir as ut,platform as de}from"os";import{dirname as dt,join as Ee}from"path";var ye="dev.atelier.vault",yt=Ee(ut(),".config","atelier"),K=Ee(yt,"vault-key.json"),Pe={N:32768,r:8,p:1},bt=67108864,fe="aes-256-gcm";async function be(){let e=U();return{initialized:(await X()).initialized,unlocked:Boolean(se(e.user.id)),activeProfile:b()}}async function we(e,r=b()){ht(e);let t=U();if((await X()).initialized)throw new Error("Atelier vault already exists. Run `atl vault unlock` if this machine is locked.");let i=await m(r);if(!i)throw new Error(`Profile not found: ${r}`);let l=Y(32),a=Y(32),d=Y(16),ee=tr(e,d,Pe),re=ge(l,ee),R=ge(a,l);return await Oe({kdf:{algorithm:"scrypt",salt:N(d),params:Pe},encryptedVaultKey:re}),await ce(r,{version:1,algorithm:R.algorithm,nonce:R.nonce,encryptedKey:R.ciphertext}),he(t.user.id,l),{profileName:i.name}}async function De(e){let r=U(),t=await X();if(!t.initialized)throw new Error("Atelier vault is not initialized. Run `atl save <id>` or `atl vault init`.");let n=rr(t,e);return he(r.user.id,n),{activeProfile:b()}}function je(){let e=U();pt(e.user.id)}async function wt(e,r=b()){let t=U(),n=se(t.user.id);if(n)return n;if(!e)throw new Error("Vault passphrase is required");let i=await X();if(!i.initialized){await we(e,r);let l=se(t.user.id);if(!l)throw new Error("Vault initialized but local key was not stored");return l}return rr(i,e)}async function er(e,r=b()){let t=await wt(e,r),n=await Ie(r);if(n)return nr(n.encryptedKey,n.nonce,t);let i=Y(32),l=ge(i,t);return await ce(r,{version:1,algorithm:l.algorithm,nonce:l.nonce,encryptedKey:l.ciphertext}),i}function rr(e,r){if(!e.kdf||!e.encryptedVaultKey)throw new Error("Vault payload is incomplete");if(e.kdf.algorithm!=="scrypt")throw new Error(`Unsupported vault KDF: ${e.kdf.algorithm}`);let t=G(e.kdf.salt),n=tr(r,t,e.kdf.params),i=nr(e.encryptedVaultKey.ciphertext,e.encryptedVaultKey.nonce,n);return he(U().user.id,i),i}function tr(e,r,t){return lt(e,r,32,{N:t.N,r:t.r,p:t.p,maxmem:bt})}function ge(e,r){let t=Y(12),n=ot(fe,r,t),i=Buffer.concat([n.update(e),n.final()]),l=n.getAuthTag();return{algorithm:fe,nonce:N(t),ciphertext:N(Buffer.concat([i,l]))}}function nr(e,r,t){let n=Buffer.from(G(e)),i=Buffer.from(G(r)),l=n.subarray(0,-16),a=n.subarray(-16),d=it(fe,t,i);return d.setAuthTag(a),Buffer.concat([d.update(l),d.final()])}function ht(e){if(e.length<12)throw new Error("Vault passphrase must be at least 12 characters.")}function U(){let e=_();if(!e)throw new Error("Not logged in. Run `atl login`.");return e}function he(e,r){let t=N(r);if(de()==="darwin"){ue("security",["add-generic-password","-a",e,"-s",ye,"-w",t,"-U"],{stdio:"ignore"});return}ct(dt(K),{recursive:!0}),st(K,JSON.stringify({userId:e,key:t},null,2)+`
|
|
6
|
+
`,"utf-8"),at(K,384)}function se(e){let r=de()==="darwin"?Bt(e):zt(e);return r?Buffer.from(G(r)):void 0}function pt(e){if(de()==="darwin"){try{ue("security",["delete-generic-password","-a",e,"-s",ye],{stdio:"ignore"})}catch{}return}if(Ve(K))gt(K)}function Bt(e){try{return ue("security",["find-generic-password","-a",e,"-s",ye,"-w"],{encoding:"utf-8",stdio:["ignore","pipe","ignore"]}).trim()}catch{return}}function zt(e){if(!Ve(K))return;let r=JSON.parse(ft(K,"utf-8"));return r.userId===e?r.key:void 0}function N(e){return Buffer.from(e).toString("base64url")}function G(e){return new Uint8Array(Buffer.from(e,"base64url"))}function or(e){let r=[];if(!e.exists)r.push("file does not exist");if(e.isDirectory||e.kind==="directory"||e.format==="directory")r.push("directory saving is not supported yet");if(e.kind==="generated")r.push("generated/cache files are not saved");if(e.kind==="private"||e.shareability==="private")r.push("private/auth files are blocked");if(e.secretFindings.length>0)r.push("detected secret material");if(e.size!==void 0&&e.size>1048576)r.push(`file is larger than ${Tt(1048576)}`);if(Kt(e))r.push("binary files are not supported yet");return{ok:r.length===0,reasons:r}}function pe(e){let r=or(e);if(!r.ok)throw new Error(`Cannot save ${e.id}: ${r.reasons.join("; ")}`)}function Kt(e){if(e.previewSuppressedReason?.toLowerCase().includes("binary"))return!0;return["binary","sqlite","db"].includes(e.format.toLowerCase())}function Tt(e){if(e<1024)return`${e} B`;let r=e/1024;if(r<1024)return`${Math.round(r)} KiB`;return`${Math.round(r/1024)} MiB`}var ir="aes-256-gcm";async function cr(e,r={}){pe(e);let t=b(),n=await er(r.passphrase,t),i=await xt(e.path),l=vt(i,n),a=await Fe(t,e.id,{kind:"file",pathHint:e.displayPath,contentSha256:lr(i),ciphertextSha256:lr(l.ciphertextBytes),sizeBytes:i.byteLength,algorithm:l.algorithm,profileKeyVersion:1,nonce:l.nonce,ciphertext:l.ciphertext});return{profileName:t,stableId:e.id,version:a.version,reused:a.reused}}function vt(e,r){let t=Ct(12),n=$t(ir,r,t),i=Buffer.concat([n.update(e),n.final()]),l=n.getAuthTag(),a=Buffer.concat([i,l]);return{algorithm:ir,nonce:ar(t),ciphertext:ar(a),ciphertextBytes:a}}function lr(e){return At("sha256").update(e).digest("hex")}function ar(e){return Buffer.from(e).toString("base64url")}import{execFileSync as wr}from"child_process";import{existsSync as $,lstatSync as Mt,mkdirSync as Rt,readdirSync as Ke,readFileSync as J,statSync as k,writeFileSync as St}from"fs";import{homedir as _t,userInfo as mt}from"os";import{dirname as hr,join as T,relative as pr,resolve as Xt}from"path";var fr=[{domain:"AI Tools",app:"Pi",items:[{id:"pi.dir",path:"~/.pi",kind:"directory",format:"directory",shareability:"machine-specific"}]},{domain:"Shell",app:"Starship",items:[{id:"starship.config",path:"~/.config/starship.toml",kind:"config",format:"toml",shareability:"shareable"}]},{domain:"Editors",app:"Zed",items:[{id:"zed.dir",path:"~/.config/zed",kind:"directory",format:"directory",shareability:"shareable"},{id:"zed.settings",path:"~/.config/zed/settings.json",kind:"config",format:"jsonc",shareability:"shareable"},{id:"zed.themes",path:"~/.config/zed/themes",kind:"config",format:"directory",shareability:"shareable"},{id:"zed.prompts",path:"~/.config/zed/prompts",kind:"generated",format:"directory",shareability:"machine-specific"}]},{domain:"Git",app:"Git",items:[{id:"git.config",path:"~/.gitconfig",kind:"config",format:"gitconfig",shareability:"machine-specific"},{id:"git.config-dir",path:"~/.config/git",kind:"directory",format:"directory",shareability:"machine-specific"},{id:"git.ignore",path:"~/.gitignore",kind:"config",format:"gitignore",shareability:"machine-specific"}]},{domain:"Private/Auth",app:"Credentials",privateByDefault:!0,items:[{id:"auth.ssh",path:"~/.ssh",kind:"private",format:"directory",shareability:"private"},{id:"auth.aws",path:"~/.aws",kind:"private",format:"directory",shareability:"private"},{id:"auth.gh",path:"~/.config/gh",kind:"private",format:"directory",shareability:"private"},{id:"auth.gcloud-adc",path:"~/.config/gcloud/application_default_credentials.json",kind:"private",format:"json",shareability:"private"},{id:"auth.docker",path:"~/.docker/config.json",kind:"private",format:"json",shareability:"private"},{id:"auth.pi",path:"~/.config/pi/auth.json",kind:"private",format:"json",shareability:"private"}]},{domain:"Shell",app:"Zsh",items:[{id:"zsh.home-rc",path:"~/.zshrc",kind:"config",format:"shell",shareability:"shareable"},{id:"zsh.home-env",path:"~/.zshenv",kind:"config",format:"shell",shareability:"private"},{id:"zsh.dir",path:"~/.config/zsh",kind:"directory",format:"directory",shareability:"machine-specific"},{id:"zsh.env",path:"~/.config/zsh/.zshenv",kind:"config",format:"shell",shareability:"private"},{id:"zsh.rc",path:"~/.config/zsh/.zshrc",kind:"config",format:"shell",shareability:"shareable"},{id:"zsh.conf",path:"~/.config/zsh/conf.d",kind:"directory",format:"directory",shareability:"shareable"},{id:"zsh.zcompdump",path:"~/.config/zsh/.zcompdump",kind:"generated",format:"text",shareability:"machine-specific"},{id:"zsh.sessions",path:"~/.config/zsh/.zsh_sessions",kind:"generated",format:"directory",shareability:"machine-specific"}]},{domain:"Terminals",app:"cmux",items:[{id:"cmux.dir",path:"~/.config/cmux",kind:"directory",format:"directory",shareability:"shareable"},{id:"cmux.config",path:"~/.config/cmux/cmux.json",kind:"config",format:"jsonc",shareability:"shareable"},{id:"cmux.ghostty",path:"~/Library/Application Support/com.cmuxterm.app/config.ghostty",kind:"config",format:"ghostty",shareability:"shareable"},{id:"cmux.browser-history",path:"~/Library/Application Support/com.cmuxterm.app/browser_history.json",kind:"generated",format:"json",shareability:"machine-specific"}]}];var Be=[["GitHub token",/gh[pousr]_[A-Za-z0-9_]{20,}/g],["Slack token",/xox[baprs]-[A-Za-z0-9-]{20,}/g],["AWS access key",/AKIA[0-9A-Z]{16}/g],["Private key",/-----BEGIN (?:RSA |OPENSSH |EC |DSA )?PRIVATE KEY-----/g],["Generic token assignment",/(?:token|api[_-]?key|secret|webhook|password)\s*[=:]\s*["']?[^"'\s]{12,}/gi]];function gr(e){let r=[],t=e.split(/\r?\n/);for(let[n,i]of Be)for(let l of e.matchAll(i)){let a=l.index??0,d=e.slice(0,a).split(/\r?\n/).length;r.push({type:n,line:d,preview:O(l[0])})}return t.forEach((n,i)=>{for(let l of n.matchAll(/[A-Za-z0-9_+\/=.-]{32,}/g)){let a=l[0];if(!Ut(a,n)&&ur(a,n))r.push({type:"High-entropy string",line:i+1,preview:O(a)})}}),Zt(r)}function O(e){if(e.length<=8)return"[redacted]";return`${e.slice(0,4)}\u2026${e.slice(-4)}`}function sr(e){let r=e;for(let[,t]of Be)r=r.replace(t,(n)=>O(n));return r=r.replace(/[A-Za-z0-9_+\/=.-]{32,}/g,(t)=>{if(ur(t,e))return O(t);return t}),r}function Ut(e,r){return Be.some(([,t])=>{return t.lastIndex=0,Array.from(r.matchAll(t)).some((n)=>n[0].includes(e)||e.includes(n[0]))})}function ur(e,r){if(e.startsWith("amazon."))return!1;if(e.includes("anthropic.claude"))return!1;if(/^[A-Za-z0-9.-]+@[A-Za-z0-9.-]+$/.test(e))return!1;if(/model/i.test(r)&&/^[A-Za-z0-9_.-]+$/.test(e))return!1;return kt(e)>4.2&&/[A-Za-z]/.test(e)&&/[0-9]/.test(e)}function kt(e){let r=new Map;for(let n of e)r.set(n,(r.get(n)??0)+1);let t=0;for(let n of r.values()){let i=n/e.length;t-=i*Math.log2(i)}return t}function Zt(e){let r=new Set;return e.filter((t)=>{let n=`${t.type}:${t.line}:${t.preview}`;if(r.has(n))return!1;return r.add(n),!0})}function dr(e){let n=e.replace(/\/\*[\s\S]*?\*\//g,"").replace(/(^|[^:])\/\/.*$/gm,"$1").replace(/,\s*([}\]])/g,"$1");return JSON.parse(n)}var A=_t(),Yt=new Set([".zsh",".zshrc",".zshenv",".sh",".json",".jsonc",".toml",".md",".yml",".yaml",".gitconfig",".gitignore",".ghostty",""]),Jt=[/cache/i,/history/i,/session/i,/state/i,/\.mdb$/i,/\.sqlite/i,/\.db$/i,/zcompdump/i,/logs?/i],Wt=[/credential/i,/secret/i,/token/i,/auth\.json$/i,/hosts\.yml$/i,/config\.json$/i],H;async function Te(e){H=e.includeLegacyManagers?Et():void 0;let t=Lt(e.repoRoot).flatMap((l)=>l.items.map((a)=>({definition:l,item:a}))),n=new Set,i=[];for(let{definition:l,item:a}of t){let d=ze(a.path);n.add(d),i.push(Qt(l,a,d))}for(let l of Nt()){if(n.has(l.path))continue;n.add(l.path),i.push(yr(l))}for(let l of e.manualPaths??[]){let a=ze(l);if(n.has(a))continue;n.add(a),i.push(yr({path:a,reason:"manual path"}))}return i.sort((l,a)=>`${l.domain}:${l.app}:${l.path}`.localeCompare(`${a.domain}:${a.app}:${a.path}`)),{version:1,generatedAt:new Date().toISOString(),repoRoot:e.repoRoot,home:A,items:i,summary:{total:i.length,existing:i.filter((l)=>l.exists).length,secrets:i.filter((l)=>l.secretFindings.length>0).length,drift:i.filter((l)=>l.mirrors.some((a)=>a.exists&&a.identical===!1)).length,generated:i.filter((l)=>l.kind==="generated").length,private:i.filter((l)=>l.shareability==="private").length}}}function $e(e,r={}){let t=r.path??T(e.repoRoot,".atelier","state","inventory.json");return Rt(hr(t),{recursive:!0}),St(t,JSON.stringify(e,null,2)+`
|
|
7
|
+
`,"utf-8"),t}function Lt(e){let r=T(e,"packages","registry","definitions");if(!$(r))return fr;return Ke(r).filter((t)=>t.endsWith(".jsonc")).map((t)=>dr(J(T(r,t),"utf-8")))}function Qt(e,r,t){return Br({path:t,domain:e.domain,app:e.app,kind:r.kind,format:r.format,shareability:r.shareability,reason:"registry match",mirrors:r.mirrors??[],privateByDefault:e.privateByDefault,id:r.id})}function yr(e){let r=jt(e.path),t=rn(e.path),n=tn(e.path,t);return Br({path:e.path,domain:en(e.path),app:r,kind:t,format:nn(e.path),shareability:n,reason:e.reason,mirrors:[],privateByDefault:n==="private"})}function Br(e){let r=$(e.path),t=r?Mt(e.path):void 0,n=r?k(e.path):void 0,i=!!n?.isDirectory(),l=!!t?.isSymbolicLink(),a=r&&!i&&zr(e.path)?gr(J(e.path,"utf-8")):[],d=e.mirrors.map((R)=>Ot(e.path,ze(R))),ee=qt(e.kind,e.shareability,a.length,d),re=Ht(e.path,r,i,e.shareability,e.privateByDefault,e.kind);return{id:e.id??Dt(e.path),domain:e.domain,app:e.app,path:e.path,displayPath:W(e.path),kind:e.kind,format:e.format,shareability:e.shareability,exists:r,isDirectory:i,isSymlink:l,mode:n?`0${(n.mode&511).toString(8)}`:void 0,owner:r?mt().username:void 0,size:n?.size,git:r?Ft(e.path):void 0,legacyManagers:r?Pt(e.path):{},mirrors:d,secretFindings:a,...re,recommendation:ee,reason:e.reason}}function Nt(){let e=[],r=T(A,".config");if(!$(r))return e;for(let t of Ke(r,{withFileTypes:!0})){if(t.name.startsWith("."))continue;let n=T(r,t.name);if(e.push({path:n,reason:"~/.config app directory"}),!t.isDirectory())continue;for(let i of Gt(n).slice(0,80)){let l=T(n,i.name);if(Ae(l)){e.push({path:l,reason:"generated/app-state candidate"});continue}if(i.isFile()&&Ce(i.name))e.push({path:l,reason:"shallow ~/.config config candidate"});if(i.isDirectory()&&["conf.d","themes","snippets","plugins"].includes(i.name))e.push({path:l,reason:"shallow ~/.config config directory"})}}return e}function Gt(e){try{return Ke(e,{withFileTypes:!0})}catch{return[]}}function Ot(e,r){let t=$(r);if(!t||!$(e)||k(e).isDirectory()||k(r).isDirectory())return{path:r,displayPath:W(r),exists:t};let n=J(e,"utf-8"),i=J(r,"utf-8");return{path:r,displayPath:W(r),exists:t,identical:n===i,diff:n===i?void 0:It(i,n)}}function It(e,r){let t=e.split(/\r?\n/),n=r.split(/\r?\n/),i=Math.max(t.length,n.length),l=[];for(let a=0;a<i;a++){if(t[a]===n[a])continue;if(t[a]!==void 0)l.push(`-${t[a]}`);if(n[a]!==void 0)l.push(`+${n[a]}`)}return l.slice(0,200).join(`
|
|
8
|
+
`)}function Ht(e,r,t,n,i,l){if(!r)return{previewSuppressedReason:"missing"};if(t)return{previewSuppressedReason:"directory"};if(n==="private"||i||l==="private")return{previewSuppressedReason:"private/auth metadata-only"};if(!zr(e))return{previewSuppressedReason:"binary or unsupported file type"};let a=J(e,"utf-8");return{preview:sr(a).slice(0,20000)}}function qt(e,r,t,n){if(t>0)return"rotate-secret";if(e==="generated")return"ignore-generated";if(r==="private")return"mark-private";if(n.some((i)=>i.exists&&i.identical===!1))return"resolve-drift";if(r==="machine-specific")return"review-machine-specific";if(e==="config"||e==="directory")return"adopt-candidate";return"none"}function Ft(e){let r=k(e).isDirectory()?e:hr(e),t=I(["rev-parse","--show-toplevel"],r);if(!t)return;let n=pr(t,e),i=!!I(["check-ignore","-q",n],t,!0),l=!!I(["ls-files","--error-unmatch",n],t,!0),a=l&&!!I(["status","--porcelain","--",n],t);return{root:t,tracked:l,modified:a,ignored:i}}function Pt(e){if(!H)return{};return{yadm:Vt(e)}}function Vt(e){let r=W(e).replace(/^~\//,"");if(H?.modified.has(r))return"modified";if(H?.tracked.has(r))return"tracked";return"unknown"}function Et(){let e=new Set((br("yadm",["ls-files"])??"").split(`
|
|
9
|
+
`).filter(Boolean)),r=new Set((br("yadm",["status","--porcelain"])??"").split(`
|
|
10
|
+
`).map((t)=>t.slice(3).trim()).filter(Boolean));return{tracked:e,modified:r}}function I(e,r,t=!1){try{return wr("git",e,{cwd:r,encoding:"utf-8",stdio:["ignore","pipe","ignore"]}).trim()||(t?"ok":void 0)}catch{return}}function br(e,r){try{return wr(e,r,{cwd:A,encoding:"utf-8",stdio:["ignore","pipe","ignore"]})}catch{return}}function ze(e){return Xt(e.replace(/^~(?=\/|$)/,A))}function W(e){return e.startsWith(A)?`~${e.slice(A.length)}`:e}function Dt(e){return W(e).replace(/[^A-Za-z0-9_.-]+/g,":")}function jt(e){let r=pr(T(A,".config"),e);if(!r.startsWith(".."))return r.split(/[\\/]/)[0]||"Unknown";return"Unknown"}function en(e){if(e.includes("/.config/"))return"~/.config";if(e.includes("/.ssh")||e.includes("/.aws"))return"Private/Auth";return"Unknown"}function rn(e){if(Ae(e))return"generated";if(Wt.some((r)=>r.test(e)))return"private";if($(e)&&k(e).isDirectory())return"directory";if(Ce(e))return"config";return"unknown"}function tn(e,r){if(r==="private")return"private";if(r==="generated")return"machine-specific";if(e.includes("/credentials")||e.includes("/.ssh")||e.includes("/.aws"))return"private";return"machine-specific"}function nn(e){if($(e)&&k(e).isDirectory())return"directory";if(e.endsWith(".jsonc"))return"jsonc";if(e.endsWith(".json"))return"json";if(e.endsWith(".toml"))return"toml";if(e.endsWith(".zsh"))return"shell";if(e.endsWith(".md"))return"markdown";return"text"}function Ae(e){return Jt.some((r)=>r.test(e))}function Ce(e){return/(^config\.|settings\.|rc$|\.rc$|\.zshrc$|\.zshenv$|\.jsonc?$|\.toml$|\.ya?ml$|\.zsh$|\.conf$|\.ini$)/i.test(e)}function zr(e){let r=e.split("/").pop()??"";if(Ae(e))return!1;if(r.includes("lock")||r.endsWith(".mdb")||r.endsWith(".sqlite")||r.endsWith(".db"))return!1;let t=r.includes(".")?r.slice(r.lastIndexOf(".")):"";return Yt.has(t)||Ce(r)}import{Box as f,Text as c,render as on}from"ink";import{jsxDEV as o,Fragment as sn}from"react/jsx-dev-runtime";function g(e){return on(o(sn,{children:e},void 0,!1,void 0,this)).waitUntilExit()}function xe({inventory:e,path:r}){return o(f,{flexDirection:"column",gap:1,children:[o(y,{title:"Scan complete",subtitle:r},void 0,!1,void 0,this),o(f,{gap:2,children:[o(Z,{label:"total",value:e.summary.total,color:"cyan"},void 0,!1,void 0,this),o(Z,{label:"existing",value:e.summary.existing,color:"green"},void 0,!1,void 0,this),o(Z,{label:"private",value:e.summary.private,color:"red"},void 0,!1,void 0,this),o(Z,{label:"generated",value:e.summary.generated,color:"magenta"},void 0,!1,void 0,this),o(Z,{label:"secrets",value:e.summary.secrets,color:"red"},void 0,!1,void 0,this),o(Z,{label:"drift",value:e.summary.drift,color:"blue"},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}function Kr({items:e,title:r}){return o(f,{flexDirection:"column",children:[o(y,{title:r,subtitle:`${e.length} item${e.length===1?"":"s"}`},void 0,!1,void 0,this),o(f,{flexDirection:"column",marginTop:1,children:e.map((t)=>o(f,{gap:1,children:[o(c,{color:t.exists?"white":"gray",children:t.exists?"\u25CF":"\u25CB"},void 0,!1,void 0,this),o(c,{color:_r(t),children:t.domain},void 0,!1,void 0,this),o(c,{color:"gray",children:"/"},void 0,!1,void 0,this),o(c,{color:"cyan",children:t.app},void 0,!1,void 0,this),o(c,{children:t.displayPath},void 0,!1,void 0,this),o(B,{label:t.shareability,color:mr(t.shareability)},void 0,!1,void 0,this),t.secretFindings.length>0?o(B,{label:"secret",color:"red"},void 0,!1,void 0,this):null,t.mirrors.some((n)=>n.exists&&n.identical===!1)?o(B,{label:"drift",color:"blue"},void 0,!1,void 0,this):null]},t.id,!0,void 0,this))},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function Tr({item:e}){return o(f,{flexDirection:"column",gap:1,children:[o(y,{title:e.app,subtitle:e.displayPath},void 0,!1,void 0,this),o(f,{gap:1,children:[o(B,{label:e.domain,color:"cyan"},void 0,!1,void 0,this),o(B,{label:e.kind,color:_r(e)},void 0,!1,void 0,this),o(B,{label:e.shareability,color:mr(e.shareability)},void 0,!1,void 0,this),o(B,{label:e.recommendation,color:"yellow"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),o(ln,{rows:[["reason",e.reason],["exists",String(e.exists)],["mode",e.mode??"\u2014"],["symlink",String(e.isSymlink)],["git",gn(e)],["legacy",fn(e)]]},void 0,!1,void 0,this),e.secretFindings.length>0?o(f,{flexDirection:"column",children:[o(c,{color:"red",bold:!0,children:"Secret warnings"},void 0,!1,void 0,this),e.secretFindings.map((r,t)=>o(c,{color:"red",children:[" ",r.type,r.line?` line ${r.line}`:"",": ",r.preview]},`${r.type}-${t}`,!0,void 0,this))]},void 0,!0,void 0,this):null,e.mirrors.length>0?o(f,{flexDirection:"column",children:[o(c,{color:"blue",bold:!0,children:"Mirrors"},void 0,!1,void 0,this),e.mirrors.map((r)=>o(c,{children:[" ",r.displayPath," \u2014 ",r.exists?r.identical===!1?"differs":"identical":"missing"]},r.path,!0,void 0,this))]},void 0,!0,void 0,this):null,o(f,{flexDirection:"column",children:[o(c,{bold:!0,children:e.preview?"Safe preview":"Preview"},void 0,!1,void 0,this),o(c,{color:e.preview?"white":"gray",children:e.preview?e.preview.slice(0,3000):`Suppressed: ${e.previewSuppressedReason??"not available"}`},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}function $r({decisions:e}){return o(f,{flexDirection:"column",children:[o(y,{title:"Decisions",subtitle:`${e.length} saved`},void 0,!1,void 0,this),o(f,{flexDirection:"column",marginTop:1,children:e.length===0?o(c,{color:"gray",children:"No decisions recorded yet."},void 0,!1,void 0,this):e.map((r)=>o(f,{gap:1,children:[o(B,{label:r.status,color:"cyan"},void 0,!1,void 0,this),o(c,{children:r.itemId},void 0,!1,void 0,this),o(c,{color:"gray",children:r.updatedAt},void 0,!1,void 0,this)]},`${r.itemId}-${r.updatedAt}`,!0,void 0,this))},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function Ar({verificationUri:e,userCode:r,expiresIn:t}){return o(f,{flexDirection:"column",gap:1,children:[o(y,{title:"Login with GitHub",subtitle:`code expires in ${Math.round(t/60)} minutes`},void 0,!1,void 0,this),o(f,{flexDirection:"column",children:[o(c,{children:"Open:"},void 0,!1,void 0,this),o(c,{color:"blue",underline:!0,children:e},void 0,!1,void 0,this)]},void 0,!0,void 0,this),o(f,{gap:1,children:[o(c,{children:"Enter code:"},void 0,!1,void 0,this),o(c,{color:"green",bold:!0,children:r},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}function Cr({login:e,apiUrl:r}){return o(f,{flexDirection:"column",children:[o(y,{title:"Logged in",subtitle:r},void 0,!1,void 0,this),o(c,{color:"green",children:["\u2713 ",e]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}function xr({login:e,apiUrl:r}){if(!e)return o(c,{color:"yellow",children:"Not logged in. Run `atl login`."},void 0,!1,void 0,this);return o(f,{flexDirection:"column",children:[o(y,{title:"Current account",subtitle:r},void 0,!1,void 0,this),o(c,{color:"green",children:e},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function vr(){return o(c,{color:"green",children:"\u2713 Logged out"},void 0,!1,void 0,this)}function Ur({profiles:e,activeProfile:r}){return o(f,{flexDirection:"column",children:[o(y,{title:"Profiles",subtitle:`active: ${r}`},void 0,!1,void 0,this),o(f,{flexDirection:"column",marginTop:1,children:e.map((t)=>o(f,{gap:1,children:[o(c,{color:t.name===r?"green":"gray",children:t.name===r?"\u25CF":"\u25CB"},void 0,!1,void 0,this),o(c,{bold:t.name===r,children:t.name},void 0,!1,void 0,this),o(c,{color:"gray",children:t.createdAt},void 0,!1,void 0,this)]},t.id,!0,void 0,this))},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function L({title:e,name:r}){return o(f,{flexDirection:"column",children:[o(y,{title:e},void 0,!1,void 0,this),o(c,{color:"green",children:["\u2713 ",r]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}function kr({configs:e,profileName:r}){return o(f,{flexDirection:"column",children:[o(y,{title:"Saved configs",subtitle:r},void 0,!1,void 0,this),e.length===0?o(c,{color:"gray",children:"No saved configs yet."},void 0,!1,void 0,this):e.map((t)=>o(f,{flexDirection:"column",marginTop:1,children:[o(c,{bold:!0,children:t.stableId},void 0,!1,void 0,this),o(c,{color:"gray",children:t.pathHint??"no path hint"},void 0,!1,void 0,this),t.latestVersion?o(c,{color:"green",children:[t.latestVersion.contentSha256.slice(0,12)," \xB7 ",t.latestVersion.sizeBytes," bytes \xB7 ",t.latestVersion.createdAt]},void 0,!0,void 0,this):null]},t.id,!0,void 0,this))]},void 0,!0,void 0,this)}function Zr({config:e,profileName:r}){return o(f,{flexDirection:"column",children:[o(y,{title:e.stableId,subtitle:r},void 0,!1,void 0,this),o(c,{color:"gray",children:e.pathHint??"no path hint"},void 0,!1,void 0,this),o(f,{flexDirection:"column",marginTop:1,children:(e.versions??[]).map((t)=>o(c,{children:[t.contentSha256.slice(0,12)," \xB7 ",t.sizeBytes," bytes \xB7 ",t.createdAt]},t.id,!0,void 0,this))},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function Mr({stableId:e,profileName:r,versionHash:t,sizeBytes:n,reused:i}){return o(f,{flexDirection:"column",children:[o(y,{title:i?"Config already saved":"Config saved",subtitle:r},void 0,!1,void 0,this),o(c,{color:"green",children:["\u2713 ",e]},void 0,!0,void 0,this),o(c,{color:"gray",children:["Version ",t.slice(0,12)," \xB7 ",n," bytes \xB7 encrypted"]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}function Rr({item:e,status:r}){return o(f,{flexDirection:"column",children:[o(y,{title:"Decision saved",subtitle:e.displayPath},void 0,!1,void 0,this),o(f,{gap:1,children:[o(c,{children:"Status:"},void 0,!1,void 0,this),o(B,{label:r,color:"green"},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}function Sr({checks:e}){let r=e.filter((n)=>n.status==="fail").length,t=e.filter((n)=>n.status==="warn").length;return o(f,{flexDirection:"column",gap:1,children:[o(y,{title:"Doctor",subtitle:r?`${r} failing`:t?`${t} warning${t===1?"":"s"}`:"all checks passed"},void 0,!1,void 0,this),o(f,{flexDirection:"column",children:e.map((n)=>o(f,{gap:1,children:[o(c,{color:cn(n.status),children:an(n.status)},void 0,!1,void 0,this),o(c,{bold:!0,children:n.label},void 0,!1,void 0,this),o(c,{color:"gray",children:n.detail},void 0,!1,void 0,this)]},n.label,!0,void 0,this))},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function q({query:e}){return o(c,{color:"red",children:["No inventory item matched: ",e]},void 0,!0,void 0,this)}function y({title:e,subtitle:r}){return o(f,{flexDirection:"column",children:[o(c,{bold:!0,color:"cyan",children:"\u25C6 Atelier"},void 0,!1,void 0,this),o(f,{gap:1,children:[o(c,{bold:!0,children:e},void 0,!1,void 0,this),r?o(c,{color:"gray",children:r},void 0,!1,void 0,this):null]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}function Z({label:e,value:r,color:t}){return o(f,{flexDirection:"column",borderStyle:"round",borderColor:t,paddingX:1,children:[o(c,{color:t,bold:!0,children:r},void 0,!1,void 0,this),o(c,{color:"gray",children:e},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}function B({label:e,color:r}){return o(c,{color:r,children:["[",e,"]"]},void 0,!0,void 0,this)}function ln({rows:e}){return o(f,{flexDirection:"column",children:e.map(([r,t])=>o(f,{gap:1,children:[o(c,{color:"gray",children:r.padEnd(10)},void 0,!1,void 0,this),o(c,{children:t},void 0,!1,void 0,this)]},r,!0,void 0,this))},void 0,!1,void 0,this)}function _r(e){let r=typeof e==="string"?e:e.kind;if(r==="private")return"red";if(r==="generated")return"magenta";if(r==="config")return"green";return"yellow"}function mr(e){if(e==="private")return"red";if(e==="shareable")return"green";return"yellow"}function an(e){if(e==="pass")return"\u2713";if(e==="warn")return"!";return"\u2717"}function cn(e){if(e==="pass")return"green";if(e==="warn")return"yellow";return"red"}function fn(e){let r=Object.entries(e.legacyManagers??{});if(r.length===0)return"not scanned";return r.map(([t,n])=>`${t}: ${n}`).join(", ")}function gn(e){if(!e.git)return"not in repo";return`${e.git.tracked?"tracked":"untracked"}${e.git.modified?", modified":""} @ ${e.git.root}`}import{jsxDEV as s}from"react/jsx-dev-runtime";var w=pn(import.meta.dir,"../../.."),Jr=zn(),v=p(Jr,"inventory.json"),V=p(Jr,"decisions.json"),ve=new Set(["undecided","candidate","shareable","machine-specific","private","ignored"]),Xr,u=new Bn;u.name("atl").description("Atelier config inventory cockpit").version("0.1.2");u.command("login").description("Login to Atelier with GitHub device auth").option("--api-url <url>","Atelier API URL",le()).action(async(e)=>{let r=await Ye({apiUrl:e.apiUrl,onPrompt:async(t)=>{await g(s(Ar,{verificationUri:t.verificationUri,userCode:t.userCode,expiresIn:t.expiresIn},void 0,!1,void 0,this))}});await g(s(Cr,{login:r.user.login,apiUrl:r.apiUrl},void 0,!1,void 0,this))});u.command("whoami").description("Show the current Atelier account").action(async()=>{let e=await Je();if(await g(s(xr,{login:e?.user.login,apiUrl:e?.apiUrl},void 0,!1,void 0,this)),!e)process.exitCode=1});u.command("logout").description("Remove the local Atelier session").action(async()=>{We(),await g(s(vr,{},void 0,!1,void 0,this))});u.command("api").description("Run the local Atelier API server for development").option("--port <port>","port to bind","8787").action(async(e)=>{await $n(Number(e.port))});var D=u.command("profile").description("Manage account-backed config profiles");D.command("list").description("List remote profiles").action(async()=>{await g(s(Ur,{profiles:await Ne(),activeProfile:b()},void 0,!1,void 0,this))});D.command("create").description("Create a remote profile").argument("<name>").action(async(e)=>{let r=await Ge(e);await g(s(L,{title:"Profile created",name:r.name},void 0,!1,void 0,this))});D.command("switch").description("Set the active local profile after verifying it exists remotely").argument("<name>").action(async(e)=>{let r=await m(e);if(!r)throw new Error(`Profile not found: ${e}`);ae(r.name),await g(s(L,{title:"Active profile",name:r.name},void 0,!1,void 0,this))});D.command("current").description("Show the active local profile").action(async()=>{await g(s(L,{title:"Active profile",name:b()},void 0,!1,void 0,this))});u.command("checkout").description("Alias for `atl profile switch`").argument("<name>").action(async(e)=>{let r=await m(e);if(!r)throw new Error(`Profile not found: ${e}`);ae(r.name),await g(s(L,{title:"Active profile",name:r.name},void 0,!1,void 0,this))});var j=u.command("vault").description("Advanced vault controls");j.command("status").description("Show vault initialization and local unlock state").action(async()=>{let e=await be();console.log(`Vault: ${e.initialized?"initialized":"not initialized"}`),console.log(`Local state: ${e.unlocked?"unlocked":"locked"}`),console.log(`Active profile: ${e.activeProfile}`)});j.command("init").description("Initialize the encrypted vault now instead of waiting for first save").action(async()=>{let e=await Nr("Create an Atelier vault passphrase: "),r=await we(e);console.log(`Vault initialized for profile ${r.profileName}`)});j.command("unlock").description("Unlock this machine with the vault passphrase").action(async()=>{let e=await E("Vault passphrase: "),r=await De(e);console.log(`Vault unlocked for profile ${r.activeProfile}`)});j.command("lock").description("Remove local unlocked vault material from this machine").action(async()=>{je(),console.log("Vault locked")});u.command("save").description("Save a config snapshot to the active encrypted profile").argument("<id-or-path>").action(async(e)=>{await Qr(e)});u.command("adopt").description("Alias for `atl save`").argument("<id-or-path>").action(async(e)=>{await Qr(e)});var Wr=u.command("saved").description("Inspect encrypted configs saved remotely");Wr.command("list").description("List saved configs for the active profile").action(async()=>{let e=b();await g(s(kr,{configs:await He(e),profileName:e},void 0,!1,void 0,this))});Wr.command("show").description("Show saved metadata and version history").argument("<stable-id>").action(async(e)=>{let r=b(),t=await qe(r,e);if(!t)throw new Error(`Saved config not found: ${e}`);await g(s(Zr,{config:t,profileName:r},void 0,!1,void 0,this))});u.command("scan").description("Scan this machine and write local inventory").option("--path <path...>","additional manual path(s) to scan").option("--legacy","include legacy manager signals like yadm").action(async(e)=>{let r=await Te({repoRoot:w,manualPaths:e.path??[],includeLegacyManagers:e.legacy}),t=$e(r,{path:v});await g(s(xe,{inventory:r,path:t},void 0,!1,void 0,this))});u.command("ui").description("Open the local TanStack Start inventory UI").option("--scan","rescan before opening the UI").option("--path <path...>","additional manual path(s) to scan").option("--legacy","include legacy manager signals like yadm during scan").option("--port <port>","port to bind","4141").action(async(e)=>{if(!x(v)||e.scan){console.log("Atelier: scanning configs\u2026");let r=await Te({repoRoot:w,manualPaths:e.path??[],includeLegacyManagers:e.legacy});$e(r,{path:v}),console.log(`Atelier: scan complete (${r.summary.existing}/${r.summary.total} existing)`)}console.log("Atelier: starting UI\u2026"),await An(Number(e.port))});u.command("list").description("List inventory items with polished terminal output").option("--domain <domain>","filter by domain").option("--app <app>","filter by app").option("--secrets","only items with secret warnings").option("--drift","only items with mirror drift").option("--generated","only generated/app-state items").option("--private","only private items").option("--missing","only missing registry items").option("--json","print JSON instead of Ink output").action(async(e)=>{let r=M(),t=Kn(r.items,e);if(e.json)return Q(t);await g(s(Kr,{items:t,title:"Inventory"},void 0,!1,void 0,this))});u.command("inspect").description("Inspect one inventory item by id or path").argument("<id-or-path>").option("--json","print JSON instead of Ink output").action(async(e,r)=>{let t=M(),n=Ze(t,e);if(r.json)return Q(n??null);await g(n?s(Tr,{item:n},void 0,!1,void 0,this):s(q,{query:e},void 0,!1,void 0,this))});u.command("decisions").description("Show saved item decisions").option("--json","print JSON instead of Ink output").action(async(e)=>{let r=Lr().decisions;if(e.json)return Q(r);await g(s($r,{decisions:r},void 0,!1,void 0,this))});u.command("decide").description("Save a decision for one inventory item").argument("<id-or-path>").argument("<status>",`one of: ${Array.from(ve).join(", ")}`).action(async(e,r)=>{if(!ve.has(r))u.error(`invalid status '${r}'. Valid: ${Array.from(ve).join(", ")}`);let t=M(),n=Ze(t,e);if(!n){await g(s(q,{query:e},void 0,!1,void 0,this)),process.exitCode=1;return}let i=Lr();i.decisions=i.decisions.filter((l)=>l.itemId!==n.id),i.decisions.push({itemId:n.id,status:r,updatedAt:new Date().toISOString()}),dn(hn(V),{recursive:!0}),yn(V,JSON.stringify(i,null,2)+`
|
|
11
|
+
`,"utf-8"),await g(s(Rr,{item:n,status:r},void 0,!1,void 0,this))});u.command("summary").description("Print the last scan summary").option("--json","print JSON instead of Ink output").action(async(e)=>{let r=M();if(e.json)return Q(r.summary);await g(s(xe,{inventory:r,path:v},void 0,!1,void 0,this))});u.command("doctor").description("Check Atelier CLI, UI, inventory, and local environment health").option("--json","print JSON instead of Ink output").action(async(e)=>{let r=Tn();if(e.json)Q(r);else await g(s(Sr,{checks:r},void 0,!1,void 0,this));if(r.some((t)=>t.status==="fail"))process.exitCode=1});u.parseAsync(process.argv).catch((e)=>{console.error(e instanceof Error?e.message:e),process.exit(1)});function Q(e){console.log(JSON.stringify(e,null,2))}function M(){if(!x(v))throw new Error("No inventory found. Run `atl scan` first.");return JSON.parse(ke(v,"utf-8"))}function Lr(){if(!x(V))return{version:1,decisions:[]};return JSON.parse(ke(V,"utf-8"))}function zn(){if(process.env.ATELIER_STATE_DIR)return process.env.ATELIER_STATE_DIR;if(process.env.XDG_STATE_HOME)return p(process.env.XDG_STATE_HOME,"atelier");if(wn()==="win32"&&process.env.LOCALAPPDATA)return p(process.env.LOCALAPPDATA,"Atelier");return p(bn(),".local","state","atelier")}async function Qr(e){let r=M(),t=Ze(r,e);if(!t){await g(s(q,{query:e},void 0,!1,void 0,this)),process.exitCode=1;return}let n=await be(),i;if(!n.initialized)i=await Nr("Create an Atelier vault passphrase: ");else if(!n.unlocked)i=await E("Vault passphrase: ");let l=await cr(t,{passphrase:i});await g(s(Mr,{stableId:l.stableId,profileName:l.profileName,versionHash:l.version.contentSha256,sizeBytes:l.version.sizeBytes,reused:l.reused},void 0,!1,void 0,this))}async function Nr(e){let r=await E(e),t=await E("Confirm vault passphrase: ");if(r!==t)throw new Error("Vault passphrases do not match");return r}async function E(e){if(!C.isTTY)return F.write(e),Xr??=ke(0,"utf-8").split(/\r?\n/),Xr.shift()??"";F.write(e),C.setRawMode(!0),C.resume(),C.setEncoding("utf-8");let r="";try{for await(let t of C){let n=String(t);for(let i of n){if(i==="\x03")F.write(`
|
|
12
|
+
`),process.exit(130);if(i==="\r"||i===`
|
|
13
|
+
`)return F.write(`
|
|
14
|
+
`),r;if(i==="\x7F"){r=r.slice(0,-1);continue}r+=i}}}finally{C.setRawMode(!1),C.pause()}return r}function Kn(e,r){return e.filter((t)=>{if(r.domain&&t.domain.toLowerCase()!==r.domain.toLowerCase())return!1;if(r.app&&t.app.toLowerCase()!==r.app.toLowerCase())return!1;if(r.secrets&&t.secretFindings.length===0)return!1;if(r.drift&&!t.mirrors.some((n)=>n.exists&&n.identical===!1))return!1;if(r.generated&&t.kind!=="generated")return!1;if(r.private&&t.shareability!=="private")return!1;if(r.missing&&t.exists)return!1;return!0})}function Ze(e,r){let t=Ue(r);return e.items.find((n)=>n.id===r||Ue(n.path)===t||Ue(n.displayPath)===t)??e.items.find((n)=>n.displayPath.includes(r)||n.path.includes(r))}function Ue(e){return e.replace(/^~(?=\/|$)/,process.env.HOME??"").toLowerCase()}function Tn(){let e=[],r=x(v)?M():void 0,t=P("bun",["--version"]),n=P("node",["--version"]),i=P("which",["atl"]),l=P("git",["status","--porcelain"],w);return e.push({label:"repo",status:x(p(w,"package.json"))?"pass":"fail",detail:w}),e.push({label:"bun",status:t?"pass":"fail",detail:t?`v${t}`:"not found"}),e.push({label:"node for Vite",status:n&&Yr(n)?"pass":"warn",detail:n?`${n}${Yr(n)?"":" \u2014 Vite wants 20.19+ or 22.12+"}`:"not found"}),e.push({label:"global atl",status:i?i.includes(".bun")||i.includes(w)?"pass":"warn":"fail",detail:i??"not linked; run `bun link` from the repo"}),e.push({label:"inventory",status:r?"pass":"warn",detail:r?`${r.summary.existing}/${r.summary.total} existing, generated ${r.generatedAt}`:"missing; run `atl scan`"}),e.push({label:"secret warnings",status:r&&r.summary.secrets>0?"warn":"pass",detail:r?`${r.summary.secrets} item${r.summary.secrets===1?"":"s"} flagged`:"no inventory"}),e.push({label:"ui dependencies",status:x(p(w,"node_modules","@tanstack","react-start"))?"pass":"fail",detail:x(p(w,"node_modules","@tanstack","react-start"))?"installed":"missing; run `bun install`"}),e.push({label:"git state",status:l===void 0?"warn":l.trim()?"warn":"pass",detail:l===void 0?"not a git repo":l.trim()?`${l.trim().split(`
|
|
15
|
+
`).length} changed file(s)`:"clean"}),e}function P(e,r,t=w){try{return un(e,r,{cwd:t,encoding:"utf-8",stdio:["ignore","pipe","ignore"]}).trim()}catch{return}}function Yr(e){let r=e.match(/^v?(\d+)\.(\d+)\.(\d+)/);if(!r)return!1;let[,t,n]=r,i=Number(t),l=Number(n);if(i===20)return l>=19;if(i===22)return l>=12;return i>22}async function $n(e){let r=process.env.ATELIER_API_SECRET??crypto.randomUUID(),t=Bun.spawn(["bun","src/index.ts"],{cwd:p(w,"apps","api"),env:{...process.env,PORT:String(e),ATELIER_API_SECRET:r},stdin:"inherit",stdout:"inherit",stderr:"inherit"});console.log(`Atelier API: http://localhost:${e}`);let n=await t.exited;process.exit(n)}async function An(e){let r=Bun.spawn(["bun","--bun","vite","dev","--host","127.0.0.1","--port",String(e)],{cwd:p(w,"apps","ui"),env:{...process.env,ATELIER_REPO_ROOT:w},stdin:"inherit",stdout:"inherit",stderr:"inherit"});console.log(`Atelier UI: http://localhost:${e}`);let t=await r.exited;process.exit(t)}
|