lalph 0.2.0 → 0.2.2

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/dist/cli.mjs CHANGED
@@ -66689,23 +66689,14 @@ const PlatformServices = layer$2;
66689
66689
  //#endregion
66690
66690
  //#region src/domain/Project.ts
66691
66691
  const ProjectId = String$1.pipe(brand("lalph/ProjectId"));
66692
- var Project$1 = class Project$1 extends Class("lalph/Project")({
66692
+ var Project$1 = class extends Class("lalph/Project")({
66693
66693
  id: ProjectId,
66694
66694
  enabled: Boolean$2,
66695
66695
  targetBranch: Option(String$1),
66696
66696
  concurrency: Int.check(isGreaterThanOrEqualTo(1)),
66697
66697
  gitFlow: Literals(["pr", "commit"]),
66698
66698
  reviewAgent: Boolean$2
66699
- }) {
66700
- static defaultProject = new Project$1({
66701
- id: ProjectId.makeUnsafe("default"),
66702
- enabled: true,
66703
- targetBranch: none$3(),
66704
- concurrency: 1,
66705
- gitFlow: "pr",
66706
- reviewAgent: true
66707
- });
66708
- };
66699
+ }) {};
66709
66700
 
66710
66701
  //#endregion
66711
66702
  //#region src/Kvs.ts
