|
|
@@ -110,6 +110,8 @@ async function writeConfig(dir: string, config: object, name = "opencode.json")
|
|
|
|
|
|
const writeConfigEffect = (dir: string, config: object, name = "opencode.json") =>
|
|
|
Effect.promise(() => writeConfig(dir, config, name))
|
|
|
+const mkdirEffect = (dir: string) => Effect.promise(() => fs.mkdir(dir, { recursive: true }))
|
|
|
+const writeTextEffect = (file: string, content: string) => Effect.promise(() => Filesystem.write(file, content))
|
|
|
|
|
|
function withProcessEnv<A, E, R>(key: string, value: string, effect: Effect.Effect<A, E, R>) {
|
|
|
return Effect.acquireUseRelease(
|
|
|
@@ -671,205 +673,156 @@ it.instance("migrates mode field to agent field", () =>
|
|
|
}),
|
|
|
)
|
|
|
|
|
|
-test("loads config from .opencode directory", async () => {
|
|
|
- await using tmp = await tmpdir({
|
|
|
- init: async (dir) => {
|
|
|
- const opencodeDir = path.join(dir, ".opencode")
|
|
|
- await fs.mkdir(opencodeDir, { recursive: true })
|
|
|
- const agentDir = path.join(opencodeDir, "agent")
|
|
|
- await fs.mkdir(agentDir, { recursive: true })
|
|
|
-
|
|
|
- await Filesystem.write(
|
|
|
- path.join(agentDir, "test.md"),
|
|
|
- `---
|
|
|
+it.instance("loads config from .opencode directory", () =>
|
|
|
+ Effect.gen(function* () {
|
|
|
+ const test = yield* TestInstance
|
|
|
+ yield* mkdirEffect(path.join(test.directory, ".opencode", "agent"))
|
|
|
+ yield* writeTextEffect(
|
|
|
+ path.join(test.directory, ".opencode", "agent", "test.md"),
|
|
|
+ `---
|
|
|
model: test/model
|
|
|
---
|
|
|
Test agent prompt`,
|
|
|
- )
|
|
|
- },
|
|
|
- })
|
|
|
- await withTestInstance({
|
|
|
- directory: tmp.path,
|
|
|
- fn: async (ctx) => {
|
|
|
- const config = await load(ctx)
|
|
|
- expect(config.agent?.["test"]).toEqual(
|
|
|
- expect.objectContaining({
|
|
|
- name: "test",
|
|
|
- model: "test/model",
|
|
|
- prompt: "Test agent prompt",
|
|
|
- }),
|
|
|
- )
|
|
|
- },
|
|
|
- })
|
|
|
-})
|
|
|
+ )
|
|
|
|
|
|
-test("agent markdown permission config preserves user key order", async () => {
|
|
|
- await using tmp = await tmpdir({
|
|
|
- init: async (dir) => {
|
|
|
- const agentDir = path.join(dir, ".opencode", "agent")
|
|
|
- await fs.mkdir(agentDir, { recursive: true })
|
|
|
+ const config = yield* Config.Service.use((svc) => svc.get())
|
|
|
+ expect(config.agent?.["test"]).toEqual(
|
|
|
+ expect.objectContaining({
|
|
|
+ name: "test",
|
|
|
+ model: "test/model",
|
|
|
+ prompt: "Test agent prompt",
|
|
|
+ }),
|
|
|
+ )
|
|
|
+ }),
|
|
|
+)
|
|
|
|
|
|
- await Filesystem.write(
|
|
|
- path.join(agentDir, "ordered.md"),
|
|
|
- `---
|
|
|
+it.instance("agent markdown permission config preserves user key order", () =>
|
|
|
+ Effect.gen(function* () {
|
|
|
+ const test = yield* TestInstance
|
|
|
+ yield* mkdirEffect(path.join(test.directory, ".opencode", "agent"))
|
|
|
+ yield* writeTextEffect(
|
|
|
+ path.join(test.directory, ".opencode", "agent", "ordered.md"),
|
|
|
+ `---
|
|
|
permission:
|
|
|
bash: allow
|
|
|
"*": deny
|
|
|
edit: ask
|
|
|
---
|
|
|
Ordered permissions`,
|
|
|
- )
|
|
|
- },
|
|
|
- })
|
|
|
- await withTestInstance({
|
|
|
- directory: tmp.path,
|
|
|
- fn: async (ctx) => {
|
|
|
- const config = await load(ctx)
|
|
|
- expect(Object.keys(config.agent?.ordered?.permission ?? {})).toEqual(["bash", "*", "edit"])
|
|
|
- },
|
|
|
- })
|
|
|
-})
|
|
|
-
|
|
|
-test("loads agents from .opencode/agents (plural)", async () => {
|
|
|
- await using tmp = await tmpdir({
|
|
|
- init: async (dir) => {
|
|
|
- const opencodeDir = path.join(dir, ".opencode")
|
|
|
- await fs.mkdir(opencodeDir, { recursive: true })
|
|
|
+ )
|
|
|
|
|
|
- const agentsDir = path.join(opencodeDir, "agents")
|
|
|
- await fs.mkdir(path.join(agentsDir, "nested"), { recursive: true })
|
|
|
+ const config = yield* Config.Service.use((svc) => svc.get())
|
|
|
+ expect(Object.keys(config.agent?.ordered?.permission ?? {})).toEqual(["bash", "*", "edit"])
|
|
|
+ }),
|
|
|
+)
|
|
|
|
|
|
- await Filesystem.write(
|
|
|
- path.join(agentsDir, "helper.md"),
|
|
|
- `---
|
|
|
+it.instance("loads agents from .opencode/agents (plural)", () =>
|
|
|
+ Effect.gen(function* () {
|
|
|
+ const test = yield* TestInstance
|
|
|
+ yield* mkdirEffect(path.join(test.directory, ".opencode", "agents", "nested"))
|
|
|
+ yield* writeTextEffect(
|
|
|
+ path.join(test.directory, ".opencode", "agents", "helper.md"),
|
|
|
+ `---
|
|
|
model: test/model
|
|
|
mode: subagent
|
|
|
---
|
|
|
Helper agent prompt`,
|
|
|
- )
|
|
|
+ )
|
|
|
|
|
|
- await Filesystem.write(
|
|
|
- path.join(agentsDir, "nested", "child.md"),
|
|
|
- `---
|
|
|
+ yield* writeTextEffect(
|
|
|
+ path.join(test.directory, ".opencode", "agents", "nested", "child.md"),
|
|
|
+ `---
|
|
|
model: test/model
|
|
|
mode: subagent
|
|
|
---
|
|
|
Nested agent prompt`,
|
|
|
- )
|
|
|
- },
|
|
|
- })
|
|
|
-
|
|
|
- await withTestInstance({
|
|
|
- directory: tmp.path,
|
|
|
- fn: async (ctx) => {
|
|
|
- const config = await load(ctx)
|
|
|
-
|
|
|
- expect(config.agent?.["helper"]).toMatchObject({
|
|
|
- name: "helper",
|
|
|
- model: "test/model",
|
|
|
- mode: "subagent",
|
|
|
- prompt: "Helper agent prompt",
|
|
|
- })
|
|
|
+ )
|
|
|
|
|
|
- expect(config.agent?.["nested/child"]).toMatchObject({
|
|
|
- name: "nested/child",
|
|
|
- model: "test/model",
|
|
|
- mode: "subagent",
|
|
|
- prompt: "Nested agent prompt",
|
|
|
- })
|
|
|
- },
|
|
|
- })
|
|
|
-})
|
|
|
+ const config = yield* Config.Service.use((svc) => svc.get())
|
|
|
|
|
|
-test("loads commands from .opencode/command (singular)", async () => {
|
|
|
- await using tmp = await tmpdir({
|
|
|
- init: async (dir) => {
|
|
|
- const opencodeDir = path.join(dir, ".opencode")
|
|
|
- await fs.mkdir(opencodeDir, { recursive: true })
|
|
|
+ expect(config.agent?.["helper"]).toMatchObject({
|
|
|
+ name: "helper",
|
|
|
+ model: "test/model",
|
|
|
+ mode: "subagent",
|
|
|
+ prompt: "Helper agent prompt",
|
|
|
+ })
|
|
|
|
|
|
- const commandDir = path.join(opencodeDir, "command")
|
|
|
- await fs.mkdir(path.join(commandDir, "nested"), { recursive: true })
|
|
|
+ expect(config.agent?.["nested/child"]).toMatchObject({
|
|
|
+ name: "nested/child",
|
|
|
+ model: "test/model",
|
|
|
+ mode: "subagent",
|
|
|
+ prompt: "Nested agent prompt",
|
|
|
+ })
|
|
|
+ }),
|
|
|
+)
|
|
|
|
|
|
- await Filesystem.write(
|
|
|
- path.join(commandDir, "hello.md"),
|
|
|
- `---
|
|
|
+it.instance("loads commands from .opencode/command (singular)", () =>
|
|
|
+ Effect.gen(function* () {
|
|
|
+ const test = yield* TestInstance
|
|
|
+ yield* mkdirEffect(path.join(test.directory, ".opencode", "command", "nested"))
|
|
|
+ yield* writeTextEffect(
|
|
|
+ path.join(test.directory, ".opencode", "command", "hello.md"),
|
|
|
+ `---
|
|
|
description: Test command
|
|
|
---
|
|
|
Hello from singular command`,
|
|
|
- )
|
|
|
+ )
|
|
|
|
|
|
- await Filesystem.write(
|
|
|
- path.join(commandDir, "nested", "child.md"),
|
|
|
- `---
|
|
|
+ yield* writeTextEffect(
|
|
|
+ path.join(test.directory, ".opencode", "command", "nested", "child.md"),
|
|
|
+ `---
|
|
|
description: Nested command
|
|
|
---
|
|
|
Nested command template`,
|
|
|
- )
|
|
|
- },
|
|
|
- })
|
|
|
-
|
|
|
- await withTestInstance({
|
|
|
- directory: tmp.path,
|
|
|
- fn: async (ctx) => {
|
|
|
- const config = await load(ctx)
|
|
|
-
|
|
|
- expect(config.command?.["hello"]).toEqual({
|
|
|
- description: "Test command",
|
|
|
- template: "Hello from singular command",
|
|
|
- })
|
|
|
+ )
|
|
|
|
|
|
- expect(config.command?.["nested/child"]).toEqual({
|
|
|
- description: "Nested command",
|
|
|
- template: "Nested command template",
|
|
|
- })
|
|
|
- },
|
|
|
- })
|
|
|
-})
|
|
|
+ const config = yield* Config.Service.use((svc) => svc.get())
|
|
|
|
|
|
-test("loads commands from .opencode/commands (plural)", async () => {
|
|
|
- await using tmp = await tmpdir({
|
|
|
- init: async (dir) => {
|
|
|
- const opencodeDir = path.join(dir, ".opencode")
|
|
|
- await fs.mkdir(opencodeDir, { recursive: true })
|
|
|
+ expect(config.command?.["hello"]).toEqual({
|
|
|
+ description: "Test command",
|
|
|
+ template: "Hello from singular command",
|
|
|
+ })
|
|
|
|
|
|
- const commandsDir = path.join(opencodeDir, "commands")
|
|
|
- await fs.mkdir(path.join(commandsDir, "nested"), { recursive: true })
|
|
|
+ expect(config.command?.["nested/child"]).toEqual({
|
|
|
+ description: "Nested command",
|
|
|
+ template: "Nested command template",
|
|
|
+ })
|
|
|
+ }),
|
|
|
+)
|
|
|
|
|
|
- await Filesystem.write(
|
|
|
- path.join(commandsDir, "hello.md"),
|
|
|
- `---
|
|
|
+it.instance("loads commands from .opencode/commands (plural)", () =>
|
|
|
+ Effect.gen(function* () {
|
|
|
+ const test = yield* TestInstance
|
|
|
+ yield* mkdirEffect(path.join(test.directory, ".opencode", "commands", "nested"))
|
|
|
+ yield* writeTextEffect(
|
|
|
+ path.join(test.directory, ".opencode", "commands", "hello.md"),
|
|
|
+ `---
|
|
|
description: Test command
|
|
|
---
|
|
|
Hello from plural commands`,
|
|
|
- )
|
|
|
+ )
|
|
|
|
|
|
- await Filesystem.write(
|
|
|
- path.join(commandsDir, "nested", "child.md"),
|
|
|
- `---
|
|
|
+ yield* writeTextEffect(
|
|
|
+ path.join(test.directory, ".opencode", "commands", "nested", "child.md"),
|
|
|
+ `---
|
|
|
description: Nested command
|
|
|
---
|
|
|
Nested command template`,
|
|
|
- )
|
|
|
- },
|
|
|
- })
|
|
|
+ )
|
|
|
|
|
|
- await withTestInstance({
|
|
|
- directory: tmp.path,
|
|
|
- fn: async (ctx) => {
|
|
|
- const config = await load(ctx)
|
|
|
+ const config = yield* Config.Service.use((svc) => svc.get())
|
|
|
|
|
|
- expect(config.command?.["hello"]).toEqual({
|
|
|
- description: "Test command",
|
|
|
- template: "Hello from plural commands",
|
|
|
- })
|
|
|
+ expect(config.command?.["hello"]).toEqual({
|
|
|
+ description: "Test command",
|
|
|
+ template: "Hello from plural commands",
|
|
|
+ })
|
|
|
|
|
|
- expect(config.command?.["nested/child"]).toEqual({
|
|
|
- description: "Nested command",
|
|
|
- template: "Nested command template",
|
|
|
- })
|
|
|
- },
|
|
|
- })
|
|
|
-})
|
|
|
+ expect(config.command?.["nested/child"]).toEqual({
|
|
|
+ description: "Nested command",
|
|
|
+ template: "Nested command template",
|
|
|
+ })
|
|
|
+ }),
|
|
|
+)
|
|
|
|
|
|
it.instance("updates config and writes to file", () =>
|
|
|
Effect.gen(function* () {
|