agent-tail-core 0.3.9 → 0.4.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 +3 -0
- package/dist/cli.mjs +60 -4
- package/dist/{commands-Bw4eTFz0.mjs → commands-Bv44Rf77.mjs} +78 -10
- package/dist/index.d.mts +4 -0
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,6 +14,8 @@ Each service gets its own log file (`fe.log`, `api.log`) plus a `combined.log` w
|
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
tail -f tmp/logs/latest/combined.log
|
|
17
|
+
# or
|
|
18
|
+
agent-tail tail combined -f
|
|
17
19
|
```
|
|
18
20
|
|
|
19
21
|
## CLI commands
|
|
@@ -21,6 +23,7 @@ tail -f tmp/logs/latest/combined.log
|
|
|
21
23
|
- **`agent-tail run`** — spawn services with unified logging (recommended)
|
|
22
24
|
- **`agent-tail init`** — create a session directory
|
|
23
25
|
- **`agent-tail wrap`** — pipe a single command into an existing session
|
|
26
|
+
- **`agent-tail tail`** — resolve the latest session, then forward to system `tail`
|
|
24
27
|
|
|
25
28
|
## Options
|
|
26
29
|
|
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as cmd_run, r as
|
|
2
|
+
import { i as cmd_wrap, n as cmd_run, r as cmd_tail, t as cmd_init } from "./commands-Bv44Rf77.mjs";
|
|
3
3
|
import { parseArgs } from "node:util";
|
|
4
4
|
|
|
5
5
|
//#region src/cli.ts
|
|
@@ -10,8 +10,9 @@ const HELP = `
|
|
|
10
10
|
agent-tail init Create a new log session
|
|
11
11
|
agent-tail wrap <name> -- <command...> Wrap a command, pipe output to <name>.log
|
|
12
12
|
agent-tail run <config...> Run multiple services concurrently
|
|
13
|
+
agent-tail tail [query] [--] [tail args...] Tail the latest session's logs via system tail
|
|
13
14
|
|
|
14
|
-
\x1b[
|
|
15
|
+
\x1b[1mShared Options:\x1b[0m
|
|
15
16
|
--log-dir <dir> Log directory relative to cwd (default: tmp/logs)
|
|
16
17
|
--max-sessions <n> Max sessions to keep (default: 10)
|
|
17
18
|
--no-combined Don't write to combined.log
|
|
@@ -19,11 +20,18 @@ const HELP = `
|
|
|
19
20
|
--mute <name> Mute a service from terminal and combined.log (repeatable, still logs to <name>.log)
|
|
20
21
|
-h, --help Show this help
|
|
21
22
|
|
|
23
|
+
\x1b[1mTail:\x1b[0m
|
|
24
|
+
agent-tail tail Tail every .log file in the latest session
|
|
25
|
+
agent-tail tail <log> Tail a specific log by exact or partial name
|
|
26
|
+
tail args Forwarded directly to system tail (for example: -f, -n 50)
|
|
27
|
+
|
|
22
28
|
\x1b[1mExamples:\x1b[0m
|
|
23
29
|
agent-tail init
|
|
24
30
|
agent-tail wrap api -- uv run fastapi-server
|
|
25
31
|
agent-tail wrap worker -- python -m celery worker
|
|
26
32
|
agent-tail run "fe: npm run dev" "api: uv run server" "worker: uv run worker"
|
|
33
|
+
agent-tail tail -f
|
|
34
|
+
agent-tail tail browser -n 50
|
|
27
35
|
`;
|
|
28
36
|
function parse_cli_options(args) {
|
|
29
37
|
const dash_index = args.indexOf("--");
|
|
@@ -77,6 +85,45 @@ function parse_cli_options(args) {
|
|
|
77
85
|
rest
|
|
78
86
|
};
|
|
79
87
|
}
|
|
88
|
+
function parse_tail_options(args) {
|
|
89
|
+
let log_dir = "tmp/logs";
|
|
90
|
+
let query;
|
|
91
|
+
let i = 0;
|
|
92
|
+
while (i < args.length) {
|
|
93
|
+
const arg = args[i];
|
|
94
|
+
if (arg === "--") {
|
|
95
|
+
i += 1;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
if (!arg.startsWith("-") || arg === "-") break;
|
|
99
|
+
switch (arg) {
|
|
100
|
+
case "--log-dir":
|
|
101
|
+
log_dir = args[i + 1] ?? "";
|
|
102
|
+
if (!log_dir) throw new Error(`Missing value for ${arg}`);
|
|
103
|
+
i += 2;
|
|
104
|
+
continue;
|
|
105
|
+
case "--help":
|
|
106
|
+
case "-h":
|
|
107
|
+
console.log(HELP);
|
|
108
|
+
process.exit(0);
|
|
109
|
+
default: return {
|
|
110
|
+
log_dir,
|
|
111
|
+
query,
|
|
112
|
+
tail_args: args.slice(i)
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (args[i] && !args[i].startsWith("-")) {
|
|
117
|
+
query = args[i];
|
|
118
|
+
i += 1;
|
|
119
|
+
}
|
|
120
|
+
if (args[i] === "--") i += 1;
|
|
121
|
+
return {
|
|
122
|
+
log_dir,
|
|
123
|
+
query,
|
|
124
|
+
tail_args: args.slice(i)
|
|
125
|
+
};
|
|
126
|
+
}
|
|
80
127
|
async function main() {
|
|
81
128
|
const args = process.argv.slice(2);
|
|
82
129
|
if (args.length === 0 || args[0] === "-h" || args[0] === "--help") {
|
|
@@ -84,24 +131,33 @@ async function main() {
|
|
|
84
131
|
process.exit(0);
|
|
85
132
|
}
|
|
86
133
|
const subcommand = args[0];
|
|
87
|
-
const
|
|
134
|
+
const sub_args = args.slice(1);
|
|
88
135
|
const project_root = process.cwd();
|
|
89
136
|
try {
|
|
90
137
|
switch (subcommand) {
|
|
91
138
|
case "init": {
|
|
139
|
+
const { options } = parse_cli_options(sub_args);
|
|
92
140
|
const session_dir = cmd_init(project_root, options);
|
|
93
141
|
console.log(session_dir);
|
|
94
142
|
break;
|
|
95
143
|
}
|
|
96
144
|
case "wrap": {
|
|
145
|
+
const { options, positionals, rest } = parse_cli_options(sub_args);
|
|
97
146
|
const code = await cmd_wrap(project_root, positionals[0], rest, options);
|
|
98
147
|
process.exit(code);
|
|
99
148
|
break;
|
|
100
149
|
}
|
|
101
|
-
case "run":
|
|
150
|
+
case "run": {
|
|
151
|
+
const { options, positionals } = parse_cli_options(sub_args);
|
|
102
152
|
await cmd_run(project_root, positionals, options);
|
|
103
153
|
process.exit(0);
|
|
104
154
|
break;
|
|
155
|
+
}
|
|
156
|
+
case "tail": {
|
|
157
|
+
const code = await cmd_tail(project_root, parse_tail_options(sub_args));
|
|
158
|
+
process.exit(code);
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
105
161
|
default:
|
|
106
162
|
console.error(`\x1b[36m[agent-tail]\x1b[0m Unknown command: ${subcommand}`);
|
|
107
163
|
console.log(HELP);
|
|
@@ -86,6 +86,15 @@ var LogManager = class {
|
|
|
86
86
|
* points to a valid directory, return it. Otherwise create a new session.
|
|
87
87
|
*/
|
|
88
88
|
resolve_session(project_root) {
|
|
89
|
+
const existing = this.find_session(project_root);
|
|
90
|
+
if (existing) return existing;
|
|
91
|
+
const log_path = this.initialize(project_root);
|
|
92
|
+
return path.dirname(log_path);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Find the current session directory if one already exists.
|
|
96
|
+
*/
|
|
97
|
+
find_session(project_root) {
|
|
89
98
|
const log_dir = path.resolve(project_root, this.options.logDir);
|
|
90
99
|
const latest_link = path.join(log_dir, "latest");
|
|
91
100
|
try {
|
|
@@ -93,11 +102,10 @@ var LogManager = class {
|
|
|
93
102
|
let target;
|
|
94
103
|
if (stat.isSymbolicLink()) target = fs.realpathSync(latest_link);
|
|
95
104
|
else if (stat.isFile()) target = fs.readFileSync(latest_link, "utf-8").trim();
|
|
96
|
-
else
|
|
105
|
+
else return null;
|
|
97
106
|
if (fs.existsSync(target) && fs.statSync(target).isDirectory()) return target;
|
|
98
107
|
} catch {}
|
|
99
|
-
|
|
100
|
-
return path.dirname(log_path);
|
|
108
|
+
return null;
|
|
101
109
|
}
|
|
102
110
|
check_gitignore(project_root) {
|
|
103
111
|
const gitignore_path = path.join(project_root, ".gitignore");
|
|
@@ -178,6 +186,42 @@ function cmd_init(project_root, options = DEFAULT_CLI_OPTIONS) {
|
|
|
178
186
|
function resolve_session_dir(project_root, options = DEFAULT_CLI_OPTIONS) {
|
|
179
187
|
return create_manager(options).resolve_session(project_root);
|
|
180
188
|
}
|
|
189
|
+
function find_session_dir(project_root, log_dir = DEFAULT_CLI_OPTIONS.log_dir) {
|
|
190
|
+
return create_manager({
|
|
191
|
+
...DEFAULT_CLI_OPTIONS,
|
|
192
|
+
log_dir
|
|
193
|
+
}).find_session(project_root);
|
|
194
|
+
}
|
|
195
|
+
function forward_signals_to_children(children) {
|
|
196
|
+
const signal_handlers = ["SIGINT", "SIGTERM"].map((signal) => {
|
|
197
|
+
const handler = () => {
|
|
198
|
+
for (const child of children) child.kill(signal);
|
|
199
|
+
};
|
|
200
|
+
process.on(signal, handler);
|
|
201
|
+
return [signal, handler];
|
|
202
|
+
});
|
|
203
|
+
return () => {
|
|
204
|
+
for (const [signal, handler] of signal_handlers) process.off(signal, handler);
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function resolve_tail_paths(project_root, options) {
|
|
208
|
+
const session_dir = find_session_dir(project_root, options.log_dir);
|
|
209
|
+
const resolved_log_dir = path.resolve(project_root, options.log_dir);
|
|
210
|
+
if (!session_dir) throw new Error(`No log session found in ${resolved_log_dir}. Run "agent-tail run", "agent-tail wrap", or start a framework plugin first.`);
|
|
211
|
+
const log_files = fs.readdirSync(session_dir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".log")).map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
|
|
212
|
+
if (log_files.length === 0) throw new Error(`No .log files found in ${session_dir}.`);
|
|
213
|
+
if (!options.query) return log_files.map((file_name) => path.join(session_dir, file_name));
|
|
214
|
+
const normalized_query = options.query.toLowerCase();
|
|
215
|
+
const exact_names = new Set([normalized_query, normalized_query.endsWith(".log") ? normalized_query : `${normalized_query}.log`]);
|
|
216
|
+
const exact_matches = log_files.filter((file_name) => exact_names.has(file_name.toLowerCase()));
|
|
217
|
+
if (exact_matches.length > 0) return exact_matches.map((file_name) => path.join(session_dir, file_name));
|
|
218
|
+
const partial_matches = log_files.filter((file_name) => {
|
|
219
|
+
const normalized_name = file_name.toLowerCase();
|
|
220
|
+
return normalized_name.includes(normalized_query) || path.basename(normalized_name, ".log").includes(normalized_query);
|
|
221
|
+
});
|
|
222
|
+
if (partial_matches.length > 0) return partial_matches.map((file_name) => path.join(session_dir, file_name));
|
|
223
|
+
throw new Error(`No logs found for "${options.query}" in ${session_dir}. Available logs: ${log_files.join(", ")}`);
|
|
224
|
+
}
|
|
181
225
|
function parse_service_configs(args) {
|
|
182
226
|
return args.map((arg) => {
|
|
183
227
|
const colon_index = arg.indexOf(":");
|
|
@@ -189,6 +233,28 @@ function parse_service_configs(args) {
|
|
|
189
233
|
});
|
|
190
234
|
}
|
|
191
235
|
/**
|
|
236
|
+
* Tail the latest session's logs by forwarding to the system tail command.
|
|
237
|
+
*/
|
|
238
|
+
function cmd_tail(project_root, options) {
|
|
239
|
+
const log_paths = resolve_tail_paths(project_root, options);
|
|
240
|
+
const child = spawn("tail", [...options.tail_args, ...log_paths], { stdio: "inherit" });
|
|
241
|
+
const cleanup_signal_handlers = forward_signals_to_children([child]);
|
|
242
|
+
return new Promise((resolve, reject) => {
|
|
243
|
+
child.on("close", (code) => {
|
|
244
|
+
cleanup_signal_handlers();
|
|
245
|
+
resolve(code ?? 0);
|
|
246
|
+
});
|
|
247
|
+
child.on("error", (err) => {
|
|
248
|
+
cleanup_signal_handlers();
|
|
249
|
+
if (err.code === "ENOENT") {
|
|
250
|
+
reject(/* @__PURE__ */ new Error("System \"tail\" command not found. \"agent-tail tail\" requires a POSIX-style tail binary."));
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
reject(err);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
192
258
|
* Write data to a log stream and optionally to combined.log with a prefix.
|
|
193
259
|
*/
|
|
194
260
|
function write_to_logs(chunk, name, log_stream, combined_stream, excludes = []) {
|
|
@@ -238,20 +304,20 @@ function cmd_wrap(project_root, name, command, options = DEFAULT_CLI_OPTIONS) {
|
|
|
238
304
|
process.stderr.write(chunk);
|
|
239
305
|
write_to_logs(chunk, name, log_stream, combined_stream, options.excludes);
|
|
240
306
|
});
|
|
307
|
+
const cleanup_signal_handlers = forward_signals_to_children([child]);
|
|
241
308
|
return new Promise((resolve, reject) => {
|
|
242
309
|
child.on("close", (code) => {
|
|
310
|
+
cleanup_signal_handlers();
|
|
243
311
|
log_stream.end();
|
|
244
312
|
combined_stream?.end();
|
|
245
313
|
resolve(code ?? 0);
|
|
246
314
|
});
|
|
247
315
|
child.on("error", (err) => {
|
|
316
|
+
cleanup_signal_handlers();
|
|
248
317
|
log_stream.end();
|
|
249
318
|
combined_stream?.end();
|
|
250
319
|
reject(err);
|
|
251
320
|
});
|
|
252
|
-
for (const signal of ["SIGINT", "SIGTERM"]) process.on(signal, () => {
|
|
253
|
-
child.kill(signal);
|
|
254
|
-
});
|
|
255
321
|
});
|
|
256
322
|
}
|
|
257
323
|
const COLORS = [
|
|
@@ -332,13 +398,15 @@ function cmd_run(project_root, service_args, options = DEFAULT_CLI_OPTIONS) {
|
|
|
332
398
|
});
|
|
333
399
|
});
|
|
334
400
|
});
|
|
335
|
-
|
|
336
|
-
for (const child of children) child.kill(signal);
|
|
337
|
-
});
|
|
401
|
+
const cleanup_signal_handlers = forward_signals_to_children(children);
|
|
338
402
|
return Promise.all(promises).then(() => {
|
|
403
|
+
cleanup_signal_handlers();
|
|
339
404
|
combined_stream?.end();
|
|
405
|
+
}, (error) => {
|
|
406
|
+
cleanup_signal_handlers();
|
|
407
|
+
throw error;
|
|
340
408
|
});
|
|
341
409
|
}
|
|
342
410
|
|
|
343
411
|
//#endregion
|
|
344
|
-
export {
|
|
412
|
+
export { parse_service_configs as a, resolve_options as c, should_exclude as d, cmd_wrap as i, LogManager as l, cmd_run as n, resolve_session_dir as o, cmd_tail as r, DEFAULT_OPTIONS as s, cmd_init as t, SESSION_ENV_VAR as u };
|
package/dist/index.d.mts
CHANGED
|
@@ -65,6 +65,10 @@ declare class LogManager {
|
|
|
65
65
|
* points to a valid directory, return it. Otherwise create a new session.
|
|
66
66
|
*/
|
|
67
67
|
resolve_session(project_root: string): string;
|
|
68
|
+
/**
|
|
69
|
+
* Find the current session directory if one already exists.
|
|
70
|
+
*/
|
|
71
|
+
find_session(project_root: string): string | null;
|
|
68
72
|
check_gitignore(project_root: string): void;
|
|
69
73
|
}
|
|
70
74
|
//#endregion
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as parse_service_configs, c as resolve_options, d as should_exclude, i as cmd_wrap, l as LogManager, n as cmd_run, o as resolve_session_dir, s as DEFAULT_OPTIONS, t as cmd_init, u as SESSION_ENV_VAR } from "./commands-Bv44Rf77.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/formatter.ts
|
|
4
4
|
function format_log_line(entry) {
|