@appchy/jarvis 0.1.43 → 0.1.44
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/jarvis.mjs +78 -12
- package/dist/bin.js +154 -56
- package/dist/bin.js.map +1 -1
- package/dist/hooks/config-change.js.map +1 -1
- package/dist/hooks/pre-tool-use.js.map +1 -1
- package/dist/hooks/session-start.js.map +1 -1
- package/dist/hooks/stop.js.map +1 -1
- package/dist/hooks/user-prompt-submit.js.map +1 -1
- package/package.json +1 -1
package/bin/jarvis.mjs
CHANGED
|
@@ -8,25 +8,91 @@
|
|
|
8
8
|
* pairs, so one binary serves our cloud, a self-hosted instance and a laptop
|
|
9
9
|
* alike. For source-tree iteration with hot reload, run
|
|
10
10
|
* `pnpm -C apps/cli dev` — that runs `tsx src/bin.ts` directly.
|
|
11
|
+
*
|
|
12
|
+
* It does decide WHICH BUILD, for the two commands a repo reaches jarvis with.
|
|
13
|
+
* A repo names jarvis by `command: "jarvis"` in a checked-in `.mcp.json`, so the
|
|
14
|
+
* only place a machine-specific answer can live is here.
|
|
11
15
|
*/
|
|
12
16
|
|
|
13
17
|
import { fileURLToPath } from "url";
|
|
14
|
-
import { dirname, join } from "path";
|
|
15
|
-
import { existsSync } from "fs";
|
|
18
|
+
import { dirname, isAbsolute, join, relative } from "path";
|
|
19
|
+
import { existsSync, readFileSync, realpathSync } from "fs";
|
|
20
|
+
import { homedir } from "os";
|
|
16
21
|
|
|
17
22
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
23
|
const distEntry = join(__dirname, "..", "dist", "bin.js");
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
/** The commands a repo reaches jarvis with. Machine lifecycle — `start`, `init`, `pair` —
|
|
26
|
+
* is deliberately absent: a redirect meant for an MCP server must never move the daemon,
|
|
27
|
+
* which owns this machine's Claude Code and is identified by its config dir, not its build. */
|
|
28
|
+
const REDIRECTED = new Set(["serve", "work"]);
|
|
29
|
+
|
|
30
|
+
/** The development build to run here, or null for the one this launcher ships with.
|
|
31
|
+
* `src/dev.ts` answers the same question for `jarvis dev status`; it cannot be imported,
|
|
32
|
+
* because this has to decide before any bundle is loaded. Keep the two answers identical. */
|
|
33
|
+
function devBuild() {
|
|
34
|
+
const command = process.argv[2];
|
|
35
|
+
if (!REDIRECTED.has(command)) return null;
|
|
36
|
+
|
|
37
|
+
const configDir = process.env.JARVIS_CONFIG_DIR ?? join(homedir(), ".jarvis");
|
|
38
|
+
let redirect;
|
|
39
|
+
try {
|
|
40
|
+
redirect = JSON.parse(readFileSync(join(configDir, "dev.json"), "utf-8"));
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
28
43
|
}
|
|
29
|
-
|
|
44
|
+
if (typeof redirect?.root !== "string") return null;
|
|
45
|
+
|
|
46
|
+
// Every symlink resolved on both sides: an unresolved comparison fails wherever a home
|
|
47
|
+
// or temp directory is a link, and that failure is indistinguishable from being switched off.
|
|
48
|
+
const real = (target) => {
|
|
49
|
+
try {
|
|
50
|
+
return realpathSync(target);
|
|
51
|
+
} catch {
|
|
52
|
+
return target;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const cwd = real(process.cwd());
|
|
56
|
+
const here =
|
|
57
|
+
redirect.all === true ||
|
|
58
|
+
(Array.isArray(redirect.repos) &&
|
|
59
|
+
redirect.repos.some((repo) => {
|
|
60
|
+
if (typeof repo !== "string") return false;
|
|
61
|
+
const rel = relative(real(repo), cwd);
|
|
62
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
63
|
+
}));
|
|
64
|
+
if (!here) return null;
|
|
65
|
+
|
|
66
|
+
const entry = join(redirect.root, "apps", "cli", "dist", "bin.js");
|
|
67
|
+
if (!existsSync(entry)) {
|
|
68
|
+
// Falling back silently would serve the installed build under the name of the one
|
|
69
|
+
// being tested, which is the shape of this bug nobody catches: the half you look at
|
|
70
|
+
// is right. stderr, because stdout is the MCP transport.
|
|
71
|
+
process.stderr.write(
|
|
72
|
+
`jarvis: ${redirect.root} has no build to run — using the installed one.\n` +
|
|
73
|
+
` Build it with \`pnpm --filter @appchy/jarvis build\`.\n`,
|
|
74
|
+
);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return entry;
|
|
30
78
|
}
|
|
31
79
|
|
|
32
|
-
|
|
80
|
+
const dev = devBuild();
|
|
81
|
+
if (dev) {
|
|
82
|
+
process.stderr.write(`jarvis: development build, from ${dev}\n`);
|
|
83
|
+
await import(dev);
|
|
84
|
+
} else {
|
|
85
|
+
if (!existsSync(distEntry)) {
|
|
86
|
+
const isSourceCheckout = existsSync(join(__dirname, "..", "src", "bin.ts"));
|
|
87
|
+
console.error("Error: dist/bin.js not found.");
|
|
88
|
+
if (isSourceCheckout) {
|
|
89
|
+
console.error(" This is a source checkout. Run `pnpm -C apps/cli dev` for dev work,");
|
|
90
|
+
console.error(" or `pnpm -C apps/cli build` to produce a bundle.");
|
|
91
|
+
} else {
|
|
92
|
+
console.error(" The package may be corrupted. Reinstall with `pnpm i -g @appchy/jarvis`.");
|
|
93
|
+
}
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
await import(distEntry);
|
|
98
|
+
}
|
package/dist/bin.js
CHANGED
|
@@ -4433,8 +4433,8 @@ async function scanTree(root) {
|
|
|
4433
4433
|
for (const file of await walk(join3(work, base))) {
|
|
4434
4434
|
const text = await readFile(file, "utf-8");
|
|
4435
4435
|
const changedAt = (await stat(file)).mtime.toISOString().slice(0, 10);
|
|
4436
|
-
const
|
|
4437
|
-
const item = toWorkItem({ path:
|
|
4436
|
+
const path22 = relative(work, file).split(sep).join("/");
|
|
4437
|
+
const item = toWorkItem({ path: path22, text, changedAt });
|
|
4438
4438
|
if (item) items.push(item);
|
|
4439
4439
|
}
|
|
4440
4440
|
}
|
|
@@ -5400,8 +5400,8 @@ function reason(err) {
|
|
|
5400
5400
|
}
|
|
5401
5401
|
function issueText(issues) {
|
|
5402
5402
|
return issues.slice(0, 3).map((issue) => {
|
|
5403
|
-
const
|
|
5404
|
-
return
|
|
5403
|
+
const path22 = (issue.path ?? []).map((step) => String(typeof step === "object" ? step.key : step)).join(".");
|
|
5404
|
+
return path22 ? `${path22}: ${issue.message}` : issue.message;
|
|
5405
5405
|
}).join("; ");
|
|
5406
5406
|
}
|
|
5407
5407
|
function keyText(data) {
|
|
@@ -5689,9 +5689,9 @@ function localFiles(options = {}) {
|
|
|
5689
5689
|
}
|
|
5690
5690
|
},
|
|
5691
5691
|
save: async (ctx, key, bytes) => {
|
|
5692
|
-
const
|
|
5693
|
-
await mkdir(dirname3(
|
|
5694
|
-
await writeFile(
|
|
5692
|
+
const path22 = pathOf(ctx, key);
|
|
5693
|
+
await mkdir(dirname3(path22), { recursive: true });
|
|
5694
|
+
await writeFile(path22, bytes, "utf8");
|
|
5695
5695
|
},
|
|
5696
5696
|
locate: (ctx, key) => pathOf(ctx, key)
|
|
5697
5697
|
});
|
|
@@ -5709,10 +5709,10 @@ async function loadConfig2(cwd) {
|
|
|
5709
5709
|
let dir = resolve3(cwd);
|
|
5710
5710
|
for (; ; ) {
|
|
5711
5711
|
for (const name of CONFIG_NAMES) {
|
|
5712
|
-
const
|
|
5713
|
-
if (existsSync5(
|
|
5714
|
-
const config2 = await importConfig(
|
|
5715
|
-
validateConfig(config2,
|
|
5712
|
+
const path22 = resolve3(dir, name);
|
|
5713
|
+
if (existsSync5(path22)) {
|
|
5714
|
+
const config2 = await importConfig(path22);
|
|
5715
|
+
validateConfig(config2, path22);
|
|
5716
5716
|
return {
|
|
5717
5717
|
...config2,
|
|
5718
5718
|
persistence: config2.persistence ?? localFiles(),
|
|
@@ -5728,9 +5728,9 @@ async function loadConfig2(cwd) {
|
|
|
5728
5728
|
`No jarvis.config.{ts,js,mjs} found from ${cwd}. A repo maps its world in one, at its root.`
|
|
5729
5729
|
);
|
|
5730
5730
|
}
|
|
5731
|
-
async function importConfig(
|
|
5731
|
+
async function importConfig(path22) {
|
|
5732
5732
|
const jiti = createJiti(import.meta.url, { moduleCache: false, alias: selfAlias() });
|
|
5733
|
-
const mod = await jiti.import(
|
|
5733
|
+
const mod = await jiti.import(path22);
|
|
5734
5734
|
const config2 = mod.default ?? mod;
|
|
5735
5735
|
return config2;
|
|
5736
5736
|
}
|
|
@@ -5783,9 +5783,9 @@ function toVendorAlias(dir) {
|
|
|
5783
5783
|
function toSpecifier(subpath) {
|
|
5784
5784
|
return subpath === "." ? PACKAGE_NAME : `${PACKAGE_NAME}/${subpath.replace(/^\.\//, "")}`;
|
|
5785
5785
|
}
|
|
5786
|
-
function validateConfig(value,
|
|
5786
|
+
function validateConfig(value, path22) {
|
|
5787
5787
|
const fail = (msg) => {
|
|
5788
|
-
throw new Error(`${
|
|
5788
|
+
throw new Error(`${path22}: ${msg}`);
|
|
5789
5789
|
};
|
|
5790
5790
|
if (typeof value !== "object" || value === null) fail("config must export an object");
|
|
5791
5791
|
const c = value;
|
|
@@ -6491,10 +6491,10 @@ async function git3(root, argv, timeout = 5e3) {
|
|
|
6491
6491
|
}
|
|
6492
6492
|
}
|
|
6493
6493
|
async function fetchHeadIso(root) {
|
|
6494
|
-
const
|
|
6495
|
-
if (!
|
|
6494
|
+
const path22 = await git3(root, ["rev-parse", "--git-path", "FETCH_HEAD"]);
|
|
6495
|
+
if (!path22) return void 0;
|
|
6496
6496
|
try {
|
|
6497
|
-
return (await stat2(resolve4(root,
|
|
6497
|
+
return (await stat2(resolve4(root, path22))).mtime.toISOString();
|
|
6498
6498
|
} catch {
|
|
6499
6499
|
return void 0;
|
|
6500
6500
|
}
|
|
@@ -6571,10 +6571,10 @@ async function freshness(ctx) {
|
|
|
6571
6571
|
};
|
|
6572
6572
|
}
|
|
6573
6573
|
async function snapshotMtimeMs(ctx) {
|
|
6574
|
-
const
|
|
6575
|
-
if (!
|
|
6574
|
+
const path22 = ctx.config.persistence?.locate?.({ repoRoot: ctx.config.repoRoot }, "snapshot");
|
|
6575
|
+
if (!path22) return void 0;
|
|
6576
6576
|
try {
|
|
6577
|
-
return (await stat2(
|
|
6577
|
+
return (await stat2(path22)).mtimeMs;
|
|
6578
6578
|
} catch {
|
|
6579
6579
|
return void 0;
|
|
6580
6580
|
}
|
|
@@ -6996,10 +6996,10 @@ async function cachedEnforcerDiags(ctx, freshPaths) {
|
|
|
6996
6996
|
}
|
|
6997
6997
|
function isFresh(snapshot, repoRoot, freshPaths) {
|
|
6998
6998
|
if (!freshPaths || freshPaths.length === 0) return true;
|
|
6999
|
-
for (const
|
|
6999
|
+
for (const path22 of freshPaths) {
|
|
7000
7000
|
let mtimeMs;
|
|
7001
7001
|
try {
|
|
7002
|
-
mtimeMs = statSync2(resolve5(repoRoot,
|
|
7002
|
+
mtimeMs = statSync2(resolve5(repoRoot, path22)).mtimeMs;
|
|
7003
7003
|
} catch {
|
|
7004
7004
|
return false;
|
|
7005
7005
|
}
|
|
@@ -7652,15 +7652,15 @@ import { readFile as readFile5 } from "fs/promises";
|
|
|
7652
7652
|
import { resolve as resolve6 } from "path";
|
|
7653
7653
|
|
|
7654
7654
|
// ../../packages/data/src/meta.ts
|
|
7655
|
-
function stringMeta(metadata,
|
|
7656
|
-
const value = metaValue(metadata,
|
|
7655
|
+
function stringMeta(metadata, path22) {
|
|
7656
|
+
const value = metaValue(metadata, path22);
|
|
7657
7657
|
return typeof value === "string" ? value : void 0;
|
|
7658
7658
|
}
|
|
7659
|
-
function metaValue(metadata,
|
|
7659
|
+
function metaValue(metadata, path22) {
|
|
7660
7660
|
if (!metadata) return void 0;
|
|
7661
|
-
if (Object.prototype.hasOwnProperty.call(metadata,
|
|
7661
|
+
if (Object.prototype.hasOwnProperty.call(metadata, path22)) return metadata[path22];
|
|
7662
7662
|
let current = metadata;
|
|
7663
|
-
for (const part of
|
|
7663
|
+
for (const part of path22.split(".")) {
|
|
7664
7664
|
if (!current || typeof current !== "object" || !Object.prototype.hasOwnProperty.call(current, part))
|
|
7665
7665
|
return void 0;
|
|
7666
7666
|
current = current[part];
|
|
@@ -7771,8 +7771,8 @@ function resolveScopeGovernance(ctx, members, codeNodes) {
|
|
|
7771
7771
|
}
|
|
7772
7772
|
return [...out.values()];
|
|
7773
7773
|
}
|
|
7774
|
-
function resolveDirectoryGovernance(ctx,
|
|
7775
|
-
let dir =
|
|
7774
|
+
function resolveDirectoryGovernance(ctx, path22) {
|
|
7775
|
+
let dir = path22.includes("/") ? path22.slice(0, path22.lastIndexOf("/")) : "";
|
|
7776
7776
|
for (; ; ) {
|
|
7777
7777
|
const siblings = codeNodesInDir(ctx, dir);
|
|
7778
7778
|
if (siblings.length > 0) return resolveScopeGovernance(ctx, [], siblings);
|
|
@@ -8333,15 +8333,15 @@ async function review(ctx, req) {
|
|
|
8333
8333
|
cites: []
|
|
8334
8334
|
});
|
|
8335
8335
|
}
|
|
8336
|
-
for (const
|
|
8337
|
-
const dirGov = resolveDirectoryGovernance(ctx,
|
|
8336
|
+
for (const path22 of unresolved) {
|
|
8337
|
+
const dirGov = resolveDirectoryGovernance(ctx, path22).filter((n) => !isWorkItem(n)).map((g) => g.id).filter((id) => !cites.has(id)).slice(0, 3);
|
|
8338
8338
|
const hint = dirGov.length ? ` Files in its directory are governed by ${dirGov.join(", ")} \u2014 cite if it applies.` : "";
|
|
8339
8339
|
findings.push({
|
|
8340
8340
|
code: "unresolved-edit",
|
|
8341
8341
|
severity: "info",
|
|
8342
8342
|
confidence: "precise",
|
|
8343
|
-
subject:
|
|
8344
|
-
message: `${
|
|
8343
|
+
subject: path22,
|
|
8344
|
+
message: `${path22} isn't in the graph yet (a new file, or a path that doesn't match).${hint || " No governed siblings to assess it by."}`,
|
|
8345
8345
|
cites: dirGov
|
|
8346
8346
|
});
|
|
8347
8347
|
}
|
|
@@ -9192,8 +9192,8 @@ async function readFileContent(workdir, relPath) {
|
|
|
9192
9192
|
|
|
9193
9193
|
// src/workspace.ts
|
|
9194
9194
|
var workspacePath = null;
|
|
9195
|
-
function setWorkspacePath(
|
|
9196
|
-
workspacePath =
|
|
9195
|
+
function setWorkspacePath(path22) {
|
|
9196
|
+
workspacePath = path22;
|
|
9197
9197
|
}
|
|
9198
9198
|
function getWorkspacePath() {
|
|
9199
9199
|
if (!workspacePath) {
|
|
@@ -9612,10 +9612,11 @@ import WebSocket from "ws";
|
|
|
9612
9612
|
import { createRequire as createRequire2 } from "module";
|
|
9613
9613
|
var _require = createRequire2(import.meta.url);
|
|
9614
9614
|
var VERSION2 = _require("../package.json").version ?? "0.0.0";
|
|
9615
|
-
var
|
|
9615
|
+
var IS_DEV = String(_require("../package.json").name ?? "").endsWith("-dev");
|
|
9616
|
+
var SHA = "d64996d";
|
|
9616
9617
|
var BUILT = "2026-09-07";
|
|
9617
9618
|
var BUILD = SHA ?? "source";
|
|
9618
|
-
var BUILD_LABEL = SHA ? `${VERSION2} (${SHA}${BUILT ? ` ${BUILT}` : ""})` : `${VERSION2} (source)`;
|
|
9619
|
+
var BUILD_LABEL = `${SHA ? `${VERSION2} (${SHA}${BUILT ? ` ${BUILT}` : ""})` : `${VERSION2} (source)`}${IS_DEV ? " \u2014 development build" : ""}`;
|
|
9619
9620
|
|
|
9620
9621
|
// src/upstream.ts
|
|
9621
9622
|
function createUpstreamClient(config2) {
|
|
@@ -10542,12 +10543,12 @@ function register14(program) {
|
|
|
10542
10543
|
}
|
|
10543
10544
|
|
|
10544
10545
|
// src/commands/mcp.ts
|
|
10545
|
-
async function call(
|
|
10546
|
+
async function call(path22, method, body) {
|
|
10546
10547
|
const config2 = loadConfig();
|
|
10547
10548
|
if (!config2?.token) {
|
|
10548
10549
|
throw new Error("not connected \u2014 run `jarvis connect` first");
|
|
10549
10550
|
}
|
|
10550
|
-
const resp = await fetch(new URL(
|
|
10551
|
+
const resp = await fetch(new URL(path22, config2.url ?? getJarvisUrl()), {
|
|
10551
10552
|
method,
|
|
10552
10553
|
headers: {
|
|
10553
10554
|
authorization: `Bearer ${config2.token}`,
|
|
@@ -10556,7 +10557,7 @@ async function call(path20, method, body) {
|
|
|
10556
10557
|
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
10557
10558
|
});
|
|
10558
10559
|
if (!resp.ok) {
|
|
10559
|
-
throw new Error(`${method} ${
|
|
10560
|
+
throw new Error(`${method} ${path22} failed: ${resp.status} ${await resp.text()}`);
|
|
10560
10561
|
}
|
|
10561
10562
|
return await resp.json();
|
|
10562
10563
|
}
|
|
@@ -10904,9 +10905,9 @@ function resolveRepo(repo) {
|
|
|
10904
10905
|
loadEnvFile(resolve8(homedir4(), ".jarvis", "data.env"));
|
|
10905
10906
|
return cwd;
|
|
10906
10907
|
}
|
|
10907
|
-
function loadEnvFile(
|
|
10908
|
+
function loadEnvFile(path22) {
|
|
10908
10909
|
try {
|
|
10909
|
-
process.loadEnvFile(
|
|
10910
|
+
process.loadEnvFile(path22);
|
|
10910
10911
|
} catch {
|
|
10911
10912
|
}
|
|
10912
10913
|
}
|
|
@@ -10927,6 +10928,102 @@ function register16(program) {
|
|
|
10927
10928
|
});
|
|
10928
10929
|
}
|
|
10929
10930
|
|
|
10931
|
+
// src/commands/dev.ts
|
|
10932
|
+
import path20 from "path";
|
|
10933
|
+
|
|
10934
|
+
// src/dev.ts
|
|
10935
|
+
import fs18 from "fs";
|
|
10936
|
+
import path19 from "path";
|
|
10937
|
+
function redirectFile() {
|
|
10938
|
+
return path19.join(getConfigDir(), "dev.json");
|
|
10939
|
+
}
|
|
10940
|
+
function readDevRedirect() {
|
|
10941
|
+
try {
|
|
10942
|
+
const raw = JSON.parse(fs18.readFileSync(redirectFile(), "utf-8"));
|
|
10943
|
+
if (typeof raw.root !== "string" || raw.root.length === 0) return null;
|
|
10944
|
+
return {
|
|
10945
|
+
root: raw.root,
|
|
10946
|
+
repos: Array.isArray(raw.repos) ? raw.repos.filter((r) => typeof r === "string") : [],
|
|
10947
|
+
all: raw.all === true
|
|
10948
|
+
};
|
|
10949
|
+
} catch {
|
|
10950
|
+
return null;
|
|
10951
|
+
}
|
|
10952
|
+
}
|
|
10953
|
+
function saveDevRedirect(req) {
|
|
10954
|
+
const dir = getConfigDir();
|
|
10955
|
+
fs18.mkdirSync(dir, { recursive: true });
|
|
10956
|
+
fs18.writeFileSync(redirectFile(), `${JSON.stringify(req, null, 2)}
|
|
10957
|
+
`);
|
|
10958
|
+
}
|
|
10959
|
+
function clearDevRedirect() {
|
|
10960
|
+
fs18.rmSync(redirectFile(), { force: true });
|
|
10961
|
+
}
|
|
10962
|
+
function realPath(target) {
|
|
10963
|
+
try {
|
|
10964
|
+
return fs18.realpathSync(target);
|
|
10965
|
+
} catch {
|
|
10966
|
+
return target;
|
|
10967
|
+
}
|
|
10968
|
+
}
|
|
10969
|
+
function isRedirected(req) {
|
|
10970
|
+
if (req.redirect.all) return true;
|
|
10971
|
+
const cwd = realPath(req.cwd);
|
|
10972
|
+
return req.redirect.repos.some((repo) => {
|
|
10973
|
+
const rel = path19.relative(realPath(repo), cwd);
|
|
10974
|
+
return rel === "" || !rel.startsWith("..") && !path19.isAbsolute(rel);
|
|
10975
|
+
});
|
|
10976
|
+
}
|
|
10977
|
+
|
|
10978
|
+
// src/commands/dev.ts
|
|
10979
|
+
function register17(program) {
|
|
10980
|
+
const dev = program.command("dev").description("Run a development build in a repo, without replacing the installed jarvis");
|
|
10981
|
+
dev.command("use").argument("[where]", "'here' for the working directory, 'all' for every repo", "here").option("--root <path>", "The checkout to run. Default: the one already recorded").description("Point a repo at the development build").action((where, opts) => {
|
|
10982
|
+
const current = readDevRedirect();
|
|
10983
|
+
const root = path20.resolve(opts.root ?? current?.root ?? process.cwd());
|
|
10984
|
+
if (where !== "here" && where !== "all") {
|
|
10985
|
+
console.error(`Unknown target '${where}' \u2014 say 'here' or 'all'.`);
|
|
10986
|
+
process.exitCode = 1;
|
|
10987
|
+
return;
|
|
10988
|
+
}
|
|
10989
|
+
const repos = new Set(current?.repos ?? []);
|
|
10990
|
+
if (where === "here") repos.add(realPath(process.cwd()));
|
|
10991
|
+
saveDevRedirect({ root, repos: [...repos], all: where === "all" || (current?.all ?? false) });
|
|
10992
|
+
console.log(
|
|
10993
|
+
where === "all" ? `Every repo on this machine now serves the development build in ${root}.` : `${process.cwd()} now serves the development build in ${root}.`
|
|
10994
|
+
);
|
|
10995
|
+
console.log("The installed jarvis is untouched. Undo with `jarvis dev off`.");
|
|
10996
|
+
});
|
|
10997
|
+
dev.command("off").argument("[where]", "'here' for the working directory, 'all' to stop everywhere", "all").description("Stop serving the development build").action((where) => {
|
|
10998
|
+
const current = readDevRedirect();
|
|
10999
|
+
if (!current) {
|
|
11000
|
+
console.log("No repo is on a development build.");
|
|
11001
|
+
return;
|
|
11002
|
+
}
|
|
11003
|
+
if (where === "here") {
|
|
11004
|
+
const repos = current.repos.filter((repo) => realPath(repo) !== realPath(process.cwd()));
|
|
11005
|
+
saveDevRedirect({ ...current, repos, all: false });
|
|
11006
|
+
console.log(`${process.cwd()} is back on the installed jarvis.`);
|
|
11007
|
+
return;
|
|
11008
|
+
}
|
|
11009
|
+
clearDevRedirect();
|
|
11010
|
+
console.log("Every repo is back on the installed jarvis.");
|
|
11011
|
+
});
|
|
11012
|
+
dev.command("status").description("Say which repos are on a development build").action(() => {
|
|
11013
|
+
const current = readDevRedirect();
|
|
11014
|
+
if (!current) {
|
|
11015
|
+
console.log("No repo is on a development build.");
|
|
11016
|
+
return;
|
|
11017
|
+
}
|
|
11018
|
+
console.log(`development build ${current.root}`);
|
|
11019
|
+
console.log(current.all ? " every repo on this machine" : "");
|
|
11020
|
+
for (const repo of current.repos) console.log(` ${repo}`);
|
|
11021
|
+
console.log(
|
|
11022
|
+
isRedirected({ cwd: process.cwd(), redirect: current }) ? "\nHere: the development build." : "\nHere: the installed jarvis."
|
|
11023
|
+
);
|
|
11024
|
+
});
|
|
11025
|
+
}
|
|
11026
|
+
|
|
10930
11027
|
// src/graph/mcp.ts
|
|
10931
11028
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10932
11029
|
async function runMcp(cwd, hosted = [], prompts = []) {
|
|
@@ -11464,7 +11561,7 @@ function describeStart(response) {
|
|
|
11464
11561
|
// src/serve/approvals.ts
|
|
11465
11562
|
import { readFileSync as readFileSync7 } from "fs";
|
|
11466
11563
|
import os10 from "os";
|
|
11467
|
-
import
|
|
11564
|
+
import path21 from "path";
|
|
11468
11565
|
function pendingApproval(cwd) {
|
|
11469
11566
|
const unapproved = unapprovedServers(cwd);
|
|
11470
11567
|
const reasons = [
|
|
@@ -11475,15 +11572,15 @@ function pendingApproval(cwd) {
|
|
|
11475
11572
|
return `it will HOLD at a prompt and take no turn, because ${reasons.join(" and ")}. Nothing will appear in Claude Code's history or in the app until somebody opens ${cwd} in Claude Code once and answers. This is a one-time gate per folder.`;
|
|
11476
11573
|
}
|
|
11477
11574
|
function untrustedFolder(cwd) {
|
|
11478
|
-
const state2 = readJson2(
|
|
11575
|
+
const state2 = readJson2(path21.join(os10.homedir(), ".claude.json"));
|
|
11479
11576
|
const project = state2?.projects?.[cwd];
|
|
11480
11577
|
if (!state2) return false;
|
|
11481
11578
|
return project?.hasTrustDialogAccepted !== true;
|
|
11482
11579
|
}
|
|
11483
11580
|
function unapprovedServers(cwd) {
|
|
11484
|
-
const declared = readJson2(
|
|
11581
|
+
const declared = readJson2(path21.join(cwd, ".mcp.json"));
|
|
11485
11582
|
if (!declared?.mcpServers) return [];
|
|
11486
|
-
const settings = readJson2(
|
|
11583
|
+
const settings = readJson2(path21.join(cwd, ".claude", "settings.local.json"));
|
|
11487
11584
|
if (settings?.enableAllProjectMcpServers === true) return [];
|
|
11488
11585
|
const answered = /* @__PURE__ */ new Set([
|
|
11489
11586
|
...settings?.enabledMcpjsonServers ?? [],
|
|
@@ -11593,7 +11690,7 @@ async function brief2(repo) {
|
|
|
11593
11690
|
}
|
|
11594
11691
|
|
|
11595
11692
|
// src/commands/serve.ts
|
|
11596
|
-
function
|
|
11693
|
+
function register18(program) {
|
|
11597
11694
|
program.command("serve").description("Serve this repo to an agent over stdio \u2014 what an .mcp.json launches").option("--repo <path>", "The repo to serve. Default: the working directory").action(async (opts) => {
|
|
11598
11695
|
const repo = resolveRepo(opts.repo);
|
|
11599
11696
|
const problem = harnessProblem();
|
|
@@ -11655,7 +11752,7 @@ function refuseNamedConfigDir() {
|
|
|
11655
11752
|
);
|
|
11656
11753
|
return true;
|
|
11657
11754
|
}
|
|
11658
|
-
function
|
|
11755
|
+
function register19(program) {
|
|
11659
11756
|
const hooks = program.command("hooks").description("Wire this machine's Claude Code to jarvis \u2014 no pairing, no daemon");
|
|
11660
11757
|
hooks.command("install").description("Install the Claude Code hooks (the session block and the rules on edit)").action(() => {
|
|
11661
11758
|
if (refuseNamedConfigDir()) process.exit(1);
|
|
@@ -11710,7 +11807,7 @@ Open Claude Code in a repo that has a work/ tree to see them.`);
|
|
|
11710
11807
|
|
|
11711
11808
|
// src/commands/work.ts
|
|
11712
11809
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
11713
|
-
function
|
|
11810
|
+
function register20(program) {
|
|
11714
11811
|
program.command("work").description("The board \u2014 take work, prove it, finish it").argument("[args...]", "Passed to the harness untouched").allowUnknownOption().passThroughOptions().helpOption(false).action((passed) => {
|
|
11715
11812
|
const problem = harnessProblem();
|
|
11716
11813
|
if (problem) {
|
|
@@ -11752,22 +11849,23 @@ function createCli() {
|
|
|
11752
11849
|
register17(program);
|
|
11753
11850
|
register18(program);
|
|
11754
11851
|
register19(program);
|
|
11852
|
+
register20(program);
|
|
11755
11853
|
return program;
|
|
11756
11854
|
}
|
|
11757
11855
|
|
|
11758
11856
|
// src/bin.ts
|
|
11759
11857
|
if (isRunningFromSource()) {
|
|
11760
11858
|
const dotenv = await import("dotenv");
|
|
11761
|
-
const
|
|
11762
|
-
const
|
|
11859
|
+
const fs19 = await import("fs");
|
|
11860
|
+
const path22 = await import("path");
|
|
11763
11861
|
let dir = process.cwd();
|
|
11764
|
-
while (dir !==
|
|
11765
|
-
const envPath =
|
|
11766
|
-
if (
|
|
11862
|
+
while (dir !== path22.dirname(dir)) {
|
|
11863
|
+
const envPath = path22.join(dir, ".env");
|
|
11864
|
+
if (fs19.existsSync(envPath)) {
|
|
11767
11865
|
dotenv.config({ path: envPath });
|
|
11768
11866
|
break;
|
|
11769
11867
|
}
|
|
11770
|
-
dir =
|
|
11868
|
+
dir = path22.dirname(dir);
|
|
11771
11869
|
}
|
|
11772
11870
|
}
|
|
11773
11871
|
createCli().parse();
|