oy-cli 0.14.8

Focused coding agent integrations with repeatable audits, reviews, and one-finding fixes
Documentation
import assert from "node:assert/strict"
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs"
import { dirname, join } from "node:path"
import test from "node:test"
import { fileURLToPath, pathToFileURL } from "node:url"

import plugin, {
  applyCursorEnvironmentDefaults,
  createPlugin,
  defaultCursorStallMs,
  isGeneratedCursorRule,
  removeGeneratedCursorRule,
} from "../index.js"

const fakeBridge = async ({ onModels }) => ({
  url: "http://127.0.0.1:12345/v1",
  token: "test-token",
  current: { onModels },
  close() {},
})

const createEventStream = () => {
  const queued = []
  const waiting = []
  let closed = false
  return {
    push(event) {
      const resolve = waiting.shift()
      if (resolve) resolve(event)
      else queued.push(event)
    },
    subscribe({ signal }) {
      return {
        async *[Symbol.asyncIterator]() {
          signal.addEventListener(
            "abort",
            () => {
              closed = true
              for (const resolve of waiting.splice(0)) resolve(undefined)
            },
            { once: true },
          )
          while (!closed && !signal.aborted) {
            const event =
              queued.shift() ??
              (await new Promise((resolve) => {
                waiting.push(resolve)
              }))
            if (event === undefined) return
            yield event
          }
        },
      }
    },
  }
}

const createHarness = ({ onReload } = {}) => {
  const sources = []
  const agents = new Map()
  const commands = new Map()
  const integrations = new Map()
  const methods = []
  const providers = new Map()
  const models = new Map()
  const hooks = new Map()
  const events = createEventStream()
  let connection
  let credential
  let catalogTransform
  let reloads = 0
  const update = (entries) => (name, apply) => {
    const draft = {}
    apply(draft)
    entries.set(name, draft)
  }
  const catalog = {
    provider: { update: update(providers) },
    model: { update: (provider, model, mutate) => update(models)(`${provider}/${model}`, mutate) },
  }
  const replayCatalog = async () => {
    providers.clear()
    models.clear()
    await catalogTransform(catalog)
  }
  const ctx = {
    options: {},
    event: { subscribe: events.subscribe },
    skill: {
      transform: async (apply) => apply({ source: (source) => sources.push(source) }),
    },
    agent: {
      transform: async (apply) => apply({ update: update(agents) }),
    },
    command: {
      transform: async (apply) => apply({ update: update(commands) }),
    },
    integration: {
      transform: async (apply) =>
        apply({
          update: update(integrations),
          method: { update: (method) => methods.push(method) },
        }),
      connection: {
        active: async () => connection,
        resolve: async () => credential,
      },
    },
    catalog: {
      model: {
        list: async () => ({ location: { directory: process.cwd() }, data: [] }),
      },
      reload: async () => {
        reloads += 1
        await replayCatalog()
        onReload?.()
      },
      transform: async (apply) => {
        catalogTransform = apply
        await replayCatalog()
      },
    },
    aisdk: {
      sdk: async (hook) => hooks.set("sdk", hook),
      hook: async (name, hook) => hooks.set(name, hook),
    },
  }
  return {
    agents,
    commands,
    ctx,
    events,
    hooks,
    integrations,
    methods,
    models,
    providers,
    reloads: () => reloads,
    setConnection(value, resolved) {
      connection = value
      credential = resolved
    },
    sources,
  }
}

test("registers the oy agent, skills, commands, and Cursor provider", async () => {
  const harness = createHarness()
  const environment = {}
  const deterministicPlugin = createPlugin({
    createCursor: () => ({ languageModel: () => ({}) }),
    environment,
    reportError: assert.fail,
    startCursorBridge: fakeBridge,
  })
  const cleanup = await deterministicPlugin.setup(harness.ctx)

  assert.equal(plugin.id, "oy")
  assert.equal(harness.sources.length, 1)
  assert.equal(harness.sources[0].type, "directory")
  assert.match(harness.sources[0].path, /assets[/\\]skills$/)
  assert.equal(harness.agents.get("oy").mode, "primary")
  assert.match(harness.agents.get("oy").system, /OpenCode and the user own permissions/)
  assert.deepEqual([...harness.commands.keys()], ["oy-audit", "oy-review", "oy-enhance"])
  assert.equal(harness.commands.get("oy-audit").agent, "oy")
  assert.equal(harness.integrations.get("cursor").name, "Cursor")
  assert.deepEqual(
    harness.methods.map((entry) => entry.method),
    [
      { type: "key", label: "Cursor API key" },
      { type: "env", names: ["CURSOR_API_KEY"] },
    ],
  )
  assert.deepEqual(
    harness.methods.map((entry) => entry.integrationID),
    ["cursor", "cursor"],
  )
  assert.deepEqual(harness.providers.get("cursor").api, {
    type: "aisdk",
    package: "@ai-sdk/openai",
    url: "http://127.0.0.1:12345/v1",
  })
  assert.equal(harness.providers.get("cursor").settings.baseURL, "http://127.0.0.1:12345/v1")
  assert.equal(harness.providers.get("cursor").settings.headers["x-oy-cursor-bridge"], "test-token")
  assert.deepEqual(harness.models.get("cursor/composer-2.5").api, {
    id: "composer-2.5",
    type: "aisdk",
    package: "@ai-sdk/openai",
    url: "http://127.0.0.1:12345/v1",
  })
  assert.deepEqual(
    harness.models.get("cursor/composer-2.5").variants.map((variant) => variant.id),
    ["off", "on", "plan"],
  )
  assert.equal(defaultCursorStallMs, 120_000)
  assert.equal(environment.OPENCODE_CURSOR_STALL_MS, String(defaultCursorStallMs))
  await cleanup()
})

