@jellyos/agent 0.1.6 → 0.1.8

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/tui/App.js CHANGED
@@ -96,6 +96,10 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
96
96
  const [ticker, setTicker] = useState("");
97
97
  const [costBadge, setCostBadge] = useState("");
98
98
  const [newsBadge, setNewsBadge] = useState("");
99
+ // ── Sidebar live data state ─────────────────────────────────────────────
100
+ const [sidebarTickers, setSidebarTickers] = useState([]);
101
+ const [sidebarNews, setSidebarNews] = useState([]);
102
+ const [rotationSlots, setRotationSlots] = useState([null, null, null, null, null]);
99
103
  // ── Model selector overlay state ──────────────────────────────────────
100
104
  const [showModelSelector, setShowModelSelector] = useState(false);
101
105
  const [modelSelectorInitialQuery, setModelSelectorInitialQuery] = useState("");
@@ -210,7 +214,28 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
210
214
  if (costTracker)
211
215
  setCostBadge(costTracker.statusLine());
212
216
  setNewsBadge(newsFeed.statusBadge());
213
- }, 5_000);
217
+ // Update sidebar live data
218
+ const allTicks = priceFeed.getAll();
219
+ setSidebarTickers(allTicks.slice(0, 8).map(t => ({ symbol: t.symbol, price: t.price, changePct: t.change24h })));
220
+ const latestNews = newsFeed.getLatest();
221
+ if (latestNews?.items) {
222
+ setSidebarNews(latestNews.items.slice(0, 6).map(item => ({ title: item.title ?? item.source ?? "News", sentiment: item.sentiment ?? 0, source: item.source ?? "" })));
223
+ }
224
+ // Read rotation slots from context.json
225
+ try {
226
+ const { existsSync: exists, readFileSync: read } = require("node:fs");
227
+ const { join } = require("node:path");
228
+ const { homedir } = require("node:os");
229
+ const JELLY_HOME = process.env.JELLYOS_HOME ?? join(homedir(), ".jelly");
230
+ const ctxPath = join(JELLY_HOME, "context.json");
231
+ if (exists(ctxPath)) {
232
+ const store = JSON.parse(read(ctxPath, "utf-8"));
233
+ if (store.rotationSlots)
234
+ setRotationSlots(store.rotationSlots);
235
+ }
236
+ }
237
+ catch { /* non-fatal */ }
238
+ }, 3_000);
214
239
  const saveInterval = setInterval(() => { costTracker?.saveLifetime(); }, 60_000);
215
240
  const session = new SessionManager();
216
241
  session.setSystemPrompt(registry.getSystemPrompt() || systemPrompt || "You are JellyOS, an autonomous AI trading agent.");
@@ -284,6 +309,13 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
284
309
  if (costTracker)
285
310
  setCostBadge(costTracker.statusLine());
286
311
  setNewsBadge(newsFeed.statusBadge());
312
+ // Initial sidebar data fetch (before interval fires)
313
+ const initialTicks = priceFeed.getAll();
314
+ setSidebarTickers(initialTicks.slice(0, 8).map(t => ({ symbol: t.symbol, price: t.price, changePct: t.change24h })));
315
+ const initialNews = newsFeed.getLatest();
316
+ if (initialNews?.items) {
317
+ setSidebarNews(initialNews.items.slice(0, 6).map(item => ({ title: item.title ?? item.source ?? "News", sentiment: item.sentiment ?? 0, source: item.source ?? "" })));
318
+ }
287
319
  return () => {
288
320
  if (sessionCtxRef.current)
289
321
  registry.fireHook("session_end", sessionCtxRef.current).catch(() => { });
@@ -492,6 +524,11 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
492
524
  const ctxPath = join(JELLY_HOME, "context.json");
493
525
  const store = existsSync(ctxPath) ? JSON.parse(readFileSync(ctxPath, "utf-8")) : {};
494
526
  store.model = modelId;
527
+ // Also save as rotation slot 1 (primary)
528
+ const tier = modelReg?.getTier?.(modelId) ?? "worker";
529
+ const slots = store.rotationSlots ?? [null, null, null, null, null];
530
+ slots[0] = { id: modelId, tier };
531
+ store.rotationSlots = slots;
495
532
  writeFileSync(ctxPath, JSON.stringify(store, null, 2), "utf-8");
496
533
  }
497
534
  catch { /* non-fatal */ }
