From 201abf98cedc2ea50706093240bc96366e32ad40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E6=9C=88?= Date: Fri, 8 May 2026 09:14:40 +0800 Subject: [PATCH] =?UTF-8?q?refactor(cli):=20Phase=204=20cleanup=20?= =?UTF-8?q?=E2=80=94=20dedup,=20extract,=20deprecate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - bundle-store.ts: remove private pathExists, import from fs-utils - thread-scan.ts: extract parseFirstJsonLineObject helper, dedup first-line parsing - commands/workflow/add-argv.ts: extract parseAddArgv from add.ts - commands/thread/fork-argv.ts: extract parseForkArgv from fork.ts - commands/cas/*.ts: remove unused _threadId params from cmdCas* functions - cli-dispatch.ts: add deprecation warning to help command - commands/init/templates.ts: extract template strings from template.ts - cli-color.ts: extract shouldUseColor, highlightLiveRole, dimGreyLine from live.ts - 242 tests pass, biome clean Closes #97 --- .../cli-workflow/__tests__/bundle-fixture.ts | 2 +- .../cli-workflow/__tests__/commands.test.ts | 10 +- .../cli-workflow/__tests__/thread-cli.test.ts | 2 +- packages/cli-workflow/src/bundle-store.ts | 11 +- packages/cli-workflow/src/cli-color.ts | 17 +++ packages/cli-workflow/src/cli-dispatch.ts | 1 + .../cli-workflow/src/commands/cas/dispatch.ts | 8 +- packages/cli-workflow/src/commands/cas/get.ts | 1 - .../cli-workflow/src/commands/cas/list.ts | 5 +- packages/cli-workflow/src/commands/cas/put.ts | 1 - packages/cli-workflow/src/commands/cas/rm.ts | 6 +- .../src/commands/init/template.ts | 110 ++---------------- .../src/commands/init/templates.ts | 101 ++++++++++++++++ .../src/commands/thread/dispatch.ts | 3 +- .../src/commands/thread/fork-argv.ts | 28 +++++ .../cli-workflow/src/commands/thread/fork.ts | 27 ----- .../cli-workflow/src/commands/thread/index.ts | 3 +- .../cli-workflow/src/commands/thread/live.ts | 19 +-- .../src/commands/workflow/add-argv.ts | 77 ++++++++++++ .../cli-workflow/src/commands/workflow/add.ts | 76 +----------- .../src/commands/workflow/dispatch.ts | 3 +- .../src/commands/workflow/index.ts | 6 +- packages/cli-workflow/src/thread-scan.ts | 47 ++++---- 23 files changed, 282 insertions(+), 282 deletions(-) create mode 100644 packages/cli-workflow/src/cli-color.ts create mode 100644 packages/cli-workflow/src/commands/init/templates.ts create mode 100644 packages/cli-workflow/src/commands/thread/fork-argv.ts create mode 100644 packages/cli-workflow/src/commands/workflow/add-argv.ts diff --git a/packages/cli-workflow/__tests__/bundle-fixture.ts b/packages/cli-workflow/__tests__/bundle-fixture.ts index 7e74d07..5ba1fbc 100644 --- a/packages/cli-workflow/__tests__/bundle-fixture.ts +++ b/packages/cli-workflow/__tests__/bundle-fixture.ts @@ -1,4 +1,4 @@ -import type { ParsedAddArgv } from "../src/commands/workflow/add.js"; +import type { ParsedAddArgv } from "../src/commands/workflow/add-argv.js"; export function addCliArgs(name: string, filePath: string): ParsedAddArgv { return { name, filePath, typesPath: null }; diff --git a/packages/cli-workflow/__tests__/commands.test.ts b/packages/cli-workflow/__tests__/commands.test.ts index 0b5135c..f008661 100644 --- a/packages/cli-workflow/__tests__/commands.test.ts +++ b/packages/cli-workflow/__tests__/commands.test.ts @@ -402,7 +402,7 @@ export const run = async function* (input, options) { }); test("cas put/get/list/rm use global cas dir (thread id not required for storage)", async () => { - const put = await cmdCasPut(storageRoot, "nonexistent-thread-id", "phase doc"); + const put = await cmdCasPut(storageRoot, "phase doc"); expect(put.ok).toBe(true); if (!put.ok) { return; @@ -411,24 +411,24 @@ export const run = async function* (input, options) { const blobPath = join(getGlobalCasDir(storageRoot), `${hash}.txt`); expect(await readFile(blobPath, "utf8")).toBe("phase doc"); - const got = await cmdCasGet(storageRoot, "other-thread", hash); + const got = await cmdCasGet(storageRoot, hash); expect(got.ok).toBe(true); if (!got.ok) { return; } expect(got.value).toBe("phase doc"); - const listed = await cmdCasList(storageRoot, "another-thread"); + const listed = await cmdCasList(storageRoot); expect(listed.ok).toBe(true); if (!listed.ok) { return; } expect(listed.value).toContain(hash); - const removed = await cmdCasRm(storageRoot, "rm-thread", hash); + const removed = await cmdCasRm(storageRoot, hash); expect(removed.ok).toBe(true); - const missing = await cmdCasGet(storageRoot, "after-rm", hash); + const missing = await cmdCasGet(storageRoot, hash); expect(missing.ok).toBe(false); }); diff --git a/packages/cli-workflow/__tests__/thread-cli.test.ts b/packages/cli-workflow/__tests__/thread-cli.test.ts index f6c7d3d..6be99ca 100644 --- a/packages/cli-workflow/__tests__/thread-cli.test.ts +++ b/packages/cli-workflow/__tests__/thread-cli.test.ts @@ -232,7 +232,7 @@ describe("cli thread commands", () => { const runningPath = join(dirname(dataPath), `${threadId}.running`); await waitUntilRunningFileAbsent(runningPath, 120); - const put = await cmdCasPut(storageRoot, threadId, "keep-after-thread-rm"); + const put = await cmdCasPut(storageRoot, "keep-after-thread-rm"); expect(put.ok).toBe(true); if (!put.ok) { return; diff --git a/packages/cli-workflow/src/bundle-store.ts b/packages/cli-workflow/src/bundle-store.ts index 743c2fb..83d9af2 100644 --- a/packages/cli-workflow/src/bundle-store.ts +++ b/packages/cli-workflow/src/bundle-store.ts @@ -1,16 +1,9 @@ -import { mkdir, readFile, stat, writeFile } from "node:fs/promises"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { err, ok, type Result } from "@uncaged/workflow"; -async function pathExists(path: string): Promise { - try { - await stat(path); - return true; - } catch { - return false; - } -} +import { pathExists } from "./fs-utils.js"; export type BundleFileSource = { kind: "text"; text: string } | { kind: "path"; path: string }; diff --git a/packages/cli-workflow/src/cli-color.ts b/packages/cli-workflow/src/cli-color.ts new file mode 100644 index 0000000..095d718 --- /dev/null +++ b/packages/cli-workflow/src/cli-color.ts @@ -0,0 +1,17 @@ +export function shouldUseColor(): boolean { + return process.stdout.isTTY === true && process.env.NO_COLOR === undefined; +} + +export function highlightLiveRole(name: string): string { + if (!shouldUseColor()) { + return name; + } + return `\x1b[1m\x1b[36m${name}\x1b[0m`; +} + +export function dimGreyLine(line: string): string { + if (!shouldUseColor()) { + return line; + } + return `\x1b[2m\x1b[90m${line}\x1b[0m`; +} diff --git a/packages/cli-workflow/src/cli-dispatch.ts b/packages/cli-workflow/src/cli-dispatch.ts index eae2aa5..8a002c2 100644 --- a/packages/cli-workflow/src/cli-dispatch.ts +++ b/packages/cli-workflow/src/cli-dispatch.ts @@ -86,6 +86,7 @@ async function dispatchSkill(_storageRoot: string, argv: string[]): Promise { + printCliWarn('⚠ "help" is deprecated, use "skill" instead'); const skillIdx = argv.indexOf("--skill"); if (skillIdx !== -1) { return showSkillDocOrIndex(argv[skillIdx + 1]); diff --git a/packages/cli-workflow/src/commands/cas/dispatch.ts b/packages/cli-workflow/src/commands/cas/dispatch.ts index fd5ff6c..a807a86 100644 --- a/packages/cli-workflow/src/commands/cas/dispatch.ts +++ b/packages/cli-workflow/src/commands/cas/dispatch.ts @@ -36,7 +36,7 @@ export async function dispatchCasGet(storageRoot: string, rest: string[]): Promi printCliError(`${usageText()}\n\nerror: cas get requires `); return 1; } - const result = await cmdCasGet(storageRoot, threadId, hash); + const result = await cmdCasGet(storageRoot, hash); if (!result.ok) { printCliError(result.error); return 1; @@ -52,7 +52,7 @@ export async function dispatchCasPut(storageRoot: string, rest: string[]): Promi printCliError(`${usageText()}\n\nerror: cas put requires `); return 1; } - const result = await cmdCasPut(storageRoot, threadId, content); + const result = await cmdCasPut(storageRoot, content); if (!result.ok) { printCliError(result.error); return 1; @@ -67,7 +67,7 @@ export async function dispatchCasList(storageRoot: string, rest: string[]): Prom printCliError(`${usageText()}\n\nerror: cas list requires `); return 1; } - const result = await cmdCasList(storageRoot, threadId); + const result = await cmdCasList(storageRoot); if (!result.ok) { printCliError(result.error); return 1; @@ -85,7 +85,7 @@ export async function dispatchCasRm(storageRoot: string, rest: string[]): Promis printCliError(`${usageText()}\n\nerror: cas rm requires `); return 1; } - const result = await cmdCasRm(storageRoot, threadId, hash); + const result = await cmdCasRm(storageRoot, hash); if (!result.ok) { printCliError(result.error); return 1; diff --git a/packages/cli-workflow/src/commands/cas/get.ts b/packages/cli-workflow/src/commands/cas/get.ts index 3b45e67..2ecdccd 100644 --- a/packages/cli-workflow/src/commands/cas/get.ts +++ b/packages/cli-workflow/src/commands/cas/get.ts @@ -2,7 +2,6 @@ import { createCasStore, err, getGlobalCasDir, ok, type Result } from "@uncaged/ export async function cmdCasGet( storageRoot: string, - _threadId: string, hash: string, ): Promise> { const cas = createCasStore(getGlobalCasDir(storageRoot)); diff --git a/packages/cli-workflow/src/commands/cas/list.ts b/packages/cli-workflow/src/commands/cas/list.ts index ae0694b..fbfafad 100644 --- a/packages/cli-workflow/src/commands/cas/list.ts +++ b/packages/cli-workflow/src/commands/cas/list.ts @@ -1,9 +1,6 @@ import { createCasStore, getGlobalCasDir, ok, type Result } from "@uncaged/workflow"; -export async function cmdCasList( - storageRoot: string, - _threadId: string, -): Promise> { +export async function cmdCasList(storageRoot: string): Promise> { const cas = createCasStore(getGlobalCasDir(storageRoot)); const hashes = await cas.list(); return ok(hashes); diff --git a/packages/cli-workflow/src/commands/cas/put.ts b/packages/cli-workflow/src/commands/cas/put.ts index 2fa0e25..a6745e5 100644 --- a/packages/cli-workflow/src/commands/cas/put.ts +++ b/packages/cli-workflow/src/commands/cas/put.ts @@ -2,7 +2,6 @@ import { createCasStore, getGlobalCasDir, ok, type Result } from "@uncaged/workf export async function cmdCasPut( storageRoot: string, - _threadId: string, content: string, ): Promise> { const cas = createCasStore(getGlobalCasDir(storageRoot)); diff --git a/packages/cli-workflow/src/commands/cas/rm.ts b/packages/cli-workflow/src/commands/cas/rm.ts index 5d85a3b..aec6980 100644 --- a/packages/cli-workflow/src/commands/cas/rm.ts +++ b/packages/cli-workflow/src/commands/cas/rm.ts @@ -1,10 +1,6 @@ import { createCasStore, getGlobalCasDir, ok, type Result } from "@uncaged/workflow"; -export async function cmdCasRm( - storageRoot: string, - _threadId: string, - hash: string, -): Promise> { +export async function cmdCasRm(storageRoot: string, hash: string): Promise> { const cas = createCasStore(getGlobalCasDir(storageRoot)); await cas.delete(hash); return ok(undefined); diff --git a/packages/cli-workflow/src/commands/init/template.ts b/packages/cli-workflow/src/commands/init/template.ts index 01b7a44..2a422e2 100644 --- a/packages/cli-workflow/src/commands/init/template.ts +++ b/packages/cli-workflow/src/commands/init/template.ts @@ -5,6 +5,14 @@ import { err, ok, type Result } from "@uncaged/workflow"; import { pathExists } from "../../fs-utils.js"; +import { + templateIndexTs, + templateModeratorTs, + templatePackageJson, + templateRolesTs, + templateTsconfigJson, +} from "./templates.js"; + export type CmdInitTemplateSuccess = { templatePath: string; }; @@ -67,108 +75,6 @@ async function findWorkflowWorkspaceRoot(startDir: string): Promise = { - description: "Says hello — replace with your first role.", - systemPrompt: "You are a helpful assistant. Reply with one short friendly sentence.", - extractPrompt: "Extract the assistant's greeting as message.", - schema: greeterMetaSchema, - extractRefs: null, -}; -`; -} - -function templateModeratorTs(): string { - return `import { END, type Moderator, type ModeratorContext } from "@uncaged/workflow"; - -import type { HelloTemplateMeta } from "./roles.js"; - -export const helloTemplateModerator: Moderator = ( - ctx: ModeratorContext, -) => { - if (ctx.steps.length === 0) { - return "greeter"; - } - return END; -}; -`; -} - -function templateIndexTs(): string { - return `import type { WorkflowDefinition } from "@uncaged/workflow"; - -import { helloTemplateModerator } from "./moderator.js"; -import { - HELLO_TEMPLATE_DESCRIPTION, - type HelloTemplateMeta, - greeterRole, -} from "./roles.js"; - -export { - HELLO_TEMPLATE_DESCRIPTION, - type HelloTemplateMeta, - greeterRole, -} from "./roles.js"; -export { helloTemplateModerator } from "./moderator.js"; - -export const helloTemplateWorkflowDefinition: WorkflowDefinition = { - description: HELLO_TEMPLATE_DESCRIPTION, - roles: { - greeter: greeterRole, - }, - moderator: helloTemplateModerator, -}; -`; -} - export async function cmdInitTemplate( startDir: string, templateName: string, diff --git a/packages/cli-workflow/src/commands/init/templates.ts b/packages/cli-workflow/src/commands/init/templates.ts new file mode 100644 index 0000000..89d2809 --- /dev/null +++ b/packages/cli-workflow/src/commands/init/templates.ts @@ -0,0 +1,101 @@ +export function templatePackageJson(templateName: string): string { + return `${JSON.stringify( + { + name: `template-${templateName}`, + version: "0.0.0", + private: true, + type: "module", + dependencies: { + "@uncaged/workflow": "^0.1.0", + zod: "^4.0.0", + }, + }, + null, + 2, + )}\n`; +} + +export function templateTsconfigJson(): string { + return `${JSON.stringify( + { + extends: "../../tsconfig.json", + compilerOptions: { + rootDir: "src", + outDir: "dist", + }, + include: ["src/**/*.ts"], + }, + null, + 2, + )}\n`; +} + +export function templateRolesTs(): string { + return `import type { RoleDefinition } from "@uncaged/workflow"; +import * as z from "zod/v4"; + +export const HELLO_TEMPLATE_DESCRIPTION = + "Minimal starter template: one greeter role, then END."; + +export type HelloTemplateMeta = { + greeter: { + message: string; + }; +}; + +const greeterMetaSchema = z.object({ + message: z.string(), +}); + +export const greeterRole: RoleDefinition = { + description: "Says hello — replace with your first role.", + systemPrompt: "You are a helpful assistant. Reply with one short friendly sentence.", + extractPrompt: "Extract the assistant's greeting as message.", + schema: greeterMetaSchema, + extractRefs: null, +}; +`; +} + +export function templateModeratorTs(): string { + return `import { END, type Moderator, type ModeratorContext } from "@uncaged/workflow"; + +import type { HelloTemplateMeta } from "./roles.js"; + +export const helloTemplateModerator: Moderator = ( + ctx: ModeratorContext, +) => { + if (ctx.steps.length === 0) { + return "greeter"; + } + return END; +}; +`; +} + +export function templateIndexTs(): string { + return `import type { WorkflowDefinition } from "@uncaged/workflow"; + +import { helloTemplateModerator } from "./moderator.js"; +import { + HELLO_TEMPLATE_DESCRIPTION, + type HelloTemplateMeta, + greeterRole, +} from "./roles.js"; + +export { + HELLO_TEMPLATE_DESCRIPTION, + type HelloTemplateMeta, + greeterRole, +} from "./roles.js"; +export { helloTemplateModerator } from "./moderator.js"; + +export const helloTemplateWorkflowDefinition: WorkflowDefinition = { + description: HELLO_TEMPLATE_DESCRIPTION, + roles: { + greeter: greeterRole, + }, + moderator: helloTemplateModerator, +}; +`; +} diff --git a/packages/cli-workflow/src/commands/thread/dispatch.ts b/packages/cli-workflow/src/commands/thread/dispatch.ts index 9d2fb9a..ff00975 100644 --- a/packages/cli-workflow/src/commands/thread/dispatch.ts +++ b/packages/cli-workflow/src/commands/thread/dispatch.ts @@ -5,7 +5,8 @@ import { getCommandGroupsForUsage } from "../../cli-usage-context.js"; import { parseLiveArgv } from "../../live-argv.js"; import { parseRunArgv } from "../../run-argv.js"; import { cmdKill, cmdPause, cmdResume } from "./control.js"; -import { cmdFork, parseForkArgv } from "./fork.js"; +import { cmdFork } from "./fork.js"; +import { parseForkArgv } from "./fork-argv.js"; import { cmdThreads } from "./list.js"; import { cmdLive } from "./live.js"; import { cmdPs } from "./ps.js"; diff --git a/packages/cli-workflow/src/commands/thread/fork-argv.ts b/packages/cli-workflow/src/commands/thread/fork-argv.ts new file mode 100644 index 0000000..233b66a --- /dev/null +++ b/packages/cli-workflow/src/commands/thread/fork-argv.ts @@ -0,0 +1,28 @@ +import { err, ok, type Result } from "@uncaged/workflow"; + +export function parseForkArgv( + argv: string[], +): Result<{ threadId: string; fromRole: string | null }, string> { + if (argv.length === 0) { + return err("fork requires "); + } + const threadId = argv[0]; + if (threadId === undefined || threadId === "") { + return err("fork requires "); + } + let fromRole: string | null = null; + for (let i = 1; i < argv.length; i++) { + const a = argv[i]; + if (a === "--from-role") { + const r = argv[i + 1]; + if (r === undefined || r === "") { + return err("--from-role requires a role name"); + } + fromRole = r; + i++; + continue; + } + return err(`unexpected argument: ${a}`); + } + return ok({ threadId, fromRole }); +} diff --git a/packages/cli-workflow/src/commands/thread/fork.ts b/packages/cli-workflow/src/commands/thread/fork.ts index a99333a..8003d66 100644 --- a/packages/cli-workflow/src/commands/thread/fork.ts +++ b/packages/cli-workflow/src/commands/thread/fork.ts @@ -6,33 +6,6 @@ import { pathExists, readTextFileIfExists } from "../../fs-utils.js"; import { resolveThreadDataPath } from "../../thread-scan.js"; import { ensureWorkerForHash, sendWorkerTcpCommand } from "../../worker-spawn.js"; -export function parseForkArgv( - argv: string[], -): Result<{ threadId: string; fromRole: string | null }, string> { - if (argv.length === 0) { - return err("fork requires "); - } - const threadId = argv[0]; - if (threadId === undefined || threadId === "") { - return err("fork requires "); - } - let fromRole: string | null = null; - for (let i = 1; i < argv.length; i++) { - const a = argv[i]; - if (a === "--from-role") { - const r = argv[i + 1]; - if (r === undefined || r === "") { - return err("--from-role requires a role name"); - } - fromRole = r; - i++; - continue; - } - return err(`unexpected argument: ${a}`); - } - return ok({ threadId, fromRole }); -} - export async function cmdFork( storageRoot: string, threadId: string, diff --git a/packages/cli-workflow/src/commands/thread/index.ts b/packages/cli-workflow/src/commands/thread/index.ts index cd0c5d9..d215204 100644 --- a/packages/cli-workflow/src/commands/thread/index.ts +++ b/packages/cli-workflow/src/commands/thread/index.ts @@ -1,5 +1,6 @@ export { cmdKill, cmdPause, cmdResume } from "./control.js"; -export { cmdFork, parseForkArgv } from "./fork.js"; +export { cmdFork } from "./fork.js"; +export { parseForkArgv } from "./fork-argv.js"; export { cmdThreads } from "./list.js"; export type { LiveRoleRow } from "./live.js"; export { diff --git a/packages/cli-workflow/src/commands/thread/live.ts b/packages/cli-workflow/src/commands/thread/live.ts index 31d79ba..496ce5f 100644 --- a/packages/cli-workflow/src/commands/thread/live.ts +++ b/packages/cli-workflow/src/commands/thread/live.ts @@ -12,6 +12,7 @@ import { type WorkflowCompletion, } from "@uncaged/workflow"; +import { dimGreyLine, highlightLiveRole } from "../../cli-color.js"; import { printCliError, printCliLine } from "../../cli-output.js"; import { pathExists } from "../../fs-utils.js"; import type { ParsedLiveArgv } from "../../live-argv.js"; @@ -34,24 +35,6 @@ export function formatLiveTimeLabel(timestampMs: number): string { return `${hh}:${mm}:${ss}`; } -function shouldUseColor(): boolean { - return process.stdout.isTTY === true && process.env.NO_COLOR === undefined; -} - -function highlightLiveRole(name: string): string { - if (!shouldUseColor()) { - return name; - } - return `\x1b[1m\x1b[36m${name}\x1b[0m`; -} - -function dimGreyLine(line: string): string { - if (!shouldUseColor()) { - return line; - } - return `\x1b[2m\x1b[90m${line}\x1b[0m`; -} - export function formatLiveDebugLine(timestampMs: number, tag: string, message: string): string { const label = `[${formatLiveTimeLabel(timestampMs)}] [${tag}] ${message.replace(/\n/g, " ")}`; return dimGreyLine(label); diff --git a/packages/cli-workflow/src/commands/workflow/add-argv.ts b/packages/cli-workflow/src/commands/workflow/add-argv.ts new file mode 100644 index 0000000..587262c --- /dev/null +++ b/packages/cli-workflow/src/commands/workflow/add-argv.ts @@ -0,0 +1,77 @@ +import { err, ok, type Result } from "@uncaged/workflow"; + +export type ParsedAddArgv = { + name: string; + filePath: string; + /** Override path to `.d.ts` when adding a bundle. */ + typesPath: string | null; +}; + +type ParsedLongFlag = { advance: 2; kind: "types"; value: string }; + +function tryParseAddLongFlag(argv: string[], index: number): Result { + const tok = argv[index]; + if (tok !== "--types") { + return ok(null); + } + const value = argv[index + 1]; + if (value === undefined || value.startsWith("--")) { + return err("missing value for --types"); + } + return ok({ advance: 2, kind: "types", value }); +} + +type PositionalSlots = { + name: string | undefined; + filePath: string | undefined; +}; + +function assignPositional(tok: string, slots: PositionalSlots): Result { + if (slots.name === undefined) { + slots.name = tok; + return ok(undefined); + } + if (slots.filePath === undefined) { + slots.filePath = tok; + return ok(undefined); + } + return err("too many arguments"); +} + +export function parseAddArgv(argv: string[]): Result { + const slots: PositionalSlots = { name: undefined, filePath: undefined }; + let typesPath: string | null = null; + + let i = 0; + while (i < argv.length) { + const flag = tryParseAddLongFlag(argv, i); + if (!flag.ok) { + return flag; + } + if (flag.value !== null) { + typesPath = flag.value.value; + i += flag.value.advance; + continue; + } + + const tok = argv[i]; + if (tok?.startsWith("--")) { + return err(`unknown add flag: ${tok}`); + } + if (tok === undefined) { + break; + } + const placed = assignPositional(tok, slots); + if (!placed.ok) { + return placed; + } + i += 1; + } + + const { name, filePath } = slots; + if (name === undefined || name === "" || filePath === undefined || filePath === "") { + return err("add requires "); + } + + return ok({ name, filePath, typesPath }); +} diff --git a/packages/cli-workflow/src/commands/workflow/add.ts b/packages/cli-workflow/src/commands/workflow/add.ts index 1cb1af4..3fade78 100644 --- a/packages/cli-workflow/src/commands/workflow/add.ts +++ b/packages/cli-workflow/src/commands/workflow/add.ts @@ -17,12 +17,7 @@ import { import { storeWorkflowBundleArtifacts } from "../../bundle-store.js"; import { validateCliWorkflowName } from "../../workflow-name.js"; -export type ParsedAddArgv = { - name: string; - filePath: string; - /** Override path to `.d.ts` when adding a bundle. */ - typesPath: string | null; -}; +import type { ParsedAddArgv } from "./add-argv.js"; export type CmdAddSuccess = { hash: string; @@ -37,75 +32,6 @@ function defaultTypesPath(bundlePath: string): string { return bundlePath.replace(/\.esm\.js$/i, ".d.ts"); } -type ParsedLongFlag = { advance: 2; kind: "types"; value: string }; - -function tryParseAddLongFlag(argv: string[], index: number): Result { - const tok = argv[index]; - if (tok !== "--types") { - return ok(null); - } - const value = argv[index + 1]; - if (value === undefined || value.startsWith("--")) { - return err("missing value for --types"); - } - return ok({ advance: 2, kind: "types", value }); -} - -type PositionalSlots = { - name: string | undefined; - filePath: string | undefined; -}; - -function assignPositional(tok: string, slots: PositionalSlots): Result { - if (slots.name === undefined) { - slots.name = tok; - return ok(undefined); - } - if (slots.filePath === undefined) { - slots.filePath = tok; - return ok(undefined); - } - return err("too many arguments"); -} - -export function parseAddArgv(argv: string[]): Result { - const slots: PositionalSlots = { name: undefined, filePath: undefined }; - let typesPath: string | null = null; - - let i = 0; - while (i < argv.length) { - const flag = tryParseAddLongFlag(argv, i); - if (!flag.ok) { - return flag; - } - if (flag.value !== null) { - typesPath = flag.value.value; - i += flag.value.advance; - continue; - } - - const tok = argv[i]; - if (tok?.startsWith("--")) { - return err(`unknown add flag: ${tok}`); - } - if (tok === undefined) { - break; - } - const placed = assignPositional(tok, slots); - if (!placed.ok) { - return placed; - } - i += 1; - } - - const { name, filePath } = slots; - if (name === undefined || name === "" || filePath === undefined || filePath === "") { - return err("add requires "); - } - - return ok({ name, filePath, typesPath }); -} - async function registerHash( storageRoot: string, name: string, diff --git a/packages/cli-workflow/src/commands/workflow/dispatch.ts b/packages/cli-workflow/src/commands/workflow/dispatch.ts index 560a742..b73ceca 100644 --- a/packages/cli-workflow/src/commands/workflow/dispatch.ts +++ b/packages/cli-workflow/src/commands/workflow/dispatch.ts @@ -2,7 +2,8 @@ import type { CommandEntry, DispatchGroupFn } from "../../cli-command-types.js"; import { printCliError, printCliLine, printCliWarn } from "../../cli-output.js"; import { formatCliUsage, USAGE_SKILL_TOPIC_ROWS } from "../../cli-usage.js"; import { getCommandGroupsForUsage } from "../../cli-usage-context.js"; -import { cmdAdd, formatAddSuccess, parseAddArgv } from "./add.js"; +import { cmdAdd, formatAddSuccess } from "./add.js"; +import { parseAddArgv } from "./add-argv.js"; import { cmdHistory } from "./history.js"; import { cmdList, formatListLines } from "./list.js"; import { cmdRemove } from "./rm.js"; diff --git a/packages/cli-workflow/src/commands/workflow/index.ts b/packages/cli-workflow/src/commands/workflow/index.ts index 6cfde82..46e2503 100644 --- a/packages/cli-workflow/src/commands/workflow/index.ts +++ b/packages/cli-workflow/src/commands/workflow/index.ts @@ -1,5 +1,7 @@ -export type { CmdAddSuccess, ParsedAddArgv } from "./add.js"; -export { cmdAdd, formatAddSuccess, parseAddArgv } from "./add.js"; +export type { CmdAddSuccess } from "./add.js"; +export { cmdAdd, formatAddSuccess } from "./add.js"; +export type { ParsedAddArgv } from "./add-argv.js"; +export { parseAddArgv } from "./add-argv.js"; export { cmdHistory } from "./history.js"; export { cmdList, formatListLines } from "./list.js"; export { cmdRemove } from "./rm.js"; diff --git a/packages/cli-workflow/src/thread-scan.ts b/packages/cli-workflow/src/thread-scan.ts index 0cf2951..642ff24 100644 --- a/packages/cli-workflow/src/thread-scan.ts +++ b/packages/cli-workflow/src/thread-scan.ts @@ -3,6 +3,23 @@ import { join } from "node:path"; import { pathExists, readTextFileIfExists } from "./fs-utils.js"; +function parseFirstJsonLineObject(text: string): Record | null { + const firstLine = text.split("\n")[0]; + if (firstLine === undefined || firstLine.trim() === "") { + return null; + } + let parsed: unknown; + try { + parsed = JSON.parse(firstLine) as unknown; + } catch { + return null; + } + if (parsed === null || typeof parsed !== "object") { + return null; + } + return parsed as Record; +} + export type RunningThreadRow = { threadId: string; hash: string; @@ -20,20 +37,11 @@ async function readThreadStartTimestampMs(dataPath: string): Promise).timestamp; + const ts = parsed.timestamp; return typeof ts === "number" && Number.isFinite(ts) ? ts : null; } @@ -42,20 +50,11 @@ async function readWorkflowNameFromDataJsonl(dataPath: string): Promise).name; + const name = parsed.name; return typeof name === "string" ? name : null; }