chore: rebrand companion-os to clanker-agent

- Rename all package names from companion-* to clanker-*
- Update npm scopes from @mariozechner to @harivansh-afk
- Rename config directories .companion -> .clanker
- Rename environment variables COMPANION_* -> CLANKER_*
- Update all documentation, README files, and install scripts
- Rename package directories (companion-channels, companion-grind, companion-teams)
- Update GitHub URLs to harivansh-afk/clanker-agent
- Preserve full git history from companion-cloud monorepo
This commit is contained in:
Harivansh Rathi 2026-03-26 16:22:52 -04:00
parent f93fe7d1a0
commit 67168d8289
356 changed files with 2249 additions and 10223 deletions

View file

@ -1,8 +1,8 @@
import { existsSync, mkdirSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Agent } from "@mariozechner/companion-agent-core";
import { type AssistantMessage, getModel } from "@mariozechner/companion-ai";
import { Agent } from "@mariozechner/clanker-agent-core";
import { type AssistantMessage, getModel } from "@mariozechner/clanker-ai";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { AgentSession } from "../src/core/agent-session.js";
import { AuthStorage } from "../src/core/auth-storage.js";
@ -44,7 +44,7 @@ describe("AgentSession auto-compaction queue resume", () => {
let tempDir: string;
beforeEach(() => {
tempDir = join(tmpdir(), `companion-auto-compaction-queue-${Date.now()}`);
tempDir = join(tmpdir(), `clanker-auto-compaction-queue-${Date.now()}`);
mkdirSync(tempDir, { recursive: true });
vi.useFakeTimers();

View file

@ -10,8 +10,8 @@
import { existsSync, mkdirSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Agent } from "@mariozechner/companion-agent-core";
import { getModel } from "@mariozechner/companion-ai";
import { Agent } from "@mariozechner/clanker-agent-core";
import { getModel } from "@mariozechner/clanker-ai";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { AgentSession } from "../src/core/agent-session.js";
import { AuthStorage } from "../src/core/auth-storage.js";
@ -28,7 +28,7 @@ describe.skipIf(!API_KEY)("AgentSession forking", () => {
beforeEach(() => {
// Create temp directory for session files
tempDir = join(tmpdir(), `companion-branching-test-${Date.now()}`);
tempDir = join(tmpdir(), `clanker-branching-test-${Date.now()}`);
mkdirSync(tempDir, { recursive: true });
});

View file

@ -10,8 +10,8 @@
import { existsSync, mkdirSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Agent } from "@mariozechner/companion-agent-core";
import { getModel } from "@mariozechner/companion-ai";
import { Agent } from "@mariozechner/clanker-agent-core";
import { getModel } from "@mariozechner/clanker-ai";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
AgentSession,
@ -32,7 +32,7 @@ describe.skipIf(!API_KEY)("AgentSession compaction e2e", () => {
beforeEach(() => {
// Create temp directory for session files
tempDir = join(tmpdir(), `companion-compaction-test-${Date.now()}`);
tempDir = join(tmpdir(), `clanker-compaction-test-${Date.now()}`);
mkdirSync(tempDir, { recursive: true });
// Track events

View file

@ -5,13 +5,13 @@
import { existsSync, mkdirSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Agent } from "@mariozechner/companion-agent-core";
import { Agent } from "@mariozechner/clanker-agent-core";
import {
type AssistantMessage,
type AssistantMessageEvent,
EventStream,
getModel,
} from "@mariozechner/companion-ai";
} from "@mariozechner/clanker-ai";
import { Type } from "@sinclair/typebox";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { AgentSession } from "../src/core/agent-session.js";
@ -63,7 +63,7 @@ describe("AgentSession concurrent prompt guard", () => {
let tempDir: string;
beforeEach(() => {
tempDir = join(tmpdir(), `companion-concurrent-test-${Date.now()}`);
tempDir = join(tmpdir(), `clanker-concurrent-test-${Date.now()}`);
mkdirSync(tempDir, { recursive: true });
});

View file

@ -1,7 +1,7 @@
import { existsSync, mkdirSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { getModel } from "@mariozechner/companion-ai";
import { getModel } from "@mariozechner/clanker-ai";
import { Type } from "@sinclair/typebox";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { DefaultResourceLoader } from "../src/core/resource-loader.js";
@ -16,7 +16,7 @@ describe("AgentSession dynamic tool registration", () => {
beforeEach(() => {
tempDir = join(
tmpdir(),
`companion-dynamic-tool-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
`clanker-dynamic-tool-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
);
agentDir = join(tempDir, "agent");
mkdirSync(agentDir, { recursive: true });
@ -37,9 +37,9 @@ describe("AgentSession dynamic tool registration", () => {
agentDir,
settingsManager,
extensionFactories: [
(companion) => {
companion.on("session_start", () => {
companion.registerTool({
clanker => {
clanker.on("session_start", () => {
clanker.registerTool({
name: "dynamic_tool",
label: "Dynamic Tool",
description: "Tool registered from session_start",

View file

@ -1,13 +1,13 @@
import { existsSync, mkdirSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Agent, type AgentEvent } from "@mariozechner/companion-agent-core";
import { Agent, type AgentEvent } from "@mariozechner/clanker-agent-core";
import {
type AssistantMessage,
type AssistantMessageEvent,
EventStream,
getModel,
} from "@mariozechner/companion-ai";
} from "@mariozechner/clanker-ai";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { AgentSession } from "../src/core/agent-session.js";
import { AuthStorage } from "../src/core/auth-storage.js";
@ -65,7 +65,7 @@ describe("AgentSession retry", () => {
let tempDir: string;
beforeEach(() => {
tempDir = join(tmpdir(), `companion-retry-test-${Date.now()}`);
tempDir = join(tmpdir(), `clanker-retry-test-${Date.now()}`);
mkdirSync(tempDir, { recursive: true });
});

View file

@ -7,7 +7,7 @@ import {
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { registerOAuthProvider } from "@mariozechner/companion-ai/oauth";
import { registerOAuthProvider } from "@mariozechner/clanker-ai/oauth";
import lockfile from "proper-lockfile";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { AuthStorage } from "../src/core/auth-storage.js";
@ -21,7 +21,7 @@ describe("AuthStorage", () => {
beforeEach(() => {
tempDir = join(
tmpdir(),
`companion-test-auth-storage-${Date.now()}-${Math.random().toString(36).slice(2)}`,
`clanker-test-auth-storage-${Date.now()}-${Math.random().toString(36).slice(2)}`,
);
mkdirSync(tempDir, { recursive: true });
authJsonPath = join(tempDir, "auth.json");

View file

@ -155,7 +155,7 @@ describe("browser tool", () => {
profileDir,
stateDir,
headed: true,
windowClass: "CompanionBrowser",
windowClass: "ClankerBrowser",
});
await browserTool.execute("browser-open-window-class", {
@ -168,7 +168,7 @@ describe("browser tool", () => {
profileDir,
"--headed",
"--args",
"--class=CompanionBrowser",
"--class=ClankerBrowser",
"open",
"https://example.com",
]);

View file

@ -12,8 +12,8 @@ import type {
describe("Documentation example", () => {
it("custom compaction example should type-check correctly", () => {
// This is the example from extensions.md - verify it compiles
const exampleExtension = (companion: ExtensionAPI) => {
companion.on(
const exampleExtension = (clanker: ExtensionAPI) => {
clanker.on(
"session_before_compact",
async (event: SessionBeforeCompactEvent, ctx) => {
// All these should be accessible on the event
@ -63,8 +63,8 @@ describe("Documentation example", () => {
});
it("compact event should have correct fields", () => {
const checkCompactEvent = (companion: ExtensionAPI) => {
companion.on("session_compact", async (event: SessionCompactEvent) => {
const checkCompactEvent = (clanker: ExtensionAPI) => {
clanker.on("session_compact", async (event: SessionCompactEvent) => {
// These should all be accessible
const entry = event.compactionEntry;
const fromExtension = event.fromExtension;

View file

@ -5,8 +5,8 @@
import { existsSync, mkdirSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Agent } from "@mariozechner/companion-agent-core";
import { getModel } from "@mariozechner/companion-ai";
import { Agent } from "@mariozechner/clanker-agent-core";
import { getModel } from "@mariozechner/clanker-ai";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { AgentSession } from "../src/core/agent-session.js";
import { AuthStorage } from "../src/core/auth-storage.js";
@ -32,7 +32,7 @@ describe.skipIf(!API_KEY)("Compaction extensions", () => {
let capturedEvents: SessionEvent[];
beforeEach(() => {
tempDir = join(tmpdir(), `companion-compaction-extensions-test-${Date.now()}`);
tempDir = join(tmpdir(), `clanker-compaction-extensions-test-${Date.now()}`);
mkdirSync(tempDir, { recursive: true });
capturedEvents = [];
});

View file

@ -1,5 +1,5 @@
import type { AgentMessage } from "@mariozechner/companion-agent-core";
import type { AssistantMessage, Model } from "@mariozechner/companion-ai";
import type { AgentMessage } from "@mariozechner/clanker-agent-core";
import type { AssistantMessage, Model } from "@mariozechner/clanker-ai";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { generateSummary } from "../src/core/compaction/index.js";
@ -7,8 +7,8 @@ const { completeSimpleMock } = vi.hoisted(() => ({
completeSimpleMock: vi.fn(),
}));
vi.mock("@mariozechner/companion-ai", async (importOriginal) => {
const actual = await importOriginal<typeof import("@mariozechner/companion-ai")>();
vi.mock("@mariozechner/clanker-ai", async (importOriginal) => {
const actual = await importOriginal<typeof import("@mariozechner/clanker-ai")>();
return {
...actual,
completeSimple: completeSimpleMock,

View file

@ -11,8 +11,8 @@
import { existsSync, mkdirSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Agent, type ThinkingLevel } from "@mariozechner/companion-agent-core";
import { getModel, type Model } from "@mariozechner/companion-ai";
import { Agent, type ThinkingLevel } from "@mariozechner/clanker-agent-core";
import { getModel, type Model } from "@mariozechner/clanker-ai";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { AgentSession } from "../src/core/agent-session.js";
import { ModelRegistry } from "../src/core/model-registry.js";
@ -45,7 +45,7 @@ describe.skipIf(!HAS_ANTIGRAVITY_AUTH)(
});
beforeEach(() => {
tempDir = join(tmpdir(), `companion-thinking-compaction-test-${Date.now()}`);
tempDir = join(tmpdir(), `clanker-thinking-compaction-test-${Date.now()}`);
mkdirSync(tempDir, { recursive: true });
});
@ -156,7 +156,7 @@ describe.skipIf(!HAS_ANTHROPIC_AUTH)(
beforeEach(() => {
tempDir = join(
tmpdir(),
`companion-thinking-compaction-anthropic-test-${Date.now()}`,
`clanker-thinking-compaction-anthropic-test-${Date.now()}`,
);
mkdirSync(tempDir, { recursive: true });
});

View file

@ -1,6 +1,6 @@
import type { AgentMessage } from "@mariozechner/companion-agent-core";
import type { AssistantMessage, Usage } from "@mariozechner/companion-ai";
import { getModel } from "@mariozechner/companion-ai";
import type { AgentMessage } from "@mariozechner/clanker-agent-core";
import type { AssistantMessage, Usage } from "@mariozechner/clanker-ai";
import { getModel } from "@mariozechner/clanker-ai";
import { readFileSync } from "fs";
import { join } from "path";
import { beforeEach, describe, expect, it } from "vitest";

View file

@ -79,7 +79,7 @@ function createMockComputerOperations(
function getAgentComputerScriptPath(): string {
return resolve(
process.cwd(),
"../../../../docker/companion/agent-computer.js",
"../../../../docker/clanker/agent-computer.js",
);
}

View file

@ -12,7 +12,7 @@ describe("extensions discovery", () => {
let extensionsDir: string;
beforeEach(() => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "companion-ext-test-"));
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "clanker-ext-test-"));
extensionsDir = path.join(tempDir, "extensions");
fs.mkdirSync(extensionsDir);
});
@ -22,15 +22,15 @@ describe("extensions discovery", () => {
});
const extensionCode = `
export default function(companion) {
companion.registerCommand("test", { handler: async () => {} });
export default function(clanker) {
clanker.registerCommand("test", { handler: async () => {} });
}
`;
const extensionCodeWithTool = (toolName: string) => `
import { Type } from "@sinclair/typebox";
export default function(companion) {
companion.registerTool({
export default function(clanker) {
clanker.registerTool({
name: "${toolName}",
label: "${toolName}",
description: "Test tool",
@ -102,7 +102,7 @@ describe("extensions discovery", () => {
expect(result.extensions[0].path).toContain("index.ts");
});
it("discovers subdirectory with package.json companion field", async () => {
it("discovers subdirectory with package.json clanker field", async () => {
const subdir = path.join(extensionsDir, "my-package");
const srcDir = path.join(subdir, "src");
fs.mkdirSync(subdir);
@ -112,7 +112,7 @@ describe("extensions discovery", () => {
path.join(subdir, "package.json"),
JSON.stringify({
name: "my-package",
companion: {
clanker: {
extensions: ["./src/main.ts"],
},
}),
@ -135,7 +135,7 @@ describe("extensions discovery", () => {
path.join(subdir, "package.json"),
JSON.stringify({
name: "my-package",
companion: {
clanker: {
extensions: ["./ext1.ts", "./ext2.ts"],
},
}),
@ -147,7 +147,7 @@ describe("extensions discovery", () => {
expect(result.extensions).toHaveLength(2);
});
it("package.json with companion field takes precedence over index.ts", async () => {
it("package.json with clanker field takes precedence over index.ts", async () => {
const subdir = path.join(extensionsDir, "my-package");
fs.mkdirSync(subdir);
fs.writeFileSync(
@ -162,7 +162,7 @@ describe("extensions discovery", () => {
path.join(subdir, "package.json"),
JSON.stringify({
name: "my-package",
companion: {
clanker: {
extensions: ["./custom.ts"],
},
}),
@ -178,7 +178,7 @@ describe("extensions discovery", () => {
expect(result.extensions[0].tools.has("from-index")).toBe(false);
});
it("ignores package.json without companion field, falls back to index.ts", async () => {
it("ignores package.json without clanker field, falls back to index.ts", async () => {
const subdir = path.join(extensionsDir, "my-package");
fs.mkdirSync(subdir);
fs.writeFileSync(path.join(subdir, "index.ts"), extensionCode);
@ -238,7 +238,7 @@ describe("extensions discovery", () => {
fs.writeFileSync(path.join(subdir2, "entry.ts"), extensionCode);
fs.writeFileSync(
path.join(subdir2, "package.json"),
JSON.stringify({ companion: { extensions: ["./entry.ts"] } }),
JSON.stringify({ clanker: { extensions: ["./entry.ts"] } }),
);
const result = await discoverAndLoadExtensions([], tempDir, tempDir);
@ -254,7 +254,7 @@ describe("extensions discovery", () => {
fs.writeFileSync(
path.join(subdir, "package.json"),
JSON.stringify({
companion: {
clanker: {
extensions: ["./exists.ts", "./missing.ts"],
},
}),
@ -331,8 +331,8 @@ describe("extensions discovery", () => {
`
import { Type } from "@sinclair/typebox";
import ms from "ms";
export default function(companion) {
companion.registerTool({
export default function(clanker) {
clanker.registerTool({
name: "parse_duration",
label: "parse_duration",
description: "Parse a duration string",
@ -375,8 +375,8 @@ describe("extensions discovery", () => {
it("registers message renderers", async () => {
const extCode = `
export default function(companion) {
companion.registerMessageRenderer("my-custom-type", (message, options, theme) => {
export default function(clanker) {
clanker.registerMessageRenderer("my-custom-type", (message, options, theme) => {
return null; // Use default rendering
});
}
@ -394,7 +394,7 @@ describe("extensions discovery", () => {
it("reports error when extension throws during initialization", async () => {
const extCode = `
export default function(companion) {
export default function(clanker) {
throw new Error("Initialization failed!");
}
`;
@ -409,8 +409,8 @@ describe("extensions discovery", () => {
it("reports error when extension has no default export", async () => {
const extCode = `
export function notDefault(companion) {
companion.registerCommand("test", { handler: async () => {} });
export function notDefaultclanker {
clanker.registerCommand("test", { handler: async () => {} });
}
`;
fs.writeFileSync(path.join(extensionsDir, "no-default.ts"), extCode);
@ -451,10 +451,10 @@ describe("extensions discovery", () => {
it("loads extension with event handlers", async () => {
const extCode = `
export default function(companion) {
companion.on("agent_start", async () => {});
companion.on("tool_call", async (event) => undefined);
companion.on("agent_end", async () => {});
export default function(clanker) {
clanker.on("agent_start", async () => {});
clanker.on("tool_call", async (event) => undefined);
clanker.on("agent_end", async () => {});
}
`;
fs.writeFileSync(path.join(extensionsDir, "with-handlers.ts"), extCode);
@ -470,8 +470,8 @@ describe("extensions discovery", () => {
it("loads extension with shortcuts", async () => {
const extCode = `
export default function(companion) {
companion.registerShortcut("ctrl+t", {
export default function(clanker) {
clanker.registerShortcut("ctrl+t", {
description: "Test shortcut",
handler: async (ctx) => {},
});
@ -488,8 +488,8 @@ describe("extensions discovery", () => {
it("loads extension with flags", async () => {
const extCode = `
export default function(companion) {
companion.registerFlag("my-flag", {
export default function(clanker) {
clanker.registerFlag("my-flag", {
description: "My custom flag",
handler: async (value) => {},
});

View file

@ -13,7 +13,7 @@ describe("Input Event", () => {
let extensionsDir: string;
beforeEach(() => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "companion-input-test-"));
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "clanker-input-test-"));
extensionsDir = path.join(tempDir, "extensions");
fs.mkdirSync(extensionsDir);
// Clean globalThis test vars

View file

@ -28,7 +28,7 @@ describe("ExtensionRunner", () => {
let modelRegistry: ModelRegistry;
beforeEach(() => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "companion-runner-test-"));
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "clanker-runner-test-"));
extensionsDir = path.join(tempDir, "extensions");
fs.mkdirSync(extensionsDir);
sessionManager = SessionManager.inMemory();
@ -88,8 +88,8 @@ describe("ExtensionRunner", () => {
describe("shortcut conflicts", () => {
it("warns when extension shortcut conflicts with built-in", async () => {
const extCode = `
export default function(companion) {
companion.registerShortcut("ctrl+c", {
export default function(clanker) {
clanker.registerShortcut("ctrl+c", {
description: "Conflicts with built-in",
handler: async () => {},
});
@ -119,8 +119,8 @@ describe("ExtensionRunner", () => {
it("allows a shortcut when the reserved set no longer contains the default key", async () => {
const extCode = `
export default function(companion) {
companion.registerShortcut("ctrl+p", {
export default function(clanker) {
clanker.registerShortcut("ctrl+p", {
description: "Uses freed default",
handler: async () => {},
});
@ -154,8 +154,8 @@ describe("ExtensionRunner", () => {
it("warns but allows when extension uses non-reserved built-in shortcut", async () => {
const extCode = `
export default function(companion) {
companion.registerShortcut("ctrl+v", {
export default function(clanker) {
clanker.registerShortcut("ctrl+v", {
description: "Overrides non-reserved",
handler: async () => {},
});
@ -185,8 +185,8 @@ describe("ExtensionRunner", () => {
it("blocks shortcuts for reserved actions even when rebound", async () => {
const extCode = `
export default function(companion) {
companion.registerShortcut("ctrl+x", {
export default function(clanker) {
clanker.registerShortcut("ctrl+x", {
description: "Conflicts with rebound reserved",
handler: async () => {},
});
@ -223,8 +223,8 @@ describe("ExtensionRunner", () => {
it("blocks shortcuts when reserved action has multiple keys", async () => {
const extCode = `
export default function(companion) {
companion.registerShortcut("ctrl+y", {
export default function(clanker) {
clanker.registerShortcut("ctrl+y", {
description: "Conflicts with multi-key reserved",
handler: async () => {},
});
@ -258,8 +258,8 @@ describe("ExtensionRunner", () => {
it("warns but allows when non-reserved action has multiple keys", async () => {
const extCode = `
export default function(companion) {
companion.registerShortcut("ctrl+y", {
export default function(clanker) {
clanker.registerShortcut("ctrl+y", {
description: "Overrides multi-key non-reserved",
handler: async () => {},
});
@ -297,16 +297,16 @@ describe("ExtensionRunner", () => {
it("warns when two extensions register same shortcut", async () => {
// Use a non-reserved shortcut
const extCode1 = `
export default function(companion) {
companion.registerShortcut("ctrl+shift+x", {
export default function(clanker) {
clanker.registerShortcut("ctrl+shift+x", {
description: "First extension",
handler: async () => {},
});
}
`;
const extCode2 = `
export default function(companion) {
companion.registerShortcut("ctrl+shift+x", {
export default function(clanker) {
clanker.registerShortcut("ctrl+shift+x", {
description: "Second extension",
handler: async () => {},
});
@ -341,8 +341,8 @@ describe("ExtensionRunner", () => {
it("collects tools from multiple extensions", async () => {
const toolCode = (name: string) => `
import { Type } from "@sinclair/typebox";
export default function(companion) {
companion.registerTool({
export default function(clanker) {
clanker.registerTool({
name: "${name}",
label: "${name}",
description: "Test tool",
@ -380,8 +380,8 @@ describe("ExtensionRunner", () => {
it("keeps first tool when two extensions register the same name", async () => {
const first = `
import { Type } from "@sinclair/typebox";
export default function(companion) {
companion.registerTool({
export default function(clanker) {
clanker.registerTool({
name: "shared",
label: "shared",
description: "first",
@ -392,8 +392,8 @@ describe("ExtensionRunner", () => {
`;
const second = `
import { Type } from "@sinclair/typebox";
export default function(companion) {
companion.registerTool({
export default function(clanker) {
clanker.registerTool({
name: "shared",
label: "shared",
description: "second",
@ -423,8 +423,8 @@ describe("ExtensionRunner", () => {
describe("command collection", () => {
it("collects commands from multiple extensions", async () => {
const cmdCode = (name: string) => `
export default function(companion) {
companion.registerCommand("${name}", {
export default function(clanker) {
clanker.registerCommand("${name}", {
description: "Test command",
handler: async () => {},
});
@ -449,8 +449,8 @@ describe("ExtensionRunner", () => {
it("gets command by name", async () => {
const cmdCode = `
export default function(companion) {
companion.registerCommand("my-cmd", {
export default function(clanker) {
clanker.registerCommand("my-cmd", {
description: "My command",
handler: async () => {},
});
@ -478,8 +478,8 @@ describe("ExtensionRunner", () => {
it("filters out commands conflict with reseved", async () => {
const cmdCode = (name: string) => `
export default function(companion) {
companion.registerCommand("${name}", {
export default function(clanker) {
clanker.registerCommand("${name}", {
description: "Test command",
handler: async () => {},
});
@ -517,8 +517,8 @@ describe("ExtensionRunner", () => {
describe("error handling", () => {
it("calls error listeners when handler throws", async () => {
const extCode = `
export default function(companion) {
companion.on("context", async () => {
export default function(clanker) {
clanker.on("context", async () => {
throw new Error("Handler error!");
});
}
@ -555,8 +555,8 @@ describe("ExtensionRunner", () => {
describe("message renderers", () => {
it("gets message renderer by type", async () => {
const extCode = `
export default function(companion) {
companion.registerMessageRenderer("my-type", (message, options, theme) => null);
export default function(clanker) {
clanker.registerMessageRenderer("my-type", (message, options, theme) => null);
}
`;
fs.writeFileSync(path.join(extensionsDir, "renderer.ts"), extCode);
@ -581,8 +581,8 @@ describe("ExtensionRunner", () => {
describe("flags", () => {
it("collects flags from extensions", async () => {
const extCode = `
export default function(companion) {
companion.registerFlag("my-flag", {
export default function(clanker) {
clanker.registerFlag("my-flag", {
description: "My flag",
handler: async () => {},
});
@ -605,8 +605,8 @@ describe("ExtensionRunner", () => {
it("keeps first flag when two extensions register the same name", async () => {
const first = `
export default function(companion) {
companion.registerFlag("shared-flag", {
export default function(clanker) {
clanker.registerFlag("shared-flag", {
description: "first",
type: "boolean",
default: true,
@ -614,8 +614,8 @@ describe("ExtensionRunner", () => {
}
`;
const second = `
export default function(companion) {
companion.registerFlag("shared-flag", {
export default function(clanker) {
clanker.registerFlag("shared-flag", {
description: "second",
type: "boolean",
default: false,
@ -641,8 +641,8 @@ describe("ExtensionRunner", () => {
it("can set flag values", async () => {
const extCode = `
export default function(companion) {
companion.registerFlag("test-flag", {
export default function(clanker) {
clanker.registerFlag("test-flag", {
description: "Test flag",
handler: async () => {},
});
@ -670,8 +670,8 @@ describe("ExtensionRunner", () => {
describe("tool_result chaining", () => {
it("chains content modifications across handlers", async () => {
const extCode1 = `
export default function(companion) {
companion.on("tool_result", async (event) => {
export default function(clanker) {
clanker.on("tool_result", async (event) => {
return {
content: [...event.content, { type: "text", text: "ext1" }],
};
@ -679,8 +679,8 @@ describe("ExtensionRunner", () => {
}
`;
const extCode2 = `
export default function(companion) {
companion.on("tool_result", async (event) => {
export default function(clanker) {
clanker.on("tool_result", async (event) => {
return {
content: [...event.content, { type: "text", text: "ext2" }],
};
@ -726,8 +726,8 @@ describe("ExtensionRunner", () => {
it("preserves previous modifications when later handlers return partial patches", async () => {
const extCode1 = `
export default function(companion) {
companion.on("tool_result", async () => {
export default function(clanker) {
clanker.on("tool_result", async () => {
return {
content: [{ type: "text", text: "first" }],
details: { source: "ext1" },
@ -736,8 +736,8 @@ describe("ExtensionRunner", () => {
}
`;
const extCode2 = `
export default function(companion) {
companion.on("tool_result", async () => {
export default function(clanker) {
clanker.on("tool_result", async () => {
return {
isError: true,
};
@ -834,8 +834,8 @@ describe("ExtensionRunner", () => {
describe("hasHandlers", () => {
it("returns true when handlers exist for event type", async () => {
const extCode = `
export default function(companion) {
companion.on("tool_call", async () => undefined);
export default function(clanker) {
clanker.on("tool_call", async () => undefined);
}
`;
fs.writeFileSync(path.join(extensionsDir, "handler.ts"), extCode);

View file

@ -1,4 +1,4 @@
import { visibleWidth } from "@mariozechner/companion-tui";
import { visibleWidth } from "@mariozechner/clanker-tui";
import { beforeAll, describe, expect, it } from "vitest";
import type { AgentSession } from "../src/core/agent-session.js";
import type { ReadonlyFooterDataProvider } from "../src/core/footer-data-provider.js";

View file

@ -12,7 +12,7 @@ function createMockSession(options?: { sessionName?: string }) {
dispose: vi.fn(),
subscribe: vi.fn(() => () => {}),
sessionManager: {
getSessionDir: () => "/tmp/companion-gateway-test",
getSessionDir: () => "/tmp/clanker-gateway-test",
appendSessionInfo: vi.fn(),
appendLabelChange: vi.fn(),
},

View file

@ -13,7 +13,7 @@ function createMockSession() {
dispose: vi.fn(),
subscribe: vi.fn(() => () => {}),
sessionManager: {
getSessionDir: () => "/tmp/companion-gateway-test",
getSessionDir: () => "/tmp/clanker-gateway-test",
},
};
}

View file

@ -313,22 +313,22 @@ describe("DefaultPackageManager git update", () => {
.slice(0, 8);
const cachedDir = join(
tmpdir(),
"companion-extensions",
"clanker-extensions",
`git-${gitHost}`,
hash,
gitPath,
);
const extensionFile = join(
cachedDir,
"companion-extensions",
"clanker-extensions",
"session-breakdown.ts",
);
rmSync(cachedDir, { recursive: true, force: true });
mkdirSync(join(cachedDir, "companion-extensions"), { recursive: true });
mkdirSync(join(cachedDir, "clanker-extensions"), { recursive: true });
writeFileSync(
join(cachedDir, "package.json"),
JSON.stringify({ companion: { extensions: ["./companion-extensions"] } }, null, 2),
JSON.stringify({ clanker: { extensions: ["./clanker-extensions"] } }, null, 2),
);
writeFileSync(extensionFile, "// stale");
@ -353,7 +353,7 @@ describe("DefaultPackageManager git update", () => {
expect(executedCommands).toContain("git fetch --prune origin");
expect(
getFileContent(cachedDir, "companion-extensions/session-breakdown.ts"),
getFileContent(cachedDir, "clanker-extensions/session-breakdown.ts"),
).toBe("// fresh");
});
@ -366,22 +366,22 @@ describe("DefaultPackageManager git update", () => {
.slice(0, 8);
const cachedDir = join(
tmpdir(),
"companion-extensions",
"clanker-extensions",
`git-${gitHost}`,
hash,
gitPath,
);
const extensionFile = join(
cachedDir,
"companion-extensions",
"clanker-extensions",
"session-breakdown.ts",
);
rmSync(cachedDir, { recursive: true, force: true });
mkdirSync(join(cachedDir, "companion-extensions"), { recursive: true });
mkdirSync(join(cachedDir, "clanker-extensions"), { recursive: true });
writeFileSync(
join(cachedDir, "package.json"),
JSON.stringify({ companion: { extensions: ["./companion-extensions"] } }, null, 2),
JSON.stringify({ clanker: { extensions: ["./clanker-extensions"] } }, null, 2),
);
writeFileSync(extensionFile, "// pinned");
@ -403,7 +403,7 @@ describe("DefaultPackageManager git update", () => {
expect(executedCommands).toEqual([]);
expect(
getFileContent(cachedDir, "companion-extensions/session-breakdown.ts"),
getFileContent(cachedDir, "clanker-extensions/session-breakdown.ts"),
).toBe("// pinned");
});
});
@ -418,7 +418,7 @@ describe("DefaultPackageManager git update", () => {
// The project-scope install path should not exist before or after update
const projectGitDir = join(
tempDir,
".companion",
".clanker",
"git",
"github.com",
"test",

View file

@ -1,4 +1,4 @@
import { Container } from "@mariozechner/companion-tui";
import { Container } from "@mariozechner/clanker-tui";
import { beforeAll, describe, expect, test, vi } from "vitest";
import { InteractiveMode } from "../src/modes/interactive/interactive-mode.js";
import { initTheme } from "../src/modes/interactive/theme/theme.js";

View file

@ -12,9 +12,9 @@ import type {
Context,
Model,
OpenAICompletionsCompat,
} from "@mariozechner/companion-ai";
import { getApiProvider } from "@mariozechner/companion-ai";
import { getOAuthProvider } from "@mariozechner/companion-ai/oauth";
} from "@mariozechner/clanker-ai";
import { getApiProvider } from "@mariozechner/clanker-ai";
import { getOAuthProvider } from "@mariozechner/clanker-ai/oauth";
import { afterEach, beforeEach, describe, expect, test } from "vitest";
import { AuthStorage } from "../src/core/auth-storage.js";
import { clearApiKeyCache, ModelRegistry } from "../src/core/model-registry.js";
@ -27,7 +27,7 @@ describe("ModelRegistry", () => {
beforeEach(() => {
tempDir = join(
tmpdir(),
`companion-test-model-registry-${Date.now()}-${Math.random().toString(36).slice(2)}`,
`clanker-test-model-registry-${Date.now()}-${Math.random().toString(36).slice(2)}`,
);
mkdirSync(tempDir, { recursive: true });
modelsJsonPath = join(tempDir, "models.json");

View file

@ -1,4 +1,4 @@
import type { Model } from "@mariozechner/companion-ai";
import type { Model } from "@mariozechner/clanker-ai";
import { describe, expect, test } from "vitest";
import {
defaultModelPerProvider,

View file

@ -17,7 +17,7 @@ describe("package commands", () => {
beforeEach(() => {
tempDir = join(
tmpdir(),
`companion-package-commands-${Date.now()}-${Math.random().toString(36).slice(2)}`,
`clanker-package-commands-${Date.now()}-${Math.random().toString(36).slice(2)}`,
);
agentDir = join(tempDir, "agent");
projectDir = join(tempDir, "project");
@ -89,7 +89,7 @@ describe("package commands", () => {
.map(([message]) => String(message))
.join("\n");
expect(stdout).toContain("Usage:");
expect(stdout).toContain("companion install <source> [-l]");
expect(stdout).toContain("clanker install <source> [-l]");
expect(errorSpy).not.toHaveBeenCalled();
expect(process.exitCode).toBeUndefined();
} finally {
@ -109,7 +109,7 @@ describe("package commands", () => {
.join("\n");
expect(stderr).toContain('Unknown option --unknown for "install".');
expect(stderr).toContain(
'Use "companion --help" or "companion install <source> [-l]".',
'Use "clanker --help" or "clanker install <source> [-l]".',
);
expect(process.exitCode).toBe(1);
} finally {
@ -127,7 +127,7 @@ describe("package commands", () => {
.map(([message]) => String(message))
.join("\n");
expect(stderr).toContain("Missing install source.");
expect(stderr).toContain("Usage: companion install <source> [-l]");
expect(stderr).toContain("Usage: clanker install <source> [-l]");
expect(stderr).not.toContain("at ");
expect(process.exitCode).toBe(1);
} finally {

View file

@ -36,8 +36,8 @@ describe("DefaultPackageManager", () => {
let previousOfflineEnv: string | undefined;
beforeEach(() => {
previousOfflineEnv = process.env.COMPANION_OFFLINE;
delete process.env.COMPANION_OFFLINE;
previousOfflineEnv = process.env.CLANKER_OFFLINE;
delete process.env.CLANKER_OFFLINE;
tempDir = join(
tmpdir(),
`pm-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
@ -56,9 +56,9 @@ describe("DefaultPackageManager", () => {
afterEach(() => {
if (previousOfflineEnv === undefined) {
delete process.env.COMPANION_OFFLINE;
delete process.env.CLANKER_OFFLINE;
} else {
process.env.COMPANION_OFFLINE = previousOfflineEnv;
process.env.CLANKER_OFFLINE = previousOfflineEnv;
}
vi.restoreAllMocks();
vi.unstubAllGlobals();
@ -114,8 +114,8 @@ Content`,
);
});
it("should resolve project paths relative to .companion", async () => {
const extDir = join(tempDir, ".companion", "extensions");
it("should resolve project paths relative to .clanker", async () => {
const extDir = join(tempDir, ".clanker", "extensions");
mkdirSync(extDir, { recursive: true });
const extPath = join(extDir, "project-ext.ts");
writeFileSync(extPath, "export default function() {}");
@ -143,7 +143,7 @@ Content`,
});
it("should auto-discover project prompts with overrides", async () => {
const promptsDir = join(tempDir, ".companion", "prompts");
const promptsDir = join(tempDir, ".clanker", "prompts");
mkdirSync(promptsDir, { recursive: true });
const promptPath = join(promptsDir, "is.md");
writeFileSync(promptPath, "Is prompt");
@ -156,15 +156,15 @@ Content`,
).toBe(true);
});
it("should resolve directory with package.json companion.extensions in extensions setting", async () => {
// Create a package with companion.extensions in package.json
it("should resolve directory with package.json clanker.extensions in extensions setting", async () => {
// Create a package with clanker.extensions in package.json
const pkgDir = join(tempDir, "my-extensions-pkg");
mkdirSync(join(pkgDir, "extensions"), { recursive: true });
writeFileSync(
join(pkgDir, "package.json"),
JSON.stringify({
name: "my-extensions-pkg",
companion: {
clanker: {
extensions: ["./extensions/clip.ts", "./extensions/cost.ts"],
},
}),
@ -187,7 +187,7 @@ Content`,
const result = await packageManager.resolve();
// Should find the extensions declared in package.json companion.extensions
// Should find the extensions declared in package.json clanker.extensions
expect(
result.extensions.some(
(r) => r.path === join(pkgDir, "extensions", "clip.ts") && r.enabled,
@ -355,10 +355,10 @@ Content`,
).toBe(false);
});
it("should not apply parent .gitignore to .companion auto-discovery", async () => {
writeFileSync(join(tempDir, ".gitignore"), ".companion\n");
it("should not apply parent .gitignore to .clanker auto-discovery", async () => {
writeFileSync(join(tempDir, ".gitignore"), ".clanker\n");
const skillDir = join(tempDir, ".companion", "skills", "auto-skill");
const skillDir = join(tempDir, ".clanker", "skills", "auto-skill");
mkdirSync(skillDir, { recursive: true });
const skillPath = join(skillDir, "SKILL.md");
writeFileSync(
@ -384,14 +384,14 @@ Content`,
).toBe(true);
});
it("should handle directories with companion manifest", async () => {
it("should handle directories with clanker manifest", async () => {
const pkgDir = join(tempDir, "my-package");
mkdirSync(pkgDir, { recursive: true });
writeFileSync(
join(pkgDir, "package.json"),
JSON.stringify({
name: "my-package",
companion: {
clanker: {
extensions: ["./src/index.ts"],
skills: ["./skills"],
},
@ -565,7 +565,7 @@ Content`,
expect(settings.packages?.[0]).toBe(expected);
});
it("should store project local packages relative to .companion settings base", () => {
it("should store project local packages relative to .clanker settings base", () => {
const projectPkgDir = join(tempDir, "project-local-pkg");
mkdirSync(join(projectPkgDir, "extensions"), { recursive: true });
writeFileSync(
@ -579,7 +579,7 @@ Content`,
expect(added).toBe(true);
const settings = settingsManager.getProjectSettings();
const rel = relative(join(tempDir, ".companion"), projectPkgDir);
const rel = relative(join(tempDir, ".clanker"), projectPkgDir);
const expected = rel.startsWith(".") ? rel : `./${rel}`;
expect(settings.packages?.[0]).toBe(expected);
});
@ -837,7 +837,7 @@ Content`,
});
});
describe("pattern filtering in companion manifest", () => {
describe("pattern filtering in clanker manifest", () => {
it("should support glob patterns in manifest extensions", async () => {
const pkgDir = join(tempDir, "manifest-pkg");
mkdirSync(join(pkgDir, "extensions"), { recursive: true });
@ -860,7 +860,7 @@ Content`,
join(pkgDir, "package.json"),
JSON.stringify({
name: "manifest-pkg",
companion: {
clanker: {
extensions: [
"extensions",
"node_modules/dep/extensions",
@ -898,7 +898,7 @@ Content`,
join(pkgDir, "package.json"),
JSON.stringify({
name: "skill-manifest-pkg",
companion: {
clanker: {
skills: ["skills", "!**/bad-skill"],
},
}),
@ -936,7 +936,7 @@ Content`,
join(pkgDir, "package.json"),
JSON.stringify({
name: "layered-pkg",
companion: {
clanker: {
extensions: ["extensions", "!**/baz.ts"],
},
}),
@ -1229,7 +1229,7 @@ Content`,
join(pkgDir, "package.json"),
JSON.stringify({
name: "manifest-force-pkg",
companion: {
clanker: {
extensions: ["extensions", "!**/two.ts", "+extensions/two.ts"],
},
}),
@ -1515,7 +1515,7 @@ export default function(api) { api.registerTool({ name: "test", description: "te
);
});
it("should respect package.json companion.extensions manifest in subdirectories", async () => {
it("should respect package.json clanker.extensions manifest in subdirectories", async () => {
const pkgDir = join(tempDir, "manifest-subdir-pkg");
mkdirSync(join(pkgDir, "extensions", "custom"), { recursive: true });
@ -1523,7 +1523,7 @@ export default function(api) { api.registerTool({ name: "test", description: "te
writeFileSync(
join(pkgDir, "extensions", "custom", "package.json"),
JSON.stringify({
companion: {
clanker: {
extensions: ["./main.ts"],
},
}),
@ -1634,7 +1634,7 @@ export default function(api) { api.registerTool({ name: "test", description: "te
describe("offline mode and network timeouts", () => {
it("should skip installing missing package sources when offline", async () => {
process.env.COMPANION_OFFLINE = "1";
process.env.CLANKER_OFFLINE = "1";
settingsManager.setProjectPackages([
"npm:missing-package",
"git:github.com/example/missing-repo",
@ -1659,7 +1659,7 @@ export default function(api) { api.registerTool({ name: "test", description: "te
});
it("should skip refreshing temporary git sources when offline", async () => {
process.env.COMPANION_OFFLINE = "1";
process.env.CLANKER_OFFLINE = "1";
const gitSource = "git:github.com/example/repo";
const parsedGitSource = (packageManager as any).parseSource(gitSource);
const installedPath = (packageManager as any).getGitInstallPath(
@ -1690,7 +1690,7 @@ export default function(api) { api.registerTool({ name: "test", description: "te
});
it("should not call fetch in npmNeedsUpdate when offline", async () => {
process.env.COMPANION_OFFLINE = "1";
process.env.CLANKER_OFFLINE = "1";
const installedPath = join(tempDir, "installed-package");
mkdirSync(installedPath, { recursive: true });
writeFileSync(

View file

@ -60,7 +60,7 @@ Skill content here.`,
});
it("should ignore extra markdown files in auto-discovered skill dirs", async () => {
const skillDir = join(agentDir, "skills", "companion-skills", "browser-tools");
const skillDir = join(agentDir, "skills", "clanker-skills", "browser-tools");
mkdirSync(skillDir, { recursive: true });
writeFileSync(
join(skillDir, "SKILL.md"),
@ -102,7 +102,7 @@ Prompt content.`,
it("should prefer project resources over user on name collisions", async () => {
const userPromptsDir = join(agentDir, "prompts");
const projectPromptsDir = join(cwd, ".companion", "prompts");
const projectPromptsDir = join(cwd, ".clanker", "prompts");
mkdirSync(userPromptsDir, { recursive: true });
mkdirSync(projectPromptsDir, { recursive: true });
const userPromptPath = join(userPromptsDir, "commit.md");
@ -111,7 +111,7 @@ Prompt content.`,
writeFileSync(projectPromptPath, "Project prompt");
const userSkillDir = join(agentDir, "skills", "collision-skill");
const projectSkillDir = join(cwd, ".companion", "skills", "collision-skill");
const projectSkillDir = join(cwd, ".clanker", "skills", "collision-skill");
mkdirSync(userSkillDir, { recursive: true });
mkdirSync(projectSkillDir, { recursive: true });
const userSkillPath = join(userSkillDir, "SKILL.md");
@ -148,9 +148,9 @@ Project skill`,
) as { name: string; vars?: Record<string, string> };
baseTheme.name = "collision-theme";
const userThemePath = join(agentDir, "themes", "collision.json");
const projectThemePath = join(cwd, ".companion", "themes", "collision.json");
const projectThemePath = join(cwd, ".clanker", "themes", "collision.json");
mkdirSync(join(agentDir, "themes"), { recursive: true });
mkdirSync(join(cwd, ".companion", "themes"), { recursive: true });
mkdirSync(join(cwd, ".clanker", "themes"), { recursive: true });
writeFileSync(userThemePath, JSON.stringify(baseTheme, null, 2));
if (baseTheme.vars) {
baseTheme.vars.accent = "#ff00ff";
@ -178,18 +178,18 @@ Project skill`,
it("should keep both extensions loaded when command names collide", async () => {
const userExtDir = join(agentDir, "extensions");
const projectExtDir = join(cwd, ".companion", "extensions");
const projectExtDir = join(cwd, ".clanker", "extensions");
mkdirSync(userExtDir, { recursive: true });
mkdirSync(projectExtDir, { recursive: true });
writeFileSync(
join(projectExtDir, "project.ts"),
`export default function(companion) {
companion.registerCommand("deploy", {
`export default function(clanker) {
clanker.registerCommand("deploy", {
description: "project deploy",
handler: async () => {},
});
companion.registerCommand("project-only", {
clanker.registerCommand("project-only", {
description: "project only",
handler: async () => {},
});
@ -198,12 +198,12 @@ Project skill`,
writeFileSync(
join(userExtDir, "user.ts"),
`export default function(companion) {
companion.registerCommand("deploy", {
`export default function(clanker) {
clanker.registerCommand("deploy", {
description: "user deploy",
handler: async () => {},
});
companion.registerCommand("user-only", {
clanker.registerCommand("user-only", {
description: "user only",
handler: async () => {},
});
@ -320,13 +320,13 @@ Content`,
expect(agentsFiles.some((f) => f.path.endsWith("SOUL.md"))).toBe(true);
});
it("should discover companion context files from the default workspace", async () => {
it("should discover clanker context files from the default workspace", async () => {
const workspaceDir = join(tempDir, "workspace");
const appDir = join(tempDir, "apps", "todo-app");
mkdirSync(workspaceDir, { recursive: true });
mkdirSync(appDir, { recursive: true });
writeFileSync(join(workspaceDir, "IDENTITY.md"), "# Identity\n\nPi");
writeFileSync(join(workspaceDir, "TOOLS.md"), "# Tools\n\nUse ~/.companion");
writeFileSync(join(workspaceDir, "TOOLS.md"), "# Tools\n\nUse ~/.clanker");
const loader = new DefaultResourceLoader({ cwd: appDir, agentDir });
await loader.reload();
@ -338,8 +338,8 @@ Content`,
expect(agentsFiles.some((f) => f.path.endsWith("TOOLS.md"))).toBe(true);
});
it("should discover SYSTEM.md from cwd/.companion", async () => {
const piDir = join(cwd, ".companion");
it("should discover SYSTEM.md from cwd/.clanker", async () => {
const piDir = join(cwd, ".clanker");
mkdirSync(piDir, { recursive: true });
writeFileSync(join(piDir, "SYSTEM.md"), "You are a helpful assistant.");
@ -350,7 +350,7 @@ Content`,
});
it("should discover APPEND_SYSTEM.md", async () => {
const piDir = join(cwd, ".companion");
const piDir = join(cwd, ".clanker");
mkdirSync(piDir, { recursive: true });
writeFileSync(
join(piDir, "APPEND_SYSTEM.md"),
@ -528,10 +528,10 @@ Content`,
writeFileSync(
join(ext1Dir, "index.ts"),
`
import type { ExtensionAPI } from "@mariozechner/companion-coding-agent";
import type { ExtensionAPI } from "@mariozechner/clanker-coding-agent";
import { Type } from "@sinclair/typebox";
export default function(companion: ExtensionAPI) {
companion.registerTool({
export default function(clanker: ExtensionAPI) {
clanker.registerTool({
name: "duplicate-tool",
description: "First",
parameters: Type.Object({}),
@ -543,10 +543,10 @@ export default function(companion: ExtensionAPI) {
writeFileSync(
join(ext2Dir, "index.ts"),
`
import type { ExtensionAPI } from "@mariozechner/companion-coding-agent";
import type { ExtensionAPI } from "@mariozechner/clanker-coding-agent";
import { Type } from "@sinclair/typebox";
export default function(companion: ExtensionAPI) {
companion.registerTool({
export default function(clanker: ExtensionAPI) {
clanker.registerTool({
name: "duplicate-tool",
description: "Second",
parameters: Type.Object({}),

View file

@ -2,7 +2,7 @@ import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import type { AgentEvent } from "@mariozechner/companion-agent-core";
import type { AgentEvent } from "@mariozechner/clanker-agent-core";
import { afterEach, beforeEach, describe, expect, test } from "vitest";
import { RpcClient } from "../src/modes/rpc/rpc-client.js";
@ -18,11 +18,11 @@ describe.skipIf(
let sessionDir: string;
beforeEach(() => {
sessionDir = join(tmpdir(), `companion-rpc-test-${Date.now()}`);
sessionDir = join(tmpdir(), `clanker-rpc-test-${Date.now()}`);
client = new RpcClient({
cliPath: join(__dirname, "..", "dist", "cli.js"),
cwd: join(__dirname, ".."),
env: { COMPANION_CODING_AGENT_DIR: sessionDir },
env: { CLANKER_CODING_AGENT_DIR: sessionDir },
provider: "anthropic",
model: "claude-sonnet-4-5",
});

View file

@ -14,12 +14,12 @@ describe("createAgentSession skills option", () => {
beforeEach(() => {
tempDir = join(
tmpdir(),
`companion-sdk-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
`clanker-sdk-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
);
skillsDir = join(tempDir, "skills", "test-skill");
mkdirSync(skillsDir, { recursive: true });
// Create a test skill in the companion skills directory
// Create a test skill in the clanker skills directory
writeFileSync(
join(skillsDir, "SKILL.md"),
`---

View file

@ -47,7 +47,7 @@ describe("SessionInfo.modified", () => {
});
it("uses last user/assistant message timestamp instead of file mtime", async () => {
const filePath = join(tmpdir(), `companion-session-${Date.now()}-modified.jsonl`);
const filePath = join(tmpdir(), `clanker-session-${Date.now()}-modified.jsonl`);
createSessionFile(filePath);
const before = await stat(filePath);

View file

@ -2,7 +2,7 @@ import {
DEFAULT_EDITOR_KEYBINDINGS,
EditorKeybindingsManager,
setEditorKeybindings,
} from "@mariozechner/companion-tui";
} from "@mariozechner/clanker-tui";
import { beforeAll, beforeEach, describe, expect, it } from "vitest";
import { KeybindingsManager } from "../src/core/keybindings.js";
import type { SessionInfo } from "../src/core/session-manager.js";

View file

@ -7,7 +7,7 @@ import { SettingsManager } from "../src/core/settings-manager.js";
* Tests for the fix to a bug where external file changes to arrays were overwritten.
*
* The bug scenario was:
* 1. Companion starts with settings.json containing packages: ["npm:some-pkg"]
* 1. Clanker starts with settings.json containing packages: ["npm:some-pkg"]
* 2. User externally edits file to packages: []
* 3. User changes an unrelated setting (e.g., theme) via UI
* 4. save() would overwrite packages back to ["npm:some-pkg"] from stale in-memory state
@ -25,7 +25,7 @@ describe("SettingsManager - External Edit Preservation", () => {
rmSync(testDir, { recursive: true });
}
mkdirSync(agentDir, { recursive: true });
mkdirSync(join(projectDir, ".companion"), { recursive: true });
mkdirSync(join(projectDir, ".clanker"), { recursive: true });
});
afterEach(() => {
@ -42,15 +42,15 @@ describe("SettingsManager - External Edit Preservation", () => {
settingsPath,
JSON.stringify({
theme: "dark",
packages: ["npm:companion-mcp-adapter"],
packages: ["npm:clanker-mcp-adapter"],
}),
);
// Companion starts up and loads settings into memory
// Clanker starts up and loads settings into memory
const manager = SettingsManager.create(projectDir, agentDir);
// At this point, globalSettings.packages = ["npm:companion-mcp-adapter"]
expect(manager.getPackages()).toEqual(["npm:companion-mcp-adapter"]);
// At this point, globalSettings.packages = ["npm:clanker-mcp-adapter"]
expect(manager.getPackages()).toEqual(["npm:clanker-mcp-adapter"]);
// User externally edits settings.json to remove the package
const currentSettings = JSON.parse(readFileSync(settingsPath, "utf-8"));
@ -102,7 +102,7 @@ describe("SettingsManager - External Edit Preservation", () => {
});
it("should preserve external project settings changes when updating unrelated project field", async () => {
const projectSettingsPath = join(projectDir, ".companion", "settings.json");
const projectSettingsPath = join(projectDir, ".clanker", "settings.json");
writeFileSync(
projectSettingsPath,
JSON.stringify({
@ -133,7 +133,7 @@ describe("SettingsManager - External Edit Preservation", () => {
});
it("should let in-memory project changes override external changes for the same project field", async () => {
const projectSettingsPath = join(projectDir, ".companion", "settings.json");
const projectSettingsPath = join(projectDir, ".clanker", "settings.json");
writeFileSync(
projectSettingsPath,
JSON.stringify({

View file

@ -14,7 +14,7 @@ describe("SettingsManager", () => {
rmSync(testDir, { recursive: true });
}
mkdirSync(agentDir, { recursive: true });
mkdirSync(join(projectDir, ".companion"), { recursive: true });
mkdirSync(join(projectDir, ".clanker"), { recursive: true });
});
afterEach(() => {
@ -35,7 +35,7 @@ describe("SettingsManager", () => {
}),
);
// Create SettingsManager (simulates companion starting up)
// Create SettingsManager (simulates clanker starting up)
const manager = SettingsManager.create(projectDir, agentDir);
// Simulate user editing settings.json externally to add enabledModels
@ -205,7 +205,7 @@ describe("SettingsManager", () => {
describe("error tracking", () => {
it("should collect and clear load errors via drainErrors", () => {
const globalSettingsPath = join(agentDir, "settings.json");
const projectSettingsPath = join(projectDir, ".companion", "settings.json");
const projectSettingsPath = join(projectDir, ".clanker", "settings.json");
writeFileSync(globalSettingsPath, "{ invalid global json");
writeFileSync(projectSettingsPath, "{ invalid project json");
@ -219,46 +219,46 @@ describe("SettingsManager", () => {
});
describe("project settings directory creation", () => {
it("should not create .companion folder when only reading project settings", () => {
// Create agent dir with global settings, but NO .companion folder in project
it("should not create .clanker folder when only reading project settings", () => {
// Create agent dir with global settings, but NO .clanker folder in project
const settingsPath = join(agentDir, "settings.json");
writeFileSync(settingsPath, JSON.stringify({ theme: "dark" }));
// Delete the .companion folder that beforeEach created
rmSync(join(projectDir, ".companion"), { recursive: true });
// Delete the .clanker folder that beforeEach created
rmSync(join(projectDir, ".clanker"), { recursive: true });
// Create SettingsManager (reads both global and project settings)
const manager = SettingsManager.create(projectDir, agentDir);
// .companion folder should NOT have been created just from reading
expect(existsSync(join(projectDir, ".companion"))).toBe(false);
// .clanker folder should NOT have been created just from reading
expect(existsSync(join(projectDir, ".clanker"))).toBe(false);
// Settings should still be loaded from global
expect(manager.getTheme()).toBe("dark");
});
it("should create .companion folder when writing project settings", async () => {
// Create agent dir with global settings, but NO .companion folder in project
it("should create .clanker folder when writing project settings", async () => {
// Create agent dir with global settings, but NO .clanker folder in project
const settingsPath = join(agentDir, "settings.json");
writeFileSync(settingsPath, JSON.stringify({ theme: "dark" }));
// Delete the .companion folder that beforeEach created
rmSync(join(projectDir, ".companion"), { recursive: true });
// Delete the .clanker folder that beforeEach created
rmSync(join(projectDir, ".clanker"), { recursive: true });
const manager = SettingsManager.create(projectDir, agentDir);
// .companion folder should NOT exist yet
expect(existsSync(join(projectDir, ".companion"))).toBe(false);
// .clanker folder should NOT exist yet
expect(existsSync(join(projectDir, ".clanker"))).toBe(false);
// Write a project-specific setting
manager.setProjectPackages([{ source: "npm:test-pkg" }]);
await manager.flush();
// Now .companion folder should exist
expect(existsSync(join(projectDir, ".companion"))).toBe(true);
// Now .clanker folder should exist
expect(existsSync(join(projectDir, ".clanker"))).toBe(true);
// And settings file should be created
expect(existsSync(join(projectDir, ".companion", "settings.json"))).toBe(true);
expect(existsSync(join(projectDir, ".clanker", "settings.json"))).toBe(true);
});
});

View file

@ -395,11 +395,11 @@ describe("skills", () => {
});
it("should expand ~ in skillPaths", () => {
const homeSkillsDir = join(homedir(), ".companion/agent/skills");
const homeSkillsDir = join(homedir(), ".clanker/agent/skills");
const { skills: withTilde } = loadSkills({
agentDir: emptyAgentDir,
cwd: emptyCwd,
skillPaths: ["~/.companion/agent/skills"],
skillPaths: ["~/.clanker/agent/skills"],
});
const { skills: withoutTilde } = loadSkills({
agentDir: emptyAgentDir,

View file

@ -4,8 +4,8 @@
* Run with: npx tsx test/streaming-render-debug.ts
*/
import type { AssistantMessage } from "@mariozechner/companion-ai";
import { ProcessTerminal, TUI } from "@mariozechner/companion-tui";
import type { AssistantMessage } from "@mariozechner/clanker-ai";
import { ProcessTerminal, TUI } from "@mariozechner/clanker-tui";
import { readFileSync } from "fs";
import { dirname, join } from "path";
import { fileURLToPath } from "url";

View file

@ -103,7 +103,7 @@ describe("buildSystemPrompt", () => {
const prompt = buildSystemPrompt({
contextFiles: [
{
path: "/home/node/.companion/workspace/USER.md",
path: "/home/node/.clanker/workspace/USER.md",
content: "# User\n\nLikes coffee.",
},
],

View file

@ -1,4 +1,4 @@
import { Text, type TUI } from "@mariozechner/companion-tui";
import { Text, type TUI } from "@mariozechner/clanker-tui";
import { Type } from "@sinclair/typebox";
import stripAnsi from "strip-ansi";
import { beforeAll, describe, expect, test } from "vitest";

View file

@ -1,4 +1,4 @@
import { truncateToWidth, visibleWidth } from "@mariozechner/companion-tui";
import { truncateToWidth, visibleWidth } from "@mariozechner/clanker-tui";
import { describe, expect, it } from "vitest";
/**

View file

@ -12,13 +12,13 @@ import {
} from "node:fs";
import { homedir, tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { Agent } from "@mariozechner/companion-agent-core";
import { Agent } from "@mariozechner/clanker-agent-core";
import {
getModel,
type OAuthCredentials,
type OAuthProvider,
} from "@mariozechner/companion-ai";
import { getOAuthApiKey } from "@mariozechner/companion-ai/oauth";
} from "@mariozechner/clanker-ai";
import { getOAuthApiKey } from "@mariozechner/clanker-ai/oauth";
import { AgentSession } from "../src/core/agent-session.js";
import { AuthStorage } from "../src/core/auth-storage.js";
import { createExtensionRuntime } from "../src/core/extensions/loader.js";
@ -36,10 +36,10 @@ export const API_KEY =
process.env.ANTHROPIC_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY;
// ============================================================================
// OAuth API key resolution from ~/.companion/agent/auth.json
// OAuth API key resolution from ~/.clanker/agent/auth.json
// ============================================================================
const AUTH_PATH = join(homedir(), ".companion", "agent", "auth.json");
const AUTH_PATH = join(homedir(), ".clanker", "agent", "auth.json");
type ApiKeyCredential = {
type: "api_key";
@ -76,7 +76,7 @@ function saveAuthStorage(storage: AuthStorageData): void {
}
/**
* Resolve API key for a provider from ~/.companion/agent/auth.json
* Resolve API key for a provider from ~/.clanker/agent/auth.json
*
* For API key credentials, returns the key directly.
* For OAuth credentials, returns the access token (refreshing if expired and saving back).
@ -122,18 +122,18 @@ export async function resolveApiKey(
}
/**
* Check if a provider has credentials in ~/.companion/agent/auth.json
* Check if a provider has credentials in ~/.clanker/agent/auth.json
*/
export function hasAuthForProvider(provider: string): boolean {
const storage = loadAuthStorage();
return provider in storage;
}
/** Path to the real companion agent config directory */
export const COMPANION_AGENT_DIR = join(homedir(), ".companion", "agent");
/** Path to the real clanker agent config directory */
export const CLANKER_AGENT_DIR = join(homedir(), ".clanker", "agent");
/**
* Get an AuthStorage instance backed by ~/.companion/agent/auth.json
* Get an AuthStorage instance backed by ~/.clanker/agent/auth.json
* Use this for tests that need real OAuth credentials.
*/
export function getRealAuthStorage(): AuthStorage {
@ -220,7 +220,7 @@ export function createTestSession(
): TestSessionContext {
const tempDir = join(
tmpdir(),
`companion-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
`clanker-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
);
mkdirSync(tempDir, { recursive: true });

View file

@ -1,5 +1,5 @@
import type { IncomingMessage, ServerResponse } from "node:http";
import type { AssistantMessage } from "@mariozechner/companion-ai";
import type { AssistantMessage } from "@mariozechner/clanker-ai";
import { describe, expect, it } from "vitest";
import type { AgentSessionEvent } from "../src/core/agent-session.js";
import {