sesame-kit 0.4.1 → 0.4.2
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.
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sesame-kit",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "SESAME cloud CLI & library (lock control + Hub3 IR + device management). Node.js port of biz3 React app with the Cognito consumer client swapped in for long-lived sessions.",
|
|
5
5
|
"author": "Ikuma Fukumoto",
|
|
6
6
|
"license": "MIT",
|
|
@@ -85,7 +85,8 @@
|
|
|
85
85
|
"build:rpc-schema": "node scripts/gen-rpc-schema.mjs",
|
|
86
86
|
"build:grpc-proto": "node scripts/gen-grpc-proto.mjs",
|
|
87
87
|
"build": "tsc -p tsconfig.json && node scripts/gen-rpc-schema.mjs && node scripts/gen-grpc-proto.mjs",
|
|
88
|
-
"prepack": "tsc -p tsconfig.json && node scripts/gen-rpc-schema.mjs && node scripts/gen-grpc-proto.mjs"
|
|
88
|
+
"prepack": "tsc -p tsconfig.json && node scripts/gen-rpc-schema.mjs && node scripts/gen-grpc-proto.mjs",
|
|
89
|
+
"release": "bash scripts/release.sh"
|
|
89
90
|
},
|
|
90
91
|
"dependencies": {
|
|
91
92
|
"@aws-sdk/client-cognito-identity-provider": "^3.1057.0",
|
package/src/cli.js
CHANGED
|
@@ -1755,9 +1755,11 @@ devices だけで完結します (手入力は呼び名のみ):
|
|
|
1755
1755
|
// BLE 権限/電源エラーは macOS なら該当設定ペインを自動で開いて誘導する。
|
|
1756
1756
|
if (maybeHandleBleError(err)) { finishCli(); return; }
|
|
1757
1757
|
const code = (typeof err.exitCode === "number" && err.exitCode !== 0) ? err.exitCode : 1;
|
|
1758
|
-
// commander の usage
|
|
1759
|
-
if (typeof err.code === "string" && err.code.startsWith("commander.")
|
|
1760
|
-
process.exitCode = code; finishCli(); return;
|
|
1758
|
+
// commander の usage エラー。非 JSON 時は commander が stderr に整形済み (usage 付き) なので二重出力を避ける。
|
|
1759
|
+
if (typeof err.code === "string" && err.code.startsWith("commander.")) {
|
|
1760
|
+
if (!CLI_JSON) { process.exitCode = code; finishCli(); return; }
|
|
1761
|
+
// --json: commander のメッセージ先頭 "error: " を剥がして封筒に載せる (error が二重にならないように)。
|
|
1762
|
+
die((err.message || "usage error").replace(/^error:\s*/i, ""), code); return;
|
|
1761
1763
|
}
|
|
1762
1764
|
die(withStaleHint(err.message || String(err)), code);
|
|
1763
1765
|
}
|
package/src/session-ui.js
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
// 状態が変わった瞬間に描き直す Ink (React for CLI) で実装する。BLE の mechStatus publish や
|
|
6
6
|
// バックグラウンド接続の完了を `bus` の "update" イベントで受け、React state を更新して再描画する。
|
|
7
7
|
//
|
|
8
|
+
// 操作: ↑↓ 移動 / → か Enter で決定 / ← か Esc で戻る / q で終了。
|
|
9
|
+
// アクション実行後はホームに戻らず、その操作メニューに留まる (続けて操作できる)。
|
|
10
|
+
//
|
|
8
11
|
// JSX は使わない (本リポは src を素の ESM で実行しビルド工程が無いため)。React.createElement = h。
|
|
9
12
|
|
|
10
13
|
import React from "react";
|
|
@@ -43,6 +46,7 @@ export function SessionApp({ devices, hasCloud, bus, exec, actionsFor, fmtState,
|
|
|
43
46
|
const [numVal, setNumVal] = React.useState(""); // autolock 秒数 / LED duty 入力
|
|
44
47
|
const [selRemote, setSelRemote] = React.useState(null); // IR: 選択中リモコン
|
|
45
48
|
const [irKeys, setIrKeys] = React.useState(null); // IR: 取得したキー一覧 (null=未取得)
|
|
49
|
+
const [hi, setHi] = React.useState(null); // → 決定用: ハイライト中の項目
|
|
46
50
|
|
|
47
51
|
// BLE onStatus / 背景接続完了 → 再描画。
|
|
48
52
|
React.useEffect(() => {
|
|
@@ -51,18 +55,82 @@ export function SessionApp({ devices, hasCloud, bus, exec, actionsFor, fmtState,
|
|
|
51
55
|
return () => bus.off("update", on);
|
|
52
56
|
}, [bus]);
|
|
53
57
|
|
|
58
|
+
// hi (→ 決定用ハイライト) は各 SelectInput の onHighlight が先頭項目で更新する。
|
|
59
|
+
// SelectInput を描画しない可能性のある mode (空の IR リスト) に入る時だけ明示的にクリアし、
|
|
60
|
+
// 前メニューの項目が残って → が誤爆しないようにする (下の selectAction / selectIrRemote 参照)。
|
|
61
|
+
|
|
54
62
|
const backToActions = () => { setMode("actions"); };
|
|
55
63
|
|
|
56
|
-
//
|
|
64
|
+
// ---- 各メニューの選択ハンドラ (SelectInput.onSelect と → 決定の両方から呼ぶ) ----
|
|
65
|
+
const selectDevice = (it) => {
|
|
66
|
+
if (!it) return;
|
|
67
|
+
if (it.value === "__quit") { exit(); return; }
|
|
68
|
+
setSelName(it.value); setMsg(""); setMode("actions");
|
|
69
|
+
};
|
|
70
|
+
const selectAction = (it) => {
|
|
71
|
+
if (!it) return;
|
|
72
|
+
if (it.value === "__back") { if (single) exit(); else { setMode("devices"); setMsg(""); } return; }
|
|
73
|
+
if (it.value === "autolock") { setNumVal(""); setMode("autolock"); return; }
|
|
74
|
+
if (it.value === "led") { setNumVal(""); setMode("led"); return; }
|
|
75
|
+
if (it.value === "ir") { setHi(null); setSelRemote(null); setIrKeys(null); setMode("ir-remote"); return; }
|
|
76
|
+
runExec(it.value, devices.get(selName));
|
|
77
|
+
};
|
|
78
|
+
const selectIrRemote = (it) => {
|
|
79
|
+
if (!it) return;
|
|
80
|
+
if (it.value === "__back") { backToActions(); return; }
|
|
81
|
+
setHi(null); setSelRemote(it.value); setIrKeys(null); setMode("ir-key");
|
|
82
|
+
Promise.resolve(listKeysFor ? listKeysFor(it.value) : []).then(setIrKeys).catch(() => setIrKeys([]));
|
|
83
|
+
};
|
|
84
|
+
const selectIrKey = (it) => {
|
|
85
|
+
if (!it) return;
|
|
86
|
+
if (it.value === "__back") { setMode("ir-remote"); return; }
|
|
87
|
+
runExec("ir", devices.get(selName), { remote: selRemote, key: it.value });
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// ← / Esc で 1 つ戻る。Esc は最上位 (devices / single の actions) では終了する。
|
|
91
|
+
// ← は最上位では何もしない (誤操作で終了しないように)。
|
|
92
|
+
const goBack = (allowExitAtTop) => {
|
|
93
|
+
if (mode === "actions") {
|
|
94
|
+
if (single) { if (allowExitAtTop) exit(); }
|
|
95
|
+
else { setMode("devices"); setMsg(""); }
|
|
96
|
+
} else if (mode === "ir-key") setMode("ir-remote");
|
|
97
|
+
else if (mode === "devices") { if (allowExitAtTop) exit(); }
|
|
98
|
+
else backToActions(); // autolock / led / ir-remote
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// 現在 mode のメニュー項目 (render と → 決定で共有。順序が両者で一致する)。
|
|
102
|
+
const menuItems = () => {
|
|
103
|
+
if (mode === "devices") return [...names.map((n) => ({ label: n, value: n })), { label: "終了", value: "__quit" }];
|
|
104
|
+
if (mode === "actions") return [...actionsFor(devices.get(selName)), { label: single ? "終了" : "← 戻る", value: "__back" }];
|
|
105
|
+
if (mode === "ir-remote") {
|
|
106
|
+
const r = hub3RemotesFor ? hub3RemotesFor(devices.get(selName)) : [];
|
|
107
|
+
return r.length ? [...r, { label: "← 戻る", value: "__back" }] : [];
|
|
108
|
+
}
|
|
109
|
+
if (mode === "ir-key") return (irKeys && irKeys.length) ? [...irKeys, { label: "← 戻る", value: "__back" }] : [];
|
|
110
|
+
return [];
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// → で決定。ink-select-input の onHighlight は初期項目では発火しないため、移動前は hi=null。
|
|
114
|
+
// その場合は先頭 (= 既定でハイライトされている項目) にフォールバックする。
|
|
115
|
+
const goForward = () => {
|
|
116
|
+
const it = hi || menuItems()[0];
|
|
117
|
+
if (!it) return;
|
|
118
|
+
if (mode === "devices") selectDevice(it);
|
|
119
|
+
else if (mode === "actions") selectAction(it);
|
|
120
|
+
else if (mode === "ir-remote") selectIrRemote(it);
|
|
121
|
+
else if (mode === "ir-key") selectIrKey(it);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const isList = mode === "devices" || mode === "actions" || mode === "ir-remote" || mode === "ir-key";
|
|
125
|
+
|
|
57
126
|
useInput((input, key) => {
|
|
58
127
|
if (mode === "busy") return;
|
|
59
|
-
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
128
|
+
// q は**メニュー系のみ**で終了。autolock/LED の数値入力中は文字として TextInput へ渡す。
|
|
129
|
+
if (input === "q" && isList) { exit(); return; }
|
|
130
|
+
if (key.escape) { goBack(true); return; } // Esc: 戻る (最上位では終了)
|
|
131
|
+
// ← / → は数値入力 (autolock/led) ではテキストカーソル移動に使うので、リスト系のみで奪う。
|
|
132
|
+
if (isList && key.leftArrow) { goBack(false); return; } // ←: 戻る (最上位では何もしない)
|
|
133
|
+
if (isList && key.rightArrow) { goForward(); return; } // →: 決定
|
|
66
134
|
});
|
|
67
135
|
|
|
68
136
|
const runExec = (op, d, extra) => {
|
|
@@ -70,10 +138,10 @@ export function SessionApp({ devices, hasCloud, bus, exec, actionsFor, fmtState,
|
|
|
70
138
|
exec(op, d, extra)
|
|
71
139
|
.then((m) => setMsg(m))
|
|
72
140
|
.catch((e) => setMsg(`error: ${e?.message || e}`))
|
|
73
|
-
.finally(() => setMode(
|
|
141
|
+
.finally(() => setMode("actions")); // ホームに戻らず操作メニューに留まる (続けて操作できる)
|
|
74
142
|
};
|
|
75
143
|
|
|
76
|
-
// ヘッダ: 全デバイスの現在状態 (ライブ)
|
|
144
|
+
// ヘッダ: 全デバイスの現在状態 (ライブ) + 操作ヒント。
|
|
77
145
|
const header = h(
|
|
78
146
|
Box,
|
|
79
147
|
{ flexDirection: "column" },
|
|
@@ -85,6 +153,7 @@ export function SessionApp({ devices, hasCloud, bus, exec, actionsFor, fmtState,
|
|
|
85
153
|
return h(Text, { key: n, color: d.ble ? "green" : undefined },
|
|
86
154
|
` ${n} [${label}·${tag}]: ${fmtState(d)}`);
|
|
87
155
|
}),
|
|
156
|
+
h(Text, { dimColor: true }, " ↑↓ 移動 → 決定 ← 戻る q 終了"),
|
|
88
157
|
msg ? h(Text, { color: "yellow" }, msg) : null,
|
|
89
158
|
);
|
|
90
159
|
const box = (...kids) => h(Box, { flexDirection: "column" }, header, ...kids);
|
|
@@ -116,66 +185,30 @@ export function SessionApp({ devices, hasCloud, bus, exec, actionsFor, fmtState,
|
|
|
116
185
|
const d = devices.get(selName);
|
|
117
186
|
const remotes = (hub3RemotesFor ? hub3RemotesFor(d) : []);
|
|
118
187
|
if (remotes.length === 0) {
|
|
119
|
-
return box(h(Text, null, `${selName}: 登録リモコンがありません ( sesame remote add で登録 )
|
|
188
|
+
return box(h(Text, null, `${selName}: 登録リモコンがありません ( sesame remote add で登録 )。← / Esc で戻る`));
|
|
120
189
|
}
|
|
121
|
-
const items = [...remotes, { label: "← 戻る", value: "__back" }];
|
|
122
190
|
return box(h(Text, null, `${selName} の IR: リモコン選択`),
|
|
123
|
-
h(SelectInput, {
|
|
124
|
-
items,
|
|
125
|
-
onSelect: (it) => {
|
|
126
|
-
if (it.value === "__back") { backToActions(); return; }
|
|
127
|
-
setSelRemote(it.value); setIrKeys(null); setMode("ir-key");
|
|
128
|
-
Promise.resolve(listKeysFor ? listKeysFor(it.value) : []).then(setIrKeys).catch(() => setIrKeys([]));
|
|
129
|
-
},
|
|
130
|
-
}),
|
|
191
|
+
h(SelectInput, { items: menuItems(), onHighlight: setHi, onSelect: selectIrRemote }),
|
|
131
192
|
);
|
|
132
193
|
}
|
|
133
194
|
|
|
134
195
|
// IR: キー選択 (非同期取得中はローディング表示)。
|
|
135
196
|
if (mode === "ir-key") {
|
|
136
|
-
const d = devices.get(selName);
|
|
137
197
|
if (irKeys === null) return box(h(Text, null, `${selRemote}: キー取得中...`));
|
|
138
|
-
if (irKeys.length === 0) return box(h(Text, null, `${selRemote}: キーがありません ( sesame remote sync-keys )
|
|
139
|
-
const items = [...irKeys, { label: "← 戻る", value: "__back" }];
|
|
198
|
+
if (irKeys.length === 0) return box(h(Text, null, `${selRemote}: キーがありません ( sesame remote sync-keys )。← / Esc で戻る`));
|
|
140
199
|
return box(h(Text, null, `${selRemote} のキー選択 (送信)`),
|
|
141
|
-
h(SelectInput, {
|
|
142
|
-
items,
|
|
143
|
-
onSelect: (it) => {
|
|
144
|
-
if (it.value === "__back") { setMode("ir-remote"); return; }
|
|
145
|
-
runExec("ir", d, { remote: selRemote, key: it.value });
|
|
146
|
-
},
|
|
147
|
-
}),
|
|
200
|
+
h(SelectInput, { items: menuItems(), onHighlight: setHi, onSelect: selectIrKey }),
|
|
148
201
|
);
|
|
149
202
|
}
|
|
150
203
|
|
|
151
204
|
if (mode === "actions") {
|
|
152
|
-
const d = devices.get(selName);
|
|
153
|
-
const items = [...actionsFor(d)];
|
|
154
|
-
items.push({ label: single ? "終了" : "← 戻る", value: "__back" });
|
|
155
205
|
return box(h(Text, null, `${selName} の操作:`),
|
|
156
|
-
h(SelectInput, {
|
|
157
|
-
items,
|
|
158
|
-
onSelect: (it) => {
|
|
159
|
-
if (it.value === "__back") { if (single) exit(); else { setMode("devices"); setMsg(""); } return; }
|
|
160
|
-
if (it.value === "autolock") { setNumVal(""); setMode("autolock"); return; }
|
|
161
|
-
if (it.value === "led") { setNumVal(""); setMode("led"); return; }
|
|
162
|
-
if (it.value === "ir") { setSelRemote(null); setIrKeys(null); setMode("ir-remote"); return; }
|
|
163
|
-
runExec(it.value, d);
|
|
164
|
-
},
|
|
165
|
-
}),
|
|
206
|
+
h(SelectInput, { items: menuItems(), onHighlight: setHi, onSelect: selectAction }),
|
|
166
207
|
);
|
|
167
208
|
}
|
|
168
209
|
|
|
169
210
|
// mode === "devices"
|
|
170
|
-
const items = names.map((n) => ({ label: n, value: n }));
|
|
171
|
-
items.push({ label: "終了", value: "__quit" });
|
|
172
211
|
return box(h(Text, null, "操作するデバイス:"),
|
|
173
|
-
h(SelectInput, {
|
|
174
|
-
items,
|
|
175
|
-
onSelect: (it) => {
|
|
176
|
-
if (it.value === "__quit") { exit(); return; }
|
|
177
|
-
setSelName(it.value); setMsg(""); setMode("actions");
|
|
178
|
-
},
|
|
179
|
-
}),
|
|
212
|
+
h(SelectInput, { items: menuItems(), onHighlight: setHi, onSelect: selectDevice }),
|
|
180
213
|
);
|
|
181
214
|
}
|
package/types/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.js"],"names":[],"mappings":"AA+9CA,
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.js"],"names":[],"mappings":"AA+9CA,oDAuQC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-ui.d.ts","sourceRoot":"","sources":["../src/session-ui.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"session-ui.d.ts","sourceRoot":"","sources":["../src/session-ui.js"],"names":[],"mappings":"AAmBA;;;;;;;;;;GAUG;AACH,oCATW;IACN,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE;QAAC,KAAK,EAAC,MAAM,CAAC;QAAC,GAAG,EAAC,MAAM,GAAC,IAAI,CAAA;KAAC,CAAC,CAAC;IACtD,QAAQ,EAAE,OAAO,CAAC;IAClB,GAAG,EAAE,OAAO,aAAa,EAAE,YAAY,CAAC;IACxC,IAAI,EAAE,CAAC,EAAE,EAAC,MAAM,EAAE,MAAM,EAAC,MAAM,EAAE,OAAO,CAAC,EAAC,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACnE,UAAU,EAAE,CAAC,MAAM,EAAC,MAAM,KAAG,KAAK,CAAC;QAAC,KAAK,EAAC,MAAM,CAAC;QAAC,KAAK,EAAC,MAAM,CAAA;KAAC,CAAC,CAAC;IACjE,QAAQ,EAAE,CAAC,MAAM,EAAC,MAAM,KAAG,MAAM,CAAC;CACnC,iBAKH;AAED;;;;;;;;;QAkLC"}
|