moneyos 0.4.1 → 0.5.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/README.md +47 -6
- package/dist/cli/index.js +399 -21
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +221 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.js +218 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,6 +26,10 @@ moneyos auth unlock
|
|
|
26
26
|
moneyos auth lock
|
|
27
27
|
moneyos auth status
|
|
28
28
|
moneyos auth change-password
|
|
29
|
+
moneyos add <tool>
|
|
30
|
+
moneyos remove <tool>
|
|
31
|
+
moneyos tools
|
|
32
|
+
moneyos swap <amount> <tokenIn> <tokenOut> [--chain <id>] [--provider odos]
|
|
29
33
|
moneyos backup export [--out ./wallet-backup.json] [--force]
|
|
30
34
|
moneyos backup restore <path> [--force]
|
|
31
35
|
moneyos backup status
|
|
@@ -41,8 +45,9 @@ Example:
|
|
|
41
45
|
```bash
|
|
42
46
|
moneyos init
|
|
43
47
|
moneyos auth unlock
|
|
44
|
-
moneyos
|
|
45
|
-
moneyos
|
|
48
|
+
moneyos add swap
|
|
49
|
+
moneyos tools
|
|
50
|
+
moneyos swap 0.1 RYZE ETH
|
|
46
51
|
```
|
|
47
52
|
|
|
48
53
|
## SDK
|
|
@@ -62,14 +67,48 @@ const tx = await moneyos.send("USDC", "0x...", "10");
|
|
|
62
67
|
console.log(tx.hash);
|
|
63
68
|
```
|
|
64
69
|
|
|
70
|
+
If you want a local script or workflow to reuse an already-unlocked MoneyOS
|
|
71
|
+
session, unlock first in the terminal:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
moneyos auth unlock
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Then attach that session explicitly in code:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { createMoneyOS, connectLocalSession } from "moneyos";
|
|
81
|
+
|
|
82
|
+
const execute = await connectLocalSession();
|
|
83
|
+
const moneyos = createMoneyOS({
|
|
84
|
+
chainId: 42161,
|
|
85
|
+
execute,
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Tool packages should still execute against `moneyos.runtime`; they should not
|
|
90
|
+
import `connectLocalSession()` themselves.
|
|
91
|
+
|
|
65
92
|
The runtime seam is intentionally small. `createMoneyOS` can also take injected
|
|
66
93
|
`execute`, `read`, and `assets` implementations, which is how external packages
|
|
67
94
|
plug in.
|
|
68
95
|
|
|
69
|
-
Swap
|
|
70
|
-
|
|
96
|
+
Swap still lives in the separate `@moneyos/swap` package, but the root CLI now
|
|
97
|
+
owns first-class tool install/use UX for CLI-integrated packages.
|
|
98
|
+
|
|
99
|
+
For CLI usage, install the root package and then add tools into the user-level
|
|
100
|
+
MoneyOS tool home:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npm install moneyos
|
|
104
|
+
moneyos init
|
|
105
|
+
moneyos auth unlock
|
|
106
|
+
moneyos add swap
|
|
107
|
+
moneyos tools
|
|
108
|
+
moneyos swap 0.1 RYZE ETH
|
|
109
|
+
```
|
|
71
110
|
|
|
72
|
-
|
|
111
|
+
For direct package usage, install the tool package alongside `moneyos`:
|
|
73
112
|
|
|
74
113
|
```bash
|
|
75
114
|
npm install moneyos @moneyos/swap
|
|
@@ -109,7 +148,8 @@ Published packages:
|
|
|
109
148
|
- `@moneyos/swap`
|
|
110
149
|
|
|
111
150
|
Current `moneyos` releases no longer bundle swap into the root SDK or CLI. If
|
|
112
|
-
you want swap, install
|
|
151
|
+
you want swap from the root CLI, install `moneyos` and then run
|
|
152
|
+
`moneyos add swap`.
|
|
113
153
|
|
|
114
154
|
## Current wallet model
|
|
115
155
|
|
|
@@ -119,6 +159,7 @@ What is landed in code today:
|
|
|
119
159
|
- `~/.moneyos/config.json` now stores only non-secret settings such as chain and RPC configuration
|
|
120
160
|
- `MONEYOS_PRIVATE_KEY` remains an explicit override for ephemeral CI or agent runs
|
|
121
161
|
- `moneyos auth unlock` opens a short-lived local session for write commands
|
|
162
|
+
- workflow scripts can attach to that unlocked session with `connectLocalSession()`
|
|
122
163
|
- `moneyos auth change-password` rotates the local wallet password and locks the current session
|
|
123
164
|
- `moneyos backup export|restore|status` manages encrypted wallet backups
|
|
124
165
|
- Normal wallet commands resolve their write path through one shared session-aware flow
|
package/dist/cli/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command10 } from "commander";
|
|
5
5
|
import { realpathSync } from "fs";
|
|
6
6
|
import { resolve as resolve3 } from "path";
|
|
7
7
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -511,6 +511,15 @@ function saveConfig(config, path = CONFIG_FILE) {
|
|
|
511
511
|
function getConfigPath() {
|
|
512
512
|
return CONFIG_FILE;
|
|
513
513
|
}
|
|
514
|
+
function getToolHomeDir() {
|
|
515
|
+
return join3(CONFIG_DIR, "tools");
|
|
516
|
+
}
|
|
517
|
+
function getToolHomePackageJsonPath() {
|
|
518
|
+
return join3(getToolHomeDir(), "package.json");
|
|
519
|
+
}
|
|
520
|
+
function getToolRegistryPath() {
|
|
521
|
+
return join3(getToolHomeDir(), "registry.json");
|
|
522
|
+
}
|
|
514
523
|
function getWalletPath(config) {
|
|
515
524
|
return config?.walletPath ?? join3(CONFIG_DIR, "wallet.json");
|
|
516
525
|
}
|
|
@@ -1442,6 +1451,43 @@ import {
|
|
|
1442
1451
|
parseUnits,
|
|
1443
1452
|
encodeFunctionData
|
|
1444
1453
|
} from "viem";
|
|
1454
|
+
|
|
1455
|
+
// src/core/assets.ts
|
|
1456
|
+
var DefaultAssetRegistry = class {
|
|
1457
|
+
nativeTokenAddress = NATIVE_TOKEN_ADDRESS;
|
|
1458
|
+
getToken = getToken;
|
|
1459
|
+
getTokenAddress = getTokenAddress;
|
|
1460
|
+
getChain = getChain;
|
|
1461
|
+
};
|
|
1462
|
+
|
|
1463
|
+
// src/core/no-executor.ts
|
|
1464
|
+
var NO_SIGNING_ACCOUNT_ERROR = "No signing account configured. Set `signer`, `privateKey`, or `execute` in MoneyOS config.";
|
|
1465
|
+
function throwNoSigningAccount() {
|
|
1466
|
+
throw new Error(NO_SIGNING_ACCOUNT_ERROR);
|
|
1467
|
+
}
|
|
1468
|
+
function createNoExecutorClient() {
|
|
1469
|
+
return {
|
|
1470
|
+
mode: "eoa",
|
|
1471
|
+
getAddress() {
|
|
1472
|
+
return throwNoSigningAccount();
|
|
1473
|
+
},
|
|
1474
|
+
async send(_call) {
|
|
1475
|
+
return throwNoSigningAccount();
|
|
1476
|
+
},
|
|
1477
|
+
async sendBatch(_calls) {
|
|
1478
|
+
return throwNoSigningAccount();
|
|
1479
|
+
},
|
|
1480
|
+
capabilities() {
|
|
1481
|
+
return {
|
|
1482
|
+
sponsoredGas: false,
|
|
1483
|
+
batching: false,
|
|
1484
|
+
simulation: false
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
// src/core/client.ts
|
|
1445
1491
|
var ERC20_ABI = [
|
|
1446
1492
|
{
|
|
1447
1493
|
name: "balanceOf",
|
|
@@ -1475,12 +1521,6 @@ var ERC20_ABI = [
|
|
|
1475
1521
|
outputs: [{ name: "", type: "string" }]
|
|
1476
1522
|
}
|
|
1477
1523
|
];
|
|
1478
|
-
var DefaultAssetRegistry = class {
|
|
1479
|
-
nativeTokenAddress = NATIVE_TOKEN_ADDRESS;
|
|
1480
|
-
getToken = getToken;
|
|
1481
|
-
getTokenAddress = getTokenAddress;
|
|
1482
|
-
getChain = getChain;
|
|
1483
|
-
};
|
|
1484
1524
|
var MoneyOS = class {
|
|
1485
1525
|
read;
|
|
1486
1526
|
executor;
|
|
@@ -1526,9 +1566,7 @@ var MoneyOS = class {
|
|
|
1526
1566
|
}
|
|
1527
1567
|
requireExecutor() {
|
|
1528
1568
|
if (!this.executor) {
|
|
1529
|
-
throw new Error(
|
|
1530
|
-
"No signing account configured. Set `signer`, `privateKey`, or `execute` in MoneyOS config."
|
|
1531
|
-
);
|
|
1569
|
+
throw new Error(NO_SIGNING_ACCOUNT_ERROR);
|
|
1532
1570
|
}
|
|
1533
1571
|
return this.executor;
|
|
1534
1572
|
}
|
|
@@ -1605,6 +1643,23 @@ var MoneyOS = class {
|
|
|
1605
1643
|
}
|
|
1606
1644
|
};
|
|
1607
1645
|
|
|
1646
|
+
// src/local-session.ts
|
|
1647
|
+
async function connectLocalSession(options = {}) {
|
|
1648
|
+
const socketPath = options.socketPath ?? getSessionSocketPath();
|
|
1649
|
+
const tokenPath = options.tokenPath ?? getSessionTokenPath();
|
|
1650
|
+
const session = await getSessionStatus(socketPath, tokenPath);
|
|
1651
|
+
if (!session) {
|
|
1652
|
+
throw new Error(
|
|
1653
|
+
"No active local MoneyOS session found. Run `moneyos auth unlock` locally first."
|
|
1654
|
+
);
|
|
1655
|
+
}
|
|
1656
|
+
return new SessionExecutionClient({
|
|
1657
|
+
socketPath,
|
|
1658
|
+
tokenPath,
|
|
1659
|
+
address: session.address
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1608
1663
|
// src/cli/wallet.ts
|
|
1609
1664
|
function resolveEnvPrivateKey(explicit) {
|
|
1610
1665
|
return explicit ?? process.env.MONEYOS_PRIVATE_KEY;
|
|
@@ -1665,16 +1720,15 @@ async function buildCliMoneyOSConfig(config, options = {}) {
|
|
|
1665
1720
|
}
|
|
1666
1721
|
const socketPath = getSessionPath(options);
|
|
1667
1722
|
const tokenPath = getTokenPath(options);
|
|
1668
|
-
|
|
1669
|
-
if (session) {
|
|
1723
|
+
try {
|
|
1670
1724
|
return {
|
|
1671
1725
|
...moneyosConfig,
|
|
1672
|
-
execute:
|
|
1726
|
+
execute: await connectLocalSession({
|
|
1673
1727
|
socketPath,
|
|
1674
|
-
tokenPath
|
|
1675
|
-
address: session.address
|
|
1728
|
+
tokenPath
|
|
1676
1729
|
})
|
|
1677
1730
|
};
|
|
1731
|
+
} catch {
|
|
1678
1732
|
}
|
|
1679
1733
|
const wallet = getWalletStore(config, options);
|
|
1680
1734
|
if (wallet.exists()) {
|
|
@@ -2089,21 +2143,341 @@ backupCommand.command("status").description("Show the local wallet backup status
|
|
|
2089
2143
|
console.log(formatBackupStatus(status));
|
|
2090
2144
|
});
|
|
2091
2145
|
|
|
2146
|
+
// src/cli/commands/tools.ts
|
|
2147
|
+
import { Command as Command9 } from "commander";
|
|
2148
|
+
|
|
2149
|
+
// src/cli/tools/manager.ts
|
|
2150
|
+
import { spawn as spawn2 } from "child_process";
|
|
2151
|
+
import { createRequire } from "module";
|
|
2152
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
2153
|
+
import { dirname as dirname4, join as join4 } from "path";
|
|
2154
|
+
import { pathToFileURL } from "url";
|
|
2155
|
+
import { Command as Command8, CommanderError } from "commander";
|
|
2156
|
+
|
|
2157
|
+
// src/cli/tools/runtime.ts
|
|
2158
|
+
import { Command as Command7 } from "commander";
|
|
2159
|
+
var defaultCreateMoneyOSCliContextDependencies = {
|
|
2160
|
+
loadConfig,
|
|
2161
|
+
connectLocalSession,
|
|
2162
|
+
createReadClient: ({ chainId, rpcUrl }) => new ViemReadClient({
|
|
2163
|
+
defaultChainId: chainId,
|
|
2164
|
+
rpcUrl
|
|
2165
|
+
}),
|
|
2166
|
+
createAssets: () => new DefaultAssetRegistry()
|
|
2167
|
+
};
|
|
2168
|
+
function createMoneyOSCliContext(deps = defaultCreateMoneyOSCliContextDependencies) {
|
|
2169
|
+
return {
|
|
2170
|
+
Command: Command7,
|
|
2171
|
+
async getRuntime(options = {}) {
|
|
2172
|
+
const config = deps.loadConfig();
|
|
2173
|
+
const chainId = options.chainId ?? config.chainId ?? 42161;
|
|
2174
|
+
const rpcUrl = config.rpcUrl;
|
|
2175
|
+
return {
|
|
2176
|
+
read: deps.createReadClient({ chainId, rpcUrl }),
|
|
2177
|
+
execute: options.requireSession ? await deps.connectLocalSession() : createNoExecutorClient(),
|
|
2178
|
+
assets: deps.createAssets(),
|
|
2179
|
+
config: {
|
|
2180
|
+
defaultChainId: chainId,
|
|
2181
|
+
rpcUrl
|
|
2182
|
+
}
|
|
2183
|
+
};
|
|
2184
|
+
}
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
// src/cli/tools/manager.ts
|
|
2189
|
+
var RESERVED_ROOT_COMMANDS = /* @__PURE__ */ new Set(["init", "auth", "backup", "balance", "send", "keystore", "add", "remove", "tools", "help", "__session-daemon"]);
|
|
2190
|
+
var FIRST_PARTY_TOOL_ALIASES = { swap: "@moneyos/swap" };
|
|
2191
|
+
var SHARED_TOOL_HOME_DEPENDENCIES = { "@moneyos/core": "^0.1.0", viem: "^2.45.1" };
|
|
2192
|
+
var VALID_COMMAND_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
|
|
2193
|
+
var getPaths = () => ({
|
|
2194
|
+
rootDir: getToolHomeDir(),
|
|
2195
|
+
packageJsonPath: getToolHomePackageJsonPath(),
|
|
2196
|
+
registryPath: getToolRegistryPath()
|
|
2197
|
+
});
|
|
2198
|
+
function ensureToolHome(paths) {
|
|
2199
|
+
if (!existsSync5(paths.rootDir)) mkdirSync4(paths.rootDir, { recursive: true, mode: 448 });
|
|
2200
|
+
const exists = existsSync5(paths.packageJsonPath);
|
|
2201
|
+
const parsed = exists ? JSON.parse(readFileSync4(paths.packageJsonPath, "utf8")) : void 0;
|
|
2202
|
+
const packageJson = {
|
|
2203
|
+
name: parsed?.name ?? "moneyos-tools",
|
|
2204
|
+
private: parsed?.private ?? true,
|
|
2205
|
+
description: parsed?.description ?? "User-level installed MoneyOS CLI tools",
|
|
2206
|
+
dependencies: { ...parsed?.dependencies ?? {} }
|
|
2207
|
+
};
|
|
2208
|
+
let changed = !exists;
|
|
2209
|
+
for (const [name, version2] of Object.entries(SHARED_TOOL_HOME_DEPENDENCIES)) {
|
|
2210
|
+
if (packageJson.dependencies[name] !== version2) {
|
|
2211
|
+
packageJson.dependencies[name] = version2;
|
|
2212
|
+
changed = true;
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
if (changed) writeFileSync3(paths.packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
|
|
2216
|
+
`, { mode: 384 });
|
|
2217
|
+
if (!existsSync5(paths.registryPath)) writeFileSync3(paths.registryPath, "[]\n", { mode: 384 });
|
|
2218
|
+
}
|
|
2219
|
+
function parseRegistryEntry(value) {
|
|
2220
|
+
if (typeof value !== "object" || value === null) throw new Error("Invalid tool registry entry.");
|
|
2221
|
+
const entry = value;
|
|
2222
|
+
if (typeof entry.packageName !== "string" || typeof entry.packageVersion !== "string" || entry.toolVersion !== 1 || typeof entry.name !== "string" || typeof entry.description !== "string" || !Array.isArray(entry.commandPath) || entry.commandPath.length === 0 || entry.commandPath.some((segment) => typeof segment !== "string" || !VALID_COMMAND_SEGMENT.test(segment))) throw new Error("Invalid tool registry entry.");
|
|
2223
|
+
return { packageName: entry.packageName, packageVersion: entry.packageVersion, toolVersion: 1, name: entry.name, commandPath: [...entry.commandPath], description: entry.description };
|
|
2224
|
+
}
|
|
2225
|
+
function parseLoadedTool(packageName, packageVersion, value) {
|
|
2226
|
+
if (typeof value !== "object" || value === null) throw new Error(`Package ${packageName} does not export \`moneyosCliTool\`.`);
|
|
2227
|
+
const tool = value;
|
|
2228
|
+
if (tool.version !== 1 || typeof tool.name !== "string" || typeof tool.description !== "string" || !Array.isArray(tool.commandPath) || tool.commandPath.length === 0 || tool.commandPath.some((segment) => typeof segment !== "string" || !VALID_COMMAND_SEGMENT.test(segment)) || typeof tool.createCommand !== "function") throw new Error(`Package ${packageName} exports an invalid \`moneyosCliTool\`.`);
|
|
2229
|
+
return { packageName, packageVersion, toolVersion: 1, name: tool.name, commandPath: [...tool.commandPath], description: tool.description, createCommand: tool.createCommand };
|
|
2230
|
+
}
|
|
2231
|
+
function readRegistry(paths) {
|
|
2232
|
+
ensureToolHome(paths);
|
|
2233
|
+
const parsed = JSON.parse(readFileSync4(paths.registryPath, "utf8"));
|
|
2234
|
+
if (!Array.isArray(parsed)) throw new Error(`Tool registry at ${paths.registryPath} is invalid.`);
|
|
2235
|
+
return parsed.map(parseRegistryEntry);
|
|
2236
|
+
}
|
|
2237
|
+
function writeRegistry(paths, entries) {
|
|
2238
|
+
ensureToolHome(paths);
|
|
2239
|
+
writeFileSync3(paths.registryPath, `${JSON.stringify(entries, null, 2)}
|
|
2240
|
+
`, { mode: 384 });
|
|
2241
|
+
}
|
|
2242
|
+
function getConflict(entry, entries) {
|
|
2243
|
+
const path = entry.commandPath.join(" ");
|
|
2244
|
+
if (RESERVED_ROOT_COMMANDS.has(entry.name) || RESERVED_ROOT_COMMANDS.has(entry.commandPath[0])) {
|
|
2245
|
+
return `reserved root command collision for ${path}`;
|
|
2246
|
+
}
|
|
2247
|
+
for (const other of entries) {
|
|
2248
|
+
if (other.packageName === entry.packageName) continue;
|
|
2249
|
+
const otherPath = other.commandPath.join(" ");
|
|
2250
|
+
if (path === otherPath || path.startsWith(`${otherPath} `) || otherPath.startsWith(`${path} `)) {
|
|
2251
|
+
return `command path ${path} collides with installed tool path ${otherPath}`;
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
async function runNpm(paths, args) {
|
|
2256
|
+
await new Promise((resolve4, reject) => {
|
|
2257
|
+
const child = spawn2("npm", args, { cwd: paths.rootDir, stdio: "pipe" });
|
|
2258
|
+
let stderr = "";
|
|
2259
|
+
child.stderr.on("data", (chunk) => {
|
|
2260
|
+
stderr += chunk.toString();
|
|
2261
|
+
});
|
|
2262
|
+
child.on("error", reject);
|
|
2263
|
+
child.on("close", (code) => code === 0 ? resolve4() : reject(new Error(stderr.trim() || `npm ${args[0]} failed with exit code ${String(code)}.`)));
|
|
2264
|
+
});
|
|
2265
|
+
}
|
|
2266
|
+
async function loadInstalledToolFromFs(paths, packageName) {
|
|
2267
|
+
const toolRequire = createRequire(paths.packageJsonPath);
|
|
2268
|
+
let entryPath;
|
|
2269
|
+
try {
|
|
2270
|
+
entryPath = toolRequire.resolve(packageName);
|
|
2271
|
+
} catch {
|
|
2272
|
+
throw new Error(`Package ${packageName} is missing from ${join4(paths.rootDir, "node_modules")}.`);
|
|
2273
|
+
}
|
|
2274
|
+
for (let root = dirname4(entryPath); root !== dirname4(root); root = dirname4(root)) {
|
|
2275
|
+
const packageJsonPath2 = join4(root, "package.json");
|
|
2276
|
+
if (!existsSync5(packageJsonPath2)) continue;
|
|
2277
|
+
const packageJson = JSON.parse(readFileSync4(packageJsonPath2, "utf8"));
|
|
2278
|
+
if (packageJson.name === packageName && typeof packageJson.version === "string") {
|
|
2279
|
+
const moduleNamespace = await import(pathToFileURL(entryPath).href);
|
|
2280
|
+
return parseLoadedTool(
|
|
2281
|
+
packageJson.name,
|
|
2282
|
+
packageJson.version,
|
|
2283
|
+
moduleNamespace.moneyosCliTool ?? moduleNamespace.default?.moneyosCliTool
|
|
2284
|
+
);
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
throw new Error(`Could not find package.json for installed package ${packageName}.`);
|
|
2288
|
+
}
|
|
2289
|
+
function createCliToolManager(params = {}) {
|
|
2290
|
+
const paths = params.paths ?? getPaths();
|
|
2291
|
+
const cliContext = params.cliContext ?? createMoneyOSCliContext();
|
|
2292
|
+
const install = params.packageManager?.install ?? ((toolPaths, spec) => runNpm(toolPaths, ["install", "--save-exact", "--no-fund", "--no-audit", spec]));
|
|
2293
|
+
const uninstall = params.packageManager?.uninstall ?? ((toolPaths, packageName) => runNpm(toolPaths, ["uninstall", "--no-fund", "--no-audit", packageName]));
|
|
2294
|
+
const loadInstalledTool = params.moduleLoader ? async (toolPaths, packageName) => {
|
|
2295
|
+
const loaded = await params.moduleLoader(toolPaths, packageName);
|
|
2296
|
+
return parseLoadedTool(loaded.packageName, loaded.packageVersion, loaded.cliTool);
|
|
2297
|
+
} : loadInstalledToolFromFs;
|
|
2298
|
+
const sameMetadata = (left, right) => left.packageName === right.packageName && left.packageVersion === right.packageVersion && left.toolVersion === right.toolVersion && left.name === right.name && left.description === right.description && left.commandPath.join("\0") === right.commandPath.join("\0");
|
|
2299
|
+
const resolveInstalledTool = (input, entries) => entries.find((entry) => entry.packageName === input) ?? entries.find((entry) => entry.packageName === FIRST_PARTY_TOOL_ALIASES[input]) ?? (() => {
|
|
2300
|
+
const matches = entries.filter((entry) => entry.name === input || entry.commandPath.join(" ") === input);
|
|
2301
|
+
return matches.length === 1 ? matches[0] : void 0;
|
|
2302
|
+
})();
|
|
2303
|
+
async function invoke(entry, args) {
|
|
2304
|
+
let command;
|
|
2305
|
+
try {
|
|
2306
|
+
const loaded = await loadInstalledTool(paths, entry.packageName);
|
|
2307
|
+
if (!sameMetadata(entry, loaded)) throw new Error("installed metadata does not match registry");
|
|
2308
|
+
command = loaded.createCommand(cliContext);
|
|
2309
|
+
if (!(command instanceof Command8) || command.name() !== entry.commandPath[entry.commandPath.length - 1]) {
|
|
2310
|
+
throw new Error("createCommand() did not return the expected command");
|
|
2311
|
+
}
|
|
2312
|
+
command.exitOverride();
|
|
2313
|
+
} catch (error) {
|
|
2314
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2315
|
+
throw new Error(`Installed tool ${entry.commandPath.join(" ")} is broken: ${message}. Run \`moneyos add ${entry.packageName}\` to repair it or \`moneyos remove ${entry.packageName}\` to uninstall it.`);
|
|
2316
|
+
}
|
|
2317
|
+
try {
|
|
2318
|
+
await command.parseAsync([process.execPath, entry.commandPath[entry.commandPath.length - 1], ...args]);
|
|
2319
|
+
} catch (error) {
|
|
2320
|
+
if (error instanceof CommanderError) {
|
|
2321
|
+
if (error.code !== "commander.helpDisplayed") process.exitCode = error.exitCode || 1;
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
2324
|
+
throw error;
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
return {
|
|
2328
|
+
getRegistryEntries() {
|
|
2329
|
+
return readRegistry(paths);
|
|
2330
|
+
},
|
|
2331
|
+
mountInstalledToolCommands(program) {
|
|
2332
|
+
let entries;
|
|
2333
|
+
try {
|
|
2334
|
+
entries = readRegistry(paths);
|
|
2335
|
+
} catch {
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2338
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2339
|
+
for (const entry of entries) {
|
|
2340
|
+
if (getConflict(entry, entries)) continue;
|
|
2341
|
+
let parent = program;
|
|
2342
|
+
const segments = [];
|
|
2343
|
+
for (const segment of entry.commandPath.slice(0, -1)) {
|
|
2344
|
+
segments.push(segment);
|
|
2345
|
+
const key = segments.join(" ");
|
|
2346
|
+
const group = groups.get(key) ?? new Command8(segment).description("Installed MoneyOS tool commands");
|
|
2347
|
+
if (!groups.has(key)) {
|
|
2348
|
+
groups.set(key, group);
|
|
2349
|
+
parent.addCommand(group);
|
|
2350
|
+
}
|
|
2351
|
+
parent = group;
|
|
2352
|
+
}
|
|
2353
|
+
parent.addCommand(
|
|
2354
|
+
new Command8(entry.commandPath[entry.commandPath.length - 1]).description(entry.description).allowUnknownOption(true).allowExcessArguments(true).helpOption(false).argument("[args...]").action(async (args) => {
|
|
2355
|
+
await invoke(entry, args);
|
|
2356
|
+
})
|
|
2357
|
+
);
|
|
2358
|
+
}
|
|
2359
|
+
},
|
|
2360
|
+
async addTool(input) {
|
|
2361
|
+
const spec = FIRST_PARTY_TOOL_ALIASES[input] ?? input;
|
|
2362
|
+
const entries = readRegistry(paths);
|
|
2363
|
+
const previous = resolveInstalledTool(input, entries);
|
|
2364
|
+
ensureToolHome(paths);
|
|
2365
|
+
const before = JSON.parse(readFileSync4(paths.packageJsonPath, "utf8")).dependencies ?? {};
|
|
2366
|
+
await install(paths, spec);
|
|
2367
|
+
const after = JSON.parse(readFileSync4(paths.packageJsonPath, "utf8")).dependencies ?? {};
|
|
2368
|
+
const changed = previous?.packageName ?? Object.keys(after).find((name) => before[name] !== after[name] && !(name in SHARED_TOOL_HOME_DEPENDENCIES));
|
|
2369
|
+
try {
|
|
2370
|
+
const loaded = await loadInstalledTool(paths, changed ?? spec);
|
|
2371
|
+
const next = previous ? entries.filter((entry2) => entry2.packageName !== previous.packageName) : entries;
|
|
2372
|
+
const conflict = getConflict(loaded, next);
|
|
2373
|
+
if (conflict) throw new Error(conflict);
|
|
2374
|
+
const entry = { packageName: loaded.packageName, packageVersion: loaded.packageVersion, toolVersion: loaded.toolVersion, name: loaded.name, commandPath: loaded.commandPath, description: loaded.description };
|
|
2375
|
+
writeRegistry(paths, [...next, entry]);
|
|
2376
|
+
return entry;
|
|
2377
|
+
} catch (error) {
|
|
2378
|
+
try {
|
|
2379
|
+
if (previous) await install(paths, `${previous.packageName}@${previous.packageVersion}`);
|
|
2380
|
+
else if (changed) await uninstall(paths, changed);
|
|
2381
|
+
} catch {
|
|
2382
|
+
}
|
|
2383
|
+
throw error;
|
|
2384
|
+
}
|
|
2385
|
+
},
|
|
2386
|
+
async removeTool(input) {
|
|
2387
|
+
const entries = readRegistry(paths);
|
|
2388
|
+
const entry = resolveInstalledTool(input, entries);
|
|
2389
|
+
if (!entry) throw new Error(`Tool ${input} is not installed.`);
|
|
2390
|
+
await uninstall(paths, entry.packageName);
|
|
2391
|
+
writeRegistry(paths, entries.filter((installed) => installed.packageName !== entry.packageName));
|
|
2392
|
+
return entry;
|
|
2393
|
+
},
|
|
2394
|
+
async listTools() {
|
|
2395
|
+
const entries = readRegistry(paths);
|
|
2396
|
+
const tools = await Promise.all(entries.map(async (entry) => {
|
|
2397
|
+
const conflict = getConflict(entry, entries);
|
|
2398
|
+
if (conflict) return { ...entry, state: "conflict", problems: [conflict] };
|
|
2399
|
+
try {
|
|
2400
|
+
const loaded = await loadInstalledTool(paths, entry.packageName);
|
|
2401
|
+
if (!sameMetadata(entry, loaded)) throw new Error("installed metadata does not match registry");
|
|
2402
|
+
return { ...entry, state: "ok", problems: [] };
|
|
2403
|
+
} catch (error) {
|
|
2404
|
+
return { ...entry, state: "broken", problems: [error instanceof Error ? error.message : String(error)] };
|
|
2405
|
+
}
|
|
2406
|
+
}));
|
|
2407
|
+
return tools.sort((left, right) => left.commandPath.join(" ").localeCompare(right.commandPath.join(" ")));
|
|
2408
|
+
}
|
|
2409
|
+
};
|
|
2410
|
+
}
|
|
2411
|
+
function formatToolStatusTable(tools) {
|
|
2412
|
+
if (tools.length === 0) return "No tools installed.";
|
|
2413
|
+
const commandWidth = Math.max("COMMAND".length, ...tools.map((tool) => tool.commandPath.join(" ").length));
|
|
2414
|
+
const packageWidth = Math.max("PACKAGE".length, ...tools.map((tool) => tool.packageName.length));
|
|
2415
|
+
const versionWidth = Math.max("VERSION".length, ...tools.map((tool) => tool.packageVersion.length));
|
|
2416
|
+
const lines = [`${"COMMAND".padEnd(commandWidth)} ${"PACKAGE".padEnd(packageWidth)} ${"VERSION".padEnd(versionWidth)} STATE`];
|
|
2417
|
+
for (const tool of tools) {
|
|
2418
|
+
lines.push(`${tool.commandPath.join(" ").padEnd(commandWidth)} ${tool.packageName.padEnd(packageWidth)} ${tool.packageVersion.padEnd(versionWidth)} ${tool.state}`);
|
|
2419
|
+
for (const problem of tool.problems) lines.push(` ${problem}`);
|
|
2420
|
+
}
|
|
2421
|
+
return lines.join("\n");
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
// src/cli/commands/tools.ts
|
|
2425
|
+
function formatError(error) {
|
|
2426
|
+
return error instanceof Error ? error.message : String(error);
|
|
2427
|
+
}
|
|
2428
|
+
function createAddToolCommand(toolManager) {
|
|
2429
|
+
return new Command9("add").description("Install or update a MoneyOS CLI tool into the user tool home").argument("<tool>", "Tool alias or npm package spec").action(async (tool) => {
|
|
2430
|
+
try {
|
|
2431
|
+
const entry = await toolManager.addTool(tool);
|
|
2432
|
+
console.log(
|
|
2433
|
+
`Installed ${entry.packageName}@${entry.packageVersion} as \`moneyos ${entry.commandPath.join(" ")}\`.`
|
|
2434
|
+
);
|
|
2435
|
+
} catch (error) {
|
|
2436
|
+
console.error(formatError(error));
|
|
2437
|
+
process.exitCode = 1;
|
|
2438
|
+
}
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2441
|
+
function createRemoveToolCommand(toolManager) {
|
|
2442
|
+
return new Command9("remove").description("Remove an installed MoneyOS CLI tool from the user tool home").argument("<tool>", "Installed tool name, command path, or npm package").action(async (tool) => {
|
|
2443
|
+
try {
|
|
2444
|
+
const entry = await toolManager.removeTool(tool);
|
|
2445
|
+
console.log(
|
|
2446
|
+
`Removed ${entry.packageName} from \`moneyos ${entry.commandPath.join(" ")}\`.`
|
|
2447
|
+
);
|
|
2448
|
+
} catch (error) {
|
|
2449
|
+
console.error(formatError(error));
|
|
2450
|
+
process.exitCode = 1;
|
|
2451
|
+
}
|
|
2452
|
+
});
|
|
2453
|
+
}
|
|
2454
|
+
function createToolsCommand(toolManager) {
|
|
2455
|
+
return new Command9("tools").description("List installed MoneyOS CLI tools from the registry").action(async () => {
|
|
2456
|
+
try {
|
|
2457
|
+
console.log(formatToolStatusTable(await toolManager.listTools()));
|
|
2458
|
+
} catch (error) {
|
|
2459
|
+
console.error(formatError(error));
|
|
2460
|
+
process.exitCode = 1;
|
|
2461
|
+
}
|
|
2462
|
+
});
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2092
2465
|
// src/cli/version.ts
|
|
2093
|
-
import { readFileSync as
|
|
2466
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
2094
2467
|
import { fileURLToPath } from "url";
|
|
2095
|
-
import { dirname as
|
|
2468
|
+
import { dirname as dirname5, resolve as resolve2 } from "path";
|
|
2096
2469
|
var packageJsonPath = resolve2(
|
|
2097
|
-
|
|
2470
|
+
dirname5(fileURLToPath(import.meta.url)),
|
|
2098
2471
|
"../../package.json"
|
|
2099
2472
|
);
|
|
2100
2473
|
var version = JSON.parse(
|
|
2101
|
-
|
|
2474
|
+
readFileSync5(packageJsonPath, "utf8")
|
|
2102
2475
|
).version;
|
|
2103
2476
|
|
|
2104
2477
|
// src/cli/index.ts
|
|
2105
|
-
function createProgram() {
|
|
2106
|
-
const program = new
|
|
2478
|
+
function createProgram(options = {}) {
|
|
2479
|
+
const program = new Command10();
|
|
2480
|
+
const toolManager = options.toolManager ?? createCliToolManager();
|
|
2107
2481
|
program.name("moneyos").description("The operating system for money").version(version);
|
|
2108
2482
|
program.addCommand(initCommand);
|
|
2109
2483
|
program.addCommand(balanceCommand);
|
|
@@ -2111,6 +2485,10 @@ function createProgram() {
|
|
|
2111
2485
|
program.addCommand(keystoreCommand);
|
|
2112
2486
|
program.addCommand(authCommand);
|
|
2113
2487
|
program.addCommand(backupCommand);
|
|
2488
|
+
program.addCommand(createAddToolCommand(toolManager));
|
|
2489
|
+
program.addCommand(createRemoveToolCommand(toolManager));
|
|
2490
|
+
program.addCommand(createToolsCommand(toolManager));
|
|
2491
|
+
toolManager.mountInstalledToolCommands(program);
|
|
2114
2492
|
program.command("__session-daemon", { hidden: true }).action(async () => {
|
|
2115
2493
|
await runSessionDaemonProcess();
|
|
2116
2494
|
});
|