@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.
Files changed (67) hide show
  1. package/README.md +91 -0
  2. package/bin/albacli +31 -0
  3. package/dist/api/ExtensionAPI.d.ts +103 -0
  4. package/dist/api/ExtensionAPI.js +15 -0
  5. package/dist/api/Registry.d.ts +54 -0
  6. package/dist/api/Registry.js +103 -0
  7. package/dist/cli.d.ts +6 -0
  8. package/dist/cli.js +230 -0
  9. package/dist/index.d.ts +41 -0
  10. package/dist/index.js +41 -0
  11. package/dist/loader.d.ts +16 -0
  12. package/dist/loader.js +89 -0
  13. package/dist/mcp/entry.d.ts +2 -0
  14. package/dist/mcp/entry.js +71 -0
  15. package/dist/mcp/server.d.ts +31 -0
  16. package/dist/mcp/server.js +128 -0
  17. package/dist/models/CostTracker.d.ts +67 -0
  18. package/dist/models/CostTracker.js +151 -0
  19. package/dist/models/ModelRegistry.d.ts +159 -0
  20. package/dist/models/ModelRegistry.js +500 -0
  21. package/dist/models/index.d.ts +5 -0
  22. package/dist/models/index.js +3 -0
  23. package/dist/runner/AgentRunner.d.ts +88 -0
  24. package/dist/runner/AgentRunner.js +473 -0
  25. package/dist/runner/ModelClient.d.ts +97 -0
  26. package/dist/runner/ModelClient.js +350 -0
  27. package/dist/runner/SwarmRouter.d.ts +64 -0
  28. package/dist/runner/SwarmRouter.js +216 -0
  29. package/dist/runner/ToolDispatcher.d.ts +29 -0
  30. package/dist/runner/ToolDispatcher.js +168 -0
  31. package/dist/scheduler/AgentScheduler.d.ts +119 -0
  32. package/dist/scheduler/AgentScheduler.js +263 -0
  33. package/dist/server-entry.d.ts +11 -0
  34. package/dist/server-entry.js +15 -0
  35. package/dist/session/ContextStore.d.ts +96 -0
  36. package/dist/session/ContextStore.js +207 -0
  37. package/dist/session/GoalManager.d.ts +101 -0
  38. package/dist/session/GoalManager.js +167 -0
  39. package/dist/session/MemoryStore.d.ts +48 -0
  40. package/dist/session/MemoryStore.js +167 -0
  41. package/dist/session/SessionManager.d.ts +64 -0
  42. package/dist/session/SessionManager.js +193 -0
  43. package/dist/telemetry/Tracer.d.ts +48 -0
  44. package/dist/telemetry/Tracer.js +102 -0
  45. package/dist/tools/MarketSentiment.d.ts +166 -0
  46. package/dist/tools/MarketSentiment.js +209 -0
  47. package/dist/tools/NewsSentiment.d.ts +67 -0
  48. package/dist/tools/NewsSentiment.js +226 -0
  49. package/dist/tools/PriceFeed.d.ts +105 -0
  50. package/dist/tools/PriceFeed.js +282 -0
  51. package/dist/tools/TechnicalAnalysis.d.ts +110 -0
  52. package/dist/tools/TechnicalAnalysis.js +357 -0
  53. package/dist/tools/index.d.ts +7 -0
  54. package/dist/tools/index.js +4 -0
  55. package/dist/tui/App.d.ts +17 -0
  56. package/dist/tui/App.js +225 -0
  57. package/dist/tui/ModelSelector.d.ts +12 -0
  58. package/dist/tui/ModelSelector.js +87 -0
  59. package/dist/tui/REPL.d.ts +24 -0
  60. package/dist/tui/REPL.js +51 -0
  61. package/dist/tui/StatusBar.d.ts +14 -0
  62. package/dist/tui/StatusBar.js +14 -0
  63. package/dist/tui/theme.d.ts +30 -0
  64. package/dist/tui/theme.js +41 -0
  65. package/dist/util/safeLog.d.ts +3 -0
  66. package/dist/util/safeLog.js +21 -0
  67. package/package.json +67 -0
