@appchy/jarvis 0.1.18 → 0.1.20

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 CHANGED
@@ -1,27 +1,28 @@
1
- // src/bin.ts
2
- import dotenv from "dotenv";
3
- import fs11 from "fs";
4
- import path11 from "path";
5
-
6
- // src/cli.ts
7
- import { Command } from "commander";
8
- import { spawn as spawn3, execSync as execSync2 } from "child_process";
9
- import fs10 from "fs";
10
- import path10 from "path";
11
- import os5 from "os";
12
-
13
1
  // src/config.ts
14
2
  import fs from "fs";
15
3
  import path from "path";
16
4
  import os from "os";
5
+ import { fileURLToPath } from "url";
6
+ var __filename = fileURLToPath(import.meta.url);
7
+ var __dirname = path.dirname(__filename);
8
+ function isDev() {
9
+ try {
10
+ const envFile = path.join(__dirname, "..", "env.json");
11
+ const data = JSON.parse(fs.readFileSync(envFile, "utf-8"));
12
+ return data.env === "development";
13
+ } catch {
14
+ return false;
15
+ }
16
+ }
17
+ var PROD_APP_URL = "https://jarvis.appchy.com";
18
+ var DEV_APP_URL = "http://localhost:3000";
19
+ function getDefaultAppUrl() {
20
+ return isDev() ? process.env.APP_URL || DEV_APP_URL : PROD_APP_URL;
21
+ }
17
22
  function resolveConfigDir() {
18
23
  if (process.env.JARVIS_CONFIG_DIR) return process.env.JARVIS_CONFIG_DIR;
19
24
  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
+ return isDev() ? path.join(base, "dev") : base;
25
26
  }
26
27
  var CONFIG_DIR = resolveConfigDir();
27
28
  var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
@@ -62,6 +63,13 @@ function getConfigPath() {
62
63
  return CONFIG_FILE;
63
64
  }
64
65
 
66
+ // src/cli.ts
67
+ import { Command } from "commander";
68
+ import { spawn as spawn3, execSync as execSync2 } from "child_process";
69
+ import fs10 from "fs";
70
+ import path10 from "path";
71
+ import os6 from "os";
72
+
65
73
  // ../../packages/logger/src/logger.ts
