@aiaiaichain/agent 0.1.3 → 0.1.5
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/README.md +2 -2
- package/dist/cli.js +274 -26
- package/dist/core/ChainConfig.js +1 -1
- package/dist/core/SystemMonitor.d.ts +32 -0
- package/dist/core/SystemMonitor.js +89 -0
- package/dist/index.d.ts +17 -2
- package/dist/index.js +18 -1
- package/dist/models/ModelRegistry.js +12 -4
- package/dist/runner/AgentRunner.d.ts +2 -0
- package/dist/runner/AgentRunner.js +18 -1
- package/dist/runner/ModelClient.js +109 -48
- package/dist/session/SessionManager.d.ts +1 -0
- package/dist/session/SessionManager.js +8 -2
- package/dist/session/SessionStore.d.ts +45 -0
- package/dist/session/SessionStore.js +128 -0
- package/dist/tools/CrossTools.d.ts +52 -0
- package/dist/tools/CrossTools.js +190 -0
- package/dist/tools/MarketSentiment.js +22 -13
- package/dist/tools/NewsSentiment.js +9 -3
- package/dist/tools/PriceFeed.js +11 -4
- package/dist/tools/TechnicalAnalysis.js +2 -1
- package/dist/tools/TokenCalendar.d.ts +24 -0
- package/dist/tools/TokenCalendar.js +81 -0
- package/dist/tools/TokenSecurityScanner.d.ts +22 -0
- package/dist/tools/TokenSecurityScanner.js +102 -0
- package/dist/tools/TransactionSim.d.ts +17 -0
- package/dist/tools/TransactionSim.js +78 -0
- package/dist/tui/App.d.ts +4 -3
- package/dist/tui/App.js +371 -118
- package/dist/tui/REPL.d.ts +2 -1
- package/dist/tui/REPL.js +190 -16
- package/dist/tui/Sparkline.d.ts +21 -0
- package/dist/tui/Sparkline.js +44 -0
- package/dist/tui/StatusBar.d.ts +5 -1
- package/dist/tui/StatusBar.js +6 -4
- package/dist/tui/ThemePresets.d.ts +25 -0
- package/dist/tui/ThemePresets.js +117 -0
- package/dist/util/clipboard.d.ts +9 -0
- package/dist/util/clipboard.js +26 -0
- package/dist/util/commandSuggest.d.ts +7 -0
- package/dist/util/commandSuggest.js +44 -0
- package/dist/util/confirmation.d.ts +6 -0
- package/dist/util/confirmation.js +16 -0
- package/dist/util/errorHandler.d.ts +3 -0
- package/dist/util/errorHandler.js +28 -0
- package/dist/util/logger.d.ts +11 -0
- package/dist/util/logger.js +43 -0
- package/dist/util/processManager.d.ts +5 -0
- package/dist/util/processManager.js +39 -0
- package/dist/util/resilientFetch.d.ts +21 -0
- package/dist/util/resilientFetch.js +94 -0
- package/dist/util/responseCache.d.ts +27 -0
- package/dist/util/responseCache.js +54 -0
- package/dist/util/safeLog.d.ts +4 -5
- package/dist/util/safeLog.js +68 -30
- package/dist/util/scheduler.d.ts +14 -0
- package/dist/util/scheduler.js +75 -0
- package/dist/util/webhooks.d.ts +9 -0
- package/dist/util/webhooks.js +75 -0
- package/dist/wallet/ActionFeed.js +12 -4
- package/dist/wallet/AgentWallet.d.ts +2 -0
- package/dist/wallet/AgentWallet.js +31 -5
- package/dist/wallet/ProfitTracker.d.ts +30 -0
- package/dist/wallet/ProfitTracker.js +93 -0
- package/docs/COMMANDS.md +40 -9
- package/docs/README.md +2 -0
- package/package.json +5 -3
- package/scripts/postinstall.js +34 -0
|
@@ -8,24 +8,34 @@
|
|
|
8
8
|
* Signer: GmFrDZT2cdrqykgTikVdXbe8EtCgzUDM9VsDhQnwsUsG (authority)
|
|
9
9
|
*/
|
|
10
10
|
import { Type } from "@sinclair/typebox";
|
|
11
|
+
import { resilientFetch } from "../util/resilientFetch.js";
|
|
12
|
+
import { logger } from "../util/logger.js";
|
|
11
13
|
export const COLD_WALLET = "A11iZoqEt6hU7HyggqC67ee4AtYmaJjwKCvJLerJRV2J";
|
|
12
14
|
export const ACTION_WALLET = "BygDYM1ZXLQNC1HXLhnd1rHZ7E5XjioqT3vPjJFfjnU2";
|
|
13
15
|
export const DEPOSIT_WALLET = "FBMDYpG9WXKy4SgxuATQdB2sCyzHsJWPrEr45z3TgL2e";
|
|
14
16
|
export const SIGNER = "GmFrDZT2cdrqykgTikVdXbe8EtCgzUDM9VsDhQnwsUsG";
|
|
15
17
|
export const AIAIAI_MINT = "AVPJS61gZmWKtaEpb7qYPKo8Fk2xQUsayYQxPiPMpump";
|
|
18
|
+
export const AIAIAI_TOKEN = AIAIAI_MINT;
|
|
16
19
|
const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
17
20
|
function getRpcUrl() {
|
|
18
21
|
return process.env.SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com";
|
|
19
22
|
}
|
|
20
23
|
async function rpcCall(method, params = []) {
|
|
21
|
-
const response = await
|
|
24
|
+
const response = await resilientFetch(getRpcUrl(), {
|
|
25
|
+
timeout: 15_000,
|
|
26
|
+
retries: 2,
|
|
22
27
|
method: "POST",
|
|
23
28
|
headers: { "Content-Type": "application/json" },
|
|
24
29
|
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
|
|
25
30
|
});
|
|
26
31
|
const data = await response.json();
|
|
27
|
-
if (data.error)
|
|
28
|
-
|
|
32
|
+
if (data.error) {
|
|
33
|
+
const msg = data.error.message || 'Unknown RPC error';
|
|
34
|
+
if (data.error.code === -32005 || msg.includes('rate limit')) {
|
|
35
|
+
logger.warn('AgentWallet', 'RPC rate limited', { method, code: data.error.code });
|
|
36
|
+
}
|
|
37
|
+
throw new Error(msg);
|
|
38
|
+
}
|
|
29
39
|
return data.result;
|
|
30
40
|
}
|
|
31
41
|
async function getBalance(address) {
|
|
@@ -33,7 +43,8 @@ async function getBalance(address) {
|
|
|
33
43
|
const result = await rpcCall("getBalance", [address]);
|
|
34
44
|
return (result?.value ?? 0) / 1e9;
|
|
35
45
|
}
|
|
36
|
-
catch {
|
|
46
|
+
catch (error) {
|
|
47
|
+
logger.warn('AgentWallet', 'getBalance failed', { address, error: error.message });
|
|
37
48
|
return 0;
|
|
38
49
|
}
|
|
39
50
|
}
|
|
@@ -49,7 +60,8 @@ async function getTokenBalance(address, mint) {
|
|
|
49
60
|
return 0;
|
|
50
61
|
return parseFloat(accounts[0]?.account?.data?.parsed?.info?.tokenAmount?.uiAmount ?? "0");
|
|
51
62
|
}
|
|
52
|
-
catch {
|
|
63
|
+
catch (error) {
|
|
64
|
+
logger.warn('AgentWallet', 'getTokenBalance failed', { address, mint, error: error.message });
|
|
53
65
|
return 0;
|
|
54
66
|
}
|
|
55
67
|
}
|
|
@@ -61,6 +73,9 @@ export class AgentWallet {
|
|
|
61
73
|
if (this.cachedWallets && Date.now() - this.lastFetch < this.cacheDuration) {
|
|
62
74
|
return this.cachedWallets;
|
|
63
75
|
}
|
|
76
|
+
// Parallel: 3 SOL balances + 6 token balances (9 concurrent queries)
|
|
77
|
+
const wallets = [COLD_WALLET, ACTION_WALLET, DEPOSIT_WALLET];
|
|
78
|
+
const mints = [AIAIAI_MINT, USDC_MINT];
|
|
64
79
|
const [coldSol, coldAiai, coldUsdc, actionSol, actionAiai, actionUsdc, depSol, depAiai, depUsdc] = await Promise.all([
|
|
65
80
|
getBalance(COLD_WALLET),
|
|
66
81
|
getTokenBalance(COLD_WALLET, AIAIAI_MINT),
|
|
@@ -89,6 +104,17 @@ export class AgentWallet {
|
|
|
89
104
|
async getDepositWallet() {
|
|
90
105
|
return (await this.getAll()).deposit;
|
|
91
106
|
}
|
|
107
|
+
// Get live SOL price from PriceFeed
|
|
108
|
+
async getSolPrice() {
|
|
109
|
+
try {
|
|
110
|
+
const { priceFeed } = await import("../tools/PriceFeed.js");
|
|
111
|
+
const result = await priceFeed.fetchToken("So11111111111111111111111111111111111111112");
|
|
112
|
+
return parseFloat(result.priceUsd ?? "150");
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return 150; // fallback
|
|
116
|
+
}
|
|
117
|
+
}
|
|
92
118
|
// ── Tools ───────────────────────────────────────────────────────────────
|
|
93
119
|
getAgentBalanceParams = Type.Object({});
|
|
94
120
|
getDepositBalanceParams = Type.Object({});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
/ProfitTracker.ts - Track cost basis and P&L for agent wallet trades
|
|
3
|
+
*/
|
|
4
|
+
export interface Trade {
|
|
5
|
+
timestamp: number;
|
|
6
|
+
type: "buy" | "sell" | "burn";
|
|
7
|
+
tokenAmount: number;
|
|
8
|
+
solAmount: number;
|
|
9
|
+
pricePerToken: number;
|
|
10
|
+
txSignature?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ProfitState {
|
|
13
|
+
trades: Trade[];
|
|
14
|
+
totalSpent: number;
|
|
15
|
+
totalReceived: number;
|
|
16
|
+
avgCostBasis: number;
|
|
17
|
+
}
|
|
18
|
+
export interface PLSummary {
|
|
19
|
+
avgCostBasis: number;
|
|
20
|
+
totalSpent: number;
|
|
21
|
+
totalReceived: number;
|
|
22
|
+
realizedPL: number;
|
|
23
|
+
unrealizedPL: number;
|
|
24
|
+
roi: number;
|
|
25
|
+
}
|
|
26
|
+
export declare function recordTrade(type: "buy" | "sell" | "burn", tokenAmount: number, solAmount: number, txSignature?: string): void;
|
|
27
|
+
export declare function getPLSummary(currentTokenPrice: number): PLSummary;
|
|
28
|
+
export declare function getTradeHistory(): Trade[];
|
|
29
|
+
export declare function exportCSV(): string;
|
|
30
|
+
//# sourceMappingURL=ProfitTracker.d.ts.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
/ProfitTracker.ts - Track cost basis and P&L for agent wallet trades
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { logger } from "../util/logger.js";
|
|
8
|
+
const AIAIAI_HOME = process.env.AIAIAI_HOME ?? join(homedir(), ".aiaiai");
|
|
9
|
+
const TRACKER_DIR = join(AIAIAI_HOME, ".tracker");
|
|
10
|
+
const TRACKER_FILE = join(TRACKER_DIR, "pl.json");
|
|
11
|
+
function loadState() {
|
|
12
|
+
try {
|
|
13
|
+
const raw = readFileSync(TRACKER_FILE, "utf-8");
|
|
14
|
+
return JSON.parse(raw);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return { trades: [], totalSpent: 0, totalReceived: 0, avgCostBasis: 0 };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function saveState(state) {
|
|
21
|
+
try {
|
|
22
|
+
if (!existsSync(TRACKER_DIR)) {
|
|
23
|
+
mkdirSync(TRACKER_DIR, { recursive: true, mode: 0o700 });
|
|
24
|
+
}
|
|
25
|
+
writeFileSync(TRACKER_FILE, JSON.stringify(state, null, 2), "utf-8");
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
logger.warn("ProfitTracker", "Failed to save state", { error: error.message });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function recordTrade(type, tokenAmount, solAmount, txSignature) {
|
|
32
|
+
const state = loadState();
|
|
33
|
+
const pricePerToken = tokenAmount > 0 ? solAmount / tokenAmount : 0;
|
|
34
|
+
const trade = {
|
|
35
|
+
timestamp: Date.now(),
|
|
36
|
+
type,
|
|
37
|
+
tokenAmount,
|
|
38
|
+
solAmount,
|
|
39
|
+
pricePerToken,
|
|
40
|
+
txSignature,
|
|
41
|
+
};
|
|
42
|
+
state.trades.push(trade);
|
|
43
|
+
if (type === "buy") {
|
|
44
|
+
state.totalSpent += solAmount;
|
|
45
|
+
const totalTokensInvested = state.trades
|
|
46
|
+
.filter(t => t.type === "buy")
|
|
47
|
+
.reduce((sum, t) => sum + t.tokenAmount, 0);
|
|
48
|
+
state.avgCostBasis = totalTokensInvested > 0 ? state.totalSpent / totalTokensInvested : 0;
|
|
49
|
+
}
|
|
50
|
+
else if (type === "sell") {
|
|
51
|
+
state.totalReceived += solAmount;
|
|
52
|
+
}
|
|
53
|
+
saveState(state);
|
|
54
|
+
logger.info("ProfitTracker", "Recorded " + type, { tokenAmount, solAmount, pricePerToken });
|
|
55
|
+
}
|
|
56
|
+
export function getPLSummary(currentTokenPrice) {
|
|
57
|
+
const state = loadState();
|
|
58
|
+
const buyTrades = state.trades.filter(t => t.type === "buy");
|
|
59
|
+
const totalBought = buyTrades.reduce((sum, t) => sum + t.tokenAmount, 0);
|
|
60
|
+
const totalSoldOrBurned = state.trades
|
|
61
|
+
.filter(t => t.type === "sell" || t.type === "burn")
|
|
62
|
+
.reduce((sum, t) => sum + t.tokenAmount, 0);
|
|
63
|
+
const remainingTokens = totalBought - totalSoldOrBurned;
|
|
64
|
+
const realizedPL = state.totalReceived - (state.totalSpent * (totalSoldOrBurned / Math.max(totalBought, 1)));
|
|
65
|
+
const unrealizedPL = remainingTokens * currentTokenPrice;
|
|
66
|
+
const totalPL = realizedPL + unrealizedPL;
|
|
67
|
+
const roi = state.totalSpent > 0 ? (totalPL / state.totalSpent) * 100 : 0;
|
|
68
|
+
return {
|
|
69
|
+
avgCostBasis: state.avgCostBasis,
|
|
70
|
+
totalSpent: state.totalSpent,
|
|
71
|
+
totalReceived: state.totalReceived,
|
|
72
|
+
realizedPL,
|
|
73
|
+
unrealizedPL,
|
|
74
|
+
roi,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export function getTradeHistory() {
|
|
78
|
+
return loadState().trades;
|
|
79
|
+
}
|
|
80
|
+
export function exportCSV() {
|
|
81
|
+
const state = loadState();
|
|
82
|
+
const header = "timestamp,type,tokenAmount,solAmount,pricePerToken,txSignature";
|
|
83
|
+
const rows = state.trades.map(t => t.timestamp + "," + t.type + "," + t.tokenAmount + "," + t.solAmount + "," + t.pricePerToken + "," + (t.txSignature ?? ""));
|
|
84
|
+
const csv = header + "\n" + rows.join("\n");
|
|
85
|
+
const csvPath = join(TRACKER_DIR, "pl-export.csv");
|
|
86
|
+
if (!existsSync(TRACKER_DIR)) {
|
|
87
|
+
mkdirSync(TRACKER_DIR, { recursive: true, mode: 0o700 });
|
|
88
|
+
}
|
|
89
|
+
writeFileSync(csvPath, csv, "utf-8");
|
|
90
|
+
logger.info("ProfitTracker", "Exported CSV", { path: csvPath });
|
|
91
|
+
return csvPath;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=ProfitTracker.js.map
|
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
|
|
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
|
-
| `/
|
|
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
|
-
|
|
|
61
|
-
| `
|
|
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
|
+
"version": "0.1.5",
|
|
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.
|
|
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
|
+
}
|