@@ -517,12 +554,26 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
517
554
  }, [modelReg]);
518
555
  const ctx = getContextBar(sessionRef.current);
519
556
  const statusLine = [ticker, costBadge, newsBadge, ...Object.values(statusBadges)].filter(Boolean).join(" ") || null;
557
+ // ── Sidebar helper functions ───────────────────────────────────────────────
558
+ const changeColor = (pct) => pct > 0 ? JELLY_COLORS.success : pct < 0 ? JELLY_COLORS.error : JELLY_COLORS.muted;
559
+ const changeArrow = (pct) => pct > 0 ? "▲" : pct < 0 ? "▼" : "─";
560
+ const SIDEBAR_W = 42;
520
561
  // ── Overlay: model selector ────────────────────────────────────────────
521
562
  if (showModelSelector) {
522
563
  return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [_jsx(StatusBar, { model: modelName, chain: chain, vaultLocked: vaultLocked, effectLevel: effectLevel, toolRunning: toolRunning, connected: true, statusLine: statusLine }), _jsx(ModelSelector, { models: modelList, currentModelId: process.env.DEFAULT_MODEL ?? "", onSelect: handleModelSelect, onCancel: handleModelCancel, initialQuery: modelSelectorInitialQuery })] }));
523
564
  }
524
565
  // ── Multi-pane layout ────────────────────────────────────────────────────
