@siftd/connect-agent 0.1.0 → 0.2.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 ADDED
@@ -0,0 +1,106 @@
1
+ # @siftd/connect-agent
2
+
3
+ Master orchestrator agent that connects to [Connect](https://connect.siftd.app) - control Claude Code remotely from any browser.
4
+
5
+ ## What is this?
6
+
7
+ Connect Agent runs on your machine and bridges the Connect web app to Claude Code CLI. It's not just a relay - it's a **master orchestrator** with:
8
+
9
+ - **Persistent Memory** - Remembers your preferences, projects, and context across sessions
10
+ - **Worker Delegation** - Delegates complex tasks to Claude Code CLI workers
11
+ - **Task Scheduling** - Schedule tasks for future execution
12
+ - **Semantic Search** - Finds relevant memories by meaning, not just keywords
13
+
14
+ ## Quick Start
15
+
16
+ ```bash
17
+ # 1. Go to https://connect.siftd.app and get a pairing code
18
+
19
+ # 2. Pair your machine (with API key for full orchestrator mode)
20
+ npx @siftd/connect-agent pair <CODE> --api-key <YOUR_ANTHROPIC_API_KEY>
21
+
22
+ # 3. Start the agent
23
+ npx @siftd/connect-agent start
24
+ ```
25
+
26
+ ## Modes
27
+
28
+ ### Orchestrator Mode (Recommended)
29
+ When you provide an Anthropic API key, the agent runs as a **master orchestrator**:
30
+ - Uses Claude API directly for the orchestration layer
31
+ - Maintains persistent memory about you and your projects
32
+ - Delegates file/code work to Claude Code CLI workers
33
+ - Schedules future tasks
34
+
35
+ ### Simple Relay Mode
36
+ Without an API key, the agent acts as a simple relay to `claude -p --continue`.
37
+
38
+ ## Commands
39
+
40
+ ```bash
41
+ # Pair with the web app
42
+ connect-agent pair <CODE> [--api-key <KEY>]
43
+
44
+ # Start processing messages
45
+ connect-agent start [--interval <ms>]
46
+
47
+ # Check status
48
+ connect-agent status
49
+
50
+ # Set API key (without re-pairing)
51
+ connect-agent set-key <KEY>
52
+
53
+ # Clear configuration
54
+ connect-agent logout
55
+ ```
56
+
57
+ ## Special Commands (via web chat)
58
+
59
+ - `/reset` or `/clear` - Clear conversation history
60
+ - `/mode` - Check current mode (orchestrator vs simple)
61
+ - `/memory` - Show memory statistics
62
+
63
+ ## How It Works
64
+
65
+ ```
66
+ ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
67
+ │ Browser │────▶│ Connect Server │◀────│ Connect Agent │
68
+ │ (any device) │ │ (connect.siftd) │ │ (your machine) │
69
+ └─────────────────┘ └──────────────────┘ └────────┬────────┘
70
+
71
+
72
+ ┌─────────────────┐
73
+ │ Master │
74
+ │ Orchestrator │
75
+ │ (memory, sched) │
76
+ └────────┬────────┘
77
+ │ delegates
78
+
79
+ ┌─────────────────┐
80
+ │ Claude Code CLI │
81
+ │ (file ops, code)│
82
+ └─────────────────┘
83
+ ```
84
+
85
+ The orchestrator understands it's the **master**, not a worker. It:
86
+ 1. Receives your message from the web
87
+ 2. Searches memory for relevant context
88
+ 3. Decides what needs to be done
89
+ 4. Delegates actual work to Claude Code CLI
90
+ 5. Synthesizes results into a clear response
91
+ 6. Remembers important things for next time
92
+
93
+ ## Environment Variables
94
+
95
+ - `ANTHROPIC_API_KEY` - Alternative to `--api-key` flag
96
+ - `VOYAGE_API_KEY` - (Optional) For better semantic search embeddings
97
+
98
+ ## Requirements
99
+
100
+ - Node.js 18+
101
+ - [Claude Code CLI](https://claude.ai/code) installed and authenticated
102
+ - Anthropic API key (for orchestrator mode)
103
+
104
+ ## License
105
+
106
+ MIT
package/dist/agent.js CHANGED
@@ -1,17 +1,44 @@
1
1
  import { spawn } from 'child_process';
2
2
  import { pollMessages, sendResponse } from './api.js';
3
+ import { getUserId, getAnthropicApiKey } from './config.js';
4
+ import { MasterOrchestrator } from './orchestrator.js';
3
5
  // Strip ANSI escape codes for clean output
4
6
  function stripAnsi(str) {
5
7
  return str.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
6
8
  }
9
+ // Conversation history for orchestrator mode
10
+ let conversationHistory = [];
11
+ let orchestrator = null;
7
12
  /**
8
- * Send a message to Claude Code and get the response
9
- * Uses --continue to maintain conversation context
13
+ * Initialize the orchestrator if API key is available
10
14
  */
11
- async function sendToClaude(input) {
12
- return new Promise((resolve, reject) => {
15
+ function initOrchestrator() {
16
+ // Check env first, then stored config
17
+ const apiKey = process.env.ANTHROPIC_API_KEY || getAnthropicApiKey();
18
+ const userId = getUserId();
19
+ if (!apiKey) {
20
+ console.log('[AGENT] No API key - using simple relay mode');
21
+ console.log('[AGENT] To enable orchestrator: pair with --api-key flag');
22
+ return null;
23
+ }
24
+ if (!userId) {
25
+ console.log('[AGENT] No userId configured - using simple relay mode');
26
+ return null;
27
+ }
28
+ console.log('[AGENT] Initializing Master Orchestrator...');
29
+ return new MasterOrchestrator({
30
+ apiKey,
31
+ userId,
32
+ workspaceDir: process.env.HOME || '/tmp',
33
+ voyageApiKey: process.env.VOYAGE_API_KEY
34
+ });
35
+ }
36
+ /**
37
+ * Simple relay mode - just pipes to Claude Code CLI
38
+ */
39
+ async function sendToClaudeSimple(input) {
40
+ return new Promise((resolve) => {
13
41
  console.log(`\n[CLAUDE] Sending: ${input.substring(0, 80)}...`);
14
- // Use -p (print mode) with --continue to maintain context
15
42
  const claude = spawn('claude', ['-p', '--continue'], {
16
43
  cwd: process.env.HOME,
17
44
  env: process.env,
@@ -27,7 +54,6 @@ async function sendToClaude(input) {
27
54
  claude.stderr?.on('data', (data) => {
28
55
  const text = data.toString();
29
56
  errorOutput += text;
30
- // Only print stderr if it's not just status info
31
57
  if (!text.includes('Checking') && !text.includes('Connected')) {
32
58
  process.stderr.write(text);
33
59
  }
@@ -48,29 +74,88 @@ async function sendToClaude(input) {
48
74
  console.error(`\n[CLAUDE] Failed to start: ${err.message}`);
49
75
  resolve(`Error: Failed to start Claude - ${err.message}`);
50
76
  });
51
- // Send the input to claude's stdin
52
77
  if (claude.stdin) {
53
78
  claude.stdin.write(input);
54
79
  claude.stdin.end();
55
80
  }
56
81
  });
57
82
  }
83
+ /**
84
+ * Orchestrator mode - uses the master orchestrator with memory
85
+ */
86
+ async function sendToOrchestrator(input, orch) {
87
+ console.log(`\n[ORCHESTRATOR] Processing: ${input.substring(0, 80)}...`);
88
+ try {
89
+ const response = await orch.processMessage(input, conversationHistory, async (msg) => {
90
+ // Progress callback - log to console
91
+ console.log(`[ORCHESTRATOR] ${msg}`);
92
+ });
93
+ // Update conversation history
94
+ conversationHistory.push({ role: 'user', content: input });
95
+ conversationHistory.push({ role: 'assistant', content: response });
96
+ // Keep history manageable (last 20 exchanges)
97
+ if (conversationHistory.length > 40) {
98
+ conversationHistory = conversationHistory.slice(-40);
99
+ }
100
+ console.log(`\n[ORCHESTRATOR] Response: ${response.substring(0, 80)}...`);
101
+ return response;
102
+ }
103
+ catch (error) {
104
+ console.error('[ORCHESTRATOR] Error:', error);
105
+ return `Error: ${error instanceof Error ? error.message : 'Unknown error'}`;
106
+ }
107
+ }
58
108
  export async function processMessage(message) {
59
109
  console.log(`\n[WEB] >>> ${message.content}`);
60
- try {
61
- const response = await sendToClaude(message.content);
110
+ // Handle special commands
111
+ const content = message.content.trim().toLowerCase();
112
+ if (content === '/reset' || content === '/clear') {
113
+ conversationHistory = [];
114
+ return 'Conversation history cleared.';
115
+ }
116
+ if (content === '/mode') {
117
+ return orchestrator
118
+ ? 'Running in ORCHESTRATOR mode (with memory and delegation)'
119
+ : 'Running in SIMPLE mode (direct Claude Code relay)';
120
+ }
121
+ if (content === '/memory' && orchestrator) {
122
+ // Trigger memory stats
123
+ const response = await orchestrator.processMessage('Show me my memory stats', [], undefined);
62
124
  return response;
63
125
  }
126
+ try {
127
+ if (orchestrator) {
128
+ return await sendToOrchestrator(message.content, orchestrator);
129
+ }
130
+ else {
131
+ return await sendToClaudeSimple(message.content);
132
+ }
133
+ }
64
134
  catch (error) {
65
135
  console.error('Error:', error);
66
136
  return `Error: ${error instanceof Error ? error.message : 'Unknown error'}`;
67
137
  }
68
138
  }
69
139
  export async function runAgent(pollInterval = 2000) {
70
- console.log('╔══════════════════════════════════════════╗');
71
- console.log('║ Connect Agent - Claude Code Relay ║');
72
- console.log('╚══════════════════════════════════════════╝\n');
140
+ // Initialize orchestrator
141
+ orchestrator = initOrchestrator();
142
+ const mode = orchestrator ? 'ORCHESTRATOR' : 'SIMPLE RELAY';
143
+ console.log('╔══════════════════════════════════════════════════╗');
144
+ console.log('║ Connect Agent - Master Orchestrator ║');
145
+ console.log(`║ Mode: ${mode.padEnd(41)}║`);
146
+ console.log('╚══════════════════════════════════════════════════╝\n');
147
+ if (orchestrator) {
148
+ console.log('[AGENT] Features: Memory, Scheduling, Worker Delegation');
149
+ }
73
150
  console.log('[AGENT] Polling for web messages...\n');
151
+ // Graceful shutdown
152
+ process.on('SIGINT', () => {
153
+ console.log('\n[AGENT] Shutting down...');
154
+ if (orchestrator) {
155
+ orchestrator.shutdown();
156
+ }
157
+ process.exit(0);
158
+ });
74
159
  while (true) {
75
160
  try {
76
161
  const { messages } = await pollMessages();
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { program } from 'commander';
3
3
  import ora from 'ora';
4
- import { getServerUrl, setServerUrl, setAgentToken, setUserId, isConfigured, clearConfig, getConfigPath, } from './config.js';
4
+ import { getServerUrl, setServerUrl, setAgentToken, setUserId, setAnthropicApiKey, getAnthropicApiKey, isConfigured, clearConfig, getConfigPath, } from './config.js';
5
5
  import { connectWithPairingCode, checkConnection } from './api.js';
6
6
  import { runAgent } from './agent.js';
7
7
  program
@@ -13,6 +13,7 @@ program
13
13
  .description('Pair with Connect web app using a pairing code')
14
14
  .argument('<code>', 'The 6-character pairing code from the web app')
15
15
  .option('-s, --server <url>', 'Server URL', getServerUrl())
16
+ .option('-k, --api-key <key>', 'Anthropic API key for orchestrator mode')
16
17
  .action(async (code, options) => {
17
18
  const spinner = ora('Connecting to server...').start();
18
19
  try {
@@ -26,8 +27,15 @@ program
26
27
  }
27
28
  setAgentToken(result.agentToken);
28
29
  setUserId(result.userId);
30
+ // Store API key if provided
31
+ if (options.apiKey) {
32
+ setAnthropicApiKey(options.apiKey);
33
+ }
29
34
  spinner.succeed('Paired successfully!');
30
35
  console.log(`\nAgent is now connected to ${getServerUrl()}`);
36
+ if (options.apiKey) {
37
+ console.log('Orchestrator mode enabled (API key saved)');
38
+ }
31
39
  console.log('Run "connect-agent start" to begin processing messages.');
32
40
  }
33
41
  catch (error) {
@@ -64,6 +72,7 @@ program
64
72
  console.log(`Config file: ${getConfigPath()}`);
65
73
  console.log(`Server URL: ${getServerUrl()}`);
66
74
  console.log(`Configured: ${isConfigured() ? 'Yes' : 'No'}`);
75
+ console.log(`Orchestrator mode: ${getAnthropicApiKey() ? 'Yes (API key saved)' : 'No (simple relay)'}`);
67
76
  if (isConfigured()) {
68
77
  const spinner = ora('Checking connection...').start();
69
78
  const connected = await checkConnection();
@@ -90,4 +99,12 @@ program
90
99
  setServerUrl(url);
91
100
  console.log(`Server URL set to: ${url}`);
92
101
  });
102
+ program
103
+ .command('set-key')
104
+ .description('Set Anthropic API key for orchestrator mode')
105
+ .argument('<key>', 'Your Anthropic API key')
106
+ .action((key) => {
107
+ setAnthropicApiKey(key);
108
+ console.log('API key saved. Orchestrator mode will be enabled on next start.');
109
+ });
93
110
  program.parse();
package/dist/config.d.ts CHANGED
@@ -4,6 +4,8 @@ export declare function getAgentToken(): string | null;
4
4
  export declare function setAgentToken(token: string | null): void;
5
5
  export declare function getUserId(): string | null;
6
6
  export declare function setUserId(id: string | null): void;
7
+ export declare function getAnthropicApiKey(): string | null;
8
+ export declare function setAnthropicApiKey(key: string | null): void;
7
9
  export declare function isConfigured(): boolean;
8
10
  export declare function clearConfig(): void;
9
11
  export declare function getConfigPath(): string;
package/dist/config.js CHANGED
@@ -33,12 +33,24 @@ export function setUserId(id) {
33
33
  config.set('userId', id);
34
34
  }
35
35
  }
36
+ export function getAnthropicApiKey() {
37
+ return config.get('anthropicApiKey') ?? null;
38
+ }
39
+ export function setAnthropicApiKey(key) {
40
+ if (key === null) {
41
+ config.delete('anthropicApiKey');
42
+ }
43
+ else {
44
+ config.set('anthropicApiKey', key);
45
+ }
46
+ }
36
47
  export function isConfigured() {
37
48
  return !!getAgentToken() && !!getUserId();
38
49
  }
39
50
  export function clearConfig() {
40
51
  config.delete('agentToken');
41
52
  config.delete('userId');
53
+ config.delete('anthropicApiKey');
42
54
  }
43
55
  export function getConfigPath() {
44
56
  return config.path;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Advanced Memory System
3
+ *
4
+ * Features:
5
+ * - Hierarchical memory types (episodic, semantic, procedural)
6
+ * - Vector embeddings for semantic search
7
+ * - Importance scoring with temporal decay
8
+ * - Memory associations/links
9
+ * - Reflection and consolidation
10
+ */
11
+ export type MemoryType = 'episodic' | 'semantic' | 'procedural' | 'working';
12
+ export interface Memory {
13
+ id: string;
14
+ type: MemoryType;
15
+ content: string;
16
+ summary?: string;
17
+ source: string;
18
+ timestamp: string;
19
+ lastAccessed: string;
20
+ importance: number;
21
+ accessCount: number;
22
+ decayRate: number;
23
+ associations: string[];
24
+ tags: string[];
25
+ hasEmbedding: boolean;
26
+ }
27
+ export declare class AdvancedMemoryStore {
28
+ private db;
29
+ private vectorIndex;
30
+ private vectorPath;
31
+ private embedder;
32
+ private config;
33
+ constructor(options?: {
34
+ dbPath?: string;
35
+ vectorPath?: string;
36
+ voyageApiKey?: string;
37
+ maxMemories?: number;
38
+ });
39
+ private init;
40
+ private initVectorIndex;
41
+ remember(content: string, options?: {
42
+ type?: MemoryType;
43
+ source?: string;
44
+ importance?: number;
45
+ tags?: string[];
46
+ associations?: string[];
47
+ summary?: string;
48
+ }): Promise<string>;
49
+ search(query: string, options?: {
50
+ limit?: number;
51
+ type?: MemoryType;
52
+ minImportance?: number;
53
+ includeAssociations?: boolean;
54
+ }): Promise<Memory[]>;
55
+ private textSearch;
56
+ getById(id: string): Memory | null;
57
+ getByType(type: MemoryType, limit?: number): Memory[];
58
+ recall(id: string): Memory | null;
59
+ forget(id: string): Promise<boolean>;
60
+ updateImportance(id: string, importance: number): void;
61
+ associate(id1: string, id2: string): void;
62
+ applyDecay(): number;
63
+ reflect(): Promise<{
64
+ insights: string[];
65
+ consolidations: number;
66
+ memoriesProcessed: number;
67
+ }>;
68
+ stats(): {
69
+ total: number;
70
+ byType: Record<MemoryType, number>;
71
+ avgImportance: number;
72
+ totalAssociations: number;
73
+ oldestMemory: string | null;
74
+ newestMemory: string | null;
75
+ };
76
+ list(limit?: number): Memory[];
77
+ getRecent(limit?: number): Memory[];
78
+ private addEmbedding;
79
+ private createAssociations;
80
+ private removeAssociations;
81
+ private recordAccess;
82
+ private inferMemoryType;
83
+ private calculateInitialImportance;
84
+ private getDecayRateForType;
85
+ private extractTags;
86
+ private consolidateMemories;
87
+ private findCommonWords;
88
+ private pruneIfNeeded;
89
+ private rowToMemory;
90
+ close(): void;
91
+ }