@lumenkey/cli 0.3.1 → 0.4.0-rc.1
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.
- package/README.md +36 -2
- package/dist/cli/copied.js +86 -85
- package/dist/cli/index.js +56 -44
- package/dist/cli/render.js +3 -2
- package/dist/core/asset-stream.d.ts +16 -0
- package/dist/core/asset-stream.js +64 -0
- package/dist/core/atomic-config-file.js +8 -7
- package/dist/core/chinese-messages.d.ts +4 -0
- package/dist/core/chinese-messages.js +907 -0
- package/dist/core/chinese-script.d.ts +7 -0
- package/dist/core/chinese-script.js +28 -0
- package/dist/core/client.js +2 -1
- package/dist/core/config-dir.js +2 -1
- package/dist/core/config.js +7 -6
- package/dist/core/download-root.d.ts +5 -0
- package/dist/core/download-root.js +64 -0
- package/dist/core/download.js +15 -12
- package/dist/core/generation-input.js +3 -2
- package/dist/core/generation.d.ts +8 -1
- package/dist/core/generation.js +13 -10
- package/dist/core/job-display.js +7 -6
- package/dist/core/locale.js +163 -148
- package/dist/core/outcome.js +6 -4
- package/dist/core/output-path.d.ts +3 -1
- package/dist/core/output-path.js +5 -2
- package/dist/core/params.js +26 -25
- package/dist/core/profiles.js +5 -4
- package/dist/core/set-values.d.ts +0 -1
- package/dist/core/set-values.js +7 -7
- package/dist/core/submission-journal.js +31 -16
- package/dist/core/version.js +2 -3
- package/dist/mcp/server.js +30 -29
- package/dist/studio/asset-integrity-hash.d.ts +28 -0
- package/dist/studio/asset-integrity-hash.js +93 -0
- package/dist/studio/asset-integrity-queue.d.ts +24 -0
- package/dist/studio/asset-integrity-queue.js +94 -0
- package/dist/studio/asset-store.d.ts +52 -0
- package/dist/studio/asset-store.js +352 -0
- package/dist/studio/canvas/LICENSES.txt +1590 -0
- package/dist/studio/canvas/dependencies.json +862 -0
- package/dist/studio/canvas/director-source-manifest.json +47 -0
- package/dist/studio/canvas/editor.css +239 -0
- package/dist/studio/canvas/editor.js +4613 -0
- package/dist/studio/canvas/integration-source-manifest.json +384 -0
- package/dist/studio/canvas/source-manifest.json +240 -0
- package/dist/studio/canvas-store.d.ts +1254 -0
- package/dist/studio/canvas-store.js +205 -0
- package/dist/studio/capture-placement.d.ts +11 -0
- package/dist/studio/capture-placement.js +85 -0
- package/dist/studio/cloud-contract.d.ts +128 -0
- package/dist/studio/cloud-contract.js +229 -0
- package/dist/studio/cloud-service.d.ts +178 -0
- package/dist/studio/cloud-service.js +202 -0
- package/dist/studio/director-captures.d.ts +59 -0
- package/dist/studio/director-captures.js +141 -0
- package/dist/studio/director-contract.d.ts +1489 -0
- package/dist/studio/director-contract.js +288 -0
- package/dist/studio/director-png.d.ts +6 -0
- package/dist/studio/director-png.js +41 -0
- package/dist/studio/director-relations.d.ts +40 -0
- package/dist/studio/director-relations.js +103 -0
- package/dist/studio/document-authority.d.ts +7 -0
- package/dist/studio/document-authority.js +50 -0
- package/dist/studio/draft-store.d.ts +95 -0
- package/dist/studio/draft-store.js +100 -0
- package/dist/studio/errors.d.ts +85 -0
- package/dist/studio/errors.js +94 -0
- package/dist/studio/launch.d.ts +5 -0
- package/dist/studio/launch.js +31 -0
- package/dist/studio/media.d.ts +9 -0
- package/dist/studio/media.js +50 -0
- package/dist/studio/output-save-contract.d.ts +1321 -0
- package/dist/studio/output-save-contract.js +26 -0
- package/dist/studio/output-save-journal.d.ts +971 -0
- package/dist/studio/output-save-journal.js +287 -0
- package/dist/studio/output-save-recovery.d.ts +18 -0
- package/dist/studio/output-save-recovery.js +161 -0
- package/dist/studio/output-save-service.d.ts +436 -0
- package/dist/studio/output-save-service.js +251 -0
- package/dist/studio/output-save-worker.d.ts +40 -0
- package/dist/studio/output-save-worker.js +360 -0
- package/dist/studio/paths.d.ts +6 -0
- package/dist/studio/paths.js +68 -0
- package/dist/studio/run-journal.d.ts +993 -0
- package/dist/studio/run-journal.js +125 -0
- package/dist/studio/server.d.ts +17 -0
- package/dist/studio/server.js +440 -0
- package/dist/studio/session.d.ts +6 -0
- package/dist/studio/session.js +26 -0
- package/dist/studio/shell.d.ts +3 -0
- package/dist/studio/shell.js +23 -0
- package/dist/studio/style.d.ts +2 -0
- package/dist/studio/style.js +29 -0
- package/dist/studio/types.d.ts +26 -0
- package/dist/studio/types.js +2 -0
- package/dist/studio/web/app.d.ts +2 -0
- package/dist/studio/web/app.js +662 -0
- package/dist/studio/web/asset-controller.d.ts +37 -0
- package/dist/studio/web/asset-controller.js +191 -0
- package/dist/studio/web/asset-model.d.ts +29 -0
- package/dist/studio/web/asset-model.js +51 -0
- package/dist/studio/web/controller.d.ts +113 -0
- package/dist/studio/web/controller.js +401 -0
- package/dist/studio/web/messages.d.ts +2 -0
- package/dist/studio/web/messages.js +74 -0
- package/dist/studio/web/model.d.ts +75 -0
- package/dist/studio/web/model.js +182 -0
- package/dist/studio/web/save-controller.d.ts +85 -0
- package/dist/studio/web/save-controller.js +495 -0
- package/dist/studio/web/save-model.d.ts +63 -0
- package/dist/studio/web/save-model.js +64 -0
- package/dist/studio/web/save-queue.d.ts +7 -0
- package/dist/studio/web/save-queue.js +62 -0
- package/dist/studio/web/save-view.d.ts +9 -0
- package/dist/studio/web/save-view.js +19 -0
- package/dist/studio/writer-lock.d.ts +5 -0
- package/dist/studio/writer-lock.js +88 -0
- package/package.json +11 -5
- package/dist/cli/copied.d.ts.map +0 -1
- package/dist/cli/copied.js.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/render.d.ts.map +0 -1
- package/dist/cli/render.js.map +0 -1
- package/dist/core/atomic-config-file.d.ts.map +0 -1
- package/dist/core/atomic-config-file.js.map +0 -1
- package/dist/core/chat.d.ts.map +0 -1
- package/dist/core/chat.js.map +0 -1
- package/dist/core/client.d.ts.map +0 -1
- package/dist/core/client.js.map +0 -1
- package/dist/core/config-dir.d.ts.map +0 -1
- package/dist/core/config-dir.js.map +0 -1
- package/dist/core/config-error.d.ts.map +0 -1
- package/dist/core/config-error.js.map +0 -1
- package/dist/core/config.d.ts.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/credit-cap.d.ts.map +0 -1
- package/dist/core/credit-cap.js.map +0 -1
- package/dist/core/download.d.ts.map +0 -1
- package/dist/core/download.js.map +0 -1
- package/dist/core/error-result.d.ts.map +0 -1
- package/dist/core/error-result.js.map +0 -1
- package/dist/core/explain.d.ts.map +0 -1
- package/dist/core/explain.js.map +0 -1
- package/dist/core/generation-input.d.ts.map +0 -1
- package/dist/core/generation-input.js.map +0 -1
- package/dist/core/generation-result.d.ts.map +0 -1
- package/dist/core/generation-result.js.map +0 -1
- package/dist/core/generation.d.ts.map +0 -1
- package/dist/core/generation.js.map +0 -1
- package/dist/core/job-display.d.ts.map +0 -1
- package/dist/core/job-display.js.map +0 -1
- package/dist/core/locale.d.ts.map +0 -1
- package/dist/core/locale.js.map +0 -1
- package/dist/core/origin.d.ts.map +0 -1
- package/dist/core/origin.js.map +0 -1
- package/dist/core/outcome.d.ts.map +0 -1
- package/dist/core/outcome.js.map +0 -1
- package/dist/core/output-path.d.ts.map +0 -1
- package/dist/core/output-path.js.map +0 -1
- package/dist/core/params.d.ts.map +0 -1
- package/dist/core/params.js.map +0 -1
- package/dist/core/poller.d.ts.map +0 -1
- package/dist/core/poller.js.map +0 -1
- package/dist/core/profiles.d.ts.map +0 -1
- package/dist/core/profiles.js.map +0 -1
- package/dist/core/set-values.d.ts.map +0 -1
- package/dist/core/set-values.js.map +0 -1
- package/dist/core/submission-journal.d.ts.map +0 -1
- package/dist/core/submission-journal.js.map +0 -1
- package/dist/core/types.d.ts.map +0 -1
- package/dist/core/types.js.map +0 -1
- package/dist/core/version.d.ts.map +0 -1
- package/dist/core/version.js.map +0 -1
- package/dist/core/wait.d.ts.map +0 -1
- package/dist/core/wait.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/mcp/render.d.ts.map +0 -1
- package/dist/mcp/render.js.map +0 -1
- package/dist/mcp/server.d.ts.map +0 -1
- package/dist/mcp/server.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @lumenkey/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
本候选版:**0.4.0-rc.1**(CLI、MCP、本机 Studio、画布与 3D 导演台);下文 0.2.3 均为旧版行为对照。候选版不代表已发布。
|
|
4
4
|
|
|
5
5
|
> **光钥 (LumenKey) 官方 CLI 命令行工具 & 智能体 MCP (Model Context Protocol) 统一服务包**
|
|
6
6
|
> 一套代码同构支持:**人类终端命令行 (CLI)** 与 **AI Agent (Cursor / Claude Code / Codex / Antigravity) 原生工具挂载 (MCP Server)**。
|
|
@@ -103,6 +103,12 @@ CLI `--json` 与 MCP 工具结果使用**同一个结果对象**(MCP 的 `cont
|
|
|
103
103
|
|
|
104
104
|
供人阅读的短 `message` 及 CLI 错误显示使用**执行机本机时区**,附 `±HH:MM` 偏移;例如 `2026-10-16 17:47 (-07:00)`。机器字段 `resumeDeadline` 保持 UTC。已过期、记录丢失或幂等冲突时不提供 `recoveryCommand`;先核对任务列表与余额,不能靠换键重提来解决 unknown。
|
|
105
105
|
|
|
106
|
+
过期的待确认提交统一返回 `code: "SUBMISSION_RECORD_EXPIRED"`、`recovery: "expired"` 和原记录的 `resumeDeadline`,CLI 与 MCP 相同。提前拒绝时 `submission: "none"` 仅说明这次没有重发,不证明历史提交未受理;若请求发出后才超过窗口,仍保留 `submission: "unknown"`。两种情况都不提供 `recoveryCommand`,不自动创建新任务。
|
|
107
|
+
|
|
108
|
+
内部待确认记录的绝对路径不会出现在 `message`、`detail`、`error`、通知或 stderr,损坏记录用 `record:<短哈希>` 定位。配置文件与用户指定的下载目标仍可通过明确的 `path` 字段及兼容的配置错误文本显示,便于用户修正自己的路径;下载底层异常不会直接透传。该例外不适用于内部提交记录。
|
|
109
|
+
|
|
110
|
+
中文简繁跟随设备:`LC_ALL` → `LC_MESSAGES` → `LANG` → Windows UI 语言 → Intl locale;`zh-TW`、`zh-HK`、`zh-MO`、`zh-Hant` 使用繁体,其余回落简体。可用 `LUMENKEY_ZH_SCRIPT=Hans|Hant` 明确覆写。此设置只选择 CLI/MCP 内置文案表,不转换用户输入、路径、模型回复或错误码;`--lang en` / `LUMENKEY_LANG=en` 仍选择英文。
|
|
111
|
+
|
|
106
112
|
**提交后的真金字段。** 只要请求了 `--confirm`,无论成功失败,结果都带 `submission`;一旦拿到报价就带 `quoteId`,一旦决定了幂等键就带 `idempotencyKey`(报价之前的失败——参数错误、没有密钥、目录不可写——只带 `submission:"none"`,没有键和报价)。`submission`:`"created"` 已创建任务(带 `jobId`)、`"none"` 什么都没创建(请求没发出,或网关返回了格式正确的 4xx)、`"unknown"` 请求已发出但没有拿到格式正确的回答(连接中断、客户端超时、任何 5xx、2xx 但响应不合约)。旧的布尔 `submitted` 只在成功结果上保留(报价 `false`、已建单 `true`),已标记弃用,不会出现在失败结果里。`"unknown"` 时网关可能已受理并冻结算力点:**不要换键重提**。客户端在 POST 前把这次提交(幂等键、quoteId、projectId、模型与输入、状态、网关地址与密钥的 SHA-256 指纹,不含密钥本身)原子写入 `~/.lumenkey/pending-submissions/`;记录只对创建它的账号和网关有效:换档案后不会自动接续别人的记录,显式带别人的键会本地返回 `SUBMISSION_CREDENTIALS_MISMATCH` 且不发请求、不动记录;同一账号换了密钥也不再匹配(记录保留并在 24 小时后过期)。观察到 `"unknown"` 后记录改为 unknown 状态。之后**用相同参数再次运行同一命令/同一工具调用**(不带键也可以):客户端找到模型与输入完全相同、状态为 unknown 的最早记录,原样重发它的请求体而不是重新报价,结果带 `resumed: true`;显式带 `--idempotency-key <同一个键>`(MCP `idempotencyKey`)则直接重发该键的记录,参数不同返回 `SUBMISSION_RETRY_MISMATCH`,不发任何请求。仍在运行的进程的 in-flight 记录不会被自动接续(stderr 会提示它的键),所以并行提交多个相同生成仍会得到多个任务;被杀死进程留下的本机记录会被接续。**恢复动词**:`lumenkey tasks resume <幂等键>`(MCP `lumenkey_resume_submission`)把记录的请求体原样重发——不读目录、不重新报价、不接受任何生成参数——遵守同样的账号绑定、23 小时窗口和重放分类,返回同样的结果对象并带 `resumed: true`;模型下架或目录变更后仍可用它找回已有任务。显式带一个新键而本机已有相同输入的未确认记录时,不会用新键发送任何内容,而是返回 `SUBMISSION_PENDING_IDENTICAL`(`submission:"none"`):结果里 `idempotencyKey` 仍是你提供的键,`pendingIdempotencyKey`、`pendingQuoteId`、`pendingRecordedAt`、`pendingAgeSeconds` 指出阻塞的那条记录,文案指引先运行 `tasks resume <那个键>`。在那条记录处理完或记录后 23 小时过期之前,无法创建第二个相同的任务(显式键的语义不会被覆盖)。`tasks` 的各子命令只接受自己的选项(`progress` 无,`wait` 为 `--interval/--timeout`,`cancel` 为 `--yes`,`resume` 为 `-o/--interval/--timeout/--key`),其他选项返回 `INPUT_INVALID`;以 `-` 开头的键用 `tasks resume --key=<键>` 或 `tasks resume -- <键>` 传入。同模型但输入不同的未确认记录、其他主机的记录、过期的记录都会在 stderr 和 `detail` 里点名,不会被静默跳过。重发时只有网关在建单步骤内给出的明确拒绝(如 `QUOTE_NOT_AVAILABLE`、`INSUFFICIENT_CREDITS`)才算 `"none"` 并删除记录;鉴权、限流等其他 4xx 不能说明第一次有没有建单,结果保持 `"unknown"`、记录保留。首次提交时也只有带业务码的 4xx 才算 `"none"`;没有业务码的 4xx(例如代理返回的 HTML)同样是 `"unknown"`。记录可接续的窗口是 23 小时(比网关的 24 小时幂等记录短一小时):过期记录不再自动接续,显式带它的键会本地返回 `SUBMISSION_RECORD_EXPIRED` 且不发请求;过期记录保留到 48 小时以便报告,之后清理。`"unknown"` 结果里的恢复建议按磁盘上的真实状态给出:记录已标记为 unknown → 直接重跑即接续;记录仍是 in-flight(标记失败)→ 需显式带键;记录已不存在 → 核对任务列表与余额。被杀死的进程留下的 in-flight 记录(本机、进程已不存在)会被相同的重跑接续;仍在运行的进程的记录不会;其他主机的记录会在 stderr 和 `detail` 中提示但不接续。
|
|
107
113
|
|
|
108
114
|
**写不进记录时不提交任务。** 在报价之前就会探测记录目录是否可写,失败返回 `SUBMISSION_JOURNAL_UNWRITABLE`(带目录与原因),不会买下报价。这意味着 0.2.3 在只读 HOME(容器、CI、`HOME=/nonexistent`)下仍可 `--confirm`,而本版本会拒绝;请设置 `LUMENKEY_JOURNAL_DIR=<可写目录>` 把待确认记录放到别处(目录以 0700 创建),记录不可关闭。任务创建成功后记录删不掉只会写进 `detail`,不会改变结果。`--json --confirm` 下任务一创建就向 stderr 写一行 `{"event":"job_created","jobId":…,"idempotencyKey":…,"quoteId":…}`,stdout 仍是单个 JSON 文档;人类模式在任务号旁打印幂等键。
|
|
@@ -119,7 +125,11 @@ Ctrl+C / SIGTERM 只停止本机等待或下载,不调用取消接口;分别
|
|
|
119
125
|
|
|
120
126
|
`--output` 下载不覆盖已有文件,单次限 120 秒、128 MiB;目标目录里超过 1 小时的残留 `.part` 文件会被清理。生成成功但保存失败时,仍返回 `status: "succeeded"`、`jobId`、`idempotencyKey` 和全部 `urls`,另带 `download: {ok:false,code,error}`;CLI 以 1 退出表示本机保存失败,MCP 保留生成成功和下载警告。请用结果地址另存,不要为下载失败重新提交付费生成。下载中断时也保留这些结果。
|
|
121
127
|
|
|
122
|
-
`--output` 必须是包含文件名的路径;目标为目录(包括指向目录的符号链接/junction)时返回 `DOWNLOAD_IS_DIRECTORY`,已有普通文件(或指向文件的链接)则返回 `DOWNLOAD_EXISTS`,不会覆盖、删除或穿过链接写入。目标目录链只在结果开始落盘时创建,保存失败会删掉本次创建的空目录;不支持硬链接的文件系统(exFAT、部分 SMB)自动改用独占复制,仍不覆盖。两个入口都把开头的 `~/` 展开为主目录(不展开 `~user`);CLI 的相对路径按当前目录解析,MCP 的 `downloadPath` 必须是绝对路径或 `~/` 开头,否则在报价前返回 `DOWNLOAD_PATH_NOT_ABSOLUTE`(MCP
|
|
128
|
+
`--output` 必须是包含文件名的路径;目标为目录(包括指向目录的符号链接/junction)时返回 `DOWNLOAD_IS_DIRECTORY`,已有普通文件(或指向文件的链接)则返回 `DOWNLOAD_EXISTS`,不会覆盖、删除或穿过链接写入。目标目录链只在结果开始落盘时创建,保存失败会删掉本次创建的空目录;不支持硬链接的文件系统(exFAT、部分 SMB)自动改用独占复制,仍不覆盖。两个入口都把开头的 `~/` 展开为主目录(不展开 `~user`);CLI 的相对路径按当前目录解析,MCP 的 `downloadPath` 必须是绝对路径或 `~/` 开头,否则在报价前返回 `DOWNLOAD_PATH_NOT_ABSOLUTE`(MCP 服务的工作目录不属于调用方)。
|
|
129
|
+
|
|
130
|
+
可设置绝对目录 `LUMENKEY_DOWNLOAD_ROOT` 限制 CLI/MCP 下载位置;该配置为相对路径时返回 `DOWNLOAD_ROOT_INVALID`,请修正环境变量,而非更换输出文件名。越界或任何 `..` 路径段返回 `DOWNLOAD_OUTSIDE_ROOT`,无法验证、子目录符号链接/junction 或硬链接目标返回 `DOWNLOAD_UNSAFE_PATH`。配置根目录本身按 realpath 规范化,根内子路径不能通过链接逃逸;报价前及写入/发布前复查。未设置时沿用调用方选择的目标父目录作为默认范围,**不默认限制在 cwd**;IDE 管理员需要固定白名单时应明确设置此变量。此限制不代替操作系统权限隔离,不能对抗具有同等文件系统写权限的进程在检查与系统调用之间恶意替换目录。
|
|
131
|
+
|
|
132
|
+
行为变更:即使未设置 `LUMENKEY_DOWNLOAD_ROOT`,也不再接受带 `..` 路径段的下载路径(包括 `~/a/../file`);请传不含上级跳转的目标路径。为兼容脚本仍使用 `DOWNLOAD_OUTSIDE_ROOT`,这里表示拒绝上级跳转,不表示需要增设白名单。链接/junction/硬链接**目标**不会被覆盖或穿过写入;依检查阶段会返回 `DOWNLOAD_EXISTS`、`DOWNLOAD_IS_DIRECTORY` 或 `DOWNLOAD_UNSAFE_PATH`。未配置根时,其默认目标父目录按 realpath 处理,不代表任意祖先目录都禁止包含链接;要约束根内子目录,请显式配置固定根。
|
|
123
133
|
|
|
124
134
|
`~/.lumenkey` 在首次写入时以 0700 创建,已存在但权限更宽时会被收紧(config.json、profiles.json、待确认提交记录共用同一写入路径;Windows 不应用权限位)。配置路径为符号链接或硬链接时,`config.json` 与 `profiles.json` 都返回 `CONFIG_UNSAFE_PATH`(0.2.3 对 profiles 返回 `PROFILE_UNREADABLE`,这是有意的契约变更);路径存在但不是普通文件(目录、FIFO、套接字、设备)或 `.lumenkey` 不是目录时返回 `CONFIG_NOT_A_FILE`,带 `path` 与 `detail`。
|
|
125
135
|
|
|
@@ -217,3 +227,27 @@ claude mcp add lumenkey npx -y @lumenkey/cli mcp
|
|
|
217
227
|
|
|
218
228
|
## 📄 License
|
|
219
229
|
MIT © 2026 LumenKey Team.
|
|
230
|
+
|
|
231
|
+
## 本机 Studio(开发候选,尚未发布)
|
|
232
|
+
|
|
233
|
+
在此源码包构建后运行:
|
|
234
|
+
|
|
235
|
+
```powershell
|
|
236
|
+
node bin/lumenkey.js studio --output-dir "D:\MyAssets"
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
服务只在本机启动,并打开本地工作台;可将已有 PNG/JPEG/WebP/MP4/WebM 保存到指定目录的 `LumenKey` 子目录、查看与读取素材。关闭网页或重启后,素材与已保存草稿仍从磁盘清单恢复。图像/视频创作可显式连接已有 CLI 配置,按目录报价和确认提交。
|
|
240
|
+
|
|
241
|
+
省略 `--output-dir` 可以启动,但不能保存;不自动创建项目。`--no-open` 用于无浏览器诊断,打印的地址没有会话授权,交互使用请去掉该参数重新启动。退出按 Ctrl+C;同一保存目录同时只允许一个服务。
|
|
242
|
+
|
|
243
|
+
启动、本机素材与草稿不需要云端 Key,也不自动调用云端。开发候选页面已接入云端目录、余额、报价、明确确认生成和任务查询;只有点击“连接云端”才读取已有 CLI 配置。页面按目录展示参数,留空使用平台默认,不写死模型、价格、时长或分辨率。报价不会自动下单,提交结果未知时会阻止再次下单;不宣称退款。详见 `docs/studio/runtime-contract-v1.md`。
|
|
244
|
+
|
|
245
|
+
“保存草稿”把当前图像/视频编辑分别写入所选目录的 `LumenKey/drafts.json`,浏览器刷新或服务重启后可恢复。未保存编辑在关闭时提醒;强制结束浏览器无法保证保留未保存内容。草稿与任务输入均为明文,应保存在本人可控目录。目录被另一窗口更新时会提示冲突,不自动覆盖。
|
|
246
|
+
|
|
247
|
+
“画布”与普通创作在同一个 Studio 内:可新建画布、编辑节点与连线、打组/解组、排列、引用本机素材、撤销重做、使用快捷键和保存。保存写入 `LumenKey/canvases.json`,刷新或服务重启后恢复;未完成的参数输入也保留为草稿。离开未保存画布时可保存、留下或放弃,遇到冲突不会自动覆盖。画布的“获取报价”和“确认提交”是两个明确动作,顶栏自动执行仍未开放;查询状态不会自动下载结果。
|
|
248
|
+
|
|
249
|
+
生成任务的“云端完成”不等于“已保存到本机”。媒体结果在任务页明确选择“保存到本机”,受本机目录授权、输出身份和保存回执约束;核验结果与历史保存事实分别显示。画布中可打开任务与手动保存入口,不会另起一套远程下载/自动导入流程。远程文字结果自动回填及站位 JSON 自动应用尚未接入受保护的本机读取链路;不要把“查询成功”当成已写回节点。
|
|
250
|
+
|
|
251
|
+
本地 3D 导演台提供人物/物件、机位、路径、时间线、PNG 和 WebM 导出,并与同一画布文件、素材和历史关联。WebGL、录制格式和设备性能受浏览器支持限制;Windows 合成验收不代表 Mac/Linux 已验收。角色造型室仍隐藏;手机虚拟相机、全景 AI、远程 ONNX 和多段成片拼接未开放。营销/充值、自动更新、参考素材上传也不在这版的已完成能力内。
|
|
252
|
+
|
|
253
|
+
任务记录在本机 `LumenKey/runs.json`,包含提示词但不包含 API Key,当前不加密,请选择本人可控目录。原有 CLI 和 MCP 继续独立使用。本候选版默认打包包含 Studio、画布和导演台,不需要另外下载前端源码。0.4.0-rc.1 尚待审核,不代表已发布;已发布的 0.3.1 仍是 CLI/MCP 包。
|
package/dist/cli/copied.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { zhText } from '../core/chinese-messages.js';
|
|
1
2
|
import os from 'node:os';
|
|
2
3
|
import pc from 'picocolors';
|
|
3
4
|
import { describeKey, loadConfig } from '../core/config.js';
|
|
@@ -43,7 +44,7 @@ export function applyRaw(options, key, names) {
|
|
|
43
44
|
if (raw !== undefined)
|
|
44
45
|
options[key] = raw;
|
|
45
46
|
else if (options[key] !== undefined && typeof options[key] !== 'string') {
|
|
46
|
-
throw new Error(ui(`${names[0]} could not be read as text. Use ${names[0]} <value>.`,
|
|
47
|
+
throw new Error(ui(`${names[0]} could not be read as text. Use ${names[0]} <value>.`, ("" + (names[0]) + zhText("m359") + (names[0]) + zhText("m360"))));
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
/** Plain data commands (catalog, balance, profiles) print their payload; outcomes go through renderOutcome. */
|
|
@@ -82,7 +83,7 @@ export async function withCliSignals(run) {
|
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
85
|
function progressLine(status) {
|
|
85
|
-
process.stderr.write(ui(` Status: ${jobStatusText(status)}\n`,
|
|
86
|
+
process.stderr.write(ui(` Status: ${jobStatusText(status)}\n`, (zhText("m358") + (jobStatusText(status)) + "\n")));
|
|
86
87
|
}
|
|
87
88
|
/** CLI view of generation progress. In --json mode the job identity goes to stderr the moment it exists. */
|
|
88
89
|
export function cliGenerationEvents() {
|
|
@@ -91,24 +92,24 @@ export function cliGenerationEvents() {
|
|
|
91
92
|
switch (event.type) {
|
|
92
93
|
case 'quoting':
|
|
93
94
|
if (!json && event.kind === 'video')
|
|
94
|
-
console.log(pc.cyan(ui(`\n🎬 Requesting quote for model: ${pc.bold(event.modelId)}...`,
|
|
95
|
+
console.log(pc.cyan(ui(`\n🎬 Requesting quote for model: ${pc.bold(event.modelId)}...`, (zhText("m356") + (pc.bold(event.modelId)) + zhText("m357")))));
|
|
95
96
|
return;
|
|
96
97
|
case 'submitting':
|
|
97
98
|
console.error(event.resubmit
|
|
98
|
-
? ui(`Re-sending an earlier unconfirmed submission (idempotency key ${event.idempotencyKey}, quote ${event.quoteId}) instead of creating a new job...`,
|
|
99
|
-
: ui('Submitting generation job...',
|
|
99
|
+
? ui(`Re-sending an earlier unconfirmed submission (idempotency key ${event.idempotencyKey}, quote ${event.quoteId}) instead of creating a new job...`, (zhText("m354") + (event.idempotencyKey) + zhText("m34") + (event.quoteId) + zhText("m355")))
|
|
100
|
+
: ui('Submitting generation job...', zhText("m353")));
|
|
100
101
|
return;
|
|
101
102
|
case 'job_created':
|
|
102
103
|
if (json)
|
|
103
104
|
console.error(JSON.stringify({ event: 'job_created', jobId: event.jobId, idempotencyKey: event.idempotencyKey, quoteId: event.quoteId }));
|
|
104
105
|
else
|
|
105
|
-
console.log(ui(`Job created: ${event.jobId} (idempotency key: ${event.idempotencyKey})`,
|
|
106
|
+
console.log(ui(`Job created: ${event.jobId} (idempotency key: ${event.idempotencyKey})`, (zhText("m351") + (event.jobId) + zhText("m352") + (event.idempotencyKey) + ")")));
|
|
106
107
|
return;
|
|
107
108
|
case 'progress':
|
|
108
109
|
progressLine(event.detail.status);
|
|
109
110
|
return;
|
|
110
111
|
case 'notice':
|
|
111
|
-
console.error(ui(`Note: ${event.text}`,
|
|
112
|
+
console.error(ui(`Note: ${event.text}`, (zhText("m350") + (event.text) + "")));
|
|
112
113
|
return;
|
|
113
114
|
default:
|
|
114
115
|
return;
|
|
@@ -121,19 +122,19 @@ export function printGeneration(result) {
|
|
|
121
122
|
return;
|
|
122
123
|
}
|
|
123
124
|
if ('requiresConfirmation' in result) {
|
|
124
|
-
console.log(ui(`Quote ID: ${result.quoteId} (${result.credits} credits)`,
|
|
125
|
-
console.log(ui('Quote only. No job submitted.',
|
|
125
|
+
console.log(ui(`Quote ID: ${result.quoteId} (${result.credits} credits)`, (zhText("m348") + (result.quoteId) + "(" + (result.credits) + zhText("m349"))));
|
|
126
|
+
console.log(ui('Quote only. No job submitted.', zhText("m347")));
|
|
126
127
|
return;
|
|
127
128
|
}
|
|
128
129
|
const done = result;
|
|
129
|
-
console.log(ui('Generation succeeded!',
|
|
130
|
-
console.log(ui(`Task: ${done.jobId}`,
|
|
130
|
+
console.log(ui('Generation succeeded!', zhText("m346")));
|
|
131
|
+
console.log(ui(`Task: ${done.jobId}`, (zhText("m50") + (done.jobId) + "")));
|
|
131
132
|
for (const url of done.urls)
|
|
132
133
|
console.log(url);
|
|
133
134
|
if (done.localFile)
|
|
134
|
-
console.log(ui(`Saved to ${done.localFile}`,
|
|
135
|
+
console.log(ui(`Saved to ${done.localFile}`, (zhText("m345") + (done.localFile) + "")));
|
|
135
136
|
if (done.download?.ok === false)
|
|
136
|
-
console.error(ui(`Local save failed: ${done.download.error} (${done.download.code}). The generation result remains available above.`,
|
|
137
|
+
console.error(ui(`Local save failed: ${done.download.error} (${done.download.code}). The generation result remains available above.`, (zhText("m343") + (done.download.error) + "(" + (done.download.code) + zhText("m344"))));
|
|
137
138
|
}
|
|
138
139
|
/** Shared by `video` and `image generate`: options come from the core spec, values go through the core normalizer, one use case, one renderer. */
|
|
139
140
|
export async function runCliGeneration(kind, spec, options) {
|
|
@@ -165,51 +166,51 @@ export function registerOptions(command, spec) {
|
|
|
165
166
|
}
|
|
166
167
|
function printProfiles(listed) {
|
|
167
168
|
if (!listed.profiles.length) {
|
|
168
|
-
console.log(ui('No profiles configured. Create one with: lumenkey profile create <name>',
|
|
169
|
+
console.log(ui('No profiles configured. Create one with: lumenkey profile create <name>', zhText("m342")));
|
|
169
170
|
return;
|
|
170
171
|
}
|
|
171
172
|
for (const row of listed.profiles)
|
|
172
173
|
console.log(`${row.isSet ? '* ' : ' '}${row.profile} ${row.settings.join(', ')}`);
|
|
173
|
-
console.log(ui('* = active profile; fields shown are configured settings, not their values.',
|
|
174
|
+
console.log(ui('* = active profile; fields shown are configured settings, not their values.', zhText("m341")));
|
|
174
175
|
}
|
|
175
176
|
function printModel(model) {
|
|
176
177
|
console.log(`${model.id} — ${model.displayName || model.name || model.id}`);
|
|
177
178
|
for (const param of explainModel(model).params) {
|
|
178
|
-
const choices = param.options.length ? `; ${ui('allowed',
|
|
179
|
-
const defaultValue = param.default === null ? '' : `; ${ui('default',
|
|
180
|
-
console.log(`${param.name}: ${param.required ? ui('required',
|
|
179
|
+
const choices = param.options.length ? `; ${ui('allowed', zhText("m340"))}: ${param.options.map(value => JSON.stringify(value)).join(', ')}` : '';
|
|
180
|
+
const defaultValue = param.default === null ? '' : `; ${ui('default', zhText("m339"))}: ${JSON.stringify(param.default)}`;
|
|
181
|
+
console.log(`${param.name}: ${param.required ? ui('required', zhText("m338")) : ui('optional', zhText("m337"))}${choices}${defaultValue}`);
|
|
181
182
|
}
|
|
182
|
-
const prices = model.pricing?.map(price => `${price.credits} ${ui('credits',
|
|
183
|
-
console.log(`${ui('Price',
|
|
183
|
+
const prices = model.pricing?.map(price => `${price.credits} ${ui('credits', zhText("m336"))}/${price.unit}`).join(', ');
|
|
184
|
+
console.log(`${ui('Price', zhText("m335"))}: ${prices || ui('not reported in catalog', zhText("m334"))}`);
|
|
184
185
|
}
|
|
185
186
|
export function registerCopied(cli) {
|
|
186
|
-
cli.option('--json', ui('Emit machine-readable JSON',
|
|
187
|
-
cli.command('model <action> [name]', ui('model search | get | explain',
|
|
187
|
+
cli.option('--json', ui('Emit machine-readable JSON', zhText("m333")));
|
|
188
|
+
cli.command('model <action> [name]', ui('model search | get | explain', zhText("m332")))
|
|
188
189
|
.example('lumenkey model search wan --type video')
|
|
189
190
|
.example('lumenkey model get minimax-h3')
|
|
190
191
|
.example('lumenkey --json model explain minimax-h3')
|
|
191
|
-
.option('--type <type>', ui('Filter or disambiguate by catalog type',
|
|
192
|
+
.option('--type <type>', ui('Filter or disambiguate by catalog type', zhText("m331")))
|
|
192
193
|
.action(async (action, name, options) => {
|
|
193
194
|
applyRaw(options, 'type', ['--type']);
|
|
194
195
|
validateModelType(options.type);
|
|
195
196
|
if (!['search', 'get', 'explain'].includes(action))
|
|
196
|
-
throw new Error(ui(`Unknown model command: ${action}`,
|
|
197
|
+
throw new Error(ui(`Unknown model command: ${action}`, (zhText("m326") + (action) + "")));
|
|
197
198
|
if (action === 'search') {
|
|
198
199
|
const models = findModels(await client().listModels(), name || '', options.type);
|
|
199
200
|
emit({ query: name || '', count: models.length, models }, () => {
|
|
200
201
|
for (const model of models)
|
|
201
202
|
console.log(model.id);
|
|
202
203
|
if (models.length === 0)
|
|
203
|
-
console.log(ui('No models available for this request.',
|
|
204
|
+
console.log(ui('No models available for this request.', zhText("m330")));
|
|
204
205
|
});
|
|
205
206
|
return;
|
|
206
207
|
}
|
|
207
208
|
if (!name)
|
|
208
|
-
throw new Error(ui('Model name is required. Try: model search',
|
|
209
|
+
throw new Error(ui('Model name is required. Try: model search', zhText("m329")));
|
|
209
210
|
if (action === 'get') {
|
|
210
211
|
const exact = findModels(await client().listModels(), '').filter((model) => model.id === name || (model.displayName || model.name || '').toLowerCase() === name.toLowerCase());
|
|
211
212
|
if (exact.length !== 1)
|
|
212
|
-
throw exact.length === 0 ? new LumenKeyError('MODEL_NOT_IN_CATALOG', undefined, undefined, missingModelSentence(name)) : new Error(ui(`More than one model is named ${name}. Run: lumenkey model search ${name}`,
|
|
213
|
+
throw exact.length === 0 ? new LumenKeyError('MODEL_NOT_IN_CATALOG', undefined, undefined, missingModelSentence(name)) : new Error(ui(`More than one model is named ${name}. Run: lumenkey model search ${name}`, (zhText("m327") + (name) + zhText("m328") + (name) + "")));
|
|
213
214
|
emit(exact[0], () => printModel(exact[0]));
|
|
214
215
|
return;
|
|
215
216
|
}
|
|
@@ -218,36 +219,36 @@ export function registerCopied(cli) {
|
|
|
218
219
|
const exact = all.filter((model) => model.id === name || (model.displayName || model.name || '').toLowerCase() === name.toLowerCase());
|
|
219
220
|
const chosen = exact.length === 1 ? exact[0] : findModels(all, name, options.type).length === 1 ? findModels(all, name, options.type)[0] : undefined;
|
|
220
221
|
if (!chosen)
|
|
221
|
-
throw exact.length === 0 ? new LumenKeyError('MODEL_NOT_IN_CATALOG', undefined, undefined, missingModelSentence(name)) : new Error(ui(`More than one model is named ${name}. Run: lumenkey model search ${name}`,
|
|
222
|
+
throw exact.length === 0 ? new LumenKeyError('MODEL_NOT_IN_CATALOG', undefined, undefined, missingModelSentence(name)) : new Error(ui(`More than one model is named ${name}. Run: lumenkey model search ${name}`, (zhText("m327") + (name) + zhText("m328") + (name) + "")));
|
|
222
223
|
const explained = explainModel(chosen);
|
|
223
224
|
emit(explained, () => printModel(chosen));
|
|
224
225
|
return;
|
|
225
226
|
}
|
|
226
|
-
throw new Error(ui(`Unknown model command: ${action}`,
|
|
227
|
+
throw new Error(ui(`Unknown model command: ${action}`, (zhText("m326") + (action) + "")));
|
|
227
228
|
});
|
|
228
|
-
registerOptions(cli.command('image <action>', ui('image generate',
|
|
229
|
+
registerOptions(cli.command('image <action>', ui('image generate', zhText("m325")))
|
|
229
230
|
.example('lumenkey image generate -m wan2.7-image -p "red circle"')
|
|
230
231
|
.example('lumenkey image generate -m wan2.7-image -p "red circle" --set seed=2 --dry-run'), imageParams)
|
|
231
232
|
.action(async (action, options) => {
|
|
232
233
|
if (action !== 'generate')
|
|
233
|
-
throw new Error(ui(`Unknown image command: ${action}`,
|
|
234
|
+
throw new Error(ui(`Unknown image command: ${action}`, (zhText("m324") + (action) + "")));
|
|
234
235
|
await runCliGeneration('image', imageParams, options);
|
|
235
236
|
});
|
|
236
|
-
registerOptions(cli.command('tasks <action> [jobId]', ui('tasks progress | wait | cancel | resume',
|
|
237
|
+
registerOptions(cli.command('tasks <action> [jobId]', ui('tasks progress | wait | cancel | resume', zhText("m323")))
|
|
237
238
|
.example('lumenkey tasks progress <jobId>')
|
|
238
239
|
.example('lumenkey tasks wait <jobId> --interval 5 --timeout 1800')
|
|
239
240
|
.example('lumenkey tasks cancel <jobId> --yes')
|
|
240
241
|
.example('lumenkey tasks resume <idempotencyKey>')
|
|
241
242
|
.example('lumenkey tasks resume --key=<idempotencyKey> (for a key that begins with -)'), resumeParams)
|
|
242
|
-
.option('--yes', ui('Confirm cancellation',
|
|
243
|
-
.option('--key <idempotencyKey>', ui('The idempotency key for tasks resume; use --key=<key> or -- <key> when the key begins with -',
|
|
243
|
+
.option('--yes', ui('Confirm cancellation', zhText("m322")))
|
|
244
|
+
.option('--key <idempotencyKey>', ui('The idempotency key for tasks resume; use --key=<key> or -- <key> when the key begins with -', zhText("m321")))
|
|
244
245
|
.action(async (action, positional, options) => {
|
|
245
246
|
// Each subcommand owns its options; any other option is as unknown here as on the MCP surface.
|
|
246
247
|
rejectForeignTaskOptions(action);
|
|
247
248
|
applyRaw(options, 'key', ['--key']);
|
|
248
249
|
const jobId = positional ?? firstDoubleDashArgument(options) ?? (typeof options.key === 'string' ? options.key : undefined);
|
|
249
250
|
if (jobId === undefined)
|
|
250
|
-
throw new UsageArgumentError(ui(`missing required args for command \`tasks ${action}\``,
|
|
251
|
+
throw new UsageArgumentError(ui(`missing required args for command \`tasks ${action}\``, (zhText("m319") + (action) + zhText("m320"))));
|
|
251
252
|
if (action === 'progress') {
|
|
252
253
|
renderOutcome(await readJob(client(), jobId), ok => console.log(formatJobDetail(ok.job)));
|
|
253
254
|
return;
|
|
@@ -276,12 +277,12 @@ export function registerCopied(cli) {
|
|
|
276
277
|
if (options.yes !== true)
|
|
277
278
|
throw new LumenKeyError('CANCEL_CONFIRMATION_REQUIRED');
|
|
278
279
|
const result = await client().cancelJob(jobId);
|
|
279
|
-
emit(result, () => console.log(ui(`Cancellation requested for ${jobId}. Settlement is determined by the gateway.`,
|
|
280
|
+
emit(result, () => console.log(ui(`Cancellation requested for ${jobId}. Settlement is determined by the gateway.`, (zhText("m317") + (jobId) + zhText("m318")))));
|
|
280
281
|
return;
|
|
281
282
|
}
|
|
282
|
-
throw new Error(ui(`Unknown tasks command: ${action}`,
|
|
283
|
+
throw new Error(ui(`Unknown tasks command: ${action}`, (zhText("m316") + (action) + "")));
|
|
283
284
|
});
|
|
284
|
-
cli.command('profile <action> [value] [...extra]', ui('profile list | create | set | unset | key | host | delete',
|
|
285
|
+
cli.command('profile <action> [value] [...extra]', ui('profile list | create | set | unset | key | host | delete', zhText("m315")))
|
|
285
286
|
.example('lumenkey profile create work')
|
|
286
287
|
.example('lumenkey profile key')
|
|
287
288
|
.example('lumenkey profile host https://123outlets.com')
|
|
@@ -290,7 +291,7 @@ export function registerCopied(cli) {
|
|
|
290
291
|
if (extra.length || (value !== undefined && ['list', 'unset'].includes(action)))
|
|
291
292
|
throw new LumenKeyError('CLI_ARGUMENTS_INVALID');
|
|
292
293
|
if (!['list', 'create', 'set', 'unset', 'key', 'host', 'delete'].includes(action)) {
|
|
293
|
-
throw new LumenKeyError('CLI_ARGUMENTS_INVALID', undefined, undefined, ui(`Unknown profile command: ${action}`,
|
|
294
|
+
throw new LumenKeyError('CLI_ARGUMENTS_INVALID', undefined, undefined, ui(`Unknown profile command: ${action}`, (zhText("m303") + (action) + "")));
|
|
294
295
|
}
|
|
295
296
|
if (action === 'list') {
|
|
296
297
|
const listed = listProfiles();
|
|
@@ -299,29 +300,29 @@ export function registerCopied(cli) {
|
|
|
299
300
|
}
|
|
300
301
|
if (action === 'unset') {
|
|
301
302
|
unsetProfile();
|
|
302
|
-
emit({ current: null }, () => console.log(ui('Default profile unset.',
|
|
303
|
+
emit({ current: null }, () => console.log(ui('Default profile unset.', zhText("m314"))));
|
|
303
304
|
return;
|
|
304
305
|
}
|
|
305
306
|
if (action === 'key') {
|
|
306
307
|
const target = value ?? currentProfile().name;
|
|
307
308
|
if (!target || !listProfiles().profiles.some(row => row.profile === target))
|
|
308
|
-
throw new ProfileMissingError(target ?? ui('(active profile)',
|
|
309
|
+
throw new ProfileMissingError(target ?? ui('(active profile)', zhText("m313")));
|
|
309
310
|
const key = await readProfileKey();
|
|
310
311
|
const name = value ? (setNamedProfileFields(value, { key }), value) : setProfileFields({ key });
|
|
311
|
-
emit({ profile: name, hasStoredToken: true }, () => console.log(ui(`Key set for profile ${name}.`,
|
|
312
|
+
emit({ profile: name, hasStoredToken: true }, () => console.log(ui(`Key set for profile ${name}.`, (zhText("m311") + (name) + zhText("m312")))));
|
|
312
313
|
return;
|
|
313
314
|
}
|
|
314
315
|
if (action === 'host' && value === undefined)
|
|
315
|
-
throw new LumenKeyError('CLI_ARGUMENTS_INVALID', undefined, undefined, ui('Profile host needs a gateway address. Use profile host <address>.',
|
|
316
|
+
throw new LumenKeyError('CLI_ARGUMENTS_INVALID', undefined, undefined, ui('Profile host needs a gateway address. Use profile host <address>.', zhText("m310")));
|
|
316
317
|
if (action !== 'host' && !value)
|
|
317
|
-
throw new LumenKeyError('CLI_ARGUMENTS_INVALID', undefined, undefined, ui(`Profile ${action} needs a name.`,
|
|
318
|
+
throw new LumenKeyError('CLI_ARGUMENTS_INVALID', undefined, undefined, ui(`Profile ${action} needs a name.`, ("profile " + (action) + zhText("m295"))));
|
|
318
319
|
if (value === undefined)
|
|
319
320
|
throw new LumenKeyError('CLI_ARGUMENTS_INVALID');
|
|
320
321
|
if (action === 'create') {
|
|
321
322
|
const result = createProfile(value);
|
|
322
323
|
if (result === 'exists')
|
|
323
324
|
throw new LumenKeyError('PROFILE_EXISTS');
|
|
324
|
-
emit({ name: value, created: true }, () => console.log(ui(`Profile ${value} created.`,
|
|
325
|
+
emit({ name: value, created: true }, () => console.log(ui(`Profile ${value} created.`, (zhText("m309") + (value) + "。"))));
|
|
325
326
|
return;
|
|
326
327
|
}
|
|
327
328
|
if (action === 'set') {
|
|
@@ -332,14 +333,14 @@ export function registerCopied(cli) {
|
|
|
332
333
|
if (!(err instanceof ProfileMissingError))
|
|
333
334
|
throw err;
|
|
334
335
|
console.error(err.message);
|
|
335
|
-
const answer = (await ask(ui('Would you like to create it? (y/N): ',
|
|
336
|
+
const answer = (await ask(ui('Would you like to create it? (y/N): ', zhText("m308")))).trim().toLowerCase();
|
|
336
337
|
if (answer !== 'y' && answer !== 'yes') {
|
|
337
|
-
emit({ current: null, created: false }, () => console.log(ui('Profile creation cancelled.',
|
|
338
|
+
emit({ current: null, created: false }, () => console.log(ui('Profile creation cancelled.', zhText("m307"))));
|
|
338
339
|
return;
|
|
339
340
|
}
|
|
340
341
|
createProfile(value);
|
|
341
342
|
}
|
|
342
|
-
emit({ current: value }, () => console.log(ui(`Default profile set to ${value}.`,
|
|
343
|
+
emit({ current: value }, () => console.log(ui(`Default profile set to ${value}.`, (zhText("m306") + (value) + "。"))));
|
|
343
344
|
return;
|
|
344
345
|
}
|
|
345
346
|
if (action === 'host') {
|
|
@@ -351,34 +352,34 @@ export function registerCopied(cli) {
|
|
|
351
352
|
throw new LumenKeyError('BASE_URL_INVALID');
|
|
352
353
|
}
|
|
353
354
|
const name = setProfileFields({ host: value });
|
|
354
|
-
emit({ profile: name, host: value }, () => console.log(ui(`Host set to ${value}.`,
|
|
355
|
+
emit({ profile: name, host: value }, () => console.log(ui(`Host set to ${value}.`, (zhText("m305") + (value) + "。"))));
|
|
355
356
|
return;
|
|
356
357
|
}
|
|
357
358
|
if (action === 'delete') {
|
|
358
359
|
const removed = deleteProfile(value);
|
|
359
360
|
if (!removed)
|
|
360
361
|
throw new ProfileMissingError(value);
|
|
361
|
-
emit({ deleted: true, name: value }, () => console.log(ui(`Profile ${value} deleted.`,
|
|
362
|
+
emit({ deleted: true, name: value }, () => console.log(ui(`Profile ${value} deleted.`, (zhText("m304") + (value) + "。"))));
|
|
362
363
|
return;
|
|
363
364
|
}
|
|
364
|
-
throw new Error(ui(`Unknown profile command: ${action}`,
|
|
365
|
+
throw new Error(ui(`Unknown profile command: ${action}`, (zhText("m303") + (action) + "")));
|
|
365
366
|
});
|
|
366
|
-
cli.command('auth <group> [action] [name]', ui('auth status | profile list | use | save | delete',
|
|
367
|
+
cli.command('auth <group> [action] [name]', ui('auth status | profile list | use | save | delete', zhText("m302")))
|
|
367
368
|
.example('lumenkey auth profile list')
|
|
368
369
|
.example('lumenkey auth profile save work')
|
|
369
370
|
.example('lumenkey auth profile use work')
|
|
370
371
|
.example('lumenkey auth profile delete work --yes')
|
|
371
|
-
.option('--yes', ui('Confirm deletion',
|
|
372
|
-
.option('--store-token', ui('Store the environment key after a balance check',
|
|
373
|
-
.option('--no-activate', ui('Do not make the saved profile active',
|
|
372
|
+
.option('--yes', ui('Confirm deletion', zhText("m301")))
|
|
373
|
+
.option('--store-token', ui('Store the environment key after a balance check', zhText("m300")))
|
|
374
|
+
.option('--no-activate', ui('Do not make the saved profile active', zhText("m299")))
|
|
374
375
|
.action(async (group, action, name, options) => {
|
|
375
376
|
if (group === 'status') {
|
|
376
377
|
const body = describeKey();
|
|
377
|
-
emit(body, () => console.log(body.keyConfigured ? ui(`key: ${body.keySource}`,
|
|
378
|
+
emit(body, () => console.log(body.keyConfigured ? ui(`key: ${body.keySource}`, (zhText("m298") + (body.keySource) + "")) : errorSentence('API_KEY_NOT_CONFIGURED')));
|
|
378
379
|
return;
|
|
379
380
|
}
|
|
380
381
|
if (group !== 'profile')
|
|
381
|
-
throw new Error(ui(`Unknown auth command: ${group}`,
|
|
382
|
+
throw new Error(ui(`Unknown auth command: ${group}`, (zhText("m297") + (group) + "")));
|
|
382
383
|
if (action === 'list') {
|
|
383
384
|
const data = listProfiles();
|
|
384
385
|
const body = { active: data.current ?? null, profiles: data.profiles.map((row) => ({ name: row.profile, settings: row.settings })) };
|
|
@@ -387,15 +388,15 @@ export function registerCopied(cli) {
|
|
|
387
388
|
}
|
|
388
389
|
if (!action || !['use', 'save', 'delete'].includes(action)) {
|
|
389
390
|
throw new Error(action
|
|
390
|
-
? ui(`Unknown auth profile command: ${action}`,
|
|
391
|
-
: ui('Unknown auth profile command.',
|
|
391
|
+
? ui(`Unknown auth profile command: ${action}`, (zhText("m290") + (action) + ""))
|
|
392
|
+
: ui('Unknown auth profile command.', zhText("m296")));
|
|
392
393
|
}
|
|
393
394
|
if (!name)
|
|
394
|
-
throw new LumenKeyError('CLI_ARGUMENTS_INVALID', undefined, undefined, ui(`Auth profile ${action} needs a name.`,
|
|
395
|
+
throw new LumenKeyError('CLI_ARGUMENTS_INVALID', undefined, undefined, ui(`Auth profile ${action} needs a name.`, ("auth profile " + (action) + zhText("m295"))));
|
|
395
396
|
if (action === 'use') {
|
|
396
397
|
setProfile(name);
|
|
397
398
|
const record = currentProfile().record;
|
|
398
|
-
emit({ name, hasStoredToken: Boolean(record?.key), account: publicProfile(record) }, () => console.log(ui(`Profile ${name} activated.`,
|
|
399
|
+
emit({ name, hasStoredToken: Boolean(record?.key), account: publicProfile(record) }, () => console.log(ui(`Profile ${name} activated.`, (zhText("m294") + (name) + "。"))));
|
|
399
400
|
return;
|
|
400
401
|
}
|
|
401
402
|
if (action === 'save') {
|
|
@@ -410,22 +411,22 @@ export function registerCopied(cli) {
|
|
|
410
411
|
setNamedProfileFields(name, { key, host: process.env.LUMENKEY_BASE_URL });
|
|
411
412
|
}
|
|
412
413
|
const record = listProfiles().profiles.find((row) => row.profile === name);
|
|
413
|
-
emit({ name, active: activate, storedToken: Boolean(options.storeToken), settings: record?.settings ?? [] }, () => console.log(ui(`Auth profile ${name} saved.`,
|
|
414
|
+
emit({ name, active: activate, storedToken: Boolean(options.storeToken), settings: record?.settings ?? [] }, () => console.log(ui(`Auth profile ${name} saved.`, (zhText("m293") + (name) + "。"))));
|
|
414
415
|
return;
|
|
415
416
|
}
|
|
416
417
|
if (action === 'delete') {
|
|
417
418
|
if (!options.yes)
|
|
418
|
-
throw new Error(ui('Refusing to delete auth profile without --yes.',
|
|
419
|
+
throw new Error(ui('Refusing to delete auth profile without --yes.', zhText("m292")));
|
|
419
420
|
const removed = deleteProfile(name);
|
|
420
421
|
if (!removed)
|
|
421
422
|
throw new ProfileMissingError(name);
|
|
422
|
-
emit({ deleted: true, name }, () => console.log(ui(`Auth profile ${name} deleted.`,
|
|
423
|
+
emit({ deleted: true, name }, () => console.log(ui(`Auth profile ${name} deleted.`, (zhText("m291") + (name) + "。"))));
|
|
423
424
|
return;
|
|
424
425
|
}
|
|
425
|
-
throw new Error(ui(`Unknown auth profile command: ${action}`,
|
|
426
|
+
throw new Error(ui(`Unknown auth profile command: ${action}`, (zhText("m290") + (action) + "")));
|
|
426
427
|
});
|
|
427
|
-
cli.command('doctor', ui('Local environment summary. --online checks the key by reading balance',
|
|
428
|
-
.option('--online', ui('Verify the configured key',
|
|
428
|
+
cli.command('doctor', ui('Local environment summary. --online checks the key by reading balance', zhText("m289")))
|
|
429
|
+
.option('--online', ui('Verify the configured key', zhText("m288")))
|
|
429
430
|
.action(async (options) => {
|
|
430
431
|
const config = loadConfig();
|
|
431
432
|
const checks = [
|
|
@@ -446,24 +447,24 @@ export function registerCopied(cli) {
|
|
|
446
447
|
}
|
|
447
448
|
const ok = checks.every((check) => check.status !== 'fail');
|
|
448
449
|
emit({ ok, checks }, () => {
|
|
449
|
-
console.log(ui(`cli: ${packageVersion}`,
|
|
450
|
-
console.log(ui(`runtime: ${process.version}`,
|
|
451
|
-
console.log(ui(`platform: ${process.platform} ${os.release()}`,
|
|
452
|
-
console.log(ui(`host: ${config.baseUrl}`,
|
|
453
|
-
console.log(config.apiKey ? ui('key: configured',
|
|
450
|
+
console.log(ui(`cli: ${packageVersion}`, (zhText("m287") + (packageVersion) + "")));
|
|
451
|
+
console.log(ui(`runtime: ${process.version}`, (zhText("m286") + (process.version) + "")));
|
|
452
|
+
console.log(ui(`platform: ${process.platform} ${os.release()}`, (zhText("m285") + (process.platform) + " " + (os.release()) + "")));
|
|
453
|
+
console.log(ui(`host: ${config.baseUrl}`, (zhText("m284") + (config.baseUrl) + "")));
|
|
454
|
+
console.log(config.apiKey ? ui('key: configured', zhText("m283")) : ui('key: not configured', zhText("m282")));
|
|
454
455
|
if (options.online) {
|
|
455
456
|
const check = checks.find(row => row.name === 'onlineAuth');
|
|
456
|
-
console.log(`${ui('onlineAuth',
|
|
457
|
+
console.log(`${ui('onlineAuth', zhText("m281"))}: ${check?.status}${check?.message ? ` — ${check.message}` : ''}`);
|
|
457
458
|
}
|
|
458
459
|
});
|
|
459
460
|
if (!ok)
|
|
460
461
|
process.exitCode = 1;
|
|
461
462
|
});
|
|
462
|
-
cli.command('completion <shell>', ui('Print a shell completion script',
|
|
463
|
+
cli.command('completion <shell>', ui('Print a shell completion script', zhText("m280")))
|
|
463
464
|
.action((shell) => {
|
|
464
465
|
const script = Object.hasOwn(COMPLETIONS, shell) ? COMPLETIONS[shell] : undefined;
|
|
465
466
|
if (!script)
|
|
466
|
-
throw new LumenKeyError('CLI_ARGUMENTS_INVALID', undefined, undefined, ui('Shell must be bash, zsh, tcsh, or fish.',
|
|
467
|
+
throw new LumenKeyError('CLI_ARGUMENTS_INVALID', undefined, undefined, ui('Shell must be bash, zsh, tcsh, or fish.', zhText("m279")));
|
|
467
468
|
console.log(script);
|
|
468
469
|
});
|
|
469
470
|
}
|
|
@@ -483,7 +484,7 @@ function rejectForeignTaskOptions(action) {
|
|
|
483
484
|
break;
|
|
484
485
|
const name = arg.split('=')[0];
|
|
485
486
|
if (all.includes(name) && !owned.has(name)) {
|
|
486
|
-
throw new LumenKeyError('INPUT_INVALID', undefined, undefined, ui(`Unknown option ${JSON.stringify(name)} for tasks ${action}.`,
|
|
487
|
+
throw new LumenKeyError('INPUT_INVALID', undefined, undefined, ui(`Unknown option ${JSON.stringify(name)} for tasks ${action}.`, ("tasks " + (action) + zhText("m278") + (JSON.stringify(name)) + "。")));
|
|
487
488
|
}
|
|
488
489
|
}
|
|
489
490
|
}
|
|
@@ -529,14 +530,14 @@ export async function readSecret(input = process.stdin, output = process.stderr)
|
|
|
529
530
|
answered = true;
|
|
530
531
|
reader.close();
|
|
531
532
|
if (!line.trim())
|
|
532
|
-
reject(new Error(ui('Enter the key: empty.',
|
|
533
|
+
reject(new Error(ui('Enter the key: empty.', zhText("m276"))));
|
|
533
534
|
else
|
|
534
535
|
resolve(line.trim());
|
|
535
536
|
});
|
|
536
537
|
reader.once('close', () => { if (!answered)
|
|
537
538
|
reject(new LumenKeyError('INPUT_CLOSED')); });
|
|
538
539
|
reader.once('error', reject);
|
|
539
|
-
output.write(ui('Enter the key: ',
|
|
540
|
+
output.write(ui('Enter the key: ', zhText("m272")));
|
|
540
541
|
});
|
|
541
542
|
}
|
|
542
543
|
const wasRaw = Boolean(input.isRaw);
|
|
@@ -558,7 +559,7 @@ export async function readSecret(input = process.stdin, output = process.stderr)
|
|
|
558
559
|
input.setRawMode(wasRaw);
|
|
559
560
|
}
|
|
560
561
|
catch (restoreError) {
|
|
561
|
-
error = restoreError instanceof Error ? restoreError : new Error(ui('Could not restore terminal mode.',
|
|
562
|
+
error = restoreError instanceof Error ? restoreError : new Error(ui('Could not restore terminal mode.', zhText("m277")));
|
|
562
563
|
}
|
|
563
564
|
if (wasPaused)
|
|
564
565
|
input.pause();
|
|
@@ -566,12 +567,12 @@ export async function readSecret(input = process.stdin, output = process.stderr)
|
|
|
566
567
|
if (error)
|
|
567
568
|
reject(error);
|
|
568
569
|
else if (!value.trim())
|
|
569
|
-
reject(new Error(ui('Enter the key: empty.',
|
|
570
|
+
reject(new Error(ui('Enter the key: empty.', zhText("m276"))));
|
|
570
571
|
else
|
|
571
572
|
resolve(value.trim());
|
|
572
573
|
value = '';
|
|
573
574
|
};
|
|
574
|
-
const onEnd = () => finish(new Error(ui('stdin closed before an answer was given.',
|
|
575
|
+
const onEnd = () => finish(new Error(ui('stdin closed before an answer was given.', zhText("m275"))));
|
|
575
576
|
const onError = (error) => finish(error);
|
|
576
577
|
const onData = (chunk) => {
|
|
577
578
|
for (const char of typeof chunk === 'string' ? chunk : decoder.write(chunk)) {
|
|
@@ -580,7 +581,7 @@ export async function readSecret(input = process.stdin, output = process.stderr)
|
|
|
580
581
|
return;
|
|
581
582
|
}
|
|
582
583
|
if (char === '\u0003' || char === '\u0004') {
|
|
583
|
-
finish(new Error(ui('Key entry cancelled.',
|
|
584
|
+
finish(new Error(ui('Key entry cancelled.', zhText("m274"))));
|
|
584
585
|
return;
|
|
585
586
|
}
|
|
586
587
|
if (char === '\u007f' || char === '\b')
|
|
@@ -588,7 +589,7 @@ export async function readSecret(input = process.stdin, output = process.stderr)
|
|
|
588
589
|
else if (char >= ' ')
|
|
589
590
|
value += char;
|
|
590
591
|
if (value.length > 8192) {
|
|
591
|
-
finish(new Error(ui('Key input is too long.',
|
|
592
|
+
finish(new Error(ui('Key input is too long.', zhText("m273"))));
|
|
592
593
|
return;
|
|
593
594
|
}
|
|
594
595
|
}
|
|
@@ -599,11 +600,11 @@ export async function readSecret(input = process.stdin, output = process.stderr)
|
|
|
599
600
|
input.once('end', onEnd);
|
|
600
601
|
input.once('close', onEnd);
|
|
601
602
|
input.once('error', onError);
|
|
602
|
-
output.write(ui('Enter the key: ',
|
|
603
|
+
output.write(ui('Enter the key: ', zhText("m272")));
|
|
603
604
|
input.resume();
|
|
604
605
|
}
|
|
605
606
|
catch (error) {
|
|
606
|
-
finish(error instanceof Error ? error : new Error(ui('Could not disable terminal echo.',
|
|
607
|
+
finish(error instanceof Error ? error : new Error(ui('Could not disable terminal echo.', zhText("m271"))));
|
|
607
608
|
}
|
|
608
609
|
});
|
|
609
610
|
}
|