akm-cli 0.6.1 → 0.7.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 +66 -0
- package/dist/{cli.js → src/cli.js} +712 -34
- package/dist/{commands → src/commands}/config-cli.js +47 -4
- package/dist/src/commands/distill.js +283 -0
- package/dist/src/commands/events.js +108 -0
- package/dist/src/commands/history.js +191 -0
- package/dist/{commands → src/commands}/installed-stashes.js +1 -1
- package/dist/src/commands/proposal.js +119 -0
- package/dist/src/commands/propose.js +171 -0
- package/dist/src/commands/reflect.js +193 -0
- package/dist/{commands → src/commands}/registry-search.js +71 -7
- package/dist/{commands → src/commands}/remember.js +12 -0
- package/dist/{commands → src/commands}/search.js +104 -4
- package/dist/{commands → src/commands}/self-update.js +4 -3
- package/dist/{commands → src/commands}/show.js +73 -0
- package/dist/{commands → src/commands}/source-add.js +5 -1
- package/dist/{commands → src/commands}/source-manage.js +7 -1
- package/dist/{core → src/core}/asset-ref.js +5 -5
- package/dist/{core → src/core}/asset-spec.js +12 -0
- package/dist/{core → src/core}/common.js +1 -1
- package/dist/{core → src/core}/config.js +203 -121
- package/dist/{core → src/core}/errors.js +4 -0
- package/dist/src/core/events.js +239 -0
- package/dist/src/core/lesson-lint.js +86 -0
- package/dist/src/core/proposals.js +406 -0
- package/dist/src/core/warn.js +72 -0
- package/dist/{core → src/core}/write-source.js +80 -5
- package/dist/{indexer → src/indexer}/db-search.js +114 -24
- package/dist/{indexer → src/indexer}/db.js +76 -23
- package/dist/{indexer → src/indexer}/file-context.js +0 -3
- package/dist/src/indexer/graph-boost.js +179 -0
- package/dist/src/indexer/graph-extraction.js +212 -0
- package/dist/{indexer → src/indexer}/indexer.js +88 -7
- package/dist/{indexer → src/indexer}/matchers.js +1 -1
- package/dist/src/indexer/memory-inference.js +263 -0
- package/dist/{indexer → src/indexer}/metadata.js +111 -3
- package/dist/{indexer → src/indexer}/search-source.js +4 -2
- package/dist/src/integrations/agent/config.js +292 -0
- package/dist/src/integrations/agent/detect.js +94 -0
- package/dist/src/integrations/agent/index.js +17 -0
- package/dist/src/integrations/agent/profiles.js +65 -0
- package/dist/src/integrations/agent/prompts.js +167 -0
- package/dist/src/integrations/agent/spawn.js +272 -0
- package/dist/{integrations → src/integrations}/github.js +9 -3
- package/dist/{integrations → src/integrations}/lockfile.js +0 -26
- package/dist/{llm → src/llm}/client.js +33 -2
- package/dist/{llm → src/llm}/embedders/remote.js +37 -3
- package/dist/src/llm/feature-gate.js +108 -0
- package/dist/src/llm/graph-extract.js +107 -0
- package/dist/src/llm/index-passes.js +35 -0
- package/dist/src/llm/memory-infer.js +86 -0
- package/dist/{output → src/output}/cli-hints.js +15 -2
- package/dist/{output → src/output}/renderers.js +63 -2
- package/dist/src/output/shapes.js +523 -0
- package/dist/src/output/text.js +1116 -0
- package/dist/{registry → src/registry}/build-index.js +19 -8
- package/dist/{registry → src/registry}/factory.js +0 -8
- package/dist/{registry → src/registry}/providers/static-index.js +6 -3
- package/dist/{registry → src/registry}/resolve.js +68 -2
- package/dist/{setup → src/setup}/setup.js +52 -5
- package/dist/{sources → src/sources}/providers/git.js +7 -15
- package/dist/{wiki → src/wiki}/wiki.js +54 -6
- package/dist/{workflows → src/workflows}/runs.js +37 -3
- package/dist/tests/add-website-source.test.js +119 -0
- package/dist/tests/agent/agent-config-loader.test.js +70 -0
- package/dist/tests/agent/agent-config.test.js +221 -0
- package/dist/tests/agent/agent-detect.test.js +100 -0
- package/dist/tests/agent/agent-spawn.test.js +234 -0
- package/dist/tests/agent-output.test.js +186 -0
- package/dist/tests/architecture/agent-no-llm-sdk-guard.test.js +103 -0
- package/dist/tests/architecture/agent-spawn-seam.test.js +193 -0
- package/dist/tests/architecture/llm-stateless-seam.test.js +112 -0
- package/dist/tests/asset-ref.test.js +192 -0
- package/dist/tests/asset-registry.test.js +103 -0
- package/dist/tests/asset-spec.test.js +241 -0
- package/dist/tests/bench/attribution.test.js +996 -0
- package/dist/tests/bench/cleanup-sigint.test.js +83 -0
- package/dist/tests/bench/cleanup.js +234 -0
- package/dist/tests/bench/cleanup.test.js +166 -0
- package/dist/tests/bench/cli.js +1018 -0
- package/dist/tests/bench/cli.test.js +445 -0
- package/dist/tests/bench/compare.test.js +556 -0
- package/dist/tests/bench/corpus.js +317 -0
- package/dist/tests/bench/corpus.test.js +258 -0
- package/dist/tests/bench/doctor.js +525 -0
- package/dist/tests/bench/driver.js +401 -0
- package/dist/tests/bench/driver.test.js +584 -0
- package/dist/tests/bench/environment.js +233 -0
- package/dist/tests/bench/environment.test.js +199 -0
- package/dist/tests/bench/evolve-metrics.js +179 -0
- package/dist/tests/bench/evolve-metrics.test.js +187 -0
- package/dist/tests/bench/evolve.js +647 -0
- package/dist/tests/bench/evolve.test.js +624 -0
- package/dist/tests/bench/failure-modes.test.js +349 -0
- package/dist/tests/bench/feedback-integrity.test.js +457 -0
- package/dist/tests/bench/leakage.test.js +228 -0
- package/dist/tests/bench/learning-curve.test.js +134 -0
- package/dist/tests/bench/metrics.js +2395 -0
- package/dist/tests/bench/metrics.test.js +1150 -0
- package/dist/tests/bench/no-os-tmpdir-invariant.test.js +43 -0
- package/dist/tests/bench/opencode-config.js +194 -0
- package/dist/tests/bench/opencode-config.test.js +370 -0
- package/dist/tests/bench/report.js +1885 -0
- package/dist/tests/bench/report.test.js +1038 -0
- package/dist/tests/bench/run-config.js +355 -0
- package/dist/tests/bench/run-config.test.js +298 -0
- package/dist/tests/bench/run-curate-test.js +32 -0
- package/dist/tests/bench/run-failing-tasks.js +56 -0
- package/dist/tests/bench/run-full-bench.js +51 -0
- package/dist/tests/bench/run-items36-targeted.js +69 -0
- package/dist/tests/bench/run-nano-quick.js +42 -0
- package/dist/tests/bench/run-waveg-targeted.js +62 -0
- package/dist/tests/bench/runner.js +699 -0
- package/dist/tests/bench/runner.test.js +958 -0
- package/dist/tests/bench/search-bridge.test.js +331 -0
- package/dist/tests/bench/tmp.js +131 -0
- package/dist/tests/bench/trajectory.js +116 -0
- package/dist/tests/bench/trajectory.test.js +127 -0
- package/dist/tests/bench/verifier.js +114 -0
- package/dist/tests/bench/verifier.test.js +118 -0
- package/dist/tests/bench/workflow-evaluator.js +557 -0
- package/dist/tests/bench/workflow-evaluator.test.js +421 -0
- package/dist/tests/bench/workflow-spec.js +345 -0
- package/dist/tests/bench/workflow-spec.test.js +363 -0
- package/dist/tests/bench/workflow-trace.js +472 -0
- package/dist/tests/bench/workflow-trace.test.js +254 -0
- package/dist/tests/benchmark-search-quality.js +536 -0
- package/dist/tests/benchmark-suite.js +1441 -0
- package/dist/tests/capture-cli.test.js +112 -0
- package/dist/tests/cli-errors.test.js +204 -0
- package/dist/tests/commands/events.test.js +370 -0
- package/dist/tests/commands/history.test.js +418 -0
- package/dist/tests/commands/import.test.js +103 -0
- package/dist/tests/commands/proposal-cli.test.js +209 -0
- package/dist/tests/commands/reflect-propose-cli.test.js +333 -0
- package/dist/tests/commands/remember.test.js +97 -0
- package/dist/tests/commands/scope-flags.test.js +300 -0
- package/dist/tests/commands/search.test.js +537 -0
- package/dist/tests/commands/show-indexer-parity.test.js +117 -0
- package/dist/tests/commands/show.test.js +294 -0
- package/dist/tests/common.test.js +266 -0
- package/dist/tests/completions.test.js +142 -0
- package/dist/tests/config-cli.test.js +193 -0
- package/dist/tests/config-llm-features.test.js +139 -0
- package/dist/tests/config.test.js +569 -0
- package/dist/tests/contracts/migration-baseline.test.js +43 -0
- package/dist/tests/contracts/reflect-propose-envelope.test.js +139 -0
- package/dist/tests/contracts/spec-helpers.js +46 -0
- package/dist/tests/contracts/v1-spec-section-11-proposal-queue.test.js +228 -0
- package/dist/tests/contracts/v1-spec-section-12-agent-config.test.js +56 -0
- package/dist/tests/contracts/v1-spec-section-13-lesson-type.test.js +34 -0
- package/dist/tests/contracts/v1-spec-section-14-llm-features.test.js +94 -0
- package/dist/tests/contracts/v1-spec-section-4-1-asset-types.test.js +39 -0
- package/dist/tests/contracts/v1-spec-section-4-2-quality-rules.test.js +44 -0
- package/dist/tests/contracts/v1-spec-section-5-configuration.test.js +47 -0
- package/dist/tests/contracts/v1-spec-section-6-orchestration.test.js +40 -0
- package/dist/tests/contracts/v1-spec-section-7-module-layout.test.js +58 -0
- package/dist/tests/contracts/v1-spec-section-8-extension-points.test.js +34 -0
- package/dist/tests/contracts/v1-spec-section-9-4-cli-surface.test.js +75 -0
- package/dist/tests/contracts/v1-spec-section-9-7-llm-agent-boundary.test.js +36 -0
- package/dist/tests/core/write-source.test.js +366 -0
- package/dist/tests/curate-command.test.js +87 -0
- package/dist/tests/db-scoring.test.js +201 -0
- package/dist/tests/db.test.js +654 -0
- package/dist/tests/distill-cli-flag.test.js +208 -0
- package/dist/tests/distill.test.js +515 -0
- package/dist/tests/docker-install.test.js +120 -0
- package/dist/tests/e2e.test.js +1419 -0
- package/dist/tests/embedder.test.js +340 -0
- package/dist/tests/embedding-model-config.test.js +379 -0
- package/dist/tests/feedback-command.test.js +172 -0
- package/dist/tests/file-context.test.js +552 -0
- package/dist/tests/fixtures/scripts/git/summarize-diff.js +9 -0
- package/dist/tests/fixtures/scripts/lint/eslint-check.js +7 -0
- package/dist/tests/fixtures/stashes/load.js +166 -0
- package/dist/tests/fixtures/stashes/load.test.js +97 -0
- package/dist/tests/fixtures/stashes/ranking-baseline/scripts/mem0-search.js +12 -0
- package/dist/tests/frontmatter.test.js +190 -0
- package/dist/tests/fts-field-weighting.test.js +254 -0
- package/dist/tests/fuzzy-search.test.js +230 -0
- package/dist/tests/git-provider-clone.test.js +45 -0
- package/dist/tests/github.test.js +161 -0
- package/dist/tests/graph-boost-ranking.test.js +305 -0
- package/dist/tests/graph-extraction.test.js +282 -0
- package/dist/tests/helpers/usage-events.js +8 -0
- package/dist/tests/index-pass-llm.test.js +161 -0
- package/dist/tests/indexer.test.js +570 -0
- package/dist/tests/info-command.test.js +166 -0
- package/dist/tests/init.test.js +69 -0
- package/dist/tests/install-script.test.js +246 -0
- package/dist/tests/integration/agent-real-profile.test.js +94 -0
- package/dist/tests/issue-36-repro.test.js +304 -0
- package/dist/tests/issues-191-194.test.js +160 -0
- package/dist/tests/lesson-lint.test.js +111 -0
- package/dist/tests/llm-client.test.js +115 -0
- package/dist/tests/llm-feature-gate.test.js +151 -0
- package/dist/tests/llm.test.js +139 -0
- package/dist/tests/lockfile.test.js +216 -0
- package/dist/tests/manifest.test.js +205 -0
- package/dist/tests/markdown.test.js +126 -0
- package/dist/tests/matchers-unit.test.js +189 -0
- package/dist/tests/memory-inference.test.js +299 -0
- package/dist/tests/merge-scoring.test.js +136 -0
- package/dist/tests/metadata.test.js +313 -0
- package/dist/tests/migration-help.test.js +89 -0
- package/dist/tests/origin-resolve.test.js +124 -0
- package/dist/tests/output-baseline.test.js +218 -0
- package/dist/tests/output-shapes-unit.test.js +478 -0
- package/dist/tests/parallel-search.test.js +272 -0
- package/dist/tests/parameter-metadata.test.js +365 -0
- package/dist/tests/paths.test.js +177 -0
- package/dist/tests/progressive-disclosure.test.js +280 -0
- package/dist/tests/proposals.test.js +279 -0
- package/dist/tests/proposed-quality.test.js +271 -0
- package/dist/tests/provider-registry.test.js +32 -0
- package/dist/tests/ranking-regression.test.js +548 -0
- package/dist/tests/reflect-propose.test.js +455 -0
- package/dist/tests/registry-build-index.test.js +394 -0
- package/dist/tests/registry-cli.test.js +290 -0
- package/dist/tests/registry-index-v2.test.js +430 -0
- package/dist/tests/registry-install.test.js +728 -0
- package/dist/tests/registry-providers/parity.test.js +189 -0
- package/dist/tests/registry-providers/skills-sh.test.js +309 -0
- package/dist/tests/registry-providers/static-index.test.js +238 -0
- package/dist/tests/registry-resolve.test.js +126 -0
- package/dist/tests/registry-search.test.js +923 -0
- package/dist/tests/remember-frontmatter.test.js +378 -0
- package/dist/tests/remember-unit.test.js +123 -0
- package/dist/tests/ripgrep-install.test.js +251 -0
- package/dist/tests/ripgrep-resolve.test.js +108 -0
- package/dist/tests/ripgrep.test.js +163 -0
- package/dist/tests/save-command.test.js +94 -0
- package/dist/tests/save-trust-qa-fixes.test.js +270 -0
- package/dist/tests/scoring-pipeline.test.js +648 -0
- package/dist/tests/search-include-proposed-cli.test.js +118 -0
- package/dist/tests/self-update.test.js +442 -0
- package/dist/tests/semantic-search-e2e.test.js +512 -0
- package/dist/tests/semantic-status.test.js +471 -0
- package/dist/tests/setup-run.integration.js +877 -0
- package/dist/tests/setup-wizard.test.js +198 -0
- package/dist/tests/setup.test.js +131 -0
- package/dist/tests/source-add.test.js +11 -0
- package/dist/tests/source-clone.test.js +254 -0
- package/dist/tests/source-manage.test.js +366 -0
- package/dist/tests/source-providers/filesystem.test.js +82 -0
- package/dist/tests/source-providers/git.test.js +252 -0
- package/dist/tests/source-providers/website.test.js +128 -0
- package/dist/tests/source-qa-fixes.test.js +286 -0
- package/dist/tests/source-registry.test.js +350 -0
- package/dist/tests/source-resolve.test.js +100 -0
- package/dist/tests/source-source.test.js +281 -0
- package/dist/tests/source.test.js +533 -0
- package/dist/tests/tar-utils-scan.test.js +73 -0
- package/dist/tests/toggle-components.test.js +73 -0
- package/dist/tests/usage-telemetry.test.js +265 -0
- package/dist/tests/utility-scoring.test.js +558 -0
- package/dist/tests/vault-load-error.test.js +78 -0
- package/dist/tests/vault-qa-fixes.test.js +194 -0
- package/dist/tests/vault.test.js +429 -0
- package/dist/tests/vector-search.test.js +608 -0
- package/dist/tests/walker.test.js +252 -0
- package/dist/tests/wave2-cluster-bc.test.js +228 -0
- package/dist/tests/wave2-cluster-d.test.js +180 -0
- package/dist/tests/wave2-cluster-e.test.js +179 -0
- package/dist/tests/wiki-qa-fixes.test.js +270 -0
- package/dist/tests/wiki.test.js +529 -0
- package/dist/tests/workflow-cli.test.js +271 -0
- package/dist/tests/workflow-markdown.test.js +171 -0
- package/dist/tests/workflow-path-escape.test.js +132 -0
- package/dist/tests/workflow-qa-fixes.test.js +395 -0
- package/dist/tests/workflows/indexer-rejection.test.js +213 -0
- package/docs/README.md +8 -0
- package/docs/migration/release-notes/0.7.0.md +244 -0
- package/package.json +2 -2
- package/dist/core/warn.js +0 -27
- package/dist/output/shapes.js +0 -212
- package/dist/output/text.js +0 -520
- /package/dist/{commands → src/commands}/completions.js +0 -0
- /package/dist/{commands → src/commands}/curate.js +0 -0
- /package/dist/{commands → src/commands}/info.js +0 -0
- /package/dist/{commands → src/commands}/init.js +0 -0
- /package/dist/{commands → src/commands}/install-audit.js +0 -0
- /package/dist/{commands → src/commands}/migration-help.js +0 -0
- /package/dist/{commands → src/commands}/source-clone.js +0 -0
- /package/dist/{commands → src/commands}/vault.js +0 -0
- /package/dist/{core → src/core}/asset-registry.js +0 -0
- /package/dist/{core → src/core}/frontmatter.js +0 -0
- /package/dist/{core → src/core}/markdown.js +0 -0
- /package/dist/{core → src/core}/paths.js +0 -0
- /package/dist/{indexer → src/indexer}/manifest.js +0 -0
- /package/dist/{indexer → src/indexer}/search-fields.js +0 -0
- /package/dist/{indexer → src/indexer}/semantic-status.js +0 -0
- /package/dist/{indexer → src/indexer}/usage-events.js +0 -0
- /package/dist/{indexer → src/indexer}/walker.js +0 -0
- /package/dist/{llm → src/llm}/embedder.js +0 -0
- /package/dist/{llm → src/llm}/embedders/cache.js +0 -0
- /package/dist/{llm → src/llm}/embedders/local.js +0 -0
- /package/dist/{llm → src/llm}/embedders/types.js +0 -0
- /package/dist/{llm → src/llm}/metadata-enhance.js +0 -0
- /package/dist/{output → src/output}/context.js +0 -0
- /package/dist/{registry → src/registry}/create-provider-registry.js +0 -0
- /package/dist/{registry → src/registry}/origin-resolve.js +0 -0
- /package/dist/{registry → src/registry}/providers/index.js +0 -0
- /package/dist/{registry → src/registry}/providers/skills-sh.js +0 -0
- /package/dist/{registry → src/registry}/providers/types.js +0 -0
- /package/dist/{registry → src/registry}/types.js +0 -0
- /package/dist/{setup → src/setup}/detect.js +0 -0
- /package/dist/{setup → src/setup}/ripgrep-install.js +0 -0
- /package/dist/{setup → src/setup}/ripgrep-resolve.js +0 -0
- /package/dist/{setup → src/setup}/steps.js +0 -0
- /package/dist/{sources → src/sources}/include.js +0 -0
- /package/dist/{sources → src/sources}/provider-factory.js +0 -0
- /package/dist/{sources → src/sources}/provider.js +0 -0
- /package/dist/{sources → src/sources}/providers/filesystem.js +0 -0
- /package/dist/{sources → src/sources}/providers/index.js +0 -0
- /package/dist/{sources → src/sources}/providers/install-types.js +0 -0
- /package/dist/{sources → src/sources}/providers/npm.js +0 -0
- /package/dist/{sources → src/sources}/providers/provider-utils.js +0 -0
- /package/dist/{sources → src/sources}/providers/sync-from-ref.js +0 -0
- /package/dist/{sources → src/sources}/providers/tar-utils.js +0 -0
- /package/dist/{sources → src/sources}/providers/website.js +0 -0
- /package/dist/{sources → src/sources}/resolve.js +0 -0
- /package/dist/{sources → src/sources}/types.js +0 -0
- /package/dist/{templates → src/templates}/wiki-templates.js +0 -0
- /package/dist/{version.js → src/version.js} +0 -0
- /package/dist/{workflows → src/workflows}/authoring.js +0 -0
- /package/dist/{workflows → src/workflows}/cli.js +0 -0
- /package/dist/{workflows → src/workflows}/db.js +0 -0
- /package/dist/{workflows → src/workflows}/document-cache.js +0 -0
- /package/dist/{workflows → src/workflows}/parser.js +0 -0
- /package/dist/{workflows → src/workflows}/renderer.js +0 -0
- /package/dist/{workflows → src/workflows}/schema.js +0 -0
- /package/dist/{workflows → src/workflows}/validator.js +0 -0
|
@@ -5,13 +5,19 @@ import { defineCommand, runMain } from "citty";
|
|
|
5
5
|
import { generateBashCompletions, installBashCompletions } from "./commands/completions";
|
|
6
6
|
import { getConfigValue, listConfig, setConfigValue, unsetConfigValue } from "./commands/config-cli";
|
|
7
7
|
import { akmCurate } from "./commands/curate";
|
|
8
|
+
import { akmDistill } from "./commands/distill";
|
|
9
|
+
import { akmEventsList, akmEventsTail } from "./commands/events";
|
|
10
|
+
import { akmHistory } from "./commands/history";
|
|
8
11
|
import { assembleInfo } from "./commands/info";
|
|
9
12
|
import { akmInit } from "./commands/init";
|
|
10
13
|
import { akmListSources, akmRemove, akmUpdate } from "./commands/installed-stashes";
|
|
11
14
|
import { renderMigrationHelp } from "./commands/migration-help";
|
|
15
|
+
import { akmProposalAccept, akmProposalDiff, akmProposalList, akmProposalReject, akmProposalShow, } from "./commands/proposal";
|
|
16
|
+
import { akmPropose } from "./commands/propose";
|
|
17
|
+
import { akmReflect } from "./commands/reflect";
|
|
12
18
|
import { searchRegistry } from "./commands/registry-search";
|
|
13
19
|
import { buildMemoryFrontmatter, parseDuration, readMemoryContent, runAutoHeuristics, runLlmEnrich, } from "./commands/remember";
|
|
14
|
-
import { akmSearch, parseSearchSource } from "./commands/search";
|
|
20
|
+
import { akmSearch, parseScopeFilterFlags, parseSearchSource } from "./commands/search";
|
|
15
21
|
import { checkForUpdate, performUpgrade } from "./commands/self-update";
|
|
16
22
|
import { akmShowUnified } from "./commands/show";
|
|
17
23
|
import { akmAdd } from "./commands/source-add";
|
|
@@ -22,8 +28,9 @@ import { deriveCanonicalAssetName, resolveAssetPathFromName } from "./core/asset
|
|
|
22
28
|
import { isWithin, resolveStashDir, tryReadStdinText } from "./core/common";
|
|
23
29
|
import { DEFAULT_CONFIG, getConfigPath, loadConfig, loadUserConfig, saveConfig } from "./core/config";
|
|
24
30
|
import { ConfigError, NotFoundError, UsageError } from "./core/errors";
|
|
31
|
+
import { appendEvent } from "./core/events";
|
|
25
32
|
import { getCacheDir, getDbPath, getDefaultStashDir } from "./core/paths";
|
|
26
|
-
import { setQuiet, warn } from "./core/warn";
|
|
33
|
+
import { setQuiet, setVerbose, warn } from "./core/warn";
|
|
27
34
|
import { resolveWriteTarget, writeAssetToSource } from "./core/write-source";
|
|
28
35
|
import { closeDatabase, findEntryIdByRef, openDatabase } from "./indexer/db";
|
|
29
36
|
import { akmIndex } from "./indexer/indexer";
|
|
@@ -32,7 +39,7 @@ import { insertUsageEvent } from "./indexer/usage-events";
|
|
|
32
39
|
import { EMBEDDED_HINTS, EMBEDDED_HINTS_FULL } from "./output/cli-hints";
|
|
33
40
|
import { getHyphenatedArg, getHyphenatedBoolean, getOutputMode, initOutputMode, parseFlagValue, } from "./output/context";
|
|
34
41
|
import { shapeForCommand } from "./output/shapes";
|
|
35
|
-
import { formatPlain, outputJsonl } from "./output/text";
|
|
42
|
+
import { formatEventLine, formatPlain, outputJsonl } from "./output/text";
|
|
36
43
|
import { buildRegistryIndex, writeRegistryIndex } from "./registry/build-index";
|
|
37
44
|
import { resolveSourcesForOrigin } from "./registry/origin-resolve";
|
|
38
45
|
import { saveGitStash } from "./sources/providers/git";
|
|
@@ -58,6 +65,10 @@ function parseAllFlagValues(flag) {
|
|
|
58
65
|
const arg = process.argv[i];
|
|
59
66
|
if (arg === flag && i + 1 < process.argv.length) {
|
|
60
67
|
values.push(process.argv[i + 1]);
|
|
68
|
+
// BUG-M4: skip the value index so `--tag --tag` (literal `--tag`
|
|
69
|
+
// value) does not double-count the second `--tag` as a separate
|
|
70
|
+
// flag occurrence.
|
|
71
|
+
i++;
|
|
61
72
|
}
|
|
62
73
|
else if (arg.startsWith(`${flag}=`)) {
|
|
63
74
|
values.push(arg.slice(flag.length + 1));
|
|
@@ -96,7 +107,7 @@ function output(command, result) {
|
|
|
96
107
|
const setupCommand = defineCommand({
|
|
97
108
|
meta: {
|
|
98
109
|
name: "setup",
|
|
99
|
-
description: "Interactive configuration wizard
|
|
110
|
+
description: "Interactive configuration wizard: detects services and walks you through embeddings, LLM, registries, sources, and agent profiles. Writes config once at the end.",
|
|
100
111
|
},
|
|
101
112
|
async run() {
|
|
102
113
|
await runWithJsonErrors(async () => {
|
|
@@ -154,19 +165,29 @@ const searchCommand = defineCommand({
|
|
|
154
165
|
query: { type: "positional", description: "Search query (omit to list all assets)", required: false, default: "" },
|
|
155
166
|
type: {
|
|
156
167
|
type: "string",
|
|
157
|
-
description: "Asset type filter (skill, command, agent, knowledge, workflow, script, memory, vault, wiki, or any). Use workflow to find step-by-step task assets.",
|
|
168
|
+
description: "Asset type filter (skill, command, agent, knowledge, workflow, script, memory, vault, wiki, lesson, or any). Use workflow to find step-by-step task assets.",
|
|
158
169
|
},
|
|
159
170
|
limit: { type: "string", description: "Maximum number of results" },
|
|
160
171
|
source: { type: "string", description: "Search source (stash|registry|both)", default: "stash" },
|
|
172
|
+
filter: {
|
|
173
|
+
type: "string",
|
|
174
|
+
description: "Scope filter (repeatable): --filter user=<id> --filter agent=<id> --filter run=<id> --filter channel=<name>. Narrows results without changing ranking.",
|
|
175
|
+
},
|
|
176
|
+
"include-proposed": {
|
|
177
|
+
type: "boolean",
|
|
178
|
+
description: 'Include entries with quality:"proposed" in the result set. Excluded by default (v1 spec §4.2).',
|
|
179
|
+
default: false,
|
|
180
|
+
},
|
|
161
181
|
format: { type: "string", description: "Output format (json|jsonl|text|yaml)" },
|
|
162
182
|
detail: { type: "string", description: "Detail level (brief|normal|full|summary|agent)" },
|
|
163
183
|
},
|
|
164
184
|
async run({ args }) {
|
|
165
185
|
await runWithJsonErrors(async () => {
|
|
186
|
+
// An empty query enumerates all indexed assets (list mode).
|
|
187
|
+
// The guard that rejected empty queries was removed; akmSearch handles
|
|
188
|
+
// empty strings end-to-end via getAllEntries (DB path) and the
|
|
189
|
+
// substring-search fallback's query-less branch.
|
|
166
190
|
const query = (args.query ?? "").trim();
|
|
167
|
-
if (!query) {
|
|
168
|
-
throw new UsageError('A search query is required. Usage: akm search "<query>" [--type <type>] [--limit <n>]', "MISSING_REQUIRED_ARGUMENT");
|
|
169
|
-
}
|
|
170
191
|
const type = args.type;
|
|
171
192
|
const limitRaw = args.limit ? parseInt(args.limit, 10) : undefined;
|
|
172
193
|
if (limitRaw !== undefined && Number.isNaN(limitRaw)) {
|
|
@@ -174,7 +195,12 @@ const searchCommand = defineCommand({
|
|
|
174
195
|
}
|
|
175
196
|
const limit = limitRaw;
|
|
176
197
|
const source = parseSearchSource(args.source);
|
|
177
|
-
|
|
198
|
+
// Repeatable; citty exposes only the last `--filter` value, so read all
|
|
199
|
+
// occurrences directly from argv (same pattern as `--tag`).
|
|
200
|
+
const filterTokens = parseAllFlagValues("--filter");
|
|
201
|
+
const filters = parseScopeFilterFlags(filterTokens, "--filter");
|
|
202
|
+
const includeProposed = args["include-proposed"] === true;
|
|
203
|
+
const result = await akmSearch({ query, type, limit, source, filters, includeProposed });
|
|
178
204
|
output("search", result);
|
|
179
205
|
});
|
|
180
206
|
},
|
|
@@ -182,16 +208,22 @@ const searchCommand = defineCommand({
|
|
|
182
208
|
const curateCommand = defineCommand({
|
|
183
209
|
meta: { name: "curate", description: "Curate the best matching assets for a task or prompt" },
|
|
184
210
|
args: {
|
|
185
|
-
|
|
211
|
+
// Optional in citty so run() is invoked when omitted; we re-validate
|
|
212
|
+
// below to surface a structured UsageError (exit 2) instead of citty's
|
|
213
|
+
// default help-banner exit-0.
|
|
214
|
+
query: { type: "positional", description: "Task or prompt to curate assets for", required: false },
|
|
186
215
|
type: {
|
|
187
216
|
type: "string",
|
|
188
|
-
description: "Asset type filter (skill, command, agent, knowledge, workflow, script, memory, vault, wiki, or any). Use workflow to curate step-by-step task assets.",
|
|
217
|
+
description: "Asset type filter (skill, command, agent, knowledge, workflow, script, memory, vault, wiki, lesson, or any). Use workflow to curate step-by-step task assets.",
|
|
189
218
|
},
|
|
190
219
|
limit: { type: "string", description: "Maximum number of curated results", default: "4" },
|
|
191
220
|
source: { type: "string", description: "Search source (stash|registry|both)", default: "stash" },
|
|
192
221
|
},
|
|
193
222
|
async run({ args }) {
|
|
194
223
|
await runWithJsonErrors(async () => {
|
|
224
|
+
if (!args.query || !String(args.query).trim()) {
|
|
225
|
+
throw new UsageError('A curate query is required. Usage: akm curate "<task or prompt>" [--type <type>] [--limit <n>]', "MISSING_REQUIRED_ARGUMENT", 'Describe the task you want assets for, e.g. `akm curate "deploy to prod"`.');
|
|
226
|
+
}
|
|
195
227
|
const type = args.type;
|
|
196
228
|
const limitRaw = args.limit ? parseInt(args.limit, 10) : undefined;
|
|
197
229
|
if (limitRaw !== undefined && Number.isNaN(limitRaw)) {
|
|
@@ -234,14 +266,24 @@ const addCommand = defineCommand({
|
|
|
234
266
|
},
|
|
235
267
|
"max-pages": { type: "string", description: "Maximum pages to crawl for website sources (default: 50)" },
|
|
236
268
|
"max-depth": { type: "string", description: "Maximum crawl depth for website sources (default: 3)" },
|
|
269
|
+
"allow-insecure": {
|
|
270
|
+
type: "boolean",
|
|
271
|
+
description: "Allow a plain HTTP source URL (otherwise rejected for non-localhost hosts)",
|
|
272
|
+
default: false,
|
|
273
|
+
},
|
|
237
274
|
},
|
|
238
275
|
async run({ args }) {
|
|
239
276
|
await runWithJsonErrors(async () => {
|
|
240
277
|
const ref = args.ref.trim();
|
|
278
|
+
const allowInsecure = getHyphenatedBoolean(args, "allow-insecure");
|
|
241
279
|
// URL with --provider → stash source (remote or git provider)
|
|
242
280
|
if (args.provider) {
|
|
243
281
|
if (shouldWarnOnPlainHttp(ref)) {
|
|
244
|
-
|
|
282
|
+
if (!allowInsecure) {
|
|
283
|
+
throw new UsageError("Source URL uses plain HTTP (not HTTPS). An on-path attacker could substitute a malicious payload. " +
|
|
284
|
+
"Use https:// or pass --allow-insecure if you have explicitly accepted the risk.", "INVALID_FLAG_VALUE", "Re-run with `--allow-insecure` only after confirming the URL is trusted.");
|
|
285
|
+
}
|
|
286
|
+
warn("Warning: source URL uses plain HTTP (not HTTPS). --allow-insecure was set; an on-path attacker could substitute a malicious payload.");
|
|
245
287
|
}
|
|
246
288
|
let parsedOptions;
|
|
247
289
|
if (args.options) {
|
|
@@ -265,11 +307,19 @@ const addCommand = defineCommand({
|
|
|
265
307
|
options: parsedOptions,
|
|
266
308
|
writable: args.writable,
|
|
267
309
|
});
|
|
310
|
+
appendEvent({
|
|
311
|
+
eventType: "add",
|
|
312
|
+
metadata: { target: ref, provider: args.provider, name: args.name ?? null, writable: args.writable === true },
|
|
313
|
+
});
|
|
268
314
|
output("add", result);
|
|
269
315
|
return;
|
|
270
316
|
}
|
|
271
317
|
if (shouldWarnOnPlainHttp(ref)) {
|
|
272
|
-
|
|
318
|
+
if (!allowInsecure) {
|
|
319
|
+
throw new UsageError("Source URL uses plain HTTP (not HTTPS). An on-path attacker could substitute a malicious payload. " +
|
|
320
|
+
"Use https:// or pass --allow-insecure if you have explicitly accepted the risk.", "INVALID_FLAG_VALUE", "Re-run with `--allow-insecure` only after confirming the URL is trusted.");
|
|
321
|
+
}
|
|
322
|
+
warn("Warning: source URL uses plain HTTP (not HTTPS). --allow-insecure was set; an on-path attacker could substitute a malicious payload.");
|
|
273
323
|
}
|
|
274
324
|
const websiteOptions = buildWebsiteOptions(args);
|
|
275
325
|
if (args.type === "wiki") {
|
|
@@ -281,6 +331,10 @@ const addCommand = defineCommand({
|
|
|
281
331
|
trustThisInstall: args.trust,
|
|
282
332
|
writable: args.writable,
|
|
283
333
|
});
|
|
334
|
+
appendEvent({
|
|
335
|
+
eventType: "add",
|
|
336
|
+
metadata: { target: ref, type: "wiki", name: args.name ?? null, writable: args.writable === true },
|
|
337
|
+
});
|
|
284
338
|
output("add", result);
|
|
285
339
|
return;
|
|
286
340
|
}
|
|
@@ -292,6 +346,15 @@ const addCommand = defineCommand({
|
|
|
292
346
|
trustThisInstall: args.trust,
|
|
293
347
|
writable: args.writable,
|
|
294
348
|
});
|
|
349
|
+
appendEvent({
|
|
350
|
+
eventType: "add",
|
|
351
|
+
metadata: {
|
|
352
|
+
target: ref,
|
|
353
|
+
name: args.name ?? null,
|
|
354
|
+
overrideType: args.type ?? null,
|
|
355
|
+
writable: args.writable === true,
|
|
356
|
+
},
|
|
357
|
+
});
|
|
295
358
|
output("add", result);
|
|
296
359
|
});
|
|
297
360
|
},
|
|
@@ -353,6 +416,14 @@ const removeCommand = defineCommand({
|
|
|
353
416
|
async run({ args }) {
|
|
354
417
|
await runWithJsonErrors(async () => {
|
|
355
418
|
const result = await akmRemove({ target: args.target });
|
|
419
|
+
appendEvent({
|
|
420
|
+
eventType: "remove",
|
|
421
|
+
metadata: {
|
|
422
|
+
target: args.target,
|
|
423
|
+
ref: typeof result.removed?.ref === "string" ? result.removed.ref : null,
|
|
424
|
+
id: typeof result.removed?.id === "string" ? result.removed.id : null,
|
|
425
|
+
},
|
|
426
|
+
});
|
|
356
427
|
output("remove", result);
|
|
357
428
|
});
|
|
358
429
|
},
|
|
@@ -367,6 +438,17 @@ const updateCommand = defineCommand({
|
|
|
367
438
|
async run({ args }) {
|
|
368
439
|
await runWithJsonErrors(async () => {
|
|
369
440
|
const result = await akmUpdate({ target: args.target, all: args.all, force: args.force });
|
|
441
|
+
appendEvent({
|
|
442
|
+
eventType: "update",
|
|
443
|
+
metadata: {
|
|
444
|
+
target: args.target ?? null,
|
|
445
|
+
all: args.all === true,
|
|
446
|
+
force: args.force === true,
|
|
447
|
+
processed: Array.isArray(result.processed)
|
|
448
|
+
? result.processed.length
|
|
449
|
+
: 0,
|
|
450
|
+
},
|
|
451
|
+
});
|
|
370
452
|
output("update", result);
|
|
371
453
|
});
|
|
372
454
|
},
|
|
@@ -407,12 +489,29 @@ const showCommand = defineCommand({
|
|
|
407
489
|
description: "Show a stash asset by ref (e.g. akm show knowledge:guide.md toc, akm show knowledge:guide.md section 'Auth')",
|
|
408
490
|
},
|
|
409
491
|
args: {
|
|
410
|
-
ref: {
|
|
492
|
+
ref: {
|
|
493
|
+
type: "positional",
|
|
494
|
+
description: 'Asset ref ([origin//]type:name) optionally followed by a view mode. View modes: `toc` (table of contents), `section "Heading"` (extract one section), `lines <start> <end>` (line range), `frontmatter` (YAML metadata only), `full` (raw file). Example: `akm show knowledge:guide.md section "Auth"`.',
|
|
495
|
+
required: true,
|
|
496
|
+
},
|
|
411
497
|
format: { type: "string", description: "Output format (json|jsonl|text|yaml)" },
|
|
412
498
|
detail: { type: "string", description: "Detail level (brief|normal|full|summary|agent)" },
|
|
499
|
+
scope: {
|
|
500
|
+
type: "string",
|
|
501
|
+
description: "Scope filter (repeatable): --scope user=<id> --scope agent=<id> --scope run=<id> --scope channel=<name>. Narrows resolution to assets whose frontmatter scope matches.",
|
|
502
|
+
},
|
|
413
503
|
},
|
|
414
504
|
async run({ args }) {
|
|
415
505
|
await runWithJsonErrors(async () => {
|
|
506
|
+
try {
|
|
507
|
+
parseAssetRef(args.ref);
|
|
508
|
+
}
|
|
509
|
+
catch (error) {
|
|
510
|
+
if (error instanceof UsageError && error.code === "MISSING_REQUIRED_ARGUMENT") {
|
|
511
|
+
throw new UsageError(error.message, "INVALID_FLAG_VALUE", error.hint());
|
|
512
|
+
}
|
|
513
|
+
throw error;
|
|
514
|
+
}
|
|
416
515
|
// The knowledge-view positional syntax (`akm show knowledge:foo section "Auth"`)
|
|
417
516
|
// is rewritten to `--akmView` / `--akmHeading` / `--akmStart` / `--akmEnd`
|
|
418
517
|
// by `normalizeShowArgv` before citty parses argv. We read those values
|
|
@@ -447,7 +546,11 @@ const showCommand = defineCommand({
|
|
|
447
546
|
const cliDetail = getOutputMode().detail;
|
|
448
547
|
const explicitDetail = parseFlagValue(process.argv, "--detail");
|
|
449
548
|
const showDetail = explicitDetail === "brief" ? "brief" : cliDetail === "summary" ? "summary" : undefined;
|
|
450
|
-
|
|
549
|
+
// `--scope` is repeatable — citty only exposes the last value, so read
|
|
550
|
+
// every occurrence directly from argv (same pattern as `--filter`).
|
|
551
|
+
const scopeTokens = parseAllFlagValues("--scope");
|
|
552
|
+
const scope = parseScopeFilterFlags(scopeTokens, "--scope");
|
|
553
|
+
const result = await akmShowUnified({ ref: args.ref, view, detail: showDetail, scope });
|
|
451
554
|
output("show", result);
|
|
452
555
|
});
|
|
453
556
|
},
|
|
@@ -588,6 +691,14 @@ const saveCommand = defineCommand({
|
|
|
588
691
|
writable = cfg.writable === true ? true : undefined;
|
|
589
692
|
}
|
|
590
693
|
const result = saveGitStash(effectiveName, args.message, writable);
|
|
694
|
+
appendEvent({
|
|
695
|
+
eventType: "save",
|
|
696
|
+
metadata: {
|
|
697
|
+
name: effectiveName ?? null,
|
|
698
|
+
message: args.message ?? null,
|
|
699
|
+
ok: result.ok !== false,
|
|
700
|
+
},
|
|
701
|
+
});
|
|
591
702
|
output("save", result);
|
|
592
703
|
});
|
|
593
704
|
},
|
|
@@ -762,8 +873,8 @@ const registryCommand = defineCommand({
|
|
|
762
873
|
"build-index": defineCommand({
|
|
763
874
|
meta: { name: "build-index", description: "Build a v2 registry index from discovery and manual entries" },
|
|
764
875
|
args: {
|
|
765
|
-
out: { type: "string", description: "Output path for the generated index"
|
|
766
|
-
manual: { type: "string", description: "Manual entries JSON file"
|
|
876
|
+
out: { type: "string", description: "Output path for the generated index" },
|
|
877
|
+
manual: { type: "string", description: "Manual entries JSON file" },
|
|
767
878
|
"npm-registry": { type: "string", description: "Override npm registry base URL" },
|
|
768
879
|
"github-api": { type: "string", description: "Override GitHub API base URL" },
|
|
769
880
|
},
|
|
@@ -791,19 +902,33 @@ const registryCommand = defineCommand({
|
|
|
791
902
|
const feedbackCommand = defineCommand({
|
|
792
903
|
meta: {
|
|
793
904
|
name: "feedback",
|
|
794
|
-
description: "Record positive or negative feedback for any indexed stash asset"
|
|
905
|
+
description: "Record positive or negative feedback for any indexed stash asset.\n\n" +
|
|
906
|
+
"Positive feedback boosts an asset's EMA utility score, making it rank higher\n" +
|
|
907
|
+
"in future searches without requiring a full reindex.\n\n" +
|
|
908
|
+
"Negative feedback records a negative signal in usage_events and events.jsonl.\n" +
|
|
909
|
+
"It does NOT immediately lower the asset's ranking — the EMA utility score is\n" +
|
|
910
|
+
"updated the next time `akm index` runs (incremental or full). Run `akm index`\n" +
|
|
911
|
+
"after recording negative feedback to have it reflected in search results.",
|
|
795
912
|
},
|
|
796
913
|
args: {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
914
|
+
// Optional in citty so run() is invoked even when omitted; we re-validate
|
|
915
|
+
// and throw a structured UsageError below so exit code is 2 (USAGE) rather
|
|
916
|
+
// than citty's default 0 (help banner).
|
|
917
|
+
ref: { type: "positional", description: "Asset ref (type:name)", required: false },
|
|
918
|
+
positive: { type: "boolean", description: "Record positive feedback (boosts ranking immediately)", default: false },
|
|
919
|
+
negative: {
|
|
920
|
+
type: "boolean",
|
|
921
|
+
description: "Record negative feedback (suppresses ranking after next `akm index`). " +
|
|
922
|
+
"Reindexing is required for the signal to affect search results.",
|
|
923
|
+
default: false,
|
|
924
|
+
},
|
|
800
925
|
note: { type: "string", description: "Optional note to attach to the feedback" },
|
|
801
926
|
},
|
|
802
927
|
run({ args }) {
|
|
803
928
|
return runWithJsonErrors(() => {
|
|
804
|
-
const ref = args.ref.trim();
|
|
929
|
+
const ref = (args.ref ?? "").trim();
|
|
805
930
|
if (!ref) {
|
|
806
|
-
throw new UsageError("Asset ref is required. Usage: akm feedback <ref> --positive|--negative");
|
|
931
|
+
throw new UsageError("Asset ref is required. Usage: akm feedback <ref> --positive|--negative", "MISSING_REQUIRED_ARGUMENT", "Pass a ref like `skill:deploy` and either --positive or --negative.");
|
|
807
932
|
}
|
|
808
933
|
parseAssetRef(ref);
|
|
809
934
|
if (args.positive && args.negative) {
|
|
@@ -820,6 +945,11 @@ const feedbackCommand = defineCommand({
|
|
|
820
945
|
if (entryId === undefined) {
|
|
821
946
|
throw new UsageError(`Ref "${ref}" is not in the current index. Run "akm index" and try again.`);
|
|
822
947
|
}
|
|
948
|
+
// Persist the feedback signal into usage_events. For positive signals,
|
|
949
|
+
// the EMA utility score is updated immediately on the next read path.
|
|
950
|
+
// For negative signals, the score is adjusted the next time `akm index`
|
|
951
|
+
// runs — the signal is durable in the DB but does NOT suppress ranking
|
|
952
|
+
// in search results until after reindexing.
|
|
823
953
|
insertUsageEvent(db, {
|
|
824
954
|
event_type: "feedback",
|
|
825
955
|
entry_ref: ref,
|
|
@@ -831,10 +961,47 @@ const feedbackCommand = defineCommand({
|
|
|
831
961
|
finally {
|
|
832
962
|
closeDatabase(db);
|
|
833
963
|
}
|
|
964
|
+
appendEvent({
|
|
965
|
+
eventType: "feedback",
|
|
966
|
+
ref,
|
|
967
|
+
metadata: { signal, ...(args.note ? { note: args.note } : {}) },
|
|
968
|
+
});
|
|
834
969
|
output("feedback", { ok: true, ref, signal, note: args.note ?? null });
|
|
835
970
|
});
|
|
836
971
|
},
|
|
837
972
|
});
|
|
973
|
+
const historyCommand = defineCommand({
|
|
974
|
+
meta: {
|
|
975
|
+
name: "history",
|
|
976
|
+
description: "Show mutation/usage history for a single asset (--ref) or stash-wide.\n\n" +
|
|
977
|
+
"Event sources:\n" +
|
|
978
|
+
" usage_events (default): search, show, and feedback events from the local index.\n" +
|
|
979
|
+
" events.jsonl (--include-proposals): proposal lifecycle events (promoted, rejected)\n" +
|
|
980
|
+
" emitted by `akm proposal accept` / `akm proposal reject`.\n\n" +
|
|
981
|
+
"Results from all active sources are merged and sorted chronologically.",
|
|
982
|
+
},
|
|
983
|
+
args: {
|
|
984
|
+
ref: { type: "string", description: "Asset ref (type:name). Omit for stash-wide history." },
|
|
985
|
+
since: { type: "string", description: "ISO timestamp or epoch ms — only events on/after this time" },
|
|
986
|
+
"include-proposals": {
|
|
987
|
+
type: "boolean",
|
|
988
|
+
description: "Also include proposal lifecycle events (promoted, rejected) from events.jsonl. " +
|
|
989
|
+
"Default: false (usage_events only).",
|
|
990
|
+
default: false,
|
|
991
|
+
},
|
|
992
|
+
format: { type: "string", description: "Output format (json|jsonl|text|yaml)" },
|
|
993
|
+
},
|
|
994
|
+
run({ args }) {
|
|
995
|
+
return runWithJsonErrors(async () => {
|
|
996
|
+
const result = await akmHistory({
|
|
997
|
+
ref: args.ref,
|
|
998
|
+
since: args.since,
|
|
999
|
+
includeProposals: args["include-proposals"],
|
|
1000
|
+
});
|
|
1001
|
+
output("history", result);
|
|
1002
|
+
});
|
|
1003
|
+
},
|
|
1004
|
+
});
|
|
838
1005
|
function normalizeMarkdownAssetName(name, fallback) {
|
|
839
1006
|
const trimmed = (name ?? fallback)
|
|
840
1007
|
.trim()
|
|
@@ -944,11 +1111,40 @@ const workflowNextCommand = defineCommand({
|
|
|
944
1111
|
async run({ args }) {
|
|
945
1112
|
await runWithJsonErrors(async () => {
|
|
946
1113
|
const parsedParams = args.params ? parseWorkflowJsonObject(args.params, "--params") : undefined;
|
|
1114
|
+
// If the target looks like a UUID-style run id (no `:` and matches the
|
|
1115
|
+
// run-id shape), short-circuit with a structured WORKFLOW_NOT_FOUND
|
|
1116
|
+
// error before parseAssetRef gets to throw an unhelpful ref-parse error.
|
|
1117
|
+
if (looksLikeWorkflowRunId(args.target)) {
|
|
1118
|
+
const { listWorkflowRuns: listRuns } = await import("./workflows/runs.js");
|
|
1119
|
+
const { runs: existingRuns } = listRuns({});
|
|
1120
|
+
if (!existingRuns.some((r) => r.id === args.target)) {
|
|
1121
|
+
throw new NotFoundError(`Workflow run "${args.target}" not found.`, "WORKFLOW_NOT_FOUND", "Run `akm workflow list --active` to see runs.");
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
947
1124
|
const result = await getNextWorkflowStep(args.target, parsedParams);
|
|
948
1125
|
output("workflow-next", result);
|
|
949
1126
|
});
|
|
950
1127
|
},
|
|
951
1128
|
});
|
|
1129
|
+
/**
|
|
1130
|
+
* Heuristic: a workflow run id is a UUID-shaped or hex-id-shaped string with
|
|
1131
|
+
* no `:` separator (refs always contain a colon: `workflow:<name>` or
|
|
1132
|
+
* `<origin>//workflow:<name>`). When this matches we can give a much better
|
|
1133
|
+
* error than parseAssetRef's "Invalid asset type" failure.
|
|
1134
|
+
*/
|
|
1135
|
+
function looksLikeWorkflowRunId(target) {
|
|
1136
|
+
if (target.includes(":"))
|
|
1137
|
+
return false;
|
|
1138
|
+
if (target.includes("/"))
|
|
1139
|
+
return false;
|
|
1140
|
+
// UUID v4-ish: 8-4-4-4-12 hex digits separated by dashes.
|
|
1141
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(target))
|
|
1142
|
+
return true;
|
|
1143
|
+
// Bare hex/alphanumeric run ids of >=8 chars (covers shortened ids).
|
|
1144
|
+
if (/^[0-9a-z][0-9a-z_-]{7,}$/i.test(target) && /[0-9]/.test(target))
|
|
1145
|
+
return true;
|
|
1146
|
+
return false;
|
|
1147
|
+
}
|
|
952
1148
|
const workflowCompleteCommand = defineCommand({
|
|
953
1149
|
meta: {
|
|
954
1150
|
name: "complete",
|
|
@@ -1220,15 +1416,45 @@ const rememberCommand = defineCommand({
|
|
|
1220
1416
|
type: "string",
|
|
1221
1417
|
description: "Override the write destination. Accepts a source name from your config; falls back to defaultWriteTarget then the working stash.",
|
|
1222
1418
|
},
|
|
1419
|
+
user: {
|
|
1420
|
+
type: "string",
|
|
1421
|
+
description: "Scope this memory to a user id (persisted as `scope_user` frontmatter)",
|
|
1422
|
+
},
|
|
1423
|
+
agent: {
|
|
1424
|
+
type: "string",
|
|
1425
|
+
description: "Scope this memory to an agent id (persisted as `scope_agent` frontmatter)",
|
|
1426
|
+
},
|
|
1427
|
+
run: {
|
|
1428
|
+
type: "string",
|
|
1429
|
+
description: "Scope this memory to a run id (persisted as `scope_run` frontmatter)",
|
|
1430
|
+
},
|
|
1431
|
+
channel: {
|
|
1432
|
+
type: "string",
|
|
1433
|
+
description: "Scope this memory to a channel name (persisted as `scope_channel` frontmatter)",
|
|
1434
|
+
},
|
|
1223
1435
|
},
|
|
1224
1436
|
async run({ args }) {
|
|
1225
1437
|
return runWithJsonErrors(async () => {
|
|
1226
|
-
const body = readMemoryContent(args.content);
|
|
1438
|
+
const body = readMemoryContent(resolveRememberContentArg(args.content));
|
|
1227
1439
|
// Determine if the user has requested any structured metadata mode.
|
|
1228
1440
|
// Collect all --tag occurrences directly from process.argv because citty
|
|
1229
1441
|
// only exposes the last value for repeated string flags.
|
|
1230
1442
|
const rawTags = parseAllFlagValues("--tag");
|
|
1231
|
-
|
|
1443
|
+
// Collect scope flags. Scope alone counts as structured metadata so we
|
|
1444
|
+
// emit frontmatter, but it does NOT trigger the "tags required" check —
|
|
1445
|
+
// memory + scope (no tags) is a valid combination for multi-tenant use.
|
|
1446
|
+
const scopeFields = {};
|
|
1447
|
+
if (typeof args.user === "string" && args.user.trim())
|
|
1448
|
+
scopeFields.user = args.user.trim();
|
|
1449
|
+
if (typeof args.agent === "string" && args.agent.trim())
|
|
1450
|
+
scopeFields.agent = args.agent.trim();
|
|
1451
|
+
if (typeof args.run === "string" && args.run.trim())
|
|
1452
|
+
scopeFields.run = args.run.trim();
|
|
1453
|
+
if (typeof args.channel === "string" && args.channel.trim())
|
|
1454
|
+
scopeFields.channel = args.channel.trim();
|
|
1455
|
+
const hasScope = Object.keys(scopeFields).length > 0;
|
|
1456
|
+
const hasTagRequiringArgs = rawTags.length > 0 || !!args.expires || !!args.source || !!args.description || args.enrich;
|
|
1457
|
+
const hasStructuredArgs = hasTagRequiringArgs || hasScope || args.auto;
|
|
1232
1458
|
if (!hasStructuredArgs) {
|
|
1233
1459
|
const result = await writeMarkdownAsset({
|
|
1234
1460
|
type: "memory",
|
|
@@ -1238,6 +1464,11 @@ const rememberCommand = defineCommand({
|
|
|
1238
1464
|
force: args.force,
|
|
1239
1465
|
target: args.target,
|
|
1240
1466
|
});
|
|
1467
|
+
appendEvent({
|
|
1468
|
+
eventType: "remember",
|
|
1469
|
+
ref: result.ref,
|
|
1470
|
+
metadata: { path: result.path, force: args.force === true },
|
|
1471
|
+
});
|
|
1241
1472
|
output("remember", { ok: true, ...result });
|
|
1242
1473
|
return;
|
|
1243
1474
|
}
|
|
@@ -1283,8 +1514,14 @@ const rememberCommand = defineCommand({
|
|
|
1283
1514
|
observed_at = enriched.observed_at;
|
|
1284
1515
|
}
|
|
1285
1516
|
// ── Required-field check (before any write) ───────────────────────────
|
|
1517
|
+
// Tags remain required when the user explicitly asked for tag-bearing
|
|
1518
|
+
// metadata (--tag / --enrich / --description / --source / --expires).
|
|
1519
|
+
// `--auto` alone is allowed even when its heuristics derive zero tags.
|
|
1520
|
+
// Scope-only writes (`akm remember "..." --user u1`) also skip this
|
|
1521
|
+
// check — scope is independent metadata and a memory with only scope is
|
|
1522
|
+
// valid.
|
|
1286
1523
|
const missing = [];
|
|
1287
|
-
if (tags.length === 0)
|
|
1524
|
+
if (hasTagRequiringArgs && tags.length === 0)
|
|
1288
1525
|
missing.push("tags");
|
|
1289
1526
|
if (missing.length > 0) {
|
|
1290
1527
|
throw new UsageError(`Memory is missing required frontmatter field(s): ${missing.join(", ")}. ` +
|
|
@@ -1298,6 +1535,7 @@ const rememberCommand = defineCommand({
|
|
|
1298
1535
|
observed_at,
|
|
1299
1536
|
expires,
|
|
1300
1537
|
subjective,
|
|
1538
|
+
...(hasScope ? { scope: scopeFields } : {}),
|
|
1301
1539
|
});
|
|
1302
1540
|
const contentWithFrontmatter = `${frontmatterBlock}\n${body}`;
|
|
1303
1541
|
const result = await writeMarkdownAsset({
|
|
@@ -1308,10 +1546,66 @@ const rememberCommand = defineCommand({
|
|
|
1308
1546
|
force: args.force,
|
|
1309
1547
|
target: args.target,
|
|
1310
1548
|
});
|
|
1549
|
+
appendEvent({
|
|
1550
|
+
eventType: "remember",
|
|
1551
|
+
ref: result.ref,
|
|
1552
|
+
metadata: {
|
|
1553
|
+
path: result.path,
|
|
1554
|
+
force: args.force === true,
|
|
1555
|
+
tagCount: tags.length,
|
|
1556
|
+
enriched: args.enrich === true,
|
|
1557
|
+
auto: args.auto === true,
|
|
1558
|
+
...(hasScope ? { scope: scopeFields } : {}),
|
|
1559
|
+
},
|
|
1560
|
+
});
|
|
1311
1561
|
output("remember", { ok: true, ...result });
|
|
1312
1562
|
});
|
|
1313
1563
|
},
|
|
1314
1564
|
});
|
|
1565
|
+
function resolveRememberContentArg(content) {
|
|
1566
|
+
if (content === undefined)
|
|
1567
|
+
return undefined;
|
|
1568
|
+
const parsedFormat = parseFlagValue(process.argv, "--format");
|
|
1569
|
+
if (parsedFormat !== undefined &&
|
|
1570
|
+
content === parsedFormat &&
|
|
1571
|
+
wasRememberFlagValueConsumedAsContent(content, parsedFormat, "--format")) {
|
|
1572
|
+
return undefined;
|
|
1573
|
+
}
|
|
1574
|
+
const parsedDetail = parseFlagValue(process.argv, "--detail");
|
|
1575
|
+
if (parsedDetail !== undefined &&
|
|
1576
|
+
content === parsedDetail &&
|
|
1577
|
+
wasRememberFlagValueConsumedAsContent(content, parsedDetail, "--detail")) {
|
|
1578
|
+
return undefined;
|
|
1579
|
+
}
|
|
1580
|
+
return content;
|
|
1581
|
+
}
|
|
1582
|
+
function wasRememberFlagValueConsumedAsContent(content, flagValue, flagName) {
|
|
1583
|
+
const argv = process.argv.slice(2);
|
|
1584
|
+
const rememberIndex = argv.indexOf("remember");
|
|
1585
|
+
const tokens = rememberIndex >= 0 ? argv.slice(rememberIndex + 1) : argv;
|
|
1586
|
+
let flagIndex = -1;
|
|
1587
|
+
let flagConsumesNextToken = false;
|
|
1588
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
1589
|
+
const token = tokens[i];
|
|
1590
|
+
if (token === flagName) {
|
|
1591
|
+
flagIndex = i;
|
|
1592
|
+
flagConsumesNextToken = true;
|
|
1593
|
+
break;
|
|
1594
|
+
}
|
|
1595
|
+
if (token === `${flagName}=${flagValue}`) {
|
|
1596
|
+
flagIndex = i;
|
|
1597
|
+
break;
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
if (flagIndex === -1)
|
|
1601
|
+
return false;
|
|
1602
|
+
if (tokens.slice(0, flagIndex).includes(content))
|
|
1603
|
+
return false;
|
|
1604
|
+
const firstTokenAfterFlag = flagIndex + (flagConsumesNextToken ? 2 : 1);
|
|
1605
|
+
if (tokens.slice(firstTokenAfterFlag).includes(content))
|
|
1606
|
+
return false;
|
|
1607
|
+
return true;
|
|
1608
|
+
}
|
|
1315
1609
|
const importKnowledgeCommand = defineCommand({
|
|
1316
1610
|
meta: {
|
|
1317
1611
|
name: "import",
|
|
@@ -1349,6 +1643,11 @@ const importKnowledgeCommand = defineCommand({
|
|
|
1349
1643
|
force: args.force,
|
|
1350
1644
|
target: args.target,
|
|
1351
1645
|
});
|
|
1646
|
+
appendEvent({
|
|
1647
|
+
eventType: "import",
|
|
1648
|
+
ref: result.ref,
|
|
1649
|
+
metadata: { source: args.source, path: result.path, force: args.force === true },
|
|
1650
|
+
});
|
|
1352
1651
|
output("import", { ok: true, source: args.source, ...result });
|
|
1353
1652
|
});
|
|
1354
1653
|
},
|
|
@@ -1359,7 +1658,11 @@ const hintsCommand = defineCommand({
|
|
|
1359
1658
|
description: "Print agent instructions on how to use akm, use --detail full for a complete guide",
|
|
1360
1659
|
},
|
|
1361
1660
|
args: {
|
|
1362
|
-
detail: {
|
|
1661
|
+
detail: {
|
|
1662
|
+
type: "string",
|
|
1663
|
+
description: "Hints detail level — accepts only `normal` or `full`. Differs from the global --detail flag (brief|normal|full|summary|agent); other values are rejected with INVALID_DETAIL_VALUE.",
|
|
1664
|
+
default: "normal",
|
|
1665
|
+
},
|
|
1363
1666
|
},
|
|
1364
1667
|
run({ args }) {
|
|
1365
1668
|
if (args.detail !== "normal" && args.detail !== "full") {
|
|
@@ -1380,14 +1683,22 @@ const helpCommand = defineCommand({
|
|
|
1380
1683
|
description: "Print release notes and migration guidance for a version. Bundled notes live in docs/migration/release-notes/<version>.md; an unknown version lists what's available.",
|
|
1381
1684
|
},
|
|
1382
1685
|
args: {
|
|
1686
|
+
// Optional in citty so run() is invoked even when omitted; we
|
|
1687
|
+
// re-validate below to surface a structured UsageError (exit 2)
|
|
1688
|
+
// instead of citty's default help-banner exit-0.
|
|
1383
1689
|
version: {
|
|
1384
1690
|
type: "positional",
|
|
1385
1691
|
description: "Version to review (for example 0.6.0, v0.6.0, 0.6.0-rc1, or latest)",
|
|
1386
|
-
required:
|
|
1692
|
+
required: false,
|
|
1387
1693
|
},
|
|
1388
1694
|
},
|
|
1389
1695
|
run({ args }) {
|
|
1390
|
-
|
|
1696
|
+
return runWithJsonErrors(() => {
|
|
1697
|
+
if (!args.version || !String(args.version).trim()) {
|
|
1698
|
+
throw new UsageError("Usage: akm help migrate <version>.", "MISSING_REQUIRED_ARGUMENT", "Pass a version like `0.6.0`, `v0.6.0`, `0.6.0-rc1`, or `latest`.");
|
|
1699
|
+
}
|
|
1700
|
+
process.stdout.write(renderMigrationHelp(args.version));
|
|
1701
|
+
});
|
|
1391
1702
|
},
|
|
1392
1703
|
}),
|
|
1393
1704
|
},
|
|
@@ -1428,9 +1739,6 @@ function normalizeToggleTarget(target) {
|
|
|
1428
1739
|
const normalized = target.trim().toLowerCase();
|
|
1429
1740
|
if (normalized === "skills.sh" || normalized === "skills-sh")
|
|
1430
1741
|
return "skills.sh";
|
|
1431
|
-
if (normalized === "context-hub") {
|
|
1432
|
-
throw new UsageError('The "context-hub" component is no longer toggleable. Run `akm add github:andrewyng/context-hub --name context-hub` to add it as a git stash.');
|
|
1433
|
-
}
|
|
1434
1742
|
throw new UsageError(`Unsupported target "${target}". Supported targets: skills.sh`);
|
|
1435
1743
|
}
|
|
1436
1744
|
function toggleSkillsShRegistry(enabled) {
|
|
@@ -1698,6 +2006,22 @@ const vaultLoadCommand = defineCommand({
|
|
|
1698
2006
|
// metacharacters like $, backticks, or $(...).
|
|
1699
2007
|
const script = buildShellExportScript(absPath);
|
|
1700
2008
|
// Write to a mode-0600 temp file the shell can source.
|
|
2009
|
+
//
|
|
2010
|
+
// INTENTIONAL: this site uses `os.tmpdir()` (i.e. `/tmp` on Unix)
|
|
2011
|
+
// rather than `${getCacheDir()}/vault/`. The temp file is written
|
|
2012
|
+
// mode-0600, sourced by the parent shell via `eval`, and immediately
|
|
2013
|
+
// `rm -f`'d on the same line of the emitted snippet. `/tmp` is the
|
|
2014
|
+
// conventional location for short-lived shell-eval scratch files and
|
|
2015
|
+
// benefits from tmp-cleanup-on-reboot semantics, which operators
|
|
2016
|
+
// expect for ephemeral secret material. Moving to `~/.cache/akm/`
|
|
2017
|
+
// would surprise those operators and also persist the file across
|
|
2018
|
+
// reboots if the eval is interrupted before the inline `rm -f` runs.
|
|
2019
|
+
// The bench/registry-build rationale (#276/#284) — orphan dirs
|
|
2020
|
+
// accumulating under `/tmp` from long-running builds — does not
|
|
2021
|
+
// apply here: the file is single-shot, a few hundred bytes, and
|
|
2022
|
+
// removed by the same shell command that sources it.
|
|
2023
|
+
// Regression test: tests/vault-load-error.test.ts verifies the
|
|
2024
|
+
// emitted snippet contains both `. <path>` and `rm -f <path>`.
|
|
1701
2025
|
const tmpPath = path.join(os.tmpdir(), `akm-vault-${crypto.randomBytes(12).toString("hex")}.sh`);
|
|
1702
2026
|
fs.writeFileSync(tmpPath, script, { mode: 0o600, encoding: "utf8" });
|
|
1703
2027
|
try {
|
|
@@ -1991,6 +2315,343 @@ const wikiCommand = defineCommand({
|
|
|
1991
2315
|
});
|
|
1992
2316
|
},
|
|
1993
2317
|
});
|
|
2318
|
+
// ── `akm events` ────────────────────────────────────────────────────────────
|
|
2319
|
+
// Append-only events stream surface (#204). `list` reads `events.jsonl`
|
|
2320
|
+
// with optional --since/--type/--ref filters; `tail` follows the file via
|
|
2321
|
+
// a polling loop and prints each event as a single JSONL line.
|
|
2322
|
+
const eventsListCommand = defineCommand({
|
|
2323
|
+
meta: { name: "list", description: "List events from the append-only events.jsonl stream" },
|
|
2324
|
+
args: {
|
|
2325
|
+
since: {
|
|
2326
|
+
type: "string",
|
|
2327
|
+
description: "ISO timestamp / epoch ms, OR `@offset:<bytes>` for a durable byte-cursor (resume across processes)",
|
|
2328
|
+
},
|
|
2329
|
+
type: { type: "string", description: "Filter by event type (add, remove, remember, feedback, ...)" },
|
|
2330
|
+
ref: { type: "string", description: "Filter by asset ref (type:name)" },
|
|
2331
|
+
},
|
|
2332
|
+
run({ args }) {
|
|
2333
|
+
return runWithJsonErrors(() => {
|
|
2334
|
+
const result = akmEventsList({ since: args.since, type: args.type, ref: args.ref });
|
|
2335
|
+
output("events-list", result);
|
|
2336
|
+
});
|
|
2337
|
+
},
|
|
2338
|
+
});
|
|
2339
|
+
const eventsTailCommand = defineCommand({
|
|
2340
|
+
meta: { name: "tail", description: "Follow the append-only events.jsonl stream (polling)" },
|
|
2341
|
+
args: {
|
|
2342
|
+
since: {
|
|
2343
|
+
type: "string",
|
|
2344
|
+
description: "ISO timestamp / epoch ms, OR `@offset:<bytes>` for a durable byte-cursor (resume across processes)",
|
|
2345
|
+
},
|
|
2346
|
+
type: { type: "string", description: "Filter by event type" },
|
|
2347
|
+
ref: { type: "string", description: "Filter by asset ref (type:name)" },
|
|
2348
|
+
"interval-ms": { type: "string", description: "Polling interval in ms (default: 75)" },
|
|
2349
|
+
"max-duration-ms": { type: "string", description: "Stop after this many ms (default: never)" },
|
|
2350
|
+
"max-events": { type: "string", description: "Stop after observing this many events" },
|
|
2351
|
+
},
|
|
2352
|
+
async run({ args }) {
|
|
2353
|
+
await runWithJsonErrors(async () => {
|
|
2354
|
+
const intervalMs = parsePositiveInt(getHyphenatedArg(args, "interval-ms"), "--interval-ms");
|
|
2355
|
+
const maxDurationMs = parsePositiveInt(getHyphenatedArg(args, "max-duration-ms"), "--max-duration-ms");
|
|
2356
|
+
const maxEvents = parsePositiveInt(getHyphenatedArg(args, "max-events"), "--max-events");
|
|
2357
|
+
const mode = getOutputMode();
|
|
2358
|
+
// In streaming text mode we want each event to print as soon as it
|
|
2359
|
+
// arrives. The polling loop emits via `onEvent`; the final result is
|
|
2360
|
+
// also rendered through the standard output() pipeline so JSON
|
|
2361
|
+
// consumers always get the canonical envelope.
|
|
2362
|
+
const stream = mode.format === "text" || mode.format === "jsonl";
|
|
2363
|
+
const result = await akmEventsTail({
|
|
2364
|
+
since: args.since,
|
|
2365
|
+
type: args.type,
|
|
2366
|
+
ref: args.ref,
|
|
2367
|
+
intervalMs,
|
|
2368
|
+
maxDurationMs,
|
|
2369
|
+
maxEvents,
|
|
2370
|
+
onEvent: stream
|
|
2371
|
+
? (event) => {
|
|
2372
|
+
if (mode.format === "jsonl") {
|
|
2373
|
+
console.log(JSON.stringify(event));
|
|
2374
|
+
}
|
|
2375
|
+
else {
|
|
2376
|
+
console.log(formatEventLine(event));
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
: undefined,
|
|
2380
|
+
});
|
|
2381
|
+
// Emit the canonical envelope last (JSON/YAML modes rely on this;
|
|
2382
|
+
// streaming modes already printed each event but we still emit a
|
|
2383
|
+
// trailer so callers can persist the resumable cursor).
|
|
2384
|
+
if (!stream) {
|
|
2385
|
+
output("events-tail", result);
|
|
2386
|
+
}
|
|
2387
|
+
else if (mode.format === "jsonl") {
|
|
2388
|
+
// Final discriminated trailer row so jsonl consumers can resume.
|
|
2389
|
+
const trailer = {
|
|
2390
|
+
_kind: "trailer",
|
|
2391
|
+
schemaVersion: 1,
|
|
2392
|
+
nextOffset: result.nextOffset,
|
|
2393
|
+
totalCount: result.totalCount,
|
|
2394
|
+
reason: result.reason,
|
|
2395
|
+
};
|
|
2396
|
+
console.log(JSON.stringify(trailer));
|
|
2397
|
+
}
|
|
2398
|
+
else {
|
|
2399
|
+
// text mode: keep stdout pristine for line-oriented parsers and
|
|
2400
|
+
// emit the trailer on stderr.
|
|
2401
|
+
process.stderr.write(`[events-tail] reason=${result.reason} nextOffset=${result.nextOffset} total=${result.totalCount}\n`);
|
|
2402
|
+
}
|
|
2403
|
+
});
|
|
2404
|
+
},
|
|
2405
|
+
});
|
|
2406
|
+
function parsePositiveInt(raw, flag) {
|
|
2407
|
+
if (raw === undefined)
|
|
2408
|
+
return undefined;
|
|
2409
|
+
const trimmed = raw.trim();
|
|
2410
|
+
if (!trimmed)
|
|
2411
|
+
return undefined;
|
|
2412
|
+
const value = Number.parseInt(trimmed, 10);
|
|
2413
|
+
if (Number.isNaN(value) || value <= 0) {
|
|
2414
|
+
throw new UsageError(`Invalid ${flag} value: "${raw}". Must be a positive integer.`, "INVALID_FLAG_VALUE");
|
|
2415
|
+
}
|
|
2416
|
+
return value;
|
|
2417
|
+
}
|
|
2418
|
+
const eventsCommand = defineCommand({
|
|
2419
|
+
meta: {
|
|
2420
|
+
name: "events",
|
|
2421
|
+
description: "Read or follow the append-only events.jsonl stream (mutations, feedback, indexing)",
|
|
2422
|
+
},
|
|
2423
|
+
subCommands: {
|
|
2424
|
+
list: eventsListCommand,
|
|
2425
|
+
tail: eventsTailCommand,
|
|
2426
|
+
},
|
|
2427
|
+
});
|
|
2428
|
+
// ── proposal substrate (#225) ────────────────────────────────────────────────
|
|
2429
|
+
const proposalListCommand = defineCommand({
|
|
2430
|
+
meta: { name: "list", description: "List pending proposals (use --include-archive to see decided ones)" },
|
|
2431
|
+
args: {
|
|
2432
|
+
status: { type: "string", description: "Filter by status (pending|accepted|rejected)" },
|
|
2433
|
+
ref: { type: "string", description: "Filter by asset ref (type:name)" },
|
|
2434
|
+
"include-archive": {
|
|
2435
|
+
type: "boolean",
|
|
2436
|
+
description: "Include accepted/rejected proposals from the archive",
|
|
2437
|
+
default: false,
|
|
2438
|
+
},
|
|
2439
|
+
},
|
|
2440
|
+
run({ args }) {
|
|
2441
|
+
return runWithJsonErrors(() => {
|
|
2442
|
+
const status = parseProposalStatus(args.status);
|
|
2443
|
+
const result = akmProposalList({
|
|
2444
|
+
status,
|
|
2445
|
+
ref: args.ref,
|
|
2446
|
+
includeArchive: getHyphenatedBoolean(args, "include-archive"),
|
|
2447
|
+
});
|
|
2448
|
+
output("proposal-list", result);
|
|
2449
|
+
});
|
|
2450
|
+
},
|
|
2451
|
+
});
|
|
2452
|
+
const proposalShowCommand = defineCommand({
|
|
2453
|
+
meta: { name: "show", description: "Show a proposal's metadata, payload, and validation report" },
|
|
2454
|
+
args: {
|
|
2455
|
+
id: { type: "positional", description: "Proposal id (uuid)", required: true },
|
|
2456
|
+
},
|
|
2457
|
+
run({ args }) {
|
|
2458
|
+
return runWithJsonErrors(() => {
|
|
2459
|
+
const result = akmProposalShow({ id: args.id });
|
|
2460
|
+
output("proposal-show", result);
|
|
2461
|
+
});
|
|
2462
|
+
},
|
|
2463
|
+
});
|
|
2464
|
+
const proposalAcceptCommand = defineCommand({
|
|
2465
|
+
meta: { name: "accept", description: "Validate and promote a proposal to a real asset" },
|
|
2466
|
+
args: {
|
|
2467
|
+
id: { type: "positional", description: "Proposal id (uuid)", required: true },
|
|
2468
|
+
target: { type: "string", description: "Override the write target by source name" },
|
|
2469
|
+
},
|
|
2470
|
+
async run({ args }) {
|
|
2471
|
+
await runWithJsonErrors(async () => {
|
|
2472
|
+
const result = await akmProposalAccept({ id: args.id, target: args.target });
|
|
2473
|
+
output("proposal-accept", result);
|
|
2474
|
+
});
|
|
2475
|
+
},
|
|
2476
|
+
});
|
|
2477
|
+
const proposalRejectCommand = defineCommand({
|
|
2478
|
+
meta: { name: "reject", description: "Archive a pending proposal with an optional reason" },
|
|
2479
|
+
args: {
|
|
2480
|
+
id: { type: "positional", description: "Proposal id (uuid)", required: true },
|
|
2481
|
+
reason: { type: "string", description: "Reason for rejection (recorded in the archived proposal)" },
|
|
2482
|
+
},
|
|
2483
|
+
run({ args }) {
|
|
2484
|
+
return runWithJsonErrors(() => {
|
|
2485
|
+
const result = akmProposalReject({ id: args.id, reason: args.reason });
|
|
2486
|
+
output("proposal-reject", result);
|
|
2487
|
+
});
|
|
2488
|
+
},
|
|
2489
|
+
});
|
|
2490
|
+
const proposalDiffCommand = defineCommand({
|
|
2491
|
+
meta: { name: "diff", description: "Show the diff between an existing asset and a pending proposal" },
|
|
2492
|
+
args: {
|
|
2493
|
+
id: { type: "positional", description: "Proposal id (uuid)", required: true },
|
|
2494
|
+
target: { type: "string", description: "Override the write target by source name" },
|
|
2495
|
+
},
|
|
2496
|
+
run({ args }) {
|
|
2497
|
+
return runWithJsonErrors(() => {
|
|
2498
|
+
const result = akmProposalDiff({ id: args.id, target: args.target });
|
|
2499
|
+
output("proposal-diff", result);
|
|
2500
|
+
});
|
|
2501
|
+
},
|
|
2502
|
+
});
|
|
2503
|
+
const proposalCommand = defineCommand({
|
|
2504
|
+
meta: {
|
|
2505
|
+
name: "proposal",
|
|
2506
|
+
description: "Review and promote queued asset proposals (durable storage under .akm/proposals/)",
|
|
2507
|
+
},
|
|
2508
|
+
subCommands: {
|
|
2509
|
+
list: proposalListCommand,
|
|
2510
|
+
show: proposalShowCommand,
|
|
2511
|
+
accept: proposalAcceptCommand,
|
|
2512
|
+
reject: proposalRejectCommand,
|
|
2513
|
+
diff: proposalDiffCommand,
|
|
2514
|
+
},
|
|
2515
|
+
});
|
|
2516
|
+
// ── distill (#228) ──────────────────────────────────────────────────────────
|
|
2517
|
+
const distillCommand = defineCommand({
|
|
2518
|
+
meta: {
|
|
2519
|
+
name: "distill",
|
|
2520
|
+
description: "Distil feedback for an asset into a queued lesson proposal (gated on llm.features.feedback_distillation)",
|
|
2521
|
+
},
|
|
2522
|
+
args: {
|
|
2523
|
+
ref: { type: "positional", description: "Asset ref (type:name) to distil from", required: true },
|
|
2524
|
+
"source-run": {
|
|
2525
|
+
type: "string",
|
|
2526
|
+
description: "Optional run id propagated onto the queued proposal for traceability",
|
|
2527
|
+
},
|
|
2528
|
+
"exclude-feedback-from": {
|
|
2529
|
+
type: "string",
|
|
2530
|
+
description: "Comma-separated asset refs whose feedback events MUST be filtered out before the LLM input is built. Falls back to AKM_DISTILL_EXCLUDE_FEEDBACK_FROM when omitted.",
|
|
2531
|
+
},
|
|
2532
|
+
},
|
|
2533
|
+
async run({ args }) {
|
|
2534
|
+
await runWithJsonErrors(async () => {
|
|
2535
|
+
const excludeFlag = getHyphenatedArg(args, "exclude-feedback-from");
|
|
2536
|
+
const excludeEnv = process.env.AKM_DISTILL_EXCLUDE_FEEDBACK_FROM;
|
|
2537
|
+
// CLI flag takes precedence over the env var when both are present.
|
|
2538
|
+
const excludeRaw = excludeFlag ?? excludeEnv;
|
|
2539
|
+
const excludeFeedbackFromRefs = parseExcludeFeedbackFromRefs(excludeRaw);
|
|
2540
|
+
const result = await akmDistill({
|
|
2541
|
+
ref: args.ref,
|
|
2542
|
+
sourceRun: getHyphenatedArg(args, "source-run"),
|
|
2543
|
+
...(excludeFeedbackFromRefs.length > 0 ? { excludeFeedbackFromRefs } : {}),
|
|
2544
|
+
});
|
|
2545
|
+
output("distill", result);
|
|
2546
|
+
});
|
|
2547
|
+
},
|
|
2548
|
+
});
|
|
2549
|
+
/**
|
|
2550
|
+
* Parse a comma-separated list of asset refs (#267 — `--exclude-feedback-from`
|
|
2551
|
+
* and `AKM_DISTILL_EXCLUDE_FEEDBACK_FROM`). Each entry is validated against
|
|
2552
|
+
* the canonical `[origin//]type:name` grammar via `parseAssetRef`; an
|
|
2553
|
+
* invalid entry surfaces as a UsageError → exit 2.
|
|
2554
|
+
*/
|
|
2555
|
+
function parseExcludeFeedbackFromRefs(raw) {
|
|
2556
|
+
if (raw === undefined || raw.trim() === "")
|
|
2557
|
+
return [];
|
|
2558
|
+
const refs = raw
|
|
2559
|
+
.split(",")
|
|
2560
|
+
.map((part) => part.trim())
|
|
2561
|
+
.filter((part) => part.length > 0);
|
|
2562
|
+
for (const ref of refs) {
|
|
2563
|
+
try {
|
|
2564
|
+
parseAssetRef(ref);
|
|
2565
|
+
}
|
|
2566
|
+
catch (err) {
|
|
2567
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2568
|
+
throw new UsageError(`Invalid --exclude-feedback-from ref "${ref}": ${message}`, "INVALID_FLAG_VALUE", "Each ref must match `[origin//]type:name`, e.g. skill:deploy or team//memory:auth-tips.");
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
return refs;
|
|
2572
|
+
}
|
|
2573
|
+
function parseProposalStatus(raw) {
|
|
2574
|
+
if (raw === undefined)
|
|
2575
|
+
return undefined;
|
|
2576
|
+
const trimmed = raw.trim();
|
|
2577
|
+
if (!trimmed)
|
|
2578
|
+
return undefined;
|
|
2579
|
+
if (trimmed === "pending" || trimmed === "accepted" || trimmed === "rejected")
|
|
2580
|
+
return trimmed;
|
|
2581
|
+
throw new UsageError(`Invalid --status value: "${raw}". Expected one of: pending, accepted, rejected.`, "INVALID_FLAG_VALUE");
|
|
2582
|
+
}
|
|
2583
|
+
// ── reflect / propose (agent proposal-producers, #226) ──────────────────────
|
|
2584
|
+
const reflectCommand = defineCommand({
|
|
2585
|
+
meta: {
|
|
2586
|
+
name: "reflect",
|
|
2587
|
+
description: "Ask the configured agent CLI to review an asset (or recent feedback) and queue a revised proposal",
|
|
2588
|
+
},
|
|
2589
|
+
args: {
|
|
2590
|
+
ref: {
|
|
2591
|
+
type: "positional",
|
|
2592
|
+
description: "Asset ref (type:name) to reflect on. Optional — omit to reflect across recent feedback.",
|
|
2593
|
+
required: false,
|
|
2594
|
+
},
|
|
2595
|
+
task: { type: "string", description: "Optional task hint passed into the reflection prompt" },
|
|
2596
|
+
profile: { type: "string", description: "Override the agent profile (defaults to agent.default)" },
|
|
2597
|
+
"timeout-ms": { type: "string", description: "Override the agent CLI timeout in milliseconds" },
|
|
2598
|
+
},
|
|
2599
|
+
async run({ args }) {
|
|
2600
|
+
await runWithJsonErrors(async () => {
|
|
2601
|
+
const timeoutRaw = args["timeout-ms"];
|
|
2602
|
+
const timeoutMs = typeof timeoutRaw === "string" && timeoutRaw.trim() ? Number.parseInt(timeoutRaw, 10) : undefined;
|
|
2603
|
+
const result = await akmReflect({
|
|
2604
|
+
ref: typeof args.ref === "string" && args.ref.trim() ? args.ref : undefined,
|
|
2605
|
+
task: typeof args.task === "string" && args.task.trim() ? args.task : undefined,
|
|
2606
|
+
profile: typeof args.profile === "string" && args.profile.trim() ? args.profile : undefined,
|
|
2607
|
+
...(timeoutMs !== undefined && Number.isFinite(timeoutMs) ? { timeoutMs } : {}),
|
|
2608
|
+
});
|
|
2609
|
+
output("reflect", result);
|
|
2610
|
+
if (result.ok === false) {
|
|
2611
|
+
process.exit(EXIT_GENERAL);
|
|
2612
|
+
}
|
|
2613
|
+
});
|
|
2614
|
+
},
|
|
2615
|
+
});
|
|
2616
|
+
const proposeCommand = defineCommand({
|
|
2617
|
+
meta: {
|
|
2618
|
+
name: "propose",
|
|
2619
|
+
description: "Ask the configured agent CLI to author a brand-new asset and queue it as a proposal",
|
|
2620
|
+
},
|
|
2621
|
+
args: {
|
|
2622
|
+
// Optional in citty so run() is invoked when omitted; we re-validate
|
|
2623
|
+
// below to surface a structured UsageError (exit 2) instead of citty's
|
|
2624
|
+
// default help-banner exit-0.
|
|
2625
|
+
type: { type: "positional", description: "Asset type (skill, command, knowledge, lesson, ...)", required: false },
|
|
2626
|
+
name: { type: "positional", description: "Asset name (slug or path under the type dir)", required: false },
|
|
2627
|
+
task: { type: "string", description: "Task description for the agent (what should the asset do?)" },
|
|
2628
|
+
profile: { type: "string", description: "Override the agent profile (defaults to agent.default)" },
|
|
2629
|
+
"timeout-ms": { type: "string", description: "Override the agent CLI timeout in milliseconds" },
|
|
2630
|
+
},
|
|
2631
|
+
async run({ args }) {
|
|
2632
|
+
await runWithJsonErrors(async () => {
|
|
2633
|
+
// citty silently shows help and exits 0 when required positionals are
|
|
2634
|
+
// omitted. Re-validate explicitly so the exit code is 2 (USAGE) and a
|
|
2635
|
+
// structured JSON error reaches scripted callers.
|
|
2636
|
+
if (!args.type || !args.name || !args.task) {
|
|
2637
|
+
throw new UsageError("Usage: akm propose <type> <name> --task '<task>'.", "MISSING_REQUIRED_ARGUMENT", "Provide the asset type, name, and a --task description, e.g. `akm propose skill deploy --task 'Deploy a service'`.");
|
|
2638
|
+
}
|
|
2639
|
+
const timeoutRaw = args["timeout-ms"];
|
|
2640
|
+
const timeoutMs = typeof timeoutRaw === "string" && timeoutRaw.trim() ? Number.parseInt(timeoutRaw, 10) : undefined;
|
|
2641
|
+
const result = await akmPropose({
|
|
2642
|
+
type: String(args.type),
|
|
2643
|
+
name: String(args.name),
|
|
2644
|
+
task: String(args.task ?? ""),
|
|
2645
|
+
profile: typeof args.profile === "string" && args.profile.trim() ? args.profile : undefined,
|
|
2646
|
+
...(timeoutMs !== undefined && Number.isFinite(timeoutMs) ? { timeoutMs } : {}),
|
|
2647
|
+
});
|
|
2648
|
+
output("propose", result);
|
|
2649
|
+
if (result.ok === false) {
|
|
2650
|
+
process.exit(EXIT_GENERAL);
|
|
2651
|
+
}
|
|
2652
|
+
});
|
|
2653
|
+
},
|
|
2654
|
+
});
|
|
1994
2655
|
const main = defineCommand({
|
|
1995
2656
|
meta: {
|
|
1996
2657
|
name: "akm",
|
|
@@ -1998,9 +2659,14 @@ const main = defineCommand({
|
|
|
1998
2659
|
description: "Agent Kit Manager — search, show, and manage assets from your stash.",
|
|
1999
2660
|
},
|
|
2000
2661
|
args: {
|
|
2001
|
-
format: { type: "string", description: "Output format (json|jsonl|text|yaml)" },
|
|
2002
|
-
detail: { type: "string", description: "Detail level (brief|normal|full|summary|agent)" },
|
|
2662
|
+
format: { type: "string", description: "Output format (json|jsonl|text|yaml)", default: "json" },
|
|
2663
|
+
detail: { type: "string", description: "Detail level (brief|normal|full|summary|agent)", default: "brief" },
|
|
2003
2664
|
quiet: { type: "boolean", alias: "q", description: "Suppress stderr warnings", default: false },
|
|
2665
|
+
verbose: {
|
|
2666
|
+
type: "boolean",
|
|
2667
|
+
description: "Print per-spec diagnostics to stderr (also honours AKM_VERBOSE env var)",
|
|
2668
|
+
default: false,
|
|
2669
|
+
},
|
|
2004
2670
|
},
|
|
2005
2671
|
subCommands: {
|
|
2006
2672
|
setup: setupCommand,
|
|
@@ -2025,6 +2691,12 @@ const main = defineCommand({
|
|
|
2025
2691
|
enable: enableCommand,
|
|
2026
2692
|
disable: disableCommand,
|
|
2027
2693
|
feedback: feedbackCommand,
|
|
2694
|
+
history: historyCommand,
|
|
2695
|
+
events: eventsCommand,
|
|
2696
|
+
proposal: proposalCommand,
|
|
2697
|
+
reflect: reflectCommand,
|
|
2698
|
+
propose: proposeCommand,
|
|
2699
|
+
distill: distillCommand,
|
|
2028
2700
|
help: helpCommand,
|
|
2029
2701
|
hints: hintsCommand,
|
|
2030
2702
|
completions: completionsCommand,
|
|
@@ -2088,6 +2760,12 @@ async function runWithJsonErrors(fn) {
|
|
|
2088
2760
|
if (process.argv.includes("--quiet") || process.argv.includes("-q")) {
|
|
2089
2761
|
setQuiet(true);
|
|
2090
2762
|
}
|
|
2763
|
+
// Apply --verbose flag early so per-spec diagnostics (gated behind
|
|
2764
|
+
// `isVerbose()` in src/core/warn.ts) are restored. The `AKM_VERBOSE`
|
|
2765
|
+
// env var still wins regardless — see warn.ts for the precedence rule.
|
|
2766
|
+
if (process.argv.includes("--verbose")) {
|
|
2767
|
+
setVerbose(true);
|
|
2768
|
+
}
|
|
2091
2769
|
await fn();
|
|
2092
2770
|
}
|
|
2093
2771
|
catch (error) {
|