@jellyos/agent 0.1.1

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 (59) hide show
  1. package/README.md +375 -0
  2. package/bin/jellyagent +31 -0
  3. package/dist/api/ExtensionAPI.d.ts +92 -0
  4. package/dist/api/ExtensionAPI.d.ts.map +1 -0
  5. package/dist/api/ExtensionAPI.js +15 -0
  6. package/dist/api/ExtensionAPI.js.map +1 -0
  7. package/dist/api/Registry.d.ts +54 -0
  8. package/dist/api/Registry.d.ts.map +1 -0
  9. package/dist/api/Registry.js +101 -0
  10. package/dist/api/Registry.js.map +1 -0
  11. package/dist/cli.d.ts +6 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +134 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/index.d.ts +18 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +24 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/loader.d.ts +23 -0
  20. package/dist/loader.d.ts.map +1 -0
  21. package/dist/loader.js +84 -0
  22. package/dist/loader.js.map +1 -0
  23. package/dist/runner/AgentRunner.d.ts +66 -0
  24. package/dist/runner/AgentRunner.d.ts.map +1 -0
  25. package/dist/runner/AgentRunner.js +179 -0
  26. package/dist/runner/AgentRunner.js.map +1 -0
  27. package/dist/runner/ModelClient.d.ts +77 -0
  28. package/dist/runner/ModelClient.d.ts.map +1 -0
  29. package/dist/runner/ModelClient.js +224 -0
  30. package/dist/runner/ModelClient.js.map +1 -0
  31. package/dist/runner/SwarmRouter.d.ts +58 -0
  32. package/dist/runner/SwarmRouter.d.ts.map +1 -0
  33. package/dist/runner/SwarmRouter.js +153 -0
  34. package/dist/runner/SwarmRouter.js.map +1 -0
  35. package/dist/runner/ToolDispatcher.d.ts +19 -0
  36. package/dist/runner/ToolDispatcher.d.ts.map +1 -0
  37. package/dist/runner/ToolDispatcher.js +64 -0
  38. package/dist/runner/ToolDispatcher.js.map +1 -0
  39. package/dist/session/SessionManager.d.ts +23 -0
  40. package/dist/session/SessionManager.d.ts.map +1 -0
  41. package/dist/session/SessionManager.js +50 -0
  42. package/dist/session/SessionManager.js.map +1 -0
  43. package/dist/tui/App.d.ts +18 -0
  44. package/dist/tui/App.d.ts.map +1 -0
  45. package/dist/tui/App.js +188 -0
  46. package/dist/tui/App.js.map +1 -0
  47. package/dist/tui/REPL.d.ts +22 -0
  48. package/dist/tui/REPL.d.ts.map +1 -0
  49. package/dist/tui/REPL.js +46 -0
  50. package/dist/tui/REPL.js.map +1 -0
  51. package/dist/tui/StatusBar.d.ts +16 -0
  52. package/dist/tui/StatusBar.d.ts.map +1 -0
  53. package/dist/tui/StatusBar.js +15 -0
  54. package/dist/tui/StatusBar.js.map +1 -0
  55. package/dist/tui/theme.d.ts +30 -0
  56. package/dist/tui/theme.d.ts.map +1 -0
  57. package/dist/tui/theme.js +41 -0
  58. package/dist/tui/theme.js.map +1 -0
  59. package/package.json +59 -0
