@agenticmail/enterprise 0.5.432 → 0.5.433

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.
@@ -0,0 +1,1483 @@
1
+ import {
2
+ VALID_CATEGORIES,
3
+ VALID_RISK_LEVELS,
4
+ init_skill_validator,
5
+ validateSkillManifest
6
+ } from "./chunk-22U7TZPN.js";
7
+ import "./chunk-KFQGP6VL.js";
8
+
9
+ // src/engine/cli-build-skill.ts
10
+ init_skill_validator();
11
+ var WELL_KNOWN_SERVICES = {
12
+ notion: {
13
+ description: "Connect to Notion workspaces to manage pages, databases, blocks, and comments. Full CRUD on structured data with rich content support.",
14
+ category: "productivity",
15
+ authType: "token",
16
+ authLabel: "Integration Token",
17
+ authDescription: "Notion internal integration token from https://www.notion.so/my-integrations",
18
+ baseUrl: "https://api.notion.com/v1",
19
+ docsUrl: "https://developers.notion.com",
20
+ rateLimits: { requests: 3, window: "second" },
21
+ suggestedOps: [
22
+ {
23
+ id: "notion_search",
24
+ name: "Search",
25
+ description: "Search across all pages and databases in the workspace by title or content",
26
+ category: "read",
27
+ riskLevel: "low",
28
+ sideEffects: [],
29
+ parameters: {
30
+ query: { type: "string", description: "Search query text", required: true, example: "Q3 roadmap" },
31
+ filter: { type: "string", description: "Filter results by object type", enum: ["page", "database"] },
32
+ sort_direction: { type: "string", description: "Sort order by last edited time", enum: ["ascending", "descending"], default: "descending" },
33
+ page_size: { type: "number", description: "Number of results to return (max 100)", minimum: 1, maximum: 100, default: 10 }
34
+ }
35
+ },
36
+ {
37
+ id: "notion_get_page",
38
+ name: "Get Page",
39
+ description: "Retrieve a page and its properties by ID",
40
+ category: "read",
41
+ riskLevel: "low",
42
+ sideEffects: [],
43
+ parameters: {
44
+ page_id: { type: "string", description: "Notion page ID or URL", required: true, example: "a1b2c3d4-e5f6-..." },
45
+ include_content: { type: "boolean", description: "Also retrieve all block children (page content)", default: false }
46
+ }
47
+ },
48
+ {
49
+ id: "notion_create_page",
50
+ name: "Create Page",
51
+ description: "Create a new page in a database or as a child of another page",
52
+ category: "write",
53
+ riskLevel: "medium",
54
+ sideEffects: ["network-request"],
55
+ parameters: {
56
+ parent_id: { type: "string", description: "Parent page ID or database ID", required: true },
57
+ parent_type: { type: "string", description: "Type of parent", required: true, enum: ["page", "database"] },
58
+ title: { type: "string", description: "Page title", required: true, maxLength: 2e3 },
59
+ properties: { type: "object", description: "Database properties (key-value pairs matching the database schema)" },
60
+ content: { type: "string", description: "Page body content in markdown (converted to Notion blocks)" },
61
+ icon: { type: "string", description: "Page icon (emoji or external URL)", example: "\u{1F4CB}" },
62
+ cover: { type: "string", description: "Cover image URL" }
63
+ }
64
+ },
65
+ {
66
+ id: "notion_update_page",
67
+ name: "Update Page",
68
+ description: "Update a page's properties, icon, cover, or archive status",
69
+ category: "write",
70
+ riskLevel: "medium",
71
+ sideEffects: ["network-request"],
72
+ parameters: {
73
+ page_id: { type: "string", description: "Page ID to update", required: true },
74
+ properties: { type: "object", description: "Properties to update (key-value pairs)" },
75
+ icon: { type: "string", description: "New icon (emoji or URL)" },
76
+ cover: { type: "string", description: "New cover image URL" },
77
+ archived: { type: "boolean", description: "Set to true to archive/trash the page" }
78
+ }
79
+ },
80
+ {
81
+ id: "notion_query_database",
82
+ name: "Query Database",
83
+ description: "Query a Notion database with filters, sorts, and pagination",
84
+ category: "read",
85
+ riskLevel: "low",
86
+ sideEffects: [],
87
+ parameters: {
88
+ database_id: { type: "string", description: "Database ID to query", required: true },
89
+ filter: { type: "object", description: "Notion filter object (see API docs for schema)" },
90
+ sorts: { type: "array", description: "Array of sort objects: [{property, direction}]", items: { type: "object" } },
91
+ page_size: { type: "number", description: "Results per page (max 100)", minimum: 1, maximum: 100, default: 25 },
92
+ start_cursor: { type: "string", description: "Pagination cursor from previous response" }
93
+ }
94
+ },
95
+ {
96
+ id: "notion_list_databases",
97
+ name: "List Databases",
98
+ description: "List all databases the integration has access to",
99
+ category: "read",
100
+ riskLevel: "low",
101
+ sideEffects: [],
102
+ parameters: {
103
+ page_size: { type: "number", description: "Number of results (max 100)", minimum: 1, maximum: 100, default: 25 }
104
+ }
105
+ },
106
+ {
107
+ id: "notion_append_blocks",
108
+ name: "Append Content",
109
+ description: "Append new content blocks to an existing page",
110
+ category: "write",
111
+ riskLevel: "medium",
112
+ sideEffects: ["network-request"],
113
+ parameters: {
114
+ page_id: { type: "string", description: "Page ID to append content to", required: true },
115
+ content: { type: "string", description: "Content in markdown format to append", required: true },
116
+ after: { type: "string", description: "Block ID to insert after (appends to end if omitted)" }
117
+ }
118
+ },
119
+ {
120
+ id: "notion_delete_block",
121
+ name: "Delete Block",
122
+ description: "Delete (archive) a specific content block from a page",
123
+ category: "destroy",
124
+ riskLevel: "high",
125
+ sideEffects: ["deletes-data", "network-request"],
126
+ parameters: {
127
+ block_id: { type: "string", description: "Block ID to delete", required: true }
128
+ }
129
+ },
130
+ {
131
+ id: "notion_add_comment",
132
+ name: "Add Comment",
133
+ description: "Add a comment to a page or discussion thread",
134
+ category: "communicate",
135
+ riskLevel: "medium",
136
+ sideEffects: ["sends-message", "network-request"],
137
+ parameters: {
138
+ page_id: { type: "string", description: "Page ID to comment on", required: true },
139
+ content: { type: "string", description: "Comment text in rich text format", required: true, maxLength: 2e3 },
140
+ discussion_id: { type: "string", description: "Reply to a specific discussion thread" }
141
+ }
142
+ },
143
+ {
144
+ id: "notion_get_comments",
145
+ name: "Get Comments",
146
+ description: "Retrieve all comments on a page or block",
147
+ category: "read",
148
+ riskLevel: "low",
149
+ sideEffects: [],
150
+ parameters: {
151
+ block_id: { type: "string", description: "Page or block ID to get comments for", required: true },
152
+ page_size: { type: "number", description: "Results per page (max 100)", minimum: 1, maximum: 100, default: 25 }
153
+ }
154
+ }
155
+ ]
156
+ },
157
+ slack: {
158
+ description: "Integrate with Slack workspaces to send messages, manage channels, search history, upload files, and handle reactions.",
159
+ category: "communication",
160
+ authType: "oauth2",
161
+ authLabel: "Bot Token",
162
+ authDescription: "Slack Bot User OAuth Token (xoxb-...) from https://api.slack.com/apps",
163
+ baseUrl: "https://slack.com/api",
164
+ docsUrl: "https://api.slack.com/methods",
165
+ rateLimits: { requests: 50, window: "minute" },
166
+ scopes: ["channels:read", "channels:write", "chat:write", "files:read", "files:write", "reactions:read", "reactions:write", "search:read", "users:read"],
167
+ suggestedOps: [
168
+ {
169
+ id: "slack_send_message",
170
+ name: "Send Message",
171
+ description: "Post a message to a Slack channel or DM",
172
+ category: "communicate",
173
+ riskLevel: "medium",
174
+ sideEffects: ["sends-message", "network-request"],
175
+ parameters: {
176
+ channel: { type: "string", description: "Channel ID or name (e.g. #general or C01234)", required: true },
177
+ text: { type: "string", description: "Message text (supports mrkdwn formatting)", required: true, maxLength: 4e4 },
178
+ thread_ts: { type: "string", description: "Thread timestamp to reply in a thread" },
179
+ unfurl_links: { type: "boolean", description: "Enable link previews", default: true },
180
+ blocks: { type: "array", description: "Block Kit blocks for rich layout", items: { type: "object" } }
181
+ }
182
+ },
183
+ {
184
+ id: "slack_list_channels",
185
+ name: "List Channels",
186
+ description: "List public and private channels in the workspace",
187
+ category: "read",
188
+ riskLevel: "low",
189
+ sideEffects: [],
190
+ parameters: {
191
+ types: { type: "string", description: "Comma-separated channel types", default: "public_channel,private_channel", enum: ["public_channel", "private_channel", "mpim", "im"] },
192
+ limit: { type: "number", description: "Max channels to return", minimum: 1, maximum: 1e3, default: 100 },
193
+ exclude_archived: { type: "boolean", description: "Exclude archived channels", default: true }
194
+ }
195
+ },
196
+ {
197
+ id: "slack_search",
198
+ name: "Search Messages",
199
+ description: "Search for messages across the workspace",
200
+ category: "read",
201
+ riskLevel: "low",
202
+ sideEffects: [],
203
+ parameters: {
204
+ query: { type: "string", description: "Search query (supports Slack search modifiers)", required: true, example: "from:@alice in:#engineering after:2024-01-01" },
205
+ sort: { type: "string", description: "Sort order", enum: ["score", "timestamp"], default: "score" },
206
+ count: { type: "number", description: "Number of results", minimum: 1, maximum: 100, default: 20 }
207
+ }
208
+ },
209
+ {
210
+ id: "slack_get_history",
211
+ name: "Channel History",
212
+ description: "Fetch message history from a channel",
213
+ category: "read",
214
+ riskLevel: "low",
215
+ sideEffects: [],
216
+ parameters: {
217
+ channel: { type: "string", description: "Channel ID", required: true },
218
+ limit: { type: "number", description: "Number of messages", minimum: 1, maximum: 1e3, default: 50 },
219
+ oldest: { type: "string", description: "Start of time range (Unix timestamp)" },
220
+ latest: { type: "string", description: "End of time range (Unix timestamp)" }
221
+ }
222
+ },
223
+ {
224
+ id: "slack_upload_file",
225
+ name: "Upload File",
226
+ description: "Upload a file to a channel",
227
+ category: "write",
228
+ riskLevel: "medium",
229
+ sideEffects: ["network-request", "modifies-files"],
230
+ parameters: {
231
+ channel: { type: "string", description: "Channel to share file in", required: true },
232
+ content: { type: "string", description: "File content (for text files)" },
233
+ file_path: { type: "string", description: "Local file path to upload" },
234
+ filename: { type: "string", description: "Filename to display", required: true },
235
+ title: { type: "string", description: "File title" },
236
+ initial_comment: { type: "string", description: "Message to include with the file" }
237
+ }
238
+ },
239
+ {
240
+ id: "slack_react",
241
+ name: "Add Reaction",
242
+ description: "Add an emoji reaction to a message",
243
+ category: "communicate",
244
+ riskLevel: "low",
245
+ sideEffects: ["network-request"],
246
+ parameters: {
247
+ channel: { type: "string", description: "Channel containing the message", required: true },
248
+ timestamp: { type: "string", description: "Message timestamp to react to", required: true },
249
+ name: { type: "string", description: "Emoji name without colons (e.g. thumbsup)", required: true }
250
+ }
251
+ },
252
+ {
253
+ id: "slack_set_topic",
254
+ name: "Set Topic",
255
+ description: "Set the topic of a channel",
256
+ category: "write",
257
+ riskLevel: "medium",
258
+ sideEffects: ["network-request"],
259
+ parameters: {
260
+ channel: { type: "string", description: "Channel ID", required: true },
261
+ topic: { type: "string", description: "New channel topic", required: true, maxLength: 250 }
262
+ }
263
+ },
264
+ {
265
+ id: "slack_list_users",
266
+ name: "List Users",
267
+ description: "List all users in the workspace",
268
+ category: "read",
269
+ riskLevel: "low",
270
+ sideEffects: [],
271
+ parameters: {
272
+ limit: { type: "number", description: "Max users to return", minimum: 1, maximum: 1e3, default: 200 },
273
+ include_locale: { type: "boolean", description: "Include locale data", default: false }
274
+ }
275
+ }
276
+ ]
277
+ },
278
+ airtable: {
279
+ description: "Connect to Airtable bases to query, create, update, and delete records, manage views, and upload attachments.",
280
+ category: "database",
281
+ authType: "token",
282
+ authLabel: "Personal Access Token",
283
+ authDescription: "Airtable personal access token from https://airtable.com/create/tokens",
284
+ baseUrl: "https://api.airtable.com/v0",
285
+ docsUrl: "https://airtable.com/developers/web/api",
286
+ rateLimits: { requests: 5, window: "second" },
287
+ suggestedOps: [
288
+ {
289
+ id: "airtable_list_records",
290
+ name: "List Records",
291
+ description: "List records from a table with optional filtering, sorting, and field selection",
292
+ category: "read",
293
+ riskLevel: "low",
294
+ sideEffects: [],
295
+ parameters: {
296
+ base_id: { type: "string", description: "Airtable base ID (starts with app...)", required: true },
297
+ table: { type: "string", description: "Table name or ID", required: true },
298
+ view: { type: "string", description: "View name or ID to use for default filtering/sorting" },
299
+ filter_formula: { type: "string", description: "Airtable formula to filter records", example: '{Status} = "Active"' },
300
+ sort: { type: "array", description: "Sort specification: [{field, direction}]", items: { type: "object" } },
301
+ fields: { type: "array", description: "Specific field names to return", items: { type: "string" } },
302
+ max_records: { type: "number", description: "Maximum records to return", minimum: 1, maximum: 100, default: 25 },
303
+ page_size: { type: "number", description: "Records per page", minimum: 1, maximum: 100, default: 25 },
304
+ offset: { type: "string", description: "Pagination offset from previous response" }
305
+ }
306
+ },
307
+ {
308
+ id: "airtable_get_record",
309
+ name: "Get Record",
310
+ description: "Retrieve a single record by ID",
311
+ category: "read",
312
+ riskLevel: "low",
313
+ sideEffects: [],
314
+ parameters: {
315
+ base_id: { type: "string", description: "Base ID", required: true },
316
+ table: { type: "string", description: "Table name or ID", required: true },
317
+ record_id: { type: "string", description: "Record ID (starts with rec...)", required: true }
318
+ }
319
+ },
320
+ {
321
+ id: "airtable_create_records",
322
+ name: "Create Records",
323
+ description: "Create one or more records in a table (max 10 per request)",
324
+ category: "write",
325
+ riskLevel: "medium",
326
+ sideEffects: ["network-request"],
327
+ parameters: {
328
+ base_id: { type: "string", description: "Base ID", required: true },
329
+ table: { type: "string", description: "Table name or ID", required: true },
330
+ records: { type: "array", description: "Array of record objects with fields property", required: true, items: { type: "object" } },
331
+ typecast: { type: "boolean", description: "Auto-convert string values to proper field types", default: false }
332
+ }
333
+ },
334
+ {
335
+ id: "airtable_update_records",
336
+ name: "Update Records",
337
+ description: "Update one or more existing records (max 10 per request)",
338
+ category: "write",
339
+ riskLevel: "medium",
340
+ sideEffects: ["network-request"],
341
+ parameters: {
342
+ base_id: { type: "string", description: "Base ID", required: true },
343
+ table: { type: "string", description: "Table name or ID", required: true },
344
+ records: { type: "array", description: "Array of {id, fields} objects", required: true, items: { type: "object" } },
345
+ method: { type: "string", description: "Update method", enum: ["patch", "put"], default: "patch" },
346
+ typecast: { type: "boolean", description: "Auto-convert values", default: false }
347
+ }
348
+ },
349
+ {
350
+ id: "airtable_delete_records",
351
+ name: "Delete Records",
352
+ description: "Delete one or more records by ID (max 10 per request)",
353
+ category: "destroy",
354
+ riskLevel: "high",
355
+ sideEffects: ["deletes-data", "network-request"],
356
+ parameters: {
357
+ base_id: { type: "string", description: "Base ID", required: true },
358
+ table: { type: "string", description: "Table name or ID", required: true },
359
+ record_ids: { type: "array", description: "Record IDs to delete", required: true, items: { type: "string" } }
360
+ }
361
+ },
362
+ {
363
+ id: "airtable_list_bases",
364
+ name: "List Bases",
365
+ description: "List all accessible bases in the workspace",
366
+ category: "read",
367
+ riskLevel: "low",
368
+ sideEffects: [],
369
+ parameters: {
370
+ offset: { type: "string", description: "Pagination offset" }
371
+ }
372
+ },
373
+ {
374
+ id: "airtable_get_schema",
375
+ name: "Get Table Schema",
376
+ description: "Get the schema (fields, types, options) of all tables in a base",
377
+ category: "read",
378
+ riskLevel: "low",
379
+ sideEffects: [],
380
+ parameters: {
381
+ base_id: { type: "string", description: "Base ID", required: true }
382
+ }
383
+ }
384
+ ]
385
+ },
386
+ stripe: {
387
+ description: "Integrate with Stripe for payment processing, customer management, subscription handling, invoice management, and financial reporting.",
388
+ category: "finance",
389
+ authType: "api-key",
390
+ authLabel: "Secret Key",
391
+ authDescription: "Stripe secret API key (sk_live_... or sk_test_...) from https://dashboard.stripe.com/apikeys",
392
+ baseUrl: "https://api.stripe.com/v1",
393
+ docsUrl: "https://docs.stripe.com/api",
394
+ rateLimits: { requests: 100, window: "second" },
395
+ suggestedOps: [
396
+ {
397
+ id: "stripe_list_customers",
398
+ name: "List Customers",
399
+ description: "List customers with optional filtering by email, creation date, or metadata",
400
+ category: "read",
401
+ riskLevel: "low",
402
+ sideEffects: [],
403
+ parameters: {
404
+ email: { type: "string", description: "Filter by exact email address" },
405
+ limit: { type: "number", description: "Number of results (max 100)", minimum: 1, maximum: 100, default: 25 },
406
+ starting_after: { type: "string", description: "Cursor for pagination" },
407
+ created_after: { type: "string", description: "Filter by creation date (ISO 8601)" }
408
+ }
409
+ },
410
+ {
411
+ id: "stripe_create_customer",
412
+ name: "Create Customer",
413
+ description: "Create a new Stripe customer with email, name, and metadata",
414
+ category: "write",
415
+ riskLevel: "medium",
416
+ sideEffects: ["network-request"],
417
+ parameters: {
418
+ email: { type: "string", description: "Customer email address", required: true },
419
+ name: { type: "string", description: "Customer full name" },
420
+ description: { type: "string", description: "Internal description" },
421
+ metadata: { type: "object", description: "Key-value metadata pairs" },
422
+ payment_method: { type: "string", description: "Default payment method ID to attach" }
423
+ }
424
+ },
425
+ {
426
+ id: "stripe_create_invoice",
427
+ name: "Create Invoice",
428
+ description: "Create and optionally finalize/send an invoice",
429
+ category: "write",
430
+ riskLevel: "high",
431
+ sideEffects: ["network-request", "financial"],
432
+ parameters: {
433
+ customer: { type: "string", description: "Customer ID (cus_...)", required: true },
434
+ items: { type: "array", description: "Line items: [{description, amount, quantity}]", required: true, items: { type: "object" } },
435
+ auto_advance: { type: "boolean", description: "Auto-finalize and send", default: false },
436
+ collection_method: { type: "string", description: "How to collect payment", enum: ["charge_automatically", "send_invoice"], default: "send_invoice" },
437
+ days_until_due: { type: "number", description: "Days until invoice is due", default: 30 },
438
+ memo: { type: "string", description: "Internal memo (not shown to customer)" }
439
+ }
440
+ },
441
+ {
442
+ id: "stripe_list_payments",
443
+ name: "List Payments",
444
+ description: "List payment intents with status and amount filtering",
445
+ category: "read",
446
+ riskLevel: "low",
447
+ sideEffects: [],
448
+ parameters: {
449
+ customer: { type: "string", description: "Filter by customer ID" },
450
+ status: { type: "string", description: "Filter by status", enum: ["requires_payment_method", "requires_confirmation", "requires_action", "processing", "succeeded", "canceled"] },
451
+ limit: { type: "number", description: "Number of results", minimum: 1, maximum: 100, default: 25 },
452
+ created_after: { type: "string", description: "Filter by creation date (ISO 8601)" }
453
+ }
454
+ },
455
+ {
456
+ id: "stripe_get_balance",
457
+ name: "Get Balance",
458
+ description: "Retrieve the current account balance (available, pending, connect reserved)",
459
+ category: "read",
460
+ riskLevel: "medium",
461
+ sideEffects: [],
462
+ parameters: {}
463
+ },
464
+ {
465
+ id: "stripe_refund",
466
+ name: "Create Refund",
467
+ description: "Refund a payment (full or partial)",
468
+ category: "write",
469
+ riskLevel: "critical",
470
+ sideEffects: ["financial", "network-request"],
471
+ parameters: {
472
+ payment_intent: { type: "string", description: "Payment Intent ID (pi_...)", required: true },
473
+ amount: { type: "number", description: "Amount to refund in cents (omit for full refund)" },
474
+ reason: { type: "string", description: "Refund reason", enum: ["duplicate", "fraudulent", "requested_by_customer"] }
475
+ }
476
+ }
477
+ ]
478
+ },
479
+ github: {
480
+ description: "Connect to GitHub for repository management, issue tracking, pull request workflows, code search, and Actions CI/CD.",
481
+ category: "development",
482
+ authType: "token",
483
+ authLabel: "Personal Access Token",
484
+ authDescription: "GitHub personal access token (classic or fine-grained) from https://github.com/settings/tokens",
485
+ baseUrl: "https://api.github.com",
486
+ docsUrl: "https://docs.github.com/rest",
487
+ rateLimits: { requests: 5e3, window: "hour" },
488
+ suggestedOps: [
489
+ {
490
+ id: "github_list_repos",
491
+ name: "List Repositories",
492
+ description: "List repositories for the authenticated user or an organization",
493
+ category: "read",
494
+ riskLevel: "low",
495
+ sideEffects: [],
496
+ parameters: {
497
+ owner: { type: "string", description: "User or org name (omit for authenticated user)" },
498
+ type: { type: "string", description: "Repository type filter", enum: ["all", "owner", "public", "private", "member"], default: "all" },
499
+ sort: { type: "string", description: "Sort field", enum: ["created", "updated", "pushed", "full_name"], default: "updated" },
500
+ per_page: { type: "number", description: "Results per page", minimum: 1, maximum: 100, default: 30 }
501
+ }
502
+ },
503
+ {
504
+ id: "github_create_issue",
505
+ name: "Create Issue",
506
+ description: "Create a new issue in a repository",
507
+ category: "write",
508
+ riskLevel: "medium",
509
+ sideEffects: ["network-request"],
510
+ parameters: {
511
+ owner: { type: "string", description: "Repository owner", required: true },
512
+ repo: { type: "string", description: "Repository name", required: true },
513
+ title: { type: "string", description: "Issue title", required: true, maxLength: 256 },
514
+ body: { type: "string", description: "Issue body (Markdown supported)", maxLength: 65536 },
515
+ labels: { type: "array", description: "Label names to apply", items: { type: "string" } },
516
+ assignees: { type: "array", description: "Usernames to assign", items: { type: "string" } },
517
+ milestone: { type: "number", description: "Milestone number to associate" }
518
+ }
519
+ },
520
+ {
521
+ id: "github_list_issues",
522
+ name: "List Issues",
523
+ description: "List and filter issues for a repository",
524
+ category: "read",
525
+ riskLevel: "low",
526
+ sideEffects: [],
527
+ parameters: {
528
+ owner: { type: "string", description: "Repository owner", required: true },
529
+ repo: { type: "string", description: "Repository name", required: true },
530
+ state: { type: "string", description: "Issue state", enum: ["open", "closed", "all"], default: "open" },
531
+ labels: { type: "string", description: "Comma-separated label names" },
532
+ assignee: { type: "string", description: "Filter by assignee username" },
533
+ sort: { type: "string", description: "Sort field", enum: ["created", "updated", "comments"], default: "created" },
534
+ per_page: { type: "number", description: "Results per page", minimum: 1, maximum: 100, default: 30 }
535
+ }
536
+ },
537
+ {
538
+ id: "github_create_pr",
539
+ name: "Create Pull Request",
540
+ description: "Create a pull request from one branch to another",
541
+ category: "write",
542
+ riskLevel: "high",
543
+ sideEffects: ["network-request"],
544
+ parameters: {
545
+ owner: { type: "string", description: "Repository owner", required: true },
546
+ repo: { type: "string", description: "Repository name", required: true },
547
+ title: { type: "string", description: "PR title", required: true },
548
+ head: { type: "string", description: "Source branch (or fork:branch)", required: true },
549
+ base: { type: "string", description: "Target branch", required: true, default: "main" },
550
+ body: { type: "string", description: "PR description (Markdown)" },
551
+ draft: { type: "boolean", description: "Create as draft PR", default: false }
552
+ }
553
+ },
554
+ {
555
+ id: "github_search_code",
556
+ name: "Search Code",
557
+ description: "Search for code across GitHub repositories",
558
+ category: "read",
559
+ riskLevel: "low",
560
+ sideEffects: [],
561
+ parameters: {
562
+ query: { type: "string", description: "Search query (supports GitHub code search syntax)", required: true, example: "addClass repo:jquery/jquery language:js" },
563
+ per_page: { type: "number", description: "Results per page", minimum: 1, maximum: 100, default: 30 }
564
+ }
565
+ },
566
+ {
567
+ id: "github_get_file",
568
+ name: "Get File Contents",
569
+ description: "Get the contents of a file from a repository",
570
+ category: "read",
571
+ riskLevel: "low",
572
+ sideEffects: [],
573
+ parameters: {
574
+ owner: { type: "string", description: "Repository owner", required: true },
575
+ repo: { type: "string", description: "Repository name", required: true },
576
+ path: { type: "string", description: "File path in repo", required: true },
577
+ ref: { type: "string", description: "Branch, tag, or commit SHA", default: "main" }
578
+ }
579
+ },
580
+ {
581
+ id: "github_list_workflows",
582
+ name: "List Workflow Runs",
583
+ description: "List recent GitHub Actions workflow runs",
584
+ category: "read",
585
+ riskLevel: "low",
586
+ sideEffects: [],
587
+ parameters: {
588
+ owner: { type: "string", description: "Repository owner", required: true },
589
+ repo: { type: "string", description: "Repository name", required: true },
590
+ status: { type: "string", description: "Filter by status", enum: ["completed", "action_required", "cancelled", "failure", "neutral", "skipped", "stale", "success", "timed_out", "in_progress", "queued", "requested", "waiting"] },
591
+ per_page: { type: "number", description: "Results per page", minimum: 1, maximum: 100, default: 10 }
592
+ }
593
+ }
594
+ ]
595
+ },
596
+ linear: {
597
+ description: "Connect to Linear for issue tracking, project management, cycle planning, and team workflow automation.",
598
+ category: "project-management",
599
+ authType: "api-key",
600
+ authLabel: "API Key",
601
+ authDescription: "Linear API key from https://linear.app/settings/api",
602
+ baseUrl: "https://api.linear.app/graphql",
603
+ docsUrl: "https://developers.linear.app",
604
+ rateLimits: { requests: 1500, window: "hour" },
605
+ suggestedOps: [
606
+ {
607
+ id: "linear_search_issues",
608
+ name: "Search Issues",
609
+ description: "Search and filter issues across teams and projects",
610
+ category: "read",
611
+ riskLevel: "low",
612
+ sideEffects: [],
613
+ parameters: {
614
+ query: { type: "string", description: "Search query text" },
615
+ team: { type: "string", description: "Team key or ID to filter by" },
616
+ status: { type: "string", description: "Status name to filter by (e.g. In Progress, Done)" },
617
+ assignee: { type: "string", description: "Assignee email or display name" },
618
+ label: { type: "string", description: "Label name to filter by" },
619
+ project: { type: "string", description: "Project name or ID" },
620
+ limit: { type: "number", description: "Max results", minimum: 1, maximum: 250, default: 50 }
621
+ }
622
+ },
623
+ {
624
+ id: "linear_create_issue",
625
+ name: "Create Issue",
626
+ description: "Create a new issue with title, description, assignee, labels, and priority",
627
+ category: "write",
628
+ riskLevel: "medium",
629
+ sideEffects: ["network-request"],
630
+ parameters: {
631
+ title: { type: "string", description: "Issue title", required: true, maxLength: 500 },
632
+ team: { type: "string", description: "Team key (e.g. ENG)", required: true },
633
+ description: { type: "string", description: "Issue description (Markdown)" },
634
+ assignee: { type: "string", description: "Assignee email or ID" },
635
+ priority: { type: "number", description: "Priority (0=none, 1=urgent, 2=high, 3=medium, 4=low)", minimum: 0, maximum: 4 },
636
+ labels: { type: "array", description: "Label names to apply", items: { type: "string" } },
637
+ project: { type: "string", description: "Project name or ID" },
638
+ cycle: { type: "string", description: "Cycle name or ID" },
639
+ estimate: { type: "number", description: "Story point estimate" }
640
+ }
641
+ },
642
+ {
643
+ id: "linear_update_issue",
644
+ name: "Update Issue",
645
+ description: "Update an existing issue's properties",
646
+ category: "write",
647
+ riskLevel: "medium",
648
+ sideEffects: ["network-request"],
649
+ parameters: {
650
+ issue_id: { type: "string", description: "Issue ID or identifier (e.g. ENG-123)", required: true },
651
+ title: { type: "string", description: "New title" },
652
+ status: { type: "string", description: "New status name" },
653
+ assignee: { type: "string", description: "New assignee" },
654
+ priority: { type: "number", description: "New priority (0-4)" },
655
+ description: { type: "string", description: "New description" }
656
+ }
657
+ },
658
+ {
659
+ id: "linear_add_comment",
660
+ name: "Add Comment",
661
+ description: "Add a comment to an issue",
662
+ category: "communicate",
663
+ riskLevel: "medium",
664
+ sideEffects: ["sends-message", "network-request"],
665
+ parameters: {
666
+ issue_id: { type: "string", description: "Issue ID or identifier", required: true },
667
+ body: { type: "string", description: "Comment body (Markdown)", required: true }
668
+ }
669
+ },
670
+ {
671
+ id: "linear_list_teams",
672
+ name: "List Teams",
673
+ description: "List all teams in the workspace",
674
+ category: "read",
675
+ riskLevel: "low",
676
+ sideEffects: [],
677
+ parameters: {}
678
+ },
679
+ {
680
+ id: "linear_list_projects",
681
+ name: "List Projects",
682
+ description: "List projects with optional team and status filtering",
683
+ category: "read",
684
+ riskLevel: "low",
685
+ sideEffects: [],
686
+ parameters: {
687
+ team: { type: "string", description: "Filter by team key" },
688
+ status: { type: "string", description: "Filter by status", enum: ["planned", "started", "paused", "completed", "canceled"] }
689
+ }
690
+ }
691
+ ]
692
+ }
693
+ };
694
+ var OPERATION_BLUEPRINTS = [
695
+ {
696
+ keywords: ["list", "browse", "index"],
697
+ category: "read",
698
+ riskLevel: "low",
699
+ sideEffects: [],
700
+ descriptionTemplate: "List {resource} from {app} with optional filtering and pagination",
701
+ parameterTemplates: {
702
+ filter: { type: "string", description: "Filter expression or search query" },
703
+ limit: { type: "number", description: "Maximum number of results to return", minimum: 1, maximum: 100, default: 25 },
704
+ offset: { type: "string", description: "Pagination cursor or offset" },
705
+ sort_by: { type: "string", description: "Field to sort results by" },
706
+ sort_order: { type: "string", description: "Sort direction", enum: ["asc", "desc"], default: "desc" }
707
+ }
708
+ },
709
+ {
710
+ keywords: ["read", "get", "fetch", "retrieve", "view", "show"],
711
+ category: "read",
712
+ riskLevel: "low",
713
+ sideEffects: [],
714
+ descriptionTemplate: "Retrieve a specific {resource} from {app} by ID",
715
+ parameterTemplates: {
716
+ id: { type: "string", description: "Unique identifier of the {resource}", required: true },
717
+ include: { type: "array", description: "Additional related data to include", items: { type: "string" } }
718
+ }
719
+ },
720
+ {
721
+ keywords: ["search", "query", "find", "lookup"],
722
+ category: "read",
723
+ riskLevel: "low",
724
+ sideEffects: [],
725
+ descriptionTemplate: "Search for {resource} in {app} using filters and queries",
726
+ parameterTemplates: {
727
+ query: { type: "string", description: "Search query text", required: true },
728
+ filters: { type: "object", description: "Additional filter criteria" },
729
+ limit: { type: "number", description: "Maximum results to return", minimum: 1, maximum: 100, default: 25 }
730
+ }
731
+ },
732
+ {
733
+ keywords: ["create", "add", "new", "insert", "post"],
734
+ category: "write",
735
+ riskLevel: "medium",
736
+ sideEffects: ["network-request"],
737
+ descriptionTemplate: "Create a new {resource} in {app}",
738
+ parameterTemplates: {
739
+ name: { type: "string", description: "Name or title of the new {resource}", required: true, maxLength: 500 },
740
+ description: { type: "string", description: "Description or body content" },
741
+ metadata: { type: "object", description: "Additional properties as key-value pairs" }
742
+ }
743
+ },
744
+ {
745
+ keywords: ["update", "edit", "modify", "patch", "change"],
746
+ category: "write",
747
+ riskLevel: "medium",
748
+ sideEffects: ["network-request"],
749
+ descriptionTemplate: "Update an existing {resource} in {app}",
750
+ parameterTemplates: {
751
+ id: { type: "string", description: "ID of the {resource} to update", required: true },
752
+ fields: { type: "object", description: "Fields to update (key-value pairs)", required: true }
753
+ }
754
+ },
755
+ {
756
+ keywords: ["delete", "remove", "destroy", "trash", "archive"],
757
+ category: "destroy",
758
+ riskLevel: "high",
759
+ sideEffects: ["deletes-data", "network-request"],
760
+ descriptionTemplate: "Delete or archive a {resource} in {app}",
761
+ parameterTemplates: {
762
+ id: { type: "string", description: "ID of the {resource} to delete", required: true },
763
+ permanent: { type: "boolean", description: "Permanently delete instead of archiving/trashing", default: false }
764
+ }
765
+ },
766
+ {
767
+ keywords: ["send", "notify", "message", "post", "publish", "share"],
768
+ category: "communicate",
769
+ riskLevel: "medium",
770
+ sideEffects: ["sends-message", "network-request"],
771
+ descriptionTemplate: "Send or publish a {resource} via {app}",
772
+ parameterTemplates: {
773
+ to: { type: "string", description: "Recipient or destination", required: true },
774
+ content: { type: "string", description: "Message or content body", required: true, maxLength: 1e4 },
775
+ subject: { type: "string", description: "Subject line or title" }
776
+ }
777
+ },
778
+ {
779
+ keywords: ["upload", "import", "attach"],
780
+ category: "write",
781
+ riskLevel: "medium",
782
+ sideEffects: ["network-request", "modifies-files"],
783
+ descriptionTemplate: "Upload or import a file/resource to {app}",
784
+ parameterTemplates: {
785
+ file_path: { type: "string", description: "Local file path to upload", required: true },
786
+ destination: { type: "string", description: "Target location or folder in {app}" },
787
+ filename: { type: "string", description: "Override filename" }
788
+ }
789
+ },
790
+ {
791
+ keywords: ["download", "export", "extract"],
792
+ category: "read",
793
+ riskLevel: "low",
794
+ sideEffects: ["modifies-files"],
795
+ descriptionTemplate: "Download or export a {resource} from {app}",
796
+ parameterTemplates: {
797
+ id: { type: "string", description: "ID of the resource to download", required: true },
798
+ format: { type: "string", description: "Export format", enum: ["json", "csv", "pdf", "html"] },
799
+ output_path: { type: "string", description: "Local path to save the file" }
800
+ }
801
+ },
802
+ {
803
+ keywords: ["run", "execute", "trigger", "start", "launch"],
804
+ category: "execute",
805
+ riskLevel: "high",
806
+ sideEffects: ["runs-code", "network-request"],
807
+ descriptionTemplate: "Execute or trigger a {resource} in {app}",
808
+ parameterTemplates: {
809
+ target: { type: "string", description: "ID or name of the resource to execute", required: true },
810
+ params: { type: "object", description: "Execution parameters" },
811
+ async: { type: "boolean", description: "Run asynchronously and return immediately", default: false }
812
+ }
813
+ }
814
+ ];
815
+ async function runBuildSkill(_args) {
816
+ const chalk = (await import("chalk")).default;
817
+ const ora = (await import("ora")).default;
818
+ const inquirer = (await import("inquirer")).default;
819
+ const fs = await import("fs/promises");
820
+ const path = await import("path");
821
+ console.log("");
822
+ console.log(chalk.bold("\u{1F6E0}\uFE0F AgenticMail Enterprise Skill Builder"));
823
+ console.log(chalk.dim(" Generate production-quality skill manifests with rich tool definitions"));
824
+ console.log("");
825
+ const { application } = await inquirer.prompt([{
826
+ type: "input",
827
+ name: "application",
828
+ message: "What application or service should this skill integrate with?",
829
+ validate: (v) => v.trim().length > 0 || "Application name is required"
830
+ }]);
831
+ const appKey = application.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
832
+ const knownService = WELL_KNOWN_SERVICES[appKey] || WELL_KNOWN_SERVICES[application.toLowerCase()];
833
+ let operations;
834
+ let useKnownOps = false;
835
+ if (knownService) {
836
+ console.log(chalk.green(`
837
+ \u2714 Recognized "${application}" \u2014 loading enterprise template with ${knownService.suggestedOps.length} pre-built tools
838
+ `));
839
+ const { usePrebuilt } = await inquirer.prompt([{
840
+ type: "confirm",
841
+ name: "usePrebuilt",
842
+ message: `Use pre-built ${application} tool definitions? (${knownService.suggestedOps.length} tools with full parameters)`,
843
+ default: true
844
+ }]);
845
+ if (usePrebuilt) {
846
+ const { selectedOps } = await inquirer.prompt([{
847
+ type: "checkbox",
848
+ name: "selectedOps",
849
+ message: "Select tools to include:",
850
+ choices: knownService.suggestedOps.map((op) => ({
851
+ name: `${op.name} \u2014 ${op.description} [${op.riskLevel}]`,
852
+ value: op.id,
853
+ checked: true
854
+ })),
855
+ validate: (v) => v.length > 0 || "Select at least one tool"
856
+ }]);
857
+ operations = selectedOps;
858
+ useKnownOps = true;
859
+ } else {
860
+ const { ops } = await inquirer.prompt([{
861
+ type: "input",
862
+ name: "ops",
863
+ message: "What operations should it support? (comma-separated)",
864
+ default: "search, get, create, update, delete, list"
865
+ }]);
866
+ operations = ops.split(",").map((s) => s.trim()).filter(Boolean);
867
+ }
868
+ } else {
869
+ const { ops } = await inquirer.prompt([{
870
+ type: "input",
871
+ name: "ops",
872
+ message: "What operations should it support? (comma-separated)",
873
+ default: "search, get, create, update, delete, list"
874
+ }]);
875
+ operations = ops.split(",").map((s) => s.trim()).filter(Boolean);
876
+ }
877
+ const defaults = knownService ? { category: knownService.category, risk: "medium" } : { category: "productivity", risk: "medium" };
878
+ const { category, risk } = await inquirer.prompt([
879
+ {
880
+ type: "list",
881
+ name: "category",
882
+ message: "Category:",
883
+ choices: [...VALID_CATEGORIES],
884
+ default: defaults.category
885
+ },
886
+ {
887
+ type: "list",
888
+ name: "risk",
889
+ message: "Overall risk level:",
890
+ choices: [...VALID_RISK_LEVELS],
891
+ default: defaults.risk
892
+ }
893
+ ]);
894
+ let authConfig;
895
+ if (knownService) {
896
+ authConfig = {
897
+ type: knownService.authType,
898
+ label: knownService.authLabel,
899
+ description: knownService.authDescription,
900
+ scopes: knownService.scopes
901
+ };
902
+ console.log(chalk.dim(`
903
+ Auth: ${knownService.authType} (${knownService.authLabel})`));
904
+ } else {
905
+ const { authType } = await inquirer.prompt([{
906
+ type: "list",
907
+ name: "authType",
908
+ message: "Authentication method:",
909
+ choices: [
910
+ { name: "API Key \u2014 Simple secret key", value: "api-key" },
911
+ { name: "OAuth 2.0 \u2014 Token-based with scopes", value: "oauth2" },
912
+ { name: "Bearer Token \u2014 Static auth token", value: "token" },
913
+ { name: "Basic Auth \u2014 Username + password", value: "basic" },
914
+ { name: "Custom \u2014 Manual configuration", value: "custom" }
915
+ ]
916
+ }]);
917
+ const authLabels = {
918
+ "api-key": "API Key",
919
+ "oauth2": "OAuth Token",
920
+ "token": "Bearer Token",
921
+ "basic": "Username/Password",
922
+ "custom": "Credentials"
923
+ };
924
+ authConfig = {
925
+ type: authType,
926
+ label: authLabels[authType] || "Credentials",
927
+ description: `${application} ${authLabels[authType] || "credentials"} for authentication`
928
+ };
929
+ }
930
+ const { author, outputDir, addRateLimits } = await inquirer.prompt([
931
+ {
932
+ type: "input",
933
+ name: "author",
934
+ message: "Your GitHub username:",
935
+ validate: (v) => /^[a-zA-Z0-9_-]+$/.test(v.trim()) || "Must be a valid GitHub username"
936
+ },
937
+ {
938
+ type: "input",
939
+ name: "outputDir",
940
+ message: "Output directory:",
941
+ default: `./community-skills/${appKey}`
942
+ },
943
+ {
944
+ type: "confirm",
945
+ name: "addRateLimits",
946
+ message: "Include rate limiting configuration?",
947
+ default: !!knownService?.rateLimits
948
+ }
949
+ ]);
950
+ const appSlug = appKey;
951
+ const toolPrefix = appSlug.replace(/-/g, "_");
952
+ const spinner = ora("Generating enterprise skill manifest...").start();
953
+ let manifest = null;
954
+ if (!useKnownOps) {
955
+ manifest = await tryDirectLLMGeneration(spinner, application, operations, category, risk, author, appSlug, toolPrefix, authConfig);
956
+ }
957
+ if (!manifest && !useKnownOps) {
958
+ manifest = await tryLocalRuntime(spinner, application, operations, category, risk, author, appSlug, toolPrefix);
959
+ }
960
+ if (!manifest) {
961
+ if (useKnownOps && knownService) {
962
+ spinner.text = "Building from enterprise template...";
963
+ const selectedTools = knownService.suggestedOps.filter((op) => operations.includes(op.id));
964
+ manifest = buildKnownServiceManifest(application, knownService, selectedTools, category, risk, author, appSlug, addRateLimits);
965
+ } else {
966
+ spinner.text = "Generating intelligent template...";
967
+ manifest = generateIntelligentTemplate(application, operations, category, risk, author, appSlug, toolPrefix, authConfig, addRateLimits, knownService);
968
+ }
969
+ }
970
+ if (!manifest.configSchema) manifest.configSchema = {};
971
+ if (!manifest.configSchema.apiKey && !manifest.configSchema.token && !manifest.configSchema.clientId) {
972
+ const configKey = authConfig.type === "oauth2" ? "clientId" : authConfig.type === "basic" ? "username" : "apiKey";
973
+ manifest.configSchema[configKey] = {
974
+ type: "secret",
975
+ label: authConfig.label,
976
+ description: authConfig.description,
977
+ required: true
978
+ };
979
+ if (authConfig.type === "basic") {
980
+ manifest.configSchema.password = {
981
+ type: "secret",
982
+ label: "Password",
983
+ description: `${application} password`,
984
+ required: true
985
+ };
986
+ }
987
+ if (authConfig.type === "oauth2") {
988
+ manifest.configSchema.clientSecret = {
989
+ type: "secret",
990
+ label: "Client Secret",
991
+ description: `${application} OAuth client secret`,
992
+ required: true
993
+ };
994
+ if (authConfig.scopes?.length) {
995
+ manifest.configSchema.scopes = {
996
+ type: "string",
997
+ label: "OAuth Scopes",
998
+ description: `Required scopes: ${authConfig.scopes.join(", ")}`,
999
+ default: authConfig.scopes.join(" ")
1000
+ };
1001
+ }
1002
+ }
1003
+ }
1004
+ spinner.succeed("Enterprise skill manifest generated");
1005
+ const validation = validateSkillManifest(manifest);
1006
+ if (!validation.valid) {
1007
+ console.log(chalk.yellow("\n Auto-fixing validation issues..."));
1008
+ if (!manifest.category) manifest.category = category;
1009
+ if (!manifest.risk) manifest.risk = risk;
1010
+ if (!manifest.license) manifest.license = "MIT";
1011
+ if (!manifest.description || manifest.description.length < 20) {
1012
+ manifest.description = `Integrates with ${application} to ${operations.slice(0, 3).join(", ")} and more. Community-contributed skill for AgenticMail agents.`;
1013
+ }
1014
+ }
1015
+ const outDir = path.resolve(outputDir);
1016
+ await fs.mkdir(outDir, { recursive: true });
1017
+ const manifestPath = path.join(outDir, "agenticmail-skill.json");
1018
+ await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
1019
+ console.log(chalk.green(" \u2714") + ` Written: ${manifestPath}`);
1020
+ const readmePath = path.join(outDir, "README.md");
1021
+ await fs.writeFile(readmePath, generateEnterpriseReadme(manifest, authConfig, knownService));
1022
+ console.log(chalk.green(" \u2714") + ` Written: ${readmePath}`);
1023
+ const typesPath = path.join(outDir, "types.d.ts");
1024
+ await fs.writeFile(typesPath, generateTypeDefinitions(manifest));
1025
+ console.log(chalk.green(" \u2714") + ` Written: ${typesPath}`);
1026
+ const finalCheck = validateSkillManifest(manifest);
1027
+ if (finalCheck.valid) {
1028
+ console.log(chalk.green("\n \u2714 Manifest is valid!"));
1029
+ } else {
1030
+ console.log(chalk.yellow("\n \u26A0 Manifest has issues:"));
1031
+ for (const err of finalCheck.errors) console.log(chalk.red(" " + err));
1032
+ }
1033
+ for (const warn of finalCheck.warnings) console.log(chalk.yellow(" \u26A0 " + warn));
1034
+ const toolCount = manifest.tools?.length || 0;
1035
+ const paramCount = (manifest.tools || []).reduce((sum, t) => sum + Object.keys(t.parameters || {}).length, 0);
1036
+ console.log("");
1037
+ console.log(chalk.bold(" \u{1F4CA} Summary"));
1038
+ console.log(` Tools: ${chalk.cyan(toolCount)}`);
1039
+ console.log(` Parameters: ${chalk.cyan(paramCount)} total across all tools`);
1040
+ console.log(` Category: ${chalk.cyan(category)}`);
1041
+ console.log(` Risk: ${chalk.cyan(risk)}`);
1042
+ console.log(` Auth: ${chalk.cyan(authConfig.type)}`);
1043
+ if (manifest.rateLimits) {
1044
+ console.log(` Rate limit: ${chalk.cyan(`${manifest.rateLimits.requests}/${manifest.rateLimits.window}`)}`);
1045
+ }
1046
+ console.log("");
1047
+ const { submit } = await inquirer.prompt([{
1048
+ type: "confirm",
1049
+ name: "submit",
1050
+ message: "Submit this skill as a PR to agenticmail/enterprise?",
1051
+ default: false
1052
+ }]);
1053
+ if (submit) {
1054
+ const { runSubmitSkill } = await import("./cli-submit-skill-S7UMEPH5.js");
1055
+ await runSubmitSkill([outDir]);
1056
+ } else {
1057
+ console.log(chalk.dim("\n To submit later: npx @agenticmail/enterprise submit-skill " + outputDir));
1058
+ console.log(chalk.dim(" To validate: npx @agenticmail/enterprise validate " + outputDir));
1059
+ }
1060
+ }
1061
+ async function tryDirectLLMGeneration(spinner, app, operations, category, risk, author, slug, prefix, authConfig) {
1062
+ const anthropicKey = process.env.ANTHROPIC_API_KEY;
1063
+ const openaiKey = process.env.OPENAI_API_KEY;
1064
+ if (!anthropicKey && !openaiKey) return null;
1065
+ try {
1066
+ spinner.text = "Using AI to generate rich tool definitions...";
1067
+ const prompt = buildEnterpriseAIPrompt(app, operations, category, risk, author, slug, prefix, authConfig);
1068
+ if (anthropicKey) {
1069
+ const res = await fetch("https://api.anthropic.com/v1/messages", {
1070
+ method: "POST",
1071
+ headers: {
1072
+ "Content-Type": "application/json",
1073
+ "x-api-key": anthropicKey,
1074
+ "anthropic-version": "2023-06-01"
1075
+ },
1076
+ body: JSON.stringify({
1077
+ model: "claude-sonnet-4-20250514",
1078
+ max_tokens: 8192,
1079
+ messages: [{ role: "user", content: prompt }]
1080
+ }),
1081
+ signal: AbortSignal.timeout(6e4)
1082
+ });
1083
+ if (res.ok) {
1084
+ const data = await res.json();
1085
+ const content = data.content?.[0]?.text || "";
1086
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
1087
+ if (jsonMatch) return JSON.parse(jsonMatch[0]);
1088
+ }
1089
+ } else if (openaiKey) {
1090
+ const res = await fetch("https://api.openai.com/v1/chat/completions", {
1091
+ method: "POST",
1092
+ headers: {
1093
+ "Content-Type": "application/json",
1094
+ "Authorization": `Bearer ${openaiKey}`
1095
+ },
1096
+ body: JSON.stringify({
1097
+ model: "gpt-4o",
1098
+ messages: [
1099
+ { role: "system", content: "You are a skill manifest generator. Output ONLY valid JSON." },
1100
+ { role: "user", content: prompt }
1101
+ ],
1102
+ max_tokens: 8192
1103
+ }),
1104
+ signal: AbortSignal.timeout(6e4)
1105
+ });
1106
+ if (res.ok) {
1107
+ const data = await res.json();
1108
+ const content = data.choices?.[0]?.message?.content || "";
1109
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
1110
+ if (jsonMatch) return JSON.parse(jsonMatch[0]);
1111
+ }
1112
+ }
1113
+ } catch {
1114
+ }
1115
+ return null;
1116
+ }
1117
+ async function tryLocalRuntime(spinner, app, operations, category, risk, author, slug, prefix) {
1118
+ try {
1119
+ const res = await fetch("http://localhost:3000/health", { signal: AbortSignal.timeout(2e3) });
1120
+ if (!res.ok) return null;
1121
+ spinner.text = "Agent runtime detected \u2014 using AI to generate manifest...";
1122
+ const aiRes = await fetch("http://localhost:3000/api/chat", {
1123
+ method: "POST",
1124
+ headers: { "Content-Type": "application/json" },
1125
+ body: JSON.stringify({
1126
+ message: buildEnterpriseAIPrompt(app, operations, category, risk, author, slug, prefix, {}),
1127
+ system: "You are a skill manifest generator. Respond with ONLY valid JSON, no markdown, no explanation."
1128
+ }),
1129
+ signal: AbortSignal.timeout(3e4)
1130
+ });
1131
+ if (aiRes.ok) {
1132
+ const aiData = await aiRes.json();
1133
+ const content = aiData.response || aiData.message || "";
1134
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
1135
+ if (jsonMatch) return JSON.parse(jsonMatch[0]);
1136
+ }
1137
+ } catch {
1138
+ }
1139
+ return null;
1140
+ }
1141
+ function buildEnterpriseAIPrompt(app, operations, category, risk, author, slug, prefix, authConfig) {
1142
+ return `Generate a production-quality agenticmail-skill.json manifest for "${app}".
1143
+
1144
+ CRITICAL: Each tool MUST have detailed parameters with proper JSON Schema. Generic tools without parameters are NOT acceptable.
1145
+
1146
+ Requirements:
1147
+ - id: "${slug}"
1148
+ - name: "${app}"
1149
+ - Operations to create tools for: ${operations.join(", ")}
1150
+ - category: "${category}"
1151
+ - risk: "${risk}"
1152
+ - author: "${author}"
1153
+ - repository: "https://github.com/${author}/${slug}"
1154
+ - license: "MIT"
1155
+ - version: "1.0.0"
1156
+ - minEngineVersion: "0.3.0"
1157
+ - description: 20-500 chars, specific to what the integration does
1158
+
1159
+ Each tool object needs:
1160
+ - id: "${prefix}_<action>" (lowercase, underscores)
1161
+ - name: Human-readable name
1162
+ - description: Specific, detailed description of what this tool does (not generic)
1163
+ - category: one of [read, write, execute, communicate, destroy]
1164
+ - riskLevel: one of [low, medium, high, critical]
1165
+ - sideEffects: array of valid effects [sends-email, sends-message, sends-sms, posts-social, runs-code, modifies-files, deletes-data, network-request, controls-device, accesses-secrets, financial]
1166
+ - parameters: Object where each key is a parameter name and value is: { type: "string"|"number"|"boolean"|"array"|"object", description: "...", required?: boolean, default?: any, enum?: [...], minimum?: number, maximum?: number, maxLength?: number, items?: {type: "..."}, example?: any }
1167
+
1168
+ IMPORTANT: Every tool must have meaningful parameters based on the real ${app} API. For example:
1169
+ - A "list" tool should have: limit, offset/cursor, filter, sort parameters
1170
+ - A "create" tool should have: all required fields for that resource
1171
+ - A "search" tool should have: query, filters, result count
1172
+ - A "delete" tool should have: id, permanent/force flag
1173
+
1174
+ Include a configSchema with authentication configuration.
1175
+ ${authConfig.type ? `Auth type: ${authConfig.type}` : ""}
1176
+ Include tags array with relevant keywords (lowercase, hyphenated).
1177
+
1178
+ Output ONLY the JSON object, no markdown fences, no explanation.`;
1179
+ }
1180
+ function buildKnownServiceManifest(app, service, selectedTools, category, risk, author, slug, addRateLimits) {
1181
+ const manifest = {
1182
+ id: slug,
1183
+ name: app,
1184
+ description: service.description,
1185
+ version: "1.0.0",
1186
+ author,
1187
+ repository: `https://github.com/${author}/${slug}`,
1188
+ license: "MIT",
1189
+ category,
1190
+ risk,
1191
+ tags: [slug, category, ...extraTags(category)],
1192
+ tools: selectedTools.map((op) => ({
1193
+ id: op.id,
1194
+ name: op.name,
1195
+ description: op.description,
1196
+ category: op.category,
1197
+ riskLevel: op.riskLevel,
1198
+ sideEffects: op.sideEffects,
1199
+ parameters: op.parameters
1200
+ })),
1201
+ configSchema: {},
1202
+ minEngineVersion: "0.3.0",
1203
+ homepage: service.docsUrl || `https://github.com/${author}/${slug}`
1204
+ };
1205
+ if (service.baseUrl) manifest.baseUrl = service.baseUrl;
1206
+ if (addRateLimits && service.rateLimits) manifest.rateLimits = service.rateLimits;
1207
+ return manifest;
1208
+ }
1209
+ function generateIntelligentTemplate(app, operations, category, risk, author, slug, prefix, authConfig, addRateLimits, knownService) {
1210
+ const tools = operations.map((op) => {
1211
+ const opLower = op.toLowerCase().trim();
1212
+ const opSlug = opLower.replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
1213
+ const blueprint = OPERATION_BLUEPRINTS.find(
1214
+ (bp) => bp.keywords.some((kw) => opLower.includes(kw))
1215
+ ) || OPERATION_BLUEPRINTS[0];
1216
+ const description = blueprint.descriptionTemplate.replace("{resource}", pluralize(opSlug, app)).replace("{app}", app);
1217
+ const parameters = {};
1218
+ for (const [key, param] of Object.entries(blueprint.parameterTemplates)) {
1219
+ parameters[key] = {
1220
+ ...param,
1221
+ description: param.description.replace("{resource}", "resource")
1222
+ };
1223
+ }
1224
+ return {
1225
+ id: `${prefix}_${opSlug}`,
1226
+ name: op.charAt(0).toUpperCase() + op.slice(1).trim(),
1227
+ description,
1228
+ category: blueprint.category,
1229
+ riskLevel: blueprint.riskLevel,
1230
+ sideEffects: [...blueprint.sideEffects],
1231
+ parameters
1232
+ };
1233
+ });
1234
+ const manifest = {
1235
+ id: slug,
1236
+ name: app,
1237
+ description: `Integrates with ${app} to ${operations.slice(0, 3).join(", ")}${operations.length > 3 ? ", and more" : ""}. Community-contributed skill for AgenticMail Enterprise agents.`,
1238
+ version: "1.0.0",
1239
+ author,
1240
+ repository: `https://github.com/${author}/${slug}`,
1241
+ license: "MIT",
1242
+ category,
1243
+ risk,
1244
+ tags: [slug, category, ...extraTags(category)],
1245
+ tools,
1246
+ configSchema: {},
1247
+ minEngineVersion: "0.3.0",
1248
+ homepage: `https://github.com/${author}/${slug}`
1249
+ };
1250
+ if (addRateLimits) {
1251
+ manifest.rateLimits = { requests: 60, window: "minute" };
1252
+ }
1253
+ return manifest;
1254
+ }
1255
+ function pluralize(op, app) {
1256
+ const readOps = ["list", "search", "query", "browse", "index"];
1257
+ if (readOps.some((r) => op.includes(r))) return "resources";
1258
+ return "a resource";
1259
+ }
1260
+ function extraTags(category) {
1261
+ const tagMap = {
1262
+ "productivity": ["workflow", "automation"],
1263
+ "communication": ["messaging", "chat"],
1264
+ "development": ["dev-tools", "engineering"],
1265
+ "finance": ["payments", "billing"],
1266
+ "project-management": ["tasks", "agile"],
1267
+ "database": ["data", "storage"],
1268
+ "marketing": ["campaigns", "analytics"],
1269
+ "crm": ["sales", "contacts"],
1270
+ "customer-support": ["helpdesk", "tickets"]
1271
+ };
1272
+ return tagMap[category] || [];
1273
+ }
1274
+ function generateEnterpriseReadme(manifest, authConfig, knownService) {
1275
+ const toolRows = (manifest.tools || []).map((t) => {
1276
+ const paramCount = Object.keys(t.parameters || {}).length;
1277
+ const sideEffectBadges = (t.sideEffects || []).map((se) => `\`${se}\``).join(" ");
1278
+ return `| \`${t.id}\` | ${t.name} | ${t.description} | ${t.riskLevel || "medium"} | ${paramCount} | ${sideEffectBadges || "\u2014"} |`;
1279
+ }).join("\n");
1280
+ const configSection = manifest.configSchema ? Object.entries(manifest.configSchema).map(
1281
+ ([k, v]) => `| \`${k}\` | ${v.type || "string"} | ${v.description || k} | ${v.required ? "\u2705" : "\u2014"} | ${v.default || "\u2014"} |`
1282
+ ).join("\n") : "";
1283
+ const toolDetails = (manifest.tools || []).map((t) => {
1284
+ const params = t.parameters || {};
1285
+ if (Object.keys(params).length === 0) return "";
1286
+ const paramRows = Object.entries(params).map(([name, p]) => {
1287
+ const flags = [];
1288
+ if (p.required) flags.push("**required**");
1289
+ if (p.default !== void 0) flags.push(`default: \`${p.default}\``);
1290
+ if (p.enum) flags.push(`enum: ${p.enum.map((e) => `\`${e}\``).join(", ")}`);
1291
+ if (p.minimum !== void 0) flags.push(`min: ${p.minimum}`);
1292
+ if (p.maximum !== void 0) flags.push(`max: ${p.maximum}`);
1293
+ if (p.maxLength !== void 0) flags.push(`maxLength: ${p.maxLength}`);
1294
+ return `| \`${name}\` | \`${p.type}\` | ${p.description || "\u2014"} | ${flags.join(", ") || "\u2014"} |`;
1295
+ }).join("\n");
1296
+ return `### \`${t.id}\` \u2014 ${t.name}
1297
+
1298
+ ${t.description}
1299
+
1300
+ | Parameter | Type | Description | Constraints |
1301
+ |-----------|------|-------------|-------------|
1302
+ ${paramRows}`;
1303
+ }).filter(Boolean).join("\n\n");
1304
+ let readme = `# ${manifest.name}
1305
+
1306
+ ${manifest.description}
1307
+
1308
+ ${knownService?.docsUrl ? `**API Documentation:** ${knownService.docsUrl}` : ""}
1309
+ ${manifest.baseUrl ? `**Base URL:** \`${manifest.baseUrl}\`` : ""}
1310
+
1311
+ ---
1312
+
1313
+ ## Tools Overview
1314
+
1315
+ | ID | Name | Description | Risk | Params | Side Effects |
1316
+ |----|------|-------------|------|--------|-------------|
1317
+ ${toolRows}
1318
+
1319
+ ---
1320
+
1321
+ ## Tool Reference
1322
+
1323
+ ${toolDetails}
1324
+
1325
+ ---
1326
+
1327
+ ## Configuration
1328
+
1329
+ | Key | Type | Description | Required | Default |
1330
+ |-----|------|-------------|----------|---------|
1331
+ ${configSection}
1332
+
1333
+ ${authConfig.type === "oauth2" ? `
1334
+ ### OAuth 2.0 Setup
1335
+
1336
+ 1. Create an OAuth app in your ${manifest.name} developer settings
1337
+ 2. Set the redirect URI to your AgenticMail dashboard URL + \`/api/engine/oauth/callback\`
1338
+ 3. Copy the Client ID and Client Secret into the skill configuration
1339
+ ${authConfig.scopes ? `4. Required scopes: \`${authConfig.scopes.join("`, `")}\`` : ""}
1340
+ ` : ""}
1341
+ ${authConfig.type === "api-key" || authConfig.type === "token" ? `
1342
+ ### API Key Setup
1343
+
1344
+ 1. Go to your ${manifest.name} account settings
1345
+ ${knownService?.authDescription ? `2. ${knownService.authDescription.replace(/^.*from /, "Navigate to ")}` : "2. Generate a new API key"}
1346
+ 3. Copy the key into the skill configuration in the dashboard
1347
+ ` : ""}
1348
+ `;
1349
+ if (manifest.rateLimits) {
1350
+ readme += `
1351
+ ## Rate Limits
1352
+
1353
+ This skill respects ${manifest.name}'s API rate limits: **${manifest.rateLimits.requests} requests per ${manifest.rateLimits.window}**.
1354
+
1355
+ AgenticMail automatically handles rate limiting with exponential backoff and retry.
1356
+
1357
+ `;
1358
+ }
1359
+ readme += `---
1360
+
1361
+ ## Installation
1362
+
1363
+ ### From the Dashboard
1364
+
1365
+ 1. Go to **Community Skills** in the sidebar
1366
+ 2. Search for "${manifest.name}"
1367
+ 3. Click **Install**
1368
+ 4. Configure credentials in **Skill Connections**
1369
+
1370
+ ### Via API
1371
+
1372
+ \`\`\`bash
1373
+ curl -X POST /api/engine/community/skills/${manifest.id}/install \\
1374
+ -H "Content-Type: application/json" \\
1375
+ -H "Authorization: Bearer <token>" \\
1376
+ -d '{"orgId": "your-org-id"}'
1377
+ \`\`\`
1378
+
1379
+ ### Assign to an Agent
1380
+
1381
+ \`\`\`bash
1382
+ curl -X POST /api/engine/agents/<agent-id>/skills \\
1383
+ -H "Content-Type: application/json" \\
1384
+ -H "Authorization: Bearer <token>" \\
1385
+ -d '{"skillId": "${manifest.id}"}'
1386
+ \`\`\`
1387
+
1388
+ ---
1389
+
1390
+ ## Development
1391
+
1392
+ \`\`\`bash
1393
+ # Validate manifest
1394
+ npx @agenticmail/enterprise validate ./
1395
+
1396
+ # Submit to marketplace
1397
+ npx @agenticmail/enterprise submit-skill ./
1398
+ \`\`\`
1399
+
1400
+ ## License
1401
+
1402
+ ${manifest.license || "MIT"}
1403
+ `;
1404
+ return readme;
1405
+ }
1406
+ function generateTypeDefinitions(manifest) {
1407
+ let types = `/**
1408
+ * Auto-generated type definitions for ${manifest.name} skill
1409
+ * Generated by AgenticMail Enterprise Skill Builder
1410
+ */
1411
+
1412
+ `;
1413
+ for (const tool of manifest.tools || []) {
1414
+ const interfaceName = tool.id.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("") + "Params";
1415
+ const params = tool.parameters || {};
1416
+ const entries = Object.entries(params);
1417
+ if (entries.length === 0) {
1418
+ types += `/** ${tool.description} */
1419
+ export type ${interfaceName} = Record<string, never>;
1420
+
1421
+ `;
1422
+ continue;
1423
+ }
1424
+ types += `/** ${tool.description} */
1425
+ export interface ${interfaceName} {
1426
+ `;
1427
+ for (const [name, param] of entries) {
1428
+ const tsType = paramToTsType(param);
1429
+ const optional = !param.required ? "?" : "";
1430
+ const doc = param.description || "";
1431
+ const extra = [];
1432
+ if (param.default !== void 0) extra.push(`@default ${JSON.stringify(param.default)}`);
1433
+ if (param.enum) extra.push(`@enum ${JSON.stringify(param.enum)}`);
1434
+ if (param.example !== void 0) extra.push(`@example ${JSON.stringify(param.example)}`);
1435
+ types += ` /** ${doc}${extra.length ? " " + extra.join(" ") : ""} */
1436
+ `;
1437
+ types += ` ${name}${optional}: ${tsType};
1438
+ `;
1439
+ }
1440
+ types += `}
1441
+
1442
+ `;
1443
+ }
1444
+ const configEntries = Object.entries(manifest.configSchema || {});
1445
+ if (configEntries.length > 0) {
1446
+ types += `/** Skill configuration */
1447
+ export interface ${manifest.id.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("")}Config {
1448
+ `;
1449
+ for (const [name, param] of configEntries) {
1450
+ const optional = !param.required ? "?" : "";
1451
+ types += ` /** ${param.description || name} */
1452
+ `;
1453
+ types += ` ${name}${optional}: string;
1454
+ `;
1455
+ }
1456
+ types += `}
1457
+ `;
1458
+ }
1459
+ return types;
1460
+ }
1461
+ function paramToTsType(param) {
1462
+ if (param.enum) return param.enum.map((e) => JSON.stringify(e)).join(" | ");
1463
+ switch (param.type) {
1464
+ case "string":
1465
+ return "string";
1466
+ case "number":
1467
+ return "number";
1468
+ case "boolean":
1469
+ return "boolean";
1470
+ case "array":
1471
+ if (param.items?.type === "string") return "string[]";
1472
+ if (param.items?.type === "number") return "number[]";
1473
+ if (param.items?.type === "object") return "Record<string, unknown>[]";
1474
+ return "unknown[]";
1475
+ case "object":
1476
+ return "Record<string, unknown>";
1477
+ default:
1478
+ return "unknown";
1479
+ }
1480
+ }
1481
+ export {
1482
+ runBuildSkill
1483
+ };