@appchy/jarvis 0.1.13 → 0.1.15
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 +716 -127
- package/dist/bin.js.map +1 -1
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
|
+
// src/bin.ts
|
|
2
|
+
import dotenv from "dotenv";
|
|
3
|
+
import fs9 from "fs";
|
|
4
|
+
import path9 from "path";
|
|
5
|
+
|
|
1
6
|
// src/cli.ts
|
|
2
7
|
import { Command } from "commander";
|
|
3
|
-
import { spawn as
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
8
|
+
import { spawn as spawn3, execSync as execSync2 } from "child_process";
|
|
9
|
+
import fs8 from "fs";
|
|
10
|
+
import path8 from "path";
|
|
11
|
+
import os5 from "os";
|
|
7
12
|
|
|
8
13
|
// src/config.ts
|
|
9
14
|
import fs from "fs";
|
|
10
15
|
import path from "path";
|
|
11
16
|
import os from "os";
|
|
12
|
-
var CONFIG_DIR = path.join(os.homedir(), ".jarvis");
|
|
17
|
+
var CONFIG_DIR = process.env.JARVIS_CONFIG_DIR ?? path.join(os.homedir(), ".jarvis");
|
|
13
18
|
var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
14
19
|
function loadConfig() {
|
|
15
20
|
try {
|
|
@@ -2556,7 +2561,7 @@ function createUpstreamClient(config) {
|
|
|
2556
2561
|
if (!config.refreshToken) return null;
|
|
2557
2562
|
return config.apiUrl.replace(/^wss:/, "https:").replace(/^ws:/, "http:") + "/refresh";
|
|
2558
2563
|
}
|
|
2559
|
-
function
|
|
2564
|
+
function getTokenExpiry2(token) {
|
|
2560
2565
|
try {
|
|
2561
2566
|
const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64").toString());
|
|
2562
2567
|
return payload.exp ?? null;
|
|
@@ -2567,7 +2572,7 @@ function createUpstreamClient(config) {
|
|
|
2567
2572
|
function scheduleRefresh() {
|
|
2568
2573
|
if (refreshTimer) clearTimeout(refreshTimer);
|
|
2569
2574
|
if (!config.refreshToken) return;
|
|
2570
|
-
const exp =
|
|
2575
|
+
const exp = getTokenExpiry2(currentToken);
|
|
2571
2576
|
if (!exp) return;
|
|
2572
2577
|
const msUntilExpiry = exp * 1e3 - Date.now();
|
|
2573
2578
|
const refreshIn = Math.max(msUntilExpiry - REFRESH_BUFFER_MS, 0);
|
|
@@ -2627,6 +2632,7 @@ function createUpstreamClient(config) {
|
|
|
2627
2632
|
}
|
|
2628
2633
|
});
|
|
2629
2634
|
ws.on("close", (code) => {
|
|
2635
|
+
console.error("[Upstream] Connection closed, code:", code);
|
|
2630
2636
|
if (closed) return;
|
|
2631
2637
|
if (code === 4001 || code === 4003) {
|
|
2632
2638
|
if (config.refreshToken) {
|
|
@@ -2678,6 +2684,7 @@ function createUpstreamClient(config) {
|
|
|
2678
2684
|
reconnectTimer = setTimeout(connect, 3e3);
|
|
2679
2685
|
});
|
|
2680
2686
|
ws.on("error", (err) => {
|
|
2687
|
+
console.error("[Upstream] Connection error:", err.message);
|
|
2681
2688
|
logger.sys.error("[Upstream] Connection error", {
|
|
2682
2689
|
error: err.message
|
|
2683
2690
|
});
|
|
@@ -4796,26 +4803,453 @@ async function waitForAgent(port, maxAttempts = 30) {
|
|
|
4796
4803
|
}
|
|
4797
4804
|
}
|
|
4798
4805
|
|
|
4806
|
+
// src/service.ts
|
|
4807
|
+
import { execSync, spawn as spawn2 } from "child_process";
|
|
4808
|
+
import fs7 from "fs";
|
|
4809
|
+
import path7 from "path";
|
|
4810
|
+
import os4 from "os";
|
|
4811
|
+
function createServiceManager() {
|
|
4812
|
+
if (process.platform === "darwin") return new MacOSService();
|
|
4813
|
+
if (process.platform === "win32") return new WindowsService();
|
|
4814
|
+
if (process.platform === "linux") return new LinuxService();
|
|
4815
|
+
return new FallbackService();
|
|
4816
|
+
}
|
|
4817
|
+
var PLIST_LABEL = "com.appchy.jarvis";
|
|
4818
|
+
var PLIST_DIR = path7.join(os4.homedir(), "Library", "LaunchAgents");
|
|
4819
|
+
var PLIST_PATH = path7.join(PLIST_DIR, `${PLIST_LABEL}.plist`);
|
|
4820
|
+
function escapeXml(s) {
|
|
4821
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
4822
|
+
}
|
|
4823
|
+
var MacOSService = class {
|
|
4824
|
+
install(opts) {
|
|
4825
|
+
const args = [
|
|
4826
|
+
opts.nodePath,
|
|
4827
|
+
opts.entryPath,
|
|
4828
|
+
"start",
|
|
4829
|
+
"--foreground",
|
|
4830
|
+
"--port",
|
|
4831
|
+
String(opts.port),
|
|
4832
|
+
"--workspace",
|
|
4833
|
+
opts.workspacePath
|
|
4834
|
+
];
|
|
4835
|
+
if (!opts.upstream) {
|
|
4836
|
+
args.push("--no-upstream");
|
|
4837
|
+
}
|
|
4838
|
+
const logFile = path7.join(os4.homedir(), ".jarvis", "agent.log");
|
|
4839
|
+
const envPath = process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin";
|
|
4840
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
4841
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
4842
|
+
<plist version="1.0">
|
|
4843
|
+
<dict>
|
|
4844
|
+
<key>Label</key>
|
|
4845
|
+
<string>${PLIST_LABEL}</string>
|
|
4846
|
+
|
|
4847
|
+
<key>ProgramArguments</key>
|
|
4848
|
+
<array>
|
|
4849
|
+
${args.map((a) => ` <string>${escapeXml(a)}</string>`).join("\n")}
|
|
4850
|
+
</array>
|
|
4851
|
+
|
|
4852
|
+
<key>WorkingDirectory</key>
|
|
4853
|
+
<string>${escapeXml(opts.workspacePath)}</string>
|
|
4854
|
+
|
|
4855
|
+
<key>EnvironmentVariables</key>
|
|
4856
|
+
<dict>
|
|
4857
|
+
<key>PATH</key>
|
|
4858
|
+
<string>${escapeXml(envPath)}</string>
|
|
4859
|
+
<key>HOME</key>
|
|
4860
|
+
<string>${escapeXml(os4.homedir())}</string>
|
|
4861
|
+
<key>NODE_NO_WARNINGS</key>
|
|
4862
|
+
<string>1</string>
|
|
4863
|
+
</dict>
|
|
4864
|
+
|
|
4865
|
+
<key>RunAtLoad</key>
|
|
4866
|
+
<true/>
|
|
4867
|
+
|
|
4868
|
+
<key>KeepAlive</key>
|
|
4869
|
+
<true/>
|
|
4870
|
+
|
|
4871
|
+
<key>ProcessType</key>
|
|
4872
|
+
<string>Interactive</string>
|
|
4873
|
+
|
|
4874
|
+
<key>ThrottleInterval</key>
|
|
4875
|
+
<integer>5</integer>
|
|
4876
|
+
|
|
4877
|
+
<key>StandardOutPath</key>
|
|
4878
|
+
<string>${escapeXml(logFile)}</string>
|
|
4879
|
+
|
|
4880
|
+
<key>StandardErrorPath</key>
|
|
4881
|
+
<string>${escapeXml(logFile)}</string>
|
|
4882
|
+
</dict>
|
|
4883
|
+
</plist>
|
|
4884
|
+
`;
|
|
4885
|
+
fs7.mkdirSync(PLIST_DIR, { recursive: true });
|
|
4886
|
+
fs7.mkdirSync(path7.dirname(logFile), { recursive: true });
|
|
4887
|
+
if (this.isInstalled()) {
|
|
4888
|
+
try {
|
|
4889
|
+
execSync(`launchctl unload -w "${PLIST_PATH}"`, { stdio: "ignore" });
|
|
4890
|
+
} catch {
|
|
4891
|
+
}
|
|
4892
|
+
}
|
|
4893
|
+
fs7.writeFileSync(PLIST_PATH, plist);
|
|
4894
|
+
execSync(`launchctl load -w "${PLIST_PATH}"`);
|
|
4895
|
+
}
|
|
4896
|
+
uninstall() {
|
|
4897
|
+
try {
|
|
4898
|
+
execSync(`launchctl unload -w "${PLIST_PATH}"`, { stdio: "ignore" });
|
|
4899
|
+
} catch {
|
|
4900
|
+
}
|
|
4901
|
+
try {
|
|
4902
|
+
fs7.unlinkSync(PLIST_PATH);
|
|
4903
|
+
} catch {
|
|
4904
|
+
}
|
|
4905
|
+
}
|
|
4906
|
+
isInstalled() {
|
|
4907
|
+
return fs7.existsSync(PLIST_PATH);
|
|
4908
|
+
}
|
|
4909
|
+
start() {
|
|
4910
|
+
try {
|
|
4911
|
+
execSync(`launchctl load -w "${PLIST_PATH}"`);
|
|
4912
|
+
} catch {
|
|
4913
|
+
execSync(`launchctl start ${PLIST_LABEL}`);
|
|
4914
|
+
}
|
|
4915
|
+
}
|
|
4916
|
+
stop() {
|
|
4917
|
+
try {
|
|
4918
|
+
execSync(`launchctl unload -w "${PLIST_PATH}"`, { stdio: "ignore" });
|
|
4919
|
+
} catch {
|
|
4920
|
+
}
|
|
4921
|
+
}
|
|
4922
|
+
status() {
|
|
4923
|
+
const installed = this.isInstalled();
|
|
4924
|
+
if (!installed) return { installed: false, running: false };
|
|
4925
|
+
try {
|
|
4926
|
+
const output = execSync(`launchctl list ${PLIST_LABEL}`, {
|
|
4927
|
+
encoding: "utf-8",
|
|
4928
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
4929
|
+
});
|
|
4930
|
+
const pidMatch = output.match(/"PID"\s*=\s*(\d+)/);
|
|
4931
|
+
const pid = pidMatch ? parseInt(pidMatch[1], 10) : void 0;
|
|
4932
|
+
return { installed: true, running: pid !== void 0, pid, os: "macOS" };
|
|
4933
|
+
} catch {
|
|
4934
|
+
return { installed: true, running: false, os: "macOS" };
|
|
4935
|
+
}
|
|
4936
|
+
}
|
|
4937
|
+
};
|
|
4938
|
+
var TASK_NAME = "JarvisAgent";
|
|
4939
|
+
var WindowsService = class {
|
|
4940
|
+
install(opts) {
|
|
4941
|
+
const args = [
|
|
4942
|
+
opts.entryPath,
|
|
4943
|
+
"start",
|
|
4944
|
+
"--foreground",
|
|
4945
|
+
"--port",
|
|
4946
|
+
String(opts.port),
|
|
4947
|
+
"--workspace",
|
|
4948
|
+
`"${opts.workspacePath}"`
|
|
4949
|
+
];
|
|
4950
|
+
if (!opts.upstream) {
|
|
4951
|
+
args.push("--no-upstream");
|
|
4952
|
+
}
|
|
4953
|
+
const xml = `<?xml version="1.0" encoding="UTF-16"?>
|
|
4954
|
+
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
4955
|
+
<RegistrationInfo>
|
|
4956
|
+
<Description>Jarvis AI Agent \u2014 always-on background service</Description>
|
|
4957
|
+
</RegistrationInfo>
|
|
4958
|
+
<Triggers>
|
|
4959
|
+
<LogonTrigger>
|
|
4960
|
+
<Enabled>true</Enabled>
|
|
4961
|
+
</LogonTrigger>
|
|
4962
|
+
</Triggers>
|
|
4963
|
+
<Principals>
|
|
4964
|
+
<Principal id="Author">
|
|
4965
|
+
<LogonType>InteractiveToken</LogonType>
|
|
4966
|
+
<RunLevel>LeastPrivilege</RunLevel>
|
|
4967
|
+
</Principal>
|
|
4968
|
+
</Principals>
|
|
4969
|
+
<Settings>
|
|
4970
|
+
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
|
|
4971
|
+
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
|
4972
|
+
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
|
4973
|
+
<AllowHardTerminate>true</AllowHardTerminate>
|
|
4974
|
+
<StartWhenAvailable>true</StartWhenAvailable>
|
|
4975
|
+
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
|
|
4976
|
+
<AllowStartOnDemand>true</AllowStartOnDemand>
|
|
4977
|
+
<Enabled>true</Enabled>
|
|
4978
|
+
<Hidden>false</Hidden>
|
|
4979
|
+
<RunOnlyIfIdle>false</RunOnlyIfIdle>
|
|
4980
|
+
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
|
|
4981
|
+
<Priority>7</Priority>
|
|
4982
|
+
<RestartOnFailure>
|
|
4983
|
+
<Interval>PT1M</Interval>
|
|
4984
|
+
<Count>999</Count>
|
|
4985
|
+
</RestartOnFailure>
|
|
4986
|
+
</Settings>
|
|
4987
|
+
<Actions Context="Author">
|
|
4988
|
+
<Exec>
|
|
4989
|
+
<Command>${escapeXml(opts.nodePath)}</Command>
|
|
4990
|
+
<Arguments>${escapeXml(args.join(" "))}</Arguments>
|
|
4991
|
+
<WorkingDirectory>${escapeXml(opts.workspacePath)}</WorkingDirectory>
|
|
4992
|
+
</Exec>
|
|
4993
|
+
</Actions>
|
|
4994
|
+
</Task>
|
|
4995
|
+
`;
|
|
4996
|
+
const tmpDir = os4.tmpdir();
|
|
4997
|
+
const tmpFile = path7.join(tmpDir, `jarvis-task-${Date.now()}.xml`);
|
|
4998
|
+
fs7.writeFileSync(tmpFile, xml, { encoding: "utf-16le" });
|
|
4999
|
+
try {
|
|
5000
|
+
execSync(`schtasks /Create /TN "${TASK_NAME}" /XML "${tmpFile}" /F`, {
|
|
5001
|
+
stdio: "ignore"
|
|
5002
|
+
});
|
|
5003
|
+
} finally {
|
|
5004
|
+
try {
|
|
5005
|
+
fs7.unlinkSync(tmpFile);
|
|
5006
|
+
} catch {
|
|
5007
|
+
}
|
|
5008
|
+
}
|
|
5009
|
+
}
|
|
5010
|
+
uninstall() {
|
|
5011
|
+
try {
|
|
5012
|
+
execSync(`schtasks /Delete /TN "${TASK_NAME}" /F`, { stdio: "ignore" });
|
|
5013
|
+
} catch {
|
|
5014
|
+
}
|
|
5015
|
+
}
|
|
5016
|
+
isInstalled() {
|
|
5017
|
+
try {
|
|
5018
|
+
execSync(`schtasks /Query /TN "${TASK_NAME}"`, { stdio: "ignore" });
|
|
5019
|
+
return true;
|
|
5020
|
+
} catch {
|
|
5021
|
+
return false;
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
5024
|
+
start() {
|
|
5025
|
+
execSync(`schtasks /Run /TN "${TASK_NAME}"`, { stdio: "ignore" });
|
|
5026
|
+
}
|
|
5027
|
+
stop() {
|
|
5028
|
+
execSync(`schtasks /End /TN "${TASK_NAME}"`, { stdio: "ignore" });
|
|
5029
|
+
}
|
|
5030
|
+
status() {
|
|
5031
|
+
if (!this.isInstalled()) return { installed: false, running: false };
|
|
5032
|
+
try {
|
|
5033
|
+
const output = execSync(`schtasks /Query /TN "${TASK_NAME}" /FO CSV /NH`, {
|
|
5034
|
+
encoding: "utf-8"
|
|
5035
|
+
});
|
|
5036
|
+
const running = output.includes("Running");
|
|
5037
|
+
return { installed: true, running, os: "windows" };
|
|
5038
|
+
} catch {
|
|
5039
|
+
return { installed: true, running: false, os: "windows" };
|
|
5040
|
+
}
|
|
5041
|
+
}
|
|
5042
|
+
};
|
|
5043
|
+
var SYSTEMD_DIR = path7.join(os4.homedir(), ".config", "systemd", "user");
|
|
5044
|
+
var UNIT_NAME = "jarvis.service";
|
|
5045
|
+
var UNIT_PATH = path7.join(SYSTEMD_DIR, UNIT_NAME);
|
|
5046
|
+
var LinuxService = class {
|
|
5047
|
+
install(opts) {
|
|
5048
|
+
const args = [
|
|
5049
|
+
opts.entryPath,
|
|
5050
|
+
"start",
|
|
5051
|
+
"--foreground",
|
|
5052
|
+
"--port",
|
|
5053
|
+
String(opts.port),
|
|
5054
|
+
"--workspace",
|
|
5055
|
+
opts.workspacePath
|
|
5056
|
+
];
|
|
5057
|
+
if (!opts.upstream) {
|
|
5058
|
+
args.push("--no-upstream");
|
|
5059
|
+
}
|
|
5060
|
+
const logFile = path7.join(os4.homedir(), ".jarvis", "agent.log");
|
|
5061
|
+
const envPath = process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin";
|
|
5062
|
+
const unit = `[Unit]
|
|
5063
|
+
Description=Jarvis AI Agent
|
|
5064
|
+
After=network.target
|
|
5065
|
+
|
|
5066
|
+
[Service]
|
|
5067
|
+
Type=simple
|
|
5068
|
+
ExecStart=${opts.nodePath} ${args.join(" ")}
|
|
5069
|
+
WorkingDirectory=${opts.workspacePath}
|
|
5070
|
+
Environment=PATH=${envPath}
|
|
5071
|
+
Environment=HOME=${os4.homedir()}
|
|
5072
|
+
Environment=NODE_NO_WARNINGS=1
|
|
5073
|
+
Restart=always
|
|
5074
|
+
RestartSec=5
|
|
5075
|
+
StandardOutput=append:${logFile}
|
|
5076
|
+
StandardError=append:${logFile}
|
|
5077
|
+
|
|
5078
|
+
[Install]
|
|
5079
|
+
WantedBy=default.target
|
|
5080
|
+
`;
|
|
5081
|
+
fs7.mkdirSync(SYSTEMD_DIR, { recursive: true });
|
|
5082
|
+
fs7.mkdirSync(path7.dirname(logFile), { recursive: true });
|
|
5083
|
+
if (this.isInstalled()) {
|
|
5084
|
+
try {
|
|
5085
|
+
execSync("systemctl --user stop jarvis.service", { stdio: "ignore" });
|
|
5086
|
+
} catch {
|
|
5087
|
+
}
|
|
5088
|
+
}
|
|
5089
|
+
fs7.writeFileSync(UNIT_PATH, unit);
|
|
5090
|
+
execSync("systemctl --user daemon-reload", { stdio: "ignore" });
|
|
5091
|
+
execSync("systemctl --user enable jarvis.service", { stdio: "ignore" });
|
|
5092
|
+
execSync("systemctl --user start jarvis.service", { stdio: "ignore" });
|
|
5093
|
+
try {
|
|
5094
|
+
execSync(`loginctl enable-linger ${os4.userInfo().username}`, { stdio: "ignore" });
|
|
5095
|
+
} catch {
|
|
5096
|
+
}
|
|
5097
|
+
}
|
|
5098
|
+
uninstall() {
|
|
5099
|
+
try {
|
|
5100
|
+
execSync("systemctl --user stop jarvis.service", { stdio: "ignore" });
|
|
5101
|
+
} catch {
|
|
5102
|
+
}
|
|
5103
|
+
try {
|
|
5104
|
+
execSync("systemctl --user disable jarvis.service", { stdio: "ignore" });
|
|
5105
|
+
} catch {
|
|
5106
|
+
}
|
|
5107
|
+
try {
|
|
5108
|
+
fs7.unlinkSync(UNIT_PATH);
|
|
5109
|
+
} catch {
|
|
5110
|
+
}
|
|
5111
|
+
try {
|
|
5112
|
+
execSync("systemctl --user daemon-reload", { stdio: "ignore" });
|
|
5113
|
+
} catch {
|
|
5114
|
+
}
|
|
5115
|
+
}
|
|
5116
|
+
isInstalled() {
|
|
5117
|
+
return fs7.existsSync(UNIT_PATH);
|
|
5118
|
+
}
|
|
5119
|
+
start() {
|
|
5120
|
+
execSync("systemctl --user start jarvis.service");
|
|
5121
|
+
}
|
|
5122
|
+
stop() {
|
|
5123
|
+
execSync("systemctl --user stop jarvis.service", { stdio: "ignore" });
|
|
5124
|
+
}
|
|
5125
|
+
status() {
|
|
5126
|
+
if (!this.isInstalled()) return { installed: false, running: false };
|
|
5127
|
+
try {
|
|
5128
|
+
const output = execSync("systemctl --user show jarvis.service --property=ActiveState,MainPID", {
|
|
5129
|
+
encoding: "utf-8"
|
|
5130
|
+
});
|
|
5131
|
+
const active = output.includes("ActiveState=active");
|
|
5132
|
+
const pidMatch = output.match(/MainPID=(\d+)/);
|
|
5133
|
+
const pid = pidMatch ? parseInt(pidMatch[1], 10) : void 0;
|
|
5134
|
+
return {
|
|
5135
|
+
installed: true,
|
|
5136
|
+
running: active && pid !== void 0 && pid > 0,
|
|
5137
|
+
pid: pid && pid > 0 ? pid : void 0,
|
|
5138
|
+
os: "linux"
|
|
5139
|
+
};
|
|
5140
|
+
} catch {
|
|
5141
|
+
return { installed: true, running: false, os: "linux" };
|
|
5142
|
+
}
|
|
5143
|
+
}
|
|
5144
|
+
};
|
|
5145
|
+
var FallbackService = class {
|
|
5146
|
+
constructor() {
|
|
5147
|
+
this.pidFile = path7.join(os4.homedir(), ".jarvis", "agent.pid");
|
|
5148
|
+
this.logFile = path7.join(os4.homedir(), ".jarvis", "agent.log");
|
|
5149
|
+
this.markerFile = path7.join(os4.homedir(), ".jarvis", "service-installed");
|
|
5150
|
+
}
|
|
5151
|
+
install(opts) {
|
|
5152
|
+
const jarvisDir = path7.join(os4.homedir(), ".jarvis");
|
|
5153
|
+
fs7.mkdirSync(jarvisDir, { recursive: true });
|
|
5154
|
+
this.stop();
|
|
5155
|
+
const args = [
|
|
5156
|
+
opts.entryPath,
|
|
5157
|
+
"start",
|
|
5158
|
+
"--foreground",
|
|
5159
|
+
"--port",
|
|
5160
|
+
String(opts.port),
|
|
5161
|
+
"--workspace",
|
|
5162
|
+
opts.workspacePath
|
|
5163
|
+
];
|
|
5164
|
+
if (!opts.upstream) {
|
|
5165
|
+
args.push("--no-upstream");
|
|
5166
|
+
}
|
|
5167
|
+
const logFd = fs7.openSync(this.logFile, "a");
|
|
5168
|
+
const child = spawn2(opts.nodePath, args, {
|
|
5169
|
+
detached: true,
|
|
5170
|
+
stdio: ["ignore", logFd, logFd],
|
|
5171
|
+
cwd: opts.workspacePath,
|
|
5172
|
+
env: { ...process.env, NODE_NO_WARNINGS: "1" }
|
|
5173
|
+
});
|
|
5174
|
+
child.unref();
|
|
5175
|
+
fs7.closeSync(logFd);
|
|
5176
|
+
fs7.writeFileSync(this.pidFile, String(child.pid));
|
|
5177
|
+
fs7.writeFileSync(this.markerFile, JSON.stringify({
|
|
5178
|
+
nodePath: opts.nodePath,
|
|
5179
|
+
entryPath: opts.entryPath,
|
|
5180
|
+
port: opts.port,
|
|
5181
|
+
workspacePath: opts.workspacePath,
|
|
5182
|
+
upstream: opts.upstream
|
|
5183
|
+
}));
|
|
5184
|
+
console.log(
|
|
5185
|
+
`\x1B[33mNote:\x1B[0m OS-level service not available on ${process.platform}.`
|
|
5186
|
+
);
|
|
5187
|
+
console.log(" Agent is running as a background process (PID: " + child.pid + ").");
|
|
5188
|
+
console.log(" It will NOT auto-start on reboot or recover from crashes.");
|
|
5189
|
+
}
|
|
5190
|
+
uninstall() {
|
|
5191
|
+
this.stop();
|
|
5192
|
+
try {
|
|
5193
|
+
fs7.unlinkSync(this.markerFile);
|
|
5194
|
+
} catch {
|
|
5195
|
+
}
|
|
5196
|
+
}
|
|
5197
|
+
isInstalled() {
|
|
5198
|
+
return fs7.existsSync(this.markerFile);
|
|
5199
|
+
}
|
|
5200
|
+
start() {
|
|
5201
|
+
if (!this.isInstalled()) {
|
|
5202
|
+
throw new Error("Service not installed. Run 'jarvis install' first.");
|
|
5203
|
+
}
|
|
5204
|
+
const saved = JSON.parse(fs7.readFileSync(this.markerFile, "utf-8"));
|
|
5205
|
+
this.install(saved);
|
|
5206
|
+
}
|
|
5207
|
+
stop() {
|
|
5208
|
+
try {
|
|
5209
|
+
const pid = parseInt(fs7.readFileSync(this.pidFile, "utf-8").trim(), 10);
|
|
5210
|
+
if (!isNaN(pid)) {
|
|
5211
|
+
process.kill(pid, "SIGTERM");
|
|
5212
|
+
}
|
|
5213
|
+
} catch {
|
|
5214
|
+
}
|
|
5215
|
+
try {
|
|
5216
|
+
fs7.unlinkSync(this.pidFile);
|
|
5217
|
+
} catch {
|
|
5218
|
+
}
|
|
5219
|
+
}
|
|
5220
|
+
status() {
|
|
5221
|
+
if (!this.isInstalled()) return { installed: false, running: false };
|
|
5222
|
+
try {
|
|
5223
|
+
const pid = parseInt(fs7.readFileSync(this.pidFile, "utf-8").trim(), 10);
|
|
5224
|
+
if (isNaN(pid)) return { installed: true, running: false };
|
|
5225
|
+
process.kill(pid, 0);
|
|
5226
|
+
return { installed: true, running: true, pid };
|
|
5227
|
+
} catch {
|
|
5228
|
+
return { installed: true, running: false };
|
|
5229
|
+
}
|
|
5230
|
+
}
|
|
5231
|
+
};
|
|
5232
|
+
|
|
4799
5233
|
// src/cli.ts
|
|
4800
5234
|
import { createRequire } from "module";
|
|
4801
5235
|
var _require = createRequire(import.meta.url);
|
|
4802
5236
|
var PKG_VERSION = _require("../package.json").version ?? "dev";
|
|
4803
|
-
var LOG_DIR =
|
|
4804
|
-
var LOG_FILE =
|
|
4805
|
-
var PID_FILE =
|
|
5237
|
+
var LOG_DIR = path8.join(os5.homedir(), ".jarvis");
|
|
5238
|
+
var LOG_FILE = path8.join(LOG_DIR, "agent.log");
|
|
5239
|
+
var PID_FILE = path8.join(LOG_DIR, "agent.pid");
|
|
4806
5240
|
function savePid(pid) {
|
|
4807
|
-
|
|
4808
|
-
|
|
5241
|
+
fs8.mkdirSync(LOG_DIR, { recursive: true });
|
|
5242
|
+
fs8.writeFileSync(PID_FILE, String(pid));
|
|
4809
5243
|
}
|
|
4810
5244
|
function readPid() {
|
|
4811
5245
|
try {
|
|
4812
|
-
const pid = parseInt(
|
|
5246
|
+
const pid = parseInt(fs8.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
4813
5247
|
if (isNaN(pid)) return null;
|
|
4814
5248
|
try {
|
|
4815
5249
|
process.kill(pid, 0);
|
|
4816
5250
|
return pid;
|
|
4817
5251
|
} catch {
|
|
4818
|
-
|
|
5252
|
+
fs8.unlinkSync(PID_FILE);
|
|
4819
5253
|
return null;
|
|
4820
5254
|
}
|
|
4821
5255
|
} catch {
|
|
@@ -4824,7 +5258,7 @@ function readPid() {
|
|
|
4824
5258
|
}
|
|
4825
5259
|
function clearPid() {
|
|
4826
5260
|
try {
|
|
4827
|
-
|
|
5261
|
+
fs8.unlinkSync(PID_FILE);
|
|
4828
5262
|
} catch {
|
|
4829
5263
|
}
|
|
4830
5264
|
}
|
|
@@ -4874,7 +5308,7 @@ async function findFreePort() {
|
|
|
4874
5308
|
async function browserAuth(appUrl) {
|
|
4875
5309
|
const config = loadConfig();
|
|
4876
5310
|
const callbackPort = await findFreePort();
|
|
4877
|
-
const loginUrl = `${appUrl}/
|
|
5311
|
+
const loginUrl = `${appUrl}/api/auth/cli?port=${callbackPort}`;
|
|
4878
5312
|
console.log();
|
|
4879
5313
|
console.log("Opening browser for sign in...");
|
|
4880
5314
|
console.log(` If it doesn't open, visit: ${loginUrl}`);
|
|
@@ -4904,34 +5338,61 @@ async function browserAuth(appUrl) {
|
|
|
4904
5338
|
return false;
|
|
4905
5339
|
}
|
|
4906
5340
|
}
|
|
4907
|
-
|
|
5341
|
+
function getAppUrl() {
|
|
5342
|
+
return process.env.APP_URL ?? "https://jarvis.appchy.com" ?? "https://jarvis.appchy.com";
|
|
5343
|
+
}
|
|
5344
|
+
function getTokenExpiry(token) {
|
|
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
|
+
}
|
|
5372
|
+
}
|
|
4908
5373
|
async function ensureSetup() {
|
|
4909
5374
|
const config = loadConfig();
|
|
4910
|
-
if (config?.token
|
|
5375
|
+
if (!config?.token && !config?.anthropicApiKey && !config?.useSubscription) {
|
|
5376
|
+
return browserAuth(getAppUrl());
|
|
5377
|
+
}
|
|
5378
|
+
if (config?.anthropicApiKey || config?.useSubscription) {
|
|
4911
5379
|
return true;
|
|
4912
5380
|
}
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
5381
|
+
if (config?.token && isTokenExpired(config.token)) {
|
|
5382
|
+
console.log("Token expired, attempting refresh...");
|
|
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;
|
|
4920
5388
|
}
|
|
4921
|
-
|
|
4922
|
-
|
|
5389
|
+
console.log("Token refresh failed. Re-authenticating...");
|
|
5390
|
+
return browserAuth(getAppUrl());
|
|
4923
5391
|
}
|
|
4924
5392
|
return true;
|
|
4925
5393
|
}
|
|
4926
5394
|
function createCli() {
|
|
4927
5395
|
const program = new Command().name("jarvis").description("Jarvis local agent \u2014 runs Claude Code on your machine").version(PKG_VERSION);
|
|
4928
|
-
program.argument("[message]", "Optional initial message to send").option("--model <id>", "Model (e.g., claude-opus-4-20250514)").option("--mode <mode>", "Permission mode: supervised|auto|plan|yolo").option("--thinking <config>", "Thinking: adaptive|enabled|disabled").option("--thinking-budget <n>", "Token budget when thinking=enabled").option("--max-turns <n>", "Max turns per task").option("-p, --port <port>", "Agent server port", "7862").option("-w, --workspace <path>", "Workspace root path").option("--no-server", "Connect to existing server, don't auto-start").action(async (message, opts) => {
|
|
4929
|
-
if (message && program.commands.some((c) => c.name() === message)) {
|
|
4930
|
-
return;
|
|
4931
|
-
}
|
|
4932
|
-
await ensureSetup();
|
|
4933
|
-
await launchChat(opts);
|
|
4934
|
-
});
|
|
4935
5396
|
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) => {
|
|
4936
5397
|
try {
|
|
4937
5398
|
const parsed = parseConnectToken(token);
|
|
@@ -4958,86 +5419,133 @@ function createCli() {
|
|
|
4958
5419
|
process.exit(1);
|
|
4959
5420
|
}
|
|
4960
5421
|
});
|
|
4961
|
-
program.command("start").description("Start the local agent (
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
5422
|
+
program.command("start").description("Start the local agent (authenticates, cleans up stale processes, starts fresh)").option("-p, --port <port>", "Local WS server port", "7862").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)").action(
|
|
5423
|
+
async (opts) => {
|
|
5424
|
+
if (!opts.foreground) {
|
|
5425
|
+
const ok = await ensureSetup();
|
|
5426
|
+
if (!ok) {
|
|
5427
|
+
console.error("Setup failed. Cannot start agent.");
|
|
5428
|
+
process.exit(1);
|
|
5429
|
+
}
|
|
5430
|
+
}
|
|
5431
|
+
const config = loadConfig();
|
|
5432
|
+
const port = parseInt(opts.port, 10);
|
|
5433
|
+
const workspacePath = opts.workspace ?? config?.workspacePath ?? process.cwd();
|
|
5434
|
+
const userId = config?.userId ?? process.env.JARVIS_USER_ID ?? "local";
|
|
5435
|
+
const explicitApiKey = opts.apiKey ?? config?.anthropicApiKey;
|
|
5436
|
+
const useSubscription = !explicitApiKey;
|
|
5437
|
+
const anthropicApiKey = explicitApiKey;
|
|
5438
|
+
if (opts.foreground) {
|
|
5439
|
+
if (useSubscription && process.env.ANTHROPIC_API_KEY) {
|
|
5440
|
+
delete process.env.ANTHROPIC_API_KEY;
|
|
5441
|
+
}
|
|
5442
|
+
await startAgent({
|
|
5443
|
+
port,
|
|
5444
|
+
workspacePath,
|
|
5445
|
+
anthropicApiKey,
|
|
5446
|
+
useSubscription,
|
|
5447
|
+
userId,
|
|
5448
|
+
upstream: (() => {
|
|
5449
|
+
const apiUrl = process.env.JARVIS_UPSTREAM_URL ?? config?.apiUrl;
|
|
5450
|
+
const token = process.env.JARVIS_UPSTREAM_TOKEN ?? config?.token;
|
|
5451
|
+
if (opts.upstream && apiUrl && token) {
|
|
5452
|
+
return { apiUrl, token, refreshToken: config?.refreshToken };
|
|
5453
|
+
}
|
|
5454
|
+
return void 0;
|
|
5455
|
+
})()
|
|
5456
|
+
});
|
|
5457
|
+
return;
|
|
5458
|
+
}
|
|
5459
|
+
const service = createServiceManager();
|
|
5460
|
+
if (service.isInstalled()) {
|
|
5461
|
+
try {
|
|
5462
|
+
service.stop();
|
|
5463
|
+
} catch {
|
|
5464
|
+
}
|
|
5465
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
5466
|
+
service.start();
|
|
5467
|
+
console.log("Jarvis agent started via OS service.");
|
|
5468
|
+
return;
|
|
5469
|
+
}
|
|
5470
|
+
const stalePid = readPid();
|
|
5471
|
+
if (stalePid) {
|
|
5472
|
+
try {
|
|
5473
|
+
process.kill(stalePid, "SIGTERM");
|
|
5474
|
+
} catch {
|
|
5475
|
+
}
|
|
5476
|
+
clearPid();
|
|
5477
|
+
}
|
|
5478
|
+
if (await isPortInUse(port)) {
|
|
5479
|
+
try {
|
|
5480
|
+
const output = execSync2(`lsof -ti :${port}`, { encoding: "utf-8" }).trim();
|
|
5481
|
+
if (output) {
|
|
5482
|
+
for (const p of output.split("\n")) {
|
|
5483
|
+
try {
|
|
5484
|
+
process.kill(parseInt(p, 10), "SIGTERM");
|
|
5485
|
+
} catch {
|
|
5486
|
+
}
|
|
5487
|
+
}
|
|
4988
5488
|
}
|
|
4989
|
-
|
|
4990
|
-
}
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5489
|
+
} catch {
|
|
5490
|
+
}
|
|
5491
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
5492
|
+
}
|
|
5493
|
+
fs8.mkdirSync(LOG_DIR, { recursive: true });
|
|
5494
|
+
const logFd = fs8.openSync(LOG_FILE, "a");
|
|
5495
|
+
const args = [
|
|
5496
|
+
"start",
|
|
5497
|
+
"--foreground",
|
|
5498
|
+
"--port",
|
|
5499
|
+
String(port),
|
|
5500
|
+
"--workspace",
|
|
5501
|
+
workspacePath
|
|
5502
|
+
];
|
|
5503
|
+
if (!opts.upstream) {
|
|
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: "" } : {}
|
|
5525
|
+
}
|
|
5526
|
+
}
|
|
5527
|
+
);
|
|
5528
|
+
child.unref();
|
|
5529
|
+
fs8.closeSync(logFd);
|
|
5530
|
+
savePid(child.pid);
|
|
5531
|
+
console.log(`Jarvis agent started (PID: ${child.pid})`);
|
|
5532
|
+
console.log(` Local: ws://127.0.0.1:${port}`);
|
|
5533
|
+
console.log(` Workspace: ${workspacePath}`);
|
|
5534
|
+
console.log(` Logs: ${LOG_FILE}`);
|
|
5535
|
+
if (config?.apiUrl) {
|
|
5536
|
+
console.log(` Cloud: ${config.apiUrl}`);
|
|
5022
5537
|
}
|
|
5023
|
-
);
|
|
5024
|
-
child.unref();
|
|
5025
|
-
fs7.closeSync(logFd);
|
|
5026
|
-
savePid(child.pid);
|
|
5027
|
-
console.log(`Jarvis agent started (PID: ${child.pid})`);
|
|
5028
|
-
console.log(` Local: ws://127.0.0.1:${port}`);
|
|
5029
|
-
console.log(` Workspace: ${workspacePath}`);
|
|
5030
|
-
console.log(` Logs: ${LOG_FILE}`);
|
|
5031
|
-
if (config?.apiUrl) {
|
|
5032
|
-
console.log(` Cloud: ${config.apiUrl}`);
|
|
5033
5538
|
}
|
|
5034
|
-
|
|
5035
|
-
console.log(`Commands:`);
|
|
5036
|
-
console.log(` jarvis logs \u2014 View agent logs`);
|
|
5037
|
-
console.log(` jarvis stop \u2014 Stop the agent`);
|
|
5038
|
-
console.log(` jarvis restart \u2014 Restart the agent`);
|
|
5039
|
-
});
|
|
5539
|
+
);
|
|
5040
5540
|
program.command("stop").description("Stop the running agent").action(async () => {
|
|
5541
|
+
const service = createServiceManager();
|
|
5542
|
+
if (service.isInstalled()) {
|
|
5543
|
+
service.stop();
|
|
5544
|
+
clearPid();
|
|
5545
|
+
console.log("Agent stopped via OS service.");
|
|
5546
|
+
console.log("Use 'jarvis start' to re-enable, or 'jarvis uninstall' to remove auto-start.");
|
|
5547
|
+
return;
|
|
5548
|
+
}
|
|
5041
5549
|
const pid = readPid();
|
|
5042
5550
|
if (pid) {
|
|
5043
5551
|
try {
|
|
@@ -5053,7 +5561,7 @@ function createCli() {
|
|
|
5053
5561
|
const running = await isPortInUse(7862);
|
|
5054
5562
|
if (running) {
|
|
5055
5563
|
try {
|
|
5056
|
-
const output =
|
|
5564
|
+
const output = execSync2("lsof -ti :7862", { encoding: "utf-8" }).trim();
|
|
5057
5565
|
if (output) {
|
|
5058
5566
|
const pids = output.split("\n");
|
|
5059
5567
|
for (const p of pids) {
|
|
@@ -5070,27 +5578,16 @@ function createCli() {
|
|
|
5070
5578
|
}
|
|
5071
5579
|
console.log("No agent is running.");
|
|
5072
5580
|
});
|
|
5073
|
-
program.command("restart").description("Restart the agent").action(async () => {
|
|
5074
|
-
const pid = readPid();
|
|
5075
|
-
if (pid) {
|
|
5076
|
-
try {
|
|
5077
|
-
process.kill(pid, "SIGTERM");
|
|
5078
|
-
} catch {
|
|
5079
|
-
}
|
|
5080
|
-
clearPid();
|
|
5081
|
-
console.log(`Stopped agent (PID: ${pid})`);
|
|
5082
|
-
await new Promise((r) => setTimeout(r, 1e3));
|
|
5083
|
-
}
|
|
5084
|
-
console.log("Starting agent...");
|
|
5581
|
+
program.command("restart").description("Restart the agent (alias for start \u2014 start always does a clean restart)").action(async () => {
|
|
5085
5582
|
await program.parseAsync(["node", "jarvis", "start"]);
|
|
5086
5583
|
});
|
|
5087
5584
|
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) => {
|
|
5088
|
-
if (!
|
|
5585
|
+
if (!fs8.existsSync(LOG_FILE)) {
|
|
5089
5586
|
console.log("No log file found. Start the agent first: jarvis start");
|
|
5090
5587
|
return;
|
|
5091
5588
|
}
|
|
5092
5589
|
if (opts.follow) {
|
|
5093
|
-
const tail =
|
|
5590
|
+
const tail = spawn3("tail", ["-f", "-n", opts.lines, LOG_FILE], {
|
|
5094
5591
|
stdio: "inherit"
|
|
5095
5592
|
});
|
|
5096
5593
|
process.on("SIGINT", () => {
|
|
@@ -5099,7 +5596,7 @@ function createCli() {
|
|
|
5099
5596
|
});
|
|
5100
5597
|
tail.on("exit", () => process.exit(0));
|
|
5101
5598
|
} else {
|
|
5102
|
-
const content =
|
|
5599
|
+
const content = execSync2(`tail -n ${opts.lines} "${LOG_FILE}"`, { encoding: "utf-8" });
|
|
5103
5600
|
process.stdout.write(content);
|
|
5104
5601
|
}
|
|
5105
5602
|
});
|
|
@@ -5109,7 +5606,9 @@ function createCli() {
|
|
|
5109
5606
|
const port = config?.port ?? 7862;
|
|
5110
5607
|
const running = await isPortInUse(port);
|
|
5111
5608
|
console.log(`Jarvis Agent v${PKG_VERSION}:`);
|
|
5112
|
-
console.log(
|
|
5609
|
+
console.log(
|
|
5610
|
+
` Status: ${running ? `\x1B[32mrunning\x1B[0m` : `\x1B[31mstopped\x1B[0m`}${pid ? ` (PID: ${pid})` : ""}`
|
|
5611
|
+
);
|
|
5113
5612
|
console.log(` Port: ${port}`);
|
|
5114
5613
|
console.log(` Config: ${getConfigPath()}`);
|
|
5115
5614
|
if (config) {
|
|
@@ -5123,15 +5622,105 @@ function createCli() {
|
|
|
5123
5622
|
} else {
|
|
5124
5623
|
console.log(` Config: Not set up. Run 'jarvis connect <token>'`);
|
|
5125
5624
|
}
|
|
5625
|
+
const svc = createServiceManager();
|
|
5626
|
+
const svcStatus = svc.status();
|
|
5627
|
+
if (svcStatus.installed) {
|
|
5628
|
+
const label = svcStatus.os ?? "unknown";
|
|
5629
|
+
const state2 = svcStatus.running ? `\x1B[32minstalled & running\x1B[0m (${label})` : `\x1B[33minstalled, stopped\x1B[0m (${label})`;
|
|
5630
|
+
console.log(` Service: ${state2}`);
|
|
5631
|
+
} else {
|
|
5632
|
+
console.log(` Service: not installed (run 'jarvis install' for always-on)`);
|
|
5633
|
+
}
|
|
5126
5634
|
console.log(` Logs: ${LOG_FILE}`);
|
|
5127
5635
|
});
|
|
5636
|
+
program.command("install").description("Install as OS service for auto-start on login and crash recovery").option("-p, --port <port>", "Local WS server port", "7862").option("-w, --workspace <path>", "Workspace root path").option("--no-upstream", "Don't connect to cloud").action(async (opts) => {
|
|
5637
|
+
await ensureSetup();
|
|
5638
|
+
const config = loadConfig();
|
|
5639
|
+
const port = parseInt(opts.port, 10);
|
|
5640
|
+
const workspacePath = opts.workspace ?? config?.workspacePath ?? process.cwd();
|
|
5641
|
+
const service = createServiceManager();
|
|
5642
|
+
const binPath = process.argv[1];
|
|
5643
|
+
const cliRoot = path8.resolve(path8.dirname(binPath), "..");
|
|
5644
|
+
const distEntry = path8.join(cliRoot, "dist", "bin.js");
|
|
5645
|
+
const entryPath = fs8.existsSync(distEntry) ? distEntry : binPath;
|
|
5646
|
+
if (!fs8.existsSync(distEntry)) {
|
|
5647
|
+
console.log("\x1B[33mWarning:\x1B[0m Using dev mode entry point (tsx).");
|
|
5648
|
+
console.log(" For reliability, build first: pnpm build");
|
|
5649
|
+
console.log();
|
|
5650
|
+
}
|
|
5651
|
+
const pid = readPid();
|
|
5652
|
+
if (pid) {
|
|
5653
|
+
try {
|
|
5654
|
+
process.kill(pid, "SIGTERM");
|
|
5655
|
+
} catch {
|
|
5656
|
+
}
|
|
5657
|
+
clearPid();
|
|
5658
|
+
}
|
|
5659
|
+
try {
|
|
5660
|
+
service.install({
|
|
5661
|
+
port,
|
|
5662
|
+
workspacePath,
|
|
5663
|
+
nodePath: process.execPath,
|
|
5664
|
+
entryPath,
|
|
5665
|
+
upstream: opts.upstream
|
|
5666
|
+
});
|
|
5667
|
+
} catch (err) {
|
|
5668
|
+
console.error(`Failed to install service: ${err instanceof Error ? err.message : err}`);
|
|
5669
|
+
process.exit(1);
|
|
5670
|
+
}
|
|
5671
|
+
const status = service.status();
|
|
5672
|
+
console.log(`Jarvis agent installed as OS service`);
|
|
5673
|
+
console.log(` Platform: ${status.os}`);
|
|
5674
|
+
console.log(` Port: ${port}`);
|
|
5675
|
+
console.log(` Workspace: ${workspacePath}`);
|
|
5676
|
+
console.log(` Auto-start on login: \x1B[32menabled\x1B[0m`);
|
|
5677
|
+
console.log(` Crash recovery: \x1B[32menabled\x1B[0m`);
|
|
5678
|
+
console.log();
|
|
5679
|
+
console.log(`Commands:`);
|
|
5680
|
+
console.log(` jarvis status \u2014 Check service status`);
|
|
5681
|
+
console.log(` jarvis uninstall \u2014 Remove OS service`);
|
|
5682
|
+
});
|
|
5683
|
+
program.command("uninstall").description("Remove OS service (stops auto-start and crash recovery)").action(() => {
|
|
5684
|
+
const service = createServiceManager();
|
|
5685
|
+
if (!service.isInstalled()) {
|
|
5686
|
+
console.log("No service is installed. Nothing to do.");
|
|
5687
|
+
return;
|
|
5688
|
+
}
|
|
5689
|
+
try {
|
|
5690
|
+
service.stop();
|
|
5691
|
+
} catch {
|
|
5692
|
+
}
|
|
5693
|
+
service.uninstall();
|
|
5694
|
+
clearPid();
|
|
5695
|
+
console.log("Jarvis OS service removed.");
|
|
5696
|
+
console.log("The agent will no longer auto-start on login or restart on crash.");
|
|
5697
|
+
});
|
|
5128
5698
|
program.command("logout").description("Clear saved configuration").action(() => {
|
|
5129
5699
|
clearConfig();
|
|
5130
5700
|
console.log("Configuration cleared.");
|
|
5131
5701
|
});
|
|
5702
|
+
program.command("chat", { isDefault: true, hidden: true }).description("Interactive TUI").option("--model <id>", "Model (e.g., claude-opus-4-20250514)").option("--mode <mode>", "Permission mode: supervised|auto|plan|yolo").option("--thinking <config>", "Thinking: adaptive|enabled|disabled").option("--thinking-budget <n>", "Token budget when thinking=enabled").option("--max-turns <n>", "Max turns per task").option("-p, --port <port>", "Agent server port", "7862").option("-w, --workspace <path>", "Workspace root path").option("--no-server", "Connect to existing server, don't auto-start").action(async (opts) => {
|
|
5703
|
+
await ensureSetup();
|
|
5704
|
+
await launchChat(opts);
|
|
5705
|
+
});
|
|
5132
5706
|
return program;
|
|
5133
5707
|
}
|
|
5134
5708
|
|
|
5135
5709
|
// src/bin.ts
|
|
5710
|
+
function findEnv() {
|
|
5711
|
+
let dir = process.cwd();
|
|
5712
|
+
while (dir !== path9.dirname(dir)) {
|
|
5713
|
+
const envPath = path9.join(dir, ".env");
|
|
5714
|
+
if (fs9.existsSync(envPath)) return envPath;
|
|
5715
|
+
dir = path9.dirname(dir);
|
|
5716
|
+
}
|
|
5717
|
+
return void 0;
|
|
5718
|
+
}
|
|
5719
|
+
var envFile = findEnv();
|
|
5720
|
+
if (envFile) {
|
|
5721
|
+
const prev = process.env.ANTHROPIC_API_KEY;
|
|
5722
|
+
dotenv.config({ path: envFile });
|
|
5723
|
+
if (prev === void 0) delete process.env.ANTHROPIC_API_KEY;
|
|
5724
|
+
}
|
|
5136
5725
|
createCli().parse();
|
|
5137
5726
|
//# sourceMappingURL=bin.js.map
|