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.
- package/LICENSE +21 -0
- package/README.md +28 -0
- package/dist/cli.mjs +132396 -0
- package/package.json +32 -0
- package/src/Kvs.ts +3 -0
- package/src/Linear/TokenManager.ts +183 -0
- package/src/Linear.ts +149 -0
- package/src/Prd.ts +188 -0
- package/src/PromptGen.ts +64 -0
- package/src/Runner.ts +52 -0
- package/src/Settings.ts +70 -0
- package/src/cli.ts +69 -0
- package/src/domain/CliAgent.ts +43 -0
package/src/Settings.ts
ADDED
|
@@ -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]
|