@opencoreai/opencore 0.2.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,493 @@
1
+ #import <Cocoa/Cocoa.h>
2
+ #import <QuartzCore/QuartzCore.h>
3
+
4
+ @interface OpenCoreIndicatorAppDelegate : NSObject <NSApplicationDelegate>
5
+ @property(nonatomic, strong) NSStatusItem *statusItem;
6
+ @property(nonatomic, strong) NSMenu *menu;
7
+ @property(nonatomic, strong) NSTimer *timer;
8
+ @property(nonatomic, copy) NSString *statePath;
9
+ @property(nonatomic, strong) NSDictionary *lastState;
10
+ @property(nonatomic, strong) CAGradientLayer *statusPillLayer;
11
+ @property(nonatomic, strong) CAGradientLayer *statusShineLayer;
12
+ @property(nonatomic, strong) CALayer *statusGlowLayer;
13
+ @property(nonatomic, strong) NSTrackingArea *statusTrackingArea;
14
+ @property(nonatomic, assign) BOOL hoverActive;
15
+ @end
16
+
17
+ @implementation OpenCoreIndicatorAppDelegate
18
+
19
+ - (BOOL)isDarkMode {
20
+ if (@available(macOS 10.14, *)) {
21
+ NSAppearanceName best = [NSApp.effectiveAppearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]];
22
+ return [best isEqualToString:NSAppearanceNameDarkAqua];
23
+ }
24
+ return NO;
25
+ }
26
+
27
+ - (NSColor *)menuHeaderTextColor {
28
+ return [self isDarkMode]
29
+ ? [NSColor colorWithCalibratedRed:0.96 green:0.93 blue:0.87 alpha:1.0]
30
+ : [NSColor colorWithCalibratedRed:0.18 green:0.12 blue:0.09 alpha:1.0];
31
+ }
32
+
33
+ - (NSColor *)menuBodyTextColor {
34
+ return [self isDarkMode]
35
+ ? [NSColor colorWithCalibratedRed:0.82 green:0.73 blue:0.65 alpha:1.0]
36
+ : [NSColor colorWithCalibratedRed:0.38 green:0.28 blue:0.22 alpha:1.0];
37
+ }
38
+
39
+ - (NSColor *)menuAccentTextColor {
40
+ return [self isDarkMode]
41
+ ? [NSColor colorWithCalibratedRed:1.0 green:0.69 blue:0.35 alpha:1.0]
42
+ : [NSColor colorWithCalibratedRed:0.74 green:0.37 blue:0.09 alpha:1.0];
43
+ }
44
+
45
+ - (NSColor *)activityBubbleFillColor {
46
+ return [self isDarkMode]
47
+ ? [NSColor colorWithCalibratedRed:0.15 green:0.11 blue:0.09 alpha:0.96]
48
+ : [NSColor colorWithCalibratedWhite:0.985 alpha:1.0];
49
+ }
50
+
51
+ - (NSColor *)activityBubbleFillEndColor {
52
+ return [self isDarkMode]
53
+ ? [NSColor colorWithCalibratedRed:0.20 green:0.14 blue:0.11 alpha:0.98]
54
+ : [NSColor colorWithCalibratedRed:0.995 green:0.965 blue:0.93 alpha:1.0];
55
+ }
56
+
57
+ - (NSColor *)activityBubbleBorderColor {
58
+ return [self isDarkMode]
59
+ ? [NSColor colorWithCalibratedRed:0.33 green:0.23 blue:0.17 alpha:1.0]
60
+ : [NSColor colorWithCalibratedRed:0.93 green:0.86 blue:0.79 alpha:1.0];
61
+ }
62
+
63
+ - (NSColor *)orangeBadgeFillColor {
64
+ return [NSColor colorWithCalibratedRed:0.95 green:0.55 blue:0.18 alpha:0.16];
65
+ }
66
+
67
+ - (NSColor *)pillStartColorForActive:(BOOL)active {
68
+ if ([self isDarkMode]) {
69
+ return active
70
+ ? [NSColor colorWithCalibratedRed:0.78 green:0.33 blue:0.09 alpha:0.98]
71
+ : [NSColor colorWithCalibratedRed:0.58 green:0.28 blue:0.10 alpha:0.94];
72
+ }
73
+ return active
74
+ ? [NSColor colorWithCalibratedRed:0.91 green:0.46 blue:0.10 alpha:0.98]
75
+ : [NSColor colorWithCalibratedRed:0.87 green:0.47 blue:0.16 alpha:0.92];
76
+ }
77
+
78
+ - (NSColor *)pillEndColorForActive:(BOOL)active {
79
+ if ([self isDarkMode]) {
80
+ return active
81
+ ? [NSColor colorWithCalibratedRed:1.0 green:0.58 blue:0.22 alpha:0.98]
82
+ : [NSColor colorWithCalibratedRed:0.84 green:0.45 blue:0.16 alpha:0.94];
83
+ }
84
+ return active
85
+ ? [NSColor colorWithCalibratedRed:0.99 green:0.66 blue:0.26 alpha:0.98]
86
+ : [NSColor colorWithCalibratedRed:0.95 green:0.59 blue:0.20 alpha:0.92];
87
+ }
88
+
89
+ - (NSColor *)orangeBadgeBorderColor {
90
+ return [self isDarkMode]
91
+ ? [NSColor colorWithCalibratedRed:1.0 green:0.70 blue:0.40 alpha:0.72]
92
+ : [NSColor colorWithCalibratedRed:0.96 green:0.54 blue:0.15 alpha:0.55];
93
+ }
94
+
95
+ - (NSColor *)orangeBadgeTextColor {
96
+ return [self isDarkMode]
97
+ ? [NSColor colorWithCalibratedRed:1.0 green:0.82 blue:0.63 alpha:1.0]
98
+ : [NSColor colorWithCalibratedRed:0.78 green:0.35 blue:0.04 alpha:1.0];
99
+ }
100
+
101
+ - (NSColor *)activityBubbleTextColor {
102
+ return [self isDarkMode]
103
+ ? [NSColor colorWithCalibratedRed:0.94 green:0.89 blue:0.84 alpha:1.0]
104
+ : [NSColor colorWithCalibratedRed:0.20 green:0.13 blue:0.09 alpha:1.0];
105
+ }
106
+
107
+ - (void)installWaveAnimationOnLayer:(CALayer *)layer key:(NSString *)key amplitude:(CGFloat)amplitude duration:(CFTimeInterval)duration {
108
+ if (layer == nil) return;
109
+ CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
110
+ animation.fromValue = @(-amplitude);
111
+ animation.toValue = @(amplitude);
112
+ animation.duration = duration;
113
+ animation.repeatCount = HUGE_VALF;
114
+ animation.autoreverses = YES;
115
+ animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
116
+ [layer addAnimation:animation forKey:key];
117
+ }
118
+
119
+ - (void)ensureStatusLayers {
120
+ NSStatusBarButton *button = self.statusItem.button;
121
+ if (button == nil) return;
122
+
123
+ button.wantsLayer = YES;
124
+ button.layer.masksToBounds = NO;
125
+
126
+ if (self.statusGlowLayer == nil) {
127
+ self.statusGlowLayer = [CALayer layer];
128
+ self.statusGlowLayer.cornerRadius = 12.0;
129
+ self.statusGlowLayer.shadowOpacity = 0.34f;
130
+ self.statusGlowLayer.shadowRadius = 10.0f;
131
+ self.statusGlowLayer.shadowOffset = CGSizeMake(0, 0);
132
+ self.statusGlowLayer.zPosition = -20.0;
133
+ [button.layer insertSublayer:self.statusGlowLayer atIndex:0];
134
+ }
135
+
136
+ if (self.statusPillLayer == nil) {
137
+ self.statusPillLayer = [CAGradientLayer layer];
138
+ self.statusPillLayer.cornerRadius = 12.0;
139
+ self.statusPillLayer.borderWidth = 1.0;
140
+ self.statusPillLayer.startPoint = CGPointMake(0.0, 0.5);
141
+ self.statusPillLayer.endPoint = CGPointMake(1.0, 0.5);
142
+ self.statusPillLayer.zPosition = -10.0;
143
+ [button.layer insertSublayer:self.statusPillLayer above:self.statusGlowLayer];
144
+ }
145
+
146
+ if (self.statusShineLayer == nil) {
147
+ self.statusShineLayer = [CAGradientLayer layer];
148
+ self.statusShineLayer.startPoint = CGPointMake(0.0, 0.5);
149
+ self.statusShineLayer.endPoint = CGPointMake(1.0, 0.5);
150
+ self.statusShineLayer.colors = @[
151
+ (__bridge id)[NSColor colorWithCalibratedWhite:1.0 alpha:0.0].CGColor,
152
+ (__bridge id)[NSColor colorWithCalibratedWhite:1.0 alpha:0.32].CGColor,
153
+ (__bridge id)[NSColor colorWithCalibratedWhite:1.0 alpha:0.0].CGColor
154
+ ];
155
+ self.statusShineLayer.locations = @[@0.0, @0.45, @0.9];
156
+ self.statusShineLayer.zPosition = 1.0;
157
+ [self.statusPillLayer addSublayer:self.statusShineLayer];
158
+ [self installWaveAnimationOnLayer:self.statusShineLayer key:@"opencoreShimmer" amplitude:36.0 duration:2.2];
159
+ }
160
+
161
+ if ([self.statusGlowLayer animationForKey:@"opencoreGlowPulse"] == nil) {
162
+ CABasicAnimation *pulse = [CABasicAnimation animationWithKeyPath:@"shadowRadius"];
163
+ pulse.fromValue = @(8.0);
164
+ pulse.toValue = @(12.5);
165
+ pulse.duration = 1.8;
166
+ pulse.repeatCount = HUGE_VALF;
167
+ pulse.autoreverses = YES;
168
+ pulse.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
169
+ [self.statusGlowLayer addAnimation:pulse forKey:@"opencoreGlowPulse"];
170
+ }
171
+ }
172
+
173
+ - (void)configureStatusTracking {
174
+ NSStatusBarButton *button = self.statusItem.button;
175
+ if (button == nil) return;
176
+ if (self.statusTrackingArea != nil) {
177
+ [button removeTrackingArea:self.statusTrackingArea];
178
+ }
179
+ self.statusTrackingArea = [[NSTrackingArea alloc] initWithRect:button.bounds
180
+ options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect
181
+ owner:self
182
+ userInfo:nil];
183
+ [button addTrackingArea:self.statusTrackingArea];
184
+ }
185
+
186
+ - (void)mouseEntered:(NSEvent *)event {
187
+ (void)event;
188
+ self.hoverActive = YES;
189
+ if (self.lastState != nil) {
190
+ [self applyState:self.lastState];
191
+ }
192
+ }
193
+
194
+ - (void)mouseExited:(NSEvent *)event {
195
+ (void)event;
196
+ self.hoverActive = NO;
197
+ if (self.lastState != nil) {
198
+ [self applyState:self.lastState];
199
+ }
200
+ }
201
+
202
+ - (void)updateStatusPillForTitle:(NSString *)title active:(BOOL)active {
203
+ NSStatusBarButton *button = self.statusItem.button;
204
+ if (button == nil) return;
205
+
206
+ [self ensureStatusLayers];
207
+
208
+ NSDictionary *attributes = @{
209
+ NSFontAttributeName: [NSFont systemFontOfSize:13 weight:NSFontWeightSemibold]
210
+ };
211
+ CGFloat textWidth = ceil([title sizeWithAttributes:attributes].width);
212
+ CGFloat iconWidth = button.image != nil ? 18.0 : 0.0;
213
+ CGFloat totalWidth = textWidth + iconWidth + 22.0;
214
+ CGFloat width = MAX(122.0, totalWidth);
215
+ CGFloat height = 24.0;
216
+ CGFloat x = floor((NSWidth(button.bounds) - width) / 2.0);
217
+ CGFloat y = floor((NSHeight(button.bounds) - height) / 2.0) - 1.0;
218
+ CGRect frame = CGRectMake(x, y, width, height);
219
+
220
+ self.statusGlowLayer.frame = CGRectInset(frame, -2.0, -1.0);
221
+ self.statusGlowLayer.backgroundColor = [NSColor clearColor].CGColor;
222
+ self.statusGlowLayer.shadowColor = [[self pillEndColorForActive:active] colorWithAlphaComponent:(active ? 0.92 : 0.66)].CGColor;
223
+ self.statusGlowLayer.shadowRadius = active ? (self.hoverActive ? 18.0f : 12.0f) : (self.hoverActive ? 13.0f : 8.0f);
224
+ self.statusGlowLayer.shadowOpacity = active ? (self.hoverActive ? 0.68f : 0.42f) : (self.hoverActive ? 0.46f : 0.26f);
225
+
226
+ self.statusPillLayer.frame = frame;
227
+ self.statusPillLayer.colors = @[
228
+ (__bridge id)[[self pillStartColorForActive:active] blendedColorWithFraction:(self.hoverActive ? 0.16 : 0.0) ofColor:[NSColor whiteColor]].CGColor,
229
+ (__bridge id)[[self pillEndColorForActive:active] blendedColorWithFraction:(self.hoverActive ? 0.12 : 0.0) ofColor:[NSColor whiteColor]].CGColor
230
+ ];
231
+ self.statusPillLayer.borderColor = [[self.orangeBadgeBorderColor colorWithAlphaComponent:(active ? (self.hoverActive ? 1.0 : 0.85) : (self.hoverActive ? 0.86 : 0.60))] CGColor];
232
+
233
+ self.statusShineLayer.frame = CGRectMake(8.0, 2.0, width - 16.0, height * 0.48);
234
+ self.statusShineLayer.cornerRadius = 9.0;
235
+ self.statusShineLayer.opacity = active ? (self.hoverActive ? 1.0f : 0.95f) : (self.hoverActive ? 0.84f : 0.62f);
236
+ }
237
+
238
+ - (void)applyStatusButtonTitle:(NSString *)title active:(BOOL)active {
239
+ NSStatusBarButton *button = self.statusItem.button;
240
+ if (button == nil) return;
241
+
242
+ NSDictionary *attributes = @{
243
+ NSFontAttributeName: [NSFont systemFontOfSize:13 weight:NSFontWeightSemibold],
244
+ NSForegroundColorAttributeName: [NSColor colorWithCalibratedWhite:1.0 alpha:0.98],
245
+ NSBaselineOffsetAttributeName: @0.5
246
+ };
247
+ button.attributedTitle = [[NSAttributedString alloc] initWithString:title attributes:attributes];
248
+ button.title = title;
249
+ button.imagePosition = NSImageLeft;
250
+ button.imageScaling = NSImageScaleProportionallyDown;
251
+ button.contentTintColor = [NSColor colorWithCalibratedWhite:1.0 alpha:0.96];
252
+ button.layer.zPosition = 10.0;
253
+ if ([button respondsToSelector:@selector(setImageHugsTitle:)]) {
254
+ button.imageHugsTitle = YES;
255
+ }
256
+ [self updateStatusPillForTitle:title active:active];
257
+ }
258
+
259
+ - (NSTextField *)labelWithFrame:(NSRect)frame text:(NSString *)text font:(NSFont *)font color:(NSColor *)color alignment:(NSTextAlignment)alignment {
260
+ NSTextField *label = [[NSTextField alloc] initWithFrame:frame];
261
+ label.stringValue = text ?: @"";
262
+ label.editable = NO;
263
+ label.bordered = NO;
264
+ label.bezeled = NO;
265
+ label.drawsBackground = NO;
266
+ label.selectable = NO;
267
+ label.alignment = alignment;
268
+ label.font = font;
269
+ label.textColor = color;
270
+ return label;
271
+ }
272
+
273
+ - (NSView *)activityRowViewWithText:(NSString *)text width:(CGFloat)width {
274
+ NSView *container = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, width, 42)];
275
+
276
+ CGFloat bubbleWidth = width - 34;
277
+ NSView *bubble = [[NSView alloc] initWithFrame:NSMakeRect(floor((width - bubbleWidth) / 2.0), 5, bubbleWidth, 32)];
278
+ bubble.wantsLayer = YES;
279
+ bubble.layer.cornerRadius = 12.0;
280
+ CAGradientLayer *bubbleGradient = [CAGradientLayer layer];
281
+ bubbleGradient.frame = bubble.bounds;
282
+ bubbleGradient.cornerRadius = 12.0;
283
+ bubbleGradient.startPoint = CGPointMake(0.0, 0.5);
284
+ bubbleGradient.endPoint = CGPointMake(1.0, 0.5);
285
+ bubbleGradient.colors = @[
286
+ (__bridge id)[self.activityBubbleFillColor CGColor],
287
+ (__bridge id)[self.activityBubbleFillEndColor CGColor]
288
+ ];
289
+ [bubble.layer addSublayer:bubbleGradient];
290
+ bubble.layer.borderWidth = 1.0;
291
+ bubble.layer.borderColor = [self.activityBubbleBorderColor CGColor];
292
+ bubble.layer.shadowColor = [[self pillEndColorForActive:YES] colorWithAlphaComponent:([self isDarkMode] ? 0.22 : 0.18)].CGColor;
293
+ bubble.layer.shadowOpacity = 1.0f;
294
+ bubble.layer.shadowRadius = 6.0f;
295
+ bubble.layer.shadowOffset = CGSizeMake(0, -1);
296
+ [container addSubview:bubble];
297
+
298
+ CGFloat dotSize = 6.0;
299
+ CGFloat spacing = 8.0;
300
+ CGFloat contentHeight = 16.0;
301
+ NSDictionary *textAttributes = @{
302
+ NSFontAttributeName: [NSFont systemFontOfSize:11 weight:NSFontWeightMedium]
303
+ };
304
+ CGFloat textWidth = MIN(ceil([text sizeWithAttributes:textAttributes].width), NSWidth(bubble.bounds) - 42.0);
305
+ CGFloat groupWidth = dotSize + spacing + textWidth;
306
+ CGFloat groupStartX = NSMinX(bubble.frame) + floor((NSWidth(bubble.bounds) - groupWidth) / 2.0);
307
+ CGFloat groupStartY = NSMinY(bubble.frame) + floor((NSHeight(bubble.bounds) - contentHeight) / 2.0);
308
+
309
+ NSView *group = [[NSView alloc] initWithFrame:NSMakeRect(groupStartX, groupStartY, groupWidth, contentHeight)];
310
+ [container addSubview:group];
311
+
312
+ NSView *dot = [[NSView alloc] initWithFrame:NSMakeRect(0, floor((contentHeight - dotSize) / 2.0), dotSize, dotSize)];
313
+ dot.wantsLayer = YES;
314
+ dot.layer.cornerRadius = 3.0;
315
+ dot.layer.backgroundColor = [self pillEndColorForActive:YES].CGColor;
316
+ [group addSubview:dot];
317
+
318
+ NSTextField *label = [self labelWithFrame:NSMakeRect(NSMaxX(dot.frame) + spacing, floor((contentHeight - 16.0) / 2.0), textWidth, 16.0)
319
+ text:text
320
+ font:[NSFont systemFontOfSize:11 weight:NSFontWeightMedium]
321
+ color:self.activityBubbleTextColor
322
+ alignment:NSTextAlignmentLeft];
323
+ label.cell.controlSize = NSControlSizeSmall;
324
+ label.lineBreakMode = NSLineBreakByTruncatingTail;
325
+ label.cell.wraps = NO;
326
+ label.cell.scrollable = NO;
327
+ label.cell.usesSingleLineMode = YES;
328
+ [group addSubview:label];
329
+
330
+ return container;
331
+ }
332
+
333
+ - (void)applicationDidFinishLaunching:(NSNotification *)notification {
334
+ (void)notification;
335
+ NSString *statePath = NSProcessInfo.processInfo.environment[@"OPENCORE_INDICATOR_STATE_PATH"];
336
+ if (statePath.length == 0) {
337
+ [NSApp terminate:nil];
338
+ return;
339
+ }
340
+
341
+ self.statePath = statePath;
342
+ self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
343
+ self.menu = [[NSMenu alloc] initWithTitle:@"OpenCore"];
344
+ self.statusItem.menu = self.menu;
345
+
346
+ NSStatusBarButton *button = self.statusItem.button;
347
+ if (button != nil) {
348
+ if (@available(macOS 11.0, *)) {
349
+ NSImage *image = [NSImage imageWithSystemSymbolName:@"laptopcomputer" accessibilityDescription:@"OpenCore"];
350
+ if (image != nil) {
351
+ image.size = NSMakeSize(14, 14);
352
+ button.image = image;
353
+ button.imagePosition = NSImageLeft;
354
+ }
355
+ }
356
+ [self configureStatusTracking];
357
+ [self applyStatusButtonTitle:@"OpenCore Idle" active:NO];
358
+ }
359
+
360
+ [self refreshState:nil];
361
+ self.timer = [NSTimer scheduledTimerWithTimeInterval:0.35
362
+ target:self
363
+ selector:@selector(refreshState:)
364
+ userInfo:nil
365
+ repeats:YES];
366
+ }
367
+
368
+ - (void)applicationWillTerminate:(NSNotification *)notification {
369
+ (void)notification;
370
+ [self.timer invalidate];
371
+ self.timer = nil;
372
+ if (self.statusItem != nil) {
373
+ [[NSStatusBar systemStatusBar] removeStatusItem:self.statusItem];
374
+ self.statusItem = nil;
375
+ }
376
+ }
377
+
378
+ - (NSDictionary *)fallbackState {
379
+ return @{
380
+ @"running": @YES,
381
+ @"active": @NO,
382
+ @"message": @"OpenCore is idle",
383
+ @"events": @[]
384
+ };
385
+ }
386
+
387
+ - (NSDictionary *)loadState {
388
+ NSData *data = [NSData dataWithContentsOfFile:self.statePath];
389
+ if (data == nil) {
390
+ return @{ @"running": @NO, @"active": @NO, @"message": @"OpenCore has stopped", @"events": @[] };
391
+ }
392
+
393
+ NSError *error = nil;
394
+ id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
395
+ if (error != nil || ![json isKindOfClass:[NSDictionary class]]) {
396
+ return [self fallbackState];
397
+ }
398
+ return (NSDictionary *)json;
399
+ }
400
+
401
+ - (NSMenuItem *)disabledItemWithTitle:(NSString *)title {
402
+ NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title action:nil keyEquivalent:@""];
403
+ item.enabled = NO;
404
+ return item;
405
+ }
406
+
407
+ - (NSString *)truncate:(NSString *)value limit:(NSUInteger)limit {
408
+ NSString *text = [[value ?: @"" stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] copy];
409
+ if (text.length <= limit) {
410
+ return text;
411
+ }
412
+ if (limit == 0) {
413
+ return @"";
414
+ }
415
+ return [[text substringToIndex:(limit - 1)] stringByAppendingString:@"…"];
416
+ }
417
+
418
+ - (void)rebuildMenuForState:(NSDictionary *)state {
419
+ [self.menu removeAllItems];
420
+ if (@available(macOS 10.14, *)) {
421
+ self.menu.appearance = [NSAppearance appearanceNamed:([self isDarkMode] ? NSAppearanceNameDarkAqua : NSAppearanceNameAqua)];
422
+ }
423
+
424
+ BOOL active = [state[@"active"] boolValue];
425
+ NSString *title = active ? @"OpenCore Active" : @"OpenCore Idle";
426
+ NSString *message = [self truncate:(state[@"message"] ?: (active ? @"OpenCore is working" : @"OpenCore is idle")) limit:88];
427
+
428
+ NSMenuItem *header = [self disabledItemWithTitle:title];
429
+ header.attributedTitle = [[NSAttributedString alloc] initWithString:title attributes:@{
430
+ NSFontAttributeName: [NSFont systemFontOfSize:13 weight:NSFontWeightSemibold],
431
+ NSForegroundColorAttributeName: [self menuHeaderTextColor]
432
+ }];
433
+ [self.menu addItem:header];
434
+ [self.menu addItem:[NSMenuItem separatorItem]];
435
+ NSMenuItem *messageItem = [self disabledItemWithTitle:message.length ? message : @"OpenCore is idle"];
436
+ messageItem.attributedTitle = [[NSAttributedString alloc] initWithString:(message.length ? message : @"OpenCore is idle") attributes:@{
437
+ NSFontAttributeName: [NSFont systemFontOfSize:11 weight:NSFontWeightRegular],
438
+ NSForegroundColorAttributeName: [self menuBodyTextColor]
439
+ }];
440
+ [self.menu addItem:messageItem];
441
+
442
+ NSArray *events = [state[@"events"] isKindOfClass:[NSArray class]] ? state[@"events"] : @[];
443
+ if (events.count > 0) {
444
+ [self.menu addItem:[NSMenuItem separatorItem]];
445
+ NSMenuItem *feedHeader = [self disabledItemWithTitle:@"Live Activity"];
446
+ feedHeader.attributedTitle = [[NSAttributedString alloc] initWithString:@"Live Activity" attributes:@{
447
+ NSFontAttributeName: [NSFont systemFontOfSize:11 weight:NSFontWeightSemibold],
448
+ NSForegroundColorAttributeName: [self menuAccentTextColor]
449
+ }];
450
+ [self.menu addItem:feedHeader];
451
+ NSUInteger start = events.count > 8 ? events.count - 8 : 0;
452
+ for (NSUInteger i = start; i < events.count; i += 1) {
453
+ NSString *line = [events[i] isKindOfClass:[NSString class]] ? events[i] : @"";
454
+ NSMenuItem *rowItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
455
+ rowItem.enabled = NO;
456
+ rowItem.view = [self activityRowViewWithText:[self truncate:line limit:96] width:300];
457
+ [self.menu addItem:rowItem];
458
+ }
459
+ }
460
+ }
461
+
462
+ - (void)applyState:(NSDictionary *)state {
463
+ BOOL active = [state[@"active"] boolValue];
464
+ NSString *title = active ? @"OpenCore Active" : @"OpenCore Idle";
465
+ [self applyStatusButtonTitle:title active:active];
466
+ [self rebuildMenuForState:state];
467
+ }
468
+
469
+ - (void)refreshState:(NSTimer *)timer {
470
+ (void)timer;
471
+ NSDictionary *state = [self loadState];
472
+ if ([state[@"running"] boolValue] == NO) {
473
+ [NSApp terminate:nil];
474
+ return;
475
+ }
476
+ self.lastState = state;
477
+ [self applyState:state];
478
+ }
479
+
480
+ @end
481
+
482
+ int main(int argc, const char *argv[]) {
483
+ (void)argc;
484
+ (void)argv;
485
+ @autoreleasepool {
486
+ NSApplication *app = [NSApplication sharedApplication];
487
+ OpenCoreIndicatorAppDelegate *delegate = [OpenCoreIndicatorAppDelegate new];
488
+ app.delegate = delegate;
489
+ [app setActivationPolicy:NSApplicationActivationPolicyAccessory];
490
+ [app run];
491
+ }
492
+ return 0;
493
+ }
@@ -9,12 +9,17 @@ export const SKILL_CATALOG = [
9
9
  triggers: ["create skill", "make a skill", "new skill", "update skill", "improve skill"],
10
10
  builtin: true,
11
11
  },
