@kairyou/agent-tools 0.13.3 → 0.13.5
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/dist/log/hook.mjs
CHANGED
|
@@ -1251,30 +1251,46 @@ async function updateDailyFile(outputFile, day, items) {
|
|
|
1251
1251
|
(item, index) => ` ${index + 1}. ${item.project ? `${item.project}: ` : ""}${item.text}`
|
|
1252
1252
|
);
|
|
1253
1253
|
const block = [`<!-- log:${day}:start -->`, ...lines, `<!-- log:${day}:end -->`];
|
|
1254
|
-
let
|
|
1254
|
+
let raw = "";
|
|
1255
1255
|
try {
|
|
1256
|
-
|
|
1256
|
+
raw = await fs.readFile(outputFile, "utf8");
|
|
1257
1257
|
} catch {
|
|
1258
1258
|
}
|
|
1259
|
+
const hadBom = raw.charCodeAt(0) === 65279;
|
|
1260
|
+
const current = hadBom ? raw.slice(1) : raw;
|
|
1259
1261
|
const eol = current.includes("\r\n") ? "\r\n" : "\n";
|
|
1260
1262
|
const fileLines = current ? current.split(/\r?\n/) : [];
|
|
1261
1263
|
const startMarker = `<!-- log:${day}:start -->`;
|
|
1262
1264
|
const endMarker = `<!-- log:${day}:end -->`;
|
|
1263
|
-
const startIndex = fileLines.
|
|
1264
|
-
const endIndex = fileLines.findIndex(
|
|
1265
|
+
const startIndex = fileLines.findLastIndex((line) => line.trim() === startMarker);
|
|
1266
|
+
const endIndex = fileLines.findIndex(
|
|
1267
|
+
(line, index) => index > startIndex && line.trim() === endMarker
|
|
1268
|
+
);
|
|
1265
1269
|
let nextLines;
|
|
1266
1270
|
if (startIndex !== -1 && endIndex > startIndex) {
|
|
1267
1271
|
nextLines = [...fileLines.slice(0, startIndex), ...block, ...fileLines.slice(endIndex + 1)];
|
|
1268
|
-
} else if (startIndex !== -1 || endIndex !== -1) {
|
|
1269
|
-
console.error(`[agent-tools log] Unpaired markers for ${day} in ${outputFile}; skipped.`);
|
|
1270
|
-
return;
|
|
1271
1272
|
} else {
|
|
1272
1273
|
nextLines = insertDatedBlock(fileLines, day, block);
|
|
1273
1274
|
}
|
|
1274
|
-
const
|
|
1275
|
+
const body = nextLines.join(eol).replace(/(\r?\n)*$/, eol);
|
|
1276
|
+
const text = hadBom ? "\uFEFF" + body : body;
|
|
1275
1277
|
await fs.mkdir(path.dirname(outputFile), { recursive: true });
|
|
1276
1278
|
await writeFileAtomic(outputFile, text);
|
|
1277
1279
|
}
|
|
1280
|
+
var ENTRY_MARKER_RE = /^<!--\s*(?:daily-)?log:\d{4}-\d{2}-\d{2}:(?:start|end)\b/;
|
|
1281
|
+
function datedSectionEnd(fileLines, dateIndex) {
|
|
1282
|
+
let end = dateIndex + 1;
|
|
1283
|
+
for (let i = dateIndex + 1; i < fileLines.length; i += 1) {
|
|
1284
|
+
const line = fileLines[i];
|
|
1285
|
+
if (!line.trim()) continue;
|
|
1286
|
+
if (ENTRY_MARKER_RE.test(line.trim()) || /^\s/.test(line)) {
|
|
1287
|
+
end = i + 1;
|
|
1288
|
+
continue;
|
|
1289
|
+
}
|
|
1290
|
+
break;
|
|
1291
|
+
}
|
|
1292
|
+
return end;
|
|
1293
|
+
}
|
|
1278
1294
|
function insertDatedBlock(fileLines, day, block) {
|
|
1279
1295
|
const dateLineRe = /^\+ (\d{4}-\d{2}-\d{2})\s*$/;
|
|
1280
1296
|
const dates = [];
|
|
@@ -1284,22 +1300,14 @@ function insertDatedBlock(fileLines, day, block) {
|
|
|
1284
1300
|
}
|
|
1285
1301
|
const existing = dates.find((entry) => entry.date === day);
|
|
1286
1302
|
if (existing) {
|
|
1287
|
-
const
|
|
1288
|
-
let insertAt2 = fileLines.length;
|
|
1289
|
-
for (let i = existing.index + 1; i < fileLines.length; i += 1) {
|
|
1290
|
-
if (boundaryRe.test(fileLines[i])) {
|
|
1291
|
-
insertAt2 = i;
|
|
1292
|
-
break;
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
while (insertAt2 - 1 > existing.index && fileLines[insertAt2 - 1].trim() === "") insertAt2 -= 1;
|
|
1303
|
+
const insertAt2 = datedSectionEnd(fileLines, existing.index);
|
|
1296
1304
|
const trailing = fileLines.slice(insertAt2);
|
|
1297
1305
|
const inserted = [...block];
|
|
1298
1306
|
if (trailing.length > 0 && trailing[0].trim() !== "") inserted.push("");
|
|
1299
1307
|
return [...fileLines.slice(0, insertAt2), ...inserted, ...trailing];
|
|
1300
1308
|
}
|
|
1301
1309
|
const ascending = dates.length < 2 || dates[0].date <= dates[dates.length - 1].date;
|
|
1302
|
-
let insertAt = fileLines.length;
|
|
1310
|
+
let insertAt = dates.length > 0 ? datedSectionEnd(fileLines, dates[dates.length - 1].index) : fileLines.length;
|
|
1303
1311
|
for (const entry of dates) {
|
|
1304
1312
|
if (ascending ? entry.date > day : entry.date < day) {
|
|
1305
1313
|
insertAt = entry.index;
|
|
@@ -520,36 +520,63 @@ async function updateDailyFile(outputFile, day, items) {
|
|
|
520
520
|
);
|
|
521
521
|
const block = [`<!-- log:${day}:start -->`, ...lines, `<!-- log:${day}:end -->`];
|
|
522
522
|
|
|
523
|
-
let
|
|
523
|
+
let raw = "";
|
|
524
524
|
try {
|
|
525
|
-
|
|
525
|
+
raw = await fs.readFile(outputFile, "utf8");
|
|
526
526
|
} catch {
|
|
527
527
|
// First write creates the file.
|
|
528
528
|
}
|
|
529
|
+
// Windows editors and PowerShell write a BOM; strip it for parsing and put
|
|
530
|
+
// it back on write so the file keeps the encoding its author chose.
|
|
531
|
+
const hadBom = raw.charCodeAt(0) === 0xfeff;
|
|
532
|
+
const current = hadBom ? raw.slice(1) : raw;
|
|
529
533
|
const eol = current.includes("\r\n") ? "\r\n" : "\n";
|
|
530
534
|
const fileLines = current ? current.split(/\r?\n/) : [];
|
|
531
535
|
|
|
532
536
|
const startMarker = `<!-- log:${day}:start -->`;
|
|
533
537
|
const endMarker = `<!-- log:${day}:end -->`;
|
|
534
|
-
|
|
535
|
-
|
|
538
|
+
// Pair with the LAST start: an earlier damaged start must not swallow
|
|
539
|
+
// whatever the user wrote between it and our block.
|
|
540
|
+
const startIndex = fileLines.findLastIndex((line) => line.trim() === startMarker);
|
|
541
|
+
const endIndex = fileLines.findIndex(
|
|
542
|
+
(line, index) => index > startIndex && line.trim() === endMarker
|
|
543
|
+
);
|
|
536
544
|
|
|
537
545
|
let nextLines;
|
|
538
546
|
if (startIndex !== -1 && endIndex > startIndex) {
|
|
539
547
|
nextLines = [...fileLines.slice(0, startIndex), ...block, ...fileLines.slice(endIndex + 1)];
|
|
540
|
-
} else if (startIndex !== -1 || endIndex !== -1) {
|
|
541
|
-
// Unpaired markers: refuse to guess an edit range in an unattended run.
|
|
542
|
-
console.error(`[agent-tools log] Unpaired markers for ${day} in ${outputFile}; skipped.`);
|
|
543
|
-
return;
|
|
544
548
|
} else {
|
|
549
|
+
// Also covers an unpaired marker: never guess its range, just add an
|
|
550
|
+
// intact block. Recording must not stop because one block got damaged;
|
|
551
|
+
// the stray marker stays visible for the user to clean up.
|
|
545
552
|
nextLines = insertDatedBlock(fileLines, day, block);
|
|
546
553
|
}
|
|
547
554
|
|
|
548
|
-
const
|
|
555
|
+
const body = nextLines.join(eol).replace(/(\r?\n)*$/, eol);
|
|
556
|
+
const text = hadBom ? "" + body : body;
|
|
549
557
|
await fs.mkdir(path.dirname(outputFile), { recursive: true });
|
|
550
558
|
await writeFileAtomic(outputFile, text);
|
|
551
559
|
}
|
|
552
560
|
|
|
561
|
+
// Where a dated entry stops: markers and indented items belong to it, anything
|
|
562
|
+
// else below (headings, comment blocks, separators, todo lists) does not. Both
|
|
563
|
+
// marker namespaces count, since at-daily-log may share this file.
|
|
564
|
+
const ENTRY_MARKER_RE = /^<!--\s*(?:daily-)?log:\d{4}-\d{2}-\d{2}:(?:start|end)\b/;
|
|
565
|
+
|
|
566
|
+
function datedSectionEnd(fileLines, dateIndex) {
|
|
567
|
+
let end = dateIndex + 1;
|
|
568
|
+
for (let i = dateIndex + 1; i < fileLines.length; i += 1) {
|
|
569
|
+
const line = fileLines[i];
|
|
570
|
+
if (!line.trim()) continue;
|
|
571
|
+
if (ENTRY_MARKER_RE.test(line.trim()) || /^\s/.test(line)) {
|
|
572
|
+
end = i + 1;
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
return end;
|
|
578
|
+
}
|
|
579
|
+
|
|
553
580
|
function insertDatedBlock(fileLines, day, block) {
|
|
554
581
|
const dateLineRe = /^\+ (\d{4}-\d{2}-\d{2})\s*$/;
|
|
555
582
|
const dates = [];
|
|
@@ -560,18 +587,10 @@ function insertDatedBlock(fileLines, day, block) {
|
|
|
560
587
|
|
|
561
588
|
const existing = dates.find((entry) => entry.date === day);
|
|
562
589
|
if (existing) {
|
|
563
|
-
//
|
|
564
|
-
//
|
|
565
|
-
//
|
|
566
|
-
const
|
|
567
|
-
let insertAt = fileLines.length;
|
|
568
|
-
for (let i = existing.index + 1; i < fileLines.length; i += 1) {
|
|
569
|
-
if (boundaryRe.test(fileLines[i])) {
|
|
570
|
-
insertAt = i;
|
|
571
|
-
break;
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
while (insertAt - 1 > existing.index && fileLines[insertAt - 1].trim() === "") insertAt -= 1;
|
|
590
|
+
// The date exists but carries no block of ours yet (user-written, or only
|
|
591
|
+
// at-daily-log's). Append below that date's own lines, never past the
|
|
592
|
+
// notes and todo lists that live further down the file.
|
|
593
|
+
const insertAt = datedSectionEnd(fileLines, existing.index);
|
|
575
594
|
const trailing = fileLines.slice(insertAt);
|
|
576
595
|
const inserted = [...block];
|
|
577
596
|
if (trailing.length > 0 && trailing[0].trim() !== "") inserted.push("");
|
|
@@ -579,8 +598,12 @@ function insertDatedBlock(fileLines, day, block) {
|
|
|
579
598
|
}
|
|
580
599
|
|
|
581
600
|
// Insert a new date at its date-order position; ascending when ambiguous.
|
|
601
|
+
// Falling off the end means this date sorts last, so it goes at the end of
|
|
602
|
+
// the dated section rather than the end of the file: notes, comment blocks
|
|
603
|
+
// and todo lists kept below the log must stay below it.
|
|
582
604
|
const ascending = dates.length < 2 || dates[0].date <= dates[dates.length - 1].date;
|
|
583
|
-
let insertAt =
|
|
605
|
+
let insertAt =
|
|
606
|
+
dates.length > 0 ? datedSectionEnd(fileLines, dates[dates.length - 1].index) : fileLines.length;
|
|
584
607
|
for (const entry of dates) {
|
|
585
608
|
if (ascending ? entry.date > day : entry.date < day) {
|
|
586
609
|
insertAt = entry.index;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kairyou/agent-tools",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.5",
|
|
4
4
|
"description": "Reusable Agent Skills, plus integrations (statusline, provider usage, vision) that install into Codex, Claude Code, and opencode.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -26,13 +26,23 @@ Resolve paths to Git roots and deduplicate them. Report invalid paths; a non-Git
|
|
|
26
26
|
directory does not block other valid projects. Do not clone remote URLs without consent.
|
|
27
27
|
|
|
28
28
|
For each repository, resolve the author with `git -C <root> config user.name`; never
|
|
29
|
-
infer aliases or use the remote login as the author.
|
|
30
|
-
|
|
31
|
-
HEAD, user-named branches, and configured
|
|
32
|
-
|
|
33
|
-
`git fetch --no-tags <remote>
|
|
34
|
-
non-fatal. Do not change the
|
|
35
|
-
|
|
29
|
+
infer aliases or use the remote login as the author.
|
|
30
|
+
|
|
31
|
+
Inspect all local branches, current HEAD, user-named branches, and configured
|
|
32
|
+
upstreams. Unless the user requests local-only data, refresh only the configured
|
|
33
|
+
upstream branches, grouped into one best-effort `git fetch --no-tags <remote>
|
|
34
|
+
<branch...>` per involved remote. Fetch failure is non-fatal. Do not change the
|
|
35
|
+
working tree or local branch history.
|
|
36
|
+
|
|
37
|
+
Then list candidates with `git -C <root> log --no-merges --since=<from>
|
|
38
|
+
--until=<to+1day> --format=%H%x09%an --branches HEAD <upstream refs>`, taking the
|
|
39
|
+
upstream refs from `git -C <root> for-each-ref --format=%(upstream) refs/heads/`.
|
|
40
|
+
Passing no ref walks only the current branch, so work on an unmerged branch reads as
|
|
41
|
+
no activity; `--all` reaches past the scope above into remote branches nobody tracks.
|
|
42
|
+
Keep the rows whose `%an` equals the resolved name exactly. Never pass `--author`: it
|
|
43
|
+
matches the whole `Name <email>` header, so anchored patterns silently match nothing.
|
|
44
|
+
When the window holds commits but none carry that name, report the names actually
|
|
45
|
+
found instead. Deduplicate commits by hash.
|
|
36
46
|
|
|
37
47
|
## Build work items
|
|
38
48
|
|
|
@@ -87,7 +97,9 @@ first, and skip empty days.
|
|
|
87
97
|
## Draft or record
|
|
88
98
|
|
|
89
99
|
"Generate" / `生成日报` returns a draft. "Record" / `记录日报`, or an explicit
|
|
90
|
-
write request, writes after previewing the entry.
|
|
100
|
+
write request, writes after previewing the entry. A draft may close with one short
|
|
101
|
+
line offering to record it; treat any affirmative reply to that offer as the go-ahead,
|
|
102
|
+
and keep the suggested word short (`记录` / `record`). Resolve the destination in order:
|
|
91
103
|
|
|
92
104
|
1. a path supplied in conversation;
|
|
93
105
|
2. optional `dailyLog.output` in `~/.agent-tools/config.jsonc`;
|
|
@@ -103,17 +115,19 @@ the current block and the regenerated one before writing, then replace only that
|
|
|
103
115
|
date's block. If the date exists without markers, append a marked block below the
|
|
104
116
|
user's lines instead of editing them. Do not duplicate the date. Insert a new date
|
|
105
117
|
among the existing dated entries at its date-order position, inferring ascending or
|
|
106
|
-
descending from the dates already present (ascending when that is ambiguous)
|
|
107
|
-
|
|
118
|
+
descending from the dates already present (ascending when that is ambiguous). A date
|
|
119
|
+
that sorts last goes right after the final dated entry, not at the end of the file:
|
|
120
|
+
notes, comment blocks and todo lists kept below the log stay below it. On duplicate or
|
|
108
121
|
unpaired markers, stop and propose the edit instead of writing. When neither Git nor
|
|
109
122
|
the session log shows activity for the day, leave the file unchanged and say so; the
|
|
110
123
|
user can add a manual entry themselves. Recording a range applies these rules to each day's block independently.
|
|
111
124
|
|
|
112
125
|
Scheduling is separate; set it up only when the user asks. Prefer the OS scheduler
|
|
113
126
|
(Task Scheduler, cron, launchd) over agent-internal timers, which stop with the agent:
|
|
114
|
-
schedule a headless run of the CLI this skill is executing in
|
|
115
|
-
|
|
116
|
-
`claude -p "/at-daily-log record the log"
|
|
127
|
+
schedule a headless run of the CLI this skill is executing in — `claude -p <prompt>`,
|
|
128
|
+
`codex exec <prompt>`, `opencode run <prompt>` — invoking the skill with the recording
|
|
129
|
+
request the user asked for, as in `claude -p "/at-daily-log record the log"`. How a
|
|
130
|
+
skill is addressed varies per CLI, so the test run below is what confirms it.
|
|
117
131
|
Keep projects and output in config so the command stays stable. Before registering,
|
|
118
132
|
confirm the schedule, make sure the output file is resolvable, and run the exact
|
|
119
133
|
command once; register only after that test run records correctly, and show how to
|
|
@@ -27,8 +27,15 @@ Always exclude merge commits.
|
|
|
27
27
|
|
|
28
28
|
Use an explicit author when supplied; otherwise resolve it independently per repository
|
|
29
29
|
with `git -C <root> config user.name`. Never infer aliases or use the remote login as
|
|
30
|
-
the author.
|
|
31
|
-
|
|
30
|
+
the author. List candidates with `git -C <root> log --no-merges --since=<from>
|
|
31
|
+
--until=<to+1day> --format=%H%x09%an --branches HEAD <upstream refs>`, taking the
|
|
32
|
+
upstream refs from `git -C <root> for-each-ref --format=%(upstream) refs/heads/`;
|
|
33
|
+
passing no ref walks only the current branch, so work on an unmerged branch reads as
|
|
34
|
+
no activity, while `--all` reaches into remote branches nobody tracks. Keep the rows
|
|
35
|
+
whose `%an` equals that name exactly. Never pass `--author`: it matches the whole
|
|
36
|
+
`Name <email>` header, so anchored patterns silently match nothing. If no commits
|
|
37
|
+
match, report that together with the author names the window actually holds; do not
|
|
38
|
+
try spelling or language variants. When results exist, mention once that the
|
|
32
39
|
user can provide other author names if needed.
|
|
33
40
|
|
|
34
41
|
## Project evidence
|