feedeas 0.1.0-alpha.13 → 0.1.0-alpha.15

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.
Files changed (3) hide show
  1. package/README.md +13 -2
  2. package/dist/cli/index.js +1022 -331
  3. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -19,7 +19,7 @@ var playwright_installer_exports = {};
19
19
  __export(playwright_installer_exports, {
20
20
  ensurePlaywrightBrowsers: () => ensurePlaywrightBrowsers
21
21
  });
22
- import { spawn as spawn2 } from "child_process";
22
+ import { spawn as spawn3 } from "child_process";
23
23
  import { createInterface } from "readline";
24
24
  async function ensurePlaywrightBrowsers() {
25
25
  try {
@@ -71,7 +71,7 @@ function promptUser(question) {
71
71
  }
72
72
  function installPlaywrightBrowsers() {
73
73
  return new Promise((resolve) => {
74
- const child = spawn2("npx", ["playwright", "install", "chromium", "--with-deps"], {
74
+ const child = spawn3("npx", ["playwright", "install", "chromium", "--with-deps"], {
75
75
  stdio: "inherit",
76
76
  // Show installation progress to user
77
77
  shell: true
@@ -97,10 +97,10 @@ import { Command as Command14 } from "commander";
97
97
  // src/cli/commands/record.ts
98
98
  import { Command } from "commander";
99
99
  import { chromium } from "playwright-core";
100
- import { spawn as spawn3, spawnSync } from "child_process";
101
- import fs4 from "fs";
102
- import path4 from "path";
103
- import { fileURLToPath as fileURLToPath2 } from "url";
100
+ import { spawn as spawn4, spawnSync } from "child_process";
101
+ import fs5 from "fs";
102
+ import path6 from "path";
103
+ import { fileURLToPath as fileURLToPath3 } from "url";
104
104
 
105
105
  // src/cli/services/ffprobe.ts
106
106
  import { spawn } from "child_process";
@@ -256,9 +256,92 @@ import { readdir, readFile, writeFile, mkdir } from "node:fs/promises";
256
256
  import { join, dirname } from "node:path";
257
257
  import { existsSync } from "node:fs";
258
258
 
259
+ // src/cli/server/cli-runner.ts
260
+ import { spawn as spawn2 } from "child_process";
261
+ import path2 from "path";
262
+ import fs2 from "fs";
263
+ import { fileURLToPath } from "url";
264
+ var __filename = fileURLToPath(import.meta.url);
265
+ var __dirname = path2.dirname(__filename);
266
+ async function runCliCommand(args, cwd) {
267
+ const isCompiled = __filename.endsWith(".js");
268
+ let command;
269
+ let spawnArgs;
270
+ if (isCompiled) {
271
+ const binPath = path2.resolve(__dirname, "../../../bin/feedeas.js");
272
+ if (!fs2.existsSync(binPath)) {
273
+ throw new Error(`Could not find compiled CLI binary at ${binPath}`);
274
+ }
275
+ command = "node";
276
+ spawnArgs = [binPath, ...args];
277
+ } else {
278
+ const srcPath = path2.resolve(__dirname, "../index.ts");
279
+ command = "bun";
280
+ spawnArgs = ["run", srcPath, ...args];
281
+ }
282
+ return new Promise((resolve, reject) => {
283
+ const child = spawn2(command, spawnArgs, {
284
+ cwd,
285
+ stdio: ["ignore", "pipe", "pipe"],
286
+ env: {
287
+ ...process.env,
288
+ FORCE_COLOR: "0"
289
+ // Disable colored output for easier JSON parsing
290
+ }
291
+ });
292
+ let stdoutData = "";
293
+ let stderrData = "";
294
+ child.stdout.on("data", (chunk) => {
295
+ stdoutData += chunk.toString();
296
+ });
297
+ child.stderr.on("data", (chunk) => {
298
+ stderrData += chunk.toString();
299
+ });
300
+ child.on("close", (code) => {
301
+ let parsedJson = null;
302
+ try {
303
+ const lines = stdoutData.split("\n");
304
+ for (let i = lines.length - 1; i >= 0; i--) {
305
+ const line = lines[i].trim();
306
+ if (line.startsWith("{") || line.startsWith("[")) {
307
+ try {
308
+ parsedJson = JSON.parse(line);
309
+ break;
310
+ } catch {
311
+ const block = lines.slice(i).join("\n");
312
+ try {
313
+ parsedJson = JSON.parse(block);
314
+ break;
315
+ } catch {
316
+ }
317
+ }
318
+ }
319
+ }
320
+ } catch (e) {
321
+ }
322
+ if (code === 0) {
323
+ if (parsedJson) {
324
+ resolve(parsedJson);
325
+ } else {
326
+ resolve({ rawOutput: stdoutData.trim() });
327
+ }
328
+ } else {
329
+ if (parsedJson && parsedJson.error) {
330
+ reject(new Error(parsedJson.error));
331
+ } else {
332
+ reject(new Error(`CLI command failed with code ${code}:
333
+ ${stderrData || stdoutData}`));
334
+ }
335
+ }
336
+ });
337
+ child.on("error", (err) => {
338
+ reject(new Error(`Failed to spawn CLI process: ${err.message}`));
339
+ });
340
+ });
341
+ }
342
+
259
343
  // src/cli/services/taste.ts
260
- import fs2 from "node:fs";
261
- import path2 from "node:path";
344
+ import path3 from "node:path";
262
345
  var DEFAULT_TASTE_FILE_CONTENT = `# Taste File v1
263
346
  ## Brand Identity
264
347
  Describe brand voice, tone, and core promise.
@@ -332,28 +415,16 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
332
415
  ]);
333
416
  function safeResolveFromCwd(relativePath) {
334
417
  const cwd = process.cwd();
335
- const normalized = path2.normalize(relativePath);
418
+ const normalized = path3.normalize(relativePath);
336
419
  if (normalized.includes("..")) {
337
420
  throw new Error("Invalid path");
338
421
  }
339
- const fullPath = path2.resolve(cwd, normalized);
340
- if (fullPath !== cwd && !fullPath.startsWith(`${cwd}${path2.sep}`)) {
422
+ const fullPath = path3.resolve(cwd, normalized);
423
+ if (fullPath !== cwd && !fullPath.startsWith(`${cwd}${path3.sep}`)) {
341
424
  throw new Error("Invalid path");
342
425
  }
343
426
  return fullPath;
344
427
  }
345
- function ensureTasteWorkspaceFiles(tasteFilePath, memoryFilePath) {
346
- const tasteFull = safeResolveFromCwd(tasteFilePath);
347
- const memoryFull = safeResolveFromCwd(memoryFilePath);
348
- fs2.mkdirSync(path2.dirname(tasteFull), { recursive: true });
349
- fs2.mkdirSync(path2.dirname(memoryFull), { recursive: true });
350
- if (!fs2.existsSync(tasteFull)) {
351
- fs2.writeFileSync(tasteFull, DEFAULT_TASTE_FILE_CONTENT, "utf-8");
352
- }
353
- if (!fs2.existsSync(memoryFull)) {
354
- fs2.writeFileSync(memoryFull, DEFAULT_MEMORY_FILE_CONTENT, "utf-8");
355
- }
356
- }
357
428
  function parseListValue(value) {
358
429
  const trimmed = value.trim();
359
430
  if (!trimmed) return [];
@@ -414,7 +485,10 @@ function parseTasteMemoryMarkdown(markdown) {
414
485
  tags: parseListValue(map.tags || ""),
415
486
  freshnessTerms: parseListValue(map.freshness_terms || ""),
416
487
  summary: map.summary || "",
417
- content: map.content || ""
488
+ content: map.content || "",
489
+ status: map.status || void 0,
490
+ reasonTags: parseListValue(map.reason_tags || ""),
491
+ notes: map.notes || ""
418
492
  });
419
493
  }
420
494
  return entries;
@@ -432,30 +506,21 @@ function formatMemoryEntry(entry, id) {
432
506
  `tags: [${(entry.tags || []).join(", ")}]`,
433
507
  `freshness_terms: [${(entry.freshnessTerms || []).join(", ")}]`,
434
508
  `summary: ${entry.summary || ""}`,
509
+ ...entry.status ? [`status: ${entry.status}`] : [],
510
+ ...entry.reasonTags && entry.reasonTags.length ? [`reason_tags: [${entry.reasonTags.join(", ")}]`] : [],
511
+ ...entry.notes ? [`notes: ${entry.notes}`] : [],
435
512
  "content: |",
436
513
  contentLines || " ",
437
514
  "```",
438
515
  ""
439
516
  ].join("\n");
440
517
  }
441
- function appendMemoryEntries(markdown, ideas) {
442
- const base = markdown.trim().length > 0 ? markdown.trimEnd() : DEFAULT_MEMORY_FILE_CONTENT.trimEnd();
443
- const appendedIds = [];
444
- let output = `${base}
445
-
446
- `;
447
- for (const idea of ideas) {
448
- const id = `mem_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}_${Math.floor(Math.random() * 900 + 100)}_${appendedIds.length + 1}`;
449
- appendedIds.push(id);
450
- output += formatMemoryEntry(idea, id);
451
- }
452
- return { markdown: output, appendedIds };
453
- }
454
518
  function queryMemoryItems(items, options) {
455
519
  const limit = Math.max(1, options.limit ?? 20);
456
520
  const sinceDate = options.since ? new Date(options.since) : null;
457
521
  const text = options.text?.toLowerCase().trim();
458
522
  const tag = options.tag?.toLowerCase().trim();
523
+ const status = options.status?.toLowerCase().trim();
459
524
  return items.filter((item) => {
460
525
  if (sinceDate && !Number.isNaN(sinceDate.getTime())) {
461
526
  const createdAt = new Date(item.createdAt);
@@ -464,6 +529,9 @@ function queryMemoryItems(items, options) {
464
529
  if (tag && !item.tags.map((t) => t.toLowerCase()).includes(tag)) {
465
530
  return false;
466
531
  }
532
+ if (status && (item.status || "").toLowerCase() !== status) {
533
+ return false;
534
+ }
467
535
  if (text) {
468
536
  const hay = `${item.title}
469
537
  ${item.summary}
@@ -473,6 +541,71 @@ ${item.content}`.toLowerCase();
473
541
  return true;
474
542
  }).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()).slice(0, limit);
475
543
  }
544
+ function upsertFeedbackSection(tasteContent, sectionContent) {
545
+ const start = "<!-- taste-feedback:start -->";
546
+ const end = "<!-- taste-feedback:end -->";
547
+ const block = `${start}
548
+ ${sectionContent.trim()}
549
+ ${end}`;
550
+ const hasStart = tasteContent.includes(start);
551
+ const hasEnd = tasteContent.includes(end);
552
+ if (hasStart && hasEnd) {
553
+ return tasteContent.replace(new RegExp(`${start}[\\s\\S]*?${end}`, "m"), block);
554
+ }
555
+ const trimmed = tasteContent.trimEnd();
556
+ return `${trimmed}
557
+
558
+ ${block}
559
+ `;
560
+ }
561
+ function buildTasteSuggestionFromMemory(tasteContent, memories) {
562
+ const recent = [...memories].sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()).slice(0, 50);
563
+ const acceptedTags = /* @__PURE__ */ new Map();
564
+ const rejectedReasons = /* @__PURE__ */ new Map();
565
+ for (const item of recent) {
566
+ const status = (item.status || "").toLowerCase();
567
+ if (status === "accepted") {
568
+ for (const tag of item.tags) {
569
+ acceptedTags.set(tag, (acceptedTags.get(tag) || 0) + 1);
570
+ }
571
+ }
572
+ if (status === "rejected") {
573
+ const reasons = item.reasonTags && item.reasonTags.length > 0 ? item.reasonTags : item.tags;
574
+ for (const reason of reasons) {
575
+ rejectedReasons.set(reason, (rejectedReasons.get(reason) || 0) + 1);
576
+ }
577
+ }
578
+ }
579
+ const topAccepted = [...acceptedTags.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([name]) => name);
580
+ const topRejected = [...rejectedReasons.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([name]) => name);
581
+ const sectionLines = [
582
+ "## Feedback Signals",
583
+ ...topAccepted.length ? [`- Prioritize these themes: ${topAccepted.join(", ")}`] : ["- Prioritize themes with proven audience resonance from recent accepted ideas."],
584
+ ...topRejected.length ? [`- Avoid these recurring issues: ${topRejected.join(", ")}`] : ["- Avoid repeating patterns that were consistently rejected."],
585
+ "- Keep hooks specific and concrete, not generic."
586
+ ];
587
+ const proposedTasteContent = upsertFeedbackSection(tasteContent, sectionLines.join("\n"));
588
+ const patch = [
589
+ "--- taste/taste.md",
590
+ "+++ taste/taste.md",
591
+ "@@",
592
+ `+ ${sectionLines[0]}`,
593
+ `+ ${sectionLines[1]}`,
594
+ `+ ${sectionLines[2]}`,
595
+ `+ ${sectionLines[3]}`
596
+ ].join("\n");
597
+ return {
598
+ title: "Feedback-driven taste refinement",
599
+ summary: "Update taste profile with recurring accepted/rejected memory patterns.",
600
+ rationale: [
601
+ `Analyzed ${recent.length} recent memory entries.`,
602
+ topAccepted.length ? `Accepted patterns concentrated around: ${topAccepted.join(", ")}.` : "No strong accepted-tag pattern found; using generic prioritization guidance.",
603
+ topRejected.length ? `Rejected patterns concentrated around: ${topRejected.join(", ")}.` : "No strong rejected-tag pattern found; using generic avoidance guidance."
604
+ ].join("\n"),
605
+ patch,
606
+ proposedTasteContent
607
+ };
608
+ }
476
609
  function tokenize(input) {
477
610
  return new Set(
478
611
  input.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).map((word) => word.trim()).filter((word) => word.length > 3 && !STOP_WORDS.has(word))
@@ -667,9 +800,298 @@ async function simulateIdeas(params) {
667
800
  };
668
801
  }
669
802
 
803
+ // src/cli/services/taste-store.ts
804
+ import fs3 from "node:fs";
805
+ import path4 from "node:path";
806
+ import { createHash } from "node:crypto";
807
+ var DEFAULT_SUGGESTIONS_FILE_CONTENT = "# Taste Suggestions v1\n";
808
+ function hashContent(content) {
809
+ return createHash("sha256").update(content).digest("hex");
810
+ }
811
+ function buildId(prefix) {
812
+ return `${prefix}_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}_${Math.floor(Math.random() * 9e5 + 1e5)}`;
813
+ }
814
+ function formatMultilineField(name, value) {
815
+ const lines = (value || "").split("\n");
816
+ return [
817
+ `${name}: |`,
818
+ ...(lines.length ? lines : [""]).map((line) => ` ${line}`)
819
+ ];
820
+ }
821
+ function parseSuggestionMarkdown(markdown) {
822
+ const blockRegex = /```taste-suggestion\s*([\s\S]*?)```/g;
823
+ const entries = [];
824
+ let match;
825
+ while ((match = blockRegex.exec(markdown)) !== null) {
826
+ const block = match[1].trim();
827
+ const lines = block.split("\n");
828
+ const map = {};
829
+ let idx = 0;
830
+ while (idx < lines.length) {
831
+ const line = lines[idx];
832
+ const keyMatch = line.match(/^([a-z_]+):\s*(.*)$/i);
833
+ if (!keyMatch) {
834
+ idx += 1;
835
+ continue;
836
+ }
837
+ const key = keyMatch[1];
838
+ const value = keyMatch[2] ?? "";
839
+ if ((key === "rationale" || key === "patch" || key === "proposed_taste_content") && value.trim() === "|") {
840
+ const contentLines = [];
841
+ idx += 1;
842
+ while (idx < lines.length) {
843
+ const contentLine = lines[idx];
844
+ if (contentLine.startsWith(" ")) {
845
+ contentLines.push(contentLine.slice(2));
846
+ idx += 1;
847
+ continue;
848
+ }
849
+ if (contentLine.trim() === "") {
850
+ contentLines.push("");
851
+ idx += 1;
852
+ continue;
853
+ }
854
+ break;
855
+ }
856
+ map[key] = contentLines.join("\n").trimEnd();
857
+ continue;
858
+ }
859
+ map[key] = value.trim();
860
+ idx += 1;
861
+ }
862
+ if (!map.id || !map.created_at || !map.title) {
863
+ continue;
864
+ }
865
+ const status = map.status || "pending";
866
+ entries.push({
867
+ id: map.id,
868
+ createdAt: map.created_at,
869
+ status: status === "applied" || status === "rejected" ? status : "pending",
870
+ title: map.title,
871
+ summary: map.summary || "",
872
+ rationale: map.rationale || "",
873
+ patch: map.patch || "",
874
+ proposedTasteContent: map.proposed_taste_content || "",
875
+ baseVersion: map.base_version || void 0,
876
+ appliedAt: map.applied_at || void 0,
877
+ rejectedAt: map.rejected_at || void 0,
878
+ reviewNote: map.review_note || void 0
879
+ });
880
+ }
881
+ return entries;
882
+ }
883
+ function formatSuggestionEntry(entry) {
884
+ const output = [
885
+ "```taste-suggestion",
886
+ `id: ${entry.id}`,
887
+ `created_at: ${entry.createdAt}`,
888
+ `status: ${entry.status}`,
889
+ `title: ${entry.title}`,
890
+ `summary: ${entry.summary || ""}`,
891
+ ...entry.baseVersion ? [`base_version: ${entry.baseVersion}`] : [],
892
+ ...formatMultilineField("rationale", entry.rationale),
893
+ ...formatMultilineField("patch", entry.patch),
894
+ ...formatMultilineField("proposed_taste_content", entry.proposedTasteContent),
895
+ ...entry.appliedAt ? [`applied_at: ${entry.appliedAt}`] : [],
896
+ ...entry.rejectedAt ? [`rejected_at: ${entry.rejectedAt}`] : [],
897
+ ...entry.reviewNote ? [`review_note: ${entry.reviewNote}`] : [],
898
+ "```",
899
+ ""
900
+ ];
901
+ return output.join("\n");
902
+ }
903
+ function formatSuggestionsMarkdown(entries) {
904
+ const base = DEFAULT_SUGGESTIONS_FILE_CONTENT.trimEnd();
905
+ if (entries.length === 0) {
906
+ return `${base}
907
+ `;
908
+ }
909
+ const blocks = entries.map((entry) => formatSuggestionEntry(entry)).join("");
910
+ return `${base}
911
+
912
+ ${blocks}`;
913
+ }
914
+ var MarkdownTasteStore = class {
915
+ constructor(paths) {
916
+ this.paths = paths;
917
+ }
918
+ backend = "markdown";
919
+ resolveAll() {
920
+ return {
921
+ tastePath: safeResolveFromCwd(this.paths.tasteFilePath),
922
+ memoryPath: safeResolveFromCwd(this.paths.memoryFilePath),
923
+ suggestionsPath: safeResolveFromCwd(this.paths.suggestionsFilePath)
924
+ };
925
+ }
926
+ async ensureWorkspace() {
927
+ const { tastePath, memoryPath, suggestionsPath } = this.resolveAll();
928
+ fs3.mkdirSync(path4.dirname(tastePath), { recursive: true });
929
+ fs3.mkdirSync(path4.dirname(memoryPath), { recursive: true });
930
+ fs3.mkdirSync(path4.dirname(suggestionsPath), { recursive: true });
931
+ if (!fs3.existsSync(tastePath)) {
932
+ fs3.writeFileSync(tastePath, DEFAULT_TASTE_FILE_CONTENT, "utf-8");
933
+ }
934
+ if (!fs3.existsSync(memoryPath)) {
935
+ fs3.writeFileSync(memoryPath, DEFAULT_MEMORY_FILE_CONTENT, "utf-8");
936
+ }
937
+ if (!fs3.existsSync(suggestionsPath)) {
938
+ fs3.writeFileSync(suggestionsPath, DEFAULT_SUGGESTIONS_FILE_CONTENT, "utf-8");
939
+ }
940
+ }
941
+ async readTaste() {
942
+ await this.ensureWorkspace();
943
+ const { tastePath } = this.resolveAll();
944
+ const content = fs3.readFileSync(tastePath, "utf-8");
945
+ return { content, version: hashContent(content) };
946
+ }
947
+ async writeTaste(content, expectedVersion) {
948
+ await this.ensureWorkspace();
949
+ const { tastePath } = this.resolveAll();
950
+ const current = fs3.existsSync(tastePath) ? fs3.readFileSync(tastePath, "utf-8") : "";
951
+ const currentVersion = hashContent(current);
952
+ if (expectedVersion && expectedVersion !== currentVersion) {
953
+ throw new Error("Taste file changed since proposal creation. Re-run suggest before apply.");
954
+ }
955
+ fs3.writeFileSync(tastePath, content, "utf-8");
956
+ return { version: hashContent(content) };
957
+ }
958
+ async appendMemory(entry) {
959
+ await this.ensureWorkspace();
960
+ const { memoryPath } = this.resolveAll();
961
+ const current = fs3.existsSync(memoryPath) ? fs3.readFileSync(memoryPath, "utf-8") : "";
962
+ const id = buildId("mem");
963
+ const markdown = `${(current || DEFAULT_MEMORY_FILE_CONTENT).trimEnd()}
964
+
965
+ ${formatMemoryEntry(entry, id)}`;
966
+ fs3.writeFileSync(memoryPath, markdown, "utf-8");
967
+ const parsed = parseTasteMemoryMarkdown(markdown).find((item) => item.id === id);
968
+ if (!parsed) {
969
+ throw new Error("Failed to append memory entry");
970
+ }
971
+ return parsed;
972
+ }
973
+ async queryMemory(options) {
974
+ await this.ensureWorkspace();
975
+ const { memoryPath } = this.resolveAll();
976
+ const content = fs3.existsSync(memoryPath) ? fs3.readFileSync(memoryPath, "utf-8") : "";
977
+ const items = parseTasteMemoryMarkdown(content);
978
+ return queryMemoryItems(items, options);
979
+ }
980
+ async appendSuggestion(suggestion) {
981
+ await this.ensureWorkspace();
982
+ const { suggestionsPath } = this.resolveAll();
983
+ const current = fs3.existsSync(suggestionsPath) ? fs3.readFileSync(suggestionsPath, "utf-8") : "";
984
+ const entry = {
985
+ id: buildId("sug"),
986
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
987
+ status: "pending",
988
+ title: suggestion.title,
989
+ summary: suggestion.summary,
990
+ rationale: suggestion.rationale,
991
+ patch: suggestion.patch,
992
+ proposedTasteContent: suggestion.proposedTasteContent,
993
+ baseVersion: suggestion.baseVersion,
994
+ appliedAt: void 0,
995
+ rejectedAt: void 0,
996
+ reviewNote: void 0
997
+ };
998
+ const existing = parseSuggestionMarkdown(current);
999
+ const markdown = formatSuggestionsMarkdown([...existing, entry]);
1000
+ fs3.writeFileSync(suggestionsPath, markdown, "utf-8");
1001
+ return entry;
1002
+ }
1003
+ async listSuggestions(status) {
1004
+ await this.ensureWorkspace();
1005
+ const { suggestionsPath } = this.resolveAll();
1006
+ const current = fs3.existsSync(suggestionsPath) ? fs3.readFileSync(suggestionsPath, "utf-8") : "";
1007
+ const entries = parseSuggestionMarkdown(current).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
1008
+ if (!status) {
1009
+ return entries;
1010
+ }
1011
+ return entries.filter((item) => item.status === status);
1012
+ }
1013
+ async updateSuggestionStatus(id, status, metadata) {
1014
+ await this.ensureWorkspace();
1015
+ const { suggestionsPath } = this.resolveAll();
1016
+ const current = fs3.existsSync(suggestionsPath) ? fs3.readFileSync(suggestionsPath, "utf-8") : "";
1017
+ const entries = parseSuggestionMarkdown(current);
1018
+ const idx = entries.findIndex((entry) => entry.id === id);
1019
+ if (idx === -1) {
1020
+ throw new Error(`Suggestion not found: ${id}`);
1021
+ }
1022
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1023
+ const updated = {
1024
+ ...entries[idx],
1025
+ status,
1026
+ appliedAt: status === "applied" ? metadata?.appliedAt || now : void 0,
1027
+ rejectedAt: status === "rejected" ? metadata?.rejectedAt || now : void 0,
1028
+ reviewNote: metadata?.reviewNote
1029
+ };
1030
+ entries[idx] = updated;
1031
+ fs3.writeFileSync(suggestionsPath, formatSuggestionsMarkdown(entries), "utf-8");
1032
+ return updated;
1033
+ }
1034
+ };
1035
+ var DbTasteStore = class {
1036
+ backend = "db";
1037
+ constructor(_) {
1038
+ }
1039
+ async ensureWorkspace() {
1040
+ throw new Error("DB backend is not implemented yet. Use --storage markdown.");
1041
+ }
1042
+ async readTaste() {
1043
+ throw new Error("DB backend is not implemented yet. Use --storage markdown.");
1044
+ }
1045
+ async writeTaste(_, __) {
1046
+ throw new Error("DB backend is not implemented yet. Use --storage markdown.");
1047
+ }
1048
+ async appendMemory(_) {
1049
+ throw new Error("DB backend is not implemented yet. Use --storage markdown.");
1050
+ }
1051
+ async queryMemory(_) {
1052
+ throw new Error("DB backend is not implemented yet. Use --storage markdown.");
1053
+ }
1054
+ async appendSuggestion(_) {
1055
+ throw new Error("DB backend is not implemented yet. Use --storage markdown.");
1056
+ }
1057
+ async listSuggestions(_) {
1058
+ throw new Error("DB backend is not implemented yet. Use --storage markdown.");
1059
+ }
1060
+ async updateSuggestionStatus(_, __, ___) {
1061
+ throw new Error("DB backend is not implemented yet. Use --storage markdown.");
1062
+ }
1063
+ };
1064
+ function normalizeStorageBackend(value) {
1065
+ const lower = (value || process.env.TASTE_STORAGE_BACKEND || "markdown").toLowerCase();
1066
+ if (lower === "db") {
1067
+ return "db";
1068
+ }
1069
+ return "markdown";
1070
+ }
1071
+ function createTasteStore(params) {
1072
+ const backend = normalizeStorageBackend(params.backend);
1073
+ const paths = {
1074
+ tasteFilePath: params.tasteFilePath,
1075
+ memoryFilePath: params.memoryFilePath,
1076
+ suggestionsFilePath: params.suggestionsFilePath
1077
+ };
1078
+ if (backend === "db") {
1079
+ return new DbTasteStore(paths);
1080
+ }
1081
+ return new MarkdownTasteStore(paths);
1082
+ }
1083
+
670
1084
  // src/cli/server/api.ts
671
1085
  var api = new Hono();
672
1086
  var resolveSafePath = (relativePath) => safeResolveFromCwd(relativePath);
1087
+ function createStoreFromBody(body) {
1088
+ return createTasteStore({
1089
+ backend: body.storage ? String(body.storage) : void 0,
1090
+ tasteFilePath: body.tasteFilePath ? String(body.tasteFilePath) : "taste/taste.md",
1091
+ memoryFilePath: body.memoryFilePath ? String(body.memoryFilePath) : "taste/memory.md",
1092
+ suggestionsFilePath: body.suggestionsFilePath ? String(body.suggestionsFilePath) : "taste/suggestions.md"
1093
+ });
1094
+ }
673
1095
  api.get("/fs/list", async (c) => {
674
1096
  const cwd = process.cwd();
675
1097
  try {
@@ -678,7 +1100,6 @@ api.get("/fs/list", async (c) => {
678
1100
  name: f.name,
679
1101
  isDirectory: f.isDirectory(),
680
1102
  size: 0
681
- // Simplification for now
682
1103
  }));
683
1104
  return c.json({ path: cwd, files: result });
684
1105
  } catch (e) {
@@ -686,35 +1107,35 @@ api.get("/fs/list", async (c) => {
686
1107
  }
687
1108
  });
688
1109
  api.get("/fs/read", async (c) => {
689
- const path17 = c.req.query("path");
690
- if (!path17) return c.json({ error: "Path required" }, 400);
1110
+ const path19 = c.req.query("path");
1111
+ if (!path19) return c.json({ error: "Path required" }, 400);
691
1112
  try {
692
- const fullPath = resolveSafePath(path17);
1113
+ const fullPath = resolveSafePath(path19);
693
1114
  const content = await readFile(fullPath, "utf-8");
694
- if (path17.endsWith(".json")) {
1115
+ if (path19.endsWith(".json")) {
695
1116
  try {
696
1117
  const json = JSON.parse(content);
697
1118
  if (json.meta && Array.isArray(json.entities)) {
698
- console.debug(`[API] Resolving scene: ${path17}`);
1119
+ console.debug(`[API] Resolving scene: ${path19}`);
699
1120
  const resolved = await SceneResolver.resolve(json, process.cwd());
700
1121
  return c.json({ content: JSON.stringify(resolved, null, 2) });
701
1122
  }
702
1123
  } catch (e) {
703
- console.error(`[API] Scene resolution failed for ${path17}:`, e.message);
1124
+ console.error(`[API] Scene resolution failed for ${path19}:`, e.message);
704
1125
  }
705
1126
  }
706
1127
  return c.json({ content });
707
1128
  } catch (e) {
708
- console.error(`[API] Error reading file ${path17}:`, e.message, e.stack);
1129
+ console.error(`[API] Error reading file ${path19}:`, e.message, e.stack);
709
1130
  return c.json({ error: e.message }, 500);
710
1131
  }
711
1132
  });
712
1133
  api.post("/fs/write", async (c) => {
713
1134
  const body = await c.req.json();
714
- const { path: path17, content } = body;
715
- if (!path17 || content === void 0) return c.json({ error: "Path and content required" }, 400);
1135
+ const { path: path19, content } = body;
1136
+ if (!path19 || content === void 0) return c.json({ error: "Path and content required" }, 400);
716
1137
  try {
717
- const fullPath = resolveSafePath(path17);
1138
+ const fullPath = resolveSafePath(path19);
718
1139
  const dir = dirname(fullPath);
719
1140
  if (!existsSync(dir)) {
720
1141
  await mkdir(dir, { recursive: true });
@@ -728,7 +1149,7 @@ api.post("/fs/write", async (c) => {
728
1149
  api.post("/fs/upload", async (c) => {
729
1150
  try {
730
1151
  const body = await c.req.parseBody();
731
- const file = body["file"];
1152
+ const file = body.file;
732
1153
  if (!file || !(file instanceof File)) {
733
1154
  return c.json({ error: "File required" }, 400);
734
1155
  }
@@ -736,7 +1157,7 @@ api.post("/fs/upload", async (c) => {
736
1157
  if (!existsSync(assetsDir)) {
737
1158
  await mkdir(assetsDir, { recursive: true });
738
1159
  }
739
- const fileName = body["name"] || file.name;
1160
+ const fileName = body.name || file.name;
740
1161
  const filePath = join(assetsDir, fileName);
741
1162
  const bytes = Buffer.from(await file.arrayBuffer());
742
1163
  await writeFile(filePath, bytes);
@@ -744,7 +1165,6 @@ api.post("/fs/upload", async (c) => {
744
1165
  success: true,
745
1166
  path: `assets/${fileName}`,
746
1167
  url: `/api/fs/assets/assets/${fileName}`
747
- // Adjust URL based on routing
748
1168
  });
749
1169
  } catch (e) {
750
1170
  console.error(e);
@@ -752,10 +1172,10 @@ api.post("/fs/upload", async (c) => {
752
1172
  }
753
1173
  });
754
1174
  api.get("/fs/assets/*", async (c) => {
755
- const path17 = c.req.path;
1175
+ const reqPath = c.req.path;
756
1176
  const marker = "/fs/assets/";
757
- const index = path17.lastIndexOf(marker);
758
- const relativePath = path17.substring(index + marker.length);
1177
+ const index = reqPath.lastIndexOf(marker);
1178
+ const relativePath = reqPath.substring(index + marker.length);
759
1179
  if (relativePath.includes("..")) {
760
1180
  console.error(`[Asset Server] Blocked directory traversal attempt: ${relativePath}`);
761
1181
  return c.json({ error: "Invalid path" }, 403);
@@ -773,22 +1193,21 @@ api.get("/fs/assets/*", async (c) => {
773
1193
  return c.json({ error: "Invalid path" }, 403);
774
1194
  }
775
1195
  }
776
- console.log(`[Asset Server] Request: ${c.req.path} -> Resolved: ${fullPath} (exists: ${existsSync(fullPath)})`);
777
1196
  if (!existsSync(fullPath)) {
778
1197
  console.error(`[Asset Server] File not found: ${fullPath}`);
779
1198
  return c.notFound();
780
1199
  }
781
1200
  const ext = fullPath.split(".").pop()?.toLowerCase();
782
1201
  const mimeTypes = {
783
- "png": "image/png",
784
- "jpg": "image/jpeg",
785
- "jpeg": "image/jpeg",
786
- "gif": "image/gif",
787
- "webp": "image/webp",
788
- "svg": "image/svg+xml",
789
- "mp3": "audio/mpeg",
790
- "mp4": "video/mp4",
791
- "webm": "video/webm"
1202
+ png: "image/png",
1203
+ jpg: "image/jpeg",
1204
+ jpeg: "image/jpeg",
1205
+ gif: "image/gif",
1206
+ webp: "image/webp",
1207
+ svg: "image/svg+xml",
1208
+ mp3: "audio/mpeg",
1209
+ mp4: "video/mp4",
1210
+ webm: "video/webm"
792
1211
  };
793
1212
  const mimeType = mimeTypes[ext || ""] || "application/octet-stream";
794
1213
  try {
@@ -808,18 +1227,17 @@ api.post("/taste/chat", async (c) => {
808
1227
  try {
809
1228
  const body = await c.req.json();
810
1229
  const message = String(body.message || "").trim();
811
- const tasteFilePath = String(body.tasteFilePath || "taste/taste.md");
812
- const memoryFilePath = String(body.memoryFilePath || "taste/memory.md");
813
1230
  const model = body.model ? String(body.model) : void 0;
814
1231
  const apiKey = body.apiKey || process.env.GEMINI_API_KEY;
815
1232
  if (!message) return c.json({ error: "message is required" }, 400);
816
1233
  if (!apiKey) return c.json({ error: "Missing GEMINI_API_KEY" }, 400);
817
- const tasteContent = body.tasteContent ? String(body.tasteContent) : await readFile(resolveSafePath(tasteFilePath), "utf-8");
818
- const memoryContent = body.memoryContent ? String(body.memoryContent) : await readFile(resolveSafePath(memoryFilePath), "utf-8");
819
- const memories = parseTasteMemoryMarkdown(memoryContent);
1234
+ const store = createStoreFromBody(body);
1235
+ await store.ensureWorkspace();
1236
+ const taste = await store.readTaste();
1237
+ const memories = body.memoryContent ? parseTasteMemoryMarkdown(String(body.memoryContent)).slice(0, 40) : await store.queryMemory({ limit: 40 });
820
1238
  const result = await runTasteChat({
821
1239
  message,
822
- tasteContent,
1240
+ tasteContent: body.tasteContent ? String(body.tasteContent) : taste.content,
823
1241
  memories,
824
1242
  apiKey: String(apiKey),
825
1243
  model
@@ -832,20 +1250,18 @@ api.post("/taste/chat", async (c) => {
832
1250
  api.post("/taste/simulate", async (c) => {
833
1251
  try {
834
1252
  const body = await c.req.json();
835
- const tasteFilePath = String(body.tasteFilePath || "taste/taste.md");
836
- const memoryFilePath = String(body.memoryFilePath || "taste/memory.md");
837
1253
  const mode = body.mode === "parallel" ? "parallel" : "sequential";
838
1254
  const count = Math.max(1, Number(body.count || 1));
839
1255
  const saveToMemory = Boolean(body.saveToMemory);
840
1256
  const model = body.model ? String(body.model) : void 0;
841
1257
  const apiKey = body.apiKey || process.env.GEMINI_API_KEY;
842
1258
  if (!apiKey) return c.json({ error: "Missing GEMINI_API_KEY" }, 400);
843
- const tasteContent = body.tasteContent ? String(body.tasteContent) : await readFile(resolveSafePath(tasteFilePath), "utf-8");
844
- const memoryPath = resolveSafePath(memoryFilePath);
845
- const memoryContent = body.memoryContent ? String(body.memoryContent) : await readFile(memoryPath, "utf-8");
846
- const memories = parseTasteMemoryMarkdown(memoryContent);
1259
+ const store = createStoreFromBody(body);
1260
+ await store.ensureWorkspace();
1261
+ const taste = await store.readTaste();
1262
+ const memories = body.memoryContent ? parseTasteMemoryMarkdown(String(body.memoryContent)).slice(0, 40) : await store.queryMemory({ limit: 40 });
847
1263
  const result = await simulateIdeas({
848
- tasteContent,
1264
+ tasteContent: body.tasteContent ? String(body.tasteContent) : taste.content,
849
1265
  memories,
850
1266
  count,
851
1267
  mode,
@@ -854,27 +1270,114 @@ api.post("/taste/simulate", async (c) => {
854
1270
  });
855
1271
  let appendedIds = [];
856
1272
  if (saveToMemory && result.ideas.length > 0) {
857
- const appended = appendMemoryEntries(memoryContent, result.ideas);
858
- appendedIds = appended.appendedIds;
859
- await writeFile(memoryPath, appended.markdown, "utf-8");
1273
+ for (const idea of result.ideas) {
1274
+ const saved = await store.appendMemory({ ...idea, status: "generated" });
1275
+ appendedIds.push(saved.id);
1276
+ }
860
1277
  }
861
1278
  return c.json({ ...result, appendedIds });
862
1279
  } catch (e) {
863
1280
  return c.json({ error: e.message }, 500);
864
1281
  }
865
1282
  });
866
- api.post("/taste/memory/append", async (c) => {
1283
+ api.post("/taste/feedback", async (c) => {
1284
+ try {
1285
+ const body = await c.req.json();
1286
+ const decision = String(body.decision || "").toLowerCase();
1287
+ if (decision !== "accepted" && decision !== "rejected") {
1288
+ return c.json({ error: "decision must be accepted or rejected" }, 400);
1289
+ }
1290
+ if (!body.title) {
1291
+ return c.json({ error: "title is required" }, 400);
1292
+ }
1293
+ const store = createStoreFromBody(body);
1294
+ await store.ensureWorkspace();
1295
+ const item = {
1296
+ id: body.ideaId ? String(body.ideaId) : void 0,
1297
+ title: String(body.title),
1298
+ format: body.format ? String(body.format) : "daily-reel",
1299
+ tags: Array.isArray(body.tags) ? body.tags.map((x) => String(x)) : [],
1300
+ freshnessTerms: [],
1301
+ summary: body.summary ? String(body.summary) : "",
1302
+ content: body.content ? String(body.content) : "",
1303
+ status: decision,
1304
+ reasonTags: Array.isArray(body.reasonTags) ? body.reasonTags.map((x) => String(x)) : [],
1305
+ notes: body.notes ? String(body.notes) : ""
1306
+ };
1307
+ const saved = await store.appendMemory(item);
1308
+ return c.json({ item: saved });
1309
+ } catch (e) {
1310
+ return c.json({ error: e.message }, 500);
1311
+ }
1312
+ });
1313
+ api.post("/taste/suggest", async (c) => {
867
1314
  try {
868
1315
  const body = await c.req.json();
869
- const memoryFilePath = String(body.memoryFilePath || "taste/memory.md");
870
- const entries = Array.isArray(body.entries) ? body.entries : [];
871
- if (!entries.length) return c.json({ error: "entries are required" }, 400);
872
- const fullPath = resolveSafePath(memoryFilePath);
873
- const current = existsSync(fullPath) ? await readFile(fullPath, "utf-8") : "";
874
- const appended = appendMemoryEntries(current, entries);
875
- await mkdir(dirname(fullPath), { recursive: true });
876
- await writeFile(fullPath, appended.markdown, "utf-8");
877
- return c.json({ appendedIds: appended.appendedIds });
1316
+ const memoryWindow = Math.max(1, Number(body.memoryWindow || 50));
1317
+ const store = createStoreFromBody(body);
1318
+ await store.ensureWorkspace();
1319
+ const taste = await store.readTaste();
1320
+ const memories = await store.queryMemory({ limit: memoryWindow });
1321
+ const draft = buildTasteSuggestionFromMemory(taste.content, memories);
1322
+ const suggestion = await store.appendSuggestion({
1323
+ title: draft.title,
1324
+ summary: draft.summary,
1325
+ rationale: draft.rationale,
1326
+ patch: draft.patch,
1327
+ proposedTasteContent: draft.proposedTasteContent,
1328
+ baseVersion: taste.version
1329
+ });
1330
+ return c.json({ suggestion });
1331
+ } catch (e) {
1332
+ return c.json({ error: e.message }, 500);
1333
+ }
1334
+ });
1335
+ api.post("/taste/suggestions/list", async (c) => {
1336
+ try {
1337
+ const body = await c.req.json();
1338
+ const store = createStoreFromBody(body);
1339
+ await store.ensureWorkspace();
1340
+ const status = body.status ? String(body.status) : void 0;
1341
+ const suggestions = await store.listSuggestions(
1342
+ status === "pending" || status === "applied" || status === "rejected" ? status : void 0
1343
+ );
1344
+ return c.json({ suggestions });
1345
+ } catch (e) {
1346
+ return c.json({ error: e.message }, 500);
1347
+ }
1348
+ });
1349
+ api.post("/taste/apply", async (c) => {
1350
+ try {
1351
+ const body = await c.req.json();
1352
+ const suggestionId = String(body.suggestionId || "").trim();
1353
+ const rejectOnly = Boolean(body.reject);
1354
+ if (!suggestionId) {
1355
+ return c.json({ error: "suggestionId is required" }, 400);
1356
+ }
1357
+ const store = createStoreFromBody(body);
1358
+ await store.ensureWorkspace();
1359
+ const all = await store.listSuggestions();
1360
+ const target = all.find((entry) => entry.id === suggestionId);
1361
+ if (!target) {
1362
+ return c.json({ error: `Suggestion not found: ${suggestionId}` }, 404);
1363
+ }
1364
+ if (target.status !== "pending") {
1365
+ return c.json({ error: `Suggestion ${target.id} is already ${target.status}` }, 400);
1366
+ }
1367
+ if (rejectOnly) {
1368
+ const suggestion2 = await store.updateSuggestionStatus(target.id, "rejected", {
1369
+ reviewNote: body.note ? String(body.note) : void 0
1370
+ });
1371
+ return c.json({ suggestion: suggestion2 });
1372
+ }
1373
+ if (!target.proposedTasteContent.trim()) {
1374
+ return c.json({ error: "Suggestion is missing proposedTasteContent" }, 400);
1375
+ }
1376
+ await store.writeTaste(target.proposedTasteContent, target.baseVersion);
1377
+ const suggestion = await store.updateSuggestionStatus(target.id, "applied", {
1378
+ reviewNote: body.note ? String(body.note) : void 0
1379
+ });
1380
+ return c.json({ suggestion });
878
1381
  } catch (e) {
879
1382
  return c.json({ error: e.message }, 500);
880
1383
  }
@@ -882,32 +1385,46 @@ api.post("/taste/memory/append", async (c) => {
882
1385
  api.post("/taste/memory/query", async (c) => {
883
1386
  try {
884
1387
  const body = await c.req.json();
885
- const memoryFilePath = String(body.memoryFilePath || "taste/memory.md");
886
- const fullPath = resolveSafePath(memoryFilePath);
887
- const content = existsSync(fullPath) ? await readFile(fullPath, "utf-8") : "";
888
- const items = parseTasteMemoryMarkdown(content);
889
- const filtered = queryMemoryItems(items, {
1388
+ const store = createStoreFromBody(body);
1389
+ await store.ensureWorkspace();
1390
+ const items = await store.queryMemory({
890
1391
  tag: body.tag ? String(body.tag) : void 0,
1392
+ status: body.status ? String(body.status) : void 0,
891
1393
  since: body.since ? String(body.since) : void 0,
892
1394
  text: body.text ? String(body.text) : void 0,
893
1395
  limit: body.limit ? Number(body.limit) : void 0
894
1396
  });
895
- return c.json({ items: filtered });
1397
+ return c.json({ items });
1398
+ } catch (e) {
1399
+ return c.json({ error: e.message }, 500);
1400
+ }
1401
+ });
1402
+ api.post("/cli/run", async (c) => {
1403
+ try {
1404
+ const body = await c.req.json();
1405
+ const args = Array.isArray(body.args) ? body.args : [];
1406
+ if (!args.length) {
1407
+ return c.json({ error: "args array is required" }, 400);
1408
+ }
1409
+ const cwd = body.cwd ? resolveSafePath(String(body.cwd)) : process.cwd();
1410
+ const result = await runCliCommand(args, cwd);
1411
+ return c.json(result);
896
1412
  } catch (e) {
1413
+ console.error("[API] /cli/run error:", e.message);
897
1414
  return c.json({ error: e.message }, 500);
898
1415
  }
899
1416
  });
900
1417
  var api_default = api;
901
1418
 
902
1419
  // src/cli/server/index.ts
903
- import path3 from "path";
904
- import { fileURLToPath } from "url";
905
- import fs3 from "fs";
1420
+ import path5 from "path";
1421
+ import { fileURLToPath as fileURLToPath2 } from "url";
1422
+ import fs4 from "fs";
906
1423
  var app = new Hono2();
907
1424
  app.use("/*", cors());
908
1425
  app.route("/api", api_default);
909
- var __filename = fileURLToPath(import.meta.url);
910
- var __dirname = path3.dirname(__filename);
1426
+ var __filename2 = fileURLToPath2(import.meta.url);
1427
+ var __dirname2 = path5.dirname(__filename2);
911
1428
  var createServer = (staticRoot) => {
912
1429
  const app2 = new Hono2();
913
1430
  app2.use("/*", cors());
@@ -938,10 +1455,10 @@ var createServer = (staticRoot) => {
938
1455
  };
939
1456
  app2.get("/*", async (c) => {
940
1457
  const requestPath = c.req.path === "/" ? "/index.html" : c.req.path;
941
- const filePath = path3.join(staticRoot, requestPath);
1458
+ const filePath = path5.join(staticRoot, requestPath);
942
1459
  try {
943
- if (fs3.existsSync(filePath)) {
944
- const file = await fs3.promises.readFile(filePath);
1460
+ if (fs4.existsSync(filePath)) {
1461
+ const file = await fs4.promises.readFile(filePath);
945
1462
  const mimeType = getMimeType(filePath);
946
1463
  return new Response(file, {
947
1464
  headers: {
@@ -953,9 +1470,9 @@ var createServer = (staticRoot) => {
953
1470
  }
954
1471
  if (!requestPath.includes(".")) {
955
1472
  try {
956
- const indexPath = path3.join(staticRoot, "index.html");
957
- if (fs3.existsSync(indexPath)) {
958
- const indexFile = await fs3.promises.readFile(indexPath);
1473
+ const indexPath = path5.join(staticRoot, "index.html");
1474
+ if (fs4.existsSync(indexPath)) {
1475
+ const indexFile = await fs4.promises.readFile(indexPath);
959
1476
  return new Response(indexFile, {
960
1477
  headers: {
961
1478
  "Content-Type": "text/html"
@@ -983,8 +1500,8 @@ function startServer(app2, port) {
983
1500
  }
984
1501
 
985
1502
  // src/cli/commands/record.ts
986
- var __filename2 = fileURLToPath2(import.meta.url);
987
- var __dirname2 = path4.dirname(__filename2);
1503
+ var __filename3 = fileURLToPath3(import.meta.url);
1504
+ var __dirname3 = path6.dirname(__filename3);
988
1505
  function formatBytes(bytes) {
989
1506
  if (bytes === 0) return "0 B";
990
1507
  const k = 1024;
@@ -993,21 +1510,21 @@ function formatBytes(bytes) {
993
1510
  return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
994
1511
  }
995
1512
  function getMissingUiAssets(staticRoot) {
996
- const indexPath = path4.join(staticRoot, "index.html");
997
- if (!fs4.existsSync(indexPath)) {
1513
+ const indexPath = path6.join(staticRoot, "index.html");
1514
+ if (!fs5.existsSync(indexPath)) {
998
1515
  return {
999
1516
  missingFiles: [indexPath],
1000
1517
  expectedUrls: []
1001
1518
  };
1002
1519
  }
1003
- const indexHtml = fs4.readFileSync(indexPath, "utf-8");
1520
+ const indexHtml = fs5.readFileSync(indexPath, "utf-8");
1004
1521
  const assetMatches = Array.from(indexHtml.matchAll(/(?:src|href)=["'](\/assets\/[^"']+)["']/g));
1005
1522
  const assetPaths = [...new Set(assetMatches.map((match) => match[1]))];
1006
1523
  const missingFiles = [];
1007
1524
  const expectedUrls = [];
1008
1525
  for (const assetPath of assetPaths) {
1009
- const localPath = path4.join(staticRoot, assetPath.replace(/^\//, ""));
1010
- if (!fs4.existsSync(localPath)) {
1526
+ const localPath = path6.join(staticRoot, assetPath.replace(/^\//, ""));
1527
+ if (!fs5.existsSync(localPath)) {
1011
1528
  missingFiles.push(localPath);
1012
1529
  expectedUrls.push(assetPath);
1013
1530
  }
@@ -1097,17 +1614,17 @@ async function ensurePlaywrightReadyOrExit() {
1097
1614
  }
1098
1615
  var recordCommand = new Command("record").description("Record the project to a video file with audio").option("-o, --output <path>", "Output file path", "output.mp4").option("-u, --url <url>", "URL of the running server", "http://localhost:3331").option("-w, --width <number>", "Viewport width", "1920").option("-h, --height <number>", "Viewport height", "1080").option("-f, --fps <number>", "Frames per second", "30").option("--project <path>", "Path to project file", "scene_1.json").option("--dry-run", "Validate scene and show estimates without rendering").option("--debug", "Enable verbose logging (FFmpeg, browser console)").action(async (options) => {
1099
1616
  const { output, url, width, height, fps, project: projectOption, debug, dryRun } = options;
1100
- const projectPath = path4.resolve(process.cwd(), projectOption);
1101
- if (!fs4.existsSync(projectPath)) {
1617
+ const projectPath = path6.resolve(process.cwd(), projectOption);
1618
+ if (!fs5.existsSync(projectPath)) {
1102
1619
  console.error(`Project file not found at ${projectPath}`);
1103
1620
  process.exit(1);
1104
1621
  }
1105
- const projectDir = path4.dirname(projectPath);
1622
+ const projectDir = path6.dirname(projectPath);
1106
1623
  process.chdir(projectDir);
1107
1624
  console.log(`Working in project directory: ${projectDir}`);
1108
1625
  let project;
1109
1626
  try {
1110
- const rawProject = JSON.parse(fs4.readFileSync(projectPath, "utf-8"));
1627
+ const rawProject = JSON.parse(fs5.readFileSync(projectPath, "utf-8"));
1111
1628
  const resolvedCwd = process.cwd();
1112
1629
  project = await SceneResolver.resolve(rawProject, resolvedCwd);
1113
1630
  } catch (e) {
@@ -1128,9 +1645,9 @@ var recordCommand = new Command("record").description("Record the project to a v
1128
1645
  if (entity.src.startsWith("/")) {
1129
1646
  assetPath = entity.src;
1130
1647
  } else {
1131
- assetPath = path4.resolve(process.cwd(), entity.src);
1648
+ assetPath = path6.resolve(process.cwd(), entity.src);
1132
1649
  }
1133
- if (!fs4.existsSync(assetPath)) {
1650
+ if (!fs5.existsSync(assetPath)) {
1134
1651
  missingAssets.push(entity.src);
1135
1652
  }
1136
1653
  });
@@ -1154,10 +1671,10 @@ var recordCommand = new Command("record").description("Record the project to a v
1154
1671
  Assets(${allAssets.length}):`);
1155
1672
  allAssets.forEach((entity) => {
1156
1673
  if (!entity.src) return;
1157
- let assetPath = path4.resolve(process.cwd(), "assets", entity.src);
1158
- if (!fs4.existsSync(assetPath)) assetPath = path4.resolve(process.cwd(), entity.src);
1159
- if (fs4.existsSync(assetPath)) {
1160
- const stats = fs4.statSync(assetPath);
1674
+ let assetPath = path6.resolve(process.cwd(), "assets", entity.src);
1675
+ if (!fs5.existsSync(assetPath)) assetPath = path6.resolve(process.cwd(), entity.src);
1676
+ if (fs5.existsSync(assetPath)) {
1677
+ const stats = fs5.statSync(assetPath);
1161
1678
  const sizeStr = formatBytes(stats.size);
1162
1679
  console.log(` \u2713 ${entity.src} (${sizeStr})`);
1163
1680
  }
@@ -1191,11 +1708,11 @@ Ready to record. Run without --dry-run to start rendering.`);
1191
1708
  ensureFfmpegAvailableOrExit();
1192
1709
  await ensurePlaywrightReadyOrExit();
1193
1710
  console.log("Starting temporary server...");
1194
- let staticRoot = path4.resolve(__dirname2, "../../dist/ui");
1195
- if (!fs4.existsSync(staticRoot)) {
1196
- staticRoot = path4.resolve(__dirname2, "../ui");
1711
+ let staticRoot = path6.resolve(__dirname3, "../../dist/ui");
1712
+ if (!fs5.existsSync(staticRoot)) {
1713
+ staticRoot = path6.resolve(__dirname3, "../ui");
1197
1714
  }
1198
- if (!fs4.existsSync(staticRoot)) {
1715
+ if (!fs5.existsSync(staticRoot)) {
1199
1716
  console.error(`Error: UI assets not found at ${staticRoot}. Please run 'bun run build' first.`);
1200
1717
  process.exit(1);
1201
1718
  }
@@ -1251,14 +1768,14 @@ Ready to record. Run without --dry-run to start rendering.`);
1251
1768
  let assetPath = "";
1252
1769
  if (e.src.startsWith("/")) {
1253
1770
  if (e.src.startsWith("/api/fs/assets/")) {
1254
- assetPath = path4.resolve(process.cwd(), "assets", e.src.replace("/api/fs/assets/", ""));
1771
+ assetPath = path6.resolve(process.cwd(), "assets", e.src.replace("/api/fs/assets/", ""));
1255
1772
  } else {
1256
1773
  assetPath = e.src;
1257
1774
  }
1258
1775
  } else {
1259
- assetPath = path4.resolve(process.cwd(), e.src);
1776
+ assetPath = path6.resolve(process.cwd(), e.src);
1260
1777
  }
1261
- if (fs4.existsSync(assetPath)) {
1778
+ if (fs5.existsSync(assetPath)) {
1262
1779
  e.src = assetPath;
1263
1780
  return true;
1264
1781
  } else {
@@ -1312,7 +1829,7 @@ Ready to record. Run without --dry-run to start rendering.`);
1312
1829
  if (debug) {
1313
1830
  console.log("FFmpeg args:", ffmpegArgs.join(" "));
1314
1831
  }
1315
- const ffmpeg = spawn3("ffmpeg", ffmpegArgs);
1832
+ const ffmpeg = spawn4("ffmpeg", ffmpegArgs);
1316
1833
  ffmpeg.stderr.on("data", (data) => {
1317
1834
  if (debug) {
1318
1835
  console.error(`FFmpeg: ${data.toString()}`);
@@ -1368,7 +1885,7 @@ Ready to record. Run without --dry-run to start rendering.`);
1368
1885
  try {
1369
1886
  for (let i = 0; i < totalFrames; i++) {
1370
1887
  const time = i / fpsNum;
1371
- const projectRelativePath = path4.relative(process.cwd(), projectPath);
1888
+ const projectRelativePath = path6.relative(process.cwd(), projectPath);
1372
1889
  const targetUrl = `${url}?mode=render&time=${time}&project=${encodeURIComponent(projectRelativePath)}`;
1373
1890
  await page.goto(targetUrl, { waitUntil: "load" });
1374
1891
  await page.waitForSelector('canvas[data-ready="true"]', { timeout: 3e4 });
@@ -1406,12 +1923,12 @@ Ready to record. Run without --dry-run to start rendering.`);
1406
1923
 
1407
1924
  // src/cli/commands/inspect.ts
1408
1925
  import { Command as Command2 } from "commander";
1409
- import fs5 from "fs";
1410
- import path5 from "path";
1926
+ import fs6 from "fs";
1927
+ import path7 from "path";
1411
1928
  var inspectCommand = new Command2("inspect");
1412
1929
  inspectCommand.description("Inspect an asset file to get metadata (duration, format, etc.)").argument("<file>", "Path to the asset file").option("--json", "Output in JSON format").action(async (file, options) => {
1413
- const filePath = path5.resolve(process.cwd(), file);
1414
- if (!fs5.existsSync(filePath)) {
1930
+ const filePath = path7.resolve(process.cwd(), file);
1931
+ if (!fs6.existsSync(filePath)) {
1415
1932
  console.error(`File not found: ${filePath}`);
1416
1933
  process.exit(1);
1417
1934
  }
@@ -1440,8 +1957,8 @@ inspectCommand.description("Inspect an asset file to get metadata (duration, for
1440
1957
 
1441
1958
  // src/cli/commands/validate.ts
1442
1959
  import { Command as Command3 } from "commander";
1443
- import fs6 from "fs";
1444
- import path6 from "path";
1960
+ import fs7 from "fs";
1961
+ import path8 from "path";
1445
1962
  var validateCommand = new Command3("validate");
1446
1963
  function resolveRuntimeFit(fit, imageRatio, canvasRatio) {
1447
1964
  if (fit === "cover" || fit === "contain") return fit;
@@ -1449,13 +1966,13 @@ function resolveRuntimeFit(fit, imageRatio, canvasRatio) {
1449
1966
  return ratioDelta > 0.2 ? "contain" : "cover";
1450
1967
  }
1451
1968
  validateCommand.description("Validate a scene file").argument("<file>", "Path to the scene file").action(async (file) => {
1452
- const filePath = path6.resolve(process.cwd(), file);
1453
- if (!fs6.existsSync(filePath)) {
1969
+ const filePath = path8.resolve(process.cwd(), file);
1970
+ if (!fs7.existsSync(filePath)) {
1454
1971
  console.error(`File not found: ${filePath}`);
1455
1972
  process.exit(1);
1456
1973
  }
1457
1974
  try {
1458
- const rawScene = JSON.parse(fs6.readFileSync(filePath, "utf-8"));
1975
+ const rawScene = JSON.parse(fs7.readFileSync(filePath, "utf-8"));
1459
1976
  const data = await SceneResolver.resolve(rawScene, process.cwd());
1460
1977
  const errors = [];
1461
1978
  const warnings = [];
@@ -1495,13 +2012,13 @@ validateCommand.description("Validate a scene file").argument("<file>", "Path to
1495
2012
  let fullPath = "";
1496
2013
  if (entity.src.startsWith("/api/fs/assets/")) {
1497
2014
  const stripped = entity.src.replace("/api/fs/assets/", "");
1498
- fullPath = path6.resolve(process.cwd(), stripped);
2015
+ fullPath = path8.resolve(process.cwd(), stripped);
1499
2016
  } else if (entity.src.startsWith("/")) {
1500
2017
  fullPath = entity.src;
1501
2018
  } else {
1502
- fullPath = path6.resolve(process.cwd(), entity.src);
2019
+ fullPath = path8.resolve(process.cwd(), entity.src);
1503
2020
  }
1504
- if (!fs6.existsSync(fullPath)) {
2021
+ if (!fs7.existsSync(fullPath)) {
1505
2022
  errors.push(`${prefix} asset not found at ${fullPath} (derived from src: "${entity.src}")`);
1506
2023
  } else {
1507
2024
  try {
@@ -1535,13 +2052,13 @@ validateCommand.description("Validate a scene file").argument("<file>", "Path to
1535
2052
  let fullPath = "";
1536
2053
  if (entity.src.startsWith("/api/fs/assets/")) {
1537
2054
  const stripped = entity.src.replace("/api/fs/assets/", "");
1538
- fullPath = path6.resolve(process.cwd(), stripped);
2055
+ fullPath = path8.resolve(process.cwd(), stripped);
1539
2056
  } else if (entity.src.startsWith("/")) {
1540
2057
  fullPath = entity.src;
1541
2058
  } else {
1542
- fullPath = path6.resolve(process.cwd(), entity.src);
2059
+ fullPath = path8.resolve(process.cwd(), entity.src);
1543
2060
  }
1544
- if (!fs6.existsSync(fullPath)) {
2061
+ if (!fs7.existsSync(fullPath)) {
1545
2062
  errors.push(`${prefix} asset not found at ${fullPath} (derived from src: "${entity.src}")`);
1546
2063
  }
1547
2064
  }
@@ -1573,21 +2090,21 @@ validateCommand.description("Validate a scene file").argument("<file>", "Path to
1573
2090
 
1574
2091
  // src/cli/commands/set-scene.ts
1575
2092
  import { Command as Command4 } from "commander";
1576
- import fs7 from "fs";
1577
- import path7 from "path";
2093
+ import fs8 from "fs";
2094
+ import path9 from "path";
1578
2095
  var setSceneCommand = new Command4("set-scene");
1579
2096
  setSceneCommand.description("Update or create a scene file with new content").argument("<file>", "Path to the scene file to update").option("-c, --content <json>", "JSON content string").option("-i, --input <file>", "Input JSON file to copy from").action(async (file, options) => {
1580
- const targetPath = path7.resolve(process.cwd(), file);
2097
+ const targetPath = path9.resolve(process.cwd(), file);
1581
2098
  let contentStr = "";
1582
2099
  if (options.content) {
1583
2100
  contentStr = options.content;
1584
2101
  } else if (options.input) {
1585
- const inputPath = path7.resolve(process.cwd(), options.input);
1586
- if (!fs7.existsSync(inputPath)) {
2102
+ const inputPath = path9.resolve(process.cwd(), options.input);
2103
+ if (!fs8.existsSync(inputPath)) {
1587
2104
  console.error(`Input file not found: ${inputPath}`);
1588
2105
  process.exit(1);
1589
2106
  }
1590
- contentStr = fs7.readFileSync(inputPath, "utf-8");
2107
+ contentStr = fs8.readFileSync(inputPath, "utf-8");
1591
2108
  } else {
1592
2109
  console.error("Error: Please provide --content <json> or --input <file>");
1593
2110
  process.exit(1);
@@ -1598,7 +2115,7 @@ setSceneCommand.description("Update or create a scene file with new content").ar
1598
2115
  console.error('Error: Invalid scene format. Missing "meta" or "entities".');
1599
2116
  process.exit(1);
1600
2117
  }
1601
- fs7.writeFileSync(targetPath, JSON.stringify(data, null, 2));
2118
+ fs8.writeFileSync(targetPath, JSON.stringify(data, null, 2));
1602
2119
  console.log(`Scene updated at ${targetPath}`);
1603
2120
  } catch (e) {
1604
2121
  console.error("Error: Content is not valid JSON.", e.message);
@@ -1608,10 +2125,10 @@ setSceneCommand.description("Update or create a scene file with new content").ar
1608
2125
 
1609
2126
  // src/cli/commands/imagine.ts
1610
2127
  import { Command as Command5 } from "commander";
1611
- import fs8 from "fs";
1612
- import path8 from "path";
2128
+ import fs9 from "fs";
2129
+ import path10 from "path";
1613
2130
  var imagineCommand = new Command5("imagine");
1614
- imagineCommand.description("Generate images using Google Gemini AI (Imagen)").argument("<prompt-or-file>", "Text prompt or path to a file containing the prompt").option("-o, --output <filename>", "Output filename (relative to assets/ or absolute path)", "generated.png").option("-k, --api-key <key>", "Gemini API key (or set GEMINI_API_KEY env var)").option("-n, --number <count>", "Number of images to generate", "1").option("--aspect-ratio <ratio>", "Aspect ratio (e.g., 9:16, 1:1, 16:9, or auto)", "auto").option("--image-size <size>", "Image size hint: 1K, 2K, or 4K", "2K").option("--project <path>", "Scene file path to infer aspect ratio from (reads meta.width/meta.height)", "scene_1.json").addHelpText("after", `
2131
+ imagineCommand.description("Generate images using Google Gemini AI (Imagen)").argument("<prompt-or-file>", "Text prompt or path to a file containing the prompt").option("-o, --output <filename>", "Output filename (relative to assets/ or absolute path)", "generated.png").option("-k, --api-key <key>", "Gemini API key (or set GEMINI_API_KEY env var)").option("-n, --number <count>", "Number of images to generate", "1").option("--aspect-ratio <ratio>", "Aspect ratio (e.g., 9:16, 1:1, 16:9, or auto)", "auto").option("--image-size <size>", "Image size hint: 1K, 2K, or 4K", "2K").option("--project <path>", "Scene file path to infer aspect ratio from (reads meta.width/meta.height)", "scene_1.json").option("--json", "Emit machine-readable JSON output").addHelpText("after", `
1615
2132
 
1616
2133
  Examples:
1617
2134
  # Basic usage - saves to assets/generated.png
@@ -1659,18 +2176,26 @@ Note:
1659
2176
  `).action(async (promptOrFile, options) => {
1660
2177
  try {
1661
2178
  let prompt;
1662
- if (fs8.existsSync(promptOrFile)) {
2179
+ if (fs9.existsSync(promptOrFile)) {
1663
2180
  console.log(`Reading prompt from file: ${promptOrFile}`);
1664
- prompt = fs8.readFileSync(promptOrFile, "utf-8").trim();
2181
+ prompt = fs9.readFileSync(promptOrFile, "utf-8").trim();
1665
2182
  } else {
1666
2183
  prompt = promptOrFile;
1667
2184
  }
1668
2185
  if (!prompt) {
2186
+ if (options.json) {
2187
+ console.log(JSON.stringify({ error: "Prompt cannot be empty" }));
2188
+ return;
2189
+ }
1669
2190
  console.error("Error: Prompt cannot be empty");
1670
2191
  process.exit(1);
1671
2192
  }
1672
2193
  const apiKey = options.apiKey || process.env.GEMINI_API_KEY;
1673
2194
  if (!apiKey) {
2195
+ if (options.json) {
2196
+ console.log(JSON.stringify({ error: "Missing Gemini API key" }));
2197
+ return;
2198
+ }
1674
2199
  console.error("Error: Missing Gemini API key");
1675
2200
  console.error("Please provide via:");
1676
2201
  console.error(' 1. -k flag: feedeas imagine "prompt" -k YOUR_API_KEY');
@@ -1681,10 +2206,12 @@ Note:
1681
2206
  const numberOfImages = Math.max(1, parseInt(options.number) || 1);
1682
2207
  const imageSize = normalizeImageSize(options.imageSize);
1683
2208
  const aspectRatio = resolveAspectRatio(options.aspectRatio, options.project);
1684
- console.log("\u{1F3A8} Generating image with Gemini AI (Imagen)...");
1685
- console.log(`\u{1F4DD} Prompt: "${prompt.substring(0, 80)}${prompt.length > 80 ? "..." : ""}"`);
1686
- console.log(`\u{1F9ED} Aspect ratio: ${aspectRatio}`);
1687
- console.log(`\u{1F4D0} Image size: ${imageSize}`);
2209
+ if (!options.json) {
2210
+ console.log("\u{1F3A8} Generating image with Gemini AI (Imagen)...");
2211
+ console.log(`\u{1F4DD} Prompt: "${prompt.substring(0, 80)}${prompt.length > 80 ? "..." : ""}"`);
2212
+ console.log(`\u{1F9ED} Aspect ratio: ${aspectRatio}`);
2213
+ console.log(`\u{1F4D0} Image size: ${imageSize}`);
2214
+ }
1688
2215
  const images = await generateImages({
1689
2216
  prompt,
1690
2217
  numberOfImages,
@@ -1693,30 +2220,45 @@ Note:
1693
2220
  imageSize
1694
2221
  });
1695
2222
  let outputPath;
1696
- if (path8.isAbsolute(options.output)) {
2223
+ if (path10.isAbsolute(options.output)) {
1697
2224
  outputPath = options.output;
1698
2225
  } else {
1699
- const assetsDir = path8.resolve(process.cwd(), "assets");
1700
- if (!fs8.existsSync(assetsDir)) {
2226
+ const assetsDir = path10.resolve(process.cwd(), "assets");
2227
+ if (!fs9.existsSync(assetsDir)) {
1701
2228
  console.log(`\u{1F4C1} Creating assets directory: ${assetsDir}`);
1702
- fs8.mkdirSync(assetsDir, { recursive: true });
2229
+ fs9.mkdirSync(assetsDir, { recursive: true });
1703
2230
  }
1704
- outputPath = path8.join(assetsDir, options.output);
2231
+ outputPath = path10.join(assetsDir, options.output);
1705
2232
  }
1706
- const outputDir = path8.dirname(outputPath);
1707
- const outputExt = path8.extname(outputPath) || ".png";
1708
- const outputBase = path8.basename(outputPath, outputExt);
1709
- if (!fs8.existsSync(outputDir)) {
1710
- fs8.mkdirSync(outputDir, { recursive: true });
2233
+ const outputDir = path10.dirname(outputPath);
2234
+ const outputExt = path10.extname(outputPath) || ".png";
2235
+ const outputBase = path10.basename(outputPath, outputExt);
2236
+ if (!fs9.existsSync(outputDir)) {
2237
+ fs9.mkdirSync(outputDir, { recursive: true });
1711
2238
  }
2239
+ const savedFiles = [];
1712
2240
  for (let i = 0; i < images.length; i++) {
1713
- const filename = images.length > 1 ? path8.join(outputDir, `${outputBase}_${i + 1}${outputExt}`) : outputPath;
1714
- fs8.writeFileSync(filename, images[i]);
1715
- console.log(`\u2705 Image saved: ${filename}`);
2241
+ const filename = images.length > 1 ? path10.join(outputDir, `${outputBase}_${i + 1}${outputExt}`) : outputPath;
2242
+ fs9.writeFileSync(filename, images[i]);
2243
+ if (!options.json) console.log(`\u2705 Image saved: ${filename}`);
2244
+ savedFiles.push(filename);
2245
+ }
2246
+ if (options.json) {
2247
+ console.log(JSON.stringify({
2248
+ success: true,
2249
+ files: savedFiles,
2250
+ count: images.length,
2251
+ prompt: prompt.substring(0, 80)
2252
+ }, null, 2));
2253
+ return;
1716
2254
  }
1717
2255
  console.log(`
1718
2256
  \u2728 Successfully generated ${images.length} image(s)!`);
1719
2257
  } catch (error) {
2258
+ if (options.json) {
2259
+ console.log(JSON.stringify({ error: error.message || String(error) }));
2260
+ return;
2261
+ }
1720
2262
  console.error("\u274C Error generating image:", error.message);
1721
2263
  if (error.cause) {
1722
2264
  console.error("Details:", error.cause);
@@ -1805,10 +2347,10 @@ function resolveAspectRatio(input, projectFile) {
1805
2347
  return normalizeAspectRatio(input);
1806
2348
  }
1807
2349
  function inferAspectRatioFromProject(projectFile) {
1808
- const filePath = path8.resolve(process.cwd(), projectFile);
1809
- if (!fs8.existsSync(filePath)) return null;
2350
+ const filePath = path10.resolve(process.cwd(), projectFile);
2351
+ if (!fs9.existsSync(filePath)) return null;
1810
2352
  try {
1811
- const scene = JSON.parse(fs8.readFileSync(filePath, "utf-8"));
2353
+ const scene = JSON.parse(fs9.readFileSync(filePath, "utf-8"));
1812
2354
  const width = scene?.meta?.width;
1813
2355
  const height = scene?.meta?.height;
1814
2356
  if (typeof width !== "number" || typeof height !== "number" || width <= 0 || height <= 0) {
@@ -1848,51 +2390,51 @@ function gcd(a, b) {
1848
2390
 
1849
2391
  // src/cli/commands/audio.ts
1850
2392
  import { Command as Command6 } from "commander";
1851
- import fs10 from "fs";
1852
- import path10 from "path";
2393
+ import fs11 from "fs";
2394
+ import path12 from "path";
1853
2395
 
1854
2396
  // src/cli/services/whisper.ts
1855
- import fs9 from "fs";
1856
- import path9 from "path";
2397
+ import fs10 from "fs";
2398
+ import path11 from "path";
1857
2399
  import os from "os";
1858
- import { spawn as spawn4 } from "child_process";
2400
+ import { spawn as spawn5 } from "child_process";
1859
2401
  import https from "https";
1860
2402
  var WhisperService = class {
1861
2403
  static getHomeDir() {
1862
2404
  return os.homedir();
1863
2405
  }
1864
2406
  static getBaseDir() {
1865
- return path9.join(this.getHomeDir(), ".feedeas");
2407
+ return path11.join(this.getHomeDir(), ".feedeas");
1866
2408
  }
1867
2409
  static getBinDir() {
1868
- return path9.join(this.getBaseDir(), "bin");
2410
+ return path11.join(this.getBaseDir(), "bin");
1869
2411
  }
1870
2412
  static getModelsDir() {
1871
- return path9.join(this.getBaseDir(), "models");
2413
+ return path11.join(this.getBaseDir(), "models");
1872
2414
  }
1873
2415
  static getExecutablePath() {
1874
- const repoDir = path9.join(this.getBaseDir(), "whisper.cpp-repo");
2416
+ const repoDir = path11.join(this.getBaseDir(), "whisper.cpp-repo");
1875
2417
  const possiblePaths = [
1876
- path9.join(repoDir, "build", "bin", "whisper-cli"),
1877
- path9.join(repoDir, "build", "bin", "main"),
1878
- path9.join(repoDir, "main"),
1879
- path9.join(this.getBinDir(), "whisper-main")
2418
+ path11.join(repoDir, "build", "bin", "whisper-cli"),
2419
+ path11.join(repoDir, "build", "bin", "main"),
2420
+ path11.join(repoDir, "main"),
2421
+ path11.join(this.getBinDir(), "whisper-main")
1880
2422
  // fallback to copied/downloaded
1881
2423
  ];
1882
2424
  for (const p of possiblePaths) {
1883
- if (fs9.existsSync(p)) return p;
2425
+ if (fs10.existsSync(p)) return p;
1884
2426
  }
1885
- return path9.join(this.getBinDir(), "whisper-main");
2427
+ return path11.join(this.getBinDir(), "whisper-main");
1886
2428
  }
1887
2429
  static getModelPath(modelName = "base.en") {
1888
- return path9.join(this.getModelsDir(), `ggml-${modelName}.bin`);
2430
+ return path11.join(this.getModelsDir(), `ggml-${modelName}.bin`);
1889
2431
  }
1890
2432
  /**
1891
2433
  * Download a file from a URL to a destination path
1892
2434
  */
1893
2435
  static async downloadFile(url, destPath) {
1894
2436
  return new Promise((resolve, reject) => {
1895
- const file = fs9.createWriteStream(destPath);
2437
+ const file = fs10.createWriteStream(destPath);
1896
2438
  https.get(url, (response) => {
1897
2439
  if (response.statusCode === 302 || response.statusCode === 301) {
1898
2440
  this.downloadFile(response.headers.location, destPath).then(resolve).catch(reject);
@@ -1908,7 +2450,7 @@ var WhisperService = class {
1908
2450
  resolve();
1909
2451
  });
1910
2452
  }).on("error", (err) => {
1911
- fs9.unlink(destPath, () => {
2453
+ fs10.unlink(destPath, () => {
1912
2454
  });
1913
2455
  reject(err);
1914
2456
  });
@@ -1920,15 +2462,15 @@ var WhisperService = class {
1920
2462
  static async ensureReady() {
1921
2463
  const binDir = this.getBinDir();
1922
2464
  const modelsDir = this.getModelsDir();
1923
- if (!fs9.existsSync(binDir)) fs9.mkdirSync(binDir, { recursive: true });
1924
- if (!fs9.existsSync(modelsDir)) fs9.mkdirSync(modelsDir, { recursive: true });
2465
+ if (!fs10.existsSync(binDir)) fs10.mkdirSync(binDir, { recursive: true });
2466
+ if (!fs10.existsSync(modelsDir)) fs10.mkdirSync(modelsDir, { recursive: true });
1925
2467
  const execPath = this.getExecutablePath();
1926
2468
  const modelPath = this.getModelPath();
1927
- if (!fs9.existsSync(execPath)) {
2469
+ if (!fs10.existsSync(execPath)) {
1928
2470
  console.log("\u2B07\uFE0F Whisper binary not found. Attempting to build...");
1929
2471
  try {
1930
- const repoDir = path9.join(this.getBaseDir(), "whisper.cpp-repo");
1931
- if (!fs9.existsSync(repoDir)) {
2472
+ const repoDir = path11.join(this.getBaseDir(), "whisper.cpp-repo");
2473
+ if (!fs10.existsSync(repoDir)) {
1932
2474
  console.log("\u{1F4E6} Cloning whisper.cpp...");
1933
2475
  await runCommand("git", ["clone", "https://github.com/ggerganov/whisper.cpp.git", repoDir]);
1934
2476
  } else {
@@ -1936,7 +2478,7 @@ var WhisperService = class {
1936
2478
  console.log("\u{1F528} Building whisper.cpp (this may take a minute)...");
1937
2479
  await runCommand("make", [], repoDir);
1938
2480
  const newPath = this.getExecutablePath();
1939
- if (fs9.existsSync(newPath)) {
2481
+ if (fs10.existsSync(newPath)) {
1940
2482
  console.log(`\u2705 Whisper binary built at: ${newPath}`);
1941
2483
  } else {
1942
2484
  throw new Error("Build command finished but binary not found in expected paths.");
@@ -1947,7 +2489,7 @@ var WhisperService = class {
1947
2489
  Please install manually: 'brew install whisper-cpp'`);
1948
2490
  }
1949
2491
  }
1950
- if (!fs9.existsSync(modelPath)) {
2492
+ if (!fs10.existsSync(modelPath)) {
1951
2493
  console.log("\u2B07\uFE0F Downloading Whisper model (base.en)...");
1952
2494
  const modelUrl = "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin";
1953
2495
  await this.downloadFile(modelUrl, modelPath);
@@ -1960,12 +2502,12 @@ Please install manually: 'brew install whisper-cpp'`);
1960
2502
  static async transcribe(audioPath) {
1961
2503
  const execPath = this.getExecutablePath();
1962
2504
  const modelPath = this.getModelPath();
1963
- const ext = path9.extname(audioPath).toLowerCase();
2505
+ const ext = path11.extname(audioPath).toLowerCase();
1964
2506
  let inputToWhisper = audioPath;
1965
2507
  let isTempFile = false;
1966
2508
  if (ext !== ".wav") {
1967
2509
  console.log("\u{1F504} Converting audio to 16kHz WAV for Whisper...");
1968
- const tempWav = path9.join(path9.dirname(audioPath), `temp_${Date.now()}.wav`);
2510
+ const tempWav = path11.join(path11.dirname(audioPath), `temp_${Date.now()}.wav`);
1969
2511
  await runCommand("ffmpeg", [
1970
2512
  "-i",
1971
2513
  audioPath,
@@ -1982,7 +2524,7 @@ Please install manually: 'brew install whisper-cpp'`);
1982
2524
  isTempFile = true;
1983
2525
  }
1984
2526
  try {
1985
- const outputBase = path9.join(path9.dirname(audioPath), path9.basename(audioPath, path9.extname(audioPath)));
2527
+ const outputBase = path11.join(path11.dirname(audioPath), path11.basename(audioPath, path11.extname(audioPath)));
1986
2528
  const baseArgs = [
1987
2529
  "-m",
1988
2530
  modelPath,
@@ -2019,15 +2561,15 @@ Please install manually: 'brew install whisper-cpp'`);
2019
2561
  words
2020
2562
  };
2021
2563
  } finally {
2022
- if (isTempFile && fs9.existsSync(inputToWhisper)) {
2023
- fs9.unlinkSync(inputToWhisper);
2564
+ if (isTempFile && fs10.existsSync(inputToWhisper)) {
2565
+ fs10.unlinkSync(inputToWhisper);
2024
2566
  }
2025
2567
  }
2026
2568
  }
2027
2569
  };
2028
2570
  function runCommand(command, args, cwd) {
2029
2571
  return new Promise((resolve, reject) => {
2030
- const proc = spawn4(command, args, { cwd, stdio: "inherit" });
2572
+ const proc = spawn5(command, args, { cwd, stdio: "inherit" });
2031
2573
  proc.on("close", (code) => {
2032
2574
  if (code === 0) resolve();
2033
2575
  else reject(new Error(`${command} exited with code ${code}`));
@@ -2037,7 +2579,7 @@ function runCommand(command, args, cwd) {
2037
2579
  }
2038
2580
  function runWhisper(execPath, args) {
2039
2581
  return new Promise((resolve, reject) => {
2040
- const proc = spawn4(execPath, args, { stdio: "inherit" });
2582
+ const proc = spawn5(execPath, args, { stdio: "inherit" });
2041
2583
  proc.on("close", (code) => {
2042
2584
  if (code !== 0) {
2043
2585
  reject(new Error(`Whisper process exited with code ${code}`));
@@ -2046,12 +2588,12 @@ function runWhisper(execPath, args) {
2046
2588
  const outputFileIndex = args.indexOf("-of");
2047
2589
  const outputBase = outputFileIndex >= 0 ? args[outputFileIndex + 1] : void 0;
2048
2590
  const jsonPath = outputBase ? `${outputBase}.json` : "";
2049
- if (!jsonPath || !fs9.existsSync(jsonPath)) {
2591
+ if (!jsonPath || !fs10.existsSync(jsonPath)) {
2050
2592
  reject(new Error("Whisper output JSON not found"));
2051
2593
  return;
2052
2594
  }
2053
2595
  try {
2054
- const data = JSON.parse(fs9.readFileSync(jsonPath, "utf-8"));
2596
+ const data = JSON.parse(fs10.readFileSync(jsonPath, "utf-8"));
2055
2597
  resolve(data);
2056
2598
  } catch (err) {
2057
2599
  reject(err);
@@ -2104,9 +2646,9 @@ var audioCommand = new Command6("generate:audio");
2104
2646
  audioCommand.alias("audio").description("Generate audio from text using Gemini API and extract metadata").argument("<text-or-file>", "Input text or path to text file").option("-o, --output <filename>", "Output filename (relative to assets/ or absolute path)", "speech.mp3").option("-k, --api-key <key>", "Gemini API key (or set GEMINI_API_KEY env var)").option("--voice <name>", "Voice name (optional)").option("--no-transcribe", "Skip Whisper transcription/metadata generation").action(async (textOrFile, options) => {
2105
2647
  try {
2106
2648
  let text;
2107
- if (fs10.existsSync(textOrFile)) {
2649
+ if (fs11.existsSync(textOrFile)) {
2108
2650
  console.log(`\u{1F4D6} Reading text from file: ${textOrFile}`);
2109
- text = fs10.readFileSync(textOrFile, "utf-8").trim();
2651
+ text = fs11.readFileSync(textOrFile, "utf-8").trim();
2110
2652
  } else {
2111
2653
  text = textOrFile;
2112
2654
  }
@@ -2122,10 +2664,10 @@ audioCommand.alias("audio").description("Generate audio from text using Gemini A
2122
2664
  console.log("\u{1F5E3}\uFE0F Generating speech with Gemini...");
2123
2665
  const audioBuffer = await generateGeminiAudio(text, apiKey, options.voice);
2124
2666
  const outputPath = resolveOutputPath(options.output);
2125
- const tempPcmPath = path10.join(path10.dirname(outputPath), `temp_${Date.now()}.pcm`);
2126
- fs10.writeFileSync(tempPcmPath, audioBuffer);
2667
+ const tempPcmPath = path12.join(path12.dirname(outputPath), `temp_${Date.now()}.pcm`);
2668
+ fs11.writeFileSync(tempPcmPath, audioBuffer);
2127
2669
  try {
2128
- console.log(`\u{1F504} Converting raw PCM to ${path10.extname(outputPath)}...`);
2670
+ console.log(`\u{1F504} Converting raw PCM to ${path12.extname(outputPath)}...`);
2129
2671
  await new Promise((resolve, reject) => {
2130
2672
  const ffmpeg = __require("child_process").spawn("ffmpeg", [
2131
2673
  "-f",
@@ -2147,15 +2689,15 @@ audioCommand.alias("audio").description("Generate audio from text using Gemini A
2147
2689
  });
2148
2690
  console.log(`\u2705 Audio saved: ${outputPath}`);
2149
2691
  } finally {
2150
- if (fs10.existsSync(tempPcmPath)) fs10.unlinkSync(tempPcmPath);
2692
+ if (fs11.existsSync(tempPcmPath)) fs11.unlinkSync(tempPcmPath);
2151
2693
  }
2152
2694
  if (options.transcribe) {
2153
2695
  try {
2154
2696
  console.log("\u{1F50D} Aligning audio with Whisper...");
2155
2697
  await WhisperService.ensureReady();
2156
2698
  const metadata = await WhisperService.transcribe(outputPath);
2157
- const metaPath = outputPath.replace(path10.extname(outputPath), ".json");
2158
- fs10.writeFileSync(metaPath, JSON.stringify(metadata, null, 2));
2699
+ const metaPath = outputPath.replace(path12.extname(outputPath), ".json");
2700
+ fs11.writeFileSync(metaPath, JSON.stringify(metadata, null, 2));
2159
2701
  console.log(`\u2705 Metadata saved: ${metaPath}`);
2160
2702
  } catch (wErr) {
2161
2703
  console.warn("\u26A0\uFE0F Whisper alignment failed:", wErr.message);
@@ -2167,10 +2709,10 @@ audioCommand.alias("audio").description("Generate audio from text using Gemini A
2167
2709
  }
2168
2710
  });
2169
2711
  function resolveOutputPath(outputOption) {
2170
- if (path10.isAbsolute(outputOption)) return outputOption;
2171
- const assetsDir = path10.resolve(process.cwd(), "assets");
2172
- if (!fs10.existsSync(assetsDir)) fs10.mkdirSync(assetsDir, { recursive: true });
2173
- return path10.join(assetsDir, outputOption);
2712
+ if (path12.isAbsolute(outputOption)) return outputOption;
2713
+ const assetsDir = path12.resolve(process.cwd(), "assets");
2714
+ if (!fs11.existsSync(assetsDir)) fs11.mkdirSync(assetsDir, { recursive: true });
2715
+ return path12.join(assetsDir, outputOption);
2174
2716
  }
2175
2717
  async function generateGeminiAudio(text, apiKey, voiceName) {
2176
2718
  const endpoint = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-tts:generateContent`;
@@ -2211,11 +2753,11 @@ async function generateGeminiAudio(text, apiKey, voiceName) {
2211
2753
 
2212
2754
  // src/cli/commands/bgm.ts
2213
2755
  import { Command as Command7 } from "commander";
2214
- import fs11 from "fs";
2215
- import path11 from "path";
2216
- import { spawn as spawn5 } from "child_process";
2756
+ import fs12 from "fs";
2757
+ import path13 from "path";
2758
+ import { spawn as spawn6 } from "child_process";
2217
2759
  var bgmCommand = new Command7("generate:bgm");
2218
- bgmCommand.alias("bgm").alias("music").description("Generate background music with Gemini Lyria (Live Music API)").argument("<prompt-or-file>", "Music prompt text or path to a text file").option("-o, --output <filename>", "Output filename (relative to assets/ or absolute path)", "bgm.mp3").option("-k, --api-key <key>", "Gemini API key (or set GEMINI_API_KEY env var)").option("-d, --duration <seconds>", "Target duration in seconds (5-300)", "30").option("--seed <number>", "Optional seed for reproducible output").action(async (promptOrFile, options) => {
2760
+ bgmCommand.alias("bgm").alias("music").description("Generate background music with Gemini Lyria (Live Music API)").argument("<prompt-or-file>", "Music prompt text or path to a text file").option("-o, --output <filename>", "Output filename (relative to assets/ or absolute path)", "bgm.mp3").option("-k, --api-key <key>", "Gemini API key (or set GEMINI_API_KEY env var)").option("-d, --duration <seconds>", "Target duration in seconds (5-300)", "30").option("--seed <number>", "Optional seed for reproducible output").option("--json", "Emit machine-readable JSON output").action(async (promptOrFile, options) => {
2219
2761
  try {
2220
2762
  const prompt = resolvePrompt(promptOrFile);
2221
2763
  const apiKey = options.apiKey || process.env.GEMINI_API_KEY;
@@ -2235,22 +2777,36 @@ bgmCommand.alias("bgm").alias("music").description("Generate background music wi
2235
2777
  apiKey,
2236
2778
  seed
2237
2779
  };
2238
- console.log("\u{1F3B5} Generating BGM with Gemini Lyria...");
2239
- console.log(`\u{1F4DD} Prompt: "${request.prompt.substring(0, 100)}${request.prompt.length > 100 ? "..." : ""}"`);
2240
- console.log(`\u23F1\uFE0F Duration: ${request.durationSec}s`);
2241
- if (typeof request.seed === "number") {
2242
- console.log(`\u{1F331} Seed: ${request.seed}`);
2780
+ if (!options.json) {
2781
+ console.log("\u{1F3B5} Generating BGM with Gemini Lyria...");
2782
+ console.log(`\u{1F4DD} Prompt: "${request.prompt.substring(0, 100)}${request.prompt.length > 100 ? "..." : ""}"`);
2783
+ console.log(`\u23F1\uFE0F Duration: ${request.durationSec}s`);
2784
+ if (typeof request.seed === "number") {
2785
+ console.log(`\u{1F331} Seed: ${request.seed}`);
2786
+ }
2243
2787
  }
2244
2788
  const pcmBuffer = await generateGeminiMusicPcm(request);
2245
2789
  await convertPcmToOutput(pcmBuffer, request.outputPath, request.format, request.durationSec);
2790
+ if (options.json) {
2791
+ console.log(JSON.stringify({
2792
+ success: true,
2793
+ file: request.outputPath,
2794
+ durationSec: request.durationSec
2795
+ }, null, 2));
2796
+ return;
2797
+ }
2246
2798
  console.log(`\u2705 BGM saved: ${request.outputPath}`);
2247
2799
  } catch (error) {
2800
+ if (options.json) {
2801
+ console.log(JSON.stringify({ error: error?.message || String(error) }));
2802
+ return;
2803
+ }
2248
2804
  console.error("\u274C Error generating BGM:", error?.message || error);
2249
2805
  process.exit(1);
2250
2806
  }
2251
2807
  });
2252
2808
  function resolvePrompt(promptOrFile) {
2253
- const prompt = fs11.existsSync(promptOrFile) ? fs11.readFileSync(promptOrFile, "utf-8").trim() : String(promptOrFile).trim();
2809
+ const prompt = fs12.existsSync(promptOrFile) ? fs12.readFileSync(promptOrFile, "utf-8").trim() : String(promptOrFile).trim();
2254
2810
  if (!prompt) {
2255
2811
  throw new Error("Prompt cannot be empty");
2256
2812
  }
@@ -2272,13 +2828,13 @@ function parseSeed(input) {
2272
2828
  return value;
2273
2829
  }
2274
2830
  function resolveOutputPath2(outputOption) {
2275
- if (path11.isAbsolute(outputOption)) return outputOption;
2276
- const assetsDir = path11.resolve(process.cwd(), "assets");
2277
- if (!fs11.existsSync(assetsDir)) fs11.mkdirSync(assetsDir, { recursive: true });
2278
- return path11.join(assetsDir, outputOption);
2831
+ if (path13.isAbsolute(outputOption)) return outputOption;
2832
+ const assetsDir = path13.resolve(process.cwd(), "assets");
2833
+ if (!fs12.existsSync(assetsDir)) fs12.mkdirSync(assetsDir, { recursive: true });
2834
+ return path13.join(assetsDir, outputOption);
2279
2835
  }
2280
2836
  function inferOutputFormat(outputPath) {
2281
- const ext = path11.extname(outputPath).toLowerCase();
2837
+ const ext = path13.extname(outputPath).toLowerCase();
2282
2838
  if (ext === ".wav") return "wav";
2283
2839
  if (ext === ".mp3" || !ext) return "mp3";
2284
2840
  throw new Error("Invalid output format. Use .mp3 or .wav.");
@@ -2366,14 +2922,14 @@ async function generateGeminiMusicPcm(request) {
2366
2922
  });
2367
2923
  }
2368
2924
  async function convertPcmToOutput(pcmBuffer, outputPath, format, durationSec) {
2369
- const outputDir = path11.dirname(outputPath);
2370
- if (!fs11.existsSync(outputDir)) fs11.mkdirSync(outputDir, { recursive: true });
2371
- const tempPcmPath = path11.join(outputDir, `temp_bgm_${Date.now()}.pcm`);
2372
- fs11.writeFileSync(tempPcmPath, pcmBuffer);
2925
+ const outputDir = path13.dirname(outputPath);
2926
+ if (!fs12.existsSync(outputDir)) fs12.mkdirSync(outputDir, { recursive: true });
2927
+ const tempPcmPath = path13.join(outputDir, `temp_bgm_${Date.now()}.pcm`);
2928
+ fs12.writeFileSync(tempPcmPath, pcmBuffer);
2373
2929
  const codecArgs = format === "wav" ? ["-c:a", "pcm_s16le"] : ["-c:a", "libmp3lame", "-q:a", "2"];
2374
2930
  try {
2375
2931
  await new Promise((resolve, reject) => {
2376
- const ffmpeg = spawn5("ffmpeg", [
2932
+ const ffmpeg = spawn6("ffmpeg", [
2377
2933
  "-f",
2378
2934
  "s16le",
2379
2935
  "-ar",
@@ -2395,25 +2951,25 @@ async function convertPcmToOutput(pcmBuffer, outputPath, format, durationSec) {
2395
2951
  ffmpeg.on("error", reject);
2396
2952
  });
2397
2953
  } finally {
2398
- if (fs11.existsSync(tempPcmPath)) fs11.unlinkSync(tempPcmPath);
2954
+ if (fs12.existsSync(tempPcmPath)) fs12.unlinkSync(tempPcmPath);
2399
2955
  }
2400
2956
  }
2401
2957
 
2402
2958
  // src/cli/commands/asset.ts
2403
2959
  import { Command as Command8 } from "commander";
2404
- import fs12 from "fs";
2405
- import path12 from "path";
2406
- import { spawn as spawn6 } from "child_process";
2960
+ import fs13 from "fs";
2961
+ import path14 from "path";
2962
+ import { spawn as spawn7 } from "child_process";
2407
2963
  var assetCommand = new Command8("asset").description("Asset information and management");
2408
2964
  assetCommand.command("info <file>").description("Show detailed information about an asset").action(async (file) => {
2409
- const assetPath = path12.resolve(process.cwd(), "assets", file);
2410
- if (!fs12.existsSync(assetPath)) {
2965
+ const assetPath = path14.resolve(process.cwd(), "assets", file);
2966
+ if (!fs13.existsSync(assetPath)) {
2411
2967
  console.error(`Asset not found: ${file}`);
2412
2968
  console.error(`Looked in: ${assetPath}`);
2413
2969
  process.exit(1);
2414
2970
  }
2415
- const stats = fs12.statSync(assetPath);
2416
- const ext = path12.extname(file).toLowerCase();
2971
+ const stats = fs13.statSync(assetPath);
2972
+ const ext = path14.extname(file).toLowerCase();
2417
2973
  console.log(`
2418
2974
  Asset: ${file}`);
2419
2975
  console.log(`Size: ${formatBytes2(stats.size)}`);
@@ -2436,11 +2992,11 @@ Asset: ${file}`);
2436
2992
  console.log(`Channels: ${audioInfo.channels}`);
2437
2993
  console.log(`Bitrate: ${audioInfo.bitrate}`);
2438
2994
  const metadataFile = file.replace(ext, ".json");
2439
- const metadataPath = path12.resolve(process.cwd(), "assets", metadataFile);
2440
- if (fs12.existsSync(metadataPath)) {
2995
+ const metadataPath = path14.resolve(process.cwd(), "assets", metadataFile);
2996
+ if (fs13.existsSync(metadataPath)) {
2441
2997
  console.log(`
2442
2998
  Metadata: ${metadataFile}`);
2443
- const metadata = JSON.parse(fs12.readFileSync(metadataPath, "utf-8"));
2999
+ const metadata = JSON.parse(fs13.readFileSync(metadataPath, "utf-8"));
2444
3000
  if (Array.isArray(metadata.words) && metadata.words.length > 0) {
2445
3001
  console.log(`Word timings: ${metadata.words.length} words`);
2446
3002
  const firstWord = metadata.words[0];
@@ -2485,7 +3041,7 @@ async function getImageDimensions(filePath) {
2485
3041
  }
2486
3042
  async function getAudioInfo(filePath) {
2487
3043
  return new Promise((resolve, reject) => {
2488
- const ffprobe = spawn6("ffprobe", [
3044
+ const ffprobe = spawn7("ffprobe", [
2489
3045
  "-v",
2490
3046
  "error",
2491
3047
  "-show_entries",
@@ -2524,9 +3080,9 @@ async function getAudioInfo(filePath) {
2524
3080
 
2525
3081
  // src/cli/index.ts
2526
3082
  import open2 from "open";
2527
- import path16 from "path";
2528
- import { fileURLToPath as fileURLToPath4 } from "url";
2529
- import fs16 from "fs";
3083
+ import path18 from "path";
3084
+ import { fileURLToPath as fileURLToPath5 } from "url";
3085
+ import fs17 from "fs";
2530
3086
 
2531
3087
  // src/cli/commands/example.ts
2532
3088
  import { Command as Command9 } from "commander";
@@ -2819,14 +3375,14 @@ function printSchema(name, schema) {
2819
3375
 
2820
3376
  // src/cli/commands/create-scene.ts
2821
3377
  import { Command as Command11 } from "commander";
2822
- import path13 from "path";
3378
+ import path15 from "path";
2823
3379
 
2824
3380
  // src/cli/services/scene-builder.ts
2825
- import fs13 from "fs";
3381
+ import fs14 from "fs";
2826
3382
  function loadScene(filePath) {
2827
- if (fs13.existsSync(filePath)) {
3383
+ if (fs14.existsSync(filePath)) {
2828
3384
  try {
2829
- return JSON.parse(fs13.readFileSync(filePath, "utf-8"));
3385
+ return JSON.parse(fs14.readFileSync(filePath, "utf-8"));
2830
3386
  } catch (e) {
2831
3387
  throw new Error(`Failed to parse existing scene file: ${e}`);
2832
3388
  }
@@ -2842,7 +3398,7 @@ function loadScene(filePath) {
2842
3398
  }
2843
3399
  }
2844
3400
  function saveScene(filePath, scene) {
2845
- fs13.writeFileSync(filePath, JSON.stringify(scene, null, 2));
3401
+ fs14.writeFileSync(filePath, JSON.stringify(scene, null, 2));
2846
3402
  }
2847
3403
  function addEntityToScene(scene, entity) {
2848
3404
  if (!entity.id) {
@@ -2862,10 +3418,10 @@ function addEntityToScene(scene, entity) {
2862
3418
  }
2863
3419
 
2864
3420
  // src/cli/commands/create-scene.ts
2865
- import fs14 from "fs";
3421
+ import fs15 from "fs";
2866
3422
  var createSceneCommand = new Command11("create-scene");
2867
3423
  createSceneCommand.description("Create and modify scene files").argument("<file>", "Path to the scene file").action(async (file) => {
2868
- const filePath = path13.resolve(process.cwd(), file);
3424
+ const filePath = path15.resolve(process.cwd(), file);
2869
3425
  try {
2870
3426
  const scene = loadScene(filePath);
2871
3427
  saveScene(filePath, scene);
@@ -2877,8 +3433,8 @@ createSceneCommand.description("Create and modify scene files").argument("<file>
2877
3433
  });
2878
3434
  var addEntityCommand = new Command11("add-entity");
2879
3435
  addEntityCommand.description("Add an entity to an existing scene file").argument("<file>", "Path to the scene file").requiredOption("-t, --type <type>", "Entity type (image, audio, text)").option("--src <path>", "Asset path (relative to CWD or absolute)").option("--text <content>", "Text content (for text)").option("--fit <mode>", "Image fit mode: smart | contain | cover", "smart").option("--start <number>", "Start time in seconds").option("--duration <string>", 'Duration in seconds or "auto"', "5").option("--at <position>", 'Position ("end" to append after last entity)', "end").action(async (file, options) => {
2880
- const filePath = path13.resolve(process.cwd(), file);
2881
- if (!fs14.existsSync(filePath)) {
3436
+ const filePath = path15.resolve(process.cwd(), file);
3437
+ if (!fs15.existsSync(filePath)) {
2882
3438
  console.error(`\u274C Scene file not found: ${filePath}`);
2883
3439
  process.exit(1);
2884
3440
  }
@@ -2909,7 +3465,7 @@ addEntityCommand.description("Add an entity to an existing scene file").argument
2909
3465
  startTime = 0;
2910
3466
  }
2911
3467
  if (options.duration === "auto" && options.src) {
2912
- const assetPath = path13.resolve(process.cwd(), options.src);
3468
+ const assetPath = path15.resolve(process.cwd(), options.src);
2913
3469
  try {
2914
3470
  const metadata = await FFprobeService.getMetadata(assetPath);
2915
3471
  duration = metadata.duration;
@@ -2956,19 +3512,19 @@ addEntityCommand.description("Add an entity to an existing scene file").argument
2956
3512
 
2957
3513
  // src/cli/services/telemetry.ts
2958
3514
  import os2 from "os";
2959
- import path14 from "path";
2960
- import { createHash } from "crypto";
3515
+ import path16 from "path";
3516
+ import { createHash as createHash2 } from "crypto";
2961
3517
  var POSTHOG_CAPTURE_URL = "https://us.i.posthog.com/capture/";
2962
3518
  var TELEMETRY_DISABLED_VALUES = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
2963
3519
  var BUILT_POSTHOG_KEY = String(
2964
- ""
3520
+ "phc_w3BnJp2sDXvSpIsMkofnwDSObboBd6DXhNetfZuyWVO"
2965
3521
  ).trim();
2966
3522
  var PostHogTelemetryService = class {
2967
3523
  trackFeedback(details) {
2968
3524
  if (!this.enabled) return;
2969
3525
  this.capture("cli_feedback", {
2970
3526
  details: String(details).slice(0, 2e3),
2971
- cwd_basename: path14.basename(process.cwd())
3527
+ cwd_basename: path16.basename(process.cwd())
2972
3528
  });
2973
3529
  }
2974
3530
  apiKey;
@@ -2991,7 +3547,7 @@ var PostHogTelemetryService = class {
2991
3547
  command_name: this.getCommandPath(actionCommand),
2992
3548
  command_aliases: actionCommand.aliases(),
2993
3549
  options_used: this.getUsedOptionNames(actionCommand),
2994
- cwd_basename: path14.basename(process.cwd()),
3550
+ cwd_basename: path16.basename(process.cwd()),
2995
3551
  node_version: process.version,
2996
3552
  platform: process.platform,
2997
3553
  arch: process.arch
@@ -3004,7 +3560,7 @@ var PostHogTelemetryService = class {
3004
3560
  status,
3005
3561
  duration_ms: Math.max(0, Math.round(durationMs)),
3006
3562
  error_message: errorMessage ? String(errorMessage).slice(0, 300) : void 0,
3007
- cwd_basename: path14.basename(process.cwd())
3563
+ cwd_basename: path16.basename(process.cwd())
3008
3564
  });
3009
3565
  }
3010
3566
  getCommandPath(command) {
@@ -3027,7 +3583,7 @@ var PostHogTelemetryService = class {
3027
3583
  os2.hostname(),
3028
3584
  os2.userInfo().username
3029
3585
  ].join("|");
3030
- return createHash("sha256").update(seed).digest("hex");
3586
+ return createHash2("sha256").update(seed).digest("hex");
3031
3587
  }
3032
3588
  capture(event, properties) {
3033
3589
  if (!this.apiKey) return;
@@ -3057,26 +3613,26 @@ var PostHogTelemetryService = class {
3057
3613
 
3058
3614
  // src/cli/commands/taste.ts
3059
3615
  import { Command as Command12 } from "commander";
3060
- import fs15 from "node:fs";
3061
- import path15 from "node:path";
3062
- import { fileURLToPath as fileURLToPath3 } from "node:url";
3616
+ import fs16 from "node:fs";
3617
+ import path17 from "node:path";
3618
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
3063
3619
  import open from "open";
3064
- var __filename3 = fileURLToPath3(import.meta.url);
3065
- var __dirname3 = path15.dirname(__filename3);
3620
+ var __filename4 = fileURLToPath4(import.meta.url);
3621
+ var __dirname4 = path17.dirname(__filename4);
3066
3622
  function resolveStaticRoot() {
3067
- let staticRoot = path15.resolve(__dirname3, "../../../dist/ui");
3068
- if (!fs15.existsSync(staticRoot)) {
3069
- staticRoot = path15.resolve(__dirname3, "../../ui");
3623
+ let staticRoot = path17.resolve(__dirname4, "../../../dist/ui");
3624
+ if (!fs16.existsSync(staticRoot)) {
3625
+ staticRoot = path17.resolve(__dirname4, "../../ui");
3070
3626
  }
3071
3627
  return staticRoot;
3072
3628
  }
3073
3629
  function prepareWorkingDirectory(pathArg) {
3074
3630
  if (!pathArg) return;
3075
- const targetPath = path15.resolve(process.cwd(), pathArg);
3076
- if (fs15.existsSync(targetPath)) {
3077
- const stats = fs15.statSync(targetPath);
3631
+ const targetPath = path17.resolve(process.cwd(), pathArg);
3632
+ if (fs16.existsSync(targetPath)) {
3633
+ const stats = fs16.statSync(targetPath);
3078
3634
  if (stats.isFile()) {
3079
- process.chdir(path15.dirname(targetPath));
3635
+ process.chdir(path17.dirname(targetPath));
3080
3636
  return;
3081
3637
  }
3082
3638
  if (stats.isDirectory()) {
@@ -3084,20 +3640,33 @@ function prepareWorkingDirectory(pathArg) {
3084
3640
  return;
3085
3641
  }
3086
3642
  }
3087
- if (path15.extname(pathArg)) {
3088
- const dir = path15.dirname(targetPath);
3089
- fs15.mkdirSync(dir, { recursive: true });
3643
+ if (path17.extname(pathArg)) {
3644
+ const dir = path17.dirname(targetPath);
3645
+ fs16.mkdirSync(dir, { recursive: true });
3090
3646
  process.chdir(dir);
3091
3647
  return;
3092
3648
  }
3093
- fs15.mkdirSync(targetPath, { recursive: true });
3649
+ fs16.mkdirSync(targetPath, { recursive: true });
3094
3650
  process.chdir(targetPath);
3095
3651
  }
3096
- var tasteCommand = new Command12("taste").description("Launch and automate the Taste workspace").argument("[path]", "Optional workspace directory").option("-p, --port <number>", "Port to run on", "3331").option("--no-open", "Do not open browser").option("--taste-file <path>", "Taste file path", "taste/taste.md").option("--memory-file <path>", "Memory file path", "taste/memory.md").action(async (pathArg, options) => {
3652
+ function createStoreFromOptions(options) {
3653
+ return createTasteStore({
3654
+ backend: options.storage,
3655
+ tasteFilePath: options.tasteFile || "taste/taste.md",
3656
+ memoryFilePath: options.memoryFile || "taste/memory.md",
3657
+ suggestionsFilePath: options.suggestionsFile || "taste/suggestions.md"
3658
+ });
3659
+ }
3660
+ function parseCsvList(value) {
3661
+ if (!value) return [];
3662
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
3663
+ }
3664
+ var tasteCommand = new Command12("taste").description("Launch and automate the Taste workspace").argument("[path]", "Optional workspace directory").option("-p, --port <number>", "Port to run on", "3331").option("--no-open", "Do not open browser").option("--storage <backend>", "Storage backend: markdown|db", "markdown").option("--taste-file <path>", "Taste file path", "taste/taste.md").option("--memory-file <path>", "Memory file path", "taste/memory.md").option("--suggestions-file <path>", "Suggestions file path", "taste/suggestions.md").action(async (pathArg, options) => {
3097
3665
  prepareWorkingDirectory(pathArg);
3098
- ensureTasteWorkspaceFiles(options.tasteFile, options.memoryFile);
3666
+ const store = createStoreFromOptions(options);
3667
+ await store.ensureWorkspace();
3099
3668
  const staticRoot = resolveStaticRoot();
3100
- if (!fs15.existsSync(staticRoot)) {
3669
+ if (!fs16.existsSync(staticRoot)) {
3101
3670
  console.warn(`Warning: UI assets not found at ${staticRoot}. Did you run 'bun run build'?`);
3102
3671
  }
3103
3672
  const port = parseInt(options.port, 10);
@@ -3113,21 +3682,21 @@ var tasteCommand = new Command12("taste").description("Launch and automate the T
3113
3682
  await open(url);
3114
3683
  }
3115
3684
  });
3116
- tasteCommand.command("generate").description("Generate fresh ideas from taste and memory files").option("--taste-file <path>", "Taste file path", "taste/taste.md").option("--memory-file <path>", "Memory file path", "taste/memory.md").option("-n, --count <number>", "How many ideas to generate", "1").option("--mode <mode>", "Generation mode: sequential|parallel", "sequential").option("--no-save", "Do not append accepted ideas into memory.md").option("--json", "Emit machine-readable JSON output").option("-k, --api-key <key>", "Gemini API key (or set GEMINI_API_KEY env var)").action(async (options) => {
3685
+ tasteCommand.command("generate").description("Generate fresh ideas from taste and memory files").option("--storage <backend>", "Storage backend: markdown|db").option("--taste-file <path>", "Taste file path", "taste/taste.md").option("--memory-file <path>", "Memory file path", "taste/memory.md").option("--suggestions-file <path>", "Suggestions file path", "taste/suggestions.md").option("-n, --count <number>", "How many ideas to generate", "1").option("--memory-window <number>", "Recent memory window for generation context", "40").option("--mode <mode>", "Generation mode: sequential|parallel", "sequential").option("--no-save", "Do not append generated ideas into memory.md").option("--json", "Emit machine-readable JSON output").option("-k, --api-key <key>", "Gemini API key (or set GEMINI_API_KEY env var)").action(async (options) => {
3117
3686
  try {
3118
- ensureTasteWorkspaceFiles(options.tasteFile, options.memoryFile);
3687
+ const store = createStoreFromOptions(options);
3688
+ await store.ensureWorkspace();
3119
3689
  const apiKey = options.apiKey || process.env.GEMINI_API_KEY;
3120
3690
  if (!apiKey) {
3121
3691
  throw new Error("Missing Gemini API key. Set GEMINI_API_KEY or pass --api-key.");
3122
3692
  }
3123
- const tastePath = safeResolveFromCwd(options.tasteFile);
3124
- const memoryPath = safeResolveFromCwd(options.memoryFile);
3125
- const tasteContent = fs15.readFileSync(tastePath, "utf-8");
3126
- const memoryContent = fs15.existsSync(memoryPath) ? fs15.readFileSync(memoryPath, "utf-8") : "";
3127
- const memories = parseTasteMemoryMarkdown(memoryContent);
3693
+ const taste = await store.readTaste();
3694
+ const memories = await store.queryMemory({
3695
+ limit: Math.max(1, parseInt(options.memoryWindow, 10) || 40)
3696
+ });
3128
3697
  const mode = options.mode === "parallel" ? "parallel" : "sequential";
3129
3698
  const result = await simulateIdeas({
3130
- tasteContent,
3699
+ tasteContent: taste.content,
3131
3700
  memories,
3132
3701
  count: Math.max(1, parseInt(options.count, 10) || 1),
3133
3702
  mode,
@@ -3135,9 +3704,10 @@ tasteCommand.command("generate").description("Generate fresh ideas from taste an
3135
3704
  });
3136
3705
  let appendedIds = [];
3137
3706
  if (options.save && result.ideas.length > 0) {
3138
- const appended = appendMemoryEntries(memoryContent, result.ideas);
3139
- fs15.writeFileSync(memoryPath, appended.markdown, "utf-8");
3140
- appendedIds = appended.appendedIds;
3707
+ for (const idea of result.ideas) {
3708
+ const saved = await store.appendMemory({ ...idea, status: "generated" });
3709
+ appendedIds.push(saved.id);
3710
+ }
3141
3711
  }
3142
3712
  const output = {
3143
3713
  ...result,
@@ -3163,13 +3733,124 @@ tasteCommand.command("generate").description("Generate fresh ideas from taste an
3163
3733
  process.exit(1);
3164
3734
  }
3165
3735
  });
3166
- var tasteMemoryQueryCommand = new Command12("query").description("Query structured taste memories").option("--memory-file <path>", "Memory file path", "taste/memory.md").option("--tag <tag>", "Filter by tag").option("--since <date>", "Filter by created_at >= YYYY-MM-DD").option("--limit <number>", "Maximum number of results", "20").option("--text <text>", "Search term in title/summary/content").option("--json", "Emit machine-readable JSON output").action((options) => {
3736
+ tasteCommand.command("feedback").description("Append accept/reject feedback into memory").option("--storage <backend>", "Storage backend: markdown|db").option("--taste-file <path>", "Taste file path", "taste/taste.md").option("--memory-file <path>", "Memory file path", "taste/memory.md").option("--suggestions-file <path>", "Suggestions file path", "taste/suggestions.md").requiredOption("--decision <status>", "accepted|rejected").option("--idea-id <id>", "Optional original idea id").requiredOption("--title <text>", "Idea title").option("--format <text>", "Idea format", "daily-reel").option("--tags <csv>", "Comma separated tags").option("--reason-tags <csv>", "Comma separated reason tags").option("--summary <text>", "Optional summary", "").option("--content <text>", "Optional content", "").option("--notes <text>", "Optional reviewer notes", "").option("--json", "Emit machine-readable JSON output").action(async (options) => {
3167
3737
  try {
3168
- const fullPath = safeResolveFromCwd(options.memoryFile);
3169
- const content = fs15.existsSync(fullPath) ? fs15.readFileSync(fullPath, "utf-8") : "";
3170
- const items = parseTasteMemoryMarkdown(content);
3171
- const filtered = queryMemoryItems(items, {
3738
+ const decision = String(options.decision || "").toLowerCase();
3739
+ if (decision !== "accepted" && decision !== "rejected") {
3740
+ throw new Error("Invalid --decision. Use accepted|rejected.");
3741
+ }
3742
+ const store = createStoreFromOptions(options);
3743
+ await store.ensureWorkspace();
3744
+ const entry = {
3745
+ id: options.ideaId,
3746
+ title: options.title,
3747
+ format: options.format || "daily-reel",
3748
+ tags: parseCsvList(options.tags),
3749
+ freshnessTerms: [],
3750
+ summary: options.summary || "",
3751
+ content: options.content || "",
3752
+ status: decision,
3753
+ reasonTags: parseCsvList(options.reasonTags),
3754
+ notes: options.notes || ""
3755
+ };
3756
+ const saved = await store.appendMemory(entry);
3757
+ if (options.json) {
3758
+ console.log(JSON.stringify({ item: saved }, null, 2));
3759
+ return;
3760
+ }
3761
+ console.log(`Saved feedback: ${saved.id} (${saved.status || decision})`);
3762
+ } catch (error) {
3763
+ console.error(`Error: ${error.message}`);
3764
+ process.exit(1);
3765
+ }
3766
+ });
3767
+ tasteCommand.command("suggest").description("Generate pending taste suggestions from memory feedback").option("--storage <backend>", "Storage backend: markdown|db").option("--taste-file <path>", "Taste file path", "taste/taste.md").option("--memory-file <path>", "Memory file path", "taste/memory.md").option("--suggestions-file <path>", "Suggestions file path", "taste/suggestions.md").option("--memory-window <number>", "Recent memory window for suggestion generation", "50").option("--json", "Emit machine-readable JSON output").action(async (options) => {
3768
+ try {
3769
+ const store = createStoreFromOptions(options);
3770
+ await store.ensureWorkspace();
3771
+ const taste = await store.readTaste();
3772
+ const memories = await store.queryMemory({
3773
+ limit: Math.max(1, parseInt(options.memoryWindow, 10) || 50)
3774
+ });
3775
+ const draft = buildTasteSuggestionFromMemory(taste.content, memories);
3776
+ const suggestion = await store.appendSuggestion({
3777
+ title: draft.title,
3778
+ summary: draft.summary,
3779
+ rationale: draft.rationale,
3780
+ patch: draft.patch,
3781
+ proposedTasteContent: draft.proposedTasteContent,
3782
+ baseVersion: taste.version
3783
+ });
3784
+ if (options.json) {
3785
+ console.log(JSON.stringify({ suggestion }, null, 2));
3786
+ return;
3787
+ }
3788
+ console.log(`Created suggestion: ${suggestion.id} (${suggestion.status})`);
3789
+ } catch (error) {
3790
+ console.error(`Error: ${error.message}`);
3791
+ process.exit(1);
3792
+ }
3793
+ });
3794
+ tasteCommand.command("apply").description("Apply or reject a suggestion entry").requiredOption("--suggestion <id>", "Suggestion id").option("--reject", "Mark suggestion as rejected without applying").option("--note <text>", "Optional reviewer note").option("--storage <backend>", "Storage backend: markdown|db").option("--taste-file <path>", "Taste file path", "taste/taste.md").option("--memory-file <path>", "Memory file path", "taste/memory.md").option("--suggestions-file <path>", "Suggestions file path", "taste/suggestions.md").option("--json", "Emit machine-readable JSON output").action(async (options) => {
3795
+ try {
3796
+ const store = createStoreFromOptions(options);
3797
+ await store.ensureWorkspace();
3798
+ const suggestions = await store.listSuggestions();
3799
+ const target = suggestions.find((item) => item.id === options.suggestion);
3800
+ if (!target) {
3801
+ throw new Error(`Suggestion not found: ${options.suggestion}`);
3802
+ }
3803
+ if (target.status !== "pending") {
3804
+ throw new Error(`Suggestion ${target.id} is already ${target.status}.`);
3805
+ }
3806
+ if (options.reject) {
3807
+ const updated2 = await store.updateSuggestionStatus(target.id, "rejected", { reviewNote: options.note });
3808
+ if (options.json) {
3809
+ console.log(JSON.stringify({ suggestion: updated2 }, null, 2));
3810
+ return;
3811
+ }
3812
+ console.log(`Rejected suggestion: ${updated2.id}`);
3813
+ return;
3814
+ }
3815
+ if (!target.proposedTasteContent.trim()) {
3816
+ throw new Error("Suggestion is missing proposed taste content.");
3817
+ }
3818
+ await store.writeTaste(target.proposedTasteContent, target.baseVersion);
3819
+ const updated = await store.updateSuggestionStatus(target.id, "applied", { reviewNote: options.note });
3820
+ if (options.json) {
3821
+ console.log(JSON.stringify({ suggestion: updated }, null, 2));
3822
+ return;
3823
+ }
3824
+ console.log(`Applied suggestion: ${updated.id}`);
3825
+ } catch (error) {
3826
+ console.error(`Error: ${error.message}`);
3827
+ process.exit(1);
3828
+ }
3829
+ });
3830
+ tasteCommand.command("read").description("Read the taste file and output its content (useful for agents)").option("--storage <backend>", "Storage backend: markdown|db").option("--taste-file <path>", "Taste file path", "taste/taste.md").option("--memory-file <path>", "Memory file path", "taste/memory.md").option("--suggestions-file <path>", "Suggestions file path", "taste/suggestions.md").option("--json", "Emit machine-readable JSON output").action(async (options) => {
3831
+ try {
3832
+ const store = createStoreFromOptions(options);
3833
+ const taste = await store.readTaste();
3834
+ if (!taste.content) {
3835
+ const fullPath = safeResolveFromCwd(options.tasteFile);
3836
+ console.warn(`Taste file not found or empty at: ${fullPath}`);
3837
+ }
3838
+ if (options.json) {
3839
+ console.log(JSON.stringify({ content: taste.content, version: taste.version }, null, 2));
3840
+ } else {
3841
+ console.log(taste.content);
3842
+ }
3843
+ } catch (error) {
3844
+ console.error(`Error: ${error.message}`);
3845
+ process.exit(1);
3846
+ }
3847
+ });
3848
+ var tasteMemoryQueryCommand = new Command12("query").description("Query structured taste memories").option("--storage <backend>", "Storage backend: markdown|db").option("--taste-file <path>", "Taste file path", "taste/taste.md").option("--memory-file <path>", "Memory file path", "taste/memory.md").option("--suggestions-file <path>", "Suggestions file path", "taste/suggestions.md").option("--tag <tag>", "Filter by tag").option("--status <status>", "Filter by status (generated|accepted|rejected)").option("--since <date>", "Filter by created_at >= YYYY-MM-DD").option("--limit <number>", "Maximum number of results", "20").option("--text <text>", "Search term in title/summary/content").option("--json", "Emit machine-readable JSON output").action(async (options) => {
3849
+ try {
3850
+ const store = createStoreFromOptions(options);
3851
+ const filtered = await store.queryMemory({
3172
3852
  tag: options.tag,
3853
+ status: options.status,
3173
3854
  since: options.since,
3174
3855
  text: options.text,
3175
3856
  limit: parseInt(options.limit, 10) || 20
@@ -3184,6 +3865,7 @@ var tasteMemoryQueryCommand = new Command12("query").description("Query structur
3184
3865
  }
3185
3866
  for (const item of filtered) {
3186
3867
  console.log(`${item.id} | ${item.createdAt} | ${item.title}`);
3868
+ if (item.status) console.log(` status: ${item.status}`);
3187
3869
  if (item.tags.length) console.log(` tags: ${item.tags.join(", ")}`);
3188
3870
  if (item.summary) console.log(` summary: ${item.summary}`);
3189
3871
  }
@@ -3210,8 +3892,8 @@ function createFeedbackCommand(telemetry2) {
3210
3892
  }
3211
3893
 
3212
3894
  // src/cli/index.ts
3213
- var __filename4 = fileURLToPath4(import.meta.url);
3214
- var __dirname4 = path16.dirname(__filename4);
3895
+ var __filename5 = fileURLToPath5(import.meta.url);
3896
+ var __dirname5 = path18.dirname(__filename5);
3215
3897
  var program = new Command14();
3216
3898
  var telemetry = new PostHogTelemetryService();
3217
3899
  var commandStartTimes = /* @__PURE__ */ new WeakMap();
@@ -3228,12 +3910,12 @@ program.command("edit [path]").alias("start").alias("init").description("Start t
3228
3910
  const port = parseInt(options.port);
3229
3911
  let sceneFile;
3230
3912
  if (pathArg) {
3231
- const targetPath = path16.resolve(process.cwd(), pathArg);
3232
- if (fs16.existsSync(targetPath)) {
3233
- const stats = fs16.statSync(targetPath);
3913
+ const targetPath = path18.resolve(process.cwd(), pathArg);
3914
+ if (fs17.existsSync(targetPath)) {
3915
+ const stats = fs17.statSync(targetPath);
3234
3916
  if (stats.isFile()) {
3235
- sceneFile = path16.basename(targetPath);
3236
- const dir = path16.dirname(targetPath);
3917
+ sceneFile = path18.basename(targetPath);
3918
+ const dir = path18.dirname(targetPath);
3237
3919
  process.chdir(dir);
3238
3920
  console.log(`Opening scene file: ${sceneFile}`);
3239
3921
  } else if (stats.isDirectory()) {
@@ -3241,28 +3923,28 @@ program.command("edit [path]").alias("start").alias("init").description("Start t
3241
3923
  }
3242
3924
  } else {
3243
3925
  if (pathArg.endsWith(".json")) {
3244
- sceneFile = path16.basename(pathArg);
3245
- const dir = path16.dirname(path16.resolve(process.cwd(), pathArg));
3246
- if (!fs16.existsSync(dir)) {
3926
+ sceneFile = path18.basename(pathArg);
3927
+ const dir = path18.dirname(path18.resolve(process.cwd(), pathArg));
3928
+ if (!fs17.existsSync(dir)) {
3247
3929
  console.log(`Creating directory ${dir}...`);
3248
- fs16.mkdirSync(dir, { recursive: true });
3930
+ fs17.mkdirSync(dir, { recursive: true });
3249
3931
  }
3250
3932
  process.chdir(dir);
3251
3933
  console.log(`Will create new scene file: ${sceneFile}`);
3252
3934
  } else {
3253
3935
  console.log(`Creating directory ${targetPath}...`);
3254
- fs16.mkdirSync(targetPath, { recursive: true });
3936
+ fs17.mkdirSync(targetPath, { recursive: true });
3255
3937
  process.chdir(targetPath);
3256
3938
  }
3257
3939
  }
3258
3940
  }
3259
3941
  const cwd = process.cwd();
3260
3942
  console.log(`Starting Feedeas in ${cwd}...`);
3261
- let staticRoot = path16.resolve(__dirname4, "../../dist/ui");
3262
- if (!fs16.existsSync(staticRoot)) {
3263
- staticRoot = path16.resolve(__dirname4, "../ui");
3943
+ let staticRoot = path18.resolve(__dirname5, "../../dist/ui");
3944
+ if (!fs17.existsSync(staticRoot)) {
3945
+ staticRoot = path18.resolve(__dirname5, "../ui");
3264
3946
  }
3265
- if (!fs16.existsSync(staticRoot)) {
3947
+ if (!fs17.existsSync(staticRoot)) {
3266
3948
  console.warn(`Warning: UI assets not found at ${staticRoot}. Did you run 'bun run build'?`);
3267
3949
  }
3268
3950
  const app2 = createServer(staticRoot);
@@ -3365,7 +4047,12 @@ Guided Flow (Agent-Friendly, End-to-End):
3365
4047
  Agent Conversation Example:
3366
4048
  User: Create a nice reel about small life habits that heal us.
3367
4049
  Agent: I'll draft a short storyline, generate assets, build the scene, validate, and render.
3368
- Agent: Let me create the narration script first.
4050
+ Agent: Let me create the narration script first, first check if the user has any taste preferences.
4051
+ Agent (tool): feedeas taste memory query --tag "content preferences" --limit 5
4052
+ Agent: Based on past preferences, I'll go with a warm, hopeful tone.
4053
+ Agent (tool): feedeas audio "Life changes in small daily choices. A morning ritual of mindfulness can set the tone for your day..." -o narration.mp3 --no-transcribe
4054
+ Agent: Now I'll generate some visuals to match the narration.
4055
+ Agent (tool): feedeas example documentary > scene.json
3369
4056
  Agent (tool): feedeas imagine "warm cinematic kitchen morning..." --aspect-ratio 9:16 -o scene1.png
3370
4057
  Agent (tool): feedeas imagine "A person meditating in a park..." --aspect-ratio 9:16 -o scene2.png
3371
4058
  Agent (tool): feedeas audio "Life changes in small daily choices..." -o narration.mp3 --no-transcribe
@@ -3449,8 +4136,12 @@ Interactive Workflow (for Humans):
3449
4136
  $ feedeas snap 2.5 -o frame.png # Take snapshot at 2.5s
3450
4137
 
3451
4138
  Taste Workflow:
3452
- $ feedeas taste
4139
+ $ feedeas taste # Start UI Workspace
4140
+ $ feedeas taste read --json # Read instructions/taste definitions
3453
4141
  $ feedeas taste generate -n 3 --mode sequential --json
4142
+ $ feedeas taste feedback --decision accepted --title "Idea title"
4143
+ $ feedeas taste suggest --json
4144
+ $ feedeas taste apply --suggestion <id>
3454
4145
  $ feedeas taste memory query --tag podcast --limit 10
3455
4146
  `);
3456
4147
  if (!process.argv.slice(2).length) {