12
- markdown: `# OpenCore Skill: Skill Creator
12
+ markdown: `---
13
+ name: skill-creator
14
+ description: Create or update OpenCore skills with focused triggers, reusable instructions, and local config.
15
+ ---
16
+
17
+ # Skill Creator
13
18
 
14
19
  ## Purpose
15
20
  Create, repair, or improve reusable OpenCore skills under ~/.opencore/skills.
16
21
 
17
- ## When To Use
22
+ ## Use This Skill When
18
23
  - User asks for a new OpenCore skill.
19
24
  - User asks to improve an installed skill.
20
25
  - Manager notices repeated work that should become a reusable skill.
@@ -43,12 +48,17 @@ Create, repair, or improve reusable OpenCore skills under ~/.opencore/skills.
43
48
  triggers: ["run for days", "run for weeks", "continuous task", "long-running", "keep working", "autonomous for days"],
44
49
  builtin: true,
45
50
  },
46
- markdown: `# OpenCore Skill: Continuous Operations
51
+ markdown: `---
52
+ name: continuous-operations
53
+ description: Run multi-day or multi-week tasks safely by turning them into heartbeat steps, checkpoints, memory updates, and recoverable scheduled work.
54
+ ---
55
+
56
+ # Continuous Operations
47
57
 
48
58
  ## Purpose
