@stalfh233/omc-cli 0.6.10 → 0.6.12

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.
@@ -46,7 +46,28 @@ export function asciiStdout() {
46
46
  // ever answer "not a Windows code page here".
47
47
  if (process.platform !== "win32")
48
48
  return false;
49
- return asciiStdoutFor(process.platform, process.stdout.isTTY === true, activeCodePage());
49
+ return asciiStdoutFor(process.platform, process.stdout.isTTY === true, activeCodePage(), process.env);
50
+ }
51
+ /**
52
+ * Hosts that are known to decode UTF-8 from a pipe regardless of the console
53
+ * code page. Escaping inside them is **pure loss**: the reader is a UTF-8
54
+ * consumer (Windows Terminal, VS Code, an agent harness), so `\u6295\u5f71`
55
+ * reads as garbage to the human skimming the transcript while the JSON was
56
+ * parseable either way.
57
+ *
58
+ * 2026-11-05 用户实测(Windows + agent harness):整屏 `\uXXXX`,人眼无法阅读。
59
+ * 之前只判「TTY 与否 + 代码页」,把「现代终端里被 harness 捕获」也当成了旧管道。
60
+ */
61
+ export function utf8CapableHost(env) {
62
+ // Windows Terminal 与任何设置了 TERM_PROGRAM 的宿主(VS Code / WezTerm / …)。
63
+ if (typeof env.WT_SESSION === "string" && env.WT_SESSION !== "")
64
+ return true;
65
+ if (typeof env.TERM_PROGRAM === "string" && env.TERM_PROGRAM !== "")
66
+ return true;
67
+ // 与上面同理:显式声明了 TERM 的现代终端/PTY。
68
+ if (typeof env.TERM === "string" && env.TERM !== "" && env.TERM !== "dumb")
69
+ return true;
70
+ return false;
50
71
  }
51
72
  /**
52
73
  * T2(GitLab #17,2026-11-04):`--utf8` 是「人类可读文本按 UTF-8 原文输出」的
@@ -110,11 +131,14 @@ function activeCodePage() {
110
131
  return cachedCodePage;
111
132
  }
112
133
  /** Pure core of asciiStdout for tests: platform × tty × active code page. */
