feat/sidebar-modules-discovery
5
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a0a8469448 |
feat(sidebar): build-tag footer + theme auto-pull + × removability + pack badges (#98)
Five user-visible changes to the Kforge Labs sidebar that complete the
dev-sync verification loop and fix theme-mode discovery:
- Build-tag footer at the bottom of the sidebar: `dev <sha> · <time>`
on line 1 (SHA at 13px monospace, the at-a-glance identifier),
italic <scope> on line 2. Reads `web/_dev_build.json` written by
`scripts/sync_to_dev.py --scope "<short scope>"`. Visible-in-app
companion to PR #95's chat-report convention — closes the gap when
the maintainer is juggling parallel sessions and can't tell whether
the browser tab is showing the latest sync. Absent on registry
installs; footer renders empty there.
- Theme/category mode now mirrors repo-mode population: REPOS auto-
pull union user-picks, deduped, with the new auto-pull-hidden set
filtered out. The earlier picks-only design dropped the entire
Koolook pack from theme mode on a stock install, even though those
same nodes appeared in repo mode — silently violating the "my
favorites in two views" mental model. `gatherNodesByTheme` rewritten
to seed from REPOS first, then user picks, with the same
autoCategoryRoots + hidden filtering repo mode uses.
- `×` works on every favorite, including auto-pulled REPOS nodes.
Adds `koolook.autoPullHidden.v1` localStorage Set; `×` on an auto-
pulled row calls `hideAutoPullType(type)` so the next render skips
it. `addToMyPicks` calls `unhideAutoPullType` as a side effect so
re-adding via `+` automatically restores visibility. Same uniform
"remove / bring back" UX whether the node was a user pick or REPOS
auto-pull.
- Pack-name badges restored on theme-mode leaves only. Small dim
`Koolook` / `KJNodes` text on the right of each row, same opacity
as the search-flatten breadcrumb. Memory aid for "where did this
node come from?" Repo mode and search-flatten still skip the badge
(origin is conveyed by tree nesting / breadcrumb prefix).
- `scripts/sync_to_dev.py --scope` flag: extends PR #95's brief-
output design with a JSON artifact (`<target>/web/_dev_build.json`)
carrying commit + timestamp + worktree + scope, which the sidebar
footer fetches on render. Doesn't change the existing one-line
chat-report contract or the `_build_line()` shape.
Tested via dev-sync against the live ComfyUI install:
- Footer renders correctly (`dev
|
||
|
|
afe539e8dc |
chore(dev-sync): brief output + build-id stamp + chat-report convention (#95)
* chore(dev-sync): brief output + build-id stamp + chat-report convention
After a dev-sync the maintainer was running 4 parallel ComfyUI installs
with no way to tell which version was just synced and a 50-word "what's
new" message that was hard to skim. Two fixes, one PR.
Script (`scripts/sync_to_dev.py`):
• Default output is now ONE line — the per-file "copied: <path>" lines
move behind `-v` / `--verbose`. Useful when something goes wrong; not
useful for the routine "did it work" signal.
• New summary line includes a build identifier:
synced 10 entries @ <short-sha> v<version> -> <target>
Both the SHA (via `git rev-parse --short HEAD`) and version (parsed
from pyproject.toml) are best-effort — either can be missing and the
line still reads cleanly.
• Dry-run picks up the same shape:
would sync 10 entries @ <short-sha> v<version> -> <target>
CLAUDE.md (project instructions):
• Tightened the `dev-sync` section to its essentials.
• Added a "Chat report format" subsection — the agent must include the
build line from script output and a ≤10-word summary naming what to
LOOK AT after restart, not a list of changes:
dev-sync @ <short-sha> v<version> — <≤10 word summary>. Restart.
• Concrete examples included so the convention is unambiguous.
No runtime behavior change — pure tooling + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(dev-sync): two-line report format — sha + worktree, then summary
Maintainer feedback: the previous format ("dev-sync @ <sha> v<version>
— <summary>. Restart.") cluttered slot 1 with version info and forced
the summary onto the same line. Both made the message harder to scan
when juggling multiple parallel ComfyUI installs.
New format — two lines, build id alone on line 1:
<short-sha> - <worktree-name>
<≤10 word summary of what to look at>
Worktree name disambiguates "which checkout fed this sync" when the
maintainer is running several in parallel — the version field rarely
moves between syncs so it bought less than the worktree name does.
Script changes:
• Replaced `_build_id()` with `_build_line()` returning the
pre-formatted ``<sha> - <worktree>`` header.
• Replaced `_get_version()` with `_get_worktree_name()` (REPO_ROOT
basename — works for both worktrees and the main checkout).
• Output now prints the header line first, then the mechanical
"synced N entries -> <target>" line, then the restart hint. Eye
lands on the identifier before the details.
CLAUDE.md change: matching update to the chat-report convention,
including two examples.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(dev-sync): drop "restart ComfyUI" line + user-initiated-only policy
Maintainer feedback:
• The "restart ComfyUI to pick up changes" line at the end of the
script output is noise — they know to restart, the reminder
clutters the message. Removed.
• Dev-sync was being run by sessions automatically (post-commit, on
ship-pr, etc.), silently overwriting code that other parallel
sessions were reviewing. Now explicitly user-initiated only:
project CLAUDE.md spells out the policy and lists the automated
flows that must NOT trigger it; the script docstring carries the
same warning so anything that reads it sees the rule.
Chat report convention: line 2 is now described as "scope of what to
review" (the change in this build) rather than "thing to look at after
restart", to match how the maintainer actually thinks about it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
93363cc625 |
feat(sidebar): snapshot library v2 — configurable filesystem path via KFORGELABS_PRESETS (#68)
* feat(sidebar): snapshot library — named presets for picks + workflows Implements #46 item 1 (custom save location + cross-machine portability) as a preset library: each preset is one named JSON file under /userdata/koolook-presets/, capturing the full Kforge Labs state. Use cases addressed ------------------- - **Personal portability:** copy `koolook-presets/` between machines, or sync ComfyUI's userdata folder via Dropbox/iCloud/Drive — your kit follows. - **Team / facility shared library:** multiple workstations on the same ComfyUI server share `/userdata/koolook-presets/` natively. One person publishes "facility kit", everyone else loads it. - **One-off transfer (different servers):** Manage tab's Download button saves a snapshot to local disk; Upload imports it into another machine's library. UI -- New top-level **Snapshot** action row above the search field, mirroring the Nodes/Workflows action-row pattern. Three icon buttons (Load / Save / Manage), each opens the same modal at a different starting tab. - Load tab: lists every preset with metadata (workflow count · pick count · export date). Click a row → "Replace current state?" confirm → restores both picks and workflows. - Save tab: input field defaults to `preset YYYY-MM-DD`, fully editable. Existing-name save prompts to overwrite. - Manage tab: per-row Download / Delete buttons + a top-of-tab Upload action (file picker imports a JSON snapshot from disk into the library). Schema ------ ```json { "kind": "koolook-snapshot", "version": 1, "name": "Wan video kit", "exportedAt": "2026-05-05T08:42:13.000Z", "picks": [...], "workflows": { "directories": {...} } } ``` `kind` + `version` for future schema migrations. Anything without `kind === "koolook-snapshot"` is rejected on import. Module shape ------------ - `web/sidebar/snapshot.js` (new) — server I/O on the preset directory, schema validation, gather/apply, download/upload helpers. UI-free. - `web/sidebar/modals.js` — new `showSnapshotModal({initialTab, ...})` that takes capability functions instead of importing snapshot.js directly, so the UI module stays UI-only. - `web/sidebar/workflows_store.js` — new `replaceAllWorkflows(rawStore)` for bulk-replace (bypasses persistMutation since the entire cache is being swapped) + `getAllWorkflowsForExport()` for the gather flow. - `web/sidebar/picks_store.js` — new `setAllPicks(picks)` exported helper. - `web/sidebar/tree.js` — new top action row + 3 toolbar buttons + one wiring helper. - `web/sidebar/constants.js` — CSS for tab strip, list rows, manage buttons. Deliberate scope cuts (per the design discussion) ------------------------------------------------- - No continuous sync. Each save is a deliberate act. - No conflict resolution. Same preset name written from two machines on the same server = last-write-wins. Documented as a known limit; revisit when it bites. - No selective import (preset = full state). Merge can be a v2. - No custom userdata path config. The `koolook-presets/` location is fixed; portability comes from the snapshot files themselves, not from rerouting storage. Verification ------------ - `node --input-type=module --check` on all modified + new JS files — clean. - `python tools/preflight_release.py --skip manager-meta -v` PASS. - Synced to live install on Mac for browser smoke-test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(sidebar): snapshot library now uses configurable filesystem path Reworks PR #67 (which served snapshots from /userdata/koolook-presets/) to expose the library through Koolook's own server-side endpoints, making the storage location configurable via an env var. The userdata namespace is per-install — fine for personal use, useless for the "facility shared library" use case the user explicitly called out. Architectural change -------------------- - The client no longer talks to ComfyUI's `/userdata/*` API for snapshot files. New JS-side endpoints: `/koolook/presets/{info,list,file}`. - Server side: new `koolook_routes.py` registers aiohttp routes on ComfyUI's PromptServer at custom-node load time. Path-traversal protection at the route boundary via a strict filename whitelist regex (`^[A-Za-z0-9 _.()\-]+\.json$`). - Storage location is read from the `KFORGELABS_PRESETS` env var on every request (so re-launching ComfyUI with a new value picks it up without any cache fuss). Default fallback: `<comfyui-userdata>/koolook-presets/`. - Auto-creates the configured directory on first save (parent must already exist — for NFS/SMB mounts the mount must be present). Use cases this enables vs. PR #67 --------------------------------- | Use case | PR #67 | This PR | |-------------------------|--------|---------| | Personal, single-machine | ✓ | ✓ | | Personal cross-machine | symlink-or-sync userdata | point env var at Dropbox folder | | NFS/SMB facility share | ✗ (locked to userdata) | ✓ point env var at mount | | Multiple parallel libs | ✗ | per-instance env var | | Read-only distribution | ✗ | ✓ (server detects, save fails cleanly) | UI difference vs. PR #67 ------------------------ - The Manage tab gains a small `i` icon at the right edge of the upload row. Hover tooltip shows the current library path, source (default vs `KFORGELABS_PRESETS` override), and writability status. No header line, no extra UI weight. Files added/changed ------------------- - `koolook_routes.py` (new, ~180 lines) — aiohttp routes + env-var config + filename validation. Loaded by `__init__.py` at startup; failure is non-fatal (the rest of Koolook still works, just the snapshot feature is unavailable). - `__init__.py` — registers `koolook_routes` import-time side-effect. - `scripts/sync_to_dev.py` — `koolook_routes.py` added to `RUNTIME_PATHS` so dev-sync includes it. - `web/sidebar/snapshot.js` — server I/O section rewritten to call `/koolook/presets/*` instead of `/userdata/koolook-presets/*`. Drops the URL-encoding workaround (we own the route now, so the `name=<file>` query param is clean and the server validates). - `web/sidebar/modals.js` — `showSnapshotModal` accepts a `getLibraryInfo` capability function; the Manage tab calls it and populates the info-icon tooltip. - `web/sidebar/tree.js` — passes `getLibraryInfo` from `snapshot.js` to the modal. - `.env.example` — new `KFORGELABS_PRESETS` block with macOS / Linux / Windows examples for personal-sync, NFS facility share, and SMB facility share patterns. - `CHANGELOG.md` — Added entry rewritten for the env-var design; Internal entry on the new server-side module. Why a separate v2 PR (instead of patching #67) ---------------------------------------------- The userdata-namespace design in #67 was the wrong architecture for what the user actually wanted. Patching it to the env-var design would have meant a near-total rewrite of #67's server I/O paths mid-review. Cleaner: branch fresh from main, cherry-pick #67's UI + stores work as a base, layer the new endpoint design on top. PR #67 will be closed as superseded once this lands. Verification ------------ - `node --input-type=module --check` on every modified JS file — clean. - `python -c "import ast; ast.parse(...)"` on `koolook_routes.py` + `__init__.py` — clean. - `python tools/preflight_release.py --skip manager-meta -v` PASS. - Synced to live install on Mac. User to smoke-test against a real ComfyUI session (the previous URL-encoding 405 should be gone since the new route handlers own the URL space). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(sidebar): redesign snapshot UX — single-click Save, real Settings panel User feedback on #68's first cut: tabbed modal is too complicated, can't see existing presets in the Save flow, the env var alone isn't a real settings UI, the default-path text was stale (still claimed `/userdata/koolook-presets/` after the configurable-path landed). UX changes ---------- - **Save button → one click.** If a preset is currently loaded (tracked in localStorage as `koolook.snapshot.currentPresetName.v1`, survives reloads), prompts "Save over '<name>'?" with three options: Save (overwrite) / Save as new… (rename) / Cancel. If nothing is loaded yet, prompts for a name with `preset YYYY-MM-DD` default, fully editable. - **Load → single dialog.** Preset list with per-row × Delete button. Header line shows the current library path so the user can see where they're loading from. No tabs. - **Cog → real Settings panel.** Single field for the library path (text input, absolute path). Save / Reset to default / Close. Read- only line shows the currently-resolved path + source label (settings panel / env var / built-in default). The "Manage" tab and its Download / Upload sub-actions are gone — the user can already reach the file on disk after a save, no in-app file ops needed. Server side ----------- - New `GET /koolook/presets/settings` returns the saved library path + resolved path + source. - New `POST /koolook/presets/settings` writes the saved library path to `<comfyui-userdata>/koolook-settings.json`. Empty value clears the override. - Resolution chain in `_configured_dir()`: settings file > KFORGELABS_PRESETS env var > built-in default Reads each on every call; no module-load caching. - `info` endpoint gains a `source` field ("settings" | "env" | "default") the UI uses for the readout line. Client side ----------- - `showSnapshotModal` (tabbed) replaced by three exports: showSaveSnapshotDialog, showLoadSnapshotDialog, showSnapshotSettingsDialog. Each is self-contained, no shared state. - `snapshot.js` adds `getSettings`/`saveSettings` for the Settings dialog and `getCurrentPresetName`/`setCurrentPresetName` for the one-click Save tracking. Settings ride on the new server endpoints; current-preset tracking is browser-local. - `applySnapshot` now also updates the current-preset tracker (via the dialog wrapper, not the function itself) on successful load. - `tree.js` toolbar wires three direct callbacks instead of one `openSnapshotModal(initialTab)` helper. Stale text fix -------------- - The Save dialog's hint text in #68's first cut hardcoded "/userdata/koolook-presets/<name>.json" — stale from the userdata- era PR #67. The redesigned Save flow doesn't show that text at all (the path is no longer relevant to the user-typed name); the Load dialog's header line and the Settings dialog's read-only field are the canonical surfaces for "where my presets actually live." Deliberately out of scope ------------------------- - No per-save path picker. The library is one location at a time, set in advance via Settings or env var. Saving to ad-hoc locations would fragment the library and break the "facility shared library" use case. - No in-app Download / Upload (was in #68's first cut). Files are on disk at the configured path; the OS file manager handles transfer. Verification ------------ - `node --input-type=module --check` on every modified JS file — clean. - `python -c "import ast; ast.parse(...)"` on `koolook_routes.py` — clean. - `python tools/preflight_release.py --skip manager-meta -v` PASS. - Synced to live install on Mac. User to smoke-test against a real ComfyUI session. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(sidebar): address PR #68 review — atomicity, security, error UX Multi-agent review surfaced 2 BLOCKING + 5 HIGH + 4 MEDIUM + 3 LOW. Validated the BLOCKINGs against the actual code (both real) and applied the bundle of fixes that hold up. Modal-size HIGH (#7 file size) deferred to a follow-up — that's a wider refactor. BLOCKING fixes -------------- - **`applySnapshot` partial-failure tracker gating** (modals.js Load dialog). `setCurrentPresetName(...)` was called unconditionally before checking `{picksOk, workflowsOk}` from the apply. Partial apply (one of picks/workflows persisted, the other didn't) left the tracker pointing at the preset, so the next Save would offer "Save over '<name>'?" and silently overwrite the on-disk preset with the corrupted half-state. Now: tracker is set only on `picksOk && workflowsOk`; partial failure clears it (and the toast tells the user "Reload to recover prior state. Tracker cleared."). - **`replaceAllWorkflows` non-atomic rebind** (workflows_store.js). Rebinds `workflowsCache` before awaiting persist, with no rollback. Now: snapshots the cache via the existing `snapshotCache()` primitive that `persistMutation` uses; on persist failure, restore the prior cache and return false. Load is fully atomic. HIGH fixes ---------- - **Workflows-store invariant block updated.** Lists `replaceAllWorkflows` as the fourth legitimate rebind site (with rollback semantics). "Fifth rebind" warning bumped to "sixth." - **Explicit HEAD route on `/koolook/presets/file`** that just does `is_file()` + status, no body work. The auto-synthesis path was reading the full file from disk just to discard it — wasteful on multi-MB snapshots over Dropbox/iCloud/NFS. - **Client `sanitizeName` mirrors server whitelist regex** (`^[A-Za-z0-9 _.()\-]+$`). Characters outside the whitelist (`+`, `&`, `'`, accented letters, emoji, etc.) collapse to `_` rather than triggering an opaque HTTP 400 at save time. - **Error-reason surfacing via `await resp.text()`.** RFC 7540 strips HTTP/2 reason phrases; behind any HTTP/2-terminating proxy `resp.statusText` is empty. Reading the body always carries aiohttp's `reason=` text. Used by writePreset, deletePreset, saveSettings, readPreset. MEDIUM fixes ------------ - **Symlink follow-through hardening.** `_resolve_within_base` helper computes `(base / name).resolve()` and asserts it's still under `base.resolve()`. Defends against a symlink planted in a facility- shared library dir redirecting reads/writes off-share. - **Atomic settings write.** `_write_settings` writes to `<tmp>` then `os.replace`, so an interrupted process can't truncate the file and silently revert the user's saved library path on next load. - **`__init__.py` import wrapped in try/except.** The "non-fatal" comment promised that an import failure wouldn't break node registration; previously only `install()` returning False was guarded. Now any module-load exception is caught and logged with the `[Koolook]` prefix, and the rest of the plugin loads. LOW housekeeping ---------------- - Dropped dead exports `downloadSnapshotAsFile` + `readSnapshotFile` (left over from an earlier iteration that had Download/Upload UI). - Dropped 5 dead CSS rules (`.koolook-snapshot-tabs*`, `.koolook-snapshot-tab-pane`) from when the modal was tabbed. - `currentPresetName` cleared when the user deletes the currently- loaded preset (Load dialog's promptDelete now checks via the new `getCurrentPresetName` capability). - `loadPreview` warns to console on `kind` mismatch — non-snapshot JSON files in the library directory are now visible in DevTools rather than silently filtered. - `presetExists` is tri-state (`true` / `false` / `null`) — the Save flow refuses to write when the library is unreachable rather than silently skipping the overwrite-confirm prompt. Deferred -------- - modals.js size (1080 lines) — extracting the snapshot dialogs into their own file is a follow-up refactor. Not blocking merge. - Doc drift on maintainer docs / README env-var convention — separate documentation sweep. Verification ------------ - `python -c "import ast; ast.parse(...)"` on all .py files — clean. - `node --input-type=module --check` on every modified JS file — clean. - `python tools/preflight_release.py --skip manager-meta -v` PASS. - `grep -rn "downloadSnapshotAsFile\\|readSnapshotFile\\|koolook-snapshot-tab" web/` — zero hits (dead code gone). - Synced to live install for browser smoke-test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: nudge — re-trigger CI after webhook miss on d6d2bf8 The CI workflow didn't fire on the prior push (Socket Security ran but GitHub Actions Lint/Smoke/Pre-flight/Security Gate didn't). Empty commit to refire the pull_request webhook. * fix(routes): drop unnecessary f-string prefixes flagged by ruff (F541) Two adjacent string literals in the symlink-escape error message had an `f` prefix despite containing no placeholders. CI's ruff check fails on F541 — strip the prefixes so the lint gate passes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
0d4c81295b |
chore(sync): --init flag for first-run dev-sync setup + Mac path docs (#61)
Background
----------
Setting up `scripts/sync_to_dev.py` on a fresh machine required two
pre-steps the script didn't help with: (1) editing `.env` to point at
a path that didn't exist yet, then (2) `mkdir -p` on that path before
the first sync would work. The script errored out cleanly with
"target does not exist" but didn't tell the user the obvious next
move was to create it. This was particularly silent on Mac where the
ComfyUI install path differs from the Windows examples in `.env.example`.
Changes
-------
- **`--init` flag** on `scripts/sync_to_dev.py`. When the target
directory doesn't exist and `--init` is passed, the script creates
it and proceeds with the sync. Without `--init`, it errors out with
the same "first time on this machine?" hint pointing at the new flag.
- **Safety guards** on `--init`. Refuses (exit 3) if:
- the parent directory doesn't exist, OR
- the parent doesn't look like a ComfyUI `custom_nodes/` folder
(heuristic: parent is named `custom_nodes` OR sits inside a
directory named `ComfyUI`).
This prevents a typo in `KOLOOK_COMFYUI_DEV_PATH` from quietly
creating a `ComfyUI-Koolook/` somewhere unexpected on disk. The
refusal message includes a manual escape hatch (`mkdir -p` + retry
without `--init`) for the case where the parent really IS the
ComfyUI install but doesn't match the heuristic.
- **Drop stale `k_easy_version.py`** from `RUNTIME_PATHS`. The file
was deleted in v0.1.10 (PR #41); the script's `if not src.exists()`
guard meant it was a no-op every run, but the entry was misleading.
- **`.env.example`** gains explicit macOS / Linux / Windows path
examples for `KOLOOK_COMFYUI_DEV_PATH` and a 3-step first-run
recipe (cp .env.example → edit → `--init`).
- **`docs/maintainers/dev-iteration-loop.md`** gains a "First-time
setup on a new machine" section with the per-OS path table and the
same 3-step recipe.
Verification
------------
- `python scripts/sync_to_dev.py --dry-run` against a missing target
→ exit 2, hint at `--init`.
- `python scripts/sync_to_dev.py --init` against a real ComfyUI
`custom_nodes/` parent → creates the leaf, copies 9 entries, exits 0.
- `python scripts/sync_to_dev.py` (no flag) re-run after `--init` →
re-syncs cleanly, idempotent.
- `python scripts/sync_to_dev.py --init` against `/tmp/not-custom-nodes`
→ exit 3, "parent does not exist".
- `python scripts/sync_to_dev.py --init` against an existing
`/tmp/wrong-parent` (not custom_nodes) → exit 3, "doesn't look like
a ComfyUI custom_nodes/ folder", manual escape hatch in the message.
- `python -c "import ast; ast.parse(open('scripts/sync_to_dev.py').read())"`
→ syntax OK.
- `python tools/preflight_release.py --skip manager-meta -v` → PASS.
No CHANGELOG entry — dev-tooling under `scripts/` doesn't ship to
users of the published package. Avoiding noise on the [Unreleased]
section that PRs #59 / #60 also touch.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
0a914cb8de |
chore(dev): add scripts/sync_to_dev.py + KOLOOK_COMFYUI_DEV_PATH env var (#40)
For one-line fixes that don't deserve their own version bump, copy the runtime files into the maintainer's live ComfyUI custom_nodes folder directly instead of going through the publish-and-update round-trip. What this adds: - `scripts/sync_to_dev.py` — minimal Python (no third-party deps); reads `KOLOOK_COMFYUI_DEV_PATH` from the environment (auto-loads `.env` at the repo root); copies the curated set of runtime-relevant files (top-level `k_*.py`, `__init__.py`, `config.json`, `forks/`, `web/`) while skipping CI/docs/metadata. Errors out cleanly when the env var is unset — never guesses a path. - `.env.example` entry `KOLOOK_COMFYUI_DEV_PATH=` with explanatory comment, matching the existing `KOLOOK_*` convention. - `CLAUDE.md` section documenting the trigger phrase `dev-sync` so a fresh agent session knows to invoke the script when the maintainer asks for it. This is a development-loop convenience only — anything worth shipping still goes through the normal PR + release flow. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |