@aiaiaichain/agent 0.1.3 → 0.1.4

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/tui/REPL.js CHANGED
@@ -1,12 +1,33 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /**
3
- * REPL — scrolling message history + bottom input box.
3
+ * REPL — scrolling message history + feature-rich input box.
4
+ * Supports: multi-line (Shift+Enter), command history (↑/↓), tab completion.
4
5
  */
5
- import { useState, useCallback } from "react";
6
+ import { useState, useCallback, useRef } from "react";
6
7
  import { Box, Text, useStdout, useInput } from "ink";
7
- import TextInput from "ink-text-input";
8
8
  import { AIAIAI_COLORS } from "./theme.js";
9
9
  const MAX_VISIBLE = 40;
10
+ const MAX_HISTORY = 100;
11
+ // ── Command completions ─────────────────────────────────────────────────────
12
+ const COMMANDS = [
13
+ "token info", "token security", "token holders", "token traders",
14
+ "track kol", "track smartmoney", "track follow-wallet",
15
+ "market kline", "market trending", "market trenches", "market signal",
16
+ "watch", "unwatch", "watchlist", "alerts", "alert above", "alert below",
17
+ "compare",
18
+ "price", "news", "models", "cost", "model", "clear",
19
+ "wallet", "deposit", "burn", "actions", "activity", "fees",
20
+ "keys", "providers", "goals", "goal add", "goal done", "goal list",
21
+ "schedule", "sessions", "resume", "memory", "update", "gmgn", "help",
22
+ "exit", "quit",
23
+ ];
24
+ function getCompletions(prefix) {
25
+ if (!prefix || prefix === "/")
26
+ return COMMANDS.map(c => `/${c}`);
27
+ const p = prefix.startsWith("/") ? prefix.slice(1) : prefix;
28
+ return COMMANDS.filter(c => c.startsWith(p)).map(c => `/${c}`);
29
+ }
30
+ // ── Message components ──────────────────────────────────────────────────────
10
31
  function MessageLine({ msg }) {
11
32
  const time = new Date(msg.ts).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
12
33
  if (msg.role === "user") {
@@ -24,25 +45,178 @@ function MessageLine({ msg }) {
24
45
  if (msg.role === "notify") {
25
46
  return (_jsx(Box, { borderStyle: "round", borderColor: AIAIAI_COLORS.accent, marginY: 1, paddingX: 1, children: _jsx(Text, { wrap: "wrap", children: msg.content }) }));
26
47
  }
27
- // system messages — dimmed
48
+ // system — dimmed
28
49
  return (_jsx(Box, { flexDirection: "row", gap: 1, children: _jsx(Text, { color: AIAIAI_COLORS.dim, wrap: "wrap", children: msg.content }) }));
29
50
  }
51
+ function InputBox({ value, onChange, onSubmit, disabled, onAbort }) {
52
+ const [cursor, setCursor] = useState(value.length);
53
+ const [history, setHistory] = useState([]);
54
+ const [historyIdx, setHistoryIdx] = useState(-1);
55
+ const [completions, setCompletions] = useState([]);
56
+ const [completionIdx, setCompletionIdx] = useState(-1);
57
+ const [showCompletions, setShowCompletions] = useState(false);
58
+ const valueRef = useRef(value);
59
+ const cursorRef = useRef(cursor);
60
+ valueRef.current = value;
61
+ cursorRef.current = cursor;
62
+ // Messages scroll handled by Ink container
63
+ const insertAtCursor = useCallback((text) => {
64
+ const before = valueRef.current.slice(0, cursorRef.current);
65
+ const after = valueRef.current.slice(cursorRef.current);
66
+ const newVal = before + text + after;
67
+ onChange(newVal);
68
+ setCursor(cursorRef.current + text.length);
69
+ }, [onChange]);
70
+ const doSubmit = useCallback(() => {
71
+ const val = valueRef.current;
72
+ if (!val.trim() || disabled)
73
+ return;
74
+ setHistory(prev => [val, ...prev].slice(0, MAX_HISTORY));
75
+ setHistoryIdx(-1);
76
+ setShowCompletions(false);
77
+ onChange("");
78
+ setCursor(0);
79
+ onSubmit(val);
80
+ }, [disabled, onChange, onSubmit]);
81
+ useInput((input, key) => {
82
+ // Always handle Escape for abort
83
+ if (key.escape) {
84
+ if (disabled) {
85
+ onAbort();
86
+ return;
87
+ }
88
+ setShowCompletions(false);
89
+ return;
90
+ }
91
+ if (disabled)
92
+ return;
93
+ // Tab completion
94
+ if (key.tab) {
95
+ const val = valueRef.current;
96
+ if (!showCompletions) {
97
+ // Check only when typing a command
98
+ if (val.startsWith("/") && val.length > 0) {
99
+ const comps = getCompletions(val);
100
+ if (comps.length === 1 && comps[0] !== val) {
101
+ // Auto-complete if only one match
102
+ onChange(comps[0] + " ");
103
+ setCursor(comps[0].length + 1);
104
+ return;
105
+ }
106
+ if (comps.length > 0) {
107
+ setCompletions(comps);
108
+ setCompletionIdx(0);
109
+ setShowCompletions(true);
110
+ return;
111
+ }
112
+ }
113
+ return;
114
+ }
115
+ // Cycle through completions
116
+ if (completions.length > 0) {
117
+ const nextIdx = (completionIdx + 1) % completions.length;
118
+ setCompletionIdx(nextIdx);
119
+ // Show the completion
120
+ const comp = completions[nextIdx];
121
+ onChange(comp + " ");
122
+ setCursor(comp.length + 1);
123
+ }
124
+ return;
125
+ }
126
+ // Close completions on any other key
127
+ if (showCompletions)
128
+ setShowCompletions(false);
129
+ // Enter submits
130
+ if (key.return) {
131
+ // Shift+Enter = new line, Enter = submit
132
+ if (input === "\r" && !key.shift) {
133
+ doSubmit();
134
+ return;
135
+ }
136
+ // Shift+Enter inserts newline
137
+ if (key.shift || input === "\u0003") {
138
+ insertAtCursor("\n");
139
+ return;
140
+ }
141
+ }
142
+ // History navigation (↑/↓)
143
+ if (key.upArrow) {
144
+ if (history.length === 0)
145
+ return;
146
+ const newIdx = historyIdx < history.length - 1 ? historyIdx + 1 : historyIdx;
147
+ setHistoryIdx(newIdx);
148
+ onChange(history[newIdx]);
149
+ setCursor(history[newIdx].length);
150
+ return;
151
+ }
152
+ if (key.downArrow) {
153
+ if (historyIdx <= 0) {
154
+ setHistoryIdx(-1);
155
+ onChange("");
156
+ setCursor(0);
157
+ return;
158
+ }
159
+ const newIdx = historyIdx - 1;
160
+ setHistoryIdx(newIdx);
161
+ onChange(history[newIdx]);
162
+ setCursor(history[newIdx].length);
163
+ return;
164
+ }
165
+ // Left/right arrow — update cursor tracking
166
+ if (key.leftArrow) {
167
+ setCursor(Math.max(0, cursorRef.current - 1));
168
+ return;
169
+ }
170
+ if (key.rightArrow) {
171
+ setCursor(Math.min(valueRef.current.length, cursorRef.current + 1));
172
+ return;
173
+ }
174
+ // Backspace
175
+ if (key.backspace || key.delete) {
176
+ if (cursorRef.current <= 0)
177
+ return;
178
+ const before = valueRef.current.slice(0, cursorRef.current - 1);
179
+ const after = valueRef.current.slice(cursorRef.current);
180
+ onChange(before + after);
181
+ setCursor(cursorRef.current - 1);
182
+ return;
183
+ }
184
+ // Printable character
185
+ if (input && input.length === 1 && input.charCodeAt(0) >= 32) {
186
+ insertAtCursor(input);
187
+ return;
188
+ }
189
+ });
190
+ const placeholder = disabled ? "thinking…" : "message or /command";
191
+ const displayValue = value || placeholder;
192
+ const inputLines = displayValue.split("\n");
193
+ const inputHeight = Math.min(inputLines.length, 5); // max 5 visible lines
194
+ return (_jsxs(Box, { flexDirection: "column", children: [showCompletions && completions.length > 0 && (_jsxs(Box, { borderStyle: "single", borderColor: AIAIAI_COLORS.accent, paddingX: 1, flexDirection: "column", marginBottom: 0, children: [completions.slice(0, 8).map((c, i) => (_jsxs(Text, { color: i === completionIdx ? AIAIAI_COLORS.accent : AIAIAI_COLORS.muted, bold: i === completionIdx, children: [i === completionIdx ? "▸ " : " ", c] }, c))), completions.length > 8 && (_jsxs(Text, { color: AIAIAI_COLORS.dim, children: [" \u2026", completions.length - 8, " more"] }))] })), _jsx(Box, { borderStyle: "round", borderColor: disabled ? AIAIAI_COLORS.dim : AIAIAI_COLORS.accent, paddingX: 1, paddingY: inputHeight > 1 ? 0 : 0, flexDirection: "column", marginTop: 1, children: _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: AIAIAI_COLORS.accent, bold: true, children: "\u203A " }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: value ? (
195
+ // Show actual input with cursor
196
+ inputLines.slice(0, 5).map((line, i) => {
197
+ const isCursorLine = i === Math.min(cursorRef.current, inputLines.length - 1);
198
+ const cursorCol = isCursorLine
199
+ ? cursorRef.current - inputLines.slice(0, i).join("\n").length - (i > 0 ? 1 : 0)
200
+ : line.length;
201
+ const cursorColClamped = Math.min(cursorCol, line.length);
202
+ const before = line.slice(0, cursorColClamped);
203
+ const at = line[cursorColClamped] || " ";
204
+ const after = line.slice(cursorColClamped + 1);
205
+ return (_jsxs(Text, { wrap: "truncate-end", children: [before, _jsx(Text, { inverse: true, children: at }), after] }, i));
206
+ })) : (_jsx(Text, { color: AIAIAI_COLORS.dim, children: placeholder })) })] }) })] }));
207
+ }
30
208
  export function REPL({ messages, streamingText, toolRunning, onSubmit, onAbort, disabled }) {
31
209
  const [input, setInput] = useState("");
32
210
  const { stdout } = useStdout();
33
211
  const termWidth = stdout?.columns ?? 80;
34
212
  const handleSubmit = useCallback((val) => {
35
- const trimmed = val.trim();
36
- if (!trimmed || disabled)
37
- return;
38
- setInput("");
39
- onSubmit(trimmed);
40
- }, [onSubmit, disabled]);
41
- useInput((_input, key) => {
42
- if (key.escape && disabled && onAbort)
43
- onAbort();
44
- });
213
+ onSubmit(val);
214
+ }, [onSubmit]);
215
+ const handleAbort = useCallback(() => {
216
+ onAbort?.();
217
+ }, [onAbort]);
218
+ // Scrolling: show last N messages
45
219
  const visible = messages.slice(-MAX_VISIBLE);
