@khalilgharbaoui/opencode-claude-code-plugin 0.1.0 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2520,16 +2520,153 @@ var defaultModels = {
2520
2520
  })
2521
2521
  };
2522
2522
 
2523
+ // src/accounts.ts
2524
+ import { chmod, lstat, mkdir, readlink, symlink, writeFile } from "fs/promises";
2525
+ import path3 from "path";
2526
+ var BASE_PROVIDER_ID = "claude-code";
2527
+ var DEFAULT_ACCOUNT = "default";
2528
+ var SHARED_CAPABILITY_ITEMS = [
2529
+ "CLAUDE.md",
2530
+ "settings.json",
2531
+ "skills",
2532
+ "agents",
2533
+ "commands",
2534
+ "plugins"
2535
+ ];
2536
+ function normalizeAccountName(account) {
2537
+ return account.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
2538
+ }
2539
+ function resolveAccounts(value) {
2540
+ if (!Array.isArray(value)) return null;
2541
+ const accounts = value.map((account) => normalizeAccountName(String(account))).filter(Boolean);
2542
+ return Array.from(/* @__PURE__ */ new Set([DEFAULT_ACCOUNT, ...accounts]));
2543
+ }
2544
+ function accountProviderId(account) {
2545
+ return `${BASE_PROVIDER_ID}-${normalizeAccountName(account)}`;
2546
+ }
2547
+ function accountDisplayName(account) {
2548
+ return `Claude Code (${titleizeAccount(account)})`;
2549
+ }
2550
+ function accountModelSuffix(account) {
2551
+ const normalized = normalizeAccountName(account);
2552
+ return normalized === DEFAULT_ACCOUNT ? void 0 : normalized;
2553
+ }
2554
+ function accountConfigDir(account) {
2555
+ const normalized = normalizeAccountName(account);
2556
+ if (!normalized || normalized === DEFAULT_ACCOUNT) return void 0;
2557
+ return `~/.claude-${normalized}`;
2558
+ }
2559
+ function expandHome(value) {
2560
+ const home = process.env.HOME ?? process.env.USERPROFILE;
2561
+ if (value === "~") return home ?? value;
2562
+ if (value.startsWith("~/") || value.startsWith("~\\")) {
2563
+ return home ? path3.join(home, value.slice(2)) : value;
2564
+ }
2565
+ return value;
2566
+ }
2567
+ async function ensureAccountRuntime(account, baseCliPath) {
2568
+ const configDir = accountConfigDir(account);
2569
+ if (!configDir) return { cliPath: baseCliPath };
2570
+ const expandedConfigDir = expandHome(configDir);
2571
+ await mkdir(expandedConfigDir, { recursive: true });
2572
+ await ensureSharedCapabilities(expandedConfigDir);
2573
+ const cliPath = await writeAccountWrapper(
2574
+ normalizeAccountName(account),
2575
+ baseCliPath,
2576
+ expandedConfigDir
2577
+ );
2578
+ return { cliPath, configDir };
2579
+ }
2580
+ async function ensureSharedCapabilities(targetRoot) {
2581
+ const sourceRoot = expandHome("~/.claude");
2582
+ for (const item of SHARED_CAPABILITY_ITEMS) {
2583
+ await ensureSharedCapabilityItem(sourceRoot, targetRoot, item);
2584
+ }
2585
+ }
2586
+ async function ensureSharedCapabilityItem(sourceRoot, targetRoot, item) {
2587
+ const source = path3.join(sourceRoot, item);
2588
+ const target = path3.join(targetRoot, item);
2589
+ let sourceStat;
2590
+ try {
2591
+ sourceStat = await lstat(source);
2592
+ } catch {
2593
+ return;
2594
+ }
2595
+ try {
2596
+ const targetStat = await lstat(target);
2597
+ if (targetStat.isSymbolicLink()) {
2598
+ const current = await readlink(target);
2599
+ const resolvedCurrent = path3.resolve(path3.dirname(target), current);
2600
+ const resolvedSource = path3.resolve(source);
2601
+ if (resolvedCurrent === resolvedSource) return;
2602
+ }
2603
+ log.warn("shared Claude capability already exists; leaving untouched", {
2604
+ item,
2605
+ target,
2606
+ source
2607
+ });
2608
+ return;
2609
+ } catch {
2610
+ }
2611
+ const type = sourceStat.isDirectory() ? process.platform === "win32" ? "junction" : "dir" : "file";
2612
+ await symlink(source, target, type);
2613
+ }
2614
+ async function writeAccountWrapper(account, baseCliPath, configDir) {
2615
+ const cacheRoot = path3.join(
2616
+ process.env.XDG_CACHE_HOME ?? expandHome("~/.cache"),
2617
+ "opencode-claude-code-plugin"
2618
+ );
2619
+ const wrapperPath = path3.join(cacheRoot, `claude-${account}`);
2620
+ const suffix = `@${account}`;
2621
+ await mkdir(cacheRoot, { recursive: true });
2622
+ const script = `#!/usr/bin/env bash
2623
+ set -euo pipefail
2624
+
2625
+ args=()
2626
+ while [[ $# -gt 0 ]]; do
2627
+ if [[ "$1" == "--model" && $# -ge 2 ]]; then
2628
+ model="$2"
2629
+ if [[ "$model" == *${shellDoubleQuote(suffix)} ]]; then
2630
+ model="\${model%${shellDoubleQuote(suffix)}}"
2631
+ fi
2632
+ args+=("$1" "$model")
2633
+ shift 2
2634
+ else
2635
+ args+=("$1")
2636
+ shift
2637
+ fi
2638
+ done
2639
+
2640
+ export CLAUDE_CONFIG_DIR=${shellSingleQuote(configDir)}
2641
+ exec ${shellSingleQuote(baseCliPath)} "\${args[@]}"
2642
+ `;
2643
+ await writeFile(wrapperPath, script, "utf8");
2644
+ await chmod(wrapperPath, 493);
2645
+ return wrapperPath;
2646
+ }
2647
+ function shellSingleQuote(value) {
2648
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
2649
+ }
2650
+ function shellDoubleQuote(value) {
2651
+ return value.replace(/[$`"\\]/g, "\\$&");
2652
+ }
2653
+ function titleizeAccount(account) {
2654
+ return normalizeAccountName(account).split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
2655
+ }
2656
+
2523
2657
  // src/index.ts
2524
2658
  function createClaudeCode(settings = {}) {
2525
2659
  const cliPath = settings.cliPath ?? process.env.CLAUDE_CLI_PATH ?? "claude";
2526
- const providerName = settings.name ?? "claude-code";
2660
+ const providerName = settings.providerID ?? settings.name ?? "claude-code";
2527
2661
  const proxyTools = settings.proxyTools ?? ["Bash", "Edit", "Write", "WebFetch"];
2528
2662
  const createModel = (modelId) => {
2529
2663
  return new ClaudeCodeLanguageModel(modelId, {
2530
2664
  provider: providerName,
2531
2665
  cliPath,
2532
2666
  cwd: settings.cwd,
2667
+ account: settings.account,
2668
+ configDir: settings.configDir,
2669
+ providerID: settings.providerID,
2533
2670
  skipPermissions: settings.skipPermissions ?? true,
2534
2671
  permissionMode: settings.permissionMode,
2535
2672
  mcpConfig: settings.mcpConfig,
@@ -2548,11 +2685,16 @@ function createClaudeCode(settings = {}) {
2548
2685
  provider.languageModel = createModel;
2549
2686
  return provider;
2550
2687
  }
2551
- var PROVIDER_ID2 = "claude-code";
2688
+ var PROVIDER_ID2 = BASE_PROVIDER_ID;
2552
2689
  var PACKAGE_NPM = "@khalilgharbaoui/opencode-claude-code-plugin";
2553
2690
  function pluginEntrypoint() {
2554
2691
  return import.meta.url.startsWith("file:") ? import.meta.url : PACKAGE_NPM;
2555
2692
  }
2693
+ function cleanProviderOptions(options = {}) {
2694
+ const result = { ...options };
2695
+ delete result.accounts;
2696
+ return result;
2697
+ }
2556
2698
  function mergeDefaultVariants(models = {}) {
2557
2699
  const result = { ...models };
2558
2700
  for (const [id, model] of Object.entries(defaultModels)) {
@@ -2569,16 +2711,20 @@ function mergeDefaultVariants(models = {}) {
2569
2711
  }
2570
2712
  return result;
2571
2713
  }
2572
- function defaultModelsForProvider(providerModels) {
2714
+ function defaultModelsForProvider(providerModels, providerID = PROVIDER_ID2, modelSuffix) {
2573
2715
  const models = Object.fromEntries(
2574
2716
  Object.entries(defaultModels).map(([id, model]) => {
2575
- const existing = providerModels[id];
2717
+ const modelId = modelSuffix ? `${id}@${modelSuffix}` : id;
2718
+ const existing = providerModels[id] ?? providerModels[modelId];
2576
2719
  return [
2577
- id,
2720
+ modelId,
2578
2721
  {
2579
2722
  ...model,
2723
+ id: modelId,
2724
+ providerID,
2580
2725
  api: {
2581
2726
  ...model.api,
2727
+ id: modelId,
2582
2728
  npm: existing?.api?.npm ?? model.api.npm,
2583
2729
  url: existing?.api?.url ?? model.api.url
2584
2730
  }
@@ -2587,29 +2733,76 @@ function defaultModelsForProvider(providerModels) {
2587
2733
  })
2588
2734
  );
2589
2735
  for (const [id, model] of Object.entries(providerModels)) {
2590
- if (!(id in models)) models[id] = model;
2736
+ if (!(id in models)) {
2737
+ models[id] = {
2738
+ ...model,
2739
+ providerID
2740
+ };
2741
+ }
2591
2742
  }
2592
2743
  return models;
2593
2744
  }
2594
- function providerConfig(existing) {
2745
+ async function providerConfig(existing, providerID = PROVIDER_ID2, optionDefaults = {}, displayName) {
2746
+ const mergedOptions = {
2747
+ cliPath: "claude",
2748
+ proxyTools: ["Bash", "Edit", "Write", "WebFetch"],
2749
+ ...optionDefaults,
2750
+ ...cleanProviderOptions(existing?.options),
2751
+ providerID
2752
+ };
2753
+ const cliPath = String(mergedOptions.cliPath ?? "claude");
2754
+ const account = typeof mergedOptions.account === "string" ? mergedOptions.account : void 0;
2755
+ const runtime = account ? await ensureAccountRuntime(account, cliPath) : { cliPath };
2595
2756
  return {
2596
- name: existing?.name,
2757
+ name: displayName ?? existing?.name,
2597
2758
  npm: existing?.npm ?? pluginEntrypoint(),
2598
2759
  options: {
2599
- cliPath: "claude",
2600
- proxyTools: ["Bash", "Edit", "Write", "WebFetch"],
2601
- ...existing?.options ?? {}
2760
+ ...mergedOptions,
2761
+ ...runtime
2602
2762
  },
2603
2763
  models: mergeDefaultVariants(existing?.models)
2604
2764
  };
2605
2765
  }
2766
+ async function expandAccountProviders(config) {
2767
+ const seed = config.provider?.[PROVIDER_ID2];
2768
+ const accounts = resolveAccounts(seed?.options?.accounts);
2769
+ if (!accounts) return false;
2770
+ config.provider ??= {};
2771
+ const seedOptions = cleanProviderOptions(seed?.options);
2772
+ for (const account of accounts) {
2773
+ const providerID = accountProviderId(account);
2774
+ const existing = config.provider[providerID];
2775
+ const modelSuffix = accountModelSuffix(account);
2776
+ config.provider[providerID] = {
2777
+ ...existing,
2778
+ ...await providerConfig(
2779
+ existing,
2780
+ providerID,
2781
+ {
2782
+ ...seedOptions,
2783
+ account
2784
+ },
2785
+ accountDisplayName(account)
2786
+ ),
2787
+ models: defaultModelsForProvider(
2788
+ existing?.models ?? seed?.models ?? {},
2789
+ providerID,
2790
+ modelSuffix
2791
+ )
2792
+ };
2793
+ }
2794
+ delete config.provider[PROVIDER_ID2];
2795
+ return true;
2796
+ }
2606
2797
  var server = async () => ({
2607
2798
  config: async (config) => {
2608
2799
  config.provider ??= {};
2800
+ const expanded = await expandAccountProviders(config);
2801
+ if (expanded) return;
2609
2802
  const existing = config.provider[PROVIDER_ID2];
2610
2803
  config.provider[PROVIDER_ID2] = {
2611
2804
  ...existing,
2612
- ...providerConfig(existing)
2805
+ ...await providerConfig(existing)
2613
2806
  };
2614
2807
  },
2615
2808
  provider: {