@counterposition/skill-pi 0.1.1 → 0.65.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  # SDK
2
2
 
3
- The SDK lets you embed Pi programmatically in Node.js applications via `createAgentSession()`.
3
+ The SDK embeds Pi in Node.js applications via `createAgentSession()`. Use `createAgentSessionRuntime()` when you need `/new`, `/resume`, `/fork`, or import-style session replacement.
4
4
 
5
5
  ## Installation
6
6
 
@@ -11,167 +11,223 @@ npm install @mariozechner/pi-coding-agent
11
11
  ## Quick Start
12
12
 
13
13
  ```typescript
14
- import { createAgentSession } from "@mariozechner/pi-coding-agent";
14
+ import {
15
+ AuthStorage,
16
+ createAgentSession,
17
+ ModelRegistry,
18
+ SessionManager,
19
+ } from "@mariozechner/pi-coding-agent";
20
+
21
+ const authStorage = AuthStorage.create();
22
+ const modelRegistry = ModelRegistry.create(authStorage);
15
23
 
16
- const { session } = await createAgentSession();
24
+ const { session } = await createAgentSession({
25
+ sessionManager: SessionManager.inMemory(),
26
+ authStorage,
27
+ modelRegistry,
28
+ });
17
29
 
18
30
  session.subscribe((event) => {
19
- if (event.type === "message_update" && event.delta?.type === "text_delta") {
20
- process.stdout.write(event.delta.text);
21
- }
22
- if (event.type === "agent_end") {
23
- console.log("\nDone.");
31
+ if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
32
+ process.stdout.write(event.assistantMessageEvent.delta);
24
33
  }
25
34
  });
26
35
 
27
- await session.prompt("List all TypeScript files in the current directory");
36
+ await session.prompt("What files are in the current directory?");
28
37
  ```
29
38
 
30
- Run with: `npx tsx my-script.ts`
39
+ Run with `npx tsx my-script.ts`.
40
+
41
+ **Important:** `ModelRegistry` no longer has a public constructor. Use `ModelRegistry.create(authStorage, modelsJsonPath?)` for file-backed registries or `ModelRegistry.inMemory(authStorage)` for built-in models only.
31
42
 
32
- ## Factory Options
43
+ ## Key Imports
33
44
 
34
45
  ```typescript
35
- const { session } = await createAgentSession({
36
- // Authentication
37
- authStorage: new AuthStorage({ path: "~/.pi/agent/auth.json" }),
46
+ import {
47
+ AuthStorage,
48
+ createAgentSession,
49
+ createCodingTools,
50
+ createReadOnlyTools,
51
+ DefaultResourceLoader,
52
+ ModelRegistry,
53
+ SessionManager,
54
+ } from "@mariozechner/pi-coding-agent";
55
+ import { getModel } from "@mariozechner/pi-ai";
56
+ ```
38
57
 
39
- // Model configuration
40
- modelRegistry: new ModelRegistry(),
41
- model: getModel("anthropic", "claude-sonnet-4-20250514"),
42
- thinkingLevel: "medium",
58
+ ## Core Options
43
59
 
44
- // Working directory
45
- cwd: process.cwd(),
60
+ ```typescript
61
+ const cwd = "/path/to/project";
62
+ const authStorage = AuthStorage.create();
63
+ const modelRegistry = ModelRegistry.create(authStorage);
46
64
 
47
- // Agent config directory
65
+ const { session } = await createAgentSession({
66
+ cwd,
48
67
  agentDir: "~/.pi/agent",
68
+ authStorage,
69
+ modelRegistry,
70
+ model: getModel("anthropic", "claude-sonnet-4-20250514"),
71
+ thinkingLevel: "medium",
72
+ scopedModels: [
73
+ { model: getModel("anthropic", "claude-sonnet-4-20250514"), thinkingLevel: "high" },
74
+ ],
75
+ tools: createCodingTools(cwd),
76
+ resourceLoader: new DefaultResourceLoader(),
77
+ sessionManager: SessionManager.inMemory(),
78
+ });
79
+ ```
49
80
 
50
- // Tools (built-in presets or custom)
51
- tools: codingTools(), // Default: read, bash, edit, write
52
- // tools: readOnlyTools(), // read, grep, find, ls
53
- // tools: allTools(), // All seven built-in tools
81
+ Notes:
54
82
 
