@dmsdc-ai/aigentry-telepty 0.5.8 โ 0.6.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/CHANGELOG.md +82 -0
- package/cli.js +392 -30
- package/cross-machine.js +124 -1
- package/daemon-control.js +9 -0
- package/daemon.js +415 -17
- package/install.js +367 -62
- package/package.json +5 -5
- package/src/audit/inject-log.js +234 -0
- package/src/protocol/http-auth.js +36 -1
- package/src/submit-gate.js +130 -5
- package/src/transport/broker-client.js +498 -0
- package/src/transport/broker-protocol.js +155 -0
- package/src/transport/broker-server.js +505 -0
- package/src/win-resolve-executable.js +6 -1
package/install.js
CHANGED
|
@@ -5,9 +5,6 @@ const os = require('os');
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const { cleanupDaemonProcesses } = require('./daemon-control');
|
|
8
|
-
const { runInteractiveSkillInstaller } = require('./skill-installer');
|
|
9
|
-
|
|
10
|
-
console.log("๐ Installing @dmsdc-ai/aigentry-telepty...");
|
|
11
8
|
|
|
12
9
|
function run(cmd) {
|
|
13
10
|
try {
|
|
@@ -18,6 +15,10 @@ function run(cmd) {
|
|
|
18
15
|
}
|
|
19
16
|
}
|
|
20
17
|
|
|
18
|
+
function shellQuote(value) {
|
|
19
|
+
return `'${String(value).replace(/'/g, "'\\''")}'`;
|
|
20
|
+
}
|
|
21
|
+
|
|
21
22
|
function resolveInstalledPackageRoot() {
|
|
22
23
|
try {
|
|
23
24
|
const globalRoot = execSync('npm root -g', { encoding: 'utf8' }).trim();
|
|
@@ -27,6 +28,15 @@ function resolveInstalledPackageRoot() {
|
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
function resolveDaemonLaunchOptions(options = {}) {
|
|
32
|
+
const packageRoot = options.packageRoot || __dirname;
|
|
33
|
+
const nodeBin = options.nodeBin || process.execPath;
|
|
34
|
+
const cliJs = options.cliJs || path.join(packageRoot, 'cli.js');
|
|
35
|
+
const logDir = options.logDir || path.join(os.homedir(), '.telepty', 'logs');
|
|
36
|
+
|
|
37
|
+
return { nodeBin, cliJs, logDir };
|
|
38
|
+
}
|
|
39
|
+
|
|
30
40
|
function cleanupLocalDaemons() {
|
|
31
41
|
console.log('๐งน Cleaning up existing telepty daemons...');
|
|
32
42
|
const results = cleanupDaemonProcesses();
|
|
@@ -36,6 +46,261 @@ function cleanupLocalDaemons() {
|
|
|
36
46
|
}
|
|
37
47
|
}
|
|
38
48
|
|
|
49
|
+
function escapeXml(value) {
|
|
50
|
+
return String(value)
|
|
51
|
+
.replace(/&/g, '&')
|
|
52
|
+
.replace(/</g, '<')
|
|
53
|
+
.replace(/>/g, '>')
|
|
54
|
+
.replace(/"/g, '"')
|
|
55
|
+
.replace(/'/g, ''');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function uniquePathEntries(entries) {
|
|
59
|
+
const seen = new Set();
|
|
60
|
+
const result = [];
|
|
61
|
+
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
if (!entry || seen.has(entry)) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
seen.add(entry);
|
|
67
|
+
result.push(entry);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function buildDaemonPath(nodeBin, baseEntries) {
|
|
74
|
+
return uniquePathEntries([path.dirname(nodeBin), ...baseEntries]).join(':');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function systemdExecArg(value) {
|
|
78
|
+
const text = String(value);
|
|
79
|
+
if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(text)) {
|
|
80
|
+
return text;
|
|
81
|
+
}
|
|
82
|
+
return `"${text.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function quoteWindowsArg(value) {
|
|
86
|
+
return `"${String(value).replace(/"/g, '\\"')}"`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function buildLaunchdPlist(options = {}) {
|
|
90
|
+
const label = options.label || 'com.aigentry.telepty';
|
|
91
|
+
const nodeBin = options.nodeBin || process.execPath;
|
|
92
|
+
const cliJs = options.cliJs || path.join(__dirname, 'cli.js');
|
|
93
|
+
const logDir = options.logDir || path.join(os.homedir(), '.telepty', 'logs');
|
|
94
|
+
const command = options.command || 'daemon';
|
|
95
|
+
const daemonPath = buildDaemonPath(nodeBin, ['/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin']);
|
|
96
|
+
const stdoutPath = path.join(logDir, 'launchd.out.log');
|
|
97
|
+
const stderrPath = path.join(logDir, 'launchd.err.log');
|
|
98
|
+
const envPairs = [['PATH', daemonPath], ...Object.entries(options.extraEnv || {})];
|
|
99
|
+
const envXml = envPairs
|
|
100
|
+
.map(([key, value]) => ` <key>${escapeXml(key)}</key>\n <string>${escapeXml(value)}</string>`)
|
|
101
|
+
.join('\n');
|
|
102
|
+
|
|
103
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
104
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
105
|
+
<plist version="1.0">
|
|
106
|
+
<dict>
|
|
107
|
+
<key>Label</key>
|
|
108
|
+
<string>${escapeXml(label)}</string>
|
|
109
|
+
<key>ProgramArguments</key>
|
|
110
|
+
<array>
|
|
111
|
+
<string>${escapeXml(nodeBin)}</string>
|
|
112
|
+
<string>${escapeXml(cliJs)}</string>
|
|
113
|
+
<string>${escapeXml(command)}</string>
|
|
114
|
+
</array>
|
|
115
|
+
<key>EnvironmentVariables</key>
|
|
116
|
+
<dict>
|
|
117
|
+
${envXml}
|
|
118
|
+
</dict>
|
|
119
|
+
<key>StandardOutPath</key>
|
|
120
|
+
<string>${escapeXml(stdoutPath)}</string>
|
|
121
|
+
<key>StandardErrorPath</key>
|
|
122
|
+
<string>${escapeXml(stderrPath)}</string>
|
|
123
|
+
<key>RunAtLoad</key>
|
|
124
|
+
<true/>
|
|
125
|
+
<key>KeepAlive</key>
|
|
126
|
+
<true/>
|
|
127
|
+
</dict>
|
|
128
|
+
</plist>`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function systemdEnvLine(key, value) {
|
|
132
|
+
const escaped = String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
133
|
+
return `Environment="${key}=${escaped}"`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function buildSystemdService(options = {}) {
|
|
137
|
+
const nodeBin = options.nodeBin || process.execPath;
|
|
138
|
+
const cliJs = options.cliJs || path.join(__dirname, 'cli.js');
|
|
139
|
+
const user = options.user;
|
|
140
|
+
const userLine = user ? `User=${user}\n` : '';
|
|
141
|
+
const daemonPath = buildDaemonPath(nodeBin, ['/usr/local/bin', '/usr/bin', '/bin']);
|
|
142
|
+
const wantedBy = options.wantedBy || 'multi-user.target';
|
|
143
|
+
const command = options.command || 'daemon';
|
|
144
|
+
const description = options.description || 'Telepty Daemon';
|
|
145
|
+
const extraEnvLines = Object.entries(options.extraEnv || {})
|
|
146
|
+
.map(([key, value]) => `${systemdEnvLine(key, value)}\n`)
|
|
147
|
+
.join('');
|
|
148
|
+
|
|
149
|
+
return `[Unit]
|
|
150
|
+
Description=${description}
|
|
151
|
+
After=network.target
|
|
152
|
+
|
|
153
|
+
[Service]
|
|
154
|
+
ExecStart=${systemdExecArg(nodeBin)} ${systemdExecArg(cliJs)} ${systemdExecArg(command)}
|
|
155
|
+
Restart=always
|
|
156
|
+
${userLine}Environment=PATH=${daemonPath}
|
|
157
|
+
Environment=NODE_ENV=production
|
|
158
|
+
${extraEnvLines}
|
|
159
|
+
[Install]
|
|
160
|
+
WantedBy=${wantedBy}`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function buildWindowsAutostartCommand(options = {}) {
|
|
164
|
+
const nodeBin = options.nodeBin || process.execPath;
|
|
165
|
+
const cliJs = options.cliJs || path.join(__dirname, 'cli.js');
|
|
166
|
+
const taskName = options.taskName || 'telepty-daemon';
|
|
167
|
+
const command = options.command || 'daemon';
|
|
168
|
+
const taskCommand = `${quoteWindowsArg(nodeBin)} ${quoteWindowsArg(cliJs)} ${command}`;
|
|
169
|
+
|
|
170
|
+
return `schtasks /create /tn ${quoteWindowsArg(taskName)} /sc onlogon /rl LIMITED /f /tr ${quoteWindowsArg(taskCommand)}`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function buildWindowsRunTaskCommand(options = {}) {
|
|
174
|
+
const taskName = options.taskName || 'telepty-daemon';
|
|
175
|
+
return `schtasks /run /tn ${quoteWindowsArg(taskName)}`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function buildWindowsQueryTaskCommand(options = {}) {
|
|
179
|
+
const taskName = options.taskName || 'telepty-daemon';
|
|
180
|
+
return `schtasks /query /tn ${quoteWindowsArg(taskName)} /fo LIST`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// --- Broker-host service variant (telepty #42 broker MVP โ spec ยง6 + ยง2 H; reuses #41 hardening) ---
|
|
184
|
+
// The broker runs the SAME hardened service definition as the daemon, but executes
|
|
185
|
+
// `<node> <cli.js> broker` (spec ยง5/ยง6) and carries the broker host env (TLS + JWT/enroll
|
|
186
|
+
// secrets, spec ยง6). Selected via `telepty install --broker` or env TELEPTY_BROKER_MODE.
|
|
187
|
+
const BROKER_LAUNCHD_LABEL = 'com.aigentry.telepty-broker';
|
|
188
|
+
const BROKER_SYSTEMD_SERVICE = 'telepty-broker';
|
|
189
|
+
const BROKER_WINDOWS_TASK = 'telepty-broker';
|
|
190
|
+
|
|
191
|
+
// Pass-through env keys for the broker host service (spec ยง5/ยง6). Secrets are NEVER
|
|
192
|
+
// hardcoded โ they are read from the install-time environment and forwarded into the
|
|
193
|
+
// generated service definition so the always-on broker host loads them on start.
|
|
194
|
+
const BROKER_ENV_KEYS = [
|
|
195
|
+
'TELEPTY_JWT_SECRET',
|
|
196
|
+
'TELEPTY_ENROLL_SECRET',
|
|
197
|
+
'TELEPTY_TLS_CERT',
|
|
198
|
+
'TELEPTY_TLS_KEY',
|
|
199
|
+
'TELEPTY_BROKER_ACL',
|
|
200
|
+
'TELEPTY_ENROLL_MAX_NODES',
|
|
201
|
+
'PORT',
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
function collectBrokerServiceEnv(env = process.env) {
|
|
205
|
+
const result = { TELEPTY_BROKER_MODE: '1' };
|
|
206
|
+
for (const key of BROKER_ENV_KEYS) {
|
|
207
|
+
const value = env[key];
|
|
208
|
+
if (value !== undefined && value !== '') {
|
|
209
|
+
result[key] = String(value);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function buildBrokerLaunchdPlist(options = {}) {
|
|
216
|
+
return buildLaunchdPlist({
|
|
217
|
+
...options,
|
|
218
|
+
label: options.label || BROKER_LAUNCHD_LABEL,
|
|
219
|
+
command: 'broker',
|
|
220
|
+
extraEnv: { ...collectBrokerServiceEnv(options.env), ...(options.extraEnv || {}) },
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function buildBrokerSystemdService(options = {}) {
|
|
225
|
+
return buildSystemdService({
|
|
226
|
+
...options,
|
|
227
|
+
command: 'broker',
|
|
228
|
+
description: options.description || 'Telepty Broker',
|
|
229
|
+
extraEnv: { ...collectBrokerServiceEnv(options.env), ...(options.extraEnv || {}) },
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function buildBrokerWindowsAutostartCommand(options = {}) {
|
|
234
|
+
return buildWindowsAutostartCommand({
|
|
235
|
+
...options,
|
|
236
|
+
taskName: options.taskName || BROKER_WINDOWS_TASK,
|
|
237
|
+
command: 'broker',
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Resolve the active service profile (daemon vs broker) so main() can install either
|
|
242
|
+
// variant from one code path. The daemon profile reproduces the exact pre-#42 values
|
|
243
|
+
// (no behavior change for existing installs); the broker profile selects distinct
|
|
244
|
+
// label/service/task names, the `broker` command, and the broker host env.
|
|
245
|
+
function resolveServiceProfile(options = {}) {
|
|
246
|
+
if (options.broker) {
|
|
247
|
+
return {
|
|
248
|
+
command: 'broker',
|
|
249
|
+
launchdLabel: BROKER_LAUNCHD_LABEL,
|
|
250
|
+
launchdPlistName: `${BROKER_LAUNCHD_LABEL}.plist`,
|
|
251
|
+
systemdService: BROKER_SYSTEMD_SERVICE,
|
|
252
|
+
windowsTask: BROKER_WINDOWS_TASK,
|
|
253
|
+
description: 'Telepty Broker',
|
|
254
|
+
extraEnv: collectBrokerServiceEnv(options.env),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
command: 'daemon',
|
|
259
|
+
launchdLabel: 'com.aigentry.telepty',
|
|
260
|
+
launchdPlistName: 'com.aigentry.telepty.plist',
|
|
261
|
+
systemdService: 'telepty',
|
|
262
|
+
windowsTask: 'telepty-daemon',
|
|
263
|
+
description: 'Telepty Daemon',
|
|
264
|
+
extraEnv: {},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function assertLaunchdServiceLive(label = 'com.aigentry.telepty') {
|
|
269
|
+
let output = '';
|
|
270
|
+
try {
|
|
271
|
+
output = execSync(`launchctl list ${shellQuote(label)}`, { encoding: 'utf8' });
|
|
272
|
+
} catch (e) {
|
|
273
|
+
throw new Error(`launchd service ${label} was not found after load`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const pidMatch = output.match(/"PID"\s*=\s*([0-9]+)/);
|
|
277
|
+
if (!pidMatch || Number(pidMatch[1]) <= 0) {
|
|
278
|
+
throw new Error(`launchd service ${label} loaded but has no live PID. launchctl output:\n${output}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function assertSystemdServiceLive(serviceName = 'telepty', options = {}) {
|
|
283
|
+
const scope = options.user ? '--user ' : '';
|
|
284
|
+
try {
|
|
285
|
+
execSync(`systemctl ${scope}is-active --quiet ${serviceName}`, { stdio: 'ignore' });
|
|
286
|
+
} catch (e) {
|
|
287
|
+
throw new Error(`systemd service ${serviceName} is not active after start`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function assertWindowsTaskRunning(taskName = 'telepty-daemon') {
|
|
292
|
+
let output = '';
|
|
293
|
+
try {
|
|
294
|
+
output = execSync(buildWindowsQueryTaskCommand({ taskName }), { encoding: 'utf8' });
|
|
295
|
+
} catch (e) {
|
|
296
|
+
throw new Error(`Windows scheduled task ${taskName} was not found after creation`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!/^Status:\s*Running$/im.test(output)) {
|
|
300
|
+
throw new Error(`Windows scheduled task ${taskName} started but is not running. schtasks output:\n${output}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
39
304
|
async function installSkills() {
|
|
40
305
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
41
306
|
console.log('โญ๏ธ Skipping interactive skill installation (no TTY).');
|
|
@@ -46,6 +311,7 @@ async function installSkills() {
|
|
|
46
311
|
console.log('\n๐ Telepty skill installation');
|
|
47
312
|
|
|
48
313
|
try {
|
|
314
|
+
const { runInteractiveSkillInstaller } = require('./skill-installer');
|
|
49
315
|
await runInteractiveSkillInstaller({
|
|
50
316
|
packageRoot: resolveInstalledPackageRoot(),
|
|
51
317
|
cwd: process.cwd()
|
|
@@ -55,7 +321,19 @@ async function installSkills() {
|
|
|
55
321
|
}
|
|
56
322
|
}
|
|
57
323
|
|
|
58
|
-
|
|
324
|
+
async function main() {
|
|
325
|
+
console.log("๐ Installing @dmsdc-ai/aigentry-telepty...");
|
|
326
|
+
|
|
327
|
+
// Broker-host variant (telepty #42): `telepty install --broker` or env TELEPTY_BROKER_MODE
|
|
328
|
+
// installs the SAME hardened service running `telepty broker` instead of `telepty daemon`.
|
|
329
|
+
// Default-OFF โ existing daemon installs are entirely unaffected (additive).
|
|
330
|
+
const wantsBroker = process.argv.slice(2).includes('--broker')
|
|
331
|
+
|| process.env.TELEPTY_BROKER_MODE === '1';
|
|
332
|
+
const profile = resolveServiceProfile({ broker: wantsBroker });
|
|
333
|
+
if (wantsBroker) {
|
|
334
|
+
console.log(`๐ฐ๏ธ Broker-host mode: installing service '${profile.launchdLabel}' (runs 'telepty broker').`);
|
|
335
|
+
}
|
|
336
|
+
|
|
59
337
|
// 1. Install globally via npm
|
|
60
338
|
console.log("๐ฆ Installing package globally...");
|
|
61
339
|
run("npm install -g @dmsdc-ai/aigentry-telepty");
|
|
@@ -63,12 +341,10 @@ async function installSkills() {
|
|
|
63
341
|
// 2. Install telepty skills for supported clients
|
|
64
342
|
await installSkills();
|
|
65
343
|
|
|
66
|
-
// 3.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
} catch (e) {
|
|
71
|
-
teleptyPath = 'telepty'; // fallback
|
|
344
|
+
// 3. Resolve daemon entrypoint without relying on service-manager PATH.
|
|
345
|
+
const launchOptions = resolveDaemonLaunchOptions({ packageRoot: resolveInstalledPackageRoot() });
|
|
346
|
+
if (!fs.existsSync(launchOptions.cliJs)) {
|
|
347
|
+
throw new Error(`Cannot find daemon entrypoint: ${launchOptions.cliJs}`);
|
|
72
348
|
}
|
|
73
349
|
|
|
74
350
|
// 4. Setup OS-specific autostart or background daemon
|
|
@@ -76,86 +352,115 @@ async function installSkills() {
|
|
|
76
352
|
|
|
77
353
|
if (platform === 'win32') {
|
|
78
354
|
cleanupLocalDaemons();
|
|
79
|
-
console.log("โ๏ธ Setting up Windows
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
});
|
|
85
|
-
subprocess.unref();
|
|
86
|
-
console.log("โ
Windows daemon started in background.");
|
|
355
|
+
console.log("โ๏ธ Setting up Windows scheduled task...");
|
|
356
|
+
run(buildWindowsAutostartCommand({ ...launchOptions, taskName: profile.windowsTask, command: profile.command }));
|
|
357
|
+
run(buildWindowsRunTaskCommand({ taskName: profile.windowsTask }));
|
|
358
|
+
assertWindowsTaskRunning(profile.windowsTask);
|
|
359
|
+
console.log("โ
Windows scheduled task installed and started.");
|
|
87
360
|
|
|
88
361
|
} else if (platform === 'darwin') {
|
|
89
362
|
console.log("โ๏ธ Setting up macOS launchd service...");
|
|
90
|
-
const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents',
|
|
363
|
+
const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', profile.launchdPlistName);
|
|
91
364
|
fs.mkdirSync(path.dirname(plistPath), { recursive: true });
|
|
92
|
-
|
|
365
|
+
fs.mkdirSync(launchOptions.logDir, { recursive: true });
|
|
366
|
+
try { execSync(`launchctl unload ${shellQuote(plistPath)} 2>/dev/null`); } catch(e){}
|
|
93
367
|
cleanupLocalDaemons();
|
|
94
368
|
|
|
95
|
-
const plistContent =
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
<key>ProgramArguments</key>
|
|
102
|
-
<array>
|
|
103
|
-
<string>${teleptyPath}</string>
|
|
104
|
-
<string>daemon</string>
|
|
105
|
-
</array>
|
|
106
|
-
<key>RunAtLoad</key>
|
|
107
|
-
<true/>
|
|
108
|
-
<key>KeepAlive</key>
|
|
109
|
-
<true/>
|
|
110
|
-
</dict>
|
|
111
|
-
</plist>`;
|
|
369
|
+
const plistContent = buildLaunchdPlist({
|
|
370
|
+
...launchOptions,
|
|
371
|
+
label: profile.launchdLabel,
|
|
372
|
+
command: profile.command,
|
|
373
|
+
extraEnv: profile.extraEnv,
|
|
374
|
+
});
|
|
112
375
|
|
|
113
376
|
fs.writeFileSync(plistPath, plistContent);
|
|
114
|
-
run(`launchctl load
|
|
377
|
+
run(`launchctl load ${shellQuote(plistPath)}`);
|
|
378
|
+
assertLaunchdServiceLive(profile.launchdLabel);
|
|
115
379
|
console.log("โ
macOS LaunchAgent installed and started.");
|
|
116
380
|
|
|
117
381
|
} else {
|
|
118
382
|
// Linux
|
|
383
|
+
let hasSystemd = false;
|
|
119
384
|
try {
|
|
120
385
|
execSync('systemctl --version', { stdio: 'ignore' });
|
|
386
|
+
hasSystemd = true;
|
|
387
|
+
} catch(e) {}
|
|
388
|
+
|
|
389
|
+
if (hasSystemd) {
|
|
121
390
|
if (process.getuid && process.getuid() === 0) {
|
|
122
391
|
console.log("โ๏ธ Setting up systemd service for Linux...");
|
|
123
|
-
try { execSync(
|
|
392
|
+
try { execSync(`systemctl stop ${profile.systemdService}`, { stdio: 'ignore' }); } catch(e) {}
|
|
124
393
|
cleanupLocalDaemons();
|
|
125
|
-
const serviceContent =
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
User=${process.env.SUDO_USER || process.env.USER || 'root'}
|
|
133
|
-
Environment=PATH=/usr/bin:/usr/local/bin:$PATH
|
|
134
|
-
Environment=NODE_ENV=production
|
|
135
|
-
|
|
136
|
-
[Install]
|
|
137
|
-
WantedBy=multi-user.target`;
|
|
394
|
+
const serviceContent = buildSystemdService({
|
|
395
|
+
...launchOptions,
|
|
396
|
+
user: process.env.SUDO_USER || process.env.USER || 'root',
|
|
397
|
+
command: profile.command,
|
|
398
|
+
description: profile.description,
|
|
399
|
+
extraEnv: profile.extraEnv,
|
|
400
|
+
});
|
|
138
401
|
|
|
139
|
-
fs.writeFileSync(
|
|
402
|
+
fs.writeFileSync(`/etc/systemd/system/${profile.systemdService}.service`, serviceContent);
|
|
140
403
|
run('systemctl daemon-reload');
|
|
141
|
-
run(
|
|
142
|
-
run(
|
|
404
|
+
run(`systemctl enable ${profile.systemdService}`);
|
|
405
|
+
run(`systemctl start ${profile.systemdService}`);
|
|
406
|
+
assertSystemdServiceLive(profile.systemdService);
|
|
143
407
|
console.log("โ
Systemd service installed and started.");
|
|
144
408
|
process.exit(0);
|
|
145
409
|
}
|
|
146
|
-
} catch(e) {}
|
|
147
410
|
|
|
148
|
-
|
|
149
|
-
|
|
411
|
+
console.log("โ๏ธ Setting up user systemd service for Linux...");
|
|
412
|
+
const userServicePath = path.join(os.homedir(), '.config', 'systemd', 'user', `${profile.systemdService}.service`);
|
|
413
|
+
fs.mkdirSync(path.dirname(userServicePath), { recursive: true });
|
|
414
|
+
cleanupLocalDaemons();
|
|
415
|
+
fs.writeFileSync(userServicePath, buildSystemdService({
|
|
416
|
+
...launchOptions,
|
|
417
|
+
wantedBy: 'default.target',
|
|
418
|
+
command: profile.command,
|
|
419
|
+
description: profile.description,
|
|
420
|
+
extraEnv: profile.extraEnv,
|
|
421
|
+
}));
|
|
422
|
+
run('systemctl --user daemon-reload');
|
|
423
|
+
run(`systemctl --user enable ${profile.systemdService}`);
|
|
424
|
+
run(`systemctl --user start ${profile.systemdService}`);
|
|
425
|
+
assertSystemdServiceLive(profile.systemdService, { user: true });
|
|
426
|
+
console.log("โ
User systemd service installed and started.");
|
|
427
|
+
process.exit(0);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Fallback for Linux without systemd
|
|
431
|
+
console.log("โ ๏ธ Skipping persistent systemd setup. Starting daemon for this session only...");
|
|
150
432
|
cleanupLocalDaemons();
|
|
151
|
-
const subprocess = spawn(
|
|
433
|
+
const subprocess = spawn(launchOptions.nodeBin, [launchOptions.cliJs, profile.command], {
|
|
152
434
|
detached: true,
|
|
153
|
-
stdio: 'ignore'
|
|
435
|
+
stdio: 'ignore',
|
|
436
|
+
env: { ...process.env, ...profile.extraEnv }
|
|
154
437
|
});
|
|
155
438
|
subprocess.unref();
|
|
156
|
-
console.log("โ
Linux daemon started in background
|
|
439
|
+
console.log("โ
Linux daemon started in background for the current session.");
|
|
157
440
|
}
|
|
158
441
|
|
|
159
442
|
console.log("\n๐ Installation complete! Telepty daemon is running.");
|
|
160
443
|
console.log("๐ Try running: telepty attach\n");
|
|
161
|
-
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (require.main === module) {
|
|
447
|
+
main().catch((e) => {
|
|
448
|
+
console.error('โ Installation failed:', e.message);
|
|
449
|
+
process.exit(1);
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
module.exports = {
|
|
454
|
+
buildLaunchdPlist,
|
|
455
|
+
buildSystemdService,
|
|
456
|
+
buildWindowsAutostartCommand,
|
|
457
|
+
resolveDaemonLaunchOptions,
|
|
458
|
+
buildBrokerLaunchdPlist,
|
|
459
|
+
buildBrokerSystemdService,
|
|
460
|
+
buildBrokerWindowsAutostartCommand,
|
|
461
|
+
collectBrokerServiceEnv,
|
|
462
|
+
resolveServiceProfile,
|
|
463
|
+
BROKER_LAUNCHD_LABEL,
|
|
464
|
+
BROKER_SYSTEMD_SERVICE,
|
|
465
|
+
BROKER_WINDOWS_TASK,
|
|
466
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dmsdc-ai/aigentry-telepty",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"main": "daemon.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"aigentry-telepty": "install.js",
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
],
|
|
36
36
|
"scripts": {
|
|
37
37
|
"postinstall": "node scripts/postinstall.js",
|
|
38
|
-
"test": "node --require ./test-support/setup-env.js --test test/auth.test.js test/http-auth.test.js test/daemon.test.js test/daemon-singleton.test.js test/integration/daemon-launch.test.js test/cli.test.js test/telepty-kill.test.js test/idle-ttl.test.js test/telepty-clean-older-than.test.js test/lifecycle-transport-agnostic.test.js test/skill-installer.test.js test/interactive-terminal.test.js test/runtime-info.test.js test/session-routing.test.js test/session-state.test.js test/session-store-persistence.test.js test/mailbox-lock.test.js test/report-enforcement.test.js test/enforce-report.test.js test/peer-inject-validator.test.js test/enforce-submit-gate.test.js test/submit-gate.test.js test/submit-via-pty.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/inject-submit-force-env.test.js test/host-spec.test.js test/cross-host-inject.test.js test/cross-machine-ssh-routing.test.js test/init.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/win-kill-process.test.js test/daemon-control-port-owner.test.js test/banner-stderr-jq-safety.test.js test/bridge-supervisor-ipc.test.js test/bridge-j3-shim.test.js test/bridge-e2e.test.js test/release-0.4.5-bugfixes.test.js && git diff --exit-code tests/snippet-protocol/v1/",
|
|
39
|
-
"test:watch": "node --require ./test-support/setup-env.js --test --watch test/auth.test.js test/http-auth.test.js test/daemon.test.js test/daemon-singleton.test.js test/integration/daemon-launch.test.js test/cli.test.js test/telepty-kill.test.js test/idle-ttl.test.js test/telepty-clean-older-than.test.js test/lifecycle-transport-agnostic.test.js test/skill-installer.test.js test/interactive-terminal.test.js test/runtime-info.test.js test/session-routing.test.js test/session-state.test.js test/session-store-persistence.test.js test/mailbox-lock.test.js test/report-enforcement.test.js test/enforce-report.test.js test/peer-inject-validator.test.js test/enforce-submit-gate.test.js test/submit-gate.test.js test/submit-via-pty.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/inject-submit-force-env.test.js test/host-spec.test.js test/cross-host-inject.test.js test/cross-machine-ssh-routing.test.js test/init.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/win-kill-process.test.js test/daemon-control-port-owner.test.js test/banner-stderr-jq-safety.test.js test/bridge-supervisor-ipc.test.js test/bridge-j3-shim.test.js test/bridge-e2e.test.js test/release-0.4.5-bugfixes.test.js",
|
|
40
|
-
"test:ci": "node --require ./test-support/setup-env.js --test --test-reporter=spec test/auth.test.js test/http-auth.test.js test/daemon.test.js test/daemon-singleton.test.js test/integration/daemon-launch.test.js test/cli.test.js test/telepty-kill.test.js test/idle-ttl.test.js test/telepty-clean-older-than.test.js test/lifecycle-transport-agnostic.test.js test/skill-installer.test.js test/interactive-terminal.test.js test/runtime-info.test.js test/session-routing.test.js test/session-state.test.js test/session-store-persistence.test.js test/mailbox-lock.test.js test/report-enforcement.test.js test/enforce-report.test.js test/peer-inject-validator.test.js test/enforce-submit-gate.test.js test/submit-gate.test.js test/submit-via-pty.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/inject-submit-force-env.test.js test/host-spec.test.js test/cross-host-inject.test.js test/cross-machine-ssh-routing.test.js test/init.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/win-kill-process.test.js test/daemon-control-port-owner.test.js test/banner-stderr-jq-safety.test.js test/bridge-supervisor-ipc.test.js test/bridge-j3-shim.test.js test/bridge-e2e.test.js test/release-0.4.5-bugfixes.test.js && git diff --exit-code tests/snippet-protocol/v1/",
|
|
38
|
+
"test": "node --require ./test-support/setup-env.js --test test/auth.test.js test/http-auth.test.js test/broker-protocol.test.js test/broker-auth.test.js test/broker-server.test.js test/broker-client.test.js test/daemon-broker-wiring.test.js test/broker-cli.test.js test/broker-integration.test.js test/daemon.test.js test/daemon-singleton.test.js test/integration/daemon-launch.test.js test/cli.test.js test/telepty-kill.test.js test/idle-ttl.test.js test/telepty-clean-older-than.test.js test/lifecycle-transport-agnostic.test.js test/skill-installer.test.js test/interactive-terminal.test.js test/runtime-info.test.js test/session-routing.test.js test/session-state.test.js test/session-store-persistence.test.js test/mailbox-lock.test.js test/report-enforcement.test.js test/enforce-report.test.js test/peer-inject-validator.test.js test/enforce-submit-gate.test.js test/submit-gate.test.js test/submit-via-pty.test.js test/submit-render-gate.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/inject-submit-force-env.test.js test/host-spec.test.js test/cross-host-inject.test.js test/cross-machine-ssh-routing.test.js test/init.test.js test/install-service-generation.test.js test/install-broker-service.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/ensure-daemon-running.test.js test/win-kill-process.test.js test/daemon-control-port-owner.test.js test/banner-stderr-jq-safety.test.js test/bridge-supervisor-ipc.test.js test/bridge-j3-shim.test.js test/bridge-e2e.test.js test/release-0.4.5-bugfixes.test.js test/inject-audit-log.test.js test/inject-audit-daemon.test.js test/inject-audit-cli.test.js && git diff --exit-code tests/snippet-protocol/v1/",
|
|
39
|
+
"test:watch": "node --require ./test-support/setup-env.js --test --watch test/auth.test.js test/http-auth.test.js test/broker-protocol.test.js test/broker-auth.test.js test/broker-server.test.js test/broker-client.test.js test/daemon-broker-wiring.test.js test/broker-cli.test.js test/broker-integration.test.js test/daemon.test.js test/daemon-singleton.test.js test/integration/daemon-launch.test.js test/cli.test.js test/telepty-kill.test.js test/idle-ttl.test.js test/telepty-clean-older-than.test.js test/lifecycle-transport-agnostic.test.js test/skill-installer.test.js test/interactive-terminal.test.js test/runtime-info.test.js test/session-routing.test.js test/session-state.test.js test/session-store-persistence.test.js test/mailbox-lock.test.js test/report-enforcement.test.js test/enforce-report.test.js test/peer-inject-validator.test.js test/enforce-submit-gate.test.js test/submit-gate.test.js test/submit-via-pty.test.js test/submit-render-gate.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/inject-submit-force-env.test.js test/host-spec.test.js test/cross-host-inject.test.js test/cross-machine-ssh-routing.test.js test/init.test.js test/install-service-generation.test.js test/install-broker-service.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/ensure-daemon-running.test.js test/win-kill-process.test.js test/daemon-control-port-owner.test.js test/banner-stderr-jq-safety.test.js test/bridge-supervisor-ipc.test.js test/bridge-j3-shim.test.js test/bridge-e2e.test.js test/release-0.4.5-bugfixes.test.js test/inject-audit-log.test.js test/inject-audit-daemon.test.js test/inject-audit-cli.test.js",
|
|
40
|
+
"test:ci": "node --require ./test-support/setup-env.js --test --test-reporter=spec test/auth.test.js test/http-auth.test.js test/broker-protocol.test.js test/broker-auth.test.js test/broker-server.test.js test/broker-client.test.js test/daemon-broker-wiring.test.js test/broker-cli.test.js test/broker-integration.test.js test/daemon.test.js test/daemon-singleton.test.js test/integration/daemon-launch.test.js test/cli.test.js test/telepty-kill.test.js test/idle-ttl.test.js test/telepty-clean-older-than.test.js test/lifecycle-transport-agnostic.test.js test/skill-installer.test.js test/interactive-terminal.test.js test/runtime-info.test.js test/session-routing.test.js test/session-state.test.js test/session-store-persistence.test.js test/mailbox-lock.test.js test/report-enforcement.test.js test/enforce-report.test.js test/peer-inject-validator.test.js test/enforce-submit-gate.test.js test/submit-gate.test.js test/submit-via-pty.test.js test/submit-render-gate.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/inject-submit-force-env.test.js test/host-spec.test.js test/cross-host-inject.test.js test/cross-machine-ssh-routing.test.js test/init.test.js test/install-service-generation.test.js test/install-broker-service.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/ensure-daemon-running.test.js test/win-kill-process.test.js test/daemon-control-port-owner.test.js test/banner-stderr-jq-safety.test.js test/bridge-supervisor-ipc.test.js test/bridge-j3-shim.test.js test/bridge-e2e.test.js test/release-0.4.5-bugfixes.test.js test/inject-audit-log.test.js test/inject-audit-daemon.test.js test/inject-audit-cli.test.js && git diff --exit-code tests/snippet-protocol/v1/",
|
|
41
41
|
"typecheck": "tsc --noEmit",
|
|
42
42
|
"regen-fixtures": "node scripts/regen-snippet-fixtures.js"
|
|
43
43
|
},
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
],
|
|
60
60
|
"author": "dmsdc-ai",
|
|
61
61
|
"license": "ISC",
|
|
62
|
-
"description": "Universal terminal session bridge
|
|
62
|
+
"description": "Universal terminal session bridge \u2014 connect any terminal to any terminal, any machine",
|
|
63
63
|
"repository": {
|
|
64
64
|
"type": "git",
|
|
65
65
|
"url": "git+https://github.com/dmsdc-ai/aigentry-telepty.git"
|