oh-aicoding-tool 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.
- package/CODEX_LANGFUSE_PLAN.md +62 -0
- package/README.md +114 -0
- package/bin/cli.js +166 -0
- package/bin/langfuse-cli.js +718 -0
- package/codex_langfuse_notify.py +591 -0
- package/langfuse_hook.py +603 -0
- package/opencode-ohai-report/.claude/commands/report-ai-issue.md +60 -0
- package/opencode-ohai-report/.opencode/commands/report-ai-issue.md +30 -0
- package/opencode-ohai-report/.opencode/plugins/oh-ai-report.ts +569 -0
- package/opencode-ohai-report/README.md +45 -0
- package/opencode-ohai-report/bin/cli.js +421 -0
- package/opencode-ohai-report/docs/opencode-ai-issue-collection-architecture.md +313 -0
- package/opencode-ohai-report/docs/opencode-ai-issue-collection-best-practices.md +476 -0
- package/opencode-ohai-report/docs/opencode-ai-issue-collection-phase1-summary.md +405 -0
- package/opencode-ohai-report/examples/issue_output.json +4 -0
- package/opencode-ohai-report/package.json +40 -0
- package/opencode-ohai-report/scripts/claude_report_hook.py +257 -0
- package/opencode-ohai-report/scripts/create_issue.py +34 -0
- package/opencode-ohai-report/scripts/install-claude-plugin.ps1 +254 -0
- package/opencode-ohai-report/scripts/install-opencode-plugin.ps1 +264 -0
- package/opencode-ohai-report/scripts/install-opencode-plugin.sh +218 -0
- package/opencode-ohai-report/scripts/merge-claude-settings.py +99 -0
- package/opencode-ohai-report/tools/ohai-report/README.md +151 -0
- package/opencode-ohai-report/tools/ohai-report/examples/issue-input.json +26 -0
- package/opencode-ohai-report/tools/ohai-report/ohai_report/__init__.py +5 -0
- package/opencode-ohai-report/tools/ohai-report/ohai_report/__main__.py +9 -0
- package/opencode-ohai-report/tools/ohai-report/ohai_report/cli.py +319 -0
- package/opencode-ohai-report/tools/ohai-report/ohai_report/git_context.py +32 -0
- package/opencode-ohai-report/tools/ohai-report/ohai_report/gitcode_defaults.py +14 -0
- package/opencode-ohai-report/tools/ohai-report/ohai_report/issue_markdown.py +313 -0
- package/opencode-ohai-report/tools/ohai-report/ohai_report/metadata.py +360 -0
- package/opencode-ohai-report/tools/ohai-report/ohai_report/observability/__init__.py +1 -0
- package/opencode-ohai-report/tools/ohai-report/ohai_report/observability/langfuse.py +38 -0
- package/opencode-ohai-report/tools/ohai-report/ohai_report/payload.py +64 -0
- package/opencode-ohai-report/tools/ohai-report/ohai_report/schema.py +80 -0
- package/opencode-ohai-report/tools/ohai-report/ohai_report/sinks/__init__.py +1 -0
- package/opencode-ohai-report/tools/ohai-report/ohai_report/sinks/base.py +15 -0
- package/opencode-ohai-report/tools/ohai-report/ohai_report/sinks/gitcode.py +405 -0
- package/opencode-ohai-report/tools/ohai-report/ohai_report/sinks/local.py +21 -0
- package/opencode-ohai-report/tools/ohai-report/ohai_report/sinks/webhook.py +354 -0
- package/opencode-ohai-report/tools/ohai-report/ohai_report/webhook_defaults.py +9 -0
- package/opencode-ohai-report/tools/ohai-report/ohai_report/workspace.py +61 -0
- package/opencode-ohai-report/tools/ohai-report/ohai_report.py +10 -0
- package/opencode-ohai-report/tools/ohai-report/schemas/report_issue.schema.json +166 -0
- package/package.json +59 -0
- package/scripts/codex-langfuse-check.mjs +101 -0
- package/scripts/codex-langfuse-setup.mjs +181 -0
- package/scripts/langfuse-check.mjs +90 -0
- package/scripts/langfuse-setup.mjs +278 -0
- package/scripts/opencode-langfuse-check.mjs +94 -0
- package/scripts/opencode-langfuse-run.mjs +96 -0
- package/scripts/opencode-langfuse-setup.mjs +478 -0
- package/scripts/resolve-opencode-cli.mjs +58 -0
- package/setup-langfuse.bat +163 -0
- package/setup-langfuse.sh +130 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Codex Langfuse implementation plan
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Add Codex support without depending on an unstable Codex plugin API. The first
|
|
6
|
+
version uses Codex's `notify` lifecycle command to trigger an exporter after a
|
|
7
|
+
turn, then the exporter reads Codex session JSONL files and sends the new events
|
|
8
|
+
to Langfuse.
|
|
9
|
+
|
|
10
|
+
## Architecture
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
Codex turn ends
|
|
14
|
+
-> ~/.codex/config.toml notify command
|
|
15
|
+
-> ~/.codex/hooks/codex_langfuse_notify.py
|
|
16
|
+
-> incremental read of ~/.codex/sessions/**/*.jsonl
|
|
17
|
+
-> Langfuse trace / generation / tool observations
|
|
18
|
+
-> ~/.codex/langfuse/state.json stores offsets
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Files
|
|
22
|
+
|
|
23
|
+
- `codex_langfuse_notify.py`: Python notify hook and JSONL exporter.
|
|
24
|
+
- `scripts/codex-langfuse-setup.mjs`: installs the hook, writes Langfuse
|
|
25
|
+
config, creates `~/.codex/langfuse-venv`, updates `~/.codex/config.toml`,
|
|
26
|
+
and installs the Python SDK inside the venv.
|
|
27
|
+
- `scripts/codex-langfuse-check.mjs`: validates hook/config/session/package
|
|
28
|
+
availability.
|
|
29
|
+
- `bin/cli.js`: adds `setup codex` and `check codex`.
|
|
30
|
+
- `package.json`: adds Codex npm scripts and publishes the hook file.
|
|
31
|
+
|
|
32
|
+
## Commands
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
npm run codex:setup -- --userId=h00613222
|
|
36
|
+
npm run codex:check
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
or:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
node bin/cli.js setup codex
|
|
43
|
+
node bin/cli.js check codex
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Restart Codex after setup so it reloads `~/.codex/config.toml`.
|
|
47
|
+
|
|
48
|
+
## Behavior
|
|
49
|
+
|
|
50
|
+
- The exporter reads Langfuse keys from environment variables first, then from
|
|
51
|
+
`~/.codex/langfuse/config.json`.
|
|
52
|
+
- It prefers a session path from the notify payload when Codex provides one.
|
|
53
|
+
If no path is present, it falls back to the latest session JSONL file.
|
|
54
|
+
- It fails open. Errors are logged to
|
|
55
|
+
`~/.codex/langfuse/codex_langfuse_notify.log` and never block Codex.
|
|
56
|
+
|
|
57
|
+
## Future improvements
|
|
58
|
+
|
|
59
|
+
- If Codex exposes a stable OTEL or plugin trace API, add it as an optional
|
|
60
|
+
second backend.
|
|
61
|
+
- Split shared Langfuse emit logic from the Claude and Codex Python hooks.
|
|
62
|
+
- Add a redaction layer before sending tool output to Langfuse.
|
package/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# oh-aicoding-tool
|
|
2
|
+
|
|
3
|
+
One npm package for AI coding tooling:
|
|
4
|
+
|
|
5
|
+
- Langfuse tracing setup for Claude Code, OpenCode, and Codex.
|
|
6
|
+
- `oh-ai-report` issue feedback plugin installer for OpenCode and Claude Code.
|
|
7
|
+
|
|
8
|
+
## Usage
|
|
9
|
+
|
|
10
|
+
Run locally from this project:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
node bin/cli.js
|
|
14
|
+
node bin/cli.js langfuse
|
|
15
|
+
node bin/cli.js report doctor
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
After publishing to npm:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
npx oh-aicoding-tool
|
|
22
|
+
npx oh-aicoding-tool langfuse setup
|
|
23
|
+
npx oh-aicoding-tool report install opencode --email user@company.com
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Running `npx oh-aicoding-tool` with no subcommand opens a guided menu. Choose
|
|
27
|
+
Langfuse tracing or the issue report plugin; the report path asks for the
|
|
28
|
+
install target and guides company email configuration before installing.
|
|
29
|
+
|
|
30
|
+
## Commands
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
oh-aicoding-tool # guided menu
|
|
34
|
+
oh-aicoding-tool langfuse setup
|
|
35
|
+
oh-aicoding-tool langfuse setup claude
|
|
36
|
+
oh-aicoding-tool langfuse setup opencode
|
|
37
|
+
oh-aicoding-tool langfuse setup codex
|
|
38
|
+
oh-aicoding-tool langfuse check
|
|
39
|
+
oh-aicoding-tool langfuse check environment
|
|
40
|
+
oh-aicoding-tool langfuse check claude
|
|
41
|
+
oh-aicoding-tool langfuse check opencode
|
|
42
|
+
oh-aicoding-tool langfuse check codex
|
|
43
|
+
oh-aicoding-tool report install opencode
|
|
44
|
+
oh-aicoding-tool report install claude
|
|
45
|
+
oh-aicoding-tool report install both
|
|
46
|
+
oh-aicoding-tool report doctor
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Compatibility binaries are still published:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
code-tool-langfuse
|
|
53
|
+
opencode-ohai-report
|
|
54
|
+
ohai-report
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Langfuse
|
|
58
|
+
|
|
59
|
+
The interactive UI supports arrow-key navigation in terminals that support raw input. Use `Up` / `Down` to move, number keys for quick selection, `Enter` to select, and `q` or `Esc` to exit. In multi-select setup screens, use `Space` to toggle targets and `Enter` to continue. It uses a soft color palette and falls back to plain numeric prompts when raw terminal input is unavailable.
|
|
60
|
+
|
|
61
|
+
`Setup Langfuse` supports selecting one or more setup targets:
|
|
62
|
+
|
|
63
|
+
- Claude Code Langfuse
|
|
64
|
+
- OpenCode Langfuse
|
|
65
|
+
- Codex Langfuse
|
|
66
|
+
|
|
67
|
+
During setup, the only required input is `User ID`, which should be your employee number, for example `h00613222`. Other setup values use the built-in defaults.
|
|
68
|
+
|
|
69
|
+
Before setup, the CLI checks required local tools and prints install hints when something is missing:
|
|
70
|
+
|
|
71
|
+
- Node.js / npm
|
|
72
|
+
- Python / pip for Claude Code setup
|
|
73
|
+
- Python / pip for Codex setup
|
|
74
|
+
- OpenCode CLI for OpenCode setup
|
|
75
|
+
|
|
76
|
+
Python package installation uses pip's default package index by default. If you need a custom PyPI mirror, set `CODE_TOOL_PIP_INDEX_URL` or pass `--pipIndexUrl=...`.
|
|
77
|
+
|
|
78
|
+
Use dry-run mode to preview the script calls without modifying files, installing packages, or writing environment variables:
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
oh-aicoding-tool langfuse setup --dry-run
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Issue Report Plugin
|
|
85
|
+
|
|
86
|
+
The report installer copies the packaged runtime to `~/.config/ohai-report/runtime`, installs OpenCode/Claude command files, and sets `OHAI_REPORT_CLI` to a stable path outside the temporary npx cache.
|
|
87
|
+
|
|
88
|
+
The guided menu prompts for company email and saves it into the corresponding
|
|
89
|
+
tool config so issue reports can carry a stable user identity.
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
oh-aicoding-tool report install opencode --email user@company.com
|
|
93
|
+
oh-aicoding-tool report install claude --email user@company.com
|
|
94
|
+
oh-aicoding-tool report install both --email user@company.com
|
|
95
|
+
oh-aicoding-tool report doctor
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## What It Configures
|
|
99
|
+
|
|
100
|
+
For Claude Code Langfuse setup, the CLI installs `langfuse_hook.py`, creates `~/.claude/langfuse-venv`, installs `langfuse` into that virtual environment, updates `~/.claude/settings.json`, and configures the hook environment. On Ubuntu/Debian, install `python3-venv` first if the setup reports that venv support is missing.
|
|
101
|
+
|
|
102
|
+
For OpenCode Langfuse setup, the CLI installs and patches `opencode-plugin-langfuse`, updates `~/.config/opencode/opencode.json`, and can write `LANGFUSE_*` values to the user environment.
|
|
103
|
+
|
|
104
|
+
For Codex Langfuse setup, the CLI installs `codex_langfuse_notify.py`, creates `~/.codex/langfuse-venv`, installs `langfuse` into that virtual environment, writes Langfuse credentials to `~/.codex/langfuse/config.json`, and updates the top-level `notify` command in `~/.codex/config.toml`. Restart Codex after setup so the notify command is loaded.
|
|
105
|
+
|
|
106
|
+
The Codex integration is implemented as a notify-hook exporter. On each notify event it incrementally reads the matching `~/.codex/sessions/**/*.jsonl` file, converts new user/assistant/tool/token events into Langfuse observations, and then saves its offset in `~/.codex/langfuse/state.json`. If Codex does not provide a session path in the notify payload, the exporter falls back to the latest session JSONL file.
|
|
107
|
+
|
|
108
|
+
## Notes
|
|
109
|
+
|
|
110
|
+
- Langfuse Base URL, Public Key, and Secret Key have built-in defaults for this internal setup. You can override them with `LANGFUSE_BASEURL`, `LANGFUSE_PUBLIC_KEY`, and `LANGFUSE_SECRET_KEY`.
|
|
111
|
+
- The default secret key is not printed in the terminal UI.
|
|
112
|
+
- The CLI calls the Node setup/check scripts directly, so it avoids PowerShell `npm.ps1` execution-policy failures.
|
|
113
|
+
- The Python hook reads `userId` from hook payload first, then from configured environment variables.
|
|
114
|
+
- The Codex hook fails open: tracing errors are logged to `~/.codex/langfuse/codex_langfuse_notify.log` and do not block Codex.
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { createInterface } from "node:readline/promises";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
+
|
|
9
|
+
function runNodeScript(script, args) {
|
|
10
|
+
const result = spawnSync(process.execPath, [script, ...args], {
|
|
11
|
+
stdio: "inherit",
|
|
12
|
+
windowsHide: true,
|
|
13
|
+
});
|
|
14
|
+
return result.status ?? (result.error ? 1 : 0);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function printHelp() {
|
|
18
|
+
console.log("oh-aicoding-tool");
|
|
19
|
+
console.log("");
|
|
20
|
+
console.log("Usage:");
|
|
21
|
+
console.log(" oh-aicoding-tool Open the installer menu");
|
|
22
|
+
console.log(" oh-aicoding-tool langfuse [setup|check] [target]");
|
|
23
|
+
console.log(" oh-aicoding-tool report install opencode [--email user@company.com]");
|
|
24
|
+
console.log(" oh-aicoding-tool report install claude");
|
|
25
|
+
console.log(" oh-aicoding-tool report install both");
|
|
26
|
+
console.log(" oh-aicoding-tool report doctor");
|
|
27
|
+
console.log("");
|
|
28
|
+
console.log("Compatibility bins:");
|
|
29
|
+
console.log(" code-tool-langfuse");
|
|
30
|
+
console.log(" opencode-ohai-report");
|
|
31
|
+
console.log(" ohai-report");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function emailIsValid(value) {
|
|
35
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(value || "").trim());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function askChoice(rl, title, choices) {
|
|
39
|
+
console.log("");
|
|
40
|
+
console.log(title);
|
|
41
|
+
choices.forEach((choice, index) => {
|
|
42
|
+
console.log(` ${index + 1}. ${choice.label}`);
|
|
43
|
+
if (choice.description) console.log(` ${choice.description}`);
|
|
44
|
+
});
|
|
45
|
+
while (true) {
|
|
46
|
+
const answer = (await rl.question("> ")).trim().toLowerCase();
|
|
47
|
+
const index = Number.parseInt(answer, 10) - 1;
|
|
48
|
+
if (Number.isInteger(index) && choices[index]) return choices[index].value;
|
|
49
|
+
const byValue = choices.find((choice) => choice.value === answer);
|
|
50
|
+
if (byValue) return byValue.value;
|
|
51
|
+
console.log("Please choose one of the listed options.");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const langfuseCli = path.join(rootDir, "bin", "langfuse-cli.js");
|
|
56
|
+
const reportCli = path.join(rootDir, "opencode-ohai-report", "bin", "cli.js");
|
|
57
|
+
|
|
58
|
+
async function askEmail(rl) {
|
|
59
|
+
const envEmail = process.env.OHAI_INSTALL_USER_EMAIL || "";
|
|
60
|
+
const hint = emailIsValid(envEmail) ? ` [${envEmail}]` : "";
|
|
61
|
+
console.log("");
|
|
62
|
+
console.log("Company email is used as the user identity in issue reports.");
|
|
63
|
+
console.log("Press Enter to skip email setup for now.");
|
|
64
|
+
while (true) {
|
|
65
|
+
const answer = (await rl.question(`Company email${hint}: `)).trim();
|
|
66
|
+
const value = answer || (emailIsValid(envEmail) ? envEmail.trim() : "");
|
|
67
|
+
if (!value) return "";
|
|
68
|
+
if (emailIsValid(value)) return value;
|
|
69
|
+
console.log("Invalid email. Example: name@company.com");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function runReportInstallMenu(rl) {
|
|
74
|
+
const target = await askChoice(rl, "Install issue report plugin for:", [
|
|
75
|
+
{ label: "OpenCode", value: "opencode", description: "Install /report-ai-issue and the OpenCode plugin." },
|
|
76
|
+
{ label: "Claude Code", value: "claude", description: "Install the command and optional hook integration." },
|
|
77
|
+
{ label: "Both", value: "both", description: "Install OpenCode and Claude Code integrations together." },
|
|
78
|
+
{ label: "Back", value: "back" },
|
|
79
|
+
]);
|
|
80
|
+
if (target === "back") return 0;
|
|
81
|
+
const email = await askEmail(rl);
|
|
82
|
+
const args = ["install", target];
|
|
83
|
+
if (email) args.push("--email", email);
|
|
84
|
+
else args.push("--skip-email");
|
|
85
|
+
rl.close();
|
|
86
|
+
return runNodeScript(reportCli, args);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function runMainMenu() {
|
|
90
|
+
if (!process.stdin.isTTY) {
|
|
91
|
+
printHelp();
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
95
|
+
try {
|
|
96
|
+
console.log("");
|
|
97
|
+
console.log("oh-aicoding-tool");
|
|
98
|
+
const action = await askChoice(rl, "What would you like to install or configure?", [
|
|
99
|
+
{
|
|
100
|
+
label: "Langfuse tracing",
|
|
101
|
+
value: "langfuse",
|
|
102
|
+
description: "Configure tracing for Claude Code, OpenCode, or Codex.",
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
label: "Issue report plugin",
|
|
106
|
+
value: "report",
|
|
107
|
+
description: "Install oh-ai-report and configure company email.",
|
|
108
|
+
},
|
|
109
|
+
{ label: "Check Langfuse configuration", value: "check-langfuse" },
|
|
110
|
+
{ label: "Check report plugin", value: "check-report" },
|
|
111
|
+
{ label: "Exit", value: "exit" },
|
|
112
|
+
]);
|
|
113
|
+
if (action === "langfuse") {
|
|
114
|
+
rl.close();
|
|
115
|
+
return runNodeScript(langfuseCli, ["setup"]);
|
|
116
|
+
}
|
|
117
|
+
if (action === "report") return await runReportInstallMenu(rl);
|
|
118
|
+
if (action === "check-langfuse") {
|
|
119
|
+
rl.close();
|
|
120
|
+
return runNodeScript(langfuseCli, ["check"]);
|
|
121
|
+
}
|
|
122
|
+
if (action === "check-report") {
|
|
123
|
+
rl.close();
|
|
124
|
+
return runNodeScript(reportCli, ["doctor"]);
|
|
125
|
+
}
|
|
126
|
+
return 0;
|
|
127
|
+
} finally {
|
|
128
|
+
rl.close();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function main() {
|
|
133
|
+
const argv = process.argv.slice(2);
|
|
134
|
+
const [cmd, ...rest] = argv;
|
|
135
|
+
|
|
136
|
+
if (cmd === "--help" || cmd === "-h" || cmd === "help") {
|
|
137
|
+
printHelp();
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (!cmd) return await runMainMenu();
|
|
142
|
+
|
|
143
|
+
if (cmd === "langfuse" || cmd === "trace" || cmd === "tracing") {
|
|
144
|
+
return runNodeScript(langfuseCli, rest);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (cmd === "report" || cmd === "ohai" || cmd === "issue") {
|
|
148
|
+
return runNodeScript(reportCli, rest);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (cmd === "setup" || cmd === "check") {
|
|
152
|
+
return runNodeScript(langfuseCli, argv);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.error(`Unknown command: ${cmd}`);
|
|
156
|
+
console.error("");
|
|
157
|
+
printHelp();
|
|
158
|
+
return 1;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
main()
|
|
162
|
+
.then((code) => process.exit(code))
|
|
163
|
+
.catch((err) => {
|
|
164
|
+
console.error(err?.message || String(err));
|
|
165
|
+
process.exit(1);
|
|
166
|
+
});
|