55
- // Resource discovery
56
- resourceLoader: new DefaultResourceLoader(),
83
+ - `cwd` and `agentDir` control default resource discovery when using `DefaultResourceLoader`.
84
+ - If you provide a custom `cwd` **and** explicit built-in tools, use `createCodingTools(cwd)` / `createReadOnlyTools(cwd)` so paths resolve against that cwd.
85
+ - `DefaultResourceLoader` loads extensions, skills, prompt templates, themes, and context files. If you replace it, `cwd` and `agentDir` no longer drive resource discovery.
57
86
 
58
- // Extensions (inline factories)
59
- extensionFactories: [
60
- (pi) => {
61
- pi.on("agent_start", async () => { /* ... */ });
62
- },
63
- ],
87
+ ## Prompting & Queueing
64
88
 
65
- // Session persistence
66
- sessionManager: SessionManager.create("path/to/session.jsonl"),
67
- // sessionManager: SessionManager.inMemory(), // No persistence
89
+ ```typescript
90
+ await session.prompt("Review the current directory");
68
91
 
69
- // Settings
70
- settings: new SettingsManager({ /* ... */ }),
92
+ // During streaming, choose how the new prompt should queue.
93
+ await session.prompt("Stop and do this instead", { streamingBehavior: "steer" });
94
+ await session.prompt("After you finish, also check tests", { streamingBehavior: "followUp" });
71
95
 
72
- // System prompt
73
- systemPrompt: "Custom system prompt",
74
- });
96
+ await session.steer("Use a smaller diff");
97
+ await session.followUp("Summarize the changes afterward");
75
98
  ```
76
99
 
77
- ## Key Imports
100
+ `prompt()` expands file-based prompt templates. During active streaming, calling it without `streamingBehavior` throws.
101
+
102
+ ## Session Runtime
103
+
104
+ Pi 0.65.0 moved session replacement off `AgentSession` and onto `AgentSessionRuntime`. Use the runtime when you need `newSession()`, `switchSession()`, `fork()`, or `importFromJsonl()`.
78
105
 
79
106
  ```typescript
80
107
  import {
81
- createAgentSession,
82
- // Auth & config
83
- AuthStorage,
84
- ModelRegistry,
85
- SettingsManager,
108
+ type CreateAgentSessionRuntimeFactory,
109
+ createAgentSessionFromServices,
110
+ createAgentSessionRuntime,
111
+ createAgentSessionServices,
112
+ getAgentDir,
86
113
  SessionManager,
87
- DefaultResourceLoader,
88
- // Tools
89
- codingTools,
90
- readOnlyTools,
91
- allTools,
92
- readTool,
93
- bashTool,
94
- editTool,
95
- writeTool,
96
- grepTool,
97
- findTool,
98
- lsTool,
99
- // Models
100
- getModel,
101
114
  } from "@mariozechner/pi-coding-agent";
102
- ```
103
115
 
104
- ## Event Types
105
-
106
- ```typescript
107
- session.subscribe((event) => {
108
- switch (event.type) {
109
- // Streaming
110
- case "message_start": // New assistant message begins
111
- case "message_update": // Text/thinking/tool-call delta
112
- case "message_end": // Assistant message complete (includes full message)
113
-
114
- // Tool execution
115
- case "tool_execution_start": // Tool begins (toolCallId, toolName)
116
- case "tool_execution_update": // Partial tool output
117
- case "tool_execution_end": // Tool complete (toolCallId, result)
118
-
119
- // Lifecycle
120
- case "agent_start": // Agent begins processing
121
- case "agent_end": // Agent finishes
122
- case "turn_start": // New LLM turn
123
- case "turn_end": // LLM turn complete
124
-
125
- // System
126
- case "auto_compaction_start":
127
- case "auto_compaction_end":
128
- case "auto_retry_start":
129
- case "auto_retry_end":
130
- }
116
+ const createRuntime: CreateAgentSessionRuntimeFactory = async ({
117
+ cwd,
118
+ sessionManager,
119
+ sessionStartEvent,
120
+ }) => {
121
+ const services = await createAgentSessionServices({ cwd });
122
+ return {
123
+ ...(await createAgentSessionFromServices({
124
+ services,
125
+ sessionManager,
126
+ sessionStartEvent,
127
+ })),
128
+ services,
129
+ diagnostics: services.diagnostics,
130
+ };
131
+ };
132
+
133
+ const runtime = await createAgentSessionRuntime(createRuntime, {
134
+ cwd: process.cwd(),
135
+ agentDir: getAgentDir(),
136
+ sessionManager: SessionManager.create(process.cwd()),
131
137
  });
138
+
139
+ let session = runtime.session;
140
+ let unsubscribe = session.subscribe(() => {});
141
+
142
+ await runtime.newSession();
143
+
144
+ unsubscribe();
145
+ session = runtime.session;
146
+ unsubscribe = session.subscribe(() => {});
132
147
  ```
