letmecook 0.0.13 → 0.0.15

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "letmecook",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/rustydotwtf/letmecook.git"
@@ -1,10 +1,11 @@
1
+ import { join } from "node:path";
1
2
  import type { CliRenderer } from "@opentui/core";
2
- import type { Session } from "../types";
3
+ import type { Session, RepoSpec } from "../types";
3
4
  import { updateSessionRepos } from "../sessions";
4
- import { cloneAllRepos } from "../git";
5
5
  import { writeAgentsMd } from "../agents-md";
6
6
  import { recordRepoHistory } from "../repo-history";
7
7
  import { showAddReposPrompt } from "../ui/add-repos";
8
+ import { runCommands, hideCommandRunner, type CommandTask } from "../ui/common/command-runner";
8
9
 
9
10
  export interface AddReposParams {
10
11
  renderer: CliRenderer;
@@ -16,6 +17,33 @@ export interface AddReposResult {
16
17
  cancelled: boolean;
17
18
  }
18
19
 
20
+ function reposToCommandTasks(repos: RepoSpec[], sessionPath: string): CommandTask[] {
21
+ return repos.map((repo) => {
22
+ const url = `https://github.com/${repo.owner}/${repo.name}.git`;
23
+ const targetDir = join(sessionPath, repo.dir);
24
+ const args = repo.branch
25
+ ? [
26
+ "git",
27
+ "clone",
28
+ "--depth",
29
+ "1",
30
+ "--single-branch",
31
+ "--branch",
32
+ repo.branch,
33
+ "--progress",
34
+ url,
35
+ targetDir,
36
+ ]
37
+ : ["git", "clone", "--depth", "1", "--single-branch", "--progress", url, targetDir];
38
+
39
+ return {
40
+ label: `Cloning ${repo.owner}/${repo.name}`,
41
+ command: args,
42
+ cwd: sessionPath,
43
+ };
44
+ });
45
+ }
46
+
19
47
  export async function addReposFlow(params: AddReposParams): Promise<AddReposResult> {
20
48
  const { renderer, session } = params;
21
49
 
@@ -26,19 +54,30 @@ export async function addReposFlow(params: AddReposParams): Promise<AddReposResu
26
54
  const newRepos = addResult.repos.filter((r) => !existingSpecs.has(r.spec));
27
55
 
28
56
  if (newRepos.length > 0) {
29
- console.log(`\nCloning ${newRepos.length} new repository(ies)...`);
30
-
31
- await cloneAllRepos(newRepos, session.path, (repoIndex, status) => {
32
- const repo = newRepos[repoIndex];
33
- if (repo) {
34
- if (status === "done") {
35
- console.log(` ✓ ${repo.owner}/${repo.name}`);
36
- } else if (status === "error") {
37
- console.log(` ✗ ${repo.owner}/${repo.name} (failed)`);
38
- }
39
- }
57
+ const tasks = reposToCommandTasks(newRepos, session.path);
58
+
59
+ const results = await runCommands(renderer, {
60
+ title: "Adding repositories",
61
+ tasks,
62
+ showOutput: true,
63
+ outputLines: 5,
40
64
  });
41
65
 
66
+ // Check for errors
67
+ const errors = results.filter((r) => !r.success);
68
+ if (errors.length > 0) {
69
+ console.error(`\n⚠️ ${errors.length} repository(ies) failed to clone:`);
70
+ errors.forEach((err) => {
71
+ console.error(` ✗ ${err.task.label}`);
72
+ if (err.error) {
73
+ console.error(` ${err.error}`);
74
+ }
75
+ });
76
+ }
77
+
78
+ await new Promise((resolve) => setTimeout(resolve, 700));
79
+ hideCommandRunner(renderer);
80
+
42
81
  const allRepos = [...session.repos, ...newRepos];
43
82
  const updatedSession = await updateSessionRepos(session.name, allRepos);
44
83
  const nextSession = updatedSession ?? session;
@@ -46,11 +85,8 @@ export async function addReposFlow(params: AddReposParams): Promise<AddReposResu
46
85
  await recordRepoHistory(newRepos);
47
86
  await writeAgentsMd(nextSession);
48
87
 
49
- console.log("\n✅ Repositories added.\n");
50
88
  return { session: nextSession, cancelled: false };
51
89
  }
52
-
53
- console.log("\nNo new repositories to add (all already in session).\n");
54
90
  }
55
91
 
56
92
  return { session, cancelled: addResult.cancelled };
@@ -1,10 +1,11 @@
1
+ import { join } from "node:path";
1
2
  import type { Session, RepoSpec } from "../types";
2
3
  import { updateSessionRepos, updateSessionSkills, deleteSession } from "../sessions";
3
- import { cloneAllRepos } from "../git";
4
4
  import { writeAgentsMd } from "../agents-md";
5
5
  import { recordRepoHistory } from "../repo-history";
6
6
  import { removeSkillFromSession } from "../skills";
7
- import { addSkillToSession } from "../skills";
7
+ import { createRenderer, destroyRenderer } from "../ui/renderer";
8
+ import { runCommands, hideCommandRunner, type CommandTask } from "../ui/common/command-runner";
8
9
 
9
10
  export interface EditSessionParams {
10
11
  session: Session;
@@ -15,6 +16,41 @@ export interface EditSessionParams {
15
16
  };
16
17
  }
17
18
 
19
+ function reposToCommandTasks(repos: RepoSpec[], sessionPath: string): CommandTask[] {
20
+ return repos.map((repo) => {
21
+ const url = `https://github.com/${repo.owner}/${repo.name}.git`;
22
+ const targetDir = join(sessionPath, repo.dir);
23
+ const args = repo.branch
24
+ ? [
25
+ "git",
26
+ "clone",
27
+ "--depth",
28
+ "1",
29
+ "--single-branch",
30
+ "--branch",
31
+ repo.branch,
32
+ "--progress",
33
+ url,
34
+ targetDir,
35
+ ]
36
+ : ["git", "clone", "--depth", "1", "--single-branch", "--progress", url, targetDir];
37
+
38
+ return {
39
+ label: `Cloning ${repo.owner}/${repo.name}`,
40
+ command: args,
41
+ cwd: sessionPath,
42
+ };
43
+ });
44
+ }
45
+
46
+ function skillsToCommandTasks(skills: string[], sessionPath: string): CommandTask[] {
47
+ return skills.map((skill) => ({
48
+ label: `Installing ${skill}`,
49
+ command: ["bunx", "skills", "add", skill, "-y"],
50
+ cwd: sessionPath,
51
+ }));
52
+ }
53
+
18
54
  export async function editSession(params: EditSessionParams): Promise<Session | null> {
19
55
  const { session, updates } = params;
20
56
 
@@ -23,66 +59,88 @@ export async function editSession(params: EditSessionParams): Promise<Session |
23
59
  }
24
60
 
25
61
  let currentSession = session;
62
+ const renderer = await createRenderer();
63
+
64
+ try {
65
+ if (updates.repos) {
66
+ const existingSpecs = new Set(session.repos.map((r) => r.spec));
67
+ const newRepos = updates.repos.filter((r) => !existingSpecs.has(r.spec));
68
+
69
+ if (newRepos.length > 0) {
70
+ const tasks = reposToCommandTasks(newRepos, currentSession.path);
26
71
 
27
- if (updates.repos) {
28
- const existingSpecs = new Set(session.repos.map((r) => r.spec));
29
- const newRepos = updates.repos.filter((r) => !existingSpecs.has(r.spec));
30
-
31
- if (newRepos.length > 0) {
32
- console.log(`\nCloning ${newRepos.length} new repository(ies)...`);
33
-
34
- await cloneAllRepos(newRepos, currentSession.path, (repoIndex, status) => {
35
- const repo = newRepos[repoIndex];
36
- if (repo) {
37
- if (status === "done") {
38
- console.log(` ✓ ${repo.owner}/${repo.name}`);
39
- } else if (status === "error") {
40
- console.log(` ✗ ${repo.owner}/${repo.name} (failed)`);
41
- }
72
+ const results = await runCommands(renderer, {
73
+ title: "Adding repositories",
74
+ tasks,
75
+ showOutput: true,
76
+ outputLines: 5,
77
+ });
78
+
79
+ // Check for errors
80
+ const errors = results.filter((r) => !r.success);
81
+ if (errors.length > 0) {
82
+ console.error(`\n⚠️ ${errors.length} repository(ies) failed to clone:`);
83
+ errors.forEach((err) => {
84
+ console.error(` ✗ ${err.task.label}`);
85
+ if (err.error) {
86
+ console.error(` ${err.error}`);
87
+ }
88
+ });
42
89
  }
43
- });
44
90
 
45
- await recordRepoHistory(newRepos);
46
- await writeAgentsMd(currentSession);
91
+ await new Promise((resolve) => setTimeout(resolve, 700));
92
+ hideCommandRunner(renderer);
47
93
 
48
- console.log("\n✅ Repositories added.\n");
49
- }
94
+ await recordRepoHistory(newRepos);
95
+ await writeAgentsMd(currentSession);
96
+ }
50
97
 
51
- const updatedSession = await updateSessionRepos(session.name, updates.repos);
52
- if (updatedSession) {
53
- currentSession = updatedSession;
98
+ const updatedSession = await updateSessionRepos(session.name, updates.repos);
99
+ if (updatedSession) {
100
+ currentSession = updatedSession;
101
+ }
54
102
  }
55
- }
56
103
 
57
- if (updates.skills) {
58
- const existingSkills = new Set(session.skills || []);
59
- const newSkills = updates.skills.filter((s) => !existingSkills.has(s));
104
+ if (updates.skills) {
105
+ const existingSkills = new Set(session.skills || []);
106
+ const newSkills = updates.skills.filter((s) => !existingSkills.has(s));
60
107
 
61
- if (newSkills.length > 0) {
62
- console.log(`\nAdding ${newSkills.length} skill package(s)...`);
108
+ if (newSkills.length > 0) {
109
+ const tasks = skillsToCommandTasks(newSkills, currentSession.path);
63
110
 
64
- for (const skill of newSkills) {
65
- console.log(` Adding ${skill}...`);
66
- const { success } = await addSkillToSession(currentSession, skill, (output) => {
67
- console.log(` ${output}`);
111
+ const results = await runCommands(renderer, {
112
+ title: "Adding skills",
113
+ tasks,
114
+ showOutput: true,
115
+ outputLines: 5,
68
116
  });
69
117
 
70
- if (success) {
71
- console.log(` ✓ ${skill}`);
72
- } else {
73
- console.log(` ${skill} (addition failed)`);
118
+ // Check for errors
119
+ const errors = results.filter((r) => !r.success);
120
+ if (errors.length > 0) {
121
+ console.error(`\n⚠️ ${errors.length} skill(s) failed to install:`);
122
+ errors.forEach((err) => {
123
+ console.error(` ✗ ${err.task.label}`);
124
+ if (err.error) {
125
+ console.error(` ${err.error}`);
126
+ }
127
+ });
74
128
  }
75
- }
76
129
 
77
- const allSkills = [...(session.skills || []), ...newSkills];
78
- const updatedSession = await updateSessionSkills(currentSession.name, allSkills);
130
+ await new Promise((resolve) => setTimeout(resolve, 700));
131
+ hideCommandRunner(renderer);
79
132
 
80
- if (updatedSession) {
81
- currentSession = updatedSession;
82
- await writeAgentsMd(currentSession);
83
- console.log("\n✅ Skills added.\n");
133
+ const allSkills = [...(session.skills || []), ...newSkills];
134
+ const updatedSession = await updateSessionSkills(currentSession.name, allSkills);
135
+
136
+ if (updatedSession) {
137
+ currentSession = updatedSession;
138
+ await writeAgentsMd(currentSession);
139
+ }
84
140
  }
85
141
  }
142
+ } finally {
143
+ destroyRenderer();
86
144
  }
87
145
 
88
146
  if (updates.goal !== undefined) {
@@ -1,14 +1,13 @@
1
+ import { join } from "node:path";
1
2
  import type { CliRenderer } from "@opentui/core";
2
3
  import type { Session, RepoSpec } from "../types";
3
4
  import { findMatchingSession, createSession, deleteSession } from "../sessions";
4
- import { cloneAllRepos } from "../git";
5
5
  import { generateSessionName } from "../naming";
6
6
  import { writeAgentsMd, createClaudeMdSymlink } from "../agents-md";
7
- import { addSkillToSession } from "../skills";
8
7
  import { recordRepoHistory } from "../repo-history";
9
- import { showProgress, updateProgress, hideProgress } from "../ui/progress";
10
8
  import { showAgentProposal } from "../ui/agent-proposal";
11
9
  import { showConflictPrompt } from "../ui/conflict";
10
+ import { runCommands, hideCommandRunner, type CommandTask } from "../ui/common/command-runner";
12
11
 
13
12
  export interface NewSessionParams {
14
13
  repos: RepoSpec[];
@@ -23,6 +22,41 @@ export interface NewSessionResult {
23
22
  skipped?: boolean;
24
23
  }
25
24
 
25
+ function reposToCommandTasks(repos: RepoSpec[], sessionPath: string): CommandTask[] {
26
+ return repos.map((repo) => {
27
+ const url = `https://github.com/${repo.owner}/${repo.name}.git`;
28
+ const targetDir = join(sessionPath, repo.dir);
29
+ const args = repo.branch
30
+ ? [
31
+ "git",
32
+ "clone",
33
+ "--depth",
34
+ "1",
35
+ "--single-branch",
36
+ "--branch",
37
+ repo.branch,
38
+ "--progress",
39
+ url,
40
+ targetDir,
41
+ ]
42
+ : ["git", "clone", "--depth", "1", "--single-branch", "--progress", url, targetDir];
43
+
44
+ return {
45
+ label: `Cloning ${repo.owner}/${repo.name}`,
46
+ command: args,
47
+ cwd: sessionPath,
48
+ };
49
+ });
50
+ }
51
+
52
+ function skillsToCommandTasks(skills: string[], sessionPath: string): CommandTask[] {
53
+ return skills.map((skill) => ({
54
+ label: `Installing ${skill}`,
55
+ command: ["bunx", "skills", "add", skill, "-y"],
56
+ cwd: sessionPath,
57
+ }));
58
+ }
59
+
26
60
  export async function createNewSession(
27
61
  renderer: CliRenderer,
28
62
  params: NewSessionParams,
@@ -53,20 +87,9 @@ export async function createNewSession(
53
87
  }
54
88
  }
55
89
 
56
- const progressState = showProgress(renderer, repos);
57
-
58
- progressState.phase = "naming";
59
- updateProgress(renderer, progressState);
60
-
61
90
  const sessionName = await generateSessionName(repos, goal);
62
- progressState.sessionName = sessionName;
63
- progressState.phase = "cloning";
64
- updateProgress(renderer, progressState);
65
91
 
66
92
  if (mode === "tui") {
67
- progressState.phase = "proposal";
68
- updateProgress(renderer, progressState);
69
-
70
93
  showAgentProposal(renderer, {
71
94
  sessionName,
72
95
  repos,
@@ -75,11 +98,6 @@ export async function createNewSession(
75
98
 
76
99
  await new Promise((resolve) => setTimeout(resolve, 3000));
77
100
 
78
- const cloneProgressState = showProgress(renderer, repos);
79
- cloneProgressState.sessionName = sessionName;
80
- cloneProgressState.phase = "cloning";
81
- updateProgress(renderer, cloneProgressState);
82
-
83
101
  const session = await createSession(
84
102
  sessionName,
85
103
  repos,
@@ -87,49 +105,42 @@ export async function createNewSession(
87
105
  skills?.length ? skills : undefined,
88
106
  );
89
107
 
90
- await cloneAllRepos(repos, session.path, (repoIndex, status, outputLines) => {
91
- const repoState = cloneProgressState.repos[repoIndex];
92
- if (repoState) {
93
- repoState.status = status;
94
- if (outputLines) {
95
- cloneProgressState.currentOutput = outputLines;
96
- }
97
- updateProgress(renderer, cloneProgressState);
98
- }
108
+ // Build all tasks (repos + skills)
109
+ const tasks: CommandTask[] = [
110
+ ...reposToCommandTasks(repos, session.path),
111
+ ...(skills && skills.length > 0 ? skillsToCommandTasks(skills, session.path) : []),
112
+ ];
113
+
114
+ const results = await runCommands(renderer, {
115
+ title: "Setting up session",
116
+ tasks,
117
+ showOutput: true,
118
+ outputLines: 5,
99
119
  });
100
120
 
101
- if (skills && skills.length > 0) {
102
- cloneProgressState.phase = "installing-skills";
103
- updateProgress(renderer, cloneProgressState);
104
-
105
- for (const skill of skills) {
106
- console.log(`Installing skill: ${skill}...`);
107
- const { success } = await addSkillToSession(session, skill, (output) => {
108
- console.log(` ${output}`);
109
- });
110
-
111
- if (success) {
112
- console.log(`✓ ${skill}`);
113
- } else {
114
- console.log(`✗ ${skill} (installation failed)`);
121
+ // Check for errors
122
+ const errors = results.filter((r) => !r.success);
123
+ if (errors.length > 0) {
124
+ console.error(`\n⚠️ ${errors.length} task(s) failed:`);
125
+ errors.forEach((err) => {
126
+ console.error(` ${err.task.label}`);
127
+ if (err.error) {
128
+ console.error(` ${err.error}`);
115
129
  }
116
- }
130
+ });
117
131
  }
118
132
 
119
133
  await recordRepoHistory(repos);
120
134
 
121
- cloneProgressState.phase = "done";
122
- updateProgress(renderer, cloneProgressState);
123
-
124
- await new Promise((resolve) => setTimeout(resolve, 1000));
125
-
126
- hideProgress(renderer);
135
+ await new Promise((resolve) => setTimeout(resolve, 700));
136
+ hideCommandRunner(renderer);
127
137
 
128
138
  await writeAgentsMd(session);
129
139
  await createClaudeMdSymlink(session.path);
130
140
 
131
141
  return { session };
132
142
  } else {
143
+ // CLI mode - simpler output
133
144
  const session = await createSession(
134
145
  sessionName,
135
146
  repos,
@@ -137,46 +148,40 @@ export async function createNewSession(
137
148
  skills?.length ? skills : undefined,
138
149
  );
139
150
 
140
- await cloneAllRepos(repos, session.path, (repoIndex, status) => {
141
- const repoState = progressState.repos[repoIndex];
142
- if (repoState) {
143
- repoState.status = status;
144
- updateProgress(renderer, progressState);
145
- }
146
- });
151
+ console.log(`\nCloning ${repos.length} repository(ies)...`);
147
152
 
148
- if (skills && skills.length > 0) {
149
- progressState.phase = "installing-skills";
150
- updateProgress(renderer, progressState);
153
+ const tasks: CommandTask[] = [
154
+ ...reposToCommandTasks(repos, session.path),
155
+ ...(skills && skills.length > 0 ? skillsToCommandTasks(skills, session.path) : []),
156
+ ];
151
157
 
152
- for (const skill of skills) {
153
- console.log(`Installing skill: ${skill}...`);
154
- const { success } = await addSkillToSession(session, skill, (output) => {
155
- console.log(` ${output}`);
156
- });
158
+ const results = await runCommands(renderer, {
159
+ title: "Setting up session",
160
+ tasks,
161
+ showOutput: false, // CLI mode doesn't need visual output
162
+ });
157
163
 
158
- if (success) {
159
- console.log(`✓ ${skill}`);
160
- } else {
161
- console.log(`✗ ${skill} (installation failed)`);
164
+ // Print results
165
+ results.forEach((result) => {
166
+ if (result.success) {
167
+ console.log(` ✓ ${result.task.label}`);
168
+ } else {
169
+ console.log(` ✗ ${result.task.label}`);
170
+ if (result.error) {
171
+ console.log(` ${result.error}`);
162
172
  }
163
173
  }
164
- }
174
+ });
165
175
 
166
176
  await writeAgentsMd(session);
167
177
  await createClaudeMdSymlink(session.path);
168
178
 
169
- progressState.phase = "done";
170
- updateProgress(renderer, progressState);
171
-
172
- await new Promise((resolve) => setTimeout(resolve, 500));
173
-
174
- hideProgress(renderer);
179
+ hideCommandRunner(renderer);
175
180
 
176
181
  return { session };
177
182
  }
178
183
  } catch (error) {
179
- hideProgress(renderer);
184
+ hideCommandRunner(renderer);
180
185
  throw error;
181
186
  }
182
187
  }