package/README.md ADDED
@@ -0,0 +1,375 @@
1
+ # @jellyos/agent
2
+
3
+ <div align="center">
4
+
5
+ <pre>
6
+ ██╗███████╗██╗ ██╗ ██╗ ██╗ ██████╗ ███████╗
7
+ ██║██╔════╝██║ ██║ ╚██╗ ██╔╝ ██╔═══██╗██╔════╝
8
+ ██║█████╗ ██║ ██║ ╚████╔╝ ██║ ██║███████╗
9
+ ██ ██║██╔══╝ ██║ ██║ ╚██╔╝ ██║ ██║╚════██║
10
+ ╚█████╔╝███████╗███████╗███████╗██║ ╚██████╔╝███████║
11
+ ╚════╝ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═════╝ ╚══════╝
12
+ </pre>
13
+
14
+ **Standalone AI trading agent. Runs 100% locally. No server. No inbound ports. No cloud dependency.**
15
+
16
+ [![npm](https://img.shields.io/npm/v/@jellyos/agent?color=14b8a6&style=flat-square)](https://www.npmjs.com/package/@jellyos/agent)
17
+ [![Node](https://img.shields.io/badge/node-%3E%3D20-brightgreen?style=flat-square)](https://nodejs.org)
18
+ [![License: MIT](https://img.shields.io/badge/license-MIT-yellow?style=flat-square)](LICENSE)
19
+
20
+ [Website](http://jelly-os.xyz/) • [Telegram](https://t.me/jellyxchain) • [X / Twitter](https://x.com/agentz010) • [API](https://jellychain.fun)
21
+
22
+ </div>
23
+
24
+ ---
25
+
26
+ ## What is JellyOS?
27
+
28
+ JellyOS is an AI trading agent that runs entirely in your terminal. It connects to AI model providers and blockchain data APIs through outbound HTTP calls only — nothing is hosted, nothing listens for inbound connections, and your keys never leave your machine.
29
+
30
+ ```
31
+ Your machine
32
+ ├── jellyos (CLI)
33
+ │ ├── Ink TUI ← streaming terminal UI
34
+ │ ├── AgentRunner ← model loop + tool dispatcher
35
+ │ ├── SwarmRouter ← parallel sub-agent orchestration
36
+ │ ├── FeedManager ← 21 live data sources (background)
37
+ │ ├── SignalEngine ← cross-feed trading signals
38
+ │ ├── WalletManager ← EVM / Solana / Cosmos keypairs
39
+ │ ├── VaultManager ← AES-256-GCM encrypted profit vault
40
+ │ └── DashboardServer ← optional local WebSocket dashboard
41
+
42
+ ├── ~/.jellyos/
43
+ │ ├── .env ← your API keys
44
+ │ ├── wallets/ ← local keypairs (never synced)
45
+ │ ├── vault/ ← encrypted vault file
46
+ │ └── context.json ← session state, watchlist, positions
47
+
48
+ └── Outbound only:
49
+ ├── openrouter.ai ← AI model gateway (your key)
50
+ ├── api.coingecko.com ← prices, trending
51
+ ├── api.binance.com ← 24h tickers
52
+ ├── api.llama.fi ← DeFi TVL
53
+ ├── alchemy.com ← on-chain data (optional key)
54
+ ├── mempool.space ← BTC mempool
55
+ ├── etherscan.io ← ETH gas
56
+ └── ... 15 more sources
57
+ ```
58
+
59
+ ---
60
+
61
+ ## Install
62
+
63
+ ```bash
64
+ npm install -g @jellyos/agent
65
+ ```
66
+
67
+ ## Quick Start
68
+
69
+ ```bash
70
+ # 1. Create config directory and set your API key
71
+ mkdir -p ~/.jellyos
72
+ echo "OPENROUTER_API_KEY=sk-or-..." > ~/.jellyos/.env
73
+
74
+ # 2. Launch
75
+ jellyos
76
+ ```
77
+
78
+ The terminal boots with an ASCII header and drops you into the `jell>` prompt.
79
+
80
+ ---
81
+
82
+ ## Using the Full JellyOS Stack
83
+
84
+ For wallets, vault, live feeds, and all 28 trading tools:
85
+
86
+ ```bash
87
+ # Clone the full project
88
+ git clone https://github.com/jelly-chain/JellyOS.git
89
+ cd JellyOS
90
+
91
+ # Install dependencies
92
+ npm install
93
+
94
+ # Run one-command setup (generates wallets, vault ceremony, writes ~/.jellyos/.env)
95
+ bash setup.sh # macOS / Linux
96
+ # or
97
+ powershell -ExecutionPolicy Bypass -File setup.ps1 # Windows
98
+
99
+ # Launch — auto-detects extensions/jellyos.ts
100
+ jellyos
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Configuration
106
+
107
+ All config lives in `~/.jellyos/.env`. The setup wizard creates this for you, or create it manually:
108
+
109
+ ```bash
110
+ cp .env.example ~/.jellyos/.env
111
+ nano ~/.jellyos/.env
112
+ ```
113
+
114
+ ### AI Model Provider (pick one)
115
+
116
+ | Variable | Provider | Example models |
117
+ |----------|----------|----------------|
118
+ | `OPENROUTER_API_KEY` | OpenRouter (recommended) | `anthropic/claude-sonnet-4-5` |
119
+ | `ANTHROPIC_API_KEY` | Anthropic direct | `claude-sonnet-4-5-20251101` |
120
+ | `OPENAI_API_KEY` | OpenAI | `gpt-4o` |
121
+ | `OPENAI_BASE_URL` | Local (Ollama / LM Studio) | `http://localhost:11434/v1` |
122
+
123
+ ### Model Pool (optional — up to 5 models)
124
+
125
+ Configure a named pool. JellyOS rotates through them automatically on rate-limits or errors:
126
+
127
+ ```env
128
+ JELLY_MODEL_1=anthropic/claude-sonnet-4-5 # primary
129
+ JELLY_MODEL_2=openai/gpt-4o-mini # secondary
130
+ JELLY_MODEL_3=google/gemini-flash-1.5 # tertiary
131
+ JELLY_MODEL_4=meta-llama/llama-3-8b-instruct:free # budget / eco
132
+ JELLY_MODEL_5=deepseek/deepseek-chat # swarm fallback
133
+ ```
134
+
135
+ If not set, JellyOS uses built-in defaults for your provider.
136
+
137
+ ### Data & Chain APIs (optional)
138
+
139
+ | Variable | Description |
140
+ |----------|-------------|
141
+ | `ALCHEMY_KEY` | On-chain balances, gas, whale scanning across 16 EVM chains |
142
+ | `COINGLASS_API_KEY` | Funding rates + open interest (broader exchange coverage) |
143
+ | `DUNE_API_KEY` | Dune Analytics whale wallet tracking |
144
+ | `GLASSNODE_API_KEY` | On-chain metrics (active addresses, SOPR) |
145
+ | `POLYMARKET_API_KEY` | Prediction market data and trading |
146
+
147
+ ### Vault & Agent Behaviour
148
+
149
+ | Variable | Default | Description |
150
+ |----------|---------|-------------|
151
+ | `JELLY_EFFECT_LEVEL` | `normal` | Default power level: `eco` / `normal` / `turbo` / `max` |
152
+ | `AUTO_VAULT_THRESHOLD` | `500` | Auto-sweep P&L to vault when this USD amount is exceeded |
153
+ | `JELLY_DASHBOARD_PORT` | `4320` | WebSocket port for the local dashboard |
154
+ | `JELLY_MAX_AGENTS` | `5` | Max parallel sub-agents in swarm mode |
155
+
156
+ ---
157
+
158
+ ## Effect Levels
159
+
160
+ Switch at any time with `/effect <level>`. Takes effect immediately on your next message.
161
+
162
+ | Level | Power | Swarm | Best For |
163
+ |-------|-------|-------|----------|
164
+ | `eco` | 30% | Off | Quick balance checks, simple questions |
165
+ | `normal` | 50% | Off | Default — most everyday tasks |
166
+ | `turbo` | 70% | 2 agents | Analysis + signal generation in parallel |
167
+ | `max` | 100% | 5 agents | Multi-chain deep research, complex strategies |
168
+
169
+ **How swarm works (`turbo` / `max`):**
170
+ Complex prompts are automatically scored using a conjunction + action-verb heuristic. When the score crosses the threshold, the prompt is decomposed into 2–5 focused sub-tasks. Sub-tasks run in groups of 3 (parallel within each group, groups run sequentially). A reviewer model then synthesises all results into a single coherent answer.
171
+
172
+ ---
173
+
174
+ ## Live Data Feeds
175
+
176
+ JellyOS polls 21 sources in the background and injects relevant data into every agent turn:
177
+
178
+ | Source | Interval | Data |
179
+ |--------|----------|------|
180
+ | CoinGecko prices | 5 min | Top asset prices + 24h change |
181
+ | Binance tickers | 2 min | Top 8 USDT pairs by volume |
182
+ | CoinGecko trending | 30 min | 7 trending coins |
183
+ | Fear & Greed Index | 1 hr | Alternative.me sentiment score |
184
+ | DeFiLlama TVL | 1 hr | Total TVL by chain |
185
+ | DeFiLlama protocols | 1 hr | Top TVL movers |
186
+ | Global market cap | 1 hr | BTC dominance, total cap, 24h change |
187
+ | Messari RSS | 10 min | Latest news headlines |
188
+ | CoinTelegraph RSS | 10 min | Latest news headlines |
189
+ | Coinglass funding rates | 15 min | BTC funding rates across exchanges |
190
+ | Coinglass open interest | 15 min | BTC OI aggregate |
191
+ | Etherscan gas | 1 min | ETH base / fast / slow gas |
192
+ | Reddit sentiment | 30 min | r/CryptoCurrency hot posts |
193
+ | Solana TPS | 5 min | Network transactions per second |
194
+ | BTC mempool | 3 min | Pending txs + fee rates |
195
+ | Polymarket trends | 30 min | Trending prediction markets |
196
+ | Whale watch | 10 min | Large on-chain movements (Alchemy) |
197
+ | Dune Analytics | 1 hr | Whale wallet tracker (API key required) |
198
+ | Glassnode | 1 hr | BTC active addresses (API key required) |
199
+ | CryptoCompare social | 2 hr | Twitter / Reddit social stats |
200
+ | Kalshi markets | 30 min | Prediction market odds |
201
+
202
+ ---
203
+
204
+ ## REPL Commands
205
+
206
+ ```
207
+ /help List all available commands
208
+ /effect [level] Set effect level: eco | normal | turbo | max
209
+ /vault Show encrypted vault balance
210
+ /unlock <passphrase> Unlock the vault for sweeps
211
+ /lock Lock the vault immediately
212
+ /agents Show live swarm routing status
213
+ /feeds Show feed statistics (last fetch, item count)
214
+ /wallets Show all wallet addresses (EVM / Solana / Cosmos)
215
+ /signals Request current trading signals from signal engine
216
+ /status Full system health check (feeds, vault, model, wallets)
217
+ /clear Clear conversation history
218
+ /panic Emergency stop — closes all positions, sweeps vault, locks, halts feeds
219
+ /exit Quit JellyOS
220
+ ```
221
+
222
+ ---
223
+
224
+ ## Tools Reference
225
+
226
+ The AI calls these tools automatically. You can also ask for them in plain language.
227
+
228
+ | Tool | Description |
229
+ |------|-------------|
230
+ | `get_balance` | Wallet balance on any supported chain |
231
+ | `get_wallet_addresses` | All generated wallet addresses |
232
+ | `sign_transaction` | Sign a tx hex in-memory (key never broadcast) |
233
+ | `vault_status` | Vault balance + lock state + entry count |
234
+ | `vault_sweep` | Move profits from trading account into vault |
235
+ | `vault_history` | Last 50 vault entries |
236
+ | `get_live_feeds` | Latest items from all active feed sources |
237
+ | `get_signals` | Active directional signals from the signal engine |
238
+ | `get_fear_greed` | Crypto Fear & Greed Index (current + 7d history) |
239
+ | `get_funding_rates` | Perpetual funding rates across exchanges |
240
+ | `get_market_data` | Prices + 24h change for up to 10 assets |
241
+ | `get_defi_tvl` | DeFiLlama TVL by chain or protocol |
242
+ | `get_gas_prices` | Gas prices across EVM chains |
243
+ | `scan_chain` | Scan recent blocks for large whale transactions |
244
+ | `get_polymarket` | Trending prediction markets + current odds |
245
+ | `predict_market` | AI-generated price prediction for an asset |
246
+ | `execute_trade` | Submit a swap (requires explicit user confirmation) |
247
+ | `get_positions` | List all open positions |
248
+ | `get_portfolio` | Full portfolio summary with P&L |
249
+ | `calculate_risk` | Risk/reward ratio + recommended position size |
250
+ | `set_stop_loss` | Update a stop-loss level |
251
+ | `execute_skill` | Run a saved trading strategy |
252
+ | `list_skills` | Show available strategy skills |
253
+ | `get_system_status` | Health check for all agent subsystems |
254
+ | `get_context` | Read persistent session context |
255
+ | `set_context` | Write persistent session context |
256
+ | `get_news` | Latest crypto news headlines |
257
+ | `get_chain_list` | All supported chains with IDs |
258
+
259
+ ---
260
+
261
+ ## Supported Chains
262
+
263
+ Ethereum, BSC, Arbitrum, Base, Polygon, Avalanche, Optimism, Scroll, Linea, zkSync Era, Mantle, Blast, Solana, Cosmos, and more via Alchemy and public RPCs.
264
+
265
+ ---
266
+
267
+ ## Local Dashboard
268
+
269
+ An optional React dashboard connects to the agent over WebSocket and shows live data:
270
+
271
+ ```bash
272
+ cd dashboard
273
+ npm install
274
+ npm run dev
275
+ # Open http://localhost:4321
276
+ ```
277
+
278
+ Real-time events pushed from the agent:
279
+
280
+ | Event | Description |
281
+ |-------|-------------|
282
+ | `feed_item` | New item from any live feed |
283
+ | `log_entry` | Agent / tool conversation messages |
284
+ | `trade_executed` | A trade was submitted |
285
+ | `vault_sweep` | Auto-vault swept profits |
286
+ | `swarm_update` | Sub-agent start / complete events |
287
+ | `signal_update` | New trading signal generated |
288
+ | `vault_update` | Vault balance changed |
289
+ | `panic` | Emergency stop triggered |
290
+
291
+ ---
292
+
293
+ ## Extension API
294
+
295
+ Build your own tools, commands, and hooks that run inside the JellyOS agent loop:
296
+
297
+ ```typescript
298
+ import { Type } from "@jellyos/agent";
299
+ import type { ExtensionAPI } from "@jellyos/agent";
300
+
301
+ export default function (agent: ExtensionAPI) {
302
+ // Set the AI's system prompt
303
+ agent.setSystemPrompt("You are a DeFi yield optimizer.");
304
+
305
+ // Register a slash command
306
+ agent.registerCommand("yields", {
307
+ description: "Show top yield opportunities",
308
+ async handler(_args, ctx) {
309
+ ctx.ui.notify("Fetching yields...");
310
+ },
311
+ });
312
+
313
+ // Register a tool the AI can call automatically
314
+ agent.registerTool({
315
+ name: "get_apy",
316
+ label: "Get APY",
317
+ description: "Fetch current APY for a DeFi protocol",
318
+ parameters: Type.Object({
319
+ protocol: Type.String({ description: "Protocol name, e.g. aave" }),
320
+ }),
321
+ async execute(_id, { protocol }) {
322
+ const res = await fetch(`https://api.llama.fi/protocol/${protocol}`);
323
+ const data = await res.json() as any;
324
+ return {
325
+ content: [{ type: "text", text: `APY data for ${protocol}: ${JSON.stringify(data.apy)}` }],
326
+ details: {},
327
+ };
328
+ },
329
+ });
330
+
331
+ // Lifecycle hooks
332
+ agent.on("session_start", async (ctx) => {
333
+ ctx.ui.setStatus("yields", "ready");
334
+ ctx.ui.notify("Yield optimizer loaded.");
335
+ });
336
+
337
+ agent.on("session_end", async () => {
338
+ // cleanup
339
+ });
340
+ }
341
+ ```
342
+
343
+ Load your extension at startup:
344
+
345
+ ```bash
346
+ jellyos --extension ./my-extension.ts
347
+ ```
348
+
349
+ ---
350
+
351
+ ## CLI Reference
352
+
353
+ ```bash
354
+ jellyos # interactive TUI (auto-loads extensions/)
355
+ jellyos --extension /path/to/ext.ts # load a specific extension file
356
+ jellyos --prompt /path/to/system.md # override system prompt from file
357
+ jellyos config # show current config (keys masked)
358
+ jellyos setup # run the setup wizard
359
+ ```
360
+
361
+ ---
362
+
363
+ ## Security
364
+
365
+ - **Keys stay local** — API keys are read from `~/.jellyos/.env` at startup and never logged or transmitted beyond outbound API calls
366
+ - **Private keys never leave the process** — signing happens in memory; only the resulting signature is returned
367
+ - **Vault encryption** — AES-256-GCM with a key derived from your passphrase using scrypt (memory-hard KDF) + random per-vault salt; the key itself is never persisted
368
+ - **Auto-lock** — vault locks automatically on `/panic` and on process exit
369
+ - **Wallet storage** — keypairs are written to `~/.jellyos/wallets/` which is in `.gitignore` and never included in any sync or backup by the agent
370
+
371
+ ---
372
+
373
+ ## License
374
+
375
+ MIT — see [LICENSE](LICENSE)
package/bin/jellyagent ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * jellyagent / jellyos — CLI entry point.
4
+ * Uses tsx for transparent TypeScript support at runtime.
5
+ * No compilation step needed when running from source.
6
+ */
7
+ import { createRequire } from "module";
8
+ import { fileURLToPath } from "url";
9
+ import { dirname, join } from "path";
10
+ import { existsSync } from "fs";
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const require = createRequire(import.meta.url);
14
+
15
+ // Prefer pre-built dist/ if available, otherwise run TypeScript source via tsx
16
+ const distEntry = join(__dirname, "..", "dist", "cli.js");
17
+ const srcEntry = join(__dirname, "..", "src", "cli.ts");
18
+
19
+ if (existsSync(distEntry)) {
20
+ // Built package — just run the compiled JS
21
+ await import(distEntry);
22
+ } else if (existsSync(srcEntry)) {
23
+ // Development / source install — run via tsx
24
+ const { register } = await import("tsx/esm");
25
+ // tsx registers TypeScript support on the module system
26
+ await import(srcEntry);
27
+ } else {
28
+ console.error("JellyOS: could not find dist/cli.js or src/cli.ts");
29
+ console.error("Run: npm run build or pnpm build");
30
+ process.exit(1);
31
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * ExtensionAPI — the interface exposed to extension files (e.g. extensions/jellyos.ts).
3
+ *
4
+ * Drop-in replacement for Pi's ExtensionAPI. Extension files that ran under Pi
5
+ * work without changes — all Pi methods and calling conventions are preserved:
6
+ * - ui.setStatus, ui.setTheme, ui.setHeader
7
+ * - ctx.hasUI
8
+ * - pi.on("session_start", async (_event, ctx) => { ... }) ← Pi convention
9
+ * - pi.on("session_shutdown", async () => { ... })
10
+ * - pi.on("before_agent_start", async (_event, ctx) => { ... })
11
+ */
12
+ import { type Static, type TObject, type TSchema } from "@sinclair/typebox";
13
+ export type { Static, TObject, TSchema };
14
+ export interface ToolContent {
15
+ content: Array<{
16
+ type: "text";
17
+ text: string;
18
+ }>;
19
+ details: Record<string, unknown>;
20
+ }
21
+ export declare function text(t: string): ToolContent;
22
+ export interface ToolDef<P extends TSchema = TSchema> {
23
+ name: string;
24
+ label: string;
25
+ description: string;
26
+ parameters: P;
27
+ execute(id: string, params: Static<P>): Promise<ToolContent>;
28
+ }
29
+ export interface TuiHeader {
30
+ render(width: number): string[];
31
+ invalidate(): void;
32
+ }
33
+ export type HeaderFactory = (tui: unknown, theme: ThemeContext) => TuiHeader;
34
+ export interface ThemeContext {
35
+ /** Apply a named colour to text. Colours: accent, error, warn, muted, success, border, dim */
36
+ fg(color: string, text: string): string;
37
+ }
38
+ export interface UIContext {
39
+ /** Show a bordered notification in the REPL panel */
40
+ notify(message: string): void;
41
+ /**
42
+ * Set a named status badge in the status bar.
43
+ * Pi compat — multiple badges can be shown simultaneously.
44
+ * @param key Badge slot name (e.g. "vault", "jelly", "chain")
45
+ * @param value Themed text to display
46
+ */
47
+ setStatus(key: string, value: string): void;
48
+ /**
49
+ * Activate a named theme. Pi compat — engine uses jelly theme by default.
50
+ */
51
+ setTheme(name: string): void;
52
+ /**
53
+ * Replace the TUI header with a custom rendering factory.
54
+ * Pi compat — accepted but engine renders its own Ink header.
55
+ */
56
+ setHeader(factory: HeaderFactory): void;
57
+ theme: ThemeContext;
58
+ }
59
+ export interface CommandContext {
60
+ ui: UIContext;
61
+ }
62
+ export interface CommandDef {
63
+ description: string;
64
+ handler(args: string, ctx: CommandContext): Promise<void>;
65
+ }
66
+ export interface SkillDef {
67
+ name: string;
68
+ content: string;
69
+ }
70
+ export type SessionEvent = "session_start" | "session_end" | "session_shutdown" | "before_agent_start";
71
+ export interface SessionContext {
72
+ ui: UIContext;
73
+ /** True when running inside an interactive TUI (always true for @jellychain/agent) */
74
+ hasUI: boolean;
75
+ config: Record<string, string | undefined>;
76
+ }
77
+ export interface ExtensionAPI {
78
+ registerCommand(name: string, def: CommandDef): void;
79
+ registerTool<P extends TSchema>(def: ToolDef<P>): void;
80
+ registerSkill(def: SkillDef): void;
81
+ /**
82
+ * Subscribe to a lifecycle event.
83
+ * Pi calling convention: handler receives (event, ctx).
84
+ * Handlers may use any arity: (event, ctx) | (ctx) | () — all are safe.
85
+ */
86
+ on(event: SessionEvent, handler: (...args: any[]) => Promise<void>): void;
87
+ setSystemPrompt(prompt: string): void;
88
+ /** Direct UI access available at any time */
89
+ ui: UIContext;
90
+ }
91
+ export type ExtensionModule = (api: ExtensionAPI) => void | Promise<void>;
92
+ //# sourceMappingURL=ExtensionAPI.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExtensionAPI.d.ts","sourceRoot":"","sources":["../../src/api/ExtensionAPI.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * ExtensionAPI — the interface exposed to extension files (e.g. extensions/jellyos.ts).
3
+ *
4
+ * Drop-in replacement for Pi's ExtensionAPI. Extension files that ran under Pi
5
+ * work without changes — all Pi methods and calling conventions are preserved:
6
+ * - ui.setStatus, ui.setTheme, ui.setHeader
7
+ * - ctx.hasUI
8
+ * - pi.on("session_start", async (_event, ctx) => { ... }) ← Pi convention
9
+ * - pi.on("session_shutdown", async () => { ... })
10
+ * - pi.on("before_agent_start", async (_event, ctx) => { ... })
11
+ */
12
+ export function text(t) {
13
+ return { content: [{ type: "text", text: t }], details: {} };
14
+ }
15
+ //# sourceMappingURL=ExtensionAPI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExtensionAPI.js","sourceRoot":"","sources":["../../src/api/ExtensionAPI.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAaH,MAAM,UAAU,IAAI,CAAC,CAAS;IAC5B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC/D,CAAC"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Registry — stores all commands, tools, and skills registered by extension files.
3
+ *
4
+ * Pi calling convention for lifecycle hooks:
5
+ * session_start / session_end / session_shutdown: handler(event, ctx)
6
+ * before_agent_start: handler(event, ctx)
7
+ *
8
+ * We always call hooks as handler(undefined, ctx) — handlers that expect only
9
+ * one argument (ctx) will receive it as the first positional parameter only if
10
+ * they follow Pi's two-arg (event, ctx) convention; handlers with no args are
11
+ * also safe. This matches Pi's actual calling convention exactly.
12
+ */
13
+ import type { CommandDef, ToolDef, SkillDef, SessionEvent, SessionContext } from "./ExtensionAPI.js";
14
+ import type { TSchema } from "@sinclair/typebox";
15
+ export declare class Registry {
16
+ private commands;
17
+ private tools;
18
+ private skills;
19
+ private hooks;
20
+ private _systemPrompt;
21
+ addCommand(name: string, def: CommandDef): void;
22
+ addTool<P extends TSchema>(def: ToolDef<P>): void;
23
+ addSkill(def: SkillDef): void;
24
+ addHook(event: SessionEvent, handler: (...args: any[]) => Promise<void>): void;
25
+ setSystemPrompt(prompt: string): void;
26
+ getCommand(name: string): CommandDef | undefined;
27
+ listCommands(): Array<[string, CommandDef]>;
28
+ listTools(): Array<ToolDef<TSchema>>;
29
+ getTool(name: string): ToolDef<TSchema> | undefined;
30
+ listSkills(): SkillDef[];
31
+ getSystemPrompt(): string;
32
+ /**
33
+ * Fire hooks for a lifecycle event.
34
+ *
35
+ * Supports all handler arities so Pi-compat and simplified extensions both work:
36
+ * h.length === 0 : async () => { ... } → called with no args
37
+ * h.length === 1 : async (ctx) => { ... } → called with ctx as first arg
38
+ * h.length >= 2 : async (event, ctx) => { ... } → Pi convention: (undefined, ctx)
39
+ *
40
+ * Using Function.length (declared parameter count) lets us detect intent without
41
+ * runtime type checks, avoiding the bug where h(undefined, ctx) puts `undefined`
42
+ * into the `ctx` parameter of a single-argument handler.
43
+ */
44
+ fireHook(event: string, ctx: SessionContext): Promise<void>;
45
+ toOpenAITools(): Array<{
46
+ type: "function";
47
+ function: {
48
+ name: string;
49
+ description: string;
50
+ parameters: unknown;
51
+ };
52
+ }>;
53
+ }
54
+ //# sourceMappingURL=Registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Registry.d.ts","sourceRoot":"","sources":["../../src/api/Registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACV,UAAU,EAAE,OAAO,EAAE,QAAQ,EAC7B,YAAY,EAAE,cAAc,EAC7B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAMjD,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,KAAK,CAA0C;IACvD,OAAO,CAAC,MAAM,CAAmB;IAEjC,OAAO,CAAC,KAAK,CAAgC;IAE7C,OAAO,CAAC,aAAa,CAAM;IAI3B,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,IAAI;IAI/C,OAAO,CAAC,CAAC,SAAS,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI;IAIjD,QAAQ,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI;IAI7B,OAAO,CAAC,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAQ9E,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAMrC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAIhD,YAAY,IAAI,KAAK,CAAC,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAI3C,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAIpC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,SAAS;IAInD,UAAU,IAAI,QAAQ,EAAE;IAIxB,eAAe,IAAI,MAAM;IAMzB;;;;;;;;;;;OAWG;IACG,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBjE,aAAa,IAAI,KAAK,CAAC;QACrB,IAAI,EAAE,UAAU,CAAC;QACjB,QAAQ,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,OAAO,CAAA;SAAE,CAAC;KACtE,CAAC;CAUH"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Registry — stores all commands, tools, and skills registered by extension files.
3
+ *
4
+ * Pi calling convention for lifecycle hooks:
5
+ * session_start / session_end / session_shutdown: handler(event, ctx)
6
+ * before_agent_start: handler(event, ctx)
7
+ *
8
+ * We always call hooks as handler(undefined, ctx) — handlers that expect only
9
+ * one argument (ctx) will receive it as the first positional parameter only if
10
+ * they follow Pi's two-arg (event, ctx) convention; handlers with no args are
11
+ * also safe. This matches Pi's actual calling convention exactly.
12
+ */
13
+ export class Registry {
14
+ commands = new Map();
15
+ tools = new Map();
16
+ skills = [];
17
+ hooks = new Map();
18
+ _systemPrompt = "";
19
+ // ── Registration ────────────────────────────────────────────────────────
20
+ addCommand(name, def) {
21
+ this.commands.set(name.toLowerCase(), def);
22
+ }
23
+ addTool(def) {
24
+ this.tools.set(def.name, def);
25
+ }
26
+ addSkill(def) {
27
+ this.skills.push(def);
28
+ }
29
+ addHook(event, handler) {
30
+ // Normalize aliases: session_shutdown → session_end
31
+ const key = event === "session_shutdown" ? "session_end" : event;
32
+ const list = this.hooks.get(key) ?? [];
33
+ list.push(handler);
34
+ this.hooks.set(key, list);
35
+ }
36
+ setSystemPrompt(prompt) {
37
+ this._systemPrompt = prompt;
38
+ }
39
+ // ── Reads ────────────────────────────────────────────────────────────────
40
+ getCommand(name) {
41
+ return this.commands.get(name.toLowerCase());
42
+ }
43
+ listCommands() {
44
+ return [...this.commands.entries()];
45
+ }
46
+ listTools() {
47
+ return [...this.tools.values()];
48
+ }
49
+ getTool(name) {
50
+ return this.tools.get(name);
51
+ }
52
+ listSkills() {
53
+ return this.skills;
54
+ }
55
+ getSystemPrompt() {
56
+ return this._systemPrompt;
57
+ }
58
+ // ── Hook dispatch ────────────────────────────────────────────────────────
59
+ /**
60
+ * Fire hooks for a lifecycle event.
61
+ *
62
+ * Supports all handler arities so Pi-compat and simplified extensions both work:
63
+ * h.length === 0 : async () => { ... } → called with no args
64
+ * h.length === 1 : async (ctx) => { ... } → called with ctx as first arg
65
+ * h.length >= 2 : async (event, ctx) => { ... } → Pi convention: (undefined, ctx)
66
+ *
67
+ * Using Function.length (declared parameter count) lets us detect intent without
68
+ * runtime type checks, avoiding the bug where h(undefined, ctx) puts `undefined`
69
+ * into the `ctx` parameter of a single-argument handler.
70
+ */
71
+ async fireHook(event, ctx) {
72
+ const key = event === "session_shutdown" ? "session_end" : event;
73
+ const handlers = this.hooks.get(key) ?? [];
74
+ for (const h of handlers) {
75
+ try {
76
+ if (h.length === 0) {
77
+ await h(); // () => {...}
78
+ }
79
+ else if (h.length === 1) {
80
+ await h(ctx); // (ctx) => {...}
81
+ }
82
+ else {
83
+ await h(undefined, ctx); // (_event, ctx) => {...} — Pi convention
84
+ }
85
+ }
86
+ catch { /* hooks must not crash the session */ }
87
+ }
88
+ }
89
+ // ── OpenAI tool schema ───────────────────────────────────────────────────
90
+ toOpenAITools() {
91
+ return [...this.tools.values()].map(t => ({
92
+ type: "function",
93
+ function: {
94
+ name: t.name,
95
+ description: t.description,
96
+ parameters: t.parameters,
97
+ },
98
+ }));
99
+ }
100
+ }
101
+ //# sourceMappingURL=Registry.js.map