133
148
 
134
- ## Session Methods
149
+ Notes:
150
+
151
+ - `runtime.session` changes after replacement. Re-subscribe to session-local events after `newSession()`, `switchSession()`, `fork()`, or `importFromJsonl()`.
152
+ - Cross-cwd replacement rebuilds cwd-bound services and session configuration.
153
+ - `runtime.diagnostics` carries startup and replacement diagnostics instead of printing or exiting directly.
154
+
155
+ ## Session Surface
135
156
 
136
157
  ```typescript
137
- // Send a prompt
138
- await session.prompt("Do something");
158
+ await session.navigateTree("entry-id", { summarize: true });
159
+ await session.compact("Summarize design decisions only");
139
160
 
140
- // Queue message during active streaming
141
- session.steer("Actually, do this instead"); // Interrupts current tool execution
142
- session.followUp("After you're done, also..."); // Queued until agent finishes
161
+ await session.abort();
162
+ await session.agent.waitForIdle();
163
+ session.dispose();
164
+ ```
165
+
166
+ Use `runtime.newSession()`, `runtime.switchSession()`, and `runtime.fork()` for session replacement. Keep `navigateTree()` and `compact()` on the live `session`.
143
167
 
144
- // Control
145
- session.abort(); // Cancel current generation
146
- await session.waitForIdle(); // Wait until agent is idle
168
+ Useful state:
147
169
 
148
- // Model
149
- session.setModel(getModel("openai", "gpt-4o"));
150
- session.setThinkingLevel("high");
151
- session.cycleModel();
170
+ - `session.sessionFile` / `session.sessionId`
171
+ - `session.agent.state.messages`
172
+ - `session.model`
173
+ - `session.thinkingLevel`
152
174
 
153
- // Compaction
154
- await session.compact();
175
+ ## Events
155
176
 
156
- // State
157
- const messages = session.getMessages();
158
- const state = session.getState();
177
+ ```typescript
178
+ session.subscribe((event) => {
179
+ switch (event.type) {
180
+ case "message_update":
181
+ if (event.assistantMessageEvent.type === "text_delta") {
182
+ process.stdout.write(event.assistantMessageEvent.delta);
183
+ }
184
+ break;
185
+ case "tool_execution_start":
186
+ case "tool_execution_update":
187
+ case "tool_execution_end":
188
+ case "agent_start":
189
+ case "agent_end":
190
+ case "turn_start":
191
+ case "turn_end":
192
+ case "queue_update":
193
+ case "compaction_start":
194
+ case "compaction_end":
195
+ case "auto_retry_start":
196
+ case "auto_retry_end":
197
+ break;
198
+ }
199
+ });
159
200
  ```
160
201
 
161
- ## Run Modes
202
+ ## Resource Loader Hooks
162
203
 
163
- For non-SDK usage, Pi provides run mode functions:
204
+ `DefaultResourceLoader` supports overrides for extensions, skills, prompts, themes, and context files. Common patterns:
164
205
 
