[telegram] how do we set up a deepseek api key to pi?


**Considering user query about Pi CLI**

The user likely wants a concise answer related to the Pi CLI or service configuration. I need to determine if "Pi" refers to a personal agent. My response could include a generic explanation involving environment variables. It might be helpful to inspect the documentation or search the repository for deepseek to clarify further. Let’s make sure I provide the most useful information possible!

bash

cd /home/sebas && rg -n "deepseek|DEEPSEEK|OPENAI_API_KEY|ANTHROPIC_API_KEY" pi-config .agents -S --hidden --glob '!**/node_modules/**'


(no output)

Command exited with code 1


# Personal Agent

## Communication
- Caveman full. Active every response.
- Short, direct, low-noise.
- No filler, pleasantries, hedging, verbal tics.
- Avoid repetition at all costs. Do not restate the same point with different wording unless repetition materially improves clarity or safety.
- Keep tech terms exact. Fragments OK.
- Ask only if scope, risk, or success changes.
- No drift unless user says `normal mode` or `stop caveman`.
- Code, commands, commits, PR text: normal technical form.

## Intent
- Optimize business impact.
- Chase real objective, not surface ask.
- Once clear, execute end to end.
- Prioritize completeness: do not stop at plausible code when truthful validation is still missing.
- Routine choices: decide and move.
- Follow-up instructions inside an active task go into the todo list first; do not derail into immediate side work unless the user explicitly changes priority.
- Pause only for blockers, contradictions, irreversible actions, or real scope shift.
- Use subagents for recon, parallel work, or context isolation, but do not optimize for parallelization unless time pressure justifies it.
- Prefer `ralph-loop-run` when the work is a bounded multi-pass audit, migration, or hardening pass on one repo and can be driven through shared `TASKS.md` / `STATE.md` handoff.
- Good `ralph-loop-run` cases: accessibility sweeps, focused polish passes, test/backfill passes, real-backend migrations, and other cumulative review loops where each pass should pick up exactly where the previous one stopped.
- Do not define Ralph loops mainly by a fixed number of passes. Default objective is to run until one goal gate is true: `done`, `blocked_with_reason`, or `needs_user_decision`.
- Before meaningful execution, lock the intent frame when not already explicit: objective, binary done gate, exclusions, and reality constraints that decide whether a feature is truthful.
- Goal gates that express user intent, business outcome, exclusions, or what counts as truly done should come from the user when unclear. Do not invent intent gates by yourself when doubt remains; ask and lock them first.
- Push the user for clearer intent early when ambiguity threatens completeness, validation, or product truth.
- You may derive technical checklists, feature states, and validator markers after intent gates are clear, but not substitute your own guessed intent for the user's done criteria.
- Every Ralph loop must carry a binary checklist for visible success criteria. Validator tail checks that list item by item; no soft "mostly done" verdicts.
- Use explicit feature states in loop artifacts for every visible feature: `real_working`, `real_read_only`, `blocked_hidden`, or `unknown_do_not_show`.
- Hard rule: if a feature depends on business truth, never present fake/local placeholder backend as real. Validator must fail immediately on that condition.
- Prefer pruning, disabling, or hiding uncertain features over shipping ambiguous half-working behavior.
- Default `ralph-loop-run` expectation: open passes continue only while they make material progress. Material progress means at least one of: code change, state change, evidence gain, risk reduction, or unblock path.
- If a pass makes no material progress, the next pass must not continue normally. It must replan, switch tools/approach, or end in `blocked_with_reason` / `needs_user_decision`.
- Reserve the tail of the loop for validation. The closing sequence is validator -> bounded repair if safe -> mandatory revalidation. Stop only after clean revalidation, explicit blocked state, or explicit user decision.
- The validator should report-only instead of self-repair only when the fix changes scope, raises meaningful risk, or needs user judgment.
- Let validator prune visible scope when needed. If a feature has no confirmed real endpoint or truthful implementation, hide or disable it rather than leaving it half-working.
- Leave this visible in loop artifacts. `STATE.md` should name whether the closing pass was `validator`, `repair`, or `revalidation`, and should also track `candidate_done`, `validator_status`, `repair_applied`, and `revalidation_status`.
- Avoid `ralph-loop-run` for one-shot tasks, broad open-ended exploration, or work that needs rich parallel branching more than strict sequential handoff.
- Use Agents Database as durable shared memory.
- Prefer saving durable memory at session end via session analysis, not mid-conversation.
- Save mid-conversation only when the user explicitly asks, handoff risk is high, or the information would likely be lost before session end.
- Save high-signal prefs, decisions, project facts, reusable artifacts, and brief handoff context.
- Prefer few strong memories over many noisy ones.
- Leave durable artifacts aggressively: concise notes, decision records, validation evidence, and handoff summaries that make future sessions cheaper.
- Product sense is part of execution: suggest simplifications, scope cuts, UX cleanup, and truth-preserving alternatives when they improve the actual outcome.
- Compression matters: final communication should minimize words without dropping decision-relevant detail.
- This Pi config repo is the place to evolve these rules.

## Rules
- Irreversible actions need explicit confirmation.
- Never merge or push to main without user confirmation.
- Stay direct and low-noise.
- Call out contradictions when they matter.
- Code, commands, commits, PR text: normal technical form.

## Machine Context
- Machine: `ballbox-first`.
- Do not SSH into `ballbox-first` for normal local work.
- This Pi is workstation + server for Sebas.
- "the server" usually means this machine unless user says other.

## Server Details
- Hostname: `ballbox-first.emperor-ratio.ts.net`
- Tailscale IP: `100.116.176.16`
- Runs 24/7
- Nginx on `:80` routes local services
- `agents-database`: `http://100.116.176.16:8091/api/status`

## Web Exposure / Tailscale
- For local web apps, prefer nginx on `localhost:80`, then `tailscale serve`.
- Prefer subpaths like `/cli/` before `/` when portal/home exists.
- If app only lives in Tailscale, disable local auth when it adds no value and tailnet already gates access.
- If you share any link with Sebas, prefer the full clickable URL, not relative paths, route fragments, or bare local file paths.
- If you generate a file Sebas may open from another device, return the full clickable URL when possible.
- Prefer: `https://ballbox-first.emperor-ratio.ts.net/files/...`
- Do not expose the local username in shared file URLs unless required.
- Preferred path mapping: `/home/sebas/...` -> `/files/...`, `/mnt/...` -> `/files/mnt/...`.
- Legacy compatibility: old `/files/home/...` links should redirect to `/files/...`.

## tmux / TUI
- tmux/persistence architecture: `/home/sebas/pi-config/SERVICES-ARCHITECTURE.md`.
- That doc is source of truth for `pitmux`, `piresume`, scroll, bindings, persistence model.
- If you touch tmux or `pi` launcher, update that file first.

## Storage Layout
- Canonical layout doc: `/home/sebas/pi-config/docs/system-layout.md`.
- Prefer canonical home-first roots in `/home/sebas` for config, runtime, work trees, outputs, and docs.
- Runtime plane: `/home/sebas/runtime`.
- Work plane: `/home/sebas/work`.
- Use `/mnt/rpi` only when it has an explicit special-storage role, not as the default agent plane.

## Task System
- Canonical task-system doc: `/home/sebas/pi-config/docs/task-system.md`.
- Use `BACKLOG.md` for shaping/research, `TASKS.md` for actionable work, `DONE.md` for completions, and session `todo` for live execution state.
- Concrete tasks should have a workspace folder under `/home/sebas/work/tasks/T-xxx/` for notes and small artifacts.
- Repo-local task systems are allowed; global docs track cross-session outcome/priority, repo-local docs track implementation detail.

## /tmp Cleanup
- Use `bin/tmp-cleanup` for top-level `/tmp` files older than 3 days.
- Install via `scripts/install-tmp-cleanup-cron.sh`.

## Laptop Helpers
- CLI helpers exist for Sebas's laptop via Tailnet SSH-over-TCP.
- Default host: `brag-pipe-fur-kneel.emperor-ratio.ts.net`.
- Default port: `2222`.
- Default key: `~/.ssh/id_ed25519_laptop_agent`.
- Helpers:
  - `laptop-read <remote-path>`: remote cat over SSH.
  - `laptop-run <cmd ...>`: remote command over SSH.
  - `laptop-rsync <src> <dest> [extra args]`: rsync via SSH.
  - `laptop-mount`, `laptop-umount`, `laptop-write`: access/copy flows.