test("preserves an explicit Cursor stall timeout", () => {
  const environment = { OPENCODE_CURSOR_STALL_MS: "0" }
  applyCursorEnvironmentDefaults(environment)
  assert.equal(environment.OPENCODE_CURSOR_STALL_MS, "0")
})

test("refreshes fallback models after a Cursor connection update", async () => {
  let resolveReload
  const reloaded = new Promise((resolve) => {
    resolveReload = resolve
  })
  const harness = createHarness({ onReload: resolveReload })
  const apiKeys = []
  const deterministicPlugin = createPlugin({
    createCursor: () => ({ languageModel: () => ({}) }),
    listCursorModels: async (apiKey) => {
      apiKeys.push(apiKey)
      return [{ id: "live-model", displayName: "Live Cursor model" }]
    },
    reportError: assert.fail,
    startCursorBridge: fakeBridge,
  })
  const cleanup = await deterministicPlugin.setup(harness.ctx)
  assert.equal(harness.models.has("cursor/composer-2.5"), true)
  await new Promise((resolve) => setImmediate(resolve))

  harness.setConnection({ id: "cursor-connection" }, { type: "key", key: "secret" })
  harness.events.push({
    type: "integration.connection.updated",
    data: { integrationID: "cursor" },
  })
  await reloaded

  for (let attempt = 0; attempt < 20 && apiKeys.length === 0; attempt += 1) {
    await new Promise((resolve) => setImmediate(resolve))
  }
  assert.deepEqual(apiKeys, ["secret"])
  assert.equal(harness.reloads(), 2)
  assert.equal(harness.models.has("cursor/composer-2.5"), false)
  assert.equal(harness.models.get("cursor/live-model").name, "Live Cursor model")
  await cleanup()
})

test("reruns model discovery with credentials updated during an active refresh", async () => {
  let resolveOld
  const oldModels = new Promise((resolve) => {
    resolveOld = resolve
  })
  let resolveNewReload
  const newReloaded = new Promise((resolve) => {
    resolveNewReload = resolve
  })
  const harness = createHarness({
    onReload: () => {
      if (harness.models.has("cursor/new-model")) resolveNewReload()
    },
  })
  const apiKeys = []
  const deterministicPlugin = createPlugin({
    createCursor: () => ({ languageModel: () => ({}) }),
    listCursorModels: async (apiKey) => {
      apiKeys.push(apiKey)
      if (apiKey === "old-key") return oldModels
      return [{ id: "new-model", displayName: "New credential model" }]
    },
    reportError: assert.fail,
    startCursorBridge: fakeBridge,
  })
  const cleanup = await deterministicPlugin.setup(harness.ctx)
  await new Promise((resolve) => setImmediate(resolve))

  harness.setConnection({ id: "cursor-connection" }, { type: "key", key: "old-key" })
  harness.events.push({
    type: "integration.connection.updated",
    data: { integrationID: "cursor" },
  })
  for (let attempt = 0; attempt < 20 && apiKeys.length === 0; attempt += 1) {
    await new Promise((resolve) => setImmediate(resolve))
  }

  harness.setConnection({ id: "cursor-connection" }, { type: "key", key: "new-key" })
  harness.events.push({
    type: "integration.connection.updated",
    data: { integrationID: "cursor" },
  })
  await new Promise((resolve) => setImmediate(resolve))
  resolveOld([{ id: "old-model", displayName: "Stale credential model" }])
  await newReloaded

  assert.deepEqual(apiKeys, ["old-key", "new-key"])
  assert.equal(harness.models.has("cursor/old-model"), false)
  assert.equal(harness.models.get("cursor/new-model").name, "New credential model")
  await cleanup()
})