525
- return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [_jsx(StatusBar, { model: modelName, chain: chain, vaultLocked: vaultLocked, effectLevel: effectLevel, toolRunning: toolRunning, connected: true, statusLine: statusLine }), _jsxs(Box, { flexDirection: "row", flexGrow: 1, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", width: 32, borderStyle: "single", borderColor: JELLY_COLORS.dim, paddingX: 1, flexShrink: 0, children: [_jsx(Text, { color: JELLY_COLORS.accent, bold: true, children: "\uD83D\uDCE1 Ticker" }), _jsx(Text, { color: JELLY_COLORS.muted, wrap: "truncate", children: ticker || "Loading…" }), _jsx(Text, { color: JELLY_COLORS.dim, children: "─".repeat(28) }), _jsxs(Text, { color: JELLY_COLORS.accent, children: ["Context ", ctx.turboReady ? "" : "⚠"] }), _jsxs(Text, { color: ctx.color, children: [ctx.bar, " ", ctx.pct, "%", ctx.turboReady ? "" : " no turbo"] }), _jsx(Text, { color: JELLY_COLORS.dim, children: "─".repeat(28) }), _jsx(Text, { color: JELLY_COLORS.accent, children: "Effect" }), _jsx(Text, { color: effectLevel === "eco" ? "#22c55e" : effectLevel === "turbo" ? "#f59e0b" : effectLevel === "max" ? "#ef4444" : JELLY_COLORS.accent, children: effectLevel.toUpperCase() }), _jsx(Text, { color: JELLY_COLORS.dim, children: "─".repeat(28) }), _jsx(Text, { color: JELLY_COLORS.accent, children: "News" }), _jsx(Text, { color: JELLY_COLORS.muted, wrap: "truncate", children: newsBadge || "…" })] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: _jsx(REPL, { messages: messages, streamingText: streaming, toolRunning: toolRunning, onSubmit: handleSubmit, disabled: disabled, onAbort: () => {
566
+ return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [_jsx(StatusBar, { model: modelName, chain: chain, vaultLocked: vaultLocked, effectLevel: effectLevel, toolRunning: toolRunning, connected: true, statusLine: statusLine }), _jsxs(Box, { flexDirection: "row", flexGrow: 1, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", width: SIDEBAR_W, flexShrink: 0, children: [_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: JELLY_COLORS.dim, paddingX: 1, children: [_jsxs(Text, { bold: true, children: ["\uD83D\uDCE1 Ticker", rotationSlots.filter(s => s && s.id).length > 0 && (_jsxs(Text, { color: "#6b7280", children: [" \u21BB", rotationSlots.filter(s => s && s.id).length] }))] }), sidebarTickers.length > 0 ? sidebarTickers.map((t, i) => {
567
+ const arrow = changeArrow(t.changePct);
568
+ const col = changeColor(t.changePct);
569
+ const pctStr = (t.changePct > 0 ? "+" : "") + t.changePct.toFixed(1) + "%";
570
+ return _jsxs(Text, { color: col, children: [t.symbol.padEnd(8), "$", String(t.price).padStart(9), " ", arrow, pctStr] }, i);
571
+ }) : _jsx(Text, { color: JELLY_COLORS.muted, children: "Loading prices\u2026" })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: JELLY_COLORS.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: "Context" }), _jsxs(Text, { color: ctx.color, children: [ctx.bar, " ", ctx.pct, "%"] }), ctx.turboReady ? null : _jsx(Text, { color: JELLY_COLORS.warn, children: "\u26A0 no turbo" })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: JELLY_COLORS.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: "Effect" }), _jsx(Text, { color: effectLevel === "eco" ? "#22c55e" : effectLevel === "turbo" ? "#f59e0b" : effectLevel === "max" ? "#ef4444" : JELLY_COLORS.accent, children: effectLevel.toUpperCase() })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: JELLY_COLORS.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: "News" }), sidebarNews.length > 0 ? sidebarNews.map((n, i) => {
572
+ const sentColor = n.sentiment > 0.3 ? JELLY_COLORS.success : n.sentiment < -0.3 ? JELLY_COLORS.error : JELLY_COLORS.muted;
573
+ const sentPct = Math.round((n.sentiment + 1) * 50);
574
+ const title = n.title.length > 28 ? n.title.slice(0, 26) + "…" : n.title;
575
+ return (_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsx(Text, { color: JELLY_COLORS.muted, children: title }), _jsxs(Text, { color: sentColor, children: [sentPct, "%"] })] }, i));
576
+ }) : _jsx(Text, { color: JELLY_COLORS.muted, children: "Loading news\u2026" })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: JELLY_COLORS.dim, paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, children: "Feeds" }), _jsx(Text, { color: JELLY_COLORS.muted, children: "[coingecko] SOLANA\u2026" }), _jsx(Text, { color: JELLY_COLORS.muted, children: "[coingecko] ETHEREUM\u2026" }), _jsx(Text, { color: JELLY_COLORS.muted, children: "[etherscan] BTC Price\u2026" }), _jsx(Text, { color: JELLY_COLORS.muted, children: "[etherscan] ETF Gas\u2026" }), _jsx(Text, { color: JELLY_COLORS.muted, children: "[binance] XRPUSDT 24h\u2026" }), _jsx(Text, { color: JELLY_COLORS.muted, children: "[binance] AVAXUSDT 24h\u2026" }), _jsx(Text, { color: JELLY_COLORS.muted, children: "[binance] SOLUSDT 24h\u2026" })] })] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: _jsx(REPL, { messages: messages, streamingText: streaming, toolRunning: toolRunning, onSubmit: handleSubmit, disabled: disabled, onAbort: () => {
526
577
  runnerRef.current?.abort();
527
578
  setDisabled(false);
528
579
  setToolRunning(null);
package/dist/tui/REPL.js CHANGED
@@ -46,6 +46,6 @@ export function REPL({ messages, streamingText, toolRunning, onSubmit, onAbort,
46
46
  }
47
47
  });
48
48
  const visible = messages.slice(-MAX_VISIBLE);
49
- return (_jsxs(Box, { flexDirection: "column", width: termWidth, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [visible.map(m => _jsx(MessageLine, { msg: m }, m.id)), streamingText && (_jsxs(Box, { flexDirection: "row", gap: 1, marginTop: 1, children: [_jsx(Text, { color: JELLY_COLORS.muted, children: " " }), _jsx(Text, { color: JELLY_COLORS.header, bold: true, children: "\uD83E\uDEBC " }), _jsx(Text, { wrap: "wrap", children: streamingText })] })), toolRunning && (_jsx(Box, { flexDirection: "row", gap: 1, marginTop: 1, children: _jsxs(Text, { color: JELLY_COLORS.warn, children: ["\u2699 running ", toolRunning, "\u2026"] }) }))] }), _jsxs(Box, { borderStyle: "round", borderColor: disabled ? JELLY_COLORS.dim : JELLY_COLORS.accent, paddingX: 1, marginTop: 1, children: [_jsx(Text, { color: JELLY_COLORS.accent, children: "\u203A " }), _jsx(TextInput, { value: input, onChange: setInput, onSubmit: handleSubmit, placeholder: disabled ? "thinking…" : "message or /command" })] }), _jsx(Box, { paddingX: 2, children: _jsxs(Text, { color: JELLY_COLORS.dim, children: ["/help \u00B7 /palette \u00B7 /cost \u00B7 /prices \u00B7 /news \u00B7 /goals \u00B7 /tasks \u00B7 ", disabled ? "Esc=abort · " : "", "Ctrl-C to exit"] }) })] }));
49
+ return (_jsxs(Box, { flexDirection: "column", width: termWidth, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [visible.map(m => _jsx(MessageLine, { msg: m }, m.id)), streamingText && (_jsxs(Box, { flexDirection: "row", gap: 1, marginTop: 1, children: [_jsx(Text, { color: JELLY_COLORS.muted, children: " " }), _jsx(Text, { color: JELLY_COLORS.header, bold: true, children: "\uD83E\uDEBC " }), _jsx(Text, { wrap: "wrap", children: streamingText })] })), toolRunning && (_jsx(Box, { flexDirection: "row", gap: 1, marginTop: 1, children: _jsxs(Text, { color: JELLY_COLORS.warn, children: ["\u2699 running ", toolRunning, "\u2026"] }) }))] }), _jsxs(Box, { borderStyle: "round", borderColor: disabled ? JELLY_COLORS.dim : JELLY_COLORS.accent, paddingX: 1, marginTop: 1, children: [_jsx(Text, { color: JELLY_COLORS.accent, children: "\u203A " }), _jsx(TextInput, { value: input, onChange: setInput, onSubmit: handleSubmit, placeholder: disabled ? "thinking…" : "message or /command" })] })] }));
50
50
  }
