letmecook 0.0.21 → 0.0.23
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/index.ts +90 -256
- package/package.json +7 -2
- package/src/agents-md.ts +12 -15
- package/src/chat-logger.ts +220 -0
- package/src/chat-mode.ts +465 -0
- package/src/cli-mode.ts +366 -0
- package/src/config-builder.ts +147 -0
- package/src/env.ts +76 -0
- package/src/flows/add-repos.ts +51 -115
- package/src/flows/chat-to-config.ts +373 -0
- package/src/flows/new-session.ts +69 -145
- package/src/flows/resume-session.ts +33 -37
- package/src/git.ts +39 -77
- package/src/naming.ts +2 -2
- package/src/prompts/chat-prompt.ts +143 -0
- package/src/schemas.ts +82 -0
- package/src/splash.ts +199 -0
- package/src/tui-mode.ts +41 -0
- package/src/types.ts +16 -78
- package/src/ui/add-repos.ts +34 -26
- package/src/ui/agent-proposal.ts +13 -1
- package/src/ui/chat-confirmation.ts +151 -0
- package/src/ui/chat-with-sidebar.ts +524 -0
- package/src/ui/common/clipboard.ts +105 -0
- package/src/ui/common/keyboard.ts +7 -0
- package/src/ui/common/repo-formatter.ts +4 -4
- package/src/ui/cooking-indicator.ts +88 -0
- package/src/ui/main-menu.ts +8 -0
- package/src/ui/new-session.ts +2 -2
- package/src/ui/progress.ts +1 -1
- package/src/ui/renderer.ts +7 -14
- package/src/ui/session-settings.ts +4 -3
- package/src/validation.ts +152 -0
- package/src/reference-repo.ts +0 -288
package/src/ui/add-repos.ts
CHANGED
|
@@ -22,13 +22,14 @@ export async function showAddReposPrompt(renderer: CliRenderer): Promise<AddRepo
|
|
|
22
22
|
|
|
23
23
|
const repos: RepoSpec[] = [];
|
|
24
24
|
let currentInput = "";
|
|
25
|
-
let
|
|
25
|
+
let currentReadOnly = false;
|
|
26
|
+
let currentLatest = false;
|
|
26
27
|
let currentValidRepo: RepoSpec | null = null;
|
|
27
28
|
let selectedMatchIndex = -1; // -1 means no match selected, user is typing freely
|
|
28
29
|
let lastQuery = ""; // Track the query that generated current matches
|
|
29
30
|
let isNavigating = false; // Flag to prevent input handler from resetting when navigating
|
|
30
31
|
let isConfirming = false; // Flag for confirmation mode (showing checkboxes)
|
|
31
|
-
let confirmOptionIndex = 0; // 0 =
|
|
32
|
+
let confirmOptionIndex = 0; // 0 = read-only, 1 = confirm button
|
|
32
33
|
|
|
33
34
|
// Repository input
|
|
34
35
|
const repoLabel = new TextRenderable(renderer, {
|
|
@@ -78,13 +79,13 @@ export async function showAddReposPrompt(renderer: CliRenderer): Promise<AddRepo
|
|
|
78
79
|
});
|
|
79
80
|
content.add(detailsLabel);
|
|
80
81
|
|
|
81
|
-
const
|
|
82
|
-
id: "details-
|
|
82
|
+
const detailsLatest = new TextRenderable(renderer, {
|
|
83
|
+
id: "details-latest",
|
|
83
84
|
content: "",
|
|
84
85
|
fg: "#94a3b8",
|
|
85
86
|
marginTop: 0,
|
|
86
87
|
});
|
|
87
|
-
content.add(
|
|
88
|
+
content.add(detailsLatest);
|
|
88
89
|
|
|
89
90
|
const confirmButton = new TextRenderable(renderer, {
|
|
90
91
|
id: "confirm-button",
|
|
@@ -103,7 +104,8 @@ export async function showAddReposPrompt(renderer: CliRenderer): Promise<AddRepo
|
|
|
103
104
|
const repo = validateRepo(currentInput.trim());
|
|
104
105
|
currentValidRepo = repo;
|
|
105
106
|
if (repo) {
|
|
106
|
-
|
|
107
|
+
currentReadOnly = false;
|
|
108
|
+
currentLatest = false;
|
|
107
109
|
}
|
|
108
110
|
} else {
|
|
109
111
|
statusText.content = "";
|
|
@@ -151,8 +153,8 @@ export async function showAddReposPrompt(renderer: CliRenderer): Promise<AddRepo
|
|
|
151
153
|
} else {
|
|
152
154
|
reposList.content = repos
|
|
153
155
|
.map((repo, i) => {
|
|
154
|
-
const
|
|
155
|
-
return ` ${i + 1}. ${repo.spec}${
|
|
156
|
+
const roMarker = repo.readOnly ? " [Read-only]" : "";
|
|
157
|
+
return ` ${i + 1}. ${repo.spec}${roMarker}`;
|
|
156
158
|
})
|
|
157
159
|
.join("\n");
|
|
158
160
|
reposList.fg = "#94a3b8";
|
|
@@ -165,22 +167,22 @@ export async function showAddReposPrompt(renderer: CliRenderer): Promise<AddRepo
|
|
|
165
167
|
detailsLabel.content = `\nConfigure options for: ${currentInput.trim()}`;
|
|
166
168
|
detailsLabel.fg = "#38bdf8";
|
|
167
169
|
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
const latestCheckbox = currentLatest ? "[✓]" : "[ ]";
|
|
171
|
+
const latestSelected = confirmOptionIndex === 0;
|
|
172
|
+
detailsLatest.content = ` ${latestSelected ? "▶" : " "} ${latestCheckbox} Read-only [l]`;
|
|
173
|
+
detailsLatest.fg = latestSelected ? "#f8fafc" : currentLatest ? "#22d3ee" : "#94a3b8";
|
|
172
174
|
|
|
173
175
|
const confirmSelected = confirmOptionIndex === 1;
|
|
174
176
|
confirmButton.content = ` ${confirmSelected ? "▶" : " "} [Add repository]`;
|
|
175
177
|
confirmButton.fg = confirmSelected ? "#10b981" : "#64748b";
|
|
176
178
|
} else if (currentValidRepo) {
|
|
177
179
|
detailsLabel.content = "\nPress Enter to configure options";
|
|
178
|
-
detailsLabel.fg = "#
|
|
179
|
-
|
|
180
|
+
detailsLabel.fg = "#64798b";
|
|
181
|
+
detailsLatest.content = "";
|
|
180
182
|
confirmButton.content = "";
|
|
181
183
|
} else {
|
|
182
184
|
detailsLabel.content = "";
|
|
183
|
-
|
|
185
|
+
detailsLatest.content = "";
|
|
184
186
|
confirmButton.content = "";
|
|
185
187
|
}
|
|
186
188
|
}
|
|
@@ -250,7 +252,8 @@ export async function showAddReposPrompt(renderer: CliRenderer): Promise<AddRepo
|
|
|
250
252
|
const repo = validateRepo(selectedMatch.trim());
|
|
251
253
|
currentValidRepo = repo;
|
|
252
254
|
if (repo) {
|
|
253
|
-
|
|
255
|
+
currentReadOnly = false;
|
|
256
|
+
currentLatest = false;
|
|
254
257
|
}
|
|
255
258
|
|
|
256
259
|
updateMatches(); // Refresh display with new selection (matches stay the same, just highlight changes)
|
|
@@ -271,13 +274,15 @@ export async function showAddReposPrompt(renderer: CliRenderer): Promise<AddRepo
|
|
|
271
274
|
}
|
|
272
275
|
|
|
273
276
|
const repoToAdd = { ...currentValidRepo };
|
|
274
|
-
repoToAdd.
|
|
277
|
+
repoToAdd.readOnly = currentReadOnly;
|
|
278
|
+
repoToAdd.latest = currentLatest;
|
|
275
279
|
repos.push(repoToAdd);
|
|
276
280
|
|
|
277
281
|
currentInput = "";
|
|
278
282
|
repoInput.value = "";
|
|
279
283
|
currentValidRepo = null;
|
|
280
|
-
|
|
284
|
+
currentReadOnly = false;
|
|
285
|
+
currentLatest = false;
|
|
281
286
|
updateReposList();
|
|
282
287
|
lastQuery = "";
|
|
283
288
|
selectedMatchIndex = -1;
|
|
@@ -289,7 +294,7 @@ export async function showAddReposPrompt(renderer: CliRenderer): Promise<AddRepo
|
|
|
289
294
|
|
|
290
295
|
function enterConfirmMode() {
|
|
291
296
|
isConfirming = true;
|
|
292
|
-
confirmOptionIndex =
|
|
297
|
+
confirmOptionIndex = 2; // Start on confirm button for quick add
|
|
293
298
|
repoInput.blur();
|
|
294
299
|
updateDetails();
|
|
295
300
|
updateFooter();
|
|
@@ -305,8 +310,9 @@ export async function showAddReposPrompt(renderer: CliRenderer): Promise<AddRepo
|
|
|
305
310
|
|
|
306
311
|
function toggleCurrentOption() {
|
|
307
312
|
if (confirmOptionIndex === 0) {
|
|
308
|
-
// Toggle
|
|
309
|
-
|
|
313
|
+
// Toggle read-only
|
|
314
|
+
currentLatest = !currentLatest;
|
|
315
|
+
currentReadOnly = currentLatest;
|
|
310
316
|
} else if (confirmOptionIndex === 1) {
|
|
311
317
|
// Confirm button - add the repo
|
|
312
318
|
addCurrentRepo();
|
|
@@ -321,7 +327,7 @@ export async function showAddReposPrompt(renderer: CliRenderer): Promise<AddRepo
|
|
|
321
327
|
navigate: true,
|
|
322
328
|
select: false,
|
|
323
329
|
back: true,
|
|
324
|
-
custom: ["
|
|
330
|
+
custom: ["l Read-only", "space Toggle", "enter Add"],
|
|
325
331
|
});
|
|
326
332
|
} else {
|
|
327
333
|
showFooter(renderer, content, {
|
|
@@ -348,9 +354,10 @@ export async function showAddReposPrompt(renderer: CliRenderer): Promise<AddRepo
|
|
|
348
354
|
|
|
349
355
|
// Confirmation mode handling
|
|
350
356
|
if (isConfirming) {
|
|
351
|
-
// Toggle
|
|
352
|
-
if (key.name === "
|
|
353
|
-
|
|
357
|
+
// Toggle read-only with 'l' hotkey
|
|
358
|
+
if (key.name === "l") {
|
|
359
|
+
currentLatest = !currentLatest;
|
|
360
|
+
currentReadOnly = currentLatest;
|
|
354
361
|
updateDetails();
|
|
355
362
|
return;
|
|
356
363
|
}
|
|
@@ -446,7 +453,8 @@ export async function showAddReposPrompt(renderer: CliRenderer): Promise<AddRepo
|
|
|
446
453
|
const repo = validateRepo(value.trim());
|
|
447
454
|
currentValidRepo = repo;
|
|
448
455
|
if (repo) {
|
|
449
|
-
|
|
456
|
+
currentReadOnly = false;
|
|
457
|
+
currentLatest = false;
|
|
450
458
|
}
|
|
451
459
|
} else {
|
|
452
460
|
statusText.content = "";
|
package/src/ui/agent-proposal.ts
CHANGED
|
@@ -8,7 +8,15 @@ export interface AgentProposal {
|
|
|
8
8
|
goal?: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export
|
|
11
|
+
export interface AgentProposalResult {
|
|
12
|
+
sessionNameText: TextRenderable;
|
|
13
|
+
content: ReturnType<typeof createBaseLayout>["content"];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function showAgentProposal(
|
|
17
|
+
renderer: CliRenderer,
|
|
18
|
+
proposal: AgentProposal,
|
|
19
|
+
): AgentProposalResult {
|
|
12
20
|
clearLayout(renderer);
|
|
13
21
|
|
|
14
22
|
const { content } = createBaseLayout(renderer, "Agent Proposal");
|
|
@@ -77,4 +85,8 @@ export function showAgentProposal(renderer: CliRenderer, proposal: AgentProposal
|
|
|
77
85
|
marginTop: 1,
|
|
78
86
|
});
|
|
79
87
|
content.add(continueText);
|
|
88
|
+
|
|
89
|
+
renderer.requestRender();
|
|
90
|
+
|
|
91
|
+
return { sessionNameText, content };
|
|
80
92
|
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { type CliRenderer, TextRenderable, type KeyEvent } from "@opentui/core";
|
|
2
|
+
import { createBaseLayout, clearLayout } from "./renderer";
|
|
3
|
+
import { showFooter, hideFooter } from "./common/footer";
|
|
4
|
+
import { isEnter, isEscape, isKey } from "./common/keyboard";
|
|
5
|
+
import type { ChatConfig } from "../flows/chat-to-config";
|
|
6
|
+
|
|
7
|
+
export type ConfirmationAction = "confirm" | "edit" | "cancel" | "back";
|
|
8
|
+
|
|
9
|
+
export interface ConfirmationResult {
|
|
10
|
+
action: ConfirmationAction;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function showChatConfirmation(
|
|
14
|
+
renderer: CliRenderer,
|
|
15
|
+
config: ChatConfig,
|
|
16
|
+
): Promise<ConfirmationResult> {
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
clearLayout(renderer);
|
|
19
|
+
|
|
20
|
+
const { content } = createBaseLayout(renderer, "Review Configuration");
|
|
21
|
+
|
|
22
|
+
// Title
|
|
23
|
+
const title = new TextRenderable(renderer, {
|
|
24
|
+
id: "confirm-title",
|
|
25
|
+
content: "Here's what I'll set up for you:",
|
|
26
|
+
fg: "#e2e8f0",
|
|
27
|
+
marginBottom: 2,
|
|
28
|
+
});
|
|
29
|
+
content.add(title);
|
|
30
|
+
|
|
31
|
+
// Repositories
|
|
32
|
+
const reposLabel = new TextRenderable(renderer, {
|
|
33
|
+
id: "repos-label",
|
|
34
|
+
content: "📦 Repositories:",
|
|
35
|
+
fg: "#38bdf8",
|
|
36
|
+
marginBottom: 0,
|
|
37
|
+
});
|
|
38
|
+
content.add(reposLabel);
|
|
39
|
+
|
|
40
|
+
if (config.repos.length > 0) {
|
|
41
|
+
config.repos.forEach((repo, i) => {
|
|
42
|
+
const repoText = new TextRenderable(renderer, {
|
|
43
|
+
id: `repo-${i}`,
|
|
44
|
+
content: ` • ${repo}`,
|
|
45
|
+
fg: "#94a3b8",
|
|
46
|
+
});
|
|
47
|
+
content.add(repoText);
|
|
48
|
+
});
|
|
49
|
+
} else {
|
|
50
|
+
const noRepos = new TextRenderable(renderer, {
|
|
51
|
+
id: "no-repos",
|
|
52
|
+
content: " (none)",
|
|
53
|
+
fg: "#64748b",
|
|
54
|
+
});
|
|
55
|
+
content.add(noRepos);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Skills
|
|
59
|
+
const skillsLabel = new TextRenderable(renderer, {
|
|
60
|
+
id: "skills-label",
|
|
61
|
+
content: "\n🛠️ Skills:",
|
|
62
|
+
fg: "#38bdf8",
|
|
63
|
+
marginBottom: 0,
|
|
64
|
+
});
|
|
65
|
+
content.add(skillsLabel);
|
|
66
|
+
|
|
67
|
+
if (config.skills && config.skills.length > 0) {
|
|
68
|
+
config.skills.forEach((skill, i) => {
|
|
69
|
+
const skillText = new TextRenderable(renderer, {
|
|
70
|
+
id: `skill-${i}`,
|
|
71
|
+
content: ` • ${skill}`,
|
|
72
|
+
fg: "#94a3b8",
|
|
73
|
+
});
|
|
74
|
+
content.add(skillText);
|
|
75
|
+
});
|
|
76
|
+
} else {
|
|
77
|
+
const noSkills = new TextRenderable(renderer, {
|
|
78
|
+
id: "no-skills",
|
|
79
|
+
content: " (none)",
|
|
80
|
+
fg: "#64748b",
|
|
81
|
+
});
|
|
82
|
+
content.add(noSkills);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Goal
|
|
86
|
+
const goalLabel = new TextRenderable(renderer, {
|
|
87
|
+
id: "goal-label",
|
|
88
|
+
content: "\n🎯 Goal:",
|
|
89
|
+
fg: "#38bdf8",
|
|
90
|
+
marginBottom: 0,
|
|
91
|
+
});
|
|
92
|
+
content.add(goalLabel);
|
|
93
|
+
|
|
94
|
+
const goalText = new TextRenderable(renderer, {
|
|
95
|
+
id: "goal-text",
|
|
96
|
+
content: config.goal ? ` ${config.goal}` : " (none)",
|
|
97
|
+
fg: config.goal ? "#94a3b8" : "#64748b",
|
|
98
|
+
marginBottom: 2,
|
|
99
|
+
});
|
|
100
|
+
content.add(goalText);
|
|
101
|
+
|
|
102
|
+
// Confirmation question
|
|
103
|
+
const confirmText = new TextRenderable(renderer, {
|
|
104
|
+
id: "confirm-question",
|
|
105
|
+
content: "Does this look right?",
|
|
106
|
+
fg: "#e2e8f0",
|
|
107
|
+
marginTop: 2,
|
|
108
|
+
});
|
|
109
|
+
content.add(confirmText);
|
|
110
|
+
|
|
111
|
+
const cleanup = () => {
|
|
112
|
+
renderer.keyInput.off("keypress", handleKeypress);
|
|
113
|
+
hideFooter(renderer);
|
|
114
|
+
clearLayout(renderer);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const handleKeypress = (key: KeyEvent) => {
|
|
118
|
+
if (isEscape(key)) {
|
|
119
|
+
cleanup();
|
|
120
|
+
resolve({ action: "cancel" });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (isEnter(key)) {
|
|
125
|
+
cleanup();
|
|
126
|
+
resolve({ action: "confirm" });
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (isKey(key, "e")) {
|
|
131
|
+
cleanup();
|
|
132
|
+
resolve({ action: "edit" });
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (isKey(key, "b")) {
|
|
137
|
+
cleanup();
|
|
138
|
+
resolve({ action: "back" });
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
showFooter(renderer, content, {
|
|
144
|
+
navigate: false,
|
|
145
|
+
select: false,
|
|
146
|
+
back: true,
|
|
147
|
+
custom: ["Enter Confirm", "b Back to Chat", "e Manual Edit", "Esc Cancel"],
|
|
148
|
+
});
|
|
149
|
+
renderer.keyInput.on("keypress", handleKeypress);
|
|
150
|
+
});
|
|
151
|
+
}
|