agent-skill-installer 0.1.5 → 0.1.7
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/bin/cli.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const fs = require("node:fs/promises");
|
|
4
4
|
const path = require("node:path");
|
|
5
5
|
const os = require("node:os");
|
|
6
|
+
const readline = require("node:readline/promises");
|
|
6
7
|
|
|
7
8
|
const REPO_ROOT = path.resolve(__dirname, "..");
|
|
8
9
|
const SKILLS_SOURCE_DIR = path.join(REPO_ROOT, "skills");
|
|
@@ -21,7 +22,7 @@ Commands:
|
|
|
21
22
|
|
|
22
23
|
Options:
|
|
23
24
|
--dest <path> Custom destination directory.
|
|
24
|
-
--force Overwrite destination if a skill already exists.
|
|
25
|
+
--force Overwrite destination if a skill already exists (no prompt).
|
|
25
26
|
-h, --help Show this help.
|
|
26
27
|
`);
|
|
27
28
|
}
|
|
@@ -112,6 +113,33 @@ async function listSkills() {
|
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
async function confirmOverwrite(name, targetPath) {
|
|
117
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`Destination already exists for "${name}": ${targetPath} (non-interactive mode, use --force to overwrite)`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const rl = readline.createInterface({
|
|
124
|
+
input: process.stdin,
|
|
125
|
+
output: process.stdout
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const answer = (
|
|
130
|
+
await rl.question(
|
|
131
|
+
`Skill "${name}" already exists at ${targetPath}. Overwrite it? [y/N] `
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
.trim()
|
|
135
|
+
.toLowerCase();
|
|
136
|
+
|
|
137
|
+
return answer === "y" || answer === "yes";
|
|
138
|
+
} finally {
|
|
139
|
+
rl.close();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
115
143
|
async function installSkills(rawArgs) {
|
|
116
144
|
const parsed = parseInstallArgs(rawArgs);
|
|
117
145
|
if (parsed.help) {
|
|
@@ -137,13 +165,16 @@ async function installSkills(rawArgs) {
|
|
|
137
165
|
const to = path.join(parsed.dest, name);
|
|
138
166
|
const exists = await dirExists(to);
|
|
139
167
|
|
|
140
|
-
if (exists &&
|
|
141
|
-
|
|
142
|
-
`Destination already exists for "${name}": ${to} (use --force to overwrite)`
|
|
143
|
-
);
|
|
168
|
+
if (exists && parsed.force) {
|
|
169
|
+
await fs.rm(to, { recursive: true, force: true });
|
|
144
170
|
}
|
|
145
171
|
|
|
146
|
-
if (exists && parsed.force) {
|
|
172
|
+
if (exists && !parsed.force) {
|
|
173
|
+
const shouldOverwrite = await confirmOverwrite(name, to);
|
|
174
|
+
if (!shouldOverwrite) {
|
|
175
|
+
console.log(`Skipped "${name}" (kept existing skill).`);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
147
178
|
await fs.rm(to, { recursive: true, force: true });
|
|
148
179
|
}
|
|
149
180
|
|
package/package.json
CHANGED
|
@@ -7,45 +7,56 @@ Produce a daily report from local Codex sessions.
|
|
|
7
7
|
|
|
8
8
|
## Workflow
|
|
9
9
|
|
|
10
|
-
1. Resolve target date
|
|
11
|
-
- If user says `today`,
|
|
10
|
+
1. Resolve target date to absolute date `YYYY-MM-DD`.
|
|
11
|
+
- If user says `today`, convert explicitly (for example `2026-03-02`).
|
|
12
|
+
- If user says relative dates (`yesterday`, `tomorrow`), convert to concrete dates first.
|
|
12
13
|
|
|
13
|
-
2.
|
|
14
|
+
2. Extract `assistant final_answer` entries with bundled script.
|
|
14
15
|
|
|
15
16
|
```bash
|
|
16
|
-
|
|
17
|
+
python3 skills/codex-session-daily-report/scripts/extract_final_answers.py --date 2026-03-02
|
|
17
18
|
```
|
|
18
19
|
|
|
19
|
-
3.
|
|
20
|
+
3. If user asks for a custom session root, pass `--root`.
|
|
20
21
|
|
|
21
22
|
```bash
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
| map(select(.type=="output_text") | .text)
|
|
26
|
-
| join(" ") | gsub("[\n\r\t]+";" ") | gsub(" +";" "))' "$f"
|
|
27
|
-
done
|
|
23
|
+
python3 skills/codex-session-daily-report/scripts/extract_final_answers.py \
|
|
24
|
+
--date 2026-03-02 \
|
|
25
|
+
--root ~/.codex/sessions
|
|
28
26
|
```
|
|
29
27
|
|
|
30
|
-
4. Summarize
|
|
31
|
-
- Merge similar tasks into one topic
|
|
32
|
-
- Keep
|
|
33
|
-
- Keep neutral
|
|
28
|
+
4. Summarize only completed outcomes from extracted lines.
|
|
29
|
+
- Merge similar tasks into one topic.
|
|
30
|
+
- Keep concrete evidence when present: file paths, test commands/results, commit hashes.
|
|
31
|
+
- Keep neutral wording and avoid guessing missing facts.
|
|
34
32
|
|
|
35
|
-
5. Output
|
|
33
|
+
5. Output report only, not raw logs, unless user explicitly asks for raw lines.
|
|
36
34
|
|
|
37
35
|
## Output Format
|
|
38
36
|
|
|
39
37
|
Use this structure:
|
|
40
38
|
|
|
41
39
|
1. 日期:`YYYY-MM-DD`
|
|
42
|
-
2.
|
|
43
|
-
3.
|
|
44
|
-
4.
|
|
40
|
+
2. 统计快照:`会话文件 N`、`final_answer N`、`覆盖时段 HH:mm-HH:mm`
|
|
41
|
+
3. 今日业务事项:按主题列 2-6 条,每条包含 `动作`、`结果`、`证据`
|
|
42
|
+
4. 关键产出:按类型分组输出
|
|
43
|
+
- 代码改动:关键文件路径
|
|
44
|
+
- 验证结果:命令与结论
|
|
45
|
+
- 提交记录:提交哈希与提交说明
|
|
46
|
+
5. 风险与待办:每条包含 `风险/阻塞`、`影响`、`下一步`
|
|
47
|
+
6. 数据缺口说明:若无会话文件、无 final_answer 或证据不足,明确说明影响范围
|
|
45
48
|
|
|
46
49
|
## Constraints
|
|
47
50
|
|
|
48
|
-
-
|
|
51
|
+
- Use `assistant final_answer` only; do not use `commentary` as completion evidence.
|
|
49
52
|
- Do not output raw JSONL unless user explicitly asks.
|
|
50
53
|
- Keep report concise and factual.
|
|
51
|
-
- If no
|
|
54
|
+
- If no session files or no `final_answer` are found, state that clearly in the report.
|
|
55
|
+
- Prefer explicit evidence fields over narrative paragraphs.
|
|
56
|
+
- If evidence is missing for a topic, mark it as `证据不足` instead of guessing.
|
|
57
|
+
|
|
58
|
+
## Resource
|
|
59
|
+
|
|
60
|
+
- `skills/codex-session-daily-report/scripts/extract_final_answers.py`
|
|
61
|
+
- Input: `--date YYYY-MM-DD`, optional `--root`.
|
|
62
|
+
- Output: `timestamp<TAB>session_file<TAB>text`.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "codex-session-daily-report"
|
|
3
|
-
short_description: "
|
|
4
|
-
default_prompt: "Use $codex-session-daily-report to
|
|
3
|
+
short_description: "Summarize Codex final answers into a daily report"
|
|
4
|
+
default_prompt: "Use $codex-session-daily-report to resolve a concrete target date, extract assistant final_answer entries from ~/.codex/sessions, and output a concise factual daily report."
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Extract assistant final answers from Codex session JSONL files."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import glob
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import re
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
WHITESPACE_RE = re.compile(r"\s+")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def normalize_text(text: str) -> str:
|
|
18
|
+
return WHITESPACE_RE.sub(" ", text).strip()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def parse_date(value: str) -> tuple[str, str, str]:
|
|
22
|
+
parts = value.split("-")
|
|
23
|
+
if len(parts) != 3 or not all(part.isdigit() for part in parts):
|
|
24
|
+
raise ValueError("日期格式必须为 YYYY-MM-DD")
|
|
25
|
+
|
|
26
|
+
year, month, day = parts
|
|
27
|
+
if len(year) != 4 or len(month) != 2 or len(day) != 2:
|
|
28
|
+
raise ValueError("日期格式必须为 YYYY-MM-DD")
|
|
29
|
+
|
|
30
|
+
return year, month, day
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def collect_session_files(root: str, date_value: str) -> list[str]:
|
|
34
|
+
year, month, day = parse_date(date_value)
|
|
35
|
+
pattern = os.path.join(root, year, month, day, "*.jsonl")
|
|
36
|
+
return sorted(glob.glob(pattern))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def extract_records(file_path: str) -> list[str]:
|
|
40
|
+
records: list[str] = []
|
|
41
|
+
with Path(file_path).open("r", encoding="utf-8") as handle:
|
|
42
|
+
for line in handle:
|
|
43
|
+
if not line.strip():
|
|
44
|
+
continue
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
item = json.loads(line)
|
|
48
|
+
except json.JSONDecodeError:
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
if item.get("type") != "response_item":
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
payload = item.get("payload") or {}
|
|
55
|
+
if payload.get("type") != "message":
|
|
56
|
+
continue
|
|
57
|
+
if payload.get("role") != "assistant":
|
|
58
|
+
continue
|
|
59
|
+
if payload.get("phase") != "final_answer":
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
content = payload.get("content") or []
|
|
63
|
+
text_parts = [
|
|
64
|
+
part.get("text", "")
|
|
65
|
+
for part in content
|
|
66
|
+
if isinstance(part, dict) and part.get("type") == "output_text"
|
|
67
|
+
]
|
|
68
|
+
text = normalize_text(" ".join(text_parts))
|
|
69
|
+
if not text:
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
timestamp = item.get("timestamp", "")
|
|
73
|
+
session_file = os.path.basename(file_path)
|
|
74
|
+
records.append(f"{timestamp}\t{session_file}\t{text}")
|
|
75
|
+
|
|
76
|
+
return records
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def main() -> int:
|
|
80
|
+
parser = argparse.ArgumentParser(
|
|
81
|
+
description="提取指定日期的 assistant final_answer 内容",
|
|
82
|
+
)
|
|
83
|
+
parser.add_argument(
|
|
84
|
+
"--date",
|
|
85
|
+
required=True,
|
|
86
|
+
help="目标日期,格式 YYYY-MM-DD",
|
|
87
|
+
)
|
|
88
|
+
parser.add_argument(
|
|
89
|
+
"--root",
|
|
90
|
+
default="~/.codex/sessions",
|
|
91
|
+
help="sessions 根目录,默认 ~/.codex/sessions",
|
|
92
|
+
)
|
|
93
|
+
args = parser.parse_args()
|
|
94
|
+
|
|
95
|
+
root = os.path.expanduser(args.root)
|
|
96
|
+
files = collect_session_files(root, args.date)
|
|
97
|
+
|
|
98
|
+
if not files:
|
|
99
|
+
print(f"未找到会话文件: {args.date}", file=sys.stderr)
|
|
100
|
+
return 1
|
|
101
|
+
|
|
102
|
+
wrote = False
|
|
103
|
+
for file_path in files:
|
|
104
|
+
for record in extract_records(file_path):
|
|
105
|
+
print(record)
|
|
106
|
+
wrote = True
|
|
107
|
+
|
|
108
|
+
if not wrote:
|
|
109
|
+
print(f"未找到 final_answer: {args.date}", file=sys.stderr)
|
|
110
|
+
return 2
|
|
111
|
+
|
|
112
|
+
return 0
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
if __name__ == "__main__":
|
|
116
|
+
sys.exit(main())
|