@oh-my-pi/pi-coding-agent 13.2.0 → 13.2.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.
- package/CHANGELOG.md +26 -0
- package/package.json +7 -7
- package/scripts/format-prompts.ts +33 -14
- package/src/capability/index.ts +1 -2
- package/src/cli/args.ts +1 -2
- package/src/cli/config-cli.ts +1 -1
- package/src/cli/file-processor.ts +1 -2
- package/src/cli/grep-cli.ts +1 -1
- package/src/cli/jupyter-cli.ts +1 -1
- package/src/cli/plugin-cli.ts +1 -1
- package/src/cli/setup-cli.ts +1 -1
- package/src/cli/shell-cli.ts +1 -1
- package/src/cli/ssh-cli.ts +1 -1
- package/src/cli/stats-cli.ts +1 -2
- package/src/cli/update-cli.ts +1 -2
- package/src/cli/web-search-cli.ts +1 -1
- package/src/cli.ts +1 -1
- package/src/commands/launch.ts +2 -1
- package/src/commit/agentic/agent.ts +2 -1
- package/src/commit/agentic/index.ts +1 -2
- package/src/commit/agentic/prompts/system.md +3 -3
- package/src/commit/agentic/tools/propose-changelog.ts +30 -19
- package/src/commit/changelog/generate.ts +16 -6
- package/src/commit/changelog/index.ts +2 -1
- package/src/commit/pipeline.ts +1 -2
- package/src/commit/prompts/reduce-system.md +1 -1
- package/src/commit/types.ts +10 -1
- package/src/config/keybindings.ts +1 -2
- package/src/config/model-registry.ts +1 -1
- package/src/config/prompt-templates.ts +14 -2
- package/src/config/settings.ts +9 -2
- package/src/config.ts +1 -2
- package/src/debug/index.ts +1 -1
- package/src/debug/report-bundle.ts +1 -2
- package/src/debug/system-info.ts +1 -2
- package/src/discovery/agents.ts +2 -2
- package/src/discovery/builtin.ts +8 -9
- package/src/discovery/claude-plugins.ts +2 -2
- package/src/discovery/claude.ts +7 -7
- package/src/discovery/codex.ts +3 -3
- package/src/discovery/cursor.ts +5 -4
- package/src/discovery/gemini.ts +5 -5
- package/src/discovery/helpers.ts +47 -69
- package/src/discovery/mcp-json.ts +3 -3
- package/src/discovery/opencode.ts +7 -8
- package/src/discovery/ssh.ts +3 -3
- package/src/discovery/vscode.ts +3 -2
- package/src/discovery/windsurf.ts +3 -2
- package/src/exa/company.ts +1 -1
- package/src/exa/factory.ts +1 -6
- package/src/exa/linkedin.ts +1 -1
- package/src/exa/mcp-client.ts +19 -8
- package/src/exa/search.ts +2 -2
- package/src/exa/types.ts +3 -3
- package/src/exec/bash-executor.ts +2 -1
- package/src/exec/non-interactive-env.ts +43 -0
- package/src/export/custom-share.ts +1 -1
- package/src/export/html/index.ts +1 -2
- package/src/extensibility/custom-commands/loader.ts +1 -2
- package/src/extensibility/plugins/installer.ts +1 -2
- package/src/extensibility/plugins/loader.ts +1 -2
- package/src/extensibility/plugins/manager.ts +3 -2
- package/src/extensibility/skills.ts +59 -115
- package/src/index.ts +1 -3
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/ipy/executor.ts +1 -2
- package/src/ipy/gateway-coordinator.ts +1 -2
- package/src/ipy/modules.ts +1 -1
- package/src/ipy/runtime.ts +1 -3
- package/src/main.ts +1 -2
- package/src/mcp/config.ts +1 -1
- package/src/mcp/transports/stdio.ts +1 -2
- package/src/memories/index.ts +1 -2
- package/src/modes/components/extensions/extension-dashboard.ts +1 -1
- package/src/modes/components/extensions/inspector-panel.ts +8 -2
- package/src/modes/components/footer.ts +1 -2
- package/src/modes/components/status-line/segments.ts +1 -2
- package/src/modes/components/tool-execution.ts +3 -10
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/command-controller.ts +1 -2
- package/src/modes/controllers/mcp-command-controller.ts +1 -1
- package/src/modes/controllers/selector-controller.ts +1 -1
- package/src/modes/controllers/ssh-command-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +2 -3
- package/src/modes/shared.ts +1 -2
- package/src/modes/theme/theme.ts +1 -2
- package/src/patch/index.ts +1 -25
- package/src/prompts/agents/designer.md +7 -10
- package/src/prompts/agents/explore.md +15 -23
- package/src/prompts/agents/init.md +23 -23
- package/src/prompts/agents/plan.md +14 -77
- package/src/prompts/agents/reviewer.md +6 -5
- package/src/prompts/agents/task.md +13 -11
- package/src/prompts/compaction/branch-summary.md +3 -3
- package/src/prompts/compaction/compaction-short-summary.md +7 -7
- package/src/prompts/compaction/compaction-summary-context.md +1 -1
- package/src/prompts/compaction/compaction-summary.md +5 -5
- package/src/prompts/compaction/compaction-turn-prefix.md +3 -3
- package/src/prompts/compaction/compaction-update-summary.md +11 -11
- package/src/prompts/memories/consolidation.md +5 -5
- package/src/prompts/memories/read-path.md +6 -6
- package/src/prompts/memories/stage_one_input.md +1 -1
- package/src/prompts/memories/stage_one_system.md +5 -5
- package/src/prompts/review-request.md +4 -4
- package/src/prompts/system/agent-creation-architect.md +17 -17
- package/src/prompts/system/agent-creation-user.md +2 -2
- package/src/prompts/system/custom-system-prompt.md +4 -4
- package/src/prompts/system/plan-mode-active.md +20 -20
- package/src/prompts/system/plan-mode-approved.md +7 -7
- package/src/prompts/system/plan-mode-reference.md +2 -2
- package/src/prompts/system/plan-mode-subagent.md +8 -8
- package/src/prompts/system/subagent-submit-reminder.md +5 -5
- package/src/prompts/system/subagent-system-prompt.md +29 -22
- package/src/prompts/system/subagent-user-prompt.md +7 -3
- package/src/prompts/system/summarization-system.md +1 -1
- package/src/prompts/system/system-prompt.md +201 -226
- package/src/prompts/system/title-system.md +2 -2
- package/src/prompts/system/ttsr-interrupt.md +1 -1
- package/src/prompts/system/web-search.md +16 -16
- package/src/prompts/tools/ask.md +1 -3
- package/src/prompts/tools/await.md +2 -4
- package/src/prompts/tools/bash.md +5 -7
- package/src/prompts/tools/browser.md +4 -6
- package/src/prompts/tools/calculator.md +1 -3
- package/src/prompts/tools/cancel-job.md +2 -4
- package/src/prompts/tools/exit-plan-mode.md +7 -7
- package/src/prompts/tools/fetch.md +0 -2
- package/src/prompts/tools/find.md +3 -5
- package/src/prompts/tools/gemini-image.md +6 -22
- package/src/prompts/tools/grep.md +4 -6
- package/src/prompts/tools/hashline.md +12 -15
- package/src/prompts/tools/lsp.md +1 -3
- package/src/prompts/tools/patch.md +7 -9
- package/src/prompts/tools/python.md +10 -14
- package/src/prompts/tools/read.md +0 -2
- package/src/prompts/tools/replace.md +5 -7
- package/src/prompts/tools/ssh.md +3 -5
- package/src/prompts/tools/task.md +6 -8
- package/src/prompts/tools/todo-write.md +7 -9
- package/src/prompts/tools/web-search.md +3 -5
- package/src/prompts/tools/write.md +3 -5
- package/src/sdk.ts +1 -2
- package/src/session/agent-session.ts +10 -26
- package/src/session/agent-storage.ts +1 -2
- package/src/session/history-storage.ts +1 -2
- package/src/session/session-manager.ts +10 -2
- package/src/ssh/connection-manager.ts +11 -2
- package/src/ssh/sshfs-mount.ts +7 -1
- package/src/system-prompt.ts +25 -103
- package/src/task/agents.ts +1 -1
- package/src/task/worktree.ts +1 -2
- package/src/tools/ask.ts +0 -1
- package/src/tools/bash-interactive.ts +2 -45
- package/src/tools/bash.ts +5 -5
- package/src/tools/browser.ts +1 -2
- package/src/tools/gemini-image.ts +8 -28
- package/src/tools/json-tree.ts +2 -1
- package/src/tools/python.ts +1 -1
- package/src/tools/read.ts +1 -2
- package/src/utils/tools-manager.ts +1 -2
- package/src/web/scrapers/artifacthub.ts +2 -1
- package/src/web/scrapers/aur.ts +2 -1
- package/src/web/scrapers/biorxiv.ts +2 -1
- package/src/web/scrapers/bluesky.ts +2 -1
- package/src/web/scrapers/chocolatey.ts +2 -1
- package/src/web/scrapers/cisa-kev.ts +2 -1
- package/src/web/scrapers/clojars.ts +2 -1
- package/src/web/scrapers/coingecko.ts +2 -1
- package/src/web/scrapers/crates-io.ts +2 -1
- package/src/web/scrapers/crossref.ts +2 -1
- package/src/web/scrapers/discogs.ts +3 -1
- package/src/web/scrapers/discourse.ts +2 -1
- package/src/web/scrapers/dockerhub.ts +2 -1
- package/src/web/scrapers/fdroid.ts +2 -1
- package/src/web/scrapers/firefox-addons.ts +2 -1
- package/src/web/scrapers/flathub.ts +2 -1
- package/src/web/scrapers/gitlab.ts +1 -1
- package/src/web/scrapers/go-pkg.ts +2 -1
- package/src/web/scrapers/hackage.ts +2 -1
- package/src/web/scrapers/hackernews.ts +2 -1
- package/src/web/scrapers/hex.ts +2 -1
- package/src/web/scrapers/huggingface.ts +2 -1
- package/src/web/scrapers/jetbrains-marketplace.ts +2 -1
- package/src/web/scrapers/lemmy.ts +2 -1
- package/src/web/scrapers/lobsters.ts +2 -1
- package/src/web/scrapers/mastodon.ts +2 -1
- package/src/web/scrapers/maven.ts +2 -1
- package/src/web/scrapers/mdn.ts +2 -1
- package/src/web/scrapers/metacpan.ts +2 -1
- package/src/web/scrapers/musicbrainz.ts +3 -1
- package/src/web/scrapers/npm.ts +2 -1
- package/src/web/scrapers/nuget.ts +2 -1
- package/src/web/scrapers/nvd.ts +2 -1
- package/src/web/scrapers/ollama.ts +2 -1
- package/src/web/scrapers/open-vsx.ts +2 -1
- package/src/web/scrapers/opencorporates.ts +2 -1
- package/src/web/scrapers/openlibrary.ts +2 -1
- package/src/web/scrapers/orcid.ts +3 -1
- package/src/web/scrapers/osv.ts +2 -1
- package/src/web/scrapers/packagist.ts +2 -1
- package/src/web/scrapers/pub-dev.ts +2 -1
- package/src/web/scrapers/pubmed.ts +2 -1
- package/src/web/scrapers/pypi.ts +2 -1
- package/src/web/scrapers/rawg.ts +2 -8
- package/src/web/scrapers/reddit.ts +2 -1
- package/src/web/scrapers/repology.ts +2 -1
- package/src/web/scrapers/rfc.ts +2 -1
- package/src/web/scrapers/rubygems.ts +2 -1
- package/src/web/scrapers/searchcode.ts +2 -1
- package/src/web/scrapers/sec-edgar.ts +2 -1
- package/src/web/scrapers/semantic-scholar.ts +2 -1
- package/src/web/scrapers/snapcraft.ts +2 -1
- package/src/web/scrapers/sourcegraph.ts +2 -1
- package/src/web/scrapers/spdx.ts +2 -1
- package/src/web/scrapers/stackoverflow.ts +2 -1
- package/src/web/scrapers/terraform.ts +2 -1
- package/src/web/scrapers/types.ts +0 -11
- package/src/web/scrapers/vimeo.ts +2 -1
- package/src/web/scrapers/vscode-marketplace.ts +2 -1
- package/src/web/scrapers/w3c.ts +2 -1
- package/src/web/scrapers/wikidata.ts +2 -1
- package/src/web/search/index.ts +10 -14
- package/src/web/search/provider.ts +2 -2
- package/src/web/search/providers/codex.ts +1 -2
- package/src/web/search/providers/exa.ts +1 -6
- package/src/web/search/providers/gemini.ts +1 -1
- package/src/web/search/providers/perplexity.ts +1 -2
- package/src/web/search/providers/utils.ts +1 -1
package/src/exa/factory.ts
CHANGED
|
@@ -31,12 +31,7 @@ export function createExaTool(
|
|
|
31
31
|
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
|
|
32
32
|
try {
|
|
33
33
|
const apiKey = await findApiKey();
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
content: [{ type: "text" as const, text: "Error: EXA_API_KEY not found" }],
|
|
37
|
-
details: { error: "EXA_API_KEY not found", toolName: name },
|
|
38
|
-
};
|
|
39
|
-
}
|
|
34
|
+
// Exa MCP endpoint is publicly accessible; API key is optional
|
|
40
35
|
const args = transformParams ? transformParams(params as Record<string, unknown>) : params;
|
|
41
36
|
const response = await callExaTool(mcpToolName, args, apiKey);
|
|
42
37
|
|
package/src/exa/linkedin.ts
CHANGED
package/src/exa/mcp-client.ts
CHANGED
|
@@ -17,8 +17,11 @@ export function findApiKey(): string | null {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/** Fetch available tools from Exa MCP */
|
|
20
|
-
export async function fetchExaTools(apiKey: string, toolNames: string[]): Promise<MCPTool[]> {
|
|
21
|
-
const
|
|
20
|
+
export async function fetchExaTools(apiKey: string | null, toolNames: string[]): Promise<MCPTool[]> {
|
|
21
|
+
const params = new URLSearchParams();
|
|
22
|
+
if (apiKey) params.set("exaApiKey", apiKey);
|
|
23
|
+
params.set("toolNames", toolNames.join(","));
|
|
24
|
+
const url = `https://mcp.exa.ai/mcp?${params.toString()}`;
|
|
22
25
|
const response = (await callMCP(url, "tools/list")) as MCPToolsResponse;
|
|
23
26
|
|
|
24
27
|
if (response.error) {
|
|
@@ -43,8 +46,15 @@ export async function fetchWebsetsTools(apiKey: string): Promise<MCPTool[]> {
|
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
/** Call a tool on Exa MCP (simplified: toolName as first arg for easier use) */
|
|
46
|
-
export async function callExaTool(
|
|
47
|
-
|
|
49
|
+
export async function callExaTool(
|
|
50
|
+
toolName: string,
|
|
51
|
+
args: Record<string, unknown>,
|
|
52
|
+
apiKey: string | null,
|
|
53
|
+
): Promise<unknown> {
|
|
54
|
+
const params = new URLSearchParams();
|
|
55
|
+
if (apiKey) params.set("exaApiKey", apiKey);
|
|
56
|
+
params.set("tools", toolName);
|
|
57
|
+
const url = `https://mcp.exa.ai/mcp?${params.toString()}`;
|
|
48
58
|
const response = (await callMCP(url, "tools/call", {
|
|
49
59
|
name: toolName,
|
|
50
60
|
arguments: args,
|
|
@@ -174,15 +184,16 @@ export class MCPWrappedTool implements CustomTool<TSchema, ExaRenderDetails> {
|
|
|
174
184
|
): Promise<CustomToolResult<ExaRenderDetails>> {
|
|
175
185
|
try {
|
|
176
186
|
const apiKey = await findApiKey();
|
|
177
|
-
|
|
187
|
+
// Websets tools require an API key; basic Exa MCP tools work without one
|
|
188
|
+
if (!apiKey && this.config.isWebsetsTool) {
|
|
178
189
|
return {
|
|
179
|
-
content: [{ type: "text" as const, text: "Error: EXA_API_KEY
|
|
180
|
-
details: { error: "EXA_API_KEY
|
|
190
|
+
content: [{ type: "text" as const, text: "Error: EXA_API_KEY required for Websets tools" }],
|
|
191
|
+
details: { error: "EXA_API_KEY required for Websets tools", toolName: this.config.name },
|
|
181
192
|
};
|
|
182
193
|
}
|
|
183
194
|
|
|
184
195
|
const response = this.config.isWebsetsTool
|
|
185
|
-
? await callWebsetsTool(apiKey
|
|
196
|
+
? await callWebsetsTool(apiKey!, this.config.mcpToolName, params as Record<string, unknown>)
|
|
186
197
|
: await callExaTool(this.config.mcpToolName, params as Record<string, unknown>, apiKey);
|
|
187
198
|
|
|
188
199
|
if (isSearchResponse(response)) {
|
package/src/exa/search.ts
CHANGED
|
@@ -143,7 +143,7 @@ Similar parameters to exa_search, optimized for research depth.`,
|
|
|
143
143
|
),
|
|
144
144
|
}),
|
|
145
145
|
"web_search_exa",
|
|
146
|
-
{ transformParams: params => ({ ...params, type: "
|
|
146
|
+
{ transformParams: params => ({ ...params, type: "auto" }) },
|
|
147
147
|
);
|
|
148
148
|
|
|
149
149
|
/** exa_search_code - Code-focused search */
|
|
@@ -195,7 +195,7 @@ Parameters:
|
|
|
195
195
|
}),
|
|
196
196
|
),
|
|
197
197
|
}),
|
|
198
|
-
"
|
|
198
|
+
"crawling_exa",
|
|
199
199
|
);
|
|
200
200
|
|
|
201
201
|
export const searchTools: CustomTool<any, ExaRenderDetails>[] = [
|
package/src/exa/types.ts
CHANGED
|
@@ -122,11 +122,11 @@ export const EXA_TOOL_MAPPINGS = {
|
|
|
122
122
|
// Search tools
|
|
123
123
|
web_search_exa: "exa_search",
|
|
124
124
|
get_code_context_exa: "exa_search_code",
|
|
125
|
-
|
|
125
|
+
crawling_exa: "exa_crawl",
|
|
126
126
|
// LinkedIn
|
|
127
|
-
|
|
127
|
+
linkedin_search_exa: "exa_linkedin",
|
|
128
128
|
// Company
|
|
129
|
-
|
|
129
|
+
company_research_exa: "exa_company",
|
|
130
130
|
// Researcher
|
|
131
131
|
deep_researcher_start: "exa_researcher_start",
|
|
132
132
|
deep_researcher_check: "exa_researcher_poll",
|
|
@@ -7,6 +7,7 @@ import { Shell } from "@oh-my-pi/pi-natives";
|
|
|
7
7
|
import { Settings } from "../config/settings";
|
|
8
8
|
import { OutputSink } from "../session/streaming-output";
|
|
9
9
|
import { getOrCreateSnapshot } from "../utils/shell-snapshot";
|
|
10
|
+
import { NON_INTERACTIVE_ENV } from "./non-interactive-env";
|
|
10
11
|
|
|
11
12
|
export interface BashExecutorOptions {
|
|
12
13
|
cwd?: string;
|
|
@@ -97,7 +98,7 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
97
98
|
{
|
|
98
99
|
command: finalCommand,
|
|
99
100
|
cwd: options?.cwd,
|
|
100
|
-
env: options?.env,
|
|
101
|
+
env: options?.env ? { ...NON_INTERACTIVE_ENV, ...options.env } : NON_INTERACTIVE_ENV,
|
|
101
102
|
timeoutMs: options?.timeout,
|
|
102
103
|
signal,
|
|
103
104
|
},
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export const NON_INTERACTIVE_ENV: Readonly<Record<string, string>> = {
|
|
2
|
+
// Disable pagers so commands don't block on interactive views.
|
|
3
|
+
PAGER: "cat",
|
|
4
|
+
GIT_PAGER: "cat",
|
|
5
|
+
MANPAGER: "cat",
|
|
6
|
+
SYSTEMD_PAGER: "cat",
|
|
7
|
+
BAT_PAGER: "cat",
|
|
8
|
+
DELTA_PAGER: "cat",
|
|
9
|
+
GH_PAGER: "cat",
|
|
10
|
+
GLAB_PAGER: "cat",
|
|
11
|
+
PSQL_PAGER: "cat",
|
|
12
|
+
MYSQL_PAGER: "cat",
|
|
13
|
+
AWS_PAGER: "",
|
|
14
|
+
HOMEBREW_PAGER: "cat",
|
|
15
|
+
LESS: "FRX",
|
|
16
|
+
// Disable editor and terminal credential prompts.
|
|
17
|
+
GIT_EDITOR: "true",
|
|
18
|
+
VISUAL: "true",
|
|
19
|
+
EDITOR: "true",
|
|
20
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
21
|
+
SSH_ASKPASS: "/usr/bin/false",
|
|
22
|
+
CI: "1",
|
|
23
|
+
// Package manager defaults for unattended execution.
|
|
24
|
+
npm_config_yes: "true",
|
|
25
|
+
npm_config_update_notifier: "false",
|
|
26
|
+
npm_config_fund: "false",
|
|
27
|
+
npm_config_audit: "false",
|
|
28
|
+
npm_config_progress: "false",
|
|
29
|
+
PNPM_DISABLE_SELF_UPDATE_CHECK: "true",
|
|
30
|
+
PNPM_UPDATE_NOTIFIER: "false",
|
|
31
|
+
YARN_ENABLE_TELEMETRY: "0",
|
|
32
|
+
YARN_ENABLE_PROGRESS_BARS: "0",
|
|
33
|
+
// Cross-language/tooling non-interactive defaults.
|
|
34
|
+
CARGO_TERM_PROGRESS_WHEN: "never",
|
|
35
|
+
DEBIAN_FRONTEND: "noninteractive",
|
|
36
|
+
PIP_NO_INPUT: "1",
|
|
37
|
+
PIP_DISABLE_PIP_VERSION_CHECK: "1",
|
|
38
|
+
TF_INPUT: "0",
|
|
39
|
+
TF_IN_AUTOMATION: "1",
|
|
40
|
+
GH_PROMPT_DISABLED: "1",
|
|
41
|
+
COMPOSER_NO_INTERACTION: "1",
|
|
42
|
+
CLOUDSDK_CORE_DISABLE_PROMPTS: "1",
|
|
43
|
+
};
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
-
import { getAgentDir } from "@oh-my-pi/pi-utils
|
|
9
|
+
import { getAgentDir } from "@oh-my-pi/pi-utils";
|
|
10
10
|
|
|
11
11
|
export interface CustomShareResult {
|
|
12
12
|
/** URL to display/open (optional - script may handle everything itself) */
|
package/src/export/html/index.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import type { AgentState } from "@oh-my-pi/pi-agent-core";
|
|
3
|
-
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
4
|
-
import { APP_NAME } from "@oh-my-pi/pi-utils/dirs";
|
|
3
|
+
import { APP_NAME, isEnoent } from "@oh-my-pi/pi-utils";
|
|
5
4
|
import { getResolvedThemeColors, getThemeExportColors } from "../../modes/theme/theme";
|
|
6
5
|
import { type SessionEntry, type SessionHeader, SessionManager } from "../../session/session-manager";
|
|
7
6
|
// Pre-generated template (created by scripts/generate-template.ts at publish time)
|
|
@@ -7,8 +7,7 @@
|
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
import * as piCodingAgent from "@oh-my-pi/pi-coding-agent";
|
|
10
|
-
import { isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
11
|
-
import { getAgentDir, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
|
10
|
+
import { getAgentDir, getProjectDir, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
12
11
|
import * as typebox from "@sinclair/typebox";
|
|
13
12
|
import { getConfigDirs } from "../../config";
|
|
14
13
|
import { execCommand } from "../../exec/exec";
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
4
|
-
import { getAgentDir, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
|
3
|
+
import { getAgentDir, getProjectDir, isEnoent } from "@oh-my-pi/pi-utils";
|
|
5
4
|
import { extractPackageName } from "./parser";
|
|
6
5
|
import type { InstalledPlugin } from "./types";
|
|
7
6
|
|
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
-
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
10
|
-
import { getPluginsLockfile, getPluginsNodeModules, getPluginsPackageJson } from "@oh-my-pi/pi-utils/dirs";
|
|
9
|
+
import { getPluginsLockfile, getPluginsNodeModules, getPluginsPackageJson, isEnoent } from "@oh-my-pi/pi-utils";
|
|
11
10
|
import { getConfigDirPaths } from "../../config";
|
|
12
11
|
import type { InstalledPlugin, PluginManifest, PluginRuntimeConfig, ProjectPluginOverrides } from "./types";
|
|
13
12
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
4
3
|
import {
|
|
5
4
|
getPluginsDir,
|
|
6
5
|
getPluginsLockfile,
|
|
@@ -8,7 +7,9 @@ import {
|
|
|
8
7
|
getPluginsPackageJson,
|
|
9
8
|
getProjectDir,
|
|
10
9
|
getProjectPluginOverridesPath,
|
|
11
|
-
|
|
10
|
+
isEnoent,
|
|
11
|
+
logger,
|
|
12
|
+
} from "@oh-my-pi/pi-utils";
|
|
12
13
|
import { extractPackageName, parsePluginSpec } from "./parser";
|
|
13
14
|
import type {
|
|
14
15
|
DoctorCheck,
|
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
|
-
import * as
|
|
3
|
-
import {
|
|
4
|
-
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import { getProjectDir } from "@oh-my-pi/pi-utils";
|
|
5
4
|
import { skillCapability } from "../capability/skill";
|
|
6
5
|
import type { SourceMeta } from "../capability/types";
|
|
7
6
|
import type { SkillsSettings } from "../config/settings";
|
|
8
|
-
import type
|
|
9
|
-
import {
|
|
7
|
+
import { type Skill as CapabilitySkill, loadCapability } from "../discovery";
|
|
8
|
+
import { scanSkillsFromDir } from "../discovery/helpers";
|
|
10
9
|
import { expandTilde } from "../tools/path-utils";
|
|
11
|
-
import { parseFrontmatter } from "../utils/frontmatter";
|
|
12
|
-
import { addIgnoreRules, createIgnoreMatcher, type IgnoreMatcher, shouldIgnore } from "../utils/ignore-files";
|
|
13
|
-
|
|
14
|
-
// Re-export SkillFrontmatter for backward compatibility
|
|
15
|
-
export type { ImportedSkillFrontmatter as SkillFrontmatter };
|
|
16
10
|
|
|
17
11
|
export interface Skill {
|
|
18
12
|
name: string;
|
|
@@ -41,91 +35,31 @@ export interface LoadSkillsFromDirOptions {
|
|
|
41
35
|
source: string;
|
|
42
36
|
}
|
|
43
37
|
|
|
44
|
-
async function readFileContent(filePath: string): Promise<string | null> {
|
|
45
|
-
try {
|
|
46
|
-
return await fs.readFile(filePath, "utf-8");
|
|
47
|
-
} catch {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Load skills from a directory recursively.
|
|
54
|
-
* Skills are directories containing a SKILL.md file with frontmatter including a description.
|
|
55
|
-
* Respects .gitignore, .ignore, and .fdignore files.
|
|
56
|
-
*/
|
|
57
38
|
export async function loadSkillsFromDir(options: LoadSkillsFromDirOptions): Promise<LoadSkillsResult> {
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (description) {
|
|
72
|
-
seenPaths.add(skillFile);
|
|
73
|
-
skills.push({
|
|
74
|
-
name,
|
|
75
|
-
description,
|
|
76
|
-
filePath: skillFile,
|
|
77
|
-
baseDir: skillDir,
|
|
78
|
-
source: options.source,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
} catch (error) {
|
|
82
|
-
logger.warn("Failed to load skill", { path: skillFile, error: String(error) });
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async function scanDir(dir: string, ig: IgnoreMatcher): Promise<void> {
|
|
87
|
-
try {
|
|
88
|
-
// Add ignore rules from this directory
|
|
89
|
-
await addIgnoreRules(ig, dir, rootDir, readFileContent);
|
|
90
|
-
|
|
91
|
-
// First check if this directory itself is a skill
|
|
92
|
-
const selfSkillFile = path.join(dir, "SKILL.md");
|
|
93
|
-
try {
|
|
94
|
-
const s = await fs.stat(selfSkillFile);
|
|
95
|
-
if (s.isFile()) {
|
|
96
|
-
await addSkill(selfSkillFile, dir, path.basename(dir));
|
|
97
|
-
// This directory is a skill, don't recurse
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
} catch {
|
|
101
|
-
// No SKILL.md in this directory
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Recurse into subdirectories
|
|
105
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
106
|
-
|
|
107
|
-
for (const entry of entries) {
|
|
108
|
-
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
109
|
-
|
|
110
|
-
const fullPath = path.join(dir, entry.name);
|
|
111
|
-
const isDir = entry.isDirectory();
|
|
112
|
-
|
|
113
|
-
// Check if this entry should be ignored
|
|
114
|
-
if (shouldIgnore(ig, rootDir, fullPath, isDir)) continue;
|
|
115
|
-
|
|
116
|
-
if (isDir) {
|
|
117
|
-
await scanDir(fullPath, ig);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
} catch (err) {
|
|
121
|
-
warnings.push({ skillPath: dir, message: `Failed to read directory: ${err}` });
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const ig = createIgnoreMatcher();
|
|
126
|
-
await scanDir(options.dir, ig);
|
|
39
|
+
const [rawProviderId, rawLevel] = options.source.split(":", 2);
|
|
40
|
+
const providerId = rawProviderId || "custom";
|
|
41
|
+
const level: "user" | "project" = rawLevel === "project" ? "project" : "user";
|
|
42
|
+
const result = await scanSkillsFromDir(
|
|
43
|
+
{ cwd: getProjectDir(), home: os.homedir() },
|
|
44
|
+
{
|
|
45
|
+
dir: options.dir,
|
|
46
|
+
providerId,
|
|
47
|
+
level,
|
|
48
|
+
requireDescription: true,
|
|
49
|
+
},
|
|
50
|
+
);
|
|
127
51
|
|
|
128
|
-
return {
|
|
52
|
+
return {
|
|
53
|
+
skills: result.items.map(capSkill => ({
|
|
54
|
+
name: capSkill.name,
|
|
55
|
+
description: typeof capSkill.frontmatter?.description === "string" ? capSkill.frontmatter.description : "",
|
|
56
|
+
filePath: capSkill.path,
|
|
57
|
+
baseDir: capSkill.path.replace(/\/SKILL\.md$/, ""),
|
|
58
|
+
source: options.source,
|
|
59
|
+
_source: capSkill._source,
|
|
60
|
+
})),
|
|
61
|
+
warnings: (result.warnings ?? []).map(message => ({ skillPath: options.dir, message })),
|
|
62
|
+
};
|
|
129
63
|
}
|
|
130
64
|
|
|
131
65
|
export interface LoadSkillsOptions extends SkillsSettings {
|
|
@@ -225,45 +159,55 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
|
|
|
225
159
|
message: `name collision: "${capSkill.name}" already loaded from ${existing.filePath}, skipping this one`,
|
|
226
160
|
});
|
|
227
161
|
} else {
|
|
228
|
-
|
|
229
|
-
const skill: Skill = {
|
|
162
|
+
skillMap.set(capSkill.name, {
|
|
230
163
|
name: capSkill.name,
|
|
231
|
-
description: capSkill.frontmatter?.description
|
|
164
|
+
description: typeof capSkill.frontmatter?.description === "string" ? capSkill.frontmatter.description : "",
|
|
232
165
|
filePath: capSkill.path,
|
|
233
166
|
baseDir: capSkill.path.replace(/\/SKILL\.md$/, ""),
|
|
234
167
|
source: `${capSkill._source.provider}:${capSkill.level}`,
|
|
235
168
|
_source: capSkill._source,
|
|
236
|
-
};
|
|
237
|
-
skillMap.set(capSkill.name, skill);
|
|
169
|
+
});
|
|
238
170
|
realPathSet.add(resolvedPath);
|
|
239
171
|
}
|
|
240
172
|
}
|
|
241
173
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
174
|
+
const customDirectoryResults = await Promise.all(
|
|
175
|
+
customDirectories.map(async dir => {
|
|
176
|
+
const expandedDir = expandTilde(dir);
|
|
177
|
+
const scanResult = await scanSkillsFromDir(
|
|
178
|
+
{ cwd, home: os.homedir() },
|
|
179
|
+
{
|
|
180
|
+
dir: expandedDir,
|
|
181
|
+
providerId: "custom",
|
|
182
|
+
level: "user",
|
|
183
|
+
requireDescription: true,
|
|
184
|
+
},
|
|
185
|
+
);
|
|
186
|
+
return { expandedDir, scanResult };
|
|
187
|
+
}),
|
|
246
188
|
);
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
189
|
+
|
|
190
|
+
const allCustomSkills: Array<{ skill: Skill; path: string }> = [];
|
|
191
|
+
for (const { expandedDir, scanResult } of customDirectoryResults) {
|
|
192
|
+
for (const capSkill of scanResult.items) {
|
|
193
|
+
if (matchesIgnorePatterns(capSkill.name)) continue;
|
|
194
|
+
if (!matchesIncludePatterns(capSkill.name)) continue;
|
|
251
195
|
allCustomSkills.push({
|
|
252
196
|
skill: {
|
|
253
|
-
name:
|
|
254
|
-
description:
|
|
255
|
-
|
|
256
|
-
|
|
197
|
+
name: capSkill.name,
|
|
198
|
+
description:
|
|
199
|
+
typeof capSkill.frontmatter?.description === "string" ? capSkill.frontmatter.description : "",
|
|
200
|
+
filePath: capSkill.path,
|
|
201
|
+
baseDir: capSkill.path.replace(/\/SKILL\.md$/, ""),
|
|
257
202
|
source: "custom:user",
|
|
258
|
-
_source: {
|
|
203
|
+
_source: { ...capSkill._source, providerName: "Custom" },
|
|
259
204
|
},
|
|
260
|
-
path:
|
|
205
|
+
path: capSkill.path,
|
|
261
206
|
});
|
|
262
207
|
}
|
|
263
|
-
collisionWarnings.push(...
|
|
208
|
+
collisionWarnings.push(...(scanResult.warnings ?? []).map(message => ({ skillPath: expandedDir, message })));
|
|
264
209
|
}
|
|
265
210
|
|
|
266
|
-
// Batch resolve custom skill paths
|
|
267
211
|
const customRealPaths = await Promise.all(
|
|
268
212
|
allCustomSkills.map(async ({ path }) => {
|
|
269
213
|
try {
|
|
@@ -293,6 +237,6 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
|
|
|
293
237
|
|
|
294
238
|
return {
|
|
295
239
|
skills: Array.from(skillMap.values()),
|
|
296
|
-
warnings: [...result.warnings.map(w => ({ skillPath: "", message: w })), ...collisionWarnings],
|
|
240
|
+
warnings: [...(result.warnings ?? []).map(w => ({ skillPath: "", message: w })), ...collisionWarnings],
|
|
297
241
|
};
|
|
298
242
|
}
|
package/src/index.ts
CHANGED
|
@@ -6,8 +6,7 @@ export { StringEnum } from "@oh-my-pi/pi-ai";
|
|
|
6
6
|
// Re-export TUI components for custom tool rendering
|
|
7
7
|
export { Container, Markdown, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
8
8
|
// Logging
|
|
9
|
-
export { logger } from "@oh-my-pi/pi-utils";
|
|
10
|
-
export { getAgentDir, VERSION } from "@oh-my-pi/pi-utils/dirs";
|
|
9
|
+
export { getAgentDir, logger, VERSION } from "@oh-my-pi/pi-utils";
|
|
11
10
|
export { formatKeyHint, formatKeyHints } from "./config/keybindings";
|
|
12
11
|
export { ModelRegistry } from "./config/model-registry";
|
|
13
12
|
// Prompt templates
|
|
@@ -89,7 +88,6 @@ export {
|
|
|
89
88
|
loadSkills,
|
|
90
89
|
loadSkillsFromDir,
|
|
91
90
|
type Skill,
|
|
92
|
-
type SkillFrontmatter,
|
|
93
91
|
type SkillWarning,
|
|
94
92
|
} from "./extensibility/skills";
|
|
95
93
|
// Slash commands
|