diff --git a/.gitignore b/.gitignore index b1a4fa8..fbb8ac8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ npm-debug.log* .DS_Store +dist/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..074ad55 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,63 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Commands + +- `npm install` — first-time setup. +- `npm run build` — bundles `src/app.jsx` (and everything it imports) to `dist/app.mjs` via esbuild. Required before the CLI can run; `bin/server-config.js` exits with an error if the bundle is missing. +- `npm run build:watch` — rebuild on change. +- `npm test` — runs `node --test test/*.test.js` against the pure-logic library tests. +- `npm run check` — syntax-checks every hand-written `.js` file (does not check `.jsx` — they're consumed by esbuild). +- `node bin/server-config.js` — launch the TUI (or `server-config` after `npm link`). It refuses to start until `npm run build` has been run. + +Requirements: Node ≥18, but in practice ≥20 because Ink 7 + React 19 expect modern runtimes. The TUI requires a real TTY on stdin/stdout; running with redirected stdin will fail with "Raw mode is not supported". + +## Architecture + +The project is split into three layers. **Do not let UI concerns leak into the logic layers, and do not call `child_process.spawn` from anywhere other than the runner** — that single chokepoint is what gives the TUI its uniform spinner/log behavior. + +### 1. Pure logic — `src/lib/` + +- `frp-config.js` (CJS) — parser/renderer for `frpc.toml`. Lossy TOML-ish: accepts both legacy `[name]` headers and modern `[[proxies]]` arrays, migrates legacy ini keys (`server_addr` → `serverAddr`, `token` → `auth.token`, etc.) on read, and always emits the modern form. Internal proxy values are snake_case (`local_ip`, `local_port`, `remote_port`) but rendered camelCase (`localIP`, `localPort`, `remotePort`). `createUniqueProxyName` appends a random 8-char hex suffix to avoid collisions. +- `tasks.js` (CJS) — plan builders. Each public function (`zshInstallPlan`, `sshInstallPlan`, `frpInstallPlan`, `frpInitConfigPlan`, `frpAddProxyPlan`, `frpRemoveProxyPlan`, `frpRestartPlan`, `bootstrapPlan`) returns `{ title, steps[] }`. Steps come in three kinds: `run` (spawn a command), `write` (write a file, possibly via `sudo tee`), `zshrc-plugins` (in-place edit of `~/.zshrc`). Plan builders may read the filesystem (e.g. to preserve existing proxies on `--force` re-init) and may throw — call sites must catch. +- `runner.js` (CJS) — `startRun(steps)` returns an EventEmitter-backed object that executes the step list and emits `step-start`, `log` (stdout/stderr lines), `step-done`, and `done` events. The runner owns all `child_process.spawn` calls and is the only place that touches stdio. Privileged writes go through `sudo tee` when `canWriteDirectly` returns false. `cancel()` SIGTERMs the active child. + +The tests (`test/frp-config.test.js`) only exercise `frp-config.js`. They're plain `node:test` and import via `require`, so keep the lib modules in CJS. + +### 2. Ink screens — `src/screens/*.jsx` + +A small screen-stack lives in `src/app.jsx`: `{stack: Screen[]}` with `push`, `replace`, `back`, `home`. Each screen is a self-contained component that takes `nav` (the navigation helpers) and any screen-specific props. Adding a new screen = new file in `src/screens/`, new `case` in the `renderScreen` switch in `app.jsx`. + +Screens in dependency order: +- `MainMenu` → `FrpMenu` (submenu) or `PlanPreview` (for zsh/ssh) or `TokenPrompt` (for bootstrap). +- `TokenPrompt` → `FrpConfigForm` (collects `serverAddr`/`serverPort`/`installDir`). +- `FrpConfigForm` / `ProxyForm` build a plan and `replace` to `PlanPreview`. +- `ProxyList` → `PlanPreview` (for a remove plan). +- `PlanPreview` → `RunLog` (the only place that calls `runner.startRun`). +- `RunLog` subscribes to runner events with `useReducer`; the last `MAX_LOG_LINES` (12) lines of streamed output are shown in a bordered scrollback. + +Forms use a `step` index plus arrow-key/Tab traversal between `` widgets. Avoid pulling in a forms library — the pattern is small enough to keep inline. + +### 3. Entry/build — `bin/`, `dist/`, esbuild + +`bin/server-config.js` is a CJS shim that `import()`s `dist/app.mjs`. The bundle is ESM because Ink 7 + yoga-layout use top-level await, which esbuild cannot lower to CJS — that's why `--format=esm` is non-negotiable. + +Two esbuild quirks worth knowing: + +1. **`react-devtools-core` is aliased to a stub** (`src/stubs/react-devtools-core.js`). Ink imports it behind a `process.env.DEV === "true"` check, but esbuild hoists ESM imports to module-load time, so we'd otherwise execute the real package's UMD preamble (which references `self`) and crash. Keep the alias; do not change to `--external` (that reintroduces the crash at runtime). +2. The `--banner:js` injects a `createRequire` so CJS lib modules called from the bundle can still use `require(...)`. Don't drop it without also rewriting the libs to ESM. + +The bundle is ~1.8 MB — that's mostly React + Yoga, not something to fight. + +## Sensitive values & defaults + +The FRP token comes from the TokenPrompt screen or the `FRP_TOKEN` env var (the screen pre-fills from env). It is never logged or persisted outside the rendered `frpc.toml`. Defaults that callers depend on: server `81.70.134.9:15443`, install dir `/opt/frp/frp__linux_`, systemd unit `frpc`, frp version `0.58.1`. Changing any is a breaking change for existing deployments. + +## When adding a new task type + +1. Add a plan builder in `src/lib/tasks.js` returning `{ title, steps }`. +2. If you need a new step kind (beyond `run` / `write` / `zshrc-plugins`), extend the `runStep` switch in `src/lib/runner.js` and `formatStep` for the preview label. +3. Add a screen (or extend an existing menu) that constructs the plan and pushes `{ name: "plan", props: { plan } }`. + +Never spawn commands directly from a screen — always build a plan and route it through `PlanPreview` → `RunLog` so the user sees what's about to happen and gets uniform log/spinner UI. diff --git a/README.md b/README.md index a95411e..e59d2e9 100644 --- a/README.md +++ b/README.md @@ -1,146 +1,114 @@ # server-config-cli -一个用于初始化服务器环境的 Node.js CLI,覆盖 zsh、openssh-server 和 frp client 配置。frp 支持通过命令追加端口穿透配置。 +一个用 [Ink](https://github.com/vadimdemedes/ink) 写的交互式 TUI,用来初始化服务器:zsh + oh-my-zsh + nvm、openssh-server、frp 客户端及其端口穿透配置。 -## 安装和本地使用 +启动后是菜单式界面,没有传统的子命令/flag —— 所有选项、token、端口都在 TUI 里输入。 + +## 安装 ```bash -npm link -server-config --help +npm install +npm run build # 必须先打包,bin/server-config.js 会加载 dist/app.mjs +npm link # 可选;之后可以直接 server-config 调起 ``` -也可以不 link,直接在项目目录执行: +也可以不 link,直接: ```bash -node bin/server-config.js --help +node bin/server-config.js ``` -建议先用 `--dry-run` 看要执行的命令: +要求 Node ≥ 18(建议 20+),并且必须在真正的终端里运行(stdin 需要 TTY,否则 Ink 报 "Raw mode is not supported")。 -```bash -server-config zsh install --dry-run -server-config ssh install --dry-run -server-config frp install --token "$FRP_TOKEN" --dry-run +## 启动后界面 + +``` +╭───────────────────────────────────────╮ +│ server-config zsh · ssh · frp client │ +╰───────────────────────────────────────╯ + +Choose an action: + +❯ Install zsh + oh-my-zsh + nvm + Install OpenSSH server + FRP setup ▸ + Bootstrap (zsh + ssh + frp) + Quit + +↑↓ select · Enter confirm · q quit ``` -## zsh +通用键位: -```bash -server-config zsh install -``` +- `↑` `↓` 移动光标,`Enter` 确认 +- `Tab` / `↓` 在表单字段间切换,`↑` 回上一项 +- `Esc` 返回上一屏(运行中除外) +- 在主菜单按 `q` 退出 -会执行: +## 各动作做什么 -- `apt update && apt upgrade` -- 安装 `zsh git curl wget` -- 切换当前用户 shell 到 `/bin/zsh` -- 安装 oh-my-zsh -- 安装 nvm,并执行 `nvm install --lts` -- 安装 `zsh-autosuggestions` 和 `zsh-syntax-highlighting` -- 更新 `~/.zshrc` 的插件列表为 `git zsh-autosuggestions zsh-syntax-highlighting` +### Install zsh + oh-my-zsh + nvm -安装完成后重新登录 shell,或手动执行: +会依次执行: -```bash -source ~/.zshrc -``` +- `apt update` + `apt upgrade -y` +- 装 `zsh git curl wget` +- `chsh -s /bin/zsh` +- 从 gitee 镜像装 oh-my-zsh(`RUNZSH=no CHSH=no KEEP_ZSHRC=yes`) +- 装 nvm(`v0.40.4`)并 `nvm install --lts`、`nvm alias default lts/*` +- 安装 `zsh-autosuggestions` 和 `zsh-syntax-highlighting`(已存在则 `git pull`) +- 把 `~/.zshrc` 的 `plugins=(...)` 改成 `git zsh-autosuggestions zsh-syntax-highlighting` -## ssh +完成后请 `source ~/.zshrc` 或重开终端。 -```bash -server-config ssh install -``` +### Install OpenSSH server -会安装 `openssh-server`,并执行 `systemctl enable --now ssh`。 +`apt install openssh-server -y` + `systemctl enable --now ssh`。 -## frp +### FRP setup -不要把 frp token 写死到仓库。使用环境变量或命令参数传入: +进入子菜单: -```bash -export FRP_TOKEN="your-token" -server-config frp install --token "$FRP_TOKEN" -``` +- **Install frp client + service** — 下载 `frp_0.58.1_linux_amd64.tar.gz`,解压、放到 `/opt/frp/frp_0.58.1_linux_amd64/`,写 `frpc.toml` 和 systemd unit,然后 `systemctl enable --now frpc`。需要输入 token,也可以通过 `FRP_TOKEN` 环境变量预填。 +- **Init / rewrite frpc.toml** — 仅重写配置(保留已有代理段)。 +- **Add proxy** — 表单式输入名字 / 类型 / local IP / local port / remote port,写入的实际 name 会带 8 位随机后缀(例如 `ssh-aaa12312`),避免重名。可选执行后重启 frpc。 +- **List / remove proxies** — 选中即生成删除计划,预览后确认执行。 +- **Restart frpc** — `systemctl restart frpc`。 -默认配置: +### Bootstrap + +按顺序执行 zsh + ssh + frp 三套,token 走同一个输入。 + +## 计划预览和执行 + +任何会改系统的动作都会先停在 "Plan" 屏:列出每一步要做什么(命令或写文件),可以选 **Run now** 或 **Cancel**。 + +确认运行后进入实时日志屏:每一步显示状态图标(◐ 运行中 / ✓ 成功 / ✗ 失败),底部 12 行滚动展示子进程 stdout/stderr。任何一步失败立刻停止后续步骤。 + +## 默认值 -- `serverAddr = "81.70.134.9"` -- `serverPort = 15443` -- `auth.token = "$FRP_TOKEN"` -- `transport.tls.enable = false` -- `transport.tcpMux = true` -- `log.to = "/var/log/frpc.log"` -- `log.level = "info"` -- `log.maxDays = 7` - frp 版本:`0.58.1` -- 安装目录:`/opt/frp/frp_0.58.1_linux_amd64` -- systemd 服务:`frpc` +- 安装目录:`/opt/frp/frp__linux_amd64` +- 配置文件:`/frpc.toml` +- 服务名:`frpc` +- 默认服务端:`81.70.134.9:15443` +- 日志:`/var/log/frpc.log`,level `info`,保留 7 天 +- `transport.tcpMux = true`,`transport.tls.enable = false` -只初始化配置文件: +`serverAddr` / `serverPort` / `installDir` 在 FRP 配置表单里都可以改。如果要改更深的配置(log 路径、TLS 等),目前需要直接编辑 `src/lib/tasks.js` 里的 `buildGlobalsFromOptions`。 + +## 开发 ```bash -server-config frp init --token "$FRP_TOKEN" +npm run build:watch # 文件变了自动 rebuild +npm test # 跑 frp-config 解析/渲染的单元测试 +npm run check # 对所有手写 .js 做 node --check ``` -如果配置文件已经存在,默认不会覆盖。需要覆盖时加: +代码分三层: -```bash -server-config frp init --token "$FRP_TOKEN" --force -``` +- `src/lib/` — 纯逻辑(CJS),没有 Ink/React 依赖:`frp-config.js` 解析渲染,`tasks.js` 输出计划,`runner.js` 执行并发事件。 +- `src/screens/` — Ink/React 组件,每个屏一个文件。 +- `src/app.jsx` — 入口 + screen stack。 -### 增加端口穿透 - -SSH 示例,本机 ssh 监听 `22`,远端暴露 `17227`: - -```bash -server-config frp add ssh --local-port 22 --remote-port 17227 --restart -``` - -实际写入 frp 的 `name` 会自动追加随机后缀,例如 `ssh-aaa12312`,避免与已有代理重名。删除代理时使用 `server-config frp list` 看到的完整名称。 - -MySQL 示例: - -```bash -server-config frp add mysql --local-port 3306 --remote-port 33061 --restart -``` - -如果本地服务不在 `127.0.0.1`,可以指定: - -```bash -server-config frp add web --local-ip 0.0.0.0 --local-port 8080 --remote-port 18080 --restart -``` - -查看当前代理: - -```bash -server-config frp list -``` - -删除代理: - -```bash -server-config frp remove mysql-aaa12312 --restart -``` - -重启 frpc: - -```bash -server-config frp restart -``` - -### 自定义路径 - -```bash -server-config frp install \ - --token "$FRP_TOKEN" \ - --install-dir /home/scyk/frp_0.58.1_linux_amd64 \ - --config /home/scyk/frp_0.58.1_linux_amd64/frpc.toml -``` - -## 一键初始化 - -```bash -server-config bootstrap --token "$FRP_TOKEN" -``` - -会依次执行 zsh、ssh 和 frp 安装。 +esbuild 把 `src/app.jsx` 打到 `dist/app.mjs`(ESM 输出,因为 Ink 7 / yoga-layout 用了 top-level await)。 diff --git a/USAGE.md b/USAGE.md new file mode 100644 index 0000000..c637c83 --- /dev/null +++ b/USAGE.md @@ -0,0 +1,70 @@ +# 使用说明 + +## 安装 + +```bash +npm install +npm run build +npm link # 可选,之后可以全局用 server-config +``` + +要求:Node ≥ 18,真实终端(不能 pipe stdin)。 + +## 启动 + +```bash +server-config # 或:node bin/server-config.js +``` + +进入交互式菜单。**没有命令行参数**,所有输入都在 TUI 里完成。 + +## 键位 + +| 键 | 作用 | +|----|------| +| `↑` `↓` | 移动光标 | +| `Enter` | 确认 | +| `Tab` / `↓` | 表单字段切换 | +| `Space` | 切换勾选项(如 "Restart frpc") | +| `Esc` | 返回上一屏 | +| `q` | 在主菜单退出 | + +## 菜单 + +主菜单: + +- **Install zsh + oh-my-zsh + nvm** — 一键装 zsh / 插件 / nvm + LTS Node。 +- **Install OpenSSH server** — 装 openssh-server 并 enable。 +- **FRP setup ▸** — 进入 frp 子菜单。 +- **Bootstrap** — 顺序执行 zsh + ssh + frp install,token 走同一次输入。 + +FRP 子菜单: + +- **Install frp client + service** — 下载安装 frp + 写配置 + 装 systemd unit + 启动。需要 token。 +- **Init / rewrite frpc.toml** — 仅重写配置,保留已有代理。 +- **Add proxy** — 填名字 / 类型 / 本地 IP+端口 / 远端端口,写入的实际 name 会带 8 位随机后缀。 +- **List / remove proxies** — 列出现有代理,选中即删。 +- **Restart frpc** — `systemctl restart frpc`。 + +## 流程 + +任何会改系统的动作都是两步: + +1. **Plan 屏** — 列出每一步要做什么(命令或写文件),选 `Run now` 或 `Cancel`。 +2. **Run 屏** — 实时显示 ◐/✓/✗ 状态和子进程输出。失败立刻停止。 + +## 环境变量 + +- `FRP_TOKEN` — 启动前 export,TokenPrompt 会自动填好,省去手输。 + +## 注意 + +- sudo 没有交互密码框。事先 `sudo -v` 缓存凭据,或配 NOPASSWD,否则带 sudo 的步骤会失败。 +- 默认 frp 服务端 `81.70.134.9:15443`,安装到 `/opt/frp/frp_0.58.1_linux_amd64/`,systemd 服务名 `frpc`。 + +## 开发 + +```bash +npm run build:watch # 监听重建 +npm test # 跑解析器单元测试 +``` diff --git a/bin/server-config.js b/bin/server-config.js index d025d15..33271f5 100755 --- a/bin/server-config.js +++ b/bin/server-config.js @@ -1,3 +1,20 @@ #!/usr/bin/env node +"use strict"; -require("../src/cli").main(process.argv.slice(2)); +const fs = require("node:fs"); +const path = require("node:path"); +const { pathToFileURL } = require("node:url"); + +const bundle = path.join(__dirname, "..", "dist", "app.mjs"); + +if (!fs.existsSync(bundle)) { + process.stderr.write( + "server-config: bundle not built. Run `npm run build` from the package root first.\n", + ); + process.exit(1); +} + +import(pathToFileURL(bundle).href).catch((error) => { + process.stderr.write(`server-config: ${error?.stack || error}\n`); + process.exit(1); +}); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f83d934 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1187 @@ +{ + "name": "server-config-cli", + "version": "0.2.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "server-config-cli", + "version": "0.2.0", + "license": "MIT", + "dependencies": { + "ink": "^7.0.2", + "ink-select-input": "^6.2.0", + "ink-spinner": "^5.0.0", + "ink-text-input": "^6.0.0", + "react": "^19.2.0", + "react-devtools-core": "^7.0.1" + }, + "bin": { + "scc": "bin/server-config.js", + "server-config": "bin/server-config.js" + }, + "devDependencies": { + "esbuild": "^0.28.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@alcalzone/ansi-tokenize": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.3.0.tgz", + "integrity": "sha512-p+CMKJ93HFmLkjXKlXiVGlMQEuRb6H0MokBSwUsX+S6BRX8eV5naFZpQJFfJHjRZY0Hmnqy1/r6UWl3x+19zYA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/auto-bind": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", + "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-boxes": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-4.0.1.tgz", + "integrity": "sha512-5IOn+jcCEHEraYolBPs/sT4BxYCe2nHg374OPiItB1O96KZFseS2gthU4twyYzeDcFew4DaUM/xwc5BQf08JJw==", + "license": "MIT", + "engines": { + "node": ">=18.20 <19 || >=20.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-6.0.0.tgz", + "integrity": "sha512-3+YKIUFsohD9MIoOFPFBldjAlnfCmCDcqe6aYGFqlDTRKg80p4wg35L+j83QQ63iOlKRccEkbn8IuM++HsgEjA==", + "license": "MIT", + "dependencies": { + "slice-ansi": "^9.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/code-excerpt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", + "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", + "license": "MIT", + "dependencies": { + "convert-to-spaces": "^2.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/convert-to-spaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", + "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es-toolkit": { + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.1.tgz", + "integrity": "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ink": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/ink/-/ink-7.0.2.tgz", + "integrity": "sha512-cnkE2SsDC/gieJ+BD8+gWpXrZPMInv7agBYN5gcKVlQZYp+IKa/FKM5bp1OIuJFp3ZIuRK7ZNxY4MZR3tUzyfQ==", + "license": "MIT", + "dependencies": { + "@alcalzone/ansi-tokenize": "^0.3.0", + "ansi-escapes": "^7.3.0", + "ansi-styles": "^6.2.3", + "auto-bind": "^5.0.1", + "chalk": "^5.6.2", + "cli-boxes": "^4.0.1", + "cli-cursor": "^4.0.0", + "cli-truncate": "^6.0.0", + "code-excerpt": "^4.0.0", + "es-toolkit": "^1.45.1", + "indent-string": "^5.0.0", + "is-in-ci": "^2.0.0", + "patch-console": "^2.0.0", + "react-reconciler": "^0.33.0", + "scheduler": "^0.27.0", + "signal-exit": "^3.0.7", + "slice-ansi": "^9.0.0", + "stack-utils": "^2.0.6", + "string-width": "^8.2.0", + "terminal-size": "^4.0.1", + "type-fest": "^5.5.0", + "widest-line": "^6.0.0", + "wrap-ansi": "^10.0.0", + "ws": "^8.20.0", + "yoga-layout": "~3.2.1" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "@types/react": ">=19.2.0", + "react": ">=19.2.0", + "react-devtools-core": ">=6.1.2" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-devtools-core": { + "optional": true + } + } + }, + "node_modules/ink-select-input": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ink-select-input/-/ink-select-input-6.2.0.tgz", + "integrity": "sha512-304fZXxkpYxJ9si5lxRCaX01GNlmPBgOZumXXRnPYbHW/iI31cgQynqk2tRypGLOF1cMIwPUzL2LSm6q4I5rQQ==", + "license": "MIT", + "dependencies": { + "figures": "^6.1.0", + "to-rotated": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "ink": ">=5.0.0", + "react": ">=18.0.0" + } + }, + "node_modules/ink-spinner": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ink-spinner/-/ink-spinner-5.0.0.tgz", + "integrity": "sha512-EYEasbEjkqLGyPOUc8hBJZNuC5GvXGMLu0w5gdTNskPc7Izc5vO3tdQEYnzvshucyGCBXc86ig0ujXPMWaQCdA==", + "license": "MIT", + "dependencies": { + "cli-spinners": "^2.7.0" + }, + "engines": { + "node": ">=14.16" + }, + "peerDependencies": { + "ink": ">=4.0.0", + "react": ">=18.0.0" + } + }, + "node_modules/ink-text-input": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ink-text-input/-/ink-text-input-6.0.0.tgz", + "integrity": "sha512-Fw64n7Yha5deb1rHY137zHTAbSTNelUKuB5Kkk2HACXEtwIHBCf9OH2tP/LQ9fRYTl1F0dZgbW0zPnZk6FA9Lw==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "type-fest": "^4.18.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "ink": ">=5", + "react": ">=18" + } + }, + "node_modules/ink-text-input/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-in-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-2.0.0.tgz", + "integrity": "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==", + "license": "MIT", + "bin": { + "is-in-ci": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/patch-console": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", + "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/react": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-devtools-core": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-7.0.1.tgz", + "integrity": "sha512-C3yNvRHaizlpiASzy7b9vbnBGLrhvdhl1CbdU6EnZgxPNbai60szdLtl+VL76UNOt5bOoVTOz5rNWZxgGt+Gsw==", + "license": "MIT", + "dependencies": { + "shell-quote": "^1.6.1", + "ws": "^7" + } + }, + "node_modules/react-devtools-core/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/react-reconciler": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.33.0.tgz", + "integrity": "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/slice-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-9.0.0.tgz", + "integrity": "sha512-SO/3iYL5S3W57LLEniscOGPZgOqZUPCx6d3dB+52B80yJ0XstzsC/eV8gnA4tM3MHDrKz+OCFSLNjswdSC+/bA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", + "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terminal-size": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/terminal-size/-/terminal-size-4.0.1.tgz", + "integrity": "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/to-rotated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-rotated/-/to-rotated-1.0.0.tgz", + "integrity": "sha512-KsEID8AfgUy+pxVRLsWp0VzCa69wxzUDZnzGbyIST/bcgcrMvTYoFBX/QORH4YApoD89EDuUovx4BTdpOn319Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-fest": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.6.0.tgz", + "integrity": "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==", + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-6.0.0.tgz", + "integrity": "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA==", + "license": "MIT", + "dependencies": { + "string-width": "^8.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-10.0.0.tgz", + "integrity": "sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "string-width": "^8.2.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "license": "MIT" + } + } +} diff --git a/package.json b/package.json index 3e87a67..f97ebfa 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,30 @@ { "name": "server-config-cli", - "version": "0.1.0", - "description": "Node.js CLI for bootstrapping server shell, ssh and frp client configuration.", + "version": "0.2.0", + "description": "Ink-based TUI for bootstrapping server shell, ssh and frp client configuration.", "bin": { "server-config": "bin/server-config.js", "scc": "bin/server-config.js" }, "scripts": { - "check": "node --check bin/server-config.js && node --check src/cli.js && node --check test/frp-config.test.js", - "test": "node --test" + "build": "esbuild src/app.jsx --bundle --platform=node --target=node18 --format=esm --jsx=automatic --outfile=dist/app.mjs --alias:react-devtools-core=./src/stubs/react-devtools-core.js --banner:js=\"import { createRequire as __cR } from 'node:module'; const require = __cR(import.meta.url);\"", + "build:watch": "npm run build -- --watch", + "check": "node --check bin/server-config.js && node --check src/lib/frp-config.js && node --check src/lib/runner.js && node --check src/lib/tasks.js && node --check test/frp-config.test.js", + "test": "node --test test/*.test.js" }, "engines": { "node": ">=18" }, + "dependencies": { + "ink": "^7.0.2", + "ink-select-input": "^6.2.0", + "ink-spinner": "^5.0.0", + "ink-text-input": "^6.0.0", + "react": "^19.2.0", + "react-devtools-core": "^7.0.1" + }, + "devDependencies": { + "esbuild": "^0.28.0" + }, "license": "MIT" } diff --git a/src/app.jsx b/src/app.jsx new file mode 100644 index 0000000..c87b16a --- /dev/null +++ b/src/app.jsx @@ -0,0 +1,106 @@ +import React, { useState, useCallback } from "react"; +import { render, Box, Text, useApp, useInput } from "ink"; + +import MainMenu from "./screens/MainMenu.jsx"; +import FrpMenu from "./screens/FrpMenu.jsx"; +import TokenPrompt from "./screens/TokenPrompt.jsx"; +import ProxyForm from "./screens/ProxyForm.jsx"; +import ProxyList from "./screens/ProxyList.jsx"; +import PlanPreview from "./screens/PlanPreview.jsx"; +import RunLog from "./screens/RunLog.jsx"; +import FrpConfigForm from "./screens/FrpConfigForm.jsx"; + +function App() { + const { exit } = useApp(); + const [stack, setStack] = useState([{ name: "main" }]); + const screen = stack[stack.length - 1]; + + const push = useCallback((next) => { + setStack((current) => [...current, next]); + }, []); + + const replace = useCallback((next) => { + setStack((current) => [...current.slice(0, -1), next]); + }, []); + + const back = useCallback(() => { + setStack((current) => (current.length > 1 ? current.slice(0, -1) : current)); + }, []); + + const home = useCallback(() => { + setStack([{ name: "main" }]); + }, []); + + useInput((input, key) => { + if (key.escape && stack.length > 1 && screen.name !== "run") { + back(); + } + if (input === "q" && screen.name === "main") { + exit(); + } + }); + + const nav = { push, replace, back, home, exit }; + + return ( + +
+ + {renderScreen(screen, nav)} + +