agentscreenshots 0.1.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.
@@ -0,0 +1,35 @@
1
+ import { resolveApiUrl, resolveLicenseKey } from './config.js';
2
+ export async function sendFeedback(options) {
3
+ const apiUrl = await resolveApiUrl(options.apiUrl);
4
+ const licenseKey = await resolveLicenseKey(options.licenseKey);
5
+ const headers = {
6
+ 'content-type': 'application/json'
7
+ };
8
+ if (licenseKey) {
9
+ headers.authorization = `Bearer ${licenseKey}`;
10
+ }
11
+ const response = await fetch(`${apiUrl}/api/cli/feedback`, {
12
+ method: 'POST',
13
+ headers,
14
+ body: JSON.stringify({
15
+ kind: options.kind,
16
+ message: options.message,
17
+ metadata: {
18
+ cliVersion: options.cliVersion,
19
+ nodeVersion: process.versions.node,
20
+ platform: process.platform,
21
+ arch: process.arch,
22
+ apiUrl,
23
+ licenseKeyConfigured: Boolean(licenseKey)
24
+ }
25
+ })
26
+ });
27
+ const body = await response.json().catch(() => null);
28
+ if (!response.ok) {
29
+ const error = body && typeof body === 'object' && 'error' in body
30
+ ? String(body.error)
31
+ : `HTTP ${response.status}`;
32
+ throw new Error(`Feedback failed: ${error}`);
33
+ }
34
+ return body;
35
+ }
package/dist/index.js ADDED
@@ -0,0 +1,503 @@
1
+ #!/usr/bin/env node
2
+ import { readFile } from 'node:fs/promises';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { dirname, join } from 'node:path';
5
+ import { capture } from './capture.js';
6
+ import { clearLicenseKey, readConfig, writeConfig } from './config.js';
7
+ import { runDoctor } from './doctor.js';
8
+ import { sendFeedback } from './feedback.js';
9
+ import { validateLicense } from './reporting.js';
10
+ function printHelp() {
11
+ console.log(`agentshot
12
+
13
+ Usage:
14
+ agentshot URL OUTPUT [options]
15
+ agentshot auth LICENSE_KEY [--api-url URL] [--json]
16
+ agentshot status [--api-url URL] [--license-key KEY]
17
+ agentshot doctor [--api-url URL] [--license-key KEY]
18
+ agentshot feedback "MESSAGE" [--kind feedback|bug|idea]
19
+ agentshot logout
20
+
21
+ Examples:
22
+ agentshot "http://127.0.0.1:5200/design" "./shots/design.png"
23
+ agentshot "http://127.0.0.1:5200/design" "./shots/hero.png" --height 1200
24
+ agentshot "http://127.0.0.1:5200/design" "./shots/cards.png" --from 1800 --to 2500
25
+ agentshot "http://127.0.0.1:5200/design" "./shots/borders.png" --selector "section:has-text('Borders')" --padding 24
26
+ agentshot "http://127.0.0.1:5200/design" "./shots/page.png" --scroll --wait 1000
27
+
28
+ Capture options:
29
+ --wait MS Wait after load/scroll before capture
30
+ --scroll Scroll through the page first to trigger lazy-loaded content
31
+ --selector SELECTOR Capture the first matching Playwright/CSS selector
32
+ --section SELECTOR Alias for --selector
33
+ --nth INDEX Capture selector match by zero-based index (default: 0)
34
+ --padding PX Add padding around selector screenshots (default: 0)
35
+ --height PX Capture from --from/top to this height; defaults from 0
36
+ --from PX Vertical crop start, page pixels
37
+ --to PX Vertical crop end, page pixels
38
+ --width PX Browser viewport width (default: 1280)
39
+ --viewport-height PX Browser viewport height (default: 900)
40
+ --viewport WIDTHxHEIGHT Set viewport width and height together
41
+ --wait-for CSS Wait for a selector before capture
42
+ --wait-until STATE load, domcontentloaded, or networkidle (default: load)
43
+ --timeout MS Navigation/action timeout (default: 30000)
44
+ --browser NAME chromium or chrome (default: chromium)
45
+ --headed Show the browser window
46
+ --no-full-page Capture only viewport when no selector/crop is used
47
+ --device-scale-factor N Device scale factor (default: 1)
48
+ --json Print machine-readable JSON
49
+ --no-report Do not report a visual check to AgentScreenshots
50
+ --api-url URL Override AgentScreenshots API URL
51
+ --license-key KEY Override configured license key
52
+
53
+ Environment:
54
+ AGENTSHOT_API_URL
55
+ AGENTSHOT_LICENSE_KEY
56
+ AGENTSHOT_CONFIG
57
+ `);
58
+ }
59
+ function parsePositiveNumber(value, name) {
60
+ const parsed = Number(value);
61
+ if (!Number.isFinite(parsed) || parsed < 0) {
62
+ throw new Error(`${name} must be a positive number.`);
63
+ }
64
+ return parsed;
65
+ }
66
+ function parseInteger(value, name) {
67
+ const parsed = Number.parseInt(value, 10);
68
+ if (!Number.isFinite(parsed) || parsed < 0) {
69
+ throw new Error(`${name} must be a positive integer.`);
70
+ }
71
+ return parsed;
72
+ }
73
+ function readValue(args, index, option) {
74
+ const value = args[index + 1];
75
+ if (!value || value.startsWith('--')) {
76
+ throw new Error(`${option} requires a value.`);
77
+ }
78
+ return value;
79
+ }
80
+ function getDefaultCaptureOptions(url, output) {
81
+ return {
82
+ url,
83
+ output,
84
+ width: 1280,
85
+ viewportHeight: 900,
86
+ deviceScaleFactor: 1,
87
+ waitMs: 0,
88
+ timeoutMs: 30_000,
89
+ scroll: false,
90
+ selector: null,
91
+ selectorIndex: 0,
92
+ padding: 0,
93
+ fromY: null,
94
+ toY: null,
95
+ clipHeight: null,
96
+ fullPage: true,
97
+ waitForSelector: null,
98
+ waitForLoadState: 'load',
99
+ browser: 'chromium',
100
+ headed: false,
101
+ json: false,
102
+ report: true,
103
+ apiUrl: null,
104
+ licenseKey: null
105
+ };
106
+ }
107
+ function parseCommonAuthOptions(args, startIndex = 0) {
108
+ let apiUrl = null;
109
+ let licenseKey = null;
110
+ let json = false;
111
+ for (let index = startIndex; index < args.length; index += 1) {
112
+ const arg = args[index];
113
+ if (arg === '--api-url') {
114
+ apiUrl = readValue(args, index, arg);
115
+ index += 1;
116
+ }
117
+ else if (arg === '--license-key') {
118
+ licenseKey = readValue(args, index, arg);
119
+ index += 1;
120
+ }
121
+ else if (arg === '--json') {
122
+ json = true;
123
+ }
124
+ else {
125
+ throw new Error(`Unknown option: ${arg}`);
126
+ }
127
+ }
128
+ return { apiUrl, licenseKey, json };
129
+ }
130
+ function parseFeedback(args) {
131
+ const messageParts = [];
132
+ let kind = 'feedback';
133
+ let apiUrl = null;
134
+ let licenseKey = null;
135
+ let json = false;
136
+ for (let index = 0; index < args.length; index += 1) {
137
+ const arg = args[index];
138
+ if (arg === '--kind') {
139
+ const value = readValue(args, index, arg);
140
+ if (value !== 'feedback' && value !== 'bug' && value !== 'idea') {
141
+ throw new Error('--kind must be feedback, bug, or idea.');
142
+ }
143
+ kind = value;
144
+ index += 1;
145
+ }
146
+ else if (arg === '--api-url') {
147
+ apiUrl = readValue(args, index, arg);
148
+ index += 1;
149
+ }
150
+ else if (arg === '--license-key') {
151
+ licenseKey = readValue(args, index, arg);
152
+ index += 1;
153
+ }
154
+ else if (arg === '--json') {
155
+ json = true;
156
+ }
157
+ else if (arg.startsWith('--')) {
158
+ throw new Error(`Unknown option: ${arg}`);
159
+ }
160
+ else {
161
+ messageParts.push(arg);
162
+ }
163
+ }
164
+ const message = messageParts.join(' ').trim();
165
+ if (!message) {
166
+ throw new Error('Usage: agentshot feedback "MESSAGE" [--kind feedback|bug|idea]');
167
+ }
168
+ return {
169
+ name: 'feedback',
170
+ kind,
171
+ message,
172
+ apiUrl,
173
+ licenseKey,
174
+ json
175
+ };
176
+ }
177
+ function parseLogout(args) {
178
+ let json = false;
179
+ for (const arg of args) {
180
+ if (arg === '--json') {
181
+ json = true;
182
+ }
183
+ else {
184
+ throw new Error(`Unknown option: ${arg}`);
185
+ }
186
+ }
187
+ return { name: 'logout', json };
188
+ }
189
+ function parseCapture(args) {
190
+ if (args.length < 2) {
191
+ throw new Error('Capture requires URL and OUTPUT.');
192
+ }
193
+ const [url, output, ...rest] = args;
194
+ const options = getDefaultCaptureOptions(url, output);
195
+ for (let index = 0; index < rest.length; index += 1) {
196
+ const arg = rest[index];
197
+ switch (arg) {
198
+ case '--wait':
199
+ options.waitMs = parseInteger(readValue(rest, index, arg), arg);
200
+ index += 1;
201
+ break;
202
+ case '--scroll':
203
+ options.scroll = true;
204
+ break;
205
+ case '--selector':
206
+ case '--section':
207
+ options.selector = readValue(rest, index, arg);
208
+ index += 1;
209
+ break;
210
+ case '--nth':
211
+ options.selectorIndex = parseInteger(readValue(rest, index, arg), arg);
212
+ index += 1;
213
+ break;
214
+ case '--padding':
215
+ options.padding = parseInteger(readValue(rest, index, arg), arg);
216
+ index += 1;
217
+ break;
218
+ case '--height':
219
+ options.clipHeight = parseInteger(readValue(rest, index, arg), arg);
220
+ index += 1;
221
+ break;
222
+ case '--from':
223
+ options.fromY = parseInteger(readValue(rest, index, arg), arg);
224
+ index += 1;
225
+ break;
226
+ case '--to':
227
+ options.toY = parseInteger(readValue(rest, index, arg), arg);
228
+ index += 1;
229
+ break;
230
+ case '--width':
231
+ options.width = parseInteger(readValue(rest, index, arg), arg);
232
+ index += 1;
233
+ break;
234
+ case '--viewport-height':
235
+ options.viewportHeight = parseInteger(readValue(rest, index, arg), arg);
236
+ index += 1;
237
+ break;
238
+ case '--viewport': {
239
+ const value = readValue(rest, index, arg);
240
+ const match = value.match(/^(\d+)x(\d+)$/i);
241
+ if (!match) {
242
+ throw new Error('--viewport must look like 1280x900.');
243
+ }
244
+ options.width = parseInteger(match[1], '--viewport width');
245
+ options.viewportHeight = parseInteger(match[2], '--viewport height');
246
+ index += 1;
247
+ break;
248
+ }
249
+ case '--wait-for':
250
+ options.waitForSelector = readValue(rest, index, arg);
251
+ index += 1;
252
+ break;
253
+ case '--wait-until': {
254
+ const value = readValue(rest, index, arg);
255
+ if (value !== 'load' && value !== 'domcontentloaded' && value !== 'networkidle') {
256
+ throw new Error('--wait-until must be load, domcontentloaded, or networkidle.');
257
+ }
258
+ options.waitForLoadState = value;
259
+ index += 1;
260
+ break;
261
+ }
262
+ case '--timeout':
263
+ options.timeoutMs = parseInteger(readValue(rest, index, arg), arg);
264
+ index += 1;
265
+ break;
266
+ case '--browser': {
267
+ const value = readValue(rest, index, arg);
268
+ if (value !== 'chromium' && value !== 'chrome') {
269
+ throw new Error('--browser must be chromium or chrome.');
270
+ }
271
+ options.browser = value;
272
+ index += 1;
273
+ break;
274
+ }
275
+ case '--headed':
276
+ options.headed = true;
277
+ break;
278
+ case '--no-full-page':
279
+ options.fullPage = false;
280
+ break;
281
+ case '--device-scale-factor':
282
+ options.deviceScaleFactor = parsePositiveNumber(readValue(rest, index, arg), arg);
283
+ index += 1;
284
+ break;
285
+ case '--json':
286
+ options.json = true;
287
+ break;
288
+ case '--no-report':
289
+ options.report = false;
290
+ break;
291
+ case '--api-url':
292
+ options.apiUrl = readValue(rest, index, arg);
293
+ index += 1;
294
+ break;
295
+ case '--license-key':
296
+ options.licenseKey = readValue(rest, index, arg);
297
+ index += 1;
298
+ break;
299
+ default:
300
+ throw new Error(`Unknown option: ${arg}`);
301
+ }
302
+ }
303
+ return { name: 'capture', options };
304
+ }
305
+ function parseArgs(args) {
306
+ const first = args[0];
307
+ if (!first || first === '--help' || first === '-h' || first === 'help') {
308
+ return { name: 'help' };
309
+ }
310
+ if (first === '--version' || first === '-v' || first === 'version') {
311
+ return { name: 'version' };
312
+ }
313
+ if (first === 'auth') {
314
+ const key = args[1];
315
+ if (!key || key.startsWith('--')) {
316
+ throw new Error('Usage: agentshot auth LICENSE_KEY [--api-url URL]');
317
+ }
318
+ const { apiUrl, json } = parseCommonAuthOptions(args, 2);
319
+ return { name: 'auth', key, apiUrl, json };
320
+ }
321
+ if (first === 'status') {
322
+ return { name: 'status', ...parseCommonAuthOptions(args, 1) };
323
+ }
324
+ if (first === 'doctor') {
325
+ return { name: 'doctor', ...parseCommonAuthOptions(args, 1) };
326
+ }
327
+ if (first === 'feedback') {
328
+ return parseFeedback(args.slice(1));
329
+ }
330
+ if (first === 'logout') {
331
+ return parseLogout(args.slice(1));
332
+ }
333
+ return parseCapture(args);
334
+ }
335
+ async function readPackageVersion() {
336
+ const currentFile = fileURLToPath(import.meta.url);
337
+ const packageJsonPath = join(dirname(currentFile), '..', 'package.json');
338
+ try {
339
+ const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8'));
340
+ return packageJson.version ?? '0.1.0';
341
+ }
342
+ catch {
343
+ return '0.1.0';
344
+ }
345
+ }
346
+ async function run() {
347
+ const command = parseArgs(process.argv.slice(2));
348
+ if (command.name === 'help') {
349
+ printHelp();
350
+ return;
351
+ }
352
+ if (command.name === 'version') {
353
+ console.log(await readPackageVersion());
354
+ return;
355
+ }
356
+ if (command.name === 'auth') {
357
+ const config = await readConfig();
358
+ const apiUrl = command.apiUrl ?? config.apiUrl;
359
+ const validation = await validateLicense({
360
+ apiUrl,
361
+ licenseKey: command.key
362
+ });
363
+ if (!validation.ok) {
364
+ if (command.json) {
365
+ console.log(JSON.stringify(validation.body, null, 2));
366
+ }
367
+ else {
368
+ console.error(`License validation failed (${validation.status}).`);
369
+ if (validation.body) {
370
+ console.error(JSON.stringify(validation.body, null, 2));
371
+ }
372
+ }
373
+ process.exitCode = 1;
374
+ return;
375
+ }
376
+ const path = await writeConfig({
377
+ ...config,
378
+ apiUrl,
379
+ licenseKey: command.key
380
+ });
381
+ if (command.json) {
382
+ console.log(JSON.stringify({
383
+ ok: true,
384
+ path,
385
+ license: validation.body?.license
386
+ }, null, 2));
387
+ }
388
+ else {
389
+ const license = validation.body?.license;
390
+ console.log(`Saved license key to ${path}`);
391
+ console.log(`License ${license?.keyPrefix ?? ''} valid: ${license?.usedChecks ?? 0}/${license?.quotaChecks ?? 0} checks used`);
392
+ }
393
+ return;
394
+ }
395
+ if (command.name === 'logout') {
396
+ const result = await clearLicenseKey();
397
+ if (command.json) {
398
+ console.log(JSON.stringify(result, null, 2));
399
+ }
400
+ else if (result.hadLicenseKey) {
401
+ console.log(`Removed saved license key from ${result.path}`);
402
+ }
403
+ else {
404
+ console.log(`No saved license key found in ${result.path}`);
405
+ }
406
+ if (result.envOverrideActive && !command.json) {
407
+ console.error('AGENTSHOT_LICENSE_KEY is still set in the environment.');
408
+ }
409
+ return;
410
+ }
411
+ if (command.name === 'status') {
412
+ let result;
413
+ try {
414
+ result = await validateLicense({
415
+ apiUrl: command.apiUrl,
416
+ licenseKey: command.licenseKey
417
+ });
418
+ }
419
+ catch (error) {
420
+ const message = error instanceof Error ? error.message : String(error);
421
+ if (command.json) {
422
+ console.log(JSON.stringify({
423
+ ok: false,
424
+ reason: 'no_license_key',
425
+ message
426
+ }, null, 2));
427
+ }
428
+ else {
429
+ console.error(message);
430
+ }
431
+ process.exitCode = 1;
432
+ return;
433
+ }
434
+ if (command.json) {
435
+ console.log(JSON.stringify(result.body, null, 2));
436
+ }
437
+ else if (result.ok) {
438
+ const license = result.body?.license;
439
+ console.log(`License ${license?.keyPrefix ?? ''} valid: ${license?.usedChecks ?? 0}/${license?.quotaChecks ?? 0} checks used`);
440
+ }
441
+ else {
442
+ console.error(`License check failed (${result.status}).`);
443
+ if (result.body) {
444
+ console.error(JSON.stringify(result.body, null, 2));
445
+ }
446
+ process.exitCode = 1;
447
+ }
448
+ return;
449
+ }
450
+ if (command.name === 'doctor') {
451
+ const result = await runDoctor(command);
452
+ if (command.json) {
453
+ console.log(JSON.stringify(result, null, 2));
454
+ }
455
+ else {
456
+ console.log(`agentshot doctor: ${result.status}`);
457
+ for (const check of result.checks) {
458
+ console.log(`${check.status.padEnd(4)} ${check.name}: ${check.message}`);
459
+ }
460
+ }
461
+ if (result.status === 'fail') {
462
+ process.exitCode = 1;
463
+ }
464
+ return;
465
+ }
466
+ if (command.name === 'feedback') {
467
+ const result = await sendFeedback({
468
+ apiUrl: command.apiUrl,
469
+ licenseKey: command.licenseKey,
470
+ json: command.json,
471
+ kind: command.kind,
472
+ message: command.message,
473
+ cliVersion: await readPackageVersion()
474
+ });
475
+ if (command.json) {
476
+ console.log(JSON.stringify(result, null, 2));
477
+ }
478
+ else {
479
+ const feedback = result?.feedback;
480
+ console.log(`Feedback sent${feedback?.id ? ` (${feedback.id})` : ''}.`);
481
+ }
482
+ return;
483
+ }
484
+ const result = await capture(command.options);
485
+ if (command.options.json) {
486
+ console.log(JSON.stringify(result, null, 2));
487
+ return;
488
+ }
489
+ console.log(`Captured ${result.width}x${result.height} -> ${result.output} (${(result.durationMs / 1000).toFixed(1)}s)`);
490
+ if (result.reportStatus === 'failed') {
491
+ console.error(`Usage report failed: ${result.reportReason}`);
492
+ }
493
+ else if (result.reportStatus === 'queued') {
494
+ console.error(`Usage report queued for retry: ${result.reportReason}`);
495
+ }
496
+ else if (result.reportStatus === 'skipped' && result.reportReason === 'no_license_key') {
497
+ console.error('Usage report skipped: no license key configured.');
498
+ }
499
+ }
500
+ run().catch((error) => {
501
+ console.error(error instanceof Error ? error.message : String(error));
502
+ process.exit(1);
503
+ });
@@ -0,0 +1,169 @@
1
+ import { appendFile, mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
2
+ import { dirname, join } from 'node:path';
3
+ import { getConfigPath, resolveApiUrl, resolveLicenseKey } from './config.js';
4
+ export async function getFileSize(path) {
5
+ const file = await stat(path);
6
+ return file.size;
7
+ }
8
+ export function getTargetKind(url) {
9
+ try {
10
+ const parsed = new URL(url);
11
+ const hostname = parsed.hostname.toLowerCase();
12
+ if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') {
13
+ return 'localhost';
14
+ }
15
+ }
16
+ catch {
17
+ return 'localhost';
18
+ }
19
+ return 'public';
20
+ }
21
+ function getQueuePath() {
22
+ return join(dirname(getConfigPath()), 'usage-queue.jsonl');
23
+ }
24
+ async function readQueuedEvents() {
25
+ const path = getQueuePath();
26
+ try {
27
+ const content = await readFile(path, 'utf8');
28
+ return content
29
+ .split('\n')
30
+ .map((line) => line.trim())
31
+ .filter(Boolean)
32
+ .map((line) => JSON.parse(line));
33
+ }
34
+ catch (error) {
35
+ if (error.code === 'ENOENT') {
36
+ return [];
37
+ }
38
+ throw error;
39
+ }
40
+ }
41
+ async function writeQueuedEvents(events) {
42
+ const path = getQueuePath();
43
+ if (events.length === 0) {
44
+ await rm(path, { force: true });
45
+ return;
46
+ }
47
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
48
+ await writeFile(path, `${events.map((event) => JSON.stringify(event)).join('\n')}\n`, {
49
+ mode: 0o600
50
+ });
51
+ }
52
+ async function queueUsageEvent(event) {
53
+ const path = getQueuePath();
54
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
55
+ await appendFile(path, `${JSON.stringify({ ...event, queuedAt: event.queuedAt ?? new Date().toISOString() })}\n`, { mode: 0o600 });
56
+ }
57
+ async function postUsageEvent(apiUrl, licenseKey, event) {
58
+ const controller = new AbortController();
59
+ const timeout = setTimeout(() => controller.abort(), 4_000);
60
+ try {
61
+ const response = await fetch(`${apiUrl}/api/cli/report-check`, {
62
+ method: 'POST',
63
+ headers: {
64
+ authorization: `Bearer ${licenseKey}`,
65
+ 'content-type': 'application/json'
66
+ },
67
+ body: JSON.stringify({
68
+ eventId: event.eventId,
69
+ metadata: event.metadata
70
+ }),
71
+ signal: controller.signal
72
+ });
73
+ const payload = (await response.json().catch(() => null));
74
+ const reason = payload?.reason ?? `http_${response.status}`;
75
+ if (response.ok) {
76
+ return { ok: true, retryable: false, reason };
77
+ }
78
+ return {
79
+ ok: false,
80
+ retryable: response.status >= 500 || response.status === 429,
81
+ reason
82
+ };
83
+ }
84
+ finally {
85
+ clearTimeout(timeout);
86
+ }
87
+ }
88
+ async function flushQueuedEvents(apiUrl, licenseKey) {
89
+ const queued = await readQueuedEvents();
90
+ if (queued.length === 0) {
91
+ return { flushed: 0, kept: 0 };
92
+ }
93
+ const remaining = [];
94
+ let flushed = 0;
95
+ for (const event of queued) {
96
+ try {
97
+ const result = await postUsageEvent(apiUrl, licenseKey, event);
98
+ if (result.ok || !result.retryable) {
99
+ flushed += 1;
100
+ continue;
101
+ }
102
+ remaining.push(event);
103
+ }
104
+ catch {
105
+ remaining.push(event);
106
+ }
107
+ }
108
+ await writeQueuedEvents(remaining);
109
+ return {
110
+ flushed,
111
+ kept: remaining.length
112
+ };
113
+ }
114
+ export async function reportCheck(options) {
115
+ const licenseKey = await resolveLicenseKey(options.licenseKey);
116
+ if (!licenseKey) {
117
+ return {
118
+ reported: false,
119
+ status: 'skipped',
120
+ reason: 'no_license_key'
121
+ };
122
+ }
123
+ const apiUrl = await resolveApiUrl(options.apiUrl);
124
+ const event = {
125
+ eventId: crypto.randomUUID(),
126
+ metadata: options.metadata
127
+ };
128
+ try {
129
+ await flushQueuedEvents(apiUrl, licenseKey);
130
+ const result = await postUsageEvent(apiUrl, licenseKey, event);
131
+ if (!result.ok) {
132
+ return {
133
+ reported: false,
134
+ status: 'failed',
135
+ reason: result.reason
136
+ };
137
+ }
138
+ return {
139
+ reported: true,
140
+ status: 'ok',
141
+ reason: result.reason
142
+ };
143
+ }
144
+ catch (error) {
145
+ await queueUsageEvent(event);
146
+ return {
147
+ reported: false,
148
+ status: 'queued',
149
+ reason: error instanceof Error ? error.message : 'queued_for_retry'
150
+ };
151
+ }
152
+ }
153
+ export async function validateLicense(options) {
154
+ const licenseKey = await resolveLicenseKey(options.licenseKey);
155
+ if (!licenseKey) {
156
+ throw new Error('No license key configured. Run `agentshot auth ags_live_...` first.');
157
+ }
158
+ const apiUrl = await resolveApiUrl(options.apiUrl);
159
+ const response = await fetch(`${apiUrl}/api/cli/license`, {
160
+ headers: {
161
+ authorization: `Bearer ${licenseKey}`
162
+ }
163
+ });
164
+ return {
165
+ ok: response.ok,
166
+ status: response.status,
167
+ body: await response.json().catch(() => null)
168
+ };
169
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};