@darksol/terminal 0.2.0 → 0.2.2
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 +263 -189
- package/package.json +1 -1
- package/skill/SKILL.md +177 -0
- package/src/cli.js +45 -1
- package/src/llm/intent.js +126 -0
- package/src/services/skills.js +229 -0
- package/src/ui/banner.js +4 -2
- package/tests/cli.test.js +0 -72
- package/tests/config.test.js +0 -75
- package/tests/dca.test.js +0 -141
- package/tests/keystore.test.js +0 -94
- package/tests/scripts.test.js +0 -136
- package/tests/trading.test.js +0 -21
- package/tests/ui.test.js +0 -27
package/tests/cli.test.js
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import test, { before, after } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { spawnSync } from 'node:child_process';
|
|
4
|
-
import { mkdtempSync, mkdirSync, rmSync } from 'node:fs';
|
|
5
|
-
import { tmpdir } from 'node:os';
|
|
6
|
-
import { join } from 'node:path';
|
|
7
|
-
|
|
8
|
-
function runCli(args, env) {
|
|
9
|
-
return spawnSync(process.execPath, ['bin/darksol.js', ...args], {
|
|
10
|
-
cwd: process.cwd(),
|
|
11
|
-
encoding: 'utf8',
|
|
12
|
-
env,
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
let tempRoot;
|
|
17
|
-
let env;
|
|
18
|
-
|
|
19
|
-
before(() => {
|
|
20
|
-
tempRoot = mkdtempSync(join(tmpdir(), 'darksol-cli-'));
|
|
21
|
-
const home = join(tempRoot, 'home');
|
|
22
|
-
const appData = join(tempRoot, 'appdata');
|
|
23
|
-
const localAppData = join(tempRoot, 'localappdata');
|
|
24
|
-
mkdirSync(home, { recursive: true });
|
|
25
|
-
mkdirSync(appData, { recursive: true });
|
|
26
|
-
mkdirSync(localAppData, { recursive: true });
|
|
27
|
-
|
|
28
|
-
env = {
|
|
29
|
-
...process.env,
|
|
30
|
-
HOME: home,
|
|
31
|
-
USERPROFILE: home,
|
|
32
|
-
APPDATA: appData,
|
|
33
|
-
LOCALAPPDATA: localAppData,
|
|
34
|
-
FORCE_COLOR: '0',
|
|
35
|
-
};
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
after(() => {
|
|
39
|
-
rmSync(tempRoot, { recursive: true, force: true });
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('CLI help output renders', (t) => {
|
|
43
|
-
const res = runCli(['--help'], env);
|
|
44
|
-
if (res.error && res.error.code === 'EPERM') {
|
|
45
|
-
t.skip('Child process spawn not permitted in this environment');
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
assert.equal(res.status, 0, res.stderr);
|
|
49
|
-
assert.match(res.stdout, /Usage:/);
|
|
50
|
-
assert.match(res.stdout, /\bwallet\b/);
|
|
51
|
-
assert.match(res.stdout, /\btrade\b/);
|
|
52
|
-
assert.match(res.stdout, /\bscript\b/);
|
|
53
|
-
assert.match(res.stdout, /\bconfig\b/);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test('command registration includes trade and script subcommands', (t) => {
|
|
57
|
-
const tradeHelp = runCli(['trade', '--help'], env);
|
|
58
|
-
if (tradeHelp.error && tradeHelp.error.code === 'EPERM') {
|
|
59
|
-
t.skip('Child process spawn not permitted in this environment');
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
assert.equal(tradeHelp.status, 0, tradeHelp.stderr);
|
|
63
|
-
assert.match(tradeHelp.stdout, /\bswap\b/);
|
|
64
|
-
assert.match(tradeHelp.stdout, /\bsnipe\b/);
|
|
65
|
-
assert.match(tradeHelp.stdout, /\bwatch\b/);
|
|
66
|
-
|
|
67
|
-
const scriptHelp = runCli(['script', '--help'], env);
|
|
68
|
-
assert.equal(scriptHelp.status, 0, scriptHelp.stderr);
|
|
69
|
-
assert.match(scriptHelp.stdout, /\bcreate\b/);
|
|
70
|
-
assert.match(scriptHelp.stdout, /\brun\b/);
|
|
71
|
-
assert.match(scriptHelp.stdout, /\btemplates\b/);
|
|
72
|
-
});
|
package/tests/config.test.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import test, { before, after } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { mkdtempSync, mkdirSync, rmSync } from 'node:fs';
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
5
|
-
import { join } from 'node:path';
|
|
6
|
-
|
|
7
|
-
function importFresh(relativePath) {
|
|
8
|
-
const url = new URL(`${relativePath}?t=${Date.now()}-${Math.random()}`, import.meta.url);
|
|
9
|
-
return import(url);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function setTempEnv(tempRoot) {
|
|
13
|
-
const home = join(tempRoot, 'home');
|
|
14
|
-
const appData = join(tempRoot, 'appdata');
|
|
15
|
-
const localAppData = join(tempRoot, 'localappdata');
|
|
16
|
-
mkdirSync(home, { recursive: true });
|
|
17
|
-
mkdirSync(appData, { recursive: true });
|
|
18
|
-
mkdirSync(localAppData, { recursive: true });
|
|
19
|
-
|
|
20
|
-
const prev = {
|
|
21
|
-
HOME: process.env.HOME,
|
|
22
|
-
USERPROFILE: process.env.USERPROFILE,
|
|
23
|
-
APPDATA: process.env.APPDATA,
|
|
24
|
-
LOCALAPPDATA: process.env.LOCALAPPDATA,
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
process.env.HOME = home;
|
|
28
|
-
process.env.USERPROFILE = home;
|
|
29
|
-
process.env.APPDATA = appData;
|
|
30
|
-
process.env.LOCALAPPDATA = localAppData;
|
|
31
|
-
|
|
32
|
-
return prev;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function restoreEnv(prev) {
|
|
36
|
-
for (const key of Object.keys(prev)) {
|
|
37
|
-
if (prev[key] === undefined) delete process.env[key];
|
|
38
|
-
else process.env[key] = prev[key];
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
let tempRoot;
|
|
43
|
-
let prevEnv;
|
|
44
|
-
let store;
|
|
45
|
-
|
|
46
|
-
before(async () => {
|
|
47
|
-
tempRoot = mkdtempSync(join(tmpdir(), 'darksol-config-'));
|
|
48
|
-
prevEnv = setTempEnv(tempRoot);
|
|
49
|
-
store = await importFresh('../src/config/store.js');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
after(() => {
|
|
53
|
-
restoreEnv(prevEnv);
|
|
54
|
-
rmSync(tempRoot, { recursive: true, force: true });
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('get/set config values', () => {
|
|
58
|
-
assert.equal(store.getConfig('chain'), 'base');
|
|
59
|
-
store.setConfig('chain', 'ethereum');
|
|
60
|
-
assert.equal(store.getConfig('chain'), 'ethereum');
|
|
61
|
-
const all = store.getAllConfig();
|
|
62
|
-
assert.equal(all.chain, 'ethereum');
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test('RPC management works for getRPC/setRPC', () => {
|
|
66
|
-
const defaultBaseRpc = store.getRPC('base');
|
|
67
|
-
assert.ok(typeof defaultBaseRpc === 'string' && defaultBaseRpc.startsWith('http'));
|
|
68
|
-
|
|
69
|
-
const custom = 'https://rpc.example.invalid';
|
|
70
|
-
store.setRPC('base', custom);
|
|
71
|
-
assert.equal(store.getRPC('base'), custom);
|
|
72
|
-
|
|
73
|
-
store.setConfig('chain', 'base');
|
|
74
|
-
assert.equal(store.getRPC(), custom);
|
|
75
|
-
});
|
package/tests/dca.test.js
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import test, { before, after } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { mkdtempSync, mkdirSync, rmSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
5
|
-
import { join } from 'node:path';
|
|
6
|
-
import inquirer from 'inquirer';
|
|
7
|
-
|
|
8
|
-
function importFresh(relativePath) {
|
|
9
|
-
const url = new URL(`${relativePath}?t=${Date.now()}-${Math.random()}`, import.meta.url);
|
|
10
|
-
return import(url);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function setTempEnv(tempRoot) {
|
|
14
|
-
const home = join(tempRoot, 'home');
|
|
15
|
-
const appData = join(tempRoot, 'appdata');
|
|
16
|
-
const localAppData = join(tempRoot, 'localappdata');
|
|
17
|
-
mkdirSync(home, { recursive: true });
|
|
18
|
-
mkdirSync(appData, { recursive: true });
|
|
19
|
-
mkdirSync(localAppData, { recursive: true });
|
|
20
|
-
|
|
21
|
-
const prev = {
|
|
22
|
-
HOME: process.env.HOME,
|
|
23
|
-
USERPROFILE: process.env.USERPROFILE,
|
|
24
|
-
APPDATA: process.env.APPDATA,
|
|
25
|
-
LOCALAPPDATA: process.env.LOCALAPPDATA,
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
process.env.HOME = home;
|
|
29
|
-
process.env.USERPROFILE = home;
|
|
30
|
-
process.env.APPDATA = appData;
|
|
31
|
-
process.env.LOCALAPPDATA = localAppData;
|
|
32
|
-
|
|
33
|
-
return { prev, home };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function restoreEnv(prev) {
|
|
37
|
-
for (const key of Object.keys(prev)) {
|
|
38
|
-
if (prev[key] === undefined) delete process.env[key];
|
|
39
|
-
else process.env[key] = prev[key];
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async function withPromptQueue(queue, fn) {
|
|
44
|
-
const original = inquirer.prompt;
|
|
45
|
-
let idx = 0;
|
|
46
|
-
inquirer.prompt = async () => {
|
|
47
|
-
if (idx >= queue.length) {
|
|
48
|
-
throw new Error('Prompt queue exhausted');
|
|
49
|
-
}
|
|
50
|
-
return queue[idx++];
|
|
51
|
-
};
|
|
52
|
-
try {
|
|
53
|
-
await fn();
|
|
54
|
-
} finally {
|
|
55
|
-
inquirer.prompt = original;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
let tempRoot;
|
|
60
|
-
let prevEnv;
|
|
61
|
-
let tempHome;
|
|
62
|
-
let dca;
|
|
63
|
-
let store;
|
|
64
|
-
|
|
65
|
-
before(async () => {
|
|
66
|
-
tempRoot = mkdtempSync(join(tmpdir(), 'darksol-dca-'));
|
|
67
|
-
const env = setTempEnv(tempRoot);
|
|
68
|
-
prevEnv = env.prev;
|
|
69
|
-
tempHome = env.home;
|
|
70
|
-
store = await importFresh('../src/config/store.js');
|
|
71
|
-
dca = await importFresh('../src/trading/dca.js');
|
|
72
|
-
store.setConfig('chain', 'base');
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
after(() => {
|
|
76
|
-
restoreEnv(prevEnv);
|
|
77
|
-
rmSync(tempRoot, { recursive: true, force: true });
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test('DCA create/cancel/run CRUD in temp directory', async () => {
|
|
81
|
-
await withPromptQueue(
|
|
82
|
-
[
|
|
83
|
-
{
|
|
84
|
-
tokenIn: 'ETH',
|
|
85
|
-
tokenOut: 'USDC',
|
|
86
|
-
amountPerOrder: '0.1',
|
|
87
|
-
interval: 3600,
|
|
88
|
-
totalOrders: '2',
|
|
89
|
-
},
|
|
90
|
-
{ confirm: true },
|
|
91
|
-
],
|
|
92
|
-
async () => {
|
|
93
|
-
await dca.createDCA();
|
|
94
|
-
},
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
const candidates = [
|
|
98
|
-
join(tempHome, '.darksol', 'dca', 'orders.json'),
|
|
99
|
-
join(process.env.HOME || '', '.darksol', 'dca', 'orders.json'),
|
|
100
|
-
join(process.env.USERPROFILE || '', '.darksol', 'dca', 'orders.json'),
|
|
101
|
-
];
|
|
102
|
-
const ordersPath = candidates.find((p) => p && existsSync(p));
|
|
103
|
-
assert.ok(ordersPath, 'orders.json should exist');
|
|
104
|
-
let orders = JSON.parse(readFileSync(ordersPath, 'utf8'));
|
|
105
|
-
assert.equal(orders.length, 1);
|
|
106
|
-
assert.equal(orders[0].status, 'active');
|
|
107
|
-
|
|
108
|
-
await dca.cancelDCA(orders[0].id);
|
|
109
|
-
orders = JSON.parse(readFileSync(ordersPath, 'utf8'));
|
|
110
|
-
assert.equal(orders[0].status, 'cancelled');
|
|
111
|
-
|
|
112
|
-
await withPromptQueue(
|
|
113
|
-
[
|
|
114
|
-
{
|
|
115
|
-
tokenIn: 'ETH',
|
|
116
|
-
tokenOut: 'USDC',
|
|
117
|
-
amountPerOrder: '0.2',
|
|
118
|
-
interval: 3600,
|
|
119
|
-
totalOrders: '1',
|
|
120
|
-
},
|
|
121
|
-
{ confirm: true },
|
|
122
|
-
],
|
|
123
|
-
async () => {
|
|
124
|
-
await dca.createDCA();
|
|
125
|
-
},
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
orders = JSON.parse(readFileSync(ordersPath, 'utf8'));
|
|
129
|
-
const active = orders.find((o) => o.status === 'active');
|
|
130
|
-
assert.ok(active);
|
|
131
|
-
active.nextExecution = new Date(Date.now() - 1000).toISOString();
|
|
132
|
-
writeFileSync(ordersPath, JSON.stringify(orders, null, 2));
|
|
133
|
-
|
|
134
|
-
await dca.runDCA({ password: 'unused-in-simulated-run' });
|
|
135
|
-
orders = JSON.parse(readFileSync(ordersPath, 'utf8'));
|
|
136
|
-
const ran = orders.find((o) => o.id === active.id);
|
|
137
|
-
assert.equal(ran.executedOrders, 1);
|
|
138
|
-
assert.equal(ran.status, 'completed');
|
|
139
|
-
assert.equal(ran.history.length, 1);
|
|
140
|
-
assert.equal(ran.history[0].status, 'simulated');
|
|
141
|
-
});
|
package/tests/keystore.test.js
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import test, { before, after } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { mkdtempSync, mkdirSync, rmSync } from 'node:fs';
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
5
|
-
import { join } from 'node:path';
|
|
6
|
-
|
|
7
|
-
function importFresh(relativePath) {
|
|
8
|
-
const url = new URL(`${relativePath}?t=${Date.now()}-${Math.random()}`, import.meta.url);
|
|
9
|
-
return import(url);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function setTempEnv(tempRoot) {
|
|
13
|
-
const home = join(tempRoot, 'home');
|
|
14
|
-
const appData = join(tempRoot, 'appdata');
|
|
15
|
-
const localAppData = join(tempRoot, 'localappdata');
|
|
16
|
-
mkdirSync(home, { recursive: true });
|
|
17
|
-
mkdirSync(appData, { recursive: true });
|
|
18
|
-
mkdirSync(localAppData, { recursive: true });
|
|
19
|
-
|
|
20
|
-
const prev = {
|
|
21
|
-
HOME: process.env.HOME,
|
|
22
|
-
USERPROFILE: process.env.USERPROFILE,
|
|
23
|
-
APPDATA: process.env.APPDATA,
|
|
24
|
-
LOCALAPPDATA: process.env.LOCALAPPDATA,
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
process.env.HOME = home;
|
|
28
|
-
process.env.USERPROFILE = home;
|
|
29
|
-
process.env.APPDATA = appData;
|
|
30
|
-
process.env.LOCALAPPDATA = localAppData;
|
|
31
|
-
|
|
32
|
-
return { prev, home };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function restoreEnv(prev) {
|
|
36
|
-
for (const key of Object.keys(prev)) {
|
|
37
|
-
if (prev[key] === undefined) delete process.env[key];
|
|
38
|
-
else process.env[key] = prev[key];
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
let tempRoot;
|
|
43
|
-
let prevEnv;
|
|
44
|
-
let keystore;
|
|
45
|
-
|
|
46
|
-
before(async () => {
|
|
47
|
-
tempRoot = mkdtempSync(join(tmpdir(), 'darksol-keystore-'));
|
|
48
|
-
const env = setTempEnv(tempRoot);
|
|
49
|
-
prevEnv = env.prev;
|
|
50
|
-
keystore = await importFresh('../src/wallet/keystore.js');
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
after(() => {
|
|
54
|
-
restoreEnv(prevEnv);
|
|
55
|
-
rmSync(tempRoot, { recursive: true, force: true });
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('encrypt/decrypt roundtrip', () => {
|
|
59
|
-
const privateKey = '0x' + '11'.repeat(32);
|
|
60
|
-
const password = 'strong-password-123';
|
|
61
|
-
const encrypted = keystore.encryptKey(privateKey, password);
|
|
62
|
-
const decrypted = keystore.decryptKey(encrypted, password);
|
|
63
|
-
assert.equal(decrypted, privateKey);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test('decrypt with wrong password throws', () => {
|
|
67
|
-
const encrypted = keystore.encryptKey('0x' + '22'.repeat(32), 'correct-password');
|
|
68
|
-
assert.throws(() => keystore.decryptKey(encrypted, 'wrong-password'));
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('wallet CRUD in temp home directory', () => {
|
|
72
|
-
const name = 'alice';
|
|
73
|
-
const address = '0x1234567890abcdef1234567890abcdef12345678';
|
|
74
|
-
const encrypted = keystore.encryptKey('0x' + '33'.repeat(32), 'wallet-pass');
|
|
75
|
-
|
|
76
|
-
const walletFile = keystore.saveWallet(name, address, encrypted, { chain: 'base' });
|
|
77
|
-
assert.ok(walletFile.startsWith(keystore.WALLET_DIR));
|
|
78
|
-
assert.equal(keystore.walletExists(name), true);
|
|
79
|
-
|
|
80
|
-
const loaded = keystore.loadWallet(name);
|
|
81
|
-
assert.equal(loaded.name, name);
|
|
82
|
-
assert.equal(loaded.address, address);
|
|
83
|
-
assert.equal(loaded.chain, 'base');
|
|
84
|
-
assert.ok(loaded.createdAt);
|
|
85
|
-
|
|
86
|
-
const wallets = keystore.listWallets();
|
|
87
|
-
assert.equal(wallets.length, 1);
|
|
88
|
-
assert.equal(wallets[0].name, name);
|
|
89
|
-
assert.equal(wallets[0].address, address);
|
|
90
|
-
|
|
91
|
-
keystore.deleteWallet(name);
|
|
92
|
-
assert.equal(keystore.walletExists(name), false);
|
|
93
|
-
assert.throws(() => keystore.loadWallet(name), /not found/i);
|
|
94
|
-
});
|
package/tests/scripts.test.js
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import test, { before, after } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { mkdtempSync, mkdirSync, rmSync, readFileSync, existsSync } from 'node:fs';
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
5
|
-
import { join } from 'node:path';
|
|
6
|
-
import inquirer from 'inquirer';
|
|
7
|
-
|
|
8
|
-
function importFresh(relativePath) {
|
|
9
|
-
const url = new URL(`${relativePath}?t=${Date.now()}-${Math.random()}`, import.meta.url);
|
|
10
|
-
return import(url);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function setTempEnv(tempRoot) {
|
|
14
|
-
const home = join(tempRoot, 'home');
|
|
15
|
-
const appData = join(tempRoot, 'appdata');
|
|
16
|
-
const localAppData = join(tempRoot, 'localappdata');
|
|
17
|
-
mkdirSync(home, { recursive: true });
|
|
18
|
-
mkdirSync(appData, { recursive: true });
|
|
19
|
-
mkdirSync(localAppData, { recursive: true });
|
|
20
|
-
|
|
21
|
-
const prev = {
|
|
22
|
-
HOME: process.env.HOME,
|
|
23
|
-
USERPROFILE: process.env.USERPROFILE,
|
|
24
|
-
APPDATA: process.env.APPDATA,
|
|
25
|
-
LOCALAPPDATA: process.env.LOCALAPPDATA,
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
process.env.HOME = home;
|
|
29
|
-
process.env.USERPROFILE = home;
|
|
30
|
-
process.env.APPDATA = appData;
|
|
31
|
-
process.env.LOCALAPPDATA = localAppData;
|
|
32
|
-
|
|
33
|
-
return { prev, home };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function restoreEnv(prev) {
|
|
37
|
-
for (const key of Object.keys(prev)) {
|
|
38
|
-
if (prev[key] === undefined) delete process.env[key];
|
|
39
|
-
else process.env[key] = prev[key];
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async function withPromptQueue(queue, fn) {
|
|
44
|
-
const original = inquirer.prompt;
|
|
45
|
-
let idx = 0;
|
|
46
|
-
inquirer.prompt = async () => {
|
|
47
|
-
if (idx >= queue.length) {
|
|
48
|
-
throw new Error('Prompt queue exhausted');
|
|
49
|
-
}
|
|
50
|
-
return queue[idx++];
|
|
51
|
-
};
|
|
52
|
-
try {
|
|
53
|
-
await fn();
|
|
54
|
-
} finally {
|
|
55
|
-
inquirer.prompt = original;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
let tempRoot;
|
|
60
|
-
let prevEnv;
|
|
61
|
-
let tempHome;
|
|
62
|
-
let engine;
|
|
63
|
-
let store;
|
|
64
|
-
|
|
65
|
-
before(async () => {
|
|
66
|
-
tempRoot = mkdtempSync(join(tmpdir(), 'darksol-scripts-'));
|
|
67
|
-
const env = setTempEnv(tempRoot);
|
|
68
|
-
prevEnv = env.prev;
|
|
69
|
-
tempHome = env.home;
|
|
70
|
-
store = await importFresh('../src/config/store.js');
|
|
71
|
-
engine = await importFresh('../src/scripts/engine.js');
|
|
72
|
-
store.setConfig('activeWallet', 'test-wallet');
|
|
73
|
-
store.setConfig('chain', 'base');
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
after(() => {
|
|
77
|
-
restoreEnv(prevEnv);
|
|
78
|
-
rmSync(tempRoot, { recursive: true, force: true });
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test('script templates have expected structure', () => {
|
|
82
|
-
for (const [key, tmpl] of Object.entries(engine.TEMPLATES)) {
|
|
83
|
-
assert.ok(key.length > 0);
|
|
84
|
-
assert.equal(typeof tmpl.name, 'string');
|
|
85
|
-
assert.equal(typeof tmpl.description, 'string');
|
|
86
|
-
assert.ok(Array.isArray(tmpl.params));
|
|
87
|
-
assert.equal(typeof tmpl.template, 'string');
|
|
88
|
-
assert.match(tmpl.template, /module\.exports\s*=\s*async function/);
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test('script CRUD works in temp directory', async () => {
|
|
93
|
-
await withPromptQueue(
|
|
94
|
-
[
|
|
95
|
-
{ templateKey: 'empty' },
|
|
96
|
-
{ scriptName: 'alpha' },
|
|
97
|
-
{ walletName: 'test-wallet' },
|
|
98
|
-
],
|
|
99
|
-
async () => {
|
|
100
|
-
await engine.createScript();
|
|
101
|
-
},
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
const scriptsDir = join(tempHome, '.darksol', 'scripts');
|
|
105
|
-
const alphaPath = join(scriptsDir, 'alpha.json');
|
|
106
|
-
assert.equal(existsSync(alphaPath), true);
|
|
107
|
-
|
|
108
|
-
const alpha = JSON.parse(readFileSync(alphaPath, 'utf8'));
|
|
109
|
-
assert.equal(alpha.name, 'alpha');
|
|
110
|
-
assert.equal(alpha.template, 'empty');
|
|
111
|
-
assert.equal(alpha.wallet, 'test-wallet');
|
|
112
|
-
assert.equal(alpha.chain, 'base');
|
|
113
|
-
assert.deepEqual(alpha.params, {});
|
|
114
|
-
|
|
115
|
-
await engine.cloneScript('alpha', 'beta');
|
|
116
|
-
const betaPath = join(scriptsDir, 'beta.json');
|
|
117
|
-
assert.equal(existsSync(betaPath), true);
|
|
118
|
-
|
|
119
|
-
await withPromptQueue(
|
|
120
|
-
[
|
|
121
|
-
{ what: 'description' },
|
|
122
|
-
{ desc: 'updated description' },
|
|
123
|
-
],
|
|
124
|
-
async () => {
|
|
125
|
-
await engine.editScript('alpha');
|
|
126
|
-
},
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
const edited = JSON.parse(readFileSync(alphaPath, 'utf8'));
|
|
130
|
-
assert.equal(edited.description, 'updated description');
|
|
131
|
-
|
|
132
|
-
await withPromptQueue([{ confirm: true }], async () => {
|
|
133
|
-
await engine.deleteScript('beta');
|
|
134
|
-
});
|
|
135
|
-
assert.equal(existsSync(betaPath), false);
|
|
136
|
-
});
|
package/tests/trading.test.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import test from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { ethers } from 'ethers';
|
|
4
|
-
import { resolveToken } from '../src/trading/swap.js';
|
|
5
|
-
|
|
6
|
-
test('resolveToken resolves known symbols', () => {
|
|
7
|
-
assert.equal(resolveToken('ETH', 'base'), ethers.ZeroAddress);
|
|
8
|
-
assert.equal(resolveToken('usdc', 'base'), '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913');
|
|
9
|
-
assert.equal(resolveToken('WETH', 'ethereum'), '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2');
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
test('resolveToken accepts direct token addresses', () => {
|
|
13
|
-
const address = '0x1234567890abcdef1234567890abcdef12345678';
|
|
14
|
-
assert.equal(resolveToken(address, 'base'), address);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
test('resolveToken handles unknown symbols and chain fallback', () => {
|
|
18
|
-
assert.equal(resolveToken('NOT_A_TOKEN', 'base'), null);
|
|
19
|
-
assert.equal(resolveToken('USDC', 'unknown-chain'), '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913');
|
|
20
|
-
assert.equal(resolveToken('0xabc', 'base'), null);
|
|
21
|
-
});
|
package/tests/ui.test.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import test from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { formatPrice, formatChange, formatAddress } from '../src/ui/components.js';
|
|
4
|
-
|
|
5
|
-
function stripAnsi(value) {
|
|
6
|
-
return String(value).replace(/\x1B\[[0-9;]*m/g, '');
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
test('formatPrice formats values and handles invalid inputs', () => {
|
|
10
|
-
assert.equal(formatPrice(12.3456), '$12.35');
|
|
11
|
-
assert.equal(formatPrice(0.001234), '$0.001234');
|
|
12
|
-
assert.equal(stripAnsi(formatPrice('invalid')), 'N/A');
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
test('formatChange formats positive/negative/zero values', () => {
|
|
16
|
-
assert.equal(stripAnsi(formatChange(2.3456)), '+2.35%');
|
|
17
|
-
assert.equal(stripAnsi(formatChange(-2.3456)), '-2.35%');
|
|
18
|
-
assert.equal(stripAnsi(formatChange(0)), '+0.00%');
|
|
19
|
-
assert.equal(stripAnsi(formatChange('x')), 'N/A');
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test('formatAddress shortens addresses predictably', () => {
|
|
23
|
-
const addr = '0x1234567890abcdef1234567890abcdef12345678';
|
|
24
|
-
assert.equal(formatAddress(addr), '0x1234...5678');
|
|
25
|
-
assert.equal(formatAddress(addr, 8), '0x123456...5678');
|
|
26
|
-
assert.equal(stripAnsi(formatAddress('')), 'N/A');
|
|
27
|
-
});
|