@tacone/prosey 0.7.1 → 0.8.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/bin/prosey CHANGED
@@ -101623,26 +101623,55 @@ function configPath() {
101623
101623
  function ensureDir(path) {
101624
101624
  return mkdir(path, { recursive: true });
101625
101625
  }
101626
+ function mergeOverDefaults(user, defaults) {
101627
+ return {
101628
+ pager: user.pager ?? defaults.pager,
101629
+ hints: user.hints ?? defaults.hints,
101630
+ format: user.format ?? defaults.format,
101631
+ ai: user.ai?.command !== undefined ? user.ai : defaults.ai,
101632
+ summarize: {
101633
+ command: user.summarize?.command ?? defaults.summarize?.command,
101634
+ prompt: user.summarize?.prompt ?? defaults.summarize?.prompt
101635
+ },
101636
+ transcribe: {
101637
+ command: user.transcribe?.command ?? defaults.transcribe?.command,
101638
+ prompt: user.transcribe?.prompt ?? defaults.transcribe?.prompt
101639
+ }
101640
+ };
101641
+ }
101642
+ async function parseOrEmpty(text) {
101643
+ try {
101644
+ return lt(text);
101645
+ } catch {
101646
+ return {};
101647
+ }
101648
+ }
101626
101649
  async function loadConfig() {
101650
+ const defaultConfig = await parseOrEmpty(await readDefaultConfig());
101627
101651
  const path = configPath();
101628
101652
  if (!existsSync(path)) {
101629
101653
  const dir = configDir();
101630
101654
  await ensureDir(dir);
101631
101655
  await writeFile(path, await readDefaultConfig(), "utf8");
101632
- return {};
101633
- }
101634
- const raw = await readFile(path, "utf8");
101635
- try {
101636
- return lt(raw);
101637
- } catch {
101638
- return {};
101656
+ return defaultConfig;
101639
101657
  }
101658
+ const userConfig = await parseOrEmpty(await readFile(path, "utf8"));
101659
+ return mergeOverDefaults(userConfig, defaultConfig);
101640
101660
  }
101641
101661
  async function resetConfig() {
101642
101662
  const path = configPath();
101643
101663
  const dir = configDir();
101644
101664
  await ensureDir(dir);
101645
- await writeFile(path, await readDefaultConfig(), "utf8");
101665
+ const raw = await readDefaultConfig();
101666
+ const commented = raw.split(`
101667
+ `).map((line) => {
101668
+ const trimmed = line.trim();
101669
+ if (trimmed === "" || trimmed.startsWith("#"))
101670
+ return line;
101671
+ return line.replace(trimmed, `# ${trimmed}`);
101672
+ }).join(`
101673
+ `);
101674
+ await writeFile(path, commented, "utf8");
101646
101675
  return path;
101647
101676
  }
101648
101677
 
@@ -103185,6 +103214,17 @@ ${css}
103185
103214
 
103186
103215
  }
103187
103216
  }
103217
+ main.content {
103218
+ max-width: 50em;
103219
+ margin: 0 auto;
103220
+ padding: 0 1rem;
103221
+ }
103222
+ @media (max-width: 767px) {
103223
+ main.content {
103224
+ margin-top: 0rem;
103225
+ }
103226
+ }
103227
+
103188
103228
  h1 { --pico-font-size: 1.9rem; }
103189
103229
  h2 { --pico-font-size: 1.5rem; }
103190
103230
  h3 { --pico-font-size: 1.25rem; }
@@ -103194,12 +103234,22 @@ h6 { --pico-font-size: 0.85rem; }
103194
103234
  h1, h2, h3, h4, h5, h6 {
103195
103235
  --pico-font-weight: 500;
103196
103236
  --pico-line-height: 1.25;
103197
- margin-top: 0;
103237
+ margin-top: 0.1em;
103238
+ margin-bottom: 0.666em;
103198
103239
  }
103199
103240
  h1 {
103200
103241
  letter-spacing: -0.03em;
103242
+ margin-top: calc(var(--pico-typography-spacing-vertical) * 2);
103201
103243
  margin-bottom: calc(var(--pico-typography-spacing-vertical) * 2);
103202
103244
  }
103245
+ @media (max-width: 767px) {
103246
+ h1 {
103247
+ margin-top: calc(var(--pico-typography-spacing-vertical) * 2);
103248
+ margin-bottom: calc(var(--pico-typography-spacing-vertical) * 2);
103249
+ text-align: center;
103250
+ }
103251
+ }
103252
+
103203
103253
  li:last-child {
103204
103254
  margin-bottom: 0px;
103205
103255
  }