- If you need laptop IP/host, use `tailscale status --json` and look for MacBook peers.
- If task says "files on the laptop", use these helpers instead of manual SSH.

## Telegram
- Vars:
  - `TELEGRAM_BOT_TOKEN`
  - `TELEGRAM_CHAT_ID`
- Notify:
```bash
~/.agents/skills/telegram-notify/telegram-notify "Message"
```
- If a Telegram user sends literal `/new`, do not treat it as normal chat. Acknowledge briefly and run `/home/sebas/pi-config/bin/pi-telegram-new-session` to rotate the tmux-backed Telegram Pi session.


cd /home/sebas && rg -n "deepseek|DEEPSEEK|api key|OPENAI_API_KEY|ANTHROPIC_API_KEY|OPENROUTER_API_KEY" /home/sebas -S --hidden --glob '!**/node_modules/**' --glob '!**/.git/**'


/home/sebas/.pi/agent/sessions/--mnt-rpi--/2026-04-16T19-33-46-674Z_019d97c9-0431-734a-b9ce-92b7cb3e75df.jsonl:116:{"type":"message","id":"849bdd3e","parentId":"48225515","timestamp":"2026-04-16T20:20:39.163Z","message":{"role":"toolResult","toolCallId":"call_874riH7xe3F8M1Cl9734bvZq|fc_09ce6544570a8b100169e14496c6dc819390e7efa2e4206d31","toolName":"read","content":[{"type":"text","text":"> pi can help you use the SDK. Ask it to build an integration for your use case.\n\n# SDK\n\nThe SDK provides programmatic access to pi's agent capabilities. Use it to embed pi in other applications, build custom interfaces, or integrate with automated workflows.\n\n**Example use cases:**\n- Build a custom UI (web, desktop, mobile)\n- Integrate agent capabilities into existing applications\n- Create automated pipelines with agent reasoning\n- Build custom tools that spawn sub-agents\n- Test agent behavior programmatically\n\nSee [examples/sdk/](../examples/sdk/) for working examples from minimal to full control.\n\n## Quick Start\n\n```typescript\nimport { AuthStorage, createAgentSession, ModelRegistry, SessionManager } from \"@mariozechner/pi-coding-agent\";\n\n// Set up credential storage and model registry\nconst authStorage = AuthStorage.create();\nconst modelRegistry = ModelRegistry.create(authStorage);\n\nconst { session } = await createAgentSession({\n  sessionManager: SessionManager.inMemory(),\n  authStorage,\n  modelRegistry,\n});\n\nsession.subscribe((event) => {\n  if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n    process.stdout.write(event.assistantMessageEvent.delta);\n  }\n});\n\nawait session.prompt(\"What files are in the current directory?\");\n```\n\n## Installation\n\n```bash\nnpm install @mariozechner/pi-coding-agent\n```\n\nThe SDK is included in the main package. No separate installation needed.\n\n## Core Concepts\n\n### createAgentSession()\n\nThe main factory function for a single `AgentSession`.\n\n`createAgentSession()` uses a `ResourceLoader` to supply extensions, skills, prompt templates, themes, and context files. If you do not provide one, it uses `DefaultResourceLoader` with standard discovery.\n\n```typescript\nimport { createAgentSession } from \"@mariozechner/pi-coding-agent\";\n\n// Minimal: defaults with DefaultResourceLoader\nconst { session } = await createAgentSession();\n\n// Custom: override specific options\nconst { session } = await createAgentSession({\n  model: myModel,\n  tools: [readTool, bashTool],\n  sessionManager: SessionManager.inMemory(),\n});\n```\n\n### AgentSession\n\nThe session manages agent lifecycle, message history, model state, compaction, and event streaming.\n\n```typescript\ninterface AgentSession {\n  // Send a prompt and wait for completion\n  prompt(text: string, options?: PromptOptions): Promise<void>;\n\n  // Queue messages during streaming\n  steer(text: string): Promise<void>;\n  followUp(text: string): Promise<void>;\n\n  // Subscribe to events (returns unsubscribe function)\n  subscribe(listener: (event: AgentSessionEvent) => void): () => void;\n\n  // Session info\n  sessionFile: string | undefined;\n  sessionId: string;\n\n  // Model control\n  setModel(model: Model): Promise<void>;\n  setThinkingLevel(level: ThinkingLevel): void;\n  cycleModel(): Promise<ModelCycleResult | undefined>;\n  cycleThinkingLevel(): ThinkingLevel | undefined;\n\n  // State access\n  agent: Agent;\n  model: Model | undefined;\n  thinkingLevel: ThinkingLevel;\n  messages: AgentMessage[];\n  isStreaming: boolean;\n\n  // In-place tree navigation within the current session file\n  navigateTree(targetId: string, options?: { summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string }): Promise<{ editorText?: string; cancelled: boolean }>;\n\n  // Compaction\n  compact(customInstructions?: string): Promise<CompactionResult>;\n  abortCompaction(): void;\n\n  // Abort current operation\n  abort(): Promise<void>;\n\n  // Cleanup\n  dispose(): void;\n}\n```\n\nSession replacement APIs such as new-session, resume, fork, and import live on `AgentSessionRuntime`, not on `AgentSession`.\n\n### createAgentSessionRuntime() and AgentSessionRuntime\n\nUse the runtime API when you need to replace the active session and rebuild cwd-bound runtime state.\nThis is the same layer used by the built-in interactive, print, and RPC modes.\n\n`createAgentSessionRuntime()` takes a runtime factory plus the initial cwd/session target. The factory closes over process-global fixed inputs, recreates cwd-bound services for the effective cwd, resolves session options against those services, and returns a full runtime result.\n\n```typescript\nimport {\n  type CreateAgentSessionRuntimeFactory,\n  createAgentSessionFromServices,\n  createAgentSessionRuntime,\n  createAgentSessionServices,\n  getAgentDir,\n  SessionManager,\n} from \"@mariozechner/pi-coding-agent\";\n\nconst createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {\n  const services = await createAgentSessionServices({ cwd });\n  return {\n    ...(await createAgentSessionFromServices({\n      services,\n      sessionManager,\n      sessionStartEvent,\n    })),\n    services,\n    diagnostics: services.diagnostics,\n  };\n};\n\nconst runtime = await createAgentSessionRuntime(createRuntime, {\n  cwd: process.cwd(),\n  agentDir: getAgentDir(),\n  sessionManager: SessionManager.create(process.cwd()),\n});\n```\n\n`AgentSessionRuntime` owns replacement of the active runtime across:\n\n- `newSession()`\n- `switchSession()`\n- `fork()`\n- `importFromJsonl()`\n\nImportant behavior:\n\n- `runtime.session` changes after those operations\n- event subscriptions are attached to a specific `AgentSession`, so re-subscribe after replacement\n- if you use extensions, call `runtime.session.bindExtensions(...)` again for the new session\n- creation returns diagnostics on `runtime.diagnostics`\n- if runtime creation or replacement fails, the method throws and the caller decides how to handle it\n\n```typescript\nlet session = runtime.session;\nlet unsubscribe = session.subscribe(() => {});\n\nawait runtime.newSession();\n\nunsubscribe();\nsession = runtime.session;\nunsubscribe = session.subscribe(() => {});\n```\n\n### Prompting and Message Queueing\n\n`PromptOptions` controls prompt expansion, queueing behavior while streaming, and prompt preflight notifications:\n\n```typescript\ninterface PromptOptions {\n  expandPromptTemplates?: boolean;\n  images?: ImageContent[];\n  streamingBehavior?: \"steer\" | \"followUp\";\n  source?: InputSource;\n  preflightResult?: (success: boolean) => void;\n}\n```\n\n`preflightResult` is called once per `prompt()` invocation:\n\n- `true` when the prompt was accepted, queued, or handled immediately\n- `false` when prompt preflight rejected before acceptance\n\nIt fires before `prompt()` resolves. `prompt()` still resolves only after the full accepted run finishes, including retries. Failures after acceptance are reported through the normal event and message stream, not through `preflightResult(false)`.\n\nThe `prompt()` method handles prompt templates, extension commands, and message sending:\n\n```typescript\n// Basic prompt (when not streaming)\nawait session.prompt(\"What files are here?\");\n\n// With images\nawait session.prompt(\"What's in this image?\", {\n  images: [{ type: \"image\", source: { type: \"base64\", mediaType: \"image/png\", data: \"...\" } }]\n});\n\n// During streaming: must specify how to queue the message\nawait session.prompt(\"Stop and do this instead\", { streamingBehavior: \"steer\" });\nawait session.prompt(\"After you're done, also check X\", { streamingBehavior: \"followUp\" });\n```\n\n**Behavior:**\n- **Extension commands** (e.g., `/mycommand`): Execute immediately, even during streaming. They manage their own LLM interaction via `pi.sendMessage()`.\n- **File-based prompt templates** (from `.md` files): Expanded to their content before sending or queueing.\n- **During streaming without `streamingBehavior`**: Throws an error. Use `steer()` or `followUp()` directly, or specify the option.\n- **`preflightResult(true)`**: Means the prompt was accepted, queued, or handled immediately.\n- **`preflightResult(false)`**: Means preflight rejected before acceptance.\n\nFor explicit queueing during streaming:\n\n```typescript\n// Queue a steering message for delivery after the current assistant turn finishes its tool calls\nawait session.steer(\"New instruction\");\n\n// Wait for agent to finish (delivered only when agent stops)\nawait session.followUp(\"After you're done, also do this\");\n```\n\nBoth `steer()` and `followUp()` expand file-based prompt templates but error on extension commands (extension commands cannot be queued).\n\n### Agent and AgentState\n\nThe `Agent` class (from `@mariozechner/pi-agent-core`) handles the core LLM interaction. Access it via `session.agent`.\n\n```typescript\n// Access current state\nconst state = session.agent.state;\n\n// state.messages: AgentMessage[] - conversation history\n// state.model: Model - current model\n// state.thinkingLevel: ThinkingLevel - current thinking level\n// state.systemPrompt: string - system prompt\n// state.tools: AgentTool[] - available tools\n// state.streamingMessage?: AgentMessage - current partial assistant message\n// state.errorMessage?: string - latest assistant error\n\n// Replace messages (useful for branching or restoration)\nsession.agent.state.messages = messages; // copies the top-level array\n\n// Replace tools\nsession.agent.state.tools = tools; // copies the top-level array\n\n// Wait for agent to finish processing\nawait session.agent.waitForIdle();\n```\n\n### Events\n\nSubscribe to events to receive streaming output and lifecycle notifications.\n\n```typescript\nsession.subscribe((event) => {\n  switch (event.type) {\n    // Streaming text from assistant\n    case \"message_update\":\n      if (event.assistantMessageEvent.type === \"text_delta\") {\n        process.stdout.write(event.assistantMessageEvent.delta);\n      }\n      if (event.assistantMessageEvent.type === \"thinking_delta\") {\n        // Thinking output (if thinking enabled)\n      }\n      break;\n    \n    // Tool execution\n    case \"tool_execution_start\":\n      console.log(`Tool: ${event.toolName}`);\n      break;\n    case \"tool_execution_update\":\n      // Streaming tool output\n      break;\n    case \"tool_execution_end\":\n      console.log(`Result: ${event.isError ? \"error\" : \"success\"}`);\n      break;\n    \n    // Message lifecycle\n    case \"message_start\":\n      // New message starting\n      break;\n    case \"message_end\":\n      // Message complete\n      break;\n    \n    // Agent lifecycle\n    case \"agent_start\":\n      // Agent started processing prompt\n      break;\n    case \"agent_end\":\n      // Agent finished (event.messages contains new messages)\n      break;\n    \n    // Turn lifecycle (one LLM response + tool calls)\n    case \"turn_start\":\n      break;\n    case \"turn_end\":\n      // event.message: assistant response\n      // event.toolResults: tool results from this turn\n      break;\n    \n    // Session events (queue, compaction, retry)\n    case \"queue_update\":\n      console.log(event.steering, event.followUp);\n      break;\n    case \"compaction_start\":\n    case \"compaction_end\":\n    case \"auto_retry_start\":\n    case \"auto_retry_end\":\n      break;\n  }\n});\n```\n\n## Options Reference\n\n### Directories\n\n```typescript\nconst { session } = await createAgentSession({\n  // Working directory for DefaultResourceLoader discovery\n  cwd: process.cwd(), // default\n  \n  // Global config directory\n  agentDir: \"~/.pi/agent\", // default (expands ~)\n});\n```\n\n`cwd` is used by `DefaultResourceLoader` for:\n- Project extensions (`.pi/extensions/`)\n- Project skills:\n  - `.pi/skills/`\n  - `.agents/skills/` in `cwd` and ancestor directories (up to git repo root, or filesystem root when not in a repo)\n- Project prompts (`.pi/prompts/`)\n- Context files (`AGENTS.md` walking up from cwd)\n- Session directory naming\n\n`agentDir` is used by `DefaultResourceLoader` for:\n- Global extensions (`extensions/`)\n- Global skills:\n  - `skills/` under `agentDir` (for example `~/.pi/agent/skills/`)\n  - `~/.agents/skills/`\n- Global prompts (`prompts/`)\n- Global context file (`AGENTS.md`)\n- Settings (`settings.json`)\n- Custom models (`models.json`)\n- Credentials (`auth.json`)\n- Sessions (`sessions/`)\n\nWhen you pass a custom `ResourceLoader`, `cwd` and `agentDir` no longer control resource discovery. They still influence session naming and tool path resolution.\n\n### Model\n\n```typescript\nimport { getModel } from \"@mariozechner/pi-ai\";\nimport { AuthStorage, ModelRegistry } from \"@mariozechner/pi-coding-agent\";\n\nconst authStorage = AuthStorage.create();\nconst modelRegistry = ModelRegistry.create(authStorage);\n\n// Find specific built-in model (doesn't check if API key exists)\nconst opus = getModel(\"anthropic\", \"claude-opus-4-5\");\nif (!opus) throw new Error(\"Model not found\");\n\n// Find any model by provider/id, including custom models from models.json\n// (doesn't check if API key exists)\nconst customModel = modelRegistry.find(\"my-provider\", \"my-model\");\n\n// Get only models that have valid API keys configured\nconst available = await modelRegistry.getAvailable();\n\nconst { session } = await createAgentSession({\n  model: opus,\n  thinkingLevel: \"medium\", // off, minimal, low, medium, high, xhigh\n  \n  // Models for cycling (Ctrl+P in interactive mode)\n  scopedModels: [\n    { model: opus, thinkingLevel: \"high\" },\n    { model: haiku, thinkingLevel: \"off\" },\n  ],\n  \n  authStorage,\n  modelRegistry,\n});\n```\n\nIf no model is provided:\n1. Tries to restore from session (if continuing)\n2. Uses default from settings\n3. Falls back to first available model\n\n> See [examples/sdk/02-custom-model.ts](../examples/sdk/02-custom-model.ts)\n\n### API Keys and OAuth\n\nAPI key resolution priority (handled by AuthStorage):\n1. Runtime overrides (via `setRuntimeApiKey`, not persisted)\n2. Stored credentials in `auth.json` (API keys or OAuth tokens)\n3. Environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, etc.)\n4. Fallback resolver (for custom provider keys from `models.json`)\n\n```typescript\nimport { AuthStorage, ModelRegistry } from \"@mariozechner/pi-coding-agent\";\n\n// Default: uses ~/.pi/agent/auth.json and ~/.pi/agent/models.json\nconst authStorage = AuthStorage.create();\nconst modelRegistry = ModelRegistry.create(authStorage);\n\nconst { session } = await createAgentSession({\n  sessionManager: SessionManager.inMemory(),\n  authStorage,\n  modelRegistry,\n});\n\n// Runtime API key override (not persisted to disk)\nauthStorage.setRuntimeApiKey(\"anthropic\", \"sk-my-temp-key\");\n\n// Custom auth storage location\nconst customAuth = AuthStorage.create(\"/my/app/auth.json\");\nconst customRegistry = ModelRegistry.create(customAuth, \"/my/app/models.json\");\n\nconst { session } = await createAgentSession({\n  sessionManager: SessionManager.inMemory(),\n  authStorage: customAuth,\n  modelRegistry: customRegistry,\n});\n\n// No custom models.json (built-in models only)\nconst simpleRegistry = ModelRegistry.inMemory(authStorage);\n```\n\n> See [examples/sdk/09-api-keys-and-oauth.ts](../examples/sdk/09-api-keys-and-oauth.ts)\n\n### System Prompt\n\nUse a `ResourceLoader` to override the system prompt:\n\n```typescript\nimport { createAgentSession, DefaultResourceLoader } from \"@mariozechner/pi-coding-agent\";\n\nconst loader = new DefaultResourceLoader({\n  systemPromptOverride: () => \"You are a helpful assistant.\",\n});\nawait loader.reload();\n\nconst { session } = await createAgentSession({ resourceLoader: loader });\n```\n\n> See [examples/sdk/03-custom-prompt.ts](../examples/sdk/03-custom-prompt.ts)\n\n### Tools\n\n```typescript\nimport {\n  codingTools,   // read, bash, edit, write (default)\n  readOnlyTools, // read, grep, find, ls\n  readTool, bashTool, editTool, writeTool,\n  grepTool, findTool, lsTool,\n} from \"@mariozechner/pi-coding-agent\";\n\n// Use built-in tool set\nconst { session } = await createAgentSession({\n  tools: readOnlyTools,\n});\n\n// Pick specific tools\nconst { session } = await createAgentSession({\n  tools: [readTool, bashTool, grepTool],\n});\n```\n\n#### Tools with Custom cwd\n\n**Important:** The pre-built tool instances (`readTool`, `bashTool`, etc.) use `process.cwd()` for path resolution. When you specify a custom `cwd` AND provide explicit `tools`, you must use the tool factory functions to ensure paths resolve correctly:\n\n```typescript\nimport {\n  createCodingTools,    // Creates [read, bash, edit, write] for specific cwd\n  createReadOnlyTools,  // Creates [read, grep, find, ls] for specific cwd\n  createReadTool,\n  createBashTool,\n  createEditTool,\n  createWriteTool,\n  createGrepTool,\n  createFindTool,\n  createLsTool,\n} from \"@mariozechner/pi-coding-agent\";\n\nconst cwd = \"/path/to/project\";\n\n// Use factory for tool sets\nconst { session } = await createAgentSession({\n  cwd,\n  tools: createCodingTools(cwd),  // Tools resolve paths relative to cwd\n});\n\n// Or pick specific tools\nconst { session } = await createAgentSession({\n  cwd,\n  tools: [createReadTool(cwd), createBashTool(cwd), createGrepTool(cwd)],\n});\n```\n\n**When you don't need factories:**\n- If you omit `tools`, pi automatically creates them with the correct `cwd`\n- If you use `process.cwd()` as your `cwd`, the pre-built instances work fine\n\n**When you must use factories:**\n- When you specify both `cwd` (different from `process.cwd()`) AND `tools`\n\n> See [examples/sdk/05-tools.ts](../examples/sdk/05-tools.ts)\n\n### Custom Tools\n\n```typescript\nimport { Type } from \"@sinclair/typebox\";\nimport { createAgentSession, defineTool } from \"@mariozechner/pi-coding-agent\";\n\n// Inline custom tool\nconst myTool = defineTool({\n  name: \"my_tool\",\n  label: \"My Tool\",\n  description: \"Does something useful\",\n  parameters: Type.Object({\n    input: Type.String({ description: \"Input value\" }),\n  }),\n  execute: async (_toolCallId, params) => ({\n    content: [{ type: \"text\", text: `Result: ${params.input}` }],\n    details: {},\n  }),\n});\n\n// Pass custom tools directly\nconst { session } = await createAgentSession({\n  customTools: [myTool],\n});\n```\n\nUse `defineTool()` for standalone definitions and arrays like `customTools: [myTool]`. Inline `pi.registerTool({ ... })` already infers parameter types correctly.\n\nCustom tools passed via `customTools` are combined with extension-registered tools. Extensions loaded by the ResourceLoader can also register tools via `pi.registerTool()`.\n\n> See [examples/sdk/05-tools.ts](../examples/sdk/05-tools.ts)\n\n### Extensions\n\nExtensions are loaded by the `ResourceLoader`. `DefaultResourceLoader` discovers extensions from `~/.pi/agent/extensions/`, `.pi/extensions/`, and settings.json extension sources.\n\n```typescript\nimport { createAgentSession, DefaultResourceLoader } from \"@mariozechner/pi-coding-agent\";\n\nconst loader = new DefaultResourceLoader({\n  additionalExtensionPaths: [\"/path/to/my-extension.ts\"],\n  extensionFactories: [\n    (pi) => {\n      pi.on(\"agent_start\", () => {\n        console.log(\"[Inline Extension] Agent starting\");\n      });\n    },\n  ],\n});\nawait loader.reload();\n\nconst { session } = await createAgentSession({ resourceLoader: loader });\n```\n\nExtensions can register tools, subscribe to events, add commands, and more. See [extensions.md](extensions.md) for the full API.\n\n**Event Bus:** Extensions can communicate via `pi.events`. Pass a shared `eventBus` to `DefaultResourceLoader` if you need to emit or listen from outside:\n\n```typescript\nimport { createEventBus, DefaultResourceLoader } from \"@mariozechner/pi-coding-agent\";\n\nconst eventBus = createEventBus();\nconst loader = new DefaultResourceLoader({\n  eventBus,\n});\nawait loader.reload();\n\neventBus.on(\"my-extension:status\", (data) => console.log(data));\n```\n\n> See [examples/sdk/06-extensions.ts](../examples/sdk/06-extensions.ts) and [docs/extensions.md](extensions.md)\n\n### Skills\n\n```typescript\nimport {\n  createAgentSession,\n  DefaultResourceLoader,\n  type Skill,\n} from \"@mariozechner/pi-coding-agent\";\n\nconst customSkill: Skill = {\n  name: \"my-skill\",\n  description: \"Custom instructions\",\n  filePath: \"/path/to/SKILL.md\",\n  baseDir: \"/path/to\",\n  source: \"custom\",\n};\n\nconst loader = new DefaultResourceLoader({\n  skillsOverride: (current) => ({\n    skills: [...current.skills, customSkill],\n    diagnostics: current.diagnostics,\n  }),\n});\nawait loader.reload();\n\nconst { session } = await createAgentSession({ resourceLoader: loader });\n```\n\n> See [examples/sdk/04-skills.ts](../examples/sdk/04-skills.ts)\n\n### Context Files\n\n```typescript\nimport { createAgentSession, DefaultResourceLoader } from \"@mariozechner/pi-coding-agent\";\n\nconst loader = new DefaultResourceLoader({\n  agentsFilesOverride: (current) => ({\n    agentsFiles: [\n      ...current.agentsFiles,\n      { path: \"/virtual/AGENTS.md\", content: \"# Guidelines\\n\\n- Be concise\" },\n    ],\n  }),\n});\nawait loader.reload();\n\nconst { session } = await createAgentSession({ resourceLoader: loader });\n```\n\n> See [examples/sdk/07-context-files.ts](../examples/sdk/07-context-files.ts)\n\n### Slash Commands\n\n```typescript\nimport {\n  createAgentSession,\n  DefaultResourceLoader,\n  type PromptTemplate,\n} from \"@mariozechner/pi-coding-agent\";\n\nconst customCommand: PromptTemplate = {\n  name: \"deploy\",\n  description: \"Deploy the application\",\n  source: \"(custom)\",\n  content: \"# Deploy\\n\\n1. Build\\n2. Test\\n3. Deploy\",\n};\n\nconst loader = new DefaultResourceLoader({\n  promptsOverride: (current) => ({\n    prompts: [...current.prompts, customCommand],\n    diagnostics: current.diagnostics,\n  }),\n});\nawait loader.reload();\n\nconst { session } = await createAgentSession({ resourceLoader: loader });\n```\n\n> See [examples/sdk/08-prompt-templates.ts](../examples/sdk/08-prompt-templates.ts)\n\n### Session Management\n\nSessions use a tree structure with `id`/`parentId` linking, enabling in-place branching.\n\n```typescript\nimport {\n  type CreateAgentSessionRuntimeFactory,\n  createAgentSession,\n  createAgentSessionFromServices,\n  createAgentSessionRuntime,\n  createAgentSessionServices,\n  getAgentDir,\n  SessionManager,\n} from \"@mariozechner/pi-coding-agent\";\n\n// In-memory (no persistence)\nconst { session } = await createAgentSession({\n  sessionManager: SessionManager.inMemory(),\n});\n\n// New persistent session\nconst { session: persisted } = await createAgentSession({\n  sessionManager: SessionManager.create(process.cwd()),\n});\n\n// Continue most recent\nconst { session: continued, modelFallbackMessage } = await createAgentSession({\n  sessionManager: SessionManager.continueRecent(process.cwd()),\n});\nif (modelFallbackMessage) {\n  console.log(\"Note:\", modelFallbackMessage);\n}\n\n// Open specific file\nconst { session: opened } = await createAgentSession({\n  sessionManager: SessionManager.open(\"/path/to/session.jsonl\"),\n});\n\n// List sessions\nconst currentProjectSessions = await SessionManager.list(process.cwd());\nconst allSessions = await SessionManager.listAll(process.cwd());\n\n// Session replacement API for /new, /resume, /fork, and import flows.\nconst createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {\n  const services = await createAgentSessionServices({ cwd });\n  return {\n    ...(await createAgentSessionFromServices({\n      services,\n      sessionManager,\n      sessionStartEvent,\n    })),\n    services,\n    diagnostics: services.diagnostics,\n  };\n};\n\nconst runtime = await createAgentSessionRuntime(createRuntime, {\n  cwd: process.cwd(),\n  agentDir: getAgentDir(),\n  sessionManager: SessionManager.create(process.cwd()),\n});\n\n// Replace the active session with a fresh one\nawait runtime.newSession();\n\n// Replace the active session with another saved session\nawait runtime.switchSession(\"/path/to/session.jsonl\");\n\n// Replace the active session with a fork from a specific entry\nawait runtime.fork(\"entry-id\");\n```\n\n**SessionManager tree API:**\n\n```typescript\nconst sm = SessionManager.open(\"/path/to/session.jsonl\");\n\n// Session listing\nconst currentProjectSessions = await SessionManager.list(process.cwd());\nconst allSessions = await SessionManager.listAll(process.cwd());\n\n// Tree traversal\nconst entries = sm.getEntries();        // All entries (excludes header)\nconst tree = sm.getTree();              // Full tree structure\nconst path = sm.getPath();              // Path from root to current leaf\nconst leaf = sm.getLeafEntry();         // Current leaf entry\nconst entry = sm.getEntry(id);          // Get entry by ID\nconst children = sm.getChildren(id);    // Direct children of entry\n\n// Labels\nconst label = sm.getLabel(id);          // Get label for entry\nsm.appendLabelChange(id, \"checkpoint\"); // Set label\n\n// Branching\nsm.branch(entryId);                     // Move leaf to earlier entry\nsm.branchWithSummary(id, \"Summary...\");  // Branch with context summary\nsm.createBranchedSession(leafId);       // Extract path to new file\n```\n\n> See [examples/sdk/11-sessions.ts](../examples/sdk/11-sessions.ts) and [docs/session.md](session.md)\n\n### Settings Management\n\n```typescript\nimport { createAgentSession, SettingsManager, SessionManager } from \"@mariozechner/pi-coding-agent\";\n\n// Default: loads from files (global + project merged)\nconst { session } = await createAgentSession({\n  settingsManager: SettingsManager.create(),\n});\n\n// With overrides\nconst settingsManager = SettingsManager.create();\nsettingsManager.applyOverrides({\n  compaction: { enabled: false },\n  retry: { enabled: true, maxRetries: 5 },\n});\nconst { session } = await createAgentSession({ settingsManager });\n\n// In-memory (no file I/O, for testing)\nconst { session } = await createAgentSession({\n  settingsManager: SettingsManager.inMemory({ compaction: { enabled: false } }),\n  sessionManager: SessionManager.inMemory(),\n});\n\n// Custom directories\nconst { session } = await createAgentSession({\n  settingsManager: SettingsManager.create(\"/custom/cwd\", \"/custom/agent\"),\n});\n```\n\n**Static factories:**\n- `SettingsManager.create(cwd?, agentDir?)` - Load from files\n- `SettingsManager.inMemory(settings?)` - No file I/O\n\n**Project-specific settings:**\n\nSettings load from two locations and merge:\n1. Global: `~/.pi/agent/settings.json`\n2. Project: `<cwd>/.pi/settings.json`\n\nProject overrides global. Nested objects merge keys. Setters modify global settings by default.\n\n**Persistence and error handling semantics:**\n\n- Settings getters/setters are synchronous for in-memory state.\n- Setters enqueue persistence writes asynchronously.\n- Call `await settingsManager.flush()` when you need a durability boundary (for example, before process exit or before asserting file contents in tests).\n- `SettingsManager` does not print settings I/O errors. Use `settingsManager.drainErrors()` and report them in your app layer.\n\n> See [examples/sdk/10-settings.ts](../examples/sdk/10-settings.ts)\n\n## ResourceLoader\n\nUse `DefaultResourceLoader` to discover extensions, skills, prompts, themes, and context files.\n\n```typescript\nimport {\n  DefaultResourceLoader,\n  getAgentDir,\n} from \"@mariozechner/pi-coding-agent\";\n\nconst loader = new DefaultResourceLoader({\n  cwd,\n  agentDir: getAgentDir(),\n});\nawait loader.reload();\n\nconst extensions = loader.getExtensions();\nconst skills = loader.getSkills();\nconst prompts = loader.getPrompts();\nconst themes = loader.getThemes();\nconst contextFiles = loader.getAgentsFiles().agentsFiles;\n```\n\n## Return Value\n\n`createAgentSession()` returns:\n\n```typescript\ninterface CreateAgentSessionResult {\n  // The session\n  session: AgentSession;\n  \n  // Extensions result (for runner setup)\n  extensionsResult: LoadExtensionsResult;\n  \n  // Warning if session model couldn't be restored\n  modelFallbackMessage?: string;\n}\n\ninterface LoadExtensionsResult {\n  extensions: Extension[];\n  errors: Array<{ path: string; error: string }>;\n  runtime: ExtensionRuntime;\n}\n```\n\n## Complete Example\n\n```typescript\nimport { getModel } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport {\n  AuthStorage,\n  bashTool,\n  createAgentSession,\n  DefaultResourceLoader,\n  defineTool,\n  ModelRegistry,\n  readTool,\n  SessionManager,\n  SettingsManager,\n} from \"@mariozechner/pi-coding-agent\";\n\n// Set up auth storage (custom location)\nconst authStorage = AuthStorage.create(\"/custom/agent/auth.json\");\n\n// Runtime API key override (not persisted)\nif (process.env.MY_KEY) {\n  authStorage.setRuntimeApiKey(\"anthropic\", process.env.MY_KEY);\n}\n\n// Model registry (no custom models.json)\nconst modelRegistry = ModelRegistry.create(authStorage);\n\n// Inline tool\nconst statusTool = defineTool({\n  name: \"status\",\n  label: \"Status\",\n  description: \"Get system status\",\n  parameters: Type.Object({}),\n  execute: async () => ({\n    content: [{ type: \"text\", text: `Uptime: ${process.uptime()}s` }],\n    details: {},\n  }),\n});\n\nconst model = getModel(\"anthropic\", \"claude-opus-4-5\");\nif (!model) throw new Error(\"Model not found\");\n\n// In-memory settings with overrides\nconst settingsManager = SettingsManager.inMemory({\n  compaction: { enabled: false },\n  retry: { enabled: true, maxRetries: 2 },\n});\n\nconst loader = new DefaultResourceLoader({\n  cwd: process.cwd(),\n  agentDir: \"/custom/agent\",\n  settingsManager,\n  systemPromptOverride: () => \"You are a minimal assistant. Be concise.\",\n});\nawait loader.reload();\n\nconst { session } = await createAgentSession({\n  cwd: process.cwd(),\n  agentDir: \"/custom/agent\",\n\n  model,\n  thinkingLevel: \"off\",\n  authStorage,\n  modelRegistry,\n\n  tools: [readTool, bashTool],\n  customTools: [statusTool],\n  resourceLoader: loader,\n\n  sessionManager: SessionManager.inMemory(),\n  settingsManager,\n});\n\nsession.subscribe((event) => {\n  if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n    process.stdout.write(event.assistantMessageEvent.delta);\n  }\n});\n\nawait session.prompt(\"Get status and list files.\");\n```\n\n## Run Modes\n\nThe SDK exports run mode utilities for building custom interfaces on top of `createAgentSession()`:\n\n### InteractiveMode\n\nFull TUI interactive mode with editor, chat history, and all built-in commands:\n\n```typescript\nimport {\n  type CreateAgentSessionRuntimeFactory,\n  createAgentSessionFromServices,\n  createAgentSessionRuntime,\n  createAgentSessionServices,\n  getAgentDir,\n  InteractiveMode,\n  SessionManager,\n} from \"@mariozechner/pi-coding-agent\";\n\nconst createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {\n  const services = await createAgentSessionServices({ cwd });\n  return {\n    ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent })),\n    services,\n    diagnostics: services.diagnostics,\n  };\n};\nconst runtime = await createAgentSessionRuntime(createRuntime, {\n  cwd: process.cwd(),\n  agentDir: getAgentDir(),\n  sessionManager: SessionManager.create(process.cwd()),\n});\n\nconst mode = new InteractiveMode(runtime, {\n  migratedProviders: [],\n  modelFallbackMessage: undefined,\n  initialMessage: \"Hello\",\n  initialImages: [],\n  initialMessages: [],\n});\n\nawait mode.run();\n```\n\n### runPrintMode\n\nSingle-shot mode: send prompts, output result, exit:\n\n```typescript\nimport {\n  type CreateAgentSessionRuntimeFactory,\n  createAgentSessionFromServices,\n  createAgentSessionRuntime,\n  createAgentSessionServices,\n  getAgentDir,\n  runPrintMode,\n  SessionManager,\n} from \"@mariozechner/pi-coding-agent\";\n\nconst createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {\n  const services = await createAgentSessionServices({ cwd });\n  return {\n    ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent })),\n    services,\n    diagnostics: services.diagnostics,\n  };\n};\nconst runtime = await createAgentSessionRuntime(createRuntime, {\n  cwd: process.cwd(),\n  agentDir: getAgentDir(),\n  sessionManager: SessionManager.create(process.cwd()),\n});\n\nawait runPrintMode(runtime, {\n  mode: \"text\",\n  initialMessage: \"Hello\",\n  initialImages: [],\n  messages: [\"Follow up\"],\n});\n```\n\n### runRpcMode\n\nJSON-RPC mode for subprocess integration:\n\n```typescript\nimport {\n  type CreateAgentSessionRuntimeFactory,\n  createAgentSessionFromServices,\n  createAgentSessionRuntime,\n  createAgentSessionServices,\n  getAgentDir,\n  runRpcMode,\n  SessionManager,\n} from \"@mariozechner/pi-coding-agent\";\n\nconst createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {\n  const services = await createAgentSessionServices({ cwd });\n  return {\n    ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent })),\n    services,\n    diagnostics: services.diagnostics,\n  };\n};\nconst runtime = await createAgentSessionRuntime(createRuntime, {\n  cwd: process.cwd(),\n  agentDir: getAgentDir(),\n  sessionManager: SessionManager.create(process.cwd()),\n});\n\nawait runRpcMode(runtime);\n```\n\nSee [RPC documentation](rpc.md) for the JSON protocol.\n\n## RPC Mode Alternative\n\nFor subprocess-based integration without building with the SDK, use the CLI directly:\n\n```bash\npi --mode rpc --no-session\n```\n\nSee [RPC documentation](rpc.md) for the JSON protocol.\n\nThe SDK is preferred when:\n- You want type safety\n- You're in the same Node.js process\n- You need direct access to agent state\n- You want to customize tools/extensions programmatically\n\nRPC mode is preferred when:\n- You're integrating from another language\n- You want process isolation\n- You're building a language-agnostic client\n\n## Exports\n\nThe main entry point exports:\n\n```typescript\n// Factory\ncreateAgentSession\ncreateAgentSessionRuntime\nAgentSessionRuntime\n\n// Auth and Models\nAuthStorage\nModelRegistry\n\n// Resource loading\nDefaultResourceLoader\ntype ResourceLoader\ncreateEventBus\n\n// Helpers\ndefineTool\n\n// Session management\nSessionManager\nSettingsManager\n\n// Built-in tools (use process.cwd())\ncodingTools\nreadOnlyTools\nreadTool, bashTool, editTool, writeTool\ngrepTool, findTool, lsTool\n\n// Tool factories (for custom cwd)\ncreateCodingTools\ncreateReadOnlyTools\ncreateReadTool, createBashTool, createEditTool, createWriteTool\ncreateGrepTool, createFindTool, createLsTool\n\n// Types\ntype CreateAgentSessionOptions\ntype CreateAgentSessionResult\ntype ExtensionFactory\ntype ExtensionAPI\ntype ToolDefinition\ntype Skill\ntype PromptTemplate\ntype Tool\n```\n\nFor extension types, see [extensions.md](extensions.md) for the full API.\n"}],"isError":false,"timestamp":1776370839163}}
/home/sebas/archive/backups/telegram-bot-listener-backup-20260118/config.py:20:ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY")
/home/sebas/archive/backups/telegram-bot-listener-backup-20260118/venv/lib/python3.13/site-packages/anthropic/_client.py:89:        - `api_key` from `ANTHROPIC_API_KEY`
/home/sebas/archive/backups/telegram-bot-listener-backup-20260118/venv/lib/python3.13/site-packages/anthropic/_client.py:93:            api_key = os.environ.get("ANTHROPIC_API_KEY")
/home/sebas/archive/backups/telegram-bot-listener-backup-20260118/venv/lib/python3.13/site-packages/anthropic/_client.py:329:        - `api_key` from `ANTHROPIC_API_KEY`
/home/sebas/archive/backups/telegram-bot-listener-backup-20260118/venv/lib/python3.13/site-packages/anthropic/_client.py:333:            api_key = os.environ.get("ANTHROPIC_API_KEY")
/home/sebas/runtime/venvs/catt/lib/python3.13/site-packages/yt_dlp/extractor/locipo.py:124:        api_key = traverse_obj(config, ('public', 'streaksVodPlaybackApiKey', {str}, {require('api key')}))
/home/sebas/runtime/venvs/catt/lib/python3.13/site-packages/yt_dlp/extractor/flickr.py:70:            'Downloading api key')['site_key']
/home/sebas/archive/backups/telegram-bot-listener-backup-20260118/venv/lib/python3.13/site-packages/anthropic-0.76.0.dist-info/METADATA:72:    api_key=os.environ.get("ANTHROPIC_API_KEY"),  # This is the default and can be omitted
/home/sebas/archive/backups/telegram-bot-listener-backup-20260118/venv/lib/python3.13/site-packages/anthropic-0.76.0.dist-info/METADATA:90:to add `ANTHROPIC_API_KEY="my-anthropic-api-key"` to your `.env` file
/home/sebas/archive/backups/telegram-bot-listener-backup-20260118/venv/lib/python3.13/site-packages/anthropic-0.76.0.dist-info/METADATA:103:    api_key=os.environ.get("ANTHROPIC_API_KEY"),  # This is the default and can be omitted
/home/sebas/archive/backups/telegram-bot-listener-backup-20260118/venv/lib/python3.13/site-packages/anthropic-0.76.0.dist-info/METADATA:148:        api_key=os.environ.get("ANTHROPIC_API_KEY"),  # This is the default and can be omitted
/home/sebas/runtime/rss-offline-build.log:36:OK rendered https://simonw.substack.com/p/deepseek-v4-and-the-end-of-the-openaimicrosoft
/home/sebas/runtime/rss-offline-build.log:72:OK simple https://simonw.substack.com/p/deepseek-v4-and-the-end-of-the-openaimicrosoft
/home/sebas/runtime/venvs/whisper/lib/python3.13/site-packages/torch/include/ATen/cuda/CUDAScaledBlas.h:151:bool check_deepseek_recipe(ScalingType,
/home/sebas/runtime/venvs/whisper/lib/python3.13/site-packages/torch/distributed/checkpoint/quantized_hf_storage.py:482:                # 2D block-wise quantization, e.g., used in deepseek v3.1
/home/sebas/runtime/venvs/whisper/lib/python3.13/site-packages/torch/distributed/pipelining/schedules.py:3008:    Based on the open sourced code from https://github.com/deepseek-ai/DualPipe
/home/sebas/.pnpm-store/v10/files/7b/f87de30e222ec16ffd1224d4941925d7bfec993f77c8d792ce2395e7c4f68e4dcc7d3264880650c071d7a020e5e55ce4bd4f3c38868e46c0888858a413c32a:425:| `apiKey`          | Required | Your api key.                                                                                |

[Showing lines 3909-3927 of 3927 (50.0KB limit). Full output: /tmp/pi-bash-4122af059b88a3b9.log]

Command timed out after 20 seconds


**Searching for SDK information**

I’m looking for information in the SDK documents to provide a generic answer. I think I should include details about the provider and possibly mention custom models. I need to find out if DeepSeek is built-in, so I may need to search through the package for that. I wonder what specific sections in the documentation will help clarify whether DeepSeek is available or not.

node -e 'try{const {getModel}=require("@mariozechner/pi-ai"); console.log(!!getModel("deepseek","deepseek-chat"), !!getModel("deepseek","deepseek-reasoner"));}catch(e){console.error(e.message); process.exit(1)}'


Cannot find module '@mariozechner/pi-ai'
Require stack:
- /home/sebas/[eval]

Command exited with code 1


Two paths.

### Fast
Put key in shell env used to start `pi`:

```bash
export DEEPSEEK_API_KEY='your-key-here'
pi
```

Make it persistent:

```bash
echo "export DEEPSEEK_API_KEY='your-key-here'" >> ~/.bashrc
source ~/.bashrc
```

### Pi-native
Pi checks creds in this order:
1. runtime override
2. `~/.pi/agent/auth.json`
3. env vars

So you can also store it in:

`~/.pi/agent/auth.json`

Example shape:

```json
{
  "apiKeys": {
    "deepseek": "your-key-here"
  }
}
```

### If DeepSeek model is not already registered
Then also add provider/model entries in:

`~/.pi/agent/models.json`

If you want, I can set this up on `ballbox-first` now.
Send:
- the key
- which model(s): `deepseek-chat`, `deepseek-reasoner`, or other

Do not paste the key here unless you want me to install it.


[telegram] is it officially supported?


Not sure. Likely **not first-class/built-in** on this Pi setup right now.

What I do know:
- `pi` supports provider keys via env/auth storage.
- I did **not** find local config showing DeepSeek already wired in.
- So this may be:
  - supported if current `pi` build includes a DeepSeek provider, or
  - possible via custom `models.json` provider config.

Short version:
- **Official in upstream pi?** I can verify.
- **Official in your current machine config?** No sign of it.

If you want, I check the installed `pi` version and whether `deepseek` is a known provider/model here.


[telegram] upstream, check here and online


**Checking package and tools**

I need to use the web, possibly looking at the curl package documentation. My local installed pi might not be adequate. I’m considering if skill memory is needed — maybe not. I’ll check online and locally using bash to inspect the installed packages or get help from the command line. I could use npm package documentation too. Let's start with bash commands like "which pi" and "pi --help" to list models and check npm info.

set -e
which pi || true
pi --version || true
pi --help | sed -n '1,220p' || true
python - <<'PY'
import os,glob
for p in glob.glob('/home/sebas/.pnpm-store/v10/files/**', recursive=True):
    pass
PY

set -e
curl -L --max-time 20 -s https://raw.githubusercontent.com/mariozechner/pi-coding-agent/main/docs/sdk.md | rg -n "deepseek|DeepSeek" -n -S || true
curl -L --max-time 20 -s https://raw.githubusercontent.com/mariozechner/pi-coding-agent/main/README.md | rg -n "deepseek|DeepSeek" -n -S || true
npm view @mariozechner/pi-coding-agent version dist-tags.latest --json || true


/home/sebas/.npm-global/bin/pi
0.74.0
Warning: No models match pattern "google-gemini-cli/gemini-3-flash-preview"
Warning: No models match pattern "nvidia/nvidia/nemotron-4-340b-instruct"
Warning: No models match pattern "openrouter/nvidia/nemotron-3-super-120b-a12b:free"
Warning: No models match pattern "openrouter/qwen/qwen3-coder:free"
pi - AI coding assistant with read, bash, edit, write tools

Usage:
  pi [options] [@files...] [messages...]

Commands:
  pi install <source> [-l]     Install extension source and add to settings
  pi remove <source> [-l]      Remove extension source from settings
  pi uninstall <source> [-l]   Alias for remove
  pi update [source|self|pi]   Update pi and installed extensions
  pi list                      List installed extensions from settings
  pi config                    Open TUI to enable/disable package resources
  pi <command> --help          Show help for install/remove/uninstall/update/list

Options:
  --provider <name>              Provider name (default: google)
  --model <pattern>              Model pattern or ID (supports "provider/id" and optional ":<thinking>")
  --api-key <key>                API key (defaults to env vars)
  --system-prompt <text>         System prompt (default: coding assistant prompt)
  --append-system-prompt <text>  Append text or file contents to the system prompt (can be used multiple times)
  --mode <mode>                  Output mode: text (default), json, or rpc
  --print, -p                    Non-interactive mode: process prompt and exit
  --continue, -c                 Continue previous session
  --resume, -r                   Select a session to resume
  --session <path|id>            Use specific session file or partial UUID
  --fork <path|id>               Fork specific session file or partial UUID into a new session
  --session-dir <dir>            Directory for session storage and lookup
  --no-session                   Don't save session (ephemeral)
  --models <patterns>            Comma-separated model patterns for Ctrl+P cycling
                                 Supports globs (anthropic/*, *sonnet*) and fuzzy matching
  --no-tools, -nt                Disable all tools by default (built-in and extension)
  --no-builtin-tools, -nbt       Disable built-in tools by default but keep extension/custom tools enabled
  --tools, -t <tools>            Comma-separated allowlist of tool names to enable
                                 Applies to built-in, extension, and custom tools
  --thinking <level>             Set thinking level: off, minimal, low, medium, high, xhigh
  --extension, -e <path>         Load an extension file (can be used multiple times)
  --no-extensions, -ne           Disable extension discovery (explicit -e paths still work)
  --skill <path>                 Load a skill file or directory (can be used multiple times)
  --no-skills, -ns               Disable skills discovery and loading
  --prompt-template <path>       Load a prompt template file or directory (can be used multiple times)
  --no-prompt-templates, -np     Disable prompt template discovery and loading
  --theme <path>                 Load a theme file or directory (can be used multiple times)
  --no-themes                    Disable theme discovery and loading
  --no-context-files, -nc        Disable AGENTS.md and CLAUDE.md discovery and loading
  --export <file>                Export session file to HTML and exit
  --list-models [search]         List available models (with optional fuzzy search)
  --verbose                      Force verbose startup (overrides quietStartup setting)
  --offline                      Disable startup network operations (same as PI_OFFLINE=1)
  --help, -h                     Show this help
  --version, -v                  Show version number

Extensions can register additional flags (e.g., --plan from plan-mode extension).
Extension CLI Flags:
  --switch-session            Open the session-switch picker after startup, then relaunch into the selected session

Examples:
  # Interactive mode
  pi

  # Interactive mode with initial prompt
  pi "List all .ts files in src/"

  # Include files in initial message
  pi @prompt.md @image.png "What color is the sky?"

  # Non-interactive mode (process and exit)
  pi -p "List all .ts files in src/"

  # Multiple messages (interactive)
  pi "Read package.json" "What dependencies do we have?"

  # Continue previous session
  pi --continue "What did we discuss?"

  # Use different model
  pi --provider openai --model gpt-4o-mini "Help me refactor this code"

  # Use model with provider prefix (no --provider needed)
  pi --model openai/gpt-4o "Help me refactor this code"

  # Use model with thinking level shorthand
  pi --model sonnet:high "Solve this complex problem"

  # Limit model cycling to specific models
  pi --models claude-sonnet,claude-haiku,gpt-4o

  # Limit to a specific provider with glob pattern
  pi --models "github-copilot/*"

  # Cycle models with fixed thinking levels
  pi --models sonnet:high,haiku:low

  # Start with a specific thinking level
  pi --thinking high "Solve this complex problem"

  # Read-only mode (no file modifications possible)
  pi --tools read,grep,find,ls -p "Review the code in src/"

  # Export a session file to HTML
  pi --export ~/.pi/agent/sessions/--path--/session.jsonl
  pi --export session.jsonl output.html

Environment Variables:
  ANTHROPIC_API_KEY                - Anthropic Claude API key
  ANTHROPIC_OAUTH_TOKEN            - Anthropic OAuth token (alternative to API key)
  OPENAI_API_KEY                   - OpenAI GPT API key
  AZURE_OPENAI_API_KEY             - Azure OpenAI API key
  AZURE_OPENAI_BASE_URL            - Azure OpenAI/Cognitive Services base URL (e.g. https://{resource}.openai.azure.com)
  AZURE_OPENAI_RESOURCE_NAME       - Azure OpenAI resource name (alternative to base URL)
  AZURE_OPENAI_API_VERSION         - Azure OpenAI API version (default: v1)
  AZURE_OPENAI_DEPLOYMENT_NAME_MAP - Azure OpenAI model=deployment map (comma-separated)
  DEEPSEEK_API_KEY                 - DeepSeek API key
  GEMINI_API_KEY                   - Google Gemini API key
  GROQ_API_KEY                     - Groq API key
  CEREBRAS_API_KEY                 - Cerebras API key
  XAI_API_KEY                      - xAI Grok API key
  FIREWORKS_API_KEY                - Fireworks API key
  OPENROUTER_API_KEY               - OpenRouter API key
  AI_GATEWAY_API_KEY               - Vercel AI Gateway API key
  ZAI_API_KEY                      - ZAI API key
  MISTRAL_API_KEY                  - Mistral API key
  MINIMAX_API_KEY                  - MiniMax API key
  MOONSHOT_API_KEY                 - Moonshot AI API key
  OPENCODE_API_KEY                 - OpenCode Zen/OpenCode Go API key
  KIMI_API_KEY                     - Kimi For Coding API key
  CLOUDFLARE_API_KEY               - Cloudflare API token (Workers AI and AI Gateway)
  CLOUDFLARE_ACCOUNT_ID            - Cloudflare account id (required for both)
  CLOUDFLARE_GATEWAY_ID            - Cloudflare AI Gateway slug (required for AI Gateway)
  XIAOMI_API_KEY                   - Xiaomi MiMo API key (api.xiaomimimo.com billing)
  XIAOMI_TOKEN_PLAN_CN_API_KEY     - Xiaomi MiMo Token Plan API key (China region)
  XIAOMI_TOKEN_PLAN_AMS_API_KEY    - Xiaomi MiMo Token Plan API key (Amsterdam region)
  XIAOMI_TOKEN_PLAN_SGP_API_KEY    - Xiaomi MiMo Token Plan API key (Singapore region)
  AWS_PROFILE                      - AWS profile for Amazon Bedrock
  AWS_ACCESS_KEY_ID                - AWS access key for Amazon Bedrock
  AWS_SECRET_ACCESS_KEY            - AWS secret key for Amazon Bedrock
  AWS_BEARER_TOKEN_BEDROCK         - Bedrock API key (bearer token)
  AWS_REGION                       - AWS region for Amazon Bedrock (e.g., us-east-1)
  PI_CODING_AGENT_DIR              - Config directory (default: ~/.pi/agent)
  PI_CODING_AGENT_SESSION_DIR      - Session storage directory (overridden by --session-dir)
  PI_PACKAGE_DIR                   - Override package directory (for Nix/Guix store paths)
  PI_OFFLINE                       - Disable startup network operations when set to 1/true/yes
  PI_TELEMETRY                     - Override install telemetry when set to 1/true/yes or 0/false/no
  PI_SHARE_VIEWER_URL              - Base URL for /share command (default: https://pi.dev/session/)

Built-in Tool Names:
  read   - Read file contents
  bash   - Execute bash commands
  edit   - Edit files with find/replace
  write  - Write files (creates/overwrites)
  grep   - Search file contents (read-only, off by default)
  find   - Find files by glob pattern (read-only, off by default)
  ls     - List directory contents (read-only, off by default)


**Exploring package details**

I need to figure out why the online grep output didn't show anything relevant. It seems like there’s no mention at all. Maybe I should check the package source online for deepseek. I could inspect the installed package or the npm tarball; that might help find the deepseek provider. So, let's try listing the models for deepseek and then grep the installed package specifically for deepseek.

pi --list-models deepseek | sed -n '1,220p'

set -e
PI_BIN=$(readlink -f $(which pi))
echo "$PI_BIN"
PKG_DIR=$(cd "$(dirname "$PI_BIN")/../lib/node_modules/@mariozechner/pi-coding-agent" 2>/dev/null && pwd || true)
echo "PKG_DIR=$PKG_DIR"
if [ -n "$PKG_DIR" ]; then rg -n "deepseek|DEEPSEEK" "$PKG_DIR" -S --glob '!**/node_modules/**' | sed -n '1,200p'; fi

set -e
curl -L --max-time 20 -s https://registry.npmjs.org/@mariozechner/pi-coding-agent/latest | jq -r '.version, .repository.url, .homepage, .bugs.url'


Warning: No models match pattern "google-gemini-cli/gemini-3-flash-preview"
Warning: No models match pattern "nvidia/nvidia/nemotron-4-340b-instruct"
Warning: No models match pattern "openrouter/nvidia/nemotron-3-super-120b-a12b:free"
Warning: No models match pattern "openrouter/qwen/qwen3-coder:free"
provider  model                               context  max-out  thinking  images
nvidia    deepseek-ai/deepseek-v3.1-terminus  128K     8.2K     yes       no
nvidia    deepseek-ai/deepseek-v3.2           163.8K   65.5K    yes       no
nvidia    deepseek-ai/deepseek-v4-flash       1.0M     393.2K   yes       no
nvidia    deepseek-ai/deepseek-v4-pro         1.0M     393.2K   yes       no


/home/sebas/.npm-global/lib/node_modules/@earendil-works/pi-coding-agent/dist/cli.js
PKG_DIR=


0.73.1
git+https://github.com/badlogic/pi-mono.git
https://github.com/badlogic/pi-mono#readme
https://github.com/badlogic/pi-mono/issues


**Exploring package options**

I need to gather some information from upstream sources, maybe checking the docs or source for the DeepSeek environment variable and the provider implementation. Since the installed path uses a different namespace, I'm considering using curl to search the raw package repo via the GitHub API. However, maybe using an npm tarball would be simpler. I'll explore these options—checking if the GitHub search API is unauthenticated and possibly using curl on the repo tree.

set -e
TMP=$(mktemp -d)
cd "$TMP"
URL=$(curl -s https://registry.npmjs.org/@mariozechner/pi-coding-agent/latest | jq -r '.dist.tarball')
echo "$URL"
curl -L -s "$URL" -o pkg.tgz
tar -xzf pkg.tgz
rg -n "DEEPSEEK_API_KEY|deepseek" package -S | sed -n '1,220p'

set -e
curl -L --max-time 20 -s https://raw.githubusercontent.com/badlogic/pi-mono/main/README.md | rg -n "DEEPSEEK|deepseek" -S || true
curl -L --max-time 20 -s https://raw.githubusercontent.com/badlogic/pi-mono/main/docs/sdk.md | rg -n "DEEPSEEK|deepseek" -S || true