113
- export function asciiStdoutFor(platform, isTty, codePage) {
134
+ export function asciiStdoutFor(platform, isTty, codePage, env = {}) {
114
135
  if (platform !== "win32")
115
136
  return false;
116
137
  if (isTty)
117
138
  return false;
139
+ // 已知 UTF-8 宿主:代码页是旧的不代表**消费者**解不了 UTF-8(harness / 现代终端)。
140
+ if (utf8CapableHost(env))
141
+ return false;
118
142
  // Unknown code page (chcp unavailable — modern terminal hosts): trust
119
143
  // UTF-8. Windows Terminal defaults to it, and \u escapes are unreadable to
120
144
  // humans either way.
@@ -132,8 +132,12 @@ function emitAsciiBannerOnce() {
132
132
  // 注意:ascii 模式下 stderr 本身也会被转义,所以横幅必须是纯 ASCII。
133
133
  if (process.env.OMC_ASCII_STDOUT !== undefined || process.env.OMC_HUMAN_UTF8 !== undefined)
134
134
  return;
135
- process.stderr.write("[omc] non-UTF-8 console code page: the JSON envelope is \\u-escaped so it stays parseable. "
136
- + "Human text: `chcp 65001`, or add --utf8 (stderr only; the envelope stays escaped by design).\n");
135
+ // 2026-11-05:旧横幅只给了 `--utf8`,而它**只管 stderr**——用户要的正是不想再
136
+ // 看 `\uXXXX`,照它做没有效果。给出真正能关掉转义的两个办法。
137
+ process.stderr.write("[omc] legacy console code page detected: the JSON envelope is \\u-escaped (e.g. \\u6295\\u5f71) so it stays parseable.\n"
138
+ + " If your consumer decodes UTF-8 (agent harness / VS Code / Windows Terminal), turn it off:\n"
139
+ + " set OMC_ASCII_STDOUT=0 (Windows cmd / PowerShell: $env:OMC_ASCII_STDOUT=0)\n"
140
+ + " or switch the console itself: chcp 65001\n");
137
141
  }
138
142
  /**
139
143
  * Attach a repeat warning to a refusal that repeats the previous one. Refusal
package/dist/cli.js CHANGED
@@ -1027,11 +1027,31 @@ async function cmdBench(args) {
1027
1027
  const liveSample = args.includes("--live-sample");
1028
1028
  const envIndex = args.indexOf("--env");
1029
1029
  const liveEnvironment = envIndex >= 0 ? args[envIndex + 1] : undefined;
1030
- const { runBench, formatBenchReport } = await import("./bench/run.js");
1030
+ // 2026-11-05(安装事故):`@stalfh233/omc-fake-platform` 是**评测基座**,只有
1031
+ // `omc bench` 用得到;它此前是硬依赖,导致「这一个包在某人缓存里还没同步」
1032
+ // 就把整次 `npm i -g omc-cli` 卡死(ETARGET)。现在它降为 optionalDependencies
1033
+ // (装不上不阻塞安装),bench 在缺失时给可执行的补救指引而不是抛堆栈。
1034
+ let runBenchImport;
1035
+ try {
1036
+ const mod = await import("./bench/run.js");
1037
+ runBenchImport = async () => mod;
1038
+ }
1039
+ catch (error) {
1040
+ const code = error.code;
1041
+ if (code === "ERR_MODULE_NOT_FOUND" && String(error.message).includes("omc-fake-platform")) {
1042
+ console.error("omc bench 需要评测基座 @stalfh233/omc-fake-platform(**可选依赖**,不装 CLI 也能正常用)。\n" +
1043
+ "补齐:npm i -D @stalfh233/omc-fake-platform@<与 omc-cli 同版本>\n" +
1044
+ "(它只是本地假平台/假 HTTP 服务器,仅 bench 与测试使用,配置 CloudPivot 完全不需要。)");
1045
+ return 10;
1046
+ }
1047
+ throw error;
1048
+ }
1049
+ const { runBench, formatBenchReport } = await runBenchImport();
1031
1050
  const report = await runBench({ mode: liveSample ? "live-sample" : "fake", ...(liveEnvironment ? { liveEnvironment } : {}) });
1032
1051
  console.error(formatBenchReport(report));
1033
1052
  const failedSteps = report.steps.filter((step) => !step.ok).length;
1034
- return failedSteps === 0 && report.dimensions.intent.passed === report.dimensions.intent.cases ? 0 : 1;
1053
+ const dims = report.dimensions;
1054
+ return failedSteps === 0 && dims.intent.passed === dims.intent.cases ? 0 : 1;
1035
1055
  }
1036
1056
  function cmdDiscovery(kind, transport, value, options = {}) {
1037
1057
  const registry = buildToolRegistry();
@@ -2172,7 +2192,9 @@ async function main() {
2172
2192
  commandExitCode = await withTransport(options, request, (transport) => cmdDiscovery("describe", transport, tool, { full: options.full === true, schemaOnly: options.schemaOnly === true, ...(options.param ? { param: options.param } : {}), ...(options.example ? { example: options.example } : {}) }));
2173
2193
  });
2174
2194
  command.command("contract <lane>").option("--full").option("--tool <name>").option("--max-output-bytes <bytes>").action(async (lane, options) => { commandExitCode = await withTransport(options, request, (transport) => cmdDiscovery("contract", transport, lane, { full: options.full === true, ...(options.tool ? { tool: options.tool } : {}) })); });
2175
- command.command("capabilities").option("--compact").option("--max-output-bytes <bytes>").action(async (options) => { commandExitCode = await withTransport(options, request, (transport) => cmdCapabilities(transport, options.compact === true)); });
2195
+ // 2026-11-05 用户实测:`commands --json` 存在,于是大家自然写 `capabilities --json`
2196
+ // ——旧实现报 `unknown option`(退出码 10)。输出本来就是 JSON 信封,接受它做兼容别名。
2197
+ command.command("capabilities").option("--compact").option("--json", "兼容别名:输出本就是 JSON 信封(与 commands --json 对齐)").option("--max-output-bytes <bytes>").action(async (options) => { commandExitCode = await withTransport(options, request, (transport) => cmdCapabilities(transport, options.compact === true)); });
2176
2198
  // `--env` is accepted but ignored: `next` is a zero-environment-contact route
2177
2199
  // solver, and a caller who reflexively passes `--env` should not be refused
2178
2200
  // (round 15 dogfood: `next "<goal>" --env local` → unknown option, one round).
@@ -220,6 +220,24 @@ export function pathMissEntryHint(at, available) {
220
220
  * available 不是字符串数组(范围说明 / 空对象文案)或匹配不到时不加字段——
221
221
  * 误导建议比没有更糟;过短输入由 closestMatch 自己静默。
222
222
  */
223
+ /**
224
+ * 读面里「人人都会猜错一次」的键名(2026-11-05 用户 Windows 实测):
225
+ * `app.list` 回的是 `applications`,但 `--jq .apps[]` 是所有人的第一反应,
226
+ * 而 closestMatch(编辑距离)判不了 app→application 这种**语义改名**,于是
227
+ * pathMiss 只给键列表,用户要再猜一轮。这里补一张极小的已知别名表。
228
+ * 只放**已证实的高频误写**,不要变成第二套命名。
229
+ */
230
+ const PATH_KEY_ALIASES = Object.freeze({
231
+ apps: "applications",
232
+ application: "applications",
233
+ app: "applications",
234
+ models: "items",
235
+ rows: "items",
236
+ list: "items",
237
+ content: "publishedContent",
238
+ data: "fields",
239
+ props: "properties",
240
+ });
223
241
  export function pathMissDidYouMean(at, available) {
224
242
  if (at === undefined || at.length === 0)
225
243
  return undefined;
@@ -233,7 +251,12 @@ export function pathMissDidYouMean(at, available) {
233
251
  const leaf = at.includes(".") || at.includes("/") || at.includes("[")
234
252
  ? at.replace(/^[.\/]+/, "").split(/[./\[]/).filter(Boolean).pop() ?? at
235
253
  : at;
236
- return closestMatch(leaf, keys);
254
+ const closest = closestMatch(leaf, keys);
255
+ if (closest !== undefined)
256
+ return closest;
257
+ // 语义改名(apps→applications 这类编辑距离判不出的),只在该键**确实存在**时建议。
258
+ const alias = PATH_KEY_ALIASES[leaf.toLowerCase()];
259
+ return alias !== undefined && keys.includes(alias) ? alias : undefined;
237
260
  }
238
261
  /**
239
262
  * 多路径一次投影时的**唯一一份**语法说明(放结果顶层,不再逐条复制)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stalfh233/omc-cli",
3
- "version": "0.6.10",
3
+ "version": "0.6.12",
4
4
  "description": "CloudPivot 8.6 configuration capability CLI — governed, fail-closed, coverage-manifest bounded.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -54,9 +54,8 @@
54
54
  "access": "public"
55
55
  },
56
56
  "dependencies": {
57
- "@stalfh233/omc-fake-platform": "0.6.10",
58
57
  "commander": "14.0.3",
59
- "omc-core": "0.6.9",
58
+ "omc-core": "0.6.12",
60
59
  "proper-lockfile": "4.1.2",
61
60
  "write-file-atomic": "6.0.0",
62
61
  "zod": "^3.23.8",
@@ -72,5 +71,8 @@
72
71
  "type": "git",
73
72
  "url": "https://gitlab.h3yun.com/fuwenbin/oh-my-cpagents.git",
74
73
  "directory": "packages/omc-cli"
74
+ },
75
+ "optionalDependencies": {
76
+ "@stalfh233/omc-fake-platform": "0.6.12"
75
77
  }
76
78
  }