@phnx-labs/agents-cli 1.20.4 → 1.20.6
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 +19 -0
- package/README.md +49 -18
- package/dist/commands/browser.js +31 -4
- package/dist/commands/cli.js +1 -1
- package/dist/commands/cloud.js +1 -1
- package/dist/commands/commands.js +2 -0
- package/dist/commands/computer.js +10 -2
- package/dist/commands/defaults.d.ts +7 -0
- package/dist/commands/defaults.js +89 -0
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/exec.js +73 -19
- package/dist/commands/hooks.js +6 -6
- package/dist/commands/inspect.d.ts +26 -0
- package/dist/commands/inspect.js +590 -0
- package/dist/commands/mcp.js +17 -16
- package/dist/commands/models.js +1 -1
- package/dist/commands/packages.js +6 -4
- package/dist/commands/permissions.js +13 -12
- package/dist/commands/plugins.d.ts +13 -0
- package/dist/commands/plugins.js +100 -11
- package/dist/commands/prune.js +3 -2
- package/dist/commands/pull.d.ts +12 -5
- package/dist/commands/pull.js +26 -422
- package/dist/commands/push.d.ts +14 -0
- package/dist/commands/push.js +30 -0
- package/dist/commands/repo.d.ts +1 -1
- package/dist/commands/repo.js +155 -112
- package/dist/commands/resource-view.d.ts +2 -0
- package/dist/commands/resource-view.js +12 -3
- package/dist/commands/routines.js +32 -7
- package/dist/commands/rules.js +4 -4
- package/dist/commands/secrets.js +46 -9
- package/dist/commands/sessions.js +1 -0
- package/dist/commands/setup.d.ts +3 -3
- package/dist/commands/setup.js +17 -17
- package/dist/commands/skills.js +6 -5
- package/dist/commands/subagents.js +5 -4
- package/dist/commands/sync.d.ts +18 -5
- package/dist/commands/sync.js +251 -65
- package/dist/commands/teams.js +109 -11
- package/dist/commands/tmux.d.ts +25 -0
- package/dist/commands/tmux.js +415 -0
- package/dist/commands/trash.d.ts +2 -2
- package/dist/commands/trash.js +1 -1
- package/dist/commands/versions.js +2 -2
- package/dist/commands/view.d.ts +12 -1
- package/dist/commands/view.js +128 -40
- package/dist/commands/workflows.js +4 -3
- package/dist/commands/worktree.d.ts +4 -5
- package/dist/commands/worktree.js +4 -4
- package/dist/index.js +106 -41
- package/dist/lib/agents.d.ts +23 -10
- package/dist/lib/agents.js +88 -25
- package/dist/lib/auto-pull-worker.d.ts +1 -1
- package/dist/lib/auto-pull-worker.js +2 -2
- package/dist/lib/auto-pull.d.ts +1 -1
- package/dist/lib/auto-pull.js +1 -1
- package/dist/lib/beta.d.ts +1 -1
- package/dist/lib/beta.js +1 -1
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +84 -3
- package/dist/lib/capabilities.js +2 -0
- package/dist/lib/commands.d.ts +28 -1
- package/dist/lib/commands.js +125 -20
- package/dist/lib/doctor-diff.js +2 -2
- package/dist/lib/exec.d.ts +14 -0
- package/dist/lib/exec.js +59 -5
- package/dist/lib/fuzzy.d.ts +12 -2
- package/dist/lib/fuzzy.js +29 -4
- package/dist/lib/git.js +8 -1
- package/dist/lib/hooks.d.ts +2 -2
- package/dist/lib/hooks.js +97 -10
- package/dist/lib/mcp.js +32 -2
- package/dist/lib/migrate.d.ts +51 -0
- package/dist/lib/migrate.js +233 -5
- package/dist/lib/models.js +62 -15
- package/dist/lib/permissions.d.ts +59 -2
- package/dist/lib/permissions.js +299 -7
- package/dist/lib/plugin-marketplace.d.ts +98 -40
- package/dist/lib/plugin-marketplace.js +196 -93
- package/dist/lib/plugins.d.ts +21 -4
- package/dist/lib/plugins.js +130 -49
- package/dist/lib/profiles-presets.js +12 -12
- package/dist/lib/project-launch.d.ts +70 -0
- package/dist/lib/project-launch.js +404 -0
- package/dist/lib/pty-client.js +1 -1
- package/dist/lib/pty-server.d.ts +1 -1
- package/dist/lib/pty-server.js +8 -5
- package/dist/lib/refresh.d.ts +26 -0
- package/dist/lib/refresh.js +315 -0
- package/dist/lib/resource-patterns.d.ts +1 -1
- package/dist/lib/resource-patterns.js +1 -1
- package/dist/lib/resources/commands.js +2 -2
- package/dist/lib/resources/hooks.d.ts +1 -1
- package/dist/lib/resources/hooks.js +1 -1
- package/dist/lib/resources/mcp.d.ts +1 -1
- package/dist/lib/resources/mcp.js +5 -6
- package/dist/lib/resources/permissions.js +5 -2
- package/dist/lib/resources/rules.js +3 -2
- package/dist/lib/resources/skills.js +3 -2
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/resources.d.ts +2 -0
- package/dist/lib/resources.js +4 -3
- package/dist/lib/rotate.d.ts +1 -1
- package/dist/lib/rotate.js +7 -19
- package/dist/lib/routines.d.ts +16 -4
- package/dist/lib/routines.js +67 -17
- package/dist/lib/rules/compile.js +22 -10
- package/dist/lib/rules/rules.js +3 -3
- package/dist/lib/run-config.d.ts +9 -0
- package/dist/lib/run-config.js +35 -0
- package/dist/lib/run-defaults.d.ts +42 -0
- package/dist/lib/run-defaults.js +180 -0
- package/dist/lib/runner.js +16 -3
- package/dist/lib/scheduler.js +15 -1
- 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/Agents CLI.app/Contents/_CodeSignature/CodeResources +9 -1
- package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
- package/dist/lib/secrets/install-helper.d.ts +11 -3
- package/dist/lib/secrets/install-helper.js +48 -6
- package/dist/lib/secrets/linux.d.ts +56 -9
- package/dist/lib/secrets/linux.js +327 -59
- package/dist/lib/session/db.js +15 -2
- package/dist/lib/session/discover.js +118 -3
- package/dist/lib/session/parse.js +3 -0
- package/dist/lib/session/types.d.ts +1 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/shims.d.ts +18 -9
- package/dist/lib/shims.js +133 -50
- package/dist/lib/skills.d.ts +1 -1
- package/dist/lib/skills.js +10 -9
- package/dist/lib/staleness/detectors/commands.d.ts +3 -0
- package/dist/lib/staleness/detectors/commands.js +46 -0
- package/dist/lib/staleness/detectors/hooks.d.ts +3 -0
- package/dist/lib/staleness/detectors/hooks.js +44 -0
- package/dist/lib/staleness/detectors/mcp.d.ts +3 -0
- package/dist/lib/staleness/detectors/mcp.js +31 -0
- package/dist/lib/staleness/detectors/permissions.d.ts +3 -0
- package/dist/lib/staleness/detectors/permissions.js +201 -0
- package/dist/lib/staleness/detectors/plugins.d.ts +8 -0
- package/dist/lib/staleness/detectors/plugins.js +23 -0
- package/dist/lib/staleness/detectors/rules.d.ts +3 -0
- package/dist/lib/staleness/detectors/rules.js +34 -0
- package/dist/lib/staleness/detectors/skills.d.ts +3 -0
- package/dist/lib/staleness/detectors/skills.js +71 -0
- package/dist/lib/staleness/detectors/subagents.d.ts +3 -0
- package/dist/lib/staleness/detectors/subagents.js +50 -0
- package/dist/lib/staleness/detectors/types.d.ts +22 -0
- package/dist/lib/staleness/detectors/types.js +1 -0
- package/dist/lib/staleness/detectors/workflows.d.ts +3 -0
- package/dist/lib/staleness/detectors/workflows.js +28 -0
- package/dist/lib/staleness/registry.d.ts +26 -0
- package/dist/lib/staleness/registry.js +123 -0
- package/dist/lib/staleness/writers/commands.d.ts +3 -0
- package/dist/lib/staleness/writers/commands.js +111 -0
- package/dist/lib/staleness/writers/hooks.d.ts +3 -0
- package/dist/lib/staleness/writers/hooks.js +47 -0
- package/dist/lib/staleness/writers/kinds.d.ts +10 -0
- package/dist/lib/staleness/writers/kinds.js +15 -0
- package/dist/lib/staleness/writers/lazy-map.d.ts +13 -0
- package/dist/lib/staleness/writers/lazy-map.js +19 -0
- package/dist/lib/staleness/writers/mcp.d.ts +10 -0
- package/dist/lib/staleness/writers/mcp.js +19 -0
- package/dist/lib/staleness/writers/permissions.d.ts +13 -0
- package/dist/lib/staleness/writers/permissions.js +26 -0
- package/dist/lib/staleness/writers/plugins.d.ts +7 -0
- package/dist/lib/staleness/writers/plugins.js +31 -0
- package/dist/lib/staleness/writers/rules.d.ts +7 -0
- package/dist/lib/staleness/writers/rules.js +55 -0
- package/dist/lib/staleness/writers/skills.d.ts +3 -0
- package/dist/lib/staleness/writers/skills.js +81 -0
- package/dist/lib/staleness/writers/sources.d.ts +16 -0
- package/dist/lib/staleness/writers/sources.js +72 -0
- package/dist/lib/staleness/writers/subagents.d.ts +3 -0
- package/dist/lib/staleness/writers/subagents.js +53 -0
- package/dist/lib/staleness/writers/types.d.ts +36 -0
- package/dist/lib/staleness/writers/types.js +1 -0
- package/dist/lib/staleness/writers/workflows.d.ts +7 -0
- package/dist/lib/staleness/writers/workflows.js +31 -0
- package/dist/lib/state.d.ts +34 -11
- package/dist/lib/state.js +58 -13
- package/dist/lib/subagents.d.ts +0 -2
- package/dist/lib/subagents.js +6 -6
- package/dist/lib/teams/agents.js +1 -1
- package/dist/lib/teams/api.d.ts +67 -0
- package/dist/lib/teams/api.js +78 -0
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/tmux/binary.d.ts +67 -0
- package/dist/lib/tmux/binary.js +141 -0
- package/dist/lib/tmux/index.d.ts +8 -0
- package/dist/lib/tmux/index.js +8 -0
- package/dist/lib/tmux/paths.d.ts +17 -0
- package/dist/lib/tmux/paths.js +30 -0
- package/dist/lib/tmux/session.d.ts +122 -0
- package/dist/lib/tmux/session.js +305 -0
- package/dist/lib/types.d.ts +73 -13
- package/dist/lib/types.js +1 -1
- package/dist/lib/usage.js +1 -1
- package/dist/lib/versions.d.ts +4 -4
- package/dist/lib/versions.js +138 -496
- package/dist/lib/workflows.d.ts +2 -4
- package/dist/lib/workflows.js +3 -4
- package/package.json +6 -3
- package/scripts/postinstall.js +16 -63
- package/dist/commands/status.d.ts +0 -9
- package/dist/commands/status.js +0 -25
package/dist/lib/teams/api.js
CHANGED
|
@@ -55,6 +55,84 @@ function recentToolCalls(events, max = 10) {
|
|
|
55
55
|
timestamp: typeof event.timestamp === 'string' ? event.timestamp : null,
|
|
56
56
|
}));
|
|
57
57
|
}
|
|
58
|
+
/** Max files to name per category in the summary. Counts are always exact. */
|
|
59
|
+
const SUMMARY_MAX_FILE_NAMES = 6;
|
|
60
|
+
/** Max messages in the summary. */
|
|
61
|
+
const SUMMARY_MAX_MESSAGES = 3;
|
|
62
|
+
/** Max chars per message body in the summary. */
|
|
63
|
+
const SUMMARY_MESSAGE_MAX_CHARS = 400;
|
|
64
|
+
/**
|
|
65
|
+
* Reduce a file path to just its basename for compact rendering. Keeps the
|
|
66
|
+
* orchestrator oriented ("you touched types.ts") without dumping the full
|
|
67
|
+
* absolute path every time. The full paths are still in `AgentStatusDetail`
|
|
68
|
+
* for the verbose path.
|
|
69
|
+
*/
|
|
70
|
+
function basenameOf(p) {
|
|
71
|
+
const ix = p.lastIndexOf('/');
|
|
72
|
+
return ix < 0 ? p : p.slice(ix + 1);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Trim long assistant messages to a fixed budget. We collapse leading
|
|
76
|
+
* whitespace so the budget covers actual content, not indent.
|
|
77
|
+
*/
|
|
78
|
+
function trimMessage(msg, max = SUMMARY_MESSAGE_MAX_CHARS) {
|
|
79
|
+
const s = msg.replace(/^\s+/, '');
|
|
80
|
+
if (s.length <= max)
|
|
81
|
+
return s;
|
|
82
|
+
return s.slice(0, max - 1) + '…';
|
|
83
|
+
}
|
|
84
|
+
/** Pull at most `max` basenames out of a path list, preserving order. */
|
|
85
|
+
function compactFileList(paths, max = SUMMARY_MAX_FILE_NAMES) {
|
|
86
|
+
const seen = new Set();
|
|
87
|
+
const names = [];
|
|
88
|
+
for (const p of paths) {
|
|
89
|
+
const base = basenameOf(p);
|
|
90
|
+
if (seen.has(base))
|
|
91
|
+
continue;
|
|
92
|
+
seen.add(base);
|
|
93
|
+
names.push(base);
|
|
94
|
+
if (names.length >= max)
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
return { count: paths.length, names };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Project a full AgentStatusDetail down to a compact AgentStatusSummary.
|
|
101
|
+
* Drops `prompt` entirely (caller knows what they queued), folds file lists
|
|
102
|
+
* to basenames + counts, caps `last_messages` to 3 × {@link SUMMARY_MESSAGE_MAX_CHARS}.
|
|
103
|
+
*/
|
|
104
|
+
export function toAgentStatusSummary(detail) {
|
|
105
|
+
return {
|
|
106
|
+
agent_id: detail.agent_id,
|
|
107
|
+
name: detail.name ?? null,
|
|
108
|
+
agent_type: detail.agent_type,
|
|
109
|
+
status: detail.status,
|
|
110
|
+
duration: detail.duration,
|
|
111
|
+
tool_count: detail.tool_count,
|
|
112
|
+
has_errors: detail.has_errors,
|
|
113
|
+
pr_url: detail.pr_url ?? null,
|
|
114
|
+
files: {
|
|
115
|
+
modified: compactFileList(detail.files_modified),
|
|
116
|
+
created: compactFileList(detail.files_created),
|
|
117
|
+
deleted: compactFileList(detail.files_deleted),
|
|
118
|
+
read: { count: detail.files_read.length },
|
|
119
|
+
},
|
|
120
|
+
bash_commands: detail.bash_commands,
|
|
121
|
+
last_messages: detail.last_messages
|
|
122
|
+
.slice(-SUMMARY_MAX_MESSAGES)
|
|
123
|
+
.map((m) => trimMessage(m)),
|
|
124
|
+
cursor: detail.cursor,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/** Project a full TaskStatusResult down to the compact summary shape. */
|
|
128
|
+
export function toTaskStatusSummary(result) {
|
|
129
|
+
return {
|
|
130
|
+
task_name: result.task_name,
|
|
131
|
+
agents: result.agents.map(toAgentStatusSummary),
|
|
132
|
+
summary: result.summary,
|
|
133
|
+
cursor: result.cursor,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
58
136
|
/** Spawn a new teammate in a task and return its initial metadata. */
|
|
59
137
|
export async function handleSpawn(manager, taskName, agentType, prompt, cwd, mode, effort = 'medium', parentSessionId = null, workspaceDir = null, version = null, name = null, after = [], model = null, envOverrides = null, taskType = null, cloudProvider = null, cloudSessionId = null, cloudRepo = null, cloudBranch = null, worktreeName = null, worktreePath = null, profileName = null) {
|
|
60
138
|
const defaultMode = manager.getDefaultMode();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Supported agent CLI types for team spawning. */
|
|
2
|
-
export type AgentType = 'codex' | 'gemini' | 'cursor' | 'claude' | 'opencode' | 'grok' | 'antigravity';
|
|
2
|
+
export type AgentType = 'codex' | 'gemini' | 'cursor' | 'claude' | 'opencode' | 'grok' | 'antigravity' | 'kimi';
|
|
3
3
|
/** Normalize a raw JSON event from any agent type into an array of unified event objects. */
|
|
4
4
|
export declare function normalizeEvents(agentType: AgentType, raw: any): any[];
|
|
5
5
|
/** Normalize a raw JSON event, returning only the first unified event (convenience wrapper). */
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tmux binary discovery + spawn helpers.
|
|
3
|
+
*
|
|
4
|
+
* Every shell-out goes through `runTmux()` so:
|
|
5
|
+
* - args are passed as an array, never interpolated into a shell string (no
|
|
6
|
+
* quoting bugs like swarmify's `command.replace(/'/g, "'\\''")` hack);
|
|
7
|
+
* - the socket arg is positioned correctly (`-S <sock>` MUST come before the
|
|
8
|
+
* subcommand);
|
|
9
|
+
* - stdout/stderr capture is consistent for the session module to parse.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Locate the tmux binary on PATH. Cached after first call — tmux either is or
|
|
13
|
+
* isn't installed for the duration of the process.
|
|
14
|
+
*
|
|
15
|
+
* Returns null when tmux is not installed.
|
|
16
|
+
*/
|
|
17
|
+
export declare function findTmuxBinary(): string | null;
|
|
18
|
+
/** True when tmux is installed somewhere on PATH. */
|
|
19
|
+
export declare function isTmuxInstalled(): boolean;
|
|
20
|
+
/** Best-effort tmux version string (e.g. "tmux 3.6a"). Returns null when not installed or version probe fails. */
|
|
21
|
+
export declare function getTmuxVersion(): string | null;
|
|
22
|
+
/**
|
|
23
|
+
* Throw a user-friendly error when tmux isn't installed. Command handlers call
|
|
24
|
+
* this first thing so the error message is the same shape every time.
|
|
25
|
+
*/
|
|
26
|
+
export declare function assertTmuxAvailable(): string;
|
|
27
|
+
export declare class TmuxUnavailableError extends Error {
|
|
28
|
+
constructor(message: string);
|
|
29
|
+
}
|
|
30
|
+
export declare class TmuxCommandError extends Error {
|
|
31
|
+
readonly stderr: string;
|
|
32
|
+
readonly stdout: string;
|
|
33
|
+
readonly code: number | null;
|
|
34
|
+
constructor(message: string, stderr: string, stdout: string, code: number | null);
|
|
35
|
+
}
|
|
36
|
+
export interface RunTmuxOptions {
|
|
37
|
+
/** Socket path (default: shared server socket). */
|
|
38
|
+
socket?: string;
|
|
39
|
+
/** Args after `tmux -S <socket>` — e.g. `['has-session', '-t', 'foo']`. */
|
|
40
|
+
args: string[];
|
|
41
|
+
/** Throw on nonzero exit. Defaults true. */
|
|
42
|
+
throwOnError?: boolean;
|
|
43
|
+
/** Child process env. */
|
|
44
|
+
env?: NodeJS.ProcessEnv;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Run a tmux command and capture stdout/stderr. The socket arg is hoisted in
|
|
48
|
+
* front of `args` so callers never have to remember the `-S` position.
|
|
49
|
+
*
|
|
50
|
+
* For interactive `attach`, use `attachTmux()` instead — this helper is for
|
|
51
|
+
* scripted commands where you want output back as strings.
|
|
52
|
+
*/
|
|
53
|
+
export declare function runTmux(opts: RunTmuxOptions): Promise<{
|
|
54
|
+
stdout: string;
|
|
55
|
+
stderr: string;
|
|
56
|
+
code: number;
|
|
57
|
+
}>;
|
|
58
|
+
/**
|
|
59
|
+
* Foreground attach. Replaces this process's stdio with tmux's so the user is
|
|
60
|
+
* fully inside tmux. Returns the tmux exit code (which the caller should mirror
|
|
61
|
+
* via process.exit so detach/Ctrl-D propagates cleanly).
|
|
62
|
+
*/
|
|
63
|
+
export declare function attachTmux(opts: {
|
|
64
|
+
socket: string;
|
|
65
|
+
args: string[];
|
|
66
|
+
env?: NodeJS.ProcessEnv;
|
|
67
|
+
}): Promise<number>;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tmux binary discovery + spawn helpers.
|
|
3
|
+
*
|
|
4
|
+
* Every shell-out goes through `runTmux()` so:
|
|
5
|
+
* - args are passed as an array, never interpolated into a shell string (no
|
|
6
|
+
* quoting bugs like swarmify's `command.replace(/'/g, "'\\''")` hack);
|
|
7
|
+
* - the socket arg is positioned correctly (`-S <sock>` MUST come before the
|
|
8
|
+
* subcommand);
|
|
9
|
+
* - stdout/stderr capture is consistent for the session module to parse.
|
|
10
|
+
*/
|
|
11
|
+
import { spawn, spawnSync } from 'child_process';
|
|
12
|
+
import { existsSync } from 'fs';
|
|
13
|
+
let cachedBin;
|
|
14
|
+
/**
|
|
15
|
+
* Locate the tmux binary on PATH. Cached after first call — tmux either is or
|
|
16
|
+
* isn't installed for the duration of the process.
|
|
17
|
+
*
|
|
18
|
+
* Returns null when tmux is not installed.
|
|
19
|
+
*/
|
|
20
|
+
export function findTmuxBinary() {
|
|
21
|
+
if (cachedBin !== undefined)
|
|
22
|
+
return cachedBin;
|
|
23
|
+
// Try `which` first (respects PATH); fall back to common Homebrew/Linux paths
|
|
24
|
+
// so a stripped CI shell with a sparse PATH still works.
|
|
25
|
+
const fromWhich = spawnSync('sh', ['-c', 'command -v tmux'], { encoding: 'utf8' });
|
|
26
|
+
if (fromWhich.status === 0) {
|
|
27
|
+
const out = fromWhich.stdout.trim();
|
|
28
|
+
if (out && existsSync(out)) {
|
|
29
|
+
cachedBin = out;
|
|
30
|
+
return cachedBin;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
for (const p of ['/opt/homebrew/bin/tmux', '/usr/local/bin/tmux', '/usr/bin/tmux']) {
|
|
34
|
+
if (existsSync(p)) {
|
|
35
|
+
cachedBin = p;
|
|
36
|
+
return cachedBin;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
cachedBin = null;
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
/** True when tmux is installed somewhere on PATH. */
|
|
43
|
+
export function isTmuxInstalled() {
|
|
44
|
+
return findTmuxBinary() !== null;
|
|
45
|
+
}
|
|
46
|
+
/** Best-effort tmux version string (e.g. "tmux 3.6a"). Returns null when not installed or version probe fails. */
|
|
47
|
+
export function getTmuxVersion() {
|
|
48
|
+
const bin = findTmuxBinary();
|
|
49
|
+
if (!bin)
|
|
50
|
+
return null;
|
|
51
|
+
const res = spawnSync(bin, ['-V'], { encoding: 'utf8' });
|
|
52
|
+
if (res.status !== 0)
|
|
53
|
+
return null;
|
|
54
|
+
return res.stdout.trim() || null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Throw a user-friendly error when tmux isn't installed. Command handlers call
|
|
58
|
+
* this first thing so the error message is the same shape every time.
|
|
59
|
+
*/
|
|
60
|
+
export function assertTmuxAvailable() {
|
|
61
|
+
const bin = findTmuxBinary();
|
|
62
|
+
if (!bin) {
|
|
63
|
+
const platform = process.platform;
|
|
64
|
+
const hint = platform === 'darwin'
|
|
65
|
+
? 'Install with: brew install tmux'
|
|
66
|
+
: platform === 'linux'
|
|
67
|
+
? 'Install with: apt install tmux (or dnf/yum/pacman equivalent)'
|
|
68
|
+
: 'Install tmux from https://github.com/tmux/tmux';
|
|
69
|
+
throw new TmuxUnavailableError(`tmux is not installed. ${hint}`);
|
|
70
|
+
}
|
|
71
|
+
return bin;
|
|
72
|
+
}
|
|
73
|
+
export class TmuxUnavailableError extends Error {
|
|
74
|
+
constructor(message) {
|
|
75
|
+
super(message);
|
|
76
|
+
this.name = 'TmuxUnavailableError';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export class TmuxCommandError extends Error {
|
|
80
|
+
stderr;
|
|
81
|
+
stdout;
|
|
82
|
+
code;
|
|
83
|
+
constructor(message, stderr, stdout, code) {
|
|
84
|
+
super(message);
|
|
85
|
+
this.name = 'TmuxCommandError';
|
|
86
|
+
this.stderr = stderr;
|
|
87
|
+
this.stdout = stdout;
|
|
88
|
+
this.code = code;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Run a tmux command and capture stdout/stderr. The socket arg is hoisted in
|
|
93
|
+
* front of `args` so callers never have to remember the `-S` position.
|
|
94
|
+
*
|
|
95
|
+
* For interactive `attach`, use `attachTmux()` instead — this helper is for
|
|
96
|
+
* scripted commands where you want output back as strings.
|
|
97
|
+
*/
|
|
98
|
+
export async function runTmux(opts) {
|
|
99
|
+
const bin = assertTmuxAvailable();
|
|
100
|
+
const fullArgs = [];
|
|
101
|
+
if (opts.socket)
|
|
102
|
+
fullArgs.push('-S', opts.socket);
|
|
103
|
+
fullArgs.push(...opts.args);
|
|
104
|
+
return new Promise((resolve, reject) => {
|
|
105
|
+
const child = spawn(bin, fullArgs, {
|
|
106
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
107
|
+
env: opts.env ?? process.env,
|
|
108
|
+
});
|
|
109
|
+
let stdout = '';
|
|
110
|
+
let stderr = '';
|
|
111
|
+
child.stdout?.on('data', (b) => { stdout += b.toString('utf8'); });
|
|
112
|
+
child.stderr?.on('data', (b) => { stderr += b.toString('utf8'); });
|
|
113
|
+
child.on('error', reject);
|
|
114
|
+
child.on('close', (code) => {
|
|
115
|
+
const exitCode = code ?? -1;
|
|
116
|
+
const throwOnError = opts.throwOnError !== false;
|
|
117
|
+
if (throwOnError && exitCode !== 0) {
|
|
118
|
+
reject(new TmuxCommandError(`tmux ${fullArgs.join(' ')} failed (exit ${exitCode}): ${stderr.trim() || stdout.trim()}`, stderr, stdout, exitCode));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
resolve({ stdout, stderr, code: exitCode });
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Foreground attach. Replaces this process's stdio with tmux's so the user is
|
|
127
|
+
* fully inside tmux. Returns the tmux exit code (which the caller should mirror
|
|
128
|
+
* via process.exit so detach/Ctrl-D propagates cleanly).
|
|
129
|
+
*/
|
|
130
|
+
export function attachTmux(opts) {
|
|
131
|
+
const bin = assertTmuxAvailable();
|
|
132
|
+
const fullArgs = ['-S', opts.socket, ...opts.args];
|
|
133
|
+
return new Promise((resolve, reject) => {
|
|
134
|
+
const child = spawn(bin, fullArgs, {
|
|
135
|
+
stdio: 'inherit',
|
|
136
|
+
env: opts.env ?? process.env,
|
|
137
|
+
});
|
|
138
|
+
child.on('error', reject);
|
|
139
|
+
child.on('close', (code) => resolve(code ?? 0));
|
|
140
|
+
});
|
|
141
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public entry-point for the tmux integration. Consumers outside the CLI
|
|
3
|
+
* (swarmify extension, `agents teams` multiplexer mode, future MCP wrapper)
|
|
4
|
+
* should import from here.
|
|
5
|
+
*/
|
|
6
|
+
export { findTmuxBinary, isTmuxInstalled, getTmuxVersion, assertTmuxAvailable, TmuxUnavailableError, TmuxCommandError, runTmux, attachTmux, } from './binary.js';
|
|
7
|
+
export { getDefaultSocketPath, getSessionMetaPath, ensureTmuxDir, } from './paths.js';
|
|
8
|
+
export { assertValidSessionName, slugifyName, hasSession, createSession, killSession, killAll, listSessions, splitPane, sendKeys, capturePane, readSessionMeta, TmuxSessionError, type SessionMeta, type CreateSessionOptions, type ListedSession, type SplitOptions, type SendOptions, type CaptureOptions, } from './session.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public entry-point for the tmux integration. Consumers outside the CLI
|
|
3
|
+
* (swarmify extension, `agents teams` multiplexer mode, future MCP wrapper)
|
|
4
|
+
* should import from here.
|
|
5
|
+
*/
|
|
6
|
+
export { findTmuxBinary, isTmuxInstalled, getTmuxVersion, assertTmuxAvailable, TmuxUnavailableError, TmuxCommandError, runTmux, attachTmux, } from './binary.js';
|
|
7
|
+
export { getDefaultSocketPath, getSessionMetaPath, ensureTmuxDir, } from './paths.js';
|
|
8
|
+
export { assertValidSessionName, slugifyName, hasSession, createSession, killSession, killAll, listSessions, splitPane, sendKeys, capturePane, readSessionMeta, TmuxSessionError, } from './session.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem paths for the tmux integration.
|
|
3
|
+
*
|
|
4
|
+
* Layout under ~/.agents/.cache/helpers/tmux/:
|
|
5
|
+
* server.sock — shared tmux server socket hosting all named sessions
|
|
6
|
+
* <name>.json — per-session provenance (cmd, cwd, created_at, source)
|
|
7
|
+
*
|
|
8
|
+
* A single shared server is simpler than per-session sockets: one round-trip
|
|
9
|
+
* for `tmux ls`, one place to clean up, and no extra tmux servers eating
|
|
10
|
+
* memory. Per-session isolation can still be opted into via `--socket`.
|
|
11
|
+
*/
|
|
12
|
+
/** Default shared server socket — every `agents tmux *` command targets this unless overridden. */
|
|
13
|
+
export declare function getDefaultSocketPath(): string;
|
|
14
|
+
/** Per-session provenance JSON. tmux itself is the source of truth for liveness; this file is metadata only. */
|
|
15
|
+
export declare function getSessionMetaPath(name: string): string;
|
|
16
|
+
/** Ensure the tmux scratch dir exists with restrictive permissions (sockets are user-private). */
|
|
17
|
+
export declare function ensureTmuxDir(): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem paths for the tmux integration.
|
|
3
|
+
*
|
|
4
|
+
* Layout under ~/.agents/.cache/helpers/tmux/:
|
|
5
|
+
* server.sock — shared tmux server socket hosting all named sessions
|
|
6
|
+
* <name>.json — per-session provenance (cmd, cwd, created_at, source)
|
|
7
|
+
*
|
|
8
|
+
* A single shared server is simpler than per-session sockets: one round-trip
|
|
9
|
+
* for `tmux ls`, one place to clean up, and no extra tmux servers eating
|
|
10
|
+
* memory. Per-session isolation can still be opted into via `--socket`.
|
|
11
|
+
*/
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
import { getTmuxDir } from '../state.js';
|
|
15
|
+
/** Default shared server socket — every `agents tmux *` command targets this unless overridden. */
|
|
16
|
+
export function getDefaultSocketPath() {
|
|
17
|
+
return path.join(getTmuxDir(), 'server.sock');
|
|
18
|
+
}
|
|
19
|
+
/** Per-session provenance JSON. tmux itself is the source of truth for liveness; this file is metadata only. */
|
|
20
|
+
export function getSessionMetaPath(name) {
|
|
21
|
+
return path.join(getTmuxDir(), `${name}.json`);
|
|
22
|
+
}
|
|
23
|
+
/** Ensure the tmux scratch dir exists with restrictive permissions (sockets are user-private). */
|
|
24
|
+
export function ensureTmuxDir() {
|
|
25
|
+
const dir = getTmuxDir();
|
|
26
|
+
if (!fs.existsSync(dir)) {
|
|
27
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
28
|
+
}
|
|
29
|
+
return dir;
|
|
30
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tmux session lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* Public surface used by both `agents tmux` commands and external consumers
|
|
5
|
+
* (swarmify, `agents teams` with a future multiplexer mode). Every state
|
|
6
|
+
* change goes through here so the CLI surface stays a thin parser.
|
|
7
|
+
*
|
|
8
|
+
* Liveness model: tmux itself owns liveness. The `<name>.json` meta files are
|
|
9
|
+
* pure provenance — `listSessions()` always reconciles them against
|
|
10
|
+
* `tmux list-sessions` and prunes stale entries on the fly.
|
|
11
|
+
*/
|
|
12
|
+
/** Provenance written alongside each live tmux session. */
|
|
13
|
+
export interface SessionMeta {
|
|
14
|
+
name: string;
|
|
15
|
+
socket: string;
|
|
16
|
+
createdAt: number;
|
|
17
|
+
/** Initial command launched in the first pane (informational — tmux owns the actual process). */
|
|
18
|
+
cmd?: string;
|
|
19
|
+
/** Working directory the session was created in. */
|
|
20
|
+
cwd?: string;
|
|
21
|
+
/** Who created this session — useful for `agents sessions --active` attribution. */
|
|
22
|
+
source: 'cli' | 'extension' | 'teams' | 'external';
|
|
23
|
+
/** Free-form labels callers can stamp (e.g. `{ agent: 'claude', vscodePid: 1234 }`). */
|
|
24
|
+
labels?: Record<string, string>;
|
|
25
|
+
}
|
|
26
|
+
export interface CreateSessionOptions {
|
|
27
|
+
name: string;
|
|
28
|
+
cmd?: string;
|
|
29
|
+
cwd?: string;
|
|
30
|
+
env?: NodeJS.ProcessEnv;
|
|
31
|
+
socket?: string;
|
|
32
|
+
source?: SessionMeta['source'];
|
|
33
|
+
labels?: Record<string, string>;
|
|
34
|
+
/** When the named session already exists, kill it before creating. */
|
|
35
|
+
replace?: boolean;
|
|
36
|
+
/** When the named session already exists, return it instead of failing. */
|
|
37
|
+
attachExisting?: boolean;
|
|
38
|
+
/** Initial window dimensions for the detached session. tmux clamps to client size on attach. */
|
|
39
|
+
width?: number;
|
|
40
|
+
height?: number;
|
|
41
|
+
}
|
|
42
|
+
export interface ListedSession {
|
|
43
|
+
name: string;
|
|
44
|
+
socket: string;
|
|
45
|
+
/** Unix epoch seconds reported by tmux (`session_created`). */
|
|
46
|
+
createdAtTmux: number;
|
|
47
|
+
windows: number;
|
|
48
|
+
attached: boolean;
|
|
49
|
+
meta?: SessionMeta;
|
|
50
|
+
}
|
|
51
|
+
export declare class TmuxSessionError extends Error {
|
|
52
|
+
readonly cause?: unknown | undefined;
|
|
53
|
+
constructor(message: string, cause?: unknown | undefined);
|
|
54
|
+
}
|
|
55
|
+
/** Reject invalid session names early — tmux's error for `.`/`:` is cryptic. */
|
|
56
|
+
export declare function assertValidSessionName(name: string): void;
|
|
57
|
+
/** Slugify an arbitrary string into a valid session name. Useful for swarmify auto-generated names. */
|
|
58
|
+
export declare function slugifyName(input: string): string;
|
|
59
|
+
/** True when a session by this name currently exists on the given socket. */
|
|
60
|
+
export declare function hasSession(name: string, socket?: string): Promise<boolean>;
|
|
61
|
+
/**
|
|
62
|
+
* Create a new detached session. Throws when the name is already taken unless
|
|
63
|
+
* `replace` or `attachExisting` is set.
|
|
64
|
+
*/
|
|
65
|
+
export declare function createSession(opts: CreateSessionOptions): Promise<SessionMeta>;
|
|
66
|
+
/** Kill one named session. Idempotent — killing a non-existent session is a no-op. */
|
|
67
|
+
export declare function killSession(name: string, socket?: string): Promise<boolean>;
|
|
68
|
+
/**
|
|
69
|
+
* Kill every session on the shared server AND the server itself, then prune
|
|
70
|
+
* meta files. Wipes the socket so the next `new` starts from a clean slate.
|
|
71
|
+
*/
|
|
72
|
+
export declare function killAll(socket?: string): Promise<number>;
|
|
73
|
+
/**
|
|
74
|
+
* List live sessions on the socket. Reconciles meta JSONs against tmux's view:
|
|
75
|
+
* - tmux session with no meta → returned without `meta` (external session)
|
|
76
|
+
* - meta file with no tmux session → meta deleted (stale)
|
|
77
|
+
*/
|
|
78
|
+
export declare function listSessions(opts?: {
|
|
79
|
+
socket?: string;
|
|
80
|
+
}): Promise<ListedSession[]>;
|
|
81
|
+
export interface SplitOptions {
|
|
82
|
+
name: string;
|
|
83
|
+
direction: 'h' | 'v';
|
|
84
|
+
cmd?: string;
|
|
85
|
+
cwd?: string;
|
|
86
|
+
socket?: string;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Split the active pane of a session. Returns the new pane's tmux pane id
|
|
90
|
+
* (e.g. `%3`) so callers can target it later via `send`/`capture`.
|
|
91
|
+
*/
|
|
92
|
+
export declare function splitPane(opts: SplitOptions): Promise<string>;
|
|
93
|
+
export interface SendOptions {
|
|
94
|
+
name: string;
|
|
95
|
+
/** Pane id (e.g. `%2`) or pane index (`0`, `1`) — appended after the session name when targeting a specific pane. */
|
|
96
|
+
pane?: string;
|
|
97
|
+
/** The literal characters to type into the pane. */
|
|
98
|
+
keys: string;
|
|
99
|
+
/** When true, do NOT append Enter at the end. */
|
|
100
|
+
noEnter?: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* When true, treat `keys` as a single literal string (-l flag). When false
|
|
103
|
+
* (default), tmux interprets named keys like `C-c`, `Enter`, `Escape`.
|
|
104
|
+
*/
|
|
105
|
+
raw?: boolean;
|
|
106
|
+
socket?: string;
|
|
107
|
+
}
|
|
108
|
+
/** Send keystrokes to a session's active pane (or a specific pane via :pane). */
|
|
109
|
+
export declare function sendKeys(opts: SendOptions): Promise<void>;
|
|
110
|
+
export interface CaptureOptions {
|
|
111
|
+
name: string;
|
|
112
|
+
pane?: string;
|
|
113
|
+
/** Number of lines from history to include (default: visible screen only). */
|
|
114
|
+
lines?: number;
|
|
115
|
+
/** Keep ANSI escape sequences (default strips them). */
|
|
116
|
+
ansi?: boolean;
|
|
117
|
+
socket?: string;
|
|
118
|
+
}
|
|
119
|
+
/** Capture pane contents as a string. The cleaned form is what humans see. */
|
|
120
|
+
export declare function capturePane(opts: CaptureOptions): Promise<string>;
|
|
121
|
+
/** Read a session's provenance JSON, if present. */
|
|
122
|
+
export declare function readSessionMeta(name: string): SessionMeta | null;
|