oh-langfuse 0.1.53 → 0.1.55

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/bin/cli.js CHANGED
@@ -1,49 +1,49 @@
1
1
  #!/usr/bin/env node
2
- import fs from "fs";
3
- import path from "path";
4
- import readline from "readline";
5
- import { fileURLToPath } from "url";
6
- import { spawnSync } from "child_process";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import readline from "readline";
5
+ import { fileURLToPath } from "url";
6
+ import { spawnSync } from "child_process";
7
7
 
8
8
  const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
9
9
  const scriptsDir = path.join(rootDir, "scripts");
10
10
 
11
- const DEFAULT_LANGFUSE_BASE_URL = "http://120.46.221.227:3000";
12
- const DEFAULT_LANGFUSE_PUBLIC_KEY = "pk-lf-da0c90a7-6e93-4eb7-bb86-c1047c8d187d";
13
- const DEFAULT_LANGFUSE_SECRET_KEY = "sk-lf-0269b85d-bfdc-442c-bfa3-e737954e3315";
14
- const USER_ID_PATTERN = /^[a-z](?:\d{8}|wx\d{7})$/;
15
- const USER_ID_PATTERN_TEXT = "^[a-z](?:\\d{8}|wx\\d{7})$";
16
-
17
- function nodeMajorVersion() {
18
- const raw = process.versions && process.versions.node ? process.versions.node : "0.0.0";
19
- return Number.parseInt(raw.split(".")[0], 10) || 0;
20
- }
21
-
22
- function assertSupportedNode() {
23
- if (nodeMajorVersion() >= 16) return;
24
- console.error("oh-langfuse requires Node.js >= 16.");
25
- console.error(`Current Node.js: ${process.version}`);
26
- console.error("Please upgrade Node.js, then run: npx oh-langfuse@latest");
27
- process.exit(1);
28
- }
29
-
30
- function createPromptInterface(options) {
31
- const rl = readline.createInterface(options);
32
- return {
33
- question(query) {
34
- return new Promise((resolve) => rl.question(query, resolve));
35
- },
36
- pause() {
37
- rl.pause();
38
- },
39
- resume() {
40
- rl.resume();
41
- },
42
- close() {
43
- rl.close();
44
- },
45
- };
46
- }
11
+ const DEFAULT_LANGFUSE_BASE_URL = "http://120.46.221.227:3000";
12
+ const DEFAULT_LANGFUSE_PUBLIC_KEY = "pk-lf-da0c90a7-6e93-4eb7-bb86-c1047c8d187d";
13
+ const DEFAULT_LANGFUSE_SECRET_KEY = "sk-lf-0269b85d-bfdc-442c-bfa3-e737954e3315";
14
+ const USER_ID_PATTERN = /^[a-z](?:\d{8}|wx\d{7})$/;
15
+ const USER_ID_PATTERN_TEXT = "^[a-z](?:\\d{8}|wx\\d{7})$";
16
+
17
+ function nodeMajorVersion() {
18
+ const raw = process.versions && process.versions.node ? process.versions.node : "0.0.0";
19
+ return Number.parseInt(raw.split(".")[0], 10) || 0;
20
+ }
21
+
22
+ function assertSupportedNode() {
23
+ if (nodeMajorVersion() >= 16) return;
24
+ console.error("oh-langfuse requires Node.js >= 16.");
25
+ console.error(`Current Node.js: ${process.version}`);
26
+ console.error("Please upgrade Node.js, then run: npx oh-langfuse@latest");
27
+ process.exit(1);
28
+ }
29
+
30
+ function createPromptInterface(options) {
31
+ const rl = readline.createInterface(options);
32
+ return {
33
+ question(query) {
34
+ return new Promise((resolve) => rl.question(query, resolve));
35
+ },
36
+ pause() {
37
+ rl.pause();
38
+ },
39
+ resume() {
40
+ rl.resume();
41
+ },
42
+ close() {
43
+ rl.close();
44
+ },
45
+ };
46
+ }
47
47
 
48
48
  const colorEnabled = process.stdout.isTTY && process.env.NO_COLOR !== "1";
49
49
  const ansi = (code) => (colorEnabled ? `\x1b[${code}m` : "");
@@ -114,21 +114,21 @@ function mask(v) {
114
114
  return `${v.slice(0, 4)}...${v.slice(-4)}`;
115
115
  }
116
116
 
117
- function hasValue(v) {
118
- return typeof v === "string" && v.trim().length > 0;
119
- }
120
-
121
- function normalizeUserId(v) {
122
- return String(v || "").trim();
123
- }
124
-
125
- function isValidUserId(v) {
126
- return USER_ID_PATTERN.test(normalizeUserId(v));
127
- }
128
-
129
- function userIdValidationMessage() {
130
- return `User ID must match ${USER_ID_PATTERN_TEXT}, for example h00613222 or hwx1234567.`;
131
- }
117
+ function hasValue(v) {
118
+ return typeof v === "string" && v.trim().length > 0;
119
+ }
120
+
121
+ function normalizeUserId(v) {
122
+ return String(v || "").trim();
123
+ }
124
+
125
+ function isValidUserId(v) {
126
+ return USER_ID_PATTERN.test(normalizeUserId(v));
127
+ }
128
+
129
+ function userIdValidationMessage() {
130
+ return `User ID must match ${USER_ID_PATTERN_TEXT}, for example h00613222 or hwx1234567.`;
131
+ }
132
132
 
133
133
  function scriptPath(name) {
134
134
  return path.join(scriptsDir, name);
@@ -244,7 +244,7 @@ async function ensureEnvironment(rl, target, options) {
244
244
  "Action Needed",
245
245
  missing.map(([name, hint]) => `${paint(name, t.red)} ${paint(hint, t.muted)}`)
246
246
  );
247
- console.log("");
247
+ console.log("");
248
248
  await rl.question(`${paint("Press Enter after installing the missing dependency, or Ctrl+C to exit.", t.gold)} `);
249
249
  return false;
250
250
  }
@@ -280,7 +280,7 @@ function labelValue(label, value, valueStyle = t.reset) {
280
280
  return `${paint(label.padEnd(22), t.muted)} ${paint(value, valueStyle)}`;
281
281
  }
282
282
 
