@appchy/jarvis 0.1.47 → 0.1.48
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/dist/bin.js +613 -573
- 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 +5 -5
package/dist/bin.js
CHANGED
|
@@ -30,7 +30,6 @@ import { Command } from "commander";
|
|
|
30
30
|
import fs2 from "fs";
|
|
31
31
|
import path2 from "path";
|
|
32
32
|
import os from "os";
|
|
33
|
-
var DEFAULT_URL = "https://jarvis.appchy.com";
|
|
34
33
|
function configDir() {
|
|
35
34
|
if (process.env.JARVIS_CONFIG_DIR) return process.env.JARVIS_CONFIG_DIR;
|
|
36
35
|
return path2.join(os.homedir(), ".jarvis");
|
|
@@ -129,8 +128,8 @@ function register(program) {
|
|
|
129
128
|
import { execSync as execSync4 } from "child_process";
|
|
130
129
|
import { createInterface as createInterface2 } from "readline";
|
|
131
130
|
import fs10 from "fs";
|
|
132
|
-
import
|
|
133
|
-
import
|
|
131
|
+
import os6 from "os";
|
|
132
|
+
import path10 from "path";
|
|
134
133
|
|
|
135
134
|
// src/env.ts
|
|
136
135
|
import fs7 from "fs";
|
|
@@ -1756,415 +1755,93 @@ var FallbackService = class {
|
|
|
1756
1755
|
}
|
|
1757
1756
|
};
|
|
1758
1757
|
|
|
1759
|
-
// src/
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1758
|
+
// src/hooks/install.ts
|
|
1759
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "fs";
|
|
1760
|
+
import os5 from "os";
|
|
1761
|
+
import path9 from "path";
|
|
1762
|
+
var SETTINGS_DIR = path9.join(os5.homedir(), ".claude");
|
|
1763
|
+
var SETTINGS_FILE = path9.join(SETTINGS_DIR, "settings.json");
|
|
1764
|
+
var HOOK_STEMS = [
|
|
1765
|
+
"pre-tool-use",
|
|
1766
|
+
"session-start",
|
|
1767
|
+
"stop",
|
|
1768
|
+
"user-prompt-submit",
|
|
1769
|
+
"config-change"
|
|
1770
|
+
];
|
|
1771
|
+
function jarvisHooks() {
|
|
1772
|
+
return [
|
|
1773
|
+
{ type: "PreToolUse", matcher: "*", binBasename: "pre-tool-use.mjs" },
|
|
1774
|
+
{ type: "SessionStart", matcher: "*", binBasename: "session-start.mjs" },
|
|
1775
|
+
{ type: "Stop", matcher: "*", binBasename: "stop.mjs" },
|
|
1776
|
+
{ type: "UserPromptSubmit", matcher: "*", binBasename: "user-prompt-submit.mjs" },
|
|
1777
|
+
{ type: "ConfigChange", matcher: "*", binBasename: "config-change.mjs" }
|
|
1778
|
+
];
|
|
1769
1779
|
}
|
|
1770
|
-
|
|
1771
|
-
const
|
|
1772
|
-
|
|
1773
|
-
try {
|
|
1774
|
-
console.log("\n Jarvis local agent setup");
|
|
1775
|
-
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
1776
|
-
console.log(` \u25B8 Pairing this machine [${url}]`);
|
|
1777
|
-
const saved = loadConfig();
|
|
1778
|
-
let exchanged;
|
|
1779
|
-
try {
|
|
1780
|
-
exchanged = await exchangeCode({
|
|
1781
|
-
url,
|
|
1782
|
-
machineId: getMachine().id,
|
|
1783
|
-
...saved?.envId ? { envId: saved.envId } : {},
|
|
1784
|
-
version: process.env.JARVIS_VERSION ?? "0.0.0"
|
|
1785
|
-
});
|
|
1786
|
-
} catch (err) {
|
|
1787
|
-
const cause = err instanceof Error ? err.cause : void 0;
|
|
1788
|
-
const errCode = cause?.code ? ` (${cause.code})` : "";
|
|
1789
|
-
const hint = ` Check that ${url} is reachable.`;
|
|
1790
|
-
throw new Error(`${err instanceof Error ? err.message : err}${errCode}
|
|
1791
|
-
${hint}`);
|
|
1792
|
-
}
|
|
1793
|
-
console.log(` paired \u2713 (you = ${exchanged.userId}, this env = "${os5.hostname()}")
|
|
1794
|
-
`);
|
|
1795
|
-
const workspacePath2 = saved?.workspacePath ?? process.cwd();
|
|
1796
|
-
saveConfig({
|
|
1797
|
-
...saved ?? {},
|
|
1798
|
-
hubUrl: exchanged.hubUrl,
|
|
1799
|
-
url,
|
|
1800
|
-
token: exchanged.token,
|
|
1801
|
-
...exchanged.refreshToken ? { refreshToken: exchanged.refreshToken } : {},
|
|
1802
|
-
userId: exchanged.userId,
|
|
1803
|
-
envId: exchanged.envId,
|
|
1804
|
-
workspacePath: workspacePath2,
|
|
1805
|
-
connectedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1806
|
-
});
|
|
1807
|
-
const existingRoots = readLocalRoots();
|
|
1808
|
-
const roots = await chooseRoots({
|
|
1809
|
-
existingRoots,
|
|
1810
|
-
force: options.force ?? false,
|
|
1811
|
-
prompts
|
|
1812
|
-
});
|
|
1813
|
-
if (roots && roots.length > 0 && !rootsEqual(roots, existingRoots)) {
|
|
1814
|
-
await applyRoots(roots);
|
|
1815
|
-
console.log(` registered ${roots.length} root(s) \u2713
|
|
1816
|
-
`);
|
|
1817
|
-
} else if (roots && rootsEqual(roots, existingRoots)) {
|
|
1818
|
-
console.log(` kept ${roots.length} saved root(s) \u2713
|
|
1819
|
-
`);
|
|
1820
|
-
}
|
|
1821
|
-
if (!options.skipDaemonInstall) {
|
|
1822
|
-
console.log(" \u25B8 Installing background daemon \u2026");
|
|
1823
|
-
const installed = await tryInstallDaemon();
|
|
1824
|
-
console.log(installed ? " \u2713\n" : " skipped (run 'jarvis start' manually)\n");
|
|
1825
|
-
}
|
|
1826
|
-
console.log(" \u2714 Done. Open " + url + " \u2014 your data is there.");
|
|
1827
|
-
console.log(" Manage anytime: jarvis settings");
|
|
1828
|
-
console.log(" " + url + "/settings/environments\n");
|
|
1829
|
-
return true;
|
|
1830
|
-
} catch (err) {
|
|
1831
|
-
console.error(`
|
|
1832
|
-
Error: ${err instanceof Error ? err.message : err}`);
|
|
1833
|
-
return false;
|
|
1834
|
-
}
|
|
1780
|
+
function isJarvisCommand(command) {
|
|
1781
|
+
const name = path9.basename(command);
|
|
1782
|
+
return HOOK_STEMS.some((stem) => name === `${stem}.mjs` || name === `${stem}.dev.mjs`);
|
|
1835
1783
|
}
|
|
1836
|
-
function
|
|
1837
|
-
const
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
for (const name of names) {
|
|
1841
|
-
const p = path9.join(home, name);
|
|
1842
|
-
let exists = false;
|
|
1843
|
-
try {
|
|
1844
|
-
exists = fs10.statSync(p).isDirectory();
|
|
1845
|
-
} catch {
|
|
1846
|
-
continue;
|
|
1847
|
-
}
|
|
1848
|
-
if (!exists) continue;
|
|
1849
|
-
out.push({
|
|
1850
|
-
path: p,
|
|
1851
|
-
exists: true,
|
|
1852
|
-
repoCount: countRepos(p)
|
|
1853
|
-
});
|
|
1784
|
+
function getCliRoot() {
|
|
1785
|
+
const binPath = process.argv[1];
|
|
1786
|
+
if (!binPath) {
|
|
1787
|
+
return process.cwd();
|
|
1854
1788
|
}
|
|
1855
|
-
|
|
1856
|
-
}
|
|
1857
|
-
function countRepos(root) {
|
|
1789
|
+
let entry = binPath;
|
|
1858
1790
|
try {
|
|
1859
|
-
|
|
1860
|
-
encoding: "utf-8",
|
|
1861
|
-
timeout: 5e3
|
|
1862
|
-
}).trim();
|
|
1863
|
-
const n = parseInt(out, 10);
|
|
1864
|
-
return Number.isFinite(n) ? n : void 0;
|
|
1791
|
+
entry = realpathSync(binPath);
|
|
1865
1792
|
} catch {
|
|
1866
|
-
return void 0;
|
|
1867
1793
|
}
|
|
1794
|
+
return path9.resolve(path9.dirname(entry), "..");
|
|
1868
1795
|
}
|
|
1869
|
-
|
|
1796
|
+
function resolveHooks() {
|
|
1797
|
+
const cliRoot = getCliRoot();
|
|
1798
|
+
return jarvisHooks().map((h) => ({
|
|
1799
|
+
...h,
|
|
1800
|
+
binAbsPath: path9.join(cliRoot, "bin", h.binBasename)
|
|
1801
|
+
}));
|
|
1802
|
+
}
|
|
1803
|
+
function ownsClaudeCode() {
|
|
1804
|
+
return getConfigDir() === path9.join(os5.homedir(), ".jarvis");
|
|
1805
|
+
}
|
|
1806
|
+
function readSettings() {
|
|
1807
|
+
if (!existsSync(SETTINGS_FILE)) return {};
|
|
1870
1808
|
try {
|
|
1871
|
-
const
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
nodePath,
|
|
1877
|
-
entryPath,
|
|
1878
|
-
upstream: true
|
|
1809
|
+
const raw = readFileSync(SETTINGS_FILE, "utf-8");
|
|
1810
|
+
return raw.trim() ? JSON.parse(raw) : {};
|
|
1811
|
+
} catch (err) {
|
|
1812
|
+
logger.sys.warn("[hooks.install] settings.json unreadable", {
|
|
1813
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1879
1814
|
});
|
|
1880
|
-
return
|
|
1881
|
-
} catch {
|
|
1882
|
-
return false;
|
|
1815
|
+
return {};
|
|
1883
1816
|
}
|
|
1884
1817
|
}
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
if (existingRoots.length > 0) {
|
|
1896
|
-
console.log(" saved roots:\n");
|
|
1897
|
-
for (const r of existingRoots) console.log(` [\u2713] ${r}`);
|
|
1898
|
-
console.log();
|
|
1899
|
-
}
|
|
1900
|
-
const choices = [];
|
|
1901
|
-
if (existingRoots.length > 0) {
|
|
1902
|
-
choices.push({ key: "k", label: "keep saved (Enter)" });
|
|
1903
|
-
}
|
|
1904
|
-
choices.push({ key: "m", label: "manual entry" });
|
|
1905
|
-
choices.push({ key: "s", label: "scan home dir" });
|
|
1906
|
-
const prompt = " " + choices.map((c) => `[${c.key}] ${c.label}`).join(" \xB7 ") + "\n > ";
|
|
1907
|
-
const answer = (await readlineQuestion2(prompt)).trim().toLowerCase();
|
|
1908
|
-
const defaultKey = existingRoots.length > 0 ? "k" : "m";
|
|
1909
|
-
const choice = answer || defaultKey;
|
|
1910
|
-
if (choice === "k" || choice === "keep") return dedupePaths2(existingRoots);
|
|
1911
|
-
if (choice === "m" || choice === "manual") {
|
|
1912
|
-
const fallback = existingRoots.length > 0 ? existingRoots : [];
|
|
1913
|
-
const hint = fallback.length > 0 ? ` enter comma-separated paths (or Enter to keep saved)
|
|
1914
|
-
[${fallback.join(", ")}]
|
|
1915
|
-
> ` : " enter comma-separated paths\n > ";
|
|
1916
|
-
return promptList(hint, fallback);
|
|
1917
|
-
}
|
|
1918
|
-
console.log(" scanning home dir \u2026 (may take a few seconds)");
|
|
1919
|
-
const discovered = discoverCandidateRoots();
|
|
1920
|
-
const merged = mergeRootCandidates(existingRoots, discovered);
|
|
1921
|
-
if (merged.length === 0) {
|
|
1922
|
-
console.log(" no roots found under your home dir.");
|
|
1923
|
-
return existingRoots.length > 0 ? dedupePaths2(existingRoots) : [];
|
|
1924
|
-
}
|
|
1925
|
-
if (existingRoots.length > 0) {
|
|
1926
|
-
console.log(" saved [\u2713] / discovered [+] roots:\n");
|
|
1927
|
-
} else {
|
|
1928
|
-
console.log(" found these likely roots:\n");
|
|
1929
|
-
}
|
|
1930
|
-
for (const c of merged) {
|
|
1931
|
-
const marker = c.saved ? "\u2713" : "+";
|
|
1932
|
-
const meta = [
|
|
1933
|
-
c.repoCount !== void 0 ? `${c.repoCount} repos` : null,
|
|
1934
|
-
c.saved ? "saved" : null
|
|
1935
|
-
].filter(Boolean).join(", ");
|
|
1936
|
-
const suffix = meta ? ` (${meta})` : "";
|
|
1937
|
-
console.log(` [${marker}] ${c.path}${suffix}`);
|
|
1818
|
+
function writeSettings(s) {
|
|
1819
|
+
try {
|
|
1820
|
+
mkdirSync(SETTINGS_DIR, { recursive: true });
|
|
1821
|
+
writeFileSync(SETTINGS_FILE, JSON.stringify(s, null, 2) + "\n");
|
|
1822
|
+
return true;
|
|
1823
|
+
} catch (err) {
|
|
1824
|
+
logger.sys.warn("[hooks.install] settings.json unwritable", {
|
|
1825
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1826
|
+
});
|
|
1827
|
+
return false;
|
|
1938
1828
|
}
|
|
1939
|
-
console.log();
|
|
1940
|
-
return prompts.pickRoots(merged);
|
|
1941
1829
|
}
|
|
1942
|
-
function
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
return true;
|
|
1830
|
+
function ownsEntry(entry) {
|
|
1831
|
+
return (entry.hooks ?? []).some(
|
|
1832
|
+
(h) => typeof h.command === "string" && isJarvisCommand(h.command)
|
|
1833
|
+
);
|
|
1947
1834
|
}
|
|
1948
|
-
|
|
1949
|
-
const
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
}
|
|
1835
|
+
function upsertHook(settings, hook) {
|
|
1836
|
+
const hooks = { ...settings.hooks ?? {} };
|
|
1837
|
+
const prior = hooks[hook.type];
|
|
1838
|
+
const list4 = Array.isArray(prior) ? prior.filter((e) => !ownsEntry(e)) : [];
|
|
1839
|
+
list4.push({
|
|
1840
|
+
matcher: hook.matcher,
|
|
1841
|
+
hooks: [{ type: "command", command: hook.binAbsPath }]
|
|
1955
1842
|
});
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
const resolved = path9.resolve(p.trim());
|
|
1959
|
-
try {
|
|
1960
|
-
return fs10.realpathSync.native(resolved);
|
|
1961
|
-
} catch {
|
|
1962
|
-
return resolved;
|
|
1963
|
-
}
|
|
1964
|
-
}
|
|
1965
|
-
function dedupePaths2(paths) {
|
|
1966
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1967
|
-
const out = [];
|
|
1968
|
-
for (const p of paths) {
|
|
1969
|
-
if (!p) continue;
|
|
1970
|
-
const norm = normalizePath(p);
|
|
1971
|
-
if (seen.has(norm)) continue;
|
|
1972
|
-
seen.add(norm);
|
|
1973
|
-
out.push(norm);
|
|
1974
|
-
}
|
|
1975
|
-
return out;
|
|
1976
|
-
}
|
|
1977
|
-
function mergeRootCandidates(saved, discovered) {
|
|
1978
|
-
const out = [];
|
|
1979
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1980
|
-
const discoveredByPath = /* @__PURE__ */ new Map();
|
|
1981
|
-
for (const c of discovered) discoveredByPath.set(normalizePath(c.path), c);
|
|
1982
|
-
for (const raw of saved) {
|
|
1983
|
-
const norm = normalizePath(raw);
|
|
1984
|
-
if (seen.has(norm)) continue;
|
|
1985
|
-
seen.add(norm);
|
|
1986
|
-
const found = discoveredByPath.get(norm);
|
|
1987
|
-
let exists = false;
|
|
1988
|
-
try {
|
|
1989
|
-
exists = fs10.statSync(norm).isDirectory();
|
|
1990
|
-
} catch {
|
|
1991
|
-
}
|
|
1992
|
-
out.push({
|
|
1993
|
-
path: norm,
|
|
1994
|
-
exists,
|
|
1995
|
-
saved: true,
|
|
1996
|
-
...found?.repoCount !== void 0 ? { repoCount: found.repoCount } : {}
|
|
1997
|
-
});
|
|
1998
|
-
}
|
|
1999
|
-
for (const c of discovered) {
|
|
2000
|
-
const norm = normalizePath(c.path);
|
|
2001
|
-
if (seen.has(norm)) continue;
|
|
2002
|
-
seen.add(norm);
|
|
2003
|
-
out.push({ ...c, path: norm });
|
|
2004
|
-
}
|
|
2005
|
-
return out;
|
|
2006
|
-
}
|
|
2007
|
-
function readLocalRoots() {
|
|
2008
|
-
try {
|
|
2009
|
-
const raw = fs10.readFileSync(path9.join(getConfigDir(), "env.json"), "utf-8");
|
|
2010
|
-
const parsed = JSON.parse(raw);
|
|
2011
|
-
return Array.isArray(parsed.roots) ? parsed.roots : [];
|
|
2012
|
-
} catch {
|
|
2013
|
-
return [];
|
|
2014
|
-
}
|
|
2015
|
-
}
|
|
2016
|
-
function defaultPrompts() {
|
|
2017
|
-
return {
|
|
2018
|
-
async pickRoots(candidates) {
|
|
2019
|
-
const savedDefaults = candidates.filter((c) => c.saved).map((c) => c.path);
|
|
2020
|
-
const defaults = savedDefaults.length > 0 ? savedDefaults : candidates.filter((c) => c.exists).map((c) => c.path);
|
|
2021
|
-
const hint = defaults.length > 0 ? `keep saved (Enter), or enter comma-separated paths
|
|
2022
|
-
[${defaults.join(", ")}]
|
|
2023
|
-
> ` : " enter comma-separated paths\n > ";
|
|
2024
|
-
return promptList(hint, defaults);
|
|
2025
|
-
}
|
|
2026
|
-
};
|
|
2027
|
-
}
|
|
2028
|
-
async function promptList(question, fallback) {
|
|
2029
|
-
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
2030
|
-
const answer = await new Promise((resolve9) => {
|
|
2031
|
-
rl.question(question, (input) => resolve9(input.trim()));
|
|
2032
|
-
});
|
|
2033
|
-
rl.close();
|
|
2034
|
-
const raw = !answer ? fallback : answer.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2035
|
-
return dedupePaths2(raw);
|
|
2036
|
-
}
|
|
2037
|
-
|
|
2038
|
-
// src/commands/pair.ts
|
|
2039
|
-
function register3(program) {
|
|
2040
|
-
program.command("pair").description("Re-pair this machine (when token expired)").action(async () => {
|
|
2041
|
-
try {
|
|
2042
|
-
await runPair();
|
|
2043
|
-
} catch (err) {
|
|
2044
|
-
console.error(`
|
|
2045
|
-
Error: ${err instanceof Error ? err.message : err}`);
|
|
2046
|
-
process.exit(1);
|
|
2047
|
-
}
|
|
2048
|
-
});
|
|
2049
|
-
}
|
|
2050
|
-
async function runPair() {
|
|
2051
|
-
const existing = loadConfig();
|
|
2052
|
-
const url = existing?.url ?? DEFAULT_URL;
|
|
2053
|
-
const result2 = await exchangeCode({
|
|
2054
|
-
url,
|
|
2055
|
-
machineId: getMachine().id,
|
|
2056
|
-
...existing?.envId ? { envId: existing.envId } : {}
|
|
2057
|
-
});
|
|
2058
|
-
saveConfig({
|
|
2059
|
-
...existing ?? {},
|
|
2060
|
-
hubUrl: result2.hubUrl,
|
|
2061
|
-
url,
|
|
2062
|
-
token: result2.token,
|
|
2063
|
-
...result2.refreshToken ? { refreshToken: result2.refreshToken } : {},
|
|
2064
|
-
userId: result2.userId,
|
|
2065
|
-
envId: result2.envId,
|
|
2066
|
-
connectedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2067
|
-
});
|
|
2068
|
-
console.log(" paired \u2713");
|
|
2069
|
-
}
|
|
2070
|
-
|
|
2071
|
-
// src/commands/unpair.ts
|
|
2072
|
-
import { createInterface as createInterface3 } from "readline";
|
|
2073
|
-
|
|
2074
|
-
// src/mcp.ts
|
|
2075
|
-
import { execFile } from "child_process";
|
|
2076
|
-
import { existsSync as existsSync2, readdirSync } from "fs";
|
|
2077
|
-
import os7 from "os";
|
|
2078
|
-
import path11 from "path";
|
|
2079
|
-
import { promisify } from "util";
|
|
2080
|
-
|
|
2081
|
-
// src/hooks/install.ts
|
|
2082
|
-
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "fs";
|
|
2083
|
-
import os6 from "os";
|
|
2084
|
-
import path10 from "path";
|
|
2085
|
-
var SETTINGS_DIR = path10.join(os6.homedir(), ".claude");
|
|
2086
|
-
var SETTINGS_FILE = path10.join(SETTINGS_DIR, "settings.json");
|
|
2087
|
-
var HOOK_STEMS = [
|
|
2088
|
-
"pre-tool-use",
|
|
2089
|
-
"session-start",
|
|
2090
|
-
"stop",
|
|
2091
|
-
"user-prompt-submit",
|
|
2092
|
-
"config-change"
|
|
2093
|
-
];
|
|
2094
|
-
function jarvisHooks() {
|
|
2095
|
-
return [
|
|
2096
|
-
{ type: "PreToolUse", matcher: "*", binBasename: "pre-tool-use.mjs" },
|
|
2097
|
-
{ type: "SessionStart", matcher: "*", binBasename: "session-start.mjs" },
|
|
2098
|
-
{ type: "Stop", matcher: "*", binBasename: "stop.mjs" },
|
|
2099
|
-
{ type: "UserPromptSubmit", matcher: "*", binBasename: "user-prompt-submit.mjs" },
|
|
2100
|
-
{ type: "ConfigChange", matcher: "*", binBasename: "config-change.mjs" }
|
|
2101
|
-
];
|
|
2102
|
-
}
|
|
2103
|
-
function isJarvisCommand(command) {
|
|
2104
|
-
const name = path10.basename(command);
|
|
2105
|
-
return HOOK_STEMS.some((stem) => name === `${stem}.mjs` || name === `${stem}.dev.mjs`);
|
|
2106
|
-
}
|
|
2107
|
-
function getCliRoot() {
|
|
2108
|
-
const binPath = process.argv[1];
|
|
2109
|
-
if (!binPath) {
|
|
2110
|
-
return process.cwd();
|
|
2111
|
-
}
|
|
2112
|
-
let entry = binPath;
|
|
2113
|
-
try {
|
|
2114
|
-
entry = realpathSync(binPath);
|
|
2115
|
-
} catch {
|
|
2116
|
-
}
|
|
2117
|
-
return path10.resolve(path10.dirname(entry), "..");
|
|
2118
|
-
}
|
|
2119
|
-
function resolveHooks() {
|
|
2120
|
-
const cliRoot = getCliRoot();
|
|
2121
|
-
return jarvisHooks().map((h) => ({
|
|
2122
|
-
...h,
|
|
2123
|
-
binAbsPath: path10.join(cliRoot, "bin", h.binBasename)
|
|
2124
|
-
}));
|
|
2125
|
-
}
|
|
2126
|
-
function ownsClaudeCode() {
|
|
2127
|
-
return getConfigDir() === path10.join(os6.homedir(), ".jarvis");
|
|
2128
|
-
}
|
|
2129
|
-
function readSettings() {
|
|
2130
|
-
if (!existsSync(SETTINGS_FILE)) return {};
|
|
2131
|
-
try {
|
|
2132
|
-
const raw = readFileSync(SETTINGS_FILE, "utf-8");
|
|
2133
|
-
return raw.trim() ? JSON.parse(raw) : {};
|
|
2134
|
-
} catch (err) {
|
|
2135
|
-
logger.sys.warn("[hooks.install] settings.json unreadable", {
|
|
2136
|
-
error: err instanceof Error ? err.message : String(err)
|
|
2137
|
-
});
|
|
2138
|
-
return {};
|
|
2139
|
-
}
|
|
2140
|
-
}
|
|
2141
|
-
function writeSettings(s) {
|
|
2142
|
-
try {
|
|
2143
|
-
mkdirSync(SETTINGS_DIR, { recursive: true });
|
|
2144
|
-
writeFileSync(SETTINGS_FILE, JSON.stringify(s, null, 2) + "\n");
|
|
2145
|
-
return true;
|
|
2146
|
-
} catch (err) {
|
|
2147
|
-
logger.sys.warn("[hooks.install] settings.json unwritable", {
|
|
2148
|
-
error: err instanceof Error ? err.message : String(err)
|
|
2149
|
-
});
|
|
2150
|
-
return false;
|
|
2151
|
-
}
|
|
2152
|
-
}
|
|
2153
|
-
function ownsEntry(entry) {
|
|
2154
|
-
return (entry.hooks ?? []).some(
|
|
2155
|
-
(h) => typeof h.command === "string" && isJarvisCommand(h.command)
|
|
2156
|
-
);
|
|
2157
|
-
}
|
|
2158
|
-
function upsertHook(settings, hook) {
|
|
2159
|
-
const hooks = { ...settings.hooks ?? {} };
|
|
2160
|
-
const prior = hooks[hook.type];
|
|
2161
|
-
const list4 = Array.isArray(prior) ? prior.filter((e) => !ownsEntry(e)) : [];
|
|
2162
|
-
list4.push({
|
|
2163
|
-
matcher: hook.matcher,
|
|
2164
|
-
hooks: [{ type: "command", command: hook.binAbsPath }]
|
|
2165
|
-
});
|
|
2166
|
-
hooks[hook.type] = list4;
|
|
2167
|
-
return { ...settings, hooks };
|
|
1843
|
+
hooks[hook.type] = list4;
|
|
1844
|
+
return { ...settings, hooks };
|
|
2168
1845
|
}
|
|
2169
1846
|
function removeHook(settings, hook) {
|
|
2170
1847
|
const hooks = { ...settings.hooks ?? {} };
|
|
@@ -2243,48 +1920,507 @@ function installClaudeCodeHooks() {
|
|
|
2243
1920
|
hooks: installed,
|
|
2244
1921
|
settingsFile: SETTINGS_FILE
|
|
2245
1922
|
});
|
|
2246
|
-
} else {
|
|
2247
|
-
logger.sys.warn("[hooks.install] settings.json write failed", { configDir: configDir2 });
|
|
1923
|
+
} else {
|
|
1924
|
+
logger.sys.warn("[hooks.install] settings.json write failed", { configDir: configDir2 });
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
function uninstallClaudeCodeHooks() {
|
|
1928
|
+
const configDir2 = getConfigDir();
|
|
1929
|
+
if (!ownsClaudeCode()) {
|
|
1930
|
+
logger.sys.info("[hooks.install] not this daemon's Claude Code to unhook, skipping", {
|
|
1931
|
+
configDir: configDir2
|
|
1932
|
+
});
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1935
|
+
logger.sys.info("[hooks.install] uninstall starting", {
|
|
1936
|
+
configDir: configDir2,
|
|
1937
|
+
settingsFile: SETTINGS_FILE
|
|
1938
|
+
});
|
|
1939
|
+
let settings = readSettings();
|
|
1940
|
+
let changed = false;
|
|
1941
|
+
const removed = [];
|
|
1942
|
+
for (const hook of jarvisHooks()) {
|
|
1943
|
+
const r = removeHook(settings, hook);
|
|
1944
|
+
if (r.changed) {
|
|
1945
|
+
settings = r.next;
|
|
1946
|
+
changed = true;
|
|
1947
|
+
removed.push(`${hook.type}:${hook.binBasename}`);
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
if (!changed) {
|
|
1951
|
+
logger.sys.info("[hooks.install] uninstall: nothing to remove", { configDir: configDir2 });
|
|
1952
|
+
return;
|
|
1953
|
+
}
|
|
1954
|
+
if (writeSettings(settings)) {
|
|
1955
|
+
logger.sys.info("[hooks.install] Claude Code hooks uninstalled", {
|
|
1956
|
+
configDir: configDir2,
|
|
1957
|
+
hooks: removed
|
|
1958
|
+
});
|
|
1959
|
+
} else {
|
|
1960
|
+
logger.sys.warn("[hooks.install] uninstall: settings.json write failed", { configDir: configDir2 });
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
// src/commands/hooks.ts
|
|
1965
|
+
var GREEN = (text) => `\x1B[32m${text}\x1B[0m`;
|
|
1966
|
+
var RED = (text) => `\x1B[31m${text}\x1B[0m`;
|
|
1967
|
+
var YELLOW = (text) => `\x1B[33m${text}\x1B[0m`;
|
|
1968
|
+
function quietInstallerNarration() {
|
|
1969
|
+
const drop = () => {
|
|
1970
|
+
};
|
|
1971
|
+
const provider2 = {
|
|
1972
|
+
debug: drop,
|
|
1973
|
+
info: drop,
|
|
1974
|
+
warn: (_ctx, message) => console.warn(` ${message}`),
|
|
1975
|
+
error: (_ctx, message) => console.error(` ${message}`),
|
|
1976
|
+
audit: drop
|
|
1977
|
+
};
|
|
1978
|
+
logger.init({ provider: provider2 });
|
|
1979
|
+
}
|
|
1980
|
+
function isOurs(hook) {
|
|
1981
|
+
return hook.command === hook.bin;
|
|
1982
|
+
}
|
|
1983
|
+
function isElsewhere(hook) {
|
|
1984
|
+
return hook.command !== void 0 && hook.command !== hook.bin;
|
|
1985
|
+
}
|
|
1986
|
+
function report(state2) {
|
|
1987
|
+
console.log(` Settings: ${claudeSettingsFile}`);
|
|
1988
|
+
for (const hook of state2) {
|
|
1989
|
+
const mark = isOurs(hook) ? GREEN("installed") : isElsewhere(hook) ? YELLOW("another install") : RED("missing");
|
|
1990
|
+
const gone = hook.exists ? "" : RED(" \u2014 the shim it names is not there");
|
|
1991
|
+
console.log(` ${hook.type.padEnd(17)} ${mark}${gone}`);
|
|
1992
|
+
console.log(` ${"".padEnd(17)} ${hook.command ?? hook.bin}`);
|
|
1993
|
+
}
|
|
1994
|
+
if (state2.some(isElsewhere)) {
|
|
1995
|
+
console.log(
|
|
1996
|
+
`
|
|
1997
|
+
Claude Code is running hooks from a different copy of the CLI than this one.
|
|
1998
|
+
Both work, and the one you are reading is not the one running. Install from
|
|
1999
|
+
whichever copy you mean to keep, or remove the other.`
|
|
2000
|
+
);
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
function refuseNamedConfigDir() {
|
|
2004
|
+
if (ownsClaudeCode()) return false;
|
|
2005
|
+
console.error(
|
|
2006
|
+
`This config dir does not own this machine's Claude Code, so nothing was changed.
|
|
2007
|
+
Config dir: ${getConfigDir()}
|
|
2008
|
+
There is one Claude Code per machine and the default dir owns it. Run this
|
|
2009
|
+
without JARVIS_CONFIG_DIR set.`
|
|
2010
|
+
);
|
|
2011
|
+
return true;
|
|
2012
|
+
}
|
|
2013
|
+
function installHooks() {
|
|
2014
|
+
if (refuseNamedConfigDir()) return { outcome: "not-ours", installed: 0, total: 0 };
|
|
2015
|
+
quietInstallerNarration();
|
|
2016
|
+
installClaudeCodeHooks();
|
|
2017
|
+
const state2 = claudeCodeHooksState();
|
|
2018
|
+
const installed = state2.filter(isOurs).length;
|
|
2019
|
+
if (installed === 0) {
|
|
2020
|
+
console.error("No hooks were installed.");
|
|
2021
|
+
report(state2);
|
|
2022
|
+
console.error(
|
|
2023
|
+
`
|
|
2024
|
+
Every shim above is missing, which means this is not a complete install.
|
|
2025
|
+
Reinstall the CLI: npm i -g @appchy/jarvis`
|
|
2026
|
+
);
|
|
2027
|
+
return { outcome: "incomplete", installed, total: state2.length };
|
|
2028
|
+
}
|
|
2029
|
+
console.log(`Claude Code hooks installed (${installed} of ${state2.length}):`);
|
|
2030
|
+
report(state2);
|
|
2031
|
+
return { outcome: "installed", installed, total: state2.length };
|
|
2032
|
+
}
|
|
2033
|
+
function register2(program) {
|
|
2034
|
+
const hooks = program.command("hooks").description("Wire this machine's Claude Code to jarvis \u2014 no pairing, no daemon");
|
|
2035
|
+
hooks.command("install").description("Install the Claude Code hooks (the session block and the rules on edit)").action(() => {
|
|
2036
|
+
if (installHooks().outcome !== "installed") process.exit(1);
|
|
2037
|
+
console.log(`
|
|
2038
|
+
Open Claude Code in a repo that has a work/ tree to see them.`);
|
|
2039
|
+
});
|
|
2040
|
+
hooks.command("status").description("What Claude Code will actually run").action(() => {
|
|
2041
|
+
const state2 = claudeCodeHooksState();
|
|
2042
|
+
const installed = state2.filter(isOurs).length;
|
|
2043
|
+
console.log(
|
|
2044
|
+
installed === state2.length ? `All ${state2.length} Claude Code hooks are installed:` : `${installed} of ${state2.length} Claude Code hooks are installed:`
|
|
2045
|
+
);
|
|
2046
|
+
report(state2);
|
|
2047
|
+
if (!ownsClaudeCode()) {
|
|
2048
|
+
console.log(
|
|
2049
|
+
`
|
|
2050
|
+
Note: this config dir (${getConfigDir()}) does not own this machine's
|
|
2051
|
+
Claude Code, so it neither installs these nor removes them.`
|
|
2052
|
+
);
|
|
2053
|
+
}
|
|
2054
|
+
});
|
|
2055
|
+
hooks.command("uninstall").description("Remove the Claude Code hooks this machine installed").action(() => {
|
|
2056
|
+
if (refuseNamedConfigDir()) process.exit(1);
|
|
2057
|
+
quietInstallerNarration();
|
|
2058
|
+
uninstallClaudeCodeHooks();
|
|
2059
|
+
const left = claudeCodeHooksState().filter(isOurs);
|
|
2060
|
+
if (left.length === 0) {
|
|
2061
|
+
console.log("Claude Code hooks removed.");
|
|
2062
|
+
return;
|
|
2063
|
+
}
|
|
2064
|
+
console.error(`${left.length} hook(s) could not be removed:`);
|
|
2065
|
+
report(left);
|
|
2066
|
+
process.exit(1);
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
// src/commands/init.ts
|
|
2071
|
+
function register3(program) {
|
|
2072
|
+
program.command("init").description("Set this machine up to work in repos, and join an instance if you name one").option("--url <url>", "Also join the jarvis instance at this address").option("--skip-daemon", "Skip the background daemon install step").option("--force", "Re-ask all prompts even when config already has answers").action(async (opts) => {
|
|
2073
|
+
const ok = await runInit({
|
|
2074
|
+
url: opts.url,
|
|
2075
|
+
skipDaemonInstall: opts.skipDaemon,
|
|
2076
|
+
force: opts.force
|
|
2077
|
+
});
|
|
2078
|
+
if (!ok) process.exit(1);
|
|
2079
|
+
});
|
|
2080
|
+
}
|
|
2081
|
+
async function runInit(options = {}) {
|
|
2082
|
+
const url = options.url ?? loadConfig()?.url;
|
|
2083
|
+
const prompts = options.prompts ?? defaultPrompts();
|
|
2084
|
+
try {
|
|
2085
|
+
console.log("\n Jarvis setup");
|
|
2086
|
+
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
2087
|
+
setUpThisMachine();
|
|
2088
|
+
if (!url) {
|
|
2089
|
+
reportLocalOnly();
|
|
2090
|
+
return true;
|
|
2091
|
+
}
|
|
2092
|
+
console.log(` \u25B8 Pairing this machine [${url}]`);
|
|
2093
|
+
const saved = loadConfig();
|
|
2094
|
+
let exchanged;
|
|
2095
|
+
try {
|
|
2096
|
+
exchanged = await exchangeCode({
|
|
2097
|
+
url,
|
|
2098
|
+
machineId: getMachine().id,
|
|
2099
|
+
...saved?.envId ? { envId: saved.envId } : {},
|
|
2100
|
+
version: process.env.JARVIS_VERSION ?? "0.0.0"
|
|
2101
|
+
});
|
|
2102
|
+
} catch (err) {
|
|
2103
|
+
const cause = err instanceof Error ? err.cause : void 0;
|
|
2104
|
+
const errCode = cause?.code ? ` (${cause.code})` : "";
|
|
2105
|
+
const hint = ` Check that ${url} is reachable.`;
|
|
2106
|
+
throw new Error(`${err instanceof Error ? err.message : err}${errCode}
|
|
2107
|
+
${hint}`);
|
|
2108
|
+
}
|
|
2109
|
+
console.log(` paired \u2713 (you = ${exchanged.userId}, this env = "${os6.hostname()}")
|
|
2110
|
+
`);
|
|
2111
|
+
const workspacePath2 = saved?.workspacePath ?? process.cwd();
|
|
2112
|
+
saveConfig({
|
|
2113
|
+
...saved ?? {},
|
|
2114
|
+
hubUrl: exchanged.hubUrl,
|
|
2115
|
+
url,
|
|
2116
|
+
token: exchanged.token,
|
|
2117
|
+
...exchanged.refreshToken ? { refreshToken: exchanged.refreshToken } : {},
|
|
2118
|
+
userId: exchanged.userId,
|
|
2119
|
+
envId: exchanged.envId,
|
|
2120
|
+
workspacePath: workspacePath2,
|
|
2121
|
+
connectedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2122
|
+
});
|
|
2123
|
+
const existingRoots = readLocalRoots();
|
|
2124
|
+
const roots = await chooseRoots({
|
|
2125
|
+
existingRoots,
|
|
2126
|
+
force: options.force ?? false,
|
|
2127
|
+
prompts
|
|
2128
|
+
});
|
|
2129
|
+
if (roots && roots.length > 0 && !rootsEqual(roots, existingRoots)) {
|
|
2130
|
+
await applyRoots(roots);
|
|
2131
|
+
console.log(` registered ${roots.length} root(s) \u2713
|
|
2132
|
+
`);
|
|
2133
|
+
} else if (roots && rootsEqual(roots, existingRoots)) {
|
|
2134
|
+
console.log(` kept ${roots.length} saved root(s) \u2713
|
|
2135
|
+
`);
|
|
2136
|
+
}
|
|
2137
|
+
if (!options.skipDaemonInstall) {
|
|
2138
|
+
console.log(" \u25B8 Installing background daemon \u2026");
|
|
2139
|
+
const installed = await tryInstallDaemon();
|
|
2140
|
+
console.log(installed ? " \u2713\n" : " skipped (run 'jarvis start' manually)\n");
|
|
2141
|
+
}
|
|
2142
|
+
console.log(" \u2714 Done. This machine works in repos, and it has joined " + url + ".");
|
|
2143
|
+
console.log(" Manage anytime: jarvis settings");
|
|
2144
|
+
console.log(" " + url + "/settings/environments\n");
|
|
2145
|
+
return true;
|
|
2146
|
+
} catch (err) {
|
|
2147
|
+
console.error(`
|
|
2148
|
+
Error: ${err instanceof Error ? err.message : err}`);
|
|
2149
|
+
return false;
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
function setUpThisMachine() {
|
|
2153
|
+
console.log(" \u25B8 Wiring this machine's Claude Code to jarvis");
|
|
2154
|
+
installHooks();
|
|
2155
|
+
console.log();
|
|
2156
|
+
console.log(" \u25B8 The model key the map embeds with");
|
|
2157
|
+
const key = loadConfig()?.openaiApiKey;
|
|
2158
|
+
console.log(
|
|
2159
|
+
key ? ` set (\u2026${key.slice(-4)}) \u2713 the map builds a semantic index
|
|
2160
|
+
` : ` not set \u2014 the map still builds, and its search stays lexical.
|
|
2161
|
+
Set one with 'jarvis model key <key>'.
|
|
2162
|
+
`
|
|
2163
|
+
);
|
|
2164
|
+
}
|
|
2165
|
+
function reportLocalOnly() {
|
|
2166
|
+
console.log(" \u2714 Done. This machine works in any repo that has a work/ tree:");
|
|
2167
|
+
console.log(" the board, the map, the session block, the rules that arrive on edit,");
|
|
2168
|
+
console.log(" and 'jarvis serve' for any agent that speaks MCP.\n");
|
|
2169
|
+
console.log(" In a repo you have cloned: jarvis build graph");
|
|
2170
|
+
console.log(" jarvis work list\n");
|
|
2171
|
+
console.log(" It has joined no jarvis instance, so it has no web surface and cannot");
|
|
2172
|
+
console.log(" start sessions on other machines. Both need one:");
|
|
2173
|
+
console.log(" jarvis init --url <address>\n");
|
|
2174
|
+
}
|
|
2175
|
+
function discoverCandidateRoots() {
|
|
2176
|
+
const home = os6.homedir();
|
|
2177
|
+
const names = ["Code", "code", "work", "src", "Projects", "projects", "dev", "Developer"];
|
|
2178
|
+
const out = [];
|
|
2179
|
+
for (const name of names) {
|
|
2180
|
+
const p = path10.join(home, name);
|
|
2181
|
+
let exists = false;
|
|
2182
|
+
try {
|
|
2183
|
+
exists = fs10.statSync(p).isDirectory();
|
|
2184
|
+
} catch {
|
|
2185
|
+
continue;
|
|
2186
|
+
}
|
|
2187
|
+
if (!exists) continue;
|
|
2188
|
+
out.push({
|
|
2189
|
+
path: p,
|
|
2190
|
+
exists: true,
|
|
2191
|
+
repoCount: countRepos(p)
|
|
2192
|
+
});
|
|
2193
|
+
}
|
|
2194
|
+
return out;
|
|
2195
|
+
}
|
|
2196
|
+
function countRepos(root) {
|
|
2197
|
+
try {
|
|
2198
|
+
const out = execSync4(`find "${root}" -maxdepth 3 -type d -name .git 2>/dev/null | wc -l`, {
|
|
2199
|
+
encoding: "utf-8",
|
|
2200
|
+
timeout: 5e3
|
|
2201
|
+
}).trim();
|
|
2202
|
+
const n = parseInt(out, 10);
|
|
2203
|
+
return Number.isFinite(n) ? n : void 0;
|
|
2204
|
+
} catch {
|
|
2205
|
+
return void 0;
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
async function tryInstallDaemon() {
|
|
2209
|
+
try {
|
|
2210
|
+
const service = createServiceManager();
|
|
2211
|
+
if (service.isInstalled()) return true;
|
|
2212
|
+
const { nodePath, entryPath } = resolveEntryPoint();
|
|
2213
|
+
service.install({
|
|
2214
|
+
workspacePath: process.cwd(),
|
|
2215
|
+
nodePath,
|
|
2216
|
+
entryPath,
|
|
2217
|
+
upstream: true
|
|
2218
|
+
});
|
|
2219
|
+
return true;
|
|
2220
|
+
} catch {
|
|
2221
|
+
return false;
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
async function chooseRoots(req) {
|
|
2225
|
+
const { existingRoots, force, prompts } = req;
|
|
2226
|
+
if (!force && existingRoots.length > 0) {
|
|
2227
|
+
console.log(
|
|
2228
|
+
` \u25B8 Using existing roots: ${existingRoots.join(", ")} (pass --force to re-pick)
|
|
2229
|
+
`
|
|
2230
|
+
);
|
|
2231
|
+
return null;
|
|
2232
|
+
}
|
|
2233
|
+
console.log(" \u25B8 Where does your code live?");
|
|
2234
|
+
if (existingRoots.length > 0) {
|
|
2235
|
+
console.log(" saved roots:\n");
|
|
2236
|
+
for (const r of existingRoots) console.log(` [\u2713] ${r}`);
|
|
2237
|
+
console.log();
|
|
2238
|
+
}
|
|
2239
|
+
const choices = [];
|
|
2240
|
+
if (existingRoots.length > 0) {
|
|
2241
|
+
choices.push({ key: "k", label: "keep saved (Enter)" });
|
|
2242
|
+
}
|
|
2243
|
+
choices.push({ key: "m", label: "manual entry" });
|
|
2244
|
+
choices.push({ key: "s", label: "scan home dir" });
|
|
2245
|
+
const prompt = " " + choices.map((c) => `[${c.key}] ${c.label}`).join(" \xB7 ") + "\n > ";
|
|
2246
|
+
const answer = (await readlineQuestion2(prompt)).trim().toLowerCase();
|
|
2247
|
+
const defaultKey = existingRoots.length > 0 ? "k" : "m";
|
|
2248
|
+
const choice = answer || defaultKey;
|
|
2249
|
+
if (choice === "k" || choice === "keep") return dedupePaths2(existingRoots);
|
|
2250
|
+
if (choice === "m" || choice === "manual") {
|
|
2251
|
+
const fallback = existingRoots.length > 0 ? existingRoots : [];
|
|
2252
|
+
const hint = fallback.length > 0 ? ` enter comma-separated paths (or Enter to keep saved)
|
|
2253
|
+
[${fallback.join(", ")}]
|
|
2254
|
+
> ` : " enter comma-separated paths\n > ";
|
|
2255
|
+
return promptList(hint, fallback);
|
|
2256
|
+
}
|
|
2257
|
+
console.log(" scanning home dir \u2026 (may take a few seconds)");
|
|
2258
|
+
const discovered = discoverCandidateRoots();
|
|
2259
|
+
const merged = mergeRootCandidates(existingRoots, discovered);
|
|
2260
|
+
if (merged.length === 0) {
|
|
2261
|
+
console.log(" no roots found under your home dir.");
|
|
2262
|
+
return existingRoots.length > 0 ? dedupePaths2(existingRoots) : [];
|
|
2263
|
+
}
|
|
2264
|
+
if (existingRoots.length > 0) {
|
|
2265
|
+
console.log(" saved [\u2713] / discovered [+] roots:\n");
|
|
2266
|
+
} else {
|
|
2267
|
+
console.log(" found these likely roots:\n");
|
|
2268
|
+
}
|
|
2269
|
+
for (const c of merged) {
|
|
2270
|
+
const marker = c.saved ? "\u2713" : "+";
|
|
2271
|
+
const meta = [
|
|
2272
|
+
c.repoCount !== void 0 ? `${c.repoCount} repos` : null,
|
|
2273
|
+
c.saved ? "saved" : null
|
|
2274
|
+
].filter(Boolean).join(", ");
|
|
2275
|
+
const suffix = meta ? ` (${meta})` : "";
|
|
2276
|
+
console.log(` [${marker}] ${c.path}${suffix}`);
|
|
2277
|
+
}
|
|
2278
|
+
console.log();
|
|
2279
|
+
return prompts.pickRoots(merged);
|
|
2280
|
+
}
|
|
2281
|
+
function rootsEqual(a, b) {
|
|
2282
|
+
if (a.length !== b.length) return false;
|
|
2283
|
+
const setA = new Set(a.map(normalizePath));
|
|
2284
|
+
for (const p of b) if (!setA.has(normalizePath(p))) return false;
|
|
2285
|
+
return true;
|
|
2286
|
+
}
|
|
2287
|
+
async function readlineQuestion2(question) {
|
|
2288
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
2289
|
+
return new Promise((resolve9) => {
|
|
2290
|
+
rl.question(question, (input) => {
|
|
2291
|
+
rl.close();
|
|
2292
|
+
resolve9(input);
|
|
2293
|
+
});
|
|
2294
|
+
});
|
|
2295
|
+
}
|
|
2296
|
+
function normalizePath(p) {
|
|
2297
|
+
const resolved = path10.resolve(p.trim());
|
|
2298
|
+
try {
|
|
2299
|
+
return fs10.realpathSync.native(resolved);
|
|
2300
|
+
} catch {
|
|
2301
|
+
return resolved;
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
function dedupePaths2(paths) {
|
|
2305
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2306
|
+
const out = [];
|
|
2307
|
+
for (const p of paths) {
|
|
2308
|
+
if (!p) continue;
|
|
2309
|
+
const norm = normalizePath(p);
|
|
2310
|
+
if (seen.has(norm)) continue;
|
|
2311
|
+
seen.add(norm);
|
|
2312
|
+
out.push(norm);
|
|
2313
|
+
}
|
|
2314
|
+
return out;
|
|
2315
|
+
}
|
|
2316
|
+
function mergeRootCandidates(saved, discovered) {
|
|
2317
|
+
const out = [];
|
|
2318
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2319
|
+
const discoveredByPath = /* @__PURE__ */ new Map();
|
|
2320
|
+
for (const c of discovered) discoveredByPath.set(normalizePath(c.path), c);
|
|
2321
|
+
for (const raw of saved) {
|
|
2322
|
+
const norm = normalizePath(raw);
|
|
2323
|
+
if (seen.has(norm)) continue;
|
|
2324
|
+
seen.add(norm);
|
|
2325
|
+
const found = discoveredByPath.get(norm);
|
|
2326
|
+
let exists = false;
|
|
2327
|
+
try {
|
|
2328
|
+
exists = fs10.statSync(norm).isDirectory();
|
|
2329
|
+
} catch {
|
|
2330
|
+
}
|
|
2331
|
+
out.push({
|
|
2332
|
+
path: norm,
|
|
2333
|
+
exists,
|
|
2334
|
+
saved: true,
|
|
2335
|
+
...found?.repoCount !== void 0 ? { repoCount: found.repoCount } : {}
|
|
2336
|
+
});
|
|
2337
|
+
}
|
|
2338
|
+
for (const c of discovered) {
|
|
2339
|
+
const norm = normalizePath(c.path);
|
|
2340
|
+
if (seen.has(norm)) continue;
|
|
2341
|
+
seen.add(norm);
|
|
2342
|
+
out.push({ ...c, path: norm });
|
|
2248
2343
|
}
|
|
2344
|
+
return out;
|
|
2249
2345
|
}
|
|
2250
|
-
function
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
return;
|
|
2346
|
+
function readLocalRoots() {
|
|
2347
|
+
try {
|
|
2348
|
+
const raw = fs10.readFileSync(path10.join(getConfigDir(), "env.json"), "utf-8");
|
|
2349
|
+
const parsed = JSON.parse(raw);
|
|
2350
|
+
return Array.isArray(parsed.roots) ? parsed.roots : [];
|
|
2351
|
+
} catch {
|
|
2352
|
+
return [];
|
|
2257
2353
|
}
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2354
|
+
}
|
|
2355
|
+
function defaultPrompts() {
|
|
2356
|
+
return {
|
|
2357
|
+
async pickRoots(candidates) {
|
|
2358
|
+
const savedDefaults = candidates.filter((c) => c.saved).map((c) => c.path);
|
|
2359
|
+
const defaults = savedDefaults.length > 0 ? savedDefaults : candidates.filter((c) => c.exists).map((c) => c.path);
|
|
2360
|
+
const hint = defaults.length > 0 ? `keep saved (Enter), or enter comma-separated paths
|
|
2361
|
+
[${defaults.join(", ")}]
|
|
2362
|
+
> ` : " enter comma-separated paths\n > ";
|
|
2363
|
+
return promptList(hint, defaults);
|
|
2364
|
+
}
|
|
2365
|
+
};
|
|
2366
|
+
}
|
|
2367
|
+
async function promptList(question, fallback) {
|
|
2368
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
2369
|
+
const answer = await new Promise((resolve9) => {
|
|
2370
|
+
rl.question(question, (input) => resolve9(input.trim()));
|
|
2261
2371
|
});
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2372
|
+
rl.close();
|
|
2373
|
+
const raw = !answer ? fallback : answer.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2374
|
+
return dedupePaths2(raw);
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
// src/commands/pair.ts
|
|
2378
|
+
function register4(program) {
|
|
2379
|
+
program.command("pair").description("Re-pair this machine (when token expired)").action(async () => {
|
|
2380
|
+
try {
|
|
2381
|
+
await runPair();
|
|
2382
|
+
} catch (err) {
|
|
2383
|
+
console.error(`
|
|
2384
|
+
Error: ${err instanceof Error ? err.message : err}`);
|
|
2385
|
+
process.exit(1);
|
|
2271
2386
|
}
|
|
2387
|
+
});
|
|
2388
|
+
}
|
|
2389
|
+
async function runPair() {
|
|
2390
|
+
const existing = loadConfig();
|
|
2391
|
+
const url = existing?.url;
|
|
2392
|
+
if (!url) {
|
|
2393
|
+
throw new Error(
|
|
2394
|
+
"This machine has not joined a jarvis instance, so there is nothing to re-pair.\n Join one with: jarvis init --url <address>"
|
|
2395
|
+
);
|
|
2272
2396
|
}
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2397
|
+
const result2 = await exchangeCode({
|
|
2398
|
+
url,
|
|
2399
|
+
machineId: getMachine().id,
|
|
2400
|
+
...existing?.envId ? { envId: existing.envId } : {}
|
|
2401
|
+
});
|
|
2402
|
+
saveConfig({
|
|
2403
|
+
...existing ?? {},
|
|
2404
|
+
hubUrl: result2.hubUrl,
|
|
2405
|
+
url,
|
|
2406
|
+
token: result2.token,
|
|
2407
|
+
...result2.refreshToken ? { refreshToken: result2.refreshToken } : {},
|
|
2408
|
+
userId: result2.userId,
|
|
2409
|
+
envId: result2.envId,
|
|
2410
|
+
connectedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2411
|
+
});
|
|
2412
|
+
console.log(" paired \u2713");
|
|
2285
2413
|
}
|
|
2286
2414
|
|
|
2415
|
+
// src/commands/unpair.ts
|
|
2416
|
+
import { createInterface as createInterface3 } from "readline";
|
|
2417
|
+
|
|
2287
2418
|
// src/mcp.ts
|
|
2419
|
+
import { execFile } from "child_process";
|
|
2420
|
+
import { existsSync as existsSync2, readdirSync } from "fs";
|
|
2421
|
+
import os7 from "os";
|
|
2422
|
+
import path11 from "path";
|
|
2423
|
+
import { promisify } from "util";
|
|
2288
2424
|
var run = promisify(execFile);
|
|
2289
2425
|
var SERVER_NAME = "jarvis";
|
|
2290
2426
|
function candidatePaths() {
|
|
@@ -2353,7 +2489,7 @@ async function unpair() {
|
|
|
2353
2489
|
clearConfig();
|
|
2354
2490
|
return { attempted: true, serverStatus };
|
|
2355
2491
|
}
|
|
2356
|
-
function
|
|
2492
|
+
function register5(program) {
|
|
2357
2493
|
program.command("unpair").description("Unregister this env from cloud and wipe local config").option("-y, --yes", "Skip confirmation prompt").action(async (opts) => {
|
|
2358
2494
|
try {
|
|
2359
2495
|
const config2 = loadConfig();
|
|
@@ -2408,7 +2544,7 @@ function getRoots() {
|
|
|
2408
2544
|
const cfg = readConfig();
|
|
2409
2545
|
return Array.isArray(cfg.roots) ? cfg.roots : [];
|
|
2410
2546
|
}
|
|
2411
|
-
function
|
|
2547
|
+
function register6(program) {
|
|
2412
2548
|
const cmd = program.command("roots").description("Manage code-root directories");
|
|
2413
2549
|
cmd.command("list", { isDefault: true }).description("List current roots").action(() => {
|
|
2414
2550
|
const roots = getRoots();
|
|
@@ -2453,7 +2589,7 @@ function readLocalConfig() {
|
|
|
2453
2589
|
return {};
|
|
2454
2590
|
}
|
|
2455
2591
|
}
|
|
2456
|
-
function
|
|
2592
|
+
function register7(program) {
|
|
2457
2593
|
program.command("settings").description("Show this env's settings (roots, repos map)").action(async () => {
|
|
2458
2594
|
try {
|
|
2459
2595
|
const config2 = loadConfig();
|
|
@@ -2496,14 +2632,21 @@ function register6(program) {
|
|
|
2496
2632
|
import { execSync as execSync5 } from "child_process";
|
|
2497
2633
|
|
|
2498
2634
|
// src/auth.ts
|
|
2499
|
-
function
|
|
2500
|
-
return loadConfig()?.url ?? DEFAULT_URL;
|
|
2501
|
-
}
|
|
2502
|
-
async function ensurePaired() {
|
|
2635
|
+
function refuseWithoutInstance() {
|
|
2503
2636
|
const config2 = loadConfig();
|
|
2504
|
-
if (config2?.
|
|
2505
|
-
|
|
2506
|
-
|
|
2637
|
+
if (config2?.hubUrl && config2.envId && config2.token) return false;
|
|
2638
|
+
console.error(
|
|
2639
|
+
`This machine has not joined a jarvis instance, so there is nothing for the daemon to
|
|
2640
|
+
connect to. The daemon is what carries sessions between machines and puts your
|
|
2641
|
+
boards on the web \u2014 it is not what makes jarvis work in a repo.
|
|
2642
|
+
|
|
2643
|
+
Working in a repo needs none of this. If you have not set this machine up yet:
|
|
2644
|
+
jarvis init
|
|
2645
|
+
|
|
2646
|
+
To join an instance, name it:
|
|
2647
|
+
jarvis init --url <address>`
|
|
2648
|
+
);
|
|
2649
|
+
return true;
|
|
2507
2650
|
}
|
|
2508
2651
|
|
|
2509
2652
|
// src/start.ts
|
|
@@ -9645,7 +9788,7 @@ import { createRequire as createRequire2 } from "module";
|
|
|
9645
9788
|
var _require = createRequire2(import.meta.url);
|
|
9646
9789
|
var VERSION2 = _require("../package.json").version ?? "0.0.0";
|
|
9647
9790
|
var IS_DEV = String(_require("../package.json").name ?? "").endsWith("-dev");
|
|
9648
|
-
var SHA = "
|
|
9791
|
+
var SHA = "5b34716";
|
|
9649
9792
|
var BUILT = "2026-09-08";
|
|
9650
9793
|
var BUILD = SHA ?? "source";
|
|
9651
9794
|
var BUILD_LABEL = `${SHA ? `${VERSION2} (${SHA}${BUILT ? ` ${BUILT}` : ""})` : `${VERSION2} (source)`}${IS_DEV ? " \u2014 development build" : ""}`;
|
|
@@ -10163,15 +10306,9 @@ async function fetchRoots(hubUrl, envId, token) {
|
|
|
10163
10306
|
}
|
|
10164
10307
|
|
|
10165
10308
|
// src/commands/start.ts
|
|
10166
|
-
function
|
|
10309
|
+
function register8(program) {
|
|
10167
10310
|
program.command("start").description("Start the local agent (authenticates, cleans up stale processes, starts fresh)").option("-w, --workspace <path>", "Workspace root path").option("--api-key <key>", "Anthropic API key (for local-only use)").option("--no-upstream", "Don't connect to cloud (local-only mode)").option("--foreground", "Run in foreground (don't daemonize)").option("--force", "Re-ask all prompts even when config has them").action(async (opts) => {
|
|
10168
|
-
if (
|
|
10169
|
-
const ok = await ensurePaired();
|
|
10170
|
-
if (!ok) {
|
|
10171
|
-
console.error("Setup failed. Cannot start agent.");
|
|
10172
|
-
process.exit(1);
|
|
10173
|
-
}
|
|
10174
|
-
}
|
|
10311
|
+
if (refuseWithoutInstance()) process.exit(1);
|
|
10175
10312
|
const config2 = loadConfig();
|
|
10176
10313
|
const workspacePath2 = await resolveWorkspacePath(opts, config2);
|
|
10177
10314
|
const userId = config2?.userId ?? process.env.JARVIS_USER_ID ?? "local";
|
|
@@ -10306,7 +10443,7 @@ function buildUpstreamConfig(req) {
|
|
|
10306
10443
|
}
|
|
10307
10444
|
|
|
10308
10445
|
// src/commands/stop.ts
|
|
10309
|
-
function
|
|
10446
|
+
function register9(program) {
|
|
10310
10447
|
program.command("stop").description("Stop the running agent").action(() => {
|
|
10311
10448
|
uninstallClaudeCodeHooks();
|
|
10312
10449
|
const service = createServiceManager();
|
|
@@ -10333,7 +10470,7 @@ function register8(program) {
|
|
|
10333
10470
|
}
|
|
10334
10471
|
|
|
10335
10472
|
// src/commands/restart.ts
|
|
10336
|
-
function
|
|
10473
|
+
function register10(program) {
|
|
10337
10474
|
program.command("restart").description("Restart the agent (alias for start \u2014 start always does a clean restart)").action(async () => {
|
|
10338
10475
|
await program.parseAsync(["node", "jarvis", "start"]);
|
|
10339
10476
|
});
|
|
@@ -10342,7 +10479,7 @@ function register9(program) {
|
|
|
10342
10479
|
// src/commands/logs.ts
|
|
10343
10480
|
import { spawn as spawn2, execSync as execSync6 } from "child_process";
|
|
10344
10481
|
import fs16 from "fs";
|
|
10345
|
-
function
|
|
10482
|
+
function register11(program) {
|
|
10346
10483
|
program.command("logs").description("View agent logs").option("-f, --follow", "Follow log output (like tail -f)", true).option("-n, --lines <n>", "Number of lines to show", "50").action((opts) => {
|
|
10347
10484
|
const logFile = getLogFile();
|
|
10348
10485
|
if (!fs16.existsSync(logFile)) {
|
|
@@ -10415,16 +10552,16 @@ function portOf(url) {
|
|
|
10415
10552
|
}
|
|
10416
10553
|
|
|
10417
10554
|
// src/commands/status.ts
|
|
10418
|
-
var
|
|
10419
|
-
var
|
|
10420
|
-
function
|
|
10555
|
+
var GREEN2 = (text) => `\x1B[32m${text}\x1B[0m`;
|
|
10556
|
+
var RED2 = (text) => `\x1B[31m${text}\x1B[0m`;
|
|
10557
|
+
function register12(program) {
|
|
10421
10558
|
program.command("status").description("Show agent configuration and connection status").action(async () => {
|
|
10422
10559
|
const config2 = loadConfig();
|
|
10423
10560
|
const health = await getHealth();
|
|
10424
10561
|
const { daemon } = health;
|
|
10425
10562
|
console.log(`Jarvis Agent v${BUILD_LABEL}:`);
|
|
10426
10563
|
console.log(
|
|
10427
|
-
` Status: ${daemon.running ?
|
|
10564
|
+
` Status: ${daemon.running ? GREEN2("running") : RED2("stopped")}${daemon.pid ? ` (PID: ${daemon.pid})` : ""}`
|
|
10428
10565
|
);
|
|
10429
10566
|
console.log(` Config: ${getConfigPath()}`);
|
|
10430
10567
|
if (config2) {
|
|
@@ -10442,7 +10579,7 @@ function register11(program) {
|
|
|
10442
10579
|
const svcStatus = createServiceManager().status();
|
|
10443
10580
|
if (svcStatus.installed) {
|
|
10444
10581
|
const label = svcStatus.os ?? "unknown";
|
|
10445
|
-
const state2 = svcStatus.running ?
|
|
10582
|
+
const state2 = svcStatus.running ? GREEN2(`installed & running`) : `\x1B[33minstalled, stopped\x1B[0m`;
|
|
10446
10583
|
console.log(` Service: ${state2} (${label})`);
|
|
10447
10584
|
} else {
|
|
10448
10585
|
console.log(` Service: not installed (run 'jarvis install' for always-on)`);
|
|
@@ -10452,13 +10589,13 @@ function register11(program) {
|
|
|
10452
10589
|
}
|
|
10453
10590
|
function endpoint(probed) {
|
|
10454
10591
|
if (!probed) return null;
|
|
10455
|
-
return `${probed.url} \u2014 ${probed.reachable ?
|
|
10592
|
+
return `${probed.url} \u2014 ${probed.reachable ? GREEN2("reachable") : RED2("not answering")}`;
|
|
10456
10593
|
}
|
|
10457
10594
|
|
|
10458
10595
|
// src/commands/install.ts
|
|
10459
|
-
function
|
|
10596
|
+
function register13(program) {
|
|
10460
10597
|
program.command("install").description("Install as OS service for auto-start on login and crash recovery").option("-w, --workspace <path>", "Workspace root path").option("--no-upstream", "Don't connect to cloud").action(async (opts) => {
|
|
10461
|
-
|
|
10598
|
+
if (refuseWithoutInstance()) process.exit(1);
|
|
10462
10599
|
const config2 = loadConfig();
|
|
10463
10600
|
const workspacePath2 = opts.workspace ?? config2?.workspacePath ?? process.cwd();
|
|
10464
10601
|
const service = createServiceManager();
|
|
@@ -10505,7 +10642,7 @@ function register12(program) {
|
|
|
10505
10642
|
import fs17 from "fs";
|
|
10506
10643
|
import path18 from "path";
|
|
10507
10644
|
import { createInterface as createInterface4 } from "readline";
|
|
10508
|
-
function
|
|
10645
|
+
function register14(program) {
|
|
10509
10646
|
program.command("uninstall").description("Stop the agent, remove the OS service, and wipe local config + identity").option("--keep-data", "Skip the data-wipe prompt; preserve skills/prompts/cache/settings").option("--purge", "Skip the prompt and wipe ALL local data (skills, prompts, cache, settings)").action(async (opts) => {
|
|
10510
10647
|
try {
|
|
10511
10648
|
await runUninstall(opts);
|
|
@@ -10567,7 +10704,7 @@ function confirm2(prompt) {
|
|
|
10567
10704
|
}
|
|
10568
10705
|
|
|
10569
10706
|
// src/commands/logout.ts
|
|
10570
|
-
function
|
|
10707
|
+
function register15(program) {
|
|
10571
10708
|
program.command("logout").description("Clear saved configuration").action(() => {
|
|
10572
10709
|
clearConfig();
|
|
10573
10710
|
console.log("Configuration cleared.");
|
|
@@ -10575,12 +10712,17 @@ function register14(program) {
|
|
|
10575
10712
|
}
|
|
10576
10713
|
|
|
10577
10714
|
// src/commands/mcp.ts
|
|
10715
|
+
function instanceUrl() {
|
|
10716
|
+
const url = loadConfig()?.url;
|
|
10717
|
+
if (!url) throw new Error("not connected \u2014 run `jarvis connect` first");
|
|
10718
|
+
return url;
|
|
10719
|
+
}
|
|
10578
10720
|
async function call(path22, method, body) {
|
|
10579
10721
|
const config2 = loadConfig();
|
|
10580
10722
|
if (!config2?.token) {
|
|
10581
10723
|
throw new Error("not connected \u2014 run `jarvis connect` first");
|
|
10582
10724
|
}
|
|
10583
|
-
const resp = await fetch(new URL(path22,
|
|
10725
|
+
const resp = await fetch(new URL(path22, instanceUrl()), {
|
|
10584
10726
|
method,
|
|
10585
10727
|
headers: {
|
|
10586
10728
|
authorization: `Bearer ${config2.token}`,
|
|
@@ -10593,7 +10735,7 @@ async function call(path22, method, body) {
|
|
|
10593
10735
|
}
|
|
10594
10736
|
return await resp.json();
|
|
10595
10737
|
}
|
|
10596
|
-
function
|
|
10738
|
+
function register16(program) {
|
|
10597
10739
|
const mcp = program.command("mcp").description("Connect an agent to this workspace's board");
|
|
10598
10740
|
mcp.command("token").description("Mint a personal token for the Jarvis MCP server").option("--name <name>", "Which machine this token is for, e.g. 'laptop'").option("--scopes <list>", "read | write | read,write", "read,write").option("--show", "Show the current token's details instead of minting").option("--revoke", "Revoke the current token").action(async (opts) => {
|
|
10599
10741
|
try {
|
|
@@ -10621,7 +10763,7 @@ function register15(program) {
|
|
|
10621
10763
|
console.log(token);
|
|
10622
10764
|
console.log("");
|
|
10623
10765
|
console.log("Shown once. Paste it into your client's config now:");
|
|
10624
|
-
console.log(` ${new URL("/api/mcp",
|
|
10766
|
+
console.log(` ${new URL("/api/mcp", instanceUrl())}`);
|
|
10625
10767
|
console.log(" header: Authorization: Bearer <the token above>");
|
|
10626
10768
|
if (!opts.name) console.log("");
|
|
10627
10769
|
if (!opts.name) console.log("Tip: --name laptop makes it obvious later which is which.");
|
|
@@ -10647,7 +10789,7 @@ function register15(program) {
|
|
|
10647
10789
|
}
|
|
10648
10790
|
|
|
10649
10791
|
// src/commands/model.ts
|
|
10650
|
-
function
|
|
10792
|
+
function register17(program) {
|
|
10651
10793
|
const model = program.command("model").description("Model access for the map's semantic search, on this machine");
|
|
10652
10794
|
model.command("key").argument("[key]", "The OpenAI key to store. Omit it to see what is set.").option("--show", "Say whether a key is set, without printing it").option("--clear", "Forget the stored key").description("Set the OpenAI key the map embeds with").action((key, opts) => {
|
|
10653
10795
|
const config2 = loadConfig();
|
|
@@ -10959,7 +11101,7 @@ function loadEnvFile(path22) {
|
|
|
10959
11101
|
}
|
|
10960
11102
|
|
|
10961
11103
|
// src/commands/build.ts
|
|
10962
|
-
function
|
|
11104
|
+
function register18(program) {
|
|
10963
11105
|
const build2 = program.command("build").description("Build an artifact from this repo");
|
|
10964
11106
|
build2.command("graph").description("Read the repo's jarvis.config.ts, build the graph, and say what is wrong with it").option("--repo <path>", "The repo to map. Default: the working directory").option("--rebuild", "LAST RESORT: throw the graph away and re-extract from scratch").option("--label", "Re-name the detected communities through the configured labeler").option("--gate", "Fail when a governance class has more findings than this repo carries").action(async (opts) => {
|
|
10965
11107
|
const argv = [
|
|
@@ -11019,7 +11161,7 @@ function isRedirected(req) {
|
|
|
11019
11161
|
}
|
|
11020
11162
|
|
|
11021
11163
|
// src/commands/dev.ts
|
|
11022
|
-
function
|
|
11164
|
+
function register19(program) {
|
|
11023
11165
|
const dev = program.command("dev").description("Run a development build in a repo, without replacing the installed jarvis");
|
|
11024
11166
|
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) => {
|
|
11025
11167
|
const current = readDevRedirect();
|
|
@@ -11733,7 +11875,7 @@ async function brief2(repo) {
|
|
|
11733
11875
|
}
|
|
11734
11876
|
|
|
11735
11877
|
// src/commands/serve.ts
|
|
11736
|
-
function
|
|
11878
|
+
function register20(program) {
|
|
11737
11879
|
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) => {
|
|
11738
11880
|
const repo = resolveRepo(opts.repo);
|
|
11739
11881
|
const problem = harnessProblem();
|
|
@@ -11746,108 +11888,6 @@ function register19(program) {
|
|
|
11746
11888
|
});
|
|
11747
11889
|
}
|
|
11748
11890
|
|
|
11749
|
-
// src/commands/hooks.ts
|
|
11750
|
-
var GREEN2 = (text) => `\x1B[32m${text}\x1B[0m`;
|
|
11751
|
-
var RED2 = (text) => `\x1B[31m${text}\x1B[0m`;
|
|
11752
|
-
var YELLOW = (text) => `\x1B[33m${text}\x1B[0m`;
|
|
11753
|
-
function quietInstallerNarration() {
|
|
11754
|
-
const drop = () => {
|
|
11755
|
-
};
|
|
11756
|
-
const provider2 = {
|
|
11757
|
-
debug: drop,
|
|
11758
|
-
info: drop,
|
|
11759
|
-
warn: (_ctx, message) => console.warn(` ${message}`),
|
|
11760
|
-
error: (_ctx, message) => console.error(` ${message}`),
|
|
11761
|
-
audit: drop
|
|
11762
|
-
};
|
|
11763
|
-
logger.init({ provider: provider2 });
|
|
11764
|
-
}
|
|
11765
|
-
function isOurs(hook) {
|
|
11766
|
-
return hook.command === hook.bin;
|
|
11767
|
-
}
|
|
11768
|
-
function isElsewhere(hook) {
|
|
11769
|
-
return hook.command !== void 0 && hook.command !== hook.bin;
|
|
11770
|
-
}
|
|
11771
|
-
function report(state2) {
|
|
11772
|
-
console.log(` Settings: ${claudeSettingsFile}`);
|
|
11773
|
-
for (const hook of state2) {
|
|
11774
|
-
const mark = isOurs(hook) ? GREEN2("installed") : isElsewhere(hook) ? YELLOW("another install") : RED2("missing");
|
|
11775
|
-
const gone = hook.exists ? "" : RED2(" \u2014 the shim it names is not there");
|
|
11776
|
-
console.log(` ${hook.type.padEnd(17)} ${mark}${gone}`);
|
|
11777
|
-
console.log(` ${"".padEnd(17)} ${hook.command ?? hook.bin}`);
|
|
11778
|
-
}
|
|
11779
|
-
if (state2.some(isElsewhere)) {
|
|
11780
|
-
console.log(
|
|
11781
|
-
`
|
|
11782
|
-
Claude Code is running hooks from a different copy of the CLI than this one.
|
|
11783
|
-
Both work, and the one you are reading is not the one running. Install from
|
|
11784
|
-
whichever copy you mean to keep, or remove the other.`
|
|
11785
|
-
);
|
|
11786
|
-
}
|
|
11787
|
-
}
|
|
11788
|
-
function refuseNamedConfigDir() {
|
|
11789
|
-
if (ownsClaudeCode()) return false;
|
|
11790
|
-
console.error(
|
|
11791
|
-
`This config dir does not own this machine's Claude Code, so nothing was changed.
|
|
11792
|
-
Config dir: ${getConfigDir()}
|
|
11793
|
-
There is one Claude Code per machine and the default dir owns it. Run this
|
|
11794
|
-
without JARVIS_CONFIG_DIR set.`
|
|
11795
|
-
);
|
|
11796
|
-
return true;
|
|
11797
|
-
}
|
|
11798
|
-
function register20(program) {
|
|
11799
|
-
const hooks = program.command("hooks").description("Wire this machine's Claude Code to jarvis \u2014 no pairing, no daemon");
|
|
11800
|
-
hooks.command("install").description("Install the Claude Code hooks (the session block and the rules on edit)").action(() => {
|
|
11801
|
-
if (refuseNamedConfigDir()) process.exit(1);
|
|
11802
|
-
quietInstallerNarration();
|
|
11803
|
-
installClaudeCodeHooks();
|
|
11804
|
-
const state2 = claudeCodeHooksState();
|
|
11805
|
-
const installed = state2.filter(isOurs).length;
|
|
11806
|
-
if (installed === 0) {
|
|
11807
|
-
console.error("No hooks were installed.");
|
|
11808
|
-
report(state2);
|
|
11809
|
-
console.error(
|
|
11810
|
-
`
|
|
11811
|
-
Every shim above is missing, which means this is not a complete install.
|
|
11812
|
-
Reinstall the CLI: npm i -g @appchy/jarvis`
|
|
11813
|
-
);
|
|
11814
|
-
process.exit(1);
|
|
11815
|
-
}
|
|
11816
|
-
console.log(`Claude Code hooks installed (${installed} of ${state2.length}):`);
|
|
11817
|
-
report(state2);
|
|
11818
|
-
console.log(`
|
|
11819
|
-
Open Claude Code in a repo that has a work/ tree to see them.`);
|
|
11820
|
-
});
|
|
11821
|
-
hooks.command("status").description("What Claude Code will actually run").action(() => {
|
|
11822
|
-
const state2 = claudeCodeHooksState();
|
|
11823
|
-
const installed = state2.filter(isOurs).length;
|
|
11824
|
-
console.log(
|
|
11825
|
-
installed === state2.length ? `All ${state2.length} Claude Code hooks are installed:` : `${installed} of ${state2.length} Claude Code hooks are installed:`
|
|
11826
|
-
);
|
|
11827
|
-
report(state2);
|
|
11828
|
-
if (!ownsClaudeCode()) {
|
|
11829
|
-
console.log(
|
|
11830
|
-
`
|
|
11831
|
-
Note: this config dir (${getConfigDir()}) does not own this machine's
|
|
11832
|
-
Claude Code, so it neither installs these nor removes them.`
|
|
11833
|
-
);
|
|
11834
|
-
}
|
|
11835
|
-
});
|
|
11836
|
-
hooks.command("uninstall").description("Remove the Claude Code hooks this machine installed").action(() => {
|
|
11837
|
-
if (refuseNamedConfigDir()) process.exit(1);
|
|
11838
|
-
quietInstallerNarration();
|
|
11839
|
-
uninstallClaudeCodeHooks();
|
|
11840
|
-
const left = claudeCodeHooksState().filter(isOurs);
|
|
11841
|
-
if (left.length === 0) {
|
|
11842
|
-
console.log("Claude Code hooks removed.");
|
|
11843
|
-
return;
|
|
11844
|
-
}
|
|
11845
|
-
console.error(`${left.length} hook(s) could not be removed:`);
|
|
11846
|
-
report(left);
|
|
11847
|
-
process.exit(1);
|
|
11848
|
-
});
|
|
11849
|
-
}
|
|
11850
|
-
|
|
11851
11891
|
// src/commands/work.ts
|
|
11852
11892
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
11853
11893
|
function register21(program) {
|
|
@@ -11873,11 +11913,10 @@ function register21(program) {
|
|
|
11873
11913
|
// src/cli.ts
|
|
11874
11914
|
function createCli() {
|
|
11875
11915
|
const program = new Command().enablePositionalOptions().name("jarvis").description("Jarvis local agent \u2014 watches Claude Code sessions on your machine").version(BUILD_LABEL);
|
|
11876
|
-
register2(program);
|
|
11877
11916
|
register3(program);
|
|
11878
11917
|
register4(program);
|
|
11879
|
-
register(program);
|
|
11880
11918
|
register5(program);
|
|
11919
|
+
register(program);
|
|
11881
11920
|
register6(program);
|
|
11882
11921
|
register7(program);
|
|
11883
11922
|
register8(program);
|
|
@@ -11893,6 +11932,7 @@ function createCli() {
|
|
|
11893
11932
|
register18(program);
|
|
11894
11933
|
register19(program);
|
|
11895
11934
|
register20(program);
|
|
11935
|
+
register2(program);
|
|
11896
11936
|
register21(program);
|
|
11897
11937
|
return program;
|
|
11898
11938
|
}
|