@hera-al/atn-proxy 1.0.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/LICENSE +21 -0
- package/README.md +90 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +2 -0
- package/dist/logger.d.ts +12 -0
- package/dist/logger.js +1 -0
- package/dist/proxy.d.ts +25 -0
- package/dist/proxy.js +1 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TGP / Hera Artificial Life
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# @hera-al/atn-proxy
|
|
2
|
+
|
|
3
|
+
> **Part of [Hera Artificial Life](https://github.com/hera-artificial-life/hera-al)** — an opinionated AI assistant platform that runs locally on your machine. This package provides the tool name translation proxy used by Hera agents to communicate with OpenAI-compatible endpoints. It can also be used independently in any Node.js project.
|
|
4
|
+
|
|
5
|
+
**Anthropic Tool Name Proxy** — sits between the Claude Agent SDK and OpenAI-compatible endpoints (e.g. OpenRouter), resolving the 64-character tool name limit.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Agent SDK --> ATN-Proxy (:4181) --> OpenRouter --> Model
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## What it does
|
|
12
|
+
|
|
13
|
+
| Direction | Action |
|
|
14
|
+
|-----------|--------|
|
|
15
|
+
| **Request** | Truncates `tools[].name` to 64 chars using a deterministic hash suffix |
|
|
16
|
+
| **Request** | Rewrites `?beta=true` query param into the `anthropic-beta` header |
|
|
17
|
+
| **Request** | Truncates `tool_use.name` in conversation history to match |
|
|
18
|
+
| **Response** | Remaps truncated tool names back to originals (stream + non-stream) |
|
|
19
|
+
| **Response** | Normalizes `usage{}` to Anthropic format (missing fields default to 0) |
|
|
20
|
+
| **Response** | Wraps non-JSON responses (HTML 404) into Anthropic-compatible errors |
|
|
21
|
+
| **Response** | Handles `/count_tokens` locally with a token estimate |
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install -g @hera-al/atn-proxy
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or run from source:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
git clone https://github.com/hera-artificial-life/hera-al.git
|
|
33
|
+
cd hera-al/atn-proxy
|
|
34
|
+
npm install
|
|
35
|
+
npm run build
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Start with defaults (port 4181, target openrouter.ai)
|
|
42
|
+
atn-proxy
|
|
43
|
+
|
|
44
|
+
# Custom port and verbose logging
|
|
45
|
+
atn-proxy --port 8080 --verbose
|
|
46
|
+
|
|
47
|
+
# Show all options
|
|
48
|
+
atn-proxy --help
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Options
|
|
52
|
+
|
|
53
|
+
| Flag | Default | Description |
|
|
54
|
+
|------|---------|-------------|
|
|
55
|
+
| `--port <n>` | `4181` (env: `PORT`) | Listen port |
|
|
56
|
+
| `--target <url>` | `https://openrouter.ai` | Upstream endpoint |
|
|
57
|
+
| `--prefix <path>` | `/api` | Target path prefix |
|
|
58
|
+
| `--max-name <n>` | `64` | Max tool name length |
|
|
59
|
+
| `--logs <dir>` | `./logs` | Log directory |
|
|
60
|
+
| `--verbose` | off (env: `VERBOSE=1`) | Debug logging |
|
|
61
|
+
|
|
62
|
+
### Connect the Agent SDK
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
ANTHROPIC_BASE_URL=http://localhost:4181 \
|
|
66
|
+
ANTHROPIC_AUTH_TOKEN=sk-or-v1-xxx \
|
|
67
|
+
ANTHROPIC_API_KEY="" \
|
|
68
|
+
node your-agent.mjs
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Logging
|
|
72
|
+
|
|
73
|
+
Logs are written to `./logs/atn-proxy.log` with automatic rotation:
|
|
74
|
+
- Max file size: 10 MB
|
|
75
|
+
- Rotated files: up to 9 (`atn-proxy.1.log` ... `atn-proxy.9.log`)
|
|
76
|
+
|
|
77
|
+
## How tool name truncation works
|
|
78
|
+
|
|
79
|
+
When a tool name exceeds the max length, ATN-Proxy generates a deterministic short name by keeping a readable prefix and appending an 8-char SHA-256 hash:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
mcp__very-long-server-name__very_long_tool_name
|
|
83
|
+
--> mcp__very-long-server-name__very_long_t_a1b2c3d4
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The mapping is maintained in memory for the proxy session. Response tool names are automatically restored to the originals before reaching the SDK.
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Anthropic Tool Name Proxy (ATN-Proxy)
|
|
4
|
+
*
|
|
5
|
+
* Sits between the Claude Agent SDK and OpenRouter, resolving the
|
|
6
|
+
* 64-char tool name limit of the OpenAI API.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* atn-proxy [--port 4181] [--target https://openrouter.ai] [--verbose]
|
|
10
|
+
*
|
|
11
|
+
* Environment:
|
|
12
|
+
* PORT — listen port (default 4181)
|
|
13
|
+
* VERBOSE — set to "1" for debug logging
|
|
14
|
+
*/
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{resolve as o}from"node:path";import{Logger as e}from"./logger.js";import{createProxyServer as l}from"./proxy.js";const s="[0m",r="[1m",t="[2m",n=o=>`[38;5;${o}m`,g=51,a=39,c=44,$=33,p=141,i=98;!function(){const m=function(){const o=process.argv.slice(2),e={port:parseInt(process.env.PORT||"4181"),target:"https://openrouter.ai",prefix:"/api",maxName:64,logsDir:"./logs",verbose:"1"===process.env.VERBOSE,help:!1};for(let l=0;l<o.length;l++){const s=o[l];"--help"===s||"-h"===s?e.help=!0:"--verbose"===s||"-v"===s?e.verbose=!0:"--port"===s&&o[l+1]?e.port=parseInt(o[++l]):"--target"===s&&o[l+1]?e.target=o[++l]:"--prefix"===s&&o[l+1]?e.prefix=o[++l]:"--max-name"===s&&o[l+1]?e.maxName=parseInt(o[++l]):"--logs"===s&&o[l+1]&&(e.logsDir=o[++l])}return e}();m.help&&(console.log(""),console.log(` ${n(p)}${r}Anthropic Tool Name Proxy${s}`),console.log(""),console.log(` ${r}Usage:${s} atn-proxy [options]`),console.log(""),console.log(` ${r}Options:${s}`),console.log(" --port <port> Listen port (default: 4181, env: PORT)"),console.log(" --target <url> Upstream URL (default: https://openrouter.ai)"),console.log(" --prefix <path> Target path prefix (default: /api)"),console.log(" --max-name <n> Max tool name length (default: 64)"),console.log(" --logs <dir> Logs directory (default: ./logs)"),console.log(" --verbose Enable debug logging (env: VERBOSE=1)"),console.log(" --help Show this help message"),console.log(""),console.log(` ${r}Example:${s}`),console.log(" atn-proxy --port 4181 --verbose"),console.log(""),console.log(` ${r}Usage with Agent SDK:${s}`),console.log(" ANTHROPIC_BASE_URL=http://localhost:4181 \\"),console.log(" ANTHROPIC_AUTH_TOKEN=sk-or-v1-xxx \\"),console.log(' ANTHROPIC_API_KEY="" \\'),console.log(" node your-agent.mjs"),console.log(""),process.exit(0));const x=new e(o(m.logsDir)),h={port:m.port,target:m.target,targetPathPrefix:m.prefix,maxToolName:m.maxName,verbose:m.verbose};var f,v,T,S;f=h.port,v=h.target,T=h.maxToolName,S=h.verbose,console.log(""),console.log(` ${n(g)}${r} █████╗ ████████╗███╗ ██╗${s}`),console.log(` ${n(g)}${r} ██╔══██╗╚══██╔══╝████╗ ██║${s}`),console.log(` ${n(a)}${r} ███████║ ██║ ██╔██╗ ██║${s}`),console.log(` ${n(c)}${r} ██╔══██║ ██║ ██║╚██╗██║${s}`),console.log(` ${n($)}${r} ██║ ██║ ██║ ██║ ╚████║${s}`),console.log(` ${n(i)}${r} ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═══╝${s}`),console.log(""),console.log(` ${n(p)}${r}Anthropic Tool Name Proxy${s}`),console.log(` ${t}${"─".repeat(42)}${s}`),console.log(` ${t}Listen${s} ${r}http://localhost:${f}${s}`),console.log(` ${t}Target${s} ${t}${v}${s}`),console.log(` ${t}Max name${s} ${t}${T} chars${s}`),console.log(` ${t}Verbose${s} ${t}${S?"on":"off"}${s}`),console.log(` ${t}Logs${s} ${t}./logs/atn-proxy.log${s}`),console.log(""),x.info("Server",`Starting ATN-Proxy on port ${h.port}`),x.info("Server",`Target: ${h.target}${h.targetPathPrefix}`),x.info("Server",`Max tool name: ${h.maxToolName} chars`),x.info("Server",`Logs dir: ${o(m.logsDir)}`);const u=l(h,x);u.listen(h.port,()=>{x.info("Server",`Listening on http://localhost:${h.port}`)});const d=()=>{console.log(`\n ${t}Shutting down...${s}`),x.info("Server","Shutting down"),u.close(()=>{x.info("Server","Server closed"),process.exit(0)}),setTimeout(()=>process.exit(0),5e3).unref()};process.on("SIGINT",d),process.on("SIGTERM",d)}();
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class Logger {
|
|
2
|
+
private dir;
|
|
3
|
+
private filePath;
|
|
4
|
+
constructor(logsDir: string);
|
|
5
|
+
info(tag: string, msg: string): void;
|
|
6
|
+
warn(tag: string, msg: string): void;
|
|
7
|
+
error(tag: string, msg: string): void;
|
|
8
|
+
debug(tag: string, msg: string): void;
|
|
9
|
+
private write;
|
|
10
|
+
private rotate;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=logger.d.ts.map
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{existsSync as t,mkdirSync as i,appendFileSync as r,renameSync as e,statSync as o}from"node:fs";import{resolve as h,join as s}from"node:path";export class Logger{dir;filePath;constructor(r){this.dir=h(r),t(this.dir)||i(this.dir,{recursive:!0}),this.filePath=s(this.dir,"atn-proxy.log")}info(t,i){this.write("INFO",t,i)}warn(t,i){this.write("WARN",t,i)}error(t,i){this.write("ERROR",t,i)}debug(t,i){this.write("DEBUG",t,i)}write(t,i,e){const o=`${(new Date).toISOString()} [${t}] [${i}] ${e}\n`;try{this.rotate(),r(this.filePath,o,"utf-8")}catch{}}rotate(){try{if(!t(this.filePath))return;if(o(this.filePath).size<10485760)return;for(let i=9;i>=1;i--){const r=s(this.dir,`atn-proxy.${i}.log`);if(!t(r))continue;const o=s(this.dir,`atn-proxy.${i+1}.log`);e(r,o)}e(this.filePath,s(this.dir,"atn-proxy.1.log"))}catch{}}}
|
package/dist/proxy.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic -> OpenRouter Proxy core.
|
|
3
|
+
*
|
|
4
|
+
* Resolves the 64-char tool name limit of the OpenAI API.
|
|
5
|
+
* Sits between the Claude Agent SDK and OpenRouter:
|
|
6
|
+
*
|
|
7
|
+
* Agent SDK -> this proxy (:4181) -> OpenRouter -> model
|
|
8
|
+
*
|
|
9
|
+
* REQUEST: truncates tools[].name to 64 chars (hash-based) if needed
|
|
10
|
+
* REQUEST: rewrites ?beta=true -> anthropic-beta header (OpenRouter ignores query param)
|
|
11
|
+
* RESPONSE: remaps tool_use.name to original (both stream and non-stream)
|
|
12
|
+
* RESPONSE: normalizes usage{} to Anthropic format (missing fields -> 0)
|
|
13
|
+
* RESPONSE: wraps non-JSON (HTML 404) into an Anthropic-compatible error
|
|
14
|
+
*/
|
|
15
|
+
import http from "node:http";
|
|
16
|
+
import type { Logger } from "./logger.js";
|
|
17
|
+
export interface ProxyConfig {
|
|
18
|
+
port: number;
|
|
19
|
+
target: string;
|
|
20
|
+
targetPathPrefix: string;
|
|
21
|
+
maxToolName: number;
|
|
22
|
+
verbose: boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare function createProxyServer(config: ProxyConfig, log: Logger): http.Server;
|
|
25
|
+
//# sourceMappingURL=proxy.d.ts.map
|
package/dist/proxy.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import t from"node:http";import e from"node:https";import n from"node:crypto";const o=new Map,r=new Map;function s(t,e){if(t.length<=e)return t;const s=n.createHash("sha256").update(t).digest("hex").slice(0,8),a=`${t.slice(0,e-9)}_${s}`;return o.set(a,t),r.set(t,a),a}function a(t){return o.get(t)||t}function c(t){if(!t)return t;t.usage||(t.usage={});const e=t.usage;return e.input_tokens=e.input_tokens??0,e.output_tokens=e.output_tokens??0,e.cache_creation_input_tokens=e.cache_creation_input_tokens??0,e.cache_read_input_tokens=e.cache_read_input_tokens??0,t}function i(t){if(!t.startsWith("data: "))return t;const e=t.slice(6);if("[DONE]"===e)return t;try{const n=JSON.parse(e);let o=!1;if("content_block_start"===n.type&&"tool_use"===n.content_block?.type&&n.content_block?.name&&(n.content_block.name=a(n.content_block.name),o=!0),"message_start"===n.type&&n.message){if(c(n.message),Array.isArray(n.message.content))for(const t of n.message.content)"tool_use"===t.type&&t.name&&(t.name=a(t.name),o=!0);o=!0}return"message_delta"===n.type&&n.usage&&(n.usage.input_tokens=n.usage.input_tokens??0,n.usage.output_tokens=n.usage.output_tokens??0,n.usage.cache_creation_input_tokens=n.usage.cache_creation_input_tokens??0,n.usage.cache_read_input_tokens=n.usage.cache_read_input_tokens??0,o=!0),o?`data: ${JSON.stringify(n)}`:t}catch{return t}}export function createProxyServer(n,o){return t.createServer(async(t,u)=>{const f=Date.now();if(o.info("Proxy",`-> ${t.method} ${t.url}`),t.url?.includes("/count_tokens")){const e=[];for await(const n of t)e.push(n);const n=Buffer.concat(e).toString("utf8");let r=1e3;try{const t=JSON.parse(n),e=JSON.stringify(t.messages||[]).length,o=JSON.stringify(t.tools||[]).length;r=Math.ceil((e+o)/3.5)}catch{}o.info("Proxy",`count_tokens -> local estimate: ~${r} tokens`);const s=JSON.stringify({input_tokens:r});return u.writeHead(200,{"content-type":"application/json","content-length":Buffer.byteLength(s).toString()}),void u.end(s)}const p=[];for await(const e of t)p.push(e);const g=Buffer.concat(p);let m,h=g;if("POST"===t.method&&g.length>0)try{m=JSON.parse(g.toString("utf8")),n.verbose&&o.debug("Proxy",`Request: model=${m.model} stream=${m.stream} tools=${m.tools?.length??0} msgs=${m.messages?.length??0}`);const{body:t,hadChanges:e}=function(t,e,n){let o=!1;if(Array.isArray(t.tools))for(const r of t.tools)if(r.name&&r.name.length>e){const t=r.name;r.name=s(t,e),o=!0,n.debug("Proxy",`Tool name truncated: ${t} -> ${r.name}`)}if(Array.isArray(t.messages))for(const e of t.messages)if(Array.isArray(e.content))for(const t of e.content)if("tool_use"===t.type&&t.name){const e=r.get(t.name);e&&(t.name=e,o=!0)}return{body:t,hadChanges:o}}(m,n.maxToolName,o);e&&(h=Buffer.from(JSON.stringify(t),"utf8"),o.info("Proxy","Tool names truncated in request"))}catch{}const d=!0===m?.stream,y=new URL(t.url,n.target),l={};for(const[e,n]of Object.entries(t.headers))"host"!==e&&"accept-encoding"!==e&&n&&(l[e]=Array.isArray(n)?n.join(", "):n);l.host=y.host,l["content-length"]=h.length.toString();let _=n.targetPathPrefix+y.pathname+y.search;if(y.searchParams.has("beta")){y.searchParams.delete("beta");const t=y.searchParams.toString();_=n.targetPathPrefix+y.pathname+(t?`?${t}`:"");const e=l["anthropic-beta"]||"";e.includes("prompt-caching")||(l["anthropic-beta"]=e?`${e},prompt-caching-2024-07-31`:"prompt-caching-2024-07-31"),o.debug("Proxy","Rewritten ?beta=true -> anthropic-beta header")}o.debug("Proxy",`Target: ${n.target}${_}`);const k={hostname:y.hostname,port:y.port||443,path:_,method:t.method,headers:l},S=e.request(k,t=>{const e=Date.now()-f;o.info("Proxy",`<- ${t.statusCode} (${e}ms) stream=${d}`);const r={};for(const[e,n]of Object.entries(t.headers))"transfer-encoding"!==e&&n&&(r[e]=n);if(d){r["transfer-encoding"]="chunked",u.writeHead(t.statusCode,r);let e="";t.on("data",t=>{e+=t.toString("utf8");const n=e.split("\n");e=n.pop()||"";for(const t of n)u.write(i(t)+"\n")}),t.on("end",()=>{e&&u.write(i(e)+"\n"),u.end(),o.info("Proxy",`Stream completed (${Date.now()-f}ms)`)})}else{const e=[];t.on("data",t=>e.push(t)),t.on("end",()=>{let s=Buffer.concat(e);try{const t=JSON.parse(s.toString("utf8"));n.verbose&&o.debug("Proxy",`Response: type=${t.type} error=${JSON.stringify(t.error)}`);const e=function(t){if(!t)return t;if(c(t),Array.isArray(t.content))for(const e of t.content)"tool_use"===e.type&&e.name&&(e.name=a(e.name));return t}(t);s=Buffer.from(JSON.stringify(e),"utf8")}catch{o.warn("Proxy",`Non-JSON response (${s.length} bytes)`);const e={type:"error",error:{type:"api_error",message:`OpenRouter returned non-JSON response (${t.statusCode}). Endpoint may not be supported.`},usage:{input_tokens:0,output_tokens:0,cache_creation_input_tokens:0,cache_read_input_tokens:0}};s=Buffer.from(JSON.stringify(e),"utf8"),r["content-type"]="application/json",o.info("Proxy","Generated Anthropic-compatible error response")}r["content-length"]=s.length.toString(),u.writeHead(t.statusCode,r),u.end(s),o.info("Proxy",`Response sent (${Date.now()-f}ms)`)})}});S.on("error",t=>{o.error("Proxy",`Upstream error: ${t.message}`),u.writeHead(502,{"content-type":"application/json"}),u.end(JSON.stringify({error:{message:`Proxy error: ${t.message}`}}))}),S.write(h),S.end()})}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hera-al/atn-proxy",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Anthropic Tool Name Proxy — translates tool names > 64 chars for OpenAI-compatible endpoints",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "TGP <heralife.dev@gmail.com>",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/hera-artificial-life/hera-al.git",
|
|
10
|
+
"directory": "atn-proxy"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/hera-artificial-life/hera-al",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"anthropic",
|
|
15
|
+
"openrouter",
|
|
16
|
+
"proxy",
|
|
17
|
+
"tool-name",
|
|
18
|
+
"openai-compatible",
|
|
19
|
+
"ai-agent",
|
|
20
|
+
"hera"
|
|
21
|
+
],
|
|
22
|
+
"type": "module",
|
|
23
|
+
"main": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"bin": {
|
|
26
|
+
"atn-proxy": "./dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"!dist/**/*.map",
|
|
31
|
+
"!src",
|
|
32
|
+
"!tsconfig.json"
|
|
33
|
+
],
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"start": "tsx src/index.ts",
|
|
39
|
+
"dev": "tsx watch src/index.ts",
|
|
40
|
+
"build": "tsc",
|
|
41
|
+
"minify": "find dist -name '*.map' -delete && find dist -name '*.js' -exec terser {} --module --compress --mangle -o {} \\;",
|
|
42
|
+
"build:prod": "rm -rf dist && npm run build && npm run minify",
|
|
43
|
+
"node": "node dist/index.js",
|
|
44
|
+
"help": "tsx src/index.ts --help",
|
|
45
|
+
"prepublishOnly": "npm run build:prod"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^22.13.4",
|
|
49
|
+
"terser": "^5.46.0",
|
|
50
|
+
"tsx": "^4.19.3",
|
|
51
|
+
"typescript": "^5.7.3"
|
|
52
|
+
}
|
|
53
|
+
}
|