standout 0.6.0 → 0.6.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 +6 -4
- package/dist/cli.js +59 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,10 +14,12 @@ to build a full profile interactively.
|
|
|
14
14
|
|
|
15
15
|
## Runs monthly, automatically
|
|
16
16
|
|
|
17
|
-
After a full run, standout schedules a monthly refresh
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
After a full run, standout schedules a monthly refresh so your stats never
|
|
18
|
+
decay — local session logs only cover ~30 days, and the refresh keeps your
|
|
19
|
+
full history intact. It fires at the first hour your machine is awake in each
|
|
20
|
+
new month (an hourly launchd/cron tick guarded by a month stamp in
|
|
21
|
+
`~/.standout/last-refresh`, so sleeping through the 1st never skips a month).
|
|
22
|
+
Uses launchd on macOS and cron on Linux; not available on Windows.
|
|
21
23
|
|
|
22
24
|
```bash
|
|
23
25
|
npx standout schedule status # is it on? command, log path, last run
|
package/dist/cli.js
CHANGED
|
@@ -5203,7 +5203,14 @@ function escapeXml(text) {
|
|
|
5203
5203
|
function unescapeXml(text) {
|
|
5204
5204
|
return text.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
5205
5205
|
}
|
|
5206
|
-
function
|
|
5206
|
+
function buildGuardedCommand(runCommand) {
|
|
5207
|
+
return `STAMP="$HOME/.standout/last-refresh"; MONTH=$(date +%Y-%m); [ "$(cat "$STAMP" 2>/dev/null)" = "$MONTH" ] || { mkdir -p "$HOME/.standout"; STANDOUT_SCHEDULED=1 ${runCommand} && echo "$MONTH" > "$STAMP"; }`;
|
|
5208
|
+
}
|
|
5209
|
+
function extractRunCommand(guarded) {
|
|
5210
|
+
const m = guarded.match(/STANDOUT_SCHEDULED=1 (.*?) && echo "\$MONTH"/);
|
|
5211
|
+
return m ? m[1] : null;
|
|
5212
|
+
}
|
|
5213
|
+
function buildLaunchdPlist(guardedCommand, logPath) {
|
|
5207
5214
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
5208
5215
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
5209
5216
|
<plist version="1.0">
|
|
@@ -5213,22 +5220,18 @@ function buildLaunchdPlist(command, logPath) {
|
|
|
5213
5220
|
<array>
|
|
5214
5221
|
<string>/bin/zsh</string>
|
|
5215
5222
|
<string>-lc</string>
|
|
5216
|
-
<string>${escapeXml(
|
|
5223
|
+
<string>${escapeXml(guardedCommand)}</string>
|
|
5217
5224
|
</array>
|
|
5218
|
-
<key>
|
|
5219
|
-
<dict>
|
|
5220
|
-
<key>Day</key><integer>1</integer>
|
|
5221
|
-
<key>Hour</key><integer>10</integer>
|
|
5222
|
-
<key>Minute</key><integer>0</integer>
|
|
5223
|
-
</dict>
|
|
5225
|
+
<key>StartInterval</key><integer>3600</integer>
|
|
5224
5226
|
<key>StandardOutPath</key><string>${escapeXml(logPath)}</string>
|
|
5225
5227
|
<key>StandardErrorPath</key><string>${escapeXml(logPath)}</string>
|
|
5226
5228
|
</dict>
|
|
5227
5229
|
</plist>
|
|
5228
5230
|
`;
|
|
5229
5231
|
}
|
|
5230
|
-
function buildCronLine(
|
|
5231
|
-
|
|
5232
|
+
function buildCronLine(guardedCommand, logPath) {
|
|
5233
|
+
const escaped = guardedCommand.replace(/%/g, "\\%");
|
|
5234
|
+
return `0 * * * * /bin/bash -lc '${escaped}' >> "${logPath}" 2>&1 ${CRON_MARKER}`;
|
|
5232
5235
|
}
|
|
5233
5236
|
function stripCronEntries(crontab) {
|
|
5234
5237
|
return crontab.split("\n").filter((line) => !line.includes(CRON_MARKER)).join("\n");
|
|
@@ -5261,6 +5264,23 @@ function writeOptOut() {
|
|
|
5261
5264
|
function clearOptOut() {
|
|
5262
5265
|
rmSync2(optOutPath(), { force: true });
|
|
5263
5266
|
}
|
|
5267
|
+
function refreshStampPath() {
|
|
5268
|
+
return join6(stateDir(), "last-refresh");
|
|
5269
|
+
}
|
|
5270
|
+
function readRefreshStamp() {
|
|
5271
|
+
try {
|
|
5272
|
+
return readFileSync6(refreshStampPath(), "utf8").trim() || null;
|
|
5273
|
+
} catch {
|
|
5274
|
+
return null;
|
|
5275
|
+
}
|
|
5276
|
+
}
|
|
5277
|
+
function writeRefreshStamp() {
|
|
5278
|
+
mkdirSync2(stateDir(), { recursive: true });
|
|
5279
|
+
const now = /* @__PURE__ */ new Date();
|
|
5280
|
+
const month = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
5281
|
+
writeFileSync(refreshStampPath(), `${month}
|
|
5282
|
+
`);
|
|
5283
|
+
}
|
|
5264
5284
|
function launchctl(args) {
|
|
5265
5285
|
const res = spawnSync2("launchctl", args, { encoding: "utf8" });
|
|
5266
5286
|
return {
|
|
@@ -5286,10 +5306,11 @@ function writeCrontab(content) {
|
|
|
5286
5306
|
}
|
|
5287
5307
|
}
|
|
5288
5308
|
function installJob(command) {
|
|
5309
|
+
const guarded = buildGuardedCommand(command);
|
|
5289
5310
|
if (process.platform === "darwin") {
|
|
5290
5311
|
const path2 = plistPath();
|
|
5291
5312
|
mkdirSync2(join6(homedir6(), "Library", "LaunchAgents"), { recursive: true });
|
|
5292
|
-
writeFileSync(path2, buildLaunchdPlist(
|
|
5313
|
+
writeFileSync(path2, buildLaunchdPlist(guarded, schedLogPath()));
|
|
5293
5314
|
launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
|
|
5294
5315
|
const res = launchctl(["bootstrap", `gui/${uid()}`, path2]);
|
|
5295
5316
|
if (res.status !== 0) {
|
|
@@ -5301,10 +5322,11 @@ function installJob(command) {
|
|
|
5301
5322
|
mkdirSync2(stateDir(), { recursive: true });
|
|
5302
5323
|
const stripped = stripCronEntries(readCrontab());
|
|
5303
5324
|
const base = stripped === "" || stripped.endsWith("\n") ? stripped : stripped + "\n";
|
|
5304
|
-
writeCrontab(base + buildCronLine(
|
|
5325
|
+
writeCrontab(base + buildCronLine(guarded, schedLogPath()) + "\n");
|
|
5305
5326
|
}
|
|
5327
|
+
writeRefreshStamp();
|
|
5306
5328
|
}
|
|
5307
|
-
function
|
|
5329
|
+
function installedShellCommand() {
|
|
5308
5330
|
if (process.platform === "darwin") {
|
|
5309
5331
|
if (!existsSync6(plistPath())) return null;
|
|
5310
5332
|
const xml = readFileSync6(plistPath(), "utf8");
|
|
@@ -5314,7 +5336,7 @@ function installedCommand() {
|
|
|
5314
5336
|
if (process.platform === "linux") {
|
|
5315
5337
|
const line = readCrontab().split("\n").find((l) => l.includes(CRON_MARKER));
|
|
5316
5338
|
const m = line?.match(/-lc '([^']*)'/);
|
|
5317
|
-
return m ? m[1] : null;
|
|
5339
|
+
return m ? m[1].replace(/\\%/g, "%") : null;
|
|
5318
5340
|
}
|
|
5319
5341
|
return null;
|
|
5320
5342
|
}
|
|
@@ -5330,7 +5352,7 @@ async function installSchedule(slug, flags) {
|
|
|
5330
5352
|
installJob(command);
|
|
5331
5353
|
process.stderr.write(
|
|
5332
5354
|
`
|
|
5333
|
-
${chalk.hex(ACCENT)("\u2713")} ${chalk.white("monthly refresh scheduled")} ${chalk.dim("(
|
|
5355
|
+
${chalk.hex(ACCENT)("\u2713")} ${chalk.white("monthly refresh scheduled")} ${chalk.dim("(first awake hour of each month)")}
|
|
5334
5356
|
` + chalk.dim(` runs: ${command}
|
|
5335
5357
|
`) + chalk.dim(` log: ${schedLogPath()}
|
|
5336
5358
|
`) + chalk.dim(" disable anytime with `npx standout schedule off`\n\n")
|
|
@@ -5377,19 +5399,21 @@ async function scheduleStatus() {
|
|
|
5377
5399
|
);
|
|
5378
5400
|
return;
|
|
5379
5401
|
}
|
|
5380
|
-
const
|
|
5381
|
-
if (!
|
|
5402
|
+
const shell = installedShellCommand();
|
|
5403
|
+
if (!shell) {
|
|
5382
5404
|
process.stderr.write(
|
|
5383
5405
|
`
|
|
5384
5406
|
${chalk.white("no monthly schedule installed")}` + (isOptedOut() ? chalk.dim(" (opted out)") : "") + "\n" + chalk.dim(" enable with `npx standout schedule`\n\n")
|
|
5385
5407
|
);
|
|
5386
5408
|
return;
|
|
5387
5409
|
}
|
|
5410
|
+
const stamp = readRefreshStamp();
|
|
5388
5411
|
process.stderr.write(
|
|
5389
5412
|
`
|
|
5390
|
-
${chalk.hex(ACCENT)("\u2713")} ${chalk.white("monthly refresh is on")} ${chalk.dim("(
|
|
5391
|
-
` + chalk.dim(` runs: ${
|
|
5413
|
+
${chalk.hex(ACCENT)("\u2713")} ${chalk.white("monthly refresh is on")} ${chalk.dim("(first awake hour of each month)")}
|
|
5414
|
+
` + chalk.dim(` runs: ${extractRunCommand(shell) ?? shell}
|
|
5392
5415
|
`) + chalk.dim(` log: ${schedLogPath()}
|
|
5416
|
+
`) + chalk.dim(` last refreshed month: ${stamp ?? "never"}
|
|
5393
5417
|
`)
|
|
5394
5418
|
);
|
|
5395
5419
|
if (process.platform === "darwin") {
|
|
@@ -5418,24 +5442,28 @@ async function scheduleStatus() {
|
|
|
5418
5442
|
}
|
|
5419
5443
|
process.stderr.write("\n");
|
|
5420
5444
|
}
|
|
5445
|
+
function autoRefreshNotice(headline) {
|
|
5446
|
+
const optOut = "npx standout schedule off";
|
|
5447
|
+
return `
|
|
5448
|
+
${chalk.hex(ACCENT)("\u27F3")} ${chalk.bold.white(headline)}
|
|
5449
|
+
${chalk.white("standout re-runs once a month so your wrapped keeps building")}
|
|
5450
|
+
${chalk.white("(because local logs only keep ~30 days of history).")}
|
|
5451
|
+
${chalk.white("opt out anytime:")} ${chalk.hex(ACCENT)(optOut)}
|
|
5452
|
+
|
|
5453
|
+
`;
|
|
5454
|
+
}
|
|
5421
5455
|
async function maybeAutoInstallSchedule(slug) {
|
|
5422
5456
|
try {
|
|
5457
|
+
if (process.env.STANDOUT_SCHEDULED === "1") return;
|
|
5423
5458
|
if (!supportedPlatform() || isOptedOut()) return;
|
|
5424
5459
|
const command = buildRunCommand(slug, []);
|
|
5425
|
-
if (
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
" monthly refresh is on \u2014 `npx standout schedule off` to disable\n\n"
|
|
5429
|
-
)
|
|
5430
|
-
);
|
|
5460
|
+
if (installedShellCommand() === buildGuardedCommand(command)) {
|
|
5461
|
+
writeRefreshStamp();
|
|
5462
|
+
process.stderr.write(autoRefreshNotice("auto-refresh is on"));
|
|
5431
5463
|
return;
|
|
5432
5464
|
}
|
|
5433
5465
|
installJob(command);
|
|
5434
|
-
process.stderr.write(
|
|
5435
|
-
` ${chalk.hex(ACCENT)("\u2713")} ${chalk.white("monthly refresh scheduled")} ${chalk.dim("(1st of each month) \u2014 disable with `npx standout schedule off`")}
|
|
5436
|
-
|
|
5437
|
-
`
|
|
5438
|
-
);
|
|
5466
|
+
process.stderr.write(autoRefreshNotice("auto-refresh scheduled"));
|
|
5439
5467
|
} catch (error) {
|
|
5440
5468
|
process.stderr.write(
|
|
5441
5469
|
chalk.dim(
|
|
@@ -7703,6 +7731,7 @@ async function runAgent(jobId) {
|
|
|
7703
7731
|
`
|
|
7704
7732
|
);
|
|
7705
7733
|
}
|
|
7734
|
+
await maybeAutoInstallSchedule(jobId);
|
|
7706
7735
|
await maybeShareWrapped(wrapped);
|
|
7707
7736
|
} else {
|
|
7708
7737
|
process.stderr.write(
|
|
@@ -7872,7 +7901,6 @@ async function main() {
|
|
|
7872
7901
|
} else {
|
|
7873
7902
|
if (jobId) process.env.STANDOUT_JOB_ID = jobId;
|
|
7874
7903
|
await runAgent(jobId);
|
|
7875
|
-
await maybeAutoInstallSchedule(jobId);
|
|
7876
7904
|
}
|
|
7877
7905
|
}
|
|
7878
7906
|
main().catch((error) => {
|