arbella 0.1.0 → 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/LICENSE +661 -21
- package/README.md +8 -6
- package/dist/index.js +693 -226
- 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);
|
|
@@ -908,7 +927,7 @@ function splitPluginId(id) {
|
|
|
908
927
|
if (at <= 0) return { name: id };
|
|
909
928
|
return { name: id.slice(0, at), marketplace: id.slice(at + 1) };
|
|
910
929
|
}
|
|
911
|
-
function parseInstalledPlugins(json) {
|
|
930
|
+
function parseInstalledPlugins(json, foldPath = (p) => p) {
|
|
912
931
|
if (!isRecord(json)) return [];
|
|
913
932
|
const plugins = json.plugins;
|
|
914
933
|
if (!isRecord(plugins)) return [];
|
|
@@ -921,7 +940,7 @@ function parseInstalledPlugins(json) {
|
|
|
921
940
|
if (!isRecord(rec)) continue;
|
|
922
941
|
const scope = rec.scope === "project" ? "project" : "user";
|
|
923
942
|
const version = typeof rec.version === "string" ? rec.version : void 0;
|
|
924
|
-
const projectPath = typeof rec.projectPath === "string" ? rec.projectPath : void 0;
|
|
943
|
+
const projectPath = typeof rec.projectPath === "string" ? foldPath(rec.projectPath) : void 0;
|
|
925
944
|
const entry = {
|
|
926
945
|
id,
|
|
927
946
|
name,
|
|
@@ -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,
|
|
@@ -1119,7 +1138,10 @@ async function capture(ctx, opts) {
|
|
|
1119
1138
|
const installedJson = await readJson(ctx, p.installedPlugins, warnings, "installed_plugins.json");
|
|
1120
1139
|
const marketplacesJson = await readJson(ctx, p.knownMarketplaces, warnings, "known_marketplaces.json");
|
|
1121
1140
|
const settingsJson = await readJson(ctx, p.settings, warnings, "settings.json");
|
|
1122
|
-
const plugins = parseInstalledPlugins(
|
|
1141
|
+
const plugins = parseInstalledPlugins(
|
|
1142
|
+
installedJson,
|
|
1143
|
+
(p2) => ctx.templater.toTemplate(p2, ctx.vars)
|
|
1144
|
+
);
|
|
1123
1145
|
const marketplaces = parseKnownMarketplaces(marketplacesJson);
|
|
1124
1146
|
const enabledPlugins = extractEnabledPlugins(settingsJson);
|
|
1125
1147
|
for (const entry of plugins) {
|
|
@@ -1163,6 +1185,7 @@ var init_capture = __esm({
|
|
|
1163
1185
|
"src/adapters/claude/capture.ts"() {
|
|
1164
1186
|
"use strict";
|
|
1165
1187
|
init_denylist();
|
|
1188
|
+
init_symlink();
|
|
1166
1189
|
init_install();
|
|
1167
1190
|
init_paths();
|
|
1168
1191
|
init_plugins();
|
|
@@ -1846,7 +1869,7 @@ async function captureFile2(ctx, absPath, rel, out2) {
|
|
|
1846
1869
|
async function captureSymlink(ctx, absPath, rel, out2) {
|
|
1847
1870
|
let target;
|
|
1848
1871
|
try {
|
|
1849
|
-
target = await ctx.fs.readLink(absPath);
|
|
1872
|
+
target = normalizeCapturedSymlinkTarget(await ctx.fs.readLink(absPath));
|
|
1850
1873
|
} catch {
|
|
1851
1874
|
out2.warnings.push(`codex: could not read symlink ${rel}; skipped`);
|
|
1852
1875
|
return;
|
|
@@ -2004,6 +2027,7 @@ var init_capture2 = __esm({
|
|
|
2004
2027
|
"use strict";
|
|
2005
2028
|
init_denylist();
|
|
2006
2029
|
init_install();
|
|
2030
|
+
init_symlink();
|
|
2007
2031
|
init_paths2();
|
|
2008
2032
|
init_configToml();
|
|
2009
2033
|
DENY = denylistFor("codex");
|
|
@@ -2060,8 +2084,11 @@ async function planActions2(ctx, data) {
|
|
|
2060
2084
|
description: `Register marketplace ${m.id} (${m.source})`
|
|
2061
2085
|
});
|
|
2062
2086
|
}
|
|
2063
|
-
|
|
2064
|
-
|
|
2087
|
+
const { installable } = partitionPluginsForRestore(
|
|
2088
|
+
data.manifest.marketplaces,
|
|
2089
|
+
data.manifest.plugins.filter((p) => p.scope === "user")
|
|
2090
|
+
);
|
|
2091
|
+
for (const plugin of installable) {
|
|
2065
2092
|
actions.push({
|
|
2066
2093
|
type: "install-plugin",
|
|
2067
2094
|
tool: "codex",
|
|
@@ -2117,6 +2144,19 @@ async function writeCapturedFile(ctx, file, overwriteAllowed) {
|
|
|
2117
2144
|
const content = isConfigToml(rel) ? rehydrateConfigToml(file.content, ctx.templater, ctx.vars) : ctx.templater.fromTemplate(file.content, ctx.vars);
|
|
2118
2145
|
await ctx.fs.write(targetPath, content, file.mode);
|
|
2119
2146
|
}
|
|
2147
|
+
function partitionPluginsForRestore(marketplaces, userPlugins) {
|
|
2148
|
+
const known = new Set(marketplaces.map((m) => m.id));
|
|
2149
|
+
const installable = [];
|
|
2150
|
+
const deferred = [];
|
|
2151
|
+
for (const p of userPlugins) {
|
|
2152
|
+
if (p.marketplace !== void 0 && !known.has(p.marketplace)) {
|
|
2153
|
+
deferred.push(p);
|
|
2154
|
+
} else {
|
|
2155
|
+
installable.push(p);
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
return { installable, deferred };
|
|
2159
|
+
}
|
|
2120
2160
|
async function reinstallPluginsAndMarketplaces(ctx, marketplaces, plugins) {
|
|
2121
2161
|
const userPlugins = plugins.filter((p) => p.scope === "user");
|
|
2122
2162
|
if (marketplaces.length === 0 && userPlugins.length === 0) return;
|
|
@@ -2137,7 +2177,13 @@ async function reinstallPluginsAndMarketplaces(ctx, marketplaces, plugins) {
|
|
|
2137
2177
|
ctx.log.warn(`codex: 'codex ${args.join(" ")}' failed (${msg}); config.toml retains it.`);
|
|
2138
2178
|
}
|
|
2139
2179
|
}
|
|
2140
|
-
|
|
2180
|
+
const { installable, deferred } = partitionPluginsForRestore(marketplaces, userPlugins);
|
|
2181
|
+
for (const plugin of deferred) {
|
|
2182
|
+
ctx.log.step(
|
|
2183
|
+
`codex: ${plugin.id} uses a built-in marketplace (${plugin.marketplace}); left to config.toml for Codex to re-sync.`
|
|
2184
|
+
);
|
|
2185
|
+
}
|
|
2186
|
+
for (const plugin of installable) {
|
|
2141
2187
|
const args = pluginInstallArgs2(plugin);
|
|
2142
2188
|
try {
|
|
2143
2189
|
await execa3("codex", args, { stdout: "ignore", stderr: "ignore", stdin: "ignore" });
|
|
@@ -2169,7 +2215,7 @@ function marketplaceAddArgs2(m) {
|
|
|
2169
2215
|
return ["plugin", "marketplace", "add", m.source];
|
|
2170
2216
|
}
|
|
2171
2217
|
function pluginInstallArgs2(p) {
|
|
2172
|
-
return ["plugin", "
|
|
2218
|
+
return ["plugin", "add", p.id];
|
|
2173
2219
|
}
|
|
2174
2220
|
var PREFIX_WITH_SLASH;
|
|
2175
2221
|
var init_restore2 = __esm({
|
|
@@ -2234,38 +2280,54 @@ function paths3(overrideHome) {
|
|
|
2234
2280
|
return {
|
|
2235
2281
|
home: base,
|
|
2236
2282
|
mcpJson: path10.join(base, "mcp.json"),
|
|
2237
|
-
skillsDir: path10.join(base, "skills")
|
|
2238
|
-
rulesDir: path10.join(base, "rules")
|
|
2283
|
+
skillsDir: path10.join(base, "skills")
|
|
2239
2284
|
};
|
|
2240
2285
|
}
|
|
2241
|
-
function
|
|
2242
|
-
|
|
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
|
+
};
|
|
2243
2295
|
}
|
|
2244
|
-
var REPO_PREFIX3,
|
|
2296
|
+
var REPO_PREFIX3, USER_REPO_PREFIX, FROZEN_PATHS3;
|
|
2245
2297
|
var init_paths3 = __esm({
|
|
2246
2298
|
"src/adapters/cursor/paths.ts"() {
|
|
2247
2299
|
"use strict";
|
|
2248
2300
|
init_os();
|
|
2249
2301
|
REPO_PREFIX3 = "cursor/files";
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
SHARED_RULE_FILENAME = "arbella-shared-instructions.mdc";
|
|
2302
|
+
USER_REPO_PREFIX = "cursor/user";
|
|
2303
|
+
FROZEN_PATHS3 = ["mcp.json", "skills"];
|
|
2253
2304
|
}
|
|
2254
2305
|
});
|
|
2255
2306
|
|
|
2256
2307
|
// src/adapters/cursor/index.ts
|
|
2257
2308
|
import path11 from "path";
|
|
2309
|
+
import process3 from "process";
|
|
2310
|
+
import { execa as execa4 } from "execa";
|
|
2258
2311
|
function repoPathFor3(rel) {
|
|
2259
2312
|
return `${REPO_PREFIX3}/${rel}`;
|
|
2260
2313
|
}
|
|
2314
|
+
function userRepoPathFor(rel) {
|
|
2315
|
+
return `${USER_REPO_PREFIX}/${rel}`;
|
|
2316
|
+
}
|
|
2261
2317
|
function toRel2(home4, abs) {
|
|
2262
2318
|
return path11.relative(home4, abs).split(path11.sep).join("/");
|
|
2263
2319
|
}
|
|
2264
|
-
function
|
|
2320
|
+
function targetFromRepoPath(repoPath) {
|
|
2265
2321
|
const norm = repoPath.replace(/\\/g, "/");
|
|
2266
|
-
const
|
|
2267
|
-
if (
|
|
2268
|
-
|
|
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));
|
|
2269
2331
|
}
|
|
2270
2332
|
function looksBinary2(buf) {
|
|
2271
2333
|
const n = Math.min(buf.length, 8e3);
|
|
@@ -2308,72 +2370,250 @@ function collectMcpSecretRefs(ctx, parsed, source) {
|
|
|
2308
2370
|
}
|
|
2309
2371
|
return refs;
|
|
2310
2372
|
}
|
|
2311
|
-
async function
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
const deny = denylistFor("cursor");
|
|
2319
|
-
if (await ctx.fs.statKind(p.home) !== "dir") {
|
|
2320
|
-
warnings.push("cursor: ~/.cursor not found; skipping (Cursor not installed?).");
|
|
2321
|
-
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;
|
|
2322
2380
|
}
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
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);
|
|
2329
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;
|
|
2330
2462
|
const kind = await ctx.fs.statKind(abs);
|
|
2331
|
-
if (kind === "
|
|
2332
|
-
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
|
+
});
|
|
2333
2472
|
continue;
|
|
2334
2473
|
}
|
|
2335
|
-
if (kind
|
|
2336
|
-
|
|
2474
|
+
if (kind === "dir") {
|
|
2475
|
+
skills.push({ name, source: "frozen", symlinked: false });
|
|
2476
|
+
await walkFrozen(ctx, home4, abs, deny, repoPathFor3, files, symlinks, secrets, warnings);
|
|
2337
2477
|
continue;
|
|
2338
2478
|
}
|
|
2339
|
-
|
|
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") {
|
|
2340
2544
|
try {
|
|
2341
|
-
|
|
2545
|
+
const fromState = parseCursorExtensionsJson(await ctx.fs.read(extensionState));
|
|
2546
|
+
if (fromState.length > 0) return fromState;
|
|
2342
2547
|
} catch (err) {
|
|
2343
|
-
warnings.push(`cursor: could not read
|
|
2344
|
-
continue;
|
|
2345
|
-
}
|
|
2346
|
-
if (looksBinary2(bytes)) {
|
|
2347
|
-
files.push({ repoPath: repoPathFor3(relPosix), content: bytes.toString("base64"), binary: true });
|
|
2348
|
-
continue;
|
|
2548
|
+
warnings.push(`cursor: could not read extensions metadata: ${err.message}`);
|
|
2349
2549
|
}
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
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);
|
|
2356
2593
|
}
|
|
2357
2594
|
}
|
|
2358
|
-
|
|
2359
|
-
const templated = ctx.templater.toTemplate(content, ctx.vars);
|
|
2360
|
-
files.push({ repoPath: repoPathFor3(relPosix), content: templated });
|
|
2361
|
-
ctx.log.debug(`cursor: froze ${relPosix}`);
|
|
2595
|
+
manifest.plugins = await captureExtensions(ctx, p.home, warnings);
|
|
2362
2596
|
}
|
|
2363
|
-
if (
|
|
2364
|
-
|
|
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.");
|
|
2365
2605
|
}
|
|
2366
2606
|
return { tool: "cursor", files, symlinks, manifest, secrets, warnings };
|
|
2367
2607
|
}
|
|
2368
2608
|
async function writeRestoredFile(ctx, file) {
|
|
2369
|
-
const
|
|
2370
|
-
if (
|
|
2609
|
+
const target = targetFromRepoPath(file.repoPath);
|
|
2610
|
+
if (target === void 0) {
|
|
2371
2611
|
ctx.log.debug(`cursor: ignoring foreign repoPath ${file.repoPath}`);
|
|
2372
2612
|
return;
|
|
2373
2613
|
}
|
|
2374
|
-
const dest =
|
|
2614
|
+
const dest = targetAbsFor2(ctx, target);
|
|
2375
2615
|
if (ctx.sourceOfTruth === "local" && await ctx.fs.exists(dest)) {
|
|
2376
|
-
ctx.log.debug(`cursor: keep local ${rel} (sourceOfTruth=local)`);
|
|
2616
|
+
ctx.log.debug(`cursor: keep local ${target.rel} (sourceOfTruth=local)`);
|
|
2377
2617
|
return;
|
|
2378
2618
|
}
|
|
2379
2619
|
if (file.binary) {
|
|
@@ -2383,33 +2623,93 @@ async function writeRestoredFile(ctx, file) {
|
|
|
2383
2623
|
const expanded = ctx.templater.fromTemplate(file.content, ctx.vars);
|
|
2384
2624
|
await ctx.fs.write(dest, expanded, file.mode);
|
|
2385
2625
|
}
|
|
2386
|
-
async function
|
|
2387
|
-
const
|
|
2388
|
-
if (
|
|
2389
|
-
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}`);
|
|
2390
2630
|
return;
|
|
2391
2631
|
}
|
|
2392
|
-
const
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
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;
|
|
2402
2708
|
}
|
|
2403
2709
|
async function restore4(ctx, data) {
|
|
2404
2710
|
if (ctx.dryRun) {
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
if (rel) ctx.log.step(`cursor: would write ${path11.join(ctx.toolHome, ...rel.split("/"))}`);
|
|
2408
|
-
}
|
|
2409
|
-
const sharedAbs = path11.join(ctx.repoRoot, ...SHARED_INSTRUCTIONS_REPO_PATH.split("/"));
|
|
2410
|
-
if (await ctx.fs.exists(sharedAbs)) {
|
|
2411
|
-
ctx.log.step(`cursor: would write shared-instructions rule -> ${sharedRulePath(ctx.toolHome)}`);
|
|
2412
|
-
}
|
|
2711
|
+
const actions = await planActions3(ctx, data);
|
|
2712
|
+
for (const action of actions) ctx.log.step(action.description);
|
|
2413
2713
|
return;
|
|
2414
2714
|
}
|
|
2415
2715
|
for (const file of data.files) {
|
|
@@ -2419,18 +2719,22 @@ async function restore4(ctx, data) {
|
|
|
2419
2719
|
ctx.log.warn(`cursor: failed to write ${file.repoPath}: ${err.message}`);
|
|
2420
2720
|
}
|
|
2421
2721
|
}
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
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
|
+
}
|
|
2426
2728
|
}
|
|
2729
|
+
await reinstallExtensions(ctx, data.manifest.plugins);
|
|
2427
2730
|
}
|
|
2428
2731
|
async function detect2() {
|
|
2429
|
-
|
|
2732
|
+
const p = paths3();
|
|
2733
|
+
return await fsExistsDir(p.home) || await fsExistsDir(cursorUserPaths(p.home, detectOS(), process3.env).userDir);
|
|
2430
2734
|
}
|
|
2431
2735
|
async function fsExistsDir(p) {
|
|
2432
|
-
const { fs:
|
|
2433
|
-
return await
|
|
2736
|
+
const { fs: fs4 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
2737
|
+
return await fs4.statKind(p) === "dir";
|
|
2434
2738
|
}
|
|
2435
2739
|
async function isCliInstalled3() {
|
|
2436
2740
|
return which(cliBinaryName("cursor"));
|
|
@@ -2451,6 +2755,7 @@ var init_cursor = __esm({
|
|
|
2451
2755
|
init_denylist();
|
|
2452
2756
|
init_os();
|
|
2453
2757
|
init_install();
|
|
2758
|
+
init_symlink();
|
|
2454
2759
|
init_paths3();
|
|
2455
2760
|
cursorAdapter = {
|
|
2456
2761
|
id: "cursor",
|
|
@@ -2472,8 +2777,39 @@ var init_cursor = __esm({
|
|
|
2472
2777
|
}
|
|
2473
2778
|
});
|
|
2474
2779
|
|
|
2475
|
-
// src/core/
|
|
2780
|
+
// src/core/version.ts
|
|
2781
|
+
import fs2 from "fs";
|
|
2476
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";
|
|
2477
2813
|
function hookCommand() {
|
|
2478
2814
|
if (detectOS() === "win32") {
|
|
2479
2815
|
return `start /b "" arbella push --auto >NUL 2>&1 :: ${HOOK_TAG}`;
|
|
@@ -2523,7 +2859,7 @@ async function writeJsonObject(file, value) {
|
|
|
2523
2859
|
await fs.write(file, JSON.stringify(value, null, 2) + "\n");
|
|
2524
2860
|
}
|
|
2525
2861
|
function claudeSettingsPath() {
|
|
2526
|
-
return
|
|
2862
|
+
return path13.join(toolHomeDir("claude"), "settings.json");
|
|
2527
2863
|
}
|
|
2528
2864
|
async function applyClaude(enable) {
|
|
2529
2865
|
const home4 = toolHomeDir("claude");
|
|
@@ -2548,7 +2884,7 @@ async function applyClaude(enable) {
|
|
|
2548
2884
|
return true;
|
|
2549
2885
|
}
|
|
2550
2886
|
function codexHooksPath() {
|
|
2551
|
-
return
|
|
2887
|
+
return path13.join(toolHomeDir("codex"), "hooks.json");
|
|
2552
2888
|
}
|
|
2553
2889
|
async function applyCodex(enable) {
|
|
2554
2890
|
const home4 = toolHomeDir("codex");
|
|
@@ -2594,7 +2930,7 @@ var init_hook = __esm({
|
|
|
2594
2930
|
});
|
|
2595
2931
|
|
|
2596
2932
|
// src/core/autobackup/throttle.ts
|
|
2597
|
-
import
|
|
2933
|
+
import path14 from "path";
|
|
2598
2934
|
async function readState() {
|
|
2599
2935
|
if (!await fs.exists(STAMP_FILE)) {
|
|
2600
2936
|
return { lastRunIso: null };
|
|
@@ -2637,7 +2973,7 @@ var init_throttle = __esm({
|
|
|
2637
2973
|
"use strict";
|
|
2638
2974
|
init_fs();
|
|
2639
2975
|
init_os();
|
|
2640
|
-
STAMP_FILE =
|
|
2976
|
+
STAMP_FILE = path14.join(dataDir(), "autobackup.json");
|
|
2641
2977
|
MIN_SESSION_GAP_MS = 5 * 60 * 1e3;
|
|
2642
2978
|
DAILY_GAP_MS = 24 * 60 * 60 * 1e3;
|
|
2643
2979
|
}
|
|
@@ -2725,9 +3061,9 @@ var init_schema = __esm({
|
|
|
2725
3061
|
});
|
|
2726
3062
|
|
|
2727
3063
|
// src/core/config/index.ts
|
|
2728
|
-
import
|
|
3064
|
+
import path15 from "path";
|
|
2729
3065
|
function configPath() {
|
|
2730
|
-
return
|
|
3066
|
+
return path15.join(configDir(), "config.json");
|
|
2731
3067
|
}
|
|
2732
3068
|
async function configExists() {
|
|
2733
3069
|
return fs.exists(configPath());
|
|
@@ -2776,7 +3112,7 @@ function defaultConfig() {
|
|
|
2776
3112
|
async function saveConfig(config) {
|
|
2777
3113
|
const valid = arbellaConfigSchema.parse(config);
|
|
2778
3114
|
const file = configPath();
|
|
2779
|
-
await fs.ensureDir(
|
|
3115
|
+
await fs.ensureDir(path15.dirname(file));
|
|
2780
3116
|
await fs.write(file, serializeConfig(valid));
|
|
2781
3117
|
}
|
|
2782
3118
|
function serializeConfig(value) {
|
|
@@ -2812,7 +3148,7 @@ var init_config = __esm({
|
|
|
2812
3148
|
});
|
|
2813
3149
|
|
|
2814
3150
|
// src/core/auth/cli.ts
|
|
2815
|
-
import { execa as
|
|
3151
|
+
import { execa as execa5 } from "execa";
|
|
2816
3152
|
function cliForProvider(provider) {
|
|
2817
3153
|
return CLI_BY_PROVIDER[provider];
|
|
2818
3154
|
}
|
|
@@ -2831,7 +3167,7 @@ async function providerCliAuthStatus(provider, host) {
|
|
|
2831
3167
|
let exitCode = 1;
|
|
2832
3168
|
let combined = "";
|
|
2833
3169
|
try {
|
|
2834
|
-
const res = await
|
|
3170
|
+
const res = await execa5(cli.bin, args, {
|
|
2835
3171
|
reject: false,
|
|
2836
3172
|
// exit code is the signal; never throw on "not logged in".
|
|
2837
3173
|
stdin: "ignore",
|
|
@@ -2874,7 +3210,7 @@ async function providerCliLogin(provider, opts = {}) {
|
|
|
2874
3210
|
);
|
|
2875
3211
|
log.step(`Running: ${cli.bin} ${args.join(" ")}`);
|
|
2876
3212
|
try {
|
|
2877
|
-
await
|
|
3213
|
+
await execa5(cli.bin, args, {
|
|
2878
3214
|
// INHERITED stdio: the user interacts with gh/glab directly. This is the
|
|
2879
3215
|
// whole point — arbella steps out of the way for the actual login.
|
|
2880
3216
|
stdio: "inherit",
|
|
@@ -2917,7 +3253,7 @@ async function providerCliLogout(provider, host) {
|
|
|
2917
3253
|
}
|
|
2918
3254
|
log.step(`Running: ${cli.bin} ${args.join(" ")}`);
|
|
2919
3255
|
try {
|
|
2920
|
-
await
|
|
3256
|
+
await execa5(cli.bin, args, { stdio: "inherit", timeout: 6e4 });
|
|
2921
3257
|
log.success(`${cli.label}: signed out.`);
|
|
2922
3258
|
return true;
|
|
2923
3259
|
} catch (err) {
|
|
@@ -3169,7 +3505,7 @@ var init_device_flow = __esm({
|
|
|
3169
3505
|
});
|
|
3170
3506
|
|
|
3171
3507
|
// src/core/auth/providers.ts
|
|
3172
|
-
import
|
|
3508
|
+
import process4 from "process";
|
|
3173
3509
|
function isPlaceholderClientId(clientId) {
|
|
3174
3510
|
return clientId.trim() === "" || clientId === PLACEHOLDER_CLIENT_ID;
|
|
3175
3511
|
}
|
|
@@ -3215,7 +3551,7 @@ function resolveClientId(spec, overrides) {
|
|
|
3215
3551
|
if (typeof override === "string" && override.trim() !== "") {
|
|
3216
3552
|
return override.trim();
|
|
3217
3553
|
}
|
|
3218
|
-
const fromEnv =
|
|
3554
|
+
const fromEnv = process4.env[spec.clientIdEnvVar];
|
|
3219
3555
|
if (typeof fromEnv === "string" && fromEnv.trim() !== "") {
|
|
3220
3556
|
return fromEnv.trim();
|
|
3221
3557
|
}
|
|
@@ -3276,9 +3612,9 @@ var init_providers = __esm({
|
|
|
3276
3612
|
|
|
3277
3613
|
// src/core/auth/store.ts
|
|
3278
3614
|
import { promises as fsp3 } from "fs";
|
|
3279
|
-
import
|
|
3615
|
+
import path16 from "path";
|
|
3280
3616
|
function credentialsPath() {
|
|
3281
|
-
return
|
|
3617
|
+
return path16.join(dataDir(), "credentials.json");
|
|
3282
3618
|
}
|
|
3283
3619
|
async function loadFile() {
|
|
3284
3620
|
const file = credentialsPath();
|
|
@@ -3333,7 +3669,7 @@ function normalizeCredential(host, value) {
|
|
|
3333
3669
|
}
|
|
3334
3670
|
async function saveFile(data) {
|
|
3335
3671
|
const file = credentialsPath();
|
|
3336
|
-
await fs.ensureDir(
|
|
3672
|
+
await fs.ensureDir(path16.dirname(file));
|
|
3337
3673
|
const json = `${JSON.stringify(data, null, 2)}
|
|
3338
3674
|
`;
|
|
3339
3675
|
await fs.write(file, json, CREDENTIALS_MODE);
|
|
@@ -3807,13 +4143,13 @@ var init_auth = __esm({
|
|
|
3807
4143
|
});
|
|
3808
4144
|
|
|
3809
4145
|
// src/core/repo/git.ts
|
|
3810
|
-
import { execa as
|
|
4146
|
+
import { execa as execa6 } from "execa";
|
|
3811
4147
|
async function git(cwd, args, opts = {}) {
|
|
3812
4148
|
const reject = opts.reject ?? true;
|
|
3813
4149
|
log.debug(`git ${args.map(redactArg).join(" ")} (cwd=${cwd})`);
|
|
3814
4150
|
let res;
|
|
3815
4151
|
try {
|
|
3816
|
-
res = await
|
|
4152
|
+
res = await execa6("git", args, {
|
|
3817
4153
|
cwd,
|
|
3818
4154
|
reject,
|
|
3819
4155
|
// Keep output as strings; do not inherit stdio so we can capture/redact.
|
|
@@ -3896,7 +4232,7 @@ async function hasUpstream(cwd, branch) {
|
|
|
3896
4232
|
async function clone(url, dest) {
|
|
3897
4233
|
log.debug(`git clone <url> -> ${dest}`);
|
|
3898
4234
|
try {
|
|
3899
|
-
await
|
|
4235
|
+
await execa6("git", ["clone", url, dest], {
|
|
3900
4236
|
reject: true,
|
|
3901
4237
|
stdout: "pipe",
|
|
3902
4238
|
stderr: "pipe",
|
|
@@ -3999,10 +4335,10 @@ var init_generic = __esm({
|
|
|
3999
4335
|
});
|
|
4000
4336
|
|
|
4001
4337
|
// src/core/repo/providers/github.ts
|
|
4002
|
-
import { execa as
|
|
4338
|
+
import { execa as execa7 } from "execa";
|
|
4003
4339
|
async function ghPresent() {
|
|
4004
4340
|
try {
|
|
4005
|
-
await
|
|
4341
|
+
await execa7("gh", ["--version"], { reject: true, stdin: "ignore" });
|
|
4006
4342
|
return true;
|
|
4007
4343
|
} catch {
|
|
4008
4344
|
return false;
|
|
@@ -4011,7 +4347,7 @@ async function ghPresent() {
|
|
|
4011
4347
|
async function gh(args) {
|
|
4012
4348
|
log.debug(`gh ${args.join(" ")}`);
|
|
4013
4349
|
try {
|
|
4014
|
-
const res = await
|
|
4350
|
+
const res = await execa7("gh", args, {
|
|
4015
4351
|
reject: true,
|
|
4016
4352
|
stdout: "pipe",
|
|
4017
4353
|
stderr: "pipe",
|
|
@@ -4090,10 +4426,10 @@ var init_github = __esm({
|
|
|
4090
4426
|
});
|
|
4091
4427
|
|
|
4092
4428
|
// src/core/repo/providers/gitlab.ts
|
|
4093
|
-
import { execa as
|
|
4429
|
+
import { execa as execa8 } from "execa";
|
|
4094
4430
|
async function glabPresent() {
|
|
4095
4431
|
try {
|
|
4096
|
-
await
|
|
4432
|
+
await execa8("glab", ["--version"], { reject: true, stdin: "ignore" });
|
|
4097
4433
|
return true;
|
|
4098
4434
|
} catch {
|
|
4099
4435
|
return false;
|
|
@@ -4102,7 +4438,7 @@ async function glabPresent() {
|
|
|
4102
4438
|
async function glab(args) {
|
|
4103
4439
|
log.debug(`glab ${args.join(" ")}`);
|
|
4104
4440
|
try {
|
|
4105
|
-
const res = await
|
|
4441
|
+
const res = await execa8("glab", args, {
|
|
4106
4442
|
reject: true,
|
|
4107
4443
|
stdout: "pipe",
|
|
4108
4444
|
stderr: "pipe",
|
|
@@ -4181,7 +4517,7 @@ var init_gitlab = __esm({
|
|
|
4181
4517
|
});
|
|
4182
4518
|
|
|
4183
4519
|
// src/core/repo/index.ts
|
|
4184
|
-
import
|
|
4520
|
+
import path17 from "path";
|
|
4185
4521
|
async function withRepoAuth(repoUrl, hooks, op) {
|
|
4186
4522
|
try {
|
|
4187
4523
|
await op(repoUrl, false);
|
|
@@ -4256,7 +4592,7 @@ async function ensureLocalClone(repo, auth) {
|
|
|
4256
4592
|
"repo.url is not configured; cannot clone. Run `arbella init` first."
|
|
4257
4593
|
);
|
|
4258
4594
|
}
|
|
4259
|
-
await fs.ensureDir(
|
|
4595
|
+
await fs.ensureDir(path17.dirname(localPath));
|
|
4260
4596
|
log.step(`Cloning backup repo into ${localPath}`);
|
|
4261
4597
|
await withRepoAuth(repo.url, auth, async (url, credentialed) => {
|
|
4262
4598
|
await clone(url, localPath);
|
|
@@ -4408,13 +4744,11 @@ function createSanitizer() {
|
|
|
4408
4744
|
}
|
|
4409
4745
|
function sanitizeFile(content, tool, source) {
|
|
4410
4746
|
if (looksLikeJsonSource(source)) {
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
parsed = JSON.parse(content);
|
|
4414
|
-
} catch {
|
|
4747
|
+
const parsed = parseJsonOrJsonc(content);
|
|
4748
|
+
if (!parsed.ok) {
|
|
4415
4749
|
return sanitizeText(content, tool, source);
|
|
4416
4750
|
}
|
|
4417
|
-
const { value, found } = sanitizeJson(parsed, tool, source);
|
|
4751
|
+
const { value, found } = sanitizeJson(parsed.value, tool, source);
|
|
4418
4752
|
const indent = detectJsonIndent(content);
|
|
4419
4753
|
const serialized = JSON.stringify(value, null, indent);
|
|
4420
4754
|
return { content: serialized, found, changed: serialized !== content };
|
|
@@ -4427,6 +4761,92 @@ function looksLikeJsonSource(source) {
|
|
|
4427
4761
|
const base = source.replace(/\\/g, "/").split("/").pop() ?? source;
|
|
4428
4762
|
return /\.json$/i.test(base);
|
|
4429
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
|
+
}
|
|
4430
4850
|
function detectJsonIndent(content) {
|
|
4431
4851
|
const m = /\n([ \t]+)\S/.exec(content);
|
|
4432
4852
|
if (!m) return 2;
|
|
@@ -4649,6 +5069,7 @@ var init_templater = __esm({
|
|
|
4649
5069
|
|
|
4650
5070
|
// src/commands/_context.ts
|
|
4651
5071
|
import { confirm, isCancel, password } from "@clack/prompts";
|
|
5072
|
+
import process5 from "process";
|
|
4652
5073
|
function buildRepoAuthHooks(args) {
|
|
4653
5074
|
return {
|
|
4654
5075
|
interactive: args.interactive ?? true,
|
|
@@ -5109,7 +5530,7 @@ function shouldShareInstructions(claudeMd, agentsMd) {
|
|
|
5109
5530
|
}
|
|
5110
5531
|
function buildSharedInstructionsFile(content) {
|
|
5111
5532
|
return {
|
|
5112
|
-
repoPath:
|
|
5533
|
+
repoPath: SHARED_INSTRUCTIONS_REPO_PATH,
|
|
5113
5534
|
content
|
|
5114
5535
|
};
|
|
5115
5536
|
}
|
|
@@ -5119,12 +5540,12 @@ function sharedInstructionsTargets() {
|
|
|
5119
5540
|
{ tool: "codex", relPath: "AGENTS.md" }
|
|
5120
5541
|
];
|
|
5121
5542
|
}
|
|
5122
|
-
var
|
|
5543
|
+
var SHARED_INSTRUCTIONS_REPO_PATH;
|
|
5123
5544
|
var init_manifest = __esm({
|
|
5124
5545
|
"src/core/manifest/index.ts"() {
|
|
5125
5546
|
"use strict";
|
|
5126
5547
|
init_schema2();
|
|
5127
|
-
|
|
5548
|
+
SHARED_INSTRUCTIONS_REPO_PATH = "shared/instructions.md";
|
|
5128
5549
|
}
|
|
5129
5550
|
});
|
|
5130
5551
|
|
|
@@ -5134,7 +5555,8 @@ __export(backup_exports, {
|
|
|
5134
5555
|
register: () => register2,
|
|
5135
5556
|
run: () => run2
|
|
5136
5557
|
});
|
|
5137
|
-
import
|
|
5558
|
+
import path18 from "path";
|
|
5559
|
+
import process6 from "process";
|
|
5138
5560
|
function register2(program) {
|
|
5139
5561
|
const configure = (cmd) => cmd.description(
|
|
5140
5562
|
"Push your AI dev setup to your private repo (snapshot local changes, then commit + push)."
|
|
@@ -5165,7 +5587,8 @@ function buildCoreServices(toolHome) {
|
|
|
5165
5587
|
sanitizer,
|
|
5166
5588
|
templater,
|
|
5167
5589
|
vars: buildVariables(toolHome),
|
|
5168
|
-
os: detectOS()
|
|
5590
|
+
os: detectOS(),
|
|
5591
|
+
env: process6.env
|
|
5169
5592
|
};
|
|
5170
5593
|
}
|
|
5171
5594
|
function buildCaptureContext(tool, config, dryRun) {
|
|
@@ -5250,9 +5673,9 @@ async function run2(opts = {}) {
|
|
|
5250
5673
|
if (sharedContent !== void 0) {
|
|
5251
5674
|
const shared = buildSharedInstructionsFile(sharedContent);
|
|
5252
5675
|
await writeCapturedFile2(repoRoot, shared);
|
|
5253
|
-
log.step(`Wrote ${
|
|
5676
|
+
log.step(`Wrote ${SHARED_INSTRUCTIONS_REPO_PATH} (shared CLAUDE.md == AGENTS.md)`);
|
|
5254
5677
|
} else {
|
|
5255
|
-
await fs.rmrf(repoJoin(repoRoot,
|
|
5678
|
+
await fs.rmrf(repoJoin(repoRoot, SHARED_INSTRUCTIONS_REPO_PATH));
|
|
5256
5679
|
}
|
|
5257
5680
|
const meta = buildArbellaMeta({
|
|
5258
5681
|
arbellaVersion: ARBELLA_VERSION,
|
|
@@ -5284,8 +5707,8 @@ async function decideSharedInstructions(present) {
|
|
|
5284
5707
|
if (!present.includes("claude") || !present.includes("codex")) {
|
|
5285
5708
|
return { share: false };
|
|
5286
5709
|
}
|
|
5287
|
-
const claudeMd = await readIfExists(
|
|
5288
|
-
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"));
|
|
5289
5712
|
if (shouldShareInstructions(claudeMd, agentsMd)) {
|
|
5290
5713
|
return { share: true, content: claudeMd };
|
|
5291
5714
|
}
|
|
@@ -5295,8 +5718,9 @@ function toolFilesPrefix(tool) {
|
|
|
5295
5718
|
return `${tool}/files`;
|
|
5296
5719
|
}
|
|
5297
5720
|
async function replaceToolFiles(repoRoot, result) {
|
|
5298
|
-
const
|
|
5299
|
-
|
|
5721
|
+
for (const root of toolRepoDataRoots(result.tool)) {
|
|
5722
|
+
await fs.rmrf(repoJoin(repoRoot, root));
|
|
5723
|
+
}
|
|
5300
5724
|
for (const file of result.files) {
|
|
5301
5725
|
await writeCapturedFile2(repoRoot, file);
|
|
5302
5726
|
}
|
|
@@ -5307,6 +5731,11 @@ async function replaceToolFiles(repoRoot, result) {
|
|
|
5307
5731
|
const manifestPath = repoJoin(repoRoot, `${result.tool}/manifest.json`);
|
|
5308
5732
|
await fs.write(manifestPath, serialize(result.manifest));
|
|
5309
5733
|
}
|
|
5734
|
+
function toolRepoDataRoots(tool) {
|
|
5735
|
+
const roots = [toolFilesPrefix(tool)];
|
|
5736
|
+
if (tool === "cursor") roots.push("cursor/user");
|
|
5737
|
+
return roots;
|
|
5738
|
+
}
|
|
5310
5739
|
async function writeCapturedFile2(repoRoot, file) {
|
|
5311
5740
|
const dest = repoJoin(repoRoot, file.repoPath);
|
|
5312
5741
|
if (file.binary === true) {
|
|
@@ -5318,7 +5747,7 @@ async function writeCapturedFile2(repoRoot, file) {
|
|
|
5318
5747
|
}
|
|
5319
5748
|
function repoJoin(repoRoot, repoPath) {
|
|
5320
5749
|
const segments = repoPath.split("/").filter((s) => s.length > 0);
|
|
5321
|
-
return
|
|
5750
|
+
return path18.join(repoRoot, ...segments);
|
|
5322
5751
|
}
|
|
5323
5752
|
function toolLabel(tool) {
|
|
5324
5753
|
switch (tool) {
|
|
@@ -5332,7 +5761,8 @@ function toolLabel(tool) {
|
|
|
5332
5761
|
}
|
|
5333
5762
|
function renderRepoReadme(meta, generatedAtIso) {
|
|
5334
5763
|
const toolList = meta.tools.map((t) => `- ${toolLabel(t)} (\`${t}\`)`).join("\n");
|
|
5335
|
-
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" : "";
|
|
5336
5766
|
return `# arbella backup
|
|
5337
5767
|
|
|
5338
5768
|
This is a **private** backup of an AI coding setup, produced by
|
|
@@ -5352,6 +5782,7 @@ Each tool lives under \`<tool>/\`:
|
|
|
5352
5782
|
|
|
5353
5783
|
- \`<tool>/files/\u2026\` \u2014 frozen config files (paths replaced with \`{{HOME}}\`-style
|
|
5354
5784
|
placeholders, secret values redacted).
|
|
5785
|
+
${cursorUserLine}
|
|
5355
5786
|
- \`<tool>/manifest.json\` \u2014 what to reinstall (plugins, marketplaces, skills,
|
|
5356
5787
|
npm globals) and which plugins to re-enable.
|
|
5357
5788
|
|
|
@@ -5366,7 +5797,7 @@ npm install -g arbella
|
|
|
5366
5797
|
arbella pull <this-repo-url>
|
|
5367
5798
|
\`\`\`
|
|
5368
5799
|
|
|
5369
|
-
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,
|
|
5370
5801
|
auto-install missing CLIs, write the frozen files back (re-expanding placeholders
|
|
5371
5802
|
to this machine's paths), reinstall plugins/marketplaces/skills, and re-enable
|
|
5372
5803
|
plugins.
|
|
@@ -5421,15 +5852,17 @@ function renderRepoGitignore(tools) {
|
|
|
5421
5852
|
"*.key"
|
|
5422
5853
|
];
|
|
5423
5854
|
for (const name of secretBasenames) lines.push(name);
|
|
5424
|
-
lines.push("", "# Per-tool excluded directories (scoped under
|
|
5855
|
+
lines.push("", "# Per-tool excluded directories (scoped under owned data roots)");
|
|
5425
5856
|
const seen = /* @__PURE__ */ new Set();
|
|
5426
5857
|
for (const tool of tools) {
|
|
5427
5858
|
for (const pattern of denylistFor(tool)) {
|
|
5428
5859
|
if (!pattern.endsWith("/")) continue;
|
|
5429
|
-
const
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
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
|
+
}
|
|
5433
5866
|
}
|
|
5434
5867
|
}
|
|
5435
5868
|
return lines.join("\n") + "\n";
|
|
@@ -5450,7 +5883,7 @@ function reportDryRun(results, sharing, config) {
|
|
|
5450
5883
|
);
|
|
5451
5884
|
}
|
|
5452
5885
|
if (sharing) {
|
|
5453
|
-
log.step(`+ ${
|
|
5886
|
+
log.step(`+ ${SHARED_INSTRUCTIONS_REPO_PATH} (shared CLAUDE.md == AGENTS.md)`);
|
|
5454
5887
|
}
|
|
5455
5888
|
log.step("+ arbella.json, README.md, .gitignore");
|
|
5456
5889
|
reportSecrets(results.flatMap((r) => r.secrets), config.includeSecrets);
|
|
@@ -5550,15 +5983,16 @@ var init_backup = __esm({
|
|
|
5550
5983
|
init_capture();
|
|
5551
5984
|
init_capture2();
|
|
5552
5985
|
init_cursor();
|
|
5553
|
-
|
|
5986
|
+
init_version();
|
|
5987
|
+
ARBELLA_VERSION = getPackageVersion();
|
|
5554
5988
|
}
|
|
5555
5989
|
});
|
|
5556
5990
|
|
|
5557
5991
|
// src/index.ts
|
|
5558
5992
|
init_log();
|
|
5559
|
-
import
|
|
5560
|
-
import
|
|
5561
|
-
import { fileURLToPath } from "url";
|
|
5993
|
+
import fs3 from "fs";
|
|
5994
|
+
import path24 from "path";
|
|
5995
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5562
5996
|
import { Command } from "commander";
|
|
5563
5997
|
import pc2 from "picocolors";
|
|
5564
5998
|
|
|
@@ -5579,8 +6013,11 @@ function listAdapters() {
|
|
|
5579
6013
|
return ALL_ADAPTERS.map((a) => ({ id: a.id, displayName: a.displayName }));
|
|
5580
6014
|
}
|
|
5581
6015
|
|
|
6016
|
+
// src/index.ts
|
|
6017
|
+
init_version();
|
|
6018
|
+
|
|
5582
6019
|
// src/commands/init.ts
|
|
5583
|
-
import
|
|
6020
|
+
import path19 from "path";
|
|
5584
6021
|
import {
|
|
5585
6022
|
cancel as cancel2,
|
|
5586
6023
|
confirm as confirm3,
|
|
@@ -5998,7 +6435,7 @@ async function ensureProviderSignedIn(provider, yes) {
|
|
|
5998
6435
|
}
|
|
5999
6436
|
}
|
|
6000
6437
|
function defaultLocalPath() {
|
|
6001
|
-
return
|
|
6438
|
+
return path19.join(dataDir(), "repo");
|
|
6002
6439
|
}
|
|
6003
6440
|
function displayName(id) {
|
|
6004
6441
|
switch (id) {
|
|
@@ -6214,7 +6651,8 @@ function resolveProviderSpec(raw) {
|
|
|
6214
6651
|
init_backup();
|
|
6215
6652
|
|
|
6216
6653
|
// src/commands/restore.ts
|
|
6217
|
-
import
|
|
6654
|
+
import path20 from "path";
|
|
6655
|
+
import process7 from "process";
|
|
6218
6656
|
init_fs();
|
|
6219
6657
|
init_log();
|
|
6220
6658
|
init_os();
|
|
@@ -6231,12 +6669,13 @@ init_manifest();
|
|
|
6231
6669
|
init_claude();
|
|
6232
6670
|
init_codex();
|
|
6233
6671
|
init_cursor();
|
|
6672
|
+
init_paths3();
|
|
6234
6673
|
init_restore();
|
|
6235
6674
|
init_restore2();
|
|
6236
6675
|
var WIRING = [
|
|
6237
6676
|
{ id: "claude", adapter: claudeAdapter, planActions },
|
|
6238
6677
|
{ id: "codex", adapter: codexAdapter, planActions: planActions2 },
|
|
6239
|
-
{ id: "cursor", adapter: cursorAdapter }
|
|
6678
|
+
{ id: "cursor", adapter: cursorAdapter, planActions: planActions3 }
|
|
6240
6679
|
];
|
|
6241
6680
|
function wiringFor(id) {
|
|
6242
6681
|
const w = WIRING.find((entry) => entry.id === id);
|
|
@@ -6250,12 +6689,13 @@ function buildCoreServices2(toolHome, os2) {
|
|
|
6250
6689
|
sanitizer: createSanitizer(),
|
|
6251
6690
|
templater: createTemplater(),
|
|
6252
6691
|
vars: buildVariables(toolHome),
|
|
6253
|
-
os: os2
|
|
6692
|
+
os: os2,
|
|
6693
|
+
env: process7.env
|
|
6254
6694
|
};
|
|
6255
6695
|
}
|
|
6256
6696
|
function buildRestoreContext(args) {
|
|
6257
6697
|
const toolHome = toolHomeDir(args.toolId);
|
|
6258
|
-
const repoToolDir =
|
|
6698
|
+
const repoToolDir = path20.join(args.repoRoot, args.toolId);
|
|
6259
6699
|
return {
|
|
6260
6700
|
...buildCoreServices2(toolHome, args.os),
|
|
6261
6701
|
toolHome,
|
|
@@ -6273,20 +6713,17 @@ function looksBinary3(buf) {
|
|
|
6273
6713
|
return false;
|
|
6274
6714
|
}
|
|
6275
6715
|
async function readToolFrozen(repoToolDir, tool) {
|
|
6276
|
-
const filesRoot = path19.join(repoToolDir, "files");
|
|
6277
6716
|
const files = [];
|
|
6278
6717
|
const symlinks = [];
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
}
|
|
6282
|
-
async function walk2(absDir, relParts) {
|
|
6718
|
+
const roots = frozenRootsForTool(repoToolDir, tool);
|
|
6719
|
+
async function walk2(absDir, relParts, repoPrefix) {
|
|
6283
6720
|
const entries = await fs.list(absDir);
|
|
6284
6721
|
entries.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
6285
6722
|
for (const name of entries) {
|
|
6286
|
-
const abs =
|
|
6723
|
+
const abs = path20.join(absDir, name);
|
|
6287
6724
|
const nextRel = [...relParts, name];
|
|
6288
6725
|
const relPosix = nextRel.join("/");
|
|
6289
|
-
const repoPath = `${
|
|
6726
|
+
const repoPath = `${repoPrefix}/${relPosix}`;
|
|
6290
6727
|
const kind = await fs.statKind(abs);
|
|
6291
6728
|
if (kind === "symlink") {
|
|
6292
6729
|
let target;
|
|
@@ -6302,7 +6739,7 @@ async function readToolFrozen(repoToolDir, tool) {
|
|
|
6302
6739
|
continue;
|
|
6303
6740
|
}
|
|
6304
6741
|
if (kind === "dir") {
|
|
6305
|
-
await walk2(abs, nextRel);
|
|
6742
|
+
await walk2(abs, nextRel, repoPrefix);
|
|
6306
6743
|
continue;
|
|
6307
6744
|
}
|
|
6308
6745
|
if (kind !== "file") continue;
|
|
@@ -6330,11 +6767,23 @@ async function readToolFrozen(repoToolDir, tool) {
|
|
|
6330
6767
|
}
|
|
6331
6768
|
}
|
|
6332
6769
|
}
|
|
6333
|
-
|
|
6770
|
+
for (const root of roots) {
|
|
6771
|
+
if (await fs.statKind(root.absRoot) !== "dir") continue;
|
|
6772
|
+
await walk2(root.absRoot, [], root.repoPrefix);
|
|
6773
|
+
}
|
|
6334
6774
|
return { files, symlinks };
|
|
6335
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
|
+
}
|
|
6336
6785
|
async function readToolManifest(repoToolDir, tool) {
|
|
6337
|
-
const manifestPath =
|
|
6786
|
+
const manifestPath = path20.join(repoToolDir, "manifest.json");
|
|
6338
6787
|
if (!await fs.exists(manifestPath)) {
|
|
6339
6788
|
log.debug(`restore: no manifest.json for ${tool}; using empty manifest.`);
|
|
6340
6789
|
return emptyManifest(tool);
|
|
@@ -6351,7 +6800,7 @@ async function readToolManifest(repoToolDir, tool) {
|
|
|
6351
6800
|
return parseManifest(json);
|
|
6352
6801
|
}
|
|
6353
6802
|
async function loadRestoreData(repoRoot, tool) {
|
|
6354
|
-
const repoToolDir =
|
|
6803
|
+
const repoToolDir = path20.join(repoRoot, tool);
|
|
6355
6804
|
const manifest = await readToolManifest(repoToolDir, tool);
|
|
6356
6805
|
const { files, symlinks } = await readToolFrozen(repoToolDir, tool);
|
|
6357
6806
|
return { manifest, files, symlinks };
|
|
@@ -6364,49 +6813,65 @@ function parseToolsFlag(raw) {
|
|
|
6364
6813
|
);
|
|
6365
6814
|
return ids;
|
|
6366
6815
|
}
|
|
6367
|
-
function
|
|
6816
|
+
function selectToolsForRestore(meta, flagTools, _configTools) {
|
|
6368
6817
|
const captured = new Set(meta.tools);
|
|
6369
6818
|
let candidate;
|
|
6370
6819
|
if (flagTools && flagTools.length > 0) {
|
|
6371
6820
|
candidate = new Set(flagTools);
|
|
6372
|
-
} else if (configTools.length > 0) {
|
|
6373
|
-
candidate = new Set(configTools);
|
|
6374
6821
|
} else {
|
|
6375
6822
|
candidate = new Set(captured);
|
|
6376
6823
|
}
|
|
6377
6824
|
return TOOL_IDS.filter((id) => captured.has(id) && candidate.has(id));
|
|
6378
6825
|
}
|
|
6379
|
-
async function safetyBackup(tools, iso) {
|
|
6826
|
+
async function safetyBackup(tools, iso, os2) {
|
|
6380
6827
|
const stamp = iso.replace(/[:.]/g, "-");
|
|
6381
|
-
const backupsRoot = path19.join(dataDir(), "safety-backups");
|
|
6382
6828
|
const made = [];
|
|
6383
6829
|
for (const tool of tools) {
|
|
6384
|
-
const
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
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
|
+
}
|
|
6398
6844
|
}
|
|
6399
6845
|
}
|
|
6400
6846
|
return made;
|
|
6401
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
|
+
}
|
|
6402
6867
|
async function deploySharedInstructions(repoRoot, toolsInScope, dryRun) {
|
|
6403
|
-
const sharedAbs =
|
|
6868
|
+
const sharedAbs = path20.join(
|
|
6404
6869
|
repoRoot,
|
|
6405
|
-
...
|
|
6870
|
+
...SHARED_INSTRUCTIONS_REPO_PATH.split("/")
|
|
6406
6871
|
);
|
|
6407
6872
|
if (!await fs.exists(sharedAbs)) {
|
|
6408
6873
|
log.warn(
|
|
6409
|
-
`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.`
|
|
6410
6875
|
);
|
|
6411
6876
|
return;
|
|
6412
6877
|
}
|
|
@@ -6414,7 +6879,7 @@ async function deploySharedInstructions(repoRoot, toolsInScope, dryRun) {
|
|
|
6414
6879
|
const content = await fs.read(sharedAbs);
|
|
6415
6880
|
for (const target of sharedInstructionsTargets()) {
|
|
6416
6881
|
if (!inScope.has(target.tool)) continue;
|
|
6417
|
-
const dest =
|
|
6882
|
+
const dest = path20.join(toolHomeDir(target.tool), target.relPath);
|
|
6418
6883
|
if (dryRun) {
|
|
6419
6884
|
log.step(`Would deploy shared instructions -> ${dest}`);
|
|
6420
6885
|
continue;
|
|
@@ -6471,7 +6936,7 @@ async function fallbackActions(ctx, tool, data) {
|
|
|
6471
6936
|
const out2 = [];
|
|
6472
6937
|
for (const file of data.files) {
|
|
6473
6938
|
const rel = stripFilesPrefix(tool, file.repoPath);
|
|
6474
|
-
const dest =
|
|
6939
|
+
const dest = path20.join(ctx.toolHome, ...rel.split("/"));
|
|
6475
6940
|
const overwrites = await ctx.fs.exists(dest);
|
|
6476
6941
|
if (ctx.sourceOfTruth === "local" && overwrites) continue;
|
|
6477
6942
|
out2.push({
|
|
@@ -6484,7 +6949,7 @@ async function fallbackActions(ctx, tool, data) {
|
|
|
6484
6949
|
}
|
|
6485
6950
|
for (const link of data.symlinks) {
|
|
6486
6951
|
const rel = stripFilesPrefix(tool, link.repoPath);
|
|
6487
|
-
const dest =
|
|
6952
|
+
const dest = path20.join(ctx.toolHome, ...rel.split("/"));
|
|
6488
6953
|
const overwrites = await ctx.fs.statKind(dest) !== "missing";
|
|
6489
6954
|
if (ctx.sourceOfTruth === "local" && overwrites) continue;
|
|
6490
6955
|
out2.push({
|
|
@@ -6539,7 +7004,7 @@ function printPlan(plan, meta, l = log) {
|
|
|
6539
7004
|
`Tools: ${plan.tools.length > 0 ? plan.tools.join(", ") : "(none)"}`
|
|
6540
7005
|
);
|
|
6541
7006
|
l.step(
|
|
6542
|
-
`A timestamped safety backup of existing
|
|
7007
|
+
`A timestamped safety backup of existing restore targets WILL be taken first (R14).`
|
|
6543
7008
|
);
|
|
6544
7009
|
if (plan.missingClis.length > 0) {
|
|
6545
7010
|
l.step(`CLIs to auto-install: ${plan.missingClis.join(", ")}`);
|
|
@@ -6606,7 +7071,7 @@ async function resolveRepo(repoUrl, optRepo) {
|
|
|
6606
7071
|
if (explicit && explicit.trim() !== "") {
|
|
6607
7072
|
const url = explicit.trim();
|
|
6608
7073
|
const sameAsConfig = config.repo.url.trim() !== "" && config.repo.url.trim() === url;
|
|
6609
|
-
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));
|
|
6610
7075
|
return { provider: config.repo.provider, url, localPath: localPath2 };
|
|
6611
7076
|
}
|
|
6612
7077
|
if (!config.repo.url || config.repo.url.trim() === "") {
|
|
@@ -6614,7 +7079,7 @@ async function resolveRepo(repoUrl, optRepo) {
|
|
|
6614
7079
|
"No repo to pull from. Pass a repo URL (`arbella pull <repo-url>`) or run `arbella init` first."
|
|
6615
7080
|
);
|
|
6616
7081
|
}
|
|
6617
|
-
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));
|
|
6618
7083
|
return { provider: config.repo.provider, url: config.repo.url, localPath };
|
|
6619
7084
|
}
|
|
6620
7085
|
function slugForUrl(url) {
|
|
@@ -6651,7 +7116,7 @@ async function run4(repoUrl, opts) {
|
|
|
6651
7116
|
});
|
|
6652
7117
|
await ensureRepoReady(repo, authHooks);
|
|
6653
7118
|
const repoRoot = repo.localPath;
|
|
6654
|
-
const metaPath =
|
|
7119
|
+
const metaPath = path20.join(repoRoot, "arbella.json");
|
|
6655
7120
|
if (!await fs.exists(metaPath)) {
|
|
6656
7121
|
throw new Error(
|
|
6657
7122
|
`Not a arbella backup repo: ${metaPath} is missing. Did you point restore at the right repository?`
|
|
@@ -6667,7 +7132,7 @@ async function run4(repoUrl, opts) {
|
|
|
6667
7132
|
}
|
|
6668
7133
|
const config = await loadConfigOrDefault();
|
|
6669
7134
|
const flagTools = parseToolsFlag(opts.tools);
|
|
6670
|
-
const tools =
|
|
7135
|
+
const tools = selectToolsForRestore(meta, flagTools, config.tools);
|
|
6671
7136
|
if (tools.length === 0) {
|
|
6672
7137
|
log.warn(
|
|
6673
7138
|
`No tools to restore (the repo + your selection have no overlap). Repo captured: ${meta.tools.join(", ") || "(none)"}.`
|
|
@@ -6691,7 +7156,7 @@ async function run4(repoUrl, opts) {
|
|
|
6691
7156
|
}
|
|
6692
7157
|
const iso = (/* @__PURE__ */ new Date()).toISOString();
|
|
6693
7158
|
log.info("Creating safety backups of existing tool homes (R14)\u2026");
|
|
6694
|
-
const backups = await safetyBackup(tools, iso);
|
|
7159
|
+
const backups = await safetyBackup(tools, iso, os2);
|
|
6695
7160
|
if (backups.length === 0) {
|
|
6696
7161
|
log.debug("restore: no existing tool homes needed backing up.");
|
|
6697
7162
|
}
|
|
@@ -6731,7 +7196,7 @@ async function run4(repoUrl, opts) {
|
|
|
6731
7196
|
printReauthReminder(tools);
|
|
6732
7197
|
if (backups.length > 0) {
|
|
6733
7198
|
log.info(
|
|
6734
|
-
`Previous tool homes were safely backed up under ${
|
|
7199
|
+
`Previous tool homes were safely backed up under ${path20.join(
|
|
6735
7200
|
dataDir(),
|
|
6736
7201
|
"safety-backups"
|
|
6737
7202
|
)} (restore them manually if anything looks wrong).`
|
|
@@ -6791,7 +7256,8 @@ init_cursor();
|
|
|
6791
7256
|
init_capture();
|
|
6792
7257
|
init_capture2();
|
|
6793
7258
|
init_cursor();
|
|
6794
|
-
import
|
|
7259
|
+
import path21 from "path";
|
|
7260
|
+
import process8 from "process";
|
|
6795
7261
|
var ADAPTERS = {
|
|
6796
7262
|
claude: { adapter: claudeAdapter, capture },
|
|
6797
7263
|
codex: { adapter: codexAdapter, capture: capture2 },
|
|
@@ -6809,8 +7275,8 @@ async function run5(opts) {
|
|
|
6809
7275
|
await ensureLocalClone(config.repo);
|
|
6810
7276
|
const repoRoot = config.repo.localPath;
|
|
6811
7277
|
const repoInitialized = await isRepoInitialized(repoRoot);
|
|
6812
|
-
const claudeMd = await readIfExists2(
|
|
6813
|
-
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"));
|
|
6814
7280
|
const sharing = shouldShareInstructions(claudeMd, agentsMd);
|
|
6815
7281
|
const toolStatuses = [];
|
|
6816
7282
|
for (const tool of config.tools) {
|
|
@@ -6872,8 +7338,8 @@ async function run5(opts) {
|
|
|
6872
7338
|
const change = await classifyFile(repoRoot, file);
|
|
6873
7339
|
if (change.kind !== "unchanged") sharedChanges.push(change);
|
|
6874
7340
|
} else {
|
|
6875
|
-
if (await committedFileExists(repoRoot,
|
|
6876
|
-
sharedChanges.push({ repoPath:
|
|
7341
|
+
if (await committedFileExists(repoRoot, SHARED_INSTRUCTIONS_REPO_PATH)) {
|
|
7342
|
+
sharedChanges.push({ repoPath: SHARED_INSTRUCTIONS_REPO_PATH, kind: "removed" });
|
|
6877
7343
|
}
|
|
6878
7344
|
}
|
|
6879
7345
|
const clean = sharedChanges.length === 0 && toolStatuses.every(
|
|
@@ -6889,7 +7355,7 @@ async function run5(opts) {
|
|
|
6889
7355
|
clean
|
|
6890
7356
|
};
|
|
6891
7357
|
if (opts.json) {
|
|
6892
|
-
|
|
7358
|
+
process8.stdout.write(serialize(report));
|
|
6893
7359
|
return;
|
|
6894
7360
|
}
|
|
6895
7361
|
printHuman(report);
|
|
@@ -6903,6 +7369,7 @@ function buildCaptureContext2(tool, includeSecrets, includeMemories) {
|
|
|
6903
7369
|
templater,
|
|
6904
7370
|
vars: buildVariables(toolHome),
|
|
6905
7371
|
os: detectOS(),
|
|
7372
|
+
env: process8.env,
|
|
6906
7373
|
toolHome,
|
|
6907
7374
|
includeSecrets,
|
|
6908
7375
|
includeMemories,
|
|
@@ -7108,7 +7575,7 @@ async function walkRepoFiles(baseAbs, basePosix) {
|
|
|
7108
7575
|
const entries = await fs.list(baseAbs);
|
|
7109
7576
|
for (const name of entries) {
|
|
7110
7577
|
if (name === ".git") continue;
|
|
7111
|
-
const childAbs =
|
|
7578
|
+
const childAbs = path21.join(baseAbs, name);
|
|
7112
7579
|
const childPosix = `${basePosix}/${name}`;
|
|
7113
7580
|
const kind = await fs.statKind(childAbs);
|
|
7114
7581
|
if (kind === "symlink") {
|
|
@@ -7122,7 +7589,7 @@ async function walkRepoFiles(baseAbs, basePosix) {
|
|
|
7122
7589
|
return out2;
|
|
7123
7590
|
}
|
|
7124
7591
|
function repoAbsPath(repoRoot, repoPath) {
|
|
7125
|
-
return
|
|
7592
|
+
return path21.join(repoRoot, ...repoPath.split("/"));
|
|
7126
7593
|
}
|
|
7127
7594
|
async function committedFileExists(repoRoot, repoPath) {
|
|
7128
7595
|
const kind = await fs.statKind(repoAbsPath(repoRoot, repoPath));
|
|
@@ -7233,7 +7700,7 @@ function renderChangeLine(c) {
|
|
|
7233
7700
|
}
|
|
7234
7701
|
|
|
7235
7702
|
// src/commands/secrets.ts
|
|
7236
|
-
import
|
|
7703
|
+
import path23 from "path";
|
|
7237
7704
|
import * as clack2 from "@clack/prompts";
|
|
7238
7705
|
|
|
7239
7706
|
// src/core/secrets/index.ts
|
|
@@ -7246,7 +7713,7 @@ import {
|
|
|
7246
7713
|
scryptSync
|
|
7247
7714
|
} from "crypto";
|
|
7248
7715
|
import { promises as fsp4 } from "fs";
|
|
7249
|
-
import
|
|
7716
|
+
import path22 from "path";
|
|
7250
7717
|
var SCRYPT_PARAMS = {
|
|
7251
7718
|
N: 1 << 15,
|
|
7252
7719
|
// 32768
|
|
@@ -7341,7 +7808,7 @@ async function gatherSecretRefs(toolId) {
|
|
|
7341
7808
|
const refs = [];
|
|
7342
7809
|
const home4 = toolHomeDir(toolId);
|
|
7343
7810
|
for (const spec of SECRET_FILES_BY_TOOL[toolId]) {
|
|
7344
|
-
const abs =
|
|
7811
|
+
const abs = path22.join(home4, spec.relPath);
|
|
7345
7812
|
if (await fs.exists(abs)) {
|
|
7346
7813
|
refs.push({
|
|
7347
7814
|
tool: toolId,
|
|
@@ -7363,10 +7830,10 @@ async function collectSecretFiles(refs, createdAt) {
|
|
|
7363
7830
|
const dedupeKey = `${ref.tool}:${relPath}`;
|
|
7364
7831
|
if (seen.has(dedupeKey)) continue;
|
|
7365
7832
|
seen.add(dedupeKey);
|
|
7366
|
-
const abs =
|
|
7833
|
+
const abs = path22.join(toolHomeDir(ref.tool), relPath);
|
|
7367
7834
|
if (!await fs.exists(abs)) continue;
|
|
7368
7835
|
const bytes = await fs.readBytes(abs);
|
|
7369
|
-
const mode = await
|
|
7836
|
+
const mode = await readMode2(abs);
|
|
7370
7837
|
entries.push({
|
|
7371
7838
|
tool: ref.tool,
|
|
7372
7839
|
relPath,
|
|
@@ -7380,9 +7847,9 @@ async function applySecretBundle(bundle) {
|
|
|
7380
7847
|
for (const entry of bundle.entries) {
|
|
7381
7848
|
const home4 = toolHomeDir(entry.tool);
|
|
7382
7849
|
const rel = entry.relPath.replace(/\\/g, "/");
|
|
7383
|
-
const dest =
|
|
7384
|
-
const homeResolved =
|
|
7385
|
-
const withinHome = dest === homeResolved || dest.startsWith(homeResolved +
|
|
7850
|
+
const dest = path22.resolve(home4, rel);
|
|
7851
|
+
const homeResolved = path22.resolve(home4);
|
|
7852
|
+
const withinHome = dest === homeResolved || dest.startsWith(homeResolved + path22.sep);
|
|
7386
7853
|
if (!withinHome) {
|
|
7387
7854
|
throw new Error(
|
|
7388
7855
|
`Refusing to write secret outside ${entry.tool} home (suspicious path: ${rel}).`
|
|
@@ -7393,7 +7860,7 @@ async function applySecretBundle(bundle) {
|
|
|
7393
7860
|
await fs.writeBytes(dest, bytes, mode);
|
|
7394
7861
|
}
|
|
7395
7862
|
}
|
|
7396
|
-
async function
|
|
7863
|
+
async function readMode2(abs) {
|
|
7397
7864
|
try {
|
|
7398
7865
|
const st = await fsp4.lstat(abs);
|
|
7399
7866
|
const m = st.mode & 4095;
|
|
@@ -7498,7 +7965,7 @@ async function runExport(opts) {
|
|
|
7498
7965
|
return;
|
|
7499
7966
|
}
|
|
7500
7967
|
const blob = encryptBundle(bundle, passphrase);
|
|
7501
|
-
const outPath =
|
|
7968
|
+
const outPath = path23.resolve(opts.out);
|
|
7502
7969
|
await fs.write(outPath, blob + "\n", 384);
|
|
7503
7970
|
clack2.note(
|
|
7504
7971
|
[
|
|
@@ -7507,7 +7974,7 @@ async function runExport(opts) {
|
|
|
7507
7974
|
"",
|
|
7508
7975
|
"This blob is safe to copy between machines (it is encrypted with your",
|
|
7509
7976
|
"passphrase). It NEVER goes through git. On the target machine run:",
|
|
7510
|
-
` arbella secrets import --in ${
|
|
7977
|
+
` arbella secrets import --in ${path23.basename(outPath)}`,
|
|
7511
7978
|
"",
|
|
7512
7979
|
"Keep your passphrase safe: without it the blob cannot be decrypted."
|
|
7513
7980
|
].join("\n"),
|
|
@@ -7517,7 +7984,7 @@ async function runExport(opts) {
|
|
|
7517
7984
|
}
|
|
7518
7985
|
async function runImport(opts) {
|
|
7519
7986
|
clack2.intro("arbella secrets import");
|
|
7520
|
-
const inPath =
|
|
7987
|
+
const inPath = path23.resolve(opts.in);
|
|
7521
7988
|
if (!await fs.exists(inPath)) {
|
|
7522
7989
|
clack2.cancel(
|
|
7523
7990
|
`No blob found at:
|
|
@@ -7619,7 +8086,7 @@ function errMessage7(err) {
|
|
|
7619
8086
|
}
|
|
7620
8087
|
|
|
7621
8088
|
// src/index.ts
|
|
7622
|
-
var VERSION =
|
|
8089
|
+
var VERSION = getPackageVersion();
|
|
7623
8090
|
function buildProgram() {
|
|
7624
8091
|
const program = new Command();
|
|
7625
8092
|
const supported = listAdapters().map((a) => a.displayName).join(", ");
|
|
@@ -7648,11 +8115,11 @@ async function main(argv = process.argv) {
|
|
|
7648
8115
|
function isDirectRun() {
|
|
7649
8116
|
const entry = process.argv[1];
|
|
7650
8117
|
if (!entry) return false;
|
|
7651
|
-
const modulePath =
|
|
8118
|
+
const modulePath = fileURLToPath2(import.meta.url);
|
|
7652
8119
|
try {
|
|
7653
|
-
return
|
|
8120
|
+
return fs3.realpathSync(entry) === fs3.realpathSync(modulePath);
|
|
7654
8121
|
} catch {
|
|
7655
|
-
return
|
|
8122
|
+
return path24.resolve(entry) === modulePath;
|
|
7656
8123
|
}
|
|
7657
8124
|
}
|
|
7658
8125
|
function handleTopLevelError(err) {
|