@lightning-tools/lt 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/dist/api.d.ts +12 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +77 -0
- package/dist/commands/balance.d.ts +2 -0
- package/dist/commands/balance.d.ts.map +1 -0
- package/dist/commands/balance.js +21 -0
- package/dist/commands/login.d.ts +7 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +75 -0
- package/dist/commands/run.d.ts +6 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +40 -0
- package/dist/commands/set-key.d.ts +2 -0
- package/dist/commands/set-key.d.ts.map +1 -0
- package/dist/commands/set-key.js +11 -0
- package/dist/commands/start.d.ts +7 -0
- package/dist/commands/start.d.ts.map +1 -0
- package/dist/commands/start.js +137 -0
- package/dist/commands/tools.d.ts +2 -0
- package/dist/commands/tools.d.ts.map +1 -0
- package/dist/commands/tools.js +20 -0
- package/dist/commands/topup.d.ts +7 -0
- package/dist/commands/topup.d.ts.map +1 -0
- package/dist/commands/topup.js +68 -0
- package/dist/commands/wizard.d.ts +2 -0
- package/dist/commands/wizard.d.ts.map +1 -0
- package/dist/commands/wizard.js +176 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +27 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +4 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +206 -0
- package/dist/openapi.d.ts +20 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +62 -0
- package/dist/payment-poll.d.ts +10 -0
- package/dist/payment-poll.d.ts.map +1 -0
- package/dist/payment-poll.js +17 -0
- package/dist/qr.d.ts +2 -0
- package/dist/qr.d.ts.map +1 -0
- package/dist/qr.js +10 -0
- package/package.json +37 -0
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class ApiError extends Error {
|
|
2
|
+
readonly status: number;
|
|
3
|
+
constructor(status: number, message: string);
|
|
4
|
+
}
|
|
5
|
+
export interface ApiResult<T = unknown> {
|
|
6
|
+
data: T;
|
|
7
|
+
creditsRemaining: number | null;
|
|
8
|
+
}
|
|
9
|
+
export declare function apiGet<T = unknown>(path: string, apiKey: string): Promise<T>;
|
|
10
|
+
export declare function apiPost<T = unknown>(path: string, body: unknown): Promise<T>;
|
|
11
|
+
export declare function apiPostAuth<T = unknown>(path: string, body: unknown, apiKey: string): Promise<ApiResult<T>>;
|
|
12
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAGA,qBAAa,QAAS,SAAQ,KAAK;aAEf,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM,EAC9B,OAAO,EAAE,MAAM;CAKlB;AAED,MAAM,WAAW,SAAS,CAAC,CAAC,GAAG,OAAO;IACpC,IAAI,EAAE,CAAC,CAAC;IACR,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,wBAAsB,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAelF;AAED,wBAAsB,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAgBlF;AAED,wBAAsB,WAAW,CAAC,CAAC,GAAG,OAAO,EAC3C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAuBvB"}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ApiError = void 0;
|
|
4
|
+
exports.apiGet = apiGet;
|
|
5
|
+
exports.apiPost = apiPost;
|
|
6
|
+
exports.apiPostAuth = apiPostAuth;
|
|
7
|
+
const crypto_1 = require("crypto");
|
|
8
|
+
const constants_1 = require("./constants");
|
|
9
|
+
class ApiError extends Error {
|
|
10
|
+
status;
|
|
11
|
+
constructor(status, message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.status = status;
|
|
14
|
+
this.name = 'ApiError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.ApiError = ApiError;
|
|
18
|
+
async function apiGet(path, apiKey) {
|
|
19
|
+
let res;
|
|
20
|
+
try {
|
|
21
|
+
res = await fetch(`${constants_1.API_BASE}${path}`, {
|
|
22
|
+
method: 'GET',
|
|
23
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
throw new ApiError(0, err instanceof Error ? err.message : 'Network error');
|
|
28
|
+
}
|
|
29
|
+
const data = await res.json();
|
|
30
|
+
if (!res.ok) {
|
|
31
|
+
throw new ApiError(res.status, data.message ?? `HTTP ${res.status}`);
|
|
32
|
+
}
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
async function apiPost(path, body) {
|
|
36
|
+
let res;
|
|
37
|
+
try {
|
|
38
|
+
res = await fetch(`${constants_1.API_BASE}${path}`, {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: { 'Content-Type': 'application/json' },
|
|
41
|
+
body: JSON.stringify(body),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
throw new ApiError(0, err instanceof Error ? err.message : 'Network error');
|
|
46
|
+
}
|
|
47
|
+
const data = await res.json();
|
|
48
|
+
if (!res.ok && res.status !== 402) {
|
|
49
|
+
throw new ApiError(res.status, data.message ?? `HTTP ${res.status}`);
|
|
50
|
+
}
|
|
51
|
+
return data;
|
|
52
|
+
}
|
|
53
|
+
async function apiPostAuth(path, body, apiKey) {
|
|
54
|
+
let res;
|
|
55
|
+
try {
|
|
56
|
+
res = await fetch(`${constants_1.API_BASE}${path}`, {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: {
|
|
59
|
+
Authorization: `Bearer ${apiKey}`,
|
|
60
|
+
'Content-Type': 'application/json',
|
|
61
|
+
'Idempotency-Key': (0, crypto_1.randomUUID)(),
|
|
62
|
+
},
|
|
63
|
+
body: JSON.stringify(body),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
throw new ApiError(0, err instanceof Error ? err.message : 'Network error');
|
|
68
|
+
}
|
|
69
|
+
const data = await res.json();
|
|
70
|
+
if (!res.ok) {
|
|
71
|
+
throw new ApiError(res.status, data.message ?? `HTTP ${res.status}`);
|
|
72
|
+
}
|
|
73
|
+
const creditsHeader = res.headers.get('X-Credits-Remaining');
|
|
74
|
+
const parsed = creditsHeader !== null ? parseInt(creditsHeader, 10) : null;
|
|
75
|
+
const creditsRemaining = parsed !== null && !isNaN(parsed) ? parsed : null;
|
|
76
|
+
return { data, creditsRemaining };
|
|
77
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"balance.d.ts","sourceRoot":"","sources":["../../src/commands/balance.ts"],"names":[],"mappings":"AAQA,wBAAsB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAelD"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runBalance = runBalance;
|
|
4
|
+
const config_1 = require("../config");
|
|
5
|
+
const api_1 = require("../api");
|
|
6
|
+
async function runBalance() {
|
|
7
|
+
const apiKey = await (0, config_1.loadApiKey)();
|
|
8
|
+
if (!apiKey) {
|
|
9
|
+
throw new Error('No API key found. Run `lt start` to get started.');
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const balance = await (0, api_1.apiGet)('/api/v1/balance', apiKey);
|
|
13
|
+
return `Available: ${balance.available} credits\nTotal: ${balance.total} credits`;
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
if (err instanceof api_1.ApiError && err.status === 401) {
|
|
17
|
+
throw new Error('Invalid API key. Run `lt set-key <key>` or `lt start`.\nIf using LIGHTNING_TOOLS_API_KEY env var, check it is correct.', { cause: err });
|
|
18
|
+
}
|
|
19
|
+
throw err;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAMA,UAAU,YAAY;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAQD,wBAAsB,QAAQ,CAAC,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CA4CvE"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runLogin = runLogin;
|
|
37
|
+
const clack = __importStar(require("@clack/prompts"));
|
|
38
|
+
const api_1 = require("../api");
|
|
39
|
+
const config_1 = require("../config");
|
|
40
|
+
const qr_1 = require("../qr");
|
|
41
|
+
const payment_poll_1 = require("../payment-poll");
|
|
42
|
+
async function runLogin(opts = {}) {
|
|
43
|
+
const maxPolls = opts.maxPolls ?? payment_poll_1.DEFAULT_MAX_POLLS;
|
|
44
|
+
const pollIntervalMs = opts.pollIntervalMs ?? 3000;
|
|
45
|
+
clack.intro('Lightning Tools — Buy Credits');
|
|
46
|
+
const input = await clack.text({
|
|
47
|
+
message: 'How many credits? (minimum 1000)',
|
|
48
|
+
defaultValue: '1000',
|
|
49
|
+
validate: (v) => {
|
|
50
|
+
const n = parseInt(v, 10);
|
|
51
|
+
if (isNaN(n) || n < 1000)
|
|
52
|
+
return 'Minimum quantity is 1000.';
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
if (clack.isCancel(input)) {
|
|
56
|
+
clack.cancel('Cancelled.');
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
const quantity = parseInt(input, 10);
|
|
60
|
+
const { invoice, invoiceId } = await (0, api_1.apiPost)('/api/v1/credits', { quantity });
|
|
61
|
+
console.log('\nPay this Lightning invoice:\n');
|
|
62
|
+
(0, qr_1.printQr)(invoice);
|
|
63
|
+
console.log(`\n${invoice}\n`);
|
|
64
|
+
const spin = clack.spinner();
|
|
65
|
+
spin.start('Waiting for payment…');
|
|
66
|
+
const verified = await (0, payment_poll_1.pollForPayment)(invoiceId, quantity, maxPolls, pollIntervalMs);
|
|
67
|
+
if (!verified) {
|
|
68
|
+
spin.stop('Timed out.');
|
|
69
|
+
throw new Error('Invoice expired. Run `lt login` to try again.');
|
|
70
|
+
}
|
|
71
|
+
spin.stop('Payment confirmed!');
|
|
72
|
+
await (0, config_1.saveApiKey)(verified.apiKey);
|
|
73
|
+
clack.outro(`API key saved. Credits available: ${verified.balance.available}`);
|
|
74
|
+
return `Available: ${verified.balance.available}\nTotal: ${verified.balance.total}`;
|
|
75
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,wBAAsB,OAAO,CAC3B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC,SAAS,CAAC,CAsCpB"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runTool = runTool;
|
|
4
|
+
const openapi_1 = require("../openapi");
|
|
5
|
+
const api_1 = require("../api");
|
|
6
|
+
const config_1 = require("../config");
|
|
7
|
+
async function runTool(cliName, flags) {
|
|
8
|
+
const apiKey = await (0, config_1.loadApiKey)();
|
|
9
|
+
if (!apiKey) {
|
|
10
|
+
throw new Error('No API key found. Run `lt start` to get started.');
|
|
11
|
+
}
|
|
12
|
+
const tools = await (0, openapi_1.fetchServiceTools)();
|
|
13
|
+
const snakeName = cliName.replace(/-/g, '_');
|
|
14
|
+
const tool = tools.find((t) => t.name === snakeName);
|
|
15
|
+
if (!tool) {
|
|
16
|
+
throw new Error(`Unknown tool: ${cliName}. Run \`lt tools\` to see available tools.`);
|
|
17
|
+
}
|
|
18
|
+
const required = tool.inputSchema.required ?? [];
|
|
19
|
+
for (const field of required) {
|
|
20
|
+
if (flags[field] === undefined || flags[field] === '') {
|
|
21
|
+
throw new Error(`Missing required flag: --${field}. Run \`lt ${cliName} --help\` for usage.`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const result = await (0, api_1.apiPostAuth)(tool.path, flags, apiKey);
|
|
26
|
+
return {
|
|
27
|
+
output: JSON.stringify(result.data),
|
|
28
|
+
creditsRemaining: result.creditsRemaining,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
if (err instanceof api_1.ApiError && err.status === 402) {
|
|
33
|
+
throw new Error('Insufficient credits. Run `lt topup <quantity>` to add more.', { cause: err });
|
|
34
|
+
}
|
|
35
|
+
if (err instanceof api_1.ApiError && err.status === 401) {
|
|
36
|
+
throw new Error('Invalid API key. Run `lt set-key <key>` or `lt start`.\nIf using LIGHTNING_TOOLS_API_KEY env var, check it is correct.', { cause: err });
|
|
37
|
+
}
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"set-key.d.ts","sourceRoot":"","sources":["../../src/commands/set-key.ts"],"names":[],"mappings":"AAEA,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK5D"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runSetKey = runSetKey;
|
|
4
|
+
const config_1 = require("../config");
|
|
5
|
+
async function runSetKey(key) {
|
|
6
|
+
if (!key)
|
|
7
|
+
throw new Error('API key cannot be empty');
|
|
8
|
+
await (0, config_1.saveApiKey)(key);
|
|
9
|
+
const preview = key.length > 8 ? `${key.slice(0, 4)}...${key.slice(-4)}` : '****';
|
|
10
|
+
return `API key saved (${preview})`;
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAOA,UAAU,YAAY;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AA2DD,wBAAsB,QAAQ,CAAC,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgErE"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runStart = runStart;
|
|
37
|
+
const clack = __importStar(require("@clack/prompts"));
|
|
38
|
+
const api_1 = require("../api");
|
|
39
|
+
const config_1 = require("../config");
|
|
40
|
+
const qr_1 = require("../qr");
|
|
41
|
+
const payment_poll_1 = require("../payment-poll");
|
|
42
|
+
const wizard_1 = require("./wizard");
|
|
43
|
+
async function runInvoiceFlow(quantity, body, maxPolls, pollIntervalMs) {
|
|
44
|
+
const { invoice, invoiceId } = await (0, api_1.apiPost)('/api/v1/credits', body);
|
|
45
|
+
console.log('\nPay this Lightning invoice:\n');
|
|
46
|
+
(0, qr_1.printQr)(invoice);
|
|
47
|
+
console.log(`\n${invoice}\n`);
|
|
48
|
+
const spin = clack.spinner();
|
|
49
|
+
spin.start('Waiting for payment…');
|
|
50
|
+
const verified = await (0, payment_poll_1.pollForPayment)(invoiceId, quantity, maxPolls, pollIntervalMs);
|
|
51
|
+
if (!verified) {
|
|
52
|
+
spin.stop('Timed out.');
|
|
53
|
+
throw new Error('Invoice expired. Run `lt start` to try again.');
|
|
54
|
+
}
|
|
55
|
+
spin.stop('Payment confirmed!');
|
|
56
|
+
return verified;
|
|
57
|
+
}
|
|
58
|
+
async function runBuyCredits(maxPolls, pollIntervalMs) {
|
|
59
|
+
const input = await clack.text({
|
|
60
|
+
message: 'How many credits? (minimum 1000)',
|
|
61
|
+
defaultValue: '1000',
|
|
62
|
+
validate: (v) => {
|
|
63
|
+
const n = parseInt(v, 10);
|
|
64
|
+
if (isNaN(n) || n < 1000)
|
|
65
|
+
return 'Minimum quantity is 1000.';
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
if (clack.isCancel(input)) {
|
|
69
|
+
clack.cancel('Cancelled.');
|
|
70
|
+
process.exit(0);
|
|
71
|
+
}
|
|
72
|
+
const quantity = parseInt(input, 10);
|
|
73
|
+
const verified = await runInvoiceFlow(quantity, { quantity }, maxPolls, pollIntervalMs);
|
|
74
|
+
await (0, config_1.saveApiKey)(verified.apiKey);
|
|
75
|
+
clack.outro(`API key saved. Credits available: ${verified.balance.available}`);
|
|
76
|
+
}
|
|
77
|
+
async function runStart(opts = {}) {
|
|
78
|
+
const maxPolls = opts.maxPolls ?? payment_poll_1.DEFAULT_MAX_POLLS;
|
|
79
|
+
const pollIntervalMs = opts.pollIntervalMs ?? 3000;
|
|
80
|
+
const apiKey = await (0, config_1.loadApiKey)();
|
|
81
|
+
if (!apiKey) {
|
|
82
|
+
clack.intro('Lightning Tools — pay-per-use AI tools via Lightning');
|
|
83
|
+
await runBuyCredits(maxPolls, pollIntervalMs);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
clack.intro('Lightning Tools — Manage Your Account');
|
|
87
|
+
let balance;
|
|
88
|
+
try {
|
|
89
|
+
balance = await (0, api_1.apiGet)('/api/v1/balance', apiKey);
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
if (err instanceof api_1.ApiError && err.status === 401) {
|
|
93
|
+
clack.log.warn('Your API key is invalid. Let\'s get a new one.');
|
|
94
|
+
await runBuyCredits(maxPolls, pollIntervalMs);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
console.log(`\nCredits available: ${balance.available} / ${balance.total}\n`);
|
|
100
|
+
const action = await clack.select({
|
|
101
|
+
message: 'What would you like to do?',
|
|
102
|
+
options: [
|
|
103
|
+
{ value: 'topup', label: 'Top up credits' },
|
|
104
|
+
{ value: 'new-key', label: 'Get a new API key' },
|
|
105
|
+
{ value: 'continue', label: 'Run a tool' },
|
|
106
|
+
],
|
|
107
|
+
});
|
|
108
|
+
if (clack.isCancel(action)) {
|
|
109
|
+
clack.cancel('Cancelled.');
|
|
110
|
+
process.exit(0);
|
|
111
|
+
}
|
|
112
|
+
if (action === 'topup') {
|
|
113
|
+
const input = await clack.text({
|
|
114
|
+
message: 'How many credits? (minimum 1000)',
|
|
115
|
+
defaultValue: '1000',
|
|
116
|
+
validate: (v) => {
|
|
117
|
+
const n = parseInt(v, 10);
|
|
118
|
+
if (isNaN(n) || n < 1000)
|
|
119
|
+
return 'Minimum quantity is 1000.';
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
if (clack.isCancel(input)) {
|
|
123
|
+
clack.cancel('Cancelled.');
|
|
124
|
+
process.exit(0);
|
|
125
|
+
}
|
|
126
|
+
const quantity = parseInt(input, 10);
|
|
127
|
+
const verified = await runInvoiceFlow(quantity, { quantity, apiKey }, maxPolls, pollIntervalMs);
|
|
128
|
+
clack.outro(`Credits available: ${verified.balance.available}`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (action === 'new-key') {
|
|
132
|
+
await runBuyCredits(maxPolls, pollIntervalMs);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
// continue — run wizard
|
|
136
|
+
await (0, wizard_1.runToolWizard)();
|
|
137
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/commands/tools.ts"],"names":[],"mappings":"AAEA,wBAAsB,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,CAgBhD"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runTools = runTools;
|
|
4
|
+
const openapi_1 = require("../openapi");
|
|
5
|
+
async function runTools() {
|
|
6
|
+
let tools;
|
|
7
|
+
try {
|
|
8
|
+
tools = await (0, openapi_1.fetchServiceTools)();
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
throw new Error('Failed to fetch available tools. Check your connection.');
|
|
12
|
+
}
|
|
13
|
+
if (tools.length === 0)
|
|
14
|
+
return 'No tools available.';
|
|
15
|
+
const nameWidth = Math.max(...tools.map((t) => t.name.length)) + 2;
|
|
16
|
+
const summaryWidth = Math.max(...tools.map((t) => t.summary.length)) + 2;
|
|
17
|
+
return tools
|
|
18
|
+
.map((t) => `${t.name.replace(/_/g, '-').padEnd(nameWidth)}${t.summary.padEnd(summaryWidth)}${t.priceDisplay}`)
|
|
19
|
+
.join('\n');
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"topup.d.ts","sourceRoot":"","sources":["../../src/commands/topup.ts"],"names":[],"mappings":"AAMA,UAAU,YAAY;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAOD,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAkCzF"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runTopup = runTopup;
|
|
37
|
+
const clack = __importStar(require("@clack/prompts"));
|
|
38
|
+
const api_1 = require("../api");
|
|
39
|
+
const config_1 = require("../config");
|
|
40
|
+
const qr_1 = require("../qr");
|
|
41
|
+
const payment_poll_1 = require("../payment-poll");
|
|
42
|
+
async function runTopup(quantity, opts = {}) {
|
|
43
|
+
if (quantity < 1000)
|
|
44
|
+
throw new Error('Minimum quantity is 1000.');
|
|
45
|
+
const apiKey = await (0, config_1.loadApiKey)();
|
|
46
|
+
if (!apiKey)
|
|
47
|
+
throw new Error('No API key found. Run `lt start` to get started.');
|
|
48
|
+
const maxPolls = opts.maxPolls ?? payment_poll_1.DEFAULT_MAX_POLLS;
|
|
49
|
+
const pollIntervalMs = opts.pollIntervalMs ?? 3000;
|
|
50
|
+
clack.intro(`Lightning Tools — Top Up ${quantity} Credits`);
|
|
51
|
+
const { invoice, invoiceId } = await (0, api_1.apiPost)('/api/v1/credits', {
|
|
52
|
+
quantity,
|
|
53
|
+
apiKey,
|
|
54
|
+
});
|
|
55
|
+
console.log('\nPay this Lightning invoice:\n');
|
|
56
|
+
(0, qr_1.printQr)(invoice);
|
|
57
|
+
console.log(`\n${invoice}\n`);
|
|
58
|
+
const spin = clack.spinner();
|
|
59
|
+
spin.start('Waiting for payment…');
|
|
60
|
+
const verified = await (0, payment_poll_1.pollForPayment)(invoiceId, quantity, maxPolls, pollIntervalMs);
|
|
61
|
+
if (!verified) {
|
|
62
|
+
spin.stop('Timed out.');
|
|
63
|
+
throw new Error('Invoice expired. Run `lt topup <quantity>` to try again.');
|
|
64
|
+
}
|
|
65
|
+
spin.stop('Payment confirmed!');
|
|
66
|
+
clack.outro(`Credits available: ${verified.balance.available}`);
|
|
67
|
+
return `Available: ${verified.balance.available}\nTotal: ${verified.balance.total}`;
|
|
68
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wizard.d.ts","sourceRoot":"","sources":["../../src/commands/wizard.ts"],"names":[],"mappings":"AA4EA,wBAAsB,aAAa,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmFpE"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runToolWizard = runToolWizard;
|
|
37
|
+
const clack = __importStar(require("@clack/prompts"));
|
|
38
|
+
const openapi_1 = require("../openapi");
|
|
39
|
+
const run_1 = require("./run");
|
|
40
|
+
const SKIP = '__skip__';
|
|
41
|
+
// Note: @clack/prompts ^0.9.0 does not expose a `hint` API on any prompt type.
|
|
42
|
+
// Property descriptions from the schema are not surfaced in the terminal UI.
|
|
43
|
+
async function promptParam(key, prop, isRequired) {
|
|
44
|
+
if (prop.type === 'boolean') {
|
|
45
|
+
if (isRequired) {
|
|
46
|
+
return clack.confirm({ message: `${key} (required)` });
|
|
47
|
+
}
|
|
48
|
+
return clack.select({
|
|
49
|
+
message: `${key} (optional)`,
|
|
50
|
+
options: [
|
|
51
|
+
{ value: SKIP, label: '— skip —' },
|
|
52
|
+
{ value: 'true', label: 'Yes' },
|
|
53
|
+
{ value: 'false', label: 'No' },
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (prop.enum) {
|
|
58
|
+
const hasDefault = prop.default !== undefined;
|
|
59
|
+
if (isRequired) {
|
|
60
|
+
return clack.select({
|
|
61
|
+
message: `${key} (required)`,
|
|
62
|
+
options: prop.enum.map((v) => ({ value: v, label: v })),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (hasDefault) {
|
|
66
|
+
const defaultStr = String(prop.default);
|
|
67
|
+
return clack.select({
|
|
68
|
+
message: `${key} (optional)`,
|
|
69
|
+
options: prop.enum.map((v) => ({
|
|
70
|
+
value: v,
|
|
71
|
+
label: v === defaultStr ? `${v} (default)` : v,
|
|
72
|
+
})),
|
|
73
|
+
initialValue: defaultStr,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return clack.select({
|
|
77
|
+
message: `${key} (optional)`,
|
|
78
|
+
options: [
|
|
79
|
+
{ value: SKIP, label: '— skip —' },
|
|
80
|
+
...prop.enum.map((v) => ({ value: v, label: v })),
|
|
81
|
+
],
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// text / number
|
|
85
|
+
if (isRequired) {
|
|
86
|
+
return clack.text({
|
|
87
|
+
message: `${key} (required)`,
|
|
88
|
+
validate: (v) => { if (!v.trim())
|
|
89
|
+
return 'This field is required.'; },
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
if (prop.default !== undefined) {
|
|
93
|
+
return clack.text({
|
|
94
|
+
message: `${key} (optional)`,
|
|
95
|
+
defaultValue: String(prop.default),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return clack.text({
|
|
99
|
+
message: `${key} (optional · press Enter to skip)`,
|
|
100
|
+
placeholder: 'press Enter to skip',
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
async function runToolWizard(toolName) {
|
|
104
|
+
const tools = await (0, openapi_1.fetchServiceTools)();
|
|
105
|
+
// Validate tool existence before the TTY check so unknown tool names surface
|
|
106
|
+
// immediately (even in non-interactive environments like CI or scripts).
|
|
107
|
+
if (toolName !== undefined) {
|
|
108
|
+
const snakeName = toolName.replace(/-/g, '_');
|
|
109
|
+
if (!tools.find((t) => t.name === snakeName)) {
|
|
110
|
+
throw new Error(`Unknown tool: ${toolName}. Run \`lt tools\` to see available tools.`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (!process.stdin.isTTY) {
|
|
114
|
+
const hint = toolName ? `lt ${toolName} --help` : 'lt tools';
|
|
115
|
+
throw new Error(`Interactive mode requires a terminal. Run \`${hint}\` for usage.`);
|
|
116
|
+
}
|
|
117
|
+
clack.intro('Lightning Tools');
|
|
118
|
+
let cliName;
|
|
119
|
+
if (toolName === undefined) {
|
|
120
|
+
const selected = await clack.select({
|
|
121
|
+
message: 'Which tool would you like to run?',
|
|
122
|
+
options: tools.map((t) => {
|
|
123
|
+
const dashName = t.name.replace(/_/g, '-');
|
|
124
|
+
return { value: dashName, label: `${dashName} ${t.summary} ${t.priceDisplay}` };
|
|
125
|
+
}),
|
|
126
|
+
});
|
|
127
|
+
if (clack.isCancel(selected)) {
|
|
128
|
+
clack.cancel('Cancelled.');
|
|
129
|
+
process.exit(0);
|
|
130
|
+
}
|
|
131
|
+
cliName = selected;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
cliName = toolName;
|
|
135
|
+
}
|
|
136
|
+
const snakeName = cliName.replace(/-/g, '_');
|
|
137
|
+
const tool = tools.find((t) => t.name === snakeName);
|
|
138
|
+
const properties = tool.inputSchema.properties ?? {};
|
|
139
|
+
const required = tool.inputSchema.required ?? [];
|
|
140
|
+
const allKeys = Object.keys(properties);
|
|
141
|
+
const requiredKeys = allKeys.filter((k) => required.includes(k));
|
|
142
|
+
const optionalKeys = allKeys.filter((k) => !required.includes(k));
|
|
143
|
+
const orderedKeys = [...requiredKeys, ...optionalKeys];
|
|
144
|
+
const flags = {};
|
|
145
|
+
for (const key of orderedKeys) {
|
|
146
|
+
const prop = properties[key];
|
|
147
|
+
const isRequired = required.includes(key);
|
|
148
|
+
const raw = await promptParam(key, prop, isRequired);
|
|
149
|
+
if (clack.isCancel(raw)) {
|
|
150
|
+
clack.cancel('Cancelled.');
|
|
151
|
+
process.exit(0);
|
|
152
|
+
}
|
|
153
|
+
if (raw === SKIP)
|
|
154
|
+
continue;
|
|
155
|
+
if (prop.type === 'boolean' && !isRequired) {
|
|
156
|
+
flags[key] = raw === 'true';
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
// Optional text without default: empty string → omit
|
|
160
|
+
if (!isRequired && prop.default === undefined && !prop.enum && typeof raw === 'string' && raw === '') {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
// Coerce numeric types — clack.text always returns strings
|
|
164
|
+
if ((prop.type === 'number' || prop.type === 'integer') && typeof raw === 'string') {
|
|
165
|
+
flags[key] = Number(raw);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
flags[key] = raw;
|
|
169
|
+
}
|
|
170
|
+
const { output, creditsRemaining } = await (0, run_1.runTool)(cliName, flags);
|
|
171
|
+
const prettyOutput = JSON.stringify(JSON.parse(output), null, 2);
|
|
172
|
+
clack.outro(prettyOutput);
|
|
173
|
+
if (creditsRemaining !== null) {
|
|
174
|
+
process.stderr.write(`Credits remaining: ${creditsRemaining}\n`);
|
|
175
|
+
}
|
|
176
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW,QAA+D,CAAC;AAGxF,wBAAsB,UAAU,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAWzD;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG9D"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CONFIG_PATH = void 0;
|
|
4
|
+
exports.loadApiKey = loadApiKey;
|
|
5
|
+
exports.saveApiKey = saveApiKey;
|
|
6
|
+
const promises_1 = require("fs/promises");
|
|
7
|
+
const os_1 = require("os");
|
|
8
|
+
const path_1 = require("path");
|
|
9
|
+
exports.CONFIG_PATH = (0, path_1.join)((0, os_1.homedir)(), '.config', 'lightning-tools', 'config.json');
|
|
10
|
+
const CONFIG_DIR = (0, path_1.dirname)(exports.CONFIG_PATH);
|
|
11
|
+
async function loadApiKey() {
|
|
12
|
+
const envKey = process.env.LIGHTNING_TOOLS_API_KEY;
|
|
13
|
+
if (envKey)
|
|
14
|
+
return envKey;
|
|
15
|
+
try {
|
|
16
|
+
const raw = await (0, promises_1.readFile)(exports.CONFIG_PATH, 'utf8');
|
|
17
|
+
const config = JSON.parse(raw);
|
|
18
|
+
return typeof config.apiKey === 'string' ? config.apiKey : null;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function saveApiKey(apiKey) {
|
|
25
|
+
await (0, promises_1.mkdir)(CONFIG_DIR, { recursive: true });
|
|
26
|
+
await (0, promises_1.writeFile)(exports.CONFIG_PATH, JSON.stringify({ apiKey }, null, 2), 'utf8');
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,QAAQ,mCAAmC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const openapi_1 = require("./openapi");
|
|
6
|
+
const balance_1 = require("./commands/balance");
|
|
7
|
+
const tools_1 = require("./commands/tools");
|
|
8
|
+
const set_key_1 = require("./commands/set-key");
|
|
9
|
+
const start_1 = require("./commands/start");
|
|
10
|
+
const topup_1 = require("./commands/topup");
|
|
11
|
+
const run_1 = require("./commands/run");
|
|
12
|
+
const wizard_1 = require("./commands/wizard");
|
|
13
|
+
const program = new commander_1.Command();
|
|
14
|
+
program
|
|
15
|
+
.name('lt')
|
|
16
|
+
.description('Lightning Tools CLI — manage credits and call pay-per-use tools')
|
|
17
|
+
.version('0.1.0');
|
|
18
|
+
program
|
|
19
|
+
.command('start')
|
|
20
|
+
.description('Get started — buy credits and get an API key, or manage your existing account')
|
|
21
|
+
.action(async () => {
|
|
22
|
+
try {
|
|
23
|
+
await (0, start_1.runStart)();
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
console.error(err.message);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
program
|
|
31
|
+
.command('topup <quantity>')
|
|
32
|
+
.description('Top up credits for your existing account')
|
|
33
|
+
.action(async (quantity) => {
|
|
34
|
+
try {
|
|
35
|
+
const n = parseInt(quantity, 10);
|
|
36
|
+
if (isNaN(n)) {
|
|
37
|
+
console.error('Quantity must be a number.');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
await (0, topup_1.runTopup)(n);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
console.error(err.message);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
program
|
|
48
|
+
.command('set-key <key>')
|
|
49
|
+
.description('Store an existing API key')
|
|
50
|
+
.action(async (key) => {
|
|
51
|
+
try {
|
|
52
|
+
const msg = await (0, set_key_1.runSetKey)(key);
|
|
53
|
+
console.log(msg);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
console.error(err.message);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
program
|
|
61
|
+
.command('balance')
|
|
62
|
+
.description('Show your current credit balance')
|
|
63
|
+
.action(async () => {
|
|
64
|
+
try {
|
|
65
|
+
const result = await (0, balance_1.runBalance)();
|
|
66
|
+
console.log(result);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
console.error(err.message);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
program
|
|
74
|
+
.command('tools')
|
|
75
|
+
.description('List all available tools')
|
|
76
|
+
.action(async () => {
|
|
77
|
+
try {
|
|
78
|
+
const result = await (0, tools_1.runTools)();
|
|
79
|
+
console.log(result);
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
console.error(err.message);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
program
|
|
87
|
+
.command('run [name]')
|
|
88
|
+
.description('Run a tool interactively with guided prompts, or pass flags directly: lt run <tool> --flag value')
|
|
89
|
+
.allowUnknownOption()
|
|
90
|
+
.action(async (name) => {
|
|
91
|
+
try {
|
|
92
|
+
const extraFlags = parseRunFlags(name);
|
|
93
|
+
if (name && Object.keys(extraFlags).length > 0) {
|
|
94
|
+
const { output, creditsRemaining } = await (0, run_1.runTool)(name, extraFlags);
|
|
95
|
+
console.log(output);
|
|
96
|
+
if (creditsRemaining !== null) {
|
|
97
|
+
process.stderr.write(`Credits remaining: ${creditsRemaining}\n`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
await (0, wizard_1.runToolWizard)(name);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
console.error(err.message);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
// Parse --flag value pairs from argv after 'run [toolName]'
|
|
110
|
+
function parseRunFlags(toolName) {
|
|
111
|
+
const flags = {};
|
|
112
|
+
const runIdx = process.argv.indexOf('run');
|
|
113
|
+
if (runIdx === -1)
|
|
114
|
+
return flags;
|
|
115
|
+
const rest = process.argv.slice(runIdx + 1 + (toolName ? 1 : 0));
|
|
116
|
+
for (let i = 0; i < rest.length; i++) {
|
|
117
|
+
if (rest[i].startsWith('--')) {
|
|
118
|
+
const key = rest[i].slice(2);
|
|
119
|
+
if (i + 1 < rest.length && !rest[i + 1].startsWith('--')) {
|
|
120
|
+
flags[key] = rest[i + 1];
|
|
121
|
+
i++;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
flags[key] = true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return flags;
|
|
129
|
+
}
|
|
130
|
+
// Dynamic tool commands — registered after fetching OpenAPI spec
|
|
131
|
+
async function registerToolCommands() {
|
|
132
|
+
let tools;
|
|
133
|
+
try {
|
|
134
|
+
tools = await (0, openapi_1.fetchServiceTools)();
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return; // If fetch fails, only built-in commands available
|
|
138
|
+
}
|
|
139
|
+
for (const tool of tools) {
|
|
140
|
+
const cliName = tool.name.replace(/_/g, '-');
|
|
141
|
+
const cmd = program.command(cliName).description(`${tool.summary} (${tool.priceDisplay})`);
|
|
142
|
+
const properties = tool.inputSchema.properties ?? {};
|
|
143
|
+
const required = tool.inputSchema.required ?? [];
|
|
144
|
+
for (const [key, prop] of Object.entries(properties)) {
|
|
145
|
+
const desc = prop.description ?? key;
|
|
146
|
+
const isRequired = required.includes(key);
|
|
147
|
+
const flag = isRequired ? `--${key} <value>` : `--${key} [value]`;
|
|
148
|
+
cmd.option(flag, `${desc}${isRequired ? ' (required)' : ''}`);
|
|
149
|
+
}
|
|
150
|
+
cmd.option('--pretty', 'Pretty-print JSON output');
|
|
151
|
+
cmd.action(async (opts) => {
|
|
152
|
+
const { pretty, ...rawFlags } = opts;
|
|
153
|
+
// Coerce numeric fields — Commander always provides option values as strings
|
|
154
|
+
const flags = {};
|
|
155
|
+
for (const [key, val] of Object.entries(rawFlags)) {
|
|
156
|
+
const prop = properties[key];
|
|
157
|
+
if ((prop?.type === 'number' || prop?.type === 'integer') && typeof val === 'string' && val !== '') {
|
|
158
|
+
flags[key] = Number(val);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
flags[key] = val;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// If any required flag is missing, launch the interactive wizard instead
|
|
165
|
+
const missingRequired = required.some((field) => flags[field] === undefined);
|
|
166
|
+
if (missingRequired) {
|
|
167
|
+
try {
|
|
168
|
+
await (0, wizard_1.runToolWizard)(cliName);
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
console.error(err.message);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
const { output, creditsRemaining } = await (0, run_1.runTool)(cliName, flags);
|
|
178
|
+
if (pretty) {
|
|
179
|
+
console.log(JSON.stringify(JSON.parse(output), null, 2));
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
console.log(output);
|
|
183
|
+
}
|
|
184
|
+
if (creditsRemaining !== null) {
|
|
185
|
+
process.stderr.write(`Credits remaining: ${creditsRemaining}\n`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
console.error(err.message);
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
registerToolCommands().then(() => {
|
|
196
|
+
// No-args: show help and exit 0 (Commander defaults to exit 1)
|
|
197
|
+
if (process.argv.length <= 2) {
|
|
198
|
+
program.outputHelp();
|
|
199
|
+
process.exit(0);
|
|
200
|
+
}
|
|
201
|
+
program.on('command:*', () => {
|
|
202
|
+
console.error(`Unknown command: '${program.args[0]}'. Run \`lt --help\` for usage.`);
|
|
203
|
+
process.exit(1);
|
|
204
|
+
});
|
|
205
|
+
program.parseAsync(process.argv);
|
|
206
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare function clearToolsCache(): void;
|
|
2
|
+
export interface ServiceTool {
|
|
3
|
+
name: string;
|
|
4
|
+
path: string;
|
|
5
|
+
summary: string;
|
|
6
|
+
priceDisplay: string;
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: 'object';
|
|
9
|
+
properties: Record<string, {
|
|
10
|
+
type?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
enum?: string[];
|
|
13
|
+
default?: string | number | boolean;
|
|
14
|
+
}>;
|
|
15
|
+
required?: string[];
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export declare function formatPrice(price: unknown): string;
|
|
19
|
+
export declare function fetchServiceTools(): Promise<ServiceTool[]>;
|
|
20
|
+
//# sourceMappingURL=openapi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAIA,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,WAAW,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;YAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC,CAAC;QAC1H,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;CACH;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAWlD;AAUD,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAoChE"}
|
package/dist/openapi.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.clearToolsCache = clearToolsCache;
|
|
4
|
+
exports.formatPrice = formatPrice;
|
|
5
|
+
exports.fetchServiceTools = fetchServiceTools;
|
|
6
|
+
const constants_1 = require("./constants");
|
|
7
|
+
let cachedTools = null;
|
|
8
|
+
function clearToolsCache() {
|
|
9
|
+
cachedTools = null;
|
|
10
|
+
}
|
|
11
|
+
function formatPrice(price) {
|
|
12
|
+
if (typeof price === 'number')
|
|
13
|
+
return `${price} credit${price === 1 ? '' : 's'}`;
|
|
14
|
+
if (typeof price === 'object' && price !== null) {
|
|
15
|
+
const values = Object.values(price);
|
|
16
|
+
const min = Math.min(...values);
|
|
17
|
+
const max = Math.max(...values);
|
|
18
|
+
return min === max
|
|
19
|
+
? `${min} credit${min === 1 ? '' : 's'}`
|
|
20
|
+
: `${min}–${max} credits`;
|
|
21
|
+
}
|
|
22
|
+
return '';
|
|
23
|
+
}
|
|
24
|
+
function toSnakeCase(str) {
|
|
25
|
+
return str
|
|
26
|
+
.replace(/([A-Z])/g, '_$1')
|
|
27
|
+
.toLowerCase()
|
|
28
|
+
.replace(/^_/, '')
|
|
29
|
+
.replace(/[-\s]+/g, '_');
|
|
30
|
+
}
|
|
31
|
+
async function fetchServiceTools() {
|
|
32
|
+
if (cachedTools)
|
|
33
|
+
return cachedTools;
|
|
34
|
+
const res = await fetch(`${constants_1.API_BASE}/openapi.json`);
|
|
35
|
+
if (!res.ok)
|
|
36
|
+
throw new Error(`Failed to fetch openapi.json: ${res.status}`);
|
|
37
|
+
const spec = await res.json();
|
|
38
|
+
const tools = [];
|
|
39
|
+
for (const [path, pathItem] of Object.entries(spec.paths)) {
|
|
40
|
+
if (path.startsWith('/api/v1/'))
|
|
41
|
+
continue;
|
|
42
|
+
const op = pathItem.post;
|
|
43
|
+
if (!op || !('x-price' in op))
|
|
44
|
+
continue;
|
|
45
|
+
const schema = op.requestBody
|
|
46
|
+
?.content?.['application/json'];
|
|
47
|
+
const inputSchema = schema?.schema ?? {
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {},
|
|
50
|
+
};
|
|
51
|
+
const operationId = typeof op.operationId === 'string' ? op.operationId : path.replace(/\//g, '_');
|
|
52
|
+
tools.push({
|
|
53
|
+
name: toSnakeCase(operationId),
|
|
54
|
+
path,
|
|
55
|
+
summary: typeof op.summary === 'string' ? op.summary : '',
|
|
56
|
+
priceDisplay: formatPrice(op['x-price']),
|
|
57
|
+
inputSchema,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
cachedTools = tools;
|
|
61
|
+
return tools;
|
|
62
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface VerifyResponse {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
balance: {
|
|
4
|
+
available: number;
|
|
5
|
+
total: number;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export declare const DEFAULT_MAX_POLLS = 200;
|
|
9
|
+
export declare function pollForPayment(invoiceId: string, quantity: number, maxPolls: number, pollIntervalMs: number): Promise<VerifyResponse | null>;
|
|
10
|
+
//# sourceMappingURL=payment-poll.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment-poll.d.ts","sourceRoot":"","sources":["../src/payment-poll.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/C;AAED,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAIrC,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAOhC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_MAX_POLLS = void 0;
|
|
4
|
+
exports.pollForPayment = pollForPayment;
|
|
5
|
+
const api_1 = require("./api");
|
|
6
|
+
exports.DEFAULT_MAX_POLLS = 200; // 10 minutes at 3s intervals
|
|
7
|
+
// apiPost returns body even on 402 (non-throwing). Poll by checking
|
|
8
|
+
// whether the response contains an apiKey rather than catching exceptions.
|
|
9
|
+
async function pollForPayment(invoiceId, quantity, maxPolls, pollIntervalMs) {
|
|
10
|
+
for (let polls = 0; polls < maxPolls; polls++) {
|
|
11
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
12
|
+
const body = await (0, api_1.apiPost)('/api/v1/credits/verify', { invoiceId, quantity });
|
|
13
|
+
if (body.apiKey)
|
|
14
|
+
return body;
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
package/dist/qr.d.ts
ADDED
package/dist/qr.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qr.d.ts","sourceRoot":"","sources":["../src/qr.ts"],"names":[],"mappings":"AAEA,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAE1C"}
|
package/dist/qr.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.printQr = printQr;
|
|
7
|
+
const qrcode_terminal_1 = __importDefault(require("qrcode-terminal"));
|
|
8
|
+
function printQr(data) {
|
|
9
|
+
qrcode_terminal_1.default.generate(data, { small: true });
|
|
10
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lightning-tools/lt",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "CLI for Lightning Tools — manage credits and call pay-per-use tools from the terminal",
|
|
5
|
+
"bin": {
|
|
6
|
+
"lt": "./dist/index.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"test": "vitest --passWithNoTests",
|
|
15
|
+
"lint": "eslint src tests . --no-error-on-unmatched-pattern",
|
|
16
|
+
"publish:patch": "npm run build && npm version patch --no-git-tag-version && npm publish",
|
|
17
|
+
"publish:minor": "npm run build && npm version minor --no-git-tag-version && npm publish",
|
|
18
|
+
"publish:major": "npm run build && npm version major --no-git-tag-version && npm publish"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"commander": "^12.0.0",
|
|
22
|
+
"@clack/prompts": "^0.9.0",
|
|
23
|
+
"qrcode-terminal": "^0.12.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^20.0.0",
|
|
27
|
+
"@types/qrcode-terminal": "^0.12.0",
|
|
28
|
+
"typescript": "^5.0.0",
|
|
29
|
+
"vitest": "^4.0.18"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
}
|
|
37
|
+
}
|