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

options.ts10.8 KB
// Config options (the Rust `options.rs`). The model list and each model's
// effort levels come from `models.json`; the slash commands and which
// toggles exist at all come from the CLI's `initialize` control response.
import * as wire from "./wire.ts";
import type { ConfigOption, ConfigChoice, SlashCommand, Values } from "./types.ts";
import { permissionMode } from "../sdk/agent.ts";
import { manifest as loadManifest, modelMatches, modelValue } from "./models.ts";

export const MODEL = "model";
export const EFFORT = "effort";
export const PERMISSION_MODE = permissionMode.OPTION_ID;
export const THINKING = "thinking";
export const FAST_MODE = "fast_mode";
/// The value that means "leave this to Claude Code", so no flag is passed.
export const DEFAULT = "default";
/// The effort level above `xhigh`: `xhigh` plus standing dynamic-workflow
/// orchestration. The CLI takes it as `--effort ultracode` and as the
/// session setting `ultracode`; it needs a model that offers `xhigh`.
export const ULTRACODE = "ultracode";
/// The effort level ultracode runs at, and needs.
const XHIGH = "xhigh";
/// The mark of the ultracode choice: the `workflow` icon.
const ULTRACODE_ICON =
  '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="8" height="8" x="3" y="3" rx="2"/><path d="M7 11v4a2 2 0 0 0 2 2h4"/><rect width="8" height="8" x="13" y="13" rx="2"/></svg>';

/// The `--permission-mode` value for one of the four shared modes. Claude
/// has its own reviewer, so `auto` is a real mode. The mode is always
/// sent: the composer shows a mode, and it must be the one in force.
export function permissionFlag(mode: string) {
  switch (mode) {
    case permissionMode.AUTO_EDITS:
      return "acceptEdits";
    case permissionMode.AUTO:
      return "auto";
    case permissionMode.FULL:
      return "bypassPermissions";
    default:
      return "default";
  }
}

/// Why fast mode cannot serve, in words the user can act on. The reasons
/// the toggle itself clears, and the ones that pass on their own, give
/// nothing: "unavailable" next to the switch that turns it on is wrong.
export function fastModeNote(reason: string) {
  switch (reason) {
    case "not_first_party":
      return "Only an Anthropic account can use fast mode.";
    case "model_not_allowed":
      return "Your organization does not allow fast mode for this model.";
    case "free":
    case "extra_usage_disabled":
      return "Fast mode needs usage credits.";
    case "network_error":
      return "Fast mode could not be reached.";
    case "disabled_by_env":
      return "Fast mode is switched off on this machine.";
    default:
      return null;
  }
}

const EFFORT_NAMES: Record<string, string> = {
  low: "Low",
  medium: "Medium",
  high: "High",
  xhigh: "Extra high",
  max: "Max",
};

export function effortChoice(level: string): ConfigChoice {
  if (level === ULTRACODE) {
    return {
      value: ULTRACODE,
      name: "Ultracode",
      description: "Extra high effort, and Claude plans dynamic workflows: many agents in parallel.",
      icon: ULTRACODE_ICON,
      // The CLI never persists it either: every workflow costs many
      // agents' worth of tokens, so each chat opts in again.
      transient: true,
    };
  }
  return { value: level, name: EFFORT_NAMES[level] ?? level };
}

/// The `apply_flag_settings` layer for an effort choice, which changes a
/// running session without a restart. Ultracode is a setting of its own,
/// so every other level turns it off again; `null` drops the layer's
/// effort and the CLI falls back to the user's configured level.
export function effortSettings(value: string | null | undefined) {
  if (value === ULTRACODE) return { effortLevel: XHIGH, ultracode: true };
  if (value === DEFAULT || value === "" || value === null || value === undefined)
    return { effortLevel: null, ultracode: false };
  return { effortLevel: value, ultracode: false };
}

/// A string option value, ignoring the "leave it to Claude Code"
/// placeholder.
export function selected(values: Values | null | undefined, id: string) {
  const value = values?.[id];
  return typeof value === "string" && value !== "" && value !== DEFAULT ? value : null;
}

/// A toggle value.
export function toggle(values: Values | null | undefined, id: string, fallback: boolean) {
  const value = values?.[id];
  return typeof value === "boolean" ? value : fallback;
}

/// The parts of the `initialize` control response the plugin uses.
export class Catalog {
  models: wire.Model[];
  commands: wire.Command[];
  fastModeState: string | null;
  fastModeDisabledReason: string | null;
  manifest: ReturnType<typeof loadManifest>;
  workflowsOff: boolean;
  constructor({
    models = [],
    commands = [],
    fastModeState = null,
    fastModeDisabledReason = null,
    manifest = loadManifest(),
    workflowsOff = false,
  }: {
    models?: wire.Model[];
    commands?: wire.Command[];
    fastModeState?: string | null;
    fastModeDisabledReason?: string | null;
    manifest?: ReturnType<typeof loadManifest>;
    workflowsOff?: boolean;
  } = {}) {
    this.models = models;
    this.commands = commands;
    /// `off`, `cooldown` or `on`. Absent on a CLI without fast mode.
    this.fastModeState = fastModeState;
    this.fastModeDisabledReason = fastModeDisabledReason;
    this.manifest = manifest;
    /// The user's settings turn dynamic workflows off: no ultracode.
    this.workflowsOff = workflowsOff;
  }

