satoridb 1.2.5 โ†’ 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.
Files changed (3) hide show
  1. package/cli.js +712 -278
  2. package/package.json +8 -3
  3. package/postinstall.js +20 -2
package/cli.js CHANGED
@@ -1,278 +1,712 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Satori Interactive CLI (Improved)
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
10
- */
11
-
12
- const path = require("path");
13
- const os = require("os");
14
- const fs = require("fs");
15
- const { spawnSync } = require("child_process");
16
- const readline = require("readline");
17
- const { Satori } = require("satori-node");
18
-
19
- // -------------------------------
20
- // Globals / State
21
- // -------------------------------
22
- const binName = os.platform() === "win32" ? "satori.exe" : "satori";
23
- const binPath = path.join(os.homedir(), ".satori", "bin", binName);
24
-
25
- let satori = null;
26
- let session = {
27
- host: null,
28
- user: null,
29
- password: null,
30
- mindspace: null,
31
- };
32
-
33
- // -------------------------------
34
- // Utils
35
- // -------------------------------
36
- function assertInstalled() {
37
- if (!fs.existsSync(binPath)) {
38
- console.error("โŒ Satori binary not found. Reinstall the package.");
39
- process.exit(1);
40
- }
41
- }
42
-
43
- function runBinary(args) {
44
- assertInstalled();
45
- const r = spawnSync(binPath, args, { stdio: "inherit" });
46
- if (r.error) throw r.error;
47
- }
48
-
49
- function parseArgs(line) {
50
- const re = /"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|(\S+)/g;
51
- const out = [];
52
- let m;
53
- while ((m = re.exec(line))) out.push(m[1] ?? m[2] ?? m[3]);
54
- return out.map(v => v.replace(/\\"/g, '"').replace(/\\'/g, "'"));
55
- }
56
-
57
- function parseData(v) {
58
- if (typeof v !== "string") return v;
59
- const t = v.trim();
60
- if (t === "true") return true;
61
- if (t === "false") return false;
62
- if (!isNaN(t) && t !== "") return Number(t);
63
- if (t.startsWith("{") || t.startsWith("[")) {
64
- try { return JSON.parse(t); } catch {}
65
- }
66
- return v;
67
- }
68
-
69
- function requireConn() {
70
- if (!satori) throw new Error("No active connection. Use: connect <host> [user] [password]");
71
- }
72
-
73
- function withCreds(p = {}) {
74
- if (session.user) p.user = session.user;
75
- if (session.password) p.password = session.password;
76
- return p;
77
- }
78
-
79
- function promptLabel() {
80
- return `satori${session.mindspace ? `:${session.mindspace}` : ""}> `;
81
- }
82
-
83
- // -------------------------------
84
- // Commands Registry
85
- // -------------------------------
86
- const commands = {
87
- help: {
88
- desc: "Show help",
89
- run() { showHelp(); }
90
- },
91
-
92
- exit: { run() { process.exit(0); } },
93
- quit: { run() { process.exit(0); } },
94
- clear: { run() { console.clear(); } },
95
-
96
- connect: {
97
- desc: "connect <host> [user] [password]",
98
- async run([host, user = null, password = null]) {
99
- if (!host) throw new Error("Usage: connect <host> [user] [password]");
100
- console.log(`๐Ÿ”— Connecting to ${host}...`);
101
- satori = new Satori({ host, user, password });
102
- await satori.connect();
103
- Object.assign(session, { host, user, password });
104
- console.log("โœ… Connected");
105
- }
106
- },
107
-
108
- mindspace: {
109
- desc: "mindspace select <id>",
110
- run([sub, id]) {
111
- if (sub !== "select" || !id) throw new Error("Usage: mindspace select <id>");
112
- session.mindspace = id;
113
- console.log(`๐Ÿง  Mindspace selected: ${id}`);
114
- }
115
- },
116
-
117
- set: {
118
- desc: "set <key> <data>",
119
- async run([key, ...rest]) {
120
- requireConn();
121
- if (!key || !rest.length) throw new Error("Usage: set <key> <data>");
122
- const data = parseData(rest.join(" "));
123
- await satori.set(withCreds({ key, data }));
124
- console.log(`โœ… Saved: ${key}`);
125
- }
126
- },
127
-
128
- get: {
129
- desc: "get <key>",
130
- async run([key]) {
131
- requireConn();
132
- const r = await satori.get(withCreds(key ? { key } : {}));
133
- console.log(JSON.stringify(r, null, 2));
134
- }
135
- },
136
-
137
- put: {
138
- desc: "put <key> <field> <value>",
139
- async run([key, field, value]) {
140
- requireConn();
141
- if (!key || !field) throw new Error("Usage: put <key> <field> <value>");
142
- await satori.put(withCreds({ key, replace_field: field, replace_value: parseData(value) }));
143
- console.log("โœ… Updated");
144
- }
145
- },
146
-
147
- delete: {
148
- desc: "delete <key>",
149
- async run([key]) {
150
- requireConn();
151
- if (!key) throw new Error("Usage: delete <key>");
152
- await satori.delete(withCreds({ key }));
153
- console.log(`๐Ÿ—‘๏ธ Deleted: ${key}`);
154
- }
155
- },
156
-
157
- ask: {
158
- desc: "ask \"<question>\"",
159
- async run([q, backend = null]) {
160
- requireConn();
161
- if (!q) throw new Error("Usage: ask \"<question>\"");
162
- const r = await satori.ask(withCreds({ question: q, backend }));
163
- console.log(JSON.stringify(r, null, 2));
164
- }
165
- },
166
-
167
- query: {
168
- desc: "query \"<query>\"",
169
- async run([q, backend = null]) {
170
- requireConn();
171
- if (!q) throw new Error("Usage: query \"<query>\"");
172
- const r = await satori.query(withCreds({ query: q, backend }));
173
- console.log(JSON.stringify(r, null, 2));
174
- }
175
- },
176
-
177
- chat: {
178
- desc: "chat \"<message>\"",
179
- async run([msg]) {
180
- requireConn();
181
- if (!session.mindspace) throw new Error("Select a mindspace first");
182
- const r = await satori.chatMindspace({ mindspace_id: session.mindspace, message: msg });
183
- console.log(r);
184
- }
185
- },
186
-
187
- lecture: {
188
- desc: "lecture \"<corpus>\"",
189
- async run([corpus]) {
190
- requireConn();
191
- if (!session.mindspace) throw new Error("Select a mindspace first");
192
- const r = await satori.lectureMindspace({ mindspace_id: session.mindspace, corpus });
193
- console.log(r.data);
194
- }
195
- },
196
- set_mindspace: {
197
- desc: "set_mindspace <config> [mindspace_id]",
198
- async run([config, mindspace_id]) {
199
- requireConn();
200
- if (!config) throw new Error("Usage: set_mindspace <config> [mindspace_id]");
201
-
202
-
203
- const payload = { config };
204
- if (mindspace_id) payload.mindspace_id = mindspace_id;
205
- else if (session.mindspace) payload.mindspace_id = session.mindspace;
206
-
207
-
208
- const res = await satori.setMindspace(payload);
209
- if (payload.mindspace_id) session.mindspace = payload.mindspace_id;
210
-
211
-
212
- console.log(`๐Ÿง  Mindspace configured: ${payload.mindspace_id ?? res.data}`);
213
- }
214
- },
215
- };
216
-
217
- // -------------------------------
218
- // Help
219
- // -------------------------------
220
- function showHelp() {
221
- console.log("\n๐Ÿ“š Commands:\n");
222
- Object.entries(commands).forEach(([k, v]) => {
223
- if (v.desc) console.log(` ${k.padEnd(12)} ${v.desc}`);
224
- });
225
- console.log("");
226
- }
227
-
228
- // -------------------------------
229
- // Interactive Shell
230
- // -------------------------------
231
- async function startREPL() {
232
- const rl = readline.createInterface({
233
- input: process.stdin,
234
- output: process.stdout,
235
- historySize: 500,
236
- completer(line) {
237
- const hits = Object.keys(commands).filter(c => c.startsWith(line));
238
- return [hits.length ? hits : Object.keys(commands), line];
239
- }
240
- });
241
-
242
- const refreshPrompt = () => rl.setPrompt(promptLabel());
243
- refreshPrompt();
244
- rl.prompt();
245
-
246
- rl.on("line", async (line) => {
247
- const parts = parseArgs(line.trim());
248
- if (!parts.length) return rl.prompt();
249
-
250
- const [cmd, ...args] = parts;
251
- const entry = commands[cmd];
252
-
253
- try {
254
- if (!entry) throw new Error("Unknown command. Type 'help'.");
255
- await entry.run(args);
256
- } catch (e) {
257
- console.error("โŒ", e.message);
258
- }
259
-
260
- refreshPrompt();
261
- rl.prompt();
262
- });
263
- }
264
-
265
- // -------------------------------
266
- // Entry
267
- // -------------------------------
268
- const argv = process.argv.slice(2);
269
-
270
- if (argv[0] === "run") {
271
- runBinary(argv.slice(1));
272
- } else if (argv[0] === "update") {
273
- require("./postinstall");
274
- } else if (argv[0] === "verify") {
275
- require("./verify-install");
276
- } else {
277
- startREPL();
278
- }
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * Satori Interactive CLI
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ const satori_node_1 = require("satori-node");
11
+ const path_1 = __importDefault(require("path"));
12
+ const os_1 = __importDefault(require("os"));
13
+ const fs_1 = __importDefault(require("fs"));
14
+ const child_process_1 = require("child_process");
15
+ const readline_1 = __importDefault(require("readline"));
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');
38
+ let satori = null;
39
+ let session = {
40
+ host: null,
41
+ user: null,
42
+ password: null,
43
+ mindspace: null,
44
+ };
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 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
101
+ function assertInstalled() {
102
+ if (!fs_1.default.existsSync(binPath)) {
103
+ fail('Satori binary not found. Run: npm install -g satoridb');
104
+ process.exit(1);
105
+ }
106
+ }
107
+ function runBinary(args) {
108
+ assertInstalled();
109
+ const r = (0, child_process_1.spawnSync)(binPath, args, { stdio: 'inherit' });
110
+ if (r.error)
111
+ throw r.error;
112
+ }
113
+ /** Tokenises a shell-style line respecting single/double quotes and backslash escapes. */
114
+ function parseArgs(line) {
115
+ const re = /"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|(\S+)/g;
116
+ const out = [];
117
+ let m;
118
+ while ((m = re.exec(line)))
119
+ out.push(m[1] ?? m[2] ?? m[3]);
120
+ return out.map(v => v.replace(/\\"/g, '"').replace(/\\'/g, "'"));
121
+ }
122
+ /** Auto-casts string tokens to bool / number / JSON when unambiguous. */
123
+ function parseData(v) {
124
+ if (typeof v !== 'string')
125
+ return v;
126
+ const t = v.trim();
127
+ if (t === 'true')
128
+ return true;
129
+ if (t === 'false')
130
+ return false;
131
+ if (t !== '' && !isNaN(Number(t)))
132
+ return Number(t);
133
+ if (t.startsWith('{') || t.startsWith('[')) {
134
+ try {
135
+ return JSON.parse(t);
136
+ }
137
+ catch { }
138
+ }
139
+ return v;
140
+ }
141
+ function requireConn() {
142
+ if (!satori)
143
+ throw new Error('Not connected โ€” use: connect <host>');
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.
154
+ function withCreds(p = {}) {
155
+ if (session.user)
156
+ p.user = session.user;
157
+ if (session.password)
158
+ p.password = session.password;
159
+ return p;
160
+ }
161
+ /**
162
+ * Plain-text prompt โ€” ANSI codes inside the prompt string break readline's
163
+ * cursor-position tracking, so keep it clean.
164
+ */
165
+ function promptLabel() {
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 { }
189
+ }
190
+ const commands = {
191
+ // โ”€โ”€ Connection โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
192
+ connect: {
193
+ group: 'Connection', noConn: true,
194
+ desc: 'connect <host> [user] [password]',
195
+ async run([host, user, password]) {
196
+ if (!host)
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}`);
207
+ }
208
+ catch (e) {
209
+ stop();
210
+ satori = null;
211
+ throw new Error(`Connection failed: ${e.message ?? e}`);
212
+ }
213
+ }
214
+ },
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}`);
224
+ }
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 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
245
+ set: {
246
+ group: 'Data',
247
+ desc: 'set <key> <data>',
248
+ async run([key, ...rest]) {
249
+ requireConn();
250
+ if (!key || !rest.length)
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}`);
256
+ }
257
+ },
258
+ get: {
259
+ group: 'Data',
260
+ desc: 'get [key]',
261
+ async run([key]) {
262
+ requireConn();
263
+ const stop = spinner('Fetching...');
264
+ const r = await satori.get(withCreds(key ? { key } : {}));
265
+ stop();
266
+ print(r);
267
+ }
268
+ },
269
+ put: {
270
+ group: 'Data',
271
+ desc: 'put <key> <field> <value>',
272
+ async run([key, field, ...rest]) {
273
+ requireConn();
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}`);
284
+ }
285
+ },
286
+ delete: {
287
+ group: 'Data',
288
+ desc: 'delete <key>',
289
+ async run([key]) {
290
+ requireConn();
291
+ if (!key)
292
+ throw new Error('Usage: delete <key>');
293
+ const stop = spinner('Deleting...');
294
+ await satori.delete(withCreds({ key }));
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}`);
392
+ }
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 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
434
+ ask: {
435
+ group: 'AI',
436
+ desc: 'ask "<question>" [backend]',
437
+ async run([q, backend]) {
438
+ requireConn();
439
+ if (!q)
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);
449
+ }
450
+ },
451
+ query: {
452
+ group: 'AI',
453
+ desc: 'query "<text>" [backend]',
454
+ async run([q, backend]) {
455
+ requireConn();
456
+ if (!q)
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}`);
506
+ }
507
+ },
508
+ chat: {
509
+ group: 'Mindspace',
510
+ desc: 'chat "<message>"',
511
+ async run([msg]) {
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);
519
+ }
520
+ },
521
+ lecture: {
522
+ group: 'Mindspace',
523
+ desc: 'lecture "<corpus>"',
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() {
570
+ requireConn();
571
+ const stop = spinner('Fetching...');
572
+ const r = await satori.getOperations();
573
+ stop();
574
+ print(r);
575
+ }
576
+ },
577
+ freq: {
578
+ group: 'Analytics',
579
+ desc: 'freq <key> โ€“ access frequency for a key',
580
+ async run([key]) {
581
+ requireConn();
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);
588
+ }
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',
620
+ };
621
+ for (const [alias, canonical] of Object.entries(aliases)) {
622
+ commands[alias] = commands[canonical];
623
+ }
624
+ // โ”€โ”€ Help โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
625
+ function showHelp() {
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
+ }
644
+ }
645
+ // โ”€โ”€ REPL โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
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');
650
+ const rl = readline_1.default.createInterface({
651
+ input: process.stdin,
652
+ output: process.stdout,
653
+ historySize: 500,
654
+ completer(line) {
655
+ const hits = completionList.filter(k => k.startsWith(line));
656
+ return [hits.length ? hits : completionList, line];
657
+ },
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
+ }
664
+ const refreshPrompt = () => rl.setPrompt(promptLabel());
665
+ refreshPrompt();
666
+ rl.prompt();
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);
674
+ const [cmd, ...args] = parts;
675
+ const entry = commands[cmd];
676
+ try {
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}`);
682
+ }
683
+ else {
684
+ await entry.run(args);
685
+ }
686
+ }
687
+ catch (e) {
688
+ fail(e.message ?? String(e));
689
+ }
690
+ refreshPrompt();
691
+ rl.prompt();
692
+ });
693
+ rl.on('close', () => {
694
+ saveHistory(rl.history ?? []);
695
+ console.log();
696
+ process.exit(0);
697
+ });
698
+ }
699
+ // โ”€โ”€ Entry โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
700
+ const argv = process.argv.slice(2);
701
+ if (argv[0] === 'run') {
702
+ runBinary(argv.slice(1));
703
+ }
704
+ else if (argv[0] === 'update') {
705
+ require('./postinstall');
706
+ }
707
+ else if (argv[0] === 'verify') {
708
+ require('./verify-install');
709
+ }
710
+ else {
711
+ startREPL();
712
+ }
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "satoridb",
3
- "version": "1.2.5",
3
+ "version": "1.2.7",
4
4
  "description": "Install satori",
5
5
  "bin": {
6
6
  "satoridb": "./cli.js"
7
7
  },
8
8
  "scripts": {
9
+ "build": "tsc cli.ts --outDir . --target es2020 --module commonjs --esModuleInterop",
9
10
  "postinstall": "node postinstall.js",
10
11
  "verify": "node verify-install.js"
11
12
  },
@@ -13,12 +14,16 @@
13
14
  "license": "ISC",
14
15
  "dependencies": {
15
16
  "adm-zip": "^0.5.16",
16
- "satori-node": "^1.1.13"
17
+ "satori-node": "^1.1.13",
18
+ "typescript": "^5.9.3"
17
19
  },
18
20
  "files": [
19
21
  "postinstall.js",
20
22
  "add-to-path.ps1",
21
23
  "add-to-path.sh",
22
24
  "verify-install.js"
23
- ]
25
+ ],
26
+ "devDependencies": {
27
+ "@types/node": "^25.1.0"
28
+ }
24
29
  }
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
- console.log("โŒ Platform not supported:", platform);
20
- process.exit(1);
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";