@imdeadpool/guardex 7.0.39 → 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 +85 -16
- 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 +78 -35
- package/src/doctor/index.js +4 -3
- 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 +421 -37
- 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 +545 -27
- package/templates/scripts/agent-branch-start.sh +193 -21
- 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,387 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const PANE_MENU_ACTION_IDS = Object.freeze({
|
|
4
|
+
VIEW: 'view',
|
|
5
|
+
HIDE_PANE: 'hide-pane',
|
|
6
|
+
CLOSE: 'close',
|
|
7
|
+
MERGE: 'merge',
|
|
8
|
+
CREATE_PR: 'create-pr',
|
|
9
|
+
PROJECT_FOCUS: 'project-focus',
|
|
10
|
+
RENAME: 'rename',
|
|
11
|
+
COPY_PATH: 'copy-path',
|
|
12
|
+
OPEN_EDITOR: 'open-editor',
|
|
13
|
+
TOGGLE_AUTOPILOT: 'toggle-autopilot',
|
|
14
|
+
CREATE_CHILD_WORKTREE: 'create-child-worktree',
|
|
15
|
+
BROWSE_FILES: 'browse-files',
|
|
16
|
+
ADD_TERMINAL: 'add-terminal',
|
|
17
|
+
ADD_AGENT: 'add-agent',
|
|
18
|
+
REOPEN_CLOSED_WORKTREE: 'reopen-closed-worktree',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const PANE_MENU_ACTIONS = PANE_MENU_ACTION_IDS;
|
|
22
|
+
|
|
23
|
+
const PANE_MENU_ITEMS = Object.freeze([
|
|
24
|
+
{ id: PANE_MENU_ACTION_IDS.VIEW, label: 'View', hotkey: 'j', needsSession: true },
|
|
25
|
+
{ id: PANE_MENU_ACTION_IDS.HIDE_PANE, label: 'Hide Pane', hotkey: 'h', needsSession: true },
|
|
26
|
+
{ id: PANE_MENU_ACTION_IDS.CLOSE, label: 'Close', hotkey: 'x', danger: true, needsSession: true },
|
|
27
|
+
{ id: PANE_MENU_ACTION_IDS.MERGE, label: 'Merge', hotkey: 'm', needsSession: true, needsWorktree: true, needsBranch: true },
|
|
28
|
+
{ id: PANE_MENU_ACTION_IDS.CREATE_PR, label: 'Create GitHub PR', needsSession: true, needsWorktree: true, needsBranch: true },
|
|
29
|
+
{ id: PANE_MENU_ACTION_IDS.PROJECT_FOCUS, label: 'Project Focus', hotkey: 'P', needsSession: true },
|
|
30
|
+
{ id: PANE_MENU_ACTION_IDS.RENAME, label: 'Rename', needsSession: true },
|
|
31
|
+
{ id: PANE_MENU_ACTION_IDS.COPY_PATH, label: 'Copy Path', needsSession: true, needsWorktree: true },
|
|
32
|
+
{ id: PANE_MENU_ACTION_IDS.OPEN_EDITOR, label: 'Open in Editor', needsSession: true, needsWorktree: true },
|
|
33
|
+
{ id: PANE_MENU_ACTION_IDS.TOGGLE_AUTOPILOT, label: 'Toggle Autopilot', needsSession: true, needsWorktree: true, needsBranch: true },
|
|
34
|
+
{ id: PANE_MENU_ACTION_IDS.CREATE_CHILD_WORKTREE, label: 'Create Child Worktree', hotkey: 'b', needsSession: true, needsWorktree: true, needsBranch: true },
|
|
35
|
+
{ id: PANE_MENU_ACTION_IDS.BROWSE_FILES, label: 'Browse Files', hotkey: 'f', needsSession: true, needsWorktree: true },
|
|
36
|
+
{ id: PANE_MENU_ACTION_IDS.ADD_TERMINAL, label: 'Add Terminal to Worktree', hotkey: 'A', needsSession: true, needsWorktree: true },
|
|
37
|
+
{ id: PANE_MENU_ACTION_IDS.ADD_AGENT, label: 'Add Agent to Worktree', hotkey: 'a', needsSession: true, needsWorktree: true, needsBranch: true },
|
|
38
|
+
{ id: PANE_MENU_ACTION_IDS.REOPEN_CLOSED_WORKTREE, label: 'Reopen Closed Worktree', hotkey: 'r', needsSession: true },
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
const PANE_MENU_FOOTER = '↑↓ to navigate • Enter or hotkey to select • ESC to cancel';
|
|
42
|
+
|
|
43
|
+
const BOX_CHARS = {
|
|
44
|
+
unicode: {
|
|
45
|
+
topLeft: '┌',
|
|
46
|
+
topRight: '┐',
|
|
47
|
+
middleLeft: '├',
|
|
48
|
+
middleRight: '┤',
|
|
49
|
+
bottomLeft: '└',
|
|
50
|
+
bottomRight: '┘',
|
|
51
|
+
horizontal: '─',
|
|
52
|
+
vertical: '│',
|
|
53
|
+
selected: '▶',
|
|
54
|
+
},
|
|
55
|
+
ascii: {
|
|
56
|
+
topLeft: '+',
|
|
57
|
+
topRight: '+',
|
|
58
|
+
middleLeft: '+',
|
|
59
|
+
middleRight: '+',
|
|
60
|
+
bottomLeft: '+',
|
|
61
|
+
bottomRight: '+',
|
|
62
|
+
horizontal: '-',
|
|
63
|
+
vertical: '|',
|
|
64
|
+
selected: '>',
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
function firstString(...values) {
|
|
69
|
+
for (const value of values) {
|
|
70
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
71
|
+
return value.trim();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return '';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function clampIndex(index, length) {
|
|
78
|
+
if (length <= 0) return 0;
|
|
79
|
+
if (!Number.isInteger(index)) return 0;
|
|
80
|
+
return Math.max(0, Math.min(index, length - 1));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function wrapIndex(index, length) {
|
|
84
|
+
if (length <= 0) return 0;
|
|
85
|
+
const value = Number.isInteger(index) ? index : 0;
|
|
86
|
+
return ((value % length) + length) % length;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function fileName(value) {
|
|
90
|
+
const text = String(value || '').replace(/[/\\]+$/, '');
|
|
91
|
+
const parts = text.split(/[/\\]+/).filter(Boolean);
|
|
92
|
+
return parts[parts.length - 1] || '';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function selectedPaneName(session = {}, context = {}) {
|
|
96
|
+
return firstString(
|
|
97
|
+
context.name,
|
|
98
|
+
session.displayName,
|
|
99
|
+
session.paneName,
|
|
100
|
+
session.name,
|
|
101
|
+
session.agentName,
|
|
102
|
+
session.agent,
|
|
103
|
+
fileName(session.worktreePath),
|
|
104
|
+
fileName(session.path),
|
|
105
|
+
session.branch,
|
|
106
|
+
session.id,
|
|
107
|
+
'selected pane',
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function paneMenuTitle(name) {
|
|
112
|
+
const text = String(name || '').trim() || 'selected pane';
|
|
113
|
+
return text.startsWith('Menu:') ? text : `Menu: ${text}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function selectedSession(context = {}) {
|
|
117
|
+
return context.session || context.selectedSession || context.pane || context.lane || null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function resolveBranch(session = {}, context = {}) {
|
|
121
|
+
return firstString(
|
|
122
|
+
context.branch,
|
|
123
|
+
session.branch,
|
|
124
|
+
session.lane && session.lane.branch,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function resolveWorktreePath(session = {}, context = {}) {
|
|
129
|
+
return firstString(
|
|
130
|
+
context.worktreePath,
|
|
131
|
+
context.path,
|
|
132
|
+
session.worktreePath,
|
|
133
|
+
session.worktree && session.worktree.path,
|
|
134
|
+
session.path,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function resolveWorktreeExists(session = {}, context = {}, worktreePath = '') {
|
|
139
|
+
if (typeof context.worktreeExists === 'boolean') return context.worktreeExists;
|
|
140
|
+
if (typeof session.worktreeExists === 'boolean') return session.worktreeExists;
|
|
141
|
+
return worktreePath.length > 0;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function disabledReason(item, context) {
|
|
145
|
+
if (item.needsSession && !context.selected) return 'No pane selected';
|
|
146
|
+
|
|
147
|
+
const reasons = [];
|
|
148
|
+
if (item.needsWorktree && !context.worktreeExists) reasons.push('Worktree missing');
|
|
149
|
+
if (item.needsBranch && !context.branch) reasons.push('Branch missing');
|
|
150
|
+
return reasons.join('; ');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function createPaneMenuItems(context) {
|
|
154
|
+
return PANE_MENU_ITEMS.map((item) => {
|
|
155
|
+
const reason = disabledReason(item, context);
|
|
156
|
+
return {
|
|
157
|
+
id: item.id,
|
|
158
|
+
label: item.label,
|
|
159
|
+
hotkey: item.hotkey || '',
|
|
160
|
+
shortcut: item.hotkey || '',
|
|
161
|
+
enabled: reason.length === 0,
|
|
162
|
+
danger: Boolean(item.danger),
|
|
163
|
+
reason,
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function createPaneMenuState(options = {}) {
|
|
169
|
+
const session = selectedSession(options);
|
|
170
|
+
const selected = Boolean(session) && options.selected !== false;
|
|
171
|
+
const source = session || {};
|
|
172
|
+
const branch = selected ? resolveBranch(source, options) : '';
|
|
173
|
+
const worktreePath = selected ? resolveWorktreePath(source, options) : '';
|
|
174
|
+
const context = {
|
|
175
|
+
selected,
|
|
176
|
+
branch,
|
|
177
|
+
worktreePath,
|
|
178
|
+
worktreeExists: selected && resolveWorktreeExists(source, options, worktreePath),
|
|
179
|
+
};
|
|
180
|
+
const items = Array.isArray(options.items) && options.items.length > 0
|
|
181
|
+
? options.items.map((item) => ({ ...item }))
|
|
182
|
+
: createPaneMenuItems(context);
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
id: 'pane-menu',
|
|
186
|
+
title: paneMenuTitle(firstString(options.title, selectedPaneName(source, options))),
|
|
187
|
+
selectedIndex: clampIndex(options.selectedIndex, items.length),
|
|
188
|
+
hotkeyPriority: Object.prototype.hasOwnProperty.call(options, 'hotkeyPriority')
|
|
189
|
+
? Boolean(options.hotkeyPriority)
|
|
190
|
+
: false,
|
|
191
|
+
selectedActionId: firstString(options.selectedActionId),
|
|
192
|
+
canceled: Boolean(options.canceled),
|
|
193
|
+
message: firstString(options.message),
|
|
194
|
+
branch,
|
|
195
|
+
worktreePath,
|
|
196
|
+
worktreeExists: context.worktreeExists,
|
|
197
|
+
items,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function normalizePaneMenuKey(value) {
|
|
202
|
+
if (!value) return '';
|
|
203
|
+
if (value && typeof value === 'object' && !Buffer.isBuffer(value)) {
|
|
204
|
+
if ((value.meta || value.alt) && value.shift && String(value.name || value.key || '').toLowerCase() === 'm') {
|
|
205
|
+
return 'alt-shift-m';
|
|
206
|
+
}
|
|
207
|
+
return normalizePaneMenuKey(value.name || value.sequence || value.key || '');
|
|
208
|
+
}
|
|
209
|
+
const raw = Buffer.isBuffer(value) ? value.toString('utf8') : String(value);
|
|
210
|
+
if (raw === '\u0003') return 'ctrl-c';
|
|
211
|
+
if (raw === '\u001bM') return 'alt-shift-m';
|
|
212
|
+
if (raw === '\u001b') return 'escape';
|
|
213
|
+
if (raw === '\r' || raw === '\n') return 'enter';
|
|
214
|
+
if (raw === '\u001b[A') return 'up';
|
|
215
|
+
if (raw === '\u001b[B') return 'down';
|
|
216
|
+
if (raw === '\u001bM' || raw === '\u001bm') return 'alt-shift-m';
|
|
217
|
+
if (raw === 'ArrowUp') return 'up';
|
|
218
|
+
if (raw === 'ArrowDown') return 'down';
|
|
219
|
+
if (raw.length === 1) return raw;
|
|
220
|
+
return raw.toLowerCase();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function itemForHotkey(items, key) {
|
|
224
|
+
return items.find((item) => item.hotkey === key || item.shortcut === key) || null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function selectedItem(state) {
|
|
228
|
+
const items = Array.isArray(state.items) ? state.items : [];
|
|
229
|
+
return items[clampIndex(state.selectedIndex, items.length)] || null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function selectItem(state, item) {
|
|
233
|
+
if (!item) {
|
|
234
|
+
return {
|
|
235
|
+
state: { ...state, message: 'No pane menu action selected.' },
|
|
236
|
+
action: 'render',
|
|
237
|
+
actionId: '',
|
|
238
|
+
item: null,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
if (item.enabled === false) {
|
|
242
|
+
return {
|
|
243
|
+
state: {
|
|
244
|
+
...state,
|
|
245
|
+
selectedActionId: '',
|
|
246
|
+
message: item.reason || 'Action unavailable.',
|
|
247
|
+
},
|
|
248
|
+
action: 'render',
|
|
249
|
+
actionId: '',
|
|
250
|
+
item,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
state: {
|
|
255
|
+
...state,
|
|
256
|
+
selectedActionId: item.id,
|
|
257
|
+
message: '',
|
|
258
|
+
},
|
|
259
|
+
action: 'select',
|
|
260
|
+
actionId: item.id,
|
|
261
|
+
item,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function applyPaneMenuKey(state = {}, rawKey) {
|
|
266
|
+
const current = createPaneMenuState(state);
|
|
267
|
+
const key = normalizePaneMenuKey(rawKey);
|
|
268
|
+
const items = current.items;
|
|
269
|
+
|
|
270
|
+
if (key === 'escape' || key === 'esc' || key === 'ctrl-c') {
|
|
271
|
+
return {
|
|
272
|
+
state: { ...current, canceled: true, message: '' },
|
|
273
|
+
action: 'cancel',
|
|
274
|
+
actionId: '',
|
|
275
|
+
item: null,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (key === 'enter') return selectItem(current, selectedItem(current));
|
|
280
|
+
|
|
281
|
+
if (key === 'up' || key === 'k') {
|
|
282
|
+
return {
|
|
283
|
+
state: { ...current, selectedIndex: wrapIndex(current.selectedIndex - 1, items.length), message: '' },
|
|
284
|
+
action: 'render',
|
|
285
|
+
actionId: '',
|
|
286
|
+
item: null,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (key === 'down' || (key === 'j' && !current.hotkeyPriority)) {
|
|
291
|
+
return {
|
|
292
|
+
state: { ...current, selectedIndex: wrapIndex(current.selectedIndex + 1, items.length), message: '' },
|
|
293
|
+
action: 'render',
|
|
294
|
+
actionId: '',
|
|
295
|
+
item: null,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const hotkeyItem = itemForHotkey(items, key);
|
|
300
|
+
if (hotkeyItem) return selectItem(current, hotkeyItem);
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
state: current,
|
|
304
|
+
action: 'render',
|
|
305
|
+
actionId: '',
|
|
306
|
+
item: null,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function truncate(value, width) {
|
|
311
|
+
const text = String(value || '');
|
|
312
|
+
if (text.length <= width) return text;
|
|
313
|
+
if (width <= 1) return text.slice(0, width);
|
|
314
|
+
return `${text.slice(0, width - 1)}…`;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function padRight(value, width) {
|
|
318
|
+
const text = String(value || '');
|
|
319
|
+
return text + ' '.repeat(Math.max(0, width - text.length));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function renderItem(item, index, state, width, box) {
|
|
323
|
+
const marker = index === state.selectedIndex ? box.selected : ' ';
|
|
324
|
+
const hotkey = item.hotkey ? `[${item.hotkey}]` : '';
|
|
325
|
+
const suffix = item.enabled === false ? ` - ${item.reason || 'Unavailable'}` : '';
|
|
326
|
+
const labelWidth = Math.max(1, width - marker.length - hotkey.length - suffix.length - 3);
|
|
327
|
+
const label = truncate(item.label, labelWidth);
|
|
328
|
+
const left = `${marker} ${label}`;
|
|
329
|
+
const gap = ' '.repeat(Math.max(1, width - left.length - hotkey.length - suffix.length));
|
|
330
|
+
return truncate(`${left}${gap}${hotkey}${suffix}`, width);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function framedRow(text, width, box) {
|
|
334
|
+
return `${box.vertical} ${padRight(truncate(text, width), width)} ${box.vertical}`;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function horizontalRow(width, box, kind) {
|
|
338
|
+
const left = kind === 'middle' ? box.middleLeft : kind === 'bottom' ? box.bottomLeft : box.topLeft;
|
|
339
|
+
const right = kind === 'middle' ? box.middleRight : kind === 'bottom' ? box.bottomRight : box.topRight;
|
|
340
|
+
return `${left}${box.horizontal.repeat(width + 2)}${right}`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function renderPaneMenu(state = {}, options = {}) {
|
|
344
|
+
const current = createPaneMenuState(state);
|
|
345
|
+
const box = options.unicode === false || options.ascii === true ? BOX_CHARS.ascii : BOX_CHARS.unicode;
|
|
346
|
+
const contentRows = [
|
|
347
|
+
current.title,
|
|
348
|
+
...current.items.map((item, index) => renderItem(item, index, current, 1_000, box)),
|
|
349
|
+
...(current.message ? [`status: ${current.message}`] : []),
|
|
350
|
+
PANE_MENU_FOOTER,
|
|
351
|
+
];
|
|
352
|
+
const naturalWidth = Math.max(...contentRows.map((row) => String(row).length));
|
|
353
|
+
const requestedWidth = Number.isFinite(Number(options.width)) ? Math.floor(Number(options.width)) : naturalWidth + 4;
|
|
354
|
+
const width = Math.max(28, Math.min(72, requestedWidth) - 4);
|
|
355
|
+
const lines = [
|
|
356
|
+
horizontalRow(width, box, 'top'),
|
|
357
|
+
framedRow(current.title, width, box),
|
|
358
|
+
horizontalRow(width, box, 'middle'),
|
|
359
|
+
...current.items.map((item, index) => framedRow(renderItem(item, index, current, width, box), width, box)),
|
|
360
|
+
horizontalRow(width, box, 'middle'),
|
|
361
|
+
...(current.message ? [framedRow(`status: ${current.message}`, width, box), horizontalRow(width, box, 'middle')] : []),
|
|
362
|
+
framedRow(PANE_MENU_FOOTER, width, box),
|
|
363
|
+
horizontalRow(width, box, 'bottom'),
|
|
364
|
+
];
|
|
365
|
+
return `${lines.join('\n')}\n`;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function buildLaneMenu(session, context = {}) {
|
|
369
|
+
return createPaneMenuState({ ...context, session });
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function renderLaneMenu(menu, options = {}) {
|
|
373
|
+
return renderPaneMenu(menu, options);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
module.exports = {
|
|
377
|
+
PANE_MENU_ACTIONS,
|
|
378
|
+
PANE_MENU_ACTION_IDS,
|
|
379
|
+
PANE_MENU_FOOTER,
|
|
380
|
+
PANE_MENU_ITEMS,
|
|
381
|
+
applyPaneMenuKey,
|
|
382
|
+
buildLaneMenu,
|
|
383
|
+
createPaneMenuState,
|
|
384
|
+
normalizePaneMenuKey,
|
|
385
|
+
renderLaneMenu,
|
|
386
|
+
renderPaneMenu,
|
|
387
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
const DEFAULT_DEPTH = 2;
|
|
8
|
+
const DEFAULT_LIMIT = 50;
|
|
9
|
+
const SKIP_NAMES = new Set([
|
|
10
|
+
'.git',
|
|
11
|
+
'.svn',
|
|
12
|
+
'.hg',
|
|
13
|
+
'node_modules',
|
|
14
|
+
'.npm-cache',
|
|
15
|
+
'.npm-logs',
|
|
16
|
+
'.omc',
|
|
17
|
+
'.omx',
|
|
18
|
+
'dist',
|
|
19
|
+
'build',
|
|
20
|
+
'target',
|
|
21
|
+
'.cache',
|
|
22
|
+
'.next',
|
|
23
|
+
'.venv',
|
|
24
|
+
'venv',
|
|
25
|
+
'__pycache__',
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
function text(value, fallback = '') {
|
|
29
|
+
if (typeof value === 'string') return value.trim() || fallback;
|
|
30
|
+
if (value === null || value === undefined) return fallback;
|
|
31
|
+
return String(value).trim() || fallback;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function expandHome(input) {
|
|
35
|
+
const value = text(input);
|
|
36
|
+
if (!value) return '';
|
|
37
|
+
if (value === '~') return os.homedir();
|
|
38
|
+
if (value.startsWith('~/')) return path.join(os.homedir(), value.slice(2));
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function uniqueRoots(values) {
|
|
43
|
+
const seen = new Set();
|
|
44
|
+
const result = [];
|
|
45
|
+
for (const value of values) {
|
|
46
|
+
const expanded = expandHome(value);
|
|
47
|
+
if (!expanded) continue;
|
|
48
|
+
const resolved = path.resolve(expanded);
|
|
49
|
+
if (seen.has(resolved)) continue;
|
|
50
|
+
seen.add(resolved);
|
|
51
|
+
result.push(resolved);
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function defaultRoots(options = {}) {
|
|
57
|
+
const env = options.env || process.env;
|
|
58
|
+
const explicit = text(env.GUARDEX_PROJECT_ROOTS);
|
|
59
|
+
if (explicit) {
|
|
60
|
+
return uniqueRoots(explicit.split(path.delimiter).map((entry) => entry.trim()).filter(Boolean));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const seeds = [];
|
|
64
|
+
const repoRoot = text(options.repoRoot);
|
|
65
|
+
if (repoRoot) {
|
|
66
|
+
seeds.push(path.dirname(repoRoot));
|
|
67
|
+
}
|
|
68
|
+
seeds.push(path.join(os.homedir(), 'Documents'));
|
|
69
|
+
seeds.push(path.join(os.homedir(), 'code'));
|
|
70
|
+
seeds.push(path.join(os.homedir(), 'src'));
|
|
71
|
+
seeds.push(path.join(os.homedir(), 'projects'));
|
|
72
|
+
return uniqueRoots(seeds);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isGitRepo(dir, fsImpl) {
|
|
76
|
+
try {
|
|
77
|
+
const gitEntry = path.join(dir, '.git');
|
|
78
|
+
const stat = fsImpl.statSync(gitEntry, { throwIfNoEntry: false });
|
|
79
|
+
if (!stat) return false;
|
|
80
|
+
return stat.isDirectory() || stat.isFile();
|
|
81
|
+
} catch (_error) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function listDirectories(dir, fsImpl) {
|
|
87
|
+
try {
|
|
88
|
+
const entries = fsImpl.readdirSync(dir, { withFileTypes: true });
|
|
89
|
+
return entries
|
|
90
|
+
.filter((entry) => entry.isDirectory())
|
|
91
|
+
.map((entry) => ({ name: entry.name, fullPath: path.join(dir, entry.name) }));
|
|
92
|
+
} catch (_error) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function projectName(repoPath, root) {
|
|
98
|
+
const rel = path.relative(root, repoPath);
|
|
99
|
+
return rel && !rel.startsWith('..') ? rel : path.basename(repoPath);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function walkRoot(root, options = {}) {
|
|
103
|
+
const depth = Number.isFinite(options.depth) && options.depth >= 0 ? options.depth : DEFAULT_DEPTH;
|
|
104
|
+
const limit = Number.isFinite(options.limit) && options.limit > 0 ? options.limit : DEFAULT_LIMIT;
|
|
105
|
+
const fsImpl = options.fs || fs;
|
|
106
|
+
|
|
107
|
+
if (!isAccessibleDirectory(root, fsImpl)) return [];
|
|
108
|
+
|
|
109
|
+
const results = [];
|
|
110
|
+
const stack = [{ dir: root, level: 0 }];
|
|
111
|
+
|
|
112
|
+
while (stack.length > 0 && results.length < limit) {
|
|
113
|
+
const { dir, level } = stack.pop();
|
|
114
|
+
if (SKIP_NAMES.has(path.basename(dir))) continue;
|
|
115
|
+
|
|
116
|
+
if (isGitRepo(dir, fsImpl)) {
|
|
117
|
+
results.push({
|
|
118
|
+
path: dir,
|
|
119
|
+
name: projectName(dir, root),
|
|
120
|
+
root,
|
|
121
|
+
});
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (level >= depth) continue;
|
|
126
|
+
const children = listDirectories(dir, fsImpl).reverse();
|
|
127
|
+
for (const child of children) {
|
|
128
|
+
if (SKIP_NAMES.has(child.name)) continue;
|
|
129
|
+
stack.push({ dir: child.fullPath, level: level + 1 });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return results;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function isAccessibleDirectory(dir, fsImpl) {
|
|
137
|
+
try {
|
|
138
|
+
const stat = fsImpl.statSync(dir, { throwIfNoEntry: false });
|
|
139
|
+
return Boolean(stat && stat.isDirectory());
|
|
140
|
+
} catch (_error) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function findProjects(options = {}) {
|
|
146
|
+
const fsImpl = options.fs || fs;
|
|
147
|
+
const roots = options.roots && options.roots.length > 0
|
|
148
|
+
? uniqueRoots(options.roots)
|
|
149
|
+
: defaultRoots(options);
|
|
150
|
+
const limit = Number.isFinite(options.limit) && options.limit > 0 ? options.limit : DEFAULT_LIMIT;
|
|
151
|
+
const seen = new Set();
|
|
152
|
+
const results = [];
|
|
153
|
+
|
|
154
|
+
for (const root of roots) {
|
|
155
|
+
const found = walkRoot(root, { ...options, fs: fsImpl, limit: limit - results.length });
|
|
156
|
+
for (const project of found) {
|
|
157
|
+
if (seen.has(project.path)) continue;
|
|
158
|
+
seen.add(project.path);
|
|
159
|
+
results.push(project);
|
|
160
|
+
if (results.length >= limit) break;
|
|
161
|
+
}
|
|
162
|
+
if (results.length >= limit) break;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
results.sort((a, b) => a.name.localeCompare(b.name) || a.path.localeCompare(b.path));
|
|
166
|
+
return { roots, projects: results };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = {
|
|
170
|
+
DEFAULT_DEPTH,
|
|
171
|
+
DEFAULT_LIMIT,
|
|
172
|
+
SKIP_NAMES,
|
|
173
|
+
defaultRoots,
|
|
174
|
+
expandHome,
|
|
175
|
+
findProjects,
|
|
176
|
+
uniqueRoots,
|
|
177
|
+
walkRoot,
|
|
178
|
+
};
|