@oh-my-pi/pi-coding-agent 0.1.0
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/CHANGELOG.md +1629 -0
- package/README.md +1041 -0
- package/docs/compaction.md +403 -0
- package/docs/config-usage.md +113 -0
- package/docs/custom-tools.md +541 -0
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +867 -0
- package/docs/rpc.md +1040 -0
- package/docs/sdk.md +994 -0
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +240 -0
- package/docs/skills.md +290 -0
- package/docs/theme.md +670 -0
- package/docs/tree.md +197 -0
- package/docs/tui.md +341 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +124 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/question/index.ts +84 -0
- package/examples/custom-tools/subagent/README.md +172 -0
- package/examples/custom-tools/subagent/agents/planner.md +37 -0
- package/examples/custom-tools/subagent/agents/scout.md +50 -0
- package/examples/custom-tools/subagent/agents/worker.md +24 -0
- package/examples/custom-tools/subagent/agents.ts +156 -0
- package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
- package/examples/custom-tools/subagent/commands/implement.md +10 -0
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
- package/examples/custom-tools/subagent/index.ts +1002 -0
- package/examples/custom-tools/todo/index.ts +212 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +49 -0
- package/examples/hooks/confirm-destructive.ts +59 -0
- package/examples/hooks/custom-compaction.ts +116 -0
- package/examples/hooks/dirty-repo-guard.ts +52 -0
- package/examples/hooks/file-trigger.ts +41 -0
- package/examples/hooks/git-checkpoint.ts +53 -0
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +34 -0
- package/examples/hooks/protected-paths.ts +30 -0
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/snake.ts +343 -0
- package/examples/hooks/status-line.ts +40 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +44 -0
- package/examples/sdk/04-skills.ts +44 -0
- package/examples/sdk/05-tools.ts +90 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +36 -0
- package/examples/sdk/08-slash-commands.ts +42 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +95 -0
- package/examples/sdk/README.md +154 -0
- package/package.json +89 -0
- package/src/bun-imports.d.ts +16 -0
- package/src/capability/context-file.ts +40 -0
- package/src/capability/extension.ts +48 -0
- package/src/capability/hook.ts +40 -0
- package/src/capability/index.ts +616 -0
- package/src/capability/instruction.ts +37 -0
- package/src/capability/mcp.ts +52 -0
- package/src/capability/prompt.ts +35 -0
- package/src/capability/rule.ts +56 -0
- package/src/capability/settings.ts +35 -0
- package/src/capability/skill.ts +49 -0
- package/src/capability/slash-command.ts +40 -0
- package/src/capability/system-prompt.ts +35 -0
- package/src/capability/tool.ts +38 -0
- package/src/capability/types.ts +166 -0
- package/src/cli/args.ts +259 -0
- package/src/cli/file-processor.ts +121 -0
- package/src/cli/list-models.ts +104 -0
- package/src/cli/plugin-cli.ts +661 -0
- package/src/cli/session-picker.ts +41 -0
- package/src/cli/update-cli.ts +274 -0
- package/src/cli.ts +10 -0
- package/src/config.ts +391 -0
- package/src/core/agent-session.ts +2178 -0
- package/src/core/auth-storage.ts +258 -0
- package/src/core/bash-executor.ts +197 -0
- package/src/core/compaction/branch-summarization.ts +315 -0
- package/src/core/compaction/compaction.ts +664 -0
- package/src/core/compaction/index.ts +7 -0
- package/src/core/compaction/utils.ts +153 -0
- package/src/core/custom-commands/bundled/review/index.ts +156 -0
- package/src/core/custom-commands/index.ts +15 -0
- package/src/core/custom-commands/loader.ts +226 -0
- package/src/core/custom-commands/types.ts +112 -0
- package/src/core/custom-tools/index.ts +22 -0
- package/src/core/custom-tools/loader.ts +248 -0
- package/src/core/custom-tools/types.ts +185 -0
- package/src/core/custom-tools/wrapper.ts +29 -0
- package/src/core/exec.ts +139 -0
- package/src/core/export-html/index.ts +159 -0
- package/src/core/export-html/template.css +774 -0
- package/src/core/export-html/template.generated.ts +2 -0
- package/src/core/export-html/template.html +45 -0
- package/src/core/export-html/template.js +1185 -0
- package/src/core/export-html/template.macro.ts +24 -0
- package/src/core/file-mentions.ts +54 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/loader.ts +288 -0
- package/src/core/hooks/runner.ts +434 -0
- package/src/core/hooks/tool-wrapper.ts +98 -0
- package/src/core/hooks/types.ts +770 -0
- package/src/core/index.ts +53 -0
- package/src/core/logger.ts +112 -0
- package/src/core/mcp/client.ts +185 -0
- package/src/core/mcp/config.ts +248 -0
- package/src/core/mcp/index.ts +45 -0
- package/src/core/mcp/loader.ts +99 -0
- package/src/core/mcp/manager.ts +235 -0
- package/src/core/mcp/tool-bridge.ts +156 -0
- package/src/core/mcp/transports/http.ts +316 -0
- package/src/core/mcp/transports/index.ts +6 -0
- package/src/core/mcp/transports/stdio.ts +252 -0
- package/src/core/mcp/types.ts +228 -0
- package/src/core/messages.ts +211 -0
- package/src/core/model-registry.ts +334 -0
- package/src/core/model-resolver.ts +494 -0
- package/src/core/plugins/doctor.ts +67 -0
- package/src/core/plugins/index.ts +38 -0
- package/src/core/plugins/installer.ts +189 -0
- package/src/core/plugins/loader.ts +339 -0
- package/src/core/plugins/manager.ts +672 -0
- package/src/core/plugins/parser.ts +105 -0
- package/src/core/plugins/paths.ts +37 -0
- package/src/core/plugins/types.ts +190 -0
- package/src/core/sdk.ts +900 -0
- package/src/core/session-manager.ts +1837 -0
- package/src/core/settings-manager.ts +860 -0
- package/src/core/skills.ts +352 -0
- package/src/core/slash-commands.ts +132 -0
- package/src/core/system-prompt.ts +442 -0
- package/src/core/timings.ts +25 -0
- package/src/core/title-generator.ts +110 -0
- package/src/core/tools/ask.ts +193 -0
- package/src/core/tools/bash-interceptor.ts +120 -0
- package/src/core/tools/bash.ts +91 -0
- package/src/core/tools/context.ts +32 -0
- package/src/core/tools/edit-diff.ts +487 -0
- package/src/core/tools/edit.ts +140 -0
- package/src/core/tools/exa/company.ts +59 -0
- package/src/core/tools/exa/index.ts +63 -0
- package/src/core/tools/exa/linkedin.ts +59 -0
- package/src/core/tools/exa/mcp-client.ts +368 -0
- package/src/core/tools/exa/render.ts +200 -0
- package/src/core/tools/exa/researcher.ts +90 -0
- package/src/core/tools/exa/search.ts +338 -0
- package/src/core/tools/exa/types.ts +167 -0
- package/src/core/tools/exa/websets.ts +248 -0
- package/src/core/tools/find.ts +244 -0
- package/src/core/tools/grep.ts +584 -0
- package/src/core/tools/index.ts +283 -0
- package/src/core/tools/ls.ts +142 -0
- package/src/core/tools/lsp/client.ts +767 -0
- package/src/core/tools/lsp/clients/biome-client.ts +207 -0
- package/src/core/tools/lsp/clients/index.ts +49 -0
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
- package/src/core/tools/lsp/config.ts +845 -0
- package/src/core/tools/lsp/edits.ts +110 -0
- package/src/core/tools/lsp/index.ts +1364 -0
- package/src/core/tools/lsp/render.ts +560 -0
- package/src/core/tools/lsp/rust-analyzer.ts +145 -0
- package/src/core/tools/lsp/types.ts +495 -0
- package/src/core/tools/lsp/utils.ts +526 -0
- package/src/core/tools/notebook.ts +182 -0
- package/src/core/tools/output.ts +198 -0
- package/src/core/tools/path-utils.ts +61 -0
- package/src/core/tools/read.ts +507 -0
- package/src/core/tools/renderers.ts +820 -0
- package/src/core/tools/review.ts +275 -0
- package/src/core/tools/rulebook.ts +124 -0
- package/src/core/tools/task/agents.ts +158 -0
- package/src/core/tools/task/artifacts.ts +114 -0
- package/src/core/tools/task/commands.ts +157 -0
- package/src/core/tools/task/discovery.ts +217 -0
- package/src/core/tools/task/executor.ts +531 -0
- package/src/core/tools/task/index.ts +548 -0
- package/src/core/tools/task/model-resolver.ts +176 -0
- package/src/core/tools/task/parallel.ts +38 -0
- package/src/core/tools/task/render.ts +502 -0
- package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
- package/src/core/tools/task/types.ts +142 -0
- package/src/core/tools/truncate.ts +265 -0
- package/src/core/tools/web-fetch.ts +2511 -0
- package/src/core/tools/web-search/auth.ts +199 -0
- package/src/core/tools/web-search/index.ts +583 -0
- package/src/core/tools/web-search/providers/anthropic.ts +198 -0
- package/src/core/tools/web-search/providers/exa.ts +196 -0
- package/src/core/tools/web-search/providers/perplexity.ts +195 -0
- package/src/core/tools/web-search/render.ts +372 -0
- package/src/core/tools/web-search/types.ts +180 -0
- package/src/core/tools/write.ts +63 -0
- package/src/core/ttsr.ts +211 -0
- package/src/core/utils.ts +187 -0
- package/src/discovery/agents-md.ts +75 -0
- package/src/discovery/builtin.ts +647 -0
- package/src/discovery/claude.ts +623 -0
- package/src/discovery/cline.ts +104 -0
- package/src/discovery/codex.ts +571 -0
- package/src/discovery/cursor.ts +266 -0
- package/src/discovery/gemini.ts +368 -0
- package/src/discovery/github.ts +120 -0
- package/src/discovery/helpers.test.ts +127 -0
- package/src/discovery/helpers.ts +249 -0
- package/src/discovery/index.ts +84 -0
- package/src/discovery/mcp-json.ts +127 -0
- package/src/discovery/vscode.ts +99 -0
- package/src/discovery/windsurf.ts +219 -0
- package/src/index.ts +192 -0
- package/src/main.ts +507 -0
- package/src/migrations.ts +156 -0
- package/src/modes/cleanup.ts +23 -0
- package/src/modes/index.ts +48 -0
- package/src/modes/interactive/components/armin.ts +382 -0
- package/src/modes/interactive/components/assistant-message.ts +86 -0
- package/src/modes/interactive/components/bash-execution.ts +199 -0
- package/src/modes/interactive/components/bordered-loader.ts +41 -0
- package/src/modes/interactive/components/branch-summary-message.ts +42 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
- package/src/modes/interactive/components/custom-editor.ts +122 -0
- package/src/modes/interactive/components/diff.ts +147 -0
- package/src/modes/interactive/components/dynamic-border.ts +25 -0
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +296 -0
- package/src/modes/interactive/components/extensions/extension-list.ts +479 -0
- package/src/modes/interactive/components/extensions/index.ts +9 -0
- package/src/modes/interactive/components/extensions/inspector-panel.ts +313 -0
- package/src/modes/interactive/components/extensions/state-manager.ts +558 -0
- package/src/modes/interactive/components/extensions/types.ts +191 -0
- package/src/modes/interactive/components/hook-editor.ts +117 -0
- package/src/modes/interactive/components/hook-input.ts +64 -0
- package/src/modes/interactive/components/hook-message.ts +96 -0
- package/src/modes/interactive/components/hook-selector.ts +91 -0
- package/src/modes/interactive/components/model-selector.ts +560 -0
- package/src/modes/interactive/components/oauth-selector.ts +136 -0
- package/src/modes/interactive/components/plugin-settings.ts +481 -0
- package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
- package/src/modes/interactive/components/session-selector.ts +220 -0
- package/src/modes/interactive/components/settings-defs.ts +597 -0
- package/src/modes/interactive/components/settings-selector.ts +545 -0
- package/src/modes/interactive/components/show-images-selector.ts +45 -0
- package/src/modes/interactive/components/status-line/index.ts +4 -0
- package/src/modes/interactive/components/status-line/presets.ts +94 -0
- package/src/modes/interactive/components/status-line/segments.ts +350 -0
- package/src/modes/interactive/components/status-line/separators.ts +55 -0
- package/src/modes/interactive/components/status-line/types.ts +81 -0
- package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
- package/src/modes/interactive/components/status-line.ts +384 -0
- package/src/modes/interactive/components/theme-selector.ts +62 -0
- package/src/modes/interactive/components/thinking-selector.ts +64 -0
- package/src/modes/interactive/components/tool-execution.ts +946 -0
- package/src/modes/interactive/components/tree-selector.ts +877 -0
- package/src/modes/interactive/components/ttsr-notification.ts +82 -0
- package/src/modes/interactive/components/user-message-selector.ts +159 -0
- package/src/modes/interactive/components/user-message.ts +18 -0
- package/src/modes/interactive/components/visual-truncate.ts +50 -0
- package/src/modes/interactive/components/welcome.ts +228 -0
- package/src/modes/interactive/interactive-mode.ts +2669 -0
- package/src/modes/interactive/theme/dark.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
- package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
- package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
- package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
- package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
- package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
- package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
- package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
- package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
- package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
- package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
- package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
- package/src/modes/interactive/theme/defaults/index.ts +67 -0
- package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
- package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
- package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
- package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
- package/src/modes/interactive/theme/defaults/light-github.json +114 -0
- package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
- package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
- package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
- package/src/modes/interactive/theme/defaults/light-one.json +105 -0
- package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
- package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
- package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
- package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
- package/src/modes/interactive/theme/light.json +99 -0
- package/src/modes/interactive/theme/theme-schema.json +424 -0
- package/src/modes/interactive/theme/theme.ts +2211 -0
- package/src/modes/print-mode.ts +163 -0
- package/src/modes/rpc/rpc-client.ts +527 -0
- package/src/modes/rpc/rpc-mode.ts +494 -0
- package/src/modes/rpc/rpc-types.ts +203 -0
- package/src/prompts/architect-plan.md +10 -0
- package/src/prompts/branch-summary-preamble.md +3 -0
- package/src/prompts/branch-summary.md +28 -0
- package/src/prompts/browser.md +71 -0
- package/src/prompts/compaction-summary.md +34 -0
- package/src/prompts/compaction-turn-prefix.md +16 -0
- package/src/prompts/compaction-update-summary.md +41 -0
- package/src/prompts/explore.md +82 -0
- package/src/prompts/implement-with-critic.md +11 -0
- package/src/prompts/implement.md +11 -0
- package/src/prompts/init.md +30 -0
- package/src/prompts/plan.md +54 -0
- package/src/prompts/reviewer.md +81 -0
- package/src/prompts/summarization-system.md +3 -0
- package/src/prompts/system-prompt.md +27 -0
- package/src/prompts/task.md +56 -0
- package/src/prompts/title-system.md +8 -0
- package/src/prompts/tools/ask.md +24 -0
- package/src/prompts/tools/bash.md +23 -0
- package/src/prompts/tools/edit.md +9 -0
- package/src/prompts/tools/find.md +6 -0
- package/src/prompts/tools/grep.md +12 -0
- package/src/prompts/tools/lsp.md +14 -0
- package/src/prompts/tools/output.md +23 -0
- package/src/prompts/tools/read.md +25 -0
- package/src/prompts/tools/web-fetch.md +8 -0
- package/src/prompts/tools/web-search.md +10 -0
- package/src/prompts/tools/write.md +10 -0
- package/src/utils/changelog.ts +99 -0
- package/src/utils/clipboard.ts +265 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/mime.ts +30 -0
- package/src/utils/shell-snapshot.ts +218 -0
- package/src/utils/shell.ts +364 -0
- package/src/utils/tools-manager.ts +265 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for compaction and branch summarization.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
6
|
+
import type { Message } from "@oh-my-pi/pi-ai";
|
|
7
|
+
import summarizationSystemPrompt from "../../prompts/summarization-system.md" with { type: "text" };
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// File Operation Tracking
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
export interface FileOperations {
|
|
14
|
+
read: Set<string>;
|
|
15
|
+
written: Set<string>;
|
|
16
|
+
edited: Set<string>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createFileOps(): FileOperations {
|
|
20
|
+
return {
|
|
21
|
+
read: new Set(),
|
|
22
|
+
written: new Set(),
|
|
23
|
+
edited: new Set(),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Extract file operations from tool calls in an assistant message.
|
|
29
|
+
*/
|
|
30
|
+
export function extractFileOpsFromMessage(message: AgentMessage, fileOps: FileOperations): void {
|
|
31
|
+
if (message.role !== "assistant") return;
|
|
32
|
+
if (!("content" in message) || !Array.isArray(message.content)) return;
|
|
33
|
+
|
|
34
|
+
for (const block of message.content) {
|
|
35
|
+
if (typeof block !== "object" || block === null) continue;
|
|
36
|
+
if (!("type" in block) || block.type !== "toolCall") continue;
|
|
37
|
+
if (!("arguments" in block) || !("name" in block)) continue;
|
|
38
|
+
|
|
39
|
+
const args = block.arguments as Record<string, unknown> | undefined;
|
|
40
|
+
if (!args) continue;
|
|
41
|
+
|
|
42
|
+
const path = typeof args.path === "string" ? args.path : undefined;
|
|
43
|
+
if (!path) continue;
|
|
44
|
+
|
|
45
|
+
switch (block.name) {
|
|
46
|
+
case "read":
|
|
47
|
+
fileOps.read.add(path);
|
|
48
|
+
break;
|
|
49
|
+
case "write":
|
|
50
|
+
fileOps.written.add(path);
|
|
51
|
+
break;
|
|
52
|
+
case "edit":
|
|
53
|
+
fileOps.edited.add(path);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Compute final file lists from file operations.
|
|
61
|
+
* Returns readFiles (files only read, not modified) and modifiedFiles.
|
|
62
|
+
*/
|
|
63
|
+
export function computeFileLists(fileOps: FileOperations): { readFiles: string[]; modifiedFiles: string[] } {
|
|
64
|
+
const modified = new Set([...fileOps.edited, ...fileOps.written]);
|
|
65
|
+
const readOnly = [...fileOps.read].filter((f) => !modified.has(f)).sort();
|
|
66
|
+
const modifiedFiles = [...modified].sort();
|
|
67
|
+
return { readFiles: readOnly, modifiedFiles };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Format file operations as XML tags for summary.
|
|
72
|
+
*/
|
|
73
|
+
export function formatFileOperations(readFiles: string[], modifiedFiles: string[]): string {
|
|
74
|
+
const sections: string[] = [];
|
|
75
|
+
if (readFiles.length > 0) {
|
|
76
|
+
sections.push(`<read-files>\n${readFiles.join("\n")}\n</read-files>`);
|
|
77
|
+
}
|
|
78
|
+
if (modifiedFiles.length > 0) {
|
|
79
|
+
sections.push(`<modified-files>\n${modifiedFiles.join("\n")}\n</modified-files>`);
|
|
80
|
+
}
|
|
81
|
+
if (sections.length === 0) return "";
|
|
82
|
+
return `\n\n${sections.join("\n\n")}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// Message Serialization
|
|
87
|
+
// ============================================================================
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Serialize LLM messages to text for summarization.
|
|
91
|
+
* This prevents the model from treating it as a conversation to continue.
|
|
92
|
+
* Call convertToLlm() first to handle custom message types.
|
|
93
|
+
*/
|
|
94
|
+
export function serializeConversation(messages: Message[]): string {
|
|
95
|
+
const parts: string[] = [];
|
|
96
|
+
|
|
97
|
+
for (const msg of messages) {
|
|
98
|
+
if (msg.role === "user") {
|
|
99
|
+
const content =
|
|
100
|
+
typeof msg.content === "string"
|
|
101
|
+
? msg.content
|
|
102
|
+
: msg.content
|
|
103
|
+
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
104
|
+
.map((c) => c.text)
|
|
105
|
+
.join("");
|
|
106
|
+
if (content) parts.push(`[User]: ${content}`);
|
|
107
|
+
} else if (msg.role === "assistant") {
|
|
108
|
+
const textParts: string[] = [];
|
|
109
|
+
const thinkingParts: string[] = [];
|
|
110
|
+
const toolCalls: string[] = [];
|
|
111
|
+
|
|
112
|
+
for (const block of msg.content) {
|
|
113
|
+
if (block.type === "text") {
|
|
114
|
+
textParts.push(block.text);
|
|
115
|
+
} else if (block.type === "thinking") {
|
|
116
|
+
thinkingParts.push(block.thinking);
|
|
117
|
+
} else if (block.type === "toolCall") {
|
|
118
|
+
const args = block.arguments as Record<string, unknown>;
|
|
119
|
+
const argsStr = Object.entries(args)
|
|
120
|
+
.map(([k, v]) => `${k}=${JSON.stringify(v)}`)
|
|
121
|
+
.join(", ");
|
|
122
|
+
toolCalls.push(`${block.name}(${argsStr})`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (thinkingParts.length > 0) {
|
|
127
|
+
parts.push(`[Assistant thinking]: ${thinkingParts.join("\n")}`);
|
|
128
|
+
}
|
|
129
|
+
if (textParts.length > 0) {
|
|
130
|
+
parts.push(`[Assistant]: ${textParts.join("\n")}`);
|
|
131
|
+
}
|
|
132
|
+
if (toolCalls.length > 0) {
|
|
133
|
+
parts.push(`[Assistant tool calls]: ${toolCalls.join("; ")}`);
|
|
134
|
+
}
|
|
135
|
+
} else if (msg.role === "toolResult") {
|
|
136
|
+
const content = msg.content
|
|
137
|
+
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
138
|
+
.map((c) => c.text)
|
|
139
|
+
.join("");
|
|
140
|
+
if (content) {
|
|
141
|
+
parts.push(`[Tool result]: ${content}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return parts.join("\n\n");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ============================================================================
|
|
150
|
+
// Summarization System Prompt
|
|
151
|
+
// ============================================================================
|
|
152
|
+
|
|
153
|
+
export const SUMMARIZATION_SYSTEM_PROMPT = summarizationSystemPrompt;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /review command - Interactive code review launcher
|
|
3
|
+
*
|
|
4
|
+
* Provides a menu to select review mode:
|
|
5
|
+
* 1. Review against a base branch (PR style)
|
|
6
|
+
* 2. Review uncommitted changes
|
|
7
|
+
* 3. Review a specific commit
|
|
8
|
+
* 4. Custom review instructions
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { HookCommandContext } from "../../../hooks/types";
|
|
12
|
+
import type { CustomCommand, CustomCommandAPI } from "../../types";
|
|
13
|
+
|
|
14
|
+
export function createReviewCommand(api: CustomCommandAPI): CustomCommand {
|
|
15
|
+
return {
|
|
16
|
+
name: "review",
|
|
17
|
+
description: "Launch interactive code review",
|
|
18
|
+
|
|
19
|
+
async execute(_args: string[], ctx: HookCommandContext): Promise<string | undefined> {
|
|
20
|
+
if (!ctx.hasUI) {
|
|
21
|
+
return "Use the Task tool to run the 'reviewer' agent to review recent code changes.";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Main menu
|
|
25
|
+
const mode = await ctx.ui.select("Review Mode", [
|
|
26
|
+
"1. Review against a base branch (PR Style)",
|
|
27
|
+
"2. Review uncommitted changes",
|
|
28
|
+
"3. Review a specific commit",
|
|
29
|
+
"4. Custom review instructions",
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
if (!mode) return undefined;
|
|
33
|
+
|
|
34
|
+
const modeNum = parseInt(mode[0], 10);
|
|
35
|
+
|
|
36
|
+
switch (modeNum) {
|
|
37
|
+
case 1: {
|
|
38
|
+
// PR-style review against base branch
|
|
39
|
+
const branches = await getGitBranches(api);
|
|
40
|
+
if (branches.length === 0) {
|
|
41
|
+
ctx.ui.notify("No git branches found", "error");
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const baseBranch = await ctx.ui.select("Select base branch to compare against", branches);
|
|
46
|
+
if (!baseBranch) return undefined;
|
|
47
|
+
|
|
48
|
+
const currentBranch = await getCurrentBranch(api);
|
|
49
|
+
return `Use the Task tool to run the "reviewer" agent with this task:
|
|
50
|
+
|
|
51
|
+
Review the changes between "${baseBranch}" and "${currentBranch}".
|
|
52
|
+
|
|
53
|
+
Run \`git diff ${baseBranch}...${currentBranch}\` to see the changes, then analyze the modified files.`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
case 2: {
|
|
57
|
+
// Uncommitted changes
|
|
58
|
+
const status = await getGitStatus(api);
|
|
59
|
+
if (!status.trim()) {
|
|
60
|
+
ctx.ui.notify("No uncommitted changes found", "warning");
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return `Use the Task tool to run the "reviewer" agent with this task:
|
|
65
|
+
|
|
66
|
+
Review all uncommitted changes in the working directory.
|
|
67
|
+
|
|
68
|
+
Run \`git diff\` for unstaged changes and \`git diff --cached\` for staged changes.`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
case 3: {
|
|
72
|
+
// Specific commit
|
|
73
|
+
const commits = await getRecentCommits(api, 20);
|
|
74
|
+
if (commits.length === 0) {
|
|
75
|
+
ctx.ui.notify("No commits found", "error");
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const selected = await ctx.ui.select("Select commit to review", commits);
|
|
80
|
+
if (!selected) return undefined;
|
|
81
|
+
|
|
82
|
+
// Extract commit hash from selection (format: "abc1234 message")
|
|
83
|
+
const hash = selected.split(" ")[0];
|
|
84
|
+
|
|
85
|
+
return `Use the Task tool to run the "reviewer" agent with this task:
|
|
86
|
+
|
|
87
|
+
Review commit ${hash}.
|
|
88
|
+
|
|
89
|
+
Run \`git show ${hash}\` to see the changes introduced by this commit.`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
case 4: {
|
|
93
|
+
// Custom instructions
|
|
94
|
+
const instructions = await ctx.ui.editor(
|
|
95
|
+
"Enter custom review instructions",
|
|
96
|
+
"Review the following:\n\n",
|
|
97
|
+
);
|
|
98
|
+
if (!instructions?.trim()) return undefined;
|
|
99
|
+
|
|
100
|
+
return `Use the Task tool to run the "reviewer" agent with this task:
|
|
101
|
+
|
|
102
|
+
${instructions}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
default:
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function getGitBranches(api: CustomCommandAPI): Promise<string[]> {
|
|
113
|
+
try {
|
|
114
|
+
const result = await api.exec("git", ["branch", "-a", "--format=%(refname:short)"]);
|
|
115
|
+
if (result.code !== 0) return [];
|
|
116
|
+
return result.stdout
|
|
117
|
+
.split("\n")
|
|
118
|
+
.map((b) => b.trim())
|
|
119
|
+
.filter(Boolean);
|
|
120
|
+
} catch {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function getCurrentBranch(api: CustomCommandAPI): Promise<string> {
|
|
126
|
+
try {
|
|
127
|
+
const result = await api.exec("git", ["branch", "--show-current"]);
|
|
128
|
+
return result.stdout.trim() || "HEAD";
|
|
129
|
+
} catch {
|
|
130
|
+
return "HEAD";
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function getGitStatus(api: CustomCommandAPI): Promise<string> {
|
|
135
|
+
try {
|
|
136
|
+
const result = await api.exec("git", ["status", "--porcelain"]);
|
|
137
|
+
return result.stdout;
|
|
138
|
+
} catch {
|
|
139
|
+
return "";
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function getRecentCommits(api: CustomCommandAPI, count: number): Promise<string[]> {
|
|
144
|
+
try {
|
|
145
|
+
const result = await api.exec("git", ["log", `-${count}`, "--oneline", "--no-decorate"]);
|
|
146
|
+
if (result.code !== 0) return [];
|
|
147
|
+
return result.stdout
|
|
148
|
+
.split("\n")
|
|
149
|
+
.map((c) => c.trim())
|
|
150
|
+
.filter(Boolean);
|
|
151
|
+
} catch {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export default createReviewCommand;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export {
|
|
2
|
+
type DiscoverCustomCommandsOptions,
|
|
3
|
+
type DiscoverCustomCommandsResult,
|
|
4
|
+
discoverCustomCommands,
|
|
5
|
+
type LoadCustomCommandsOptions,
|
|
6
|
+
loadCustomCommands,
|
|
7
|
+
} from "./loader";
|
|
8
|
+
export type {
|
|
9
|
+
CustomCommand,
|
|
10
|
+
CustomCommandAPI,
|
|
11
|
+
CustomCommandFactory,
|
|
12
|
+
CustomCommandSource,
|
|
13
|
+
CustomCommandsLoadResult,
|
|
14
|
+
LoadedCustomCommand,
|
|
15
|
+
} from "./types";
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom command loader - loads TypeScript command modules using native Bun import.
|
|
3
|
+
*
|
|
4
|
+
* Dependencies (@sinclair/typebox and pi-coding-agent) are injected via the CustomCommandAPI
|
|
5
|
+
* to avoid import resolution issues with custom commands loaded from user directories.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { type Dirent, existsSync, readdirSync } from "node:fs";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
import * as typebox from "@sinclair/typebox";
|
|
11
|
+
import { getAgentDir, getConfigDirs } from "../../config";
|
|
12
|
+
import * as piCodingAgent from "../../index";
|
|
13
|
+
import { execCommand } from "../exec";
|
|
14
|
+
import { createReviewCommand } from "./bundled/review";
|
|
15
|
+
import type {
|
|
16
|
+
CustomCommand,
|
|
17
|
+
CustomCommandAPI,
|
|
18
|
+
CustomCommandFactory,
|
|
19
|
+
CustomCommandSource,
|
|
20
|
+
CustomCommandsLoadResult,
|
|
21
|
+
LoadedCustomCommand,
|
|
22
|
+
} from "./types";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Load a single command module using native Bun import.
|
|
26
|
+
*/
|
|
27
|
+
async function loadCommandModule(
|
|
28
|
+
commandPath: string,
|
|
29
|
+
_cwd: string,
|
|
30
|
+
sharedApi: CustomCommandAPI,
|
|
31
|
+
): Promise<{ commands: CustomCommand[] | null; error: string | null }> {
|
|
32
|
+
try {
|
|
33
|
+
const module = await import(commandPath);
|
|
34
|
+
const factory = (module.default ?? module) as CustomCommandFactory;
|
|
35
|
+
|
|
36
|
+
if (typeof factory !== "function") {
|
|
37
|
+
return { commands: null, error: "Command must export a default function" };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const result = await factory(sharedApi);
|
|
41
|
+
const commands = Array.isArray(result) ? result : [result];
|
|
42
|
+
|
|
43
|
+
// Validate commands
|
|
44
|
+
for (const cmd of commands) {
|
|
45
|
+
if (!cmd.name || typeof cmd.name !== "string") {
|
|
46
|
+
return { commands: null, error: "Command must have a name" };
|
|
47
|
+
}
|
|
48
|
+
if (!cmd.description || typeof cmd.description !== "string") {
|
|
49
|
+
return { commands: null, error: `Command "${cmd.name}" must have a description` };
|
|
50
|
+
}
|
|
51
|
+
if (typeof cmd.execute !== "function") {
|
|
52
|
+
return { commands: null, error: `Command "${cmd.name}" must have an execute function` };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { commands, error: null };
|
|
57
|
+
} catch (err) {
|
|
58
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
59
|
+
return { commands: null, error: `Failed to load command: ${message}` };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface DiscoverCustomCommandsOptions {
|
|
64
|
+
/** Current working directory. Default: process.cwd() */
|
|
65
|
+
cwd?: string;
|
|
66
|
+
/** Agent config directory. Default: from getAgentDir() */
|
|
67
|
+
agentDir?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface DiscoverCustomCommandsResult {
|
|
71
|
+
/** Paths to command modules */
|
|
72
|
+
paths: Array<{ path: string; source: CustomCommandSource }>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Discover custom command modules (TypeScript slash commands).
|
|
77
|
+
* Markdown slash commands are handled by core/slash-commands.ts.
|
|
78
|
+
*/
|
|
79
|
+
export function discoverCustomCommands(options: DiscoverCustomCommandsOptions = {}): DiscoverCustomCommandsResult {
|
|
80
|
+
const cwd = options.cwd ?? process.cwd();
|
|
81
|
+
const agentDir = options.agentDir ?? getAgentDir();
|
|
82
|
+
const paths: Array<{ path: string; source: CustomCommandSource }> = [];
|
|
83
|
+
const seen = new Set<string>();
|
|
84
|
+
|
|
85
|
+
const addPath = (commandPath: string, source: CustomCommandSource): void => {
|
|
86
|
+
const resolved = path.resolve(commandPath);
|
|
87
|
+
if (seen.has(resolved)) return;
|
|
88
|
+
seen.add(resolved);
|
|
89
|
+
paths.push({ path: resolved, source });
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const commandDirs: Array<{ path: string; source: CustomCommandSource }> = [];
|
|
93
|
+
if (agentDir) {
|
|
94
|
+
const userCommandsDir = path.join(agentDir, "commands");
|
|
95
|
+
if (existsSync(userCommandsDir)) {
|
|
96
|
+
commandDirs.push({ path: userCommandsDir, source: "user" });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
for (const entry of getConfigDirs("commands", { cwd, existingOnly: true })) {
|
|
101
|
+
const source = entry.level === "user" ? "user" : "project";
|
|
102
|
+
if (!commandDirs.some((d) => d.path === entry.path)) {
|
|
103
|
+
commandDirs.push({ path: entry.path, source });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const indexCandidates = ["index.ts", "index.js", "index.mjs", "index.cjs"];
|
|
108
|
+
for (const { path: commandsDir, source } of commandDirs) {
|
|
109
|
+
let entries: Dirent[];
|
|
110
|
+
try {
|
|
111
|
+
entries = readdirSync(commandsDir, { withFileTypes: true });
|
|
112
|
+
} catch {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
117
|
+
const commandDir = path.join(commandsDir, entry.name);
|
|
118
|
+
|
|
119
|
+
for (const filename of indexCandidates) {
|
|
120
|
+
const candidate = path.join(commandDir, filename);
|
|
121
|
+
if (existsSync(candidate)) {
|
|
122
|
+
addPath(candidate, source);
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return { paths };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface LoadCustomCommandsOptions {
|
|
133
|
+
/** Current working directory. Default: process.cwd() */
|
|
134
|
+
cwd?: string;
|
|
135
|
+
/** Agent config directory. Default: from getAgentDir() */
|
|
136
|
+
agentDir?: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Load bundled commands (shipped with pi-coding-agent).
|
|
141
|
+
*/
|
|
142
|
+
function loadBundledCommands(sharedApi: CustomCommandAPI): LoadedCustomCommand[] {
|
|
143
|
+
const bundled: LoadedCustomCommand[] = [];
|
|
144
|
+
|
|
145
|
+
// Add bundled commands here
|
|
146
|
+
const reviewCommand = createReviewCommand(sharedApi);
|
|
147
|
+
bundled.push({
|
|
148
|
+
path: "bundled:review",
|
|
149
|
+
resolvedPath: "bundled:review",
|
|
150
|
+
command: reviewCommand,
|
|
151
|
+
source: "bundled",
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return bundled;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Discover and load custom commands from standard locations.
|
|
159
|
+
*/
|
|
160
|
+
export async function loadCustomCommands(options: LoadCustomCommandsOptions = {}): Promise<CustomCommandsLoadResult> {
|
|
161
|
+
const cwd = options.cwd ?? process.cwd();
|
|
162
|
+
const agentDir = options.agentDir ?? getAgentDir();
|
|
163
|
+
|
|
164
|
+
const { paths } = discoverCustomCommands({ cwd, agentDir });
|
|
165
|
+
|
|
166
|
+
const commands: LoadedCustomCommand[] = [];
|
|
167
|
+
const errors: Array<{ path: string; error: string }> = [];
|
|
168
|
+
const seenNames = new Set<string>();
|
|
169
|
+
|
|
170
|
+
// Shared API object - all commands get the same instance
|
|
171
|
+
const sharedApi: CustomCommandAPI = {
|
|
172
|
+
cwd,
|
|
173
|
+
exec: (command: string, args: string[], execOptions) =>
|
|
174
|
+
execCommand(command, args, execOptions?.cwd ?? cwd, execOptions),
|
|
175
|
+
typebox,
|
|
176
|
+
pi: piCodingAgent,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// 1. Load bundled commands first (lowest priority - can be overridden)
|
|
180
|
+
for (const loaded of loadBundledCommands(sharedApi)) {
|
|
181
|
+
seenNames.add(loaded.command.name);
|
|
182
|
+
commands.push(loaded);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 2. Load user/project commands (can override bundled)
|
|
186
|
+
for (const { path: commandPath, source } of paths) {
|
|
187
|
+
const { commands: loadedCommands, error } = await loadCommandModule(commandPath, cwd, sharedApi);
|
|
188
|
+
|
|
189
|
+
if (error) {
|
|
190
|
+
errors.push({ path: commandPath, error });
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (loadedCommands) {
|
|
195
|
+
for (const command of loadedCommands) {
|
|
196
|
+
// Allow overriding bundled commands, but not user/project conflicts
|
|
197
|
+
const existingIdx = commands.findIndex((c) => c.command.name === command.name);
|
|
198
|
+
if (existingIdx !== -1) {
|
|
199
|
+
const existing = commands[existingIdx];
|
|
200
|
+
if (existing.source === "bundled") {
|
|
201
|
+
// Override bundled command
|
|
202
|
+
commands.splice(existingIdx, 1);
|
|
203
|
+
seenNames.delete(command.name);
|
|
204
|
+
} else {
|
|
205
|
+
// Conflict between user/project commands
|
|
206
|
+
errors.push({
|
|
207
|
+
path: commandPath,
|
|
208
|
+
error: `Command name "${command.name}" conflicts with existing command`,
|
|
209
|
+
});
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
seenNames.add(command.name);
|
|
215
|
+
commands.push({
|
|
216
|
+
path: commandPath,
|
|
217
|
+
resolvedPath: path.resolve(commandPath),
|
|
218
|
+
command,
|
|
219
|
+
source,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return { commands, errors };
|
|
226
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom command types.
|
|
3
|
+
*
|
|
4
|
+
* Custom commands are TypeScript modules that define executable slash commands.
|
|
5
|
+
* Unlike markdown commands which expand to prompts, custom commands can execute
|
|
6
|
+
* arbitrary logic with full access to the hook context.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ExecOptions, ExecResult, HookCommandContext } from "../hooks/types";
|
|
10
|
+
|
|
11
|
+
// Re-export for custom commands to use
|
|
12
|
+
export type { ExecOptions, ExecResult, HookCommandContext };
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* API passed to custom command factory.
|
|
16
|
+
* Similar to HookAPI but focused on command needs.
|
|
17
|
+
*/
|
|
18
|
+
export interface CustomCommandAPI {
|
|
19
|
+
/** Current working directory */
|
|
20
|
+
cwd: string;
|
|
21
|
+
/** Execute a shell command */
|
|
22
|
+
exec(command: string, args: string[], options?: ExecOptions): Promise<ExecResult>;
|
|
23
|
+
/** Injected @sinclair/typebox module */
|
|
24
|
+
typebox: typeof import("@sinclair/typebox");
|
|
25
|
+
/** Injected pi-coding-agent exports */
|
|
26
|
+
pi: typeof import("../../index.js");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Custom command definition.
|
|
31
|
+
*
|
|
32
|
+
* Commands can either:
|
|
33
|
+
* - Return a string to be sent to the LLM as a prompt
|
|
34
|
+
* - Return void/undefined to do nothing (fire-and-forget)
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* const factory: CustomCommandFactory = (pi) => ({
|
|
39
|
+
* name: "deploy",
|
|
40
|
+
* description: "Deploy current branch to staging",
|
|
41
|
+
* async execute(args, ctx) {
|
|
42
|
+
* const env = args[0] || "staging";
|
|
43
|
+
* const confirmed = await ctx.ui.confirm("Deploy", `Deploy to ${env}?`);
|
|
44
|
+
* if (!confirmed) return;
|
|
45
|
+
*
|
|
46
|
+
* const result = await pi.exec("./deploy.sh", [env]);
|
|
47
|
+
* if (result.exitCode !== 0) {
|
|
48
|
+
* ctx.ui.notify(`Deploy failed: ${result.stderr}`, "error");
|
|
49
|
+
* return;
|
|
50
|
+
* }
|
|
51
|
+
*
|
|
52
|
+
* ctx.ui.notify("Deploy successful!", "info");
|
|
53
|
+
* // No return = no prompt sent to LLM
|
|
54
|
+
* }
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* // Return a prompt to send to the LLM
|
|
61
|
+
* const factory: CustomCommandFactory = (pi) => ({
|
|
62
|
+
* name: "git:status",
|
|
63
|
+
* description: "Show git status and suggest actions",
|
|
64
|
+
* async execute(args, ctx) {
|
|
65
|
+
* const result = await pi.exec("git", ["status", "--porcelain"]);
|
|
66
|
+
* return `Here's the git status:\n\`\`\`\n${result.stdout}\`\`\`\nSuggest what to do next.`;
|
|
67
|
+
* }
|
|
68
|
+
* });
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export interface CustomCommand {
|
|
72
|
+
/** Command name (can include namespace like "git:commit") */
|
|
73
|
+
name: string;
|
|
74
|
+
/** Description shown in command autocomplete */
|
|
75
|
+
description: string;
|
|
76
|
+
/**
|
|
77
|
+
* Execute the command.
|
|
78
|
+
* @param args - Parsed command arguments
|
|
79
|
+
* @param ctx - Command context with UI and session control
|
|
80
|
+
* @returns String to send as prompt, or void for fire-and-forget
|
|
81
|
+
*/
|
|
82
|
+
execute(args: string[], ctx: HookCommandContext): Promise<string | undefined> | string | undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Factory function that creates custom command(s).
|
|
87
|
+
* Can return a single command or an array of commands.
|
|
88
|
+
*/
|
|
89
|
+
export type CustomCommandFactory = (
|
|
90
|
+
api: CustomCommandAPI,
|
|
91
|
+
) => CustomCommand | CustomCommand[] | Promise<CustomCommand | CustomCommand[]>;
|
|
92
|
+
|
|
93
|
+
/** Source of a loaded custom command */
|
|
94
|
+
export type CustomCommandSource = "bundled" | "user" | "project";
|
|
95
|
+
|
|
96
|
+
/** Loaded custom command with metadata */
|
|
97
|
+
export interface LoadedCustomCommand {
|
|
98
|
+
/** Original path to the command module */
|
|
99
|
+
path: string;
|
|
100
|
+
/** Resolved absolute path */
|
|
101
|
+
resolvedPath: string;
|
|
102
|
+
/** The command definition */
|
|
103
|
+
command: CustomCommand;
|
|
104
|
+
/** Where the command was loaded from */
|
|
105
|
+
source: CustomCommandSource;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Result from loading custom commands */
|
|
109
|
+
export interface CustomCommandsLoadResult {
|
|
110
|
+
commands: LoadedCustomCommand[];
|
|
111
|
+
errors: Array<{ path: string; error: string }>;
|
|
112
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom tools module.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { discoverAndLoadCustomTools, loadCustomTools } from "./loader";
|
|
6
|
+
export type {
|
|
7
|
+
AgentToolResult,
|
|
8
|
+
AgentToolUpdateCallback,
|
|
9
|
+
CustomTool,
|
|
10
|
+
CustomToolAPI,
|
|
11
|
+
CustomToolContext,
|
|
12
|
+
CustomToolFactory,
|
|
13
|
+
CustomToolResult,
|
|
14
|
+
CustomToolSessionEvent,
|
|
15
|
+
CustomToolsLoadResult,
|
|
16
|
+
CustomToolUIContext,
|
|
17
|
+
ExecResult,
|
|
18
|
+
LoadedCustomTool,
|
|
19
|
+
RenderResultOptions,
|
|
20
|
+
ToolLoadError,
|
|
21
|
+
} from "./types";
|
|
22
|
+
export { wrapCustomTool, wrapCustomTools } from "./wrapper";
|