165
- ```typescript
166
- import { runPrintMode, runRpcMode, InteractiveMode } from "@mariozechner/pi-coding-agent";
206
+ - Add inline extension factories
207
+ - Override `agentsFiles` to inject virtual `AGENTS.md` content
208
+ - Add custom skills or prompt templates without touching disk
209
+ - Share an event bus between the host app and loaded extensions
167
210
 
168
- // Print mode (single-shot)
169
- await runPrintMode(session, { outputFormat: "text" }); // or "json"
211
+ ## Standalone Custom Tools
170
212
 
171
- // RPC mode (JSON-RPC over stdin/stdout)
172
- await runRpcMode(session);
213
+ Use `defineTool()` when you want a reusable custom tool definition outside `pi.registerTool(...)`:
173
214
 
174
- // Interactive mode (full TUI)
175
- const mode = new InteractiveMode(session);
176
- await mode.start();
215
+ ```typescript
216
+ import { defineTool } from "@mariozechner/pi-coding-agent";
217
+ import { Type } from "@sinclair/typebox";
218
+
219
+ const myTool = defineTool({
220
+ name: "my_tool",
221
+ label: "My Tool",
222
+ description: "Does something useful",
223
+ parameters: Type.Object({
224
+ input: Type.String({ description: "Input value" }),
225
+ }),
226
+ async execute(_toolCallId, params) {
227
+ return {
228
+ content: [{ type: "text", text: `Result: ${params.input}` }],
229
+ details: {},
230
+ };
231
+ },
232
+ });
177
233
  ```
@@ -1,170 +1,189 @@
1
1
  # Settings
2
2
 
3
- Pi uses hierarchical JSON configuration. Project settings (`.pi/settings.json`) merge over global settings (`~/.pi/agent/settings.json`). Nested objects are deep-merged; project values override global for the same key.
3
+ Pi uses JSON settings files with project settings overriding global settings:
4
4
 
5
- Edit settings directly or use `/settings` in interactive mode.
5
+ | Location | Scope |
6
+ |----------|-------|
7
+ | `~/.pi/agent/settings.json` | Global |
8
+ | `.pi/settings.json` | Project |
6
9
 
7
- ## Complete Settings Reference
10
+ Edit JSON directly or use `/settings` for common interactive options.
8
11
 
9
- ### Model & Thinking
12
+ ## Model & Thinking
10
13
 
11
14
  ```json
12
15
  {
13
- "provider": "anthropic", // Default provider name
14
- "model": "claude-sonnet-4-20250514", // Default model ID
15
- "thinkingLevel": "medium", // "off" | "minimal" | "low" | "medium" | "high" | "xhigh"
16
- "thinkingLevelBudget": null, // Custom token budget per thinking level (overrides defaults)
17
- "modelCyclePatterns": ["claude-*", "gpt-4o"] // Glob patterns for Ctrl+P model cycling
16
+ "defaultProvider": "anthropic",
17
+ "defaultModel": "claude-sonnet-4-20250514",
18
+ "defaultThinkingLevel": "medium",
19
+ "hideThinkingBlock": false,
20
+ "thinkingBudgets": {
21
+ "minimal": 1024,
22
+ "low": 4096,
23
+ "medium": 10240,
24
+ "high": 32768
25
+ }
18
26
  }
19
27
  ```
20
28
 
21
- ### Compaction
29
+ - Thinking levels: `"off"`, `"minimal"`, `"low"`, `"medium"`, `"high"`, `"xhigh"`
30
+ - `hideThinkingBlock` hides visible thinking output in the UI
31
+
32
+ ## UI & Display
22
33
 
23
34
  ```json
24
35
  {
25
- "compaction": {
26
- "enabled": true, // Enable auto-compaction
27
- "reserveTokens": 16384, // Tokens to reserve for new responses
28
- "keepRecentTokens": 20000 // Recent tokens to keep uncompacted
29
- }
36
+ "theme": "dark",
37
+ "quietStartup": false,
38
+ "collapseChangelog": false,
39
+ "doubleEscapeAction": "tree",
40
+ "treeFilterMode": "default",
41
+ "editorPaddingX": 0,
42
+ "autocompleteMaxVisible": 5,
43
+ "showHardwareCursor": false
30
44
  }
31
45
  ```
32
46
 
33
- ### Branch Summary
47
+ - `doubleEscapeAction`: `"tree"`, `"fork"`, or `"none"`
48
+ - `treeFilterMode`: `"default"`, `"no-tools"`, `"user-only"`, `"labeled-only"`, or `"all"`
49
+ - In `/tree`, `Shift+T` toggles timestamps on entry labels
50
+
51
+ ## Compaction, Branch Summary, Retry
34
52
 
35
53
  ```json
