@albalink/agent 1.0.0
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/README.md +91 -0
- package/bin/albacli +31 -0
- package/dist/api/ExtensionAPI.d.ts +103 -0
- package/dist/api/ExtensionAPI.js +15 -0
- package/dist/api/Registry.d.ts +54 -0
- package/dist/api/Registry.js +103 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +230 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +41 -0
- package/dist/loader.d.ts +16 -0
- package/dist/loader.js +89 -0
- package/dist/mcp/entry.d.ts +2 -0
- package/dist/mcp/entry.js +71 -0
- package/dist/mcp/server.d.ts +31 -0
- package/dist/mcp/server.js +128 -0
- package/dist/models/CostTracker.d.ts +67 -0
- package/dist/models/CostTracker.js +151 -0
- package/dist/models/ModelRegistry.d.ts +159 -0
- package/dist/models/ModelRegistry.js +500 -0
- package/dist/models/index.d.ts +5 -0
- package/dist/models/index.js +3 -0
- package/dist/runner/AgentRunner.d.ts +88 -0
- package/dist/runner/AgentRunner.js +473 -0
- package/dist/runner/ModelClient.d.ts +97 -0
- package/dist/runner/ModelClient.js +350 -0
- package/dist/runner/SwarmRouter.d.ts +64 -0
- package/dist/runner/SwarmRouter.js +216 -0
- package/dist/runner/ToolDispatcher.d.ts +29 -0
- package/dist/runner/ToolDispatcher.js +168 -0
- package/dist/scheduler/AgentScheduler.d.ts +119 -0
- package/dist/scheduler/AgentScheduler.js +263 -0
- package/dist/server-entry.d.ts +11 -0
- package/dist/server-entry.js +15 -0
- package/dist/session/ContextStore.d.ts +96 -0
- package/dist/session/ContextStore.js +207 -0
- package/dist/session/GoalManager.d.ts +101 -0
- package/dist/session/GoalManager.js +167 -0
- package/dist/session/MemoryStore.d.ts +48 -0
- package/dist/session/MemoryStore.js +167 -0
- package/dist/session/SessionManager.d.ts +64 -0
- package/dist/session/SessionManager.js +193 -0
- package/dist/telemetry/Tracer.d.ts +48 -0
- package/dist/telemetry/Tracer.js +102 -0
- package/dist/tools/MarketSentiment.d.ts +166 -0
- package/dist/tools/MarketSentiment.js +209 -0
- package/dist/tools/NewsSentiment.d.ts +67 -0
- package/dist/tools/NewsSentiment.js +226 -0
- package/dist/tools/PriceFeed.d.ts +105 -0
- package/dist/tools/PriceFeed.js +282 -0
- package/dist/tools/TechnicalAnalysis.d.ts +110 -0
- package/dist/tools/TechnicalAnalysis.js +357 -0
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.js +4 -0
- package/dist/tui/App.d.ts +17 -0
- package/dist/tui/App.js +225 -0
- package/dist/tui/ModelSelector.d.ts +12 -0
- package/dist/tui/ModelSelector.js +87 -0
- package/dist/tui/REPL.d.ts +24 -0
- package/dist/tui/REPL.js +51 -0
- package/dist/tui/StatusBar.d.ts +14 -0
- package/dist/tui/StatusBar.js +14 -0
- package/dist/tui/theme.d.ts +30 -0
- package/dist/tui/theme.js +41 -0
- package/dist/util/safeLog.d.ts +3 -0
- package/dist/util/safeLog.js +21 -0
- package/package.json +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# @albalink/agent
|
|
2
|
+
|
|
3
|
+
**THE AGENT • THE WEB3 AGENT**
|
|
4
|
+
|
|
5
|
+
A React dashboard and open SDK. Bring any provider — Anthropic, OpenAI, local models on your Mac — and let ALBA reason, act and transact across chains. Fully open-source.
|
|
6
|
+
|
|
7
|
+
## Links
|
|
8
|
+
|
|
9
|
+
- **GitHub:** [github.com/alba-agent](https://github.com/alba-agent)
|
|
10
|
+
- **Website:** [alba.souldotmd.xyz](https://alba.souldotmd.xyz)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## What is ALBA?
|
|
15
|
+
|
|
16
|
+
Open-source Web3 agent. React dashboard. Bring your own AI provider — cloud or local.
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
TypeScript
|
|
20
|
+
React
|
|
21
|
+
Web3
|
|
22
|
+
MIT
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm i @albalink/agent
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Install the agent SDK in any project. Plug providers from Anthropic API to local Mac models. More coming soon.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- **Multi-Provider Support:** OpenRouter, Anthropic, OpenAI, local models (Ollama, etc.)
|
|
40
|
+
- **Tool System:** File operations, shell commands, grep, price feeds, news sentiment
|
|
41
|
+
- **Memory Layer:** Persistent context across sessions
|
|
42
|
+
- **Swarm Mode:** Sub-agent orchestration for complex tasks
|
|
43
|
+
- **Zero Cloud Dependency:** Runs entirely on your machine
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
import { ModelClient, resolveModelConfig, PriceFeed, NewsFeed, SwarmRouter } from "@albalink/agent";
|
|
51
|
+
|
|
52
|
+
// Configure with your provider
|
|
53
|
+
const cfg = resolveModelConfig();
|
|
54
|
+
const client = new ModelClient(cfg);
|
|
55
|
+
|
|
56
|
+
// Stream chat completions
|
|
57
|
+
for await (const chunk of client.stream(messages, tools)) {
|
|
58
|
+
console.log(chunk.type, chunk.text || "");
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## // WEB VERSION — COMING SOON
|
|
65
|
+
|
|
66
|
+
NOTIFY ME
|
|
67
|
+
|
|
68
|
+
Drop your Telegram username. We'll ping you the moment the web app opens.
|
|
69
|
+
@
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## // 04 — ROADMAP
|
|
74
|
+
|
|
75
|
+
### THE DAWN
|
|
76
|
+
|
|
77
|
+
**PHASE 01 · DONE**
|
|
78
|
+
GENESIS
|
|
79
|
+
$ALBA launch, liquidity burned, contract audited, community formed.
|
|
80
|
+
|
|
81
|
+
**PHASE 02 · LIVE**
|
|
82
|
+
ALBA AGENT SDK
|
|
83
|
+
Open-source Web3 agent — React dashboard + @albalink/agent npm package. Any provider, any chain, run locally on your Mac.
|
|
84
|
+
|
|
85
|
+
**PHASE 03 · Q3 2026**
|
|
86
|
+
OPEN MIND — WEB APP
|
|
87
|
+
Public web app, memory layer and community-driven training. Notify list first.
|
|
88
|
+
|
|
89
|
+
**PHASE 04 · Q4 2026**
|
|
90
|
+
CROSS-CHAIN DAWN
|
|
91
|
+
Multi-chain presence, DAO governance and an open SDK to build on top of ALBA.
|
package/bin/albacli
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* albacli — ALBA 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("ALBA: 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,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExtensionAPI — the interface exposed to extension files (e.g. extensions/alba.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
|
+
/**
|
|
28
|
+
* #10: If true, the agent pauses before executing this tool and asks the
|
|
29
|
+
* user for confirmation. Use for any tool that moves money or signs txs.
|
|
30
|
+
* The REPL renders an [APPROVE] prompt — user types y/n within 60 seconds.
|
|
31
|
+
*/
|
|
32
|
+
requiresApproval?: boolean;
|
|
33
|
+
execute(id: string, params: Static<P>): Promise<ToolContent>;
|
|
34
|
+
}
|
|
35
|
+
export interface TuiHeader {
|
|
36
|
+
render(width: number): string[];
|
|
37
|
+
invalidate(): void;
|
|
38
|
+
}
|
|
39
|
+
export type HeaderFactory = (tui: unknown, theme: ThemeContext) => TuiHeader;
|
|
40
|
+
export interface ThemeContext {
|
|
41
|
+
/** Apply a named colour to text. Colours: accent, error, warn, muted, success, border, dim */
|
|
42
|
+
fg(color: string, text: string): string;
|
|
43
|
+
}
|
|
44
|
+
export interface UIContext {
|
|
45
|
+
/** Show a bordered notification in the REPL panel */
|
|
46
|
+
notify(message: string): void;
|
|
47
|
+
/**
|
|
48
|
+
* Set a named status badge in the status bar.
|
|
49
|
+
* Pi compat — multiple badges can be shown simultaneously.
|
|
50
|
+
* @param key Badge slot name (e.g. "vault", "alba", "chain")
|
|
51
|
+
* @param value Themed text to display
|
|
52
|
+
*/
|
|
53
|
+
setStatus(key: string, value: string): void;
|
|
54
|
+
/**
|
|
55
|
+
* Activate a named theme. Pi compat — engine uses alba theme by default.
|
|
56
|
+
*/
|
|
57
|
+
setTheme(name: string): void;
|
|
58
|
+
/**
|
|
59
|
+
* Replace the TUI header with a custom rendering factory.
|
|
60
|
+
* Pi compat — accepted but engine renders its own Ink header.
|
|
61
|
+
*/
|
|
62
|
+
setHeader(factory: HeaderFactory): void;
|
|
63
|
+
/**
|
|
64
|
+
* Open the interactive model selector overlay.
|
|
65
|
+
* @param query Optional initial search query to pre-filter the list.
|
|
66
|
+
*/
|
|
67
|
+
showModelSelector(query?: string): void;
|
|
68
|
+
theme: ThemeContext;
|
|
69
|
+
}
|
|
70
|
+
export interface CommandContext {
|
|
71
|
+
ui: UIContext;
|
|
72
|
+
}
|
|
73
|
+
export interface CommandDef {
|
|
74
|
+
description: string;
|
|
75
|
+
handler(args: string, ctx: CommandContext): Promise<void>;
|
|
76
|
+
}
|
|
77
|
+
export interface SkillDef {
|
|
78
|
+
name: string;
|
|
79
|
+
content: string;
|
|
80
|
+
}
|
|
81
|
+
export type SessionEvent = "session_start" | "session_end" | "session_shutdown" | "before_agent_start";
|
|
82
|
+
export interface SessionContext {
|
|
83
|
+
ui: UIContext;
|
|
84
|
+
/** True when running inside an interactive TUI (always true for @alba/agent) */
|
|
85
|
+
hasUI: boolean;
|
|
86
|
+
config: Record<string, string | undefined>;
|
|
87
|
+
}
|
|
88
|
+
export interface ExtensionAPI {
|
|
89
|
+
registerCommand(name: string, def: CommandDef): void;
|
|
90
|
+
registerTool<P extends TSchema>(def: ToolDef<P>): void;
|
|
91
|
+
registerSkill(def: SkillDef): void;
|
|
92
|
+
/**
|
|
93
|
+
* Subscribe to a lifecycle event.
|
|
94
|
+
* Pi calling convention: handler receives (event, ctx).
|
|
95
|
+
* Handlers may use any arity: (event, ctx) | (ctx) | () — all are safe.
|
|
96
|
+
*/
|
|
97
|
+
on(event: SessionEvent, handler: (...args: any[]) => Promise<void>): void;
|
|
98
|
+
setSystemPrompt(prompt: string): void;
|
|
99
|
+
/** Direct UI access available at any time */
|
|
100
|
+
ui: UIContext;
|
|
101
|
+
}
|
|
102
|
+
export type ExtensionModule = (api: ExtensionAPI) => void | Promise<void>;
|
|
103
|
+
//# sourceMappingURL=ExtensionAPI.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExtensionAPI — the interface exposed to extension files (e.g. extensions/alba.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,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,103 @@
|
|
|
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 (e) {
|
|
87
|
+
console.error(`[Registry] hook "${event}" threw:`, e);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// ── OpenAI tool schema ───────────────────────────────────────────────────
|
|
92
|
+
toOpenAITools() {
|
|
93
|
+
return [...this.tools.values()].map(t => ({
|
|
94
|
+
type: "function",
|
|
95
|
+
function: {
|
|
96
|
+
name: t.name,
|
|
97
|
+
description: t.description,
|
|
98
|
+
parameters: t.parameters,
|
|
99
|
+
},
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=Registry.js.map
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli.ts — ALBA entry point.
|
|
3
|
+
* Completely standalone — all outbound, no inbound ports exposed.
|
|
4
|
+
*/
|
|
5
|
+
import { readFileSync, existsSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { resolve, join } from "node:path";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { render } from "ink";
|
|
9
|
+
import React from "react";
|
|
10
|
+
import { config as loadDotenv } from "dotenv";
|
|
11
|
+
import { Registry } from "./api/Registry.js";
|
|
12
|
+
import { loadExtension } from "./loader.js";
|
|
13
|
+
import { App } from "./tui/App.js";
|
|
14
|
+
import { T } from "./tui/theme.js";
|
|
15
|
+
import { wireNotify } from "./util/safeLog.js";
|
|
16
|
+
import { priceFeed } from "./tools/PriceFeed.js";
|
|
17
|
+
import { newsFeed } from "./tools/NewsSentiment.js";
|
|
18
|
+
// ── Load env vars from ~/.alba/.env ─────────────────────────────────────────
|
|
19
|
+
const ALBA_HOME = process.env.ALBA_HOME ?? join(homedir(), ".alba");
|
|
20
|
+
const envPath = join(ALBA_HOME, ".env");
|
|
21
|
+
if (existsSync(envPath))
|
|
22
|
+
loadDotenv({ path: envPath, override: false });
|
|
23
|
+
// ── Parse CLI args ────────────────────────────────────────────────────────────
|
|
24
|
+
const args = process.argv.slice(2);
|
|
25
|
+
const subcmd = args[0];
|
|
26
|
+
// ── Setup Command ───────────────────────────────────────────────────────────
|
|
27
|
+
function runSetup() {
|
|
28
|
+
(async () => {
|
|
29
|
+
console.log(T.accent("\n ALBA Setup\n"));
|
|
30
|
+
const readline = await import("node:readline");
|
|
31
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
32
|
+
const ask = (prompt) => new Promise((res) => rl.question(prompt, res));
|
|
33
|
+
const fs = await import("node:fs");
|
|
34
|
+
fs.mkdirSync(ALBA_HOME, { recursive: true });
|
|
35
|
+
const existingLines = existsSync(envPath) ? readFileSync(envPath, "utf-8").split("\n") : [];
|
|
36
|
+
const existingValues = {};
|
|
37
|
+
for (const line of existingLines) {
|
|
38
|
+
const m = line.match(/^([A-Z_]+)=(.*)$/);
|
|
39
|
+
if (m)
|
|
40
|
+
existingValues[m[1]] = m[2];
|
|
41
|
+
}
|
|
42
|
+
const requiredFields = [
|
|
43
|
+
{ key: "OPENROUTER_API_KEY", label: "OpenRouter API key", hint: "https://openrouter.ai/keys (or use local Ollama)", required: false },
|
|
44
|
+
];
|
|
45
|
+
const optionalFields = [
|
|
46
|
+
{ key: "ANTHROPIC_API_KEY", label: "Anthropic API key", hint: "https://console.anthropic.com", required: false },
|
|
47
|
+
{ key: "OPENAI_API_KEY", label: "OpenAI API key", hint: "https://platform.openai.com", required: false },
|
|
48
|
+
{ key: "OPENAI_BASE_URL", label: "Local API base URL", hint: "e.g. http://localhost:11434/v1 for Ollama", required: false },
|
|
49
|
+
];
|
|
50
|
+
const updateEnv = (key, value) => {
|
|
51
|
+
const re = new RegExp(`^${key}=.*$`, "m");
|
|
52
|
+
const content = existsSync(envPath) ? readFileSync(envPath, "utf-8") : "";
|
|
53
|
+
const newContent = re.test(content) ? content.replace(re, `${key}=${value}`) : content + (content ? "\n" : "") + `${key}=${value}`;
|
|
54
|
+
writeFileSync(envPath, newContent, "utf-8");
|
|
55
|
+
};
|
|
56
|
+
console.log(T.bold(" Required Configuration\n"));
|
|
57
|
+
for (const f of requiredFields) {
|
|
58
|
+
const current = existingValues[f.key] || process.env[f.key] || "";
|
|
59
|
+
const hint = f.hint ? ` (${f.hint})` : "";
|
|
60
|
+
const prompt = ` ${f.label}${hint}${current ? " [set]" : ""}: `;
|
|
61
|
+
const val = (await ask(prompt)).trim();
|
|
62
|
+
if (val)
|
|
63
|
+
updateEnv(f.key, val);
|
|
64
|
+
}
|
|
65
|
+
console.log("\n" + T.bold(" Optional Configuration\n"));
|
|
66
|
+
for (const f of optionalFields) {
|
|
67
|
+
const current = existingValues[f.key] || "";
|
|
68
|
+
const hint = f.hint ? ` (${f.hint})` : "";
|
|
69
|
+
const prompt = ` ${f.label}${hint}${current ? " [set]" : ""}: `;
|
|
70
|
+
const val = (await ask(prompt)).trim();
|
|
71
|
+
if (val)
|
|
72
|
+
updateEnv(f.key, val);
|
|
73
|
+
}
|
|
74
|
+
rl.close();
|
|
75
|
+
console.log(T.success(`\n ✓ Config saved → ${envPath}\n`));
|
|
76
|
+
console.log(T.muted(" Run: albacli (terminal mode)\n"));
|
|
77
|
+
})();
|
|
78
|
+
process.exit(0);
|
|
79
|
+
}
|
|
80
|
+
// ── Config Command ───────────────────────────────────────────────────────────
|
|
81
|
+
function runConfig() {
|
|
82
|
+
console.log(T.accent("ALBA Config"));
|
|
83
|
+
console.log(T.muted(`Config file: ${envPath}`));
|
|
84
|
+
if (existsSync(envPath)) {
|
|
85
|
+
for (const line of readFileSync(envPath, "utf-8").split("\n")) {
|
|
86
|
+
const m = line.match(/^([A-Z_]+)=(.*)$/);
|
|
87
|
+
if (!m)
|
|
88
|
+
continue;
|
|
89
|
+
const masked = m[2].length > 12 ? m[2].slice(0, 6) + "******" : "******";
|
|
90
|
+
console.log(` ${m[1].padEnd(28)} ${masked}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
console.log(T.warn("No config found. Run: albacli setup"));
|
|
95
|
+
}
|
|
96
|
+
process.exit(0);
|
|
97
|
+
}
|
|
98
|
+
// ── Resolve extension + prompt ────────────────────────────────────────────────
|
|
99
|
+
let extensionPath = null;
|
|
100
|
+
let promptPath = null;
|
|
101
|
+
for (let i = 0; i < args.length; i++) {
|
|
102
|
+
if (args[i] === "--extension" && args[i + 1])
|
|
103
|
+
extensionPath = args[i + 1];
|
|
104
|
+
if (args[i] === "--prompt" && args[i + 1])
|
|
105
|
+
promptPath = args[i + 1];
|
|
106
|
+
}
|
|
107
|
+
if (!extensionPath) {
|
|
108
|
+
for (const c of ["extensions/alba.ts", "extensions/alba.js", "extension.ts", "extension.js"]) {
|
|
109
|
+
const abs = resolve(c);
|
|
110
|
+
if (existsSync(abs)) {
|
|
111
|
+
extensionPath = abs;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
let systemPrompt = "";
|
|
117
|
+
if (promptPath && existsSync(promptPath)) {
|
|
118
|
+
systemPrompt = readFileSync(promptPath, "utf-8");
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
const dp = resolve("prompts/alba.md");
|
|
122
|
+
if (existsSync(dp))
|
|
123
|
+
systemPrompt = readFileSync(dp, "utf-8");
|
|
124
|
+
}
|
|
125
|
+
function loadContext() {
|
|
126
|
+
const ctxPath = join(ALBA_HOME, "context.json");
|
|
127
|
+
if (!existsSync(ctxPath))
|
|
128
|
+
return { effectLevel: "normal", chain: "ethereum" };
|
|
129
|
+
try {
|
|
130
|
+
const ctx = JSON.parse(readFileSync(ctxPath, "utf-8"));
|
|
131
|
+
if (ctx.model && typeof ctx.model === "string") {
|
|
132
|
+
process.env.DEFAULT_MODEL = ctx.model;
|
|
133
|
+
try {
|
|
134
|
+
const envFile = join(ALBA_HOME, ".env");
|
|
135
|
+
const content = existsSync(envFile) ? readFileSync(envFile, "utf-8") : "";
|
|
136
|
+
const re = /^DEFAULT_MODEL=.*$/m;
|
|
137
|
+
const line = `DEFAULT_MODEL=${ctx.model}`;
|
|
138
|
+
writeFileSync(envFile, re.test(content) ? content.replace(re, line) : content + "\n" + line + "\n", "utf-8");
|
|
139
|
+
}
|
|
140
|
+
catch { /* non-fatal */ }
|
|
141
|
+
}
|
|
142
|
+
return { effectLevel: ctx.effect_level ?? "normal", chain: ctx.active_chain ?? "ethereum" };
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return { effectLevel: "normal", chain: "ethereum" };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// ── Subcommand Router ───────────────────────────────────────────────────────
|
|
149
|
+
if (subcmd === "setup") {
|
|
150
|
+
runSetup();
|
|
151
|
+
}
|
|
152
|
+
if (subcmd === "config") {
|
|
153
|
+
runConfig();
|
|
154
|
+
}
|
|
155
|
+
// ── Boot ──────────────────────────────────────────────────────────────────────
|
|
156
|
+
(async () => {
|
|
157
|
+
console.clear();
|
|
158
|
+
console.log(T.accent(`
|
|
159
|
+
█████╗ ██╗ ██████╗ █████╗
|
|
160
|
+
██╔══██╗██║ ██╔══██╗██╔══██╗
|
|
161
|
+
███████║██║ ██████╔╝███████║
|
|
162
|
+
██╔══██║██║ ██╔══██╗██╔══██║
|
|
163
|
+
██║ ██║███████╗██████╔╝██║ ██║
|
|
164
|
+
╚═╝ ╚═╝╚══════╝╚═════╝ ╚═╝ ╚═╝
|
|
165
|
+
`));
|
|
166
|
+
console.log(T.muted(" Personal AI assistant — all local, zero exposure\n"));
|
|
167
|
+
const registry = new Registry();
|
|
168
|
+
const { effectLevel, chain } = loadContext();
|
|
169
|
+
let _notifyFn = null;
|
|
170
|
+
let _setStatusFn = null;
|
|
171
|
+
let _showModelSelectorFn = null;
|
|
172
|
+
if (extensionPath) {
|
|
173
|
+
try {
|
|
174
|
+
console.log(T.muted(` Loading: ${extensionPath}`));
|
|
175
|
+
await loadExtension(extensionPath, registry, {
|
|
176
|
+
onNotify: (msg) => { _notifyFn?.(msg); },
|
|
177
|
+
onStatusUpdate: (k, v) => { _setStatusFn?.(k, v); },
|
|
178
|
+
onShowModelSelector: (q) => { _showModelSelectorFn?.(q); },
|
|
179
|
+
});
|
|
180
|
+
console.log(T.success(` ✓ ${registry.listTools().length} tools · ${registry.listCommands().length} commands`));
|
|
181
|
+
}
|
|
182
|
+
catch (e) {
|
|
183
|
+
console.error(T.error(` ✗ Extension load failed: ${e.message}`));
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
console.log(T.warn(" No extension found — base agent only."));
|
|
189
|
+
console.log(T.muted(" Run from alba project root or pass --extension path\n"));
|
|
190
|
+
}
|
|
191
|
+
if (!process.env.OPENROUTER_API_KEY && !process.env.ANTHROPIC_API_KEY && !process.env.OPENAI_API_KEY) {
|
|
192
|
+
console.log(T.warn(" No API key found. Set OPENROUTER_API_KEY in ~/.alba/.env\n"));
|
|
193
|
+
console.log(T.muted(" Or use local Ollama: OPENAI_BASE_URL=http://localhost:11434/v1\n"));
|
|
194
|
+
}
|
|
195
|
+
await new Promise(r => setTimeout(r, 500));
|
|
196
|
+
console.clear();
|
|
197
|
+
// Start price + news feeds
|
|
198
|
+
priceFeed.start();
|
|
199
|
+
newsFeed.start();
|
|
200
|
+
// Graceful shutdown
|
|
201
|
+
let shutdownInProgress = false;
|
|
202
|
+
function handleShutdown() {
|
|
203
|
+
if (shutdownInProgress)
|
|
204
|
+
return;
|
|
205
|
+
shutdownInProgress = true;
|
|
206
|
+
console.log(T.muted("\n Shutting down..."));
|
|
207
|
+
priceFeed.stop();
|
|
208
|
+
newsFeed.stop();
|
|
209
|
+
process.exit(0);
|
|
210
|
+
}
|
|
211
|
+
process.on("SIGINT", handleShutdown);
|
|
212
|
+
process.on("SIGTERM", handleShutdown);
|
|
213
|
+
render(React.createElement(App, {
|
|
214
|
+
registry,
|
|
215
|
+
systemPrompt,
|
|
216
|
+
effectLevel,
|
|
217
|
+
chain,
|
|
218
|
+
onNotifyReady: (fn) => { _notifyFn = fn; wireNotify(fn); },
|
|
219
|
+
onStatusReady: (fn) => { _setStatusFn = fn; },
|
|
220
|
+
onModelSelectorReady: (fn) => { _showModelSelectorFn = fn; },
|
|
221
|
+
}), { exitOnCtrlC: false });
|
|
222
|
+
const _safeLog = (...args) => {
|
|
223
|
+
const msg = args.map(a => (typeof a === "string" ? a : String(a))).join(" ");
|
|
224
|
+
_notifyFn?.(msg);
|
|
225
|
+
};
|
|
226
|
+
console.log = _safeLog;
|
|
227
|
+
console.error = _safeLog;
|
|
228
|
+
console.warn = _safeLog;
|
|
229
|
+
})();
|
|
230
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @alba/agent — public API
|
|
3
|
+
*
|
|
4
|
+
* Extension files import from here:
|
|
5
|
+
* import { Type } from "@alba/agent"
|
|
6
|
+
* import type { ExtensionAPI } from "@alba/agent"
|
|
7
|
+
*/
|
|
8
|
+
export { Type, type Static } from "@sinclair/typebox";
|
|
9
|
+
export type { ExtensionAPI, ExtensionModule, CommandDef, CommandContext, UIContext, ThemeContext, ToolDef, ToolContent, SkillDef, SessionEvent, SessionContext, } from "./api/ExtensionAPI.js";
|
|
10
|
+
export { text } from "./api/ExtensionAPI.js";
|
|
11
|
+
export { Registry } from "./api/Registry.js";
|
|
12
|
+
export { AgentRunner } from "./runner/AgentRunner.js";
|
|
13
|
+
export { ModelClient, resolveModelConfig } from "./runner/ModelClient.js";
|
|
14
|
+
export { ToolDispatcher } from "./runner/ToolDispatcher.js";
|
|
15
|
+
export { loadExtension } from "./loader.js";
|
|
16
|
+
export { makeTheme, T, ALBA_COLORS } from "./tui/theme.js";
|
|
17
|
+
export { ModelRegistry, modelRegistry, classifyModel } from "./models/ModelRegistry.js";
|
|
18
|
+
export { CostTracker } from "./models/CostTracker.js";
|
|
19
|
+
export type { OpenRouterModel, TieredModel, TieredPool, ModelTier } from "./models/ModelRegistry.js";
|
|
20
|
+
export type { UsageEntry, SessionUsage, LifetimeUsage } from "./models/CostTracker.js";
|
|
21
|
+
export { fullAnalysis, rsi, macd, ema, sma, bollingerBands, atr, getCandlesTool, getCandlesParams } from "./tools/TechnicalAnalysis.js";
|
|
22
|
+
export { PriceFeed, priceFeed, getPricesTool, topMoversTool, marketOverviewTool } from "./tools/PriceFeed.js";
|
|
23
|
+
export { NewsFeed, newsFeed, getNewsTool, scoreSentiment } from "./tools/NewsSentiment.js";
|
|
24
|
+
export { getFearGreedTool, fundingRatesParams, getFundingRatesTool, getBtcMempoolTool, getDefiTvlTool, getSolanaStatsTool, } from "./tools/MarketSentiment.js";
|
|
25
|
+
export type { OHLCV, AnalysisResult } from "./tools/TechnicalAnalysis.js";
|
|
26
|
+
export type { PriceTick } from "./tools/PriceFeed.js";
|
|
27
|
+
export type { NewsItem, SentimentReport } from "./tools/NewsSentiment.js";
|
|
28
|
+
export { SessionManager } from "./session/SessionManager.js";
|
|
29
|
+
export { MemoryStore, memoryStore } from "./session/MemoryStore.js";
|
|
30
|
+
export { GoalManager, goalManager } from "./session/GoalManager.js";
|
|
31
|
+
export { ContextStore, contextStore } from "./session/ContextStore.js";
|
|
32
|
+
export type { MemoryEntry, RecentSession } from "./session/MemoryStore.js";
|
|
33
|
+
export type { Goal } from "./session/GoalManager.js";
|
|
34
|
+
export type { TaskContext } from "./session/ContextStore.js";
|
|
35
|
+
export type { ContextPressure } from "./session/SessionManager.js";
|
|
36
|
+
export { MCPServer } from "./mcp/server.js";
|
|
37
|
+
export { AgentScheduler, agentScheduler } from "./scheduler/AgentScheduler.js";
|
|
38
|
+
export type { ScheduledTask, PriceTrigger } from "./scheduler/AgentScheduler.js";
|
|
39
|
+
export { Tracer } from "./telemetry/Tracer.js";
|
|
40
|
+
export type { Span, Trace } from "./telemetry/Tracer.js";
|
|
41
|
+
//# sourceMappingURL=index.d.ts.map
|