@imdeadpool/guardex 7.0.41 → 7.1.0
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 +94 -13
- package/package.json +3 -1
- package/skills/gitguardex/SKILL.md +13 -0
- package/skills/guardex-merge-skills-to-dev/SKILL.md +59 -0
- package/skills/gx-act/SKILL.md +82 -0
- package/src/agents/cleanup-sessions.js +126 -0
- package/src/agents/finish.js +172 -0
- package/src/agents/inspect.js +202 -0
- package/src/agents/launch.js +249 -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 +146 -0
- package/src/agents/terminal.js +152 -0
- package/src/budget/index.js +344 -0
- package/src/ci-init/index.js +265 -0
- package/src/cli/args.js +357 -3
- package/src/cli/commands/agents.js +364 -0
- package/src/cli/commands/bootstrap.js +92 -0
- package/src/cli/commands/branch.js +127 -0
- package/src/cli/commands/claude.js +674 -0
- package/src/cli/commands/doctor.js +268 -0
- package/src/cli/commands/finish.js +26 -0
- package/src/cli/commands/mcp.js +122 -0
- package/src/cli/commands/misc.js +304 -0
- package/src/cli/commands/pr.js +439 -0
- package/src/cli/commands/prompt.js +92 -0
- package/src/cli/commands/release.js +305 -0
- package/src/cli/commands/report.js +244 -0
- package/src/cli/commands/review.js +32 -0
- package/src/cli/commands/setup.js +242 -0
- package/src/cli/commands/status.js +338 -0
- package/src/cli/commands/watch.js +234 -0
- package/src/cli/main.js +85 -3613
- package/src/cli/shared/repo-env.js +161 -0
- package/src/cli/shared/sandbox.js +417 -0
- package/src/cli/shared/scaffolding.js +535 -0
- package/src/cli/shared/toolchain-shims.js +420 -0
- 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/kitty-layout.js +549 -0
- package/src/cockpit/kitty-tree.js +144 -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 +304 -43
- package/src/core/runtime.js +6 -1
- package/src/doctor/index.js +45 -15
- package/src/finish/index.js +186 -7
- package/src/finish/preflight.js +177 -0
- package/src/finish/review-gate.js +182 -0
- package/src/git/index.js +511 -4
- package/src/hooks/index.js +0 -64
- package/src/kitty/command.js +101 -0
- package/src/kitty/runtime.js +250 -0
- package/src/mcp/collect.js +370 -0
- package/src/mcp/server.js +157 -0
- package/src/output/index.js +68 -2
- package/src/pr-review.js +264 -0
- package/src/pr.js +381 -0
- package/src/sandbox/index.js +13 -2
- package/src/scaffold/agent-worktree-prep.js +213 -0
- package/src/scaffold/index.js +127 -10
- package/src/speckit/index.js +226 -0
- package/src/submodule/index.js +288 -0
- package/src/terminal/index.js +45 -0
- package/src/terminal/kitty.js +622 -0
- package/src/terminal/tmux.js +125 -0
- package/src/tmux/command.js +27 -0
- package/src/tmux/session.js +89 -0
- package/src/toolchain/index.js +20 -0
- package/templates/AGENTS.monorepo-apps.md +26 -0
- package/templates/AGENTS.multiagent-safety.md +63 -323
- package/templates/AGENTS.multiagent-safety.min.md +11 -0
- package/templates/codex/skills/gitguardex/SKILL.md +2 -0
- package/templates/codex/skills/gx-act/SKILL.md +82 -0
- package/templates/githooks/pre-commit +44 -20
- 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 +519 -23
- package/templates/scripts/agent-branch-merge.sh +4 -1
- package/templates/scripts/agent-branch-start.sh +176 -24
- package/templates/scripts/agent-preflight.sh +115 -0
- package/templates/scripts/agent-worktree-prune.sh +96 -5
- package/templates/scripts/codex-agent.sh +41 -97
- 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,1121 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { readCockpitState } = require('./state');
|
|
5
|
+
const { renderSidebar } = require('./sidebar');
|
|
6
|
+
const { renderSettingsScreen } = require('./settings-render');
|
|
7
|
+
const { CONTROL_KEY_HELP } = require('./shortcuts');
|
|
8
|
+
const { stripAnsi } = require('./theme');
|
|
9
|
+
const { renderWelcomePage } = require('./welcome');
|
|
10
|
+
const { runCockpitAction } = require('./action-runner');
|
|
11
|
+
const { findProjects } = require('./projects-finder');
|
|
12
|
+
const { readKittyTree } = require('./kitty-tree');
|
|
13
|
+
const { readLogs, filterEntries, LEVELS: LOG_LEVELS } = require('./logs-reader');
|
|
14
|
+
const {
|
|
15
|
+
PANE_MENU_ITEMS,
|
|
16
|
+
applyPaneMenuKey,
|
|
17
|
+
createPaneMenuState,
|
|
18
|
+
normalizePaneMenuKey,
|
|
19
|
+
renderPaneMenu,
|
|
20
|
+
} = require('./menu');
|
|
21
|
+
|
|
22
|
+
const DEFAULT_REFRESH_MS = 2000;
|
|
23
|
+
const DEFAULT_SETTINGS = {
|
|
24
|
+
sidebarWidth: 32,
|
|
25
|
+
refreshMs: DEFAULT_REFRESH_MS,
|
|
26
|
+
defaultAgent: 'codex',
|
|
27
|
+
defaultBase: 'main',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const MODES = new Set(['main', 'menu', 'settings', 'shortcuts', 'new-agent', 'terminal', 'logs', 'projects']);
|
|
31
|
+
const EMPTY_ACTION_ROWS = Object.freeze(['new-agent', 'terminal', 'logs', 'projects', 'settings', 'shortcuts']);
|
|
32
|
+
const SETTINGS_FIELDS = [
|
|
33
|
+
'theme',
|
|
34
|
+
'sidebarWidth',
|
|
35
|
+
'refreshMs',
|
|
36
|
+
'showWorktreePaths',
|
|
37
|
+
'defaultAgent',
|
|
38
|
+
'defaultBase',
|
|
39
|
+
'autopilotDefault',
|
|
40
|
+
'showLocks',
|
|
41
|
+
'editorCommand',
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const MENU_ITEMS = PANE_MENU_ITEMS;
|
|
45
|
+
const PANE_ACTION_IDS = new Set(PANE_MENU_ITEMS.map((item) => item.id));
|
|
46
|
+
const DIRECT_DETAIL_PANE_KEYS = new Set(['v', 'h', 'x', 'p', 'r', 'c', 'o', 'a', 'b', 'f', 'T', 'A']);
|
|
47
|
+
|
|
48
|
+
function text(value, fallback = '') {
|
|
49
|
+
if (typeof value === 'string') return value.trim() || fallback;
|
|
50
|
+
if (value === null || value === undefined) return fallback;
|
|
51
|
+
return String(value).trim() || fallback;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function number(value, fallback) {
|
|
55
|
+
const parsed = Number(value);
|
|
56
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function clampIndex(index, length) {
|
|
60
|
+
if (length <= 0) return 0;
|
|
61
|
+
if (!Number.isInteger(index)) return 0;
|
|
62
|
+
return Math.max(0, Math.min(index, length - 1));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function wrapIndex(index, length) {
|
|
66
|
+
if (length <= 0) return 0;
|
|
67
|
+
const next = Number.isInteger(index) ? index : 0;
|
|
68
|
+
return ((next % length) + length) % length;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function sessionId(session = {}) {
|
|
72
|
+
return text(session.id || session.sessionId || session.branch);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function selectedSession(state = {}) {
|
|
76
|
+
const sessions = Array.isArray(state.sessions) ? state.sessions : [];
|
|
77
|
+
if (sessions.length === 0) return null;
|
|
78
|
+
return sessions[clampIndex(state.selectedIndex, sessions.length)] || null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function paneMenuStateFromControl(state = {}) {
|
|
82
|
+
const current = normalizeControlState(state);
|
|
83
|
+
return createPaneMenuState({
|
|
84
|
+
session: selectedSession(current),
|
|
85
|
+
selectedIndex: current.menuIndex,
|
|
86
|
+
hotkeyPriority: false,
|
|
87
|
+
message: current.paneMenuMessage,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function firstString(...values) {
|
|
92
|
+
for (const value of values) {
|
|
93
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
94
|
+
return value.trim();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return '';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function cockpitSessions(state = {}) {
|
|
101
|
+
if (Array.isArray(state.sessions)) return state.sessions;
|
|
102
|
+
if (state.agentsStatus && Array.isArray(state.agentsStatus.sessions)) return state.agentsStatus.sessions;
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function resolveSelectedSession(state = {}, options = {}) {
|
|
107
|
+
if (options.session) return options.session;
|
|
108
|
+
if (options.selectedSession) return options.selectedSession;
|
|
109
|
+
|
|
110
|
+
const sessions = cockpitSessions(state);
|
|
111
|
+
const requestedSessionId = firstString(options.sessionId, state.selectedSessionId);
|
|
112
|
+
if (requestedSessionId) {
|
|
113
|
+
return sessions.find((session) => sessionId(session) === requestedSessionId) || null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const requestedBranch = firstString(options.branch, state.selectedBranch);
|
|
117
|
+
if (requestedBranch) {
|
|
118
|
+
return sessions.find((session) => firstString(session.branch, session.lane && session.lane.branch) === requestedBranch) || null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const selectedIndex = Number.isInteger(options.selectedIndex)
|
|
122
|
+
? options.selectedIndex
|
|
123
|
+
: Number.isInteger(state.selectedIndex)
|
|
124
|
+
? state.selectedIndex
|
|
125
|
+
: 0;
|
|
126
|
+
return sessions[selectedIndex] || null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function buildCockpitActionContext(state = {}, options = {}) {
|
|
130
|
+
return {
|
|
131
|
+
...options,
|
|
132
|
+
session: resolveSelectedSession(state, options),
|
|
133
|
+
repoRoot: firstString(options.repoRoot, options.repoPath, state.repoPath),
|
|
134
|
+
baseBranch: firstString(options.baseBranch, state.baseBranch),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function runCockpitControlAction(action, state = {}, options = {}) {
|
|
139
|
+
return runCockpitAction(action, buildCockpitActionContext(state, options));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function runSelectedLaneAction(action, context = {}) {
|
|
143
|
+
if (context.state) {
|
|
144
|
+
return runCockpitControlAction(action, context.state, context);
|
|
145
|
+
}
|
|
146
|
+
return runCockpitAction(action, context);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function normalizeSettings(settings) {
|
|
150
|
+
if (!settings || typeof settings !== 'object' || Array.isArray(settings)) {
|
|
151
|
+
return { ...DEFAULT_SETTINGS };
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
...DEFAULT_SETTINGS,
|
|
155
|
+
...settings,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function normalizeActionRows(rows) {
|
|
160
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
161
|
+
return [...EMPTY_ACTION_ROWS];
|
|
162
|
+
}
|
|
163
|
+
const normalized = rows.map((row) => text(row)).filter(Boolean);
|
|
164
|
+
return normalized.length > 0 ? normalized : [...EMPTY_ACTION_ROWS];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function normalizeMode(mode) {
|
|
168
|
+
if (mode === 'details') return 'main';
|
|
169
|
+
return MODES.has(mode) ? mode : 'main';
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function normalizeControlState(state = {}) {
|
|
173
|
+
const cockpitState = state.cockpitState && typeof state.cockpitState === 'object'
|
|
174
|
+
? state.cockpitState
|
|
175
|
+
: state;
|
|
176
|
+
const sessions = Array.isArray(state.sessions)
|
|
177
|
+
? state.sessions
|
|
178
|
+
: Array.isArray(cockpitState.sessions)
|
|
179
|
+
? cockpitState.sessions
|
|
180
|
+
: [];
|
|
181
|
+
const actionRows = normalizeActionRows(state.actionRows);
|
|
182
|
+
const selectedIndex = clampIndex(number(state.selectedIndex, 0), sessions.length);
|
|
183
|
+
const selected = sessions[selectedIndex] || null;
|
|
184
|
+
const selectedScope = sessions.length > 0 ? 'lane' : 'action';
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
...state,
|
|
188
|
+
cockpitState,
|
|
189
|
+
repoPath: text(state.repoPath || cockpitState.repoPath),
|
|
190
|
+
baseBranch: text(state.baseBranch || cockpitState.baseBranch),
|
|
191
|
+
sessions,
|
|
192
|
+
selectedIndex,
|
|
193
|
+
selectedSessionId: text(state.selectedSessionId || (selected && sessionId(selected))),
|
|
194
|
+
selectedScope,
|
|
195
|
+
actionRows,
|
|
196
|
+
actionIndex: wrapIndex(number(state.actionIndex, 0), actionRows.length),
|
|
197
|
+
mode: normalizeMode(state.mode),
|
|
198
|
+
menuIndex: wrapIndex(number(state.menuIndex, 0), MENU_ITEMS.length),
|
|
199
|
+
settingsIndex: wrapIndex(number(state.settingsIndex, 0), SETTINGS_FIELDS.length),
|
|
200
|
+
settings: normalizeSettings(state.settings),
|
|
201
|
+
paneMenuMessage: text(state.paneMenuMessage),
|
|
202
|
+
lastIntent: state.lastIntent || null,
|
|
203
|
+
shouldExit: Boolean(state.shouldExit),
|
|
204
|
+
error: state.error || null,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function mergeCockpitSnapshot(state, snapshot, settings, at) {
|
|
209
|
+
const current = normalizeControlState(state);
|
|
210
|
+
const cockpitState = snapshot && typeof snapshot === 'object' ? snapshot : {};
|
|
211
|
+
const sessions = Array.isArray(cockpitState.sessions) ? cockpitState.sessions : [];
|
|
212
|
+
const previousId = text(current.selectedSessionId);
|
|
213
|
+
const byId = previousId ? sessions.findIndex((session) => sessionId(session) === previousId) : -1;
|
|
214
|
+
const selectedIndex = byId >= 0 ? byId : clampIndex(current.selectedIndex, sessions.length);
|
|
215
|
+
const selected = sessions[selectedIndex] || null;
|
|
216
|
+
|
|
217
|
+
return normalizeControlState({
|
|
218
|
+
...current,
|
|
219
|
+
cockpitState,
|
|
220
|
+
repoPath: text(cockpitState.repoPath, current.repoPath),
|
|
221
|
+
baseBranch: text(cockpitState.baseBranch, current.baseBranch),
|
|
222
|
+
sessions,
|
|
223
|
+
selectedIndex,
|
|
224
|
+
selectedSessionId: selected ? sessionId(selected) : '',
|
|
225
|
+
settings: normalizeSettings(settings || current.settings),
|
|
226
|
+
lastRefreshAt: at || current.lastRefreshAt,
|
|
227
|
+
lastIntent: null,
|
|
228
|
+
error: null,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function buildIntent(state, kind) {
|
|
233
|
+
const current = normalizeControlState(state);
|
|
234
|
+
const session = selectedSession(current);
|
|
235
|
+
if (kind === 'quit') {
|
|
236
|
+
return { type: 'quit' };
|
|
237
|
+
}
|
|
238
|
+
if (kind === 'refresh') {
|
|
239
|
+
return { type: 'refresh' };
|
|
240
|
+
}
|
|
241
|
+
if (kind === 'agent:start') {
|
|
242
|
+
return {
|
|
243
|
+
type: 'agent:start',
|
|
244
|
+
agent: current.settings.defaultAgent,
|
|
245
|
+
base: current.settings.defaultBase,
|
|
246
|
+
task: text(current.newAgentInput || ''),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
if (kind === 'terminal:open') {
|
|
250
|
+
return {
|
|
251
|
+
type: 'terminal:open',
|
|
252
|
+
sessionId: session ? sessionId(session) : '',
|
|
253
|
+
branch: session ? text(session.branch) : '',
|
|
254
|
+
worktreePath: session ? text(session.worktreePath) : '',
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
if (kind === 'settings:edit') {
|
|
258
|
+
const field = SETTINGS_FIELDS[current.settingsIndex] || SETTINGS_FIELDS[0];
|
|
259
|
+
return {
|
|
260
|
+
type: 'settings:edit',
|
|
261
|
+
field,
|
|
262
|
+
value: current.settings[field],
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
if (PANE_ACTION_IDS.has(kind)) {
|
|
266
|
+
return {
|
|
267
|
+
type: kind,
|
|
268
|
+
sessionId: session ? sessionId(session) : '',
|
|
269
|
+
branch: session ? text(session.branch) : '',
|
|
270
|
+
worktreePath: session ? text(session.worktreePath) : '',
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return { type: kind };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function chooseMenuItem(state) {
|
|
277
|
+
const current = normalizeControlState(state);
|
|
278
|
+
const result = applyPaneMenuKey(paneMenuStateFromControl(current), 'enter');
|
|
279
|
+
if (result.action !== 'select') {
|
|
280
|
+
return normalizeControlState({
|
|
281
|
+
...current,
|
|
282
|
+
menuIndex: result.state.selectedIndex,
|
|
283
|
+
paneMenuMessage: result.state.message,
|
|
284
|
+
lastIntent: null,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
const intent = buildIntent(current, result.actionId);
|
|
288
|
+
return normalizeControlState({
|
|
289
|
+
...current,
|
|
290
|
+
mode: 'main',
|
|
291
|
+
paneMenuMessage: '',
|
|
292
|
+
shouldExit: intent.type === 'quit',
|
|
293
|
+
lastIntent: intent,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function normalizeKey(value) {
|
|
298
|
+
if (!value) return '';
|
|
299
|
+
if (value && typeof value === 'object' && !Buffer.isBuffer(value)) {
|
|
300
|
+
if ((value.meta || value.alt) && value.shift && String(value.name || value.key || '').toLowerCase() === 'm') {
|
|
301
|
+
return 'alt-shift-m';
|
|
302
|
+
}
|
|
303
|
+
return normalizeKey(value.name || value.sequence || value.key || '');
|
|
304
|
+
}
|
|
305
|
+
const raw = Buffer.isBuffer(value) ? value.toString('utf8') : String(value);
|
|
306
|
+
if (raw === '\u0003') return 'ctrl-c';
|
|
307
|
+
if (raw === '\u001bM' || raw === '\u001bm') return 'alt-shift-m';
|
|
308
|
+
if (raw === '\u001b') return 'escape';
|
|
309
|
+
if (raw === '\r' || raw === '\n') return 'enter';
|
|
310
|
+
if (raw === '\u001b[A') return 'up';
|
|
311
|
+
if (raw === '\u001b[B') return 'down';
|
|
312
|
+
if (raw === '\t') return 'tab';
|
|
313
|
+
if (/^alt(?:\+|-)?shift(?:\+|-)?m$/i.test(raw)) return 'alt-shift-m';
|
|
314
|
+
if (/^(esc|escape)$/i.test(raw)) return 'escape';
|
|
315
|
+
return raw.toLowerCase();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function moveSelection(state, direction) {
|
|
319
|
+
const current = normalizeControlState(state);
|
|
320
|
+
if (current.sessions.length > 0) {
|
|
321
|
+
return normalizeControlState({
|
|
322
|
+
...current,
|
|
323
|
+
selectedScope: 'lane',
|
|
324
|
+
selectedIndex: wrapIndex(current.selectedIndex + direction, current.sessions.length),
|
|
325
|
+
selectedSessionId: '',
|
|
326
|
+
lastIntent: null,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return normalizeControlState({
|
|
331
|
+
...current,
|
|
332
|
+
selectedScope: 'action',
|
|
333
|
+
selectedIndex: 0,
|
|
334
|
+
actionIndex: wrapIndex(current.actionIndex + direction, current.actionRows.length),
|
|
335
|
+
selectedSessionId: '',
|
|
336
|
+
lastIntent: null,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function openActionRow(state, actionId) {
|
|
341
|
+
const current = normalizeControlState(state);
|
|
342
|
+
if (actionId === 'new-agent') {
|
|
343
|
+
return normalizeControlState({ ...current, mode: 'new-agent', lastIntent: null });
|
|
344
|
+
}
|
|
345
|
+
if (actionId === 'terminal') {
|
|
346
|
+
return normalizeControlState({ ...current, mode: 'terminal', lastIntent: null });
|
|
347
|
+
}
|
|
348
|
+
if (actionId === 'settings') {
|
|
349
|
+
return normalizeControlState({ ...current, mode: 'settings', lastIntent: null });
|
|
350
|
+
}
|
|
351
|
+
if (actionId === 'shortcuts') {
|
|
352
|
+
return normalizeControlState({ ...current, mode: 'shortcuts', lastIntent: null });
|
|
353
|
+
}
|
|
354
|
+
if (actionId === 'logs') {
|
|
355
|
+
const withLogs = loadLogsState(current);
|
|
356
|
+
return normalizeControlState({
|
|
357
|
+
...withLogs,
|
|
358
|
+
mode: 'logs',
|
|
359
|
+
lastIntent: null,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
if (actionId === 'projects') {
|
|
363
|
+
const withProjects = loadProjectsState(current);
|
|
364
|
+
return normalizeControlState({
|
|
365
|
+
...withProjects,
|
|
366
|
+
mode: 'projects',
|
|
367
|
+
lastIntent: null,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
return normalizeControlState({ ...current, lastIntent: null });
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function openSelectedActionRow(state) {
|
|
374
|
+
const current = normalizeControlState(state);
|
|
375
|
+
return openActionRow(current, current.actionRows[current.actionIndex] || current.actionRows[0]);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function applyKey(state, rawKey) {
|
|
379
|
+
const current = normalizeControlState(state);
|
|
380
|
+
const key = normalizeKey(rawKey);
|
|
381
|
+
const mode = current.mode;
|
|
382
|
+
|
|
383
|
+
if (mode === 'menu') {
|
|
384
|
+
const result = applyPaneMenuKey(paneMenuStateFromControl(current), rawKey);
|
|
385
|
+
if (result.action === 'cancel') {
|
|
386
|
+
return normalizeControlState({
|
|
387
|
+
...current,
|
|
388
|
+
mode: 'main',
|
|
389
|
+
paneMenuMessage: '',
|
|
390
|
+
lastIntent: null,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
if (result.action === 'select') {
|
|
394
|
+
return normalizeControlState({
|
|
395
|
+
...current,
|
|
396
|
+
mode: 'main',
|
|
397
|
+
menuIndex: result.state.selectedIndex,
|
|
398
|
+
paneMenuMessage: '',
|
|
399
|
+
lastIntent: buildIntent(current, result.actionId),
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
return normalizeControlState({
|
|
403
|
+
...current,
|
|
404
|
+
menuIndex: result.state.selectedIndex,
|
|
405
|
+
paneMenuMessage: result.state.message,
|
|
406
|
+
lastIntent: null,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (key === 'ctrl-c' || key === 'q') {
|
|
411
|
+
return normalizeControlState({
|
|
412
|
+
...current,
|
|
413
|
+
shouldExit: true,
|
|
414
|
+
lastIntent: buildIntent(current, 'quit'),
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
if (key === 'escape') {
|
|
418
|
+
return normalizeControlState({
|
|
419
|
+
...current,
|
|
420
|
+
mode: 'main',
|
|
421
|
+
lastIntent: null,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
if (mode === 'main' && key === 'p' && current.selectedScope === 'action') {
|
|
425
|
+
return openActionRow(current, 'projects');
|
|
426
|
+
}
|
|
427
|
+
if (mode === 'main' && DIRECT_DETAIL_PANE_KEYS.has(normalizePaneMenuKey(rawKey))) {
|
|
428
|
+
const result = applyPaneMenuKey(paneMenuStateFromControl(current), rawKey);
|
|
429
|
+
if (result.action === 'select') {
|
|
430
|
+
return normalizeControlState({
|
|
431
|
+
...current,
|
|
432
|
+
paneMenuMessage: '',
|
|
433
|
+
lastIntent: buildIntent(current, result.actionId),
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
return normalizeControlState({
|
|
437
|
+
...current,
|
|
438
|
+
paneMenuMessage: result.state.message,
|
|
439
|
+
lastIntent: null,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
if (mode === 'new-agent') {
|
|
443
|
+
const raw = typeof rawKey === 'string' ? rawKey : (rawKey && rawKey.sequence) || '';
|
|
444
|
+
if (raw === '' || raw === '\b') {
|
|
445
|
+
const next = (current.newAgentInput || '').slice(0, -1);
|
|
446
|
+
return normalizeControlState({ ...current, newAgentInput: next, lastIntent: null });
|
|
447
|
+
}
|
|
448
|
+
if (typeof raw === 'string' && raw.length === 1) {
|
|
449
|
+
const code = raw.charCodeAt(0);
|
|
450
|
+
if (code >= 0x20 && code <= 0x7e) {
|
|
451
|
+
const next = `${current.newAgentInput || ''}${raw}`;
|
|
452
|
+
return normalizeControlState({ ...current, newAgentInput: next, lastIntent: null });
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (key === 'n') {
|
|
457
|
+
return openActionRow(current, 'new-agent');
|
|
458
|
+
}
|
|
459
|
+
if (key === 't') {
|
|
460
|
+
return openActionRow(current, 'terminal');
|
|
461
|
+
}
|
|
462
|
+
if (key === 'l') {
|
|
463
|
+
return openActionRow(current, 'logs');
|
|
464
|
+
}
|
|
465
|
+
if (key === '?') {
|
|
466
|
+
return openActionRow(current, 'shortcuts');
|
|
467
|
+
}
|
|
468
|
+
if (key === 's') {
|
|
469
|
+
return openActionRow(current, 'settings');
|
|
470
|
+
}
|
|
471
|
+
if (key === 'm' || key === 'tab' || key === 'alt-shift-m') {
|
|
472
|
+
return normalizeControlState({
|
|
473
|
+
...current,
|
|
474
|
+
mode: 'menu',
|
|
475
|
+
paneMenuMessage: '',
|
|
476
|
+
lastIntent: null,
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
if (key === 'enter') {
|
|
480
|
+
if (mode === 'menu') return chooseMenuItem(current);
|
|
481
|
+
if (mode === 'settings') {
|
|
482
|
+
return normalizeControlState({
|
|
483
|
+
...current,
|
|
484
|
+
lastIntent: buildIntent(current, 'settings:edit'),
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
if (mode === 'new-agent') {
|
|
488
|
+
const intent = buildIntent(current, 'agent:start');
|
|
489
|
+
return normalizeControlState({
|
|
490
|
+
...current,
|
|
491
|
+
mode: 'main',
|
|
492
|
+
newAgentInput: '',
|
|
493
|
+
lastIntent: intent,
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
if (mode === 'terminal') {
|
|
497
|
+
return normalizeControlState({
|
|
498
|
+
...current,
|
|
499
|
+
mode: 'main',
|
|
500
|
+
lastIntent: buildIntent(current, 'terminal:open'),
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
if (mode === 'projects') {
|
|
504
|
+
const projects = Array.isArray(current.projects) ? current.projects : [];
|
|
505
|
+
const project = projects[current.projectsIndex] || null;
|
|
506
|
+
if (!project) return current;
|
|
507
|
+
return normalizeControlState({
|
|
508
|
+
...current,
|
|
509
|
+
mode: 'main',
|
|
510
|
+
lastIntent: {
|
|
511
|
+
type: 'project:switch',
|
|
512
|
+
path: project.path,
|
|
513
|
+
name: project.name,
|
|
514
|
+
},
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
if (current.sessions.length === 0 && current.selectedScope === 'action') {
|
|
518
|
+
return openSelectedActionRow(current);
|
|
519
|
+
}
|
|
520
|
+
return normalizeControlState({
|
|
521
|
+
...current,
|
|
522
|
+
mode: 'main',
|
|
523
|
+
lastIntent: buildIntent(current, 'view'),
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
if (key === 'down' || key === 'j') {
|
|
527
|
+
if (mode === 'menu') {
|
|
528
|
+
return normalizeControlState({ ...current, menuIndex: current.menuIndex + 1, lastIntent: null });
|
|
529
|
+
}
|
|
530
|
+
if (mode === 'settings') {
|
|
531
|
+
return normalizeControlState({ ...current, settingsIndex: current.settingsIndex + 1, lastIntent: null });
|
|
532
|
+
}
|
|
533
|
+
if (mode === 'projects') {
|
|
534
|
+
const projects = Array.isArray(current.projects) ? current.projects : [];
|
|
535
|
+
if (projects.length === 0) return current;
|
|
536
|
+
const next = (current.projectsIndex + 1) % projects.length;
|
|
537
|
+
return normalizeControlState({ ...current, projectsIndex: next, lastIntent: null });
|
|
538
|
+
}
|
|
539
|
+
return moveSelection(current, 1);
|
|
540
|
+
}
|
|
541
|
+
if (key === 'up' || key === 'k') {
|
|
542
|
+
if (mode === 'menu') {
|
|
543
|
+
return normalizeControlState({ ...current, menuIndex: current.menuIndex - 1, lastIntent: null });
|
|
544
|
+
}
|
|
545
|
+
if (mode === 'settings') {
|
|
546
|
+
return normalizeControlState({ ...current, settingsIndex: current.settingsIndex - 1, lastIntent: null });
|
|
547
|
+
}
|
|
548
|
+
if (mode === 'projects') {
|
|
549
|
+
const projects = Array.isArray(current.projects) ? current.projects : [];
|
|
550
|
+
if (projects.length === 0) return current;
|
|
551
|
+
const next = (current.projectsIndex - 1 + projects.length) % projects.length;
|
|
552
|
+
return normalizeControlState({ ...current, projectsIndex: next, lastIntent: null });
|
|
553
|
+
}
|
|
554
|
+
return moveSelection(current, -1);
|
|
555
|
+
}
|
|
556
|
+
if (mode === 'projects' && key === 'r') {
|
|
557
|
+
const refreshed = loadProjectsState(current, { refresh: true });
|
|
558
|
+
return normalizeControlState({ ...refreshed, lastIntent: null });
|
|
559
|
+
}
|
|
560
|
+
if (mode === 'logs') {
|
|
561
|
+
if (Object.prototype.hasOwnProperty.call(LOGS_FILTER_KEYS, key)) {
|
|
562
|
+
return normalizeControlState({
|
|
563
|
+
...current,
|
|
564
|
+
logsFilter: LOGS_FILTER_KEYS[key],
|
|
565
|
+
lastIntent: null,
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
if (key === 'r') {
|
|
569
|
+
const refreshed = loadLogsState(current, { refresh: true });
|
|
570
|
+
return normalizeControlState({ ...refreshed, lastIntent: null });
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return current;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function applyCockpitAction(state, action = {}) {
|
|
578
|
+
const current = normalizeControlState(state);
|
|
579
|
+
const type = action.type || action.kind;
|
|
580
|
+
|
|
581
|
+
if (type === 'refresh' || type === 'state:refresh') {
|
|
582
|
+
return mergeCockpitSnapshot(current, action.cockpitState || action.state, action.settings, action.at);
|
|
583
|
+
}
|
|
584
|
+
if (type === 'key') {
|
|
585
|
+
return applyKey(current, action.key || action.input || action.sequence || action.name);
|
|
586
|
+
}
|
|
587
|
+
if (type === 'mode') {
|
|
588
|
+
return normalizeControlState({ ...current, mode: action.mode, lastIntent: null });
|
|
589
|
+
}
|
|
590
|
+
if (type === 'select') {
|
|
591
|
+
return normalizeControlState({ ...current, selectedIndex: number(action.index, current.selectedIndex), selectedSessionId: '', lastIntent: null });
|
|
592
|
+
}
|
|
593
|
+
if (type === 'menu:choose') {
|
|
594
|
+
return chooseMenuItem(current);
|
|
595
|
+
}
|
|
596
|
+
if (type === 'intent:clear') {
|
|
597
|
+
return normalizeControlState({ ...current, lastIntent: null });
|
|
598
|
+
}
|
|
599
|
+
if (type === 'error') {
|
|
600
|
+
return normalizeControlState({ ...current, error: action.error || action.message || 'Unknown cockpit error' });
|
|
601
|
+
}
|
|
602
|
+
if (type === 'quit') {
|
|
603
|
+
return normalizeControlState({ ...current, shouldExit: true, lastIntent: buildIntent(current, 'quit') });
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
return current;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function splitLines(value) {
|
|
610
|
+
return String(value || '').replace(/\n$/, '').split('\n');
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function padAnsi(value, width) {
|
|
614
|
+
const raw = String(value || '');
|
|
615
|
+
const visible = stripAnsi(raw).length;
|
|
616
|
+
return visible >= width ? raw : `${raw}${' '.repeat(width - visible)}`;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function visibleWidth(value) {
|
|
620
|
+
return stripAnsi(value).length;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function centerLine(value, width) {
|
|
624
|
+
const raw = String(value || '');
|
|
625
|
+
const left = Math.max(0, Math.floor((width - visibleWidth(raw)) / 2));
|
|
626
|
+
return `${' '.repeat(left)}${raw}`;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function overlayCenteredBox(baseLines, overlayText) {
|
|
630
|
+
const overlay = splitLines(overlayText);
|
|
631
|
+
const width = Math.max(
|
|
632
|
+
...baseLines.map((line) => visibleWidth(line)),
|
|
633
|
+
...overlay.map((line) => visibleWidth(line)),
|
|
634
|
+
);
|
|
635
|
+
const height = Math.max(baseLines.length, overlay.length + 2);
|
|
636
|
+
const lines = [...baseLines];
|
|
637
|
+
|
|
638
|
+
while (lines.length < height) lines.push('');
|
|
639
|
+
|
|
640
|
+
const top = Math.max(0, Math.floor((height - overlay.length) / 2));
|
|
641
|
+
for (let index = 0; index < overlay.length; index += 1) {
|
|
642
|
+
lines[top + index] = centerLine(overlay[index], width);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return lines;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function selectedField(state) {
|
|
649
|
+
const current = normalizeControlState(state);
|
|
650
|
+
return SETTINGS_FIELDS[current.settingsIndex] || SETTINGS_FIELDS[0];
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function welcomeState(state) {
|
|
654
|
+
const current = normalizeControlState(state);
|
|
655
|
+
return {
|
|
656
|
+
...current.cockpitState,
|
|
657
|
+
repoPath: current.repoPath,
|
|
658
|
+
baseBranch: current.baseBranch,
|
|
659
|
+
sessions: current.sessions,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function renderDetailsPanel(state) {
|
|
664
|
+
const current = normalizeControlState(state);
|
|
665
|
+
const session = selectedSession(current);
|
|
666
|
+
const lines = [
|
|
667
|
+
'main',
|
|
668
|
+
`repo: ${current.repoPath || '-'}`,
|
|
669
|
+
`base: ${current.baseBranch || '-'}`,
|
|
670
|
+
`mode: ${current.mode}`,
|
|
671
|
+
`refresh: ${current.settings.refreshMs}ms`,
|
|
672
|
+
'',
|
|
673
|
+
];
|
|
674
|
+
|
|
675
|
+
if (!session) {
|
|
676
|
+
lines.push('No session selected.');
|
|
677
|
+
} else {
|
|
678
|
+
lines.push(
|
|
679
|
+
`session: ${sessionId(session) || '-'}`,
|
|
680
|
+
`agent: ${text(session.agentName, 'agent')}`,
|
|
681
|
+
`status: ${text(session.status, 'unknown')}`,
|
|
682
|
+
`branch: ${text(session.branch, '-')}`,
|
|
683
|
+
`worktree: ${text(session.worktreePath, '-')}`,
|
|
684
|
+
);
|
|
685
|
+
if (session.task) lines.push(`task: ${session.task}`);
|
|
686
|
+
lines.push(`locks: ${Number.isFinite(session.lockCount) ? session.lockCount : 0}`);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
lines.push('', CONTROL_KEY_HELP);
|
|
690
|
+
if (current.error) {
|
|
691
|
+
lines.push('', `error: ${text(current.error)}`);
|
|
692
|
+
}
|
|
693
|
+
if (current.lastIntent) {
|
|
694
|
+
lines.push('', `intent: ${current.lastIntent.type}`);
|
|
695
|
+
}
|
|
696
|
+
return `${lines.join('\n')}\n`;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function renderShortcutsPanel() {
|
|
700
|
+
return [
|
|
701
|
+
'shortcuts',
|
|
702
|
+
'',
|
|
703
|
+
'j/down: next lane',
|
|
704
|
+
'k/up: previous lane',
|
|
705
|
+
'enter: view selected lane / open selected action',
|
|
706
|
+
'n: new agent',
|
|
707
|
+
't: terminal',
|
|
708
|
+
'l: logs',
|
|
709
|
+
'p: projects (no lane selected)',
|
|
710
|
+
'm or Alt+Shift+M: pane menu',
|
|
711
|
+
's: settings',
|
|
712
|
+
'v/h/x/p/r/c/o/a/b/f/T/A: pane actions',
|
|
713
|
+
'esc: back to main',
|
|
714
|
+
'q: quit',
|
|
715
|
+
'',
|
|
716
|
+
].join('\n');
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function renderNewAgentPanel(state) {
|
|
720
|
+
const current = normalizeControlState(state);
|
|
721
|
+
const input = text(current.newAgentInput || '');
|
|
722
|
+
const repoLabel = current.repoPath ? path.basename(current.repoPath) : 'project';
|
|
723
|
+
const inputBox = `+${'-'.repeat(64)}+`;
|
|
724
|
+
const inputRow = `| > ${input}_${' '.repeat(Math.max(60 - input.length, 0))} |`;
|
|
725
|
+
return [
|
|
726
|
+
`+ New Pane - ${repoLabel}`,
|
|
727
|
+
'',
|
|
728
|
+
`Project: ${repoLabel} (${current.repoPath || '-'})`,
|
|
729
|
+
`Agent: ${current.settings.defaultAgent}`,
|
|
730
|
+
`Base: ${current.settings.defaultBase}`,
|
|
731
|
+
'',
|
|
732
|
+
'Enter a prompt for your AI agent.',
|
|
733
|
+
'',
|
|
734
|
+
inputBox,
|
|
735
|
+
inputRow,
|
|
736
|
+
inputBox,
|
|
737
|
+
'',
|
|
738
|
+
'Enter to submit · Backspace to edit · Esc to cancel',
|
|
739
|
+
'',
|
|
740
|
+
].join('\n');
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function renderTerminalPanel(state) {
|
|
744
|
+
const current = normalizeControlState(state);
|
|
745
|
+
const session = selectedSession(current);
|
|
746
|
+
return [
|
|
747
|
+
'terminal',
|
|
748
|
+
'',
|
|
749
|
+
session
|
|
750
|
+
? `target: ${sessionId(session) || text(session.branch, 'selected lane')}`
|
|
751
|
+
: `target: ${current.repoPath || 'repo'}`,
|
|
752
|
+
'',
|
|
753
|
+
'Enter: open Kitty terminal',
|
|
754
|
+
'Esc: back to main',
|
|
755
|
+
'',
|
|
756
|
+
].join('\n');
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
const LOGS_FILTER_KEYS = {
|
|
760
|
+
'1': 'all',
|
|
761
|
+
'2': 'info',
|
|
762
|
+
'3': 'warning',
|
|
763
|
+
'4': 'error',
|
|
764
|
+
'5': 'by-pane',
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
function loadLogsState(current, options = {}) {
|
|
768
|
+
if (current.logs && options.refresh !== true) {
|
|
769
|
+
return current;
|
|
770
|
+
}
|
|
771
|
+
const result = readLogs({
|
|
772
|
+
repoRoot: current.repoPath,
|
|
773
|
+
fs: options.fs,
|
|
774
|
+
sources: options.sources,
|
|
775
|
+
limit: options.limit,
|
|
776
|
+
tailBytes: options.tailBytes,
|
|
777
|
+
});
|
|
778
|
+
return {
|
|
779
|
+
...current,
|
|
780
|
+
logs: result.entries,
|
|
781
|
+
logsCounts: result.counts,
|
|
782
|
+
logsSources: result.sources,
|
|
783
|
+
logsFilter: current.logsFilter || 'all',
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function logsFilterLabel(filter) {
|
|
788
|
+
switch (filter) {
|
|
789
|
+
case 'info': return 'Info';
|
|
790
|
+
case 'warning': return 'Warnings';
|
|
791
|
+
case 'error': return 'Errors';
|
|
792
|
+
case 'by-pane': return 'By Pane';
|
|
793
|
+
default: return 'All';
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function renderLogsPanel(state) {
|
|
798
|
+
const current = normalizeControlState(state);
|
|
799
|
+
const counts = current.logsCounts || { all: 0 };
|
|
800
|
+
const filter = current.logsFilter || 'all';
|
|
801
|
+
const entries = filterEntries(current.logs || [], filter);
|
|
802
|
+
const sources = Array.isArray(current.logsSources) ? current.logsSources : [];
|
|
803
|
+
const summary = `${counts.all || 0} total`
|
|
804
|
+
+ ` ${counts.info || 0} info`
|
|
805
|
+
+ ` ${counts.warning || 0} warn`
|
|
806
|
+
+ ` ${counts.error || 0} err`;
|
|
807
|
+
|
|
808
|
+
const lines = [
|
|
809
|
+
'gitguardex logs',
|
|
810
|
+
'',
|
|
811
|
+
summary,
|
|
812
|
+
`filter: ${logsFilterLabel(filter)}`,
|
|
813
|
+
`sources: ${sources.length}`,
|
|
814
|
+
'',
|
|
815
|
+
'[1] All [2] Info [3] Warnings [4] Errors [5] By Pane',
|
|
816
|
+
'',
|
|
817
|
+
];
|
|
818
|
+
|
|
819
|
+
if (entries.length === 0) {
|
|
820
|
+
lines.push(' no log entries (filter or no log files yet)');
|
|
821
|
+
} else {
|
|
822
|
+
const tail = entries.slice(-20);
|
|
823
|
+
for (const entry of tail) {
|
|
824
|
+
const tag = entry.level === 'error' ? '[ERR]'
|
|
825
|
+
: entry.level === 'warning' ? '[WRN]'
|
|
826
|
+
: entry.level === 'debug' ? '[DBG]'
|
|
827
|
+
: '[INF]';
|
|
828
|
+
lines.push(`${tag} ${entry.source} · ${entry.line}`);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
lines.push('');
|
|
833
|
+
lines.push('r: rescan Esc: back to main');
|
|
834
|
+
lines.push('');
|
|
835
|
+
return lines.join('\n');
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
function loadProjectsState(current, options = {}) {
|
|
839
|
+
if (Array.isArray(current.projects) && current.projects.length > 0 && options.refresh !== true) {
|
|
840
|
+
return current;
|
|
841
|
+
}
|
|
842
|
+
const result = findProjects({
|
|
843
|
+
repoRoot: current.repoPath,
|
|
844
|
+
env: options.env || process.env,
|
|
845
|
+
fs: options.fs,
|
|
846
|
+
});
|
|
847
|
+
return {
|
|
848
|
+
...current,
|
|
849
|
+
projects: result.projects,
|
|
850
|
+
projectsRoots: result.roots,
|
|
851
|
+
projectsIndex: 0,
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
function renderProjectsPanel(state) {
|
|
856
|
+
const current = normalizeControlState(state);
|
|
857
|
+
const projects = Array.isArray(current.projects) ? current.projects : [];
|
|
858
|
+
const roots = Array.isArray(current.projectsRoots) ? current.projectsRoots : [];
|
|
859
|
+
const index = Math.max(0, Math.min(current.projectsIndex || 0, Math.max(projects.length - 1, 0)));
|
|
860
|
+
const lines = [
|
|
861
|
+
'projects',
|
|
862
|
+
'',
|
|
863
|
+
`current: ${current.repoPath || '(none)'}`,
|
|
864
|
+
`roots: ${roots.join(' | ') || '(none)'}`,
|
|
865
|
+
'',
|
|
866
|
+
];
|
|
867
|
+
|
|
868
|
+
if (projects.length === 0) {
|
|
869
|
+
lines.push(' no git repos found under any configured root');
|
|
870
|
+
lines.push(' set GUARDEX_PROJECT_ROOTS=/path/a:/path/b to override');
|
|
871
|
+
} else {
|
|
872
|
+
projects.forEach((project, i) => {
|
|
873
|
+
const cursor = i === index ? '>' : ' ';
|
|
874
|
+
const here = project.path === current.repoPath ? '*' : ' ';
|
|
875
|
+
lines.push(`${cursor} ${here} ${project.name}`);
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
lines.push('');
|
|
880
|
+
lines.push('Enter: switch to selected project');
|
|
881
|
+
lines.push('r: rescan');
|
|
882
|
+
lines.push('Esc: back to main');
|
|
883
|
+
lines.push('');
|
|
884
|
+
return lines.join('\n');
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function renderMenuPanel(state) {
|
|
888
|
+
const current = normalizeControlState(state);
|
|
889
|
+
return renderPaneMenu(paneMenuStateFromControl(current), { width: 72, theme: current.settings.theme });
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
function renderSettingsPanel(state) {
|
|
893
|
+
const current = normalizeControlState(state);
|
|
894
|
+
return renderSettingsScreen(current.settings, {
|
|
895
|
+
selectedField: selectedField(current),
|
|
896
|
+
theme: current.settings.theme,
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function renderPanel(state) {
|
|
901
|
+
const current = normalizeControlState(state);
|
|
902
|
+
if (current.mode === 'menu') return renderMenuPanel(current);
|
|
903
|
+
if (current.mode === 'settings') return renderSettingsPanel(current);
|
|
904
|
+
if (current.mode === 'shortcuts') return renderShortcutsPanel(current);
|
|
905
|
+
if (current.mode === 'new-agent') return renderNewAgentPanel(current);
|
|
906
|
+
if (current.mode === 'terminal') return renderTerminalPanel(current);
|
|
907
|
+
if (current.mode === 'logs') return renderLogsPanel(current);
|
|
908
|
+
if (current.mode === 'projects') return renderProjectsPanel(current);
|
|
909
|
+
if (current.sessions.length === 0) {
|
|
910
|
+
return renderWelcomePage(welcomeState(current), current.settings);
|
|
911
|
+
}
|
|
912
|
+
return renderDetailsPanel(current);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function renderControlFrame(state) {
|
|
916
|
+
const current = normalizeControlState(state);
|
|
917
|
+
const width = number(current.settings.sidebarWidth, DEFAULT_SETTINGS.sidebarWidth);
|
|
918
|
+
const sidebar = splitLines(renderSidebar(current, { width, theme: current.settings.theme }));
|
|
919
|
+
const framePanelState = current.mode === 'menu'
|
|
920
|
+
? normalizeControlState({ ...current, mode: 'main' })
|
|
921
|
+
: current;
|
|
922
|
+
const panel = splitLines(renderPanel(framePanelState));
|
|
923
|
+
const leftWidth = Math.max(width, ...sidebar.map((line) => stripAnsi(line).length));
|
|
924
|
+
const max = Math.max(sidebar.length, panel.length);
|
|
925
|
+
const lines = [];
|
|
926
|
+
|
|
927
|
+
for (let index = 0; index < max; index += 1) {
|
|
928
|
+
lines.push(`${padAnsi(sidebar[index] || '', leftWidth)} ${panel[index] || ''}`.trimEnd());
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
const rendered = current.mode === 'menu'
|
|
932
|
+
? overlayCenteredBox(lines, renderMenuPanel(current))
|
|
933
|
+
: lines;
|
|
934
|
+
|
|
935
|
+
return `${rendered.join('\n')}\n`;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
function optionalSettingsModule() {
|
|
939
|
+
try {
|
|
940
|
+
return require('./settings');
|
|
941
|
+
} catch (error) {
|
|
942
|
+
if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message || '').includes('./settings')) {
|
|
943
|
+
return null;
|
|
944
|
+
}
|
|
945
|
+
throw error;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
function readCockpitSettings(repoPath = process.cwd(), deps = {}) {
|
|
950
|
+
if (typeof deps.readSettings === 'function') return deps.readSettings(repoPath);
|
|
951
|
+
if (typeof deps.readCockpitSettings === 'function') return deps.readCockpitSettings(repoPath);
|
|
952
|
+
|
|
953
|
+
const settingsModule = optionalSettingsModule();
|
|
954
|
+
if (!settingsModule) return {};
|
|
955
|
+
if (typeof settingsModule.readCockpitSettings === 'function') return settingsModule.readCockpitSettings(repoPath);
|
|
956
|
+
if (typeof settingsModule.readSettings === 'function') return settingsModule.readSettings(repoPath);
|
|
957
|
+
if (typeof settingsModule.loadSettings === 'function') return settingsModule.loadSettings(repoPath);
|
|
958
|
+
return {};
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function readControlSnapshot(options = {}, previousState) {
|
|
962
|
+
const repoPath = options.repoPath || process.cwd();
|
|
963
|
+
const stateReader = typeof options.readState === 'function' ? options.readState : readCockpitState;
|
|
964
|
+
const cockpitState = stateReader(repoPath);
|
|
965
|
+
const settings = readCockpitSettings(repoPath, options);
|
|
966
|
+
const at = typeof options.now === 'function' ? options.now() : new Date().toISOString();
|
|
967
|
+
const next = applyCockpitAction(previousState || { repoPath }, {
|
|
968
|
+
type: 'refresh',
|
|
969
|
+
cockpitState,
|
|
970
|
+
settings,
|
|
971
|
+
at,
|
|
972
|
+
});
|
|
973
|
+
return attachKittyTree(next, options);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
function attachKittyTree(state, options = {}) {
|
|
977
|
+
if (!state || typeof state !== 'object') return state;
|
|
978
|
+
const env = options.env || process.env;
|
|
979
|
+
if (!env || !env.KITTY_LISTEN_ON) {
|
|
980
|
+
if (state.kittyTree) {
|
|
981
|
+
return { ...state, kittyTree: null };
|
|
982
|
+
}
|
|
983
|
+
return state;
|
|
984
|
+
}
|
|
985
|
+
const reader = typeof options.readKittyTree === 'function' ? options.readKittyTree : readKittyTree;
|
|
986
|
+
let tree;
|
|
987
|
+
try {
|
|
988
|
+
tree = reader({
|
|
989
|
+
env,
|
|
990
|
+
repoRoot: state.repoPath,
|
|
991
|
+
runner: options.kittyTreeRunner,
|
|
992
|
+
timeoutMs: options.kittyTreeTimeoutMs,
|
|
993
|
+
});
|
|
994
|
+
} catch (_error) {
|
|
995
|
+
return state;
|
|
996
|
+
}
|
|
997
|
+
if (!tree || tree.error) return state;
|
|
998
|
+
return { ...state, kittyTree: tree };
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
function refreshMsFrom(options, state) {
|
|
1002
|
+
if (options.refreshMs === false || options.refreshMs === 0) return 0;
|
|
1003
|
+
const requested = number(options.refreshMs, number(state && state.settings && state.settings.refreshMs, DEFAULT_REFRESH_MS));
|
|
1004
|
+
return requested > 0 ? requested : DEFAULT_REFRESH_MS;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
function startCockpitControl(options = {}) {
|
|
1008
|
+
const stdin = options.stdin || process.stdin;
|
|
1009
|
+
const stdout = options.stdout || process.stdout;
|
|
1010
|
+
const setTimer = options.setInterval || setInterval;
|
|
1011
|
+
const clearTimer = options.clearInterval || clearInterval;
|
|
1012
|
+
const clearScreen = options.clearScreen !== false;
|
|
1013
|
+
let state = readControlSnapshot(options);
|
|
1014
|
+
let interval = null;
|
|
1015
|
+
let stopped = false;
|
|
1016
|
+
let rawModeEnabled = false;
|
|
1017
|
+
|
|
1018
|
+
const paint = () => {
|
|
1019
|
+
if (stdout && typeof stdout.write === 'function') {
|
|
1020
|
+
if (clearScreen && stdout.isTTY) stdout.write('\x1b[H\x1b[2J\x1b[3J');
|
|
1021
|
+
stdout.write(renderControlFrame(state));
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
|
|
1025
|
+
const refresh = () => {
|
|
1026
|
+
try {
|
|
1027
|
+
state = readControlSnapshot(options, state);
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
state = applyCockpitAction(state, {
|
|
1030
|
+
type: 'error',
|
|
1031
|
+
error: error && error.message ? error.message : String(error),
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
paint();
|
|
1035
|
+
return state;
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
const dispatch = (action) => {
|
|
1039
|
+
state = applyCockpitAction(state, action);
|
|
1040
|
+
const intent = state.lastIntent;
|
|
1041
|
+
if (intent && intent.type === 'refresh') {
|
|
1042
|
+
state = applyCockpitAction(state, { type: 'intent:clear' });
|
|
1043
|
+
refresh();
|
|
1044
|
+
} else {
|
|
1045
|
+
paint();
|
|
1046
|
+
}
|
|
1047
|
+
if (state.shouldExit) stop();
|
|
1048
|
+
return intent;
|
|
1049
|
+
};
|
|
1050
|
+
|
|
1051
|
+
const onData = (chunk) => dispatch({ type: 'key', key: chunk });
|
|
1052
|
+
|
|
1053
|
+
function stop() {
|
|
1054
|
+
if (stopped) return state;
|
|
1055
|
+
stopped = true;
|
|
1056
|
+
if (interval) {
|
|
1057
|
+
clearTimer(interval);
|
|
1058
|
+
interval = null;
|
|
1059
|
+
}
|
|
1060
|
+
if (stdin && typeof stdin.off === 'function') {
|
|
1061
|
+
stdin.off('data', onData);
|
|
1062
|
+
} else if (stdin && typeof stdin.removeListener === 'function') {
|
|
1063
|
+
stdin.removeListener('data', onData);
|
|
1064
|
+
}
|
|
1065
|
+
if (rawModeEnabled && typeof stdin.setRawMode === 'function') {
|
|
1066
|
+
stdin.setRawMode(false);
|
|
1067
|
+
}
|
|
1068
|
+
return state;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
paint();
|
|
1072
|
+
|
|
1073
|
+
const ms = refreshMsFrom(options, state);
|
|
1074
|
+
if (ms > 0) {
|
|
1075
|
+
interval = setTimer(refresh, ms);
|
|
1076
|
+
if (interval && typeof interval.unref === 'function') interval.unref();
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
if (stdin && stdin.isTTY && typeof stdin.on === 'function') {
|
|
1080
|
+
if (typeof stdin.setEncoding === 'function') stdin.setEncoding('utf8');
|
|
1081
|
+
if (typeof stdin.setRawMode === 'function') {
|
|
1082
|
+
stdin.setRawMode(true);
|
|
1083
|
+
rawModeEnabled = true;
|
|
1084
|
+
}
|
|
1085
|
+
if (typeof stdin.resume === 'function') stdin.resume();
|
|
1086
|
+
stdin.on('data', onData);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
return {
|
|
1090
|
+
dispatch,
|
|
1091
|
+
refresh,
|
|
1092
|
+
stop,
|
|
1093
|
+
getState: () => state,
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
if (require.main === module) {
|
|
1098
|
+
startCockpitControl({
|
|
1099
|
+
repoPath: process.argv[2] || process.cwd(),
|
|
1100
|
+
refreshMs: Number.parseInt(process.env.GUARDEX_COCKPIT_REFRESH_MS || String(DEFAULT_REFRESH_MS), 10),
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
module.exports = {
|
|
1105
|
+
MENU_ITEMS,
|
|
1106
|
+
SETTINGS_FIELDS,
|
|
1107
|
+
applyCockpitAction,
|
|
1108
|
+
applyCockpitKey: applyKey,
|
|
1109
|
+
attachKittyTree,
|
|
1110
|
+
buildCockpitActionContext,
|
|
1111
|
+
normalizeControlState,
|
|
1112
|
+
normalizeKey,
|
|
1113
|
+
readCockpitSettings,
|
|
1114
|
+
readControlSnapshot,
|
|
1115
|
+
renderControlFrame,
|
|
1116
|
+
resolveSelectedSession,
|
|
1117
|
+
runCockpitAction,
|
|
1118
|
+
runCockpitControlAction,
|
|
1119
|
+
runSelectedLaneAction,
|
|
1120
|
+
startCockpitControl,
|
|
1121
|
+
};
|