@hyperline/cli 0.1.0-build.1.9301c8c → 0.1.0-build.1.deb4fbd
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/bin/hyperline-main.js +485 -23
- package/package.json +1 -1
|
@@ -71,6 +71,87 @@ async function clearCredentials() {
|
|
|
71
71
|
} catch {
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
+
async function resolveCompanyId({ flagCompanyId }) {
|
|
75
|
+
if (flagCompanyId)
|
|
76
|
+
return flagCompanyId;
|
|
77
|
+
const envCompanyId = process.env.HYPERLINE_COMPANY_ID;
|
|
78
|
+
if (envCompanyId)
|
|
79
|
+
return envCompanyId;
|
|
80
|
+
const configFile = await readJsonFile(CONFIG_FILE);
|
|
81
|
+
return configFile?.companyId;
|
|
82
|
+
}
|
|
83
|
+
async function saveCompanyId({ companyId }) {
|
|
84
|
+
const existing = await readJsonFile(CONFIG_FILE) ?? {};
|
|
85
|
+
existing.companyId = companyId;
|
|
86
|
+
await writeJsonFile(CONFIG_FILE, existing);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// build/prompt.js
|
|
90
|
+
import * as readline from "node:readline";
|
|
91
|
+
function selectPrompt({ message, items }) {
|
|
92
|
+
if (items.length === 0)
|
|
93
|
+
return Promise.resolve(void 0);
|
|
94
|
+
if (items.length === 1)
|
|
95
|
+
return Promise.resolve(items[0].value);
|
|
96
|
+
return new Promise((resolve) => {
|
|
97
|
+
const input = process.stdin;
|
|
98
|
+
const output = process.stderr;
|
|
99
|
+
let selectedIndex = 0;
|
|
100
|
+
const wasRaw = input.isRaw;
|
|
101
|
+
if (!input.isTTY) {
|
|
102
|
+
resolve(items[0].value);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
input.setRawMode(true);
|
|
106
|
+
readline.emitKeypressEvents(input);
|
|
107
|
+
function render() {
|
|
108
|
+
output.write(`\x1B[?25l`);
|
|
109
|
+
output.write(`\x1B[${items.length}A`);
|
|
110
|
+
for (const [index, item] of items.entries()) {
|
|
111
|
+
const isSelected = index === selectedIndex;
|
|
112
|
+
const pointer = isSelected ? "\x1B[36m>\x1B[0m" : " ";
|
|
113
|
+
const label = isSelected ? `\x1B[36m${item.label}\x1B[0m` : item.label;
|
|
114
|
+
const hint = item.hint ? `\x1B[2m ${item.hint}\x1B[0m` : "";
|
|
115
|
+
output.write(`\x1B[2K${pointer} ${label}${hint}
|
|
116
|
+
`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
output.write(`${message}
|
|
120
|
+
`);
|
|
121
|
+
for (const [index, item] of items.entries()) {
|
|
122
|
+
const isSelected = index === selectedIndex;
|
|
123
|
+
const pointer = isSelected ? "\x1B[36m>\x1B[0m" : " ";
|
|
124
|
+
const label = isSelected ? `\x1B[36m${item.label}\x1B[0m` : item.label;
|
|
125
|
+
const hint = item.hint ? `\x1B[2m ${item.hint}\x1B[0m` : "";
|
|
126
|
+
output.write(`${pointer} ${label}${hint}
|
|
127
|
+
`);
|
|
128
|
+
}
|
|
129
|
+
function cleanup() {
|
|
130
|
+
input.setRawMode(wasRaw ?? false);
|
|
131
|
+
input.removeListener("keypress", onKeypress);
|
|
132
|
+
input.pause();
|
|
133
|
+
output.write("\x1B[?25h");
|
|
134
|
+
}
|
|
135
|
+
function onKeypress(_str, key) {
|
|
136
|
+
if (!key)
|
|
137
|
+
return;
|
|
138
|
+
if (key.name === "up" || key.name === "k" && !key.ctrl) {
|
|
139
|
+
selectedIndex = selectedIndex <= 0 ? items.length - 1 : selectedIndex - 1;
|
|
140
|
+
render();
|
|
141
|
+
} else if (key.name === "down" || key.name === "j" && !key.ctrl) {
|
|
142
|
+
selectedIndex = selectedIndex >= items.length - 1 ? 0 : selectedIndex + 1;
|
|
143
|
+
render();
|
|
144
|
+
} else if (key.name === "return") {
|
|
145
|
+
cleanup();
|
|
146
|
+
resolve(items[selectedIndex]?.value);
|
|
147
|
+
} else if (key.name === "escape" || key.name === "c" && key.ctrl) {
|
|
148
|
+
cleanup();
|
|
149
|
+
resolve(void 0);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
input.on("keypress", onKeypress);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
74
155
|
|
|
75
156
|
// build/auth.js
|
|
76
157
|
var CLI_CLIENT_NAME = "Hyperline CLI";
|
|
@@ -88,7 +169,10 @@ function generateState() {
|
|
|
88
169
|
async function registerDynamicClient({ baseUrl, redirectUri }) {
|
|
89
170
|
const response = await fetch(`${baseUrl}/oauth/register`, {
|
|
90
171
|
method: "POST",
|
|
91
|
-
headers: {
|
|
172
|
+
headers: {
|
|
173
|
+
"Content-Type": "application/json",
|
|
174
|
+
"Hyperline-Source": "cli"
|
|
175
|
+
},
|
|
92
176
|
body: JSON.stringify({
|
|
93
177
|
client_name: CLI_CLIENT_NAME,
|
|
94
178
|
redirect_uris: [redirectUri],
|
|
@@ -106,7 +190,10 @@ async function registerDynamicClient({ baseUrl, redirectUri }) {
|
|
|
106
190
|
async function exchangeCodeForTokens({ baseUrl, code, clientId, codeVerifier, redirectUri }) {
|
|
107
191
|
const response = await fetch(`${baseUrl}/oauth/tokens`, {
|
|
108
192
|
method: "POST",
|
|
109
|
-
headers: {
|
|
193
|
+
headers: {
|
|
194
|
+
"Content-Type": "application/json",
|
|
195
|
+
"Hyperline-Source": "cli"
|
|
196
|
+
},
|
|
110
197
|
body: JSON.stringify({
|
|
111
198
|
grant_type: "authorization_code",
|
|
112
199
|
code,
|
|
@@ -238,11 +325,340 @@ Please open this URL in your browser:
|
|
|
238
325
|
expiresIn: tokens.expires_in
|
|
239
326
|
});
|
|
240
327
|
process.stdout.write("Login successful!\n");
|
|
328
|
+
await promptCompanySelection({
|
|
329
|
+
baseUrl,
|
|
330
|
+
accessToken: tokens.access_token
|
|
331
|
+
});
|
|
241
332
|
} catch (error) {
|
|
242
333
|
server.close();
|
|
243
334
|
throw error;
|
|
244
335
|
}
|
|
245
336
|
}
|
|
337
|
+
async function fetchCompanies({ baseUrl, accessToken }) {
|
|
338
|
+
const response = await fetch(`${baseUrl}/v1/companies`, {
|
|
339
|
+
headers: {
|
|
340
|
+
Authorization: `Bearer ${accessToken}`,
|
|
341
|
+
"Hyperline-Source": "cli"
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
if (!response.ok) {
|
|
345
|
+
throw new Error(`Failed to fetch companies: ${response.status}`);
|
|
346
|
+
}
|
|
347
|
+
const body = await response.json();
|
|
348
|
+
return body.data;
|
|
349
|
+
}
|
|
350
|
+
async function promptCompanySelection({ baseUrl, accessToken }) {
|
|
351
|
+
process.stdout.write("\nFetching your companies...\n");
|
|
352
|
+
const companies = await fetchCompanies({ baseUrl, accessToken });
|
|
353
|
+
if (companies.length === 0) {
|
|
354
|
+
process.stdout.write("No companies found for this account.\n");
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (companies.length === 1) {
|
|
358
|
+
const onlyCompany = companies[0];
|
|
359
|
+
await saveCompanyId({ companyId: onlyCompany.id });
|
|
360
|
+
process.stdout.write(`Company set to ${onlyCompany.name}
|
|
361
|
+
`);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const selectedCompanyId = await selectPrompt({
|
|
365
|
+
message: "Select a company:",
|
|
366
|
+
items: companies.map((company) => ({
|
|
367
|
+
label: company.name,
|
|
368
|
+
value: company.id,
|
|
369
|
+
hint: company.id
|
|
370
|
+
}))
|
|
371
|
+
});
|
|
372
|
+
if (!selectedCompanyId) {
|
|
373
|
+
process.stdout.write("No company selected. You can set one later with: hyperline company select\n");
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
await saveCompanyId({ companyId: selectedCompanyId });
|
|
377
|
+
const selectedCompany = companies.find((company) => company.id === selectedCompanyId);
|
|
378
|
+
process.stdout.write(`Company set to ${selectedCompany?.name ?? selectedCompanyId}
|
|
379
|
+
`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// build/commands/confirm.js
|
|
383
|
+
import * as readline2 from "node:readline";
|
|
384
|
+
function confirmPrompt(message) {
|
|
385
|
+
const rl = readline2.createInterface({
|
|
386
|
+
input: process.stdin,
|
|
387
|
+
output: process.stderr
|
|
388
|
+
});
|
|
389
|
+
return new Promise((resolve) => {
|
|
390
|
+
rl.question(message, (answer) => {
|
|
391
|
+
rl.close();
|
|
392
|
+
resolve(answer.toLowerCase() === "y");
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// build/commands/events.js
|
|
398
|
+
function resolveIngestBaseUrl(apiBaseUrl) {
|
|
399
|
+
return apiBaseUrl.replace("api.", "ingest.");
|
|
400
|
+
}
|
|
401
|
+
function registerEventsCommands(parent) {
|
|
402
|
+
const resource = parent.command("events").description("Manage billable events");
|
|
403
|
+
resource.command("list").description(`List billable events filtered by event type with optional filters for customer_id, record_id, and timestamp range. Paginated with take/skip.`).requiredOption("--event-type <value>", `Event type to filter by`).option("--take <number>", `take`).option("--skip <number>", `skip`).option("--customer-id <value>", `Filter by customer ID`).option("--record-id-in <value>", `Filter by record ID (comma-separated for multiple)`).option("--timestamp-gte <value>", `Filter events with timestamp >= this ISO8601 date`).option("--timestamp-lte <value>", `Filter events with timestamp <= this ISO8601 date`).option("--order <value>", `Sort order for timestamp`).addHelpText("after", `
|
|
404
|
+
Examples:
|
|
405
|
+
hyperline events list --event-type api_call
|
|
406
|
+
hyperline events list --event-type api_call --customer-id cus_xyz789 --take 10`).action(async (opts) => {
|
|
407
|
+
const ctx = resource.parent?.opts()._ctx;
|
|
408
|
+
if (!ctx) {
|
|
409
|
+
process.stderr.write("Error: Not authenticated\n");
|
|
410
|
+
process.exit(1);
|
|
411
|
+
}
|
|
412
|
+
const args = {};
|
|
413
|
+
if (opts.eventType !== void 0)
|
|
414
|
+
args.event_type = opts.eventType;
|
|
415
|
+
if (opts.customerId !== void 0)
|
|
416
|
+
args.customer_id = opts.customerId;
|
|
417
|
+
if (opts.recordIdIn !== void 0)
|
|
418
|
+
args.record_id__in = opts.recordIdIn;
|
|
419
|
+
if (opts.timestampGte !== void 0)
|
|
420
|
+
args.timestamp_gte = opts.timestampGte;
|
|
421
|
+
if (opts.timestampLte !== void 0)
|
|
422
|
+
args.timestamp_lte = opts.timestampLte;
|
|
423
|
+
if (opts.order !== void 0)
|
|
424
|
+
args.order = opts.order;
|
|
425
|
+
if (opts.take !== void 0)
|
|
426
|
+
args.take = Number(opts.take);
|
|
427
|
+
if (opts.skip !== void 0)
|
|
428
|
+
args.skip = Number(opts.skip);
|
|
429
|
+
await ctx.execute({
|
|
430
|
+
method: "GET",
|
|
431
|
+
path: "/v1/events",
|
|
432
|
+
args,
|
|
433
|
+
queryParamKeys: [
|
|
434
|
+
"event_type",
|
|
435
|
+
"customer_id",
|
|
436
|
+
"record_id__in",
|
|
437
|
+
"timestamp_gte",
|
|
438
|
+
"timestamp_lte",
|
|
439
|
+
"order",
|
|
440
|
+
"take",
|
|
441
|
+
"skip"
|
|
442
|
+
],
|
|
443
|
+
baseUrlOverride: resolveIngestBaseUrl(ctx.baseUrl)
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
resource.command("send").description(`Send a single billable event.`).requiredOption("--customer-id <value>", `Customer ID or external ID`).requiredOption("--event-type <value>", `Event type`).requiredOption("--timestamp <value>", `ISO8601 timestamp`).requiredOption("--record <value>", `Event record as JSON (must include "id" key)`).option("--with-price-calculation", `Whether to calculate prices for the event`).addHelpText("after", `
|
|
447
|
+
Examples:
|
|
448
|
+
hyperline events send --customer-id cus_xyz --event-type api_call --timestamp 2024-01-15T10:00:00Z --record '{"id":"evt_1","duration":32}'
|
|
449
|
+
hyperline events send --customer-id cus_xyz --event-type api_call --timestamp 2024-01-15T10:00:00Z --record '{"id":"evt_1"}' --with-price-calculation`).action(async (opts) => {
|
|
450
|
+
const ctx = resource.parent?.opts()._ctx;
|
|
451
|
+
if (!ctx) {
|
|
452
|
+
process.stderr.write("Error: Not authenticated\n");
|
|
453
|
+
process.exit(1);
|
|
454
|
+
}
|
|
455
|
+
let record;
|
|
456
|
+
try {
|
|
457
|
+
record = JSON.parse(opts.record);
|
|
458
|
+
} catch {
|
|
459
|
+
process.stderr.write(`Error: --record must be valid JSON (e.g. '{"id":"evt_1"}')
|
|
460
|
+
`);
|
|
461
|
+
process.exit(1);
|
|
462
|
+
}
|
|
463
|
+
const body = {
|
|
464
|
+
customer_id: opts.customerId,
|
|
465
|
+
event_type: opts.eventType,
|
|
466
|
+
timestamp: opts.timestamp,
|
|
467
|
+
record
|
|
468
|
+
};
|
|
469
|
+
const args = {};
|
|
470
|
+
if (opts.withPriceCalculation !== void 0)
|
|
471
|
+
args.with_price_calculation = true;
|
|
472
|
+
await ctx.execute({
|
|
473
|
+
method: "POST",
|
|
474
|
+
path: "/v1/events",
|
|
475
|
+
args,
|
|
476
|
+
queryParamKeys: ["with_price_calculation"],
|
|
477
|
+
body,
|
|
478
|
+
baseUrlOverride: resolveIngestBaseUrl(ctx.baseUrl)
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
resource.command("send-batch").description(`Send multiple billable events in batch (max 5000 per request). All events must have the same event_type.`).requiredOption("--events <value>", `JSON array of events, each with customer_id, event_type, timestamp, record`).addHelpText("after", `
|
|
482
|
+
Examples:
|
|
483
|
+
hyperline events send-batch --events '[{"customer_id":"cus_xyz","event_type":"api_call","timestamp":"2024-01-15T10:00:00Z","record":{"id":"evt_1"}},{"customer_id":"cus_xyz","event_type":"api_call","timestamp":"2024-01-15T10:01:00Z","record":{"id":"evt_2"}}]'`).action(async (opts) => {
|
|
484
|
+
const ctx = resource.parent?.opts()._ctx;
|
|
485
|
+
if (!ctx) {
|
|
486
|
+
process.stderr.write("Error: Not authenticated\n");
|
|
487
|
+
process.exit(1);
|
|
488
|
+
}
|
|
489
|
+
let events;
|
|
490
|
+
try {
|
|
491
|
+
events = JSON.parse(opts.events);
|
|
492
|
+
} catch {
|
|
493
|
+
process.stderr.write("Error: --events must be valid JSON array\n");
|
|
494
|
+
process.exit(1);
|
|
495
|
+
}
|
|
496
|
+
if (!Array.isArray(events)) {
|
|
497
|
+
process.stderr.write("Error: --events must be a JSON array\n");
|
|
498
|
+
process.exit(1);
|
|
499
|
+
}
|
|
500
|
+
await ctx.execute({
|
|
501
|
+
method: "POST",
|
|
502
|
+
path: "/v1/events/batch",
|
|
503
|
+
args: {},
|
|
504
|
+
body: events,
|
|
505
|
+
baseUrlOverride: resolveIngestBaseUrl(ctx.baseUrl)
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
resource.command("delete").description(`Delete billable events by record IDs, customer ID, or all events.`).option("--matching <value>", `Matching strategy: record_ids, customer_id, or all`).option("--record-ids <value>", `Comma-separated list of record IDs (when matching=record_ids)`).option("--customer-id <value>", `Customer ID (when matching=customer_id)`).addHelpText("after", `
|
|
509
|
+
Examples:
|
|
510
|
+
hyperline events delete --matching record_ids --record-ids evt_1,evt_2
|
|
511
|
+
hyperline events delete --matching customer_id --customer-id cus_xyz
|
|
512
|
+
hyperline events delete --matching all`).action(async (opts) => {
|
|
513
|
+
const ctx = resource.parent?.opts()._ctx;
|
|
514
|
+
if (!ctx) {
|
|
515
|
+
process.stderr.write("Error: Not authenticated\n");
|
|
516
|
+
process.exit(1);
|
|
517
|
+
}
|
|
518
|
+
const matching = opts.matching;
|
|
519
|
+
if (!matching || !["record_ids", "customer_id", "all"].includes(matching)) {
|
|
520
|
+
process.stderr.write("Error: --matching must be one of: record_ids, customer_id, all\n");
|
|
521
|
+
process.exit(1);
|
|
522
|
+
}
|
|
523
|
+
let body;
|
|
524
|
+
if (matching === "record_ids") {
|
|
525
|
+
if (!opts.recordIds) {
|
|
526
|
+
process.stderr.write("Error: --record-ids is required when matching=record_ids\n");
|
|
527
|
+
process.exit(1);
|
|
528
|
+
}
|
|
529
|
+
body = {
|
|
530
|
+
matching: "record_ids",
|
|
531
|
+
record_ids: opts.recordIds.split(",")
|
|
532
|
+
};
|
|
533
|
+
} else if (matching === "customer_id") {
|
|
534
|
+
if (!opts.customerId) {
|
|
535
|
+
process.stderr.write("Error: --customer-id is required when matching=customer_id\n");
|
|
536
|
+
process.exit(1);
|
|
537
|
+
}
|
|
538
|
+
body = { matching: "customer_id", customer_id: opts.customerId };
|
|
539
|
+
} else {
|
|
540
|
+
const confirmed = await confirmPrompt("Are you sure you want to delete ALL events? (y/N) ");
|
|
541
|
+
if (!confirmed) {
|
|
542
|
+
process.stdout.write("Aborted.\n");
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
body = { matching: "all" };
|
|
546
|
+
}
|
|
547
|
+
await ctx.execute({
|
|
548
|
+
method: "DELETE",
|
|
549
|
+
path: "/v1/events",
|
|
550
|
+
args: {},
|
|
551
|
+
body,
|
|
552
|
+
baseUrlOverride: resolveIngestBaseUrl(ctx.baseUrl)
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
resource.command("calculate-prices").description(`Ingest a billable event and calculate its prices. After first ingestion, returns cached prices.`).requiredOption("--customer-id <value>", `Customer ID or external ID`).requiredOption("--event-type <value>", `Event type`).requiredOption("--timestamp <value>", `ISO8601 timestamp`).requiredOption("--record <value>", `Event record as JSON (must include "id" key)`).option("--product-id <value>", `Hyperline product ID`).option("--subscription-id <value>", `Hyperline subscription ID`).option("--include-tax", `Include amounts with tax`).addHelpText("after", `
|
|
556
|
+
Examples:
|
|
557
|
+
hyperline events calculate-prices --customer-id cus_xyz --event-type api_call --timestamp 2024-01-15T10:00:00Z --record '{"id":"evt_1"}'
|
|
558
|
+
hyperline events calculate-prices --customer-id cus_xyz --event-type api_call --timestamp 2024-01-15T10:00:00Z --record '{"id":"evt_1"}' --product-id itm_abc --include-tax`).action(async (opts) => {
|
|
559
|
+
const ctx = resource.parent?.opts()._ctx;
|
|
560
|
+
if (!ctx) {
|
|
561
|
+
process.stderr.write("Error: Not authenticated\n");
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
564
|
+
let record;
|
|
565
|
+
try {
|
|
566
|
+
record = JSON.parse(opts.record);
|
|
567
|
+
} catch {
|
|
568
|
+
process.stderr.write(`Error: --record must be valid JSON (e.g. '{"id":"evt_1"}')
|
|
569
|
+
`);
|
|
570
|
+
process.exit(1);
|
|
571
|
+
}
|
|
572
|
+
const body = {
|
|
573
|
+
customer_id: opts.customerId,
|
|
574
|
+
event_type: opts.eventType,
|
|
575
|
+
timestamp: opts.timestamp,
|
|
576
|
+
record
|
|
577
|
+
};
|
|
578
|
+
const args = {};
|
|
579
|
+
if (opts.productId !== void 0)
|
|
580
|
+
args.product_id = opts.productId;
|
|
581
|
+
if (opts.subscriptionId !== void 0)
|
|
582
|
+
args.subscription_id = opts.subscriptionId;
|
|
583
|
+
if (opts.includeTax !== void 0)
|
|
584
|
+
args.include_tax = true;
|
|
585
|
+
await ctx.execute({
|
|
586
|
+
method: "POST",
|
|
587
|
+
path: "/v1/events/prices",
|
|
588
|
+
args,
|
|
589
|
+
queryParamKeys: ["product_id", "subscription_id", "include_tax"],
|
|
590
|
+
body,
|
|
591
|
+
baseUrlOverride: resolveIngestBaseUrl(ctx.baseUrl)
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
resource.command("simulate-prices").description(`Simulate prices for a billable event without ingesting it.`).requiredOption("--event-type <value>", `Event type`).requiredOption("--record <value>", `Event record as JSON (must include "id" key)`).option("--customer-id <value>", `Customer ID or external ID`).option("--timestamp <value>", `ISO8601 timestamp`).option("--product-id <value>", `Hyperline product ID`).option("--subscription-id <value>", `Hyperline subscription ID`).option("--include-tax", `Include amounts with tax`).addHelpText("after", `
|
|
595
|
+
Examples:
|
|
596
|
+
hyperline events simulate-prices --event-type api_call --record '{"id":"evt_1","duration":32}'
|
|
597
|
+
hyperline events simulate-prices --event-type api_call --record '{"id":"evt_1"}' --product-id itm_abc --include-tax`).action(async (opts) => {
|
|
598
|
+
const ctx = resource.parent?.opts()._ctx;
|
|
599
|
+
if (!ctx) {
|
|
600
|
+
process.stderr.write("Error: Not authenticated\n");
|
|
601
|
+
process.exit(1);
|
|
602
|
+
}
|
|
603
|
+
let record;
|
|
604
|
+
try {
|
|
605
|
+
record = JSON.parse(opts.record);
|
|
606
|
+
} catch {
|
|
607
|
+
process.stderr.write(`Error: --record must be valid JSON (e.g. '{"id":"evt_1"}')
|
|
608
|
+
`);
|
|
609
|
+
process.exit(1);
|
|
610
|
+
}
|
|
611
|
+
const body = {
|
|
612
|
+
event_type: opts.eventType,
|
|
613
|
+
record
|
|
614
|
+
};
|
|
615
|
+
if (opts.customerId !== void 0)
|
|
616
|
+
body.customer_id = opts.customerId;
|
|
617
|
+
if (opts.timestamp !== void 0)
|
|
618
|
+
body.timestamp = opts.timestamp;
|
|
619
|
+
const args = {};
|
|
620
|
+
if (opts.productId !== void 0)
|
|
621
|
+
args.product_id = opts.productId;
|
|
622
|
+
if (opts.subscriptionId !== void 0)
|
|
623
|
+
args.subscription_id = opts.subscriptionId;
|
|
624
|
+
if (opts.includeTax !== void 0)
|
|
625
|
+
args.include_tax = true;
|
|
626
|
+
await ctx.execute({
|
|
627
|
+
method: "POST",
|
|
628
|
+
path: "/v1/events/simulate-prices",
|
|
629
|
+
args,
|
|
630
|
+
queryParamKeys: ["product_id", "subscription_id", "include_tax"],
|
|
631
|
+
body,
|
|
632
|
+
baseUrlOverride: resolveIngestBaseUrl(ctx.baseUrl)
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
resource.command("get-prices").description(`Get previous price calculation results by calculation ID or record ID.`).option("--record-id <value>", `Record ID to look up`).option("--calculation-id <value>", `Calculation ID to look up`).addHelpText("after", `
|
|
636
|
+
Examples:
|
|
637
|
+
hyperline events get-prices --record-id D32NAA8
|
|
638
|
+
hyperline events get-prices --calculation-id cal_1234567890`).action(async (opts) => {
|
|
639
|
+
const ctx = resource.parent?.opts()._ctx;
|
|
640
|
+
if (!ctx) {
|
|
641
|
+
process.stderr.write("Error: Not authenticated\n");
|
|
642
|
+
process.exit(1);
|
|
643
|
+
}
|
|
644
|
+
if (!opts.recordId && !opts.calculationId) {
|
|
645
|
+
process.stderr.write("Error: at least one of --record-id or --calculation-id is required\n");
|
|
646
|
+
process.exit(1);
|
|
647
|
+
}
|
|
648
|
+
const args = {};
|
|
649
|
+
if (opts.recordId !== void 0)
|
|
650
|
+
args.record_id = opts.recordId;
|
|
651
|
+
if (opts.calculationId !== void 0)
|
|
652
|
+
args.calculation_id = opts.calculationId;
|
|
653
|
+
await ctx.execute({
|
|
654
|
+
method: "GET",
|
|
655
|
+
path: "/v1/events/prices",
|
|
656
|
+
args,
|
|
657
|
+
queryParamKeys: ["record_id", "calculation_id"],
|
|
658
|
+
baseUrlOverride: resolveIngestBaseUrl(ctx.baseUrl)
|
|
659
|
+
});
|
|
660
|
+
});
|
|
661
|
+
}
|
|
246
662
|
|
|
247
663
|
// build/commands/generated/analytics.js
|
|
248
664
|
function registerAnalyticsCommands(parent) {
|
|
@@ -354,21 +770,6 @@ Examples:
|
|
|
354
770
|
});
|
|
355
771
|
}
|
|
356
772
|
|
|
357
|
-
// build/commands/confirm.js
|
|
358
|
-
import * as readline from "node:readline";
|
|
359
|
-
function confirmPrompt(message) {
|
|
360
|
-
const rl = readline.createInterface({
|
|
361
|
-
input: process.stdin,
|
|
362
|
-
output: process.stderr
|
|
363
|
-
});
|
|
364
|
-
return new Promise((resolve) => {
|
|
365
|
-
rl.question(message, (answer) => {
|
|
366
|
-
rl.close();
|
|
367
|
-
resolve(answer.toLowerCase() === "y");
|
|
368
|
-
});
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
|
|
372
773
|
// build/commands/generated/coupons.js
|
|
373
774
|
function registerCouponsCommands(parent) {
|
|
374
775
|
const resource = parent.command("coupons").description("Manage coupons");
|
|
@@ -8805,7 +9206,7 @@ ${formatText(value, indent + 2)}`;
|
|
|
8805
9206
|
}
|
|
8806
9207
|
|
|
8807
9208
|
// build/http.js
|
|
8808
|
-
function createCLIContext({ apiKey, baseUrl, outputFormat, source }) {
|
|
9209
|
+
function createCLIContext({ apiKey, baseUrl, outputFormat, source, companyId }) {
|
|
8809
9210
|
const noop = () => {
|
|
8810
9211
|
};
|
|
8811
9212
|
const httpClient = buildHttpClient({
|
|
@@ -8819,8 +9220,9 @@ function createCLIContext({ apiKey, baseUrl, outputFormat, source }) {
|
|
|
8819
9220
|
}
|
|
8820
9221
|
});
|
|
8821
9222
|
return {
|
|
9223
|
+
baseUrl,
|
|
8822
9224
|
outputFormat,
|
|
8823
|
-
async execute({ method, path: path2, args, queryParamKeys = [], body }) {
|
|
9225
|
+
async execute({ method, path: path2, args, queryParamKeys = [], body, baseUrlOverride }) {
|
|
8824
9226
|
const { url, params, data } = buildRequestConfig(path2, args, method, queryParamKeys);
|
|
8825
9227
|
const requestData = body ?? data;
|
|
8826
9228
|
const hasData = Array.isArray(requestData) ? requestData.length > 0 : requestData != null && Object.keys(requestData).length > 0;
|
|
@@ -8830,8 +9232,10 @@ function createCLIContext({ apiKey, baseUrl, outputFormat, source }) {
|
|
|
8830
9232
|
url,
|
|
8831
9233
|
params,
|
|
8832
9234
|
data: hasData ? requestData : void 0,
|
|
9235
|
+
...baseUrlOverride ? { baseURL: baseUrlOverride } : {},
|
|
8833
9236
|
headers: {
|
|
8834
|
-
"Hyperline-Source": source
|
|
9237
|
+
"Hyperline-Source": source,
|
|
9238
|
+
...companyId ? { "Hyperline-CompanyId": companyId } : {}
|
|
8835
9239
|
}
|
|
8836
9240
|
});
|
|
8837
9241
|
process.stdout.write(formatOutput({ data: response.data, format: outputFormat }));
|
|
@@ -8877,7 +9281,7 @@ function isAxiosError2(error) {
|
|
|
8877
9281
|
|
|
8878
9282
|
// build/bin/hyperline.js
|
|
8879
9283
|
var program = new Command();
|
|
8880
|
-
program.name("hyperline").description("Agent-first CLI for the Hyperline API").version("0.1.0").option("--api-key <key>", "Hyperline API key (overrides HYPERLINE_API_KEY)").option("--base-url <url>", "API base URL (overrides HYPERLINE_API_URL)").option("--output <format>", "Output format: json or text", "text");
|
|
9284
|
+
program.name("hyperline").description("Agent-first CLI for the Hyperline API").version("0.1.0").option("--api-key <key>", "Hyperline API key (overrides HYPERLINE_API_KEY)").option("--base-url <url>", "API base URL (overrides HYPERLINE_API_URL)").option("--output <format>", "Output format: json or text", "text").option("--company-id <id>", "Company ID (overrides HYPERLINE_COMPANY_ID and stored selection)");
|
|
8881
9285
|
program.command("login").description("Authenticate with Hyperline via browser").action(async () => {
|
|
8882
9286
|
const options = program.opts();
|
|
8883
9287
|
const baseUrl = await resolveBaseUrl({
|
|
@@ -8889,10 +9293,64 @@ program.command("logout").description("Clear stored credentials").action(async (
|
|
|
8889
9293
|
await clearCredentials();
|
|
8890
9294
|
process.stdout.write("Logged out.\n");
|
|
8891
9295
|
});
|
|
9296
|
+
var companyCommand = program.command("company").description("Manage active company");
|
|
9297
|
+
companyCommand.command("select").description("Interactively select the active company").action(async () => {
|
|
9298
|
+
const options = program.opts();
|
|
9299
|
+
const baseUrl = await resolveBaseUrl({
|
|
9300
|
+
flagBaseUrl: options.baseUrl
|
|
9301
|
+
});
|
|
9302
|
+
const apiKey = resolveApiKey({
|
|
9303
|
+
flagApiKey: options.apiKey
|
|
9304
|
+
});
|
|
9305
|
+
const accessToken = apiKey ?? await resolveAccessToken();
|
|
9306
|
+
if (!accessToken) {
|
|
9307
|
+
process.stderr.write("Error: No authentication found. Run: hyperline login\n");
|
|
9308
|
+
process.exit(1);
|
|
9309
|
+
}
|
|
9310
|
+
await promptCompanySelection({ baseUrl, accessToken });
|
|
9311
|
+
});
|
|
9312
|
+
companyCommand.command("set <companyId>").description("Set the active company by ID (for scripts and agents)").action(async (companyId) => {
|
|
9313
|
+
await saveCompanyId({ companyId });
|
|
9314
|
+
process.stdout.write(`Company set to ${companyId}
|
|
9315
|
+
`);
|
|
9316
|
+
});
|
|
9317
|
+
companyCommand.command("list").description("List all accessible companies").action(async () => {
|
|
9318
|
+
const options = program.opts();
|
|
9319
|
+
const baseUrl = await resolveBaseUrl({
|
|
9320
|
+
flagBaseUrl: options.baseUrl
|
|
9321
|
+
});
|
|
9322
|
+
const apiKey = resolveApiKey({
|
|
9323
|
+
flagApiKey: options.apiKey
|
|
9324
|
+
});
|
|
9325
|
+
const accessToken = apiKey ?? await resolveAccessToken();
|
|
9326
|
+
if (!accessToken) {
|
|
9327
|
+
process.stderr.write("Error: No authentication found. Run: hyperline login\n");
|
|
9328
|
+
process.exit(1);
|
|
9329
|
+
}
|
|
9330
|
+
const companies = await fetchCompanies({ baseUrl, accessToken });
|
|
9331
|
+
const currentCompanyId = await resolveCompanyId({});
|
|
9332
|
+
for (const company of companies) {
|
|
9333
|
+
const marker = company.id === currentCompanyId ? " (active)" : "";
|
|
9334
|
+
process.stdout.write(`${company.name} ${company.id}${marker}
|
|
9335
|
+
`);
|
|
9336
|
+
}
|
|
9337
|
+
});
|
|
9338
|
+
companyCommand.command("current").description("Show the currently active company").action(async () => {
|
|
9339
|
+
const companyId = await resolveCompanyId({});
|
|
9340
|
+
if (companyId) {
|
|
9341
|
+
process.stdout.write(`${companyId}
|
|
9342
|
+
`);
|
|
9343
|
+
} else {
|
|
9344
|
+
process.stdout.write("No company selected. Run: hyperline company select\n");
|
|
9345
|
+
}
|
|
9346
|
+
});
|
|
8892
9347
|
registerAllCommands(program);
|
|
9348
|
+
registerEventsCommands(program);
|
|
9349
|
+
var selfAuthCommands = /* @__PURE__ */ new Set(["login", "logout", "company"]);
|
|
8893
9350
|
program.hook("preAction", async (thisCommand, actionCommand) => {
|
|
8894
9351
|
const commandName = actionCommand.name();
|
|
8895
|
-
|
|
9352
|
+
const parentName = actionCommand.parent?.name();
|
|
9353
|
+
if (selfAuthCommands.has(commandName) || selfAuthCommands.has(parentName ?? ""))
|
|
8896
9354
|
return;
|
|
8897
9355
|
const options = program.opts();
|
|
8898
9356
|
const outputFormat = options.output ?? "text";
|
|
@@ -8907,11 +9365,15 @@ program.hook("preAction", async (thisCommand, actionCommand) => {
|
|
|
8907
9365
|
const baseUrl = await resolveBaseUrl({
|
|
8908
9366
|
flagBaseUrl: options.baseUrl
|
|
8909
9367
|
});
|
|
9368
|
+
const companyId = await resolveCompanyId({
|
|
9369
|
+
flagCompanyId: options.companyId
|
|
9370
|
+
});
|
|
8910
9371
|
const ctx = createCLIContext({
|
|
8911
9372
|
apiKey: token,
|
|
8912
9373
|
baseUrl,
|
|
8913
9374
|
outputFormat,
|
|
8914
|
-
source: apiKey ? "cli" : "cli-oauth"
|
|
9375
|
+
source: apiKey ? "cli" : "cli-oauth",
|
|
9376
|
+
companyId
|
|
8915
9377
|
});
|
|
8916
9378
|
thisCommand.setOptionValue("_ctx", ctx);
|
|
8917
9379
|
});
|