@oh-my-pi/pi-coding-agent 11.0.3 → 11.2.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.
Files changed (143) hide show
  1. package/CHANGELOG.md +199 -49
  2. package/README.md +1 -1
  3. package/docs/config-usage.md +3 -4
  4. package/docs/sdk.md +6 -5
  5. package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
  6. package/examples/sdk/README.md +1 -1
  7. package/package.json +19 -11
  8. package/src/cli/args.ts +11 -94
  9. package/src/cli/config-cli.ts +1 -1
  10. package/src/cli/file-processor.ts +3 -3
  11. package/src/cli/oclif-help.ts +26 -0
  12. package/src/cli/web-search-cli.ts +148 -0
  13. package/src/cli.ts +8 -2
  14. package/src/commands/commit.ts +36 -0
  15. package/src/commands/config.ts +51 -0
  16. package/src/commands/grep.ts +41 -0
  17. package/src/commands/index/index.ts +136 -0
  18. package/src/commands/jupyter.ts +32 -0
  19. package/src/commands/plugin.ts +70 -0
  20. package/src/commands/setup.ts +39 -0
  21. package/src/commands/shell.ts +29 -0
  22. package/src/commands/stats.ts +29 -0
  23. package/src/commands/update.ts +21 -0
  24. package/src/commands/web-search.ts +50 -0
  25. package/src/commit/agentic/index.ts +3 -2
  26. package/src/commit/agentic/tools/analyze-file.ts +1 -3
  27. package/src/commit/git/errors.ts +4 -6
  28. package/src/commit/pipeline.ts +3 -2
  29. package/src/config/keybindings.ts +1 -3
  30. package/src/config/model-registry.ts +89 -162
  31. package/src/config/settings-schema.ts +10 -0
  32. package/src/config.ts +202 -132
  33. package/src/exa/mcp-client.ts +8 -41
  34. package/src/export/html/index.ts +1 -1
  35. package/src/extensibility/extensions/loader.ts +7 -10
  36. package/src/extensibility/extensions/runner.ts +5 -15
  37. package/src/extensibility/extensions/types.ts +1 -1
  38. package/src/extensibility/hooks/runner.ts +6 -9
  39. package/src/index.ts +0 -1
  40. package/src/ipy/kernel.ts +10 -22
  41. package/src/lsp/clients/biome-client.ts +4 -7
  42. package/src/lsp/clients/lsp-linter-client.ts +4 -6
  43. package/src/lsp/index.ts +5 -4
  44. package/src/lsp/utils.ts +18 -0
  45. package/src/main.ts +86 -181
  46. package/src/mcp/json-rpc.ts +2 -2
  47. package/src/mcp/transports/http.ts +12 -49
  48. package/src/modes/components/armin.ts +1 -3
  49. package/src/modes/components/assistant-message.ts +4 -4
  50. package/src/modes/components/bash-execution.ts +5 -3
  51. package/src/modes/components/branch-summary-message.ts +1 -3
  52. package/src/modes/components/compaction-summary-message.ts +1 -3
  53. package/src/modes/components/custom-message.ts +4 -5
  54. package/src/modes/components/extensions/extension-dashboard.ts +10 -16
  55. package/src/modes/components/extensions/extension-list.ts +5 -5
  56. package/src/modes/components/footer.ts +1 -4
  57. package/src/modes/components/hook-editor.ts +7 -32
  58. package/src/modes/components/hook-message.ts +4 -5
  59. package/src/modes/components/model-selector.ts +2 -2
  60. package/src/modes/components/plugin-settings.ts +16 -20
  61. package/src/modes/components/python-execution.ts +5 -5
  62. package/src/modes/components/session-selector.ts +6 -7
  63. package/src/modes/components/settings-defs.ts +49 -40
  64. package/src/modes/components/settings-selector.ts +8 -17
  65. package/src/modes/components/skill-message.ts +1 -3
  66. package/src/modes/components/status-line-segment-editor.ts +1 -3
  67. package/src/modes/components/status-line.ts +1 -3
  68. package/src/modes/components/todo-reminder.ts +5 -7
  69. package/src/modes/components/tree-selector.ts +10 -12
  70. package/src/modes/components/ttsr-notification.ts +1 -3
  71. package/src/modes/components/user-message-selector.ts +2 -4
  72. package/src/modes/components/welcome.ts +6 -18
  73. package/src/modes/controllers/event-controller.ts +1 -0
  74. package/src/modes/controllers/extension-ui-controller.ts +1 -1
  75. package/src/modes/controllers/input-controller.ts +7 -34
  76. package/src/modes/controllers/selector-controller.ts +3 -3
  77. package/src/modes/interactive-mode.ts +27 -1
  78. package/src/modes/rpc/rpc-client.ts +2 -5
  79. package/src/modes/rpc/rpc-mode.ts +2 -2
  80. package/src/modes/theme/theme.ts +2 -6
  81. package/src/modes/types.ts +1 -0
  82. package/src/modes/utils/ui-helpers.ts +6 -1
  83. package/src/patch/index.ts +1 -4
  84. package/src/prompts/agents/explore.md +1 -0
  85. package/src/prompts/agents/frontmatter.md +2 -1
  86. package/src/prompts/agents/init.md +1 -0
  87. package/src/prompts/agents/plan.md +1 -0
  88. package/src/prompts/agents/reviewer.md +1 -0
  89. package/src/prompts/system/subagent-submit-reminder.md +2 -0
  90. package/src/prompts/system/subagent-system-prompt.md +2 -0
  91. package/src/prompts/system/subagent-user-prompt.md +8 -0
  92. package/src/prompts/system/system-prompt.md +5 -3
  93. package/src/prompts/system/web-search.md +6 -4
  94. package/src/prompts/tools/task.md +216 -163
  95. package/src/sdk.ts +11 -110
  96. package/src/session/agent-session.ts +117 -83
  97. package/src/session/auth-storage.ts +10 -51
  98. package/src/session/messages.ts +17 -3
  99. package/src/session/session-manager.ts +30 -30
  100. package/src/session/streaming-output.ts +1 -1
  101. package/src/ssh/ssh-executor.ts +6 -3
  102. package/src/task/agents.ts +2 -0
  103. package/src/task/discovery.ts +1 -1
  104. package/src/task/executor.ts +5 -10
  105. package/src/task/index.ts +43 -23
  106. package/src/task/render.ts +67 -64
  107. package/src/task/template.ts +17 -34
  108. package/src/task/types.ts +49 -22
  109. package/src/tools/ask.ts +1 -3
  110. package/src/tools/bash.ts +1 -4
  111. package/src/tools/browser.ts +5 -7
  112. package/src/tools/exit-plan-mode.ts +1 -4
  113. package/src/tools/fetch.ts +1 -3
  114. package/src/tools/find.ts +4 -3
  115. package/src/tools/gemini-image.ts +24 -55
  116. package/src/tools/grep.ts +4 -4
  117. package/src/tools/index.ts +12 -14
  118. package/src/tools/notebook.ts +1 -5
  119. package/src/tools/python.ts +4 -3
  120. package/src/tools/read.ts +2 -4
  121. package/src/tools/render-utils.ts +23 -0
  122. package/src/tools/ssh.ts +8 -12
  123. package/src/tools/todo-write.ts +1 -4
  124. package/src/tools/tool-errors.ts +1 -4
  125. package/src/tools/write.ts +1 -3
  126. package/src/utils/external-editor.ts +59 -0
  127. package/src/utils/file-mentions.ts +39 -1
  128. package/src/utils/image-convert.ts +1 -1
  129. package/src/utils/image-resize.ts +4 -4
  130. package/src/web/search/auth.ts +3 -33
  131. package/src/web/search/index.ts +73 -139
  132. package/src/web/search/provider.ts +58 -0
  133. package/src/web/search/providers/anthropic.ts +53 -14
  134. package/src/web/search/providers/base.ts +22 -0
  135. package/src/web/search/providers/codex.ts +38 -16
  136. package/src/web/search/providers/exa.ts +30 -6
  137. package/src/web/search/providers/gemini.ts +56 -20
  138. package/src/web/search/providers/jina.ts +28 -5
  139. package/src/web/search/providers/perplexity.ts +103 -36
  140. package/src/web/search/render.ts +84 -74
  141. package/src/web/search/types.ts +285 -59
  142. package/src/migrations.ts +0 -175
  143. package/src/session/storage-migration.ts +0 -173
