bom-pi 0.0.5 → 0.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bom-pi",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "type": "module",
5
5
  "description": "bom × pi — hệ sinh thái durable cho pi trên nền pi-durable: core daemon + web UI pocket + CLI",
6
6
  "engines": {
@@ -5,7 +5,7 @@
5
5
  // Mọi lệnh conversation nhận --cid N (mặc định: conversation active của daemon).
6
6
  import { connect } from 'node:net';
7
7
  import { join, resolve } from 'node:path';
8
- import { userInfo } from 'node:os';
8
+ import { userInfo, homedir, hostname } from 'node:os';
9
9
  import { readFile, writeFile, mkdir, stat } from 'node:fs/promises';
10
10
  import { execFile } from 'node:child_process';
11
11
  import { promisify } from 'node:util';
@@ -27,10 +27,18 @@ const cid = arg('cid', null); // conversation id cho submit/transcript/reset/wat
27
27
  const cidParams = () => (cid ? { conversationId: Number(cid) } : {});
28
28
  const cmd = process.argv[2] ?? 'status';
29
29
 
30
- // Remote (thiết bị khác — Windows/phone đã pair): BOM_PI_URL=http://host:8791 (+ BOM_PI_TOKEN
31
- // device token bom_… copy sau khi pair qua browser). CLI nói chuyện web /rpc thay unix socket.
32
- const REMOTE_URL = arg('url', process.env.BOM_PI_URL || null);
33
- const REMOTE_TOKEN = arg('token', process.env.BOM_PI_TOKEN || null);
30
+ // Remote (thiết bị khác — Windows/phone): pair MỘT lần bằng 'bom-pi pair <code> --url …'
31
+ // ⇒ CLI tự lưu {url, token} vào ~/.bom-pi.json và dùng mãi (env BOM_PI_URL/BOM_PI_TOKEN
32
+ // vẫn đè lên nếu đặt; --url/--token đè lên tất cả). CLI nói chuyện web /rpc thay unix socket.
33
+ const CONFIG_PATH = join(homedir(), '.bom-pi.json');
34
+ let cliConfig = {};
35
+ try { cliConfig = JSON.parse(await readFile(CONFIG_PATH, 'utf8')); } catch { /* chưa pair bao giờ */ }
36
+ async function saveCliConfig(next) {
37
+ cliConfig = next;
38
+ await writeFile(CONFIG_PATH, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
39
+ }
40
+ const REMOTE_URL = arg('url', process.env.BOM_PI_URL || cliConfig.url || null);
41
+ const REMOTE_TOKEN = arg('token', process.env.BOM_PI_TOKEN || cliConfig.token || null);
34
42
 
35
43
  // -v/--version trước mọi thứ — user kiểm bản nào đang chạy (bài học: máy Windows kẹt bản cũ)
36
44
  const VERSION = (await import('../../../package.json', { with: { type: 'json' } })).default.version;
@@ -47,20 +55,17 @@ function unreachable(e) {
47
55
  return `${head}
48
56
 
49
57
  bom-core chạy trên máy chủ Linux (unix socket + systemd) — CLI chưa chạy được trên Windows.
50
- Hai đường từ máy khác:
51
- · WEB (đầy đủ nhất): mở http://<ip-máy-chạy-daemon>:8791 (hoặc tunnel https://…) rồi pair
52
- bằng code in ra từ lệnh 'bom-pi pair' chạy TRÊN MÁY CHỦ đó.
53
- · CLI REMOTE: pair qua browser trước, copy device token bom_…, rồi đặt:
54
- set BOM_PI_URL=http://<ip>:8791 & set BOM_PI_TOKEN=bom_…
55
- (PowerShell: $env:BOM_PI_URL=…; $env:BOM_PI_TOKEN=…) rồi chạy bom-pi status`;
58
+ Từ máy khác, pair MỘT LẦN bằng CLI (cần code in từ lệnh 'bom-pi pair' chạy TRÊN MÁY CHỦ):
59
+ bom-pi pair <CODE> --url http://<ip-máy-chủ>:8791
60
+ xong CLI tự nhớ (lưu ~/.bom-pi.json) — mọi lệnh chạy remote: bom-pi status / sessions …
61
+ (hoặc dùng WEB đầy đủ: mở http://<ip-máy-chủ>:8791 — pair bằng cùng code đó)`;
56
62
  }
57
63
  return `${head}
58
64
 
59
65
  daemon chưa chạy? thử: systemctl --user start bom-core (xem trạng thái: bom-pi status)
60
66
  chưa cài daemon?: bom-pi setup
61
- ở THIẾT BỊ KHÁC?: CLI có thể nói chuyện daemon từ xa — pair qua browser trước
62
- (http://<ip-máy-chạy-daemon>:8791), copy device token, rồi:
63
- BOM_PI_URL=http://<ip>:8791 BOM_PI_TOKEN=bom_… bom-pi status`;
67
+ ở THIẾT BỊ KHÁC?: pair một lần rồi mọi lệnh chạy remote:
68
+ bom-pi pair <CODE> --url http://<ip-máy-chạy-daemon>:8791`;
64
69
  }
65
70
 
66
71
  function rpcSocket(method, params) {
@@ -101,11 +106,16 @@ const pretty = (o) => console.log(JSON.stringify(o, null, 2));
101
106
 
102
107
  // Dispatcher: remote (/rpc qua HTTP) khi BOM_PI_URL đặt — thiết bị khác không có unix socket
103
108
  async function rpcRemote(method, params) {
104
- const r = await fetch(`${REMOTE_URL.replace(/\/+$/, '')}/rpc`, {
105
- method: 'POST',
106
- headers: { 'content-type': 'application/json', ...(REMOTE_TOKEN ? { Authorization: `Bearer ${REMOTE_TOKEN}` } : {}) },
107
- body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params: params ?? {} }),
108
- });
109
+ let r;
110
+ try {
111
+ r = await fetch(`${REMOTE_URL.replace(/\/+$/, '')}/rpc`, {
112
+ method: 'POST',
113
+ headers: { 'content-type': 'application/json', ...(REMOTE_TOKEN ? { Authorization: `Bearer ${REMOTE_TOKEN}` } : {}) },
114
+ body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params: params ?? {} }),
115
+ });
116
+ } catch (e) {
117
+ throw new Error(`không nối được ${REMOTE_URL}: ${e.message} — kiểm tra URL và web daemon (bom-web) đang chạy ở đó`);
118
+ }
109
119
  const j = await r.json().catch(() => ({}));
