twinclaw 1.1.1 → 1.1.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.
- package/README.md +3 -3
- package/dist/api/handlers/agents.js +82 -0
- package/dist/api/handlers/debug.js +69 -0
- package/dist/api/handlers/devices.js +79 -0
- package/dist/api/handlers/jobs.js +99 -0
- package/dist/api/handlers/status.js +149 -0
- package/dist/api/router.js +272 -2
- package/dist/api/runtime-event-producer.js +20 -0
- package/dist/api/shared.js +34 -0
- package/dist/api/websocket-hub.js +18 -7
- package/dist/config/json-config.js +393 -1
- package/dist/core/heartbeat.js +304 -8
- package/dist/core/lane-executor.js +18 -0
- package/dist/core/onboarding.js +2 -2
- package/dist/core/self-improve-cli.js +212 -0
- package/dist/core/simplified-onboarding.js +158 -16
- package/dist/index.js +61 -33
- package/dist/interfaces/dispatcher.js +4 -2
- package/dist/interfaces/whatsapp_handler.js +243 -2
- package/dist/services/auto-configurer.js +591 -0
- package/dist/services/device-pairing.js +378 -0
- package/dist/services/hooks.js +130 -0
- package/dist/services/job-scheduler.js +133 -1
- package/dist/services/learning-system.js +226 -0
- package/dist/services/self-healing.js +267 -0
- package/dist/services/skill-acquisition/intent-parser.js +115 -0
- package/dist/services/skill-acquisition/research-engine.js +319 -0
- package/dist/services/skill-builder.js +235 -0
- package/dist/services/sub-agent-service.js +215 -0
- package/dist/services/web-service.js +70 -0
- package/dist/services/webhook-service.js +127 -0
- package/dist/skills/builtin.js +827 -0
- package/dist/tools/agent-improvement.js +208 -0
- package/dist/types/skill-acquisition.js +4 -0
- package/package.json +3 -1
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
import { readConfig, writeConfig } from '../config/config-loader.js';
|
|
2
|
+
import { logThought } from '../utils/logger.js';
|
|
3
|
+
import { writeFile, mkdir } from 'fs/promises';
|
|
4
|
+
import { existsSync } from 'fs';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { getWorkspaceDir } from '../config/workspace.js';
|
|
7
|
+
const KNOWN_SERVICES = {
|
|
8
|
+
slack: {
|
|
9
|
+
name: 'slack',
|
|
10
|
+
displayName: 'Slack',
|
|
11
|
+
description: 'Team messaging and collaboration',
|
|
12
|
+
authType: 'api_key',
|
|
13
|
+
docsUrl: 'https://api.slack.com/docs',
|
|
14
|
+
envVars: ['SLACK_BOT_TOKEN', 'SLACK_SIGNING_SECRET'],
|
|
15
|
+
configSchema: {
|
|
16
|
+
enabled: { type: 'boolean', required: true, description: 'Enable Slack integration' },
|
|
17
|
+
botToken: { type: 'string', required: true, description: 'Bot User OAuth Token' },
|
|
18
|
+
signingSecret: { type: 'string', required: true, description: 'Signing Secret' },
|
|
19
|
+
defaultChannel: { type: 'string', required: false, description: 'Default channel for notifications' }
|
|
20
|
+
},
|
|
21
|
+
steps: [
|
|
22
|
+
'1. Go to https://api.slack.com/apps',
|
|
23
|
+
'2. Click "Create New App" → "From scratch"',
|
|
24
|
+
'3. Name your app and select workspace',
|
|
25
|
+
'4. Go to "OAuth & Permissions"',
|
|
26
|
+
'5. Add scopes: chat:write, channels:read, users:read',
|
|
27
|
+
'6. Click "Install to Workspace"',
|
|
28
|
+
'7. Copy the "Bot User OAuth Token" (starts with xoxb-)'
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
discord: {
|
|
32
|
+
name: 'discord',
|
|
33
|
+
displayName: 'Discord',
|
|
34
|
+
description: 'Chat platform for communities',
|
|
35
|
+
authType: 'api_key',
|
|
36
|
+
docsUrl: 'https://discord.com/developers/docs/intro',
|
|
37
|
+
envVars: ['DISCORD_BOT_TOKEN', 'DISCORD_GUILD_ID'],
|
|
38
|
+
configSchema: {
|
|
39
|
+
enabled: { type: 'boolean', required: true, description: 'Enable Discord integration' },
|
|
40
|
+
botToken: { type: 'string', required: true, description: 'Bot Token' },
|
|
41
|
+
guildId: { type: 'string', required: false, description: 'Server ID' }
|
|
42
|
+
},
|
|
43
|
+
steps: [
|
|
44
|
+
'1. Go to https://discord.com/developers/applications',
|
|
45
|
+
'2. Click "New Application"',
|
|
46
|
+
'3. Go to "Bot" section and create bot',
|
|
47
|
+
'4. Enable "Message Content Intent"',
|
|
48
|
+
'5. Go to "OAuth2" → "URL Generator"',
|
|
49
|
+
'6. Select scopes: bot, applications.commands',
|
|
50
|
+
'7. Copy the generated URL and invite bot'
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
notion: {
|
|
54
|
+
name: 'notion',
|
|
55
|
+
displayName: 'Notion',
|
|
56
|
+
description: 'All-in-one workspace for notes and docs',
|
|
57
|
+
authType: 'api_key',
|
|
58
|
+
docsUrl: 'https://developers.notion.com/',
|
|
59
|
+
envVars: ['NOTION_API_KEY', 'NOTION_DATABASE_ID'],
|
|
60
|
+
configSchema: {
|
|
61
|
+
enabled: { type: 'boolean', required: true, description: 'Enable Notion integration' },
|
|
62
|
+
apiKey: { type: 'string', required: true, description: 'Internal Integration Token' },
|
|
63
|
+
databaseId: { type: 'string', required: false, description: 'Database ID for pages' }
|
|
64
|
+
},
|
|
65
|
+
steps: [
|
|
66
|
+
'1. Go to https://www.notion.so/my-integrations',
|
|
67
|
+
'2. Click "+ New integration"',
|
|
68
|
+
'3. Name your integration and submit',
|
|
69
|
+
'4. Copy "Internal Integration Token"',
|
|
70
|
+
'5. Open the database/page you want to access',
|
|
71
|
+
'6. Click "..." → "Connect to" → select your integration'
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
github: {
|
|
75
|
+
name: 'github',
|
|
76
|
+
displayName: 'GitHub',
|
|
77
|
+
description: 'Code hosting and version control',
|
|
78
|
+
authType: 'api_key',
|
|
79
|
+
docsUrl: 'https://docs.github.com/en/rest',
|
|
80
|
+
envVars: ['GITHUB_TOKEN', 'GITHUB_REPO'],
|
|
81
|
+
configSchema: {
|
|
82
|
+
enabled: { type: 'boolean', required: true, description: 'Enable GitHub integration' },
|
|
83
|
+
token: { type: 'string', required: true, description: 'Personal Access Token' },
|
|
84
|
+
repo: { type: 'string', required: false, description: 'Repository (owner/repo)' }
|
|
85
|
+
},
|
|
86
|
+
steps: [
|
|
87
|
+
'1. Go to https://github.com/settings/tokens',
|
|
88
|
+
'2. Click "Generate new token (classic)"',
|
|
89
|
+
'3. Select scopes: repo, workflow, read:user',
|
|
90
|
+
'4. Generate and copy token'
|
|
91
|
+
]
|
|
92
|
+
},
|
|
93
|
+
jira: {
|
|
94
|
+
name: 'jira',
|
|
95
|
+
displayName: 'Jira',
|
|
96
|
+
description: 'Project and issue tracking',
|
|
97
|
+
authType: 'basic',
|
|
98
|
+
docsUrl: 'https://developer.atlassian.com/cloud/jira/',
|
|
99
|
+
envVars: ['JIRA_DOMAIN', 'JIRA_EMAIL', 'JIRA_API_TOKEN'],
|
|
100
|
+
configSchema: {
|
|
101
|
+
enabled: { type: 'boolean', required: true, description: 'Enable Jira integration' },
|
|
102
|
+
domain: { type: 'string', required: true, description: 'Your domain.atlassian.net' },
|
|
103
|
+
email: { type: 'string', required: true, description: 'Your email' },
|
|
104
|
+
apiToken: { type: 'string', required: true, description: 'API Token' }
|
|
105
|
+
},
|
|
106
|
+
steps: [
|
|
107
|
+
'1. Go to https://id.atlassian.com/manage-profile/security/api-tokens',
|
|
108
|
+
'2. Click "Create API token"',
|
|
109
|
+
'3. Label it and copy the token',
|
|
110
|
+
'4. Your email is your Atlassian account email'
|
|
111
|
+
]
|
|
112
|
+
},
|
|
113
|
+
linear: {
|
|
114
|
+
name: 'linear',
|
|
115
|
+
displayName: 'Linear',
|
|
116
|
+
description: 'Issue tracking for software teams',
|
|
117
|
+
authType: 'api_key',
|
|
118
|
+
docsUrl: 'https://developers.linear.app/',
|
|
119
|
+
envVars: ['LINEAR_API_KEY', 'LINEAR_TEAM_ID'],
|
|
120
|
+
configSchema: {
|
|
121
|
+
enabled: { type: 'boolean', required: true, description: 'Enable Linear integration' },
|
|
122
|
+
apiKey: { type: 'string', required: true, description: 'API Key' },
|
|
123
|
+
teamId: { type: 'string', required: false, description: 'Team ID' }
|
|
124
|
+
},
|
|
125
|
+
steps: [
|
|
126
|
+
'1. Go to https://linear.app/settings/api',
|
|
127
|
+
'2. Click "Create API key"',
|
|
128
|
+
'3. Name it and copy the key'
|
|
129
|
+
]
|
|
130
|
+
},
|
|
131
|
+
notioncalendar: {
|
|
132
|
+
name: 'notioncalendar',
|
|
133
|
+
displayName: 'Notion Calendar',
|
|
134
|
+
description: 'Calendar synced with Notion',
|
|
135
|
+
authType: 'api_key',
|
|
136
|
+
docsUrl: 'https://developers.notion.com/',
|
|
137
|
+
envVars: ['NOTION_API_KEY'],
|
|
138
|
+
configSchema: {
|
|
139
|
+
enabled: { type: 'boolean', required: true, description: 'Enable calendar sync' },
|
|
140
|
+
calendarId: { type: 'string', required: false, description: 'Calendar ID' }
|
|
141
|
+
},
|
|
142
|
+
steps: [
|
|
143
|
+
'1. Create a Notion database with calendar properties',
|
|
144
|
+
'2. Get API key from https://www.notion.so/my-integrations',
|
|
145
|
+
'3. Share the database with the integration'
|
|
146
|
+
]
|
|
147
|
+
},
|
|
148
|
+
spotify: {
|
|
149
|
+
name: 'spotify',
|
|
150
|
+
displayName: 'Spotify',
|
|
151
|
+
description: 'Music streaming and control',
|
|
152
|
+
authType: 'oauth',
|
|
153
|
+
docsUrl: 'https://developer.spotify.com/',
|
|
154
|
+
envVars: ['SPOTIFY_CLIENT_ID', 'SPOTIFY_CLIENT_SECRET'],
|
|
155
|
+
configSchema: {
|
|
156
|
+
enabled: { type: 'boolean', required: true, description: 'Enable Spotify control' },
|
|
157
|
+
clientId: { type: 'string', required: true, description: 'Client ID' },
|
|
158
|
+
clientSecret: { type: 'string', required: true, description: 'Client Secret' }
|
|
159
|
+
},
|
|
160
|
+
steps: [
|
|
161
|
+
'1. Go to https://developer.spotify.com/dashboard',
|
|
162
|
+
'2. Create new app',
|
|
163
|
+
'3. Add redirect URI: http://localhost:8888/callback',
|
|
164
|
+
'4. Copy Client ID and Client Secret'
|
|
165
|
+
]
|
|
166
|
+
},
|
|
167
|
+
openweather: {
|
|
168
|
+
name: 'openweather',
|
|
169
|
+
displayName: 'OpenWeather',
|
|
170
|
+
description: 'Weather data and forecasts',
|
|
171
|
+
authType: 'api_key',
|
|
172
|
+
docsUrl: 'https://openweathermap.org/api',
|
|
173
|
+
envVars: ['OPENWEATHER_API_KEY'],
|
|
174
|
+
configSchema: {
|
|
175
|
+
enabled: { type: 'boolean', required: true, description: 'Enable weather' },
|
|
176
|
+
apiKey: { type: 'string', required: true, description: 'API Key' }
|
|
177
|
+
},
|
|
178
|
+
steps: [
|
|
179
|
+
'1. Go to https://openweathermap.org/api',
|
|
180
|
+
'2. Sign up for free account',
|
|
181
|
+
'3. Go to "API Keys" and copy your key'
|
|
182
|
+
]
|
|
183
|
+
},
|
|
184
|
+
twilio: {
|
|
185
|
+
name: 'twilio',
|
|
186
|
+
displayName: 'Twilio',
|
|
187
|
+
description: 'SMS, Voice and Messaging',
|
|
188
|
+
authType: 'api_key',
|
|
189
|
+
docsUrl: 'https://www.twilio.com/docs',
|
|
190
|
+
envVars: ['TWILIO_ACCOUNT_SID', 'TWILIO_AUTH_TOKEN', 'TWILIO_PHONE_NUMBER'],
|
|
191
|
+
configSchema: {
|
|
192
|
+
enabled: { type: 'boolean', required: true, description: 'Enable Twilio' },
|
|
193
|
+
accountSid: { type: 'string', required: true, description: 'Account SID' },
|
|
194
|
+
authToken: { type: 'string', required: true, description: 'Auth Token' },
|
|
195
|
+
phoneNumber: { type: 'string', required: true, description: 'Phone Number' }
|
|
196
|
+
},
|
|
197
|
+
steps: [
|
|
198
|
+
'1. Go to https://www.twilio.com/console',
|
|
199
|
+
'2. Get Account SID and Auth Token',
|
|
200
|
+
'3. Get a phone number from "Phone Numbers"'
|
|
201
|
+
]
|
|
202
|
+
},
|
|
203
|
+
gmail: {
|
|
204
|
+
name: 'gmail',
|
|
205
|
+
displayName: 'Gmail',
|
|
206
|
+
description: 'Email via Google Gmail',
|
|
207
|
+
authType: 'oauth',
|
|
208
|
+
docsUrl: 'https://developers.google.com/gmail/api',
|
|
209
|
+
envVars: ['GMAIL_CLIENT_ID', 'GMAIL_CLIENT_SECRET'],
|
|
210
|
+
configSchema: {
|
|
211
|
+
enabled: { type: 'boolean', required: true, description: 'Enable Gmail' },
|
|
212
|
+
clientId: { type: 'string', required: true, description: 'OAuth Client ID' },
|
|
213
|
+
clientSecret: { type: 'string', required: true, description: 'OAuth Client Secret' }
|
|
214
|
+
},
|
|
215
|
+
steps: [
|
|
216
|
+
'1. Go to https://console.cloud.google.com/apis/credentials',
|
|
217
|
+
'2. Create OAuth 2.0 Client ID',
|
|
218
|
+
'3. Set redirect URI: http://localhost:3000/oauth/callback',
|
|
219
|
+
'4. Copy Client ID and Client Secret'
|
|
220
|
+
]
|
|
221
|
+
},
|
|
222
|
+
googlecalendar: {
|
|
223
|
+
name: 'googlecalendar',
|
|
224
|
+
displayName: 'Google Calendar',
|
|
225
|
+
description: 'Calendar via Google Calendar API',
|
|
226
|
+
authType: 'oauth',
|
|
227
|
+
docsUrl: 'https://developers.google.com/calendar',
|
|
228
|
+
envVars: ['GCAL_CLIENT_ID', 'GCAL_CLIENT_SECRET'],
|
|
229
|
+
configSchema: {
|
|
230
|
+
enabled: { type: 'boolean', required: true, description: 'Enable Google Calendar' },
|
|
231
|
+
clientId: { type: 'string', required: true, description: 'OAuth Client ID' },
|
|
232
|
+
clientSecret: { type: 'string', required: true, description: 'OAuth Client Secret' }
|
|
233
|
+
},
|
|
234
|
+
steps: [
|
|
235
|
+
'1. Go to https://console.cloud.google.com/apis/credentials',
|
|
236
|
+
'2. Create OAuth 2.0 Client ID for Calendar API',
|
|
237
|
+
'3. Copy Client ID and Secret'
|
|
238
|
+
]
|
|
239
|
+
},
|
|
240
|
+
postgresql: {
|
|
241
|
+
name: 'postgresql',
|
|
242
|
+
displayName: 'PostgreSQL',
|
|
243
|
+
description: 'Database - PostgreSQL',
|
|
244
|
+
authType: 'basic',
|
|
245
|
+
docsUrl: 'https://www.postgresql.org/docs/',
|
|
246
|
+
envVars: ['PG_HOST', 'PG_USER', 'PG_PASSWORD', 'PG_DATABASE'],
|
|
247
|
+
configSchema: {
|
|
248
|
+
enabled: { type: 'boolean', required: true, description: 'Enable PostgreSQL' },
|
|
249
|
+
host: { type: 'string', required: true, description: 'Database host' },
|
|
250
|
+
port: { type: 'number', required: false, description: 'Port (default: 5432)' },
|
|
251
|
+
user: { type: 'string', required: true, description: 'Database user' },
|
|
252
|
+
password: { type: 'string', required: true, description: 'Database password' },
|
|
253
|
+
database: { type: 'string', required: true, description: 'Database name' }
|
|
254
|
+
},
|
|
255
|
+
steps: [
|
|
256
|
+
'1. Install PostgreSQL: https://www.postgresql.org/download/',
|
|
257
|
+
'2. Create a database and user',
|
|
258
|
+
'3. Note connection details'
|
|
259
|
+
]
|
|
260
|
+
},
|
|
261
|
+
mysql: {
|
|
262
|
+
name: 'mysql',
|
|
263
|
+
displayName: 'MySQL',
|
|
264
|
+
description: 'Database - MySQL/MariaDB',
|
|
265
|
+
authType: 'basic',
|
|
266
|
+
docsUrl: 'https://dev.mysql.com/doc/',
|
|
267
|
+
envVars: ['MYSQL_HOST', 'MYSQL_USER', 'MYSQL_PASSWORD', 'MYSQL_DATABASE'],
|
|
268
|
+
configSchema: {
|
|
269
|
+
enabled: { type: 'boolean', required: true, description: 'Enable MySQL' },
|
|
270
|
+
host: { type: 'string', required: true, description: 'Database host' },
|
|
271
|
+
port: { type: 'number', required: false, description: 'Port (default: 3306)' },
|
|
272
|
+
user: { type: 'string', required: true, description: 'Database user' },
|
|
273
|
+
password: { type: 'string', required: true, description: 'Database password' },
|
|
274
|
+
database: { type: 'string', required: true, description: 'Database name' }
|
|
275
|
+
},
|
|
276
|
+
steps: [
|
|
277
|
+
'1. Install MySQL or MariaDB',
|
|
278
|
+
'2. Create a database and user',
|
|
279
|
+
'3. Note connection details'
|
|
280
|
+
]
|
|
281
|
+
},
|
|
282
|
+
mongodb: {
|
|
283
|
+
name: 'mongodb',
|
|
284
|
+
displayName: 'MongoDB',
|
|
285
|
+
description: 'NoSQL Database - MongoDB',
|
|
286
|
+
authType: 'api_key',
|
|
287
|
+
docsUrl: 'https://docs.mongodb.com/',
|
|
288
|
+
envVars: ['MONGO_URI', 'MONGO_DB'],
|
|
289
|
+
configSchema: {
|
|
290
|
+
enabled: { type: 'boolean', required: true, description: 'Enable MongoDB' },
|
|
291
|
+
uri: { type: 'string', required: true, description: 'Connection URI' },
|
|
292
|
+
database: { type: 'string', required: false, description: 'Database name' }
|
|
293
|
+
},
|
|
294
|
+
steps: [
|
|
295
|
+
'1. Install MongoDB or use MongoDB Atlas',
|
|
296
|
+
'2. Get connection URI',
|
|
297
|
+
'3. Create database if needed'
|
|
298
|
+
]
|
|
299
|
+
},
|
|
300
|
+
redis: {
|
|
301
|
+
name: 'redis',
|
|
302
|
+
displayName: 'Redis',
|
|
303
|
+
description: 'In-memory cache and message broker',
|
|
304
|
+
authType: 'none',
|
|
305
|
+
docsUrl: 'https://redis.io/docs/',
|
|
306
|
+
envVars: ['REDIS_HOST', 'REDIS_PORT'],
|
|
307
|
+
configSchema: {
|
|
308
|
+
enabled: { type: 'boolean', required: true, description: 'Enable Redis' },
|
|
309
|
+
host: { type: 'string', required: true, description: 'Redis host' },
|
|
310
|
+
port: { type: 'number', required: false, description: 'Port (default: 6379)' },
|
|
311
|
+
password: { type: 'string', required: false, description: 'Redis password (if auth enabled)' }
|
|
312
|
+
},
|
|
313
|
+
steps: [
|
|
314
|
+
'1. Install Redis: https://redis.io/download',
|
|
315
|
+
'2. Start redis-server',
|
|
316
|
+
'3. Note host and port'
|
|
317
|
+
]
|
|
318
|
+
},
|
|
319
|
+
s3: {
|
|
320
|
+
name: 's3',
|
|
321
|
+
displayName: 'AWS S3',
|
|
322
|
+
description: 'Cloud storage - Amazon S3',
|
|
323
|
+
authType: 'api_key',
|
|
324
|
+
docsUrl: 'https://docs.aws.amazon.com/s3/',
|
|
325
|
+
envVars: ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_REGION', 'AWS_BUCKET'],
|
|
326
|
+
configSchema: {
|
|
327
|
+
enabled: { type: 'boolean', required: true, description: 'Enable S3' },
|
|
328
|
+
accessKeyId: { type: 'string', required: true, description: 'AWS Access Key' },
|
|
329
|
+
secretAccessKey: { type: 'string', required: true, description: 'AWS Secret Key' },
|
|
330
|
+
region: { type: 'string', required: true, description: 'AWS Region' },
|
|
331
|
+
bucket: { type: 'string', required: true, description: 'S3 Bucket name' }
|
|
332
|
+
},
|
|
333
|
+
steps: [
|
|
334
|
+
'1. Go to AWS Console → IAM → Users',
|
|
335
|
+
'2. Create user with S3 permissions',
|
|
336
|
+
'3. Copy Access Key ID and Secret Access Key',
|
|
337
|
+
'4. Note bucket name and region'
|
|
338
|
+
]
|
|
339
|
+
},
|
|
340
|
+
azureblob: {
|
|
341
|
+
name: 'azureblob',
|
|
342
|
+
displayName: 'Azure Blob Storage',
|
|
343
|
+
description: 'Cloud storage - Microsoft Azure',
|
|
344
|
+
authType: 'api_key',
|
|
345
|
+
docsUrl: 'https://docs.microsoft.com/en-us/azure/storage/',
|
|
346
|
+
envVars: ['AZURE_STORAGE_ACCOUNT', 'AZURE_STORAGE_KEY', 'AZURE_CONTAINER'],
|
|
347
|
+
configSchema: {
|
|
348
|
+
enabled: { type: 'boolean', required: true, description: 'Enable Azure Blob' },
|
|
349
|
+
accountName: { type: 'string', required: true, description: 'Storage Account Name' },
|
|
350
|
+
accountKey: { type: 'string', required: true, description: 'Storage Account Key' },
|
|
351
|
+
container: { type: 'string', required: true, description: 'Container name' }
|
|
352
|
+
},
|
|
353
|
+
steps: [
|
|
354
|
+
'1. Go to Azure Portal → Storage Accounts',
|
|
355
|
+
'2. Create or select storage account',
|
|
356
|
+
'3. Go to "Access keys" and copy',
|
|
357
|
+
'4. Create a blob container'
|
|
358
|
+
]
|
|
359
|
+
},
|
|
360
|
+
dropbox: {
|
|
361
|
+
name: 'dropbox',
|
|
362
|
+
displayName: 'Dropbox',
|
|
363
|
+
description: 'Cloud storage - Dropbox',
|
|
364
|
+
authType: 'oauth',
|
|
365
|
+
docsUrl: 'https://www.dropbox.com/developers',
|
|
366
|
+
envVars: ['DROPBOX_APP_KEY', 'DROPBOX_APP_SECRET'],
|
|
367
|
+
configSchema: {
|
|
368
|
+
enabled: { type: 'boolean', required: true, description: 'Enable Dropbox' },
|
|
369
|
+
appKey: { type: 'string', required: true, description: 'App Key' },
|
|
370
|
+
appSecret: { type: 'string', required: true, description: 'App Secret' }
|
|
371
|
+
},
|
|
372
|
+
steps: [
|
|
373
|
+
'1. Go to https://www.dropbox.com/developers/apps',
|
|
374
|
+
'2. Create app → Scoped access → Full Dropbox',
|
|
375
|
+
'3. Add redirect URI',
|
|
376
|
+
'4. Copy App Key and Secret'
|
|
377
|
+
]
|
|
378
|
+
},
|
|
379
|
+
sendgrid: {
|
|
380
|
+
name: 'sendgrid',
|
|
381
|
+
displayName: 'SendGrid',
|
|
382
|
+
description: 'Email delivery service',
|
|
383
|
+
authType: 'api_key',
|
|
384
|
+
docsUrl: 'https://docs.sendgrid.com/',
|
|
385
|
+
envVars: ['SENDGRID_API_KEY'],
|
|
386
|
+
configSchema: {
|
|
387
|
+
enabled: { type: 'boolean', required: true, description: 'Enable SendGrid' },
|
|
388
|
+
apiKey: { type: 'string', required: true, description: 'API Key' },
|
|
389
|
+
fromEmail: { type: 'string', required: true, description: 'Verified sender email' }
|
|
390
|
+
},
|
|
391
|
+
steps: [
|
|
392
|
+
'1. Go to https://sendgrid.com/',
|
|
393
|
+
'2. Sign up and verify sender',
|
|
394
|
+
'3. Go to API Keys → Create API Key',
|
|
395
|
+
'4. Copy the API key'
|
|
396
|
+
]
|
|
397
|
+
},
|
|
398
|
+
stripe: {
|
|
399
|
+
name: 'stripe',
|
|
400
|
+
displayName: 'Stripe',
|
|
401
|
+
description: 'Payment processing',
|
|
402
|
+
authType: 'api_key',
|
|
403
|
+
docsUrl: 'https://stripe.com/docs',
|
|
404
|
+
envVars: ['STRIPE_SECRET_KEY', 'STRIPE_WEBHOOK_SECRET'],
|
|
405
|
+
configSchema: {
|
|
406
|
+
enabled: { type: 'boolean', required: true, description: 'Enable Stripe' },
|
|
407
|
+
secretKey: { type: 'string', required: true, description: 'Secret Key' },
|
|
408
|
+
webhookSecret: { type: 'string', required: false, description: 'Webhook Secret' }
|
|
409
|
+
},
|
|
410
|
+
steps: [
|
|
411
|
+
'1. Go to https://dashboard.stripe.com/apikeys',
|
|
412
|
+
'2. Copy Secret Key (starts with sk_)',
|
|
413
|
+
'3. For webhooks: go to Webhooks → Add endpoint'
|
|
414
|
+
]
|
|
415
|
+
},
|
|
416
|
+
anthropic: {
|
|
417
|
+
name: 'anthropic',
|
|
418
|
+
displayName: 'Anthropic Claude',
|
|
419
|
+
description: 'AI Models - Claude',
|
|
420
|
+
authType: 'api_key',
|
|
421
|
+
docsUrl: 'https://docs.anthropic.com/',
|
|
422
|
+
envVars: ['ANTHROPIC_API_KEY'],
|
|
423
|
+
configSchema: {
|
|
424
|
+
enabled: { type: 'boolean', required: true, description: 'Enable Anthropic' },
|
|
425
|
+
apiKey: { type: 'string', required: true, description: 'API Key' }
|
|
426
|
+
},
|
|
427
|
+
steps: [
|
|
428
|
+
'1. Go to https://console.anthropic.com/',
|
|
429
|
+
'2. Sign up and verify account',
|
|
430
|
+
'3. Go to API Keys → Create Key',
|
|
431
|
+
'4. Copy the API key'
|
|
432
|
+
]
|
|
433
|
+
},
|
|
434
|
+
openai: {
|
|
435
|
+
name: 'openai',
|
|
436
|
+
displayName: 'OpenAI',
|
|
437
|
+
description: 'AI Models - GPT',
|
|
438
|
+
authType: 'api_key',
|
|
439
|
+
docsUrl: 'https://platform.openai.com/docs',
|
|
440
|
+
envVars: ['OPENAI_API_KEY'],
|
|
441
|
+
configSchema: {
|
|
442
|
+
enabled: { type: 'boolean', required: true, description: 'Enable OpenAI' },
|
|
443
|
+
apiKey: { type: 'string', required: true, description: 'API Key' }
|
|
444
|
+
},
|
|
445
|
+
steps: [
|
|
446
|
+
'1. Go to https://platform.openai.com/api-keys',
|
|
447
|
+
'2. Create new secret key',
|
|
448
|
+
'3. Copy the API key'
|
|
449
|
+
]
|
|
450
|
+
},
|
|
451
|
+
modal: {
|
|
452
|
+
name: 'modal',
|
|
453
|
+
displayName: 'Modal',
|
|
454
|
+
description: 'AI Models - Modal Direct',
|
|
455
|
+
authType: 'api_key',
|
|
456
|
+
docsUrl: 'https://modal.com/docs',
|
|
457
|
+
envVars: ['MODAL_API_KEY'],
|
|
458
|
+
configSchema: {
|
|
459
|
+
enabled: { type: 'boolean', required: true, description: 'Enable Modal' },
|
|
460
|
+
apiKey: { type: 'string', required: true, description: 'API Key' }
|
|
461
|
+
},
|
|
462
|
+
steps: [
|
|
463
|
+
'1. Go to https://modal.com/',
|
|
464
|
+
'2. Sign up and get API key',
|
|
465
|
+
'3. Copy from Account Settings'
|
|
466
|
+
]
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
class AutoConfigurer {
|
|
470
|
+
#workspaceDir;
|
|
471
|
+
#logsDir;
|
|
472
|
+
constructor() {
|
|
473
|
+
this.#workspaceDir = '';
|
|
474
|
+
this.#logsDir = '';
|
|
475
|
+
}
|
|
476
|
+
async initialize() {
|
|
477
|
+
this.#workspaceDir = getWorkspaceDir();
|
|
478
|
+
this.#logsDir = path.join(this.#workspaceDir, 'auto-config-logs');
|
|
479
|
+
if (!existsSync(this.#logsDir)) {
|
|
480
|
+
await mkdir(this.#logsDir, { recursive: true });
|
|
481
|
+
}
|
|
482
|
+
void logThought('[AutoConfigurer] Initialized');
|
|
483
|
+
}
|
|
484
|
+
listKnownServices() {
|
|
485
|
+
return Object.values(KNOWN_SERVICES);
|
|
486
|
+
}
|
|
487
|
+
getServiceConfig(serviceName) {
|
|
488
|
+
return KNOWN_SERVICES[serviceName.toLowerCase()];
|
|
489
|
+
}
|
|
490
|
+
async fetchDocsAndConfig(serviceName) {
|
|
491
|
+
const service = KNOWN_SERVICES[serviceName.toLowerCase()];
|
|
492
|
+
if (!service) {
|
|
493
|
+
return {
|
|
494
|
+
config: {},
|
|
495
|
+
steps: [`Unknown service: ${serviceName}`]
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
void logThought(`[AutoConfigurer] Fetching docs for ${serviceName}...`);
|
|
499
|
+
const config = {
|
|
500
|
+
enabled: false
|
|
501
|
+
};
|
|
502
|
+
for (const [key, schema] of Object.entries(service.configSchema)) {
|
|
503
|
+
if (key !== 'enabled') {
|
|
504
|
+
config[key] = '';
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
config,
|
|
509
|
+
steps: service.steps
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
async saveServiceConfig(serviceName, config) {
|
|
513
|
+
const service = KNOWN_SERVICES[serviceName.toLowerCase()];
|
|
514
|
+
if (!service) {
|
|
515
|
+
return {
|
|
516
|
+
success: false,
|
|
517
|
+
service: serviceName,
|
|
518
|
+
message: `Unknown service: ${serviceName}`
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
try {
|
|
522
|
+
const currentConfig = await readConfig();
|
|
523
|
+
const integrationConfig = {
|
|
524
|
+
...currentConfig.integration || {},
|
|
525
|
+
[service.name]: config
|
|
526
|
+
};
|
|
527
|
+
await writeConfig({
|
|
528
|
+
...currentConfig,
|
|
529
|
+
integration: integrationConfig
|
|
530
|
+
});
|
|
531
|
+
await this.#logConfiguration(serviceName, config, 'success');
|
|
532
|
+
void logThought(`[AutoConfigurer] Saved config for ${serviceName}`);
|
|
533
|
+
return {
|
|
534
|
+
success: true,
|
|
535
|
+
service: serviceName,
|
|
536
|
+
config,
|
|
537
|
+
message: `Successfully configured ${service.displayName}`,
|
|
538
|
+
nextSteps: [
|
|
539
|
+
`Restart TwinClaw to activate ${service.displayName}`,
|
|
540
|
+
`Test with: twinclaw channels test ${service.name}`
|
|
541
|
+
]
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
catch (err) {
|
|
545
|
+
await this.#logConfiguration(serviceName, config, 'failed', err);
|
|
546
|
+
return {
|
|
547
|
+
success: false,
|
|
548
|
+
service: serviceName,
|
|
549
|
+
message: err instanceof Error ? err.message : String(err)
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
async #logConfiguration(service, config, status, error) {
|
|
554
|
+
const logFile = path.join(this.#logsDir, `${service}_${Date.now()}.json`);
|
|
555
|
+
const log = {
|
|
556
|
+
service,
|
|
557
|
+
config: { ...config, apiKey: config['apiKey'] ? '***' : undefined },
|
|
558
|
+
status,
|
|
559
|
+
error: error?.toString(),
|
|
560
|
+
timestamp: new Date().toISOString()
|
|
561
|
+
};
|
|
562
|
+
await writeFile(logFile, JSON.stringify(log, null, 2), 'utf-8');
|
|
563
|
+
}
|
|
564
|
+
async checkConfigurationHealth() {
|
|
565
|
+
const health = {};
|
|
566
|
+
for (const [name, service] of Object.entries(KNOWN_SERVICES)) {
|
|
567
|
+
const issues = [];
|
|
568
|
+
for (const [key, schema] of Object.entries(service.configSchema)) {
|
|
569
|
+
if (schema.required && key !== 'enabled') {
|
|
570
|
+
issues.push(`Missing required: ${key}`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
health[name] = {
|
|
574
|
+
configured: issues.length === 0,
|
|
575
|
+
issues
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
return health;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
let autoConfigurer = null;
|
|
582
|
+
export function getAutoConfigurer() {
|
|
583
|
+
if (!autoConfigurer) {
|
|
584
|
+
autoConfigurer = new AutoConfigurer();
|
|
585
|
+
}
|
|
586
|
+
return autoConfigurer;
|
|
587
|
+
}
|
|
588
|
+
export async function initializeAutoConfigurer() {
|
|
589
|
+
const configurer = getAutoConfigurer();
|
|
590
|
+
await configurer.initialize();
|
|
591
|
+
}
|