assistme 0.4.0 → 0.5.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.
@@ -61,6 +61,7 @@ import chalk from "chalk";
61
61
  import { randomUUID } from "crypto";
62
62
  var currentLevel = "info";
63
63
  var currentCorrelationId = null;
64
+ var logHook = null;
64
65
  var LEVEL_ORDER = {
65
66
  debug: 0,
66
67
  info: 1,
@@ -70,6 +71,9 @@ var LEVEL_ORDER = {
70
71
  function setLogLevel(level) {
71
72
  currentLevel = level;
72
73
  }
74
+ function setLogHook(hook) {
75
+ logHook = hook;
76
+ }
73
77
  function setCorrelationId(id) {
74
78
  currentCorrelationId = id;
75
79
  }
@@ -91,39 +95,56 @@ function prefix() {
91
95
  var log = {
92
96
  debug(msg, ...args) {
93
97
  if (shouldLog("debug")) {
98
+ const text = formatArgs(msg, args);
99
+ logHook?.("stdout", `[DEBUG] ${text}`);
94
100
  console.log(chalk.gray(`[${prefix()}] DEBUG`), msg, ...args);
95
101
  }
96
102
  },
97
103
  info(msg, ...args) {
98
104
  if (shouldLog("info")) {
105
+ const text = formatArgs(msg, args);
106
+ logHook?.("stdout", text);
99
107
  console.log(chalk.blue(`[${prefix()}]`), msg, ...args);
100
108
  }
101
109
  },
102
110
  success(msg, ...args) {
103
111
  if (shouldLog("info")) {
112
+ const text = formatArgs(msg, args);
113
+ logHook?.("stdout", `\u2713 ${text}`);
104
114
  console.log(chalk.green(`[${prefix()}] \u2713`), msg, ...args);
105
115
  }
106
116
  },
107
117
  warn(msg, ...args) {
108
118
  if (shouldLog("warn")) {
119
+ const text = formatArgs(msg, args);
120
+ logHook?.("stderr", `[WARN] ${text}`);
109
121
  console.log(chalk.yellow(`[${prefix()}] WARN`), msg, ...args);
110
122
  }
111
123
  },
112
124
  error(msg, ...args) {
113
125
  if (shouldLog("error")) {
126
+ const text = formatArgs(msg, args);
127
+ logHook?.("stderr", `[ERROR] ${text}`);
114
128
  console.error(chalk.red(`[${prefix()}] ERROR`), msg, ...args);
115
129
  }
116
130
  },
117
131
  agent(msg) {
132
+ logHook?.("stdout", `\u25B8 ${msg}`);
118
133
  console.log(chalk.cyan(" \u25B8"), msg);
119
134
  },
120
135
  tool(name, msg) {
136
+ logHook?.("stdout", `\u26A1 ${name}: ${msg}`);
121
137
  console.log(chalk.magenta(` \u26A1 ${name}:`), msg);
122
138
  },
123
139
  result(msg) {
140
+ logHook?.("stdout", `\u2190 ${msg}`);
124
141
  console.log(chalk.green(" \u2190"), msg);
125
142
  }
126
143
  };
144
+ function formatArgs(msg, args) {
145
+ if (args.length === 0) return msg;
146
+ return `${msg} ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}`;
147
+ }
127
148
 
128
149
  // src/utils/schemas.ts
129
150
  import { z } from "zod";
@@ -424,6 +445,7 @@ export {
424
445
  writeAuthStore,
425
446
  callMcpHandler,
426
447
  setLogLevel,
448
+ setLogHook,
427
449
  setCorrelationId,
428
450
  newCorrelationId,
429
451
  log,
package/dist/index.js CHANGED
@@ -35,9 +35,10 @@ import {
35
35
  readAuthStore,
36
36
  safeParse,
37
37
  setCorrelationId,
38
+ setLogHook,
38
39
  setLogLevel,
39
40
  writeAuthStore
40
- } from "./chunk-4SBIN27G.js";
41
+ } from "./chunk-KAS2PTOX.js";
41
42
  import {
42
43
  clearConfig,
43
44
  getConfig,
@@ -6119,6 +6120,57 @@ var TaskProcessor = class {
6119
6120
  }
6120
6121
  };
6121
6122
 
6123
+ // src/db/session-log.ts
6124
+ var FLUSH_INTERVAL_MS = 3e3;
6125
+ var MAX_BATCH_SIZE = 100;
6126
+ var SessionLogEmitter = class {
6127
+ constructor(sessionId) {
6128
+ this.sessionId = sessionId;
6129
+ this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
6130
+ }
6131
+ sequence = 0;
6132
+ buffer = [];
6133
+ flushTimer = null;
6134
+ flushing = false;
6135
+ /** Queue a log entry for batch insertion */
6136
+ push(logType, message) {
6137
+ this.sequence++;
6138
+ this.buffer.push({ log_type: logType, message, seq: this.sequence });
6139
+ if (this.buffer.length >= MAX_BATCH_SIZE) {
6140
+ this.flush();
6141
+ }
6142
+ }
6143
+ /** Flush buffered logs to Supabase */
6144
+ async flush() {
6145
+ if (this.flushing || this.buffer.length === 0) return;
6146
+ const batch = this.buffer.splice(0);
6147
+ this.flushing = true;
6148
+ try {
6149
+ await callMcpHandler("log.emit_batch", {
6150
+ session_id: this.sessionId,
6151
+ logs: batch
6152
+ });
6153
+ } catch (err) {
6154
+ log.debug(
6155
+ `Failed to flush session logs: ${err instanceof Error ? err.message : err}`
6156
+ );
6157
+ if (this.buffer.length < MAX_BATCH_SIZE * 5) {
6158
+ this.buffer.unshift(...batch);
6159
+ }
6160
+ } finally {
6161
+ this.flushing = false;
6162
+ }
6163
+ }
6164
+ /** Stop the emitter and flush remaining logs */
6165
+ async stop() {
6166
+ if (this.flushTimer) {
6167
+ clearInterval(this.flushTimer);
6168
+ this.flushTimer = null;
6169
+ }
6170
+ await this.flush();
6171
+ }
6172
+ };
6173
+
6122
6174
  // src/commands/start.ts
6123
6175
  function registerStartCommand(program2) {
6124
6176
  program2.command("start", { isDefault: true, hidden: true }).description("Start the agent (default command)").option("-w, --workspace <path>", "Workspace path (default: current directory)").option("-n, --name <name>", "Session name").option("-v, --verbose", "Enable verbose/debug logging").action(runAgent);
@@ -6182,10 +6234,16 @@ async function runAgent(opts) {
6182
6234
  const processor = new TaskProcessor();
6183
6235
  processor.init(userId);
6184
6236
  const sessionManager = new SessionManager();
6237
+ let logEmitter = null;
6185
6238
  const browserRef = getBrowser();
6186
6239
  const shutdown = async () => {
6187
6240
  console.log();
6188
6241
  log.info("Shutting down...");
6242
+ setLogHook(null);
6243
+ try {
6244
+ if (logEmitter) await logEmitter.stop();
6245
+ } catch {
6246
+ }
6189
6247
  try {
6190
6248
  if (browserRef.isConnected()) await browserRef.disconnect();
6191
6249
  } catch {
@@ -6200,6 +6258,10 @@ async function runAgent(opts) {
6200
6258
  await processor.processTask(task);
6201
6259
  });
6202
6260
  processor.setSessionId(session.id);
6261
+ logEmitter = new SessionLogEmitter(session.id);
6262
+ setLogHook((logType, message) => {
6263
+ logEmitter?.push(logType, message);
6264
+ });
6203
6265
  log.info("Listening for tasks (chat + jobs) from web UI...");
6204
6266
  log.info("Press Ctrl+C to stop.\n");
6205
6267
  const rl = createInterface2({
@@ -6605,7 +6667,7 @@ function registerJobCommands(program2) {
6605
6667
  jobCmd.command("list").description("List your defined jobs").action(async () => {
6606
6668
  try {
6607
6669
  const userId = await getCurrentUserId();
6608
- const { JobRunner: JobRunner2 } = await import("./job-runner-CJ7HM4GZ.js");
6670
+ const { JobRunner: JobRunner2 } = await import("./job-runner-AT3V6LAQ.js");
6609
6671
  const runner = new JobRunner2();
6610
6672
  const jobs = await runner.listJobs();
6611
6673
  if (jobs.length === 0) {
@@ -6629,7 +6691,7 @@ function registerJobCommands(program2) {
6629
6691
  jobCmd.command("status [name]").description("Show run history for a job (or all jobs)").option("-l, --limit <number>", "Max runs to show (default: 5)").action(async (name, opts) => {
6630
6692
  try {
6631
6693
  const userId = await getCurrentUserId();
6632
- const { JobRunner: JobRunner2 } = await import("./job-runner-CJ7HM4GZ.js");
6694
+ const { JobRunner: JobRunner2 } = await import("./job-runner-AT3V6LAQ.js");
6633
6695
  const runner = new JobRunner2();
6634
6696
  const runs = await runner.getRunHistory(name, parseInt(opts.limit || "5"));
6635
6697
  if (runs.length === 0) {
@@ -6668,7 +6730,7 @@ Job Run History${name ? ` \u2014 ${name}` : ""}:`));
6668
6730
  process.exit(1);
6669
6731
  }
6670
6732
  const userId = await getCurrentUserId();
6671
- const { JobRunner: JobRunner2 } = await import("./job-runner-CJ7HM4GZ.js");
6733
+ const { JobRunner: JobRunner2 } = await import("./job-runner-AT3V6LAQ.js");
6672
6734
  const runner = new JobRunner2();
6673
6735
  const job = await runner.loadJob(name);
6674
6736
  if (!job) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  JobRunner
3
- } from "./chunk-4SBIN27G.js";
3
+ } from "./chunk-KAS2PTOX.js";
4
4
  import "./chunk-JVA6DHXD.js";
5
5
  export {
6
6
  JobRunner
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assistme",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "AssistMe CLI Agent - AI-powered assistant that controls your real browser",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -4,10 +4,11 @@ import ora from "ora";
4
4
  import { createInterface } from "readline";
5
5
  import { getCurrentUserId } from "../db/supabase.js";
6
6
  import { setConfig } from "../utils/config.js";
7
- import { log, setLogLevel } from "../utils/logger.js";
7
+ import { log, setLogLevel, setLogHook } from "../utils/logger.js";
8
8
  import { SessionManager } from "../agent/session.js";
9
9
  import { TaskProcessor } from "../agent/processor.js";
10
10
  import { getBrowser, ensureBrowserAvailable } from "../tools/browser.js";
11
+ import { SessionLogEmitter } from "../db/session-log.js";
11
12
 
12
13
  export function registerStartCommand(program: Command): void {
13
14
  program
@@ -85,12 +86,19 @@ async function runAgent(opts: { workspace?: string; name?: string; verbose?: boo
85
86
  const processor = new TaskProcessor();
86
87
  processor.init(userId);
87
88
  const sessionManager = new SessionManager();
89
+ let logEmitter: SessionLogEmitter | null = null;
88
90
 
89
91
  // Graceful shutdown
90
92
  const browserRef = getBrowser();
91
93
  const shutdown = async () => {
92
94
  console.log();
93
95
  log.info("Shutting down...");
96
+ setLogHook(null);
97
+ try {
98
+ if (logEmitter) await logEmitter.stop();
99
+ } catch {
100
+ /* ignore */
101
+ }
94
102
  try {
95
103
  if (browserRef.isConnected()) await browserRef.disconnect();
96
104
  } catch {
@@ -109,6 +117,12 @@ async function runAgent(opts: { workspace?: string; name?: string; verbose?: boo
109
117
  });
110
118
  processor.setSessionId(session.id);
111
119
 
120
+ // Start persisting logs to Supabase
121
+ logEmitter = new SessionLogEmitter(session.id);
122
+ setLogHook((logType, message) => {
123
+ logEmitter?.push(logType, message);
124
+ });
125
+
112
126
  log.info("Listening for tasks (chat + jobs) from web UI...");
113
127
  log.info("Press Ctrl+C to stop.\n");
114
128
 
@@ -0,0 +1,71 @@
1
+ import { callMcpHandler } from "./api-client.js";
2
+ import { log } from "../utils/logger.js";
3
+
4
+ const FLUSH_INTERVAL_MS = 3_000;
5
+ const MAX_BATCH_SIZE = 100;
6
+
7
+ interface PendingLog {
8
+ log_type: "stdout" | "stderr" | "status";
9
+ message: string;
10
+ seq: number;
11
+ }
12
+
13
+ /**
14
+ * Batches agent process logs and flushes them to Supabase periodically.
15
+ * Each session gets its own emitter with an auto-incrementing sequence.
16
+ */
17
+ export class SessionLogEmitter {
18
+ private sequence = 0;
19
+ private buffer: PendingLog[] = [];
20
+ private flushTimer: ReturnType<typeof setInterval> | null = null;
21
+ private flushing = false;
22
+
23
+ constructor(private sessionId: string) {
24
+ this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
25
+ }
26
+
27
+ /** Queue a log entry for batch insertion */
28
+ push(logType: "stdout" | "stderr" | "status", message: string): void {
29
+ this.sequence++;
30
+ this.buffer.push({ log_type: logType, message, seq: this.sequence });
31
+
32
+ // Flush immediately if buffer is large
33
+ if (this.buffer.length >= MAX_BATCH_SIZE) {
34
+ this.flush();
35
+ }
36
+ }
37
+
38
+ /** Flush buffered logs to Supabase */
39
+ async flush(): Promise<void> {
40
+ if (this.flushing || this.buffer.length === 0) return;
41
+
42
+ const batch = this.buffer.splice(0);
43
+ this.flushing = true;
44
+
45
+ try {
46
+ await callMcpHandler("log.emit_batch", {
47
+ session_id: this.sessionId,
48
+ logs: batch,
49
+ });
50
+ } catch (err) {
51
+ log.debug(
52
+ `Failed to flush session logs: ${err instanceof Error ? err.message : err}`
53
+ );
54
+ // Re-queue failed batch at the front (best-effort, drop if too old)
55
+ if (this.buffer.length < MAX_BATCH_SIZE * 5) {
56
+ this.buffer.unshift(...batch);
57
+ }
58
+ } finally {
59
+ this.flushing = false;
60
+ }
61
+ }
62
+
63
+ /** Stop the emitter and flush remaining logs */
64
+ async stop(): Promise<void> {
65
+ if (this.flushTimer) {
66
+ clearInterval(this.flushTimer);
67
+ this.flushTimer = null;
68
+ }
69
+ await this.flush();
70
+ }
71
+ }
@@ -2,9 +2,11 @@ import chalk from "chalk";
2
2
  import { randomUUID } from "crypto";
3
3
 
4
4
  export type LogLevel = "debug" | "info" | "warn" | "error";
5
+ export type LogHook = (logType: "stdout" | "stderr", message: string) => void;
5
6
 
6
7
  let currentLevel: LogLevel = "info";
7
8
  let currentCorrelationId: string | null = null;
9
+ let logHook: LogHook | null = null;
8
10
 
9
11
  const LEVEL_ORDER: Record<LogLevel, number> = {
10
12
  debug: 0,
@@ -17,6 +19,14 @@ export function setLogLevel(level: LogLevel) {
17
19
  currentLevel = level;
18
20
  }
19
21
 
22
+ /**
23
+ * Set a hook to capture all log output for persistence.
24
+ * Call with null to remove.
25
+ */
26
+ export function setLogHook(hook: LogHook | null) {
27
+ logHook = hook;
28
+ }
29
+
20
30
  /**
21
31
  * Set a correlation ID that will be included in all subsequent log messages.
22
32
  * Call with null to clear.
@@ -54,36 +64,54 @@ function prefix(): string {
54
64
  export const log = {
55
65
  debug(msg: string, ...args: unknown[]) {
56
66
  if (shouldLog("debug")) {
67
+ const text = formatArgs(msg, args);
68
+ logHook?.("stdout", `[DEBUG] ${text}`);
57
69
  console.log(chalk.gray(`[${prefix()}] DEBUG`), msg, ...args);
58
70
  }
59
71
  },
60
72
  info(msg: string, ...args: unknown[]) {
61
73
  if (shouldLog("info")) {
74
+ const text = formatArgs(msg, args);
75
+ logHook?.("stdout", text);
62
76
  console.log(chalk.blue(`[${prefix()}]`), msg, ...args);
63
77
  }
64
78
  },
65
79
  success(msg: string, ...args: unknown[]) {
66
80
  if (shouldLog("info")) {
81
+ const text = formatArgs(msg, args);
82
+ logHook?.("stdout", `✓ ${text}`);
67
83
  console.log(chalk.green(`[${prefix()}] ✓`), msg, ...args);
68
84
  }
69
85
  },
70
86
  warn(msg: string, ...args: unknown[]) {
71
87
  if (shouldLog("warn")) {
88
+ const text = formatArgs(msg, args);
89
+ logHook?.("stderr", `[WARN] ${text}`);
72
90
  console.log(chalk.yellow(`[${prefix()}] WARN`), msg, ...args);
73
91
  }
74
92
  },
75
93
  error(msg: string, ...args: unknown[]) {
76
94
  if (shouldLog("error")) {
95
+ const text = formatArgs(msg, args);
96
+ logHook?.("stderr", `[ERROR] ${text}`);
77
97
  console.error(chalk.red(`[${prefix()}] ERROR`), msg, ...args);
78
98
  }
79
99
  },
80
100
  agent(msg: string) {
101
+ logHook?.("stdout", `▸ ${msg}`);
81
102
  console.log(chalk.cyan(" ▸"), msg);
82
103
  },
83
104
  tool(name: string, msg: string) {
105
+ logHook?.("stdout", `⚡ ${name}: ${msg}`);
84
106
  console.log(chalk.magenta(` ⚡ ${name}:`), msg);
85
107
  },
86
108
  result(msg: string) {
109
+ logHook?.("stdout", `← ${msg}`);
87
110
  console.log(chalk.green(" ←"), msg);
88
111
  },
89
112
  };
113
+
114
+ function formatArgs(msg: string, args: unknown[]): string {
115
+ if (args.length === 0) return msg;
116
+ return `${msg} ${args.map((a) => (typeof a === "string" ? a : JSON.stringify(a))).join(" ")}`;
117
+ }