archer-wizard 0.1.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 (59) hide show
  1. package/.env.example +6 -0
  2. package/README.md +140 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +77 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/lib/ascii.d.ts +14 -0
  8. package/dist/lib/ascii.d.ts.map +1 -0
  9. package/dist/lib/ascii.js +88 -0
  10. package/dist/lib/ascii.js.map +1 -0
  11. package/dist/lib/supabase.d.ts +7 -0
  12. package/dist/lib/supabase.d.ts.map +1 -0
  13. package/dist/lib/supabase.js +54 -0
  14. package/dist/lib/supabase.js.map +1 -0
  15. package/dist/lib/webhook.d.ts +4 -0
  16. package/dist/lib/webhook.d.ts.map +1 -0
  17. package/dist/lib/webhook.js +53 -0
  18. package/dist/lib/webhook.js.map +1 -0
  19. package/dist/tools/watch.d.ts +35 -0
  20. package/dist/tools/watch.d.ts.map +1 -0
  21. package/dist/tools/watch.js +178 -0
  22. package/dist/tools/watch.js.map +1 -0
  23. package/dist/types/index.d.ts +84 -0
  24. package/dist/types/index.d.ts.map +1 -0
  25. package/dist/types/index.js +24 -0
  26. package/dist/types/index.js.map +1 -0
  27. package/dist/wizard/detector.d.ts +5 -0
  28. package/dist/wizard/detector.d.ts.map +1 -0
  29. package/dist/wizard/detector.js +125 -0
  30. package/dist/wizard/detector.js.map +1 -0
  31. package/dist/wizard/index.d.ts +2 -0
  32. package/dist/wizard/index.d.ts.map +1 -0
  33. package/dist/wizard/index.js +50 -0
  34. package/dist/wizard/index.js.map +1 -0
  35. package/dist/wizard/injector.d.ts +3 -0
  36. package/dist/wizard/injector.d.ts.map +1 -0
  37. package/dist/wizard/injector.js +91 -0
  38. package/dist/wizard/injector.js.map +1 -0
  39. package/dist/wizard/rules.d.ts +3 -0
  40. package/dist/wizard/rules.d.ts.map +1 -0
  41. package/dist/wizard/rules.js +94 -0
  42. package/dist/wizard/rules.js.map +1 -0
  43. package/dist/wizard/scanner.d.ts +7 -0
  44. package/dist/wizard/scanner.d.ts.map +1 -0
  45. package/dist/wizard/scanner.js +201 -0
  46. package/dist/wizard/scanner.js.map +1 -0
  47. package/package.json +28 -0
  48. package/src/index.ts +90 -0
  49. package/src/lib/ascii.ts +109 -0
  50. package/src/lib/supabase.ts +78 -0
  51. package/src/lib/webhook.ts +69 -0
  52. package/src/tools/watch.ts +223 -0
  53. package/src/types/index.ts +120 -0
  54. package/src/wizard/detector.ts +151 -0
  55. package/src/wizard/index.ts +69 -0
  56. package/src/wizard/injector.ts +115 -0
  57. package/src/wizard/rules.ts +112 -0
  58. package/src/wizard/scanner.ts +250 -0
  59. package/tsconfig.json +20 -0
