coding-tool-x 3.4.11 → 3.4.12

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.
@@ -239,40 +239,190 @@ function escapeForXml(value) {
239
239
 
240
240
  function buildWindowsPopupCommand(title, message) {
241
241
  const script = [
242
+ "$ErrorActionPreference = 'Stop'",
243
+ `$titleText = '${escapeForPowerShellSingleQuote(title)}'`,
244
+ `$messageText = '${escapeForPowerShellSingleQuote(message)}'`,
245
+ "$segments = $messageText -split '\\s*\\|\\s*', 2",
246
+ "$headlineText = if ($segments.Length -ge 1) { $segments[0] } else { $messageText }",
247
+ "$detailText = if ($segments.Length -ge 2) { $segments[1] } else { '' }",
248
+ 'try {',
249
+ 'Add-Type -AssemblyName PresentationFramework',
250
+ 'Add-Type -AssemblyName PresentationCore',
251
+ 'Add-Type -AssemblyName WindowsBase',
252
+ '$brushConverter = New-Object System.Windows.Media.BrushConverter',
253
+ '$window = New-Object System.Windows.Window',
254
+ '$window.Width = 372',
255
+ '$window.Height = 118',
256
+ '$window.WindowStyle = [System.Windows.WindowStyle]::None',
257
+ '$window.ResizeMode = [System.Windows.ResizeMode]::NoResize',
258
+ '$window.AllowsTransparency = $true',
259
+ '$window.Background = [System.Windows.Media.Brushes]::Transparent',
260
+ '$window.ShowInTaskbar = $false',
261
+ '$window.Topmost = $true',
262
+ '$window.ShowActivated = $false',
263
+ '$window.Opacity = 0',
264
+ '$window.WindowStartupLocation = [System.Windows.WindowStartupLocation]::Manual',
265
+ '$root = New-Object System.Windows.Controls.Border',
266
+ '$root.CornerRadius = New-Object System.Windows.CornerRadius -ArgumentList 20',
267
+ '$root.Padding = New-Object System.Windows.Thickness -ArgumentList 16',
268
+ "$root.Background = $brushConverter.ConvertFromString('#F3F4F7')",
269
+ "$root.BorderBrush = $brushConverter.ConvertFromString('#D9DBE3')",
270
+ '$root.BorderThickness = New-Object System.Windows.Thickness -ArgumentList 1',
271
+ '$shadow = New-Object System.Windows.Media.Effects.DropShadowEffect',
272
+ "$shadow.Color = [System.Windows.Media.ColorConverter]::ConvertFromString('#22000000')",
273
+ '$shadow.BlurRadius = 26',
274
+ '$shadow.ShadowDepth = 0',
275
+ '$shadow.Opacity = 0.85',
276
+ '$root.Effect = $shadow',
277
+ '$grid = New-Object System.Windows.Controls.Grid',
278
+ '$iconColumn = New-Object System.Windows.Controls.ColumnDefinition',
279
+ '$iconColumn.Width = New-Object System.Windows.GridLength -ArgumentList 44',
280
+ '$contentColumn = New-Object System.Windows.Controls.ColumnDefinition',
281
+ '$grid.ColumnDefinitions.Add($iconColumn) | Out-Null',
282
+ '$grid.ColumnDefinitions.Add($contentColumn) | Out-Null',
283
+ '$iconHolder = New-Object System.Windows.Controls.Border',
284
+ '$iconHolder.Width = 34',
285
+ '$iconHolder.Height = 34',
286
+ '$iconHolder.HorizontalAlignment = [System.Windows.HorizontalAlignment]::Left',
287
+ '$iconHolder.VerticalAlignment = [System.Windows.VerticalAlignment]::Top',
288
+ '$iconHolder.CornerRadius = New-Object System.Windows.CornerRadius -ArgumentList 17',
289
+ "$iconHolder.Background = $brushConverter.ConvertFromString('#DDEBFF')",
290
+ '$iconGlyph = New-Object System.Windows.Controls.TextBlock',
291
+ '$iconGlyph.Text = [char]0x2713',
292
+ '$iconGlyph.FontSize = 18',
293
+ '$iconGlyph.FontWeight = [System.Windows.FontWeights]::Bold',
294
+ "$iconGlyph.Foreground = $brushConverter.ConvertFromString('#0A84FF')",
295
+ '$iconGlyph.HorizontalAlignment = [System.Windows.HorizontalAlignment]::Center',
296
+ '$iconGlyph.VerticalAlignment = [System.Windows.VerticalAlignment]::Center',
297
+ '$iconHolder.Child = $iconGlyph',
298
+ '$contentStack = New-Object System.Windows.Controls.StackPanel',
299
+ '$contentStack.Orientation = [System.Windows.Controls.Orientation]::Vertical',
300
+ '$contentStack.Margin = New-Object System.Windows.Thickness -ArgumentList 4,0,0,0',
301
+ '$metaGrid = New-Object System.Windows.Controls.Grid',
302
+ '$metaGrid.ColumnDefinitions.Add((New-Object System.Windows.Controls.ColumnDefinition)) | Out-Null',
303
+ '$timeColumn = New-Object System.Windows.Controls.ColumnDefinition',
304
+ '$timeColumn.Width = [System.Windows.GridLength]::Auto',
305
+ '$metaGrid.ColumnDefinitions.Add($timeColumn) | Out-Null',
306
+ '$appLabel = New-Object System.Windows.Controls.TextBlock',
307
+ '$appLabel.Text = $titleText',
308
+ '$appLabel.FontSize = 11.5',
309
+ '$appLabel.FontWeight = [System.Windows.FontWeights]::SemiBold',
310
+ "$appLabel.Foreground = $brushConverter.ConvertFromString('#6B6C73')",
311
+ '$appLabel.TextTrimming = [System.Windows.TextTrimming]::CharacterEllipsis',
312
+ '$stampLabel = New-Object System.Windows.Controls.TextBlock',
313
+ "$stampLabel.Text = '刚刚'",
314
+ '$stampLabel.FontSize = 11',
315
+ "$stampLabel.Foreground = $brushConverter.ConvertFromString('#8D8E95')",
316
+ '$stampLabel.Margin = New-Object System.Windows.Thickness -ArgumentList 12,0,0,0',
317
+ '[System.Windows.Controls.Grid]::SetColumn($stampLabel, 1)',
318
+ '$metaGrid.Children.Add($appLabel) | Out-Null',
319
+ '$metaGrid.Children.Add($stampLabel) | Out-Null',
320
+ '$headlineLabel = New-Object System.Windows.Controls.TextBlock',
321
+ '$headlineLabel.Text = $headlineText',
322
+ '$headlineLabel.FontSize = 14',
323
+ '$headlineLabel.FontWeight = [System.Windows.FontWeights]::SemiBold',
324
+ "$headlineLabel.Foreground = $brushConverter.ConvertFromString('#1F2024')",
325
+ '$headlineLabel.TextWrapping = [System.Windows.TextWrapping]::Wrap',
326
+ '$headlineLabel.Margin = New-Object System.Windows.Thickness -ArgumentList 0,6,0,0',
327
+ '$detailLabel = New-Object System.Windows.Controls.TextBlock',
328
+ '$detailLabel.Text = $detailText',
329
+ '$detailLabel.FontSize = 12',
330
+ "$detailLabel.Foreground = $brushConverter.ConvertFromString('#5F6168')",
331
+ '$detailLabel.TextWrapping = [System.Windows.TextWrapping]::Wrap',
332
+ '$detailLabel.Margin = New-Object System.Windows.Thickness -ArgumentList 0,4,0,0',
333
+ '$detailLabel.Visibility = if ([string]::IsNullOrWhiteSpace($detailText)) { [System.Windows.Visibility]::Collapsed } else { [System.Windows.Visibility]::Visible }',
334
+ '$contentStack.Children.Add($metaGrid) | Out-Null',
335
+ '$contentStack.Children.Add($headlineLabel) | Out-Null',
336
+ '$contentStack.Children.Add($detailLabel) | Out-Null',
337
+ '[System.Windows.Controls.Grid]::SetColumn($contentStack, 1)',
338
+ '$grid.Children.Add($iconHolder) | Out-Null',
339
+ '$grid.Children.Add($contentStack) | Out-Null',
340
+ '$root.Child = $grid',
341
+ '$window.Content = $root',
342
+ '$workArea = [System.Windows.SystemParameters]::WorkArea',
343
+ '$window.Left = $workArea.Right - $window.Width - 18',
344
+ '$window.Top = $workArea.Top + 18',
345
+ '$frame = New-Object System.Windows.Threading.DispatcherFrame',
346
+ '$window.Add_Closed({ $frame.Continue = $false })',
347
+ '$closeTimer = New-Object System.Windows.Threading.DispatcherTimer',
348
+ '$closeTimer.Interval = [TimeSpan]::FromMilliseconds(4600)',
349
+ '$closeTimer.Add_Tick({',
350
+ ' $closeTimer.Stop()',
351
+ ' $fadeOut = New-Object System.Windows.Media.Animation.DoubleAnimation',
352
+ ' $fadeOut.From = $window.Opacity',
353
+ ' $fadeOut.To = 0',
354
+ ' $fadeOut.Duration = [TimeSpan]::FromMilliseconds(220)',
355
+ ' $fadeOut.Add_Completed({ $window.Close() })',
356
+ ' $window.BeginAnimation([System.Windows.Window]::OpacityProperty, $fadeOut)',
357
+ '})',
358
+ '$window.Show()',
359
+ '$fadeIn = New-Object System.Windows.Media.Animation.DoubleAnimation',
360
+ '$fadeIn.From = 0',
361
+ '$fadeIn.To = 1',
362
+ '$fadeIn.Duration = [TimeSpan]::FromMilliseconds(180)',
363
+ '$window.BeginAnimation([System.Windows.Window]::OpacityProperty, $fadeIn)',
364
+ '$closeTimer.Start()',
365
+ '[System.Windows.Threading.Dispatcher]::PushFrame($frame)',
366
+ '} catch {',
242
367
  'Add-Type -AssemblyName System.Windows.Forms',
243
368
  'Add-Type -AssemblyName System.Drawing',
244
369
  '$form = New-Object System.Windows.Forms.Form',
245
- `$form.Text = '${escapeForPowerShellSingleQuote(title)}'`,
370
+ '$form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::None',
371
+ '$form.BackColor = [System.Drawing.Color]::FromArgb(247, 247, 250)',
246
372
  '$form.Width = 360',
247
- '$form.Height = 120',
373
+ '$form.Height = 118',
248
374
  '$form.StartPosition = [System.Windows.Forms.FormStartPosition]::Manual',
249
- '$form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedToolWindow',
250
375
  '$form.ShowInTaskbar = $false',
251
376
  '$form.TopMost = $true',
252
377
  '$form.MaximizeBox = $false',
253
378
  '$form.MinimizeBox = $false',
254
379
  '$workingArea = [System.Windows.Forms.Screen]::PrimaryScreen.WorkingArea',
255
- '$form.Location = New-Object System.Drawing.Point(($workingArea.Right - $form.Width - 16), ($workingArea.Top + 16))',
380
+ '$form.Location = New-Object System.Drawing.Point(($workingArea.Right - $form.Width - 18), ($workingArea.Top + 18))',
381
+ '$iconPanel = New-Object System.Windows.Forms.Panel',
382
+ '$iconPanel.Width = 34',
383
+ '$iconPanel.Height = 34',
384
+ '$iconPanel.BackColor = [System.Drawing.Color]::FromArgb(221, 235, 255)',
385
+ '$iconPanel.Location = New-Object System.Drawing.Point(16, 18)',
386
+ '$iconLabel = New-Object System.Windows.Forms.Label',
387
+ '$iconLabel.Text = [char]0x2713',
388
+ "$iconLabel.Font = New-Object System.Drawing.Font('Segoe UI', 14, [System.Drawing.FontStyle]::Bold)",
389
+ '$iconLabel.ForeColor = [System.Drawing.Color]::FromArgb(10, 132, 255)',
390
+ '$iconLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter',
391
+ '$iconLabel.Dock = [System.Windows.Forms.DockStyle]::Fill',
392
+ '$iconPanel.Controls.Add($iconLabel)',
256
393
  '$titleLabel = New-Object System.Windows.Forms.Label',
257
- `$titleLabel.Text = '${escapeForPowerShellSingleQuote(title)}'`,
258
- "$titleLabel.Font = New-Object System.Drawing.Font('Segoe UI', 10, [System.Drawing.FontStyle]::Bold)",
394
+ '$titleLabel.Text = $titleText',
395
+ "$titleLabel.Font = New-Object System.Drawing.Font('Segoe UI', 9, [System.Drawing.FontStyle]::Bold)",
396
+ '$titleLabel.ForeColor = [System.Drawing.Color]::FromArgb(109, 110, 115)',
259
397
  '$titleLabel.AutoSize = $true',
260
- '$titleLabel.Location = New-Object System.Drawing.Point(14, 12)',
261
- '$messageLabel = New-Object System.Windows.Forms.Label',
262
- `$messageLabel.Text = '${escapeForPowerShellSingleQuote(message)}'`,
263
- "$messageLabel.Font = New-Object System.Drawing.Font('Segoe UI', 9)",
264
- '$messageLabel.MaximumSize = New-Object System.Drawing.Size(332, 0)',
265
- '$messageLabel.AutoSize = $true',
266
- '$messageLabel.Location = New-Object System.Drawing.Point(14, 40)',
398
+ '$titleLabel.Location = New-Object System.Drawing.Point(60, 16)',
399
+ '$headlineLabel = New-Object System.Windows.Forms.Label',
400
+ '$headlineLabel.Text = $headlineText',
401
+ "$headlineLabel.Font = New-Object System.Drawing.Font('Segoe UI', 10.5, [System.Drawing.FontStyle]::Bold)",
402
+ '$headlineLabel.ForeColor = [System.Drawing.Color]::FromArgb(31, 32, 36)',
403
+ '$headlineLabel.MaximumSize = New-Object System.Drawing.Size(272, 0)',
404
+ '$headlineLabel.AutoSize = $true',
405
+ '$headlineLabel.Location = New-Object System.Drawing.Point(60, 36)',
406
+ '$detailLabel = New-Object System.Windows.Forms.Label',
407
+ '$detailLabel.Text = $detailText',
408
+ "$detailLabel.Font = New-Object System.Drawing.Font('Segoe UI', 9)",
409
+ '$detailLabel.ForeColor = [System.Drawing.Color]::FromArgb(95, 97, 104)',
410
+ '$detailLabel.MaximumSize = New-Object System.Drawing.Size(272, 0)',
411
+ '$detailLabel.AutoSize = $true',
412
+ '$detailLabel.Location = New-Object System.Drawing.Point(60, 60)',
413
+ '$detailLabel.Visible = -not [string]::IsNullOrWhiteSpace($detailText)',
414
+ '$form.Controls.Add($iconPanel)',
267
415
  '$form.Controls.Add($titleLabel)',
268
- '$form.Controls.Add($messageLabel)',
416
+ '$form.Controls.Add($headlineLabel)',
417
+ '$form.Controls.Add($detailLabel)',
269
418
  '$timer = New-Object System.Windows.Forms.Timer',
270
- '$timer.Interval = 5000',
419
+ '$timer.Interval = 4800',
271
420
  '$timer.Add_Tick({ $timer.Stop(); $form.Close() })',
272
421
  '$timer.Start()',
273
- '[void]$form.ShowDialog()'
422
+ '[void]$form.ShowDialog()',
423
+ '}'
274
424
  ].join('; ');
275
- return `powershell -NoProfile -Command ${JSON.stringify(script)}`;
425
+ return `powershell -NoProfile -STA -Command ${JSON.stringify(script)}`;
276
426
  }
277
427
 
278
428
  function generateNotifyScript(feishu = {}) {
@@ -379,7 +529,7 @@ function notify(mode, message) {
379
529
  const ps = "Add-Type -AssemblyName PresentationFramework; [System.Windows.MessageBox]::Show('" +
380
530
  escapeForPowerShellSingleQuote(message) + "', '" +
381
531
  escapeForPowerShellSingleQuote(title) + "', 'OK', 'Information')"
382
- const command = 'powershell -NoProfile -Command ' + JSON.stringify(ps) + ' || ' + popupCommand
532
+ const command = 'powershell -NoProfile -STA -Command ' + JSON.stringify(ps) + ' || ' + popupCommand
383
533
  execSync(command, { stdio: 'ignore', windowsHide: true })
384
534
  } else {
385
535
  execSync(popupCommand, { stdio: 'ignore', windowsHide: true })
@@ -499,40 +649,190 @@ function escapeForXml(value) {
499
649
 
500
650
  function buildWindowsPopupCommand(title, message) {
501
651
  const script = [
652
+ "$ErrorActionPreference = 'Stop'",
653
+ \`$titleText = '\${escapeForPowerShellSingleQuote(title)}'\`,
654
+ \`$messageText = '\${escapeForPowerShellSingleQuote(message)}'\`,
655
+ "$segments = $messageText -split '\\\\s*\\\\|\\\\s*', 2",
656
+ "$headlineText = if ($segments.Length -ge 1) { $segments[0] } else { $messageText }",
657
+ "$detailText = if ($segments.Length -ge 2) { $segments[1] } else { '' }",
658
+ 'try {',
659
+ 'Add-Type -AssemblyName PresentationFramework',
660
+ 'Add-Type -AssemblyName PresentationCore',
661
+ 'Add-Type -AssemblyName WindowsBase',
662
+ '$brushConverter = New-Object System.Windows.Media.BrushConverter',
663
+ '$window = New-Object System.Windows.Window',
664
+ '$window.Width = 372',
665
+ '$window.Height = 118',
666
+ '$window.WindowStyle = [System.Windows.WindowStyle]::None',
667
+ '$window.ResizeMode = [System.Windows.ResizeMode]::NoResize',
668
+ '$window.AllowsTransparency = $true',
669
+ '$window.Background = [System.Windows.Media.Brushes]::Transparent',
670
+ '$window.ShowInTaskbar = $false',
671
+ '$window.Topmost = $true',
672
+ '$window.ShowActivated = $false',
673
+ '$window.Opacity = 0',
674
+ '$window.WindowStartupLocation = [System.Windows.WindowStartupLocation]::Manual',
675
+ '$root = New-Object System.Windows.Controls.Border',
676
+ '$root.CornerRadius = New-Object System.Windows.CornerRadius -ArgumentList 20',
677
+ '$root.Padding = New-Object System.Windows.Thickness -ArgumentList 16',
678
+ "$root.Background = $brushConverter.ConvertFromString('#F3F4F7')",
679
+ "$root.BorderBrush = $brushConverter.ConvertFromString('#D9DBE3')",
680
+ '$root.BorderThickness = New-Object System.Windows.Thickness -ArgumentList 1',
681
+ '$shadow = New-Object System.Windows.Media.Effects.DropShadowEffect',
682
+ "$shadow.Color = [System.Windows.Media.ColorConverter]::ConvertFromString('#22000000')",
683
+ '$shadow.BlurRadius = 26',
684
+ '$shadow.ShadowDepth = 0',
685
+ '$shadow.Opacity = 0.85',
686
+ '$root.Effect = $shadow',
687
+ '$grid = New-Object System.Windows.Controls.Grid',
688
+ '$iconColumn = New-Object System.Windows.Controls.ColumnDefinition',
689
+ '$iconColumn.Width = New-Object System.Windows.GridLength -ArgumentList 44',
690
+ '$contentColumn = New-Object System.Windows.Controls.ColumnDefinition',
691
+ '$grid.ColumnDefinitions.Add($iconColumn) | Out-Null',
692
+ '$grid.ColumnDefinitions.Add($contentColumn) | Out-Null',
693
+ '$iconHolder = New-Object System.Windows.Controls.Border',
694
+ '$iconHolder.Width = 34',
695
+ '$iconHolder.Height = 34',
696
+ '$iconHolder.HorizontalAlignment = [System.Windows.HorizontalAlignment]::Left',
697
+ '$iconHolder.VerticalAlignment = [System.Windows.VerticalAlignment]::Top',
698
+ '$iconHolder.CornerRadius = New-Object System.Windows.CornerRadius -ArgumentList 17',
699
+ "$iconHolder.Background = $brushConverter.ConvertFromString('#DDEBFF')",
700
+ '$iconGlyph = New-Object System.Windows.Controls.TextBlock',
701
+ '$iconGlyph.Text = [char]0x2713',
702
+ '$iconGlyph.FontSize = 18',
703
+ '$iconGlyph.FontWeight = [System.Windows.FontWeights]::Bold',
704
+ "$iconGlyph.Foreground = $brushConverter.ConvertFromString('#0A84FF')",
705
+ '$iconGlyph.HorizontalAlignment = [System.Windows.HorizontalAlignment]::Center',
706
+ '$iconGlyph.VerticalAlignment = [System.Windows.VerticalAlignment]::Center',
707
+ '$iconHolder.Child = $iconGlyph',
708
+ '$contentStack = New-Object System.Windows.Controls.StackPanel',
709
+ '$contentStack.Orientation = [System.Windows.Controls.Orientation]::Vertical',
710
+ '$contentStack.Margin = New-Object System.Windows.Thickness -ArgumentList 4,0,0,0',
711
+ '$metaGrid = New-Object System.Windows.Controls.Grid',
712
+ '$metaGrid.ColumnDefinitions.Add((New-Object System.Windows.Controls.ColumnDefinition)) | Out-Null',
713
+ '$timeColumn = New-Object System.Windows.Controls.ColumnDefinition',
714
+ '$timeColumn.Width = [System.Windows.GridLength]::Auto',
715
+ '$metaGrid.ColumnDefinitions.Add($timeColumn) | Out-Null',
716
+ '$appLabel = New-Object System.Windows.Controls.TextBlock',
717
+ '$appLabel.Text = $titleText',
718
+ '$appLabel.FontSize = 11.5',
719
+ '$appLabel.FontWeight = [System.Windows.FontWeights]::SemiBold',
720
+ "$appLabel.Foreground = $brushConverter.ConvertFromString('#6B6C73')",
721
+ '$appLabel.TextTrimming = [System.Windows.TextTrimming]::CharacterEllipsis',
722
+ '$stampLabel = New-Object System.Windows.Controls.TextBlock',
723
+ "$stampLabel.Text = '刚刚'",
724
+ '$stampLabel.FontSize = 11',
725
+ "$stampLabel.Foreground = $brushConverter.ConvertFromString('#8D8E95')",
726
+ '$stampLabel.Margin = New-Object System.Windows.Thickness -ArgumentList 12,0,0,0',
727
+ '[System.Windows.Controls.Grid]::SetColumn($stampLabel, 1)',
728
+ '$metaGrid.Children.Add($appLabel) | Out-Null',
729
+ '$metaGrid.Children.Add($stampLabel) | Out-Null',
730
+ '$headlineLabel = New-Object System.Windows.Controls.TextBlock',
731
+ '$headlineLabel.Text = $headlineText',
732
+ '$headlineLabel.FontSize = 14',
733
+ '$headlineLabel.FontWeight = [System.Windows.FontWeights]::SemiBold',
734
+ "$headlineLabel.Foreground = $brushConverter.ConvertFromString('#1F2024')",
735
+ '$headlineLabel.TextWrapping = [System.Windows.TextWrapping]::Wrap',
736
+ '$headlineLabel.Margin = New-Object System.Windows.Thickness -ArgumentList 0,6,0,0',
737
+ '$detailLabel = New-Object System.Windows.Controls.TextBlock',
738
+ '$detailLabel.Text = $detailText',
739
+ '$detailLabel.FontSize = 12',
740
+ "$detailLabel.Foreground = $brushConverter.ConvertFromString('#5F6168')",
741
+ '$detailLabel.TextWrapping = [System.Windows.TextWrapping]::Wrap',
742
+ '$detailLabel.Margin = New-Object System.Windows.Thickness -ArgumentList 0,4,0,0',
743
+ '$detailLabel.Visibility = if ([string]::IsNullOrWhiteSpace($detailText)) { [System.Windows.Visibility]::Collapsed } else { [System.Windows.Visibility]::Visible }',
744
+ '$contentStack.Children.Add($metaGrid) | Out-Null',
745
+ '$contentStack.Children.Add($headlineLabel) | Out-Null',
746
+ '$contentStack.Children.Add($detailLabel) | Out-Null',
747
+ '[System.Windows.Controls.Grid]::SetColumn($contentStack, 1)',
748
+ '$grid.Children.Add($iconHolder) | Out-Null',
749
+ '$grid.Children.Add($contentStack) | Out-Null',
750
+ '$root.Child = $grid',
751
+ '$window.Content = $root',
752
+ '$workArea = [System.Windows.SystemParameters]::WorkArea',
753
+ '$window.Left = $workArea.Right - $window.Width - 18',
754
+ '$window.Top = $workArea.Top + 18',
755
+ '$frame = New-Object System.Windows.Threading.DispatcherFrame',
756
+ '$window.Add_Closed({ $frame.Continue = $false })',
757
+ '$closeTimer = New-Object System.Windows.Threading.DispatcherTimer',
758
+ '$closeTimer.Interval = [TimeSpan]::FromMilliseconds(4600)',
759
+ '$closeTimer.Add_Tick({',
760
+ ' $closeTimer.Stop()',
761
+ ' $fadeOut = New-Object System.Windows.Media.Animation.DoubleAnimation',
762
+ ' $fadeOut.From = $window.Opacity',
763
+ ' $fadeOut.To = 0',
764
+ ' $fadeOut.Duration = [TimeSpan]::FromMilliseconds(220)',
765
+ ' $fadeOut.Add_Completed({ $window.Close() })',
766
+ ' $window.BeginAnimation([System.Windows.Window]::OpacityProperty, $fadeOut)',
767
+ '})',
768
+ '$window.Show()',
769
+ '$fadeIn = New-Object System.Windows.Media.Animation.DoubleAnimation',
770
+ '$fadeIn.From = 0',
771
+ '$fadeIn.To = 1',
772
+ '$fadeIn.Duration = [TimeSpan]::FromMilliseconds(180)',
773
+ '$window.BeginAnimation([System.Windows.Window]::OpacityProperty, $fadeIn)',
774
+ '$closeTimer.Start()',
775
+ '[System.Windows.Threading.Dispatcher]::PushFrame($frame)',
776
+ '} catch {',
502
777
  'Add-Type -AssemblyName System.Windows.Forms',
503
778
  'Add-Type -AssemblyName System.Drawing',
504
779
  '$form = New-Object System.Windows.Forms.Form',
505
- \`$form.Text = '\${escapeForPowerShellSingleQuote(title)}'\`,
780
+ '$form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::None',
781
+ '$form.BackColor = [System.Drawing.Color]::FromArgb(247, 247, 250)',
506
782
  '$form.Width = 360',
507
- '$form.Height = 120',
783
+ '$form.Height = 118',
508
784
  '$form.StartPosition = [System.Windows.Forms.FormStartPosition]::Manual',
509
- '$form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedToolWindow',
510
785
  '$form.ShowInTaskbar = $false',
511
786
  '$form.TopMost = $true',
512
787
  '$form.MaximizeBox = $false',
513
788
  '$form.MinimizeBox = $false',
514
789
  '$workingArea = [System.Windows.Forms.Screen]::PrimaryScreen.WorkingArea',
515
- '$form.Location = New-Object System.Drawing.Point(($workingArea.Right - $form.Width - 16), ($workingArea.Top + 16))',
790
+ '$form.Location = New-Object System.Drawing.Point(($workingArea.Right - $form.Width - 18), ($workingArea.Top + 18))',
791
+ '$iconPanel = New-Object System.Windows.Forms.Panel',
792
+ '$iconPanel.Width = 34',
793
+ '$iconPanel.Height = 34',
794
+ '$iconPanel.BackColor = [System.Drawing.Color]::FromArgb(221, 235, 255)',
795
+ '$iconPanel.Location = New-Object System.Drawing.Point(16, 18)',
796
+ '$iconLabel = New-Object System.Windows.Forms.Label',
797
+ '$iconLabel.Text = [char]0x2713',
798
+ "$iconLabel.Font = New-Object System.Drawing.Font('Segoe UI', 14, [System.Drawing.FontStyle]::Bold)",
799
+ '$iconLabel.ForeColor = [System.Drawing.Color]::FromArgb(10, 132, 255)',
800
+ '$iconLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter',
801
+ '$iconLabel.Dock = [System.Windows.Forms.DockStyle]::Fill',
802
+ '$iconPanel.Controls.Add($iconLabel)',
516
803
  '$titleLabel = New-Object System.Windows.Forms.Label',
517
- \`$titleLabel.Text = '\${escapeForPowerShellSingleQuote(title)}'\`,
518
- " $titleLabel.Font = New-Object System.Drawing.Font('Segoe UI', 10, [System.Drawing.FontStyle]::Bold)".trim(),
804
+ '$titleLabel.Text = $titleText',
805
+ "$titleLabel.Font = New-Object System.Drawing.Font('Segoe UI', 9, [System.Drawing.FontStyle]::Bold)",
806
+ '$titleLabel.ForeColor = [System.Drawing.Color]::FromArgb(109, 110, 115)',
519
807
  '$titleLabel.AutoSize = $true',
520
- '$titleLabel.Location = New-Object System.Drawing.Point(14, 12)',
521
- '$messageLabel = New-Object System.Windows.Forms.Label',
522
- \`$messageLabel.Text = '\${escapeForPowerShellSingleQuote(message)}'\`,
523
- " $messageLabel.Font = New-Object System.Drawing.Font('Segoe UI', 9)".trim(),
524
- '$messageLabel.MaximumSize = New-Object System.Drawing.Size(332, 0)',
525
- '$messageLabel.AutoSize = $true',
526
- '$messageLabel.Location = New-Object System.Drawing.Point(14, 40)',
808
+ '$titleLabel.Location = New-Object System.Drawing.Point(60, 16)',
809
+ '$headlineLabel = New-Object System.Windows.Forms.Label',
810
+ '$headlineLabel.Text = $headlineText',
811
+ "$headlineLabel.Font = New-Object System.Drawing.Font('Segoe UI', 10.5, [System.Drawing.FontStyle]::Bold)",
812
+ '$headlineLabel.ForeColor = [System.Drawing.Color]::FromArgb(31, 32, 36)',
813
+ '$headlineLabel.MaximumSize = New-Object System.Drawing.Size(272, 0)',
814
+ '$headlineLabel.AutoSize = $true',
815
+ '$headlineLabel.Location = New-Object System.Drawing.Point(60, 36)',
816
+ '$detailLabel = New-Object System.Windows.Forms.Label',
817
+ '$detailLabel.Text = $detailText',
818
+ "$detailLabel.Font = New-Object System.Drawing.Font('Segoe UI', 9)",
819
+ '$detailLabel.ForeColor = [System.Drawing.Color]::FromArgb(95, 97, 104)',
820
+ '$detailLabel.MaximumSize = New-Object System.Drawing.Size(272, 0)',
821
+ '$detailLabel.AutoSize = $true',
822
+ '$detailLabel.Location = New-Object System.Drawing.Point(60, 60)',
823
+ '$detailLabel.Visible = -not [string]::IsNullOrWhiteSpace($detailText)',
824
+ '$form.Controls.Add($iconPanel)',
527
825
  '$form.Controls.Add($titleLabel)',
528
- '$form.Controls.Add($messageLabel)',
826
+ '$form.Controls.Add($headlineLabel)',
827
+ '$form.Controls.Add($detailLabel)',
529
828
  '$timer = New-Object System.Windows.Forms.Timer',
530
- '$timer.Interval = 5000',
829
+ '$timer.Interval = 4800',
531
830
  '$timer.Add_Tick({ $timer.Stop(); $form.Close() })',
532
831
  '$timer.Start()',
533
- '[void]$form.ShowDialog()'
832
+ '[void]$form.ShowDialog()',
833
+ '}'
534
834
  ].join('; ')
535
- return 'powershell -NoProfile -Command ' + JSON.stringify(script)
835
+ return 'powershell -NoProfile -STA -Command ' + JSON.stringify(script)
536
836
  }
537
837
  `;
538
838
  }
@@ -900,6 +1200,205 @@ function saveNotificationSettings(input = {}) {
900
1200
  return getNotificationSettings();
901
1201
  }
902
1202
 
1203
+ function parseNotifyTypeMarker(command) {
1204
+ const marker = String(command || '').match(/--cc-notify-type=(['"])?(dialog|notification)\1/i);
1205
+ return marker?.[2] ? normalizeType(marker[2].toLowerCase()) : null;
1206
+ }
1207
+
1208
+ function getStopHookCommand(settings = {}) {
1209
+ const hooks = settings?.hooks?.Stop;
1210
+ if (!Array.isArray(hooks) || hooks.length === 0) {
1211
+ return '';
1212
+ }
1213
+
1214
+ let fallbackCommand = '';
1215
+ for (const group of hooks) {
1216
+ const groupHooks = Array.isArray(group?.hooks) ? group.hooks : [];
1217
+ for (const hook of groupHooks) {
1218
+ const command = String(hook?.command || '');
1219
+ if (!command) continue;
1220
+ if (!fallbackCommand) {
1221
+ fallbackCommand = command;
1222
+ }
1223
+ if (command.includes('notify-hook.js')) {
1224
+ return command;
1225
+ }
1226
+ }
1227
+ }
1228
+
1229
+ return fallbackCommand;
1230
+ }
1231
+
1232
+ function normalizePathForCompare(rawPath) {
1233
+ return String(rawPath || '').replace(/\\/g, '/');
1234
+ }
1235
+
1236
+ function shouldRepairStopHook(settings, expectedScriptPath = PATHS.notifyHook, fileExists = fs.existsSync) {
1237
+ const command = getStopHookCommand(settings);
1238
+ if (!command || !command.includes('notify-hook.js')) {
1239
+ return false;
1240
+ }
1241
+
1242
+ const normalizedCommand = normalizePathForCompare(command);
1243
+ const normalizedExpected = normalizePathForCompare(expectedScriptPath);
1244
+ if (!normalizedCommand.includes(normalizedExpected)) {
1245
+ return true;
1246
+ }
1247
+
1248
+ const markerType = parseNotifyTypeMarker(command);
1249
+ if (!markerType) {
1250
+ return true;
1251
+ }
1252
+
1253
+ return !fileExists(expectedScriptPath);
1254
+ }
1255
+
1256
+ function buildStopHookCommand(type) {
1257
+ return buildClaudeCommand(type);
1258
+ }
1259
+
1260
+ function parseStopHookStatus(settings = {}) {
1261
+ const stopGroups = Array.isArray(settings?.hooks?.Stop) ? settings.hooks.Stop : [];
1262
+ if (stopGroups.length === 0) {
1263
+ return { enabled: false, type: 'notification' };
1264
+ }
1265
+
1266
+ let sawNotificationLikeCommand = false;
1267
+ for (const group of stopGroups) {
1268
+ const groupHooks = Array.isArray(group?.hooks) ? group.hooks : [];
1269
+ for (const hook of groupHooks) {
1270
+ const command = String(hook?.command || '');
1271
+ if (!command) continue;
1272
+
1273
+ const markerType = parseNotifyTypeMarker(command) || parseManagedType(command);
1274
+ if (markerType) {
1275
+ return { enabled: true, type: markerType };
1276
+ }
1277
+
1278
+ const isDialog = command.includes('display dialog') ||
1279
+ command.includes('MessageBox') ||
1280
+ command.includes('zenity --info');
1281
+ if (isDialog) {
1282
+ return { enabled: true, type: 'dialog' };
1283
+ }
1284
+
1285
+ const isNotification = command.includes('display notification') ||
1286
+ command.includes('Popup') ||
1287
+ command.includes('GraphicsPath') ||
1288
+ command.includes('TransparencyKey') ||
1289
+ command.includes('FormBorderStyle]::None') ||
1290
+ command.includes('AllowsTransparency') ||
1291
+ command.includes('notify-send') ||
1292
+ command.includes('ToastNotificationManager') ||
1293
+ command.includes('CreateToastNotifier') ||
1294
+ command.includes('notify-hook.js');
1295
+ if (isNotification) {
1296
+ sawNotificationLikeCommand = true;
1297
+ }
1298
+ }
1299
+ }
1300
+
1301
+ return sawNotificationLikeCommand
1302
+ ? { enabled: true, type: 'notification' }
1303
+ : { enabled: false, type: 'notification' };
1304
+ }
1305
+
1306
+ function normalizeSavedPlatformStatus(platform = {}) {
1307
+ return {
1308
+ enabled: platform?.enabled === true,
1309
+ type: normalizeType(platform?.type)
1310
+ };
1311
+ }
1312
+
1313
+ function buildLegacyClaudeSaveInput(input = {}, currentSettings = getNotificationSettings()) {
1314
+ return {
1315
+ platforms: {
1316
+ claude: input.stopHook !== undefined
1317
+ ? normalizePlatformInput(input.stopHook)
1318
+ : { enabled: false, type: 'notification' },
1319
+ codex: normalizeSavedPlatformStatus(currentSettings?.platforms?.codex),
1320
+ gemini: normalizeSavedPlatformStatus(currentSettings?.platforms?.gemini),
1321
+ opencode: normalizeSavedPlatformStatus(currentSettings?.platforms?.opencode)
1322
+ },
1323
+ feishu: input.feishu !== undefined
1324
+ ? {
1325
+ enabled: input.feishu?.enabled === true,
1326
+ webhookUrl: input.feishu?.webhookUrl || ''
1327
+ }
1328
+ : {
1329
+ enabled: currentSettings?.feishu?.enabled === true,
1330
+ webhookUrl: currentSettings?.feishu?.webhookUrl || ''
1331
+ }
1332
+ };
1333
+ }
1334
+
1335
+ function getLegacyClaudeHookSettings() {
1336
+ return {
1337
+ success: true,
1338
+ stopHook: parseStopHookStatus(readClaudeSettings()),
1339
+ feishu: getFeishuConfig(),
1340
+ platform: os.platform()
1341
+ };
1342
+ }
1343
+
1344
+ function saveLegacyClaudeHookSettings(input = {}) {
1345
+ const currentSettings = getNotificationSettings();
1346
+ saveNotificationSettings(buildLegacyClaudeSaveInput(input, currentSettings));
1347
+ return getLegacyClaudeHookSettings();
1348
+ }
1349
+
1350
+ function initDefaultHooks() {
1351
+ try {
1352
+ const uiConfig = loadUIConfig();
1353
+ if (uiConfig.claudeNotificationDisabledByUser === true) {
1354
+ console.log('[Claude Hooks] 用户已主动关闭通知,跳过自动初始化');
1355
+ return;
1356
+ }
1357
+
1358
+ const currentClaudeSettings = readClaudeSettings();
1359
+ const currentStatus = parseStopHookStatus(currentClaudeSettings);
1360
+
1361
+ if (currentStatus.enabled) {
1362
+ if (shouldRepairStopHook(currentClaudeSettings)) {
1363
+ const currentSettings = getNotificationSettings();
1364
+ saveNotificationSettings({
1365
+ platforms: {
1366
+ claude: { enabled: true, type: currentStatus.type || 'notification' },
1367
+ codex: normalizeSavedPlatformStatus(currentSettings?.platforms?.codex),
1368
+ gemini: normalizeSavedPlatformStatus(currentSettings?.platforms?.gemini),
1369
+ opencode: normalizeSavedPlatformStatus(currentSettings?.platforms?.opencode)
1370
+ },
1371
+ feishu: {
1372
+ enabled: currentSettings?.feishu?.enabled === true,
1373
+ webhookUrl: currentSettings?.feishu?.webhookUrl || ''
1374
+ }
1375
+ });
1376
+ console.log('[Claude Hooks] 检测到旧版 Stop hook 路径,已自动修复');
1377
+ } else {
1378
+ console.log('[Claude Hooks] 已存在 Stop hook 配置,跳过初始化');
1379
+ }
1380
+ return;
1381
+ }
1382
+
1383
+ const currentSettings = getNotificationSettings();
1384
+ saveNotificationSettings({
1385
+ platforms: {
1386
+ claude: { enabled: true, type: 'notification' },
1387
+ codex: normalizeSavedPlatformStatus(currentSettings?.platforms?.codex),
1388
+ gemini: normalizeSavedPlatformStatus(currentSettings?.platforms?.gemini),
1389
+ opencode: normalizeSavedPlatformStatus(currentSettings?.platforms?.opencode)
1390
+ },
1391
+ feishu: {
1392
+ enabled: currentSettings?.feishu?.enabled === true,
1393
+ webhookUrl: currentSettings?.feishu?.webhookUrl || ''
1394
+ }
1395
+ });
1396
+ console.log('[Claude Hooks] 已自动开启任务完成通知(右上角卡片)');
1397
+ } catch (error) {
1398
+ console.error('[Claude Hooks] 初始化默认配置失败:', error);
1399
+ }
1400
+ }
1401
+
903
1402
  function sendFeishuTest(webhookUrl) {
904
1403
  return new Promise((resolve, reject) => {
905
1404
  try {
@@ -965,7 +1464,7 @@ function generateSystemNotificationCommand(type, message, platformOverride = os.
965
1464
  if (platform === 'win32') {
966
1465
  const popupCommand = buildWindowsPopupCommand(title, message);
967
1466
  if (normalizedType === 'dialog') {
968
- return `powershell -NoProfile -Command "Add-Type -AssemblyName PresentationFramework; [System.Windows.MessageBox]::Show('${escapeForPowerShellSingleQuote(message)}', '${escapeForPowerShellSingleQuote(title)}', 'OK', 'Information')" || ${popupCommand}`;
1467
+ return `powershell -NoProfile -STA -Command "Add-Type -AssemblyName PresentationFramework; [System.Windows.MessageBox]::Show('${escapeForPowerShellSingleQuote(message)}', '${escapeForPowerShellSingleQuote(title)}', 'OK', 'Information')" || ${popupCommand}`;
969
1468
  }
970
1469
  return popupCommand;
971
1470
  }
@@ -988,8 +1487,11 @@ function testNotification({ type, testFeishu, webhookUrl } = {}) {
988
1487
  module.exports = {
989
1488
  MANAGED_HOOK_NAME,
990
1489
  getNotificationSettings,
1490
+ getLegacyClaudeHookSettings,
991
1491
  saveNotificationSettings,
1492
+ saveLegacyClaudeHookSettings,
992
1493
  testNotification,
1494
+ initDefaultHooks,
993
1495
  getOpenCodeManagedPluginPath,
994
1496
  buildOpenCodePluginContent,
995
1497
  buildCodexNotifyCommand,
@@ -999,6 +1501,7 @@ module.exports = {
999
1501
  applyClaudeDisablePreference,
1000
1502
  getManagedCommandType,
1001
1503
  parseManagedType,
1504
+ parseNotifyTypeMarker,
1002
1505
  getClaudeHookStatus,
1003
1506
  getCodexHookStatus,
1004
1507
  getGeminiHookStatus,
@@ -1009,10 +1512,13 @@ module.exports = {
1009
1512
  validateFeishuWebhookUrl,
1010
1513
  buildCodexNotifyCommand,
1011
1514
  buildGeminiCommand,
1515
+ buildStopHookCommand,
1012
1516
  buildClaudeCommand,
1013
1517
  buildOpenCodePluginContent,
1014
1518
  getOpenCodeManagedPluginPath,
1015
1519
  generateNotifyScript,
1016
- generateSystemNotificationCommand
1520
+ generateSystemNotificationCommand,
1521
+ parseStopHookStatus,
1522
+ shouldRepairStopHook
1017
1523
  }
1018
1524
  };