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 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.0",
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
+ });