@officexapp/vidfarm-devcli 0.21.11 → 0.21.14

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 (28) hide show
  1. package/.agents/skills/hyperframes-core/SKILL.md +2 -0
  2. package/.agents/skills/hyperframes-creative/SKILL.md +1 -0
  3. package/.agents/skills/hyperframes-creative/references/beat-direction.md +17 -0
  4. package/.agents/skills/vidfarm-director/SKILL.md +4 -0
  5. package/.agents/skills/vidfarm-director/recipes/local-edit-render-approve.md +1 -1
  6. package/.agents/skills/vidfarm-director/references/automation-and-local-dev.md +23 -6
  7. package/.agents/skills/vidfarm-director/references/core-workflows.md +1 -1
  8. package/.agents/skills/vidfarm-director/references/editor-workflows.md +15 -2
  9. package/.agents/skills/vidfarm-director/references/primitives.md +1 -1
  10. package/.agents/skills/vidfarm-director/references/rest-api.md +1 -1
  11. package/.agents/skills/vidfarm-media/SKILL.md +16 -6
  12. package/README.md +2 -2
  13. package/SKILL.director.md +45 -11
  14. package/demo/dist/app.js +247 -226
  15. package/dist/src/cli.js +198 -30
  16. package/dist/src/devcli/composition-edit.js +99 -23
  17. package/dist/src/devcli/cost-mode.js +13 -4
  18. package/dist/src/devcli/doctor.js +65 -9
  19. package/dist/src/devcli/local-frontend-server.js +342 -50
  20. package/dist/src/devcli/port-utils.js +43 -0
  21. package/dist/src/devcli/process-scan.js +173 -0
  22. package/package.json +3 -1
  23. package/public/serve-shells/editor.html +75 -13
  24. package/public/serve-shells/library-files.html +75 -13
  25. package/public/serve-shells/library-raws.html +75 -13
  26. package/public/serve-shells/tools-clipper.html +75 -13
  27. package/public/serve-shells/tools-image.html +75 -13
  28. package/public/serve-shells/tools-video.html +75 -13
@@ -2529,12 +2529,25 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
2529
2529
  // Desktop-agents nudge banner: a full-width alert pinned to the top of the
2530
2530
  // /editor and /chat surfaces pointing to the setup guide. Both surfaces embed
2531
2531
  // this chrome script, so gate strictly by pathname. Dismissible per session.
