run() re-splits its argument as a bare string with no eval, so any
call like run "git clone \"$url\" \"$dir\"" corrupts the quoted
arguments (embedded escaped quotes survive as literal characters,
e.g. git sees the protocol as '"https). Each of the 4 affected call
sites papered over this by following run() with a second, correctly
quoted manual execution of the same command - meaning every real run
double-executed (git clone twice, pip install -e twice, conda env
create twice), with the first, corrupted attempt's failure masked by
the working second attempt.
Fixed by dropping run() at these sites entirely and gating on
$dry_run directly with a single, properly quoted real execution path
(matches the pattern already used in the new model-cache-redirect
extra):
- lib/cf-apps.functions: install-cf-app's clone, provision-orchard-node's clone
- cf-apps/circuitforge-core.manifest: pip install -e setup hook
- cf-apps/kiwi.manifest: conda env create setup hook
Verified on sif via a scratch-dir harness (install-cf-app
circuitforge-core): first run now clones cleanly with no mangled-URL
error, second run correctly skips the clone and re-runs the
idempotent pip install; state.conf stays single-entry across both.
Adds a manifest-driven "Install CircuitForge Apps" menu to install.sh,
alongside the existing extras-menu pattern.
- cf-apps/*.manifest: one file per product (circuitforge-core, peregrine,
kiwi, snipe, turnstone, pagepiper, linnet - the beta/alpha menagerie
products), declaring repo URLs, supported install types, conda env,
.env template, and per-install-type setup hooks. Hooks prefer shelling
out to each product's own install.sh/Makefile/docker-compose rather
than reimplementing their setup logic.
- lib/cf-apps.functions: registry loader, provisioning-profile prompt
(oem/collaborator/orchard), app multiselect menu, and the install
dispatcher (clone, .env bootstrap, install-type selection, hook
dispatch). circuitforge-core installs automatically as a dependency
for apps that declare app_needs_core=true.
Provisioning profiles gate both which apps are offered and which remote
credentials are used:
- oem: public CircuitForgeLLC GitHub mirrors only, no Forgejo access,
no circuitforge-orch (product install menu only)
- collaborator: private Circuit-Forge Forgejo, same product menu
- orchard: narrow flow, not the product menu - clones circuitforge-orch
(Forgejo-only, no public mirror exists) and hands off interactively to
its own install.sh, which gathers agent/coordinator topology itself
(it has no --topology/--coordinator-url flags to script around).
NOTE: circuitforge-orch has no model-sync/cache-sync mechanism today
(checked its install.sh and README); this wrapper doesn't invent one.
Design rationale and survey of each product's real install story:
circuitforge-plans/cf-node-bootstrap/superpowers/plans/2026-07-17-cf-apps-install-menu.md