@@ -151186,8 +151177,8 @@ const agentTimeout = fnUntraced(function* (options) {
151186
151177
  const layerProjectIdPrompt = effect$1(CurrentProjectId, gen(function* () {
151187
151178
  return (yield* selectProject).id;
151188
151179
  })).pipe(provide$3(Settings.layer));
151189
- const allProjects = new Setting("projects", NonEmptyArray(Project$1));
151190
- const getAllProjects = Settings.get(allProjects).pipe(map$8(getOrElse$1(() => [Project$1.defaultProject])));
151180
+ const allProjects = new Setting("projects", Array$1(Project$1));
151181
+ const getAllProjects = Settings.get(allProjects).pipe(map$8(getOrElse$1(() => [])));
151191
151182
  const projectById = fnUntraced(function* (projectId) {
151192
151183
  const projects = yield* getAllProjects;
151193
151184
  return findFirst$3(projects, (p) => p.id === projectId);
@@ -151195,7 +151186,7 @@ const projectById = fnUntraced(function* (projectId) {
151195
151186
  const allProjectsAtom = (function() {
151196
151187
  const read = Settings.runtime.atom(fnUntraced(function* () {
151197
151188
  const projects = yield* (yield* Settings).get(allProjects);
151198
- return getOrElse$1(projects, () => [Project$1.defaultProject]);
151189
+ return getOrElse$1(projects, () => []);
151199
151190
  }));
151200
151191
  const set = Settings.runtime.fn()(fnUntraced(function* (value, get) {
151201
151192
  yield* (yield* Settings).set(allProjects, some$2(value));
@@ -151219,7 +151210,6 @@ const projectAtom = family((projectId) => {
151219
151210
  onNone: () => filter$6(projects, (p) => p.id !== projectId),
151220
151211
  onSome: (project) => map$12(projects, (p) => p.id === projectId ? project : p)
151221
151212
  });
151222
- if (!isArrayNonEmpty(updatedProjects)) return;
151223
151213
  get.set(allProjectsAtom, updatedProjects);
151224
151214
  }));
151225
151215
  return writable((get) => {
@@ -151231,9 +151221,11 @@ const projectAtom = family((projectId) => {
151231
151221
  });
151232
151222
  const selectProject = gen(function* () {
151233
151223
  const projects = yield* getAllProjects;
151234
- if (projects.length === 1) {
151235
- yield* log$1(`Using project: ${projects[0].id}`);
151236
- return projects[0];
151224
+ if (projects.length === 0) return yield* welcomeWizard;
151225
+ else if (projects.length === 1) {
151226
+ const project = projects[0];
151227
+ yield* log$1(`Using project: ${project.id}`);
151228
+ return project;
151237
151229
  }
151238
151230
  return yield* autoComplete({
151239
151231
  message: "Select a project:",
@@ -151243,6 +151235,58 @@ const selectProject = gen(function* () {
151243
151235
  }))
151244
151236
  });
151245
151237
  });
151238
+ const welcomeWizard = gen(function* () {
151239
+ const welcome = [
151240
+ " .--.",
151241
+ " |^()^| lalph",
151242
+ " '--'",
151243
+ "",
151244
+ "Welcome! Let's add your first project.",
151245
+ "Projects let you configure how lalph runs tasks and integrations",
151246
+ "(like issue filters, concurrency, and git flow).",
151247
+ ""
151248
+ ].join("\n");
151249
+ console.log(welcome);
151250
+ return yield* addProject;
151251
+ });
151252
+ const addProject = gen(function* () {
151253
+ const projects = yield* getAllProjects;
151254
+ const id = yield* text$2({
151255
+ message: "Name",
151256
+ validate(input) {
151257
+ input = input.trim();
151258
+ if (input.length === 0) return fail$4("Project name cannot be empty");
151259
+ else if (projects.some((p) => p.id === input)) return fail$4("Project already exists");
151260
+ return succeed$1(input);
151261
+ }
151262
+ });
151263
+ const concurrency = yield* integer$2({
151264
+ message: "Concurrency",
151265
+ min: 1
151266
+ });
151267
+ const targetBranch = pipe(yield* text$2({ message: "Target branch (leave empty to use HEAD)" }), trim, liftPredicate(isNonEmpty));
151268
+ const gitFlow = yield* select({
151269
+ message: "Git flow",
151270
+ choices: [{
151271
+ title: "Pull Request",
151272
+ value: "pr"
151273
+ }, {
151274
+ title: "Commit",
151275
+ value: "commit"
151276
+ }]
151277
+ });
151278
+ const reviewAgent = yield* toggle({ message: "Enable review agent?" });
151279
+ const project = new Project$1({
151280
+ id: ProjectId.makeUnsafe(id),
151281
+ enabled: true,
151282
+ concurrency,
151283
+ targetBranch,
151284
+ gitFlow,
151285
+ reviewAgent
151286
+ });
151287
+ yield* Settings.set(allProjects, some$2([...projects, project]));
151288
+ return project;
151289
+ });
151246
151290
 
151247
151291
  //#endregion
151248
151292
  //#region src/commands/root.ts
@@ -151403,7 +151447,13 @@ const commandRoot = make$35("lalph", {
151403
151447
  }).pipe(withHandler(fnUntraced(function* ({ iterations, maxIterationMinutes, stallMinutes, specsDirectory }) {
151404
151448
  const commandPrefix = yield* getCommandPrefix;
151405
151449
  yield* getOrSelectCliAgent;
151406
- const projects = (yield* getAllProjects).filter((p) => p.enabled);
151450
+ let allProjects = yield* getAllProjects;
151451
+ if (allProjects.length === 0) {
151452
+ yield* welcomeWizard;
151453
+ allProjects = yield* getAllProjects;
151454
+ }
151455
+ const projects = allProjects.filter((p) => p.enabled);
151456
+ if (projects.length === 0) return yield* log$1("No enabled projects found. Run 'lalph projects toggle' to enable one.");
151407
151457
  yield* forEach$3(projects, (project) => runProject({
151408
151458
  iterations,
151409
151459
  project,
@@ -151523,7 +151573,7 @@ const handler$1 = flow(withHandler(fnUntraced(function* () {
151523
151573
  }));
151524
151574
  console.log(`Created issue with ID: ${created.id}`);
151525
151575
  console.log(`URL: ${created.url}`);
151526
- }, scoped$1)), provide(mergeAll(CurrentIssueSource.layer, layerProjectIdPrompt)));
151576
+ }, scoped$1)), provide(mergeAll(CurrentIssueSource.layer).pipe(provideMerge(layerProjectIdPrompt))));
151527
151577
  const commandIssue = make$35("issue").pipe(withDescription("Create a new issue in the selected issue source"), handler$1);
151528
151578
  const commandIssueAlias = make$35("i").pipe(withDescription("Alias for 'issue' command"), handler$1);
151529
151579
 
@@ -151548,7 +151598,7 @@ const commandSource = make$35("source").pipe(withDescription("Select the issue s
151548
151598
 
151549
151599
  //#endregion
151550
151600
  //#region package.json
151551
- var version = "0.2.0";
151601
+ var version = "0.2.2";
151552
151602
 
151553
151603
  //#endregion
151554
151604
  //#region src/commands/projects/ls.ts
@@ -151558,8 +151608,13 @@ const commandProjectsLs = make$35("ls").pipe(withDescription("List all configure
151558
151608
  console.log("Issue source:", meta.name);
151559
151609
  console.log("");
151560
151610
  const projects = yield* getAllProjects;
151611
+ if (projects.length === 0) {
151612
+ console.log("No projects configured yet. Run 'lalph projects add' to get started.");
151613
+ return;
151614
+ }
151561
151615
  for (const project of projects) {
151562
151616
  console.log(`Project: ${project.id}`);
151617
+ console.log(` Enabled: ${project.enabled ? "Yes" : "No"}`);
151563
151618
  yield* source.info(project.id);
151564
151619
  console.log(` Concurrency: ${project.concurrency}`);
151565
151620
  if (isSome(project.targetBranch)) console.log(` Target Branch: ${project.targetBranch.value}`);
@@ -151572,41 +151627,7 @@ const commandProjectsLs = make$35("ls").pipe(withDescription("List all configure
151572
151627
  //#endregion
151573
151628
  //#region src/commands/projects/add.ts
151574
151629
  const commandProjectsAdd = make$35("add").pipe(withDescription("Add a new project"), withHandler(fnUntraced(function* () {
151575
- const projects = yield* getAllProjects;
151576
- const id = yield* text$2({
151577
- message: "Name",
151578
- validate(input) {
151579
- input = input.trim();
151580
- if (input.length === 0) return fail$4("Project name cannot be empty");
151581
- else if (projects.some((p) => p.id === input)) return fail$4(`Project already exists`);
151582
- return succeed$1(input);
151583
- }
151584
- });
151585
- const concurrency = yield* integer$2({
151586
- message: "Concurrency",
151587
- min: 1
151588
- });
151589
- const targetBranch = pipe(yield* text$2({ message: "Target branch (leave empty to use HEAD)" }), trim, liftPredicate(isNonEmpty));
151590
- const gitFlow = yield* select({
151591
- message: "Git flow",
151592
- choices: [{
151593
- title: "Pull Request",
151594
- value: "pr"
151595
- }, {
151596
- title: "Commit",
151597
- value: "commit"
151598
- }]
151599
- });
151600
- const reviewAgent = yield* toggle({ message: "Enable review agent?" });
151601
- const project = new Project$1({
151602
- id: ProjectId.makeUnsafe(id),
151603
- enabled: true,
151604
- concurrency,
151605
- targetBranch,
151606
- gitFlow,
151607
- reviewAgent
151608
- });
151609
- yield* Settings.set(allProjects, some$2([...projects, project]));
151630
+ const project = yield* addProject;
151610
151631
  yield* (yield* IssueSource).settings(project.id);
151611
151632
  })), provide(Settings.layer), provide(CurrentIssueSource.layer));
151612
151633
 
@@ -151614,9 +151635,9 @@ const commandProjectsAdd = make$35("add").pipe(withDescription("Add a new projec
151614
151635
  //#region src/commands/projects/rm.ts
151615
151636
  const commandProjectsRm = make$35("rm").pipe(withDescription("Remove a project"), withHandler(fnUntraced(function* () {
151616
151637
  const projects = yield* getAllProjects;
151638
+ if (projects.length === 0) return yield* log$1("There are no projects to remove.");
151617
151639
  const project = yield* selectProject;
151618
151640
  const newProjects = projects.filter((p) => p.id !== project.id);
151619
- if (!isArrayNonEmpty(newProjects)) return yield* log$1("You cannot remove the last remaining project.");
151620
151641
  yield* Settings.set(allProjects, some$2(newProjects));
151621
151642
  })), provide(Settings.layer));
151622
151643
 
@@ -151624,6 +151645,7 @@ const commandProjectsRm = make$35("rm").pipe(withDescription("Remove a project")
151624
151645
  //#region src/commands/projects/edit.ts
151625
151646
  const commandProjectsEdit = make$35("edit").pipe(withDescription("Modify a project"), withHandler(fnUntraced(function* () {
151626
151647
  const projects = yield* getAllProjects;
151648
+ if (projects.length === 0) return yield* log$1("No projects available to edit.");
151627
151649
  const project = yield* selectProject;
151628
151650
  const concurrency = yield* integer$2({
151629
151651
  message: "Concurrency",
@@ -151658,6 +151680,7 @@ const commandProjectsEdit = make$35("edit").pipe(withDescription("Modify a proje
151658
151680
  //#region src/commands/projects/toggle.ts
151659
151681
  const commandProjectsToggle = make$35("toggle").pipe(withDescription("Enable or disable projects"), withHandler(fnUntraced(function* () {
151660
151682
  const projects = yield* getAllProjects;
151683
+ if (projects.length === 0) return yield* log$1("No projects available to toggle.");
151661
151684
  const enabled = yield* multiSelect({
151662
151685
  message: "Select projects to enable",
151663
151686
  choices: projects.map((project) => ({
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lalph",
3
3
  "type": "module",
4
- "version": "0.2.0",
4
+ "version": "0.2.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
package/src/Projects.ts CHANGED
@@ -4,14 +4,15 @@ import {
4
4
  Effect,
5
5
  Layer,
6
6
  Option,
7
+ pipe,
7
8
  PlatformError,
8
9
  Schema,
10
+ String,
9
11
  } from "effect"
10
- import { Project, type ProjectId } from "./domain/Project.ts"
12
+ import { Project, ProjectId } from "./domain/Project.ts"
11
13
  import { AsyncResult, Atom } from "effect/unstable/reactivity"
12
14
  import { CurrentProjectId, Setting, Settings } from "./Settings.ts"
13
15
  import { Prompt } from "effect/unstable/cli"
14
- import type { NonEmptyReadonlyArray } from "effect/Array"
15
16
 
16
17
  export const layerProjectIdPrompt = Layer.effect(
17
18
  CurrentProjectId,
@@ -21,17 +22,10 @@ export const layerProjectIdPrompt = Layer.effect(
21
22
  }),
22
23
  ).pipe(Layer.provide(Settings.layer))
23
24
 
24
- export const allProjects = new Setting(
25
- "projects",
26
- Schema.NonEmptyArray(Project),
27
- )
25
+ export const allProjects = new Setting("projects", Schema.Array(Project))
28
26
 
29
27
  export const getAllProjects = Settings.get(allProjects).pipe(
30
- Effect.map(
31
- Option.getOrElse(
32
- (): NonEmptyReadonlyArray<Project> => [Project.defaultProject],
33
- ),
34
- ),
28
+ Effect.map(Option.getOrElse((): ReadonlyArray<Project> => [])),
35
29
  )
36
30
 
37
31
  export const projectById = Effect.fnUntraced(function* (projectId: ProjectId) {
@@ -44,13 +38,10 @@ export const allProjectsAtom = (function () {
44
38
  Effect.fnUntraced(function* () {
45
39
  const settings = yield* Settings
46
40
  const projects = yield* settings.get(allProjects)
47
- return Option.getOrElse(
48
- projects,
49
- (): Array.NonEmptyReadonlyArray<Project> => [Project.defaultProject],
50
- )
41
+ return Option.getOrElse(projects, (): ReadonlyArray<Project> => [])
51
42
  }),
52
43
  )
53
- const set = Settings.runtime.fn<Array.NonEmptyReadonlyArray<Project>>()(
44
+ const set = Settings.runtime.fn<ReadonlyArray<Project>>()(
54
45
  Effect.fnUntraced(function* (value, get) {
55
46
  const settings = yield* Settings
56
47
  yield* settings.set(allProjects, Option.some(value))
@@ -62,7 +53,7 @@ export const allProjectsAtom = (function () {
62
53
  get.mount(set)
63
54
  return get(read)
64
55
  },
65
- (ctx, value: Array.NonEmptyReadonlyArray<Project>) => {
56
+ (ctx, value: ReadonlyArray<Project>) => {
66
57
  ctx.set(set, value)
67
58
  },
68
59
  (r) => r(read),
@@ -93,7 +84,6 @@ export const projectAtom = Atom.family(
93
84
  onSome: (project) =>
94
85
  Array.map(projects, (p) => (p.id === projectId ? project : p)),
95
86
  })
96
- if (!Array.isArrayNonEmpty(updatedProjects)) return
97
87
  get.set(allProjectsAtom, updatedProjects)
98
88
  }),
99
89
  )
@@ -120,15 +110,82 @@ export class ProjectNotFound extends Data.TaggedError("ProjectNotFound")<{
120
110
 
121
111
  export const selectProject = Effect.gen(function* () {
122
112
  const projects = yield* getAllProjects
123
- if (projects.length === 1) {
124
- yield* Effect.log(`Using project: ${projects[0].id}`)
125
- return projects[0]
113
+ if (projects.length === 0) {
114
+ return yield* welcomeWizard
115
+ } else if (projects.length === 1) {
116
+ const project = projects[0]!
117
+ yield* Effect.log(`Using project: ${project.id}`)
118
+ return project
126
119
  }
127
- return yield* Prompt.autoComplete({
120
+ const selection = yield* Prompt.autoComplete({
128
121
  message: "Select a project:",
129
122
  choices: projects.map((p) => ({
130
123
  title: p.id,
131
124
  value: p,
132
125
  })),
133
126
  })
127
+ return selection!
128
+ })
129
+
130
+ export const welcomeWizard = Effect.gen(function* () {
131
+ const welcome = [
132
+ " .--.",
133
+ " |^()^| lalph",
134
+ " '--'",
135
+ "",
136
+ "Welcome! Let's add your first project.",
137
+ "Projects let you configure how lalph runs tasks and integrations",
138
+ "(like issue filters, concurrency, and git flow).",
139
+ "",
140
+ ].join("\n")
141
+ console.log(welcome)
142
+ return yield* addProject
143
+ })
144
+
145
+ export const addProject = Effect.gen(function* () {
146
+ const projects = yield* getAllProjects
147
+ const id = yield* Prompt.text({
148
+ message: "Name",
149
+ validate(input) {
150
+ input = input.trim()
151
+ if (input.length === 0) {
152
+ return Effect.fail("Project name cannot be empty")
153
+ } else if (projects.some((p) => p.id === input)) {
154
+ return Effect.fail("Project already exists")
155
+ }
156
+ return Effect.succeed(input)
157
+ },
158
+ })
159
+ const concurrency = yield* Prompt.integer({
160
+ message: "Concurrency",
161
+ min: 1,
162
+ })
163
+ const targetBranch = pipe(
164
+ yield* Prompt.text({
165
+ message: "Target branch (leave empty to use HEAD)",
166
+ }),
167
+ String.trim,
168
+ Option.liftPredicate(String.isNonEmpty),
169
+ )
170
+ const gitFlow = yield* Prompt.select({
171
+ message: "Git flow",
172
+ choices: [
173
+ { title: "Pull Request", value: "pr" },
174
+ { title: "Commit", value: "commit" },
175
+ ] as const,
176
+ })
177
+ const reviewAgent = yield* Prompt.toggle({
178
+ message: "Enable review agent?",
179
+ })
180
+
181
+ const project = new Project({
182
+ id: ProjectId.makeUnsafe(id),
183
+ enabled: true,
184
+ concurrency,
185
+ targetBranch,
186
+ gitFlow,
187
+ reviewAgent,
188
+ })
189
+ yield* Settings.set(allProjects, Option.some([...projects, project]))
190
+ return project
134
191
  })
@@ -94,7 +94,9 @@ const handler = flow(
94
94
  }, Effect.scoped),
95
95
  ),
96
96
  Command.provide(
97
- Layer.mergeAll(CurrentIssueSource.layer, layerProjectIdPrompt),
97
+ Layer.mergeAll(CurrentIssueSource.layer).pipe(
98
+ Layer.provideMerge(layerProjectIdPrompt),
99
+ ),
98
100
  ),
99
101
  )
100
102
 
@@ -1,60 +1,15 @@
1
- import { Effect, Option, pipe, String } from "effect"
2
- import { Command, Prompt } from "effect/unstable/cli"
3
- import { allProjects, getAllProjects } from "../../Projects.ts"
4
- import { Settings } from "../../Settings.ts"
5
- import { Project, ProjectId } from "../../domain/Project.ts"
6
- import { IssueSource } from "../../IssueSource.ts"
1
+ import { Effect } from "effect"
2
+ import { Command } from "effect/unstable/cli"
3
+ import { addProject } from "../../Projects.ts"
7
4
  import { CurrentIssueSource } from "../../IssueSources.ts"
5
+ import { IssueSource } from "../../IssueSource.ts"
6
+ import { Settings } from "../../Settings.ts"
8
7
 
9
8
  export const commandProjectsAdd = Command.make("add").pipe(
10
9
  Command.withDescription("Add a new project"),
11
10
  Command.withHandler(
12
11
  Effect.fnUntraced(function* () {
13
- const projects = yield* getAllProjects
14
- const id = yield* Prompt.text({
15
- message: "Name",
16
- validate(input) {
17
- input = input.trim()
18
- if (input.length === 0) {
19
- return Effect.fail("Project name cannot be empty")
20
- } else if (projects.some((p) => p.id === input)) {
21
- return Effect.fail(`Project already exists`)
22
- }
23
- return Effect.succeed(input)
24
- },
25
- })
26
- const concurrency = yield* Prompt.integer({
27
- message: "Concurrency",
28
- min: 1,
29
- })
30
- const targetBranch = pipe(
31
- yield* Prompt.text({
32
- message: "Target branch (leave empty to use HEAD)",
33
- }),
34
- String.trim,
35
- Option.liftPredicate(String.isNonEmpty),
36
- )
37
- const gitFlow = yield* Prompt.select({
38
- message: "Git flow",
39
- choices: [
40
- { title: "Pull Request", value: "pr" },
41
- { title: "Commit", value: "commit" },
42
- ] as const,
43
- })
44
- const reviewAgent = yield* Prompt.toggle({
45
- message: "Enable review agent?",
46
- })
47
-
48
- const project = new Project({
49
- id: ProjectId.makeUnsafe(id),
50
- enabled: true,
51
- concurrency,
52
- targetBranch,
53
- gitFlow,
54
- reviewAgent,
55
- })
56
- yield* Settings.set(allProjects, Option.some([...projects, project]))
57
-
12
+ const project = yield* addProject
58
13
  const source = yield* IssueSource
59
14
  yield* source.settings(project.id)
60
15
  }),
@@ -11,6 +11,9 @@ export const commandProjectsEdit = Command.make("edit").pipe(
11
11
  Command.withHandler(
12
12
  Effect.fnUntraced(function* () {
13
13
  const projects = yield* getAllProjects
14
+ if (projects.length === 0) {
15
+ return yield* Effect.log("No projects available to edit.")
16
+ }
14
17
  const project = yield* selectProject
15
18
  const concurrency = yield* Prompt.integer({
16
19
  message: "Concurrency",
@@ -16,8 +16,15 @@ export const commandProjectsLs = Command.make("ls").pipe(
16
16
 
17
17
  const projects = yield* getAllProjects
18
18
 
19
+ if (projects.length === 0) {
20
+ console.log(
21
+ "No projects configured yet. Run 'lalph projects add' to get started.",
22
+ )
23
+ return
24
+ }
19
25
  for (const project of projects) {
20
26
  console.log(`Project: ${project.id}`)
27
+ console.log(` Enabled: ${project.enabled ? "Yes" : "No"}`)
21
28
  yield* source.info(project.id)
22
29
  console.log(` Concurrency: ${project.concurrency}`)
23
30
  if (Option.isSome(project.targetBranch)) {
@@ -1,4 +1,4 @@
1
- import { Array, Effect, Option } from "effect"
1
+ import { Effect, Option } from "effect"
2
2
  import { Command } from "effect/unstable/cli"
3
3
  import { allProjects, getAllProjects, selectProject } from "../../Projects.ts"
4
4
  import { Settings } from "../../Settings.ts"
@@ -8,13 +8,11 @@ export const commandProjectsRm = Command.make("rm").pipe(
8
8
  Command.withHandler(
9
9
  Effect.fnUntraced(function* () {
10
10
  const projects = yield* getAllProjects
11
+ if (projects.length === 0) {
12
+ return yield* Effect.log("There are no projects to remove.")
13
+ }
11
14
  const project = yield* selectProject
12
15
  const newProjects = projects.filter((p) => p.id !== project.id)
13
- if (!Array.isArrayNonEmpty(newProjects)) {
14
- return yield* Effect.log(
15
- "You cannot remove the last remaining project.",
16
- )
17
- }
18
16
  yield* Settings.set(allProjects, Option.some(newProjects))
19
17
  }),
20
18
  ),
@@ -9,6 +9,9 @@ export const commandProjectsToggle = Command.make("toggle").pipe(
9
9
  Command.withHandler(
10
10
  Effect.fnUntraced(function* () {
11
11
  const projects = yield* getAllProjects
12
+ if (projects.length === 0) {
13
+ return yield* Effect.log("No projects available to toggle.")
14
+ }
12
15
  const enabled = yield* Prompt.multiSelect({
13
16
  message: "Select projects to enable",
14
17
  choices: projects.map((project) => ({
@@ -39,7 +39,7 @@ import {
39
39
  import { WorkerStatus } from "../domain/WorkerState.ts"
40
40
  import { GitFlow, GitFlowCommit, GitFlowPR } from "../GitFlow.ts"
41
41
  import { parseBranch } from "../shared/git.ts"
42
- import { getAllProjects } from "../Projects.ts"
42
+ import { getAllProjects, welcomeWizard } from "../Projects.ts"
43
43
  import type { Project } from "../domain/Project.ts"
44
44
 
45
45
  // Main iteration run logic
@@ -384,7 +384,19 @@ export const commandRoot = Command.make("lalph", {
384
384
  }) {
385
385
  const commandPrefix = yield* getCommandPrefix
386
386
  yield* getOrSelectCliAgent
387
- const projects = (yield* getAllProjects).filter((p) => p.enabled)
387
+
388
+ let allProjects = yield* getAllProjects
389
+ if (allProjects.length === 0) {
390
+ yield* welcomeWizard
391
+ allProjects = yield* getAllProjects
392
+ }
393
+
394
+ const projects = allProjects.filter((p) => p.enabled)
395
+ if (projects.length === 0) {
396
+ return yield* Effect.log(
397
+ "No enabled projects found. Run 'lalph projects toggle' to enable one.",
398
+ )
399
+ }
388
400
  yield* Effect.forEach(
389
401
  projects,
390
402
  (project) =>
@@ -1,4 +1,4 @@
1
- import { Option, Schema } from "effect"
1
+ import { Schema } from "effect"
2
2
 
3
3
  export const ProjectId = Schema.String.pipe(Schema.brand("lalph/ProjectId"))
4
4
  export type ProjectId = typeof ProjectId.Type
@@ -10,13 +10,4 @@ export class Project extends Schema.Class<Project>("lalph/Project")({
10
10
  concurrency: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)),
11
11
  gitFlow: Schema.Literals(["pr", "commit"]),
12
12
  reviewAgent: Schema.Boolean,
13
- }) {
14
- static defaultProject = new Project({
15
- id: ProjectId.makeUnsafe("default"),
16
- enabled: true,
17
- targetBranch: Option.none(),
18
- concurrency: 1,
19
- gitFlow: "pr",
20
- reviewAgent: true,
21
- })
22
- }
13
+ }) {}