vana-cli 0.23.9 → 0.23.11
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 +1 -1
- package/dist/cli/index.d.ts +16 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +124 -51
- package/dist/cli/index.js.map +1 -1
- package/dist/connectors/registry.d.ts.map +1 -1
- package/dist/connectors/registry.js +5 -2
- package/dist/connectors/registry.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -304,7 +304,7 @@ Make sure your HTML includes `<link rel="manifest" href="/manifest.json">`.
|
|
|
304
304
|
## Connectors
|
|
305
305
|
|
|
306
306
|
Available data connectors and their scopes (schema definitions):
|
|
307
|
-
[`
|
|
307
|
+
[`PDP-Connect/data-connectors/schemas`](https://github.com/PDP-Connect/data-connectors/tree/main/schemas)
|
|
308
308
|
|
|
309
309
|
## API Reference
|
|
310
310
|
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ConnectRenderer } from "./render/connect-renderer.js";
|
|
1
2
|
import { readCliState } from "../core/index.js";
|
|
2
3
|
import type { CliChannel, CliInstallMethod, CliStatus, SourceStatus } from "../core/cli-types.js";
|
|
3
4
|
import type { AvailableSource } from "../connectors/registry.js";
|
|
@@ -14,6 +15,7 @@ interface SourceMetadataMap {
|
|
|
14
15
|
}
|
|
15
16
|
type RenderTone = "accent" | "success" | "warning" | "error" | "muted" | "info";
|
|
16
17
|
export declare function runCli(argv?: string[]): Promise<number>;
|
|
18
|
+
export declare function withPhaseProgress<T>(renderer: ConnectRenderer | null, label: string, work: () => Promise<T>, delayMs?: number): Promise<T>;
|
|
17
19
|
export declare function formatUptime(seconds: number): string;
|
|
18
20
|
export declare function displaySource(source: string, labels?: SourceLabelMap): string;
|
|
19
21
|
export declare function humanizeIssue(message: string): string;
|
|
@@ -88,5 +90,19 @@ export declare function compareDatasetOrder(left: {
|
|
|
88
90
|
source: string;
|
|
89
91
|
}): number;
|
|
90
92
|
export declare function hasCollectedData(dataState: SourceStatus["dataState"] | null | undefined): boolean;
|
|
93
|
+
export declare class ScheduleTargetUnstableError extends Error {
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The argv a scheduler should run, every element an absolute path.
|
|
97
|
+
*
|
|
98
|
+
* launchd, cron and Task Scheduler inherit none of the shell's PATH, so the
|
|
99
|
+
* `vana` on `$PATH` is the wrong answer twice over: under npm it is a shim
|
|
100
|
+
* whose `#!/usr/bin/env node` shebang cannot find a version-managed node,
|
|
101
|
+
* and the scheduler only reports it as `env: node: No such file or
|
|
102
|
+
* directory`. Naming the interpreter and the script outright removes the
|
|
103
|
+
* lookup entirely.
|
|
104
|
+
*/
|
|
105
|
+
export declare function resolveScheduledCommand(installMethod?: CliInstallMethod, execPath?: string, entryScript?: string): string[];
|
|
106
|
+
export declare function generateLaunchdPlist(vanaCommand: string[], intervalSeconds: number): string;
|
|
91
107
|
export {};
|
|
92
108
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/cli/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/index.js
CHANGED
|
@@ -753,6 +753,54 @@ async function runTelemetryDisable(options) {
|
|
|
753
753
|
emit.success("Telemetry disabled.");
|
|
754
754
|
return 0;
|
|
755
755
|
}
|
|
756
|
+
/**
|
|
757
|
+
* How long a phase may stay silent before it gets a spinner.
|
|
758
|
+
*
|
|
759
|
+
* Connect is deliberately quiet when everything is warm, so a line appears
|
|
760
|
+
* only once a phase outlives this. Past it the terminal would otherwise sit
|
|
761
|
+
* blank while the CLI waits on the network, on Chromium, or on a page load.
|
|
762
|
+
*/
|
|
763
|
+
const PHASE_PROGRESS_DELAY_MS = 400;
|
|
764
|
+
function startPhaseProgress(renderer, label, delayMs = PHASE_PROGRESS_DELAY_MS) {
|
|
765
|
+
if (!renderer) {
|
|
766
|
+
return { settle() { }, abandon() { } };
|
|
767
|
+
}
|
|
768
|
+
let drawn = false;
|
|
769
|
+
let finished = false;
|
|
770
|
+
const timer = setTimeout(() => {
|
|
771
|
+
drawn = true;
|
|
772
|
+
renderer.scopeActive(label);
|
|
773
|
+
}, delayMs);
|
|
774
|
+
timer.unref?.();
|
|
775
|
+
return {
|
|
776
|
+
settle() {
|
|
777
|
+
if (finished)
|
|
778
|
+
return;
|
|
779
|
+
finished = true;
|
|
780
|
+
clearTimeout(timer);
|
|
781
|
+
if (drawn)
|
|
782
|
+
renderer.scopeDone(label);
|
|
783
|
+
},
|
|
784
|
+
abandon() {
|
|
785
|
+
if (finished)
|
|
786
|
+
return;
|
|
787
|
+
finished = true;
|
|
788
|
+
clearTimeout(timer);
|
|
789
|
+
},
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
export async function withPhaseProgress(renderer, label, work, delayMs = PHASE_PROGRESS_DELAY_MS) {
|
|
793
|
+
const progress = startPhaseProgress(renderer, label, delayMs);
|
|
794
|
+
try {
|
|
795
|
+
const result = await work();
|
|
796
|
+
progress.settle();
|
|
797
|
+
return result;
|
|
798
|
+
}
|
|
799
|
+
catch (error) {
|
|
800
|
+
progress.abandon();
|
|
801
|
+
throw error;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
756
804
|
async function runConnect(rawSource, options) {
|
|
757
805
|
const source = rawSource.toLowerCase();
|
|
758
806
|
const runtime = new ManagedPlaywrightRuntime();
|
|
@@ -768,7 +816,7 @@ async function runConnect(rawSource, options) {
|
|
|
768
816
|
try {
|
|
769
817
|
// Title
|
|
770
818
|
renderer?.title(displayName);
|
|
771
|
-
const target = await detectPersonalServerTarget();
|
|
819
|
+
const target = await withPhaseProgress(renderer, "Finding your Personal Server", () => detectPersonalServerTarget());
|
|
772
820
|
// --- Phase 1: Runtime check (silent if installed) ---
|
|
773
821
|
if (runtime.state !== "installed") {
|
|
774
822
|
if (options.noInput) {
|
|
@@ -809,7 +857,7 @@ async function runConnect(rawSource, options) {
|
|
|
809
857
|
trackActiveTelemetryEvent("runtime_install_started", { source });
|
|
810
858
|
let installResult;
|
|
811
859
|
try {
|
|
812
|
-
installResult = await runtime.ensureInstalled(Boolean(options.yes));
|
|
860
|
+
installResult = await withPhaseProgress(renderer, "Installing browser engine (one time, ~150MB)", () => runtime.ensureInstalled(Boolean(options.yes)));
|
|
813
861
|
}
|
|
814
862
|
catch (error) {
|
|
815
863
|
trackActiveTelemetryEvent("runtime_install_failed", {
|
|
@@ -824,7 +872,6 @@ async function runConnect(rawSource, options) {
|
|
|
824
872
|
runtime: installResult.runtime,
|
|
825
873
|
logPath: installResult.logPath,
|
|
826
874
|
});
|
|
827
|
-
renderer?.scopeDone("Runtime ready");
|
|
828
875
|
}
|
|
829
876
|
else {
|
|
830
877
|
emit.event({
|
|
@@ -837,7 +884,7 @@ async function runConnect(rawSource, options) {
|
|
|
837
884
|
const currentVersion = preState.sources[source]?.connectorVersion;
|
|
838
885
|
let fetched;
|
|
839
886
|
try {
|
|
840
|
-
fetched = await runtime.fetchConnector(source, currentVersion);
|
|
887
|
+
fetched = await withPhaseProgress(renderer, `Getting the ${displayName} connector`, () => runtime.fetchConnector(source, currentVersion));
|
|
841
888
|
}
|
|
842
889
|
catch (firstError) {
|
|
843
890
|
const firstMessage = firstError instanceof Error ? firstError.message : "";
|
|
@@ -850,7 +897,7 @@ async function runConnect(rawSource, options) {
|
|
|
850
897
|
const cacheDir = getConnectorCacheDir();
|
|
851
898
|
const sourceCacheDir = path.join(cacheDir, source);
|
|
852
899
|
await fsp.rm(sourceCacheDir, { recursive: true, force: true });
|
|
853
|
-
const resolution = await fetchConnectorToCache(source, cacheDir, undefined);
|
|
900
|
+
const resolution = await withPhaseProgress(renderer, "Cached connector looks stale, downloading a fresh one", () => fetchConnectorToCache(source, cacheDir, undefined));
|
|
854
901
|
fetched = {
|
|
855
902
|
connectorPath: resolution.connectorPath,
|
|
856
903
|
logPath: "",
|
|
@@ -870,7 +917,8 @@ async function runConnect(rawSource, options) {
|
|
|
870
917
|
lastLogPath: getErrorLogPath(retryError),
|
|
871
918
|
});
|
|
872
919
|
renderer?.fail(`${displayName} connector could not be verified.`);
|
|
873
|
-
renderer?.detail(
|
|
920
|
+
renderer?.detail(message);
|
|
921
|
+
renderer?.detail(`Try again later, or report: https://github.com/PDP-Connect/data-connectors/issues`);
|
|
874
922
|
emit.event({
|
|
875
923
|
type: "outcome",
|
|
876
924
|
status: CliOutcomeStatus.CONNECTOR_UNAVAILABLE,
|
|
@@ -967,12 +1015,17 @@ async function runConnect(rawSource, options) {
|
|
|
967
1015
|
let collectedResult = false;
|
|
968
1016
|
let blockedByRequiredInput = false;
|
|
969
1017
|
let ingestScopeResults;
|
|
1018
|
+
// Chromium start-up and the first page load happen before a connector
|
|
1019
|
+
// reports anything, which is the longest silent stretch of a warm run.
|
|
1020
|
+
const launchProgress = startPhaseProgress(renderer, `Opening ${displayName} in a browser`);
|
|
970
1021
|
// In IPC mode (--ipc), don’t provide an interactive callback.
|
|
971
1022
|
// The runtime will write a pending-input file and poll for the
|
|
972
1023
|
// response, letting an external agent handle credential collection.
|
|
973
1024
|
const interactiveCallback = options.ipc
|
|
974
1025
|
? undefined
|
|
975
1026
|
: async (needInput) => {
|
|
1027
|
+
// Settle first: a spinner repaint would overwrite the prompt.
|
|
1028
|
+
launchProgress.settle();
|
|
976
1029
|
renderer?.pauseForPrompt();
|
|
977
1030
|
// Show connector’s prompt message
|
|
978
1031
|
if (renderer) {
|
|
@@ -1019,6 +1072,10 @@ async function runConnect(rawSource, options) {
|
|
|
1019
1072
|
if (event.logPath) {
|
|
1020
1073
|
runLogPath = event.logPath;
|
|
1021
1074
|
}
|
|
1075
|
+
// status-update draws nothing, so it is not proof the browser is up.
|
|
1076
|
+
if (event.type !== "status-update") {
|
|
1077
|
+
launchProgress.settle();
|
|
1078
|
+
}
|
|
1022
1079
|
if (pendingExitCode !== null && event.type !== "collection-complete") {
|
|
1023
1080
|
continue;
|
|
1024
1081
|
}
|
|
@@ -1157,7 +1214,7 @@ async function runConnect(rawSource, options) {
|
|
|
1157
1214
|
}
|
|
1158
1215
|
collectedResult = true;
|
|
1159
1216
|
resultPath = event.resultPath;
|
|
1160
|
-
const ingestEvents = await ingestResult(resolution.source, resultPath, target);
|
|
1217
|
+
const ingestEvents = await withPhaseProgress(renderer, "Saving to your Personal Server", () => ingestResult(resolution.source, resultPath, target));
|
|
1161
1218
|
for (const ingestEvent of ingestEvents) {
|
|
1162
1219
|
emit.event(ingestEvent);
|
|
1163
1220
|
}
|
|
@@ -1200,6 +1257,7 @@ async function runConnect(rawSource, options) {
|
|
|
1200
1257
|
}));
|
|
1201
1258
|
}
|
|
1202
1259
|
}
|
|
1260
|
+
launchProgress.settle();
|
|
1203
1261
|
if (pendingExitCode !== null && !collectedResult) {
|
|
1204
1262
|
return pendingExitCode;
|
|
1205
1263
|
}
|
|
@@ -1266,8 +1324,7 @@ async function runConnect(rawSource, options) {
|
|
|
1266
1324
|
else {
|
|
1267
1325
|
successSummary = `Collected your ${displayName} data and saved it locally.`;
|
|
1268
1326
|
}
|
|
1269
|
-
|
|
1270
|
-
await maybeAutoSchedule(emit, options).catch(() => { });
|
|
1327
|
+
const hasSchedule = (await getExistingScheduleInterval().catch(() => null)) !== null;
|
|
1271
1328
|
// --- Phase 7: Success summary ---
|
|
1272
1329
|
renderer?.success(`Connected ${displayName}.`);
|
|
1273
1330
|
renderer?.detail(successSummary);
|
|
@@ -1297,6 +1354,9 @@ async function runConnect(rawSource, options) {
|
|
|
1297
1354
|
else {
|
|
1298
1355
|
renderer?.next(`vana data show ${source}`);
|
|
1299
1356
|
}
|
|
1357
|
+
if (!hasSchedule) {
|
|
1358
|
+
renderer?.detail("Keep this fresh automatically — run `vana schedule add` to collect on a schedule.");
|
|
1359
|
+
}
|
|
1300
1360
|
// Suggest skills if not yet installed
|
|
1301
1361
|
const installedSkills = await readInstalledSkills();
|
|
1302
1362
|
if (installedSkills.length === 0) {
|
|
@@ -4087,6 +4147,15 @@ const LAUNCHD_LABEL = "com.vana.collect";
|
|
|
4087
4147
|
const LAUNCHD_PLIST_PATH = path.join(os.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
4088
4148
|
const CRONTAB_MARKER = "# vana-scheduled-collection";
|
|
4089
4149
|
const WINDOWS_TASK_NAME = "VanaScheduledCollection";
|
|
4150
|
+
function escapeXml(value) {
|
|
4151
|
+
return value
|
|
4152
|
+
.replace(/&/g, "&")
|
|
4153
|
+
.replace(/</g, "<")
|
|
4154
|
+
.replace(/>/g, ">");
|
|
4155
|
+
}
|
|
4156
|
+
function quoteForShell(value) {
|
|
4157
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
4158
|
+
}
|
|
4090
4159
|
function parseIntervalSeconds(interval) {
|
|
4091
4160
|
const lower = interval.toLowerCase().trim();
|
|
4092
4161
|
const match = /^(\d+)\s*(h|d|m|w)$/i.exec(lower);
|
|
@@ -4116,23 +4185,32 @@ function formatIntervalHuman(seconds) {
|
|
|
4116
4185
|
return `${Math.round(seconds / 3600)}h`;
|
|
4117
4186
|
return `${Math.round(seconds / 86400)}d`;
|
|
4118
4187
|
}
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4188
|
+
export class ScheduleTargetUnstableError extends Error {
|
|
4189
|
+
}
|
|
4190
|
+
/**
|
|
4191
|
+
* The argv a scheduler should run, every element an absolute path.
|
|
4192
|
+
*
|
|
4193
|
+
* launchd, cron and Task Scheduler inherit none of the shell's PATH, so the
|
|
4194
|
+
* `vana` on `$PATH` is the wrong answer twice over: under npm it is a shim
|
|
4195
|
+
* whose `#!/usr/bin/env node` shebang cannot find a version-managed node,
|
|
4196
|
+
* and the scheduler only reports it as `env: node: No such file or
|
|
4197
|
+
* directory`. Naming the interpreter and the script outright removes the
|
|
4198
|
+
* lookup entirely.
|
|
4199
|
+
*/
|
|
4200
|
+
export function resolveScheduledCommand(installMethod = getCliInstallMethod(), execPath = process.execPath, entryScript = process.argv[1]) {
|
|
4201
|
+
// A SEA binary is its own interpreter; there is no script to pass.
|
|
4122
4202
|
if (installMethod === "homebrew" || installMethod === "installer") {
|
|
4123
|
-
return
|
|
4203
|
+
return [execPath];
|
|
4124
4204
|
}
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
const cmd = process.platform === "win32" ? "where vana" : "which vana";
|
|
4128
|
-
const result = execSync(cmd, { encoding: "utf8" }).trim();
|
|
4129
|
-
// `where` on Windows may return multiple lines; take the first
|
|
4130
|
-
return result.split("\n")[0].trim();
|
|
4205
|
+
if (!entryScript || !path.isAbsolute(entryScript)) {
|
|
4206
|
+
throw new ScheduleTargetUnstableError("Could not work out which file to schedule.");
|
|
4131
4207
|
}
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4208
|
+
// npx unpacks into a cache npm is free to evict, so a schedule pointing
|
|
4209
|
+
// there works until it silently does not.
|
|
4210
|
+
if (normalizePathForMatch(entryScript).includes("/_npx/")) {
|
|
4211
|
+
throw new ScheduleTargetUnstableError("This CLI is running from an npx cache, which npm may delete at any time.");
|
|
4135
4212
|
}
|
|
4213
|
+
return [execPath, entryScript];
|
|
4136
4214
|
}
|
|
4137
4215
|
async function getExistingScheduleInterval() {
|
|
4138
4216
|
if (process.platform === "darwin") {
|
|
@@ -4179,28 +4257,11 @@ async function getExistingScheduleInterval() {
|
|
|
4179
4257
|
}
|
|
4180
4258
|
return null;
|
|
4181
4259
|
}
|
|
4182
|
-
|
|
4183
|
-
// Skip if --no-input (detached/agent context shouldn't create schedules)
|
|
4184
|
-
if (options.noInput)
|
|
4185
|
-
return;
|
|
4186
|
-
// Skip unsupported platforms
|
|
4187
|
-
if (!["darwin", "linux", "win32"].includes(process.platform))
|
|
4188
|
-
return;
|
|
4189
|
-
const existing = await getExistingScheduleInterval();
|
|
4190
|
-
if (existing !== null)
|
|
4191
|
-
return; // Schedule already exists
|
|
4192
|
-
await runScheduleAdd("daily", { json: false, quiet: true });
|
|
4193
|
-
emit.detail("Auto-scheduled daily collection.");
|
|
4194
|
-
}
|
|
4195
|
-
function generateLaunchdPlist(vanaBinary, intervalSeconds) {
|
|
4260
|
+
export function generateLaunchdPlist(vanaCommand, intervalSeconds) {
|
|
4196
4261
|
const logsPath = path.join(getLogsDir(), "schedule.log");
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
.split(" ")
|
|
4201
|
-
.map((arg) => ` <string>${arg}</string>`)
|
|
4202
|
-
.join("\n")
|
|
4203
|
-
: ` <string>${vanaBinary}</string>`;
|
|
4262
|
+
const programArgs = vanaCommand
|
|
4263
|
+
.map((arg) => ` <string>${escapeXml(arg)}</string>`)
|
|
4264
|
+
.join("\n");
|
|
4204
4265
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
4205
4266
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
4206
4267
|
<plist version="1.0">
|
|
@@ -4218,31 +4279,43 @@ ${programArgs}
|
|
|
4218
4279
|
<key>StartInterval</key>
|
|
4219
4280
|
<integer>${intervalSeconds}</integer>
|
|
4220
4281
|
<key>StandardOutPath</key>
|
|
4221
|
-
<string>${logsPath}</string>
|
|
4282
|
+
<string>${escapeXml(logsPath)}</string>
|
|
4222
4283
|
<key>StandardErrorPath</key>
|
|
4223
|
-
<string>${logsPath}</string>
|
|
4284
|
+
<string>${escapeXml(logsPath)}</string>
|
|
4224
4285
|
<key>RunAtLoad</key>
|
|
4225
4286
|
<true/>
|
|
4226
4287
|
</dict>
|
|
4227
4288
|
</plist>
|
|
4228
4289
|
`;
|
|
4229
4290
|
}
|
|
4230
|
-
function generateCrontabEntry(
|
|
4291
|
+
function generateCrontabEntry(vanaCommand, intervalHours) {
|
|
4231
4292
|
const logsPath = path.join(getLogsDir(), "schedule.log");
|
|
4232
4293
|
const hourExpr = intervalHours >= 24 ? "0" : `0`;
|
|
4233
4294
|
const dayExpr = "*";
|
|
4234
4295
|
const hourInterval = intervalHours >= 24 ? "0" : intervalHours >= 1 ? `*/${intervalHours}` : "*";
|
|
4235
|
-
|
|
4296
|
+
const program = vanaCommand.map((arg) => quoteForShell(arg)).join(" ");
|
|
4297
|
+
return `${hourExpr} ${hourInterval} ${dayExpr} * * ${program} collect --all --quiet --no-input >> ${quoteForShell(logsPath)} 2>&1 ${CRONTAB_MARKER}`;
|
|
4236
4298
|
}
|
|
4237
4299
|
async function runScheduleAdd(interval, options) {
|
|
4238
4300
|
const emit = createEmitter(options);
|
|
4239
4301
|
const intervalSeconds = parseIntervalSeconds(interval);
|
|
4240
4302
|
const intervalLabel = formatIntervalHuman(intervalSeconds);
|
|
4241
|
-
|
|
4303
|
+
let vanaCommand;
|
|
4304
|
+
try {
|
|
4305
|
+
vanaCommand = resolveScheduledCommand();
|
|
4306
|
+
}
|
|
4307
|
+
catch (error) {
|
|
4308
|
+
if (!(error instanceof ScheduleTargetUnstableError)) {
|
|
4309
|
+
throw error;
|
|
4310
|
+
}
|
|
4311
|
+
emit.info(`Cannot schedule collection. ${error.message}`);
|
|
4312
|
+
emit.detail("Install the CLI first (`npm install -g vana-cli`), then run `vana schedule add` from that install.");
|
|
4313
|
+
return 1;
|
|
4314
|
+
}
|
|
4242
4315
|
await fsp.mkdir(getLogsDir(), { recursive: true });
|
|
4243
4316
|
if (process.platform === "darwin") {
|
|
4244
4317
|
// macOS: launchd
|
|
4245
|
-
const plist = generateLaunchdPlist(
|
|
4318
|
+
const plist = generateLaunchdPlist(vanaCommand, intervalSeconds);
|
|
4246
4319
|
const plistDir = path.dirname(LAUNCHD_PLIST_PATH);
|
|
4247
4320
|
await fsp.mkdir(plistDir, { recursive: true });
|
|
4248
4321
|
// Unload existing if present
|
|
@@ -4279,7 +4352,7 @@ async function runScheduleAdd(interval, options) {
|
|
|
4279
4352
|
// Linux: cron doesn't defer missed jobs (unlike launchd), so we run
|
|
4280
4353
|
// hourly and let isCollectionDue() filter per-source. This way a
|
|
4281
4354
|
// missed 2am tick self-heals at 3am instead of waiting 24h.
|
|
4282
|
-
const entry = generateCrontabEntry(
|
|
4355
|
+
const entry = generateCrontabEntry(vanaCommand, 1);
|
|
4283
4356
|
try {
|
|
4284
4357
|
// Read existing crontab, filter out old vana entries, add new one
|
|
4285
4358
|
let existing = "";
|
|
@@ -4321,7 +4394,7 @@ async function runScheduleAdd(interval, options) {
|
|
|
4321
4394
|
if (process.platform === "win32") {
|
|
4322
4395
|
// Windows: Task Scheduler
|
|
4323
4396
|
const intervalMinutes = Math.max(1, Math.round(intervalSeconds / 60));
|
|
4324
|
-
const trCmd = `\\"${
|
|
4397
|
+
const trCmd = `${vanaCommand.map((arg) => `\\"${arg}\\"`).join(" ")} collect --all --quiet --no-input`;
|
|
4325
4398
|
try {
|
|
4326
4399
|
try {
|
|
4327
4400
|
execSync(`schtasks /Delete /TN "${WINDOWS_TASK_NAME}" /F 2>nul`, {
|