@burn0/burn0 0.1.0 → 0.2.0
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 +341 -2
- package/dist/{chunk-ZHAS7BCI.mjs → chunk-KKYHE4ZV.mjs} +146 -125
- package/dist/chunk-KKYHE4ZV.mjs.map +1 -0
- package/dist/cli/index.js +528 -269
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +145 -124
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/register.js +145 -124
- package/dist/register.js.map +1 -1
- package/dist/register.mjs +1 -1
- package/package.json +17 -3
- package/scripts/postinstall.js +27 -5
- package/dist/chunk-ZHAS7BCI.mjs.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -1156,8 +1156,8 @@ var require_command = __commonJS({
|
|
|
1156
1156
|
"use strict";
|
|
1157
1157
|
var EventEmitter = require("events").EventEmitter;
|
|
1158
1158
|
var childProcess = require("child_process");
|
|
1159
|
-
var
|
|
1160
|
-
var
|
|
1159
|
+
var path10 = require("path");
|
|
1160
|
+
var fs8 = require("fs");
|
|
1161
1161
|
var process5 = require("process");
|
|
1162
1162
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
1163
1163
|
var { CommanderError: CommanderError2 } = require_error();
|
|
@@ -2138,7 +2138,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2138
2138
|
* @param {string} subcommandName
|
|
2139
2139
|
*/
|
|
2140
2140
|
_checkForMissingExecutable(executableFile, executableDir, subcommandName) {
|
|
2141
|
-
if (
|
|
2141
|
+
if (fs8.existsSync(executableFile)) return;
|
|
2142
2142
|
const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
|
|
2143
2143
|
const executableMissing = `'${executableFile}' does not exist
|
|
2144
2144
|
- if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
|
|
@@ -2156,11 +2156,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2156
2156
|
let launchWithNode = false;
|
|
2157
2157
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
2158
2158
|
function findFile(baseDir, baseName) {
|
|
2159
|
-
const localBin =
|
|
2160
|
-
if (
|
|
2161
|
-
if (sourceExt.includes(
|
|
2159
|
+
const localBin = path10.resolve(baseDir, baseName);
|
|
2160
|
+
if (fs8.existsSync(localBin)) return localBin;
|
|
2161
|
+
if (sourceExt.includes(path10.extname(baseName))) return void 0;
|
|
2162
2162
|
const foundExt = sourceExt.find(
|
|
2163
|
-
(ext) =>
|
|
2163
|
+
(ext) => fs8.existsSync(`${localBin}${ext}`)
|
|
2164
2164
|
);
|
|
2165
2165
|
if (foundExt) return `${localBin}${foundExt}`;
|
|
2166
2166
|
return void 0;
|
|
@@ -2172,21 +2172,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2172
2172
|
if (this._scriptPath) {
|
|
2173
2173
|
let resolvedScriptPath;
|
|
2174
2174
|
try {
|
|
2175
|
-
resolvedScriptPath =
|
|
2175
|
+
resolvedScriptPath = fs8.realpathSync(this._scriptPath);
|
|
2176
2176
|
} catch {
|
|
2177
2177
|
resolvedScriptPath = this._scriptPath;
|
|
2178
2178
|
}
|
|
2179
|
-
executableDir =
|
|
2180
|
-
|
|
2179
|
+
executableDir = path10.resolve(
|
|
2180
|
+
path10.dirname(resolvedScriptPath),
|
|
2181
2181
|
executableDir
|
|
2182
2182
|
);
|
|
2183
2183
|
}
|
|
2184
2184
|
if (executableDir) {
|
|
2185
2185
|
let localFile = findFile(executableDir, executableFile);
|
|
2186
2186
|
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
2187
|
-
const legacyName =
|
|
2187
|
+
const legacyName = path10.basename(
|
|
2188
2188
|
this._scriptPath,
|
|
2189
|
-
|
|
2189
|
+
path10.extname(this._scriptPath)
|
|
2190
2190
|
);
|
|
2191
2191
|
if (legacyName !== this._name) {
|
|
2192
2192
|
localFile = findFile(
|
|
@@ -2197,7 +2197,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2197
2197
|
}
|
|
2198
2198
|
executableFile = localFile || executableFile;
|
|
2199
2199
|
}
|
|
2200
|
-
launchWithNode = sourceExt.includes(
|
|
2200
|
+
launchWithNode = sourceExt.includes(path10.extname(executableFile));
|
|
2201
2201
|
let proc;
|
|
2202
2202
|
if (process5.platform !== "win32") {
|
|
2203
2203
|
if (launchWithNode) {
|
|
@@ -3044,7 +3044,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3044
3044
|
* @return {Command}
|
|
3045
3045
|
*/
|
|
3046
3046
|
nameFromFilename(filename) {
|
|
3047
|
-
this._name =
|
|
3047
|
+
this._name = path10.basename(filename, path10.extname(filename));
|
|
3048
3048
|
return this;
|
|
3049
3049
|
}
|
|
3050
3050
|
/**
|
|
@@ -3058,9 +3058,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3058
3058
|
* @param {string} [path]
|
|
3059
3059
|
* @return {(string|null|Command)}
|
|
3060
3060
|
*/
|
|
3061
|
-
executableDir(
|
|
3062
|
-
if (
|
|
3063
|
-
this._executableDir =
|
|
3061
|
+
executableDir(path11) {
|
|
3062
|
+
if (path11 === void 0) return this._executableDir;
|
|
3063
|
+
this._executableDir = path11;
|
|
3064
3064
|
return this;
|
|
3065
3065
|
}
|
|
3066
3066
|
/**
|
|
@@ -5096,15 +5096,15 @@ var require_route = __commonJS({
|
|
|
5096
5096
|
};
|
|
5097
5097
|
}
|
|
5098
5098
|
function wrapConversion(toModel, graph) {
|
|
5099
|
-
const
|
|
5099
|
+
const path10 = [graph[toModel].parent, toModel];
|
|
5100
5100
|
let fn = conversions[graph[toModel].parent][toModel];
|
|
5101
5101
|
let cur = graph[toModel].parent;
|
|
5102
5102
|
while (graph[cur].parent) {
|
|
5103
|
-
|
|
5103
|
+
path10.unshift(graph[cur].parent);
|
|
5104
5104
|
fn = link(conversions[graph[cur].parent][cur], fn);
|
|
5105
5105
|
cur = graph[cur].parent;
|
|
5106
5106
|
}
|
|
5107
|
-
fn.conversion =
|
|
5107
|
+
fn.conversion = path10;
|
|
5108
5108
|
return fn;
|
|
5109
5109
|
}
|
|
5110
5110
|
module2.exports = function(fromModel) {
|
|
@@ -11997,10 +11997,10 @@ var require_lib2 = __commonJS({
|
|
|
11997
11997
|
exports2.analyse = analyse;
|
|
11998
11998
|
var detectFile = (filepath, opts = {}) => new Promise((resolve, reject) => {
|
|
11999
11999
|
let fd;
|
|
12000
|
-
const
|
|
12000
|
+
const fs8 = (0, node_1.default)();
|
|
12001
12001
|
const handler = (err, buffer) => {
|
|
12002
12002
|
if (fd) {
|
|
12003
|
-
|
|
12003
|
+
fs8.closeSync(fd);
|
|
12004
12004
|
}
|
|
12005
12005
|
if (err) {
|
|
12006
12006
|
reject(err);
|
|
@@ -12012,9 +12012,9 @@ var require_lib2 = __commonJS({
|
|
|
12012
12012
|
};
|
|
12013
12013
|
const sampleSize = (opts === null || opts === void 0 ? void 0 : opts.sampleSize) || 0;
|
|
12014
12014
|
if (sampleSize > 0) {
|
|
12015
|
-
fd =
|
|
12015
|
+
fd = fs8.openSync(filepath, "r");
|
|
12016
12016
|
let sample = Buffer.allocUnsafe(sampleSize);
|
|
12017
|
-
|
|
12017
|
+
fs8.read(fd, sample, 0, sampleSize, opts.offset, (err, bytesRead) => {
|
|
12018
12018
|
if (err) {
|
|
12019
12019
|
handler(err, null);
|
|
12020
12020
|
} else {
|
|
@@ -12026,22 +12026,22 @@ var require_lib2 = __commonJS({
|
|
|
12026
12026
|
});
|
|
12027
12027
|
return;
|
|
12028
12028
|
}
|
|
12029
|
-
|
|
12029
|
+
fs8.readFile(filepath, handler);
|
|
12030
12030
|
});
|
|
12031
12031
|
exports2.detectFile = detectFile;
|
|
12032
12032
|
var detectFileSync = (filepath, opts = {}) => {
|
|
12033
|
-
const
|
|
12033
|
+
const fs8 = (0, node_1.default)();
|
|
12034
12034
|
if (opts && opts.sampleSize) {
|
|
12035
|
-
const fd =
|
|
12035
|
+
const fd = fs8.openSync(filepath, "r");
|
|
12036
12036
|
let sample = Buffer.allocUnsafe(opts.sampleSize);
|
|
12037
|
-
const bytesRead =
|
|
12037
|
+
const bytesRead = fs8.readSync(fd, sample, 0, opts.sampleSize, opts.offset);
|
|
12038
12038
|
if (bytesRead < opts.sampleSize) {
|
|
12039
12039
|
sample = sample.subarray(0, bytesRead);
|
|
12040
12040
|
}
|
|
12041
|
-
|
|
12041
|
+
fs8.closeSync(fd);
|
|
12042
12042
|
return (0, exports2.detect)(sample);
|
|
12043
12043
|
}
|
|
12044
|
-
return (0, exports2.detect)(
|
|
12044
|
+
return (0, exports2.detect)(fs8.readFileSync(filepath));
|
|
12045
12045
|
};
|
|
12046
12046
|
exports2.detectFileSync = detectFileSync;
|
|
12047
12047
|
exports2.default = {
|
|
@@ -17948,9 +17948,97 @@ var init_catalog = __esm({
|
|
|
17948
17948
|
}
|
|
17949
17949
|
});
|
|
17950
17950
|
|
|
17951
|
+
// src/cli/api-key.ts
|
|
17952
|
+
function openBrowser(url) {
|
|
17953
|
+
try {
|
|
17954
|
+
const platform = process.platform;
|
|
17955
|
+
if (platform === "darwin") (0, import_node_child_process.execFileSync)("open", [url]);
|
|
17956
|
+
else if (platform === "win32") (0, import_node_child_process.execFileSync)("cmd", ["/c", "start", url]);
|
|
17957
|
+
else (0, import_node_child_process.execFileSync)("xdg-open", [url]);
|
|
17958
|
+
return true;
|
|
17959
|
+
} catch {
|
|
17960
|
+
return false;
|
|
17961
|
+
}
|
|
17962
|
+
}
|
|
17963
|
+
async function promptApiKey(cwd) {
|
|
17964
|
+
const choice = await esm_default11({
|
|
17965
|
+
message: "API key?",
|
|
17966
|
+
choices: [
|
|
17967
|
+
{ name: "Paste key", value: "paste" },
|
|
17968
|
+
{ name: "Get one free \u2192 burn0.dev/api", value: "get" },
|
|
17969
|
+
{ name: "Skip \u2014 local mode", value: "skip" }
|
|
17970
|
+
]
|
|
17971
|
+
});
|
|
17972
|
+
if (choice === "skip") return void 0;
|
|
17973
|
+
if (choice === "get") {
|
|
17974
|
+
const opened = openBrowser("https://burn0.dev/api");
|
|
17975
|
+
if (!opened) {
|
|
17976
|
+
console.log(source_default.dim("\n Visit burn0.dev/api to get your free API key\n"));
|
|
17977
|
+
} else {
|
|
17978
|
+
console.log(source_default.dim("\n Opening burn0.dev/api in your browser...\n"));
|
|
17979
|
+
}
|
|
17980
|
+
}
|
|
17981
|
+
while (true) {
|
|
17982
|
+
const apiKey = await esm_default5({ message: "Paste your API key:" });
|
|
17983
|
+
if (!apiKey || !apiKey.startsWith("b0_sk_")) {
|
|
17984
|
+
console.log(source_default.red("\n Invalid key. Keys start with b0_sk_"));
|
|
17985
|
+
const retry = await esm_default11({
|
|
17986
|
+
message: "What would you like to do?",
|
|
17987
|
+
choices: [
|
|
17988
|
+
{ name: "Try again", value: "retry" },
|
|
17989
|
+
{ name: "Skip \u2014 local mode", value: "skip" }
|
|
17990
|
+
]
|
|
17991
|
+
});
|
|
17992
|
+
if (retry === "skip") return void 0;
|
|
17993
|
+
continue;
|
|
17994
|
+
}
|
|
17995
|
+
writeApiKeyToEnv(cwd, apiKey);
|
|
17996
|
+
console.log(source_default.green(" \u2713 Added BURN0_API_KEY to .env"));
|
|
17997
|
+
return apiKey;
|
|
17998
|
+
}
|
|
17999
|
+
}
|
|
18000
|
+
function writeApiKeyToEnv(cwd, apiKey) {
|
|
18001
|
+
const envPath = import_node_path5.default.join(cwd, ".env");
|
|
18002
|
+
const examplePath = import_node_path5.default.join(cwd, ".env.example");
|
|
18003
|
+
let envContent = "";
|
|
18004
|
+
try {
|
|
18005
|
+
envContent = import_node_fs4.default.readFileSync(envPath, "utf-8");
|
|
18006
|
+
} catch {
|
|
18007
|
+
}
|
|
18008
|
+
if (envContent.includes("BURN0_API_KEY=")) {
|
|
18009
|
+
envContent = envContent.replace(/BURN0_API_KEY=.*/, `BURN0_API_KEY=${apiKey}`);
|
|
18010
|
+
} else {
|
|
18011
|
+
envContent += `${envContent && !envContent.endsWith("\n") ? "\n" : ""}BURN0_API_KEY=${apiKey}
|
|
18012
|
+
`;
|
|
18013
|
+
}
|
|
18014
|
+
import_node_fs4.default.writeFileSync(envPath, envContent);
|
|
18015
|
+
let exampleContent = "";
|
|
18016
|
+
try {
|
|
18017
|
+
exampleContent = import_node_fs4.default.readFileSync(examplePath, "utf-8");
|
|
18018
|
+
} catch {
|
|
18019
|
+
}
|
|
18020
|
+
if (!exampleContent.includes("BURN0_API_KEY=")) {
|
|
18021
|
+
exampleContent += `${exampleContent && !exampleContent.endsWith("\n") ? "\n" : ""}BURN0_API_KEY=
|
|
18022
|
+
`;
|
|
18023
|
+
import_node_fs4.default.writeFileSync(examplePath, exampleContent);
|
|
18024
|
+
}
|
|
18025
|
+
}
|
|
18026
|
+
var import_node_child_process, import_node_fs4, import_node_path5;
|
|
18027
|
+
var init_api_key = __esm({
|
|
18028
|
+
"src/cli/api-key.ts"() {
|
|
18029
|
+
"use strict";
|
|
18030
|
+
init_esm15();
|
|
18031
|
+
init_source();
|
|
18032
|
+
import_node_child_process = require("child_process");
|
|
18033
|
+
import_node_fs4 = __toESM(require("fs"));
|
|
18034
|
+
import_node_path5 = __toESM(require("path"));
|
|
18035
|
+
}
|
|
18036
|
+
});
|
|
18037
|
+
|
|
17951
18038
|
// src/cli/init.ts
|
|
17952
18039
|
var init_exports = {};
|
|
17953
18040
|
__export(init_exports, {
|
|
18041
|
+
ensureGitignore: () => ensureGitignore,
|
|
17954
18042
|
runInit: () => runInit
|
|
17955
18043
|
});
|
|
17956
18044
|
async function runInit() {
|
|
@@ -17958,7 +18046,7 @@ async function runInit() {
|
|
|
17958
18046
|
await _runInit();
|
|
17959
18047
|
} catch (err) {
|
|
17960
18048
|
if (err.name === "ExitPromptError" || err.message?.includes("SIGINT")) {
|
|
17961
|
-
console.log("\n\n Cancelled. Run `burn0 init`
|
|
18049
|
+
console.log("\n\n Cancelled. Run `npx burn0 init` when ready.\n");
|
|
17962
18050
|
process.exit(0);
|
|
17963
18051
|
}
|
|
17964
18052
|
throw err;
|
|
@@ -17966,160 +18054,115 @@ async function runInit() {
|
|
|
17966
18054
|
}
|
|
17967
18055
|
async function _runInit() {
|
|
17968
18056
|
const cwd = process.cwd();
|
|
17969
|
-
|
|
17970
|
-
const
|
|
17971
|
-
|
|
17972
|
-
|
|
17973
|
-
${"b::::::b"} ${o("00:::::::::00")}
|
|
17974
|
-
${"b::::::b"} ${o("00:::::::::::::00")}
|
|
17975
|
-
${" b:::::b"} ${o("0:::::::000:::::::0")}
|
|
17976
|
-
${" b:::::bbbbbbbbb"} ${"uuuuuu uuuuuu"} ${"rrrrr rrrrrrrrr"} ${"nnnn nnnnnnnn"} ${o("0::::::0 0::::::0")}
|
|
17977
|
-
${" b::::::::::::::bb"} ${"u::::u u::::u"} ${"r::::rrr:::::::::r"} ${"n:::nn::::::::nn"} ${o("0:::::0 0:::::0")}
|
|
17978
|
-
${" b::::::::::::::::b"} ${"u::::u u::::u"} ${"r:::::::::::::::::r"} ${"n::::::::::::::nn"} ${o("0:::::0 0:::::0")}
|
|
17979
|
-
${" b:::::bbbbb:::::::b"}${"u::::u u::::u"} ${"rr::::::rrrrr::::::r"}${"nn:::::::::::::::n"}${o("0:::::0 000 0:::::0")}
|
|
17980
|
-
${" b:::::b b::::::b"}${"u::::u u::::u"} ${"r:::::r r:::::r"} ${"n:::::nnnn:::::n"}${o("0:::::0 000 0:::::0")}
|
|
17981
|
-
${" b:::::b b:::::b"}${"u::::u u::::u"} ${"r:::::r rrrrrrr"} ${"n::::n n::::n"}${o("0:::::0 0:::::0")}
|
|
17982
|
-
${" b:::::b b:::::b"}${"u::::u u::::u"} ${"r:::::r"} ${"n::::n n::::n"}${o("0:::::0 0:::::0")}
|
|
17983
|
-
${" b:::::b b:::::b"}${"u:::::uuuu:::::u"} ${"r:::::r"} ${"n::::n n::::n"}${o("0::::::0 0::::::0")}
|
|
17984
|
-
${" b:::::bbbbbb::::::b"}${"u:::::::::::::::uu"}${"r:::::r"} ${"n::::n n::::n"}${o("0:::::::000:::::::0")}
|
|
17985
|
-
${" b::::::::::::::::b"} ${"u:::::::::::::::u"}${"r:::::r"} ${"n::::n n::::n"} ${o("00:::::::::::::00")}
|
|
17986
|
-
${" b:::::::::::::::b"} ${"uu::::::::uu:::u"}${"r:::::r"} ${"n::::n n::::n"} ${o("00:::::::::00")}
|
|
17987
|
-
${" bbbbbbbbbbbbbbbb"} ${"uuuuuuuu uuuu"}${"rrrrrrr"} ${"nnnnnn nnnnnn"} ${o("000000000")}
|
|
17988
|
-
`;
|
|
17989
|
-
console.log(banner);
|
|
17990
|
-
console.log(source_default.dim(" Track every API call. Know your costs.\n"));
|
|
17991
|
-
console.log(source_default.dim(" Scanning your project...\n"));
|
|
17992
|
-
const services = detectServices(cwd);
|
|
17993
|
-
if (services.length === 0) {
|
|
17994
|
-
console.log(source_default.yellow(" No known API services found in package.json."));
|
|
17995
|
-
console.log(source_default.dim(" burn0 will still track any outgoing HTTP calls.\n"));
|
|
17996
|
-
} else {
|
|
17997
|
-
console.log(source_default.bold(` Detected ${services.length} services:
|
|
17998
|
-
`));
|
|
17999
|
-
console.log(source_default.dim(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
18000
|
-
for (const svc of services) {
|
|
18001
|
-
const label = svc.autopriced ? source_default.green(" \u2713") : source_default.yellow(" \u25C6");
|
|
18002
|
-
const category = svc.category === "llm" ? source_default.blue("LLM") : svc.autopriced ? source_default.magenta("API") : source_default.yellow("API");
|
|
18003
|
-
const pricing = svc.autopriced ? source_default.dim("auto-priced") : source_default.yellow("plan needed");
|
|
18004
|
-
console.log(`${label} ${svc.package.padEnd(25)} ${category} ${pricing}`);
|
|
18005
|
-
}
|
|
18006
|
-
console.log(source_default.dim(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
18007
|
-
console.log();
|
|
18008
|
-
}
|
|
18009
|
-
console.log(source_default.dim(" Scanning your codebase for API usage...\n"));
|
|
18057
|
+
console.log(source_default.dim("\n burn0 \u2014 track every API cost\n"));
|
|
18058
|
+
const apiKey = await promptApiKey(cwd);
|
|
18059
|
+
console.log(source_default.dim("\n Scanning your project...\n"));
|
|
18060
|
+
const pkgServices = detectServices(cwd);
|
|
18010
18061
|
const scannedServices = scanCodebase(cwd);
|
|
18011
|
-
const detectedNames = new Set(
|
|
18062
|
+
const detectedNames = new Set(pkgServices.map((s) => s.name));
|
|
18012
18063
|
const newFromScan = scannedServices.filter((s) => !detectedNames.has(s.name));
|
|
18013
|
-
|
|
18014
|
-
|
|
18015
|
-
|
|
18016
|
-
|
|
18017
|
-
|
|
18018
|
-
|
|
18019
|
-
|
|
18020
|
-
|
|
18021
|
-
const more = svc.foundIn.length > 3 ? ` +${svc.foundIn.length - 3} more` : "";
|
|
18022
|
-
console.log(` ${source_default.yellow(" \u25C6")} ${displayName.padEnd(20)} ${source_default.dim(`found in: ${files}${more}`)}`);
|
|
18023
|
-
}
|
|
18024
|
-
console.log(source_default.dim(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
18025
|
-
console.log();
|
|
18026
|
-
for (const svc of newFromScan) {
|
|
18027
|
-
detectedNames.add(svc.name);
|
|
18028
|
-
}
|
|
18029
|
-
} else {
|
|
18030
|
-
console.log(source_default.dim(" No additional services found in codebase.\n"));
|
|
18064
|
+
const allDetected = [];
|
|
18065
|
+
for (const svc of pkgServices) {
|
|
18066
|
+
const entry = SERVICE_CATALOG.find((c) => c.name === svc.name);
|
|
18067
|
+
allDetected.push({
|
|
18068
|
+
name: svc.name,
|
|
18069
|
+
displayName: entry?.displayName ?? svc.name,
|
|
18070
|
+
autopriced: svc.autopriced
|
|
18071
|
+
});
|
|
18031
18072
|
}
|
|
18032
|
-
const
|
|
18033
|
-
|
|
18034
|
-
|
|
18035
|
-
|
|
18036
|
-
|
|
18037
|
-
|
|
18038
|
-
|
|
18039
|
-
let apiKey;
|
|
18040
|
-
if (keyChoice === "yes") {
|
|
18041
|
-
apiKey = await esm_default5({ message: "Paste your API key:" });
|
|
18042
|
-
writeApiKeyToEnv(cwd, apiKey);
|
|
18043
|
-
console.log(source_default.green(" \u2713 Added BURN0_API_KEY to .env"));
|
|
18073
|
+
for (const svc of newFromScan) {
|
|
18074
|
+
const entry = SERVICE_CATALOG.find((c) => c.name === svc.name);
|
|
18075
|
+
allDetected.push({
|
|
18076
|
+
name: svc.name,
|
|
18077
|
+
displayName: entry?.displayName ?? svc.name,
|
|
18078
|
+
autopriced: entry?.pricingType !== "fixed"
|
|
18079
|
+
});
|
|
18044
18080
|
}
|
|
18045
18081
|
const serviceConfigs = [];
|
|
18046
|
-
|
|
18047
|
-
|
|
18048
|
-
|
|
18049
|
-
|
|
18050
|
-
|
|
18051
|
-
|
|
18052
|
-
|
|
18053
|
-
|
|
18054
|
-
|
|
18055
|
-
|
|
18056
|
-
|
|
18057
|
-
|
|
18058
|
-
|
|
18059
|
-
|
|
18060
|
-
|
|
18061
|
-
|
|
18062
|
-
|
|
18063
|
-
|
|
18064
|
-
|
|
18065
|
-
|
|
18066
|
-
|
|
18067
|
-
|
|
18082
|
+
if (allDetected.length > 0) {
|
|
18083
|
+
console.log(source_default.bold(` Auto-detected ${allDetected.length} services:
|
|
18084
|
+
`));
|
|
18085
|
+
for (const svc of allDetected) {
|
|
18086
|
+
const tag = svc.autopriced ? source_default.dim("auto-priced") : source_default.yellow("needs plan");
|
|
18087
|
+
console.log(` ${source_default.green(" \u2713")} ${svc.displayName.padEnd(20)} ${tag}`);
|
|
18088
|
+
}
|
|
18089
|
+
console.log();
|
|
18090
|
+
const fixedTier = allDetected.filter((s) => !s.autopriced);
|
|
18091
|
+
if (fixedTier.length > 0) {
|
|
18092
|
+
for (const svc of fixedTier) {
|
|
18093
|
+
const entry = SERVICE_CATALOG.find((c) => c.name === svc.name);
|
|
18094
|
+
if (entry?.plans) {
|
|
18095
|
+
const plan = await esm_default11({
|
|
18096
|
+
message: `${entry.displayName} \u2014 which plan?`,
|
|
18097
|
+
choices: [
|
|
18098
|
+
...entry.plans.map((p) => ({ name: p.name, value: p.value })),
|
|
18099
|
+
{ name: "Skip", value: "skip" }
|
|
18100
|
+
]
|
|
18101
|
+
});
|
|
18102
|
+
if (plan !== "skip") {
|
|
18103
|
+
const selected = entry.plans.find((p) => p.value === plan);
|
|
18104
|
+
serviceConfigs.push({ name: svc.name, plan, monthlyCost: selected?.monthly });
|
|
18105
|
+
} else {
|
|
18106
|
+
serviceConfigs.push({ name: svc.name });
|
|
18107
|
+
}
|
|
18108
|
+
}
|
|
18068
18109
|
}
|
|
18069
18110
|
}
|
|
18111
|
+
for (const svc of allDetected.filter((s) => s.autopriced)) {
|
|
18112
|
+
serviceConfigs.push({ name: svc.name });
|
|
18113
|
+
}
|
|
18114
|
+
} else {
|
|
18115
|
+
console.log(source_default.dim(" No services detected.\n"));
|
|
18070
18116
|
}
|
|
18071
|
-
const additionalServices = SERVICE_CATALOG.filter((s) => !detectedNames.has(s.name));
|
|
18072
18117
|
const addMore = await esm_default4({
|
|
18073
|
-
message: "
|
|
18118
|
+
message: "Add other services you use?",
|
|
18074
18119
|
default: false
|
|
18075
18120
|
});
|
|
18076
18121
|
if (addMore) {
|
|
18077
|
-
const
|
|
18078
|
-
const
|
|
18079
|
-
const
|
|
18080
|
-
const
|
|
18081
|
-
|
|
18122
|
+
const alreadyAdded = new Set(serviceConfigs.map((s) => s.name));
|
|
18123
|
+
const additionalServices = SERVICE_CATALOG.filter((s) => !alreadyAdded.has(s.name));
|
|
18124
|
+
const llmChoices = additionalServices.filter((s) => s.category === "llm").map((s) => ({ name: s.displayName, value: s.name }));
|
|
18125
|
+
const apiChoices = additionalServices.filter((s) => s.category === "api").map((s) => ({ name: s.displayName, value: s.name }));
|
|
18126
|
+
const infraChoices = additionalServices.filter((s) => s.category === "infra").map((s) => ({ name: s.displayName, value: s.name }));
|
|
18127
|
+
const additional = await esm_default2({
|
|
18128
|
+
message: "Select services:",
|
|
18082
18129
|
choices: [
|
|
18083
|
-
...llmChoices.length ? [{ name: source_default.bold.blue("\u2500\u2500 LLM Providers \u2500\u2500"), value: "
|
|
18130
|
+
...llmChoices.length ? [{ name: source_default.bold.blue("\u2500\u2500 LLM Providers \u2500\u2500"), value: "__sep", disabled: true }] : [],
|
|
18084
18131
|
...llmChoices,
|
|
18085
|
-
...apiChoices.length ? [{ name: source_default.bold.magenta("\u2500\u2500 API Services \u2500\u2500"), value: "
|
|
18132
|
+
...apiChoices.length ? [{ name: source_default.bold.magenta("\u2500\u2500 API Services \u2500\u2500"), value: "__sep2", disabled: true }] : [],
|
|
18086
18133
|
...apiChoices,
|
|
18087
|
-
...infraChoices.length ? [{ name: source_default.bold.yellow("\u2500\u2500 Infrastructure \u2500\u2500"), value: "
|
|
18134
|
+
...infraChoices.length ? [{ name: source_default.bold.yellow("\u2500\u2500 Infrastructure \u2500\u2500"), value: "__sep3", disabled: true }] : [],
|
|
18088
18135
|
...infraChoices
|
|
18089
18136
|
]
|
|
18090
18137
|
});
|
|
18091
|
-
for (const name of
|
|
18092
|
-
if (name.startsWith("
|
|
18093
|
-
const
|
|
18094
|
-
if (
|
|
18095
|
-
if (catalogEntry.pricingType === "fixed" && catalogEntry.plans) {
|
|
18138
|
+
for (const name of additional) {
|
|
18139
|
+
if (name.startsWith("__sep")) continue;
|
|
18140
|
+
const entry = SERVICE_CATALOG.find((c) => c.name === name);
|
|
18141
|
+
if (entry?.pricingType === "fixed" && entry.plans) {
|
|
18096
18142
|
const plan = await esm_default11({
|
|
18097
|
-
message: `${
|
|
18143
|
+
message: `${entry.displayName} \u2014 which plan?`,
|
|
18098
18144
|
choices: [
|
|
18099
|
-
...
|
|
18100
|
-
{ name: "Skip
|
|
18145
|
+
...entry.plans.map((p) => ({ name: p.name, value: p.value })),
|
|
18146
|
+
{ name: "Skip", value: "skip" }
|
|
18101
18147
|
]
|
|
18102
18148
|
});
|
|
18103
18149
|
if (plan !== "skip") {
|
|
18104
|
-
const
|
|
18105
|
-
serviceConfigs.push({ name, plan, monthlyCost:
|
|
18150
|
+
const selected = entry.plans.find((p) => p.value === plan);
|
|
18151
|
+
serviceConfigs.push({ name, plan, monthlyCost: selected?.monthly });
|
|
18152
|
+
} else {
|
|
18153
|
+
serviceConfigs.push({ name });
|
|
18106
18154
|
}
|
|
18107
18155
|
} else {
|
|
18108
18156
|
serviceConfigs.push({ name });
|
|
18109
18157
|
}
|
|
18110
18158
|
}
|
|
18111
18159
|
}
|
|
18112
|
-
|
|
18113
|
-
let defaultName = "my-project";
|
|
18160
|
+
let projectName = "my-project";
|
|
18114
18161
|
try {
|
|
18115
|
-
const pkg = JSON.parse(
|
|
18116
|
-
if (pkg.name)
|
|
18162
|
+
const pkg = JSON.parse(import_node_fs5.default.readFileSync(import_node_path6.default.join(cwd, "package.json"), "utf-8"));
|
|
18163
|
+
if (pkg.name) projectName = pkg.name;
|
|
18117
18164
|
} catch {
|
|
18118
18165
|
}
|
|
18119
|
-
const projectName = await esm_default5({
|
|
18120
|
-
message: "Project name? (used in dashboard)",
|
|
18121
|
-
default: defaultName
|
|
18122
|
-
});
|
|
18123
18166
|
writeConfig(cwd, {
|
|
18124
18167
|
projectName,
|
|
18125
18168
|
services: serviceConfigs.map((s) => ({
|
|
@@ -18129,87 +18172,78 @@ async function _runInit() {
|
|
|
18129
18172
|
monthlyCost: s.monthlyCost
|
|
18130
18173
|
}))
|
|
18131
18174
|
});
|
|
18175
|
+
if (apiKey) {
|
|
18176
|
+
try {
|
|
18177
|
+
const apiUrl = process.env.BURN0_API_URL ?? "https://api.burn0.dev";
|
|
18178
|
+
const res = await fetch(`${apiUrl}/v1/projects/config`, {
|
|
18179
|
+
method: "POST",
|
|
18180
|
+
headers: {
|
|
18181
|
+
"Content-Type": "application/json",
|
|
18182
|
+
"Authorization": `Bearer ${apiKey}`
|
|
18183
|
+
},
|
|
18184
|
+
body: JSON.stringify({
|
|
18185
|
+
services: serviceConfigs.map((s) => ({
|
|
18186
|
+
name: s.name,
|
|
18187
|
+
pricingModel: s.plan ? "fixed-tier" : "auto",
|
|
18188
|
+
plan: s.plan,
|
|
18189
|
+
monthlyCost: s.monthlyCost
|
|
18190
|
+
}))
|
|
18191
|
+
})
|
|
18192
|
+
});
|
|
18193
|
+
if (res.ok) {
|
|
18194
|
+
console.log(source_default.green(" \u2713 Config synced to burn0.dev"));
|
|
18195
|
+
}
|
|
18196
|
+
} catch {
|
|
18197
|
+
}
|
|
18198
|
+
}
|
|
18132
18199
|
ensureGitignore(cwd, ".burn0/");
|
|
18133
|
-
const gitignorePath =
|
|
18200
|
+
const gitignorePath = import_node_path6.default.join(cwd, ".gitignore");
|
|
18134
18201
|
let gitignoreContent = "";
|
|
18135
18202
|
try {
|
|
18136
|
-
gitignoreContent =
|
|
18203
|
+
gitignoreContent = import_node_fs5.default.readFileSync(gitignorePath, "utf-8");
|
|
18137
18204
|
} catch {
|
|
18138
18205
|
}
|
|
18139
|
-
|
|
18140
|
-
|
|
18141
|
-
|
|
18206
|
+
const gitignoreLines = gitignoreContent.split("\n").map((l) => l.trim());
|
|
18207
|
+
if (!gitignoreLines.includes(".env")) {
|
|
18208
|
+
ensureGitignore(cwd, ".env");
|
|
18209
|
+
console.log(source_default.green(" \u2713 Added .env to .gitignore (protects your API keys)"));
|
|
18142
18210
|
}
|
|
18143
18211
|
console.log("");
|
|
18144
|
-
console.log(source_default.green(" \u2713
|
|
18145
|
-
console.log(source_default.green(" \u2713 Added .burn0/ to .gitignore"));
|
|
18212
|
+
console.log(source_default.green(" \u2713 Setup complete"));
|
|
18146
18213
|
console.log("");
|
|
18147
|
-
console.log(source_default.
|
|
18148
|
-
console.log(source_default.
|
|
18149
|
-
console.log(
|
|
18150
|
-
console.log(source_default.
|
|
18151
|
-
console.log(source_default.bold(" \u2502") + source_default.white(" import 'burn0' ") + source_default.bold("\u2502"));
|
|
18152
|
-
console.log(source_default.bold(" \u2502 \u2502"));
|
|
18153
|
-
console.log(source_default.bold(" \u2502") + source_default.cyan(" 2. Optional \u2014 track specific features: ") + source_default.bold("\u2502"));
|
|
18154
|
-
console.log(source_default.bold(" \u2502") + source_default.white(" import { track } from 'burn0' ") + source_default.bold("\u2502"));
|
|
18155
|
-
console.log(source_default.bold(" \u2502") + source_default.white(" track('feat', { userId }, async () => {})") + source_default.bold("\u2502"));
|
|
18156
|
-
console.log(source_default.bold(" \u2502 \u2502"));
|
|
18157
|
-
console.log(source_default.bold(" \u2502") + source_default.cyan(" 3. Check your costs: ") + source_default.bold("\u2502"));
|
|
18158
|
-
console.log(source_default.bold(" \u2502") + source_default.white(" burn0 report ") + source_default.bold("\u2502"));
|
|
18159
|
-
console.log(source_default.bold(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
18214
|
+
console.log(source_default.dim(" Add this to your entry file:"));
|
|
18215
|
+
console.log(source_default.white(" import '@burn0/burn0'"));
|
|
18216
|
+
console.log("");
|
|
18217
|
+
console.log(source_default.dim(" Then run your app to see costs."));
|
|
18160
18218
|
console.log("");
|
|
18161
|
-
}
|
|
18162
|
-
function writeApiKeyToEnv(cwd, apiKey) {
|
|
18163
|
-
const envPath = import_node_path5.default.join(cwd, ".env");
|
|
18164
|
-
const examplePath = import_node_path5.default.join(cwd, ".env.example");
|
|
18165
|
-
let envContent = "";
|
|
18166
|
-
try {
|
|
18167
|
-
envContent = import_node_fs4.default.readFileSync(envPath, "utf-8");
|
|
18168
|
-
} catch {
|
|
18169
|
-
}
|
|
18170
|
-
if (envContent.includes("BURN0_API_KEY=")) {
|
|
18171
|
-
envContent = envContent.replace(/BURN0_API_KEY=.*/, `BURN0_API_KEY=${apiKey}`);
|
|
18172
|
-
} else {
|
|
18173
|
-
envContent += `${envContent && !envContent.endsWith("\n") ? "\n" : ""}BURN0_API_KEY=${apiKey}
|
|
18174
|
-
`;
|
|
18175
|
-
}
|
|
18176
|
-
import_node_fs4.default.writeFileSync(envPath, envContent);
|
|
18177
|
-
let exampleContent = "";
|
|
18178
|
-
try {
|
|
18179
|
-
exampleContent = import_node_fs4.default.readFileSync(examplePath, "utf-8");
|
|
18180
|
-
} catch {
|
|
18181
|
-
}
|
|
18182
|
-
if (!exampleContent.includes("BURN0_API_KEY=")) {
|
|
18183
|
-
exampleContent += `${exampleContent && !exampleContent.endsWith("\n") ? "\n" : ""}BURN0_API_KEY=
|
|
18184
|
-
`;
|
|
18185
|
-
import_node_fs4.default.writeFileSync(examplePath, exampleContent);
|
|
18186
|
-
}
|
|
18187
18219
|
}
|
|
18188
18220
|
function ensureGitignore(cwd, entry) {
|
|
18189
|
-
const gitignorePath =
|
|
18221
|
+
const gitignorePath = import_node_path6.default.join(cwd, ".gitignore");
|
|
18190
18222
|
let content = "";
|
|
18191
18223
|
try {
|
|
18192
|
-
content =
|
|
18224
|
+
content = import_node_fs5.default.readFileSync(gitignorePath, "utf-8");
|
|
18193
18225
|
} catch {
|
|
18194
18226
|
}
|
|
18195
|
-
|
|
18227
|
+
const lines = content.split("\n").map((l) => l.trim());
|
|
18228
|
+
if (!lines.includes(entry)) {
|
|
18196
18229
|
content += `${content && !content.endsWith("\n") ? "\n" : ""}${entry}
|
|
18197
18230
|
`;
|
|
18198
|
-
|
|
18231
|
+
import_node_fs5.default.writeFileSync(gitignorePath, content);
|
|
18199
18232
|
}
|
|
18200
18233
|
}
|
|
18201
|
-
var
|
|
18234
|
+
var import_node_fs5, import_node_path6;
|
|
18202
18235
|
var init_init = __esm({
|
|
18203
18236
|
"src/cli/init.ts"() {
|
|
18204
18237
|
"use strict";
|
|
18205
18238
|
init_esm15();
|
|
18206
18239
|
init_source();
|
|
18207
|
-
|
|
18208
|
-
|
|
18240
|
+
import_node_fs5 = __toESM(require("fs"));
|
|
18241
|
+
import_node_path6 = __toESM(require("path"));
|
|
18209
18242
|
init_detect();
|
|
18210
18243
|
init_scan();
|
|
18211
18244
|
init_store();
|
|
18212
18245
|
init_catalog();
|
|
18246
|
+
init_api_key();
|
|
18213
18247
|
}
|
|
18214
18248
|
});
|
|
18215
18249
|
|
|
@@ -18269,43 +18303,17 @@ __export(connect_exports, {
|
|
|
18269
18303
|
});
|
|
18270
18304
|
async function runConnect() {
|
|
18271
18305
|
const cwd = process.cwd();
|
|
18272
|
-
const
|
|
18273
|
-
if (
|
|
18274
|
-
|
|
18275
|
-
|
|
18276
|
-
}
|
|
18277
|
-
const envPath = import_node_path6.default.join(cwd, ".env");
|
|
18278
|
-
let content = "";
|
|
18279
|
-
try {
|
|
18280
|
-
content = import_node_fs5.default.readFileSync(envPath, "utf-8");
|
|
18281
|
-
} catch {
|
|
18282
|
-
}
|
|
18283
|
-
if (content.includes("BURN0_API_KEY=")) {
|
|
18284
|
-
content = content.replace(/BURN0_API_KEY=.*/, `BURN0_API_KEY=${apiKey}`);
|
|
18285
|
-
} else {
|
|
18286
|
-
content += `${content && !content.endsWith("\n") ? "\n" : ""}BURN0_API_KEY=${apiKey}
|
|
18287
|
-
`;
|
|
18288
|
-
}
|
|
18289
|
-
import_node_fs5.default.writeFileSync(envPath, content);
|
|
18290
|
-
console.log(source_default.green("\n \u2713 Added BURN0_API_KEY to .env"));
|
|
18291
|
-
try {
|
|
18292
|
-
const pkg = JSON.parse(import_node_fs5.default.readFileSync(import_node_path6.default.join(cwd, "package.json"), "utf-8"));
|
|
18293
|
-
if (pkg.name) {
|
|
18294
|
-
console.log(source_default.green(` \u2713 Connected to project "${pkg.name}" on burn0.dev
|
|
18295
|
-
`));
|
|
18296
|
-
}
|
|
18297
|
-
} catch {
|
|
18298
|
-
console.log(source_default.green(" \u2713 Connected to burn0.dev\n"));
|
|
18306
|
+
const key = await promptApiKey(cwd);
|
|
18307
|
+
if (key) {
|
|
18308
|
+
ensureGitignore(cwd, ".env");
|
|
18309
|
+
ensureGitignore(cwd, ".burn0/");
|
|
18299
18310
|
}
|
|
18300
18311
|
}
|
|
18301
|
-
var import_node_fs5, import_node_path6;
|
|
18302
18312
|
var init_connect = __esm({
|
|
18303
18313
|
"src/cli/connect.ts"() {
|
|
18304
18314
|
"use strict";
|
|
18305
|
-
|
|
18306
|
-
|
|
18307
|
-
import_node_fs5 = __toESM(require("fs"));
|
|
18308
|
-
import_node_path6 = __toESM(require("path"));
|
|
18315
|
+
init_api_key();
|
|
18316
|
+
init_init();
|
|
18309
18317
|
}
|
|
18310
18318
|
});
|
|
18311
18319
|
|
|
@@ -18370,46 +18378,297 @@ var init_local = __esm({
|
|
|
18370
18378
|
}
|
|
18371
18379
|
});
|
|
18372
18380
|
|
|
18381
|
+
// src/transport/local-pricing.ts
|
|
18382
|
+
async function fetchPricing(apiUrl, fetchFn) {
|
|
18383
|
+
try {
|
|
18384
|
+
const cachePath = import_node_path8.default.join(process.cwd(), CACHE_FILE);
|
|
18385
|
+
if (import_node_fs7.default.existsSync(cachePath)) {
|
|
18386
|
+
const raw = import_node_fs7.default.readFileSync(cachePath, "utf-8");
|
|
18387
|
+
const cached = JSON.parse(raw);
|
|
18388
|
+
if (Date.now() - cached.cached_at < CACHE_TTL_MS) {
|
|
18389
|
+
pricingData = cached;
|
|
18390
|
+
return;
|
|
18391
|
+
}
|
|
18392
|
+
}
|
|
18393
|
+
} catch {
|
|
18394
|
+
}
|
|
18395
|
+
try {
|
|
18396
|
+
const response = await fetchFn(`${apiUrl}/v1/pricing`, {
|
|
18397
|
+
headers: { "Accept": "application/json" }
|
|
18398
|
+
});
|
|
18399
|
+
if (response.ok) {
|
|
18400
|
+
const data = await response.json();
|
|
18401
|
+
pricingData = data;
|
|
18402
|
+
try {
|
|
18403
|
+
const dir = import_node_path8.default.join(process.cwd(), ".burn0");
|
|
18404
|
+
if (!import_node_fs7.default.existsSync(dir)) import_node_fs7.default.mkdirSync(dir, { recursive: true });
|
|
18405
|
+
import_node_fs7.default.writeFileSync(
|
|
18406
|
+
import_node_path8.default.join(process.cwd(), CACHE_FILE),
|
|
18407
|
+
JSON.stringify({ ...data, cached_at: Date.now() }, null, 2)
|
|
18408
|
+
);
|
|
18409
|
+
} catch {
|
|
18410
|
+
}
|
|
18411
|
+
}
|
|
18412
|
+
} catch {
|
|
18413
|
+
}
|
|
18414
|
+
}
|
|
18415
|
+
function estimateLocalCost(event) {
|
|
18416
|
+
if (FREE_SERVICES.has(event.service)) {
|
|
18417
|
+
return { type: "free" };
|
|
18418
|
+
}
|
|
18419
|
+
if (event.service.startsWith("unknown:")) {
|
|
18420
|
+
return { type: "unknown" };
|
|
18421
|
+
}
|
|
18422
|
+
if (!pricingData) {
|
|
18423
|
+
return { type: "loading" };
|
|
18424
|
+
}
|
|
18425
|
+
const svc = pricingData.services[event.service];
|
|
18426
|
+
if (!svc) {
|
|
18427
|
+
return { type: "unknown" };
|
|
18428
|
+
}
|
|
18429
|
+
if (svc.type === "llm") {
|
|
18430
|
+
if (!event.model) return { type: "unknown" };
|
|
18431
|
+
if (event.tokens_in === void 0 || event.tokens_out === void 0) {
|
|
18432
|
+
return { type: "no-tokens" };
|
|
18433
|
+
}
|
|
18434
|
+
let prices = svc.models[event.model];
|
|
18435
|
+
if (!prices) {
|
|
18436
|
+
const match = Object.keys(svc.models).find((m) => event.model.startsWith(m));
|
|
18437
|
+
if (match) prices = svc.models[match];
|
|
18438
|
+
}
|
|
18439
|
+
if (prices) {
|
|
18440
|
+
const inputCost = event.tokens_in / 1e6 * prices[0];
|
|
18441
|
+
const outputCost = event.tokens_out / 1e6 * prices[1];
|
|
18442
|
+
return { type: "priced", cost: inputCost + outputCost };
|
|
18443
|
+
}
|
|
18444
|
+
return { type: "unknown" };
|
|
18445
|
+
}
|
|
18446
|
+
if (svc.type === "api") {
|
|
18447
|
+
const endpoint = event.endpoint ?? "";
|
|
18448
|
+
for (const [prefix, cost] of Object.entries(svc.endpoints)) {
|
|
18449
|
+
if (prefix !== "*" && endpoint.startsWith(prefix)) {
|
|
18450
|
+
return cost === 0 ? { type: "free" } : { type: "priced", cost };
|
|
18451
|
+
}
|
|
18452
|
+
}
|
|
18453
|
+
const defaultCost = svc.endpoints["*"];
|
|
18454
|
+
if (defaultCost !== void 0) {
|
|
18455
|
+
return defaultCost === 0 ? { type: "free" } : { type: "priced", cost: defaultCost };
|
|
18456
|
+
}
|
|
18457
|
+
return { type: "unknown" };
|
|
18458
|
+
}
|
|
18459
|
+
if (svc.type === "fixed") {
|
|
18460
|
+
return { type: "fixed-tier" };
|
|
18461
|
+
}
|
|
18462
|
+
return { type: "unknown" };
|
|
18463
|
+
}
|
|
18464
|
+
var import_node_fs7, import_node_path8, pricingData, CACHE_FILE, CACHE_TTL_MS, FREE_SERVICES;
|
|
18465
|
+
var init_local_pricing = __esm({
|
|
18466
|
+
"src/transport/local-pricing.ts"() {
|
|
18467
|
+
"use strict";
|
|
18468
|
+
import_node_fs7 = __toESM(require("fs"));
|
|
18469
|
+
import_node_path8 = __toESM(require("path"));
|
|
18470
|
+
pricingData = null;
|
|
18471
|
+
CACHE_FILE = ".burn0/pricing-cache.json";
|
|
18472
|
+
CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
18473
|
+
FREE_SERVICES = /* @__PURE__ */ new Set([
|
|
18474
|
+
"github-api",
|
|
18475
|
+
"slack-api",
|
|
18476
|
+
"discord-api"
|
|
18477
|
+
]);
|
|
18478
|
+
}
|
|
18479
|
+
});
|
|
18480
|
+
|
|
18373
18481
|
// src/cli/report.ts
|
|
18374
18482
|
var report_exports = {};
|
|
18375
18483
|
__export(report_exports, {
|
|
18484
|
+
aggregateLocal: () => aggregateLocal,
|
|
18376
18485
|
runReport: () => runReport
|
|
18377
18486
|
});
|
|
18378
|
-
|
|
18379
|
-
|
|
18380
|
-
|
|
18381
|
-
|
|
18382
|
-
if (
|
|
18383
|
-
|
|
18384
|
-
|
|
18385
|
-
|
|
18386
|
-
|
|
18487
|
+
function getLocalDateStr(date) {
|
|
18488
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
18489
|
+
}
|
|
18490
|
+
function formatCost(cost) {
|
|
18491
|
+
if (cost >= 1) return `$${cost.toFixed(2)}`;
|
|
18492
|
+
if (cost >= 0.01) return `$${cost.toFixed(4)}`;
|
|
18493
|
+
return `$${cost.toFixed(6)}`;
|
|
18494
|
+
}
|
|
18495
|
+
function formatDateLabel(dateStr) {
|
|
18496
|
+
const d = /* @__PURE__ */ new Date(dateStr + "T12:00:00");
|
|
18497
|
+
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
18498
|
+
return `${months[d.getMonth()]} ${String(d.getDate()).padStart(2, " ")}`;
|
|
18499
|
+
}
|
|
18500
|
+
function makeBar(value, max, width) {
|
|
18501
|
+
if (max === 0) return "\u2591".repeat(width);
|
|
18502
|
+
const filled = Math.round(value / max * width);
|
|
18503
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
18504
|
+
}
|
|
18505
|
+
function aggregateLocal(events, days) {
|
|
18506
|
+
const now = /* @__PURE__ */ new Date();
|
|
18507
|
+
const cutoffDate = new Date(now);
|
|
18508
|
+
cutoffDate.setDate(cutoffDate.getDate() - (days - 1));
|
|
18509
|
+
const cutoffStr = getLocalDateStr(cutoffDate);
|
|
18510
|
+
const serviceCosts = {};
|
|
18511
|
+
const serviceCallCounts = {};
|
|
18512
|
+
const dayCosts = {};
|
|
18513
|
+
let totalCost = 0;
|
|
18514
|
+
let totalCalls = 0;
|
|
18515
|
+
let unpricedCount = 0;
|
|
18516
|
+
let loadingCount = 0;
|
|
18387
18517
|
for (const event of events) {
|
|
18388
|
-
|
|
18389
|
-
|
|
18518
|
+
const eventDate = new Date(event.timestamp);
|
|
18519
|
+
const eventDateStr = getLocalDateStr(eventDate);
|
|
18520
|
+
if (eventDateStr < cutoffStr) continue;
|
|
18521
|
+
totalCalls++;
|
|
18522
|
+
serviceCallCounts[event.service] = (serviceCallCounts[event.service] ?? 0) + 1;
|
|
18523
|
+
const estimate = estimateLocalCost(event);
|
|
18524
|
+
if (estimate.type === "priced" && estimate.cost > 0) {
|
|
18525
|
+
totalCost += estimate.cost;
|
|
18526
|
+
if (!serviceCosts[event.service]) serviceCosts[event.service] = { cost: 0, calls: 0 };
|
|
18527
|
+
serviceCosts[event.service].cost += estimate.cost;
|
|
18528
|
+
serviceCosts[event.service].calls++;
|
|
18529
|
+
if (!dayCosts[eventDateStr]) dayCosts[eventDateStr] = { cost: 0, calls: 0, services: {} };
|
|
18530
|
+
dayCosts[eventDateStr].cost += estimate.cost;
|
|
18531
|
+
dayCosts[eventDateStr].calls++;
|
|
18532
|
+
dayCosts[eventDateStr].services[event.service] = (dayCosts[eventDateStr].services[event.service] ?? 0) + estimate.cost;
|
|
18533
|
+
} else if (estimate.type === "free") {
|
|
18534
|
+
} else if (estimate.type === "loading") {
|
|
18535
|
+
loadingCount++;
|
|
18536
|
+
} else {
|
|
18537
|
+
unpricedCount++;
|
|
18390
18538
|
}
|
|
18391
|
-
byService[event.service].calls++;
|
|
18392
|
-
byService[event.service].tokens_in += event.tokens_in ?? 0;
|
|
18393
|
-
byService[event.service].tokens_out += event.tokens_out ?? 0;
|
|
18394
18539
|
}
|
|
18395
|
-
const
|
|
18396
|
-
const
|
|
18397
|
-
|
|
18398
|
-
|
|
18540
|
+
const byService = Object.entries(serviceCosts).map(([name, data]) => ({ name, cost: data.cost, calls: data.calls })).sort((a, b) => b.cost - a.cost);
|
|
18541
|
+
const allServiceCalls = Object.entries(serviceCallCounts).map(([name, calls]) => ({ name, calls })).sort((a, b) => b.calls - a.calls);
|
|
18542
|
+
const byDay = Object.entries(dayCosts).sort((a, b) => b[0].localeCompare(a[0])).map(([date, data]) => {
|
|
18543
|
+
const topServices = Object.entries(data.services).sort((a, b) => b[1] - a[1]).map(([name, cost]) => ({ name, cost }));
|
|
18544
|
+
return { date, cost: data.cost, calls: data.calls, topServices };
|
|
18545
|
+
});
|
|
18546
|
+
return {
|
|
18547
|
+
total: { cost: totalCost, calls: totalCalls },
|
|
18548
|
+
byService,
|
|
18549
|
+
byDay,
|
|
18550
|
+
allServiceCalls,
|
|
18551
|
+
unpricedCount,
|
|
18552
|
+
pricingAvailable: loadingCount < totalCalls || totalCalls === 0
|
|
18553
|
+
};
|
|
18554
|
+
}
|
|
18555
|
+
function renderCallCountOnly(data) {
|
|
18556
|
+
const maxCalls = data.allServiceCalls.length > 0 ? data.allServiceCalls[0].calls : 0;
|
|
18557
|
+
const maxNameLen = Math.max(...data.allServiceCalls.map((s) => s.name.length), 8);
|
|
18558
|
+
for (const svc of data.allServiceCalls) {
|
|
18559
|
+
const bar = makeBar(svc.calls, maxCalls, 20);
|
|
18560
|
+
console.log(` ${svc.name.padEnd(maxNameLen)} ${source_default.gray(`${String(svc.calls).padStart(5)} calls`)} ${source_default.cyan(bar)}`);
|
|
18561
|
+
}
|
|
18562
|
+
console.log();
|
|
18563
|
+
}
|
|
18564
|
+
function renderCostReport(data, label, showDaily, isToday) {
|
|
18565
|
+
console.log(`
|
|
18566
|
+
${source_default.hex("#FA5D19").bold("burn0 report")} ${source_default.gray(`\u2500\u2500 ${label}`)}
|
|
18567
|
+
`);
|
|
18568
|
+
if (data.total.calls === 0) {
|
|
18569
|
+
const msg = isToday ? "No calls today." : `No cost data yet. Run your app with \`import '@burn0/burn0'\` to start tracking.`;
|
|
18570
|
+
console.log(source_default.dim(` ${msg}
|
|
18571
|
+
`));
|
|
18572
|
+
return;
|
|
18573
|
+
}
|
|
18574
|
+
if (!data.pricingAvailable) {
|
|
18575
|
+
console.log(source_default.dim(` ${data.total.calls} calls tracked (pricing data not available)
|
|
18576
|
+
`));
|
|
18577
|
+
renderCallCountOnly(data);
|
|
18578
|
+
return;
|
|
18579
|
+
}
|
|
18580
|
+
if (data.total.cost === 0 && data.total.calls > 0) {
|
|
18581
|
+
console.log(source_default.dim(` ${data.total.calls} calls tracked (no pricing data available)
|
|
18399
18582
|
`));
|
|
18400
|
-
|
|
18401
|
-
|
|
18402
|
-
|
|
18403
|
-
|
|
18404
|
-
|
|
18583
|
+
renderCallCountOnly(data);
|
|
18584
|
+
return;
|
|
18585
|
+
}
|
|
18586
|
+
console.log(` ${source_default.bold("Total:")} ${source_default.green(formatCost(data.total.cost))} ${source_default.gray(`(${data.total.calls} calls)`)}
|
|
18587
|
+
`);
|
|
18588
|
+
const maxCost = data.byService.length > 0 ? data.byService[0].cost : 0;
|
|
18589
|
+
const maxNameLen = Math.max(...data.byService.map((s) => s.name.length), 8);
|
|
18590
|
+
for (const svc of data.byService) {
|
|
18591
|
+
const pct = data.total.cost > 0 ? Math.round(svc.cost / data.total.cost * 100) : 0;
|
|
18592
|
+
const bar = makeBar(svc.cost, maxCost, 20);
|
|
18593
|
+
console.log(` ${svc.name.padEnd(maxNameLen)} ${source_default.green(formatCost(svc.cost).padStart(10))} ${source_default.cyan(bar)} ${source_default.gray(`${String(pct).padStart(3)}%`)}`);
|
|
18594
|
+
}
|
|
18595
|
+
if (data.unpricedCount > 0) {
|
|
18596
|
+
console.log(source_default.dim(`
|
|
18597
|
+
+ ${data.unpricedCount} calls not priced`));
|
|
18598
|
+
}
|
|
18599
|
+
if (showDaily && data.byDay.length > 0) {
|
|
18600
|
+
console.log(`
|
|
18601
|
+
${source_default.gray("\u2500\u2500 daily \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}
|
|
18602
|
+
`);
|
|
18603
|
+
const maxDayCost = Math.max(...data.byDay.map((d) => d.cost));
|
|
18604
|
+
for (const day of data.byDay) {
|
|
18605
|
+
const dateLabel = formatDateLabel(day.date);
|
|
18606
|
+
const bar = makeBar(day.cost, maxDayCost, 12);
|
|
18607
|
+
const top2 = day.topServices.slice(0, 2).map((s) => `${s.name} ${formatCost(s.cost)}`).join(" \xB7 ");
|
|
18608
|
+
const more = day.topServices.length > 2 ? ` +${day.topServices.length - 2} more` : "";
|
|
18609
|
+
console.log(` ${source_default.gray(dateLabel)} ${source_default.green(formatCost(day.cost).padStart(10))} ${source_default.cyan(bar)} ${source_default.dim(top2 + more)}`);
|
|
18610
|
+
}
|
|
18611
|
+
}
|
|
18612
|
+
if (data.total.cost > 0) {
|
|
18613
|
+
const daysInPeriod = showDaily ? 7 : 1;
|
|
18614
|
+
const dailyRate = data.total.cost / daysInPeriod;
|
|
18615
|
+
const monthly = dailyRate * 30;
|
|
18616
|
+
console.log(`
|
|
18617
|
+
${source_default.gray("\u2500\u2500 projection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}`);
|
|
18618
|
+
console.log(` ${source_default.gray("~")}${source_default.green(formatCost(monthly))}${source_default.gray("/mo estimated")} ${source_default.dim(`(based on ${isToday ? "today" : "last 7 days"})`)}`);
|
|
18405
18619
|
}
|
|
18406
18620
|
console.log();
|
|
18407
18621
|
}
|
|
18622
|
+
async function fetchBackendReport(apiKey, days) {
|
|
18623
|
+
try {
|
|
18624
|
+
const controller = new AbortController();
|
|
18625
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
18626
|
+
const response = await globalThis.fetch(`${BURN0_API_URL}/v1/report?days=${days}`, {
|
|
18627
|
+
headers: { "Accept": "application/json", "Authorization": `Bearer ${apiKey}` },
|
|
18628
|
+
signal: controller.signal
|
|
18629
|
+
});
|
|
18630
|
+
clearTimeout(timeout);
|
|
18631
|
+
if (!response.ok) return null;
|
|
18632
|
+
const data = await response.json();
|
|
18633
|
+
return {
|
|
18634
|
+
total: data.total ?? { cost: 0, calls: 0 },
|
|
18635
|
+
byService: data.byService ?? [],
|
|
18636
|
+
byDay: data.byDay ?? [],
|
|
18637
|
+
allServiceCalls: (data.byService ?? []).map((s) => ({ name: s.name, calls: s.calls })),
|
|
18638
|
+
unpricedCount: 0,
|
|
18639
|
+
pricingAvailable: true
|
|
18640
|
+
};
|
|
18641
|
+
} catch {
|
|
18642
|
+
return null;
|
|
18643
|
+
}
|
|
18644
|
+
}
|
|
18645
|
+
async function runReport(options = {}) {
|
|
18646
|
+
const cwd = process.cwd();
|
|
18647
|
+
const days = options.today ? 1 : 7;
|
|
18648
|
+
const label = options.today ? "today" : "last 7 days";
|
|
18649
|
+
const apiKey = getApiKey();
|
|
18650
|
+
if (apiKey) {
|
|
18651
|
+
const backendData = await fetchBackendReport(apiKey, days);
|
|
18652
|
+
if (backendData) {
|
|
18653
|
+
renderCostReport(backendData, label, !options.today, !!options.today);
|
|
18654
|
+
return;
|
|
18655
|
+
}
|
|
18656
|
+
}
|
|
18657
|
+
await fetchPricing(BURN0_API_URL, globalThis.fetch);
|
|
18658
|
+
const ledger = new LocalLedger(cwd);
|
|
18659
|
+
const events = ledger.read();
|
|
18660
|
+
const data = aggregateLocal(events, days);
|
|
18661
|
+
renderCostReport(data, label, !options.today, !!options.today);
|
|
18662
|
+
}
|
|
18663
|
+
var BURN0_API_URL;
|
|
18408
18664
|
var init_report = __esm({
|
|
18409
18665
|
"src/cli/report.ts"() {
|
|
18410
18666
|
"use strict";
|
|
18411
18667
|
init_source();
|
|
18412
18668
|
init_local();
|
|
18669
|
+
init_local_pricing();
|
|
18670
|
+
init_env();
|
|
18671
|
+
BURN0_API_URL = process.env.BURN0_API_URL ?? "https://api.burn0.dev";
|
|
18413
18672
|
}
|
|
18414
18673
|
});
|
|
18415
18674
|
|
|
@@ -18423,29 +18682,29 @@ async function runDev(command) {
|
|
|
18423
18682
|
console.log("\n Usage: burn0 dev -- node app.js\n");
|
|
18424
18683
|
process.exit(1);
|
|
18425
18684
|
}
|
|
18426
|
-
const registerPath =
|
|
18685
|
+
const registerPath = import_node_path9.default.resolve(__dirname, "../register.js");
|
|
18427
18686
|
const [cmd, ...args] = command;
|
|
18428
18687
|
if (cmd === "node") {
|
|
18429
|
-
const child = (0,
|
|
18688
|
+
const child = (0, import_node_child_process2.spawn)(cmd, ["--require", registerPath, ...args], {
|
|
18430
18689
|
stdio: "inherit",
|
|
18431
18690
|
env: { ...process.env }
|
|
18432
18691
|
});
|
|
18433
18692
|
child.on("exit", (code) => process.exit(code ?? 0));
|
|
18434
18693
|
} else {
|
|
18435
18694
|
const nodeOptions = `--require ${registerPath} ${process.env.NODE_OPTIONS ?? ""}`;
|
|
18436
|
-
const child = (0,
|
|
18695
|
+
const child = (0, import_node_child_process2.spawn)(cmd, args, {
|
|
18437
18696
|
stdio: "inherit",
|
|
18438
18697
|
env: { ...process.env, NODE_OPTIONS: nodeOptions.trim() }
|
|
18439
18698
|
});
|
|
18440
18699
|
child.on("exit", (code) => process.exit(code ?? 0));
|
|
18441
18700
|
}
|
|
18442
18701
|
}
|
|
18443
|
-
var
|
|
18702
|
+
var import_node_child_process2, import_node_path9;
|
|
18444
18703
|
var init_dev = __esm({
|
|
18445
18704
|
"src/cli/dev.ts"() {
|
|
18446
18705
|
"use strict";
|
|
18447
|
-
|
|
18448
|
-
|
|
18706
|
+
import_node_child_process2 = require("child_process");
|
|
18707
|
+
import_node_path9 = __toESM(require("path"));
|
|
18449
18708
|
}
|
|
18450
18709
|
});
|
|
18451
18710
|
|
|
@@ -18481,9 +18740,9 @@ program2.command("connect").description("Add your burn0 API key").action(async (
|
|
|
18481
18740
|
const { runConnect: runConnect2 } = await Promise.resolve().then(() => (init_connect(), connect_exports));
|
|
18482
18741
|
await runConnect2();
|
|
18483
18742
|
});
|
|
18484
|
-
program2.command("report").description("Show cost summary").action(async () => {
|
|
18743
|
+
program2.command("report").description("Show cost summary").option("--today", "Show today only").action(async (options) => {
|
|
18485
18744
|
const { runReport: runReport2 } = await Promise.resolve().then(() => (init_report(), report_exports));
|
|
18486
|
-
await runReport2();
|
|
18745
|
+
await runReport2(options);
|
|
18487
18746
|
});
|
|
18488
18747
|
program2.command("dev").description("Run your app with burn0 cost tracking").argument("[command...]", "Command to run").passThroughOptions().allowUnknownOption().action(async (command) => {
|
|
18489
18748
|
const { runDev: runDev2 } = await Promise.resolve().then(() => (init_dev(), dev_exports));
|