claude-remote-guard 1.0.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 (82) hide show
  1. package/README.md +433 -0
  2. package/dist/bin/cli.d.ts +3 -0
  3. package/dist/bin/cli.d.ts.map +1 -0
  4. package/dist/bin/cli.js +427 -0
  5. package/dist/bin/cli.js.map +1 -0
  6. package/dist/bin/hook.d.ts +3 -0
  7. package/dist/bin/hook.d.ts.map +1 -0
  8. package/dist/bin/hook.js +136 -0
  9. package/dist/bin/hook.js.map +1 -0
  10. package/dist/index.d.ts +6 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +6 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/lib/claude-settings.d.ts +11 -0
  15. package/dist/lib/claude-settings.d.ts.map +1 -0
  16. package/dist/lib/claude-settings.js +96 -0
  17. package/dist/lib/claude-settings.js.map +1 -0
  18. package/dist/lib/config.d.ts +47 -0
  19. package/dist/lib/config.d.ts.map +1 -0
  20. package/dist/lib/config.js +177 -0
  21. package/dist/lib/config.js.map +1 -0
  22. package/dist/lib/edge-function.d.ts +14 -0
  23. package/dist/lib/edge-function.d.ts.map +1 -0
  24. package/dist/lib/edge-function.js +521 -0
  25. package/dist/lib/edge-function.js.map +1 -0
  26. package/dist/lib/firebase.d.ts +27 -0
  27. package/dist/lib/firebase.d.ts.map +1 -0
  28. package/dist/lib/firebase.js +136 -0
  29. package/dist/lib/firebase.js.map +1 -0
  30. package/dist/lib/messenger/base.d.ts +6 -0
  31. package/dist/lib/messenger/base.d.ts.map +1 -0
  32. package/dist/lib/messenger/base.js +34 -0
  33. package/dist/lib/messenger/base.js.map +1 -0
  34. package/dist/lib/messenger/factory.d.ts +15 -0
  35. package/dist/lib/messenger/factory.d.ts.map +1 -0
  36. package/dist/lib/messenger/factory.js +37 -0
  37. package/dist/lib/messenger/factory.js.map +1 -0
  38. package/dist/lib/messenger/index.d.ts +7 -0
  39. package/dist/lib/messenger/index.d.ts.map +1 -0
  40. package/dist/lib/messenger/index.js +9 -0
  41. package/dist/lib/messenger/index.js.map +1 -0
  42. package/dist/lib/messenger/slack.d.ts +14 -0
  43. package/dist/lib/messenger/slack.d.ts.map +1 -0
  44. package/dist/lib/messenger/slack.js +169 -0
  45. package/dist/lib/messenger/slack.js.map +1 -0
  46. package/dist/lib/messenger/telegram.d.ts +15 -0
  47. package/dist/lib/messenger/telegram.d.ts.map +1 -0
  48. package/dist/lib/messenger/telegram.js +120 -0
  49. package/dist/lib/messenger/telegram.js.map +1 -0
  50. package/dist/lib/messenger/types.d.ts +21 -0
  51. package/dist/lib/messenger/types.d.ts.map +1 -0
  52. package/dist/lib/messenger/types.js +2 -0
  53. package/dist/lib/messenger/types.js.map +1 -0
  54. package/dist/lib/messenger/whatsapp.d.ts +16 -0
  55. package/dist/lib/messenger/whatsapp.d.ts.map +1 -0
  56. package/dist/lib/messenger/whatsapp.js +103 -0
  57. package/dist/lib/messenger/whatsapp.js.map +1 -0
  58. package/dist/lib/rules.d.ts +17 -0
  59. package/dist/lib/rules.d.ts.map +1 -0
  60. package/dist/lib/rules.js +138 -0
  61. package/dist/lib/rules.js.map +1 -0
  62. package/dist/lib/rules.test.d.ts +2 -0
  63. package/dist/lib/rules.test.d.ts.map +1 -0
  64. package/dist/lib/rules.test.js +144 -0
  65. package/dist/lib/rules.test.js.map +1 -0
  66. package/dist/lib/setup-instructions.d.ts +3 -0
  67. package/dist/lib/setup-instructions.d.ts.map +1 -0
  68. package/dist/lib/setup-instructions.js +55 -0
  69. package/dist/lib/setup-instructions.js.map +1 -0
  70. package/dist/lib/slack.d.ts +18 -0
  71. package/dist/lib/slack.d.ts.map +1 -0
  72. package/dist/lib/slack.js +21 -0
  73. package/dist/lib/slack.js.map +1 -0
  74. package/dist/lib/supabase.d.ts +33 -0
  75. package/dist/lib/supabase.d.ts.map +1 -0
  76. package/dist/lib/supabase.js +169 -0
  77. package/dist/lib/supabase.js.map +1 -0
  78. package/package.json +67 -0
  79. package/supabase/functions/slack-callback/index.ts +198 -0
  80. package/supabase/functions/telegram-callback/index.ts +209 -0
  81. package/supabase/functions/whatsapp-callback/index.ts +180 -0
  82. package/supabase/migrations/001_create_approval_requests.sql +91 -0
