@pimmesz/afterburner 1.0.11 → 1.0.12
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/{chunk-Z3H5GIPM.js → chunk-4TD2KS3A.js} +108 -0
- package/dist/cli/index.js +268 -92
- package/dist/core/index.d.ts +58 -3
- package/dist/core/index.js +9 -1
- package/package.json +1 -1
|
@@ -148,6 +148,10 @@ var scheduleConfigSchema = z.object({
|
|
|
148
148
|
cron: z.string().min(1).default("17 */4 * * *"),
|
|
149
149
|
timezone: z.string().min(1).default("UTC")
|
|
150
150
|
}).prefault({});
|
|
151
|
+
var notifyConfigSchema = z.object({
|
|
152
|
+
/** Desktop banner per completed run, opt-in. The PR stays the primary signal. */
|
|
153
|
+
desktop: z.boolean().default(false)
|
|
154
|
+
}).prefault({});
|
|
151
155
|
var agentConfigSchema = z.object({
|
|
152
156
|
backend: z.enum(["dry-run", "claude-code", "api-key"]).default("dry-run"),
|
|
153
157
|
modelByCategory: z.partialRecord(categoryEnum, modelId).default({}).transform((m) => ({ ...DEFAULT_MODEL_BY_CATEGORY, ...m })),
|
|
@@ -174,6 +178,7 @@ var configSchema = z.object({
|
|
|
174
178
|
budget: budgetConfigSchema,
|
|
175
179
|
schedule: scheduleConfigSchema,
|
|
176
180
|
agent: agentConfigSchema,
|
|
181
|
+
notify: notifyConfigSchema,
|
|
177
182
|
taskCategories: taskCategoriesConfigSchema
|
|
178
183
|
}).superRefine((config, ctx) => {
|
|
179
184
|
for (const [category, model] of Object.entries(config.agent.modelByCategory)) {
|
|
@@ -693,6 +698,85 @@ function systemdQuote(value) {
|
|
|
693
698
|
return /[\s"']/.test(value) ? `"${value.replaceAll('"', '\\"')}"` : value;
|
|
694
699
|
}
|
|
695
700
|
|
|
701
|
+
// src/core/notify/desktop.ts
|
|
702
|
+
import { execFile } from "child_process";
|
|
703
|
+
import { promisify } from "util";
|
|
704
|
+
var run = promisify(execFile);
|
|
705
|
+
var EXEC_TIMEOUT_MS = 1e4;
|
|
706
|
+
var DesktopNotifier = class {
|
|
707
|
+
exec;
|
|
708
|
+
has;
|
|
709
|
+
platform;
|
|
710
|
+
constructor(deps = {}) {
|
|
711
|
+
this.exec = deps.exec ?? ((command, args, opts) => run(command, args, { timeout: EXEC_TIMEOUT_MS, ...opts }));
|
|
712
|
+
this.has = deps.has ?? commandExists;
|
|
713
|
+
this.platform = deps.platform ?? process.platform;
|
|
714
|
+
}
|
|
715
|
+
async notify(record) {
|
|
716
|
+
const title = "Afterburner";
|
|
717
|
+
const subtitle = `${shortRepo(record.repoUrl)} \xB7 ${record.outcome}`;
|
|
718
|
+
const body = record.title;
|
|
719
|
+
try {
|
|
720
|
+
switch (this.platform) {
|
|
721
|
+
case "darwin":
|
|
722
|
+
await this.notifyMac(title, subtitle, body, record.prUrl);
|
|
723
|
+
break;
|
|
724
|
+
case "linux":
|
|
725
|
+
await this.notifyLinux(title, `${subtitle} \u2014 ${body}`);
|
|
726
|
+
break;
|
|
727
|
+
case "win32":
|
|
728
|
+
await this.notifyWindows(title, `${subtitle} \u2014 ${body}`);
|
|
729
|
+
break;
|
|
730
|
+
}
|
|
731
|
+
} catch {
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
async notifyMac(title, subtitle, body, prUrl) {
|
|
735
|
+
if (await this.has("terminal-notifier")) {
|
|
736
|
+
const args = ["-title", title, "-subtitle", subtitle, "-message", body, "-sound", "default"];
|
|
737
|
+
if (prUrl) args.push("-open", prUrl);
|
|
738
|
+
await this.exec("terminal-notifier", args);
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
const script = 'display notification (system attribute "AB_BODY") with title (system attribute "AB_TITLE") subtitle (system attribute "AB_SUBTITLE")';
|
|
742
|
+
await this.exec("osascript", ["-e", script], {
|
|
743
|
+
env: { ...process.env, AB_TITLE: title, AB_SUBTITLE: subtitle, AB_BODY: body }
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
async notifyLinux(title, body) {
|
|
747
|
+
await this.exec("notify-send", ["--app-name=afterburner", "--", title, body]);
|
|
748
|
+
}
|
|
749
|
+
async notifyWindows(title, body) {
|
|
750
|
+
const aumid = "{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\\WindowsPowerShell\\v1.0\\powershell.exe";
|
|
751
|
+
const script = [
|
|
752
|
+
"[Windows.UI.Notifications.ToastNotificationManager,Windows.UI.Notifications,ContentType=WindowsRuntime]>$null",
|
|
753
|
+
"[Windows.Data.Xml.Dom.XmlDocument,Windows.Data.Xml.Dom,ContentType=WindowsRuntime]>$null",
|
|
754
|
+
"$t=[System.Security.SecurityElement]::Escape($env:AB_TITLE)",
|
|
755
|
+
"$b=[System.Security.SecurityElement]::Escape($env:AB_BODY)",
|
|
756
|
+
'$x="<toast><visual><binding template=`"ToastGeneric`"><text>$t</text><text>$b</text></binding></visual></toast>"',
|
|
757
|
+
"$d=[Windows.Data.Xml.Dom.XmlDocument]::new();$d.LoadXml($x)",
|
|
758
|
+
`[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('${aumid}').Show([Windows.UI.Notifications.ToastNotification]::new($d))`
|
|
759
|
+
].join(";");
|
|
760
|
+
await this.exec("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
|
|
761
|
+
env: { ...process.env, AB_TITLE: title, AB_BODY: body }
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
};
|
|
765
|
+
function shortRepo(repoUrl) {
|
|
766
|
+
const trimmed = repoUrl.replace(/[/\\]+$/, "");
|
|
767
|
+
const last = trimmed.split(/[/\\]/).pop() ?? trimmed;
|
|
768
|
+
return last.replace(/\.git$/, "") || trimmed;
|
|
769
|
+
}
|
|
770
|
+
async function commandExists(command) {
|
|
771
|
+
const finder = process.platform === "win32" ? "where" : "which";
|
|
772
|
+
try {
|
|
773
|
+
await run(finder, [command]);
|
|
774
|
+
return true;
|
|
775
|
+
} catch {
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
696
780
|
// src/core/store/run-store.ts
|
|
697
781
|
import { appendFile, mkdir as mkdir2, readFile as readFile3 } from "fs/promises";
|
|
698
782
|
import { dirname as dirname2 } from "path";
|
|
@@ -734,6 +818,26 @@ var ConsoleNotifier = class {
|
|
|
734
818
|
}
|
|
735
819
|
};
|
|
736
820
|
|
|
821
|
+
// src/core/notify/factory.ts
|
|
822
|
+
var CompositeNotifier = class {
|
|
823
|
+
constructor(notifiers) {
|
|
824
|
+
this.notifiers = notifiers;
|
|
825
|
+
}
|
|
826
|
+
notifiers;
|
|
827
|
+
async notify(record) {
|
|
828
|
+
for (const notifier of this.notifiers) {
|
|
829
|
+
try {
|
|
830
|
+
await notifier.notify(record);
|
|
831
|
+
} catch {
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
function createNotifier(config) {
|
|
837
|
+
const consoleNotifier = new ConsoleNotifier();
|
|
838
|
+
return config.notify.desktop ? new CompositeNotifier([consoleNotifier, new DesktopNotifier()]) : consoleNotifier;
|
|
839
|
+
}
|
|
840
|
+
|
|
737
841
|
// src/core/orchestrator.ts
|
|
738
842
|
async function runOnce(deps) {
|
|
739
843
|
const { config } = deps;
|
|
@@ -1115,8 +1219,12 @@ export {
|
|
|
1115
1219
|
scheduleArtifactPaths,
|
|
1116
1220
|
generateScheduleArtifacts,
|
|
1117
1221
|
looksRemoteRepoUrl,
|
|
1222
|
+
DesktopNotifier,
|
|
1223
|
+
commandExists,
|
|
1118
1224
|
JsonlRunStore,
|
|
1119
1225
|
ConsoleNotifier,
|
|
1226
|
+
CompositeNotifier,
|
|
1227
|
+
createNotifier,
|
|
1120
1228
|
runOnce,
|
|
1121
1229
|
ApiKeyRunner,
|
|
1122
1230
|
taskFingerprint,
|
package/dist/cli/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
ConsoleNotifier,
|
|
4
3
|
JsonlRunStore,
|
|
5
4
|
ManualBudgetProvider,
|
|
6
5
|
budgetFromUsageCache,
|
|
7
6
|
claudeConfigDir,
|
|
7
|
+
commandExists,
|
|
8
8
|
createBudgetProvider,
|
|
9
|
+
createNotifier,
|
|
9
10
|
createRunner,
|
|
10
11
|
createSelector,
|
|
11
12
|
dataDir,
|
|
@@ -24,7 +25,7 @@ import {
|
|
|
24
25
|
shouldIgnite,
|
|
25
26
|
startWatch,
|
|
26
27
|
writeUsageCache
|
|
27
|
-
} from "../chunk-
|
|
28
|
+
} from "../chunk-4TD2KS3A.js";
|
|
28
29
|
import {
|
|
29
30
|
MCP_STUB_MESSAGE
|
|
30
31
|
} from "../chunk-2NSOEZWY.js";
|
|
@@ -35,7 +36,7 @@ import { Command } from "commander";
|
|
|
35
36
|
// package.json
|
|
36
37
|
var package_default = {
|
|
37
38
|
name: "@pimmesz/afterburner",
|
|
38
|
-
version: "1.0.
|
|
39
|
+
version: "1.0.12",
|
|
39
40
|
description: "Convert idle Claude subscription quota into shippable engineering work: budget-aware trigger, bounded task selection, PR-only output.",
|
|
40
41
|
license: "Apache-2.0",
|
|
41
42
|
publishConfig: {
|
|
@@ -277,9 +278,9 @@ function resolveCliEntry() {
|
|
|
277
278
|
|
|
278
279
|
// src/cli/commands/doctor.ts
|
|
279
280
|
function registerDoctor(program2, packageInfo2) {
|
|
280
|
-
program2.command("doctor").description("Check prerequisites and configuration; every failure prints an actionable fix").option("--config <path>", "path to a config file").option("--check-updates", "query the npm registry for the latest published version").action(
|
|
281
|
-
|
|
282
|
-
);
|
|
281
|
+
program2.command("doctor").description("Check prerequisites and configuration; every failure prints an actionable fix").option("--config <path>", "path to a config file").option("--check-updates", "query the npm registry for the latest published version").action(async (opts) => {
|
|
282
|
+
await runDoctor({ packageInfo: packageInfo2, configPath: opts.config, checkUpdates: opts.checkUpdates });
|
|
283
|
+
});
|
|
283
284
|
}
|
|
284
285
|
async function runDoctor(opts) {
|
|
285
286
|
console.log(`${deco(emoji.fuel)}${bold("afterburner doctor")}`);
|
|
@@ -325,6 +326,7 @@ async function runDoctor(opts) {
|
|
|
325
326
|
results.push(checkRepos(config, configPath));
|
|
326
327
|
results.push(...checkBackend(config));
|
|
327
328
|
results.push(await checkBudgetProvider(config));
|
|
329
|
+
if (config.notify.desktop) results.push(checkNotify());
|
|
328
330
|
}
|
|
329
331
|
results.push(await checkRunStoreWritable());
|
|
330
332
|
let failures = 0;
|
|
@@ -358,6 +360,7 @@ ${renderDoctorNextSteps({
|
|
|
358
360
|
})}`
|
|
359
361
|
);
|
|
360
362
|
process.exitCode = failures === 0 ? 0 : 1;
|
|
363
|
+
return { ok: failures === 0, config, configPath };
|
|
361
364
|
}
|
|
362
365
|
async function buildRunSummary(config) {
|
|
363
366
|
let budget = null;
|
|
@@ -414,10 +417,10 @@ function renderWhen(opts) {
|
|
|
414
417
|
const cron = config.schedule.cron;
|
|
415
418
|
const cadence = describeCadence(cron);
|
|
416
419
|
if (opts.scheduleInstalled === false) {
|
|
417
|
-
return `no afterburner schedule is installed \u2014 it only runs when you start it; \`afterburner schedule install\` would run it ${cadence}.`;
|
|
420
|
+
return `no afterburner schedule is installed \u2014 it only runs when you start it; \`afterburner schedule install\` would run it ${cadence} ${config.schedule.timezone}.`;
|
|
418
421
|
}
|
|
419
422
|
if (opts.scheduleInstalled === "unknown") {
|
|
420
|
-
return `${cadence}, once the entries from \`afterburner schedule install\` are active (not auto-detectable on this platform).`;
|
|
423
|
+
return `${cadence} ${config.schedule.timezone}, once the entries from \`afterburner schedule install\` are active (not auto-detectable on this platform).`;
|
|
421
424
|
}
|
|
422
425
|
const zone = opts.scheduleKind === "systemd-user" ? config.schedule.timezone : opts.systemTimezone;
|
|
423
426
|
const kindLabel = opts.scheduleKind === "systemd-user" ? "systemd" : "launchd";
|
|
@@ -446,12 +449,12 @@ function describeCadence(cron) {
|
|
|
446
449
|
} catch {
|
|
447
450
|
return `on cron '${cron}'`;
|
|
448
451
|
}
|
|
449
|
-
if (hours.length === 24) return `every hour at minute ${minute}`;
|
|
450
|
-
const step2 = (hours[1] ?? 0) - (hours[0] ?? 0);
|
|
451
|
-
const uniform = hours.length > 1 && hours[0] === 0 && 24 % step2 === 0 && hours.length === 24 / step2 && hours.every((h, i) => h === i * step2);
|
|
452
|
-
if (uniform) return `every ${step2} hours at minute ${minute}`;
|
|
453
452
|
const mm = String(minute).padStart(2, "0");
|
|
453
|
+
if (hours.length === 24) return `every hour at :${mm}`;
|
|
454
454
|
const times = hours.map((h) => `${String(h).padStart(2, "0")}:${mm}`);
|
|
455
|
+
const step2 = (hours[1] ?? 0) - (hours[0] ?? 0);
|
|
456
|
+
const uniform = hours.length > 1 && hours[0] === 0 && 24 % step2 === 0 && hours.length === 24 / step2 && hours.every((h, i) => h === i * step2);
|
|
457
|
+
if (uniform) return `every ${step2} hours, at ${times.join(", ")}`;
|
|
455
458
|
return `daily at ${times.join(", ")}`;
|
|
456
459
|
}
|
|
457
460
|
function renderDoctorNextSteps(opts) {
|
|
@@ -751,6 +754,42 @@ function formatAge(ms) {
|
|
|
751
754
|
if (minutes < 60) return `${minutes}m`;
|
|
752
755
|
return `${Math.round(minutes / 60)}h`;
|
|
753
756
|
}
|
|
757
|
+
function checkNotify(deps = {}) {
|
|
758
|
+
const platform = deps.platform ?? process.platform;
|
|
759
|
+
const has = deps.has ?? commandExistsSync;
|
|
760
|
+
if (platform === "darwin") {
|
|
761
|
+
return has("terminal-notifier") ? {
|
|
762
|
+
name: "notify",
|
|
763
|
+
ok: true,
|
|
764
|
+
detail: "desktop banners via terminal-notifier (clickable PR links)"
|
|
765
|
+
} : {
|
|
766
|
+
name: "notify",
|
|
767
|
+
ok: true,
|
|
768
|
+
detail: "desktop banners via osascript (banners only, not clickable); `brew install terminal-notifier` adds clickable PR links"
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
if (platform === "linux") {
|
|
772
|
+
return has("notify-send") ? { name: "notify", ok: true, detail: "desktop banners via notify-send" } : {
|
|
773
|
+
name: "notify",
|
|
774
|
+
ok: false,
|
|
775
|
+
detail: "desktop notifications enabled but notify-send is not on PATH",
|
|
776
|
+
fix: "Install libnotify (Debian/Ubuntu: `apt install libnotify-bin`, Fedora: `dnf install libnotify`), or set notify.desktop: false."
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
if (platform === "win32") {
|
|
780
|
+
return { name: "notify", ok: true, detail: "desktop banners via PowerShell toast" };
|
|
781
|
+
}
|
|
782
|
+
return {
|
|
783
|
+
name: "notify",
|
|
784
|
+
ok: false,
|
|
785
|
+
detail: `desktop notifications enabled but no native backend exists on ${platform}`,
|
|
786
|
+
fix: "Set notify.desktop: false; this platform has no desktop banner support."
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
function commandExistsSync(command) {
|
|
790
|
+
const finder = process.platform === "win32" ? "where" : "which";
|
|
791
|
+
return spawnSync(finder, [command], { stdio: "ignore" }).status === 0;
|
|
792
|
+
}
|
|
754
793
|
function classifyClaudeAuth(exitStatus, stdout) {
|
|
755
794
|
if (exitStatus !== 0) {
|
|
756
795
|
return {
|
|
@@ -798,11 +837,91 @@ async function checkRunStoreWritable() {
|
|
|
798
837
|
}
|
|
799
838
|
|
|
800
839
|
// src/cli/commands/init.ts
|
|
801
|
-
import {
|
|
802
|
-
import {
|
|
840
|
+
import { spawn } from "child_process";
|
|
841
|
+
import { existsSync as existsSync4, statSync as statSync2 } from "fs";
|
|
842
|
+
import { writeFile as writeFile3 } from "fs/promises";
|
|
803
843
|
import { createInterface } from "readline/promises";
|
|
804
844
|
import { homedir } from "os";
|
|
805
|
-
import { join as join2, resolve as
|
|
845
|
+
import { join as join2, resolve as resolve4 } from "path";
|
|
846
|
+
|
|
847
|
+
// src/cli/commands/schedule.ts
|
|
848
|
+
import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
849
|
+
import { existsSync as existsSync3 } from "fs";
|
|
850
|
+
import { dirname as dirname2, resolve as resolve3 } from "path";
|
|
851
|
+
function currentPlatform() {
|
|
852
|
+
const platform = process.platform;
|
|
853
|
+
if (platform === "darwin" || platform === "linux" || platform === "win32") return platform;
|
|
854
|
+
fail(
|
|
855
|
+
`Unsupported platform "${platform}" for native scheduling. Use \`afterburner watch\` instead.`
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
async function performScheduleInstall(platform, configPath, config) {
|
|
859
|
+
const artifacts = generateScheduleArtifacts(platform, {
|
|
860
|
+
cron: config.schedule.cron,
|
|
861
|
+
timezone: config.schedule.timezone,
|
|
862
|
+
nodePath: process.execPath,
|
|
863
|
+
cliPath: resolveCliEntry(),
|
|
864
|
+
configPath
|
|
865
|
+
});
|
|
866
|
+
const written = [];
|
|
867
|
+
for (const file of artifacts.files) {
|
|
868
|
+
await mkdir2(dirname2(file.path), { recursive: true });
|
|
869
|
+
await writeFile2(file.path, file.content, "utf8");
|
|
870
|
+
written.push(file.path);
|
|
871
|
+
}
|
|
872
|
+
return {
|
|
873
|
+
kind: artifacts.kind,
|
|
874
|
+
written,
|
|
875
|
+
activationHint: artifacts.activationHint,
|
|
876
|
+
removalHint: artifacts.removalHint
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
function registerSchedule(program2) {
|
|
880
|
+
const schedule = program2.command("schedule").description("Install or remove a native OS scheduler entry (launchd / systemd / schtasks)");
|
|
881
|
+
schedule.command("install").description("Generate and install the native scheduler entry for this OS").option("--config <path>", "path to a config file (recorded in the scheduled command)").action(async (opts) => {
|
|
882
|
+
const { config, filepath } = await loadConfigOrExit(opts.config);
|
|
883
|
+
const configPath = opts.config ? resolve3(opts.config) : filepath;
|
|
884
|
+
const result = await performScheduleInstall(currentPlatform(), configPath, config);
|
|
885
|
+
for (const path of result.written) console.log(`Wrote ${path}`);
|
|
886
|
+
console.log(`
|
|
887
|
+
Kind: ${result.kind}`);
|
|
888
|
+
console.log(`Scheduled runs will use this config: ${configPath}`);
|
|
889
|
+
console.log(`Activate with:
|
|
890
|
+
${result.activationHint}`);
|
|
891
|
+
console.log(`
|
|
892
|
+
Remove later with:
|
|
893
|
+
${result.removalHint}`);
|
|
894
|
+
});
|
|
895
|
+
schedule.command("uninstall").description("Remove the native scheduler entry files for this OS").option("--config <path>", "path to a config file").action(async (opts) => {
|
|
896
|
+
const { config } = await loadConfigOrExit(opts.config);
|
|
897
|
+
const artifacts = generateScheduleArtifacts(currentPlatform(), {
|
|
898
|
+
cron: config.schedule.cron,
|
|
899
|
+
timezone: config.schedule.timezone,
|
|
900
|
+
nodePath: process.execPath,
|
|
901
|
+
cliPath: resolveCliEntry()
|
|
902
|
+
});
|
|
903
|
+
if (artifacts.files.length === 0) {
|
|
904
|
+
console.log(
|
|
905
|
+
`Nothing to delete on this platform. Remove the tasks with:
|
|
906
|
+
${artifacts.removalHint}`
|
|
907
|
+
);
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
for (const file of artifacts.files) {
|
|
911
|
+
if (existsSync3(file.path)) {
|
|
912
|
+
await rm2(file.path);
|
|
913
|
+
console.log(`Removed ${file.path}`);
|
|
914
|
+
} else {
|
|
915
|
+
console.log(`Not found (already removed?): ${file.path}`);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
console.log(`
|
|
919
|
+
Finish deactivation with:
|
|
920
|
+
${artifacts.removalHint}`);
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// src/cli/commands/init.ts
|
|
806
925
|
var BACKENDS = ["dry-run", "claude-code", "api-key"];
|
|
807
926
|
var BUDGET_PROVIDERS = ["manual", "claude-usage", "claude-code-transcripts"];
|
|
808
927
|
var ENGINE_CHOICES = {
|
|
@@ -863,12 +982,18 @@ async function runInit(opts, packageInfo2) {
|
|
|
863
982
|
}
|
|
864
983
|
const { backend, budgetProvider, repoUrl, verifyNow, targetDir, target } = answers;
|
|
865
984
|
assertCanWriteConfig(targetDir, target, opts.force === true || answers.overwriteConsented);
|
|
866
|
-
await
|
|
985
|
+
await writeFile3(target, renderConfig(backend, budgetProvider, repoUrl), "utf8");
|
|
867
986
|
console.log(`
|
|
868
987
|
${step("Config", target)}`);
|
|
869
988
|
if (verifyNow) {
|
|
870
989
|
console.log();
|
|
871
|
-
await runDoctor({ packageInfo: packageInfo2, configPath: target });
|
|
990
|
+
const { ok, config, configPath } = await runDoctor({ packageInfo: packageInfo2, configPath: target });
|
|
991
|
+
if (!opts.yes && ok && config && configPath) {
|
|
992
|
+
await offerScheduleInstall(configPath, config);
|
|
993
|
+
if (await offerDesktopNotifications()) {
|
|
994
|
+
await writeFile3(target, renderConfig(backend, budgetProvider, repoUrl, true), "utf8");
|
|
995
|
+
}
|
|
996
|
+
}
|
|
872
997
|
return;
|
|
873
998
|
}
|
|
874
999
|
console.log(
|
|
@@ -882,6 +1007,118 @@ ${renderOnboardingSummary({
|
|
|
882
1007
|
})}`
|
|
883
1008
|
);
|
|
884
1009
|
}
|
|
1010
|
+
async function offerScheduleInstall(configPath, config, deps = {}) {
|
|
1011
|
+
const platform = deps.platform ?? process.platform;
|
|
1012
|
+
if (platform !== "darwin" && platform !== "linux" && platform !== "win32") {
|
|
1013
|
+
return false;
|
|
1014
|
+
}
|
|
1015
|
+
const created = deps.rl ? null : createInterface({ input: process.stdin, output: process.stdout });
|
|
1016
|
+
created?.on("SIGINT", () => created.close());
|
|
1017
|
+
const rl = deps.rl ?? created;
|
|
1018
|
+
try {
|
|
1019
|
+
const answer = (await rl.question("\nInstall the schedule now, so it runs unattended? [y/N]: ")).trim().toLowerCase();
|
|
1020
|
+
if (answer !== "y" && answer !== "yes") {
|
|
1021
|
+
console.log(
|
|
1022
|
+
dim("Skipped \u2014 run `afterburner schedule install` whenever you want it recurring.")
|
|
1023
|
+
);
|
|
1024
|
+
return false;
|
|
1025
|
+
}
|
|
1026
|
+
const install2 = deps.install ?? performScheduleInstall;
|
|
1027
|
+
const result = await install2(platform, configPath, config);
|
|
1028
|
+
for (const path of result.written) console.log(` ${green("\u2713")} wrote ${path}`);
|
|
1029
|
+
console.log(
|
|
1030
|
+
`
|
|
1031
|
+
${bold("One more step \u2014 turn it on")} (the entry is written but not yet active):
|
|
1032
|
+
${result.activationHint}`
|
|
1033
|
+
);
|
|
1034
|
+
console.log(dim("\nStop it later with `afterburner schedule uninstall`."));
|
|
1035
|
+
return true;
|
|
1036
|
+
} catch (error) {
|
|
1037
|
+
if (error?.code === "ABORT_ERR") {
|
|
1038
|
+
console.log(dim("\nSkipped scheduling."));
|
|
1039
|
+
return false;
|
|
1040
|
+
}
|
|
1041
|
+
console.log(
|
|
1042
|
+
errYellow(
|
|
1043
|
+
`Could not install a native schedule: ${error instanceof Error ? error.message : String(error)}. Run \`afterburner schedule install\` later, or use \`afterburner watch\`.`
|
|
1044
|
+
)
|
|
1045
|
+
);
|
|
1046
|
+
return false;
|
|
1047
|
+
} finally {
|
|
1048
|
+
created?.close();
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
async function offerDesktopNotifications(deps = {}) {
|
|
1052
|
+
const platform = deps.platform ?? process.platform;
|
|
1053
|
+
const created = deps.rl ? null : createInterface({ input: process.stdin, output: process.stdout });
|
|
1054
|
+
created?.on("SIGINT", () => created.close());
|
|
1055
|
+
const rl = deps.rl ?? created;
|
|
1056
|
+
try {
|
|
1057
|
+
const answer = (await rl.question("\nSend a desktop notification when a run completes? [y/N]: ")).trim().toLowerCase();
|
|
1058
|
+
if (answer !== "y" && answer !== "yes") {
|
|
1059
|
+
console.log(dim("Skipped \u2014 set notify.desktop: true in the config to enable later."));
|
|
1060
|
+
return false;
|
|
1061
|
+
}
|
|
1062
|
+
const has = deps.has ?? commandExists;
|
|
1063
|
+
if (platform === "darwin") {
|
|
1064
|
+
if (!await has("terminal-notifier") && await has("brew")) {
|
|
1065
|
+
const sub = (await rl.question(
|
|
1066
|
+
"Install terminal-notifier for clickable banners (brew install terminal-notifier)? [y/N]: "
|
|
1067
|
+
)).trim().toLowerCase();
|
|
1068
|
+
if (sub === "y" || sub === "yes") {
|
|
1069
|
+
try {
|
|
1070
|
+
console.log(dim("Installing terminal-notifier\u2026"));
|
|
1071
|
+
await (deps.install ?? brewInstall)("terminal-notifier");
|
|
1072
|
+
console.log(` ${green("\u2713")} installed terminal-notifier`);
|
|
1073
|
+
} catch (error) {
|
|
1074
|
+
console.log(
|
|
1075
|
+
errYellow(
|
|
1076
|
+
`Could not install terminal-notifier: ${error instanceof Error ? error.message : String(error)}. Banners will use osascript (no click) until you install it.`
|
|
1077
|
+
)
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
const backendReady = platform === "darwin" || platform === "win32" ? true : platform === "linux" ? await has("notify-send") : false;
|
|
1084
|
+
if (backendReady) {
|
|
1085
|
+
console.log(dim("Desktop notifications on \u2014 a banner fires when a run completes."));
|
|
1086
|
+
} else if (platform === "linux") {
|
|
1087
|
+
console.log(
|
|
1088
|
+
errYellow(
|
|
1089
|
+
"Desktop notifications enabled, but notify-send is not on PATH \u2014 install libnotify (Debian/Ubuntu: `apt install libnotify-bin`) for banners to appear."
|
|
1090
|
+
)
|
|
1091
|
+
);
|
|
1092
|
+
} else {
|
|
1093
|
+
console.log(
|
|
1094
|
+
errYellow(
|
|
1095
|
+
`Desktop notifications enabled, but ${platform} has no desktop banner backend; runs still log to the console and run store.`
|
|
1096
|
+
)
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
return true;
|
|
1100
|
+
} catch (error) {
|
|
1101
|
+
if (error?.code === "ABORT_ERR") return false;
|
|
1102
|
+
console.log(
|
|
1103
|
+
errYellow(
|
|
1104
|
+
`Could not set up notifications: ${error instanceof Error ? error.message : String(error)}.`
|
|
1105
|
+
)
|
|
1106
|
+
);
|
|
1107
|
+
return false;
|
|
1108
|
+
} finally {
|
|
1109
|
+
created?.close();
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
function brewInstall(formula) {
|
|
1113
|
+
return new Promise((resolve7, reject) => {
|
|
1114
|
+
const child = spawn("brew", ["install", formula], { stdio: "inherit" });
|
|
1115
|
+
child.on("error", reject);
|
|
1116
|
+
child.on(
|
|
1117
|
+
"close",
|
|
1118
|
+
(code) => code === 0 ? resolve7() : reject(new Error(`brew install exited with code ${code}`))
|
|
1119
|
+
);
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
885
1122
|
async function collectAnswers(rl, opts, cwd = process.cwd()) {
|
|
886
1123
|
let targetDir = cwd;
|
|
887
1124
|
let target = join2(targetDir, "afterburner.config.mjs");
|
|
@@ -896,7 +1133,7 @@ async function collectAnswers(rl, opts, cwd = process.cwd()) {
|
|
|
896
1133
|
console.log(step("Engine", backend));
|
|
897
1134
|
console.log(`
|
|
898
1135
|
${bold("Step 2 of 3 \u2014 Repository (the allowlist of what it may touch)")}`);
|
|
899
|
-
const detectedRepo =
|
|
1136
|
+
const detectedRepo = existsSync4(join2(cwd, ".git")) ? cwd : null;
|
|
900
1137
|
if (detectedRepo) {
|
|
901
1138
|
console.log(` - press Enter to use this folder (a git repo): ${detectedRepo}`);
|
|
902
1139
|
}
|
|
@@ -965,7 +1202,7 @@ function assertCanWriteConfig(targetDir, target, force) {
|
|
|
965
1202
|
}
|
|
966
1203
|
function configConflict(targetDir, target) {
|
|
967
1204
|
const existing = CONFIG_FILENAMES.map((name) => join2(targetDir, name)).filter(
|
|
968
|
-
(p) =>
|
|
1205
|
+
(p) => existsSync4(p)
|
|
969
1206
|
);
|
|
970
1207
|
return {
|
|
971
1208
|
exact: existing.includes(target) ? target : null,
|
|
@@ -986,7 +1223,7 @@ Overwrite it with these new answers? Only this file changes \u2014 your repo is
|
|
|
986
1223
|
return answer === "y" || answer === "yes";
|
|
987
1224
|
}
|
|
988
1225
|
function hasConfig(dir) {
|
|
989
|
-
return CONFIG_FILENAMES.some((name) =>
|
|
1226
|
+
return CONFIG_FILENAMES.some((name) => existsSync4(join2(dir, name)));
|
|
990
1227
|
}
|
|
991
1228
|
async function askChoice(rl, prompt, count) {
|
|
992
1229
|
for (; ; ) {
|
|
@@ -999,7 +1236,7 @@ async function askChoice(rl, prompt, count) {
|
|
|
999
1236
|
function localRepoPath(repoUrl, base = process.cwd()) {
|
|
1000
1237
|
if (repoUrl === "" || looksRemoteRepoUrl(repoUrl)) return null;
|
|
1001
1238
|
const expanded = repoUrl === "~" ? homedir() : repoUrl.replace(/^~(?=\/)/, homedir());
|
|
1002
|
-
const absolute =
|
|
1239
|
+
const absolute = resolve4(base, expanded);
|
|
1003
1240
|
return statSync2(absolute, { throwIfNoEntry: false })?.isDirectory() ? absolute : null;
|
|
1004
1241
|
}
|
|
1005
1242
|
function renderOnboardingSummary(opts) {
|
|
@@ -1025,7 +1262,7 @@ function renderOnboardingSummary(opts) {
|
|
|
1025
1262
|
);
|
|
1026
1263
|
return lines.join("\n");
|
|
1027
1264
|
}
|
|
1028
|
-
function renderConfig(backend, budgetProvider, repoUrl) {
|
|
1265
|
+
function renderConfig(backend, budgetProvider, repoUrl, desktopNotify = false) {
|
|
1029
1266
|
const repoBlock = repoUrl ? ` {
|
|
1030
1267
|
url: ${JSON.stringify(repoUrl)},
|
|
1031
1268
|
defaultBranch: 'main',
|
|
@@ -1075,6 +1312,11 @@ ${repoBlock}
|
|
|
1075
1312
|
// money at API rates outside promotional windows.
|
|
1076
1313
|
allowFable: false,
|
|
1077
1314
|
},
|
|
1315
|
+
notify: {
|
|
1316
|
+
// Desktop banner when a run completes (macOS/Linux/Windows). The opened PR
|
|
1317
|
+
// is still the primary notification; this is a local convenience.
|
|
1318
|
+
desktop: ${desktopNotify},
|
|
1319
|
+
},
|
|
1078
1320
|
};
|
|
1079
1321
|
|
|
1080
1322
|
export default config;
|
|
@@ -1174,7 +1416,7 @@ function registerRunOnce(program2) {
|
|
|
1174
1416
|
selector: createSelector(config),
|
|
1175
1417
|
runner,
|
|
1176
1418
|
store: new JsonlRunStore(),
|
|
1177
|
-
notifier:
|
|
1419
|
+
notifier: createNotifier(config)
|
|
1178
1420
|
});
|
|
1179
1421
|
if (outcomes.some((o) => o.status === "completed")) await ignite();
|
|
1180
1422
|
for (const outcome of outcomes) {
|
|
@@ -1207,72 +1449,6 @@ ${section(emoji.rocket, "Next")}`);
|
|
|
1207
1449
|
});
|
|
1208
1450
|
}
|
|
1209
1451
|
|
|
1210
|
-
// src/cli/commands/schedule.ts
|
|
1211
|
-
import { mkdir as mkdir2, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
1212
|
-
import { existsSync as existsSync4 } from "fs";
|
|
1213
|
-
import { dirname as dirname2, resolve as resolve4 } from "path";
|
|
1214
|
-
function currentPlatform() {
|
|
1215
|
-
const platform = process.platform;
|
|
1216
|
-
if (platform === "darwin" || platform === "linux" || platform === "win32") return platform;
|
|
1217
|
-
fail(
|
|
1218
|
-
`Unsupported platform "${platform}" for native scheduling. Use \`afterburner watch\` instead.`
|
|
1219
|
-
);
|
|
1220
|
-
}
|
|
1221
|
-
function registerSchedule(program2) {
|
|
1222
|
-
const schedule = program2.command("schedule").description("Install or remove a native OS scheduler entry (launchd / systemd / schtasks)");
|
|
1223
|
-
schedule.command("install").description("Generate and install the native scheduler entry for this OS").option("--config <path>", "path to a config file (recorded in the scheduled command)").action(async (opts) => {
|
|
1224
|
-
const { config, filepath } = await loadConfigOrExit(opts.config);
|
|
1225
|
-
const configPath = opts.config ? resolve4(opts.config) : filepath;
|
|
1226
|
-
const artifacts = generateScheduleArtifacts(currentPlatform(), {
|
|
1227
|
-
cron: config.schedule.cron,
|
|
1228
|
-
timezone: config.schedule.timezone,
|
|
1229
|
-
nodePath: process.execPath,
|
|
1230
|
-
cliPath: resolveCliEntry(),
|
|
1231
|
-
configPath
|
|
1232
|
-
});
|
|
1233
|
-
for (const file of artifacts.files) {
|
|
1234
|
-
await mkdir2(dirname2(file.path), { recursive: true });
|
|
1235
|
-
await writeFile3(file.path, file.content, "utf8");
|
|
1236
|
-
console.log(`Wrote ${file.path}`);
|
|
1237
|
-
}
|
|
1238
|
-
console.log(`
|
|
1239
|
-
Kind: ${artifacts.kind}`);
|
|
1240
|
-
console.log(`Scheduled runs will use this config: ${configPath}`);
|
|
1241
|
-
console.log(`Activate with:
|
|
1242
|
-
${artifacts.activationHint}`);
|
|
1243
|
-
console.log(`
|
|
1244
|
-
Remove later with:
|
|
1245
|
-
${artifacts.removalHint}`);
|
|
1246
|
-
});
|
|
1247
|
-
schedule.command("uninstall").description("Remove the native scheduler entry files for this OS").option("--config <path>", "path to a config file").action(async (opts) => {
|
|
1248
|
-
const { config } = await loadConfigOrExit(opts.config);
|
|
1249
|
-
const artifacts = generateScheduleArtifacts(currentPlatform(), {
|
|
1250
|
-
cron: config.schedule.cron,
|
|
1251
|
-
timezone: config.schedule.timezone,
|
|
1252
|
-
nodePath: process.execPath,
|
|
1253
|
-
cliPath: resolveCliEntry()
|
|
1254
|
-
});
|
|
1255
|
-
if (artifacts.files.length === 0) {
|
|
1256
|
-
console.log(
|
|
1257
|
-
`Nothing to delete on this platform. Remove the tasks with:
|
|
1258
|
-
${artifacts.removalHint}`
|
|
1259
|
-
);
|
|
1260
|
-
return;
|
|
1261
|
-
}
|
|
1262
|
-
for (const file of artifacts.files) {
|
|
1263
|
-
if (existsSync4(file.path)) {
|
|
1264
|
-
await rm2(file.path);
|
|
1265
|
-
console.log(`Removed ${file.path}`);
|
|
1266
|
-
} else {
|
|
1267
|
-
console.log(`Not found (already removed?): ${file.path}`);
|
|
1268
|
-
}
|
|
1269
|
-
}
|
|
1270
|
-
console.log(`
|
|
1271
|
-
Finish deactivation with:
|
|
1272
|
-
${artifacts.removalHint}`);
|
|
1273
|
-
});
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
1452
|
// src/cli/commands/skill.ts
|
|
1277
1453
|
import { copyFile, mkdir as mkdir3 } from "fs/promises";
|
|
1278
1454
|
import { existsSync as existsSync5 } from "fs";
|
|
@@ -1302,7 +1478,7 @@ function registerSkill(program2) {
|
|
|
1302
1478
|
}
|
|
1303
1479
|
|
|
1304
1480
|
// src/cli/commands/statusline.ts
|
|
1305
|
-
import { spawn } from "child_process";
|
|
1481
|
+
import { spawn as spawn2 } from "child_process";
|
|
1306
1482
|
import { existsSync as existsSync6 } from "fs";
|
|
1307
1483
|
import { copyFile as copyFile2, mkdir as mkdir4, readFile, rm as rm3, writeFile as writeFile4 } from "fs/promises";
|
|
1308
1484
|
import { dirname as dirname3, join as join4 } from "path";
|
|
@@ -1377,7 +1553,7 @@ async function readWrappedState() {
|
|
|
1377
1553
|
}
|
|
1378
1554
|
function passThrough(command, input) {
|
|
1379
1555
|
return new Promise((resolve7) => {
|
|
1380
|
-
const child =
|
|
1556
|
+
const child = spawn2(command, { shell: true, stdio: ["pipe", "inherit", "inherit"] });
|
|
1381
1557
|
child.on("error", () => resolve7());
|
|
1382
1558
|
child.on("close", () => resolve7());
|
|
1383
1559
|
child.stdin.on("error", () => {
|
|
@@ -1697,7 +1873,7 @@ function registerWatch(program2) {
|
|
|
1697
1873
|
selector: createSelector(config),
|
|
1698
1874
|
runner,
|
|
1699
1875
|
store: new JsonlRunStore(),
|
|
1700
|
-
notifier:
|
|
1876
|
+
notifier: createNotifier(config)
|
|
1701
1877
|
});
|
|
1702
1878
|
for (const outcome of outcomes) {
|
|
1703
1879
|
console.log(`[afterburner] ${outcome.repoUrl}: ${outcome.status}, ${outcome.reason}`);
|
package/dist/core/index.d.ts
CHANGED
|
@@ -53,8 +53,8 @@ declare const budgetConfigSchema: z.ZodPrefault<z.ZodObject<{
|
|
|
53
53
|
}, z.core.$strip>>;
|
|
54
54
|
declare const agentConfigSchema: z.ZodPrefault<z.ZodObject<{
|
|
55
55
|
backend: z.ZodDefault<z.ZodEnum<{
|
|
56
|
-
"claude-code": "claude-code";
|
|
57
56
|
"dry-run": "dry-run";
|
|
57
|
+
"claude-code": "claude-code";
|
|
58
58
|
"api-key": "api-key";
|
|
59
59
|
}>>;
|
|
60
60
|
modelByCategory: z.ZodPipe<z.ZodDefault<z.ZodRecord<z.ZodEnum<{
|
|
@@ -115,8 +115,8 @@ declare const configSchema: z.ZodObject<{
|
|
|
115
115
|
}, z.core.$strip>>;
|
|
116
116
|
agent: z.ZodPrefault<z.ZodObject<{
|
|
117
117
|
backend: z.ZodDefault<z.ZodEnum<{
|
|
118
|
-
"claude-code": "claude-code";
|
|
119
118
|
"dry-run": "dry-run";
|
|
119
|
+
"claude-code": "claude-code";
|
|
120
120
|
"api-key": "api-key";
|
|
121
121
|
}>>;
|
|
122
122
|
modelByCategory: z.ZodPipe<z.ZodDefault<z.ZodRecord<z.ZodEnum<{
|
|
@@ -139,6 +139,9 @@ declare const configSchema: z.ZodObject<{
|
|
|
139
139
|
maxTaskTokens: z.ZodDefault<z.ZodNumber>;
|
|
140
140
|
allowFable: z.ZodDefault<z.ZodBoolean>;
|
|
141
141
|
}, z.core.$strip>>;
|
|
142
|
+
notify: z.ZodPrefault<z.ZodObject<{
|
|
143
|
+
desktop: z.ZodDefault<z.ZodBoolean>;
|
|
144
|
+
}, z.core.$strip>>;
|
|
142
145
|
taskCategories: z.ZodPipe<z.ZodDefault<z.ZodRecord<z.ZodEnum<{
|
|
143
146
|
security: "security";
|
|
144
147
|
tests: "tests";
|
|
@@ -683,6 +686,58 @@ declare class ConsoleNotifier implements Notifier {
|
|
|
683
686
|
notify(record: RunRecord): Promise<void>;
|
|
684
687
|
}
|
|
685
688
|
|
|
689
|
+
/** Spawn options we actually use; kept narrow so the injected exec is easy to fake. */
|
|
690
|
+
interface ExecOpts {
|
|
691
|
+
env?: NodeJS.ProcessEnv;
|
|
692
|
+
}
|
|
693
|
+
type Exec = (command: string, args: string[], opts?: ExecOpts) => Promise<unknown>;
|
|
694
|
+
interface DesktopNotifierDeps {
|
|
695
|
+
/** Spawns a command (argv array, never a shell string). Injected in tests. */
|
|
696
|
+
exec?: Exec;
|
|
697
|
+
/** Whether a binary is resolvable on PATH. Injected in tests. */
|
|
698
|
+
has?: (command: string) => Promise<boolean>;
|
|
699
|
+
platform?: NodeJS.Platform;
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Fires an OS-native desktop banner per completed run, with zero npm
|
|
703
|
+
* dependencies — it shells out to platform tools: terminal-notifier (clickable)
|
|
704
|
+
* or osascript on macOS, notify-send on Linux, a PowerShell toast on Windows.
|
|
705
|
+
*
|
|
706
|
+
* Two invariants:
|
|
707
|
+
* 1. Untrusted record text (title, repo) is passed as argv or via environment
|
|
708
|
+
* variables, NEVER interpolated into a shell string or a second interpreter's
|
|
709
|
+
* source. osascript evaluates AppleScript and PowerShell evaluates a script,
|
|
710
|
+
* so their dynamic text comes from env (`system attribute` / `$env:`), which
|
|
711
|
+
* closes the AppleScript/script-injection hole an argv array alone leaves open.
|
|
712
|
+
* 2. notify() never throws: a missing tool, denied permission, or headless box
|
|
713
|
+
* must not break a run. The run store stays the source of truth.
|
|
714
|
+
*/
|
|
715
|
+
declare class DesktopNotifier implements Notifier {
|
|
716
|
+
private readonly exec;
|
|
717
|
+
private readonly has;
|
|
718
|
+
private readonly platform;
|
|
719
|
+
constructor(deps?: DesktopNotifierDeps);
|
|
720
|
+
notify(record: RunRecord): Promise<void>;
|
|
721
|
+
private notifyMac;
|
|
722
|
+
private notifyLinux;
|
|
723
|
+
private notifyWindows;
|
|
724
|
+
}
|
|
725
|
+
/** True when `command` resolves on PATH (via which/where). Never throws. */
|
|
726
|
+
declare function commandExists(command: string): Promise<boolean>;
|
|
727
|
+
|
|
728
|
+
/** Runs several notifiers in order, isolating failures so one can't suppress the rest. */
|
|
729
|
+
declare class CompositeNotifier implements Notifier {
|
|
730
|
+
private readonly notifiers;
|
|
731
|
+
constructor(notifiers: Notifier[]);
|
|
732
|
+
notify(record: RunRecord): Promise<void>;
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Picks the notifier from config. ConsoleNotifier ALWAYS runs: its line is the
|
|
736
|
+
* audit trail that reaches the scheduler's stdout log, so a swallowed desktop
|
|
737
|
+
* banner never costs the record. The desktop banner is layered on when opted in.
|
|
738
|
+
*/
|
|
739
|
+
declare function createNotifier(config: AfterburnerConfig): Notifier;
|
|
740
|
+
|
|
686
741
|
interface GateConfig {
|
|
687
742
|
minWeeklyHeadroomPct: number;
|
|
688
743
|
safetyMarginTokens: number;
|
|
@@ -778,4 +833,4 @@ interface RepoRunOutcome {
|
|
|
778
833
|
*/
|
|
779
834
|
declare function runOnce(deps: RunOnceDeps): Promise<RepoRunOutcome[]>;
|
|
780
835
|
|
|
781
|
-
export { type AfterburnerConfig, type AfterburnerUserConfig, type AgentConfig, type AgentRunner, ApiKeyRunner, BASE_TASK_TOKENS, type Budget, type BudgetConfig, type BudgetProvider, type BudgetProviderOptions, type CandidateTask, type ClaudeCodeInvocation, ClaudeCodeRunner, ClaudeCodeTranscriptsBudgetProvider, type ClaudeCodeTranscriptsOptions, type ClaudeUsageBudgetOptions, ClaudeUsageBudgetProvider, ConsoleNotifier, DEFAULT_MODEL_BY_CATEGORY, type DeterministicSelectorOptions, DeterministicTaskSelector, DryRunRunner, type GateConfig, type GateDecision, JsonlRunStore, type LoadedConfig, ManualBudgetProvider, type ModelCostTable, type ModelWeightEntry, type NativeScheduleOptions, type Notifier, type RateLimitWindow, type RateLimits, type RepoConfig, type RepoRunOutcome, type RunOnceDeps, type RunOutcome, type RunRecord, type RunResult, type RunStore, type RunnerBackend, type ScheduleArtifacts, type SupportedPlatform, TASK_CATEGORIES, TASK_TAXONOMY, type TaskCategory, type TaskCategoryInfo, type TaskIdentity, type TaskSelector, type TranscriptUsageSummary, type UsageCache, type UsageCacheBudgetOptions, type UsageCacheBudgetResult, type WatchHandle, assertModelAllowed, budgetFromUsageCache, buildClaudeCodeInvocation, claudeConfigDir, configDir, configSchema, createBudgetProvider, createModelCostTable, createRunner, createSelector, dataDir, defaultClaudeProjectsDir, defaultCostTable, defaultRunStorePath, defaultUsageCachePath, defineConfig, deriveBranchName, derivePrTitle, formatConfigError, generateScheduleArtifacts, isFableModel, loadConfig, mapConfigLoadError, parseSimpleCron, readUsageCache, repoConfigSchema, runOnce, sanitizeSpawnEnv, shouldIgnite, startWatch, summarizeTranscriptUsage, taskFingerprint, writeUsageCache };
|
|
836
|
+
export { type AfterburnerConfig, type AfterburnerUserConfig, type AgentConfig, type AgentRunner, ApiKeyRunner, BASE_TASK_TOKENS, type Budget, type BudgetConfig, type BudgetProvider, type BudgetProviderOptions, type CandidateTask, type ClaudeCodeInvocation, ClaudeCodeRunner, ClaudeCodeTranscriptsBudgetProvider, type ClaudeCodeTranscriptsOptions, type ClaudeUsageBudgetOptions, ClaudeUsageBudgetProvider, CompositeNotifier, ConsoleNotifier, DEFAULT_MODEL_BY_CATEGORY, DesktopNotifier, type DeterministicSelectorOptions, DeterministicTaskSelector, DryRunRunner, type GateConfig, type GateDecision, JsonlRunStore, type LoadedConfig, ManualBudgetProvider, type ModelCostTable, type ModelWeightEntry, type NativeScheduleOptions, type Notifier, type RateLimitWindow, type RateLimits, type RepoConfig, type RepoRunOutcome, type RunOnceDeps, type RunOutcome, type RunRecord, type RunResult, type RunStore, type RunnerBackend, type ScheduleArtifacts, type SupportedPlatform, TASK_CATEGORIES, TASK_TAXONOMY, type TaskCategory, type TaskCategoryInfo, type TaskIdentity, type TaskSelector, type TranscriptUsageSummary, type UsageCache, type UsageCacheBudgetOptions, type UsageCacheBudgetResult, type WatchHandle, assertModelAllowed, budgetFromUsageCache, buildClaudeCodeInvocation, claudeConfigDir, commandExists, configDir, configSchema, createBudgetProvider, createModelCostTable, createNotifier, createRunner, createSelector, dataDir, defaultClaudeProjectsDir, defaultCostTable, defaultRunStorePath, defaultUsageCachePath, defineConfig, deriveBranchName, derivePrTitle, formatConfigError, generateScheduleArtifacts, isFableModel, loadConfig, mapConfigLoadError, parseSimpleCron, readUsageCache, repoConfigSchema, runOnce, sanitizeSpawnEnv, shouldIgnite, startWatch, summarizeTranscriptUsage, taskFingerprint, writeUsageCache };
|
package/dist/core/index.js
CHANGED
|
@@ -4,8 +4,10 @@ import {
|
|
|
4
4
|
ClaudeCodeRunner,
|
|
5
5
|
ClaudeCodeTranscriptsBudgetProvider,
|
|
6
6
|
ClaudeUsageBudgetProvider,
|
|
7
|
+
CompositeNotifier,
|
|
7
8
|
ConsoleNotifier,
|
|
8
9
|
DEFAULT_MODEL_BY_CATEGORY,
|
|
10
|
+
DesktopNotifier,
|
|
9
11
|
DeterministicTaskSelector,
|
|
10
12
|
DryRunRunner,
|
|
11
13
|
JsonlRunStore,
|
|
@@ -16,10 +18,12 @@ import {
|
|
|
16
18
|
budgetFromUsageCache,
|
|
17
19
|
buildClaudeCodeInvocation,
|
|
18
20
|
claudeConfigDir,
|
|
21
|
+
commandExists,
|
|
19
22
|
configDir,
|
|
20
23
|
configSchema,
|
|
21
24
|
createBudgetProvider,
|
|
22
25
|
createModelCostTable,
|
|
26
|
+
createNotifier,
|
|
23
27
|
createRunner,
|
|
24
28
|
createSelector,
|
|
25
29
|
dataDir,
|
|
@@ -45,15 +49,17 @@ import {
|
|
|
45
49
|
summarizeTranscriptUsage,
|
|
46
50
|
taskFingerprint,
|
|
47
51
|
writeUsageCache
|
|
48
|
-
} from "../chunk-
|
|
52
|
+
} from "../chunk-4TD2KS3A.js";
|
|
49
53
|
export {
|
|
50
54
|
ApiKeyRunner,
|
|
51
55
|
BASE_TASK_TOKENS,
|
|
52
56
|
ClaudeCodeRunner,
|
|
53
57
|
ClaudeCodeTranscriptsBudgetProvider,
|
|
54
58
|
ClaudeUsageBudgetProvider,
|
|
59
|
+
CompositeNotifier,
|
|
55
60
|
ConsoleNotifier,
|
|
56
61
|
DEFAULT_MODEL_BY_CATEGORY,
|
|
62
|
+
DesktopNotifier,
|
|
57
63
|
DeterministicTaskSelector,
|
|
58
64
|
DryRunRunner,
|
|
59
65
|
JsonlRunStore,
|
|
@@ -64,10 +70,12 @@ export {
|
|
|
64
70
|
budgetFromUsageCache,
|
|
65
71
|
buildClaudeCodeInvocation,
|
|
66
72
|
claudeConfigDir,
|
|
73
|
+
commandExists,
|
|
67
74
|
configDir,
|
|
68
75
|
configSchema,
|
|
69
76
|
createBudgetProvider,
|
|
70
77
|
createModelCostTable,
|
|
78
|
+
createNotifier,
|
|
71
79
|
createRunner,
|
|
72
80
|
createSelector,
|
|
73
81
|
dataDir,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pimmesz/afterburner",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
4
4
|
"description": "Convert idle Claude subscription quota into shippable engineering work: budget-aware trigger, bounded task selection, PR-only output.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"publishConfig": {
|