Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions plugins/opencode/scripts/lib/process.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
// Process utilities for the OpenCode companion.
//
// Modified by JohnnyVicious (2026): added `getConfiguredProviders` which
// reads OpenCode's `auth.json` directly so `/opencode:setup` can detect
// configured providers without depending on the HTTP server. The
// pre-existing `client.listProviders()` path hits a schema/docs endpoint
// that does not return the user's configured credentials, which made
// `/opencode:setup` always report `providers: []`. (Apache License 2.0
// §4(b) modification notice.)

import { spawn } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";

/**
* Resolve the full path to the `opencode` binary.
Expand Down Expand Up @@ -78,3 +89,69 @@ export function spawnDetached(cmd, args, opts = {}) {
child.unref();
return child;
}

// ------------------------------------------------------------------
// OpenCode auth.json discovery
// ------------------------------------------------------------------

/**
* Locate OpenCode's auth.json by trying the OS-specific candidate paths
* (XDG_DATA_HOME first, then platform defaults). Returns the first path
* that exists, or null if none do.
* @returns {string | null}
*/
export function findOpencodeAuthFile() {
const home = os.homedir();
const candidates = [];

if (process.env.XDG_DATA_HOME) {
candidates.push(path.join(process.env.XDG_DATA_HOME, "opencode", "auth.json"));
}

if (process.platform === "darwin") {
candidates.push(path.join(home, "Library", "Application Support", "opencode", "auth.json"));
}

if (process.platform === "win32") {
if (process.env.APPDATA) {
candidates.push(path.join(process.env.APPDATA, "opencode", "auth.json"));
}
candidates.push(path.join(home, "AppData", "Roaming", "opencode", "auth.json"));
}

// Linux/BSD default + macOS-with-XDG fallback.
candidates.push(path.join(home, ".local", "share", "opencode", "auth.json"));

for (const candidate of candidates) {
try {
if (fs.existsSync(candidate)) return candidate;
} catch {
// ignore unreadable candidates
}
}
return null;
}

/**
* Read OpenCode's auth.json and return the list of configured provider IDs
* (the top-level keys). Returns an empty array if the file is missing,
* unreadable, or not a JSON object.
*
* This is the same source of truth that `opencode providers list` uses.
* It does not require the OpenCode HTTP server to be running.
* @returns {string[]}
*/
export function getConfiguredProviders() {
const file = findOpencodeAuthFile();
if (!file) return [];
try {
const raw = fs.readFileSync(file, "utf8");
const data = JSON.parse(raw);
if (data && typeof data === "object" && !Array.isArray(data)) {
return Object.keys(data);
}
} catch {
// ignore parse/read errors — treat as no providers
}
return [];
}
26 changes: 13 additions & 13 deletions plugins/opencode/scripts/opencode-companion.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,19 @@
// so callers can override OpenCode's default model per review;
// - accept `--pr <N>` (with `PR #N` focus auto-detect for adversarial)
// and fetch PR diffs via `gh pr diff` so reviews can target a GitHub
// pull request without checking it out.
// pull request without checking it out;
// - `handleSetup` reads OpenCode's auth.json directly via
// `getConfiguredProviders` instead of probing the `GET /provider` HTTP
// endpoint, which returns a TypeScript schema dump rather than the
// user's configured credentials.
// (Apache License 2.0 §4(b) modification notice.)

import path from "node:path";
import process from "node:process";
import fs from "node:fs";

import { parseArgs, extractTaskText } from "./lib/args.mjs";
import { isOpencodeInstalled, getOpencodeVersion, spawnDetached } from "./lib/process.mjs";
import { isOpencodeInstalled, getOpencodeVersion, spawnDetached, getConfiguredProviders } from "./lib/process.mjs";
import { isServerRunning, ensureServer, createClient, connect } from "./lib/opencode-server.mjs";
import { resolveWorkspace } from "./lib/workspace.mjs";
import { loadState, updateState, upsertJob, generateJobId, jobDataPath } from "./lib/state.mjs";
Expand Down Expand Up @@ -78,17 +82,13 @@ async function handleSetup(argv) {
if (installed) {
serverRunning = await isServerRunning();

if (serverRunning) {
try {
const client = createClient("http://127.0.0.1:4096");
const providerList = await client.listProviders();
if (Array.isArray(providerList)) {
providers = providerList.map((p) => p.id ?? p.name).filter(Boolean);
}
} catch {
// Server may not be fully ready
}
}
// Read configured providers directly from OpenCode's auth.json. The
// HTTP `GET /provider` endpoint returns a TypeScript schema dump, not
// the user's credentials, and `GET /provider/auth` only lists which
// auth methods each provider supports. auth.json is the same source
// of truth that `opencode providers list` uses, and it works whether
// or not the OpenCode server is running.
providers = getConfiguredProviders();
}

// Handle review gate toggle
Expand Down