@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.
- package/opencore dashboard/app.js +74 -31
- package/opencore dashboard/styles.css +71 -32
- package/package.json +2 -2
- package/scripts/postinstall.mjs +7 -0
- package/src/credential-store.mjs +11 -0
- package/src/dashboard-server.ts +48 -3
- package/src/index.ts +382 -28
- package/src/opencore-indicator.m +493 -0
- package/src/skill-catalog.mjs +87 -12
- package/templates/default-computer-profile.md +7 -0
- package/templates/default-guidelines.md +4 -0
- package/templates/default-instructions.md +2 -0
- package/src/opencore-indicator.js +0 -140
|
@@ -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
|
+
}
|
package/src/skill-catalog.mjs
CHANGED
|
@@ -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:
|
|
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
|
-
##
|
|
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:
|
|
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
|
-
##
|
|
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:
|
|
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
|
-
##
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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.
|