@imdeadpool/guardex 7.0.41 → 7.0.43
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/README.md +68 -13
- package/package.json +2 -1
- package/skills/gitguardex/SKILL.md +13 -0
- package/skills/guardex-merge-skills-to-dev/SKILL.md +59 -0
- package/src/agents/cleanup-sessions.js +126 -0
- package/src/agents/detect.js +160 -0
- package/src/agents/finish.js +172 -0
- package/src/agents/inspect.js +189 -0
- package/src/agents/launch.js +240 -0
- package/src/agents/registry.js +133 -0
- package/src/agents/selection-panel.js +571 -0
- package/src/agents/sessions.js +151 -0
- package/src/agents/start.js +591 -0
- package/src/agents/status.js +143 -0
- package/src/agents/terminal.js +152 -0
- package/src/budget/index.js +343 -0
- package/src/ci-init/index.js +265 -0
- package/src/cli/args.js +305 -1
- package/src/cli/main.js +262 -132
- package/src/cockpit/action-runner.js +3 -0
- package/src/cockpit/actions.js +80 -0
- package/src/cockpit/control.js +1121 -0
- package/src/cockpit/index.js +426 -0
- package/src/cockpit/keybindings.js +224 -0
- package/src/cockpit/kitty-layout.js +549 -0
- package/src/cockpit/kitty-tree.js +144 -0
- package/src/cockpit/layout.js +224 -0
- package/src/cockpit/logs-reader.js +182 -0
- package/src/cockpit/menu.js +204 -0
- package/src/cockpit/pane-actions.js +597 -0
- package/src/cockpit/pane-menu.js +387 -0
- package/src/cockpit/projects-finder.js +178 -0
- package/src/cockpit/render.js +215 -0
- package/src/cockpit/settings-render.js +128 -0
- package/src/cockpit/settings.js +124 -0
- package/src/cockpit/shortcuts.js +24 -0
- package/src/cockpit/sidebar.js +311 -0
- package/src/cockpit/state.js +72 -0
- package/src/cockpit/theme.js +128 -0
- package/src/cockpit/welcome.js +266 -0
- package/src/context.js +76 -33
- package/src/doctor/index.js +3 -2
- package/src/finish/index.js +39 -2
- package/src/git/index.js +65 -0
- package/src/kitty/command.js +101 -0
- package/src/kitty/runtime.js +250 -0
- package/src/output/index.js +1 -1
- package/src/pr-review.js +241 -0
- package/src/scaffold/index.js +19 -0
- package/src/submodule/index.js +288 -0
- package/src/terminal/index.js +120 -0
- package/src/terminal/kitty.js +622 -0
- package/src/terminal/tmux.js +126 -0
- package/src/tmux/command.js +27 -0
- package/src/tmux/session.js +89 -0
- package/templates/AGENTS.multiagent-safety.md +27 -1
- package/templates/codex/skills/gitguardex/SKILL.md +2 -0
- package/templates/githooks/pre-commit +22 -1
- package/templates/github/workflows/README.md +87 -0
- package/templates/github/workflows/ci-full.yml +55 -0
- package/templates/github/workflows/ci.yml +56 -0
- package/templates/github/workflows/cr.yml +20 -1
- package/templates/scripts/agent-branch-finish.sh +544 -26
- package/templates/scripts/agent-branch-start.sh +89 -22
- package/templates/scripts/agent-preflight.sh +89 -0
- package/templates/scripts/agent-worktree-prune.sh +96 -5
- package/templates/scripts/codex-agent.sh +41 -6
- package/templates/scripts/openspec/init-plan-workspace.sh +43 -0
- package/templates/scripts/review-bot-watch.sh +31 -2
- package/templates/scripts/agent-session-state.js +0 -171
- package/templates/scripts/install-vscode-active-agents-extension.js +0 -135
- package/templates/vscode/guardex-active-agents/README.md +0 -34
- package/templates/vscode/guardex-active-agents/extension.js +0 -3782
- package/templates/vscode/guardex-active-agents/fileicons/gitguardex-fileicons.json +0 -54
- package/templates/vscode/guardex-active-agents/fileicons/icons/agent.svg +0 -5
- package/templates/vscode/guardex-active-agents/fileicons/icons/branch.svg +0 -7
- package/templates/vscode/guardex-active-agents/fileicons/icons/config.svg +0 -4
- package/templates/vscode/guardex-active-agents/fileicons/icons/hook.svg +0 -4
- package/templates/vscode/guardex-active-agents/fileicons/icons/openspec.svg +0 -5
- package/templates/vscode/guardex-active-agents/fileicons/icons/plan.svg +0 -4
- package/templates/vscode/guardex-active-agents/fileicons/icons/spec.svg +0 -5
- package/templates/vscode/guardex-active-agents/icon.png +0 -0
- package/templates/vscode/guardex-active-agents/media/active-agents-hivemind.svg +0 -14
- package/templates/vscode/guardex-active-agents/package.json +0 -169
- package/templates/vscode/guardex-active-agents/session-schema.js +0 -1348
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const cp = require('node:child_process');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const os = require('node:os');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
|
|
8
|
+
const DEFAULT_KITTY_BIN = 'kitty';
|
|
9
|
+
const DEFAULT_COCKPIT_TITLE = 'gx cockpit';
|
|
10
|
+
const DEFAULT_AGENT_TITLE = 'agent';
|
|
11
|
+
const DEFAULT_TERMINAL_TITLE = 'terminal';
|
|
12
|
+
const DEFAULT_HOST_SOCKET_PREFIX = 'gx-cockpit-';
|
|
13
|
+
const DEFAULT_HOST_READY_TIMEOUT_MS = 5000;
|
|
14
|
+
const DEFAULT_HOST_POLL_INTERVAL_MS = 50;
|
|
15
|
+
const KITTY_MISSING_MESSAGE = 'Kitty is not installed or not on PATH. Install Kitty or run gx cockpit --backend tmux.';
|
|
16
|
+
const KITTY_REMOTE_CONTROL_MESSAGE = 'Kitty is installed, but remote control is not available. Enable allow_remote_control in kitty.conf or run gx cockpit --backend tmux.';
|
|
17
|
+
const KITTY_HOST_SOCKET_TIMEOUT_MESSAGE = 'Bootstrap Kitty host did not expose a remote-control socket in time.';
|
|
18
|
+
|
|
19
|
+
function text(value, fallback = '') {
|
|
20
|
+
if (typeof value === 'string') return value.trim() || fallback;
|
|
21
|
+
if (value === null || value === undefined) return fallback;
|
|
22
|
+
return String(value).trim() || fallback;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function requireText(value, name) {
|
|
26
|
+
const normalized = text(value);
|
|
27
|
+
if (!normalized) {
|
|
28
|
+
throw new TypeError(`${name} must be a non-empty string`);
|
|
29
|
+
}
|
|
30
|
+
return normalized;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function firstText(...values) {
|
|
34
|
+
for (const value of values) {
|
|
35
|
+
const normalized = text(value);
|
|
36
|
+
if (normalized) return normalized;
|
|
37
|
+
}
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function configEnv(config = {}) {
|
|
42
|
+
return config.env && typeof config.env === 'object' ? config.env : {};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function kittyBin(config = {}, options = {}) {
|
|
46
|
+
const envValue = options.allowEnv
|
|
47
|
+
? firstText(configEnv(config).GUARDEX_KITTY_BIN, process.env.GUARDEX_KITTY_BIN)
|
|
48
|
+
: '';
|
|
49
|
+
return text(config.kittyBin || envValue, DEFAULT_KITTY_BIN);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function hostSocket(config = {}) {
|
|
53
|
+
return text(config.hostSocket || config.socket || config.to, '');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function injectRemoteControl(args, socket) {
|
|
57
|
+
const value = text(socket);
|
|
58
|
+
if (!value) return args;
|
|
59
|
+
if (args.length === 0 || args[0] !== '@') return args;
|
|
60
|
+
if (args.some((arg) => typeof arg === 'string' && arg.startsWith('--to='))) return args;
|
|
61
|
+
return ['@', `--to=${value}`, ...args.slice(1)];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function commandShape(args, config = {}) {
|
|
65
|
+
return {
|
|
66
|
+
cmd: kittyBin(config),
|
|
67
|
+
args: injectRemoteControl(args, hostSocket(config)),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function commandShapeWithEnv(args, config = {}) {
|
|
72
|
+
return {
|
|
73
|
+
cmd: kittyBin(config, { allowEnv: true }),
|
|
74
|
+
args: injectRemoteControl(args, hostSocket(config)),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function defaultHostSocketPath(prefix = DEFAULT_HOST_SOCKET_PREFIX) {
|
|
79
|
+
const random = Math.random().toString(36).slice(2, 10);
|
|
80
|
+
return path.join(os.tmpdir(), `${prefix}${process.pid}-${Date.now()}-${random}.sock`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function buildKittyHostBootstrapCommand(options = {}, config = {}) {
|
|
84
|
+
const repoRoot = requireText(options.repoRoot || options.cwd, 'kitty host repoRoot');
|
|
85
|
+
const socket = requireText(options.socket || options.listenOn, 'kitty host socket');
|
|
86
|
+
const listenOn = socket.includes(':') ? socket : `unix:${socket}`;
|
|
87
|
+
const args = [
|
|
88
|
+
'-o', 'allow_remote_control=yes',
|
|
89
|
+
'-o', `listen_on=${listenOn}`,
|
|
90
|
+
'--listen-on', listenOn,
|
|
91
|
+
'--directory', repoRoot,
|
|
92
|
+
'--title', text(options.title, DEFAULT_COCKPIT_TITLE),
|
|
93
|
+
];
|
|
94
|
+
if (options.hold) args.push('--hold');
|
|
95
|
+
if (options.detach !== false) args.push('--detach');
|
|
96
|
+
appendCommandArgv(args, options);
|
|
97
|
+
return {
|
|
98
|
+
cmd: kittyBin(config, { allowEnv: true }),
|
|
99
|
+
args,
|
|
100
|
+
socket,
|
|
101
|
+
listenOn,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function socketReady(socket, options = {}) {
|
|
106
|
+
if (!socket) return false;
|
|
107
|
+
const candidate = socket.startsWith('unix:') ? socket.slice('unix:'.length) : socket;
|
|
108
|
+
const fsImpl = options.fs || fs;
|
|
109
|
+
if (typeof fsImpl.existsSync !== 'function') return false;
|
|
110
|
+
try {
|
|
111
|
+
return Boolean(fsImpl.existsSync(candidate));
|
|
112
|
+
} catch (_error) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function appendOption(args, flag, value) {
|
|
118
|
+
const normalized = text(value);
|
|
119
|
+
if (normalized) args.push(flag, normalized);
|
|
120
|
+
return args;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizeEnvEntries(env = {}) {
|
|
124
|
+
if (!env) return [];
|
|
125
|
+
if (Array.isArray(env)) {
|
|
126
|
+
return env.map((entry) => {
|
|
127
|
+
if (Array.isArray(entry)) {
|
|
128
|
+
const key = requireText(entry[0], 'kitty env key');
|
|
129
|
+
const value = entry.length > 1 && entry[1] !== undefined && entry[1] !== null ? String(entry[1]) : '';
|
|
130
|
+
return `${key}=${value}`;
|
|
131
|
+
}
|
|
132
|
+
return requireText(entry, 'kitty env');
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (typeof env !== 'object') {
|
|
136
|
+
throw new TypeError('kitty env must be an object, array, or undefined');
|
|
137
|
+
}
|
|
138
|
+
return Object.keys(env)
|
|
139
|
+
.sort()
|
|
140
|
+
.map((key) => `${requireText(key, 'kitty env key')}=${env[key] === undefined || env[key] === null ? '' : String(env[key])}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function appendEnv(args, env) {
|
|
144
|
+
for (const entry of normalizeEnvEntries(env)) {
|
|
145
|
+
args.push('--env', entry);
|
|
146
|
+
}
|
|
147
|
+
return args;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function normalizeCommandArgv(options = {}) {
|
|
151
|
+
const commandArgv = options.argv || options.commandArgv || (Array.isArray(options.command) ? options.command : undefined);
|
|
152
|
+
if (commandArgv === undefined || commandArgv === null) return [];
|
|
153
|
+
if (!Array.isArray(commandArgv)) {
|
|
154
|
+
throw new TypeError('kitty command argv must be an array');
|
|
155
|
+
}
|
|
156
|
+
return commandArgv.map((arg) => {
|
|
157
|
+
if (arg === undefined || arg === null) {
|
|
158
|
+
throw new TypeError('kitty command argv values must be strings');
|
|
159
|
+
}
|
|
160
|
+
return String(arg);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function appendCommandArgv(args, options = {}) {
|
|
165
|
+
const commandArgv = normalizeCommandArgv(options);
|
|
166
|
+
if (commandArgv.length > 0) args.push('--', ...commandArgv);
|
|
167
|
+
return args;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function shellCommandArgv(command) {
|
|
171
|
+
const normalized = text(command);
|
|
172
|
+
return normalized ? ['sh', '-lc', normalized] : [];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function launchTitle(options = {}) {
|
|
176
|
+
const session = options.session && typeof options.session === 'object' ? options.session : {};
|
|
177
|
+
return firstText(
|
|
178
|
+
options.title,
|
|
179
|
+
options.control || options.role === 'control' ? DEFAULT_COCKPIT_TITLE : '',
|
|
180
|
+
options.agent || options.role === 'agent' || options.session ? agentTitle(session) : '',
|
|
181
|
+
options.terminal || options.role === 'terminal' ? DEFAULT_TERMINAL_TITLE : '',
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function launchCwd(options = {}) {
|
|
186
|
+
const session = options.session && typeof options.session === 'object' ? options.session : {};
|
|
187
|
+
return firstText(options.cwd, options.repoRoot, options.worktree, session.worktreePath, session.path);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function buildKittyLaunchCommand(options = {}) {
|
|
191
|
+
const args = ['@', 'launch'];
|
|
192
|
+
const type = text(options.type, 'window');
|
|
193
|
+
const location = firstText(options.location, options.pane ? 'vsplit' : '');
|
|
194
|
+
const cwd = launchCwd(options);
|
|
195
|
+
const title = launchTitle(options);
|
|
196
|
+
|
|
197
|
+
if (type) args.push(`--type=${type}`);
|
|
198
|
+
if (location) args.push(`--location=${location}`);
|
|
199
|
+
appendOption(args, '--cwd', cwd);
|
|
200
|
+
appendOption(args, '--title', title);
|
|
201
|
+
appendEnv(args, options.env);
|
|
202
|
+
appendCommandArgv(args, options);
|
|
203
|
+
|
|
204
|
+
const shape = commandShape(args, options);
|
|
205
|
+
if (Object.prototype.hasOwnProperty.call(options, 'input')) {
|
|
206
|
+
shape.input = options.input === undefined || options.input === null ? '' : String(options.input);
|
|
207
|
+
}
|
|
208
|
+
return shape;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function buildKittyLsCommand(options = {}) {
|
|
212
|
+
return commandShape(['@', 'ls'], options);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function buildKittyVersionCommand(options = {}) {
|
|
216
|
+
return commandShape(['--version'], options);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function buildVersionCommand(config = {}) {
|
|
220
|
+
return commandShapeWithEnv(buildKittyVersionCommand().args, config);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function buildAvailabilityCommand(config = {}) {
|
|
224
|
+
return commandShapeWithEnv(buildKittyLsCommand().args, config);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function buildAvailabilityCommands(config = {}) {
|
|
228
|
+
return [
|
|
229
|
+
buildVersionCommand(config),
|
|
230
|
+
buildAvailabilityCommand(config),
|
|
231
|
+
];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function buildOpenCockpitLayoutCommand(options = {}, config = {}) {
|
|
235
|
+
const repoRoot = requireText(options.repoRoot, 'kitty cockpit repoRoot');
|
|
236
|
+
return buildKittyLaunchCommand({
|
|
237
|
+
role: 'control',
|
|
238
|
+
cwd: repoRoot,
|
|
239
|
+
title: text(options.title, DEFAULT_COCKPIT_TITLE),
|
|
240
|
+
argv: shellCommandArgv(options.command),
|
|
241
|
+
kittyBin: kittyBin(config, { allowEnv: true }),
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function agentTitle(session = {}, title) {
|
|
246
|
+
return firstText(
|
|
247
|
+
title,
|
|
248
|
+
session.title,
|
|
249
|
+
session.agentName,
|
|
250
|
+
session.sessionId,
|
|
251
|
+
session.id,
|
|
252
|
+
session.branch,
|
|
253
|
+
DEFAULT_AGENT_TITLE,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function buildLaunchAgentPaneCommand(options = {}, config = {}) {
|
|
258
|
+
const session = options.session && typeof options.session === 'object' ? options.session : {};
|
|
259
|
+
const cwd = requireText(firstText(options.worktree, session.worktreePath, session.path), 'kitty agent worktree');
|
|
260
|
+
return buildKittyLaunchCommand({
|
|
261
|
+
role: 'agent',
|
|
262
|
+
pane: true,
|
|
263
|
+
cwd,
|
|
264
|
+
title: agentTitle(session, options.title),
|
|
265
|
+
argv: shellCommandArgv(options.command),
|
|
266
|
+
kittyBin: kittyBin(config, { allowEnv: true }),
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function buildLaunchTerminalPaneCommand(options = {}, config = {}) {
|
|
271
|
+
const cwd = requireText(options.cwd, 'kitty terminal cwd');
|
|
272
|
+
return buildKittyLaunchCommand({
|
|
273
|
+
role: 'terminal',
|
|
274
|
+
pane: true,
|
|
275
|
+
cwd,
|
|
276
|
+
title: text(options.title, DEFAULT_TERMINAL_TITLE),
|
|
277
|
+
argv: shellCommandArgv(options.command),
|
|
278
|
+
kittyBin: kittyBin(config, { allowEnv: true }),
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function targetMatch(target) {
|
|
283
|
+
if (target && typeof target === 'object') {
|
|
284
|
+
const explicitMatch = firstText(target.match, target.kittyMatch);
|
|
285
|
+
if (explicitMatch) return explicitMatch;
|
|
286
|
+
|
|
287
|
+
const id = firstText(target.id, target.windowId, target.kittyWindowId, target.paneId, target.target);
|
|
288
|
+
if (id) return `id:${id}`;
|
|
289
|
+
|
|
290
|
+
const title = firstText(target.title, target.windowTitle, target.kittyTitle);
|
|
291
|
+
if (title) return `title:${title}`;
|
|
292
|
+
} else {
|
|
293
|
+
const id = text(target);
|
|
294
|
+
if (id) return `id:${id}`;
|
|
295
|
+
}
|
|
296
|
+
throw new TypeError('kitty target must include id, title, or match');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function buildKittyFocusCommand(target, options = {}) {
|
|
300
|
+
return commandShape(['@', 'focus-window', '--match', targetMatch(target)], options);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function buildKittyCloseCommand(target, options = {}) {
|
|
304
|
+
return commandShape(['@', 'close-window', '--match', targetMatch(target)], options);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function buildKittySendTextCommand(target, options = {}) {
|
|
308
|
+
const shape = commandShape(['@', 'send-text', '--match', targetMatch(target), '--stdin'], options);
|
|
309
|
+
if (Object.prototype.hasOwnProperty.call(options, 'input') || Object.prototype.hasOwnProperty.call(options, 'text') || options.submit) {
|
|
310
|
+
shape.input = sendTextInput(
|
|
311
|
+
Object.prototype.hasOwnProperty.call(options, 'input') ? options.input : options.text,
|
|
312
|
+
{ submit: options.submit },
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
return shape;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function buildFocusPaneCommand(target, config = {}) {
|
|
319
|
+
return buildKittyFocusCommand(target, { kittyBin: kittyBin(config, { allowEnv: true }) });
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function buildClosePaneCommand(target, config = {}) {
|
|
323
|
+
return buildKittyCloseCommand(target, { kittyBin: kittyBin(config, { allowEnv: true }) });
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function buildSendTextCommand(target, config = {}) {
|
|
327
|
+
return buildKittySendTextCommand(target, { kittyBin: kittyBin(config, { allowEnv: true }) });
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function sendTextInput(value, options = {}) {
|
|
331
|
+
const body = value === undefined || value === null ? '' : String(value);
|
|
332
|
+
return options.submit ? `${body}\n` : body;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function mergeEnv(config = {}, options = {}) {
|
|
336
|
+
const env = configEnv(config);
|
|
337
|
+
if (Object.keys(env).length === 0) return options.env;
|
|
338
|
+
return {
|
|
339
|
+
...(options.env || {}),
|
|
340
|
+
...env,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function defaultRunner(cmd, args, options = {}) {
|
|
345
|
+
return cp.spawnSync(cmd, args, {
|
|
346
|
+
cwd: options.cwd,
|
|
347
|
+
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
348
|
+
encoding: 'utf8',
|
|
349
|
+
input: options.input,
|
|
350
|
+
stdio: options.stdio || 'pipe',
|
|
351
|
+
timeout: options.timeout,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function runnerFor(config = {}) {
|
|
356
|
+
if (typeof config.runner === 'function') {
|
|
357
|
+
return {
|
|
358
|
+
run: config.runner,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
if (config.runtime && typeof config.runtime.run === 'function') {
|
|
362
|
+
return config.runtime;
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
run: defaultRunner,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function cloneCommand(shape) {
|
|
370
|
+
return {
|
|
371
|
+
cmd: shape.cmd,
|
|
372
|
+
args: [...shape.args],
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function makeDryRunPlan(action, commands, extra = {}) {
|
|
377
|
+
const list = Array.isArray(commands) ? commands : [commands];
|
|
378
|
+
const plan = {
|
|
379
|
+
dryRun: true,
|
|
380
|
+
action,
|
|
381
|
+
commands: list.map(cloneCommand),
|
|
382
|
+
};
|
|
383
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
384
|
+
if (value !== undefined) plan[key] = value;
|
|
385
|
+
}
|
|
386
|
+
return plan;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function resultText(result) {
|
|
390
|
+
if (!result) return '';
|
|
391
|
+
if (result.error && result.error.message) return result.error.message;
|
|
392
|
+
return String(result.stderr || result.stdout || '').trim();
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function resultOutput(result) {
|
|
396
|
+
return String((result && (result.stdout || result.stderr)) || '').trim();
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function checkResult(name, command, result) {
|
|
400
|
+
return {
|
|
401
|
+
name,
|
|
402
|
+
command: cloneCommand(command),
|
|
403
|
+
ok: Boolean(result && result.status === 0 && !result.error),
|
|
404
|
+
status: result && typeof result.status === 'number' ? result.status : null,
|
|
405
|
+
output: resultOutput(result),
|
|
406
|
+
error: resultText(result),
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function assertResult(result, message) {
|
|
411
|
+
if (result && result.error) throw result.error;
|
|
412
|
+
if (!result || result.status === 0) return result;
|
|
413
|
+
const detail = String(result.stderr || result.stdout || '').trim();
|
|
414
|
+
throw new Error(`${message}${detail ? `: ${detail}` : '.'}`);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function createKittyBackend(config = {}) {
|
|
418
|
+
const runtime = runnerFor(config);
|
|
419
|
+
const run = (shape, options = {}) => {
|
|
420
|
+
const input = Object.prototype.hasOwnProperty.call(shape, 'input') && options.input === undefined
|
|
421
|
+
? shape.input
|
|
422
|
+
: options.input;
|
|
423
|
+
return runtime.run(shape.cmd, shape.args, {
|
|
424
|
+
...options,
|
|
425
|
+
input,
|
|
426
|
+
env: mergeEnv(config, options),
|
|
427
|
+
});
|
|
428
|
+
};
|
|
429
|
+
const dryRun = Boolean(config.dryRun);
|
|
430
|
+
|
|
431
|
+
function describe() {
|
|
432
|
+
const commands = buildAvailabilityCommands(config);
|
|
433
|
+
if (dryRun) return makeDryRunPlan('check-availability', commands);
|
|
434
|
+
|
|
435
|
+
const versionResult = run(commands[0], { stdio: 'pipe' });
|
|
436
|
+
const versionCheck = checkResult('kitty --version', commands[0], versionResult);
|
|
437
|
+
if (!versionCheck.ok) {
|
|
438
|
+
return {
|
|
439
|
+
name: 'kitty',
|
|
440
|
+
available: false,
|
|
441
|
+
installed: false,
|
|
442
|
+
remoteControl: false,
|
|
443
|
+
binary: commands[0].cmd,
|
|
444
|
+
message: KITTY_MISSING_MESSAGE,
|
|
445
|
+
error: versionCheck.error,
|
|
446
|
+
checks: [versionCheck],
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const remoteResult = run(commands[1], { stdio: 'pipe' });
|
|
451
|
+
const remoteCheck = checkResult('kitty @ ls', commands[1], remoteResult);
|
|
452
|
+
const remoteControl = remoteCheck.ok;
|
|
453
|
+
return {
|
|
454
|
+
name: 'kitty',
|
|
455
|
+
available: remoteControl,
|
|
456
|
+
installed: true,
|
|
457
|
+
remoteControl,
|
|
458
|
+
binary: commands[0].cmd,
|
|
459
|
+
version: versionCheck.output,
|
|
460
|
+
message: remoteControl ? 'Kitty remote control is available.' : KITTY_REMOTE_CONTROL_MESSAGE,
|
|
461
|
+
error: remoteControl ? '' : remoteCheck.error,
|
|
462
|
+
checks: [versionCheck, remoteCheck],
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function execute(action, shape, options = {}, message) {
|
|
467
|
+
const input = Object.prototype.hasOwnProperty.call(shape, 'input') && options.input === undefined
|
|
468
|
+
? shape.input
|
|
469
|
+
: options.input;
|
|
470
|
+
if (dryRun) {
|
|
471
|
+
return makeDryRunPlan(action, shape, {
|
|
472
|
+
cwd: options.cwd,
|
|
473
|
+
input,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
return assertResult(run(shape, { ...options, input }), message);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function spawnHost(command, options = {}) {
|
|
480
|
+
const spawn = options.spawn || cp.spawn;
|
|
481
|
+
const env = mergeEnv(config, options) || process.env;
|
|
482
|
+
const child = spawn(command.cmd, command.args, {
|
|
483
|
+
cwd: options.cwd || process.cwd(),
|
|
484
|
+
env: { ...process.env, ...env },
|
|
485
|
+
detached: options.detached !== false,
|
|
486
|
+
stdio: options.stdio || 'ignore',
|
|
487
|
+
});
|
|
488
|
+
if (options.detached !== false && typeof child.unref === 'function') child.unref();
|
|
489
|
+
return child;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function bootstrapHost(options = {}) {
|
|
493
|
+
const socket = options.socket || defaultHostSocketPath(options.socketPrefix);
|
|
494
|
+
const command = buildKittyHostBootstrapCommand({ ...options, socket }, config);
|
|
495
|
+
if (dryRun) {
|
|
496
|
+
return makeDryRunPlan('bootstrap-kitty-host', command, {
|
|
497
|
+
socket: command.socket,
|
|
498
|
+
listenOn: command.listenOn,
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
const child = spawnHost(command, options);
|
|
502
|
+
const timeoutMs = Number.isFinite(options.timeoutMs) && options.timeoutMs > 0
|
|
503
|
+
? options.timeoutMs
|
|
504
|
+
: DEFAULT_HOST_READY_TIMEOUT_MS;
|
|
505
|
+
const intervalMs = Number.isFinite(options.intervalMs) && options.intervalMs > 0
|
|
506
|
+
? options.intervalMs
|
|
507
|
+
: DEFAULT_HOST_POLL_INTERVAL_MS;
|
|
508
|
+
const fsImpl = options.fs || fs;
|
|
509
|
+
const sleep = options.sleep || ((ms) => {
|
|
510
|
+
const end = Date.now() + ms;
|
|
511
|
+
while (Date.now() < end) { /* busy wait, intentionally sync */ }
|
|
512
|
+
});
|
|
513
|
+
const deadline = Date.now() + timeoutMs;
|
|
514
|
+
while (Date.now() < deadline) {
|
|
515
|
+
if (socketReady(command.socket, { fs: fsImpl })) {
|
|
516
|
+
return {
|
|
517
|
+
action: 'bootstrap-kitty-host',
|
|
518
|
+
command,
|
|
519
|
+
socket: command.socket,
|
|
520
|
+
listenOn: command.listenOn,
|
|
521
|
+
pid: child && child.pid,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
sleep(intervalMs);
|
|
525
|
+
}
|
|
526
|
+
throw new Error(KITTY_HOST_SOCKET_TIMEOUT_MESSAGE);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return {
|
|
530
|
+
name: 'kitty',
|
|
531
|
+
isAvailable() {
|
|
532
|
+
if (dryRun) return makeDryRunPlan('check-availability', buildAvailabilityCommands(config));
|
|
533
|
+
return describe().available;
|
|
534
|
+
},
|
|
535
|
+
describe,
|
|
536
|
+
remoteControlUnavailableMessage: KITTY_REMOTE_CONTROL_MESSAGE,
|
|
537
|
+
missingMessage: KITTY_MISSING_MESSAGE,
|
|
538
|
+
dryRunPlan(action, commands, extra = {}) {
|
|
539
|
+
return makeDryRunPlan(action, commands, extra);
|
|
540
|
+
},
|
|
541
|
+
bootstrapHost,
|
|
542
|
+
buildHostCommand(options = {}) {
|
|
543
|
+
const socket = options.socket || defaultHostSocketPath(options.socketPrefix);
|
|
544
|
+
return buildKittyHostBootstrapCommand({ ...options, socket }, config);
|
|
545
|
+
},
|
|
546
|
+
openCockpitLayout(options = {}) {
|
|
547
|
+
return execute(
|
|
548
|
+
'open-cockpit-layout',
|
|
549
|
+
buildOpenCockpitLayoutCommand(options, config),
|
|
550
|
+
{ cwd: options.repoRoot },
|
|
551
|
+
'kitty could not open cockpit layout',
|
|
552
|
+
);
|
|
553
|
+
},
|
|
554
|
+
launchAgentPane(options = {}) {
|
|
555
|
+
return execute(
|
|
556
|
+
'launch-agent-pane',
|
|
557
|
+
buildLaunchAgentPaneCommand(options, config),
|
|
558
|
+
{ cwd: options.worktree },
|
|
559
|
+
'kitty could not launch agent pane',
|
|
560
|
+
);
|
|
561
|
+
},
|
|
562
|
+
launchTerminalPane(options = {}) {
|
|
563
|
+
return execute(
|
|
564
|
+
'launch-terminal-pane',
|
|
565
|
+
buildLaunchTerminalPaneCommand(options, config),
|
|
566
|
+
{ cwd: options.cwd },
|
|
567
|
+
'kitty could not launch terminal pane',
|
|
568
|
+
);
|
|
569
|
+
},
|
|
570
|
+
focusPane(target) {
|
|
571
|
+
return execute('focus-pane', buildFocusPaneCommand(target, config), {}, 'kitty could not focus pane');
|
|
572
|
+
},
|
|
573
|
+
closePane(target) {
|
|
574
|
+
return execute('close-pane', buildClosePaneCommand(target, config), {}, 'kitty could not close pane');
|
|
575
|
+
},
|
|
576
|
+
sendText(target, value, options = {}) {
|
|
577
|
+
return execute(
|
|
578
|
+
'send-text',
|
|
579
|
+
buildSendTextCommand(target, config),
|
|
580
|
+
{
|
|
581
|
+
input: sendTextInput(value, options),
|
|
582
|
+
stdio: 'pipe',
|
|
583
|
+
},
|
|
584
|
+
'kitty could not send text',
|
|
585
|
+
);
|
|
586
|
+
},
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function createBackend(config = {}) {
|
|
591
|
+
return createKittyBackend(config);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
module.exports = {
|
|
595
|
+
DEFAULT_KITTY_BIN,
|
|
596
|
+
KITTY_MISSING_MESSAGE,
|
|
597
|
+
KITTY_REMOTE_CONTROL_MESSAGE,
|
|
598
|
+
KITTY_HOST_SOCKET_TIMEOUT_MESSAGE,
|
|
599
|
+
buildKittyLaunchCommand,
|
|
600
|
+
buildKittyFocusCommand,
|
|
601
|
+
buildKittyCloseCommand,
|
|
602
|
+
buildKittySendTextCommand,
|
|
603
|
+
buildKittyLsCommand,
|
|
604
|
+
buildKittyVersionCommand,
|
|
605
|
+
buildVersionCommand,
|
|
606
|
+
buildAvailabilityCommand,
|
|
607
|
+
buildAvailabilityCommands,
|
|
608
|
+
buildOpenCockpitLayoutCommand,
|
|
609
|
+
buildLaunchAgentPaneCommand,
|
|
610
|
+
buildLaunchTerminalPaneCommand,
|
|
611
|
+
buildFocusPaneCommand,
|
|
612
|
+
buildClosePaneCommand,
|
|
613
|
+
buildSendTextCommand,
|
|
614
|
+
buildKittyHostBootstrapCommand,
|
|
615
|
+
defaultHostSocketPath,
|
|
616
|
+
injectRemoteControl,
|
|
617
|
+
socketReady,
|
|
618
|
+
createBackend,
|
|
619
|
+
createKittyBackend,
|
|
620
|
+
sendTextInput,
|
|
621
|
+
targetMatch,
|
|
622
|
+
};
|