laohuang 0.8.4 → 0.8.5
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/README.md +24 -2
- package/dist/bin.js +109 -78
- package/dist/bin.js.map +4 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,25 @@ A minimal coding-agent CLI for local software work.
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Recommended standalone install (bundled Node.js; Bash required):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
curl -fsSL https://github.com/hxr223/laoHuangCode/releases/latest/download/install.sh | sh
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
On Windows, install Git for Windows first, then run in PowerShell:
|
|
14
|
+
|
|
15
|
+
```powershell
|
|
16
|
+
irm https://github.com/hxr223/laoHuangCode/releases/latest/download/install.ps1 | iex
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The installer verifies the download and startup, then configures the user PATH.
|
|
20
|
+
Open a new terminal or apply the PATH command printed by the installer.
|
|
21
|
+
Standalone packages target macOS, glibc Linux and Windows on x64 and ARM64.
|
|
22
|
+
See [installation details](https://github.com/hxr223/laoHuangCode/blob/main/INSTALLING.md).
|
|
23
|
+
|
|
24
|
+
Alternatively, install with npm (Node.js >=22.19.0 and Bash required; npm's
|
|
25
|
+
global executable directory must already be on PATH):
|
|
8
26
|
|
|
9
27
|
```bash
|
|
10
28
|
npm install -g laohuang
|
|
@@ -38,7 +56,11 @@ If no models are available, the CLI points to `/login`.
|
|
|
38
56
|
directly, and `/apikey` remains a compatibility alias. Switching models affects
|
|
39
57
|
only the current session, not the default profile.
|
|
40
58
|
|
|
41
|
-
`laohuang update`
|
|
59
|
+
For standalone installations, `laohuang update` reruns the bundled installer
|
|
60
|
+
against the latest GitHub release, verifies it before switching the launcher,
|
|
61
|
+
and retains the previous version. Restart laohuang after updating.
|
|
62
|
+
|
|
63
|
+
For npm installations, `laohuang update` updates the running installation to the latest stable
|
|
42
64
|
release and verifies its installed version. Restart the CLI after updating.
|
|
43
65
|
It supports global npm installations and direct project or workspace npm
|
|
44
66
|
dependencies; local updates change the owning manifest and lockfile while
|
package/dist/bin.js
CHANGED
|
@@ -12916,6 +12916,7 @@ var InteractiveTerminalLoop = class {
|
|
|
12916
12916
|
input?.on("end", onEnd);
|
|
12917
12917
|
this.#wakeupEnabled = true;
|
|
12918
12918
|
try {
|
|
12919
|
+
input?.resume?.();
|
|
12919
12920
|
if (!this.#exitRequested) {
|
|
12920
12921
|
await new Promise((resolve8) => {
|
|
12921
12922
|
this.#exitResolve = resolve8;
|
|
@@ -18393,8 +18394,110 @@ function createMcpCommand(runtime, presenter) {
|
|
|
18393
18394
|
};
|
|
18394
18395
|
}
|
|
18395
18396
|
|
|
18397
|
+
// src/stdin-prompts.ts
|
|
18398
|
+
import { StringDecoder as StringDecoder2 } from "node:string_decoder";
|
|
18399
|
+
var StdinPrompts = class {
|
|
18400
|
+
#input;
|
|
18401
|
+
#active = false;
|
|
18402
|
+
#skipLf = false;
|
|
18403
|
+
constructor(input) {
|
|
18404
|
+
this.#input = input;
|
|
18405
|
+
}
|
|
18406
|
+
async prompt(message, output, secret = false) {
|
|
18407
|
+
const input = this.#input;
|
|
18408
|
+
if (this.#active || input.listenerCount("data") > 0 || input.listenerCount("readable") > 0) {
|
|
18409
|
+
throw new Error("stdin is already in use by another input reader");
|
|
18410
|
+
}
|
|
18411
|
+
if (input.errored) throw input.errored;
|
|
18412
|
+
if (input.readableEnded || input.destroyed) throw new PromptEofError();
|
|
18413
|
+
this.#active = true;
|
|
18414
|
+
const raw = Boolean(secret && input.isTTY && input.setRawMode);
|
|
18415
|
+
return new Promise((resolve8, reject) => {
|
|
18416
|
+
const decoder = new StringDecoder2("utf8");
|
|
18417
|
+
let answer = "";
|
|
18418
|
+
let settled = false;
|
|
18419
|
+
let restoreRawMode;
|
|
18420
|
+
const finish = (error) => {
|
|
18421
|
+
if (settled) return;
|
|
18422
|
+
settled = true;
|
|
18423
|
+
input.pause();
|
|
18424
|
+
input.removeListener("data", onData);
|
|
18425
|
+
input.removeListener("end", onEnd);
|
|
18426
|
+
input.removeListener("close", onClose);
|
|
18427
|
+
input.removeListener("error", onError);
|
|
18428
|
+
process.removeListener("SIGINT", onInterrupt);
|
|
18429
|
+
try {
|
|
18430
|
+
restoreRawMode?.();
|
|
18431
|
+
} catch (restoreError) {
|
|
18432
|
+
error = error === void 0 ? restoreError : new AggregateError([error, restoreError], "Input and terminal restoration failed");
|
|
18433
|
+
}
|
|
18434
|
+
this.#active = false;
|
|
18435
|
+
try {
|
|
18436
|
+
if (restoreRawMode) output.write("\n");
|
|
18437
|
+
} catch (outputError) {
|
|
18438
|
+
error ??= outputError;
|
|
18439
|
+
}
|
|
18440
|
+
if (error !== void 0) reject(error);
|
|
18441
|
+
else resolve8(answer);
|
|
18442
|
+
};
|
|
18443
|
+
const onData = (data) => {
|
|
18444
|
+
try {
|
|
18445
|
+
const bytes = typeof data === "string" ? Buffer.from(data) : data;
|
|
18446
|
+
for (let index = 0; index < bytes.length; index++) {
|
|
18447
|
+
const value = bytes[index];
|
|
18448
|
+
if (this.#skipLf) {
|
|
18449
|
+
this.#skipLf = false;
|
|
18450
|
+
if (value === 10) continue;
|
|
18451
|
+
}
|
|
18452
|
+
if (raw && (value === 3 || value === 4)) {
|
|
18453
|
+
finish(value === 3 ? new PromptCancelledError() : new PromptEofError());
|
|
18454
|
+
return;
|
|
18455
|
+
}
|
|
18456
|
+
if (value === 10 || value === 13) {
|
|
18457
|
+
answer += decoder.end();
|
|
18458
|
+
if (value === 13) {
|
|
18459
|
+
if (bytes[index + 1] === 10) index++;
|
|
18460
|
+
else this.#skipLf = index + 1 === bytes.length;
|
|
18461
|
+
}
|
|
18462
|
+
input.pause();
|
|
18463
|
+
if (index + 1 < bytes.length) input.unshift(bytes.subarray(index + 1));
|
|
18464
|
+
finish();
|
|
18465
|
+
return;
|
|
18466
|
+
}
|
|
18467
|
+
if (raw && (value === 127 || value === 8)) {
|
|
18468
|
+
answer = [...answer].slice(0, -1).join("");
|
|
18469
|
+
} else {
|
|
18470
|
+
answer += decoder.write(bytes.subarray(index, index + 1));
|
|
18471
|
+
}
|
|
18472
|
+
}
|
|
18473
|
+
} catch (error) {
|
|
18474
|
+
finish(error);
|
|
18475
|
+
}
|
|
18476
|
+
};
|
|
18477
|
+
const onEnd = () => {
|
|
18478
|
+
answer += decoder.end();
|
|
18479
|
+
finish(!raw && answer ? void 0 : new PromptEofError());
|
|
18480
|
+
};
|
|
18481
|
+
const onClose = () => finish(new PromptEofError());
|
|
18482
|
+
const onError = (error) => finish(error);
|
|
18483
|
+
const onInterrupt = () => finish(new PromptCancelledError());
|
|
18484
|
+
try {
|
|
18485
|
+
if (raw) restoreRawMode = enterTerminalRawMode(input);
|
|
18486
|
+
if (message) output.write(message);
|
|
18487
|
+
if (input.isTTY) process.on("SIGINT", onInterrupt);
|
|
18488
|
+
input.on("data", onData);
|
|
18489
|
+
input.on("end", onEnd);
|
|
18490
|
+
input.on("close", onClose);
|
|
18491
|
+
input.on("error", onError);
|
|
18492
|
+
input.resume();
|
|
18493
|
+
} catch (error) {
|
|
18494
|
+
finish(error);
|
|
18495
|
+
}
|
|
18496
|
+
});
|
|
18497
|
+
}
|
|
18498
|
+
};
|
|
18499
|
+
|
|
18396
18500
|
// src/repl.ts
|
|
18397
|
-
import { readSync as readSync2 } from "node:fs";
|
|
18398
18501
|
function errorMessage11(error) {
|
|
18399
18502
|
return error instanceof Error ? error.message : String(error);
|
|
18400
18503
|
}
|
|
@@ -18404,86 +18507,14 @@ function isEofError(error) {
|
|
|
18404
18507
|
function isInterruptedError(error) {
|
|
18405
18508
|
return error instanceof PromptCancelledError || error instanceof Error && (error.name === "PromptCancelledError" || error.name === "KeyboardInterrupt");
|
|
18406
18509
|
}
|
|
18407
|
-
var
|
|
18408
|
-
function readLineFromStdin() {
|
|
18409
|
-
const chunk = Buffer.alloc(4096);
|
|
18410
|
-
for (; ; ) {
|
|
18411
|
-
const newline = stdinBuffer.pending.indexOf("\n");
|
|
18412
|
-
if (newline !== -1) {
|
|
18413
|
-
const line2 = stdinBuffer.pending.slice(0, newline);
|
|
18414
|
-
stdinBuffer.pending = stdinBuffer.pending.slice(newline + 1);
|
|
18415
|
-
return line2.endsWith("\r") ? line2.slice(0, -1) : line2;
|
|
18416
|
-
}
|
|
18417
|
-
if (stdinBuffer.eof) {
|
|
18418
|
-
const rest = stdinBuffer.pending;
|
|
18419
|
-
stdinBuffer.pending = "";
|
|
18420
|
-
if (rest) {
|
|
18421
|
-
return rest;
|
|
18422
|
-
}
|
|
18423
|
-
throw new PromptEofError();
|
|
18424
|
-
}
|
|
18425
|
-
let bytesRead = 0;
|
|
18426
|
-
try {
|
|
18427
|
-
bytesRead = readSync2(0, chunk, 0, chunk.length, null);
|
|
18428
|
-
} catch (error) {
|
|
18429
|
-
if (error.code === "EAGAIN") {
|
|
18430
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
|
|
18431
|
-
continue;
|
|
18432
|
-
}
|
|
18433
|
-
throw error;
|
|
18434
|
-
}
|
|
18435
|
-
if (bytesRead === 0) {
|
|
18436
|
-
stdinBuffer.eof = true;
|
|
18437
|
-
continue;
|
|
18438
|
-
}
|
|
18439
|
-
stdinBuffer.pending += chunk.toString("utf8", 0, bytesRead);
|
|
18440
|
-
}
|
|
18441
|
-
}
|
|
18510
|
+
var stdinPrompts;
|
|
18442
18511
|
function defaultInputFn(prompt) {
|
|
18443
|
-
|
|
18444
|
-
|
|
18445
|
-
}
|
|
18446
|
-
return readLineFromStdin();
|
|
18512
|
+
stdinPrompts ??= new StdinPrompts(process.stdin);
|
|
18513
|
+
return stdinPrompts.prompt(prompt, process.stdout);
|
|
18447
18514
|
}
|
|
18448
18515
|
function defaultSecretInputFn(prompt) {
|
|
18449
|
-
|
|
18450
|
-
|
|
18451
|
-
}
|
|
18452
|
-
if (!process.stdin.isTTY || typeof process.stdin.setRawMode !== "function") {
|
|
18453
|
-
return readLineFromStdin();
|
|
18454
|
-
}
|
|
18455
|
-
const byte = Buffer.alloc(1);
|
|
18456
|
-
let answer = "";
|
|
18457
|
-
const restoreRawMode = enterTerminalRawMode(process.stdin);
|
|
18458
|
-
try {
|
|
18459
|
-
for (; ; ) {
|
|
18460
|
-
const bytesRead = readSync2(0, byte, 0, 1, null);
|
|
18461
|
-
if (bytesRead === 0) {
|
|
18462
|
-
throw new PromptEofError();
|
|
18463
|
-
}
|
|
18464
|
-
const value = byte[0];
|
|
18465
|
-
if (value === 3) {
|
|
18466
|
-
process.stderr.write("\n");
|
|
18467
|
-
throw new PromptCancelledError();
|
|
18468
|
-
}
|
|
18469
|
-
if (value === 4) {
|
|
18470
|
-
process.stderr.write("\n");
|
|
18471
|
-
throw new PromptEofError();
|
|
18472
|
-
}
|
|
18473
|
-
if (value === 10 || value === 13) {
|
|
18474
|
-
break;
|
|
18475
|
-
}
|
|
18476
|
-
if (value === 127 || value === 8) {
|
|
18477
|
-
answer = [...answer].slice(0, -1).join("");
|
|
18478
|
-
continue;
|
|
18479
|
-
}
|
|
18480
|
-
answer += byte.toString("utf8", 0, 1);
|
|
18481
|
-
}
|
|
18482
|
-
} finally {
|
|
18483
|
-
restoreRawMode();
|
|
18484
|
-
}
|
|
18485
|
-
process.stderr.write("\n");
|
|
18486
|
-
return answer;
|
|
18516
|
+
stdinPrompts ??= new StdinPrompts(process.stdin);
|
|
18517
|
+
return stdinPrompts.prompt(prompt, process.stderr, true);
|
|
18487
18518
|
}
|
|
18488
18519
|
function supportsTerminalUI(options = {}) {
|
|
18489
18520
|
if (options.inputFn !== void 0 || options.outputFn !== void 0) {
|