@@ -0,0 +1,94 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { logSuccess, logError } from '../lib/ascii.js';
4
+ import { getRulesPath } from './detector.js';
5
+ // ─── Rule Content ───────────────────────────────────────────
6
+ const RULE_CONTENT = `
7
+ # Archer — Event Intelligence for AI Agents
8
+
9
+ Archer is connected to this project's Supabase instance.
10
+
11
+ ## Available Tool: archer.watch
12
+
13
+ You can watch real-time events from Supabase using the \`archer.watch\` MCP tool.
14
+
15
+ ### Parameters
16
+
17
+ - **source**: always \`"supabase"\`
18
+ - **event**: one of \`"auth.signup"\`, \`"table.insert"\`, \`"table.update"\`, \`"table.delete"\`
19
+ - **table**: the table name (required for table events)
20
+ - **condition**: optional filter like \`"email ends with @gmail.com"\`
21
+ - **webhookUrl**: URL to receive POST notifications
22
+
23
+ ### Examples
24
+
25
+ 1. Watch for new user signups:
26
+ \`\`\`
27
+ archer.watch({ source: "supabase", event: "auth.signup", webhookUrl: "https://hooks.example.com/signups" })
28
+ \`\`\`
29
+
30
+ 2. Watch for inserts to the orders table:
31
+ \`\`\`
32
+ archer.watch({ source: "supabase", event: "table.insert", table: "orders", webhookUrl: "https://hooks.example.com/orders" })
33
+ \`\`\`
34
+
35
+ 3. Watch with a condition:
36
+ \`\`\`
37
+ archer.watch({ source: "supabase", event: "table.insert", table: "users", condition: "email ends with @company.com", webhookUrl: "https://hooks.example.com/vip" })
38
+ \`\`\`
39
+
40
+ When the user asks to "watch" or "monitor" something in their database, use this tool.
41
+ `.trim();
42
+ // ─── Archer Block Markers ───────────────────────────────────
43
+ const BLOCK_START = '<!-- archer:start -->';
44
+ const BLOCK_END = '<!-- archer:end -->';
45
+ function wrapInBlock(content) {
46
+ return `${BLOCK_START}\n${content}\n${BLOCK_END}`;
47
+ }
48
+ // ─── Replace or Append Block ────────────────────────────────
49
+ function insertContent(existingContent, newBlock) {
50
+ const startIdx = existingContent.indexOf(BLOCK_START);
51
+ const endIdx = existingContent.indexOf(BLOCK_END);
52
+ if (startIdx !== -1 && endIdx !== -1) {
53
+ // Replace existing block
54
+ return (existingContent.slice(0, startIdx) +
55
+ newBlock +
56
+ existingContent.slice(endIdx + BLOCK_END.length));
57
+ }
58
+ // Append to end
59
+ const separator = existingContent.endsWith('\n') ? '\n' : '\n\n';
60
+ return existingContent + separator + newBlock + '\n';
61
+ }
62
+ // ─── Write Rules to Agent ───────────────────────────────────
63
+ function writeRulesForAgent(agent, cwd) {
64
+ try {
65
+ const rulesPath = getRulesPath(agent.name, cwd);
66
+ const dir = path.dirname(rulesPath);
67
+ const wrappedContent = wrapInBlock(RULE_CONTENT);
68
+ // Ensure directory exists
69
+ if (!fs.existsSync(dir)) {
70
+ fs.mkdirSync(dir, { recursive: true });
71
+ }
72
+ // Read existing content or start fresh
73
+ let existingContent = '';
74
+ if (fs.existsSync(rulesPath)) {
75
+ existingContent = fs.readFileSync(rulesPath, 'utf-8');
76
+ }
77
+ const finalContent = insertContent(existingContent, wrappedContent);
78
+ fs.writeFileSync(rulesPath, finalContent, 'utf-8');
79
+ logSuccess(`wrote rules to ${agent.name} → ${rulesPath}`);
80
+ return true;
81
+ }
82
+ catch (err) {
83
+ const message = err instanceof Error ? err.message : String(err);
84
+ logError(`failed to write rules for ${agent.name}: ${message}`);
85
+ return false;
86
+ }
87
+ }
88
+ // ─── Main Rules Injector ────────────────────────────────────
89
+ export function injectRules(agents, cwd) {
90
+ for (const agent of agents) {
91
+ writeRulesForAgent(agent, cwd);
92
+ }
93
+ }
94
+ //# sourceMappingURL=rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.js","sourceRoot":"","sources":["../../src/wizard/rules.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { ScanResult } from '../types/index.js';
2
+ export declare function scanProject(cwd: string): Promise<ScanResult>;
3
+ export declare function promptForMissing(scan: ScanResult): Promise<{
4
+ supabaseUrl: string;
5
+ serviceRoleKey: string;
6
+ }>;
7
+ //# sourceMappingURL=scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/wizard/scanner.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAa,MAAM,mBAAmB,CAAC;AAgI/D,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CA8DlE;AAID,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,UAAU,GACf,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,CAAC,CAgD1D"}
@@ -0,0 +1,201 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import * as clack from '@clack/prompts';
4
+ import { z } from 'zod';
5
+ import { logSuccess, logError, maskCredential } from '../lib/ascii.js';
6
+ // ─── Env File Priority ──────────────────────────────────────
7
+ const ENV_FILES = [
8
+ '.env.local',
9
+ '.env',
10
+ '.env.development',
11
+ '.env.production',
12
+ ];
13
+ // ─── Key Aliases ────────────────────────────────────────────
14
+ const SUPABASE_URL_ALIASES = [
15
+ 'SUPABASE_URL',
16
+ 'NEXT_PUBLIC_SUPABASE_URL',
17
+ 'VITE_SUPABASE_URL',
18
+ ];
19
+ const SERVICE_ROLE_KEY_ALIASES = [
20
+ 'SUPABASE_SERVICE_ROLE_KEY',
21
+ 'SUPABASE_SERVICE_KEY',
22
+ ];
23
+ const ANON_KEY_ALIASES = [
24
+ 'SUPABASE_ANON_KEY',
25
+ 'NEXT_PUBLIC_SUPABASE_ANON_KEY',
26
+ 'VITE_SUPABASE_ANON_KEY',
27
+ ];
28
+ // ─── Validation Schemas ─────────────────────────────────────
29
+ const SupabaseUrlSchema = z.string().url().startsWith('https://', {
30
+ message: 'SUPABASE_URL must start with https://',
31
+ });
32
+ const ServiceRoleKeySchema = z.string().min(1, {
33
+ message: 'SUPABASE_SERVICE_ROLE_KEY must be a non-empty string',
34
+ });
35
+ // ─── Parse Env File ─────────────────────────────────────────
36
+ function parseEnvFile(filePath) {
37
+ const vars = new Map();
38
+ try {
39
+ const content = fs.readFileSync(filePath, 'utf-8');
40
+ const lines = content.split('\n');
41
+ for (const rawLine of lines) {
42
+ const line = rawLine.trim();
43
+ if (!line || line.startsWith('#'))
44
+ continue;
45
+ const eqIndex = line.indexOf('=');
46
+ if (eqIndex === -1)
47
+ continue;
48
+ const key = line.slice(0, eqIndex).trim();
49
+ let value = line.slice(eqIndex + 1).trim();
50
+ // Strip surrounding quotes
51
+ if ((value.startsWith('"') && value.endsWith('"')) ||
52
+ (value.startsWith("'") && value.endsWith("'"))) {
53
+ value = value.slice(1, -1);
54
+ }
55
+ if (key && value) {
56
+ vars.set(key, value);
57
+ }
58
+ }
59
+ }
60
+ catch {
61
+ // File doesn't exist or can't be read — skip silently
62
+ }
63
+ return vars;
64
+ }
65
+ // ─── Find Key By Aliases ────────────────────────────────────
66
+ function findByAliases(envMap, aliases) {
67
+ for (const alias of aliases) {
68
+ const value = envMap.get(alias);
69
+ if (value)
70
+ return value;
71
+ }
72
+ return null;
73
+ }
74
+ // ─── Detect Framework ───────────────────────────────────────
75
+ function detectFramework(cwd) {
76
+ let framework = 'unknown';
77
+ let hasSupabaseInstalled = false;
78
+ try {
79
+ const pkgPath = path.join(cwd, 'package.json');
80
+ const content = fs.readFileSync(pkgPath, 'utf-8');
81
+ const pkg = JSON.parse(content);
82
+ const allDeps = {
83
+ ...pkg.dependencies,
84
+ ...pkg.devDependencies,
85
+ };
86
+ if ('next' in allDeps) {
87
+ framework = 'nextjs';
88
+ }
89
+ else if ('vite' in allDeps) {
90
+ framework = 'vite';
91
+ }
92
+ if ('@supabase/supabase-js' in allDeps) {
93
+ hasSupabaseInstalled = true;
94
+ }
95
+ }
96
+ catch {
97
+ // No package.json or parse error — defaults are fine
98
+ }
99
+ return { framework, hasSupabaseInstalled };
100
+ }
101
+ // ─── Main Scanner ───────────────────────────────────────────
102
+ export async function scanProject(cwd) {
103
+ // Merge all env files (earlier files win)
104
+ const mergedEnv = new Map();
105
+ let foundInFile = null;
106
+ // Read in reverse so earlier files overwrite later ones
107
+ for (const file of [...ENV_FILES].reverse()) {
108
+ const filePath = path.join(cwd, file);
109
+ const vars = parseEnvFile(filePath);
110
+ for (const [key, value] of vars) {
111
+ mergedEnv.set(key, value);
112
+ }
113
+ if (vars.size > 0 && !foundInFile) {
114
+ foundInFile = file;
115
+ }
116
+ }
117
+ // Re-check which file had the first match (priority order)
118
+ for (const file of ENV_FILES) {
119
+ const filePath = path.join(cwd, file);
120
+ const vars = parseEnvFile(filePath);
121
+ if (vars.size > 0) {
122
+ foundInFile = file;
123
+ break;
124
+ }
125
+ }
126
+ const supabaseUrl = findByAliases(mergedEnv, SUPABASE_URL_ALIASES);
127
+ const serviceRoleKey = findByAliases(mergedEnv, SERVICE_ROLE_KEY_ALIASES);
128
+ const anonKey = findByAliases(mergedEnv, ANON_KEY_ALIASES);
129
+ const { framework, hasSupabaseInstalled } = detectFramework(cwd);
130
+ // Log found credentials
131
+ if (supabaseUrl) {
132
+ logSuccess(`SUPABASE_URL = ${maskCredential(supabaseUrl)}`);
133
+ }
134
+ else {
135
+ logError('missing SUPABASE_URL');
136
+ }
137
+ if (serviceRoleKey) {
138
+ logSuccess(`SUPABASE_SERVICE_ROLE_KEY = ${maskCredential(serviceRoleKey)}`);
139
+ }
140
+ else {
141
+ logError('missing SUPABASE_SERVICE_ROLE_KEY');
142
+ }
143
+ if (anonKey) {
144
+ logSuccess(`SUPABASE_ANON_KEY = ${maskCredential(anonKey)}`);
145
+ }
146
+ if (foundInFile) {
147
+ logSuccess(`found credentials in ${foundInFile}`);
148
+ }
149
+ return {
150
+ supabaseUrl,
151
+ serviceRoleKey,
152
+ anonKey,
153
+ framework,
154
+ hasSupabaseInstalled,
155
+ foundInFile,
156
+ };
157
+ }
158
+ // ─── Prompt for Missing Credentials ─────────────────────────
159
+ export async function promptForMissing(scan) {
160
+ let supabaseUrl = scan.supabaseUrl;
161
+ let serviceRoleKey = scan.serviceRoleKey;
162
+ if (!supabaseUrl) {
163
+ const value = await clack.text({
164
+ message: 'Enter your SUPABASE_URL',
165
+ placeholder: 'https://your-project.supabase.co',
166
+ validate: (input) => {
167
+ const result = SupabaseUrlSchema.safeParse(input);
168
+ if (!result.success) {
169
+ return result.error.issues[0]?.message ?? 'Invalid URL';
170
+ }
171
+ return undefined;
172
+ },
173
+ });
174
+ if (clack.isCancel(value)) {
175
+ process.exit(0);
176
+ }
177
+ supabaseUrl = value;
178
+ }
179
+ if (!serviceRoleKey) {
180
+ const value = await clack.text({
181
+ message: 'Enter your SUPABASE_SERVICE_ROLE_KEY',
182
+ placeholder: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
183
+ validate: (input) => {
184
+ const result = ServiceRoleKeySchema.safeParse(input);
185
+ if (!result.success) {
186
+ return result.error.issues[0]?.message ?? 'Invalid key';
187
+ }
188
+ return undefined;
189
+ },
190
+ });
191
+ if (clack.isCancel(value)) {
192
+ process.exit(0);
193
+ }
194
+ serviceRoleKey = value;
195
+ }
196
+ return {
197
+ supabaseUrl: SupabaseUrlSchema.parse(supabaseUrl),
198
+ serviceRoleKey: ServiceRoleKeySchema.parse(serviceRoleKey),
199
+ };
200
+ }
201
+ //# sourceMappingURL=scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.js","sourceRoot":"","sources":["../../src/wizard/scanner.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "archer-wizard",
3
+ "version": "0.1.0",
4
+ "description": "event intelligence layer for AI agents",
5
+ "type": "module",
6
+ "bin": {
7
+ "archer-wizard": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "tsx src/index.ts",
12
+ "dev:mcp": "tsx src/index.ts --mcp",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "dependencies": {
16
+ "@modelcontextprotocol/sdk": "^1.27.1",
17
+ "@supabase/supabase-js": "^2.99.1",
18
+ "@clack/prompts": "^1.1.0",
19
+ "picocolors": "^1.1.1",
20
+ "dotenv": "^17.3.1",
21
+ "zod": "^4.3.6"
22
+ },
23
+ "devDependencies": {
24
+ "typescript": "^5.9.3",
25
+ "tsx": "^4.21.0",
26
+ "@types/node": "^25.5.0"
27
+ }
28
+ }
package/src/index.ts ADDED
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+
3
+ import 'dotenv/config';
4
+
5
+ // ─── Mode Detection ─────────────────────────────────────────
6
+
7
+ const isMcpMode = process.argv.includes('--mcp');
8
+
9
+ if (isMcpMode) {
10
+ // ─── MCP Server Mode ─────────────────────────────────────
11
+ const { Server } = await import('@modelcontextprotocol/sdk/server/index.js');
12
+ const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
13
+ const {
14
+ CallToolRequestSchema,
15
+ ListToolsRequestSchema,
16
+ } = await import('@modelcontextprotocol/sdk/types.js');
17
+ const { executeWatch, WATCH_TOOL_SCHEMA } = await import('./tools/watch.js');
18
+ const { stderrReady, stderrError } = await import('./lib/ascii.js');
19
+
20
+ const server = new Server(
21
+ {
22
+ name: 'archer',
23
+ version: '0.1.0',
24
+ },
25
+ {
26
+ capabilities: {
27
+ tools: {},
28
+ },
29
+ },
30
+ );
31
+
32
+ // Register tool listing
33
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
34
+ return {
35
+ tools: [WATCH_TOOL_SCHEMA],
36
+ };
37
+ });
38
+
39
+ // Register tool execution
40
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
41
+ const { name, arguments: args } = request.params;
42
+
43
+ if (name === 'archer_watch') {
44
+ const result = executeWatch(args);
45
+ return {
46
+ content: [
47
+ {
48
+ type: 'text' as const,
49
+ text: JSON.stringify(result, null, 2),
50
+ },
51
+ ],
52
+ };
53
+ }
54
+
55
+ return {
56
+ content: [
57
+ {
58
+ type: 'text' as const,
59
+ text: JSON.stringify({ error: `unknown tool: ${name}` }),
60
+ },
61
+ ],
62
+ isError: true,
63
+ };
64
+ });
65
+
66
+ // Start server
67
+ try {
68
+ const transport = new StdioServerTransport();
69
+ await server.connect(transport);
70
+ stderrReady('archer MCP server running on stdio');
71
+ } catch (err) {
72
+ const message = err instanceof Error ? err.message : String(err);
73
+ stderrError(`failed to start MCP server: ${message}`);
74
+ process.exit(1);
75
+ }
76
+ } else {
77
+ // ─── Wizard Mode ─────────────────────────────────────────
78
+ const { runWizard } = await import('./wizard/index.js');
79
+
80
+ try {
81
+ await runWizard();
82
+ } catch (err) {
83
+ if (err instanceof Error && err.message.includes('cancel')) {
84
+ console.log('\n cancelled.\n');
85
+ process.exit(0);
86
+ }
87
+ console.error(err);
88
+ process.exit(1);
89
+ }
90
+ }
@@ -0,0 +1,109 @@
1
+ import pc from 'picocolors';
2
+
3
+ // ─── ASCII Art ──────────────────────────────────────────────
4
+
5
+ export function showAsciiArt(): void {
6
+ const lines = [
7
+ '█████╗ ██████╗ ██████╗██╗ ██╗███████╗██████╗ ',
8
+ '██╔══██╗██╔══██╗██╔════╝██║ ██║██╔════╝██╔══██╗',
9
+ '███████║██████╔╝██║ ███████║█████╗ ██████╔╝',
10
+ '██╔══██║██╔══██╗██║ ██╔══██║██╔══╝ ██╔══██╗',
11
+ '██║ ██║██║ ██║╚██████╗██║ ██║███████╗██║ ██║',
12
+ '╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝',
13
+ ];
14
+
15
+ // Column boundaries for coloring:
16
+ // A: cols 0-5, R: 6-13, C: 14-21, H: 22-29, E: 30-37, R: 38-45
17
+ // ARCH = white, ER = green
18
+ const archEnd = 30; // columns 0-29 are A R C H
19
+ const erEnd = 46; // columns 30-45 are E R
20
+
21
+ for (const line of lines) {
22
+ const archPart = line.slice(0, archEnd);
23
+ const erPart = line.slice(archEnd, erEnd);
24
+ const rest = line.slice(erEnd);
25
+ process.stdout.write(pc.white(archPart) + pc.green(erPart) + rest + '\n');
26
+ }
27
+
28
+ console.log();
29
+ console.log(pc.dim(' v0.1.0 · event intelligence for AI agents'));
30
+ console.log();
31
+ }
32
+
33
+ // ─── Status Logger ──────────────────────────────────────────
34
+
35
+ export function logAction(message: string): void {
36
+ console.log(`${pc.blue('◆')} ${message}`);
37
+ }
38
+
39
+ export function logSuccess(message: string): void {
40
+ console.log(`${pc.green('✓')} ${message}`);
41
+ }
42
+
43
+ export function logError(message: string): void {
44
+ console.log(`${pc.red('✗')} ${message}`);
45
+ }
46
+
47
+ export function logProgress(message: string): void {
48
+ console.log(`${pc.white('●')} ${message}`);
49
+ }
50
+
51
+ export function logReady(message: string): void {
52
+ console.log(`${pc.green('▶')} ${message}`);
53
+ }
54
+
55
+ // ─── Credential Masking ─────────────────────────────────────
56
+
57
+ export function maskCredential(value: string): string {
58
+ if (value.length <= 8) {
59
+ return value.slice(0, 3) + '******';
60
+ }
61
+ return value.slice(0, 8) + '******';
62
+ }
63
+
64
+ // ─── Success Box ────────────────────────────────────────────
65
+
66
+ export function showSuccessBox(agentCount: number): void {
67
+ const lines = [
68
+ '',
69
+ ` ${pc.green('▶')} Archer is ready`,
70
+ '',
71
+ ` injected into ${agentCount} agent${agentCount !== 1 ? 's' : ''}`,
72
+ ' connected to your Supabase project',
73
+ '',
74
+ ' open your AI agent and say:',
75
+ ` ${pc.dim('"watch my users table for new signups"')}`,
76
+ '',
77
+ ];
78
+
79
+ const maxLen = 46;
80
+ console.log(` ┌${'─'.repeat(maxLen)}┐`);
81
+ for (const line of lines) {
82
+ const stripped = line.replace(/\x1b\[[0-9;]*m/g, '');
83
+ const padding = maxLen - stripped.length;
84
+ console.log(` │${line}${' '.repeat(Math.max(0, padding))}│`);
85
+ }
86
+ console.log(` └${'─'.repeat(maxLen)}┘`);
87
+ }
88
+
89
+ // ─── Stderr Logger (for MCP mode) ───────────────────────────
90
+
91
+ export function stderrLog(message: string): void {
92
+ process.stderr.write(`${message}\n`);
93
+ }
94
+
95
+ export function stderrAction(message: string): void {
96
+ process.stderr.write(`${pc.blue('◆')} ${message}\n`);
97
+ }
98
+
99
+ export function stderrSuccess(message: string): void {
100
+ process.stderr.write(`${pc.green('✓')} ${message}\n`);
101
+ }
102
+
103
+ export function stderrError(message: string): void {
104
+ process.stderr.write(`${pc.red('✗')} ${message}\n`);
105
+ }
106
+
107
+ export function stderrReady(message: string): void {
108
+ process.stderr.write(`${pc.green('▶')} ${message}\n`);
109
+ }
@@ -0,0 +1,78 @@
1
+ import { createClient, type SupabaseClient, type RealtimeChannel } from '@supabase/supabase-js';
2
+ import { stderrError } from './ascii.js';
3
+ import type { PostgresEvent } from '../types/index.js';
4
+
5
+ // ─── Env Validation ─────────────────────────────────────────
6
+
7
+ function getEnvVar(name: string): string {
8
+ const value = process.env[name];
9
+ if (!value) {
10
+ stderrError(`missing required env var: ${name}`);
11
+ throw new Error(`Missing env var: ${name}`);
12
+ }
13
+ return value;
14
+ }
15
+
16
+ // ─── Supabase Client ────────────────────────────────────────
17
+
18
+ let _client: SupabaseClient | null = null;
19
+
20
+ export function getSupabaseClient(): SupabaseClient {
21
+ if (_client) return _client;
22
+
23
+ const url = getEnvVar('SUPABASE_URL');
24
+ const key = getEnvVar('SUPABASE_SERVICE_ROLE_KEY');
25
+
26
+ _client = createClient(url, key, {
27
+ auth: {
28
+ autoRefreshToken: false,
29
+ persistSession: false,
30
+ },
31
+ });
32
+
33
+ return _client;
34
+ }
35
+
36
+ // ─── Realtime Channel Helpers ───────────────────────────────
37
+
38
+ export function createAuthChannel(
39
+ channelName: string,
40
+ onEvent: (payload: Record<string, unknown>) => void,
41
+ ): RealtimeChannel {
42
+ const client = getSupabaseClient();
43
+
44
+ const channel = client
45
+ .channel(channelName)
46
+ .on('postgres_changes', { event: 'INSERT', schema: 'auth', table: 'users' }, (payload) => {
47
+ onEvent(payload.new as Record<string, unknown>);
48
+ })
49
+ .subscribe();
50
+
51
+ return channel;
52
+ }
53
+
54
+ export function createTableChannel(
55
+ channelName: string,
56
+ table: string,
57
+ event: PostgresEvent,
58
+ onEvent: (payload: Record<string, unknown>) => void,
59
+ ): RealtimeChannel {
60
+ const client = getSupabaseClient();
61
+
62
+ const channel = client
63
+ .channel(channelName)
64
+ .on('postgres_changes', { event, schema: 'public', table }, (payload) => {
65
+ const data = event === 'DELETE' ? payload.old : payload.new;
66
+ onEvent(data as Record<string, unknown>);
67
+ })
68
+ .subscribe();
69
+
70
+ return channel;
71
+ }
72
+
73
+ // ─── Cleanup ────────────────────────────────────────────────
74
+
75
+ export async function removeChannel(channel: RealtimeChannel): Promise<void> {
76
+ const client = getSupabaseClient();
77
+ await client.removeChannel(channel);
78
+ }