@satori-sh/cli 0.0.15 → 0.0.16

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/dist/index.js CHANGED
@@ -4,8 +4,8 @@
4
4
  import { Command } from "commander";
5
5
  import { readFileSync, realpathSync } from "fs";
6
6
  import { execFile } from "child_process";
7
- import { dirname as dirname2, join as join3 } from "path";
8
- import { fileURLToPath as fileURLToPath2 } from "url";
7
+ import { dirname, join as join2 } from "path";
8
+ import { fileURLToPath } from "url";
9
9
  import chalk from "chalk";
10
10
 
11
11
  // src/config.ts
@@ -227,256 +227,6 @@ ${memoryText}`
227
227
  return [systemMessage, ...messages];
228
228
  }
229
229
 
230
- // src/ui.tsx
231
- import { render, useRenderer, useTerminalDimensions } from "@opentui/solid";
232
- import { For, Show, createSignal, onMount, onCleanup } from "solid-js";
233
- import cliSpinners from "cli-spinners";
234
-
235
- // src/logo.ts
236
- import { dirname, join as join2 } from "path";
237
- import { fileURLToPath } from "url";
238
- async function loadLogo() {
239
- const { default: fs } = await import("fs");
240
- const __dirname = dirname(fileURLToPath(import.meta.url));
241
- const logoPath = join2(__dirname, "..", "logos", "satori.ans");
242
- return fs.readFileSync(logoPath, "utf8");
243
- }
244
-
245
- // src/ui.tsx
246
- async function runInteractiveApp({
247
- initialPrompt,
248
- options,
249
- processUserInput,
250
- infoLine,
251
- infoDisplay
252
- }) {
253
- const logo = await loadLogo();
254
- console.log(` ${logo}`);
255
- const rows = process.stdout.rows ?? 24;
256
- const logoHeight = logo.endsWith("\n") ? logo.slice(0, -1).split("\n").length : logo.split("\n").length;
257
- const splitHeight = Math.max(1, rows - logoHeight - 1);
258
- render(
259
- () => /* @__PURE__ */ React.createElement(
260
- App,
261
- {
262
- initialPrompt,
263
- options,
264
- processUserInput,
265
- infoLine,
266
- infoDisplay
267
- }
268
- ),
269
- {
270
- useAlternateScreen: false,
271
- exitOnCtrlC: true,
272
- useMouse: true,
273
- enableMouseMovement: true,
274
- experimental_splitHeight: splitHeight
275
- }
276
- );
277
- }
278
- function App({ initialPrompt, options, processUserInput, infoLine, infoDisplay }) {
279
- const renderer = useRenderer();
280
- const dimensions = useTerminalDimensions();
281
- const [messages, setMessages] = createSignal([]);
282
- const [inputValue, setInputValue] = createSignal("");
283
- const [showIntro, setShowIntro] = createSignal(true);
284
- const [isFullScreen, setIsFullScreen] = createSignal(false);
285
- const [spinnerFrame, setSpinnerFrame] = createSignal(0);
286
- const [isLoading, setIsLoading] = createSignal(false);
287
- const promptFg = "#00ffff";
288
- const responseFg = "#ffffff";
289
- const promptBg = "#2b2b2b";
290
- let inputRef;
291
- let currentMemoryId = options.memoryId;
292
- let messageId = 0;
293
- const usageText = infoDisplay?.usageLine ?? infoLine ?? "";
294
- const versionText = infoDisplay?.versionLine ?? "";
295
- const modelText = infoDisplay?.modelLine ?? "";
296
- const appendMessage = (role, text) => {
297
- setMessages((prev) => [...prev, { id: messageId++, role, text }]);
298
- };
299
- const exitApp = () => {
300
- renderer.destroy();
301
- process.exit(0);
302
- };
303
- const submitPrompt = async (raw) => {
304
- const trimmed = raw.trim();
305
- if (!trimmed) return;
306
- if (trimmed.toLowerCase() === "exit" || trimmed.toLowerCase() === "quit") {
307
- exitApp();
308
- return;
309
- }
310
- if (showIntro()) {
311
- setShowIntro(false);
312
- }
313
- if (!isFullScreen()) {
314
- setIsFullScreen(true);
315
- }
316
- setInputValue("");
317
- if (inputRef) {
318
- inputRef.value = "";
319
- }
320
- appendMessage("prompt", trimmed);
321
- try {
322
- setIsLoading(true);
323
- const result = await processUserInput(trimmed, { ...options, memoryId: currentMemoryId }, "tui");
324
- currentMemoryId = result.memoryId;
325
- appendMessage("response", result.response);
326
- if (result.instruction) {
327
- appendMessage("response", result.instruction);
328
- }
329
- } catch (error) {
330
- const message = error instanceof Error ? error.message : String(error);
331
- appendMessage("response", `Error: ${message}`);
332
- } finally {
333
- setIsLoading(false);
334
- }
335
- };
336
- onMount(async () => {
337
- const spinner = cliSpinners.dots;
338
- const timer = setInterval(() => {
339
- if (isLoading()) {
340
- setSpinnerFrame((prev) => (prev + 1) % spinner.frames.length);
341
- }
342
- }, spinner.interval);
343
- onCleanup(() => clearInterval(timer));
344
- if (initialPrompt) {
345
- await submitPrompt(initialPrompt);
346
- }
347
- if (inputRef) {
348
- inputRef.focus();
349
- }
350
- });
351
- const inputBoxWidth = () => Math.max(1, Math.round(dimensions().width * 0.6));
352
- const inputBoxLeft = () => Math.max(0, Math.round(dimensions().width * 0.15));
353
- const inputBoxHeight = () => isFullScreen() ? 7 : 14;
354
- const inputBoxTop = () => isFullScreen() ? Math.max(1, dimensions().height - inputBoxHeight() - 2) : Math.max(1, Math.round(dimensions().height * 0.666));
355
- const messagesTop = () => 1;
356
- const messagesHeight = () => Math.max(1, inputBoxTop() - messagesTop() - 1);
357
- const messagesWidth = () => Math.min(dimensions().width - 2, inputBoxWidth() + 10);
358
- const messagesLeft = () => Math.max(1, inputBoxLeft() - 5);
359
- return /* @__PURE__ */ React.createElement("box", { width: "100%", height: "100%", flexDirection: "column" }, /* @__PURE__ */ React.createElement(
360
- "scrollbox",
361
- {
362
- id: "messages",
363
- width: messagesWidth(),
364
- height: messagesHeight(),
365
- position: "absolute",
366
- left: messagesLeft(),
367
- top: messagesTop(),
368
- paddingLeft: 1,
369
- paddingRight: 1,
370
- focused: true,
371
- stickyScroll: true,
372
- stickyStart: "bottom"
373
- },
374
- /* @__PURE__ */ React.createElement("box", { width: "100%", flexDirection: "column" }, /* @__PURE__ */ React.createElement(For, { each: messages() }, (message) => /* @__PURE__ */ React.createElement(
375
- "box",
376
- {
377
- width: "100%",
378
- flexDirection: "row",
379
- justifyContent: message.role === "prompt" ? "flex-start" : "flex-end",
380
- marginBottom: 1
381
- },
382
- /* @__PURE__ */ React.createElement(
383
- "box",
384
- {
385
- paddingLeft: 1,
386
- paddingRight: 1,
387
- paddingTop: 1,
388
- paddingBottom: 1,
389
- backgroundColor: message.role === "prompt" ? promptBg : void 0
390
- },
391
- /* @__PURE__ */ React.createElement(
392
- "text",
393
- {
394
- fg: message.role === "prompt" ? promptFg : responseFg,
395
- width: "100%",
396
- wrapMode: "word",
397
- selectable: false
398
- },
399
- message.text
400
- )
401
- )
402
- )))
403
- ), /* @__PURE__ */ React.createElement(
404
- "box",
405
- {
406
- id: "input-box",
407
- width: inputBoxWidth(),
408
- height: inputBoxHeight(),
409
- position: "absolute",
410
- left: inputBoxLeft(),
411
- top: inputBoxTop(),
412
- paddingLeft: 1,
413
- paddingRight: 1,
414
- paddingTop: 1,
415
- flexDirection: "column"
416
- },
417
- /* @__PURE__ */ React.createElement(For, { each: !isFullScreen() && showIntro() ? [
418
- "Use Satori just like you would use ChatGPT.",
419
- "Except, it stores your conversations in a long term memory.",
420
- "The memories you store here can be accessed through the SDK."
421
- ] : [] }, (line) => /* @__PURE__ */ React.createElement("text", { fg: "cyan" }, line)),
422
- /* @__PURE__ */ React.createElement(
423
- "box",
424
- {
425
- id: "input-box",
426
- width: inputBoxWidth(),
427
- height: 5,
428
- backgroundColor: "#1a1a1a",
429
- flexDirection: "column",
430
- justifyContent: "center"
431
- },
432
- /* @__PURE__ */ React.createElement(
433
- "input",
434
- {
435
- id: "input",
436
- width: "100%",
437
- height: 1,
438
- placeholder: "Type a message and press Enter...",
439
- focusedBackgroundColor: "#1a1a1a",
440
- onInput: (value) => setInputValue(value),
441
- onSubmit: () => submitPrompt(inputValue()),
442
- ref: (r) => {
443
- inputRef = r;
444
- }
445
- }
446
- ),
447
- /* @__PURE__ */ React.createElement("box", { flexDirection: "row", flexShrink: 0, paddingTop: 1 }, /* @__PURE__ */ React.createElement("text", { fg: "#ffffff" }, modelText))
448
- )
449
- ), /* @__PURE__ */ React.createElement(Show, { when: isLoading() }, /* @__PURE__ */ React.createElement(
450
- "box",
451
- {
452
- id: "spinner",
453
- position: "absolute",
454
- left: inputBoxLeft(),
455
- top: inputBoxTop() + inputBoxHeight(),
456
- paddingLeft: 1
457
- },
458
- /* @__PURE__ */ React.createElement("text", { fg: "#00ffff" }, cliSpinners.dots.frames[spinnerFrame()])
459
- )), /* @__PURE__ */ React.createElement(
460
- "box",
461
- {
462
- id: "footer",
463
- width: dimensions().width,
464
- height: 1,
465
- position: "absolute",
466
- bottom: 0,
467
- left: 0,
468
- backgroundColor: "#000000",
469
- paddingLeft: 1,
470
- paddingRight: 1,
471
- flexDirection: "row",
472
- justifyContent: "space-between",
473
- alignItems: "center"
474
- },
475
- /* @__PURE__ */ React.createElement("text", { fg: "#00ffff", wrapMode: "none", width: "100%" }, usageText),
476
- /* @__PURE__ */ React.createElement("box", { flexShrink: 0, paddingLeft: 1 }, /* @__PURE__ */ React.createElement("text", { fg: "#00ffff" }, versionText))
477
- ));
478
- }
479
-
480
230
  // src/index.ts
481
231
  async function main() {
482
232
  const argv = process.argv.slice(2);
@@ -494,8 +244,8 @@ async function main() {
494
244
  program.option("--memory-id <id>", "Memory ID for scoping");
495
245
  const DEFAULT_LLM_MODEL = "gpt-4o";
496
246
  const getCliVersion = () => {
497
- const __dirname = dirname2(fileURLToPath2(import.meta.url));
498
- const packagePath = join3(__dirname, "..", "package.json");
247
+ const __dirname = dirname(fileURLToPath(import.meta.url));
248
+ const packagePath = join2(__dirname, "..", "package.json");
499
249
  const raw = readFileSync(packagePath, "utf8");
500
250
  const data = JSON.parse(raw);
501
251
  return data.version ?? "unknown";
@@ -583,6 +333,9 @@ ${instruction}`);
583
333
  return;