test("loads without the legacy event subscription surface", async () => {
  const harness = createHarness()
  delete harness.ctx.event
  const deterministicPlugin = createPlugin({
    createCursor: () => ({ languageModel: () => ({}) }),
    reportError: assert.fail,
    startCursorBridge: fakeBridge,
  })

  const cleanup = await deterministicPlugin.setup(harness.ctx)

  assert.equal(harness.models.has("cursor/composer-2.5"), true)
  await cleanup()
})

test("removes only Cursor's generated rule", () => {
  const directory = mkdtempSync(join(process.cwd(), ".cursor-rule-test-"))
  const path = join(directory, ".cursor", "rules", "opencode.mdc")
  mkdirSync(dirname(path), { recursive: true })
  try {
    const generated = "---\nalwaysApply: true\ngenerated: opencode-cursor\n---\n\nGenerated\n"
    writeFileSync(path, generated)
    assert.equal(isGeneratedCursorRule(generated), true)
    removeGeneratedCursorRule(directory)
    assert.equal(existsSync(path), false)

    const userRule = "---\nalwaysApply: true\n---\n\nMentions generated: opencode-cursor\n"
    writeFileSync(path, userRule)
    assert.equal(isGeneratedCursorRule(userRule), false)
    removeGeneratedCursorRule(directory)
    assert.equal(readFileSync(path, "utf8"), userRule)
  } finally {
    rmSync(directory, { recursive: true, force: true })
  }
})

test("refuses symlinked Cursor rule ancestors during cleanup", () => {
  const generated = "---\nalwaysApply: true\ngenerated: opencode-cursor\n---\n\nGenerated\n"
  const cases = [
    { link: [".cursor"], escaped: ["rules", "opencode.mdc"] },
    { link: [".cursor", "rules"], escaped: ["opencode.mdc"] },
  ]
  for (const scenario of cases) {
    const directory = mkdtempSync(join(process.cwd(), ".cursor-rule-boundary-test-"))
    const outside = mkdtempSync(join(process.cwd(), ".cursor-rule-outside-test-"))
    const link = join(directory, ...scenario.link)
    const escaped = join(outside, ...scenario.escaped)
    mkdirSync(dirname(link), { recursive: true })
    mkdirSync(dirname(escaped), { recursive: true })
    writeFileSync(escaped, generated)
    symlinkSync(outside, link, "dir")
    try {
      removeGeneratedCursorRule(directory)
      assert.equal(readFileSync(escaped, "utf8"), generated)
    } finally {
      rmSync(directory, { recursive: true, force: true })
      rmSync(outside, { recursive: true, force: true })
    }
  }
})

test("refuses a symlinked generated Cursor rule", () => {
  const directory = mkdtempSync(join(process.cwd(), ".cursor-rule-link-test-"))
  const outside = mkdtempSync(join(process.cwd(), ".cursor-rule-link-outside-test-"))
  const path = join(directory, ".cursor", "rules", "opencode.mdc")
  const escaped = join(outside, "opencode.mdc")
  mkdirSync(dirname(path), { recursive: true })
  writeFileSync(escaped, "---\ngenerated: opencode-cursor\n---\n")
  symlinkSync(escaped, path)
  try {
    removeGeneratedCursorRule(directory)
    assert.equal(existsSync(path), true)
  } finally {
    rmSync(directory, { recursive: true, force: true })
    rmSync(outside, { recursive: true, force: true })
  }
})

test("installed plugin layout resolves packaged assets and modules", async () => {
  const packageDirectory = fileURLToPath(new URL("../", import.meta.url))
  const directory = mkdtempSync(join(packageDirectory, ".plugin-layout-test-"))
  try {
    cpSync(join(packageDirectory, "assets"), join(directory, "assets"), { recursive: true })
    cpSync(join(packageDirectory, "index.js"), join(directory, "index.js"))
    cpSync(join(packageDirectory, "cursor-catalog.js"), join(directory, "cursor-catalog.js"))
    cpSync(join(packageDirectory, "cursor-bridge.js"), join(directory, "cursor-bridge.js"))
    cpSync(join(packageDirectory, "cursor-paths.js"), join(directory, "cursor-paths.js"))
    cpSync(join(packageDirectory, "cursor-provider.js"), join(directory, "cursor-provider.js"))
    cpSync(join(packageDirectory, "cursor-skills.js"), join(directory, "cursor-skills.js"))
    cpSync(join(packageDirectory, "vendor"), join(directory, "vendor"), { recursive: true })
    const installed = await import(`${pathToFileURL(join(directory, "index.js")).href}?test=${Date.now()}`)

    assert.equal(installed.default.id, "oy")
  } finally {
    rmSync(directory, { recursive: true, force: true })
  }
})