escribano 0.2.0 → 0.2.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/dist/index.js +7 -0
- package/package.json +2 -1
- package/scripts/backfill-releases.mjs +207 -0
- package/scripts/create-release.mjs +201 -0
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { homedir } from 'node:os';
|
|
9
9
|
import path from 'node:path';
|
|
10
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
10
11
|
import { createCapCaptureSource } from './adapters/capture.cap.adapter.js';
|
|
11
12
|
import { createFilesystemCaptureSource } from './adapters/capture.filesystem.adapter.js';
|
|
12
13
|
import { cleanupMlxBridge, initializeSystem, processVideo, } from './batch-context.js';
|
|
@@ -18,6 +19,10 @@ const MODEL_FILE = 'ggml-large-v3.bin';
|
|
|
18
19
|
const MODEL_PATH = path.join(MODELS_DIR, MODEL_FILE);
|
|
19
20
|
function main() {
|
|
20
21
|
const args = parseArgs(process.argv.slice(2));
|
|
22
|
+
if (args.version) {
|
|
23
|
+
console.log(`escribano v${pkg.version}`);
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
21
26
|
if (args.help) {
|
|
22
27
|
showHelp();
|
|
23
28
|
process.exit(0);
|
|
@@ -54,6 +59,7 @@ function parseArgs(argsArray) {
|
|
|
54
59
|
return {
|
|
55
60
|
force: argsArray.includes('--force'),
|
|
56
61
|
help: argsArray.includes('--help') || argsArray.includes('-h'),
|
|
62
|
+
version: argsArray.includes('--version') || argsArray.includes('-v'),
|
|
57
63
|
doctor: argsArray[0] === 'doctor',
|
|
58
64
|
file: filePath,
|
|
59
65
|
skipSummary: argsArray.includes('--skip-summary'),
|
|
@@ -83,6 +89,7 @@ Usage:
|
|
|
83
89
|
npx escribano --include-personal Include personal time in artifact
|
|
84
90
|
npx escribano --copy Copy artifact to clipboard
|
|
85
91
|
npx escribano --stdout Print artifact to stdout
|
|
92
|
+
npx escribano --version Show version number
|
|
86
93
|
npx escribano --help Show this help
|
|
87
94
|
|
|
88
95
|
Examples:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "escribano",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "AI-powered session intelligence tool — turn screen recordings into structured work summaries",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"build": "tsc",
|
|
23
23
|
"postbuild": "node scripts/add-shebang.mjs",
|
|
24
24
|
"prepublishOnly": "pnpm build",
|
|
25
|
+
"postpublish": "node scripts/create-release.mjs",
|
|
25
26
|
"lint": "biome check .",
|
|
26
27
|
"lint:fix": "biome check --write .",
|
|
27
28
|
"format": "biome format --write .",
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
|
|
7
|
+
const REPO_ROOT = join(import.meta.dirname, "..");
|
|
8
|
+
const CHANGELOG_PATH = join(REPO_ROOT, "CHANGELOG.md");
|
|
9
|
+
|
|
10
|
+
function run(cmd) {
|
|
11
|
+
return execSync(cmd, { encoding: "utf-8", cwd: REPO_ROOT }).trim();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function getPreviousTag(currentTag, allTags) {
|
|
15
|
+
const currentIndex = allTags.indexOf(currentTag);
|
|
16
|
+
if (currentIndex === 0) return null;
|
|
17
|
+
return allTags[currentIndex - 1];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getCommitsBetweenTags(fromTag, toTag) {
|
|
21
|
+
const range = fromTag ? `${fromTag}..${toTag}` : toTag;
|
|
22
|
+
const format = "%H|%s|%an|%ad";
|
|
23
|
+
const dateformat = "--date=short";
|
|
24
|
+
|
|
25
|
+
const commits = run(`git log ${range} --pretty=format:'${format}' ${dateformat}`)
|
|
26
|
+
.split("\n")
|
|
27
|
+
.filter(Boolean)
|
|
28
|
+
.map(line => {
|
|
29
|
+
const [hash, subject, author, date] = line.split("|");
|
|
30
|
+
return { hash, subject, author, date };
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return commits;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function generateReleaseNotes(commits, version, tagDate) {
|
|
37
|
+
const commitList = commits
|
|
38
|
+
.map(c => `- ${c.subject} (${c.author}, ${c.date})`)
|
|
39
|
+
.join("\n");
|
|
40
|
+
|
|
41
|
+
const prompt = `You are a release notes generator. Given these git commits, create a clean, user-friendly changelog entry.
|
|
42
|
+
|
|
43
|
+
Version: ${version}
|
|
44
|
+
Date: ${tagDate}
|
|
45
|
+
|
|
46
|
+
Commits:
|
|
47
|
+
${commitList}
|
|
48
|
+
|
|
49
|
+
Generate a changelog entry in this exact format (use markdown):
|
|
50
|
+
|
|
51
|
+
## [${version}] - ${tagDate}
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
- (new features)
|
|
55
|
+
|
|
56
|
+
### Fixed
|
|
57
|
+
- (bug fixes)
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
- (improvements, refactors)
|
|
61
|
+
|
|
62
|
+
### Removed
|
|
63
|
+
- (deprecated features removed)
|
|
64
|
+
|
|
65
|
+
Rules:
|
|
66
|
+
- Group commits into the most appropriate section
|
|
67
|
+
- Use present tense ("Add feature" not "Added feature")
|
|
68
|
+
- Be concise but descriptive
|
|
69
|
+
- If a section has no commits, omit it entirely
|
|
70
|
+
- Only output the changelog entry, no additional text`;
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const response = await fetch("http://localhost:11434/api/generate", {
|
|
74
|
+
method: "POST",
|
|
75
|
+
headers: { "Content-Type": "application/json" },
|
|
76
|
+
body: JSON.stringify({
|
|
77
|
+
model: process.env.ESCRIBANO_LLM_MODEL || "qwen3:8b",
|
|
78
|
+
prompt,
|
|
79
|
+
stream: false,
|
|
80
|
+
options: {
|
|
81
|
+
temperature: 0.3,
|
|
82
|
+
},
|
|
83
|
+
}),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
throw new Error(`Ollama request failed: ${response.statusText}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const data = await response.json();
|
|
91
|
+
return data.response.trim();
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error("⚠️ Failed to generate notes with Ollama, using fallback");
|
|
94
|
+
console.error(error.message);
|
|
95
|
+
|
|
96
|
+
const changes = commits.map(c => `- ${c.subject}`).join("\n");
|
|
97
|
+
|
|
98
|
+
return `## [${version}] - ${tagDate}
|
|
99
|
+
|
|
100
|
+
### Changed
|
|
101
|
+
${changes}`;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function updateChangelog(releaseNotes) {
|
|
106
|
+
let changelog = "";
|
|
107
|
+
|
|
108
|
+
if (existsSync(CHANGELOG_PATH)) {
|
|
109
|
+
changelog = readFileSync(CHANGELOG_PATH, "utf-8");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const header = `# Changelog
|
|
113
|
+
|
|
114
|
+
All notable changes to this project will be documented in this file.
|
|
115
|
+
|
|
116
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
117
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
118
|
+
|
|
119
|
+
`;
|
|
120
|
+
|
|
121
|
+
if (!changelog.includes("# Changelog")) {
|
|
122
|
+
changelog = header + changelog;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Insert new release after header
|
|
126
|
+
const lines = changelog.split("\n");
|
|
127
|
+
const insertIndex = lines.findIndex(line => line.startsWith("## [")) || 6;
|
|
128
|
+
|
|
129
|
+
lines.splice(insertIndex, 0, releaseNotes, "");
|
|
130
|
+
|
|
131
|
+
writeFileSync(CHANGELOG_PATH, lines.join("\n"));
|
|
132
|
+
console.log("✅ Updated CHANGELOG.md");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function createGitHubRelease(tag, releaseNotes) {
|
|
136
|
+
const notesFile = `/tmp/escribano-release-${tag}.md`;
|
|
137
|
+
writeFileSync(notesFile, releaseNotes);
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
run(`gh release create ${tag} --title "${tag}" --notes-file "${notesFile}"`);
|
|
141
|
+
console.log(`✅ Created GitHub release: ${tag}`);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
if (error.message.includes("already exists")) {
|
|
144
|
+
console.log(`⚠️ Release ${tag} already exists, skipping`);
|
|
145
|
+
} else {
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function main() {
|
|
152
|
+
const args = process.argv.slice(2);
|
|
153
|
+
|
|
154
|
+
if (args.length === 0) {
|
|
155
|
+
console.log("Usage: node scripts/backfill-releases.mjs <tag1> [tag2] [tag3] ...");
|
|
156
|
+
console.log("Example: node scripts/backfill-releases.mjs v0.1.1 v0.1.3 v0.2.0");
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const tagsToProcess = args;
|
|
161
|
+
const allTags = run("git tag --list 'v*' | sort -V").split("\n").filter(Boolean);
|
|
162
|
+
|
|
163
|
+
console.log(`🚀 Creating releases for ${tagsToProcess.length} tags...\n`);
|
|
164
|
+
|
|
165
|
+
for (const tag of tagsToProcess) {
|
|
166
|
+
if (!allTags.includes(tag)) {
|
|
167
|
+
console.error(`❌ Tag ${tag} not found`);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
console.log(`\n📌 Processing ${tag}`);
|
|
172
|
+
|
|
173
|
+
const previousTag = getPreviousTag(tag, allTags);
|
|
174
|
+
console.log(` Previous tag: ${previousTag || "none (first release)"}`);
|
|
175
|
+
|
|
176
|
+
const commits = getCommitsBetweenTags(previousTag, tag);
|
|
177
|
+
console.log(` Found ${commits.length} commits`);
|
|
178
|
+
|
|
179
|
+
if (commits.length === 0) {
|
|
180
|
+
console.log(" ⚠️ No commits found, skipping");
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const version = tag.replace(/^v/, "");
|
|
185
|
+
const tagDate = run(`git log -1 --format=%ad --date=short ${tag}`);
|
|
186
|
+
|
|
187
|
+
const releaseNotes = await generateReleaseNotes(commits, version, tagDate);
|
|
188
|
+
|
|
189
|
+
console.log("\n Generated release notes:");
|
|
190
|
+
console.log(" " + releaseNotes.split("\n").join("\n "));
|
|
191
|
+
console.log("\n ---");
|
|
192
|
+
|
|
193
|
+
updateChangelog(releaseNotes);
|
|
194
|
+
createGitHubRelease(tag, releaseNotes);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
console.log("\n🎉 All releases complete!");
|
|
198
|
+
console.log("\nNext steps:");
|
|
199
|
+
console.log(" 1. Review CHANGELOG.md changes");
|
|
200
|
+
console.log(" 2. Commit: git add CHANGELOG.md && git commit -m 'docs: add CHANGELOG'");
|
|
201
|
+
console.log(" 3. Push: git push");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
main().catch(error => {
|
|
205
|
+
console.error("❌ Release creation failed:", error.message);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
});
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
|
|
7
|
+
const REPO_ROOT = join(import.meta.dirname, "..");
|
|
8
|
+
const CHANGELOG_PATH = join(REPO_ROOT, "CHANGELOG.md");
|
|
9
|
+
|
|
10
|
+
function run(cmd) {
|
|
11
|
+
return execSync(cmd, { encoding: "utf-8", cwd: REPO_ROOT }).trim();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function getCurrentTag() {
|
|
15
|
+
try {
|
|
16
|
+
return run("git describe --tags --exact-match");
|
|
17
|
+
} catch {
|
|
18
|
+
console.error("❌ No git tag found at current commit");
|
|
19
|
+
console.error(" Run: git tag v<x.y.z> && git push --tags");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getPreviousTag(currentTag) {
|
|
25
|
+
const allTags = run("git tag --list 'v*' | sort -V").split("\n").filter(Boolean);
|
|
26
|
+
const currentIndex = allTags.indexOf(currentTag);
|
|
27
|
+
|
|
28
|
+
if (currentIndex === 0) return null;
|
|
29
|
+
return allTags[currentIndex - 1];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getCommitsSinceTag(previousTag, currentTag) {
|
|
33
|
+
const range = previousTag ? `${previousTag}..${currentTag}` : currentTag;
|
|
34
|
+
const format = "%H|%s|%an|%ad";
|
|
35
|
+
const dateformat = "--date=short";
|
|
36
|
+
|
|
37
|
+
const commits = run(`git log ${range} --pretty=format:'${format}' ${dateformat}`)
|
|
38
|
+
.split("\n")
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.map(line => {
|
|
41
|
+
const [hash, subject, author, date] = line.split("|");
|
|
42
|
+
return { hash, subject, author, date };
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return commits;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function generateReleaseNotes(commits, version) {
|
|
49
|
+
const commitList = commits
|
|
50
|
+
.map(c => `- ${c.subject} (${c.author}, ${c.date})`)
|
|
51
|
+
.join("\n");
|
|
52
|
+
|
|
53
|
+
const prompt = `You are a release notes generator. Given these git commits, create a clean, user-friendly changelog entry.
|
|
54
|
+
|
|
55
|
+
Version: ${version}
|
|
56
|
+
|
|
57
|
+
Commits:
|
|
58
|
+
${commitList}
|
|
59
|
+
|
|
60
|
+
Generate a changelog entry in this exact format (use markdown):
|
|
61
|
+
|
|
62
|
+
## [${version}] - ${new Date().toISOString().split("T")[0]}
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
- (new features)
|
|
66
|
+
|
|
67
|
+
### Fixed
|
|
68
|
+
- (bug fixes)
|
|
69
|
+
|
|
70
|
+
### Changed
|
|
71
|
+
- (improvements, refactors)
|
|
72
|
+
|
|
73
|
+
### Removed
|
|
74
|
+
- (deprecated features removed)
|
|
75
|
+
|
|
76
|
+
Rules:
|
|
77
|
+
- Group commits into the most appropriate section
|
|
78
|
+
- Use present tense ("Add feature" not "Added feature")
|
|
79
|
+
- Be concise but descriptive
|
|
80
|
+
- If a section has no commits, omit it entirely
|
|
81
|
+
- Only output the changelog entry, no additional text`;
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const response = await fetch("http://localhost:11434/api/generate", {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: { "Content-Type": "application/json" },
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
model: process.env.ESCRIBANO_LLM_MODEL || "qwen3:8b",
|
|
89
|
+
prompt,
|
|
90
|
+
stream: false,
|
|
91
|
+
options: {
|
|
92
|
+
temperature: 0.3,
|
|
93
|
+
},
|
|
94
|
+
}),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
throw new Error(`Ollama request failed: ${response.statusText}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const data = await response.json();
|
|
102
|
+
return data.response.trim();
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error("⚠️ Failed to generate notes with Ollama, using fallback");
|
|
105
|
+
console.error(error.message);
|
|
106
|
+
|
|
107
|
+
// Fallback: simple format
|
|
108
|
+
const date = new Date().toISOString().split("T")[0];
|
|
109
|
+
const changes = commits.map(c => `- ${c.subject}`).join("\n");
|
|
110
|
+
|
|
111
|
+
return `## [${version}] - ${date}
|
|
112
|
+
|
|
113
|
+
### Changed
|
|
114
|
+
${changes}`;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function updateChangelog(releaseNotes) {
|
|
119
|
+
let changelog = "";
|
|
120
|
+
|
|
121
|
+
if (existsSync(CHANGELOG_PATH)) {
|
|
122
|
+
changelog = readFileSync(CHANGELOG_PATH, "utf-8");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const header = `# Changelog
|
|
126
|
+
|
|
127
|
+
All notable changes to this project will be documented in this file.
|
|
128
|
+
|
|
129
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
130
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
131
|
+
|
|
132
|
+
`;
|
|
133
|
+
|
|
134
|
+
if (!changelog.includes("# Changelog")) {
|
|
135
|
+
changelog = header + changelog;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Insert new release after header
|
|
139
|
+
const lines = changelog.split("\n");
|
|
140
|
+
const insertIndex = lines.findIndex(line => line.startsWith("## [")) || 6;
|
|
141
|
+
|
|
142
|
+
lines.splice(insertIndex, 0, releaseNotes, "");
|
|
143
|
+
|
|
144
|
+
writeFileSync(CHANGELOG_PATH, lines.join("\n"));
|
|
145
|
+
console.log("✅ Updated CHANGELOG.md");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function createGitHubRelease(tag, releaseNotes) {
|
|
149
|
+
const notesFile = `/tmp/escribano-release-${tag}.md`;
|
|
150
|
+
writeFileSync(notesFile, releaseNotes);
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
run(`gh release create ${tag} --title "${tag}" --notes-file "${notesFile}"`);
|
|
154
|
+
console.log(`✅ Created GitHub release: ${tag}`);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (error.message.includes("already exists")) {
|
|
157
|
+
console.log(`⚠️ Release ${tag} already exists, skipping`);
|
|
158
|
+
} else {
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function main() {
|
|
165
|
+
console.log("🚀 Creating GitHub release...\n");
|
|
166
|
+
|
|
167
|
+
const currentTag = getCurrentTag();
|
|
168
|
+
console.log(`📌 Current tag: ${currentTag}`);
|
|
169
|
+
|
|
170
|
+
const previousTag = getPreviousTag(currentTag);
|
|
171
|
+
console.log(`📌 Previous tag: ${previousTag || "none (first release)"}`);
|
|
172
|
+
|
|
173
|
+
const commits = getCommitsSinceTag(previousTag, currentTag);
|
|
174
|
+
console.log(`📝 Found ${commits.length} commits\n`);
|
|
175
|
+
|
|
176
|
+
if (commits.length === 0) {
|
|
177
|
+
console.log("⚠️ No commits found, skipping release creation");
|
|
178
|
+
process.exit(0);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const version = currentTag.replace(/^v/, "");
|
|
182
|
+
const releaseNotes = await generateReleaseNotes(commits, version);
|
|
183
|
+
|
|
184
|
+
console.log("Generated release notes:\n");
|
|
185
|
+
console.log(releaseNotes);
|
|
186
|
+
console.log("\n---\n");
|
|
187
|
+
|
|
188
|
+
updateChangelog(releaseNotes);
|
|
189
|
+
createGitHubRelease(currentTag, releaseNotes);
|
|
190
|
+
|
|
191
|
+
console.log("\n🎉 Release complete!");
|
|
192
|
+
console.log("\nNext steps:");
|
|
193
|
+
console.log(" 1. Review CHANGELOG.md changes");
|
|
194
|
+
console.log(" 2. Commit: git add CHANGELOG.md && git commit -m 'docs: update CHANGELOG'");
|
|
195
|
+
console.log(" 3. Push: git push");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
main().catch(error => {
|
|
199
|
+
console.error("❌ Release creation failed:", error.message);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
});
|