maistro 1.2.20 → 1.2.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/app.d.ts CHANGED
@@ -185,6 +185,14 @@ export declare class MaistroApp {
185
185
  * Handle settings command - show settings menu
186
186
  */
187
187
  private handleSettings;
188
+ /**
189
+ * Handle webhook URL configuration in settings
190
+ */
191
+ private handleWebhookSettings;
192
+ /**
193
+ * Prompt user to enter a webhook URL using InputBox
194
+ */
195
+ private promptWebhookUrl;
188
196
  /**
189
197
  * Show manual Cmd+V configuration instructions
190
198
  */
@@ -233,5 +241,9 @@ export declare class MaistroApp {
233
241
  private renderExecutionSection;
234
242
  private handleRun;
235
243
  private executeWithUnifiedView;
244
+ /**
245
+ * Get a display name for the project (folder name, or "projectName (folder)" if they differ)
246
+ */
247
+ private getProjectDisplayName;
236
248
  }
237
249
  //# sourceMappingURL=app.d.ts.map
package/dist/app.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAmOA,qBAAa,UAAU;IACrB,OAAO,CAAC,EAAE,CAAgB;IAC1B,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,aAAa,CAAS;IAG9B,OAAO,CAAC,cAAc,CAA+B;gBAEzC,WAAW,GAAE,MAAY;IAsBrC,OAAO,CAAC,eAAe;IASvB;;;OAGG;YACW,qBAAqB;IA+CnC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkH5B;;;OAGG;YACW,iBAAiB;IAiE/B;;OAEG;IACH,OAAO,CAAC,eAAe;IAUvB;;;OAGG;IACG,YAAY,CAAC,SAAS,UAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAqFtD;;;OAGG;YACW,iBAAiB;IAwD/B;;OAEG;YACW,sBAAsB;IAsCpC;;;OAGG;YACW,iBAAiB;IAoB/B;;;OAGG;YACW,oBAAoB;IAkClC;;OAEG;YACW,qBAAqB;IA4BnC;;OAEG;YACW,gBAAgB;IAoB9B;;;OAGG;YACW,qBAAqB;IAoEnC;;;OAGG;YACW,eAAe;IAyI7B;;;OAGG;YACW,gBAAgB;IAiD9B;;;OAGG;YACW,uBAAuB;IAyErC;;OAEG;YACW,4BAA4B;IAkE1C;;OAEG;YACW,oBAAoB;IAiElC;;OAEG;IACH,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,UAAU;IAQlB;;;;;;OAMG;IACH,OAAO,CAAC,qBAAqB;IA2J7B,OAAO,CAAC,iBAAiB,CAAM;IAC/B,OAAO,CAAC,gBAAgB,CAAK;IAE7B;;;OAGG;YACW,YAAY;IAyT1B;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IA2CxB;;OAEG;YACW,aAAa;IAsmB3B;;;OAGG;YACW,iBAAiB;IAgI/B;;OAEG;YACW,cAAc;IAwU5B,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,aAAa,CAAS;IAE9B;;OAEG;YACW,gBAAgB;IAkB9B;;OAEG;YACW,gBAAgB;IAe9B;;;OAGG;YACW,WAAW;IAsEzB;;;OAGG;YACW,mBAAmB;IA8BjC;;;OAGG;YACW,cAAc;IAkB5B;;;;OAIG;YACW,6BAA6B;IAqI3C;;;OAGG;YACW,aAAa;IA+B3B;;;;OAIG;YACW,sBAAsB;IA6NpC;;;;OAIG;YACW,sBAAsB;YA2GtB,UAAU;IA8HxB,OAAO,CAAC,YAAY;IAmFpB;;;OAGG;YACW,eAAe;IAqD7B;;OAEG;YACW,cAAc;IAsO5B;;OAEG;YACW,oBAAoB;IAwBlC;;OAEG;YACW,eAAe;IAU7B;;OAEG;YACW,wBAAwB;YAsBxB,gBAAgB;IA4B9B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsD;IAEpF;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAqB3B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAO1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAS1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAKzB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAqB5B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAQ/B;;OAEG;IACH,OAAO,CAAC,eAAe;IASvB;;OAEG;IACH,OAAO,CAAC,sBAAsB;YAoEhB,SAAS;YAiIT,sBAAsB;CAkcrC"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAwOA,qBAAa,UAAU;IACrB,OAAO,CAAC,EAAE,CAAgB;IAC1B,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,aAAa,CAAS;IAG9B,OAAO,CAAC,cAAc,CAA+B;gBAEzC,WAAW,GAAE,MAAY;IAsBrC,OAAO,CAAC,eAAe;IASvB;;;OAGG;YACW,qBAAqB;IA+CnC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkH5B;;;OAGG;YACW,iBAAiB;IAiE/B;;OAEG;IACH,OAAO,CAAC,eAAe;IAUvB;;;OAGG;IACG,YAAY,CAAC,SAAS,UAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAqFtD;;;OAGG;YACW,iBAAiB;IAwD/B;;OAEG;YACW,sBAAsB;IAsCpC;;;OAGG;YACW,iBAAiB;IAoB/B;;;OAGG;YACW,oBAAoB;IAkClC;;OAEG;YACW,qBAAqB;IA4BnC;;OAEG;YACW,gBAAgB;IAoB9B;;;OAGG;YACW,qBAAqB;IAoEnC;;;OAGG;YACW,eAAe;IAoJ7B;;;OAGG;YACW,gBAAgB;IAiD9B;;;OAGG;YACW,uBAAuB;IAyErC;;OAEG;YACW,4BAA4B;IAkE1C;;OAEG;YACW,oBAAoB;IAiElC;;OAEG;IACH,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,UAAU;IAQlB;;;;;;OAMG;IACH,OAAO,CAAC,qBAAqB;IA2J7B,OAAO,CAAC,iBAAiB,CAAM;IAC/B,OAAO,CAAC,gBAAgB,CAAK;IAE7B;;;OAGG;YACW,YAAY;IAyT1B;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IA2CxB;;OAEG;YACW,aAAa;IAsmB3B;;;OAGG;YACW,iBAAiB;IAgI/B;;OAEG;YACW,cAAc;IAwU5B,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,aAAa,CAAS;IAE9B;;OAEG;YACW,gBAAgB;IAkB9B;;OAEG;YACW,gBAAgB;IAe9B;;;OAGG;YACW,WAAW;IAsEzB;;;OAGG;YACW,mBAAmB;IA8BjC;;;OAGG;YACW,cAAc;IAkB5B;;;;OAIG;YACW,6BAA6B;IAqI3C;;;OAGG;YACW,aAAa;IA+B3B;;;;OAIG;YACW,sBAAsB;IA8NpC;;;;OAIG;YACW,sBAAsB;YAmHtB,UAAU;IAsIxB,OAAO,CAAC,YAAY;IAmFpB;;;OAGG;YACW,eAAe;IAqD7B;;OAEG;YACW,cAAc;IAgQ5B;;OAEG;YACW,qBAAqB;IA0CnC;;OAEG;YACW,gBAAgB;IA4C9B;;OAEG;YACW,oBAAoB;IAwBlC;;OAEG;YACW,eAAe;IAU7B;;OAEG;YACW,wBAAwB;YAsBxB,gBAAgB;IA4B9B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsD;IAEpF;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAqB3B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAO1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAS1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAKzB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAqB5B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAQ/B;;OAEG;IACH,OAAO,CAAC,eAAe;IASvB;;OAEG;IACH,OAAO,CAAC,sBAAsB;YAoEhB,SAAS;YAiIT,sBAAsB;IA8dpC;;OAEG;IACH,OAAO,CAAC,qBAAqB;CAc9B"}
package/dist/app.js CHANGED
@@ -15,7 +15,8 @@ import { buildHeaderWithLogo } from './logo.js';
15
15
  import { addImageFromFile, addImageFromUrl, addImageFromPaste, getImages, deleteImage, looksLikeImagePath, looksLikeImageUrl, formatImageSize, sanitizeImageId, generatePasteImageId, extractImageUrls, processDescriptionImageUrls, } from './imageManager.js';