283
- function runNodeScript(name, args = [], { dryRun = false, quiet = false } = {}) {
283
+ function runNodeScript(name, args = [], { dryRun = false, quiet = false } = {}) {
284
284
  const target = scriptPath(name);
285
285
  const cmd = `${process.execPath} ${target} ${args.join(" ")}`.trim();
286
286
  if (dryRun) {
@@ -288,31 +288,31 @@ function runNodeScript(name, args = [], { dryRun = false, quiet = false } = {})
288
288
  return 0;
289
289
  }
290
290
  console.log("");
291
- if (!quiet) console.log(paint("Running installer...", t.bold, t.teal));
292
- if (!quiet) console.log(paint("─".repeat(Math.min(terminalWidth(), 64)), t.panel));
291
+ if (!quiet) console.log(paint("Running installer...", t.bold, t.teal));
292
+ if (!quiet) console.log(paint("─".repeat(Math.min(terminalWidth(), 64)), t.panel));
293
293
  const r = spawnSync(process.execPath, [target, ...args], { stdio: "inherit" });
294
- return r.status != null ? r.status : r.error ? 1 : 0;
295
- }
296
-
297
- async function askText(rl, label, { defaultValue = "", required = false, validate = null, invalidMessage = "" } = {}) {
298
- while (true) {
299
- const suffix = defaultValue ? paint(` ${defaultValue}`, t.muted) : "";
300
- const answer = (await rl.question(`${paint(label, t.cyan)}${suffix}\n${paint(">", t.teal)} `)).trim();
301
- const value = answer || defaultValue;
302
- if (required && !hasValue(value)) {
303
- console.log(paint("This value is required.", t.red));
304
- continue;
305
- }
306
- if (validate && hasValue(value) && !validate(value)) {
307
- console.log(paint(invalidMessage || "Invalid value.", t.red));
308
- continue;
309
- }
310
- if (!required || hasValue(value)) return value;
311
- console.log(paint("This value is required.", t.red));
312
- }
313
- }
314
-
315
- async function askYesNo(rl, label, { defaultValue = false } = {}) {
294
+ return r.status != null ? r.status : r.error ? 1 : 0;
295
+ }
296
+
297
+ async function askText(rl, label, { defaultValue = "", required = false, validate = null, invalidMessage = "" } = {}) {
298
+ while (true) {
299
+ const suffix = defaultValue ? paint(` ${defaultValue}`, t.muted) : "";
300
+ const answer = (await rl.question(`${paint(label, t.cyan)}${suffix}\n${paint(">", t.teal)} `)).trim();
301
+ const value = answer || defaultValue;
302
+ if (required && !hasValue(value)) {
303
+ console.log(paint("This value is required.", t.red));
304
+ continue;
305
+ }
306
+ if (validate && hasValue(value) && !validate(value)) {
307
+ console.log(paint(invalidMessage || "Invalid value.", t.red));
308
+ continue;
309
+ }
310
+ if (!required || hasValue(value)) return value;
311
+ console.log(paint("This value is required.", t.red));
312
+ }
313
+ }
314
+
315
+ async function askYesNo(rl, label, { defaultValue = false } = {}) {
316
316
  const hint = defaultValue ? "Y/n" : "y/N";
317
317
  while (true) {
318
318
  const answer = (await rl.question(`${paint(label, t.cyan)} ${paint(`(${hint})`, t.muted)} `)).trim().toLowerCase();
@@ -320,28 +320,28 @@ async function askYesNo(rl, label, { defaultValue = false } = {}) {
320
320
  if (["y", "yes"].includes(answer)) return true;
321
321
  if (["n", "no"].includes(answer)) return false;
322
322
  console.log(paint("Please answer y or n.", t.red));
323
- }
324
- }
325
-
326
- function rawKeySeq(raw) {
327
- if (Buffer.isBuffer(raw)) return raw.toString("latin1");
328
- return String(raw == null ? "" : raw);
329
- }
330
-
331
- function parseRawKey(raw) {
332
- const seq = rawKeySeq(raw);
333
- if (seq === "\x03") return { name: "ctrl-c", sequence: seq };
334
- if (seq === "\x1b[A" || seq === "\x1bOA" || seq === "\x00H" || seq === "\xe0H") return { name: "up", sequence: seq };
335
- if (seq === "\x1b[B" || seq === "\x1bOB" || seq === "\x00P" || seq === "\xe0P") return { name: "down", sequence: seq };
336
- if (seq === "\r" || seq === "\n" || seq === "\r\n") return { name: "enter", sequence: seq };
337
- if (seq === " ") return { name: "space", sequence: seq };
338
- if (seq === "\x1b") return { name: "escape", sequence: seq };
339
- if (/^[1-9]$/.test(seq)) return { name: "number", number: Number.parseInt(seq, 10), sequence: seq };
340
- if (seq.length === 1) return { name: seq.toLowerCase(), sequence: seq };
341
- return { name: "", sequence: seq };
342
- }
343
-
344
- function renderChoiceScreen(label, choices, index, options = {}) {
323
+ }
324
+ }
325
+
326
+ function rawKeySeq(raw) {
327
+ if (Buffer.isBuffer(raw)) return raw.toString("latin1");
328
+ return String(raw == null ? "" : raw);
329
+ }
330
+
331
+ function parseRawKey(raw) {
332
+ const seq = rawKeySeq(raw);
333
+ if (seq === "\x03") return { name: "ctrl-c", sequence: seq };
334
+ if (seq === "\x1b[A" || seq === "\x1bOA" || seq === "\x00H" || seq === "\xe0H") return { name: "up", sequence: seq };
335
+ if (seq === "\x1b[B" || seq === "\x1bOB" || seq === "\x00P" || seq === "\xe0P") return { name: "down", sequence: seq };
336
+ if (seq === "\r" || seq === "\n" || seq === "\r\n") return { name: "enter", sequence: seq };
337
+ if (seq === " ") return { name: "space", sequence: seq };
338
+ if (seq === "\x1b") return { name: "escape", sequence: seq };
339
+ if (/^[1-9]$/.test(seq)) return { name: "number", number: Number.parseInt(seq, 10), sequence: seq };
340
+ if (seq.length === 1) return { name: seq.toLowerCase(), sequence: seq };
341
+ return { name: "", sequence: seq };
342
+ }
343
+
344
+ function renderChoiceScreen(label, choices, index, options = {}) {
345
345
  clearScreen();
346
346
  renderBrand(options);
347
347
  console.log("");
@@ -362,47 +362,47 @@ function renderChoiceScreen(label, choices, index, options = {}) {
362
362
  });
363
363
  }
