assistme 0.0.1 → 0.1.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 (45) hide show
  1. package/.prettierrc +7 -0
  2. package/PLAN.md +135 -0
  3. package/README.md +57 -1
  4. package/dist/chunk-3TP3YO56.js +410 -0
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.js +3432 -0
  7. package/dist/supabase-BQRPXXNA.js +44 -0
  8. package/eslint.config.js +20 -0
  9. package/package.json +52 -7
  10. package/skills/form-filling/SKILL.md +35 -0
  11. package/skills/price-comparison/SKILL.md +39 -0
  12. package/skills/web-research/SKILL.md +39 -0
  13. package/src/agent/memory-extractor.ts +128 -0
  14. package/src/agent/memory.test.ts +179 -0
  15. package/src/agent/memory.ts +266 -0
  16. package/src/agent/processor.test.ts +246 -0
  17. package/src/agent/processor.ts +586 -0
  18. package/src/agent/scheduler.test.ts +67 -0
  19. package/src/agent/scheduler.ts +286 -0
  20. package/src/agent/session.test.ts +183 -0
  21. package/src/agent/session.ts +269 -0
  22. package/src/agent/skill-extractor.ts +252 -0
  23. package/src/agent/skills.test.ts +149 -0
  24. package/src/agent/skills.ts +716 -0
  25. package/src/db/supabase.test.ts +285 -0
  26. package/src/db/supabase.ts +507 -0
  27. package/src/index.ts +835 -0
  28. package/src/tools/browser.ts +551 -0
  29. package/src/tools/filesystem.test.ts +96 -0
  30. package/src/tools/filesystem.ts +142 -0
  31. package/src/tools/index.ts +360 -0
  32. package/src/tools/shell.test.ts +79 -0
  33. package/src/tools/shell.ts +69 -0
  34. package/src/tools/web.ts +73 -0
  35. package/src/utils/config.test.ts +102 -0
  36. package/src/utils/config.ts +56 -0
  37. package/src/utils/logger.ts +89 -0
  38. package/src/utils/rate-limiter.test.ts +98 -0
  39. package/src/utils/rate-limiter.ts +112 -0
  40. package/src/utils/retry.test.ts +213 -0
  41. package/src/utils/retry.ts +182 -0
  42. package/src/utils/validation.test.ts +153 -0
  43. package/src/utils/validation.ts +101 -0
  44. package/tsconfig.json +20 -0
  45. package/vitest.config.ts +10 -0
