@moxxy/plugin-telegram 0.27.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.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/dist/approval.d.ts +37 -0
  3. package/dist/approval.d.ts.map +1 -0
  4. package/dist/approval.js +75 -0
  5. package/dist/approval.js.map +1 -0
  6. package/dist/channel/approval-prompt.d.ts +20 -0
  7. package/dist/channel/approval-prompt.d.ts.map +1 -0
  8. package/dist/channel/approval-prompt.js +34 -0
  9. package/dist/channel/approval-prompt.js.map +1 -0
  10. package/dist/channel/callback-handler.d.ts +32 -0
  11. package/dist/channel/callback-handler.d.ts.map +1 -0
  12. package/dist/channel/callback-handler.js +212 -0
  13. package/dist/channel/callback-handler.js.map +1 -0
  14. package/dist/channel/frame-pump.d.ts +58 -0
  15. package/dist/channel/frame-pump.d.ts.map +1 -0
  16. package/dist/channel/frame-pump.js +159 -0
  17. package/dist/channel/frame-pump.js.map +1 -0
  18. package/dist/channel/html.d.ts +24 -0
  19. package/dist/channel/html.d.ts.map +1 -0
  20. package/dist/channel/html.js +62 -0
  21. package/dist/channel/html.js.map +1 -0
  22. package/dist/channel/pairing-handler.d.ts +66 -0
  23. package/dist/channel/pairing-handler.d.ts.map +1 -0
  24. package/dist/channel/pairing-handler.js +147 -0
  25. package/dist/channel/pairing-handler.js.map +1 -0
  26. package/dist/channel/permission-prompt.d.ts +21 -0
  27. package/dist/channel/permission-prompt.d.ts.map +1 -0
  28. package/dist/channel/permission-prompt.js +36 -0
  29. package/dist/channel/permission-prompt.js.map +1 -0
  30. package/dist/channel/slash-handler.d.ts +47 -0
  31. package/dist/channel/slash-handler.d.ts.map +1 -0
  32. package/dist/channel/slash-handler.js +214 -0
  33. package/dist/channel/slash-handler.js.map +1 -0
  34. package/dist/channel/text-handler.d.ts +43 -0
  35. package/dist/channel/text-handler.d.ts.map +1 -0
  36. package/dist/channel/text-handler.js +116 -0
  37. package/dist/channel/text-handler.js.map +1 -0
  38. package/dist/channel/turn-runner.d.ts +40 -0
  39. package/dist/channel/turn-runner.d.ts.map +1 -0
  40. package/dist/channel/turn-runner.js +67 -0
  41. package/dist/channel/turn-runner.js.map +1 -0
  42. package/dist/channel/typing-indicator.d.ts +14 -0
  43. package/dist/channel/typing-indicator.d.ts.map +1 -0
  44. package/dist/channel/typing-indicator.js +29 -0
  45. package/dist/channel/typing-indicator.js.map +1 -0
  46. package/dist/channel/voice-handler.d.ts +42 -0
  47. package/dist/channel/voice-handler.d.ts.map +1 -0
  48. package/dist/channel/voice-handler.js +138 -0
  49. package/dist/channel/voice-handler.js.map +1 -0
  50. package/dist/channel.d.ts +118 -0
  51. package/dist/channel.d.ts.map +1 -0
  52. package/dist/channel.js +478 -0
  53. package/dist/channel.js.map +1 -0
  54. package/dist/channel.test-d.d.ts +2 -0
  55. package/dist/channel.test-d.d.ts.map +1 -0
  56. package/dist/channel.test-d.js +5 -0
  57. package/dist/channel.test-d.js.map +1 -0
  58. package/dist/format.d.ts +38 -0
  59. package/dist/format.d.ts.map +1 -0
  60. package/dist/format.js +237 -0
  61. package/dist/format.js.map +1 -0
  62. package/dist/index.d.ts +24 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +304 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/keys.d.ts +37 -0
  67. package/dist/keys.d.ts.map +1 -0
  68. package/dist/keys.js +42 -0
  69. package/dist/keys.js.map +1 -0
  70. package/dist/pair-flow.d.ts +18 -0
  71. package/dist/pair-flow.d.ts.map +1 -0
  72. package/dist/pair-flow.js +119 -0
  73. package/dist/pair-flow.js.map +1 -0
  74. package/dist/pairing.d.ts +90 -0
  75. package/dist/pairing.d.ts.map +1 -0
  76. package/dist/pairing.js +97 -0
  77. package/dist/pairing.js.map +1 -0
  78. package/dist/permission.d.ts +25 -0
  79. package/dist/permission.d.ts.map +1 -0
  80. package/dist/permission.js +49 -0
  81. package/dist/permission.js.map +1 -0
  82. package/dist/render.d.ts +68 -0
  83. package/dist/render.d.ts.map +1 -0
  84. package/dist/render.js +439 -0
  85. package/dist/render.js.map +1 -0
  86. package/dist/setup-wizard.d.ts +21 -0
  87. package/dist/setup-wizard.d.ts.map +1 -0
  88. package/dist/setup-wizard.js +140 -0
  89. package/dist/setup-wizard.js.map +1 -0
  90. package/package.json +91 -0
  91. package/src/approval.test.ts +103 -0
  92. package/src/approval.ts +86 -0
  93. package/src/channel/approval-prompt.ts +46 -0
  94. package/src/channel/callback-handler.test.ts +188 -0
  95. package/src/channel/callback-handler.ts +256 -0
  96. package/src/channel/frame-pump.ts +176 -0
  97. package/src/channel/html.test.ts +40 -0
  98. package/src/channel/html.ts +60 -0
  99. package/src/channel/pairing-handler.ts +181 -0
  100. package/src/channel/permission-prompt.ts +51 -0
  101. package/src/channel/slash-handler.ts +259 -0
  102. package/src/channel/text-handler.test.ts +222 -0
  103. package/src/channel/text-handler.ts +174 -0
  104. package/src/channel/turn-runner.ts +102 -0
  105. package/src/channel/typing-indicator.ts +31 -0
  106. package/src/channel/voice-handler.test.ts +328 -0
  107. package/src/channel/voice-handler.ts +198 -0
  108. package/src/channel/voice-reply.test.ts +223 -0
  109. package/src/channel.test-d.ts +7 -0
  110. package/src/channel.ts +564 -0
  111. package/src/format.test.ts +159 -0
  112. package/src/format.ts +257 -0
  113. package/src/index.ts +347 -0
  114. package/src/keys.ts +52 -0
  115. package/src/pair-flow.ts +131 -0
  116. package/src/pairing.test.ts +113 -0
  117. package/src/pairing.ts +184 -0
  118. package/src/permission.test.ts +56 -0
  119. package/src/permission.ts +61 -0
  120. package/src/render.test.ts +350 -0
  121. package/src/render.ts +522 -0
  122. package/src/setup-wizard.ts +178 -0
  123. package/src/subcommands.test.ts +153 -0
