{"record":{"id":"19c63b504153e6c4","repo":"jackwener/OpenCLI","slug":"missing-xiaoyuzhou-credentials-expected-filepat","errorCode":null,"errorMessage":"Missing Xiaoyuzhou credentials. Expected ${filePath}","messagePattern":"Missing Xiaoyuzhou credentials\\. Expected (.+?)","errorType":"exception","errorClass":"ConfigError","httpStatus":null,"severity":"error","filePath":"clis/xiaoyuzhou/auth.js","lineNumber":65,"sourceCode":"export function loadXiaoyuzhouCredentials() {\n    const filePath = getXiaoyuzhouCredentialFile();\n    if (fs.existsSync(filePath)) {\n        try {\n            const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));\n            const credentials = normalizeXiaoyuzhouCredentials(parsed);\n            if (!credentials.access_token || !credentials.refresh_token) {\n                throw new ConfigError(`Xiaoyuzhou credential file is missing access_token or refresh_token: ${filePath}`, 'Recreate the file with valid credentials.');\n            }\n            return credentials;\n        }\n        catch (error) {\n            if (error instanceof ConfigError) {\n                throw error;\n            }\n            throw new ConfigError(`Failed to parse Xiaoyuzhou credential file: ${filePath}`, `Ensure ${filePath} contains valid JSON. (${getErrorMessage(error)})`);\n        }\n    }\n    throw new ConfigError(`Missing Xiaoyuzhou credentials. Expected ${filePath}`, `Create ${filePath} with access_token and refresh_token.`);\n}\n\nexport function saveXiaoyuzhouCredentials(credentials) {\n    const filePath = getXiaoyuzhouCredentialFile();\n    fs.mkdirSync(path.dirname(filePath), { recursive: true });\n    fs.writeFileSync(filePath, `${JSON.stringify({\n        access_token: credentials.access_token,\n        refresh_token: credentials.refresh_token,\n        expires_at: credentials.expires_at,\n        device_id: credentials.device_id,\n        device_properties: credentials.device_properties,\n    }, null, 2)}\\n`, 'utf-8');\n}\n\nexport function shouldRefreshXiaoyuzhouCredentials(credentials, now = getNowMs()) {\n    return Number.isFinite(credentials.expires_at)\n        && credentials.expires_at > 0\n        && now >= credentials.expires_at - XIAOYUZHOU_REFRESH_SKEW_MS;","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaoyuzhou/auth.js#L47-L83","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Create ~/.opencli/xiaoyuzhou.json containing at least {\"access_token\":\"...\",\"refresh_token\":\"...\"} (tokens captured from the Xiaoyuzhou app).","Verify you are running as the same user/$HOME that owns the credential file: echo $HOME and check ls ~/.opencli/xiaoyuzhou.json.","In CI/Docker, mount or inject the credential file (e.g. copy ~/.opencli/xiaoyuzhou.json into the image or a volume).","Alternatively pass credentials explicitly via requestXiaoyuzhouJson(endpoint, { credentials }) to bypass the file entirely."],"exampleFix":"// before: running any command without credentials -> ConfigError\n$ opencli xiaoyuzhou episode <id>\n// after: create the expected credential file\n$ mkdir -p ~/.opencli\n$ cat > ~/.opencli/xiaoyuzhou.json <<'EOF'\n{\n  \"access_token\": \"<token-from-app>\",\n  \"refresh_token\": \"<refresh-token-from-app>\"\n}\nEOF","handlingStrategy":"validation","validationCode":"import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nconst credFile = path.join(os.homedir(), '.opencli', 'xiaoyuzhou.json');\nif (!fs.existsSync(credFile)) {\n  throw new Error(`Run auth setup first: ${credFile} is missing (needs access_token + refresh_token).`);\n}\nconst creds = JSON.parse(fs.readFileSync(credFile, 'utf-8'));\nif (!creds.access_token || !creds.refresh_token) {\n  throw new Error(`${credFile} is missing access_token or refresh_token.`);\n}","typeGuard":"function hasXiaoyuzhouCredentials(value) {\n  return typeof value === 'object' && value !== null\n    && typeof value.access_token === 'string' && value.access_token.length > 0\n    && typeof value.refresh_token === 'string' && value.refresh_token.length > 0;\n}","tryCatchPattern":"import { ConfigError } from '@jackwener/opencli/errors';\ntry {\n  await requestXiaoyuzhouJson('/episodes/get', { query: { eid } });\n} catch (err) {\n  if (err instanceof ConfigError && err.message.startsWith('Missing Xiaoyuzhou credentials')) {\n    console.error(`No credentials configured. Create ${err.message.match(/Expected (\\S+)/)?.[1]} first.`);\n    process.exitCode = 1;\n    return;\n  }\n  throw err;\n}","preventionTips":["Run a preflight check (fs.existsSync on ~/.opencli/xiaoyuzhou.json) at CLI startup before any API call.","Pin the credential path in setup docs and scripts; never assume $HOME — in Docker/CI mount the file explicitly.","Keep a bootstrap script that writes the credential file from environment variables on first run.","Don't delete or rename the ~/.opencli directory during cleanup operations."],"tags":["config","missing-credentials","filesystem","authentication"],"backgroundTag":"missing-credentials-file","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}