@uniquedivine/tg-sai 0.0.2 → 0.0.4

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 (4) hide show
  1. package/cfg.ts +52 -8
  2. package/health.ts +1 -1
  3. package/main.ts +70 -14
  4. package/package.json +1 -1
package/cfg.ts CHANGED
@@ -5,6 +5,12 @@ export interface TelegramIdentity {
5
5
  kind: "user" | "bot"
6
6
  }
7
7
 
8
+ export interface TelegramChatIdentity {
9
+ id: number
10
+ title: string
11
+ kind: "channel" | "group"
12
+ }
13
+
8
14
  /**
9
15
  * Telegram bot identity for the Sai team bot. The bot is the automation target
10
16
  * to add and promote in candidate chats.
@@ -17,13 +23,51 @@ export const saiBot = {
17
23
  } satisfies TelegramIdentity
18
24
 
19
25
  /**
20
- * Telegram user identity for Unique (`uniquedivine`). In this workflow, Unique
21
- * is the target user to promote to admin or prepare for ownership transfer.
26
+ * Known Telegram user identities used by tg-sai operator workflows.
22
27
  */
23
- export const uniqueDivine = {
24
- id: 1912700300,
25
- handle: "uniquedivine",
26
- displayName: "Unique (NIBIRU)",
27
- kind: "user",
28
- } satisfies TelegramIdentity
28
+ export const USERS = {
29
+ /**
30
+ * Default operational target user to promote to admin or prepare for
31
+ * ownership transfer.
32
+ */
33
+ uniqueDivine: {
34
+ id: 1912700300,
35
+ handle: "uniquedivine",
36
+ displayName: "Unique (NIBIRU)",
37
+ kind: "user",
38
+ },
39
+
40
+ /**
41
+ * Corinne's primary account. This account owns or administers some candidate
42
+ * chats used by tg-sai operator flows.
43
+ */
44
+ corinne: {
45
+ id: 7930936303,
46
+ handle: "corinnebernett",
47
+ displayName: "Corinne Bernett",
48
+ kind: "user",
49
+ },
50
+
51
+ /**
52
+ * Corinne's alternate account. Use this account as a controlled
53
+ * owner-transfer target when dry-running or testing against the internal
54
+ * Nibiru chat before touching partner or production chats.
55
+ */
56
+ corinneAlt: {
57
+ id: 8274304175,
58
+ handle: "corinneantonia",
59
+ displayName: "Corinne Antonia",
60
+ kind: "user",
61
+ },
62
+ } satisfies Record<string, TelegramIdentity>
63
+
64
+ /**
65
+ * Internal Nibiru chat used as a controlled target for tg-sai dry runs and
66
+ * ownership-transfer tests.
67
+ */
68
+ export const nibiruInternal = {
69
+ id: -1002094035214,
70
+ title: "Nibiru | Internal",
71
+ kind: "channel",
72
+ } satisfies TelegramChatIdentity
29
73
 
package/health.ts CHANGED
@@ -297,7 +297,7 @@ function checkPassword(
297
297
  profile: TgSaiProfile | null,
298
298
  ): void {
299
299
  if (env.TELEGRAM_PASSWORD || profile?.password) {
300
- info(collector, "password_status: present")
300
+ good(collector, "password_status: present")
301
301
  return
302
302
  }
303
303
  warning(
package/main.ts CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  rm,
9
9
  writeFile,
10
10
  } from "node:fs/promises"
11
+ import { createRequire } from "node:module"
11
12
  import { tmpdir } from "node:os"
12
13
  import { join } from "node:path"
13
14
  import { Command } from "commander"
@@ -20,13 +21,14 @@ import type {
20
21
  ProposedAction,
21
22
  } from "./adminAudit"
22
23
  import { planAdminBootstrap } from "./adminAudit"
23
- import { saiBot, uniqueDivine } from "./cfg"
24
+ import { saiBot, USERS } from "./cfg"
24
25
  import { runHealthCheck } from "./health"
25
26
  import {
26
27
  addTargetToChat,
27
28
  createMtcuteClient,
28
29
  fetchChatAuditInput,
29
30
  findSaiChats,
31
+ normalizeUser,
30
32
  promoteTargetAdmin,
31
33
  transferTargetOwnership,
32
34
  } from "./mtcuteAdapter"
@@ -51,7 +53,10 @@ import type {
51
53
  TelegramCredentialValues,
52
54
  } from "./profiles"
53
55
 
54
- const DEFAULT_USER_TARGET_ID = uniqueDivine.id
56
+ const require = createRequire(import.meta.url)
57
+ const packageJson = require("./package.json") as { version: string }
58
+
59
+ const DEFAULT_USER_TARGET_ID = USERS.uniqueDivine.id
55
60
  const DEFAULT_BOT_TARGET_ID = saiBot.id
56
61
  const DEFAULT_BOT_TARGET_PEER = saiBot.handle
57
62
 
@@ -211,6 +216,17 @@ function actionExecutorDeps(logger: BatchLogger = consoleBatchLogger) {
211
216
  }
212
217
  }