364
364
 
365
- async function askChoice(rl, label, choices, options = {}) {
366
- if (process.stdin.isTTY && process.stdout.isTTY) {
367
- rl.pause();
368
- return await new Promise((resolve) => {
369
- let index = 0;
365
+ async function askChoice(rl, label, choices, options = {}) {
366
+ if (process.stdin.isTTY && process.stdout.isTTY) {
367
+ rl.pause();
368
+ return await new Promise((resolve) => {
369
+ let index = 0;
370
370
  const stdin = process.stdin;
371
-
372
- function cleanup(value) {
373
- stdin.off("data", onData);
374
- if (stdin.isTTY) stdin.setRawMode(false);
375
- stdin.pause();
376
- rl.resume();
377
- clearScreen();
378
- resolve(value);
379
- }
380
-
381
- function onData(raw) {
382
- const key = parseRawKey(raw);
383
- if (key.name === "ctrl-c") return cleanup("exit");
384
- if (key.name === "up") {
385
- index = (index - 1 + choices.length) % choices.length;
386
- renderChoiceScreen(label, choices, index, options);
387
- return;
388
- }
389
- if (key.name === "down") {
390
- index = (index + 1) % choices.length;
391
- renderChoiceScreen(label, choices, index, options);
392
- return;
393
- }
394
- if (key.name === "q" || key.name === "escape") return cleanup("exit");
395
- if (key.name === "enter") return cleanup(choices[index].value);
396
- const num = key.name === "number" ? key.number : Number.NaN;
397
- if (Number.isInteger(num) && choices[num - 1]) return cleanup(choices[num - 1].value);
398
- }
399
-
400
- if (stdin.isTTY) stdin.setRawMode(true);
401
- stdin.on("data", onData);
402
- stdin.resume();
403
- renderChoiceScreen(label, choices, index, options);
404
- });
405
- }
371
+
372
+ function cleanup(value) {
373
+ stdin.off("data", onData);
374
+ if (stdin.isTTY) stdin.setRawMode(false);
375
+ stdin.pause();
376
+ rl.resume();
377
+ clearScreen();
378
+ resolve(value);
379
+ }
380
+
381
+ function onData(raw) {
382
+ const key = parseRawKey(raw);
383
+ if (key.name === "ctrl-c") return cleanup("exit");
384
+ if (key.name === "up") {
385
+ index = (index - 1 + choices.length) % choices.length;
386
+ renderChoiceScreen(label, choices, index, options);
387
+ return;
388
+ }
389
+ if (key.name === "down") {
390
+ index = (index + 1) % choices.length;
391
+ renderChoiceScreen(label, choices, index, options);
392
+ return;
393
+ }
394
+ if (key.name === "q" || key.name === "escape") return cleanup("exit");
395
+ if (key.name === "enter") return cleanup(choices[index].value);
396
+ const num = key.name === "number" ? key.number : Number.NaN;
397
+ if (Number.isInteger(num) && choices[num - 1]) return cleanup(choices[num - 1].value);
398
+ }
399
+
400
+ if (stdin.isTTY) stdin.setRawMode(true);
401
+ stdin.on("data", onData);
402
+ stdin.resume();
403
+ renderChoiceScreen(label, choices, index, options);
404
+ });
405
+ }
406
406
 
407
407
  console.log("");
408
408
  console.log(label);
@@ -446,52 +446,52 @@ async function askMultiChoice(rl, label, choices, options = {}) {
446
446
  let index = 0;
447
447
  const selected = new Set(choices.filter((choice) => choice.selected).map((choice) => choice.value));
448
448
  const stdin = process.stdin;
449
-
450
- function cleanup(value) {
451
- stdin.off("data", onData);
452
- if (stdin.isTTY) stdin.setRawMode(false);
453
- stdin.pause();
454
- rl.resume();
455
- clearScreen();
456
- resolve(value);
449
+
450
+ function cleanup(value) {
451
+ stdin.off("data", onData);
452
+ if (stdin.isTTY) stdin.setRawMode(false);
453
+ stdin.pause();
454
+ rl.resume();
455
+ clearScreen();
456
+ resolve(value);
457
457
  }
458
458
 
459
459
  function toggle() {
460
460
  const value = choices[index].value;
461
461
  if (selected.has(value)) selected.delete(value);
462
462
  else selected.add(value);
463
- renderMultiChoiceScreen(label, choices, index, selected, options);
464
- }
465
-
466
- function onData(raw) {
467
- const key = parseRawKey(raw);
468
- if (key.name === "ctrl-c") return cleanup([]);
469
- if (key.name === "up") {
470
- index = (index - 1 + choices.length) % choices.length;
471
- renderMultiChoiceScreen(label, choices, index, selected, options);
472
- return;
473
- }
474
- if (key.name === "down") {
475
- index = (index + 1) % choices.length;
476
- renderMultiChoiceScreen(label, choices, index, selected, options);
477
- return;
478
- }
479
- if (key.name === "q" || key.name === "escape") return cleanup([]);
480
- if (key.name === "space") return toggle();
481
- if (key.name === "enter") return cleanup([...selected]);
482
- const num = key.name === "number" ? key.number : Number.NaN;
483
- if (Number.isInteger(num) && choices[num - 1]) {
484
- index = num - 1;
485
- toggle();
486
- }
487
- }
488
-
489
- if (stdin.isTTY) stdin.setRawMode(true);
490
- stdin.on("data", onData);
491
- stdin.resume();
492
- renderMultiChoiceScreen(label, choices, index, selected, options);
493
- });
494
- }
463
+ renderMultiChoiceScreen(label, choices, index, selected, options);
464
+ }
465
+
466
+ function onData(raw) {
467
+ const key = parseRawKey(raw);
468
+ if (key.name === "ctrl-c") return cleanup([]);
469
+ if (key.name === "up") {
470
+ index = (index - 1 + choices.length) % choices.length;
471
+ renderMultiChoiceScreen(label, choices, index, selected, options);
472
+ return;
473
+ }
474
+ if (key.name === "down") {
475
+ index = (index + 1) % choices.length;
476
+ renderMultiChoiceScreen(label, choices, index, selected, options);
477
+ return;
478
+ }
479
+ if (key.name === "q" || key.name === "escape") return cleanup([]);
480
+ if (key.name === "space") return toggle();
481
+ if (key.name === "enter") return cleanup([...selected]);
482
+ const num = key.name === "number" ? key.number : Number.NaN;
483
+ if (Number.isInteger(num) && choices[num - 1]) {
484
+ index = num - 1;
485
+ toggle();
486
+ }
487
+ }
488
+
489
+ if (stdin.isTTY) stdin.setRawMode(true);
490
+ stdin.on("data", onData);
491
+ stdin.resume();
492
+ renderMultiChoiceScreen(label, choices, index, selected, options);
493
+ });
494
+ }
495
495
 