package/docs/sdk.md CHANGED
@@ -272,7 +272,7 @@ const { session } = await createAgentSession({
272
272
  `agentDir` is used for:
273
273
 
274
274
  - Global settings (`config.yml` + `agent.db`)
275
- - Primary auth/models locations (`auth.json`, `models.yml`, `models.json`)
275
+ - Primary auth/models locations (`agent.db`, `models.yml`, `models.json`)
276
276
  - Prompt templates (`prompts/`)
277
277
  - Custom TS commands (`commands/`)
278
278
 
@@ -328,12 +328,13 @@ API key resolution priority (handled by AuthStorage):
328
328
  3. Environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, etc.)
329
329
  4. Fallback resolver (for custom provider keys from `models.yml`)
330
330
 
331
- `discoverAuthStorage` also migrates legacy `auth.json` from user config directories (`.pi`, `.claude`, `.codex`, `.gemini`) into `agent.db`.
331
+
332
+ `discoverAuthStorage` opens the `agent.db` SQLite database in the agent directory.
332
333
 
333
334
  ```typescript
334
335
  import { AuthStorage, ModelRegistry, discoverAuthStorage, discoverModels } from "@oh-my-pi/pi-coding-agent";
335
336
 
336
- // Default: uses agentDir/auth.json → agent.db and agentDir/models.yml (with legacy fallbacks)
337
+ // Default: uses agentDir/agent.db and agentDir/models.yml
337
338
  const authStorage = await discoverAuthStorage();
338
339
  const modelRegistry = discoverModels(authStorage);
339
340
 
@@ -347,7 +348,7 @@ const { session } = await createAgentSession({
347
348
  authStorage.setRuntimeApiKey("anthropic", "sk-my-temp-key");
348
349
 
349
350
  // Custom auth storage location (use create(), constructor is private)
350
- const customAuth = await AuthStorage.create("/my/app/auth.json");
351
+ const customAuth = await AuthStorage.create("/my/app/agent.db");
351
352
  const customRegistry = new ModelRegistry(customAuth, "/my/app/models.yml");
352
353
 
353
354
  const { session } = await createAgentSession({
@@ -788,7 +789,7 @@ import {
788
789
  } from "@oh-my-pi/pi-coding-agent";
789
790
 
790
791
  // Auth and Models
791
- const authStorage = await discoverAuthStorage(); // <agentDir>/auth.json → agent.db (with fallbacks)
792
+ const authStorage = await discoverAuthStorage(); // <agentDir>/agent.db
792
793
  const modelRegistry = discoverModels(authStorage); // + <agentDir>/models.yml (or models.json)
793
794
  const allModels = modelRegistry.getAll(); // All models (built-in + custom)
794
795
  const available = modelRegistry.getAvailable(); // Only models with API keys
@@ -12,7 +12,7 @@ import {
12
12
  SessionManager,
13
13
  } from "@oh-my-pi/pi-coding-agent";
14
14
 
15
- // Default: discoverAuthStorage() uses ~/.omp/agent/auth.json
15
+ // Default: discoverAuthStorage() uses ~/.omp/agent/agent.db
16
16
  // discoverModels() loads built-in + custom models from ~/.omp/agent/models.json
17
17
  const authStorage = await discoverAuthStorage();
18
18
  const modelRegistry = await discoverModels(authStorage);
@@ -25,7 +25,7 @@ await createAgentSession({
25
25
  console.log("Session with default auth storage and model registry");
26
26
 
27
27
  // Custom auth storage location
28
- const customAuthStorage = new AuthStorage("/tmp/my-app/auth.json");
28
+ const customAuthStorage = await AuthStorage.create("/tmp/my-app/agent.db");
29
29
  const customModelRegistry = new ModelRegistry(customAuthStorage, "/tmp/my-app/models.json");
30
30
 
31
31
  await createAgentSession({
@@ -77,7 +77,7 @@ const { session } = await createAgentSession({
77
77
  });
78
78
 
79
79
  // Full control
80
- const customAuth = new AuthStorage("/my/app/auth.json");
80
+ const customAuth = await AuthStorage.create("/my/app/agent.db");
81
81
  customAuth.setRuntimeApiKey("anthropic", Bun.env.MY_KEY!);
82
82
  const customRegistry = new ModelRegistry(customAuth);
83
83
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-coding-agent",
3
- "version": "11.0.3",
3
+ "version": "11.2.0",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "ompConfig": {
@@ -10,6 +10,14 @@
10
10
  "bin": {
11
11
  "omp": "src/cli.ts"
12
12
  },
13
+ "oclif": {
14
+ "bin": "omp",
15
+ "commands": "./src/commands",
16
+ "helpClass": "./src/cli/oclif-help.ts",
17
+ "plugins": [
18
+ "@oclif/plugin-autocomplete"
19
+ ]
20
+ },
13
21
  "main": "./src/index.ts",
14
22
  "types": "./src/index.ts",
15
23
  "exports": {
@@ -79,14 +87,15 @@
79
87
  "test": "bun test"
80
88
  },
81
89
  "dependencies": {
90
+ "@oclif/core": "^4.5.6",
91
+ "@oclif/plugin-autocomplete": "^3.2.23",
82
92
  "@mozilla/readability": "0.6.0",
83
- "@oh-my-pi/omp-stats": "11.0.3",
84
- "@oh-my-pi/pi-agent-core": "11.0.3",
85
- "@oh-my-pi/pi-ai": "11.0.3",
86
- "@oh-my-pi/pi-natives": "11.0.3",
87
- "@oh-my-pi/pi-tui": "11.0.3",
88
- "@oh-my-pi/pi-utils": "11.0.3",
89
- "@openai/agents": "^0.4.5",
93
+ "@oh-my-pi/omp-stats": "11.2.0",
94
+ "@oh-my-pi/pi-agent-core": "11.2.0",
95
+ "@oh-my-pi/pi-ai": "11.2.0",
96
+ "@oh-my-pi/pi-natives": "11.2.0",
97
+ "@oh-my-pi/pi-tui": "11.2.0",
98
+ "@oh-my-pi/pi-utils": "11.2.0",
90
99
  "@sinclair/typebox": "^0.34.48",
91
100
  "ajv": "^8.17.1",
92
101
  "chalk": "^5.6.2",
@@ -104,11 +113,10 @@
104
113
  },
105
114
  "devDependencies": {
106
115
  "@types/diff": "^7.0.2",
107
- "@types/jsdom": "27.0.0",
108
- "bun-types": "^1.3.8",
109
116
  "@types/ms": "^2.1.0",
110
117
  "@types/bun": "^1.3.8",
111
- "ms": "^2.1.3"
118
+ "ms": "^2.1.3",
119
+ "@types/jsdom": "27.0.0"
112
120
  },
113
121
  "keywords": [
114
122
  "coding-agent",
package/src/cli/args.ts CHANGED
@@ -180,99 +180,8 @@ export function parseArgs(args: string[], extensionFlags?: Map<string, { type: "
180
180
  return result;
181
181
  }
182
182
 
183
- export function printHelp(): void {
184
- console.log(`${chalk.bold(APP_NAME)} - AI coding assistant
185
-
186
- ${chalk.bold("Usage:")}
187
- ${APP_NAME} [options] [@files...] [messages...]
188
-
189
- ${chalk.bold("Subcommands:")}
190
- plugin Manage plugins (install, uninstall, list, etc.)
191
- update Check for and install updates
192
- config Manage configuration settings
193
- setup Install dependencies for optional features
194
- commit AI-assisted git commits
195
- stats AI usage statistics dashboard
196
- jupyter Manage the shared Jupyter gateway
197
- shell Interactive shell console (brush-core test)
198
- grep Test grep tool
199
-
200
- ${chalk.bold("Options:")}
201
- --model <pattern> Model to use (fuzzy match: "opus", "gpt-5.2", or "p-openai/gpt-5.2")
202
- --smol <id> Smol/fast model for lightweight tasks (or PI_SMOL_MODEL env)
203
- --slow <id> Slow/reasoning model for thorough analysis (or PI_SLOW_MODEL env)
204
- --plan <id> Plan model for architectural planning (or PI_PLAN_MODEL env)
205
- --api-key <key> API key (defaults to env vars)
206
- --system-prompt <text> System prompt (default: coding assistant prompt)
207
- --append-system-prompt <text> Append text or file contents to the system prompt
208
- --allow-home Allow starting in ~ without auto-switching to a temp dir
209
- --mode <mode> Output mode: text (default), json, or rpc
210
- --print, -p Non-interactive mode: process prompt and exit
211
- --continue, -c Continue previous session
212
- --resume, -r Select a session to resume
213
- --session <path> Use specific session file
214
- --session-dir <dir> Directory for session storage and lookup
215
- --no-session Don't save session (ephemeral)
216
- --models <patterns> Comma-separated model patterns for Ctrl+P cycling
217
- Supports globs (anthropic/*, *sonnet*) and fuzzy matching
218
- --no-tools Disable all built-in tools
219
- --no-lsp Disable LSP tools, formatting, and diagnostics
220
- --tools <tools> Comma-separated list of tools to enable (default: all)
221
- Available: read, bash, edit, write, grep, find, lsp,
222
- python, notebook, task, fetch, web_search, browser, ask
223
- --thinking <level> Set thinking level: off, minimal, low, medium, high, xhigh
224
- --hook <path> Load a hook/extension file (can be used multiple times)
225
- --extension, -e <path> Load an extension file (can be used multiple times)
226
- --no-extensions Disable extension discovery (explicit -e paths still work)
227
- --no-skills Disable skills discovery and loading
228
- --skills <patterns> Comma-separated glob patterns to filter skills (e.g., git-*,docker)
229
- --export <file> Export session file to HTML and exit
230
- --list-models [search] List available models (with optional fuzzy search)
231
- --help, -h Show this help
232
- --version, -v Show version number
233
-
234
- ${chalk.bold("Examples:")}
235
- # Interactive mode
236
- ${APP_NAME}
237
-
238
- # Interactive mode with initial prompt
239
- ${APP_NAME} "List all .ts files in src/"
240
-
241
- # Include files in initial message
242
- ${APP_NAME} @prompt.md @image.png "What color is the sky?"
243
-
244
- # Non-interactive mode (process and exit)
245
- ${APP_NAME} -p "List all .ts files in src/"
246
-
247
- # Multiple messages (interactive)
248
- ${APP_NAME} "Read package.json" "What dependencies do we have?"
249
-
250
- # Continue previous session
251
- ${APP_NAME} --continue "What did we discuss?"
252
-
253
- # Use different model (fuzzy matching)
254
- ${APP_NAME} --model opus "Help me refactor this code"
255
-
256
- # Limit model cycling to specific models
257
- ${APP_NAME} --models claude-sonnet,claude-haiku,gpt-4o
258
-
259
- # Limit to a specific provider with glob pattern
260
- ${APP_NAME} --models "github-copilot/*"
261
-
262
- # Cycle models with fixed thinking levels
263
- ${APP_NAME} --models sonnet:high,haiku:low
264
-
265
- # Start with a specific thinking level
266
- ${APP_NAME} --thinking high "Solve this complex problem"
267
-
268
- # Read-only mode (no file modifications possible)
269
- ${APP_NAME} --tools read,grep,find -p "Review the code in src/"
270
-
271
- # Export a session file to HTML
272
- ${APP_NAME} --export ~/${CONFIG_DIR_NAME}/agent/sessions/--path--/session.jsonl
273
- ${APP_NAME} --export session.jsonl output.html
274
-
275
- ${chalk.bold("Environment Variables:")}
183
+ export function getExtraHelpText(): string {
184
+ return `${chalk.bold("Environment Variables:")}
276
185
  ${chalk.dim("# Core Providers")}
277
186
  ANTHROPIC_API_KEY - Anthropic Claude models
278
187
  ANTHROPIC_OAUTH_TOKEN - Anthropic OAuth (takes precedence over API key)
@@ -327,5 +236,13 @@ ${chalk.bold("Available Tools (all enabled by default):")}
327
236
  fetch - Fetch and process URLs
328
237
  web_search - Search the web
329
238
  ask - Ask user questions (interactive mode only)
330
- `);
239
+ `;
240
+ }
241
+
242
+ export function printHelp(): void {
243
+ process.stdout.write(
244
+ `${chalk.bold(APP_NAME)} - AI coding assistant\n\n` +
245
+ `Run ${APP_NAME} --help for full command and option details.\n\n` +
246
+ `${getExtraHelpText()}\n`,
247
+ );
331
248
  }
@@ -48,7 +48,7 @@ function getSettingValues(def: SettingDef): readonly string[] | undefined {
48
48
  return def.values;
49
49
  }
50
50
  if (def.type === "submenu") {
51
- const options = def.getOptions();
51
+ const options = def.options;
52
52
  if (options.length > 0) {
53
53
  return options.map(o => o.value);
54
54
  }
@@ -22,7 +22,7 @@ export interface ProcessFileOptions {
22
22
 
23
23
  /** Process @file arguments into text content and image attachments */
24
24
  export async function processFileArguments(fileArgs: string[], options?: ProcessFileOptions): Promise<ProcessedFiles> {
25
- const _autoResizeImages = options?.autoResizeImages ?? true;
25
+ const autoResizeImages = options?.autoResizeImages ?? true;
26
26
  let text = "";
27
27
  const images: ImageContent[] = [];
28
28
 
@@ -49,11 +49,11 @@ export async function processFileArguments(fileArgs: string[], options?: Process
49
49
 
50
50
  if (mimeType) {
51
51
  // Handle image file
52
- const base64Content = buffer.toString("base64");
52
+ const base64Content = buffer.toBase64();
53
53
  let attachment: ImageContent;
54
54
  let dimensionNote: string | undefined;
55
55
 
56
- if (_autoResizeImages) {
56
+ if (autoResizeImages) {
57
57
  try {
58
58
  const resized = await resizeImage({ type: "image", data: base64Content, mimeType });
59
59
  dimensionNote = formatDimensionNote(resized);
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Custom help renderer for the coding agent CLI.
3
+ */
4
+ import { CommandHelp, Help } from "@oclif/core";
5
+ import { getExtraHelpText } from "./args";
6
+
7
+ export default class OclifHelp extends Help {
8
+ protected async showRootHelp(): Promise<void> {
9
+ await super.showRootHelp();
10
+ const rootCommand = this.config.findCommand("index");
11
+ if (rootCommand) {
12
+ const rootHelp = new CommandHelp(rootCommand, this.config, {
13
+ ...this.opts,
14
+ sections: ["arguments", "flags", "examples"],
15
+ });
16
+ const output = rootHelp.generate();
17
+ if (output.trim().length > 0) {
18
+ process.stdout.write(`\n${output}\n`);
19
+ }
20
+ }
21
+ const extra = getExtraHelpText();
22
+ if (extra.trim().length > 0) {
23
+ process.stdout.write(`\n${extra}\n`);
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Web search CLI command handlers.
3
+ *
4
+ * Handles `omp q`/`omp web-search` subcommands for testing web search providers.
5
+ */
6
+ import chalk from "chalk";
7
+ import { APP_NAME } from "../config";
8
+ import { initTheme, theme } from "../modes/theme/theme";
9
+ import { runSearchQuery, type SearchParams } from "../web/search/index";
10
+ import { renderSearchResult } from "../web/search/render";
11
+ import type { SearchProviderId } from "../web/search/types";
12
+
13
+ export interface SearchCommandArgs {
14
+ query: string;
15
+ provider?: SearchProviderId | "auto";
16
+ recency?: "day" | "week" | "month" | "year";
17
+ limit?: number;
18
+ expanded: boolean;
19
+ }
20
+
21
+ const PROVIDERS: Array<SearchProviderId | "auto"> = [
22
+ "auto",
23
+ "anthropic",
24
+ "perplexity",
25
+ "exa",
26
+ "jina",
27
+ "gemini",
28
+ "codex",
29
+ ];
30
+
31
+ const RECENCY_OPTIONS: SearchCommandArgs["recency"][] = ["day", "week", "month", "year"];
32
+
33
+ /**
34
+ * Parse web search subcommand arguments.
35
+ * Returns undefined if not a web search command.
36
+ */
37
+ export function parseSearchArgs(args: string[]): SearchCommandArgs | undefined {
38
+ if (args.length === 0 || (args[0] !== "q" && args[0] !== "web-search")) {
39
+ return undefined;
40
+ }
41
+
42
+ const result: SearchCommandArgs = {
43
+ query: "",
44
+ expanded: true,
45
+ };
46
+
47
+ const positional: string[] = [];
48
+
49
+ for (let i = 1; i < args.length; i++) {
50
+ const arg = args[i];
51
+ if (arg === "--provider") {
52
+ result.provider = args[++i] as SearchCommandArgs["provider"];
53
+ } else if (arg === "--recency") {
54
+ result.recency = args[++i] as SearchCommandArgs["recency"];
55
+ } else if (arg === "--limit" || arg === "-l") {
56
+ result.limit = Number.parseInt(args[++i], 10);
57
+ } else if (arg === "--compact") {
58
+ result.expanded = false;
59
+ } else if (!arg.startsWith("-")) {
60
+ positional.push(arg);
61
+ }
62
+ }
63
+
64
+ if (positional.length > 0) {
65
+ result.query = positional.join(" ");
66
+ }
67
+
68
+ return result;
69
+ }
70
+
71
+ export async function runSearchCommand(cmd: SearchCommandArgs): Promise<void> {
72
+ if (!cmd.query) {
73
+ writeStderr(chalk.red("Error: Query is required"));
74
+ process.exit(1);
75
+ }
76
+
77
+ if (cmd.provider && !PROVIDERS.includes(cmd.provider)) {
78
+ writeStderr(chalk.red(`Error: Unknown provider "${cmd.provider}"`));
79
+ writeStderr(chalk.dim(`Valid providers: ${PROVIDERS.join(", ")}`));
80
+ process.exit(1);
81
+ }
82
+
83
+ if (cmd.recency && !RECENCY_OPTIONS.includes(cmd.recency)) {
84
+ writeStderr(chalk.red(`Error: Invalid recency "${cmd.recency}"`));
85
+ writeStderr(chalk.dim(`Valid recency values: ${RECENCY_OPTIONS.join(", ")}`));
86
+ process.exit(1);
87
+ }
88
+
89
+ if (cmd.limit !== undefined && Number.isNaN(cmd.limit)) {
90
+ writeStderr(chalk.red("Error: --limit must be a number"));
91
+ process.exit(1);
92
+ }
93
+
94
+ await initTheme();
95
+
96
+ const params: SearchParams = {
97
+ query: cmd.query,
98
+ provider: cmd.provider,
99
+ recency: cmd.recency,
100
+ limit: cmd.limit,
101
+ };
102
+
103
+ const result = await runSearchQuery(params);
104
+ const component = renderSearchResult(result, { expanded: cmd.expanded, isPartial: false }, theme, {
105
+ query: cmd.query,
106
+ provider: cmd.provider,
107
+ allowLongAnswer: true,
108
+ maxAnswerLines: cmd.expanded ? undefined : 6,
109
+ });
110
+
111
+ const width = Math.max(60, process.stdout.columns ?? 100);
112
+ writeStdout(component.render(width).join("\n"));
113
+
114
+ if (result.details?.error) {
115
+ process.exitCode = 1;
116
+ }
117
+ }
118
+
119
+ export function printSearchHelp(): void {
120
+ writeStdout(`${chalk.bold(`${APP_NAME} q`)} - Test web search providers
121
+
122
+ ${chalk.bold("Usage:")}
123
+ ${APP_NAME} q [options] <query>
124
+ ${APP_NAME} web-search [options] <query>
125
+
126
+ ${chalk.bold("Arguments:")}
127
+ query Search query text
128
+
129
+ ${chalk.bold("Options:")}
130
+ --provider <name> Provider: ${PROVIDERS.join(", ")}
131
+ --recency <value> Recency filter (Perplexity only): ${RECENCY_OPTIONS.join(", ")}
132
+ -l, --limit <n> Max results to return
133
+ --compact Render condensed output
134
+ -h, --help Show this help
135
+
136
+ ${chalk.bold("Examples:")}
137
+ ${APP_NAME} q --provider=exa "what's the color of the sky"
138
+ ${APP_NAME} q --provider=perplexity --recency=week "latest TypeScript 5.7 changes"
139
+ `);
140
+ }
141
+
142
+ function writeStdout(message: string): void {
143
+ process.stdout.write(`${message}\n`);
144
+ }
145
+
146
+ function writeStderr(message: string): void {
147
+ process.stderr.write(`${message}\n`);
148
+ }
package/src/cli.ts CHANGED
@@ -5,8 +5,14 @@
5
5
  *
6
6
  * Test with: npx tsx src/cli-new.ts [args...]
7
7
  */
8
+ import { run } from "@oclif/core";
8
9
  import { APP_NAME } from "./config";
9
- import { main } from "./main";
10
10
 
11
11
  process.title = APP_NAME;
12
- main(process.argv.slice(2));
12
+ const argv = process.argv.slice(2);
13
+ const runArgv = argv.length === 0 ? ["index"] : argv;
14
+ run(runArgv, import.meta.url).catch((error: unknown) => {
15
+ const message = error instanceof Error ? (error.stack ?? error.message) : String(error);
16
+ process.stderr.write(`${message}\n`);
17
+ process.exit(1);
18
+ });
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Generate and optionally push a commit with changelog updates.
3
+ */
4
+ import { Command, Flags } from "@oclif/core";
5
+ import { runCommitCommand } from "../commit";
6
+ import type { CommitCommandArgs } from "../commit/types";
7
+ import { initTheme } from "../modes/theme/theme";
8
+
9
+ export default class Commit extends Command {
10
+ static description = "Generate a commit message and update changelogs";
11
+
12
+ static flags = {
13
+ push: Flags.boolean({ description: "Push after committing" }),
14
+ "dry-run": Flags.boolean({ description: "Preview without committing" }),
15
+ "no-changelog": Flags.boolean({ description: "Skip changelog updates" }),
16
+ legacy: Flags.boolean({ description: "Use legacy deterministic pipeline" }),
17
+ context: Flags.string({ char: "c", description: "Additional context for the model" }),
18
+ model: Flags.string({ char: "m", description: "Override model selection" }),
19
+ };
20
+
21
+ async run(): Promise<void> {
22
+ const { flags } = await this.parse(Commit);
23
+
24
+ const cmd: CommitCommandArgs = {
25
+ push: flags.push ?? false,
26
+ dryRun: flags["dry-run"] ?? false,
27
+ noChangelog: flags["no-changelog"] ?? false,
28
+ legacy: flags.legacy,
29
+ context: flags.context,
30
+ model: flags.model,
31
+ };
32
+
33
+ await initTheme();
34
+ await runCommitCommand(cmd);
35
+ }
36
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Manage configuration settings.
3
+ */
4
+ import { Args, Command, Flags } from "@oclif/core";
5
+ import { type ConfigAction, type ConfigCommandArgs, runConfigCommand } from "../cli/config-cli";
6
+ import { initTheme } from "../modes/theme/theme";
7
+
8
+ const ACTIONS: ConfigAction[] = ["list", "get", "set", "reset", "path"];
9
+
10
+ export default class Config extends Command {
11
+ static description = "Manage configuration settings";
12
+
13
+ static args = {
14
+ action: Args.string({
15
+ description: "Config action",
16
+ required: false,
17
+ options: ACTIONS,
18
+ }),
19
+ key: Args.string({
20
+ description: "Setting key",
21
+ required: false,
22
+ }),
23
+ value: Args.string({
24
+ description: "Value (for set/reset)",
25
+ required: false,
26
+ multiple: true,
27
+ }),
28
+ };
29
+
30
+ static flags = {
31
+ json: Flags.boolean({ description: "Output JSON" }),
32
+ };
33
+
34
+ async run(): Promise<void> {
35
+ const { args, flags } = await this.parse(Config);
36
+ const action = (args.action ?? "list") as ConfigAction;
37
+ const value = Array.isArray(args.value) ? args.value.join(" ") : args.value;
38
+
39
+ const cmd: ConfigCommandArgs = {
40
+ action,
41
+ key: args.key,
42
+ value,
43
+ flags: {
44
+ json: flags.json,
45
+ },
46
+ };
47
+
48
+ await initTheme();
49
+ await runConfigCommand(cmd);
50
+ }
51
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Test grep tool.
3
+ */
4
+ import { Args, Command, Flags } from "@oclif/core";
5
+ import { type GrepCommandArgs, runGrepCommand } from "../cli/grep-cli";
6
+ import { initTheme } from "../modes/theme/theme";
7
+
8
+ export default class Grep extends Command {
9
+ static description = "Test grep tool";
10
+
11
+ static args = {
12
+ pattern: Args.string({ description: "Regex pattern to search for", required: false }),
13
+ path: Args.string({ description: "Directory or file to search", required: false }),
14
+ };
15
+
16
+ static flags = {
17
+ glob: Flags.string({ char: "g", description: "Filter files by glob pattern" }),
18
+ limit: Flags.integer({ char: "l", description: "Max matches", default: 20 }),
19
+ context: Flags.integer({ char: "C", description: "Context lines", default: 2 }),
20
+ files: Flags.boolean({ char: "f", description: "Output file names only" }),
21
+ count: Flags.boolean({ char: "c", description: "Output match counts per file" }),
22
+ };
23
+
24
+ async run(): Promise<void> {
25
+ const { args, flags } = await this.parse(Grep);
26
+
27
+ const mode: GrepCommandArgs["mode"] = flags.count ? "count" : flags.files ? "filesWithMatches" : "content";
28
+
29
+ const cmd: GrepCommandArgs = {
30
+ pattern: args.pattern ?? "",
31
+ path: args.path ?? ".",
32
+ glob: flags.glob,
33
+ limit: flags.limit,
34
+ context: flags.context,
35
+ mode,
36
+ };
37
+
38
+ await initTheme();
39
+ await runGrepCommand(cmd);
40
+ }
41
+ }