arcane-cli 1.11.5 → 1.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/dist/assets/.arcane/governance/git-conventions.md +2 -2
- package/dist/assets/.arcane/governance/testing-standards.md +1 -1
- package/dist/assets/sign-in/NOTICE.txt +16 -0
- package/dist/assets/sign-in/OFL-chakra-petch.txt +93 -0
- package/dist/assets/sign-in/OFL-rajdhani.txt +93 -0
- package/dist/assets/sign-in/sign-in-callback-denied.html +320 -0
- package/dist/assets/sign-in/sign-in-callback-failure.html +320 -0
- package/dist/assets/sign-in/sign-in-callback-success.html +320 -0
- package/dist/index.js +276 -144
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -6,11 +6,11 @@ const require = __createRequire(import.meta.url);
|
|
|
6
6
|
import { Command } from "commander";
|
|
7
7
|
import { createRequire as createRequire3 } from "module";
|
|
8
8
|
import { fileURLToPath } from "url";
|
|
9
|
-
import { dirname as
|
|
9
|
+
import { dirname as dirname13, join as join23 } from "path";
|
|
10
10
|
|
|
11
11
|
// src/commands/init.ts
|
|
12
12
|
import { select as select3, confirm as confirm2, input as input3 } from "@inquirer/prompts";
|
|
13
|
-
import { dirname as
|
|
13
|
+
import { dirname as dirname5, join as join8 } from "path";
|
|
14
14
|
import chalk2 from "chalk";
|
|
15
15
|
|
|
16
16
|
// src/modules/copier.ts
|
|
@@ -1417,7 +1417,7 @@ var WORDMARK = [
|
|
|
1417
1417
|
var INDENT = " ";
|
|
1418
1418
|
var LOCKUP_WIDTH = INDENT.length + Math.max(...WORDMARK.map((l) => l.length));
|
|
1419
1419
|
function sleep(ms) {
|
|
1420
|
-
return new Promise((
|
|
1420
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
1421
1421
|
}
|
|
1422
1422
|
function isAnimatable() {
|
|
1423
1423
|
return !!process.stdout.isTTY && process.env["TERM"] !== "dumb" && process.env["CI"] !== "true" && process.env["ARCANE_DISABLE_ANIMATION"] !== "true" && (process.stdout.columns ?? 80) >= LOCKUP_WIDTH;
|
|
@@ -2849,7 +2849,7 @@ function buildGitEnv() {
|
|
|
2849
2849
|
function runGit(cwd, args, options = {}) {
|
|
2850
2850
|
const commandClass = options.commandClass ?? classifyGitCommand(args);
|
|
2851
2851
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUTS_MS[commandClass] ?? DEFAULT_TIMEOUTS_MS.write;
|
|
2852
|
-
return new Promise((
|
|
2852
|
+
return new Promise((resolve6, reject) => {
|
|
2853
2853
|
const child = execFile(
|
|
2854
2854
|
"git",
|
|
2855
2855
|
args,
|
|
@@ -2883,7 +2883,7 @@ function runGit(cwd, args, options = {}) {
|
|
|
2883
2883
|
reject(error);
|
|
2884
2884
|
return;
|
|
2885
2885
|
}
|
|
2886
|
-
|
|
2886
|
+
resolve6({ stdout, stderr });
|
|
2887
2887
|
}
|
|
2888
2888
|
);
|
|
2889
2889
|
child.stdin?.end();
|
|
@@ -3336,7 +3336,27 @@ async function offerRegistryScaffold(targetDir, businessRoot) {
|
|
|
3336
3336
|
// src/modules/push-safety.ts
|
|
3337
3337
|
import { mkdir as mkdir5, readFile as readFile7, writeFile as writeFile8, chmod, access as access3, rm as rm4, readdir as readdir6 } from "fs/promises";
|
|
3338
3338
|
import { constants } from "fs";
|
|
3339
|
-
import { join as
|
|
3339
|
+
import { join as join7, resolve as resolve3, dirname as dirname4, basename as basename2 } from "path";
|
|
3340
|
+
|
|
3341
|
+
// src/modules/real-path.ts
|
|
3342
|
+
import { realpathSync } from "fs";
|
|
3343
|
+
import { basename, dirname as dirname3, join as join6, resolve as resolve2 } from "path";
|
|
3344
|
+
function resolveRealPath(path4) {
|
|
3345
|
+
const missing = [];
|
|
3346
|
+
let existing = path4;
|
|
3347
|
+
for (; ; ) {
|
|
3348
|
+
try {
|
|
3349
|
+
return join6(realpathSync.native(existing), ...missing);
|
|
3350
|
+
} catch {
|
|
3351
|
+
const parent = dirname3(existing);
|
|
3352
|
+
if (parent === existing) return resolve2(path4);
|
|
3353
|
+
missing.unshift(basename(existing));
|
|
3354
|
+
existing = parent;
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
|
|
3359
|
+
// src/modules/push-safety.ts
|
|
3340
3360
|
var DISABLED_PUSH_URL = "arcane-push-blocked-run-spell-unblock-push://this-repository";
|
|
3341
3361
|
var NO_PUSH_URL_MARKER = (remote) => `remote.${remote}.arcaneHadNoPushUrl`;
|
|
3342
3362
|
var ARCANE_HOOKS_DIR = ".arcane/hooks";
|
|
@@ -3365,10 +3385,12 @@ function isArcaneHooksPath(root, value) {
|
|
|
3365
3385
|
const windows = process.platform === "win32";
|
|
3366
3386
|
const normalize = (p) => {
|
|
3367
3387
|
const separated = windows ? p.replace(/\\/g, "/") : p;
|
|
3368
|
-
const
|
|
3388
|
+
const resolved = resolve3(root, separated);
|
|
3389
|
+
const absolute = (windows ? resolved.replace(/\\/g, "/") : resolved).replace(/\/+$/, "");
|
|
3369
3390
|
return windows ? absolute.toLowerCase() : absolute;
|
|
3370
3391
|
};
|
|
3371
|
-
|
|
3392
|
+
if (normalize(value) === normalize(ARCANE_HOOKS_DIR)) return true;
|
|
3393
|
+
return normalize(resolveRealPath(resolve3(root, value))) === normalize(join7(resolveRealPath(root), ARCANE_HOOKS_DIR));
|
|
3372
3394
|
}
|
|
3373
3395
|
var PRE_PUSH_HOOK_BODY = `#!/bin/sh
|
|
3374
3396
|
# Installed by Arcane because this repository's .arcane.json sets
|
|
@@ -3413,8 +3435,8 @@ async function repositoryRoot(cwd) {
|
|
|
3413
3435
|
]);
|
|
3414
3436
|
const commonDir = stdout.trim();
|
|
3415
3437
|
if (commonDir !== "") {
|
|
3416
|
-
const parent =
|
|
3417
|
-
if (
|
|
3438
|
+
const parent = dirname4(commonDir);
|
|
3439
|
+
if (basename2(commonDir) === ".git" && parent !== "") return parent;
|
|
3418
3440
|
}
|
|
3419
3441
|
} catch {
|
|
3420
3442
|
}
|
|
@@ -3427,10 +3449,11 @@ async function repositoryRoot(cwd) {
|
|
|
3427
3449
|
return cwd;
|
|
3428
3450
|
}
|
|
3429
3451
|
async function hookFilePath(cwd, hookName = "pre-push") {
|
|
3430
|
-
return
|
|
3452
|
+
return join7(await repositoryRoot(cwd), ARCANE_HOOKS_DIR, hookName);
|
|
3431
3453
|
}
|
|
3432
3454
|
async function hooksPathValue(cwd) {
|
|
3433
|
-
|
|
3455
|
+
const value = join7(await repositoryRoot(cwd), ARCANE_HOOKS_DIR);
|
|
3456
|
+
return process.platform === "win32" ? value.replace(/\\/g, "/") : value;
|
|
3434
3457
|
}
|
|
3435
3458
|
async function installHook(cwd, hookName, body) {
|
|
3436
3459
|
const root = await repositoryRoot(cwd);
|
|
@@ -3449,11 +3472,11 @@ async function installHook(cwd, hookName, body) {
|
|
|
3449
3472
|
return { status: "refused-default-hooks", hooks: displaced };
|
|
3450
3473
|
}
|
|
3451
3474
|
}
|
|
3452
|
-
const hookPath =
|
|
3475
|
+
const hookPath = join7(root, ARCANE_HOOKS_DIR, hookName);
|
|
3453
3476
|
if (existing !== void 0 && await hookFileMatches(cwd, hookName, body)) {
|
|
3454
3477
|
return { status: "already-ours" };
|
|
3455
3478
|
}
|
|
3456
|
-
await mkdir5(
|
|
3479
|
+
await mkdir5(join7(root, ARCANE_HOOKS_DIR), { recursive: true });
|
|
3457
3480
|
await writeFile8(hookPath, body, "utf-8");
|
|
3458
3481
|
await chmod(hookPath, 493);
|
|
3459
3482
|
await runGit(cwd, ["config", "--local", "core.hooksPath", await hooksPathValue(cwd)]);
|
|
@@ -3498,7 +3521,7 @@ async function defaultHooks(cwd) {
|
|
|
3498
3521
|
let hooksDir;
|
|
3499
3522
|
try {
|
|
3500
3523
|
const { stdout } = await runGit(cwd, ["rev-parse", "--git-common-dir"]);
|
|
3501
|
-
hooksDir =
|
|
3524
|
+
hooksDir = join7(resolve3(cwd, stdout.trim()), "hooks");
|
|
3502
3525
|
} catch {
|
|
3503
3526
|
return [];
|
|
3504
3527
|
}
|
|
@@ -3534,7 +3557,7 @@ async function removeHook(cwd, hookName) {
|
|
|
3534
3557
|
await rm4(await hookFilePath(cwd, hookName), { force: true });
|
|
3535
3558
|
} catch {
|
|
3536
3559
|
}
|
|
3537
|
-
const hooksDir =
|
|
3560
|
+
const hooksDir = join7(root, ARCANE_HOOKS_DIR);
|
|
3538
3561
|
let remaining = [];
|
|
3539
3562
|
try {
|
|
3540
3563
|
remaining = await readdir6(hooksDir);
|
|
@@ -3937,7 +3960,7 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
|
|
|
3937
3960
|
}
|
|
3938
3961
|
let flags = {};
|
|
3939
3962
|
try {
|
|
3940
|
-
flags = resolveManifestFlags(options.manifestFlags, {},
|
|
3963
|
+
flags = resolveManifestFlags(options.manifestFlags, {}, join8(targetDir, ".arcane.json"));
|
|
3941
3964
|
} catch (err) {
|
|
3942
3965
|
if (!(err instanceof ManifestFlagError)) throw err;
|
|
3943
3966
|
console.error(`
|
|
@@ -4072,7 +4095,7 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
|
|
|
4072
4095
|
const installedFiles = [];
|
|
4073
4096
|
const fileHashes = {};
|
|
4074
4097
|
for (const file of component.files) {
|
|
4075
|
-
const srcPath =
|
|
4098
|
+
const srcPath = join8(assetsDir, component.sourceOverrides?.[file] ?? file);
|
|
4076
4099
|
if (options.dryRun) {
|
|
4077
4100
|
printDryRun(`Would copy: ${file}`);
|
|
4078
4101
|
} else {
|
|
@@ -4089,7 +4112,7 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
|
|
|
4089
4112
|
fileCount++;
|
|
4090
4113
|
}
|
|
4091
4114
|
for (const dir of component.directories ?? []) {
|
|
4092
|
-
const srcDirPath =
|
|
4115
|
+
const srcDirPath = join8(assetsDir, dir);
|
|
4093
4116
|
if (options.dryRun) {
|
|
4094
4117
|
printDryRun(`Would copy directory: ${dir}/`);
|
|
4095
4118
|
} else {
|
|
@@ -4118,7 +4141,7 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
|
|
|
4118
4141
|
if (options.dryRun) {
|
|
4119
4142
|
if (hasAgents) {
|
|
4120
4143
|
const agentFiles = await import("fs").then(
|
|
4121
|
-
(fsModule) => fsModule.promises.readdir(
|
|
4144
|
+
(fsModule) => fsModule.promises.readdir(join8(assetsDir, "agents"))
|
|
4122
4145
|
);
|
|
4123
4146
|
const yamlFiles = agentFiles.filter((f) => f.endsWith(".yaml")).sort();
|
|
4124
4147
|
printDryRun("Would run agent setup (interactive):");
|
|
@@ -4357,7 +4380,7 @@ async function runInitUser(options, storeRoot, assetsDir, packageVersion) {
|
|
|
4357
4380
|
console.log(chalk2.hex("#a855f7")(`
|
|
4358
4381
|
\u2726 Arcane v${packageVersion}
|
|
4359
4382
|
`));
|
|
4360
|
-
const homeDir =
|
|
4383
|
+
const homeDir = dirname5(storeRoot);
|
|
4361
4384
|
const components = USER_TIER_COMPONENTS.map(
|
|
4362
4385
|
(name) => componentForScope(getComponent(name), "user")
|
|
4363
4386
|
);
|
|
@@ -4375,7 +4398,7 @@ async function runInitUser(options, storeRoot, assetsDir, packageVersion) {
|
|
|
4375
4398
|
for (const id of spellIds) {
|
|
4376
4399
|
for (const client2 of ["codex", "claude"]) {
|
|
4377
4400
|
const relativePath = USER_FANOUT_PATHS[client2](id);
|
|
4378
|
-
if (await fileExists(
|
|
4401
|
+
if (await fileExists(join8(homeDir, relativePath))) {
|
|
4379
4402
|
printDryRun(`Would leave alone (exists, not written by Arcane): ~/${relativePath}`);
|
|
4380
4403
|
} else {
|
|
4381
4404
|
printDryRun(`Would write: ~/${relativePath}`);
|
|
@@ -4393,7 +4416,7 @@ async function runInitUser(options, storeRoot, assetsDir, packageVersion) {
|
|
|
4393
4416
|
const installedFiles = [];
|
|
4394
4417
|
const fileHashes = {};
|
|
4395
4418
|
for (const file of component.files) {
|
|
4396
|
-
const srcPath =
|
|
4419
|
+
const srcPath = join8(assetsDir, component.sourceOverrides?.[file] ?? file);
|
|
4397
4420
|
fileHashes[file] = await copyFile(srcPath, storeRoot, file, { force: options.force });
|
|
4398
4421
|
installedFiles.push(file);
|
|
4399
4422
|
fileCount++;
|
|
@@ -4440,23 +4463,23 @@ async function runInitUser(options, storeRoot, assetsDir, packageVersion) {
|
|
|
4440
4463
|
|
|
4441
4464
|
// src/modules/component-install.ts
|
|
4442
4465
|
import { readFile as readFile8, stat, writeFile as writeFile9 } from "fs/promises";
|
|
4443
|
-
import { join as
|
|
4466
|
+
import { join as join9 } from "path";
|
|
4444
4467
|
async function planComponentInstall(component, targetDir, assetsDir, opts = {}) {
|
|
4445
4468
|
const files = [];
|
|
4446
4469
|
const stateOf = async (file, src) => {
|
|
4447
|
-
const dest =
|
|
4470
|
+
const dest = join9(targetDir, file);
|
|
4448
4471
|
if (!await fileExists(dest)) return { file, src, state: "missing" };
|
|
4449
4472
|
if ((await stat(dest)).isDirectory()) return { file, src, state: "directory" };
|
|
4450
4473
|
const same = await hashFile(dest) === await hashFile(src);
|
|
4451
4474
|
return { file, src, state: same ? "identical" : "differs" };
|
|
4452
4475
|
};
|
|
4453
4476
|
for (const file of component.files) {
|
|
4454
|
-
files.push(await stateOf(file,
|
|
4477
|
+
files.push(await stateOf(file, join9(assetsDir, component.sourceOverrides?.[file] ?? file)));
|
|
4455
4478
|
}
|
|
4456
4479
|
for (const dir of component.directories ?? []) {
|
|
4457
|
-
const srcDir =
|
|
4480
|
+
const srcDir = join9(assetsDir, dir);
|
|
4458
4481
|
for (const file of await listDirectoryFiles(srcDir, dir)) {
|
|
4459
|
-
files.push(await stateOf(file,
|
|
4482
|
+
files.push(await stateOf(file, join9(assetsDir, file)));
|
|
4460
4483
|
}
|
|
4461
4484
|
}
|
|
4462
4485
|
const of = (...states) => files.filter((f) => states.includes(f.state));
|
|
@@ -4474,7 +4497,7 @@ async function executeInstallPlan(plan, targetDir) {
|
|
|
4474
4497
|
const written = [];
|
|
4475
4498
|
try {
|
|
4476
4499
|
for (const { file, src, state } of plan.toCopy) {
|
|
4477
|
-
const previous = state === "missing" ? null : await readFile8(
|
|
4500
|
+
const previous = state === "missing" ? null : await readFile8(join9(targetDir, file));
|
|
4478
4501
|
fileHashes[file] = await copyFile(src, targetDir, file, { force: true });
|
|
4479
4502
|
written.push({ file, previous });
|
|
4480
4503
|
files.push(file);
|
|
@@ -4492,7 +4515,7 @@ async function rollback(written, targetDir) {
|
|
|
4492
4515
|
await removeWithin(targetDir, file);
|
|
4493
4516
|
await removeEmptyAncestors(targetDir, file);
|
|
4494
4517
|
} else {
|
|
4495
|
-
await writeFile9(
|
|
4518
|
+
await writeFile9(join9(targetDir, file), previous);
|
|
4496
4519
|
}
|
|
4497
4520
|
} catch {
|
|
4498
4521
|
}
|
|
@@ -4628,7 +4651,7 @@ async function runAdd(names, options, targetDir, assetsDir, packageVersion) {
|
|
|
4628
4651
|
}
|
|
4629
4652
|
|
|
4630
4653
|
// src/commands/update.ts
|
|
4631
|
-
import { dirname as
|
|
4654
|
+
import { dirname as dirname6, join as join11 } from "path";
|
|
4632
4655
|
import { readFile as readFile9, writeFile as writeFile10 } from "fs/promises";
|
|
4633
4656
|
|
|
4634
4657
|
// src/modules/merge3.ts
|
|
@@ -4760,7 +4783,7 @@ function merge3(base, ours, theirs) {
|
|
|
4760
4783
|
}
|
|
4761
4784
|
|
|
4762
4785
|
// src/modules/registry-changes.ts
|
|
4763
|
-
import { join as
|
|
4786
|
+
import { join as join10 } from "path";
|
|
4764
4787
|
function findMissingRequires(installed, profile) {
|
|
4765
4788
|
const names = new Set(installed.flatMap((c) => LEGACY_COMPONENT_MIGRATIONS[c.name] ?? [c.name]));
|
|
4766
4789
|
const promised = listProfiles().find((p) => p.id === profile)?.components;
|
|
@@ -4806,7 +4829,7 @@ async function findNewlyAvailable(targetDir, profile, installed, spellScope) {
|
|
|
4806
4829
|
const alreadyPresent = [];
|
|
4807
4830
|
if (component.skipExisting) {
|
|
4808
4831
|
for (const file of component.files) {
|
|
4809
|
-
if (await fileExists(
|
|
4832
|
+
if (await fileExists(join10(targetDir, file))) alreadyPresent.push(file);
|
|
4810
4833
|
}
|
|
4811
4834
|
if (component.files.length > 0 && alreadyPresent.length === component.files.length) continue;
|
|
4812
4835
|
}
|
|
@@ -5065,7 +5088,7 @@ async function resolveOrphan(targetDir, file, recordedHash, prune) {
|
|
|
5065
5088
|
console.log(` ! Refused (path escapes the target directory): ${file}`);
|
|
5066
5089
|
return "reported";
|
|
5067
5090
|
}
|
|
5068
|
-
const filePath =
|
|
5091
|
+
const filePath = join11(targetDir, file);
|
|
5069
5092
|
if (!await fileExists(filePath)) return "not-found";
|
|
5070
5093
|
if (!prune) return "reported";
|
|
5071
5094
|
if (recordedHash === void 0) {
|
|
@@ -5087,7 +5110,7 @@ async function isReleasableUserOwnedFile(targetDir, file, recordedHash) {
|
|
|
5087
5110
|
} catch {
|
|
5088
5111
|
return false;
|
|
5089
5112
|
}
|
|
5090
|
-
const filePath =
|
|
5113
|
+
const filePath = join11(targetDir, file);
|
|
5091
5114
|
if (!await fileExists(filePath)) return false;
|
|
5092
5115
|
if (recordedHash === void 0) return true;
|
|
5093
5116
|
return !await fileMatchesHash(filePath, recordedHash);
|
|
@@ -5098,7 +5121,7 @@ async function retainReportedOrphan(targetDir, file, recordedHash, into) {
|
|
|
5098
5121
|
} catch {
|
|
5099
5122
|
return;
|
|
5100
5123
|
}
|
|
5101
|
-
if (!await fileExists(
|
|
5124
|
+
if (!await fileExists(join11(targetDir, file))) return;
|
|
5102
5125
|
if (!into.files.includes(file)) into.files.push(file);
|
|
5103
5126
|
if (recordedHash !== void 0) {
|
|
5104
5127
|
into.fileHashes ??= {};
|
|
@@ -5127,7 +5150,7 @@ async function findMissingTrackedFiles(targetDir, components, scope = "repo", sp
|
|
|
5127
5150
|
const candidates = readopted.has(installed.name) ? component.files : installed.files;
|
|
5128
5151
|
for (const file of candidates) {
|
|
5129
5152
|
if (!component.files.includes(file)) continue;
|
|
5130
|
-
if (!await fileExists(
|
|
5153
|
+
if (!await fileExists(join11(targetDir, file))) missing.push(file);
|
|
5131
5154
|
}
|
|
5132
5155
|
}
|
|
5133
5156
|
return missing;
|
|
@@ -5169,7 +5192,7 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
5169
5192
|
const movedFilesPresent = /* @__PURE__ */ new Set();
|
|
5170
5193
|
for (const move of COMPONENT_FILE_MOVES) {
|
|
5171
5194
|
for (const file of move.files) {
|
|
5172
|
-
if (await fileExists(
|
|
5195
|
+
if (await fileExists(join11(targetDir, file))) movedFilesPresent.add(file);
|
|
5173
5196
|
}
|
|
5174
5197
|
}
|
|
5175
5198
|
manifest = {
|
|
@@ -5200,7 +5223,7 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
5200
5223
|
return;
|
|
5201
5224
|
}
|
|
5202
5225
|
try {
|
|
5203
|
-
flagPatch = resolveManifestFlags(options.manifestFlags, manifest,
|
|
5226
|
+
flagPatch = resolveManifestFlags(options.manifestFlags, manifest, join11(targetDir, ".arcane.json"));
|
|
5204
5227
|
} catch (err) {
|
|
5205
5228
|
if (!(err instanceof ManifestFlagError)) throw err;
|
|
5206
5229
|
console.error(`
|
|
@@ -5213,7 +5236,7 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
5213
5236
|
const answered = { ...manifest, ...flagPatch };
|
|
5214
5237
|
const spellScope = effectiveSpellScope(manifest);
|
|
5215
5238
|
const readopted = findReadoptedSpellComponents(manifest.components, scope, spellScope);
|
|
5216
|
-
const homeDir =
|
|
5239
|
+
const homeDir = dirname6(targetDir);
|
|
5217
5240
|
for (const line of await missingTierWarning(scope, spellScope)) console.log(line);
|
|
5218
5241
|
if (scope === "repo") {
|
|
5219
5242
|
console.warn(
|
|
@@ -5408,8 +5431,8 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
5408
5431
|
const updatedFiles = [];
|
|
5409
5432
|
const fileHashes = {};
|
|
5410
5433
|
for (const file of component.files) {
|
|
5411
|
-
const srcPath =
|
|
5412
|
-
const targetExists = await fileExists(
|
|
5434
|
+
const srcPath = join11(assetsDir, component.sourceOverrides?.[file] ?? file);
|
|
5435
|
+
const targetExists = await fileExists(join11(targetDir, file));
|
|
5413
5436
|
const preserveExisting = Boolean(component.skipExisting) && targetExists;
|
|
5414
5437
|
const recordedHash = installed.fileHashes?.[file];
|
|
5415
5438
|
if (component.initOnly && !targetExists) {
|
|
@@ -5429,7 +5452,7 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
5429
5452
|
continue;
|
|
5430
5453
|
}
|
|
5431
5454
|
}
|
|
5432
|
-
const editedByOperator = recordedHash !== void 0 && targetExists && !preserveExisting && !await fileMatchesHash(
|
|
5455
|
+
const editedByOperator = recordedHash !== void 0 && targetExists && !preserveExisting && !await fileMatchesHash(join11(targetDir, file), recordedHash);
|
|
5433
5456
|
if (preserveExisting) {
|
|
5434
5457
|
console.log(` ${options.dryRun ? "[dry-run] Would preserve" : "Preserved"}: ${file}`);
|
|
5435
5458
|
} else if (editedByOperator && isClientShimPath(file)) {
|
|
@@ -5456,11 +5479,11 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
5456
5479
|
handled = true;
|
|
5457
5480
|
} else {
|
|
5458
5481
|
const [currentContent, newContent] = await Promise.all([
|
|
5459
|
-
readFile9(
|
|
5482
|
+
readFile9(join11(targetDir, file), "utf-8"),
|
|
5460
5483
|
readFile9(srcPath, "utf-8")
|
|
5461
5484
|
]);
|
|
5462
5485
|
const result = merge3(oldVendorContent, currentContent, newContent);
|
|
5463
|
-
await writeFile10(
|
|
5486
|
+
await writeFile10(join11(targetDir, file), result.content, "utf-8");
|
|
5464
5487
|
fileHashes[file] = await hashFile(srcPath);
|
|
5465
5488
|
if (result.hasConflict) {
|
|
5466
5489
|
conflictedFiles.push(file);
|
|
@@ -5482,7 +5505,7 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
5482
5505
|
}
|
|
5483
5506
|
}
|
|
5484
5507
|
for (const dir of component.directories ?? []) {
|
|
5485
|
-
const srcDirPath =
|
|
5508
|
+
const srcDirPath = join11(assetsDir, dir);
|
|
5486
5509
|
if (options.dryRun) {
|
|
5487
5510
|
console.log(` [dry-run] Would update directory: ${dir}/`);
|
|
5488
5511
|
} else {
|
|
@@ -5642,11 +5665,11 @@ ${verb} ${orphansToReport.length} orphaned file(s) (tracked before this update,
|
|
|
5642
5665
|
}
|
|
5643
5666
|
|
|
5644
5667
|
// src/commands/status.ts
|
|
5645
|
-
import { dirname as
|
|
5668
|
+
import { dirname as dirname8 } from "path";
|
|
5646
5669
|
|
|
5647
5670
|
// src/modules/version-check.ts
|
|
5648
5671
|
import { readFile as readFile10, writeFile as writeFile11, mkdir as mkdir6 } from "fs/promises";
|
|
5649
|
-
import { dirname as
|
|
5672
|
+
import { dirname as dirname7, join as join12 } from "path";
|
|
5650
5673
|
import { homedir as homedir3 } from "os";
|
|
5651
5674
|
import { createRequire } from "module";
|
|
5652
5675
|
var require2 = createRequire(import.meta.url);
|
|
@@ -5660,7 +5683,7 @@ function getFeedUrl() {
|
|
|
5660
5683
|
}
|
|
5661
5684
|
return url;
|
|
5662
5685
|
}
|
|
5663
|
-
var DEFAULT_CACHE_PATH =
|
|
5686
|
+
var DEFAULT_CACHE_PATH = join12(homedir3(), ".arcane", "version-cache.json");
|
|
5664
5687
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
5665
5688
|
async function readCache(cachePath) {
|
|
5666
5689
|
try {
|
|
@@ -5675,7 +5698,7 @@ async function readCache(cachePath) {
|
|
|
5675
5698
|
}
|
|
5676
5699
|
}
|
|
5677
5700
|
async function writeCache(cachePath, latest) {
|
|
5678
|
-
await mkdir6(
|
|
5701
|
+
await mkdir6(dirname7(cachePath), { recursive: true });
|
|
5679
5702
|
const entry = { latest, checkedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
5680
5703
|
await writeFile11(cachePath, JSON.stringify(entry, null, 2), "utf-8");
|
|
5681
5704
|
}
|
|
@@ -5816,7 +5839,7 @@ async function runStatus(targetDir, packageVersion, options = {}) {
|
|
|
5816
5839
|
console.log("");
|
|
5817
5840
|
if (scope === "user") {
|
|
5818
5841
|
console.log(` Scope: user \u2014 ${targetDir}`);
|
|
5819
|
-
const health = await inspectUserTierFanout(
|
|
5842
|
+
const health = await inspectUserTierFanout(dirname8(targetDir), manifest.fanout);
|
|
5820
5843
|
console.log(
|
|
5821
5844
|
` Client files: ${health.total} (${health.byClient.codex} ${FANOUT_CLIENT_LABELS.codex}, ${health.byClient.claude} ${FANOUT_CLIENT_LABELS.claude})`
|
|
5822
5845
|
);
|
|
@@ -5879,7 +5902,7 @@ async function runStatus(targetDir, packageVersion, options = {}) {
|
|
|
5879
5902
|
|
|
5880
5903
|
// src/commands/uninstall.ts
|
|
5881
5904
|
import { rm as rm5 } from "fs/promises";
|
|
5882
|
-
import { dirname as
|
|
5905
|
+
import { dirname as dirname9, join as join13 } from "path";
|
|
5883
5906
|
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
5884
5907
|
async function runUninstall(options, targetDir) {
|
|
5885
5908
|
let manifest;
|
|
@@ -5962,16 +5985,16 @@ Run \`spell unblock-push\` first (interactive), then uninstall.`
|
|
|
5962
5985
|
}
|
|
5963
5986
|
}
|
|
5964
5987
|
}
|
|
5965
|
-
await rm5(
|
|
5988
|
+
await rm5(join13(targetDir, ".arcane.json"), { force: true });
|
|
5966
5989
|
const agentDirs = [
|
|
5967
5990
|
".github/agents",
|
|
5968
5991
|
".arcane/agents",
|
|
5969
5992
|
".arcane/generated"
|
|
5970
5993
|
];
|
|
5971
5994
|
for (const dir of agentDirs) {
|
|
5972
|
-
await rm5(
|
|
5995
|
+
await rm5(join13(targetDir, dir), { recursive: true, force: true });
|
|
5973
5996
|
}
|
|
5974
|
-
await rm5(
|
|
5997
|
+
await rm5(join13(targetDir, ".arcane/agents.yaml"), { force: true });
|
|
5975
5998
|
const markerFiles = [
|
|
5976
5999
|
"CLAUDE.md",
|
|
5977
6000
|
"AGENTS.md",
|
|
@@ -5988,7 +6011,7 @@ Run \`spell unblock-push\` first (interactive), then uninstall.`
|
|
|
5988
6011
|
\u2713 Uninstalled \u2014 ${removed} files removed.`);
|
|
5989
6012
|
}
|
|
5990
6013
|
async function uninstallUserTier(options, storeRoot, manifest) {
|
|
5991
|
-
const homeDir =
|
|
6014
|
+
const homeDir = dirname9(storeRoot);
|
|
5992
6015
|
const storeFiles = manifest.components.flatMap((c) => c.files);
|
|
5993
6016
|
const recordedCount = Object.keys(manifest.fanout ?? {}).length;
|
|
5994
6017
|
if (options.dryRun) {
|
|
@@ -6033,10 +6056,10 @@ async function uninstallUserTier(options, storeRoot, manifest) {
|
|
|
6033
6056
|
console.log(` ! Skipped ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
6034
6057
|
}
|
|
6035
6058
|
}
|
|
6036
|
-
await rm5(
|
|
6037
|
-
await rm5(
|
|
6038
|
-
await rm5(
|
|
6039
|
-
await removeDirectoryIfEmpty(
|
|
6059
|
+
await rm5(join13(storeRoot, ".arcane.json"), { force: true });
|
|
6060
|
+
await rm5(join13(storeRoot, "agents"), { recursive: true, force: true });
|
|
6061
|
+
await rm5(join13(storeRoot, "agents.yaml"), { force: true });
|
|
6062
|
+
await removeDirectoryIfEmpty(join13(storeRoot, USER_TIER_SPELLS_DIR));
|
|
6040
6063
|
await removeDirectoryIfEmpty(storeRoot);
|
|
6041
6064
|
for (const line of describeFanoutOutcomes(fanout.outcomes)) console.log(` ${line}`);
|
|
6042
6065
|
const pruned = fanout.outcomes.filter((o) => o.status === "pruned").length;
|
|
@@ -6049,7 +6072,7 @@ async function uninstallUserTier(options, storeRoot, manifest) {
|
|
|
6049
6072
|
|
|
6050
6073
|
// src/commands/doctor.ts
|
|
6051
6074
|
import { access as access4, readdir as readdir9, readFile as readFile13, mkdir as mkdir7, copyFile as fsCopyFile } from "fs/promises";
|
|
6052
|
-
import { join as
|
|
6075
|
+
import { join as join16, dirname as dirname10 } from "path";
|
|
6053
6076
|
|
|
6054
6077
|
// src/config/incidents.ts
|
|
6055
6078
|
var INCIDENT_QUEUE = [
|
|
@@ -6118,7 +6141,7 @@ function evaluateIncidentGate(records) {
|
|
|
6118
6141
|
|
|
6119
6142
|
// src/modules/denylist-scan.ts
|
|
6120
6143
|
import { readFile as readFile11, readdir as readdir7 } from "fs/promises";
|
|
6121
|
-
import { join as
|
|
6144
|
+
import { join as join14, relative } from "path";
|
|
6122
6145
|
function escapeRegExp(value) {
|
|
6123
6146
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6124
6147
|
}
|
|
@@ -6169,7 +6192,7 @@ async function collectScannableFiles(root, repoRoot = root, extensions = DEFAULT
|
|
|
6169
6192
|
return collected;
|
|
6170
6193
|
}
|
|
6171
6194
|
for (const entry of entries) {
|
|
6172
|
-
const absolutePath =
|
|
6195
|
+
const absolutePath = join14(root, entry.name);
|
|
6173
6196
|
if (entry.isDirectory()) {
|
|
6174
6197
|
if (SKIP_DIRECTORIES.has(entry.name)) continue;
|
|
6175
6198
|
collected.push(...await collectScannableFiles(absolutePath, repoRoot, extensions));
|
|
@@ -6196,7 +6219,7 @@ import { existsSync, readFileSync } from "fs";
|
|
|
6196
6219
|
import { win32 } from "path";
|
|
6197
6220
|
var EXTERNAL_CLI_TIMEOUT_MS = 1e4;
|
|
6198
6221
|
function execFileWithTimeout(file, args, timeoutMs, options = {}) {
|
|
6199
|
-
return new Promise((
|
|
6222
|
+
return new Promise((resolve6, reject) => {
|
|
6200
6223
|
const child = execFile2(
|
|
6201
6224
|
file,
|
|
6202
6225
|
args,
|
|
@@ -6212,7 +6235,7 @@ function execFileWithTimeout(file, args, timeoutMs, options = {}) {
|
|
|
6212
6235
|
reject(error);
|
|
6213
6236
|
return;
|
|
6214
6237
|
}
|
|
6215
|
-
|
|
6238
|
+
resolve6({ stdout, stderr });
|
|
6216
6239
|
}
|
|
6217
6240
|
);
|
|
6218
6241
|
child.stdin?.end();
|
|
@@ -6430,7 +6453,7 @@ function parseAdoRemote(url) {
|
|
|
6430
6453
|
|
|
6431
6454
|
// src/modules/placeholders.ts
|
|
6432
6455
|
import { readdir as readdir8, readFile as readFile12 } from "fs/promises";
|
|
6433
|
-
import { join as
|
|
6456
|
+
import { join as join15 } from "path";
|
|
6434
6457
|
var PLACEHOLDER_STANDARD_FILE = "spell-authoring-standards.md";
|
|
6435
6458
|
var RUNTIME_PLACEHOLDERS_START = "<!-- runtime-placeholders:start -->";
|
|
6436
6459
|
var RUNTIME_PLACEHOLDERS_END = "<!-- runtime-placeholders:end -->";
|
|
@@ -6525,7 +6548,7 @@ function parseRuntimePlaceholders(standardContent) {
|
|
|
6525
6548
|
async function scanGovernancePlaceholders(governanceDir) {
|
|
6526
6549
|
let standard;
|
|
6527
6550
|
try {
|
|
6528
|
-
standard = await readFile12(
|
|
6551
|
+
standard = await readFile12(join15(governanceDir, PLACEHOLDER_STANDARD_FILE), "utf8");
|
|
6529
6552
|
} catch (err) {
|
|
6530
6553
|
if (err.code === "ENOENT") {
|
|
6531
6554
|
return { status: "skipped", reason: `${PLACEHOLDER_STANDARD_FILE} is not installed` };
|
|
@@ -6544,7 +6567,7 @@ async function scanGovernancePlaceholders(governanceDir) {
|
|
|
6544
6567
|
const templateDocs = [];
|
|
6545
6568
|
const findings = [];
|
|
6546
6569
|
for (const file of files) {
|
|
6547
|
-
const content = await readFile12(
|
|
6570
|
+
const content = await readFile12(join15(governanceDir, file), "utf8");
|
|
6548
6571
|
const status = parseFrontmatterStatus(content);
|
|
6549
6572
|
if (status === "template") templateDocs.push(file);
|
|
6550
6573
|
if (status !== "active") continue;
|
|
@@ -6575,8 +6598,8 @@ async function checkNodeVersion() {
|
|
|
6575
6598
|
async function checkVSCodeExtension(id, label2, homeDir = resolveHomeDir()) {
|
|
6576
6599
|
const name = `VS Code extension: ${label2}`;
|
|
6577
6600
|
const flavours = [
|
|
6578
|
-
{ dir:
|
|
6579
|
-
{ dir:
|
|
6601
|
+
{ dir: join16(homeDir, ".vscode", "extensions"), bin: "code" },
|
|
6602
|
+
{ dir: join16(homeDir, ".vscode-insiders", "extensions"), bin: "code-insiders" }
|
|
6580
6603
|
];
|
|
6581
6604
|
let installBin = null;
|
|
6582
6605
|
for (const flavour of flavours) {
|
|
@@ -6604,11 +6627,11 @@ async function checkVSCodeExtension(id, label2, homeDir = resolveHomeDir()) {
|
|
|
6604
6627
|
}
|
|
6605
6628
|
async function checkArcaneManifest(targetDir) {
|
|
6606
6629
|
const name = "Arcane manifest (.arcane.json)";
|
|
6607
|
-
const manifestPath2 =
|
|
6630
|
+
const manifestPath2 = join16(targetDir, ".arcane.json");
|
|
6608
6631
|
try {
|
|
6609
6632
|
await access4(manifestPath2);
|
|
6610
6633
|
} catch {
|
|
6611
|
-
const selfHostedManifestPath =
|
|
6634
|
+
const selfHostedManifestPath = join16(targetDir, "src", "assets", ".arcane.json");
|
|
6612
6635
|
try {
|
|
6613
6636
|
const raw = await readFile13(selfHostedManifestPath, "utf8");
|
|
6614
6637
|
const manifest = JSON.parse(raw);
|
|
@@ -6714,7 +6737,7 @@ async function checkSessionContinuity(targetDir) {
|
|
|
6714
6737
|
const results = [];
|
|
6715
6738
|
for (const file of SESSION_CONTINUITY_FILES) {
|
|
6716
6739
|
const name = `Session file: ${file}`;
|
|
6717
|
-
const filePath =
|
|
6740
|
+
const filePath = join16(targetDir, file);
|
|
6718
6741
|
try {
|
|
6719
6742
|
await access4(filePath);
|
|
6720
6743
|
results.push({ name, passed: true, message: "present" });
|
|
@@ -6732,12 +6755,12 @@ async function checkSessionContinuity(targetDir) {
|
|
|
6732
6755
|
async function fixSessionContinuity(targetDir, assetsDir) {
|
|
6733
6756
|
const created = [];
|
|
6734
6757
|
for (const file of SESSION_CONTINUITY_FILES) {
|
|
6735
|
-
const destPath =
|
|
6758
|
+
const destPath = join16(targetDir, file);
|
|
6736
6759
|
try {
|
|
6737
6760
|
await access4(destPath);
|
|
6738
6761
|
} catch {
|
|
6739
|
-
const srcPath =
|
|
6740
|
-
await mkdir7(
|
|
6762
|
+
const srcPath = join16(assetsDir, file);
|
|
6763
|
+
await mkdir7(dirname10(destPath), { recursive: true });
|
|
6741
6764
|
await fsCopyFile(srcPath, destPath);
|
|
6742
6765
|
created.push(file);
|
|
6743
6766
|
}
|
|
@@ -6864,7 +6887,7 @@ async function checkGovernancePlaceholders(targetDir) {
|
|
|
6864
6887
|
const name = "Governance placeholders (ARC-051)";
|
|
6865
6888
|
let scan;
|
|
6866
6889
|
try {
|
|
6867
|
-
scan = await scanGovernancePlaceholders(
|
|
6890
|
+
scan = await scanGovernancePlaceholders(join16(targetDir, ".arcane", "governance"));
|
|
6868
6891
|
} catch (err) {
|
|
6869
6892
|
return {
|
|
6870
6893
|
name,
|
|
@@ -6951,7 +6974,7 @@ async function checkNewlyAvailableComponents(targetDir) {
|
|
|
6951
6974
|
}
|
|
6952
6975
|
async function checkMcpConfig(targetDir) {
|
|
6953
6976
|
const name = "MCP config (per-server timeout, I12)";
|
|
6954
|
-
const filePath =
|
|
6977
|
+
const filePath = join16(targetDir, ".mcp.json");
|
|
6955
6978
|
let raw;
|
|
6956
6979
|
try {
|
|
6957
6980
|
raw = await readFile13(filePath, "utf8");
|
|
@@ -7034,7 +7057,7 @@ function isDelegationsFile(value) {
|
|
|
7034
7057
|
}
|
|
7035
7058
|
async function checkDelegations(targetDir) {
|
|
7036
7059
|
const name = "Standing delegations (T13)";
|
|
7037
|
-
const filePath =
|
|
7060
|
+
const filePath = join16(targetDir, ".arcane", "delegations.json");
|
|
7038
7061
|
let raw;
|
|
7039
7062
|
try {
|
|
7040
7063
|
raw = await readFile13(filePath, "utf8");
|
|
@@ -7147,7 +7170,7 @@ async function checkSpellScope(targetDir, homeDir = resolveHomeDir()) {
|
|
|
7147
7170
|
let probed = false;
|
|
7148
7171
|
const probe = "spell-status.md";
|
|
7149
7172
|
try {
|
|
7150
|
-
const entries = await readdir9(
|
|
7173
|
+
const entries = await readdir9(join16(storeRoot, USER_TIER_SPELLS_DIR));
|
|
7151
7174
|
const spells = entries.filter((e) => e.startsWith("spell-") && e.endsWith(".md"));
|
|
7152
7175
|
spellCount = spells.length;
|
|
7153
7176
|
probed = spells.includes(probe);
|
|
@@ -7261,7 +7284,7 @@ import { readFile as readFile15 } from "fs/promises";
|
|
|
7261
7284
|
|
|
7262
7285
|
// src/modules/ward.ts
|
|
7263
7286
|
import { readdir as readdir10, readFile as readFile14 } from "fs/promises";
|
|
7264
|
-
import { join as
|
|
7287
|
+
import { join as join17, relative as relative2 } from "path";
|
|
7265
7288
|
async function scanFileAllRules(absolutePath, displayPath, rules) {
|
|
7266
7289
|
const findings = [];
|
|
7267
7290
|
let content;
|
|
@@ -7324,7 +7347,7 @@ async function scanFilenames(root, rules, repoRoot = root) {
|
|
|
7324
7347
|
return findings;
|
|
7325
7348
|
}
|
|
7326
7349
|
for (const entry of entries) {
|
|
7327
|
-
const absolutePath =
|
|
7350
|
+
const absolutePath = join17(root, entry.name);
|
|
7328
7351
|
if (entry.isDirectory()) {
|
|
7329
7352
|
if (SKIP_DIRECTORIES.has(entry.name)) continue;
|
|
7330
7353
|
findings.push(...await scanFilenames(absolutePath, rules, repoRoot));
|
|
@@ -7439,7 +7462,7 @@ async function runWardCli(targetDir, options = {}, exclusions = []) {
|
|
|
7439
7462
|
}
|
|
7440
7463
|
|
|
7441
7464
|
// src/commands/unblock-push.ts
|
|
7442
|
-
import { basename as
|
|
7465
|
+
import { basename as basename3, resolve as resolve4 } from "path";
|
|
7443
7466
|
import { input as input4 } from "@inquirer/prompts";
|
|
7444
7467
|
async function runUnblockPush(targetDir) {
|
|
7445
7468
|
let manifest;
|
|
@@ -7471,7 +7494,7 @@ async function runUnblockPush(targetDir) {
|
|
|
7471
7494
|
process.exit(1);
|
|
7472
7495
|
return;
|
|
7473
7496
|
}
|
|
7474
|
-
const repoName =
|
|
7497
|
+
const repoName = basename3(resolve4(targetDir));
|
|
7475
7498
|
if (repoName === "") {
|
|
7476
7499
|
console.error(
|
|
7477
7500
|
"Could not determine a repository name to confirm against (is this repository at a filesystem root?). Refusing to unblock."
|
|
@@ -7756,6 +7779,70 @@ function resolveIdentityEnvironment(env = process.env) {
|
|
|
7756
7779
|
import { createServer } from "http";
|
|
7757
7780
|
import { execFile as execFile3 } from "child_process";
|
|
7758
7781
|
import * as client from "openid-client";
|
|
7782
|
+
|
|
7783
|
+
// src/modules/result-pages.ts
|
|
7784
|
+
import { createHash as createHash3 } from "crypto";
|
|
7785
|
+
import { readFile as readFile16 } from "fs/promises";
|
|
7786
|
+
import { join as join18 } from "path";
|
|
7787
|
+
var RESULT_KINDS = ["success", "denied", "failure"];
|
|
7788
|
+
function resultPagePath(kind) {
|
|
7789
|
+
return join18("sign-in", `sign-in-callback-${kind}.html`);
|
|
7790
|
+
}
|
|
7791
|
+
function styleHashes(html) {
|
|
7792
|
+
return [...html.matchAll(/<style\b[^>]*>([\s\S]*?)<\/style\s*>/gi)].map((match) => {
|
|
7793
|
+
const text = match[1].replace(/\r\n?/g, "\n");
|
|
7794
|
+
return `'sha256-${createHash3("sha256").update(text, "utf8").digest("base64")}'`;
|
|
7795
|
+
});
|
|
7796
|
+
}
|
|
7797
|
+
function resultPageHeaders(html) {
|
|
7798
|
+
const hashes = [...new Set(styleHashes(html))];
|
|
7799
|
+
return {
|
|
7800
|
+
"cache-control": "no-store",
|
|
7801
|
+
"content-security-policy": [
|
|
7802
|
+
"default-src 'none'",
|
|
7803
|
+
`style-src ${hashes.length > 0 ? hashes.join(" ") : "'none'"}`,
|
|
7804
|
+
"img-src data:",
|
|
7805
|
+
"font-src data:",
|
|
7806
|
+
"base-uri 'none'",
|
|
7807
|
+
"form-action 'none'",
|
|
7808
|
+
"frame-ancestors 'none'"
|
|
7809
|
+
].join("; "),
|
|
7810
|
+
"content-type": "text/html; charset=utf-8",
|
|
7811
|
+
"referrer-policy": "no-referrer",
|
|
7812
|
+
"x-content-type-options": "nosniff"
|
|
7813
|
+
};
|
|
7814
|
+
}
|
|
7815
|
+
function resultPage(html) {
|
|
7816
|
+
return { html, headers: resultPageHeaders(html) };
|
|
7817
|
+
}
|
|
7818
|
+
function plainPage(heading, next) {
|
|
7819
|
+
return resultPage(
|
|
7820
|
+
[
|
|
7821
|
+
'<!doctype html><html lang="en"><meta charset="utf-8">',
|
|
7822
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
7823
|
+
`<title>${heading} \u2014 Arcane</title>`,
|
|
7824
|
+
"<style>:root{color-scheme:light dark}body{font-family:system-ui,sans-serif;margin:3rem;line-height:1.5}</style>",
|
|
7825
|
+
`<body><h1>${heading}</h1><p>${next}</p></body></html>`
|
|
7826
|
+
].join("")
|
|
7827
|
+
);
|
|
7828
|
+
}
|
|
7829
|
+
var PLAIN_RESULT_PAGES = {
|
|
7830
|
+
success: plainPage("Signed in to Arcane", "Return to your terminal. Your browser can be closed now."),
|
|
7831
|
+
denied: plainPage("Sign-in was not completed", "Return to your terminal to try again. No new session was stored."),
|
|
7832
|
+
failure: plainPage("Could not finish sign-in", "Return to your terminal for details. No new session was stored.")
|
|
7833
|
+
};
|
|
7834
|
+
async function loadResultPages(assetsDir) {
|
|
7835
|
+
try {
|
|
7836
|
+
const [success, denied, failure] = await Promise.all(
|
|
7837
|
+
RESULT_KINDS.map(async (kind) => resultPage(await readFile16(join18(assetsDir, resultPagePath(kind)), "utf8")))
|
|
7838
|
+
);
|
|
7839
|
+
return { success, denied, failure };
|
|
7840
|
+
} catch {
|
|
7841
|
+
return null;
|
|
7842
|
+
}
|
|
7843
|
+
}
|
|
7844
|
+
|
|
7845
|
+
// src/modules/oidc.ts
|
|
7759
7846
|
var IdentityUnreachableError = class extends Error {
|
|
7760
7847
|
constructor(host, cause) {
|
|
7761
7848
|
super(`Could not reach the identity service at ${host}.`);
|
|
@@ -7769,6 +7856,12 @@ var LoginTimeoutError = class extends Error {
|
|
|
7769
7856
|
this.name = "LoginTimeoutError";
|
|
7770
7857
|
}
|
|
7771
7858
|
};
|
|
7859
|
+
var LoginBrowserUnavailableError = class extends Error {
|
|
7860
|
+
constructor() {
|
|
7861
|
+
super("No browser could be opened. Run `spell login --device-code` and finish signing in on another device.");
|
|
7862
|
+
this.name = "LoginBrowserUnavailableError";
|
|
7863
|
+
}
|
|
7864
|
+
};
|
|
7772
7865
|
var LoginDeniedError = class extends Error {
|
|
7773
7866
|
constructor(code) {
|
|
7774
7867
|
super(`The identity service refused the sign-in (${code}).`);
|
|
@@ -7811,25 +7904,17 @@ function toTokenResult(tokens, previousRefreshToken) {
|
|
|
7811
7904
|
expiresAt: new Date(now.getTime() + seconds * 1e3)
|
|
7812
7905
|
};
|
|
7813
7906
|
}
|
|
7814
|
-
var
|
|
7815
|
-
|
|
7816
|
-
'<body style="font-family:system-ui;margin:3rem"><h1>Signed in to Arcane</h1>',
|
|
7817
|
-
"<p>You can close this window and go back to the terminal.</p></body>"
|
|
7818
|
-
].join("");
|
|
7819
|
-
var FAILED_PAGE = [
|
|
7820
|
-
'<!doctype html><meta charset="utf-8"><title>Arcane</title>',
|
|
7821
|
-
'<body style="font-family:system-ui;margin:3rem"><h1>Sign-in not completed</h1>',
|
|
7822
|
-
"<p>Go back to the terminal for what to do next.</p></body>"
|
|
7823
|
-
].join("");
|
|
7907
|
+
var resultPath = (kind) => `/result/${kind}`;
|
|
7908
|
+
var RESULT_PATHS = new Set(RESULT_KINDS.map(resultPath));
|
|
7824
7909
|
async function browserLogin(config, options) {
|
|
7825
7910
|
const codeVerifier = client.randomPKCECodeVerifier();
|
|
7826
7911
|
const codeChallenge = await client.calculatePKCECodeChallenge(codeVerifier);
|
|
7827
7912
|
const state = client.randomState();
|
|
7828
7913
|
const nonce = client.randomNonce();
|
|
7829
7914
|
const server = createServer();
|
|
7830
|
-
await new Promise((
|
|
7915
|
+
await new Promise((resolve6, reject) => {
|
|
7831
7916
|
server.once("error", reject);
|
|
7832
|
-
server.listen(0, "127.0.0.1", () =>
|
|
7917
|
+
server.listen(0, "127.0.0.1", () => resolve6());
|
|
7833
7918
|
});
|
|
7834
7919
|
const address = server.address();
|
|
7835
7920
|
if (address === null || typeof address === "string") {
|
|
@@ -7845,24 +7930,66 @@ async function browserLogin(config, options) {
|
|
|
7845
7930
|
state,
|
|
7846
7931
|
nonce
|
|
7847
7932
|
});
|
|
7933
|
+
const pages = options.pages ?? PLAIN_RESULT_PAGES;
|
|
7848
7934
|
const timeoutMs = options.timeoutMs ?? 5 * 60 * 1e3;
|
|
7849
7935
|
let timer;
|
|
7936
|
+
let callbackConsumed = false;
|
|
7937
|
+
let browserResult;
|
|
7850
7938
|
const close = () => {
|
|
7851
7939
|
if (timer) clearTimeout(timer);
|
|
7852
7940
|
server.closeAllConnections?.();
|
|
7853
7941
|
server.close();
|
|
7854
7942
|
};
|
|
7855
|
-
const tokens = new Promise((
|
|
7943
|
+
const tokens = new Promise((resolve6, reject) => {
|
|
7856
7944
|
server.on("request", (req, res) => {
|
|
7857
7945
|
const url = new URL(req.url ?? "/", redirectUri);
|
|
7946
|
+
if (RESULT_PATHS.has(url.pathname)) {
|
|
7947
|
+
if (!browserResult || url.pathname !== resultPath(browserResult.kind) || url.search !== "") {
|
|
7948
|
+
res.writeHead(404).end();
|
|
7949
|
+
return;
|
|
7950
|
+
}
|
|
7951
|
+
const served = browserResult;
|
|
7952
|
+
const page = pages[served.kind];
|
|
7953
|
+
const settle = () => {
|
|
7954
|
+
if (served.kind === "success") resolve6(served.result);
|
|
7955
|
+
else reject(served.error);
|
|
7956
|
+
};
|
|
7957
|
+
res.once("finish", settle).once("close", settle);
|
|
7958
|
+
res.writeHead(200, page.headers).end(page.html);
|
|
7959
|
+
return;
|
|
7960
|
+
}
|
|
7858
7961
|
if (url.pathname !== "/" || !url.searchParams.has("code") && !url.searchParams.has("error")) {
|
|
7859
7962
|
res.writeHead(404).end();
|
|
7860
7963
|
return;
|
|
7861
7964
|
}
|
|
7965
|
+
if (callbackConsumed) {
|
|
7966
|
+
res.writeHead(409, { "cache-control": "no-store" }).end();
|
|
7967
|
+
return;
|
|
7968
|
+
}
|
|
7969
|
+
if (url.searchParams.get("state") !== state) {
|
|
7970
|
+
res.writeHead(400, pages.failure.headers).end(pages.failure.html);
|
|
7971
|
+
return;
|
|
7972
|
+
}
|
|
7973
|
+
callbackConsumed = true;
|
|
7974
|
+
const redirectToResult = (result) => {
|
|
7975
|
+
browserResult = result;
|
|
7976
|
+
res.writeHead(303, {
|
|
7977
|
+
"cache-control": "no-store",
|
|
7978
|
+
location: resultPath(result.kind),
|
|
7979
|
+
"referrer-policy": "no-referrer"
|
|
7980
|
+
}).end();
|
|
7981
|
+
if (result.kind === "success") {
|
|
7982
|
+
if (timer) clearTimeout(timer);
|
|
7983
|
+
timer = setTimeout(() => resolve6(result.result), Math.min(timeoutMs, 5e3));
|
|
7984
|
+
}
|
|
7985
|
+
};
|
|
7862
7986
|
const errorCode = url.searchParams.get("error");
|
|
7863
7987
|
if (errorCode) {
|
|
7864
|
-
|
|
7865
|
-
|
|
7988
|
+
const code = errorCode.replace(/[^a-z_]/gi, "");
|
|
7989
|
+
redirectToResult({
|
|
7990
|
+
kind: code === "access_denied" ? "denied" : "failure",
|
|
7991
|
+
error: new LoginDeniedError(code)
|
|
7992
|
+
});
|
|
7866
7993
|
return;
|
|
7867
7994
|
}
|
|
7868
7995
|
client.authorizationCodeGrant(config, url, {
|
|
@@ -7873,11 +8000,9 @@ async function browserLogin(config, options) {
|
|
|
7873
8000
|
}).then(async (response) => {
|
|
7874
8001
|
const result = toTokenResult(response);
|
|
7875
8002
|
await options.onValidated?.(result);
|
|
7876
|
-
|
|
7877
|
-
resolve5(result);
|
|
8003
|
+
redirectToResult({ kind: "success", result });
|
|
7878
8004
|
}).catch((error) => {
|
|
7879
|
-
|
|
7880
|
-
reject(error);
|
|
8005
|
+
redirectToResult({ kind: "failure", error });
|
|
7881
8006
|
});
|
|
7882
8007
|
});
|
|
7883
8008
|
timer = setTimeout(() => reject(new LoginTimeoutError()), timeoutMs);
|
|
@@ -7886,7 +8011,10 @@ async function browserLogin(config, options) {
|
|
|
7886
8011
|
});
|
|
7887
8012
|
try {
|
|
7888
8013
|
const opened = await options.openUrl(authorizationUrl.href);
|
|
7889
|
-
if (!opened)
|
|
8014
|
+
if (!opened) {
|
|
8015
|
+
options.onCannotOpen?.();
|
|
8016
|
+
throw new LoginBrowserUnavailableError();
|
|
8017
|
+
}
|
|
7890
8018
|
return await tokens;
|
|
7891
8019
|
} finally {
|
|
7892
8020
|
close();
|
|
@@ -7904,12 +8032,12 @@ async function refreshSession(config, refreshToken, scopes) {
|
|
|
7904
8032
|
}
|
|
7905
8033
|
function openInDefaultBrowser(url) {
|
|
7906
8034
|
const [command, args] = process.platform === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", url]] : process.platform === "darwin" ? ["open", [url]] : ["xdg-open", [url]];
|
|
7907
|
-
return new Promise((
|
|
8035
|
+
return new Promise((resolve6) => {
|
|
7908
8036
|
try {
|
|
7909
|
-
const child = execFile3(command, args, { windowsHide: true }, (error) =>
|
|
7910
|
-
child.on("error", () =>
|
|
8037
|
+
const child = execFile3(command, args, { windowsHide: true }, (error) => resolve6(!error));
|
|
8038
|
+
child.on("error", () => resolve6(false));
|
|
7911
8039
|
} catch {
|
|
7912
|
-
|
|
8040
|
+
resolve6(false);
|
|
7913
8041
|
}
|
|
7914
8042
|
});
|
|
7915
8043
|
}
|
|
@@ -7918,7 +8046,7 @@ function openInDefaultBrowser(url) {
|
|
|
7918
8046
|
import { execFileSync } from "child_process";
|
|
7919
8047
|
import { mkdirSync, readFileSync as readFileSync2, renameSync, rmSync, writeFileSync, existsSync as existsSync2 } from "fs";
|
|
7920
8048
|
import { userInfo } from "os";
|
|
7921
|
-
import { dirname as
|
|
8049
|
+
import { dirname as dirname11, join as join19 } from "path";
|
|
7922
8050
|
|
|
7923
8051
|
// src/modules/keychain-backend.ts
|
|
7924
8052
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -8007,7 +8135,7 @@ var FileSecretBackend = class {
|
|
|
8007
8135
|
rmSync(this.filePath, { force: true });
|
|
8008
8136
|
return;
|
|
8009
8137
|
}
|
|
8010
|
-
mkdirSync(
|
|
8138
|
+
mkdirSync(dirname11(this.filePath), { recursive: true });
|
|
8011
8139
|
const tmp = `${this.filePath}.${process.pid}.tmp`;
|
|
8012
8140
|
writeFileSync(tmp, JSON.stringify(entries), { encoding: "utf8", mode: 384 });
|
|
8013
8141
|
try {
|
|
@@ -8052,7 +8180,7 @@ function restrictToCurrentUser(filePath) {
|
|
|
8052
8180
|
});
|
|
8053
8181
|
}
|
|
8054
8182
|
function insecureSessionFilePath(homeDir) {
|
|
8055
|
-
return
|
|
8183
|
+
return join19(userTierRoot(homeDir), INSECURE_SESSION_FILE_NAME);
|
|
8056
8184
|
}
|
|
8057
8185
|
function createSessionStorage(options = {}) {
|
|
8058
8186
|
const keychain = new ChunkedSecretStore(options.keychainBackend ?? createKeychainBackend());
|
|
@@ -8118,7 +8246,7 @@ function createSessionStorage(options = {}) {
|
|
|
8118
8246
|
}
|
|
8119
8247
|
|
|
8120
8248
|
// src/commands/login.ts
|
|
8121
|
-
function defaultLoginDeps() {
|
|
8249
|
+
function defaultLoginDeps(assetsDir) {
|
|
8122
8250
|
return {
|
|
8123
8251
|
storage: createSessionStorage(),
|
|
8124
8252
|
environment: () => resolveIdentityEnvironment(),
|
|
@@ -8126,10 +8254,12 @@ function defaultLoginDeps() {
|
|
|
8126
8254
|
browserLogin,
|
|
8127
8255
|
deviceLogin,
|
|
8128
8256
|
openUrl: openInDefaultBrowser,
|
|
8129
|
-
isInteractive: () => Boolean(process.stdin.isTTY && process.stdout.isTTY)
|
|
8257
|
+
isInteractive: () => Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
8258
|
+
resultPages: () => loadResultPages(assetsDir)
|
|
8130
8259
|
};
|
|
8131
8260
|
}
|
|
8132
8261
|
var INSECURE_STORAGE_WARNING = "Insecure storage: the session is kept in a file protected only by your account's file permissions, not in the OS keychain. Run `spell logout` to remove it.";
|
|
8262
|
+
var MISSING_RESULT_PAGES_WARNING = "This arcane-cli install is missing its sign-in result pages (assets/sign-in/), so the browser will show a plain page. Reinstall the package to restore them.";
|
|
8133
8263
|
function safeErrorText(error) {
|
|
8134
8264
|
const message = error instanceof Error ? error.message : String(error);
|
|
8135
8265
|
return message.split("\n")[0].replace(/([?&](?:code|state|id_token|access_token|refresh_token)=)[^&\s]+/gi, "$1[redacted]");
|
|
@@ -8149,7 +8279,7 @@ function describeAccount(record) {
|
|
|
8149
8279
|
const who = record.email ? `${record.email} (${record.sub})` : record.sub;
|
|
8150
8280
|
return record.environment === "development" ? `${who} [development]` : who;
|
|
8151
8281
|
}
|
|
8152
|
-
async function runLogin(options, deps
|
|
8282
|
+
async function runLogin(options, deps) {
|
|
8153
8283
|
let environment;
|
|
8154
8284
|
try {
|
|
8155
8285
|
environment = deps.environment();
|
|
@@ -8195,14 +8325,16 @@ async function runLogin(options, deps = defaultLoginDeps()) {
|
|
|
8195
8325
|
});
|
|
8196
8326
|
saveValidatedSession(result);
|
|
8197
8327
|
} else {
|
|
8328
|
+
const pages = await deps.resultPages();
|
|
8329
|
+
if (!pages) printWarning(MISSING_RESULT_PAGES_WARNING);
|
|
8198
8330
|
printInfo("Opening your browser to sign in to Arcane. Come back here when it says you are signed in.");
|
|
8199
8331
|
await deps.browserLogin(config, {
|
|
8200
8332
|
scopes: SIGN_IN_SCOPES,
|
|
8201
8333
|
openUrl: deps.openUrl,
|
|
8334
|
+
pages: pages ?? void 0,
|
|
8202
8335
|
onValidated: saveValidatedSession,
|
|
8203
|
-
onCannotOpen: (
|
|
8204
|
-
printWarning("No browser could be opened.
|
|
8205
|
-
printInfo(url);
|
|
8336
|
+
onCannotOpen: () => {
|
|
8337
|
+
printWarning("No browser could be opened. Run `spell login --device-code` to sign in on another device.");
|
|
8206
8338
|
}
|
|
8207
8339
|
});
|
|
8208
8340
|
if (!record) throw new Error("The browser sign-in ended without storing a session.");
|
|
@@ -8316,15 +8448,15 @@ async function runWhoami(options, deps = defaultWhoamiDeps()) {
|
|
|
8316
8448
|
|
|
8317
8449
|
// src/commands/report.ts
|
|
8318
8450
|
import { access as access5 } from "fs/promises";
|
|
8319
|
-
import { join as
|
|
8451
|
+
import { join as join22 } from "path";
|
|
8320
8452
|
|
|
8321
8453
|
// src/modules/show-report/generate.ts
|
|
8322
|
-
import { mkdir as mkdir8, readdir as readdir11, readFile as
|
|
8323
|
-
import { basename as
|
|
8454
|
+
import { mkdir as mkdir8, readdir as readdir11, readFile as readFile18, writeFile as writeFile12 } from "fs/promises";
|
|
8455
|
+
import { basename as basename4, dirname as dirname12, isAbsolute, join as join21, relative as relative3, resolve as resolve5 } from "path";
|
|
8324
8456
|
|
|
8325
8457
|
// src/modules/show-report/model.ts
|
|
8326
|
-
import { readFile as
|
|
8327
|
-
import { join as
|
|
8458
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
8459
|
+
import { join as join20 } from "path";
|
|
8328
8460
|
|
|
8329
8461
|
// src/modules/show-report/plan-parser.ts
|
|
8330
8462
|
function parseFrontmatter(content) {
|
|
@@ -8597,7 +8729,7 @@ async function getCast(cwd, fromRef, toRef, generatedOutputs = []) {
|
|
|
8597
8729
|
// src/modules/show-report/model.ts
|
|
8598
8730
|
async function readOptional(path4) {
|
|
8599
8731
|
try {
|
|
8600
|
-
return await
|
|
8732
|
+
return await readFile17(path4, "utf8");
|
|
8601
8733
|
} catch {
|
|
8602
8734
|
return null;
|
|
8603
8735
|
}
|
|
@@ -8693,13 +8825,13 @@ function buildCorrections(ledgerRows, programName) {
|
|
|
8693
8825
|
};
|
|
8694
8826
|
}
|
|
8695
8827
|
async function buildShowReportModel(options) {
|
|
8696
|
-
const planAbsPath =
|
|
8697
|
-
const queueAbsPath =
|
|
8828
|
+
const planAbsPath = join20(options.rootDir, options.planRelPath);
|
|
8829
|
+
const queueAbsPath = join20(options.rootDir, options.queueRelPath);
|
|
8698
8830
|
const ledgerRelPath = "docs/verification-ledger.md";
|
|
8699
|
-
const ledgerAbsPath =
|
|
8700
|
-
const decisionsAbsPath =
|
|
8831
|
+
const ledgerAbsPath = join20(options.rootDir, ledgerRelPath);
|
|
8832
|
+
const decisionsAbsPath = join20(options.rootDir, "DECISIONS.md");
|
|
8701
8833
|
const [planContent, queueContent, ledgerContent, decisionsContent] = await Promise.all([
|
|
8702
|
-
|
|
8834
|
+
readFile17(planAbsPath, "utf8"),
|
|
8703
8835
|
readOptional(queueAbsPath),
|
|
8704
8836
|
readOptional(ledgerAbsPath),
|
|
8705
8837
|
readOptional(decisionsAbsPath)
|
|
@@ -8895,7 +9027,7 @@ function toPosix(path4) {
|
|
|
8895
9027
|
async function programFromPlan(rootDir, slug, planRelPath) {
|
|
8896
9028
|
let planContent;
|
|
8897
9029
|
try {
|
|
8898
|
-
planContent = await
|
|
9030
|
+
planContent = await readFile18(join21(rootDir, planRelPath), "utf8");
|
|
8899
9031
|
} catch {
|
|
8900
9032
|
return { skipped: { slug, reason: `no PLAN.md at ${planRelPath}` } };
|
|
8901
9033
|
}
|
|
@@ -8907,13 +9039,13 @@ async function programFromPlan(rootDir, slug, planRelPath) {
|
|
|
8907
9039
|
program: {
|
|
8908
9040
|
slug,
|
|
8909
9041
|
planRelPath,
|
|
8910
|
-
queueRelPath: toPosix(
|
|
9042
|
+
queueRelPath: toPosix(join21(dirname12(planRelPath), "OPERATOR-QUEUE.md")),
|
|
8911
9043
|
ledgerProgramName: programNameFromTitle(frontmatter.title ?? slug)
|
|
8912
9044
|
}
|
|
8913
9045
|
};
|
|
8914
9046
|
}
|
|
8915
9047
|
async function discoverPrograms(rootDir) {
|
|
8916
|
-
const plansDir =
|
|
9048
|
+
const plansDir = join21(rootDir, "docs", "plans");
|
|
8917
9049
|
let entries;
|
|
8918
9050
|
try {
|
|
8919
9051
|
entries = (await readdir11(plansDir, { withFileTypes: true })).filter((e) => e.isDirectory());
|
|
@@ -8931,23 +9063,23 @@ async function discoverPrograms(rootDir) {
|
|
|
8931
9063
|
return { programs, skipped };
|
|
8932
9064
|
}
|
|
8933
9065
|
async function programFromPlanPath(rootDir, planPath) {
|
|
8934
|
-
const planAbs = isAbsolute(planPath) ? planPath :
|
|
9066
|
+
const planAbs = isAbsolute(planPath) ? planPath : resolve5(rootDir, planPath);
|
|
8935
9067
|
const planRelPath = toPosix(relative3(rootDir, planAbs));
|
|
8936
|
-
return programFromPlan(rootDir,
|
|
9068
|
+
return programFromPlan(rootDir, basename4(dirname12(planAbs)), planRelPath);
|
|
8937
9069
|
}
|
|
8938
9070
|
function normalizeLineEndings2(content) {
|
|
8939
9071
|
return content.replace(/\r\n?/g, "\n");
|
|
8940
9072
|
}
|
|
8941
9073
|
async function readOptionalFile(path4) {
|
|
8942
9074
|
try {
|
|
8943
|
-
return await
|
|
9075
|
+
return await readFile18(path4, "utf8");
|
|
8944
9076
|
} catch (error) {
|
|
8945
9077
|
if (error.code === "ENOENT") return null;
|
|
8946
9078
|
throw error;
|
|
8947
9079
|
}
|
|
8948
9080
|
}
|
|
8949
9081
|
async function generateReport(rootDir, program2, templateHtml, theme = "auto") {
|
|
8950
|
-
const planContent = await
|
|
9082
|
+
const planContent = await readFile18(join21(rootDir, program2.planRelPath), "utf8");
|
|
8951
9083
|
const frontmatter = parseFrontmatter(planContent);
|
|
8952
9084
|
const model = await buildShowReportModel({
|
|
8953
9085
|
rootDir,
|
|
@@ -8967,7 +9099,7 @@ async function generateReport(rootDir, program2, templateHtml, theme = "auto") {
|
|
|
8967
9099
|
};
|
|
8968
9100
|
}
|
|
8969
9101
|
async function runReportGeneration(options) {
|
|
8970
|
-
const templateHtml = await
|
|
9102
|
+
const templateHtml = await readFile18(options.templatePath, "utf8");
|
|
8971
9103
|
const theme = options.theme ?? "auto";
|
|
8972
9104
|
const drifted = [];
|
|
8973
9105
|
const repaired = [];
|
|
@@ -8978,19 +9110,19 @@ async function runReportGeneration(options) {
|
|
|
8978
9110
|
if (!generated.themeApplied) themeSkipped.push(program2.slug);
|
|
8979
9111
|
const missing = JSON.parse(generated.json).sections.flatMap((section) => section.rows).filter((row) => row.descriptionState === "unwritten").map((row) => row.id);
|
|
8980
9112
|
if (missing.length > 0) unwritten.push({ slug: program2.slug, ids: missing });
|
|
8981
|
-
const outAbs = options.outDir ? isAbsolute(options.outDir) ? options.outDir :
|
|
9113
|
+
const outAbs = options.outDir ? isAbsolute(options.outDir) ? options.outDir : resolve5(options.rootDir, options.outDir) : resolve5(options.rootDir, dirname12(program2.planRelPath));
|
|
8982
9114
|
const outRel = toPosix(relative3(options.rootDir, outAbs));
|
|
8983
9115
|
const outputs = [
|
|
8984
9116
|
[`${outRel}/show-report.json`, generated.json],
|
|
8985
9117
|
[`${outRel}/show-report.html`, generated.html]
|
|
8986
9118
|
];
|
|
8987
9119
|
for (const [relPath, expected] of outputs) {
|
|
8988
|
-
const actual = await readOptionalFile(
|
|
9120
|
+
const actual = await readOptionalFile(join21(options.rootDir, relPath));
|
|
8989
9121
|
if (actual === null || normalizeLineEndings2(actual) !== normalizeLineEndings2(expected)) {
|
|
8990
9122
|
drifted.push(`${relPath} does not match a regeneration from its sources.`);
|
|
8991
9123
|
if (options.mode === "write") {
|
|
8992
9124
|
await mkdir8(outAbs, { recursive: true });
|
|
8993
|
-
await writeFile12(
|
|
9125
|
+
await writeFile12(join21(options.rootDir, relPath), expected, "utf8");
|
|
8994
9126
|
repaired.push(relPath);
|
|
8995
9127
|
}
|
|
8996
9128
|
}
|
|
@@ -9001,7 +9133,7 @@ async function runReportGeneration(options) {
|
|
|
9001
9133
|
|
|
9002
9134
|
// src/commands/report.ts
|
|
9003
9135
|
async function runReport(targetDir, options = {}, assetsDir) {
|
|
9004
|
-
const templatePath =
|
|
9136
|
+
const templatePath = join22(assetsDir, "report", "show-report.template.html");
|
|
9005
9137
|
try {
|
|
9006
9138
|
await access5(templatePath);
|
|
9007
9139
|
} catch {
|
|
@@ -9075,10 +9207,10 @@ async function runReport(targetDir, options = {}, assetsDir) {
|
|
|
9075
9207
|
}
|
|
9076
9208
|
|
|
9077
9209
|
// src/index.ts
|
|
9078
|
-
import { realpathSync } from "fs";
|
|
9210
|
+
import { realpathSync as realpathSync2 } from "fs";
|
|
9079
9211
|
var require3 = createRequire3(import.meta.url);
|
|
9080
9212
|
var pkg = require3("../package.json");
|
|
9081
|
-
var ASSETS_DIR = process.env["ARCANE_ASSETS_DIR"] ??
|
|
9213
|
+
var ASSETS_DIR = process.env["ARCANE_ASSETS_DIR"] ?? join23(dirname13(fileURLToPath(import.meta.url)), "assets");
|
|
9082
9214
|
var program = new Command();
|
|
9083
9215
|
var USER_FLAG_DESCRIPTION = "Operate on the per-user tier at ~/.arcane \u2014 spells shared by every repository on this machine, fanned out to ~/.agents/skills (Codex, Copilot) and ~/.claude/commands (Claude Code)";
|
|
9084
9216
|
var USER_TIER_AGENTS_DESCRIPTION = "Operate on the per-user tier at ~/.arcane \u2014 one agent roster shared by every repository on this machine, rendered to ~/.copilot/agents (VS Code agent modes)";
|
|
@@ -9205,7 +9337,7 @@ program.command("login").description("Sign in to your Arcane account (browser by
|
|
|
9205
9337
|
"--insecure-storage",
|
|
9206
9338
|
"Keep the session in a file under ~/.arcane instead of the OS keychain; protected only by your account's file permissions"
|
|
9207
9339
|
).action(async (opts) => {
|
|
9208
|
-
await runLogin({ deviceCode: opts.deviceCode, insecureStorage: opts.insecureStorage });
|
|
9340
|
+
await runLogin({ deviceCode: opts.deviceCode, insecureStorage: opts.insecureStorage }, defaultLoginDeps(ASSETS_DIR));
|
|
9209
9341
|
});
|
|
9210
9342
|
program.command("logout").description("Sign out on this machine: remove the stored session").action(() => {
|
|
9211
9343
|
runLogout();
|
|
@@ -9288,7 +9420,7 @@ agentsCommand.command("list").description("Show the current agent roster with as
|
|
|
9288
9420
|
const scope = opts.user ? "user" : "repo";
|
|
9289
9421
|
await runAgentsList(agentsTargetDir(scope, process.cwd()), scope);
|
|
9290
9422
|
});
|
|
9291
|
-
if (
|
|
9423
|
+
if (realpathSync2(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
9292
9424
|
await program.parseAsync(process.argv);
|
|
9293
9425
|
}
|
|
9294
9426
|
export {
|