akm-cli 0.6.0 → 0.7.0-rc1
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} +672 -29
- package/dist/{commands → src/commands}/config-cli.js +5 -4
- package/dist/src/commands/distill.js +283 -0
- package/dist/src/commands/events.js +108 -0
- package/dist/src/commands/history.js +120 -0
- package/dist/{commands → src/commands}/installed-stashes.js +28 -2
- 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 +2 -1
- package/dist/{commands → src/commands}/remember.js +12 -0
- package/dist/{commands → src/commands}/search.js +74 -1
- package/dist/{commands → src/commands}/self-update.js +4 -3
- package/dist/{commands → src/commands}/show.js +67 -2
- 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 +175 -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 +119 -27
- 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 +73 -6
- package/dist/src/indexer/memory-inference.js +263 -0
- package/dist/{indexer → src/indexer}/metadata.js +114 -11
- 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 +221 -0
- package/dist/{integrations → src/integrations}/lockfile.js +0 -26
- package/dist/{llm → src/llm}/client.js +33 -2
- 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}/renderers.js +60 -1
- package/dist/src/output/shapes.js +516 -0
- package/dist/{output → src/output}/text.js +447 -4
- package/dist/{registry → src/registry}/build-index.js +14 -4
- package/dist/{registry → src/registry}/factory.js +0 -8
- package/dist/{registry → src/registry}/providers/static-index.js +3 -2
- package/dist/{registry → src/registry}/resolve.js +68 -2
- package/dist/{setup → src/setup}/setup.js +43 -5
- package/dist/{sources → src/sources}/providers/git.js +7 -15
- package/dist/{wiki → src/wiki}/wiki.js +9 -11
- 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 +995 -0
- package/dist/tests/bench/cleanup-sigint.test.js +83 -0
- package/dist/tests/bench/cleanup.js +203 -0
- package/dist/tests/bench/cleanup.test.js +166 -0
- package/dist/tests/bench/cli.js +683 -0
- package/dist/tests/bench/cli.test.js +177 -0
- package/dist/tests/bench/compare.test.js +556 -0
- package/dist/tests/bench/corpus.js +314 -0
- package/dist/tests/bench/corpus.test.js +258 -0
- package/dist/tests/bench/driver.js +346 -0
- package/dist/tests/bench/driver.test.js +443 -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 +580 -0
- package/dist/tests/bench/evolve.test.js +616 -0
- package/dist/tests/bench/failure-modes.test.js +300 -0
- package/dist/tests/bench/feedback-integrity.test.js +456 -0
- package/dist/tests/bench/leakage.test.js +125 -0
- package/dist/tests/bench/learning-curve.test.js +133 -0
- package/dist/tests/bench/metrics.js +2319 -0
- package/dist/tests/bench/metrics.test.js +1144 -0
- package/dist/tests/bench/no-os-tmpdir-invariant.test.js +43 -0
- package/dist/tests/bench/report.js +1821 -0
- package/dist/tests/bench/report.test.js +989 -0
- package/dist/tests/bench/runner.js +536 -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 +41 -0
- package/dist/tests/bench/trajectory.js +116 -0
- package/dist/tests/bench/trajectory.test.js +127 -0
- package/dist/tests/bench/verifier.js +109 -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 +358 -0
- package/dist/tests/bench/workflow-spec.test.js +363 -0
- package/dist/tests/bench/workflow-trace.js +438 -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 +203 -0
- package/dist/tests/commands/events.test.js +370 -0
- package/dist/tests/commands/history.test.js +223 -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 +544 -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 +1398 -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 +88 -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 +559 -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 +217 -0
- package/dist/tests/output-shapes-unit.test.js +476 -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 +378 -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 +204 -0
- package/dist/tests/registry-resolve.test.js +126 -0
- package/dist/tests/registry-search.test.js +723 -0
- package/dist/tests/remember-frontmatter.test.js +380 -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 +268 -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 +221 -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 +377 -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/{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-add.js +0 -0
- /package/dist/{commands → src/commands}/source-clone.js +0 -0
- /package/dist/{commands → src/commands}/source-manage.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}/matchers.js +0 -0
- /package/dist/{indexer → src/indexer}/search-fields.js +0 -0
- /package/dist/{indexer → src/indexer}/search-source.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/{integrations → src/integrations}/github.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/remote.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}/cli-hints.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}/runs.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,10 +165,19 @@ 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
|
},
|
|
@@ -165,7 +185,7 @@ const searchCommand = defineCommand({
|
|
|
165
185
|
await runWithJsonErrors(async () => {
|
|
166
186
|
const query = (args.query ?? "").trim();
|
|
167
187
|
if (!query) {
|
|
168
|
-
throw new UsageError('A search query is required. Usage: akm search "<query>" [--type <type>] [--limit <n>]', "MISSING_REQUIRED_ARGUMENT");
|
|
188
|
+
throw new UsageError('A search query is required. Usage: akm search "<query>" [--type <type>] [--limit <n>]', "MISSING_REQUIRED_ARGUMENT", "Provide a query string. Filter by type with --type skill|command|...; limit results with --limit N.");
|
|
169
189
|
}
|
|
170
190
|
const type = args.type;
|
|
171
191
|
const limitRaw = args.limit ? parseInt(args.limit, 10) : undefined;
|
|
@@ -174,7 +194,12 @@ const searchCommand = defineCommand({
|
|
|
174
194
|
}
|
|
175
195
|
const limit = limitRaw;
|
|
176
196
|
const source = parseSearchSource(args.source);
|
|
177
|
-
|
|
197
|
+
// Repeatable; citty exposes only the last `--filter` value, so read all
|
|
198
|
+
// occurrences directly from argv (same pattern as `--tag`).
|
|
199
|
+
const filterTokens = parseAllFlagValues("--filter");
|
|
200
|
+
const filters = parseScopeFilterFlags(filterTokens, "--filter");
|
|
201
|
+
const includeProposed = args["include-proposed"] === true;
|
|
202
|
+
const result = await akmSearch({ query, type, limit, source, filters, includeProposed });
|
|
178
203
|
output("search", result);
|
|
179
204
|
});
|
|
180
205
|
},
|
|
@@ -182,16 +207,22 @@ const searchCommand = defineCommand({
|
|
|
182
207
|
const curateCommand = defineCommand({
|
|
183
208
|
meta: { name: "curate", description: "Curate the best matching assets for a task or prompt" },
|
|
184
209
|
args: {
|
|
185
|
-
|
|
210
|
+
// Optional in citty so run() is invoked when omitted; we re-validate
|
|
211
|
+
// below to surface a structured UsageError (exit 2) instead of citty's
|
|
212
|
+
// default help-banner exit-0.
|
|
213
|
+
query: { type: "positional", description: "Task or prompt to curate assets for", required: false },
|
|
186
214
|
type: {
|
|
187
215
|
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.",
|
|
216
|
+
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
217
|
},
|
|
190
218
|
limit: { type: "string", description: "Maximum number of curated results", default: "4" },
|
|
191
219
|
source: { type: "string", description: "Search source (stash|registry|both)", default: "stash" },
|
|
192
220
|
},
|
|
193
221
|
async run({ args }) {
|
|
194
222
|
await runWithJsonErrors(async () => {
|
|
223
|
+
if (!args.query || !String(args.query).trim()) {
|
|
224
|
+
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"`.');
|
|
225
|
+
}
|
|
195
226
|
const type = args.type;
|
|
196
227
|
const limitRaw = args.limit ? parseInt(args.limit, 10) : undefined;
|
|
197
228
|
if (limitRaw !== undefined && Number.isNaN(limitRaw)) {
|
|
@@ -234,14 +265,24 @@ const addCommand = defineCommand({
|
|
|
234
265
|
},
|
|
235
266
|
"max-pages": { type: "string", description: "Maximum pages to crawl for website sources (default: 50)" },
|
|
236
267
|
"max-depth": { type: "string", description: "Maximum crawl depth for website sources (default: 3)" },
|
|
268
|
+
"allow-insecure": {
|
|
269
|
+
type: "boolean",
|
|
270
|
+
description: "Allow a plain HTTP source URL (otherwise rejected for non-localhost hosts)",
|
|
271
|
+
default: false,
|
|
272
|
+
},
|
|
237
273
|
},
|
|
238
274
|
async run({ args }) {
|
|
239
275
|
await runWithJsonErrors(async () => {
|
|
240
276
|
const ref = args.ref.trim();
|
|
277
|
+
const allowInsecure = getHyphenatedBoolean(args, "allow-insecure");
|
|
241
278
|
// URL with --provider → stash source (remote or git provider)
|
|
242
279
|
if (args.provider) {
|
|
243
280
|
if (shouldWarnOnPlainHttp(ref)) {
|
|
244
|
-
|
|
281
|
+
if (!allowInsecure) {
|
|
282
|
+
throw new UsageError("Source URL uses plain HTTP (not HTTPS). An on-path attacker could substitute a malicious payload. " +
|
|
283
|
+
"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.");
|
|
284
|
+
}
|
|
285
|
+
warn("Warning: source URL uses plain HTTP (not HTTPS). --allow-insecure was set; an on-path attacker could substitute a malicious payload.");
|
|
245
286
|
}
|
|
246
287
|
let parsedOptions;
|
|
247
288
|
if (args.options) {
|
|
@@ -265,11 +306,19 @@ const addCommand = defineCommand({
|
|
|
265
306
|
options: parsedOptions,
|
|
266
307
|
writable: args.writable,
|
|
267
308
|
});
|
|
309
|
+
appendEvent({
|
|
310
|
+
eventType: "add",
|
|
311
|
+
metadata: { target: ref, provider: args.provider, name: args.name ?? null, writable: args.writable === true },
|
|
312
|
+
});
|
|
268
313
|
output("add", result);
|
|
269
314
|
return;
|
|
270
315
|
}
|
|
271
316
|
if (shouldWarnOnPlainHttp(ref)) {
|
|
272
|
-
|
|
317
|
+
if (!allowInsecure) {
|
|
318
|
+
throw new UsageError("Source URL uses plain HTTP (not HTTPS). An on-path attacker could substitute a malicious payload. " +
|
|
319
|
+
"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.");
|
|
320
|
+
}
|
|
321
|
+
warn("Warning: source URL uses plain HTTP (not HTTPS). --allow-insecure was set; an on-path attacker could substitute a malicious payload.");
|
|
273
322
|
}
|
|
274
323
|
const websiteOptions = buildWebsiteOptions(args);
|
|
275
324
|
if (args.type === "wiki") {
|
|
@@ -281,6 +330,10 @@ const addCommand = defineCommand({
|
|
|
281
330
|
trustThisInstall: args.trust,
|
|
282
331
|
writable: args.writable,
|
|
283
332
|
});
|
|
333
|
+
appendEvent({
|
|
334
|
+
eventType: "add",
|
|
335
|
+
metadata: { target: ref, type: "wiki", name: args.name ?? null, writable: args.writable === true },
|
|
336
|
+
});
|
|
284
337
|
output("add", result);
|
|
285
338
|
return;
|
|
286
339
|
}
|
|
@@ -292,6 +345,15 @@ const addCommand = defineCommand({
|
|
|
292
345
|
trustThisInstall: args.trust,
|
|
293
346
|
writable: args.writable,
|
|
294
347
|
});
|
|
348
|
+
appendEvent({
|
|
349
|
+
eventType: "add",
|
|
350
|
+
metadata: {
|
|
351
|
+
target: ref,
|
|
352
|
+
name: args.name ?? null,
|
|
353
|
+
overrideType: args.type ?? null,
|
|
354
|
+
writable: args.writable === true,
|
|
355
|
+
},
|
|
356
|
+
});
|
|
295
357
|
output("add", result);
|
|
296
358
|
});
|
|
297
359
|
},
|
|
@@ -353,6 +415,14 @@ const removeCommand = defineCommand({
|
|
|
353
415
|
async run({ args }) {
|
|
354
416
|
await runWithJsonErrors(async () => {
|
|
355
417
|
const result = await akmRemove({ target: args.target });
|
|
418
|
+
appendEvent({
|
|
419
|
+
eventType: "remove",
|
|
420
|
+
metadata: {
|
|
421
|
+
target: args.target,
|
|
422
|
+
ref: typeof result.removed?.ref === "string" ? result.removed.ref : null,
|
|
423
|
+
id: typeof result.removed?.id === "string" ? result.removed.id : null,
|
|
424
|
+
},
|
|
425
|
+
});
|
|
356
426
|
output("remove", result);
|
|
357
427
|
});
|
|
358
428
|
},
|
|
@@ -367,6 +437,17 @@ const updateCommand = defineCommand({
|
|
|
367
437
|
async run({ args }) {
|
|
368
438
|
await runWithJsonErrors(async () => {
|
|
369
439
|
const result = await akmUpdate({ target: args.target, all: args.all, force: args.force });
|
|
440
|
+
appendEvent({
|
|
441
|
+
eventType: "update",
|
|
442
|
+
metadata: {
|
|
443
|
+
target: args.target ?? null,
|
|
444
|
+
all: args.all === true,
|
|
445
|
+
force: args.force === true,
|
|
446
|
+
processed: Array.isArray(result.processed)
|
|
447
|
+
? result.processed.length
|
|
448
|
+
: 0,
|
|
449
|
+
},
|
|
450
|
+
});
|
|
370
451
|
output("update", result);
|
|
371
452
|
});
|
|
372
453
|
},
|
|
@@ -407,9 +488,17 @@ const showCommand = defineCommand({
|
|
|
407
488
|
description: "Show a stash asset by ref (e.g. akm show knowledge:guide.md toc, akm show knowledge:guide.md section 'Auth')",
|
|
408
489
|
},
|
|
409
490
|
args: {
|
|
410
|
-
ref: {
|
|
491
|
+
ref: {
|
|
492
|
+
type: "positional",
|
|
493
|
+
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"`.',
|
|
494
|
+
required: true,
|
|
495
|
+
},
|
|
411
496
|
format: { type: "string", description: "Output format (json|jsonl|text|yaml)" },
|
|
412
497
|
detail: { type: "string", description: "Detail level (brief|normal|full|summary|agent)" },
|
|
498
|
+
scope: {
|
|
499
|
+
type: "string",
|
|
500
|
+
description: "Scope filter (repeatable): --scope user=<id> --scope agent=<id> --scope run=<id> --scope channel=<name>. Narrows resolution to assets whose frontmatter scope matches.",
|
|
501
|
+
},
|
|
413
502
|
},
|
|
414
503
|
async run({ args }) {
|
|
415
504
|
await runWithJsonErrors(async () => {
|
|
@@ -445,8 +534,13 @@ const showCommand = defineCommand({
|
|
|
445
534
|
}
|
|
446
535
|
}
|
|
447
536
|
const cliDetail = getOutputMode().detail;
|
|
448
|
-
const
|
|
449
|
-
const
|
|
537
|
+
const explicitDetail = parseFlagValue(process.argv, "--detail");
|
|
538
|
+
const showDetail = explicitDetail === "brief" ? "brief" : cliDetail === "summary" ? "summary" : undefined;
|
|
539
|
+
// `--scope` is repeatable — citty only exposes the last value, so read
|
|
540
|
+
// every occurrence directly from argv (same pattern as `--filter`).
|
|
541
|
+
const scopeTokens = parseAllFlagValues("--scope");
|
|
542
|
+
const scope = parseScopeFilterFlags(scopeTokens, "--scope");
|
|
543
|
+
const result = await akmShowUnified({ ref: args.ref, view, detail: showDetail, scope });
|
|
450
544
|
output("show", result);
|
|
451
545
|
});
|
|
452
546
|
},
|
|
@@ -587,6 +681,14 @@ const saveCommand = defineCommand({
|
|
|
587
681
|
writable = cfg.writable === true ? true : undefined;
|
|
588
682
|
}
|
|
589
683
|
const result = saveGitStash(effectiveName, args.message, writable);
|
|
684
|
+
appendEvent({
|
|
685
|
+
eventType: "save",
|
|
686
|
+
metadata: {
|
|
687
|
+
name: effectiveName ?? null,
|
|
688
|
+
message: args.message ?? null,
|
|
689
|
+
ok: result.ok !== false,
|
|
690
|
+
},
|
|
691
|
+
});
|
|
590
692
|
output("save", result);
|
|
591
693
|
});
|
|
592
694
|
},
|
|
@@ -793,16 +895,19 @@ const feedbackCommand = defineCommand({
|
|
|
793
895
|
description: "Record positive or negative feedback for any indexed stash asset",
|
|
794
896
|
},
|
|
795
897
|
args: {
|
|
796
|
-
|
|
898
|
+
// Optional in citty so run() is invoked even when omitted; we re-validate
|
|
899
|
+
// and throw a structured UsageError below so exit code is 2 (USAGE) rather
|
|
900
|
+
// than citty's default 0 (help banner).
|
|
901
|
+
ref: { type: "positional", description: "Asset ref (type:name)", required: false },
|
|
797
902
|
positive: { type: "boolean", description: "Record positive feedback", default: false },
|
|
798
903
|
negative: { type: "boolean", description: "Record negative feedback", default: false },
|
|
799
904
|
note: { type: "string", description: "Optional note to attach to the feedback" },
|
|
800
905
|
},
|
|
801
906
|
run({ args }) {
|
|
802
907
|
return runWithJsonErrors(() => {
|
|
803
|
-
const ref = args.ref.trim();
|
|
908
|
+
const ref = (args.ref ?? "").trim();
|
|
804
909
|
if (!ref) {
|
|
805
|
-
throw new UsageError("Asset ref is required. Usage: akm feedback <ref> --positive|--negative");
|
|
910
|
+
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.");
|
|
806
911
|
}
|
|
807
912
|
parseAssetRef(ref);
|
|
808
913
|
if (args.positive && args.negative) {
|
|
@@ -830,10 +935,35 @@ const feedbackCommand = defineCommand({
|
|
|
830
935
|
finally {
|
|
831
936
|
closeDatabase(db);
|
|
832
937
|
}
|
|
938
|
+
appendEvent({
|
|
939
|
+
eventType: "feedback",
|
|
940
|
+
ref,
|
|
941
|
+
metadata: { signal, ...(args.note ? { note: args.note } : {}) },
|
|
942
|
+
});
|
|
833
943
|
output("feedback", { ok: true, ref, signal, note: args.note ?? null });
|
|
834
944
|
});
|
|
835
945
|
},
|
|
836
946
|
});
|
|
947
|
+
const historyCommand = defineCommand({
|
|
948
|
+
meta: {
|
|
949
|
+
name: "history",
|
|
950
|
+
description: "Show mutation/usage history for a single asset (--ref) or stash-wide. Backed by the internal usage_events log.",
|
|
951
|
+
},
|
|
952
|
+
args: {
|
|
953
|
+
ref: { type: "string", description: "Asset ref (type:name). Omit for stash-wide history." },
|
|
954
|
+
since: { type: "string", description: "ISO timestamp or epoch ms — only events on/after this time" },
|
|
955
|
+
format: { type: "string", description: "Output format (json|jsonl|text|yaml)" },
|
|
956
|
+
},
|
|
957
|
+
run({ args }) {
|
|
958
|
+
return runWithJsonErrors(async () => {
|
|
959
|
+
const result = await akmHistory({
|
|
960
|
+
ref: args.ref,
|
|
961
|
+
since: args.since,
|
|
962
|
+
});
|
|
963
|
+
output("history", result);
|
|
964
|
+
});
|
|
965
|
+
},
|
|
966
|
+
});
|
|
837
967
|
function normalizeMarkdownAssetName(name, fallback) {
|
|
838
968
|
const trimmed = (name ?? fallback)
|
|
839
969
|
.trim()
|
|
@@ -943,11 +1073,40 @@ const workflowNextCommand = defineCommand({
|
|
|
943
1073
|
async run({ args }) {
|
|
944
1074
|
await runWithJsonErrors(async () => {
|
|
945
1075
|
const parsedParams = args.params ? parseWorkflowJsonObject(args.params, "--params") : undefined;
|
|
1076
|
+
// If the target looks like a UUID-style run id (no `:` and matches the
|
|
1077
|
+
// run-id shape), short-circuit with a structured WORKFLOW_NOT_FOUND
|
|
1078
|
+
// error before parseAssetRef gets to throw an unhelpful ref-parse error.
|
|
1079
|
+
if (looksLikeWorkflowRunId(args.target)) {
|
|
1080
|
+
const { listWorkflowRuns: listRuns } = await import("./workflows/runs.js");
|
|
1081
|
+
const { runs: existingRuns } = listRuns({});
|
|
1082
|
+
if (!existingRuns.some((r) => r.id === args.target)) {
|
|
1083
|
+
throw new NotFoundError(`Workflow run "${args.target}" not found.`, "WORKFLOW_NOT_FOUND", "Run `akm workflow list --active` to see runs.");
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
946
1086
|
const result = await getNextWorkflowStep(args.target, parsedParams);
|
|
947
1087
|
output("workflow-next", result);
|
|
948
1088
|
});
|
|
949
1089
|
},
|
|
950
1090
|
});
|
|
1091
|
+
/**
|
|
1092
|
+
* Heuristic: a workflow run id is a UUID-shaped or hex-id-shaped string with
|
|
1093
|
+
* no `:` separator (refs always contain a colon: `workflow:<name>` or
|
|
1094
|
+
* `<origin>//workflow:<name>`). When this matches we can give a much better
|
|
1095
|
+
* error than parseAssetRef's "Invalid asset type" failure.
|
|
1096
|
+
*/
|
|
1097
|
+
function looksLikeWorkflowRunId(target) {
|
|
1098
|
+
if (target.includes(":"))
|
|
1099
|
+
return false;
|
|
1100
|
+
if (target.includes("/"))
|
|
1101
|
+
return false;
|
|
1102
|
+
// UUID v4-ish: 8-4-4-4-12 hex digits separated by dashes.
|
|
1103
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(target))
|
|
1104
|
+
return true;
|
|
1105
|
+
// Bare hex/alphanumeric run ids of >=8 chars (covers shortened ids).
|
|
1106
|
+
if (/^[0-9a-z][0-9a-z_-]{7,}$/i.test(target) && /[0-9]/.test(target))
|
|
1107
|
+
return true;
|
|
1108
|
+
return false;
|
|
1109
|
+
}
|
|
951
1110
|
const workflowCompleteCommand = defineCommand({
|
|
952
1111
|
meta: {
|
|
953
1112
|
name: "complete",
|
|
@@ -1219,6 +1378,22 @@ const rememberCommand = defineCommand({
|
|
|
1219
1378
|
type: "string",
|
|
1220
1379
|
description: "Override the write destination. Accepts a source name from your config; falls back to defaultWriteTarget then the working stash.",
|
|
1221
1380
|
},
|
|
1381
|
+
user: {
|
|
1382
|
+
type: "string",
|
|
1383
|
+
description: "Scope this memory to a user id (persisted as `scope_user` frontmatter)",
|
|
1384
|
+
},
|
|
1385
|
+
agent: {
|
|
1386
|
+
type: "string",
|
|
1387
|
+
description: "Scope this memory to an agent id (persisted as `scope_agent` frontmatter)",
|
|
1388
|
+
},
|
|
1389
|
+
run: {
|
|
1390
|
+
type: "string",
|
|
1391
|
+
description: "Scope this memory to a run id (persisted as `scope_run` frontmatter)",
|
|
1392
|
+
},
|
|
1393
|
+
channel: {
|
|
1394
|
+
type: "string",
|
|
1395
|
+
description: "Scope this memory to a channel name (persisted as `scope_channel` frontmatter)",
|
|
1396
|
+
},
|
|
1222
1397
|
},
|
|
1223
1398
|
async run({ args }) {
|
|
1224
1399
|
return runWithJsonErrors(async () => {
|
|
@@ -1227,7 +1402,21 @@ const rememberCommand = defineCommand({
|
|
|
1227
1402
|
// Collect all --tag occurrences directly from process.argv because citty
|
|
1228
1403
|
// only exposes the last value for repeated string flags.
|
|
1229
1404
|
const rawTags = parseAllFlagValues("--tag");
|
|
1230
|
-
|
|
1405
|
+
// Collect scope flags. Scope alone counts as structured metadata so we
|
|
1406
|
+
// emit frontmatter, but it does NOT trigger the "tags required" check —
|
|
1407
|
+
// memory + scope (no tags) is a valid combination for multi-tenant use.
|
|
1408
|
+
const scopeFields = {};
|
|
1409
|
+
if (typeof args.user === "string" && args.user.trim())
|
|
1410
|
+
scopeFields.user = args.user.trim();
|
|
1411
|
+
if (typeof args.agent === "string" && args.agent.trim())
|
|
1412
|
+
scopeFields.agent = args.agent.trim();
|
|
1413
|
+
if (typeof args.run === "string" && args.run.trim())
|
|
1414
|
+
scopeFields.run = args.run.trim();
|
|
1415
|
+
if (typeof args.channel === "string" && args.channel.trim())
|
|
1416
|
+
scopeFields.channel = args.channel.trim();
|
|
1417
|
+
const hasScope = Object.keys(scopeFields).length > 0;
|
|
1418
|
+
const hasTagRequiringArgs = rawTags.length > 0 || !!args.expires || !!args.source || !!args.description || args.auto || args.enrich;
|
|
1419
|
+
const hasStructuredArgs = hasTagRequiringArgs || hasScope;
|
|
1231
1420
|
if (!hasStructuredArgs) {
|
|
1232
1421
|
const result = await writeMarkdownAsset({
|
|
1233
1422
|
type: "memory",
|
|
@@ -1237,6 +1426,11 @@ const rememberCommand = defineCommand({
|
|
|
1237
1426
|
force: args.force,
|
|
1238
1427
|
target: args.target,
|
|
1239
1428
|
});
|
|
1429
|
+
appendEvent({
|
|
1430
|
+
eventType: "remember",
|
|
1431
|
+
ref: result.ref,
|
|
1432
|
+
metadata: { path: result.path, force: args.force === true },
|
|
1433
|
+
});
|
|
1240
1434
|
output("remember", { ok: true, ...result });
|
|
1241
1435
|
return;
|
|
1242
1436
|
}
|
|
@@ -1282,8 +1476,12 @@ const rememberCommand = defineCommand({
|
|
|
1282
1476
|
observed_at = enriched.observed_at;
|
|
1283
1477
|
}
|
|
1284
1478
|
// ── Required-field check (before any write) ───────────────────────────
|
|
1479
|
+
// Tags remain required when the user opted into a tag-producing mode
|
|
1480
|
+
// (--tag / --auto / --enrich / --description / --source / --expires).
|
|
1481
|
+
// Scope-only writes (`akm remember "..." --user u1`) skip this check —
|
|
1482
|
+
// scope is independent metadata and a memory with only scope is valid.
|
|
1285
1483
|
const missing = [];
|
|
1286
|
-
if (tags.length === 0)
|
|
1484
|
+
if (hasTagRequiringArgs && tags.length === 0)
|
|
1287
1485
|
missing.push("tags");
|
|
1288
1486
|
if (missing.length > 0) {
|
|
1289
1487
|
throw new UsageError(`Memory is missing required frontmatter field(s): ${missing.join(", ")}. ` +
|
|
@@ -1297,6 +1495,7 @@ const rememberCommand = defineCommand({
|
|
|
1297
1495
|
observed_at,
|
|
1298
1496
|
expires,
|
|
1299
1497
|
subjective,
|
|
1498
|
+
...(hasScope ? { scope: scopeFields } : {}),
|
|
1300
1499
|
});
|
|
1301
1500
|
const contentWithFrontmatter = `${frontmatterBlock}\n${body}`;
|
|
1302
1501
|
const result = await writeMarkdownAsset({
|
|
@@ -1307,6 +1506,18 @@ const rememberCommand = defineCommand({
|
|
|
1307
1506
|
force: args.force,
|
|
1308
1507
|
target: args.target,
|
|
1309
1508
|
});
|
|
1509
|
+
appendEvent({
|
|
1510
|
+
eventType: "remember",
|
|
1511
|
+
ref: result.ref,
|
|
1512
|
+
metadata: {
|
|
1513
|
+
path: result.path,
|
|
1514
|
+
force: args.force === true,
|
|
1515
|
+
tagCount: tags.length,
|
|
1516
|
+
enriched: args.enrich === true,
|
|
1517
|
+
auto: args.auto === true,
|
|
1518
|
+
...(hasScope ? { scope: scopeFields } : {}),
|
|
1519
|
+
},
|
|
1520
|
+
});
|
|
1310
1521
|
output("remember", { ok: true, ...result });
|
|
1311
1522
|
});
|
|
1312
1523
|
},
|
|
@@ -1348,6 +1559,11 @@ const importKnowledgeCommand = defineCommand({
|
|
|
1348
1559
|
force: args.force,
|
|
1349
1560
|
target: args.target,
|
|
1350
1561
|
});
|
|
1562
|
+
appendEvent({
|
|
1563
|
+
eventType: "import",
|
|
1564
|
+
ref: result.ref,
|
|
1565
|
+
metadata: { source: args.source, path: result.path, force: args.force === true },
|
|
1566
|
+
});
|
|
1351
1567
|
output("import", { ok: true, source: args.source, ...result });
|
|
1352
1568
|
});
|
|
1353
1569
|
},
|
|
@@ -1358,7 +1574,11 @@ const hintsCommand = defineCommand({
|
|
|
1358
1574
|
description: "Print agent instructions on how to use akm, use --detail full for a complete guide",
|
|
1359
1575
|
},
|
|
1360
1576
|
args: {
|
|
1361
|
-
detail: {
|
|
1577
|
+
detail: {
|
|
1578
|
+
type: "string",
|
|
1579
|
+
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.",
|
|
1580
|
+
default: "normal",
|
|
1581
|
+
},
|
|
1362
1582
|
},
|
|
1363
1583
|
run({ args }) {
|
|
1364
1584
|
if (args.detail !== "normal" && args.detail !== "full") {
|
|
@@ -1379,14 +1599,22 @@ const helpCommand = defineCommand({
|
|
|
1379
1599
|
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.",
|
|
1380
1600
|
},
|
|
1381
1601
|
args: {
|
|
1602
|
+
// Optional in citty so run() is invoked even when omitted; we
|
|
1603
|
+
// re-validate below to surface a structured UsageError (exit 2)
|
|
1604
|
+
// instead of citty's default help-banner exit-0.
|
|
1382
1605
|
version: {
|
|
1383
1606
|
type: "positional",
|
|
1384
1607
|
description: "Version to review (for example 0.6.0, v0.6.0, 0.6.0-rc1, or latest)",
|
|
1385
|
-
required:
|
|
1608
|
+
required: false,
|
|
1386
1609
|
},
|
|
1387
1610
|
},
|
|
1388
1611
|
run({ args }) {
|
|
1389
|
-
|
|
1612
|
+
return runWithJsonErrors(() => {
|
|
1613
|
+
if (!args.version || !String(args.version).trim()) {
|
|
1614
|
+
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`.");
|
|
1615
|
+
}
|
|
1616
|
+
process.stdout.write(renderMigrationHelp(args.version));
|
|
1617
|
+
});
|
|
1390
1618
|
},
|
|
1391
1619
|
}),
|
|
1392
1620
|
},
|
|
@@ -1427,9 +1655,6 @@ function normalizeToggleTarget(target) {
|
|
|
1427
1655
|
const normalized = target.trim().toLowerCase();
|
|
1428
1656
|
if (normalized === "skills.sh" || normalized === "skills-sh")
|
|
1429
1657
|
return "skills.sh";
|
|
1430
|
-
if (normalized === "context-hub") {
|
|
1431
|
-
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.');
|
|
1432
|
-
}
|
|
1433
1658
|
throw new UsageError(`Unsupported target "${target}". Supported targets: skills.sh`);
|
|
1434
1659
|
}
|
|
1435
1660
|
function toggleSkillsShRegistry(enabled) {
|
|
@@ -1535,6 +1760,53 @@ function listVaultsRecursive(listKeysFn) {
|
|
|
1535
1760
|
walk(vaultsDir);
|
|
1536
1761
|
return result;
|
|
1537
1762
|
}
|
|
1763
|
+
function wasRefMisparsedAsFlagValue(ref, flag, flagValue) {
|
|
1764
|
+
const argv = process.argv.slice(2);
|
|
1765
|
+
const vaultIndex = argv.indexOf("vault");
|
|
1766
|
+
const listIndex = vaultIndex >= 0 ? argv.indexOf("list", vaultIndex + 1) : -1;
|
|
1767
|
+
const tokens = listIndex >= 0 ? argv.slice(listIndex + 1) : argv;
|
|
1768
|
+
let flagIndex = -1;
|
|
1769
|
+
let flagConsumesNextToken = false;
|
|
1770
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
1771
|
+
const token = tokens[i];
|
|
1772
|
+
if (token === flag) {
|
|
1773
|
+
flagIndex = i;
|
|
1774
|
+
flagConsumesNextToken = true;
|
|
1775
|
+
break;
|
|
1776
|
+
}
|
|
1777
|
+
if (token === `${flag}=${flagValue}`) {
|
|
1778
|
+
flagIndex = i;
|
|
1779
|
+
break;
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
if (flagIndex === -1)
|
|
1783
|
+
return false;
|
|
1784
|
+
// If the same token appeared before the flag, the user explicitly passed it
|
|
1785
|
+
// as the positional ref and it was not consumed by the output flag.
|
|
1786
|
+
if (tokens.slice(0, flagIndex).includes(ref))
|
|
1787
|
+
return false;
|
|
1788
|
+
// Skip past either `--flag value` (2 tokens) or `--flag=value` (1 token)
|
|
1789
|
+
// before checking whether the ref appears elsewhere as a real positional.
|
|
1790
|
+
const TOKENS_AFTER_SPACE_FLAG = 2;
|
|
1791
|
+
const TOKENS_AFTER_EQUALS_FLAG = 1;
|
|
1792
|
+
const firstTokenAfterFlag = flagIndex + (flagConsumesNextToken ? TOKENS_AFTER_SPACE_FLAG : TOKENS_AFTER_EQUALS_FLAG);
|
|
1793
|
+
if (tokens.slice(firstTokenAfterFlag).includes(ref))
|
|
1794
|
+
return false;
|
|
1795
|
+
return true;
|
|
1796
|
+
}
|
|
1797
|
+
function resolveVaultListRef(ref) {
|
|
1798
|
+
if (ref === undefined)
|
|
1799
|
+
return undefined;
|
|
1800
|
+
const parsedFormat = parseFlagValue(process.argv, "--format");
|
|
1801
|
+
if (parsedFormat !== undefined && ref === parsedFormat && wasRefMisparsedAsFlagValue(ref, "--format", parsedFormat)) {
|
|
1802
|
+
return undefined;
|
|
1803
|
+
}
|
|
1804
|
+
const parsedDetail = parseFlagValue(process.argv, "--detail");
|
|
1805
|
+
if (parsedDetail !== undefined && ref === parsedDetail && wasRefMisparsedAsFlagValue(ref, "--detail", parsedDetail)) {
|
|
1806
|
+
return undefined;
|
|
1807
|
+
}
|
|
1808
|
+
return ref;
|
|
1809
|
+
}
|
|
1538
1810
|
const vaultListCommand = defineCommand({
|
|
1539
1811
|
meta: { name: "list", description: "List vaults, or list keys (no values) inside one vault" },
|
|
1540
1812
|
args: {
|
|
@@ -1543,8 +1815,9 @@ const vaultListCommand = defineCommand({
|
|
|
1543
1815
|
run({ args }) {
|
|
1544
1816
|
return runWithJsonErrors(async () => {
|
|
1545
1817
|
const { listKeys, listEntries } = await import("./commands/vault.js");
|
|
1546
|
-
|
|
1547
|
-
|
|
1818
|
+
const effectiveRef = resolveVaultListRef(args.ref);
|
|
1819
|
+
if (effectiveRef) {
|
|
1820
|
+
const { name, absPath } = resolveVaultPath(effectiveRef);
|
|
1548
1821
|
if (!fs.existsSync(absPath)) {
|
|
1549
1822
|
throw new NotFoundError(`Vault not found: vault:${name}`);
|
|
1550
1823
|
}
|
|
@@ -1649,6 +1922,22 @@ const vaultLoadCommand = defineCommand({
|
|
|
1649
1922
|
// metacharacters like $, backticks, or $(...).
|
|
1650
1923
|
const script = buildShellExportScript(absPath);
|
|
1651
1924
|
// Write to a mode-0600 temp file the shell can source.
|
|
1925
|
+
//
|
|
1926
|
+
// INTENTIONAL: this site uses `os.tmpdir()` (i.e. `/tmp` on Unix)
|
|
1927
|
+
// rather than `${getCacheDir()}/vault/`. The temp file is written
|
|
1928
|
+
// mode-0600, sourced by the parent shell via `eval`, and immediately
|
|
1929
|
+
// `rm -f`'d on the same line of the emitted snippet. `/tmp` is the
|
|
1930
|
+
// conventional location for short-lived shell-eval scratch files and
|
|
1931
|
+
// benefits from tmp-cleanup-on-reboot semantics, which operators
|
|
1932
|
+
// expect for ephemeral secret material. Moving to `~/.cache/akm/`
|
|
1933
|
+
// would surprise those operators and also persist the file across
|
|
1934
|
+
// reboots if the eval is interrupted before the inline `rm -f` runs.
|
|
1935
|
+
// The bench/registry-build rationale (#276/#284) — orphan dirs
|
|
1936
|
+
// accumulating under `/tmp` from long-running builds — does not
|
|
1937
|
+
// apply here: the file is single-shot, a few hundred bytes, and
|
|
1938
|
+
// removed by the same shell command that sources it.
|
|
1939
|
+
// Regression test: tests/vault-load-error.test.ts verifies the
|
|
1940
|
+
// emitted snippet contains both `. <path>` and `rm -f <path>`.
|
|
1652
1941
|
const tmpPath = path.join(os.tmpdir(), `akm-vault-${crypto.randomBytes(12).toString("hex")}.sh`);
|
|
1653
1942
|
fs.writeFileSync(tmpPath, script, { mode: 0o600, encoding: "utf8" });
|
|
1654
1943
|
try {
|
|
@@ -1942,6 +2231,343 @@ const wikiCommand = defineCommand({
|
|
|
1942
2231
|
});
|
|
1943
2232
|
},
|
|
1944
2233
|
});
|
|
2234
|
+
// ── `akm events` ────────────────────────────────────────────────────────────
|
|
2235
|
+
// Append-only events stream surface (#204). `list` reads `events.jsonl`
|
|
2236
|
+
// with optional --since/--type/--ref filters; `tail` follows the file via
|
|
2237
|
+
// a polling loop and prints each event as a single JSONL line.
|
|
2238
|
+
const eventsListCommand = defineCommand({
|
|
2239
|
+
meta: { name: "list", description: "List events from the append-only events.jsonl stream" },
|
|
2240
|
+
args: {
|
|
2241
|
+
since: {
|
|
2242
|
+
type: "string",
|
|
2243
|
+
description: "ISO timestamp / epoch ms, OR `@offset:<bytes>` for a durable byte-cursor (resume across processes)",
|
|
2244
|
+
},
|
|
2245
|
+
type: { type: "string", description: "Filter by event type (add, remove, remember, feedback, ...)" },
|
|
2246
|
+
ref: { type: "string", description: "Filter by asset ref (type:name)" },
|
|
2247
|
+
},
|
|
2248
|
+
run({ args }) {
|
|
2249
|
+
return runWithJsonErrors(() => {
|
|
2250
|
+
const result = akmEventsList({ since: args.since, type: args.type, ref: args.ref });
|
|
2251
|
+
output("events-list", result);
|
|
2252
|
+
});
|
|
2253
|
+
},
|
|
2254
|
+
});
|
|
2255
|
+
const eventsTailCommand = defineCommand({
|
|
2256
|
+
meta: { name: "tail", description: "Follow the append-only events.jsonl stream (polling)" },
|
|
2257
|
+
args: {
|
|
2258
|
+
since: {
|
|
2259
|
+
type: "string",
|
|
2260
|
+
description: "ISO timestamp / epoch ms, OR `@offset:<bytes>` for a durable byte-cursor (resume across processes)",
|
|
2261
|
+
},
|
|
2262
|
+
type: { type: "string", description: "Filter by event type" },
|
|
2263
|
+
ref: { type: "string", description: "Filter by asset ref (type:name)" },
|
|
2264
|
+
"interval-ms": { type: "string", description: "Polling interval in ms (default: 75)" },
|
|
2265
|
+
"max-duration-ms": { type: "string", description: "Stop after this many ms (default: never)" },
|
|
2266
|
+
"max-events": { type: "string", description: "Stop after observing this many events" },
|
|
2267
|
+
},
|
|
2268
|
+
async run({ args }) {
|
|
2269
|
+
await runWithJsonErrors(async () => {
|
|
2270
|
+
const intervalMs = parsePositiveInt(getHyphenatedArg(args, "interval-ms"), "--interval-ms");
|
|
2271
|
+
const maxDurationMs = parsePositiveInt(getHyphenatedArg(args, "max-duration-ms"), "--max-duration-ms");
|
|
2272
|
+
const maxEvents = parsePositiveInt(getHyphenatedArg(args, "max-events"), "--max-events");
|
|
2273
|
+
const mode = getOutputMode();
|
|
2274
|
+
// In streaming text mode we want each event to print as soon as it
|
|
2275
|
+
// arrives. The polling loop emits via `onEvent`; the final result is
|
|
2276
|
+
// also rendered through the standard output() pipeline so JSON
|
|
2277
|
+
// consumers always get the canonical envelope.
|
|
2278
|
+
const stream = mode.format === "text" || mode.format === "jsonl";
|
|
2279
|
+
const result = await akmEventsTail({
|
|
2280
|
+
since: args.since,
|
|
2281
|
+
type: args.type,
|
|
2282
|
+
ref: args.ref,
|
|
2283
|
+
intervalMs,
|
|
2284
|
+
maxDurationMs,
|
|
2285
|
+
maxEvents,
|
|
2286
|
+
onEvent: stream
|
|
2287
|
+
? (event) => {
|
|
2288
|
+
if (mode.format === "jsonl") {
|
|
2289
|
+
console.log(JSON.stringify(event));
|
|
2290
|
+
}
|
|
2291
|
+
else {
|
|
2292
|
+
console.log(formatEventLine(event));
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
: undefined,
|
|
2296
|
+
});
|
|
2297
|
+
// Emit the canonical envelope last (JSON/YAML modes rely on this;
|
|
2298
|
+
// streaming modes already printed each event but we still emit a
|
|
2299
|
+
// trailer so callers can persist the resumable cursor).
|
|
2300
|
+
if (!stream) {
|
|
2301
|
+
output("events-tail", result);
|
|
2302
|
+
}
|
|
2303
|
+
else if (mode.format === "jsonl") {
|
|
2304
|
+
// Final discriminated trailer row so jsonl consumers can resume.
|
|
2305
|
+
const trailer = {
|
|
2306
|
+
_kind: "trailer",
|
|
2307
|
+
schemaVersion: 1,
|
|
2308
|
+
nextOffset: result.nextOffset,
|
|
2309
|
+
totalCount: result.totalCount,
|
|
2310
|
+
reason: result.reason,
|
|
2311
|
+
};
|
|
2312
|
+
console.log(JSON.stringify(trailer));
|
|
2313
|
+
}
|
|
2314
|
+
else {
|
|
2315
|
+
// text mode: keep stdout pristine for line-oriented parsers and
|
|
2316
|
+
// emit the trailer on stderr.
|
|
2317
|
+
process.stderr.write(`[events-tail] reason=${result.reason} nextOffset=${result.nextOffset} total=${result.totalCount}\n`);
|
|
2318
|
+
}
|
|
2319
|
+
});
|
|
2320
|
+
},
|
|
2321
|
+
});
|
|
2322
|
+
function parsePositiveInt(raw, flag) {
|
|
2323
|
+
if (raw === undefined)
|
|
2324
|
+
return undefined;
|
|
2325
|
+
const trimmed = raw.trim();
|
|
2326
|
+
if (!trimmed)
|
|
2327
|
+
return undefined;
|
|
2328
|
+
const value = Number.parseInt(trimmed, 10);
|
|
2329
|
+
if (Number.isNaN(value) || value <= 0) {
|
|
2330
|
+
throw new UsageError(`Invalid ${flag} value: "${raw}". Must be a positive integer.`, "INVALID_FLAG_VALUE");
|
|
2331
|
+
}
|
|
2332
|
+
return value;
|
|
2333
|
+
}
|
|
2334
|
+
const eventsCommand = defineCommand({
|
|
2335
|
+
meta: {
|
|
2336
|
+
name: "events",
|
|
2337
|
+
description: "Read or follow the append-only events.jsonl stream (mutations, feedback, indexing)",
|
|
2338
|
+
},
|
|
2339
|
+
subCommands: {
|
|
2340
|
+
list: eventsListCommand,
|
|
2341
|
+
tail: eventsTailCommand,
|
|
2342
|
+
},
|
|
2343
|
+
});
|
|
2344
|
+
// ── proposal substrate (#225) ────────────────────────────────────────────────
|
|
2345
|
+
const proposalListCommand = defineCommand({
|
|
2346
|
+
meta: { name: "list", description: "List pending proposals (use --include-archive to see decided ones)" },
|
|
2347
|
+
args: {
|
|
2348
|
+
status: { type: "string", description: "Filter by status (pending|accepted|rejected)" },
|
|
2349
|
+
ref: { type: "string", description: "Filter by asset ref (type:name)" },
|
|
2350
|
+
"include-archive": {
|
|
2351
|
+
type: "boolean",
|
|
2352
|
+
description: "Include accepted/rejected proposals from the archive",
|
|
2353
|
+
default: false,
|
|
2354
|
+
},
|
|
2355
|
+
},
|
|
2356
|
+
run({ args }) {
|
|
2357
|
+
return runWithJsonErrors(() => {
|
|
2358
|
+
const status = parseProposalStatus(args.status);
|
|
2359
|
+
const result = akmProposalList({
|
|
2360
|
+
status,
|
|
2361
|
+
ref: args.ref,
|
|
2362
|
+
includeArchive: getHyphenatedBoolean(args, "include-archive"),
|
|
2363
|
+
});
|
|
2364
|
+
output("proposal-list", result);
|
|
2365
|
+
});
|
|
2366
|
+
},
|
|
2367
|
+
});
|
|
2368
|
+
const proposalShowCommand = defineCommand({
|
|
2369
|
+
meta: { name: "show", description: "Show a proposal's metadata, payload, and validation report" },
|
|
2370
|
+
args: {
|
|
2371
|
+
id: { type: "positional", description: "Proposal id (uuid)", required: true },
|
|
2372
|
+
},
|
|
2373
|
+
run({ args }) {
|
|
2374
|
+
return runWithJsonErrors(() => {
|
|
2375
|
+
const result = akmProposalShow({ id: args.id });
|
|
2376
|
+
output("proposal-show", result);
|
|
2377
|
+
});
|
|
2378
|
+
},
|
|
2379
|
+
});
|
|
2380
|
+
const proposalAcceptCommand = defineCommand({
|
|
2381
|
+
meta: { name: "accept", description: "Validate and promote a proposal to a real asset" },
|
|
2382
|
+
args: {
|
|
2383
|
+
id: { type: "positional", description: "Proposal id (uuid)", required: true },
|
|
2384
|
+
target: { type: "string", description: "Override the write target by source name" },
|
|
2385
|
+
},
|
|
2386
|
+
async run({ args }) {
|
|
2387
|
+
await runWithJsonErrors(async () => {
|
|
2388
|
+
const result = await akmProposalAccept({ id: args.id, target: args.target });
|
|
2389
|
+
output("proposal-accept", result);
|
|
2390
|
+
});
|
|
2391
|
+
},
|
|
2392
|
+
});
|
|
2393
|
+
const proposalRejectCommand = defineCommand({
|
|
2394
|
+
meta: { name: "reject", description: "Archive a pending proposal with an optional reason" },
|
|
2395
|
+
args: {
|
|
2396
|
+
id: { type: "positional", description: "Proposal id (uuid)", required: true },
|
|
2397
|
+
reason: { type: "string", description: "Reason for rejection (recorded in the archived proposal)" },
|
|
2398
|
+
},
|
|
2399
|
+
run({ args }) {
|
|
2400
|
+
return runWithJsonErrors(() => {
|
|
2401
|
+
const result = akmProposalReject({ id: args.id, reason: args.reason });
|
|
2402
|
+
output("proposal-reject", result);
|
|
2403
|
+
});
|
|
2404
|
+
},
|
|
2405
|
+
});
|
|
2406
|
+
const proposalDiffCommand = defineCommand({
|
|
2407
|
+
meta: { name: "diff", description: "Show the diff between an existing asset and a pending proposal" },
|
|
2408
|
+
args: {
|
|
2409
|
+
id: { type: "positional", description: "Proposal id (uuid)", required: true },
|
|
2410
|
+
target: { type: "string", description: "Override the write target by source name" },
|
|
2411
|
+
},
|
|
2412
|
+
run({ args }) {
|
|
2413
|
+
return runWithJsonErrors(() => {
|
|
2414
|
+
const result = akmProposalDiff({ id: args.id, target: args.target });
|
|
2415
|
+
output("proposal-diff", result);
|
|
2416
|
+
});
|
|
2417
|
+
},
|
|
2418
|
+
});
|
|
2419
|
+
const proposalCommand = defineCommand({
|
|
2420
|
+
meta: {
|
|
2421
|
+
name: "proposal",
|
|
2422
|
+
description: "Review and promote queued asset proposals (durable storage under .akm/proposals/)",
|
|
2423
|
+
},
|
|
2424
|
+
subCommands: {
|
|
2425
|
+
list: proposalListCommand,
|
|
2426
|
+
show: proposalShowCommand,
|
|
2427
|
+
accept: proposalAcceptCommand,
|
|
2428
|
+
reject: proposalRejectCommand,
|
|
2429
|
+
diff: proposalDiffCommand,
|
|
2430
|
+
},
|
|
2431
|
+
});
|
|
2432
|
+
// ── distill (#228) ──────────────────────────────────────────────────────────
|
|
2433
|
+
const distillCommand = defineCommand({
|
|
2434
|
+
meta: {
|
|
2435
|
+
name: "distill",
|
|
2436
|
+
description: "Distil feedback for an asset into a queued lesson proposal (gated on llm.features.feedback_distillation)",
|
|
2437
|
+
},
|
|
2438
|
+
args: {
|
|
2439
|
+
ref: { type: "positional", description: "Asset ref (type:name) to distil from", required: true },
|
|
2440
|
+
"source-run": {
|
|
2441
|
+
type: "string",
|
|
2442
|
+
description: "Optional run id propagated onto the queued proposal for traceability",
|
|
2443
|
+
},
|
|
2444
|
+
"exclude-feedback-from": {
|
|
2445
|
+
type: "string",
|
|
2446
|
+
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.",
|
|
2447
|
+
},
|
|
2448
|
+
},
|
|
2449
|
+
async run({ args }) {
|
|
2450
|
+
await runWithJsonErrors(async () => {
|
|
2451
|
+
const excludeFlag = getHyphenatedArg(args, "exclude-feedback-from");
|
|
2452
|
+
const excludeEnv = process.env.AKM_DISTILL_EXCLUDE_FEEDBACK_FROM;
|
|
2453
|
+
// CLI flag takes precedence over the env var when both are present.
|
|
2454
|
+
const excludeRaw = excludeFlag ?? excludeEnv;
|
|
2455
|
+
const excludeFeedbackFromRefs = parseExcludeFeedbackFromRefs(excludeRaw);
|
|
2456
|
+
const result = await akmDistill({
|
|
2457
|
+
ref: args.ref,
|
|
2458
|
+
sourceRun: getHyphenatedArg(args, "source-run"),
|
|
2459
|
+
...(excludeFeedbackFromRefs.length > 0 ? { excludeFeedbackFromRefs } : {}),
|
|
2460
|
+
});
|
|
2461
|
+
output("distill", result);
|
|
2462
|
+
});
|
|
2463
|
+
},
|
|
2464
|
+
});
|
|
2465
|
+
/**
|
|
2466
|
+
* Parse a comma-separated list of asset refs (#267 — `--exclude-feedback-from`
|
|
2467
|
+
* and `AKM_DISTILL_EXCLUDE_FEEDBACK_FROM`). Each entry is validated against
|
|
2468
|
+
* the canonical `[origin//]type:name` grammar via `parseAssetRef`; an
|
|
2469
|
+
* invalid entry surfaces as a UsageError → exit 2.
|
|
2470
|
+
*/
|
|
2471
|
+
function parseExcludeFeedbackFromRefs(raw) {
|
|
2472
|
+
if (raw === undefined || raw.trim() === "")
|
|
2473
|
+
return [];
|
|
2474
|
+
const refs = raw
|
|
2475
|
+
.split(",")
|
|
2476
|
+
.map((part) => part.trim())
|
|
2477
|
+
.filter((part) => part.length > 0);
|
|
2478
|
+
for (const ref of refs) {
|
|
2479
|
+
try {
|
|
2480
|
+
parseAssetRef(ref);
|
|
2481
|
+
}
|
|
2482
|
+
catch (err) {
|
|
2483
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2484
|
+
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.");
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
return refs;
|
|
2488
|
+
}
|
|
2489
|
+
function parseProposalStatus(raw) {
|
|
2490
|
+
if (raw === undefined)
|
|
2491
|
+
return undefined;
|
|
2492
|
+
const trimmed = raw.trim();
|
|
2493
|
+
if (!trimmed)
|
|
2494
|
+
return undefined;
|
|
2495
|
+
if (trimmed === "pending" || trimmed === "accepted" || trimmed === "rejected")
|
|
2496
|
+
return trimmed;
|
|
2497
|
+
throw new UsageError(`Invalid --status value: "${raw}". Expected one of: pending, accepted, rejected.`, "INVALID_FLAG_VALUE");
|
|
2498
|
+
}
|
|
2499
|
+
// ── reflect / propose (agent proposal-producers, #226) ──────────────────────
|
|
2500
|
+
const reflectCommand = defineCommand({
|
|
2501
|
+
meta: {
|
|
2502
|
+
name: "reflect",
|
|
2503
|
+
description: "Ask the configured agent CLI to review an asset (or recent feedback) and queue a revised proposal",
|
|
2504
|
+
},
|
|
2505
|
+
args: {
|
|
2506
|
+
ref: {
|
|
2507
|
+
type: "positional",
|
|
2508
|
+
description: "Asset ref (type:name) to reflect on. Optional — omit to reflect across recent feedback.",
|
|
2509
|
+
required: false,
|
|
2510
|
+
},
|
|
2511
|
+
task: { type: "string", description: "Optional task hint passed into the reflection prompt" },
|
|
2512
|
+
profile: { type: "string", description: "Override the agent profile (defaults to agent.default)" },
|
|
2513
|
+
"timeout-ms": { type: "string", description: "Override the agent CLI timeout in milliseconds" },
|
|
2514
|
+
},
|
|
2515
|
+
async run({ args }) {
|
|
2516
|
+
await runWithJsonErrors(async () => {
|
|
2517
|
+
const timeoutRaw = args["timeout-ms"];
|
|
2518
|
+
const timeoutMs = typeof timeoutRaw === "string" && timeoutRaw.trim() ? Number.parseInt(timeoutRaw, 10) : undefined;
|
|
2519
|
+
const result = await akmReflect({
|
|
2520
|
+
ref: typeof args.ref === "string" && args.ref.trim() ? args.ref : undefined,
|
|
2521
|
+
task: typeof args.task === "string" && args.task.trim() ? args.task : undefined,
|
|
2522
|
+
profile: typeof args.profile === "string" && args.profile.trim() ? args.profile : undefined,
|
|
2523
|
+
...(timeoutMs !== undefined && Number.isFinite(timeoutMs) ? { timeoutMs } : {}),
|
|
2524
|
+
});
|
|
2525
|
+
output("reflect", result);
|
|
2526
|
+
if (result.ok === false) {
|
|
2527
|
+
process.exit(EXIT_GENERAL);
|
|
2528
|
+
}
|
|
2529
|
+
});
|
|
2530
|
+
},
|
|
2531
|
+
});
|
|
2532
|
+
const proposeCommand = defineCommand({
|
|
2533
|
+
meta: {
|
|
2534
|
+
name: "propose",
|
|
2535
|
+
description: "Ask the configured agent CLI to author a brand-new asset and queue it as a proposal",
|
|
2536
|
+
},
|
|
2537
|
+
args: {
|
|
2538
|
+
// Optional in citty so run() is invoked when omitted; we re-validate
|
|
2539
|
+
// below to surface a structured UsageError (exit 2) instead of citty's
|
|
2540
|
+
// default help-banner exit-0.
|
|
2541
|
+
type: { type: "positional", description: "Asset type (skill, command, knowledge, lesson, ...)", required: false },
|
|
2542
|
+
name: { type: "positional", description: "Asset name (slug or path under the type dir)", required: false },
|
|
2543
|
+
task: { type: "string", description: "Task description for the agent (what should the asset do?)" },
|
|
2544
|
+
profile: { type: "string", description: "Override the agent profile (defaults to agent.default)" },
|
|
2545
|
+
"timeout-ms": { type: "string", description: "Override the agent CLI timeout in milliseconds" },
|
|
2546
|
+
},
|
|
2547
|
+
async run({ args }) {
|
|
2548
|
+
await runWithJsonErrors(async () => {
|
|
2549
|
+
// citty silently shows help and exits 0 when required positionals are
|
|
2550
|
+
// omitted. Re-validate explicitly so the exit code is 2 (USAGE) and a
|
|
2551
|
+
// structured JSON error reaches scripted callers.
|
|
2552
|
+
if (!args.type || !args.name || !args.task) {
|
|
2553
|
+
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'`.");
|
|
2554
|
+
}
|
|
2555
|
+
const timeoutRaw = args["timeout-ms"];
|
|
2556
|
+
const timeoutMs = typeof timeoutRaw === "string" && timeoutRaw.trim() ? Number.parseInt(timeoutRaw, 10) : undefined;
|
|
2557
|
+
const result = await akmPropose({
|
|
2558
|
+
type: String(args.type),
|
|
2559
|
+
name: String(args.name),
|
|
2560
|
+
task: String(args.task ?? ""),
|
|
2561
|
+
profile: typeof args.profile === "string" && args.profile.trim() ? args.profile : undefined,
|
|
2562
|
+
...(timeoutMs !== undefined && Number.isFinite(timeoutMs) ? { timeoutMs } : {}),
|
|
2563
|
+
});
|
|
2564
|
+
output("propose", result);
|
|
2565
|
+
if (result.ok === false) {
|
|
2566
|
+
process.exit(EXIT_GENERAL);
|
|
2567
|
+
}
|
|
2568
|
+
});
|
|
2569
|
+
},
|
|
2570
|
+
});
|
|
1945
2571
|
const main = defineCommand({
|
|
1946
2572
|
meta: {
|
|
1947
2573
|
name: "akm",
|
|
@@ -1949,9 +2575,14 @@ const main = defineCommand({
|
|
|
1949
2575
|
description: "Agent Kit Manager — search, show, and manage assets from your stash.",
|
|
1950
2576
|
},
|
|
1951
2577
|
args: {
|
|
1952
|
-
format: { type: "string", description: "Output format (json|jsonl|text|yaml)" },
|
|
1953
|
-
detail: { type: "string", description: "Detail level (brief|normal|full|summary|agent)" },
|
|
2578
|
+
format: { type: "string", description: "Output format (json|jsonl|text|yaml)", default: "json" },
|
|
2579
|
+
detail: { type: "string", description: "Detail level (brief|normal|full|summary|agent)", default: "brief" },
|
|
1954
2580
|
quiet: { type: "boolean", alias: "q", description: "Suppress stderr warnings", default: false },
|
|
2581
|
+
verbose: {
|
|
2582
|
+
type: "boolean",
|
|
2583
|
+
description: "Print per-spec diagnostics to stderr (also honours AKM_VERBOSE env var)",
|
|
2584
|
+
default: false,
|
|
2585
|
+
},
|
|
1955
2586
|
},
|
|
1956
2587
|
subCommands: {
|
|
1957
2588
|
setup: setupCommand,
|
|
@@ -1976,6 +2607,12 @@ const main = defineCommand({
|
|
|
1976
2607
|
enable: enableCommand,
|
|
1977
2608
|
disable: disableCommand,
|
|
1978
2609
|
feedback: feedbackCommand,
|
|
2610
|
+
history: historyCommand,
|
|
2611
|
+
events: eventsCommand,
|
|
2612
|
+
proposal: proposalCommand,
|
|
2613
|
+
reflect: reflectCommand,
|
|
2614
|
+
propose: proposeCommand,
|
|
2615
|
+
distill: distillCommand,
|
|
1979
2616
|
help: helpCommand,
|
|
1980
2617
|
hints: hintsCommand,
|
|
1981
2618
|
completions: completionsCommand,
|
|
@@ -2039,6 +2676,12 @@ async function runWithJsonErrors(fn) {
|
|
|
2039
2676
|
if (process.argv.includes("--quiet") || process.argv.includes("-q")) {
|
|
2040
2677
|
setQuiet(true);
|
|
2041
2678
|
}
|
|
2679
|
+
// Apply --verbose flag early so per-spec diagnostics (gated behind
|
|
2680
|
+
// `isVerbose()` in src/core/warn.ts) are restored. The `AKM_VERBOSE`
|
|
2681
|
+
// env var still wins regardless — see warn.ts for the precedence rule.
|
|
2682
|
+
if (process.argv.includes("--verbose")) {
|
|
2683
|
+
setVerbose(true);
|
|
2684
|
+
}
|
|
2042
2685
|
await fn();
|
|
2043
2686
|
}
|
|
2044
2687
|
catch (error) {
|