16
16
  import { InputBox, getDisplayWidth, charIndexAtDisplayColumn } from './inputBox.js';
17
17
  import { startCaffeinate, stopCaffeinate, isCaffeinateAvailable, isCaffeinateRunning } from './caffeinate.js';
18
- import { getPreventSleep, setPreventSleep, getConfigPath, detectTerminal, getShiftEnterEnabled, setShiftEnterEnabled, configureTerminalShiftEnter, removeTerminalShiftEnter, getNewlineHint, getCmdVPasteEnabled, setCmdVPasteEnabled, configureTerminalCmdVPaste, removeTerminalCmdVPaste, getCmdVPasteInstructions, validateTerminalConfig, } from './config.js';
18
+ import { sendNotification, testWebhook } from './notifications.js';
19
+ import { getPreventSleep, setPreventSleep, getConfigPath, detectTerminal, getShiftEnterEnabled, setShiftEnterEnabled, configureTerminalShiftEnter, removeTerminalShiftEnter, getNewlineHint, getCmdVPasteEnabled, setCmdVPasteEnabled, configureTerminalCmdVPaste, removeTerminalCmdVPaste, getCmdVPasteInstructions, validateTerminalConfig, getSoundNotifications, setSoundNotifications, getWebhookUrl, setWebhookUrl, } from './config.js';
19
20
  import { hasClipboardImage, saveClipboardImage, cleanupTempImage } from './clipboard.js';
