jackwener/OpenCLI · error · ConfigError

Missing Xiaoyuzhou credentials. Expected ${filePath}

Error message

Missing Xiaoyuzhou credentials. Expected ${filePath}

What it means

Thrown by loadXiaoyuzhouCredentials when the credential file ~/.opencli/xiaoyuzhou.json does not exist on disk. The library stores Xiaoyuzhou (小宇宙) app access_token and refresh_token in that file and refuses to call the API without them, raising a ConfigError. It means setup, not a runtime bug: the client has never been authenticated.

Source

Thrown at clis/xiaoyuzhou/auth.js:65

export function loadXiaoyuzhouCredentials() {
    const filePath = getXiaoyuzhouCredentialFile();
    if (fs.existsSync(filePath)) {
        try {
            const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
            const credentials = normalizeXiaoyuzhouCredentials(parsed);
            if (!credentials.access_token || !credentials.refresh_token) {
                throw new ConfigError(`Xiaoyuzhou credential file is missing access_token or refresh_token: ${filePath}`, 'Recreate the file with valid credentials.');
            }
            return credentials;
        }
        catch (error) {
            if (error instanceof ConfigError) {
                throw error;
            }
            throw new ConfigError(`Failed to parse Xiaoyuzhou credential file: ${filePath}`, `Ensure ${filePath} contains valid JSON. (${getErrorMessage(error)})`);
        }
    }
    throw new ConfigError(`Missing Xiaoyuzhou credentials. Expected ${filePath}`, `Create ${filePath} with access_token and refresh_token.`);
}

export function saveXiaoyuzhouCredentials(credentials) {
    const filePath = getXiaoyuzhouCredentialFile();
    fs.mkdirSync(path.dirname(filePath), { recursive: true });
    fs.writeFileSync(filePath, `${JSON.stringify({
        access_token: credentials.access_token,
        refresh_token: credentials.refresh_token,
        expires_at: credentials.expires_at,
        device_id: credentials.device_id,
        device_properties: credentials.device_properties,
    }, null, 2)}\n`, 'utf-8');
}

export function shouldRefreshXiaoyuzhouCredentials(credentials, now = getNowMs()) {
    return Number.isFinite(credentials.expires_at)
        && credentials.expires_at > 0
        && now >= credentials.expires_at - XIAOYUZHOU_REFRESH_SKEW_MS;

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Create ~/.opencli/xiaoyuzhou.json containing at least {"access_token":"...","refresh_token":"..."} (tokens captured from the Xiaoyuzhou app).
  2. Verify you are running as the same user/$HOME that owns the credential file: echo $HOME and check ls ~/.opencli/xiaoyuzhou.json.
  3. In CI/Docker, mount or inject the credential file (e.g. copy ~/.opencli/xiaoyuzhou.json into the image or a volume).
  4. Alternatively pass credentials explicitly via requestXiaoyuzhouJson(endpoint, { credentials }) to bypass the file entirely.

Example fix

// before: running any command without credentials -> ConfigError
$ opencli xiaoyuzhou episode <id>
// after: create the expected credential file
$ mkdir -p ~/.opencli
$ cat > ~/.opencli/xiaoyuzhou.json <<'EOF'
{
  "access_token": "<token-from-app>",
  "refresh_token": "<refresh-token-from-app>"
}
EOF
Defensive patterns

Strategy: validation

Validate before calling

import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
const credFile = path.join(os.homedir(), '.opencli', 'xiaoyuzhou.json');
if (!fs.existsSync(credFile)) {
  throw new Error(`Run auth setup first: ${credFile} is missing (needs access_token + refresh_token).`);
}
const creds = JSON.parse(fs.readFileSync(credFile, 'utf-8'));
if (!creds.access_token || !creds.refresh_token) {
  throw new Error(`${credFile} is missing access_token or refresh_token.`);
}

Type guard

function hasXiaoyuzhouCredentials(value) {
  return typeof value === 'object' && value !== null
    && typeof value.access_token === 'string' && value.access_token.length > 0
    && typeof value.refresh_token === 'string' && value.refresh_token.length > 0;
}

Try / catch

import { ConfigError } from '@jackwener/opencli/errors';
try {
  await requestXiaoyuzhouJson('/episodes/get', { query: { eid } });
} catch (err) {
  if (err instanceof ConfigError && err.message.startsWith('Missing Xiaoyuzhou credentials')) {
    console.error(`No credentials configured. Create ${err.message.match(/Expected (\S+)/)?.[1]} first.`);
    process.exitCode = 1;
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Any call to requestXiaoyuzhouJson (e.g. episode, transcript, or history endpoints) without options.credentials when ~/.opencli/xiaoyuzhou.json is absent — the common case being the very first use of any xiaoyuzhou CLI command on a fresh machine or in a container/CI where HOME points elsewhere.

Common situations: Fresh install before running any auth setup; running the CLI as a different user (or with a different $HOME) than the one that saved credentials; Docker images or CI runners where the dotfile was never copied; accidentally deleted or renamed ~/.opencli directory.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/19c63b504153e6c4. Report an issue: GitHub.