2532
+ // The nudge is aimed at the DIRECTOR (the paid operator who drives Vidfarm with
2533
+ // a desktop AI agent). A client reviewing a shared video in the editor should
2534
+ // NOT be told to go install Claude Code / Codex — so on the editor we suppress
2535
+ // it for free-tier (client / reviewer) sessions, read from the editor boot JSON.
2532
2536
  (function mountDesktopAgentsBanner() {
2533
2537
  try {
2534
2538
  var path = location.pathname;
2535
2539
  var onEditor = path === '/editor' || path.indexOf('/editor/') === 0;
2536
2540
  var onChat = path === '/chat' || path.indexOf('/chat/') === 0;
2537
2541
  if (!onEditor && !onChat) return;
2542
+ if (onEditor) {
2543
+ var bootEl = document.getElementById('hf-boot');
2544
+ if (bootEl) {
2545
+ try {
2546
+ var boot = JSON.parse(bootEl.textContent || '{}');
2547
+ if (boot && boot.freeTier) return;
2548
+ } catch (e) {}
2549
+ }
2550
+ }
2538
2551
  if (document.querySelector('.vf-topbanner')) return;
2539
2552
  if (sessionStorage.getItem('vf-topbanner-dismissed') === '1') return;
2540
2553
  var GUIDE = '/blog/desktop-ai-agents';
@@ -2740,6 +2753,23 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
2740
2753
  setLeftMode(''); // show the conversation, not the Files/History drawer
2741
2754
  openThread(id);
2742
2755
  }
2756
+ var hadHandoff = !!handoffThread;
2757
+ // Sticky active thread per template so a page refresh reopens the SAME
2758
+ // conversation instead of a blank chat (server routes deliberately never
2759
+ // attach ?thread= to editor URLs — the browser owns "which chat is active").
2760
+ function activeThreadKey() { return 'rk-chat-active:' + TEMPLATE_ID; }
2761
+ function saveActiveThread(id) {
2762
+ try { if (id) localStorage.setItem(activeThreadKey(), id); else localStorage.removeItem(activeThreadKey()); } catch (e) {}
2763
+ }
2764
+ var restoredActive = false;
2765
+ function restoreActiveThread() {
2766
+ if (restoredActive || hadHandoff) return;
2767
+ restoredActive = true;
2768
+ if (convo.length) return; // user already chatting — don't clobber
2769
+ var saved = null;
2770
+ try { saved = localStorage.getItem(activeThreadKey()); } catch (e) {}
2771
+ if (saved) openThread(saved);
2772
+ }
2743
2773
  var busy = false;
2744
2774
  var pendingAbort = null; // AbortController for the in-flight reply (Stop button)
2745
2775
 
@@ -2790,14 +2820,20 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
2790
2820
  // outgoing user turn — verbatim to what the SPA's own chat sends — so the agent
2791
2821
  // knows which fork to read (video_context) and mutate (editor_action). Only the
2792
2822
  // /editor dock has this bridge; elsewhere it returns ''.
2823
+ // Async: the Option-B bridge's getSnapshot() returns a PROMISE (it re-reads the
2824
+ // composition through the files API). The old sync call JSON.stringify'd the
2825
+ // Promise itself, sending the model a literal "{}" editor_context — no fork id,
2826
+ // no layers, no viral DNA. Always resolve before serializing.
2793
2827
  function editorContextBlock() {
2794
- if (!isEditorDock) return '';
2828
+ if (!isEditorDock) return Promise.resolve('');
2795
2829
  var bridge = (typeof window !== 'undefined') ? window.__vidfarmEditorAction : null;
2796
- if (!bridge || typeof bridge.getSnapshot !== 'function') return '';
2830
+ if (!bridge || typeof bridge.getSnapshot !== 'function') return Promise.resolve('');
2797
2831
  var snap; try { snap = bridge.getSnapshot(); } catch (e) { snap = null; }
2798
- if (!snap) return '';
2799
- try { return '\n\n<editor_context>\n' + JSON.stringify(snap, null, 2) + '\n</editor_context>'; }
2800
- catch (e) { return ''; }
2832
+ return Promise.resolve(snap).then(function (s) {
2833
+ if (!s) return '';
2834
+ try { return '\n\n<editor_context>\n' + JSON.stringify(s, null, 2) + '\n</editor_context>'; }
2835
+ catch (e) { return ''; }
2836
+ }, function () { return ''; });
2801
2837
  }
2802
2838
  function loadBoot() {
2803
2839
  if (BOOT_STATE === 'ready' || BOOT_STATE === 'loading') return;
@@ -2820,6 +2856,7 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
2820
2856
  loadThreads();
2821
2857
  if (leftMode() === 'cloud') loadTasks();
2822
2858
  consumeHandoff();
2859
+ restoreActiveThread();
2823
2860
  })
2824
2861
  .catch(function () { BOOT_STATE = 'error'; });
2825
2862
  }
@@ -3373,8 +3410,10 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
3373
3410
  setBusy(true);
3374
3411
  if (!threadId) threadId = genId('thread');
3375
3412
  // Attach a fresh <editor_context> to the current (last) user turn only, so the
3376
- // model sees the composition state without bloating persisted history.
3377
- var ctxBlock = editorContextBlock();
3413
+ // model sees the composition state without bloating persisted history. The
3414
+ // block resolves asynchronously (files-API read) — wait for it before building
3415
+ // the outgoing messages so the model actually receives the composition state.
3416
+ editorContextBlock().then(function (ctxBlock) {
3378
3417
  // Attachments (pasted files OR files picked from the directory explorer) must
3379
3418
  // ride in the model messages as file content parts + a URL text line — the
3380
3419
  // backend only feeds the model messages[].content, NOT user_message.attachments
@@ -3392,6 +3431,9 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
3392
3431
  }
3393
3432
  return { role: m.role, content: content };
3394
3433
  });