20
21
  import { isGitRepo } from './git.js';
21
22
  import { UI, TipsManager } from './constants.js';
@@ -702,7 +703,7 @@ export class MaistroApp {
702
703
  * Handle project discovery phase - ask clarifying questions before decomposition
703
704
  * Uses full-screen approach for each step
704
705
  */
705
- async handleDiscovery(goal) {
706
+ async handleDiscovery(goal, existingTasks) {
706
707
  // Show analyzing screen with spinner (with escape-to-exit support)
707
708
  setStatusState('working');
708
709
  this.ui.clear();
@@ -717,6 +718,9 @@ export class MaistroApp {
717
718
  const apiPromise = discoverRequirements(goal, {
718
719
  cwd: this.projectPath,
719
720
  abortSignal: abortController.signal,
721
+ existingTasks: existingTasks
722
+ ?.filter(t => t.status !== 'completed')
723
+ .map(t => ({ id: t.id, title: t.title, status: t.status })),
720
724
  });
721
725
  result = await Promise.race([
722
726
  apiPromise,
@@ -742,6 +746,13 @@ export class MaistroApp {
742
746
  questions: result.questions.map(q => ({ id: q.id, question: q.question, options: q.options })),
743
747
  assumptions: result.assumptions,
744
748
  });
749
+ // Notify user that input is needed
750
+ await sendNotification({
751
+ event: 'attention',
752
+ title: 'Discovery: input needed',
753
+ detail: `${result.questions.length} question${result.questions.length > 1 ? 's' : ''} to answer`,
754
+ project: this.getProjectDisplayName(),
755
+ });
745
756
  // Ask each question one at a time (full-screen for each)
746
757
  const answers = {};
747
758
  for (let i = 0; i < result.questions.length; i++) {
@@ -2813,7 +2824,8 @@ export class MaistroApp {
2813
2824
  console.log(`\x1b[1m── Discussion ──\x1b[0m\n\x1b[90mYou: ${userInput.length > 100 ? userInput.slice(0, 97) + '...' : userInput}\x1b[0m\n`);
2814
2825
  // Run discovery phase for the new request (same as initial project setup)
2815
2826
  // This detects external dependencies, asks clarifying questions, and prompts for secrets
2816
- const discoveryResult = await this.handleDiscovery(userInput);
2827
+ // Pass existing tasks so discovery is aware of the current plan
2828
+ const discoveryResult = await this.handleDiscovery(userInput, currentTasks);
2817
2829
  let discoveryContext = '';
2818
2830
  if (discoveryResult && discoveryResult.confirmed) {
2819
2831
  const context = buildProjectContext(userInput, discoveryResult);
@@ -3076,6 +3088,13 @@ ${detection.projectContext}`;
3076
3088
  }
3077
3089
  console.log(`\n\x1b[32m✓ Plan created with ${tasksWithImages.length} tasks\x1b[0m\n`);
3078
3090
  getLogger()?.info('Plan created and finalized', { taskCount: tasksWithImages.length });
3091
+ // Notify user that plan is ready for review
3092
+ await sendNotification({
3093
+ event: 'attention',
3094
+ title: 'Plan ready for review',
3095
+ detail: `${tasksWithImages.length} tasks created`,
3096
+ project: this.getProjectDisplayName(),
3097
+ });
3079
3098
  }
3080
3099
  catch (err) {
3081
3100
  // Re-throw ExitRequestedError to propagate exit signal
@@ -3184,6 +3203,13 @@ ${detection.projectContext}`;
3184
3203
  }
3185
3204
  console.log(`\n\x1b[32m✓ Project initialized with ${tasksWithImages.length} tasks\x1b[0m\n`);
3186
3205
  getLogger()?.info('Plan created and finalized', { taskCount: tasksWithImages.length });
3206
+ // Notify user that plan is ready for review
3207
+ await sendNotification({
3208
+ event: 'attention',
3209
+ title: 'Plan ready for review',
3210
+ detail: `${tasksWithImages.length} tasks created`,
3211
+ project: this.getProjectDisplayName(),
3212
+ });
3187
3213
  }
3188
3214
  catch (err) {
3189
3215
  // Re-throw ExitRequestedError to propagate exit signal
@@ -3393,6 +3419,20 @@ ${detection.projectContext}`;
3393
3419
  description: `${terminalInfo.name} - use /paste command instead`,
3394
3420
  });
3395
3421
  }
3422
+ // Sound notification toggle
3423
+ const soundEnabled = getSoundNotifications();
3424
+ menuItems.push({
3425
+ key: 'sound',
3426
+ label: `Sound notifications: ${soundEnabled ? '\x1b[32mON\x1b[0m' : '\x1b[90mOFF\x1b[0m'}`,
3427
+ description: 'Play a chime when execution completes or needs attention',
3428
+ });
3429
+ // Webhook notification
3430
+ const webhookUrl = getWebhookUrl();
3431
+ menuItems.push({
3432
+ key: 'webhook',
3433
+ label: `Webhook notifications: ${webhookUrl ? '\x1b[32mConfigured\x1b[0m' : '\x1b[90mOFF\x1b[0m'}`,
3434
+ description: webhookUrl ? 'Update or remove webhook URL' : 'Configure a webhook URL for notifications',
3435
+ });
3396
3436
  menuItems.push({
3397
3437
  key: 'back',
3398
3438
  label: 'Back',
@@ -3529,8 +3569,107 @@ ${detection.projectContext}`;
3529
3569
  // Show manual instructions for Cmd+V
3530
3570
  await this.showCmdVInstructions();
3531
3571
  }
3572
+ if (choice === 'sound') {
3573
+ const newValue = !soundEnabled;
3574
+ setSoundNotifications(newValue);
3575
+ // Loop redraws with updated value
3576
+ }
3577
+ if (choice === 'webhook') {
3578
+ await this.handleWebhookSettings();
3579
+ }
3580
+ }
3581
+ }
3582
+ /**
3583
+ * Handle webhook URL configuration in settings
3584
+ */
3585
+ async handleWebhookSettings() {
3586
+ const currentUrl = getWebhookUrl();
3587
+ if (currentUrl) {
3588
+ // Already configured — show sub-menu
3589
+ this.ui.clear();
3590
+ this.showHeader();
3591
+ console.log('\x1b[1m── Settings: Webhook Notifications ──\x1b[0m\n');
3592
+ console.log(`\x1b[90mCurrent URL: ${currentUrl}\x1b[0m\n`);
3593
+ const subMenuItems = [
3594
+ { key: 'update', label: 'Update URL', description: 'Enter a new webhook URL' },
3595
+ { key: 'test', label: 'Test notification', description: 'Send a test message to the webhook' },
3596
+ { key: 'remove', label: 'Remove', description: 'Disable webhook notifications' },
3597
+ { key: 'cancel', label: 'Cancel', description: 'Return to settings' },
3598
+ ];
3599
+ const subChoice = await this.ui.quickPick(subMenuItems, 'Webhook options:', undefined, { showInput: false });
3600
+ if (subChoice === 'update') {
3601
+ await this.promptWebhookUrl();
3602
+ }
3603
+ else if (subChoice === 'test') {
3604
+ this.ui.clear();
3605
+ this.showHeader();
3606
+ console.log('\x1b[1m── Settings: Test Webhook ──\x1b[0m\n');
3607
+ console.log('Sending test notification...\n');
3608
+ const result = await testWebhook(currentUrl);
3609
+ if (result.success) {
3610
+ console.log('\x1b[32m✓ Test notification sent successfully!\x1b[0m');
3611
+ }
3612
+ else {
3613
+ console.log(`\x1b[31m✗ Failed: ${result.error}\x1b[0m`);
3614
+ }
3615
+ console.log('\n\x1b[90mPress any key to continue...\x1b[0m');
3616
+ await this.waitForKeypress();
3617
+ }
3618
+ else if (subChoice === 'remove') {
3619
+ setWebhookUrl(undefined);
3620
+ }
3621
+ }
3622
+ else {
3623
+ // Not configured — prompt for URL
3624
+ await this.promptWebhookUrl();
3532
3625
  }
3533
3626
  }
3627
+ /**
3628
+ * Prompt user to enter a webhook URL using InputBox
3629
+ */
3630
+ async promptWebhookUrl() {
3631
+ return new Promise((resolve) => {
3632
+ const inputBox = new InputBox({
3633
+ placeholder: 'https://hooks.slack.com/services/...',
3634
+ hint: 'Enter to save · Esc to cancel',
3635
+ onRedraw: () => {
3636
+ this.ui.clear();
3637
+ this.showHeader();
3638
+ console.log('\x1b[1m── Settings: Webhook URL ──\x1b[0m\n');
3639
+ console.log('Enter your webhook URL for notifications.');
3640
+ console.log('Works with Slack, Discord, or any endpoint that accepts JSON POST.\n');
3641
+ console.log('\x1b[90mSlack: Workspace Settings → Manage Apps → Custom Integrations → Incoming WebHooks\x1b[0m\n');
3642
+ },
3643
+ onSubmit: (value) => {
3644
+ const url = value.trim();
3645
+ if (!url) {
3646
+ resolve();
3647
+ return;
3648
+ }
3649
+ if (!url.startsWith('https://')) {
3650
+ this.ui.clear();
3651
+ this.showHeader();
3652
+ console.log('\x1b[1m── Settings: Webhook URL ──\x1b[0m\n');
3653
+ console.log('\x1b[31m✗ URL must start with https://\x1b[0m');
3654
+ console.log('\n\x1b[90mPress any key to continue...\x1b[0m');
3655
+ this.waitForKeypress().then(() => resolve());
3656
+ return;
3657
+ }
3658
+ setWebhookUrl(url);
3659
+ this.ui.clear();
3660
+ this.showHeader();
3661
+ console.log('\x1b[1m── Settings ──\x1b[0m\n');
3662
+ console.log('\x1b[32m✓ Webhook URL saved!\x1b[0m');
3663
+ console.log('\n\x1b[90mPress any key to continue...\x1b[0m');
3664
+ this.waitForKeypress().then(() => resolve());
3665
+ },
3666
+ onCancel: () => {
3667
+ resolve();
3668
+ },
3669
+ });
3670
+ inputBox.start();
3671
+ });
3672
+ }
3534
3673
  /**
3535
3674
  * Show manual Cmd+V configuration instructions
3536
3675
  */
@@ -4292,8 +4431,54 @@ ${detection.projectContext}`;
4292
4431
  getLogger()?.error('Execution failed with full error', { error: result.error });
4293
4432
  }
4294
4433
  }
4434
+ // Send notifications (sound + webhook)
4435
+ // Skip notification if no tasks were actually executed this run (all were already done)
4436
+ const tasksExecutedThisRun = result.completedTasks - alreadyCompleted;
4437
+ const project = this.getProjectDisplayName();
4438
+ if (result.success && tasksExecutedThisRun > 0) {
4439
+ await sendNotification({
4440
+ event: 'completed',
4441
+ title: 'All tasks completed',
4442
+ detail: `${result.completedTasks}/${result.totalTasks} tasks completed successfully`,
4443
+ project,
4444
+ });
4445
+ }
4446
+ else if (result.blockedTask) {
4447
+ await sendNotification({
4448
+ event: 'attention',
4449
+ title: `Task failed: ${result.blockedTask.title}`,
4450
+ detail: result.error ? truncateError(result.error) : undefined,
4451
+ project,
4452
+ });
4453
+ }
4454
+ else if (!this.shouldStop) {
4455
+ // User-initiated pause doesn't need a notification
4456
+ await sendNotification({
4457
+ event: 'attention',
4458
+ title: 'Execution failed',
4459
+ detail: result.error ? truncateError(result.error) : undefined,
4460
+ project,
4461
+ });
4462
+ }
4295
4463
  // Clear cached orchestrator
4296
4464
  this.orchestrator = null;
4297
4465
  }
4466
+ /**
4467
+ * Get a display name for the project (folder name, or "projectName (folder)" if they differ)
4468
+ */
4469
+ getProjectDisplayName() {
4470
+ const folderName = basename(this.projectPath);
4471
+ try {
4472
+ const orchestrator = this.getOrchestrator();
4473
+ const projectName = orchestrator.getProjectName();
4474
+ if (projectName && projectName !== folderName) {
4475
+ return `${projectName} (${folderName})`;
4476
+ }
4477
+ }
4478
+ catch {
4479
+ // Orchestrator may not be loaded yet
4480
+ }
4481
+ return folderName;
4482
+ }
4298
4483
  }
4299
4484
  //# sourceMappingURL=app.js.map