110
120
  if (!r.ok) throw new Error(`bom-web ${REMOTE_URL} ${r.status}: ${j?.error?.message ?? r.statusText}`);
111
121
  return j;
@@ -317,7 +327,45 @@ try {
317
327
  // M8 + security: pairing 2 CHIỀU — thiết bị nhập code (chiều 1) ⇒ enrollment chờ;
318
328
  // token chỉ cấp khi duyệt tại máy (chiều 2): pair approve <mã ghép>
319
329
  const sub2 = process.argv[3];
320
- if (!sub2) {
330
+ if (/^[A-Z2-7]{8}$/i.test(sub2 ?? '')) {
331
+ // THIẾT BỊ KHÁC pair từ CLI: code in ra từ máy chủ + URL web của máy chủ đó.
332
+ // /api/pair/start + /api/pair/poll công khai (code + operator duyệt tự vệ) —
333
+ // xong CLI TỰ LƯU {url, token} vào ~/.bom-pi.json, các lệnh sau chạy remote luôn.
334
+ const url = arg('url', REMOTE_URL || process.env.BOM_PI_URL || cliConfig.url || null);
335
+ if (!url) {
336
+ console.error('usage: bom-pi pair <code> --url http://<ip-máy-chủ>:8791 [--name tên-máy]\n (URL là máy CHẠY DAEMON — in từ lệnh \'bom-pi pair\' trên máy đó, vd https://bom.example.io.vn hoặc http://192.168.1.4:8791)');
337
+ process.exit(64);
338
+ }
339
+ const base = url.replace(/\/+$/, '');
340
+ const name = arg('name', `${hostname() || 'device'} (cli)`);
341
+ const post = async (path, body) => {
342
+ const r = await fetch(`${base}${path}`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });
343
+ const j = await r.json().catch(() => ({}));
344
+ if (!r.ok) throw new Error(`${path} ${r.status}: ${j?.error ?? r.statusText}`);
345
+ return j;
346
+ };
347
+ const st = await post('/api/pair/start', { code: sub2.toUpperCase(), name });
348
+ console.log(`đã gửi — chờ duyệt tại MÁY CHỦ. Ghép mã: ${st.displayId}`);
349
+ console.log(`(trên máy chủ: bom-pi pair approve ${st.displayId})`);
350
+ const t0 = Date.now();
351
+ for (;;) {
352
+ await new Promise((s) => setTimeout(s, st.pollAfterMs || 1500));
353
+ const p = await post('/api/pair/poll', { enrollId: st.enrollId });
354
+ if (p.status === 'approved') {
355
+ await saveCliConfig({ url: base, token: p.deviceToken, deviceId: p.deviceId, pairedAt: Date.now() });
356
+ console.log(`\nPAIRED ✓ — đã lưu ${CONFIG_PATH}`);
357
+ console.log(`mọi lệnh giờ tự chạy tới ${base}: bom-pi status / sessions / submit …`);
358
+ console.log(`(gỡ: bom-pi unpair)`);
359
+ process.exit(0);
360
+ }
361
+ if (p.status === 'rejected') { console.error('từ chối bởi operator tại máy chủ.'); process.exit(1); }
362
+ if (Date.now() - t0 > 5 * 60 * 1000) { console.error('hết thời gian chờ duyệt (5 phút) — chạy lại.'); process.exit(1); }
363
+ process.stdout.write('.');
364
+ }
365
+ } else if (!sub2 && REMOTE_URL) {
366
+ console.error(`máy này đã pair remote tới ${REMOTE_URL} (config ${CONFIG_PATH}).\n 'bom-pi pair' (in code/QR) là lệnh chạy TRÊN MÁY CHỦ — chạy đó, hoặc gỡ remote: bom-pi unpair.`);
367
+ process.exit(64);
368
+ } else if (!sub2) {
321
369
  const r = await rpc('pair.code');
322
370
  const nets = await import('node:os');
323
371
  const lan = Object.values(nets.networkInterfaces() ?? {}).flat()
@@ -361,6 +409,15 @@ try {
361
409
  console.log(`${d.revokedAtMs ? '○' : '●'} ${d.id} ${(d.name ?? '?').padEnd(18)} ${d.revokedAtMs ? 'revoked ' + new Date(d.revokedAtMs).toLocaleString() : 'paired ' + when} hash ${d.tokenHash8}`);
362
410
  }
363
411
  console.error(`# ${r.devices.filter((d) => !d.revokedAtMs).length} đang sống — thu hồi: bom-pi device rm <id>`);
412
+ } else if (cmd === 'unpair') {
413
+ // Gỡ remote khỏi máy này: thu hồi device TẠI MÁY CHỦ (best-effort) rồi xoá config local
414
+ if (!cliConfig.url || !cliConfig.token) { console.error('# máy này chưa pair remote (không có config)'); process.exit(0); }
415
+ try {
416
+ await rpcRemote('devices.remove', { id: cliConfig.deviceId });
417
+ console.log('đã thu hồi device tại máy chủ.');
418
+ } catch (e) { console.error(`# không thu hồi được tại máy chủ (${e.message}) — xoá config local thôi.`); }
419
+ await saveCliConfig({});
420
+ console.log(`đã xoá ${CONFIG_PATH} — CLI quay về local socket.`);
364
421
  } else if (cmd === 'setup') {
365
422
  // Sinh systemd user units đúng đường dẫn cài (npm global HOẶC working tree) + enable.
366
423
  // root = 3 cấp trên file CLI này: packages/cli/src → gốc package (cả hai kiểu cài đều đúng)
@@ -495,7 +552,7 @@ WantedBy=default.target
495
552
  } else if (cmd === 'shutdown') {
496
553
  pretty(await rpc('shutdown'));
497
554
  } else {
498
- console.error('usage: bom-pi <hello|status|models|agents|sessions|new|use|submit|reset|transcript|approvals|approve|deny|schedules|schedule|unschedule|factory|limits|abort|watch|shutdown|version> [--socket PATH] [--cid N] [--url http://host:8791] [--token bom_…]\n từ thiết bị khác: BOM_PI_URL + BOM_PI_TOKEN (xem bom-pi version)');
555
+ console.error('usage: bom-pi <hello|status|models|agents|sessions|new|use|submit|reset|transcript|approvals|approve|deny|schedules|schedule|unschedule|factory|limits|abort|watch|unpair|version> [--socket PATH] [--cid N] [--url http://host:8791] [--token bom_…]\n thiết bị khác: bom-pi pair <CODE> --url http://host:8791 — pair một lần, CLI tự nhớ (xem bom-pi version)');
499
556
  process.exit(64);
500
557
  }
501
558
  if (cmd !== 'watch') process.exit(0);