@sayue_ltr/fleq 1.50.0 → 1.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/README.md +47 -5
  3. package/dist/config.js +35 -2
  4. package/dist/dmdata/rest-client.js +58 -3
  5. package/dist/dmdata/telegram-parser.js +37 -59
  6. package/dist/dmdata/ws-client.js +49 -18
  7. package/dist/engine/cli/cli-run.js +71 -1
  8. package/dist/engine/cli/cli.js +12 -0
  9. package/dist/engine/filter/compile-filter.js +21 -0
  10. package/dist/engine/filter/compiler.js +188 -0
  11. package/dist/engine/filter/errors.js +41 -0
  12. package/dist/engine/filter/field-registry.js +78 -0
  13. package/dist/engine/filter/index.js +15 -0
  14. package/dist/engine/filter/parser.js +137 -0
  15. package/dist/engine/filter/rank-maps.js +34 -0
  16. package/dist/engine/filter/tokenizer.js +121 -0
  17. package/dist/engine/filter/type-checker.js +104 -0
  18. package/dist/engine/filter/types.js +2 -0
  19. package/dist/engine/filter-template/pipeline-controller.js +73 -0
  20. package/dist/engine/filter-template/pipeline.js +16 -0
  21. package/dist/engine/messages/display-callbacks.js +7 -0
  22. package/dist/engine/messages/message-router.js +114 -182
  23. package/dist/engine/messages/summary-tracker.js +106 -0
  24. package/dist/engine/messages/telegram-stats.js +103 -0
  25. package/dist/engine/messages/volcano-route-handler.js +122 -0
  26. package/dist/engine/monitor/monitor.js +51 -3
  27. package/dist/engine/monitor/shutdown.js +1 -0
  28. package/dist/engine/notification/notifier.js +16 -1
  29. package/dist/engine/notification/sound-player.js +193 -28
  30. package/dist/engine/presentation/diff-store.js +158 -0
  31. package/dist/engine/presentation/diff-types.js +2 -0
  32. package/dist/engine/presentation/events/from-earthquake.js +53 -0
  33. package/dist/engine/presentation/events/from-eew.js +72 -0
  34. package/dist/engine/presentation/events/from-lg-observation.js +58 -0
  35. package/dist/engine/presentation/events/from-nankai-trough.js +39 -0
  36. package/dist/engine/presentation/events/from-raw.js +35 -0
  37. package/dist/engine/presentation/events/from-seismic-text.js +37 -0
  38. package/dist/engine/presentation/events/from-tsunami.js +51 -0
  39. package/dist/engine/presentation/events/from-volcano.js +88 -0
  40. package/dist/engine/presentation/events/to-presentation-event.js +32 -0
  41. package/dist/engine/presentation/level-helpers.js +118 -0
  42. package/dist/engine/presentation/processors/process-earthquake.js +36 -0
  43. package/dist/engine/presentation/processors/process-eew.js +90 -0
  44. package/dist/engine/presentation/processors/process-lg-observation.js +30 -0
  45. package/dist/engine/presentation/processors/process-message.js +53 -0
  46. package/dist/engine/presentation/processors/process-nankai-trough.js +30 -0
  47. package/dist/engine/presentation/processors/process-raw.js +22 -0
  48. package/dist/engine/presentation/processors/process-seismic-text.js +30 -0
  49. package/dist/engine/presentation/processors/process-tsunami.js +42 -0
  50. package/dist/engine/presentation/processors/process-volcano.js +41 -0
  51. package/dist/engine/presentation/types.js +2 -0
  52. package/dist/engine/startup/config-resolver.js +2 -0
  53. package/dist/engine/template/compile-template.js +18 -0
  54. package/dist/engine/template/compiler.js +102 -0
  55. package/dist/engine/template/field-accessor.js +25 -0
  56. package/dist/engine/template/filters.js +94 -0
  57. package/dist/engine/template/index.js +5 -0
  58. package/dist/engine/template/parser.js +190 -0
  59. package/dist/engine/template/tokenizer.js +96 -0
  60. package/dist/engine/template/types.js +2 -0
  61. package/dist/types.js +2 -1
  62. package/dist/ui/display-adapter.js +60 -0
  63. package/dist/ui/earthquake-formatter.js +17 -5
  64. package/dist/ui/eew-formatter.js +25 -10
  65. package/dist/ui/formatter.js +67 -32
  66. package/dist/ui/minimap/grid-layout.js +91 -0
  67. package/dist/ui/minimap/index.js +16 -0
  68. package/dist/ui/minimap/minimap-renderer.js +277 -0
  69. package/dist/ui/minimap/pref-mapping.js +82 -0
  70. package/dist/ui/minimap/types.js +2 -0
  71. package/dist/ui/night-overlay.js +56 -0
  72. package/dist/ui/repl-handlers/command-definitions.js +320 -0
  73. package/dist/ui/repl-handlers/index.js +11 -0
  74. package/dist/ui/repl-handlers/info-handlers.js +577 -0
  75. package/dist/ui/repl-handlers/operation-handlers.js +233 -0
  76. package/dist/ui/repl-handlers/settings-handlers.js +923 -0
  77. package/dist/ui/repl-handlers/types.js +10 -0
  78. package/dist/ui/repl.js +81 -1752
  79. package/dist/ui/statistics-formatter.js +208 -0
  80. package/dist/ui/status-line.js +69 -0
  81. package/dist/ui/summary/index.js +5 -0
  82. package/dist/ui/summary/summary-line.js +18 -0
  83. package/dist/ui/summary/summary-model.js +31 -0
  84. package/dist/ui/summary/token-builders.js +317 -0
  85. package/dist/ui/summary/types.js +2 -0
  86. package/dist/ui/summary/width-fit.js +41 -0
  87. package/dist/ui/summary-interval-formatter.js +72 -0
  88. package/dist/ui/theme.js +34 -5
  89. package/dist/ui/tip-shuffler.js +81 -0
  90. package/dist/ui/volcano-formatter.js +15 -13
  91. package/dist/ui/waiting-tips.js +289 -249
  92. package/package.json +1 -1