213
218
 
219
+ function actorBatchLogger(actorUsername: string): BatchLogger {
220
+ return {
221
+ event(event) {
222
+ emitJsonObject({
223
+ ...event,
224
+ actorUsername,
225
+ })
226
+ },
227
+ }
228
+ }
229
+
214
230
  /**
215
231
  * Emits one command-level JSON event to stdout.
216
232
  */
@@ -269,6 +285,14 @@ async function getTelegramEnv(profileName?: string): Promise<TelegramEnv> {
269
285
  }
270
286
  }
271
287
 
288
+ async function selectedProfileName(profileName?: string): Promise<string | null> {
289
+ if (profileName) {
290
+ return profileName
291
+ }
292
+ const config = await readConfig()
293
+ return config.activeProfile
294
+ }
295
+
272
296
  function parsePeer(value: string): number | string {
273
297
  const normalized = value.startsWith("@") ? value.slice(1) : value
274
298
  const asNumber = Number(normalized)
@@ -310,7 +334,7 @@ function printAudit(input: ChatAuditInput) {
310
334
  console.log(
311
335
  `target_can_receive_ownership: ${audit.targetCanReceiveOwnership}`,
312
336
  )
313
- console.log(`proposed_actions: ${audit.proposedActions.join("; ")}`)
337
+ console.log(`proposed_actions: ${formatActionList(audit.proposedActions)}`)
314
338
  }
315
339
 
316
340
  function shouldRunAction(
@@ -320,6 +344,10 @@ function shouldRunAction(
320
344
  return proposedActions.includes(action)
321
345
  }
322
346
 
347
+ function formatActionList(actions: ProposedAction[]): string {
348
+ return JSON.stringify(actions)
349
+ }
350
+
323
351
  async function withClient<T>(
324
352
  profileName: string | undefined,
325
353
  fn: (
@@ -380,7 +408,9 @@ async function runAddBot(options: AddBotOptions): Promise<void> {
380
408
  console.log(`target_peer: ${options.targetPeer}`)
381
409
  console.log(`actor_role: ${beforePlan.actorRole}`)
382
410
  console.log(`target_role_before: ${beforePlan.targetRole}`)
383
- console.log(`proposed_actions: ${beforePlan.proposedActions.join("; ")}`)
411
+ console.log(
412
+ `proposed_actions: ${formatActionList(beforePlan.proposedActions)}`,
413
+ )
384
414
 
385
415
  if (options.run !== true) {
386
416
  console.log("dry_run: pass --run to add/promote the bot")
@@ -427,7 +457,9 @@ async function runAddBot(options: AddBotOptions): Promise<void> {
427
457
 
428
458
  console.log(`target_role_after: ${afterPlan.targetRole}`)
429
459
  console.log(`target_is_admin_after: ${afterPlan.targetIsAdmin}`)
430
- console.log(`remaining_actions: ${afterPlan.proposedActions.join("; ")}`)
460
+ console.log(
461
+ `remaining_actions: ${formatActionList(afterPlan.proposedActions)}`,
462
+ )
431
463
  })
432
464
  }
433
465
 
@@ -448,7 +480,9 @@ async function runAddOwner(options: AddOwnerOptions): Promise<void> {
448
480
  console.log(
449
481
  `target_can_receive_ownership_before: ${beforePlan.targetCanReceiveOwnership}`,
450
482
  )
451
- console.log(`proposed_actions: ${beforePlan.proposedActions.join("; ")}`)
483
+ console.log(
484
+ `proposed_actions: ${formatActionList(beforePlan.proposedActions)}`,
485
+ )
452
486
 
453
487
  if (options.run !== true) {
454
488
  console.log("dry_run: pass --run to add/promote the target user")
@@ -498,7 +532,9 @@ async function runAddOwner(options: AddOwnerOptions): Promise<void> {
498
532
  console.log(
499
533
  `target_can_receive_ownership_after: ${afterPlan.targetCanReceiveOwnership}`,
500
534
  )
501
- console.log(`remaining_actions: ${afterPlan.proposedActions.join("; ")}`)
535
+ console.log(
536
+ `remaining_actions: ${formatActionList(afterPlan.proposedActions)}`,
537
+ )
502
538
 
503
539
  if (!afterPlan.actorCanTransferOwnership) {
504
540
  console.log("ownership_transfer: skipped_actor_not_owner")
@@ -565,6 +601,7 @@ async function runSaiFind(options: SaiFindOptions): Promise<void> {
565
601
  async function applyTargetPlan(params: {
566
602
  client: Awaited<ReturnType<typeof createMtcuteClient>>
567
603
  chat: ChatInput
604
+ actorUsername: string
568
605
  targetUserId: number | string
569
606
  targetPeer: number | string
570
607
  rights: Record<string, boolean>
@@ -580,6 +617,7 @@ async function applyTargetPlan(params: {
580
617
  printJsonEvent("target_plan", {
581
618
  chatId: params.chat.chatId,
582
619
  title: params.chat.title,
620
+ actorUsername: params.actorUsername,
583
621
  targetUserId: params.targetUserId,
584
622
  actionPrefix: params.actionPrefix,
585
623
  run: params.run,
@@ -591,6 +629,7 @@ async function applyTargetPlan(params: {
591
629
  if (!beforePlan.actorCanApply) {
592
630
  printJsonEvent("target_skip", {
593
631
  chatId: params.chat.chatId,
632
+ actorUsername: params.actorUsername,
594
633
  actionPrefix: params.actionPrefix,
595
634
  reason: "actor_lacks_owner_or_add_admins_right",
596
635
  })
@@ -601,7 +640,7 @@ async function applyTargetPlan(params: {
601
640
  return
602
641
  }
603
642
 
604
- const deps = actionExecutorDeps()
643
+ const deps = actionExecutorDeps(actorBatchLogger(params.actorUsername))
605
644
  if (shouldRunAction(beforePlan.proposedActions, "add target user")) {
606
645
  await withFloodWaitRetry(
607
646
  {
@@ -647,6 +686,7 @@ async function maybeTransferOwner(params: {
647
686
  client: Awaited<ReturnType<typeof createMtcuteClient>>
648
687
  env: TelegramEnv
649
688
  chat: ChatInput
689
+ actorUsername: string
650
690
  targetUser: number | string
651
691
  enabled: boolean
652
692
  run: boolean
@@ -664,6 +704,7 @@ async function maybeTransferOwner(params: {
664
704
  if (!plan.actorCanTransferOwnership) {
665
705
  printJsonEvent("ownership_skip", {
666
706
  chatId: params.chat.chatId,
707
+ actorUsername: params.actorUsername,
667
708
  reason: "actor_not_owner",
668
709
  })
669
710
  return
@@ -671,6 +712,7 @@ async function maybeTransferOwner(params: {
671
712
  if (!plan.targetCanReceiveOwnership) {
672
713
  printJsonEvent("ownership_skip", {
673
714
  chatId: params.chat.chatId,
715
+ actorUsername: params.actorUsername,
674
716
  reason: "target_not_eligible",
675
717
  })
676
718
  return
@@ -678,6 +720,7 @@ async function maybeTransferOwner(params: {
678
720
  if (params.env.password === null) {
679
721
  printJsonEvent("ownership_skip", {
680
722
  chatId: params.chat.chatId,
723
+ actorUsername: params.actorUsername,
681
724
  reason: "missing_telegram_password",
682
725
  })
683
726
  return
@@ -685,6 +728,7 @@ async function maybeTransferOwner(params: {
685
728
  if (!params.run) {
686
729
  printJsonEvent("ownership_plan", {
687
730
  chatId: params.chat.chatId,
731
+ actorUsername: params.actorUsername,
688
732
  action: "transfer_owner",
689
733
  })
690
734
  return
@@ -701,7 +745,7 @@ async function maybeTransferOwner(params: {
701
745
  targetPeer: params.targetUser,
702
746
  password: params.env.password ?? "",
703
747
  }),
704
- actionExecutorDeps(),
748
+ actionExecutorDeps(actorBatchLogger(params.actorUsername)),
705
749
  )
706
750
  }
707
751
 
@@ -727,18 +771,23 @@ async function runSaiBootstrap(options: SaiBootstrapOptions): Promise<void> {
727
771
 
728
772
  await withClient(options.profile, async (client, env) => {
729
773
  activeJsonLogPath = logPath
774
+ const actor = normalizeUser(await client.getMe())
775
+ const actorUsername = actor.username ?? ""
776
+ const profileName = await selectedProfileName(options.profile)
730
777
  const ownershipTransferEnabled =
731
778
  options.transferOwner !== false && env.password !== null
732
779
  console.log(`wrote_run_log: ${logPath}`)
733
780
  printJsonEvent("run_start", {
734
781
  file: options.file,
735
782
  logPath,
783
+ profileName,
784
+ actor,
736
785
  chatCount: chats.length,
737
786
  run: options.run === true,
738
787
  transferOwner: ownershipTransferEnabled,
739
788
  passwordAvailable: env.password !== null,
740
789
  delayMs,
741
- targetUser: uniqueDivine.id,
790
+ targetUser: USERS.uniqueDivine.id,
742
791
  targetBot: saiBot.id,
743
792
  })
744
793
 
@@ -749,12 +798,14 @@ async function runSaiBootstrap(options: SaiBootstrapOptions): Promise<void> {
749
798
  total: chats.length,
750
799
  chatId: chat.chatId,
751
800
  title: chat.title,
801
+ actorUsername,
752
802
  })
753
803
 
754
804
  try {
755
805
  await applyTargetPlan({
756
806
  client,
757
807
  chat,
808
+ actorUsername,
758
809
  targetUserId: saiBot.id,
759
810
  targetPeer: saiBot.handle,
760
811
  rights: saiBotAdminRights,
@@ -764,8 +815,9 @@ async function runSaiBootstrap(options: SaiBootstrapOptions): Promise<void> {
764
815
  await applyTargetPlan({
765
816
  client,
766
817
  chat,
767
- targetUserId: uniqueDivine.id,
768
- targetPeer: uniqueDivine.handle,
818
+ actorUsername,
819
+ targetUserId: USERS.uniqueDivine.id,
820
+ targetPeer: USERS.uniqueDivine.handle,
769
821
  rights: ownerTargetAdminRights,
770
822
  actionPrefix: "owner_target",
771
823
  run: options.run === true,
@@ -774,16 +826,19 @@ async function runSaiBootstrap(options: SaiBootstrapOptions): Promise<void> {
774
826
  client,
775
827
  env,
776
828
  chat,
777
- targetUser: uniqueDivine.handle,
829
+ actorUsername,
830
+ targetUser: USERS.uniqueDivine.handle,
778
831
  enabled: ownershipTransferEnabled,
779
832
  run: options.run === true,
780
833
  })
781
834
  printJsonEvent("chat_complete", {
782
835
  chatId: chat.chatId,
836
+ actorUsername,
783
837
  })
784
838
  } catch (error) {
785
839
  printJsonEvent("chat_error", {
786
840
  chatId: chat.chatId,
841
+ actorUsername,
787
842
  errorName: error instanceof Error ? error.name : "UnknownError",
788
843
  errorMessage:
789
844
  error instanceof Error ? error.message : String(error),
@@ -793,6 +848,7 @@ async function runSaiBootstrap(options: SaiBootstrapOptions): Promise<void> {
793
848
  if (delayMs > 0 && i < chats.length - 1) {
794
849
  printJsonEvent("chat_delay", {
795
850
  chatId: chat.chatId,
851
+ actorUsername,
796
852
  delayMs,
797
853
  })
798
854
  await sleep(delayMs)
@@ -1087,7 +1143,7 @@ export function createProgram(): Command {
1087
1143
  program
1088
1144
  .name("tg-sai")
1089
1145
  .description("Audit and manage Sai Telegram chat admin state")
1090
- .version("0.1.0")
1146
+ .version(packageJson.version, "--version", "output the version number")
1091
1147
  .helpOption("--help", "display help for command")
1092
1148
 
1093
1149
  program.action(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniquedivine/tg-sai",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "Sai Telegram admin audit and bootstrap CLI",