3434
+ // The send is what turns a freshly minted thread id into a real saved
3435
+ // thread — make it the sticky-restore target from this moment on.
3436
+ saveActiveThread(threadId);
3395
3437
  var body = {
3396
3438
  messages: outMessages,
3397
3439
  thread_id: threadId,
@@ -3455,6 +3497,7 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
3455
3497
  if (!API_KEY) msg = msg + '\n\nAdd an AI provider key in Settings to chat on your own keys.';
3456
3498
  view.fail(msg); setBusy(false); if (input) input.focus();
3457
3499
  });
3500
+ }); // end editorContextBlock().then
3458
3501
  }
3459
3502
 
3460
3503
  function resetConversation() {
@@ -3708,10 +3751,25 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
3708
3751
  // fall through to chat-attach for folders / non-placeable files.
3709
3752
  if (isEditorDock && it && it.viewUrl) {
3710
3753
  var bridge = (typeof window !== 'undefined') ? window.__vidfarmEditorAction : null;
3711
- if (bridge && typeof bridge.placeMediaAtPlayhead === 'function') {
3712
- var res = bridge.placeMediaAtPlayhead({ url: it.viewUrl, name: it.name, contentType: it.contentType });
3713
- if (res && res.ok) { editorPlaceToast('Added \u201c' + (it.name || 'media') + '\u201d to the timeline'); return; }
3714
- // Non-placeable (folder / doc / not-ready): fall through to chat-attach.
3754
+ // Placeability must be decided SYNCHRONOUSLY (the Option-B bridge returns a
3755
+ // Promise, so we can't branch on its result to decide chat-attach fallback —
3756
+ // checking ".ok" on the Promise made EVERY click fall through, placing the
3757
+ // media AND attaching it to chat, with no toast). Only image/video/audio go
3758
+ // on the timeline; folders/docs still fall through to chat-attach.
3759
+ var ct = String(it.contentType || '');
3760
+ if (bridge && typeof bridge.placeMediaAtPlayhead === 'function'
3761
+ && (ct.indexOf('image/') === 0 || ct.indexOf('video/') === 0 || ct.indexOf('audio/') === 0)) {
3762
+ var placedName = it.name || 'media';
3763
+ var settlePlace = function (r) {
3764
+ if (r && r.ok) { editorPlaceToast('Added \u201c' + placedName + '\u201d to the timeline'); }
3765
+ else { editorPlaceToast('Couldn\u2019t add \u201c' + placedName + '\u201d' + ((r && r.error) ? ': ' + r.error : ''), true); }
3766
+ };
3767
+ try {
3768
+ var res = bridge.placeMediaAtPlayhead({ url: it.viewUrl, name: it.name, contentType: it.contentType });
3769
+ if (res && typeof res.then === 'function') { res.then(settlePlace, function (e) { settlePlace({ ok: false, error: (e && e.message) ? e.message : String(e) }); }); }
3770
+ else { settlePlace(res); }
3771
+ } catch (e) { settlePlace({ ok: false, error: (e && e.message) ? e.message : String(e) }); }
3772
+ return;
3715
3773
  }
3716
3774
  }
3717
3775
  // Files-only drawer (opened from the /chat page) has no chat composer of its
@@ -3828,7 +3886,11 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
3828
3886
  return wrap;
3829
3887
  }
3830
3888
  function setActiveThread(id) {
3831
- threadId = id;
3889
+ // null = "fresh unsaved chat": KEEP the freshly minted threadId (sends must
3890
+ // always carry a real thread_id or the server silently skips persistence)
3891
+ // and clear the sticky restore key; a real id becomes both current + sticky.
3892
+ if (id) { threadId = id; saveActiveThread(id); }
3893
+ else { saveActiveThread(null); }
3832
3894
  if (!histBody) return;
3833
3895
  var rows = histBody.querySelectorAll('.rk-aichat-frow');
3834
3896
  for (var i = 0; i < rows.length; i++) rows[i].classList.toggle('is-active', rows[i].getAttribute('data-id') === id);
@@ -3921,7 +3983,7 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
3921
3983
  .then(function (r) {
3922
3984
  if (!r.ok && r.status !== 404) throw new Error('http ' + r.status);
3923
3985
  threads = threads.filter(function (t) { return t.id !== id; });
3924
- if (id === threadId) resetConversation();
3986
+ if (id === threadId) { resetConversation(); saveActiveThread(null); }
3925
3987
  renderHistory();
3926
3988
  })
3927
3989
  .catch(function () {});