cyberine
UsageRun recipes

OAuth providers

One provider supports OAuth login: GitHub Copilot. The OAuth token is stored GLOBALLY under ~/.cyberine/copilot.json (mode 0o600, dir 0o700) — same home as every other provider's ~/.cyberine/.env, so a login from one repo is usable from any directory.

CommandProviderFlowToken stored
cyberine auth login copilotGitHub Copilotdevice-code~/.cyberine/copilot.json
cyberine auth logout copilotGitHub Copilotdelete token storeremoves ~/.cyberine/copilot.json
cyberine auth login copilot   # GitHub Copilot device-code; stores token to ~/.cyberine/copilot.json
CYBERINE_BUILD_FLEET=loop-copilot-build cyberine run "add a hello world function"
cyberine auth logout copilot  # deletes the token store; a subsequent status check shows not-configured

The Copilot token expires every ~30 minutes; getCopilotToken() automatically refreshes it using the stored GitHub token, so you only need to run cyberine auth login copilot once per GitHub session. A refresh failure (revoked token, GitHub outage) surfaces as a distinct "token refresh failed" error instead of the generic "not authenticated" message, so a transient blip doesn't read as a missing login. A pre-fix store at .local/auth/copilot.json (cwd-scoped) is migrated to the global path automatically on first use after upgrading; no manual action needed.

Known limitation: logout only deletes the LOCAL token store — the underlying GitHub OAuth grant stays authorized (the device-code flow is a public client with no client secret, which GitHub's revocation endpoint requires). The logout command prints this caveat with a pointer to https://github.com/settings/connections/applications, where the grant can be revoked manually.

Regression smoke (offline — stubs the GitHub endpoints, no login or network needed):

npm run selftest:copilot-auth   # global store path + legacy migration, per-write 0600/0700
                                # re-tightening, thrown-vs-null refresh failures, logout deletion

The gateway is an ON-DEMAND dev dependency: restart: "no" means it never comes back after a machine reboot or Docker restart, and it publishes the uncommon host port 41400 (CYBERINE_GATEWAY_PORT) so it rarely collides. Drive it with the cyberine-project-devstack project skill instead of raw docker compose — it frees the port (stopping our container, then killing any foreign holder) and waits until the gateway answers:

.claude/skills/cyberine-project-devstack/scripts/devstack.sh up        # free port + start, wait for HTTP 200
.claude/skills/cyberine-project-devstack/scripts/devstack.sh status    # gateway + ContextQ state, port, health
.claude/skills/cyberine-project-devstack/scripts/devstack.sh down      # stop (or just leave it — it won't survive a reboot)
.claude/skills/cyberine-project-devstack/scripts/devstack.sh up --with-contextq   # also bring up the ContextQ memory stack

Setting any GATEWAY_* / LOOP_MODEL_* env var (even to the documented defaults) opts the gateway adapters in — a live local gateway at http://localhost:41400/v1 with the default master key and the anthropic/claude + openai/gpt aliases now registers loop-gateway-a / loop-gateway-b instead of being read as "unconfigured". To put Claude/GPT into the WRITING fleet too, add the gateway-backed implementers to CYBERINE_BUILD_FLEET (they also need the gateway up):

docker compose up -d
CYBERINE_TASK="<your task>" CYBERINE_BUILD_FLEET="loop-deepseek-build,loop-claude-build,loop-gpt-build" \
  npm run wf -- src/engine/implementTaskWorkflow.ts   # races DeepSeek vs Claude vs GPT, DiffJudge picks the winner