@@ -103228,7 +103278,7 @@ img[alt="Prosey"]:hover, img[alt="Prosey"]:active, img[alt="Prosey"]:focus { fil
103228
103278
  <div style="flex:1;text-align:center;font-size:.85rem;color:var(--pico-muted-color)">${watchMeta ? watchMetaHtml(watchMeta.duration, watchMeta.wordCount, watchMeta.videoId) : ""}</div>
103229
103279
  <button id="theme-btn" type="button" style="background:none;border:none;cursor:pointer;padding:0;line-height:1;opacity:.5;filter:grayscale(100%);transition:all 0.3s;flex-shrink:0">\uD83D\uDCA1</button>
103230
103280
  </div>
103231
- <main style="max-width:50em;margin:0 auto;padding:1rem">
103281
+ <main class="content">
103232
103282
  ${body}
103233
103283
  </main>
103234
103284
  <script>
@@ -103263,7 +103313,7 @@ function openInBrowser(htmlPath) {
103263
103313
  // package.json
103264
103314
  var package_default = {
103265
103315
  name: "@tacone/prosey",
103266
- version: "0.7.1",
103316
+ version: "0.8.0",
103267
103317
  description: "Download YouTube video transcripts from the CLI",
103268
103318
  module: "src/index.ts",
103269
103319
  type: "module",
@@ -122082,6 +122132,7 @@ var listOnly = false;
122082
122132
  var outputPath;
122083
122133
  var outputJson = false;
122084
122134
  var format3 = "markdown";
122135
+ var formatExplicit = false;
122085
122136
  var noDecode = false;
122086
122137
  var showDetails = true;
122087
122138
  var noCache = false;
@@ -122114,14 +122165,19 @@ for (let i = 0;i < args.length; i++) {
122114
122165
  } else if (arg === "--json") {
122115
122166
  outputJson = true;
122116
122167
  format3 = "json";
122168
+ formatExplicit = true;
122117
122169
  } else if (arg === "--text") {
122118
122170
  outputJson = false;
122119
122171
  format3 = "text";
122172
+ formatExplicit = true;
122120
122173
  } else if (arg === "--markdown") {
122121
122174
  format3 = "markdown";
122175
+ formatExplicit = true;
122122
122176
  } else if (arg === "--html") {
122123
122177
  format3 = "html";
122178
+ formatExplicit = true;
122124
122179
  } else if (arg === "--format") {
122180
+ formatExplicit = true;
122125
122181
  const val = args[++i];
122126
122182
  if (val === "json") {
122127
122183
  format3 = "json";
@@ -122209,6 +122265,9 @@ debug("Pager:", pagerCmd ?? "none");
122209
122265
  useHints = config.hints;
122210
122266
  }
122211
122267
  }
122268
+ if (!formatExplicit && config.format) {
122269
+ format3 = config.format;
122270
+ }
122212
122271
  if (useHints) {
122213
122272
  const hasMarkdownPager = pagerCmd === "bat -lmd --style plain" || pagerCmd === "glow -p" || pagerCmd === "mdcat -l -p";
122214
122273
  if (!hasMarkdownPager) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tacone/prosey",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "Download YouTube video transcripts from the CLI",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
@@ -3,7 +3,8 @@ import { rm, readFile } from "node:fs/promises";
3
3
  import { existsSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { homedir } from "node:os";
6
- import { configPath, loadConfig, resetConfig } from "./config";
6
+ import { configPath, loadConfig, resetConfig, mergeOverDefaults } from "./config";
7
+ import type { ProseyConfig } from "./config";
7
8
 
8
9
  const ORIGINAL_XDG = process.env.XDG_CONFIG_HOME;
9
10
  const ORIGINAL_PROSEY_CONFIG = process.env.PROSEY_CONFIG_PATH;
@@ -49,7 +50,11 @@ describe("loadConfig", () => {
49
50
  expect(existsSync(tmpConfig)).toBe(false);
50
51
 
51
52
  const config = await loadConfig();
52
- expect(config).toEqual({});
53
+ expect(config.pager).toBe("auto");
54
+ expect(config.hints).toBe(true);
55
+ expect(config.format).toBe("html");
56
+ expect(config.ai?.command).toBeString();
57
+ expect(config.summarize?.prompt).toBeString();
53
58
  expect(existsSync(tmpConfig)).toBe(true);
54
59
 
55
60
  const content = await readFile(tmpConfig, "utf8");
@@ -73,7 +78,10 @@ describe("loadConfig", () => {
73
78
  await Bun.write(tmpConfig, "invalid [[\ntoml{{{");
74
79
 
75
80
  const config = await loadConfig();
76
- expect(config).toEqual({});
81
+ expect(config.pager).toBe("auto");
82
+ expect(config.hints).toBe(true);
83
+ expect(config.format).toBe("html");
84
+ expect(config.ai?.command).toBeString();
77
85
  });
78
86
  });
79
87
 
@@ -86,7 +94,20 @@ describe("resetConfig", () => {
86
94
  } catch {}
87
95
  });
88
96
 
89
- test("overwrites existing file with defaults", async () => {
97
+ test("reset + load returns full defaults", async () => {
98
+ process.env.PROSEY_CONFIG_PATH = tmpConfig;
99
+
100
+ await resetConfig();
101
+ const config = await loadConfig();
102
+ expect(config.pager).toBe("auto");
103
+ expect(config.hints).toBe(true);
104
+ expect(config.format).toBe("html");
105
+ expect(config.ai?.command).toBeString();
106
+ expect(config.summarize?.prompt).toBeString();
107
+ expect(config.transcribe?.prompt).toBeString();
108
+ });
109
+
110
+ test("overwrites existing file with commented defaults", async () => {
90
111
  process.env.PROSEY_CONFIG_PATH = tmpConfig;
91
112
 
92
113
  await Bun.write(tmpConfig, "# garbage");
@@ -94,7 +115,84 @@ describe("resetConfig", () => {
94
115
  expect(path).toBe(tmpConfig);
95
116
 
96
117
  const content = await readFile(tmpConfig, "utf8");
97
- expect(content).toContain("[summarize]");
98
- expect(content).toContain("command = ");
118
+ expect(content).toContain("# [summarize]");
119
+ expect(content).toContain("# command = ");
120
+ });
121
+ });
122
+
123
+ describe("mergeOverDefaults", () => {
124
+ const defaults: ProseyConfig = {
125
+ pager: "auto",
126
+ hints: true,
127
+ format: "html",
128
+ ai: { command: "opencode run" },
129
+ summarize: { prompt: "default summary", command: "opencode run" },
130
+ transcribe: { prompt: "default transcribe" },
131
+ };
132
+
133
+ test("empty user config returns defaults", () => {
134
+ const result = mergeOverDefaults({}, defaults);
135
+ expect(result.pager).toBe("auto");
136
+ expect(result.hints).toBe(true);
137
+ expect(result.format).toBe("html");
138
+ expect(result.ai?.command).toBe("opencode run");
139
+ expect(result.summarize?.prompt).toBe("default summary");
140
+ expect(result.transcribe?.prompt).toBe("default transcribe");
141
+ });
142
+
143
+ test("user pager overrides default", () => {
144
+ const result = mergeOverDefaults({ pager: "less" }, defaults);
145
+ expect(result.pager).toBe("less");
146
+ expect(result.hints).toBe(true);
147
+ });
148
+
149
+ test("user hints overrides default", () => {
150
+ const result = mergeOverDefaults({ hints: false }, defaults);
151
+ expect(result.hints).toBe(false);
152
+ expect(result.pager).toBe("auto");
153
+ });
154
+
155
+ test("user format overrides default", () => {
156
+ const result = mergeOverDefaults({ format: "markdown" }, defaults);
157
+ expect(result.format).toBe("markdown");
158
+ expect(result.hints).toBe(true);
159
+ });
160
+
161
+ test("user ai.command overrides default", () => {
162
+ const result = mergeOverDefaults({ ai: { command: "my-cmd" } }, defaults);
163
+ expect(result.ai?.command).toBe("my-cmd");
164
+ expect(result.summarize?.prompt).toBe("default summary");
165
+ });
166
+
167
+ test("user summarize.prompt overrides default", () => {
168
+ const result = mergeOverDefaults({ summarize: { prompt: "my prompt" } }, defaults);
169
+ expect(result.summarize?.prompt).toBe("my prompt");
170
+ expect(result.summarize?.command).toBe("opencode run");
171
+ });
172
+
173
+ test("user transcribe.prompt overrides default", () => {
174
+ const result = mergeOverDefaults({ transcribe: { prompt: "my transcribe" } }, defaults);
175
+ expect(result.transcribe?.prompt).toBe("my transcribe");
176
+ expect(result.summarize?.prompt).toBe("default summary");
177
+ });
178
+
179
+ test("all user values override all defaults", () => {
180
+ const result = mergeOverDefaults(
181
+ {
182
+ pager: "custom",
183
+ hints: false,
184
+ ai: { command: "custom-ai" },
185
+ summarize: { command: "custom-sum", prompt: "custom-sum-prompt" },
186
+ transcribe: { command: "custom-trans", prompt: "custom-trans-prompt" },
187
+ },
188
+ defaults,
189
+ );
190
+ expect(result.pager).toBe("custom");
191
+ expect(result.hints).toBe(false);
192
+ expect(result.ai?.command).toBe("custom-ai");
193
+ expect(result.summarize?.command).toBe("custom-sum");
194
+ expect(result.summarize?.prompt).toBe("custom-sum-prompt");
195
+ expect(result.transcribe?.command).toBe("custom-trans");
196
+ expect(result.transcribe?.prompt).toBe("custom-trans-prompt");
99
197
  });
100
198
  });
package/src/config.ts CHANGED
@@ -8,6 +8,7 @@ import { load } from "js-toml";
8
8
  export interface ProseyConfig {
9
9
  pager?: string;
10
10
  hints?: boolean;
11
+ format?: string;
11
12
  ai?: {
12
13
  command?: string;
13
14
  };
@@ -46,28 +47,59 @@ function ensureDir(path: string): Promise<void> {
46
47
  return mkdir(path, { recursive: true }) as Promise<void>;
47
48
  }
48
49
 
50
+ export function mergeOverDefaults(user: ProseyConfig, defaults: ProseyConfig): ProseyConfig {
51
+ return {
52
+ pager: user.pager ?? defaults.pager,
53
+ hints: user.hints ?? defaults.hints,
54
+ format: user.format ?? defaults.format,
55
+ ai: user.ai?.command !== undefined ? user.ai : defaults.ai,
56
+ summarize: {
57
+ command: user.summarize?.command ?? defaults.summarize?.command,
58
+ prompt: user.summarize?.prompt ?? defaults.summarize?.prompt,
59
+ },
60
+ transcribe: {
61
+ command: user.transcribe?.command ?? defaults.transcribe?.command,
62
+ prompt: user.transcribe?.prompt ?? defaults.transcribe?.prompt,
63
+ },
64
+ };
65
+ }
66
+
67
+ async function parseOrEmpty(text: string): Promise<ProseyConfig> {
68
+ try {
69
+ return load(text) as ProseyConfig;
70
+ } catch {
71
+ return {};
72
+ }
73
+ }
74
+
49
75
  export async function loadConfig(): Promise<ProseyConfig> {
76
+ const defaultConfig = await parseOrEmpty(await readDefaultConfig());
50
77
  const path = configPath();
51
78
 
52
79
  if (!existsSync(path)) {
53
80
  const dir = configDir();
54
81
  await ensureDir(dir);
55
82
  await writeFile(path, await readDefaultConfig(), "utf8");
56
- return {};
83
+ return defaultConfig;
57
84
  }
58
85
 
59
- const raw = await readFile(path, "utf8");
60
- try {
61
- return load(raw) as ProseyConfig;
62
- } catch {
63
- return {};
64
- }
86
+ const userConfig = await parseOrEmpty(await readFile(path, "utf8"));
87
+ return mergeOverDefaults(userConfig, defaultConfig);
65
88
  }
66
89
 
67
90
  export async function resetConfig(): Promise<string> {
68
91
  const path = configPath();
69
92
  const dir = configDir();
70
93
  await ensureDir(dir);
71
- await writeFile(path, await readDefaultConfig(), "utf8");
94
+ const raw = await readDefaultConfig();
95
+ const commented = raw
96
+ .split("\n")
97
+ .map((line) => {
98
+ const trimmed = line.trim();
99
+ if (trimmed === "" || trimmed.startsWith("#")) return line;
100
+ return line.replace(trimmed, `# ${trimmed}`);
101
+ })
102
+ .join("\n");
103
+ await writeFile(path, commented, "utf8");
72
104
  return path;
73
105
  }
@@ -1,6 +1,10 @@
1
1
  # Default prosey configuration
2
2
  # Created automatically on first run. Edit as needed.
3
3
 
4
+ # Default output format: "markdown", "html", "text", or "json".
5
+ # Can be overridden per-invocation via --html, --markdown, --text, --json, or --format.
6
+ format = "html"
7
+
4
8
  # Pager command for transcript and summary output.
5
9
  # Defaults to "auto": bat -lmd --style plain → glow -p → mdcat -l -p → less
6
10
  # Set to a custom command (e.g. "less -R") to override.
package/src/html.ts CHANGED
@@ -77,6 +77,17 @@ ${css}
77
77
 
78
78
  }
79
79
  }
80
+ main.content {
81
+ max-width: 50em;
82
+ margin: 0 auto;
83
+ padding: 0 1rem;
84
+ }
85
+ @media (max-width: 767px) {
86
+ main.content {
87
+ margin-top: 0rem;
88
+ }
89
+ }
90
+
80
91
  h1 { --pico-font-size: 1.9rem; }
81
92
  h2 { --pico-font-size: 1.5rem; }
82
93
  h3 { --pico-font-size: 1.25rem; }
@@ -86,12 +97,22 @@ h6 { --pico-font-size: 0.85rem; }
86
97
  h1, h2, h3, h4, h5, h6 {
87
98
  --pico-font-weight: 500;
88
99
  --pico-line-height: 1.25;
89
- margin-top: 0;
100
+ margin-top: 0.1em;
101
+ margin-bottom: 0.666em;
90
102
  }
91
103
  h1 {
92
104
  letter-spacing: -0.03em;
105
+ margin-top: calc(var(--pico-typography-spacing-vertical) * 2);
93
106
  margin-bottom: calc(var(--pico-typography-spacing-vertical) * 2);
94
107
  }
108
+ @media (max-width: 767px) {
109
+ h1 {
110
+ margin-top: calc(var(--pico-typography-spacing-vertical) * 2);
111
+ margin-bottom: calc(var(--pico-typography-spacing-vertical) * 2);
112
+ text-align: center;
113
+ }
114
+ }
115
+
95
116
  li:last-child {
96
117
  margin-bottom: 0px;
97
118
  }
@@ -120,7 +141,7 @@ img[alt="Prosey"]:hover, img[alt="Prosey"]:active, img[alt="Prosey"]:focus { fil
120
141
  <div style="flex:1;text-align:center;font-size:.85rem;color:var(--pico-muted-color)">${watchMeta ? watchMetaHtml(watchMeta.duration, watchMeta.wordCount, watchMeta.videoId) : ""}</div>
121
142
  <button id="theme-btn" type="button" style="background:none;border:none;cursor:pointer;padding:0;line-height:1;opacity:.5;filter:grayscale(100%);transition:all 0.3s;flex-shrink:0">💡</button>
122
143
  </div>
123
- <main style="max-width:50em;margin:0 auto;padding:1rem">
144
+ <main class="content">
124
145
  ${body}
125
146
  </main>
126
147
  <script>
package/src/index.ts CHANGED
@@ -249,6 +249,7 @@ let listOnly = false;
249
249
  let outputPath: string | undefined;
250
250
  let outputJson = false;
251
251
  let format: "text" | "json" | "markdown" | "html" = "markdown";
252
+ let formatExplicit = false;
252
253
  let noDecode = false;
253
254
  let showDetails = true;
254
255
  let noCache = false;
@@ -281,14 +282,19 @@ for (let i = 0; i < args.length; i++) {
281
282
  } else if (arg === "--json") {
282
283
  outputJson = true;
283
284
  format = "json";
285
+ formatExplicit = true;
284
286
  } else if (arg === "--text") {
285
287
  outputJson = false;
286
288
  format = "text";
289
+ formatExplicit = true;
287
290
  } else if (arg === "--markdown") {
288
291
  format = "markdown";
292
+ formatExplicit = true;
289
293
  } else if (arg === "--html") {
290
294
  format = "html";
295
+ formatExplicit = true;
291
296
  } else if (arg === "--format") {
297
+ formatExplicit = true;
292
298
  const val = args[++i];
293
299
  if (val === "json") {
294
300
  format = "json";
@@ -384,6 +390,10 @@ debug("Pager:", pagerCmd ?? "none");
384
390
  }
385
391
  }
386
392
 
393
+ if (!formatExplicit && config.format) {
394
+ format = config.format as "text" | "json" | "markdown" | "html";
395
+ }
396
+
387
397
  if (useHints) {
388
398
  const hasMarkdownPager =
389
399
  pagerCmd === "bat -lmd --style plain" || pagerCmd === "glow -p" || pagerCmd === "mdcat -l -p";