49
59
  Handle long-running work that lasts for days or weeks without losing state, repeating completed work, or forgetting learned facts.
50
60
 
51
- ## When To Use
61
+ ## Use This Skill When
52
62
  - User asks OpenCore to keep working over a long period.
53
63
  - Task needs repeated checkpoints, follow-ups, monitoring, or staged execution.
54
64
  - Task must survive gaps between CLI sessions or scheduled runs.
@@ -88,12 +98,17 @@ Handle long-running work that lasts for days or weeks without losing state, repe
88
98
  triggers: ["build a dashboard", "build a tool", "add integration", "create UI", "build for yourself", "improve opencore"],
89
99
  builtin: true,
90
100
  },
91
- markdown: `# OpenCore Skill: Builder
101
+ markdown: `---
102
+ name: builder
103
+ description: Design and build new tools, dashboards, automations, integrations, and operator surfaces for OpenCore itself.
104
+ ---
105
+
106
+ # Builder
92
107
 
93
108
  ## Purpose
94
109
  Build or improve OpenCore-owned tools such as dashboards, helpers, integrations, automations, or local operator utilities.
95
110
 
96
- ## When To Use
111
+ ## Use This Skill When
97
112
  - User asks OpenCore to create a dashboard, agent utility, local service, or integration.
98
113
  - OpenCore needs a durable internal tool to reduce repeated manual work.
