hacklab 0.10.5 → 0.11.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 +40 -20
- package/dist/commands/daemon.d.ts +6 -2
- package/dist/commands/daemon.d.ts.map +1 -1
- package/dist/commands/daemon.js +12 -8
- package/dist/commands/daemon.js.map +1 -1
- package/dist/commands/jobs.d.ts +18 -0
- package/dist/commands/jobs.d.ts.map +1 -0
- package/dist/commands/jobs.js +138 -0
- package/dist/commands/jobs.js.map +1 -0
- package/dist/commands/join.d.ts.map +1 -1
- package/dist/commands/join.js +2 -0
- package/dist/commands/join.js.map +1 -1
- package/dist/commands/org-jobs.d.ts +50 -0
- package/dist/commands/org-jobs.d.ts.map +1 -0
- package/dist/commands/org-jobs.js +383 -0
- package/dist/commands/org-jobs.js.map +1 -0
- package/dist/commands/org.d.ts +14 -0
- package/dist/commands/org.d.ts.map +1 -1
- package/dist/commands/org.js +98 -20
- package/dist/commands/org.js.map +1 -1
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +131 -6
- package/dist/commands/sync.js.map +1 -1
- package/dist/daily-sync.d.ts +20 -3
- package/dist/daily-sync.d.ts.map +1 -1
- package/dist/daily-sync.js +154 -44
- package/dist/daily-sync.js.map +1 -1
- package/dist/job-fields.d.ts +47 -0
- package/dist/job-fields.d.ts.map +1 -0
- package/dist/job-fields.js +148 -0
- package/dist/job-fields.js.map +1 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +12 -2
- package/dist/registry.js.map +1 -1
- package/dist/scanners/incremental.d.ts +144 -0
- package/dist/scanners/incremental.d.ts.map +1 -0
- package/dist/scanners/incremental.js +520 -0
- package/dist/scanners/incremental.js.map +1 -0
- package/dist/scanners/index.d.ts +58 -0
- package/dist/scanners/index.d.ts.map +1 -1
- package/dist/scanners/index.js +236 -128
- package/dist/scanners/index.js.map +1 -1
- package/dist/scanners/util.d.ts +1 -1
- package/dist/scanners/util.d.ts.map +1 -1
- package/dist/scanners/util.js.map +1 -1
- package/dist/sync.d.ts +12 -0
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +30 -6
- package/dist/sync.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/sync.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import * as clack from '@clack/prompts';
|
|
2
|
-
import { appendSyncLog, clearSyncPaused, markSyncPaused, } from '../daily-sync.js';
|
|
2
|
+
import { appendSyncLog, clearSyncPaused, markSyncPaused, readSyncPaused, trimSyncLog, } from '../daily-sync.js';
|
|
3
3
|
import { captureEvent } from '../posthog.js';
|
|
4
4
|
import { loadPromptConsent, parsePromptStatsFlag, resolvePromptConsent, savePromptConsent, } from '../prompt-consent.js';
|
|
5
|
-
import {
|
|
5
|
+
import { cumulativeTotals, loadScanState, markUploaded, rebuildScanState, runTick, sameTotals, saveScanState, tickPayload, } from '../scanners/incremental.js';
|
|
6
|
+
import { collectToolScans, mergeToolScans } from '../scanners/index.js';
|
|
6
7
|
import { loadSessionState } from '../session.js';
|
|
7
|
-
import { checkSession, ensureFreshSession, formatTokens, LOGIN_EXPIRED_MESSAGE, refreshSession, runSync, scanConsentedPromptStats, syncGithubRepos, uploadTokenScan, } from '../sync.js';
|
|
8
|
+
import { checkSession, ensureFreshSession, formatTokens, LOGIN_EXPIRED_MESSAGE, refreshSession, runSync, SyncUploadError, scanConsentedPromptStats, syncGithubRepos, uploadTokenScan, } from '../sync.js';
|
|
8
9
|
import { bold, dim, error, info, success } from '../ui.js';
|
|
9
10
|
import { daemon } from './daemon.js';
|
|
10
11
|
const SESSION_EXPIRED_REASON = 'your hacklab session expired — run `hacklab login`';
|
|
@@ -12,6 +13,7 @@ const SESSION_EXPIRED_REASON = 'your hacklab session expired — run `hacklab lo
|
|
|
12
13
|
* `hacklab sync` — dispatch on flags:
|
|
13
14
|
* (none) interactive sync (scan, upload, show stats)
|
|
14
15
|
* --install-daily deprecated alias for `hacklab daemon`
|
|
16
|
+
* --tick the every-minute incremental run (usually a no-op)
|
|
15
17
|
* --quiet the unattended daily run (logs to a file, no output)
|
|
16
18
|
*/
|
|
17
19
|
export async function sync(args = []) {
|
|
@@ -28,6 +30,8 @@ export async function sync(args = []) {
|
|
|
28
30
|
info(dim('`sync --install-daily` is now `hacklab daemon` — running that.'));
|
|
29
31
|
return daemon();
|
|
30
32
|
}
|
|
33
|
+
if (args.includes('--tick'))
|
|
34
|
+
return tickSync();
|
|
31
35
|
if (args.includes('--quiet'))
|
|
32
36
|
return quietSync();
|
|
33
37
|
return interactiveSync();
|
|
@@ -37,6 +41,11 @@ export async function sync(args = []) {
|
|
|
37
41
|
* Silent by design — everything goes to ~/.hacklab/sync.log. Proactively
|
|
38
42
|
* refreshes a near-expiry session; if the session has truly lapsed it drops a
|
|
39
43
|
* paused marker the next interactive run surfaces, then exits cleanly.
|
|
44
|
+
*
|
|
45
|
+
* It's also the repair pass for the minutely tick: a full stateless scan, so
|
|
46
|
+
* whatever the tick's tail-follow missed or double-counted is corrected here and
|
|
47
|
+
* the tick's state is re-based on the result. Incremental drift therefore can't
|
|
48
|
+
* outlive a day.
|
|
40
49
|
*/
|
|
41
50
|
async function quietSync() {
|
|
42
51
|
const state = await loadSessionState();
|
|
@@ -53,15 +62,18 @@ async function quietSync() {
|
|
|
53
62
|
return;
|
|
54
63
|
}
|
|
55
64
|
const session = await ensureFreshSession(state.session);
|
|
56
|
-
const
|
|
65
|
+
const results = await collectToolScans();
|
|
66
|
+
const scan = mergeToolScans(results);
|
|
57
67
|
// Whatever the user already consented to. The unattended run never asks, so
|
|
58
68
|
// a machine that has never answered uploads token counts only.
|
|
59
69
|
const promptStats = await scanConsentedPromptStats((await loadPromptConsent()) ?? 'none');
|
|
60
70
|
// Upload tokens AND mirror pinned repos together, so the after-refresh retry
|
|
61
71
|
// below does the exact same work as the happy path (no silently-skipped repo
|
|
62
|
-
// sync on a day the session had to be refreshed mid-run).
|
|
72
|
+
// sync on a day the session had to be refreshed mid-run). The state rebuild
|
|
73
|
+
// rides along: everything just went out, so nothing is left dirty.
|
|
63
74
|
const push = async (s) => {
|
|
64
75
|
await uploadTokenScan(s, scan, { promptStats });
|
|
76
|
+
await rebuildScanState(results);
|
|
65
77
|
await syncGithubRepos(s);
|
|
66
78
|
};
|
|
67
79
|
try {
|
|
@@ -92,6 +104,116 @@ async function quietSync() {
|
|
|
92
104
|
await appendSyncLog('paused: session expired');
|
|
93
105
|
}
|
|
94
106
|
}
|
|
107
|
+
/** How long to sit out after a 429 that came without a Retry-After. */
|
|
108
|
+
const DEFAULT_BACKOFF_MS = 15 * 60 * 1000;
|
|
109
|
+
const MAX_BACKOFF_MS = 6 * 60 * 60 * 1000;
|
|
110
|
+
function backoffMs(retryAfter) {
|
|
111
|
+
const seconds = Number(retryAfter);
|
|
112
|
+
if (!Number.isFinite(seconds) || seconds <= 0)
|
|
113
|
+
return DEFAULT_BACKOFF_MS;
|
|
114
|
+
return Math.min(seconds * 1000, MAX_BACKOFF_MS);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Log a tick failure once. A minutely job that keeps hitting the same wall (no
|
|
118
|
+
* network, server down) would otherwise write 1440 identical lines a day, so the
|
|
119
|
+
* first one goes in and the rest are suppressed until something changes.
|
|
120
|
+
*/
|
|
121
|
+
async function logTickError(state, line) {
|
|
122
|
+
if (state.lastError === line)
|
|
123
|
+
return;
|
|
124
|
+
state.lastError = line;
|
|
125
|
+
await appendSyncLog(line);
|
|
126
|
+
}
|
|
127
|
+
/** Pause the background sync, logging it only the first time. */
|
|
128
|
+
async function pauseTick(reason) {
|
|
129
|
+
const alreadyPaused = await readSyncPaused();
|
|
130
|
+
await markSyncPaused(reason);
|
|
131
|
+
if (!alreadyPaused)
|
|
132
|
+
await appendSyncLog(`tick paused: ${reason}`);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* `hacklab sync --tick` — the every-minute run. Where `--quiet` re-scans every
|
|
136
|
+
* log from scratch (~6s and a few hundred MB for a heavy user), this reads only
|
|
137
|
+
* what the AI tools appended since the last tick, and on the overwhelmingly
|
|
138
|
+
* common minute where nothing was appended it exits after a stat walk, without
|
|
139
|
+
* touching the network. Silent unless it uploaded, failed, or got paused: the
|
|
140
|
+
* log has to survive 1440 runs a day.
|
|
141
|
+
*/
|
|
142
|
+
async function tickSync() {
|
|
143
|
+
const saved = await loadScanState();
|
|
144
|
+
// The server asked us to back off — respect it before doing any work at all.
|
|
145
|
+
if (saved?.nextAllowedAt && Date.now() < saved.nextAllowedAt)
|
|
146
|
+
return;
|
|
147
|
+
await trimSyncLog();
|
|
148
|
+
const sessionState = await loadSessionState();
|
|
149
|
+
if (!sessionState.session) {
|
|
150
|
+
// 'missing' is a logged-out machine: nothing to say, every minute.
|
|
151
|
+
if (sessionState.status === 'expired' ||
|
|
152
|
+
sessionState.status === 'invalid') {
|
|
153
|
+
await pauseTick(sessionState.status === 'invalid'
|
|
154
|
+
? 'your hacklab session file is unreadable — run `hacklab login`'
|
|
155
|
+
: SESSION_EXPIRED_REASON);
|
|
156
|
+
}
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const session = await ensureFreshSession(sessionState.session);
|
|
160
|
+
const { state, changed } = await runTick(saved);
|
|
161
|
+
const totals = cumulativeTotals(state);
|
|
162
|
+
if (state.dirty.length === 0 &&
|
|
163
|
+
sameTotals(totals.toolTotals, state.uploaded.toolTotals) &&
|
|
164
|
+
sameTotals(totals.modelTotals, state.uploaded.modelTotals)) {
|
|
165
|
+
// The common case: nothing new to say. Persist only if the walk actually
|
|
166
|
+
// saw something move (a touched file, a re-read harness).
|
|
167
|
+
if (changed)
|
|
168
|
+
await saveScanState(state);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const scan = tickPayload(state);
|
|
172
|
+
const dates = state.dirty.length;
|
|
173
|
+
// No prompt stats and no GitHub mirror: those are the daily job's work, and a
|
|
174
|
+
// minutely run has no business re-reading transcripts. Not interactive either
|
|
175
|
+
// — a background tick isn't user activity.
|
|
176
|
+
try {
|
|
177
|
+
const result = await uploadTokenScan(session, scan);
|
|
178
|
+
markUploaded(state);
|
|
179
|
+
await saveScanState(state);
|
|
180
|
+
await clearSyncPaused();
|
|
181
|
+
await appendSyncLog(`tick: +${formatTokens(Number(result.tokensDelta ?? 0))} tokens (${dates} date${dates === 1 ? '' : 's'})`);
|
|
182
|
+
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
185
|
+
const status = e instanceof SyncUploadError ? e.status : 0;
|
|
186
|
+
if (status === 429) {
|
|
187
|
+
const wait = backoffMs(e instanceof SyncUploadError ? e.retryAfter : null);
|
|
188
|
+
state.nextAllowedAt = Date.now() + wait;
|
|
189
|
+
await logTickError(state, `tick: rate limited, pausing ${Math.round(wait / 1000)}s`);
|
|
190
|
+
await saveScanState(state);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (message === LOGIN_EXPIRED_MESSAGE) {
|
|
194
|
+
const refreshed = await refreshSession(session);
|
|
195
|
+
if (refreshed) {
|
|
196
|
+
try {
|
|
197
|
+
const result = await uploadTokenScan(refreshed, scan);
|
|
198
|
+
markUploaded(state);
|
|
199
|
+
await saveScanState(state);
|
|
200
|
+
await clearSyncPaused();
|
|
201
|
+
await appendSyncLog(`tick: +${formatTokens(Number(result.tokensDelta ?? 0))} tokens (${dates} dates, after refresh)`);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
// fall through to pause
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
await saveScanState(state);
|
|
209
|
+
await pauseTick(SESSION_EXPIRED_REASON);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
// Dirty dates stay dirty, so the next tick retries them.
|
|
213
|
+
await logTickError(state, `tick error: ${message}`);
|
|
214
|
+
await saveScanState(state);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
95
217
|
async function interactiveSync() {
|
|
96
218
|
const sessionState = await loadSessionState();
|
|
97
219
|
if (!sessionState.session) {
|
|
@@ -131,7 +253,7 @@ async function interactiveSync() {
|
|
|
131
253
|
error(e instanceof Error ? e.message : 'sync failed');
|
|
132
254
|
process.exit(1);
|
|
133
255
|
}
|
|
134
|
-
const { claudeTotal, codexTotal, cursorTotal, openclawTotal, hermesTotal, opencodeTotal, cursorScanStatus, result: r, } = result;
|
|
256
|
+
const { claudeTotal, codexTotal, cursorTotal, openclawTotal, hermesTotal, opencodeTotal, grokTotal, cursorScanStatus, result: r, } = result;
|
|
135
257
|
if (claudeTotal > 0)
|
|
136
258
|
info(` Claude Code ${formatTokens(claudeTotal)} tokens`);
|
|
137
259
|
if (codexTotal > 0)
|
|
@@ -144,6 +266,8 @@ async function interactiveSync() {
|
|
|
144
266
|
info(` Hermes ${formatTokens(hermesTotal)} tokens`);
|
|
145
267
|
if (opencodeTotal > 0)
|
|
146
268
|
info(` OpenCode ${formatTokens(opencodeTotal)} tokens`);
|
|
269
|
+
if (grokTotal > 0)
|
|
270
|
+
info(` Grok Build ${formatTokens(grokTotal)} tokens`);
|
|
147
271
|
// A key Cursor rejected must never be silent: the Cursor line above would be
|
|
148
272
|
// the local estimate while the user believes they're getting exact counts.
|
|
149
273
|
if (cursorScanStatus.source === 'api-failed') {
|
|
@@ -190,6 +314,7 @@ async function interactiveSync() {
|
|
|
190
314
|
tokens_openclaw: openclawTotal,
|
|
191
315
|
tokens_hermes: hermesTotal,
|
|
192
316
|
tokens_opencode: opencodeTotal,
|
|
317
|
+
tokens_grok: grokTotal,
|
|
193
318
|
level: r.level,
|
|
194
319
|
title: r.title,
|
|
195
320
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"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"}
|
package/dist/daily-sync.d.ts
CHANGED
|
@@ -14,6 +14,11 @@ export type InstallResult = {
|
|
|
14
14
|
/** The daily background job writes here; launchd/systemd also point stdout+stderr
|
|
15
15
|
* at it. Lives next to the session file so it honors HACKLAB_SESSION_PATH. */
|
|
16
16
|
export declare function syncLogPath(): string;
|
|
17
|
+
export declare const SYNC_LOG_MAX_BYTES = 1000000;
|
|
18
|
+
export declare const SYNC_LOG_KEEP_LINES = 200;
|
|
19
|
+
/** Cut an oversized sync.log down to its last few hundred lines. Best-effort,
|
|
20
|
+
* and a no-op while the log is small — which is the normal case. */
|
|
21
|
+
export declare function trimSyncLog(maxBytes?: number, keepLines?: number): Promise<void>;
|
|
17
22
|
/** Append a timestamped line to the background-sync log (best-effort). */
|
|
18
23
|
export declare function appendSyncLog(line: string): Promise<void>;
|
|
19
24
|
/** Record that the background sync can't continue (e.g. the session expired), so
|
|
@@ -29,27 +34,39 @@ export declare function launchdPlist(cmd: SyncCommand, time: {
|
|
|
29
34
|
hour: number;
|
|
30
35
|
minute: number;
|
|
31
36
|
}, logPath: string): string;
|
|
37
|
+
/** The minutely tick. StartInterval (not a calendar interval) so launchd just
|
|
38
|
+
* re-runs it every 60s; a tick with nothing to report exits without a request. */
|
|
39
|
+
export declare function launchdTickPlist(cmd: SyncCommand, logPath: string): string;
|
|
32
40
|
export declare function systemdService(cmd: SyncCommand): string;
|
|
33
41
|
export declare function systemdTimer(): string;
|
|
42
|
+
export declare function systemdTickService(cmd: SyncCommand): string;
|
|
43
|
+
export declare function systemdTickTimer(): string;
|
|
34
44
|
/** Where the Windows scheduled task's wrapper batch file lives. Next to the
|
|
35
45
|
* session file so it honors HACKLAB_SESSION_PATH, mirroring syncLogPath(). */
|
|
36
46
|
export declare function syncWrapperPath(): string;
|
|
47
|
+
/** The tick's own wrapper — same pattern, its own file so the two tasks can be
|
|
48
|
+
* created and deleted independently. */
|
|
49
|
+
export declare function tickWrapperPath(): string;
|
|
37
50
|
/** The batch wrapper Task Scheduler runs. schtasks' `/TR` chokes on the nested
|
|
38
51
|
* quotes a full `"node" "script" …` command needs, so we point the task at this
|
|
39
52
|
* one-line .cmd instead and do the quoting here, where plain-batch rules apply.
|
|
40
53
|
* Output is redirected to the same sync.log the job also appends to, so a crash
|
|
41
54
|
* before the CLI's own logging still leaves a trace. */
|
|
42
55
|
export declare function schtasksWrapper(cmd: SyncCommand, logPath: string): string;
|
|
56
|
+
/** Same wrapper for the minutely tick, logging to the same sync.log. */
|
|
57
|
+
export declare function schtasksTickWrapper(cmd: SyncCommand, logPath: string): string;
|
|
43
58
|
/** Args for `schtasks /Create`. The `/TR` value is a single pre-quoted path;
|
|
44
59
|
* spawn must run with windowsVerbatimArguments so Node doesn't re-escape it. */
|
|
45
60
|
export declare function schtasksCreateArgs(wrapperPath: string, time: {
|
|
46
61
|
hour: number;
|
|
47
62
|
minute: number;
|
|
48
63
|
}): string[];
|
|
64
|
+
/** Args for `schtasks /Create` for the tick: every minute, forever. */
|
|
65
|
+
export declare function schtasksTickCreateArgs(wrapperPath: string): string[];
|
|
49
66
|
export declare function manualInstructions(cmd: SyncCommand): string;
|
|
50
|
-
/** Install (or refresh)
|
|
67
|
+
/** Install (or refresh) both background jobs (tick + daily) for the current OS. */
|
|
51
68
|
export declare function installDailySync(): Promise<InstallResult>;
|
|
52
|
-
/** Remove
|
|
53
|
-
*
|
|
69
|
+
/** Remove both background jobs. Best-effort and never throws, so it's safe to
|
|
70
|
+
* call from `logout` whether or not they were ever installed. */
|
|
54
71
|
export declare function uninstallDailySync(): Promise<void>;
|
|
55
72
|
//# sourceMappingURL=daily-sync.d.ts.map
|
package/dist/daily-sync.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daily-sync.d.ts","sourceRoot":"","sources":["../src/daily-sync.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"daily-sync.d.ts","sourceRoot":"","sources":["../src/daily-sync.ts"],"names":[],"mappings":"AA+BA,MAAM,MAAM,WAAW,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAE1D,MAAM,MAAM,aAAa,GACrB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC3E;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,QAAQ,GAAG,aAAa,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAA;AAgB5E;8EAC8E;AAC9E,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAQD,eAAO,MAAM,kBAAkB,UAAY,CAAA;AAC3C,eAAO,MAAM,mBAAmB,MAAM,CAAA;AAEtC;oEACoE;AACpE,wBAAsB,WAAW,CAC/B,QAAQ,SAAqB,EAC7B,SAAS,SAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAUf;AAED,0EAA0E;AAC1E,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAO/D;AAED;wDACwD;AACxD,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOlE;AAED,qEAAqE;AACrE,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAO7D;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAMrD;AAED;2CAC2C;AAC3C,wBAAgB,kBAAkB,IAAI,WAAW,CAShD;AAyCD,wBAAgB,YAAY,CAC1B,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EACtC,OAAO,EAAE,MAAM,GACd,MAAM,CAcR;AAED;kFACkF;AAClF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAS1E;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CASvD;AAED,wBAAgB,YAAY,IAAI,MAAM,CAcrC;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAQ3D;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAgBzC;AAED;8EAC8E;AAC9E,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;wCACwC;AACxC,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;;;wDAIwD;AACxD,wBAAgB,eAAe,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEzE;AAED,wEAAwE;AACxE,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED;gFACgF;AAChF,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACrC,MAAM,EAAE,CAaV;AAED,uEAAuE;AACvE,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAapE;AAID,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAS3D;AAiID,mFAAmF;AACnF,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,aAAa,CAAC,CAwB/D;AAED;iEACiE;AACjE,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CA0BxD"}
|
package/dist/daily-sync.js
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { realpathSync } from 'node:fs';
|
|
3
|
-
import { appendFile, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { appendFile, mkdir, readFile, rm, stat, writeFile, } from 'node:fs/promises';
|
|
4
4
|
import { homedir, platform } from 'node:os';
|
|
5
5
|
import { dirname, join } from 'node:path';
|
|
6
6
|
import { getSessionPath } from './session.js';
|
|
7
|
-
// Sets up (and tears down)
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// systemd
|
|
7
|
+
// Sets up (and tears down) the two OS-native background jobs: a `hacklab sync
|
|
8
|
+
// --tick` every minute (the incremental one — reads only what the AI tools
|
|
9
|
+
// appended since the last run) and the full `hacklab sync --quiet` once a day
|
|
10
|
+
// (the repair pass, which also re-bases the tick's state). launchd on macOS, a
|
|
11
|
+
// systemd user timer on Linux, Task Scheduler on Windows, and printed
|
|
12
|
+
// instructions when none of those fit. The scheduled command is resolved to an
|
|
13
|
+
// absolute `node <entry>` pair so a scheduler running with a bare PATH
|
|
14
|
+
// (cron/launchd/systemd often have almost none) can launch it without a
|
|
15
|
+
// `hacklab` shim.
|
|
12
16
|
const LAUNCHD_LABEL = 'so.hacklab.sync';
|
|
17
|
+
const LAUNCHD_TICK_LABEL = 'so.hacklab.tick';
|
|
13
18
|
const SYSTEMD_UNIT = 'hacklab-sync';
|
|
19
|
+
const SYSTEMD_TICK_UNIT = 'hacklab-tick';
|
|
14
20
|
const SCHTASKS_TASK = 'hacklab-sync';
|
|
21
|
+
const SCHTASKS_TICK_TASK = 'hacklab-tick';
|
|
15
22
|
/** Run a command without ever rejecting: resolves the exit code (127 when the
|
|
16
23
|
* binary can't even be spawned, e.g. `systemctl` absent). */
|
|
17
24
|
function run(cmd, args, opts = {}) {
|
|
@@ -29,6 +36,25 @@ export function syncLogPath() {
|
|
|
29
36
|
function pausedMarkerPath() {
|
|
30
37
|
return join(dirname(getSessionPath()), 'sync-paused');
|
|
31
38
|
}
|
|
39
|
+
// The tick runs 1440 times a day, so the log has to be bounded even if every
|
|
40
|
+
// one of those runs has something to say.
|
|
41
|
+
export const SYNC_LOG_MAX_BYTES = 1_000_000;
|
|
42
|
+
export const SYNC_LOG_KEEP_LINES = 200;
|
|
43
|
+
/** Cut an oversized sync.log down to its last few hundred lines. Best-effort,
|
|
44
|
+
* and a no-op while the log is small — which is the normal case. */
|
|
45
|
+
export async function trimSyncLog(maxBytes = SYNC_LOG_MAX_BYTES, keepLines = SYNC_LOG_KEEP_LINES) {
|
|
46
|
+
try {
|
|
47
|
+
const path = syncLogPath();
|
|
48
|
+
if ((await stat(path)).size <= maxBytes)
|
|
49
|
+
return;
|
|
50
|
+
const lines = (await readFile(path, 'utf8')).split('\n');
|
|
51
|
+
const kept = lines.slice(-keepLines).join('\n');
|
|
52
|
+
await writeFile(path, kept.endsWith('\n') ? kept : `${kept}\n`, 'utf8');
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// no log yet, or we can't rewrite it — either way, nothing to do
|
|
56
|
+
}
|
|
57
|
+
}
|
|
32
58
|
/** Append a timestamped line to the background-sync log (best-effort). */
|
|
33
59
|
export async function appendSyncLog(line) {
|
|
34
60
|
try {
|
|
@@ -86,8 +112,9 @@ function xmlEscape(s) {
|
|
|
86
112
|
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
87
113
|
}
|
|
88
114
|
const pad2 = (n) => String(n).padStart(2, '0');
|
|
89
|
-
|
|
90
|
-
|
|
115
|
+
/** The plist skeleton both jobs share — same command shape, different schedule. */
|
|
116
|
+
function launchdDoc(label, cmd, mode, schedule, logPath) {
|
|
117
|
+
const args = [cmd.node, cmd.script, 'sync', mode]
|
|
91
118
|
.map((a) => ` <string>${xmlEscape(a)}</string>`)
|
|
92
119
|
.join('\n');
|
|
93
120
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -95,18 +122,12 @@ export function launchdPlist(cmd, time, logPath) {
|
|
|
95
122
|
<plist version="1.0">
|
|
96
123
|
<dict>
|
|
97
124
|
<key>Label</key>
|
|
98
|
-
<string>${
|
|
125
|
+
<string>${label}</string>
|
|
99
126
|
<key>ProgramArguments</key>
|
|
100
127
|
<array>
|
|
101
128
|
${args}
|
|
102
129
|
</array>
|
|
103
|
-
|
|
104
|
-
<dict>
|
|
105
|
-
<key>Hour</key>
|
|
106
|
-
<integer>${time.hour}</integer>
|
|
107
|
-
<key>Minute</key>
|
|
108
|
-
<integer>${time.minute}</integer>
|
|
109
|
-
</dict>
|
|
130
|
+
${schedule}
|
|
110
131
|
<key>StandardOutPath</key>
|
|
111
132
|
<string>${xmlEscape(logPath)}</string>
|
|
112
133
|
<key>StandardErrorPath</key>
|
|
@@ -117,6 +138,21 @@ ${args}
|
|
|
117
138
|
</plist>
|
|
118
139
|
`;
|
|
119
140
|
}
|
|
141
|
+
export function launchdPlist(cmd, time, logPath) {
|
|
142
|
+
return launchdDoc(LAUNCHD_LABEL, cmd, '--quiet', ` <key>StartCalendarInterval</key>
|
|
143
|
+
<dict>
|
|
144
|
+
<key>Hour</key>
|
|
145
|
+
<integer>${time.hour}</integer>
|
|
146
|
+
<key>Minute</key>
|
|
147
|
+
<integer>${time.minute}</integer>
|
|
148
|
+
</dict>`, logPath);
|
|
149
|
+
}
|
|
150
|
+
/** The minutely tick. StartInterval (not a calendar interval) so launchd just
|
|
151
|
+
* re-runs it every 60s; a tick with nothing to report exits without a request. */
|
|
152
|
+
export function launchdTickPlist(cmd, logPath) {
|
|
153
|
+
return launchdDoc(LAUNCHD_TICK_LABEL, cmd, '--tick', ` <key>StartInterval</key>
|
|
154
|
+
<integer>60</integer>`, logPath);
|
|
155
|
+
}
|
|
120
156
|
export function systemdService(cmd) {
|
|
121
157
|
// Quote node + script so paths with spaces survive systemd's parser.
|
|
122
158
|
return `[Unit]
|
|
@@ -138,6 +174,32 @@ OnCalendar=daily
|
|
|
138
174
|
Persistent=true
|
|
139
175
|
RandomizedDelaySec=3600
|
|
140
176
|
|
|
177
|
+
[Install]
|
|
178
|
+
WantedBy=timers.target
|
|
179
|
+
`;
|
|
180
|
+
}
|
|
181
|
+
export function systemdTickService(cmd) {
|
|
182
|
+
return `[Unit]
|
|
183
|
+
Description=hacklab token tick
|
|
184
|
+
|
|
185
|
+
[Service]
|
|
186
|
+
Type=oneshot
|
|
187
|
+
ExecStart="${cmd.node}" "${cmd.script}" sync --tick
|
|
188
|
+
`;
|
|
189
|
+
}
|
|
190
|
+
export function systemdTickTimer() {
|
|
191
|
+
// OnUnitActiveSec re-arms a minute after each run finishes (so a slow tick
|
|
192
|
+
// can't stack up), OnBootSec gives the desktop a moment to settle first, and
|
|
193
|
+
// AccuracySec lets systemd batch the wakeup with others — a minutely timer is
|
|
194
|
+
// otherwise a needless drain on a laptop.
|
|
195
|
+
return `[Unit]
|
|
196
|
+
Description=hacklab token tick
|
|
197
|
+
|
|
198
|
+
[Timer]
|
|
199
|
+
OnBootSec=2min
|
|
200
|
+
OnUnitActiveSec=1min
|
|
201
|
+
AccuracySec=30s
|
|
202
|
+
|
|
141
203
|
[Install]
|
|
142
204
|
WantedBy=timers.target
|
|
143
205
|
`;
|
|
@@ -147,6 +209,11 @@ WantedBy=timers.target
|
|
|
147
209
|
export function syncWrapperPath() {
|
|
148
210
|
return join(dirname(getSessionPath()), 'hacklab-sync.cmd');
|
|
149
211
|
}
|
|
212
|
+
/** The tick's own wrapper — same pattern, its own file so the two tasks can be
|
|
213
|
+
* created and deleted independently. */
|
|
214
|
+
export function tickWrapperPath() {
|
|
215
|
+
return join(dirname(getSessionPath()), 'hacklab-tick.cmd');
|
|
216
|
+
}
|
|
150
217
|
/** The batch wrapper Task Scheduler runs. schtasks' `/TR` chokes on the nested
|
|
151
218
|
* quotes a full `"node" "script" …` command needs, so we point the task at this
|
|
152
219
|
* one-line .cmd instead and do the quoting here, where plain-batch rules apply.
|
|
@@ -155,6 +222,10 @@ export function syncWrapperPath() {
|
|
|
155
222
|
export function schtasksWrapper(cmd, logPath) {
|
|
156
223
|
return `@echo off\r\n"${cmd.node}" "${cmd.script}" sync --quiet >> "${logPath}" 2>&1\r\n`;
|
|
157
224
|
}
|
|
225
|
+
/** Same wrapper for the minutely tick, logging to the same sync.log. */
|
|
226
|
+
export function schtasksTickWrapper(cmd, logPath) {
|
|
227
|
+
return `@echo off\r\n"${cmd.node}" "${cmd.script}" sync --tick >> "${logPath}" 2>&1\r\n`;
|
|
228
|
+
}
|
|
158
229
|
/** Args for `schtasks /Create`. The `/TR` value is a single pre-quoted path;
|
|
159
230
|
* spawn must run with windowsVerbatimArguments so Node doesn't re-escape it. */
|
|
160
231
|
export function schtasksCreateArgs(wrapperPath, time) {
|
|
@@ -171,35 +242,61 @@ export function schtasksCreateArgs(wrapperPath, time) {
|
|
|
171
242
|
'/F',
|
|
172
243
|
];
|
|
173
244
|
}
|
|
245
|
+
/** Args for `schtasks /Create` for the tick: every minute, forever. */
|
|
246
|
+
export function schtasksTickCreateArgs(wrapperPath) {
|
|
247
|
+
return [
|
|
248
|
+
'/Create',
|
|
249
|
+
'/SC',
|
|
250
|
+
'MINUTE',
|
|
251
|
+
'/MO',
|
|
252
|
+
'1',
|
|
253
|
+
'/TN',
|
|
254
|
+
SCHTASKS_TICK_TASK,
|
|
255
|
+
'/TR',
|
|
256
|
+
`"${wrapperPath}"`,
|
|
257
|
+
'/F',
|
|
258
|
+
];
|
|
259
|
+
}
|
|
174
260
|
// Generic fallback text, reused for both "Linux without a systemd user manager"
|
|
175
261
|
// and "OS we don't auto-schedule on" — so it must not name a specific mechanism.
|
|
176
262
|
export function manualInstructions(cmd) {
|
|
177
263
|
return [
|
|
178
|
-
"Couldn't set up
|
|
179
|
-
'To run it yourself, schedule
|
|
180
|
-
"
|
|
264
|
+
"Couldn't set up the automatic background sync on this system.",
|
|
265
|
+
'To run it yourself, schedule these two commands with cron, a systemd timer,',
|
|
266
|
+
"or your init system's scheduler — the tick every minute, the full sync once",
|
|
267
|
+
'a day:',
|
|
268
|
+
` "${cmd.node}" "${cmd.script}" sync --tick`,
|
|
181
269
|
` "${cmd.node}" "${cmd.script}" sync --quiet`,
|
|
182
270
|
].join('\n');
|
|
183
271
|
}
|
|
272
|
+
function launchAgentPath(label) {
|
|
273
|
+
return join(homedir(), 'Library', 'LaunchAgents', `${label}.plist`);
|
|
274
|
+
}
|
|
275
|
+
/** Write a plist and (re)load it. Unload first so a re-run replaces the agent
|
|
276
|
+
* rather than stacking a second copy. */
|
|
277
|
+
async function loadAgent(path, contents) {
|
|
278
|
+
await mkdir(dirname(path), { recursive: true });
|
|
279
|
+
await writeFile(path, contents, 'utf8');
|
|
280
|
+
await run('launchctl', ['unload', path]);
|
|
281
|
+
return run('launchctl', ['load', '-w', path]);
|
|
282
|
+
}
|
|
184
283
|
async function installLaunchd(cmd, logPath) {
|
|
185
|
-
const plistPath =
|
|
284
|
+
const plistPath = launchAgentPath(LAUNCHD_LABEL);
|
|
186
285
|
// Randomize the time so installs don't all hit the API at the same instant.
|
|
187
286
|
const time = {
|
|
188
287
|
hour: Math.floor(Math.random() * 24),
|
|
189
288
|
minute: Math.floor(Math.random() * 60),
|
|
190
289
|
};
|
|
191
|
-
await
|
|
192
|
-
await
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
await run('launchctl', ['unload', plistPath]);
|
|
197
|
-
const loaded = await run('launchctl', ['load', '-w', plistPath]);
|
|
290
|
+
const loaded = await loadAgent(plistPath, launchdPlist(cmd, time, logPath));
|
|
291
|
+
await loadAgent(launchAgentPath(LAUNCHD_TICK_LABEL), launchdTickPlist(cmd, logPath));
|
|
292
|
+
// A plist in ~/Library/LaunchAgents also loads at next login, so even if
|
|
293
|
+
// `load` fails now the agent activates then — report that honestly rather
|
|
294
|
+
// than claiming it's already running.
|
|
198
295
|
return {
|
|
199
296
|
ok: true,
|
|
200
297
|
mechanism: 'launchd',
|
|
201
298
|
detail: loaded === 0
|
|
202
|
-
? `daily around ${pad2(time.hour)}:${pad2(time.minute)} (${plistPath})`
|
|
299
|
+
? `tick every minute, full sync daily around ${pad2(time.hour)}:${pad2(time.minute)} (${plistPath})`
|
|
203
300
|
: `installed (${plistPath}) — activates at next login`,
|
|
204
301
|
};
|
|
205
302
|
}
|
|
@@ -219,18 +316,27 @@ async function installSystemd(cmd) {
|
|
|
219
316
|
await mkdir(dir, { recursive: true });
|
|
220
317
|
await writeFile(join(dir, `${SYSTEMD_UNIT}.service`), systemdService(cmd), 'utf8');
|
|
221
318
|
await writeFile(join(dir, `${SYSTEMD_UNIT}.timer`), systemdTimer(), 'utf8');
|
|
319
|
+
await writeFile(join(dir, `${SYSTEMD_TICK_UNIT}.service`), systemdTickService(cmd), 'utf8');
|
|
320
|
+
await writeFile(join(dir, `${SYSTEMD_TICK_UNIT}.timer`), systemdTickTimer(), 'utf8');
|
|
222
321
|
await run('systemctl', ['--user', 'daemon-reload']);
|
|
223
322
|
await run('systemctl', ['--user', 'enable', '--now', `${SYSTEMD_UNIT}.timer`]);
|
|
323
|
+
await run('systemctl', [
|
|
324
|
+
'--user',
|
|
325
|
+
'enable',
|
|
326
|
+
'--now',
|
|
327
|
+
`${SYSTEMD_TICK_UNIT}.timer`,
|
|
328
|
+
]);
|
|
224
329
|
return {
|
|
225
330
|
ok: true,
|
|
226
331
|
mechanism: 'systemd',
|
|
227
|
-
detail: `systemd user
|
|
332
|
+
detail: `systemd user timers (${SYSTEMD_TICK_UNIT}.timer every minute, ${SYSTEMD_UNIT}.timer daily); logs in journalctl --user -u ${SYSTEMD_UNIT}. To keep them running while logged out: loginctl enable-linger`,
|
|
228
333
|
};
|
|
229
334
|
}
|
|
230
335
|
async function installSchtasks(cmd) {
|
|
231
336
|
const wrapperPath = syncWrapperPath();
|
|
232
337
|
await mkdir(dirname(wrapperPath), { recursive: true });
|
|
233
338
|
await writeFile(wrapperPath, schtasksWrapper(cmd, syncLogPath()), 'utf8');
|
|
339
|
+
await writeFile(tickWrapperPath(), schtasksTickWrapper(cmd, syncLogPath()), 'utf8');
|
|
234
340
|
// Randomize the time so installs don't all hit the API at the same instant.
|
|
235
341
|
const time = {
|
|
236
342
|
hour: Math.floor(Math.random() * 24),
|
|
@@ -249,13 +355,16 @@ async function installSchtasks(cmd) {
|
|
|
249
355
|
instructions: manualInstructions(cmd),
|
|
250
356
|
};
|
|
251
357
|
}
|
|
358
|
+
// The daily job is the one that must exist; a refused minutely task (some
|
|
359
|
+
// policies cap task frequency) costs freshness, not the streak.
|
|
360
|
+
const tick = await run('schtasks', schtasksTickCreateArgs(tickWrapperPath()), { windowsVerbatimArguments: true });
|
|
252
361
|
return {
|
|
253
362
|
ok: true,
|
|
254
363
|
mechanism: 'schtasks',
|
|
255
|
-
detail:
|
|
364
|
+
detail: `${tick === 0 ? 'tick every minute, ' : ''}full sync daily around ${pad2(time.hour)}:${pad2(time.minute)} (Task Scheduler task "${SCHTASKS_TASK}")`,
|
|
256
365
|
};
|
|
257
366
|
}
|
|
258
|
-
/** Install (or refresh)
|
|
367
|
+
/** Install (or refresh) both background jobs (tick + daily) for the current OS. */
|
|
259
368
|
export async function installDailySync() {
|
|
260
369
|
const cmd = resolveSyncCommand();
|
|
261
370
|
try {
|
|
@@ -283,31 +392,32 @@ export async function installDailySync() {
|
|
|
283
392
|
};
|
|
284
393
|
}
|
|
285
394
|
}
|
|
286
|
-
/** Remove
|
|
287
|
-
*
|
|
395
|
+
/** Remove both background jobs. Best-effort and never throws, so it's safe to
|
|
396
|
+
* call from `logout` whether or not they were ever installed. */
|
|
288
397
|
export async function uninstallDailySync() {
|
|
289
398
|
const os = platform();
|
|
290
399
|
try {
|
|
291
400
|
if (os === 'darwin') {
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
401
|
+
for (const label of [LAUNCHD_LABEL, LAUNCHD_TICK_LABEL]) {
|
|
402
|
+
const plistPath = launchAgentPath(label);
|
|
403
|
+
await run('launchctl', ['unload', plistPath]);
|
|
404
|
+
await rm(plistPath, { force: true });
|
|
405
|
+
}
|
|
295
406
|
}
|
|
296
407
|
else if (os === 'linux') {
|
|
297
|
-
await run('systemctl', [
|
|
298
|
-
'--user',
|
|
299
|
-
'disable',
|
|
300
|
-
'--now',
|
|
301
|
-
`${SYSTEMD_UNIT}.timer`,
|
|
302
|
-
]);
|
|
303
408
|
const dir = join(homedir(), '.config', 'systemd', 'user');
|
|
304
|
-
|
|
305
|
-
|
|
409
|
+
for (const unit of [SYSTEMD_UNIT, SYSTEMD_TICK_UNIT]) {
|
|
410
|
+
await run('systemctl', ['--user', 'disable', '--now', `${unit}.timer`]);
|
|
411
|
+
await rm(join(dir, `${unit}.timer`), { force: true });
|
|
412
|
+
await rm(join(dir, `${unit}.service`), { force: true });
|
|
413
|
+
}
|
|
306
414
|
await run('systemctl', ['--user', 'daemon-reload']);
|
|
307
415
|
}
|
|
308
416
|
else if (os === 'win32') {
|
|
309
417
|
await run('schtasks', ['/Delete', '/TN', SCHTASKS_TASK, '/F']);
|
|
418
|
+
await run('schtasks', ['/Delete', '/TN', SCHTASKS_TICK_TASK, '/F']);
|
|
310
419
|
await rm(syncWrapperPath(), { force: true });
|
|
420
|
+
await rm(tickWrapperPath(), { force: true });
|
|
311
421
|
}
|
|
312
422
|
}
|
|
313
423
|
catch {
|