wingman-ai 1.0.0__py3-none-any.whl

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 (60) hide show
  1. share/wingman/node_listener/package-lock.json +1785 -0
  2. share/wingman/node_listener/package.json +50 -0
  3. share/wingman/node_listener/src/index.ts +108 -0
  4. share/wingman/node_listener/src/ipc.ts +70 -0
  5. share/wingman/node_listener/src/messageHandler.ts +135 -0
  6. share/wingman/node_listener/src/socket.ts +244 -0
  7. share/wingman/node_listener/src/types.d.ts +13 -0
  8. share/wingman/node_listener/tsconfig.json +19 -0
  9. wingman/__init__.py +4 -0
  10. wingman/__main__.py +6 -0
  11. wingman/cli/__init__.py +5 -0
  12. wingman/cli/commands/__init__.py +1 -0
  13. wingman/cli/commands/auth.py +90 -0
  14. wingman/cli/commands/config.py +109 -0
  15. wingman/cli/commands/init.py +71 -0
  16. wingman/cli/commands/logs.py +84 -0
  17. wingman/cli/commands/start.py +111 -0
  18. wingman/cli/commands/status.py +84 -0
  19. wingman/cli/commands/stop.py +33 -0
  20. wingman/cli/commands/uninstall.py +113 -0
  21. wingman/cli/main.py +50 -0
  22. wingman/cli/wizard.py +356 -0
  23. wingman/config/__init__.py +31 -0
  24. wingman/config/paths.py +153 -0
  25. wingman/config/personality.py +155 -0
  26. wingman/config/registry.py +343 -0
  27. wingman/config/settings.py +294 -0
  28. wingman/core/__init__.py +16 -0
  29. wingman/core/agent.py +257 -0
  30. wingman/core/ipc_handler.py +124 -0
  31. wingman/core/llm/__init__.py +5 -0
  32. wingman/core/llm/client.py +77 -0
  33. wingman/core/memory/__init__.py +6 -0
  34. wingman/core/memory/context.py +109 -0
  35. wingman/core/memory/models.py +213 -0
  36. wingman/core/message_processor.py +277 -0
  37. wingman/core/policy/__init__.py +5 -0
  38. wingman/core/policy/evaluator.py +265 -0
  39. wingman/core/process_manager.py +135 -0
  40. wingman/core/safety/__init__.py +8 -0
  41. wingman/core/safety/cooldown.py +63 -0
  42. wingman/core/safety/quiet_hours.py +75 -0
  43. wingman/core/safety/rate_limiter.py +58 -0
  44. wingman/core/safety/triggers.py +117 -0
  45. wingman/core/transports/__init__.py +14 -0
  46. wingman/core/transports/base.py +106 -0
  47. wingman/core/transports/imessage/__init__.py +5 -0
  48. wingman/core/transports/imessage/db_listener.py +280 -0
  49. wingman/core/transports/imessage/sender.py +162 -0
  50. wingman/core/transports/imessage/transport.py +140 -0
  51. wingman/core/transports/whatsapp.py +180 -0
  52. wingman/daemon/__init__.py +5 -0
  53. wingman/daemon/manager.py +303 -0
  54. wingman/installer/__init__.py +5 -0
  55. wingman/installer/node_installer.py +253 -0
  56. wingman_ai-1.0.0.dist-info/METADATA +553 -0
  57. wingman_ai-1.0.0.dist-info/RECORD +60 -0
  58. wingman_ai-1.0.0.dist-info/WHEEL +4 -0
  59. wingman_ai-1.0.0.dist-info/entry_points.txt +2 -0
  60. wingman_ai-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "wingman-ai",