36
54
  {
55
+ "compaction": {
56
+ "enabled": true,
57
+ "reserveTokens": 16384,
58
+ "keepRecentTokens": 20000
59
+ },
37
60
  "branchSummary": {
38
- "enabled": true, // Summarize when switching branches via /tree
39
- "maxTokens": 4096, // Max tokens for summary
40
- "skipPrompt": false // Skip confirmation prompt
61
+ "reserveTokens": 16384,
62
+ "skipPrompt": false
63
+ },
64
+ "retry": {
65
+ "enabled": true,
66
+ "maxRetries": 3,
67
+ "baseDelayMs": 2000,
68
+ "maxDelayMs": 60000
41
69
  }
42
70
  }
43
71
  ```
44
72
 
45
- ### Retry
73
+ Notes:
46
74
 
47
- ```json
48
- {
49
- "retry": {
50
- "maxRetries": 3, // Max automatic retries on transient errors
51
- "initialDelayMs": 2000, // First retry delay
52
- "maxDelayMs": 60000 // Max delay (exponential backoff)
53
- }
54
- }
55
- ```
75
+ - `retry.maxDelayMs = 0` disables the cap on server-requested retry delays.
76
+ - `branchSummary.skipPrompt` skips the confirmation step when navigating with `/tree`.
56
77
 
57
- ### Message Delivery
78
+ ## Message Delivery
58
79
 
59
80
  ```json
60
81
  {
61
- "steeringMode": "all", // "all" | "one-at-a-time" — how steer messages are delivered
62
- "followUpMode": "all", // "all" | "one-at-a-time" — how follow-up messages are delivered
63
- "transport": "auto" // "sse" | "websocket" | "auto"
82
+ "steeringMode": "one-at-a-time",
83
+ "followUpMode": "one-at-a-time",
84
+ "transport": "sse"
64
85
  }
65
86
  ```
66
87
 
67
- ### Terminal & Images
88
+ - `steeringMode` and `followUpMode`: `"all"` or `"one-at-a-time"`
89
+ - `transport`: `"sse"`, `"websocket"`, or `"auto"`
90
+
91
+ ## Terminal, Images, and Shell
68
92
 
69
93
  ```json
70
94
  {
71
95
  "terminal": {
72
- "showImages": true, // Display images in terminal
73
- "maxImageWidth": 2000, // Max image dimensions for resizing
74
- "maxImageHeight": 2000
96
+ "showImages": true,
97
+ "clearOnShrink": false
75
98
  },
76
- "image": {
77
- "autoResize": true // Auto-resize large images before sending to LLM
78
- }
99
+ "images": {
100
+ "autoResize": true,
101
+ "blockImages": false
102
+ },
103
+ "shellPath": "/bin/bash",
104
+ "shellCommandPrefix": "",
105
+ "npmCommand": ["mise", "exec", "node@20", "--", "npm"]
79
106
  }
80
107
  ```
81
108
 
82
- ### Shell
109
+ - `npmCommand` is argv-style and is used for npm lookup/install operations, including git-package installs.
83
110
 
84
- ```json
85
- {
86
- "shellPath": "/bin/bash", // Custom shell path (Windows: "C:\\...\\bash.exe")
87
- "shellCommandPrefix": "", // Prefix prepended to every bash command
88
- "npmCommand": "npm" // npm, pnpm, yarn, bun
89
- }
90
- ```
91
-
92
- ### UI
111
+ ## Sessions, Model Cycling, Markdown
93
112
 
94
113
  ```json
95
114
  {
96
- "theme": "dark", // "dark" | "light" | custom theme name
97
- "showStartupBanner": true, // Show banner on startup
98
- "editorPadding": 1, // Lines of padding in editor
99
- "showAutoComplete": true, // Show autocomplete suggestions
100
- "cursorStyle": "block" // "block" | "underline" | "bar"
115
+ "sessionDir": ".pi/sessions",
116
+ "enabledModels": ["claude-*", "gpt-4o", "gemini-2*"],
117
+ "markdown": {
118
+ "codeBlockIndent": " "
119
+ }
101
120
  }
102
121
  ```
103
122
 
104
- ### Sessions
105
-
106
- ```json
107
- {
108
- "sessionDir": null // Custom session storage path (default: ~/.pi/agent/sessions/)
109
- }
110
- ```
123
+ When multiple sources specify a session directory, `--session-dir` takes precedence over `sessionDir` in settings. Pi 0.65.0 removed the old `session_directory` extension/settings hook.
111
124
 
112
- ### Resources
125
+ ## Resources
113
126
 
114
- Load extensions, skills, prompts, and themes from paths or packages:
127
+ These settings control what Pi loads from disk or packages:
115
128
 
116
129
  ```json
