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
Files
account.test.ts5.7 KB
// Ported from the Rust plugin's account.rs tests.
import * as Effect from "effect/Effect";
import assert from "node:assert/strict";
import { required, test, run as runEffect } from "./testing.ts";
import type { Api } from "convergence";
import {
accountSummary,
maintenance as maintenanceEffect,
managerOf,
parseAccount,
recoveryIdentity,
signedOutMessage,
} from "./account.ts";
import { fakeApi } from "../sdk/testing.ts";
import { clearCache } from "../sdk/maintenance.ts";
test("auth status becomes an account", () => {
const account = parseAccount(
'{"loggedIn": true, "authMethod": "claude.ai", "apiProvider": "firstParty", "email": "a@b.com", "subscriptionType": "max", "configDirectory": "/h/.claude"}',
);
assert.equal(required(account).loggedIn, true);
assert.equal(required(account).email, "a@b.com");
assert.equal(accountSummary(required(account)), "Signed in as a@b.com on the Max plan.");
const out = parseAccount('{"loggedIn": false}');
assert.equal(required(out).loggedIn, false);
assert.equal(accountSummary(required(out)), null);
assert.equal(parseAccount("not json"), null);
assert.equal(
signedOutMessage("/h/.claude_work"),
"Run `claude auth login` with CLAUDE_CONFIG_DIR=/h/.claude_work to sign in.",
);
assert.equal(signedOutMessage(null), "Run `claude auth login` to sign in.");
});
/// A false claim of ownership makes Convergence run a package manager
/// against an install it did not create, so every branch needs proof.
test("only a proven path names an installer", () => {
assert.deepEqual(managerOf("/Users/a/.local/bin/claude"), { manager: "native" });
assert.deepEqual(managerOf("/Users/a/.local/share/claude/versions/2.1.271"), { manager: "native" });
assert.deepEqual(managerOf("/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/cli.js"), {
manager: "npm",
prefix: "/opt/homebrew",
});
// A checkout is not a global install.
assert.deepEqual(managerOf("/w/app/node_modules/x/lib/node_modules/@anthropic-ai/claude-code/cli.js"), {
manager: "",
});
assert.deepEqual(managerOf("/opt/homebrew/bin/claude"), { manager: "" });
assert.deepEqual(managerOf("/usr/local/bin/claude"), { manager: "" });
});
/// An install whose owner cannot be proven reports its version and nothing
/// else.
test("maintenance only offers an update it can run", async () => {
clearCache();
const api = fakeApi({
onHost: (method) =>
method === "host/process.which" ? { path: "/usr/local/bin/claude", realPath: "/usr/local/bin/claude" } : {},
});
const { out } = await maintenance(api, "2.1.271 (Claude Code)");
assert.deepEqual(out, { canUpdate: false, installedVersion: "2.1.271 (Claude Code)" });
assert.equal(api.peers.length, 0, "no registry is asked for an install nobody owns");
});
test("a native install is compared with the npm registry", async () => {
clearCache();
const api = fakeApi({
onHost: (method) =>
method === "host/process.which"
? { path: "/Users/a/.local/bin/claude", realPath: "/Users/a/.local/share/claude/versions/2.1.271" }
: {},
onSpawn: (program, args) => {
assert.equal(program, "npm");
assert.deepEqual(args, ["view", "@anthropic-ai/claude-code", "version"]);
return () => {};
},
});
const pending = maintenance(api, "2.1.271 (Claude Code)");
for (let i = 0; i < 20 && !api.peers.length; i += 1) await new Promise((resolve) => setTimeout(resolve, 1));
api.peers[0].stdout.push("2.1.300\n");
api.peers[0].exit(0);
const { out } = await pending;
assert.deepEqual(out, {
canUpdate: true,
installedVersion: "2.1.271 (Claude Code)",
latestVersion: "2.1.300",
manager: "native",
});
});
const maintenance = (api: Api, version: string) => runEffect(Effect.scoped(maintenanceEffect(version)), api);
test("non-object auth output is not a signed-out account", () => {
for (const output of ["null", "true", "42", '"signed out"']) assert.equal(parseAccount(output), null);
assert.deepEqual(parseAccount("[]"), {
loggedIn: false,
email: null,
plan: null,
apiProvider: null,
authMethod: null,
configDirectory: null,
orgId: null,
orgName: null,
});
});
test("subscription recovery identity requires matching runtime and auth/config evidence, not cached account labels", () => {
const status = {
loggedIn: true,
authMethod: "claude.ai",
email: "a@b.com",
apiProvider: "firstParty",
configDirectory: "/h/.claude",
orgId: "org-1",
orgName: "Work",
};
const native = { email: "a@b.com", organization: "Work", apiProvider: "firstParty" };
const account = parseAccount(JSON.stringify(status));
const identity = recoveryIdentity(native, account, "/h/.claude/");
assert.deepEqual(JSON.parse(required(identity)), ["claude/oauth", "/h/.claude", "a@b.com", "org-1"]);
for (const patch of [
{ loggedIn: false },
{ authMethod: "api_key" },
{ apiProvider: "bedrock" },
{ email: "other@b.com" },
{ orgId: undefined },
{ orgName: "Other org" },
{ configDirectory: undefined },
{ configDirectory: "/different" },
])
assert.equal(recoveryIdentity(native, parseAccount(JSON.stringify({ ...status, ...patch })), "/h/.claude"), null);
for (const patch of [
{ email: undefined },
{ organization: undefined },
{ apiProvider: undefined },
{ apiProvider: "gateway" },
{ tokenSource: "CLAUDE_CODE_OAUTH_TOKEN" },
{ apiKeySource: "ANTHROPIC_API_KEY" },
])
assert.equal(recoveryIdentity({ ...native, ...patch }, account, "/h/.claude"), null);
assert.equal(recoveryIdentity(native, null, "/h/.claude"), null);
assert.equal(recoveryIdentity(native, account, null), null);
assert.notEqual(
recoveryIdentity(native, parseAccount(JSON.stringify({ ...status, orgId: "org-2" })), "/h/.claude"),
identity,
);
});Versions
| Version | Published | Plugin API | Size | Permissions | Status |
|---|---|---|---|---|---|
| 0.2.0latest | Oct 5, 2026 | >=2 <3 | 128.7 KB | 4 permissions | Listed |
No comments yet.