@phnx-labs/agents-cli 1.20.33 → 1.20.35
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 +29 -0
- package/README.md +32 -2
- package/dist/commands/computer-actions.d.ts +15 -0
- package/dist/commands/computer-actions.js +120 -22
- package/dist/commands/computer.d.ts +23 -0
- package/dist/commands/computer.js +45 -3
- package/dist/commands/cost.js +17 -12
- package/dist/commands/doctor.d.ts +11 -0
- package/dist/commands/doctor.js +108 -14
- package/dist/commands/events.d.ts +16 -0
- package/dist/commands/events.js +173 -0
- package/dist/commands/exec.js +6 -1
- package/dist/commands/hosts.js +28 -24
- package/dist/commands/import.js +1 -1
- package/dist/commands/inspect.d.ts +2 -0
- package/dist/commands/inspect.js +54 -7
- package/dist/commands/logs.d.ts +17 -0
- package/dist/commands/logs.js +139 -0
- package/dist/commands/models.d.ts +3 -0
- package/dist/commands/models.js +27 -8
- package/dist/commands/permissions.js +9 -2
- package/dist/commands/repo.d.ts +34 -0
- package/dist/commands/repo.js +243 -65
- package/dist/commands/resource-view.d.ts +20 -0
- package/dist/commands/resource-view.js +90 -28
- package/dist/commands/rules.js +1 -1
- package/dist/commands/secrets-migrate.js +23 -11
- package/dist/commands/secrets.d.ts +20 -0
- package/dist/commands/secrets.js +93 -17
- package/dist/commands/sessions-resume.d.ts +2 -0
- package/dist/commands/sessions-resume.js +209 -0
- package/dist/commands/sessions-tail.d.ts +10 -0
- package/dist/commands/sessions-tail.js +11 -0
- package/dist/commands/sessions.d.ts +73 -1
- package/dist/commands/sessions.js +273 -53
- package/dist/commands/status.d.ts +12 -0
- package/dist/commands/status.js +81 -0
- package/dist/commands/teams.js +70 -6
- package/dist/commands/versions.js +2 -1
- package/dist/commands/view.d.ts +42 -0
- package/dist/commands/view.js +213 -83
- package/dist/commands/wallet.d.ts +6 -0
- package/dist/commands/wallet.js +22 -5
- package/dist/index.js +61 -32
- package/dist/lib/acp/harnesses.d.ts +1 -1
- package/dist/lib/acp/harnesses.js +2 -2
- package/dist/lib/agents.d.ts +12 -0
- package/dist/lib/agents.js +145 -41
- package/dist/lib/browser/cdp.js +5 -1
- package/dist/lib/browser/chrome.js +20 -0
- package/dist/lib/browser/drivers/ssh.d.ts +27 -0
- package/dist/lib/browser/drivers/ssh.js +94 -19
- package/dist/lib/browser/ipc.d.ts +3 -0
- package/dist/lib/browser/ipc.js +13 -9
- package/dist/lib/browser/service.js +71 -15
- package/dist/lib/daemon.d.ts +35 -0
- package/dist/lib/daemon.js +33 -5
- package/dist/lib/devices/connect.d.ts +3 -2
- package/dist/lib/devices/connect.js +5 -3
- package/dist/lib/doctor-diff.js +29 -2
- package/dist/lib/drift-sync.d.ts +43 -0
- package/dist/lib/drift-sync.js +179 -0
- package/dist/lib/events.d.ts +9 -2
- package/dist/lib/events.js +70 -11
- package/dist/lib/exec.d.ts +15 -0
- package/dist/lib/exec.js +43 -11
- package/dist/lib/hooks.js +8 -2
- package/dist/lib/hosts/dispatch.js +6 -7
- package/dist/lib/hosts/logs.d.ts +16 -0
- package/dist/lib/hosts/logs.js +45 -0
- package/dist/lib/hosts/progress.d.ts +66 -0
- package/dist/lib/hosts/progress.js +125 -17
- package/dist/lib/hosts/ready.d.ts +23 -2
- package/dist/lib/hosts/ready.js +35 -13
- package/dist/lib/hosts/reconcile.d.ts +53 -0
- package/dist/lib/hosts/reconcile.js +81 -0
- package/dist/lib/hosts/tasks.d.ts +8 -0
- package/dist/lib/hosts/tasks.js +14 -0
- package/dist/lib/permissions.d.ts +4 -0
- package/dist/lib/permissions.js +35 -0
- package/dist/lib/picker.d.ts +24 -0
- package/dist/lib/picker.js +124 -0
- package/dist/lib/platform/winpath.d.ts +31 -2
- package/dist/lib/platform/winpath.js +133 -24
- package/dist/lib/plugin-marketplace.d.ts +30 -0
- package/dist/lib/plugin-marketplace.js +215 -2
- package/dist/lib/plugins.d.ts +5 -0
- package/dist/lib/plugins.js +45 -4
- package/dist/lib/pwsh.d.ts +11 -0
- package/dist/lib/pwsh.js +13 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/agent.d.ts +42 -1
- package/dist/lib/secrets/agent.js +89 -11
- package/dist/lib/secrets/bundles.js +40 -9
- package/dist/lib/secrets/filestore.js +31 -1
- package/dist/lib/secrets/index.d.ts +33 -1
- package/dist/lib/secrets/index.js +90 -9
- package/dist/lib/secrets/windows.d.ts +74 -0
- package/dist/lib/secrets/windows.js +440 -0
- package/dist/lib/session/active.d.ts +7 -0
- package/dist/lib/session/active.js +11 -3
- package/dist/lib/session/discover.js +94 -0
- package/dist/lib/session/parse.d.ts +6 -0
- package/dist/lib/session/parse.js +283 -2
- package/dist/lib/session/pid-registry.d.ts +21 -0
- package/dist/lib/session/pid-registry.js +80 -0
- package/dist/lib/session/remote-active.d.ts +26 -0
- package/dist/lib/session/remote-active.js +141 -0
- package/dist/lib/session/remote.js +2 -6
- package/dist/lib/session/render.js +1 -1
- package/dist/lib/session/sync/config.d.ts +8 -0
- package/dist/lib/session/sync/config.js +11 -2
- package/dist/lib/session/types.d.ts +1 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/shims.d.ts +30 -2
- package/dist/lib/shims.js +107 -50
- package/dist/lib/ssh-exec.d.ts +26 -3
- package/dist/lib/ssh-exec.js +45 -3
- package/dist/lib/ssh-tunnel.d.ts +24 -5
- package/dist/lib/ssh-tunnel.js +60 -62
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +3 -0
- package/dist/lib/startup/command-registry.js +6 -0
- package/dist/lib/sync-status.d.ts +102 -0
- package/dist/lib/sync-status.js +135 -0
- package/dist/lib/teams/agents.d.ts +24 -0
- package/dist/lib/teams/agents.js +30 -1
- package/dist/lib/teams/registry.js +25 -9
- package/dist/lib/terminal/backends/ghostty.d.ts +6 -0
- package/dist/lib/terminal/backends/ghostty.js +69 -0
- package/dist/lib/terminal/backends/index.d.ts +18 -0
- package/dist/lib/terminal/backends/index.js +29 -0
- package/dist/lib/terminal/backends/iterm.d.ts +6 -0
- package/dist/lib/terminal/backends/iterm.js +62 -0
- package/dist/lib/terminal/backends/tmux.d.ts +14 -0
- package/dist/lib/terminal/backends/tmux.js +23 -0
- package/dist/lib/terminal/engine.d.ts +39 -0
- package/dist/lib/terminal/engine.js +54 -0
- package/dist/lib/terminal/index.d.ts +14 -0
- package/dist/lib/terminal/index.js +5 -0
- package/dist/lib/terminal/policy.d.ts +11 -0
- package/dist/lib/terminal/policy.js +11 -0
- package/dist/lib/terminal/quote.d.ts +11 -0
- package/dist/lib/terminal/quote.js +13 -0
- package/dist/lib/terminal/shell.d.ts +6 -0
- package/dist/lib/terminal/shell.js +23 -0
- package/dist/lib/terminal/transport.d.ts +15 -0
- package/dist/lib/terminal/transport.js +40 -0
- package/dist/lib/terminal/types.d.ts +59 -0
- package/dist/lib/terminal/types.js +13 -0
- package/dist/lib/types.d.ts +27 -1
- package/dist/lib/usage.d.ts +30 -0
- package/dist/lib/usage.js +159 -2
- package/dist/lib/versions.d.ts +7 -3
- package/dist/lib/versions.js +10 -5
- package/dist/lib/whats-new.d.ts +9 -0
- package/dist/lib/whats-new.js +35 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -51,9 +51,11 @@ if (IS_DEV_BUILD) {
|
|
|
51
51
|
// module on each invocation (which loaded the whole ~50-module tree before the
|
|
52
52
|
// first byte of output), the registry maps a command name to a thunk that
|
|
53
53
|
// imports only what that command needs. See src/lib/startup/command-registry.ts.
|
|
54
|
-
import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadPackages, loadDaemon, loadRoutines, loadRun, loadDefaults, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadProfiles, loadSecrets, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadRefreshRules, loadDrive, loadFactory, loadUsage, loadCost, loadBudget, loadAlias, loadPty, loadTmux, loadBrowser, loadComputer, loadHosts, loadSsh, loadPull, loadPush, loadRepo, loadSetup, } from './lib/startup/command-registry.js';
|
|
54
|
+
import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadPackages, loadDaemon, loadRoutines, loadRun, loadDefaults, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadStatus, loadProfiles, loadSecrets, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadRefreshRules, loadDrive, loadFactory, loadUsage, loadCost, loadBudget, loadAlias, loadPty, loadTmux, loadBrowser, loadComputer, loadHosts, loadLogs, loadEvents, loadSsh, loadPull, loadPush, loadRepo, loadSetup, } from './lib/startup/command-registry.js';
|
|
55
55
|
import { applyGlobalHelpConventions } from './lib/help.js';
|
|
56
|
+
import { renderWhatsNew } from './lib/whats-new.js';
|
|
56
57
|
import { IS_WINDOWS } from './lib/platform/index.js';
|
|
58
|
+
import { emit, redactArgs } from './lib/events.js';
|
|
57
59
|
// Transparent shim delegate: the generated Windows `.cmd` shims invoke
|
|
58
60
|
// `agents __shim <agent>[@version] <raw args>`. Intercept here, before commander
|
|
59
61
|
// parses anything, so the agent's own flags (`--help`, `--version`, etc.) pass
|
|
@@ -75,6 +77,56 @@ program
|
|
|
75
77
|
.version(VERSION)
|
|
76
78
|
.helpOption('-h, --help', 'Show help')
|
|
77
79
|
.addHelpCommand(false);
|
|
80
|
+
// ─── Audit backbone ────────────────────────────────────────────────────────────
|
|
81
|
+
// One choke point logs every `agents <module> <cmd>` invocation to the structured
|
|
82
|
+
// event log — so team create/disband, agent run, secrets access, and everything
|
|
83
|
+
// else is captured generically (with SSH/remote-user attribution added in emit()),
|
|
84
|
+
// no per-command wiring. `agents events` reads it back. Attached to the root
|
|
85
|
+
// program, so it's inherited by every subcommand regardless of lazy registration.
|
|
86
|
+
/** Command path from the acting command up to (but excluding) the `agents` root. */
|
|
87
|
+
function auditCommandPath(cmd) {
|
|
88
|
+
const parts = [];
|
|
89
|
+
let c = cmd;
|
|
90
|
+
while (c && c.name() && c.name() !== 'agents') {
|
|
91
|
+
parts.unshift(c.name());
|
|
92
|
+
c = c.parent;
|
|
93
|
+
}
|
|
94
|
+
return parts;
|
|
95
|
+
}
|
|
96
|
+
const auditStarts = new WeakMap();
|
|
97
|
+
program.hook('preAction', (_thisCommand, actionCommand) => {
|
|
98
|
+
try {
|
|
99
|
+
const parts = auditCommandPath(actionCommand);
|
|
100
|
+
if (parts.length === 0)
|
|
101
|
+
return;
|
|
102
|
+
auditStarts.set(actionCommand, Date.now());
|
|
103
|
+
emit('command.start', {
|
|
104
|
+
module: parts[0],
|
|
105
|
+
command: parts.join(' '),
|
|
106
|
+
args: redactArgs(actionCommand.args),
|
|
107
|
+
cwd: process.cwd(),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// Audit logging must never break command dispatch.
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
program.hook('postAction', (_thisCommand, actionCommand) => {
|
|
115
|
+
try {
|
|
116
|
+
const parts = auditCommandPath(actionCommand);
|
|
117
|
+
if (parts.length === 0)
|
|
118
|
+
return;
|
|
119
|
+
const started = auditStarts.get(actionCommand);
|
|
120
|
+
emit('command.end', {
|
|
121
|
+
module: parts[0],
|
|
122
|
+
command: parts.join(' '),
|
|
123
|
+
...(started !== undefined ? { durationMs: Date.now() - started } : {}),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Best-effort completion record; the start line is the durable audit fact.
|
|
128
|
+
}
|
|
129
|
+
});
|
|
78
130
|
// Custom help for the main program only
|
|
79
131
|
const originalHelpInformation = program.helpInformation.bind(program);
|
|
80
132
|
program.helpInformation = function () {
|
|
@@ -121,6 +173,7 @@ Run and dispatch:
|
|
|
121
173
|
teams Coordinate multiple agents on shared work
|
|
122
174
|
routines Run agents on a cron schedule (scheduler auto-starts)
|
|
123
175
|
sessions Browse, search, and replay past runs (live-search in TTY; grouped by workspace)
|
|
176
|
+
logs [id] Show a run's log — host-dispatch task or session; -f to follow
|
|
124
177
|
browser Automate a browser — navigate, click, screenshot, console, network
|
|
125
178
|
pty Drive interactive terminal programs (REPLs, TUIs) via a persistent PTY session
|
|
126
179
|
|
|
@@ -175,41 +228,13 @@ async function showWhatsNew(fromVersion, toVersion) {
|
|
|
175
228
|
const response = await fetch(`https://unpkg.com/@phnx-labs/agents-cli@${toVersion}/CHANGELOG.md`);
|
|
176
229
|
if (!response.ok)
|
|
177
230
|
return;
|
|
178
|
-
const
|
|
179
|
-
const lines = changelog.split('\n');
|
|
180
|
-
const relevantChanges = [];
|
|
181
|
-
let inRelevantSection = false;
|
|
182
|
-
let currentVersion = '';
|
|
183
|
-
for (const line of lines) {
|
|
184
|
-
const versionMatch = line.match(/^## (\d+\.\d+\.\d+)/);
|
|
185
|
-
if (versionMatch) {
|
|
186
|
-
currentVersion = versionMatch[1];
|
|
187
|
-
// Only the range the user actually moved through: (fromVersion, toVersion].
|
|
188
|
-
// Bounding the top end matters when upgrading to a specific older
|
|
189
|
-
// version, and guards against a changelog that lists unreleased entries.
|
|
190
|
-
const inRange = compareVersions(currentVersion, fromVersion) > 0 &&
|
|
191
|
-
compareVersions(currentVersion, toVersion) <= 0;
|
|
192
|
-
inRelevantSection = inRange;
|
|
193
|
-
if (inRelevantSection) {
|
|
194
|
-
relevantChanges.push('');
|
|
195
|
-
relevantChanges.push(chalk.bold(`v${currentVersion}`));
|
|
196
|
-
}
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
if (inRelevantSection && line.trim()) {
|
|
200
|
-
if (line.startsWith('**') && line.endsWith('**')) {
|
|
201
|
-
relevantChanges.push(chalk.cyan(line.replace(/\*\*/g, '')));
|
|
202
|
-
}
|
|
203
|
-
else if (line.startsWith('- ')) {
|
|
204
|
-
relevantChanges.push(chalk.gray(` ${line}`));
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
231
|
+
const relevantChanges = renderWhatsNew(await response.text(), fromVersion, toVersion);
|
|
208
232
|
if (relevantChanges.length > 0) {
|
|
209
233
|
console.log(chalk.bold("\nWhat's new:\n"));
|
|
210
234
|
for (const line of relevantChanges) {
|
|
211
235
|
console.log(line);
|
|
212
236
|
}
|
|
237
|
+
console.log(chalk.gray('\nFull notes: https://github.com/phnx-labs/agents-cli/blob/main/CHANGELOG.md'));
|
|
213
238
|
console.log();
|
|
214
239
|
}
|
|
215
240
|
}
|
|
@@ -764,6 +789,7 @@ async function registerAllEagerCommands() {
|
|
|
764
789
|
await reg(loadTrash);
|
|
765
790
|
await reg(loadRestore);
|
|
766
791
|
await reg(loadDoctor);
|
|
792
|
+
await reg(loadStatus);
|
|
767
793
|
registerExecAliasCommand(program);
|
|
768
794
|
await reg(loadProfiles);
|
|
769
795
|
await reg(loadSecrets);
|
|
@@ -784,6 +810,8 @@ async function registerAllEagerCommands() {
|
|
|
784
810
|
await reg(loadBrowser);
|
|
785
811
|
await reg(loadComputer);
|
|
786
812
|
await reg(loadHosts);
|
|
813
|
+
await reg(loadLogs);
|
|
814
|
+
await reg(loadEvents);
|
|
787
815
|
await reg(loadSsh);
|
|
788
816
|
registerJobsCronAliasCommand(program, 'jobs');
|
|
789
817
|
registerJobsCronAliasCommand(program, 'cron');
|
|
@@ -894,7 +922,8 @@ if (!helpOrVersionRequested) {
|
|
|
894
922
|
// fire-and-forget the next background sync. System repo gets a real fast-forward
|
|
895
923
|
// pull (read-only locally, safe). User repo and extras get fetch-only + a
|
|
896
924
|
// status marker that we'll print on the *next* invocation.
|
|
897
|
-
const { spawnDetachedSync } = await import('./lib/auto-pull.js');
|
|
925
|
+
const { spawnDetachedSync, printPendingUpdateNotices } = await import('./lib/auto-pull.js');
|
|
926
|
+
printPendingUpdateNotices();
|
|
898
927
|
spawnDetachedSync();
|
|
899
928
|
}
|
|
900
929
|
// First-run experience: no args + no config yet + TTY -> launch interactive setup.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Per-harness spawn configuration for Agent Client Protocol (ACP) mode.
|
|
3
3
|
*
|
|
4
4
|
* Each entry describes how to launch a coding agent CLI as an ACP server
|
|
5
|
-
* (stdio JSON-RPC). Unsupported harnesses (amp,
|
|
5
|
+
* (stdio JSON-RPC). Unsupported harnesses (amp, goose-stdio) are omitted;
|
|
6
6
|
* callers should fall back to the legacy direct-exec path for those.
|
|
7
7
|
*
|
|
8
8
|
* Sources: https://agentclientprotocol.com/get-started/agents + vendor docs.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Per-harness spawn configuration for Agent Client Protocol (ACP) mode.
|
|
3
3
|
*
|
|
4
4
|
* Each entry describes how to launch a coding agent CLI as an ACP server
|
|
5
|
-
* (stdio JSON-RPC). Unsupported harnesses (amp,
|
|
5
|
+
* (stdio JSON-RPC). Unsupported harnesses (amp, goose-stdio) are omitted;
|
|
6
6
|
* callers should fall back to the legacy direct-exec path for those.
|
|
7
7
|
*
|
|
8
8
|
* Sources: https://agentclientprotocol.com/get-started/agents + vendor docs.
|
|
@@ -61,7 +61,7 @@ export const ACP_HARNESSES = {
|
|
|
61
61
|
// goose: ACP over HTTP via `goosed`, not a clean stdio subcommand.
|
|
62
62
|
// copilot, kiro: excluded for now (not installed in the reference environment,
|
|
63
63
|
// no local verification possible).
|
|
64
|
-
// amp
|
|
64
|
+
// amp: not on the ACP agents list.
|
|
65
65
|
};
|
|
66
66
|
/** Returns the ACP spawn spec for an agent, or undefined if the harness does not speak ACP. */
|
|
67
67
|
export function getAcpSpec(agent) {
|
package/dist/lib/agents.d.ts
CHANGED
|
@@ -219,6 +219,18 @@ export declare const AGENT_NAME_ALIASES: Record<string, AgentId>;
|
|
|
219
219
|
export declare function resolveAgentName(input: string): AgentId | null;
|
|
220
220
|
/** Check whether the input string matches any known agent name or alias. */
|
|
221
221
|
export declare function isAgentName(input: string): boolean;
|
|
222
|
+
/**
|
|
223
|
+
* Build the deprecation notice lines for an agent, or null if it isn't
|
|
224
|
+
* deprecated. Split from the printer so tests can assert the content without
|
|
225
|
+
* capturing stdout. Lines are plain (uncolored) text.
|
|
226
|
+
*/
|
|
227
|
+
export declare function deprecationNotice(agent: AgentId): string[] | null;
|
|
228
|
+
/**
|
|
229
|
+
* Print a deprecation warning (yellow) if the agent's registry entry carries a
|
|
230
|
+
* `deprecated` marker; no-op otherwise. Call from any user entry point that
|
|
231
|
+
* acts on a chosen agent — install (`agents add`) and `agents teams add`.
|
|
232
|
+
*/
|
|
233
|
+
export declare function warnAgentDeprecated(agent: AgentId): void;
|
|
222
234
|
/** Format an error message for an unrecognized agent name, listing valid options. */
|
|
223
235
|
export declare function formatAgentError(agentName: string, validAgents?: AgentId[]): string;
|
|
224
236
|
export {};
|
package/dist/lib/agents.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Core agent configuration and detection module.
|
|
3
3
|
*
|
|
4
4
|
* Defines the canonical registry of all supported AI coding agents (Claude, Codex,
|
|
5
|
-
* Gemini, Cursor, OpenCode, OpenClaw, Copilot, Amp, Kiro, Goose,
|
|
5
|
+
* Gemini, Cursor, OpenCode, OpenClaw, Copilot, Amp, Kiro, Goose, Grok) with their
|
|
6
6
|
* CLI commands, config paths, capability flags, and MCP integration points.
|
|
7
7
|
*
|
|
8
8
|
* Provides functions for detecting installed CLIs, resolving version-managed binaries,
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { execFile } from 'child_process';
|
|
12
12
|
import { promisify } from 'util';
|
|
13
|
+
import * as crypto from 'crypto';
|
|
13
14
|
import * as fs from 'fs';
|
|
14
15
|
import * as path from 'path';
|
|
15
16
|
import * as os from 'os';
|
|
@@ -249,6 +250,16 @@ export const AGENTS = {
|
|
|
249
250
|
variableSyntax: '{{args}}',
|
|
250
251
|
supportsHooks: true,
|
|
251
252
|
nativeAgentsSkillsDir: true,
|
|
253
|
+
// Google retired the Gemini CLI (announced at Google I/O 2026, May 19); the `gemini`
|
|
254
|
+
// command stopped serving free/Pro/Ultra requests on June 18, 2026. Antigravity CLI
|
|
255
|
+
// (`agy`) is the official successor. See warnAgentDeprecated() for the surfaced warning.
|
|
256
|
+
deprecated: {
|
|
257
|
+
by: 'Google',
|
|
258
|
+
date: 'June 18, 2026',
|
|
259
|
+
reason: 'The Gemini CLI was retired for free, Pro, and Ultra tiers and no longer serves requests (announced at Google I/O 2026 on May 19).',
|
|
260
|
+
replacement: 'antigravity',
|
|
261
|
+
url: 'https://developers.googleblog.com/an-important-update-transitioning-gemini-cli-to-antigravity-cli/',
|
|
262
|
+
},
|
|
252
263
|
// gemini hooks: shipped in v0.26.0 (Jan 2026); older binaries silently ignore the `hooks` key.
|
|
253
264
|
capabilities: { hooks: { since: '0.26.0' }, mcp: true, allowlist: false, skills: true, commands: true, plugins: false, subagents: false, rules: { file: 'GEMINI.md' }, workflows: false, modes: ['plan', 'edit', 'skip'], rulesImports: true },
|
|
254
265
|
},
|
|
@@ -316,11 +327,16 @@ export const AGENTS = {
|
|
|
316
327
|
commandsSubdir: 'commands',
|
|
317
328
|
skillsDir: path.join(HOME, '.copilot', 'skills'),
|
|
318
329
|
hooksDir: 'hooks',
|
|
330
|
+
// Copilot reads a plugin's manifest from the plugin ROOT (plugin.json),
|
|
331
|
+
// not `.claude-plugin/plugin.json`. Mirror it there. Verified against the
|
|
332
|
+
// GitHub Copilot CLI (1.0.56): `copilot plugin install` produces an
|
|
333
|
+
// installed plugin dir whose manifest sits at the root.
|
|
334
|
+
pluginManifestDir: '.',
|
|
319
335
|
instructionsFile: 'AGENTS.md',
|
|
320
336
|
format: 'markdown',
|
|
321
337
|
variableSyntax: '$ARGUMENTS',
|
|
322
338
|
supportsHooks: false,
|
|
323
|
-
capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins:
|
|
339
|
+
capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: true, subagents: false, rules: { file: 'AGENTS.md' }, workflows: false, modes: ['plan', 'edit', 'auto', 'skip'] },
|
|
324
340
|
},
|
|
325
341
|
amp: {
|
|
326
342
|
id: 'amp',
|
|
@@ -375,24 +391,6 @@ export const AGENTS = {
|
|
|
375
391
|
supportsHooks: false,
|
|
376
392
|
capabilities: { hooks: false, mcp: true, allowlist: false, skills: false, commands: false, plugins: false, subagents: false, rules: { file: 'AGENTS.md' }, workflows: false, modes: ['edit'] },
|
|
377
393
|
},
|
|
378
|
-
roo: {
|
|
379
|
-
id: 'roo',
|
|
380
|
-
name: 'Roo Code',
|
|
381
|
-
color: 'cyanBright',
|
|
382
|
-
cliCommand: 'roo',
|
|
383
|
-
npmPackage: '',
|
|
384
|
-
installScript: 'curl -fsSL https://raw.githubusercontent.com/RooCodeInc/Roo-Code/main/apps/cli/install.sh | sh',
|
|
385
|
-
configDir: path.join(HOME, '.roo'),
|
|
386
|
-
commandsDir: path.join(HOME, '.roo', 'commands'),
|
|
387
|
-
commandsSubdir: 'commands',
|
|
388
|
-
skillsDir: path.join(HOME, '.roo', 'skills'),
|
|
389
|
-
hooksDir: 'hooks',
|
|
390
|
-
instructionsFile: 'AGENTS.md',
|
|
391
|
-
format: 'markdown',
|
|
392
|
-
variableSyntax: '$ARGUMENTS',
|
|
393
|
-
supportsHooks: false,
|
|
394
|
-
capabilities: { hooks: false, mcp: true, allowlist: false, skills: true, commands: true, plugins: false, subagents: false, rules: { file: 'AGENTS.md' }, workflows: false, modes: ['plan', 'edit'] },
|
|
395
|
-
},
|
|
396
394
|
// Google Antigravity CLI (`agy`) — official replacement for Gemini CLI as of IO 2026.
|
|
397
395
|
// configDir nests inside `~/.gemini/` since agy shares the parent dir with the Gemini
|
|
398
396
|
// CLI but isolates its own state in the `antigravity-cli/` subdir. Per-version HOME
|
|
@@ -498,11 +496,26 @@ export const AGENTS = {
|
|
|
498
496
|
// Install: `curl -fsSL https://app.factory.ai/cli | sh` (no npm package).
|
|
499
497
|
// Binary is NOT in node_modules/.bin — the shim resolves the fixed install
|
|
500
498
|
// path ~/.local/bin/droid directly (see the droid branch in shims.ts).
|
|
501
|
-
// Config: `~/.factory/` (settings.json, mcp.json, droids/, commands
|
|
502
|
-
// Memory: native AGENTS.md. Subagents = custom droids (top-level
|
|
503
|
-
// in ~/.factory/droids/). Config isolation rides the ~/.factory
|
|
504
|
-
// switch (no FACTORY_HOME env var exists). Headless:
|
|
505
|
-
// with --auto low|medium|high, -o stream-json,
|
|
499
|
+
// Config: `~/.factory/` (settings.json, mcp.json, droids/, commands/, hooks/,
|
|
500
|
+
// plugins/). Memory: native AGENTS.md. Subagents = custom droids (top-level
|
|
501
|
+
// .md files in ~/.factory/droids/). Config isolation rides the ~/.factory
|
|
502
|
+
// symlink switch (no FACTORY_HOME env var exists). Headless:
|
|
503
|
+
// `droid exec "<prompt>"` with --auto low|medium|high, -o stream-json,
|
|
504
|
+
// -m <model>, -r <effort>.
|
|
505
|
+
//
|
|
506
|
+
// Hooks: Claude-shaped. settings.json carries a top-level `hooks` object keyed
|
|
507
|
+
// by event (PreToolUse, PostToolUse, UserPromptSubmit, SessionStart,
|
|
508
|
+
// SessionEnd, Stop, SubagentStop, Notification, PreCompact), each an array of
|
|
509
|
+
// `{ matcher?, hooks: [{ type: "command", command, timeout? }] }` matcher
|
|
510
|
+
// groups — verified against the droid binary's zod schema. So the Claude
|
|
511
|
+
// registrar is reused verbatim, just targeting `.factory/settings.json`.
|
|
512
|
+
//
|
|
513
|
+
// Plugins: native `droid plugin` command group + ~/.factory/plugins/ with the
|
|
514
|
+
// same marketplace layout Claude uses (known_marketplaces.json +
|
|
515
|
+
// marketplaces/<name>/plugins/<plugin>/). Droid's plugin manifest dir is
|
|
516
|
+
// `.factory-plugin/` (it also reads `.claude-plugin/` for compatibility) — set
|
|
517
|
+
// pluginManifestDir so syncPluginToVersion mirrors the manifest into it, the
|
|
518
|
+
// same pattern codex uses with `.codex-plugin`.
|
|
506
519
|
droid: {
|
|
507
520
|
id: 'droid',
|
|
508
521
|
name: 'Droid',
|
|
@@ -516,20 +529,21 @@ export const AGENTS = {
|
|
|
516
529
|
commandsSubdir: 'commands',
|
|
517
530
|
skillsDir: '', // no skills concept
|
|
518
531
|
hooksDir: 'hooks',
|
|
532
|
+
pluginManifestDir: '.factory-plugin',
|
|
519
533
|
instructionsFile: 'AGENTS.md',
|
|
520
534
|
format: 'markdown',
|
|
521
535
|
variableSyntax: '$ARGUMENTS',
|
|
522
|
-
supportsHooks:
|
|
536
|
+
supportsHooks: true,
|
|
523
537
|
// Factory Droid Computers (cloud VMs) reached via `droid computer ssh` +
|
|
524
538
|
// remote headless `droid exec`.
|
|
525
539
|
cloudProvider: 'factory',
|
|
526
540
|
capabilities: {
|
|
527
|
-
hooks:
|
|
541
|
+
hooks: true,
|
|
528
542
|
mcp: true,
|
|
529
543
|
allowlist: false,
|
|
530
544
|
skills: false,
|
|
531
545
|
commands: true,
|
|
532
|
-
plugins:
|
|
546
|
+
plugins: true,
|
|
533
547
|
subagents: true,
|
|
534
548
|
rules: { file: 'AGENTS.md' },
|
|
535
549
|
workflows: false,
|
|
@@ -802,6 +816,52 @@ function resolveAccountCredentialPath(base, ...segments) {
|
|
|
802
816
|
}
|
|
803
817
|
return null;
|
|
804
818
|
}
|
|
819
|
+
/**
|
|
820
|
+
* Factory Droid stores its OAuth credential encrypted at ~/.factory/auth.v2.file
|
|
821
|
+
* (AES-256-GCM, format `ivB64:tagB64:ctB64`) with the 32-byte key base64-stored
|
|
822
|
+
* in ~/.factory/auth.v2.key. On the keyfile-v2 source there is no OS-keychain /
|
|
823
|
+
* device binding — the key is on disk — so we can decrypt locally with no
|
|
824
|
+
* network call. The decrypted JSON credential's `access_token` is a WorkOS JWT
|
|
825
|
+
* carrying an `email` claim (plus org_id / role). We decode the claim WITHOUT
|
|
826
|
+
* verifying `exp`: the email is stable identity for display, not an
|
|
827
|
+
* authorization decision, so an expired token still yields the right address.
|
|
828
|
+
* Every failure (missing key file — e.g. a keyring-v2/legacy login with no
|
|
829
|
+
* on-disk key, a bad GCM tag, malformed JSON, or no email claim) returns null so
|
|
830
|
+
* the caller falls back to the file-presence signed-in signal. Never throws.
|
|
831
|
+
*/
|
|
832
|
+
function decryptDroidCredential(base) {
|
|
833
|
+
const filePath = resolveAccountCredentialPath(base, '.factory', 'auth.v2.file');
|
|
834
|
+
const keyPath = resolveAccountCredentialPath(base, '.factory', 'auth.v2.key');
|
|
835
|
+
if (!filePath || !keyPath)
|
|
836
|
+
return null;
|
|
837
|
+
try {
|
|
838
|
+
const blob = fs.readFileSync(filePath, 'utf-8').trim();
|
|
839
|
+
const key = Buffer.from(fs.readFileSync(keyPath, 'utf-8').trim(), 'base64');
|
|
840
|
+
if (key.length !== 32)
|
|
841
|
+
return null;
|
|
842
|
+
const [ivB64, tagB64, ctB64] = blob.split(':');
|
|
843
|
+
if (!ivB64 || !tagB64 || !ctB64)
|
|
844
|
+
return null;
|
|
845
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, Buffer.from(ivB64, 'base64'));
|
|
846
|
+
decipher.setAuthTag(Buffer.from(tagB64, 'base64'));
|
|
847
|
+
const plaintext = Buffer.concat([
|
|
848
|
+
decipher.update(Buffer.from(ctB64, 'base64')),
|
|
849
|
+
decipher.final(),
|
|
850
|
+
]).toString('utf-8');
|
|
851
|
+
const cred = JSON.parse(plaintext);
|
|
852
|
+
const claims = typeof cred?.access_token === 'string' ? decodeJwtPayload(cred.access_token) : null;
|
|
853
|
+
if (!claims)
|
|
854
|
+
return null;
|
|
855
|
+
return {
|
|
856
|
+
email: typeof claims.email === 'string' ? claims.email : null,
|
|
857
|
+
orgId: normalizeIdentityPart(claims.org_id ?? cred.active_organization_id),
|
|
858
|
+
role: typeof claims.role === 'string' ? claims.role : null,
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
catch {
|
|
862
|
+
return null;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
805
865
|
let cachedAgyKeychainSignedIn;
|
|
806
866
|
/**
|
|
807
867
|
* Antigravity (`agy`, a Codeium/Windsurf-based CLI) stores its OAuth token in
|
|
@@ -1034,12 +1094,29 @@ export async function getAccountInfo(agentId, home) {
|
|
|
1034
1094
|
return { ...empty, signedIn: true, accountId: userId, accountKey, lastActive };
|
|
1035
1095
|
}
|
|
1036
1096
|
case 'droid': {
|
|
1037
|
-
// Factory Droid stores auth at ~/.factory/auth.v2.file (
|
|
1038
|
-
//
|
|
1039
|
-
//
|
|
1040
|
-
//
|
|
1041
|
-
//
|
|
1042
|
-
//
|
|
1097
|
+
// Factory Droid stores auth at ~/.factory/auth.v2.file (AES-256-GCM,
|
|
1098
|
+
// decrypted with the on-disk ~/.factory/auth.v2.key). We decrypt locally
|
|
1099
|
+
// — no network — and surface the email/org/role from the WorkOS
|
|
1100
|
+
// access-token JWT, same as claude/codex/grok. If the credential can't be
|
|
1101
|
+
// decrypted (a keyring-v2/legacy login with no on-disk key, or a decrypt
|
|
1102
|
+
// failure) we fall back to the file-presence signed-in signal so the row
|
|
1103
|
+
// still reads as logged in — the conservative floor antigravity/kimi use.
|
|
1104
|
+
// `.factory` is the config dir on every platform (macOS/Linux
|
|
1105
|
+
// ~/.factory, Windows %USERPROFILE%\.factory).
|
|
1106
|
+
const decoded = decryptDroidCredential(base);
|
|
1107
|
+
if (decoded?.email) {
|
|
1108
|
+
const organizationId = decoded.orgId;
|
|
1109
|
+
const accountKey = buildIdentityKey(agentId, [['org', organizationId]]);
|
|
1110
|
+
return {
|
|
1111
|
+
...empty,
|
|
1112
|
+
email: decoded.email,
|
|
1113
|
+
organizationId,
|
|
1114
|
+
accountId: organizationId,
|
|
1115
|
+
accountKey,
|
|
1116
|
+
signedIn: true,
|
|
1117
|
+
lastActive,
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1043
1120
|
const authPath = resolveAccountCredentialPath(base, '.factory', 'auth.v2.file');
|
|
1044
1121
|
if (!authPath)
|
|
1045
1122
|
return { ...empty, lastActive };
|
|
@@ -1574,8 +1651,6 @@ export function getMcpConfigPathForHome(agentId, home) {
|
|
|
1574
1651
|
return path.join(home, '.kiro', 'settings', 'mcp.json');
|
|
1575
1652
|
case 'goose':
|
|
1576
1653
|
return path.join(home, '.config', 'goose', 'config.yaml');
|
|
1577
|
-
case 'roo':
|
|
1578
|
-
return path.join(home, '.roo', 'mcp.json');
|
|
1579
1654
|
case 'antigravity':
|
|
1580
1655
|
return path.join(home, '.gemini', 'antigravity-cli', 'mcp_config.json');
|
|
1581
1656
|
case 'grok':
|
|
@@ -1612,8 +1687,6 @@ function getProjectMcpConfigPath(agentId, cwd = process.cwd()) {
|
|
|
1612
1687
|
return path.join(cwd, '.kiro', 'settings', 'mcp.json');
|
|
1613
1688
|
case 'goose':
|
|
1614
1689
|
return path.join(cwd, '.goose', 'config.yaml');
|
|
1615
|
-
case 'roo':
|
|
1616
|
-
return path.join(cwd, '.roo', 'mcp.json');
|
|
1617
1690
|
case 'antigravity':
|
|
1618
1691
|
return path.join(cwd, '.gemini', 'antigravity-cli', 'mcp_config.json');
|
|
1619
1692
|
case 'grok':
|
|
@@ -1746,9 +1819,6 @@ export const AGENT_NAME_ALIASES = {
|
|
|
1746
1819
|
'kiro-cli': 'kiro',
|
|
1747
1820
|
goose: 'goose',
|
|
1748
1821
|
'block-goose': 'goose',
|
|
1749
|
-
roo: 'roo',
|
|
1750
|
-
'roo-code': 'roo',
|
|
1751
|
-
roocode: 'roo',
|
|
1752
1822
|
antigravity: 'antigravity',
|
|
1753
1823
|
'google-antigravity': 'antigravity',
|
|
1754
1824
|
agy: 'antigravity',
|
|
@@ -1789,6 +1859,40 @@ export function resolveAgentName(input) {
|
|
|
1789
1859
|
export function isAgentName(input) {
|
|
1790
1860
|
return resolveAgentName(input) !== null;
|
|
1791
1861
|
}
|
|
1862
|
+
/**
|
|
1863
|
+
* Build the deprecation notice lines for an agent, or null if it isn't
|
|
1864
|
+
* deprecated. Split from the printer so tests can assert the content without
|
|
1865
|
+
* capturing stdout. Lines are plain (uncolored) text.
|
|
1866
|
+
*/
|
|
1867
|
+
export function deprecationNotice(agent) {
|
|
1868
|
+
const dep = AGENTS[agent].deprecated;
|
|
1869
|
+
if (!dep)
|
|
1870
|
+
return null;
|
|
1871
|
+
const name = AGENTS[agent].name;
|
|
1872
|
+
const lines = [
|
|
1873
|
+
`Warning: ${name} was deprecated by ${dep.by} (${dep.date}).`,
|
|
1874
|
+
` ${dep.reason}`,
|
|
1875
|
+
];
|
|
1876
|
+
if (dep.replacement) {
|
|
1877
|
+
const rep = AGENTS[dep.replacement];
|
|
1878
|
+
lines.push(` Consider using ${rep.name} instead: agents add ${rep.id}`);
|
|
1879
|
+
}
|
|
1880
|
+
if (dep.url)
|
|
1881
|
+
lines.push(` ${dep.url}`);
|
|
1882
|
+
return lines;
|
|
1883
|
+
}
|
|
1884
|
+
/**
|
|
1885
|
+
* Print a deprecation warning (yellow) if the agent's registry entry carries a
|
|
1886
|
+
* `deprecated` marker; no-op otherwise. Call from any user entry point that
|
|
1887
|
+
* acts on a chosen agent — install (`agents add`) and `agents teams add`.
|
|
1888
|
+
*/
|
|
1889
|
+
export function warnAgentDeprecated(agent) {
|
|
1890
|
+
const lines = deprecationNotice(agent);
|
|
1891
|
+
if (!lines)
|
|
1892
|
+
return;
|
|
1893
|
+
for (const line of lines)
|
|
1894
|
+
console.log(chalk.yellow(line));
|
|
1895
|
+
}
|
|
1792
1896
|
/** Format an error message for an unrecognized agent name, listing valid options. */
|
|
1793
1897
|
export function formatAgentError(agentName, validAgents = ALL_AGENT_IDS) {
|
|
1794
1898
|
return `Unknown agent '${agentName}'. Valid agents: ${validAgents.join(', ')}`;
|
package/dist/lib/browser/cdp.js
CHANGED
|
@@ -219,7 +219,11 @@ export function verifyBrowserIdentity(reported, expected, port, host = 'localhos
|
|
|
219
219
|
// to a Comet instance doesn't trip a false "identity mismatch".
|
|
220
220
|
comet: ['comet', 'chrome'],
|
|
221
221
|
brave: ['brave', 'brave-browser', 'chrome'],
|
|
222
|
-
|
|
222
|
+
// Windows/Chromium Edge reports its /json/version "Browser" field as
|
|
223
|
+
// "Edg/<version>" (the `Edg` token, not `Edge`), which normalizeBrowserName
|
|
224
|
+
// reduces to "edg". Accept it so attaching to a real Edge doesn't trip a
|
|
225
|
+
// false identity mismatch (the profile enum only allows "edge").
|
|
226
|
+
edge: ['edge', 'edg', 'microsoft-edge', 'msedge', 'chrome'],
|
|
223
227
|
};
|
|
224
228
|
const accepted = matches[expected] || [expected];
|
|
225
229
|
if (accepted.includes(reported))
|
|
@@ -51,6 +51,7 @@ const BROWSER_PATHS = {
|
|
|
51
51
|
edge: [
|
|
52
52
|
`${WIN_PROGRAMFILES}\\Microsoft\\Edge\\Application\\msedge.exe`,
|
|
53
53
|
`${WIN_PROGRAMFILES_X86}\\Microsoft\\Edge\\Application\\msedge.exe`,
|
|
54
|
+
`${WIN_LOCALAPPDATA}\\Microsoft\\Edge\\Application\\msedge.exe`,
|
|
54
55
|
],
|
|
55
56
|
custom: [],
|
|
56
57
|
},
|
|
@@ -282,6 +283,25 @@ export async function attachToChrome(port) {
|
|
|
282
283
|
return wsUrl;
|
|
283
284
|
}
|
|
284
285
|
export function killChrome(pid) {
|
|
286
|
+
if (process.platform === 'win32') {
|
|
287
|
+
// On Windows `process.kill(pid, 'SIGINT')` maps to TerminateProcess — a
|
|
288
|
+
// hard kill that skips Chromium's on-exit profile flush (leaving a dirty
|
|
289
|
+
// "Chrome didn't shut down correctly" state). Ask taskkill for a graceful
|
|
290
|
+
// stop first (no /F → posts WM_CLOSE), and only force-kill the tree if that
|
|
291
|
+
// fails or the process is still around.
|
|
292
|
+
try {
|
|
293
|
+
execFileSync('taskkill', ['/pid', String(pid)], { stdio: 'ignore' });
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
try {
|
|
297
|
+
execFileSync('taskkill', ['/pid', String(pid), '/t', '/f'], { stdio: 'ignore' });
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
// Process already dead
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
285
305
|
try {
|
|
286
306
|
process.kill(pid, 'SIGINT');
|
|
287
307
|
}
|
|
@@ -53,4 +53,31 @@ export declare function buildLaunchCmd(remoteOs: RemoteOs, browserType: string,
|
|
|
53
53
|
* (Get-NetTCPConnection → Stop-Process).
|
|
54
54
|
*/
|
|
55
55
|
export declare function buildKillCmd(remoteOs: RemoteOs, port: number): string;
|
|
56
|
+
export declare function ensureRemoteBrowser(user: string, host: string, browserType: string, port: number, remoteOs: RemoteOs, customBinary?: string): Promise<void>;
|
|
56
57
|
export declare function restartRemoteBrowser(user: string, host: string, browserType: string, port: number, remoteOs: RemoteOs, customBinary?: string): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Kill the remote browser holding the CDP port. Invoked on stop/cleanup so the
|
|
60
|
+
* WMI-spawned (Windows) / detached (posix) browser process is not orphaned when
|
|
61
|
+
* the ssh tunnel is torn down — killing only the tunnel left it running on
|
|
62
|
+
* win-mini after every `browser stop`. Best-effort: never rejects.
|
|
63
|
+
*/
|
|
64
|
+
export declare function killRemoteBrowser(user: string, host: string, remoteOs: RemoteOs, port: number): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Identify whether a pid listening on our target local port is an SSH
|
|
67
|
+
* tunnel WE would have spawned for `host:remotePort`. Used so that two
|
|
68
|
+
* agents-browser invocations of the same SSH profile share a tunnel
|
|
69
|
+
* rather than failing the second one with "port in use".
|
|
70
|
+
*
|
|
71
|
+
* Best-effort match against the ssh -L command line via `ps`. If we
|
|
72
|
+
* can't read the cmd or the args don't look like ours, treat as not-ours.
|
|
73
|
+
*
|
|
74
|
+
* Windows has no `ps`, so the POSIX branch below always throws there and every
|
|
75
|
+
* reuse check returned false — the second invocation to the same host:port then
|
|
76
|
+
* failed "port in use". On win32 we instead reuse the netstat -ano + tasklist
|
|
77
|
+
* occupant lookup (getPortOccupant): the tunnel binds `remotePort` locally
|
|
78
|
+
* (localPort === remotePort in the caller), so the pid holding that port is our
|
|
79
|
+
* ssh.exe. tasklist only exposes the image name, not the full command line, so
|
|
80
|
+
* we can't match host/remotePort as tightly as the POSIX branch — an ssh
|
|
81
|
+
* process on our exact local port is our tunnel in practice.
|
|
82
|
+
*/
|
|
83
|
+
export declare function isOwnTunnel(pid: number, host: string, remotePort: number): boolean;
|