117
130
  {
118
- "extensions": [
119
- "~/.pi/agent/extensions/my-ext.ts",
120
- "npm:@foo/pi-extensions"
121
- ],
122
- "skills": [
123
- ".pi/skills/my-skill",
124
- "npm:@foo/pi-skills"
125
- ],
126
- "prompts": [
127
- "~/.pi/agent/prompts/"
128
- ],
129
- "themes": [
130
- "npm:@foo/pi-themes"
131
+ "packages": [
132
+ "npm:@foo/pi-tools",
133
+ {
134
+ "source": "git:github.com/user/repo@v1",
135
+ "skills": ["brave-search"],
136
+ "extensions": []
137
+ }
131
138
  ],
132
- "customPackages": [
133
- "npm:@foo/pi-package@1.0.0",
134
- "git:github.com/user/repo",
135
- "./local/path"
136
- ]
139
+ "extensions": ["./extensions/my-ext.ts"],
140
+ "skills": ["./skills", "../.claude/skills"],
141
+ "prompts": ["./prompts"],
142
+ "themes": ["./themes"],
143
+ "enableSkillCommands": true
137
144
  }
138
145
  ```
139
146
 
140
- ### Resource Pattern Matching
147
+ Notes:
148
+
149
+ - Paths in `~/.pi/agent/settings.json` resolve relative to `~/.pi/agent`.
150
+ - Paths in `.pi/settings.json` resolve relative to `.pi`.
151
+ - `packages` loads npm/git/local Pi packages.
152
+ - `extensions`, `skills`, `prompts`, and `themes` are for local files/directories.
153
+ - `enableSkillCommands` controls `/skill:name` registration.
141
154
 
142
- Resources support include/exclude patterns:
155
+ ## Include / Exclude Patterns
156
+
157
+ Arrays support filtering:
143
158
 
144
159
  ```json
145
160
  {
146
161
  "skills": [
147
- "npm:@foo/pi-skills", // Include all skills from package
148
- "!secret-skill", // Exclude by name
149
- "+override-skill", // Force-include (overrides exclusions)
150
- "-unwanted-skill" // Force-exclude (overrides inclusions)
162
+ "./skills",
163
+ "!legacy-skill",
164
+ "+must-load-skill",
165
+ "-blocked-skill"
151
166
  ]
152
167
  }
153
168
  ```
154
169
 
170
+ - `!pattern` excludes glob matches
171
+ - `+path` force-includes an exact path
172
+ - `-path` force-excludes an exact path
173
+
155
174
  ## Environment Variables
156
175
 
157
176
  | Variable | Effect |
158
177
  |----------|--------|
159
- | `PI_CODING_AGENT_DIR` | Override global config directory (default: `~/.pi/agent/`) |
160
- | `PI_OFFLINE` | Disable package updates and tool downloads |
161
- | `ANTHROPIC_API_KEY` | Anthropic API key |
162
- | `OPENAI_API_KEY` | OpenAI API key |
163
- | Additional provider env vars | See `references/providers.md` |
164
-
165
- ## Editing Tips
166
-
167
- - Use `/settings` in interactive mode for a guided editor
168
- - Project settings are committed to version control (`.pi/settings.json`) for team sharing
169
- - Global settings (`~/.pi/agent/settings.json`) are personal
170
- - Settings reload on next agent start — no restart needed for most changes
178
+ | `PI_CODING_AGENT_DIR` | Override the global agent directory |
179
+ | `PI_PACKAGE_DIR` | Override package storage directory |
180
+ | `PI_SKIP_VERSION_CHECK` | Skip startup version checks |
181
+ | `PI_CACHE_RETENTION` | Use extended prompt-cache retention where supported |
182
+ | `VISUAL`, `EDITOR` | External editor for `Ctrl+G` |
183
+ | Provider API key env vars | See `references/providers.md` |
184
+
185
+ ## Practical Notes
186
+
187
+ - Project settings are good for shared package/resource configuration.
188
+ - Global settings are better for personal credentials, UI preferences, and model defaults.
189
+ - Nested objects are merged; project keys override the same global keys.