@uniquedivine/tg-sai 0.0.2 → 0.0.3

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 (3) hide show
  1. package/cfg.ts +52 -8
  2. package/main.ts +53 -9
  3. 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/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)
@@ -565,6 +589,7 @@ async function runSaiFind(options: SaiFindOptions): Promise<void> {
565
589
  async function applyTargetPlan(params: {
566
590
  client: Awaited<ReturnType<typeof createMtcuteClient>>
567
591
  chat: ChatInput
592
+ actorUsername: string
568
593
  targetUserId: number | string
569
594
  targetPeer: number | string
570
595
  rights: Record<string, boolean>
@@ -580,6 +605,7 @@ async function applyTargetPlan(params: {
580
605
  printJsonEvent("target_plan", {
581
606
  chatId: params.chat.chatId,
582
607
  title: params.chat.title,
608
+ actorUsername: params.actorUsername,
583
609
  targetUserId: params.targetUserId,
584
610
  actionPrefix: params.actionPrefix,
585
611
  run: params.run,
@@ -591,6 +617,7 @@ async function applyTargetPlan(params: {
591
617
  if (!beforePlan.actorCanApply) {
592
618
  printJsonEvent("target_skip", {
593
619
  chatId: params.chat.chatId,
620
+ actorUsername: params.actorUsername,
594
621
  actionPrefix: params.actionPrefix,
595
622
  reason: "actor_lacks_owner_or_add_admins_right",
596
623
  })
@@ -601,7 +628,7 @@ async function applyTargetPlan(params: {
601
628
  return
602
629
  }
603
630
 
604
- const deps = actionExecutorDeps()
631
+ const deps = actionExecutorDeps(actorBatchLogger(params.actorUsername))
605
632
  if (shouldRunAction(beforePlan.proposedActions, "add target user")) {
606
633
  await withFloodWaitRetry(
607
634
  {
@@ -647,6 +674,7 @@ async function maybeTransferOwner(params: {
647
674
  client: Awaited<ReturnType<typeof createMtcuteClient>>
648
675
  env: TelegramEnv
649
676
  chat: ChatInput
677
+ actorUsername: string
650
678
  targetUser: number | string
651
679
  enabled: boolean
652
680
  run: boolean
@@ -664,6 +692,7 @@ async function maybeTransferOwner(params: {
664
692
  if (!plan.actorCanTransferOwnership) {
665
693
  printJsonEvent("ownership_skip", {
666
694
  chatId: params.chat.chatId,
695
+ actorUsername: params.actorUsername,
667
696
  reason: "actor_not_owner",
668
697
  })
669
698
  return
@@ -671,6 +700,7 @@ async function maybeTransferOwner(params: {
671
700
  if (!plan.targetCanReceiveOwnership) {
672
701
  printJsonEvent("ownership_skip", {
673
702
  chatId: params.chat.chatId,
703
+ actorUsername: params.actorUsername,
674
704
  reason: "target_not_eligible",
675
705
  })
676
706
  return
@@ -678,6 +708,7 @@ async function maybeTransferOwner(params: {
678
708
  if (params.env.password === null) {
679
709
  printJsonEvent("ownership_skip", {
680
710
  chatId: params.chat.chatId,
711
+ actorUsername: params.actorUsername,
681
712
  reason: "missing_telegram_password",
682
713
  })
683
714
  return
@@ -685,6 +716,7 @@ async function maybeTransferOwner(params: {
685
716
  if (!params.run) {
686
717
  printJsonEvent("ownership_plan", {
687
718
  chatId: params.chat.chatId,
719
+ actorUsername: params.actorUsername,
688
720
  action: "transfer_owner",
689
721
  })
690
722
  return
@@ -701,7 +733,7 @@ async function maybeTransferOwner(params: {
701
733
  targetPeer: params.targetUser,
702
734
  password: params.env.password ?? "",
703
735
  }),
704
- actionExecutorDeps(),
736
+ actionExecutorDeps(actorBatchLogger(params.actorUsername)),
705
737
  )
706
738
  }
707
739
 
@@ -727,18 +759,23 @@ async function runSaiBootstrap(options: SaiBootstrapOptions): Promise<void> {
727
759
 
728
760
  await withClient(options.profile, async (client, env) => {
729
761
  activeJsonLogPath = logPath
762
+ const actor = normalizeUser(await client.getMe())
763
+ const actorUsername = actor.username ?? ""
764
+ const profileName = await selectedProfileName(options.profile)
730
765
  const ownershipTransferEnabled =
731
766
  options.transferOwner !== false && env.password !== null
732
767
  console.log(`wrote_run_log: ${logPath}`)
733
768
  printJsonEvent("run_start", {
734
769
  file: options.file,
735
770
  logPath,
771
+ profileName,
772
+ actor,
736
773
  chatCount: chats.length,
737
774
  run: options.run === true,
738
775
  transferOwner: ownershipTransferEnabled,
739
776
  passwordAvailable: env.password !== null,
740
777
  delayMs,
741
- targetUser: uniqueDivine.id,
778
+ targetUser: USERS.uniqueDivine.id,
742
779
  targetBot: saiBot.id,
743
780
  })
744
781
 
@@ -749,12 +786,14 @@ async function runSaiBootstrap(options: SaiBootstrapOptions): Promise<void> {
749
786
  total: chats.length,
750
787
  chatId: chat.chatId,
751
788
  title: chat.title,
789
+ actorUsername,
752
790
  })
753
791
 
754
792
  try {
755
793
  await applyTargetPlan({
756
794
  client,
757
795
  chat,
796
+ actorUsername,
758
797
  targetUserId: saiBot.id,
759
798
  targetPeer: saiBot.handle,
760
799
  rights: saiBotAdminRights,
@@ -764,8 +803,9 @@ async function runSaiBootstrap(options: SaiBootstrapOptions): Promise<void> {
764
803
  await applyTargetPlan({
765
804
  client,
766
805
  chat,
767
- targetUserId: uniqueDivine.id,
768
- targetPeer: uniqueDivine.handle,
806
+ actorUsername,
807
+ targetUserId: USERS.uniqueDivine.id,
808
+ targetPeer: USERS.uniqueDivine.handle,
769
809
  rights: ownerTargetAdminRights,
770
810
  actionPrefix: "owner_target",
771
811
  run: options.run === true,
@@ -774,16 +814,19 @@ async function runSaiBootstrap(options: SaiBootstrapOptions): Promise<void> {
774
814
  client,
775
815
  env,
776
816
  chat,
777
- targetUser: uniqueDivine.handle,
817
+ actorUsername,
818
+ targetUser: USERS.uniqueDivine.handle,
778
819
  enabled: ownershipTransferEnabled,
779
820
  run: options.run === true,
780
821
  })
781
822
  printJsonEvent("chat_complete", {
782
823
  chatId: chat.chatId,
824
+ actorUsername,
783
825
  })
784
826
  } catch (error) {
785
827
  printJsonEvent("chat_error", {
786
828
  chatId: chat.chatId,
829
+ actorUsername,
787
830
  errorName: error instanceof Error ? error.name : "UnknownError",
788
831
  errorMessage:
789
832
  error instanceof Error ? error.message : String(error),
@@ -793,6 +836,7 @@ async function runSaiBootstrap(options: SaiBootstrapOptions): Promise<void> {
793
836
  if (delayMs > 0 && i < chats.length - 1) {
794
837
  printJsonEvent("chat_delay", {
795
838
  chatId: chat.chatId,
839
+ actorUsername,
796
840
  delayMs,
797
841
  })
798
842
  await sleep(delayMs)
@@ -1087,7 +1131,7 @@ export function createProgram(): Command {
1087
1131
  program
1088
1132
  .name("tg-sai")
1089
1133
  .description("Audit and manage Sai Telegram chat admin state")
1090
- .version("0.1.0")
1134
+ .version(packageJson.version)
1091
1135
  .helpOption("--help", "display help for command")
1092
1136
 
1093
1137
  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.3",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "Sai Telegram admin audit and bootstrap CLI",