package/.prettierrc ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "semi": true,
3
+ "singleQuote": false,
4
+ "trailingComma": "es5",
5
+ "printWidth": 100,
6
+ "tabWidth": 2
7
+ }
package/PLAN.md ADDED
@@ -0,0 +1,135 @@
1
+ # AssistMe CLI Agent - Implementation Plan
2
+
3
+ ## Vision
4
+ Build a CLI tool under `packages/assistme` based on Claude Agent SDK that:
5
+ - Runs as a local daemon, listening for tasks from the web UI
6
+ - Processes messages using Claude Agent SDK with full agentic capabilities (tool use, multi-turn)
7
+ - Streams real-time events (thinking, tool calls, text output) back to the web UI
8
+ - Surpasses OpenClaw by offering: streaming UI, visible tool execution, workspace awareness, and tight web integration
9
+
10
+ ## Architecture
11
+
12
+ ```
13
+ ┌─────────────┐ ┌──────────────┐ ┌──────────────────┐
14
+ │ Web UI │────▶│ Supabase DB │◀────│ CLI Agent │
15
+ │ (web/) │◀────│ │────▶│ (packages/ │
16
+ │ │ │ agent_tasks │ │ assistme/) │
17
+ │ Send message│ │ task_events │ │ │
18
+ │ See results │ │ sessions │ │ Claude Agent SDK │
19
+ └─────────────┘ └──────────────┘ └──────────────────┘
20
+
21
+ Flow:
22
+ 1. User sends message on web → inserts into agent_tasks (status: pending)
23
+ 2. CLI agent polls/subscribes → picks up task (status: running)
24
+ 3. Claude Agent SDK processes → streams events to agent_task_events
25
+ 4. Web UI polls task_events → renders streaming output in real-time
26
+ 5. Task completes → status: completed, final response stored
27
+ ```
28
+
29
+ ## Database Design
30
+
31
+ ### New Tables Needed
32
+
33
+ #### 1. `agent_sessions` - Track running CLI agent instances
34
+ ```sql
35
+ - id: UUID PK
36
+ - user_id: UUID FK → user_profiles
37
+ - session_name: VARCHAR(100) -- e.g., "My Workspace"
38
+ - status: VARCHAR(20) -- 'online', 'offline', 'busy'
39
+ - workspace_path: TEXT -- local filesystem path the agent operates in
40
+ - agent_version: VARCHAR(50)
41
+ - started_at: TIMESTAMPTZ
42
+ - last_heartbeat_at: TIMESTAMPTZ -- CLI sends heartbeat every 30s
43
+ - ended_at: TIMESTAMPTZ
44
+ - metadata: JSONB -- capabilities, config, etc.
45
+ - created_at: TIMESTAMPTZ
46
+ ```
47
+
48
+ #### 2. `agent_tasks` - Commands/tasks from web to CLI
49
+ ```sql
50
+ - id: UUID PK
51
+ - session_id: UUID FK → agent_sessions
52
+ - user_id: UUID FK → user_profiles
53
+ - conversation_id: UUID FK → conversations (nullable, for context threading)
54
+ - prompt: TEXT -- the user's message/command
55
+ - status: VARCHAR(20) -- 'pending', 'running', 'completed', 'failed', 'cancelled'
56
+ - result_summary: TEXT -- final text response
57
+ - error_message: TEXT
58
+ - token_usage: JSONB -- {prompt_tokens, completion_tokens, total_tokens}
59
+ - started_at: TIMESTAMPTZ
60
+ - completed_at: TIMESTAMPTZ
61
+ - created_at: TIMESTAMPTZ
62
+ - updated_at: TIMESTAMPTZ
63
+ ```
64
+
65
+ #### 3. `agent_task_events` - Streaming events from CLI
66
+ ```sql
67
+ - id: UUID PK
68
+ - task_id: UUID FK → agent_tasks
69
+ - event_type: VARCHAR(30) -- 'text_delta', 'tool_use_start', 'tool_use_input', 'tool_result', 'thinking', 'error', 'status_change'
70
+ - event_data: JSONB -- flexible payload per event type
71
+ - sequence_number: INTEGER -- ordering within a task
72
+ - created_at: TIMESTAMPTZ
73
+ ```
74
+
75
+ ### Existing Tables - No Changes Needed
76
+ - `conversations` - Can be reused for threading agent task history
77
+ - `chat_messages` - Not used directly (agent_tasks + agent_task_events replace this)
78
+ - `user_profiles` - No changes needed
79
+ - `user_credits` / `llm_token_usage` - Reused for billing
80
+
81
+ ## Package Structure
82
+
83
+ ```
84
+ packages/assistme/
85
+ ├── package.json # CLI package with bin entry
86
+ ├── tsconfig.json
87
+ ├── src/
88
+ │ ├── index.ts # CLI entry point (commander.js)
89
+ │ ├── agent/
90
+ │ │ ├── session.ts # Session management (heartbeat, lifecycle)
91
+ │ │ └── processor.ts # Task processor using Claude Agent SDK
92
+ │ ├── db/
93
+ │ │ └── supabase.ts # Supabase client for task polling & event writing
94
+ │ ├── tools/
95
+ │ │ ├── index.ts # MCP tool registry
96
+ │ │ ├── filesystem.ts # File read/write/search tools
97
+ │ │ ├── shell.ts # Shell command execution
98
+ │ │ └── web.ts # Web search/fetch tools
99
+ │ └── utils/
100
+ │ ├── config.ts # Config management (.assistme.json)
101
+ │ └── logger.ts # Structured logging with levels
102
+ ├── .assistme.json.example
103
+ └── README.md
104
+ ```
105
+
106
+ ## Web UI Addition (web/src/)
107
+
108
+ New page: `/agent` - Agent chat interface
109
+ - Message input with send button
110
+ - Streaming output display (text, tool calls, results)
111
+ - Session status indicator (online/offline)
112
+ - Task history sidebar
113
+
114
+ ## Implementation Steps
115
+
116
+ ### Phase 1: Database (Migration)
117
+ 1. Create migration for agent_sessions, agent_tasks, agent_task_events
118
+ 2. Add RLS policies and indexes
119
+
120
+ ### Phase 2: CLI Package
121
+ 1. Set up package.json with dependencies
122
+ 2. Implement Supabase client for DB communication
123
+ 3. Implement session management (start/stop/heartbeat)
124
+ 4. Implement task processor with Claude Agent SDK
125
+ 5. Implement MCP tools (filesystem, shell, web)
126
+ 6. Wire up CLI commands (assistme start, assistme login, assistme status)
127
+
128
+ ### Phase 3: Web UI
129
+ 1. Create /agent page with chat interface
130
+ 2. Implement task submission (insert into agent_tasks)
131
+ 3. Implement event polling (poll agent_task_events)
132
+ 4. Render streaming events (text, tool use, thinking)
133
+
134
+ ### Phase 4: Edge Function
135
+ 1. Create agent-submit-task edge function for mobile clients
package/README.md CHANGED
@@ -1 +1,57 @@
1
- assist me ai
1
+ # @assistme/cli
2
+
3
+ AI-powered CLI agent that connects to the AssistMe web UI. Send messages from the web, and the CLI agent processes them locally using Claude with full tool access (file operations, shell commands, web search).
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Install dependencies
9
+ cd packages/assistme && npm install
10
+
11
+ # Build
12
+ npm run build
13
+
14
+ # Configure
15
+ assistme config set supabaseUrl https://your-project.supabase.co
16
+ assistme config set supabaseAnonKey your-anon-key
17
+ assistme config set anthropicApiKey sk-ant-...
18
+
19
+ # Login
20
+ assistme login -e your@email.com -p your-password
21
+
22
+ # Start the agent (in your project directory)
23
+ assistme start
24
+ ```
25
+
26
+ ## Commands
27
+
28
+ | Command | Description |
29
+ |---------|-------------|
30
+ | `assistme start` | Start the agent daemon and listen for tasks |
31
+ | `assistme login` | Authenticate with your AssistMe account |
32
+ | `assistme status` | Check active sessions |
33
+ | `assistme config set <key> <value>` | Set configuration |
34
+ | `assistme config show` | Show current configuration |
35
+
36
+ ## Architecture
37
+
38
+ ```
39
+ Web UI → Supabase DB (agent_tasks) → CLI Agent → Claude API → Tool Execution → Events back to DB → Web UI
40
+ ```
41
+
42
+ The CLI agent:
43
+ 1. Registers a session in `agent_sessions`
44
+ 2. Polls `agent_tasks` for new pending tasks
45
+ 3. Processes each task using Claude with tool access
46
+ 4. Streams events to `agent_task_events` (text, tool calls, results)
47
+ 5. Web UI polls events for real-time display
48
+
49
+ ## Tools Available
50
+
51
+ - **read_file** - Read file contents with line numbers
52
+ - **write_file** - Create or overwrite files
53
+ - **search_files** - Glob pattern file search
54
+ - **search_content** - Regex content search across files
55
+ - **list_directory** - List directory contents
56
+ - **execute_command** - Run shell commands
57
+ - **web_fetch** - Fetch web page content
@@ -0,0 +1,410 @@
1
+ // src/db/supabase.ts
2
+ import { createClient } from "@supabase/supabase-js";
3
+
4
+ // src/utils/config.ts
5
+ import Conf from "conf";
6
+ import { resolve } from "path";
7
+ var CONFIG_DEFAULTS = {
8
+ sessionName: "Default",
9
+ model: "claude-sonnet-4-20250514",
10
+ maxTurns: 50
11
+ };
12
+ var config = new Conf({
13
+ projectName: "assistme",
14
+ defaults: CONFIG_DEFAULTS
15
+ });
16
+ function getConfig() {
17
+ const supabaseUrl = process.env.SUPABASE_URL || config.get("supabaseUrl") || "";
18
+ const supabaseAnonKey = process.env.SUPABASE_ANON_KEY || config.get("supabaseAnonKey") || "";
19
+ const anthropicApiKey = process.env.ANTHROPIC_API_KEY || config.get("anthropicApiKey") || "";
20
+ const workspacePath = config.get("workspacePath") || process.cwd();
21
+ return {
22
+ supabaseUrl,
23
+ supabaseAnonKey,
24
+ anthropicApiKey,
25
+ workspacePath: resolve(workspacePath),
26
+ sessionName: config.get("sessionName") || "Default",
27
+ model: config.get("model") || "claude-sonnet-4-20250514",
28
+ maxTurns: config.get("maxTurns") || 50
29
+ };
30
+ }
31
+ function setConfig(key, value) {
32
+ config.set(key, value);
33
+ }
34
+ function clearConfig() {
35
+ config.clear();
36
+ }
37
+ function getConfigPath() {
38
+ return config.path;
39
+ }
40
+
41
+ // src/utils/logger.ts
42
+ import chalk from "chalk";
43
+ import { randomUUID } from "crypto";
44
+ var currentLevel = "info";
45
+ var currentCorrelationId = null;
46
+ var LEVEL_ORDER = {
47
+ debug: 0,
48
+ info: 1,
49
+ warn: 2,
50
+ error: 3
51
+ };
52
+ function setLogLevel(level) {
53
+ currentLevel = level;
54
+ }
55
+ function setCorrelationId(id) {
56
+ currentCorrelationId = id;
57
+ }
58
+ function newCorrelationId() {
59
+ const id = randomUUID().slice(0, 8);
60
+ currentCorrelationId = id;
61
+ return id;
62
+ }
63
+ function shouldLog(level) {
64
+ return LEVEL_ORDER[level] >= LEVEL_ORDER[currentLevel];
65
+ }
66
+ function timestamp() {
67
+ return (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
68
+ }
69
+ function prefix() {
70
+ const ts = timestamp();
71
+ return currentCorrelationId ? `${ts} ${currentCorrelationId}` : ts;
72
+ }
73
+ var log = {
74
+ debug(msg, ...args) {
75
+ if (shouldLog("debug")) {
76
+ console.log(chalk.gray(`[${prefix()}] DEBUG`), msg, ...args);
77
+ }
78
+ },
79
+ info(msg, ...args) {
80
+ if (shouldLog("info")) {
81
+ console.log(chalk.blue(`[${prefix()}]`), msg, ...args);
82
+ }
83
+ },
84
+ success(msg, ...args) {
85
+ if (shouldLog("info")) {
86
+ console.log(chalk.green(`[${prefix()}] \u2713`), msg, ...args);
87
+ }
88
+ },
89
+ warn(msg, ...args) {
90
+ if (shouldLog("warn")) {
91
+ console.log(chalk.yellow(`[${prefix()}] WARN`), msg, ...args);
92
+ }
93
+ },
94
+ error(msg, ...args) {
95
+ if (shouldLog("error")) {
96
+ console.error(chalk.red(`[${prefix()}] ERROR`), msg, ...args);
97
+ }
98
+ },
99
+ agent(msg) {
100
+ console.log(chalk.cyan(" \u25B8"), msg);
101
+ },
102
+ tool(name, msg) {
103
+ console.log(chalk.magenta(` \u26A1 ${name}:`), msg);
104
+ },
105
+ result(msg) {
106
+ console.log(chalk.green(" \u2190"), msg);
107
+ }
108
+ };
109
+
110
+ // src/db/supabase.ts
111
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
112
+ import { join } from "path";
113
+ import { homedir } from "os";
114
+ var CLI_AGENT_ID = "00000000-0000-0000-0000-000000000001";
115
+ var DAYBOX_AGENT_ID = "00000000-0000-0000-0000-000000000002";
116
+ var AUTH_DIR = join(homedir(), ".config", "assistme");
117
+ var AUTH_FILE = join(AUTH_DIR, "auth.json");
118
+ function ensureAuthDir() {
119
+ if (!existsSync(AUTH_DIR)) {
120
+ mkdirSync(AUTH_DIR, { recursive: true, mode: 448 });
121
+ }
122
+ }
123
+ function readAuthStore() {
124
+ try {
125
+ if (existsSync(AUTH_FILE)) {
126
+ return JSON.parse(readFileSync(AUTH_FILE, "utf-8"));
127
+ }
128
+ } catch {
129
+ }
130
+ return {};
131
+ }
132
+ function writeAuthStore(data) {
133
+ ensureAuthDir();
134
+ writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 384 });
135
+ }
136
+ var fileStorage = {
137
+ getItem(key) {
138
+ const store = readAuthStore();
139
+ return store[key] ?? null;
140
+ },
141
+ setItem(key, value) {
142
+ const store = readAuthStore();
143
+ store[key] = value;
144
+ writeAuthStore(store);
145
+ },
146
+ removeItem(key) {
147
+ const store = readAuthStore();
148
+ delete store[key];
149
+ writeAuthStore(store);
150
+ }
151
+ };
152
+ var supabase = null;
153
+ function getSupabase() {
154
+ if (!supabase) {
155
+ const config2 = getConfig();
156
+ if (!config2.supabaseUrl || !config2.supabaseAnonKey) {
157
+ throw new Error(
158
+ "Supabase not configured. Run `assistme config set supabaseUrl <url>` first."
159
+ );
160
+ }
161
+ supabase = createClient(config2.supabaseUrl, config2.supabaseAnonKey, {
162
+ auth: {
163
+ storage: fileStorage,
164
+ autoRefreshToken: true,
165
+ persistSession: true
166
+ }
167
+ });
168
+ }
169
+ return supabase;
170
+ }
171
+ async function loginWithToken(cliToken) {
172
+ let parsed;
173
+ try {
174
+ const json = Buffer.from(cliToken, "base64").toString("utf-8");
175
+ parsed = JSON.parse(json);
176
+ if (!parsed.access_token || !parsed.refresh_token) {
177
+ throw new Error("Missing token fields");
178
+ }
179
+ } catch {
180
+ throw new Error(
181
+ "Invalid token format. Please copy the full token from the web page."
182
+ );
183
+ }
184
+ const sb = getSupabase();
185
+ const { data, error } = await sb.auth.setSession({
186
+ access_token: parsed.access_token,
187
+ refresh_token: parsed.refresh_token
188
+ });
189
+ if (error) throw new Error(`Login failed: ${error.message}`);
190
+ if (!data.user) throw new Error("Login failed: no user returned");
191
+ return data.user.id;
192
+ }
193
+ async function getSession() {
194
+ const sb = getSupabase();
195
+ const { data } = await sb.auth.getSession();
196
+ return data.session;
197
+ }
198
+ async function getCurrentUserId() {
199
+ const sb = getSupabase();
200
+ const { data, error } = await sb.auth.getUser();
201
+ if (error || !data.user) {
202
+ throw new Error("Not authenticated. Run `assistme login`.");
203
+ }
204
+ return data.user.id;
205
+ }
206
+ async function logout() {
207
+ const sb = getSupabase();
208
+ await sb.auth.signOut();
209
+ try {
210
+ writeAuthStore({});
211
+ } catch {
212
+ }
213
+ }
214
+ async function createSession(userId, sessionName, workspacePath, version) {
215
+ const sb = getSupabase();
216
+ const { data, error } = await sb.from("agent_sessions").insert({
217
+ user_id: userId,
218
+ session_name: sessionName,
219
+ status: "online",
220
+ workspace_path: workspacePath,
221
+ agent_version: version,
222
+ metadata: { model: getConfig().model }
223
+ }).select().single();
224
+ if (error) throw new Error(`Failed to create session: ${error.message}`);
225
+ return data;
226
+ }
227
+ async function updateHeartbeat(sessionId) {
228
+ const sb = getSupabase();
229
+ const { error } = await sb.from("agent_sessions").update({ last_heartbeat_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", sessionId);
230
+ if (error) log.warn(`Heartbeat update failed: ${error.message}`);
231
+ }
232
+ async function endSession(sessionId) {
233
+ const sb = getSupabase();
234
+ const { error } = await sb.from("agent_sessions").update({
235
+ status: "offline",
236
+ ended_at: (/* @__PURE__ */ new Date()).toISOString()
237
+ }).eq("id", sessionId);
238
+ if (error) log.error(`Failed to end session: ${error.message}`);
239
+ }
240
+ async function setSessionBusy(sessionId, busy) {
241
+ const sb = getSupabase();
242
+ await sb.from("agent_sessions").update({ status: busy ? "busy" : "online" }).eq("id", sessionId);
243
+ }
244
+ async function getOrCreateCliConversation(userId, sessionId) {
245
+ const sb = getSupabase();
246
+ const { data: existing } = await sb.from("conversations").select("id").eq("agent", "cli").eq("created_by", userId).order("created_at", { ascending: false }).limit(1);
247
+ if (existing && existing.length > 0) {
248
+ return existing[0].id;
249
+ }
250
+ const { data: conv, error: convErr } = await sb.from("conversations").insert({
251
+ conversation_type: "direct",
252
+ agent: "cli",
253
+ created_by: userId
254
+ }).select("id").single();
255
+ if (convErr || !conv) {
256
+ throw new Error(`Failed to create conversation: ${convErr?.message}`);
257
+ }
258
+ await sb.from("conversation_participants").insert([
259
+ { conversation_id: conv.id, user_id: userId, role: "member" },
260
+ { conversation_id: conv.id, user_id: CLI_AGENT_ID, role: "member" }
261
+ ]);
262
+ return conv.id;
263
+ }
264
+ async function createTask(conversationId, userId, sessionId, prompt) {
265
+ const sb = getSupabase();
266
+ const { data: rpcResult, error: rpcError } = await sb.rpc(
267
+ "create_task_pair",
268
+ {
269
+ p_conversation_id: conversationId,
270
+ p_user_id: userId,
271
+ p_agent_id: CLI_AGENT_ID,
272
+ p_session_id: sessionId,
273
+ p_prompt: prompt
274
+ }
275
+ );
276
+ if (!rpcError && rpcResult) {
277
+ return { ...rpcResult, prompt };
278
+ }
279
+ if (rpcError) {
280
+ log.debug(`create_task_pair RPC unavailable, using fallback: ${rpcError.message}`);
281
+ }
282
+ const { error: userErr } = await sb.from("conversation_messages").insert({
283
+ conversation_id: conversationId,
284
+ sender_id: userId,
285
+ role: "user",
286
+ content: prompt
287
+ });
288
+ if (userErr) throw new Error(`Failed to create user message: ${userErr.message}`);
289
+ const { data, error } = await sb.from("conversation_messages").insert({
290
+ conversation_id: conversationId,
291
+ sender_id: CLI_AGENT_ID,
292
+ role: "assistant",
293
+ content: "",
294
+ status: "pending",
295
+ session_id: sessionId,
296
+ metadata: { prompt }
297
+ }).select().single();
298
+ if (error) throw new Error(`Failed to create task: ${error.message}`);
299
+ return { ...data, prompt };
300
+ }
301
+ async function pollPendingTasks(sessionId) {
302
+ const sb = getSupabase();
303
+ const { data, error } = await sb.from("conversation_messages").select("*").eq("session_id", sessionId).eq("status", "pending").order("created_at", { ascending: true }).limit(1);
304
+ if (error) {
305
+ log.warn(`Task poll failed: ${error.message}`);
306
+ return [];
307
+ }
308
+ return (data || []).map((row) => ({
309
+ ...row,
310
+ prompt: row.metadata?.prompt || row.content || ""
311
+ }));
312
+ }
313
+ async function claimTask(messageId) {
314
+ const sb = getSupabase();
315
+ const { error } = await sb.from("conversation_messages").update({
316
+ status: "running",
317
+ metadata: { started_at: (/* @__PURE__ */ new Date()).toISOString() }
318
+ }).eq("id", messageId).eq("status", "pending");
319
+ if (error) throw new Error(`Failed to claim task: ${error.message}`);
320
+ }
321
+ async function completeTask(messageId, resultSummary, tokenUsage) {
322
+ const sb = getSupabase();
323
+ const { data: current } = await sb.from("conversation_messages").select("metadata").eq("id", messageId).single();
324
+ const existingMeta = current?.metadata || {};
325
+ const { error } = await sb.from("conversation_messages").update({
326
+ content: resultSummary,
327
+ status: "completed",
328
+ metadata: {
329
+ ...existingMeta,
330
+ completed_at: (/* @__PURE__ */ new Date()).toISOString(),
331
+ token_usage: tokenUsage || null
332
+ }
333
+ }).eq("id", messageId);
334
+ if (error) throw new Error(`Failed to complete task: ${error.message}`);
335
+ }
336
+ async function failTask(messageId, errorMessage) {
337
+ const sb = getSupabase();
338
+ const { data: current } = await sb.from("conversation_messages").select("metadata").eq("id", messageId).single();
339
+ const existingMeta = current?.metadata || {};
340
+ const { error } = await sb.from("conversation_messages").update({
341
+ status: "failed",
342
+ content: errorMessage,
343
+ metadata: {
344
+ ...existingMeta,
345
+ completed_at: (/* @__PURE__ */ new Date()).toISOString(),
346
+ error_message: errorMessage
347
+ }
348
+ }).eq("id", messageId);
349
+ if (error) log.error(`Failed to update task status: ${error.message}`);
350
+ }
351
+ var eventSequence = 0;
352
+ function resetEventSequence() {
353
+ eventSequence = 0;
354
+ }
355
+ async function emitEvent(messageId, eventType, eventData) {
356
+ const sb = getSupabase();
357
+ eventSequence++;
358
+ const { error } = await sb.from("message_events").insert({
359
+ message_id: messageId,
360
+ event_type: eventType,
361
+ event_data: eventData,
362
+ sequence_number: eventSequence
363
+ });
364
+ if (error) log.warn(`Failed to emit event: ${error.message}`);
365
+ }
366
+ async function emitEvents(messageId, events) {
367
+ const sb = getSupabase();
368
+ const rows = events.map((e) => {
369
+ eventSequence++;
370
+ return {
371
+ message_id: messageId,
372
+ event_type: e.type,
373
+ event_data: e.data,
374
+ sequence_number: eventSequence
375
+ };
376
+ });
377
+ const { error } = await sb.from("message_events").insert(rows);
378
+ if (error) log.warn(`Failed to emit events batch: ${error.message}`);
379
+ }
380
+
381
+ export {
382
+ getConfig,
383
+ setConfig,
384
+ clearConfig,
385
+ getConfigPath,
386
+ setLogLevel,
387
+ setCorrelationId,
388
+ newCorrelationId,
389
+ log,
390
+ CLI_AGENT_ID,
391
+ DAYBOX_AGENT_ID,
392
+ getSupabase,
393
+ loginWithToken,
394
+ getSession,
395
+ getCurrentUserId,
396
+ logout,
397
+ createSession,
398
+ updateHeartbeat,
399
+ endSession,
400
+ setSessionBusy,
401
+ getOrCreateCliConversation,
402
+ createTask,
403
+ pollPendingTasks,
404
+ claimTask,
405
+ completeTask,
406
+ failTask,
407
+ resetEventSequence,
408
+ emitEvent,
409
+ emitEvents
410
+ };
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node