99
114
  - Existing OpenCore tooling is too weak for the requested workflow.
@@ -123,7 +138,12 @@ Build or improve OpenCore-owned tools such as dashboards, helpers, integrations,
123
138
  triggers: ["log in", "login", "sign up", "signup", "create account", "credentials", "password", "verification code"],
124
139
  builtin: true,
125
140
  },
126
- markdown: `# OpenCore Skill: Credential Operator
141
+ markdown: `---
142
+ name: credential-operator
143
+ description: Manage saved website credentials, default email settings, verification flows, and safe account creation/login behavior.
144
+ ---
145
+
146
+ # Credential Operator
127
147
 
128
148
  ## Purpose
129
149
  Use saved credentials safely, create new website accounts with the user's email when appropriate, handle email verification only when enabled, and save resulting credentials locally for reuse.
@@ -133,6 +153,11 @@ Use saved credentials safely, create new website accounts with the user's email
133
153
  - A default email and default email provider may also be available.
134
154
  - All credentials are local-only and should be reused instead of re-asking when already available.
135
155
 
156
+ ## Use This Skill When
157
+ - A task involves sign-in, sign-up, credentials, passwords, or verification codes.
158
+ - A site account needs to be created with the user's saved email context.
159
+ - OpenCore needs to reuse or store local credentials safely.
160
+
136
161
  ## Login Workflow