46
- return (_jsxs(Box, { flexDirection: "column", width: termWidth, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [visible.map(m => _jsx(MessageLine, { msg: m }, m.id)), streamingText && (_jsxs(Box, { flexDirection: "row", gap: 1, marginTop: 1, children: [_jsx(Text, { color: AIAIAI_COLORS.muted, children: " " }), _jsx(Text, { color: AIAIAI_COLORS.header, bold: true, children: "\uD83E\uDD16 " }), _jsx(Text, { wrap: "wrap", children: streamingText })] })), toolRunning && (_jsx(Box, { flexDirection: "row", gap: 1, marginTop: 1, children: _jsxs(Text, { color: AIAIAI_COLORS.warn, children: ["\u2699 running ", toolRunning, "\u2026"] }) }))] }), _jsxs(Box, { borderStyle: "round", borderColor: disabled ? AIAIAI_COLORS.dim : AIAIAI_COLORS.accent, paddingX: 1, marginTop: 1, children: [_jsx(Text, { color: AIAIAI_COLORS.accent, children: "\u203A " }), _jsx(TextInput, { value: input, onChange: setInput, onSubmit: handleSubmit, placeholder: disabled ? "thinking…" : "message or /command" })] })] }));
220
+ return (_jsxs(Box, { flexDirection: "column", width: termWidth, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [visible.map(m => _jsx(MessageLine, { msg: m }, m.id)), streamingText && (_jsxs(Box, { flexDirection: "row", gap: 1, marginTop: 1, children: [_jsx(Text, { color: AIAIAI_COLORS.muted, children: " " }), _jsx(Text, { color: AIAIAI_COLORS.header, bold: true, children: "\uD83E\uDD16 " }), _jsx(Text, { wrap: "wrap", children: streamingText })] })), toolRunning && (_jsx(Box, { flexDirection: "row", gap: 1, marginTop: 1, children: _jsxs(Text, { color: AIAIAI_COLORS.warn, children: ["\u2699 running ", toolRunning, "\u2026"] }) }))] }), _jsx(InputBox, { value: input, onChange: setInput, onSubmit: handleSubmit, disabled: !!disabled, onAbort: handleAbort })] }));
47
221
  }
