jobarbiter 0.3.13 β 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/dist/index.js +347 -3
- package/dist/lib/detect-tools.d.ts +2 -0
- package/dist/lib/detect-tools.js +29 -0
- package/dist/lib/launchd.d.ts +16 -0
- package/dist/lib/launchd.js +171 -0
- package/dist/lib/observe.js +31 -5
- package/dist/lib/onboard.js +18 -0
- package/dist/lib/poll-state.d.ts +28 -0
- package/dist/lib/poll-state.js +91 -0
- package/dist/lib/privacy-pause.d.ts +17 -0
- package/dist/lib/privacy-pause.js +137 -0
- package/dist/lib/transcript-poller.d.ts +23 -0
- package/dist/lib/transcript-poller.js +218 -0
- package/dist/lib/uninstall.d.ts +20 -0
- package/dist/lib/uninstall.js +81 -0
- package/package.json +1 -1
- package/src/index.ts +369 -5
- package/src/lib/detect-tools.ts +33 -0
- package/src/lib/launchd.ts +193 -0
- package/src/lib/observe.ts +31 -5
- package/src/lib/onboard.ts +21 -0
- package/src/lib/poll-state.ts +119 -0
- package/src/lib/privacy-pause.ts +167 -0
- package/src/lib/transcript-poller.ts +274 -0
- package/src/lib/uninstall.ts +104 -0
package/dist/index.js
CHANGED
|
@@ -6,6 +6,11 @@ import { output, outputList, success, error, setJsonMode } from "./lib/output.js
|
|
|
6
6
|
import { runOnboardWizard } from "./lib/onboard.js";
|
|
7
7
|
import { detectAgents, installObservers, removeObservers, getObservationStatus } from "./lib/observe.js";
|
|
8
8
|
import { runAutoPipeline, analyzeFile, analyzeRecent } from "./lib/analysis-pipeline.js";
|
|
9
|
+
import { pollAllSources } from "./lib/transcript-poller.js";
|
|
10
|
+
import { pauseObservation, resumeObservation, isPaused, getPauseStatus } from "./lib/privacy-pause.js";
|
|
11
|
+
import { loadPollState, savePollState, setDisabledSource, removeDisabledSource } from "./lib/poll-state.js";
|
|
12
|
+
import { installDaemon, uninstallDaemon, getDaemonStatus } from "./lib/launchd.js";
|
|
13
|
+
import { uninstallAll } from "./lib/uninstall.js";
|
|
9
14
|
import { readFileSync } from "node:fs";
|
|
10
15
|
import { join, dirname } from "node:path";
|
|
11
16
|
import { fileURLToPath } from "node:url";
|
|
@@ -760,14 +765,40 @@ program
|
|
|
760
765
|
const observe = program.command("observe").description("Manage AI tool proficiency observers");
|
|
761
766
|
observe
|
|
762
767
|
.command("status")
|
|
763
|
-
.description("Show observer status and accumulated data")
|
|
768
|
+
.description("Show observer status, pause state, daemon, and accumulated data")
|
|
764
769
|
.action(async () => {
|
|
765
770
|
try {
|
|
766
771
|
const agents = detectAgents();
|
|
767
772
|
const status = getObservationStatus();
|
|
768
|
-
const
|
|
773
|
+
const pauseStatus = getPauseStatus();
|
|
774
|
+
const daemonStatus = getDaemonStatus();
|
|
775
|
+
const pollState = loadPollState();
|
|
769
776
|
console.log("\nπ AI Agent Observers\n");
|
|
770
|
-
|
|
777
|
+
// Pause state
|
|
778
|
+
if (pauseStatus.paused) {
|
|
779
|
+
console.log(` βΈοΈ Status: ${pauseStatus.expiresAt
|
|
780
|
+
? `PAUSED (auto-resumes ${new Date(pauseStatus.expiresAt).toLocaleString()})`
|
|
781
|
+
: "PAUSED (indefinite)"}`);
|
|
782
|
+
console.log(` Since: ${pauseStatus.pausedAt ? new Date(pauseStatus.pausedAt).toLocaleString() : "unknown"}`);
|
|
783
|
+
}
|
|
784
|
+
else {
|
|
785
|
+
console.log(" βΆοΈ Status: ACTIVE");
|
|
786
|
+
}
|
|
787
|
+
// Daemon state
|
|
788
|
+
console.log(`\n Poller Daemon:`);
|
|
789
|
+
if (daemonStatus.installed) {
|
|
790
|
+
console.log(` ${daemonStatus.loaded ? "β
" : "β οΈ"} ${daemonStatus.loaded ? "Running" : "Installed but not loaded"}`);
|
|
791
|
+
if (daemonStatus.interval) {
|
|
792
|
+
const hours = Math.floor(daemonStatus.interval / 3600);
|
|
793
|
+
const mins = Math.floor((daemonStatus.interval % 3600) / 60);
|
|
794
|
+
console.log(` Interval: ${hours > 0 ? `${hours}h` : ""}${mins > 0 ? `${mins}m` : ""}`);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
console.log(" β¬ Not installed (run: jobarbiter observe daemon install)");
|
|
799
|
+
}
|
|
800
|
+
// Agents
|
|
801
|
+
console.log("\n Agents:");
|
|
771
802
|
for (const agent of agents) {
|
|
772
803
|
if (!agent.installed) {
|
|
773
804
|
console.log(` β¬ ${agent.name} (not installed)`);
|
|
@@ -779,6 +810,18 @@ observe
|
|
|
779
810
|
console.log(` β οΈ ${agent.name} (detected, no observer)`);
|
|
780
811
|
}
|
|
781
812
|
}
|
|
813
|
+
// Per-source poll stats
|
|
814
|
+
const pollEntries = Object.entries(pollState.lastPoll);
|
|
815
|
+
if (pollEntries.length > 0) {
|
|
816
|
+
console.log("\n Last Poll Per Source:");
|
|
817
|
+
for (const [source, time] of pollEntries) {
|
|
818
|
+
console.log(` ${source}: ${new Date(time).toLocaleString()}`);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
// Disabled sources
|
|
822
|
+
if (pollState.disabledSources.length > 0) {
|
|
823
|
+
console.log(`\n Disabled Sources: ${pollState.disabledSources.join(", ")}`);
|
|
824
|
+
}
|
|
782
825
|
console.log("\n Accumulated Data:");
|
|
783
826
|
console.log(` Sessions observed: ${status.totalSessions}`);
|
|
784
827
|
console.log(` Total tokens: ${status.totalTokens.toLocaleString()}`);
|
|
@@ -790,8 +833,23 @@ observe
|
|
|
790
833
|
console.log(` ${tool}: ${count} uses`);
|
|
791
834
|
}
|
|
792
835
|
}
|
|
836
|
+
// Pause history (last 5)
|
|
837
|
+
if (pauseStatus.pauseWindows.length > 0) {
|
|
838
|
+
console.log("\n Pause History (last 5):");
|
|
839
|
+
const recent = pauseStatus.pauseWindows.slice(-5);
|
|
840
|
+
for (const w of recent) {
|
|
841
|
+
const start = new Date(w.start).toLocaleString();
|
|
842
|
+
const end = new Date(w.end).toLocaleString();
|
|
843
|
+
console.log(` ${start} β ${end}`);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
793
846
|
console.log(`\n Data file: ~/.config/jobarbiter/observer/observations.json\n`);
|
|
794
847
|
output({
|
|
848
|
+
paused: pauseStatus.paused,
|
|
849
|
+
pausedAt: pauseStatus.pausedAt,
|
|
850
|
+
expiresAt: pauseStatus.expiresAt,
|
|
851
|
+
daemon: daemonStatus,
|
|
852
|
+
disabledSources: pollState.disabledSources,
|
|
795
853
|
detectedAgents: agents.map((a) => ({
|
|
796
854
|
id: a.id,
|
|
797
855
|
name: a.name,
|
|
@@ -799,6 +857,7 @@ observe
|
|
|
799
857
|
observerActive: a.hookInstalled,
|
|
800
858
|
})),
|
|
801
859
|
...status,
|
|
860
|
+
pauseWindows: pauseStatus.pauseWindows,
|
|
802
861
|
});
|
|
803
862
|
}
|
|
804
863
|
catch (e) {
|
|
@@ -914,6 +973,270 @@ observe
|
|
|
914
973
|
handleError(e);
|
|
915
974
|
}
|
|
916
975
|
});
|
|
976
|
+
// ββ observe poll ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
977
|
+
observe
|
|
978
|
+
.command("poll")
|
|
979
|
+
.description("One-shot poll for new transcripts from all sources")
|
|
980
|
+
.option("--source <id>", "Poll a specific source only")
|
|
981
|
+
.option("--dry-run", "Show what would be processed without writing")
|
|
982
|
+
.option("--since <date>", "Only process transcripts since this date (ISO 8601)")
|
|
983
|
+
.option("--verbose", "Show detailed output")
|
|
984
|
+
.action(async (opts) => {
|
|
985
|
+
try {
|
|
986
|
+
const since = opts.since ? new Date(opts.since) : undefined;
|
|
987
|
+
const result = await pollAllSources({
|
|
988
|
+
source: opts.source,
|
|
989
|
+
dryRun: opts.dryRun,
|
|
990
|
+
since,
|
|
991
|
+
verbose: opts.verbose,
|
|
992
|
+
});
|
|
993
|
+
if (!opts.verbose) {
|
|
994
|
+
if (result.newSessions > 0) {
|
|
995
|
+
success(`Found ${result.newSessions} new session(s) from ${result.sourcesPolled.length} source(s)`);
|
|
996
|
+
}
|
|
997
|
+
else {
|
|
998
|
+
console.log(`No new sessions found (polled ${result.sourcesPolled.length} sources)`);
|
|
999
|
+
}
|
|
1000
|
+
if (result.skippedDuplicate > 0) {
|
|
1001
|
+
console.log(` Skipped ${result.skippedDuplicate} duplicate(s)`);
|
|
1002
|
+
}
|
|
1003
|
+
if (result.skippedPaused > 0) {
|
|
1004
|
+
console.log(` Skipped ${result.skippedPaused} from pause window(s)`);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
if (result.errors.length > 0) {
|
|
1008
|
+
for (const e of result.errors) {
|
|
1009
|
+
console.log(` β οΈ ${e}`);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
output(result);
|
|
1013
|
+
}
|
|
1014
|
+
catch (e) {
|
|
1015
|
+
handleError(e);
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
// ββ observe pause/resume βββββββββββββββββββββββββββββββββββββββββββββββ
|
|
1019
|
+
observe
|
|
1020
|
+
.command("pause")
|
|
1021
|
+
.description("Pause all observation (hooks + poller)")
|
|
1022
|
+
.option("--for <duration>", "Auto-resume after duration (e.g. 2h, 30m, 1d)")
|
|
1023
|
+
.action(async (opts) => {
|
|
1024
|
+
try {
|
|
1025
|
+
let expiresAt;
|
|
1026
|
+
if (opts.for) {
|
|
1027
|
+
const seconds = parseDuration(opts.for);
|
|
1028
|
+
if (seconds <= 0) {
|
|
1029
|
+
error("Invalid duration. Use format like '2h', '30m', '1d'");
|
|
1030
|
+
process.exit(1);
|
|
1031
|
+
}
|
|
1032
|
+
expiresAt = new Date(Date.now() + seconds * 1000);
|
|
1033
|
+
}
|
|
1034
|
+
pauseObservation(expiresAt);
|
|
1035
|
+
if (expiresAt) {
|
|
1036
|
+
success(`Observation paused until ${expiresAt.toLocaleString()}`);
|
|
1037
|
+
}
|
|
1038
|
+
else {
|
|
1039
|
+
success("Observation paused indefinitely. Run 'jobarbiter observe resume' to resume.");
|
|
1040
|
+
}
|
|
1041
|
+
output({ paused: true, expiresAt: expiresAt?.toISOString() || null });
|
|
1042
|
+
}
|
|
1043
|
+
catch (e) {
|
|
1044
|
+
handleError(e);
|
|
1045
|
+
}
|
|
1046
|
+
});
|
|
1047
|
+
observe
|
|
1048
|
+
.command("resume")
|
|
1049
|
+
.description("Resume observation after a pause")
|
|
1050
|
+
.action(async () => {
|
|
1051
|
+
try {
|
|
1052
|
+
if (!isPaused()) {
|
|
1053
|
+
console.log("Observation is not paused.");
|
|
1054
|
+
output({ resumed: false, reason: "not_paused" });
|
|
1055
|
+
process.exit(0);
|
|
1056
|
+
}
|
|
1057
|
+
resumeObservation();
|
|
1058
|
+
success("Observation resumed. Pause window recorded.");
|
|
1059
|
+
output({ resumed: true });
|
|
1060
|
+
}
|
|
1061
|
+
catch (e) {
|
|
1062
|
+
handleError(e);
|
|
1063
|
+
}
|
|
1064
|
+
});
|
|
1065
|
+
// ββ observe interval βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
1066
|
+
observe
|
|
1067
|
+
.command("interval <duration>")
|
|
1068
|
+
.description("Change poll frequency (e.g. 2h, 30m, 1d)")
|
|
1069
|
+
.action(async (duration) => {
|
|
1070
|
+
try {
|
|
1071
|
+
const seconds = parseDuration(duration);
|
|
1072
|
+
if (seconds <= 0) {
|
|
1073
|
+
error("Invalid duration. Use format like '2h', '30m', '1d'");
|
|
1074
|
+
process.exit(1);
|
|
1075
|
+
}
|
|
1076
|
+
const state = loadPollState();
|
|
1077
|
+
state.interval = seconds;
|
|
1078
|
+
savePollState(state);
|
|
1079
|
+
// Reload daemon if installed
|
|
1080
|
+
const daemonStatus = getDaemonStatus();
|
|
1081
|
+
if (daemonStatus.installed) {
|
|
1082
|
+
installDaemon(seconds);
|
|
1083
|
+
success(`Poll interval set to ${duration} and daemon reloaded`);
|
|
1084
|
+
}
|
|
1085
|
+
else {
|
|
1086
|
+
success(`Poll interval set to ${duration}`);
|
|
1087
|
+
}
|
|
1088
|
+
output({ interval: seconds });
|
|
1089
|
+
}
|
|
1090
|
+
catch (e) {
|
|
1091
|
+
handleError(e);
|
|
1092
|
+
}
|
|
1093
|
+
});
|
|
1094
|
+
// ββ observe enable/disable βββββββββββββββββββββββββββββββββββββββββββββ
|
|
1095
|
+
observe
|
|
1096
|
+
.command("enable <source>")
|
|
1097
|
+
.description("Re-enable a disabled observation source")
|
|
1098
|
+
.action(async (source) => {
|
|
1099
|
+
try {
|
|
1100
|
+
removeDisabledSource(source);
|
|
1101
|
+
success(`Source '${source}' enabled`);
|
|
1102
|
+
output({ source, enabled: true });
|
|
1103
|
+
}
|
|
1104
|
+
catch (e) {
|
|
1105
|
+
handleError(e);
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
observe
|
|
1109
|
+
.command("disable <source>")
|
|
1110
|
+
.description("Disable observation for a specific source")
|
|
1111
|
+
.action(async (source) => {
|
|
1112
|
+
try {
|
|
1113
|
+
setDisabledSource(source);
|
|
1114
|
+
success(`Source '${source}' disabled`);
|
|
1115
|
+
output({ source, disabled: true });
|
|
1116
|
+
}
|
|
1117
|
+
catch (e) {
|
|
1118
|
+
handleError(e);
|
|
1119
|
+
}
|
|
1120
|
+
});
|
|
1121
|
+
// ββ observe daemon βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
1122
|
+
const daemon = observe.command("daemon").description("Manage the background polling daemon (macOS LaunchAgent)");
|
|
1123
|
+
daemon
|
|
1124
|
+
.command("install")
|
|
1125
|
+
.description("Install the background polling daemon")
|
|
1126
|
+
.option("--interval <duration>", "Poll interval (e.g. 2h, 30m)", "2h")
|
|
1127
|
+
.action(async (opts) => {
|
|
1128
|
+
try {
|
|
1129
|
+
const seconds = parseDuration(opts.interval);
|
|
1130
|
+
if (seconds <= 0) {
|
|
1131
|
+
error("Invalid interval. Use format like '2h', '30m', '1d'");
|
|
1132
|
+
process.exit(1);
|
|
1133
|
+
}
|
|
1134
|
+
installDaemon(seconds);
|
|
1135
|
+
success(`Polling daemon installed (interval: ${opts.interval})`);
|
|
1136
|
+
console.log(" The daemon will poll for new transcripts in the background.");
|
|
1137
|
+
console.log(" Log: ~/.config/jobarbiter/observer/poll.log");
|
|
1138
|
+
output({ installed: true, interval: seconds });
|
|
1139
|
+
}
|
|
1140
|
+
catch (e) {
|
|
1141
|
+
handleError(e);
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
1144
|
+
daemon
|
|
1145
|
+
.command("uninstall")
|
|
1146
|
+
.description("Remove the background polling daemon")
|
|
1147
|
+
.action(async () => {
|
|
1148
|
+
try {
|
|
1149
|
+
uninstallDaemon();
|
|
1150
|
+
success("Polling daemon uninstalled");
|
|
1151
|
+
output({ uninstalled: true });
|
|
1152
|
+
}
|
|
1153
|
+
catch (e) {
|
|
1154
|
+
handleError(e);
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
daemon
|
|
1158
|
+
.command("status")
|
|
1159
|
+
.description("Show daemon status")
|
|
1160
|
+
.action(async () => {
|
|
1161
|
+
try {
|
|
1162
|
+
const status = getDaemonStatus();
|
|
1163
|
+
if (!status.installed) {
|
|
1164
|
+
console.log("\nDaemon not installed. Run: jobarbiter observe daemon install\n");
|
|
1165
|
+
}
|
|
1166
|
+
else {
|
|
1167
|
+
console.log(`\nDaemon: ${status.loaded ? "β
Running" : "β οΈ Installed but not loaded"}`);
|
|
1168
|
+
if (status.interval) {
|
|
1169
|
+
const hours = Math.floor(status.interval / 3600);
|
|
1170
|
+
const mins = Math.floor((status.interval % 3600) / 60);
|
|
1171
|
+
console.log(`Interval: ${hours > 0 ? `${hours}h` : ""}${mins > 0 ? `${mins}m` : ""}`);
|
|
1172
|
+
}
|
|
1173
|
+
console.log(`Plist: ${status.plistPath}\n`);
|
|
1174
|
+
}
|
|
1175
|
+
output(status);
|
|
1176
|
+
}
|
|
1177
|
+
catch (e) {
|
|
1178
|
+
handleError(e);
|
|
1179
|
+
}
|
|
1180
|
+
});
|
|
1181
|
+
// ============================================================
|
|
1182
|
+
// uninstall (clean removal)
|
|
1183
|
+
// ============================================================
|
|
1184
|
+
program
|
|
1185
|
+
.command("uninstall")
|
|
1186
|
+
.description("Remove all JobArbiter components from this system")
|
|
1187
|
+
.option("--keep-data", "Keep observation data")
|
|
1188
|
+
.option("--keep-config", "Keep configuration files")
|
|
1189
|
+
.option("--force", "Skip confirmation prompts")
|
|
1190
|
+
.action(async (opts) => {
|
|
1191
|
+
try {
|
|
1192
|
+
if (!opts.force) {
|
|
1193
|
+
console.log("\nβ οΈ This will remove:");
|
|
1194
|
+
console.log(" - Observer hooks from all AI tools");
|
|
1195
|
+
console.log(" - Background polling daemon");
|
|
1196
|
+
console.log(" - Hook scripts");
|
|
1197
|
+
if (!opts.keepData)
|
|
1198
|
+
console.log(" - Observation data");
|
|
1199
|
+
if (!opts.keepConfig)
|
|
1200
|
+
console.log(" - Configuration and API keys");
|
|
1201
|
+
console.log();
|
|
1202
|
+
const readline = await import("node:readline");
|
|
1203
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1204
|
+
const answer = await new Promise((resolve) => {
|
|
1205
|
+
rl.question("Continue? [y/N] ", (ans) => { rl.close(); resolve(ans); });
|
|
1206
|
+
});
|
|
1207
|
+
if (!answer.toLowerCase().startsWith("y")) {
|
|
1208
|
+
console.log("Cancelled.");
|
|
1209
|
+
process.exit(0);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
const result = uninstallAll({
|
|
1213
|
+
keepData: opts.keepData,
|
|
1214
|
+
keepConfig: opts.keepConfig,
|
|
1215
|
+
force: opts.force,
|
|
1216
|
+
});
|
|
1217
|
+
if (result.observersRemoved.length > 0) {
|
|
1218
|
+
success(`Removed observers: ${result.observersRemoved.join(", ")}`);
|
|
1219
|
+
}
|
|
1220
|
+
if (result.daemonUninstalled)
|
|
1221
|
+
success("Daemon uninstalled");
|
|
1222
|
+
if (result.hooksDeleted)
|
|
1223
|
+
success("Hook scripts deleted");
|
|
1224
|
+
if (result.dataDeleted)
|
|
1225
|
+
success("Observation data deleted");
|
|
1226
|
+
if (result.configDeleted)
|
|
1227
|
+
success("Configuration deleted");
|
|
1228
|
+
for (const e of result.errors) {
|
|
1229
|
+
error(e);
|
|
1230
|
+
}
|
|
1231
|
+
if (result.errors.length === 0) {
|
|
1232
|
+
console.log("\nβ
JobArbiter fully uninstalled.\n");
|
|
1233
|
+
}
|
|
1234
|
+
output(result);
|
|
1235
|
+
}
|
|
1236
|
+
catch (e) {
|
|
1237
|
+
handleError(e);
|
|
1238
|
+
}
|
|
1239
|
+
});
|
|
917
1240
|
// ============================================================
|
|
918
1241
|
// analyze (session analysis and report submission)
|
|
919
1242
|
// ============================================================
|
|
@@ -1090,6 +1413,27 @@ function handleError(e) {
|
|
|
1090
1413
|
}
|
|
1091
1414
|
process.exit(1);
|
|
1092
1415
|
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Parse duration strings like '2h', '30m', '1d' into seconds.
|
|
1418
|
+
*/
|
|
1419
|
+
function parseDuration(input) {
|
|
1420
|
+
const match = input.match(/^(\d+(?:\.\d+)?)\s*(s|m|h|d|w)$/i);
|
|
1421
|
+
if (!match) {
|
|
1422
|
+
// Try bare number as seconds
|
|
1423
|
+
const n = parseFloat(input);
|
|
1424
|
+
return isNaN(n) ? 0 : n;
|
|
1425
|
+
}
|
|
1426
|
+
const value = parseFloat(match[1]);
|
|
1427
|
+
const unit = match[2].toLowerCase();
|
|
1428
|
+
switch (unit) {
|
|
1429
|
+
case "s": return value;
|
|
1430
|
+
case "m": return value * 60;
|
|
1431
|
+
case "h": return value * 3600;
|
|
1432
|
+
case "d": return value * 86400;
|
|
1433
|
+
case "w": return value * 604800;
|
|
1434
|
+
default: return 0;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1093
1437
|
function formatCompensation(comp) {
|
|
1094
1438
|
if (!comp)
|
|
1095
1439
|
return "Not listed";
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* Never logs or stores API key values β only checks for existence.
|
|
9
9
|
*/
|
|
10
10
|
export type ToolCategory = "ai-agent" | "chat" | "orchestration" | "api-provider";
|
|
11
|
+
export type ObservationMethod = "hook" | "extension" | "poller" | "both" | "none";
|
|
11
12
|
export interface DetectedTool {
|
|
12
13
|
id: string;
|
|
13
14
|
name: string;
|
|
@@ -17,6 +18,7 @@ export interface DetectedTool {
|
|
|
17
18
|
configDir?: string;
|
|
18
19
|
observerAvailable: boolean;
|
|
19
20
|
observerActive: boolean;
|
|
21
|
+
observationMethod: ObservationMethod;
|
|
20
22
|
}
|
|
21
23
|
/**
|
|
22
24
|
* Detect all AI tools on the system.
|
package/dist/lib/detect-tools.js
CHANGED
|
@@ -21,6 +21,7 @@ const TOOL_DEFINITIONS = [
|
|
|
21
21
|
binary: "claude",
|
|
22
22
|
configDir: join(homedir(), ".claude"),
|
|
23
23
|
observerAvailable: true,
|
|
24
|
+
observationMethod: "hook",
|
|
24
25
|
},
|
|
25
26
|
{
|
|
26
27
|
id: "cursor",
|
|
@@ -30,6 +31,7 @@ const TOOL_DEFINITIONS = [
|
|
|
30
31
|
configDir: join(homedir(), ".cursor"),
|
|
31
32
|
macApp: "/Applications/Cursor.app",
|
|
32
33
|
observerAvailable: true,
|
|
34
|
+
observationMethod: "hook",
|
|
33
35
|
},
|
|
34
36
|
{
|
|
35
37
|
id: "github-copilot",
|
|
@@ -39,6 +41,7 @@ const TOOL_DEFINITIONS = [
|
|
|
39
41
|
vscodeExtension: "github.copilot",
|
|
40
42
|
cursorExtension: "github.copilot",
|
|
41
43
|
observerAvailable: false,
|
|
44
|
+
observationMethod: "extension",
|
|
42
45
|
},
|
|
43
46
|
{
|
|
44
47
|
id: "codex",
|
|
@@ -47,6 +50,7 @@ const TOOL_DEFINITIONS = [
|
|
|
47
50
|
binary: "codex",
|
|
48
51
|
configDir: join(homedir(), ".codex"),
|
|
49
52
|
observerAvailable: true,
|
|
53
|
+
observationMethod: "hook",
|
|
50
54
|
},
|
|
51
55
|
{
|
|
52
56
|
id: "opencode",
|
|
@@ -55,6 +59,7 @@ const TOOL_DEFINITIONS = [
|
|
|
55
59
|
binary: "opencode",
|
|
56
60
|
configDir: join(homedir(), ".config", "opencode"),
|
|
57
61
|
observerAvailable: true,
|
|
62
|
+
observationMethod: "hook",
|
|
58
63
|
},
|
|
59
64
|
{
|
|
60
65
|
id: "aider",
|
|
@@ -64,6 +69,7 @@ const TOOL_DEFINITIONS = [
|
|
|
64
69
|
configDir: join(homedir(), ".aider"),
|
|
65
70
|
pipPackage: "aider-chat",
|
|
66
71
|
observerAvailable: false,
|
|
72
|
+
observationMethod: "poller",
|
|
67
73
|
},
|
|
68
74
|
{
|
|
69
75
|
id: "continue",
|
|
@@ -72,6 +78,7 @@ const TOOL_DEFINITIONS = [
|
|
|
72
78
|
vscodeExtension: "continue.continue",
|
|
73
79
|
cursorExtension: "continue.continue",
|
|
74
80
|
observerAvailable: false,
|
|
81
|
+
observationMethod: "extension",
|
|
75
82
|
},
|
|
76
83
|
{
|
|
77
84
|
id: "cline",
|
|
@@ -80,6 +87,7 @@ const TOOL_DEFINITIONS = [
|
|
|
80
87
|
vscodeExtension: "saoudrizwan.claude-dev",
|
|
81
88
|
cursorExtension: "saoudrizwan.claude-dev",
|
|
82
89
|
observerAvailable: false,
|
|
90
|
+
observationMethod: "extension",
|
|
83
91
|
},
|
|
84
92
|
{
|
|
85
93
|
id: "windsurf",
|
|
@@ -88,6 +96,7 @@ const TOOL_DEFINITIONS = [
|
|
|
88
96
|
binary: "windsurf",
|
|
89
97
|
macApp: "/Applications/Windsurf.app",
|
|
90
98
|
observerAvailable: false,
|
|
99
|
+
observationMethod: "extension",
|
|
91
100
|
},
|
|
92
101
|
{
|
|
93
102
|
id: "copilot-chat",
|
|
@@ -96,6 +105,7 @@ const TOOL_DEFINITIONS = [
|
|
|
96
105
|
vscodeExtension: "github.copilot-chat",
|
|
97
106
|
cursorExtension: "github.copilot-chat",
|
|
98
107
|
observerAvailable: false,
|
|
108
|
+
observationMethod: "extension",
|
|
99
109
|
},
|
|
100
110
|
{
|
|
101
111
|
id: "zed-ai",
|
|
@@ -104,6 +114,7 @@ const TOOL_DEFINITIONS = [
|
|
|
104
114
|
macApp: "/Applications/Zed.app",
|
|
105
115
|
configDir: join(homedir(), platform() === "darwin" ? "Library/Application Support/Zed" : ".config/zed"),
|
|
106
116
|
observerAvailable: false,
|
|
117
|
+
observationMethod: "poller",
|
|
107
118
|
},
|
|
108
119
|
{
|
|
109
120
|
id: "amazon-q",
|
|
@@ -112,6 +123,7 @@ const TOOL_DEFINITIONS = [
|
|
|
112
123
|
binary: "q",
|
|
113
124
|
configDir: join(homedir(), ".aws", "amazonq"),
|
|
114
125
|
observerAvailable: false,
|
|
126
|
+
observationMethod: "poller",
|
|
115
127
|
},
|
|
116
128
|
{
|
|
117
129
|
id: "warp-ai",
|
|
@@ -120,6 +132,7 @@ const TOOL_DEFINITIONS = [
|
|
|
120
132
|
macApp: "/Applications/Warp.app",
|
|
121
133
|
configDir: join(homedir(), "Library", "Application Support", "dev.warp.Warp-Stable"),
|
|
122
134
|
observerAvailable: false,
|
|
135
|
+
observationMethod: "none",
|
|
123
136
|
},
|
|
124
137
|
{
|
|
125
138
|
id: "letta",
|
|
@@ -129,6 +142,7 @@ const TOOL_DEFINITIONS = [
|
|
|
129
142
|
configDir: join(homedir(), ".letta"),
|
|
130
143
|
pipPackage: "letta",
|
|
131
144
|
observerAvailable: false,
|
|
145
|
+
observationMethod: "poller",
|
|
132
146
|
},
|
|
133
147
|
{
|
|
134
148
|
id: "goose",
|
|
@@ -137,6 +151,7 @@ const TOOL_DEFINITIONS = [
|
|
|
137
151
|
binary: "goose",
|
|
138
152
|
configDir: join(homedir(), ".config", "goose"),
|
|
139
153
|
observerAvailable: false,
|
|
154
|
+
observationMethod: "poller",
|
|
140
155
|
},
|
|
141
156
|
{
|
|
142
157
|
id: "idx",
|
|
@@ -145,6 +160,7 @@ const TOOL_DEFINITIONS = [
|
|
|
145
160
|
// IDX is cloud-based; no local binary. Detect via config dir if any local cache exists.
|
|
146
161
|
configDir: join(homedir(), ".idx"),
|
|
147
162
|
observerAvailable: false,
|
|
163
|
+
observationMethod: "none",
|
|
148
164
|
},
|
|
149
165
|
{
|
|
150
166
|
id: "gemini",
|
|
@@ -153,6 +169,7 @@ const TOOL_DEFINITIONS = [
|
|
|
153
169
|
binary: "gemini",
|
|
154
170
|
configDir: join(homedir(), ".gemini"),
|
|
155
171
|
observerAvailable: true,
|
|
172
|
+
observationMethod: "hook",
|
|
156
173
|
},
|
|
157
174
|
// AI Chat/Desktop
|
|
158
175
|
{
|
|
@@ -161,6 +178,7 @@ const TOOL_DEFINITIONS = [
|
|
|
161
178
|
category: "chat",
|
|
162
179
|
macApp: "/Applications/ChatGPT.app",
|
|
163
180
|
observerAvailable: false,
|
|
181
|
+
observationMethod: "none",
|
|
164
182
|
},
|
|
165
183
|
{
|
|
166
184
|
id: "claude-desktop",
|
|
@@ -168,6 +186,7 @@ const TOOL_DEFINITIONS = [
|
|
|
168
186
|
category: "chat",
|
|
169
187
|
macApp: "/Applications/Claude.app",
|
|
170
188
|
observerAvailable: false,
|
|
189
|
+
observationMethod: "none",
|
|
171
190
|
},
|
|
172
191
|
{
|
|
173
192
|
id: "ollama",
|
|
@@ -176,6 +195,7 @@ const TOOL_DEFINITIONS = [
|
|
|
176
195
|
binary: "ollama",
|
|
177
196
|
configDir: join(homedir(), ".ollama"),
|
|
178
197
|
observerAvailable: false,
|
|
198
|
+
observationMethod: "none",
|
|
179
199
|
},
|
|
180
200
|
// AI Orchestration
|
|
181
201
|
{
|
|
@@ -185,6 +205,7 @@ const TOOL_DEFINITIONS = [
|
|
|
185
205
|
binary: "openclaw",
|
|
186
206
|
configDir: join(homedir(), ".openclaw"),
|
|
187
207
|
observerAvailable: false,
|
|
208
|
+
observationMethod: "none",
|
|
188
209
|
},
|
|
189
210
|
{
|
|
190
211
|
id: "langchain",
|
|
@@ -192,6 +213,7 @@ const TOOL_DEFINITIONS = [
|
|
|
192
213
|
category: "orchestration",
|
|
193
214
|
pipPackage: "langchain",
|
|
194
215
|
observerAvailable: false,
|
|
216
|
+
observationMethod: "none",
|
|
195
217
|
},
|
|
196
218
|
{
|
|
197
219
|
id: "crewai",
|
|
@@ -199,6 +221,7 @@ const TOOL_DEFINITIONS = [
|
|
|
199
221
|
category: "orchestration",
|
|
200
222
|
pipPackage: "crewai",
|
|
201
223
|
observerAvailable: false,
|
|
224
|
+
observationMethod: "none",
|
|
202
225
|
},
|
|
203
226
|
// API Providers (detected via env vars)
|
|
204
227
|
{
|
|
@@ -207,6 +230,7 @@ const TOOL_DEFINITIONS = [
|
|
|
207
230
|
category: "api-provider",
|
|
208
231
|
envVars: ["ANTHROPIC_API_KEY"],
|
|
209
232
|
observerAvailable: false,
|
|
233
|
+
observationMethod: "none",
|
|
210
234
|
},
|
|
211
235
|
{
|
|
212
236
|
id: "openai-api",
|
|
@@ -214,6 +238,7 @@ const TOOL_DEFINITIONS = [
|
|
|
214
238
|
category: "api-provider",
|
|
215
239
|
envVars: ["OPENAI_API_KEY"],
|
|
216
240
|
observerAvailable: false,
|
|
241
|
+
observationMethod: "none",
|
|
217
242
|
},
|
|
218
243
|
{
|
|
219
244
|
id: "google-api",
|
|
@@ -221,6 +246,7 @@ const TOOL_DEFINITIONS = [
|
|
|
221
246
|
category: "api-provider",
|
|
222
247
|
envVars: ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
|
|
223
248
|
observerAvailable: false,
|
|
249
|
+
observationMethod: "none",
|
|
224
250
|
},
|
|
225
251
|
{
|
|
226
252
|
id: "groq-api",
|
|
@@ -228,6 +254,7 @@ const TOOL_DEFINITIONS = [
|
|
|
228
254
|
category: "api-provider",
|
|
229
255
|
envVars: ["GROQ_API_KEY"],
|
|
230
256
|
observerAvailable: false,
|
|
257
|
+
observationMethod: "none",
|
|
231
258
|
},
|
|
232
259
|
{
|
|
233
260
|
id: "mistral-api",
|
|
@@ -235,6 +262,7 @@ const TOOL_DEFINITIONS = [
|
|
|
235
262
|
category: "api-provider",
|
|
236
263
|
envVars: ["MISTRAL_API_KEY"],
|
|
237
264
|
observerAvailable: false,
|
|
265
|
+
observationMethod: "none",
|
|
238
266
|
},
|
|
239
267
|
];
|
|
240
268
|
// ββ Detection Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -481,6 +509,7 @@ export function detectAllTools() {
|
|
|
481
509
|
configDir: installed ? configDir : undefined,
|
|
482
510
|
observerAvailable: def.observerAvailable,
|
|
483
511
|
observerActive,
|
|
512
|
+
observationMethod: def.observationMethod,
|
|
484
513
|
});
|
|
485
514
|
}
|
|
486
515
|
return results;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LaunchAgent Management for macOS
|
|
3
|
+
*
|
|
4
|
+
* Manages the ai.jobarbiter.observer LaunchAgent for periodic transcript polling.
|
|
5
|
+
* Generates plist, writes to ~/Library/LaunchAgents/, manages via launchctl.
|
|
6
|
+
*/
|
|
7
|
+
export interface DaemonStatus {
|
|
8
|
+
installed: boolean;
|
|
9
|
+
loaded: boolean;
|
|
10
|
+
interval: number | null;
|
|
11
|
+
plistPath: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function installDaemon(intervalSeconds?: number): void;
|
|
14
|
+
export declare function uninstallDaemon(): void;
|
|
15
|
+
export declare function getDaemonStatus(): DaemonStatus;
|
|
16
|
+
export declare function reloadDaemon(): void;
|