Official

claude

Claude Code agent provider: runs the Claude Code CLI headless for each account.

The app opens the listing; nothing installs until an agent in your Plugins workspace has read the files and you enable the plugin. In a terminal: cvg install convergence/claude@0.2.0

Permissions in 0.2.0

  • Provide agents agents.provideMediumAdds agents to the app.Provide the Claude Code agent and pass its tool calls to plugin tools
  • Run named programs processMediumStarts the listed programs.Run the Claude Code CLI (sessions, sign-in, `claude update`), and ask the npm installation that owns it about a newer versionPrograms: claudenpm
  • Read files fs.readMediumReads files in the listed places.Read Claude Code's sessions, settings and skills (in ~/.claude, other accounts' ~/.claude-* folders, the shared skills and the folder you choose in Settings), the project's .claude folder, the administrator's skill policy, and once, what the previous provider keptPlaces: ~/.claude/**~/.claude*/**~/.agents/skills/**the open workspaceits own data folder/Library/Application Support/ClaudeCode/managed-settings.json/etc/claude-code/managed-settings.json${settings.configDir}
  • Environment variables envMediumReads the listed environment variables.Find Claude Code's configuration directory, and the launch settings you set in CLAUDE_* variables (extra arguments, MCP servers, setting sources, appended system prompt, extra folders)Variables: HOMECLAUDE_CONFIG_DIRCLAUDE_EXTRA_ARGSCLAUDE_MCP_CONFIGCLAUDE_STRICT_MCP_CONFIGCLAUDE_SETTING_SOURCESCLAUDE_APPEND_SYSTEM_PROMPTCLAUDE_ADD_DIRS

Files

config.test.ts5 KB
// Ported from the Rust plugin's config.rs and instances.rs tests.
import assert from "node:assert/strict";
import { test } from "./testing.ts";
import {
  childEnv,
  configDirOf,
  continuationKey,
  emptyLaunch,
  launchArgs,
  launchFromEnv,
  splitArgs,
  workflowSettings,
} from "./config.ts";
import { agentId, defaultAccount, displayName, parseInstances } from "./instances.ts";

test("the continuation key is empty for the default directory", () => {
  assert.equal(continuationKey(defaultAccount(), { HOME: "/h" }), "");
  assert.equal(
    continuationKey(defaultAccount(), { HOME: "/h", CLAUDE_CONFIG_DIR: "/h/.claude_work" }),
    "/h/.claude_work",
  );
  assert.equal(configDirOf(defaultAccount(), { HOME: "/h" }), "/h/.claude");
  assert.equal(configDirOf(defaultAccount(), { HOME: "/h", CLAUDE_CONFIG_DIR: "/h/.claude_work" }), "/h/.claude_work");
});

/// Exporting `CLAUDE_CONFIG_DIR`, even as `~/.claude`, switches the CLI off
/// the keychain and it answers `loggedIn: false`.
test("the default account does not export a config dir", () => {
  assert.deepEqual(childEnv(defaultAccount(), { HOME: "/h" }), {}, "the default account keeps the CLI's own login");
  const named = { id: "work", configDir: "/tmp/work", env: {} };
  assert.deepEqual(
    childEnv(named, { HOME: "/h" }),
    { CLAUDE_CONFIG_DIR: "/tmp/work" },
    "a named account gets its own directory",
  );
  // The Settings folder ~/.claude is the CLI's own: exporting it would sign the account out.
  assert.deepEqual(childEnv({ ...defaultAccount(), configDir: "/h/.claude/" }, { HOME: "/h" }), {});
  assert.deepEqual(childEnv({ ...defaultAccount(), configDir: "/h/.claude-work" }, { HOME: "/h" }), {
    CLAUDE_CONFIG_DIR: "/h/.claude-work",
  });
});

test("arguments are split like a shell", () => {
  assert.deepEqual(splitArgs("--chrome --agent reviewer"), ["--chrome", "--agent", "reviewer"]);
  assert.deepEqual(splitArgs('--append-system-prompt "be brief"'), ["--append-system-prompt", "be brief"]);
  assert.deepEqual(splitArgs("'a b' c\\ d"), ["a b", "c d"]);
  // An empty quoted word is a word, or a flag that takes an empty value
  // would lose its argument.
  assert.deepEqual(splitArgs('--name "" --x'), ["--name", "", "--x"]);
  assert.deepEqual(splitArgs("   "), []);
});

test("launch arguments repeat a list flag for every value", () => {
  const launch = {
    mcpConfig: ["/w/mcp.json", "/w/other.json"],
    strictMcpConfig: true,
    settingSources: "user,project",
    appendSystemPrompt: "be brief",
    addDirs: ["/w/shared"],
    extraArgs: ["--chrome"],
  };
  assert.deepEqual(launchArgs(launch, "/w/project"), [
    "--mcp-config",
    "/w/mcp.json",
    "--mcp-config",
    "/w/other.json",
    "--strict-mcp-config",
    "--setting-sources",
    "user,project",
    "--add-dir",
    "/w/project",
    "--add-dir",
    "/w/shared",
    "--chrome",
  ]);
  assert.deepEqual(
    launchFromEnv({
      CLAUDE_MCP_CONFIG: "/a.json '/b c.json'",
      CLAUDE_STRICT_MCP_CONFIG: "yes",
      CLAUDE_ADD_DIRS: "/x",
      CLAUDE_EXTRA_ARGS: "--chrome",
    }),
    {
      mcpConfig: ["/a.json", "/b c.json"],
      strictMcpConfig: true,
      settingSources: null,
      appendSystemPrompt: null,
      addDirs: ["/x"],
      extraArgs: ["--chrome"],
    },
  );
});

test("workflow switches default to on", () => {
  assert.deepEqual(workflowSettings({}), { enabled: true, keyword: true });
  assert.deepEqual(workflowSettings({ workflowKeywordTriggerEnabled: false }), { enabled: true, keyword: false });
  assert.deepEqual(workflowSettings({ enableWorkflows: false }), { enabled: false, keyword: false });
  assert.deepEqual(workflowSettings(null), { enabled: true, keyword: true });
});

test("an unconfigured launch only grants the workspace", () => {
  assert.deepEqual(launchArgs(emptyLaunch(), "/w"), ["--add-dir", "/w"]);
});

test("the default account is the plain agent id", () => {
  assert.equal(agentId(defaultAccount()), "claude");
  assert.equal(displayName(defaultAccount()), "Claude Code");
});

test("a named instance gets its own agent id and directory", () => {
  const [, instance] = parseInstances([{ id: "personal", configDir: "~/.claude_personal" }]);
  assert.equal(agentId(instance), "claude:personal");
  assert.equal(displayName(instance), "Claude Code (personal)");
  assert.equal(configDirOf(instance, { HOME: "/Users/me" }), "/Users/me/.claude_personal", "`~` is expanded");
  assert.equal(
    parseInstances([{ id: "" }, { id: "a" }, { id: "a" }]).length,
    2,
    "missing and repeated ids are left out",
  );
  assert.equal(parseInstances("nonsense").length, 1);
});

/// Moving `HOME` would move the macOS keychain with it, and the CLI would
/// lose the login the user just made.
test("environment that would move the login is refused", () => {
  const [, router] = parseInstances([
    {
      id: "router",
      env: { HOME: "/tmp/elsewhere", ANTHROPIC_BASE_URL: "https://openrouter.ai/api", CLAUDE_CONFIG_DIR: "/x" },
    },
  ]);
  assert.deepEqual(childEnv(router, { HOME: "/h" }), { ANTHROPIC_BASE_URL: "https://openrouter.ai/api" });
});

Versions

VersionPublishedPlugin APISizePermissionsStatus
0.2.0latestOct 5, 2026>=2 <3128.7 KB4 permissionsListed

Reviews and comments

0 threads · 0 reviews

No comments yet.