@uniquedivine/tg-sai 0.0.4 → 0.1.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.
package/README.md CHANGED
@@ -5,6 +5,47 @@ Sai Telegram admin audit and bootstrap CLI.
5
5
  See file `RUNBOOK.md` for operator setup, profile management, and safe
6
6
  execution workflows.
7
7
 
8
+ ## Install
9
+
10
+ Requires [Bun](https://bun.sh). No clone needed:
11
+
12
+ ```bash
13
+ bunx @uniquedivine/tg-sai --help # run once
14
+ bun add -g @uniquedivine/tg-sai # or install the `tg-sai` command
15
+ tg-sai profile add --name me --login --qr --interactive
16
+ ```
17
+
18
+ Move Sai chats you own to the custody account and add SaiTeam_bot as admin.
19
+ The command is a dry run until you add `--run`:
20
+
21
+ ```bash
22
+ tg-sai ops sai-bootstrap --file chats.txt # plan only
23
+ tg-sai ops sai-bootstrap --file chats.txt --run # apply
24
+ ```
25
+
26
+ File `chats.txt` holds one chat ID per line. Ownership transfer needs your
27
+ Telegram 2FA password in `TELEGRAM_PASSWORD` or the saved profile. The owner
28
+ target defaults to the Sai custody account,
29
+ [@SaiTeam_AI](https://t.me/SaiTeam_AI) (user 8274304175), and the bot is
30
+ [@SaiTeam_bot](https://t.me/SaiTeam_bot). `--target-user` overrides the owner.
31
+
32
+ ## Logs
33
+
34
+ tg-sai writes no log files. Every run records Telegram calls and app log
35
+ rows in the SQLite file `~/.tg-sai/telemetry.db` (`$TG_SAI_DIR` overrides
36
+ `~/.tg-sai`). Read them with `tg-sai events`:
37
+
38
+ ```bash
39
+ tg-sai events logs --since 24h --level warn --subsystem worker
40
+ tg-sai events show <run_id|job_id|request_id>
41
+ tg-sai events runs
42
+ ```
43
+
44
+ Saved Telegram flood waits live in `~/.tg-sai/state.db`. While one is
45
+ active, a CLI command that would send the held call exits with code 75 and
46
+ prints when to retry. `TG_SAI_TELEMETRY=off` turns off `telemetry.db` only;
47
+ `state.db` and its holds still apply.
48
+
8
49
  ## Development
9
50
 
10
51
  ```bash
package/RUNBOOK.md CHANGED
@@ -35,8 +35,15 @@ Credential profile behavior:
35
35
 
36
36
  - Command `tg-sai profile find` reports discovered Telegram credential sources
37
37
  without writing profile state.
38
- - Command `tg-sai profile add` stores a local profile from explicit flags,
39
- process environment variables, or one unambiguous discovered source.
38
+ - Bare commands `tg-sai profile` and `tg-sai profile add` show help and configured
39
+ profiles without saving state or connecting to Telegram.
40
+ - Command `tg-sai profile add` with explicit options stores a local profile from
41
+ flags, process environment variables, or merged non-conflicting discovered
42
+ sources. Use a distinct `--name` for another account; without it, explicit
43
+ options update the active/default profile.
44
+ - Explicit `--qr`, `--phone`, or `--login` starts a fresh account login and
45
+ ignores discovered/environment sessions and account passwords. App credentials
46
+ can still come from discovery, environment, or interactive input.
40
47
  - Commands `tg-sai profile list`, `tg-sai profile show`, `tg-sai profile use`,
41
48
  and `tg-sai profile remove` manage local session profiles.
42
49
 
@@ -102,11 +109,12 @@ cd /path/to/tg-sai
102
109
  uv sync
103
110
  ```
104
111
 
105
- Create file `.env` in the `tg-sai` directory with Telegram app
106
- credentials:
112
+ Create user-local file `~/.tg-sai/.env` with Telegram app credentials:
107
113
 
108
114
  ```bash
109
- cat > .env <<'EOF'
115
+ mkdir -p ~/.tg-sai
116
+ chmod 700 ~/.tg-sai
117
+ cat > ~/.tg-sai/.env <<'EOF'
110
118
  TELEGRAM_API_ID=<api id from https://my.telegram.org/apps>
111
119
  TELEGRAM_API_HASH=<api hash from https://my.telegram.org/apps>
112
120
  EOF
@@ -121,18 +129,20 @@ The session string is generated after QR login.
121
129
  Generate the session string and save it directly into a `tg-sai` profile:
122
130
 
123
131
  ```bash
124
- tg-sai profile add --name operator --src 1 --login --qr
132
+ tg-sai profile add --name sai-custody --login --qr --interactive
125
133
  ```
126
134
 
127
135
  Scan the QR code in Telegram with **Settings > Devices > Link Desktop Device**.
128
- After login succeeds, command `tg-sai profile add` saves the generated
129
- `sessionString` into profile `operator`.
136
+ Scan while signed into the intended account. After login succeeds, this command
137
+ saves its verified identity and session in the separate `sai-custody` profile.
138
+ The existing active profile stays selected. Inspect the result with command
139
+ `tg-sai profile show sai-custody`.
130
140
 
131
141
  If you generated the session string manually, save it with explicit flags:
132
142
 
133
143
  ```bash
134
144
  tg-sai profile add \
135
- --name operator \
145
+ --name sai-custody \
136
146
  --api-id <api id> \
137
147
  --api-hash <api hash> \
138
148
  --session-string '<session string>'
@@ -142,7 +152,7 @@ Store the Telegram 2FA password in the same profile when ownership transfer is
142
152
  needed:
143
153
 
144
154
  ```bash
145
- tg-sai profile add --name operator --password '<password>'
155
+ tg-sai profile add --name sai-custody --password '<password>'
146
156
  ```
147
157
 
148
158
  Check the local setup:
@@ -169,17 +179,26 @@ Add a local profile from a numbered source shown by command
169
179
  `tg-sai profile find`:
170
180
 
171
181
  ```bash
172
- tg-sai profile add --name operator --src 1
182
+ tg-sai profile add --name another-account --src 1
173
183
  ```
174
184
 
175
185
  Command `tg-sai profile add` can store the Telegram 2FA password with flag
176
186
  `--password`. Command `tg-sai profile show` redacts stored `apiHash`,
177
187
  `sessionString`, and `password`.
178
188
 
189
+ For password or metadata updates, `--name` selects an existing saved profile by
190
+ name, handle, numeric user ID, or saved-list index. The update preserves that
191
+ profile's account identity and session without importing environment or discovered
192
+ credentials. An unknown password-update selector fails instead of creating a
193
+ profile. Explicit login/import cannot replace a known profile with a different
194
+ account; use a new name for another account, or remove the old profile first.
195
+ Flag `--src` selects a credential source numbered by `tg-sai profile find`, not
196
+ the saved-profile index printed by `tg-sai profile list`.
197
+
179
198
  Create a blank draft profile for onboarding:
180
199
 
181
200
  ```bash
182
- tg-sai profile add --name operator
201
+ tg-sai profile add --name draft
183
202
  ```
184
203
 
185
204
  Blank or partial profiles are useful while collecting `apiId`, `apiHash`, and
@@ -190,7 +209,7 @@ Add a local profile from explicit values:
190
209
 
191
210
  ```bash
192
211
  tg-sai profile add \
193
- --name operator \
212
+ --name another-account \
194
213
  --handle <telegram-handle> \
195
214
  --user-id <telegram-user-id> \
196
215
  --display-name "<display name>"
@@ -202,16 +221,22 @@ List local profiles:
202
221
  tg-sai profile list
203
222
  ```
204
223
 
224
+ Command `tg-sai profile` also shows configured profiles beneath its command
225
+ list. Bare command `tg-sai profile add` explains fresh QR enrollment and how
226
+ named accounts differ from updates to the active profile. Neither changes the
227
+ saved configuration.
228
+
205
229
  Find Sai-related chats visible to the active Telegram session:
206
230
 
207
231
  ```bash
208
232
  tg-sai ops sai-find
209
233
  ```
210
234
 
211
- Command `tg-sai ops sai-find` always writes a JSONL file under directory
212
- `~/.tg-sai/logs/`, using the next available filename such as
213
- `sai-find-00.jsonl` or `sai-find-01.jsonl`. Use flag `--out <path>` to also
214
- write a copy somewhere else.
235
+ Command `tg-sai ops sai-find` always writes its chat list as a JSONL file
236
+ under directory `~/.tg-sai/logs/`, using the next available filename such as
237
+ `sai-find-00.jsonl` or `sai-find-01.jsonl`. This file is command output for
238
+ `ops sai-bootstrap --file`, not a log. Use flag `--out <path>` to also write a
239
+ copy somewhere else.
215
240
 
216
241
  Dry-run Sai bot and owner-target updates from a reviewed chat file:
217
242
 
@@ -219,13 +244,19 @@ Dry-run Sai bot and owner-target updates from a reviewed chat file:
219
244
  tg-sai ops sai-bootstrap --file ~/.tg-sai/logs/sai-find-00.jsonl
220
245
  ```
221
246
 
222
- Command `tg-sai ops sai-bootstrap` always writes a JSONL run log under directory
223
- `~/.tg-sai/logs/`, using the next available filename such as
224
- `sai-bootstrap-00.jsonl` or `sai-bootstrap-01.jsonl`. The command waits one
225
- second between chats by default. Use flag `--delay-ms <ms>` to change that
247
+ Command `tg-sai ops sai-bootstrap` prints one JSON event per line on stdout
248
+ and its `telemetry_run_id`; redirect stdout if you want a copy. Its Telegram
249
+ calls are in the telemetry database: `tg-sai events show <run_id>`. The
250
+ command waits one second between chats by default. Use flag `--delay-ms <ms>` to change that
226
251
  delay. When environment variable `TELEGRAM_PASSWORD` is available, execution
227
252
  mode attempts ownership transfer by default for eligible chats.
228
253
 
254
+ The command sleeps through flood waits up to five minutes (environment
255
+ variable `TG_SAI_FLOOD_MAX_WAIT_MS` overrides). A longer `FLOOD_WAIT_X` or any
256
+ `PEER_FLOOD` stops the remaining chats: stdout gets a `flood_stop` event with
257
+ the chat, the remaining count, the code, and `resumeAt`, and the command exits
258
+ with code 1. A `SLOWMODE_WAIT_X` fails only that chat.
259
+
229
260
  Run a live read-only audit:
230
261
 
231
262
  ```bash
@@ -276,6 +307,58 @@ TELEGRAM_PASSWORD="<2fa password>" tg-sai add-owner \
276
307
  --run
277
308
  ```
278
309
 
310
+ ## Logs, Telemetry, and Flood Holds
311
+
312
+ tg-sai writes no JSONL run logs. Earlier versions wrote
313
+ `~/.tg-sai/logs/<command>-NN.jsonl` and `sai-bootstrap-NN.jsonl`; those files
314
+ stay readable but nothing new is added. Each process records into SQLite file
315
+ `~/.tg-sai/telemetry.db` (`$TG_SAI_DIR/telemetry.db`):
316
+
317
+ - `run.start` and `run.end` rows with the sanitized argv and exit code.
318
+ - `tg.rpc.call`, `tg.rpc.attempt`, and `tg.flood.wait` rows for Telegram
319
+ calls, with summarized request and response shapes.
320
+ - App log rows: a `level` (`debug`, `info`, `warn`, `error`), a short fixed
321
+ `msg`, and summarized `attrs`. Their `event` is `log.<subsystem>` or a
322
+ dotted name such as `worker.job.held`.
323
+
324
+ Read it with `tg-sai events`. Output is TOON; add `--json` for JSON.
325
+
326
+ ```bash
327
+ tg-sai events logs --since 24h --level warn --subsystem worker
328
+ tg-sai events logs --run <run_id> --limit 200
329
+ tg-sai events show <run_id|job_id|request_id> # calls and log rows
330
+ tg-sai events runs
331
+ tg-sai events floods --since 7d
332
+ tg-sai events stats # rows, log rows by level
333
+ ```
334
+
335
+ Saved flood waits live in a separate file, `~/.tg-sai/state.db`. Every tg-sai
336
+ process checks it before invite, create, resolve, and send calls. While a
337
+ hold is active, a CLI command exits with code 75 and one stderr line naming
338
+ the held call group and the UTC time to retry. If `state.db` cannot be read,
339
+ invite and create calls are blocked, and the command also exits 75. The
340
+ hold check keys by the account the session's own `getMe` reports, not by the
341
+ profile's saved user ID; until that is known, invite and create calls are
342
+ blocked the same way (`TG_SAI_HOLD_ACCOUNT_UNKNOWN`, exit 75).
343
+
344
+ `ops sai-bootstrap` stops the batch with a `flood_stop` event on any of
345
+ these refusals and exits 75. A `PEER_FLOOD` or a Telegram flood wait longer
346
+ than the run's threshold also stops it, with exit 1.
347
+
348
+ Inspect and override holds with `tg-sai holds`. `set` and `clear` are dry
349
+ runs without `--run`; `clear --run` on an active hold also needs `--force`.
350
+ Each write adds a `holds.set` or `holds.clear` row to `telemetry.db`. A
351
+ `--run` refuses a `state.db` owned by another user, so on the custody VM
352
+ run the worker's binary as the worker user:
353
+
354
+ ```bash
355
+ sudo -u sai-custody-worker env TG_SAI_DIR=/var/lib/sai-custody-worker HOME=/var/lib/sai-custody-worker /opt/sai-custody-worker/current/tg-sai-worker holds list
356
+ ```
357
+
358
+ `TG_SAI_TELEMETRY=off` disables `telemetry.db` writes only. Flood holds in
359
+ `state.db` still apply, and `tg-sai events` reads whatever was recorded
360
+ before.
361
+
279
362
  ## File roles
280
363
 
281
364
  | File | Role |
@@ -308,10 +391,10 @@ different user is the creator.
308
391
 
309
392
  ## Suggested workflow
310
393
 
311
- 1. Run command `just tg-sai profile find` to inspect available Telegram
394
+ 1. Run command `tg-sai profile find` to inspect available Telegram
312
395
  credential sources.
313
- 2. Run command `just tg-sai profile add --name <name> --src <number>` to
314
- save a local profile from discovery output.
396
+ 2. Run command `tg-sai profile add --qr` to save a local profile and generate a
397
+ missing session string.
315
398
  3. Run command `tg-sai audit --chat <id>` before writes.
316
399
  4. Run command `tg-sai add-bot --chat <id>` or command
317
400
  `tg-sai add-owner --chat <id> --target-user <user>` as a dry-run.
@@ -341,3 +424,100 @@ list regular members as admins. This tool uses per-member lookups
341
424
  - Emit a reviewable table or JSON artifact for batch dry-runs.
342
425
  - Add named rights profiles for bot admin and owner-target admin promotion.
343
426
  - Add fixtures for a completed ownership transfer.
427
+
428
+ ## Custody Chat Provisioning
429
+
430
+ SaiTeam_bot's private `/newdealchat` flow submits an approved request to the
431
+ Deal Desk API. A separate worker creates the private supergroup as custody
432
+ user `8274304175`, profile `SaiTeam_AI`. The bot, Unique, and the requester
433
+ become admins. August and Erick are ordinary default members. The deployed bot
434
+ configuration controls the requester allowlist and defaults.
435
+
436
+ When adding an operator, configure their numeric Telegram user ID in the bot
437
+ allowlist. A fresh custody session may not know that ID's access hash. Add a
438
+ verified handle alias in `provisioningRuntime.ts` for new team operators and
439
+ deploy the worker with it. Resolve the handle through the custody account first
440
+ and verify the exact numeric ID and that it is a user account. The runtime
441
+ rejects handles that later resolve to a different account.
442
+
443
+ For explicit CLI requests, prepare a JSON file with a stable `requestId`,
444
+ `custodyProfile`, `expectedUserId`, `title`, `participants`, `admins`,
445
+ `botRightsProfile`, and `dealIds`. Each admin uses `rightsProfile: "team"`;
446
+ the bot uses `botRightsProfile: "baseline"`. `optionalParticipants` must be a
447
+ subset of ordinary participants. `dealIds: []` registers a chat without
448
+ creating or linking a commercial deal.
449
+
450
+ ```sh
451
+ tg-sai chat provision prepare --file request.json
452
+ tg-sai chat provision prepare --file request.json --run
453
+ tg-sai chat provision run --request-id <request-id>
454
+ tg-sai chat provision run --request-id <request-id> --run
455
+ tg-sai chat provision status --request-id <request-id>
456
+ ```
457
+
458
+ The cloud CLI credential needs the corresponding chat workflow capabilities.
459
+ The background worker uses a dedicated read-only record credential with only
460
+ `chats:work` granted for workflow writes. The worker service gets only the
461
+ custody profile, without personal sessions or ownership-transfer passwords.
462
+
463
+ ```sh
464
+ tg-sai worker --custody-profile SaiTeam_AI --once
465
+ tg-sai worker --custody-profile SaiTeam_AI --once --run
466
+ tg-sai worker --custody-profile SaiTeam_AI --run --interval-ms 5000
467
+ ```
468
+
469
+ Without `--run`, the worker reads one queue page and does not connect to
470
+ Telegram. Execution uses a durable worker identity, account lock, and local
471
+ receipts. Never delete these files to force a retry. An uncertain creation
472
+ blocks further creation until an operator reconciles the original request.
473
+ Use `chat provision resolve --help` to adopt a verified group with its original
474
+ creation marker. Known-group partial setup resumes against that same group.
475
+
476
+ Required admins must be members before setup becomes ready. An ordinary member
477
+ blocked by privacy settings can remain pending with a 24-hour, approval-required
478
+ invite link. A link does not mean the person joined. Handle-based resolution of
479
+ fixed team defaults must verify the expected numeric ID before any invitation.
480
+
481
+ New ready receipts include an `openGroupUrl` anchored to a message actually
482
+ observed in the group and the existing `primaryInviteLink`, when available.
483
+ These read-only lookups run together with a 10-second limit each. A missing link
484
+ does not undo verified setup. Existing primary invitations are never rotated or
485
+ created by this step, and privacy-blocked members keep their separate controlled
486
+ invitations. The bot displays the saved links in its completion reply and
487
+ `/chatstatus`. Completed requests are not rewritten to refresh missing links.
488
+
489
+ The source bot configuration starts with admission in `observe` mode. Check
490
+ `~/ki/boku/epics/comms/telegram-bot.md` and its dated rollout evidence for
491
+ the actual deployed state before testing bot commands.
492
+
493
+
494
+ ## Runtime Health and Deployment Readiness
495
+
496
+ `tg-sai health` now defaults to offline runtime checks. It verifies both bundled
497
+ WASM assets, crypto and compression operations, and actual Telegram client
498
+ preparation with a temporary SQLite store. It does not read profiles or contact
499
+ Telegram. `--json` provides a machine-readable result and failures exit nonzero.
500
+
501
+ ```sh
502
+ tg-sai health --offline --json
503
+ tg-sai health --online --custody-profile SaiTeam_AI --json
504
+ ```
505
+
506
+ Online mode additionally strict-loads custody user `8274304175`, acquires the
507
+ same account lock used by provisioning, and performs bounded `getMe` validation.
508
+ It cannot fall back to a personal account and does not create or modify chats.
509
+ An account-lock conflict fails the check; run it while the worker is stopped or
510
+ idle. The earlier active-profile/workstation diagnostics remain available with
511
+ `tg-sai health --local` and are separate from custody-worker readiness.
512
+
513
+ The deployed executable includes a worker command and the same health commands.
514
+ A release candidate must pass offline health as the worker Unix user before
515
+ installation selects it. The worker systemd unit runs offline and online checks
516
+ before starting the queue loop. A service being alive, `--help`, or an empty
517
+ queue preview does not prove Telegram client initialization.
518
+
519
+ From the Boku Deal Desk source directory, `just bot-health` checks the deployed
520
+ runtime under its service sandbox. `just bot-health --online` also validates
521
+ custody identity. These are operator probes, not the API's `/health` endpoint.
522
+ That endpoint reports API health and whether bot ingress is configured; it does
523
+ not certify Telegram custody readiness.