letmecook 0.0.13 → 0.0.14
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/package.json +1 -1
- package/src/flows/add-repos.ts +52 -16
- package/src/flows/edit-session.ts +104 -46
- package/src/flows/new-session.ts +81 -76
- package/src/ui/add-repos.ts +184 -180
- package/src/ui/common/command-runner.ts +249 -0
- package/src/ui/common/footer.ts +105 -0
- package/src/ui/common/keyboard.ts +95 -0
- package/src/ui/confirm-delete.ts +10 -9
- package/src/ui/conflict.ts +10 -1
- package/src/ui/exit.ts +79 -47
- package/src/ui/list.ts +23 -14
- package/src/ui/main-menu.ts +25 -46
- package/src/ui/reclone-prompt.ts +11 -9
- package/src/ui/renderer.ts +16 -4
- package/src/ui/session-details.ts +43 -18
- package/src/ui/session-settings.ts +91 -71
- package/src/ui/skills.ts +44 -100
|
@@ -2,6 +2,8 @@ import { type CliRenderer, TextRenderable, InputRenderable, type KeyEvent } from
|
|
|
2
2
|
import { createBaseLayout, clearLayout } from "./renderer";
|
|
3
3
|
import type { Session, RepoSpec } from "../types";
|
|
4
4
|
import { formatRepoString } from "./common/repo-formatter";
|
|
5
|
+
import { showFooter, hideFooter } from "./common/footer";
|
|
6
|
+
import { isEnter, isEscape, isTab, isArrowUp, isArrowDown } from "./common/keyboard";
|
|
5
7
|
|
|
6
8
|
export interface SessionSettingsResult {
|
|
7
9
|
action: "saved" | "add-repos" | "add-skills" | "cancel";
|
|
@@ -140,21 +142,6 @@ export function showSessionSettings(
|
|
|
140
142
|
skillsList.fg = "#94a3b8";
|
|
141
143
|
};
|
|
142
144
|
|
|
143
|
-
const updateInstructions = () => {
|
|
144
|
-
if (selectedTarget === "goal") {
|
|
145
|
-
instructions.content = "\n[Enter] Edit goal [s] Save [Esc] Cancel";
|
|
146
|
-
} else if (selectedTarget === "repo") {
|
|
147
|
-
instructions.content =
|
|
148
|
-
"\n[r] Toggle read-only [l] Toggle latest [a] Add repos [k] Add skills [s] Save [Esc] Cancel";
|
|
149
|
-
} else if (selectedTarget === "skill") {
|
|
150
|
-
instructions.content =
|
|
151
|
-
"\n[x] Remove skill [a] Add repos [k] Add skills [s] Save [Esc] Cancel";
|
|
152
|
-
} else {
|
|
153
|
-
instructions.content =
|
|
154
|
-
"\n[r] Toggle read-only [l] Toggle latest [a] Add repos [k] Add skills [s] Save [Esc] Cancel";
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
|
|
158
145
|
const updateGoalLabel = () => {
|
|
159
146
|
if (selectedTarget === "goal") {
|
|
160
147
|
goalLabel.content = "▶ Goal:";
|
|
@@ -191,77 +178,120 @@ export function showSessionSettings(
|
|
|
191
178
|
}
|
|
192
179
|
};
|
|
193
180
|
|
|
181
|
+
const switchToNextSection = () => {
|
|
182
|
+
if (selectedTarget === "goal") {
|
|
183
|
+
if (updatedRepos.length > 0) {
|
|
184
|
+
selectedTarget = "repo";
|
|
185
|
+
selectedRepoIndex = 0;
|
|
186
|
+
} else if (updatedSkills.length > 0) {
|
|
187
|
+
selectedTarget = "skill";
|
|
188
|
+
selectedSkillIndex = 0;
|
|
189
|
+
}
|
|
190
|
+
} else if (selectedTarget === "repo") {
|
|
191
|
+
if (updatedSkills.length > 0) {
|
|
192
|
+
selectedTarget = "skill";
|
|
193
|
+
selectedSkillIndex = 0;
|
|
194
|
+
} else {
|
|
195
|
+
selectedTarget = "goal";
|
|
196
|
+
}
|
|
197
|
+
} else if (selectedTarget === "skill") {
|
|
198
|
+
selectedTarget = "goal";
|
|
199
|
+
}
|
|
200
|
+
updateGoalLabel();
|
|
201
|
+
updateReposLabel();
|
|
202
|
+
updateSkillsLabel();
|
|
203
|
+
updateFooter();
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const updateFooter = () => {
|
|
207
|
+
const customActions: string[] = [];
|
|
208
|
+
if (selectedTarget === "goal") {
|
|
209
|
+
customActions.push("Enter Edit");
|
|
210
|
+
} else if (selectedTarget === "repo") {
|
|
211
|
+
customActions.push("r Toggle RO", "l Toggle Latest", "a Add repos");
|
|
212
|
+
} else if (selectedTarget === "skill") {
|
|
213
|
+
customActions.push("x Remove", "a Add repos");
|
|
214
|
+
}
|
|
215
|
+
customActions.push("+ Add skills", "Enter Save");
|
|
216
|
+
|
|
217
|
+
hideFooter(renderer);
|
|
218
|
+
showFooter(renderer, content, {
|
|
219
|
+
navigate: true,
|
|
220
|
+
select: false,
|
|
221
|
+
back: true,
|
|
222
|
+
custom: ["Tab Switch", ...customActions],
|
|
223
|
+
});
|
|
224
|
+
};
|
|
225
|
+
|
|
194
226
|
const handleKeypress = (key: KeyEvent) => {
|
|
195
227
|
if (mode === "goal") {
|
|
196
|
-
if (key
|
|
228
|
+
if (isEnter(key)) {
|
|
197
229
|
updatedGoal = goalInput.value.trim();
|
|
198
230
|
updateMode("repos");
|
|
199
|
-
|
|
231
|
+
updateFooter();
|
|
232
|
+
} else if (isEscape(key)) {
|
|
200
233
|
goalInput.value = updatedGoal;
|
|
201
234
|
updateMode("repos");
|
|
235
|
+
updateFooter();
|
|
202
236
|
}
|
|
203
237
|
return;
|
|
204
238
|
}
|
|
205
239
|
|
|
206
|
-
if (key
|
|
240
|
+
if (isEscape(key)) {
|
|
207
241
|
cleanup();
|
|
208
242
|
resolve({ action: "cancel", session });
|
|
209
243
|
return;
|
|
210
244
|
}
|
|
211
245
|
|
|
212
|
-
|
|
246
|
+
// Tab to switch sections
|
|
247
|
+
if (isTab(key)) {
|
|
248
|
+
switchToNextSection();
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Arrow keys for navigation within sections
|
|
253
|
+
if (isArrowUp(key)) {
|
|
213
254
|
if (selectedTarget === "skill" && selectedSkillIndex > 0) {
|
|
214
255
|
selectedSkillIndex = Math.max(0, selectedSkillIndex - 1);
|
|
215
256
|
} else if (selectedTarget === "repo" && selectedRepoIndex > 0) {
|
|
216
257
|
selectedRepoIndex = Math.max(0, selectedRepoIndex - 1);
|
|
217
|
-
} else if (selectedTarget === "repo") {
|
|
218
|
-
selectedTarget = "goal";
|
|
219
|
-
} else if (selectedTarget === "goal" && updatedSkills.length > 0) {
|
|
220
|
-
selectedTarget = "skill";
|
|
221
|
-
selectedSkillIndex = updatedSkills.length - 1;
|
|
222
258
|
}
|
|
223
|
-
updateGoalLabel();
|
|
224
|
-
updateReposLabel();
|
|
225
|
-
updateSkillsLabel();
|
|
226
|
-
updateInstructions();
|
|
227
259
|
updateReposList();
|
|
228
260
|
updateSkillsList();
|
|
229
261
|
return;
|
|
230
262
|
}
|
|
231
263
|
|
|
232
|
-
if (key
|
|
233
|
-
if (selectedTarget === "
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
selectedRepoIndex = 0;
|
|
237
|
-
} else if (updatedSkills.length > 0) {
|
|
238
|
-
selectedTarget = "skill";
|
|
239
|
-
selectedSkillIndex = 0;
|
|
240
|
-
}
|
|
241
|
-
} else if (selectedTarget === "repo") {
|
|
242
|
-
if (selectedRepoIndex < updatedRepos.length - 1) {
|
|
243
|
-
selectedRepoIndex = Math.min(updatedRepos.length - 1, selectedRepoIndex + 1);
|
|
244
|
-
} else if (updatedSkills.length > 0) {
|
|
245
|
-
selectedTarget = "skill";
|
|
246
|
-
selectedSkillIndex = 0;
|
|
247
|
-
}
|
|
248
|
-
} else if (selectedTarget === "skill") {
|
|
264
|
+
if (isArrowDown(key)) {
|
|
265
|
+
if (selectedTarget === "repo" && selectedRepoIndex < updatedRepos.length - 1) {
|
|
266
|
+
selectedRepoIndex = Math.min(updatedRepos.length - 1, selectedRepoIndex + 1);
|
|
267
|
+
} else if (selectedTarget === "skill" && selectedSkillIndex < updatedSkills.length - 1) {
|
|
249
268
|
selectedSkillIndex = Math.min(updatedSkills.length - 1, selectedSkillIndex + 1);
|
|
250
269
|
}
|
|
251
|
-
updateGoalLabel();
|
|
252
|
-
updateReposLabel();
|
|
253
|
-
updateSkillsLabel();
|
|
254
|
-
updateInstructions();
|
|
255
270
|
updateReposList();
|
|
256
271
|
updateSkillsList();
|
|
257
272
|
return;
|
|
258
273
|
}
|
|
259
274
|
|
|
260
|
-
|
|
261
|
-
|
|
275
|
+
// Enter to edit goal or save
|
|
276
|
+
if (isEnter(key)) {
|
|
277
|
+
if (selectedTarget === "goal") {
|
|
278
|
+
updateMode("goal");
|
|
279
|
+
} else {
|
|
280
|
+
// Save changes
|
|
281
|
+
updatedGoal = goalInput.value.trim();
|
|
282
|
+
const updatedSession: Session = {
|
|
283
|
+
...session,
|
|
284
|
+
repos: updatedRepos,
|
|
285
|
+
skills: updatedSkills.length > 0 ? updatedSkills : undefined,
|
|
286
|
+
goal: updatedGoal || undefined,
|
|
287
|
+
};
|
|
288
|
+
cleanup();
|
|
289
|
+
resolve({ action: "saved", session: updatedSession });
|
|
290
|
+
}
|
|
262
291
|
return;
|
|
263
292
|
}
|
|
264
293
|
|
|
294
|
+
// Hotkeys
|
|
265
295
|
if (key.name === "a") {
|
|
266
296
|
updatedGoal = goalInput.value.trim();
|
|
267
297
|
const updatedSession: Session = {
|
|
@@ -275,7 +305,8 @@ export function showSessionSettings(
|
|
|
275
305
|
return;
|
|
276
306
|
}
|
|
277
307
|
|
|
278
|
-
if (key.name === "
|
|
308
|
+
if (key.name === "+" || key.name === "=") {
|
|
309
|
+
// + or = for add skills
|
|
279
310
|
updatedGoal = goalInput.value.trim();
|
|
280
311
|
const updatedSession: Session = {
|
|
281
312
|
...session,
|
|
@@ -297,18 +328,18 @@ export function showSessionSettings(
|
|
|
297
328
|
}
|
|
298
329
|
if (updatedSkills.length === 0) {
|
|
299
330
|
selectedTarget = updatedRepos.length > 0 ? "repo" : "goal";
|
|
331
|
+
updateGoalLabel();
|
|
332
|
+
updateReposLabel();
|
|
333
|
+
updateSkillsLabel();
|
|
334
|
+
updateFooter();
|
|
300
335
|
}
|
|
301
|
-
updateGoalLabel();
|
|
302
|
-
updateReposLabel();
|
|
303
|
-
updateSkillsLabel();
|
|
304
|
-
updateInstructions();
|
|
305
336
|
updateReposList();
|
|
306
337
|
updateSkillsList();
|
|
307
338
|
}
|
|
308
339
|
return;
|
|
309
340
|
}
|
|
310
341
|
|
|
311
|
-
if (key.name === "r") {
|
|
342
|
+
if (key.name === "r" && selectedTarget === "repo") {
|
|
312
343
|
const repo = updatedRepos[selectedRepoIndex];
|
|
313
344
|
if (repo) {
|
|
314
345
|
repo.readOnly = !repo.readOnly;
|
|
@@ -320,7 +351,7 @@ export function showSessionSettings(
|
|
|
320
351
|
return;
|
|
321
352
|
}
|
|
322
353
|
|
|
323
|
-
if (key.name === "l") {
|
|
354
|
+
if (key.name === "l" && selectedTarget === "repo") {
|
|
324
355
|
const repo = updatedRepos[selectedRepoIndex];
|
|
325
356
|
if (repo) {
|
|
326
357
|
repo.latest = !repo.latest;
|
|
@@ -331,33 +362,22 @@ export function showSessionSettings(
|
|
|
331
362
|
}
|
|
332
363
|
return;
|
|
333
364
|
}
|
|
334
|
-
|
|
335
|
-
if (key.name === "s") {
|
|
336
|
-
updatedGoal = goalInput.value.trim();
|
|
337
|
-
const updatedSession: Session = {
|
|
338
|
-
...session,
|
|
339
|
-
repos: updatedRepos,
|
|
340
|
-
skills: updatedSkills.length > 0 ? updatedSkills : undefined,
|
|
341
|
-
goal: updatedGoal || undefined,
|
|
342
|
-
};
|
|
343
|
-
cleanup();
|
|
344
|
-
resolve({ action: "saved", session: updatedSession });
|
|
345
|
-
}
|
|
346
365
|
};
|
|
347
366
|
|
|
348
367
|
const cleanup = () => {
|
|
349
368
|
renderer.keyInput.off("keypress", handleKeypress);
|
|
350
369
|
goalInput.blur();
|
|
370
|
+
hideFooter(renderer);
|
|
351
371
|
clearLayout(renderer);
|
|
352
372
|
};
|
|
353
373
|
|
|
354
374
|
updateGoalLabel();
|
|
355
375
|
updateReposLabel();
|
|
356
376
|
updateSkillsLabel();
|
|
357
|
-
updateInstructions();
|
|
358
377
|
updateReposList();
|
|
359
378
|
updateSkillsList();
|
|
360
379
|
updateMode("repos");
|
|
380
|
+
updateFooter();
|
|
361
381
|
renderer.keyInput.on("keypress", handleKeypress);
|
|
362
382
|
});
|
|
363
383
|
}
|
package/src/ui/skills.ts
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type CliRenderer,
|
|
3
|
-
TextRenderable,
|
|
4
|
-
InputRenderable,
|
|
5
|
-
SelectRenderable,
|
|
6
|
-
SelectRenderableEvents,
|
|
7
|
-
type KeyEvent,
|
|
8
|
-
} from "@opentui/core";
|
|
1
|
+
import { type CliRenderer, TextRenderable, InputRenderable, type KeyEvent } from "@opentui/core";
|
|
9
2
|
import { createBaseLayout, clearLayout } from "./renderer";
|
|
3
|
+
import { showFooter, hideFooter } from "./common/footer";
|
|
4
|
+
import { isEnter, isEscape } from "./common/keyboard";
|
|
10
5
|
|
|
11
6
|
export interface SkillsPromptResult {
|
|
12
7
|
skills: string[];
|
|
@@ -44,93 +39,56 @@ export async function showSkillsPrompt(
|
|
|
44
39
|
});
|
|
45
40
|
content.add(input);
|
|
46
41
|
|
|
47
|
-
const
|
|
48
|
-
id: "
|
|
49
|
-
content: "",
|
|
50
|
-
fg: "#
|
|
42
|
+
const skillsLabel = new TextRenderable(renderer, {
|
|
43
|
+
id: "skills-label",
|
|
44
|
+
content: "\nAdded skills:",
|
|
45
|
+
fg: "#e2e8f0",
|
|
51
46
|
marginTop: 1,
|
|
52
47
|
marginBottom: 0,
|
|
53
48
|
});
|
|
54
|
-
content.add(
|
|
49
|
+
content.add(skillsLabel);
|
|
55
50
|
|
|
56
|
-
const
|
|
57
|
-
id: "
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
options: [
|
|
61
|
-
{ name: "Add another skill", description: "", value: "yes" },
|
|
62
|
-
{ name: "Done adding skills", description: "", value: "no" },
|
|
63
|
-
],
|
|
64
|
-
showDescription: false,
|
|
65
|
-
backgroundColor: "transparent",
|
|
66
|
-
focusedBackgroundColor: "transparent",
|
|
67
|
-
selectedBackgroundColor: "#334155",
|
|
68
|
-
textColor: "#e2e8f0",
|
|
69
|
-
selectedTextColor: "#38bdf8",
|
|
70
|
-
descriptionColor: "#64748b",
|
|
71
|
-
selectedDescriptionColor: "#94a3b8",
|
|
51
|
+
const existingSkillsText = new TextRenderable(renderer, {
|
|
52
|
+
id: "existing-skills",
|
|
53
|
+
content: "(none)",
|
|
54
|
+
fg: "#64748b",
|
|
72
55
|
marginTop: 1,
|
|
56
|
+
marginBottom: 0,
|
|
73
57
|
});
|
|
74
|
-
content.add(
|
|
58
|
+
content.add(existingSkillsText);
|
|
75
59
|
|
|
76
|
-
const
|
|
77
|
-
id: "
|
|
78
|
-
content: "
|
|
79
|
-
fg: "#
|
|
80
|
-
marginTop:
|
|
60
|
+
const skillsCounter = new TextRenderable(renderer, {
|
|
61
|
+
id: "skills-counter",
|
|
62
|
+
content: "",
|
|
63
|
+
fg: "#94a3b8",
|
|
64
|
+
marginTop: 0,
|
|
81
65
|
});
|
|
82
|
-
content.add(
|
|
66
|
+
content.add(skillsCounter);
|
|
83
67
|
|
|
84
68
|
const skills: string[] = [];
|
|
85
|
-
let showingConfirmation = false;
|
|
86
69
|
|
|
87
70
|
const updateExistingSkills = () => {
|
|
88
71
|
if (skills.length === 0) {
|
|
89
|
-
existingSkillsText.content = "";
|
|
72
|
+
existingSkillsText.content = "(none)";
|
|
73
|
+
existingSkillsText.fg = "#64748b";
|
|
74
|
+
skillsCounter.content = "";
|
|
90
75
|
} else {
|
|
91
76
|
const text = skills.map((skill) => ` ✓ ${skill}`).join("\n");
|
|
92
|
-
existingSkillsText.content =
|
|
77
|
+
existingSkillsText.content = text;
|
|
78
|
+
existingSkillsText.fg = "#94a3b8";
|
|
79
|
+
skillsCounter.content = `Added: ${skills.length} ${skills.length === 1 ? "skill" : "skills"}`;
|
|
93
80
|
}
|
|
94
81
|
};
|
|
95
82
|
|
|
96
83
|
const cleanup = () => {
|
|
97
84
|
renderer.keyInput.off("keypress", handleKeypress);
|
|
98
85
|
input.blur();
|
|
99
|
-
|
|
100
|
-
confirmation.blur();
|
|
86
|
+
hideFooter(renderer);
|
|
101
87
|
clearLayout(renderer);
|
|
102
88
|
};
|
|
103
89
|
|
|
104
90
|
const handleKeypress = (key: KeyEvent) => {
|
|
105
|
-
if (
|
|
106
|
-
if (key.name === "return" || key.name === "enter") {
|
|
107
|
-
cleanup();
|
|
108
|
-
resolve({
|
|
109
|
-
skills,
|
|
110
|
-
cancelled: false,
|
|
111
|
-
});
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
if (key.name === "escape") {
|
|
115
|
-
cleanup();
|
|
116
|
-
resolve({
|
|
117
|
-
skills,
|
|
118
|
-
cancelled: false,
|
|
119
|
-
});
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
if (key.name === "d" && key.ctrl) {
|
|
123
|
-
cleanup();
|
|
124
|
-
resolve({
|
|
125
|
-
skills,
|
|
126
|
-
cancelled: false,
|
|
127
|
-
});
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (key.name === "escape") {
|
|
91
|
+
if (isEscape(key)) {
|
|
134
92
|
cleanup();
|
|
135
93
|
resolve({
|
|
136
94
|
skills: [],
|
|
@@ -139,47 +97,33 @@ export async function showSkillsPrompt(
|
|
|
139
97
|
return;
|
|
140
98
|
}
|
|
141
99
|
|
|
142
|
-
if (key
|
|
143
|
-
cleanup();
|
|
144
|
-
resolve({
|
|
145
|
-
skills,
|
|
146
|
-
cancelled: false,
|
|
147
|
-
});
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (key.name === "return" || key.name === "enter") {
|
|
100
|
+
if (isEnter(key)) {
|
|
152
101
|
const skill = input.value.trim();
|
|
153
102
|
if (skill) {
|
|
103
|
+
// Add skill
|
|
154
104
|
skills.push(skill);
|
|
155
105
|
updateExistingSkills();
|
|
156
106
|
input.value = "";
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
107
|
+
} else {
|
|
108
|
+
// Empty input = done (with or without skills)
|
|
109
|
+
cleanup();
|
|
110
|
+
resolve({
|
|
111
|
+
skills,
|
|
112
|
+
cancelled: false,
|
|
113
|
+
});
|
|
160
114
|
}
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
const handleConfirm = (_index: number, option: { value: string }) => {
|
|
165
|
-
if (option.value === "yes") {
|
|
166
|
-
showingConfirmation = false;
|
|
167
|
-
input.focus();
|
|
168
|
-
instructions.content = "\n[Enter] Add skill [Esc] Cancel";
|
|
169
115
|
return;
|
|
170
116
|
}
|
|
171
|
-
|
|
172
|
-
cleanup();
|
|
173
|
-
resolve({
|
|
174
|
-
skills,
|
|
175
|
-
cancelled: false,
|
|
176
|
-
});
|
|
177
117
|
};
|
|
178
118
|
|
|
179
119
|
input.focus();
|
|
180
|
-
|
|
181
|
-
|
|
120
|
+
showFooter(renderer, content, {
|
|
121
|
+
navigate: false,
|
|
122
|
+
select: true,
|
|
123
|
+
back: true,
|
|
124
|
+
custom: ["Enter Add/Done"],
|
|
125
|
+
});
|
|
182
126
|
renderer.keyInput.on("keypress", handleKeypress);
|
|
183
|
-
|
|
127
|
+
updateExistingSkills();
|
|
184
128
|
});
|
|
185
129
|
}
|