@switchbot/openapi-cli 3.4.0 → 3.4.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.
Files changed (3) hide show
  1. package/README.md +110 -602
  2. package/dist/index.js +177 -40
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7221,7 +7221,7 @@ function emitJsonError(errorPayload) {
7221
7221
  function emitStreamHeader(opts) {
7222
7222
  console.log(
7223
7223
  JSON.stringify({
7224
- schemaVersion: SCHEMA_VERSION,
7224
+ schemaVersion: opts.schemaVersion ?? SCHEMA_VERSION,
7225
7225
  stream: true,
7226
7226
  eventKind: opts.eventKind,
7227
7227
  cadence: opts.cadence
@@ -7531,7 +7531,7 @@ var init_output = __esm({
7531
7531
  init_source();
7532
7532
  init_client();
7533
7533
  init_flags();
7534
- SCHEMA_VERSION = "1.1";
7534
+ SCHEMA_VERSION = "1.2";
7535
7535
  ASCII_BORDER_CHARS = {
7536
7536
  top: "-",
7537
7537
  "top-mid": "+",
@@ -28296,8 +28296,21 @@ function commandToJson(cmd, opts = {}) {
28296
28296
  }
28297
28297
  function resolveTargetCommand(root, argv) {
28298
28298
  let cmd = root;
28299
+ const rootOptions = root.options;
28300
+ let consumeNext = false;
28299
28301
  for (const token of argv) {
28300
- if (token.startsWith("-")) continue;
28302
+ if (consumeNext) {
28303
+ consumeNext = false;
28304
+ continue;
28305
+ }
28306
+ if (token.startsWith("-")) {
28307
+ if (!token.includes("=")) {
28308
+ const localOpts = cmd.options;
28309
+ const opt = localOpts.find((o) => o.short === token || o.long === token) || rootOptions.find((o) => o.short === token || o.long === token);
28310
+ if (opt && (opt.required || opt.optional)) consumeNext = true;
28311
+ }
28312
+ continue;
28313
+ }
28301
28314
  const sub = cmd.commands.find(
28302
28315
  (c) => c.name() === token || c.aliases().includes(token)
28303
28316
  );
@@ -31763,6 +31776,26 @@ function buildRelaySetMode(opts) {
31763
31776
  }
31764
31777
  return `${ch};${modeInt}`;
31765
31778
  }
31779
+ function buildBrightnessSet(opts) {
31780
+ if (!opts.brightness) throw new UsageError("--brightness is required (1-100)");
31781
+ const b2 = parseInt(opts.brightness, 10);
31782
+ if (!Number.isFinite(b2) || b2 < 1 || b2 > 100) {
31783
+ throw new UsageError(`--brightness must be an integer between 1 and 100 (got "${opts.brightness}")`);
31784
+ }
31785
+ return String(b2);
31786
+ }
31787
+ function buildColorSet(opts) {
31788
+ if (!opts.color) throw new UsageError('--color is required (e.g. "255:0:0", "#FF0000", "red")');
31789
+ const result = validateSetColor(opts.color);
31790
+ if (!result.ok) throw new UsageError(result.error);
31791
+ return result.normalized ?? opts.color;
31792
+ }
31793
+ function buildColorTemperatureSet(opts) {
31794
+ if (!opts.colorTemp) throw new UsageError("--color-temp is required (2700-6500)");
31795
+ const result = validateSetColorTemperature(opts.colorTemp);
31796
+ if (!result.ok) throw new UsageError(result.error);
31797
+ return result.normalized ?? opts.colorTemp;
31798
+ }
31766
31799
  function validateParameter(deviceType, command, raw) {
31767
31800
  if (!deviceType) return { ok: true };
31768
31801
  if (deviceType === "Air Conditioner" && command === "setAll") {
@@ -31783,7 +31816,7 @@ function validateParameter(deviceType, command, raw) {
31783
31816
  if (command === "setColor" && isColorDevice(deviceType)) {
31784
31817
  return validateSetColor(raw);
31785
31818
  }
31786
- if (command === "setColorTemperature" && isColorDevice(deviceType)) {
31819
+ if (command === "setColorTemperature" && isBrightnessDevice(deviceType)) {
31787
31820
  return validateSetColorTemperature(raw);
31788
31821
  }
31789
31822
  return { ok: true };
@@ -31792,7 +31825,12 @@ function isBrightnessDevice(deviceType) {
31792
31825
  return deviceType === "Color Bulb" || deviceType === "Strip Light" || deviceType === "Strip Light 3" || deviceType === "Ceiling Light" || deviceType === "Ceiling Light Pro" || deviceType === "Floor Lamp" || deviceType === "Light Strip" || deviceType === "Dimmer" || deviceType === "Fill Light";
31793
31826
  }
31794
31827
  function isColorDevice(deviceType) {
31795
- return deviceType === "Color Bulb" || deviceType === "Strip Light" || deviceType === "Strip Light 3" || deviceType === "Ceiling Light" || deviceType === "Ceiling Light Pro" || deviceType === "Floor Lamp" || deviceType === "Light Strip" || deviceType === "Fill Light";
31828
+ return deviceType === "Color Bulb" || deviceType === "Strip Light" || deviceType === "Strip Light 3" || deviceType === "Floor Lamp" || deviceType === "Light Strip" || deviceType === "Fill Light";
31829
+ }
31830
+ function isLightingCommandSupported(deviceType, command) {
31831
+ if (command === "setBrightness" || command === "setColorTemperature") return isBrightnessDevice(deviceType);
31832
+ if (command === "setColor") return isColorDevice(deviceType);
31833
+ return false;
31796
31834
  }
31797
31835
  function validateSetBrightness(raw) {
31798
31836
  if (raw === void 0 || raw === "" || raw === "default") {
@@ -32923,10 +32961,11 @@ init_arg_parsers();
32923
32961
  init_output();
32924
32962
  init_cache();
32925
32963
  init_devices();
32964
+ init_catalog();
32926
32965
  init_flags();
32927
32966
  init_client();
32928
32967
  function registerExpandCommand(devices) {
32929
- devices.command("expand").description("Send a command with semantic flags instead of raw positional parameters").argument("[deviceId]", 'Target device ID from "devices list" (or use --name)').argument("[command]", "Command name: setAll (AC), setPosition (Curtain/Blind Tilt), setMode (Relay Switch 2)").option("--name <query>", "Resolve device by fuzzy name instead of deviceId", stringArg("--name")).option("--name-strategy <s>", `Name match strategy: ${ALL_STRATEGIES.join("|")} (default: require-unique)`, stringArg("--name-strategy")).option("--name-type <type>", 'Narrow --name by device type (e.g. "Curtain", "Air Conditioner")', stringArg("--name-type")).option("--name-category <cat>", "Narrow --name by category: physical|ir", enumArg("--name-category", ["physical", "ir"])).option("--name-room <room>", "Narrow --name by room name (substring match)", stringArg("--name-room")).option("--temp <celsius>", "AC setAll: temperature in Celsius (16-30)", intArg("--temp", { min: 16, max: 30 })).option("--mode <mode>", "AC: auto|cool|dry|fan|heat Curtain: default|performance|silent Relay: toggle|edge|detached|momentary", stringArg("--mode")).option("--fan <speed>", "AC setAll: fan speed auto|low|mid|high", stringArg("--fan")).option("--power <state>", "AC setAll: on|off", stringArg("--power")).option("--position <percent>", "Curtain setPosition: 0-100 (0=open, 100=closed)", intArg("--position", { min: 0, max: 100 })).option("--direction <dir>", "Blind Tilt setPosition: up|down", stringArg("--direction")).option("--angle <percent>", "Blind Tilt setPosition: 0-100 (0=closed, 100=open)", intArg("--angle", { min: 0, max: 100 })).option("--channel <n>", "Relay Switch 2 setMode: channel 1 or 2", intArg("--channel", { min: 1, max: 2 })).option("--yes", "Confirm destructive commands").addHelpText("after", `
32968
+ devices.command("expand").description("Send a command with semantic flags instead of raw positional parameters").argument("[deviceId]", 'Target device ID from "devices list" (or use --name)').argument("[command]", "Command name: setAll (AC), setPosition (Curtain/Blind Tilt), setMode (Relay Switch 2), setBrightness/setColor/setColorTemperature (lighting)").option("--name <query>", "Resolve device by fuzzy name instead of deviceId", stringArg("--name")).option("--name-strategy <s>", `Name match strategy: ${ALL_STRATEGIES.join("|")} (default: require-unique)`, stringArg("--name-strategy")).option("--name-type <type>", 'Narrow --name by device type (e.g. "Curtain", "Air Conditioner")', stringArg("--name-type")).option("--name-category <cat>", "Narrow --name by category: physical|ir", enumArg("--name-category", ["physical", "ir"])).option("--name-room <room>", "Narrow --name by room name (substring match)", stringArg("--name-room")).option("--temp <celsius>", "AC setAll: temperature in Celsius (16-30)", intArg("--temp", { min: 16, max: 30 })).option("--mode <mode>", "AC: auto|cool|dry|fan|heat Curtain: default|performance|silent Relay: toggle|edge|detached|momentary", stringArg("--mode")).option("--fan <speed>", "AC setAll: fan speed auto|low|mid|high", stringArg("--fan")).option("--power <state>", "AC setAll: on|off", stringArg("--power")).option("--position <percent>", "Curtain setPosition: 0-100 (0=open, 100=closed)", intArg("--position", { min: 0, max: 100 })).option("--direction <dir>", "Blind Tilt setPosition: up|down", stringArg("--direction")).option("--angle <percent>", "Blind Tilt setPosition: 0-100 (0=closed, 100=open)", intArg("--angle", { min: 0, max: 100 })).option("--channel <n>", "Relay Switch 2 setMode: channel 1 or 2", intArg("--channel", { min: 1, max: 2 })).option("--brightness <percent>", "setBrightness: 1-100 percent", intArg("--brightness", { min: 1, max: 100 })).option("--color <value>", "setColor: R:G:B, #RRGGBB, or named color (red, blue, etc.)", stringArg("--color")).option("--color-temp <kelvin>", "setColorTemperature: 2700-6500 Kelvin", intArg("--color-temp", { min: 2700, max: 6500 })).option("--yes", "Confirm destructive commands").addHelpText("after", `
32930
32969
  Translates semantic flags into the wire parameter format, then sends the command.
32931
32970
 
32932
32971
  Supported expansions:
@@ -32947,12 +32986,25 @@ Supported expansions:
32947
32986
  --channel 1 --mode edge \u2192 "1;1"
32948
32987
  --mode values: toggle (0) | edge (1) | detached (2) | momentary (3)
32949
32988
 
32989
+ Color Bulb / Strip Light / Ceiling Light \u2014 setBrightness
32990
+ --brightness 80 \u2192 "80"
32991
+
32992
+ Color Bulb / Strip Light / Floor Lamp \u2014 setColor
32993
+ --color "255:0:0" \u2192 "255:0:0"
32994
+ --color "#FF0000" \u2192 "255:0:0"
32995
+ --color red \u2192 "255:0:0"
32996
+
32997
+ Color Bulb / Strip Light / Ceiling Light \u2014 setColorTemperature
32998
+ --color-temp 4000 \u2192 "4000"
32999
+
32950
33000
  Examples:
32951
- $ switchbot devices expand <acId> setAll --temp 26 --mode cool --fan low --power on
32952
- $ switchbot devices expand <curtainId> setPosition --position 50 --mode silent
32953
- $ switchbot devices expand <blindId> setPosition --direction up --angle 50
32954
- $ switchbot devices expand <relayId> setMode --channel 1 --mode edge
32955
- $ switchbot devices expand <acId> setAll --temp 22 --mode heat --fan auto --power on --dry-run
33001
+ $ switchbot devices expand <acId> setAll --temp 26 --mode cool --fan low --power on
33002
+ $ switchbot devices expand <curtainId> setPosition --position 50 --mode silent
33003
+ $ switchbot devices expand <blindId> setPosition --direction up --angle 50
33004
+ $ switchbot devices expand <relayId> setMode --channel 1 --mode edge
33005
+ $ switchbot devices expand <stripId> setBrightness --brightness 80
33006
+ $ switchbot devices expand <bulbId> setColor --color "#FF0000"
33007
+ $ switchbot devices expand <bulbId> setColorTemperature --color-temp 4000
32956
33008
  $ switchbot devices expand --name "Living Room AC" setAll --temp 26 --mode cool --fan low --power on
32957
33009
  `).action(async (deviceIdArg, commandArg, options) => {
32958
33010
  let deviceId = "";
@@ -32970,12 +33022,22 @@ Examples:
32970
33022
  category: options.nameCategory,
32971
33023
  room: options.nameRoom
32972
33024
  });
32973
- if (!effectiveCommand) throw new UsageError("A command argument is required (setAll, setPosition, setMode).");
33025
+ if (!effectiveCommand) throw new UsageError("A command argument is required (setAll, setPosition, setMode, setBrightness, setColor, setColorTemperature).");
32974
33026
  command = effectiveCommand;
32975
33027
  const cached2 = getCachedDevice(deviceId);
32976
33028
  const deviceType = cached2?.type ?? "";
32977
33029
  let parameter;
32978
33030
  if (command === "setAll") {
33031
+ if (!cached2) {
33032
+ throw new UsageError(
33033
+ `Device ${deviceId} is not in the local cache \u2014 run 'switchbot devices list' first so 'expand' can verify this is an Air Conditioner.`
33034
+ );
33035
+ }
33036
+ if (deviceType !== "Air Conditioner") {
33037
+ throw new UsageError(
33038
+ `"setAll" is only supported on Air Conditioner devices, but "${cached2.type}" was found.`
33039
+ );
33040
+ }
32979
33041
  parameter = buildAcSetAll(options);
32980
33042
  } else if (command === "setPosition") {
32981
33043
  if (!cached2) {
@@ -32983,13 +33045,56 @@ Examples:
32983
33045
  `Device ${deviceId} is not in the local cache \u2014 run 'switchbot devices list' first so 'expand' knows whether this is a Curtain or a Blind Tilt.`
32984
33046
  );
32985
33047
  }
33048
+ const positionTypes = ["Curtain", "Curtain 3", "Roller Shade", "Blind Tilt"];
33049
+ if (!positionTypes.some((t) => deviceType.startsWith(t))) {
33050
+ throw new UsageError(
33051
+ `"setPosition" is only supported on Curtain, Roller Shade, and Blind Tilt devices, but "${cached2.type}" was found.`
33052
+ );
33053
+ }
32986
33054
  const isBlind = deviceType.startsWith("Blind Tilt");
32987
- parameter = isBlind ? buildBlindTiltSetPosition(options) : buildCurtainSetPosition(options);
33055
+ const isRollerShade = deviceType.startsWith("Roller Shade");
33056
+ if (isBlind) {
33057
+ parameter = buildBlindTiltSetPosition(options);
33058
+ } else if (isRollerShade) {
33059
+ if (!options.position) throw new UsageError("--position is required (0-100)");
33060
+ parameter = options.position;
33061
+ } else {
33062
+ parameter = buildCurtainSetPosition(options);
33063
+ }
32988
33064
  } else if (command === "setMode" && deviceType.startsWith("Relay Switch")) {
32989
33065
  parameter = buildRelaySetMode(options);
33066
+ } else if (command === "setBrightness" || command === "setColor" || command === "setColorTemperature") {
33067
+ if (!cached2) {
33068
+ throw new UsageError(
33069
+ `Device "${deviceId}" is not in the local cache \u2014 run 'switchbot devices list' first so 'expand' can verify this device supports ${command}.`
33070
+ );
33071
+ }
33072
+ const catalogResult = findCatalogEntry(cached2.type);
33073
+ const catalogEntry = Array.isArray(catalogResult) ? catalogResult[0] : catalogResult;
33074
+ const supportedHint = command === "setColor" ? "Color Bulb, Strip Light, Floor Lamp, and similar RGB lighting devices" : "Color Bulb, Strip Light, Ceiling Light, Floor Lamp, and similar lighting devices";
33075
+ if (catalogEntry !== null) {
33076
+ if (!catalogEntry.commands.some((c) => c.command === command)) {
33077
+ throw new UsageError(
33078
+ `Device type "${cached2.type}" does not support ${command}. Supported on: ${supportedHint}.`
33079
+ );
33080
+ }
33081
+ } else {
33082
+ if (!isLightingCommandSupported(cached2.type, command)) {
33083
+ throw new UsageError(
33084
+ `Device type "${cached2.type}" does not support ${command}. Supported on: ${supportedHint}.`
33085
+ );
33086
+ }
33087
+ }
33088
+ if (command === "setBrightness") {
33089
+ parameter = buildBrightnessSet(options);
33090
+ } else if (command === "setColor") {
33091
+ parameter = buildColorSet(options);
33092
+ } else {
33093
+ parameter = buildColorTemperatureSet(options);
33094
+ }
32990
33095
  } else {
32991
33096
  throw new UsageError(
32992
- `'expand' does not support "${command}" for device type "${deviceType || "unknown"}". Use 'switchbot devices command' to send raw parameters instead.`
33097
+ `'expand' does not support "${command}" for device type "${deviceType || "unknown"}". Supported: setAll (AC), setPosition (Curtain/Blind Tilt), setMode (Relay Switch), setBrightness/setColor/setColorTemperature (lighting). Use 'switchbot devices command' to send raw parameters instead.`
32993
33098
  );
32994
33099
  }
32995
33100
  if (!options.yes && !isDryRun() && isDestructiveCommand(deviceType, command, "command")) {
@@ -33402,7 +33507,7 @@ Examples:
33402
33507
  const results = await Promise.allSettled(ids.map((id) => fetchDeviceStatus(id)));
33403
33508
  const fetchedAt2 = (/* @__PURE__ */ new Date()).toISOString();
33404
33509
  const batch = results.map(
33405
- (r, i) => r.status === "fulfilled" ? { deviceId: ids[i], ok: true, _fetchedAt: fetchedAt2, ...annotateStatusPayload(ids[i], r.value) } : { deviceId: ids[i], ok: false, error: r.reason?.message ?? String(r.reason) }
33510
+ (r, i) => r.status === "fulfilled" ? { deviceId: ids[i], ok: true, fetchedAt: fetchedAt2, ...annotateStatusPayload(ids[i], r.value) } : { deviceId: ids[i], ok: false, error: r.reason?.message ?? String(r.reason) }
33406
33511
  );
33407
33512
  const batchFmt = resolveFormat();
33408
33513
  if (isJsonMode() || batchFmt === "json") {
@@ -33414,7 +33519,7 @@ Examples:
33414
33519
  } else {
33415
33520
  const rawFields = resolveFields();
33416
33521
  for (const entry of batch) {
33417
- const { deviceId: deviceId2, ok, error: error48, _fetchedAt: ts, ...status } = entry;
33522
+ const { deviceId: deviceId2, ok, error: error48, fetchedAt: ts, ...status } = entry;
33418
33523
  console.log(`
33419
33524
  \u2500\u2500\u2500 ${String(deviceId2)} \u2500\u2500\u2500`);
33420
33525
  if (!ok) {
@@ -33440,11 +33545,11 @@ Examples:
33440
33545
  const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
33441
33546
  const fmt = resolveFormat();
33442
33547
  if (fmt === "json" && process.argv.includes("--json")) {
33443
- printJson({ ...body, _fetchedAt: fetchedAt });
33548
+ printJson({ ...body, fetchedAt });
33444
33549
  return;
33445
33550
  }
33446
33551
  if (fmt !== "table") {
33447
- const statusWithTs = { ...body, _fetchedAt: fetchedAt };
33552
+ const statusWithTs = { ...body, fetchedAt };
33448
33553
  const allHeaders = Object.keys(statusWithTs);
33449
33554
  const allRows = [Object.values(statusWithTs)];
33450
33555
  const rawFields = resolveFields();
@@ -33773,7 +33878,7 @@ Examples:
33773
33878
  const joinedMatch = findCatalogEntry(joined);
33774
33879
  if (joinedMatch && !Array.isArray(joinedMatch)) {
33775
33880
  if (isJsonMode()) {
33776
- printJson(normalizeCatalogForJson(joinedMatch));
33881
+ printJson([normalizeCatalogForJson(joinedMatch)]);
33777
33882
  } else {
33778
33883
  renderCatalogEntry(joinedMatch);
33779
33884
  }
@@ -48672,9 +48777,9 @@ var logLevel = process.env.LOG_LEVEL || "warn";
48672
48777
  var logFormat = process.env.LOG_FORMAT || "json";
48673
48778
  var pinoConfig = {
48674
48779
  level: logLevel,
48675
- transport: logFormat === "pretty" ? { target: "pino-pretty" } : void 0
48780
+ transport: logFormat === "pretty" ? { target: "pino-pretty", options: { destination: 2 } } : void 0
48676
48781
  };
48677
- var log = pino(pinoConfig);
48782
+ var log = logFormat === "pretty" ? pino(pinoConfig) : pino(pinoConfig, pino.destination(2));
48678
48783
 
48679
48784
  // src/mcp/device-history.ts
48680
48785
  init_cjs_shim();
@@ -52513,12 +52618,27 @@ function listRegisteredTools(server) {
52513
52618
  if (!internal._registeredTools) return [];
52514
52619
  return Object.keys(internal._registeredTools).sort();
52515
52620
  }
52621
+ function listRegisteredToolsWithMeta(server) {
52622
+ const internal = server;
52623
+ if (!internal._registeredTools) return [];
52624
+ return Object.entries(internal._registeredTools).sort(([a], [b2]) => a.localeCompare(b2)).map(([name, reg]) => {
52625
+ const entry = { name };
52626
+ if (reg.description) entry.description = reg.description;
52627
+ if (reg.inputSchema) {
52628
+ try {
52629
+ entry.inputSchema = external_exports.toJSONSchema(reg.inputSchema);
52630
+ } catch {
52631
+ }
52632
+ }
52633
+ return entry;
52634
+ });
52635
+ }
52516
52636
  function listRegisteredResources() {
52517
52637
  return ["switchbot://events"];
52518
52638
  }
52519
52639
  function printMcpToolDirectory() {
52520
52640
  const server = createSwitchBotMcpServer();
52521
- const tools = listRegisteredTools(server).map((name) => ({ name }));
52641
+ const tools = listRegisteredToolsWithMeta(server);
52522
52642
  const resources = listRegisteredResources().map((uri) => ({ uri }));
52523
52643
  if (isJsonMode()) {
52524
52644
  printJson({ tools, resources });
@@ -52526,7 +52646,8 @@ function printMcpToolDirectory() {
52526
52646
  }
52527
52647
  console.log("Tools:");
52528
52648
  for (const tool of tools) {
52529
- console.log(` ${tool.name}`);
52649
+ const desc = tool.description ? ` \u2014 ${tool.description.slice(0, 80)}` : "";
52650
+ console.log(` ${tool.name}${desc}`);
52530
52651
  }
52531
52652
  console.log("");
52532
52653
  console.log("Resources:");
@@ -52538,7 +52659,7 @@ Total: ${tools.length} tool(s), ${resources.length} resource(s)`);
52538
52659
  }
52539
52660
  function registerMcpCommand(program3) {
52540
52661
  const mcp = program3.command("mcp").description("Run as a Model Context Protocol server so AI agents can call SwitchBot tools").addHelpText("after", `
52541
- The MCP server exposes twenty-one tools:
52662
+ The MCP server exposes twenty-four tools:
52542
52663
  - list_devices fetch all physical + IR devices
52543
52664
  - get_device_status live status for a physical device
52544
52665
  - send_command control a device (destructive commands need confirm:true)
@@ -52561,6 +52682,8 @@ function registerMcpCommand(program3) {
52561
52682
  - audit_stats aggregate audit counts by kind/result/device/rule
52562
52683
  - rule_notifications query rule notify action delivery history
52563
52684
  - rules_suggest draft an automation rule YAML from intent (heuristic, no LLM)
52685
+ - rules_explain show why a rule evaluation fired or was blocked
52686
+ - rules_simulate simulate a rule against historical events
52564
52687
  - policy_add_rule append a rule into automation.rules[] in policy.yaml
52565
52688
 
52566
52689
  Resource (read-only):
@@ -53003,7 +53126,7 @@ Examples:
53003
53126
  throw new UsageError(`"${match.type}" exists in the effective catalog but not in source "${source}".`);
53004
53127
  }
53005
53128
  if (isJsonMode()) {
53006
- printJson(picked);
53129
+ printJson([picked]);
53007
53130
  return;
53008
53131
  }
53009
53132
  renderEntry(picked);
@@ -53745,7 +53868,7 @@ Examples:
53745
53868
  let matchedCount = 0;
53746
53869
  const ac = new AbortController();
53747
53870
  const forTimer = forMs !== null && forMs > 0 ? setTimeout(() => ac.abort(), forMs) : null;
53748
- if (isJsonMode()) emitStreamHeader({ eventKind: "event", cadence: "push" });
53871
+ if (isJsonMode()) emitStreamHeader({ eventKind: "event", cadence: "push", schemaVersion: EVENTS_SCHEMA_VERSION });
53749
53872
  await new Promise((resolve2, reject) => {
53750
53873
  let server = null;
53751
53874
  try {
@@ -53897,7 +54020,7 @@ Examples:
53897
54020
  if (!isJsonMode()) {
53898
54021
  console.error("Fetching MQTT credentials from SwitchBot service\u2026");
53899
54022
  }
53900
- if (isJsonMode()) emitStreamHeader({ eventKind: "event", cadence: "push" });
54023
+ if (isJsonMode()) emitStreamHeader({ eventKind: "event", cadence: "push", schemaVersion: EVENTS_SCHEMA_VERSION });
53901
54024
  if (isJsonMode()) {
53902
54025
  const sessionStartAt = (/* @__PURE__ */ new Date()).toISOString();
53903
54026
  emitJsonStreamRecord({
@@ -55467,7 +55590,7 @@ function runSchemaExport(options) {
55467
55590
  payload.resources = RESOURCE_CATALOG;
55468
55591
  payload.cliAddedFields = [
55469
55592
  {
55470
- field: "_fetchedAt",
55593
+ field: "fetchedAt",
55471
55594
  appliesTo: ["devices status", "devices describe"],
55472
55595
  type: "string (ISO-8601)",
55473
55596
  description: "CLI-synthesized timestamp indicating when this status response was fetched or served from the cache. Not part of the upstream SwitchBot API."
@@ -55530,7 +55653,7 @@ Common top-level fields:
55530
55653
  schemaVersion CLI schema version (stable for agent contracts)
55531
55654
  data.version Catalog schema version
55532
55655
  data.types Array of SchemaEntry (or CompactSchemaEntry with --compact)
55533
- data._fetchedAt CLI-added; present on live-query responses ('devices status'),
55656
+ data.fetchedAt CLI-added; present on live-query responses ('devices status'),
55534
55657
  not on this offline export.
55535
55658
 
55536
55659
  Examples:
@@ -56841,13 +56964,12 @@ function registerRun(rules) {
56841
56964
  const loaded = loadAutomation(pathArg);
56842
56965
  if (!loaded) return;
56843
56966
  if (loaded.automation?.enabled !== true) {
56844
- const msg = "automation.enabled is not true \u2014 nothing to run.";
56845
- if (isJsonMode()) {
56846
- printJson({ kind: "control", controlKind: "disabled", message: msg });
56847
- } else {
56848
- console.error(msg);
56849
- }
56850
- process.exit(0);
56967
+ exitWithError({
56968
+ code: 1,
56969
+ kind: "runtime",
56970
+ message: "automation.enabled is not true \u2014 set it to true in your policy file to start the daemon.",
56971
+ hint: "Set automation.enabled: true in your policy file, then re-run."
56972
+ });
56851
56973
  }
56852
56974
  const lint = lintRules(loaded.automation);
56853
56975
  if (!lint.valid) {
@@ -59658,7 +59780,7 @@ The daemon reads the same policy file as \`switchbot rules run\`.
59658
59780
  }
59659
59781
  }
59660
59782
  const thisFile = fileURLToPath3(import.meta.url);
59661
- const cliEntry = path28.resolve(path28.dirname(thisFile), "..", "index.js");
59783
+ const cliEntry = path28.basename(thisFile) === "index.js" ? thisFile : path28.resolve(path28.dirname(thisFile), "..", "index.js");
59662
59784
  const args = ["rules", "run"];
59663
59785
  if (opts.policy) args.push(opts.policy);
59664
59786
  fs32.mkdirSync(path28.dirname(DAEMON_PID_FILE), { recursive: true, mode: 448 });
@@ -60015,17 +60137,32 @@ try {
60015
60137
  } catch (err) {
60016
60138
  if (err instanceof CommanderError) {
60017
60139
  if (err.code === "commander.helpDisplayed") {
60140
+ const helpRequested = process.argv.includes("--help") || process.argv.includes("-h") || process.argv.includes("help");
60141
+ if (helpRequested) {
60142
+ if (isJsonMode()) {
60143
+ const target = resolveTargetCommand(program2, process.argv.slice(2));
60144
+ printJson(commandToJson(target, { includeIdentity: target === program2 }));
60145
+ }
60146
+ process.exit(0);
60147
+ }
60018
60148
  if (isJsonMode()) {
60019
60149
  const target = resolveTargetCommand(program2, process.argv.slice(2));
60020
- printJson(commandToJson(target, { includeIdentity: target === program2 }));
60150
+ const subNames = target.commands.map((c) => c.name()).join(", ");
60151
+ const usefulMessage = subNames ? `${target.name()}: a subcommand is required. Available: ${subNames}` : err.message;
60152
+ emitJsonError({ code: 2, kind: "usage", message: usefulMessage });
60021
60153
  }
60022
- process.exit(0);
60154
+ process.exit(2);
60023
60155
  }
60024
60156
  if (err.code === "commander.version") {
60025
60157
  process.exit(0);
60026
60158
  }
60027
60159
  if (isJsonMode()) {
60028
- emitJsonError({ code: 2, kind: "usage", message: err.message });
60160
+ const errorMessage = err.code === "commander.help" ? (() => {
60161
+ const target = resolveTargetCommand(program2, process.argv.slice(2));
60162
+ const subNames = target.commands.map((c) => c.name()).join(", ");
60163
+ return subNames ? `${target.name()}: a subcommand is required. Available: ${subNames}` : err.message;
60164
+ })() : err.message;
60165
+ emitJsonError({ code: 2, kind: "usage", message: errorMessage });
60029
60166
  }
60030
60167
  process.exit(2);
60031
60168
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@switchbot/openapi-cli",
3
- "version": "3.4.0",
3
+ "version": "3.4.1",
4
4
  "description": "SwitchBot smart home CLI — control devices, run scenes, stream real-time events, and integrate AI agents via MCP. Full API v1.1 coverage.",
5
5
  "keywords": [
6
6
  "switchbot",