From 7dd6ab532865c6a562e6579bbefdd62b3a0b6935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Mon, 18 May 2026 13:33:41 +0000 Subject: [PATCH] feat: --format json/yaml/table for all non-interactive commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add program-level --format option (default: json) inherited by all subcommands. json output unchanged, yaml via yaml package, table renders aligned columns for arrays, falls back to yaml for objects. Closes #328 小橘 🍊(NEKO Team) --- packages/cli-uwf/src/cli.ts | 41 ++++++++++++++++++---------------- packages/cli-uwf/src/format.ts | 35 +++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 19 deletions(-) create mode 100644 packages/cli-uwf/src/format.ts diff --git a/packages/cli-uwf/src/cli.ts b/packages/cli-uwf/src/cli.ts index e9d7013..280dc25 100755 --- a/packages/cli-uwf/src/cli.ts +++ b/packages/cli-uwf/src/cli.ts @@ -22,9 +22,11 @@ import { cmdCasWalk, } from "./commands/cas.js"; import { resolveStorageRoot } from "./store.js"; +import { type OutputFormat, formatOutput } from "./format.js"; -function writeJson(data: unknown): void { - process.stdout.write(`${JSON.stringify(data)}\n`); +function writeOutput(data: unknown): void { + const fmt = program.opts().format as OutputFormat; + process.stdout.write(`${formatOutput(data, fmt)}\n`); } function runAction(action: () => Promise): void { @@ -38,6 +40,7 @@ function runAction(action: () => Promise): void { const program = new Command(); program.name("uwf").description("Stateless workflow CLI"); +program.option("--format ", "Output format: json, yaml, table", "json"); const workflow = program.command("workflow").description("Workflow registry and CAS"); @@ -49,7 +52,7 @@ workflow const storageRoot = resolveStorageRoot(); runAction(async () => { const result = await cmdWorkflowPut(storageRoot, file); - writeJson(result); + writeOutput(result); }); }); @@ -61,7 +64,7 @@ workflow const storageRoot = resolveStorageRoot(); runAction(async () => { const result = await cmdWorkflowShow(storageRoot, id); - writeJson(result); + writeOutput(result); }); }); @@ -72,7 +75,7 @@ workflow const storageRoot = resolveStorageRoot(); runAction(async () => { const result = await cmdWorkflowList(storageRoot); - writeJson(result); + writeOutput(result); }); }); @@ -87,7 +90,7 @@ thread const storageRoot = resolveStorageRoot(); runAction(async () => { const result = await cmdThreadStart(storageRoot, workflow, opts.prompt); - writeJson(result); + writeOutput(result); }); }); @@ -101,7 +104,7 @@ thread runAction(async () => { const agentOverride = opts.agent ?? null; const result = await cmdThreadStep(storageRoot, threadId, agentOverride); - writeJson(result); + writeOutput(result); }); }); @@ -113,7 +116,7 @@ thread const storageRoot = resolveStorageRoot(); runAction(async () => { const result = await cmdThreadShow(storageRoot, threadId); - writeJson(result); + writeOutput(result); }); }); @@ -125,7 +128,7 @@ thread const storageRoot = resolveStorageRoot(); runAction(async () => { const result = await cmdThreadList(storageRoot, opts.all); - writeJson(result); + writeOutput(result); }); }); @@ -137,7 +140,7 @@ thread const storageRoot = resolveStorageRoot(); runAction(async () => { const result = await cmdThreadKill(storageRoot, threadId); - writeJson(result); + writeOutput(result); }); }); @@ -167,7 +170,7 @@ program agent: opts.agent ?? undefined, storageRoot, }); - writeJson(result); + writeOutput(result); } else if (!opts.provider && !opts.baseUrl && !opts.apiKey && !opts.model) { await cmdSetupInteractive(storageRoot); } else { @@ -187,7 +190,7 @@ cas .action((hash: string) => { const storageRoot = resolveStorageRoot(); runAction(async () => { - writeJson(await cmdCasGet(storageRoot, hash)); + writeOutput(await cmdCasGet(storageRoot, hash)); }); }); @@ -199,7 +202,7 @@ cas .action((hash: string, opts: { payload?: boolean }) => { const storageRoot = resolveStorageRoot(); runAction(async () => { - writeJson(await cmdCasCat(storageRoot, hash, opts)); + writeOutput(await cmdCasCat(storageRoot, hash, opts)); }); }); @@ -211,7 +214,7 @@ cas .action((typeHash: string, data: string) => { const storageRoot = resolveStorageRoot(); runAction(async () => { - writeJson(await cmdCasPut(storageRoot, typeHash, data)); + writeOutput(await cmdCasPut(storageRoot, typeHash, data)); }); }); @@ -222,7 +225,7 @@ cas .action((hash: string) => { const storageRoot = resolveStorageRoot(); runAction(async () => { - writeJson(await cmdCasHas(storageRoot, hash)); + writeOutput(await cmdCasHas(storageRoot, hash)); }); }); @@ -233,7 +236,7 @@ cas .action((hash: string) => { const storageRoot = resolveStorageRoot(); runAction(async () => { - writeJson(await cmdCasRefs(storageRoot, hash)); + writeOutput(await cmdCasRefs(storageRoot, hash)); }); }); @@ -244,7 +247,7 @@ cas .action((hash: string) => { const storageRoot = resolveStorageRoot(); runAction(async () => { - writeJson(await cmdCasWalk(storageRoot, hash)); + writeOutput(await cmdCasWalk(storageRoot, hash)); }); }); @@ -256,7 +259,7 @@ casSchema .action(() => { const storageRoot = resolveStorageRoot(); runAction(async () => { - writeJson(await cmdCasSchemaList(storageRoot)); + writeOutput(await cmdCasSchemaList(storageRoot)); }); }); @@ -267,7 +270,7 @@ casSchema .action((hash: string) => { const storageRoot = resolveStorageRoot(); runAction(async () => { - writeJson(await cmdCasSchemaGet(storageRoot, hash)); + writeOutput(await cmdCasSchemaGet(storageRoot, hash)); }); }); diff --git a/packages/cli-uwf/src/format.ts b/packages/cli-uwf/src/format.ts new file mode 100644 index 0000000..47d32c0 --- /dev/null +++ b/packages/cli-uwf/src/format.ts @@ -0,0 +1,35 @@ +import { stringify } from "yaml"; + +export type OutputFormat = "json" | "yaml" | "table"; + +function formatTable(data: Array>): string { + if (data.length === 0) return ""; + const keys = Object.keys(data[0]); + const widths = keys.map((k) => { + let max = k.length; + for (const row of data) { + const len = String(row[k] ?? "").length; + if (len > max) max = len; + } + return max; + }); + const header = keys.map((k, i) => k.toUpperCase().padEnd(widths[i])).join(" "); + const rows = data.map((row) => + keys.map((k, i) => String(row[k] ?? "").padEnd(widths[i])).join(" "), + ); + return [header, ...rows].join("\n"); +} + +export function formatOutput(data: unknown, format: OutputFormat): string { + switch (format) { + case "json": + return JSON.stringify(data); + case "yaml": + return stringify(data).trimEnd(); + case "table": + if (Array.isArray(data) && data.length > 0 && typeof data[0] === "object" && data[0] !== null) { + return formatTable(data as Array>); + } + return stringify(data).trimEnd(); + } +}