@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/README.md +243 -173
- package/dist/index.d.ts +7 -0
- package/dist/index.js +205 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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 =
|
|
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
|
|
2717
|
+
const modelId = modelSuffix ? `${id}@${modelSuffix}` : id;
|
|
2718
|
+
const existing = providerModels[id] ?? providerModels[modelId];
|
|
2576
2719
|
return [
|
|
2577
|
-
|
|
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))
|
|
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
|
-
|
|
2600
|
-
|
|
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: {
|