48
222
  //# sourceMappingURL=REPL.js.map
@@ -9,6 +9,10 @@ export interface StatusBarProps {
9
9
  toolRunning: string | null;
10
10
  connected: boolean;
11
11
  statusLine?: string | null;
12
+ cpu?: number;
13
+ ram?: number;
14
+ uptime?: string;
15
+ apiCalls?: number;
12
16
  }
13
- export declare function StatusBar({ model, chain, price, toolRunning, connected, statusLine }: StatusBarProps): React.JSX.Element;
17
+ export declare function StatusBar({ model, chain, price, toolRunning, connected, statusLine, cpu, ram, uptime, apiCalls }: StatusBarProps): React.JSX.Element;
14
18
  //# sourceMappingURL=StatusBar.d.ts.map
@@ -1,13 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from "ink";
3
3
  import { AIAIAI_COLORS } from "./theme.js";
4
- export function StatusBar({ model, chain, price, toolRunning, connected, statusLine }) {
4
+ export function StatusBar({ model, chain, price, toolRunning, connected, statusLine, cpu, ram, uptime, apiCalls }) {
5
5
  const chainShort = chain.slice(0, 8);
6
- const modelShort = model.split("/").pop()?.slice(0, 18) ?? model.slice(0, 18);
6
+ const modelShort = model.split("/").pop()?.slice(0, 16) ?? model.slice(0, 16);
7
+ const cpuColor = (cpu ?? 0) > 80 ? AIAIAI_COLORS.error : (cpu ?? 0) > 50 ? AIAIAI_COLORS.warn : AIAIAI_COLORS.success;
8
+ const ramColor = (ram ?? 0) > 85 ? AIAIAI_COLORS.error : (ram ?? 0) > 60 ? AIAIAI_COLORS.warn : AIAIAI_COLORS.success;
7
9
  return (_jsxs(Box, { borderStyle: "single", borderColor: AIAIAI_COLORS.dim, paddingX: 1, flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Box, { gap: 2, children: [_jsx(Text, { color: AIAIAI_COLORS.accent, bold: true, children: "\uD83E\uDD16 AIAIAI" }), price ? _jsx(Text, { color: AIAIAI_COLORS.success, children: price }) : null, _jsx(Text, { color: AIAIAI_COLORS.muted, children: modelShort })] }), _jsx(Box, { children: toolRunning
8
10
  ? _jsxs(Text, { color: AIAIAI_COLORS.warn, children: ["\u2699 ", toolRunning] })
9
11
  : statusLine
10
- ? _jsx(Text, { color: AIAIAI_COLORS.muted, children: statusLine.slice(0, 80) })
11
- : _jsx(Text, { color: AIAIAI_COLORS.muted, children: connected ? "ready" : "connecting…" }) }), _jsx(Box, { gap: 2, children: _jsxs(Text, { color: AIAIAI_COLORS.header, children: ["\u26D3 ", chainShort] }) })] }));
12
+ ? _jsx(Text, { color: AIAIAI_COLORS.muted, children: statusLine.slice(0, 60) })
13
+ : _jsx(Text, { color: AIAIAI_COLORS.muted, children: connected ? "ready" : "connecting…" }) }), _jsxs(Box, { gap: 2, children: [cpu !== undefined && _jsxs(Text, { color: cpuColor, children: ["CPU:", cpu, "%"] }), ram !== undefined && _jsxs(Text, { color: ramColor, children: ["RAM:", ram, "%"] }), uptime && _jsx(Text, { color: AIAIAI_COLORS.muted, children: uptime }), apiCalls !== undefined && _jsxs(Text, { color: AIAIAI_COLORS.muted, children: ["API:", apiCalls, "/m"] }), _jsxs(Text, { color: AIAIAI_COLORS.header, children: ["\u26D3 ", chainShort] })] })] }));
12
14
  }
