@tacone/prosey 0.2.0 → 0.2.1

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/README.md CHANGED
@@ -153,6 +153,18 @@ Transcripts and summaries are cached to `/tmp/prosey/`. Repeated invocations
153
153
  for the same video and options are instant and work offline. Use `--no-cache`
154
154
  to skip cache reads and force a fresh fetch.
155
155
 
156
+ When running `prosey summarize`, the command runs inside the cache directory
157
+ for that video. This prevents the AI agent from picking up project-specific
158
+ files like `AGENTS.md` or `CLAUDE.md` from the current folder, and limits its
159
+ ability to modify files outside that directory.
160
+
161
+ ## Exit codes
162
+
163
+ | Code | Meaning |
164
+ | ---- | ----------------------- |
165
+ | `0` | Success |
166
+ | `65` | Invalid video URL or ID |
167
+
156
168
  ## How it works
157
169
 
158
170
  prosey uses YouTube's Innertube API via the
package/bin/prosey CHANGED
@@ -13851,11 +13851,25 @@ var FALLBACK_CONFIG_TOML = `# Default prosey configuration
13851
13851
  # Prompt sent to the command via stdin.
13852
13852
  # Customize this to change how transcripts are summarized.
13853
13853
  prompt = """
13854
- Summarize the following transcript.
13855
- Focus on the key points and main arguments.
13854
+ Write a comprehensive summary of the following transcription.
13856
13855
  """
13857
13856
 
13858
- # Command to execute with the prompt piped via stdin.
13857
+ # Command to execute with the prompt and transcript piped via stdin.
13858
+ # The transcript is appended to the prompt automatically.
13859
+ #
13860
+ # Available options:
13861
+ #
13862
+ # opencode run — full access (default)
13863
+ # opencode run --permissions read — read-only (view files, no edits)
13864
+ #
13865
+ # claude -p "" --print — full access (--print for clean output)
13866
+ # claude --permission-mode plan -p "" --print — read-only (plan/read only)
13867
+ #
13868
+ # copilot -sp "" — full access (-s = silent, -p = prompt)
13869
+ # copilot -sp "" --deny-all-tools — read-only (no shell/write access)
13870
+ #
13871
+ # codex --sandbox default -p "" — full access
13872
+ # codex --sandbox read-only -p "" — read-only
13859
13873
  command = "opencode run"
13860
13874
  `;
