arbella 0.1.1 → 0.1.3
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/LICENSE +661 -21
- package/README.md +20 -6
- package/dist/index.js +692 -220
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -195,6 +195,15 @@ function toolHomeDir(tool) {
|
|
|
195
195
|
return path2.join(homeDir(), ".cursor");
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
|
+
function appUserConfigRoot(targetOS, userHome, env = {}) {
|
|
199
|
+
if (targetOS === "darwin") {
|
|
200
|
+
return path2.join(userHome, "Library", "Application Support");
|
|
201
|
+
}
|
|
202
|
+
if (targetOS === "win32") {
|
|
203
|
+
return env.APPDATA ?? path2.join(userHome, "AppData", "Roaming");
|
|
204
|
+
}
|
|
205
|
+
return env.XDG_CONFIG_HOME ?? path2.join(userHome, ".config");
|
|
206
|
+
}
|
|
198
207
|
function linuxPackageManagerCandidates() {
|
|
199
208
|
return LINUX_PM_PROBES;
|
|
200
209
|
}
|
|
@@ -899,6 +908,16 @@ var init_denylist = __esm({
|
|
|
899
908
|
}
|
|
900
909
|
});
|
|
901
910
|
|
|
911
|
+
// src/utils/symlink.ts
|
|
912
|
+
function normalizeCapturedSymlinkTarget(target) {
|
|
913
|
+
return target.replace(/\\/g, "/");
|
|
914
|
+
}
|
|
915
|
+
var init_symlink = __esm({
|
|
916
|
+
"src/utils/symlink.ts"() {
|
|
917
|
+
"use strict";
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
|
|
902
921
|
// src/adapters/claude/plugins.ts
|
|
903
922
|
function isRecord(v) {
|
|
904
923
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -1004,7 +1023,7 @@ async function walk(ctx, home4, abs, deny, files, symlinks, warnings) {
|
|
|
1004
1023
|
return;
|
|
1005
1024
|
}
|
|
1006
1025
|
if (kind === "symlink") {
|
|
1007
|
-
const target = await ctx.fs.readLink(abs);
|
|
1026
|
+
const target = normalizeCapturedSymlinkTarget(await ctx.fs.readLink(abs));
|
|
1008
1027
|
symlinks.push({ repoPath: repoPathFor(rel), target });
|
|
1009
1028
|
ctx.log.debug(`claude: symlink ${rel} -> ${target}`);
|
|
1010
1029
|
return;
|
|
@@ -1068,7 +1087,7 @@ async function captureSkills(ctx, home4, skillsDir, deny, files, symlinks, skill
|
|
|
1068
1087
|
}
|
|
1069
1088
|
const kind = await ctx.fs.statKind(abs);
|
|
1070
1089
|
if (kind === "symlink") {
|
|
1071
|
-
const target = await ctx.fs.readLink(abs);
|
|
1090
|
+
const target = normalizeCapturedSymlinkTarget(await ctx.fs.readLink(abs));
|
|
1072
1091
|
symlinks.push({ repoPath: repoPathFor(rel), target });
|
|
1073
1092
|
skills.push({
|
|
1074
1093
|
name,
|
|
@@ -1166,6 +1185,7 @@ var init_capture = __esm({
|
|
|
1166
1185
|
"src/adapters/claude/capture.ts"() {
|
|
1167
1186
|
"use strict";
|
|
1168
1187
|
init_denylist();
|
|
1188
|
+
init_symlink();
|
|
1169
1189
|
init_install();
|
|
1170
1190
|
init_paths();
|
|
1171
1191
|
init_plugins();
|
|
@@ -1849,7 +1869,7 @@ async function captureFile2(ctx, absPath, rel, out2) {
|
|
|
1849
1869
|
async function captureSymlink(ctx, absPath, rel, out2) {
|
|
1850
1870
|
let target;
|
|
1851
1871
|
try {
|
|
1852
|
-
target = await ctx.fs.readLink(absPath);
|
|
1872
|
+
target = normalizeCapturedSymlinkTarget(await ctx.fs.readLink(absPath));
|
|
1853
1873
|
} catch {
|
|
1854
1874
|
out2.warnings.push(`codex: could not read symlink ${rel}; skipped`);
|
|
1855
1875
|
return;
|
|
@@ -2007,6 +2027,7 @@ var init_capture2 = __esm({
|
|
|
2007
2027
|
"use strict";
|
|
2008
2028
|
init_denylist();
|
|
2009
2029
|
init_install();
|
|
2030
|
+
init_symlink();
|
|
2010
2031
|
init_paths2();
|
|
2011
2032
|
init_configToml();
|
|
2012
2033
|
DENY = denylistFor("codex");
|
|
@@ -2259,38 +2280,54 @@ function paths3(overrideHome) {
|
|
|
2259
2280
|
return {
|
|
2260
2281
|
home: base,
|
|
2261
2282
|
mcpJson: path10.join(base, "mcp.json"),
|
|
2262
|
-
skillsDir: path10.join(base, "skills")
|
|
2263
|
-
rulesDir: path10.join(base, "rules")
|
|
2283
|
+
skillsDir: path10.join(base, "skills")
|
|
2264
2284
|
};
|
|
2265
2285
|
}
|
|
2266
|
-
function
|
|
2267
|
-
|
|
2286
|
+
function cursorUserPaths(toolHome, os2, env = {}) {
|
|
2287
|
+
const userHome = path10.dirname(toolHome);
|
|
2288
|
+
const userDir = path10.join(appUserConfigRoot(os2, userHome, env), "Cursor", "User");
|
|
2289
|
+
return {
|
|
2290
|
+
userDir,
|
|
2291
|
+
settingsJson: path10.join(userDir, "settings.json"),
|
|
2292
|
+
keybindingsJson: path10.join(userDir, "keybindings.json"),
|
|
2293
|
+
snippetsDir: path10.join(userDir, "snippets")
|
|
2294
|
+
};
|
|
2268
2295
|
}
|
|
2269
|
-
var REPO_PREFIX3,
|
|
2296
|
+
var REPO_PREFIX3, USER_REPO_PREFIX, FROZEN_PATHS3;
|
|
2270
2297
|
var init_paths3 = __esm({
|
|
2271
2298
|
"src/adapters/cursor/paths.ts"() {
|
|
2272
2299
|
"use strict";
|
|
2273
2300
|
init_os();
|
|
2274
2301
|
REPO_PREFIX3 = "cursor/files";
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
SHARED_RULE_FILENAME = "arbella-shared-instructions.mdc";
|
|
2302
|
+
USER_REPO_PREFIX = "cursor/user";
|
|
2303
|
+
FROZEN_PATHS3 = ["mcp.json", "skills"];
|
|
2278
2304
|
}
|
|
2279
2305
|
});
|
|
2280
2306
|
|
|
2281
2307
|
// src/adapters/cursor/index.ts
|
|
2282
2308
|
import path11 from "path";
|
|
2309
|
+
import process3 from "process";
|
|
2310
|
+
import { execa as execa4 } from "execa";
|
|
2283
2311
|
function repoPathFor3(rel) {
|
|
2284
2312
|
return `${REPO_PREFIX3}/${rel}`;
|
|
2285
2313
|
}
|
|
2314
|
+
function userRepoPathFor(rel) {
|
|
2315
|
+
return `${USER_REPO_PREFIX}/${rel}`;
|
|
2316
|
+
}
|
|
2286
2317
|
function toRel2(home4, abs) {
|
|
2287
2318
|
return path11.relative(home4, abs).split(path11.sep).join("/");
|
|
2288
2319
|
}
|
|
2289
|
-
function
|
|
2320
|
+
function targetFromRepoPath(repoPath) {
|
|
2290
2321
|
const norm = repoPath.replace(/\\/g, "/");
|
|
2291
|
-
const
|
|
2292
|
-
if (
|
|
2293
|
-
|
|
2322
|
+
const homePrefix = `${REPO_PREFIX3}/`;
|
|
2323
|
+
if (norm.startsWith(homePrefix)) return { root: "home", rel: norm.slice(homePrefix.length) };
|
|
2324
|
+
const userPrefix = `${USER_REPO_PREFIX}/`;
|
|
2325
|
+
if (norm.startsWith(userPrefix)) return { root: "user", rel: norm.slice(userPrefix.length) };
|
|
2326
|
+
return void 0;
|
|
2327
|
+
}
|
|
2328
|
+
function targetAbsFor2(ctx, target) {
|
|
2329
|
+
const root = target.root === "home" ? ctx.toolHome : cursorUserPaths(ctx.toolHome, ctx.os, ctx.env).userDir;
|
|
2330
|
+
return path11.join(root, ...target.rel.split("/").filter(Boolean));
|
|
2294
2331
|
}
|
|
2295
2332
|
function looksBinary2(buf) {
|
|
2296
2333
|
const n = Math.min(buf.length, 8e3);
|
|
@@ -2333,72 +2370,250 @@ function collectMcpSecretRefs(ctx, parsed, source) {
|
|
|
2333
2370
|
}
|
|
2334
2371
|
return refs;
|
|
2335
2372
|
}
|
|
2336
|
-
async function
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
const deny = denylistFor("cursor");
|
|
2344
|
-
if (await ctx.fs.statKind(p.home) !== "dir") {
|
|
2345
|
-
warnings.push("cursor: ~/.cursor not found; skipping (Cursor not installed?).");
|
|
2346
|
-
return { tool: "cursor", files, symlinks, manifest, secrets, warnings };
|
|
2373
|
+
async function readMode(abs) {
|
|
2374
|
+
try {
|
|
2375
|
+
const { promises: fsp5 } = await import("fs");
|
|
2376
|
+
const st = await fsp5.stat(abs);
|
|
2377
|
+
return st.mode & 511;
|
|
2378
|
+
} catch {
|
|
2379
|
+
return void 0;
|
|
2347
2380
|
}
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2381
|
+
}
|
|
2382
|
+
async function captureFile3(args) {
|
|
2383
|
+
const { ctx, abs, rel, repoPath, files, secrets, warnings, scanMcpSecrets = false } = args;
|
|
2384
|
+
const mode = await readMode(abs);
|
|
2385
|
+
let bytes;
|
|
2386
|
+
try {
|
|
2387
|
+
bytes = await ctx.fs.readBytes(abs);
|
|
2388
|
+
} catch (err) {
|
|
2389
|
+
warnings.push(`cursor: could not read ${rel}: ${err.message}`);
|
|
2390
|
+
return;
|
|
2391
|
+
}
|
|
2392
|
+
if (looksBinary2(bytes)) {
|
|
2393
|
+
const file2 = {
|
|
2394
|
+
repoPath,
|
|
2395
|
+
content: bytes.toString("base64"),
|
|
2396
|
+
binary: true
|
|
2397
|
+
};
|
|
2398
|
+
if (mode !== void 0) file2.mode = mode;
|
|
2399
|
+
files.push(file2);
|
|
2400
|
+
return;
|
|
2401
|
+
}
|
|
2402
|
+
const raw = bytes.toString("utf8");
|
|
2403
|
+
if (scanMcpSecrets) {
|
|
2404
|
+
try {
|
|
2405
|
+
secrets.push(...collectMcpSecretRefs(ctx, JSON.parse(raw), rel));
|
|
2406
|
+
} catch (err) {
|
|
2407
|
+
warnings.push(`cursor: could not parse ${rel} for secret scan: ${err.message}`);
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
let content = raw;
|
|
2411
|
+
if (!ctx.includeSecrets) {
|
|
2412
|
+
const sanitized = ctx.sanitizer.sanitizeFile(raw, "cursor", rel);
|
|
2413
|
+
content = sanitized.content;
|
|
2414
|
+
secrets.push(...sanitized.found);
|
|
2415
|
+
}
|
|
2416
|
+
const templated = ctx.templater.toTemplate(content, ctx.vars);
|
|
2417
|
+
const file = { repoPath, content: templated };
|
|
2418
|
+
if (mode !== void 0) file.mode = mode;
|
|
2419
|
+
files.push(file);
|
|
2420
|
+
}
|
|
2421
|
+
async function walkFrozen(ctx, root, abs, deny, repoPathBuilder, files, symlinks, secrets, warnings) {
|
|
2422
|
+
const kind = await ctx.fs.statKind(abs);
|
|
2423
|
+
if (kind === "missing") return;
|
|
2424
|
+
const rel = toRel2(root, abs);
|
|
2425
|
+
if (rel && matchesDeny(rel, deny)) {
|
|
2426
|
+
ctx.log.debug(`cursor: skip (denylist) ${rel}`);
|
|
2427
|
+
return;
|
|
2428
|
+
}
|
|
2429
|
+
if (kind === "symlink") {
|
|
2430
|
+
const target = normalizeCapturedSymlinkTarget(await ctx.fs.readLink(abs));
|
|
2431
|
+
symlinks.push({ repoPath: repoPathBuilder(rel), target });
|
|
2432
|
+
ctx.log.debug(`cursor: symlink ${rel} -> ${target}`);
|
|
2433
|
+
return;
|
|
2434
|
+
}
|
|
2435
|
+
if (kind === "dir") {
|
|
2436
|
+
const entries = await ctx.fs.list(abs);
|
|
2437
|
+
entries.sort();
|
|
2438
|
+
for (const name of entries) {
|
|
2439
|
+
await walkFrozen(ctx, root, path11.join(abs, name), deny, repoPathBuilder, files, symlinks, secrets, warnings);
|
|
2354
2440
|
}
|
|
2441
|
+
return;
|
|
2442
|
+
}
|
|
2443
|
+
await captureFile3({
|
|
2444
|
+
ctx,
|
|
2445
|
+
abs,
|
|
2446
|
+
rel,
|
|
2447
|
+
repoPath: repoPathBuilder(rel),
|
|
2448
|
+
files,
|
|
2449
|
+
secrets,
|
|
2450
|
+
warnings,
|
|
2451
|
+
scanMcpSecrets: rel === "mcp.json"
|
|
2452
|
+
});
|
|
2453
|
+
}
|
|
2454
|
+
async function captureSkills3(ctx, home4, skillsDir, deny, files, symlinks, skills, secrets, warnings) {
|
|
2455
|
+
if (await ctx.fs.statKind(skillsDir) !== "dir") return;
|
|
2456
|
+
const entries = await ctx.fs.list(skillsDir);
|
|
2457
|
+
entries.sort();
|
|
2458
|
+
for (const name of entries) {
|
|
2459
|
+
const abs = path11.join(skillsDir, name);
|
|
2460
|
+
const rel = toRel2(home4, abs);
|
|
2461
|
+
if (matchesDeny(rel, deny)) continue;
|
|
2355
2462
|
const kind = await ctx.fs.statKind(abs);
|
|
2356
|
-
if (kind === "
|
|
2357
|
-
ctx.
|
|
2463
|
+
if (kind === "symlink") {
|
|
2464
|
+
const target = normalizeCapturedSymlinkTarget(await ctx.fs.readLink(abs));
|
|
2465
|
+
symlinks.push({ repoPath: repoPathFor3(rel), target });
|
|
2466
|
+
skills.push({
|
|
2467
|
+
name,
|
|
2468
|
+
source: "skills.sh",
|
|
2469
|
+
installCommand: `npx skills add ${name}`,
|
|
2470
|
+
symlinked: true
|
|
2471
|
+
});
|
|
2358
2472
|
continue;
|
|
2359
2473
|
}
|
|
2360
|
-
if (kind
|
|
2361
|
-
|
|
2474
|
+
if (kind === "dir") {
|
|
2475
|
+
skills.push({ name, source: "frozen", symlinked: false });
|
|
2476
|
+
await walkFrozen(ctx, home4, abs, deny, repoPathFor3, files, symlinks, secrets, warnings);
|
|
2362
2477
|
continue;
|
|
2363
2478
|
}
|
|
2364
|
-
|
|
2479
|
+
if (kind === "file") {
|
|
2480
|
+
skills.push({ name, source: "frozen", symlinked: false });
|
|
2481
|
+
await walkFrozen(ctx, home4, abs, deny, repoPathFor3, files, symlinks, secrets, warnings);
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
function parseExtensionId(value) {
|
|
2486
|
+
if (typeof value !== "string") return void 0;
|
|
2487
|
+
const trimmed = value.trim();
|
|
2488
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]*\.[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(trimmed)) return void 0;
|
|
2489
|
+
return trimmed;
|
|
2490
|
+
}
|
|
2491
|
+
function parseCursorExtensionEntry(entry) {
|
|
2492
|
+
if (entry === null || typeof entry !== "object") return void 0;
|
|
2493
|
+
const obj = entry;
|
|
2494
|
+
const identifier = obj.identifier;
|
|
2495
|
+
const identifierObj = identifier !== null && typeof identifier === "object" ? identifier : {};
|
|
2496
|
+
const id = parseExtensionId(identifierObj.id) ?? parseExtensionId(obj.id);
|
|
2497
|
+
if (id === void 0) return void 0;
|
|
2498
|
+
const version = typeof obj.version === "string" ? obj.version : void 0;
|
|
2499
|
+
return {
|
|
2500
|
+
id,
|
|
2501
|
+
name: id,
|
|
2502
|
+
version,
|
|
2503
|
+
enabled: true,
|
|
2504
|
+
scope: "user"
|
|
2505
|
+
};
|
|
2506
|
+
}
|
|
2507
|
+
function parseCursorExtensionsJson(raw) {
|
|
2508
|
+
let parsed;
|
|
2509
|
+
try {
|
|
2510
|
+
parsed = JSON.parse(raw);
|
|
2511
|
+
} catch {
|
|
2512
|
+
return [];
|
|
2513
|
+
}
|
|
2514
|
+
if (!Array.isArray(parsed)) return [];
|
|
2515
|
+
const out2 = [];
|
|
2516
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2517
|
+
for (const entry of parsed) {
|
|
2518
|
+
const plugin = parseCursorExtensionEntry(entry);
|
|
2519
|
+
if (plugin === void 0 || seen.has(plugin.id)) continue;
|
|
2520
|
+
seen.add(plugin.id);
|
|
2521
|
+
out2.push(plugin);
|
|
2522
|
+
}
|
|
2523
|
+
return out2;
|
|
2524
|
+
}
|
|
2525
|
+
function parseCursorExtensionLine(line) {
|
|
2526
|
+
const trimmed = line.trim();
|
|
2527
|
+
if (trimmed.length === 0) return void 0;
|
|
2528
|
+
const at = trimmed.lastIndexOf("@");
|
|
2529
|
+
const id = at > 0 ? trimmed.slice(0, at) : trimmed;
|
|
2530
|
+
const version = at > 0 ? trimmed.slice(at + 1) : void 0;
|
|
2531
|
+
const parsedId = parseExtensionId(id);
|
|
2532
|
+
if (parsedId === void 0) return void 0;
|
|
2533
|
+
return {
|
|
2534
|
+
id: parsedId,
|
|
2535
|
+
name: parsedId,
|
|
2536
|
+
version: version && version.length > 0 ? version : void 0,
|
|
2537
|
+
enabled: true,
|
|
2538
|
+
scope: "user"
|
|
2539
|
+
};
|
|
2540
|
+
}
|
|
2541
|
+
async function captureExtensions(ctx, cursorHome, warnings) {
|
|
2542
|
+
const extensionState = path11.join(cursorHome, "extensions", "extensions.json");
|
|
2543
|
+
if (await ctx.fs.statKind(extensionState) === "file") {
|
|
2365
2544
|
try {
|
|
2366
|
-
|
|
2545
|
+
const fromState = parseCursorExtensionsJson(await ctx.fs.read(extensionState));
|
|
2546
|
+
if (fromState.length > 0) return fromState;
|
|
2367
2547
|
} catch (err) {
|
|
2368
|
-
warnings.push(`cursor: could not read
|
|
2369
|
-
continue;
|
|
2370
|
-
}
|
|
2371
|
-
if (looksBinary2(bytes)) {
|
|
2372
|
-
files.push({ repoPath: repoPathFor3(relPosix), content: bytes.toString("base64"), binary: true });
|
|
2373
|
-
continue;
|
|
2548
|
+
warnings.push(`cursor: could not read extensions metadata: ${err.message}`);
|
|
2374
2549
|
}
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2550
|
+
}
|
|
2551
|
+
if (!await which(cliBinaryName("cursor"))) return [];
|
|
2552
|
+
try {
|
|
2553
|
+
const result = await execa4(cliBinaryName("cursor"), ["--list-extensions", "--show-versions"], {
|
|
2554
|
+
stdin: "ignore",
|
|
2555
|
+
stderr: "ignore",
|
|
2556
|
+
stdout: "pipe",
|
|
2557
|
+
reject: false
|
|
2558
|
+
});
|
|
2559
|
+
const plugins = result.stdout.split(/\r?\n/).map(parseCursorExtensionLine).filter((plugin) => plugin !== void 0);
|
|
2560
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2561
|
+
return plugins.filter((plugin) => {
|
|
2562
|
+
if (seen.has(plugin.id)) return false;
|
|
2563
|
+
seen.add(plugin.id);
|
|
2564
|
+
return true;
|
|
2565
|
+
});
|
|
2566
|
+
} catch (err) {
|
|
2567
|
+
warnings.push(`cursor: could not list extensions via CLI: ${err.message}`);
|
|
2568
|
+
return [];
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
async function capture4(ctx, _opts) {
|
|
2572
|
+
const files = [];
|
|
2573
|
+
const symlinks = [];
|
|
2574
|
+
const secrets = [];
|
|
2575
|
+
const warnings = [];
|
|
2576
|
+
const manifest = emptyCursorManifest();
|
|
2577
|
+
const p = paths3(ctx.toolHome);
|
|
2578
|
+
const userPaths = cursorUserPaths(p.home, ctx.os, ctx.env);
|
|
2579
|
+
const deny = denylistFor("cursor");
|
|
2580
|
+
const hasToolHome = await ctx.fs.statKind(p.home) === "dir";
|
|
2581
|
+
const hasUserDir = await ctx.fs.statKind(userPaths.userDir) === "dir";
|
|
2582
|
+
if (!hasToolHome && !hasUserDir) {
|
|
2583
|
+
warnings.push("cursor: no ~/.cursor or Cursor User data found; skipping (Cursor not installed?).");
|
|
2584
|
+
return { tool: "cursor", files, symlinks, manifest, secrets, warnings };
|
|
2585
|
+
}
|
|
2586
|
+
if (hasToolHome) {
|
|
2587
|
+
for (const rel of FROZEN_PATHS3) {
|
|
2588
|
+
const abs = path11.join(p.home, rel);
|
|
2589
|
+
if (rel === "skills") {
|
|
2590
|
+
await captureSkills3(ctx, p.home, abs, deny, files, symlinks, manifest.skills, secrets, warnings);
|
|
2591
|
+
} else {
|
|
2592
|
+
await walkFrozen(ctx, p.home, abs, deny, repoPathFor3, files, symlinks, secrets, warnings);
|
|
2381
2593
|
}
|
|
2382
2594
|
}
|
|
2383
|
-
|
|
2384
|
-
const templated = ctx.templater.toTemplate(content, ctx.vars);
|
|
2385
|
-
files.push({ repoPath: repoPathFor3(relPosix), content: templated });
|
|
2386
|
-
ctx.log.debug(`cursor: froze ${relPosix}`);
|
|
2595
|
+
manifest.plugins = await captureExtensions(ctx, p.home, warnings);
|
|
2387
2596
|
}
|
|
2388
|
-
if (
|
|
2389
|
-
|
|
2597
|
+
if (hasUserDir) {
|
|
2598
|
+
const userItems = [userPaths.settingsJson, userPaths.keybindingsJson, userPaths.snippetsDir];
|
|
2599
|
+
for (const abs of userItems) {
|
|
2600
|
+
await walkFrozen(ctx, userPaths.userDir, abs, deny, userRepoPathFor, files, symlinks, secrets, warnings);
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
if (files.length === 0 && symlinks.length === 0 && manifest.plugins.length === 0) {
|
|
2604
|
+
warnings.push("cursor: present but nothing portable to capture.");
|
|
2390
2605
|
}
|
|
2391
2606
|
return { tool: "cursor", files, symlinks, manifest, secrets, warnings };
|
|
2392
2607
|
}
|
|
2393
2608
|
async function writeRestoredFile(ctx, file) {
|
|
2394
|
-
const
|
|
2395
|
-
if (
|
|
2609
|
+
const target = targetFromRepoPath(file.repoPath);
|
|
2610
|
+
if (target === void 0) {
|
|
2396
2611
|
ctx.log.debug(`cursor: ignoring foreign repoPath ${file.repoPath}`);
|
|
2397
2612
|
return;
|
|
2398
2613
|
}
|
|
2399
|
-
const dest =
|
|
2614
|
+
const dest = targetAbsFor2(ctx, target);
|
|
2400
2615
|
if (ctx.sourceOfTruth === "local" && await ctx.fs.exists(dest)) {
|
|
2401
|
-
ctx.log.debug(`cursor: keep local ${rel} (sourceOfTruth=local)`);
|
|
2616
|
+
ctx.log.debug(`cursor: keep local ${target.rel} (sourceOfTruth=local)`);
|
|
2402
2617
|
return;
|
|
2403
2618
|
}
|
|
2404
2619
|
if (file.binary) {
|
|
@@ -2408,33 +2623,93 @@ async function writeRestoredFile(ctx, file) {
|
|
|
2408
2623
|
const expanded = ctx.templater.fromTemplate(file.content, ctx.vars);
|
|
2409
2624
|
await ctx.fs.write(dest, expanded, file.mode);
|
|
2410
2625
|
}
|
|
2411
|
-
async function
|
|
2412
|
-
const
|
|
2413
|
-
if (
|
|
2414
|
-
ctx.log.debug(
|
|
2626
|
+
async function writeRestoredSymlink(ctx, link) {
|
|
2627
|
+
const target = targetFromRepoPath(link.repoPath);
|
|
2628
|
+
if (target === void 0) {
|
|
2629
|
+
ctx.log.debug(`cursor: ignoring foreign symlink ${link.repoPath}`);
|
|
2415
2630
|
return;
|
|
2416
2631
|
}
|
|
2417
|
-
const
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2632
|
+
const dest = targetAbsFor2(ctx, target);
|
|
2633
|
+
if (ctx.sourceOfTruth === "local" && await ctx.fs.statKind(dest) !== "missing") {
|
|
2634
|
+
ctx.log.debug(`cursor: keep local symlink ${target.rel} (sourceOfTruth=local)`);
|
|
2635
|
+
return;
|
|
2636
|
+
}
|
|
2637
|
+
await ctx.fs.symlink(link.target, dest);
|
|
2638
|
+
}
|
|
2639
|
+
async function reinstallExtensions(ctx, plugins) {
|
|
2640
|
+
const userPlugins = plugins.filter((plugin) => plugin.scope === "user");
|
|
2641
|
+
if (userPlugins.length === 0) return;
|
|
2642
|
+
const cursorBin = cliBinaryName("cursor");
|
|
2643
|
+
if (!await which(cursorBin)) {
|
|
2644
|
+
ctx.log.warn(
|
|
2645
|
+
"cursor CLI not found; extension IDs were restored in manifest.json, but extensions were not installed."
|
|
2646
|
+
);
|
|
2647
|
+
return;
|
|
2648
|
+
}
|
|
2649
|
+
for (const plugin of userPlugins) {
|
|
2650
|
+
try {
|
|
2651
|
+
const result = await execa4(cursorBin, ["--install-extension", plugin.id], {
|
|
2652
|
+
stdin: "ignore",
|
|
2653
|
+
stdout: "ignore",
|
|
2654
|
+
stderr: "pipe",
|
|
2655
|
+
reject: false
|
|
2656
|
+
});
|
|
2657
|
+
if (result.exitCode === 0) {
|
|
2658
|
+
ctx.log.step(`cursor: installed extension ${plugin.id}`);
|
|
2659
|
+
} else {
|
|
2660
|
+
const stderr = result.stderr.trim();
|
|
2661
|
+
ctx.log.warn(
|
|
2662
|
+
`cursor: failed to install extension ${plugin.id}${stderr ? ` (${stderr})` : ""}`
|
|
2663
|
+
);
|
|
2664
|
+
}
|
|
2665
|
+
} catch (err) {
|
|
2666
|
+
ctx.log.warn(`cursor: failed to install extension ${plugin.id}: ${err.message}`);
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
async function planActions3(ctx, data) {
|
|
2671
|
+
const actions = [];
|
|
2672
|
+
for (const file of data.files) {
|
|
2673
|
+
const target = targetFromRepoPath(file.repoPath);
|
|
2674
|
+
if (target === void 0) continue;
|
|
2675
|
+
const targetPath = targetAbsFor2(ctx, target);
|
|
2676
|
+
const overwrites = await ctx.fs.exists(targetPath);
|
|
2677
|
+
if (ctx.sourceOfTruth === "local" && overwrites) continue;
|
|
2678
|
+
actions.push({
|
|
2679
|
+
type: "write-file",
|
|
2680
|
+
tool: "cursor",
|
|
2681
|
+
targetPath,
|
|
2682
|
+
description: `Write ${target.root === "user" ? "User/" : ""}${target.rel}`,
|
|
2683
|
+
overwrites
|
|
2684
|
+
});
|
|
2685
|
+
}
|
|
2686
|
+
for (const link of data.symlinks) {
|
|
2687
|
+
const target = targetFromRepoPath(link.repoPath);
|
|
2688
|
+
if (target === void 0) continue;
|
|
2689
|
+
const targetPath = targetAbsFor2(ctx, target);
|
|
2690
|
+
const overwrites = await ctx.fs.statKind(targetPath) !== "missing";
|
|
2691
|
+
if (ctx.sourceOfTruth === "local" && overwrites) continue;
|
|
2692
|
+
actions.push({
|
|
2693
|
+
type: "write-symlink",
|
|
2694
|
+
tool: "cursor",
|
|
2695
|
+
targetPath,
|
|
2696
|
+
description: `Link ${target.root === "user" ? "User/" : ""}${target.rel} -> ${link.target}`,
|
|
2697
|
+
overwrites
|
|
2698
|
+
});
|
|
2699
|
+
}
|
|
2700
|
+
for (const plugin of data.manifest.plugins.filter((p) => p.scope === "user")) {
|
|
2701
|
+
actions.push({
|
|
2702
|
+
type: "install-plugin",
|
|
2703
|
+
tool: "cursor",
|
|
2704
|
+
description: `cursor --install-extension ${plugin.id}`
|
|
2705
|
+
});
|
|
2706
|
+
}
|
|
2707
|
+
return actions;
|
|
2427
2708
|
}
|
|
2428
2709
|
async function restore4(ctx, data) {
|
|
2429
2710
|
if (ctx.dryRun) {
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
if (rel) ctx.log.step(`cursor: would write ${path11.join(ctx.toolHome, ...rel.split("/"))}`);
|
|
2433
|
-
}
|
|
2434
|
-
const sharedAbs = path11.join(ctx.repoRoot, ...SHARED_INSTRUCTIONS_REPO_PATH.split("/"));
|
|
2435
|
-
if (await ctx.fs.exists(sharedAbs)) {
|
|
2436
|
-
ctx.log.step(`cursor: would write shared-instructions rule -> ${sharedRulePath(ctx.toolHome)}`);
|
|
2437
|
-
}
|
|
2711
|
+
const actions = await planActions3(ctx, data);
|
|
2712
|
+
for (const action of actions) ctx.log.step(action.description);
|
|
2438
2713
|
return;
|
|
2439
2714
|
}
|
|
2440
2715
|
for (const file of data.files) {
|
|
@@ -2444,18 +2719,22 @@ async function restore4(ctx, data) {
|
|
|
2444
2719
|
ctx.log.warn(`cursor: failed to write ${file.repoPath}: ${err.message}`);
|
|
2445
2720
|
}
|
|
2446
2721
|
}
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2722
|
+
for (const link of data.symlinks) {
|
|
2723
|
+
try {
|
|
2724
|
+
await writeRestoredSymlink(ctx, link);
|
|
2725
|
+
} catch (err) {
|
|
2726
|
+
ctx.log.warn(`cursor: failed to create symlink ${link.repoPath}: ${err.message}`);
|
|
2727
|
+
}
|
|
2451
2728
|
}
|
|
2729
|
+
await reinstallExtensions(ctx, data.manifest.plugins);
|
|
2452
2730
|
}
|
|
2453
2731
|
async function detect2() {
|
|
2454
|
-
|
|
2732
|
+
const p = paths3();
|
|
2733
|
+
return await fsExistsDir(p.home) || await fsExistsDir(cursorUserPaths(p.home, detectOS(), process3.env).userDir);
|
|
2455
2734
|
}
|
|
2456
2735
|
async function fsExistsDir(p) {
|
|
2457
|
-
const { fs:
|
|
2458
|
-
return await
|
|
2736
|
+
const { fs: fs4 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
2737
|
+
return await fs4.statKind(p) === "dir";
|
|
2459
2738
|
}
|
|
2460
2739
|
async function isCliInstalled3() {
|
|
2461
2740
|
return which(cliBinaryName("cursor"));
|
|
@@ -2476,6 +2755,7 @@ var init_cursor = __esm({
|
|
|
2476
2755
|
init_denylist();
|
|
2477
2756
|
init_os();
|
|
2478
2757
|
init_install();
|
|
2758
|
+
init_symlink();
|
|
2479
2759
|
init_paths3();
|
|
2480
2760
|
cursorAdapter = {
|
|
2481
2761
|
id: "cursor",
|
|
@@ -2497,8 +2777,39 @@ var init_cursor = __esm({
|
|
|
2497
2777
|
}
|
|
2498
2778
|
});
|
|
2499
2779
|
|
|
2500
|
-
// src/core/
|
|
2780
|
+
// src/core/version.ts
|
|
2781
|
+
import fs2 from "fs";
|
|
2501
2782
|
import path12 from "path";
|
|
2783
|
+
import { fileURLToPath } from "url";
|
|
2784
|
+
function getPackageVersion() {
|
|
2785
|
+
if (cachedVersion !== void 0) return cachedVersion;
|
|
2786
|
+
const here = path12.dirname(fileURLToPath(import.meta.url));
|
|
2787
|
+
const candidates = [
|
|
2788
|
+
path12.join(here, "..", "package.json"),
|
|
2789
|
+
path12.join(here, "..", "..", "package.json")
|
|
2790
|
+
];
|
|
2791
|
+
for (const packagePath of candidates) {
|
|
2792
|
+
try {
|
|
2793
|
+
const parsed = JSON.parse(fs2.readFileSync(packagePath, "utf8"));
|
|
2794
|
+
if (typeof parsed.version === "string" && parsed.version.trim() !== "") {
|
|
2795
|
+
cachedVersion = parsed.version;
|
|
2796
|
+
return cachedVersion;
|
|
2797
|
+
}
|
|
2798
|
+
} catch {
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
cachedVersion = "0.0.0";
|
|
2802
|
+
return cachedVersion;
|
|
2803
|
+
}
|
|
2804
|
+
var cachedVersion;
|
|
2805
|
+
var init_version = __esm({
|
|
2806
|
+
"src/core/version.ts"() {
|
|
2807
|
+
"use strict";
|
|
2808
|
+
}
|
|
2809
|
+
});
|
|
2810
|
+
|
|
2811
|
+
// src/core/autobackup/hook.ts
|
|
2812
|
+
import path13 from "path";
|
|
2502
2813
|
function hookCommand() {
|
|
2503
2814
|
if (detectOS() === "win32") {
|
|
2504
2815
|
return `start /b "" arbella push --auto >NUL 2>&1 :: ${HOOK_TAG}`;
|
|
@@ -2548,7 +2859,7 @@ async function writeJsonObject(file, value) {
|
|
|
2548
2859
|
await fs.write(file, JSON.stringify(value, null, 2) + "\n");
|
|
2549
2860
|
}
|
|
2550
2861
|
function claudeSettingsPath() {
|
|
2551
|
-
return
|
|
2862
|
+
return path13.join(toolHomeDir("claude"), "settings.json");
|
|
2552
2863
|
}
|
|
2553
2864
|
async function applyClaude(enable) {
|
|
2554
2865
|
const home4 = toolHomeDir("claude");
|
|
@@ -2573,7 +2884,7 @@ async function applyClaude(enable) {
|
|
|
2573
2884
|
return true;
|
|
2574
2885
|
}
|
|
2575
2886
|
function codexHooksPath() {
|
|
2576
|
-
return
|
|
2887
|
+
return path13.join(toolHomeDir("codex"), "hooks.json");
|
|
2577
2888
|
}
|
|
2578
2889
|
async function applyCodex(enable) {
|
|
2579
2890
|
const home4 = toolHomeDir("codex");
|
|
@@ -2619,7 +2930,7 @@ var init_hook = __esm({
|
|
|
2619
2930
|
});
|
|
2620
2931
|
|
|
2621
2932
|
// src/core/autobackup/throttle.ts
|
|
2622
|
-
import
|
|
2933
|
+
import path14 from "path";
|
|
2623
2934
|
async function readState() {
|
|
2624
2935
|
if (!await fs.exists(STAMP_FILE)) {
|
|
2625
2936
|
return { lastRunIso: null };
|
|
@@ -2662,7 +2973,7 @@ var init_throttle = __esm({
|
|
|
2662
2973
|
"use strict";
|
|
2663
2974
|
init_fs();
|
|
2664
2975
|
init_os();
|
|
2665
|
-
STAMP_FILE =
|
|
2976
|
+
STAMP_FILE = path14.join(dataDir(), "autobackup.json");
|
|
2666
2977
|
MIN_SESSION_GAP_MS = 5 * 60 * 1e3;
|
|
2667
2978
|
DAILY_GAP_MS = 24 * 60 * 60 * 1e3;
|
|
2668
2979
|
}
|
|
@@ -2750,9 +3061,9 @@ var init_schema = __esm({
|
|
|
2750
3061
|
});
|
|
2751
3062
|
|
|
2752
3063
|
// src/core/config/index.ts
|
|
2753
|
-
import
|
|
3064
|
+
import path15 from "path";
|
|
2754
3065
|
function configPath() {
|
|
2755
|
-
return
|
|
3066
|
+
return path15.join(configDir(), "config.json");
|
|
2756
3067
|
}
|
|
2757
3068
|
async function configExists() {
|
|
2758
3069
|
return fs.exists(configPath());
|
|
@@ -2801,7 +3112,7 @@ function defaultConfig() {
|
|
|
2801
3112
|
async function saveConfig(config) {
|
|
2802
3113
|
const valid = arbellaConfigSchema.parse(config);
|
|
2803
3114
|
const file = configPath();
|
|
2804
|
-
await fs.ensureDir(
|
|
3115
|
+
await fs.ensureDir(path15.dirname(file));
|
|
2805
3116
|
await fs.write(file, serializeConfig(valid));
|
|
2806
3117
|
}
|
|
2807
3118
|
function serializeConfig(value) {
|
|
@@ -2837,7 +3148,7 @@ var init_config = __esm({
|
|
|
2837
3148
|
});
|
|
2838
3149
|
|
|
2839
3150
|
// src/core/auth/cli.ts
|
|
2840
|
-
import { execa as
|
|
3151
|
+
import { execa as execa5 } from "execa";
|
|
2841
3152
|
function cliForProvider(provider) {
|
|
2842
3153
|
return CLI_BY_PROVIDER[provider];
|
|
2843
3154
|
}
|
|
@@ -2856,7 +3167,7 @@ async function providerCliAuthStatus(provider, host) {
|
|
|
2856
3167
|
let exitCode = 1;
|
|
2857
3168
|
let combined = "";
|
|
2858
3169
|
try {
|
|
2859
|
-
const res = await
|
|
3170
|
+
const res = await execa5(cli.bin, args, {
|
|
2860
3171
|
reject: false,
|
|
2861
3172
|
// exit code is the signal; never throw on "not logged in".
|
|
2862
3173
|
stdin: "ignore",
|
|
@@ -2899,7 +3210,7 @@ async function providerCliLogin(provider, opts = {}) {
|
|
|
2899
3210
|
);
|
|
2900
3211
|
log.step(`Running: ${cli.bin} ${args.join(" ")}`);
|
|
2901
3212
|
try {
|
|
2902
|
-
await
|
|
3213
|
+
await execa5(cli.bin, args, {
|
|
2903
3214
|
// INHERITED stdio: the user interacts with gh/glab directly. This is the
|
|
2904
3215
|
// whole point — arbella steps out of the way for the actual login.
|
|
2905
3216
|
stdio: "inherit",
|
|
@@ -2942,7 +3253,7 @@ async function providerCliLogout(provider, host) {
|
|
|
2942
3253
|
}
|
|
2943
3254
|
log.step(`Running: ${cli.bin} ${args.join(" ")}`);
|
|
2944
3255
|
try {
|
|
2945
|
-
await
|
|
3256
|
+
await execa5(cli.bin, args, { stdio: "inherit", timeout: 6e4 });
|
|
2946
3257
|
log.success(`${cli.label}: signed out.`);
|
|
2947
3258
|
return true;
|
|
2948
3259
|
} catch (err) {
|
|
@@ -3194,7 +3505,7 @@ var init_device_flow = __esm({
|
|
|
3194
3505
|
});
|
|
3195
3506
|
|
|
3196
3507
|
// src/core/auth/providers.ts
|
|
3197
|
-
import
|
|
3508
|
+
import process4 from "process";
|
|
3198
3509
|
function isPlaceholderClientId(clientId) {
|
|
3199
3510
|
return clientId.trim() === "" || clientId === PLACEHOLDER_CLIENT_ID;
|
|
3200
3511
|
}
|
|
@@ -3240,7 +3551,7 @@ function resolveClientId(spec, overrides) {
|
|
|
3240
3551
|
if (typeof override === "string" && override.trim() !== "") {
|
|
3241
3552
|
return override.trim();
|
|
3242
3553
|
}
|
|
3243
|
-
const fromEnv =
|
|
3554
|
+
const fromEnv = process4.env[spec.clientIdEnvVar];
|
|
3244
3555
|
if (typeof fromEnv === "string" && fromEnv.trim() !== "") {
|
|
3245
3556
|
return fromEnv.trim();
|
|
3246
3557
|
}
|
|
@@ -3301,9 +3612,9 @@ var init_providers = __esm({
|
|
|
3301
3612
|
|
|
3302
3613
|
// src/core/auth/store.ts
|
|
3303
3614
|
import { promises as fsp3 } from "fs";
|
|
3304
|
-
import
|
|
3615
|
+
import path16 from "path";
|
|
3305
3616
|
function credentialsPath() {
|
|
3306
|
-
return
|
|
3617
|
+
return path16.join(dataDir(), "credentials.json");
|
|
3307
3618
|
}
|
|
3308
3619
|
async function loadFile() {
|
|
3309
3620
|
const file = credentialsPath();
|
|
@@ -3358,7 +3669,7 @@ function normalizeCredential(host, value) {
|
|
|
3358
3669
|
}
|
|
3359
3670
|
async function saveFile(data) {
|
|
3360
3671
|
const file = credentialsPath();
|
|
3361
|
-
await fs.ensureDir(
|
|
3672
|
+
await fs.ensureDir(path16.dirname(file));
|
|
3362
3673
|
const json = `${JSON.stringify(data, null, 2)}
|
|
3363
3674
|
`;
|
|
3364
3675
|
await fs.write(file, json, CREDENTIALS_MODE);
|
|
@@ -3832,13 +4143,13 @@ var init_auth = __esm({
|
|
|
3832
4143
|
});
|
|
3833
4144
|
|
|
3834
4145
|
// src/core/repo/git.ts
|
|
3835
|
-
import { execa as
|
|
4146
|
+
import { execa as execa6 } from "execa";
|
|
3836
4147
|
async function git(cwd, args, opts = {}) {
|
|
3837
4148
|
const reject = opts.reject ?? true;
|
|
3838
4149
|
log.debug(`git ${args.map(redactArg).join(" ")} (cwd=${cwd})`);
|
|
3839
4150
|
let res;
|
|
3840
4151
|
try {
|
|
3841
|
-
res = await
|
|
4152
|
+
res = await execa6("git", args, {
|
|
3842
4153
|
cwd,
|
|
3843
4154
|
reject,
|
|
3844
4155
|
// Keep output as strings; do not inherit stdio so we can capture/redact.
|
|
@@ -3921,7 +4232,7 @@ async function hasUpstream(cwd, branch) {
|
|
|
3921
4232
|
async function clone(url, dest) {
|
|
3922
4233
|
log.debug(`git clone <url> -> ${dest}`);
|
|
3923
4234
|
try {
|
|
3924
|
-
await
|
|
4235
|
+
await execa6("git", ["clone", url, dest], {
|
|
3925
4236
|
reject: true,
|
|
3926
4237
|
stdout: "pipe",
|
|
3927
4238
|
stderr: "pipe",
|
|
@@ -4024,10 +4335,10 @@ var init_generic = __esm({
|
|
|
4024
4335
|
});
|
|
4025
4336
|
|
|
4026
4337
|
// src/core/repo/providers/github.ts
|
|
4027
|
-
import { execa as
|
|
4338
|
+
import { execa as execa7 } from "execa";
|
|
4028
4339
|
async function ghPresent() {
|
|
4029
4340
|
try {
|
|
4030
|
-
await
|
|
4341
|
+
await execa7("gh", ["--version"], { reject: true, stdin: "ignore" });
|
|
4031
4342
|
return true;
|
|
4032
4343
|
} catch {
|
|
4033
4344
|
return false;
|
|
@@ -4036,7 +4347,7 @@ async function ghPresent() {
|
|
|
4036
4347
|
async function gh(args) {
|
|
4037
4348
|
log.debug(`gh ${args.join(" ")}`);
|
|
4038
4349
|
try {
|
|
4039
|
-
const res = await
|
|
4350
|
+
const res = await execa7("gh", args, {
|
|
4040
4351
|
reject: true,
|
|
4041
4352
|
stdout: "pipe",
|
|
4042
4353
|
stderr: "pipe",
|
|
@@ -4115,10 +4426,10 @@ var init_github = __esm({
|
|
|
4115
4426
|
});
|
|
4116
4427
|
|
|
4117
4428
|
// src/core/repo/providers/gitlab.ts
|
|
4118
|
-
import { execa as
|
|
4429
|
+
import { execa as execa8 } from "execa";
|
|
4119
4430
|
async function glabPresent() {
|
|
4120
4431
|
try {
|
|
4121
|
-
await
|
|
4432
|
+
await execa8("glab", ["--version"], { reject: true, stdin: "ignore" });
|
|
4122
4433
|
return true;
|
|
4123
4434
|
} catch {
|
|
4124
4435
|
return false;
|
|
@@ -4127,7 +4438,7 @@ async function glabPresent() {
|
|
|
4127
4438
|
async function glab(args) {
|
|
4128
4439
|
log.debug(`glab ${args.join(" ")}`);
|
|
4129
4440
|
try {
|
|
4130
|
-
const res = await
|
|
4441
|
+
const res = await execa8("glab", args, {
|
|
4131
4442
|
reject: true,
|
|
4132
4443
|
stdout: "pipe",
|
|
4133
4444
|
stderr: "pipe",
|
|
@@ -4206,7 +4517,7 @@ var init_gitlab = __esm({
|
|
|
4206
4517
|
});
|
|
4207
4518
|
|
|
4208
4519
|
// src/core/repo/index.ts
|
|
4209
|
-
import
|
|
4520
|
+
import path17 from "path";
|
|
4210
4521
|
async function withRepoAuth(repoUrl, hooks, op) {
|
|
4211
4522
|
try {
|
|
4212
4523
|
await op(repoUrl, false);
|
|
@@ -4281,7 +4592,7 @@ async function ensureLocalClone(repo, auth) {
|
|
|
4281
4592
|
"repo.url is not configured; cannot clone. Run `arbella init` first."
|
|
4282
4593
|
);
|
|
4283
4594
|
}
|
|
4284
|
-
await fs.ensureDir(
|
|
4595
|
+
await fs.ensureDir(path17.dirname(localPath));
|
|
4285
4596
|
log.step(`Cloning backup repo into ${localPath}`);
|
|
4286
4597
|
await withRepoAuth(repo.url, auth, async (url, credentialed) => {
|
|
4287
4598
|
await clone(url, localPath);
|
|
@@ -4433,13 +4744,11 @@ function createSanitizer() {
|
|
|
4433
4744
|
}
|
|
4434
4745
|
function sanitizeFile(content, tool, source) {
|
|
4435
4746
|
if (looksLikeJsonSource(source)) {
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
parsed = JSON.parse(content);
|
|
4439
|
-
} catch {
|
|
4747
|
+
const parsed = parseJsonOrJsonc(content);
|
|
4748
|
+
if (!parsed.ok) {
|
|
4440
4749
|
return sanitizeText(content, tool, source);
|
|
4441
4750
|
}
|
|
4442
|
-
const { value, found } = sanitizeJson(parsed, tool, source);
|
|
4751
|
+
const { value, found } = sanitizeJson(parsed.value, tool, source);
|
|
4443
4752
|
const indent = detectJsonIndent(content);
|
|
4444
4753
|
const serialized = JSON.stringify(value, null, indent);
|
|
4445
4754
|
return { content: serialized, found, changed: serialized !== content };
|
|
@@ -4452,6 +4761,92 @@ function looksLikeJsonSource(source) {
|
|
|
4452
4761
|
const base = source.replace(/\\/g, "/").split("/").pop() ?? source;
|
|
4453
4762
|
return /\.json$/i.test(base);
|
|
4454
4763
|
}
|
|
4764
|
+
function parseJsonOrJsonc(content) {
|
|
4765
|
+
try {
|
|
4766
|
+
return { ok: true, value: JSON.parse(content) };
|
|
4767
|
+
} catch {
|
|
4768
|
+
}
|
|
4769
|
+
try {
|
|
4770
|
+
return { ok: true, value: JSON.parse(stripJsonc(content)) };
|
|
4771
|
+
} catch {
|
|
4772
|
+
return { ok: false };
|
|
4773
|
+
}
|
|
4774
|
+
}
|
|
4775
|
+
function stripJsonc(content) {
|
|
4776
|
+
return removeTrailingCommas(removeJsonComments(content));
|
|
4777
|
+
}
|
|
4778
|
+
function removeJsonComments(content) {
|
|
4779
|
+
let out2 = "";
|
|
4780
|
+
let inString = false;
|
|
4781
|
+
let escaped = false;
|
|
4782
|
+
for (let i = 0; i < content.length; i++) {
|
|
4783
|
+
const ch = content[i];
|
|
4784
|
+
const next = content[i + 1];
|
|
4785
|
+
if (inString) {
|
|
4786
|
+
out2 += ch;
|
|
4787
|
+
if (escaped) {
|
|
4788
|
+
escaped = false;
|
|
4789
|
+
} else if (ch === "\\") {
|
|
4790
|
+
escaped = true;
|
|
4791
|
+
} else if (ch === '"') {
|
|
4792
|
+
inString = false;
|
|
4793
|
+
}
|
|
4794
|
+
continue;
|
|
4795
|
+
}
|
|
4796
|
+
if (ch === '"') {
|
|
4797
|
+
inString = true;
|
|
4798
|
+
out2 += ch;
|
|
4799
|
+
continue;
|
|
4800
|
+
}
|
|
4801
|
+
if (ch === "/" && next === "/") {
|
|
4802
|
+
while (i < content.length && content[i] !== "\n") i++;
|
|
4803
|
+
if (i < content.length) out2 += "\n";
|
|
4804
|
+
continue;
|
|
4805
|
+
}
|
|
4806
|
+
if (ch === "/" && next === "*") {
|
|
4807
|
+
i += 2;
|
|
4808
|
+
while (i < content.length && !(content[i] === "*" && content[i + 1] === "/")) {
|
|
4809
|
+
if (content[i] === "\n") out2 += "\n";
|
|
4810
|
+
i++;
|
|
4811
|
+
}
|
|
4812
|
+
i++;
|
|
4813
|
+
continue;
|
|
4814
|
+
}
|
|
4815
|
+
out2 += ch;
|
|
4816
|
+
}
|
|
4817
|
+
return out2;
|
|
4818
|
+
}
|
|
4819
|
+
function removeTrailingCommas(content) {
|
|
4820
|
+
let out2 = "";
|
|
4821
|
+
let inString = false;
|
|
4822
|
+
let escaped = false;
|
|
4823
|
+
for (let i = 0; i < content.length; i++) {
|
|
4824
|
+
const ch = content[i];
|
|
4825
|
+
if (inString) {
|
|
4826
|
+
out2 += ch;
|
|
4827
|
+
if (escaped) {
|
|
4828
|
+
escaped = false;
|
|
4829
|
+
} else if (ch === "\\") {
|
|
4830
|
+
escaped = true;
|
|
4831
|
+
} else if (ch === '"') {
|
|
4832
|
+
inString = false;
|
|
4833
|
+
}
|
|
4834
|
+
continue;
|
|
4835
|
+
}
|
|
4836
|
+
if (ch === '"') {
|
|
4837
|
+
inString = true;
|
|
4838
|
+
out2 += ch;
|
|
4839
|
+
continue;
|
|
4840
|
+
}
|
|
4841
|
+
if (ch === ",") {
|
|
4842
|
+
let j = i + 1;
|
|
4843
|
+
while (j < content.length && /\s/.test(content[j])) j++;
|
|
4844
|
+
if (content[j] === "}" || content[j] === "]") continue;
|
|
4845
|
+
}
|
|
4846
|
+
out2 += ch;
|
|
4847
|
+
}
|
|
4848
|
+
return out2;
|
|
4849
|
+
}
|
|
4455
4850
|
function detectJsonIndent(content) {
|
|
4456
4851
|
const m = /\n([ \t]+)\S/.exec(content);
|
|
4457
4852
|
if (!m) return 2;
|
|
@@ -4674,6 +5069,7 @@ var init_templater = __esm({
|
|
|
4674
5069
|
|
|
4675
5070
|
// src/commands/_context.ts
|
|
4676
5071
|
import { confirm, isCancel, password } from "@clack/prompts";
|
|
5072
|
+
import process5 from "process";
|
|
4677
5073
|
function buildRepoAuthHooks(args) {
|
|
4678
5074
|
return {
|
|
4679
5075
|
interactive: args.interactive ?? true,
|
|
@@ -5134,7 +5530,7 @@ function shouldShareInstructions(claudeMd, agentsMd) {
|
|
|
5134
5530
|
}
|
|
5135
5531
|
function buildSharedInstructionsFile(content) {
|
|
5136
5532
|
return {
|
|
5137
|
-
repoPath:
|
|
5533
|
+
repoPath: SHARED_INSTRUCTIONS_REPO_PATH,
|
|
5138
5534
|
content
|
|
5139
5535
|
};
|
|
5140
5536
|
}
|
|
@@ -5144,12 +5540,12 @@ function sharedInstructionsTargets() {
|
|
|
5144
5540
|
{ tool: "codex", relPath: "AGENTS.md" }
|
|
5145
5541
|
];
|
|
5146
5542
|
}
|
|
5147
|
-
var
|
|
5543
|
+
var SHARED_INSTRUCTIONS_REPO_PATH;
|
|
5148
5544
|
var init_manifest = __esm({
|
|
5149
5545
|
"src/core/manifest/index.ts"() {
|
|
5150
5546
|
"use strict";
|
|
5151
5547
|
init_schema2();
|
|
5152
|
-
|
|
5548
|
+
SHARED_INSTRUCTIONS_REPO_PATH = "shared/instructions.md";
|
|
5153
5549
|
}
|
|
5154
5550
|
});
|
|
5155
5551
|
|
|
@@ -5159,7 +5555,8 @@ __export(backup_exports, {
|
|
|
5159
5555
|
register: () => register2,
|
|
5160
5556
|
run: () => run2
|
|
5161
5557
|
});
|
|
5162
|
-
import
|
|
5558
|
+
import path18 from "path";
|
|
5559
|
+
import process6 from "process";
|
|
5163
5560
|
function register2(program) {
|
|
5164
5561
|
const configure = (cmd) => cmd.description(
|
|
5165
5562
|
"Push your AI dev setup to your private repo (snapshot local changes, then commit + push)."
|
|
@@ -5190,7 +5587,8 @@ function buildCoreServices(toolHome) {
|
|
|
5190
5587
|
sanitizer,
|
|
5191
5588
|
templater,
|
|
5192
5589
|
vars: buildVariables(toolHome),
|
|
5193
|
-
os: detectOS()
|
|
5590
|
+
os: detectOS(),
|
|
5591
|
+
env: process6.env
|
|
5194
5592
|
};
|
|
5195
5593
|
}
|
|
5196
5594
|
function buildCaptureContext(tool, config, dryRun) {
|
|
@@ -5275,9 +5673,9 @@ async function run2(opts = {}) {
|
|
|
5275
5673
|
if (sharedContent !== void 0) {
|
|
5276
5674
|
const shared = buildSharedInstructionsFile(sharedContent);
|
|
5277
5675
|
await writeCapturedFile2(repoRoot, shared);
|
|
5278
|
-
log.step(`Wrote ${
|
|
5676
|
+
log.step(`Wrote ${SHARED_INSTRUCTIONS_REPO_PATH} (shared CLAUDE.md == AGENTS.md)`);
|
|
5279
5677
|
} else {
|
|
5280
|
-
await fs.rmrf(repoJoin(repoRoot,
|
|
5678
|
+
await fs.rmrf(repoJoin(repoRoot, SHARED_INSTRUCTIONS_REPO_PATH));
|
|
5281
5679
|
}
|
|
5282
5680
|
const meta = buildArbellaMeta({
|
|
5283
5681
|
arbellaVersion: ARBELLA_VERSION,
|
|
@@ -5309,8 +5707,8 @@ async function decideSharedInstructions(present) {
|
|
|
5309
5707
|
if (!present.includes("claude") || !present.includes("codex")) {
|
|
5310
5708
|
return { share: false };
|
|
5311
5709
|
}
|
|
5312
|
-
const claudeMd = await readIfExists(
|
|
5313
|
-
const agentsMd = await readIfExists(
|
|
5710
|
+
const claudeMd = await readIfExists(path18.join(toolHomeDir("claude"), "CLAUDE.md"));
|
|
5711
|
+
const agentsMd = await readIfExists(path18.join(toolHomeDir("codex"), "AGENTS.md"));
|
|
5314
5712
|
if (shouldShareInstructions(claudeMd, agentsMd)) {
|
|
5315
5713
|
return { share: true, content: claudeMd };
|
|
5316
5714
|
}
|
|
@@ -5320,8 +5718,9 @@ function toolFilesPrefix(tool) {
|
|
|
5320
5718
|
return `${tool}/files`;
|
|
5321
5719
|
}
|
|
5322
5720
|
async function replaceToolFiles(repoRoot, result) {
|
|
5323
|
-
const
|
|
5324
|
-
|
|
5721
|
+
for (const root of toolRepoDataRoots(result.tool)) {
|
|
5722
|
+
await fs.rmrf(repoJoin(repoRoot, root));
|
|
5723
|
+
}
|
|
5325
5724
|
for (const file of result.files) {
|
|
5326
5725
|
await writeCapturedFile2(repoRoot, file);
|
|
5327
5726
|
}
|
|
@@ -5332,6 +5731,11 @@ async function replaceToolFiles(repoRoot, result) {
|
|
|
5332
5731
|
const manifestPath = repoJoin(repoRoot, `${result.tool}/manifest.json`);
|
|
5333
5732
|
await fs.write(manifestPath, serialize(result.manifest));
|
|
5334
5733
|
}
|
|
5734
|
+
function toolRepoDataRoots(tool) {
|
|
5735
|
+
const roots = [toolFilesPrefix(tool)];
|
|
5736
|
+
if (tool === "cursor") roots.push("cursor/user");
|
|
5737
|
+
return roots;
|
|
5738
|
+
}
|
|
5335
5739
|
async function writeCapturedFile2(repoRoot, file) {
|
|
5336
5740
|
const dest = repoJoin(repoRoot, file.repoPath);
|
|
5337
5741
|
if (file.binary === true) {
|
|
@@ -5343,7 +5747,7 @@ async function writeCapturedFile2(repoRoot, file) {
|
|
|
5343
5747
|
}
|
|
5344
5748
|
function repoJoin(repoRoot, repoPath) {
|
|
5345
5749
|
const segments = repoPath.split("/").filter((s) => s.length > 0);
|
|
5346
|
-
return
|
|
5750
|
+
return path18.join(repoRoot, ...segments);
|
|
5347
5751
|
}
|
|
5348
5752
|
function toolLabel(tool) {
|
|
5349
5753
|
switch (tool) {
|
|
@@ -5357,7 +5761,8 @@ function toolLabel(tool) {
|
|
|
5357
5761
|
}
|
|
5358
5762
|
function renderRepoReadme(meta, generatedAtIso) {
|
|
5359
5763
|
const toolList = meta.tools.map((t) => `- ${toolLabel(t)} (\`${t}\`)`).join("\n");
|
|
5360
|
-
const
|
|
5764
|
+
const cursorUserLine = meta.tools.includes("cursor") ? "- `cursor/user/\u2026` \u2014 Cursor application User data such as settings,\n keybindings, and snippets.\n" : "";
|
|
5765
|
+
const sharedLine = meta.sharedInstructions ? "Your `CLAUDE.md` and `AGENTS.md` were identical and are stored once in [`shared/instructions.md`](shared/instructions.md); restore deploys it to Claude Code and Codex.\n" : "";
|
|
5361
5766
|
return `# arbella backup
|
|
5362
5767
|
|
|
5363
5768
|
This is a **private** backup of an AI coding setup, produced by
|
|
@@ -5377,6 +5782,7 @@ Each tool lives under \`<tool>/\`:
|
|
|
5377
5782
|
|
|
5378
5783
|
- \`<tool>/files/\u2026\` \u2014 frozen config files (paths replaced with \`{{HOME}}\`-style
|
|
5379
5784
|
placeholders, secret values redacted).
|
|
5785
|
+
${cursorUserLine}
|
|
5380
5786
|
- \`<tool>/manifest.json\` \u2014 what to reinstall (plugins, marketplaces, skills,
|
|
5381
5787
|
npm globals) and which plugins to re-enable.
|
|
5382
5788
|
|
|
@@ -5391,7 +5797,7 @@ npm install -g arbella
|
|
|
5391
5797
|
arbella pull <this-repo-url>
|
|
5392
5798
|
\`\`\`
|
|
5393
5799
|
|
|
5394
|
-
arbella will (R6/R14) take a timestamped safety copy of any existing
|
|
5800
|
+
arbella will (R6/R14) take a timestamped safety copy of any existing restore targets,
|
|
5395
5801
|
auto-install missing CLIs, write the frozen files back (re-expanding placeholders
|
|
5396
5802
|
to this machine's paths), reinstall plugins/marketplaces/skills, and re-enable
|
|
5397
5803
|
plugins.
|
|
@@ -5446,15 +5852,17 @@ function renderRepoGitignore(tools) {
|
|
|
5446
5852
|
"*.key"
|
|
5447
5853
|
];
|
|
5448
5854
|
for (const name of secretBasenames) lines.push(name);
|
|
5449
|
-
lines.push("", "# Per-tool excluded directories (scoped under
|
|
5855
|
+
lines.push("", "# Per-tool excluded directories (scoped under owned data roots)");
|
|
5450
5856
|
const seen = /* @__PURE__ */ new Set();
|
|
5451
5857
|
for (const tool of tools) {
|
|
5452
5858
|
for (const pattern of denylistFor(tool)) {
|
|
5453
5859
|
if (!pattern.endsWith("/")) continue;
|
|
5454
|
-
const
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5860
|
+
for (const root of toolRepoDataRoots(tool)) {
|
|
5861
|
+
const scoped = `${root}/${pattern}`;
|
|
5862
|
+
if (seen.has(scoped)) continue;
|
|
5863
|
+
seen.add(scoped);
|
|
5864
|
+
lines.push(scoped);
|
|
5865
|
+
}
|
|
5458
5866
|
}
|
|
5459
5867
|
}
|
|
5460
5868
|
return lines.join("\n") + "\n";
|
|
@@ -5475,7 +5883,7 @@ function reportDryRun(results, sharing, config) {
|
|
|
5475
5883
|
);
|
|
5476
5884
|
}
|
|
5477
5885
|
if (sharing) {
|
|
5478
|
-
log.step(`+ ${
|
|
5886
|
+
log.step(`+ ${SHARED_INSTRUCTIONS_REPO_PATH} (shared CLAUDE.md == AGENTS.md)`);
|
|
5479
5887
|
}
|
|
5480
5888
|
log.step("+ arbella.json, README.md, .gitignore");
|
|
5481
5889
|
reportSecrets(results.flatMap((r) => r.secrets), config.includeSecrets);
|
|
@@ -5575,15 +5983,16 @@ var init_backup = __esm({
|
|
|
5575
5983
|
init_capture();
|
|
5576
5984
|
init_capture2();
|
|
5577
5985
|
init_cursor();
|
|
5578
|
-
|
|
5986
|
+
init_version();
|
|
5987
|
+
ARBELLA_VERSION = getPackageVersion();
|
|
5579
5988
|
}
|
|
5580
5989
|
});
|
|
5581
5990
|
|
|
5582
5991
|
// src/index.ts
|
|
5583
5992
|
init_log();
|
|
5584
|
-
import
|
|
5585
|
-
import
|
|
5586
|
-
import { fileURLToPath } from "url";
|
|
5993
|
+
import fs3 from "fs";
|
|
5994
|
+
import path24 from "path";
|
|
5995
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5587
5996
|
import { Command } from "commander";
|
|
5588
5997
|
import pc2 from "picocolors";
|
|
5589
5998
|
|
|
@@ -5604,8 +6013,11 @@ function listAdapters() {
|
|
|
5604
6013
|
return ALL_ADAPTERS.map((a) => ({ id: a.id, displayName: a.displayName }));
|
|
5605
6014
|
}
|
|
5606
6015
|
|
|
6016
|
+
// src/index.ts
|
|
6017
|
+
init_version();
|
|
6018
|
+
|
|
5607
6019
|
// src/commands/init.ts
|
|
5608
|
-
import
|
|
6020
|
+
import path19 from "path";
|
|
5609
6021
|
import {
|
|
5610
6022
|
cancel as cancel2,
|
|
5611
6023
|
confirm as confirm3,
|
|
@@ -6023,7 +6435,7 @@ async function ensureProviderSignedIn(provider, yes) {
|
|
|
6023
6435
|
}
|
|
6024
6436
|
}
|
|
6025
6437
|
function defaultLocalPath() {
|
|
6026
|
-
return
|
|
6438
|
+
return path19.join(dataDir(), "repo");
|
|
6027
6439
|
}
|
|
6028
6440
|
function displayName(id) {
|
|
6029
6441
|
switch (id) {
|
|
@@ -6239,7 +6651,8 @@ function resolveProviderSpec(raw) {
|
|
|
6239
6651
|
init_backup();
|
|
6240
6652
|
|
|
6241
6653
|
// src/commands/restore.ts
|
|
6242
|
-
import
|
|
6654
|
+
import path20 from "path";
|
|
6655
|
+
import process7 from "process";
|
|
6243
6656
|
init_fs();
|
|
6244
6657
|
init_log();
|
|
6245
6658
|
init_os();
|
|
@@ -6256,12 +6669,13 @@ init_manifest();
|
|
|
6256
6669
|
init_claude();
|
|
6257
6670
|
init_codex();
|
|
6258
6671
|
init_cursor();
|
|
6672
|
+
init_paths3();
|
|
6259
6673
|
init_restore();
|
|
6260
6674
|
init_restore2();
|
|
6261
6675
|
var WIRING = [
|
|
6262
6676
|
{ id: "claude", adapter: claudeAdapter, planActions },
|
|
6263
6677
|
{ id: "codex", adapter: codexAdapter, planActions: planActions2 },
|
|
6264
|
-
{ id: "cursor", adapter: cursorAdapter }
|
|
6678
|
+
{ id: "cursor", adapter: cursorAdapter, planActions: planActions3 }
|
|
6265
6679
|
];
|
|
6266
6680
|
function wiringFor(id) {
|
|
6267
6681
|
const w = WIRING.find((entry) => entry.id === id);
|
|
@@ -6275,12 +6689,13 @@ function buildCoreServices2(toolHome, os2) {
|
|
|
6275
6689
|
sanitizer: createSanitizer(),
|
|
6276
6690
|
templater: createTemplater(),
|
|
6277
6691
|
vars: buildVariables(toolHome),
|
|
6278
|
-
os: os2
|
|
6692
|
+
os: os2,
|
|
6693
|
+
env: process7.env
|
|
6279
6694
|
};
|
|
6280
6695
|
}
|
|
6281
6696
|
function buildRestoreContext(args) {
|
|
6282
6697
|
const toolHome = toolHomeDir(args.toolId);
|
|
6283
|
-
const repoToolDir =
|
|
6698
|
+
const repoToolDir = path20.join(args.repoRoot, args.toolId);
|
|
6284
6699
|
return {
|
|
6285
6700
|
...buildCoreServices2(toolHome, args.os),
|
|
6286
6701
|
toolHome,
|
|
@@ -6298,20 +6713,17 @@ function looksBinary3(buf) {
|
|
|
6298
6713
|
return false;
|
|
6299
6714
|
}
|
|
6300
6715
|
async function readToolFrozen(repoToolDir, tool) {
|
|
6301
|
-
const filesRoot = path19.join(repoToolDir, "files");
|
|
6302
6716
|
const files = [];
|
|
6303
6717
|
const symlinks = [];
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
}
|
|
6307
|
-
async function walk2(absDir, relParts) {
|
|
6718
|
+
const roots = frozenRootsForTool(repoToolDir, tool);
|
|
6719
|
+
async function walk2(absDir, relParts, repoPrefix) {
|
|
6308
6720
|
const entries = await fs.list(absDir);
|
|
6309
6721
|
entries.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
6310
6722
|
for (const name of entries) {
|
|
6311
|
-
const abs =
|
|
6723
|
+
const abs = path20.join(absDir, name);
|
|
6312
6724
|
const nextRel = [...relParts, name];
|
|
6313
6725
|
const relPosix = nextRel.join("/");
|
|
6314
|
-
const repoPath = `${
|
|
6726
|
+
const repoPath = `${repoPrefix}/${relPosix}`;
|
|
6315
6727
|
const kind = await fs.statKind(abs);
|
|
6316
6728
|
if (kind === "symlink") {
|
|
6317
6729
|
let target;
|
|
@@ -6327,7 +6739,7 @@ async function readToolFrozen(repoToolDir, tool) {
|
|
|
6327
6739
|
continue;
|
|
6328
6740
|
}
|
|
6329
6741
|
if (kind === "dir") {
|
|
6330
|
-
await walk2(abs, nextRel);
|
|
6742
|
+
await walk2(abs, nextRel, repoPrefix);
|
|
6331
6743
|
continue;
|
|
6332
6744
|
}
|
|
6333
6745
|
if (kind !== "file") continue;
|
|
@@ -6355,11 +6767,23 @@ async function readToolFrozen(repoToolDir, tool) {
|
|
|
6355
6767
|
}
|
|
6356
6768
|
}
|
|
6357
6769
|
}
|
|
6358
|
-
|
|
6770
|
+
for (const root of roots) {
|
|
6771
|
+
if (await fs.statKind(root.absRoot) !== "dir") continue;
|
|
6772
|
+
await walk2(root.absRoot, [], root.repoPrefix);
|
|
6773
|
+
}
|
|
6359
6774
|
return { files, symlinks };
|
|
6360
6775
|
}
|
|
6776
|
+
function frozenRootsForTool(repoToolDir, tool) {
|
|
6777
|
+
const roots = [
|
|
6778
|
+
{ absRoot: path20.join(repoToolDir, "files"), repoPrefix: `${tool}/files` }
|
|
6779
|
+
];
|
|
6780
|
+
if (tool === "cursor") {
|
|
6781
|
+
roots.push({ absRoot: path20.join(repoToolDir, "user"), repoPrefix: "cursor/user" });
|
|
6782
|
+
}
|
|
6783
|
+
return roots;
|
|
6784
|
+
}
|
|
6361
6785
|
async function readToolManifest(repoToolDir, tool) {
|
|
6362
|
-
const manifestPath =
|
|
6786
|
+
const manifestPath = path20.join(repoToolDir, "manifest.json");
|
|
6363
6787
|
if (!await fs.exists(manifestPath)) {
|
|
6364
6788
|
log.debug(`restore: no manifest.json for ${tool}; using empty manifest.`);
|
|
6365
6789
|
return emptyManifest(tool);
|
|
@@ -6376,7 +6800,7 @@ async function readToolManifest(repoToolDir, tool) {
|
|
|
6376
6800
|
return parseManifest(json);
|
|
6377
6801
|
}
|
|
6378
6802
|
async function loadRestoreData(repoRoot, tool) {
|
|
6379
|
-
const repoToolDir =
|
|
6803
|
+
const repoToolDir = path20.join(repoRoot, tool);
|
|
6380
6804
|
const manifest = await readToolManifest(repoToolDir, tool);
|
|
6381
6805
|
const { files, symlinks } = await readToolFrozen(repoToolDir, tool);
|
|
6382
6806
|
return { manifest, files, symlinks };
|
|
@@ -6389,49 +6813,65 @@ function parseToolsFlag(raw) {
|
|
|
6389
6813
|
);
|
|
6390
6814
|
return ids;
|
|
6391
6815
|
}
|
|
6392
|
-
function
|
|
6816
|
+
function selectToolsForRestore(meta, flagTools, _configTools) {
|
|
6393
6817
|
const captured = new Set(meta.tools);
|
|
6394
6818
|
let candidate;
|
|
6395
6819
|
if (flagTools && flagTools.length > 0) {
|
|
6396
6820
|
candidate = new Set(flagTools);
|
|
6397
|
-
} else if (configTools.length > 0) {
|
|
6398
|
-
candidate = new Set(configTools);
|
|
6399
6821
|
} else {
|
|
6400
6822
|
candidate = new Set(captured);
|
|
6401
6823
|
}
|
|
6402
6824
|
return TOOL_IDS.filter((id) => captured.has(id) && candidate.has(id));
|
|
6403
6825
|
}
|
|
6404
|
-
async function safetyBackup(tools, iso) {
|
|
6826
|
+
async function safetyBackup(tools, iso, os2) {
|
|
6405
6827
|
const stamp = iso.replace(/[:.]/g, "-");
|
|
6406
|
-
const backupsRoot = path19.join(dataDir(), "safety-backups");
|
|
6407
6828
|
const made = [];
|
|
6408
6829
|
for (const tool of tools) {
|
|
6409
|
-
const
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6830
|
+
for (const entry of safetySourcesForTool(tool, os2, stamp)) {
|
|
6831
|
+
if (!await fs.exists(entry.source)) {
|
|
6832
|
+
log.debug(`restore: no existing ${entry.label} to back up (${entry.source}).`);
|
|
6833
|
+
continue;
|
|
6834
|
+
}
|
|
6835
|
+
try {
|
|
6836
|
+
await fs.copy(entry.source, entry.dest);
|
|
6837
|
+
made.push({ tool, source: entry.source, dest: entry.dest });
|
|
6838
|
+
log.step(`Safety backup: ${entry.source} -> ${entry.dest}`);
|
|
6839
|
+
} catch (err) {
|
|
6840
|
+
log.warn(
|
|
6841
|
+
`restore: failed to safety-backup ${entry.label} (${entry.source}): ${errMsg(err)}`
|
|
6842
|
+
);
|
|
6843
|
+
}
|
|
6423
6844
|
}
|
|
6424
6845
|
}
|
|
6425
6846
|
return made;
|
|
6426
6847
|
}
|
|
6848
|
+
function safetySourcesForTool(tool, os2, stamp) {
|
|
6849
|
+
const backupsRoot = path20.join(dataDir(), "safety-backups");
|
|
6850
|
+
const toolHome = toolHomeDir(tool);
|
|
6851
|
+
const sources = [
|
|
6852
|
+
{
|
|
6853
|
+
label: `${tool} home`,
|
|
6854
|
+
source: toolHome,
|
|
6855
|
+
dest: path20.join(backupsRoot, `${tool}-${stamp}`)
|
|
6856
|
+
}
|
|
6857
|
+
];
|
|
6858
|
+
if (tool === "cursor") {
|
|
6859
|
+
sources.push({
|
|
6860
|
+
label: "cursor User data",
|
|
6861
|
+
source: cursorUserPaths(toolHome, os2, process7.env).userDir,
|
|
6862
|
+
dest: path20.join(backupsRoot, `cursor-user-${stamp}`)
|
|
6863
|
+
});
|
|
6864
|
+
}
|
|
6865
|
+
return sources;
|
|
6866
|
+
}
|
|
6427
6867
|
async function deploySharedInstructions(repoRoot, toolsInScope, dryRun) {
|
|
6428
|
-
const sharedAbs =
|
|
6868
|
+
const sharedAbs = path20.join(
|
|
6429
6869
|
repoRoot,
|
|
6430
|
-
...
|
|
6870
|
+
...SHARED_INSTRUCTIONS_REPO_PATH.split("/")
|
|
6431
6871
|
);
|
|
6432
6872
|
if (!await fs.exists(sharedAbs)) {
|
|
6433
6873
|
log.warn(
|
|
6434
|
-
`restore: meta.sharedInstructions is set but ${
|
|
6874
|
+
`restore: meta.sharedInstructions is set but ${SHARED_INSTRUCTIONS_REPO_PATH} is missing from the repo; skipping shared-instructions deployment.`
|
|
6435
6875
|
);
|
|
6436
6876
|
return;
|
|
6437
6877
|
}
|
|
@@ -6439,7 +6879,7 @@ async function deploySharedInstructions(repoRoot, toolsInScope, dryRun) {
|
|
|
6439
6879
|
const content = await fs.read(sharedAbs);
|
|
6440
6880
|
for (const target of sharedInstructionsTargets()) {
|
|
6441
6881
|
if (!inScope.has(target.tool)) continue;
|
|
6442
|
-
const dest =
|
|
6882
|
+
const dest = path20.join(toolHomeDir(target.tool), target.relPath);
|
|
6443
6883
|
if (dryRun) {
|
|
6444
6884
|
log.step(`Would deploy shared instructions -> ${dest}`);
|
|
6445
6885
|
continue;
|
|
@@ -6496,7 +6936,7 @@ async function fallbackActions(ctx, tool, data) {
|
|
|
6496
6936
|
const out2 = [];
|
|
6497
6937
|
for (const file of data.files) {
|
|
6498
6938
|
const rel = stripFilesPrefix(tool, file.repoPath);
|
|
6499
|
-
const dest =
|
|
6939
|
+
const dest = path20.join(ctx.toolHome, ...rel.split("/"));
|
|
6500
6940
|
const overwrites = await ctx.fs.exists(dest);
|
|
6501
6941
|
if (ctx.sourceOfTruth === "local" && overwrites) continue;
|
|
6502
6942
|
out2.push({
|
|
@@ -6509,7 +6949,7 @@ async function fallbackActions(ctx, tool, data) {
|
|
|
6509
6949
|
}
|
|
6510
6950
|
for (const link of data.symlinks) {
|
|
6511
6951
|
const rel = stripFilesPrefix(tool, link.repoPath);
|
|
6512
|
-
const dest =
|
|
6952
|
+
const dest = path20.join(ctx.toolHome, ...rel.split("/"));
|
|
6513
6953
|
const overwrites = await ctx.fs.statKind(dest) !== "missing";
|
|
6514
6954
|
if (ctx.sourceOfTruth === "local" && overwrites) continue;
|
|
6515
6955
|
out2.push({
|
|
@@ -6564,7 +7004,7 @@ function printPlan(plan, meta, l = log) {
|
|
|
6564
7004
|
`Tools: ${plan.tools.length > 0 ? plan.tools.join(", ") : "(none)"}`
|
|
6565
7005
|
);
|
|
6566
7006
|
l.step(
|
|
6567
|
-
`A timestamped safety backup of existing
|
|
7007
|
+
`A timestamped safety backup of existing restore targets WILL be taken first (R14).`
|
|
6568
7008
|
);
|
|
6569
7009
|
if (plan.missingClis.length > 0) {
|
|
6570
7010
|
l.step(`CLIs to auto-install: ${plan.missingClis.join(", ")}`);
|
|
@@ -6631,7 +7071,7 @@ async function resolveRepo(repoUrl, optRepo) {
|
|
|
6631
7071
|
if (explicit && explicit.trim() !== "") {
|
|
6632
7072
|
const url = explicit.trim();
|
|
6633
7073
|
const sameAsConfig = config.repo.url.trim() !== "" && config.repo.url.trim() === url;
|
|
6634
|
-
const localPath2 = sameAsConfig && config.repo.localPath ? config.repo.localPath :
|
|
7074
|
+
const localPath2 = sameAsConfig && config.repo.localPath ? config.repo.localPath : path20.join(dataDir(), "restore", slugForUrl(url));
|
|
6635
7075
|
return { provider: config.repo.provider, url, localPath: localPath2 };
|
|
6636
7076
|
}
|
|
6637
7077
|
if (!config.repo.url || config.repo.url.trim() === "") {
|
|
@@ -6639,7 +7079,7 @@ async function resolveRepo(repoUrl, optRepo) {
|
|
|
6639
7079
|
"No repo to pull from. Pass a repo URL (`arbella pull <repo-url>`) or run `arbella init` first."
|
|
6640
7080
|
);
|
|
6641
7081
|
}
|
|
6642
|
-
const localPath = config.repo.localPath && config.repo.localPath.trim() !== "" ? config.repo.localPath :
|
|
7082
|
+
const localPath = config.repo.localPath && config.repo.localPath.trim() !== "" ? config.repo.localPath : path20.join(dataDir(), "restore", slugForUrl(config.repo.url));
|
|
6643
7083
|
return { provider: config.repo.provider, url: config.repo.url, localPath };
|
|
6644
7084
|
}
|
|
6645
7085
|
function slugForUrl(url) {
|
|
@@ -6676,7 +7116,7 @@ async function run4(repoUrl, opts) {
|
|
|
6676
7116
|
});
|
|
6677
7117
|
await ensureRepoReady(repo, authHooks);
|
|
6678
7118
|
const repoRoot = repo.localPath;
|
|
6679
|
-
const metaPath =
|
|
7119
|
+
const metaPath = path20.join(repoRoot, "arbella.json");
|
|
6680
7120
|
if (!await fs.exists(metaPath)) {
|
|
6681
7121
|
throw new Error(
|
|
6682
7122
|
`Not a arbella backup repo: ${metaPath} is missing. Did you point restore at the right repository?`
|
|
@@ -6692,7 +7132,7 @@ async function run4(repoUrl, opts) {
|
|
|
6692
7132
|
}
|
|
6693
7133
|
const config = await loadConfigOrDefault();
|
|
6694
7134
|
const flagTools = parseToolsFlag(opts.tools);
|
|
6695
|
-
const tools =
|
|
7135
|
+
const tools = selectToolsForRestore(meta, flagTools, config.tools);
|
|
6696
7136
|
if (tools.length === 0) {
|
|
6697
7137
|
log.warn(
|
|
6698
7138
|
`No tools to restore (the repo + your selection have no overlap). Repo captured: ${meta.tools.join(", ") || "(none)"}.`
|
|
@@ -6716,7 +7156,7 @@ async function run4(repoUrl, opts) {
|
|
|
6716
7156
|
}
|
|
6717
7157
|
const iso = (/* @__PURE__ */ new Date()).toISOString();
|
|
6718
7158
|
log.info("Creating safety backups of existing tool homes (R14)\u2026");
|
|
6719
|
-
const backups = await safetyBackup(tools, iso);
|
|
7159
|
+
const backups = await safetyBackup(tools, iso, os2);
|
|
6720
7160
|
if (backups.length === 0) {
|
|
6721
7161
|
log.debug("restore: no existing tool homes needed backing up.");
|
|
6722
7162
|
}
|
|
@@ -6756,7 +7196,7 @@ async function run4(repoUrl, opts) {
|
|
|
6756
7196
|
printReauthReminder(tools);
|
|
6757
7197
|
if (backups.length > 0) {
|
|
6758
7198
|
log.info(
|
|
6759
|
-
`Previous tool homes were safely backed up under ${
|
|
7199
|
+
`Previous tool homes were safely backed up under ${path20.join(
|
|
6760
7200
|
dataDir(),
|
|
6761
7201
|
"safety-backups"
|
|
6762
7202
|
)} (restore them manually if anything looks wrong).`
|
|
@@ -6816,7 +7256,8 @@ init_cursor();
|
|
|
6816
7256
|
init_capture();
|
|
6817
7257
|
init_capture2();
|
|
6818
7258
|
init_cursor();
|
|
6819
|
-
import
|
|
7259
|
+
import path21 from "path";
|
|
7260
|
+
import process8 from "process";
|
|
6820
7261
|
var ADAPTERS = {
|
|
6821
7262
|
claude: { adapter: claudeAdapter, capture },
|
|
6822
7263
|
codex: { adapter: codexAdapter, capture: capture2 },
|
|
@@ -6834,8 +7275,8 @@ async function run5(opts) {
|
|
|
6834
7275
|
await ensureLocalClone(config.repo);
|
|
6835
7276
|
const repoRoot = config.repo.localPath;
|
|
6836
7277
|
const repoInitialized = await isRepoInitialized(repoRoot);
|
|
6837
|
-
const claudeMd = await readIfExists2(
|
|
6838
|
-
const agentsMd = await readIfExists2(
|
|
7278
|
+
const claudeMd = await readIfExists2(path21.join(toolHomeDir("claude"), "CLAUDE.md"));
|
|
7279
|
+
const agentsMd = await readIfExists2(path21.join(toolHomeDir("codex"), "AGENTS.md"));
|
|
6839
7280
|
const sharing = shouldShareInstructions(claudeMd, agentsMd);
|
|
6840
7281
|
const toolStatuses = [];
|
|
6841
7282
|
for (const tool of config.tools) {
|
|
@@ -6897,8 +7338,8 @@ async function run5(opts) {
|
|
|
6897
7338
|
const change = await classifyFile(repoRoot, file);
|
|
6898
7339
|
if (change.kind !== "unchanged") sharedChanges.push(change);
|
|
6899
7340
|
} else {
|
|
6900
|
-
if (await committedFileExists(repoRoot,
|
|
6901
|
-
sharedChanges.push({ repoPath:
|
|
7341
|
+
if (await committedFileExists(repoRoot, SHARED_INSTRUCTIONS_REPO_PATH)) {
|
|
7342
|
+
sharedChanges.push({ repoPath: SHARED_INSTRUCTIONS_REPO_PATH, kind: "removed" });
|
|
6902
7343
|
}
|
|
6903
7344
|
}
|
|
6904
7345
|
const clean = sharedChanges.length === 0 && toolStatuses.every(
|
|
@@ -6914,7 +7355,7 @@ async function run5(opts) {
|
|
|
6914
7355
|
clean
|
|
6915
7356
|
};
|
|
6916
7357
|
if (opts.json) {
|
|
6917
|
-
|
|
7358
|
+
process8.stdout.write(serialize(report));
|
|
6918
7359
|
return;
|
|
6919
7360
|
}
|
|
6920
7361
|
printHuman(report);
|
|
@@ -6928,6 +7369,7 @@ function buildCaptureContext2(tool, includeSecrets, includeMemories) {
|
|
|
6928
7369
|
templater,
|
|
6929
7370
|
vars: buildVariables(toolHome),
|
|
6930
7371
|
os: detectOS(),
|
|
7372
|
+
env: process8.env,
|
|
6931
7373
|
toolHome,
|
|
6932
7374
|
includeSecrets,
|
|
6933
7375
|
includeMemories,
|
|
@@ -7133,7 +7575,7 @@ async function walkRepoFiles(baseAbs, basePosix) {
|
|
|
7133
7575
|
const entries = await fs.list(baseAbs);
|
|
7134
7576
|
for (const name of entries) {
|
|
7135
7577
|
if (name === ".git") continue;
|
|
7136
|
-
const childAbs =
|
|
7578
|
+
const childAbs = path21.join(baseAbs, name);
|
|
7137
7579
|
const childPosix = `${basePosix}/${name}`;
|
|
7138
7580
|
const kind = await fs.statKind(childAbs);
|
|
7139
7581
|
if (kind === "symlink") {
|
|
@@ -7147,7 +7589,7 @@ async function walkRepoFiles(baseAbs, basePosix) {
|
|
|
7147
7589
|
return out2;
|
|
7148
7590
|
}
|
|
7149
7591
|
function repoAbsPath(repoRoot, repoPath) {
|
|
7150
|
-
return
|
|
7592
|
+
return path21.join(repoRoot, ...repoPath.split("/"));
|
|
7151
7593
|
}
|
|
7152
7594
|
async function committedFileExists(repoRoot, repoPath) {
|
|
7153
7595
|
const kind = await fs.statKind(repoAbsPath(repoRoot, repoPath));
|
|
@@ -7257,8 +7699,37 @@ function renderChangeLine(c) {
|
|
|
7257
7699
|
return `${sigil} ${c.repoPath}${linkTag}`;
|
|
7258
7700
|
}
|
|
7259
7701
|
|
|
7702
|
+
// src/commands/update.ts
|
|
7703
|
+
init_version();
|
|
7704
|
+
init_install();
|
|
7705
|
+
init_log();
|
|
7706
|
+
function normalizeVersion(version) {
|
|
7707
|
+
const trimmed = version?.trim();
|
|
7708
|
+
if (!trimmed) return "latest";
|
|
7709
|
+
if (trimmed.startsWith("arbella@")) return trimmed.slice("arbella@".length);
|
|
7710
|
+
return trimmed.replace(/^v(?=\d)/, "");
|
|
7711
|
+
}
|
|
7712
|
+
function packageSpec(version) {
|
|
7713
|
+
return `arbella@${normalizeVersion(version)}`;
|
|
7714
|
+
}
|
|
7715
|
+
function register7(program) {
|
|
7716
|
+
program.command("update").description("Update arbella itself through npm").option("--version <version>", "install a specific arbella version or npm tag instead of latest").option("--dry-run", "show the npm command without running it").action(async (opts) => {
|
|
7717
|
+
await run6(opts);
|
|
7718
|
+
});
|
|
7719
|
+
}
|
|
7720
|
+
async function run6(opts = {}) {
|
|
7721
|
+
const spec = packageSpec(opts.version);
|
|
7722
|
+
if (opts.dryRun) {
|
|
7723
|
+
log.info(`Would run: npm install -g ${spec}`);
|
|
7724
|
+
return;
|
|
7725
|
+
}
|
|
7726
|
+
log.info(`Updating arbella ${getPackageVersion()} -> ${spec}`);
|
|
7727
|
+
await npmInstallGlobal(spec);
|
|
7728
|
+
log.success(`arbella updated (${spec}).`);
|
|
7729
|
+
}
|
|
7730
|
+
|
|
7260
7731
|
// src/commands/secrets.ts
|
|
7261
|
-
import
|
|
7732
|
+
import path23 from "path";
|
|
7262
7733
|
import * as clack2 from "@clack/prompts";
|
|
7263
7734
|
|
|
7264
7735
|
// src/core/secrets/index.ts
|
|
@@ -7271,7 +7742,7 @@ import {
|
|
|
7271
7742
|
scryptSync
|
|
7272
7743
|
} from "crypto";
|
|
7273
7744
|
import { promises as fsp4 } from "fs";
|
|
7274
|
-
import
|
|
7745
|
+
import path22 from "path";
|
|
7275
7746
|
var SCRYPT_PARAMS = {
|
|
7276
7747
|
N: 1 << 15,
|
|
7277
7748
|
// 32768
|
|
@@ -7366,7 +7837,7 @@ async function gatherSecretRefs(toolId) {
|
|
|
7366
7837
|
const refs = [];
|
|
7367
7838
|
const home4 = toolHomeDir(toolId);
|
|
7368
7839
|
for (const spec of SECRET_FILES_BY_TOOL[toolId]) {
|
|
7369
|
-
const abs =
|
|
7840
|
+
const abs = path22.join(home4, spec.relPath);
|
|
7370
7841
|
if (await fs.exists(abs)) {
|
|
7371
7842
|
refs.push({
|
|
7372
7843
|
tool: toolId,
|
|
@@ -7388,10 +7859,10 @@ async function collectSecretFiles(refs, createdAt) {
|
|
|
7388
7859
|
const dedupeKey = `${ref.tool}:${relPath}`;
|
|
7389
7860
|
if (seen.has(dedupeKey)) continue;
|
|
7390
7861
|
seen.add(dedupeKey);
|
|
7391
|
-
const abs =
|
|
7862
|
+
const abs = path22.join(toolHomeDir(ref.tool), relPath);
|
|
7392
7863
|
if (!await fs.exists(abs)) continue;
|
|
7393
7864
|
const bytes = await fs.readBytes(abs);
|
|
7394
|
-
const mode = await
|
|
7865
|
+
const mode = await readMode2(abs);
|
|
7395
7866
|
entries.push({
|
|
7396
7867
|
tool: ref.tool,
|
|
7397
7868
|
relPath,
|
|
@@ -7405,9 +7876,9 @@ async function applySecretBundle(bundle) {
|
|
|
7405
7876
|
for (const entry of bundle.entries) {
|
|
7406
7877
|
const home4 = toolHomeDir(entry.tool);
|
|
7407
7878
|
const rel = entry.relPath.replace(/\\/g, "/");
|
|
7408
|
-
const dest =
|
|
7409
|
-
const homeResolved =
|
|
7410
|
-
const withinHome = dest === homeResolved || dest.startsWith(homeResolved +
|
|
7879
|
+
const dest = path22.resolve(home4, rel);
|
|
7880
|
+
const homeResolved = path22.resolve(home4);
|
|
7881
|
+
const withinHome = dest === homeResolved || dest.startsWith(homeResolved + path22.sep);
|
|
7411
7882
|
if (!withinHome) {
|
|
7412
7883
|
throw new Error(
|
|
7413
7884
|
`Refusing to write secret outside ${entry.tool} home (suspicious path: ${rel}).`
|
|
@@ -7418,7 +7889,7 @@ async function applySecretBundle(bundle) {
|
|
|
7418
7889
|
await fs.writeBytes(dest, bytes, mode);
|
|
7419
7890
|
}
|
|
7420
7891
|
}
|
|
7421
|
-
async function
|
|
7892
|
+
async function readMode2(abs) {
|
|
7422
7893
|
try {
|
|
7423
7894
|
const st = await fsp4.lstat(abs);
|
|
7424
7895
|
const m = st.mode & 4095;
|
|
@@ -7470,7 +7941,7 @@ function assertBundle(value) {
|
|
|
7470
7941
|
init_fs();
|
|
7471
7942
|
init_log();
|
|
7472
7943
|
var DEFAULT_BLOB_FILE = "arbella-secrets.blob";
|
|
7473
|
-
function
|
|
7944
|
+
function register8(program) {
|
|
7474
7945
|
const secrets = program.command("secrets").description(
|
|
7475
7946
|
"Move secret files (auth tokens / credentials) between machines via an encrypted, passphrase-protected blob. Never uses git."
|
|
7476
7947
|
);
|
|
@@ -7523,7 +7994,7 @@ async function runExport(opts) {
|
|
|
7523
7994
|
return;
|
|
7524
7995
|
}
|
|
7525
7996
|
const blob = encryptBundle(bundle, passphrase);
|
|
7526
|
-
const outPath =
|
|
7997
|
+
const outPath = path23.resolve(opts.out);
|
|
7527
7998
|
await fs.write(outPath, blob + "\n", 384);
|
|
7528
7999
|
clack2.note(
|
|
7529
8000
|
[
|
|
@@ -7532,7 +8003,7 @@ async function runExport(opts) {
|
|
|
7532
8003
|
"",
|
|
7533
8004
|
"This blob is safe to copy between machines (it is encrypted with your",
|
|
7534
8005
|
"passphrase). It NEVER goes through git. On the target machine run:",
|
|
7535
|
-
` arbella secrets import --in ${
|
|
8006
|
+
` arbella secrets import --in ${path23.basename(outPath)}`,
|
|
7536
8007
|
"",
|
|
7537
8008
|
"Keep your passphrase safe: without it the blob cannot be decrypted."
|
|
7538
8009
|
].join("\n"),
|
|
@@ -7542,7 +8013,7 @@ async function runExport(opts) {
|
|
|
7542
8013
|
}
|
|
7543
8014
|
async function runImport(opts) {
|
|
7544
8015
|
clack2.intro("arbella secrets import");
|
|
7545
|
-
const inPath =
|
|
8016
|
+
const inPath = path23.resolve(opts.in);
|
|
7546
8017
|
if (!await fs.exists(inPath)) {
|
|
7547
8018
|
clack2.cancel(
|
|
7548
8019
|
`No blob found at:
|
|
@@ -7644,7 +8115,7 @@ function errMessage7(err) {
|
|
|
7644
8115
|
}
|
|
7645
8116
|
|
|
7646
8117
|
// src/index.ts
|
|
7647
|
-
var VERSION =
|
|
8118
|
+
var VERSION = getPackageVersion();
|
|
7648
8119
|
function buildProgram() {
|
|
7649
8120
|
const program = new Command();
|
|
7650
8121
|
const supported = listAdapters().map((a) => a.displayName).join(", ");
|
|
@@ -7664,6 +8135,7 @@ Supported tools: ${supported}.`
|
|
|
7664
8135
|
register5(program);
|
|
7665
8136
|
register6(program);
|
|
7666
8137
|
register7(program);
|
|
8138
|
+
register8(program);
|
|
7667
8139
|
return program;
|
|
7668
8140
|
}
|
|
7669
8141
|
async function main(argv = process.argv) {
|
|
@@ -7673,11 +8145,11 @@ async function main(argv = process.argv) {
|
|
|
7673
8145
|
function isDirectRun() {
|
|
7674
8146
|
const entry = process.argv[1];
|
|
7675
8147
|
if (!entry) return false;
|
|
7676
|
-
const modulePath =
|
|
8148
|
+
const modulePath = fileURLToPath2(import.meta.url);
|
|
7677
8149
|
try {
|
|
7678
|
-
return
|
|
8150
|
+
return fs3.realpathSync(entry) === fs3.realpathSync(modulePath);
|
|
7679
8151
|
} catch {
|
|
7680
|
-
return
|
|
8152
|
+
return path24.resolve(entry) === modulePath;
|
|
7681
8153
|
}
|
|
7682
8154
|
}
|
|
7683
8155
|
function handleTopLevelError(err) {
|