137
162
  1. Identify the website and determine whether a saved account already exists.
138
163
  2. Inspect the page to see which fields are required: email, username, password, one-time code, or extra profile fields.
@@ -181,11 +206,21 @@ CREDENTIAL_SAVE {"website":"example.com","email":"user@example.com","password":"
181
206
  category: "research",
182
207
  triggers: ["find", "research", "latest", "compare", "look up"],
183
208
  },
184
- markdown: `# OpenCore Skill: Web Research
209
+ markdown: `---
210
+ name: web-research
211
+ description: Plan targeted searches, verify sources, and return concise evidence-backed summaries.
212
+ ---
213
+
214
+ # Web Research
185
215
 
186
216
  ## Purpose
187
217
  Run structured web research tasks quickly and reliably.
188
218
 
219
+ ## Use This Skill When
220
+ - The task needs current web information.
221
+ - The task needs source-backed comparison or verification.
222
+ - The task needs concise findings with links and dates.
223
+
189
224
  ## Workflow
190
225
  1. Clarify the exact research goal and required output format.
191
226
  2. Check multiple reputable sources, prioritizing primary sources where possible.
@@ -207,11 +242,21 @@ Run structured web research tasks quickly and reliably.
207
242
  category: "productivity",
208
243
  triggers: ["schedule", "calendar", "plan", "reminder", "timeline"],
