@oh-my-pi/pi-coding-agent 11.10.0 → 11.10.2
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 +34 -0
- package/package.json +7 -18
- package/src/cli.ts +52 -26
- package/src/commands/commit.ts +1 -1
- package/src/commands/config.ts +1 -1
- package/src/commands/grep.ts +1 -1
- package/src/commands/jupyter.ts +1 -1
- package/src/commands/{index/index.ts → launch.ts} +4 -4
- package/src/commands/plugin.ts +1 -1
- package/src/commands/setup.ts +1 -1
- package/src/commands/shell.ts +1 -1
- package/src/commands/stats.ts +3 -3
- package/src/commands/update.ts +3 -3
- package/src/commands/web-search.ts +1 -1
- package/src/main.ts +2 -3
- package/src/patch/hashline.ts +222 -10
- package/src/patch/index.ts +2 -0
- package/src/cli/oclif-help.ts +0 -26
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [11.10.2] - 2026-02-10
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Exported `streamHashLinesFromUtf8` and `streamHashLinesFromLines` functions for streaming hashline-formatted output with configurable chunking
|
|
10
|
+
- Added `HashlineStreamOptions` interface to control streaming behavior (startLine, maxChunkLines, maxChunkBytes)
|
|
11
|
+
- Added `streamHashLinesFromUtf8` function to incrementally format content with hash lines from a UTF-8 byte stream
|
|
12
|
+
- Added `streamHashLinesFromLines` function to incrementally format content with hash lines from an iterable of lines
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Updated hashline format to use 2-character hex hashes instead of 4-character hashes for more compact line references
|
|
17
|
+
- Modified `computeLineHash` to normalize whitespace in line content and removed line number from hash seed for consistency
|
|
18
|
+
- Improved CLI argument parsing to explicitly handle `--help`, `--version`, and subcommand detection instead of prefix-based routing
|
|
19
|
+
|
|
20
|
+
### Removed
|
|
21
|
+
|
|
22
|
+
- Removed `@types/diff` dev dependency
|
|
23
|
+
- Removed AggregateError unwrapping from console.warn in CLI initialization
|
|
24
|
+
|
|
25
|
+
## [11.10.1] - 2026-02-10
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- Migrated CLI framework from oclif to lightweight pi-utils CLI runner
|
|
29
|
+
- Replaced oclif command registration with explicit command entries in cli.ts
|
|
30
|
+
- Changed default root command name from 'index' to 'launch'
|
|
31
|
+
- Updated all command imports to use @oh-my-pi/pi-utils/cli instead of @oclif/core
|
|
32
|
+
|
|
33
|
+
### Removed
|
|
34
|
+
|
|
35
|
+
- Removed @oclif/core and @oclif/plugin-autocomplete dependencies
|
|
36
|
+
- Removed oclif configuration from package.json
|
|
37
|
+
- Removed custom oclif help renderer (oclif-help.ts)
|
|
38
|
+
|
|
5
39
|
## [11.10.0] - 2026-02-10
|
|
6
40
|
### Breaking Changes
|
|
7
41
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "11.10.
|
|
3
|
+
"version": "11.10.2",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"ompConfig": {
|
|
@@ -10,14 +10,6 @@
|
|
|
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
|
-
},
|
|
21
13
|
"main": "./src/index.ts",
|
|
22
14
|
"types": "./src/index.ts",
|
|
23
15
|
"exports": {
|
|
@@ -88,14 +80,12 @@
|
|
|
88
80
|
},
|
|
89
81
|
"dependencies": {
|
|
90
82
|
"@mozilla/readability": "0.6.0",
|
|
91
|
-
"@
|
|
92
|
-
"@
|
|
93
|
-
"@oh-my-pi/
|
|
94
|
-
"@oh-my-pi/pi-
|
|
95
|
-
"@oh-my-pi/pi-
|
|
96
|
-
"@oh-my-pi/pi-
|
|
97
|
-
"@oh-my-pi/pi-tui": "11.10.0",
|
|
98
|
-
"@oh-my-pi/pi-utils": "11.10.0",
|
|
83
|
+
"@oh-my-pi/omp-stats": "11.10.2",
|
|
84
|
+
"@oh-my-pi/pi-agent-core": "11.10.2",
|
|
85
|
+
"@oh-my-pi/pi-ai": "11.10.2",
|
|
86
|
+
"@oh-my-pi/pi-natives": "11.10.2",
|
|
87
|
+
"@oh-my-pi/pi-tui": "11.10.2",
|
|
88
|
+
"@oh-my-pi/pi-utils": "11.10.2",
|
|
99
89
|
"@sinclair/typebox": "^0.34.48",
|
|
100
90
|
"ajv": "^8.17.1",
|
|
101
91
|
"chalk": "^5.6.2",
|
|
@@ -112,7 +102,6 @@
|
|
|
112
102
|
"zod": "^4.3.6"
|
|
113
103
|
},
|
|
114
104
|
"devDependencies": {
|
|
115
|
-
"@types/diff": "^7.0.2",
|
|
116
105
|
"@types/ms": "^2.1.0",
|
|
117
106
|
"@types/bun": "^1.3.8",
|
|
118
107
|
"ms": "^2.1.3",
|
package/src/cli.ts
CHANGED
|
@@ -1,32 +1,58 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
|
-
* CLI entry point
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Test with: npx tsx src/cli-new.ts [args...]
|
|
3
|
+
* CLI entry point — registers all commands explicitly and delegates to the
|
|
4
|
+
* lightweight CLI runner from pi-utils.
|
|
7
5
|
*/
|
|
8
|
-
import { run } from "@
|
|
9
|
-
import { APP_NAME } from "./config";
|
|
6
|
+
import { type CommandEntry, run } from "@oh-my-pi/pi-utils/cli";
|
|
7
|
+
import { APP_NAME, VERSION } from "./config";
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
process.title = APP_NAME;
|
|
10
|
+
|
|
11
|
+
const commands: CommandEntry[] = [
|
|
12
|
+
{ name: "launch", load: () => import("./commands/launch").then(m => m.default) },
|
|
13
|
+
{ name: "commit", load: () => import("./commands/commit").then(m => m.default) },
|
|
14
|
+
{ name: "config", load: () => import("./commands/config").then(m => m.default) },
|
|
15
|
+
{ name: "grep", load: () => import("./commands/grep").then(m => m.default) },
|
|
16
|
+
{ name: "jupyter", load: () => import("./commands/jupyter").then(m => m.default) },
|
|
17
|
+
{ name: "plugin", load: () => import("./commands/plugin").then(m => m.default) },
|
|
18
|
+
{ name: "setup", load: () => import("./commands/setup").then(m => m.default) },
|
|
19
|
+
{ name: "shell", load: () => import("./commands/shell").then(m => m.default) },
|
|
20
|
+
{ name: "stats", load: () => import("./commands/stats").then(m => m.default) },
|
|
21
|
+
{ name: "update", load: () => import("./commands/update").then(m => m.default) },
|
|
22
|
+
{ name: "search", load: () => import("./commands/web-search").then(m => m.default), aliases: ["q"] },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
async function showHelp(config: import("@oh-my-pi/pi-utils/cli").CliConfig): Promise<void> {
|
|
26
|
+
const { renderRootHelp } = await import("@oh-my-pi/pi-utils/cli");
|
|
27
|
+
const { getExtraHelpText } = await import("./cli/args");
|
|
28
|
+
renderRootHelp(config);
|
|
29
|
+
const extra = getExtraHelpText();
|
|
30
|
+
if (extra.trim().length > 0) {
|
|
31
|
+
process.stdout.write(`\n${extra}\n`);
|
|
21
32
|
}
|
|
22
|
-
|
|
23
|
-
};
|
|
33
|
+
}
|
|
24
34
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
35
|
+
/**
|
|
36
|
+
* Determine whether argv[0] is a known subcommand name.
|
|
37
|
+
* If not, the entire argv is treated as args to the default "launch" command.
|
|
38
|
+
*/
|
|
39
|
+
function isSubcommand(first: string | undefined): boolean {
|
|
40
|
+
if (!first || first.startsWith("-") || first.startsWith("@")) return false;
|
|
41
|
+
return commands.some(e => e.name === first || e.aliases?.includes(first));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Run the CLI with the given argv (no `process.argv` prefix). */
|
|
45
|
+
export function runCli(argv: string[]): Promise<void> {
|
|
46
|
+
// --help and --version are handled by run() directly, don't rewrite those.
|
|
47
|
+
// Everything else that isn't a known subcommand routes to "launch".
|
|
48
|
+
const first = argv[0];
|
|
49
|
+
const runArgv =
|
|
50
|
+
first === "--help" || first === "-h" || first === "--version" || first === "-v" || first === "help"
|
|
51
|
+
? argv
|
|
52
|
+
: isSubcommand(first)
|
|
53
|
+
? argv
|
|
54
|
+
: ["launch", ...argv];
|
|
55
|
+
return run({ bin: APP_NAME, version: VERSION, argv: runArgv, commands, help: showHelp });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
await runCli(process.argv.slice(2));
|
package/src/commands/commit.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generate and optionally push a commit with changelog updates.
|
|
3
3
|
*/
|
|
4
|
-
import { Command, Flags } from "@
|
|
4
|
+
import { Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
5
5
|
import { runCommitCommand } from "../commit";
|
|
6
6
|
import type { CommitCommandArgs } from "../commit/types";
|
|
7
7
|
import { initTheme } from "../modes/theme/theme";
|
package/src/commands/config.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Manage configuration settings.
|
|
3
3
|
*/
|
|
4
|
-
import { Args, Command, Flags } from "@
|
|
4
|
+
import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
5
5
|
import { type ConfigAction, type ConfigCommandArgs, runConfigCommand } from "../cli/config-cli";
|
|
6
6
|
import { initTheme } from "../modes/theme/theme";
|
|
7
7
|
|
package/src/commands/grep.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Test grep tool.
|
|
3
3
|
*/
|
|
4
|
-
import { Args, Command, Flags } from "@
|
|
4
|
+
import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
5
5
|
import { type GrepCommandArgs, runGrepCommand } from "../cli/grep-cli";
|
|
6
6
|
import { initTheme } from "../modes/theme/theme";
|
|
7
7
|
|
package/src/commands/jupyter.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Manage the shared Jupyter gateway.
|
|
3
3
|
*/
|
|
4
|
-
import { Args, Command } from "@
|
|
4
|
+
import { Args, Command } from "@oh-my-pi/pi-utils/cli";
|
|
5
5
|
import { type JupyterAction, type JupyterCommandArgs, runJupyterCommand } from "../cli/jupyter-cli";
|
|
6
6
|
import { initTheme } from "../modes/theme/theme";
|
|
7
7
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Root command for the coding agent CLI.
|
|
3
3
|
*/
|
|
4
|
-
import { Args, Command, Flags } from "@
|
|
5
|
-
import { parseArgs } from "
|
|
6
|
-
import { APP_NAME } from "
|
|
7
|
-
import { runRootCommand } from "
|
|
4
|
+
import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
5
|
+
import { parseArgs } from "../cli/args";
|
|
6
|
+
import { APP_NAME } from "../config";
|
|
7
|
+
import { runRootCommand } from "../main";
|
|
8
8
|
|
|
9
9
|
export default class Index extends Command {
|
|
10
10
|
static description = "AI coding assistant";
|
package/src/commands/plugin.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Manage plugins (install, uninstall, list, etc.).
|
|
3
3
|
*/
|
|
4
|
-
import { Args, Command, Flags } from "@
|
|
4
|
+
import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
5
5
|
import { type PluginAction, type PluginCommandArgs, runPluginCommand } from "../cli/plugin-cli";
|
|
6
6
|
import { initTheme } from "../modes/theme/theme";
|
|
7
7
|
|
package/src/commands/setup.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Install dependencies for optional features.
|
|
3
3
|
*/
|
|
4
|
-
import { Args, Command, Flags } from "@
|
|
4
|
+
import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
5
5
|
import { runSetupCommand, type SetupCommandArgs, type SetupComponent } from "../cli/setup-cli";
|
|
6
6
|
import { initTheme } from "../modes/theme/theme";
|
|
7
7
|
|
package/src/commands/shell.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Interactive shell console.
|
|
3
3
|
*/
|
|
4
|
-
import { Command, Flags } from "@
|
|
4
|
+
import { Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
5
5
|
import { runShellCommand, type ShellCommandArgs } from "../cli/shell-cli";
|
|
6
6
|
import { initTheme } from "../modes/theme/theme";
|
|
7
7
|
|
package/src/commands/stats.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* View usage statistics dashboard.
|
|
3
3
|
*/
|
|
4
|
-
import { Command, Flags } from "@
|
|
4
|
+
import { Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
5
5
|
import { runStatsCommand, type StatsCommandArgs } from "../cli/stats-cli";
|
|
6
6
|
import { initTheme } from "../modes/theme/theme";
|
|
7
7
|
|
|
@@ -10,8 +10,8 @@ export default class Stats extends Command {
|
|
|
10
10
|
|
|
11
11
|
static flags = {
|
|
12
12
|
port: Flags.integer({ char: "p", description: "Port for the dashboard server", default: 3847 }),
|
|
13
|
-
json: Flags.boolean({ char: "j", description: "Output stats as JSON" }),
|
|
14
|
-
summary: Flags.boolean({ char: "s", description: "Print summary to console" }),
|
|
13
|
+
json: Flags.boolean({ char: "j", description: "Output stats as JSON", default: false }),
|
|
14
|
+
summary: Flags.boolean({ char: "s", description: "Print summary to console", default: false }),
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
async run(): Promise<void> {
|
package/src/commands/update.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Check for and install updates.
|
|
3
3
|
*/
|
|
4
|
-
import { Command, Flags } from "@
|
|
4
|
+
import { Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
5
5
|
import { runUpdateCommand } from "../cli/update-cli";
|
|
6
6
|
import { initTheme } from "../modes/theme/theme";
|
|
7
7
|
|
|
@@ -9,8 +9,8 @@ export default class Update extends Command {
|
|
|
9
9
|
static description = "Check for and install updates";
|
|
10
10
|
|
|
11
11
|
static flags = {
|
|
12
|
-
force: Flags.boolean({ char: "f", description: "Force update" }),
|
|
13
|
-
check: Flags.boolean({ char: "c", description: "Check for updates without installing" }),
|
|
12
|
+
force: Flags.boolean({ char: "f", description: "Force update", default: false }),
|
|
13
|
+
check: Flags.boolean({ char: "c", description: "Check for updates without installing", default: false }),
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
async run(): Promise<void> {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Test web search providers.
|
|
3
3
|
*/
|
|
4
|
-
import { Args, Command, Flags } from "@
|
|
4
|
+
import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
5
5
|
import { runSearchCommand, type SearchCommandArgs } from "../cli/web-search-cli";
|
|
6
6
|
import type { SearchProviderId } from "../web/search/types";
|
|
7
7
|
|
package/src/main.ts
CHANGED
|
@@ -8,7 +8,6 @@ import * as fs from "node:fs/promises";
|
|
|
8
8
|
import * as os from "node:os";
|
|
9
9
|
import * as path from "node:path";
|
|
10
10
|
import { createInterface } from "node:readline/promises";
|
|
11
|
-
import { run } from "@oclif/core";
|
|
12
11
|
import { type ImageContent, supportsXhigh } from "@oh-my-pi/pi-ai";
|
|
13
12
|
import { $env, postmortem } from "@oh-my-pi/pi-utils";
|
|
14
13
|
import chalk from "chalk";
|
|
@@ -713,6 +712,6 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
|
|
|
713
712
|
}
|
|
714
713
|
|
|
715
714
|
export async function main(args: string[]): Promise<void> {
|
|
716
|
-
const
|
|
717
|
-
await
|
|
715
|
+
const { runCli } = await import("./cli");
|
|
716
|
+
await runCli(args.length === 0 ? ["launch"] : args);
|
|
718
717
|
}
|
package/src/patch/hashline.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Hashline edit mode — a line-addressable edit format using content hashes.
|
|
3
3
|
*
|
|
4
|
-
* Each line in a file is identified by its 1-indexed line number and a
|
|
5
|
-
* hex hash derived from the line content
|
|
6
|
-
*
|
|
4
|
+
* Each line in a file is identified by its 1-indexed line number and a short
|
|
5
|
+
* hex hash derived from the normalized line content (xxHash64, truncated to 2
|
|
6
|
+
* hex chars).
|
|
7
7
|
* The combined `LINE:HASH` reference acts as both an address and a staleness check:
|
|
8
8
|
* if the file has changed since the caller last read it, hash mismatches are caught
|
|
9
9
|
* before any mutation occurs.
|
|
10
10
|
*
|
|
11
11
|
* Displayed format: `LINENUM:HASH| CONTENT`
|
|
12
|
-
* Reference format: `"LINENUM:HASH"` (e.g. `"5:
|
|
12
|
+
* Reference format: `"LINENUM:HASH"` (e.g. `"5:a3"`)
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import type { HashlineEdit, HashMismatch, SrcSpec } from "./types";
|
|
@@ -305,11 +305,11 @@ const HASH_MASK = BigInt((1 << (HASH_LEN * 4)) - 1);
|
|
|
305
305
|
const HEX_DICT = Array.from({ length: Number(HASH_MASK) + 1 }, (_, i) => i.toString(16).padStart(HASH_LEN, "0"));
|
|
306
306
|
|
|
307
307
|
/**
|
|
308
|
-
* Compute
|
|
308
|
+
* Compute a short hex hash of a single line.
|
|
309
309
|
*
|
|
310
|
-
* Uses xxHash64
|
|
311
|
-
* The
|
|
312
|
-
*
|
|
310
|
+
* Uses xxHash64 on a whitespace-normalized line, truncated to {@link HASH_LEN}
|
|
311
|
+
* hex characters. The `idx` parameter is accepted for compatibility with older
|
|
312
|
+
* call sites, but is not currently mixed into the hash.
|
|
313
313
|
* The line input should not include a trailing newline.
|
|
314
314
|
*/
|
|
315
315
|
export function computeLineHash(idx: number, line: string): string {
|
|
@@ -333,7 +333,7 @@ export function computeLineHash(idx: number, line: string): string {
|
|
|
333
333
|
* @example
|
|
334
334
|
* ```
|
|
335
335
|
* formatHashLines("function hi() {\n return;\n}")
|
|
336
|
-
* // "1:
|
|
336
|
+
* // "1:HH| function hi() {\n2:HH| return;\n3:HH| }"
|
|
337
337
|
* ```
|
|
338
338
|
*/
|
|
339
339
|
export function formatHashLines(content: string, startLine = 1): string {
|
|
@@ -347,6 +347,218 @@ export function formatHashLines(content: string, startLine = 1): string {
|
|
|
347
347
|
.join("\n");
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
351
|
+
// Hashline streaming formatter
|
|
352
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
353
|
+
|
|
354
|
+
export interface HashlineStreamOptions {
|
|
355
|
+
/** First line number to use when formatting (1-indexed). */
|
|
356
|
+
startLine?: number;
|
|
357
|
+
/** Maximum formatted lines per yielded chunk (default: 200). */
|
|
358
|
+
maxChunkLines?: number;
|
|
359
|
+
/** Maximum UTF-8 bytes per yielded chunk (default: 64 KiB). */
|
|
360
|
+
maxChunkBytes?: number;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function isReadableStream(value: unknown): value is ReadableStream<Uint8Array> {
|
|
364
|
+
return (
|
|
365
|
+
typeof value === "object" &&
|
|
366
|
+
value !== null &&
|
|
367
|
+
"getReader" in value &&
|
|
368
|
+
typeof (value as { getReader?: unknown }).getReader === "function"
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async function* bytesFromReadableStream(stream: ReadableStream<Uint8Array>): AsyncGenerator<Uint8Array> {
|
|
373
|
+
const reader = stream.getReader();
|
|
374
|
+
try {
|
|
375
|
+
while (true) {
|
|
376
|
+
const { done, value } = await reader.read();
|
|
377
|
+
if (done) return;
|
|
378
|
+
if (value) yield value;
|
|
379
|
+
}
|
|
380
|
+
} finally {
|
|
381
|
+
reader.releaseLock();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Stream hashline-formatted output from a UTF-8 byte source.
|
|
387
|
+
*
|
|
388
|
+
* This is intended for large files where callers want incremental output
|
|
389
|
+
* (e.g. while reading from a file handle) rather than allocating a single
|
|
390
|
+
* large string.
|
|
391
|
+
*/
|
|
392
|
+
export async function* streamHashLinesFromUtf8(
|
|
393
|
+
source: ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>,
|
|
394
|
+
options: HashlineStreamOptions = {},
|
|
395
|
+
): AsyncGenerator<string> {
|
|
396
|
+
const startLine = options.startLine ?? 1;
|
|
397
|
+
const maxChunkLines = options.maxChunkLines ?? 200;
|
|
398
|
+
const maxChunkBytes = options.maxChunkBytes ?? 64 * 1024;
|
|
399
|
+
const decoder = new TextDecoder("utf-8");
|
|
400
|
+
const chunks = isReadableStream(source) ? bytesFromReadableStream(source) : source;
|
|
401
|
+
let lineNum = startLine;
|
|
402
|
+
let pending = "";
|
|
403
|
+
let sawAnyText = false;
|
|
404
|
+
let endedWithNewline = false;
|
|
405
|
+
let outLines: string[] = [];
|
|
406
|
+
let outBytes = 0;
|
|
407
|
+
|
|
408
|
+
const flush = (): string | undefined => {
|
|
409
|
+
if (outLines.length === 0) return undefined;
|
|
410
|
+
const chunk = outLines.join("\n");
|
|
411
|
+
outLines = [];
|
|
412
|
+
outBytes = 0;
|
|
413
|
+
return chunk;
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
const pushLine = (line: string): string[] => {
|
|
417
|
+
const formatted = `${lineNum}:${computeLineHash(lineNum, line)}| ${line}`;
|
|
418
|
+
lineNum++;
|
|
419
|
+
|
|
420
|
+
const chunksToYield: string[] = [];
|
|
421
|
+
const sepBytes = outLines.length === 0 ? 0 : 1; // "\n"
|
|
422
|
+
const lineBytes = Buffer.byteLength(formatted, "utf-8");
|
|
423
|
+
|
|
424
|
+
if (
|
|
425
|
+
outLines.length > 0 &&
|
|
426
|
+
(outLines.length >= maxChunkLines || outBytes + sepBytes + lineBytes > maxChunkBytes)
|
|
427
|
+
) {
|
|
428
|
+
const flushed = flush();
|
|
429
|
+
if (flushed) chunksToYield.push(flushed);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
outLines.push(formatted);
|
|
433
|
+
outBytes += (outLines.length === 1 ? 0 : 1) + lineBytes;
|
|
434
|
+
|
|
435
|
+
if (outLines.length >= maxChunkLines || outBytes >= maxChunkBytes) {
|
|
436
|
+
const flushed = flush();
|
|
437
|
+
if (flushed) chunksToYield.push(flushed);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return chunksToYield;
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
const consumeText = (text: string): string[] => {
|
|
444
|
+
if (text.length === 0) return [];
|
|
445
|
+
sawAnyText = true;
|
|
446
|
+
pending += text;
|
|
447
|
+
const chunksToYield: string[] = [];
|
|
448
|
+
while (true) {
|
|
449
|
+
const idx = pending.indexOf("\n");
|
|
450
|
+
if (idx === -1) break;
|
|
451
|
+
const line = pending.slice(0, idx);
|
|
452
|
+
pending = pending.slice(idx + 1);
|
|
453
|
+
endedWithNewline = true;
|
|
454
|
+
chunksToYield.push(...pushLine(line));
|
|
455
|
+
}
|
|
456
|
+
if (pending.length > 0) endedWithNewline = false;
|
|
457
|
+
return chunksToYield;
|
|
458
|
+
};
|
|
459
|
+
for await (const chunk of chunks) {
|
|
460
|
+
for (const out of consumeText(decoder.decode(chunk, { stream: true }))) {
|
|
461
|
+
yield out;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
for (const out of consumeText(decoder.decode())) {
|
|
466
|
+
yield out;
|
|
467
|
+
}
|
|
468
|
+
if (!sawAnyText) {
|
|
469
|
+
// Mirror `"".split("\n")` behavior: one empty line.
|
|
470
|
+
for (const out of pushLine("")) {
|
|
471
|
+
yield out;
|
|
472
|
+
}
|
|
473
|
+
} else if (pending.length > 0 || endedWithNewline) {
|
|
474
|
+
// Emit the final line (may be empty if the file ended with a newline).
|
|
475
|
+
for (const out of pushLine(pending)) {
|
|
476
|
+
yield out;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const last = flush();
|
|
481
|
+
if (last) yield last;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Stream hashline-formatted output from an (async) iterable of lines.
|
|
486
|
+
*
|
|
487
|
+
* Each yielded chunk is a `\n`-joined string of one or more formatted lines.
|
|
488
|
+
*/
|
|
489
|
+
export async function* streamHashLinesFromLines(
|
|
490
|
+
lines: Iterable<string> | AsyncIterable<string>,
|
|
491
|
+
options: HashlineStreamOptions = {},
|
|
492
|
+
): AsyncGenerator<string> {
|
|
493
|
+
const startLine = options.startLine ?? 1;
|
|
494
|
+
const maxChunkLines = options.maxChunkLines ?? 200;
|
|
495
|
+
const maxChunkBytes = options.maxChunkBytes ?? 64 * 1024;
|
|
496
|
+
|
|
497
|
+
let lineNum = startLine;
|
|
498
|
+
let outLines: string[] = [];
|
|
499
|
+
let outBytes = 0;
|
|
500
|
+
let sawAnyLine = false;
|
|
501
|
+
const flush = (): string | undefined => {
|
|
502
|
+
if (outLines.length === 0) return undefined;
|
|
503
|
+
const chunk = outLines.join("\n");
|
|
504
|
+
outLines = [];
|
|
505
|
+
outBytes = 0;
|
|
506
|
+
return chunk;
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const pushLine = (line: string): string[] => {
|
|
510
|
+
sawAnyLine = true;
|
|
511
|
+
const formatted = `${lineNum}:${computeLineHash(lineNum, line)}| ${line}`;
|
|
512
|
+
lineNum++;
|
|
513
|
+
|
|
514
|
+
const chunksToYield: string[] = [];
|
|
515
|
+
const sepBytes = outLines.length === 0 ? 0 : 1;
|
|
516
|
+
const lineBytes = Buffer.byteLength(formatted, "utf-8");
|
|
517
|
+
|
|
518
|
+
if (
|
|
519
|
+
outLines.length > 0 &&
|
|
520
|
+
(outLines.length >= maxChunkLines || outBytes + sepBytes + lineBytes > maxChunkBytes)
|
|
521
|
+
) {
|
|
522
|
+
const flushed = flush();
|
|
523
|
+
if (flushed) chunksToYield.push(flushed);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
outLines.push(formatted);
|
|
527
|
+
outBytes += (outLines.length === 1 ? 0 : 1) + lineBytes;
|
|
528
|
+
|
|
529
|
+
if (outLines.length >= maxChunkLines || outBytes >= maxChunkBytes) {
|
|
530
|
+
const flushed = flush();
|
|
531
|
+
if (flushed) chunksToYield.push(flushed);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return chunksToYield;
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
const asyncIterator = (lines as AsyncIterable<string>)[Symbol.asyncIterator];
|
|
538
|
+
if (typeof asyncIterator === "function") {
|
|
539
|
+
for await (const line of lines as AsyncIterable<string>) {
|
|
540
|
+
for (const out of pushLine(line)) {
|
|
541
|
+
yield out;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
} else {
|
|
545
|
+
for (const line of lines as Iterable<string>) {
|
|
546
|
+
for (const out of pushLine(line)) {
|
|
547
|
+
yield out;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (!sawAnyLine) {
|
|
552
|
+
// Mirror `"".split("\n")` behavior: one empty line.
|
|
553
|
+
for (const out of pushLine("")) {
|
|
554
|
+
yield out;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const last = flush();
|
|
559
|
+
if (last) yield last;
|
|
560
|
+
}
|
|
561
|
+
|
|
350
562
|
/**
|
|
351
563
|
* Parse a line reference string like `"5:abcd"` into structured form.
|
|
352
564
|
*
|
|
@@ -360,7 +572,7 @@ export function parseLineRef(ref: string): { line: number; hash: string } {
|
|
|
360
572
|
const prefixMatch = strictMatch ? null : cleaned.match(new RegExp(`^(\\d+):([0-9a-fA-F]{${HASH_LEN}})`));
|
|
361
573
|
const match = strictMatch ?? prefixMatch;
|
|
362
574
|
if (!match) {
|
|
363
|
-
throw new Error(`Invalid line reference "${ref}". Expected format "LINE:HASH" (e.g. "5:
|
|
575
|
+
throw new Error(`Invalid line reference "${ref}". Expected format "LINE:HASH" (e.g. "5:aa").`);
|
|
364
576
|
}
|
|
365
577
|
const line = Number.parseInt(match[1], 10);
|
|
366
578
|
if (line < 1) {
|
package/src/patch/index.ts
CHANGED
package/src/cli/oclif-help.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
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
|
-
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
|
-
}
|