satoridb 1.2.6 → 1.2.7
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/cli.js +559 -134
- package/package.json +1 -1
- package/postinstall.js +20 -2
package/cli.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
/**
|
|
4
|
-
* Satori Interactive CLI
|
|
5
|
-
* - Command registry (easy to extend)
|
|
6
|
-
* - Robust arg parsing (quotes, escapes)
|
|
7
|
-
* - Fixed bugs (lecture args check, set_mindspace double call)
|
|
8
|
-
* - Better UX (history, tab completion, prompt context)
|
|
9
|
-
* - Centralized error handling
|
|
4
|
+
* Satori Interactive CLI
|
|
10
5
|
*/
|
|
11
6
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
7
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
@@ -18,11 +13,28 @@ const os_1 = __importDefault(require("os"));
|
|
|
18
13
|
const fs_1 = __importDefault(require("fs"));
|
|
19
14
|
const child_process_1 = require("child_process");
|
|
20
15
|
const readline_1 = __importDefault(require("readline"));
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
// ── ANSI helpers ───────────────────────────────────────────────────────────────
|
|
17
|
+
const c = {
|
|
18
|
+
reset: '\x1b[0m',
|
|
19
|
+
dim: '\x1b[2m',
|
|
20
|
+
bold: '\x1b[1m',
|
|
21
|
+
cyan: '\x1b[96m',
|
|
22
|
+
green: '\x1b[92m',
|
|
23
|
+
yellow: '\x1b[93m',
|
|
24
|
+
red: '\x1b[91m',
|
|
25
|
+
blue: '\x1b[94m',
|
|
26
|
+
magenta: '\x1b[95m',
|
|
27
|
+
};
|
|
28
|
+
// ── State ──────────────────────────────────────────────────────────────────────
|
|
29
|
+
let VERSION = '1.2.6';
|
|
30
|
+
try {
|
|
31
|
+
VERSION = require('./package.json').version;
|
|
32
|
+
}
|
|
33
|
+
catch { }
|
|
34
|
+
const binName = os_1.default.platform() === 'win32' ? 'satori.exe' : 'satori';
|
|
35
|
+
const binPath = path_1.default.join(os_1.default.homedir(), '.satori', 'bin', binName);
|
|
36
|
+
const historyDir = path_1.default.join(os_1.default.homedir(), '.satori');
|
|
37
|
+
const historyFile = path_1.default.join(historyDir, '.cli_history');
|
|
26
38
|
let satori = null;
|
|
27
39
|
let session = {
|
|
28
40
|
host: null,
|
|
@@ -30,21 +42,75 @@ let session = {
|
|
|
30
42
|
password: null,
|
|
31
43
|
mindspace: null,
|
|
32
44
|
};
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
45
|
+
// ── Print helpers ──────────────────────────────────────────────────────────────
|
|
46
|
+
const ok = (msg) => console.log(` ${c.green}✓${c.reset} ${msg}`);
|
|
47
|
+
const fail = (msg) => console.error(` ${c.red}✗${c.reset} ${msg}`);
|
|
48
|
+
const info = (msg) => console.log(` ${c.dim}${msg}${c.reset}`);
|
|
49
|
+
function colorJson(obj) {
|
|
50
|
+
if (obj === null || obj === undefined)
|
|
51
|
+
return `${c.dim}null${c.reset}`;
|
|
52
|
+
if (typeof obj === 'string')
|
|
53
|
+
return obj;
|
|
54
|
+
if (typeof obj === 'number')
|
|
55
|
+
return `${c.yellow}${obj}${c.reset}`;
|
|
56
|
+
if (typeof obj === 'boolean')
|
|
57
|
+
return `${c.magenta}${obj}${c.reset}`;
|
|
58
|
+
return JSON.stringify(obj, null, 2)
|
|
59
|
+
.replace(/"([^"]+)":/g, `${c.cyan}"$1"${c.reset}:`)
|
|
60
|
+
.replace(/: "([^"]*)"/g, `: ${c.green}"$1"${c.reset}`)
|
|
61
|
+
.replace(/: (-?\d+(?:\.\d+)?)/g, `: ${c.yellow}$1${c.reset}`)
|
|
62
|
+
.replace(/: (true|false)/g, `: ${c.magenta}$1${c.reset}`)
|
|
63
|
+
.replace(/: null/g, `: ${c.dim}null${c.reset}`);
|
|
64
|
+
}
|
|
65
|
+
function print(obj) {
|
|
66
|
+
if (obj === null || obj === undefined)
|
|
67
|
+
return;
|
|
68
|
+
const s = colorJson(obj);
|
|
69
|
+
console.log('\n' + s.split('\n').map(l => ` ${l}`).join('\n') + '\n');
|
|
70
|
+
}
|
|
71
|
+
function spinner(text) {
|
|
72
|
+
if (!process.stdout.isTTY)
|
|
73
|
+
return () => { };
|
|
74
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
75
|
+
let i = 0;
|
|
76
|
+
const id = setInterval(() => {
|
|
77
|
+
process.stdout.write(`\r ${c.dim}${frames[i++ % frames.length]}${c.reset} ${c.dim}${text}${c.reset} `);
|
|
78
|
+
}, 80);
|
|
79
|
+
return () => {
|
|
80
|
+
clearInterval(id);
|
|
81
|
+
process.stdout.write('\r' + ' '.repeat(text.length + 10) + '\r');
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// ── Banner ─────────────────────────────────────────────────────────────────────
|
|
85
|
+
function showBanner() {
|
|
86
|
+
const logo = [
|
|
87
|
+
` ___ _ _ ____ ___ ____ ____`,
|
|
88
|
+
`/ __|| | |/ _ |/ || _ \\|_ _|`,
|
|
89
|
+
`\\__ \\| | |\\__ | / \\ || / | | `,
|
|
90
|
+
`|___/|___|/____||_| |_||_|\\_\\ |_| `,
|
|
91
|
+
];
|
|
92
|
+
console.log();
|
|
93
|
+
logo.forEach(l => console.log(` ${c.cyan}${c.bold}${l}${c.reset}`));
|
|
94
|
+
console.log();
|
|
95
|
+
console.log(` ${c.dim}AI-Native Distributed Object Database · v${VERSION}${c.reset}`);
|
|
96
|
+
console.log(` ${c.dim}Type ${c.reset}${c.cyan}help${c.reset}` +
|
|
97
|
+
`${c.dim} for commands · ${c.reset}${c.cyan}connect <host>${c.reset}${c.dim} to begin${c.reset}`);
|
|
98
|
+
console.log();
|
|
99
|
+
}
|
|
100
|
+
// ── Utilities ──────────────────────────────────────────────────────────────────
|
|
36
101
|
function assertInstalled() {
|
|
37
102
|
if (!fs_1.default.existsSync(binPath)) {
|
|
38
|
-
|
|
103
|
+
fail('Satori binary not found. Run: npm install -g satoridb');
|
|
39
104
|
process.exit(1);
|
|
40
105
|
}
|
|
41
106
|
}
|
|
42
107
|
function runBinary(args) {
|
|
43
108
|
assertInstalled();
|
|
44
|
-
const r = (0, child_process_1.spawnSync)(binPath, args, { stdio:
|
|
109
|
+
const r = (0, child_process_1.spawnSync)(binPath, args, { stdio: 'inherit' });
|
|
45
110
|
if (r.error)
|
|
46
111
|
throw r.error;
|
|
47
112
|
}
|
|
113
|
+
/** Tokenises a shell-style line respecting single/double quotes and backslash escapes. */
|
|
48
114
|
function parseArgs(line) {
|
|
49
115
|
const re = /"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|(\S+)/g;
|
|
50
116
|
const out = [];
|
|
@@ -53,17 +119,18 @@ function parseArgs(line) {
|
|
|
53
119
|
out.push(m[1] ?? m[2] ?? m[3]);
|
|
54
120
|
return out.map(v => v.replace(/\\"/g, '"').replace(/\\'/g, "'"));
|
|
55
121
|
}
|
|
122
|
+
/** Auto-casts string tokens to bool / number / JSON when unambiguous. */
|
|
56
123
|
function parseData(v) {
|
|
57
|
-
if (typeof v !==
|
|
124
|
+
if (typeof v !== 'string')
|
|
58
125
|
return v;
|
|
59
126
|
const t = v.trim();
|
|
60
|
-
if (t ===
|
|
127
|
+
if (t === 'true')
|
|
61
128
|
return true;
|
|
62
|
-
if (t ===
|
|
129
|
+
if (t === 'false')
|
|
63
130
|
return false;
|
|
64
|
-
if (!isNaN(Number(t))
|
|
131
|
+
if (t !== '' && !isNaN(Number(t)))
|
|
65
132
|
return Number(t);
|
|
66
|
-
if (t.startsWith(
|
|
133
|
+
if (t.startsWith('{') || t.startsWith('[')) {
|
|
67
134
|
try {
|
|
68
135
|
return JSON.parse(t);
|
|
69
136
|
}
|
|
@@ -73,8 +140,17 @@ function parseData(v) {
|
|
|
73
140
|
}
|
|
74
141
|
function requireConn() {
|
|
75
142
|
if (!satori)
|
|
76
|
-
throw new Error(
|
|
143
|
+
throw new Error('Not connected — use: connect <host>');
|
|
77
144
|
}
|
|
145
|
+
function requireMindspace() {
|
|
146
|
+
requireConn();
|
|
147
|
+
if (!session.mindspace) {
|
|
148
|
+
throw new Error('No mindspace selected — use: mindspace select <id>');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Returns `any` so the extra auth fields don't clash with typed SDK payloads.
|
|
152
|
+
// The satori-node already injects username/password via its constructor, but
|
|
153
|
+
// some server builds also accept them in the command body — keep for compat.
|
|
78
154
|
function withCreds(p = {}) {
|
|
79
155
|
if (session.user)
|
|
80
156
|
p.user = session.user;
|
|
@@ -82,205 +158,554 @@ function withCreds(p = {}) {
|
|
|
82
158
|
p.password = session.password;
|
|
83
159
|
return p;
|
|
84
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Plain-text prompt — ANSI codes inside the prompt string break readline's
|
|
163
|
+
* cursor-position tracking, so keep it clean.
|
|
164
|
+
*/
|
|
85
165
|
function promptLabel() {
|
|
86
|
-
|
|
166
|
+
const host = session.host
|
|
167
|
+
? session.host.replace(/^wss?:\/\//, '').replace(/:\d+$/, '')
|
|
168
|
+
: null;
|
|
169
|
+
const ms = session.mindspace ? `[${session.mindspace}]` : '';
|
|
170
|
+
return host ? `${host}${ms} › ` : 'satori › ';
|
|
171
|
+
}
|
|
172
|
+
// ── History ────────────────────────────────────────────────────────────────────
|
|
173
|
+
function loadHistory() {
|
|
174
|
+
try {
|
|
175
|
+
if (fs_1.default.existsSync(historyFile)) {
|
|
176
|
+
return fs_1.default.readFileSync(historyFile, 'utf8')
|
|
177
|
+
.split('\n').filter(Boolean).reverse().slice(0, 500);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch { }
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
function saveHistory(history) {
|
|
184
|
+
try {
|
|
185
|
+
fs_1.default.mkdirSync(historyDir, { recursive: true });
|
|
186
|
+
fs_1.default.writeFileSync(historyFile, [...history].reverse().join('\n') + '\n');
|
|
187
|
+
}
|
|
188
|
+
catch { }
|
|
87
189
|
}
|
|
88
|
-
// -------------------------------
|
|
89
|
-
// Commands Registry
|
|
90
|
-
// -------------------------------
|
|
91
190
|
const commands = {
|
|
92
|
-
|
|
93
|
-
desc: "Show help",
|
|
94
|
-
run() { showHelp(); }
|
|
95
|
-
},
|
|
96
|
-
exit: { run() { process.exit(0); } },
|
|
97
|
-
quit: { run() { process.exit(0); } },
|
|
98
|
-
clear: { run() { console.clear(); } },
|
|
191
|
+
// ── Connection ───────────────────────────────────────────────────────────────
|
|
99
192
|
connect: {
|
|
100
|
-
|
|
101
|
-
|
|
193
|
+
group: 'Connection', noConn: true,
|
|
194
|
+
desc: 'connect <host> [user] [password]',
|
|
195
|
+
async run([host, user, password]) {
|
|
102
196
|
if (!host)
|
|
103
|
-
throw new Error(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
satori =
|
|
197
|
+
throw new Error('Usage: connect <host> [user] [password]');
|
|
198
|
+
const stop = spinner(`Connecting to ${host}`);
|
|
199
|
+
try {
|
|
200
|
+
satori = (user && password)
|
|
201
|
+
? new satori_node_1.Satori({ host, username: user, password })
|
|
202
|
+
: new satori_node_1.Satori({ host, username: '', password: '' });
|
|
203
|
+
await satori.connect();
|
|
204
|
+
stop();
|
|
205
|
+
Object.assign(session, { host, user: user ?? null, password: password ?? null });
|
|
206
|
+
ok(`Connected ${c.dim}${host}${c.reset}`);
|
|
107
207
|
}
|
|
108
|
-
|
|
109
|
-
|
|
208
|
+
catch (e) {
|
|
209
|
+
stop();
|
|
210
|
+
satori = null;
|
|
211
|
+
throw new Error(`Connection failed: ${e.message ?? e}`);
|
|
110
212
|
}
|
|
111
|
-
await satori.connect();
|
|
112
|
-
Object.assign(session, { host, user, password });
|
|
113
|
-
console.log("✅ Connected");
|
|
114
213
|
}
|
|
115
214
|
},
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
215
|
+
disconnect: {
|
|
216
|
+
group: 'Connection',
|
|
217
|
+
desc: 'Disconnect from server',
|
|
218
|
+
run() {
|
|
219
|
+
requireConn();
|
|
220
|
+
const host = session.host;
|
|
221
|
+
satori = null;
|
|
222
|
+
Object.assign(session, { host: null, user: null, password: null, mindspace: null });
|
|
223
|
+
ok(`Disconnected from ${host}`);
|
|
123
224
|
}
|
|
124
225
|
},
|
|
226
|
+
status: {
|
|
227
|
+
group: 'Connection', noConn: true,
|
|
228
|
+
desc: 'Show connection info',
|
|
229
|
+
run() {
|
|
230
|
+
if (!satori) {
|
|
231
|
+
info('Not connected');
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
console.log();
|
|
235
|
+
console.log(` ${c.dim}Host ${c.reset}${c.cyan}${session.host}${c.reset}`);
|
|
236
|
+
if (session.user)
|
|
237
|
+
console.log(` ${c.dim}User ${c.reset}${session.user}`);
|
|
238
|
+
if (session.mindspace)
|
|
239
|
+
console.log(` ${c.dim}Mindspace ${c.reset}${c.green}${session.mindspace}${c.reset}`);
|
|
240
|
+
console.log(` ${c.dim}Status ${c.reset}${c.green}● Connected${c.reset}`);
|
|
241
|
+
console.log();
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
// ── Data ─────────────────────────────────────────────────────────────────────
|
|
125
245
|
set: {
|
|
126
|
-
|
|
246
|
+
group: 'Data',
|
|
247
|
+
desc: 'set <key> <data>',
|
|
127
248
|
async run([key, ...rest]) {
|
|
128
249
|
requireConn();
|
|
129
250
|
if (!key || !rest.length)
|
|
130
|
-
throw new Error(
|
|
131
|
-
const
|
|
132
|
-
await satori.set(withCreds({ key, data }));
|
|
133
|
-
|
|
251
|
+
throw new Error('Usage: set <key> <data>');
|
|
252
|
+
const stop = spinner('Writing...');
|
|
253
|
+
await satori.set(withCreds({ key, data: parseData(rest.join(' ')) }));
|
|
254
|
+
stop();
|
|
255
|
+
ok(`Saved ${c.cyan}${key}${c.reset}`);
|
|
134
256
|
}
|
|
135
257
|
},
|
|
136
258
|
get: {
|
|
137
|
-
|
|
259
|
+
group: 'Data',
|
|
260
|
+
desc: 'get [key]',
|
|
138
261
|
async run([key]) {
|
|
139
262
|
requireConn();
|
|
263
|
+
const stop = spinner('Fetching...');
|
|
140
264
|
const r = await satori.get(withCreds(key ? { key } : {}));
|
|
141
|
-
|
|
265
|
+
stop();
|
|
266
|
+
print(r);
|
|
142
267
|
}
|
|
143
268
|
},
|
|
144
269
|
put: {
|
|
145
|
-
|
|
146
|
-
|
|
270
|
+
group: 'Data',
|
|
271
|
+
desc: 'put <key> <field> <value>',
|
|
272
|
+
async run([key, field, ...rest]) {
|
|
147
273
|
requireConn();
|
|
148
|
-
if (!key || !field)
|
|
149
|
-
throw new Error(
|
|
150
|
-
|
|
151
|
-
|
|
274
|
+
if (!key || !field || !rest.length)
|
|
275
|
+
throw new Error('Usage: put <key> <field> <value>');
|
|
276
|
+
const stop = spinner('Updating...');
|
|
277
|
+
await satori.put(withCreds({
|
|
278
|
+
key,
|
|
279
|
+
replace_field: field,
|
|
280
|
+
replace_value: parseData(rest.join(' ')),
|
|
281
|
+
}));
|
|
282
|
+
stop();
|
|
283
|
+
ok(`Updated ${c.cyan}${key}${c.reset}.${field}`);
|
|
152
284
|
}
|
|
153
285
|
},
|
|
154
286
|
delete: {
|
|
155
|
-
|
|
287
|
+
group: 'Data',
|
|
288
|
+
desc: 'delete <key>',
|
|
156
289
|
async run([key]) {
|
|
157
290
|
requireConn();
|
|
158
291
|
if (!key)
|
|
159
|
-
throw new Error(
|
|
292
|
+
throw new Error('Usage: delete <key>');
|
|
293
|
+
const stop = spinner('Deleting...');
|
|
160
294
|
await satori.delete(withCreds({ key }));
|
|
161
|
-
|
|
295
|
+
stop();
|
|
296
|
+
ok(`Deleted ${c.cyan}${key}${c.reset}`);
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
push: {
|
|
300
|
+
group: 'Data',
|
|
301
|
+
desc: 'push <key> <array> <value> – append to array field',
|
|
302
|
+
async run([key, array, ...rest]) {
|
|
303
|
+
requireConn();
|
|
304
|
+
if (!key || !array || !rest.length)
|
|
305
|
+
throw new Error('Usage: push <key> <array> <value>');
|
|
306
|
+
const stop = spinner('Pushing...');
|
|
307
|
+
const r = await satori.push(withCreds({ key, array, value: parseData(rest.join(' ')) }));
|
|
308
|
+
stop();
|
|
309
|
+
print(r);
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
pop: {
|
|
313
|
+
group: 'Data',
|
|
314
|
+
desc: 'pop <key> <array> – remove last array element',
|
|
315
|
+
async run([key, array]) {
|
|
316
|
+
requireConn();
|
|
317
|
+
if (!key || !array)
|
|
318
|
+
throw new Error('Usage: pop <key> <array>');
|
|
319
|
+
const stop = spinner('Popping...');
|
|
320
|
+
const r = await satori.pop(withCreds({ key, array }));
|
|
321
|
+
stop();
|
|
322
|
+
print(r);
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
splice: {
|
|
326
|
+
group: 'Data',
|
|
327
|
+
desc: 'splice <key> <array> – remove first array element',
|
|
328
|
+
async run([key, array]) {
|
|
329
|
+
requireConn();
|
|
330
|
+
if (!key || !array)
|
|
331
|
+
throw new Error('Usage: splice <key> <array>');
|
|
332
|
+
const stop = spinner('Splicing...');
|
|
333
|
+
const r = await satori.splice(withCreds({ key, array }));
|
|
334
|
+
stop();
|
|
335
|
+
print(r);
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
remove: {
|
|
339
|
+
group: 'Data',
|
|
340
|
+
desc: 'remove <key> <array> <value> – remove specific value from array',
|
|
341
|
+
async run([key, array, ...rest]) {
|
|
342
|
+
requireConn();
|
|
343
|
+
if (!key || !array || !rest.length)
|
|
344
|
+
throw new Error('Usage: remove <key> <array> <value>');
|
|
345
|
+
const stop = spinner('Removing...');
|
|
346
|
+
const r = await satori.remove(withCreds({ key, array, value: parseData(rest.join(' ')) }));
|
|
347
|
+
stop();
|
|
348
|
+
print(r);
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
// ── Graph ─────────────────────────────────────────────────────────────────────
|
|
352
|
+
'set-vertex': {
|
|
353
|
+
group: 'Graph',
|
|
354
|
+
desc: 'set-vertex <from> <to> [weight]',
|
|
355
|
+
async run([from, to, weight]) {
|
|
356
|
+
requireConn();
|
|
357
|
+
if (!from || !to)
|
|
358
|
+
throw new Error('Usage: set-vertex <from> <to> [weight]');
|
|
359
|
+
const stop = spinner('Creating edge...');
|
|
360
|
+
await satori.setVertex(withCreds({
|
|
361
|
+
key: from, vertex: to,
|
|
362
|
+
weight: weight !== undefined ? Number(weight) : 1,
|
|
363
|
+
}));
|
|
364
|
+
stop();
|
|
365
|
+
ok(`Edge ${c.cyan}${from}${c.reset} → ${c.cyan}${to}${c.reset}`);
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
'get-vertex': {
|
|
369
|
+
group: 'Graph',
|
|
370
|
+
desc: 'get-vertex <key>',
|
|
371
|
+
async run([key]) {
|
|
372
|
+
requireConn();
|
|
373
|
+
if (!key)
|
|
374
|
+
throw new Error('Usage: get-vertex <key>');
|
|
375
|
+
const stop = spinner('Fetching edges...');
|
|
376
|
+
const r = await satori.getVertex(withCreds({ key }));
|
|
377
|
+
stop();
|
|
378
|
+
print(r);
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
'delete-vertex': {
|
|
382
|
+
group: 'Graph',
|
|
383
|
+
desc: 'delete-vertex <from> <to>',
|
|
384
|
+
async run([from, to]) {
|
|
385
|
+
requireConn();
|
|
386
|
+
if (!from || !to)
|
|
387
|
+
throw new Error('Usage: delete-vertex <from> <to>');
|
|
388
|
+
const stop = spinner('Removing edge...');
|
|
389
|
+
await satori.deleteVertex(withCreds({ key: from, vertex: to }));
|
|
390
|
+
stop();
|
|
391
|
+
ok(`Edge removed ${c.cyan}${from}${c.reset} → ${c.cyan}${to}${c.reset}`);
|
|
162
392
|
}
|
|
163
393
|
},
|
|
394
|
+
dfs: {
|
|
395
|
+
group: 'Graph',
|
|
396
|
+
desc: 'dfs <node> – depth-first traversal',
|
|
397
|
+
async run([node]) {
|
|
398
|
+
requireConn();
|
|
399
|
+
if (!node)
|
|
400
|
+
throw new Error('Usage: dfs <node>');
|
|
401
|
+
const stop = spinner('Traversing (DFS)...');
|
|
402
|
+
const r = await satori.dfs(withCreds({ node }));
|
|
403
|
+
stop();
|
|
404
|
+
print(r);
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
bfs: {
|
|
408
|
+
group: 'Graph',
|
|
409
|
+
desc: 'bfs <node> – breadth-first traversal',
|
|
410
|
+
async run([node]) {
|
|
411
|
+
requireConn();
|
|
412
|
+
if (!node)
|
|
413
|
+
throw new Error('Usage: bfs <node>');
|
|
414
|
+
const stop = spinner('Traversing (BFS)...');
|
|
415
|
+
const r = await satori.graphBfs(withCreds({ node }));
|
|
416
|
+
stop();
|
|
417
|
+
print(r);
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
'shortest-path': {
|
|
421
|
+
group: 'Graph',
|
|
422
|
+
desc: 'shortest-path <node> <target>',
|
|
423
|
+
async run([node, target]) {
|
|
424
|
+
requireConn();
|
|
425
|
+
if (!node || !target)
|
|
426
|
+
throw new Error('Usage: shortest-path <node> <target>');
|
|
427
|
+
const stop = spinner('Computing...');
|
|
428
|
+
const r = await satori.graphShortestPath(withCreds({ node, target }));
|
|
429
|
+
stop();
|
|
430
|
+
print(r);
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
// ── AI ───────────────────────────────────────────────────────────────────────
|
|
164
434
|
ask: {
|
|
165
|
-
|
|
166
|
-
|
|
435
|
+
group: 'AI',
|
|
436
|
+
desc: 'ask "<question>" [backend]',
|
|
437
|
+
async run([q, backend]) {
|
|
167
438
|
requireConn();
|
|
168
439
|
if (!q)
|
|
169
|
-
throw new Error(
|
|
170
|
-
const
|
|
171
|
-
|
|
440
|
+
throw new Error('Usage: ask "<question>" [backend]');
|
|
441
|
+
const stop = spinner('Thinking...');
|
|
442
|
+
const r = await satori.ask(withCreds({
|
|
443
|
+
question: q,
|
|
444
|
+
...(backend ? { backend } : {}),
|
|
445
|
+
}));
|
|
446
|
+
stop();
|
|
447
|
+
const answer = r?.message ?? r?.data ?? r;
|
|
448
|
+
print(answer);
|
|
172
449
|
}
|
|
173
450
|
},
|
|
174
451
|
query: {
|
|
175
|
-
|
|
176
|
-
|
|
452
|
+
group: 'AI',
|
|
453
|
+
desc: 'query "<text>" [backend]',
|
|
454
|
+
async run([q, backend]) {
|
|
177
455
|
requireConn();
|
|
178
456
|
if (!q)
|
|
179
|
-
throw new Error(
|
|
180
|
-
const
|
|
181
|
-
|
|
457
|
+
throw new Error('Usage: query "<text>" [backend]');
|
|
458
|
+
const stop = spinner('Searching...');
|
|
459
|
+
const r = await satori.query(withCreds({
|
|
460
|
+
query: q,
|
|
461
|
+
...(backend ? { backend } : {}),
|
|
462
|
+
}));
|
|
463
|
+
stop();
|
|
464
|
+
print(r);
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
// ── Mindspace ─────────────────────────────────────────────────────────────────
|
|
468
|
+
mindspace: {
|
|
469
|
+
group: 'Mindspace', noConn: true,
|
|
470
|
+
desc: 'mindspace select <id> – set active mindspace (local only)',
|
|
471
|
+
run([sub, id]) {
|
|
472
|
+
if (sub !== 'select' || !id)
|
|
473
|
+
throw new Error('Usage: mindspace select <id>');
|
|
474
|
+
session.mindspace = id;
|
|
475
|
+
ok(`Active mindspace: ${c.green}${id}${c.reset}`);
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
'set-mindspace': {
|
|
479
|
+
group: 'Mindspace',
|
|
480
|
+
desc: 'set-mindspace [id] – create / activate mindspace on server',
|
|
481
|
+
async run([id]) {
|
|
482
|
+
requireConn();
|
|
483
|
+
const mid = id ?? session.mindspace;
|
|
484
|
+
if (!mid)
|
|
485
|
+
throw new Error('Provide an ID or select one first: mindspace select <id>');
|
|
486
|
+
const stop = spinner(`Activating ${mid}...`);
|
|
487
|
+
await satori.setMindspace(withCreds({ mindspace_id: mid, config: '' }));
|
|
488
|
+
stop();
|
|
489
|
+
session.mindspace = mid;
|
|
490
|
+
ok(`Mindspace active: ${c.green}${mid}${c.reset}`);
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
'delete-mindspace': {
|
|
494
|
+
group: 'Mindspace',
|
|
495
|
+
desc: 'delete-mindspace <id>',
|
|
496
|
+
async run([id]) {
|
|
497
|
+
requireConn();
|
|
498
|
+
if (!id)
|
|
499
|
+
throw new Error('Usage: delete-mindspace <id>');
|
|
500
|
+
const stop = spinner(`Deleting ${id}...`);
|
|
501
|
+
await satori.deleteMindspace(withCreds({ mindspace_id: id }));
|
|
502
|
+
stop();
|
|
503
|
+
if (session.mindspace === id)
|
|
504
|
+
session.mindspace = null;
|
|
505
|
+
ok(`Deleted mindspace: ${c.cyan}${id}${c.reset}`);
|
|
182
506
|
}
|
|
183
507
|
},
|
|
184
508
|
chat: {
|
|
185
|
-
|
|
509
|
+
group: 'Mindspace',
|
|
510
|
+
desc: 'chat "<message>"',
|
|
186
511
|
async run([msg]) {
|
|
187
|
-
|
|
188
|
-
if (!
|
|
189
|
-
throw new Error(
|
|
190
|
-
const
|
|
191
|
-
|
|
512
|
+
requireMindspace();
|
|
513
|
+
if (!msg)
|
|
514
|
+
throw new Error('Usage: chat "<message>"');
|
|
515
|
+
const stop = spinner('Thinking...');
|
|
516
|
+
const r = await satori.chatMindspace(withCreds({ minspace_id: session.mindspace, message: msg }));
|
|
517
|
+
stop();
|
|
518
|
+
print(r);
|
|
192
519
|
}
|
|
193
520
|
},
|
|
194
521
|
lecture: {
|
|
195
|
-
|
|
522
|
+
group: 'Mindspace',
|
|
523
|
+
desc: 'lecture "<corpus>"',
|
|
196
524
|
async run([corpus]) {
|
|
525
|
+
requireMindspace();
|
|
526
|
+
if (!corpus)
|
|
527
|
+
throw new Error('Usage: lecture "<corpus>"');
|
|
528
|
+
const stop = spinner('Ingesting corpus...');
|
|
529
|
+
const r = await satori.lectureMindspace(withCreds({ mindspace_id: session.mindspace, corpus }));
|
|
530
|
+
stop();
|
|
531
|
+
const msg = r?.message;
|
|
532
|
+
if (msg)
|
|
533
|
+
ok(msg);
|
|
534
|
+
else
|
|
535
|
+
ok('Corpus ingested');
|
|
536
|
+
}
|
|
537
|
+
},
|
|
538
|
+
// ── Crypto ────────────────────────────────────────────────────────────────────
|
|
539
|
+
encrypt: {
|
|
540
|
+
group: 'Crypto',
|
|
541
|
+
desc: 'encrypt <key> <encryption_key>',
|
|
542
|
+
async run([key, encryption_key]) {
|
|
543
|
+
requireConn();
|
|
544
|
+
if (!key || !encryption_key)
|
|
545
|
+
throw new Error('Usage: encrypt <key> <encryption_key>');
|
|
546
|
+
const stop = spinner('Encrypting...');
|
|
547
|
+
const r = await satori.encrypt(withCreds({ key, encryption_key }));
|
|
548
|
+
stop();
|
|
549
|
+
print(r);
|
|
550
|
+
}
|
|
551
|
+
},
|
|
552
|
+
decrypt: {
|
|
553
|
+
group: 'Crypto',
|
|
554
|
+
desc: 'decrypt <key> <encryption_key>',
|
|
555
|
+
async run([key, encryption_key]) {
|
|
556
|
+
requireConn();
|
|
557
|
+
if (!key || !encryption_key)
|
|
558
|
+
throw new Error('Usage: decrypt <key> <encryption_key>');
|
|
559
|
+
const stop = spinner('Decrypting...');
|
|
560
|
+
const r = await satori.decrypt(withCreds({ key, encryption_key }));
|
|
561
|
+
stop();
|
|
562
|
+
print(r);
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
// ── Analytics ─────────────────────────────────────────────────────────────────
|
|
566
|
+
operations: {
|
|
567
|
+
group: 'Analytics',
|
|
568
|
+
desc: 'operations – recent operation log',
|
|
569
|
+
async run() {
|
|
197
570
|
requireConn();
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
571
|
+
const stop = spinner('Fetching...');
|
|
572
|
+
const r = await satori.getOperations();
|
|
573
|
+
stop();
|
|
574
|
+
print(r);
|
|
202
575
|
}
|
|
203
576
|
},
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
577
|
+
freq: {
|
|
578
|
+
group: 'Analytics',
|
|
579
|
+
desc: 'freq <key> – access frequency for a key',
|
|
580
|
+
async run([key]) {
|
|
207
581
|
requireConn();
|
|
208
|
-
if (!
|
|
209
|
-
throw new Error(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
payload.mindspace_id = session.mindspace;
|
|
215
|
-
const res = await satori.setMindspace(payload);
|
|
216
|
-
if (payload.mindspace_id)
|
|
217
|
-
session.mindspace = payload.mindspace_id;
|
|
218
|
-
console.log(`🧠 Mindspace configured: ${payload.mindspace_id ?? res.data}`);
|
|
582
|
+
if (!key)
|
|
583
|
+
throw new Error('Usage: freq <key>');
|
|
584
|
+
const stop = spinner('Fetching...');
|
|
585
|
+
const r = await satori.getAccessFrequency(key);
|
|
586
|
+
stop();
|
|
587
|
+
print(r);
|
|
219
588
|
}
|
|
220
589
|
},
|
|
590
|
+
// ── Utility ───────────────────────────────────────────────────────────────────
|
|
591
|
+
help: {
|
|
592
|
+
group: 'Utility', noConn: true,
|
|
593
|
+
desc: 'Show help',
|
|
594
|
+
run() { showHelp(); }
|
|
595
|
+
},
|
|
596
|
+
clear: {
|
|
597
|
+
group: 'Utility', noConn: true,
|
|
598
|
+
desc: 'Clear screen',
|
|
599
|
+
run() { console.clear(); showBanner(); }
|
|
600
|
+
},
|
|
601
|
+
exit: {
|
|
602
|
+
group: 'Utility', noConn: true,
|
|
603
|
+
desc: 'Exit',
|
|
604
|
+
run() { process.exit(0); }
|
|
605
|
+
},
|
|
606
|
+
quit: {
|
|
607
|
+
group: 'Utility', noConn: true,
|
|
608
|
+
desc: 'Exit',
|
|
609
|
+
run() { process.exit(0); }
|
|
610
|
+
},
|
|
611
|
+
};
|
|
612
|
+
// Backward-compat aliases (old underscore variants kept for muscle memory)
|
|
613
|
+
const aliases = {
|
|
614
|
+
set_mindspace: 'set-mindspace',
|
|
615
|
+
delete_mindspace: 'delete-mindspace',
|
|
616
|
+
set_vertex: 'set-vertex',
|
|
617
|
+
get_vertex: 'get-vertex',
|
|
618
|
+
delete_vertex: 'delete-vertex',
|
|
619
|
+
shortest_path: 'shortest-path',
|
|
221
620
|
};
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
621
|
+
for (const [alias, canonical] of Object.entries(aliases)) {
|
|
622
|
+
commands[alias] = commands[canonical];
|
|
623
|
+
}
|
|
624
|
+
// ── Help ───────────────────────────────────────────────────────────────────────
|
|
225
625
|
function showHelp() {
|
|
226
|
-
|
|
227
|
-
Object.
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
626
|
+
var _a;
|
|
627
|
+
const hidden = new Set(['quit', ...Object.keys(aliases)]);
|
|
628
|
+
const groups = {};
|
|
629
|
+
for (const [name, cmd] of Object.entries(commands)) {
|
|
630
|
+
if (hidden.has(name))
|
|
631
|
+
continue;
|
|
632
|
+
(groups[_a = cmd.group] ?? (groups[_a] = [])).push([name, cmd.desc]);
|
|
633
|
+
}
|
|
634
|
+
console.log();
|
|
635
|
+
for (const [group, entries] of Object.entries(groups)) {
|
|
636
|
+
console.log(` ${c.yellow}${group}${c.reset}`);
|
|
637
|
+
for (const [name, desc] of entries) {
|
|
638
|
+
const argsPart = desc.startsWith(name) ? desc.slice(name.length).trim() : desc;
|
|
639
|
+
console.log(` ${c.cyan}${name.padEnd(20)}${c.reset}` +
|
|
640
|
+
`${c.dim}${argsPart}${c.reset}`);
|
|
641
|
+
}
|
|
642
|
+
console.log();
|
|
643
|
+
}
|
|
232
644
|
}
|
|
233
|
-
//
|
|
234
|
-
// Interactive Shell
|
|
235
|
-
// -------------------------------
|
|
645
|
+
// ── REPL ───────────────────────────────────────────────────────────────────────
|
|
236
646
|
async function startREPL() {
|
|
647
|
+
showBanner();
|
|
648
|
+
// Tab-completion list: canonical names only (no aliases, no quit duplicate)
|
|
649
|
+
const completionList = Object.keys(commands).filter(k => !aliases[k] && k !== 'quit');
|
|
237
650
|
const rl = readline_1.default.createInterface({
|
|
238
651
|
input: process.stdin,
|
|
239
652
|
output: process.stdout,
|
|
240
653
|
historySize: 500,
|
|
241
654
|
completer(line) {
|
|
242
|
-
const hits =
|
|
243
|
-
return [hits.length ? hits :
|
|
244
|
-
}
|
|
655
|
+
const hits = completionList.filter(k => k.startsWith(line));
|
|
656
|
+
return [hits.length ? hits : completionList, line];
|
|
657
|
+
},
|
|
245
658
|
});
|
|
659
|
+
// Restore saved history into readline's internal array
|
|
660
|
+
const history = loadHistory();
|
|
661
|
+
if (history.length && rl.history) {
|
|
662
|
+
rl.history.push(...history);
|
|
663
|
+
}
|
|
246
664
|
const refreshPrompt = () => rl.setPrompt(promptLabel());
|
|
247
665
|
refreshPrompt();
|
|
248
666
|
rl.prompt();
|
|
249
|
-
rl.on(
|
|
250
|
-
const
|
|
251
|
-
if (!
|
|
252
|
-
|
|
667
|
+
rl.on('line', async (raw) => {
|
|
668
|
+
const line = raw.trim();
|
|
669
|
+
if (!line) {
|
|
670
|
+
rl.prompt();
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
const parts = parseArgs(line);
|
|
253
674
|
const [cmd, ...args] = parts;
|
|
254
675
|
const entry = commands[cmd];
|
|
255
676
|
try {
|
|
256
|
-
if (!entry)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
677
|
+
if (!entry) {
|
|
678
|
+
fail(`Unknown command: ${c.cyan}${cmd}${c.reset} — type ${c.cyan}help${c.reset}`);
|
|
679
|
+
}
|
|
680
|
+
else if (!entry.noConn && !satori) {
|
|
681
|
+
fail(`Not connected — use: ${c.cyan}connect <host>${c.reset}`);
|
|
260
682
|
}
|
|
261
683
|
else {
|
|
262
684
|
await entry.run(args);
|
|
263
685
|
}
|
|
264
686
|
}
|
|
265
687
|
catch (e) {
|
|
266
|
-
|
|
688
|
+
fail(e.message ?? String(e));
|
|
267
689
|
}
|
|
268
690
|
refreshPrompt();
|
|
269
691
|
rl.prompt();
|
|
270
692
|
});
|
|
693
|
+
rl.on('close', () => {
|
|
694
|
+
saveHistory(rl.history ?? []);
|
|
695
|
+
console.log();
|
|
696
|
+
process.exit(0);
|
|
697
|
+
});
|
|
271
698
|
}
|
|
272
|
-
//
|
|
273
|
-
// Entry
|
|
274
|
-
// -------------------------------
|
|
699
|
+
// ── Entry ──────────────────────────────────────────────────────────────────────
|
|
275
700
|
const argv = process.argv.slice(2);
|
|
276
|
-
if (argv[0] ===
|
|
701
|
+
if (argv[0] === 'run') {
|
|
277
702
|
runBinary(argv.slice(1));
|
|
278
703
|
}
|
|
279
|
-
else if (argv[0] ===
|
|
280
|
-
require(
|
|
704
|
+
else if (argv[0] === 'update') {
|
|
705
|
+
require('./postinstall');
|
|
281
706
|
}
|
|
282
|
-
else if (argv[0] ===
|
|
283
|
-
require(
|
|
707
|
+
else if (argv[0] === 'verify') {
|
|
708
|
+
require('./verify-install');
|
|
284
709
|
}
|
|
285
710
|
else {
|
|
286
711
|
startREPL();
|
package/package.json
CHANGED
package/postinstall.js
CHANGED
|
@@ -10,14 +10,32 @@ let { spawnSync } = require("child_process");
|
|
|
10
10
|
let platform = os.platform();
|
|
11
11
|
let arch = os.arch();
|
|
12
12
|
|
|
13
|
+
// ── CLI-only mode ──────────────────────────────────────────────────────────────
|
|
14
|
+
// Set SATORI_NO_BINARY=1 to skip the binary download entirely.
|
|
15
|
+
// The interactive shell (satoridb) still works — it only needs a running
|
|
16
|
+
// Satori server reachable over WebSocket.
|
|
17
|
+
//
|
|
18
|
+
// SATORI_NO_BINARY=1 npm install -g satoridb
|
|
19
|
+
//
|
|
20
|
+
if (process.env.SATORI_NO_BINARY === "1") {
|
|
21
|
+
console.log(" ℹ Skipping Satori binary download (SATORI_NO_BINARY=1)");
|
|
22
|
+
console.log(" · The CLI shell is ready: satoridb");
|
|
23
|
+
console.log(" · To download the binary later: satoridb update");
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
|
|
13
27
|
let baseURL = "https://www.satoridb.com";
|
|
14
28
|
let fileName;
|
|
15
29
|
|
|
16
30
|
if (platform === "linux") fileName = "lin/satori-linux.zip";
|
|
17
31
|
else if (platform === "win32") fileName = "win/satori-win.zip";
|
|
18
32
|
else {
|
|
19
|
-
|
|
20
|
-
|
|
33
|
+
// Unsupported platform — warn but do not fail; CLI shell still works.
|
|
34
|
+
console.log(` ⚠ No pre-built binary for platform: ${platform}`);
|
|
35
|
+
console.log(" · The CLI shell is still available: satoridb");
|
|
36
|
+
console.log(" · Connect to any Satori server via: connect <ws://host:port>");
|
|
37
|
+
console.log(" · To skip this message next time: SATORI_NO_BINARY=1 npm install -g satoridb");
|
|
38
|
+
process.exit(0);
|
|
21
39
|
}
|
|
22
40
|
|
|
23
41
|
let binName = platform === "win32" ? "satori.exe" : "satori";
|