@crewx/cli 0.9.0-rc.20 → 0.9.0-rc.200

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/bootstrap/codex-writable-roots.d.ts +20 -1
  2. package/dist/bootstrap/codex-writable-roots.js +1 -88
  3. package/dist/bootstrap/crewx-cli.js +1 -63
  4. package/dist/bootstrap/rate-limit-snapshot.d.ts +15 -0
  5. package/dist/bootstrap/rate-limit-snapshot.js +1 -0
  6. package/dist/builtin.d.ts +1 -1
  7. package/dist/builtin.js +5 -127
  8. package/dist/commands/agent.d.ts +1 -0
  9. package/dist/commands/agent.js +3 -246
  10. package/dist/commands/ai-account.d.ts +16 -0
  11. package/dist/commands/ai-account.js +38 -0
  12. package/dist/commands/cal.d.ts +15 -0
  13. package/dist/commands/cal.js +2 -0
  14. package/dist/commands/command-help.d.ts +28 -0
  15. package/dist/commands/command-help.js +87 -0
  16. package/dist/commands/db.d.ts +3 -0
  17. package/dist/commands/db.js +59 -125
  18. package/dist/commands/detached.d.ts +121 -0
  19. package/dist/commands/detached.js +15 -0
  20. package/dist/commands/doctor.d.ts +77 -0
  21. package/dist/commands/doctor.js +4 -196
  22. package/dist/commands/emit-trailer.d.ts +25 -0
  23. package/dist/commands/emit-trailer.js +2 -0
  24. package/dist/commands/execute.d.ts +8 -4
  25. package/dist/commands/execute.js +15 -146
  26. package/dist/commands/history.d.ts +18 -0
  27. package/dist/commands/history.js +2 -0
  28. package/dist/commands/hook/command-marker.d.ts +2 -0
  29. package/dist/commands/hook/command-marker.js +1 -0
  30. package/dist/commands/hook/install.d.ts +0 -1
  31. package/dist/commands/hook/install.js +8 -175
  32. package/dist/commands/hook/paths.js +1 -94
  33. package/dist/commands/hook/status.js +1 -86
  34. package/dist/commands/hook/uninstall.js +1 -71
  35. package/dist/commands/hook-dispatch.js +2 -180
  36. package/dist/commands/init.d.ts +34 -0
  37. package/dist/commands/init.js +38 -371
  38. package/dist/commands/kill.d.ts +2 -8
  39. package/dist/commands/kill.js +1 -52
  40. package/dist/commands/log.js +3 -95
  41. package/dist/commands/parse-agent-message.js +1 -52
  42. package/dist/commands/parse-common-flags.d.ts +5 -1
  43. package/dist/commands/parse-common-flags.js +3 -181
  44. package/dist/commands/ps.d.ts +2 -2
  45. package/dist/commands/ps.js +2 -107
  46. package/dist/commands/publish.d.ts +1 -0
  47. package/dist/commands/publish.js +27 -0
  48. package/dist/commands/query.d.ts +7 -2
  49. package/dist/commands/query.js +16 -146
  50. package/dist/commands/registry.js +1 -31
  51. package/dist/commands/resolve-prompt.js +3 -54
  52. package/dist/commands/resolve-recipient.d.ts +14 -0
  53. package/dist/commands/resolve-recipient.js +3 -0
  54. package/dist/commands/restart.d.ts +3 -1
  55. package/dist/commands/restart.js +18 -162
  56. package/dist/commands/result.d.ts +8 -3
  57. package/dist/commands/result.js +1 -70
  58. package/dist/commands/shortcut.d.ts +1 -0
  59. package/dist/commands/shortcut.js +18 -0
  60. package/dist/commands/slack.js +16 -559
  61. package/dist/commands/stop.d.ts +8 -0
  62. package/dist/commands/stop.js +8 -0
  63. package/dist/commands/write-output.js +1 -24
  64. package/dist/index.d.ts +1 -1
  65. package/dist/index.js +1 -5
  66. package/dist/logging.d.ts +1 -1
  67. package/dist/logging.js +10 -86
  68. package/dist/main.d.ts +3 -2
  69. package/dist/main.js +51 -253
  70. package/dist/register-builtin-tools.js +1 -9
  71. package/dist/slack/file-download.js +1 -134
  72. package/dist/slack/markdown.js +1 -33
  73. package/dist/utils/env-defaults.d.ts +2 -5
  74. package/dist/utils/env-defaults.js +1 -10
  75. package/dist/utils/inherited-trace.d.ts +24 -0
  76. package/dist/utils/inherited-trace.js +1 -25
  77. package/dist/utils/product-package-root.d.ts +6 -0
  78. package/dist/utils/product-package-root.js +1 -0
  79. package/dist/utils/sdk-compat.d.ts +24 -0
  80. package/dist/utils/sdk-compat.js +1 -0
  81. package/dist/utils/version.js +1 -28
  82. package/package.json +16 -12
