@bluehawks/cli 1.0.3 → 1.0.6

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 (56) hide show
  1. package/.bluehawks/history.json +3 -12
  2. package/dist/cli/app.d.ts.map +1 -1
  3. package/dist/cli/app.js +13 -2
  4. package/dist/cli/app.js.map +1 -1
  5. package/dist/config/constants.d.ts +1 -1
  6. package/dist/config/constants.js +1 -1
  7. package/dist/config/schema.d.ts +2 -2
  8. package/dist/core/agents/orchestrator.d.ts.map +1 -1
  9. package/dist/core/agents/orchestrator.js +11 -1
  10. package/dist/core/agents/orchestrator.js.map +1 -1
  11. package/dist/core/api/client.d.ts +9 -1
  12. package/dist/core/api/client.d.ts.map +1 -1
  13. package/dist/core/api/client.js +16 -0
  14. package/dist/core/api/client.js.map +1 -1
  15. package/dist/core/api/types.d.ts +38 -0
  16. package/dist/core/api/types.d.ts.map +1 -1
  17. package/dist/core/api/types.js.map +1 -1
  18. package/dist/core/memory/index.d.ts +4 -0
  19. package/dist/core/memory/index.d.ts.map +1 -0
  20. package/dist/core/memory/index.js +4 -0
  21. package/dist/core/memory/index.js.map +1 -0
  22. package/dist/core/memory/manager.d.ts +29 -0
  23. package/dist/core/memory/manager.d.ts.map +1 -0
  24. package/dist/core/memory/manager.js +130 -0
  25. package/dist/core/memory/manager.js.map +1 -0
  26. package/dist/core/memory/storage.d.ts +15 -0
  27. package/dist/core/memory/storage.d.ts.map +1 -0
  28. package/dist/core/memory/storage.js +77 -0
  29. package/dist/core/memory/storage.js.map +1 -0
  30. package/dist/core/memory/types.d.ts +20 -0
  31. package/dist/core/memory/types.d.ts.map +1 -0
  32. package/dist/core/memory/types.js +2 -0
  33. package/dist/core/memory/types.js.map +1 -0
  34. package/dist/core/tools/definitions/index.d.ts +1 -0
  35. package/dist/core/tools/definitions/index.d.ts.map +1 -1
  36. package/dist/core/tools/definitions/index.js +3 -0
  37. package/dist/core/tools/definitions/index.js.map +1 -1
  38. package/dist/core/tools/definitions/memory.d.ts +2 -0
  39. package/dist/core/tools/definitions/memory.d.ts.map +1 -0
  40. package/dist/core/tools/definitions/memory.js +155 -0
  41. package/dist/core/tools/definitions/memory.js.map +1 -0
  42. package/dist/index.js +0 -3
  43. package/dist/index.js.map +1 -1
  44. package/package.json +3 -1
  45. package/src/cli/app.tsx +123 -49
  46. package/src/config/constants.ts +1 -1
  47. package/src/core/agents/orchestrator.ts +13 -2
  48. package/src/core/api/client.ts +28 -0
  49. package/src/core/api/types.ts +46 -0
  50. package/src/core/memory/index.ts +3 -0
  51. package/src/core/memory/manager.ts +146 -0
  52. package/src/core/memory/storage.ts +90 -0
  53. package/src/core/memory/types.ts +22 -0
  54. package/src/core/tools/definitions/index.ts +3 -0
  55. package/src/core/tools/definitions/memory.ts +171 -0
  56. package/src/index.ts +0 -3
@@ -8,7 +8,7 @@ export const DEFAULT_MODEL = 'Qwen/Qwen3-0.6B';
8
8
 
9
9
  // CLI Metadata
10
10
  export const CLI_NAME = 'bluehawks';
11
- export const CLI_VERSION = '1.0.3';
11
+ export const CLI_VERSION = '1.0.6';
12
12
  export const CLI_DESCRIPTION = 'A production-ready multi-agent AI CLI assistant';
13
13
 
14
14
  // Configuration Paths
@@ -9,6 +9,7 @@ import { Agent, type AgentResponse } from './agent.js';
9
9
  import { CONTEXT_FILE } from '../../config/constants.js';
