From 01a3433f5e572f09a178d05e5568938b21eab7b5 Mon Sep 17 00:00:00 2001 From: Bulat Yapparov Date: Thu, 23 Apr 2026 18:40:22 +0100 Subject: [PATCH 1/4] =?UTF-8?q?refactor:=20rename=20@aictrl/plugin=20?= =?UTF-8?q?=E2=86=92=20@aictrl/plugin-sdk;=20decouple=20from=20CLI=20relea?= =?UTF-8?q?se?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The npm name @aictrl/plugin is used by a separate telemetry-delivery package with its own release cadence. Our monorepo's packages/plugin (the plugin-author SDK — types + the tool() factory) was colliding on the name, which is how v0.3.3 got stuck: the CLI publish workflow attempted to republish @aictrl/plugin@1.2.16 but npm already has 2.0.0 from the telemetry package, so the real-version publish hit ENEEDAUTH. Changes: - Rename workspace package to @aictrl/plugin-sdk; reset to 0.1.0 for a fresh start. - Update all internal imports (8 source files in packages/cli/src/, 3 in .opencode/tool/) and tests. - packages/cli/src/config/config.ts auto-install now targets @aictrl/plugin-sdk in user project package.json. - Remove the 'Publish @aictrl/plugin' step from the CLI release workflow. @aictrl/plugin-sdk releases on its own cadence — manual bootstrap publish from a dev machine, subsequent updates independent of CLI releases. Avoids re-doing OIDC trusted publisher setup per CLI release and stops conflating two unrelated packages. - CONTRIBUTING.md documents the separation. User impact: plugin authors currently depending on @aictrl/plugin (the SDK, not the telemetry one) should migrate to @aictrl/plugin-sdk. User projects with existing @aictrl/plugin entries keep working on the disk they have; the CLI will add @aictrl/plugin-sdk on next run. Verified: typecheck 6/6 green, 1275 tests pass, 7 skip, 0 fail. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/publish.yml | 7 ++++--- .opencode/agent/translator.md | 2 +- .opencode/tool/github-comment.ts | 2 +- .opencode/tool/github-pr-search.ts | 2 +- .opencode/tool/github-triage.ts | 2 +- CONTRIBUTING.md | 2 +- bun.lock | 12 ++++++------ package.json | 2 +- packages/cli/package.json | 2 +- packages/cli/src/cli/cmd/auth.ts | 2 +- packages/cli/src/config/config.ts | 8 ++++---- packages/cli/src/plugin/codex.ts | 2 +- packages/cli/src/plugin/copilot.ts | 2 +- packages/cli/src/plugin/index.ts | 2 +- packages/cli/src/provider/auth.ts | 2 +- packages/cli/src/tool/registry.ts | 2 +- packages/cli/test/cli/plugin-auth-picker.test.ts | 2 +- packages/cli/test/config/config.test.ts | 2 +- packages/cli/test/tool/registry.test.ts | 2 +- packages/plugin/package.json | 4 ++-- 20 files changed, 32 insertions(+), 31 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index cb8ddcd..c5cf68c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -55,9 +55,10 @@ jobs: working-directory: packages/util run: ${{ github.workspace }}/.github/scripts/publish-if-new.sh "@aictrl/util" - - name: Publish @aictrl/plugin - working-directory: packages/plugin - run: ${{ github.workspace }}/.github/scripts/publish-if-new.sh "@aictrl/plugin" + # Note: @aictrl/plugin-sdk (workspace at packages/plugin) is NOT published here. + # It releases on its own cadence to avoid per-CLI-release npm ceremony and + # because the underlying npm name @aictrl/plugin is used by an independent + # telemetry package. See CONTRIBUTING.md. - name: Publish @aictrl/sdk working-directory: packages/sdk diff --git a/.opencode/agent/translator.md b/.opencode/agent/translator.md index 516cd49..8cfa718 100644 --- a/.opencode/agent/translator.md +++ b/.opencode/agent/translator.md @@ -608,7 +608,7 @@ XDG_CONFIG_HOME ```text ../../../config.mjs @astrojs/starlight/components -@aictrl/plugin +@aictrl/plugin-sdk @aictrl/sdk path shescape diff --git a/.opencode/tool/github-comment.ts b/.opencode/tool/github-comment.ts index b9a9123..1e8ea95 100644 --- a/.opencode/tool/github-comment.ts +++ b/.opencode/tool/github-comment.ts @@ -1,5 +1,5 @@ /// -import { tool } from "@aictrl/plugin" +import { tool } from "@aictrl/plugin-sdk" function getPRNumber(): number { const pr = parseInt(process.env.PR_NUMBER ?? "", 10) diff --git a/.opencode/tool/github-pr-search.ts b/.opencode/tool/github-pr-search.ts index 8840b0f..52c8b5d 100644 --- a/.opencode/tool/github-pr-search.ts +++ b/.opencode/tool/github-pr-search.ts @@ -1,5 +1,5 @@ /// -import { tool } from "@aictrl/plugin" +import { tool } from "@aictrl/plugin-sdk" import DESCRIPTION from "./github-pr-search.txt" async function githubFetch(endpoint: string, options: RequestInit = {}) { diff --git a/.opencode/tool/github-triage.ts b/.opencode/tool/github-triage.ts index de5f65c..c382ac1 100644 --- a/.opencode/tool/github-triage.ts +++ b/.opencode/tool/github-triage.ts @@ -1,5 +1,5 @@ /// -import { tool } from "@aictrl/plugin" +import { tool } from "@aictrl/plugin-sdk" import DESCRIPTION from "./github-triage.txt" const TEAM = { diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 49ce9df..8910c37 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -74,7 +74,7 @@ Replace `` with your platform (e.g., `darwin-arm64`, `linux-x64`). - `packages/aictrl/src/cli/cmd/tui/`: The TUI code, written in SolidJS with [opentui](https://github.com/sst/opentui) - `packages/app`: The shared web UI components, written in SolidJS - `packages/desktop`: The native desktop app, built with Tauri (wraps `packages/app`) - - `packages/plugin`: Source for `@aictrl/plugin` + - `packages/plugin`: Source for `@aictrl/plugin-sdk` (plugin-author SDK — types and helpers for writing CLI plugins). Published on its own cadence, not from the CLI release workflow. ### Understanding bun dev vs aictrl diff --git a/bun.lock b/bun.lock index bd60b1c..930af54 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,7 @@ "": { "name": "aictrl", "dependencies": { - "@aictrl/plugin": "workspace:*", + "@aictrl/plugin-sdk": "workspace:*", "@aictrl/script": "workspace:*", "@aictrl/sdk": "workspace:*", "@aws-sdk/client-s3": "3.933.0", @@ -25,7 +25,7 @@ }, "packages/cli": { "name": "@aictrl/cli", - "version": "0.3.2", + "version": "0.3.3", "bin": { "aictrl": "./bin/aictrl", }, @@ -35,7 +35,7 @@ "@agentclientprotocol/sdk": "0.14.1", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21", - "@aictrl/plugin": "workspace:*", + "@aictrl/plugin-sdk": "workspace:*", "@aictrl/sdk": "workspace:*", "@aictrl/util": "workspace:*", "@clack/prompts": "1.0.0-alpha.1", @@ -177,8 +177,8 @@ ], }, "packages/plugin": { - "name": "@aictrl/plugin", - "version": "1.2.16", + "name": "@aictrl/plugin-sdk", + "version": "0.1.0", "dependencies": { "@aictrl/sdk": "workspace:*", "zod": "catalog:", @@ -338,7 +338,7 @@ "@aictrl/cli": ["@aictrl/cli@workspace:packages/cli"], - "@aictrl/plugin": ["@aictrl/plugin@workspace:packages/plugin"], + "@aictrl/plugin-sdk": ["@aictrl/plugin-sdk@workspace:packages/plugin"], "@aictrl/script": ["@aictrl/script@workspace:packages/script"], diff --git a/package.json b/package.json index 492fc65..3b9757e 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ }, "dependencies": { "@aws-sdk/client-s3": "3.933.0", - "@aictrl/plugin": "workspace:*", + "@aictrl/plugin-sdk": "workspace:*", "@aictrl/script": "workspace:*", "@aictrl/sdk": "workspace:*", "typescript": "catalog:" diff --git a/packages/cli/package.json b/packages/cli/package.json index d45953c..677d357 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -87,7 +87,7 @@ "@agentclientprotocol/sdk": "0.14.1", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21", - "@aictrl/plugin": "workspace:*", + "@aictrl/plugin-sdk": "workspace:*", "@aictrl/sdk": "workspace:*", "@aictrl/util": "workspace:*", "@clack/prompts": "1.0.0-alpha.1", diff --git a/packages/cli/src/cli/cmd/auth.ts b/packages/cli/src/cli/cmd/auth.ts index b4e6108..aa0a16c 100644 --- a/packages/cli/src/cli/cmd/auth.ts +++ b/packages/cli/src/cli/cmd/auth.ts @@ -10,7 +10,7 @@ import { Config } from "../../config/config" import { Global } from "../../global" import { Plugin } from "../../plugin" import { Instance } from "../../project/instance" -import type { Hooks } from "@aictrl/plugin" +import type { Hooks } from "@aictrl/plugin-sdk" import { Process } from "../../util/process" import { text } from "node:stream/consumers" diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 1691d30..a23c557 100644 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -254,7 +254,7 @@ export namespace Config { })) json.dependencies = { ...json.dependencies, - "@aictrl/plugin": targetVersion, + "@aictrl/plugin-sdk": targetVersion, } await Filesystem.writeJson(pkg, json) @@ -304,15 +304,15 @@ export namespace Config { const parsed = await Filesystem.readJson<{ dependencies?: Record }>(pkg).catch(() => null) const dependencies = parsed?.dependencies ?? {} - const depVersion = dependencies["@aictrl/plugin"] + const depVersion = dependencies["@aictrl/plugin-sdk"] if (!depVersion) return true const targetVersion = Installation.isLocal() ? "latest" : Installation.VERSION if (targetVersion === "latest") { - const isOutdated = await PackageRegistry.isOutdated("@aictrl/plugin", depVersion, dir) + const isOutdated = await PackageRegistry.isOutdated("@aictrl/plugin-sdk", depVersion, dir) if (!isOutdated) return false log.info("Cached version is outdated, proceeding with install", { - pkg: "@aictrl/plugin", + pkg: "@aictrl/plugin-sdk", cachedVersion: depVersion, }) return true diff --git a/packages/cli/src/plugin/codex.ts b/packages/cli/src/plugin/codex.ts index d815d54..56aca43 100644 --- a/packages/cli/src/plugin/codex.ts +++ b/packages/cli/src/plugin/codex.ts @@ -1,4 +1,4 @@ -import type { Hooks, PluginInput } from "@aictrl/plugin" +import type { Hooks, PluginInput } from "@aictrl/plugin-sdk" import { Log } from "../util/log" import { Installation } from "../installation" import { Auth, OAUTH_DUMMY_KEY } from "../auth" diff --git a/packages/cli/src/plugin/copilot.ts b/packages/cli/src/plugin/copilot.ts index f59f8ed..b205bce 100644 --- a/packages/cli/src/plugin/copilot.ts +++ b/packages/cli/src/plugin/copilot.ts @@ -1,4 +1,4 @@ -import type { Hooks, PluginInput } from "@aictrl/plugin" +import type { Hooks, PluginInput } from "@aictrl/plugin-sdk" import { Installation } from "@/installation" import { iife } from "@/util/iife" diff --git a/packages/cli/src/plugin/index.ts b/packages/cli/src/plugin/index.ts index 57d6f48..8efb2f0 100644 --- a/packages/cli/src/plugin/index.ts +++ b/packages/cli/src/plugin/index.ts @@ -1,4 +1,4 @@ -import type { Hooks, PluginInput, Plugin as PluginInstance } from "@aictrl/plugin" +import type { Hooks, PluginInput, Plugin as PluginInstance } from "@aictrl/plugin-sdk" import { Config } from "../config/config" import { Bus } from "../bus" import { Log } from "../util/log" diff --git a/packages/cli/src/provider/auth.ts b/packages/cli/src/provider/auth.ts index f4e2bd8..8c54b54 100644 --- a/packages/cli/src/provider/auth.ts +++ b/packages/cli/src/provider/auth.ts @@ -3,7 +3,7 @@ import { Plugin } from "../plugin" import { map, filter, pipe, fromEntries, mapValues } from "remeda" import z from "zod" import { fn } from "@/util/fn" -import type { AuthOuathResult, Hooks } from "@aictrl/plugin" +import type { AuthOuathResult, Hooks } from "@aictrl/plugin-sdk" import { NamedError } from "@aictrl/util/error" import { Auth } from "@/auth" diff --git a/packages/cli/src/tool/registry.ts b/packages/cli/src/tool/registry.ts index 4d46147..21cb123 100644 --- a/packages/cli/src/tool/registry.ts +++ b/packages/cli/src/tool/registry.ts @@ -17,7 +17,7 @@ import { Tool } from "./tool" import { Instance } from "../project/instance" import { Config } from "../config/config" import path from "path" -import { type ToolContext as PluginToolContext, type ToolDefinition } from "@aictrl/plugin" +import { type ToolContext as PluginToolContext, type ToolDefinition } from "@aictrl/plugin-sdk" import z from "zod" import { Plugin } from "../plugin" import { WebSearchTool } from "./websearch" diff --git a/packages/cli/test/cli/plugin-auth-picker.test.ts b/packages/cli/test/cli/plugin-auth-picker.test.ts index db2b990..b2f7871 100644 --- a/packages/cli/test/cli/plugin-auth-picker.test.ts +++ b/packages/cli/test/cli/plugin-auth-picker.test.ts @@ -1,6 +1,6 @@ import { test, expect, describe } from "bun:test" import { resolvePluginProviders } from "../../src/cli/cmd/auth" -import type { Hooks } from "@aictrl/plugin" +import type { Hooks } from "@aictrl/plugin-sdk" function hookWithAuth(provider: string): Hooks { return { diff --git a/packages/cli/test/config/config.test.ts b/packages/cli/test/config/config.test.ts index d198f21..67d2672 100644 --- a/packages/cli/test/config/config.test.ts +++ b/packages/cli/test/config/config.test.ts @@ -1550,7 +1550,7 @@ describe("getPluginName", () => { test("extracts name from scoped npm package", () => { expect(Config.getPluginName("@scope/pkg@1.0.0")).toBe("@scope/pkg") - expect(Config.getPluginName("@aictrl/plugin@2.0.0")).toBe("@aictrl/plugin") + expect(Config.getPluginName("@aictrl/plugin-sdk@2.0.0")).toBe("@aictrl/plugin-sdk") }) test("returns full string for package without version", () => { diff --git a/packages/cli/test/tool/registry.test.ts b/packages/cli/test/tool/registry.test.ts index baff148..a72ba6c 100644 --- a/packages/cli/test/tool/registry.test.ts +++ b/packages/cli/test/tool/registry.test.ts @@ -90,7 +90,7 @@ describe("tool.registry", () => { JSON.stringify({ name: "custom-tools", dependencies: { - "@aictrl/plugin": "0.1.0", + "@aictrl/plugin-sdk": "0.1.0", cowsay: "^1.6.0", }, }), diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 3eaaf06..64ecf16 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", - "name": "@aictrl/plugin", - "version": "1.2.16", + "name": "@aictrl/plugin-sdk", + "version": "0.1.0", "type": "module", "license": "MIT", "repository": { From cd873bd6d3111539d9fa9e360a437daaf2493bca Mon Sep 17 00:00:00 2001 From: Bulat Yapparov Date: Thu, 23 Apr 2026 18:51:32 +0100 Subject: [PATCH 2/4] fix: migrate legacy @aictrl/plugin dep out of user package.json Addresses the PR review finding that installDependencies was adding @aictrl/plugin-sdk but leaving an orphaned @aictrl/plugin entry behind, which would cause bun install to fetch the unrelated telemetry package. - installDependencies now deletes dependencies["@aictrl/plugin"] during the same write pass that adds @aictrl/plugin-sdk. - needsInstall treats presence of the legacy key as 'install needed' so the migration runs on the next CLI invocation for existing users. - New test asserts the migration behaviour end-to-end. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/cli/src/config/config.ts | 18 ++++++++++---- packages/cli/test/config/config.test.ts | 31 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index a23c557..95e1360 100644 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -249,13 +249,18 @@ export namespace Config { const pkg = path.join(dir, "package.json") const targetVersion = Installation.isLocal() ? "*" : Installation.VERSION - const json = await Filesystem.readJson<{ dependencies?: Record }>(pkg).catch(() => ({ - dependencies: {}, - })) - json.dependencies = { - ...json.dependencies, + const json = await Filesystem.readJson<{ dependencies?: Record }>(pkg).catch( + () => ({ dependencies: {} as Record }), + ) + const deps: Record = { + ...(json.dependencies ?? {}), "@aictrl/plugin-sdk": targetVersion, } + // Migration: remove the legacy key. @aictrl/plugin on npm is an + // unrelated telemetry package; leaving it in dependencies would + // cause bun install to fetch it needlessly. + delete deps["@aictrl/plugin"] + json.dependencies = deps await Filesystem.writeJson(pkg, json) const gitignore = path.join(dir, ".gitignore") @@ -304,6 +309,9 @@ export namespace Config { const parsed = await Filesystem.readJson<{ dependencies?: Record }>(pkg).catch(() => null) const dependencies = parsed?.dependencies ?? {} + // Migration: presence of the legacy key forces a reinstall so + // installDependencies can strip it out in the same pass. + if (dependencies["@aictrl/plugin"]) return true const depVersion = dependencies["@aictrl/plugin-sdk"] if (!depVersion) return true diff --git a/packages/cli/test/config/config.test.ts b/packages/cli/test/config/config.test.ts index 67d2672..59f7478 100644 --- a/packages/cli/test/config/config.test.ts +++ b/packages/cli/test/config/config.test.ts @@ -1559,6 +1559,37 @@ describe("getPluginName", () => { }) }) +describe("installDependencies migration", () => { + test("removes legacy @aictrl/plugin key when adding @aictrl/plugin-sdk", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Filesystem.write( + path.join(dir, "package.json"), + JSON.stringify({ + name: "existing", + dependencies: { + "@aictrl/plugin": "1.2.16", + cowsay: "^1.6.0", + }, + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await Config.installDependencies(tmp.path).catch(() => {}) + const parsed = await Filesystem.readJson<{ dependencies: Record }>( + path.join(tmp.path, "package.json"), + ) + expect(parsed.dependencies["@aictrl/plugin"]).toBeUndefined() + expect(parsed.dependencies["@aictrl/plugin-sdk"]).toBeDefined() + expect(parsed.dependencies["cowsay"]).toBe("^1.6.0") + }, + }) + }) +}) + describe("deduplicatePlugins", () => { test("removes duplicates keeping higher priority (later entries)", () => { const plugins = ["global-plugin@1.0.0", "shared-plugin@1.0.0", "local-plugin@2.0.0", "shared-plugin@2.0.0"] From 21e77486e0d87d76f3cd99abde7d09132467f507 Mon Sep 17 00:00:00 2001 From: Bulat Yapparov Date: Thu, 23 Apr 2026 18:57:30 +0100 Subject: [PATCH 3/4] =?UTF-8?q?simplify:=20internal=20plugin=20SDK=20?= =?UTF-8?q?=E2=80=94=20stop=20auto-managing=20npm=20dep=20in=20user=20proj?= =?UTF-8?q?ects?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plugin extension is first-party only. The CLI embeds plugin types at runtime, so user plugin projects don't need an npm dep for the SDK at all — dropping the whole @aictrl/plugin-sdk auto-install dance. Changes: - Mark packages/plugin as "private": true so it can't be accidentally published. Workspace-only henceforth. - installDependencies: stop adding @aictrl/plugin-sdk. Scrub both the legacy @aictrl/plugin and @aictrl/plugin-sdk keys from user package.json (migrating existing installs off the old names). Still runs bun install for user-declared deps like cowsay. - needsInstall: simpler — only trigger on missing node_modules (handled above) or legacy @aictrl/plugin* key present (migration path). - Drop now-unused Installation / PackageRegistry imports from config.ts. - Update the migration test to assert both keys are stripped. Typecheck 6/6 green; 1276 tests pass, 7 skip, 0 fail. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/cli/src/config/config.ts | 60 +++++++++++-------------- packages/cli/test/config/config.test.ts | 5 ++- packages/plugin/package.json | 1 + 3 files changed, 29 insertions(+), 37 deletions(-) diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 95e1360..e5a1c07 100644 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -22,13 +22,11 @@ import { import { Instance } from "../project/instance" import { LSPServer } from "../lsp/server" import { BunProc } from "@/bun" -import { Installation } from "@/installation" import { ConfigMarkdown } from "./markdown" import { constants, existsSync } from "fs" import { Bus } from "@/bus" import { GlobalBus } from "@/bus/global" import { Glob } from "../util/glob" -import { PackageRegistry } from "@/bun/registry" import { proxied } from "@/util/proxied" import { iife } from "@/util/iife" import { Control } from "@/control" @@ -247,29 +245,31 @@ export namespace Config { export async function installDependencies(dir: string) { const pkg = path.join(dir, "package.json") - const targetVersion = Installation.isLocal() ? "*" : Installation.VERSION - const json = await Filesystem.readJson<{ dependencies?: Record }>(pkg).catch( - () => ({ dependencies: {} as Record }), - ) - const deps: Record = { - ...(json.dependencies ?? {}), - "@aictrl/plugin-sdk": targetVersion, + // Migration: scrub legacy @aictrl/plugin* entries from user + // package.json. Older CLIs auto-added these; the npm names aren't + // ours to manage (@aictrl/plugin is an unrelated telemetry package, + // and @aictrl/plugin-sdk is internal / private). Plugin types are + // supplied by the CLI runtime, not npm. + const json = await Filesystem.readJson<{ dependencies?: Record }>(pkg).catch(() => null) + if (json?.dependencies) { + const deps = { ...json.dependencies } + const hadLegacy = "@aictrl/plugin" in deps || "@aictrl/plugin-sdk" in deps + delete deps["@aictrl/plugin"] + delete deps["@aictrl/plugin-sdk"] + if (hadLegacy) { + json.dependencies = deps + await Filesystem.writeJson(pkg, json) + } } - // Migration: remove the legacy key. @aictrl/plugin on npm is an - // unrelated telemetry package; leaving it in dependencies would - // cause bun install to fetch it needlessly. - delete deps["@aictrl/plugin"] - json.dependencies = deps - await Filesystem.writeJson(pkg, json) const gitignore = path.join(dir, ".gitignore") const hasGitIgnore = await Filesystem.exists(gitignore) if (!hasGitIgnore) await Filesystem.write(gitignore, ["node_modules", "package.json", "bun.lock", ".gitignore"].join("\n")) - // Install any additional dependencies defined in the package.json - // This allows local plugins and custom tools to use external packages + // Install any dependencies declared by local plugins and custom tools + // (e.g. a .aictrl/package.json that lists 'cowsay'). await BunProc.run( [ "install", @@ -309,24 +309,14 @@ export namespace Config { const parsed = await Filesystem.readJson<{ dependencies?: Record }>(pkg).catch(() => null) const dependencies = parsed?.dependencies ?? {} - // Migration: presence of the legacy key forces a reinstall so - // installDependencies can strip it out in the same pass. - if (dependencies["@aictrl/plugin"]) return true - const depVersion = dependencies["@aictrl/plugin-sdk"] - if (!depVersion) return true - - const targetVersion = Installation.isLocal() ? "latest" : Installation.VERSION - if (targetVersion === "latest") { - const isOutdated = await PackageRegistry.isOutdated("@aictrl/plugin-sdk", depVersion, dir) - if (!isOutdated) return false - log.info("Cached version is outdated, proceeding with install", { - pkg: "@aictrl/plugin-sdk", - cachedVersion: depVersion, - }) - return true - } - if (depVersion === targetVersion) return false - return true + + // Migration: any legacy @aictrl/plugin* entry forces a reinstall + // so installDependencies can scrub it out in the same pass. + if (dependencies["@aictrl/plugin"] || dependencies["@aictrl/plugin-sdk"]) return true + + // No aictrl-managed deps; install only if user declared other deps + // but node_modules is absent (already checked above) — otherwise skip. + return false } function rel(item: string, patterns: string[]) { diff --git a/packages/cli/test/config/config.test.ts b/packages/cli/test/config/config.test.ts index 59f7478..26e8d25 100644 --- a/packages/cli/test/config/config.test.ts +++ b/packages/cli/test/config/config.test.ts @@ -1560,7 +1560,7 @@ describe("getPluginName", () => { }) describe("installDependencies migration", () => { - test("removes legacy @aictrl/plugin key when adding @aictrl/plugin-sdk", async () => { + test("scrubs legacy @aictrl/plugin* keys from user package.json", async () => { await using tmp = await tmpdir({ init: async (dir) => { await Filesystem.write( @@ -1569,6 +1569,7 @@ describe("installDependencies migration", () => { name: "existing", dependencies: { "@aictrl/plugin": "1.2.16", + "@aictrl/plugin-sdk": "0.1.0", cowsay: "^1.6.0", }, }), @@ -1583,7 +1584,7 @@ describe("installDependencies migration", () => { path.join(tmp.path, "package.json"), ) expect(parsed.dependencies["@aictrl/plugin"]).toBeUndefined() - expect(parsed.dependencies["@aictrl/plugin-sdk"]).toBeDefined() + expect(parsed.dependencies["@aictrl/plugin-sdk"]).toBeUndefined() expect(parsed.dependencies["cowsay"]).toBe("^1.6.0") }, }) diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 64ecf16..fe06b18 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -2,6 +2,7 @@ "$schema": "https://json.schemastore.org/package.json", "name": "@aictrl/plugin-sdk", "version": "0.1.0", + "private": true, "type": "module", "license": "MIT", "repository": { From 620be3e231e9d95bb916e0f20c7a686467bc6c6a Mon Sep 17 00:00:00 2001 From: Bulat Yapparov Date: Thu, 23 Apr 2026 19:00:12 +0100 Subject: [PATCH 4/4] revert -sdk suffix: package is private and internal-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With packages/plugin marked private and never published, the npm name collision with the telemetry @aictrl/plugin is moot. Dropping the -sdk suffix — the workspace name just needs to be unique inside the repo, and @aictrl/plugin matches the directory name and existing import habit. Typecheck green; 1275 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/publish.yml | 8 ++++---- .opencode/agent/translator.md | 2 +- .opencode/tool/github-comment.ts | 2 +- .opencode/tool/github-pr-search.ts | 2 +- .opencode/tool/github-triage.ts | 2 +- CONTRIBUTING.md | 2 +- bun.lock | 8 ++++---- package.json | 2 +- packages/cli/package.json | 2 +- packages/cli/src/cli/cmd/auth.ts | 2 +- packages/cli/src/config/config.ts | 8 ++++---- packages/cli/src/plugin/codex.ts | 2 +- packages/cli/src/plugin/copilot.ts | 2 +- packages/cli/src/plugin/index.ts | 2 +- packages/cli/src/provider/auth.ts | 2 +- packages/cli/src/tool/registry.ts | 2 +- packages/cli/test/cli/plugin-auth-picker.test.ts | 2 +- packages/cli/test/config/config.test.ts | 4 +--- packages/cli/test/tool/registry.test.ts | 2 +- packages/plugin/package.json | 2 +- 20 files changed, 29 insertions(+), 31 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c5cf68c..cd83212 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -55,10 +55,10 @@ jobs: working-directory: packages/util run: ${{ github.workspace }}/.github/scripts/publish-if-new.sh "@aictrl/util" - # Note: @aictrl/plugin-sdk (workspace at packages/plugin) is NOT published here. - # It releases on its own cadence to avoid per-CLI-release npm ceremony and - # because the underlying npm name @aictrl/plugin is used by an independent - # telemetry package. See CONTRIBUTING.md. + # Note: @aictrl/plugin (workspace at packages/plugin) is the first-party + # plugin-author SDK used internally by the CLI and .opencode/tool/. It is + # marked "private": true and is NOT published to npm. Plugin types ship + # embedded in the compiled CLI binary at runtime. - name: Publish @aictrl/sdk working-directory: packages/sdk diff --git a/.opencode/agent/translator.md b/.opencode/agent/translator.md index 8cfa718..516cd49 100644 --- a/.opencode/agent/translator.md +++ b/.opencode/agent/translator.md @@ -608,7 +608,7 @@ XDG_CONFIG_HOME ```text ../../../config.mjs @astrojs/starlight/components -@aictrl/plugin-sdk +@aictrl/plugin @aictrl/sdk path shescape diff --git a/.opencode/tool/github-comment.ts b/.opencode/tool/github-comment.ts index 1e8ea95..b9a9123 100644 --- a/.opencode/tool/github-comment.ts +++ b/.opencode/tool/github-comment.ts @@ -1,5 +1,5 @@ /// -import { tool } from "@aictrl/plugin-sdk" +import { tool } from "@aictrl/plugin" function getPRNumber(): number { const pr = parseInt(process.env.PR_NUMBER ?? "", 10) diff --git a/.opencode/tool/github-pr-search.ts b/.opencode/tool/github-pr-search.ts index 52c8b5d..8840b0f 100644 --- a/.opencode/tool/github-pr-search.ts +++ b/.opencode/tool/github-pr-search.ts @@ -1,5 +1,5 @@ /// -import { tool } from "@aictrl/plugin-sdk" +import { tool } from "@aictrl/plugin" import DESCRIPTION from "./github-pr-search.txt" async function githubFetch(endpoint: string, options: RequestInit = {}) { diff --git a/.opencode/tool/github-triage.ts b/.opencode/tool/github-triage.ts index c382ac1..de5f65c 100644 --- a/.opencode/tool/github-triage.ts +++ b/.opencode/tool/github-triage.ts @@ -1,5 +1,5 @@ /// -import { tool } from "@aictrl/plugin-sdk" +import { tool } from "@aictrl/plugin" import DESCRIPTION from "./github-triage.txt" const TEAM = { diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8910c37..ad79177 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -74,7 +74,7 @@ Replace `` with your platform (e.g., `darwin-arm64`, `linux-x64`). - `packages/aictrl/src/cli/cmd/tui/`: The TUI code, written in SolidJS with [opentui](https://github.com/sst/opentui) - `packages/app`: The shared web UI components, written in SolidJS - `packages/desktop`: The native desktop app, built with Tauri (wraps `packages/app`) - - `packages/plugin`: Source for `@aictrl/plugin-sdk` (plugin-author SDK — types and helpers for writing CLI plugins). Published on its own cadence, not from the CLI release workflow. + - `packages/plugin`: Source for `@aictrl/plugin` — first-party plugin-author SDK (types + `tool()` factory). Workspace-only, marked `"private": true`, not published to npm; types are embedded in the compiled CLI binary at runtime. ### Understanding bun dev vs aictrl diff --git a/bun.lock b/bun.lock index 930af54..1123e09 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,7 @@ "": { "name": "aictrl", "dependencies": { - "@aictrl/plugin-sdk": "workspace:*", + "@aictrl/plugin": "workspace:*", "@aictrl/script": "workspace:*", "@aictrl/sdk": "workspace:*", "@aws-sdk/client-s3": "3.933.0", @@ -35,7 +35,7 @@ "@agentclientprotocol/sdk": "0.14.1", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21", - "@aictrl/plugin-sdk": "workspace:*", + "@aictrl/plugin": "workspace:*", "@aictrl/sdk": "workspace:*", "@aictrl/util": "workspace:*", "@clack/prompts": "1.0.0-alpha.1", @@ -177,7 +177,7 @@ ], }, "packages/plugin": { - "name": "@aictrl/plugin-sdk", + "name": "@aictrl/plugin", "version": "0.1.0", "dependencies": { "@aictrl/sdk": "workspace:*", @@ -338,7 +338,7 @@ "@aictrl/cli": ["@aictrl/cli@workspace:packages/cli"], - "@aictrl/plugin-sdk": ["@aictrl/plugin-sdk@workspace:packages/plugin"], + "@aictrl/plugin": ["@aictrl/plugin@workspace:packages/plugin"], "@aictrl/script": ["@aictrl/script@workspace:packages/script"], diff --git a/package.json b/package.json index 3b9757e..492fc65 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ }, "dependencies": { "@aws-sdk/client-s3": "3.933.0", - "@aictrl/plugin-sdk": "workspace:*", + "@aictrl/plugin": "workspace:*", "@aictrl/script": "workspace:*", "@aictrl/sdk": "workspace:*", "typescript": "catalog:" diff --git a/packages/cli/package.json b/packages/cli/package.json index 677d357..d45953c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -87,7 +87,7 @@ "@agentclientprotocol/sdk": "0.14.1", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21", - "@aictrl/plugin-sdk": "workspace:*", + "@aictrl/plugin": "workspace:*", "@aictrl/sdk": "workspace:*", "@aictrl/util": "workspace:*", "@clack/prompts": "1.0.0-alpha.1", diff --git a/packages/cli/src/cli/cmd/auth.ts b/packages/cli/src/cli/cmd/auth.ts index aa0a16c..b4e6108 100644 --- a/packages/cli/src/cli/cmd/auth.ts +++ b/packages/cli/src/cli/cmd/auth.ts @@ -10,7 +10,7 @@ import { Config } from "../../config/config" import { Global } from "../../global" import { Plugin } from "../../plugin" import { Instance } from "../../project/instance" -import type { Hooks } from "@aictrl/plugin-sdk" +import type { Hooks } from "@aictrl/plugin" import { Process } from "../../util/process" import { text } from "node:stream/consumers" diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index e5a1c07..3b0297d 100644 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -249,14 +249,14 @@ export namespace Config { // Migration: scrub legacy @aictrl/plugin* entries from user // package.json. Older CLIs auto-added these; the npm names aren't // ours to manage (@aictrl/plugin is an unrelated telemetry package, - // and @aictrl/plugin-sdk is internal / private). Plugin types are + // and @aictrl/plugin is internal / private). Plugin types are // supplied by the CLI runtime, not npm. const json = await Filesystem.readJson<{ dependencies?: Record }>(pkg).catch(() => null) if (json?.dependencies) { const deps = { ...json.dependencies } - const hadLegacy = "@aictrl/plugin" in deps || "@aictrl/plugin-sdk" in deps + const hadLegacy = "@aictrl/plugin" in deps || "@aictrl/plugin" in deps + delete deps["@aictrl/plugin"] delete deps["@aictrl/plugin"] - delete deps["@aictrl/plugin-sdk"] if (hadLegacy) { json.dependencies = deps await Filesystem.writeJson(pkg, json) @@ -312,7 +312,7 @@ export namespace Config { // Migration: any legacy @aictrl/plugin* entry forces a reinstall // so installDependencies can scrub it out in the same pass. - if (dependencies["@aictrl/plugin"] || dependencies["@aictrl/plugin-sdk"]) return true + if (dependencies["@aictrl/plugin"] || dependencies["@aictrl/plugin"]) return true // No aictrl-managed deps; install only if user declared other deps // but node_modules is absent (already checked above) — otherwise skip. diff --git a/packages/cli/src/plugin/codex.ts b/packages/cli/src/plugin/codex.ts index 56aca43..d815d54 100644 --- a/packages/cli/src/plugin/codex.ts +++ b/packages/cli/src/plugin/codex.ts @@ -1,4 +1,4 @@ -import type { Hooks, PluginInput } from "@aictrl/plugin-sdk" +import type { Hooks, PluginInput } from "@aictrl/plugin" import { Log } from "../util/log" import { Installation } from "../installation" import { Auth, OAUTH_DUMMY_KEY } from "../auth" diff --git a/packages/cli/src/plugin/copilot.ts b/packages/cli/src/plugin/copilot.ts index b205bce..f59f8ed 100644 --- a/packages/cli/src/plugin/copilot.ts +++ b/packages/cli/src/plugin/copilot.ts @@ -1,4 +1,4 @@ -import type { Hooks, PluginInput } from "@aictrl/plugin-sdk" +import type { Hooks, PluginInput } from "@aictrl/plugin" import { Installation } from "@/installation" import { iife } from "@/util/iife" diff --git a/packages/cli/src/plugin/index.ts b/packages/cli/src/plugin/index.ts index 8efb2f0..57d6f48 100644 --- a/packages/cli/src/plugin/index.ts +++ b/packages/cli/src/plugin/index.ts @@ -1,4 +1,4 @@ -import type { Hooks, PluginInput, Plugin as PluginInstance } from "@aictrl/plugin-sdk" +import type { Hooks, PluginInput, Plugin as PluginInstance } from "@aictrl/plugin" import { Config } from "../config/config" import { Bus } from "../bus" import { Log } from "../util/log" diff --git a/packages/cli/src/provider/auth.ts b/packages/cli/src/provider/auth.ts index 8c54b54..f4e2bd8 100644 --- a/packages/cli/src/provider/auth.ts +++ b/packages/cli/src/provider/auth.ts @@ -3,7 +3,7 @@ import { Plugin } from "../plugin" import { map, filter, pipe, fromEntries, mapValues } from "remeda" import z from "zod" import { fn } from "@/util/fn" -import type { AuthOuathResult, Hooks } from "@aictrl/plugin-sdk" +import type { AuthOuathResult, Hooks } from "@aictrl/plugin" import { NamedError } from "@aictrl/util/error" import { Auth } from "@/auth" diff --git a/packages/cli/src/tool/registry.ts b/packages/cli/src/tool/registry.ts index 21cb123..4d46147 100644 --- a/packages/cli/src/tool/registry.ts +++ b/packages/cli/src/tool/registry.ts @@ -17,7 +17,7 @@ import { Tool } from "./tool" import { Instance } from "../project/instance" import { Config } from "../config/config" import path from "path" -import { type ToolContext as PluginToolContext, type ToolDefinition } from "@aictrl/plugin-sdk" +import { type ToolContext as PluginToolContext, type ToolDefinition } from "@aictrl/plugin" import z from "zod" import { Plugin } from "../plugin" import { WebSearchTool } from "./websearch" diff --git a/packages/cli/test/cli/plugin-auth-picker.test.ts b/packages/cli/test/cli/plugin-auth-picker.test.ts index b2f7871..db2b990 100644 --- a/packages/cli/test/cli/plugin-auth-picker.test.ts +++ b/packages/cli/test/cli/plugin-auth-picker.test.ts @@ -1,6 +1,6 @@ import { test, expect, describe } from "bun:test" import { resolvePluginProviders } from "../../src/cli/cmd/auth" -import type { Hooks } from "@aictrl/plugin-sdk" +import type { Hooks } from "@aictrl/plugin" function hookWithAuth(provider: string): Hooks { return { diff --git a/packages/cli/test/config/config.test.ts b/packages/cli/test/config/config.test.ts index 26e8d25..6df5b99 100644 --- a/packages/cli/test/config/config.test.ts +++ b/packages/cli/test/config/config.test.ts @@ -1550,7 +1550,7 @@ describe("getPluginName", () => { test("extracts name from scoped npm package", () => { expect(Config.getPluginName("@scope/pkg@1.0.0")).toBe("@scope/pkg") - expect(Config.getPluginName("@aictrl/plugin-sdk@2.0.0")).toBe("@aictrl/plugin-sdk") + expect(Config.getPluginName("@aictrl/plugin@2.0.0")).toBe("@aictrl/plugin") }) test("returns full string for package without version", () => { @@ -1569,7 +1569,6 @@ describe("installDependencies migration", () => { name: "existing", dependencies: { "@aictrl/plugin": "1.2.16", - "@aictrl/plugin-sdk": "0.1.0", cowsay: "^1.6.0", }, }), @@ -1584,7 +1583,6 @@ describe("installDependencies migration", () => { path.join(tmp.path, "package.json"), ) expect(parsed.dependencies["@aictrl/plugin"]).toBeUndefined() - expect(parsed.dependencies["@aictrl/plugin-sdk"]).toBeUndefined() expect(parsed.dependencies["cowsay"]).toBe("^1.6.0") }, }) diff --git a/packages/cli/test/tool/registry.test.ts b/packages/cli/test/tool/registry.test.ts index a72ba6c..baff148 100644 --- a/packages/cli/test/tool/registry.test.ts +++ b/packages/cli/test/tool/registry.test.ts @@ -90,7 +90,7 @@ describe("tool.registry", () => { JSON.stringify({ name: "custom-tools", dependencies: { - "@aictrl/plugin-sdk": "0.1.0", + "@aictrl/plugin": "0.1.0", cowsay: "^1.6.0", }, }), diff --git a/packages/plugin/package.json b/packages/plugin/package.json index fe06b18..b525686 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "name": "@aictrl/plugin-sdk", + "name": "@aictrl/plugin", "version": "0.1.0", "private": true, "type": "module",