@dmsdc-ai/aigentry-telepty 0.5.8 → 0.5.9

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/install.js +231 -56
  3. package/package.json +4 -4
package/CHANGELOG.md CHANGED
@@ -4,6 +4,29 @@ All notable changes to `@dmsdc-ai/aigentry-telepty` are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.5.9] - 2026-06-08
8
+
9
+ ### Fixed — managed service install never started the daemon (#41)
10
+
11
+ - **The launchd/systemd/Windows service install generated an `env: node`
12
+ invocation that exited 127** under a minimal service-manager PATH (the daemon
13
+ never started when managed by launchd/systemd). **Fix:** `install.js` now uses
14
+ the absolute `process.execPath` + `cli.js` path for launchd/systemd/Windows
15
+ service generation, sets the daemon `PATH` via EnvironmentVariables, and adds
16
+ managed-instance live assertions so a managed daemon actually starts. Landed on
17
+ `main` at commit `7b2ab92`.
18
+
19
+ ### Changed — CI test wiring
20
+
21
+ - Wired `test/install-service-generation.test.js` (the #41 regression test) into
22
+ the `test`, `test:ci`, and `test:watch` script file lists so CI's
23
+ `npm run test:ci` actually exercises the service-install generation.
24
+
25
+ ### Docs
26
+
27
+ - Landed the #42 cross-machine relay/broker (hub) mode ADR and MVP
28
+ implementation spec.
29
+
7
30
  ## [0.5.2] - 2026-06-06
8
31
 
9
32
  ### Fixed — submit handshake confirmation (#507-B / #508)
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,161 @@ function cleanupLocalDaemons() {
36
46
  }
37
47
  }
38
48
 
