@appchy/jarvis 0.1.46 → 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 +636 -574
- package/dist/bin.js.map +1 -1
- package/dist/data/backends.mjs +10 -8
- package/dist/data/{chunk-LYW75USL.mjs → chunk-YBCQMMVE.mjs} +24 -2
- package/dist/data/embedders.mjs +1 -1
- package/dist/data/index.mjs +1 -1
- package/dist/data/labelers.mjs +1 -1
- package/dist/data/linkers.mjs +1 -1
- package/dist/data/mcp.mjs +1 -1
- package/dist/data/stores.mjs +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 +2 -2
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 ?? {} };
|
|
@@ -2247,44 +1924,503 @@ function installClaudeCodeHooks() {
|
|
|
2247
1924
|
logger.sys.warn("[hooks.install] settings.json write failed", { configDir: configDir2 });
|
|
2248
1925
|
}
|
|
2249
1926
|
}
|
|
2250
|
-
function uninstallClaudeCodeHooks() {
|
|
2251
|
-
const configDir2 = getConfigDir();
|
|
2252
|
-
if (!ownsClaudeCode()) {
|
|
2253
|
-
logger.sys.info("[hooks.install] not this daemon's Claude Code to unhook, skipping", {
|
|
2254
|
-
configDir: configDir2
|
|
2255
|
-
});
|
|
2256
|
-
return;
|
|
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 });
|
|
2343
|
+
}
|
|
2344
|
+
return out;
|
|
2345
|
+
}
|
|
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
|
|
@@ -5819,6 +5962,17 @@ function validateConfig(value, path22) {
|
|
|
5819
5962
|
}
|
|
5820
5963
|
|
|
5821
5964
|
// ../../packages/data/src/embeddings.ts
|
|
5965
|
+
var CACHE_FORMAT = 2;
|
|
5966
|
+
function encodeVector(vector) {
|
|
5967
|
+
const floats = Float32Array.from(vector);
|
|
5968
|
+
return Buffer.from(floats.buffer, floats.byteOffset, floats.byteLength).toString("base64");
|
|
5969
|
+
}
|
|
5970
|
+
function decodeVector(encoded) {
|
|
5971
|
+
const buf = Buffer.from(encoded, "base64");
|
|
5972
|
+
const floats = new Float32Array(buf.byteLength / 4);
|
|
5973
|
+
Buffer.from(floats.buffer).set(buf);
|
|
5974
|
+
return Array.from(floats);
|
|
5975
|
+
}
|
|
5822
5976
|
async function buildEmbeddings(config2, graph2) {
|
|
5823
5977
|
const cfg = config2.search;
|
|
5824
5978
|
if (!cfg) return void 0;
|
|
@@ -5951,7 +6105,13 @@ async function loadCache(config2, ctx, model) {
|
|
|
5951
6105
|
const raw = await config2.persistence.load(ctx, "embeddings");
|
|
5952
6106
|
if (raw) {
|
|
5953
6107
|
const parsed = JSON.parse(raw);
|
|
5954
|
-
if (parsed.model === model && parsed.entries)
|
|
6108
|
+
if (parsed.model === model && parsed.format === CACHE_FORMAT && parsed.entries) {
|
|
6109
|
+
const entries = {};
|
|
6110
|
+
for (const [id, entry] of Object.entries(parsed.entries)) {
|
|
6111
|
+
entries[id] = { hash: entry.hash, vector: decodeVector(entry.vector) };
|
|
6112
|
+
}
|
|
6113
|
+
return { model: parsed.model, entries };
|
|
6114
|
+
}
|
|
5955
6115
|
}
|
|
5956
6116
|
} catch {
|
|
5957
6117
|
}
|
|
@@ -5959,7 +6119,12 @@ async function loadCache(config2, ctx, model) {
|
|
|
5959
6119
|
}
|
|
5960
6120
|
async function writeCache(config2, ctx, cache2) {
|
|
5961
6121
|
parsedCache.set(cacheKey(ctx, cache2.model), cache2);
|
|
5962
|
-
|
|
6122
|
+
const entries = {};
|
|
6123
|
+
for (const [id, entry] of Object.entries(cache2.entries)) {
|
|
6124
|
+
entries[id] = { hash: entry.hash, vector: encodeVector(entry.vector) };
|
|
6125
|
+
}
|
|
6126
|
+
const onDisk = { model: cache2.model, format: CACHE_FORMAT, entries };
|
|
6127
|
+
await config2.persistence.save(ctx, "embeddings", `${JSON.stringify(onDisk)}
|
|
5963
6128
|
`);
|
|
5964
6129
|
}
|
|
5965
6130
|
function hashText(text) {
|
|
@@ -9623,8 +9788,8 @@ import { createRequire as createRequire2 } from "module";
|
|
|
9623
9788
|
var _require = createRequire2(import.meta.url);
|
|
9624
9789
|
var VERSION2 = _require("../package.json").version ?? "0.0.0";
|
|
9625
9790
|
var IS_DEV = String(_require("../package.json").name ?? "").endsWith("-dev");
|
|
9626
|
-
var SHA = "
|
|
9627
|
-
var BUILT = "2026-09-
|
|
9791
|
+
var SHA = "5b34716";
|
|
9792
|
+
var BUILT = "2026-09-08";
|
|
9628
9793
|
var BUILD = SHA ?? "source";
|
|
9629
9794
|
var BUILD_LABEL = `${SHA ? `${VERSION2} (${SHA}${BUILT ? ` ${BUILT}` : ""})` : `${VERSION2} (source)`}${IS_DEV ? " \u2014 development build" : ""}`;
|
|
9630
9795
|
|
|
@@ -10141,15 +10306,9 @@ async function fetchRoots(hubUrl, envId, token) {
|
|
|
10141
10306
|
}
|
|
10142
10307
|
|
|
10143
10308
|
// src/commands/start.ts
|
|
10144
|
-
function
|
|
10309
|
+
function register8(program) {
|
|
10145
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) => {
|
|
10146
|
-
if (
|
|
10147
|
-
const ok = await ensurePaired();
|
|
10148
|
-
if (!ok) {
|
|
10149
|
-
console.error("Setup failed. Cannot start agent.");
|
|
10150
|
-
process.exit(1);
|
|
10151
|
-
}
|
|
10152
|
-
}
|
|
10311
|
+
if (refuseWithoutInstance()) process.exit(1);
|
|
10153
10312
|
const config2 = loadConfig();
|
|
10154
10313
|
const workspacePath2 = await resolveWorkspacePath(opts, config2);
|
|
10155
10314
|
const userId = config2?.userId ?? process.env.JARVIS_USER_ID ?? "local";
|
|
@@ -10284,7 +10443,7 @@ function buildUpstreamConfig(req) {
|
|
|
10284
10443
|
}
|
|
10285
10444
|
|
|
10286
10445
|
// src/commands/stop.ts
|
|
10287
|
-
function
|
|
10446
|
+
function register9(program) {
|
|
10288
10447
|
program.command("stop").description("Stop the running agent").action(() => {
|
|
10289
10448
|
uninstallClaudeCodeHooks();
|
|
10290
10449
|
const service = createServiceManager();
|
|
@@ -10311,7 +10470,7 @@ function register8(program) {
|
|
|
10311
10470
|
}
|
|
10312
10471
|
|
|
10313
10472
|
// src/commands/restart.ts
|
|
10314
|
-
function
|
|
10473
|
+
function register10(program) {
|
|
10315
10474
|
program.command("restart").description("Restart the agent (alias for start \u2014 start always does a clean restart)").action(async () => {
|
|
10316
10475
|
await program.parseAsync(["node", "jarvis", "start"]);
|
|
10317
10476
|
});
|
|
@@ -10320,7 +10479,7 @@ function register9(program) {
|
|
|
10320
10479
|
// src/commands/logs.ts
|
|
10321
10480
|
import { spawn as spawn2, execSync as execSync6 } from "child_process";
|
|
10322
10481
|
import fs16 from "fs";
|
|
10323
|
-
function
|
|
10482
|
+
function register11(program) {
|
|
10324
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) => {
|
|
10325
10484
|
const logFile = getLogFile();
|
|
10326
10485
|
if (!fs16.existsSync(logFile)) {
|
|
@@ -10393,16 +10552,16 @@ function portOf(url) {
|
|
|
10393
10552
|
}
|
|
10394
10553
|
|
|
10395
10554
|
// src/commands/status.ts
|
|
10396
|
-
var
|
|
10397
|
-
var
|
|
10398
|
-
function
|
|
10555
|
+
var GREEN2 = (text) => `\x1B[32m${text}\x1B[0m`;
|
|
10556
|
+
var RED2 = (text) => `\x1B[31m${text}\x1B[0m`;
|
|
10557
|
+
function register12(program) {
|
|
10399
10558
|
program.command("status").description("Show agent configuration and connection status").action(async () => {
|
|
10400
10559
|
const config2 = loadConfig();
|
|
10401
10560
|
const health = await getHealth();
|
|
10402
10561
|
const { daemon } = health;
|
|
10403
10562
|
console.log(`Jarvis Agent v${BUILD_LABEL}:`);
|
|
10404
10563
|
console.log(
|
|
10405
|
-
` Status: ${daemon.running ?
|
|
10564
|
+
` Status: ${daemon.running ? GREEN2("running") : RED2("stopped")}${daemon.pid ? ` (PID: ${daemon.pid})` : ""}`
|
|
10406
10565
|
);
|
|
10407
10566
|
console.log(` Config: ${getConfigPath()}`);
|
|
10408
10567
|
if (config2) {
|
|
@@ -10420,7 +10579,7 @@ function register11(program) {
|
|
|
10420
10579
|
const svcStatus = createServiceManager().status();
|
|
10421
10580
|
if (svcStatus.installed) {
|
|
10422
10581
|
const label = svcStatus.os ?? "unknown";
|
|
10423
|
-
const state2 = svcStatus.running ?
|
|
10582
|
+
const state2 = svcStatus.running ? GREEN2(`installed & running`) : `\x1B[33minstalled, stopped\x1B[0m`;
|
|
10424
10583
|
console.log(` Service: ${state2} (${label})`);
|
|
10425
10584
|
} else {
|
|
10426
10585
|
console.log(` Service: not installed (run 'jarvis install' for always-on)`);
|
|
@@ -10430,13 +10589,13 @@ function register11(program) {
|
|
|
10430
10589
|
}
|
|
10431
10590
|
function endpoint(probed) {
|
|
10432
10591
|
if (!probed) return null;
|
|
10433
|
-
return `${probed.url} \u2014 ${probed.reachable ?
|
|
10592
|
+
return `${probed.url} \u2014 ${probed.reachable ? GREEN2("reachable") : RED2("not answering")}`;
|
|
10434
10593
|
}
|
|
10435
10594
|
|
|
10436
10595
|
// src/commands/install.ts
|
|
10437
|
-
function
|
|
10596
|
+
function register13(program) {
|
|
10438
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) => {
|
|
10439
|
-
|
|
10598
|
+
if (refuseWithoutInstance()) process.exit(1);
|
|
10440
10599
|
const config2 = loadConfig();
|
|
10441
10600
|
const workspacePath2 = opts.workspace ?? config2?.workspacePath ?? process.cwd();
|
|
10442
10601
|
const service = createServiceManager();
|
|
@@ -10483,7 +10642,7 @@ function register12(program) {
|
|
|
10483
10642
|
import fs17 from "fs";
|
|
10484
10643
|
import path18 from "path";
|
|
10485
10644
|
import { createInterface as createInterface4 } from "readline";
|
|
10486
|
-
function
|
|
10645
|
+
function register14(program) {
|
|
10487
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) => {
|
|
10488
10647
|
try {
|
|
10489
10648
|
await runUninstall(opts);
|
|
@@ -10545,7 +10704,7 @@ function confirm2(prompt) {
|
|
|
10545
10704
|
}
|
|
10546
10705
|
|
|
10547
10706
|
// src/commands/logout.ts
|
|
10548
|
-
function
|
|
10707
|
+
function register15(program) {
|
|
10549
10708
|
program.command("logout").description("Clear saved configuration").action(() => {
|
|
10550
10709
|
clearConfig();
|
|
10551
10710
|
console.log("Configuration cleared.");
|
|
@@ -10553,12 +10712,17 @@ function register14(program) {
|
|
|
10553
10712
|
}
|
|
10554
10713
|
|
|
10555
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
|
+
}
|
|
10556
10720
|
async function call(path22, method, body) {
|
|
10557
10721
|
const config2 = loadConfig();
|
|
10558
10722
|
if (!config2?.token) {
|
|
10559
10723
|
throw new Error("not connected \u2014 run `jarvis connect` first");
|
|
10560
10724
|
}
|
|
10561
|
-
const resp = await fetch(new URL(path22,
|
|
10725
|
+
const resp = await fetch(new URL(path22, instanceUrl()), {
|
|
10562
10726
|
method,
|
|
10563
10727
|
headers: {
|
|
10564
10728
|
authorization: `Bearer ${config2.token}`,
|
|
@@ -10571,7 +10735,7 @@ async function call(path22, method, body) {
|
|
|
10571
10735
|
}
|
|
10572
10736
|
return await resp.json();
|
|
10573
10737
|
}
|
|
10574
|
-
function
|
|
10738
|
+
function register16(program) {
|
|
10575
10739
|
const mcp = program.command("mcp").description("Connect an agent to this workspace's board");
|
|
10576
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) => {
|
|
10577
10741
|
try {
|
|
@@ -10599,7 +10763,7 @@ function register15(program) {
|
|
|
10599
10763
|
console.log(token);
|
|
10600
10764
|
console.log("");
|
|
10601
10765
|
console.log("Shown once. Paste it into your client's config now:");
|
|
10602
|
-
console.log(` ${new URL("/api/mcp",
|
|
10766
|
+
console.log(` ${new URL("/api/mcp", instanceUrl())}`);
|
|
10603
10767
|
console.log(" header: Authorization: Bearer <the token above>");
|
|
10604
10768
|
if (!opts.name) console.log("");
|
|
10605
10769
|
if (!opts.name) console.log("Tip: --name laptop makes it obvious later which is which.");
|
|
@@ -10625,7 +10789,7 @@ function register15(program) {
|
|
|
10625
10789
|
}
|
|
10626
10790
|
|
|
10627
10791
|
// src/commands/model.ts
|
|
10628
|
-
function
|
|
10792
|
+
function register17(program) {
|
|
10629
10793
|
const model = program.command("model").description("Model access for the map's semantic search, on this machine");
|
|
10630
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) => {
|
|
10631
10795
|
const config2 = loadConfig();
|
|
@@ -10937,7 +11101,7 @@ function loadEnvFile(path22) {
|
|
|
10937
11101
|
}
|
|
10938
11102
|
|
|
10939
11103
|
// src/commands/build.ts
|
|
10940
|
-
function
|
|
11104
|
+
function register18(program) {
|
|
10941
11105
|
const build2 = program.command("build").description("Build an artifact from this repo");
|
|
10942
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) => {
|
|
10943
11107
|
const argv = [
|
|
@@ -10997,7 +11161,7 @@ function isRedirected(req) {
|
|
|
10997
11161
|
}
|
|
10998
11162
|
|
|
10999
11163
|
// src/commands/dev.ts
|
|
11000
|
-
function
|
|
11164
|
+
function register19(program) {
|
|
11001
11165
|
const dev = program.command("dev").description("Run a development build in a repo, without replacing the installed jarvis");
|
|
11002
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) => {
|
|
11003
11167
|
const current = readDevRedirect();
|
|
@@ -11711,7 +11875,7 @@ async function brief2(repo) {
|
|
|
11711
11875
|
}
|
|
11712
11876
|
|
|
11713
11877
|
// src/commands/serve.ts
|
|
11714
|
-
function
|
|
11878
|
+
function register20(program) {
|
|
11715
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) => {
|
|
11716
11880
|
const repo = resolveRepo(opts.repo);
|
|
11717
11881
|
const problem = harnessProblem();
|
|
@@ -11724,108 +11888,6 @@ function register19(program) {
|
|
|
11724
11888
|
});
|
|
11725
11889
|
}
|
|
11726
11890
|
|
|
11727
|
-
// src/commands/hooks.ts
|
|
11728
|
-
var GREEN2 = (text) => `\x1B[32m${text}\x1B[0m`;
|
|
11729
|
-
var RED2 = (text) => `\x1B[31m${text}\x1B[0m`;
|
|
11730
|
-
var YELLOW = (text) => `\x1B[33m${text}\x1B[0m`;
|
|
11731
|
-
function quietInstallerNarration() {
|
|
11732
|
-
const drop = () => {
|
|
11733
|
-
};
|
|
11734
|
-
const provider2 = {
|
|
11735
|
-
debug: drop,
|
|
11736
|
-
info: drop,
|
|
11737
|
-
warn: (_ctx, message) => console.warn(` ${message}`),
|
|
11738
|
-
error: (_ctx, message) => console.error(` ${message}`),
|
|
11739
|
-
audit: drop
|
|
11740
|
-
};
|
|
11741
|
-
logger.init({ provider: provider2 });
|
|
11742
|
-
}
|
|
11743
|
-
function isOurs(hook) {
|
|
11744
|
-
return hook.command === hook.bin;
|
|
11745
|
-
}
|
|
11746
|
-
function isElsewhere(hook) {
|
|
11747
|
-
return hook.command !== void 0 && hook.command !== hook.bin;
|
|
11748
|
-
}
|
|
11749
|
-
function report(state2) {
|
|
11750
|
-
console.log(` Settings: ${claudeSettingsFile}`);
|
|
11751
|
-
for (const hook of state2) {
|
|
11752
|
-
const mark = isOurs(hook) ? GREEN2("installed") : isElsewhere(hook) ? YELLOW("another install") : RED2("missing");
|
|
11753
|
-
const gone = hook.exists ? "" : RED2(" \u2014 the shim it names is not there");
|
|
11754
|
-
console.log(` ${hook.type.padEnd(17)} ${mark}${gone}`);
|
|
11755
|
-
console.log(` ${"".padEnd(17)} ${hook.command ?? hook.bin}`);
|
|
11756
|
-
}
|
|
11757
|
-
if (state2.some(isElsewhere)) {
|
|
11758
|
-
console.log(
|
|
11759
|
-
`
|
|
11760
|
-
Claude Code is running hooks from a different copy of the CLI than this one.
|
|
11761
|
-
Both work, and the one you are reading is not the one running. Install from
|
|
11762
|
-
whichever copy you mean to keep, or remove the other.`
|
|
11763
|
-
);
|
|
11764
|
-
}
|
|
11765
|
-
}
|
|
11766
|
-
function refuseNamedConfigDir() {
|
|
11767
|
-
if (ownsClaudeCode()) return false;
|
|
11768
|
-
console.error(
|
|
11769
|
-
`This config dir does not own this machine's Claude Code, so nothing was changed.
|
|
11770
|
-
Config dir: ${getConfigDir()}
|
|
11771
|
-
There is one Claude Code per machine and the default dir owns it. Run this
|
|
11772
|
-
without JARVIS_CONFIG_DIR set.`
|
|
11773
|
-
);
|
|
11774
|
-
return true;
|
|
11775
|
-
}
|
|
11776
|
-
function register20(program) {
|
|
11777
|
-
const hooks = program.command("hooks").description("Wire this machine's Claude Code to jarvis \u2014 no pairing, no daemon");
|
|
11778
|
-
hooks.command("install").description("Install the Claude Code hooks (the session block and the rules on edit)").action(() => {
|
|
11779
|
-
if (refuseNamedConfigDir()) process.exit(1);
|
|
11780
|
-
quietInstallerNarration();
|
|
11781
|
-
installClaudeCodeHooks();
|
|
11782
|
-
const state2 = claudeCodeHooksState();
|
|
11783
|
-
const installed = state2.filter(isOurs).length;
|
|
11784
|
-
if (installed === 0) {
|
|
11785
|
-
console.error("No hooks were installed.");
|
|
11786
|
-
report(state2);
|
|
11787
|
-
console.error(
|
|
11788
|
-
`
|
|
11789
|
-
Every shim above is missing, which means this is not a complete install.
|
|
11790
|
-
Reinstall the CLI: npm i -g @appchy/jarvis`
|
|
11791
|
-
);
|
|
11792
|
-
process.exit(1);
|
|
11793
|
-
}
|
|
11794
|
-
console.log(`Claude Code hooks installed (${installed} of ${state2.length}):`);
|
|
11795
|
-
report(state2);
|
|
11796
|
-
console.log(`
|
|
11797
|
-
Open Claude Code in a repo that has a work/ tree to see them.`);
|
|
11798
|
-
});
|
|
11799
|
-
hooks.command("status").description("What Claude Code will actually run").action(() => {
|
|
11800
|
-
const state2 = claudeCodeHooksState();
|
|
11801
|
-
const installed = state2.filter(isOurs).length;
|
|
11802
|
-
console.log(
|
|
11803
|
-
installed === state2.length ? `All ${state2.length} Claude Code hooks are installed:` : `${installed} of ${state2.length} Claude Code hooks are installed:`
|
|
11804
|
-
);
|
|
11805
|
-
report(state2);
|
|
11806
|
-
if (!ownsClaudeCode()) {
|
|
11807
|
-
console.log(
|
|
11808
|
-
`
|
|
11809
|
-
Note: this config dir (${getConfigDir()}) does not own this machine's
|
|
11810
|
-
Claude Code, so it neither installs these nor removes them.`
|
|
11811
|
-
);
|
|
11812
|
-
}
|
|
11813
|
-
});
|
|
11814
|
-
hooks.command("uninstall").description("Remove the Claude Code hooks this machine installed").action(() => {
|
|
11815
|
-
if (refuseNamedConfigDir()) process.exit(1);
|
|
11816
|
-
quietInstallerNarration();
|
|
11817
|
-
uninstallClaudeCodeHooks();
|
|
11818
|
-
const left = claudeCodeHooksState().filter(isOurs);
|
|
11819
|
-
if (left.length === 0) {
|
|
11820
|
-
console.log("Claude Code hooks removed.");
|
|
11821
|
-
return;
|
|
11822
|
-
}
|
|
11823
|
-
console.error(`${left.length} hook(s) could not be removed:`);
|
|
11824
|
-
report(left);
|
|
11825
|
-
process.exit(1);
|
|
11826
|
-
});
|
|
11827
|
-
}
|
|
11828
|
-
|
|
11829
11891
|
// src/commands/work.ts
|
|
11830
11892
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
11831
11893
|
function register21(program) {
|
|
@@ -11851,11 +11913,10 @@ function register21(program) {
|
|
|
11851
11913
|
// src/cli.ts
|
|
11852
11914
|
function createCli() {
|
|
11853
11915
|
const program = new Command().enablePositionalOptions().name("jarvis").description("Jarvis local agent \u2014 watches Claude Code sessions on your machine").version(BUILD_LABEL);
|
|
11854
|
-
register2(program);
|
|
11855
11916
|
register3(program);
|
|
11856
11917
|
register4(program);
|
|
11857
|
-
register(program);
|
|
11858
11918
|
register5(program);
|
|
11919
|
+
register(program);
|
|
11859
11920
|
register6(program);
|
|
11860
11921
|
register7(program);
|
|
11861
11922
|
register8(program);
|
|
@@ -11871,6 +11932,7 @@ function createCli() {
|
|
|
11871
11932
|
register18(program);
|
|
11872
11933
|
register19(program);
|
|
11873
11934
|
register20(program);
|
|
11935
|
+
register2(program);
|
|
11874
11936
|
register21(program);
|
|
11875
11937
|
return program;
|
|
11876
11938
|
}
|