209
244
  },
210
- markdown: `# OpenCore Skill: Calendar Planner
245
+ markdown: `---
246
+ name: calendar-planner
247
+ description: Create and maintain realistic plans, reminders, checkpoints, and schedule-aware task breakdowns.
248
+ ---
249
+
250
+ # Calendar Planner
211
251
 
212
252
  ## Purpose
213
253
  Convert goals into realistic scheduled actions with explicit checkpoints.
214
254
 
255
+ ## Use This Skill When
256
+ - The task involves schedules, reminders, calendars, plans, or timelines.
257
+ - A large request needs realistic sequencing and timing.
258
+ - Future work should be turned into explicit checkpoints or scheduled actions.
259
+
215
260
  ## Workflow
216
261
  1. Extract deadlines, timing constraints, and fixed commitments.
217
262
  2. Break work into executable blocks with durations and dependencies.
@@ -233,11 +278,21 @@ Convert goals into realistic scheduled actions with explicit checkpoints.
233
278
  category: "filesystem",
234
279
  triggers: ["organize files", "cleanup", "rename", "folder structure", "sort downloads"],
235
280
  },
236
- markdown: `# OpenCore Skill: File Organizer
281
+ markdown: `---
282
+ name: file-organizer
283
+ description: Organize files and folders, normalize naming, and create clean local structures without unsafe churn.
284
+ ---
285
+
286
+ # File Organizer
237
287
 