  static fromResponse(response: wire.CatalogResponse) {
    const text = (key: string) => (typeof response?.[key] === "string" ? response[key] : null);
    return new Catalog({
      models: Array.isArray(response?.models) ? response.models : [],
      commands: Array.isArray(response?.commands) ? response.commands : [],
      fastModeState: text("fast_mode_state"),
      fastModeDisabledReason: text("fast_mode_disabled_reason"),
    });
  }

  slashCommands() {
    return this.commands
      .filter((command) => typeof command?.name === "string")
      .map((command) => {
        const out: SlashCommand = {
          name: command.name ?? "",
          description: typeof command.description === "string" ? command.description : "",
          source: "agent",
        };
        if (typeof command.argumentHint === "string" && command.argumentHint) out.inputHint = command.argumentHint;
        return out;
      });
  }

  /// A model's effort levels, with ultracode after them when the model can
  /// run it: the CLI accepts ultracode for any model and silently runs
  /// without workflows where `xhigh` is missing.
  modelEfforts(levels: string[]) {
    const out = [...levels];
    if (!this.workflowsOff && out.includes(XHIGH)) out.push(ULTRACODE);
    return out;
  }

  /// Effort levels offered by any model, in the list's order.
  effortLevels() {
    const levels: string[] = [];
    for (const model of this.manifest.models) {
      for (const level of this.modelEfforts(model.effort)) if (!levels.includes(level)) levels.push(level);
    }
    return levels;
  }

  /// Whether any model the account can run declares a capability.
  anyModel(capability: "supportsAdaptiveThinking" | "supportsFastMode") {
    return this.models.some((model) => model?.[capability] === true);
  }

  /// One choice per model in `models.json`, the 1M context variant where
  /// the model has one. The CLI's own values (`opus`, `opus[1m]`,
  /// `default`) become aliases of the model they resolve to, so a chat
  /// that stored one keeps its model.
  modelChoices() {
    return this.manifest.models.map((model) => {
      const value = modelValue(model);
      const aliases = this.models
        .filter((cli) => typeof cli?.resolvedModel === "string" && modelMatches(model, cli.resolvedModel))
        .map((cli) => cli.value)
        .filter((alias) => typeof alias === "string");
      if (model.slug !== value) aliases.push(model.slug);
      const choice: ConfigChoice = { value, name: model.name };
      const levels = this.modelEfforts(model.effort).map(effortChoice);
      if (levels.length) choice.reasoningLevels = levels;
      if (aliases.length) choice.aliases = aliases;
      return choice;
    });
  }

  /// The full option snapshot with `values` applied on top of the
  /// catalog's own defaults.
  options(values: Values = {}): ConfigOption[] {
    const chosen = (id: string, fallback: string) => (typeof values?.[id] === "string" ? values[id] : fallback);
    const defaultModel = this.manifest.models.find((model) => model.slug === this.manifest.default);
    const options: ConfigOption[] = [
      {
        id: MODEL,
        name: "Model",
        category: "model",
        kind: "select",
        value: chosen(MODEL, defaultModel ? modelValue(defaultModel) : DEFAULT),
        choices: this.modelChoices(),
      },
      {
        id: EFFORT,
        name: "Effort",
        description: "Reasoning effort. Ultracode adds dynamic workflows.",
        category: "reasoning",
        kind: "select",
        value: chosen(EFFORT, DEFAULT),
        choices: [
          { value: DEFAULT, name: "Default", description: "Let Claude Code pick the effort for the model." },
          ...this.effortLevels().map(effortChoice),
        ],
      },
      // The CLI's own `current_permission_mode` is deliberately not
      // adopted: the host reads an unset option as Supervised, so a
      // session that silently ran looser would do more than the composer
      // says.
      permissionMode.option(permissionMode.selected(values), true),
    ];
    // A toggle is only offered when the CLI proves it exists: a model that
    // declares the capability, and for fast mode a CLI that reports its
    // state at all.
    if (this.anyModel("supportsAdaptiveThinking")) {
      options.push({
        id: THINKING,
        name: "Thinking",
        description: "Let the model think before answering. Applied when the session starts.",
        category: "reasoning",
        kind: "toggle",
        value: toggle(values, THINKING, true),
        choices: [],
      });
    }
    if (this.fastModeState !== null && this.anyModel("supportsFastMode")) {
      const option: ConfigOption = {
        id: FAST_MODE,
        name: "Fast mode",
        category: "speed",
        kind: "toggle",
        value: toggle(values, FAST_MODE, false),
        choices: [],
      };
      const note = this.fastModeDisabledReason !== null ? fastModeNote(this.fastModeDisabledReason) : null;
      if (note) option.description = note;
      options.push(option);
    }
    return options;
  }
}

/// The choice a stored value selects: the one with that value, or the one
/// that lists it as an alias (the host's `ConfigOption::choice_for`).
export function choiceFor(option: ConfigOption, value: string) {
  return (
    (option.choices ?? []).find((choice) => choice.value === value) ??
    (option.choices ?? []).find((choice) => (choice.aliases ?? []).includes(value)) ??
    null
  );
}

Versions

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

Reviews and comments

0 threads · 0 reviews

No comments yet.