3
+ "version": "1.0.0",
4
+ "description": "WhatsApp listener component for Wingman AI - connects to WhatsApp Web using Baileys",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "start": "node dist/index.js",
10
+ "dev": "tsc && node dist/index.js",
11
+ "prepublishOnly": "npm run build"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/metanoia-oss/wingman.git",
16
+ "directory": "node_listener"
17
+ },
18
+ "author": "Wingman Contributors",
19
+ "license": "MIT",
20
+ "bugs": {
21
+ "url": "https://github.com/metanoia-oss/wingman/issues"
22
+ },
23
+ "homepage": "https://github.com/metanoia-oss/wingman#readme",
24
+ "keywords": [
25
+ "whatsapp",
26
+ "baileys",
27
+ "chatbot",
28
+ "ai",
29
+ "wingman",
30
+ "whatsapp-web",
31
+ "messaging"
32
+ ],
33
+ "engines": {
34
+ "node": ">=20.0.0"
35
+ },
36
+ "files": [
37
+ "dist/**/*",
38
+ "package.json",
39
+ "README.md"
40
+ ],
41
+ "dependencies": {
42
+ "@whiskeysockets/baileys": "^6.7.16",
43
+ "pino": "^8.16.0",
44
+ "qrcode-terminal": "^0.12.0"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^20.10.0",
48
+ "typescript": "^5.3.2"
49
+ }
50
+ }
@@ -0,0 +1,108 @@
1
+ import { createSocket, sendMessage, closeSocket } from './socket';
2
+ import { setupStdinListener, sendToPython, log, IPCCommand } from './ipc';
3
+
4
+ /**
5
+ * Handle commands from Python orchestrator
6
+ */
7
+ async function handleCommand(cmd: IPCCommand): Promise<void> {
8
+ log('info', 'Received command', { action: cmd.action });
9
+
10
+ switch (cmd.action) {
11
+ case 'send_message':
12
+ if (cmd.payload?.jid && cmd.payload?.text) {
13
+ const success = await sendMessage(cmd.payload.jid, cmd.payload.text);
14
+ sendToPython({
15
+ type: 'send_result',
16
+ data: {
17
+ success,
18
+ jid: cmd.payload.jid,
19
+ messageId: cmd.payload.messageId
20
+ }
21
+ });
22
+ } else {
23
+ sendToPython({
24
+ type: 'error',
25
+ data: { message: 'send_message requires jid and text in payload' }
26
+ });
27
+ }
28
+ break;
29
+
30
+ case 'ping':
31
+ sendToPython({ type: 'pong' });
32
+ break;
33
+
34
+ case 'shutdown':
35
+ log('info', 'Shutdown requested');
36
+ sendToPython({ type: 'shutting_down' });
37
+ await closeSocket();
38
+ process.exit(0);
39
+ break;
40
+
41
+ default:
42
+ log('warn', 'Unknown command', { action: cmd.action });
43
+ sendToPython({
44
+ type: 'error',
45
+ data: { message: `Unknown action: ${cmd.action}` }
46
+ });
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Main entry point
52
+ */
53
+ async function main(): Promise<void> {
54
+ log('info', 'Starting WhatsApp listener...');
55
+ sendToPython({ type: 'starting' });
56
+
57
+ // Set up stdin listener for commands from Python
58
+ setupStdinListener(handleCommand);
59
+
60
+ // Handle graceful shutdown
61
+ process.on('SIGINT', async () => {
62
+ log('info', 'SIGINT received, shutting down');
63
+ sendToPython({ type: 'shutting_down' });
64
+ await closeSocket();
65
+ process.exit(0);
66
+ });
67
+
68
+ process.on('SIGTERM', async () => {
69
+ log('info', 'SIGTERM received, shutting down');
70
+ sendToPython({ type: 'shutting_down' });
71
+ await closeSocket();
72
+ process.exit(0);
73
+ });
74
+
75
+ // Handle uncaught errors
76
+ process.on('uncaughtException', (err) => {
77
+ log('error', 'Uncaught exception', { error: String(err), stack: err.stack });
78
+ sendToPython({
79
+ type: 'error',
80
+ data: { message: `Uncaught exception: ${err.message}` }
81
+ });
82
+ });
83
+
84
+ process.on('unhandledRejection', (reason) => {
85
+ log('error', 'Unhandled rejection', { reason: String(reason) });
86
+ sendToPython({
87
+ type: 'error',
88
+ data: { message: `Unhandled rejection: ${reason}` }
89
+ });
90
+ });
91
+
92
+ try {
93
+ await createSocket();
94
+ log('info', 'Socket created, waiting for connection...');
95
+ } catch (err) {
96
+ log('error', 'Failed to create socket', { error: String(err) });
97
+ sendToPython({
98
+ type: 'error',
99
+ data: { message: `Failed to create socket: ${err}` }
100
+ });
101
+ process.exit(1);
102
+ }
103
+ }
104
+
105
+ main().catch((err) => {
106
+ log('error', 'Fatal error in main', { error: String(err) });
107
+ process.exit(1);
108
+ });
@@ -0,0 +1,70 @@
1
+ import * as readline from 'readline';
2
+
3
+ export interface IPCMessage {
4
+ type: string;
5
+ data?: any;
6
+ }
7
+
8
+ export interface IPCCommand {
9
+ action: string;
10
+ payload?: any;
11
+ }
12
+
13
+ const NULL_CHAR = '\0';
14
+
15
+ /**
16
+ * Send a message to Python via stdout using NULL-delimited JSON
17
+ */
18
+ export function sendToPython(message: IPCMessage): void {
19
+ const json = JSON.stringify(message);
20
+ process.stdout.write(json + NULL_CHAR);
21
+ }
22
+
23
+ /**
24
+ * Set up stdin listener for commands from Python
25
+ */
26
+ export function setupStdinListener(onCommand: (cmd: IPCCommand) => void): void {
27
+ let buffer = '';
28
+
29
+ process.stdin.setEncoding('utf8');
30
+ process.stdin.on('data', (chunk: string) => {
31
+ buffer += chunk;
32
+
33
+ // Process all complete messages (NULL-delimited)
34
+ let nullIndex: number;
35
+ while ((nullIndex = buffer.indexOf(NULL_CHAR)) !== -1) {
36
+ const jsonStr = buffer.slice(0, nullIndex);
37
+ buffer = buffer.slice(nullIndex + 1);
38
+
39
+ if (jsonStr.trim()) {
40
+ try {
41
+ const command = JSON.parse(jsonStr) as IPCCommand;
42
+ onCommand(command);
43
+ } catch (err) {
44
+ sendToPython({
45
+ type: 'error',
46
+ data: { message: `Failed to parse command: ${err}` }
47
+ });
48
+ }
49
+ }
50
+ }
51
+ });
52
+
53
+ process.stdin.on('end', () => {
54
+ sendToPython({ type: 'stdin_closed' });
55
+ process.exit(0);
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Log a message (goes to stderr to not interfere with IPC)
61
+ */
62
+ export function log(level: 'info' | 'warn' | 'error', message: string, data?: any): void {
63
+ const logEntry = {
64
+ timestamp: new Date().toISOString(),
65
+ level,
66
+ message,
67
+ ...(data && { data })
68
+ };
69
+ process.stderr.write(JSON.stringify(logEntry) + '\n');
70
+ }
@@ -0,0 +1,135 @@
1
+ import { WAMessage, WASocket, proto } from '@whiskeysockets/baileys';
2
+ import { sendToPython, log } from './ipc';
3
+
4
+ export interface ProcessedMessage {
5
+ messageId: string;
6
+ chatId: string;
7
+ senderId: string;
8
+ senderName: string | null;
9
+ text: string;
10
+ timestamp: number;
11
+ isGroup: boolean;
12
+ isSelf: boolean;
13
+ quotedMessage?: {
14
+ text: string;
15
+ senderId: string;
16
+ };
17
+ }
18
+
19
+ /**
20
+ * Extract text content from various message types
21
+ */
22
+ function extractText(message: proto.IMessage | null | undefined): string | null {
23
+ if (!message) return null;
24
+
25
+ // Direct text message
26
+ if (message.conversation) {
27
+ return message.conversation;
28
+ }
29
+
30
+ // Extended text (with mentions, links, etc.)
31
+ if (message.extendedTextMessage?.text) {
32
+ return message.extendedTextMessage.text;
33
+ }
34
+
35
+ // Image/video with caption
36
+ if (message.imageMessage?.caption) {
37
+ return message.imageMessage.caption;
38
+ }
39
+ if (message.videoMessage?.caption) {
40
+ return message.videoMessage.caption;
41
+ }
42
+
43
+ // Document with caption
44
+ if (message.documentMessage?.caption) {
45
+ return message.documentMessage.caption;
46
+ }
47
+
48
+ return null;
49
+ }
50
+
51
+ /**
52
+ * Process incoming WhatsApp message
53
+ */
54
+ export function processMessage(
55
+ msg: WAMessage,
56
+ sock: WASocket
57
+ ): ProcessedMessage | null {
58
+ try {
59
+ const key = msg.key;
60
+ if (!key || !key.remoteJid) {
61
+ return null;
62
+ }
63
+
64
+ const chatId = key.remoteJid;
65
+ const isGroup = chatId.endsWith('@g.us');
66
+ const isSelf = key.fromMe || false;
67
+
68
+ // Get sender ID
69
+ let senderId: string;
70
+ if (isGroup) {
71
+ senderId = key.participant || chatId;
72
+ } else {
73
+ senderId = isSelf ? (sock.user?.id || 'self') : chatId;
74
+ }
75
+
76
+ // Extract text
77
+ const text = extractText(msg.message);
78
+ if (!text) {
79
+ return null; // Skip non-text messages
80
+ }
81
+
82
+ // Get sender name
83
+ const senderName = msg.pushName || null;
84
+
85
+ // Get timestamp
86
+ const timestamp = msg.messageTimestamp
87
+ ? (typeof msg.messageTimestamp === 'number'
88
+ ? msg.messageTimestamp
89
+ : Number(msg.messageTimestamp))
90
+ : Date.now() / 1000;
91
+
92
+ // Check for quoted message
93
+ let quotedMessage: ProcessedMessage['quotedMessage'];
94
+ const contextInfo = msg.message?.extendedTextMessage?.contextInfo;
95
+ if (contextInfo?.quotedMessage) {
96
+ const quotedText = extractText(contextInfo.quotedMessage);
97
+ if (quotedText) {
98
+ quotedMessage = {
99
+ text: quotedText,
100
+ senderId: contextInfo.participant || ''
101
+ };
102
+ }
103
+ }
104
+
105
+ return {
106
+ messageId: key.id || '',
107
+ chatId,
108
+ senderId,
109
+ senderName,
110
+ text,
111
+ timestamp,
112
+ isGroup,
113
+ isSelf,
114
+ quotedMessage
115
+ };
116
+ } catch (err) {
117
+ log('error', 'Failed to process message', { error: String(err) });
118
+ return null;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Handle incoming messages and emit to Python
124
+ */
125
+ export function handleMessages(messages: WAMessage[], sock: WASocket): void {
126
+ for (const msg of messages) {
127
+ const processed = processMessage(msg, sock);
128
+ if (processed) {
129
+ sendToPython({
130
+ type: 'message',
131
+ data: processed
132
+ });
133
+ }
134
+ }
135
+ }
@@ -0,0 +1,244 @@
1
+ import makeWASocket, {
2
+ DisconnectReason,
3
+ useMultiFileAuthState,
4
+ WASocket,
5
+ ConnectionState,
6
+ fetchLatestBaileysVersion
7
+ } from '@whiskeysockets/baileys';
8
+ import { Boom } from '@hapi/boom';
9
+ import * as qrcode from 'qrcode-terminal';
10
+ import * as path from 'path';
11
+ import * as fs from 'fs';
12
+ import { sendToPython, log } from './ipc';
13
+ import { handleMessages } from './messageHandler';
14
+ import pino from 'pino';
15
+
16
+ const AUTH_DIR = path.join(__dirname, '..', '..', 'auth_state');
17
+ const STARTUP_DELAY_FILE = path.join(__dirname, '..', '..', '.last_disconnect');
18
+
19
+ let sock: WASocket | null = null;
20
+ let reconnectAttempts = 0;
21
+ const MAX_RECONNECT_ATTEMPTS = 10;
22
+ let isConnecting = false; // Prevent concurrent connection attempts
23
+ const MIN_RESTART_DELAY_MS = 5000; // Minimum time between sessions
24
+
25
+ /**
26
+ * Close existing socket connection gracefully
27
+ */
28
+ async function closeExistingSocket(): Promise<void> {
29
+ if (sock) {
30
+ log('info', 'Closing existing socket connection');
31
+ try {
32
+ sock.ev.removeAllListeners('connection.update');
33
+ sock.ev.removeAllListeners('creds.update');
34
+ sock.ev.removeAllListeners('messages.upsert');
35
+ await sock.logout().catch(() => {}); // Ignore logout errors
36
+ sock.end(undefined);
37
+ } catch (err) {
38
+ log('warn', 'Error closing socket', { error: String(err) });
39
+ }
40
+ sock = null;
41
+ // Give WhatsApp servers time to register the disconnect
42
+ await new Promise(resolve => setTimeout(resolve, 2000));
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Check if we need to wait before connecting (to avoid 440 errors)
48
+ */
49
+ async function enforceStartupDelay(): Promise<void> {
50
+ try {
51
+ if (fs.existsSync(STARTUP_DELAY_FILE)) {
52
+ const lastDisconnect = parseInt(fs.readFileSync(STARTUP_DELAY_FILE, 'utf-8'), 10);
53
+ const elapsed = Date.now() - lastDisconnect;
54
+ const waitTime = MIN_RESTART_DELAY_MS - elapsed;
55
+
56
+ if (waitTime > 0) {
57
+ log('info', `Waiting ${waitTime}ms before connecting (anti-440 delay)`);
58
+ await new Promise(resolve => setTimeout(resolve, waitTime));
59
+ }
60
+ }
61
+ } catch (err) {
62
+ // Ignore errors reading the file
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Record disconnect time for anti-440 protection
68
+ */
69
+ function recordDisconnectTime(): void {
70
+ try {
71
+ fs.writeFileSync(STARTUP_DELAY_FILE, Date.now().toString());
72
+ } catch (err) {
73
+ // Ignore errors writing the file
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Create and initialize WhatsApp socket connection
79
+ */
80
+ export async function createSocket(): Promise<WASocket> {
81
+ // Prevent concurrent connection attempts
82
+ if (isConnecting) {
83
+ log('warn', 'Connection already in progress, skipping');
84
+ return sock!;
85
+ }
86
+
87
+ isConnecting = true;
88
+
89
+ try {
90
+ // Wait if we disconnected recently (anti-440 protection)
91
+ await enforceStartupDelay();
92
+
93
+ // Close any existing connection first
94
+ await closeExistingSocket();
95
+
96
+ const { state, saveCreds } = await useMultiFileAuthState(AUTH_DIR);
97
+ const { version } = await fetchLatestBaileysVersion();
98
+
99
+ // Create silent logger for Baileys
100
+ const logger = pino({ level: 'silent' });
101
+
102
+ log('info', 'Creating socket with auth state', { authDir: AUTH_DIR });
103
+
104
+ sock = makeWASocket({
105
+ version,
106
+ auth: state,
107
+ logger,
108
+ browser: ['WhatsApp Agent', 'Chrome', '120.0.0'],
109
+ connectTimeoutMs: 60000,
110
+ keepAliveIntervalMs: 30000,
111
+ retryRequestDelayMs: 2000,
112
+ markOnlineOnConnect: false, // Don't mark online to reduce conflicts
113
+ syncFullHistory: false, // Don't sync history to reduce footprint
114
+ });
115
+
116
+ // Handle connection updates
117
+ sock.ev.on('connection.update', (update: Partial<ConnectionState>) => {
118
+ handleConnectionUpdate(update, saveCreds);
119
+ });
120
+
121
+ // Save credentials on update
122
+ sock.ev.on('creds.update', saveCreds);
123
+
124
+ // Handle incoming messages
125
+ sock.ev.on('messages.upsert', async (m) => {
126
+ if (m.type === 'notify' && sock) {
127
+ handleMessages(m.messages, sock);
128
+ }
129
+ });
130
+
131
+ return sock;
132
+ } finally {
133
+ isConnecting = false;
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Handle connection state changes
139
+ */
140
+ function handleConnectionUpdate(
141
+ update: Partial<ConnectionState>,
142
+ saveCreds: () => Promise<void>
143
+ ): void {
144
+ const { connection, lastDisconnect, qr } = update;
145
+
146
+ // Display QR code for scanning
147
+ if (qr) {
148
+ log('info', 'QR Code received, display in terminal');
149
+ process.stderr.write('\n=== Scan this QR code with WhatsApp ===\n\n');
150
+ qrcode.generate(qr, { small: true }, (qrString) => {
151
+ process.stderr.write(qrString + '\n');
152
+ });
153
+ sendToPython({ type: 'qr_code', data: { qr } });
154
+ }
155
+
156
+ if (connection === 'close') {
157
+ const statusCode = (lastDisconnect?.error as Boom)?.output?.statusCode;
158
+ const shouldReconnect = statusCode !== DisconnectReason.loggedOut;
159
+
160
+ log('warn', 'Connection closed', { statusCode, shouldReconnect });
161
+ sendToPython({
162
+ type: 'disconnected',
163
+ data: { statusCode, shouldReconnect }
164
+ });
165
+
166
+ if (statusCode === DisconnectReason.loggedOut) {
167
+ log('info', 'Logged out, reconnecting for new QR code...');
168
+ sendToPython({ type: 'logged_out' });
169
+ // Reconnect to get a new QR code
170
+ setTimeout(() => {
171
+ createSocket().catch((err) => {
172
+ log('error', 'Reconnection failed', { error: String(err) });
173
+ });
174
+ }, 2000);
175
+ } else if (shouldReconnect && reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
176
+ reconnectAttempts++;
177
+ const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);
178
+ log('info', `Reconnecting in ${delay}ms (attempt ${reconnectAttempts})`);
179
+
180
+ setTimeout(() => {
181
+ createSocket().catch((err) => {
182
+ log('error', 'Reconnection failed', { error: String(err) });
183
+ });
184
+ }, delay);
185
+ } else {
186
+ log('error', 'Max reconnection attempts reached');
187
+ sendToPython({ type: 'max_reconnect_reached' });
188
+ process.exit(1);
189
+ }
190
+ } else if (connection === 'open') {
191
+ reconnectAttempts = 0;
192
+ log('info', 'Connection established');
193
+ sendToPython({
194
+ type: 'connected',
195
+ data: { user: sock?.user }
196
+ });
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Send a text message
202
+ */
203
+ export async function sendMessage(jid: string, text: string): Promise<boolean> {
204
+ if (!sock) {
205
+ log('error', 'Cannot send message: socket not initialized');
206
+ return false;
207
+ }
208
+
209
+ try {
210
+ await sock.sendMessage(jid, { text });
211
+ log('info', 'Message sent', { jid, textLength: text.length });
212
+ return true;
213
+ } catch (err) {
214
+ log('error', 'Failed to send message', { jid, error: String(err) });
215
+ return false;
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Get the current socket instance
221
+ */
222
+ export function getSocket(): WASocket | null {
223
+ return sock;
224
+ }
225
+
226
+ /**
227
+ * Gracefully close the socket connection
228
+ */
229
+ export async function closeSocket(): Promise<void> {
230
+ if (sock) {
231
+ log('info', 'Gracefully closing socket');
232
+ try {
233
+ sock.ev.removeAllListeners('connection.update');
234
+ sock.ev.removeAllListeners('creds.update');
235
+ sock.ev.removeAllListeners('messages.upsert');
236
+ sock.end(undefined);
237
+ } catch (err) {
238
+ log('warn', 'Error during socket close', { error: String(err) });
239
+ }
240
+ sock = null;
241
+ // Record disconnect time for anti-440 protection on next startup
242
+ recordDisconnectTime();
243
+ }
244
+ }
@@ -0,0 +1,13 @@
1
+ declare module 'qrcode-terminal' {
2
+ interface QRCodeOptions {
3
+ small?: boolean;
4
+ }
5
+
6
+ export function generate(
7
+ text: string,
8
+ options?: QRCodeOptions,
9
+ callback?: (qrcode: string) => void
10
+ ): void;
11
+
12
+ export function setErrorLevel(level: string): void;
13
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }
wingman/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ """Wingman - AI-powered personal chat agent for WhatsApp and iMessage."""
2
+
3
+ __version__ = "1.0.0"
4
+ __all__ = ["__version__"]
wingman/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Entry point for python -m wingman."""
2
+
3
+ from wingman.cli.main import app
4
+
5
+ if __name__ == "__main__":
6
+ app()
@@ -0,0 +1,5 @@
1
+ """CLI module for Wingman."""
2
+
3
+ from .main import app
4
+
5
+ __all__ = ["app"]
@@ -0,0 +1 @@
1
+ """CLI commands for Wingman."""