@appchy/jarvis 0.1.15 → 0.1.17
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 +388 -210
- package/dist/bin.js.map +1 -1
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
// src/bin.ts
|
|
2
2
|
import dotenv from "dotenv";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import fs11 from "fs";
|
|
4
|
+
import path11 from "path";
|
|
5
5
|
|
|
6
6
|
// src/cli.ts
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
import { spawn as spawn3, execSync as execSync2 } from "child_process";
|
|
9
|
-
import
|
|
10
|
-
import
|
|
9
|
+
import fs10 from "fs";
|
|
10
|
+
import path10 from "path";
|
|
11
11
|
import os5 from "os";
|
|
12
12
|
|
|
13
13
|
// src/config.ts
|
|
14
14
|
import fs from "fs";
|
|
15
15
|
import path from "path";
|
|
16
16
|
import os from "os";
|
|
17
|
-
|
|
17
|
+
function resolveConfigDir() {
|
|
18
|
+
if (process.env.JARVIS_CONFIG_DIR) return process.env.JARVIS_CONFIG_DIR;
|
|
19
|
+
const base = path.join(os.homedir(), ".jarvis");
|
|
20
|
+
const appUrl = process.env.APP_URL ?? "";
|
|
21
|
+
if (appUrl && !appUrl.includes("appchy.com") && appUrl !== "") {
|
|
22
|
+
return path.join(base, "dev");
|
|
23
|
+
}
|
|
24
|
+
return base;
|
|
25
|
+
}
|
|
26
|
+
var CONFIG_DIR = resolveConfigDir();
|
|
18
27
|
var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
19
28
|
function loadConfig() {
|
|
20
29
|
try {
|
|
@@ -53,30 +62,6 @@ function getConfigPath() {
|
|
|
53
62
|
return CONFIG_FILE;
|
|
54
63
|
}
|
|
55
64
|
|
|
56
|
-
// ../../providers/anthropic/src/anthropic.provider.ts
|
|
57
|
-
import Anthropic from "@anthropic-ai/sdk";
|
|
58
|
-
|
|
59
|
-
// ../../providers/anthropic/src/anthropic.models.ts
|
|
60
|
-
var claudeCodeSonnet = {
|
|
61
|
-
id: "claude-sonnet-4-5-20250929",
|
|
62
|
-
name: "Claude Code (Sonnet)",
|
|
63
|
-
provider: "claude-code",
|
|
64
|
-
maxTokens: { input: 2e5, output: 16384 },
|
|
65
|
-
cost: { multiplier: 20, inputTokens: 3, outputTokens: 15 }
|
|
66
|
-
};
|
|
67
|
-
var claudeCodeOpus = {
|
|
68
|
-
id: "claude-opus-4-6-20250515",
|
|
69
|
-
name: "Claude Code (Opus)",
|
|
70
|
-
provider: "claude-code",
|
|
71
|
-
maxTokens: { input: 2e5, output: 16384 },
|
|
72
|
-
cost: { multiplier: 50, inputTokens: 5, outputTokens: 25 }
|
|
73
|
-
};
|
|
74
|
-
var claudeCodeModels = {
|
|
75
|
-
claudeCodeSonnet,
|
|
76
|
-
claudeCodeOpus,
|
|
77
|
-
list: () => [claudeCodeSonnet, claudeCodeOpus]
|
|
78
|
-
};
|
|
79
|
-
|
|
80
65
|
// ../../packages/logger/src/logger.ts
|
|
81
66
|
function resolveError(meta) {
|
|
82
67
|
if (!meta) return {};
|
|
@@ -168,6 +153,30 @@ var logger = {
|
|
|
168
153
|
}
|
|
169
154
|
};
|
|
170
155
|
|
|
156
|
+
// ../../providers/anthropic/src/anthropic.provider.ts
|
|
157
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
158
|
+
|
|
159
|
+
// ../../providers/anthropic/src/anthropic.models.ts
|
|
160
|
+
var claudeCodeSonnet = {
|
|
161
|
+
id: "claude-sonnet-4-5-20250929",
|
|
162
|
+
name: "Claude Code (Sonnet)",
|
|
163
|
+
provider: "claude-code",
|
|
164
|
+
maxTokens: { input: 2e5, output: 16384 },
|
|
165
|
+
cost: { multiplier: 20, inputTokens: 3, outputTokens: 15 }
|
|
166
|
+
};
|
|
167
|
+
var claudeCodeOpus = {
|
|
168
|
+
id: "claude-opus-4-6-20250515",
|
|
169
|
+
name: "Claude Code (Opus)",
|
|
170
|
+
provider: "claude-code",
|
|
171
|
+
maxTokens: { input: 2e5, output: 16384 },
|
|
172
|
+
cost: { multiplier: 50, inputTokens: 5, outputTokens: 25 }
|
|
173
|
+
};
|
|
174
|
+
var claudeCodeModels = {
|
|
175
|
+
claudeCodeSonnet,
|
|
176
|
+
claudeCodeOpus,
|
|
177
|
+
list: () => [claudeCodeSonnet, claudeCodeOpus]
|
|
178
|
+
};
|
|
179
|
+
|
|
171
180
|
// ../../packages/types/src/agents/llm.types.ts
|
|
172
181
|
function hasOutputFormat(output, format) {
|
|
173
182
|
return output?.formats?.includes(format) ?? false;
|
|
@@ -2235,6 +2244,81 @@ function createWsProgressHandlers(deps) {
|
|
|
2235
2244
|
return { onMessage, completeTools, waitForInput, handleUserInput, handleProgress };
|
|
2236
2245
|
}
|
|
2237
2246
|
|
|
2247
|
+
// src/attachments.ts
|
|
2248
|
+
import fs3 from "fs/promises";
|
|
2249
|
+
import path3 from "path";
|
|
2250
|
+
var TEXT_MIME_PREFIXES = ["text/", "application/json", "application/xml", "application/javascript"];
|
|
2251
|
+
var IMAGE_MIME_PREFIXES = ["image/"];
|
|
2252
|
+
function isTextFile(mimeType) {
|
|
2253
|
+
return TEXT_MIME_PREFIXES.some((p) => mimeType.startsWith(p));
|
|
2254
|
+
}
|
|
2255
|
+
function isImageFile(mimeType) {
|
|
2256
|
+
return IMAGE_MIME_PREFIXES.some((p) => mimeType.startsWith(p));
|
|
2257
|
+
}
|
|
2258
|
+
function resolveUrl(url, baseUrl) {
|
|
2259
|
+
if (url.startsWith("http://") || url.startsWith("https://")) return url;
|
|
2260
|
+
const origin = baseUrl || process.env.APP_URL || "http://localhost:3000";
|
|
2261
|
+
return `${origin}${url}`;
|
|
2262
|
+
}
|
|
2263
|
+
async function resolveAttachments(attachments, workspacePath, baseUrl) {
|
|
2264
|
+
if (!attachments.length) return "";
|
|
2265
|
+
const parts = [];
|
|
2266
|
+
const uploadDir = path3.join(workspacePath, ".jarvis-uploads");
|
|
2267
|
+
for (const att of attachments) {
|
|
2268
|
+
try {
|
|
2269
|
+
const fullUrl = resolveUrl(att.url, baseUrl);
|
|
2270
|
+
if (isTextFile(att.mimeType)) {
|
|
2271
|
+
const res = await fetch(fullUrl);
|
|
2272
|
+
if (!res.ok) {
|
|
2273
|
+
logger.sys.warn("[Attachments] Failed to fetch text file", {
|
|
2274
|
+
fileName: att.fileName,
|
|
2275
|
+
status: res.status
|
|
2276
|
+
});
|
|
2277
|
+
continue;
|
|
2278
|
+
}
|
|
2279
|
+
const text = await res.text();
|
|
2280
|
+
parts.push(`<file name="${att.fileName}">
|
|
2281
|
+
${text}
|
|
2282
|
+
</file>`);
|
|
2283
|
+
} else if (isImageFile(att.mimeType)) {
|
|
2284
|
+
await fs3.mkdir(uploadDir, { recursive: true });
|
|
2285
|
+
const localPath = path3.join(uploadDir, att.fileName);
|
|
2286
|
+
const res = await fetch(fullUrl);
|
|
2287
|
+
if (!res.ok) {
|
|
2288
|
+
logger.sys.warn("[Attachments] Failed to fetch image", {
|
|
2289
|
+
fileName: att.fileName,
|
|
2290
|
+
status: res.status
|
|
2291
|
+
});
|
|
2292
|
+
continue;
|
|
2293
|
+
}
|
|
2294
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
2295
|
+
await fs3.writeFile(localPath, buffer);
|
|
2296
|
+
parts.push(`[Image attached: ${localPath}]`);
|
|
2297
|
+
} else {
|
|
2298
|
+
await fs3.mkdir(uploadDir, { recursive: true });
|
|
2299
|
+
const localPath = path3.join(uploadDir, att.fileName);
|
|
2300
|
+
const res = await fetch(fullUrl);
|
|
2301
|
+
if (!res.ok) {
|
|
2302
|
+
logger.sys.warn("[Attachments] Failed to fetch file", {
|
|
2303
|
+
fileName: att.fileName,
|
|
2304
|
+
status: res.status
|
|
2305
|
+
});
|
|
2306
|
+
continue;
|
|
2307
|
+
}
|
|
2308
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
2309
|
+
await fs3.writeFile(localPath, buffer);
|
|
2310
|
+
parts.push(`[File attached: ${localPath}]`);
|
|
2311
|
+
}
|
|
2312
|
+
} catch (err) {
|
|
2313
|
+
logger.sys.warn("[Attachments] Error processing attachment", {
|
|
2314
|
+
fileName: att.fileName,
|
|
2315
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2316
|
+
});
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
return parts.length ? parts.join("\n\n") : "";
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2238
2322
|
// src/agent.ts
|
|
2239
2323
|
function createAgent(deps) {
|
|
2240
2324
|
const activeTasks = /* @__PURE__ */ new Map();
|
|
@@ -2253,6 +2337,27 @@ function createAgent(deps) {
|
|
|
2253
2337
|
const wsPath = msg.worktreePath || deps.workspacePath;
|
|
2254
2338
|
const systemMsg = msg.messages.find((m) => m.role === "system");
|
|
2255
2339
|
const nonSystemMessages = msg.messages.filter((m) => m.role !== "system");
|
|
2340
|
+
if (msg.attachments?.length) {
|
|
2341
|
+
const attachmentContext = await resolveAttachments(msg.attachments, wsPath);
|
|
2342
|
+
if (attachmentContext) {
|
|
2343
|
+
let lastUserIdx = -1;
|
|
2344
|
+
for (let i = nonSystemMessages.length - 1; i >= 0; i--) {
|
|
2345
|
+
if (nonSystemMessages[i].role === "user") {
|
|
2346
|
+
lastUserIdx = i;
|
|
2347
|
+
break;
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
if (lastUserIdx >= 0) {
|
|
2351
|
+
const orig = nonSystemMessages[lastUserIdx];
|
|
2352
|
+
nonSystemMessages[lastUserIdx] = {
|
|
2353
|
+
...orig,
|
|
2354
|
+
content: `${attachmentContext}
|
|
2355
|
+
|
|
2356
|
+
${orig.content}`
|
|
2357
|
+
};
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2256
2361
|
logger.sys.info("[Agent] Running task", {
|
|
2257
2362
|
taskId,
|
|
2258
2363
|
model: msg.model,
|
|
@@ -2328,11 +2433,17 @@ function createAgent(deps) {
|
|
|
2328
2433
|
});
|
|
2329
2434
|
} catch (err) {
|
|
2330
2435
|
if (abortController.signal.aborted) {
|
|
2331
|
-
logger.sys.info("[Agent] Task
|
|
2436
|
+
logger.sys.info("[Agent] Task interrupted", { taskId });
|
|
2332
2437
|
deps.broadcast({
|
|
2333
2438
|
type: "agent:output",
|
|
2334
2439
|
taskId,
|
|
2335
|
-
output: {
|
|
2440
|
+
output: {
|
|
2441
|
+
success: false,
|
|
2442
|
+
content: "",
|
|
2443
|
+
error: "Interrupted",
|
|
2444
|
+
interrupted: true,
|
|
2445
|
+
sessionId: msg.session?.resume
|
|
2446
|
+
}
|
|
2336
2447
|
});
|
|
2337
2448
|
} else {
|
|
2338
2449
|
const error = err instanceof Error ? err.message : String(err);
|
|
@@ -2364,12 +2475,92 @@ function createAgent(deps) {
|
|
|
2364
2475
|
return { runTask, cancelTask, isRunning, activeTaskCount };
|
|
2365
2476
|
}
|
|
2366
2477
|
|
|
2478
|
+
// src/files.ts
|
|
2479
|
+
import { execFile } from "child_process";
|
|
2480
|
+
import { promisify as promisify2 } from "util";
|
|
2481
|
+
import path4 from "path";
|
|
2482
|
+
import fs4 from "fs/promises";
|
|
2483
|
+
var execFileAsync = promisify2(execFile);
|
|
2484
|
+
var MAX_RESULTS = 50;
|
|
2485
|
+
async function searchFiles(workspacePath, query) {
|
|
2486
|
+
try {
|
|
2487
|
+
const { stdout } = await execFileAsync("git", ["ls-files", "--cached", "--others", "--exclude-standard"], {
|
|
2488
|
+
cwd: workspacePath,
|
|
2489
|
+
maxBuffer: 1024 * 1024
|
|
2490
|
+
});
|
|
2491
|
+
const allFiles = stdout.split("\n").filter(Boolean);
|
|
2492
|
+
const dirSet = /* @__PURE__ */ new Set();
|
|
2493
|
+
for (const f of allFiles) {
|
|
2494
|
+
const parts = f.split("/");
|
|
2495
|
+
for (let i = 1; i < parts.length; i++) {
|
|
2496
|
+
dirSet.add(parts.slice(0, i).join("/"));
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
const allEntries = [
|
|
2500
|
+
...[...dirSet].map((d) => ({ path: d, name: path4.basename(d), type: "directory" })),
|
|
2501
|
+
...allFiles.map((f) => ({ path: f, name: path4.basename(f), type: "file" }))
|
|
2502
|
+
];
|
|
2503
|
+
if (!query.trim()) {
|
|
2504
|
+
const sorted = [...allEntries].sort((a, b) => {
|
|
2505
|
+
const depthA = a.path.split("/").length;
|
|
2506
|
+
const depthB = b.path.split("/").length;
|
|
2507
|
+
if (depthA !== depthB) return depthA - depthB;
|
|
2508
|
+
if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
|
|
2509
|
+
return a.path.localeCompare(b.path);
|
|
2510
|
+
});
|
|
2511
|
+
return sorted.slice(0, MAX_RESULTS);
|
|
2512
|
+
}
|
|
2513
|
+
const lowerQuery = query.toLowerCase();
|
|
2514
|
+
const matched = allEntries.filter((e) => e.path.toLowerCase().includes(lowerQuery)).sort((a, b) => {
|
|
2515
|
+
const aExact = a.name.toLowerCase() === lowerQuery ? 0 : 1;
|
|
2516
|
+
const bExact = b.name.toLowerCase() === lowerQuery ? 0 : 1;
|
|
2517
|
+
if (aExact !== bExact) return aExact - bExact;
|
|
2518
|
+
if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
|
|
2519
|
+
return a.path.localeCompare(b.path);
|
|
2520
|
+
}).slice(0, MAX_RESULTS);
|
|
2521
|
+
return matched;
|
|
2522
|
+
} catch (err) {
|
|
2523
|
+
logger.sys.warn("[Files] git ls-files failed, falling back to fs scan", {
|
|
2524
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2525
|
+
});
|
|
2526
|
+
return fallbackSearch(workspacePath, query);
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
async function fallbackSearch(workspacePath, query) {
|
|
2530
|
+
const results = [];
|
|
2531
|
+
const lowerQuery = query.toLowerCase();
|
|
2532
|
+
async function walk(dir, depth) {
|
|
2533
|
+
if (depth > 4 || results.length >= MAX_RESULTS) return;
|
|
2534
|
+
try {
|
|
2535
|
+
const entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
2536
|
+
for (const entry of entries) {
|
|
2537
|
+
if (results.length >= MAX_RESULTS) break;
|
|
2538
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
2539
|
+
const relPath = path4.relative(workspacePath, path4.join(dir, entry.name));
|
|
2540
|
+
if (relPath.toLowerCase().includes(lowerQuery)) {
|
|
2541
|
+
results.push({
|
|
2542
|
+
path: relPath,
|
|
2543
|
+
name: entry.name,
|
|
2544
|
+
type: entry.isDirectory() ? "directory" : "file"
|
|
2545
|
+
});
|
|
2546
|
+
}
|
|
2547
|
+
if (entry.isDirectory()) {
|
|
2548
|
+
await walk(path4.join(dir, entry.name), depth + 1);
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
} catch {
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
await walk(workspacePath, 0);
|
|
2555
|
+
return results;
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2367
2558
|
// src/git.ts
|
|
2368
2559
|
import { exec as execCb2 } from "child_process";
|
|
2369
|
-
import
|
|
2370
|
-
import
|
|
2371
|
-
import { promisify as
|
|
2372
|
-
var exec2 =
|
|
2560
|
+
import fs5 from "fs/promises";
|
|
2561
|
+
import path5 from "path";
|
|
2562
|
+
import { promisify as promisify3 } from "util";
|
|
2563
|
+
var exec2 = promisify3(execCb2);
|
|
2373
2564
|
function createGitHandler(workspacePath, broadcast) {
|
|
2374
2565
|
async function handle(msg) {
|
|
2375
2566
|
switch (msg.type) {
|
|
@@ -2394,11 +2585,11 @@ function createGitHandler(workspacePath, broadcast) {
|
|
|
2394
2585
|
}
|
|
2395
2586
|
async function handleResolveRepo(msg) {
|
|
2396
2587
|
try {
|
|
2397
|
-
const repoDir =
|
|
2398
|
-
const isCloned = await
|
|
2588
|
+
const repoDir = path5.join(workspacePath, msg.owner, msg.name);
|
|
2589
|
+
const isCloned = await fs5.access(path5.join(repoDir, ".git")).then(() => true).catch(() => false);
|
|
2399
2590
|
if (!isCloned) {
|
|
2400
2591
|
logger.sys.info("[Git] Cloning repo...", { repoDir });
|
|
2401
|
-
await
|
|
2592
|
+
await fs5.mkdir(path5.join(workspacePath, msg.owner), { recursive: true });
|
|
2402
2593
|
const cloneUrl = `git@github.com:${msg.owner}/${msg.name}.git`;
|
|
2403
2594
|
await exec2(`git clone "${cloneUrl}" "${repoDir}"`, { timeout: 3e5 });
|
|
2404
2595
|
}
|
|
@@ -2561,7 +2752,7 @@ function createUpstreamClient(config) {
|
|
|
2561
2752
|
if (!config.refreshToken) return null;
|
|
2562
2753
|
return config.apiUrl.replace(/^wss:/, "https:").replace(/^ws:/, "http:") + "/refresh";
|
|
2563
2754
|
}
|
|
2564
|
-
function
|
|
2755
|
+
function getTokenExpiry(token) {
|
|
2565
2756
|
try {
|
|
2566
2757
|
const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64").toString());
|
|
2567
2758
|
return payload.exp ?? null;
|
|
@@ -2572,7 +2763,7 @@ function createUpstreamClient(config) {
|
|
|
2572
2763
|
function scheduleRefresh() {
|
|
2573
2764
|
if (refreshTimer) clearTimeout(refreshTimer);
|
|
2574
2765
|
if (!config.refreshToken) return;
|
|
2575
|
-
const exp =
|
|
2766
|
+
const exp = getTokenExpiry(currentToken);
|
|
2576
2767
|
if (!exp) return;
|
|
2577
2768
|
const msUntilExpiry = exp * 1e3 - Date.now();
|
|
2578
2769
|
const refreshIn = Math.max(msUntilExpiry - REFRESH_BUFFER_MS, 0);
|
|
@@ -2759,6 +2950,18 @@ async function startAgent(options) {
|
|
|
2759
2950
|
}
|
|
2760
2951
|
break;
|
|
2761
2952
|
}
|
|
2953
|
+
case "files:search": {
|
|
2954
|
+
const filesMsg = msg;
|
|
2955
|
+
searchFiles(options.workspacePath, filesMsg.query).then((files) => {
|
|
2956
|
+
broadcast({ type: "files:response", requestId: filesMsg.requestId, files });
|
|
2957
|
+
}).catch((err) => {
|
|
2958
|
+
logger.sys.error("[Agent] File search failed", {
|
|
2959
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2960
|
+
});
|
|
2961
|
+
broadcast({ type: "files:response", requestId: filesMsg.requestId, files: [] });
|
|
2962
|
+
});
|
|
2963
|
+
break;
|
|
2964
|
+
}
|
|
2762
2965
|
case "ping":
|
|
2763
2966
|
break;
|
|
2764
2967
|
}
|
|
@@ -2792,14 +2995,14 @@ import React6 from "react";
|
|
|
2792
2995
|
import { render as render2 } from "ink";
|
|
2793
2996
|
import { spawn } from "child_process";
|
|
2794
2997
|
import { fileURLToPath } from "url";
|
|
2795
|
-
import
|
|
2796
|
-
import
|
|
2998
|
+
import path8 from "path";
|
|
2999
|
+
import fs8 from "fs";
|
|
2797
3000
|
import os3 from "os";
|
|
2798
3001
|
import WebSocket4 from "ws";
|
|
2799
3002
|
|
|
2800
3003
|
// src/tui/settings.ts
|
|
2801
|
-
import
|
|
2802
|
-
import
|
|
3004
|
+
import fs6 from "fs";
|
|
3005
|
+
import path6 from "path";
|
|
2803
3006
|
import os2 from "os";
|
|
2804
3007
|
var DEFAULT_SETTINGS = {
|
|
2805
3008
|
model: "claude-sonnet-4-20250514",
|
|
@@ -2809,11 +3012,11 @@ var DEFAULT_SETTINGS = {
|
|
|
2809
3012
|
theme: "dark",
|
|
2810
3013
|
disallowedTools: []
|
|
2811
3014
|
};
|
|
2812
|
-
var SETTINGS_DIR =
|
|
2813
|
-
var SETTINGS_FILE =
|
|
3015
|
+
var SETTINGS_DIR = path6.join(os2.homedir(), ".jarvis");
|
|
3016
|
+
var SETTINGS_FILE = path6.join(SETTINGS_DIR, "settings.json");
|
|
2814
3017
|
function loadSettings() {
|
|
2815
3018
|
try {
|
|
2816
|
-
const raw =
|
|
3019
|
+
const raw = fs6.readFileSync(SETTINGS_FILE, "utf-8");
|
|
2817
3020
|
const parsed = JSON.parse(raw);
|
|
2818
3021
|
return { ...DEFAULT_SETTINGS, ...parsed };
|
|
2819
3022
|
} catch {
|
|
@@ -2821,8 +3024,8 @@ function loadSettings() {
|
|
|
2821
3024
|
}
|
|
2822
3025
|
}
|
|
2823
3026
|
function saveSettings(settings) {
|
|
2824
|
-
|
|
2825
|
-
|
|
3027
|
+
fs6.mkdirSync(SETTINGS_DIR, { recursive: true });
|
|
3028
|
+
fs6.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2) + "\n");
|
|
2826
3029
|
}
|
|
2827
3030
|
function mergeCliFlags(settings, flags) {
|
|
2828
3031
|
const merged = { ...settings };
|
|
@@ -3532,8 +3735,8 @@ function Suggestions({
|
|
|
3532
3735
|
|
|
3533
3736
|
// src/tui/components/file-picker.tsx
|
|
3534
3737
|
import { Box as Box6, Text as Text6 } from "ink";
|
|
3535
|
-
import
|
|
3536
|
-
import
|
|
3738
|
+
import path7 from "path";
|
|
3739
|
+
import fs7 from "fs";
|
|
3537
3740
|
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
3538
3741
|
var cachedFiles = null;
|
|
3539
3742
|
function scanFiles(workspacePath) {
|
|
@@ -3554,11 +3757,11 @@ function scanFiles(workspacePath) {
|
|
|
3554
3757
|
function walk(dir, depth) {
|
|
3555
3758
|
if (depth > 4) return;
|
|
3556
3759
|
try {
|
|
3557
|
-
const entries =
|
|
3760
|
+
const entries = fs7.readdirSync(dir, { withFileTypes: true });
|
|
3558
3761
|
for (const entry of entries) {
|
|
3559
3762
|
if (ignoreSet.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
3560
|
-
const full =
|
|
3561
|
-
const rel =
|
|
3763
|
+
const full = path7.join(dir, entry.name);
|
|
3764
|
+
const rel = path7.relative(workspacePath, full);
|
|
3562
3765
|
if (entry.isDirectory()) {
|
|
3563
3766
|
results.push(rel + "/");
|
|
3564
3767
|
walk(full, depth + 1);
|
|
@@ -4747,13 +4950,13 @@ async function launchChat(opts) {
|
|
|
4747
4950
|
}
|
|
4748
4951
|
function spawnAgentProcess(port, workspacePath) {
|
|
4749
4952
|
const config = loadConfig();
|
|
4750
|
-
const logDir =
|
|
4751
|
-
|
|
4752
|
-
const logFile =
|
|
4753
|
-
const logFd =
|
|
4953
|
+
const logDir = path8.join(os3.homedir(), ".jarvis");
|
|
4954
|
+
fs8.mkdirSync(logDir, { recursive: true });
|
|
4955
|
+
const logFile = path8.join(logDir, "agent.log");
|
|
4956
|
+
const logFd = fs8.openSync(logFile, "a");
|
|
4754
4957
|
const __filename = fileURLToPath(import.meta.url);
|
|
4755
|
-
const cliRoot =
|
|
4756
|
-
const binPath =
|
|
4958
|
+
const cliRoot = path8.resolve(path8.dirname(__filename), "../..");
|
|
4959
|
+
const binPath = path8.join(cliRoot, "bin", "jarvis.mjs");
|
|
4757
4960
|
const args = ["start", "--port", String(port), "--workspace", workspacePath];
|
|
4758
4961
|
if (config?.apiUrl && config?.token) {
|
|
4759
4962
|
} else {
|
|
@@ -4768,7 +4971,7 @@ function spawnAgentProcess(port, workspacePath) {
|
|
|
4768
4971
|
cwd: workspacePath,
|
|
4769
4972
|
env: { ...process.env }
|
|
4770
4973
|
});
|
|
4771
|
-
|
|
4974
|
+
fs8.closeSync(logFd);
|
|
4772
4975
|
}
|
|
4773
4976
|
async function checkAgentRunning(port) {
|
|
4774
4977
|
return new Promise((resolve) => {
|
|
@@ -4805,8 +5008,8 @@ async function waitForAgent(port, maxAttempts = 30) {
|
|
|
4805
5008
|
|
|
4806
5009
|
// src/service.ts
|
|
4807
5010
|
import { execSync, spawn as spawn2 } from "child_process";
|
|
4808
|
-
import
|
|
4809
|
-
import
|
|
5011
|
+
import fs9 from "fs";
|
|
5012
|
+
import path9 from "path";
|
|
4810
5013
|
import os4 from "os";
|
|
4811
5014
|
function createServiceManager() {
|
|
4812
5015
|
if (process.platform === "darwin") return new MacOSService();
|
|
@@ -4815,8 +5018,8 @@ function createServiceManager() {
|
|
|
4815
5018
|
return new FallbackService();
|
|
4816
5019
|
}
|
|
4817
5020
|
var PLIST_LABEL = "com.appchy.jarvis";
|
|
4818
|
-
var PLIST_DIR =
|
|
4819
|
-
var PLIST_PATH =
|
|
5021
|
+
var PLIST_DIR = path9.join(os4.homedir(), "Library", "LaunchAgents");
|
|
5022
|
+
var PLIST_PATH = path9.join(PLIST_DIR, `${PLIST_LABEL}.plist`);
|
|
4820
5023
|
function escapeXml(s) {
|
|
4821
5024
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
4822
5025
|
}
|
|
@@ -4835,7 +5038,7 @@ var MacOSService = class {
|
|
|
4835
5038
|
if (!opts.upstream) {
|
|
4836
5039
|
args.push("--no-upstream");
|
|
4837
5040
|
}
|
|
4838
|
-
const logFile =
|
|
5041
|
+
const logFile = path9.join(os4.homedir(), ".jarvis", "agent.log");
|
|
4839
5042
|
const envPath = process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin";
|
|
4840
5043
|
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
4841
5044
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -4860,6 +5063,8 @@ ${args.map((a) => ` <string>${escapeXml(a)}</string>`).join("\n")}
|
|
|
4860
5063
|
<string>${escapeXml(os4.homedir())}</string>
|
|
4861
5064
|
<key>NODE_NO_WARNINGS</key>
|
|
4862
5065
|
<string>1</string>
|
|
5066
|
+
${Object.entries(opts.env ?? {}).map(([k, v]) => ` <key>${escapeXml(k)}</key>
|
|
5067
|
+
<string>${escapeXml(v)}</string>`).join("\n")}
|
|
4863
5068
|
</dict>
|
|
4864
5069
|
|
|
4865
5070
|
<key>RunAtLoad</key>
|
|
@@ -4882,15 +5087,15 @@ ${args.map((a) => ` <string>${escapeXml(a)}</string>`).join("\n")}
|
|
|
4882
5087
|
</dict>
|
|
4883
5088
|
</plist>
|
|
4884
5089
|
`;
|
|
4885
|
-
|
|
4886
|
-
|
|
5090
|
+
fs9.mkdirSync(PLIST_DIR, { recursive: true });
|
|
5091
|
+
fs9.mkdirSync(path9.dirname(logFile), { recursive: true });
|
|
4887
5092
|
if (this.isInstalled()) {
|
|
4888
5093
|
try {
|
|
4889
5094
|
execSync(`launchctl unload -w "${PLIST_PATH}"`, { stdio: "ignore" });
|
|
4890
5095
|
} catch {
|
|
4891
5096
|
}
|
|
4892
5097
|
}
|
|
4893
|
-
|
|
5098
|
+
fs9.writeFileSync(PLIST_PATH, plist);
|
|
4894
5099
|
execSync(`launchctl load -w "${PLIST_PATH}"`);
|
|
4895
5100
|
}
|
|
4896
5101
|
uninstall() {
|
|
@@ -4899,12 +5104,12 @@ ${args.map((a) => ` <string>${escapeXml(a)}</string>`).join("\n")}
|
|
|
4899
5104
|
} catch {
|
|
4900
5105
|
}
|
|
4901
5106
|
try {
|
|
4902
|
-
|
|
5107
|
+
fs9.unlinkSync(PLIST_PATH);
|
|
4903
5108
|
} catch {
|
|
4904
5109
|
}
|
|
4905
5110
|
}
|
|
4906
5111
|
isInstalled() {
|
|
4907
|
-
return
|
|
5112
|
+
return fs9.existsSync(PLIST_PATH);
|
|
4908
5113
|
}
|
|
4909
5114
|
start() {
|
|
4910
5115
|
try {
|
|
@@ -4994,15 +5199,15 @@ var WindowsService = class {
|
|
|
4994
5199
|
</Task>
|
|
4995
5200
|
`;
|
|
4996
5201
|
const tmpDir = os4.tmpdir();
|
|
4997
|
-
const tmpFile =
|
|
4998
|
-
|
|
5202
|
+
const tmpFile = path9.join(tmpDir, `jarvis-task-${Date.now()}.xml`);
|
|
5203
|
+
fs9.writeFileSync(tmpFile, xml, { encoding: "utf-16le" });
|
|
4999
5204
|
try {
|
|
5000
5205
|
execSync(`schtasks /Create /TN "${TASK_NAME}" /XML "${tmpFile}" /F`, {
|
|
5001
5206
|
stdio: "ignore"
|
|
5002
5207
|
});
|
|
5003
5208
|
} finally {
|
|
5004
5209
|
try {
|
|
5005
|
-
|
|
5210
|
+
fs9.unlinkSync(tmpFile);
|
|
5006
5211
|
} catch {
|
|
5007
5212
|
}
|
|
5008
5213
|
}
|
|
@@ -5040,9 +5245,9 @@ var WindowsService = class {
|
|
|
5040
5245
|
}
|
|
5041
5246
|
}
|
|
5042
5247
|
};
|
|
5043
|
-
var SYSTEMD_DIR =
|
|
5248
|
+
var SYSTEMD_DIR = path9.join(os4.homedir(), ".config", "systemd", "user");
|
|
5044
5249
|
var UNIT_NAME = "jarvis.service";
|
|
5045
|
-
var UNIT_PATH =
|
|
5250
|
+
var UNIT_PATH = path9.join(SYSTEMD_DIR, UNIT_NAME);
|
|
5046
5251
|
var LinuxService = class {
|
|
5047
5252
|
install(opts) {
|
|
5048
5253
|
const args = [
|
|
@@ -5057,7 +5262,7 @@ var LinuxService = class {
|
|
|
5057
5262
|
if (!opts.upstream) {
|
|
5058
5263
|
args.push("--no-upstream");
|
|
5059
5264
|
}
|
|
5060
|
-
const logFile =
|
|
5265
|
+
const logFile = path9.join(os4.homedir(), ".jarvis", "agent.log");
|
|
5061
5266
|
const envPath = process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin";
|
|
5062
5267
|
const unit = `[Unit]
|
|
5063
5268
|
Description=Jarvis AI Agent
|
|
@@ -5078,15 +5283,15 @@ StandardError=append:${logFile}
|
|
|
5078
5283
|
[Install]
|
|
5079
5284
|
WantedBy=default.target
|
|
5080
5285
|
`;
|
|
5081
|
-
|
|
5082
|
-
|
|
5286
|
+
fs9.mkdirSync(SYSTEMD_DIR, { recursive: true });
|
|
5287
|
+
fs9.mkdirSync(path9.dirname(logFile), { recursive: true });
|
|
5083
5288
|
if (this.isInstalled()) {
|
|
5084
5289
|
try {
|
|
5085
5290
|
execSync("systemctl --user stop jarvis.service", { stdio: "ignore" });
|
|
5086
5291
|
} catch {
|
|
5087
5292
|
}
|
|
5088
5293
|
}
|
|
5089
|
-
|
|
5294
|
+
fs9.writeFileSync(UNIT_PATH, unit);
|
|
5090
5295
|
execSync("systemctl --user daemon-reload", { stdio: "ignore" });
|
|
5091
5296
|
execSync("systemctl --user enable jarvis.service", { stdio: "ignore" });
|
|
5092
5297
|
execSync("systemctl --user start jarvis.service", { stdio: "ignore" });
|
|
@@ -5105,7 +5310,7 @@ WantedBy=default.target
|
|
|
5105
5310
|
} catch {
|
|
5106
5311
|
}
|
|
5107
5312
|
try {
|
|
5108
|
-
|
|
5313
|
+
fs9.unlinkSync(UNIT_PATH);
|
|
5109
5314
|
} catch {
|
|
5110
5315
|
}
|
|
5111
5316
|
try {
|
|
@@ -5114,7 +5319,7 @@ WantedBy=default.target
|
|
|
5114
5319
|
}
|
|
5115
5320
|
}
|
|
5116
5321
|
isInstalled() {
|
|
5117
|
-
return
|
|
5322
|
+
return fs9.existsSync(UNIT_PATH);
|
|
5118
5323
|
}
|
|
5119
5324
|
start() {
|
|
5120
5325
|
execSync("systemctl --user start jarvis.service");
|
|
@@ -5144,13 +5349,13 @@ WantedBy=default.target
|
|
|
5144
5349
|
};
|
|
5145
5350
|
var FallbackService = class {
|
|
5146
5351
|
constructor() {
|
|
5147
|
-
this.pidFile =
|
|
5148
|
-
this.logFile =
|
|
5149
|
-
this.markerFile =
|
|
5352
|
+
this.pidFile = path9.join(os4.homedir(), ".jarvis", "agent.pid");
|
|
5353
|
+
this.logFile = path9.join(os4.homedir(), ".jarvis", "agent.log");
|
|
5354
|
+
this.markerFile = path9.join(os4.homedir(), ".jarvis", "service-installed");
|
|
5150
5355
|
}
|
|
5151
5356
|
install(opts) {
|
|
5152
|
-
const jarvisDir =
|
|
5153
|
-
|
|
5357
|
+
const jarvisDir = path9.join(os4.homedir(), ".jarvis");
|
|
5358
|
+
fs9.mkdirSync(jarvisDir, { recursive: true });
|
|
5154
5359
|
this.stop();
|
|
5155
5360
|
const args = [
|
|
5156
5361
|
opts.entryPath,
|
|
@@ -5164,7 +5369,7 @@ var FallbackService = class {
|
|
|
5164
5369
|
if (!opts.upstream) {
|
|
5165
5370
|
args.push("--no-upstream");
|
|
5166
5371
|
}
|
|
5167
|
-
const logFd =
|
|
5372
|
+
const logFd = fs9.openSync(this.logFile, "a");
|
|
5168
5373
|
const child = spawn2(opts.nodePath, args, {
|
|
5169
5374
|
detached: true,
|
|
5170
5375
|
stdio: ["ignore", logFd, logFd],
|
|
@@ -5172,9 +5377,9 @@ var FallbackService = class {
|
|
|
5172
5377
|
env: { ...process.env, NODE_NO_WARNINGS: "1" }
|
|
5173
5378
|
});
|
|
5174
5379
|
child.unref();
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5380
|
+
fs9.closeSync(logFd);
|
|
5381
|
+
fs9.writeFileSync(this.pidFile, String(child.pid));
|
|
5382
|
+
fs9.writeFileSync(this.markerFile, JSON.stringify({
|
|
5178
5383
|
nodePath: opts.nodePath,
|
|
5179
5384
|
entryPath: opts.entryPath,
|
|
5180
5385
|
port: opts.port,
|
|
@@ -5190,37 +5395,37 @@ var FallbackService = class {
|
|
|
5190
5395
|
uninstall() {
|
|
5191
5396
|
this.stop();
|
|
5192
5397
|
try {
|
|
5193
|
-
|
|
5398
|
+
fs9.unlinkSync(this.markerFile);
|
|
5194
5399
|
} catch {
|
|
5195
5400
|
}
|
|
5196
5401
|
}
|
|
5197
5402
|
isInstalled() {
|
|
5198
|
-
return
|
|
5403
|
+
return fs9.existsSync(this.markerFile);
|
|
5199
5404
|
}
|
|
5200
5405
|
start() {
|
|
5201
5406
|
if (!this.isInstalled()) {
|
|
5202
5407
|
throw new Error("Service not installed. Run 'jarvis install' first.");
|
|
5203
5408
|
}
|
|
5204
|
-
const saved = JSON.parse(
|
|
5409
|
+
const saved = JSON.parse(fs9.readFileSync(this.markerFile, "utf-8"));
|
|
5205
5410
|
this.install(saved);
|
|
5206
5411
|
}
|
|
5207
5412
|
stop() {
|
|
5208
5413
|
try {
|
|
5209
|
-
const pid = parseInt(
|
|
5414
|
+
const pid = parseInt(fs9.readFileSync(this.pidFile, "utf-8").trim(), 10);
|
|
5210
5415
|
if (!isNaN(pid)) {
|
|
5211
5416
|
process.kill(pid, "SIGTERM");
|
|
5212
5417
|
}
|
|
5213
5418
|
} catch {
|
|
5214
5419
|
}
|
|
5215
5420
|
try {
|
|
5216
|
-
|
|
5421
|
+
fs9.unlinkSync(this.pidFile);
|
|
5217
5422
|
} catch {
|
|
5218
5423
|
}
|
|
5219
5424
|
}
|
|
5220
5425
|
status() {
|
|
5221
5426
|
if (!this.isInstalled()) return { installed: false, running: false };
|
|
5222
5427
|
try {
|
|
5223
|
-
const pid = parseInt(
|
|
5428
|
+
const pid = parseInt(fs9.readFileSync(this.pidFile, "utf-8").trim(), 10);
|
|
5224
5429
|
if (isNaN(pid)) return { installed: true, running: false };
|
|
5225
5430
|
process.kill(pid, 0);
|
|
5226
5431
|
return { installed: true, running: true, pid };
|
|
@@ -5234,22 +5439,18 @@ var FallbackService = class {
|
|
|
5234
5439
|
import { createRequire } from "module";
|
|
5235
5440
|
var _require = createRequire(import.meta.url);
|
|
5236
5441
|
var PKG_VERSION = _require("../package.json").version ?? "dev";
|
|
5237
|
-
var LOG_DIR =
|
|
5238
|
-
var LOG_FILE =
|
|
5239
|
-
var PID_FILE =
|
|
5240
|
-
function savePid(pid) {
|
|
5241
|
-
fs8.mkdirSync(LOG_DIR, { recursive: true });
|
|
5242
|
-
fs8.writeFileSync(PID_FILE, String(pid));
|
|
5243
|
-
}
|
|
5442
|
+
var LOG_DIR = path10.join(os5.homedir(), ".jarvis");
|
|
5443
|
+
var LOG_FILE = path10.join(LOG_DIR, "agent.log");
|
|
5444
|
+
var PID_FILE = path10.join(LOG_DIR, "agent.pid");
|
|
5244
5445
|
function readPid() {
|
|
5245
5446
|
try {
|
|
5246
|
-
const pid = parseInt(
|
|
5447
|
+
const pid = parseInt(fs10.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
5247
5448
|
if (isNaN(pid)) return null;
|
|
5248
5449
|
try {
|
|
5249
5450
|
process.kill(pid, 0);
|
|
5250
5451
|
return pid;
|
|
5251
5452
|
} catch {
|
|
5252
|
-
|
|
5453
|
+
fs10.unlinkSync(PID_FILE);
|
|
5253
5454
|
return null;
|
|
5254
5455
|
}
|
|
5255
5456
|
} catch {
|
|
@@ -5258,7 +5459,7 @@ function readPid() {
|
|
|
5258
5459
|
}
|
|
5259
5460
|
function clearPid() {
|
|
5260
5461
|
try {
|
|
5261
|
-
|
|
5462
|
+
fs10.unlinkSync(PID_FILE);
|
|
5262
5463
|
} catch {
|
|
5263
5464
|
}
|
|
5264
5465
|
}
|
|
@@ -5308,7 +5509,7 @@ async function findFreePort() {
|
|
|
5308
5509
|
async function browserAuth(appUrl) {
|
|
5309
5510
|
const config = loadConfig();
|
|
5310
5511
|
const callbackPort = await findFreePort();
|
|
5311
|
-
const loginUrl = `${appUrl}/
|
|
5512
|
+
const loginUrl = `${appUrl}/auth/cli?port=${callbackPort}`;
|
|
5312
5513
|
console.log();
|
|
5313
5514
|
console.log("Opening browser for sign in...");
|
|
5314
5515
|
console.log(` If it doesn't open, visit: ${loginUrl}`);
|
|
@@ -5339,57 +5540,17 @@ async function browserAuth(appUrl) {
|
|
|
5339
5540
|
}
|
|
5340
5541
|
}
|
|
5341
5542
|
function getAppUrl() {
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
try {
|
|
5346
|
-
const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64").toString());
|
|
5347
|
-
return payload.exp ?? null;
|
|
5348
|
-
} catch {
|
|
5349
|
-
return null;
|
|
5350
|
-
}
|
|
5351
|
-
}
|
|
5352
|
-
function isTokenExpired(token, bufferMs = 6e4) {
|
|
5353
|
-
const exp = getTokenExpiry(token);
|
|
5354
|
-
if (!exp) return true;
|
|
5355
|
-
return exp * 1e3 - Date.now() < bufferMs;
|
|
5356
|
-
}
|
|
5357
|
-
async function tryRefreshToken(config) {
|
|
5358
|
-
if (!config.apiUrl || !config.refreshToken) return null;
|
|
5359
|
-
const refreshUrl = config.apiUrl.replace(/^wss:/, "https:").replace(/^ws:/, "http:") + "/refresh";
|
|
5360
|
-
try {
|
|
5361
|
-
const res = await fetch(refreshUrl, {
|
|
5362
|
-
method: "POST",
|
|
5363
|
-
headers: { "Content-Type": "application/json" },
|
|
5364
|
-
body: JSON.stringify({ refreshToken: config.refreshToken })
|
|
5365
|
-
});
|
|
5366
|
-
if (!res.ok) return null;
|
|
5367
|
-
const { token } = await res.json();
|
|
5368
|
-
return token;
|
|
5369
|
-
} catch {
|
|
5370
|
-
return null;
|
|
5371
|
-
}
|
|
5543
|
+
if ("https://jarvis.appchy.com") return "https://jarvis.appchy.com";
|
|
5544
|
+
if (process.env.APP_URL) return process.env.APP_URL;
|
|
5545
|
+
return "https://jarvis.appchy.com";
|
|
5372
5546
|
}
|
|
5373
5547
|
async function ensureSetup() {
|
|
5374
5548
|
const config = loadConfig();
|
|
5375
|
-
if (!config?.token && !config?.anthropicApiKey && !config?.useSubscription) {
|
|
5376
|
-
return browserAuth(getAppUrl());
|
|
5377
|
-
}
|
|
5378
5549
|
if (config?.anthropicApiKey || config?.useSubscription) {
|
|
5379
5550
|
return true;
|
|
5380
5551
|
}
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
const newToken = await tryRefreshToken(config);
|
|
5384
|
-
if (newToken) {
|
|
5385
|
-
saveConfig({ ...config, token: newToken, connectedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
5386
|
-
console.log("Token refreshed successfully.");
|
|
5387
|
-
return true;
|
|
5388
|
-
}
|
|
5389
|
-
console.log("Token refresh failed. Re-authenticating...");
|
|
5390
|
-
return browserAuth(getAppUrl());
|
|
5391
|
-
}
|
|
5392
|
-
return true;
|
|
5552
|
+
clearConfig();
|
|
5553
|
+
return browserAuth(getAppUrl());
|
|
5393
5554
|
}
|
|
5394
5555
|
function createCli() {
|
|
5395
5556
|
const program = new Command().name("jarvis").description("Jarvis local agent \u2014 runs Claude Code on your machine").version(PKG_VERSION);
|
|
@@ -5459,13 +5620,9 @@ function createCli() {
|
|
|
5459
5620
|
const service = createServiceManager();
|
|
5460
5621
|
if (service.isInstalled()) {
|
|
5461
5622
|
try {
|
|
5462
|
-
service.
|
|
5623
|
+
service.uninstall();
|
|
5463
5624
|
} catch {
|
|
5464
5625
|
}
|
|
5465
|
-
await new Promise((r) => setTimeout(r, 1e3));
|
|
5466
|
-
service.start();
|
|
5467
|
-
console.log("Jarvis agent started via OS service.");
|
|
5468
|
-
return;
|
|
5469
5626
|
}
|
|
5470
5627
|
const stalePid = readPid();
|
|
5471
5628
|
if (stalePid) {
|
|
@@ -5475,13 +5632,13 @@ function createCli() {
|
|
|
5475
5632
|
}
|
|
5476
5633
|
clearPid();
|
|
5477
5634
|
}
|
|
5478
|
-
|
|
5635
|
+
const killPort = async (p) => {
|
|
5479
5636
|
try {
|
|
5480
|
-
const output = execSync2(`lsof -ti :${
|
|
5637
|
+
const output = execSync2(`lsof -ti :${p}`, { encoding: "utf-8" }).trim();
|
|
5481
5638
|
if (output) {
|
|
5482
|
-
for (const
|
|
5639
|
+
for (const pid of output.split("\n")) {
|
|
5483
5640
|
try {
|
|
5484
|
-
process.kill(parseInt(
|
|
5641
|
+
process.kill(parseInt(pid, 10), "SIGTERM");
|
|
5485
5642
|
} catch {
|
|
5486
5643
|
}
|
|
5487
5644
|
}
|
|
@@ -5489,52 +5646,66 @@ function createCli() {
|
|
|
5489
5646
|
} catch {
|
|
5490
5647
|
}
|
|
5491
5648
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
args.push("--no-upstream");
|
|
5505
|
-
}
|
|
5506
|
-
if (anthropicApiKey) {
|
|
5507
|
-
args.push("--api-key", anthropicApiKey);
|
|
5508
|
-
}
|
|
5509
|
-
const binPath = process.argv[1];
|
|
5510
|
-
const cliRoot = path8.resolve(path8.dirname(binPath), "..");
|
|
5511
|
-
const distEntry = path8.join(cliRoot, "dist", "bin.js");
|
|
5512
|
-
const useDistEntry = fs8.existsSync(distEntry);
|
|
5513
|
-
const child = spawn3(
|
|
5514
|
-
process.execPath,
|
|
5515
|
-
useDistEntry ? [distEntry, ...args] : [binPath, ...args],
|
|
5516
|
-
{
|
|
5517
|
-
detached: true,
|
|
5518
|
-
stdio: ["ignore", logFd, logFd],
|
|
5519
|
-
cwd: workspacePath,
|
|
5520
|
-
env: {
|
|
5521
|
-
...process.env,
|
|
5522
|
-
NODE_NO_WARNINGS: "1",
|
|
5523
|
-
// Strip API key when using subscription to prevent SDK from picking it up
|
|
5524
|
-
...useSubscription ? { ANTHROPIC_API_KEY: "" } : {}
|
|
5649
|
+
if (await isPortInUse(p)) {
|
|
5650
|
+
try {
|
|
5651
|
+
const output = execSync2(`lsof -ti :${p}`, { encoding: "utf-8" }).trim();
|
|
5652
|
+
if (output) {
|
|
5653
|
+
for (const pid of output.split("\n")) {
|
|
5654
|
+
try {
|
|
5655
|
+
process.kill(parseInt(pid, 10), "SIGKILL");
|
|
5656
|
+
} catch {
|
|
5657
|
+
}
|
|
5658
|
+
}
|
|
5659
|
+
}
|
|
5660
|
+
} catch {
|
|
5525
5661
|
}
|
|
5662
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
5526
5663
|
}
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5664
|
+
};
|
|
5665
|
+
await killPort(port);
|
|
5666
|
+
const binPath = process.argv[1];
|
|
5667
|
+
const cliRoot = path10.resolve(path10.dirname(binPath), "..");
|
|
5668
|
+
const distEntry = path10.join(cliRoot, "dist", "bin.js");
|
|
5669
|
+
const entryPath = fs10.existsSync(distEntry) ? distEntry : binPath;
|
|
5670
|
+
const serviceEnv = {};
|
|
5671
|
+
if (process.env.APP_URL) serviceEnv.APP_URL = process.env.APP_URL;
|
|
5672
|
+
if ("https://jarvis.appchy.com") serviceEnv.JARVIS_APP_URL = "https://jarvis.appchy.com";
|
|
5673
|
+
if (process.env.JARVIS_CONFIG_DIR) serviceEnv.JARVIS_CONFIG_DIR = process.env.JARVIS_CONFIG_DIR;
|
|
5674
|
+
if (process.env.JARVIS_USER_ID) serviceEnv.JARVIS_USER_ID = process.env.JARVIS_USER_ID;
|
|
5675
|
+
if (process.env.JARVIS_ENV_ID) serviceEnv.JARVIS_ENV_ID = process.env.JARVIS_ENV_ID;
|
|
5676
|
+
service.install({
|
|
5677
|
+
port,
|
|
5678
|
+
workspacePath,
|
|
5679
|
+
nodePath: process.execPath,
|
|
5680
|
+
entryPath,
|
|
5681
|
+
upstream: opts.upstream,
|
|
5682
|
+
...Object.keys(serviceEnv).length > 0 ? { env: serviceEnv } : {}
|
|
5683
|
+
});
|
|
5684
|
+
const startTime = Date.now();
|
|
5685
|
+
let agentReady = false;
|
|
5686
|
+
while (Date.now() - startTime < 15e3) {
|
|
5687
|
+
if (await isPortInUse(port)) {
|
|
5688
|
+
agentReady = true;
|
|
5689
|
+
break;
|
|
5690
|
+
}
|
|
5691
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
5692
|
+
}
|
|
5693
|
+
if (!agentReady) {
|
|
5694
|
+
console.error("Agent failed to start. Check logs:");
|
|
5695
|
+
console.error(` ${LOG_FILE}`);
|
|
5696
|
+
process.exit(1);
|
|
5697
|
+
}
|
|
5698
|
+
const status = service.status();
|
|
5699
|
+
console.log(`Jarvis agent started`);
|
|
5532
5700
|
console.log(` Local: ws://127.0.0.1:${port}`);
|
|
5533
5701
|
console.log(` Workspace: ${workspacePath}`);
|
|
5534
5702
|
console.log(` Logs: ${LOG_FILE}`);
|
|
5535
5703
|
if (config?.apiUrl) {
|
|
5536
5704
|
console.log(` Cloud: ${config.apiUrl}`);
|
|
5537
5705
|
}
|
|
5706
|
+
if (status.os) {
|
|
5707
|
+
console.log(` Service: ${status.os} (auto-start on login, crash recovery)`);
|
|
5708
|
+
}
|
|
5538
5709
|
}
|
|
5539
5710
|
);
|
|
5540
5711
|
program.command("stop").description("Stop the running agent").action(async () => {
|
|
@@ -5582,7 +5753,7 @@ function createCli() {
|
|
|
5582
5753
|
await program.parseAsync(["node", "jarvis", "start"]);
|
|
5583
5754
|
});
|
|
5584
5755
|
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) => {
|
|
5585
|
-
if (!
|
|
5756
|
+
if (!fs10.existsSync(LOG_FILE)) {
|
|
5586
5757
|
console.log("No log file found. Start the agent first: jarvis start");
|
|
5587
5758
|
return;
|
|
5588
5759
|
}
|
|
@@ -5640,10 +5811,10 @@ function createCli() {
|
|
|
5640
5811
|
const workspacePath = opts.workspace ?? config?.workspacePath ?? process.cwd();
|
|
5641
5812
|
const service = createServiceManager();
|
|
5642
5813
|
const binPath = process.argv[1];
|
|
5643
|
-
const cliRoot =
|
|
5644
|
-
const distEntry =
|
|
5645
|
-
const entryPath =
|
|
5646
|
-
if (!
|
|
5814
|
+
const cliRoot = path10.resolve(path10.dirname(binPath), "..");
|
|
5815
|
+
const distEntry = path10.join(cliRoot, "dist", "bin.js");
|
|
5816
|
+
const entryPath = fs10.existsSync(distEntry) ? distEntry : binPath;
|
|
5817
|
+
if (!fs10.existsSync(distEntry)) {
|
|
5647
5818
|
console.log("\x1B[33mWarning:\x1B[0m Using dev mode entry point (tsx).");
|
|
5648
5819
|
console.log(" For reliability, build first: pnpm build");
|
|
5649
5820
|
console.log();
|
|
@@ -5656,13 +5827,20 @@ function createCli() {
|
|
|
5656
5827
|
}
|
|
5657
5828
|
clearPid();
|
|
5658
5829
|
}
|
|
5830
|
+
const installEnv = {};
|
|
5831
|
+
if (process.env.APP_URL) installEnv.APP_URL = process.env.APP_URL;
|
|
5832
|
+
if ("https://jarvis.appchy.com") installEnv.JARVIS_APP_URL = "https://jarvis.appchy.com";
|
|
5833
|
+
if (process.env.JARVIS_CONFIG_DIR) installEnv.JARVIS_CONFIG_DIR = process.env.JARVIS_CONFIG_DIR;
|
|
5834
|
+
if (process.env.JARVIS_USER_ID) installEnv.JARVIS_USER_ID = process.env.JARVIS_USER_ID;
|
|
5835
|
+
if (process.env.JARVIS_ENV_ID) installEnv.JARVIS_ENV_ID = process.env.JARVIS_ENV_ID;
|
|
5659
5836
|
try {
|
|
5660
5837
|
service.install({
|
|
5661
5838
|
port,
|
|
5662
5839
|
workspacePath,
|
|
5663
5840
|
nodePath: process.execPath,
|
|
5664
5841
|
entryPath,
|
|
5665
|
-
upstream: opts.upstream
|
|
5842
|
+
upstream: opts.upstream,
|
|
5843
|
+
...Object.keys(installEnv).length > 0 ? { env: installEnv } : {}
|
|
5666
5844
|
});
|
|
5667
5845
|
} catch (err) {
|
|
5668
5846
|
console.error(`Failed to install service: ${err instanceof Error ? err.message : err}`);
|
|
@@ -5709,10 +5887,10 @@ function createCli() {
|
|
|
5709
5887
|
// src/bin.ts
|
|
5710
5888
|
function findEnv() {
|
|
5711
5889
|
let dir = process.cwd();
|
|
5712
|
-
while (dir !==
|
|
5713
|
-
const envPath =
|
|
5714
|
-
if (
|
|
5715
|
-
dir =
|
|
5890
|
+
while (dir !== path11.dirname(dir)) {
|
|
5891
|
+
const envPath = path11.join(dir, ".env");
|
|
5892
|
+
if (fs11.existsSync(envPath)) return envPath;
|
|
5893
|
+
dir = path11.dirname(dir);
|
|
5716
5894
|
}
|
|
5717
5895
|
return void 0;
|
|
5718
5896
|
}
|