@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.
Files changed (46) hide show
  1. package/dist/api.d.ts +12 -0
  2. package/dist/api.d.ts.map +1 -0
  3. package/dist/api.js +77 -0
  4. package/dist/commands/balance.d.ts +2 -0
  5. package/dist/commands/balance.d.ts.map +1 -0
  6. package/dist/commands/balance.js +21 -0
  7. package/dist/commands/login.d.ts +7 -0
  8. package/dist/commands/login.d.ts.map +1 -0
  9. package/dist/commands/login.js +75 -0
  10. package/dist/commands/run.d.ts +6 -0
  11. package/dist/commands/run.d.ts.map +1 -0
  12. package/dist/commands/run.js +40 -0
  13. package/dist/commands/set-key.d.ts +2 -0
  14. package/dist/commands/set-key.d.ts.map +1 -0
  15. package/dist/commands/set-key.js +11 -0
  16. package/dist/commands/start.d.ts +7 -0
  17. package/dist/commands/start.d.ts.map +1 -0
  18. package/dist/commands/start.js +137 -0
  19. package/dist/commands/tools.d.ts +2 -0
  20. package/dist/commands/tools.d.ts.map +1 -0
  21. package/dist/commands/tools.js +20 -0
  22. package/dist/commands/topup.d.ts +7 -0
  23. package/dist/commands/topup.d.ts.map +1 -0
  24. package/dist/commands/topup.js +68 -0
  25. package/dist/commands/wizard.d.ts +2 -0
  26. package/dist/commands/wizard.d.ts.map +1 -0
  27. package/dist/commands/wizard.js +176 -0
  28. package/dist/config.d.ts +4 -0
  29. package/dist/config.d.ts.map +1 -0
  30. package/dist/config.js +27 -0
  31. package/dist/constants.d.ts +2 -0
  32. package/dist/constants.d.ts.map +1 -0
  33. package/dist/constants.js +4 -0
  34. package/dist/index.d.ts +3 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +206 -0
  37. package/dist/openapi.d.ts +20 -0
  38. package/dist/openapi.d.ts.map +1 -0
  39. package/dist/openapi.js +62 -0
  40. package/dist/payment-poll.d.ts +10 -0
  41. package/dist/payment-poll.d.ts.map +1 -0
  42. package/dist/payment-poll.js +17 -0
  43. package/dist/qr.d.ts +2 -0
  44. package/dist/qr.d.ts.map +1 -0
  45. package/dist/qr.js +10 -0
  46. 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,2 @@
1
+ export declare function runBalance(): Promise<string>;
2
+ //# sourceMappingURL=balance.d.ts.map
@@ -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,7 @@
1
+ interface LoginOptions {
2
+ maxPolls?: number;
3
+ pollIntervalMs?: number;
4
+ }
5
+ export declare function runLogin(opts?: LoginOptions): Promise<string>;
6
+ export {};
7
+ //# sourceMappingURL=login.d.ts.map
@@ -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,6 @@
1
+ export interface RunResult {
2
+ output: string;
3
+ creditsRemaining: number | null;
4
+ }
5
+ export declare function runTool(cliName: string, flags: Record<string, unknown>): Promise<RunResult>;
6
+ //# sourceMappingURL=run.d.ts.map
@@ -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,2 @@
1
+ export declare function runSetKey(key: string): Promise<string>;
2
+ //# sourceMappingURL=set-key.d.ts.map
@@ -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,7 @@
1
+ interface StartOptions {
2
+ maxPolls?: number;
3
+ pollIntervalMs?: number;
4
+ }
5
+ export declare function runStart(opts?: StartOptions): Promise<void>;
6
+ export {};
7
+ //# sourceMappingURL=start.d.ts.map
@@ -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,2 @@
1
+ export declare function runTools(): Promise<string>;
2
+ //# sourceMappingURL=tools.d.ts.map
@@ -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,7 @@
1
+ interface TopupOptions {
2
+ maxPolls?: number;
3
+ pollIntervalMs?: number;
4
+ }
5
+ export declare function runTopup(quantity: number, opts?: TopupOptions): Promise<string>;
6
+ export {};
7
+ //# sourceMappingURL=topup.d.ts.map
@@ -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,2 @@
1
+ export declare function runToolWizard(toolName?: string): Promise<void>;
2
+ //# sourceMappingURL=wizard.d.ts.map
@@ -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
+ }
@@ -0,0 +1,4 @@
1
+ export declare const CONFIG_PATH: string;
2
+ export declare function loadApiKey(): Promise<string | null>;
3
+ export declare function saveApiKey(apiKey: string): Promise<void>;
4
+ //# sourceMappingURL=config.d.ts.map
@@ -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,2 @@
1
+ export declare const API_BASE = "https://api.lightningapi.tools";
2
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,QAAQ,mCAAmC,CAAC"}
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.API_BASE = void 0;
4
+ exports.API_BASE = 'https://api.lightningapi.tools';
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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"}
@@ -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
@@ -0,0 +1,2 @@
1
+ export declare function printQr(data: string): void;
2
+ //# sourceMappingURL=qr.d.ts.map
@@ -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
+ }