Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion .githooks/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,14 @@ touches_consent=$(printf '%s\n' "$staged" | grep -E '(^|/)(play\.html|scripts/ch
# play.html bakes creator sample results into #a= shares so keyless recipients see what the app makes;
# index.html's buildShareUrl("app") packs the same envelope (samples + lang parity, asserted by the checker).
touches_sharesamples=$(printf '%s\n' "$staged" | grep -E '(^|/)(index\.html|play\.html|scripts/check-share-samples\.mjs)$' || true)
# llms-full.txt is generated and says "do not edit by hand", so only a re-run of the generator keeps it
# true. It once advertised the retired draw node for days after every source had dropped it.
touches_llmsfull=$(printf '%s\n' "$staged" | grep -E '(^|/)(llms-full\.txt|llms\.txt|README\.md|index\.html|scripts/(gen-llms-full|check-llms-full)\.mjs)$' || true)

# Nothing to check unless an .html file, one of the checkers, or a guarded doc changed.
# Every touches_* variable belongs in this chain — a gate left out of it never runs on a
# commit that stages only its own files, which is exactly the commit it exists to catch.
[ -z "$staged_html" ] && [ -z "$touches_launchfacts" ] && [ -z "$touches_njseditor" ] && [ -z "$touches_llmgate" ] && [ -z "$touches_export" ] && [ -z "$touches_login" ] && [ -z "$touches_sw" ] && [ -z "$touches_compat" ] && [ -z "$touches_run" ] && [ -z "$touches_jsparity" ] && [ -z "$touches_seedcache" ] && [ -z "$touches_imgports" ] && [ -z "$touches_langpages" ] && [ -z "$touches_editorports" ] && [ -z "$touches_settings" ] && [ -z "$touches_quickadd" ] && [ -z "$touches_i18n" ] && [ -z "$touches_updates" ] && [ -z "$touches_createapp" ] && [ -z "$touches_stale" ] && [ -z "$touches_vresume" ] && [ -z "$touches_lipsync" ] && [ -z "$touches_aresume" ] && [ -z "$touches_share" ] && [ -z "$touches_pricing" ] && [ -z "$touches_combine" ] && [ -z "$touches_pacing" ] && [ -z "$touches_graphpersist" ] && [ -z "$touches_graphsigs" ] && [ -z "$touches_describeapply" ] && [ -z "$touches_authlc" ] && [ -z "$touches_mediapriv" ] && [ -z "$touches_verhist" ] && [ -z "$touches_rungating" ] && [ -z "$touches_appsstorage" ] && [ -z "$touches_linkerr" ] && [ -z "$touches_deploy" ] && [ -z "$touches_hiddencss" ] && [ -z "$touches_connect" ] && [ -z "$touches_undo" ] && [ -z "$touches_costaccrue" ] && [ -z "$touches_resize" ] && [ -z "$touches_catalog" ] && [ -z "$touches_upscale" ] && [ -z "$touches_drift" ] && [ -z "$touches_promptcaps" ] && [ -z "$touches_consent" ] && [ -z "$touches_sharesamples" ] && [ -z "$touches_examples" ] && exit 0
[ -z "$staged_html" ] && [ -z "$touches_launchfacts" ] && [ -z "$touches_njseditor" ] && [ -z "$touches_llmgate" ] && [ -z "$touches_export" ] && [ -z "$touches_login" ] && [ -z "$touches_sw" ] && [ -z "$touches_compat" ] && [ -z "$touches_run" ] && [ -z "$touches_jsparity" ] && [ -z "$touches_seedcache" ] && [ -z "$touches_imgports" ] && [ -z "$touches_langpages" ] && [ -z "$touches_editorports" ] && [ -z "$touches_settings" ] && [ -z "$touches_quickadd" ] && [ -z "$touches_i18n" ] && [ -z "$touches_updates" ] && [ -z "$touches_createapp" ] && [ -z "$touches_stale" ] && [ -z "$touches_vresume" ] && [ -z "$touches_lipsync" ] && [ -z "$touches_aresume" ] && [ -z "$touches_share" ] && [ -z "$touches_pricing" ] && [ -z "$touches_combine" ] && [ -z "$touches_pacing" ] && [ -z "$touches_graphpersist" ] && [ -z "$touches_graphsigs" ] && [ -z "$touches_describeapply" ] && [ -z "$touches_authlc" ] && [ -z "$touches_mediapriv" ] && [ -z "$touches_verhist" ] && [ -z "$touches_rungating" ] && [ -z "$touches_appsstorage" ] && [ -z "$touches_linkerr" ] && [ -z "$touches_deploy" ] && [ -z "$touches_hiddencss" ] && [ -z "$touches_connect" ] && [ -z "$touches_undo" ] && [ -z "$touches_costaccrue" ] && [ -z "$touches_resize" ] && [ -z "$touches_catalog" ] && [ -z "$touches_upscale" ] && [ -z "$touches_drift" ] && [ -z "$touches_promptcaps" ] && [ -z "$touches_consent" ] && [ -z "$touches_sharesamples" ] && [ -z "$touches_llmsfull" ] && [ -z "$touches_examples" ] && exit 0
if ! command -v node >/dev/null 2>&1; then
echo "pre-commit: node not found, skipping HTML JS checks" >&2
exit 0
Expand Down Expand Up @@ -502,3 +505,14 @@ fi
if [ -n "$touches_launchfacts" ]; then
node "$root/scripts/check-launch-facts.mjs"
fi

# 42) LLMS-FULL FRESHNESS: llms-full.txt is the single-file reference agents fetch, it is generated,
# and it says "do not edit by hand" — so nothing keeps it true except somebody re-running the
# generator. Nobody did, and it advertised the retired draw node after every source had dropped
# it. Offline: regenerates the sections sourced from THIS repo (llms.txt, README.md, index.html)
# and diffs them byte for byte, then asserts every node type named in the two libraries'
# supported-node tables is still registered in index.html. Regenerate with
# `node scripts/gen-llms-full.mjs`. CI diffs the library sections too, against fresh checkouts.
if [ -n "$touches_llmsfull" ]; then
node "$root/scripts/check-llms-full.mjs"
fi
16 changes: 16 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ jobs:
repository: nanoodlecom/nanoodle-js
path: nanoodle-js-sibling

- name: Checkout nanoodle-py (llms-full.txt source)
uses: actions/checkout@v4
with:
repository: nanoodlecom/nanoodle-py
path: nanoodle-py-sibling

- uses: actions/setup-node@v4
with:
node-version: 22
Expand All @@ -45,3 +51,13 @@ jobs:
fails=$((fails+1))
fi
exit $fails

# The pre-commit guard can only diff the sections llms-full.txt takes from THIS
# repo; the two library sections need the sibling READMEs. Here they are checked
# out fresh, so this is the check that catches a library README moving on without
# a regeneration. Reads the siblings from disk — no clone, no network.
- name: llms-full.txt matches the library READMEs
env:
NANOODLE_JS: ${{ github.workspace }}/nanoodle-js-sibling
NANOODLE_PY: ${{ github.workspace }}/nanoodle-py-sibling
run: node scripts/gen-llms-full.mjs --check
2 changes: 1 addition & 1 deletion growth/i18n-drafts/zenn-comfyui-ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ https://nanoodle.com/ja/ を開くとエディタがそのまま起動します

### 2. ノードを置いてつなぐ

テキスト・画像・動画・音声のモデルノードに加えて、Draw(手描き入力)、
テキスト・画像・動画・音声のモデルノードに加えて、Join(テキスト結合)、
Resize、Choice、Comment などのユーティリティノードがあります。
たとえば:

Expand Down
28 changes: 18 additions & 10 deletions guide/graph-format.html
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ <h2>Ports and wires</h2>
<ul>
<li><strong>Declared inputs</strong> — fixed per type (e.g. <code>inpaint</code> takes
<code>image</code> and <code>mask</code>), plus dynamic families that grow as you wire
more: <code>img1</code>, <code>img2</code>, … (LLM/Draw vision references),
more: <code>img1</code>, <code>img2</code>, … (LLM vision references),
<code>image</code>, <code>image2</code>, … (Edit references), <code>ref1</code>, …
(Text→Video references), <code>clip1</code>, … (Combine), and <code>endframe</code>
(Image→Video) / <code>audio</code> (LLM audio input).</li>
Expand All @@ -143,8 +143,11 @@ <h2>Ports and wires</h2>

<h2>Node types</h2>
<p>"local" runs without any network call; "NanoGPT" calls the API (and spends);
"browser-only" nodes do media processing with browser APIs — they work in the app but the
headless executors reject them up front, before any paid call.</p>
"local media" nodes process media on your own machine and never spend. The browser app
does that work with canvas, Web Audio and MediaRecorder. The headless executors run the
same 6 node types, but some of them need <code>ffmpeg</code> and <code>ffprobe</code> on
<code>PATH</code> — see <a href="/guide/run-headless#what-runs-where">what runs where</a>
for the per-node detail.</p>
<div class="tablewrap"><table>
<tr><th>type</th><th>node</th><th>runs</th><th>inputs</th><th>outputs</th><th>key fields</th></tr>
<tr><td><code>text</code></td><td>Text</td><td>local</td><td>—</td><td>text</td><td><code>text</code></td></tr>
Expand All @@ -155,23 +158,22 @@ <h2>Node types</h2>
<tr><td><code>join</code></td><td>Join</td><td>local</td><td>a, b (text)</td><td>text</td><td><code>sep</code></td></tr>
<tr><td><code>llm</code></td><td>LLM</td><td>NanoGPT</td><td>img1… (image), audio</td><td>text</td><td><code>model</code>, <code>system</code>, <code>prompt</code>, <code>temperature</code>, <code>maxTokens</code>, <code>format</code>, <code>reasoningEffort</code>, <code>showThinking</code></td></tr>
<tr><td><code>image</code></td><td>Image</td><td>NanoGPT</td><td>—</td><td>image</td><td><code>model</code>, <code>prompt</code>, <code>size</code>, <code>variations</code>, <code>seed</code>, <code>customCivitaiAir</code>, <code>negativePrompt</code></td></tr>
<tr><td><code>draw</code></td><td>Draw</td><td>NanoGPT</td><td>img1… (image)</td><td>image, text</td><td><code>model</code>, <code>system</code>, <code>prompt</code>, <code>showThinking</code></td></tr>
<tr><td><code>edit</code></td><td>Edit</td><td>NanoGPT</td><td>image, image2…</td><td>image</td><td><code>model</code>, <code>prompt</code>, <code>size</code>, <code>seed</code></td></tr>
<tr><td><code>inpaint</code></td><td>Inpaint</td><td>NanoGPT</td><td>image, mask</td><td>image</td><td><code>model</code>, <code>prompt</code>, <code>size</code>, <code>seed</code></td></tr>
<tr><td><code>resize</code></td><td>Resize / crop</td><td>browser-only</td><td>image</td><td>image</td><td><code>mode</code>, <code>width</code>, <code>height</code></td></tr>
<tr><td><code>resize</code></td><td>Resize / crop</td><td>local media</td><td>image</td><td>image</td><td><code>mode</code>, <code>width</code>, <code>height</code></td></tr>
<tr><td><code>vision</code></td><td>Vision</td><td>NanoGPT</td><td>image</td><td>text</td><td><code>model</code>, <code>q</code></td></tr>
<tr><td><code>tvideo</code></td><td>Text→Video</td><td>NanoGPT</td><td>ref1… (image)</td><td>video</td><td><code>model</code>, <code>prompt</code>, <code>duration</code>, <code>aspect</code>, <code>resolution</code>, <code>modelOpts</code></td></tr>
<tr><td><code>ivideo</code></td><td>Image→Video</td><td>NanoGPT</td><td>image, endframe</td><td>video</td><td><code>model</code>, <code>prompt</code>, <code>duration</code>, <code>aspect</code>, <code>resolution</code>, <code>modelOpts</code></td></tr>
<tr><td><code>vedit</code></td><td>Video edit</td><td>NanoGPT</td><td>video</td><td>video</td><td><code>model</code>, <code>prompt</code>, <code>resolution</code>, <code>modelOpts</code></td></tr>
<tr><td><code>vframes</code></td><td>Video → frames</td><td>browser-only</td><td>video</td><td>frame1…frameN (image)</td><td><code>frames</code>, <code>gap</code>, <code>dir</code></td></tr>
<tr><td><code>combine</code></td><td>Combine videos</td><td>browser-only</td><td>clip1… (video)</td><td>video</td><td><code>dedup</code></td></tr>
<tr><td><code>soundtrack</code></td><td>Soundtrack</td><td>browser-only</td><td>video, audio</td><td>video</td><td><code>loop</code></td></tr>
<tr><td><code>vframes</code></td><td>Video → frames</td><td>local media</td><td>video</td><td>frame1…frameN (image)</td><td><code>frames</code>, <code>gap</code>, <code>dir</code></td></tr>
<tr><td><code>combine</code></td><td>Combine videos</td><td>local media</td><td>clip1… (video)</td><td>video</td><td><code>dedup</code></td></tr>
<tr><td><code>soundtrack</code></td><td>Soundtrack</td><td>local media</td><td>video, audio</td><td>video</td><td><code>loop</code></td></tr>
<tr><td><code>lipsync</code></td><td>Avatar / lipsync</td><td>NanoGPT</td><td>image, audio</td><td>video</td><td><code>model</code>, <code>prompt</code>, <code>resolution</code>, <code>modelOpts</code></td></tr>
<tr><td><code>music</code></td><td>Music</td><td>NanoGPT</td><td>—</td><td>audio</td><td><code>model</code>, <code>prompt</code>, <code>lyrics</code>, <code>instrumental</code>, <code>duration</code>, <code>negative_prompt</code>, <code>seed</code>, <code>extraJson</code></td></tr>
<tr><td><code>remix</code></td><td>Remix audio</td><td>NanoGPT</td><td>audio</td><td>audio</td><td><code>model</code>, <code>prompt</code>, <code>lyrics</code>, <code>duration</code>, <code>extraJson</code></td></tr>
<tr><td><code>tts</code></td><td>Speech</td><td>NanoGPT</td><td>—</td><td>audio</td><td><code>model</code>, <code>prompt</code>, <code>voice</code>, <code>speed</code>, <code>instructions</code>, <code>extraJson</code></td></tr>
<tr><td><code>trim</code></td><td>Trim audio</td><td>browser-only</td><td>audio</td><td>audio</td><td><code>start</code>, <code>length</code></td></tr>
<tr><td><code>extractaudio</code></td><td>Extract audio</td><td>browser-only</td><td>video</td><td>audio</td><td><code>start</code>, <code>length</code></td></tr>
<tr><td><code>trim</code></td><td>Trim audio</td><td>local media</td><td>audio</td><td>audio</td><td><code>start</code>, <code>length</code></td></tr>
<tr><td><code>extractaudio</code></td><td>Extract audio</td><td>local media</td><td>video</td><td>audio</td><td><code>start</code>, <code>length</code></td></tr>
<tr><td><code>transcribe</code></td><td>Transcribe</td><td>NanoGPT</td><td>audio</td><td>text</td><td><code>model</code>, <code>language</code></td></tr>
<tr><td><code>comment</code></td><td>Comment</td><td>local (never runs)</td><td>—</td><td>—</td><td><code>text</code>, <code>color</code></td></tr>
</table></div>
Expand All @@ -186,6 +188,12 @@ <h2>Loader rules</h2>
had a single combined audio node).</li>
<li>Unknown node types don't crash a load — the editor skips them; the headless
libraries keep them, warn, and fail fast at run time.</li>
<li>Retired type: <code>draw</code> (🎨 Draw) retired on 2026-07-22, because its
Gemini omni model left the NanoGPT catalog. It is not in the table above and you
cannot add it. A graph saved before that date still loads — the editor skips the
node and the headless libraries warn on load, then refuse the run. To repair such a
graph, replace the <code>draw</code> node with <code>image</code> (generate) or
<code>edit</code> (edit an image you wire in).</li>
<li>Keep a link only if <em>both</em> endpoint nodes exist after filtering.</li>
<li>Migration: a link into a <code>music</code> or <code>tts</code> node's
<code>"text"</code> port is rewritten to <code>"prompt"</code>.</li>
Expand Down
30 changes: 24 additions & 6 deletions guide/run-headless.html
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,31 @@ <h2>Inputs, outputs, settings</h2>
inline as base64 (NanoGPT has no upload endpoint); files over ~4.4&nbsp;MB (~3.5&nbsp;MB
for transcription) are refused locally with a clear error before any paid call.</p>