584
334
  }
585
335
  const info = await getInfoDisplay();
336
+ const { runInteractiveApp } = await import("./ui-AFMM6JTQ.js").catch(() => {
337
+ throw new Error("Interactive mode requires Bun. Run `bunx @satori-sh/cli`.");
338
+ });
586
339
  await runInteractiveApp({
587
340
  initialPrompt,
588
341
  options,
@@ -649,7 +402,7 @@ ${instruction}`);
649
402
  program.parse();
650
403
  }
651
404
  var entryPath = process.argv[1] ? realpathSync(process.argv[1]) : "";
652
- var modulePath = realpathSync(fileURLToPath2(import.meta.url));
405
+ var modulePath = realpathSync(fileURLToPath(import.meta.url));
653
406
  if (entryPath === modulePath) {
654
407
  main();
655
408
  }
@@ -0,0 +1,252 @@
1
+ // src/ui.tsx
2
+ import { render, useRenderer, useTerminalDimensions } from "@opentui/solid";
3
+ import { For, Show, createSignal, onMount, onCleanup } from "solid-js";
4
+ import cliSpinners from "cli-spinners";
5
+
6
+ // src/logo.ts
7
+ import { dirname, join } from "path";
8
+ import { fileURLToPath } from "url";
9
+ async function loadLogo() {
10
+ const { default: fs } = await import("fs");
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ const logoPath = join(__dirname, "..", "logos", "satori.ans");
13
+ return fs.readFileSync(logoPath, "utf8");
14
+ }
15
+
16
+ // src/ui.tsx
17
+ async function runInteractiveApp({
18
+ initialPrompt,
19
+ options,
20
+ processUserInput,
21
+ infoLine,
22
+ infoDisplay
23
+ }) {
24
+ const logo = await loadLogo();
25
+ console.log(` ${logo}`);
26
+ const rows = process.stdout.rows ?? 24;
27
+ const logoHeight = logo.endsWith("\n") ? logo.slice(0, -1).split("\n").length : logo.split("\n").length;
28
+ const splitHeight = Math.max(1, rows - logoHeight - 1);
29
+ render(
30
+ () => /* @__PURE__ */ React.createElement(
31
+ App,
32
+ {
33
+ initialPrompt,
34
+ options,
35
+ processUserInput,
36
+ infoLine,
37
+ infoDisplay
38
+ }
39
+ ),
40
+ {
41
+ useAlternateScreen: false,
42
+ exitOnCtrlC: true,
43
+ useMouse: true,
44
+ enableMouseMovement: true,
45
+ experimental_splitHeight: splitHeight
46
+ }
47
+ );
48
+ }
49
+ function App({ initialPrompt, options, processUserInput, infoLine, infoDisplay }) {
50
+ const renderer = useRenderer();
51
+ const dimensions = useTerminalDimensions();
52
+ const [messages, setMessages] = createSignal([]);
53
+ const [inputValue, setInputValue] = createSignal("");
54
+ const [showIntro, setShowIntro] = createSignal(true);
55
+ const [isFullScreen, setIsFullScreen] = createSignal(false);
56
+ const [spinnerFrame, setSpinnerFrame] = createSignal(0);
57
+ const [isLoading, setIsLoading] = createSignal(false);
58
+ const promptFg = "#00ffff";
59
+ const responseFg = "#ffffff";
60
+ const promptBg = "#2b2b2b";
61
+ let inputRef;
62
+ let currentMemoryId = options.memoryId;
63
+ let messageId = 0;
64
+ const usageText = infoDisplay?.usageLine ?? infoLine ?? "";
65
+ const versionText = infoDisplay?.versionLine ?? "";
66
+ const modelText = infoDisplay?.modelLine ?? "";
67
+ const appendMessage = (role, text) => {
68
+ setMessages((prev) => [...prev, { id: messageId++, role, text }]);
69
+ };
70
+ const exitApp = () => {
71
+ renderer.destroy();
72
+ process.exit(0);
73
+ };
74
+ const submitPrompt = async (raw) => {
75
+ const trimmed = raw.trim();
76
+ if (!trimmed) return;
77
+ if (trimmed.toLowerCase() === "exit" || trimmed.toLowerCase() === "quit") {
78
+ exitApp();
79
+ return;
80
+ }
81
+ if (showIntro()) {
82
+ setShowIntro(false);
83
+ }
84
+ if (!isFullScreen()) {
85
+ setIsFullScreen(true);
86
+ }
87
+ setInputValue("");
88
+ if (inputRef) {
89
+ inputRef.value = "";
90
+ }
91
+ appendMessage("prompt", trimmed);
92
+ try {
93
+ setIsLoading(true);
94
+ const result = await processUserInput(trimmed, { ...options, memoryId: currentMemoryId }, "tui");
95
+ currentMemoryId = result.memoryId;
96
+ appendMessage("response", result.response);
97
+ if (result.instruction) {
98
+ appendMessage("response", result.instruction);
99
+ }
100
+ } catch (error) {
101
+ const message = error instanceof Error ? error.message : String(error);
102
+ appendMessage("response", `Error: ${message}`);
103
+ } finally {
104
+ setIsLoading(false);
105
+ }
106
+ };
107
+ onMount(async () => {
108
+ const spinner = cliSpinners.dots;
109
+ const timer = setInterval(() => {
110
+ if (isLoading()) {
111
+ setSpinnerFrame((prev) => (prev + 1) % spinner.frames.length);
112
+ }
113
+ }, spinner.interval);
114
+ onCleanup(() => clearInterval(timer));
115
+ if (initialPrompt) {
116
+ await submitPrompt(initialPrompt);
117
+ }
118
+ if (inputRef) {
119
+ inputRef.focus();
120
+ }
121
+ });
122
+ const inputBoxWidth = () => Math.max(1, Math.round(dimensions().width * 0.6));
123
+ const inputBoxLeft = () => Math.max(0, Math.round(dimensions().width * 0.15));
124
+ const inputBoxHeight = () => isFullScreen() ? 7 : 14;
125
+ const inputBoxTop = () => isFullScreen() ? Math.max(1, dimensions().height - inputBoxHeight() - 2) : Math.max(1, Math.round(dimensions().height * 0.666));
126
+ const messagesTop = () => 1;
127
+ const messagesHeight = () => Math.max(1, inputBoxTop() - messagesTop() - 1);
128
+ const messagesWidth = () => Math.min(dimensions().width - 2, inputBoxWidth() + 10);
129
+ const messagesLeft = () => Math.max(1, inputBoxLeft() - 5);
130
+ return /* @__PURE__ */ React.createElement("box", { width: "100%", height: "100%", flexDirection: "column" }, /* @__PURE__ */ React.createElement(
131
+ "scrollbox",
132
+ {
133
+ id: "messages",
134
+ width: messagesWidth(),
135
+ height: messagesHeight(),
136
+ position: "absolute",
137
+ left: messagesLeft(),
138
+ top: messagesTop(),
139
+ paddingLeft: 1,
140
+ paddingRight: 1,
141
+ focused: true,
142
+ stickyScroll: true,
143
+ stickyStart: "bottom"
144
+ },
145
+ /* @__PURE__ */ React.createElement("box", { width: "100%", flexDirection: "column" }, /* @__PURE__ */ React.createElement(For, { each: messages() }, (message) => /* @__PURE__ */ React.createElement(
146
+ "box",
147
+ {
148
+ width: "100%",
149
+ flexDirection: "row",
150
+ justifyContent: message.role === "prompt" ? "flex-start" : "flex-end",
151
+ marginBottom: 1
152
+ },
153
+ /* @__PURE__ */ React.createElement(
154
+ "box",
155
+ {
156
+ paddingLeft: 1,
157
+ paddingRight: 1,
158
+ paddingTop: 1,
159
+ paddingBottom: 1,
160
+ backgroundColor: message.role === "prompt" ? promptBg : void 0
161
+ },
162
+ /* @__PURE__ */ React.createElement(
163
+ "text",
164
+ {
165
+ fg: message.role === "prompt" ? promptFg : responseFg,
166
+ width: "100%",
167
+ wrapMode: "word",
168
+ selectable: false
169
+ },
170
+ message.text
171
+ )
172
+ )
173
+ )))
174
+ ), /* @__PURE__ */ React.createElement(
175
+ "box",
176
+ {
177
+ id: "input-box",
178
+ width: inputBoxWidth(),
179
+ height: inputBoxHeight(),
180
+ position: "absolute",
181
+ left: inputBoxLeft(),
182
+ top: inputBoxTop(),
183
+ paddingLeft: 1,
184
+ paddingRight: 1,
185
+ paddingTop: 1,
186
+ flexDirection: "column"
187
+ },
188
+ /* @__PURE__ */ React.createElement(For, { each: !isFullScreen() && showIntro() ? [
189
+ "Use Satori just like you would use ChatGPT.",
190
+ "Except, it stores your conversations in a long term memory.",
191
+ "The memories you store here can be accessed through the SDK."
192
+ ] : [] }, (line) => /* @__PURE__ */ React.createElement("text", { fg: "cyan" }, line)),
193
+ /* @__PURE__ */ React.createElement(
194
+ "box",
195
+ {
196
+ id: "input-box",
197
+ width: inputBoxWidth(),
198
+ height: 5,
199
+ backgroundColor: "#1a1a1a",
200
+ flexDirection: "column",
201
+ justifyContent: "center"
202
+ },
203
+ /* @__PURE__ */ React.createElement(
204
+ "input",
205
+ {
206
+ id: "input",
207
+ width: "100%",
208
+ height: 1,
209
+ placeholder: "Type a message and press Enter...",
210
+ focusedBackgroundColor: "#1a1a1a",
211
+ onInput: (value) => setInputValue(value),
212
+ onSubmit: () => submitPrompt(inputValue()),
213
+ ref: (r) => {
214
+ inputRef = r;
215
+ }
216
+ }
217
+ ),
218
+ /* @__PURE__ */ React.createElement("box", { flexDirection: "row", flexShrink: 0, paddingTop: 1 }, /* @__PURE__ */ React.createElement("text", { fg: "#ffffff" }, modelText))
219
+ )
220
+ ), /* @__PURE__ */ React.createElement(Show, { when: isLoading() }, /* @__PURE__ */ React.createElement(
221
+ "box",
222
+ {
223
+ id: "spinner",
224
+ position: "absolute",
225
+ left: inputBoxLeft(),
226
+ top: inputBoxTop() + inputBoxHeight(),
227
+ paddingLeft: 1
228
+ },
229
+ /* @__PURE__ */ React.createElement("text", { fg: "#00ffff" }, cliSpinners.dots.frames[spinnerFrame()])
230
+ )), /* @__PURE__ */ React.createElement(
231
+ "box",
232
+ {
233
+ id: "footer",
234
+ width: dimensions().width,
235
+ height: 1,
236
+ position: "absolute",
237
+ bottom: 0,
238
+ left: 0,
239
+ backgroundColor: "#000000",
240
+ paddingLeft: 1,
241
+ paddingRight: 1,
242
+ flexDirection: "row",
243
+ justifyContent: "space-between",
244
+ alignItems: "center"
245
+ },
246
+ /* @__PURE__ */ React.createElement("text", { fg: "#00ffff", wrapMode: "none", width: "100%" }, usageText),
247
+ /* @__PURE__ */ React.createElement("box", { flexShrink: 0, paddingLeft: 1 }, /* @__PURE__ */ React.createElement("text", { fg: "#00ffff" }, versionText))
248
+ ));
249
+ }
250
+ export {
251
+ runInteractiveApp
252
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@satori-sh/cli",
3
- "version": "0.0.15",
3
+ "version": "0.0.16",
4
4
  "description": "CLI tool for Satori memory server",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",