@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,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential storage for API keys and OAuth tokens.
|
|
3
|
+
* Handles loading, saving, and refreshing credentials from auth.json.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
7
|
+
import { dirname } from "node:path";
|
|
8
|
+
import {
|
|
9
|
+
getEnvApiKey,
|
|
10
|
+
getOAuthApiKey,
|
|
11
|
+
loginAnthropic,
|
|
12
|
+
loginAntigravity,
|
|
13
|
+
loginGeminiCli,
|
|
14
|
+
loginGitHubCopilot,
|
|
15
|
+
type OAuthCredentials,
|
|
16
|
+
type OAuthProvider,
|
|
17
|
+
} from "@oh-my-pi/pi-ai";
|
|
18
|
+
import { logger } from "./logger";
|
|
19
|
+
|
|
20
|
+
export type ApiKeyCredential = {
|
|
21
|
+
type: "api_key";
|
|
22
|
+
key: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type OAuthCredential = {
|
|
26
|
+
type: "oauth";
|
|
27
|
+
} & OAuthCredentials;
|
|
28
|
+
|
|
29
|
+
export type AuthCredential = ApiKeyCredential | OAuthCredential;
|
|
30
|
+
|
|
31
|
+
export type AuthStorageData = Record<string, AuthCredential>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Credential storage backed by a JSON file.
|
|
35
|
+
* Reads from multiple fallback paths, writes to primary path.
|
|
36
|
+
*/
|
|
37
|
+
export class AuthStorage {
|
|
38
|
+
private data: AuthStorageData = {};
|
|
39
|
+
private runtimeOverrides: Map<string, string> = new Map();
|
|
40
|
+
private fallbackResolver?: (provider: string) => string | undefined;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param authPath - Primary path for reading/writing auth.json
|
|
44
|
+
* @param fallbackPaths - Additional paths to check when reading (legacy support)
|
|
45
|
+
*/
|
|
46
|
+
constructor(
|
|
47
|
+
private authPath: string,
|
|
48
|
+
private fallbackPaths: string[] = [],
|
|
49
|
+
) {
|
|
50
|
+
this.reload();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Set a runtime API key override (not persisted to disk).
|
|
55
|
+
* Used for CLI --api-key flag.
|
|
56
|
+
*/
|
|
57
|
+
setRuntimeApiKey(provider: string, apiKey: string): void {
|
|
58
|
+
this.runtimeOverrides.set(provider, apiKey);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Remove a runtime API key override.
|
|
63
|
+
*/
|
|
64
|
+
removeRuntimeApiKey(provider: string): void {
|
|
65
|
+
this.runtimeOverrides.delete(provider);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Set a fallback resolver for API keys not found in auth.json or env vars.
|
|
70
|
+
* Used for custom provider keys from models.json.
|
|
71
|
+
*/
|
|
72
|
+
setFallbackResolver(resolver: (provider: string) => string | undefined): void {
|
|
73
|
+
this.fallbackResolver = resolver;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Reload credentials from disk.
|
|
78
|
+
* Checks primary path first, then fallback paths.
|
|
79
|
+
*/
|
|
80
|
+
reload(): void {
|
|
81
|
+
const pathsToCheck = [this.authPath, ...this.fallbackPaths];
|
|
82
|
+
|
|
83
|
+
logger.debug("AuthStorage.reload checking paths", { paths: pathsToCheck });
|
|
84
|
+
|
|
85
|
+
for (const authPath of pathsToCheck) {
|
|
86
|
+
const exists = existsSync(authPath);
|
|
87
|
+
logger.debug("AuthStorage.reload path check", { path: authPath, exists });
|
|
88
|
+
|
|
89
|
+
if (exists) {
|
|
90
|
+
try {
|
|
91
|
+
this.data = JSON.parse(readFileSync(authPath, "utf-8"));
|
|
92
|
+
logger.debug("AuthStorage.reload loaded", { path: authPath, providers: Object.keys(this.data) });
|
|
93
|
+
return;
|
|
94
|
+
} catch (e) {
|
|
95
|
+
logger.error("AuthStorage failed to parse auth file", { path: authPath, error: String(e) });
|
|
96
|
+
// Continue to next path on parse error
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
logger.warn("AuthStorage no auth file found", { checkedPaths: pathsToCheck });
|
|
102
|
+
this.data = {};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Save credentials to disk.
|
|
107
|
+
*/
|
|
108
|
+
private save(): void {
|
|
109
|
+
const dir = dirname(this.authPath);
|
|
110
|
+
if (!existsSync(dir)) {
|
|
111
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
112
|
+
}
|
|
113
|
+
Bun.write(this.authPath, JSON.stringify(this.data, null, 2));
|
|
114
|
+
chmodSync(this.authPath, 0o600);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get credential for a provider.
|
|
119
|
+
*/
|
|
120
|
+
get(provider: string): AuthCredential | undefined {
|
|
121
|
+
return this.data[provider] ?? undefined;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Set credential for a provider.
|
|
126
|
+
*/
|
|
127
|
+
set(provider: string, credential: AuthCredential): void {
|
|
128
|
+
this.data[provider] = credential;
|
|
129
|
+
this.save();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Remove credential for a provider.
|
|
134
|
+
*/
|
|
135
|
+
remove(provider: string): void {
|
|
136
|
+
delete this.data[provider];
|
|
137
|
+
this.save();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* List all providers with credentials.
|
|
142
|
+
*/
|
|
143
|
+
list(): string[] {
|
|
144
|
+
return Object.keys(this.data);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Check if credentials exist for a provider.
|
|
149
|
+
*/
|
|
150
|
+
has(provider: string): boolean {
|
|
151
|
+
return provider in this.data;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get all credentials (for passing to getOAuthApiKey).
|
|
156
|
+
*/
|
|
157
|
+
getAll(): AuthStorageData {
|
|
158
|
+
return { ...this.data };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Login to an OAuth provider.
|
|
163
|
+
*/
|
|
164
|
+
async login(
|
|
165
|
+
provider: OAuthProvider,
|
|
166
|
+
callbacks: {
|
|
167
|
+
onAuth: (info: { url: string; instructions?: string }) => void;
|
|
168
|
+
onPrompt: (prompt: { message: string; placeholder?: string }) => Promise<string>;
|
|
169
|
+
onProgress?: (message: string) => void;
|
|
170
|
+
},
|
|
171
|
+
): Promise<void> {
|
|
172
|
+
let credentials: OAuthCredentials;
|
|
173
|
+
|
|
174
|
+
switch (provider) {
|
|
175
|
+
case "anthropic":
|
|
176
|
+
credentials = await loginAnthropic(
|
|
177
|
+
(url) => callbacks.onAuth({ url }),
|
|
178
|
+
() => callbacks.onPrompt({ message: "Paste the authorization code:" }),
|
|
179
|
+
);
|
|
180
|
+
break;
|
|
181
|
+
case "github-copilot":
|
|
182
|
+
credentials = await loginGitHubCopilot({
|
|
183
|
+
onAuth: (url, instructions) => callbacks.onAuth({ url, instructions }),
|
|
184
|
+
onPrompt: callbacks.onPrompt,
|
|
185
|
+
onProgress: callbacks.onProgress,
|
|
186
|
+
});
|
|
187
|
+
break;
|
|
188
|
+
case "google-gemini-cli":
|
|
189
|
+
credentials = await loginGeminiCli(callbacks.onAuth, callbacks.onProgress);
|
|
190
|
+
break;
|
|
191
|
+
case "google-antigravity":
|
|
192
|
+
credentials = await loginAntigravity(callbacks.onAuth, callbacks.onProgress);
|
|
193
|
+
break;
|
|
194
|
+
default:
|
|
195
|
+
throw new Error(`Unknown OAuth provider: ${provider}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
this.set(provider, { type: "oauth", ...credentials });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Logout from a provider.
|
|
203
|
+
*/
|
|
204
|
+
logout(provider: string): void {
|
|
205
|
+
this.remove(provider);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get API key for a provider.
|
|
210
|
+
* Priority:
|
|
211
|
+
* 1. Runtime override (CLI --api-key)
|
|
212
|
+
* 2. API key from auth.json
|
|
213
|
+
* 3. OAuth token from auth.json (auto-refreshed)
|
|
214
|
+
* 4. Environment variable
|
|
215
|
+
* 5. Fallback resolver (models.json custom providers)
|
|
216
|
+
*/
|
|
217
|
+
async getApiKey(provider: string): Promise<string | undefined> {
|
|
218
|
+
// Runtime override takes highest priority
|
|
219
|
+
const runtimeKey = this.runtimeOverrides.get(provider);
|
|
220
|
+
if (runtimeKey) {
|
|
221
|
+
return runtimeKey;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const cred = this.data[provider];
|
|
225
|
+
|
|
226
|
+
if (cred?.type === "api_key") {
|
|
227
|
+
return cred.key;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (cred?.type === "oauth") {
|
|
231
|
+
// Filter to only oauth credentials for getOAuthApiKey
|
|
232
|
+
const oauthCreds: Record<string, OAuthCredentials> = {};
|
|
233
|
+
for (const [key, value] of Object.entries(this.data)) {
|
|
234
|
+
if (value.type === "oauth") {
|
|
235
|
+
oauthCreds[key] = value;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
const result = await getOAuthApiKey(provider as OAuthProvider, oauthCreds);
|
|
241
|
+
if (result) {
|
|
242
|
+
this.data[provider] = { type: "oauth", ...result.newCredentials };
|
|
243
|
+
this.save();
|
|
244
|
+
return result.apiKey;
|
|
245
|
+
}
|
|
246
|
+
} catch {
|
|
247
|
+
this.remove(provider);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Fall back to environment variable
|
|
252
|
+
const envKey = getEnvApiKey(provider);
|
|
253
|
+
if (envKey) return envKey;
|
|
254
|
+
|
|
255
|
+
// Fall back to custom resolver (e.g., models.json custom providers)
|
|
256
|
+
return this.fallbackResolver?.(provider) ?? undefined;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bash command execution with streaming support and cancellation.
|
|
3
|
+
*
|
|
4
|
+
* This module provides a unified bash execution implementation used by:
|
|
5
|
+
* - AgentSession.executeBash() for interactive and RPC modes
|
|
6
|
+
* - Direct calls from modes that need bash execution
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createWriteStream, type WriteStream } from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import type { Subprocess } from "bun";
|
|
13
|
+
import stripAnsi from "strip-ansi";
|
|
14
|
+
import { getShellConfig, killProcessTree, sanitizeBinaryOutput } from "../utils/shell";
|
|
15
|
+
import { getOrCreateSnapshot, getSnapshotSourceCommand } from "../utils/shell-snapshot";
|
|
16
|
+
import { DEFAULT_MAX_BYTES, truncateTail } from "./tools/truncate";
|
|
17
|
+
import { ScopeSignal } from "./utils";
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Types
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
export interface BashExecutorOptions {
|
|
24
|
+
/** Working directory for command execution */
|
|
25
|
+
cwd?: string;
|
|
26
|
+
/** Timeout in milliseconds */
|
|
27
|
+
timeout?: number;
|
|
28
|
+
/** Callback for streaming output chunks (already sanitized) */
|
|
29
|
+
onChunk?: (chunk: string) => void;
|
|
30
|
+
/** AbortSignal for cancellation */
|
|
31
|
+
signal?: AbortSignal;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface BashResult {
|
|
35
|
+
/** Combined stdout + stderr output (sanitized, possibly truncated) */
|
|
36
|
+
output: string;
|
|
37
|
+
/** Process exit code (undefined if killed/cancelled) */
|
|
38
|
+
exitCode: number | undefined;
|
|
39
|
+
/** Whether the command was cancelled via signal */
|
|
40
|
+
cancelled: boolean;
|
|
41
|
+
/** Whether the output was truncated */
|
|
42
|
+
truncated: boolean;
|
|
43
|
+
/** Path to temp file containing full output (if output exceeded truncation threshold) */
|
|
44
|
+
fullOutputPath?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// Implementation
|
|
49
|
+
// ============================================================================
|
|
50
|
+
|
|
51
|
+
function createSanitizer(): TransformStream<Uint8Array, string> {
|
|
52
|
+
const decoder = new TextDecoder();
|
|
53
|
+
return new TransformStream({
|
|
54
|
+
transform(chunk, controller) {
|
|
55
|
+
const text = sanitizeBinaryOutput(stripAnsi(decoder.decode(chunk, { stream: true }))).replace(/\r/g, "");
|
|
56
|
+
controller.enqueue(text);
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function createOutputSink(
|
|
62
|
+
spillThreshold: number,
|
|
63
|
+
maxBuffer: number,
|
|
64
|
+
onChunk?: (text: string) => void,
|
|
65
|
+
): WritableStream<string> & {
|
|
66
|
+
dump: (annotation?: string) => { output: string; truncated: boolean; fullOutputPath?: string };
|
|
67
|
+
} {
|
|
68
|
+
const chunks: string[] = [];
|
|
69
|
+
let chunkBytes = 0;
|
|
70
|
+
let totalBytes = 0;
|
|
71
|
+
let fullOutputPath: string | undefined;
|
|
72
|
+
let fullOutputStream: WriteStream | undefined;
|
|
73
|
+
|
|
74
|
+
const sink = new WritableStream<string>({
|
|
75
|
+
write(text) {
|
|
76
|
+
totalBytes += text.length;
|
|
77
|
+
|
|
78
|
+
// Spill to temp file if needed
|
|
79
|
+
if (totalBytes > spillThreshold && !fullOutputPath) {
|
|
80
|
+
fullOutputPath = join(tmpdir(), `omp-${crypto.randomUUID()}.buffer`);
|
|
81
|
+
const ts = createWriteStream(fullOutputPath);
|
|
82
|
+
chunks.forEach((c) => {
|
|
83
|
+
ts.write(c);
|
|
84
|
+
});
|
|
85
|
+
fullOutputStream = ts;
|
|
86
|
+
}
|
|
87
|
+
fullOutputStream?.write(text);
|
|
88
|
+
|
|
89
|
+
// Rolling buffer
|
|
90
|
+
chunks.push(text);
|
|
91
|
+
chunkBytes += text.length;
|
|
92
|
+
while (chunkBytes > maxBuffer && chunks.length > 1) {
|
|
93
|
+
chunkBytes -= chunks.shift()!.length;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
onChunk?.(text);
|
|
97
|
+
},
|
|
98
|
+
close() {
|
|
99
|
+
fullOutputStream?.end();
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return Object.assign(sink, {
|
|
104
|
+
dump(annotation?: string) {
|
|
105
|
+
if (annotation) {
|
|
106
|
+
chunks.push(`\n\n${annotation}`);
|
|
107
|
+
}
|
|
108
|
+
const full = chunks.join("");
|
|
109
|
+
const { content, truncated } = truncateTail(full);
|
|
110
|
+
return { output: truncated ? content : full, truncated, fullOutputPath: fullOutputPath };
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Execute a bash command with optional streaming and cancellation support.
|
|
117
|
+
*
|
|
118
|
+
* Features:
|
|
119
|
+
* - Streams sanitized output via onChunk callback
|
|
120
|
+
* - Writes large output to temp file for later retrieval
|
|
121
|
+
* - Supports cancellation via AbortSignal
|
|
122
|
+
* - Sanitizes output (strips ANSI, removes binary garbage, normalizes newlines)
|
|
123
|
+
* - Truncates output if it exceeds the default max bytes
|
|
124
|
+
*
|
|
125
|
+
* @param command - The bash command to execute
|
|
126
|
+
* @param options - Optional streaming callback and abort signal
|
|
127
|
+
* @returns Promise resolving to execution result
|
|
128
|
+
*/
|
|
129
|
+
export async function executeBash(command: string, options?: BashExecutorOptions): Promise<BashResult> {
|
|
130
|
+
const { shell, args, env, prefix } = getShellConfig();
|
|
131
|
+
|
|
132
|
+
// Get or create shell snapshot (for aliases, functions, options)
|
|
133
|
+
const snapshotPath = await getOrCreateSnapshot(shell, env);
|
|
134
|
+
const snapshotPrefix = getSnapshotSourceCommand(snapshotPath);
|
|
135
|
+
|
|
136
|
+
// Build final command: snapshot + prefix + command
|
|
137
|
+
const prefixedCommand = prefix ? `${prefix} ${command}` : command;
|
|
138
|
+
const finalCommand = `${snapshotPrefix}${prefixedCommand}`;
|
|
139
|
+
|
|
140
|
+
using signal = new ScopeSignal(options);
|
|
141
|
+
|
|
142
|
+
const child: Subprocess = Bun.spawn([shell, ...args, finalCommand], {
|
|
143
|
+
cwd: options?.cwd,
|
|
144
|
+
stdin: "ignore",
|
|
145
|
+
stdout: "pipe",
|
|
146
|
+
stderr: "pipe",
|
|
147
|
+
env,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
signal.catch(() => {
|
|
151
|
+
killProcessTree(child.pid);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const sink = createOutputSink(DEFAULT_MAX_BYTES, DEFAULT_MAX_BYTES * 2, options?.onChunk);
|
|
155
|
+
|
|
156
|
+
const writer = sink.getWriter();
|
|
157
|
+
try {
|
|
158
|
+
async function pumpStream(readable: ReadableStream<Uint8Array>) {
|
|
159
|
+
const reader = readable.pipeThrough(createSanitizer()).getReader();
|
|
160
|
+
try {
|
|
161
|
+
while (true) {
|
|
162
|
+
const { done, value } = await reader.read();
|
|
163
|
+
if (done) break;
|
|
164
|
+
await writer.write(value);
|
|
165
|
+
}
|
|
166
|
+
} finally {
|
|
167
|
+
reader.releaseLock();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
await Promise.all([
|
|
171
|
+
pumpStream(child.stdout as ReadableStream<Uint8Array>),
|
|
172
|
+
pumpStream(child.stderr as ReadableStream<Uint8Array>),
|
|
173
|
+
]);
|
|
174
|
+
} finally {
|
|
175
|
+
await writer.close();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Non-zero exit codes or signal-killed processes are considered cancelled if killed via signal
|
|
179
|
+
const exitCode = await child.exited;
|
|
180
|
+
|
|
181
|
+
const cancelled = exitCode === null || (exitCode !== 0 && (options?.signal?.aborted ?? false));
|
|
182
|
+
|
|
183
|
+
if (signal.timedOut()) {
|
|
184
|
+
const secs = Math.round(options!.timeout! / 1000);
|
|
185
|
+
return {
|
|
186
|
+
exitCode: undefined,
|
|
187
|
+
cancelled: true,
|
|
188
|
+
...sink.dump(`Command timed out after ${secs} seconds`),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
exitCode: cancelled ? undefined : exitCode,
|
|
194
|
+
cancelled,
|
|
195
|
+
...sink.dump(),
|
|
196
|
+
};
|
|
197
|
+
}
|