<h2>What runs where</h2>
<h2 id="what-runs-where">What runs where</h2>
<p>Pure-logic nodes (text, uploads, choice, join, comment) run locally; generation nodes
(llm, image, draw, edit, inpaint, vision, video, lipsync, music, remix, tts, transcribe)
call NanoGPT. Browser-only media-processing nodes (resize, vframes, combine, soundtrack,
trim, extractaudio) are <em>not</em> supported headlessly: workflows containing them load
with a warning and fail fast at <code>run()</code>, before any network call. The full
table is on the <a href="/guide/graph-format">format page</a>.</p>
(llm, image, edit, inpaint, vision, video, lipsync, music, remix, tts, transcribe)
call NanoGPT. The 6 media-processing nodes (resize, vframes, combine, soundtrack, trim,
extractaudio) also run headlessly. They do their work on your machine and never spend.
Some of them need <code>ffmpeg</code> and <code>ffprobe</code> on <code>PATH</code>:</p>
<div class="tablewrap"><table>
<tr><th>node</th><th>JavaScript</th><th>Python</th></tr>
<tr><td><code>resize</code></td><td>Pure JS for a PNG source; ffmpeg for any other format</td><td>ffmpeg</td></tr>
<tr><td><code>trim</code></td><td>Pure JS for a PCM WAV source; ffmpeg for any other format</td><td>ffmpeg</td></tr>
<tr><td><code>combine</code></td><td>Pure JS (lossless mp4 remux) when every clip is mp4 with matching stream parameters; ffmpeg otherwise</td><td>ffmpeg</td></tr>
<tr><td><code>vframes</code></td><td>ffmpeg</td><td>ffmpeg</td></tr>
<tr><td><code>soundtrack</code></td><td>ffmpeg</td><td>ffmpeg</td></tr>
<tr><td><code>extractaudio</code></td><td>ffmpeg</td><td>ffmpeg</td></tr>
</table></div>
<p><code>ffmpeg</code> is a soft dependency, not a package dependency. Install it with
<code>apt install ffmpeg</code> or <code>brew install ffmpeg</code>. If a node needs it
and it is absent, that node stops the run with a clear error that names it. The error
arrives when the node's turn comes, so paid nodes earlier in the graph have already
spent — run a media-only graph once to prove your setup before you wire a paid node in
front of it.</p>
<p>Only an <em>unknown</em> node type is refused up front: the libraries warn at load and
throw at <code>run()</code> before any network call. That is how a graph saved before
2026-07-22 with the retired <code>draw</code> node behaves. The full node table is on the
<a href="/guide/graph-format">format page</a>.</p>

<h2>No account: pay per run in Nano (x402)</h2>
<p>Skip the API key entirely. With <code>--pay</code> (or a library
Expand Down
Loading
Loading