aegis-bridge 2.2.2

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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +244 -0
  3. package/dashboard/dist/assets/index-CijFoeRu.css +32 -0
  4. package/dashboard/dist/assets/index-QtT4j0ht.js +262 -0
  5. package/dashboard/dist/index.html +14 -0
  6. package/dist/auth.d.ts +76 -0
  7. package/dist/auth.js +219 -0
  8. package/dist/channels/index.d.ts +8 -0
  9. package/dist/channels/index.js +9 -0
  10. package/dist/channels/manager.d.ts +39 -0
  11. package/dist/channels/manager.js +101 -0
  12. package/dist/channels/telegram-style.d.ts +118 -0
  13. package/dist/channels/telegram-style.js +203 -0
  14. package/dist/channels/telegram.d.ts +76 -0
  15. package/dist/channels/telegram.js +1396 -0
  16. package/dist/channels/types.d.ts +77 -0
  17. package/dist/channels/types.js +9 -0
  18. package/dist/channels/webhook.d.ts +58 -0
  19. package/dist/channels/webhook.js +162 -0
  20. package/dist/cli.d.ts +8 -0
  21. package/dist/cli.js +223 -0
  22. package/dist/config.d.ts +60 -0
  23. package/dist/config.js +188 -0
  24. package/dist/dashboard/assets/index-CijFoeRu.css +32 -0
  25. package/dist/dashboard/assets/index-QtT4j0ht.js +262 -0
  26. package/dist/dashboard/index.html +14 -0
  27. package/dist/events.d.ts +86 -0
  28. package/dist/events.js +258 -0
  29. package/dist/hook-settings.d.ts +67 -0
  30. package/dist/hook-settings.js +138 -0
  31. package/dist/hook.d.ts +18 -0
  32. package/dist/hook.js +199 -0
  33. package/dist/hooks.d.ts +32 -0
  34. package/dist/hooks.js +279 -0
  35. package/dist/jsonl-watcher.d.ts +57 -0
  36. package/dist/jsonl-watcher.js +159 -0
  37. package/dist/mcp-server.d.ts +60 -0
  38. package/dist/mcp-server.js +788 -0
  39. package/dist/metrics.d.ts +104 -0
  40. package/dist/metrics.js +226 -0
  41. package/dist/monitor.d.ts +84 -0
  42. package/dist/monitor.js +553 -0
  43. package/dist/permission-guard.d.ts +51 -0
  44. package/dist/permission-guard.js +197 -0
  45. package/dist/pipeline.d.ts +84 -0
  46. package/dist/pipeline.js +218 -0
  47. package/dist/screenshot.d.ts +26 -0
  48. package/dist/screenshot.js +57 -0
  49. package/dist/server.d.ts +10 -0
  50. package/dist/server.js +1577 -0
  51. package/dist/session.d.ts +297 -0
  52. package/dist/session.js +1275 -0
  53. package/dist/sse-limiter.d.ts +47 -0
  54. package/dist/sse-limiter.js +62 -0
  55. package/dist/sse-writer.d.ts +31 -0
  56. package/dist/sse-writer.js +95 -0
  57. package/dist/ssrf.d.ts +57 -0
  58. package/dist/ssrf.js +169 -0
  59. package/dist/swarm-monitor.d.ts +114 -0
  60. package/dist/swarm-monitor.js +267 -0
  61. package/dist/terminal-parser.d.ts +16 -0
  62. package/dist/terminal-parser.js +343 -0
  63. package/dist/tmux.d.ts +161 -0
  64. package/dist/tmux.js +725 -0
  65. package/dist/transcript.d.ts +47 -0
  66. package/dist/transcript.js +244 -0
  67. package/dist/validation.d.ts +222 -0
  68. package/dist/validation.js +268 -0
  69. package/dist/ws-terminal.d.ts +32 -0
  70. package/dist/ws-terminal.js +297 -0
  71. package/package.json +71 -0
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="dark">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Aegis Dashboard</title>
7
+ <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🛡️</text></svg>" />
8
+ <script type="module" crossorigin src="/dashboard/assets/index-QtT4j0ht.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/dashboard/assets/index-CijFoeRu.css">
10
+ </head>
11
+ <body class="bg-[#0a0a0f] text-gray-200 antialiased">
12
+ <div id="root"></div>
13
+ </body>
14
+ </html>
package/dist/auth.d.ts ADDED
@@ -0,0 +1,76 @@
1
+ /**
2
+ * auth.ts — API key management and authentication middleware.
3
+ *
4
+ * Issue #39: Multi-key auth with rate limiting.
5
+ * Keys are hashed with SHA-256 (no bcrypt dependency needed).
6
+ * Backward compatible with single authToken from config.
7
+ */
8
+ export interface ApiKey {
9
+ id: string;
10
+ name: string;
11
+ hash: string;
12
+ createdAt: number;
13
+ lastUsedAt: number;
14
+ rateLimit: number;
15
+ }
16
+ export interface ApiKeyStore {
17
+ keys: ApiKey[];
18
+ }
19
+ export declare class AuthManager {
20
+ private keysFile;
21
+ private store;
22
+ private rateLimits;
23
+ private masterToken;
24
+ /** #297: Short-lived SSE tokens. Keyed by token string for O(1) lookup. */
25
+ private sseTokens;
26
+ /** Track how many SSE tokens each bearer key has outstanding. */
27
+ private sseTokenCounts;
28
+ /** #414: Mutex to prevent concurrent SSE token generation from exceeding per-key limits. */
29
+ private sseMutex;
30
+ constructor(keysFile: string, masterToken?: string);
31
+ /** Load keys from disk. */
32
+ load(): Promise<void>;
33
+ /** Save keys to disk. */
34
+ save(): Promise<void>;
35
+ /** Create a new API key. Returns the plaintext key (only shown once). */
36
+ createKey(name: string, rateLimit?: number): Promise<{
37
+ id: string;
38
+ key: string;
39
+ name: string;
40
+ }>;
41
+ /** List keys (without hashes). */
42
+ listKeys(): Array<Omit<ApiKey, 'hash'>>;
43
+ /** Revoke a key by ID. */
44
+ revokeKey(id: string): Promise<boolean>;
45
+ /**
46
+ * Validate a bearer token.
47
+ * Returns { valid, keyId, rateLimited } or null if no auth configured.
48
+ */
49
+ validate(token: string): {
50
+ valid: boolean;
51
+ keyId: string | null;
52
+ rateLimited: boolean;
53
+ };
54
+ /** Hash a key with SHA-256. */
55
+ static hashKey(key: string): string;
56
+ /** Check if auth is enabled (master token or any keys). */
57
+ get authEnabled(): boolean;
58
+ /**
59
+ * Generate a short-lived, single-use SSE token.
60
+ * The caller must already be authenticated (validated via bearer token).
61
+ * Returns the token string and its expiry timestamp.
62
+ * #414: Async with mutex to prevent concurrent calls from exceeding per-key limits.
63
+ */
64
+ generateSSEToken(keyId: string): Promise<{
65
+ token: string;
66
+ expiresAt: number;
67
+ }>;
68
+ /**
69
+ * Validate and consume a short-lived SSE token.
70
+ * Returns true if valid (and marks it as used), false otherwise.
71
+ * Also cleans up expired tokens as a side effect.
72
+ */
73
+ validateSSEToken(token: string): boolean;
74
+ /** Remove expired SSE tokens and recount per-key outstanding. */
75
+ private cleanExpiredSSETokens;
76
+ }
package/dist/auth.js ADDED
@@ -0,0 +1,219 @@
1
+ /**
2
+ * auth.ts — API key management and authentication middleware.
3
+ *
4
+ * Issue #39: Multi-key auth with rate limiting.
5
+ * Keys are hashed with SHA-256 (no bcrypt dependency needed).
6
+ * Backward compatible with single authToken from config.
7
+ */
8
+ import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
9
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
10
+ import { authStoreSchema } from './validation.js';
11
+ import { existsSync } from 'node:fs';
12
+ import { dirname } from 'node:path';
13
+ /** Default SSE token lifetime: 60 seconds. */
14
+ const SSE_TOKEN_TTL_MS = 60_000;
15
+ /** Max SSE tokens per bearer token to prevent abuse. */
16
+ const SSE_TOKEN_MAX_PER_KEY = 5;
17
+ export class AuthManager {
18
+ keysFile;
19
+ store = { keys: [] };
20
+ rateLimits = new Map();
21
+ masterToken;
22
+ /** #297: Short-lived SSE tokens. Keyed by token string for O(1) lookup. */
23
+ sseTokens = new Map();
24
+ /** Track how many SSE tokens each bearer key has outstanding. */
25
+ sseTokenCounts = new Map();
26
+ /** #414: Mutex to prevent concurrent SSE token generation from exceeding per-key limits. */
27
+ sseMutex = Promise.resolve();
28
+ constructor(keysFile, masterToken = '') {
29
+ this.keysFile = keysFile;
30
+ this.masterToken = masterToken;
31
+ }
32
+ /** Load keys from disk. */
33
+ async load() {
34
+ if (existsSync(this.keysFile)) {
35
+ try {
36
+ const raw = await readFile(this.keysFile, 'utf-8');
37
+ const parsed = authStoreSchema.safeParse(JSON.parse(raw));
38
+ if (parsed.success) {
39
+ this.store = parsed.data;
40
+ }
41
+ }
42
+ catch { /* corrupted or unreadable keys file — start fresh */
43
+ this.store = { keys: [] };
44
+ }
45
+ }
46
+ }
47
+ /** Save keys to disk. */
48
+ async save() {
49
+ const dir = dirname(this.keysFile);
50
+ if (!existsSync(dir)) {
51
+ await mkdir(dir, { recursive: true });
52
+ }
53
+ await writeFile(this.keysFile, JSON.stringify(this.store, null, 2));
54
+ }
55
+ /** Create a new API key. Returns the plaintext key (only shown once). */
56
+ async createKey(name, rateLimit = 100) {
57
+ const id = randomBytes(8).toString('hex');
58
+ const key = `aegis_${randomBytes(32).toString('hex')}`;
59
+ const hash = AuthManager.hashKey(key);
60
+ const apiKey = {
61
+ id,
62
+ name,
63
+ hash,
64
+ createdAt: Date.now(),
65
+ lastUsedAt: 0,
66
+ rateLimit,
67
+ };
68
+ this.store.keys.push(apiKey);
69
+ await this.save();
70
+ return { id, key, name };
71
+ }
72
+ /** List keys (without hashes). */
73
+ listKeys() {
74
+ return this.store.keys.map(({ hash: _, ...rest }) => rest);
75
+ }
76
+ /** Revoke a key by ID. */
77
+ async revokeKey(id) {
78
+ const idx = this.store.keys.findIndex(k => k.id === id);
79
+ if (idx === -1)
80
+ return false;
81
+ this.store.keys.splice(idx, 1);
82
+ this.rateLimits.delete(id);
83
+ await this.save();
84
+ return true;
85
+ }
86
+ /**
87
+ * Validate a bearer token.
88
+ * Returns { valid, keyId, rateLimited } or null if no auth configured.
89
+ */
90
+ validate(token) {
91
+ // No auth configured and no keys → allow all
92
+ if (!this.masterToken && this.store.keys.length === 0) {
93
+ return { valid: true, keyId: null, rateLimited: false };
94
+ }
95
+ // Check master token (backward compat) — timing-safe comparison (#402)
96
+ if (this.masterToken && token.length === this.masterToken.length
97
+ && timingSafeEqual(Buffer.from(token), Buffer.from(this.masterToken))) {
98
+ return { valid: true, keyId: 'master', rateLimited: false };
99
+ }
100
+ // Check API keys
101
+ const hash = AuthManager.hashKey(token);
102
+ const key = this.store.keys.find(k => k.hash === hash);
103
+ if (!key) {
104
+ return { valid: false, keyId: null, rateLimited: false };
105
+ }
106
+ // Update last used
107
+ key.lastUsedAt = Date.now();
108
+ // Rate limiting
109
+ const bucket = this.rateLimits.get(key.id) || { count: 0, windowStart: Date.now() };
110
+ const now = Date.now();
111
+ const windowMs = 60_000; // 1 minute
112
+ if (now - bucket.windowStart > windowMs) {
113
+ // New window
114
+ bucket.count = 1;
115
+ bucket.windowStart = now;
116
+ }
117
+ else {
118
+ bucket.count++;
119
+ }
120
+ this.rateLimits.set(key.id, bucket);
121
+ if (bucket.count > key.rateLimit) {
122
+ return { valid: true, keyId: key.id, rateLimited: true };
123
+ }
124
+ return { valid: true, keyId: key.id, rateLimited: false };
125
+ }
126
+ /** Hash a key with SHA-256. */
127
+ static hashKey(key) {
128
+ return createHash('sha256').update(key).digest('hex');
129
+ }
130
+ /** Check if auth is enabled (master token or any keys). */
131
+ get authEnabled() {
132
+ return !!this.masterToken || this.store.keys.length > 0;
133
+ }
134
+ // ── SSE Token Management (Issue #297) ────────────────────────
135
+ /**
136
+ * Generate a short-lived, single-use SSE token.
137
+ * The caller must already be authenticated (validated via bearer token).
138
+ * Returns the token string and its expiry timestamp.
139
+ * #414: Async with mutex to prevent concurrent calls from exceeding per-key limits.
140
+ */
141
+ async generateSSEToken(keyId) {
142
+ // Acquire mutex — chain onto the previous operation
143
+ let release = () => { };
144
+ const lock = new Promise((resolve) => { release = resolve; });
145
+ const previous = this.sseMutex;
146
+ this.sseMutex = lock;
147
+ // #509: await + try/finally together so release() fires even if previous rejects
148
+ try {
149
+ await previous;
150
+ // Cleanup expired tokens first
151
+ this.cleanExpiredSSETokens();
152
+ // Enforce per-key limit
153
+ const current = this.sseTokenCounts.get(keyId) ?? 0;
154
+ if (current >= SSE_TOKEN_MAX_PER_KEY) {
155
+ throw new Error(`SSE token limit reached (${SSE_TOKEN_MAX_PER_KEY} outstanding)`);
156
+ }
157
+ const token = `sse_${randomBytes(32).toString('hex')}`;
158
+ const expiresAt = Date.now() + SSE_TOKEN_TTL_MS;
159
+ this.sseTokens.set(token, { token, expiresAt, used: false, keyId });
160
+ this.sseTokenCounts.set(keyId, current + 1);
161
+ return { token, expiresAt };
162
+ }
163
+ finally {
164
+ release();
165
+ }
166
+ }
167
+ /**
168
+ * Validate and consume a short-lived SSE token.
169
+ * Returns true if valid (and marks it as used), false otherwise.
170
+ * Also cleans up expired tokens as a side effect.
171
+ */
172
+ validateSSEToken(token) {
173
+ const entry = this.sseTokens.get(token);
174
+ if (!entry)
175
+ return false;
176
+ // Already used
177
+ if (entry.used) {
178
+ this.sseTokens.delete(token);
179
+ return false;
180
+ }
181
+ // Expired
182
+ if (Date.now() > entry.expiresAt) {
183
+ this.sseTokens.delete(token);
184
+ return false;
185
+ }
186
+ // Valid — consume it
187
+ entry.used = true;
188
+ const keyId = entry.keyId;
189
+ this.sseTokens.delete(token);
190
+ // #357: Decrement outstanding count so generateSSEToken doesn't over-limit
191
+ const count = this.sseTokenCounts.get(keyId);
192
+ if (count !== undefined) {
193
+ if (count <= 1) {
194
+ this.sseTokenCounts.delete(keyId);
195
+ }
196
+ else {
197
+ this.sseTokenCounts.set(keyId, count - 1);
198
+ }
199
+ }
200
+ return true;
201
+ }
202
+ /** Remove expired SSE tokens and recount per-key outstanding. */
203
+ cleanExpiredSSETokens() {
204
+ const now = Date.now();
205
+ // Remove expired
206
+ for (const [key, entry] of this.sseTokens) {
207
+ if (now > entry.expiresAt) {
208
+ this.sseTokens.delete(key);
209
+ }
210
+ }
211
+ // Rebuild counts from surviving tokens
212
+ this.sseTokenCounts.clear();
213
+ for (const entry of this.sseTokens.values()) {
214
+ const count = this.sseTokenCounts.get(entry.keyId) ?? 0;
215
+ this.sseTokenCounts.set(entry.keyId, count + 1);
216
+ }
217
+ }
218
+ }
219
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * channels/index.ts — Re-exports for the channel plugin system.
3
+ */
4
+ export type { Channel, SessionEvent, SessionEventPayload, InboundCommand, InboundHandler, } from './types.js';
5
+ export { ChannelManager } from './manager.js';
6
+ export { TelegramChannel, type TelegramChannelConfig } from './telegram.js';
7
+ export { WebhookChannel, type WebhookChannelConfig, type WebhookEndpoint, type DeadLetterEntry } from './webhook.js';
8
+ export { quickUpdate, quickUpdateCode, taskComplete, alert, yesNo, decision, progress, esc, bold, code, italic, statusEmoji, type StyledMessage, type InlineButton, type StatusEmoji, type TaskCompleteData, type AlertData, type AlertButtons, type DecisionOption, type ProgressStep, } from './telegram-style.js';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * channels/index.ts — Re-exports for the channel plugin system.
3
+ */
4
+ export { ChannelManager } from './manager.js';
5
+ export { TelegramChannel } from './telegram.js';
6
+ export { WebhookChannel } from './webhook.js';
7
+ // Telegram Style Guide — 6 standard message types
8
+ export { quickUpdate, quickUpdateCode, taskComplete, alert, yesNo, decision, progress, esc, bold, code, italic, statusEmoji, } from './telegram-style.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,39 @@
1
+ /**
2
+ * channels/manager.ts — Routes events to all registered channels.
3
+ *
4
+ * The bridge calls ChannelManager methods. The manager fans out
5
+ * to every registered channel, swallowing per-channel errors so
6
+ * one broken channel never kills the bridge.
7
+ */
8
+ import type { Channel, SessionEventPayload, InboundHandler } from './types.js';
9
+ export declare class ChannelManager {
10
+ private channels;
11
+ private inboundHandler;
12
+ private health;
13
+ /** Consecutive failures before disabling a channel. */
14
+ static readonly FAILURE_THRESHOLD = 5;
15
+ /** Cooldown period in ms when a channel is disabled (5 min). */
16
+ static readonly COOLDOWN_MS: number;
17
+ /** Register a channel. Must be called before init(). */
18
+ register(channel: Channel): void;
19
+ /** Initialize all channels. Pass the inbound handler for bidirectional channels. */
20
+ init(onInbound: InboundHandler): Promise<void>;
21
+ /** Shut down all channels. */
22
+ destroy(): Promise<void>;
23
+ /** Fan out a session-created event. */
24
+ sessionCreated(payload: SessionEventPayload): Promise<void>;
25
+ /** Fan out a session-ended event. */
26
+ sessionEnded(payload: SessionEventPayload): Promise<void>;
27
+ /** Fan out a message event. */
28
+ message(payload: SessionEventPayload): Promise<void>;
29
+ /** Fan out a status change event. */
30
+ statusChange(payload: SessionEventPayload): Promise<void>;
31
+ /** Fan out a swarm teammate event. */
32
+ swarmEvent(payload: SessionEventPayload): Promise<void>;
33
+ /** How many channels are registered. */
34
+ get count(): number;
35
+ /** Get all registered channels (for wiring optional dependencies). */
36
+ getChannels(): readonly Channel[];
37
+ /** Fan out to channels, respecting filters, swallowing errors. */
38
+ private fanOut;
39
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * channels/manager.ts — Routes events to all registered channels.
3
+ *
4
+ * The bridge calls ChannelManager methods. The manager fans out
5
+ * to every registered channel, swallowing per-channel errors so
6
+ * one broken channel never kills the bridge.
7
+ */
8
+ export class ChannelManager {
9
+ channels = [];
10
+ inboundHandler = null;
11
+ health = new Map();
12
+ /** Consecutive failures before disabling a channel. */
13
+ static FAILURE_THRESHOLD = 5;
14
+ /** Cooldown period in ms when a channel is disabled (5 min). */
15
+ static COOLDOWN_MS = 5 * 60 * 1000;
16
+ /** Register a channel. Must be called before init(). */
17
+ register(channel) {
18
+ this.channels.push(channel);
19
+ }
20
+ /** Initialize all channels. Pass the inbound handler for bidirectional channels. */
21
+ async init(onInbound) {
22
+ this.inboundHandler = onInbound;
23
+ for (const ch of this.channels) {
24
+ try {
25
+ await ch.init?.(onInbound);
26
+ console.log(`Channel initialized: ${ch.name}`);
27
+ }
28
+ catch (e) {
29
+ console.error(`Channel ${ch.name} failed to init:`, e);
30
+ }
31
+ }
32
+ }
33
+ /** Shut down all channels. */
34
+ async destroy() {
35
+ for (const ch of this.channels) {
36
+ try {
37
+ await ch.destroy?.();
38
+ }
39
+ catch (e) {
40
+ console.error(`Channel ${ch.name} failed to destroy:`, e);
41
+ }
42
+ }
43
+ }
44
+ /** Fan out a session-created event. */
45
+ async sessionCreated(payload) {
46
+ await this.fanOut(payload, ch => ch.onSessionCreated?.(payload));
47
+ }
48
+ /** Fan out a session-ended event. */
49
+ async sessionEnded(payload) {
50
+ await this.fanOut(payload, ch => ch.onSessionEnded?.(payload));
51
+ }
52
+ /** Fan out a message event. */
53
+ async message(payload) {
54
+ await this.fanOut(payload, ch => ch.onMessage?.(payload));
55
+ }
56
+ /** Fan out a status change event. */
57
+ async statusChange(payload) {
58
+ await this.fanOut(payload, ch => ch.onStatusChange?.(payload));
59
+ }
60
+ /** Fan out a swarm teammate event. */
61
+ async swarmEvent(payload) {
62
+ await this.fanOut(payload, ch => ch.onStatusChange?.(payload));
63
+ }
64
+ /** How many channels are registered. */
65
+ get count() {
66
+ return this.channels.length;
67
+ }
68
+ /** Get all registered channels (for wiring optional dependencies). */
69
+ getChannels() {
70
+ return this.channels;
71
+ }
72
+ /** Fan out to channels, respecting filters, swallowing errors. */
73
+ async fanOut(payload, call) {
74
+ const promises = this.channels.map(async (ch) => {
75
+ // Circuit breaker: skip disabled channels during cooldown
76
+ const health = this.health.get(ch.name);
77
+ if (health && Date.now() < health.disabledUntil)
78
+ return;
79
+ try {
80
+ // Check filter
81
+ if (ch.filter && !ch.filter(payload.event))
82
+ return;
83
+ await call(ch);
84
+ // Success — reset failure count (channel may have been in cooldown)
85
+ this.health.set(ch.name, { failCount: 0, disabledUntil: 0 });
86
+ }
87
+ catch (e) {
88
+ console.error(`Channel ${ch.name} error on ${payload.event}:`, e);
89
+ const h = this.health.get(ch.name) ?? { failCount: 0, disabledUntil: 0 };
90
+ h.failCount++;
91
+ if (h.failCount >= ChannelManager.FAILURE_THRESHOLD) {
92
+ h.disabledUntil = Date.now() + ChannelManager.COOLDOWN_MS;
93
+ console.warn(`Channel ${ch.name} disabled after ${h.failCount} consecutive failures, cooldown until ${new Date(h.disabledUntil).toISOString()}`);
94
+ }
95
+ this.health.set(ch.name, h);
96
+ }
97
+ });
98
+ await Promise.allSettled(promises);
99
+ }
100
+ }
101
+ //# sourceMappingURL=manager.js.map
@@ -0,0 +1,118 @@
1
+ /**
2
+ * channels/telegram-style.ts — Telegram Message Style Guide
3
+ *
4
+ * 6 standard message types for clean, consistent Telegram UX.
5
+ * Rule: readable in 2 seconds. Max 2 button rows. Always an escape hatch.
6
+ *
7
+ * Types:
8
+ * ① quickUpdate — one-liner (70% of messages)
9
+ * ② taskComplete — post-merge quality gate card
10
+ * ③ alert — error/crash requiring action
11
+ * ④ yesNo — binary question
12
+ * ⑤ decision — technical choice with context + escape hatch
13
+ * ⑥ progress — pipeline/deploy with ASCII progress bar
14
+ */
15
+ export declare function esc(text: string): string;
16
+ export declare function bold(text: string): string;
17
+ export declare function code(text: string): string;
18
+ export declare function italic(text: string): string;
19
+ /** Telegram inline keyboard button. */
20
+ export interface InlineButton {
21
+ text: string;
22
+ callback_data: string;
23
+ }
24
+ /** Styled message ready to send via Telegram API. */
25
+ export interface StyledMessage {
26
+ text: string;
27
+ parse_mode: 'HTML';
28
+ reply_markup?: {
29
+ inline_keyboard: InlineButton[][];
30
+ };
31
+ }
32
+ export type StatusEmoji = '🟢' | '✅' | '⚠️' | '🔴' | '❌' | '🔄' | '🔨' | '🚀';
33
+ export declare function statusEmoji(status: string): StatusEmoji;
34
+ /**
35
+ * One-liner status update. 70% of all messages.
36
+ * No buttons, no separators. Emoji + text + optional data.
37
+ */
38
+ export declare function quickUpdate(emoji: StatusEmoji | string, message: string): StyledMessage;
39
+ /**
40
+ * Quick update with inline code for technical data.
41
+ * e.g. quickUpdateCode('🟢', 'CI verde su', 'main', '— 150/150 test ✅')
42
+ */
43
+ export declare function quickUpdateCode(emoji: StatusEmoji | string, prefix: string, codeText: string, suffix?: string): StyledMessage;
44
+ export interface TaskCompleteData {
45
+ /** Issue/task reference, e.g. "issue-7" */
46
+ taskRef: string;
47
+ /** Short description */
48
+ title: string;
49
+ /** Duration string, e.g. "28 min" */
50
+ duration: string;
51
+ /** Branch name */
52
+ branch: string;
53
+ /** Quality gate results: [label, passed][] */
54
+ checks: Array<[string, boolean]>;
55
+ /** Optional PR URL */
56
+ prUrl?: string;
57
+ }
58
+ /**
59
+ * Post-merge/completion card with quality gate.
60
+ * 3 lines max + 1 row of buttons.
61
+ */
62
+ export declare function taskComplete(data: TaskCompleteData, buttons?: {
63
+ merge?: string;
64
+ review?: string;
65
+ close?: string;
66
+ }): StyledMessage;
67
+ export interface AlertData {
68
+ /** Short title, e.g. "Session crash" */
69
+ title: string;
70
+ /** Session/resource id, e.g. "sess-4a7b" */
71
+ resourceId: string;
72
+ /** Technical details (max 3 lines, shown in monospace) */
73
+ details: string;
74
+ }
75
+ export interface AlertButtons {
76
+ restart?: string;
77
+ log?: string;
78
+ ignore?: string;
79
+ }
80
+ /**
81
+ * Error/crash alert requiring action.
82
+ * Monospace block for technical details + 1 row of buttons.
83
+ */
84
+ export declare function alert(data: AlertData, buttons?: AlertButtons): StyledMessage;
85
+ /**
86
+ * Binary question. 2 buttons, zero ambiguity.
87
+ * The "no" label should describe what happens if declined.
88
+ */
89
+ export declare function yesNo(question: string, yesLabel: string, noLabel: string, yesCallback: string, noCallback: string): StyledMessage;
90
+ export interface DecisionOption {
91
+ emoji: string;
92
+ label: string;
93
+ description: string;
94
+ callback: string;
95
+ }
96
+ /**
97
+ * Technical decision with context + escape hatch.
98
+ * The ONLY type allowed 2 rows of buttons.
99
+ * Max 3 options + always "Decidi tu" / "Parliamone".
100
+ */
101
+ export declare function decision(question: string, options: DecisionOption[], escapeCallbacks?: {
102
+ decideTu?: string;
103
+ parliamone?: string;
104
+ }): StyledMessage;
105
+ export interface ProgressStep {
106
+ label: string;
107
+ /** Duration string if done, e.g. "2.1s" */
108
+ duration?: string;
109
+ done: boolean;
110
+ }
111
+ /**
112
+ * Progress/pipeline card with ASCII bar.
113
+ * Updates via editMessageText — never send a new message for refresh.
114
+ */
115
+ export declare function progress(title: string, steps: ProgressStep[], percent: number, buttons?: {
116
+ pause?: string;
117
+ cancel?: string;
118
+ }): StyledMessage;