@dmsdc-ai/aigentry-telepty 0.4.0 → 0.4.1
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 +24 -0
- package/cli.js +5 -1
- package/package.json +4 -4
- package/src/win-resolve-executable.js +87 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@dmsdc-ai/aigentry-telepty` are documented here.
|
|
4
4
|
|
|
5
|
+
## [0.4.1] - 2026-05-17
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **#25** — Windows PATHEXT resolution for `telepty allow`. npm-global CLIs
|
|
10
|
+
(`claude`, `codex`, `gemini`) now spawn correctly with bare names on
|
|
11
|
+
Windows. Previously `telepty allow … claude` failed with
|
|
12
|
+
`Cannot create process, error code: 2` (ERROR_FILE_NOT_FOUND) because
|
|
13
|
+
node-pty's `CreateProcessW` does not walk `%PATHEXT%` the way `cmd.exe`
|
|
14
|
+
does, so the npm-global `claude.cmd` shim was unreachable from the bare
|
|
15
|
+
name. New: `src/win-resolve-executable.js` resolver (Windows-only branch
|
|
16
|
+
walks `PATH` × `PATHEXT`; POSIX no-op) + 14 unit tests. macOS/Linux
|
|
17
|
+
behavior unchanged.
|
|
18
|
+
|
|
19
|
+
### Notes
|
|
20
|
+
|
|
21
|
+
- **Snyk SAST scan on changed files** — `src/win-resolve-executable.js`
|
|
22
|
+
+ `test/win-resolve-executable.test.js` = **0 findings** (At-Inception
|
|
23
|
+
clean). `cli.js` shows **5 pre-existing findings** (2 Medium Command
|
|
24
|
+
Injection at `execSync` L469 + `pty.spawn` L1075, 3 Low Path Traversal
|
|
25
|
+
at L2287/L2289/L2598) verified identical fingerprint vs HEAD~1 — out
|
|
26
|
+
of #25 surgical scope. Tracked in **dmsdc-ai/aigentry-telepty#26** for
|
|
27
|
+
follow-up PR.
|
|
28
|
+
|
|
5
29
|
## [0.4.0] — 2026-05-15
|
|
6
30
|
|
|
7
31
|
### Added — Phase 1 sidecar supervisor spike (M1–M5)
|
package/cli.js
CHANGED
|
@@ -17,6 +17,7 @@ const { getRuntimeInfo } = require('./runtime-info');
|
|
|
17
17
|
const { formatHostLabel, groupSessionsByHost, pickSessionTarget } = require('./session-routing');
|
|
18
18
|
const { buildSharedContextPrompt, createSharedContextDescriptor, ensureSharedContextFile } = require('./shared-context');
|
|
19
19
|
const { runInteractiveSkillInstaller } = require('./skill-installer');
|
|
20
|
+
const { resolveWindowsExecutable } = require('./src/win-resolve-executable');
|
|
20
21
|
const crossMachine = require('./cross-machine');
|
|
21
22
|
const { parseHostSpec, buildDaemonUrl, buildDaemonWsUrl } = require('./host-spec');
|
|
22
23
|
const { FileMailbox } = require('./src/mailbox/index');
|
|
@@ -1068,7 +1069,10 @@ async function main() {
|
|
|
1068
1069
|
}
|
|
1069
1070
|
|
|
1070
1071
|
function spawnChild() {
|
|
1071
|
-
|
|
1072
|
+
// Windows: walk %PATHEXT% so bare names (`claude`, `codex`, `gemini`)
|
|
1073
|
+
// resolve to their npm-global `.cmd`/`.ps1` shims. POSIX: no-op. (#25)
|
|
1074
|
+
const resolvedCommand = resolveWindowsExecutable(command, process.env);
|
|
1075
|
+
child = pty.spawn(resolvedCommand, cmdArgs, {
|
|
1072
1076
|
name: 'xterm-256color',
|
|
1073
1077
|
cols: process.stdout.columns || 80,
|
|
1074
1078
|
rows: process.stdout.rows || 30,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dmsdc-ai/aigentry-telepty",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"main": "daemon.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"aigentry-telepty": "install.js",
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
"CHANGELOG.md"
|
|
34
34
|
],
|
|
35
35
|
"scripts": {
|
|
36
|
-
"test": "node --test test/auth.test.js test/daemon.test.js test/daemon-singleton.test.js test/cli.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/mailbox-lock.test.js test/report-enforcement.test.js test/enforce-report.test.js test/submit-gate.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/host-spec.test.js test/cross-host-inject.test.js test/init.test.js && git diff --exit-code tests/snippet-protocol/v1/",
|
|
37
|
-
"test:watch": "node --test --watch test/auth.test.js test/daemon.test.js test/daemon-singleton.test.js test/cli.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/mailbox-lock.test.js test/report-enforcement.test.js test/enforce-report.test.js test/submit-gate.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/host-spec.test.js test/cross-host-inject.test.js test/init.test.js",
|
|
38
|
-
"test:ci": "node --test --test-reporter=spec test/auth.test.js test/daemon.test.js test/daemon-singleton.test.js test/cli.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/mailbox-lock.test.js test/report-enforcement.test.js test/enforce-report.test.js test/submit-gate.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/host-spec.test.js test/cross-host-inject.test.js test/init.test.js && git diff --exit-code tests/snippet-protocol/v1/",
|
|
36
|
+
"test": "node --test test/auth.test.js test/daemon.test.js test/daemon-singleton.test.js test/cli.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/mailbox-lock.test.js test/report-enforcement.test.js test/enforce-report.test.js test/submit-gate.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/host-spec.test.js test/cross-host-inject.test.js test/init.test.js test/win-resolve-executable.test.js && git diff --exit-code tests/snippet-protocol/v1/",
|
|
37
|
+
"test:watch": "node --test --watch test/auth.test.js test/daemon.test.js test/daemon-singleton.test.js test/cli.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/mailbox-lock.test.js test/report-enforcement.test.js test/enforce-report.test.js test/submit-gate.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/host-spec.test.js test/cross-host-inject.test.js test/init.test.js test/win-resolve-executable.test.js",
|
|
38
|
+
"test:ci": "node --test --test-reporter=spec test/auth.test.js test/daemon.test.js test/daemon-singleton.test.js test/cli.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/mailbox-lock.test.js test/report-enforcement.test.js test/enforce-report.test.js test/submit-gate.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/host-spec.test.js test/cross-host-inject.test.js test/init.test.js test/win-resolve-executable.test.js && git diff --exit-code tests/snippet-protocol/v1/",
|
|
39
39
|
"regen-fixtures": "node scripts/regen-snippet-fixtures.js"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// src/win-resolve-executable.js — Windows PATHEXT-aware executable resolver
|
|
2
|
+
//
|
|
3
|
+
// Fixes #25: `telepty allow ... <bare-command>` on Windows fails with
|
|
4
|
+
// `ERROR_FILE_NOT_FOUND` because node-pty's `CreateProcessW` does not walk
|
|
5
|
+
// `%PATHEXT%` the way cmd.exe shell does. npm-global CLIs install as
|
|
6
|
+
// `<cmd>.cmd` / `<cmd>.ps1`, so the bare name `claude` resolves to nothing.
|
|
7
|
+
//
|
|
8
|
+
// On POSIX this resolver is a no-op (execve handles PATH lookup natively).
|
|
9
|
+
//
|
|
10
|
+
// Exports:
|
|
11
|
+
// resolveWindowsExecutable(command, env = process.env, opts = {})
|
|
12
|
+
// → resolved absolute path on Windows when bare command is found via
|
|
13
|
+
// PATH × PATHEXT walk, the original `command` on POSIX, or throws.
|
|
14
|
+
//
|
|
15
|
+
// Constraints honored:
|
|
16
|
+
// - Constitution §1 lightweight: ≤80 lines, fs + path + process only.
|
|
17
|
+
// - Constitution §2 cross-platform: POSIX behavior unchanged.
|
|
18
|
+
// - Constitution §17 무의존: no new dependencies.
|
|
19
|
+
|
|
20
|
+
'use strict';
|
|
21
|
+
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
|
|
25
|
+
const DEFAULT_PATHEXT = '.COM;.EXE;.BAT;.CMD;.VBS;.JS';
|
|
26
|
+
|
|
27
|
+
function resolveWindowsExecutable(command, env, opts) {
|
|
28
|
+
if (typeof command !== 'string' || command.length === 0) {
|
|
29
|
+
throw new Error('telepty: resolveWindowsExecutable requires a non-empty command');
|
|
30
|
+
}
|
|
31
|
+
const e = env || process.env;
|
|
32
|
+
const o = opts || {};
|
|
33
|
+
const platform = o.platform || process.platform;
|
|
34
|
+
const existsSync = o.existsSync || fs.existsSync;
|
|
35
|
+
|
|
36
|
+
if (platform !== 'win32') return command;
|
|
37
|
+
|
|
38
|
+
// Always use win32 path semantics in the Windows branch so behavior is
|
|
39
|
+
// identical when the resolver runs on a Windows host AND when tests mock
|
|
40
|
+
// `platform: 'win32'` on a POSIX host.
|
|
41
|
+
const p = path.win32;
|
|
42
|
+
|
|
43
|
+
// Already extension-bearing absolute path → trust if it exists.
|
|
44
|
+
if (p.isAbsolute(command)) {
|
|
45
|
+
return existsSync(command) ? command : tryWithExt(command, e, existsSync, command);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Contains a separator → resolve relative to cwd, then try with extensions.
|
|
49
|
+
if (command.includes('\\') || command.includes('/')) {
|
|
50
|
+
const abs = p.resolve(command);
|
|
51
|
+
return existsSync(abs) ? abs : tryWithExt(abs, e, existsSync, command);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Bare name → walk PATH × PATHEXT.
|
|
55
|
+
const exts = parseExts(e.PATHEXT || DEFAULT_PATHEXT);
|
|
56
|
+
const dirs = (e.PATH || '').split(';').filter(Boolean);
|
|
57
|
+
for (const dir of dirs) {
|
|
58
|
+
for (const ext of exts) {
|
|
59
|
+
const candidate = p.join(dir, command + ext);
|
|
60
|
+
if (existsSync(candidate)) return candidate;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
throw new Error(
|
|
64
|
+
`telepty: cannot find executable "${command}" on PATH (Windows PATHEXT walk failed)`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function tryWithExt(absPath, env, existsSync, originalCommand) {
|
|
69
|
+
const exts = parseExts(env.PATHEXT || DEFAULT_PATHEXT);
|
|
70
|
+
for (const ext of exts) {
|
|
71
|
+
if (ext === '') continue;
|
|
72
|
+
const candidate = absPath + ext;
|
|
73
|
+
if (existsSync(candidate)) return candidate;
|
|
74
|
+
}
|
|
75
|
+
throw new Error(
|
|
76
|
+
`telepty: cannot find executable "${originalCommand}" (Windows PATHEXT walk failed)`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function parseExts(pathext) {
|
|
81
|
+
// Always include empty string first so an already-extension-bearing command
|
|
82
|
+
// matches before the PATHEXT-suffixed candidates.
|
|
83
|
+
const list = pathext.split(';').map((s) => s.trim()).filter(Boolean);
|
|
84
|
+
return ['', ...list];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = { resolveWindowsExecutable };
|