From 4087e69e70683af35e56edc7a3ebe9ae23c9f6ce Mon Sep 17 00:00:00 2001 From: can1357 Date: Mon, 4 May 2026 04:27:47 +0200 Subject: [PATCH] feat(coding-agent): added read CLI command for inspecting tool output - Registered a new `read` command in the CLI command registry so `omp read` can be invoked. - Implemented `runReadCommand` to execute the read tool, wrap it with meta notices, and print text or image-result blocks. - Added a `read` command class with required path input, optional timeout flag, and usage examples. --- packages/coding-agent/src/cli.ts | 1 + packages/coding-agent/src/cli/read-cli.ts | 58 ++++++++++++++++++++++ packages/coding-agent/src/commands/read.ts | 40 +++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 packages/coding-agent/src/cli/read-cli.ts create mode 100644 packages/coding-agent/src/commands/read.ts diff --git a/packages/coding-agent/src/cli.ts b/packages/coding-agent/src/cli.ts index d04888e88..35b73bc63 100755 --- a/packages/coding-agent/src/cli.ts +++ b/packages/coding-agent/src/cli.ts @@ -53,6 +53,7 @@ const commands: CommandEntry[] = [ { name: "plugin", load: () => import("./commands/plugin").then(m => m.default) }, { name: "setup", load: () => import("./commands/setup").then(m => m.default) }, { name: "shell", load: () => import("./commands/shell").then(m => m.default) }, + { name: "read", load: () => import("./commands/read").then(m => m.default) }, { name: "ssh", load: () => import("./commands/ssh").then(m => m.default) }, { name: "stats", load: () => import("./commands/stats").then(m => m.default) }, { name: "update", load: () => import("./commands/update").then(m => m.default) }, diff --git a/packages/coding-agent/src/cli/read-cli.ts b/packages/coding-agent/src/cli/read-cli.ts new file mode 100644 index 000000000..a490e07f7 --- /dev/null +++ b/packages/coding-agent/src/cli/read-cli.ts @@ -0,0 +1,58 @@ +/** + * Read CLI command handler. + * + * Handles `omp read` — invokes the `read` agent tool against a path/URL and + * prints the resulting content blocks exactly as the model would receive them + * (including truncation/limit notices appended by the meta-notice wrapper). + */ +import { getProjectDir } from "@oh-my-pi/pi-utils"; +import chalk from "chalk"; +import { Settings } from "../config/settings"; +import type { ToolSession } from "../tools"; +import { wrapToolWithMetaNotice } from "../tools/output-meta"; +import { ReadTool } from "../tools/read"; +import { renderError } from "../tools/tool-errors"; + +export interface ReadCommandArgs { + path: string; + timeout?: number; +} + +export async function runReadCommand(cmd: ReadCommandArgs): Promise { + if (!cmd.path) { + process.stderr.write(chalk.red("error: path is required\n")); + process.exit(1); + } + + const cwd = getProjectDir(); + const settings = await Settings.init({ cwd }); + + const session: ToolSession = { + cwd, + hasUI: false, + settings, + getSessionFile: () => null, + getSessionSpawns: () => "*", + }; + + const tool = wrapToolWithMetaNotice(new ReadTool(session)); + + try { + const result = await tool.execute("omp-read", { path: cmd.path, timeout: cmd.timeout }); + + for (const block of result.content) { + if (block.type === "text") { + process.stdout.write(block.text); + if (!block.text.endsWith("\n")) process.stdout.write("\n"); + } else if (block.type === "image") { + const decodedBytes = Buffer.from(block.data, "base64").byteLength; + process.stdout.write( + chalk.dim(`[image content: ${block.mimeType}, ${decodedBytes} bytes base64-decoded]\n`), + ); + } + } + } catch (err) { + process.stderr.write(`${chalk.red(renderError(err))}\n`); + process.exit(1); + } +} diff --git a/packages/coding-agent/src/commands/read.ts b/packages/coding-agent/src/commands/read.ts new file mode 100644 index 000000000..6b910e06c --- /dev/null +++ b/packages/coding-agent/src/commands/read.ts @@ -0,0 +1,40 @@ +/** + * Show what the read tool will return for a given path. + */ +import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli"; +import { type ReadCommandArgs, runReadCommand } from "../cli/read-cli"; +import { initTheme } from "../modes/theme/theme"; + +export default class Read extends Command { + static description = "Show what the read tool will return for a path or URL"; + + static args = { + path: Args.string({ + description: "Path or URL to read (append :sel for line ranges or raw mode, e.g. src/foo.ts:50-100)", + required: true, + }), + }; + + static flags = { + timeout: Flags.integer({ description: "Request timeout in seconds (URLs only)" }), + }; + + static examples = [ + "omp read src/foo.ts", + "omp read src/foo.ts:50-100", + "omp read src/foo.ts:raw", + "omp read https://example.com", + "omp read path/to/archive.zip:dir/file.ts", + "omp read path/to/db.sqlite:users:42", + ]; + + async run(): Promise { + const { args, flags } = await this.parse(Read); + const cmd: ReadCommandArgs = { + path: args.path ?? "", + timeout: flags.timeout, + }; + await initTheme(); + await runReadCommand(cmd); + } +}