@scottzx/1agents 20260601.1.0
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/README.md +38 -0
- package/cc-run.js +47 -0
- package/install.js +193 -0
- package/package.json +37 -0
- package/run.js +307 -0
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# @scottzx/1agents
|
|
2
|
+
|
|
3
|
+
随时随地,通过浏览器远程访问你的 AI 智能体和开发工作台。
|
|
4
|
+
|
|
5
|
+
Remote Agent 是一个基于 Web 的远程工作台平台,让你打破必须在电脑前才能与 AI 交互的限制。它集成了终端访问、文件管理、Git 操作等功能,只需一个浏览器就能从任何地方连接到你的工作环境,继续对话、编辑代码、管理文件、查看仓库状态 —— 就像你正坐在它面前一样。
|
|
6
|
+
|
|
7
|
+
终端与通信能力基于 [ttyd](https://github.com/tsl0922/ttyd) 和 [cc-connect](https://github.com/scottzx/cc-connect) 构建。
|
|
8
|
+
|
|
9
|
+
## 安装
|
|
10
|
+
|
|
11
|
+
你可以通过 npm 全局安装 `1agents`:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g @scottzx/1agents
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
安装脚本会自动检测您的操作系统和架构,并从 GitHub Releases 自动下载对应的预编译二进制包(包含 `1agent`、`ttyd` 静态程序和前端 Web 静态资源)。
|
|
18
|
+
|
|
19
|
+
## 使用
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# 启动远程工作台服务(默认端口 :8080)
|
|
23
|
+
1agent
|
|
24
|
+
|
|
25
|
+
# 指定监听端口和工作目录
|
|
26
|
+
1agent -listen :9000 -workdir /path/to/your/workspace
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
启动后,在浏览器中打开 `http://localhost:8080` (或对应的监听端口) 即可访问完整的工作台。
|
|
30
|
+
|
|
31
|
+
## 常用参数
|
|
32
|
+
|
|
33
|
+
- `-listen string`:服务对外监听地址(默认 `":8080"`)
|
|
34
|
+
- `-workdir string`:工作台暴露的文件系统根目录(默认 `"~"`)
|
|
35
|
+
- `-tmux-session string`:用于终端持久化的 tmux会话名称(默认 `"1agents"`)
|
|
36
|
+
- `-no-ttyd`:跳过启动内嵌的 ttyd 进程
|
|
37
|
+
|
|
38
|
+
更多详情与文档请参考 GitHub 仓库:https://github.com/scottzx/1Agents
|
package/cc-run.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const { execFileSync, execSync } = require("child_process");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
|
|
9
|
+
const packageDir = __dirname;
|
|
10
|
+
const myBinDir = path.join(packageDir, "bin");
|
|
11
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
12
|
+
let ccPath = path.join(myBinDir, "cc-connect" + ext);
|
|
13
|
+
|
|
14
|
+
// ── Smart Compatibility: Delegate to standalone global cc-connect if it is newer/installed ──
|
|
15
|
+
try {
|
|
16
|
+
const globalPrefix = execSync("npm config get prefix", { encoding: "utf8" }).trim();
|
|
17
|
+
const standaloneCCPath = process.platform === "win32"
|
|
18
|
+
? path.join(globalPrefix, "node_modules", "cc-connect", "bin", "cc-connect.exe")
|
|
19
|
+
: path.join(globalPrefix, "lib", "node_modules", "cc-connect", "bin", "cc-connect");
|
|
20
|
+
|
|
21
|
+
if (fs.existsSync(standaloneCCPath)) {
|
|
22
|
+
// If standalone global cc-connect exists, use it to ensure absolute consistency and latest version
|
|
23
|
+
ccPath = standaloneCCPath;
|
|
24
|
+
}
|
|
25
|
+
} catch (e) {
|
|
26
|
+
// Silent fallback to our bundled binary if detection fails or throws
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ── Fallback Execution ──
|
|
30
|
+
if (!fs.existsSync(ccPath)) {
|
|
31
|
+
console.log(`[1agent] cc-connect CLI binary missing, running installer...`);
|
|
32
|
+
try {
|
|
33
|
+
execSync("node " + JSON.stringify(path.join(packageDir, "install.js")), {
|
|
34
|
+
stdio: "inherit",
|
|
35
|
+
cwd: packageDir,
|
|
36
|
+
});
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.error("[1agent] Auto-install failed. Please run manually: npm rebuild");
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
execFileSync(ccPath, process.argv.slice(2), { stdio: "inherit" });
|
|
45
|
+
} catch (err) {
|
|
46
|
+
process.exit(err.status || 1);
|
|
47
|
+
}
|
package/install.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const { execSync } = require("child_process");
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const https = require("https");
|
|
9
|
+
const http = require("http");
|
|
10
|
+
|
|
11
|
+
const PACKAGE = require("./package.json");
|
|
12
|
+
|
|
13
|
+
// Map NPM version (e.g. 20260523.1.0) back to Git Tag (e.g. v20260523-1)
|
|
14
|
+
const versionParts = PACKAGE.version.split(".");
|
|
15
|
+
if (versionParts.length < 2) {
|
|
16
|
+
throw new Error(`[1agent] Invalid package version: ${PACKAGE.version}`);
|
|
17
|
+
}
|
|
18
|
+
const VERSION = `v${versionParts[0]}-${versionParts[1]}`;
|
|
19
|
+
const NAME = "1agents";
|
|
20
|
+
|
|
21
|
+
const GITHUB_REPO = "scottzx/1Agents";
|
|
22
|
+
|
|
23
|
+
const PLATFORM_MAP = {
|
|
24
|
+
darwin: "darwin",
|
|
25
|
+
linux: "linux",
|
|
26
|
+
win32: "windows",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const ARCH_MAP = {
|
|
30
|
+
x64: "amd64",
|
|
31
|
+
arm64: "arm64",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function getPlatformInfo() {
|
|
35
|
+
const platform = PLATFORM_MAP[process.platform];
|
|
36
|
+
const arch = ARCH_MAP[process.arch];
|
|
37
|
+
if (!platform || !arch) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Unsupported platform: ${process.platform}/${process.arch}. ` +
|
|
40
|
+
`Supported: linux/darwin/windows x64/arm64`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
const ext = platform === "windows" ? ".zip" : ".tar.gz";
|
|
44
|
+
const filename = `${NAME}-${VERSION}-${platform}-${arch}${ext}`;
|
|
45
|
+
return { platform, arch, ext, filename };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getDownloadURLs(filename) {
|
|
49
|
+
return [
|
|
50
|
+
`https://github.com/${GITHUB_REPO}/releases/download/${VERSION}/${filename}`,
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function fetch(url, redirects = 5) {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
if (redirects <= 0) return reject(new Error("Too many redirects"));
|
|
57
|
+
const mod = url.startsWith("https") ? https : http;
|
|
58
|
+
mod
|
|
59
|
+
.get(url, { headers: { "User-Agent": "1agent-npm" } }, (res) => {
|
|
60
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
61
|
+
return resolve(fetch(res.headers.location, redirects - 1));
|
|
62
|
+
}
|
|
63
|
+
if (res.statusCode !== 200) {
|
|
64
|
+
res.resume();
|
|
65
|
+
return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
|
|
66
|
+
}
|
|
67
|
+
const chunks = [];
|
|
68
|
+
res.on("data", (c) => chunks.push(c));
|
|
69
|
+
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
70
|
+
res.on("error", reject);
|
|
71
|
+
})
|
|
72
|
+
.on("error", reject);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function download(urls) {
|
|
77
|
+
for (const url of urls) {
|
|
78
|
+
try {
|
|
79
|
+
console.log(`[1agent] Downloading from ${url}`);
|
|
80
|
+
const data = await fetch(url);
|
|
81
|
+
console.log(`[1agent] Downloaded ${(data.length / 1024 / 1024).toFixed(2)} MB`);
|
|
82
|
+
return data;
|
|
83
|
+
} catch (err) {
|
|
84
|
+
console.warn(`[1agent] Failed: ${err.message}, trying next source...`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
throw new Error(
|
|
88
|
+
`[1agent] Could not download binary from any source.\n` +
|
|
89
|
+
` Tried: ${urls.join(", ")}\n` +
|
|
90
|
+
` You can download manually from https://github.com/${GITHUB_REPO}/releases`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function extractTarGz(buffer, destDir) {
|
|
95
|
+
const tmpFile = path.join(destDir, "_tmp.tar.gz");
|
|
96
|
+
fs.writeFileSync(tmpFile, buffer);
|
|
97
|
+
try {
|
|
98
|
+
execSync(`tar xzf "${tmpFile}" -C "${destDir}"`, { stdio: "pipe" });
|
|
99
|
+
} finally {
|
|
100
|
+
try {
|
|
101
|
+
fs.unlinkSync(tmpFile);
|
|
102
|
+
} catch {}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function extractZip(buffer, destDir) {
|
|
107
|
+
const tmpFile = path.join(destDir, "_tmp.zip");
|
|
108
|
+
fs.writeFileSync(tmpFile, buffer);
|
|
109
|
+
try {
|
|
110
|
+
try {
|
|
111
|
+
execSync(`unzip -o "${tmpFile}" -d "${destDir}"`, { stdio: "pipe" });
|
|
112
|
+
} catch {
|
|
113
|
+
execSync(`powershell -Command "Expand-Archive -Force '${tmpFile}' '${destDir}'"`, {
|
|
114
|
+
stdio: "pipe",
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
} finally {
|
|
118
|
+
try {
|
|
119
|
+
fs.unlinkSync(tmpFile);
|
|
120
|
+
} catch {}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function main() {
|
|
125
|
+
const { platform, arch, ext, filename } = getPlatformInfo();
|
|
126
|
+
console.log(`[1agent] Platform: ${platform}/${arch}`);
|
|
127
|
+
|
|
128
|
+
const packageDir = __dirname;
|
|
129
|
+
const binDir = path.join(packageDir, "bin");
|
|
130
|
+
const extSuffix = platform === "windows" ? ".exe" : "";
|
|
131
|
+
const agentPath = path.join(binDir, NAME + extSuffix);
|
|
132
|
+
const ttydPath = path.join(binDir, "ttyd" + extSuffix);
|
|
133
|
+
|
|
134
|
+
// Check if binaries already exist and match version
|
|
135
|
+
if (fs.existsSync(agentPath) && fs.existsSync(ttydPath)) {
|
|
136
|
+
console.log(`[1agent] Binaries already exist in ${binDir}. Replacing with ${VERSION}...`);
|
|
137
|
+
try {
|
|
138
|
+
fs.unlinkSync(agentPath);
|
|
139
|
+
fs.unlinkSync(ttydPath);
|
|
140
|
+
} catch (err) {
|
|
141
|
+
// Ignore if they can't be deleted, we will overwrite
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Create bin and dist directory structure just in case
|
|
146
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
147
|
+
|
|
148
|
+
const urls = getDownloadURLs(filename);
|
|
149
|
+
const data = await download(urls);
|
|
150
|
+
|
|
151
|
+
console.log(`[1agent] Extracting files into ${packageDir}...`);
|
|
152
|
+
if (ext === ".tar.gz") {
|
|
153
|
+
extractTarGz(data, packageDir);
|
|
154
|
+
} else {
|
|
155
|
+
extractZip(data, packageDir);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Make binaries executable on Darwin and Linux
|
|
159
|
+
if (platform !== "windows") {
|
|
160
|
+
if (fs.existsSync(agentPath)) {
|
|
161
|
+
fs.chmodSync(agentPath, 0o755);
|
|
162
|
+
}
|
|
163
|
+
if (fs.existsSync(ttydPath)) {
|
|
164
|
+
fs.chmodSync(ttydPath, 0o755);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Darwin Quarantine Attribute Removal
|
|
169
|
+
if (platform === "darwin") {
|
|
170
|
+
try {
|
|
171
|
+
if (fs.existsSync(agentPath)) {
|
|
172
|
+
execSync(`xattr -d com.apple.quarantine "${agentPath}"`, { stdio: "pipe" });
|
|
173
|
+
}
|
|
174
|
+
if (fs.existsSync(ttydPath)) {
|
|
175
|
+
execSync(`xattr -d com.apple.quarantine "${ttydPath}"`, { stdio: "pipe" });
|
|
176
|
+
}
|
|
177
|
+
console.log(`[1agent] Removed macOS quarantine attribute`);
|
|
178
|
+
} catch {
|
|
179
|
+
// xattr fails if attribute doesn't exist, which is fine
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
console.log(`[1agent] Successfully installed to ${packageDir}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
main().catch((err) => {
|
|
187
|
+
console.error(err.message);
|
|
188
|
+
console.error(
|
|
189
|
+
"[1agent] Installation failed. You can install manually:\n" +
|
|
190
|
+
` https://github.com/${GITHUB_REPO}/releases/tag/${VERSION}`
|
|
191
|
+
);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@scottzx/1agents",
|
|
3
|
+
"version": "20260601.1.0",
|
|
4
|
+
"description": "Browser-based remote workbench integrating terminal access, file management, and AI agent communication",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"1agent",
|
|
7
|
+
"terminal",
|
|
8
|
+
"xterm",
|
|
9
|
+
"ttyd",
|
|
10
|
+
"file-manager",
|
|
11
|
+
"ai-agent",
|
|
12
|
+
"workbench"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://github.com/scottzx/1Agents",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/scottzx/1Agents.git"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"author": "scottzx",
|
|
21
|
+
"bin": {
|
|
22
|
+
"1agents": "run.js",
|
|
23
|
+
"cc-connect": "cc-run.js"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"postinstall": "node install.js"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=22"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"install.js",
|
|
33
|
+
"run.js",
|
|
34
|
+
"cc-run.js",
|
|
35
|
+
"README.md"
|
|
36
|
+
]
|
|
37
|
+
}
|
package/run.js
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const { execFileSync, execSync, spawn } = require("child_process");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
const os = require("os");
|
|
9
|
+
|
|
10
|
+
const PACKAGE = require("./package.json");
|
|
11
|
+
const NAME = "1agents";
|
|
12
|
+
const packageDir = __dirname;
|
|
13
|
+
const binDir = path.join(packageDir, "bin");
|
|
14
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
15
|
+
const agentPath = path.join(binDir, NAME + ext);
|
|
16
|
+
const ttydPath = path.join(binDir, "ttyd" + ext);
|
|
17
|
+
|
|
18
|
+
function needsInstall() {
|
|
19
|
+
if (!fs.existsSync(agentPath)) return true;
|
|
20
|
+
if (!fs.existsSync(ttydPath)) return true;
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (needsInstall()) {
|
|
25
|
+
console.log(`[1agent] Binaries missing, running installer...`);
|
|
26
|
+
try {
|
|
27
|
+
execSync("node " + JSON.stringify(path.join(packageDir, "install.js")), {
|
|
28
|
+
stdio: "inherit",
|
|
29
|
+
cwd: packageDir,
|
|
30
|
+
});
|
|
31
|
+
} catch (err) {
|
|
32
|
+
console.error("[1agent] Auto-install failed. Please run manually: npm rebuild");
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
38
|
+
|
|
39
|
+
async function main() {
|
|
40
|
+
const userArgs = process.argv.slice(2);
|
|
41
|
+
const command = userArgs[0];
|
|
42
|
+
|
|
43
|
+
const daemonDir = path.join(os.homedir(), ".1agents");
|
|
44
|
+
const daemonJson = path.join(daemonDir, "daemon.json");
|
|
45
|
+
const logFile = path.join(daemonDir, "1agents.log");
|
|
46
|
+
|
|
47
|
+
const isDaemonCommand = ["start", "stop", "status", "logs"].includes(command);
|
|
48
|
+
|
|
49
|
+
if (isDaemonCommand) {
|
|
50
|
+
if (command === "start") {
|
|
51
|
+
let isRunning = false;
|
|
52
|
+
let existingPid = null;
|
|
53
|
+
let existingAddr = "";
|
|
54
|
+
try {
|
|
55
|
+
if (fs.existsSync(daemonJson)) {
|
|
56
|
+
const info = JSON.parse(fs.readFileSync(daemonJson, "utf8"));
|
|
57
|
+
existingPid = info.pid;
|
|
58
|
+
existingAddr = info.listen_addr;
|
|
59
|
+
if (existingPid) {
|
|
60
|
+
process.kill(existingPid, 0);
|
|
61
|
+
isRunning = true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch (e) {
|
|
65
|
+
isRunning = false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (isRunning) {
|
|
69
|
+
console.log(`⚠️ 1agents is already running in the background (PID: ${existingPid}) on ${existingAddr}.`);
|
|
70
|
+
process.exit(0);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Prepare arguments
|
|
74
|
+
const finalArgs = [];
|
|
75
|
+
if (!userArgs.some(arg => arg.startsWith("-ttyd-bin"))) {
|
|
76
|
+
finalArgs.push("-ttyd-bin", ttydPath);
|
|
77
|
+
}
|
|
78
|
+
if (!userArgs.some(arg => arg.startsWith("-static"))) {
|
|
79
|
+
const staticPath = path.join(packageDir, "dist");
|
|
80
|
+
finalArgs.push("-static", staticPath);
|
|
81
|
+
}
|
|
82
|
+
// Add all original arguments except 'start'
|
|
83
|
+
finalArgs.push(...userArgs.slice(1));
|
|
84
|
+
|
|
85
|
+
if (!fs.existsSync(daemonDir)) {
|
|
86
|
+
fs.mkdirSync(daemonDir, { recursive: true });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.log("Starting 1agents in the background...");
|
|
90
|
+
const logStream = fs.openSync(logFile, "a");
|
|
91
|
+
const child = spawn(agentPath, finalArgs, {
|
|
92
|
+
detached: true,
|
|
93
|
+
stdio: ["ignore", logStream, logStream]
|
|
94
|
+
});
|
|
95
|
+
child.unref();
|
|
96
|
+
|
|
97
|
+
// Wait for the Go binary to boot and write its daemon.json
|
|
98
|
+
let started = false;
|
|
99
|
+
let pid = child.pid;
|
|
100
|
+
let listenAddr = "";
|
|
101
|
+
|
|
102
|
+
for (let i = 0; i < 20; i++) {
|
|
103
|
+
await sleep(200);
|
|
104
|
+
try {
|
|
105
|
+
process.kill(child.pid, 0);
|
|
106
|
+
} catch (e) {
|
|
107
|
+
break; // Exited early
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
if (fs.existsSync(daemonJson)) {
|
|
112
|
+
const info = JSON.parse(fs.readFileSync(daemonJson, "utf8"));
|
|
113
|
+
if (info.pid === child.pid || process.platform === "win32") {
|
|
114
|
+
pid = info.pid || child.pid;
|
|
115
|
+
listenAddr = info.listen_addr;
|
|
116
|
+
started = true;
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} catch (e) {}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!started) {
|
|
124
|
+
console.error("❌ Failed to start 1agents in the background.");
|
|
125
|
+
try {
|
|
126
|
+
if (fs.existsSync(logFile)) {
|
|
127
|
+
const logs = fs.readFileSync(logFile, "utf8").split("\n").slice(-15).join("\n");
|
|
128
|
+
console.error("\nLast 15 lines of log:\n" + logs);
|
|
129
|
+
}
|
|
130
|
+
} catch (e) {}
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
console.log("\n==================================================================");
|
|
135
|
+
console.log("🚀 1agents started successfully in the background!");
|
|
136
|
+
console.log(`● PID : ${pid}`);
|
|
137
|
+
console.log(`● Local Port : ${listenAddr}`);
|
|
138
|
+
console.log(`● Log File : ${logFile}`);
|
|
139
|
+
console.log("==================================================================");
|
|
140
|
+
console.log("Commands to manage the daemon:");
|
|
141
|
+
console.log(" 1agents status - Check running status");
|
|
142
|
+
console.log(" 1agents logs -f - Follow log output");
|
|
143
|
+
console.log(" 1agents stop - Stop the background server\n");
|
|
144
|
+
process.exit(0);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
else if (command === "stop") {
|
|
148
|
+
let pid = null;
|
|
149
|
+
try {
|
|
150
|
+
if (fs.existsSync(daemonJson)) {
|
|
151
|
+
const info = JSON.parse(fs.readFileSync(daemonJson, "utf8"));
|
|
152
|
+
pid = info.pid;
|
|
153
|
+
}
|
|
154
|
+
} catch (e) {}
|
|
155
|
+
|
|
156
|
+
if (!pid) {
|
|
157
|
+
console.log("1agents is not running (no active daemon found).");
|
|
158
|
+
process.exit(0);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let isAlive = false;
|
|
162
|
+
try {
|
|
163
|
+
process.kill(pid, 0);
|
|
164
|
+
isAlive = true;
|
|
165
|
+
} catch (e) {}
|
|
166
|
+
|
|
167
|
+
if (!isAlive) {
|
|
168
|
+
console.log("1agents is not running (PID not active). Cleaning up...");
|
|
169
|
+
try { fs.unlinkSync(daemonJson); } catch (e) {}
|
|
170
|
+
process.exit(0);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log(`Stopping 1agents (PID: ${pid})...`);
|
|
174
|
+
try {
|
|
175
|
+
process.kill(pid, "SIGTERM");
|
|
176
|
+
} catch (e) {
|
|
177
|
+
console.error(`Failed to send SIGTERM: ${e.message}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let stopped = false;
|
|
181
|
+
for (let i = 0; i < 25; i++) {
|
|
182
|
+
await sleep(200);
|
|
183
|
+
try {
|
|
184
|
+
process.kill(pid, 0);
|
|
185
|
+
} catch (e) {
|
|
186
|
+
stopped = true;
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!stopped) {
|
|
192
|
+
console.log("Process did not stop gracefully. Sending SIGKILL...");
|
|
193
|
+
try {
|
|
194
|
+
process.kill(pid, "SIGKILL");
|
|
195
|
+
} catch (e) {}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
try { fs.unlinkSync(daemonJson); } catch (e) {}
|
|
199
|
+
console.log("❇️ 1agents stopped successfully.");
|
|
200
|
+
process.exit(0);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
else if (command === "status") {
|
|
204
|
+
let isRunning = false;
|
|
205
|
+
let pid = null;
|
|
206
|
+
let listenAddr = "";
|
|
207
|
+
let mtime = null;
|
|
208
|
+
try {
|
|
209
|
+
if (fs.existsSync(daemonJson)) {
|
|
210
|
+
const stats = fs.statSync(daemonJson);
|
|
211
|
+
mtime = stats.mtime;
|
|
212
|
+
const info = JSON.parse(fs.readFileSync(daemonJson, "utf8"));
|
|
213
|
+
pid = info.pid;
|
|
214
|
+
listenAddr = info.listen_addr;
|
|
215
|
+
if (pid) {
|
|
216
|
+
process.kill(pid, 0);
|
|
217
|
+
isRunning = true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
} catch (e) {
|
|
221
|
+
isRunning = false;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (isRunning) {
|
|
225
|
+
console.log("\n1agents daemon status:");
|
|
226
|
+
console.log(`● Active : running (since ${mtime ? mtime.toLocaleString() : "unknown"})`);
|
|
227
|
+
console.log(`● PID : ${pid}`);
|
|
228
|
+
console.log(`● Address : ${listenAddr}`);
|
|
229
|
+
console.log(`● Log File : ${logFile}`);
|
|
230
|
+
|
|
231
|
+
console.log();
|
|
232
|
+
} else {
|
|
233
|
+
if (fs.existsSync(daemonJson)) {
|
|
234
|
+
console.log("● Status : stopped (stale PID file found)");
|
|
235
|
+
try {
|
|
236
|
+
fs.unlinkSync(daemonJson);
|
|
237
|
+
} catch (e) {}
|
|
238
|
+
} else {
|
|
239
|
+
console.log("● Status : stopped");
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
process.exit(0);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
else if (command === "logs") {
|
|
246
|
+
const follow = userArgs.includes("-f") || userArgs.includes("--follow");
|
|
247
|
+
let numLines = 50;
|
|
248
|
+
|
|
249
|
+
const nIndex = userArgs.findIndex(arg => arg === "-n");
|
|
250
|
+
if (nIndex !== -1 && nIndex + 1 < userArgs.length) {
|
|
251
|
+
const val = parseInt(userArgs[nIndex + 1], 10);
|
|
252
|
+
if (!isNaN(val) && val > 0) {
|
|
253
|
+
numLines = val;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!fs.existsSync(logFile)) {
|
|
258
|
+
console.log("No log file found.");
|
|
259
|
+
process.exit(0);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const data = fs.readFileSync(logFile, "utf8");
|
|
263
|
+
const lines = data.split("\n");
|
|
264
|
+
const lastLines = lines.slice(-numLines).join("\n");
|
|
265
|
+
console.log(lastLines);
|
|
266
|
+
|
|
267
|
+
if (follow) {
|
|
268
|
+
fs.watchFile(logFile, { interval: 250 }, (curr, prev) => {
|
|
269
|
+
if (curr.mtime <= prev.mtime) return;
|
|
270
|
+
if (curr.size <= prev.size) return;
|
|
271
|
+
try {
|
|
272
|
+
const fd = fs.openSync(logFile, "r");
|
|
273
|
+
const buffer = Buffer.alloc(curr.size - prev.size);
|
|
274
|
+
fs.readSync(fd, buffer, 0, buffer.length, prev.size);
|
|
275
|
+
fs.closeSync(fd);
|
|
276
|
+
process.stdout.write(buffer.toString());
|
|
277
|
+
} catch (e) {}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
await new Promise(() => {}); // Keep alive
|
|
281
|
+
}
|
|
282
|
+
process.exit(0);
|
|
283
|
+
}
|
|
284
|
+
} else {
|
|
285
|
+
// Foreground run (default)
|
|
286
|
+
const finalArgs = [];
|
|
287
|
+
if (!userArgs.some(arg => arg.startsWith("-ttyd-bin"))) {
|
|
288
|
+
finalArgs.push("-ttyd-bin", ttydPath);
|
|
289
|
+
}
|
|
290
|
+
if (!userArgs.some(arg => arg.startsWith("-static"))) {
|
|
291
|
+
const staticPath = path.join(packageDir, "dist");
|
|
292
|
+
finalArgs.push("-static", staticPath);
|
|
293
|
+
}
|
|
294
|
+
finalArgs.push(...userArgs);
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
execFileSync(agentPath, finalArgs, { stdio: "inherit" });
|
|
298
|
+
} catch (err) {
|
|
299
|
+
process.exit(err.status || 1);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
main().catch((err) => {
|
|
305
|
+
console.error(err);
|
|
306
|
+
process.exit(1);
|
|
307
|
+
});
|