66
74
  function resolveError(meta) {
67
75
  if (!meta) return {};
@@ -1982,42 +1990,58 @@ function formatToolDescription(toolName, input) {
1982
1990
  // ../../packages/agent/src/worktree.ts
1983
1991
  import { exec as execCb } from "child_process";
1984
1992
  import fs2 from "fs/promises";
1993
+ import os2 from "os";
1985
1994
  import path2 from "path";
1986
1995
  import { promisify } from "util";
1987
1996
  var exec = promisify(execCb);
1988
- var WORKTREE_DIR = ".jarvis/worktrees";
1989
- async function createWorktree(repoPath, taskId, baseBranch) {
1990
- const branchName = `jarvis/task-${taskId}`;
1991
- const worktreeDir = path2.join(repoPath, WORKTREE_DIR, `task-${taskId}`);
1992
- await fs2.mkdir(path2.dirname(worktreeDir), { recursive: true });
1993
- await ensureGitignore(repoPath);
1994
- if (await worktreeExists(repoPath, taskId)) {
1995
- return worktreeDir;
1996
- }
1997
- const startPoint = baseBranch ?? "HEAD";
1998
- await exec(`git worktree add -b "${branchName}" "${worktreeDir}" "${startPoint}"`, {
1999
- cwd: repoPath
1997
+ var DEFAULT_WORKTREE_DIR = path2.join(os2.homedir(), ".jarvis", "worktrees");
1998
+ function resolveDir(worktreeDir) {
1999
+ if (!worktreeDir) return DEFAULT_WORKTREE_DIR;
2000
+ return worktreeDir.replace(/^~/, os2.homedir());
2001
+ }
2002
+ async function createWorktree(req) {
2003
+ const branchName = `jarvis/task-${req.taskId}`;
2004
+ const baseDir = resolveDir(req.worktreeDir);
2005
+ const worktreePath = path2.join(baseDir, `task-${req.taskId}`);
2006
+ await fs2.mkdir(path2.dirname(worktreePath), { recursive: true });
2007
+ if (await worktreeExists({ taskId: req.taskId, worktreeDir: req.worktreeDir })) {
2008
+ return { worktreePath, branchName };
2009
+ }
2010
+ const startPoint = req.branch ?? "HEAD";
2011
+ await exec(`git worktree add -b "${branchName}" "${worktreePath}" "${startPoint}"`, {
2012
+ cwd: req.repoPath
2000
2013
  });
2001
- return worktreeDir;
2014
+ return { worktreePath, branchName };
2002
2015
  }
2003
- async function worktreeExists(repoPath, taskId) {
2004
- const worktreeDir = path2.join(repoPath, WORKTREE_DIR, `task-${taskId}`);
2005
- try {
2006
- await fs2.access(worktreeDir);
2007
- return true;
2008
- } catch {
2009
- return false;
2016
+ async function listWorktrees(req) {
2017
+ const { stdout } = await exec("git worktree list --porcelain", { cwd: req.repoPath });
2018
+ const worktrees = [];
2019
+ let currentPath = "";
2020
+ let currentBranch = "";
2021
+ for (const line of stdout.split("\n")) {
2022
+ if (line.startsWith("worktree ")) {
2023
+ currentPath = line.slice(9);
2024
+ } else if (line.startsWith("branch refs/heads/")) {
2025
+ currentBranch = line.slice(18);
2026
+ if (currentBranch.startsWith("jarvis/task-")) {
2027
+ worktrees.push({
2028
+ taskId: currentBranch.replace("jarvis/task-", ""),
2029
+ path: currentPath,
2030
+ branch: currentBranch
2031
+ });
2032
+ }
2033
+ }
2010
2034
  }
2035
+ return worktrees;
2011
2036
  }
2012
- async function ensureGitignore(repoPath) {
2013
- const gitignorePath = path2.join(repoPath, ".gitignore");
2037
+ async function worktreeExists(req) {
2038
+ const baseDir = resolveDir(req.worktreeDir);
2039
+ const worktreePath = path2.join(baseDir, `task-${req.taskId}`);
2014
2040
  try {
2015
- const content = await fs2.readFile(gitignorePath, "utf-8");
2016
- if (!content.includes(".jarvis/")) {
2017
- await fs2.appendFile(gitignorePath, "\n.jarvis/\n");
2018
- }
2041
+ await fs2.access(worktreePath);
2042
+ return true;
2019
2043
  } catch {
2020
- await fs2.writeFile(gitignorePath, ".jarvis/\n");
2044
+ return false;
2021
2045
  }
2022
2046
  }
2023
2047
 
@@ -2257,7 +2281,7 @@ function isImageFile(mimeType) {
2257
2281
  }
2258
2282
  function resolveUrl(url, baseUrl) {
2259
2283
  if (url.startsWith("http://") || url.startsWith("https://")) return url;
2260
- const origin = baseUrl || process.env.APP_URL || "http://localhost:3000";
2284
+ const origin = baseUrl || getDefaultAppUrl();
2261
2285
  return `${origin}${url}`;
2262
2286
  }
2263
2287
  async function resolveAttachments(attachments, workspacePath, baseUrl) {
@@ -2338,7 +2362,7 @@ function createAgent(deps) {
2338
2362
  const systemMsg = msg.messages.find((m) => m.role === "system");
2339
2363
  const nonSystemMessages = msg.messages.filter((m) => m.role !== "system");
2340
2364
  if (msg.attachments?.length) {
2341
- const attachmentContext = await resolveAttachments(msg.attachments, wsPath);
2365
+ const attachmentContext = await resolveAttachments(msg.attachments, wsPath, getDefaultAppUrl());
2342
2366
  if (attachmentContext) {
2343
2367
  let lastUserIdx = -1;
2344
2368
  for (let i = nonSystemMessages.length - 1; i >= 0; i--) {
@@ -2576,6 +2600,12 @@ function createGitHandler(workspacePath, broadcast) {
2576
2600
  case "git:diffStats":
2577
2601
  await handleDiffStats(msg);
2578
2602
  return true;
2603
+ case "git:listWorktrees":
2604
+ await handleListWorktrees(msg);
2605
+ return true;
2606
+ case "git:browseDir":
2607
+ await handleBrowseDir(msg);
2608
+ return true;
2579
2609
  default:
2580
2610
  return false;
2581
2611
  }
@@ -2583,34 +2613,55 @@ function createGitHandler(workspacePath, broadcast) {
2583
2613
  function respond(requestId, result, error) {
2584
2614
  broadcast({ type: "git:response", requestId, result, error });
2585
2615
  }
2616
+ async function hasGitDir(dir) {
2617
+ return fs5.access(path5.join(dir, ".git")).then(() => true).catch(() => false);
2618
+ }
2619
+ async function fetchAndCheckout(repoDir, branch) {
2620
+ await exec2("git fetch origin", { cwd: repoDir, timeout: 12e4 });
2621
+ try {
2622
+ await exec2(`git checkout "${branch}"`, { cwd: repoDir });
2623
+ } catch {
2624
+ await exec2(`git checkout -b "${branch}" "origin/${branch}"`, { cwd: repoDir });
2625
+ }
2626
+ await exec2("git pull --ff-only", { cwd: repoDir }).catch(() => {
2627
+ });
2628
+ }
2586
2629
  async function handleResolveRepo(msg) {
2587
2630
  try {
2588
- const repoDir = path5.join(workspacePath, msg.owner, msg.name);
2589
- const isCloned = await fs5.access(path5.join(repoDir, ".git")).then(() => true).catch(() => false);
2590
- if (!isCloned) {
2591
- logger.sys.info("[Git] Cloning repo...", { repoDir });
2592
- await fs5.mkdir(path5.join(workspacePath, msg.owner), { recursive: true });
2593
- const cloneUrl = `git@github.com:${msg.owner}/${msg.name}.git`;
2594
- await exec2(`git clone "${cloneUrl}" "${repoDir}"`, { timeout: 3e5 });
2631
+ if (msg.repoPath && await hasGitDir(msg.repoPath)) {
2632
+ logger.sys.info("[Git] Using explicit repo path", { repoPath: msg.repoPath });
2633
+ await fetchAndCheckout(msg.repoPath, msg.branch);
2634
+ respond(msg.requestId, { repoPath: msg.repoPath });
2635
+ return;
2595
2636
  }
2596
- await exec2("git fetch origin", { cwd: repoDir, timeout: 12e4 });
2597
- try {
2598
- await exec2(`git checkout "${msg.branch}"`, { cwd: repoDir });
2599
- } catch {
2600
- await exec2(`git checkout -b "${msg.branch}" "origin/${msg.branch}"`, { cwd: repoDir });
2637
+ const byName = path5.join(workspacePath, msg.name);
2638
+ if (await hasGitDir(byName)) {
2639
+ logger.sys.info("[Git] Found repo by name", { repoPath: byName });
2640
+ await fetchAndCheckout(byName, msg.branch);
2641
+ respond(msg.requestId, { repoPath: byName });
2642
+ return;
2601
2643
  }
2602
- await exec2("git pull --ff-only", { cwd: repoDir }).catch(() => {
2603
- });
2604
- logger.sys.info("[Git] Repo resolved", { repoDir, branch: msg.branch });
2605
- respond(msg.requestId, { repoPath: repoDir });
2644
+ const byOwnerName = path5.join(workspacePath, msg.owner, msg.name);
2645
+ if (await hasGitDir(byOwnerName)) {
2646
+ logger.sys.info("[Git] Found repo by owner/name", { repoPath: byOwnerName });
2647
+ await fetchAndCheckout(byOwnerName, msg.branch);
2648
+ respond(msg.requestId, { repoPath: byOwnerName });
2649
+ return;
2650
+ }
2651
+ logger.sys.info("[Git] Cloning repo...", { repoDir: byOwnerName });
2652
+ await fs5.mkdir(path5.join(workspacePath, msg.owner), { recursive: true });
2653
+ const cloneUrl = `git@github.com:${msg.owner}/${msg.name}.git`;
2654
+ await exec2(`git clone "${cloneUrl}" "${byOwnerName}"`, { timeout: 3e5 });
2655
+ await fetchAndCheckout(byOwnerName, msg.branch);
2656
+ respond(msg.requestId, { repoPath: byOwnerName });
2606
2657
  } catch (err) {
2607
2658
  respond(msg.requestId, null, err instanceof Error ? err.message : String(err));
2608
2659
  }
2609
2660
  }
2610
2661
  async function handleCreateWorktree(msg) {
2611
2662
  try {
2612
- const worktreePath = await createWorktree(msg.repoPath, msg.taskId, msg.branch);
2613
- respond(msg.requestId, { worktreePath, branchName: `jarvis/task-${msg.taskId}` });
2663
+ const result = await createWorktree({ repoPath: msg.repoPath, taskId: msg.taskId, branch: msg.branch });
2664
+ respond(msg.requestId, result);
2614
2665
  } catch (err) {
2615
2666
  respond(msg.requestId, null, err instanceof Error ? err.message : String(err));
2616
2667
  }
@@ -2652,6 +2703,26 @@ function createGitHandler(workspacePath, broadcast) {
2652
2703
  respond(msg.requestId, { files: [], additions: 0, deletions: 0 });
2653
2704
  }
2654
2705
  }
2706
+ async function handleListWorktrees(msg) {
2707
+ try {
2708
+ const worktrees = await listWorktrees({ repoPath: msg.repoPath });
2709
+ respond(msg.requestId, worktrees);
2710
+ } catch (err) {
2711
+ respond(msg.requestId, null, err instanceof Error ? err.message : String(err));
2712
+ }
2713
+ }
2714
+ async function handleBrowseDir(msg) {
2715
+ try {
2716
+ const { stdout } = await exec2(
2717
+ `osascript -e 'set theFolder to POSIX path of (choose folder with prompt "Select worktree directory")' -e 'return theFolder'`,
2718
+ { timeout: 6e4 }
2719
+ );
2720
+ const selectedPath = stdout.trim();
2721
+ respond(msg.requestId, { path: selectedPath });
2722
+ } catch (err) {
2723
+ respond(msg.requestId, null, err instanceof Error ? err.message : String(err));
2724
+ }
2725
+ }
2655
2726
  return { handle };
2656
2727
  }
2657
2728
 
@@ -2754,7 +2825,11 @@ function createUpstreamClient(config) {
2754
2825
  }
2755
2826
  function getTokenExpiry(token) {
2756
2827
  try {
2757
- const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64").toString());
2828
+ const parts = token.split(".");
2829
+ if (parts.length === 5) {
2830
+ return Math.floor(Date.now() / 1e3) + 3600;
2831
+ }
2832
+ const payload = JSON.parse(Buffer.from(parts[1], "base64").toString());
2758
2833
  return payload.exp ?? null;
2759
2834
  } catch {
2760
2835
  return null;
@@ -2800,6 +2875,29 @@ function createUpstreamClient(config) {
2800
2875
  return false;
2801
2876
  }
2802
2877
  }
2878
+ function attemptReauth() {
2879
+ if (!config.onAuthExhausted) {
2880
+ logger.sys.error("[Upstream] Auth failed after max retries, giving up");
2881
+ return;
2882
+ }
2883
+ logger.sys.info("[Upstream] Auth exhausted, attempting re-authentication...");
2884
+ config.onAuthExhausted().then((result) => {
2885
+ if (!result) {
2886
+ logger.sys.error("[Upstream] Re-authentication failed, giving up");
2887
+ return;
2888
+ }
2889
+ currentToken = result.token;
2890
+ if (result.refreshToken) config.refreshToken = result.refreshToken;
2891
+ authFailures = 0;
2892
+ retries = 0;
2893
+ logger.sys.info("[Upstream] Re-authenticated, reconnecting...");
2894
+ reconnectTimer = setTimeout(connect, 1e3);
2895
+ }).catch((err) => {
2896
+ logger.sys.error("[Upstream] Re-authentication error", {
2897
+ error: err instanceof Error ? err.message : String(err)
2898
+ });
2899
+ });
2900
+ }
2803
2901
  function connect() {
2804
2902
  if (closed) return;
2805
2903
  ws = new WebSocket2(config.apiUrl, {
@@ -2833,10 +2931,7 @@ function createUpstreamClient(config) {
2833
2931
  } else {
2834
2932
  authFailures++;
2835
2933
  if (authFailures >= MAX_AUTH_RETRIES) {
2836
- logger.sys.error("[Upstream] Auth failed after max retries, giving up", {
2837
- code,
2838
- attempts: authFailures
2839
- });
2934
+ attemptReauth();
2840
2935
  return;
2841
2936
  }
2842
2937
  reconnectTimer = setTimeout(connect, 3e3);
@@ -2846,10 +2941,7 @@ function createUpstreamClient(config) {
2846
2941
  }
2847
2942
  authFailures++;
2848
2943
  if (authFailures >= MAX_AUTH_RETRIES) {
2849
- logger.sys.error("[Upstream] Auth failed after max retries, giving up", {
2850
- code,
2851
- attempts: authFailures
2852
- });
2944
+ attemptReauth();
2853
2945
  return;
2854
2946
  }
2855
2947
  logger.sys.warn("[Upstream] Auth failed, retrying...", {
@@ -2994,16 +3086,16 @@ async function startAgent(options) {
2994
3086
  import React6 from "react";
2995
3087
  import { render as render2 } from "ink";
2996
3088
  import { spawn } from "child_process";
2997
- import { fileURLToPath } from "url";
3089
+ import { fileURLToPath as fileURLToPath2 } from "url";
2998
3090
  import path8 from "path";
2999
3091
  import fs8 from "fs";
3000
- import os3 from "os";
3092
+ import os4 from "os";
3001
3093
  import WebSocket4 from "ws";
3002
3094
 
3003
3095
  // src/tui/settings.ts
3004
3096
  import fs6 from "fs";
3005
3097
  import path6 from "path";
3006
- import os2 from "os";
3098
+ import os3 from "os";
3007
3099
  var DEFAULT_SETTINGS = {
3008
3100
  model: "claude-sonnet-4-20250514",
3009
3101
  thinking: { type: "adaptive" },
@@ -3012,7 +3104,7 @@ var DEFAULT_SETTINGS = {
3012
3104
  theme: "dark",
3013
3105
  disallowedTools: []
3014
3106
  };
3015
- var SETTINGS_DIR = path6.join(os2.homedir(), ".jarvis");
3107
+ var SETTINGS_DIR = path6.join(os3.homedir(), ".jarvis");
3016
3108
  var SETTINGS_FILE = path6.join(SETTINGS_DIR, "settings.json");
3017
3109
  function loadSettings() {
3018
3110
  try {
@@ -4950,12 +5042,12 @@ async function launchChat(opts) {
4950
5042
  }
4951
5043
  function spawnAgentProcess(port, workspacePath) {
4952
5044
  const config = loadConfig();
4953
- const logDir = path8.join(os3.homedir(), ".jarvis");
5045
+ const logDir = path8.join(os4.homedir(), ".jarvis");
4954
5046
  fs8.mkdirSync(logDir, { recursive: true });
4955
5047
  const logFile = path8.join(logDir, "agent.log");
4956
5048
  const logFd = fs8.openSync(logFile, "a");
4957
- const __filename = fileURLToPath(import.meta.url);
4958
- const cliRoot = path8.resolve(path8.dirname(__filename), "../..");
5049
+ const __filename2 = fileURLToPath2(import.meta.url);
5050
+ const cliRoot = path8.resolve(path8.dirname(__filename2), "../..");
4959
5051
  const binPath = path8.join(cliRoot, "bin", "jarvis.mjs");
4960
5052
  const args = ["start", "--port", String(port), "--workspace", workspacePath];
4961
5053
  if (config?.apiUrl && config?.token) {
@@ -5010,7 +5102,7 @@ async function waitForAgent(port, maxAttempts = 30) {
5010
5102
  import { execSync, spawn as spawn2 } from "child_process";
5011
5103
  import fs9 from "fs";
5012
5104
  import path9 from "path";
5013
- import os4 from "os";
5105
+ import os5 from "os";
5014
5106
  function createServiceManager() {
5015
5107
  if (process.platform === "darwin") return new MacOSService();
5016
5108
  if (process.platform === "win32") return new WindowsService();
@@ -5018,7 +5110,7 @@ function createServiceManager() {
5018
5110
  return new FallbackService();
5019
5111
  }
5020
5112
  var PLIST_LABEL = "com.appchy.jarvis";
5021
- var PLIST_DIR = path9.join(os4.homedir(), "Library", "LaunchAgents");
5113
+ var PLIST_DIR = path9.join(os5.homedir(), "Library", "LaunchAgents");
5022
5114
  var PLIST_PATH = path9.join(PLIST_DIR, `${PLIST_LABEL}.plist`);
5023
5115
  function escapeXml(s) {
5024
5116
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
@@ -5038,7 +5130,7 @@ var MacOSService = class {
5038
5130
  if (!opts.upstream) {
5039
5131
  args.push("--no-upstream");
5040
5132
  }
5041
- const logFile = path9.join(os4.homedir(), ".jarvis", "agent.log");
5133
+ const logFile = path9.join(os5.homedir(), ".jarvis", "agent.log");
5042
5134
  const envPath = process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin";
5043
5135
  const plist = `<?xml version="1.0" encoding="UTF-8"?>
5044
5136
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -5060,7 +5152,7 @@ ${args.map((a) => ` <string>${escapeXml(a)}</string>`).join("\n")}
5060
5152
  <key>PATH</key>
5061
5153
  <string>${escapeXml(envPath)}</string>
5062
5154
  <key>HOME</key>
5063
- <string>${escapeXml(os4.homedir())}</string>
5155
+ <string>${escapeXml(os5.homedir())}</string>
5064
5156
  <key>NODE_NO_WARNINGS</key>
5065
5157
  <string>1</string>
5066
5158
  ${Object.entries(opts.env ?? {}).map(([k, v]) => ` <key>${escapeXml(k)}</key>
@@ -5198,7 +5290,7 @@ var WindowsService = class {
5198
5290
  </Actions>
5199
5291
  </Task>
5200
5292
  `;
5201
- const tmpDir = os4.tmpdir();
5293
+ const tmpDir = os5.tmpdir();
5202
5294
  const tmpFile = path9.join(tmpDir, `jarvis-task-${Date.now()}.xml`);
5203
5295
  fs9.writeFileSync(tmpFile, xml, { encoding: "utf-16le" });
5204
5296
  try {
@@ -5245,7 +5337,7 @@ var WindowsService = class {
5245
5337
  }
5246
5338
  }
5247
5339
  };
5248
- var SYSTEMD_DIR = path9.join(os4.homedir(), ".config", "systemd", "user");
5340
+ var SYSTEMD_DIR = path9.join(os5.homedir(), ".config", "systemd", "user");
5249
5341
  var UNIT_NAME = "jarvis.service";
5250
5342
  var UNIT_PATH = path9.join(SYSTEMD_DIR, UNIT_NAME);
5251
5343
  var LinuxService = class {
@@ -5262,7 +5354,7 @@ var LinuxService = class {
5262
5354
  if (!opts.upstream) {
5263
5355
  args.push("--no-upstream");
5264
5356
  }
5265
- const logFile = path9.join(os4.homedir(), ".jarvis", "agent.log");
5357
+ const logFile = path9.join(os5.homedir(), ".jarvis", "agent.log");
5266
5358
  const envPath = process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin";
5267
5359
  const unit = `[Unit]
5268
5360
  Description=Jarvis AI Agent
@@ -5273,7 +5365,7 @@ Type=simple
5273
5365
  ExecStart=${opts.nodePath} ${args.join(" ")}
5274
5366
  WorkingDirectory=${opts.workspacePath}
5275
5367
  Environment=PATH=${envPath}
5276
- Environment=HOME=${os4.homedir()}
5368
+ Environment=HOME=${os5.homedir()}
5277
5369
  Environment=NODE_NO_WARNINGS=1
5278
5370
  Restart=always
5279
5371
  RestartSec=5
@@ -5296,7 +5388,7 @@ WantedBy=default.target
5296
5388
  execSync("systemctl --user enable jarvis.service", { stdio: "ignore" });
5297
5389
  execSync("systemctl --user start jarvis.service", { stdio: "ignore" });
5298
5390
  try {
5299
- execSync(`loginctl enable-linger ${os4.userInfo().username}`, { stdio: "ignore" });
5391
+ execSync(`loginctl enable-linger ${os5.userInfo().username}`, { stdio: "ignore" });
5300
5392
  } catch {
5301
5393
  }
5302
5394
  }
@@ -5349,12 +5441,12 @@ WantedBy=default.target
5349
5441
  };
5350
5442
  var FallbackService = class {
5351
5443
  constructor() {
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");
5444
+ this.pidFile = path9.join(os5.homedir(), ".jarvis", "agent.pid");
5445
+ this.logFile = path9.join(os5.homedir(), ".jarvis", "agent.log");
5446
+ this.markerFile = path9.join(os5.homedir(), ".jarvis", "service-installed");
5355
5447
  }
5356
5448
  install(opts) {
5357
- const jarvisDir = path9.join(os4.homedir(), ".jarvis");
5449
+ const jarvisDir = path9.join(os5.homedir(), ".jarvis");
5358
5450
  fs9.mkdirSync(jarvisDir, { recursive: true });
5359
5451
  this.stop();
5360
5452
  const args = [
@@ -5439,7 +5531,7 @@ var FallbackService = class {
5439
5531
  import { createRequire } from "module";
5440
5532
  var _require = createRequire(import.meta.url);
5441
5533
  var PKG_VERSION = _require("../package.json").version ?? "dev";
5442
- var LOG_DIR = path10.join(os5.homedir(), ".jarvis");
5534
+ var LOG_DIR = path10.join(os6.homedir(), ".jarvis");
5443
5535
  var LOG_FILE = path10.join(LOG_DIR, "agent.log");
5444
5536
  var PID_FILE = path10.join(LOG_DIR, "agent.pid");
5445
5537
  function readPid() {
@@ -5540,9 +5632,8 @@ async function browserAuth(appUrl) {
5540
5632
  }
5541
5633
  }
5542
5634
  function getAppUrl() {
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";
5635
+ const config = loadConfig();
5636
+ return config?.appUrl ?? getDefaultAppUrl();
5546
5637
  }
5547
5638
  async function ensureSetup() {
5548
5639
  const config = loadConfig();
@@ -5552,6 +5643,45 @@ async function ensureSetup() {
5552
5643
  clearConfig();
5553
5644
  return browserAuth(getAppUrl());
5554
5645
  }
5646
+ function parseGitRemote(url) {
5647
+ const sshMatch = url.match(/[:\/]([^/]+)\/([^/]+?)(?:\.git)?$/);
5648
+ if (sshMatch) return { owner: sshMatch[1], name: sshMatch[2] };
5649
+ return null;
5650
+ }
5651
+ async function autoRegisterRepo(workspacePath, config) {
5652
+ try {
5653
+ const remoteUrl = execSync2("git config --get remote.origin.url", {
5654
+ cwd: workspacePath,
5655
+ encoding: "utf-8",
5656
+ timeout: 5e3
5657
+ }).trim();
5658
+ if (!remoteUrl) return;
5659
+ const parsed = parseGitRemote(remoteUrl);
5660
+ if (!parsed) return;
5661
+ const slug = `${parsed.owner}/${parsed.name}`;
5662
+ const appUrl = config?.appUrl ?? getDefaultAppUrl();
5663
+ const token = config?.token;
5664
+ if (!token) return;
5665
+ console.log(`Registering repo ${slug} \u2192 ${workspacePath}`);
5666
+ const envId = config?.envId ?? "local";
5667
+ const res = await fetch(`${appUrl}/api/v1/settings/preferences/repos?envId=${envId}`, {
5668
+ method: "PATCH",
5669
+ headers: {
5670
+ "Content-Type": "application/json",
5671
+ Authorization: `Bearer ${token}`
5672
+ },
5673
+ body: JSON.stringify({ [slug]: workspacePath })
5674
+ });
5675
+ if (res.ok) {
5676
+ console.log(` Repo registered`);
5677
+ } else {
5678
+ const body = await res.text().catch(() => "");
5679
+ console.error(` Failed to register repo (${res.status}): ${body}`);
5680
+ }
5681
+ } catch (err) {
5682
+ console.error(` Failed to register repo: ${err instanceof Error ? err.message : err}`);
5683
+ }
5684
+ }
5555
5685
  function createCli() {
5556
5686
  const program = new Command().name("jarvis").description("Jarvis local agent \u2014 runs Claude Code on your machine").version(PKG_VERSION);
5557
5687
  program.command("connect <token>").description("Connect to Jarvis cloud using a token from the web UI").option("-w, --workspace <path>", "Workspace root path for repo operations").action((token, opts) => {
@@ -5593,6 +5723,9 @@ function createCli() {
5593
5723
  const port = parseInt(opts.port, 10);
5594
5724
  const workspacePath = opts.workspace ?? config?.workspacePath ?? process.cwd();
5595
5725
  const userId = config?.userId ?? process.env.JARVIS_USER_ID ?? "local";
5726
+ if (!opts.foreground) {
5727
+ await autoRegisterRepo(workspacePath, config);
5728
+ }
5596
5729
  const explicitApiKey = opts.apiKey ?? config?.anthropicApiKey;
5597
5730
  const useSubscription = !explicitApiKey;
5598
5731
  const anthropicApiKey = explicitApiKey;
@@ -5610,7 +5743,20 @@ function createCli() {
5610
5743
  const apiUrl = process.env.JARVIS_UPSTREAM_URL ?? config?.apiUrl;
5611
5744
  const token = process.env.JARVIS_UPSTREAM_TOKEN ?? config?.token;
5612
5745
  if (opts.upstream && apiUrl && token) {
5613
- return { apiUrl, token, refreshToken: config?.refreshToken };
5746
+ const appUrl = getAppUrl();
5747
+ return {
5748
+ apiUrl,
5749
+ token,
5750
+ refreshToken: config?.refreshToken,
5751
+ onAuthExhausted: async () => {
5752
+ console.log("\n[Auth] Token expired \u2014 re-authenticating...");
5753
+ const ok = await browserAuth(appUrl);
5754
+ if (!ok) return null;
5755
+ const fresh = loadConfig();
5756
+ if (!fresh?.token) return null;
5757
+ return { token: fresh.token, refreshToken: fresh.refreshToken };
5758
+ }
5759
+ };
5614
5760
  }
5615
5761
  return void 0;
5616
5762
  })()
@@ -5666,17 +5812,19 @@ function createCli() {
5666
5812
  const binPath = process.argv[1];
5667
5813
  const cliRoot = path10.resolve(path10.dirname(binPath), "..");
5668
5814
  const distEntry = path10.join(cliRoot, "dist", "bin.js");
5669
- const entryPath = fs10.existsSync(distEntry) ? distEntry : binPath;
5815
+ const srcEntry = path10.join(cliRoot, "src", "bin.ts");
5816
+ const useSrc = isDev() && fs10.existsSync(srcEntry);
5817
+ const entryPath = useSrc ? srcEntry : fs10.existsSync(distEntry) ? distEntry : binPath;
5818
+ const tsxBin = path10.join(cliRoot, "node_modules", ".bin", "tsx");
5819
+ const nodePath = useSrc && fs10.existsSync(tsxBin) ? tsxBin : process.execPath;
5670
5820
  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
5821
  if (process.env.JARVIS_CONFIG_DIR) serviceEnv.JARVIS_CONFIG_DIR = process.env.JARVIS_CONFIG_DIR;
5674
5822
  if (process.env.JARVIS_USER_ID) serviceEnv.JARVIS_USER_ID = process.env.JARVIS_USER_ID;
5675
5823
  if (process.env.JARVIS_ENV_ID) serviceEnv.JARVIS_ENV_ID = process.env.JARVIS_ENV_ID;
5676
5824
  service.install({
5677
5825
  port,
5678
5826
  workspacePath,
5679
- nodePath: process.execPath,
5827
+ nodePath,
5680
5828
  entryPath,
5681
5829
  upstream: opts.upstream,
5682
5830
  ...Object.keys(serviceEnv).length > 0 ? { env: serviceEnv } : {}
@@ -5813,12 +5961,11 @@ function createCli() {
5813
5961
  const binPath = process.argv[1];
5814
5962
  const cliRoot = path10.resolve(path10.dirname(binPath), "..");
5815
5963
  const distEntry = path10.join(cliRoot, "dist", "bin.js");
5816
- const entryPath = fs10.existsSync(distEntry) ? distEntry : binPath;
5817
- if (!fs10.existsSync(distEntry)) {
5818
- console.log("\x1B[33mWarning:\x1B[0m Using dev mode entry point (tsx).");
5819
- console.log(" For reliability, build first: pnpm build");
5820
- console.log();
5821
- }
5964
+ const srcEntry = path10.join(cliRoot, "src", "bin.ts");
5965
+ const useSrc = isDev() && fs10.existsSync(srcEntry);
5966
+ const entryPath = useSrc ? srcEntry : fs10.existsSync(distEntry) ? distEntry : binPath;
5967
+ const tsxBin = path10.join(cliRoot, "node_modules", ".bin", "tsx");
5968
+ const nodePath = useSrc && fs10.existsSync(tsxBin) ? tsxBin : process.execPath;
5822
5969
  const pid = readPid();
5823
5970
  if (pid) {
5824
5971
  try {
@@ -5828,8 +5975,6 @@ function createCli() {
5828
5975
  clearPid();
5829
5976
  }
5830
5977
  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
5978
  if (process.env.JARVIS_CONFIG_DIR) installEnv.JARVIS_CONFIG_DIR = process.env.JARVIS_CONFIG_DIR;
5834
5979
  if (process.env.JARVIS_USER_ID) installEnv.JARVIS_USER_ID = process.env.JARVIS_USER_ID;
5835
5980
  if (process.env.JARVIS_ENV_ID) installEnv.JARVIS_ENV_ID = process.env.JARVIS_ENV_ID;
@@ -5837,7 +5982,7 @@ function createCli() {
5837
5982
  service.install({
5838
5983
  port,
5839
5984
  workspacePath,
5840
- nodePath: process.execPath,
5985
+ nodePath,
5841
5986
  entryPath,
5842
5987
  upstream: opts.upstream,
5843
5988
  ...Object.keys(installEnv).length > 0 ? { env: installEnv } : {}
@@ -5885,22 +6030,19 @@ function createCli() {
5885
6030
  }
5886
6031
 
5887
6032
  // src/bin.ts
5888
- function findEnv() {
6033
+ if (isDev()) {
6034
+ const dotenv = await import("dotenv");
6035
+ const fs11 = await import("fs");
6036
+ const path11 = await import("path");
5889
6037
  let dir = process.cwd();
5890
6038
  while (dir !== path11.dirname(dir)) {
5891
6039
  const envPath = path11.join(dir, ".env");
5892
- if (fs11.existsSync(envPath)) return envPath;
6040
+ if (fs11.existsSync(envPath)) {
6041
+ dotenv.config({ path: envPath });
6042
+ break;
6043
+ }
5893
6044
  dir = path11.dirname(dir);
5894
6045
  }
5895
- return void 0;
5896
- }
5897
- var envFile = findEnv();
5898
- if (envFile) {
5899
- const prevApiKey = process.env.ANTHROPIC_API_KEY;
5900
- const prevAppUrl = process.env.APP_URL;
5901
- dotenv.config({ path: envFile });
5902
- if (prevApiKey === void 0) delete process.env.ANTHROPIC_API_KEY;
5903
- if (prevAppUrl === void 0) delete process.env.APP_URL;
5904
6046
  }
5905
6047
  createCli().parse();
5906
6048
  //# sourceMappingURL=bin.js.map