@ag-eco/agentplate-cli 0.13.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/LICENSE +21 -0
- package/README.md +462 -0
- package/agents/ap-co-creation.md +90 -0
- package/agents/builder.md +144 -0
- package/agents/coordinator.md +377 -0
- package/agents/lead.md +435 -0
- package/agents/merger.md +164 -0
- package/agents/monitor.md +214 -0
- package/agents/orchestrator.md +239 -0
- package/agents/reviewer.md +140 -0
- package/agents/scout.md +125 -0
- package/agents/supervisor.md +427 -0
- package/package.json +66 -0
- package/src/agents/capabilities.test.ts +85 -0
- package/src/agents/capabilities.ts +125 -0
- package/src/agents/checkpoint.test.ts +88 -0
- package/src/agents/checkpoint.ts +101 -0
- package/src/agents/copilot-hooks-deployer.test.ts +162 -0
- package/src/agents/copilot-hooks-deployer.ts +93 -0
- package/src/agents/guard-rules.test.ts +372 -0
- package/src/agents/guard-rules.ts +97 -0
- package/src/agents/headless-mail-injector.test.ts +709 -0
- package/src/agents/headless-mail-injector.ts +377 -0
- package/src/agents/headless-prompt.test.ts +102 -0
- package/src/agents/headless-prompt.ts +68 -0
- package/src/agents/hooks-deployer.test.ts +3119 -0
- package/src/agents/hooks-deployer.ts +804 -0
- package/src/agents/identity.test.ts +604 -0
- package/src/agents/identity.ts +384 -0
- package/src/agents/lifecycle.test.ts +196 -0
- package/src/agents/lifecycle.ts +183 -0
- package/src/agents/mail-poll-detect.test.ts +153 -0
- package/src/agents/mail-poll-detect.ts +73 -0
- package/src/agents/manifest.test.ts +1026 -0
- package/src/agents/manifest.ts +376 -0
- package/src/agents/overlay.test.ts +1058 -0
- package/src/agents/overlay.ts +490 -0
- package/src/agents/scope-detect.test.ts +190 -0
- package/src/agents/scope-detect.ts +146 -0
- package/src/agents/turn-lock.test.ts +181 -0
- package/src/agents/turn-lock.ts +235 -0
- package/src/agents/turn-runner-dispatch.test.ts +182 -0
- package/src/agents/turn-runner-dispatch.ts +105 -0
- package/src/agents/turn-runner.test.ts +2312 -0
- package/src/agents/turn-runner.ts +1383 -0
- package/src/beads/client.test.ts +217 -0
- package/src/beads/client.ts +230 -0
- package/src/beads/molecules.test.ts +338 -0
- package/src/beads/molecules.ts +198 -0
- package/src/commands/agents.test.ts +328 -0
- package/src/commands/agents.ts +299 -0
- package/src/commands/clean.test.ts +797 -0
- package/src/commands/clean.ts +791 -0
- package/src/commands/completions.test.ts +348 -0
- package/src/commands/completions.ts +981 -0
- package/src/commands/coordinator.test.ts +2975 -0
- package/src/commands/coordinator.ts +1841 -0
- package/src/commands/costs.test.ts +1183 -0
- package/src/commands/costs.ts +599 -0
- package/src/commands/dashboard.test.ts +954 -0
- package/src/commands/dashboard.ts +1212 -0
- package/src/commands/discover.test.ts +288 -0
- package/src/commands/discover.ts +202 -0
- package/src/commands/doctor.test.ts +303 -0
- package/src/commands/doctor.ts +311 -0
- package/src/commands/ecosystem.test.ts +226 -0
- package/src/commands/ecosystem.ts +248 -0
- package/src/commands/errors.test.ts +654 -0
- package/src/commands/errors.ts +197 -0
- package/src/commands/feed.test.ts +709 -0
- package/src/commands/feed.ts +260 -0
- package/src/commands/group.test.ts +475 -0
- package/src/commands/group.ts +546 -0
- package/src/commands/hooks.test.ts +458 -0
- package/src/commands/hooks.ts +263 -0
- package/src/commands/init.test.ts +1011 -0
- package/src/commands/init.ts +967 -0
- package/src/commands/inspect.test.ts +1239 -0
- package/src/commands/inspect.ts +648 -0
- package/src/commands/log.test.ts +1913 -0
- package/src/commands/log.ts +958 -0
- package/src/commands/logs.test.ts +801 -0
- package/src/commands/logs.ts +483 -0
- package/src/commands/mail.test.ts +1501 -0
- package/src/commands/mail.ts +848 -0
- package/src/commands/merge.test.ts +864 -0
- package/src/commands/merge.ts +381 -0
- package/src/commands/metrics.test.ts +458 -0
- package/src/commands/metrics.ts +129 -0
- package/src/commands/monitor.test.ts +191 -0
- package/src/commands/monitor.ts +409 -0
- package/src/commands/nudge.test.ts +579 -0
- package/src/commands/nudge.ts +646 -0
- package/src/commands/orchestrator.ts +42 -0
- package/src/commands/prime.test.ts +612 -0
- package/src/commands/prime.ts +359 -0
- package/src/commands/replay.test.ts +757 -0
- package/src/commands/replay.ts +231 -0
- package/src/commands/run.test.ts +469 -0
- package/src/commands/run.ts +353 -0
- package/src/commands/serve/agent-actions.test.ts +210 -0
- package/src/commands/serve/agent-actions.ts +192 -0
- package/src/commands/serve/build.test.ts +202 -0
- package/src/commands/serve/build.ts +206 -0
- package/src/commands/serve/coordinator-actions.test.ts +339 -0
- package/src/commands/serve/coordinator-actions.ts +410 -0
- package/src/commands/serve/dev.test.ts +168 -0
- package/src/commands/serve/dev.ts +117 -0
- package/src/commands/serve/mail-actions.test.ts +312 -0
- package/src/commands/serve/mail-actions.ts +167 -0
- package/src/commands/serve/rest.test.ts +1680 -0
- package/src/commands/serve/rest.ts +1130 -0
- package/src/commands/serve/static.ts +51 -0
- package/src/commands/serve/ws.test.ts +361 -0
- package/src/commands/serve/ws.ts +332 -0
- package/src/commands/serve.test.ts +459 -0
- package/src/commands/serve.ts +654 -0
- package/src/commands/sling.test.ts +1583 -0
- package/src/commands/sling.ts +1351 -0
- package/src/commands/spec.test.ts +179 -0
- package/src/commands/spec.ts +105 -0
- package/src/commands/status.test.ts +614 -0
- package/src/commands/status.ts +403 -0
- package/src/commands/stop.test.ts +964 -0
- package/src/commands/stop.ts +319 -0
- package/src/commands/supervisor.test.ts +185 -0
- package/src/commands/supervisor.ts +537 -0
- package/src/commands/trace.test.ts +762 -0
- package/src/commands/trace.ts +205 -0
- package/src/commands/update.test.ts +466 -0
- package/src/commands/update.ts +263 -0
- package/src/commands/upgrade.test.ts +48 -0
- package/src/commands/upgrade.ts +240 -0
- package/src/commands/watch.test.ts +257 -0
- package/src/commands/watch.ts +308 -0
- package/src/commands/worktree.test.ts +1297 -0
- package/src/commands/worktree.ts +451 -0
- package/src/config.test.ts +1535 -0
- package/src/config.ts +1064 -0
- package/src/doctor/agents.test.ts +523 -0
- package/src/doctor/agents.ts +399 -0
- package/src/doctor/config-check.test.ts +191 -0
- package/src/doctor/config-check.ts +183 -0
- package/src/doctor/consistency.test.ts +807 -0
- package/src/doctor/consistency.ts +347 -0
- package/src/doctor/databases.test.ts +350 -0
- package/src/doctor/databases.ts +243 -0
- package/src/doctor/dependencies.test.ts +296 -0
- package/src/doctor/dependencies.ts +272 -0
- package/src/doctor/ecosystem.test.ts +308 -0
- package/src/doctor/ecosystem.ts +156 -0
- package/src/doctor/logs.test.ts +253 -0
- package/src/doctor/logs.ts +295 -0
- package/src/doctor/merge-queue.test.ts +315 -0
- package/src/doctor/merge-queue.ts +167 -0
- package/src/doctor/providers.test.ts +409 -0
- package/src/doctor/providers.ts +250 -0
- package/src/doctor/serve.test.ts +95 -0
- package/src/doctor/serve.ts +86 -0
- package/src/doctor/structure.test.ts +423 -0
- package/src/doctor/structure.ts +285 -0
- package/src/doctor/types.ts +43 -0
- package/src/doctor/version.test.ts +241 -0
- package/src/doctor/version.ts +132 -0
- package/src/doctor/watchdog.test.ts +167 -0
- package/src/doctor/watchdog.ts +214 -0
- package/src/e2e/init-sling-lifecycle.test.ts +283 -0
- package/src/errors.test.ts +350 -0
- package/src/errors.ts +217 -0
- package/src/events/store.test.ts +660 -0
- package/src/events/store.ts +369 -0
- package/src/events/tailer.test.ts +719 -0
- package/src/events/tailer.ts +332 -0
- package/src/events/tool-filter.test.ts +330 -0
- package/src/events/tool-filter.ts +126 -0
- package/src/index.ts +533 -0
- package/src/insights/analyzer.test.ts +466 -0
- package/src/insights/analyzer.ts +203 -0
- package/src/insights/quality-gates.test.ts +141 -0
- package/src/insights/quality-gates.ts +156 -0
- package/src/json.test.ts +72 -0
- package/src/json.ts +53 -0
- package/src/loam/client.test.ts +752 -0
- package/src/loam/client.ts +664 -0
- package/src/logging/color.test.ts +252 -0
- package/src/logging/color.ts +105 -0
- package/src/logging/format.test.ts +110 -0
- package/src/logging/format.ts +255 -0
- package/src/logging/logger.test.ts +814 -0
- package/src/logging/logger.ts +266 -0
- package/src/logging/reporter.test.ts +259 -0
- package/src/logging/reporter.ts +110 -0
- package/src/logging/sanitizer.test.ts +190 -0
- package/src/logging/sanitizer.ts +57 -0
- package/src/logging/theme.ts +140 -0
- package/src/mail/broadcast.test.ts +204 -0
- package/src/mail/broadcast.ts +92 -0
- package/src/mail/client.test.ts +774 -0
- package/src/mail/client.ts +236 -0
- package/src/mail/store.test.ts +898 -0
- package/src/mail/store.ts +425 -0
- package/src/merge/lock.test.ts +149 -0
- package/src/merge/lock.ts +140 -0
- package/src/merge/predict.test.ts +387 -0
- package/src/merge/predict.ts +249 -0
- package/src/merge/queue.test.ts +426 -0
- package/src/merge/queue.ts +246 -0
- package/src/merge/resolver.test.ts +1993 -0
- package/src/merge/resolver.ts +926 -0
- package/src/metrics/pricing.test.ts +258 -0
- package/src/metrics/pricing.ts +135 -0
- package/src/metrics/store.test.ts +978 -0
- package/src/metrics/store.ts +501 -0
- package/src/metrics/summary.test.ts +398 -0
- package/src/metrics/summary.ts +178 -0
- package/src/metrics/transcript.test.ts +483 -0
- package/src/metrics/transcript.ts +114 -0
- package/src/runtimes/__fixtures__/claude-stream-fixture.ts +22 -0
- package/src/runtimes/aider.test.ts +124 -0
- package/src/runtimes/aider.ts +147 -0
- package/src/runtimes/amp.test.ts +164 -0
- package/src/runtimes/amp.ts +154 -0
- package/src/runtimes/claude.test.ts +1474 -0
- package/src/runtimes/claude.ts +579 -0
- package/src/runtimes/codex.test.ts +805 -0
- package/src/runtimes/codex.ts +273 -0
- package/src/runtimes/connections.test.ts +214 -0
- package/src/runtimes/connections.ts +103 -0
- package/src/runtimes/copilot.test.ts +707 -0
- package/src/runtimes/copilot.ts +316 -0
- package/src/runtimes/cursor.test.ts +497 -0
- package/src/runtimes/cursor.ts +205 -0
- package/src/runtimes/gemini.test.ts +537 -0
- package/src/runtimes/gemini.ts +243 -0
- package/src/runtimes/goose.test.ts +133 -0
- package/src/runtimes/goose.ts +157 -0
- package/src/runtimes/headless-connection.test.ts +264 -0
- package/src/runtimes/headless-connection.ts +158 -0
- package/src/runtimes/opencode.test.ts +325 -0
- package/src/runtimes/opencode.ts +188 -0
- package/src/runtimes/pi-guards.test.ts +486 -0
- package/src/runtimes/pi-guards.ts +367 -0
- package/src/runtimes/pi.test.ts +789 -0
- package/src/runtimes/pi.ts +305 -0
- package/src/runtimes/registry.test.ts +196 -0
- package/src/runtimes/registry.ts +99 -0
- package/src/runtimes/sapling.test.ts +1267 -0
- package/src/runtimes/sapling.ts +710 -0
- package/src/runtimes/types.ts +266 -0
- package/src/schema-consistency.test.ts +246 -0
- package/src/sessions/compat.test.ts +281 -0
- package/src/sessions/compat.ts +105 -0
- package/src/sessions/store.test.ts +1748 -0
- package/src/sessions/store.ts +858 -0
- package/src/test-helpers.test.ts +124 -0
- package/src/test-helpers.ts +145 -0
- package/src/test-setup.test.ts +31 -0
- package/src/test-setup.ts +28 -0
- package/src/tools/loam/api.ts +368 -0
- package/src/tools/loam/cli.ts +278 -0
- package/src/tools/loam/commands/add.ts +52 -0
- package/src/tools/loam/commands/archive.ts +214 -0
- package/src/tools/loam/commands/audit.ts +276 -0
- package/src/tools/loam/commands/compact.ts +1062 -0
- package/src/tools/loam/commands/completions.ts +79 -0
- package/src/tools/loam/commands/config.ts +381 -0
- package/src/tools/loam/commands/delete-domain.ts +121 -0
- package/src/tools/loam/commands/delete.ts +316 -0
- package/src/tools/loam/commands/diff.ts +200 -0
- package/src/tools/loam/commands/doctor.ts +1113 -0
- package/src/tools/loam/commands/edit.ts +226 -0
- package/src/tools/loam/commands/init.ts +31 -0
- package/src/tools/loam/commands/learn.ts +179 -0
- package/src/tools/loam/commands/move.ts +323 -0
- package/src/tools/loam/commands/onboard.ts +374 -0
- package/src/tools/loam/commands/outcome.ts +185 -0
- package/src/tools/loam/commands/prime.ts +688 -0
- package/src/tools/loam/commands/prune.ts +614 -0
- package/src/tools/loam/commands/query.ts +218 -0
- package/src/tools/loam/commands/rank.ts +180 -0
- package/src/tools/loam/commands/ready.ts +189 -0
- package/src/tools/loam/commands/record.ts +1210 -0
- package/src/tools/loam/commands/restore.ts +166 -0
- package/src/tools/loam/commands/search.ts +327 -0
- package/src/tools/loam/commands/setup.ts +887 -0
- package/src/tools/loam/commands/status.ts +103 -0
- package/src/tools/loam/commands/sync.ts +298 -0
- package/src/tools/loam/commands/update.ts +19 -0
- package/src/tools/loam/commands/upgrade.ts +93 -0
- package/src/tools/loam/commands/validate.ts +190 -0
- package/src/tools/loam/index.ts +62 -0
- package/src/tools/loam/log.ts +127 -0
- package/src/tools/loam/registry/builtins.ts +409 -0
- package/src/tools/loam/registry/custom.ts +431 -0
- package/src/tools/loam/registry/init.ts +55 -0
- package/src/tools/loam/registry/template.ts +40 -0
- package/src/tools/loam/registry/type-registry.ts +113 -0
- package/src/tools/loam/schemas/config-schema.ts +489 -0
- package/src/tools/loam/schemas/config.ts +245 -0
- package/src/tools/loam/schemas/index.ts +18 -0
- package/src/tools/loam/schemas/record-schema.ts +191 -0
- package/src/tools/loam/schemas/record.ts +115 -0
- package/src/tools/loam/utils/active-work.ts +205 -0
- package/src/tools/loam/utils/anchor-validity.ts +80 -0
- package/src/tools/loam/utils/archive.ts +146 -0
- package/src/tools/loam/utils/audit.ts +667 -0
- package/src/tools/loam/utils/bm25.ts +238 -0
- package/src/tools/loam/utils/budget.ts +142 -0
- package/src/tools/loam/utils/config.ts +344 -0
- package/src/tools/loam/utils/dir-anchors.ts +62 -0
- package/src/tools/loam/utils/domain-rules.ts +114 -0
- package/src/tools/loam/utils/expertise.ts +393 -0
- package/src/tools/loam/utils/format-helpers.ts +96 -0
- package/src/tools/loam/utils/format.ts +1234 -0
- package/src/tools/loam/utils/git-context.ts +50 -0
- package/src/tools/loam/utils/git.ts +183 -0
- package/src/tools/loam/utils/hooks.ts +299 -0
- package/src/tools/loam/utils/index.ts +52 -0
- package/src/tools/loam/utils/json-output.ts +13 -0
- package/src/tools/loam/utils/lock.ts +76 -0
- package/src/tools/loam/utils/markers.ts +48 -0
- package/src/tools/loam/utils/numeric-flags.ts +20 -0
- package/src/tools/loam/utils/palette.ts +44 -0
- package/src/tools/loam/utils/prime-ranking.ts +135 -0
- package/src/tools/loam/utils/recipe-discovery.ts +195 -0
- package/src/tools/loam/utils/runtime-flags.ts +28 -0
- package/src/tools/loam/utils/scoring.ts +94 -0
- package/src/tools/loam/utils/version.ts +116 -0
- package/src/tools/sprout/commands/block.ts +64 -0
- package/src/tools/sprout/commands/blocked.ts +86 -0
- package/src/tools/sprout/commands/close.ts +129 -0
- package/src/tools/sprout/commands/completions.ts +198 -0
- package/src/tools/sprout/commands/config.ts +238 -0
- package/src/tools/sprout/commands/create.ts +164 -0
- package/src/tools/sprout/commands/dep.ts +148 -0
- package/src/tools/sprout/commands/doctor.ts +979 -0
- package/src/tools/sprout/commands/init.ts +83 -0
- package/src/tools/sprout/commands/label.ts +178 -0
- package/src/tools/sprout/commands/list.ts +210 -0
- package/src/tools/sprout/commands/migrate.ts +133 -0
- package/src/tools/sprout/commands/onboard.ts +207 -0
- package/src/tools/sprout/commands/plan-show.ts +278 -0
- package/src/tools/sprout/commands/plan.ts +2526 -0
- package/src/tools/sprout/commands/prime.ts +399 -0
- package/src/tools/sprout/commands/ready.ts +245 -0
- package/src/tools/sprout/commands/search.ts +221 -0
- package/src/tools/sprout/commands/show.ts +277 -0
- package/src/tools/sprout/commands/stats.ts +146 -0
- package/src/tools/sprout/commands/sync.ts +134 -0
- package/src/tools/sprout/commands/tpl.ts +364 -0
- package/src/tools/sprout/commands/unblock.ts +115 -0
- package/src/tools/sprout/commands/update.ts +257 -0
- package/src/tools/sprout/commands/upgrade.ts +91 -0
- package/src/tools/sprout/config-schema.ts +152 -0
- package/src/tools/sprout/config.ts +355 -0
- package/src/tools/sprout/filter.ts +107 -0
- package/src/tools/sprout/format.ts +43 -0
- package/src/tools/sprout/id.ts +22 -0
- package/src/tools/sprout/index.ts +204 -0
- package/src/tools/sprout/log.ts +76 -0
- package/src/tools/sprout/markers.ts +22 -0
- package/src/tools/sprout/output.ts +121 -0
- package/src/tools/sprout/plan-backref.ts +93 -0
- package/src/tools/sprout/plan-context.ts +81 -0
- package/src/tools/sprout/plan-domain.ts +139 -0
- package/src/tools/sprout/plan-lifecycle.ts +65 -0
- package/src/tools/sprout/plan-loam.ts +207 -0
- package/src/tools/sprout/plan-schema.ts +209 -0
- package/src/tools/sprout/sort.ts +31 -0
- package/src/tools/sprout/store.ts +172 -0
- package/src/tools/sprout/types.ts +118 -0
- package/src/tools/sprout/validation.ts +119 -0
- package/src/tools/sprout/version.ts +1 -0
- package/src/tools/sprout/yaml.ts +387 -0
- package/src/tools/trellis/commands/archive.ts +87 -0
- package/src/tools/trellis/commands/completions.ts +610 -0
- package/src/tools/trellis/commands/config.ts +382 -0
- package/src/tools/trellis/commands/create.ts +252 -0
- package/src/tools/trellis/commands/diff.ts +150 -0
- package/src/tools/trellis/commands/doctor.ts +771 -0
- package/src/tools/trellis/commands/emit.ts +365 -0
- package/src/tools/trellis/commands/history.ts +83 -0
- package/src/tools/trellis/commands/import.ts +198 -0
- package/src/tools/trellis/commands/init.ts +81 -0
- package/src/tools/trellis/commands/list.ts +103 -0
- package/src/tools/trellis/commands/onboard.ts +156 -0
- package/src/tools/trellis/commands/pin.ts +172 -0
- package/src/tools/trellis/commands/prime.ts +193 -0
- package/src/tools/trellis/commands/render.ts +122 -0
- package/src/tools/trellis/commands/schema.ts +353 -0
- package/src/tools/trellis/commands/show.ts +115 -0
- package/src/tools/trellis/commands/stats.ts +65 -0
- package/src/tools/trellis/commands/sync.ts +112 -0
- package/src/tools/trellis/commands/tree.ts +123 -0
- package/src/tools/trellis/commands/update.ts +330 -0
- package/src/tools/trellis/commands/upgrade.ts +95 -0
- package/src/tools/trellis/commands/validate.ts +166 -0
- package/src/tools/trellis/config-schema.ts +81 -0
- package/src/tools/trellis/config.ts +108 -0
- package/src/tools/trellis/frontmatter.ts +348 -0
- package/src/tools/trellis/id.ts +24 -0
- package/src/tools/trellis/index.ts +209 -0
- package/src/tools/trellis/markers.ts +28 -0
- package/src/tools/trellis/output.ts +84 -0
- package/src/tools/trellis/render.ts +212 -0
- package/src/tools/trellis/store.ts +144 -0
- package/src/tools/trellis/types.ts +82 -0
- package/src/tools/trellis/validate.ts +199 -0
- package/src/tools/trellis/yaml.ts +309 -0
- package/src/tracker/beads.test.ts +454 -0
- package/src/tracker/beads.ts +56 -0
- package/src/tracker/factory.test.ts +90 -0
- package/src/tracker/factory.ts +65 -0
- package/src/tracker/sprout.test.ts +461 -0
- package/src/tracker/sprout.ts +182 -0
- package/src/tracker/types.ts +52 -0
- package/src/trellis/client.test.ts +107 -0
- package/src/trellis/client.ts +179 -0
- package/src/types.ts +970 -0
- package/src/utils/bin.test.ts +10 -0
- package/src/utils/bin.ts +37 -0
- package/src/utils/browser.test.ts +49 -0
- package/src/utils/browser.ts +48 -0
- package/src/utils/fs.test.ts +119 -0
- package/src/utils/fs.ts +62 -0
- package/src/utils/pid.test.ts +152 -0
- package/src/utils/pid.ts +130 -0
- package/src/utils/process-scan.test.ts +53 -0
- package/src/utils/process-scan.ts +76 -0
- package/src/utils/time.test.ts +43 -0
- package/src/utils/time.ts +37 -0
- package/src/utils/version.test.ts +33 -0
- package/src/utils/version.ts +70 -0
- package/src/version.ts +5 -0
- package/src/watchdog/daemon.test.ts +3721 -0
- package/src/watchdog/daemon.ts +1257 -0
- package/src/watchdog/health.test.ts +830 -0
- package/src/watchdog/health.ts +434 -0
- package/src/watchdog/triage.test.ts +205 -0
- package/src/watchdog/triage.ts +205 -0
- package/src/worktree/manager.test.ts +720 -0
- package/src/worktree/manager.ts +405 -0
- package/src/worktree/process.test.ts +172 -0
- package/src/worktree/process.ts +131 -0
- package/src/worktree/tmux.test.ts +1616 -0
- package/src/worktree/tmux.ts +721 -0
- package/templates/CLAUDE.md.tmpl +100 -0
- package/templates/copilot-hooks.json.tmpl +13 -0
- package/templates/hooks.json.tmpl +109 -0
- package/templates/overlay.md.tmpl +88 -0
- package/ui/dist/apple-touch-icon-bdy6teep.png +0 -0
- package/ui/dist/chunk-8s31f05k.css +1 -0
- package/ui/dist/chunk-vm5rz679.js +300 -0
- package/ui/dist/favicon-nzb39vza.svg +4 -0
- package/ui/dist/index.html +17 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import { printError } from "../utils/palette.ts";
|
|
3
|
+
|
|
4
|
+
const SUPPORTED_SHELLS = ["bash", "zsh", "fish"] as const;
|
|
5
|
+
type Shell = (typeof SUPPORTED_SHELLS)[number];
|
|
6
|
+
|
|
7
|
+
function getVisibleCommands(program: Command): string[] {
|
|
8
|
+
const helper = program.createHelp();
|
|
9
|
+
return helper.visibleCommands(program).map((cmd) => cmd.name());
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function getGlobalOptions(program: Command): string[] {
|
|
13
|
+
return program.options.map((opt) => opt.long).filter(Boolean) as string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function generateBash(commands: string[], globalOptions: string[]): string {
|
|
17
|
+
const allWords = [...commands, ...globalOptions].join(" ");
|
|
18
|
+
return `# loam bash completions
|
|
19
|
+
# Add to ~/.bashrc: eval "$(loam completions bash)"
|
|
20
|
+
_loam() {
|
|
21
|
+
local cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
22
|
+
COMPREPLY=( $(compgen -W "${allWords}" -- "$cur") )
|
|
23
|
+
}
|
|
24
|
+
complete -F _loam loam
|
|
25
|
+
complete -F _loam lm
|
|
26
|
+
`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function generateZsh(commands: string[], globalOptions: string[]): string {
|
|
30
|
+
const allWords = [...commands, ...globalOptions].join(" ");
|
|
31
|
+
return `# loam zsh completions
|
|
32
|
+
# Add to ~/.zshrc: eval "$(loam completions zsh)"
|
|
33
|
+
_loam() {
|
|
34
|
+
local -a words
|
|
35
|
+
words=(${allWords})
|
|
36
|
+
_describe 'loam commands' words
|
|
37
|
+
}
|
|
38
|
+
compdef _loam loam
|
|
39
|
+
compdef _loam lm
|
|
40
|
+
`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function generateFish(commands: string[], _globalOptions: string[]): string {
|
|
44
|
+
const lines = ["# loam fish completions", "# Save to ~/.config/fish/completions/loam.fish", ""];
|
|
45
|
+
for (const cmd of commands) {
|
|
46
|
+
lines.push(`complete -c loam -n '__fish_use_subcommand' -a '${cmd}' -d '${cmd} command'`);
|
|
47
|
+
lines.push(`complete -c lm -n '__fish_use_subcommand' -a '${cmd}' -d '${cmd} command'`);
|
|
48
|
+
}
|
|
49
|
+
return `${lines.join("\n")}\n`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function registerCompletionsCommand(program: Command): void {
|
|
53
|
+
program
|
|
54
|
+
.command("completions <shell>")
|
|
55
|
+
.description("Output shell completion script (bash, zsh, fish)")
|
|
56
|
+
.action((shell: string) => {
|
|
57
|
+
const s = shell.toLowerCase();
|
|
58
|
+
if (!SUPPORTED_SHELLS.includes(s as Shell)) {
|
|
59
|
+
printError(`Unsupported shell: "${shell}". Supported: ${SUPPORTED_SHELLS.join(", ")}`);
|
|
60
|
+
process.exitCode = 1;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const commands = getVisibleCommands(program);
|
|
65
|
+
const globalOptions = getGlobalOptions(program);
|
|
66
|
+
|
|
67
|
+
switch (s as Shell) {
|
|
68
|
+
case "bash":
|
|
69
|
+
process.stdout.write(generateBash(commands, globalOptions));
|
|
70
|
+
break;
|
|
71
|
+
case "zsh":
|
|
72
|
+
process.stdout.write(generateZsh(commands, globalOptions));
|
|
73
|
+
break;
|
|
74
|
+
case "fish":
|
|
75
|
+
process.stdout.write(generateFish(commands, globalOptions));
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { rename, unlink, writeFile } from "node:fs/promises";
|
|
4
|
+
import Ajv, { type ErrorObject } from "ajv";
|
|
5
|
+
import type { Command } from "commander";
|
|
6
|
+
import yaml from "js-yaml";
|
|
7
|
+
import { configSchema } from "../schemas/config-schema.ts";
|
|
8
|
+
import { getConfigPath, readConfig } from "../utils/config.ts";
|
|
9
|
+
import { withFileLock } from "../utils/lock.ts";
|
|
10
|
+
|
|
11
|
+
export function registerConfigCommand(program: Command): void {
|
|
12
|
+
const config = program.command("config").description("Read and write .loam/loam.config.yaml");
|
|
13
|
+
|
|
14
|
+
config
|
|
15
|
+
.command("schema")
|
|
16
|
+
.description("Emit LoamConfig JSON Schema for warren and other config-UI consumers")
|
|
17
|
+
.action(() => {
|
|
18
|
+
process.stdout.write(`${JSON.stringify(configSchema, null, 2)}\n`);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
config
|
|
22
|
+
.command("show")
|
|
23
|
+
.description(
|
|
24
|
+
"Emit the effective LoamConfig as JSON. Pass --path to read a single knob; falls back to the schema default when the knob is unset.",
|
|
25
|
+
)
|
|
26
|
+
.option(
|
|
27
|
+
"--path <path>",
|
|
28
|
+
"Dot-notation path to a single knob (e.g. governance.max_entries, search.boost_factor)",
|
|
29
|
+
)
|
|
30
|
+
.action(async (opts: { path?: string }) => {
|
|
31
|
+
let cfg: unknown;
|
|
32
|
+
try {
|
|
33
|
+
cfg = await readConfig();
|
|
34
|
+
} catch (err) {
|
|
35
|
+
process.stderr.write(`${(err as Error).message}\n`);
|
|
36
|
+
process.exitCode = 1;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (opts.path === undefined) {
|
|
40
|
+
process.stdout.write(`${JSON.stringify(cfg, null, 2)}\n`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const segments = opts.path.split(".").filter((s) => s.length > 0);
|
|
44
|
+
if (segments.length === 0) {
|
|
45
|
+
process.stderr.write("--path must not be empty.\n");
|
|
46
|
+
process.exitCode = 1;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const fromConfig = walkConfig(cfg, segments);
|
|
50
|
+
const value = fromConfig !== undefined ? fromConfig : walkSchemaDefault(segments);
|
|
51
|
+
if (value === undefined) {
|
|
52
|
+
process.stderr.write(
|
|
53
|
+
`Path '${opts.path}' not found in config and has no schema default.\n`,
|
|
54
|
+
);
|
|
55
|
+
process.exitCode = 1;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
config
|
|
62
|
+
.command("set")
|
|
63
|
+
.description(
|
|
64
|
+
"Set a config knob via dot-notation path. <value> is YAML-parsed (so booleans, numbers, lists, and objects all work uniformly). The full resulting config is validated against the schema before atomic write under a file lock; invalid values are rejected with a schema-referenced error. Last-writer-wins semantics under concurrent writes — re-read via `lm config show` after every write.",
|
|
65
|
+
)
|
|
66
|
+
.argument(
|
|
67
|
+
"<path>",
|
|
68
|
+
"Dot-notation path (e.g. governance.max_entries, search.boost_factor, domains.warren.allowed_types)",
|
|
69
|
+
)
|
|
70
|
+
.argument("<value>", "YAML-parsed value to set at <path>")
|
|
71
|
+
.action(async (path: string, value: string) => {
|
|
72
|
+
try {
|
|
73
|
+
await runConfigSet(path, value);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
process.stderr.write(`${(err as Error).message}\n`);
|
|
76
|
+
process.exitCode = 1;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
config
|
|
81
|
+
.command("unset")
|
|
82
|
+
.description(
|
|
83
|
+
"Remove a config knob via dot-notation path so subsequent reads fall back to the schema default. Empty parent objects along the unset path are pruned when the schema allows the parent to be omitted; required-field removals are rejected with a schema-referenced error (use `lm config set` to override). Idempotent — unsetting a never-set path is a silent no-op. Atomic write under a file lock.",
|
|
84
|
+
)
|
|
85
|
+
.argument(
|
|
86
|
+
"<path>",
|
|
87
|
+
"Dot-notation path (e.g. search.boost_factor, domains.warren, hooks.pre-record)",
|
|
88
|
+
)
|
|
89
|
+
.action(async (path: string) => {
|
|
90
|
+
try {
|
|
91
|
+
await runConfigUnset(path);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
process.stderr.write(`${(err as Error).message}\n`);
|
|
94
|
+
process.exitCode = 1;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function runConfigSet(rawPath: string, rawValue: string): Promise<void> {
|
|
100
|
+
const segments = rawPath.split(".").filter((s) => s.length > 0);
|
|
101
|
+
if (segments.length === 0) {
|
|
102
|
+
throw new Error("<path> must not be empty.");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const configPath = getConfigPath();
|
|
106
|
+
if (!existsSync(configPath)) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
"No .loam/ directory found. Run `loam init` to set up this project before `lm config set`.",
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Reject paths that descend through a closed-shape boundary into an unknown
|
|
113
|
+
// key. AJV catches type/range mismatches at validate time; this gives a
|
|
114
|
+
// targeted error before write so `lm config set governance.typo 5` doesn't
|
|
115
|
+
// silently turn into "valid value at an unknown leaf".
|
|
116
|
+
validatePathInSchema(segments);
|
|
117
|
+
|
|
118
|
+
let parsedValue: unknown;
|
|
119
|
+
try {
|
|
120
|
+
parsedValue = yaml.load(rawValue);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
throw new Error(`Invalid YAML for <value>: ${(err as Error).message}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
await withFileLock(configPath, async () => {
|
|
126
|
+
const cfg = (await readConfig()) as unknown as Record<string, unknown>;
|
|
127
|
+
setAtPath(cfg, segments, parsedValue);
|
|
128
|
+
|
|
129
|
+
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
130
|
+
const validate = ajv.compile(configSchema);
|
|
131
|
+
if (!validate(cfg)) {
|
|
132
|
+
const errs = validate.errors ?? [];
|
|
133
|
+
const lines = errs.map(formatAjvError);
|
|
134
|
+
throw new Error(`Invalid config after set:\n${lines.join("\n")}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const dumped = yaml.dump(cfg, { lineWidth: -1 });
|
|
138
|
+
await writeFileAtomic(configPath, dumped);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function runConfigUnset(rawPath: string): Promise<void> {
|
|
143
|
+
const segments = rawPath.split(".").filter((s) => s.length > 0);
|
|
144
|
+
if (segments.length === 0) {
|
|
145
|
+
throw new Error("<path> must not be empty.");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const configPath = getConfigPath();
|
|
149
|
+
if (!existsSync(configPath)) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
"No .loam/ directory found. Run `loam init` to set up this project before `lm config unset`.",
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Same closed-shape gate as `lm config set`: catches typos like
|
|
156
|
+
// `governance.typo` before we touch the file.
|
|
157
|
+
validatePathInSchema(segments);
|
|
158
|
+
|
|
159
|
+
await withFileLock(configPath, async () => {
|
|
160
|
+
const cfg = (await readConfig()) as unknown as Record<string, unknown>;
|
|
161
|
+
const changed = unsetAtPath(cfg, segments);
|
|
162
|
+
if (!changed) {
|
|
163
|
+
// Idempotent: the knob wasn't set, nothing to write.
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
168
|
+
const validate = ajv.compile(configSchema);
|
|
169
|
+
if (!validate(cfg)) {
|
|
170
|
+
const errs = validate.errors ?? [];
|
|
171
|
+
const lines = errs.map(formatAjvError);
|
|
172
|
+
throw new Error(`Invalid config after unset:\n${lines.join("\n")}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const dumped = yaml.dump(cfg, { lineWidth: -1 });
|
|
176
|
+
await writeFileAtomic(configPath, dumped);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function validatePathInSchema(segments: string[]): void {
|
|
181
|
+
let cur: unknown = configSchema;
|
|
182
|
+
for (let i = 0; i < segments.length; i++) {
|
|
183
|
+
const seg = segments[i] as string;
|
|
184
|
+
if (!cur || typeof cur !== "object") {
|
|
185
|
+
throw new Error(
|
|
186
|
+
`Path '${segments.slice(0, i + 1).join(".")}' descends past a leaf in the schema.`,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
const node = cur as Record<string, unknown>;
|
|
190
|
+
const props = node.properties as Record<string, unknown> | undefined;
|
|
191
|
+
if (props && Object.hasOwn(props, seg)) {
|
|
192
|
+
cur = props[seg];
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const additional = node.additionalProperties;
|
|
196
|
+
if (additional && typeof additional === "object") {
|
|
197
|
+
cur = additional;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const known = props ? Object.keys(props).join(", ") : "(none)";
|
|
201
|
+
const parent = segments.slice(0, i).join(".") || "<root>";
|
|
202
|
+
throw new Error(
|
|
203
|
+
`Unknown config path: '${segments.slice(0, i + 1).join(".")}' is not a known knob. Known keys at '${parent}': ${known}.`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Returns true if the leaf was actually present and removed. Prunes empty
|
|
209
|
+
// ancestor objects walking up the unset path, but stops at any ancestor whose
|
|
210
|
+
// key is in its parent schema node's `required` list — those must remain in
|
|
211
|
+
// the on-disk shape (validation catches the case where the leaf itself was
|
|
212
|
+
// required).
|
|
213
|
+
function unsetAtPath(cfg: Record<string, unknown>, segments: string[]): boolean {
|
|
214
|
+
const ancestors: Record<string, unknown>[] = [cfg];
|
|
215
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
216
|
+
const seg = segments[i] as string;
|
|
217
|
+
const parent = ancestors[ancestors.length - 1] as Record<string, unknown>;
|
|
218
|
+
const next = parent[seg];
|
|
219
|
+
if (next === null || typeof next !== "object" || Array.isArray(next)) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
ancestors.push(next as Record<string, unknown>);
|
|
223
|
+
}
|
|
224
|
+
const leafKey = segments[segments.length - 1] as string;
|
|
225
|
+
const leafParent = ancestors[ancestors.length - 1] as Record<string, unknown>;
|
|
226
|
+
if (!Object.hasOwn(leafParent, leafKey)) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
delete leafParent[leafKey];
|
|
230
|
+
|
|
231
|
+
for (let i = ancestors.length - 1; i >= 1; i--) {
|
|
232
|
+
const node = ancestors[i] as Record<string, unknown>;
|
|
233
|
+
if (Object.keys(node).length > 0) break;
|
|
234
|
+
const parent = ancestors[i - 1] as Record<string, unknown>;
|
|
235
|
+
const keyInParent = segments[i - 1] as string;
|
|
236
|
+
if (isRequiredInSchema(segments.slice(0, i - 1), keyInParent)) break;
|
|
237
|
+
delete parent[keyInParent];
|
|
238
|
+
}
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function isRequiredInSchema(parentSegments: string[], key: string): boolean {
|
|
243
|
+
let cur: unknown = configSchema;
|
|
244
|
+
for (const seg of parentSegments) {
|
|
245
|
+
if (!cur || typeof cur !== "object") return false;
|
|
246
|
+
const node = cur as Record<string, unknown>;
|
|
247
|
+
const props = node.properties as Record<string, unknown> | undefined;
|
|
248
|
+
if (props && Object.hasOwn(props, seg)) {
|
|
249
|
+
cur = props[seg];
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const additional = node.additionalProperties;
|
|
253
|
+
if (additional && typeof additional === "object") {
|
|
254
|
+
cur = additional;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
if (!cur || typeof cur !== "object") return false;
|
|
260
|
+
const required = (cur as { required?: unknown }).required;
|
|
261
|
+
return Array.isArray(required) && required.includes(key);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function setAtPath(obj: Record<string, unknown>, segments: string[], value: unknown): void {
|
|
265
|
+
let cur = obj;
|
|
266
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
267
|
+
const seg = segments[i] as string;
|
|
268
|
+
const next = cur[seg];
|
|
269
|
+
if (next === null || typeof next !== "object" || Array.isArray(next)) {
|
|
270
|
+
cur[seg] = {};
|
|
271
|
+
}
|
|
272
|
+
cur = cur[seg] as Record<string, unknown>;
|
|
273
|
+
}
|
|
274
|
+
const leaf = segments[segments.length - 1] as string;
|
|
275
|
+
cur[leaf] = value;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function formatAjvError(err: ErrorObject): string {
|
|
279
|
+
const path = err.instancePath || "<root>";
|
|
280
|
+
const meta = lookupSchemaMeta(err.instancePath);
|
|
281
|
+
const tail = meta?.title ? ` (${meta.title})` : "";
|
|
282
|
+
return ` - ${path}: ${err.message ?? "(unknown)"}${tail}`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function lookupSchemaMeta(
|
|
286
|
+
instancePath: string,
|
|
287
|
+
): { title?: string; description?: string } | undefined {
|
|
288
|
+
if (!instancePath) return undefined;
|
|
289
|
+
const segs = instancePath
|
|
290
|
+
.split("/")
|
|
291
|
+
.slice(1)
|
|
292
|
+
.filter((s) => s.length > 0);
|
|
293
|
+
let cur: unknown = configSchema;
|
|
294
|
+
for (const seg of segs) {
|
|
295
|
+
if (!cur || typeof cur !== "object") return undefined;
|
|
296
|
+
const node = cur as Record<string, unknown>;
|
|
297
|
+
const props = node.properties as Record<string, unknown> | undefined;
|
|
298
|
+
if (props && Object.hasOwn(props, seg)) {
|
|
299
|
+
cur = props[seg];
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
const additional = node.additionalProperties;
|
|
303
|
+
if (additional && typeof additional === "object") {
|
|
304
|
+
cur = additional;
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
return undefined;
|
|
308
|
+
}
|
|
309
|
+
if (!cur || typeof cur !== "object") return undefined;
|
|
310
|
+
const node = cur as Record<string, unknown>;
|
|
311
|
+
return {
|
|
312
|
+
title: node.title as string | undefined,
|
|
313
|
+
description: node.description as string | undefined,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function writeFileAtomic(filePath: string, content: string): Promise<void> {
|
|
318
|
+
const tmpPath = `${filePath}.tmp.${randomBytes(8).toString("hex")}`;
|
|
319
|
+
await writeFile(tmpPath, content, "utf-8");
|
|
320
|
+
try {
|
|
321
|
+
await rename(tmpPath, filePath);
|
|
322
|
+
} catch (err) {
|
|
323
|
+
try {
|
|
324
|
+
await unlink(tmpPath);
|
|
325
|
+
} catch {
|
|
326
|
+
// best-effort cleanup
|
|
327
|
+
}
|
|
328
|
+
throw err;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function walkConfig(value: unknown, segments: string[]): unknown {
|
|
333
|
+
let cur: unknown = value;
|
|
334
|
+
for (const seg of segments) {
|
|
335
|
+
if (cur === null || typeof cur !== "object") return undefined;
|
|
336
|
+
cur = (cur as Record<string, unknown>)[seg];
|
|
337
|
+
if (cur === undefined) return undefined;
|
|
338
|
+
}
|
|
339
|
+
return cur;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Walk the JSON Schema down `segments` and synthesize a default for the
|
|
343
|
+
// resulting node. Leaves return their declared `default`; non-leaf nodes
|
|
344
|
+
// recurse into `properties` and collect child defaults so `--path search`
|
|
345
|
+
// (after `lm config unset search`) yields `{ boost_factor: 0.1 }` rather than
|
|
346
|
+
// "not found". Open maps (additionalProperties as an object) are followed when
|
|
347
|
+
// no matching `properties` entry exists, supporting paths like
|
|
348
|
+
// `domains.<name>.allowed_types`.
|
|
349
|
+
function walkSchemaDefault(segments: string[]): unknown {
|
|
350
|
+
let cur: unknown = configSchema;
|
|
351
|
+
for (const seg of segments) {
|
|
352
|
+
if (!cur || typeof cur !== "object") return undefined;
|
|
353
|
+
const node = cur as Record<string, unknown>;
|
|
354
|
+
const props = node.properties as Record<string, unknown> | undefined;
|
|
355
|
+
if (props && Object.hasOwn(props, seg)) {
|
|
356
|
+
cur = props[seg];
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
const additional = node.additionalProperties;
|
|
360
|
+
if (additional && typeof additional === "object") {
|
|
361
|
+
cur = additional;
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
return undefined;
|
|
365
|
+
}
|
|
366
|
+
return collectSchemaDefaults(cur);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function collectSchemaDefaults(node: unknown): unknown {
|
|
370
|
+
if (!node || typeof node !== "object") return undefined;
|
|
371
|
+
const n = node as Record<string, unknown>;
|
|
372
|
+
if ("default" in n) return n.default;
|
|
373
|
+
const props = n.properties as Record<string, unknown> | undefined;
|
|
374
|
+
if (!props) return undefined;
|
|
375
|
+
const result: Record<string, unknown> = {};
|
|
376
|
+
for (const [k, v] of Object.entries(props)) {
|
|
377
|
+
const sub = collectSchemaDefaults(v);
|
|
378
|
+
if (sub !== undefined) result[k] = sub;
|
|
379
|
+
}
|
|
380
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
381
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { createInterface } from "node:readline";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import type { Command } from "commander";
|
|
4
|
+
import { getExpertisePath, readConfig, removeDomain } from "../utils/config.ts";
|
|
5
|
+
import { readExpertiseFile } from "../utils/expertise.ts";
|
|
6
|
+
import { outputJson, outputJsonError } from "../utils/json-output.ts";
|
|
7
|
+
import { withFileLock } from "../utils/lock.ts";
|
|
8
|
+
import { accent, brand, isQuiet } from "../utils/palette.ts";
|
|
9
|
+
|
|
10
|
+
async function confirmAction(prompt: string): Promise<boolean> {
|
|
11
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
rl.question(`${prompt} (y/N): `, (answer) => {
|
|
14
|
+
rl.close();
|
|
15
|
+
resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function registerDeleteDomainCommand(program: Command): void {
|
|
21
|
+
program
|
|
22
|
+
.command("delete-domain")
|
|
23
|
+
.argument("<domain>", "expertise domain to delete")
|
|
24
|
+
.description("Delete an expertise domain and its expertise file")
|
|
25
|
+
.option("--yes", "skip confirmation prompt")
|
|
26
|
+
.option("--dry-run", "preview what would be deleted without making changes", false)
|
|
27
|
+
.action(async (domain: string, options: { yes?: boolean; dryRun: boolean }) => {
|
|
28
|
+
const jsonMode = program.opts().json === true;
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const config = await readConfig();
|
|
32
|
+
|
|
33
|
+
if (!(domain in config.domains)) {
|
|
34
|
+
if (jsonMode) {
|
|
35
|
+
outputJsonError(
|
|
36
|
+
"delete-domain",
|
|
37
|
+
`Domain "${domain}" not found in config. Available domains: ${Object.keys(config.domains).join(", ") || "(none)"}`,
|
|
38
|
+
);
|
|
39
|
+
} else {
|
|
40
|
+
console.error(chalk.red(`Error: domain "${domain}" not found in config.`));
|
|
41
|
+
console.error(
|
|
42
|
+
chalk.red(
|
|
43
|
+
`Hint: Run \`loam add ${domain}\` to create it, or check \`loam status\` for existing domains.`,
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
process.exitCode = 1;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const filePath = getExpertisePath(domain);
|
|
52
|
+
const records = await readExpertiseFile(filePath);
|
|
53
|
+
const recordCount = records.length;
|
|
54
|
+
|
|
55
|
+
if (options.dryRun) {
|
|
56
|
+
if (jsonMode) {
|
|
57
|
+
outputJson({
|
|
58
|
+
success: true,
|
|
59
|
+
command: "delete-domain",
|
|
60
|
+
domain,
|
|
61
|
+
dryRun: true,
|
|
62
|
+
recordCount,
|
|
63
|
+
});
|
|
64
|
+
} else {
|
|
65
|
+
if (!isQuiet()) {
|
|
66
|
+
console.log(
|
|
67
|
+
`${chalk.yellow("[DRY RUN]")} Would delete domain ${accent(domain)} (${recordCount} record${recordCount === 1 ? "" : "s"}) and its expertise file.`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Confirmation (skip in JSON mode or when --yes is passed)
|
|
75
|
+
if (!jsonMode && !options.yes) {
|
|
76
|
+
const confirmed = await confirmAction(
|
|
77
|
+
`This will delete domain "${domain}" (${recordCount} record${recordCount === 1 ? "" : "s"}) and its expertise file. Continue?`,
|
|
78
|
+
);
|
|
79
|
+
if (!confirmed) {
|
|
80
|
+
console.log(chalk.yellow("Cancelled."));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
await withFileLock(filePath, async () => {
|
|
86
|
+
await removeDomain(domain, process.cwd());
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (jsonMode) {
|
|
90
|
+
outputJson({
|
|
91
|
+
success: true,
|
|
92
|
+
command: "delete-domain",
|
|
93
|
+
domain,
|
|
94
|
+
deletedFile: true,
|
|
95
|
+
recordCount,
|
|
96
|
+
});
|
|
97
|
+
} else {
|
|
98
|
+
if (!isQuiet()) {
|
|
99
|
+
console.log(
|
|
100
|
+
`${brand("✓")} ${brand("Removed domain")} ${accent(domain)} and deleted expertise file.`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} catch (err) {
|
|
105
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
106
|
+
if (jsonMode) {
|
|
107
|
+
outputJsonError("delete-domain", "No .loam/ directory found. Run `loam init` first.");
|
|
108
|
+
} else {
|
|
109
|
+
console.error(chalk.red("Error: No .loam/ directory found. Run `loam init` first."));
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
if (jsonMode) {
|
|
113
|
+
outputJsonError("delete-domain", (err as Error).message);
|
|
114
|
+
} else {
|
|
115
|
+
console.error(chalk.red(`Error: ${(err as Error).message}`));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
process.exitCode = 1;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|