238
288
  ## Purpose
239
289
  Create clean, predictable file layouts and naming standards.
240
290
 
291
+ ## Use This Skill When
292
+ - The user asks to organize files or folders.
293
+ - Downloads, workspaces, or local directories need cleanup.
294
+ - A naming convention or folder structure needs to be applied safely.
295
+
241
296
  ## Workflow
242
297
  1. Inspect the current folder structure and identify patterns.
243
298
  2. Define the target structure and naming convention.
@@ -259,11 +314,21 @@ Create clean, predictable file layouts and naming standards.
259
314
  category: "engineering",
260
315
  triggers: ["bug", "feature", "refactor", "test", "build", "fix code"],
261
316
  },
262
- markdown: `# OpenCore Skill: Code Assistant
317
+ markdown: `---
318
+ name: code-assistant
319
+ description: Handle coding tasks with implementation, validation, and concise change summaries.
320
+ ---
321
+
322
+ # Code Assistant
263
323
 
264
324
  ## Purpose
265
325
  Deliver production-quality code changes with minimal regressions.
266
326
 
327
+ ## Use This Skill When
328
+ - The task is a bug fix, feature, refactor, test, or build issue.
329
+ - The user expects code changes, validation, and a concise summary.
330
+ - OpenCore needs to modify local code rather than only describe a solution.
331
+
267
332
  ## Workflow