13
15
  //# sourceMappingURL=StatusBar.js.map
@@ -13,6 +13,7 @@ export declare const ACTION_WALLET = "BygDYM1ZXLQNC1HXLhnd1rHZ7E5XjioqT3vPjJFfjn
13
13
  export declare const DEPOSIT_WALLET = "FBMDYpG9WXKy4SgxuATQdB2sCyzHsJWPrEr45z3TgL2e";
14
14
  export declare const SIGNER = "GmFrDZT2cdrqykgTikVdXbe8EtCgzUDM9VsDhQnwsUsG";
15
15
  export declare const AIAIAI_MINT = "AVPJS61gZmWKtaEpb7qYPKo8Fk2xQUsayYQxPiPMpump";
16
+ export declare const AIAIAI_TOKEN = "AVPJS61gZmWKtaEpb7qYPKo8Fk2xQUsayYQxPiPMpump";
16
17
  export interface WalletBalance {
17
18
  address: string;
18
19
  sol: number;
@@ -13,6 +13,7 @@ export const ACTION_WALLET = "BygDYM1ZXLQNC1HXLhnd1rHZ7E5XjioqT3vPjJFfjnU2";
13
13
  export const DEPOSIT_WALLET = "FBMDYpG9WXKy4SgxuATQdB2sCyzHsJWPrEr45z3TgL2e";
14
14
  export const SIGNER = "GmFrDZT2cdrqykgTikVdXbe8EtCgzUDM9VsDhQnwsUsG";
15
15
  export const AIAIAI_MINT = "AVPJS61gZmWKtaEpb7qYPKo8Fk2xQUsayYQxPiPMpump";
16
+ export const AIAIAI_TOKEN = AIAIAI_MINT;
16
17
  const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
17
18
  function getRpcUrl() {
18
19
  return process.env.SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com";
package/docs/COMMANDS.md CHANGED
@@ -32,8 +32,8 @@
32
32
  | `/help` | Show all commands |
33
33
  | `/price` | $AIAIAI price |
34
34
  | `/news` | Latest crypto news |
35
- | `/models` | List available models |
36
- | `/model` | Switch AI model |
35
+ | `/models [q]` | List/search models |
36
+ | `/model [q]` | Switch AI model |
37
37
  | `/cost` | Token usage cost |
38
38
  | `/wallet` | Agent wallets |
39
39
  | `/deposit` | Deposit instructions |
@@ -41,22 +41,53 @@
41
41
  | `/actions` | Recent actions |
42
42
  | `/fees` | Fee tracker |
43
43
  | `/keys` | Provider management |
44
- | `/gmgn help` | GMGN help |
45
- | `/gmgn status` | GMGN status |
46
- | `/gmgn <cmd>` | GMGN sub-command |
47
- | `/update` | Check for updates |
48
- | `/goals` | Goal management |
44
+ | `/goals [add|done|list]` | Goal management |
49
45
  | `/schedule` | Task scheduler |
50
46
  | `/memory <q>` | Search memory |
47
+ | `/sessions` | List saved sessions |
48
+ | `/resume <n>` | Resume a session |
51
49
  | `/clear` | Clear chat |
52
50
  | `/exit` | Exit |
53
51
 
52
+ ### GMGN Commands
53
+
54
+ | Command | Description |
55
+ |---------|-------------|
56
+ | `/token info --chain <c> --address <a>` | Token info |
57
+ | `/token security --chain <c> --address <a>` | Security audit |
58
+ | `/token holders --chain <c> --address <a>` | Holder list |
59
+ | `/token traders --chain <c> --address <a>` | Trader list |
60
+ | `/track kol --chain <c>` | Top KOL buys |
61
+ | `/track smartmoney --chain <c>` | Smart money buys |
62
+ | `/track follow-wallet --chain <c>` | Followed wallet buys |
63
+ | `/market kline --chain <c> --address <a> --resolution <1h|4h|1d>` | Candlestick chart |
64
+ | `/market trending --chain <c> --interval <1h|6h|24h>` | Trending tokens |
65
+ | `/market trenches --chain <c> --type <new|near|completed>` | Launchpad trenches |
66
+ | `/market signal --chain <sol|bsc>` | Buy/sell signals |
67
+ | `/gmgn` / `/gmgnhelp` | GMGN help |
68
+ | `/gmgn status` | GMGN status |
69
+
70
+ ### Cross-Tool Commands
71
+
72
+ | Command | Description |
73
+ |---------|-------------|
74
+ | `/watch <address>` | Add token to watch list |
75
+ | `/unwatch <address>` | Remove from watch list |
76
+ | `/watchlist` | Show watched tokens |
77
+ | `/alert above <price>` | Set price alert (above) |
78
+ | `/alert below <price>` | Set price alert (below) |
79
+ | `/alerts` | List active alerts |
80
+ | `/compare <addr1> <addr2>` | Side-by-side token comparison |
81
+ | `/portfolio [address]` | Wallet portfolio analysis |
82
+
54
83
  ## TUI Keyboard
55
84
 
56
85
  | Key | Action |
57
86
  |-----|--------|
58
87
  | `Ctrl+C` | Exit |
59
88
  | `Escape` | Abort streaming response |
60
- | `↑/↓` | Scroll (in model selector) |
61
- | `Enter` | Submit / Select |
89
+ | `↑` / `↓` | Command history navigation |
90
+ | `Tab` | Auto-complete commands |
91
+ | `Enter` | Submit message / command |
92
+ | `Shift+Enter` | New line (multi-line input) |
62
93
  | `/` | Command prefix |
package/docs/README.md CHANGED
@@ -9,6 +9,8 @@ AIAIAI Chain Agent is a Solana-native AI agent that runs entirely in your termin
9
9
  **Chain:** Solana (primary)
10
10
  **License:** MIT
11
11
 
12
+ **Links:** [X / Twitter](https://x.com/aiaiaisol) · [aiaiaichain.xyz](https://aiaiaichain.xyz)
13
+
12
14
  ## Installation
13
15
 
14
16
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiaiaichain/agent",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "AIAIAI Chain Agent — Solana-native AI agent for decentralized AI governance. Ticker: $AIAIAI",
5
5
  "author": "AIAIAI Foundation",
6
6
  "license": "MIT",
@@ -28,7 +28,8 @@
28
28
  "start": "node dist/cli.js",
29
29
  "type-check": "tsc --noEmit --project tsconfig.json",
30
30
  "test": "echo \"no tests yet\" && exit 0",
31
- "prepublishOnly": "npm run build"
31
+ "prepublishOnly": "npm run build",
32
+ "postinstall": "node scripts/postinstall.js"
32
33
  },
33
34
  "dependencies": {
34
35
  "@sinclair/typebox": "^0.32.0",
@@ -41,7 +42,7 @@
41
42
  "devDependencies": {
42
43
  "@types/node": "^20.19.41",
43
44
  "@types/react": "^18.3.29",
44
- "typescript": "^5.9.3"
45
+ "typescript": "^5.10.0"
45
46
  },
46
47
  "engines": {
47
48
  "node": ">=20.0.0"
@@ -58,6 +59,7 @@
58
59
  "files": [
59
60
  "dist",
60
61
  "bin",
62
+ "scripts",
61
63
  "README.md",
62
64
  "docs",
63
65
  "!dist/**/*.map"
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * postinstall.js — creates bin symlinks if npm didn't do it.
6
+ * This handles the case where `npm install -g` doesn't create
7
+ * symlinks for scoped packages.
8
+ */
9
+
10
+ import { existsSync, symlinkSync, mkdirSync, readdirSync } from 'node:fs';
11
+ import { join, dirname } from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
13
+
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+ const PKG_ROOT = join(__dirname, '..');
16
+ const BIN_DIR = join(PKG_ROOT, 'bin');
17
+
18
+ // Only run if we're in a node_modules install (not dev)
19
+ if (!PKG_ROOT.includes('node_modules')) process.exit(0);
20
+
21
+ const NM_BIN = join(PKG_ROOT, 'node_modules', '.bin');
22
+
23
+ try {
24
+ mkdirSync(NM_BIN, { recursive: true });
25
+
26
+ const bins = readdirSync(BIN_DIR).filter(f => !f.endsWith('.map'));
27
+ for (const bin of bins) {
28
+ const src = join(BIN_DIR, bin);
29
+ const dst = join(NM_BIN, bin);
30
+ try { symlinkSync(src, dst); } catch { /* already exists */ }
31
+ }
32
+ } catch {
33
+ // Non-fatal — npm might have already created the links
34
+ }