@@ -0,0 +1,153 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import * as os from 'node:os';
3
+ import * as path from 'node:path';
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import { VaultStore, createStaticKeySource, deriveKey, generateSalt } from '@moxxy/plugin-vault';
6
+ import type { ChannelDef } from '@moxxy/sdk';
7
+ import { buildTelegramPlugin } from './index.js';
8
+
9
+ let tmp: string;
10
+ let vault: VaultStore;
11
+ let telegramDef: ChannelDef;
12
+ let writeOut: string[];
13
+ let writeErr: string[];
14
+ let origStdoutWrite: typeof process.stdout.write;
15
+ let origStderrWrite: typeof process.stderr.write;
16
+
17
+ beforeEach(async () => {
18
+ tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'mox-tg-sub-'));
19
+ vault = new VaultStore({
20
+ filePath: path.join(tmp, 'vault.json'),
21
+ keySource: createStaticKeySource(deriveKey('test', generateSalt())),
22
+ });
23
+ const plugin = buildTelegramPlugin({ vault });
24
+ telegramDef = plugin.channels![0]!;
25
+ writeOut = [];
26
+ writeErr = [];
27
+ origStdoutWrite = process.stdout.write.bind(process.stdout);
28
+ origStderrWrite = process.stderr.write.bind(process.stderr);
29
+ process.stdout.write = ((chunk: string | Uint8Array) => {
30
+ writeOut.push(typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString());
31
+ return true;
32
+ }) as typeof process.stdout.write;
33
+ process.stderr.write = ((chunk: string | Uint8Array) => {
34
+ writeErr.push(typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString());
35
+ return true;
36
+ }) as typeof process.stderr.write;
37
+ });
38
+
39
+ afterEach(async () => {
40
+ await fs.rm(tmp, { recursive: true, force: true });
41
+ process.stdout.write = origStdoutWrite;
42
+ process.stderr.write = origStderrWrite;
43
+ });
44
+
45
+ function ctx(
46
+ overrides: {
47
+ startChannel?: () => Promise<number>;
48
+ session?: { setPermissionResolver: (r: unknown) => void };
49
+ } = {},
50
+ ) {
51
+ return {
52
+ deps: { cwd: tmp, vault, logger: undefined, options: {} },
53
+ args: { positional: [], flags: {} },
54
+ startChannel: overrides.startChannel ?? (async () => 0),
55
+ session: overrides.session ?? { setPermissionResolver: () => {} },
56
+ } as never;
57
+ }
58
+
59
+ describe('telegram channel subcommands (registered on ChannelDef)', () => {
60
+ it('exposes pair, unpair, status', () => {
61
+ expect(telegramDef.subcommands).toBeDefined();
62
+ expect(Object.keys(telegramDef.subcommands!)).toEqual(
63
+ expect.arrayContaining(['pair', 'unpair', 'status']),
64
+ );
65
+ });
66
+
67
+ it('`status` reports unconfigured vault state as JSON', async () => {
68
+ const code = await telegramDef.subcommands!.status!.run(ctx());
69
+ expect(code).toBe(0);
70
+ const parsed = JSON.parse(writeOut.join(''));
71
+ expect(parsed).toEqual({ tokenConfigured: false, authorizedChatId: null });
72
+ });
73
+
74
+ it('`status` surfaces stored token + authorized chat', async () => {
75
+ await vault.set('telegram_bot_token', '1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghi');
76
+ await vault.set('telegram_authorized_chat_id', '987654321');
77
+ const code = await telegramDef.subcommands!.status!.run(ctx());
78
+ expect(code).toBe(0);
79
+ const parsed = JSON.parse(writeOut.join(''));
80
+ expect(parsed).toEqual({ tokenConfigured: true, authorizedChatId: 987654321 });
81
+ });
82
+
83
+ it('`status` reports null (not NaN) for a corrupt stored chat id', async () => {
84
+ await vault.set('telegram_bot_token', '1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghi');
85
+ await vault.set('telegram_authorized_chat_id', 'not-a-number');
86
+ const code = await telegramDef.subcommands!.status!.run(ctx());
87
+ expect(code).toBe(0);
88
+ const parsed = JSON.parse(writeOut.join(''));
89
+ // A bare Number('not-a-number') would be NaN → serialized to null, masking
90
+ // the corruption; parseChatId makes the not-paired signal explicit.
91
+ expect(parsed).toEqual({ tokenConfigured: true, authorizedChatId: null });
92
+ });
93
+
94
+ it('`unpair` clears the authorized chat and reports', async () => {
95
+ await vault.set('telegram_authorized_chat_id', '111');
96
+ const code = await telegramDef.subcommands!.unpair!.run(ctx());
97
+ expect(code).toBe(0);
98
+ expect(writeOut.join('')).toContain('unpaired');
99
+ expect(await vault.get('telegram_authorized_chat_id')).toBeNull();
100
+ });
101
+
102
+ it('`unpair` is a no-op when nothing is paired', async () => {
103
+ const code = await telegramDef.subcommands!.unpair!.run(ctx());
104
+ expect(code).toBe(0);
105
+ expect(writeOut.join('')).toContain('no pairing was active');
106
+ });
107
+
108
+ it('`pair` drives the pairing flow on the session in an interactive TTY', async () => {
109
+ const originalIsTTY = process.stdin.isTTY;
110
+ Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true });
111
+ try {
112
+ const startChannel = vi.fn(async () => 0);
113
+ const setPermissionResolver = vi.fn();
114
+ // No token in the vault -> the channel throws before it can open
115
+ // a real polling connection, so the flow fails fast (no network).
116
+ // We only assert that `pair` now drives the in-process pairing
117
+ // flow (wires the session's permission resolver) instead of
118
+ // delegating to `startChannel`.
119
+ await expect(
120
+ telegramDef.subcommands!.pair!.run(ctx({ startChannel, session: { setPermissionResolver } })),
121
+ ).rejects.toThrow(/token/i);
122
+ expect(setPermissionResolver).toHaveBeenCalledTimes(1);
123
+ expect(startChannel).not.toHaveBeenCalled();
124
+ } finally {
125
+ Object.defineProperty(process.stdin, 'isTTY', { value: originalIsTTY, configurable: true });
126
+ }
127
+ });
128
+
129
+ it('`pair` refuses to start without a TTY (interactive-only flow)', async () => {
130
+ const originalIsTTY = process.stdin.isTTY;
131
+ Object.defineProperty(process.stdin, 'isTTY', { value: false, configurable: true });
132
+ try {
133
+ const startChannel = vi.fn(async () => 0);
134
+ const code = await telegramDef.subcommands!.pair!.run(ctx({ startChannel }));
135
+ expect(code).toBe(1);
136
+ expect(startChannel).not.toHaveBeenCalled();
137
+ expect(writeErr.join('')).toMatch(/TTY/);
138
+ } finally {
139
+ Object.defineProperty(process.stdin, 'isTTY', { value: originalIsTTY, configurable: true });
140
+ }
141
+ });
142
+
143
+ it('subcommands return 1 when vault is unavailable', async () => {
144
+ const badCtx = {
145
+ deps: { cwd: tmp, vault: undefined, logger: undefined, options: {} },
146
+ args: { positional: [], flags: {} },
147
+ startChannel: async () => 0,
148
+ };
149
+ const code = await telegramDef.subcommands!.status!.run(badCtx);
150
+ expect(code).toBe(1);
151
+ expect(writeErr.join('')).toContain('vault unavailable');
152
+ });
153
+ });