agent-tail-core 0.3.0 → 0.3.2

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 CHANGED
@@ -2,10 +2,12 @@
2
2
 
3
3
  CLI and shared core for [agent-tail](https://agent-tail.vercel.app/) — pipes server output and browser console logs to log files your AI coding agents can read and `grep`.
4
4
 
5
+ > **Tip:** Install the umbrella [`agent-tail`](https://www.npmjs.com/package/agent-tail) package to get the CLI, Vite plugin, and Next.js plugin in one install: `npm install -D agent-tail`
6
+
5
7
  ## Quick start
6
8
 
7
9
  ```bash
8
- npx agent-tail-core run 'fe: npm run dev' 'api: uvicorn main:app'
10
+ npx agent-tail run 'fe: npm run dev' 'api: uvicorn main:app'
9
11
  ```
10
12
 
11
13
  Each service gets its own log file (`fe.log`, `api.log`) plus a `combined.log` with all output interleaved. A `latest` symlink always points to the current session:
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as cmd_run, r as cmd_wrap, t as cmd_init } from "./commands-CzU-rrPM.mjs";
2
+ import { n as cmd_run, r as cmd_wrap, t as cmd_init } from "./commands-BGQ-_7t4.mjs";
3
3
  import { parseArgs } from "node:util";
4
4
 
5
5
  //#region src/cli.ts
@@ -17,6 +17,7 @@ function should_exclude(message, excludes) {
17
17
  //#endregion
18
18
  //#region src/log-manager.ts
19
19
  const PLUGIN_PREFIX = "\x1B[36m[agent-tail]\x1B[0m";
20
+ const SESSION_ENV_VAR = "AGENT_TAIL_SESSION";
20
21
  let session_counter = 0;
21
22
  var LogManager = class {
22
23
  constructor(options) {
@@ -49,7 +50,11 @@ var LogManager = class {
49
50
  fs.unlinkSync(latest_link);
50
51
  } catch {}
51
52
  const relative_target = path.relative(log_dir, session_dir);
52
- fs.symlinkSync(relative_target, latest_link);
53
+ try {
54
+ fs.symlinkSync(relative_target, latest_link);
55
+ } catch {
56
+ fs.writeFileSync(latest_link, session_dir);
57
+ }
53
58
  }
54
59
  prune_sessions(log_dir) {
55
60
  try {
@@ -66,6 +71,17 @@ var LogManager = class {
66
71
  } catch {}
67
72
  }
68
73
  /**
74
+ * Join an existing session directory. Creates the log file if it doesn't
75
+ * exist. Use this when a parent process (e.g. `agent-tail run`) has
76
+ * already created the session and passed its path via AGENT_TAIL_SESSION.
77
+ */
78
+ join_session(session_dir) {
79
+ const log_file = path.join(session_dir, this.options.logFileName);
80
+ if (!fs.existsSync(log_file)) fs.writeFileSync(log_file, "");
81
+ console.log(`${PLUGIN_PREFIX} Writing to ${log_file}`);
82
+ return log_file;
83
+ }
84
+ /**
69
85
  * Resolve the current session directory. If a `latest` symlink exists and
70
86
  * points to a valid directory, return it. Otherwise create a new session.
71
87
  */
@@ -73,8 +89,12 @@ var LogManager = class {
73
89
  const log_dir = path.resolve(project_root, this.options.logDir);
74
90
  const latest_link = path.join(log_dir, "latest");
75
91
  try {
76
- const real = fs.realpathSync(latest_link);
77
- if (fs.statSync(real).isDirectory()) return real;
92
+ const stat = fs.lstatSync(latest_link);
93
+ let target;
94
+ if (stat.isSymbolicLink()) target = fs.realpathSync(latest_link);
95
+ else if (stat.isFile()) target = fs.readFileSync(latest_link, "utf-8").trim();
96
+ else throw new Error("unexpected latest type");
97
+ if (fs.existsSync(target) && fs.statSync(target).isDirectory()) return target;
78
98
  } catch {}
79
99
  const log_path = this.initialize(project_root);
80
100
  return path.dirname(log_path);
@@ -82,8 +102,8 @@ var LogManager = class {
82
102
  check_gitignore(project_root) {
83
103
  const gitignore_path = path.join(project_root, ".gitignore");
84
104
  try {
85
- const lines = fs.readFileSync(gitignore_path, "utf-8").split("\n").map((l) => l.trim());
86
- const log_dir = this.options.logDir;
105
+ const lines = fs.readFileSync(gitignore_path, "utf-8").split(/\r?\n/).map((l) => l.trim());
106
+ const log_dir = this.options.logDir.replace(/\\/g, "/");
87
107
  const parts = log_dir.split("/");
88
108
  let covered = false;
89
109
  for (let i = 1; i <= parts.length; i++) {
@@ -171,7 +191,7 @@ function parse_service_configs(args) {
171
191
  * Write data to a log stream and optionally to combined.log with a prefix.
172
192
  */
173
193
  function write_to_logs(chunk, name, log_stream, combined_stream, excludes = []) {
174
- const lines = chunk.toString().split("\n");
194
+ const lines = chunk.toString().split(/\r?\n/);
175
195
  for (let i = 0; i < lines.length; i++) if (lines[i].length > 0) {
176
196
  if (excludes.length && should_exclude(lines[i], excludes)) continue;
177
197
  log_stream.write(lines[i] + "\n");
@@ -197,13 +217,14 @@ function cmd_wrap(project_root, name, command, options = DEFAULT_CLI_OPTIONS) {
197
217
  combined_stream = fs.createWriteStream(combined_file, { flags: "a" });
198
218
  }
199
219
  console.log(`${PREFIX} ${name} → ${log_file}`);
200
- const child = spawn("sh", ["-c", command.join(" ")], {
220
+ const child = spawn(command.join(" "), {
201
221
  stdio: [
202
222
  "inherit",
203
223
  "pipe",
204
224
  "pipe"
205
225
  ],
206
- env: { ...process.env }
226
+ env: { ...process.env },
227
+ shell: true
207
228
  });
208
229
  child.stdout?.on("data", (chunk) => {
209
230
  process.stdout.write(chunk);
@@ -261,16 +282,20 @@ function cmd_run(project_root, service_args, options = DEFAULT_CLI_OPTIONS) {
261
282
  const tag = `${COLORS[i % COLORS.length]}[${svc.name}]${RESET}`;
262
283
  const log_file = path.join(session_dir, `${svc.name}.log`);
263
284
  const log_stream = fs.createWriteStream(log_file, { flags: "a" });
264
- const child = spawn("sh", ["-c", svc.command], {
285
+ const child = spawn(svc.command, {
265
286
  stdio: [
266
287
  "inherit",
267
288
  "pipe",
268
289
  "pipe"
269
290
  ],
270
- env: { ...process.env }
291
+ env: {
292
+ ...process.env,
293
+ [SESSION_ENV_VAR]: session_dir
294
+ },
295
+ shell: true
271
296
  });
272
297
  function handle(target, chunk) {
273
- const lines = chunk.toString().split("\n");
298
+ const lines = chunk.toString().split(/\r?\n/);
274
299
  for (let j = 0; j < lines.length; j++) if (lines[j].length > 0) {
275
300
  if (options.excludes.length && should_exclude(lines[j], options.excludes)) continue;
276
301
  log_stream.write(lines[j] + "\n");
@@ -307,4 +332,4 @@ function cmd_run(project_root, service_args, options = DEFAULT_CLI_OPTIONS) {
307
332
  }
308
333
 
309
334
  //#endregion
310
- export { resolve_session_dir as a, LogManager as c, parse_service_configs as i, should_exclude as l, cmd_run as n, DEFAULT_OPTIONS as o, cmd_wrap as r, resolve_options as s, cmd_init as t };
335
+ export { resolve_session_dir as a, LogManager as c, parse_service_configs as i, SESSION_ENV_VAR as l, cmd_run as n, DEFAULT_OPTIONS as o, cmd_wrap as r, resolve_options as s, cmd_init as t, should_exclude as u };
package/dist/index.d.mts CHANGED
@@ -46,6 +46,7 @@ declare function should_exclude(message: string, excludes: string[]): boolean;
46
46
  declare function generate_client_script(options: ResolvedOptions): string;
47
47
  //#endregion
48
48
  //#region src/log-manager.d.ts
49
+ declare const SESSION_ENV_VAR = "AGENT_TAIL_SESSION";
49
50
  declare class LogManager {
50
51
  private options;
51
52
  constructor(options: ResolvedOptions);
@@ -53,6 +54,12 @@ declare class LogManager {
53
54
  create_session_name(): string;
54
55
  update_latest_symlink(log_dir: string, session_dir: string): void;
55
56
  prune_sessions(log_dir: string): void;
57
+ /**
58
+ * Join an existing session directory. Creates the log file if it doesn't
59
+ * exist. Use this when a parent process (e.g. `agent-tail run`) has
60
+ * already created the session and passed its path via AGENT_TAIL_SESSION.
61
+ */
62
+ join_session(session_dir: string): string;
56
63
  /**
57
64
  * Resolve the current session directory. If a `latest` symlink exists and
58
65
  * points to a valid directory, return it. Otherwise create a new session.
@@ -92,4 +99,4 @@ declare function cmd_wrap(project_root: string, name: string, command: string[],
92
99
  */
93
100
  declare function cmd_run(project_root: string, service_args: string[], options?: CliOptions): Promise<void>;
94
101
  //#endregion
95
- export { type BrowserLogsOptions, type CliOptions, DEFAULT_OPTIONS, type LogEntry, LogManager, type ResolvedOptions, type ServiceConfig, cmd_init, cmd_run, cmd_wrap, format_log_line, generate_client_script, parse_service_configs, resolve_options, resolve_session_dir, should_exclude };
102
+ export { type BrowserLogsOptions, type CliOptions, DEFAULT_OPTIONS, type LogEntry, LogManager, type ResolvedOptions, SESSION_ENV_VAR, type ServiceConfig, cmd_init, cmd_run, cmd_wrap, format_log_line, generate_client_script, parse_service_configs, resolve_options, resolve_session_dir, should_exclude };
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
- import { a as resolve_session_dir, c as LogManager, i as parse_service_configs, l as should_exclude, n as cmd_run, o as DEFAULT_OPTIONS, r as cmd_wrap, s as resolve_options, t as cmd_init } from "./commands-CzU-rrPM.mjs";
1
+ import { a as resolve_session_dir, c as LogManager, i as parse_service_configs, l as SESSION_ENV_VAR, n as cmd_run, o as DEFAULT_OPTIONS, r as cmd_wrap, s as resolve_options, t as cmd_init, u as should_exclude } from "./commands-BGQ-_7t4.mjs";
2
2
 
3
3
  //#region src/formatter.ts
4
4
  function format_log_line(entry) {
5
- return `[${entry.timestamp}] [${entry.level.toUpperCase().padEnd(7)}] ${entry.args.join(" ")}${entry.url ? ` (${entry.url})` : ""}${entry.stack ? `\n ${entry.stack.split("\n").join("\n ")}` : ""}\n`;
5
+ return `[${entry.timestamp}] [${entry.level.toUpperCase().padEnd(7)}] ${entry.args.join(" ")}${entry.url ? ` (${entry.url})` : ""}${entry.stack ? `\n ${entry.stack.split(/\r?\n/).join("\n ")}` : ""}\n`;
6
6
  }
7
7
 
8
8
  //#endregion
@@ -121,4 +121,4 @@ function generate_client_script(options) {
121
121
  }
122
122
 
123
123
  //#endregion
124
- export { DEFAULT_OPTIONS, LogManager, cmd_init, cmd_run, cmd_wrap, format_log_line, generate_client_script, parse_service_configs, resolve_options, resolve_session_dir, should_exclude };
124
+ export { DEFAULT_OPTIONS, LogManager, SESSION_ENV_VAR, cmd_init, cmd_run, cmd_wrap, format_log_line, generate_client_script, parse_service_configs, resolve_options, resolve_session_dir, should_exclude };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agent-tail-core",
3
3
  "type": "module",
4
- "version": "0.3.0",
4
+ "version": "0.3.2",
5
5
  "description": "Core utilities for agent-tail log capture plugins.",
6
6
  "license": "MIT",
7
7
  "repository": {