mirror of
https://github.com/qwibitai/nanoclaw.git
synced 2026-07-09 18:57:08 +08:00
763a3f757b
Every ncl verb now declares its args: strict validation with fix-carrying error messages, generated per-verb --help, and multi-word custom-op key resolution (spaces vs dashes). Longest-prefix command fallback keeps dashed positional ids intact. Responses gain a server-rendered 'human' frame field (formatHuman hook): the host renders tables once and every client prints them verbatim — container agents cannot import host formatters, so this is how they get aligned output instead of a raw column dump. --help responses carry 'human' for clean multi-line rendering. Robustness fixes riding along: - generic list returns newest rows first, so the LIMIT no longer hides recent rows - stdout flushed before exit — >64KB responses were silently truncated Additive only: new response-frame fields, no schema changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
119 lines
3.2 KiB
TypeScript
119 lines
3.2 KiB
TypeScript
/**
|
|
* `ncl` binary entry point.
|
|
*
|
|
* Parses argv, builds a request frame, sends it via the picked transport,
|
|
* formats the response, exits non-zero on error.
|
|
*
|
|
* Usage:
|
|
* ncl <resource> <verb> [target] [--key value ...] [--json]
|
|
*
|
|
* Examples:
|
|
* ncl groups list
|
|
* ncl groups get abc123
|
|
* ncl groups create --name foo --folder bar
|
|
* ncl groups update abc123 --name baz
|
|
* ncl help
|
|
* ncl groups help
|
|
*/
|
|
import { randomUUID } from 'crypto';
|
|
|
|
import { formatResponse } from './format.js';
|
|
import type { RequestFrame } from './frame.js';
|
|
import { SocketTransport } from './socket-client.js';
|
|
import type { Transport } from './transport.js';
|
|
import { formatTransportError } from './transport-errors.js';
|
|
|
|
async function main(): Promise<void> {
|
|
const argv = process.argv.slice(2);
|
|
|
|
if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h') {
|
|
printUsage();
|
|
process.exit(0);
|
|
}
|
|
|
|
const { command, args, json } = parseArgv(argv);
|
|
const req: RequestFrame = { id: randomUUID(), command, args };
|
|
const transport: Transport = pickTransport();
|
|
|
|
let res;
|
|
try {
|
|
res = await transport.sendFrame(req);
|
|
} catch (e) {
|
|
process.stderr.write(formatTransportError(e));
|
|
process.exit(2);
|
|
}
|
|
|
|
const output =
|
|
!json && res.ok && res.human !== undefined
|
|
? res.human + '\n' // server-rendered view — print verbatim
|
|
: formatResponse(res, json ? 'json' : 'human');
|
|
// Exit only after stdout drains: process.exit() discards buffered pipe
|
|
// writes, silently truncating any response past the 64KB pipe buffer
|
|
// (bit `ncl sessions list --json` at scale).
|
|
process.stdout.write(output, () => process.exit(res.ok ? 0 : 1));
|
|
}
|
|
|
|
function pickTransport(): Transport {
|
|
return new SocketTransport();
|
|
}
|
|
|
|
function parseArgv(argv: string[]): {
|
|
command: string;
|
|
args: Record<string, unknown>;
|
|
json: boolean;
|
|
} {
|
|
const positional: string[] = [];
|
|
const args: Record<string, unknown> = {};
|
|
let json = false;
|
|
|
|
for (let i = 0; i < argv.length; i++) {
|
|
const a = argv[i];
|
|
if (a === '--json') {
|
|
json = true;
|
|
continue;
|
|
}
|
|
if (a.startsWith('--')) {
|
|
const key = a.slice(2);
|
|
const next = argv[i + 1];
|
|
if (next === undefined || next.startsWith('--')) {
|
|
args[key] = true;
|
|
} else {
|
|
args[key] = next;
|
|
i++;
|
|
}
|
|
continue;
|
|
}
|
|
positional.push(a);
|
|
}
|
|
|
|
if (positional.length === 0) {
|
|
process.stderr.write('ncl: missing command\n');
|
|
printUsage();
|
|
process.exit(2);
|
|
}
|
|
|
|
// Join all positionals with dashes to form the command name.
|
|
// If the full name isn't a command, the dispatcher will try trimming
|
|
// the last segment and using it as the target ID (e.g. `groups get abc`
|
|
// → command "groups-get", id "abc").
|
|
const command = positional.join('-');
|
|
|
|
return { command, args, json };
|
|
}
|
|
|
|
function printUsage(): void {
|
|
process.stdout.write(
|
|
[
|
|
'Usage: ncl <resource> <verb> [target] [--key value ...] [--json]',
|
|
'',
|
|
'Run `ncl help` to list available resources and commands.',
|
|
'',
|
|
].join('\n'),
|
|
);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
process.stderr.write(`ncl: unexpected error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
process.exit(2);
|
|
});
|