Skip to content
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "token-optimizer",
"type": "module",
"version": "0.1.8",
"version": "0.2.1",
"description": "60-75% token reduction for all AI coding agents (OpenCode, Codex, Cursor, Claude Desktop) via output compression, schema slimming, and read compaction. Runs as a persistent MCP server.",
"main": "dist/src/plugin.js",
"types": "dist/src/plugin.d.ts",
Expand Down
54 changes: 28 additions & 26 deletions scripts/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ const HOME = os.homedir();
const IS_WINDOWS = process.platform === "win32";
const PKG_NAME = "token-optimizer";
const SERVER_ENTRY = "dist/src/mcp-server.js";
const PLUGIN_ENTRY = "dist/src/plugin.js";

// ── URL pathname → filesystem path (handles Windows /C:/... prefix) ───────────

Expand Down Expand Up @@ -180,12 +179,20 @@ function detectAgents(): AgentConfig[] {

// ── Patchers ──────────────────────────────────────────────────────────────────

// OpenCode MCP entry format (matches opencode.json schema)
const OPENCODE_MCP_ENTRY = (serverPath: string) => ({
enabled: true,
type: "local",
command: ["node", serverPath],
});

// Other agents (Cursor, Claude Desktop, Windsurf) use the flat { command, args } shape
const MCP_ENTRY = (serverPath: string) => ({
command: "node",
args: [serverPath],
});

function patchOpenCodeJson(configPath: string, serverPath: string, pluginPath: string, remove: boolean): boolean {
function patchOpenCodeJson(configPath: string, serverPath: string, remove: boolean): boolean {
let raw = "{}";
if (fs.existsSync(configPath)) raw = fs.readFileSync(configPath, "utf8");

Expand All @@ -198,37 +205,37 @@ function patchOpenCodeJson(configPath: string, serverPath: string, pluginPath: s
}

// ── MCP server entry ────────────────────────────────────────────────────────
// OpenCode schema: mcp entries sit directly under the "mcp" key, NOT mcp.servers.
if (!cfg.mcp || typeof cfg.mcp !== "object") {
cfg.mcp = {};
}
const mcp = cfg.mcp as Record<string, unknown>;
if (!mcp.servers || typeof mcp.servers !== "object") {
mcp.servers = {};

// Clean up stale mcp.servers entries written by older versions
if (mcp.servers && typeof mcp.servers === "object") {
const servers = mcp.servers as Record<string, unknown>;
delete servers[PKG_NAME];
if (Object.keys(servers).length === 0) delete mcp.servers;
}
const servers = mcp.servers as Record<string, unknown>;

if (remove) {
delete servers[PKG_NAME];
delete mcp[PKG_NAME];
} else {
servers[PKG_NAME] = MCP_ENTRY(serverPath);
mcp[PKG_NAME] = OPENCODE_MCP_ENTRY(serverPath);
}

// ── Plugin entry (absolute path to plugin.js in the "plugin" array) ─────────
// OpenCode resolves plugin entries as absolute paths — package names only work
// if the package is locally installed, which it won't be for global installs.
if (!Array.isArray(cfg.plugin)) {
cfg.plugin = [];
}
const plugins = cfg.plugin as string[];
// Remove any stale entries (old package-name form or old path)
const filtered = plugins.filter(
(p) => p !== PKG_NAME && p !== pluginPath
// ── Plugin entry ─────────────────────────────────────────────────────────────
// OpenCode installs npm plugins via Bun at startup. Use the package name.
// Clean up any stale absolute-path entries written by older versions first.
if (!Array.isArray(cfg.plugin)) cfg.plugin = [];
const plugins = (cfg.plugin as string[]).filter(
(p) => p !== PKG_NAME && !p.includes(path.join(PKG_NAME, "dist"))
);

if (remove) {
cfg.plugin = filtered;
cfg.plugin = plugins;
if (plugins.length === 0) delete cfg.plugin;
} else {
cfg.plugin = [...filtered, pluginPath];
cfg.plugin = [...plugins, PKG_NAME];
}

fs.mkdirSync(path.dirname(configPath), { recursive: true });
Expand Down Expand Up @@ -321,14 +328,9 @@ function patchAgentsMd(configPath: string, serverPath: string, remove: boolean):

function patchAgent(agent: AgentConfig, serverPath: string, remove: boolean): void {
let ok = false;
// Derive plugin path from server path: same package root, different entry file.
const pluginPath = serverPath.replace(
SERVER_ENTRY.replace(/\//g, path.sep),
PLUGIN_ENTRY.replace(/\//g, path.sep),
);
switch (agent.type) {
case "opencode-json":
ok = patchOpenCodeJson(agent.configPath, serverPath, pluginPath, remove);
ok = patchOpenCodeJson(agent.configPath, serverPath, remove);
break;
case "mcp-json":
ok = patchMcpJson(agent.configPath, serverPath, remove);
Expand Down
4 changes: 2 additions & 2 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ function compressGrepOutput(raw: string, workingDirectory: string): string {

// ─── Plugin definition ────────────────────────────────────────────────────────

export const TokenOptimizerPlugin: Plugin = async ({ directory, client }) => {
export const server: Plugin = async ({ directory, client }) => {
const stats = createStats()

/**
Expand Down Expand Up @@ -924,4 +924,4 @@ export const TokenOptimizerPlugin: Plugin = async ({ directory, client }) => {
}
}

export default TokenOptimizerPlugin
export default server