os-context 0.1.0 → 0.1.1
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/README.md +21 -6
- package/dist/index.js +423 -27
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -15,10 +15,24 @@ OS Context gives AI agents a quick, local snapshot of your machine (app, window,
|
|
|
15
15
|
|
|
16
16
|
## Install
|
|
17
17
|
|
|
18
|
+
**Run without installing (npx):**
|
|
18
19
|
```bash
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
npx os-context --pretty
|
|
21
|
+
```
|
|
22
|
+
Use the package name `os-context` with npx; the command is then `context` inside the package.
|
|
23
|
+
|
|
24
|
+
**Install globally (get `context` in your PATH):**
|
|
25
|
+
```bash
|
|
26
|
+
npm i -g os-context
|
|
27
|
+
context --pretty
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**From source:**
|
|
31
|
+
```bash
|
|
32
|
+
git clone https://github.com/treadiehq/os-context.git && cd os-context
|
|
33
|
+
npm install && npm run build
|
|
34
|
+
./node_modules/.bin/context --pretty
|
|
35
|
+
# or: npm link then context --pretty
|
|
22
36
|
```
|
|
23
37
|
|
|
24
38
|
## Usage
|
|
@@ -26,19 +40,20 @@ npm run build
|
|
|
26
40
|
**Default (safe core context only):**
|
|
27
41
|
|
|
28
42
|
```bash
|
|
29
|
-
context
|
|
43
|
+
npx os-context
|
|
44
|
+
# or, after global install: context
|
|
30
45
|
```
|
|
31
46
|
|
|
32
47
|
**Pretty-print with frontmost window and clipboard, redact sensitive fields:**
|
|
33
48
|
|
|
34
49
|
```bash
|
|
35
|
-
context --pretty --frontmost-window --clipboard --redact
|
|
50
|
+
npx os-context --pretty --frontmost-window --clipboard --redact
|
|
36
51
|
```
|
|
37
52
|
|
|
38
53
|
**All optional features and debug timings:**
|
|
39
54
|
|
|
40
55
|
```bash
|
|
41
|
-
context --pretty --clipboard --frontmost-window --apps \
|
|
56
|
+
npx os-context --pretty --clipboard --frontmost-window --apps \
|
|
42
57
|
--battery --network --calendar --reminders \
|
|
43
58
|
--redact --debug
|
|
44
59
|
```
|
package/dist/index.js
CHANGED
|
@@ -45,11 +45,17 @@ var NetworkSchema = z.object({
|
|
|
45
45
|
var CalendarEventSchema = z.object({
|
|
46
46
|
start: z.string(),
|
|
47
47
|
end: z.string(),
|
|
48
|
-
title: z.string(),
|
|
49
|
-
|
|
48
|
+
title: z.string().optional(),
|
|
49
|
+
title_sha256: z.string().optional(),
|
|
50
|
+
title_length: z.number().optional(),
|
|
51
|
+
location: z.string().optional(),
|
|
52
|
+
location_sha256: z.string().optional(),
|
|
53
|
+
location_length: z.number().optional()
|
|
50
54
|
});
|
|
51
55
|
var ReminderSchema = z.object({
|
|
52
|
-
title: z.string(),
|
|
56
|
+
title: z.string().optional(),
|
|
57
|
+
title_sha256: z.string().optional(),
|
|
58
|
+
title_length: z.number().optional(),
|
|
53
59
|
due: z.string().optional(),
|
|
54
60
|
list: z.string().optional()
|
|
55
61
|
});
|
|
@@ -422,8 +428,55 @@ async function collectFrontmost(options) {
|
|
|
422
428
|
|
|
423
429
|
// src/modules/apps.ts
|
|
424
430
|
var MAX_APPS = 50;
|
|
425
|
-
|
|
426
|
-
|
|
431
|
+
var APPS_SCRIPT = `
|
|
432
|
+
tell application "System Events"
|
|
433
|
+
set out to ""
|
|
434
|
+
set procs to (every process whose background only is false)
|
|
435
|
+
set n to (count of procs)
|
|
436
|
+
if n > 50 then set n to 50
|
|
437
|
+
repeat with i from 1 to n
|
|
438
|
+
set p to item i of procs
|
|
439
|
+
set pname to name of p
|
|
440
|
+
set pid to unix id of p
|
|
441
|
+
set bid to ""
|
|
442
|
+
try
|
|
443
|
+
set bid to bundle identifier of p
|
|
444
|
+
end try
|
|
445
|
+
if bid is missing value then set bid to ""
|
|
446
|
+
set out to out & pname & tab & (pid as string) & tab & bid & return
|
|
447
|
+
end repeat
|
|
448
|
+
return out
|
|
449
|
+
end tell
|
|
450
|
+
`;
|
|
451
|
+
async function collectAppsDarwin(timeoutMs) {
|
|
452
|
+
const t = timer();
|
|
453
|
+
try {
|
|
454
|
+
const result = await run("osascript", ["-e", APPS_SCRIPT], { timeoutMs });
|
|
455
|
+
if (result.timedOut || !result.ok) {
|
|
456
|
+
return {
|
|
457
|
+
data: [],
|
|
458
|
+
error: result.timedOut ? { module: "apps", message: "Timeout", code: "timeout" } : { module: "apps", message: result.stderr.trim() || "Failed to list apps", code: "error" },
|
|
459
|
+
timingMs: t.elapsed()
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
const normalized = result.stdout.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
463
|
+
const lines = normalized.trim().split("\n").filter(Boolean);
|
|
464
|
+
const apps = lines.map((line) => {
|
|
465
|
+
const parts = line.split(" ");
|
|
466
|
+
const name = (parts[0] ?? "").replace(/\r/g, "").trim() || "unknown";
|
|
467
|
+
const pid = parseInt((parts[1] ?? "0").replace(/\r/g, ""), 10);
|
|
468
|
+
const bundle_id = (parts[2] ?? "").replace(/\r/g, "").trim() || name;
|
|
469
|
+
return { name, bundle_id, pid: Number.isNaN(pid) ? 0 : pid };
|
|
470
|
+
}).filter((a) => a.pid > 0);
|
|
471
|
+
return { data: apps, timingMs: t.elapsed() };
|
|
472
|
+
} catch (err) {
|
|
473
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
474
|
+
return {
|
|
475
|
+
data: [],
|
|
476
|
+
error: { module: "apps", message, code: "error" },
|
|
477
|
+
timingMs: t.elapsed()
|
|
478
|
+
};
|
|
479
|
+
}
|
|
427
480
|
}
|
|
428
481
|
async function collectAppsLinux(timeoutMs) {
|
|
429
482
|
const t = timer();
|
|
@@ -461,11 +514,35 @@ async function collectApps(timeoutMs) {
|
|
|
461
514
|
}
|
|
462
515
|
|
|
463
516
|
// src/modules/clipboard.ts
|
|
464
|
-
async function collectClipboardDarwin(
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
517
|
+
async function collectClipboardDarwin(options) {
|
|
518
|
+
const t = timer();
|
|
519
|
+
const timeoutMs = options.timeoutMs;
|
|
520
|
+
const redact = options.redact;
|
|
521
|
+
try {
|
|
522
|
+
const result = await run("pbpaste", [], { timeoutMs });
|
|
523
|
+
const text = result.ok ? result.stdout ?? "" : "";
|
|
524
|
+
const types = text.length > 0 ? ["public.utf8-plain-text"] : [];
|
|
525
|
+
const data = {
|
|
526
|
+
available: result.ok,
|
|
527
|
+
types
|
|
528
|
+
};
|
|
529
|
+
if (text.length > 0) {
|
|
530
|
+
if (redact) {
|
|
531
|
+
const r = redactString(text);
|
|
532
|
+
data.text_sha256 = r.sha256;
|
|
533
|
+
data.text_length = r.length;
|
|
534
|
+
} else {
|
|
535
|
+
data.text = text;
|
|
536
|
+
data.text_length = text.length;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return { data, timingMs: t.elapsed() };
|
|
540
|
+
} catch {
|
|
541
|
+
return {
|
|
542
|
+
data: { available: false, types: [] },
|
|
543
|
+
timingMs: t.elapsed()
|
|
544
|
+
};
|
|
545
|
+
}
|
|
469
546
|
}
|
|
470
547
|
async function collectClipboardLinux(options) {
|
|
471
548
|
const t = timer();
|
|
@@ -518,11 +595,38 @@ async function collectClipboard(options) {
|
|
|
518
595
|
|
|
519
596
|
// src/modules/battery.ts
|
|
520
597
|
import { readFile } from "fs/promises";
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
598
|
+
function parsePmsetBatt(stdout) {
|
|
599
|
+
const line = stdout.split("\n").find((l) => l.includes("%") || l.includes("InternalBattery"));
|
|
600
|
+
const pctMatch = stdout.match(/(\d+)%/);
|
|
601
|
+
const percentage = pctMatch ? Math.min(1, Math.max(0, parseInt(pctMatch[1], 10) / 100)) : 0;
|
|
602
|
+
const lower = stdout.toLowerCase();
|
|
603
|
+
const is_charging = lower.includes("charging") || lower.includes("charged");
|
|
604
|
+
let power_source = "unknown";
|
|
605
|
+
if (lower.includes("ac power") || lower.includes("charging") || lower.includes("charged")) {
|
|
606
|
+
power_source = "ac";
|
|
607
|
+
} else if (lower.includes("battery power") || lower.includes("discharging")) {
|
|
608
|
+
power_source = "battery";
|
|
609
|
+
}
|
|
610
|
+
return { percentage, is_charging, power_source };
|
|
611
|
+
}
|
|
612
|
+
async function collectBatteryDarwin(timeoutMs) {
|
|
613
|
+
const t = timer();
|
|
614
|
+
try {
|
|
615
|
+
const result = await run("pmset", ["-g", "batt"], { timeoutMs });
|
|
616
|
+
if (result.timedOut || !result.ok) {
|
|
617
|
+
return {
|
|
618
|
+
data: { percentage: 0, is_charging: false, power_source: "unknown" },
|
|
619
|
+
timingMs: t.elapsed()
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
const data = parsePmsetBatt(result.stdout);
|
|
623
|
+
return { data, timingMs: t.elapsed() };
|
|
624
|
+
} catch {
|
|
625
|
+
return {
|
|
626
|
+
data: { percentage: 0, is_charging: false, power_source: "unknown" },
|
|
627
|
+
timingMs: t.elapsed()
|
|
628
|
+
};
|
|
629
|
+
}
|
|
526
630
|
}
|
|
527
631
|
async function collectBatteryLinux(_timeoutMs) {
|
|
528
632
|
const t = timer();
|
|
@@ -556,12 +660,33 @@ async function collectBattery(timeoutMs) {
|
|
|
556
660
|
}
|
|
557
661
|
|
|
558
662
|
// src/modules/network.ts
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
663
|
+
async function collectNetworkDarwin(timeoutMs) {
|
|
664
|
+
const t = timer();
|
|
665
|
+
try {
|
|
666
|
+
const routeResult = await run("route", ["get", "default"], { timeoutMs });
|
|
667
|
+
const ifMatch = routeResult.ok ? routeResult.stdout.match(/interface:\s*(\S+)/) : null;
|
|
668
|
+
const primary_interface = ifMatch ? ifMatch[1].trim() : "";
|
|
669
|
+
let ssid;
|
|
670
|
+
if (primary_interface) {
|
|
671
|
+
const ssidResult = await run("networksetup", ["-getairportnetwork", primary_interface], { timeoutMs });
|
|
672
|
+
const ssidMatch = ssidResult.ok ? ssidResult.stdout.match(/Current Wi-Fi Network:\s*(.+)/) : null;
|
|
673
|
+
if (ssidMatch && ssidMatch[1].trim()) ssid = ssidMatch[1].trim();
|
|
674
|
+
}
|
|
675
|
+
let has_internet = false;
|
|
676
|
+
if (primary_interface) {
|
|
677
|
+
const ifconfigResult = await run("ifconfig", [primary_interface], { timeoutMs });
|
|
678
|
+
has_internet = ifconfigResult.ok && /inet\s+\d+\.\d+\.\d+\.\d+/.test(ifconfigResult.stdout);
|
|
679
|
+
}
|
|
680
|
+
const data = { primary_interface, ssid, has_internet };
|
|
681
|
+
return { data, timingMs: t.elapsed() };
|
|
682
|
+
} catch (err) {
|
|
683
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
684
|
+
return {
|
|
685
|
+
data: { primary_interface: "", has_internet: false },
|
|
686
|
+
error: { module: "network", message, code: "error" },
|
|
687
|
+
timingMs: t.elapsed()
|
|
688
|
+
};
|
|
689
|
+
}
|
|
565
690
|
}
|
|
566
691
|
async function collectNetworkLinux(timeoutMs) {
|
|
567
692
|
const t = timer();
|
|
@@ -578,12 +703,12 @@ async function collectNetworkLinux(timeoutMs) {
|
|
|
578
703
|
let has_internet = false;
|
|
579
704
|
if (primary_interface) {
|
|
580
705
|
try {
|
|
706
|
+
const { readFile: readFile2 } = await import("fs/promises");
|
|
581
707
|
const operstate = await readFile2(
|
|
582
708
|
`/sys/class/net/${primary_interface}/operstate`,
|
|
583
709
|
"utf8"
|
|
584
710
|
).then((s) => s.trim()).catch(() => "");
|
|
585
|
-
|
|
586
|
-
if (hasCarrier) {
|
|
711
|
+
if (operstate === "up") {
|
|
587
712
|
const addrResult = await run("ip", ["-4", "addr", "show", primary_interface], { timeoutMs });
|
|
588
713
|
has_internet = addrResult.ok && /inet\s+\d+\.\d+\.\d+\.\d+/.test(addrResult.stdout);
|
|
589
714
|
}
|
|
@@ -612,19 +737,290 @@ async function collectNetwork(timeoutMs) {
|
|
|
612
737
|
}
|
|
613
738
|
|
|
614
739
|
// src/modules/calendar.ts
|
|
615
|
-
|
|
740
|
+
var EVENT_SEP = "";
|
|
741
|
+
var FIELD_SEP = "";
|
|
742
|
+
var CALENDAR_DENIED_PATTERNS = [
|
|
743
|
+
/calendar/i,
|
|
744
|
+
/permission/i,
|
|
745
|
+
/privacy/i,
|
|
746
|
+
/not authorised/i,
|
|
747
|
+
/access denied/i
|
|
748
|
+
];
|
|
749
|
+
function isCalendarDenied(stderr) {
|
|
750
|
+
return CALENDAR_DENIED_PATTERNS.some((p) => p.test(stderr));
|
|
751
|
+
}
|
|
752
|
+
var CALENDAR_SCRIPT = `
|
|
753
|
+
tell application "Calendar"
|
|
754
|
+
set startDate to current date
|
|
755
|
+
set endDate to startDate + (24 * 60 * 60)
|
|
756
|
+
set eventList to {}
|
|
757
|
+
repeat with aCal in calendars
|
|
758
|
+
try
|
|
759
|
+
set theseEvents to (every event of aCal whose start date >= startDate and start date <= endDate)
|
|
760
|
+
repeat with e in theseEvents
|
|
761
|
+
copy e to end of eventList
|
|
762
|
+
end repeat
|
|
763
|
+
end try
|
|
764
|
+
end repeat
|
|
765
|
+
set output to ""
|
|
766
|
+
set d31 to character id 31
|
|
767
|
+
set d30 to character id 30
|
|
768
|
+
set total to (count of eventList)
|
|
769
|
+
set maxToOutput to 20
|
|
770
|
+
if total > maxToOutput then set total to maxToOutput
|
|
771
|
+
repeat with i from 1 to total
|
|
772
|
+
set e to item i of eventList
|
|
773
|
+
set startStr to (start date of e as string)
|
|
774
|
+
set endStr to (end date of e as string)
|
|
775
|
+
set sumStr to (summary of e as string)
|
|
776
|
+
set locStr to ""
|
|
777
|
+
try
|
|
778
|
+
set locStr to (location of e as string)
|
|
779
|
+
end try
|
|
780
|
+
set sumSafe to ""
|
|
781
|
+
repeat with j from 1 to (length of sumStr)
|
|
782
|
+
set c to character j of sumStr
|
|
783
|
+
if c is d30 or c is d31 or c is return then
|
|
784
|
+
set sumSafe to sumSafe & " "
|
|
785
|
+
else
|
|
786
|
+
set sumSafe to sumSafe & c
|
|
787
|
+
end if
|
|
788
|
+
end repeat
|
|
789
|
+
set locSafe to ""
|
|
790
|
+
repeat with j from 1 to (length of locStr)
|
|
791
|
+
set c to character j of locStr
|
|
792
|
+
if c is d30 or c is d31 or c is return then
|
|
793
|
+
set locSafe to locSafe & " "
|
|
794
|
+
else
|
|
795
|
+
set locSafe to locSafe & c
|
|
796
|
+
end if
|
|
797
|
+
end repeat
|
|
798
|
+
set output to output & startStr & d31 & endStr & d31 & sumSafe & d31 & locSafe & d30
|
|
799
|
+
end repeat
|
|
800
|
+
return output
|
|
801
|
+
end tell
|
|
802
|
+
`;
|
|
803
|
+
function parseCalendarOutput(raw) {
|
|
804
|
+
const events = [];
|
|
805
|
+
const blocks = raw.split(EVENT_SEP).filter((b) => b.trim().length > 0);
|
|
806
|
+
for (const block of blocks) {
|
|
807
|
+
const parts = block.split(FIELD_SEP);
|
|
808
|
+
if (parts.length >= 4) {
|
|
809
|
+
events.push({
|
|
810
|
+
start: parts[0].trim(),
|
|
811
|
+
end: parts[1].trim(),
|
|
812
|
+
title: parts[2].trim(),
|
|
813
|
+
location: parts[3].trim() || void 0
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
events.sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime());
|
|
818
|
+
return events.slice(0, 3);
|
|
819
|
+
}
|
|
820
|
+
async function collectCalendar(options) {
|
|
616
821
|
if (getPlatform() === "linux") {
|
|
617
822
|
return { data: [], timingMs: 0 };
|
|
618
823
|
}
|
|
619
|
-
|
|
824
|
+
const t = timer();
|
|
825
|
+
const timeoutMs = options.timeoutMs;
|
|
826
|
+
const redact = options.redact;
|
|
827
|
+
const warnings = [];
|
|
828
|
+
try {
|
|
829
|
+
const result = await run("osascript", ["-e", CALENDAR_SCRIPT], { timeoutMs });
|
|
830
|
+
if (result.timedOut) {
|
|
831
|
+
return {
|
|
832
|
+
data: [],
|
|
833
|
+
warnings: [...warnings, "calendar: timeout"],
|
|
834
|
+
error: { module: "calendar", message: "Timeout", code: "timeout" },
|
|
835
|
+
timingMs: t.elapsed()
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
if (!result.ok) {
|
|
839
|
+
if (isCalendarDenied(result.stderr)) {
|
|
840
|
+
return {
|
|
841
|
+
data: [],
|
|
842
|
+
permission: "denied",
|
|
843
|
+
error: {
|
|
844
|
+
module: "calendar",
|
|
845
|
+
message: "Calendar permission required",
|
|
846
|
+
code: "permission_denied"
|
|
847
|
+
},
|
|
848
|
+
timingMs: t.elapsed()
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
return {
|
|
852
|
+
data: [],
|
|
853
|
+
error: {
|
|
854
|
+
module: "calendar",
|
|
855
|
+
message: result.stderr.trim() || "Failed to get calendar events",
|
|
856
|
+
code: "error"
|
|
857
|
+
},
|
|
858
|
+
timingMs: t.elapsed()
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
const raw = result.stdout?.trim() ?? "";
|
|
862
|
+
let events = raw ? parseCalendarOutput(raw) : [];
|
|
863
|
+
if (redact && events.length > 0) {
|
|
864
|
+
events = events.map((ev) => {
|
|
865
|
+
const titleRedacted = redactString(ev.title);
|
|
866
|
+
const out = {
|
|
867
|
+
start: ev.start,
|
|
868
|
+
end: ev.end,
|
|
869
|
+
title_sha256: titleRedacted.sha256,
|
|
870
|
+
title_length: titleRedacted.length
|
|
871
|
+
};
|
|
872
|
+
if (ev.location != null && ev.location !== "") {
|
|
873
|
+
const locRedacted = redactString(ev.location);
|
|
874
|
+
out.location_sha256 = locRedacted.sha256;
|
|
875
|
+
out.location_length = locRedacted.length;
|
|
876
|
+
}
|
|
877
|
+
return out;
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
return {
|
|
881
|
+
data: events,
|
|
882
|
+
permission: "granted",
|
|
883
|
+
timingMs: t.elapsed()
|
|
884
|
+
};
|
|
885
|
+
} catch (err) {
|
|
886
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
887
|
+
return {
|
|
888
|
+
data: [],
|
|
889
|
+
error: { module: "calendar", message, code: "error" },
|
|
890
|
+
timingMs: t.elapsed()
|
|
891
|
+
};
|
|
892
|
+
}
|
|
620
893
|
}
|
|
621
894
|
|
|
622
895
|
// src/modules/reminders.ts
|
|
623
|
-
|
|
896
|
+
var REMINDER_SEP = "";
|
|
897
|
+
var FIELD_SEP2 = "";
|
|
898
|
+
var REMINDERS_DENIED_PATTERNS = [
|
|
899
|
+
/reminders/i,
|
|
900
|
+
/permission/i,
|
|
901
|
+
/privacy/i,
|
|
902
|
+
/not authorised/i,
|
|
903
|
+
/access denied/i
|
|
904
|
+
];
|
|
905
|
+
function isRemindersDenied(stderr) {
|
|
906
|
+
return REMINDERS_DENIED_PATTERNS.some((p) => p.test(stderr));
|
|
907
|
+
}
|
|
908
|
+
var REMINDERS_SCRIPT = `
|
|
909
|
+
tell application "Reminders"
|
|
910
|
+
set startDate to current date
|
|
911
|
+
set endDate to startDate + (7 * 24 * 60 * 60)
|
|
912
|
+
set out to ""
|
|
913
|
+
set d31 to character id 31
|
|
914
|
+
set d30 to character id 30
|
|
915
|
+
set count to 0
|
|
916
|
+
repeat with aList in lists
|
|
917
|
+
try
|
|
918
|
+
set theReminders to (every reminder of aList whose completed is false and (due date is not missing value) and (due date >= startDate) and (due date <= endDate))
|
|
919
|
+
repeat with r in theReminders
|
|
920
|
+
if count >= 10 then exit repeat
|
|
921
|
+
set titleStr to (name of r as string)
|
|
922
|
+
set dueStr to ""
|
|
923
|
+
try
|
|
924
|
+
set dueStr to (due date of r as string)
|
|
925
|
+
end try
|
|
926
|
+
set listStr to (name of aList as string)
|
|
927
|
+
set titleSafe to ""
|
|
928
|
+
repeat with j from 1 to (length of titleStr)
|
|
929
|
+
set c to character j of titleStr
|
|
930
|
+
if c is d30 or c is d31 or c is return then
|
|
931
|
+
set titleSafe to titleSafe & " "
|
|
932
|
+
else
|
|
933
|
+
set titleSafe to titleSafe & c
|
|
934
|
+
end if
|
|
935
|
+
end repeat
|
|
936
|
+
set out to out & titleSafe & d31 & dueStr & d31 & listStr & d30
|
|
937
|
+
set count to count + 1
|
|
938
|
+
end repeat
|
|
939
|
+
end try
|
|
940
|
+
end repeat
|
|
941
|
+
return out
|
|
942
|
+
end tell
|
|
943
|
+
`;
|
|
944
|
+
function parseRemindersOutput(raw) {
|
|
945
|
+
const reminders = [];
|
|
946
|
+
const blocks = raw.split(REMINDER_SEP).filter((b) => b.trim().length > 0);
|
|
947
|
+
for (const block of blocks) {
|
|
948
|
+
const parts = block.split(FIELD_SEP2);
|
|
949
|
+
if (parts.length >= 3) {
|
|
950
|
+
reminders.push({
|
|
951
|
+
title: parts[0].trim(),
|
|
952
|
+
due: parts[1].trim() || void 0,
|
|
953
|
+
list: parts[2].trim() || void 0
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
return reminders;
|
|
958
|
+
}
|
|
959
|
+
async function collectReminders(options) {
|
|
624
960
|
if (getPlatform() === "linux") {
|
|
625
961
|
return { data: [], timingMs: 0 };
|
|
626
962
|
}
|
|
627
|
-
|
|
963
|
+
const t = timer();
|
|
964
|
+
const timeoutMs = options.timeoutMs;
|
|
965
|
+
const redact = options.redact;
|
|
966
|
+
try {
|
|
967
|
+
const result = await run("osascript", ["-e", REMINDERS_SCRIPT], { timeoutMs });
|
|
968
|
+
if (result.timedOut) {
|
|
969
|
+
return {
|
|
970
|
+
data: [],
|
|
971
|
+
error: { module: "reminders", message: "Timeout", code: "timeout" },
|
|
972
|
+
timingMs: t.elapsed()
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
if (!result.ok) {
|
|
976
|
+
if (isRemindersDenied(result.stderr)) {
|
|
977
|
+
return {
|
|
978
|
+
data: [],
|
|
979
|
+
permission: "denied",
|
|
980
|
+
error: {
|
|
981
|
+
module: "reminders",
|
|
982
|
+
message: "Reminders permission required",
|
|
983
|
+
code: "permission_denied"
|
|
984
|
+
},
|
|
985
|
+
timingMs: t.elapsed()
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
return {
|
|
989
|
+
data: [],
|
|
990
|
+
error: {
|
|
991
|
+
module: "reminders",
|
|
992
|
+
message: result.stderr.trim() || "Failed to get reminders",
|
|
993
|
+
code: "error"
|
|
994
|
+
},
|
|
995
|
+
timingMs: t.elapsed()
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
const raw = result.stdout?.trim() ?? "";
|
|
999
|
+
let reminders = raw ? parseRemindersOutput(raw) : [];
|
|
1000
|
+
if (redact && reminders.length > 0) {
|
|
1001
|
+
reminders = reminders.map((r) => {
|
|
1002
|
+
const titleRedacted = redactString(r.title ?? "");
|
|
1003
|
+
return {
|
|
1004
|
+
title_sha256: titleRedacted.sha256,
|
|
1005
|
+
title_length: titleRedacted.length,
|
|
1006
|
+
due: r.due,
|
|
1007
|
+
list: r.list
|
|
1008
|
+
};
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
return {
|
|
1012
|
+
data: reminders,
|
|
1013
|
+
permission: "granted",
|
|
1014
|
+
timingMs: t.elapsed()
|
|
1015
|
+
};
|
|
1016
|
+
} catch (err) {
|
|
1017
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1018
|
+
return {
|
|
1019
|
+
data: [],
|
|
1020
|
+
error: { module: "reminders", message, code: "error" },
|
|
1021
|
+
timingMs: t.elapsed()
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
628
1024
|
}
|
|
629
1025
|
|
|
630
1026
|
// src/util/json.ts
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "os-context",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Fast macOS & Linux CLI that prints a single JSON object describing your current local context for agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"context": "dist/index.js"
|
|
8
|
+
"context": "dist/index.js",
|
|
9
|
+
"os-context": "dist/index.js"
|
|
9
10
|
},
|
|
10
11
|
"files": ["dist"],
|
|
11
12
|
"scripts": {
|