indusagi-coding-agent 0.1.21 → 0.1.23

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.
package/docs/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # Indusagi Coding Agent
2
+
3
+ A terminal-first coding agent with a small core and strong extensibility. Use it interactively, in scripts (print/JSON), over RPC, or as an SDK in your own apps.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g indusagi-coding-agent
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ indusagi
15
+ ```
16
+
17
+ Authenticate using `/login` or set provider API keys.
18
+
19
+ ## Modes
20
+
21
+ | Mode | Description | Docs |
22
+ |------|-------------|------|
23
+ | Interactive | Default mode with TUI | - |
24
+ | Print/JSON | For scripting | [json.md](json.md) |
25
+ | RPC | Process integration | [rpc.md](rpc.md) |
26
+ | SDK | Programmatic usage | [sdk.md](sdk.md) |
27
+
28
+ ## Key Features
29
+
30
+ - **Multi-provider support** - Anthropic, OpenAI, Google, and more
31
+ - **Extensible** - Extensions, skills, hooks, and themes
32
+ - **Session management** - Tree-based navigation, branching, compaction
33
+ - **Tool system** - Built-in tools for files, bash, search
34
+ - **Subagents** - Spawn specialized agents for complex tasks
35
+
36
+ ## Documentation Map
37
+
38
+ ### Getting Started
39
+ - [Providers](providers.md) - Configure LLM providers
40
+ - [Settings](settings.md) - Global and project settings
41
+
42
+ ### SDK & API
43
+ - [SDK Reference](sdk.md) - Programmatic usage
44
+ - [RPC Mode](rpc.md) - JSON-RPC integration
45
+ - [JSON Mode](json.md) - Scripting output
46
+
47
+ ### Customization
48
+ - [Extensions](extensions.md) - Extend agent behavior
49
+ - [Skills](skills.md) - On-demand capability packages
50
+ - [Hooks](hooks.md) - Low-level interception
51
+ - [Subagents](subagents.md) - Spawn specialized agents
52
+ - [Prompt Templates](prompt-templates.md) - Custom prompts
53
+ - [Themes](themes.md) - UI theming
54
+ - [Packages](packages.md) - Share resources
55
+
56
+ ### Session Management
57
+ - [Session Format](session.md) - File format and structure
58
+ - [Tree Navigation](tree.md) - Branch and navigate history
59
+ - [Compaction](compaction.md) - Context management
60
+
61
+ ### Configuration
62
+ - [Settings](settings.md) - All configuration options
63
+ - [Custom Models](models.md) - Add custom providers
64
+ - [Custom Providers](custom-provider.md) - Proxy support
65
+ - [Keybindings](keybindings.md) - Keyboard shortcuts
66
+
67
+ ### UI
68
+ - [TUI Components](tui.md) - Terminal UI
69
+ - [Terminal Setup](terminal-setup.md) - Terminal configuration
70
+
71
+ ### Platform
72
+ - [Development](development.md) - Contributing
73
+ - [Shell Aliases](shell-aliases.md) - Shell integration
74
+ - [Windows](windows.md) - Windows-specific notes
75
+
76
+ ## License
77
+
78
+ MIT
@@ -1713,7 +1713,7 @@ All examples in [examples/extensions/](../examples/extensions/).
1713
1713
  | `ssh.ts` | SSH remote execution | `registerFlag`, `on("user_bash")`, `on("before_agent_start")`, tool operations |
1714
1714
  | `interactive-shell.ts` | Persistent shell session | `on("user_bash")` |
1715
1715
  | `sandbox/` | Sandboxed tool execution | Tool operations |
1716
- | `subagent/` | Spawn sub-agents | `registerTool`, `exec` |
1716
+ | `subagent/` | Spawn sub-agents | `registerTool`, `exec` (see [subagents.md](subagents.md) for built-in task tool) |
1717
1717
  | **Games** |||
1718
1718
  | `snake.ts` | Snake game | `registerCommand`, `ui.custom`, keyboard handling |
1719
1719
  | `space-invaders.ts` | Space Invaders game | `registerCommand`, `ui.custom` |
package/docs/hooks.md ADDED
@@ -0,0 +1,378 @@
1
+ # Hooks
2
+
3
+ Hooks are TypeScript modules that intercept and modify internal operations. Unlike extensions (which use event-based APIs), hooks use a simpler input/output transformation model for specific injection points.
4
+
5
+ > **Note**: Most customization should use [extensions](extensions.md) instead. Hooks are lower-level and intended for specific use cases where you need to transform data at well-defined points.
6
+
7
+ ## When to Use Hooks vs Extensions
8
+
9
+ | Use Hooks When | Use Extensions When |
10
+ |----------------|---------------------|
11
+ | Transforming chat parameters | Listening to lifecycle events |
12
+ | Modifying tool arguments/results | Registering custom tools |
13
+ | Adding custom headers to requests | Adding slash commands |
14
+ | Transforming system prompts | Building interactive UIs |
15
+ | Modifying shell environment | Stateful tool implementations |
16
+
17
+ ## Hook Locations
18
+
19
+ Hooks are loaded from:
20
+
21
+ - Global: `~/.indusagi/agent/hooks/*.ts`
22
+ - Project: `.indusagi/hooks/*.ts`
23
+ - Settings: `hooks` array in `settings.json`
24
+ - CLI: `--hook <path>` flag
25
+
26
+ ## Hook File Format
27
+
28
+ A hook file exports a factory function that receives context and returns hook handlers:
29
+
30
+ ```typescript
31
+ import type { HookFactory } from "indusagi-coding-agent";
32
+
33
+ export default ((ctx) => {
34
+ console.log(`Hooks loading in: ${ctx.cwd}`);
35
+
36
+ return {
37
+ // Hook handlers go here
38
+ "tool.execute.before": async (input, output) => {
39
+ // Modify tool arguments before execution
40
+ },
41
+ };
42
+ }) satisfies HookFactory;
43
+ ```
44
+
45
+ ### HookInitContext
46
+
47
+ The factory receives an initialization context:
48
+
49
+ ```typescript
50
+ interface HookInitContext {
51
+ cwd: string; // Current working directory
52
+ agentDir: string; // Agent config directory
53
+ events: EventBus; // Shared event bus
54
+ exec: (command: string, args: string[], options?: ExecOptions) => Promise<ExecResult>;
55
+ }
56
+ ```
57
+
58
+ ## Available Hooks
59
+
60
+ ### chat.params
61
+
62
+ Modify streaming parameters before calling the LLM.
63
+
64
+ ```typescript
65
+ "chat.params": async (input, output) => {
66
+ // input: { sessionId, model, systemPrompt }
67
+ // output: { options: SimpleStreamOptions }
68
+
69
+ // Add custom timeout
70
+ output.options.timeout = 60000;
71
+
72
+ // Enable caching
73
+ output.options.caching = true;
74
+ }
75
+ ```
76
+
77
+ ### chat.headers
78
+
79
+ Add custom headers to LLM requests.
80
+
81
+ ```typescript
82
+ "chat.headers": async (input, output) => {
83
+ // input: { sessionId, model, systemPrompt }
84
+ // output: { headers: Record<string, string> }
85
+
86
+ // Add custom headers
87
+ output.headers["x-custom-header"] = "value";
88
+ output.headers["x-request-id"] = input.sessionId;
89
+ }
90
+ ```
91
+
92
+ ### chat.message
93
+
94
+ Transform user messages before sending to LLM.
95
+
96
+ ```typescript
97
+ "chat.message": async (input, output) => {
98
+ // input: { sessionId, model? }
99
+ // output: { content: (TextContent | ImageContent)[] }
100
+
101
+ // Prepend context to message
102
+ output.content = [
103
+ { type: "text", text: "Additional context:\n" },
104
+ ...output.content,
105
+ ];
106
+ }
107
+ ```
108
+
109
+ ### tool.execute.before
110
+
111
+ Modify tool arguments before execution.
112
+
113
+ ```typescript
114
+ "tool.execute.before": async (input, output) => {
115
+ // input: { tool, sessionId, callId }
116
+ // output: { args: Record<string, unknown> }
117
+
118
+ if (input.tool === "bash") {
119
+ // Log all bash commands
120
+ console.log(`Bash: ${output.args.command}`);
121
+
122
+ // Inject environment variable
123
+ output.args.command = `export CUSTOM_VAR=1 && ${output.args.command}`;
124
+ }
125
+
126
+ if (input.tool === "write") {
127
+ // Block writes to sensitive paths
128
+ if (String(output.args.path).includes(".env")) {
129
+ throw new Error("Cannot write to .env files");
130
+ }
131
+ }
132
+ }
133
+ ```
134
+
135
+ ### tool.execute.after
136
+
137
+ Transform tool results after execution.
138
+
139
+ ```typescript
140
+ "tool.execute.after": async (input, output) => {
141
+ // input: { tool, sessionId, callId, args }
142
+ // output: { content, details, isError }
143
+
144
+ if (input.tool === "bash" && !output.isError) {
145
+ // Truncate long output
146
+ const text = output.content[0];
147
+ if (text?.type === "text" && text.text.length > 10000) {
148
+ output.content = [{
149
+ type: "text",
150
+ text: text.text.slice(0, 10000) + "\n... (truncated)",
151
+ }];
152
+ }
153
+ }
154
+ }
155
+ ```
156
+
157
+ ### tool.definition
158
+
159
+ Modify tool descriptions and parameters.
160
+
161
+ ```typescript
162
+ "tool.definition": async (input, output) => {
163
+ // input: { toolId }
164
+ // output: { description, parameters }
165
+
166
+ if (input.toolId === "bash") {
167
+ // Add usage examples to description
168
+ output.description += "\n\nExamples:\n- List files: `ls -la`\n- Find text: `grep -r 'pattern' .`";
169
+ }
170
+ }
171
+ ```
172
+
173
+ ### shell.env
174
+
175
+ Inject environment variables into shell sessions.
176
+
177
+ ```typescript
178
+ "shell.env": async (input, output) => {
179
+ // input: { cwd }
180
+ // output: { env: Record<string, string> }
181
+
182
+ // Add custom environment
183
+ output.env["MY_PROJECT_ROOT"] = input.cwd;
184
+ output.env["NODE_ENV"] = "development";
185
+
186
+ // Load from .env file
187
+ const dotenv = await import("dotenv");
188
+ const config = dotenv.config({ path: `${input.cwd}/.env` });
189
+ if (config.parsed) {
190
+ Object.assign(output.env, config.parsed);
191
+ }
192
+ }
193
+ ```
194
+
195
+ ### command.execute.before
196
+
197
+ Transform extension command arguments.
198
+
199
+ ```typescript
200
+ "command.execute.before": async (input, output) => {
201
+ // input: { sessionId, command, args }
202
+ // output: { args: string }
203
+
204
+ if (input.command === "mycommand") {
205
+ // Preprocess arguments
206
+ output.args = output.args.toUpperCase();
207
+ }
208
+ }
209
+ ```
210
+
211
+ ## Experimental Hooks
212
+
213
+ These hooks are experimental and may change:
214
+
215
+ ### experimental.chat.system.transform
216
+
217
+ Transform the system prompt.
218
+
219
+ ```typescript
220
+ "experimental.chat.system.transform": async (input, output) => {
221
+ // input: { sessionId, model }
222
+ // output: { system: string }
223
+
224
+ // Append custom instructions
225
+ output.system += "\n\nAdditional instructions:\n- Be concise\n- Focus on code quality";
226
+ }
227
+ ```
228
+
229
+ ### experimental.chat.messages.transform
230
+
231
+ Transform the message history.
232
+
233
+ ```typescript
234
+ "experimental.chat.messages.transform": async (input, output) => {
235
+ // input: { sessionId }
236
+ // output: { messages: AgentMessage[] }
237
+
238
+ // Filter out old messages
239
+ const cutoff = Date.now() - 3600000; // 1 hour
240
+ output.messages = output.messages.filter(
241
+ (m) => m.timestamp > cutoff
242
+ );
243
+ }
244
+ ```
245
+
246
+ ### experimental.session.compacting
247
+
248
+ Customize session compaction.
249
+
250
+ ```typescript
251
+ "experimental.session.compacting": async (input, output) => {
252
+ // input: { sessionId }
253
+ // output: { context: string[], prompt?: string }
254
+
255
+ // Custom compaction prompt
256
+ output.prompt = "Summarize this conversation focusing on:\n- Decisions made\n- Files modified\n- Pending tasks";
257
+ }
258
+ ```
259
+
260
+ ### experimental.text.complete
261
+
262
+ Transform auto-complete suggestions.
263
+
264
+ ```typescript
265
+ "experimental.text.complete": async (input, output) => {
266
+ // input: { sessionId }
267
+ // output: { text: string }
268
+
269
+ // Post-process completion
270
+ output.text = output.text.trim();
271
+ }
272
+ ```
273
+
274
+ ## Error Handling
275
+
276
+ Hook errors are logged but don't crash the session:
277
+
278
+ ```typescript
279
+ "tool.execute.before": async (input, output) => {
280
+ throw new Error("This error is logged, session continues");
281
+ }
282
+ ```
283
+
284
+ To abort an operation, modify the output appropriately:
285
+
286
+ ```typescript
287
+ "tool.execute.before": async (input, output) => {
288
+ // Block by throwing with specific message
289
+ if (forbiddenOperation(output.args)) {
290
+ throw new Error("Operation blocked by security policy");
291
+ }
292
+ }
293
+ ```
294
+
295
+ ## Complete Example
296
+
297
+ ```typescript
298
+ // ~/.indusagi/agent/hooks/audit-logger.ts
299
+ import type { HookFactory } from "indusagi-coding-agent";
300
+ import * as fs from "fs";
301
+ import * as path from "path";
302
+
303
+ export default ((ctx) => {
304
+ const logFile = path.join(ctx.agentDir, "audit.log");
305
+
306
+ function log(entry: object) {
307
+ const line = JSON.stringify({ ...entry, timestamp: new Date().toISOString() }) + "\n";
308
+ fs.appendFileSync(logFile, line);
309
+ }
310
+
311
+ return {
312
+ "tool.execute.before": async (input, output) => {
313
+ log({
314
+ event: "tool_start",
315
+ tool: input.tool,
316
+ callId: input.callId,
317
+ args: output.args,
318
+ });
319
+ },
320
+
321
+ "tool.execute.after": async (input, output) => {
322
+ log({
323
+ event: "tool_end",
324
+ tool: input.tool,
325
+ callId: input.callId,
326
+ isError: output.isError,
327
+ });
328
+ },
329
+
330
+ "shell.env": async (input, output) => {
331
+ log({
332
+ event: "shell_start",
333
+ cwd: input.cwd,
334
+ });
335
+ },
336
+ };
337
+ }) satisfies HookFactory;
338
+ ```
339
+
340
+ ## Loading Hooks
341
+
342
+ ### Via Settings
343
+
344
+ ```json
345
+ {
346
+ "hooks": [
347
+ "~/.indusagi/agent/hooks/audit-logger.ts",
348
+ ".indusagi/hooks/project-hooks.ts"
349
+ ]
350
+ }
351
+ ```
352
+
353
+ ### Via CLI
354
+
355
+ ```bash
356
+ indusagi --hook ./my-hooks.ts
357
+ ```
358
+
359
+ ### Programmatically
360
+
361
+ ```typescript
362
+ import { loadHooks, discoverHooksInDir, DefaultResourceLoader } from "indusagi-coding-agent";
363
+
364
+ // Discover hooks in a directory
365
+ const hookPaths = discoverHooksInDir("./hooks");
366
+
367
+ // Load hooks
368
+ const result = await loadHooks(hookPaths, process.cwd());
369
+ if (result.errors.length > 0) {
370
+ console.error("Hook errors:", result.errors);
371
+ }
372
+ ```
373
+
374
+ ## Related
375
+
376
+ - [Extensions](extensions.md) - Higher-level customization API
377
+ - [SDK](sdk.md) - Programmatic usage
378
+ - [Settings](settings.md) - Configuration options
package/docs/sdk.md CHANGED
@@ -952,6 +952,18 @@ type ToolDefinition
952
952
  type Skill
953
953
  type PromptTemplate
954
954
  type Tool
955
+
956
+ // Subagents
957
+ SubagentStore
958
+ createTaskTool
959
+
960
+ // Hooks
961
+ loadHooks
962
+ HookFactory
963
+ type Hooks
964
+ type HookInitContext
955
965
  ```
956
966
 
957
967
  For extension types, see [extensions.md](extensions.md) for the full API.
968
+ For subagents, see [subagents.md](subagents.md).
969
+ For hooks, see [hooks.md](hooks.md).
@@ -0,0 +1,225 @@
1
+ # Subagents
2
+
3
+ Subagents are specialized agents that run in isolated contexts with their own system prompts, tools, and model configurations. They're useful for delegating complex or multi-step tasks while keeping the primary context clean.
4
+
5
+ ## Overview
6
+
7
+ The subagent system consists of:
8
+
9
+ - **Task Tool** - Built-in `task` tool that the LLM can call to spawn subagents
10
+ - **Subagent Definitions** - Markdown files defining subagent behavior, tools, and model
11
+ - **SubagentStore** - Discovers and manages available subagent definitions
12
+
13
+ ## Using Subagents
14
+
15
+ The LLM can spawn subagents via the `task` tool:
16
+
17
+ ```
18
+ Use the explore subagent to search for authentication-related code
19
+ ```
20
+
21
+ The task tool parameters:
22
+
23
+ | Parameter | Type | Description |
24
+ |-----------|------|-------------|
25
+ | `description` | string | Short (3-5 words) description of the task |
26
+ | `prompt` | string | The task for the subagent to perform |
27
+ | `subagent_type` | string | Type of subagent (e.g., "general", "explore") |
28
+ | `task_id` | string? | Resume a previous task by ID |
29
+
30
+ ## Built-in Subagents
31
+
32
+ ### general
33
+
34
+ General-purpose subagent for multi-step tasks and research.
35
+
36
+ - **Tools**: read, bash, edit, write, grep, find, ls
37
+ - **Mode**: subagent only
38
+
39
+ ### explore
40
+
41
+ Specialized in codebase exploration and search.
42
+
43
+ - **Tools**: read, grep, find, ls (read-only)
44
+ - **Mode**: subagent only
45
+
46
+ ## Custom Subagent Definitions
47
+
48
+ Create subagent definitions in:
49
+
50
+ - **Global**: `~/.indusagi/agent/agents/*.md`
51
+ - **Project**: `.indusagi/agents/*.md`
52
+
53
+ ### Definition Format
54
+
55
+ ```markdown
56
+ ---
57
+ name: my-agent
58
+ description: What this agent does and when to use it
59
+ tools: read, grep, find, ls
60
+ model: anthropic/claude-sonnet-4-5
61
+ thinkingLevel: medium
62
+ mode: subagent
63
+ hidden: false
64
+ ---
65
+
66
+ System prompt for the agent goes here. This becomes the subagent's
67
+ system prompt, replacing the default.
68
+
69
+ Use this space to define:
70
+ - The agent's specialization
71
+ - How it should approach tasks
72
+ - Output format expectations
73
+ - Any constraints or guidelines
74
+ ```
75
+
76
+ ### Frontmatter Fields
77
+
78
+ | Field | Required | Description |
79
+ |-------|----------|-------------|
80
+ | `name` | Yes | Unique identifier (lowercase, hyphens allowed) |
81
+ | `description` | No | Brief description of the agent's purpose |
82
+ | `tools` | No | Comma-separated list of tools: `read, bash, edit, write, grep, find, ls` |
83
+ | `model` | No | Model in `provider/id` format (e.g., `anthropic/claude-sonnet-4-5`) |
84
+ | `thinkingLevel` | No | Thinking level: `off`, `minimal`, `low`, `medium`, `high`, `xhigh` |
85
+ | `mode` | No | Visibility: `subagent` (default), `primary`, `all` |
86
+ | `hidden` | No | Hide from LLM discovery (default: `false`) |
87
+
88
+ ### Mode Values
89
+
90
+ | Mode | Description |
91
+ |------|-------------|
92
+ | `subagent` | Only available as a subagent via task tool (default) |
93
+ | `primary` | Only available as primary agent (not as subagent) |
94
+ | `all` | Available in both contexts |
95
+
96
+ ### Tool Options
97
+
98
+ Available tools for subagents:
99
+
100
+ | Tool | Description |
101
+ |------|-------------|
102
+ | `read` | Read file contents |
103
+ | `bash` | Execute shell commands |
104
+ | `edit` | Edit files by replacing text |
105
+ | `write` | Create or overwrite files |
106
+ | `grep` | Search file contents |
107
+ | `find` | Find files by pattern |
108
+ | `ls` | List directory contents |
109
+
110
+ ## SubagentStore API
111
+
112
+ For programmatic access:
113
+
114
+ ```typescript
115
+ import { SubagentStore } from "indusagi-coding-agent";
116
+
117
+ const store = new SubagentStore({
118
+ cwd: process.cwd(),
119
+ agentDir: "~/.indusagi/agent",
120
+ });
121
+
122
+ // List all available subagents
123
+ const agents = store.list();
124
+ // [{ name: "general", description: "...", tools: [...], ... }, ...]
125
+
126
+ // Get specific subagent
127
+ const agent = store.get("explore");
128
+
129
+ // Refresh after adding new definitions
130
+ store.refresh();
131
+ ```
132
+
133
+ ## How It Works
134
+
135
+ 1. **Discovery**: SubagentStore scans `agents/` directories for `.md` files
136
+ 2. **Registration**: Available subagents are listed in the task tool description
137
+ 3. **Invocation**: When the LLM calls the task tool, a new `indusagi` subprocess is spawned
138
+ 4. **Isolation**: The subagent runs with its own context, system prompt, and tool set
139
+ 5. **Streaming**: Progress updates stream back to the primary session
140
+ 6. **Completion**: Final result is returned to the primary agent
141
+
142
+ ## Example: Code Review Agent
143
+
144
+ Create `.indusagi/agents/reviewer.md`:
145
+
146
+ ```markdown
147
+ ---
148
+ name: reviewer
149
+ description: Code review specialist. Use for reviewing pull requests, patches, or changes.
150
+ tools: read, grep, find, ls, bash
151
+ model: anthropic/claude-sonnet-4-5
152
+ ---
153
+
154
+ You are a code review specialist. Your job is to:
155
+
156
+ 1. Review code changes for correctness, security, and best practices
157
+ 2. Identify potential bugs, performance issues, or code smells
158
+ 3. Suggest improvements with specific, actionable feedback
159
+ 4. Check for proper error handling and edge cases
160
+
161
+ Output format:
162
+ - Summary of changes reviewed
163
+ - Critical issues (if any)
164
+ - Suggestions for improvement
165
+ - Overall assessment
166
+
167
+ Be thorough but concise. Focus on actionable feedback.
168
+ ```
169
+
170
+ Use it:
171
+
172
+ ```
173
+ Have the reviewer agent review the changes in src/auth/login.ts
174
+ ```
175
+
176
+ ## Example: Documentation Agent
177
+
178
+ Create `~/.indusagi/agent/agents/docs-writer.md`:
179
+
180
+ ```markdown
181
+ ---
182
+ name: docs-writer
183
+ description: Technical documentation writer. Use for creating or updating documentation.
184
+ tools: read, grep, find, ls, write
185
+ model: anthropic/claude-sonnet-4-5
186
+ ---
187
+
188
+ You are a technical documentation specialist. Write clear, comprehensive documentation.
189
+
190
+ Guidelines:
191
+ - Use proper markdown formatting
192
+ - Include code examples where helpful
193
+ - Structure with clear headings and sections
194
+ - Add usage examples and common patterns
195
+ - Document parameters, return values, and edge cases
196
+
197
+ Do not modify code files - only create or update documentation (.md files).
198
+ ```
199
+
200
+ ## Task Tool in SDK
201
+
202
+ When creating tools programmatically:
203
+
204
+ ```typescript
205
+ import { createTaskTool, SubagentStore } from "indusagi-coding-agent";
206
+
207
+ const subagentStore = new SubagentStore({ cwd: process.cwd() });
208
+
209
+ const taskTool = createTaskTool({
210
+ cwd: process.cwd(),
211
+ settingsManager,
212
+ modelRegistry,
213
+ subagentStore,
214
+ getDefaultModel: () => model,
215
+ getDefaultThinkingLevel: () => "medium",
216
+ contextFiles: [],
217
+ skills: [],
218
+ });
219
+ ```
220
+
221
+ ## Related
222
+
223
+ - [SDK Documentation](sdk.md) - Programmatic usage
224
+ - [Extensions](extensions.md) - Building custom extensions
225
+ - [Skills](skills.md) - On-demand capability packages