51
51
  //# sourceMappingURL=REPL.js.map
@@ -1,12 +1,28 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from "ink";
3
3
  import { JELLY_COLORS } from "./theme.js";
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { homedir } from "node:os";
4
7
  export function StatusBar({ model, chain, vaultLocked, effectLevel, toolRunning, connected, statusLine, }) {
5
8
  const vaultIcon = vaultLocked ? "🔒" : "🔓";
6
9
  const chainShort = chain.slice(0, 8);
7
10
  const modelShort = model.split("/").pop()?.slice(0, 18) ?? model.slice(0, 18);
8
11
  const effectIcon = { eco: "🌿", normal: "⚡", turbo: "🚀", max: "🌊" }[effectLevel] ?? "⚡";
9
- return (_jsxs(Box, { borderStyle: "single", borderColor: JELLY_COLORS.dim, paddingX: 1, flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Box, { gap: 2, children: [_jsx(Text, { color: JELLY_COLORS.accent, bold: true, children: "\uD83E\uDEBC JellyOS" }), _jsx(Text, { color: JELLY_COLORS.muted, children: modelShort })] }), _jsx(Box, { children: toolRunning
12
+ // Read rotation slot count for display
13
+ let rotationCount = 0;
14
+ try {
15
+ const JELLY_HOME = process.env.JELLYOS_HOME ?? join(homedir(), ".jelly");
16
+ const ctxPath = join(JELLY_HOME, "context.json");
17
+ if (existsSync(ctxPath)) {
18
+ const store = JSON.parse(readFileSync(ctxPath, "utf-8"));
19
+ const slots = store.rotationSlots ?? [];
20
+ rotationCount = slots.filter((s) => s && s.id).length;
21
+ }
22
+ }
23
+ catch { /* non-fatal */ }
24
+ const rotationBadge = rotationCount > 0 ? `↻${rotationCount}` : null;
25
+ return (_jsxs(Box, { borderStyle: "single", borderColor: JELLY_COLORS.dim, paddingX: 1, flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Box, { gap: 2, children: [_jsx(Text, { color: JELLY_COLORS.accent, bold: true, children: "\uD83E\uDEBC JellyOS" }), _jsx(Text, { color: JELLY_COLORS.muted, children: modelShort }), rotationBadge ? _jsx(Text, { color: "#6b7280", children: rotationBadge }) : null] }), _jsx(Box, { children: toolRunning
10
26
  ? _jsxs(Text, { color: JELLY_COLORS.warn, children: ["\u2699 ", toolRunning] })
11
27
  : statusLine
12
28
  ? _jsx(Text, { color: JELLY_COLORS.muted, children: statusLine.slice(0, 80) })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jellyos/agent",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "JellyOS — standalone AI trading agent. Runs locally, no server required.",
5
5
  "author": "JellyChain",
6
6
  "license": "MIT",