@@ -0,0 +1,87 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * ModelSelector — interactive model picker overlay for ALBA.
4
+ */
5
+ import { useState, useCallback, useEffect, useMemo } from "react";
6
+ import { Box, Text, useInput } from "ink";
7
+ import TextInput from "ink-text-input";
8
+ import { ALBA_COLORS, T } from "./theme.js";
9
+ import { modelRegistry } from "../models/ModelRegistry.js";
10
+ const STATIC_FALLBACK = [
11
+ { id: "anthropic/claude-sonnet-4-5", provider: "Anthropic" },
12
+ { id: "openai/gpt-4o", provider: "OpenAI" },
13
+ { id: "openai/gpt-4o-mini", provider: "OpenAI" },
14
+ { id: "google/gemini-flash-1.5", provider: "Google" },
15
+ { id: "meta-llama/llama-3-8b-instruct:free", provider: "Meta" },
16
+ { id: "deepseek/deepseek-chat", provider: "DeepSeek" },
17
+ ];
18
+ function getAvailableModels() {
19
+ try {
20
+ const registryModels = modelRegistry.search("");
21
+ if (registryModels && registryModels.length > 0) {
22
+ return registryModels.map(m => ({
23
+ id: m.model.id,
24
+ provider: m.tier.charAt(0).toUpperCase() + m.tier.slice(1),
25
+ }));
26
+ }
27
+ }
28
+ catch { /* fall through to static */ }
29
+ return STATIC_FALLBACK;
30
+ }
31
+ export function ModelSelector({ currentModelId, onSelect, onCancel, initialQuery = "", }) {
32
+ const [query, setQuery] = useState(initialQuery);
33
+ const [selectedIndex, setSelectedIndex] = useState(0);
34
+ const availableModels = useMemo(() => getAvailableModels(), []);
35
+ const filtered = useMemo(() => {
36
+ const q = query.toLowerCase().trim();
37
+ if (!q)
38
+ return availableModels;
39
+ return availableModels.filter(m => `${m.id} ${m.provider}`.toLowerCase().includes(q));
40
+ }, [query, availableModels]);
41
+ useEffect(() => {
42
+ setSelectedIndex((prev) => Math.min(prev, Math.max(0, filtered.length - 1)));
43
+ }, [filtered.length]);
44
+ useEffect(() => {
45
+ const idx = availableModels.findIndex(m => m.id === currentModelId);
46
+ if (idx >= 0)
47
+ setSelectedIndex(idx);
48
+ }, [currentModelId]);
49
+ const handleSubmit = useCallback(() => {
50
+ const selected = filtered[selectedIndex];
51
+ if (selected)
52
+ onSelect(selected.id);
53
+ }, [filtered, selectedIndex, onSelect]);
54
+ useInput((input, key) => {
55
+ if (key.escape) {
56
+ onCancel();
57
+ return;
58
+ }
59
+ if (key.return) {
60
+ handleSubmit();
61
+ return;
62
+ }
63
+ if (key.upArrow) {
64
+ setSelectedIndex(prev => (prev <= 0 ? filtered.length - 1 : prev - 1));
65
+ return;
66
+ }
67
+ if (key.downArrow) {
68
+ setSelectedIndex(prev => (prev >= filtered.length - 1 ? 0 : prev + 1));
69
+ return;
70
+ }
71
+ if (key.backspace || key.delete) {
72
+ setQuery(prev => prev.slice(0, -1));
73
+ return;
74
+ }
75
+ if (!key.ctrl && !key.meta && input) {
76
+ setQuery(prev => prev + input);
77
+ return;
78
+ }
79
+ });
80
+ return (_jsxs(Box, { flexDirection: "column", width: "100%", children: [_jsx(Box, { borderStyle: "round", borderColor: ALBA_COLORS.accent, marginY: 1, paddingX: 1, children: _jsx(Text, { color: ALBA_COLORS.accent, bold: true, children: "Select Model" }) }), _jsxs(Box, { paddingX: 2, children: [_jsx(Text, { color: ALBA_COLORS.muted, children: " Search: " }), _jsx(TextInput, { value: query, onChange: setQuery, onSubmit: handleSubmit, placeholder: "type to filter..." })] }), _jsx(Box, { paddingX: 1, children: _jsx(Text, { color: ALBA_COLORS.dim, children: " Up/Down navigate \u00B7 Enter select \u00B7 Esc cancel" }) }), _jsx(Box, { flexDirection: "column", paddingX: 2, marginTop: 1, children: filtered.length === 0 ? (_jsxs(Text, { color: ALBA_COLORS.warn, children: [" No models match \"", query, "\""] })) : (filtered.map((item, idx) => {
81
+ const isSelected = idx === selectedIndex;
82
+ const isCurrent = item.id === currentModelId;
83
+ const checkMark = isCurrent ? T.success(" (current)") : "";
84
+ return (_jsxs(Text, { color: isSelected ? ALBA_COLORS.accent : undefined, children: [_jsx(Text, { color: ALBA_COLORS.accent, children: isSelected ? ">" : " " }), " ", _jsx(Text, { color: isSelected ? ALBA_COLORS.accent : ALBA_COLORS.header, bold: isSelected, children: item.id }), _jsxs(Text, { color: ALBA_COLORS.muted, children: [" [", item.provider, "]"] }), checkMark] }, item.id));
85
+ })) })] }));
86
+ }
87
+ //# sourceMappingURL=ModelSelector.js.map
@@ -0,0 +1,24 @@
1
+ /**
2
+ * REPL — scrolling message history + bottom input box.
3
+ * Renders assistant text, tool calls, tool results, and user messages.
4
+ */
5
+ import React from "react";
6
+ export type MessageRole = "user" | "assistant" | "tool" | "system" | "notify";
7
+ export interface ChatMessage {
8
+ id: string;
9
+ role: MessageRole;
10
+ content: string;
11
+ toolName?: string;
12
+ isError?: boolean;
13
+ ts: number;
14
+ }
15
+ export interface REPLProps {
16
+ messages: ChatMessage[];
17
+ streamingText: string;
18
+ toolRunning: string | null;
19
+ onSubmit(input: string): void;
20
+ onAbort?(): void;
21
+ disabled: boolean;
22
+ }
23
+ export declare function REPL({ messages, streamingText, toolRunning, onSubmit, onAbort, disabled }: REPLProps): React.JSX.Element;
24
+ //# sourceMappingURL=REPL.d.ts.map
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * REPL — scrolling message history + bottom input box.
4
+ * Renders assistant text, tool calls, tool results, and user messages.
5
+ */
6
+ import { useState, useCallback } from "react";
7
+ import { Box, Text, useStdout, useInput } from "ink";
8
+ import TextInput from "ink-text-input";
9
+ import { ALBA_COLORS } from "./theme.js";
10
+ const MAX_VISIBLE = 40;
11
+ function MessageLine({ msg }) {
12
+ const time = new Date(msg.ts).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
13
+ if (msg.role === "user") {
14
+ return (_jsxs(Box, { flexDirection: "row", gap: 1, marginTop: 1, children: [_jsx(Text, { color: ALBA_COLORS.muted, children: time }), _jsx(Text, { color: ALBA_COLORS.accent, bold: true, children: "you " }), _jsx(Text, { wrap: "wrap", children: msg.content })] }));
15
+ }
16
+ if (msg.role === "assistant") {
17
+ return (_jsxs(Box, { flexDirection: "row", gap: 1, marginTop: 1, children: [_jsx(Text, { color: ALBA_COLORS.muted, children: time }), _jsx(Text, { color: ALBA_COLORS.header, bold: true, children: "\uD83E\uDEBC " }), _jsx(Text, { wrap: "wrap", children: msg.content })] }));
18
+ }
19
+ if (msg.role === "tool") {
20
+ const icon = msg.isError ? "✗" : "✓";
21
+ const col = msg.isError ? ALBA_COLORS.error : ALBA_COLORS.success;
22
+ const isLong = msg.content.length > 120;
23
+ return (_jsxs(Box, { flexDirection: "column", marginY: 0, children: [_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Text, { color: ALBA_COLORS.muted, children: time }), _jsxs(Text, { color: col, children: [icon, " ", msg.toolName ?? "tool"] }), isLong && _jsxs(Text, { color: ALBA_COLORS.dim, children: [" (", msg.content.length, " chars)"] })] }), !isLong && (_jsx(Box, { marginLeft: 6, children: _jsx(Text, { color: ALBA_COLORS.muted, wrap: "wrap", children: msg.content }) }))] }));
24
+ }
25
+ if (msg.role === "notify") {
26
+ return (_jsx(Box, { borderStyle: "round", borderColor: ALBA_COLORS.accent, marginY: 1, paddingX: 1, children: _jsx(Text, { wrap: "wrap", children: msg.content }) }));
27
+ }
28
+ // system messages — dimmed
29
+ return (_jsx(Box, { flexDirection: "row", gap: 1, children: _jsx(Text, { color: ALBA_COLORS.dim, wrap: "wrap", children: msg.content }) }));
30
+ }
31
+ export function REPL({ messages, streamingText, toolRunning, onSubmit, onAbort, disabled }) {
32
+ const [input, setInput] = useState("");
33
+ const { stdout } = useStdout();
34
+ const termWidth = stdout?.columns ?? 80;
35
+ const handleSubmit = useCallback((val) => {
36
+ const trimmed = val.trim();
37
+ if (!trimmed || disabled)
38
+ return;
39
+ setInput("");
40
+ onSubmit(trimmed);
41
+ }, [onSubmit, disabled]);
42
+ // #25: Escape key aborts in-flight stream
43
+ useInput((_input, key) => {
44
+ if (key.escape && disabled && onAbort) {
45
+ onAbort();
46
+ }
47
+ });
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: ALBA_COLORS.muted, children: " " }), _jsx(Text, { color: ALBA_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: ALBA_COLORS.warn, children: ["\u2699 running ", toolRunning, "\u2026"] }) }))] }), _jsxs(Box, { borderStyle: "round", borderColor: disabled ? ALBA_COLORS.dim : ALBA_COLORS.accent, paddingX: 1, marginTop: 1, children: [_jsx(Text, { color: ALBA_COLORS.accent, children: "\u203A " }), _jsx(TextInput, { value: input, onChange: setInput, onSubmit: handleSubmit, placeholder: disabled ? "thinking…" : "message or /command" })] })] }));
50
+ }
51
+ //# sourceMappingURL=REPL.js.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * StatusBar — single line at the top of the TUI showing live agent state.
3
+ */
4
+ import React from "react";
5
+ export interface StatusBarProps {
6
+ model: string;
7
+ chain: string;
8
+ effectLevel: string;
9
+ toolRunning: string | null;
10
+ connected: boolean;
11
+ statusLine?: string | null;
12
+ }
13
+ export declare function StatusBar({ model, chain, effectLevel, toolRunning, connected, statusLine, }: StatusBarProps): React.JSX.Element;
14
+ //# sourceMappingURL=StatusBar.d.ts.map
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { ALBA_COLORS } from "./theme.js";
4
+ export function StatusBar({ model, chain, effectLevel, toolRunning, connected, statusLine, }) {
5
+ const chainShort = chain.slice(0, 8);
6
+ const modelShort = model.split("/").pop()?.slice(0, 18) ?? model.slice(0, 18);
7
+ const effectIcon = { eco: "🌿", normal: "⚡", turbo: "🚀", max: "🌊" }[effectLevel] ?? "⚡";
8
+ return (_jsxs(Box, { flexDirection: "column", width: "100%", children: [_jsx(Text, { color: ALBA_COLORS.dim, children: "".padEnd(80, "─") }), _jsxs(Box, { paddingX: 1, flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Box, { gap: 2, children: [_jsx(Text, { color: ALBA_COLORS.accent, bold: true, children: "ALBA" }), _jsx(Text, { color: ALBA_COLORS.muted, children: modelShort })] }), _jsx(Box, { children: toolRunning
9
+ ? _jsxs(Text, { color: ALBA_COLORS.warn, children: ["\u2699 ", toolRunning] })
10
+ : statusLine
11
+ ? _jsx(Text, { color: ALBA_COLORS.muted, children: statusLine.slice(0, 80) })
12
+ : _jsx(Text, { color: ALBA_COLORS.muted, children: connected ? "ready" : "connecting…" }) }), _jsxs(Box, { gap: 2, children: [_jsx(Text, { color: ALBA_COLORS.muted, children: chainShort }), _jsxs(Text, { color: ALBA_COLORS.header, children: [effectIcon, " ", effectLevel] })] })] })] }));
13
+ }
14
+ //# sourceMappingURL=StatusBar.js.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * ALBA colour theme — maps semantic names to chalk/ANSI colours.
3
+ * Exported as a ThemeContext-compatible object so it can be passed into
4
+ * extension command handlers via ctx.ui.theme.
5
+ */
6
+ import type { ThemeContext } from "../api/ExtensionAPI.js";
7
+ export declare const ALBA_COLORS: {
8
+ readonly accent: "#00e5ff";
9
+ readonly success: "#69ff94";
10
+ readonly error: "#ff5370";
11
+ readonly warn: "#ffcb6b";
12
+ readonly muted: "#546e7a";
13
+ readonly header: "#c792ea";
14
+ readonly dim: "#37474f";
15
+ };
16
+ export type AlbaColor = keyof typeof ALBA_COLORS;
17
+ export declare function makeTheme(): ThemeContext;
18
+ /** Convenience — themed prefix for agent output lines */
19
+ export declare const theme: ThemeContext;
20
+ export declare const T: {
21
+ accent: (s: string) => string;
22
+ success: (s: string) => string;
23
+ error: (s: string) => string;
24
+ warn: (s: string) => string;
25
+ muted: (s: string) => string;
26
+ header: (s: string) => string;
27
+ dim: (s: string) => string;
28
+ bold: (s: string) => string;
29
+ };
30
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * ALBA colour theme — maps semantic names to chalk/ANSI colours.
3
+ * Exported as a ThemeContext-compatible object so it can be passed into
4
+ * extension command handlers via ctx.ui.theme.
5
+ */
6
+ import chalk from "chalk";
7
+ export const ALBA_COLORS = {
8
+ accent: "#00e5ff", // cyan-aqua — alba signature
9
+ success: "#69ff94", // soft green
10
+ error: "#ff5370", // coral red
11
+ warn: "#ffcb6b", // amber
12
+ muted: "#546e7a", // slate grey
13
+ header: "#c792ea", // soft purple
14
+ dim: "#37474f", // dark slate
15
+ };
16
+ export function makeTheme() {
17
+ return {
18
+ fg(color, text) {
19
+ const hex = ALBA_COLORS[color] ?? color;
20
+ try {
21
+ return chalk.hex(hex)(text);
22
+ }
23
+ catch {
24
+ return text;
25
+ }
26
+ },
27
+ };
28
+ }
29
+ /** Convenience — themed prefix for agent output lines */
30
+ export const theme = makeTheme();
31
+ export const T = {
32
+ accent: (s) => chalk.hex(ALBA_COLORS.accent)(s),
33
+ success: (s) => chalk.hex(ALBA_COLORS.success)(s),
34
+ error: (s) => chalk.hex(ALBA_COLORS.error)(s),
35
+ warn: (s) => chalk.hex(ALBA_COLORS.warn)(s),
36
+ muted: (s) => chalk.hex(ALBA_COLORS.muted)(s),
37
+ header: (s) => chalk.hex(ALBA_COLORS.header)(s),
38
+ dim: (s) => chalk.hex(ALBA_COLORS.dim)(s),
39
+ bold: (s) => chalk.bold(s),
40
+ };
41
+ //# sourceMappingURL=theme.js.map
@@ -0,0 +1,3 @@
1
+ export declare function wireNotify(fn: (msg: string) => void): void;
2
+ export declare function safeLog(...args: unknown[]): void;
3
+ //# sourceMappingURL=safeLog.d.ts.map
@@ -0,0 +1,21 @@
1
+ import { appendFileSync, mkdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ const ALBA_HOME = process.env.ALBA_HOME ?? join(homedir(), ".alba");
5
+ const DEBUG_LOG = join(ALBA_HOME, "debug.log");
6
+ let _notifyFn = null;
7
+ export function wireNotify(fn) { _notifyFn = fn; }
8
+ export function safeLog(...args) {
9
+ const msg = args.map(a => (typeof a === "string" ? a : String(a))).join(" ");
10
+ if (_notifyFn) {
11
+ _notifyFn(msg);
12
+ }
13
+ else {
14
+ try {
15
+ mkdirSync(ALBA_HOME, { recursive: true });
16
+ appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] ${msg}\n`);
17
+ }
18
+ catch { /* non-fatal */ }
19
+ }
20
+ }
21
+ //# sourceMappingURL=safeLog.js.map
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@albalink/agent",
3
+ "version": "1.0.0",
4
+ "description": "ALBA — standalone AI agent engine. Runs locally, extensible, zero cloud dependency.",
5
+ "author": "ALBA",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/index.js",
13
+ "types": "./dist/index.d.ts"
14
+ }
15
+ },
16
+ "bin": {
17
+ "albacli": "bin/albacli",
18
+ "alba-mcp": "bin/alba-mcp",
19
+ "albalink": "bin/albacli"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "scripts": {
25
+ "build": "tsc",
26
+ "build:watch": "tsc --watch",
27
+ "dev": "tsx src/cli.ts",
28
+ "start": "node dist/cli.js",
29
+ "type-check": "tsc --noEmit",
30
+ "prepublishOnly": "npm run build"
31
+ },
32
+ "dependencies": {
33
+ "@sinclair/typebox": "^0.32.0",
34
+ "chalk": "^5.3.0",
35
+ "dotenv": "^16.4.5",
36
+ "ink": "^5.0.1",
37
+ "ink-text-input": "^6.0.0",
38
+ "react": "^18.3.1",
39
+ "tsx": "^4.15.0"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "^20.19.41",
43
+ "@types/react": "^18.3.29",
44
+ "typescript": "^5.9.3"
45
+ },
46
+ "engines": {
47
+ "node": ">=20.11.0"
48
+ },
49
+ "keywords": [
50
+ "ai",
51
+ "agent",
52
+ "local",
53
+ "assistant",
54
+ "terminal",
55
+ "cli",
56
+ "alba",
57
+ "swarm",
58
+ "extensible"
59
+ ],
60
+ "files": [
61
+ "dist",
62
+ "bin",
63
+ "README.md",
64
+ "!dist/**/*.map",
65
+ "!dist/**/*.test.*"
66
+ ]
67
+ }