10
10
  import * as fs from 'node:fs/promises';
11
11
  import * as path from 'node:path';
12
+ import { memoryManager } from '../memory/index.js'; // Corrected path
12
13
 
13
14
  export interface SubAgentConfig {
14
15
  name: string;
@@ -178,17 +179,27 @@ Before making any changes, first:
178
179
  onToolEnd?: (name: string, result: string) => void;
179
180
  }
180
181
  ): Promise<AgentResponse> {
182
+ // Retrieve relevant memories
183
+ const memories = await memoryManager.search(userMessage, 5);
184
+ let systemPrompt = this.buildSystemPrompt();
185
+
186
+ if (memories.length > 0) {
187
+ const memoryContext = memories
188
+ .map(m => `- [${m.type.toUpperCase()}] ${m.content}`)
189
+ .join('\n');
190
+ systemPrompt += `\n\n## Long-Term Memory (Relevant Context)\n${memoryContext}\n\nUse this information to guide your decisions and avoid past mistakes.`;
191
+ }
192
+
181
193
  const mainAgent = new Agent(
182
194
  {
183
195
  name: 'main',
184
- systemPrompt: this.buildSystemPrompt(),
196
+ systemPrompt,
185
197
  maxIterations: this.maxTurns,
186
198
  },
187
199
  this.apiClient,
188
200
  this.toolExecutor
189
201
  );
190
202
 
191
-
192
203
  return mainAgent.run(
193
204
  userMessage,
194
205
  callbacks?.onChunk,
@@ -22,6 +22,10 @@ import {
22
22
  type ToolDefinition,
23
23
  type ToolResult,
24
24
  type StreamDelta,
25
+ type EmbeddingRequest,
26
+ type EmbeddingResponse,
27
+ type RerankRequest,
28
+ type RerankResponse,
25
29
  APIError,
26
30
  } from './types.js';
27
31
 
@@ -42,6 +46,30 @@ export class APIClient {
42
46
  this.timeout = options.timeout || DEFAULT_TIMEOUT_MS;
43
47
  }
44
48
 
49
+ /**
50
+ * Create embeddings for the given input
51
+ */
52
+ async createEmbeddings(
53
+ input: string | string[],
54
+ model?: string
55
+ ): Promise<EmbeddingResponse> {
56
+ const body: EmbeddingRequest = {
57
+ model: model || 'text-embedding-3-small',
58
+ input,
59
+ };
60
+
61
+ return this.makeRequest<EmbeddingResponse>('/embeddings', body);
62
+ }
63
+
64
+ /**
65
+ * Rerank documents based on query relevance
66
+ */
67
+ async rerank(request: RerankRequest): Promise<RerankResponse> {
68
+ return this.makeRequest<RerankResponse>('/rerank', request);
69
+ }
70
+
71
+
72
+
45
73
  /**
46
74
  * Create a chat completion (non-streaming)
47
75
  */
@@ -136,6 +136,52 @@ export interface APIClientOptions {
136
136
  export type StreamCallback = (chunk: string) => void;
137
137
  export type ToolCallCallback = (toolCalls: ToolCall[]) => void;
138
138
 
139
+ // Embedding Types
140
+ export interface EmbeddingRequest {
141
+ model: string;
142
+ input: string | string[];
143
+ user?: string;
144
+ }
145
+
146
+ export interface EmbeddingResponse {
147
+ object: 'list';
148
+ data: EmbeddingObject[];
149
+ model: string;
150
+ usage: {
151
+ prompt_tokens: number;
152
+ total_tokens: number;
153
+ };
154
+ }
155
+
156
+ export interface EmbeddingObject {
157
+ object: 'embedding';
158
+ index: number;
159
+ embedding: number[];
160
+ }
161
+
162
+ // Rerank Types
163
+ export interface RerankRequest {
164
+ model?: string;
165
+ query: string;
166
+ documents: string[];
167
+ top_n?: number;
168
+ }
169
+
170
+ export interface RerankResult {
171
+ index: number;
172
+ score: number;
173
+ document: string;
174
+ }
175
+
176
+ export interface RerankResponse {
177
+ model: string;
178
+ results: RerankResult[];
179
+ usage?: {
180
+ total_tokens: number;
181
+ prompt_tokens?: number;
182
+ };
183
+ }
184
+
139
185
  // API Error
140
186
  export class APIError extends Error {
141
187
  constructor(
@@ -0,0 +1,3 @@
1
+ export * from './types.js';
2
+ export * from './storage.js';
3
+ export * from './manager.js';
@@ -0,0 +1,146 @@
1
+ import { apiClient } from '../api/client.js';
2
+ import { memoryStorage } from './storage.js';
3
+ import type { Memory, MemoryType, SearchResult } from './types.js';
4
+ import * as crypto from 'crypto';
5
+
6
+ export class MemoryManager {
7
+ private static instance: MemoryManager;
8
+
9
+ private constructor() { }
10
+
11
+ static getInstance(): MemoryManager {
12
+ if (!MemoryManager.instance) {
13
+ MemoryManager.instance = new MemoryManager();
14
+ }
15
+ return MemoryManager.instance;
16
+ }
17
+
18
+ /**
19
+ * Store a new memory
20
+ */
21
+ async remember(content: string, type: MemoryType = 'knowledge', metadata: Record<string, any> = {}): Promise<Memory> {
22
+ // Generate embedding
23
+ let embedding: number[] = [];
24
+ try {
25
+ const response = await apiClient.createEmbeddings(content);
26
+ if (response.data && response.data.length > 0) {
27
+ embedding = response.data[0].embedding;
28
+ }
29
+ } catch (error) {
30
+ console.error('Failed to generate embedding for memory:', error);
31
+ // We still save the memory without embedding, it just won't be searchable by vector
32
+ }
33
+
34
+ const memory: Memory = {
35
+ id: crypto.randomUUID(),
36
+ content,
37
+ type,
38
+ metadata,
39
+ embedding,
40
+ created_at: Date.now(),
41
+ updated_at: Date.now(),
42
+ };
43
+
44
+ await memoryStorage.save(memory);
45
+ return memory;
46
+ }
47
+
48
+ /**
49
+ * Search memories by semantic similarity
50
+ */
51
+ async search(query: string, limit: number = 5, minSimilarity: number = 0.7): Promise<SearchResult[]> {
52
+ // Generate query embedding
53
+ let queryEmbedding: number[] = [];
54
+ try {
55
+ const response = await apiClient.createEmbeddings(query);
56
+ if (response.data && response.data.length > 0) {
57
+ queryEmbedding = response.data[0].embedding;
58
+ }
59
+ } catch (error) {
60
+ console.error('Failed to generate embedding for search query:', error);
61
+ // Fallback to keyword search? For now just return empty if vector search fails
62
+ return [];
63
+ }
64
+
65
+ const allMemories = await memoryStorage.getAll();
66
+
67
+ // Stage 1: Vector Search (Fetch 50 candidates)
68
+ const candidates = allMemories
69
+ .filter(m => m.embedding && m.embedding.length > 0)
70
+ .map(memory => {
71
+ const similarity = this.cosineSimilarity(queryEmbedding, memory.embedding!);
72
+ return { ...memory, similarity };
73
+ })
74
+ .filter(r => r.similarity >= minSimilarity)
75
+ .sort((a, b) => b.similarity - a.similarity)
76
+ .slice(0, 50); // increased limit for reranking
77
+
78
+ if (candidates.length === 0) return [];
79
+
80
+ // Stage 2: Reranking
81
+ try {
82
+ // Rerank candidates
83
+ const rerankResponse = await apiClient.rerank({
84
+ query,
85
+ documents: candidates.map(c => c.content),
86
+ top_n: limit
87
+ });
88
+
89
+ // Map reranked results back to memory objects
90
+ const rerankedResults = rerankResponse.results.map(result => {
91
+ const candidate = candidates[result.index];
92
+ return {
93
+ ...candidate,
94
+ similarity: result.score // Update similarity with reranker score
95
+ };
96
+ });
97
+
98
+ return rerankedResults;
99
+
100
+ } catch (error) {
101
+ console.warn('Reranking failed, falling back to vector similarity:', error);
102
+ // Fallback: return top K from vector search
103
+ return candidates.slice(0, limit);
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Retrieve a memory by ID
109
+ */
110
+ async get(id: string): Promise<Memory | null> {
111
+ return memoryStorage.get(id);
112
+ }
113
+
114
+ /**
115
+ * Delete a memory
116
+ */
117
+ async forget(id: string): Promise<void> {
118
+ await memoryStorage.delete(id);
119
+ }
120
+
121
+ /**
122
+ * Clear all memories (useful for testing or reset)
123
+ */
124
+ async clear(): Promise<void> {
125
+ await memoryStorage.clear();
126
+ }
127
+
128
+ private cosineSimilarity(vecA: number[], vecB: number[]): number {
129
+ if (vecA.length !== vecB.length) return 0;
130
+
131
+ let dotProduct = 0;
132
+ let normA = 0;
133
+ let normB = 0;
134
+
135
+ for (let i = 0; i < vecA.length; i++) {
136
+ dotProduct += vecA[i] * vecB[i];
137
+ normA += vecA[i] * vecA[i];
138
+ normB += vecB[i] * vecB[i];
139
+ }
140
+
141
+ if (normA === 0 || normB === 0) return 0;
142
+ return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
143
+ }
144
+ }
145
+
146
+ export const memoryManager = MemoryManager.getInstance();
@@ -0,0 +1,90 @@
1
+ import Database from 'better-sqlite3';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+ import * as fs from 'fs';
5
+ import { CONFIG_DIR_NAME } from '../../config/constants.js';
6
+ import type { Memory, MemoryType } from './types.js';
7
+
8
+ export class MemoryStorage {
9
+ private db: Database.Database;
10
+ private dbPath: string;
11
+
12
+ constructor() {
13
+ const homeDir = os.homedir();
14
+ const configDir = path.join(homeDir, CONFIG_DIR_NAME);
15
+
16
+ // Ensure directory exists
17
+ if (!fs.existsSync(configDir)) {
18
+ fs.mkdirSync(configDir, { recursive: true });
19
+ }
20
+
21
+ this.dbPath = path.join(configDir, 'memory.db');
22
+ this.db = new Database(this.dbPath);
23
+ this.initialize();
24
+ }
25
+
26
+ private initialize(): void {
27
+ this.db.exec(`
28
+ CREATE TABLE IF NOT EXISTS memories (
29
+ id TEXT PRIMARY KEY,
30
+ content TEXT NOT NULL,
31
+ type TEXT NOT NULL,
32
+ metadata TEXT,
33
+ embedding TEXT,
34
+ created_at INTEGER NOT NULL,
35
+ updated_at INTEGER NOT NULL
36
+ );
37
+ CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
38
+ `);
39
+ }
40
+
41
+ async save(memory: Memory): Promise<void> {
42
+ const stmt = this.db.prepare(`
43
+ INSERT OR REPLACE INTO memories (id, content, type, metadata, embedding, created_at, updated_at)
44
+ VALUES (@id, @content, @type, @metadata, @embedding, @created_at, @updated_at)
45
+ `);
46
+
47
+ stmt.run({
48
+ ...memory,
49
+ metadata: JSON.stringify(memory.metadata || {}),
50
+ embedding: JSON.stringify(memory.embedding || []),
51
+ });
52
+ }
53
+
54
+ async get(id: string): Promise<Memory | null> {
55
+ const stmt = this.db.prepare('SELECT * FROM memories WHERE id = ?');
56
+ const row = stmt.get(id) as any;
57
+
58
+ if (!row) return null;
59
+ return this.mapRowToMemory(row);
60
+ }
61
+
62
+ async getAll(): Promise<Memory[]> {
63
+ const stmt = this.db.prepare('SELECT * FROM memories');
64
+ const rows = stmt.all() as any[];
65
+ return rows.map(r => this.mapRowToMemory(r));
66
+ }
67
+
68
+ async delete(id: string): Promise<void> {
69
+ const stmt = this.db.prepare('DELETE FROM memories WHERE id = ?');
70
+ stmt.run(id);
71
+ }
72
+
73
+ async clear(): Promise<void> {
74
+ this.db.exec('DELETE FROM memories');
75
+ }
76
+
77
+ private mapRowToMemory(row: any): Memory {
78
+ return {
79
+ id: row.id,
80
+ content: row.content,
81
+ type: row.type as MemoryType,
82
+ metadata: JSON.parse(row.metadata),
83
+ embedding: JSON.parse(row.embedding),
84
+ created_at: row.created_at,
85
+ updated_at: row.updated_at,
86
+ };
87
+ }
88
+ }
89
+
90
+ export const memoryStorage = new MemoryStorage();
@@ -0,0 +1,22 @@
1
+ export interface Memory {
2
+ id: string;
3
+ content: string;
4
+ type: MemoryType;
5
+ metadata?: Record<string, any>;
6
+ embedding?: number[];
7
+ created_at: number;
8
+ updated_at: number;
9
+ }
10
+
11
+ export type MemoryType = 'preference' | 'mistake' | 'knowledge' | 'task_context';
12
+
13
+ export interface MemoryMetadata {
14
+ source?: string;
15
+ confidence?: number;
16
+ tags?: string[];
17
+ [key: string]: any;
18
+ }
19
+
20
+ export interface SearchResult extends Memory {
21
+ similarity: number;
22
+ }
@@ -8,6 +8,7 @@ import { registerShellTools } from './shell.js';
8
8
  import { registerSearchTools } from './search.js';
9
9
  import { registerGitTools } from './git.js';
10
10
  import { registerWebTools } from './web.js';
11
+ import { registerMemoryTools } from './memory.js';
11
12
 
12
13
  export function registerAllTools(): void {
13
14
  registerFileTools();
@@ -15,6 +16,7 @@ export function registerAllTools(): void {
15
16
  registerSearchTools();
16
17
  registerGitTools();
17
18
  registerWebTools();
19
+ registerMemoryTools();
18
20
  }
19
21
 
20
22
  export * from './file.js';
@@ -22,3 +24,4 @@ export * from './shell.js';
22
24
  export * from './search.js';
23
25
  export * from './git.js';
24
26
  export * from './web.js';
27
+ export * from './memory.js';
@@ -0,0 +1,171 @@
1
+ import { toolRegistry, type ToolHandler } from '../registry.js';
2
+ import { memoryManager } from '../../memory/index.js';
3
+
4
+ // Remember Tool
5
+ const rememberTool: ToolHandler = {
6
+ name: 'remember',
7
+ safeToAutoRun: true,
8
+ definition: {
9
+ type: 'function',
10
+ function: {
11
+ name: 'remember',
12
+ description:
13
+ 'Store a piece of information, preference, or concept in long-term memory. Use this to remember user choices, project guidelines, or important facts.',
14
+ parameters: {
15
+ type: 'object',
16
+ properties: {
17
+ content: {
18
+ type: 'string',
19
+ description: 'The information to remember.',
20
+ },
21
+ type: {
22
+ type: 'string',
23
+ enum: ['preference', 'mistake', 'knowledge', 'task_context'],
24
+ description: 'The type of memory. Default is knowledge.',
25
+ },
26
+ tags: {
27
+ type: 'array',
28
+ items: { type: 'string' },
29
+ description: 'Tags to categorize this memory.',
30
+ },
31
+ },
32
+ required: ['content'],
33
+ },
34
+ },
35
+ },
36
+ async execute(args) {
37
+ const content = args.content as string;
38
+ const type = (args.type as any) || 'knowledge';
39
+ const tags = (args.tags as string[]) || [];
40
+
41
+ const memory = await memoryManager.remember(content, type, { tags });
42
+ return `Remembered: "${content}" (ID: ${memory.id})`;
43
+ },
44
+ };
45
+
46
+ // Recall Tool
47
+ const recallTool: ToolHandler = {
48
+ name: 'recall',
49
+ safeToAutoRun: true,
50
+ definition: {
51
+ type: 'function',
52
+ function: {
53
+ name: 'recall',
54
+ description:
55
+ 'Search long-term memory for relevant information. Use this to find past decisions, user preferences, or project guidelines.',
56
+ parameters: {
57
+ type: 'object',
58
+ properties: {
59
+ query: {
60
+ type: 'string',
61
+ description: 'The search query to find relevant memories.',
62
+ },
63
+ limit: {
64
+ type: 'number',
65
+ description: 'Maximum number of results to return. Default is 5.',
66
+ },
67
+ },
68
+ required: ['query'],
69
+ },
70
+ },
71
+ },
72
+ async execute(args) {
73
+ const query = args.query as string;
74
+ const limit = (args.limit as number) || 5;
75
+
76
+ const results = await memoryManager.search(query, limit);
77
+ if (results.length === 0) {
78
+ return 'No relevant memories found.';
79
+ }
80
+
81
+ return results
82
+ .map(
83
+ (m) =>
84
+ `[${m.type.toUpperCase()}] ${m.content} (Similarity: ${(m.similarity * 100).toFixed(1)}%)`
85
+ )
86
+ .join('\n');
87
+ },
88
+ };
89
+
90
+ // Forget Tool
91
+ const forgetTool: ToolHandler = {
92
+ name: 'forget',
93
+ safeToAutoRun: false, // Deletion is unsafe
94
+ definition: {
95
+ type: 'function',
96
+ function: {
97
+ name: 'forget',
98
+ description: 'Delete a memory by its ID.',
99
+ parameters: {
100
+ type: 'object',
101
+ properties: {
102
+ id: {
103
+ type: 'string',
104
+ description: 'The ID of the memory to delete.',
105
+ },
106
+ },
107
+ required: ['id'],
108
+ },
109
+ },
110
+ },
111
+ async execute(args) {
112
+ const id = args.id as string;
113
+ await memoryManager.forget(id);
114
+ return `Forgot memory with ID: ${id}`;
115
+ },
116
+ };
117
+
118
+ // Learn Mistake Tool
119
+ const learnMistakeTool: ToolHandler = {
120
+ name: 'learn_mistake',
121
+ safeToAutoRun: true, // Learning is safe
122
+ definition: {
123
+ type: 'function',
124
+ function: {
125
+ name: 'learn_mistake',
126
+ description:
127
+ 'Record a mistake and its solution to avoid repeating it in the future.',
128
+ parameters: {
129
+ type: 'object',
130
+ properties: {
131
+ error: {
132
+ type: 'string',
133
+ description: 'The error or mistake that occurred.',
134
+ },
135
+ fix: {
136
+ type: 'string',
137
+ description: 'The solution or lesson learned.',
138
+ },
139
+ context: {
140
+ type: 'string',
141
+ description: 'Optional context (e.g., file path, command).',
142
+ },
143
+ },
144
+ required: ['error', 'fix'],
145
+ },
146
+ },
147
+ },
148
+ async execute(args) {
149
+ const error = args.error as string;
150
+ const fix = args.fix as string;
151
+ const context = args.context as string | undefined;
152
+
153
+ const content = `Mistake: ${error}\nSolution: ${fix}${context ? `\nContext: ${context}` : ''}`;
154
+
155
+ const memory = await memoryManager.remember(content, 'mistake', {
156
+ originalError: error,
157
+ fix,
158
+ context,
159
+ });
160
+
161
+ return `Recorded mistake and solution (ID: ${memory.id}). I will remember this to avoid it in the future.`;
162
+ },
163
+ };
164
+
165
+ // Register all memory tools
166
+ export function registerMemoryTools(): void {
167
+ toolRegistry.register(rememberTool);
168
+ toolRegistry.register(recallTool);
169
+ toolRegistry.register(forgetTool);
170
+ toolRegistry.register(learnMistakeTool);
171
+ }
package/src/index.ts CHANGED
@@ -192,9 +192,6 @@ program
192
192
 
193
193
 
194
194
  // Interactive mode
195
- console.log(`\nšŸ¦… ${CLI_NAME} v${CLI_VERSION}`);
196
- console.log(` API: ${API_BASE_URL}`);
197
- console.log(` Model: ${DEFAULT_MODEL}\n`);
198
195
 
199
196
  const { waitUntilExit } = render(
200
197
  React.createElement(App, {