jobarbiter 0.3.13 â 0.4.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/dist/index.js +376 -6
- 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 +395 -8
- 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/src/index.ts
CHANGED
|
@@ -6,8 +6,13 @@ import { api, apiUnauthenticated, ApiError } from "./lib/api.js";
|
|
|
6
6
|
import { output, outputList, success, error, setJsonMode } from "./lib/output.js";
|
|
7
7
|
import { runOnboardWizard } from "./lib/onboard.js";
|
|
8
8
|
import { detectAgents, installObservers, removeObservers, getObservationStatus } from "./lib/observe.js";
|
|
9
|
-
import { getObservableTools, formatToolDisplay } from "./lib/detect-tools.js";
|
|
9
|
+
import { getObservableTools, formatToolDisplay, detectAllTools } from "./lib/detect-tools.js";
|
|
10
10
|
import { runAutoPipeline, analyzeFile, analyzeRecent } from "./lib/analysis-pipeline.js";
|
|
11
|
+
import { pollAllSources } from "./lib/transcript-poller.js";
|
|
12
|
+
import { pauseObservation, resumeObservation, isPaused, getPauseStatus } from "./lib/privacy-pause.js";
|
|
13
|
+
import { loadPollState, savePollState, setDisabledSource, removeDisabledSource } from "./lib/poll-state.js";
|
|
14
|
+
import { installDaemon, uninstallDaemon, getDaemonStatus, reloadDaemon } from "./lib/launchd.js";
|
|
15
|
+
import { uninstallAll } from "./lib/uninstall.js";
|
|
11
16
|
|
|
12
17
|
import { readFileSync } from "node:fs";
|
|
13
18
|
import { join, dirname } from "node:path";
|
|
@@ -801,16 +806,42 @@ const observe = program.command("observe").description("Manage AI tool proficien
|
|
|
801
806
|
|
|
802
807
|
observe
|
|
803
808
|
.command("status")
|
|
804
|
-
.description("Show observer status and accumulated data")
|
|
809
|
+
.description("Show observer status, pause state, daemon, and accumulated data")
|
|
805
810
|
.action(async () => {
|
|
806
811
|
try {
|
|
807
812
|
const agents = detectAgents();
|
|
808
813
|
const status = getObservationStatus();
|
|
809
|
-
|
|
810
|
-
const
|
|
814
|
+
const pauseStatus = getPauseStatus();
|
|
815
|
+
const daemonStatus = getDaemonStatus();
|
|
816
|
+
const pollState = loadPollState();
|
|
811
817
|
|
|
812
818
|
console.log("\nđ AI Agent Observers\n");
|
|
813
|
-
|
|
819
|
+
|
|
820
|
+
// Pause state
|
|
821
|
+
if (pauseStatus.paused) {
|
|
822
|
+
console.log(` â¸ď¸ Status: ${pauseStatus.expiresAt
|
|
823
|
+
? `PAUSED (auto-resumes ${new Date(pauseStatus.expiresAt).toLocaleString()})`
|
|
824
|
+
: "PAUSED (indefinite)"}`);
|
|
825
|
+
console.log(` Since: ${pauseStatus.pausedAt ? new Date(pauseStatus.pausedAt).toLocaleString() : "unknown"}`);
|
|
826
|
+
} else {
|
|
827
|
+
console.log(" âśď¸ Status: ACTIVE");
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// Daemon state
|
|
831
|
+
console.log(`\n Poller Daemon:`);
|
|
832
|
+
if (daemonStatus.installed) {
|
|
833
|
+
console.log(` ${daemonStatus.loaded ? "â
" : "â ď¸"} ${daemonStatus.loaded ? "Running" : "Installed but not loaded"}`);
|
|
834
|
+
if (daemonStatus.interval) {
|
|
835
|
+
const hours = Math.floor(daemonStatus.interval / 3600);
|
|
836
|
+
const mins = Math.floor((daemonStatus.interval % 3600) / 60);
|
|
837
|
+
console.log(` Interval: ${hours > 0 ? `${hours}h` : ""}${mins > 0 ? `${mins}m` : ""}`);
|
|
838
|
+
}
|
|
839
|
+
} else {
|
|
840
|
+
console.log(" ⏠Not installed (run: jobarbiter observe daemon install)");
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// Agents
|
|
844
|
+
console.log("\n Agents:");
|
|
814
845
|
for (const agent of agents) {
|
|
815
846
|
if (!agent.installed) {
|
|
816
847
|
console.log(` ⏠${agent.name} (not installed)`);
|
|
@@ -821,6 +852,20 @@ observe
|
|
|
821
852
|
}
|
|
822
853
|
}
|
|
823
854
|
|
|
855
|
+
// Per-source poll stats
|
|
856
|
+
const pollEntries = Object.entries(pollState.lastPoll);
|
|
857
|
+
if (pollEntries.length > 0) {
|
|
858
|
+
console.log("\n Last Poll Per Source:");
|
|
859
|
+
for (const [source, time] of pollEntries) {
|
|
860
|
+
console.log(` ${source}: ${new Date(time).toLocaleString()}`);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// Disabled sources
|
|
865
|
+
if (pollState.disabledSources.length > 0) {
|
|
866
|
+
console.log(`\n Disabled Sources: ${pollState.disabledSources.join(", ")}`);
|
|
867
|
+
}
|
|
868
|
+
|
|
824
869
|
console.log("\n Accumulated Data:");
|
|
825
870
|
console.log(` Sessions observed: ${status.totalSessions}`);
|
|
826
871
|
console.log(` Total tokens: ${status.totalTokens.toLocaleString()}`);
|
|
@@ -834,9 +879,25 @@ observe
|
|
|
834
879
|
}
|
|
835
880
|
}
|
|
836
881
|
|
|
882
|
+
// Pause history (last 5)
|
|
883
|
+
if (pauseStatus.pauseWindows.length > 0) {
|
|
884
|
+
console.log("\n Pause History (last 5):");
|
|
885
|
+
const recent = pauseStatus.pauseWindows.slice(-5);
|
|
886
|
+
for (const w of recent) {
|
|
887
|
+
const start = new Date(w.start).toLocaleString();
|
|
888
|
+
const end = new Date(w.end).toLocaleString();
|
|
889
|
+
console.log(` ${start} â ${end}`);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
837
893
|
console.log(`\n Data file: ~/.config/jobarbiter/observer/observations.json\n`);
|
|
838
894
|
|
|
839
895
|
output({
|
|
896
|
+
paused: pauseStatus.paused,
|
|
897
|
+
pausedAt: pauseStatus.pausedAt,
|
|
898
|
+
expiresAt: pauseStatus.expiresAt,
|
|
899
|
+
daemon: daemonStatus,
|
|
900
|
+
disabledSources: pollState.disabledSources,
|
|
840
901
|
detectedAgents: agents.map((a) => ({
|
|
841
902
|
id: a.id,
|
|
842
903
|
name: a.name,
|
|
@@ -844,6 +905,7 @@ observe
|
|
|
844
905
|
observerActive: a.hookInstalled,
|
|
845
906
|
})),
|
|
846
907
|
...status,
|
|
908
|
+
pauseWindows: pauseStatus.pauseWindows,
|
|
847
909
|
});
|
|
848
910
|
} catch (e) {
|
|
849
911
|
handleError(e);
|
|
@@ -974,6 +1036,286 @@ observe
|
|
|
974
1036
|
}
|
|
975
1037
|
});
|
|
976
1038
|
|
|
1039
|
+
// ââ observe poll ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
1040
|
+
|
|
1041
|
+
observe
|
|
1042
|
+
.command("poll")
|
|
1043
|
+
.description("One-shot poll for new transcripts from all sources")
|
|
1044
|
+
.option("--source <id>", "Poll a specific source only")
|
|
1045
|
+
.option("--dry-run", "Show what would be processed without writing")
|
|
1046
|
+
.option("--since <date>", "Only process transcripts since this date (ISO 8601)")
|
|
1047
|
+
.option("--verbose", "Show detailed output")
|
|
1048
|
+
.action(async (opts) => {
|
|
1049
|
+
try {
|
|
1050
|
+
const since = opts.since ? new Date(opts.since) : undefined;
|
|
1051
|
+
const result = await pollAllSources({
|
|
1052
|
+
source: opts.source,
|
|
1053
|
+
dryRun: opts.dryRun,
|
|
1054
|
+
since,
|
|
1055
|
+
verbose: opts.verbose,
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
if (!opts.verbose) {
|
|
1059
|
+
if (result.newSessions > 0) {
|
|
1060
|
+
success(`Found ${result.newSessions} new session(s) from ${result.sourcesPolled.length} source(s)`);
|
|
1061
|
+
} else {
|
|
1062
|
+
console.log(`No new sessions found (polled ${result.sourcesPolled.length} sources)`);
|
|
1063
|
+
}
|
|
1064
|
+
if (result.skippedDuplicate > 0) {
|
|
1065
|
+
console.log(` Skipped ${result.skippedDuplicate} duplicate(s)`);
|
|
1066
|
+
}
|
|
1067
|
+
if (result.skippedPaused > 0) {
|
|
1068
|
+
console.log(` Skipped ${result.skippedPaused} from pause window(s)`);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
if (result.errors.length > 0) {
|
|
1073
|
+
for (const e of result.errors) {
|
|
1074
|
+
console.log(` â ď¸ ${e}`);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
output(result as unknown as Record<string, unknown>);
|
|
1079
|
+
} catch (e) {
|
|
1080
|
+
handleError(e);
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
|
|
1084
|
+
// ââ observe pause/resume âââââââââââââââââââââââââââââââââââââââââââââââ
|
|
1085
|
+
|
|
1086
|
+
observe
|
|
1087
|
+
.command("pause")
|
|
1088
|
+
.description("Pause all observation (hooks + poller)")
|
|
1089
|
+
.option("--for <duration>", "Auto-resume after duration (e.g. 2h, 30m, 1d)")
|
|
1090
|
+
.action(async (opts) => {
|
|
1091
|
+
try {
|
|
1092
|
+
let expiresAt: Date | undefined;
|
|
1093
|
+
if (opts.for) {
|
|
1094
|
+
const seconds = parseDuration(opts.for);
|
|
1095
|
+
if (seconds <= 0) {
|
|
1096
|
+
error("Invalid duration. Use format like '2h', '30m', '1d'");
|
|
1097
|
+
process.exit(1);
|
|
1098
|
+
}
|
|
1099
|
+
expiresAt = new Date(Date.now() + seconds * 1000);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
pauseObservation(expiresAt);
|
|
1103
|
+
|
|
1104
|
+
if (expiresAt) {
|
|
1105
|
+
success(`Observation paused until ${expiresAt.toLocaleString()}`);
|
|
1106
|
+
} else {
|
|
1107
|
+
success("Observation paused indefinitely. Run 'jobarbiter observe resume' to resume.");
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
output({ paused: true, expiresAt: expiresAt?.toISOString() || null });
|
|
1111
|
+
} catch (e) {
|
|
1112
|
+
handleError(e);
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
observe
|
|
1117
|
+
.command("resume")
|
|
1118
|
+
.description("Resume observation after a pause")
|
|
1119
|
+
.action(async () => {
|
|
1120
|
+
try {
|
|
1121
|
+
if (!isPaused()) {
|
|
1122
|
+
console.log("Observation is not paused.");
|
|
1123
|
+
output({ resumed: false, reason: "not_paused" });
|
|
1124
|
+
process.exit(0);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
resumeObservation();
|
|
1128
|
+
success("Observation resumed. Pause window recorded.");
|
|
1129
|
+
output({ resumed: true });
|
|
1130
|
+
} catch (e) {
|
|
1131
|
+
handleError(e);
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
// ââ observe interval âââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
1136
|
+
|
|
1137
|
+
observe
|
|
1138
|
+
.command("interval <duration>")
|
|
1139
|
+
.description("Change poll frequency (e.g. 2h, 30m, 1d)")
|
|
1140
|
+
.action(async (duration) => {
|
|
1141
|
+
try {
|
|
1142
|
+
const seconds = parseDuration(duration);
|
|
1143
|
+
if (seconds <= 0) {
|
|
1144
|
+
error("Invalid duration. Use format like '2h', '30m', '1d'");
|
|
1145
|
+
process.exit(1);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
const state = loadPollState();
|
|
1149
|
+
state.interval = seconds;
|
|
1150
|
+
savePollState(state);
|
|
1151
|
+
|
|
1152
|
+
// Reload daemon if installed
|
|
1153
|
+
const daemonStatus = getDaemonStatus();
|
|
1154
|
+
if (daemonStatus.installed) {
|
|
1155
|
+
installDaemon(seconds);
|
|
1156
|
+
success(`Poll interval set to ${duration} and daemon reloaded`);
|
|
1157
|
+
} else {
|
|
1158
|
+
success(`Poll interval set to ${duration}`);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
output({ interval: seconds });
|
|
1162
|
+
} catch (e) {
|
|
1163
|
+
handleError(e);
|
|
1164
|
+
}
|
|
1165
|
+
});
|
|
1166
|
+
|
|
1167
|
+
// ââ observe enable/disable âââââââââââââââââââââââââââââââââââââââââââââ
|
|
1168
|
+
|
|
1169
|
+
observe
|
|
1170
|
+
.command("enable <source>")
|
|
1171
|
+
.description("Re-enable a disabled observation source")
|
|
1172
|
+
.action(async (source) => {
|
|
1173
|
+
try {
|
|
1174
|
+
removeDisabledSource(source);
|
|
1175
|
+
success(`Source '${source}' enabled`);
|
|
1176
|
+
output({ source, enabled: true });
|
|
1177
|
+
} catch (e) {
|
|
1178
|
+
handleError(e);
|
|
1179
|
+
}
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
observe
|
|
1183
|
+
.command("disable <source>")
|
|
1184
|
+
.description("Disable observation for a specific source")
|
|
1185
|
+
.action(async (source) => {
|
|
1186
|
+
try {
|
|
1187
|
+
setDisabledSource(source);
|
|
1188
|
+
success(`Source '${source}' disabled`);
|
|
1189
|
+
output({ source, disabled: true });
|
|
1190
|
+
} catch (e) {
|
|
1191
|
+
handleError(e);
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
// ââ observe daemon âââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
1196
|
+
|
|
1197
|
+
const daemon = observe.command("daemon").description("Manage the background polling daemon (macOS LaunchAgent)");
|
|
1198
|
+
|
|
1199
|
+
daemon
|
|
1200
|
+
.command("install")
|
|
1201
|
+
.description("Install the background polling daemon")
|
|
1202
|
+
.option("--interval <duration>", "Poll interval (e.g. 2h, 30m)", "2h")
|
|
1203
|
+
.action(async (opts) => {
|
|
1204
|
+
try {
|
|
1205
|
+
const seconds = parseDuration(opts.interval);
|
|
1206
|
+
if (seconds <= 0) {
|
|
1207
|
+
error("Invalid interval. Use format like '2h', '30m', '1d'");
|
|
1208
|
+
process.exit(1);
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
installDaemon(seconds);
|
|
1212
|
+
success(`Polling daemon installed (interval: ${opts.interval})`);
|
|
1213
|
+
console.log(" The daemon will poll for new transcripts in the background.");
|
|
1214
|
+
console.log(" Log: ~/.config/jobarbiter/observer/poll.log");
|
|
1215
|
+
output({ installed: true, interval: seconds });
|
|
1216
|
+
} catch (e) {
|
|
1217
|
+
handleError(e);
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1220
|
+
|
|
1221
|
+
daemon
|
|
1222
|
+
.command("uninstall")
|
|
1223
|
+
.description("Remove the background polling daemon")
|
|
1224
|
+
.action(async () => {
|
|
1225
|
+
try {
|
|
1226
|
+
uninstallDaemon();
|
|
1227
|
+
success("Polling daemon uninstalled");
|
|
1228
|
+
output({ uninstalled: true });
|
|
1229
|
+
} catch (e) {
|
|
1230
|
+
handleError(e);
|
|
1231
|
+
}
|
|
1232
|
+
});
|
|
1233
|
+
|
|
1234
|
+
daemon
|
|
1235
|
+
.command("status")
|
|
1236
|
+
.description("Show daemon status")
|
|
1237
|
+
.action(async () => {
|
|
1238
|
+
try {
|
|
1239
|
+
const status = getDaemonStatus();
|
|
1240
|
+
|
|
1241
|
+
if (!status.installed) {
|
|
1242
|
+
console.log("\nDaemon not installed. Run: jobarbiter observe daemon install\n");
|
|
1243
|
+
} else {
|
|
1244
|
+
console.log(`\nDaemon: ${status.loaded ? "â
Running" : "â ď¸ Installed but not loaded"}`);
|
|
1245
|
+
if (status.interval) {
|
|
1246
|
+
const hours = Math.floor(status.interval / 3600);
|
|
1247
|
+
const mins = Math.floor((status.interval % 3600) / 60);
|
|
1248
|
+
console.log(`Interval: ${hours > 0 ? `${hours}h` : ""}${mins > 0 ? `${mins}m` : ""}`);
|
|
1249
|
+
}
|
|
1250
|
+
console.log(`Plist: ${status.plistPath}\n`);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
output(status as unknown as Record<string, unknown>);
|
|
1254
|
+
} catch (e) {
|
|
1255
|
+
handleError(e);
|
|
1256
|
+
}
|
|
1257
|
+
});
|
|
1258
|
+
|
|
1259
|
+
// ============================================================
|
|
1260
|
+
// uninstall (clean removal)
|
|
1261
|
+
// ============================================================
|
|
1262
|
+
|
|
1263
|
+
program
|
|
1264
|
+
.command("uninstall")
|
|
1265
|
+
.description("Remove all JobArbiter components from this system")
|
|
1266
|
+
.option("--keep-data", "Keep observation data")
|
|
1267
|
+
.option("--keep-config", "Keep configuration files")
|
|
1268
|
+
.option("--force", "Skip confirmation prompts")
|
|
1269
|
+
.action(async (opts) => {
|
|
1270
|
+
try {
|
|
1271
|
+
if (!opts.force) {
|
|
1272
|
+
console.log("\nâ ď¸ This will remove:");
|
|
1273
|
+
console.log(" - Observer hooks from all AI tools");
|
|
1274
|
+
console.log(" - Background polling daemon");
|
|
1275
|
+
console.log(" - Hook scripts");
|
|
1276
|
+
if (!opts.keepData) console.log(" - Observation data");
|
|
1277
|
+
if (!opts.keepConfig) console.log(" - Configuration and API keys");
|
|
1278
|
+
console.log();
|
|
1279
|
+
|
|
1280
|
+
const readline = await import("node:readline");
|
|
1281
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1282
|
+
const answer = await new Promise<string>((resolve) => {
|
|
1283
|
+
rl.question("Continue? [y/N] ", (ans) => { rl.close(); resolve(ans); });
|
|
1284
|
+
});
|
|
1285
|
+
if (!answer.toLowerCase().startsWith("y")) {
|
|
1286
|
+
console.log("Cancelled.");
|
|
1287
|
+
process.exit(0);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
const result = uninstallAll({
|
|
1292
|
+
keepData: opts.keepData,
|
|
1293
|
+
keepConfig: opts.keepConfig,
|
|
1294
|
+
force: opts.force,
|
|
1295
|
+
});
|
|
1296
|
+
|
|
1297
|
+
if (result.observersRemoved.length > 0) {
|
|
1298
|
+
success(`Removed observers: ${result.observersRemoved.join(", ")}`);
|
|
1299
|
+
}
|
|
1300
|
+
if (result.daemonUninstalled) success("Daemon uninstalled");
|
|
1301
|
+
if (result.hooksDeleted) success("Hook scripts deleted");
|
|
1302
|
+
if (result.dataDeleted) success("Observation data deleted");
|
|
1303
|
+
if (result.configDeleted) success("Configuration deleted");
|
|
1304
|
+
|
|
1305
|
+
for (const e of result.errors) {
|
|
1306
|
+
error(e);
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
if (result.errors.length === 0) {
|
|
1310
|
+
console.log("\nâ
JobArbiter fully uninstalled.\n");
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
output(result as unknown as Record<string, unknown>);
|
|
1314
|
+
} catch (e) {
|
|
1315
|
+
handleError(e);
|
|
1316
|
+
}
|
|
1317
|
+
});
|
|
1318
|
+
|
|
977
1319
|
// ============================================================
|
|
978
1320
|
// analyze (session analysis and report submission)
|
|
979
1321
|
// ============================================================
|
|
@@ -1078,6 +1420,7 @@ program
|
|
|
1078
1420
|
.command("update")
|
|
1079
1421
|
.description("Check for and install CLI updates")
|
|
1080
1422
|
.option("--check", "Just check for updates, don't install")
|
|
1423
|
+
.option("--no-reinstall-observers", "Skip automatic observer reinstall on major/minor bump")
|
|
1081
1424
|
.action(async (opts) => {
|
|
1082
1425
|
try {
|
|
1083
1426
|
console.log(`\nCurrent version: v${CLI_VERSION}`);
|
|
@@ -1129,9 +1472,31 @@ program
|
|
|
1129
1472
|
pa[0] !== pb[0] || pa[1] !== pb[1];
|
|
1130
1473
|
|
|
1131
1474
|
if (majorMinorChanged) {
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1475
|
+
if (opts.reinstallObservers) {
|
|
1476
|
+
console.log("\nReinstalling observers for new version...");
|
|
1477
|
+
try {
|
|
1478
|
+
const agents = await detectAgents();
|
|
1479
|
+
const installedIds = agents.filter(a => a.installed).map(a => a.id);
|
|
1480
|
+
if (installedIds.length === 0) {
|
|
1481
|
+
console.log(" No observers installed â nothing to reinstall.\n");
|
|
1482
|
+
} else {
|
|
1483
|
+
await removeObservers(installedIds);
|
|
1484
|
+
console.log(` â Removed observers for: ${installedIds.join(", ")}`);
|
|
1485
|
+
await installObservers(installedIds);
|
|
1486
|
+
success(`Reinstalled observers for: ${installedIds.join(", ")}`);
|
|
1487
|
+
}
|
|
1488
|
+
} catch (reinstallErr) {
|
|
1489
|
+
error(`Failed to reinstall observers: ${reinstallErr instanceof Error ? reinstallErr.message : String(reinstallErr)}`);
|
|
1490
|
+
console.log(" You can manually reinstall with:");
|
|
1491
|
+
console.log(" jobarbiter observe remove --all");
|
|
1492
|
+
console.log(" jobarbiter observe install --all\n");
|
|
1493
|
+
}
|
|
1494
|
+
} else {
|
|
1495
|
+
console.log("\nâ ď¸ Major/minor version changed. Skipped observer reinstall (--no-reinstall-observers).");
|
|
1496
|
+
console.log(" To reinstall manually:");
|
|
1497
|
+
console.log(" jobarbiter observe remove --all");
|
|
1498
|
+
console.log(" jobarbiter observe install --all\n");
|
|
1499
|
+
}
|
|
1135
1500
|
}
|
|
1136
1501
|
} catch (e) {
|
|
1137
1502
|
handleError(e);
|
|
@@ -1161,6 +1526,28 @@ function handleError(e: unknown): void {
|
|
|
1161
1526
|
process.exit(1);
|
|
1162
1527
|
}
|
|
1163
1528
|
|
|
1529
|
+
/**
|
|
1530
|
+
* Parse duration strings like '2h', '30m', '1d' into seconds.
|
|
1531
|
+
*/
|
|
1532
|
+
function parseDuration(input: string): number {
|
|
1533
|
+
const match = input.match(/^(\d+(?:\.\d+)?)\s*(s|m|h|d|w)$/i);
|
|
1534
|
+
if (!match) {
|
|
1535
|
+
// Try bare number as seconds
|
|
1536
|
+
const n = parseFloat(input);
|
|
1537
|
+
return isNaN(n) ? 0 : n;
|
|
1538
|
+
}
|
|
1539
|
+
const value = parseFloat(match[1]);
|
|
1540
|
+
const unit = match[2].toLowerCase();
|
|
1541
|
+
switch (unit) {
|
|
1542
|
+
case "s": return value;
|
|
1543
|
+
case "m": return value * 60;
|
|
1544
|
+
case "h": return value * 3600;
|
|
1545
|
+
case "d": return value * 86400;
|
|
1546
|
+
case "w": return value * 604800;
|
|
1547
|
+
default: return 0;
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1164
1551
|
function formatCompensation(comp: Record<string, unknown> | undefined): string {
|
|
1165
1552
|
if (!comp) return "Not listed";
|
|
1166
1553
|
const min = comp.min as number;
|