@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,215 @@
|
|
|
1
|
+
function line(label, value) {
|
|
2
|
+
return `${label}: ${value || '-'}`;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
const BUCKETS = [
|
|
6
|
+
{ key: 'working', title: 'WORKING NOW' },
|
|
7
|
+
{ key: 'thinking', title: 'THINKING' },
|
|
8
|
+
{ key: 'blocked', title: 'BLOCKED' },
|
|
9
|
+
{ key: 'done', title: 'DONE' },
|
|
10
|
+
{ key: 'stale', title: 'STALE' },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
function lockSummary(locks) {
|
|
14
|
+
if (!Array.isArray(locks) || locks.length === 0) {
|
|
15
|
+
return 'none';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const preview = locks.slice(0, 3).join(', ');
|
|
19
|
+
const suffix = locks.length > 3 ? `, +${locks.length - 3} more` : '';
|
|
20
|
+
return `${locks.length} (${preview}${suffix})`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function lockCountSummary(session) {
|
|
24
|
+
if (Array.isArray(session.locks)) {
|
|
25
|
+
return lockSummary(session.locks);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return Number.isFinite(session.lockCount) ? String(session.lockCount) : 'none';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function filePreview(files) {
|
|
32
|
+
if (!Array.isArray(files) || files.length === 0) {
|
|
33
|
+
return 'none';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const preview = files.slice(0, 3).join(', ');
|
|
37
|
+
const suffix = files.length > 3 ? `, +${files.length - 3} more` : '';
|
|
38
|
+
return `${files.length} (${preview}${suffix})`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function metadataSummary(metadata) {
|
|
42
|
+
if (!metadata || typeof metadata !== 'object') return '';
|
|
43
|
+
return Object.entries(metadata)
|
|
44
|
+
.filter(([key, value]) => key.startsWith('colony.') && value !== null && value !== undefined && String(value) !== '')
|
|
45
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
46
|
+
.join(' ');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function worktreeSummary(session) {
|
|
50
|
+
const worktreePath = session.worktreePath || '-';
|
|
51
|
+
if (session.worktreeExists === false) {
|
|
52
|
+
return `${worktreePath} (missing)`;
|
|
53
|
+
}
|
|
54
|
+
if (session.worktreeExists === true) {
|
|
55
|
+
return `${worktreePath} (present)`;
|
|
56
|
+
}
|
|
57
|
+
return worktreePath;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function normalizeState(value) {
|
|
61
|
+
return String(value || '').trim().toLowerCase();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function sessionBucket(session) {
|
|
65
|
+
if (session.worktreeExists === false) {
|
|
66
|
+
return 'stale';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const status = normalizeState(session.status);
|
|
70
|
+
const activity = normalizeState(session.activity);
|
|
71
|
+
const prState = normalizeState(session.prState);
|
|
72
|
+
const state = `${status} ${activity}`.trim();
|
|
73
|
+
|
|
74
|
+
if (prState === 'merged' || /\b(done|complete|completed|merged)\b/.test(state)) {
|
|
75
|
+
return 'done';
|
|
76
|
+
}
|
|
77
|
+
if (/\b(blocked|failed|failing|error|errored|stalled|dead)\b/.test(state)) {
|
|
78
|
+
return 'blocked';
|
|
79
|
+
}
|
|
80
|
+
if (/\b(thinking|pending|queued|idle|waiting)\b/.test(state)) {
|
|
81
|
+
return 'thinking';
|
|
82
|
+
}
|
|
83
|
+
if (/\b(working|running|active|orbiting|symbioting|advising)\b/.test(state)) {
|
|
84
|
+
return 'working';
|
|
85
|
+
}
|
|
86
|
+
return 'thinking';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function groupSessions(sessions) {
|
|
90
|
+
const grouped = new Map(BUCKETS.map((bucket) => [bucket.key, []]));
|
|
91
|
+
sessions.forEach((session) => {
|
|
92
|
+
grouped.get(sessionBucket(session)).push(session);
|
|
93
|
+
});
|
|
94
|
+
return grouped;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function summaryLine(grouped) {
|
|
98
|
+
return BUCKETS
|
|
99
|
+
.map((bucket) => `${bucket.key}=${grouped.get(bucket.key).length}`)
|
|
100
|
+
.join(' ');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function stage(value) {
|
|
104
|
+
return value ? 'ok' : 'todo';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function progressSummary(session) {
|
|
108
|
+
const metadata = session.metadata && typeof session.metadata === 'object' ? session.metadata : {};
|
|
109
|
+
const hasSpec = Boolean(metadata['colony.plan'] || metadata['colony.subtask'] || metadata['colony.task_id']);
|
|
110
|
+
const hasCode = (
|
|
111
|
+
(Array.isArray(session.changedFiles) && session.changedFiles.length > 0) ||
|
|
112
|
+
(Array.isArray(session.locks) && session.locks.length > 0) ||
|
|
113
|
+
Number(session.lockCount || 0) > 0
|
|
114
|
+
);
|
|
115
|
+
const hasPr = Boolean(session.prUrl || session.prState);
|
|
116
|
+
const merged = normalizeState(session.prState) === 'merged';
|
|
117
|
+
|
|
118
|
+
return [
|
|
119
|
+
`Spec ${stage(hasSpec)}`,
|
|
120
|
+
`Code ${hasCode ? 'active' : 'todo'}`,
|
|
121
|
+
`Tests ${metadata['colony.verification'] ? 'ok' : 'todo'}`,
|
|
122
|
+
`PR ${stage(hasPr)}`,
|
|
123
|
+
`Merge ${stage(merged)}`,
|
|
124
|
+
`Cleanup ${merged ? 'ready' : 'todo'}`,
|
|
125
|
+
].join(' | ');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function readinessSummary(session) {
|
|
129
|
+
const bucket = sessionBucket(session);
|
|
130
|
+
if (bucket === 'stale') return 'STALE';
|
|
131
|
+
if (bucket === 'blocked') return 'BLOCKED';
|
|
132
|
+
if (normalizeState(session.prState) === 'merged') return 'MERGED';
|
|
133
|
+
if (session.prUrl) return 'PR OPEN';
|
|
134
|
+
if (Array.isArray(session.changedFiles) && session.changedFiles.length > 0) return 'CHANGED';
|
|
135
|
+
return 'OPEN';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function renderSession(session, index) {
|
|
139
|
+
const lines = [
|
|
140
|
+
`${index + 1}. ${session.agentName || 'agent'} | ${readinessSummary(session)} | ${session.status || 'unknown'}`,
|
|
141
|
+
` branch: ${session.branch || '-'}`,
|
|
142
|
+
` progress: ${progressSummary(session)}`,
|
|
143
|
+
` worktree: ${worktreeSummary(session)}`,
|
|
144
|
+
` locks: ${lockCountSummary(session)}`,
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
if (Array.isArray(session.changedFiles)) {
|
|
148
|
+
lines.push(` changed: ${filePreview(session.changedFiles)}`);
|
|
149
|
+
}
|
|
150
|
+
if (session.task) {
|
|
151
|
+
lines.push(` task: ${session.task}`);
|
|
152
|
+
}
|
|
153
|
+
const meta = metadataSummary(session.metadata);
|
|
154
|
+
if (meta) {
|
|
155
|
+
lines.push(` colony: ${meta}`);
|
|
156
|
+
}
|
|
157
|
+
if (session.prUrl || session.prState) {
|
|
158
|
+
lines.push(` pr: ${session.prState || '-'} ${session.prUrl || '-'}`);
|
|
159
|
+
}
|
|
160
|
+
if (session.lastHeartbeatAt) {
|
|
161
|
+
lines.push(` heartbeat: ${session.lastHeartbeatAt}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return lines.join('\n');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function renderCockpit(state) {
|
|
168
|
+
const sessions = Array.isArray(state && state.sessions) ? state.sessions : [];
|
|
169
|
+
const grouped = groupSessions(sessions);
|
|
170
|
+
const lines = [
|
|
171
|
+
'GitGuardex Cockpit Fleet',
|
|
172
|
+
line('repo', state && state.repoPath),
|
|
173
|
+
line('base', state && state.baseBranch),
|
|
174
|
+
line('active sessions', String(sessions.length)),
|
|
175
|
+
line('summary', summaryLine(grouped)),
|
|
176
|
+
'actions: Enter inspect | f finish | h handoff | r refresh',
|
|
177
|
+
'',
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
if (sessions.length === 0) {
|
|
181
|
+
lines.push('No active agent sessions.');
|
|
182
|
+
} else {
|
|
183
|
+
let displayIndex = 0;
|
|
184
|
+
BUCKETS.forEach((bucket) => {
|
|
185
|
+
const bucketSessions = grouped.get(bucket.key);
|
|
186
|
+
if (bucketSessions.length === 0) return;
|
|
187
|
+
if (displayIndex > 0) {
|
|
188
|
+
lines.push('');
|
|
189
|
+
}
|
|
190
|
+
lines.push(`${bucket.title} (${bucketSessions.length})`);
|
|
191
|
+
bucketSessions.forEach((session) => {
|
|
192
|
+
lines.push(renderSession(session, displayIndex));
|
|
193
|
+
displayIndex += 1;
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
lines.push('');
|
|
197
|
+
lines.push('detail: selected lane shows branch, progress, claims, changed files, PR, heartbeat, and Colony metadata.');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return `${lines.join('\n')}\n`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = {
|
|
204
|
+
renderCockpit,
|
|
205
|
+
renderSession,
|
|
206
|
+
filePreview,
|
|
207
|
+
groupSessions,
|
|
208
|
+
progressSummary,
|
|
209
|
+
readinessSummary,
|
|
210
|
+
sessionBucket,
|
|
211
|
+
lockSummary,
|
|
212
|
+
lockCountSummary,
|
|
213
|
+
metadataSummary,
|
|
214
|
+
worktreeSummary,
|
|
215
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { SETTINGS_KEYBINDINGS } = require('./shortcuts');
|
|
4
|
+
const { colorize, getCockpitTheme } = require('./theme');
|
|
5
|
+
|
|
6
|
+
const AVAILABLE_THEMES = 'blue, amber, dim, high-contrast, none';
|
|
7
|
+
|
|
8
|
+
const DEFAULT_SETTINGS = {
|
|
9
|
+
theme: 'blue',
|
|
10
|
+
sidebarWidth: 32,
|
|
11
|
+
refreshMs: 2000,
|
|
12
|
+
defaultAgent: 'codex',
|
|
13
|
+
defaultBase: 'main',
|
|
14
|
+
showLocks: true,
|
|
15
|
+
showWorktreePaths: true,
|
|
16
|
+
autopilotDefault: false,
|
|
17
|
+
editorCommand: '',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const SECTION_DEFINITIONS = [
|
|
21
|
+
{
|
|
22
|
+
title: 'Appearance',
|
|
23
|
+
fields: [
|
|
24
|
+
['theme', 'Theme', AVAILABLE_THEMES],
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
title: 'Layout',
|
|
29
|
+
fields: [
|
|
30
|
+
['sidebarWidth', 'Sidebar width', '20-80 columns'],
|
|
31
|
+
['refreshMs', 'Refresh interval', '500-60000 ms'],
|
|
32
|
+
['showWorktreePaths', 'Show worktree paths', 'true, false'],
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
title: 'Agents',
|
|
37
|
+
fields: [
|
|
38
|
+
['defaultAgent', 'Default agent', 'codex, claude, opencode, cursor, gemini'],
|
|
39
|
+
['defaultBase', 'Default base', 'any branch name'],
|
|
40
|
+
['autopilotDefault', 'Autopilot default', 'true, false'],
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
title: 'Safety',
|
|
45
|
+
fields: [
|
|
46
|
+
['showLocks', 'Show locks', 'true, false'],
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
title: 'Editor',
|
|
51
|
+
fields: [
|
|
52
|
+
['editorCommand', 'Editor command', 'any shell command, blank'],
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
function normalizeSettings(settings) {
|
|
58
|
+
if (!settings || typeof settings !== 'object' || Array.isArray(settings)) {
|
|
59
|
+
return { ...DEFAULT_SETTINGS };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
...DEFAULT_SETTINGS,
|
|
64
|
+
...settings,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function formatValue(value) {
|
|
69
|
+
if (value === '') {
|
|
70
|
+
return '(blank)';
|
|
71
|
+
}
|
|
72
|
+
if (value === undefined || value === null) {
|
|
73
|
+
return '-';
|
|
74
|
+
}
|
|
75
|
+
return String(value);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function fieldLine(field, label, available, settings, selectedField, theme) {
|
|
79
|
+
const marker = field === selectedField ? '>' : ' ';
|
|
80
|
+
const line = `${marker} ${label}: ${formatValue(settings[field])} (available: ${available})`;
|
|
81
|
+
return field === selectedField ? colorize(line, 'selected', theme) : line;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function resolveSelectedField(options) {
|
|
85
|
+
if (!options || typeof options !== 'object') {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
if (typeof options.selectedField === 'string') {
|
|
89
|
+
return options.selectedField;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function renderSection(section, settings, selectedField, theme) {
|
|
96
|
+
const lines = [colorize(`[${section.title}]`, 'heading', theme)];
|
|
97
|
+
for (const [field, label, available] of section.fields) {
|
|
98
|
+
lines.push(fieldLine(field, label, available, settings, selectedField, theme));
|
|
99
|
+
}
|
|
100
|
+
return lines.join('\n');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function renderSettingsScreen(settings, options = {}) {
|
|
104
|
+
const current = normalizeSettings(settings);
|
|
105
|
+
const theme = getCockpitTheme(options.theme || current.theme, options);
|
|
106
|
+
const selectedField = resolveSelectedField(options);
|
|
107
|
+
const lines = [
|
|
108
|
+
colorize('gx cockpit settings', 'title', theme),
|
|
109
|
+
colorize('Plain terminal settings view', 'secondary', theme),
|
|
110
|
+
'',
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
for (const section of SECTION_DEFINITIONS) {
|
|
114
|
+
lines.push(renderSection(section, current, selectedField, theme));
|
|
115
|
+
lines.push('');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
lines.push(colorize('[Keybindings]', 'heading', theme));
|
|
119
|
+
for (const keybinding of SETTINGS_KEYBINDINGS) {
|
|
120
|
+
lines.push(colorize(` ${keybinding}`, 'secondary', theme));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return `${lines.join('\n')}\n`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = {
|
|
127
|
+
renderSettingsScreen,
|
|
128
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const SETTINGS_RELATIVE_PATH = ['.guardex', 'cockpit', 'settings.json'];
|
|
7
|
+
const THEMES = new Set(['default', 'dim', 'high-contrast']);
|
|
8
|
+
const AGENTS = new Set(['codex', 'claude', 'opencode', 'cursor', 'gemini']);
|
|
9
|
+
const MIN_SIDEBAR_WIDTH = 20;
|
|
10
|
+
const MAX_SIDEBAR_WIDTH = 80;
|
|
11
|
+
const MIN_REFRESH_MS = 500;
|
|
12
|
+
const MAX_REFRESH_MS = 60000;
|
|
13
|
+
|
|
14
|
+
function defaultCockpitSettings() {
|
|
15
|
+
return {
|
|
16
|
+
theme: 'default',
|
|
17
|
+
sidebarWidth: 32,
|
|
18
|
+
refreshMs: 2000,
|
|
19
|
+
defaultAgent: 'codex',
|
|
20
|
+
defaultBase: 'main',
|
|
21
|
+
showLocks: true,
|
|
22
|
+
showWorktreePaths: true,
|
|
23
|
+
autopilotDefault: false,
|
|
24
|
+
editorCommand: '',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function settingsPath(repoRoot = process.cwd()) {
|
|
29
|
+
return path.join(path.resolve(repoRoot), ...SETTINGS_RELATIVE_PATH);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function clampNumber(value, fallback, min, max) {
|
|
33
|
+
const number = typeof value === 'number' ? value : Number(value);
|
|
34
|
+
if (!Number.isFinite(number)) {
|
|
35
|
+
return fallback;
|
|
36
|
+
}
|
|
37
|
+
return Math.min(max, Math.max(min, Math.round(number)));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function stringSetting(value, fallback) {
|
|
41
|
+
if (typeof value !== 'string') {
|
|
42
|
+
return fallback;
|
|
43
|
+
}
|
|
44
|
+
const trimmed = value.trim();
|
|
45
|
+
return trimmed || fallback;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function booleanSetting(value, fallback) {
|
|
49
|
+
return typeof value === 'boolean' ? value : fallback;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function normalizeCockpitSettings(raw) {
|
|
53
|
+
const defaults = defaultCockpitSettings();
|
|
54
|
+
const source = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {};
|
|
55
|
+
const theme = stringSetting(source.theme, defaults.theme);
|
|
56
|
+
const defaultAgent = stringSetting(source.defaultAgent, defaults.defaultAgent);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
theme: THEMES.has(theme) ? theme : defaults.theme,
|
|
60
|
+
sidebarWidth: clampNumber(
|
|
61
|
+
source.sidebarWidth,
|
|
62
|
+
defaults.sidebarWidth,
|
|
63
|
+
MIN_SIDEBAR_WIDTH,
|
|
64
|
+
MAX_SIDEBAR_WIDTH,
|
|
65
|
+
),
|
|
66
|
+
refreshMs: clampNumber(source.refreshMs, defaults.refreshMs, MIN_REFRESH_MS, MAX_REFRESH_MS),
|
|
67
|
+
defaultAgent: AGENTS.has(defaultAgent) ? defaultAgent : defaults.defaultAgent,
|
|
68
|
+
defaultBase: stringSetting(source.defaultBase, defaults.defaultBase),
|
|
69
|
+
showLocks: booleanSetting(source.showLocks, defaults.showLocks),
|
|
70
|
+
showWorktreePaths: booleanSetting(source.showWorktreePaths, defaults.showWorktreePaths),
|
|
71
|
+
autopilotDefault: booleanSetting(source.autopilotDefault, defaults.autopilotDefault),
|
|
72
|
+
editorCommand: typeof source.editorCommand === 'string' ? source.editorCommand.trim() : defaults.editorCommand,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function readCockpitSettings(repoRoot = process.cwd()) {
|
|
77
|
+
const target = settingsPath(repoRoot);
|
|
78
|
+
try {
|
|
79
|
+
return normalizeCockpitSettings(JSON.parse(fs.readFileSync(target, 'utf8')));
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (error && (error.code === 'ENOENT' || error instanceof SyntaxError)) {
|
|
82
|
+
return defaultCockpitSettings();
|
|
83
|
+
}
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function writeCockpitSettings(repoRoot = process.cwd(), settings = {}) {
|
|
89
|
+
const normalized = normalizeCockpitSettings(settings);
|
|
90
|
+
const target = settingsPath(repoRoot);
|
|
91
|
+
const dir = path.dirname(target);
|
|
92
|
+
const temp = path.join(dir, `.settings.${process.pid}.${Date.now()}.tmp`);
|
|
93
|
+
|
|
94
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
95
|
+
try {
|
|
96
|
+
fs.writeFileSync(temp, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8');
|
|
97
|
+
fs.renameSync(temp, target);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
try {
|
|
100
|
+
fs.unlinkSync(temp);
|
|
101
|
+
} catch (_cleanupError) {
|
|
102
|
+
// Best effort: the original settings file is untouched until rename succeeds.
|
|
103
|
+
}
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return normalized;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function updateCockpitSettings(repoRoot = process.cwd(), patch = {}) {
|
|
111
|
+
const source = patch && typeof patch === 'object' && !Array.isArray(patch) ? patch : {};
|
|
112
|
+
return writeCockpitSettings(repoRoot, {
|
|
113
|
+
...readCockpitSettings(repoRoot),
|
|
114
|
+
...source,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = {
|
|
119
|
+
defaultCockpitSettings,
|
|
120
|
+
readCockpitSettings,
|
|
121
|
+
writeCockpitSettings,
|
|
122
|
+
updateCockpitSettings,
|
|
123
|
+
normalizeCockpitSettings,
|
|
124
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const WELCOME_SHORTCUTS = Object.freeze([
|
|
4
|
+
['n', 'new agent'],
|
|
5
|
+
['t', 'terminal'],
|
|
6
|
+
['s', 'settings'],
|
|
7
|
+
['?', 'shortcuts'],
|
|
8
|
+
['q', 'quit'],
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
const SETTINGS_KEYBINDINGS = Object.freeze([
|
|
12
|
+
'↑/↓ navigate',
|
|
13
|
+
'Enter edit',
|
|
14
|
+
'Esc back',
|
|
15
|
+
'q quit',
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
const CONTROL_KEY_HELP = 'keys: up/down select j/k move enter view/open n new agent t terminal m menu s settings ? shortcuts q quit';
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
CONTROL_KEY_HELP,
|
|
22
|
+
SETTINGS_KEYBINDINGS,
|
|
23
|
+
WELCOME_SHORTCUTS,
|
|
24
|
+
};
|