49
+ function escapeXml(value) {
50
+ return String(value)
51
+ .replace(/&/g, '&')
52
+ .replace(/</g, '&lt;')
53
+ .replace(/>/g, '&gt;')
54
+ .replace(/"/g, '&quot;')
55
+ .replace(/'/g, '&apos;');
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 daemonPath = buildDaemonPath(nodeBin, ['/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin']);
95
+ const stdoutPath = path.join(logDir, 'launchd.out.log');
96
+ const stderrPath = path.join(logDir, 'launchd.err.log');
97
+
98
+ return `<?xml version="1.0" encoding="UTF-8"?>
99
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
100
+ <plist version="1.0">
101
+ <dict>
102
+ <key>Label</key>
103
+ <string>${escapeXml(label)}</string>
104
+ <key>ProgramArguments</key>
105
+ <array>
106
+ <string>${escapeXml(nodeBin)}</string>
107
+ <string>${escapeXml(cliJs)}</string>
108
+ <string>daemon</string>
109
+ </array>
110
+ <key>EnvironmentVariables</key>
111
+ <dict>
112
+ <key>PATH</key>
113
+ <string>${escapeXml(daemonPath)}</string>
114
+ </dict>
115
+ <key>StandardOutPath</key>
116
+ <string>${escapeXml(stdoutPath)}</string>
117
+ <key>StandardErrorPath</key>
118
+ <string>${escapeXml(stderrPath)}</string>
119
+ <key>RunAtLoad</key>
120
+ <true/>
121
+ <key>KeepAlive</key>
122
+ <true/>
123
+ </dict>
124
+ </plist>`;
125
+ }
126
+
127
+ function buildSystemdService(options = {}) {
128
+ const nodeBin = options.nodeBin || process.execPath;
129
+ const cliJs = options.cliJs || path.join(__dirname, 'cli.js');
130
+ const user = options.user;
131
+ const userLine = user ? `User=${user}\n` : '';
132
+ const daemonPath = buildDaemonPath(nodeBin, ['/usr/local/bin', '/usr/bin', '/bin']);
133
+ const wantedBy = options.wantedBy || 'multi-user.target';
134
+
135
+ return `[Unit]
136
+ Description=Telepty Daemon
137
+ After=network.target
138
+
139
+ [Service]
140
+ ExecStart=${systemdExecArg(nodeBin)} ${systemdExecArg(cliJs)} daemon
141
+ Restart=always
142
+ ${userLine}Environment=PATH=${daemonPath}
143
+ Environment=NODE_ENV=production
144
+
145
+ [Install]
146
+ WantedBy=${wantedBy}`;
147
+ }
148
+
149
+ function buildWindowsAutostartCommand(options = {}) {
150
+ const nodeBin = options.nodeBin || process.execPath;
151
+ const cliJs = options.cliJs || path.join(__dirname, 'cli.js');
152
+ const taskName = options.taskName || 'telepty-daemon';
153
+ const taskCommand = `${quoteWindowsArg(nodeBin)} ${quoteWindowsArg(cliJs)} daemon`;
154
+
155
+ return `schtasks /create /tn ${quoteWindowsArg(taskName)} /sc onlogon /rl LIMITED /f /tr ${quoteWindowsArg(taskCommand)}`;
156
+ }
157
+
158
+ function buildWindowsRunTaskCommand(options = {}) {
159
+ const taskName = options.taskName || 'telepty-daemon';
160
+ return `schtasks /run /tn ${quoteWindowsArg(taskName)}`;
161
+ }
162
+
163
+ function buildWindowsQueryTaskCommand(options = {}) {
164
+ const taskName = options.taskName || 'telepty-daemon';
165
+ return `schtasks /query /tn ${quoteWindowsArg(taskName)} /fo LIST`;
166
+ }
167
+
168
+ function assertLaunchdServiceLive(label = 'com.aigentry.telepty') {
169
+ let output = '';
170
+ try {
171
+ output = execSync(`launchctl list ${shellQuote(label)}`, { encoding: 'utf8' });
172
+ } catch (e) {
173
+ throw new Error(`launchd service ${label} was not found after load`);
174
+ }
175
+
176
+ const pidMatch = output.match(/"PID"\s*=\s*([0-9]+)/);
177
+ if (!pidMatch || Number(pidMatch[1]) <= 0) {
178
+ throw new Error(`launchd service ${label} loaded but has no live PID. launchctl output:\n${output}`);
179
+ }
180
+ }
181
+
182
+ function assertSystemdServiceLive(serviceName = 'telepty', options = {}) {
183
+ const scope = options.user ? '--user ' : '';
184
+ try {
185
+ execSync(`systemctl ${scope}is-active --quiet ${serviceName}`, { stdio: 'ignore' });
186
+ } catch (e) {
187
+ throw new Error(`systemd service ${serviceName} is not active after start`);
188
+ }
189
+ }
190
+
191
+ function assertWindowsTaskRunning(taskName = 'telepty-daemon') {
192
+ let output = '';
193
+ try {
194
+ output = execSync(buildWindowsQueryTaskCommand({ taskName }), { encoding: 'utf8' });
195
+ } catch (e) {
196
+ throw new Error(`Windows scheduled task ${taskName} was not found after creation`);
197
+ }
198
+
199
+ if (!/^Status:\s*Running$/im.test(output)) {
200
+ throw new Error(`Windows scheduled task ${taskName} started but is not running. schtasks output:\n${output}`);
201
+ }
202
+ }
203
+
39
204
  async function installSkills() {
40
205
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
41
206
  console.log('⏭️ Skipping interactive skill installation (no TTY).');
@@ -46,6 +211,7 @@ async function installSkills() {
46
211
  console.log('\n📋 Telepty skill installation');
47
212
 
48
213
  try {
214
+ const { runInteractiveSkillInstaller } = require('./skill-installer');
49
215
  await runInteractiveSkillInstaller({
50
216
  packageRoot: resolveInstalledPackageRoot(),
51
217
  cwd: process.cwd()
@@ -55,7 +221,9 @@ async function installSkills() {
55
221
  }
56
222
  }
57
223
 
58
- (async () => {
224
+ async function main() {
225
+ console.log("🚀 Installing @dmsdc-ai/aigentry-telepty...");
226
+
59
227
  // 1. Install globally via npm
60
228
  console.log("📦 Installing package globally...");
61
229
  run("npm install -g @dmsdc-ai/aigentry-telepty");
@@ -63,12 +231,10 @@ async function installSkills() {
63
231
  // 2. Install telepty skills for supported clients
64
232
  await installSkills();
65
233
 
66
- // 3. Find executable
67
- let teleptyPath = '';
68
- try {
69
- teleptyPath = execSync(os.platform() === 'win32' ? 'where telepty' : 'which telepty', { encoding: 'utf8' }).split('\n')[0].trim();
70
- } catch (e) {
71
- teleptyPath = 'telepty'; // fallback
234
+ // 3. Resolve daemon entrypoint without relying on service-manager PATH.
235
+ const launchOptions = resolveDaemonLaunchOptions({ packageRoot: resolveInstalledPackageRoot() });
236
+ if (!fs.existsSync(launchOptions.cliJs)) {
237
+ throw new Error(`Cannot find daemon entrypoint: ${launchOptions.cliJs}`);
72
238
  }
73
239
 
74
240
  // 4. Setup OS-specific autostart or background daemon
@@ -76,86 +242,95 @@ async function installSkills() {
76
242
 
77
243
  if (platform === 'win32') {
78
244
  cleanupLocalDaemons();
79
- console.log("⚙️ Setting up Windows background process...");
80
- const subprocess = spawn(teleptyPath, ['daemon'], {
81
- detached: true,
82
- stdio: 'ignore',
83
- windowsHide: true
84
- });
85
- subprocess.unref();
86
- console.log("✅ Windows daemon started in background.");
245
+ console.log("⚙️ Setting up Windows scheduled task...");
246
+ run(buildWindowsAutostartCommand(launchOptions));
247
+ run(buildWindowsRunTaskCommand());
248
+ assertWindowsTaskRunning();
249
+ console.log("✅ Windows scheduled task installed and started.");
87
250
 
88
251
  } else if (platform === 'darwin') {
89
252
  console.log("⚙️ Setting up macOS launchd service...");
90
253
  const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', 'com.aigentry.telepty.plist');
91
254
  fs.mkdirSync(path.dirname(plistPath), { recursive: true });
92
- try { execSync(`launchctl unload "${plistPath}" 2>/dev/null`); } catch(e){}
255
+ fs.mkdirSync(launchOptions.logDir, { recursive: true });
256
+ try { execSync(`launchctl unload ${shellQuote(plistPath)} 2>/dev/null`); } catch(e){}
93
257
  cleanupLocalDaemons();
94
258
 
95
- const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
96
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
97
- <plist version="1.0">
98
- <dict>
99
- <key>Label</key>
100
- <string>com.aigentry.telepty</string>
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>`;
259
+ const plistContent = buildLaunchdPlist(launchOptions);
112
260
 
113
261
  fs.writeFileSync(plistPath, plistContent);
114
- run(`launchctl load "${plistPath}"`);
262
+ run(`launchctl load ${shellQuote(plistPath)}`);
263
+ assertLaunchdServiceLive();
115
264
  console.log("✅ macOS LaunchAgent installed and started.");
116
265
 
117
266
  } else {
118
267
  // Linux
268
+ let hasSystemd = false;
119
269
  try {
120
270
  execSync('systemctl --version', { stdio: 'ignore' });
271
+ hasSystemd = true;
272
+ } catch(e) {}
273
+
274
+ if (hasSystemd) {
121
275
  if (process.getuid && process.getuid() === 0) {
122
276
  console.log("⚙️ Setting up systemd service for Linux...");
123
277
  try { execSync('systemctl stop telepty', { stdio: 'ignore' }); } catch(e) {}
124
278
  cleanupLocalDaemons();
125
- const serviceContent = `[Unit]
126
- Description=Telepty Daemon
127
- After=network.target
128
-
129
- [Service]
130
- ExecStart=${teleptyPath} daemon
131
- Restart=always
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`;
279
+ const serviceContent = buildSystemdService({
280
+ ...launchOptions,
281
+ user: process.env.SUDO_USER || process.env.USER || 'root'
282
+ });
138
283
 
139
284
  fs.writeFileSync('/etc/systemd/system/telepty.service', serviceContent);
140
285
  run('systemctl daemon-reload');
141
286
  run('systemctl enable telepty');
142
287
  run('systemctl start telepty');
288
+ assertSystemdServiceLive();
143
289
  console.log("✅ Systemd service installed and started.");
144
290
  process.exit(0);
145
291
  }
146
- } catch(e) {}
147
292
 
148
- // Fallback for Linux without systemd or non-root
149
- console.log("⚠️ Skipping systemd (no root or no systemd). Starting in background...");
293
+ console.log("⚙️ Setting up user systemd service for Linux...");
294
+ const userServicePath = path.join(os.homedir(), '.config', 'systemd', 'user', 'telepty.service');
295
+ fs.mkdirSync(path.dirname(userServicePath), { recursive: true });
296
+ cleanupLocalDaemons();
297
+ fs.writeFileSync(userServicePath, buildSystemdService({
298
+ ...launchOptions,
299
+ wantedBy: 'default.target'
300
+ }));
301
+ run('systemctl --user daemon-reload');
302
+ run('systemctl --user enable telepty');
303
+ run('systemctl --user start telepty');
304
+ assertSystemdServiceLive('telepty', { user: true });
305
+ console.log("✅ User systemd service installed and started.");
306
+ process.exit(0);
307
+ }
308
+
309
+ // Fallback for Linux without systemd
310
+ console.log("⚠️ Skipping persistent systemd setup. Starting daemon for this session only...");
150
311
  cleanupLocalDaemons();
151
- const subprocess = spawn(teleptyPath, ['daemon'], {
312
+ const subprocess = spawn(launchOptions.nodeBin, [launchOptions.cliJs, 'daemon'], {
152
313
  detached: true,
153
314
  stdio: 'ignore'
154
315
  });
155
316
  subprocess.unref();
156
- console.log("✅ Linux daemon started in background using nohup equivalent.");
317
+ console.log("✅ Linux daemon started in background for the current session.");
157
318
  }
158
319
 
159
320
  console.log("\n🎉 Installation complete! Telepty daemon is running.");
160
321
  console.log("👉 Try running: telepty attach\n");
161
- })();
322
+ }
323
+
324
+ if (require.main === module) {
325
+ main().catch((e) => {
326
+ console.error('❌ Installation failed:', e.message);
327
+ process.exit(1);
328
+ });
329
+ }
330
+
331
+ module.exports = {
332
+ buildLaunchdPlist,
333
+ buildSystemdService,
334
+ buildWindowsAutostartCommand,
335
+ resolveDaemonLaunchOptions,
336
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dmsdc-ai/aigentry-telepty",
3
- "version": "0.5.8",
3
+ "version": "0.5.9",
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/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/install-service-generation.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/install-service-generation.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/install-service-generation.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/",
41
41
  "typecheck": "tsc --noEmit",
42
42
  "regen-fixtures": "node scripts/regen-snippet-fixtures.js"
43
43
  },