13861
13875
  async function readDefaultConfig() {
@@ -13934,7 +13948,11 @@ async function summarize(options) {
13934
13948
 
13935
13949
  ${transcript}`;
13936
13950
  const output = await executeCommand(command, fullPrompt, cwd);
13937
- return output.replace(fullPrompt, "").replace(/\n+$/, "");
13951
+ const cleaned = output.startsWith(fullPrompt) ? output.slice(fullPrompt.length).replace(/\n+$/, "") : output.replace(/\n+$/, "");
13952
+ if (!cleaned || cleaned === transcript) {
13953
+ throw new Error("Summarization command returned no meaningful output");
13954
+ }
13955
+ return cleaned;
13938
13956
  }
13939
13957
 
13940
13958
  // src/cache.ts
@@ -13942,6 +13960,16 @@ import { createHash } from "node:crypto";
13942
13960
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
13943
13961
  import { existsSync as existsSync2 } from "node:fs";
13944
13962
  import { join as join2 } from "node:path";
13963
+ var RE_YOUTUBE2 = /(?:v=|\/|v\/|embed\/|watch\?.*v=|youtu\.be\/|\/v\/|e\/|watch\?.*vi?=|\/embed\/|\/v\/|vi?\/|watch\?.*vi?=|youtu\.be\/|\/vi?\/|\/e\/)([a-zA-Z0-9_-]{11})/i;
13964
+ var RE_BARE_ID = /^[a-zA-Z0-9_-]{11}$/;
13965
+ function extractVideoId(input) {
13966
+ if (RE_BARE_ID.test(input))
13967
+ return input;
13968
+ const match = input.match(RE_YOUTUBE2);
13969
+ if (match)
13970
+ return match[1] || null;
13971
+ return null;
13972
+ }
13945
13973
  function hashOptions(opts) {
13946
13974
  return createHash("sha256").update(JSON.stringify(opts)).digest("hex").slice(0, 8);
13947
13975
  }
@@ -13964,6 +13992,19 @@ async function writeCache(dir, filename, data) {
13964
13992
  await writeFile2(join2(dir, filename), data, "utf8");
13965
13993
  }
13966
13994
 
13995
+ // src/debug.ts
13996
+ var GRAY = "\x1B[90m";
13997
+ var RESET = "\x1B[0m";
13998
+ var enabled = false;
13999
+ function enableDebug() {
14000
+ enabled = true;
14001
+ }
14002
+ function debug(...args) {
14003
+ if (!enabled)
14004
+ return;
14005
+ console.error(GRAY, ...args, RESET);
14006
+ }
14007
+
13967
14008
  // src/index.ts
13968
14009
  var NAME2 = "prosey";
13969
14010
  var VERSION2 = "0.1.0";
@@ -13995,6 +14036,7 @@ Options:
13995
14036
  --no-decode-entities Preserve HTML entities (decoded by default).
13996
14037
  --reset-config Reset config file to defaults and exit.
13997
14038
  --no-cache Skip cache and overwrite cache files.
14039
+ --debug Print debug information to stderr.
13998
14040
  --help Show this help message.
13999
14041
  --version Show version.
14000
14042
 
@@ -14073,12 +14115,10 @@ if (args.includes("--reset-config")) {
14073
14115
  }
14074
14116
  var config = await loadConfig().catch(() => ({}));
14075
14117
  var mode = "transcript";
14076
- if (args[0] === "info") {
14077
- mode = "info";
14078
- args.splice(0, 1);
14079
- } else if (args[0] === "summarize") {
14080
- mode = "summarize";
14081
- args.splice(0, 1);
14118
+ var subcmdIndex = args.findIndex((a2) => a2 === "info" || a2 === "summarize");
14119
+ if (subcmdIndex !== -1) {
14120
+ mode = args[subcmdIndex];
14121
+ args.splice(subcmdIndex, 1);
14082
14122
  }
14083
14123
  var videoId = "";
14084
14124
  var lang;
@@ -14089,6 +14129,7 @@ var outputJson = false;
14089
14129
  var noDecode = false;
14090
14130
  var showDetails = true;
14091
14131
  var noCache = false;
14132
+ var debugMode = false;
14092
14133
  for (let i = 0;i < args.length; i++) {
14093
14134
  const arg = args[i];
14094
14135
  if (!arg)
@@ -14119,6 +14160,8 @@ for (let i = 0;i < args.length; i++) {
14119
14160
  showDetails = false;
14120
14161
  } else if (arg === "--no-cache") {
14121
14162
  noCache = true;
14163
+ } else if (arg === "--debug") {
14164
+ debugMode = true;
14122
14165
  } else if (arg === "--no-decode-entities") {
14123
14166
  noDecode = true;
14124
14167
  } else if (arg.startsWith("-")) {
@@ -14133,6 +14176,19 @@ if (!videoId) {
14133
14176
  console.log(help());
14134
14177
  process.exit(1);
14135
14178
  }
14179
+ var extracted = extractVideoId(videoId);
14180
+ if (!extracted) {
14181
+ console.error("Error: invalid YouTube video URL or ID");
14182
+ process.exit(65);
14183
+ }
14184
+ videoId = extracted;
14185
+ if (debugMode)
14186
+ enableDebug();
14187
+ debug("Config file:", configPath());
14188
+ debug("Video ID:", videoId);
14189
+ debug("Mode:", mode);
14190
+ if (lang)
14191
+ debug("Language:", lang);
14136
14192
  try {
14137
14193
  if (mode === "info") {
14138
14194
  const result = await fetchTranscript(videoId, { videoDetails: true, lang });
@@ -14156,24 +14212,35 @@ try {
14156
14212
  const cachedSegments = await readCache(dir2, "transcript.json");
14157
14213
  const cachedSummary = await readCache(dir2, "summary.md");
14158
14214
  if (cachedSegments && cachedSummary) {
14215
+ debug("Cache hit:", dir2);
14159
14216
  segments2 = JSON.parse(cachedSegments);
14160
14217
  summary = cachedSummary;
14218
+ } else {
14219
+ debug("Cache miss:", dir2);
14161
14220
  }
14221
+ } else {
14222
+ debug("Cache skipped (--no-cache)");
14162
14223
  }
14163
14224
  if (!segments2) {
14225
+ debug("Fetching transcript...");
14164
14226
  segments2 = lang ? await fetchTranscript(videoId, { lang }) : await fetchTranscript(videoId);
14227
+ debug(`Transcript fetched: ${segments2.length} segments`);
14165
14228
  await writeCache(dir2, "transcript.json", JSON.stringify(segments2));
14229
+ debug("Cache written: transcript.json");
14166
14230
  }
14167
14231
  const prompt = config.summarize.prompt ?? "";
14168
14232
  const transcriptText = toText(segments2, !noDecode);
14169
14233
  if (!summary) {
14234
+ debug("Running command:", config.summarize.command);
14170
14235
  summary = await summarize({
14171
14236
  prompt,
14172
14237
  command: config.summarize.command,
14173
14238
  transcript: transcriptText,
14174
14239
  cwd: dir2
14175
14240
  });
14241
+ debug("Command exit: 0");
14176
14242
  await writeCache(dir2, "summary.md", summary);
14243
+ debug("Cache written: summary.md");
14177
14244
  }
14178
14245
  if (outputPath) {
14179
14246
  await writeFile3(outputPath, summary, "utf8");
@@ -14193,10 +14260,17 @@ try {
14193
14260
  let videoDetailsCache = null;
14194
14261
  if (!noCache) {
14195
14262
  const cached = await readCache(dir, "transcript.json");
14196
- if (cached)
14263
+ if (cached) {
14264
+ debug("Cache hit:", dir);
14197
14265
  segments = JSON.parse(cached);
14266
+ } else {
14267
+ debug("Cache miss:", dir);
14268
+ }
14269
+ } else {
14270
+ debug("Cache skipped (--no-cache)");
14198
14271
  }
14199
14272
  if (!segments) {
14273
+ debug("Fetching transcript...");
14200
14274
  if (showDetails && !outputJson) {
14201
14275
  const opts = lang ? { lang, videoDetails: true } : { videoDetails: true };
14202
14276
  const result = await fetchTranscript(videoId, opts);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tacone/prosey",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Download YouTube video transcripts from the CLI",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
package/src/cache.test.ts CHANGED
@@ -2,7 +2,7 @@ import { describe, expect, test, afterEach } from "bun:test";
2
2
  import { rm, readFile } from "node:fs/promises";
3
3
  import { existsSync } from "node:fs";
4
4
  import { join } from "node:path";
5
- import { cacheKey, cacheDir, readCache, writeCache } from "./cache";
5
+ import { cacheKey, cacheDir, readCache, writeCache, extractVideoId } from "./cache";
6
6
 
7
7
  const testDir = "/tmp/prosey/test-cache-spec";
8
8
 
@@ -44,6 +44,34 @@ describe("cacheDir", () => {
44
44
  });
45
45
  });
46
46
 
47
+ describe("extractVideoId", () => {
48
+ test("bare ID passes through", () => {
49
+ expect(extractVideoId("dQw4w9WgXcQ")).toBe("dQw4w9WgXcQ");
50
+ });
51
+
52
+ test("full watch URL", () => {
53
+ expect(extractVideoId("https://www.youtube.com/watch?v=jNAAG3Ma5K8")).toBe("jNAAG3Ma5K8");
54
+ });
55
+
56
+ test("watch URL with extra query params", () => {
57
+ expect(
58
+ extractVideoId("https://www.youtube.com/watch?v=jNAAG3Ma5K8&pp=ygUKc3BhY2V4IGlwbw%3D%3D"),
59
+ ).toBe("jNAAG3Ma5K8");
60
+ });
61
+
62
+ test("short youtu.be URL", () => {
63
+ expect(extractVideoId("https://youtu.be/dQw4w9WgXcQ")).toBe("dQw4w9WgXcQ");
64
+ });
65
+
66
+ test("embed URL", () => {
67
+ expect(extractVideoId("https://www.youtube.com/embed/dQw4w9WgXcQ")).toBe("dQw4w9WgXcQ");
68
+ });
69
+
70
+ test("invalid URL without video ID returns null", () => {
71
+ expect(extractVideoId("https://example.com/search?q=hello")).toBeNull();
72
+ });
73
+ });
74
+
47
75
  describe("readCache / writeCache", () => {
48
76
  test("writes and reads a file", async () => {
49
77
  await writeCache(testDir, "test.txt", "hello world");
package/src/cache.ts CHANGED
@@ -3,6 +3,17 @@ import { readFile, writeFile, mkdir } from "node:fs/promises";
3
3
  import { existsSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
 
6
+ const RE_YOUTUBE =
7
+ /(?:v=|\/|v\/|embed\/|watch\?.*v=|youtu\.be\/|\/v\/|e\/|watch\?.*vi?=|\/embed\/|\/v\/|vi?\/|watch\?.*vi?=|youtu\.be\/|\/vi?\/|\/e\/)([a-zA-Z0-9_-]{11})/i;
8
+ const RE_BARE_ID = /^[a-zA-Z0-9_-]{11}$/;
9
+
10
+ export function extractVideoId(input: string): string | null {
11
+ if (RE_BARE_ID.test(input)) return input;
12
+ const match = input.match(RE_YOUTUBE);
13
+ if (match) return match[1] || null;
14
+ return null;
15
+ }
16
+
6
17
  export interface CacheOptions {
7
18
  lang?: string;
8
19
  timestamps?: boolean;
package/src/config.ts CHANGED
@@ -19,11 +19,25 @@ const FALLBACK_CONFIG_TOML = `# Default prosey configuration
19
19
  # Prompt sent to the command via stdin.
20
20
  # Customize this to change how transcripts are summarized.
21
21
  prompt = """
22
- Summarize the following transcript.
23
- Focus on the key points and main arguments.
22
+ Write a comprehensive summary of the following transcription.
24
23
  """
25
24
 
26
- # Command to execute with the prompt piped via stdin.
25
+ # Command to execute with the prompt and transcript piped via stdin.
26
+ # The transcript is appended to the prompt automatically.
27
+ #
28
+ # Available options:
29
+ #
30
+ # opencode run — full access (default)
31
+ # opencode run --permissions read — read-only (view files, no edits)
32
+ #
33
+ # claude -p "" --print — full access (--print for clean output)
34
+ # claude --permission-mode plan -p "" --print — read-only (plan/read only)
35
+ #
36
+ # copilot -sp "" — full access (-s = silent, -p = prompt)
37
+ # copilot -sp "" --deny-all-tools — read-only (no shell/write access)
38
+ #
39
+ # codex --sandbox default -p "" — full access
40
+ # codex --sandbox read-only -p "" — read-only
27
41
  command = "opencode run"
28
42
  `;
29
43
 
package/src/debug.ts ADDED
@@ -0,0 +1,13 @@
1
+ const GRAY = "\x1b[90m";
2
+ const RESET = "\x1b[0m";
3
+
4
+ let enabled = false;
5
+
6
+ export function enableDebug(): void {
7
+ enabled = true;
8
+ }
9
+
10
+ export function debug(...args: unknown[]): void {
11
+ if (!enabled) return;
12
+ console.error(GRAY, ...args, RESET);
13
+ }
@@ -2,11 +2,15 @@
2
2
  # Created automatically on first run. Edit as needed.
3
3
 
4
4
  [summarize]
5
+
5
6
  # Prompt sent to the command via stdin.
6
7
  # Customize this to change how transcripts are summarized.
8
+
7
9
  prompt = """
8
10
  Write a comprehensive summary of the following transcription.
9
11
  """
10
12
 
11
- # Command to execute with the prompt piped via stdin.
12
- command = "opencode run"
13
+ # Command to execute with the prompt and transcript piped via stdin.
14
+ # The transcript is appended to the prompt automatically.
15
+
16
+ command = "OPENCODE_PERMISSION='{\"read\":\"allow\",\"write\":\"deny\",\"edit\":\"deny\",\"bash\":\"deny\",\"glob\":\"deny\",\"grep\":\"deny\",\"webfetch\":\"deny\",\"task\":\"deny\",\"todowrite\":\"deny\",\"websearch\":\"deny\",\"lsp\":\"deny\"}' opencode run --pure"
package/src/index.ts CHANGED
@@ -4,10 +4,11 @@ import { writeFile } from "node:fs/promises";
4
4
  import { fetchTranscript, listLanguages } from "youtube-transcript-plus";
5
5
  import type { CaptionTrackInfo, VideoDetails, TranscriptSegment } from "youtube-transcript-plus";
6
6
  import { formatWithTimestamps, toText, toJSON, formatDuration, decodeEntities } from "./format";
7
- import { loadConfig, resetConfig } from "./config";
7
+ import { loadConfig, resetConfig, configPath } from "./config";
8
8
  import type { ProseyConfig } from "./config";
9
9
  import { summarize } from "./summarize";
10
- import { cacheDir, readCache, writeCache } from "./cache";
10
+ import { cacheDir, readCache, writeCache, extractVideoId } from "./cache";
11
+ import { enableDebug, debug } from "./debug";
11
12
 
12
13
  const NAME = "prosey";
13
14
  const VERSION = "0.1.0";
@@ -40,6 +41,7 @@ Options:
40
41
  --no-decode-entities Preserve HTML entities (decoded by default).
41
42
  --reset-config Reset config file to defaults and exit.
42
43
  --no-cache Skip cache and overwrite cache files.
44
+ --debug Print debug information to stderr.
43
45
  --help Show this help message.
44
46
  --version Show version.
45
47
 
@@ -138,12 +140,10 @@ if (args.includes("--reset-config")) {
138
140
  const config: ProseyConfig = await loadConfig().catch(() => ({}) as ProseyConfig);
139
141
 
140
142
  let mode = "transcript";
141
- if (args[0] === "info") {
142
- mode = "info";
143
- args.splice(0, 1);
144
- } else if (args[0] === "summarize") {
145
- mode = "summarize";
146
- args.splice(0, 1);
143
+ const subcmdIndex = args.findIndex((a) => a === "info" || a === "summarize");
144
+ if (subcmdIndex !== -1) {
145
+ mode = args[subcmdIndex]!;
146
+ args.splice(subcmdIndex, 1);
147
147
  }
148
148
 
149
149
  let videoId = "";
@@ -155,6 +155,7 @@ let outputJson = false;
155
155
  let noDecode = false;
156
156
  let showDetails = true;
157
157
  let noCache = false;
158
+ let debugMode = false;
158
159
 
159
160
  for (let i = 0; i < args.length; i++) {
160
161
  const arg = args[i];
@@ -185,6 +186,8 @@ for (let i = 0; i < args.length; i++) {
185
186
  showDetails = false;
186
187
  } else if (arg === "--no-cache") {
187
188
  noCache = true;
189
+ } else if (arg === "--debug") {
190
+ debugMode = true;
188
191
  } else if (arg === "--no-decode-entities") {
189
192
  noDecode = true;
190
193
  } else if (arg.startsWith("-")) {
@@ -201,6 +204,21 @@ if (!videoId) {
201
204
  process.exit(1);
202
205
  }
203
206
 
207
+ const extracted = extractVideoId(videoId);
208
+
209
+ if (!extracted) {
210
+ console.error("Error: invalid YouTube video URL or ID");
211
+ process.exit(65);
212
+ }
213
+
214
+ videoId = extracted;
215
+
216
+ if (debugMode) enableDebug();
217
+ debug("Config file:", configPath());
218
+ debug("Video ID:", videoId);
219
+ debug("Mode:", mode);
220
+ if (lang) debug("Language:", lang);
221
+
204
222
  try {
205
223
  if (mode === "info") {
206
224
  const result = await fetchTranscript(videoId, { videoDetails: true, lang } as any);
@@ -227,27 +245,38 @@ try {
227
245
  const cachedSegments = await readCache(dir, "transcript.json");
228
246
  const cachedSummary = await readCache(dir, "summary.md");
229
247
  if (cachedSegments && cachedSummary) {
248
+ debug("Cache hit:", dir);
230
249
  segments = JSON.parse(cachedSegments);
231
250
  summary = cachedSummary;
251
+ } else {
252
+ debug("Cache miss:", dir);
232
253
  }
254
+ } else {
255
+ debug("Cache skipped (--no-cache)");
233
256
  }
234
257
 
235
258
  if (!segments) {
259
+ debug("Fetching transcript...");
236
260
  segments = lang ? await fetchTranscript(videoId, { lang }) : await fetchTranscript(videoId);
261
+ debug(`Transcript fetched: ${segments.length} segments`);
237
262
  await writeCache(dir, "transcript.json", JSON.stringify(segments));
263
+ debug("Cache written: transcript.json");
238
264
  }
239
265
 
240
266
  const prompt = config.summarize.prompt ?? "";
241
267
  const transcriptText = toText(segments, !noDecode);
242
268
 
243
269
  if (!summary) {
270
+ debug("Running command:", config.summarize.command);
244
271
  summary = await summarize({
245
272
  prompt,
246
273
  command: config.summarize.command,
247
274
  transcript: transcriptText,
248
275
  cwd: dir,
249
276
  });
277
+ debug("Command exit: 0");
250
278
  await writeCache(dir, "summary.md", summary);
279
+ debug("Cache written: summary.md");
251
280
  }
252
281
 
253
282
  if (outputPath) {
@@ -270,10 +299,18 @@ try {
270
299
 
271
300
  if (!noCache) {
272
301
  const cached = await readCache(dir, "transcript.json");
273
- if (cached) segments = JSON.parse(cached);
302
+ if (cached) {
303
+ debug("Cache hit:", dir);
304
+ segments = JSON.parse(cached);
305
+ } else {
306
+ debug("Cache miss:", dir);
307
+ }
308
+ } else {
309
+ debug("Cache skipped (--no-cache)");
274
310
  }
275
311
 
276
312
  if (!segments) {
313
+ debug("Fetching transcript...");
277
314
  if (showDetails && !outputJson) {
278
315
  const opts = lang ? { lang, videoDetails: true as const } : { videoDetails: true as const };
279
316
  const result = (await fetchTranscript(videoId, opts)) as {
@@ -2,22 +2,24 @@ import { describe, expect, test } from "bun:test";
2
2
  import { summarize } from "./summarize";
3
3
 
4
4
  describe("summarize", () => {
5
- test("strips input text from output", async () => {
6
- const result = await summarize({
7
- prompt: "Summarize this:",
8
- command: "cat",
9
- transcript: "Hello world. This is the transcript.",
10
- });
11
- expect(result).toBe("");
5
+ test("rejects when command only echoes input", async () => {
6
+ await expect(
7
+ summarize({
8
+ prompt: "Summarize this:",
9
+ command: "cat",
10
+ transcript: "Hello world. This is the transcript.",
11
+ }),
12
+ ).rejects.toThrow("Summarization command returned no meaningful output");
12
13
  });
13
14
 
14
- test("strips input text from output with empty prompt", async () => {
15
- const result = await summarize({
16
- prompt: "",
17
- command: "cat",
18
- transcript: "Just the transcript.",
19
- });
20
- expect(result).toBe("");
15
+ test("rejects when command only echoes input with empty prompt", async () => {
16
+ await expect(
17
+ summarize({
18
+ prompt: "",
19
+ command: "cat",
20
+ transcript: "Just the transcript.",
21
+ }),
22
+ ).rejects.toThrow("Summarization command returned no meaningful output");
21
23
  });
22
24
 
23
25
  test("preserves response text beyond the input", async () => {
package/src/summarize.ts CHANGED
@@ -38,5 +38,14 @@ export async function summarize(options: SummarizeOptions): Promise<string> {
38
38
  const { prompt, command, transcript, cwd } = options;
39
39
  const fullPrompt = `${prompt}\n\n${transcript}`;
40
40
  const output = await executeCommand(command, fullPrompt, cwd);
41
- return output.replace(fullPrompt, "").replace(/\n+$/, "");
41
+
42
+ const cleaned = output.startsWith(fullPrompt)
43
+ ? output.slice(fullPrompt.length).replace(/\n+$/, "")
44
+ : output.replace(/\n+$/, "");
45
+
46
+ if (!cleaned || cleaned === transcript) {
47
+ throw new Error("Summarization command returned no meaningful output");
48
+ }
49
+
50
+ return cleaned;
42
51
  }