@@ -0,0 +1,96 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import * as os from 'node:os';
4
+ const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json');
5
+ const GUARD_HOOK = {
6
+ type: 'command',
7
+ command: 'claude-guard-hook',
8
+ matcher: 'Bash',
9
+ timeout: 310000, // 310 seconds (slightly more than the default 300s approval timeout)
10
+ };
11
+ function readClaudeSettings() {
12
+ if (!fs.existsSync(CLAUDE_SETTINGS_PATH)) {
13
+ return {};
14
+ }
15
+ try {
16
+ const content = fs.readFileSync(CLAUDE_SETTINGS_PATH, 'utf-8');
17
+ return JSON.parse(content);
18
+ }
19
+ catch {
20
+ return {};
21
+ }
22
+ }
23
+ function writeClaudeSettings(settings) {
24
+ const dir = path.dirname(CLAUDE_SETTINGS_PATH);
25
+ if (!fs.existsSync(dir)) {
26
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
27
+ }
28
+ fs.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2), {
29
+ encoding: 'utf-8',
30
+ mode: 0o600, // Owner read/write only
31
+ });
32
+ }
33
+ function isGuardHook(hook) {
34
+ return hook.command === 'claude-guard-hook' || hook.command.includes('claude-guard-hook');
35
+ }
36
+ export function isHookRegistered() {
37
+ const settings = readClaudeSettings();
38
+ if (!settings.hooks?.PreToolUse) {
39
+ return false;
40
+ }
41
+ return settings.hooks.PreToolUse.some(isGuardHook);
42
+ }
43
+ export function registerHook() {
44
+ try {
45
+ const settings = readClaudeSettings();
46
+ // Initialize hooks structure if needed
47
+ if (!settings.hooks) {
48
+ settings.hooks = {};
49
+ }
50
+ if (!settings.hooks.PreToolUse) {
51
+ settings.hooks.PreToolUse = [];
52
+ }
53
+ // Check if already registered
54
+ if (settings.hooks.PreToolUse.some(isGuardHook)) {
55
+ return { success: true, message: 'Hook is already registered' };
56
+ }
57
+ // Add the hook
58
+ settings.hooks.PreToolUse.push(GUARD_HOOK);
59
+ writeClaudeSettings(settings);
60
+ return { success: true, message: 'Hook registered successfully' };
61
+ }
62
+ catch (error) {
63
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
64
+ return { success: false, message: `Failed to register hook: ${errorMessage}` };
65
+ }
66
+ }
67
+ export function unregisterHook() {
68
+ try {
69
+ const settings = readClaudeSettings();
70
+ if (!settings.hooks?.PreToolUse) {
71
+ return { success: true, message: 'No hooks to remove' };
72
+ }
73
+ const originalLength = settings.hooks.PreToolUse.length;
74
+ settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter((hook) => !isGuardHook(hook));
75
+ if (settings.hooks.PreToolUse.length === originalLength) {
76
+ return { success: true, message: 'Hook was not registered' };
77
+ }
78
+ // Clean up empty arrays
79
+ if (settings.hooks.PreToolUse.length === 0) {
80
+ delete settings.hooks.PreToolUse;
81
+ }
82
+ if (Object.keys(settings.hooks).length === 0) {
83
+ delete settings.hooks;
84
+ }
85
+ writeClaudeSettings(settings);
86
+ return { success: true, message: 'Hook unregistered successfully' };
87
+ }
88
+ catch (error) {
89
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
90
+ return { success: false, message: `Failed to unregister hook: ${errorMessage}` };
91
+ }
92
+ }
93
+ export function getClaudeSettingsPath() {
94
+ return CLAUDE_SETTINGS_PATH;
95
+ }
96
+ //# sourceMappingURL=claude-settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-settings.js","sourceRoot":"","sources":["../../src/lib/claude-settings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import type { MessengerType } from './messenger/types.js';
2
+ import type { SlackConfig } from './messenger/slack.js';
3
+ import type { TelegramConfig } from './messenger/telegram.js';
4
+ import type { WhatsAppConfig } from './messenger/whatsapp.js';
5
+ export type { SlackConfig } from './messenger/slack.js';
6
+ export type { TelegramConfig } from './messenger/telegram.js';
7
+ export type { WhatsAppConfig } from './messenger/whatsapp.js';
8
+ export interface MessengerConfig {
9
+ type: MessengerType;
10
+ slack?: SlackConfig;
11
+ telegram?: TelegramConfig;
12
+ whatsapp?: WhatsAppConfig;
13
+ }
14
+ export interface SupabaseConfig {
15
+ url: string;
16
+ anonKey: string;
17
+ }
18
+ export interface RulesConfig {
19
+ timeoutSeconds: number;
20
+ defaultAction: 'allow' | 'deny';
21
+ customPatterns?: Array<{
22
+ pattern: string;
23
+ severity: 'low' | 'medium' | 'high' | 'critical';
24
+ reason: string;
25
+ }>;
26
+ whitelist?: string[];
27
+ }
28
+ export interface Config {
29
+ messenger: MessengerConfig;
30
+ supabase: SupabaseConfig;
31
+ rules: RulesConfig;
32
+ }
33
+ export interface LegacyConfig {
34
+ slack: SlackConfig;
35
+ supabase: SupabaseConfig;
36
+ rules: RulesConfig;
37
+ }
38
+ export declare function getConfigDir(): string;
39
+ export declare function getConfigPath(): string;
40
+ export declare function configExists(): boolean;
41
+ export declare function migrateConfig(legacy: LegacyConfig): Config;
42
+ export declare function loadConfig(): Config | null;
43
+ export declare function saveConfig(config: Config): void;
44
+ export declare function deleteConfig(): void;
45
+ export declare function expandPath(filePath: string): string;
46
+ export declare function getDefaultConfig(): Config;
47
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAG9D,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE9D,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,aAAa,CAAC;IACpB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,OAAO,GAAG,MAAM,CAAC;IAChC,cAAc,CAAC,EAAE,KAAK,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;QACjD,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC,CAAC;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,MAAM;IACrB,SAAS,EAAE,eAAe,CAAC;IAC3B,QAAQ,EAAE,cAAc,CAAC;IACzB,KAAK,EAAE,WAAW,CAAC;CACpB;AAGD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,EAAE,cAAc,CAAC;IACzB,KAAK,EAAE,WAAW,CAAC;CACpB;AAKD,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAGD,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAS1D;AAYD,wBAAgB,UAAU,IAAI,MAAM,GAAG,IAAI,CAsB1C;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAS/C;AAED,wBAAgB,YAAY,IAAI,IAAI,CAInC;AAgGD,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKnD;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAkBzC"}
@@ -0,0 +1,177 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import * as os from 'node:os';
4
+ const CONFIG_DIR = path.join(os.homedir(), '.claude-guard');
5
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
6
+ export function getConfigDir() {
7
+ return CONFIG_DIR;
8
+ }
9
+ export function getConfigPath() {
10
+ return CONFIG_FILE;
11
+ }
12
+ export function configExists() {
13
+ return fs.existsSync(CONFIG_FILE);
14
+ }
15
+ // Migrate legacy config to new format
16
+ export function migrateConfig(legacy) {
17
+ return {
18
+ messenger: {
19
+ type: 'slack',
20
+ slack: legacy.slack,
21
+ },
22
+ supabase: legacy.supabase,
23
+ rules: legacy.rules,
24
+ };
25
+ }
26
+ // Check if config is legacy format
27
+ function isLegacyConfig(config) {
28
+ if (typeof config !== 'object' || config === null) {
29
+ return false;
30
+ }
31
+ const c = config;
32
+ // Legacy format has 'slack' at top level, new format has 'messenger'
33
+ return 'slack' in c && !('messenger' in c);
34
+ }
35
+ export function loadConfig() {
36
+ if (!configExists()) {
37
+ return null;
38
+ }
39
+ try {
40
+ const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
41
+ const rawConfig = JSON.parse(content);
42
+ // Handle legacy config migration
43
+ if (isLegacyConfig(rawConfig)) {
44
+ const migratedConfig = migrateConfig(rawConfig);
45
+ // Auto-save migrated config
46
+ saveConfig(migratedConfig);
47
+ return migratedConfig;
48
+ }
49
+ const config = rawConfig;
50
+ return validateConfig(config) ? config : null;
51
+ }
52
+ catch {
53
+ return null;
54
+ }
55
+ }
56
+ export function saveConfig(config) {
57
+ if (!fs.existsSync(CONFIG_DIR)) {
58
+ fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
59
+ }
60
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), {
61
+ encoding: 'utf-8',
62
+ mode: 0o600,
63
+ });
64
+ }
65
+ export function deleteConfig() {
66
+ if (fs.existsSync(CONFIG_FILE)) {
67
+ fs.unlinkSync(CONFIG_FILE);
68
+ }
69
+ }
70
+ function validateMessengerConfig(messenger) {
71
+ if (typeof messenger !== 'object' || messenger === null) {
72
+ return false;
73
+ }
74
+ const m = messenger;
75
+ if (!['slack', 'telegram', 'whatsapp'].includes(m.type)) {
76
+ return false;
77
+ }
78
+ const type = m.type;
79
+ switch (type) {
80
+ case 'slack': {
81
+ const slack = m.slack;
82
+ if (!slack || typeof slack.webhookUrl !== 'string' || !slack.webhookUrl.startsWith('https://')) {
83
+ return false;
84
+ }
85
+ break;
86
+ }
87
+ case 'telegram': {
88
+ const telegram = m.telegram;
89
+ if (!telegram || typeof telegram.botToken !== 'string' || telegram.botToken.length === 0) {
90
+ return false;
91
+ }
92
+ if (typeof telegram.chatId !== 'string' || telegram.chatId.length === 0) {
93
+ return false;
94
+ }
95
+ break;
96
+ }
97
+ case 'whatsapp': {
98
+ const whatsapp = m.whatsapp;
99
+ if (!whatsapp) {
100
+ return false;
101
+ }
102
+ if (typeof whatsapp.accountSid !== 'string' || whatsapp.accountSid.length === 0) {
103
+ return false;
104
+ }
105
+ if (typeof whatsapp.authToken !== 'string' || whatsapp.authToken.length === 0) {
106
+ return false;
107
+ }
108
+ if (typeof whatsapp.fromNumber !== 'string' || !whatsapp.fromNumber.startsWith('whatsapp:')) {
109
+ return false;
110
+ }
111
+ if (typeof whatsapp.toNumber !== 'string' || !whatsapp.toNumber.startsWith('whatsapp:')) {
112
+ return false;
113
+ }
114
+ break;
115
+ }
116
+ }
117
+ return true;
118
+ }
119
+ function validateConfig(config) {
120
+ if (typeof config !== 'object' || config === null) {
121
+ return false;
122
+ }
123
+ const c = config;
124
+ // Validate messenger config
125
+ if (!validateMessengerConfig(c.messenger)) {
126
+ return false;
127
+ }
128
+ // Validate supabase config
129
+ if (typeof c.supabase !== 'object' || c.supabase === null) {
130
+ return false;
131
+ }
132
+ const supabase = c.supabase;
133
+ if (typeof supabase.url !== 'string' || !supabase.url.startsWith('https://')) {
134
+ return false;
135
+ }
136
+ if (typeof supabase.anonKey !== 'string' || supabase.anonKey.length === 0) {
137
+ return false;
138
+ }
139
+ // Validate rules config
140
+ if (typeof c.rules !== 'object' || c.rules === null) {
141
+ return false;
142
+ }
143
+ const rules = c.rules;
144
+ if (typeof rules.timeoutSeconds !== 'number' || rules.timeoutSeconds < 10) {
145
+ return false;
146
+ }
147
+ if (rules.defaultAction !== 'allow' && rules.defaultAction !== 'deny') {
148
+ return false;
149
+ }
150
+ return true;
151
+ }
152
+ export function expandPath(filePath) {
153
+ if (filePath.startsWith('~/')) {
154
+ return path.join(os.homedir(), filePath.slice(2));
155
+ }
156
+ return filePath;
157
+ }
158
+ export function getDefaultConfig() {
159
+ return {
160
+ messenger: {
161
+ type: 'slack',
162
+ slack: {
163
+ webhookUrl: '',
164
+ channelId: '',
165
+ },
166
+ },
167
+ supabase: {
168
+ url: '',
169
+ anonKey: '',
170
+ },
171
+ rules: {
172
+ timeoutSeconds: 300,
173
+ defaultAction: 'deny',
174
+ },
175
+ };
176
+ }
177
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { MessengerType } from './messenger/types.js';
2
+ export declare const SLACK_EDGE_FUNCTION_CODE = "// Supabase Edge Function for Slack Interactive Callbacks\n// Deploy: supabase functions deploy slack-callback\n//\n// Required environment variables:\n// - SLACK_SIGNING_SECRET: Your Slack app's signing secret\n// - SUPABASE_URL: Auto-provided by Supabase\n// - SUPABASE_SERVICE_ROLE_KEY: Auto-provided by Supabase\n\nimport { serve } from 'https://deno.land/std@0.168.0/http/server.ts';\nimport { createClient } from 'https://esm.sh/@supabase/supabase-js@2';\n\ninterface SlackAction {\n action_id: string;\n value: string;\n}\n\ninterface SlackUser {\n id: string;\n username: string;\n name: string;\n}\n\ninterface SlackPayload {\n type: string;\n user: SlackUser;\n actions: SlackAction[];\n response_url: string;\n}\n\n// HMAC-SHA256 signature verification for Slack requests\nasync function verifySlackSignature(\n body: string,\n timestamp: string,\n signature: string,\n signingSecret: string\n): Promise<boolean> {\n const encoder = new TextEncoder();\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(signingSecret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n );\n\n const baseString = `v0:${timestamp}:${body}`;\n const signatureBuffer = await crypto.subtle.sign('HMAC', key, encoder.encode(baseString));\n const computedSignature = 'v0=' + Array.from(new Uint8Array(signatureBuffer))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n\n // Timing-safe comparison\n if (computedSignature.length !== signature.length) {\n return false;\n }\n let result = 0;\n for (let i = 0; i < computedSignature.length; i++) {\n result |= computedSignature.charCodeAt(i) ^ signature.charCodeAt(i);\n }\n return result === 0;\n}\n\nserve(async (req: Request) => {\n if (req.method !== 'POST') {\n return new Response('Method not allowed', { status: 405 });\n }\n\n try {\n const signingSecret = Deno.env.get('SLACK_SIGNING_SECRET');\n if (!signingSecret) {\n console.error('Missing SLACK_SIGNING_SECRET environment variable');\n return new Response('Server configuration error', { status: 500 });\n }\n\n const slackSignature = req.headers.get('x-slack-signature');\n const slackTimestamp = req.headers.get('x-slack-request-timestamp');\n\n if (!slackSignature || !slackTimestamp) {\n console.error('Missing Slack signature headers');\n return new Response('Unauthorized', { status: 401 });\n }\n\n const currentTime = Math.floor(Date.now() / 1000);\n const requestTime = parseInt(slackTimestamp, 10);\n if (Math.abs(currentTime - requestTime) > 300) {\n console.error('Request timestamp too old');\n return new Response('Unauthorized', { status: 401 });\n }\n\n const bodyText = await req.text();\n const isValid = await verifySlackSignature(bodyText, slackTimestamp, slackSignature, signingSecret);\n if (!isValid) {\n console.error('Invalid Slack signature');\n return new Response('Unauthorized', { status: 401 });\n }\n\n const params = new URLSearchParams(bodyText);\n const payloadStr = params.get('payload');\n if (!payloadStr) {\n return new Response('Invalid payload', { status: 400 });\n }\n\n const payload: SlackPayload = JSON.parse(payloadStr);\n if (payload.type !== 'block_actions') {\n return new Response('Unsupported interaction type', { status: 400 });\n }\n\n const action = payload.actions[0];\n if (!action) {\n return new Response('No action found', { status: 400 });\n }\n\n const { action_id, value: requestId } = action;\n const resolvedBy = payload.user.username || payload.user.name || payload.user.id;\n\n let status: 'approved' | 'rejected';\n if (action_id === 'approve_command') {\n status = 'approved';\n } else if (action_id === 'reject_command') {\n status = 'rejected';\n } else {\n return new Response('Unknown action', { status: 400 });\n }\n\n const supabaseUrl = Deno.env.get('SUPABASE_URL');\n const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY');\n if (!supabaseUrl || !supabaseServiceKey) {\n console.error('Missing Supabase environment variables');\n return new Response('Server configuration error', { status: 500 });\n }\n\n const supabase = createClient(supabaseUrl, supabaseServiceKey);\n const { data, error } = await supabase\n .from('approval_requests')\n .update({\n status,\n resolved_at: new Date().toISOString(),\n resolved_by: resolvedBy,\n })\n .eq('id', requestId)\n .eq('status', 'pending')\n .select('id');\n\n if (error) {\n console.error('Failed to update request:', error);\n return new Response('Failed to update request', { status: 500 });\n }\n\n if (!data || data.length === 0) {\n console.error('Request not found or already resolved:', requestId);\n return new Response('Request not found or already resolved', { status: 404 });\n }\n\n const responseMessage =\n status === 'approved'\n ? `:white_check_mark: *Approved* by @${resolvedBy}`\n : `:x: *Rejected* by @${resolvedBy}`;\n\n if (payload.response_url) {\n await fetch(payload.response_url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n replace_original: false,\n text: responseMessage,\n }),\n });\n }\n\n return new Response('OK', { status: 200, headers: { 'Content-Type': 'text/plain' } });\n } catch (error) {\n console.error('Error processing request:', error);\n return new Response('Internal server error', { status: 500 });\n }\n});\n";
3
+ export declare const TELEGRAM_EDGE_FUNCTION_CODE = "// Supabase Edge Function for Telegram Bot Callbacks\n// Deploy: supabase functions deploy telegram-callback\n//\n// Required environment variables:\n// - TELEGRAM_BOT_TOKEN: Your Telegram bot token\n// - TELEGRAM_WEBHOOK_SECRET: Secret token for webhook verification (set via setWebhook API)\n// - SUPABASE_URL: Auto-provided by Supabase\n// - SUPABASE_SERVICE_ROLE_KEY: Auto-provided by Supabase\n\nimport { serve } from 'https://deno.land/std@0.168.0/http/server.ts';\nimport { createClient } from 'https://esm.sh/@supabase/supabase-js@2';\n\n// Timing-safe string comparison to prevent timing attacks\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false;\n }\n let result = 0;\n for (let i = 0; i < a.length; i++) {\n result |= a.charCodeAt(i) ^ b.charCodeAt(i);\n }\n return result === 0;\n}\n\ninterface TelegramUser {\n id: number;\n first_name: string;\n last_name?: string;\n username?: string;\n}\n\ninterface TelegramMessage {\n message_id: number;\n chat: { id: number };\n}\n\ninterface CallbackQuery {\n id: string;\n from: TelegramUser;\n message?: TelegramMessage;\n data?: string;\n}\n\ninterface TelegramUpdate {\n update_id: number;\n callback_query?: CallbackQuery;\n}\n\nserve(async (req: Request) => {\n if (req.method !== 'POST') {\n return new Response('Method not allowed', { status: 405 });\n }\n\n try {\n const botToken = Deno.env.get('TELEGRAM_BOT_TOKEN');\n if (!botToken) {\n console.error('Missing TELEGRAM_BOT_TOKEN environment variable');\n return new Response('Server configuration error', { status: 500 });\n }\n\n // Verify webhook secret token\n const webhookSecret = Deno.env.get('TELEGRAM_WEBHOOK_SECRET');\n if (!webhookSecret) {\n console.error('Missing TELEGRAM_WEBHOOK_SECRET environment variable');\n return new Response('Server configuration error', { status: 500 });\n }\n\n const headerToken = req.headers.get('X-Telegram-Bot-Api-Secret-Token');\n if (!headerToken || !timingSafeEqual(headerToken, webhookSecret)) {\n console.error('Invalid or missing Telegram webhook secret token');\n return new Response('Unauthorized', { status: 401 });\n }\n\n const update: TelegramUpdate = await req.json();\n if (!update.callback_query) {\n return new Response('OK', { status: 200 });\n }\n\n const callbackQuery = update.callback_query;\n const callbackData = callbackQuery.data;\n if (!callbackData) {\n return new Response('No callback data', { status: 400 });\n }\n\n const [action, requestId] = callbackData.split(':');\n if (!action || !requestId || (action !== 'approve' && action !== 'reject')) {\n return new Response('Invalid callback data format', { status: 400 });\n }\n\n const status = action === 'approve' ? 'approved' : 'rejected';\n const resolvedBy = callbackQuery.from.username ||\n `${callbackQuery.from.first_name}${callbackQuery.from.last_name ? ' ' + callbackQuery.from.last_name : ''}` ||\n String(callbackQuery.from.id);\n\n const supabaseUrl = Deno.env.get('SUPABASE_URL');\n const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY');\n if (!supabaseUrl || !supabaseServiceKey) {\n console.error('Missing Supabase environment variables');\n return new Response('Server configuration error', { status: 500 });\n }\n\n const supabase = createClient(supabaseUrl, supabaseServiceKey);\n const { data, error } = await supabase\n .from('approval_requests')\n .update({\n status,\n resolved_at: new Date().toISOString(),\n resolved_by: resolvedBy,\n })\n .eq('id', requestId)\n .eq('status', 'pending')\n .select('id');\n\n if (error) {\n console.error('Failed to update request:', error);\n await answerCallbackQuery(botToken, callbackQuery.id, '\u274C Failed to update request');\n return new Response('Failed to update request', { status: 500 });\n }\n\n if (!data || data.length === 0) {\n await answerCallbackQuery(botToken, callbackQuery.id, '\u26A0\uFE0F Request not found or already resolved');\n return new Response('OK', { status: 200 });\n }\n\n const emoji = status === 'approved' ? '\u2705' : '\u274C';\n const actionText = status === 'approved' ? 'Approved' : 'Rejected';\n await answerCallbackQuery(botToken, callbackQuery.id, `${emoji} ${actionText}`);\n\n if (callbackQuery.message) {\n await editMessageReplyMarkup(botToken, callbackQuery.message.chat.id, callbackQuery.message.message_id, `\\n\\n${emoji} *${actionText}* by @${resolvedBy}`);\n }\n\n return new Response('OK', { status: 200 });\n } catch (error) {\n console.error('Error processing request:', error);\n return new Response('Internal server error', { status: 500 });\n }\n});\n\nasync function answerCallbackQuery(botToken: string, callbackQueryId: string, text: string): Promise<void> {\n await fetch(`https://api.telegram.org/bot${botToken}/answerCallbackQuery`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ callback_query_id: callbackQueryId, text, show_alert: false }),\n });\n}\n\nasync function editMessageReplyMarkup(botToken: string, chatId: number, messageId: number, appendText: string): Promise<void> {\n await fetch(`https://api.telegram.org/bot${botToken}/editMessageReplyMarkup`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ chat_id: chatId, message_id: messageId, reply_markup: { inline_keyboard: [] } }),\n });\n await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n chat_id: chatId,\n reply_to_message_id: messageId,\n text: appendText.replace(/[_*\\[\\]()~`>#+\\-=|{}.!\\\\]/g, '\\\\$&'),\n parse_mode: 'MarkdownV2',\n }),\n });\n}\n";
4
+ export declare const WHATSAPP_EDGE_FUNCTION_CODE = "// Supabase Edge Function for WhatsApp (Twilio) Callbacks\n// Deploy: supabase functions deploy whatsapp-callback\n//\n// Required environment variables:\n// - TWILIO_AUTH_TOKEN: Your Twilio auth token for signature verification\n// - SUPABASE_URL: Auto-provided by Supabase\n// - SUPABASE_SERVICE_ROLE_KEY: Auto-provided by Supabase\n\nimport { serve } from 'https://deno.land/std@0.168.0/http/server.ts';\nimport { createClient } from 'https://esm.sh/@supabase/supabase-js@2';\n\n// Timing-safe string comparison to prevent timing attacks\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false;\n }\n let result = 0;\n for (let i = 0; i < a.length; i++) {\n result |= a.charCodeAt(i) ^ b.charCodeAt(i);\n }\n return result === 0;\n}\n\nasync function verifyTwilioSignature(\n authToken: string,\n signature: string,\n url: string,\n params: Record<string, string>\n): Promise<boolean> {\n const sortedParams = Object.keys(params).sort().map(key => `${key}${params[key]}`).join('');\n const data = url + sortedParams;\n const encoder = new TextEncoder();\n const key = await crypto.subtle.importKey('raw', encoder.encode(authToken), { name: 'HMAC', hash: 'SHA-1' }, false, ['sign']);\n const signatureBuffer = await crypto.subtle.sign('HMAC', key, encoder.encode(data));\n const computedSignature = btoa(String.fromCharCode(...new Uint8Array(signatureBuffer)));\n return timingSafeEqual(signature, computedSignature);\n}\n\nserve(async (req: Request) => {\n if (req.method !== 'POST') {\n return new Response('Method not allowed', { status: 405 });\n }\n\n try {\n const authToken = Deno.env.get('TWILIO_AUTH_TOKEN');\n if (!authToken) {\n console.error('Missing TWILIO_AUTH_TOKEN environment variable');\n return new Response('Server configuration error', { status: 500 });\n }\n\n const formData = await req.formData();\n const params: Record<string, string> = {};\n formData.forEach((value, key) => { params[key] = value.toString(); });\n\n const twilioSignature = req.headers.get('X-Twilio-Signature');\n if (!twilioSignature) {\n console.error('Missing Twilio signature header');\n return new Response('Unauthorized', { status: 401 });\n }\n\n const isValid = await verifyTwilioSignature(authToken, twilioSignature, req.url, params);\n if (!isValid) {\n console.error('Invalid Twilio signature');\n return new Response('Unauthorized', { status: 401 });\n }\n\n const body = params['Body']?.trim();\n const from = params['From'];\n if (!body) {\n return twimlResponse('No message body received.');\n }\n\n const match = body.match(/^(APPROVE|REJECT)\\s+([a-f0-9-]+)$/i);\n if (!match) {\n return twimlResponse('Invalid format. Use:\\nAPPROVE <request-id>\\nor\\nREJECT <request-id>');\n }\n\n const [, action, requestId] = match;\n const status = action.toUpperCase() === 'APPROVE' ? 'approved' : 'rejected';\n const resolvedBy = from?.replace('whatsapp:', '') || 'unknown';\n\n const supabaseUrl = Deno.env.get('SUPABASE_URL');\n const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY');\n if (!supabaseUrl || !supabaseServiceKey) {\n console.error('Missing Supabase environment variables');\n return twimlResponse('Server configuration error.');\n }\n\n const supabase = createClient(supabaseUrl, supabaseServiceKey);\n const { data, error } = await supabase\n .from('approval_requests')\n .update({\n status,\n resolved_at: new Date().toISOString(),\n resolved_by: resolvedBy,\n })\n .eq('id', requestId)\n .eq('status', 'pending')\n .select('id');\n\n if (error) {\n console.error('Failed to update request:', error);\n return twimlResponse('Failed to update request. Please try again.');\n }\n\n if (!data || data.length === 0) {\n return twimlResponse('Request not found or already resolved.');\n }\n\n const emoji = status === 'approved' ? '\u2705' : '\u274C';\n const actionText = status === 'approved' ? 'approved' : 'rejected';\n return twimlResponse(`${emoji} Request ${requestId.substring(0, 8)}... has been ${actionText}.`);\n } catch (error) {\n console.error('Error processing request:', error);\n return twimlResponse('Internal server error.');\n }\n});\n\nfunction twimlResponse(message: string): Response {\n const twiml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Response>\n <Message>${escapeXml(message)}</Message>\n</Response>`;\n return new Response(twiml, { status: 200, headers: { 'Content-Type': 'application/xml' } });\n}\n\nfunction escapeXml(text: string): string {\n return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;').replace(/'/g, '&apos;');\n}\n";
5
+ export declare const EDGE_FUNCTION_CODE = "// Supabase Edge Function for Slack Interactive Callbacks\n// Deploy: supabase functions deploy slack-callback\n//\n// Required environment variables:\n// - SLACK_SIGNING_SECRET: Your Slack app's signing secret\n// - SUPABASE_URL: Auto-provided by Supabase\n// - SUPABASE_SERVICE_ROLE_KEY: Auto-provided by Supabase\n\nimport { serve } from 'https://deno.land/std@0.168.0/http/server.ts';\nimport { createClient } from 'https://esm.sh/@supabase/supabase-js@2';\n\ninterface SlackAction {\n action_id: string;\n value: string;\n}\n\ninterface SlackUser {\n id: string;\n username: string;\n name: string;\n}\n\ninterface SlackPayload {\n type: string;\n user: SlackUser;\n actions: SlackAction[];\n response_url: string;\n}\n\n// HMAC-SHA256 signature verification for Slack requests\nasync function verifySlackSignature(\n body: string,\n timestamp: string,\n signature: string,\n signingSecret: string\n): Promise<boolean> {\n const encoder = new TextEncoder();\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(signingSecret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n );\n\n const baseString = `v0:${timestamp}:${body}`;\n const signatureBuffer = await crypto.subtle.sign('HMAC', key, encoder.encode(baseString));\n const computedSignature = 'v0=' + Array.from(new Uint8Array(signatureBuffer))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n\n // Timing-safe comparison\n if (computedSignature.length !== signature.length) {\n return false;\n }\n let result = 0;\n for (let i = 0; i < computedSignature.length; i++) {\n result |= computedSignature.charCodeAt(i) ^ signature.charCodeAt(i);\n }\n return result === 0;\n}\n\nserve(async (req: Request) => {\n if (req.method !== 'POST') {\n return new Response('Method not allowed', { status: 405 });\n }\n\n try {\n const signingSecret = Deno.env.get('SLACK_SIGNING_SECRET');\n if (!signingSecret) {\n console.error('Missing SLACK_SIGNING_SECRET environment variable');\n return new Response('Server configuration error', { status: 500 });\n }\n\n const slackSignature = req.headers.get('x-slack-signature');\n const slackTimestamp = req.headers.get('x-slack-request-timestamp');\n\n if (!slackSignature || !slackTimestamp) {\n console.error('Missing Slack signature headers');\n return new Response('Unauthorized', { status: 401 });\n }\n\n const currentTime = Math.floor(Date.now() / 1000);\n const requestTime = parseInt(slackTimestamp, 10);\n if (Math.abs(currentTime - requestTime) > 300) {\n console.error('Request timestamp too old');\n return new Response('Unauthorized', { status: 401 });\n }\n\n const bodyText = await req.text();\n const isValid = await verifySlackSignature(bodyText, slackTimestamp, slackSignature, signingSecret);\n if (!isValid) {\n console.error('Invalid Slack signature');\n return new Response('Unauthorized', { status: 401 });\n }\n\n const params = new URLSearchParams(bodyText);\n const payloadStr = params.get('payload');\n if (!payloadStr) {\n return new Response('Invalid payload', { status: 400 });\n }\n\n const payload: SlackPayload = JSON.parse(payloadStr);\n if (payload.type !== 'block_actions') {\n return new Response('Unsupported interaction type', { status: 400 });\n }\n\n const action = payload.actions[0];\n if (!action) {\n return new Response('No action found', { status: 400 });\n }\n\n const { action_id, value: requestId } = action;\n const resolvedBy = payload.user.username || payload.user.name || payload.user.id;\n\n let status: 'approved' | 'rejected';\n if (action_id === 'approve_command') {\n status = 'approved';\n } else if (action_id === 'reject_command') {\n status = 'rejected';\n } else {\n return new Response('Unknown action', { status: 400 });\n }\n\n const supabaseUrl = Deno.env.get('SUPABASE_URL');\n const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY');\n if (!supabaseUrl || !supabaseServiceKey) {\n console.error('Missing Supabase environment variables');\n return new Response('Server configuration error', { status: 500 });\n }\n\n const supabase = createClient(supabaseUrl, supabaseServiceKey);\n const { data, error } = await supabase\n .from('approval_requests')\n .update({\n status,\n resolved_at: new Date().toISOString(),\n resolved_by: resolvedBy,\n })\n .eq('id', requestId)\n .eq('status', 'pending')\n .select('id');\n\n if (error) {\n console.error('Failed to update request:', error);\n return new Response('Failed to update request', { status: 500 });\n }\n\n if (!data || data.length === 0) {\n console.error('Request not found or already resolved:', requestId);\n return new Response('Request not found or already resolved', { status: 404 });\n }\n\n const responseMessage =\n status === 'approved'\n ? `:white_check_mark: *Approved* by @${resolvedBy}`\n : `:x: *Rejected* by @${resolvedBy}`;\n\n if (payload.response_url) {\n await fetch(payload.response_url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n replace_original: false,\n text: responseMessage,\n }),\n });\n }\n\n return new Response('OK', { status: 200, headers: { 'Content-Type': 'text/plain' } });\n } catch (error) {\n console.error('Error processing request:', error);\n return new Response('Internal server error', { status: 500 });\n }\n});\n";
6
+ export interface CreateEdgeFunctionResult {
7
+ success: boolean;
8
+ path?: string;
9
+ error?: string;
10
+ }
11
+ export declare function createEdgeFunctionFiles(targetDir: string, messengerType?: MessengerType): CreateEdgeFunctionResult;
12
+ export declare function getEdgeFunctionEnvVars(messengerType: MessengerType): string[];
13
+ export declare function getEdgeFunctionName(messengerType: MessengerType): string;
14
+ //# sourceMappingURL=edge-function.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edge-function.d.ts","sourceRoot":"","sources":["../../src/lib/edge-function.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAG1D,eAAO,MAAM,wBAAwB,mlLA+KpC,CAAC;AAGF,eAAO,MAAM,2BAA2B,44LAoKvC,CAAC;AAGF,eAAO,MAAM,2BAA2B,owJAiIvC,CAAC;AAGF,eAAO,MAAM,kBAAkB,mlLAA2B,CAAC;AAE3D,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA0BD,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,EACjB,aAAa,GAAE,aAAuB,GACrC,wBAAwB,CAoB1B;AAED,wBAAgB,sBAAsB,CAAC,aAAa,EAAE,aAAa,GAAG,MAAM,EAAE,CAE7E;AAED,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,aAAa,GAAG,MAAM,CAExE"}