496
496
  console.log("");
497
497
  console.log(label);
@@ -504,81 +504,81 @@ async function askMultiChoice(rl, label, choices, options = {}) {
504
504
  .map((idx) => choices[idx].value);
505
505
  }
506
506
 
507
- function langfuseConfig(overrides = {}) {
508
- const config = {
509
- baseUrl: envDefault("LANGFUSE_BASEURL", envDefault("LANGFUSE_HOST", DEFAULT_LANGFUSE_BASE_URL)),
510
- publicKey: envDefault("LANGFUSE_PUBLIC_KEY", DEFAULT_LANGFUSE_PUBLIC_KEY),
511
- secretKey: envDefault("LANGFUSE_SECRET_KEY", DEFAULT_LANGFUSE_SECRET_KEY),
512
- userId: ""
513
- };
514
- for (const [key, value] of Object.entries(overrides)) {
515
- if (hasValue(value)) config[key] = value;
516
- }
517
- return config;
518
- }
519
-
520
- async function collectLangfuseConfig(rl, { requireUserId = false, overrides = {} } = {}) {
521
- const config = langfuseConfig(overrides);
522
- renderSection("Langfuse Target", [
523
- labelValue("Base URL", config.baseUrl, t.teal),
524
- labelValue("Public Key", config.publicKey, t.blue),
525
- labelValue("Secret Key", "configured", t.teal)
526
- ]);
527
- if (hasValue(config.userId)) {
528
- config.userId = normalizeUserId(config.userId);
529
- if (!isValidUserId(config.userId)) {
530
- throw new Error(userIdValidationMessage());
531
- }
532
- if (requireUserId) return config;
533
- }
534
- config.userId = await askText(rl, "User ID / employee number, for example h00613222", {
535
- defaultValue: "",
536
- required: requireUserId,
537
- validate: isValidUserId,
538
- invalidMessage: userIdValidationMessage()
539
- });
507
+ function langfuseConfig(overrides = {}) {
508
+ const config = {
509
+ baseUrl: envDefault("LANGFUSE_BASEURL", envDefault("LANGFUSE_HOST", DEFAULT_LANGFUSE_BASE_URL)),
510
+ publicKey: envDefault("LANGFUSE_PUBLIC_KEY", DEFAULT_LANGFUSE_PUBLIC_KEY),
511
+ secretKey: envDefault("LANGFUSE_SECRET_KEY", DEFAULT_LANGFUSE_SECRET_KEY),
512
+ userId: ""
513
+ };
514
+ for (const [key, value] of Object.entries(overrides)) {
515
+ if (hasValue(value)) config[key] = value;
516
+ }
517
+ return config;
518
+ }
519
+
520
+ async function collectLangfuseConfig(rl, { requireUserId = false, overrides = {} } = {}) {
521
+ const config = langfuseConfig(overrides);
522
+ renderSection("Langfuse Target", [
523
+ labelValue("Base URL", config.baseUrl, t.teal),
524
+ labelValue("Public Key", config.publicKey, t.blue),
525
+ labelValue("Secret Key", "configured", t.teal)
526
+ ]);
527
+ if (hasValue(config.userId)) {
528
+ config.userId = normalizeUserId(config.userId);
529
+ if (!isValidUserId(config.userId)) {
530
+ throw new Error(userIdValidationMessage());
531
+ }
532
+ if (requireUserId) return config;
533
+ }
534
+ config.userId = await askText(rl, "User ID / employee number, for example h00613222", {
535
+ defaultValue: "",
536
+ required: requireUserId,
537
+ validate: isValidUserId,
538
+ invalidMessage: userIdValidationMessage()
539
+ });
540
540
  return config;
541
541
  }
542
542
 
543
- async function collectSharedConfig(rl, options) {
544
- clearScreen();
545
- renderBrand(options);
546
- return await collectLangfuseConfig(rl, { requireUserId: true, overrides: options.configOverrides });
547
- }
548
-
549
- function commonLangfuseArgs(config) {
550
- return [
551
- `--langfuseBaseUrl=${config.baseUrl}`,
552
- `--publicKey=${config.publicKey}`,
553
- `--secretKey=${config.secretKey}`,
554
- ...(hasValue(config.userId) ? [`--userId=${config.userId}`] : [])
555
- ];
556
- }
557
-
558
- function optionalInstallerArgs(options) {
559
- return [...(hasValue(options.pipIndexUrl) ? [`--pipIndexUrl=${options.pipIndexUrl}`] : [])];
560
- }
561
-
562
- async function confirmAction(rl, title, rows, options) {
543
+ async function collectSharedConfig(rl, options) {
544
+ clearScreen();
545
+ renderBrand(options);
546
+ return await collectLangfuseConfig(rl, { requireUserId: true, overrides: options.configOverrides });
547
+ }
548
+
549
+ function commonLangfuseArgs(config) {
550
+ return [
551
+ `--langfuseBaseUrl=${config.baseUrl}`,
552
+ `--publicKey=${config.publicKey}`,
553
+ `--secretKey=${config.secretKey}`,
554
+ ...(hasValue(config.userId) ? [`--userId=${config.userId}`] : [])
555
+ ];
556
+ }
557
+
558
+ function optionalInstallerArgs(options) {
559
+ return [...(hasValue(options.pipIndexUrl) ? [`--pipIndexUrl=${options.pipIndexUrl}`] : [])];
560
+ }
561
+
562
+ async function confirmAction(rl, title, rows, options) {
563
563
  clearScreen();
564
564
  renderBrand(options);
565
565
  renderSection(title, rows);
566
- if (options.dryRun || options.yes) return true;
566
+ if (options.dryRun || options.yes) return true;
567
567
  console.log("");
568
- return await askYesNo(rl, "Continue with these changes", { defaultValue: false });
568
+ return await askYesNo(rl, "Continue with these changes", { defaultValue: false });
569
569
  }
570
570
 
571
- async function setupClaude(rl, options) {
572
- if (!options.dryRun) {
573
- while (!(await ensureEnvironment(rl, "claude", options))) {}
574
- }
575
- clearScreen();
576
- renderBrand(options);
577
- const config = options.config || (await collectLangfuseConfig(rl, { requireUserId: true, overrides: options.configOverrides }));
571
+ async function setupClaude(rl, options) {
572
+ if (!options.dryRun) {
573
+ while (!(await ensureEnvironment(rl, "claude", options))) {}
574
+ }
575
+ clearScreen();
576
+ renderBrand(options);
577
+ const config = options.config || (await collectLangfuseConfig(rl, { requireUserId: true, overrides: options.configOverrides }));
578
578
  const defaultHook = path.join(rootDir, "langfuse_hook.py");
579
579
  const pyPath = fs.existsSync(defaultHook) ? defaultHook : path.join(rootDir, "langfuse_hook.py");
580
580
 
581
- const args = [...commonLangfuseArgs(config), ...optionalInstallerArgs(options), `--pyPath=${pyPath}`];
581
+ const args = [...commonLangfuseArgs(config), ...optionalInstallerArgs(options), `--pyPath=${pyPath}`];
582
582
  const ok = await confirmAction(
583
583
  rl,
584
584
  "Claude Code Langfuse Setup",
@@ -589,29 +589,29 @@ async function setupClaude(rl, options) {
589
589
  labelValue("Python package", "langfuse", t.gold)
590
590
  ],
591
591
  options
592
- );
593
- if (!ok) return 0;
594
- const code = runNodeScript("langfuse-setup.mjs", args, options);
595
- if (code === 0 && !options.dryRun && !options.skipCheck) return checkClaude(options, { clear: false });
596
- return code;
597
- }
598
-
599
- async function setupOpenCode(rl, options) {
600
- if (!options.dryRun) {
601
- while (!(await ensureEnvironment(rl, "opencode", options))) {}
602
- }
603
- clearScreen();
604
- renderBrand(options);
605
- const config = options.config || (await collectLangfuseConfig(rl, { requireUserId: true, overrides: options.configOverrides }));
606
- const setEnv = !options.noSetEnv;
607
- const installPlugin = !options.skipPluginInstall;
608
- const cliPath = options.cmd || "";
609
-
610
- const args = [
611
- ...commonLangfuseArgs(config),
612
- ...(hasValue(options.npmRegistry) ? [`--npmRegistry=${options.npmRegistry}`] : []),
613
- ...(!setEnv ? ["--no-set-env"] : []),
614
- ...(!installPlugin ? ["--skip-plugin-install"] : []),
592
+ );
593
+ if (!ok) return 0;
594
+ const code = runNodeScript("langfuse-setup.mjs", args, options);
595
+ if (code === 0 && !options.dryRun && !options.skipCheck) return checkClaude(options, { clear: false });
596
+ return code;
597
+ }
598
+
599
+ async function setupOpenCode(rl, options) {
600
+ if (!options.dryRun) {
601
+ while (!(await ensureEnvironment(rl, "opencode", options))) {}
602
+ }
603
+ clearScreen();
604
+ renderBrand(options);
605
+ const config = options.config || (await collectLangfuseConfig(rl, { requireUserId: true, overrides: options.configOverrides }));
606
+ const setEnv = !options.noSetEnv;
607
+ const installPlugin = !options.skipPluginInstall;
608
+ const cliPath = options.cmd || "";
609
+
610
+ const args = [
611
+ ...commonLangfuseArgs(config),
612
+ ...(hasValue(options.npmRegistry) ? [`--npmRegistry=${options.npmRegistry}`] : []),
613
+ ...(!setEnv ? ["--no-set-env"] : []),
614
+ ...(!installPlugin ? ["--skip-plugin-install"] : []),
615
615
  ...(hasValue(cliPath) ? [`--cmd=${cliPath}`] : [])
616
616
  ];
617
617
  const ok = await confirmAction(
@@ -619,31 +619,31 @@ async function setupOpenCode(rl, options) {
619
619
  "OpenCode Langfuse Setup",
620
620
  [
621
621
  labelValue("User ID", config.userId || "<none>", config.userId ? t.teal : t.muted),
622
- labelValue("User env vars", setEnv ? "write" : "skip", setEnv ? t.teal : t.gold),
623
- labelValue("Plugin install", installPlugin ? "install/update" : "skip", installPlugin ? t.teal : t.gold),
624
- labelValue("CLI path", cliPath || "auto-detect", t.blue),
625
- labelValue("Target runtime", "current shell", t.violet),
626
- labelValue("Config file", "~/.config/opencode/opencode.json", t.violet)
622
+ labelValue("User env vars", setEnv ? "write" : "skip", setEnv ? t.teal : t.gold),
623
+ labelValue("Plugin install", installPlugin ? "install/update" : "skip", installPlugin ? t.teal : t.gold),
624
+ labelValue("CLI path", cliPath || "auto-detect", t.blue),
625
+ labelValue("Target runtime", "current shell", t.violet),
626
+ labelValue("Config file", "~/.config/opencode/opencode.json", t.violet)
627
627
  ],
628
628
  options
629
- );
630
- if (!ok) return 0;
631
- const code = runNodeScript("opencode-langfuse-setup.mjs", args, options);
632
- if (code === 0 && !options.dryRun && !options.skipCheck) return checkOpenCode(options, { clear: false });
633
- return code;
634
- }
635
-
636
- async function setupCodex(rl, options) {
637
- if (!options.dryRun) {
638
- while (!(await ensureEnvironment(rl, "codex", options))) {}
639
- }
640
- clearScreen();
641
- renderBrand(options);
642
- const config = options.config || (await collectLangfuseConfig(rl, { requireUserId: true, overrides: options.configOverrides }));
629
+ );
630
+ if (!ok) return 0;
631
+ const code = runNodeScript("opencode-langfuse-setup.mjs", args, options);
632
+ if (code === 0 && !options.dryRun && !options.skipCheck) return checkOpenCode(options, { clear: false });
633
+ return code;
634
+ }
635
+
636
+ async function setupCodex(rl, options) {
637
+ if (!options.dryRun) {
638
+ while (!(await ensureEnvironment(rl, "codex", options))) {}
639
+ }
640
+ clearScreen();
641
+ renderBrand(options);
642
+ const config = options.config || (await collectLangfuseConfig(rl, { requireUserId: true, overrides: options.configOverrides }));
643
643
  const defaultHook = path.join(rootDir, "codex_langfuse_notify.py");
644
644
  const pyPath = fs.existsSync(defaultHook) ? defaultHook : path.join(rootDir, "codex_langfuse_notify.py");
645
645
 
646
- const args = [...commonLangfuseArgs(config), ...optionalInstallerArgs(options), `--pyPath=${pyPath}`];
646
+ const args = [...commonLangfuseArgs(config), ...optionalInstallerArgs(options), `--pyPath=${pyPath}`];
647
647
  const ok = await confirmAction(
648
648
  rl,
649
649
  "Codex Langfuse Setup",
@@ -654,12 +654,12 @@ async function setupCodex(rl, options) {
654
654
  labelValue("Python package", "langfuse", t.gold)
655
655
  ],
656
656
  options
657
- );
658
- if (!ok) return 0;
659
- const code = runNodeScript("codex-langfuse-setup.mjs", args, options);
660
- if (code === 0 && !options.dryRun && !options.skipCheck) return checkCodex(options, { clear: false });
661
- return code;
662
- }
657
+ );
658
+ if (!ok) return 0;
659
+ const code = runNodeScript("codex-langfuse-setup.mjs", args, options);
660
+ if (code === 0 && !options.dryRun && !options.skipCheck) return checkCodex(options, { clear: false });
661
+ return code;
662
+ }
663
663
 
664
664
  function checkClaude(options, { clear = true } = {}) {
665
665
  if (clear) clearScreen();
@@ -667,11 +667,11 @@ function checkClaude(options, { clear = true } = {}) {
667
667
  return runNodeScript("langfuse-check.mjs", [], options);
668
668
  }
669
669
 
670
- function checkOpenCode(options, { clear = true } = {}) {
671
- if (clear) clearScreen();
672
- renderBrand(options);
673
- return runNodeScript("opencode-langfuse-check.mjs", [], options);
674
- }
670
+ function checkOpenCode(options, { clear = true } = {}) {
671
+ if (clear) clearScreen();
672
+ renderBrand(options);
673
+ return runNodeScript("opencode-langfuse-check.mjs", [], options);
674
+ }
675
675
 
676
676
  function checkCodex(options, { clear = true } = {}) {
677
677
  if (clear) clearScreen();
@@ -711,34 +711,34 @@ async function checkMenu(rl, options) {
711
711
  return claude || opencode || codex;
712
712
  }
713
713
 
714
- async function interactiveMain(options) {
715
- const rl = createPromptInterface({ input: process.stdin, output: process.stdout });
714
+ async function interactiveMain(options) {
715
+ const rl = createPromptInterface({ input: process.stdin, output: process.stdout });
716
716
  try {
717
717
  const action = await askChoice(
718
718
  rl,
719
719
  "What would you like to configure?",
720
720
  [
721
- { label: "Setup Langfuse", value: "setup-langfuse", description: "Select one or more targets: Claude Code, OpenCode, Codex." },
722
- { label: "Update Installed Runtimes", value: "update", description: "Refresh installed Claude, OpenCode, and Codex Langfuse hooks/plugins." },
723
- { label: "Check Environment", value: "check-environment", description: "Verify required local tools before setup." },
724
- { label: "Check Configuration", value: "check", description: "Inspect current setup without changing local files." },
725
- { label: "Exit", value: "exit", description: "Close the setup console." }
721
+ { label: "Setup Langfuse", value: "setup-langfuse", description: "Select one or more targets: Claude Code, OpenCode, Codex." },
722
+ { label: "Update Installed Runtimes", value: "update", description: "Refresh installed Claude, OpenCode, and Codex Langfuse hooks/plugins." },
723
+ { label: "Check Environment", value: "check-environment", description: "Verify required local tools before setup." },
724
+ { label: "Check Configuration", value: "check", description: "Inspect current setup without changing local files." },
725
+ { label: "Exit", value: "exit", description: "Close the setup console." }
726
726
  ],
727
727
  options
728
728
  );
729
-
730
- if (action === "setup-langfuse") return await setupLangfuseMenu(rl, options);
731
- if (action === "update") {
732
- const config = langfuseConfig(options.configOverrides);
733
- return runNodeScript("update-langfuse-runtime.mjs", [
734
- "all",
735
- ...commonLangfuseArgs(config),
736
- ...(hasValue(options.npmRegistry) ? [`--npmRegistry=${options.npmRegistry}`] : []),
737
- ...(hasValue(options.pipIndexUrl) ? [`--pipIndexUrl=${options.pipIndexUrl}`] : []),
738
- ...(options.skipCheck ? ["--skip-check"] : []),
739
- ...(options.skipPluginInstall ? ["--skip-plugin-install"] : []),
740
- ], options);
741
- }
729
+
730
+ if (action === "setup-langfuse") return await setupLangfuseMenu(rl, options);
731
+ if (action === "update") {
732
+ const config = langfuseConfig(options.configOverrides);
733
+ return runNodeScript("update-langfuse-runtime.mjs", [
734
+ "all",
735
+ ...commonLangfuseArgs(config),
736
+ ...(hasValue(options.npmRegistry) ? [`--npmRegistry=${options.npmRegistry}`] : []),
737
+ ...(hasValue(options.pipIndexUrl) ? [`--pipIndexUrl=${options.pipIndexUrl}`] : []),
738
+ ...(options.skipCheck ? ["--skip-check"] : []),
739
+ ...(options.skipPluginInstall ? ["--skip-plugin-install"] : []),
740
+ ], options);
741
+ }
742
742
  if (action === "setup-claude") return await setupClaude(rl, options);
743
743
  if (action === "setup-opencode") return await setupOpenCode(rl, options);
744
744
  if (action === "setup-codex") return await setupCodex(rl, options);
@@ -760,7 +760,7 @@ async function setupLangfuseMenu(rl, options) {
760
760
  rl,
761
761
  "Select Langfuse setup targets",
762
762
  [
763
- { label: "Claude Code Langfuse", value: "claude", selected: false, description: "Install the Langfuse Stop hook and connect Claude transcripts." },
763
+ { label: "Claude Code Langfuse", value: "claude", selected: false, description: "Install the Langfuse Stop hook and connect Claude transcripts." },
764
764
  { label: "OpenCode Langfuse", value: "opencode", selected: false, description: "Install the Langfuse plugin and enable OpenTelemetry." },
765
765
  { label: "Codex Langfuse", value: "codex", selected: false, description: "Install the Codex notify hook and connect session JSONL events." }
766
766
  ],
@@ -770,75 +770,75 @@ async function setupLangfuseMenu(rl, options) {
770
770
  if (!targets.length) return 0;
771
771
  const config = await collectSharedConfig(rl, options);
772
772
  let code = 0;
773
- if (targets.includes("claude")) code = code || await setupClaude(rl, { ...options, config });
774
- if (targets.includes("opencode")) code = code || await setupOpenCode(rl, { ...options, config });
775
- if (targets.includes("codex")) code = code || await setupCodex(rl, { ...options, config });
773
+ if (targets.includes("claude")) code = code || await setupClaude(rl, { ...options, config });
774
+ if (targets.includes("opencode")) code = code || await setupOpenCode(rl, { ...options, config });
775
+ if (targets.includes("codex")) code = code || await setupCodex(rl, { ...options, config });
776
776
  return code;
777
777
  }
778
778
 
779
- function printHelp() {
780
- renderBrand({ dryRun: false });
781
- console.log("");
782
- renderSection("Usage", [
783
- "npx oh-langfuse@latest",
784
- "npx oh-langfuse@latest check opencode",
785
- "oh-langfuse",
786
- "oh-langfuse setup",
787
- "oh-langfuse setup claude",
788
- "oh-langfuse setup opencode",
789
- "oh-langfuse setup codex",
790
- "oh-langfuse check",
791
- "oh-langfuse check environment",
792
- "oh-langfuse check claude",
793
- "oh-langfuse check opencode",
794
- "oh-langfuse check codex",
795
- "oh-langfuse update",
796
- "oh-langfuse update all",
797
- "oh-langfuse update claude",
798
- "oh-langfuse update opencode",
799
- "oh-langfuse update codex",
800
- "oh-langfuse auto-update opencode"
779
+ function printHelp() {
780
+ renderBrand({ dryRun: false });
781
+ console.log("");
782
+ renderSection("Usage", [
783
+ "npx oh-langfuse@latest",
784
+ "npx oh-langfuse@latest check opencode",
785
+ "oh-langfuse",
786
+ "oh-langfuse setup",
787
+ "oh-langfuse setup claude",
788
+ "oh-langfuse setup opencode",
789
+ "oh-langfuse setup codex",
790
+ "oh-langfuse check",
791
+ "oh-langfuse check environment",
792
+ "oh-langfuse check claude",
793
+ "oh-langfuse check opencode",
794
+ "oh-langfuse check codex",
795
+ "oh-langfuse update",
796
+ "oh-langfuse update all",
797
+ "oh-langfuse update claude",
798
+ "oh-langfuse update opencode",
799
+ "oh-langfuse update codex",
800
+ "oh-langfuse auto-update opencode"
801
+ ]);
802
+ renderSection("Options", [
803
+ `${paint("--dry-run", t.gold)} Preview actions without writing files or installing packages.`,
804
+ `${paint("--userId=ID", t.gold)} Provide the Langfuse user id without prompting.`,
805
+ `${paint("--langfuseBaseUrl=URL", t.gold)} Override the Langfuse base URL.`,
806
+ `${paint("--publicKey=KEY", t.gold)} Override the Langfuse public key.`,
807
+ `${paint("--secretKey=KEY", t.gold)} Override the Langfuse secret key.`,
808
+ `${paint("--yes", t.gold)} Accept setup changes without the confirmation prompt.`,
809
+ `${paint("--npmRegistry=URL", t.gold)} Use a specific npm registry when installing the OpenCode plugin.`,
810
+ `${paint("--pipIndexUrl=URL", t.gold)} Use a specific pip index for Python Langfuse installs.`,
811
+ `${paint("--skip-check", t.gold)} Do not run the target check after a successful setup.`,
812
+ `${paint("--help", t.gold)} Show this help.`
801
813
  ]);
802
- renderSection("Options", [
803
- `${paint("--dry-run", t.gold)} Preview actions without writing files or installing packages.`,
804
- `${paint("--userId=ID", t.gold)} Provide the Langfuse user id without prompting.`,
805
- `${paint("--langfuseBaseUrl=URL", t.gold)} Override the Langfuse base URL.`,
806
- `${paint("--publicKey=KEY", t.gold)} Override the Langfuse public key.`,
807
- `${paint("--secretKey=KEY", t.gold)} Override the Langfuse secret key.`,
808
- `${paint("--yes", t.gold)} Accept setup changes without the confirmation prompt.`,
809
- `${paint("--npmRegistry=URL", t.gold)} Use a specific npm registry when installing the OpenCode plugin.`,
810
- `${paint("--pipIndexUrl=URL", t.gold)} Use a specific pip index for Python Langfuse installs.`,
811
- `${paint("--skip-check", t.gold)} Do not run the target check after a successful setup.`,
812
- `${paint("--help", t.gold)} Show this help.`
813
- ]);
814
- }
815
-
816
- function commandNeedsPrompt(cmd, target) {
817
- if (!cmd) return true;
818
- if (cmd === "setup") return true;
819
- if (cmd === "check" && !["claude", "opencode", "codex", "environment"].includes(target || "")) return true;
820
- return false;
821
- }
822
-
823
- async function main() {
824
- const args = parseArgs(process.argv.slice(2));
825
- const options = {
826
- dryRun: !!args["dry-run"],
827
- npmRegistry: args.npmRegistry || "",
828
- pipIndexUrl: args.pipIndexUrl || "",
829
- yes: !!(args.yes || args.y),
830
- noSetEnv: !!args["no-set-env"],
831
- skipPluginInstall: !!(args["skip-plugin-install"] || args.skipNpmInstall),
832
- skipCheck: !!args["skip-check"],
833
- startupStatus: !!(args["startup-status"] || args.startupStatus),
834
- cmd: args.cmd || "",
835
- configOverrides: {
836
- baseUrl: args.langfuseBaseUrl || args.langfuseHost || args.host || "",
837
- publicKey: args.publicKey || "",
838
- secretKey: args.secretKey || "",
839
- userId: args.userId || args.userid || ""
840
- }
841
- };
814
+ }
815
+
816
+ function commandNeedsPrompt(cmd, target) {
817
+ if (!cmd) return true;
818
+ if (cmd === "setup") return true;
819
+ if (cmd === "check" && !["claude", "opencode", "codex", "environment"].includes(target || "")) return true;
820
+ return false;
821
+ }
822
+
823
+ async function main() {
824
+ const args = parseArgs(process.argv.slice(2));
825
+ const options = {
826
+ dryRun: !!args["dry-run"],
827
+ npmRegistry: args.npmRegistry || "",
828
+ pipIndexUrl: args.pipIndexUrl || "",
829
+ yes: !!(args.yes || args.y),
830
+ noSetEnv: !!args["no-set-env"],
831
+ skipPluginInstall: !!(args["skip-plugin-install"] || args.skipNpmInstall),
832
+ skipCheck: !!args["skip-check"],
833
+ startupStatus: !!(args["startup-status"] || args.startupStatus),
834
+ cmd: args.cmd || "",
835
+ configOverrides: {
836
+ baseUrl: args.langfuseBaseUrl || args.langfuseHost || args.host || "",
837
+ publicKey: args.publicKey || "",
838
+ secretKey: args.secretKey || "",
839
+ userId: args.userId || args.userid || ""
840
+ }
841
+ };
842
842
  const [cmd, target] = args._;
843
843
 
844
844
  if (args.help || args.h) {
@@ -848,34 +848,34 @@ async function main() {
848
848
 
849
849
  if (!cmd) return await interactiveMain(options);
850
850
 
851
- const rl = commandNeedsPrompt(cmd, target) ? createPromptInterface({ input: process.stdin, output: process.stdout }) : null;
852
- try {
853
- if (cmd === "setup" && target === "claude") return await setupClaude(rl, options);
854
- if (cmd === "setup" && target === "opencode") return await setupOpenCode(rl, options);
851
+ const rl = commandNeedsPrompt(cmd, target) ? createPromptInterface({ input: process.stdin, output: process.stdout }) : null;
852
+ try {
853
+ if (cmd === "setup" && target === "claude") return await setupClaude(rl, options);
854
+ if (cmd === "setup" && target === "opencode") return await setupOpenCode(rl, options);
855
855
  if (cmd === "setup" && target === "codex") return await setupCodex(rl, options);
856
- if (cmd === "setup") return await setupLangfuseMenu(rl, options);
857
- if (cmd === "update") {
858
- const updateArgs = [
859
- target || "all",
860
- ...commonLangfuseArgs(langfuseConfig(options.configOverrides)),
861
- ...(hasValue(options.npmRegistry) ? [`--npmRegistry=${options.npmRegistry}`] : []),
862
- ...(hasValue(options.pipIndexUrl) ? [`--pipIndexUrl=${options.pipIndexUrl}`] : []),
863
- ...(options.skipCheck ? ["--skip-check"] : []),
864
- ...(options.skipPluginInstall ? ["--skip-plugin-install"] : []),
865
- ];
866
- return runNodeScript("update-langfuse-runtime.mjs", updateArgs, options);
867
- }
868
- if (cmd === "auto-update") {
869
- return runNodeScript("auto-update-runtime.mjs", [
870
- target || "all",
871
- ...(hasValue(options.npmRegistry) ? [`--npmRegistry=${options.npmRegistry}`] : []),
872
- ...(hasValue(options.pipIndexUrl) ? [`--pipIndexUrl=${options.pipIndexUrl}`] : []),
873
- ...(options.skipCheck ? ["--skip-check"] : []),
874
- ...(options.startupStatus ? ["--startup-status"] : []),
875
- ...(options.yes ? ["--yes"] : []),
876
- ], { ...options, quiet: true });
877
- }
878
- if (cmd === "check" && target === "claude") return checkClaude(options);
856
+ if (cmd === "setup") return await setupLangfuseMenu(rl, options);
857
+ if (cmd === "update") {
858
+ const updateArgs = [
859
+ target || "all",
860
+ ...commonLangfuseArgs(langfuseConfig(options.configOverrides)),
861
+ ...(hasValue(options.npmRegistry) ? [`--npmRegistry=${options.npmRegistry}`] : []),
862
+ ...(hasValue(options.pipIndexUrl) ? [`--pipIndexUrl=${options.pipIndexUrl}`] : []),
863
+ ...(options.skipCheck ? ["--skip-check"] : []),
864
+ ...(options.skipPluginInstall ? ["--skip-plugin-install"] : []),
865
+ ];
866
+ return runNodeScript("update-langfuse-runtime.mjs", updateArgs, options);
867
+ }
868
+ if (cmd === "auto-update") {
869
+ return runNodeScript("auto-update-runtime.mjs", [
870
+ target || "all",
871
+ ...(hasValue(options.npmRegistry) ? [`--npmRegistry=${options.npmRegistry}`] : []),
872
+ ...(hasValue(options.pipIndexUrl) ? [`--pipIndexUrl=${options.pipIndexUrl}`] : []),
873
+ ...(options.skipCheck ? ["--skip-check"] : []),
874
+ ...(options.startupStatus ? ["--startup-status"] : []),
875
+ ...(options.yes ? ["--yes"] : []),
876
+ ], { ...options, quiet: true });
877
+ }
878
+ if (cmd === "check" && target === "claude") return checkClaude(options);
879
879
  if (cmd === "check" && target === "opencode") return checkOpenCode(options);
880
880
  if (cmd === "check" && target === "codex") return checkCodex(options);
881
881
  if (cmd === "check" && target === "environment") {
@@ -885,19 +885,19 @@ async function main() {
885
885
  return 0;
886
886
  }
887
887
  if (cmd === "check") return await checkMenu(rl, options);
888
- } finally {
889
- rl?.close();
890
- }
888
+ } finally {
889
+ rl?.close();
890
+ }
891
891
 
892
892
  printHelp();
893
893
  return 1;
894
894
  }
895
895
 
896
- assertSupportedNode();
897
-
898
- main()
899
- .then((code) => process.exit(code))
900
- .catch((err) => {
901
- console.error(paint((err && err.message) || String(err), t.red));
902
- process.exit(1);
903
- });
896
+ assertSupportedNode();
897
+
898
+ main()
899
+ .then((code) => process.exit(code))
900
+ .catch((err) => {
901
+ console.error(paint((err && err.message) || String(err), t.red));
902
+ process.exit(1);
903
+ });