268
333
  1. Reproduce or isolate the issue.
269
334
  2. Edit the smallest correct surface area.
@@ -285,11 +350,21 @@ Deliver production-quality code changes with minimal regressions.
285
350
  category: "communication",
286
351
  triggers: ["email", "reply", "message", "draft", "write a note"],
287
352
  },
288
- markdown: `# OpenCore Skill: Communication Drafts
353
+ markdown: `---
354
+ name: communication-drafts
355
+ description: Draft polished emails and messages with clear tone, intent, and next-step asks.
356
+ ---
357
+
358
+ # Communication Drafts
289
359
 
290
360
  ## Purpose
291
361
  Write concise, audience-appropriate communication drafts.
292
362
 
363
+ ## Use This Skill When
364
+ - The task is to draft an email, message, note, or reply.
365
+ - Tone, clarity, and next steps matter.
366
+ - The user needs a polished communication artifact, not raw bullets.
367
+
293
368
  ## Workflow
294
369
  1. Identify the audience, objective, and preferred tone.
295
370
  2. Draft the message with a clear opening, body, and call to action.
@@ -0,0 +1,7 @@
1
+ # OpenCore Computer Profile
2
+
3
+ ## Machine Snapshot
4
+ - Pending first machine scan.
5
+
6
+ ## Learned Facts
7
+ - None yet.