@appchy/jarvis 0.1.14 → 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 +705 -127
- package/dist/bin.js.map +1 -1
- package/package.json +3 -3
package/dist/bin.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
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";
|
|
@@ -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);
|
|
@@ -4798,26 +4803,453 @@ async function waitForAgent(port, maxAttempts = 30) {
|
|
|
4798
4803
|
}
|
|
4799
4804
|
}
|
|
4800
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
|
+
|
|
4801
5233
|
// src/cli.ts
|
|
4802
5234
|
import { createRequire } from "module";
|
|
4803
5235
|
var _require = createRequire(import.meta.url);
|
|
4804
5236
|
var PKG_VERSION = _require("../package.json").version ?? "dev";
|
|
4805
|
-
var LOG_DIR =
|
|
4806
|
-
var LOG_FILE =
|
|
4807
|
-
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");
|
|
4808
5240
|
function savePid(pid) {
|
|
4809
|
-
|
|
4810
|
-
|
|
5241
|
+
fs8.mkdirSync(LOG_DIR, { recursive: true });
|
|
5242
|
+
fs8.writeFileSync(PID_FILE, String(pid));
|
|
4811
5243
|
}
|
|
4812
5244
|
function readPid() {
|
|
4813
5245
|
try {
|
|
4814
|
-
const pid = parseInt(
|
|
5246
|
+
const pid = parseInt(fs8.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
4815
5247
|
if (isNaN(pid)) return null;
|
|
4816
5248
|
try {
|
|
4817
5249
|
process.kill(pid, 0);
|
|
4818
5250
|
return pid;
|
|
4819
5251
|
} catch {
|
|
4820
|
-
|
|
5252
|
+
fs8.unlinkSync(PID_FILE);
|
|
4821
5253
|
return null;
|
|
4822
5254
|
}
|
|
4823
5255
|
} catch {
|
|
@@ -4826,7 +5258,7 @@ function readPid() {
|
|
|
4826
5258
|
}
|
|
4827
5259
|
function clearPid() {
|
|
4828
5260
|
try {
|
|
4829
|
-
|
|
5261
|
+
fs8.unlinkSync(PID_FILE);
|
|
4830
5262
|
} catch {
|
|
4831
5263
|
}
|
|
4832
5264
|
}
|
|
@@ -4876,7 +5308,7 @@ async function findFreePort() {
|
|
|
4876
5308
|
async function browserAuth(appUrl) {
|
|
4877
5309
|
const config = loadConfig();
|
|
4878
5310
|
const callbackPort = await findFreePort();
|
|
4879
|
-
const loginUrl = `${appUrl}/
|
|
5311
|
+
const loginUrl = `${appUrl}/api/auth/cli?port=${callbackPort}`;
|
|
4880
5312
|
console.log();
|
|
4881
5313
|
console.log("Opening browser for sign in...");
|
|
4882
5314
|
console.log(` If it doesn't open, visit: ${loginUrl}`);
|
|
@@ -4906,22 +5338,56 @@ async function browserAuth(appUrl) {
|
|
|
4906
5338
|
return false;
|
|
4907
5339
|
}
|
|
4908
5340
|
}
|
|
4909
|
-
|
|
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
|
+
}
|
|
4910
5373
|
async function ensureSetup() {
|
|
4911
5374
|
const config = loadConfig();
|
|
4912
|
-
if (config?.token
|
|
5375
|
+
if (!config?.token && !config?.anthropicApiKey && !config?.useSubscription) {
|
|
5376
|
+
return browserAuth(getAppUrl());
|
|
5377
|
+
}
|
|
5378
|
+
if (config?.anthropicApiKey || config?.useSubscription) {
|
|
4913
5379
|
return true;
|
|
4914
5380
|
}
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
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;
|
|
4922
5388
|
}
|
|
4923
|
-
|
|
4924
|
-
|
|
5389
|
+
console.log("Token refresh failed. Re-authenticating...");
|
|
5390
|
+
return browserAuth(getAppUrl());
|
|
4925
5391
|
}
|
|
4926
5392
|
return true;
|
|
4927
5393
|
}
|
|
@@ -4953,98 +5419,133 @@ function createCli() {
|
|
|
4953
5419
|
process.exit(1);
|
|
4954
5420
|
}
|
|
4955
5421
|
});
|
|
4956
|
-
program.command("start").description("Start the local agent (
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
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
|
+
});
|
|
4967
5457
|
return;
|
|
4968
5458
|
}
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
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;
|
|
4978
5469
|
}
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
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
|
+
}
|
|
4990
5488
|
}
|
|
4991
|
-
|
|
4992
|
-
})()
|
|
4993
|
-
});
|
|
4994
|
-
return;
|
|
4995
|
-
}
|
|
4996
|
-
fs7.mkdirSync(LOG_DIR, { recursive: true });
|
|
4997
|
-
const logFd = fs7.openSync(LOG_FILE, "a");
|
|
4998
|
-
const args = [
|
|
4999
|
-
"start",
|
|
5000
|
-
"--foreground",
|
|
5001
|
-
"--port",
|
|
5002
|
-
String(port),
|
|
5003
|
-
"--workspace",
|
|
5004
|
-
workspacePath
|
|
5005
|
-
];
|
|
5006
|
-
if (!opts.upstream) {
|
|
5007
|
-
args.push("--no-upstream");
|
|
5008
|
-
}
|
|
5009
|
-
if (anthropicApiKey) {
|
|
5010
|
-
args.push("--api-key", anthropicApiKey);
|
|
5011
|
-
}
|
|
5012
|
-
const binPath = process.argv[1];
|
|
5013
|
-
const cliRoot = path7.resolve(path7.dirname(binPath), "..");
|
|
5014
|
-
const distEntry = path7.join(cliRoot, "dist", "bin.js");
|
|
5015
|
-
const useDistEntry = fs7.existsSync(distEntry);
|
|
5016
|
-
const child = spawn2(
|
|
5017
|
-
process.execPath,
|
|
5018
|
-
useDistEntry ? [distEntry, ...args] : [binPath, ...args],
|
|
5019
|
-
{
|
|
5020
|
-
detached: true,
|
|
5021
|
-
stdio: ["ignore", logFd, logFd],
|
|
5022
|
-
cwd: workspacePath,
|
|
5023
|
-
env: {
|
|
5024
|
-
...process.env,
|
|
5025
|
-
NODE_NO_WARNINGS: "1",
|
|
5026
|
-
// Strip API key when using subscription to prevent SDK from picking it up
|
|
5027
|
-
...useSubscription ? { ANTHROPIC_API_KEY: "" } : {}
|
|
5489
|
+
} catch {
|
|
5028
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}`);
|
|
5029
5537
|
}
|
|
5030
|
-
);
|
|
5031
|
-
child.unref();
|
|
5032
|
-
fs7.closeSync(logFd);
|
|
5033
|
-
savePid(child.pid);
|
|
5034
|
-
console.log(`Jarvis agent started (PID: ${child.pid})`);
|
|
5035
|
-
console.log(` Local: ws://127.0.0.1:${port}`);
|
|
5036
|
-
console.log(` Workspace: ${workspacePath}`);
|
|
5037
|
-
console.log(` Logs: ${LOG_FILE}`);
|
|
5038
|
-
if (config?.apiUrl) {
|
|
5039
|
-
console.log(` Cloud: ${config.apiUrl}`);
|
|
5040
5538
|
}
|
|
5041
|
-
|
|
5042
|
-
console.log(`Commands:`);
|
|
5043
|
-
console.log(` jarvis logs \u2014 View agent logs`);
|
|
5044
|
-
console.log(` jarvis stop \u2014 Stop the agent`);
|
|
5045
|
-
console.log(` jarvis restart \u2014 Restart the agent`);
|
|
5046
|
-
});
|
|
5539
|
+
);
|
|
5047
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
|
+
}
|
|
5048
5549
|
const pid = readPid();
|
|
5049
5550
|
if (pid) {
|
|
5050
5551
|
try {
|
|
@@ -5060,7 +5561,7 @@ function createCli() {
|
|
|
5060
5561
|
const running = await isPortInUse(7862);
|
|
5061
5562
|
if (running) {
|
|
5062
5563
|
try {
|
|
5063
|
-
const output =
|
|
5564
|
+
const output = execSync2("lsof -ti :7862", { encoding: "utf-8" }).trim();
|
|
5064
5565
|
if (output) {
|
|
5065
5566
|
const pids = output.split("\n");
|
|
5066
5567
|
for (const p of pids) {
|
|
@@ -5077,27 +5578,16 @@ function createCli() {
|
|
|
5077
5578
|
}
|
|
5078
5579
|
console.log("No agent is running.");
|
|
5079
5580
|
});
|
|
5080
|
-
program.command("restart").description("Restart the agent").action(async () => {
|
|
5081
|
-
const pid = readPid();
|
|
5082
|
-
if (pid) {
|
|
5083
|
-
try {
|
|
5084
|
-
process.kill(pid, "SIGTERM");
|
|
5085
|
-
} catch {
|
|
5086
|
-
}
|
|
5087
|
-
clearPid();
|
|
5088
|
-
console.log(`Stopped agent (PID: ${pid})`);
|
|
5089
|
-
await new Promise((r) => setTimeout(r, 1e3));
|
|
5090
|
-
}
|
|
5091
|
-
console.log("Starting agent...");
|
|
5581
|
+
program.command("restart").description("Restart the agent (alias for start \u2014 start always does a clean restart)").action(async () => {
|
|
5092
5582
|
await program.parseAsync(["node", "jarvis", "start"]);
|
|
5093
5583
|
});
|
|
5094
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) => {
|
|
5095
|
-
if (!
|
|
5585
|
+
if (!fs8.existsSync(LOG_FILE)) {
|
|
5096
5586
|
console.log("No log file found. Start the agent first: jarvis start");
|
|
5097
5587
|
return;
|
|
5098
5588
|
}
|
|
5099
5589
|
if (opts.follow) {
|
|
5100
|
-
const tail =
|
|
5590
|
+
const tail = spawn3("tail", ["-f", "-n", opts.lines, LOG_FILE], {
|
|
5101
5591
|
stdio: "inherit"
|
|
5102
5592
|
});
|
|
5103
5593
|
process.on("SIGINT", () => {
|
|
@@ -5106,7 +5596,7 @@ function createCli() {
|
|
|
5106
5596
|
});
|
|
5107
5597
|
tail.on("exit", () => process.exit(0));
|
|
5108
5598
|
} else {
|
|
5109
|
-
const content =
|
|
5599
|
+
const content = execSync2(`tail -n ${opts.lines} "${LOG_FILE}"`, { encoding: "utf-8" });
|
|
5110
5600
|
process.stdout.write(content);
|
|
5111
5601
|
}
|
|
5112
5602
|
});
|
|
@@ -5116,7 +5606,9 @@ function createCli() {
|
|
|
5116
5606
|
const port = config?.port ?? 7862;
|
|
5117
5607
|
const running = await isPortInUse(port);
|
|
5118
5608
|
console.log(`Jarvis Agent v${PKG_VERSION}:`);
|
|
5119
|
-
console.log(
|
|
5609
|
+
console.log(
|
|
5610
|
+
` Status: ${running ? `\x1B[32mrunning\x1B[0m` : `\x1B[31mstopped\x1B[0m`}${pid ? ` (PID: ${pid})` : ""}`
|
|
5611
|
+
);
|
|
5120
5612
|
console.log(` Port: ${port}`);
|
|
5121
5613
|
console.log(` Config: ${getConfigPath()}`);
|
|
5122
5614
|
if (config) {
|
|
@@ -5130,8 +5622,79 @@ function createCli() {
|
|
|
5130
5622
|
} else {
|
|
5131
5623
|
console.log(` Config: Not set up. Run 'jarvis connect <token>'`);
|
|
5132
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
|
+
}
|
|
5133
5634
|
console.log(` Logs: ${LOG_FILE}`);
|
|
5134
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
|
+
});
|
|
5135
5698
|
program.command("logout").description("Clear saved configuration").action(() => {
|
|
5136
5699
|
clearConfig();
|
|
5137
5700
|
console.log("Configuration cleared.");
|
|
@@ -5144,5 +5707,20 @@ function createCli() {
|
|
|
5144
5707
|
}
|
|
5145
5708
|
|
|
5146
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
|
+
}
|
|
5147
5725
|
createCli().parse();
|
|
5148
5726
|
//# sourceMappingURL=bin.js.map
|