enbu 0.1.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.mjs","names":["flows: Flow[]","commandFormatters: Record<string, CommandFormatter>"],"sources":["../src/args-parser.ts","../src/output/formatter.ts","../src/utils/fs.ts","../src/commands/init.ts","../src/commands/run.ts","../src/output/exit-code.ts","../src/main.ts"],"sourcesContent":["import type { CliError, ParsedArgs } from './types';\nimport { type Result, err, ok } from 'neverthrow';\n\n/** デフォルトタイムアウト: 30秒 */\nconst DEFAULT_TIMEOUT_MS = 30000;\n\n/**\n * コマンドライン引数をパースする\n *\n * process.argvから渡された引数を解析し、ParsedArgs型に変換する。\n * バージョンフラグが指定されている場合は、他の引数に関わらずバージョンモードとして返す。\n * ヘルプフラグが指定されている場合は、他の引数に関わらずヘルプモードとして返す。\n * 最初の位置引数がinitの場合はinitコマンド、それ以外はrunコマンドとして扱う。\n *\n * @param argv - process.argv(インデックス2以降)\n * @returns パース済み引数、またはエラー\n */\nexport const parseArgs = (argv: string[]): Result<ParsedArgs, CliError> => {\n // バージョンフラグの確認\n if (argv.includes('-V') || argv.includes('--version')) {\n return ok({\n command: 'run',\n help: false,\n version: true,\n verbose: false,\n files: [],\n headed: false,\n env: {},\n timeout: DEFAULT_TIMEOUT_MS,\n screenshot: false,\n bail: false,\n });\n }\n\n // ヘルプフラグの確認\n if (argv.includes('-h') || argv.includes('--help')) {\n return ok({\n command: 'run',\n help: true,\n version: false,\n verbose: false,\n files: [],\n headed: false,\n env: {},\n timeout: DEFAULT_TIMEOUT_MS,\n screenshot: false,\n bail: false,\n });\n }\n\n // verboseフラグ\n const verbose = argv.includes('-v') || argv.includes('--verbose');\n\n // コマンド判定\n const firstArg = argv[0];\n if (firstArg === 'init') {\n return parseInitArgs(argv.slice(1), verbose);\n }\n\n // デフォルトはrunコマンド\n return parseRunArgs(argv, verbose);\n};\n\n/**\n * initコマンドの引数をパースする\n *\n * initコマンドで利用可能なオプション(--force)を解析する。\n * 未知のオプションが指定された場合はエラーを返す。\n *\n * @param argv - initコマンド以降の引数配列\n * @param verbose - verboseフラグが有効かどうか\n * @returns パース済みのinit引数、またはエラー\n */\nconst parseInitArgs = (argv: string[], verbose: boolean): Result<ParsedArgs, CliError> => {\n const force = argv.includes('--force');\n\n // 未知のオプションチェック\n for (const arg of argv) {\n if (arg.startsWith('--')) {\n if (arg !== '--force' && arg !== '--verbose') {\n return err({\n type: 'invalid_args',\n message: `Unknown option for init command: ${arg}`,\n });\n }\n } else if (arg.startsWith('-')) {\n if (arg !== '-v') {\n return err({\n type: 'invalid_args',\n message: `Unknown option for init command: ${arg}`,\n });\n }\n }\n }\n\n return ok({\n command: 'init',\n help: false,\n version: false,\n verbose,\n force,\n });\n};\n\n/**\n * runコマンドの引数をパースする\n *\n * runコマンドで利用可能なオプション(--headed, --env, --timeout等)を解析する。\n * --envは複数回指定可能で、KEY=VALUE形式でなければならない。\n * --timeoutは正の整数値でなければならない。\n * 位置引数(オプションフラグでない引数)は全てフローファイルパスとして扱う。\n *\n * @param argv - runコマンドの引数配列\n * @param verbose - verboseフラグが有効かどうか\n * @returns パース済みのrun引数、またはエラー\n */\nconst parseRunArgs = (argv: string[], verbose: boolean): Result<ParsedArgs, CliError> => {\n const files: string[] = [];\n const env: Record<string, string> = {};\n const state = {\n files,\n env,\n headed: false,\n timeout: DEFAULT_TIMEOUT_MS,\n screenshot: false,\n bail: false,\n session: undefined,\n };\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n const processResult = processRunArg(arg, argv, i, state);\n\n const continueResult = processResult.match(\n (newIndex) => {\n // インデックスの更新(値を取る引数の場合)\n i = newIndex;\n return ok(undefined);\n },\n (error) => err(error),\n );\n\n if (continueResult.isErr()) {\n return continueResult;\n }\n }\n\n return ok({\n command: 'run',\n help: false,\n version: false,\n verbose,\n files: state.files,\n headed: state.headed,\n env: state.env,\n timeout: state.timeout,\n screenshot: state.screenshot,\n bail: state.bail,\n session: state.session,\n });\n};\n\n/**\n * runコマンドの単一引数を処理する\n *\n * 各オプションに応じた処理を行い、新しいインデックスを返す。\n * 値を取る引数(--env, --timeout, --session)の場合はインデックスを+1する。\n *\n * @param arg - 現在の引数\n * @param argv - 全引数配列\n * @param currentIndex - 現在のインデックス\n * @param state - パース状態を保持するオブジェクト\n * @returns 成功時: 新しいインデックス、失敗時: エラー\n */\nconst processRunArg = (\n arg: string,\n argv: string[],\n currentIndex: number,\n state: {\n files: string[];\n env: Record<string, string>;\n headed: boolean;\n timeout: number;\n screenshot: boolean;\n bail: boolean;\n session: string | undefined;\n },\n): Result<number, CliError> => {\n // 値を取るオプション\n switch (arg) {\n case '--env':\n return parseEnvOption(argv, currentIndex, state);\n case '--timeout':\n return parseTimeoutOption(argv, currentIndex, state);\n case '--session':\n return parseSessionOption(argv, currentIndex, state);\n default:\n // フラグオプション(値を取らない)\n return processFlagArg(arg, currentIndex, state);\n }\n};\n\n/**\n * フラグ引数を処理する(値を取らない引数)\n *\n * @param arg - 現在の引数\n * @param currentIndex - 現在のインデックス\n * @param state - パース状態を保持するオブジェクト\n * @returns 成功時: 現在のインデックス、失敗時: エラー\n */\nconst processFlagArg = (\n arg: string,\n currentIndex: number,\n state: {\n files: string[];\n headed: boolean;\n screenshot: boolean;\n bail: boolean;\n },\n): Result<number, CliError> => {\n if (arg === '--headed') {\n state.headed = true;\n return ok(currentIndex);\n }\n\n if (arg === '--screenshot') {\n state.screenshot = true;\n return ok(currentIndex);\n }\n\n if (arg === '--bail') {\n state.bail = true;\n return ok(currentIndex);\n }\n\n if (arg === '-v' || arg === '--verbose') {\n // 既に処理済み\n return ok(currentIndex);\n }\n\n if (arg.startsWith('--')) {\n return err({\n type: 'invalid_args',\n message: `Unknown option: ${arg}`,\n });\n }\n\n // 位置引数(フローファイル)\n state.files.push(arg);\n return ok(currentIndex);\n};\n\n/**\n * --env オプションをパースする\n */\nconst parseEnvOption = (\n argv: string[],\n currentIndex: number,\n state: { env: Record<string, string> },\n): Result<number, CliError> => {\n const nextArg = argv[currentIndex + 1];\n if (!nextArg) {\n return err({\n type: 'invalid_args',\n message: '--env requires KEY=VALUE argument',\n });\n }\n\n const envResult = parseEnvArg(nextArg);\n if (envResult.isErr()) {\n return err(envResult.error);\n }\n\n const [key, value] = envResult.value;\n state.env[key] = value;\n\n return ok(currentIndex + 1);\n};\n\n/**\n * --timeout オプションをパースする\n */\nconst parseTimeoutOption = (\n argv: string[],\n currentIndex: number,\n state: { timeout: number },\n): Result<number, CliError> => {\n const nextArg = argv[currentIndex + 1];\n if (!nextArg) {\n return err({\n type: 'invalid_args',\n message: '--timeout requires a number in milliseconds',\n });\n }\n\n const timeoutNum = Number.parseInt(nextArg, 10);\n if (Number.isNaN(timeoutNum) || timeoutNum <= 0) {\n return err({\n type: 'invalid_args',\n message: `--timeout must be a positive number, got: ${nextArg}`,\n });\n }\n\n state.timeout = timeoutNum;\n return ok(currentIndex + 1);\n};\n\n/**\n * --session オプションをパースする\n */\nconst parseSessionOption = (\n argv: string[],\n currentIndex: number,\n state: { session: string | undefined },\n): Result<number, CliError> => {\n const nextArg = argv[currentIndex + 1];\n if (!nextArg) {\n return err({\n type: 'invalid_args',\n message: '--session requires a session name',\n });\n }\n\n state.session = nextArg;\n return ok(currentIndex + 1);\n};\n\n/**\n * --env KEY=VALUE 引数をパースする\n *\n * 環境変数の設定値をKEY=VALUE形式から解析する。\n * =が含まれていない、またはKEYが空の場合はエラーを返す。\n * VALUEは空文字列でも許可される。\n *\n * @param arg - KEY=VALUE形式の文字列\n * @returns 成功時: [KEY, VALUE]のタプル、失敗時: エラー\n */\nconst parseEnvArg = (arg: string): Result<[string, string], CliError> => {\n const index = arg.indexOf('=');\n if (index === -1) {\n return err({\n type: 'invalid_args',\n message: `--env argument must be in KEY=VALUE format, got: ${arg}`,\n });\n }\n\n const key = arg.slice(0, index);\n const value = arg.slice(index + 1);\n\n if (key.length === 0) {\n return err({\n type: 'invalid_args',\n message: `--env KEY cannot be empty, got: ${arg}`,\n });\n }\n\n return ok([key, value]);\n};\n","import type { OutputTarget } from '../types';\n\n/**\n * 出力フォーマッター\n *\n * console.log禁止のため、process.stdout.write / process.stderr.write を使用する。\n * 進捗表示やメッセージの整形を担当するクラス。\n * 状態を持つため、classとして実装することが許可されている。\n */\nexport class OutputFormatter {\n /** 詳細ログ出力を行うかどうか */\n private verbose: boolean;\n /** スピナーのアニメーションフレーム */\n private spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n /** 現在表示中のスピナーフレームのインデックス */\n private spinnerIndex = 0;\n /** スピナーのsetIntervalのID(停止時にclearIntervalするため) */\n private spinnerIntervalId: NodeJS.Timeout | null = null;\n /** 現在表示中のスピナーメッセージ */\n private currentSpinnerMessage = '';\n\n /**\n * OutputFormatterのコンストラクタ\n *\n * @param verbose - 詳細ログ出力を行うかどうか\n */\n constructor(verbose: boolean) {\n this.verbose = verbose;\n }\n\n /**\n * 通常メッセージを出力\n *\n * 標準出力(stdout)にメッセージを出力します。\n * 一般的な情報や進捗状況を表示する際に使用します。\n *\n * @param message - 出力するメッセージ\n */\n info(message: string): void {\n this.write('stdout', message);\n }\n\n /**\n * エラーメッセージを出力\n *\n * 標準エラー出力(stderr)にメッセージを出力します。\n * エラーや警告メッセージを表示する際に使用します。\n *\n * @param message - 出力するエラーメッセージ\n */\n error(message: string): void {\n this.write('stderr', message);\n }\n\n /**\n * デバッグメッセージを出力(verboseモード時のみ)\n *\n * verboseフラグがtrueの場合のみ、標準エラー出力(stderr)に\n * デバッグメッセージを出力します。\n * \"[DEBUG]\"プレフィックスが自動的に付与されます。\n *\n * @param message - 出力するデバッグメッセージ\n */\n debug(message: string): void {\n if (this.verbose) {\n this.write('stderr', `[DEBUG] ${message}`);\n }\n }\n\n /**\n * 成功マーク付きメッセージ\n *\n * チェックマーク(✓)付きの成功メッセージを出力します。\n * オプションで実行時間(ミリ秒)を指定すると、秒単位で表示されます。\n *\n * @param message - 出力するメッセージ\n * @param durationMs - 実行時間(ミリ秒、省略可)\n */\n success(message: string, durationMs?: number): void {\n const duration = durationMs !== undefined ? ` (${(durationMs / 1000).toFixed(1)}s)` : '';\n this.info(` ✓ ${message}${duration}`);\n }\n\n /**\n * 失敗マーク付きメッセージ\n *\n * バツマーク(✗)付きの失敗メッセージを出力します。\n * オプションで実行時間(ミリ秒)を指定すると、秒単位で表示されます。\n *\n * @param message - 出力するメッセージ\n * @param durationMs - 実行時間(ミリ秒、省略可)\n */\n failure(message: string, durationMs?: number): void {\n const duration = durationMs !== undefined ? ` (${(durationMs / 1000).toFixed(1)}s)` : '';\n this.error(` ✗ ${message}${duration}`);\n }\n\n /**\n * インデント付きメッセージ(エラー詳細等)\n *\n * 指定されたレベル分のインデント(2スペース × レベル)を付けて\n * メッセージを標準エラー出力に出力します。\n * エラーの詳細情報や補足説明を階層的に表示する際に使用します。\n *\n * @param message - 出力するメッセージ\n * @param level - インデントレベル(デフォルト: 1)\n */\n indent(message: string, level = 1): void {\n const indent = ' '.repeat(level);\n this.error(`${indent}${message}`);\n }\n\n /**\n * スピナーを開始\n *\n * 指定されたメッセージと共にスピナーアニメーションを開始します。\n * スピナーは80msごとにフレームが更新されます。\n * 既に実行中のスピナーがある場合は、停止してから新しいスピナーを開始します。\n *\n * 注意: 必ずstopSpinner()を呼び出してスピナーを停止してください。\n * 停止しないとsetIntervalが残り続けます。\n *\n * @param message - スピナーと共に表示するメッセージ\n */\n startSpinner(message: string): void {\n this.currentSpinnerMessage = message;\n this.spinnerIndex = 0;\n\n // 既存のスピナーをクリア\n if (this.spinnerIntervalId !== null) {\n clearInterval(this.spinnerIntervalId);\n }\n\n // スピナーを表示\n this.renderSpinner();\n\n // 定期的に更新\n this.spinnerIntervalId = setInterval(() => {\n this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length;\n this.renderSpinner();\n }, 80);\n }\n\n /**\n * スピナーを停止\n *\n * 実行中のスピナーを停止し、スピナー行をクリアします。\n * setIntervalを適切にクリアして、リソースリークを防ぎます。\n * スピナーが実行中でない場合は何もしません。\n */\n stopSpinner(): void {\n if (this.spinnerIntervalId !== null) {\n clearInterval(this.spinnerIntervalId);\n this.spinnerIntervalId = null;\n }\n\n // スピナー行をクリア\n this.clearLine();\n }\n\n /**\n * セクション区切り線\n *\n * 視覚的にセクションを区切るための水平線を出力します。\n * サマリー表示や大きな処理の区切りに使用します。\n */\n separator(): void {\n this.info('────────────────────────────────────────');\n }\n\n /**\n * 改行\n *\n * 空行を出力します。\n * メッセージ間に視覚的な余白を作る際に使用します。\n */\n newline(): void {\n this.write('stdout', '');\n }\n\n /**\n * 内部:書き込み処理\n *\n * 指定された出力先(stdout または stderr)にメッセージを書き込みます。\n * メッセージの末尾には自動的に改行文字が付与されます。\n *\n * @param target - 出力先('stdout' または 'stderr')\n * @param message - 出力するメッセージ\n */\n private write(target: OutputTarget, message: string): void {\n const output = target === 'stdout' ? process.stdout : process.stderr;\n output.write(`${message}\\n`);\n }\n\n /**\n * 内部:スピナーを描画\n *\n * 現在のスピナーフレームとメッセージを画面に描画します。\n * カーソルを行頭に戻してから描画することで、\n * 前のフレームを上書きしてアニメーション効果を実現します。\n */\n private renderSpinner(): void {\n const frame = this.spinnerFrames[this.spinnerIndex];\n this.clearLine();\n process.stdout.write(` ${frame} ${this.currentSpinnerMessage}`);\n }\n\n /**\n * 内部:現在行をクリア\n *\n * カーソルを行頭に戻し(\\r)、行全体をクリア(\\x1b[K)します。\n * スピナーのアニメーションや、前の出力を上書きする際に使用します。\n * ANSIエスケープシーケンスを使用しています。\n */\n private clearLine(): void {\n process.stdout.write('\\r\\x1b[K');\n }\n}\n\n/**\n * ヘルプメッセージを表示\n *\n * CLIの使用方法を標準出力に表示します。\n * コマンドラインオプション、サブコマンド、使用例などを含みます。\n * --help フラグまたは -h フラグが指定された際に呼び出されます。\n */\nexport const showHelp = (): void => {\n const helpText = `\nenbu - CLI for agent-browser workflow automation\n\nUSAGE:\n npx enbu [command] [options] [flow-files...]\n\nCOMMANDS:\n init Initialize a new project\n (default) Run flow files\n\nOPTIONS:\n -h, --help Show this help message\n -V, --version Show version number\n -v, --verbose Enable verbose logging\n --headed Show browser (disable headless mode)\n --env KEY=VALUE Set environment variable (can be used multiple times)\n --timeout <ms> Set timeout in milliseconds (default: 30000)\n --screenshot Save screenshot on failure\n --bail Stop on first failure\n --session <name> Set agent-browser session name\n\nEXAMPLES:\n npx enbu init\n npx enbu\n npx enbu login.flow.yaml\n npx enbu --headed --env USER=test login.flow.yaml\n npx enbu --bail login.flow.yaml checkout.flow.yaml\n\nFor more information, visit: https://github.com/9wick/enbu\n`;\n\n process.stdout.write(helpText);\n};\n\n/**\n * バージョン情報を表示\n *\n * CLIのバージョン番号を標準出力に表示します。\n * ビルド時に埋め込まれたバージョン情報(__VERSION__定数)を使用します。\n * --version フラグまたは -V フラグが指定された際に呼び出されます。\n *\n * 実装の詳細:\n * tsdown.config.tsのdefineオプションにより、__VERSION__定数が\n * ビルド時にpackage.jsonのバージョン情報で置換されます。\n * これにより、実行時のファイル読み込みオーバーヘッドがなく、\n * ビルド構造の変更にも影響を受けません。\n */\nexport const showVersion = (): void => {\n process.stdout.write(`${__VERSION__}\\n`);\n};\n","import { readFile, writeFile, mkdir, access } from 'node:fs/promises';\nimport { constants } from 'node:fs';\nimport { type ResultAsync, fromPromise } from 'neverthrow';\nimport type { CliError } from '../types';\n\n/**\n * ファイルが存在するか確認\n *\n * 指定されたパスにファイルまたはディレクトリが存在するかを確認する。\n * アクセス権限がない場合もfalseを返す。\n *\n * @param path - 確認対象のファイルまたはディレクトリのパス\n * @returns ファイルが存在する場合はtrue、存在しない場合またはアクセスできない場合はfalse\n */\nexport const fileExists = (path: string): Promise<boolean> => {\n return access(path, constants.F_OK)\n .then(() => true)\n .catch(() => false);\n};\n\n/**\n * ディレクトリを作成(存在しない場合のみ)\n *\n * 指定されたパスにディレクトリを作成する。\n * recursive オプションにより、親ディレクトリが存在しない場合も自動的に作成される。\n * 既にディレクトリが存在する場合はエラーにならず、成功として扱われる。\n *\n * @param path - 作成するディレクトリのパス\n * @returns 成功時: void、失敗時: CliError(type: 'execution_error')\n */\nexport const createDirectory = (path: string): ResultAsync<void, CliError> => {\n return fromPromise(\n mkdir(path, { recursive: true }),\n (error): CliError => ({\n type: 'execution_error',\n message: `Failed to create directory: ${path}`,\n cause: error,\n }),\n ).map(() => undefined);\n};\n\n/**\n * ファイルを書き込み\n *\n * 指定されたパスにテキストファイルを書き込む。\n * ファイルが既に存在する場合は上書きされる。\n * 親ディレクトリが存在しない場合はエラーとなるため、事前に createDirectory を実行すること。\n *\n * @param path - 書き込み先のファイルパス\n * @param content - 書き込むテキスト内容\n * @returns 成功時: void、失敗時: CliError(type: 'execution_error')\n */\nexport const writeFileContent = (path: string, content: string): ResultAsync<void, CliError> => {\n return fromPromise(\n writeFile(path, content, 'utf-8'),\n (error): CliError => ({\n type: 'execution_error',\n message: `Failed to write file: ${path}`,\n cause: error,\n }),\n ).map(() => undefined);\n};\n\n/**\n * ファイルを読み込み\n *\n * 指定されたパスからテキストファイルを読み込む。\n * ファイルが存在しない場合やアクセス権限がない場合はエラーを返す。\n * バイナリファイルの読み込みには対応していないため、UTF-8テキストファイルのみを対象とする。\n *\n * @param path - 読み込むファイルのパス\n * @returns 成功時: ファイルの内容(文字列)、失敗時: CliError(type: 'execution_error')\n */\nexport const readFileContent = (path: string): ResultAsync<string, CliError> => {\n return fromPromise(\n readFile(path, 'utf-8'),\n (error): CliError => ({\n type: 'execution_error',\n message: `Failed to read file: ${path}`,\n cause: error,\n }),\n );\n};\n","import { type Result, ok } from 'neverthrow';\nimport { createInterface } from 'node:readline';\nimport { resolve } from 'node:path';\nimport type { CliError } from '../types';\nimport { OutputFormatter } from '../output/formatter';\nimport { fileExists, createDirectory, writeFileContent, readFileContent } from '../utils/fs';\n\n/** 生成するディレクトリ */\nconst ABFLOW_DIR = '.abflow';\n\n/** サンプルフローファイルの内容 */\nconst SAMPLE_FLOW_YAML = `# enbuのサンプルフロー\nsteps:\n - open: https://example.com\n - click: \"More information...\"\n - assertVisible: \"Example Domain\"\n`;\n\n/**\n * initコマンドを実行\n *\n * プロジェクトの初期化を行い、以下の処理を実行する:\n * 1. .abflow/ ディレクトリを作成\n * 2. サンプルフローファイル example.enbu.yaml を生成\n * 3. .gitignore への追記を対話的に提案\n *\n * forceフラグがtrueの場合、既存ファイルを上書きする。\n * forceフラグがfalseの場合、既存ファイルはスキップされる。\n *\n * @param args - initコマンドの引数\n * @param args.force - 既存ファイルを強制的に上書きするかどうか\n * @param args.verbose - 詳細なログ出力を行うかどうか\n * @returns 成功時: void、失敗時: CliError\n */\nexport const runInitCommand = async (args: {\n force: boolean;\n verbose: boolean;\n}): Promise<Result<void, CliError>> => {\n const formatter = new OutputFormatter(args.verbose);\n\n formatter.info('Initializing enbu project...');\n\n // .abflow/ ディレクトリとサンプルファイルを作成\n const setupResult = await setupAbflowDirectory(args.force, formatter);\n if (setupResult.isErr()) {\n return setupResult;\n }\n\n // .gitignore への追記を提案\n await promptGitignoreUpdate(formatter);\n\n formatter.newline();\n formatter.info('Initialization complete!');\n formatter.info(`Try: npx enbu ${ABFLOW_DIR}/example.enbu.yaml`);\n\n return ok(undefined);\n};\n\n/**\n * .abflow/ ディレクトリとサンプルファイルを作成\n *\n * .abflow/ ディレクトリを作成し、その中にサンプルフローファイルを生成する。\n * forceフラグがfalseで既存ファイルが存在する場合はスキップする。\n *\n * @param force - 既存ファイルを強制的に上書きするかどうか\n * @param formatter - 出力フォーマッター\n * @returns 成功時: void、失敗時: CliError\n */\nconst setupAbflowDirectory = async (\n force: boolean,\n formatter: OutputFormatter,\n): Promise<Result<void, CliError>> => {\n // .abflow/ ディレクトリを作成\n const abflowPath = resolve(process.cwd(), ABFLOW_DIR);\n const abflowExists = await fileExists(abflowPath);\n\n if (abflowExists && !force) {\n formatter.success(`Directory already exists: ${ABFLOW_DIR}`);\n } else {\n const createDirResult = await createDirectory(abflowPath);\n if (createDirResult.isErr()) {\n return createDirResult;\n }\n formatter.success(`Created ${ABFLOW_DIR}/ directory`);\n }\n\n // example.enbu.yaml を生成\n const exampleFlowPath = resolve(abflowPath, 'example.enbu.yaml');\n const exampleFlowExists = await fileExists(exampleFlowPath);\n\n if (exampleFlowExists && !force) {\n formatter.success(`File already exists: ${ABFLOW_DIR}/example.enbu.yaml`);\n } else {\n const writeResult = await writeFileContent(exampleFlowPath, SAMPLE_FLOW_YAML);\n if (writeResult.isErr()) {\n return writeResult;\n }\n formatter.success(`Created ${ABFLOW_DIR}/example.enbu.yaml`);\n }\n\n return ok(undefined);\n};\n\n/**\n * .gitignore への追記を対話的に提案\n *\n * ユーザーに .gitignore への追記を提案し、了承された場合に .abflow/ を追記する。\n * .gitignore の更新に失敗した場合は、エラーメッセージと手動での追記方法を表示する。\n *\n * @param formatter - 出力フォーマッター\n */\nconst promptGitignoreUpdate = async (formatter: OutputFormatter): Promise<void> => {\n formatter.newline();\n const shouldUpdateGitignore = await askYesNo(\n 'Would you like to add .abflow/ to .gitignore? (y/N): ',\n );\n\n if (shouldUpdateGitignore) {\n const gitignorePath = resolve(process.cwd(), '.gitignore');\n const updateResult = await updateGitignore(gitignorePath);\n\n updateResult.match(\n () => formatter.success('Updated .gitignore'),\n (error) => {\n formatter.error(`Failed to update .gitignore: ${error.message}`);\n formatter.indent('You can manually add \".abflow/\" to your .gitignore file', 1);\n },\n );\n }\n};\n\n/**\n * Yes/No 質問を対話的に行う\n *\n * ユーザーに質問を表示し、標準入力から回答を受け取る。\n * 'y' または 'yes'(大文字小文字を区別しない)が入力された場合にtrueを返す。\n * それ以外の入力(空文字列を含む)の場合はfalseを返す。\n *\n * readlineのcreateInterfaceを使用して対話的な入力を実現している。\n * 入力完了後は必ずrl.close()を呼び出してリソースを解放する。\n *\n * @param question - ユーザーに表示する質問文\n * @returns ユーザーが 'y' または 'yes' を入力した場合はtrue、それ以外はfalse\n */\nconst askYesNo = (question: string): Promise<boolean> => {\n return new Promise((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n rl.question(question, (answer) => {\n rl.close();\n const normalized = answer.trim().toLowerCase();\n resolve(normalized === 'y' || normalized === 'yes');\n });\n });\n};\n\n/**\n * .gitignore に .abflow/ を追記\n *\n * .gitignoreファイルに .abflow/ エントリを追加する。\n * 以下の条件に応じて処理が分岐する:\n *\n * 1. .gitignoreが存在しない場合:\n * - 新規に.gitignoreファイルを作成し、.abflow/を記述する\n *\n * 2. .gitignoreが存在し、既に.abflow/が含まれている場合:\n * - 何もせずに成功を返す(重複追記を防ぐ)\n *\n * 3. .gitignoreが存在し、.abflow/が含まれていない場合:\n * - ファイル末尾に.abflow/を追記する\n * - 元のファイルが改行で終わっていない場合は、改行を追加してから追記する\n *\n * @param path - .gitignoreファイルのパス\n * @returns 成功時: void、失敗時: CliError(type: 'execution_error')\n */\nconst updateGitignore = async (path: string): Promise<Result<void, CliError>> => {\n const exists = await fileExists(path);\n const entry = '.abflow/';\n\n if (!exists) {\n // .gitignore が存在しない場合、新規作成\n return writeFileContent(path, `${entry}\\n`);\n }\n\n // 既存の .gitignore を読み込み\n const readResult = await readFileContent(path);\n if (readResult.isErr()) {\n return readResult.andThen(() => ok(undefined));\n }\n\n const content = readResult.value;\n\n // 既に .abflow/ が含まれている場合はスキップ\n if (content.includes(entry)) {\n return ok(undefined);\n }\n\n // 追記\n const newContent = content.endsWith('\\n') ? `${content}${entry}\\n` : `${content}\\n${entry}\\n`;\n return writeFileContent(path, newContent);\n};\n","/**\n * runコマンドの実装\n *\n * フローファイルを読み込み、agent-browserで実行する。\n * 実行結果を表示し、終了コードを返す。\n */\n\nimport { type Result, ok, err, fromPromise } from 'neverthrow';\nimport { resolve } from 'node:path';\nimport { readFile } from 'node:fs/promises';\nimport { glob } from 'glob';\nimport type { CliError, FlowExecutionResult } from '../types';\nimport type { OutputFormatter } from '../output/formatter';\nimport {\n checkAgentBrowser,\n closeSession,\n type AgentBrowserError,\n} from '@packages/agent-browser-adapter';\nimport { parseFlowYaml, type Flow, executeFlow, type FlowResult } from '@packages/core';\n\n/**\n * runコマンドの引数\n */\ntype RunCommandArgs = {\n /** フローファイルパスの配列 */\n files: string[];\n /** ヘッドモードで実行するか */\n headed: boolean;\n /** 環境変数のマップ */\n env: Record<string, string>;\n /** タイムアウト時間(ミリ秒) */\n timeout: number;\n /** スクリーンショットを撮影するか */\n screenshot: boolean;\n /** 最初の失敗で停止するか */\n bail: boolean;\n /** セッション名 */\n session?: string;\n /** verboseモード */\n verbose: boolean;\n};\n\n/**\n * agent-browserのインストール状態を確認する\n *\n * @param formatter - 出力フォーマッター\n * @returns 成功時: void、失敗時: CliError\n */\nconst checkAgentBrowserInstallation = async (\n formatter: OutputFormatter,\n): Promise<Result<void, CliError>> => {\n formatter.info('Checking agent-browser...');\n formatter.debug('Checking agent-browser installation...');\n\n const checkResult = await checkAgentBrowser();\n\n return checkResult.match(\n () => {\n formatter.success('agent-browser is installed');\n formatter.newline();\n return ok(undefined);\n },\n (error) => {\n formatter.failure('agent-browser is not installed');\n formatter.newline();\n formatter.error('Error: agent-browser is not installed');\n formatter.error('Please install it with: npm install -g agent-browser');\n\n const errorMessage =\n error.type === 'not_installed'\n ? error.message\n : `${error.type}: ${error.type === 'command_failed' ? (error.errorMessage ?? error.stderr) : ''}`;\n\n return err({\n type: 'execution_error' as const,\n message: errorMessage,\n });\n },\n );\n};\n\n/**\n * フローファイルパスの配列を解決する\n *\n * ファイルが指定されていない場合、.abflow/ ディレクトリから検索する。\n *\n * @param files - 指定されたファイルパスの配列\n * @returns 成功時: 解決されたファイルパスの配列、失敗時: CliError\n */\nconst resolveFlowFiles = async (files: string[]): Promise<Result<string[], CliError>> => {\n if (files.length > 0) {\n // ファイルパスが指定されている場合、絶対パスに変換\n return ok(files.map((f) => resolve(process.cwd(), f)));\n }\n\n // 指定がない場合、.abflow/ 配下を検索\n const pattern = resolve(process.cwd(), '.abflow', '*.enbu.yaml');\n return fromPromise(\n glob(pattern),\n (error): CliError => ({\n type: 'execution_error' as const,\n message: 'Failed to search for flow files',\n cause: error,\n }),\n );\n};\n\n/**\n * ファイルパスからフローを読み込んでパースする\n *\n * @param filePath - フローファイルのパス\n * @returns 成功時: Flowオブジェクト、失敗時: CliError\n */\nconst loadFlowFromFile = async (filePath: string): Promise<Result<Flow, CliError>> => {\n // ファイル読み込み\n const readResult = await fromPromise<string, CliError>(\n readFile(filePath, 'utf-8'),\n (error): CliError => ({\n type: 'execution_error' as const,\n message: `Failed to read file: ${filePath}`,\n cause: error,\n }),\n );\n\n if (readResult.isErr()) {\n return err(readResult.error);\n }\n\n const yamlContent = readResult.value;\n\n // ファイル名を抽出(拡張子付き)\n const fileName = filePath.split('/').pop() ?? 'unknown.enbu.yaml';\n\n // YAMLをパース\n const parseResult = parseFlowYaml(yamlContent, fileName);\n return parseResult.mapErr(\n (parseError): CliError => ({\n type: 'execution_error' as const,\n message: `Failed to parse flow file: ${parseError.message}`,\n cause: parseError,\n }),\n );\n};\n\n/**\n * フローファイルを読み込む\n *\n * @param flowFiles - フローファイルパスの配列\n * @param formatter - 出力フォーマッター\n * @returns 成功時: Flowオブジェクトの配列、失敗時: CliError\n */\nconst loadFlows = async (\n flowFiles: string[],\n formatter: OutputFormatter,\n): Promise<Result<Flow[], CliError>> => {\n formatter.info('Loading flows...');\n formatter.debug(`Loading flows from: ${flowFiles.join(', ')}`);\n\n // 各ファイルを順次読み込み\n const flows: Flow[] = [];\n for (const filePath of flowFiles) {\n const loadResult = await loadFlowFromFile(filePath);\n if (loadResult.isErr()) {\n formatter.failure(`Failed to load flows: ${loadResult.error.message}`);\n return err(loadResult.error);\n }\n flows.push(loadResult.value);\n }\n\n formatter.success(`Loaded ${flows.length} flow(s)`);\n formatter.newline();\n\n return ok(flows);\n};\n\n/**\n * コマンド説明フォーマッター関数の型\n */\ntype CommandFormatter = (command: Flow['steps'][number]) => string;\n\n/**\n * コマンド説明のフォーマッター関数マップ\n */\nconst commandFormatters: Record<string, CommandFormatter> = {\n open: (cmd) => `open ${('url' in cmd && cmd.url) || ''}`,\n click: (cmd) =>\n 'selector' in cmd\n ? `click \"${cmd.selector}\"${'index' in cmd && cmd.index !== undefined ? ` [${cmd.index}]` : ''}`\n : 'click',\n type: (cmd) =>\n 'selector' in cmd && 'value' in cmd\n ? `type \"${cmd.selector}\" = \"${cmd.value}\"${'clear' in cmd && cmd.clear ? ' (clear)' : ''}`\n : 'type',\n fill: (cmd) =>\n 'selector' in cmd && 'value' in cmd ? `fill \"${cmd.selector}\" = \"${cmd.value}\"` : 'fill',\n press: (cmd) => `press ${'key' in cmd ? cmd.key : ''}`,\n hover: (cmd) => ('selector' in cmd ? `hover \"${cmd.selector}\"` : 'hover'),\n select: (cmd) =>\n 'selector' in cmd && 'value' in cmd ? `select \"${cmd.selector}\" = \"${cmd.value}\"` : 'select',\n scroll: (cmd) =>\n 'direction' in cmd && 'amount' in cmd ? `scroll ${cmd.direction} ${cmd.amount}px` : 'scroll',\n scrollIntoView: (cmd) =>\n 'selector' in cmd ? `scrollIntoView \"${cmd.selector}\"` : 'scrollIntoView',\n wait: (cmd) =>\n 'ms' in cmd ? `wait ${cmd.ms}ms` : 'target' in cmd ? `wait \"${cmd.target}\"` : 'wait',\n screenshot: (cmd) =>\n 'path' in cmd\n ? `screenshot ${cmd.path}${'fullPage' in cmd && cmd.fullPage ? ' (full page)' : ''}`\n : 'screenshot',\n snapshot: () => 'snapshot',\n eval: (cmd) =>\n 'script' in cmd\n ? `eval \"${cmd.script.substring(0, 50)}${cmd.script.length > 50 ? '...' : ''}\"`\n : 'eval',\n assertVisible: (cmd) => ('selector' in cmd ? `assertVisible \"${cmd.selector}\"` : 'assertVisible'),\n assertEnabled: (cmd) => ('selector' in cmd ? `assertEnabled \"${cmd.selector}\"` : 'assertEnabled'),\n assertChecked: (cmd) =>\n 'selector' in cmd\n ? `assertChecked \"${cmd.selector}\"${'checked' in cmd && cmd.checked === false ? ' (unchecked)' : ''}`\n : 'assertChecked',\n};\n\n/**\n * コマンドの説明を生成する\n *\n * @param command - コマンド\n * @returns コマンドの説明文字列\n */\nconst formatCommandDescription = (command: Flow['steps'][number]): string => {\n const formatter = commandFormatters[command.command];\n return formatter ? formatter(command) : 'unknown command';\n};\n\n/**\n * フローを実行しながら進捗を表示する\n *\n * @param flow - 実行するフロー\n * @param args - 実行オプション\n * @param sessionName - セッション名\n * @returns 成功時: FlowResult、失敗時: CliError\n */\nconst executeFlowWithProgress = async (\n flow: Flow,\n args: RunCommandArgs,\n sessionName: string,\n): Promise<Result<FlowResult, CliError>> => {\n // フロー実行\n const executeResult = await executeFlow(flow, {\n sessionName,\n headed: args.headed,\n env: args.env,\n commandTimeoutMs: args.timeout,\n screenshot: args.screenshot,\n bail: args.bail,\n });\n\n return executeResult.mapErr((agentError: AgentBrowserError): CliError => {\n const errorMessage =\n agentError.type === 'not_installed'\n ? agentError.message\n : agentError.type === 'parse_error'\n ? agentError.message\n : agentError.type === 'timeout'\n ? `Timeout: ${agentError.command} (${agentError.timeoutMs}ms)`\n : (agentError.errorMessage ?? agentError.stderr);\n\n return {\n type: 'execution_error' as const,\n message: errorMessage,\n cause: agentError,\n };\n });\n};\n\n/**\n * 各ステップの結果を表示する(verboseモードのみ)\n */\nconst displayStepResults = (steps: FlowResult['steps'], formatter: OutputFormatter): void => {\n formatter.newline();\n formatter.indent('Steps:', 1);\n for (const step of steps) {\n const stepDesc = formatCommandDescription(step.command);\n const statusIcon = step.status === 'passed' ? '✓' : '✗';\n formatter.indent(`${statusIcon} Step ${step.index + 1}: ${stepDesc} (${step.duration}ms)`, 2);\n if (step.error) {\n formatter.indent(`Error: ${step.error.message}`, 3);\n }\n }\n};\n\n/**\n * フロー実行結果を表示する\n *\n * @param flow - 実行したフロー\n * @param result - フロー実行結果\n * @param formatter - 出力フォーマッター\n * @param verbose - verboseモードフラグ\n */\nconst displayFlowResult = (\n flow: Flow,\n result: FlowResult,\n formatter: OutputFormatter,\n verbose: boolean,\n): void => {\n const duration = result.duration;\n\n formatter.newline();\n\n if (result.status === 'passed') {\n formatter.success(`PASSED: ${flow.name}.enbu.yaml`, duration);\n } else {\n formatter.failure(`FAILED: ${flow.name}.enbu.yaml`, duration);\n if (result.error) {\n formatter.indent(`Step ${result.error.stepIndex + 1} failed: ${result.error.message}`, 1);\n if (result.error.screenshot) {\n formatter.indent(`Screenshot: ${result.error.screenshot}`, 1);\n }\n }\n }\n\n // 各ステップの結果を表示(verboseモードのみ)\n if (verbose) {\n displayStepResults(result.steps, formatter);\n }\n\n formatter.newline();\n};\n\n/**\n * 全てのフローを実行する\n *\n * @param flows - 実行するフロー配列\n * @param args - 実行オプション\n * @param formatter - 出力フォーマッター\n * @returns フロー実行結果のサマリー\n */\nconst executeAllFlows = async (\n flows: Flow[],\n args: RunCommandArgs,\n formatter: OutputFormatter,\n): Promise<FlowExecutionResult> => {\n let passed = 0;\n let failed = 0;\n const startTime = Date.now();\n\n for (const flow of flows) {\n formatter.info(`Running: ${flow.name}.enbu.yaml`);\n formatter.debug(`Executing flow: ${flow.name} (${flow.steps.length} steps)`);\n\n const flowStartTime = Date.now();\n\n // セッション名を生成\n // args.sessionが指定されている場合でも、各フローごとにユニークなセッション名を生成する\n // これにより、複数フロー実行時に最初のフローがセッションをクローズしても\n // 2番目以降のフローが影響を受けないようにする\n const sessionName = `abf-${args.session || flow.name}-${Date.now()}`;\n\n // フロー実行\n const executeResult = await executeFlowWithProgress(flow, args, sessionName);\n\n await executeResult.match(\n async (result) => {\n displayFlowResult(flow, result, formatter, args.verbose);\n\n if (result.status === 'passed') {\n passed++;\n // 正常終了時はセッションをクローズ\n const closeResult = await closeSession(sessionName);\n closeResult.mapErr((error) => {\n formatter.debug(`Failed to close session: ${error.type}`);\n });\n } else {\n failed++;\n // 失敗時はデバッグ案内を表示\n formatter.info('💡 Debug: To inspect the browser state, run:');\n formatter.indent(`npx agent-browser snapshot --session ${sessionName}`, 1);\n }\n },\n async (error) => {\n failed++;\n const duration = Date.now() - flowStartTime;\n formatter.newline();\n formatter.failure(`FAILED: ${flow.name}.enbu.yaml`, duration);\n formatter.indent(error.message, 1);\n formatter.newline();\n // executeFlowWithProgressがerrを返した場合、初期化エラーなどで\n // セッションが作成されていない可能性があるため、デバッグ案内は表示しない\n // (result.status === 'failed'の場合のみセッションが確実に存在する)\n },\n );\n\n // --bail フラグが指定されていて、失敗した場合は中断\n if (args.bail && failed > 0) {\n formatter.error('Stopping due to --bail flag');\n formatter.newline();\n break;\n }\n }\n\n // サマリー表示\n formatter.separator();\n const totalDuration = Date.now() - startTime;\n const total = passed + failed;\n formatter.info(\n `Summary: ${passed}/${total} flows passed (${(totalDuration / 1000).toFixed(1)}s)`,\n );\n\n if (failed > 0) {\n formatter.newline();\n formatter.error('Exit code: 1');\n }\n\n return { passed, failed, total };\n};\n\n/**\n * runコマンドを実行する\n *\n * @param args - runコマンドの引数\n * @param formatter - 出力フォーマッター\n * @returns 成功時: 実行結果、失敗時: CliError\n */\nexport const runFlowCommand = async (\n args: RunCommandArgs,\n formatter: OutputFormatter,\n): Promise<Result<FlowExecutionResult, CliError>> => {\n formatter.debug(`Args: ${JSON.stringify(args)}`);\n\n // 1. agent-browserインストール確認\n const checkResult = await checkAgentBrowserInstallation(formatter);\n if (checkResult.isErr()) {\n return err(checkResult.error);\n }\n\n // 2. フローファイル解決\n const flowFilesResult = await resolveFlowFiles(args.files);\n if (flowFilesResult.isErr()) {\n return err(flowFilesResult.error);\n }\n\n const flowFiles = flowFilesResult.value;\n\n if (flowFiles.length === 0) {\n formatter.error('Error: No flow files found');\n formatter.error('Try: npx enbu init');\n return err({\n type: 'execution_error' as const,\n message: 'No flow files found',\n });\n }\n\n // 3. フローファイル読み込み\n const loadResult = await loadFlows(flowFiles, formatter);\n if (loadResult.isErr()) {\n return err(loadResult.error);\n }\n\n const flows = loadResult.value;\n\n // 4. フロー実行\n const executionResult = await executeAllFlows(flows, args, formatter);\n\n return ok(executionResult);\n};\n","/**\n * 終了コード定義\n */\nexport const EXIT_CODE = {\n /** 成功 */\n SUCCESS: 0,\n /** フロー実行失敗 */\n FLOW_FAILED: 1,\n /** 実行エラー(agent-browser未インストール等) */\n EXECUTION_ERROR: 2,\n} as const;\n\n/**\n * 終了コードで終了する\n *\n * @param code - 終了コード\n */\nexport const exitWithCode = (code: number): never => {\n process.exit(code);\n};\n","import { parseArgs } from './args-parser';\nimport { runInitCommand } from './commands/init';\nimport { runFlowCommand } from './commands/run';\nimport { showHelp, showVersion, OutputFormatter } from './output/formatter';\nimport { EXIT_CODE, exitWithCode } from './output/exit-code';\n\n/**\n * CLIエントリポイント\n *\n * コマンドライン引数をパースし、適切なコマンドを実行する。\n * 実行結果に基づいて終了コードを設定する。\n */\nconst main = async (): Promise<void> => {\n // 引数パース\n const argsResult = parseArgs(process.argv.slice(2));\n\n // neverthrowのmatchパターンで処理する\n await argsResult.match(\n async (args) => {\n // 引数のパースに成功した場合\n\n // バージョン表示\n if (args.version) {\n showVersion();\n exitWithCode(EXIT_CODE.SUCCESS);\n }\n\n // ヘルプ表示\n if (args.help) {\n showHelp();\n exitWithCode(EXIT_CODE.SUCCESS);\n }\n\n // コマンド実行\n if (args.command === 'init') {\n const result = await runInitCommand({\n force: args.force,\n verbose: args.verbose,\n });\n\n result.match(\n () => exitWithCode(EXIT_CODE.SUCCESS),\n (error) => {\n process.stderr.write(`Error: ${error.message}\\n`);\n exitWithCode(EXIT_CODE.EXECUTION_ERROR);\n },\n );\n } else {\n const formatter = new OutputFormatter(args.verbose);\n const result = await runFlowCommand(\n {\n files: args.files,\n headed: args.headed,\n env: args.env,\n timeout: args.timeout,\n screenshot: args.screenshot,\n bail: args.bail,\n session: args.session,\n verbose: args.verbose,\n },\n formatter,\n );\n\n result.match(\n (executionResult) => {\n const exitCode = executionResult.failed > 0 ? EXIT_CODE.FLOW_FAILED : EXIT_CODE.SUCCESS;\n exitWithCode(exitCode);\n },\n (error) => {\n process.stderr.write(`Error: ${error.message}\\n`);\n exitWithCode(EXIT_CODE.EXECUTION_ERROR);\n },\n );\n }\n },\n (error) => {\n // 引数のパースに失敗した場合\n process.stderr.write(`Error: ${error.message}\\n`);\n process.stderr.write('Try: npx enbu --help\\n');\n exitWithCode(EXIT_CODE.EXECUTION_ERROR);\n },\n );\n};\n\n// エントリポイント実行\nmain();\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "enbu",
3
+ "version": "0.1.0",
4
+ "description": "A simple YAML-based E2E testing framework powered by agent-browser. Define test flows in human-readable YAML and perform them in the browser.",
5
+ "keywords": [
6
+ "e2e",
7
+ "testing",
8
+ "browser",
9
+ "automation",
10
+ "yaml",
11
+ "agent-browser",
12
+ "end-to-end",
13
+ "test-framework",
14
+ "selenium-alternative",
15
+ "playwright-alternative"
16
+ ],
17
+ "homepage": "https://github.com/9wick/enbu/tree/main/apps/cli#readme",
18
+ "bugs": {
19
+ "url": "https://github.com/9wick/enbu/issues"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/9wick/enbu.git",
24
+ "directory": "apps/cli"
25
+ },
26
+ "license": "MIT",
27
+ "author": "",
28
+ "type": "module",
29
+ "main": "./dist/main.mjs",
30
+ "bin": {
31
+ "enbu": "./dist/main.mjs"
32
+ },
33
+ "files": [
34
+ "dist"
35
+ ],
36
+ "engines": {
37
+ "node": ">=22.x"
38
+ },
39
+ "scripts": {
40
+ "build": "nx run cli:build",
41
+ "dev": "nx run cli:dev",
42
+ "typecheck": "nx run cli:typecheck",
43
+ "lint": "nx run cli:lint",
44
+ "lint:check": "nx run cli:lint --configuration=ci",
45
+ "test": "nx run cli:test",
46
+ "start": "node ./dist/main.mjs"
47
+ },
48
+ "dependencies": {
49
+ },
50
+ "devDependencies": {
51
+ "@packages/config": "workspace:*",
52
+ "@types/node": "^22.14.1",
53
+ "@packages/agent-browser-adapter": "workspace:*",
54
+ "@packages/core": "workspace:*",
55
+ "glob": "^11.0.0",
56
+ "neverthrow": "^8.2.0"
57
+ }
58
+ }