runcastle 1.0.18 → 1.0.19
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/bin/runcastle.js
CHANGED
|
@@ -5431,9 +5431,143 @@ var init_feature = __esm(() => {
|
|
|
5431
5431
|
});
|
|
5432
5432
|
});
|
|
5433
5433
|
|
|
5434
|
+
// src/services/fsbrowse.ts
|
|
5435
|
+
import { existsSync as existsSync14, readdirSync as readdirSync5, statSync as statSync2 } from "fs";
|
|
5436
|
+
import { homedir as homedir2 } from "os";
|
|
5437
|
+
import { basename, dirname as dirname6, isAbsolute, join as join14, resolve as resolve8, sep as sep3 } from "path";
|
|
5438
|
+
function expandPath(input, home = homedir2()) {
|
|
5439
|
+
const trimmed = input.trim();
|
|
5440
|
+
if (trimmed === "")
|
|
5441
|
+
return trimmed;
|
|
5442
|
+
let out = trimmed;
|
|
5443
|
+
if (out === "~")
|
|
5444
|
+
out = home;
|
|
5445
|
+
else if (out.startsWith("~/") || out.startsWith("~\\"))
|
|
5446
|
+
out = join14(home, out.slice(2));
|
|
5447
|
+
return resolve8(out);
|
|
5448
|
+
}
|
|
5449
|
+
function looksLikeRepo(dir) {
|
|
5450
|
+
try {
|
|
5451
|
+
return existsSync14(join14(dir, ".git"));
|
|
5452
|
+
} catch {
|
|
5453
|
+
return false;
|
|
5454
|
+
}
|
|
5455
|
+
}
|
|
5456
|
+
function listRoots(home = homedir2()) {
|
|
5457
|
+
const roots = [];
|
|
5458
|
+
if (process.platform === "win32") {
|
|
5459
|
+
for (let code = 67;code <= 90; code++) {
|
|
5460
|
+
const drive = `${String.fromCharCode(code)}:${sep3}`;
|
|
5461
|
+
try {
|
|
5462
|
+
if (existsSync14(drive))
|
|
5463
|
+
roots.push({ label: drive.replace(/\\$/, ""), path: drive, kind: "drive" });
|
|
5464
|
+
} catch {}
|
|
5465
|
+
}
|
|
5466
|
+
} else {
|
|
5467
|
+
roots.push({ label: "/", path: "/", kind: "drive" });
|
|
5468
|
+
}
|
|
5469
|
+
if (existsSync14(home))
|
|
5470
|
+
roots.push({ label: "Home", path: home, kind: "home" });
|
|
5471
|
+
const seen = new Set(roots.map(pathKey));
|
|
5472
|
+
for (const name of COMMON_DIRS) {
|
|
5473
|
+
const candidate = join14(home, name);
|
|
5474
|
+
const key = pathKey({ path: candidate });
|
|
5475
|
+
if (seen.has(key) || !existsSync14(candidate))
|
|
5476
|
+
continue;
|
|
5477
|
+
seen.add(key);
|
|
5478
|
+
roots.push({ label: name, path: candidate, kind: "common" });
|
|
5479
|
+
}
|
|
5480
|
+
return roots;
|
|
5481
|
+
}
|
|
5482
|
+
function pathKey(r) {
|
|
5483
|
+
return process.platform === "win32" ? r.path.toLowerCase() : r.path;
|
|
5484
|
+
}
|
|
5485
|
+
function crumbsFor(dir) {
|
|
5486
|
+
const out = [];
|
|
5487
|
+
let cur = dir;
|
|
5488
|
+
for (;; ) {
|
|
5489
|
+
out.unshift({ name: basename(cur) || cur, path: cur });
|
|
5490
|
+
const parent = dirname6(cur);
|
|
5491
|
+
if (parent === cur)
|
|
5492
|
+
break;
|
|
5493
|
+
cur = parent;
|
|
5494
|
+
}
|
|
5495
|
+
return out;
|
|
5496
|
+
}
|
|
5497
|
+
function parentOf(dir) {
|
|
5498
|
+
const parent = dirname6(dir);
|
|
5499
|
+
return parent === dir ? null : parent;
|
|
5500
|
+
}
|
|
5501
|
+
function browseDir(input, showHidden = false, home = homedir2()) {
|
|
5502
|
+
const target = input && input.trim() !== "" ? expandPath(input, home) : home;
|
|
5503
|
+
if (!isAbsolute(target)) {
|
|
5504
|
+
throw new InvalidInputError(`path is not absolute: ${target}`);
|
|
5505
|
+
}
|
|
5506
|
+
if (!existsSync14(target)) {
|
|
5507
|
+
throw new InvalidInputError(`path does not exist: ${target}`);
|
|
5508
|
+
}
|
|
5509
|
+
let dir = target;
|
|
5510
|
+
try {
|
|
5511
|
+
if (!statSync2(target).isDirectory())
|
|
5512
|
+
dir = dirname6(target);
|
|
5513
|
+
} catch (e) {
|
|
5514
|
+
throw new InvalidInputError(`cannot read path: ${target} (${errMsg2(e)})`);
|
|
5515
|
+
}
|
|
5516
|
+
let dirents;
|
|
5517
|
+
try {
|
|
5518
|
+
dirents = readdirSync5(dir, { withFileTypes: true, encoding: "utf8" });
|
|
5519
|
+
} catch (e) {
|
|
5520
|
+
throw new InvalidInputError(`cannot read directory: ${dir} (${errMsg2(e)})`);
|
|
5521
|
+
}
|
|
5522
|
+
const entries = [];
|
|
5523
|
+
let truncated = false;
|
|
5524
|
+
for (const dirent of dirents) {
|
|
5525
|
+
if (entries.length >= MAX_ENTRIES) {
|
|
5526
|
+
truncated = true;
|
|
5527
|
+
break;
|
|
5528
|
+
}
|
|
5529
|
+
const name = dirent.name;
|
|
5530
|
+
if (SKIP_ALWAYS.has(name))
|
|
5531
|
+
continue;
|
|
5532
|
+
if (!showHidden && name.startsWith("."))
|
|
5533
|
+
continue;
|
|
5534
|
+
const path = join14(dir, name);
|
|
5535
|
+
const isSymlink = dirent.isSymbolicLink();
|
|
5536
|
+
let isDirectory = dirent.isDirectory();
|
|
5537
|
+
if (isSymlink) {
|
|
5538
|
+
try {
|
|
5539
|
+
isDirectory = statSync2(path).isDirectory();
|
|
5540
|
+
} catch {
|
|
5541
|
+
continue;
|
|
5542
|
+
}
|
|
5543
|
+
}
|
|
5544
|
+
if (!isDirectory)
|
|
5545
|
+
continue;
|
|
5546
|
+
entries.push({ name, path, isRepo: looksLikeRepo(path), isSymlink });
|
|
5547
|
+
}
|
|
5548
|
+
entries.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: "base" }));
|
|
5549
|
+
return {
|
|
5550
|
+
dir,
|
|
5551
|
+
parent: parentOf(dir),
|
|
5552
|
+
crumbs: crumbsFor(dir),
|
|
5553
|
+
entries,
|
|
5554
|
+
isRepo: looksLikeRepo(dir),
|
|
5555
|
+
truncated
|
|
5556
|
+
};
|
|
5557
|
+
}
|
|
5558
|
+
function errMsg2(e) {
|
|
5559
|
+
return e instanceof Error ? e.message : String(e);
|
|
5560
|
+
}
|
|
5561
|
+
var SKIP_ALWAYS, MAX_ENTRIES = 1000, COMMON_DIRS;
|
|
5562
|
+
var init_fsbrowse = __esm(() => {
|
|
5563
|
+
init_errors();
|
|
5564
|
+
SKIP_ALWAYS = new Set(["node_modules", "$RECYCLE.BIN", "System Volume Information"]);
|
|
5565
|
+
COMMON_DIRS = ["Projects", "projects", "code", "Code", "src", "dev", "repos", "git"];
|
|
5566
|
+
});
|
|
5567
|
+
|
|
5434
5568
|
// src/services/projects.ts
|
|
5435
|
-
import { existsSync as
|
|
5436
|
-
import { basename, join as
|
|
5569
|
+
import { existsSync as existsSync15 } from "fs";
|
|
5570
|
+
import { basename as basename2, join as join15 } from "path";
|
|
5437
5571
|
import { eq as eq9, isNull } from "drizzle-orm";
|
|
5438
5572
|
function listProjects(ctx) {
|
|
5439
5573
|
return ctx.db.select().from(projects).where(isNull(projects.closedAt)).all().map(rowToProject);
|
|
@@ -5449,14 +5583,15 @@ function warnIfTranslatedMount(ctx, projectId, repoPath) {
|
|
|
5449
5583
|
message: `${repoPath} is a Windows drive mounted into Linux (/mnt) \u2014 git and burns pay a per-file translation tax here; clone the repo into the Linux filesystem (e.g. ~/projects) for native speed`
|
|
5450
5584
|
});
|
|
5451
5585
|
}
|
|
5452
|
-
async function openProject(ctx,
|
|
5586
|
+
async function openProject(ctx, rawPath) {
|
|
5587
|
+
const repoPath = expandPath(rawPath);
|
|
5453
5588
|
await assertRepoTolerant(ctx, repoPath);
|
|
5454
5589
|
const mainBranch = await detectMainBranchTolerant(ctx, repoPath);
|
|
5455
|
-
const name =
|
|
5456
|
-
const existing = getProjectByRepoPath(ctx, repoPath);
|
|
5590
|
+
const name = basename2(repoPath) || repoPath;
|
|
5591
|
+
const existing = getProjectByRepoPath(ctx, repoPath) ?? findProjectByCanonPath(ctx, repoPath);
|
|
5457
5592
|
if (existing) {
|
|
5458
|
-
ctx.db.update(projects).set({ mainBranch, closedAt: null }).where(eq9(projects.id, existing.id)).run();
|
|
5459
|
-
const reopened = { ...existing, mainBranch };
|
|
5593
|
+
ctx.db.update(projects).set({ mainBranch, repoPath, closedAt: null }).where(eq9(projects.id, existing.id)).run();
|
|
5594
|
+
const reopened = { ...existing, mainBranch, repoPath };
|
|
5460
5595
|
emitProject(ctx, existing.id, {
|
|
5461
5596
|
type: "project.opened",
|
|
5462
5597
|
message: `project ${existing.name} re-opened at ${repoPath} (${mainBranch})`
|
|
@@ -5503,13 +5638,17 @@ function renameProject(ctx, projectId, name) {
|
|
|
5503
5638
|
});
|
|
5504
5639
|
return { ...project, name };
|
|
5505
5640
|
}
|
|
5641
|
+
function findProjectByCanonPath(ctx, repoPath) {
|
|
5642
|
+
const key = canon(repoPath);
|
|
5643
|
+
return allProjects(ctx).find((p) => canon(p.repoPath) === key) ?? null;
|
|
5644
|
+
}
|
|
5506
5645
|
async function assertRepoTolerant(ctx, repoPath) {
|
|
5507
5646
|
try {
|
|
5508
5647
|
await assertRepo(repoPath);
|
|
5509
5648
|
} catch (e) {
|
|
5510
5649
|
if (!isNotImplemented(e))
|
|
5511
5650
|
throw e;
|
|
5512
|
-
if (!
|
|
5651
|
+
if (!existsSync15(join15(repoPath, ".git"))) {
|
|
5513
5652
|
throw new InvalidInputError(`not a git repository: ${repoPath}`);
|
|
5514
5653
|
}
|
|
5515
5654
|
}
|
|
@@ -5528,6 +5667,7 @@ var init_projects = __esm(() => {
|
|
|
5528
5667
|
init_schema();
|
|
5529
5668
|
init_errors();
|
|
5530
5669
|
init_events();
|
|
5670
|
+
init_fsbrowse();
|
|
5531
5671
|
init_git();
|
|
5532
5672
|
init_repo();
|
|
5533
5673
|
});
|
|
@@ -5536,12 +5676,15 @@ var init_projects = __esm(() => {
|
|
|
5536
5676
|
import * as z7 from "zod";
|
|
5537
5677
|
var projectRouter;
|
|
5538
5678
|
var init_project = __esm(() => {
|
|
5679
|
+
init_fsbrowse();
|
|
5539
5680
|
init_git();
|
|
5540
5681
|
init_projects();
|
|
5541
5682
|
init_repo();
|
|
5542
5683
|
init_context();
|
|
5543
5684
|
projectRouter = router({
|
|
5544
5685
|
list: publicProcedure.query(({ ctx }) => listProjects(ctx)),
|
|
5686
|
+
roots: publicProcedure.query(() => listRoots()),
|
|
5687
|
+
browse: publicProcedure.input(z7.object({ dir: z7.string().optional(), showHidden: z7.boolean().optional() }).optional()).query(({ input }) => browseDir(input?.dir, input?.showHidden ?? false)),
|
|
5545
5688
|
branches: publicProcedure.input(z7.object({ projectId: z7.string() })).query(({ ctx, input }) => listBranches(requireProjectById(ctx, input.projectId))),
|
|
5546
5689
|
open: publicProcedure.input(z7.object({ repoPath: z7.string().min(1) })).mutation(({ ctx, input }) => openProject(ctx, input.repoPath)),
|
|
5547
5690
|
close: publicProcedure.input(z7.object({ projectId: z7.string() })).mutation(({ ctx, input }) => closeProject(ctx, input.projectId)),
|
|
@@ -5568,12 +5711,12 @@ var init_run = __esm(() => {
|
|
|
5568
5711
|
});
|
|
5569
5712
|
|
|
5570
5713
|
// src/services/settings.ts
|
|
5571
|
-
import { existsSync as
|
|
5572
|
-
import { dirname as
|
|
5714
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
5715
|
+
import { dirname as dirname7 } from "path";
|
|
5573
5716
|
import * as z9 from "zod";
|
|
5574
5717
|
import { eq as eq10 } from "drizzle-orm";
|
|
5575
5718
|
function readRawConfig(configFile) {
|
|
5576
|
-
if (!
|
|
5719
|
+
if (!existsSync16(configFile))
|
|
5577
5720
|
return {};
|
|
5578
5721
|
try {
|
|
5579
5722
|
const parsed = JSON.parse(readFileSync6(configFile, "utf8"));
|
|
@@ -5695,7 +5838,7 @@ function updateSettings(ctx, input, io = {}) {
|
|
|
5695
5838
|
function writeGlobal(configFile, configKey, value) {
|
|
5696
5839
|
const raw = readRawConfig(configFile);
|
|
5697
5840
|
raw[configKey] = value;
|
|
5698
|
-
mkdirSync7(
|
|
5841
|
+
mkdirSync7(dirname7(configFile), { recursive: true });
|
|
5699
5842
|
writeFileSync3(configFile, `${JSON.stringify(raw, null, 2)}
|
|
5700
5843
|
`);
|
|
5701
5844
|
}
|
|
@@ -5734,7 +5877,7 @@ function writeStepModel(configFile, step, value) {
|
|
|
5734
5877
|
else
|
|
5735
5878
|
stepModels[step] = value;
|
|
5736
5879
|
raw.stepModels = stepModels;
|
|
5737
|
-
mkdirSync7(
|
|
5880
|
+
mkdirSync7(dirname7(configFile), { recursive: true });
|
|
5738
5881
|
writeFileSync3(configFile, `${JSON.stringify(raw, null, 2)}
|
|
5739
5882
|
`);
|
|
5740
5883
|
}
|
|
@@ -6016,7 +6159,7 @@ var init_doctor = __esm(() => {
|
|
|
6016
6159
|
// src/doctor/system-exec.ts
|
|
6017
6160
|
import { spawn as spawn2 } from "child_process";
|
|
6018
6161
|
function createSystemExec(opts = {}) {
|
|
6019
|
-
return (command, args) => new Promise((
|
|
6162
|
+
return (command, args) => new Promise((resolve9) => {
|
|
6020
6163
|
const resolved = resolveExecutable(command);
|
|
6021
6164
|
const isShim = /\.(cmd|bat)$/i.test(resolved);
|
|
6022
6165
|
const file = isShim ? process.env.ComSpec ?? "cmd.exe" : resolved;
|
|
@@ -6031,10 +6174,10 @@ function createSystemExec(opts = {}) {
|
|
|
6031
6174
|
stderr += d.toString();
|
|
6032
6175
|
});
|
|
6033
6176
|
child.on("error", (err) => {
|
|
6034
|
-
|
|
6177
|
+
resolve9({ ok: false, code: null, stdout, stderr: stderr || String(err) });
|
|
6035
6178
|
});
|
|
6036
6179
|
child.on("close", (code) => {
|
|
6037
|
-
|
|
6180
|
+
resolve9({ ok: true, code: code ?? 0, stdout, stderr });
|
|
6038
6181
|
});
|
|
6039
6182
|
});
|
|
6040
6183
|
}
|
|
@@ -6043,8 +6186,8 @@ var init_system_exec = __esm(() => {
|
|
|
6043
6186
|
});
|
|
6044
6187
|
|
|
6045
6188
|
// src/services/setup.ts
|
|
6046
|
-
import { copyFileSync, existsSync as
|
|
6047
|
-
import { dirname as
|
|
6189
|
+
import { copyFileSync, existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync7, readdirSync as readdirSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
6190
|
+
import { dirname as dirname8, join as join16 } from "path";
|
|
6048
6191
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
6049
6192
|
async function writeGitIdentity(exec, identity) {
|
|
6050
6193
|
const name = identity.name.trim();
|
|
@@ -6120,16 +6263,16 @@ async function resolveRuntime(exec, preferred) {
|
|
|
6120
6263
|
function resolveSandcastleBin() {
|
|
6121
6264
|
try {
|
|
6122
6265
|
const mainUrl = import.meta.resolve("@ai-hero/sandcastle");
|
|
6123
|
-
let dir =
|
|
6266
|
+
let dir = dirname8(fileURLToPath6(mainUrl));
|
|
6124
6267
|
for (let hops = 0;hops < 6; hops++) {
|
|
6125
|
-
const manifestPath =
|
|
6126
|
-
if (
|
|
6268
|
+
const manifestPath = join16(dir, "package.json");
|
|
6269
|
+
if (existsSync17(manifestPath)) {
|
|
6127
6270
|
const manifest = JSON.parse(readFileSync7(manifestPath, "utf8"));
|
|
6128
6271
|
if (manifest.name === "@ai-hero/sandcastle" && manifest.bin?.sandcastle) {
|
|
6129
|
-
return
|
|
6272
|
+
return join16(dir, manifest.bin.sandcastle);
|
|
6130
6273
|
}
|
|
6131
6274
|
}
|
|
6132
|
-
const parent =
|
|
6275
|
+
const parent = dirname8(dir);
|
|
6133
6276
|
if (parent === dir)
|
|
6134
6277
|
break;
|
|
6135
6278
|
dir = parent;
|
|
@@ -6154,12 +6297,12 @@ function sandcastleTemplateDir() {
|
|
|
6154
6297
|
return resolveAsset(ASSET_ENV.sandcastleTemplate, fileURLToPath6(new URL("../assets/sandcastle", import.meta.url)));
|
|
6155
6298
|
}
|
|
6156
6299
|
function scaffoldSandcastleConfig(templateDir, targetDir) {
|
|
6157
|
-
const dir =
|
|
6158
|
-
if (
|
|
6300
|
+
const dir = join16(targetDir, ".sandcastle");
|
|
6301
|
+
if (existsSync17(dir))
|
|
6159
6302
|
return { scaffolded: false, dir };
|
|
6160
6303
|
mkdirSync8(dir, { recursive: true });
|
|
6161
|
-
for (const name of
|
|
6162
|
-
copyFileSync(
|
|
6304
|
+
for (const name of readdirSync6(templateDir)) {
|
|
6305
|
+
copyFileSync(join16(templateDir, name), join16(dir, name));
|
|
6163
6306
|
}
|
|
6164
6307
|
return { scaffolded: true, dir };
|
|
6165
6308
|
}
|
|
@@ -6184,9 +6327,9 @@ function createTokenVerifier(exec) {
|
|
|
6184
6327
|
function fileAfkTokenIo(exec) {
|
|
6185
6328
|
const path = envPath();
|
|
6186
6329
|
return {
|
|
6187
|
-
read: () =>
|
|
6330
|
+
read: () => existsSync17(path) ? readFileSync7(path, "utf8") : "",
|
|
6188
6331
|
write: (content) => {
|
|
6189
|
-
mkdirSync8(
|
|
6332
|
+
mkdirSync8(dirname8(path), { recursive: true });
|
|
6190
6333
|
writeFileSync4(path, content, "utf8");
|
|
6191
6334
|
},
|
|
6192
6335
|
verify: createTokenVerifier(exec)
|
|
@@ -6323,13 +6466,13 @@ var init_update_check = __esm(() => {
|
|
|
6323
6466
|
|
|
6324
6467
|
// src/version.ts
|
|
6325
6468
|
import { readFileSync as readFileSync8 } from "fs";
|
|
6326
|
-
import { dirname as
|
|
6469
|
+
import { dirname as dirname9, join as join17 } from "path";
|
|
6327
6470
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
6328
6471
|
function runcastleVersion() {
|
|
6329
6472
|
if (cached2)
|
|
6330
6473
|
return cached2;
|
|
6331
|
-
const here =
|
|
6332
|
-
for (const pkgPath of [
|
|
6474
|
+
const here = dirname9(fileURLToPath7(import.meta.url));
|
|
6475
|
+
for (const pkgPath of [join17(here, "..", "package.json"), join17(here, "package.json")]) {
|
|
6333
6476
|
try {
|
|
6334
6477
|
const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
|
|
6335
6478
|
if (typeof pkg.version === "string") {
|
|
@@ -6579,7 +6722,7 @@ __export(exports_cli, {
|
|
|
6579
6722
|
resolveDoctorEnv: () => resolveDoctorEnv,
|
|
6580
6723
|
parseMode: () => parseMode
|
|
6581
6724
|
});
|
|
6582
|
-
import { existsSync as
|
|
6725
|
+
import { existsSync as existsSync18, readFileSync as readFileSync9 } from "fs";
|
|
6583
6726
|
function parseMode(argv) {
|
|
6584
6727
|
return argv.includes("--gate") || argv.includes("--boot") ? "gate" : "diagnostic";
|
|
6585
6728
|
}
|
|
@@ -6587,7 +6730,7 @@ function envWithToken() {
|
|
|
6587
6730
|
const merged = { ...process.env };
|
|
6588
6731
|
try {
|
|
6589
6732
|
const path = envPath();
|
|
6590
|
-
if (
|
|
6733
|
+
if (existsSync18(path)) {
|
|
6591
6734
|
const fromFile = parseEnvFile(readFileSync9(path, "utf8")).CLAUDE_CODE_OAUTH_TOKEN;
|
|
6592
6735
|
if (fromFile && fromFile.length > 0)
|
|
6593
6736
|
merged.CLAUDE_CODE_OAUTH_TOKEN = fromFile;
|
|
@@ -6630,7 +6773,7 @@ var init_cli = __esm(() => {
|
|
|
6630
6773
|
// src/bin/runcastle.ts
|
|
6631
6774
|
init_asset_paths();
|
|
6632
6775
|
init_version();
|
|
6633
|
-
import { dirname as
|
|
6776
|
+
import { dirname as dirname10, resolve as resolve9 } from "path";
|
|
6634
6777
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
6635
6778
|
function parseCommand(argv) {
|
|
6636
6779
|
const [first, ...rest] = argv;
|
|
@@ -6655,7 +6798,7 @@ Usage:
|
|
|
6655
6798
|
runcastle --help Show this help
|
|
6656
6799
|
`;
|
|
6657
6800
|
async function main(argv) {
|
|
6658
|
-
applyInstalledAssetEnv(
|
|
6801
|
+
applyInstalledAssetEnv(resolve9(dirname10(fileURLToPath8(import.meta.url)), ".."));
|
|
6659
6802
|
const { command, args } = parseCommand(argv);
|
|
6660
6803
|
switch (command) {
|
|
6661
6804
|
case "version":
|