@@ -0,0 +1,577 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.CATEGORY_ALIASES = exports.COMMAND_ALIASES = void 0;
40
+ exports.getCurrentSettingValues = getCurrentSettingValues;
41
+ exports.handleDetail = handleDetail;
42
+ exports.handleHelp = handleHelp;
43
+ exports.handleStats = handleStats;
44
+ exports.handleHistory = handleHistory;
45
+ exports.handleStatus = handleStatus;
46
+ exports.handleConfig = handleConfig;
47
+ exports.handleContract = handleContract;
48
+ exports.handleSocket = handleSocket;
49
+ exports.handleColors = handleColors;
50
+ exports.hasBackupSupport = hasBackupSupport;
51
+ exports.resolveCommand = resolveCommand;
52
+ const chalk_1 = __importDefault(require("chalk"));
53
+ const types_1 = require("../../types");
54
+ const rest_client_1 = require("../../dmdata/rest-client");
55
+ const config_1 = require("../../config");
56
+ const formatter_1 = require("../formatter");
57
+ const themeModule = __importStar(require("../theme"));
58
+ const statistics_formatter_1 = require("../statistics-formatter");
59
+ const types_2 = require("./types");
60
+ // ── ヘルパー (モジュール内のみ) ──
61
+ /** ISO 文字列を "MM-DD HH:mm:ss" に整形 (テーブル用短縮形) */
62
+ function formatShortTime(iso) {
63
+ const d = new Date(iso);
64
+ if (isNaN(d.getTime()))
65
+ return iso;
66
+ const pad = (n) => String(n).padStart(2, "0");
67
+ return `${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
68
+ }
69
+ /** 文字列を視覚幅で指定幅に切り詰める */
70
+ function truncate(str, maxWidth) {
71
+ if (maxWidth <= 0)
72
+ return "";
73
+ if ((0, formatter_1.visualWidth)(str) <= maxWidth)
74
+ return str;
75
+ const ellipsis = "\u2026";
76
+ const ellipsisWidth = (0, formatter_1.visualWidth)(ellipsis);
77
+ if (maxWidth <= ellipsisWidth)
78
+ return ellipsis;
79
+ const targetWidth = maxWidth - ellipsisWidth;
80
+ let result = "";
81
+ let width = 0;
82
+ for (const ch of str) {
83
+ const chWidth = (0, formatter_1.visualWidth)(ch);
84
+ if (width + chWidth > targetWidth)
85
+ break;
86
+ result += ch;
87
+ width += chWidth;
88
+ }
89
+ return result + ellipsis;
90
+ }
91
+ /** GdEarthquakeItem から深さ文字列を生成 */
92
+ function formatDepth(item) {
93
+ if (item.hypocenter?.depth?.value != null) {
94
+ const val = item.hypocenter.depth.value;
95
+ const unit = item.hypocenter.depth.unit || "km";
96
+ return `${val}${unit}`;
97
+ }
98
+ return "---";
99
+ }
100
+ /** ミリ秒を人間可読な時間文字列に変換 */
101
+ function formatDuration(ms) {
102
+ const totalSec = Math.ceil(ms / 1000);
103
+ if (totalSec < 60)
104
+ return `${totalSec}秒`;
105
+ const min = Math.floor(totalSec / 60);
106
+ const sec = totalSec % 60;
107
+ if (min < 60)
108
+ return sec > 0 ? `${min}分${sec}秒` : `${min}分`;
109
+ const hour = Math.floor(min / 60);
110
+ const remMin = min % 60;
111
+ return remMin > 0 ? `${hour}時間${remMin}分` : `${hour}時間`;
112
+ }
113
+ /** 震度キーから対応するロール名を返す */
114
+ function getIntensityRole(key) {
115
+ const map = {
116
+ "1": "intensity1", "2": "intensity2", "3": "intensity3", "4": "intensity4",
117
+ "5弱": "intensity5Lower", "5強": "intensity5Upper",
118
+ "6弱": "intensity6Lower", "6強": "intensity6Upper", "7": "intensity7",
119
+ };
120
+ return map[key] ?? null;
121
+ }
122
+ /** 長周期階級キーから対応するロール名を返す */
123
+ function getLgIntRole(key) {
124
+ const map = {
125
+ "0": "lgInt0", "1": "lgInt1", "2": "lgInt2", "3": "lgInt3", "4": "lgInt4",
126
+ };
127
+ return map[key] ?? null;
128
+ }
129
+ /**
130
+ * fg/bg 分離表示用のセルを生成する。
131
+ */
132
+ function renderFgBgItem(label, fg, bg, style) {
133
+ const fgBlock = chalk_1.default.rgb(fg[0], fg[1], fg[2])("██");
134
+ const bgBlock = chalk_1.default.bgRgb(bg[0], bg[1], bg[2])(" ");
135
+ return { cell: `${fgBlock} ${bgBlock} ${style(label)}`, visualLen: (0, formatter_1.visualWidth)(label) + 6 };
136
+ }
137
+ /**
138
+ * 色付きアイテムをターミナル幅に応じたマルチカラムで出力する。
139
+ */
140
+ function printColorGrid(termWidth, items, renderFn) {
141
+ const rendered = items.map(renderFn);
142
+ const maxVisual = Math.max(...rendered.map((r) => r.visualLen));
143
+ const colWidth = maxVisual + 3;
144
+ const indent = 2;
145
+ const cols = Math.max(1, Math.floor((termWidth - indent) / colWidth));
146
+ let line = "";
147
+ let col = 0;
148
+ for (const r of rendered) {
149
+ const pad = colWidth - r.visualLen;
150
+ line += r.cell + " ".repeat(Math.max(0, pad));
151
+ col++;
152
+ if (col >= cols) {
153
+ console.log(`${" ".repeat(indent)}${line}`);
154
+ line = "";
155
+ col = 0;
156
+ }
157
+ }
158
+ if (line.length > 0) {
159
+ console.log(`${" ".repeat(indent)}${line}`);
160
+ }
161
+ }
162
+ /** コマンドのエイリアス (逆引き用) */
163
+ exports.COMMAND_ALIASES = {
164
+ hist: "history",
165
+ cols: "colors",
166
+ det: "detail",
167
+ stat: "status",
168
+ conf: "config",
169
+ cont: "contract",
170
+ sock: "socket",
171
+ noti: "notify",
172
+ ewlg: "eewlog",
173
+ tw: "tablewidth",
174
+ itxt: "infotext",
175
+ tint: "tipinterval",
176
+ snd: "sound",
177
+ thm: "theme",
178
+ bkup: "backup",
179
+ lim: "limit",
180
+ cls: "clear",
181
+ };
182
+ /** 通知カテゴリ名のエイリアス (短縮形 → 正式名) */
183
+ exports.CATEGORY_ALIASES = {
184
+ eq: "earthquake",
185
+ tsu: "tsunami",
186
+ st: "seismicText",
187
+ nt: "nankaiTrough",
188
+ lgob: "lgObservation",
189
+ };
190
+ /** 設定変更可能なコマンドの現在値と設定可能な値を返す */
191
+ function getCurrentSettingValues(ctx) {
192
+ const { config, notifier, eewLogger, statusLine } = ctx;
193
+ const notifySettings = notifier.getSettings();
194
+ const onCount = Object.values(notifySettings).filter(Boolean).length;
195
+ const totalCount = Object.keys(notifySettings).length;
196
+ const muteInfo = notifier.isMuted() ? `, ミュート中` : "";
197
+ return {
198
+ night: {
199
+ current: themeModule.isNightMode() ? "ON" : "OFF",
200
+ options: "on / off",
201
+ },
202
+ sound: {
203
+ current: notifier.getSoundEnabled() ? "ON" : "OFF",
204
+ options: "on / off",
205
+ },
206
+ tablewidth: {
207
+ current: config.tableWidth == null
208
+ ? `auto (${process.stdout.columns ?? 60})`
209
+ : `${config.tableWidth} (固定)`,
210
+ options: "40〜200 / auto",
211
+ },
212
+ infotext: {
213
+ current: config.infoFullText ? "full" : "short",
214
+ options: "full / short",
215
+ },
216
+ tipinterval: {
217
+ current: config.waitTipIntervalMin === 0
218
+ ? "無効"
219
+ : `${config.waitTipIntervalMin}分`,
220
+ options: "0〜1440 (0で無効)",
221
+ },
222
+ mode: {
223
+ current: (0, formatter_1.getDisplayMode)(),
224
+ options: "normal / compact",
225
+ },
226
+ clock: {
227
+ current: statusLine.getClockMode() === "clock" ? "現在時刻" : "経過時間",
228
+ options: "elapsed / now",
229
+ },
230
+ notify: {
231
+ current: `${onCount}/${totalCount} ON${muteInfo}`,
232
+ options: "eew, earthquake, tsunami, seismicText, nankaiTrough, lgObservation",
233
+ },
234
+ mute: {
235
+ current: notifier.isMuted()
236
+ ? `残り ${formatDuration(notifier.muteRemaining())}`
237
+ : "OFF",
238
+ options: "<duration> (例: 30m, 1h, 90s) / off",
239
+ },
240
+ eewlog: {
241
+ current: eewLogger.isEnabled()
242
+ ? (() => {
243
+ const fields = eewLogger.getFields();
244
+ const onCount = Object.values(fields).filter(Boolean).length;
245
+ return `ON (${onCount}/${Object.keys(fields).length}項目)`;
246
+ })()
247
+ : "OFF",
248
+ options: "on / off / fields",
249
+ },
250
+ limit: {
251
+ current: (() => {
252
+ const t = config.truncation;
253
+ const d = types_1.DEFAULT_CONFIG.truncation;
254
+ const changed = Object.keys(d)
255
+ .filter((k) => t[k] !== d[k]).length;
256
+ return changed > 0 ? `${changed}項目変更済` : "デフォルト";
257
+ })(),
258
+ options: "limit で詳細表示",
259
+ },
260
+ };
261
+ }
262
+ // ── コマンドハンドラ ──
263
+ function handleDetail(ctx, args) {
264
+ const sub = args.trim().toLowerCase();
265
+ if (sub === "" || sub === "tsunami") {
266
+ const provider = ctx.detailProviders.find((p) => p.category === "tsunami");
267
+ if (provider == null || !provider.hasDetail()) {
268
+ console.log(chalk_1.default.gray(" 現在、継続中の津波情報はありません。"));
269
+ }
270
+ else {
271
+ provider.showDetail();
272
+ }
273
+ return;
274
+ }
275
+ console.log(chalk_1.default.yellow(` 不明なサブコマンド: ${sub}`) + chalk_1.default.gray(" (利用可能: tsunami)"));
276
+ }
277
+ function handleHelp(ctx, args) {
278
+ const trimmed = args.trim();
279
+ if (trimmed.length > 0) {
280
+ const parts = trimmed.split(/\s+/);
281
+ const entry = resolveCommand(ctx, parts[0]);
282
+ if (entry == null) {
283
+ console.log(chalk_1.default.yellow(` 不明なコマンド: ${parts[0]}`));
284
+ return;
285
+ }
286
+ if (parts.length > 1 && entry.subcommands) {
287
+ const sub = entry.subcommands[parts[1]];
288
+ if (sub == null) {
289
+ console.log(chalk_1.default.yellow(` 不明なサブコマンド: ${parts[0]} ${parts[1]}`));
290
+ return;
291
+ }
292
+ console.log();
293
+ console.log(chalk_1.default.cyan.bold(` ${parts[0]} ${parts[1]}`) + chalk_1.default.gray(` — ${sub.description}`));
294
+ if (sub.detail) {
295
+ console.log();
296
+ for (const line of sub.detail.split("\n")) {
297
+ console.log(chalk_1.default.white(` ${line}`));
298
+ }
299
+ }
300
+ console.log();
301
+ return;
302
+ }
303
+ console.log();
304
+ console.log(chalk_1.default.cyan.bold(` ${parts[0]}`) + chalk_1.default.gray(` — ${entry.description}`));
305
+ if (entry.detail) {
306
+ console.log();
307
+ for (const line of entry.detail.split("\n")) {
308
+ console.log(chalk_1.default.white(` ${line}`));
309
+ }
310
+ }
311
+ if (entry.subcommands) {
312
+ console.log();
313
+ const subNames = Object.keys(entry.subcommands).sort();
314
+ for (let i = 0; i < subNames.length; i++) {
315
+ const subName = subNames[i];
316
+ const sub = entry.subcommands[subName];
317
+ const prefix = i < subNames.length - 1 ? "├─" : "└─";
318
+ console.log(chalk_1.default.gray(` ${prefix} `) + chalk_1.default.white(subName.padEnd(10)) + chalk_1.default.gray(sub.description));
319
+ }
320
+ }
321
+ console.log();
322
+ return;
323
+ }
324
+ // help — カテゴリ別一覧
325
+ console.log();
326
+ console.log(chalk_1.default.cyan.bold(" 利用可能なコマンド:"));
327
+ const currentValues = getCurrentSettingValues(ctx);
328
+ const displayed = new Set();
329
+ const categoryOrder = ["info", "status", "settings", "operation"];
330
+ for (const category of categoryOrder) {
331
+ console.log();
332
+ console.log(chalk_1.default.cyan(` [${types_2.CATEGORY_LABELS[category]}]`));
333
+ const commandNames = Object.keys(ctx.commands)
334
+ .filter((name) => name !== "exit" && name !== "?" && ctx.commands[name].category === category)
335
+ .sort();
336
+ for (const name of commandNames) {
337
+ const entry = ctx.commands[name];
338
+ if (displayed.has(entry.description))
339
+ continue;
340
+ displayed.add(entry.description);
341
+ const setting = currentValues[name];
342
+ const valueSuffix = setting != null
343
+ ? chalk_1.default.gray(" [") + chalk_1.default.yellow(setting.current) + chalk_1.default.gray("]") +
344
+ (setting.options ? chalk_1.default.gray(` (${setting.options})`) : "")
345
+ : "";
346
+ const alias = Object.entries(exports.COMMAND_ALIASES)
347
+ .find(([, v]) => v === name)?.[0];
348
+ const aliasSuffix = alias != null ? chalk_1.default.gray(` (${alias})`) : "";
349
+ console.log(chalk_1.default.white(` ${name.padEnd(14)}`) + chalk_1.default.gray(entry.description) + aliasSuffix + valueSuffix);
350
+ if (entry.subcommands) {
351
+ const subNames = Object.keys(entry.subcommands).sort();
352
+ for (let i = 0; i < subNames.length; i++) {
353
+ const subName = subNames[i];
354
+ const sub = entry.subcommands[subName];
355
+ const prefix = i < subNames.length - 1 ? "├─" : "└─";
356
+ console.log(chalk_1.default.gray(` ${prefix} `) + chalk_1.default.white(subName.padEnd(10)) + chalk_1.default.gray(sub.description));
357
+ }
358
+ }
359
+ }
360
+ }
361
+ console.log();
362
+ console.log(chalk_1.default.gray(" エイリアス: ") + chalk_1.default.white("?") + chalk_1.default.gray(" → help, ") + chalk_1.default.white("exit") + chalk_1.default.gray(" → quit (コマンド名の大文字小文字は区別しません)"));
363
+ console.log();
364
+ }
365
+ function handleStats(ctx) {
366
+ (0, statistics_formatter_1.displayStatistics)(ctx.stats.getSnapshot());
367
+ }
368
+ async function handleHistory(ctx, args) {
369
+ const MAX_HISTORY = 100;
370
+ const raw = args.length > 0 ? parseInt(args, 10) : 10;
371
+ if (isNaN(raw) || raw <= 0) {
372
+ console.log(chalk_1.default.yellow(" 件数は正の整数で指定してください"));
373
+ return;
374
+ }
375
+ const limit = Math.min(raw, MAX_HISTORY);
376
+ console.log(chalk_1.default.gray(" 地震履歴を取得中..."));
377
+ const res = await (0, rest_client_1.listEarthquakes)(ctx.config.apiKey, limit);
378
+ if (res.items.length === 0) {
379
+ console.log(chalk_1.default.gray(" 該当する地震情報はありません"));
380
+ return;
381
+ }
382
+ const COL = { time: 18, hypo: 16, mag: 6, depth: 8, int: 8 };
383
+ const hLine = (l, m, r, h) => chalk_1.default.gray(` ${l}${h.repeat(COL.time + 2)}${m}${h.repeat(COL.hypo + 2)}${m}${h.repeat(COL.mag + 2)}${m}${h.repeat(COL.depth + 2)}${m}${h.repeat(COL.int + 2)}${r}`);
384
+ console.log();
385
+ console.log(hLine("┌", "┬", "┐", "─"));
386
+ console.log(chalk_1.default.gray(" │ ") +
387
+ chalk_1.default.cyan((0, formatter_1.visualPadEnd)("発生時刻", COL.time)) + chalk_1.default.gray(" │ ") +
388
+ chalk_1.default.cyan((0, formatter_1.visualPadEnd)("震源地", COL.hypo)) + chalk_1.default.gray(" │ ") +
389
+ chalk_1.default.cyan((0, formatter_1.visualPadEnd)("規模", COL.mag)) + chalk_1.default.gray(" │ ") +
390
+ chalk_1.default.cyan((0, formatter_1.visualPadEnd)("深さ", COL.depth)) + chalk_1.default.gray(" │ ") +
391
+ chalk_1.default.cyan((0, formatter_1.visualPadEnd)("最大震度", COL.int)) + chalk_1.default.gray(" │"));
392
+ console.log(hLine("├", "┼", "┤", "─"));
393
+ const items = [...res.items].reverse();
394
+ for (const item of items) {
395
+ const time = formatShortTime(item.originTime || item.arrivalTime);
396
+ const hypo = truncate(item.hypocenter?.name || "不明", COL.hypo);
397
+ const mag = item.magnitude?.value != null ? `M${item.magnitude.value}` : "M---";
398
+ const depth = formatDepth(item);
399
+ const maxInt = item.maxInt != null ? item.maxInt : "---";
400
+ const intColor = item.maxInt != null ? (0, formatter_1.intensityColor)(item.maxInt) : chalk_1.default.gray;
401
+ console.log(chalk_1.default.gray(" │ ") +
402
+ chalk_1.default.white((0, formatter_1.visualPadEnd)(time, COL.time)) + chalk_1.default.gray(" │ ") +
403
+ chalk_1.default.white((0, formatter_1.visualPadEnd)(hypo, COL.hypo)) + chalk_1.default.gray(" │ ") +
404
+ chalk_1.default.yellow((0, formatter_1.visualPadEnd)(mag, COL.mag)) + chalk_1.default.gray(" │ ") +
405
+ chalk_1.default.white((0, formatter_1.visualPadEnd)(depth, COL.depth)) + chalk_1.default.gray(" │ ") +
406
+ intColor((0, formatter_1.visualPadEnd)(maxInt, COL.int)) + chalk_1.default.gray(" │"));
407
+ }
408
+ console.log(hLine("└", "┴", "┘", "─"));
409
+ console.log();
410
+ }
411
+ function handleStatus(ctx) {
412
+ const status = ctx.wsManager.getStatus();
413
+ console.log();
414
+ console.log(chalk_1.default.cyan.bold(" WebSocket 接続状態:"));
415
+ console.log(chalk_1.default.white(" 状態: ") +
416
+ (status.connected
417
+ ? chalk_1.default.green.bold("接続中")
418
+ : chalk_1.default.red.bold("切断")));
419
+ if (status.socketId != null) {
420
+ console.log(chalk_1.default.white(" SocketID: ") + chalk_1.default.white(String(status.socketId)));
421
+ }
422
+ if (status.reconnectAttempt > 0) {
423
+ console.log(chalk_1.default.white(" 再接続試行: ") +
424
+ chalk_1.default.yellow(`#${status.reconnectAttempt}`));
425
+ }
426
+ if (hasBackupSupport(ctx.wsManager)) {
427
+ if (ctx.wsManager.isBackupRunning()) {
428
+ const backupStatus = ctx.wsManager.getBackupStatus();
429
+ console.log(chalk_1.default.white(" 副回線: ") +
430
+ (backupStatus?.connected
431
+ ? chalk_1.default.green.bold("接続中")
432
+ : chalk_1.default.yellow("再接続中")));
433
+ if (backupStatus?.socketId != null) {
434
+ console.log(chalk_1.default.white(" 副回線 SocketID: ") + chalk_1.default.white(String(backupStatus.socketId)));
435
+ }
436
+ }
437
+ else {
438
+ console.log(chalk_1.default.white(" 副回線: ") + chalk_1.default.gray("未起動"));
439
+ }
440
+ }
441
+ console.log();
442
+ }
443
+ function handleConfig() {
444
+ (0, config_1.printConfig)();
445
+ }
446
+ async function handleContract(ctx) {
447
+ console.log(chalk_1.default.gray(" 契約情報を取得中..."));
448
+ const classifications = await (0, rest_client_1.listContracts)(ctx.config.apiKey);
449
+ console.log();
450
+ console.log(chalk_1.default.cyan.bold(" 契約済み区分:"));
451
+ if (classifications.length === 0) {
452
+ console.log(chalk_1.default.gray(" (なし)"));
453
+ }
454
+ else {
455
+ for (const c of classifications) {
456
+ console.log(chalk_1.default.white(` - ${c}`));
457
+ }
458
+ }
459
+ console.log();
460
+ }
461
+ async function handleSocket(ctx) {
462
+ console.log(chalk_1.default.gray(" ソケット情報を取得中..."));
463
+ const res = await (0, rest_client_1.listSockets)(ctx.config.apiKey);
464
+ console.log();
465
+ console.log(chalk_1.default.cyan.bold(" 接続中のソケット:"));
466
+ if (res.items.length === 0) {
467
+ console.log(chalk_1.default.gray(" (なし)"));
468
+ }
469
+ else {
470
+ for (const s of res.items) {
471
+ console.log(chalk_1.default.white(` id=${s.id}`) +
472
+ chalk_1.default.gray(` status=${s.status}`) +
473
+ chalk_1.default.gray(` app=${s.appName || "---"}`) +
474
+ chalk_1.default.gray(` start=${s.start}`));
475
+ }
476
+ }
477
+ console.log();
478
+ }
479
+ function handleColors() {
480
+ const termWidth = process.stdout.columns || 80;
481
+ const palette = themeModule.getPalette();
482
+ const PALETTE_USAGE = {
483
+ gray: "低優先度・補助テキスト",
484
+ sky: "通常・長周期階級1",
485
+ blue: "震度3",
486
+ blueGreen: "震度4・津波なし",
487
+ yellow: "震度5弱・M3+",
488
+ orange: "警告レベル",
489
+ vermillion: "危険レベル",
490
+ raspberry: "取消・キャンセル",
491
+ darkRed: "最高警戒 (背景用)",
492
+ };
493
+ console.log();
494
+ console.log(chalk_1.default.cyan.bold(" CUD カラーパレット:"));
495
+ if (themeModule.isCustomized()) {
496
+ console.log(chalk_1.default.gray(" (カスタムテーマ適用中)"));
497
+ }
498
+ console.log();
499
+ for (const name of themeModule.getPaletteNames()) {
500
+ const rgb = palette[name];
501
+ const swatch = chalk_1.default.rgb(rgb[0], rgb[1], rgb[2])("██");
502
+ const rgbStr = `(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
503
+ console.log(` ${swatch} ` +
504
+ chalk_1.default.white(name.padEnd(12)) +
505
+ chalk_1.default.gray(rgbStr.padEnd(16)) +
506
+ chalk_1.default.gray(PALETTE_USAGE[name] ?? ""));
507
+ }
508
+ console.log();
509
+ console.log(chalk_1.default.cyan.bold(" 震度カラー:"));
510
+ console.log();
511
+ const intensityKeys = ["1", "2", "3", "4", "5弱", "5強", "6弱", "6強", "7"];
512
+ const intensities = intensityKeys.map((key) => {
513
+ const label = `震度${key}`;
514
+ const style = (0, formatter_1.intensityColor)(key);
515
+ const role = getIntensityRole(key);
516
+ const resolved = role ? themeModule.getRole(role) : null;
517
+ return { label, key, style, resolved };
518
+ });
519
+ printColorGrid(termWidth, intensities, (item) => {
520
+ if (item.resolved?.bg && item.resolved?.fg) {
521
+ return renderFgBgItem(item.label, item.resolved.fg, item.resolved.bg, item.style);
522
+ }
523
+ return { cell: `${item.style("██")} ${item.style(item.label)}`, visualLen: (0, formatter_1.visualWidth)(item.label) + 3 };
524
+ });
525
+ console.log();
526
+ console.log(chalk_1.default.cyan.bold(" 長周期地震動階級カラー:"));
527
+ console.log();
528
+ const lgIntKeys = ["0", "1", "2", "3", "4"];
529
+ const lgInts = lgIntKeys.map((key) => {
530
+ const label = `階級${key}`;
531
+ const style = (0, formatter_1.lgIntensityColor)(key);
532
+ const role = getLgIntRole(key);
533
+ const resolved = role ? themeModule.getRole(role) : null;
534
+ return { label, key, style, resolved };
535
+ });
536
+ printColorGrid(termWidth, lgInts, (item) => {
537
+ if (item.resolved?.bg && item.resolved?.fg) {
538
+ return renderFgBgItem(item.label, item.resolved.fg, item.resolved.bg, item.style);
539
+ }
540
+ return { cell: `${item.style("██")} ${item.style(item.label)}`, visualLen: (0, formatter_1.visualWidth)(item.label) + 3 };
541
+ });
542
+ console.log();
543
+ console.log(chalk_1.default.cyan.bold(" フレームレベル:"));
544
+ console.log();
545
+ const frameRoles = [
546
+ { name: "critical", role: "frameCritical", label: "[緊急] 二重線" },
547
+ { name: "warning", role: "frameWarning", label: "[警告] 二重線" },
548
+ { name: "normal", role: "frameNormal", label: "[情報] 通常" },
549
+ { name: "info", role: "frameInfo", label: "[通知] 通常" },
550
+ { name: "cancel", role: "frameCancel", label: "[取消] 通常" },
551
+ ];
552
+ printColorGrid(termWidth, frameRoles, (lv) => {
553
+ const style = themeModule.getRoleChalk(lv.role);
554
+ const text = `${lv.name} ${lv.label}`;
555
+ return { cell: `${style("██")} ${style(text)}`, visualLen: (0, formatter_1.visualWidth)(text) + 3 };
556
+ });
557
+ console.log();
558
+ }
559
+ /** 構造的型ガード: backup 機能を持つ ConnectionManager か */
560
+ function hasBackupSupport(m) {
561
+ return ("startBackup" in m &&
562
+ "stopBackup" in m &&
563
+ "isBackupRunning" in m &&
564
+ "getBackupStatus" in m);
565
+ }
566
+ /** コマンド名を解決する (case-insensitive + エイリアス) */
567
+ function resolveCommand(ctx, input) {
568
+ const lower = input.toLowerCase();
569
+ for (const [name, entry] of Object.entries(ctx.commands)) {
570
+ if (name.toLowerCase() === lower)
571
+ return entry;
572
+ }
573
+ const canonical = exports.COMMAND_ALIASES[lower];
574
+ if (canonical != null)
575
+ return ctx.commands[canonical];
576
+ return undefined;
577
+ }