geneseed 2.0.0
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.
- package/CHANGELOG.md +938 -0
- package/DESIGN.md +1423 -0
- package/LICENSE +21 -0
- package/QUICKSTART.md +48 -0
- package/README.md +336 -0
- package/SETUP.md +1027 -0
- package/SHIPPED.md +70 -0
- package/adapters/bob/README.md +100 -0
- package/adapters/claude-code/README.md +101 -0
- package/adapters/claude-code/settings.json +47 -0
- package/adapters/claude-code/skills/geneseed/SKILL.md +72 -0
- package/adapters/copilot/README.md +76 -0
- package/adapters/opencode/GLOBAL-HARNESS-SPEC.md +434 -0
- package/adapters/opencode/HOW-OPENCODE-LOADS.md +158 -0
- package/adapters/opencode/README.md +582 -0
- package/adapters/opencode/agents/orchestrator.md +25 -0
- package/adapters/opencode/opencode.json +44 -0
- package/adapters/opencode/plugins/geneseed-activity.js +430 -0
- package/adapters/opencode/plugins/geneseed-context.js +876 -0
- package/adapters/opencode/plugins/geneseed-guard.js +290 -0
- package/adapters/opencode/plugins/geneseed-learn.js +396 -0
- package/adapters/opencode/plugins/geneseed-notify.js +142 -0
- package/adapters/opencode/plugins/geneseed-ponytail.js +148 -0
- package/adapters/opencode/plugins/geneseed-workflow.js +183 -0
- package/adapters/opencode/workflows/_runtime.js +277 -0
- package/adapters/opencode/workflows/council.js +64 -0
- package/adapters/opencode/workflows/dispatch.js +89 -0
- package/adapters/opencode/workflows/research-plan-implement.js +43 -0
- package/adapters/opencode/workflows/review.js +57 -0
- package/bin/geneseed-cli.mjs +412 -0
- package/bin/geneseed-hook.mjs +129 -0
- package/bin/geneseed.mjs +1086 -0
- package/docs/declined.md +149 -0
- package/docs/limits.md +270 -0
- package/docs/opencode-plugin-setup.md +64 -0
- package/docs/token-footprint.md +68 -0
- package/docs/web/_groups.json +45 -0
- package/docs/web/about.md +6 -0
- package/docs/web/adapters-claude-code.md +8 -0
- package/docs/web/adapters-opencode-loads.md +8 -0
- package/docs/web/adapters-opencode-spec.md +8 -0
- package/docs/web/adapters-opencode.md +8 -0
- package/docs/web/agents.md +8 -0
- package/docs/web/authoring.md +11 -0
- package/docs/web/autostart.md +9 -0
- package/docs/web/claude-hooks.md +36 -0
- package/docs/web/cli.md +6 -0
- package/docs/web/collaboration.md +27 -0
- package/docs/web/configure-wiki.md +9 -0
- package/docs/web/design.md +7 -0
- package/docs/web/env-knobs.md +9 -0
- package/docs/web/exclusions.md +26 -0
- package/docs/web/first-session.md +28 -0
- package/docs/web/footprint.md +31 -0
- package/docs/web/foreman-mode.md +33 -0
- package/docs/web/glossary.md +6 -0
- package/docs/web/harnesses.md +28 -0
- package/docs/web/headless.md +9 -0
- package/docs/web/ingest-docs.md +9 -0
- package/docs/web/install-paths.md +9 -0
- package/docs/web/install-quick.md +35 -0
- package/docs/web/lsp-overview.md +37 -0
- package/docs/web/mcp-claude-code.md +10 -0
- package/docs/web/mcp-filesystem.md +9 -0
- package/docs/web/mcp-gitlab.md +9 -0
- package/docs/web/mcp-markitdown.md +9 -0
- package/docs/web/mcp-overview.md +34 -0
- package/docs/web/mcp-trouble.md +9 -0
- package/docs/web/mcp-verify.md +9 -0
- package/docs/web/memory.md +7 -0
- package/docs/web/migrate.md +39 -0
- package/docs/web/model.md +20 -0
- package/docs/web/notebook.md +7 -0
- package/docs/web/pipelines.md +24 -0
- package/docs/web/plugin-activity.md +23 -0
- package/docs/web/plugin-context.md +26 -0
- package/docs/web/plugin-guard.md +24 -0
- package/docs/web/plugin-learn.md +27 -0
- package/docs/web/plugin-notify.md +24 -0
- package/docs/web/plugin-ponytail.md +23 -0
- package/docs/web/plugin-setup.md +7 -0
- package/docs/web/plugin-workflow.md +24 -0
- package/docs/web/plugins.md +17 -0
- package/docs/web/project-context.md +9 -0
- package/docs/web/rules.md +8 -0
- package/docs/web/run-anywhere.md +9 -0
- package/docs/web/self-improve.md +8 -0
- package/docs/web/setup-choices.md +39 -0
- package/docs/web/skills.md +8 -0
- package/docs/web/themes-author.md +8 -0
- package/docs/web/themes.md +9 -0
- package/docs/web/token-footprint.md +7 -0
- package/docs/web/trouble.md +38 -0
- package/docs/web/upgrade.md +9 -0
- package/docs/web/verify.md +30 -0
- package/docs/web/wiki.md +7 -0
- package/docs/web/worktree.md +10 -0
- package/docs/web-ui.md +144 -0
- package/docs/wiki.md +43 -0
- package/geneseed +39 -0
- package/geneseed.cmd +34 -0
- package/harness.config.json +6 -0
- package/js/anim.mjs +326 -0
- package/js/catalog.mjs +112 -0
- package/js/checkout.mjs +88 -0
- package/js/cli-table.json +1034 -0
- package/js/cli.mjs +498 -0
- package/js/diff.mjs +384 -0
- package/js/doctor.mjs +1364 -0
- package/js/emit.mjs +1373 -0
- package/js/excludes.mjs +271 -0
- package/js/generate.mjs +379 -0
- package/js/hooks.mjs +1026 -0
- package/js/hosts.mjs +253 -0
- package/js/installs.mjs +378 -0
- package/js/inventory.mjs +175 -0
- package/js/lib/fs.mjs +913 -0
- package/js/lib/proc.mjs +28 -0
- package/js/lib/udiff.mjs +275 -0
- package/js/link.mjs +286 -0
- package/js/mcp.mjs +335 -0
- package/js/memory.mjs +108 -0
- package/js/menu.mjs +118 -0
- package/js/migrate.mjs +251 -0
- package/js/native.mjs +426 -0
- package/js/opencode.mjs +325 -0
- package/js/registry.mjs +110 -0
- package/js/render.mjs +282 -0
- package/js/settings.mjs +924 -0
- package/js/setup.mjs +492 -0
- package/js/status.mjs +281 -0
- package/js/themes.mjs +195 -0
- package/js/tui.mjs +444 -0
- package/js/uninstall.mjs +1137 -0
- package/js/update.mjs +910 -0
- package/js/web/actions.mjs +868 -0
- package/js/web/activity.mjs +284 -0
- package/js/web/api.mjs +873 -0
- package/js/web/docs.mjs +531 -0
- package/js/web/graph.mjs +0 -0
- package/js/web/jobs.mjs +480 -0
- package/js/web/server.mjs +1135 -0
- package/package.json +54 -0
- package/registry.json +474 -0
- package/src/AGENT.md.tmpl +390 -0
- package/src/agents/_template.md +76 -0
- package/src/agents/advocate.md +36 -0
- package/src/agents/architect.md +55 -0
- package/src/agents/developer.md +66 -0
- package/src/agents/docs.md +54 -0
- package/src/agents/empiricist.md +39 -0
- package/src/agents/explorer.md +54 -0
- package/src/agents/framer.md +36 -0
- package/src/agents/historian.md +51 -0
- package/src/agents/operator.md +37 -0
- package/src/agents/pragmatist.md +37 -0
- package/src/agents/reviewer.md +52 -0
- package/src/agents/security.md +57 -0
- package/src/agents/skeptic.md +48 -0
- package/src/agents/steward.md +36 -0
- package/src/agents/tester.md +54 -0
- package/src/agents/user-advocate.md +36 -0
- package/src/agents/visionary.md +36 -0
- package/src/laws/universal.md +387 -0
- package/src/memory/README.md +109 -0
- package/src/memory/gitignore +6 -0
- package/src/modes/README.md +26 -0
- package/src/modes/direct.md +2 -0
- package/src/modes/foreman.md +19 -0
- package/src/notebook/README.md +39 -0
- package/src/notebook/gitignore +7 -0
- package/src/postures/README.md +31 -0
- package/src/postures/artisan.md +14 -0
- package/src/postures/assistant.md +12 -0
- package/src/postures/expert.md +11 -0
- package/src/postures/mentor.md +11 -0
- package/src/postures/peer.md +12 -0
- package/src/skills/_template.md +47 -0
- package/src/skills/brainstorm.md +28 -0
- package/src/skills/clarify.md +28 -0
- package/src/skills/codebase-design.md +44 -0
- package/src/skills/commit.md +48 -0
- package/src/skills/council.md +34 -0
- package/src/skills/crash-course.md +28 -0
- package/src/skills/daydream/.claude-plugin/plugin.json +9 -0
- package/src/skills/daydream/SKILL.md +74 -0
- package/src/skills/daydream/VENDOR.md +25 -0
- package/src/skills/daydream/critic-prompt.md +38 -0
- package/src/skills/daydream/instructions.md +422 -0
- package/src/skills/daydream/synthesizer-prompt.md +41 -0
- package/src/skills/debug.md +37 -0
- package/src/skills/decode.md +28 -0
- package/src/skills/develop.md +29 -0
- package/src/skills/document-project.md +79 -0
- package/src/skills/domain-modeling.md +38 -0
- package/src/skills/drill.md +28 -0
- package/src/skills/feynman.md +26 -0
- package/src/skills/forge-mcp.md +48 -0
- package/src/skills/fresh-eyes.md +29 -0
- package/src/skills/frontend-design.md +58 -0
- package/src/skills/gap-detector.md +27 -0
- package/src/skills/geneseed-code-review.md +33 -0
- package/src/skills/geneseed.md +29 -0
- package/src/skills/git-archaeology.md +47 -0
- package/src/skills/git-rescue.md +68 -0
- package/src/skills/handoff.md +29 -0
- package/src/skills/herdr.md +164 -0
- package/src/skills/ingest.md +49 -0
- package/src/skills/learning-path.md +26 -0
- package/src/skills/migrate.md +45 -0
- package/src/skills/opencode-theme.md +52 -0
- package/src/skills/parallel-agents.md +28 -0
- package/src/skills/pipeline.md +52 -0
- package/src/skills/plan.md +45 -0
- package/src/skills/ponytail.md +29 -0
- package/src/skills/profile.md +58 -0
- package/src/skills/prose.md +42 -0
- package/src/skills/prototype.md +44 -0
- package/src/skills/react-view-transitions/AGENTS.md +955 -0
- package/src/skills/react-view-transitions/README.md +42 -0
- package/src/skills/react-view-transitions/SKILL.md +320 -0
- package/src/skills/react-view-transitions/VENDOR.md +16 -0
- package/src/skills/react-view-transitions/metadata.json +12 -0
- package/src/skills/react-view-transitions/references/css-recipes.md +242 -0
- package/src/skills/react-view-transitions/references/implementation.md +182 -0
- package/src/skills/react-view-transitions/references/nextjs.md +176 -0
- package/src/skills/react-view-transitions/references/patterns.md +262 -0
- package/src/skills/refactor.md +32 -0
- package/src/skills/release.md +43 -0
- package/src/skills/repo-map.md +36 -0
- package/src/skills/research.md +42 -0
- package/src/skills/review-response.md +36 -0
- package/src/skills/roast-me.md +28 -0
- package/src/skills/rule.md +100 -0
- package/src/skills/ship.md +49 -0
- package/src/skills/tdd.md +31 -0
- package/src/skills/tickets.md +44 -0
- package/src/skills/token-report/SKILL.md +76 -0
- package/src/skills/token-report/VENDOR.md +23 -0
- package/src/skills/token-report/scripts/token_report.mjs +1301 -0
- package/src/skills/wayfinder.md +49 -0
- package/src/skills/wiki.md +68 -0
- package/src/skills/workflow.md +38 -0
- package/themes/README.md +69 -0
- package/themes/_TEMPLATE.json +147 -0
- package/themes/biker.json +147 -0
- package/themes/commentator.json +147 -0
- package/themes/cyberpunk.json +147 -0
- package/themes/gamer.json +147 -0
- package/themes/imperial.json +147 -0
- package/themes/joker.json +147 -0
- package/themes/marvin.json +147 -0
- package/themes/mean.json +147 -0
- package/themes/military.json +147 -0
- package/themes/neutral.json +147 -0
- package/themes/opencode/README.md +58 -0
- package/themes/opencode/catppuccin.json +27 -0
- package/themes/opencode/dracula.json +27 -0
- package/themes/opencode/everforest.json +27 -0
- package/themes/opencode/gruvbox.json +27 -0
- package/themes/opencode/kanagawa.json +27 -0
- package/themes/opencode/neon.json +27 -0
- package/themes/opencode/nord.json +27 -0
- package/themes/opencode/rosepine.json +27 -0
- package/themes/opencode/tokyonight.json +27 -0
- package/themes/pirate.json +147 -0
- package/themes/sports.json +147 -0
- package/themes/verstappen.json +147 -0
- package/themes/wizard.json +147 -0
- package/web/dist/assets/About-DX1-VTBP.js +1 -0
- package/web/dist/assets/Activity-Dp9NaXOn.js +1 -0
- package/web/dist/assets/ActivityDetail-HpOBtrXS.js +1 -0
- package/web/dist/assets/Diff-Ct4G3Se8.js +5 -0
- package/web/dist/assets/Doctor-DGjkZFuA.js +1 -0
- package/web/dist/assets/Graph-b8o-S6kv.js +1 -0
- package/web/dist/assets/Harnesses-gr3-aHBF.js +1 -0
- package/web/dist/assets/Laws-DTwF5tTC.js +1 -0
- package/web/dist/assets/Library-BEZscyaQ.js +1 -0
- package/web/dist/assets/Markdown-k_LSLBGq.js +1 -0
- package/web/dist/assets/Profile-DydukTxP.js +1 -0
- package/web/dist/assets/Rules-CIRG4Ldt.js +1 -0
- package/web/dist/assets/Skills-Buoi1IBr.js +1 -0
- package/web/dist/assets/StatusBadge-CE_AJttl.js +1 -0
- package/web/dist/assets/Themes-CI4o3gJC.js +2 -0
- package/web/dist/assets/activity-BCW7e6Cy.js +1 -0
- package/web/dist/assets/audiowide-latin-400-normal-6GFCX7ni.woff2 +0 -0
- package/web/dist/assets/geist-mono-latin-400-normal-DKaoCDn5.woff2 +0 -0
- package/web/dist/assets/geist-mono-latin-500-normal-C3sF8Y1B.woff2 +0 -0
- package/web/dist/assets/geist-mono-latin-600-normal-BHzjB6_C.woff2 +0 -0
- package/web/dist/assets/hanken-grotesk-latin-400-italic-Bserb8X5.woff2 +0 -0
- package/web/dist/assets/hanken-grotesk-latin-400-normal-BG6hkEXj.woff2 +0 -0
- package/web/dist/assets/hanken-grotesk-latin-500-italic-BGfwwhek.woff2 +0 -0
- package/web/dist/assets/hanken-grotesk-latin-500-normal-DrDcrrxK.woff2 +0 -0
- package/web/dist/assets/hanken-grotesk-latin-600-normal-CIXX6EOa.woff2 +0 -0
- package/web/dist/assets/hanken-grotesk-latin-700-normal-CeQ8H3UY.woff2 +0 -0
- package/web/dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/web/dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/web/dist/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
- package/web/dist/assets/ibm-plex-serif-latin-400-normal-BIGslYFI.woff2 +0 -0
- package/web/dist/assets/ibm-plex-serif-latin-500-normal-REKAfBVG.woff2 +0 -0
- package/web/dist/assets/ibm-plex-serif-latin-600-normal-DAxitmSu.woff2 +0 -0
- package/web/dist/assets/index-B1PIx7RM.js +1 -0
- package/web/dist/assets/index-BnTQUSUz.js +12 -0
- package/web/dist/assets/index-Ck2iCdaC.js +2 -0
- package/web/dist/assets/index-DUCsDv7z.css +1 -0
- package/web/dist/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
- package/web/dist/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
- package/web/dist/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
- package/web/dist/assets/inter-latin-700-normal-Yt3aPRUw.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-latin-600-normal-C8RAYTDA.woff2 +0 -0
- package/web/dist/assets/marked.esm-JKvyS0Pp.js +46 -0
- package/web/dist/assets/oswald-latin-400-normal-BAFo8sP8.woff2 +0 -0
- package/web/dist/assets/oswald-latin-500-normal-B-eyT0Dw.woff2 +0 -0
- package/web/dist/assets/oswald-latin-600-normal-plFmSr5g.woff2 +0 -0
- package/web/dist/assets/oswald-latin-700-normal-vy6hmKSr.woff2 +0 -0
- package/web/dist/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
- package/web/dist/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
- package/web/dist/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
- package/web/dist/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
- package/web/dist/assets/react-DOpiTn5v.js +32 -0
- package/web/dist/assets/roman-DkYN0LX9.js +1 -0
- package/web/dist/assets/space-grotesk-latin-400-normal-CJ-V5oYT.woff2 +0 -0
- package/web/dist/assets/space-grotesk-latin-500-normal-lFbtlQH6.woff2 +0 -0
- package/web/dist/assets/space-grotesk-latin-600-normal-DjKNqYRj.woff2 +0 -0
- package/web/dist/assets/space-grotesk-latin-700-normal-RjhwGPKo.woff2 +0 -0
- package/web/dist/assets/space-mono-latin-400-italic-YylcN9Ay.woff2 +0 -0
- package/web/dist/assets/space-mono-latin-400-normal-Rg4St2Dn.woff2 +0 -0
- package/web/dist/assets/space-mono-latin-700-italic-vNvENeTh.woff2 +0 -0
- package/web/dist/assets/space-mono-latin-700-normal-mWgeinG7.woff2 +0 -0
- package/web/dist/icon-maskable.svg +10 -0
- package/web/dist/icon.svg +8 -0
- package/web/dist/index.html +21 -0
- package/web/dist/manifest.webmanifest +14 -0
- package/web/dist/sw.js +40 -0
- package/web/src/pages/Laws.jsx +230 -0
package/geneseed
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Geneseed — front door. A thin shim: it finds the checkout and hands every argument
|
|
4
|
+
# to the Node CLI, which owns the verbs, the parsing and the refusals.
|
|
5
|
+
#
|
|
6
|
+
# ./geneseed open the web console, else the TUI menu (`home`)
|
|
7
|
+
# ./geneseed <command> [args] anything bin/geneseed-cli.mjs carries
|
|
8
|
+
# ./geneseed --help the verb list, printed by the CLI itself
|
|
9
|
+
#
|
|
10
|
+
# NO VERB TABLE HERE, deliberately. This launcher used to enumerate the commands it
|
|
11
|
+
# would forward, and that list had gone stale: `migrate`, `exclude` and `rule-gate`
|
|
12
|
+
# all existed, all fell to its `*)` arm and all exited 2 — while `geneseed.cmd`
|
|
13
|
+
# passed everything through, so the two front doors disagreed about what the harness
|
|
14
|
+
# could do. One CLI, one verb table, one refusal. `./geneseed` in a checkout now
|
|
15
|
+
# behaves exactly like the `geneseed` an `npm i -g` puts on PATH, because that is
|
|
16
|
+
# this same entry point (package.json `bin`). The four HOOK verbs (`context`,
|
|
17
|
+
# `learn`, `git-gate`, `rule-gate`) live on `bin/geneseed-hook.mjs` and are reached
|
|
18
|
+
# by hooks with a baked absolute path; the CLI's refusal names that file.
|
|
19
|
+
#
|
|
20
|
+
# $GENESEED_NODE overrides the interpreter — the analogue of the $PYTHON knob this
|
|
21
|
+
# file carried while the harness was Python. Unset, a bare `node` is used.
|
|
22
|
+
set -eo pipefail
|
|
23
|
+
# Resolve through symlinks so HERE is the real repo dir. `geneseed link` writes a
|
|
24
|
+
# shim naming its entry by absolute path rather than a symlink, but an install made
|
|
25
|
+
# before it did left ~/.local/bin/geneseed pointing at THIS file, and this loop is
|
|
26
|
+
# the only reason those still work.
|
|
27
|
+
SOURCE="${BASH_SOURCE[0]}"
|
|
28
|
+
while [ -h "$SOURCE" ]; do
|
|
29
|
+
dir="$(cd -P "$(dirname "$SOURCE")" && pwd)"
|
|
30
|
+
SOURCE="$(readlink "$SOURCE")"
|
|
31
|
+
case "$SOURCE" in /*) ;; *) SOURCE="$dir/$SOURCE" ;; esac
|
|
32
|
+
done
|
|
33
|
+
HERE="$(cd -P "$(dirname "$SOURCE")" && pwd)"
|
|
34
|
+
|
|
35
|
+
# Bare `./geneseed` is the `home` verb — the web console when the environment can
|
|
36
|
+
# show it (interactive + GUI browser), else the interactive TUI menu; GENESEED_NO_WEB=1
|
|
37
|
+
# disables the web-first default. The CLI entry requires a verb, so the default is here.
|
|
38
|
+
if [ "$#" -eq 0 ]; then set -- home; fi
|
|
39
|
+
exec "${GENESEED_NODE:-node}" "$HERE/bin/geneseed-cli.mjs" "$@"
|
package/geneseed.cmd
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
rem Geneseed — native Windows front door (cmd.exe). A thin shim: it hands every
|
|
3
|
+
rem argument to the Node CLI, which owns the verbs, the parsing and the refusals.
|
|
4
|
+
rem
|
|
5
|
+
rem geneseed open the web console, else the TUI menu (`home`)
|
|
6
|
+
rem geneseed <command> [args] anything bin\geneseed-cli.mjs carries
|
|
7
|
+
rem geneseed --help the verb list, printed by the CLI itself
|
|
8
|
+
rem
|
|
9
|
+
rem There is no verb table here — see the bash `geneseed` for why the old one was a
|
|
10
|
+
rem liability. There is no PowerShell twin either: pwsh and Windows PowerShell both
|
|
11
|
+
rem run a .cmd directly, so `.\geneseed.cmd setup` is the PowerShell spelling too.
|
|
12
|
+
rem
|
|
13
|
+
rem %GENESEED_NODE% overrides the interpreter — the analogue of the %PYTHON% knob
|
|
14
|
+
rem this file carried while the harness was Python. Unset, a bare `node` is used.
|
|
15
|
+
rem
|
|
16
|
+
rem TWO MECHANICS, both measured rather than assumed:
|
|
17
|
+
rem * `exit /b %ERRORLEVEL%`, not a bare `exit /b`. A bare one propagates a child's
|
|
18
|
+
rem exit code fine, but returns 0 when the command could not be LAUNCHED at all —
|
|
19
|
+
rem which is exactly the %GENESEED_NODE%-is-wrong case, and "the front door
|
|
20
|
+
rem reported success having run nothing" is the worst answer available.
|
|
21
|
+
rem * a `goto`, not a parenthesized `if` block, so each `exit /b` sits on its own
|
|
22
|
+
rem top-level line. %ERRORLEVEL% inside a block expands at parse time and would
|
|
23
|
+
rem return a stale code — which is what forced the bare form in the first place.
|
|
24
|
+
setlocal
|
|
25
|
+
set "HERE=%~dp0"
|
|
26
|
+
set "NODE=node"
|
|
27
|
+
if defined GENESEED_NODE set "NODE=%GENESEED_NODE%"
|
|
28
|
+
if "%~1"=="" goto :home
|
|
29
|
+
"%NODE%" "%HERE%bin\geneseed-cli.mjs" %*
|
|
30
|
+
exit /b %ERRORLEVEL%
|
|
31
|
+
|
|
32
|
+
:home
|
|
33
|
+
"%NODE%" "%HERE%bin\geneseed-cli.mjs" home
|
|
34
|
+
exit /b %ERRORLEVEL%
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "version is a human-readable release label only; the canonical identity of an installed harness is the source fingerprint stamped in .geneseed-version (see `harness version`), not this string.",
|
|
3
|
+
"theme": "neutral",
|
|
4
|
+
"product": "Geneseed",
|
|
5
|
+
"version": "2.0.0"
|
|
6
|
+
}
|
package/js/anim.mjs
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `rituals/theme_anim.py` — the per-theme install animation the LINE wizard plays.
|
|
3
|
+
*
|
|
4
|
+
* P7c, and the one leftover of that phase that is NOT the declared curses panel. P5i ported
|
|
5
|
+
* `_setup_lines` and left this out with a note — "belongs to P7 with the rest of the terminal
|
|
6
|
+
* layer" — and the rest of the terminal layer turned out to be the panel, which stays
|
|
7
|
+
* declared (`tests/test_tui_boundary.py` says why, and asserts it). This module is the part
|
|
8
|
+
* that has nothing to do with curses: it draws on the ORDINARY terminal, with cursor-up
|
|
9
|
+
* escapes and a sleep, and its only caller is a function that already crossed.
|
|
10
|
+
*
|
|
11
|
+
* SO IT IS NOT IN `js/tui.mjs`, DELIBERATELY. That module carries a structural assertion that
|
|
12
|
+
* it contains no escape sequence at all — the thing that makes "the panel is declared" a
|
|
13
|
+
* measurement instead of a promise. This file necessarily contains `\x1b[{n}A`, so putting it
|
|
14
|
+
* there would have turned that assertion red and invited someone to weaken it. A line-mode
|
|
15
|
+
* animation and a full-screen panel are different subsystems in the reference too: they live
|
|
16
|
+
* in different files there and they do here.
|
|
17
|
+
*
|
|
18
|
+
* WHAT THE PYTHON DOES THAT THIS DOES NOT, and it is one line. `_anim_ok` calls
|
|
19
|
+
* `os.system("")` on Windows, whose only purpose is the side effect of making the console
|
|
20
|
+
* enable VT/ANSI processing; it is not a probe and its return value is discarded. Node's
|
|
21
|
+
* console writer already enables virtual-terminal processing on Windows when stdout is a TTY
|
|
22
|
+
* (libuv does it when the stream is opened), so the effect is present and the call is not
|
|
23
|
+
* needed — which is fortunate, because it is a `child_process` spawn and this port is barred
|
|
24
|
+
* from those. The consequence is that the reference's `except Exception: return False` arm
|
|
25
|
+
* around it has no counterpart here. That arm needs `cmd.exe` to be unlaunchable on a machine
|
|
26
|
+
* that has already reached an interactive wizard, and `anim_ok` is compared over ten
|
|
27
|
+
* environments in `tests/test_pure_function_parity.py` — none of which can produce it on
|
|
28
|
+
* either side.
|
|
29
|
+
*
|
|
30
|
+
* WHAT GATES IT. `tests/test_pure_function_parity.py`'s `_theme_anim_cases()`: `art_for` over
|
|
31
|
+
* every key plus the prototype-chain names, `_place`/`_tile`/`_height` over their arithmetic,
|
|
32
|
+
* `_anim_ok` over its whole decision table, and `play_line` itself over 24 environments —
|
|
33
|
+
* captured on the reference side through a real `TextIOWrapper`, so the CRLF translation is
|
|
34
|
+
* inside the comparison rather than outside it.
|
|
35
|
+
*/
|
|
36
|
+
import { pyInt, pyLen, pyLjust, pyPrint } from './lib/fs.mjs';
|
|
37
|
+
|
|
38
|
+
export const ART = {
|
|
39
|
+
imperial: {
|
|
40
|
+
title: '+ ADEPTUS ASTARTES +',
|
|
41
|
+
sprite: [
|
|
42
|
+
[' .---. ', ' |[o o]| ', '<#|=====|#====-', ' | ### | ', ' / \\ '],
|
|
43
|
+
[' .---. ', ' |[o o]| ', '<#|=====|#====-', ' | ### | ', ' \\ / '],
|
|
44
|
+
],
|
|
45
|
+
ground: '_',
|
|
46
|
+
card: [
|
|
47
|
+
' |>>| ',
|
|
48
|
+
" .--'=='--. ",
|
|
49
|
+
' |[ O O ]| ',
|
|
50
|
+
' <#|==========|#===---',
|
|
51
|
+
' | ::##:: | ',
|
|
52
|
+
' / \\ ',
|
|
53
|
+
' THE EMPEROR PROTECTS',
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
pirate: {
|
|
57
|
+
title: '~ HOIST THE COLOURS ~',
|
|
58
|
+
sprite: [
|
|
59
|
+
[' | ', ' /|\\ ', ' /_|_\\ ', ' \\=====/ '],
|
|
60
|
+
[' | ', ' /|\\ ', ' /_|_\\ ', ' \\=====/ '],
|
|
61
|
+
],
|
|
62
|
+
ground: '~^~ ',
|
|
63
|
+
card: [
|
|
64
|
+
' | | ',
|
|
65
|
+
' /|\\ /| ',
|
|
66
|
+
' /_|_\\/_| ',
|
|
67
|
+
' __|~~~~~~~~~~|__ ',
|
|
68
|
+
' \\===== JOLLY ====/ ',
|
|
69
|
+
' ~^~~^~~^~~^~~^~~^~~^~~ ',
|
|
70
|
+
' DEAD MEN TELL NO TALES',
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
cyberpunk: {
|
|
74
|
+
title: '>_ JACK IN',
|
|
75
|
+
sprite: [
|
|
76
|
+
['1 0 01 1 0', ' 0 11 0 1 0', '0 1 10 0 1', ' 11 0 1 10'],
|
|
77
|
+
['0 1 10 0 1', ' 1 00 1 0 1', '1 0 01 1 0', ' 00 1 0 01'],
|
|
78
|
+
],
|
|
79
|
+
ground: '=-',
|
|
80
|
+
card: [
|
|
81
|
+
' 010 1 0 11 0 1 010 ',
|
|
82
|
+
'1 01 0 11 0 1 00 1 0',
|
|
83
|
+
' 11 0 WAKE UP 1 0 1 ',
|
|
84
|
+
'0 1 01 0 11 0 1 00 1',
|
|
85
|
+
' 1 0 THE NET IS YOURS 0',
|
|
86
|
+
'=-=-=-=-=-=-=-=-=-=-=-=-',
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
gamer: {
|
|
90
|
+
title: '* PRESS START *',
|
|
91
|
+
sprite: [
|
|
92
|
+
[' ___ ', ' /C o o o ', ' \\___ '],
|
|
93
|
+
[' ___ ', ' (c o o o', ' \\___ '],
|
|
94
|
+
],
|
|
95
|
+
ground: '.',
|
|
96
|
+
card: [
|
|
97
|
+
' ___ .oOo. ',
|
|
98
|
+
' /C o o o o o | x x | ',
|
|
99
|
+
' \\___ |__o__| ',
|
|
100
|
+
' HI-SCORE 999999 1UP x3 ',
|
|
101
|
+
' >>> LEVEL CLEARED <<< ',
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
military: {
|
|
105
|
+
title: '[ ADVANCE - MOVE OUT ]',
|
|
106
|
+
sprite: [
|
|
107
|
+
[' ____ ', ' __/ \\__==-', '| o o o | ', "'-O-O-O-O-' "],
|
|
108
|
+
[' ____ ', ' __/ \\__==-', '| o o o | ', "'O-O-O-O-O' "],
|
|
109
|
+
],
|
|
110
|
+
ground: '^',
|
|
111
|
+
card: [
|
|
112
|
+
' ____ ',
|
|
113
|
+
' __/ \\_____===- ',
|
|
114
|
+
' | o o o | ',
|
|
115
|
+
" '-O-O-O-O-O-O-' ",
|
|
116
|
+
' ^^^^^^^^^^^^^^^^^^^^ ',
|
|
117
|
+
' MISSION GO GO GO ',
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
sports: {
|
|
121
|
+
title: '<< GAME ON >>',
|
|
122
|
+
sprite: [
|
|
123
|
+
[' (o) |‾|', ' | |', ' _________|_|'],
|
|
124
|
+
[' (o) |‾|', ' | |', ' _________|_|'],
|
|
125
|
+
],
|
|
126
|
+
ground: '_',
|
|
127
|
+
card: [
|
|
128
|
+
' .---. ',
|
|
129
|
+
' (o) | | ',
|
|
130
|
+
' / | G | ',
|
|
131
|
+
' ________________ |_O_| ',
|
|
132
|
+
' >>> GOOOAAL !!! <<< ',
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
wizard: {
|
|
136
|
+
title: '~* CAST THE RITE *~',
|
|
137
|
+
sprite: [
|
|
138
|
+
[' ,* . ', ' /| * ', ' (o o) . ', ' /|*|\\ * ', ' | | '],
|
|
139
|
+
[' . *, ', ' /| . ', ' (o o) * ', ' /|*|\\ . ', ' | | '],
|
|
140
|
+
],
|
|
141
|
+
ground: '.',
|
|
142
|
+
card: [
|
|
143
|
+
' ,*. * . ',
|
|
144
|
+
' /| * . * ',
|
|
145
|
+
' (o o) . * . ',
|
|
146
|
+
' /|**|\\ * * ',
|
|
147
|
+
' | | . THE ',
|
|
148
|
+
' ARCANE IS BOUND ',
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
neutral: {
|
|
152
|
+
title: 'Geneseed',
|
|
153
|
+
sprite: [
|
|
154
|
+
['>>> '],
|
|
155
|
+
[' >>> '],
|
|
156
|
+
[' >>> '],
|
|
157
|
+
],
|
|
158
|
+
ground: '',
|
|
159
|
+
card: [
|
|
160
|
+
' [############------] ',
|
|
161
|
+
' harness ready. ',
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export const DEFAULT = 'neutral';
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* `theme_anim.art_for` — `ART.get(theme, ART[DEFAULT])`.
|
|
170
|
+
*
|
|
171
|
+
* `Object.hasOwn`, not `ART[theme] ?? ART[DEFAULT]`. `ART['constructor']` is a function on
|
|
172
|
+
* the prototype chain and therefore not nullish, so the `??` spelling would hand a Function
|
|
173
|
+
* back to `play_line` where the reference hands back the neutral theme. The third table in
|
|
174
|
+
* this port to carry the trap — `_icon` and `_mark` were the first two — and the corpus
|
|
175
|
+
* names all four inherited keys.
|
|
176
|
+
*/
|
|
177
|
+
export function artFor(theme) {
|
|
178
|
+
return Object.hasOwn(ART, theme) ? ART[theme] : ART[DEFAULT];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** A code-point slice — Python's `s[a:b]`, which does not count UTF-16 units. */
|
|
182
|
+
const cut = (s, a, b) => Array.from(s).slice(a, b).join('');
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* `shutil.get_terminal_size((80, 24)).columns`.
|
|
186
|
+
*
|
|
187
|
+
* `pyInt` and not `parseInt`, and the corpus has a case for exactly that: Python's `int()`
|
|
188
|
+
* refuses `'80x'` outright and `get_terminal_size` swallows the ValueError into the fallback,
|
|
189
|
+
* where `parseInt('80x')` is a cheerful 80. A width one column off is invisible in a terminal
|
|
190
|
+
* and loud in a byte comparison. `<= 0` is the reference's own guard, which is why `'0'` and
|
|
191
|
+
* `'-5'` fall through to the fallback rather than producing a zero-width canvas.
|
|
192
|
+
*/
|
|
193
|
+
function terminalColumns() {
|
|
194
|
+
const env = pyInt(process.env.COLUMNS);
|
|
195
|
+
if (env !== null && env > 0) return env;
|
|
196
|
+
// `sys.__stdout__` over there, `process.stdout` here. Both are undefined-ish off a
|
|
197
|
+
// terminal, and both then take the (80, 24) fallback.
|
|
198
|
+
const real = process.stdout.columns;
|
|
199
|
+
return typeof real === 'number' && real > 0 ? real : 80;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* `theme_anim._anim_ok` — whether an in-place ANSI animation is safe here.
|
|
204
|
+
*
|
|
205
|
+
* ⚠ TERM=dumb AND TERM UNSET ARE NOT REFUSALS ON WINDOWS. The reference guards that branch
|
|
206
|
+
* with `if os.name != "nt"`, so on Windows a dumb TERM still animates — which is right,
|
|
207
|
+
* because TERM is not what a Windows console is configured by. Reading the branch as an
|
|
208
|
+
* unconditional refusal would disable the animation on the only platform whose users get the
|
|
209
|
+
* line wizard by default.
|
|
210
|
+
*/
|
|
211
|
+
export function animOk() {
|
|
212
|
+
if (process.env.GENESEED_NO_ANIM || process.env.GENESEED_TUI_PLAIN) return false;
|
|
213
|
+
if (!process.stdout.isTTY) return false;
|
|
214
|
+
const term = (process.env.TERM || '').toLowerCase();
|
|
215
|
+
if (term === 'dumb' || term === '') {
|
|
216
|
+
if (process.platform !== 'win32') return false;
|
|
217
|
+
}
|
|
218
|
+
// The reference's `os.system("")` goes here; see the module header for why it does not.
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** `theme_anim._place` — a sprite row drawn at column `x`, clipped to `width`. */
|
|
223
|
+
export function place(row, x, width) {
|
|
224
|
+
const s = x >= 0 ? ' '.repeat(x) + row : cut(row, -x);
|
|
225
|
+
return cut(s, 0, width);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* `theme_anim._tile` — the ground line, scrolled by `phase`.
|
|
230
|
+
*
|
|
231
|
+
* Two Python arithmetic rules, both of which JavaScript spells differently. `width //
|
|
232
|
+
* len(tile)` is FLOOR division, so a negative width gives a negative repeat count — which
|
|
233
|
+
* Python turns into an empty string and `String.repeat` turns into a RangeError, hence the
|
|
234
|
+
* clamp. And `phase % len(tile)` is non-negative in Python for a positive divisor while
|
|
235
|
+
* JavaScript keeps the sign of the dividend, so a negative phase indexes off the front of the
|
|
236
|
+
* string instead of wrapping to the end. `_spin` had the same trap in P7b.
|
|
237
|
+
*/
|
|
238
|
+
export function tile(tileStr, width, phase = 0) {
|
|
239
|
+
if (!tileStr) return '';
|
|
240
|
+
const n = pyLen(tileStr);
|
|
241
|
+
const base = tileStr.repeat(Math.max(0, Math.floor(width / n) + 2));
|
|
242
|
+
const off = ((phase % n) + n) % n;
|
|
243
|
+
return cut(base, off, off + width);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** `theme_anim._height` — `max((len(p) for p in poses), default=0)`. */
|
|
247
|
+
export function height(poses) {
|
|
248
|
+
return Math.max(0, ...poses.map((p) => p.length));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* `str.center(width)`, which is not `padStart`+`padEnd` and not symmetric.
|
|
253
|
+
*
|
|
254
|
+
* CPython's `stringlib` computes `left = marg // 2 + (marg & width & 1)`, and that last term
|
|
255
|
+
* is a genuine oddity rather than a rounding convention: when the margin AND the width are
|
|
256
|
+
* both odd the extra column goes on the LEFT, and otherwise on the right. Nothing about
|
|
257
|
+
* "centre this" implies it. The title line of every animation is the only caller.
|
|
258
|
+
*/
|
|
259
|
+
function pyCenter(s, width) {
|
|
260
|
+
const marg = width - pyLen(s);
|
|
261
|
+
if (marg <= 0) return s;
|
|
262
|
+
/* eslint-disable-next-line no-bitwise */
|
|
263
|
+
const left = Math.floor(marg / 2) + (marg & width & 1);
|
|
264
|
+
return ' '.repeat(left) + s + ' '.repeat(marg - left);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** `time.sleep(seconds)`, synchronously. `Atomics.wait` is the stdlib's only sync sleep. */
|
|
268
|
+
const sleep = (ms) => {
|
|
269
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* `theme_anim.play_line` — scroll the themed sprite, then settle on the static card.
|
|
274
|
+
*
|
|
275
|
+
* Never raises: the reference wraps the animation in `except Exception` and degrades to the
|
|
276
|
+
* static card, and `_setup_lines` wraps the whole call again in a bare `except: pass` marked
|
|
277
|
+
* "cosmetic only — never block a successful install". Both layers are kept. A failed
|
|
278
|
+
* animation must not be able to fail an install that already succeeded.
|
|
279
|
+
*/
|
|
280
|
+
export function playLine(theme, ok = true) {
|
|
281
|
+
const art = artFor(theme);
|
|
282
|
+
const width = Math.min(terminalColumns() - 1, 56);
|
|
283
|
+
pyPrint('\n');
|
|
284
|
+
pyPrint(`${pyCenter(art.title, width)}\n`);
|
|
285
|
+
if (!ok) return;
|
|
286
|
+
if (!animOk()) {
|
|
287
|
+
for (const r of art.card) pyPrint(`${cut(r, 0, width)}\n`);
|
|
288
|
+
pyPrint('\n');
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const poses = art.sprite;
|
|
292
|
+
const ground = art.ground || '';
|
|
293
|
+
const h = height(poses);
|
|
294
|
+
const canvasH = h + (ground ? 1 : 0);
|
|
295
|
+
const spriteW = Math.max(0, ...poses.flatMap((p) => p.map((r) => pyLen(r))));
|
|
296
|
+
try {
|
|
297
|
+
pyPrint('\n'.repeat(canvasH));
|
|
298
|
+
const steps = Math.min(width + spriteW, 64); // cap to ~1.6s of motion
|
|
299
|
+
for (let i = 0; i <= steps; i += 1) {
|
|
300
|
+
const pose = poses[Math.floor(i / 3) % poses.length];
|
|
301
|
+
const x = width - i; // enter from the right, travel left
|
|
302
|
+
const lines = [];
|
|
303
|
+
for (let r = 0; r < h; r += 1) lines.push(place(r < pose.length ? pose[r] : '', x, width));
|
|
304
|
+
if (ground) lines.push(tile(ground, width, i));
|
|
305
|
+
pyPrint(`\x1b[${canvasH}A`);
|
|
306
|
+
for (const ln of lines) pyPrint(`\r${cut(pyLjust(ln, width), 0, width)}\n`);
|
|
307
|
+
sleep(25);
|
|
308
|
+
}
|
|
309
|
+
// settle on the card
|
|
310
|
+
pyPrint(`\x1b[${canvasH}A`);
|
|
311
|
+
const card = art.card;
|
|
312
|
+
for (let r = 0; r < canvasH; r += 1) {
|
|
313
|
+
const row = r < card.length ? cut(card[r], 0, width) : '';
|
|
314
|
+
pyPrint(`\r${cut(pyLjust(row, width), 0, width)}\n`);
|
|
315
|
+
}
|
|
316
|
+
// any extra card rows beyond the canvas
|
|
317
|
+
for (let r = canvasH; r < card.length; r += 1) pyPrint(`${cut(card[r], 0, width)}\n`);
|
|
318
|
+
pyPrint('\n');
|
|
319
|
+
} catch {
|
|
320
|
+
// Terminal didn't cooperate — leave a clean static card.
|
|
321
|
+
try {
|
|
322
|
+
for (const r of art.card) pyPrint(`${cut(r, 0, width)}\n`);
|
|
323
|
+
pyPrint('\n');
|
|
324
|
+
} catch { /* nothing left to try */ }
|
|
325
|
+
}
|
|
326
|
+
}
|
package/js/catalog.mjs
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `geneseed catalog` — the shipped roster, in a terminal.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS VERB IS NEW RATHER THAN PORTED. The reference had no `catalog` subcommand: the
|
|
5
|
+
* same information existed only behind the web console's catalog endpoint and behind the
|
|
6
|
+
* full-screen browse panel, so the one way to ask "what does this harness actually ship"
|
|
7
|
+
* from a shell was to start a server. The endpoint's answer is the source of the fields
|
|
8
|
+
* rendered here — name, one-line purpose, source file, lifecycle status, and a skill's
|
|
9
|
+
* category — so this is a second FACE on one walk and not a second walk.
|
|
10
|
+
*
|
|
11
|
+
* THAT WALK IS `tuiInventory`, WHICH ALREADY HAD FOUR CALLERS and now has five. Nothing
|
|
12
|
+
* about the classification lives here; a listing that re-derived "which files are agents"
|
|
13
|
+
* would be the second classifier the inventory module's header exists to forbid.
|
|
14
|
+
*
|
|
15
|
+
* IT RETIRES THREE HELPERS THAT HAD NO PRODUCT CALLER. `tuiEntries`, `icon`/`fit`/`truncd`
|
|
16
|
+
* and the display-width table under them were ported for a panel that was then dropped, and
|
|
17
|
+
* were reachable only from their own gates. They are the right tools for this rendering
|
|
18
|
+
* — `dwidth` is what keeps an emoji-bearing purpose line from breaking the column — so the
|
|
19
|
+
* roster is built out of them rather than beside them. `detailLines` is NOT used and stays
|
|
20
|
+
* uncalled: it renders a selected entry's whole body into a detail pane, which a listing
|
|
21
|
+
* has no place for.
|
|
22
|
+
*
|
|
23
|
+
* NOTHING HERE PAINTS. No cursor moves, no clears, no alternate screen: this writes lines
|
|
24
|
+
* and stops, exactly as `status` does. That is what keeps the dropped panel dropped.
|
|
25
|
+
*/
|
|
26
|
+
import { helpWidth } from './cli.mjs';
|
|
27
|
+
import { tuiInventory } from './inventory.mjs';
|
|
28
|
+
import { defaultTheme, installedDefaults } from './installs.mjs';
|
|
29
|
+
import { pyPrint } from './lib/fs.mjs';
|
|
30
|
+
import { dwidth, fit, icon, truncd, tuiEntries } from './tui.mjs';
|
|
31
|
+
|
|
32
|
+
/** The listing's section names, and the row kind each one selects. */
|
|
33
|
+
export const CATALOG_KINDS = { agents: 'agent', skills: 'skill', laws: 'law' };
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The badge column: what the catalog endpoint carries beside the name that is not the
|
|
37
|
+
* purpose. An agent has a lifecycle status, a skill has that and a category, a law has a
|
|
38
|
+
* governance class and no status at all — which is the endpoint's shape, not a shortcut.
|
|
39
|
+
*/
|
|
40
|
+
function badgeFor(kind, data) {
|
|
41
|
+
if (kind === 'law') return data.klass ?? 'craft';
|
|
42
|
+
if (kind === 'skill') return `${data.klass ?? 'build'}/${data.status ?? 'unknown'}`;
|
|
43
|
+
return data.status ?? 'unknown';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The rendered roster as lines, with no trailing newline on any of them.
|
|
48
|
+
*
|
|
49
|
+
* A SECTION WITH NO ROWS PRINTS NO HEADING. The heading is emitted lazily, when the first
|
|
50
|
+
* row that survives the filter arrives — so `catalog laws` on an install whose render
|
|
51
|
+
* produced no laws is EMPTY rather than a heading over nothing, and the caller can tell the
|
|
52
|
+
* two apart and say so.
|
|
53
|
+
*
|
|
54
|
+
* The name column is measured over the rows actually being printed, not over the whole
|
|
55
|
+
* inventory: filtering to `agents` must not leave a gutter the width of the longest law
|
|
56
|
+
* title. `dwidth` rather than `String.length`, because a purpose line can carry an emoji
|
|
57
|
+
* and a status glyph is two columns wide in every tier but ASCII.
|
|
58
|
+
*/
|
|
59
|
+
export function catalogLines(inv, section, width, showSource = false) {
|
|
60
|
+
const kinds = section ? [CATALOG_KINDS[section]] : Object.values(CATALOG_KINDS);
|
|
61
|
+
const rows = tuiEntries(inv);
|
|
62
|
+
const shown = rows.filter(([kind]) => kinds.includes(kind));
|
|
63
|
+
if (!shown.length) return [];
|
|
64
|
+
const cap = Math.max(12, Math.floor(width / 2));
|
|
65
|
+
const nameW = Math.min(cap, Math.max(...shown.map(([, label]) => dwidth(label))));
|
|
66
|
+
const badgeW = Math.max(...shown.map(([kind, , data]) => dwidth(badgeFor(kind, data))));
|
|
67
|
+
|
|
68
|
+
const out = [];
|
|
69
|
+
let pending = null;
|
|
70
|
+
for (const [kind, label, data] of rows) {
|
|
71
|
+
if (kind === 'head') { pending = label; continue; }
|
|
72
|
+
if (!kinds.includes(kind)) continue;
|
|
73
|
+
if (pending !== null) {
|
|
74
|
+
if (out.length) out.push('');
|
|
75
|
+
out.push(pending);
|
|
76
|
+
pending = null;
|
|
77
|
+
}
|
|
78
|
+
// `source` is the endpoint's field and is an absolute path, so it replaces the purpose
|
|
79
|
+
// column rather than joining it — two long strings on one line is a column layout that
|
|
80
|
+
// is a wrapped mess on every terminal narrower than the sum of them.
|
|
81
|
+
//
|
|
82
|
+
// AND IT IS THE ONE COLUMN THAT IS NEVER TRUNCATED. A purpose line cut at the terminal
|
|
83
|
+
// edge still reads; half a path is not a path, and the reason to ask for one is to hand
|
|
84
|
+
// it to something else.
|
|
85
|
+
const tail = showSource ? (data.source ?? '') : (data.desc ?? '');
|
|
86
|
+
const row = ` ${icon(kind)} ${fit(label, nameW)} ${fit(badgeFor(kind, data), badgeW)} ${tail}`
|
|
87
|
+
.replace(/\s+$/, '');
|
|
88
|
+
out.push(showSource ? row : truncd(row, width));
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The verb.
|
|
95
|
+
*
|
|
96
|
+
* An unknown `--theme` is refused by `effectiveTheme` on the way through the render, with
|
|
97
|
+
* the wording every other theme-taking verb already prints — so there is no second theme
|
|
98
|
+
* validator here, and the entry point's `exitCode` arm carries the refusal out.
|
|
99
|
+
*/
|
|
100
|
+
export function cmdCatalog(args) {
|
|
101
|
+
const theme = args.theme || installedDefaults().theme || defaultTheme();
|
|
102
|
+
// `helpWidth()` is `COLUMNS`, else the terminal, else 80 — minus the two columns argparse
|
|
103
|
+
// subtracts for its own layout. This listing wants the whole terminal, so it adds them
|
|
104
|
+
// back rather than owning a second copy of that precedence chain.
|
|
105
|
+
const lines = catalogLines(tuiInventory(theme), args.section, helpWidth() + 2, args.source);
|
|
106
|
+
if (!lines.length) {
|
|
107
|
+
pyPrint(`[catalog] nothing to list${args.section ? ` under ${args.section}` : ''}.\n`);
|
|
108
|
+
return 1;
|
|
109
|
+
}
|
|
110
|
+
for (const line of lines) pyPrint(`${line}\n`);
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
package/js/checkout.mjs
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The checkout this code is running FROM — `_build_core`'s ROOT and the paths under it,
|
|
3
|
+
* plus the `cfg` every render needs.
|
|
4
|
+
*
|
|
5
|
+
* The inverse of `js/hosts.mjs`. That module answers "where does the output go"; this one
|
|
6
|
+
* answers "where is the source". Both were `bin/geneseed.mjs`'s, and both leave it for the
|
|
7
|
+
* same arithmetic reason: `bin/geneseed-cli.mjs` needs them too, and the value that decides
|
|
8
|
+
* which tree gets rendered is the last thing that should exist twice.
|
|
9
|
+
*
|
|
10
|
+
* WHY A NON-EMITTING VERB NEEDS A cfg AT ALL. `harness status` counts agents, skills and
|
|
11
|
+
* laws, and it counts them by RENDERING — `_status_data` -> `_tui_inventory(theme)` ->
|
|
12
|
+
* `build.render_all(theme)`. So the CLI has to build the same `cfg` the driver builds, and
|
|
13
|
+
* until this move only the driver could.
|
|
14
|
+
*
|
|
15
|
+
* THE TWO ABSENT KEYS ARE STILL ABSENT, and the reason is bin/geneseed.mjs's, unchanged:
|
|
16
|
+
* `_build_core.js_cfg()` always sends `structure` and `capabilityLinkRe` because the Python
|
|
17
|
+
* originals are module-level names TESTS MUTATE (`_OWNED` membership asked one level out).
|
|
18
|
+
* A Node process has no Python module to mutate, so it sends neither and
|
|
19
|
+
* `js/render.mjs`'s `cfg.structure ?? STRUCTURE` takes its right-hand branch.
|
|
20
|
+
*
|
|
21
|
+
* The move is safe for the reason P5c's was: `tests/golden.py` drives `bin/geneseed.mjs`
|
|
22
|
+
* over 259 cells and compares the tree byte-for-byte, and every one of them builds a cfg
|
|
23
|
+
* from these paths. A depth error or a renamed key fails 259 cells, not zero.
|
|
24
|
+
*/
|
|
25
|
+
import { readdirSync } from 'node:fs';
|
|
26
|
+
import path from 'node:path';
|
|
27
|
+
import { fileURLToPath } from 'node:url';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* `_build_core.ROOT` — the checkout, from this file's own location (`js/..`).
|
|
31
|
+
*
|
|
32
|
+
* NOT redirectable, and that is a property rather than a gap. The Python side is
|
|
33
|
+
* `Path(__file__).resolve().parent` with every path under it derived at import; the
|
|
34
|
+
* `_OWNED` redirect the suite uses is an in-process write, and no env var moves it
|
|
35
|
+
* (`$GENESEED_ROOT` is `harness context`'s doc-discovery root — a different name for a
|
|
36
|
+
* different job). Both implementations therefore answer from their own file's location,
|
|
37
|
+
* which is what makes them comparable and what makes them unfenceable. See the
|
|
38
|
+
* status/version section of `tests/harness_golden.py`.
|
|
39
|
+
*/
|
|
40
|
+
export const ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
41
|
+
export const SRC = path.join(ROOT, 'src');
|
|
42
|
+
export const CONFIG = path.join(ROOT, 'harness.config.json');
|
|
43
|
+
export const THEMES = path.join(ROOT, 'themes');
|
|
44
|
+
export const PLUGIN_SRC = path.join(ROOT, 'adapters', 'opencode', 'plugins');
|
|
45
|
+
export const WORKFLOW_SRC = path.join(ROOT, 'adapters', 'opencode', 'workflows');
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* `_build_render.posture_names()` / `mode_names()` — discovered, never hardcoded, so a new
|
|
49
|
+
* posture file appears in both CLIs' choices with no code change.
|
|
50
|
+
*
|
|
51
|
+
* P5f moved this here from `bin/geneseed.mjs`, which had the only caller until
|
|
52
|
+
* `js/installs.mjs` needed the same two lists: `_posture_of_dir` scans a deployed carrier for
|
|
53
|
+
* `**<Name>**` and has to be scanning for the SAME names the driver's `--posture` accepts.
|
|
54
|
+
* Two discoveries of one directory is precisely the shape that lets a new posture file be
|
|
55
|
+
* buildable and undetectable.
|
|
56
|
+
*/
|
|
57
|
+
export function discoverNames(dir, first) {
|
|
58
|
+
let names = [];
|
|
59
|
+
try {
|
|
60
|
+
names = readdirSync(path.join(SRC, dir))
|
|
61
|
+
.filter((f) => f.endsWith('.md') && path.basename(f, '.md').toLowerCase() !== 'readme')
|
|
62
|
+
.map((f) => path.basename(f, '.md'))
|
|
63
|
+
.sort();
|
|
64
|
+
} catch { /* missing dir — fall through to the single default below */ }
|
|
65
|
+
names.sort((a, b) => (a !== first) - (b !== first) || (a < b ? -1 : a > b ? 1 : 0));
|
|
66
|
+
return names.length ? names : [first];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* `_build_core.js_cfg()`, originated rather than received.
|
|
71
|
+
*
|
|
72
|
+
* `posture` and `mode` are `_build_core`'s module defaults (`peer` / `direct`) unless a
|
|
73
|
+
* caller overrides them. `build.py`'s `main()` sets them from its flags; `rituals/harness.py`
|
|
74
|
+
* never touches them, so every harness verb that renders renders at the defaults.
|
|
75
|
+
*/
|
|
76
|
+
export function makeCfg({ posture = 'peer', mode = 'direct' } = {}) {
|
|
77
|
+
return {
|
|
78
|
+
root: ROOT,
|
|
79
|
+
src: SRC,
|
|
80
|
+
themes: THEMES,
|
|
81
|
+
config: CONFIG,
|
|
82
|
+
colorThemes: path.join(THEMES, 'opencode'),
|
|
83
|
+
pluginSrc: PLUGIN_SRC,
|
|
84
|
+
workflowSrc: WORKFLOW_SRC,
|
|
85
|
+
posture,
|
|
86
|
+
mode,
|
|
87
|
+
};
|
|
88
|
+
}
|