@@ -1,559 +1,16 @@
1
- "use strict";
2
- /**
3
- * Slack files command — crewx slack:files [--thread <thread_ts>] [--list] [--clean]
4
- *
5
- * Ported 1:1 from packages/cli-bak/src/cli/slack-files.handler.ts.
6
- * Implements download/list/clean operations without NestJS or @slack/web-api.
7
- * Uses Node.js built-in https and fetch for Slack API calls.
8
- */
9
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- var desc = Object.getOwnPropertyDescriptor(m, k);
12
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
- desc = { enumerable: true, get: function() { return m[k]; } };
14
- }
15
- Object.defineProperty(o, k2, desc);
16
- }) : (function(o, m, k, k2) {
17
- if (k2 === undefined) k2 = k;
18
- o[k2] = m[k];
19
- }));
20
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
- Object.defineProperty(o, "default", { enumerable: true, value: v });
22
- }) : function(o, v) {
23
- o["default"] = v;
24
- });
25
- var __importStar = (this && this.__importStar) || (function () {
26
- var ownKeys = function(o) {
27
- ownKeys = Object.getOwnPropertyNames || function (o) {
28
- var ar = [];
29
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
- return ar;
31
- };
32
- return ownKeys(o);
33
- };
34
- return function (mod) {
35
- if (mod && mod.__esModule) return mod;
36
- var result = {};
37
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
- __setModuleDefault(result, mod);
39
- return result;
40
- };
41
- })();
42
- Object.defineProperty(exports, "__esModule", { value: true });
43
- exports.handleSlack = handleSlack;
44
- const fs = __importStar(require("fs"));
45
- const path = __importStar(require("path"));
46
- const https = __importStar(require("https"));
47
- const adapter_slack_1 = require("@crewx/adapter-slack");
48
- const crewx_cli_1 = require("../bootstrap/crewx-cli");
49
- const markdown_1 = require("../slack/markdown");
50
- const file_download_1 = require("../slack/file-download");
51
- // ─── Entry point ──────────────────────────────────────────────────────────
52
- /**
53
- * Handle `crewx slack` / `crewx slack:files` commands.
54
- * Subcommand is determined by flags: --list → list, --clean → clean, default → download.
55
- */
56
- async function handleSlack(args) {
57
- if (args.includes('--mode') || args.includes('--agent')) {
58
- return await handleBotMode(args);
59
- }
60
- try {
61
- const subCommand = getSubcommand(args);
62
- const threadId = getThreadOption(args);
63
- const botToken = process.env.SLACK_BOT_TOKEN;
64
- if (!botToken && subCommand === 'download') {
65
- console.error('\n❌ Error: SLACK_BOT_TOKEN environment variable not found\n');
66
- console.log('Set your Slack bot token:');
67
- console.log(' export SLACK_BOT_TOKEN=xoxb-your-token\n');
68
- process.exit(1);
69
- }
70
- switch (subCommand) {
71
- case 'list':
72
- await handleListFiles(threadId);
73
- break;
74
- case 'clean':
75
- await handleCleanFiles(threadId);
76
- break;
77
- case 'download':
78
- default:
79
- await handleDownloadFiles(botToken, threadId);
80
- break;
81
- }
82
- }
83
- catch (error) {
84
- const errorMessage = error instanceof Error ? error.message : String(error);
85
- console.error(`\n❌ Slack files command failed:\n${errorMessage}\n`);
86
- process.exit(1);
87
- }
88
- }
89
- // ─── Subcommand resolution ───────────────────────────────────────────────
90
- function getSubcommand(args) {
91
- if (args.includes('--list') || args.includes('-l'))
92
- return 'list';
93
- if (args.includes('--clean'))
94
- return 'clean';
95
- return 'download';
96
- }
97
- function getThreadOption(args) {
98
- const idx = args.findIndex(a => a === '--thread' || a === '-t');
99
- if (idx !== -1 && idx + 1 < args.length) {
100
- return args[idx + 1];
101
- }
102
- return undefined;
103
- }
104
- // ─── Download ────────────────────────────────────────────────────────────
105
- async function handleDownloadFiles(botToken, threadId) {
106
- if (!threadId) {
107
- console.error('\n❌ Error: --thread option is required for download\n');
108
- console.log('Usage: crewx slack:files --thread <thread_ts>\n');
109
- console.log('Example:');
110
- console.log(' crewx slack:files --thread 1234567890.123456\n');
111
- process.exit(1);
112
- return;
113
- }
114
- const validThreadId = threadId;
115
- console.log(`\n📥 Downloading files from thread: ${validThreadId}\n`);
116
- const channelId = validThreadId.includes('.')
117
- ? (validThreadId.split('.')[0] || validThreadId)
118
- : validThreadId;
119
- // Fetch conversation history from Slack API
120
- const messages = await slackConversationsHistory(botToken, channelId, validThreadId);
121
- if (!messages || messages.length === 0) {
122
- console.log('⚠️ No messages found in thread\n');
123
- return;
124
- }
125
- // Collect files from messages
126
- const files = [];
127
- for (const message of messages) {
128
- if (message.files) {
129
- for (const file of message.files) {
130
- files.push({
131
- id: file.id,
132
- name: file.name,
133
- userId: message.user || 'unknown',
134
- });
135
- }
136
- }
137
- }
138
- if (files.length === 0) {
139
- console.log('ℹ️ No files found in thread\n');
140
- return;
141
- }
142
- console.log(`Found ${files.length} file${files.length > 1 ? 's' : ''} to download:\n`);
143
- let downloadedCount = 0;
144
- let skippedCount = 0;
145
- let failedCount = 0;
146
- for (const file of files) {
147
- try {
148
- const metadata = await downloadSlackFile(botToken, file.id, file.name, validThreadId, channelId, file.userId);
149
- const wasSkipped = fs.existsSync(metadata.filePath) &&
150
- fs.statSync(metadata.filePath).mtime < metadata.downloadedAt;
151
- if (wasSkipped) {
152
- console.log(` ⏭️ ${file.name} (already exists)`);
153
- skippedCount++;
154
- }
155
- else {
156
- console.log(` ✅ ${file.name} (${formatFileSize(metadata.fileSize)})`);
157
- downloadedCount++;
158
- }
159
- }
160
- catch (error) {
161
- const errorMessage = error instanceof Error ? error.message : String(error);
162
- console.log(` ❌ ${file.name} - ${errorMessage}`);
163
- failedCount++;
164
- }
165
- }
166
- console.log(`\n📊 Summary:`);
167
- if (downloadedCount > 0)
168
- console.log(` Downloaded: ${downloadedCount} file${downloadedCount > 1 ? 's' : ''}`);
169
- if (skippedCount > 0)
170
- console.log(` Skipped: ${skippedCount} file${skippedCount > 1 ? 's' : ''} (already exists)`);
171
- if (failedCount > 0)
172
- console.log(` Failed: ${failedCount} file${failedCount > 1 ? 's' : ''}`);
173
- console.log('');
174
- }
175
- // ─── List ────────────────────────────────────────────────────────────────
176
- async function handleListFiles(threadId) {
177
- const downloadDir = path.join(process.cwd(), '.crewx', 'slack-files');
178
- if (threadId) {
179
- console.log(`\n📋 Files in thread ${threadId}:\n`);
180
- const files = getThreadFilesMetadata(threadId, downloadDir);
181
- if (files.length === 0) {
182
- console.log(' No files downloaded for this thread\n');
183
- return;
184
- }
185
- for (const file of files) {
186
- console.log(` 📎 ${file.fileName}`);
187
- console.log(` Size: ${formatFileSize(file.fileSize)}`);
188
- console.log(` Path: ${file.filePath}`);
189
- console.log(` Downloaded: ${file.downloadedAt.toLocaleString()}`);
190
- console.log('');
191
- }
192
- console.log(`Total: ${files.length} file${files.length > 1 ? 's' : ''}\n`);
193
- }
194
- else {
195
- console.log('\n📋 All downloaded Slack files:\n');
196
- if (!fs.existsSync(downloadDir)) {
197
- console.log(' No files downloaded yet\n');
198
- return;
199
- }
200
- const threadDirs = fs.readdirSync(downloadDir).filter(name => fs.statSync(path.join(downloadDir, name)).isDirectory());
201
- if (threadDirs.length === 0) {
202
- console.log(' No files downloaded yet\n');
203
- return;
204
- }
205
- let totalFiles = 0;
206
- for (const threadDir of threadDirs) {
207
- const files = getThreadFilesMetadata(threadDir, downloadDir);
208
- if (files.length > 0) {
209
- console.log(` Thread: ${threadDir}`);
210
- for (const file of files) {
211
- console.log(` 📎 ${file.fileName} (${formatFileSize(file.fileSize)})`);
212
- totalFiles++;
213
- }
214
- console.log('');
215
- }
216
- }
217
- console.log(`Total: ${totalFiles} file${totalFiles > 1 ? 's' : ''} in ${threadDirs.length} thread${threadDirs.length > 1 ? 's' : ''}\n`);
218
- }
219
- }
220
- // ─── Clean ───────────────────────────────────────────────────────────────
221
- async function handleCleanFiles(threadId) {
222
- const downloadDir = path.join(process.cwd(), '.crewx', 'slack-files');
223
- if (threadId) {
224
- console.log(`\n🧹 Cleaning files for thread ${threadId}...\n`);
225
- const safeThreadDir = threadId.replace(/:/g, '_');
226
- const threadDir = path.join(downloadDir, safeThreadDir);
227
- if (!fs.existsSync(threadDir)) {
228
- console.log(' No files to clean\n');
229
- return;
230
- }
231
- const files = fs.readdirSync(threadDir);
232
- for (const file of files) {
233
- fs.unlinkSync(path.join(threadDir, file));
234
- }
235
- fs.rmdirSync(threadDir);
236
- console.log(`✅ Cleaned ${files.length} file${files.length > 1 ? 's' : ''}\n`);
237
- }
238
- else {
239
- console.log('\n🧹 Cleaning all downloaded files...\n');
240
- if (!fs.existsSync(downloadDir)) {
241
- console.log(' No files to clean\n');
242
- return;
243
- }
244
- let totalFiles = 0;
245
- let totalThreads = 0;
246
- const threadDirs = fs.readdirSync(downloadDir).filter(name => fs.statSync(path.join(downloadDir, name)).isDirectory());
247
- for (const threadDir of threadDirs) {
248
- const threadPath = path.join(downloadDir, threadDir);
249
- const files = fs.readdirSync(threadPath);
250
- for (const file of files) {
251
- fs.unlinkSync(path.join(threadPath, file));
252
- totalFiles++;
253
- }
254
- fs.rmdirSync(threadPath);
255
- totalThreads++;
256
- }
257
- console.log(`✅ Cleaned ${totalFiles} file${totalFiles > 1 ? 's' : ''} from ${totalThreads} thread${totalThreads > 1 ? 's' : ''}\n`);
258
- }
259
- }
260
- // ─── Slack API helpers ───────────────────────────────────────────────────
261
- /**
262
- * Call Slack conversations.history API using Node.js https.
263
- * Returns array of message objects.
264
- */
265
- async function slackConversationsHistory(token, channel, latest) {
266
- const params = new URLSearchParams({
267
- channel,
268
- latest,
269
- inclusive: 'true',
270
- limit: '100',
271
- });
272
- const data = await slackApiGet(token, `conversations.history?${params.toString()}`);
273
- if (!data.ok) {
274
- throw new Error(`Slack API error: ${data.error ?? 'unknown_error'}`);
275
- }
276
- return data.messages ?? [];
277
- }
278
- /**
279
- * Download a single Slack file and save it to disk.
280
- */
281
- async function downloadSlackFile(botToken, fileId, fileName, threadId, channelId, userId) {
282
- // Get file info
283
- const infoData = await slackApiGet(botToken, `files.info?file=${fileId}`);
284
- if (!infoData.ok) {
285
- throw new Error(`Failed to get file info: ${infoData.error ?? 'unknown_error'}`);
286
- }
287
- const fileInfo = infoData.file;
288
- if (!fileInfo?.url_private) {
289
- throw new Error('No private download URL available for this file');
290
- }
291
- // Build save path
292
- const sanitized = sanitizeFileName(fileName);
293
- const uniqueName = `${fileId}${path.extname(sanitized)}`;
294
- const safeThreadDir = threadId.replace(/:/g, '_');
295
- const downloadDir = path.join(process.cwd(), '.crewx', 'slack-files');
296
- const savePath = path.join(downloadDir, safeThreadDir, uniqueName);
297
- // Duplicate check
298
- if (fs.existsSync(savePath)) {
299
- const stats = fs.statSync(savePath);
300
- return {
301
- fileId,
302
- fileName: sanitized,
303
- filePath: savePath,
304
- fileSize: stats.size,
305
- mimeType: fileInfo.mimetype ?? 'application/octet-stream',
306
- uploadedBy: userId,
307
- uploadedAt: new Date((fileInfo.timestamp ?? 0) * 1000),
308
- threadId,
309
- channelId,
310
- downloadedAt: new Date(stats.mtime),
311
- };
312
- }
313
- // Fetch file content from Slack private URL
314
- const fileBuffer = await fetchPrivateUrl(botToken, fileInfo.url_private);
315
- // Save to disk
316
- await fs.promises.mkdir(path.dirname(savePath), { recursive: true });
317
- await fs.promises.writeFile(savePath, new Uint8Array(fileBuffer));
318
- return {
319
- fileId,
320
- fileName: sanitized,
321
- filePath: savePath,
322
- fileSize: fileBuffer.length,
323
- mimeType: fileInfo.mimetype ?? 'application/octet-stream',
324
- uploadedBy: userId,
325
- uploadedAt: new Date((fileInfo.timestamp ?? 0) * 1000),
326
- threadId,
327
- channelId,
328
- downloadedAt: new Date(),
329
- };
330
- }
331
- /**
332
- * Make a GET request to a Slack API endpoint.
333
- */
334
- async function slackApiGet(token, endpoint) {
335
- return new Promise((resolve, reject) => {
336
- const options = {
337
- hostname: 'slack.com',
338
- path: `/api/${endpoint}`,
339
- method: 'GET',
340
- headers: {
341
- Authorization: `Bearer ${token}`,
342
- 'Content-Type': 'application/json',
343
- },
344
- };
345
- const req = https.request(options, res => {
346
- let body = '';
347
- res.on('data', chunk => { body += chunk; });
348
- res.on('end', () => {
349
- try {
350
- resolve(JSON.parse(body));
351
- }
352
- catch {
353
- reject(new Error(`Failed to parse Slack API response: ${body}`));
354
- }
355
- });
356
- });
357
- req.on('error', reject);
358
- req.setTimeout(30000, () => {
359
- req.destroy();
360
- reject(new Error('Slack API request timed out'));
361
- });
362
- req.end();
363
- });
364
- }
365
- /**
366
- * Download content from a Slack private URL using Bearer auth.
367
- */
368
- async function fetchPrivateUrl(token, url) {
369
- const parsed = new URL(url);
370
- return new Promise((resolve, reject) => {
371
- const options = {
372
- hostname: parsed.hostname,
373
- path: parsed.pathname + parsed.search,
374
- method: 'GET',
375
- headers: {
376
- Authorization: `Bearer ${token}`,
377
- },
378
- };
379
- const req = https.request(options, res => {
380
- if (res.statusCode === 302 || res.statusCode === 301) {
381
- // Follow redirect
382
- const location = res.headers.location;
383
- if (!location) {
384
- reject(new Error('Redirect without location header'));
385
- return;
386
- }
387
- fetchPrivateUrl(token, location).then(resolve).catch(reject);
388
- return;
389
- }
390
- if (res.statusCode !== 200) {
391
- reject(new Error(`HTTP ${res.statusCode} while downloading file`));
392
- return;
393
- }
394
- const chunks = [];
395
- res.on('data', (chunk) => chunks.push(chunk));
396
- res.on('end', () => resolve(Buffer.concat(chunks)));
397
- res.on('error', reject);
398
- });
399
- req.on('error', reject);
400
- req.setTimeout(30000, () => {
401
- req.destroy();
402
- reject(new Error('File download timed out'));
403
- });
404
- req.end();
405
- });
406
- }
407
- // ─── Local file helpers ───────────────────────────────────────────────────
408
- function getThreadFilesMetadata(threadId, downloadDir) {
409
- const safeThreadDir = threadId.replace(/:/g, '_');
410
- const threadDir = path.join(downloadDir, safeThreadDir);
411
- if (!fs.existsSync(threadDir))
412
- return [];
413
- const files = fs.readdirSync(threadDir);
414
- const metadata = [];
415
- for (const file of files) {
416
- try {
417
- const filePath = path.join(threadDir, file);
418
- const stats = fs.statSync(filePath);
419
- metadata.push({
420
- fileId: '',
421
- fileName: file,
422
- filePath,
423
- fileSize: stats.size,
424
- mimeType: guessMimeType(file),
425
- uploadedBy: '',
426
- uploadedAt: new Date(stats.birthtime),
427
- threadId,
428
- channelId: '',
429
- downloadedAt: new Date(stats.mtime),
430
- });
431
- }
432
- catch {
433
- // ignore unreadable files
434
- }
435
- }
436
- return metadata;
437
- }
438
- function sanitizeFileName(fileName) {
439
- let safeName = fileName.replace(/[/\\]/g, '_');
440
- safeName = safeName.replace(/\.\./g, '_');
441
- safeName = safeName.replace(/[\x00-\x1f\x80-\x9f<>:"|?*]/g, '_');
442
- safeName = safeName.replace(/^\.+|\.+$/g, '');
443
- safeName = safeName.trim();
444
- if (!safeName)
445
- safeName = 'unnamed_file';
446
- return safeName;
447
- }
448
- function guessMimeType(fileName) {
449
- const ext = path.extname(fileName).toLowerCase();
450
- const mimeTypes = {
451
- '.pdf': 'application/pdf',
452
- '.png': 'image/png',
453
- '.jpg': 'image/jpeg',
454
- '.jpeg': 'image/jpeg',
455
- '.gif': 'image/gif',
456
- '.txt': 'text/plain',
457
- '.md': 'text/markdown',
458
- '.csv': 'text/csv',
459
- '.json': 'application/json',
460
- '.xml': 'application/xml',
461
- '.zip': 'application/zip',
462
- '.doc': 'application/msword',
463
- '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
464
- '.xls': 'application/vnd.ms-excel',
465
- '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
466
- };
467
- return mimeTypes[ext] ?? 'application/octet-stream';
468
- }
469
- function formatFileSize(bytes) {
470
- if (bytes < 1024)
471
- return `${bytes}B`;
472
- if (bytes < 1024 * 1024)
473
- return `${(bytes / 1024).toFixed(1)}KB`;
474
- return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
475
- }
476
- // ─── Bot mode ────────────────────────────────────────────────────────────
477
- function parseFlag(args, flag) {
478
- const idx = args.indexOf(flag);
479
- if (idx !== -1 && idx + 1 < args.length) {
480
- return args[idx + 1];
481
- }
482
- const prefix = `${flag}=`;
483
- const found = args.find(a => a.startsWith(prefix));
484
- return found ? found.slice(prefix.length) : undefined;
485
- }
486
- async function handleBotMode(args) {
487
- const mode = parseFlag(args, '--mode') ?? 'query';
488
- const agent = parseFlag(args, '--agent');
489
- if (!agent) {
490
- console.error('Error: --agent required for bot mode');
491
- console.error('Usage: crewx slack --mode <query|execute> --agent <agent-id>');
492
- process.exit(1);
493
- }
494
- if (mode !== 'query' && mode !== 'execute') {
495
- console.error(`Error: invalid mode "${mode}" — must be query or execute`);
496
- process.exit(1);
497
- }
498
- const botToken = process.env.SLACK_BOT_TOKEN;
499
- const appToken = process.env.SLACK_APP_TOKEN;
500
- if (!botToken || !appToken) {
501
- console.error('Error: SLACK_BOT_TOKEN and SLACK_APP_TOKEN environment variables required');
502
- process.exit(1);
503
- }
504
- const configPath = process.env.CREWX_CONFIG ?? './crewx.yaml';
505
- let crewx;
506
- try {
507
- crewx = await (0, crewx_cli_1.createCliCrewx)(configPath);
508
- }
509
- catch (err) {
510
- console.error(`Failed to load config: ${configPath}`);
511
- console.error(err instanceof Error ? err.message : String(err));
512
- process.exit(1);
513
- }
514
- const adapter = new adapter_slack_1.SlackAdapter();
515
- await crewx.registerChannelAdapter({
516
- adapter,
517
- instanceId: `slack-${agent}`,
518
- config: { mode: 'socket', botToken, appToken, myAgentId: agent },
519
- defaultMode: mode,
520
- defaultAgent: agent,
521
- onInbound: async (msg) => {
522
- if (msg.attachments && msg.attachments.length > 0) {
523
- try {
524
- await (0, file_download_1.downloadInboundFiles)(botToken, msg.attachments.map(a => ({
525
- url_private: a.url,
526
- mimetype: a.mimeType ?? 'application/octet-stream',
527
- name: a.name ?? 'unnamed',
528
- })), msg.threadId);
529
- }
530
- catch (err) {
531
- console.error('File download failed:', err instanceof Error ? err.message : String(err));
532
- }
533
- }
534
- const fn = mode === 'execute' ? crewx.execute.bind(crewx) : crewx.query.bind(crewx);
535
- const result = await fn(`@${agent}`, msg.text, { threadId: msg.threadId, platform: adapter.manifest.platform });
536
- const rawOutput = result.ok
537
- ? result.data
538
- : (result.error?.message ?? 'Error');
539
- return { accepted: true, output: (0, markdown_1.markdownToMrkdwn)(rawOutput) };
540
- },
541
- });
542
- console.log(`Slack bot started (agent=${agent}, mode=${mode}, instance=slack-${agent})`);
543
- console.log('Press Ctrl+C to shut down.');
544
- await new Promise((resolve) => {
545
- const shutdown = async () => {
546
- console.log('\nShutting down...');
547
- try {
548
- await crewx.stopAllAdapters({ timeoutMs: 5000 });
549
- }
550
- catch (err) {
551
- console.error('Shutdown error:', err instanceof Error ? err.message : String(err));
552
- }
553
- resolve();
554
- };
555
- process.once('SIGINT', shutdown);
556
- process.once('SIGTERM', shutdown);
557
- });
558
- process.exit(0);
559
- }
1
+ "use strict";var k=exports&&exports.__createBinding||(Object.create?(function(n,e,o,t){if(t===void 0)t=o;var r=Object.getOwnPropertyDescriptor(e,o);if(!r||("get"in r?!e.__esModule:r.writable||r.configurable)){r={enumerable:true,get:function(){return e[o]}}}Object.defineProperty(n,t,r)}):(function(n,e,o,t){if(t===void 0)t=o;n[t]=e[o]}));var v=exports&&exports.__setModuleDefault||(Object.create?(function(n,e){Object.defineProperty(n,"default",{enumerable:true,value:e})}):function(n,e){n["default"]=e});var h=exports&&exports.__importStar||(function(){var n=function(e){n=Object.getOwnPropertyNames||function(o){var t=[];for(var r in o)if(Object.prototype.hasOwnProperty.call(o,r))t[t.length]=r;return t};return n(e)};return function(e){if(e&&e.__esModule)return e;var o={};if(e!=null){for(var t=n(e),r=0;r<t.length;r++)if(t[r]!=="default")k(o,e,t[r])}v(o,e);return o}})();Object.defineProperty(exports,"__esModule",{value:true});exports.handleSlack=O;const d=h(require("fs"));const u=h(require("path"));const S=h(require("https"));const A=require("@crewx/sdk");const E=require("@crewx/adapter-slack");const C=require("../bootstrap/crewx-cli");const P=require("../slack/markdown");const D=require("../slack/file-download");async function O(n){if(n.includes("--mode")||n.includes("--agent")){return await L(n)}try{const e=b(n);const o=N(n);const t=process.env.SLACK_BOT_TOKEN;if(!t&&e==="download"){console.error("\n\u274C Error: SLACK_BOT_TOKEN environment variable not found\n");console.log("Set your Slack bot token:");console.log(" export SLACK_BOT_TOKEN=xoxb-your-token\n");process.exit(1)}switch(e){case"list":await q(o);break;case"clean":await j(o);break;case"download":default:await F(t,o);break}}catch(e){const o=e instanceof Error?e.message:String(e);console.error(`
2
+ \u274C Slack files command failed:
3
+ ${o}
4
+ `);process.exit(1)}}function b(n){if(n.includes("--list")||n.includes("-l"))return"list";if(n.includes("--clean"))return"clean";return"download"}function N(n){const e=n.findIndex(o=>o==="--thread"||o==="-t");if(e!==-1&&e+1<n.length){return n[e+1]}return void 0}async function F(n,e){if(!e){console.error("\n\u274C Error: --thread option is required for download\n");console.log("Usage: crewx slack:files --thread <thread_ts>\n");console.log("Example:");console.log(" crewx slack:files --thread 1234567890.123456\n");process.exit(1);return}const o=e;console.log(`
5
+ \u{1F4E5} Downloading files from thread: ${o}
6
+ `);const t=o.includes(".")?o.split(".")[0]||o:o;const r=await B(n,t,o);if(!r||r.length===0){console.log("\u26A0\uFE0F No messages found in thread\n");return}const a=[];for(const c of r){if(c.files){for(const f of c.files){a.push({id:f.id,name:f.name,userId:c.user||"unknown"})}}}if(a.length===0){console.log("\u2139\uFE0F No files found in thread\n");return}console.log(`Found ${a.length} file${a.length>1?"s":""} to download:
7
+ `);let i=0;let s=0;let l=0;for(const c of a){try{const f=await z(n,c.id,c.name,o,t,c.userId);const m=d.existsSync(f.filePath)&&d.statSync(f.filePath).mtime<f.downloadedAt;if(m){console.log(` \u23ED\uFE0F ${c.name} (already exists)`);s++}else{console.log(` \u2705 ${c.name} (${g(f.fileSize)})`);i++}}catch(f){const m=f instanceof Error?f.message:String(f);console.log(` \u274C ${c.name} - ${m}`);l++}}console.log(`
8
+ \u{1F4CA} Summary:`);if(i>0)console.log(` Downloaded: ${i} file${i>1?"s":""}`);if(s>0)console.log(` Skipped: ${s} file${s>1?"s":""} (already exists)`);if(l>0)console.log(` Failed: ${l} file${l>1?"s":""}`);console.log("")}async function q(n){const e=u.join(process.cwd(),".crewx","slack-files");if(n){console.log(`
9
+ \u{1F4CB} Files in thread ${n}:
10
+ `);const o=_(n,e);if(o.length===0){console.log(" No files downloaded for this thread\n");return}for(const t of o){console.log(` \u{1F4CE} ${t.fileName}`);console.log(` Size: ${g(t.fileSize)}`);console.log(` Path: ${t.filePath}`);console.log(` Downloaded: ${(0,A.formatDisplayTimestamp)(t.downloadedAt)}`);console.log("")}console.log(`Total: ${o.length} file${o.length>1?"s":""}
11
+ `)}else{console.log("\n\u{1F4CB} All downloaded Slack files:\n");if(!d.existsSync(e)){console.log(" No files downloaded yet\n");return}const o=d.readdirSync(e).filter(r=>d.statSync(u.join(e,r)).isDirectory());if(o.length===0){console.log(" No files downloaded yet\n");return}let t=0;for(const r of o){const a=_(r,e);if(a.length>0){console.log(` Thread: ${r}`);for(const i of a){console.log(` \u{1F4CE} ${i.fileName} (${g(i.fileSize)})`);t++}console.log("")}}console.log(`Total: ${t} file${t>1?"s":""} in ${o.length} thread${o.length>1?"s":""}
12
+ `)}}async function j(n){const e=u.join(process.cwd(),".crewx","slack-files");if(n){console.log(`
13
+ \u{1F9F9} Cleaning files for thread ${n}...
14
+ `);const o=n.replace(/:/g,"_");const t=u.join(e,o);if(!d.existsSync(t)){console.log(" No files to clean\n");return}const r=d.readdirSync(t);for(const a of r){d.unlinkSync(u.join(t,a))}d.rmdirSync(t);console.log(`\u2705 Cleaned ${r.length} file${r.length>1?"s":""}
15
+ `)}else{console.log("\n\u{1F9F9} Cleaning all downloaded files...\n");if(!d.existsSync(e)){console.log(" No files to clean\n");return}let o=0;let t=0;const r=d.readdirSync(e).filter(a=>d.statSync(u.join(e,a)).isDirectory());for(const a of r){const i=u.join(e,a);const s=d.readdirSync(i);for(const l of s){d.unlinkSync(u.join(i,l));o++}d.rmdirSync(i);t++}console.log(`\u2705 Cleaned ${o} file${o>1?"s":""} from ${t} thread${t>1?"s":""}
16
+ `)}}async function B(n,e,o){const t=new URLSearchParams({channel:e,latest:o,inclusive:"true",limit:"100"});const r=await $(n,`conversations.history?${t.toString()}`);if(!r.ok){throw new Error(`Slack API error: ${r.error??"unknown_error"}`)}return r.messages??[]}async function z(n,e,o,t,r,a){const i=await $(n,`files.info?file=${e}`);if(!i.ok){throw new Error(`Failed to get file info: ${i.error??"unknown_error"}`)}const s=i.file;if(!s?.url_private){throw new Error("No private download URL available for this file")}const l=K(o);const c=`${e}${u.extname(l)}`;const f=t.replace(/:/g,"_");const m=u.join(process.cwd(),".crewx","slack-files");const p=u.join(m,f,c);if(d.existsSync(p)){const y=d.statSync(p);return{fileId:e,fileName:l,filePath:p,fileSize:y.size,mimeType:s.mimetype??"application/octet-stream",uploadedBy:a,uploadedAt:new Date((s.timestamp??0)*1e3),threadId:t,channelId:r,downloadedAt:new Date(y.mtime)}}const w=await x(n,s.url_private);await d.promises.mkdir(u.dirname(p),{recursive:true});await d.promises.writeFile(p,new Uint8Array(w));return{fileId:e,fileName:l,filePath:p,fileSize:w.length,mimeType:s.mimetype??"application/octet-stream",uploadedBy:a,uploadedAt:new Date((s.timestamp??0)*1e3),threadId:t,channelId:r,downloadedAt:new Date}}async function $(n,e){return new Promise((o,t)=>{const r={hostname:"slack.com",path:`/api/${e}`,method:"GET",headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json"}};const a=S.request(r,i=>{let s="";i.on("data",l=>{s+=l});i.on("end",()=>{try{o(JSON.parse(s))}catch{t(new Error(`Failed to parse Slack API response: ${s}`))}})});a.on("error",t);a.setTimeout(3e4,()=>{a.destroy();t(new Error("Slack API request timed out"))});a.end()})}async function x(n,e){const o=new URL(e);return new Promise((t,r)=>{const a={hostname:o.hostname,path:o.pathname+o.search,method:"GET",headers:{Authorization:`Bearer ${n}`}};const i=S.request(a,s=>{if(s.statusCode===302||s.statusCode===301){const c=s.headers.location;if(!c){r(new Error("Redirect without location header"));return}x(n,c).then(t).catch(r);return}if(s.statusCode!==200){r(new Error(`HTTP ${s.statusCode} while downloading file`));return}const l=[];s.on("data",c=>l.push(c));s.on("end",()=>t(Buffer.concat(l)));s.on("error",r)});i.on("error",r);i.setTimeout(3e4,()=>{i.destroy();r(new Error("File download timed out"))});i.end()})}function _(n,e){const o=n.replace(/:/g,"_");const t=u.join(e,o);if(!d.existsSync(t))return[];const r=d.readdirSync(t);const a=[];for(const i of r){try{const s=u.join(t,i);const l=d.statSync(s);a.push({fileId:"",fileName:i,filePath:s,fileSize:l.size,mimeType:M(i),uploadedBy:"",uploadedAt:new Date(l.birthtime),threadId:n,channelId:"",downloadedAt:new Date(l.mtime)})}catch{}}return a}function K(n){let e=n.replace(/[/\\]/g,"_");e=e.replace(/\.\./g,"_");e=e.replace(/[\x00-\x1f\x80-\x9f<>:"|?*]/g,"_");e=e.replace(/^\.+|\.+$/g,"");e=e.trim();if(!e)e="unnamed_file";return e}function M(n){const e=u.extname(n).toLowerCase();const o={".pdf":"application/pdf",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".txt":"text/plain",".md":"text/markdown",".csv":"text/csv",".json":"application/json",".xml":"application/xml",".zip":"application/zip",".doc":"application/msword",".docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document",".xls":"application/vnd.ms-excel",".xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"};return o[e]??"application/octet-stream"}function g(n){if(n<1024)return`${n}B`;if(n<1024*1024)return`${(n/1024).toFixed(1)}KB`;return`${(n/(1024*1024)).toFixed(1)}MB`}function T(n,e){const o=n.indexOf(e);if(o!==-1&&o+1<n.length){return n[o+1]}const t=`${e}=`;const r=n.find(a=>a.startsWith(t));return r?r.slice(t.length):void 0}async function L(n){const e=T(n,"--mode")??"query";const o=T(n,"--agent");if(!o){console.error("Error: --agent required for bot mode");console.error("Usage: crewx slack --mode <query|execute> --agent <agent-id>");process.exit(1)}if(e!=="query"&&e!=="execute"){console.error(`Error: invalid mode "${e}" \u2014 must be query or execute`);process.exit(1)}const t=process.env.SLACK_BOT_TOKEN;const r=process.env.SLACK_APP_TOKEN;if(!t||!r){console.error("Error: SLACK_BOT_TOKEN and SLACK_APP_TOKEN environment variables required");process.exit(1)}const a=process.env.CREWX_CONFIG??"./crewx.yaml";let i;try{i=await(0,C.createCliCrewx)(a)}catch(l){console.error(`Failed to load config: ${a}`);console.error(l instanceof Error?l.message:String(l));process.exit(1)}const s=new E.SlackAdapter;await i.registerChannelAdapter({adapter:s,instanceId:`slack-${o}`,config:{mode:"socket",botToken:t,appToken:r,myAgentId:o},defaultMode:e,defaultAgent:o,onInbound:async l=>{if(l.attachments&&l.attachments.length>0){try{await(0,D.downloadInboundFiles)(t,l.attachments.map(p=>({url_private:p.url,mimetype:p.mimeType??"application/octet-stream",name:p.name??"unnamed"})),l.threadId)}catch(p){console.error("File download failed:",p instanceof Error?p.message:String(p))}}const c=e==="execute"?i.execute.bind(i):i.query.bind(i);const f=await c(`@${o}`,l.text,{threadId:l.threadId,platform:s.manifest.platform});const m=f.ok?f.data:f.error?.message??"Error";return{accepted:true,output:(0,P.markdownToMrkdwn)(m)}}});console.log(`Slack bot started (agent=${o}, mode=${e}, instance=slack-${o})`);console.log("Press Ctrl+C to shut down.");await new Promise(l=>{const c=async()=>{console.log("\nShutting down...");try{await i.stopAllAdapters({timeoutMs:5e3})}catch(f){console.error("Shutdown error:",f instanceof Error?f.message:String(f))}l()};process.once("SIGINT",c);process.once("SIGTERM",c)});process.exit(0)}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Stop a running task through the SDK's cooperative-stop pipeline.
3
+ *
4
+ * `kill` is kept as a compatibility alias; both entry points use this same
5
+ * handler so server, CLI, and future adapters share one stop contract.
6
+ */
7
+ /** Handle `crewx stop` and the compatibility `crewx kill` alias. */
8
+ export declare function handleStop(args: string[], invokedAs?: 'stop' | 'kill'): Promise<void>;
@@ -0,0 +1,8 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.handleStop=C;const w=require("@crewx/sdk");const g=require("@crewx/sdk/repository");const k=require("./cal");function $(r){const e=r.find(n=>n==="--grace"||n.startsWith("--grace="));if(!e)return{graceMs:5e3};const t=e==="--grace"?r[r.indexOf(e)+1]:e.slice("--grace=".length);const s=Number(t);if(!t||!Number.isFinite(s)||s<0){return{graceMs:5e3,error:`Invalid --grace value: ${t??"(missing)"}`}}return{graceMs:Math.trunc(s)}}function h(r){return r.find((e,t)=>{if(e==="--grace")return false;if(t>0&&r[t-1]==="--grace")return false;return!e.startsWith("--")})}function m(){process.stderr.write("Error: Please provide a task ID or use --all to stop all running tasks.\n");process.stderr.write("\nUsage:\n");process.stderr.write(" crewx stop <task-id> Stop a running task\n");process.stderr.write(" crewx stop --all Stop all running tasks\n");process.stderr.write(" crewx stop <task-id> --grace=<ms>\n")}function x(r){return`${r/1e3}s`}function _(r,e,t,s){if(e.status==="stopped"){if(e.method==="forced"){process.stdout.write(`Stopped ${r} (forced after ${x(s)} grace)
2
+ `)}else{process.stdout.write(`Stopped ${r} (cooperative, ${(t/1e3).toFixed(1)}s)
3
+ `)}return 0}if(e.status==="not_running"){process.stdout.write(`Task ${r} is not running (${e.finalStatus??"unknown"})
4
+ `);return 0}if(e.status==="already_requested"){process.stderr.write(`Task ${r} already has a stop request; it remains ${e.finalStatus??"running"}.
5
+ `);return 2}if(e.status==="ownership_unverified"){const n=e.pid?` (pid ${e.pid})`:"";process.stderr.write(`Could not verify the process owner of ${r}; not terminated${n}. Row stays running \u2014 the reaper will settle it.
6
+ `);return 2}process.stderr.write(`Could not stop ${r}: ${e.error??"unknown error"}
7
+ `);return 1}async function C(r,e="stop"){if(e==="kill")process.stderr.write("kill is an alias of stop\n");const t=r.includes("--all");const{graceMs:s,error:n}=$(r);if(n){process.stderr.write(`Error: ${n}
8
+ `);process.exitCode=1;return}const u=h(r);if(!u&&!t){m();process.exitCode=1;return}const p=new g.TaskRepository;const d=async(o,i)=>{const a=Date.now();const c=await(0,w.stopTask)(p,o,{graceMs:s,origin:"cli"});const l=_(o,c,Date.now()-a,s);if(i&&(c.status==="stopped"||c.status==="not_running")){(0,k.writeCalFooter)({commandId:"crewx.stop.done",params:{taskId:o}})}return l};if(t){const o=p.getRunningTasks();if(o.length===0){process.stdout.write("No running tasks found.\n");return}let i=0;for(const a of o){i=Math.max(i,await d(a.id,false))}if(i>0)process.exitCode=i;return}const f=await d(u,true);if(f>0)process.exitCode=f}
@@ -1,24 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.writeResult = writeResult;
4
- exports.appendError = appendError;
5
- /**
6
- * EPIPE-safe result output.
7
- * - out specified: write success result to file (sync, no stream EPIPE)
8
- * - out not specified: fall back to existing stdout (console.log)
9
- */
10
- const fs_1 = require("fs");
11
- function writeResult(out, data) {
12
- if (out) {
13
- (0, fs_1.writeFileSync)(out, data, 'utf8');
14
- }
15
- else {
16
- console.log(data);
17
- }
18
- }
19
- /** On failure: if out is specified, append to file (avoid empty file). stderr is kept by the caller. */
20
- function appendError(out, message) {
21
- if (out) {
22
- (0, fs_1.appendFileSync)(out, message.endsWith('\n') ? message : message + '\n', 'utf8');
23
- }
24
- }
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.writeResult=i;exports.appendError=t;const r=require("fs");function i(n,e){if(n){(0,r.writeFileSync)(n,e,"utf8")}else{console.log(e)}}function t(n,e){if(n){(0,r.appendFileSync)(n,e.endsWith("\n")?e:e+"\n","utf8")}}
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { handleInit, type InitResult } from './commands/init';
1
+ export { buildDefaultAgents, handleInit, parseInitProvider, resolveInitProvider, type InitProvider, type InitResult, } from './commands/init';
package/dist/index.js CHANGED
@@ -1,5 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.handleInit = void 0;
4
- var init_1 = require("./commands/init");
5
- Object.defineProperty(exports, "handleInit", { enumerable: true, get: function () { return init_1.handleInit; } });
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.resolveInitProvider=exports.parseInitProvider=exports.handleInit=exports.buildDefaultAgents=void 0;var e=require("./commands/init");Object.defineProperty(exports,"buildDefaultAgents",{enumerable:true,get:function(){return e.buildDefaultAgents}});Object.defineProperty(exports,"handleInit",{enumerable:true,get:function(){return e.handleInit}});Object.defineProperty(exports,"parseInitProvider",{enumerable:true,get:function(){return e.parseInitProvider}});Object.defineProperty(exports,"resolveInitProvider",{enumerable:true,get:function(){return e.resolveInitProvider}});
package/dist/logging.d.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * Format mirrors cli-bak TaskManagementService for backward compatibility.
11
11
  */
12
- import type { Crewx } from '@crewx/sdk';
12
+ import { type Crewx } from '@crewx/sdk';
13
13
  /**
14
14
  * Attach file-based logging to a Crewx instance.
15
15
  * Subscribes to task:start and task:end events and writes log files.