lalph 0.0.0

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.
@@ -0,0 +1,70 @@
1
+ import { Cache, Effect, Layer, Option, Schema, ServiceMap } from "effect"
2
+ import { KeyValueStore } from "effect/unstable/persistence"
3
+ import { layerKvs } from "./Kvs.ts"
4
+ import { allCliAgents } from "./domain/CliAgent.ts"
5
+
6
+ export class Settings extends ServiceMap.Service<Settings>()("lalph/Settings", {
7
+ make: Effect.gen(function* () {
8
+ const store = KeyValueStore.prefix(
9
+ yield* KeyValueStore.KeyValueStore,
10
+ "settings.",
11
+ )
12
+
13
+ const cache = yield* Cache.make({
14
+ lookup(setting: Setting<string, Schema.Codec<any, any>>) {
15
+ const s = KeyValueStore.toSchemaStore(store, setting.schema)
16
+ return Effect.orDie(s.get(setting.name))
17
+ },
18
+ capacity: Number.MAX_SAFE_INTEGER,
19
+ })
20
+
21
+ const get = <S extends Schema.Codec<any, any>>(
22
+ setting: Setting<string, S>,
23
+ ): Effect.Effect<Option.Option<S["Type"]>, never, S["DecodingServices"]> =>
24
+ Cache.get(cache, setting)
25
+
26
+ const set = <S extends Schema.Codec<any, any>>(
27
+ setting: Setting<string, S>,
28
+ value: Option.Option<S["Type"]>,
29
+ ): Effect.Effect<void, never, S["EncodingServices"]> => {
30
+ const s = KeyValueStore.toSchemaStore(store, setting.schema)
31
+ const setCache = Cache.set(cache, setting, value)
32
+ const update = Option.match(value, {
33
+ onNone: () => Effect.ignore(s.remove(setting.name)),
34
+ onSome: (v) => Effect.orDie(s.set(setting.name, v)),
35
+ })
36
+ return Effect.andThen(update, setCache)
37
+ }
38
+
39
+ return { get, set } as const
40
+ }),
41
+ }) {
42
+ static layer = Layer.effect(this, this.make).pipe(Layer.provide(layerKvs))
43
+ }
44
+
45
+ export class Setting<
46
+ const Name extends string,
47
+ S extends Schema.Codec<any, any>,
48
+ > {
49
+ readonly name: Name
50
+ readonly schema: S
51
+ constructor(name: Name, schema: S) {
52
+ this.name = name
53
+ this.schema = schema
54
+ }
55
+
56
+ get = Settings.use((s) => s.get(this))
57
+
58
+ set(value: Option.Option<S["Type"]>) {
59
+ return Settings.use((s) => s.set(this, value))
60
+ }
61
+ }
62
+
63
+ export const selectedTeamId = new Setting("selectedTeamId", Schema.String)
64
+
65
+ export const selectedLabelId = new Setting("selectedLabelId", Schema.String)
66
+
67
+ export const selectedCliAgentId = new Setting(
68
+ "selectedCliAgentId",
69
+ Schema.Literals(allCliAgents.map((a) => a.id)),
70
+ )
package/src/cli.ts ADDED
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command, Flag } from "effect/unstable/cli"
4
+ import { Effect, Layer, Option } from "effect"
5
+ import { NodeRuntime, NodeServices } from "@effect/platform-node"
6
+ import { CurrentProject, labelSelect, Linear } from "./Linear.ts"
7
+ import { layerKvs } from "./Kvs.ts"
8
+ import { Settings } from "./Settings.ts"
9
+ import { run, selectCliAgent } from "./Runner.ts"
10
+
11
+ const selectProject = Command.make("select-project").pipe(
12
+ Command.withDescription("Select the current Linear project"),
13
+ Command.withHandler(
14
+ Effect.fnUntraced(
15
+ function* () {
16
+ const project = yield* CurrentProject.select
17
+ yield* Effect.log(
18
+ `Selected Linear Project: ${project.name} (${project.id})`,
19
+ )
20
+ },
21
+ Effect.provide([layerKvs, Linear.layer]),
22
+ ),
23
+ ),
24
+ )
25
+
26
+ const selectLabel = Command.make("select-label").pipe(
27
+ Command.withDescription("Select the label to filter issues by"),
28
+ Command.withHandler(
29
+ Effect.fnUntraced(function* () {
30
+ const label = yield* labelSelect
31
+ yield* Effect.log(
32
+ `Selected Label: ${Option.match(label, {
33
+ onNone: () => "No Label",
34
+ onSome: (l) => l.name,
35
+ })}`,
36
+ )
37
+ }, Effect.provide(Linear.layer)),
38
+ ),
39
+ )
40
+
41
+ const selectAgent = Command.make("select-agent").pipe(
42
+ Command.withDescription("Select the CLI agent to use"),
43
+ Command.withHandler(() => selectCliAgent),
44
+ )
45
+
46
+ const iterations = Flag.integer("iterations").pipe(
47
+ Flag.withAlias("i"),
48
+ Flag.withDefault(1),
49
+ )
50
+
51
+ const root = Command.make("lalph", { iterations }).pipe(
52
+ Command.withHandler(
53
+ Effect.fnUntraced(function* ({ iterations }) {
54
+ yield* Effect.log(`Executing ${iterations} iteration(s)`)
55
+
56
+ for (let i = 0; i < iterations; i++) {
57
+ yield* run
58
+ }
59
+ }),
60
+ ),
61
+ Command.withSubcommands([selectProject, selectLabel, selectAgent]),
62
+ )
63
+
64
+ Command.run(root, {
65
+ version: "0.1.0",
66
+ }).pipe(
67
+ Effect.provide(Settings.layer.pipe(Layer.provideMerge(NodeServices.layer))),
68
+ NodeRuntime.runMain,
69
+ )
@@ -0,0 +1,43 @@
1
+ import { Data } from "effect"
2
+
3
+ export class CliAgent extends Data.Class<{
4
+ id: string
5
+ name: string
6
+ command: (options: {
7
+ readonly prompt: string
8
+ readonly prdFilePath: string
9
+ readonly progressFilePath: string
10
+ }) => ReadonlyArray<string>
11
+ }> {}
12
+
13
+ export const opencode = new CliAgent({
14
+ id: "opencode",
15
+ name: "opencode",
16
+ command: ({ prompt, prdFilePath, progressFilePath }) => [
17
+ "npx",
18
+ "-y",
19
+ "opencode-ai@latest",
20
+ "run",
21
+ prompt,
22
+ "-f",
23
+ prdFilePath,
24
+ "-f",
25
+ progressFilePath,
26
+ ],
27
+ })
28
+
29
+ export const claude = new CliAgent({
30
+ id: "claude",
31
+ name: "Claude Code",
32
+ command: ({ prompt, prdFilePath, progressFilePath }) => [
33
+ "npx",
34
+ "-y",
35
+ "@anthropic-ai/claude-code@latest",
36
+ "-p",
37
+ `@${prdFilePath} @${progressFilePath}
38
+
39
+ ${prompt}`,
40
+ ],
41
+ })
42
+
43
+ export const allCliAgents = [opencode, claude]