akm-cli 0.0.23 → 0.1.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/README.md +4 -4
- package/dist/asset-spec.js +1 -1
- package/dist/cli.js +69 -44
- package/dist/config-cli.js +0 -15
- package/dist/config.js +13 -6
- package/dist/github.js +22 -3
- package/dist/indexer.js +2 -2
- package/dist/init.js +1 -1
- package/dist/installed-kits.js +10 -10
- package/dist/kit-include.js +3 -3
- package/dist/local-search.js +1 -1
- package/dist/lockfile.js +42 -3
- package/dist/registry-build-index.js +4 -11
- package/dist/registry-install.js +12 -12
- package/dist/registry-resolve.js +1 -1
- package/dist/registry-search.js +1 -1
- package/dist/{stash-source.js → search-source.js} +4 -8
- package/dist/stash-add.js +26 -7
- package/dist/stash-clone.js +28 -2
- package/dist/stash-providers/filesystem.js +1 -1
- package/dist/stash-providers/openviking.js +1 -1
- package/dist/stash-search.js +3 -3
- package/dist/stash-show.js +8 -5
- package/dist/stash-source-manage.js +4 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Agent Kit Manager
|
|
2
2
|
|
|
3
|
-
> Agent
|
|
3
|
+
> **akm** — Agent Kit Manager
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/akm-cli)
|
|
6
6
|
[](https://github.com/itlackey/agentikit/actions/workflows/ci.yml)
|
|
@@ -25,7 +25,7 @@ Upgrade in place with `akm upgrade`.
|
|
|
25
25
|
## Quick Start
|
|
26
26
|
|
|
27
27
|
```sh
|
|
28
|
-
akm init # Initialize your stash
|
|
28
|
+
akm init # Initialize your working stash
|
|
29
29
|
akm add github:owner/repo # Add a kit from GitHub
|
|
30
30
|
akm search "deploy" # Find assets
|
|
31
31
|
akm show script:deploy.sh # View details and run command
|
|
@@ -89,7 +89,7 @@ Registries are indexes of available kits. The official
|
|
|
89
89
|
```sh
|
|
90
90
|
akm registry search "code review" # Search registries
|
|
91
91
|
akm registry add https://example.com/registry/index.json --name team # Add a registry
|
|
92
|
-
akm
|
|
92
|
+
akm stash add http://host:1933 --provider openviking \
|
|
93
93
|
--options '{"apiKey":"key"}' # Add an OpenViking stash source
|
|
94
94
|
akm registry list # List configured registries
|
|
95
95
|
akm show viking://resources/my-doc # Fetch remote content from OpenViking
|
|
@@ -130,7 +130,7 @@ See the [Kit Maker's Guide](docs/kit-makers.md) for a full walkthrough.
|
|
|
130
130
|
| [Getting Started](docs/getting-started.md) | Quick setup guide |
|
|
131
131
|
| [CLI Reference](docs/cli.md) | All commands and flags |
|
|
132
132
|
| [Configuration](docs/configuration.md) | Settings, providers, and Ollama setup |
|
|
133
|
-
| [Concepts](docs/concepts.md) |
|
|
133
|
+
| [Concepts](docs/concepts.md) | Stashes, kits, registries, asset types |
|
|
134
134
|
| [Kit Maker's Guide](docs/kit-makers.md) | Build and share kits |
|
|
135
135
|
| [Registry](docs/registry.md) | Registries, search, and the v2 index format |
|
|
136
136
|
|
package/dist/asset-spec.js
CHANGED
|
@@ -79,7 +79,7 @@ export function _setAssetTypeHooks(rendererHook, actionBuilderHook) {
|
|
|
79
79
|
_registerActionBuilder = actionBuilderHook;
|
|
80
80
|
}
|
|
81
81
|
/**
|
|
82
|
-
* Register a custom asset type with the
|
|
82
|
+
* Register a custom asset type with the akm asset system.
|
|
83
83
|
*
|
|
84
84
|
* ## Full extension registration API
|
|
85
85
|
*
|
package/dist/cli.js
CHANGED
|
@@ -6,18 +6,18 @@ import { resolveStashDir } from "./common";
|
|
|
6
6
|
import { DEFAULT_CONFIG, getConfigPath, loadConfig, saveConfig } from "./config";
|
|
7
7
|
import { getConfigValue, listConfig, setConfigValue, unsetConfigValue } from "./config-cli";
|
|
8
8
|
import { ConfigError, NotFoundError, UsageError } from "./errors";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import { akmIndex } from "./indexer";
|
|
10
|
+
import { akmInit } from "./init";
|
|
11
|
+
import { akmList, akmRemove, akmUpdate } from "./installed-kits";
|
|
12
12
|
import { getCacheDir, getDbPath, getDefaultStashDir } from "./paths";
|
|
13
13
|
import { buildRegistryIndex, writeRegistryIndex } from "./registry-build-index";
|
|
14
14
|
import { searchRegistry } from "./registry-search";
|
|
15
15
|
import { checkForUpdate, performUpgrade } from "./self-update";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
16
|
+
import { akmAdd, akmKitAdd } from "./stash-add";
|
|
17
|
+
import { akmClone } from "./stash-clone";
|
|
18
|
+
import { akmSearch, parseSearchSource } from "./stash-search";
|
|
19
|
+
import { akmShowUnified } from "./stash-show";
|
|
20
|
+
import { addStash, listStashes, removeStash } from "./stash-source-manage";
|
|
21
21
|
import { setQuiet, warn } from "./warn";
|
|
22
22
|
// Version: prefer compile-time define, then package.json, then fallback
|
|
23
23
|
const pkgVersion = (() => {
|
|
@@ -405,14 +405,14 @@ function formatSearchPlain(r, detail) {
|
|
|
405
405
|
const initCommand = defineCommand({
|
|
406
406
|
meta: {
|
|
407
407
|
name: "init",
|
|
408
|
-
description: "Initialize
|
|
408
|
+
description: "Initialize akm's working stash directory and persist stashDir in config",
|
|
409
409
|
},
|
|
410
410
|
args: {
|
|
411
411
|
dir: { type: "string", description: "Custom stash directory path (default: ~/akm)" },
|
|
412
412
|
},
|
|
413
413
|
async run({ args }) {
|
|
414
414
|
await runWithJsonErrors(async () => {
|
|
415
|
-
const result = await
|
|
415
|
+
const result = await akmInit({ dir: args.dir });
|
|
416
416
|
output("init", result);
|
|
417
417
|
});
|
|
418
418
|
},
|
|
@@ -424,7 +424,7 @@ const indexCommand = defineCommand({
|
|
|
424
424
|
},
|
|
425
425
|
async run({ args }) {
|
|
426
426
|
await runWithJsonErrors(async () => {
|
|
427
|
-
const result = await
|
|
427
|
+
const result = await akmIndex({ full: args.full });
|
|
428
428
|
output("index", result);
|
|
429
429
|
});
|
|
430
430
|
},
|
|
@@ -451,7 +451,7 @@ const searchCommand = defineCommand({
|
|
|
451
451
|
}
|
|
452
452
|
const limit = limitRaw;
|
|
453
453
|
const source = parseSearchSource(args.source);
|
|
454
|
-
const result = await
|
|
454
|
+
const result = await akmSearch({ query: args.query, type, limit, source });
|
|
455
455
|
output("search", result);
|
|
456
456
|
});
|
|
457
457
|
},
|
|
@@ -467,7 +467,7 @@ const addCommand = defineCommand({
|
|
|
467
467
|
},
|
|
468
468
|
async run({ args }) {
|
|
469
469
|
await runWithJsonErrors(async () => {
|
|
470
|
-
const result = await
|
|
470
|
+
const result = await akmAdd({ ref: args.ref });
|
|
471
471
|
output("add", result);
|
|
472
472
|
});
|
|
473
473
|
},
|
|
@@ -476,7 +476,7 @@ const listCommand = defineCommand({
|
|
|
476
476
|
meta: { name: "list", description: "List installed kits" },
|
|
477
477
|
async run() {
|
|
478
478
|
await runWithJsonErrors(async () => {
|
|
479
|
-
const result = await
|
|
479
|
+
const result = await akmList();
|
|
480
480
|
output("list", result);
|
|
481
481
|
});
|
|
482
482
|
},
|
|
@@ -488,7 +488,7 @@ const removeCommand = defineCommand({
|
|
|
488
488
|
},
|
|
489
489
|
async run({ args }) {
|
|
490
490
|
await runWithJsonErrors(async () => {
|
|
491
|
-
const result = await
|
|
491
|
+
const result = await akmRemove({ target: args.target });
|
|
492
492
|
output("remove", result);
|
|
493
493
|
});
|
|
494
494
|
},
|
|
@@ -502,11 +502,36 @@ const updateCommand = defineCommand({
|
|
|
502
502
|
},
|
|
503
503
|
async run({ args }) {
|
|
504
504
|
await runWithJsonErrors(async () => {
|
|
505
|
-
const result = await
|
|
505
|
+
const result = await akmUpdate({ target: args.target, all: args.all, force: args.force });
|
|
506
506
|
output("update", result);
|
|
507
507
|
});
|
|
508
508
|
},
|
|
509
509
|
});
|
|
510
|
+
const kitAddCommand = defineCommand({
|
|
511
|
+
meta: { name: "add", description: "Install a kit from npm, GitHub, or any git host" },
|
|
512
|
+
args: {
|
|
513
|
+
ref: {
|
|
514
|
+
type: "positional",
|
|
515
|
+
description: "Registry ref (npm package, owner/repo, or git URL)",
|
|
516
|
+
required: true,
|
|
517
|
+
},
|
|
518
|
+
},
|
|
519
|
+
async run({ args }) {
|
|
520
|
+
await runWithJsonErrors(async () => {
|
|
521
|
+
const result = await akmKitAdd({ ref: args.ref });
|
|
522
|
+
output("add", result);
|
|
523
|
+
});
|
|
524
|
+
},
|
|
525
|
+
});
|
|
526
|
+
const kitCommand = defineCommand({
|
|
527
|
+
meta: { name: "kit", description: "Manage installed kits" },
|
|
528
|
+
subCommands: {
|
|
529
|
+
add: kitAddCommand,
|
|
530
|
+
list: listCommand,
|
|
531
|
+
remove: removeCommand,
|
|
532
|
+
update: updateCommand,
|
|
533
|
+
},
|
|
534
|
+
});
|
|
510
535
|
const upgradeCommand = defineCommand({
|
|
511
536
|
meta: { name: "upgrade", description: "Upgrade akm to the latest release" },
|
|
512
537
|
args: {
|
|
@@ -568,7 +593,7 @@ const showCommand = defineCommand({
|
|
|
568
593
|
throw new UsageError(`Unknown view mode: ${args.akmView}. Expected one of: full|toc|frontmatter|section|lines`);
|
|
569
594
|
}
|
|
570
595
|
}
|
|
571
|
-
const result = await
|
|
596
|
+
const result = await akmShowUnified({ ref: args.ref, view });
|
|
572
597
|
output("show", result);
|
|
573
598
|
});
|
|
574
599
|
},
|
|
@@ -672,7 +697,7 @@ const configCommand = defineCommand({
|
|
|
672
697
|
const cloneCommand = defineCommand({
|
|
673
698
|
meta: {
|
|
674
699
|
name: "clone",
|
|
675
|
-
description: "Clone an asset from any
|
|
700
|
+
description: "Clone an asset from any source into the working stash or a custom destination",
|
|
676
701
|
},
|
|
677
702
|
args: {
|
|
678
703
|
ref: { type: "positional", description: "Asset ref (e.g. npm:@scope/pkg//script:deploy.sh)", required: true },
|
|
@@ -682,7 +707,7 @@ const cloneCommand = defineCommand({
|
|
|
682
707
|
},
|
|
683
708
|
async run({ args }) {
|
|
684
709
|
await runWithJsonErrors(async () => {
|
|
685
|
-
const result = await
|
|
710
|
+
const result = await akmClone({
|
|
686
711
|
sourceRef: args.ref,
|
|
687
712
|
newName: args.name,
|
|
688
713
|
force: args.force,
|
|
@@ -815,21 +840,20 @@ const registryCommand = defineCommand({
|
|
|
815
840
|
},
|
|
816
841
|
});
|
|
817
842
|
/**
|
|
818
|
-
*
|
|
819
|
-
* Used by both `akm stash` (preferred) and `akm sources` (legacy alias).
|
|
843
|
+
* Subcommand definitions for managing additional stashes.
|
|
820
844
|
*/
|
|
821
845
|
function buildSourceSubCommands(outputPrefix) {
|
|
822
846
|
return {
|
|
823
847
|
list: defineCommand({
|
|
824
|
-
meta: { name: "list", description: "List all
|
|
848
|
+
meta: { name: "list", description: "List all stashes in search order" },
|
|
825
849
|
run() {
|
|
826
850
|
return runWithJsonErrors(() => {
|
|
827
|
-
output(`${outputPrefix}`,
|
|
851
|
+
output(`${outputPrefix}`, listStashes());
|
|
828
852
|
});
|
|
829
853
|
},
|
|
830
854
|
}),
|
|
831
855
|
add: defineCommand({
|
|
832
|
-
meta: { name: "add", description: "
|
|
856
|
+
meta: { name: "add", description: "Register an additional stash (filesystem path or remote URL)" },
|
|
833
857
|
args: {
|
|
834
858
|
target: { type: "positional", description: "Path or URL to add", required: true },
|
|
835
859
|
name: { type: "string", description: "Human-friendly name for the source" },
|
|
@@ -856,7 +880,7 @@ function buildSourceSubCommands(outputPrefix) {
|
|
|
856
880
|
throw new UsageError("--options must be valid JSON");
|
|
857
881
|
}
|
|
858
882
|
}
|
|
859
|
-
const result =
|
|
883
|
+
const result = addStash({
|
|
860
884
|
target: args.target,
|
|
861
885
|
name: args.name,
|
|
862
886
|
providerType: args.provider,
|
|
@@ -867,13 +891,13 @@ function buildSourceSubCommands(outputPrefix) {
|
|
|
867
891
|
},
|
|
868
892
|
}),
|
|
869
893
|
remove: defineCommand({
|
|
870
|
-
meta: { name: "remove", description: "Remove
|
|
894
|
+
meta: { name: "remove", description: "Remove an additional stash by URL, path, or name" },
|
|
871
895
|
args: {
|
|
872
896
|
target: { type: "positional", description: "Source URL, path, or name to remove", required: true },
|
|
873
897
|
},
|
|
874
898
|
run({ args }) {
|
|
875
899
|
return runWithJsonErrors(() => {
|
|
876
|
-
const result =
|
|
900
|
+
const result = removeStash(args.target);
|
|
877
901
|
output(`${outputPrefix}-remove`, result);
|
|
878
902
|
});
|
|
879
903
|
},
|
|
@@ -881,13 +905,9 @@ function buildSourceSubCommands(outputPrefix) {
|
|
|
881
905
|
};
|
|
882
906
|
}
|
|
883
907
|
const stashCommand = defineCommand({
|
|
884
|
-
meta: { name: "stash", description: "Manage
|
|
908
|
+
meta: { name: "stash", description: "Manage additional stashes (local directories and remote providers)" },
|
|
885
909
|
subCommands: buildSourceSubCommands("stash"),
|
|
886
910
|
});
|
|
887
|
-
const sourcesCommand = defineCommand({
|
|
888
|
-
meta: { name: "sources", description: "Manage stash sources (alias for 'akm stash')" },
|
|
889
|
-
subCommands: buildSourceSubCommands("sources"),
|
|
890
|
-
});
|
|
891
911
|
const hintsCommand = defineCommand({
|
|
892
912
|
meta: {
|
|
893
913
|
name: "hints",
|
|
@@ -905,7 +925,7 @@ const main = defineCommand({
|
|
|
905
925
|
meta: {
|
|
906
926
|
name: "akm",
|
|
907
927
|
version: pkgVersion,
|
|
908
|
-
description: "
|
|
928
|
+
description: "Agent Kit Manager — search, show, and manage assets from your stash.",
|
|
909
929
|
},
|
|
910
930
|
args: {
|
|
911
931
|
format: { type: "string", description: "Output format (json|text|yaml)" },
|
|
@@ -919,12 +939,12 @@ const main = defineCommand({
|
|
|
919
939
|
list: listCommand,
|
|
920
940
|
remove: removeCommand,
|
|
921
941
|
update: updateCommand,
|
|
942
|
+
kit: kitCommand,
|
|
922
943
|
upgrade: upgradeCommand,
|
|
923
944
|
search: searchCommand,
|
|
924
945
|
show: showCommand,
|
|
925
946
|
clone: cloneCommand,
|
|
926
947
|
stash: stashCommand,
|
|
927
|
-
sources: sourcesCommand,
|
|
928
948
|
registry: registryCommand,
|
|
929
949
|
config: configCommand,
|
|
930
950
|
hints: hintsCommand,
|
|
@@ -1079,14 +1099,14 @@ function loadHints(detail = "normal") {
|
|
|
1079
1099
|
}
|
|
1080
1100
|
const EMBEDDED_HINTS = `# akm CLI
|
|
1081
1101
|
|
|
1082
|
-
You have access to a searchable library of scripts, skills, commands, agents, and knowledge documents via \`akm\`. Search
|
|
1102
|
+
You have access to a searchable library of scripts, skills, commands, agents, and knowledge documents via \`akm\`. Search your stashes first before writing something from scratch.
|
|
1083
1103
|
|
|
1084
1104
|
## Quick Reference
|
|
1085
1105
|
|
|
1086
1106
|
\`\`\`sh
|
|
1087
|
-
akm search "<query>" # Search
|
|
1107
|
+
akm search "<query>" # Search your stashes and installed kits
|
|
1088
1108
|
akm search "<query>" --type skill # Filter by type
|
|
1089
|
-
akm search "<query>" --source both #
|
|
1109
|
+
akm search "<query>" --source both # Also search registries for installable kits
|
|
1090
1110
|
akm show <ref> # View asset details
|
|
1091
1111
|
akm add <ref> # Install a kit (npm, GitHub, git, local)
|
|
1092
1112
|
akm clone <ref> # Copy an asset to the working stash (optional --dest arg to clone to specific location)
|
|
@@ -1107,14 +1127,14 @@ Run \`akm -h\` for the full command reference.
|
|
|
1107
1127
|
`;
|
|
1108
1128
|
const EMBEDDED_HINTS_FULL = `# akm CLI — Full Reference
|
|
1109
1129
|
|
|
1110
|
-
You have access to a searchable library of scripts, skills, commands, agents, and knowledge documents via \`akm\`. Search
|
|
1130
|
+
You have access to a searchable library of scripts, skills, commands, agents, and knowledge documents via \`akm\`. Search your stashes first before writing something from scratch.
|
|
1111
1131
|
|
|
1112
1132
|
## Search
|
|
1113
1133
|
|
|
1114
1134
|
\`\`\`sh
|
|
1115
|
-
akm search "<query>" # Search
|
|
1135
|
+
akm search "<query>" # Search your stashes and installed kits
|
|
1116
1136
|
akm search "<query>" --type skill # Filter by asset type
|
|
1117
|
-
akm search "<query>" --source both #
|
|
1137
|
+
akm search "<query>" --source both # Also search registries for installable kits
|
|
1118
1138
|
akm search "<query>" --source registry # Search registries only
|
|
1119
1139
|
akm search "<query>" --limit 10 # Limit results
|
|
1120
1140
|
akm search "<query>" --detail full # Include scores, paths, timing
|
|
@@ -1155,10 +1175,14 @@ akm show viking://resources/my-doc # Show remote OpenViking content
|
|
|
1155
1175
|
## Install & Manage Kits
|
|
1156
1176
|
|
|
1157
1177
|
\`\`\`sh
|
|
1158
|
-
akm add <ref> # Install a kit
|
|
1178
|
+
akm add <ref> # Install a kit (smart router: local dirs become stash adds)
|
|
1159
1179
|
akm add @scope/kit # From npm
|
|
1160
1180
|
akm add owner/repo # From GitHub
|
|
1161
|
-
akm add ./path/to/local/kit # From local directory
|
|
1181
|
+
akm add ./path/to/local/kit # From local directory (adds as stash)
|
|
1182
|
+
akm kit add <ref> # Install a kit (explicit)
|
|
1183
|
+
akm kit list # List installed kits
|
|
1184
|
+
akm kit remove <target> # Remove a kit
|
|
1185
|
+
akm kit update --all # Update all kits
|
|
1162
1186
|
akm list # List installed kits
|
|
1163
1187
|
akm remove <target> # Remove by id or ref
|
|
1164
1188
|
akm update --all # Update all installed kits
|
|
@@ -1206,10 +1230,11 @@ akm config path --all # Show all config paths
|
|
|
1206
1230
|
## Other Commands
|
|
1207
1231
|
|
|
1208
1232
|
\`\`\`sh
|
|
1209
|
-
akm init # Initialize stash
|
|
1233
|
+
akm init # Initialize working stash
|
|
1210
1234
|
akm index # Rebuild search index
|
|
1211
1235
|
akm index --full # Full reindex
|
|
1212
|
-
akm
|
|
1236
|
+
akm stash # List all stashes
|
|
1237
|
+
akm kit # Kit management (add, list, remove, update)
|
|
1213
1238
|
akm upgrade # Upgrade akm binary
|
|
1214
1239
|
akm upgrade --check # Check for updates
|
|
1215
1240
|
akm hints # Print this reference
|
package/dist/config-cli.js
CHANGED
|
@@ -9,16 +9,6 @@ export function parseConfigValue(key, value) {
|
|
|
9
9
|
throw new UsageError(`Invalid value for semanticSearch: expected "true" or "false"`);
|
|
10
10
|
}
|
|
11
11
|
return { semanticSearch: value === "true" };
|
|
12
|
-
case "searchPaths":
|
|
13
|
-
try {
|
|
14
|
-
const parsed = JSON.parse(value);
|
|
15
|
-
if (!Array.isArray(parsed))
|
|
16
|
-
throw new UsageError("expected JSON array");
|
|
17
|
-
return { searchPaths: parsed.filter((d) => typeof d === "string") };
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
throw new UsageError(`Invalid value for searchPaths: expected JSON array (e.g. '["/path/a","/path/b"]')`);
|
|
21
|
-
}
|
|
22
12
|
case "embedding":
|
|
23
13
|
return { embedding: parseEmbeddingConnectionValue(value) };
|
|
24
14
|
case "llm":
|
|
@@ -41,8 +31,6 @@ export function getConfigValue(config, key) {
|
|
|
41
31
|
return config.stashDir ?? null;
|
|
42
32
|
case "semanticSearch":
|
|
43
33
|
return config.semanticSearch;
|
|
44
|
-
case "searchPaths":
|
|
45
|
-
return [...config.searchPaths];
|
|
46
34
|
case "embedding":
|
|
47
35
|
return config.embedding ?? null;
|
|
48
36
|
case "llm":
|
|
@@ -63,7 +51,6 @@ export function setConfigValue(config, key, rawValue) {
|
|
|
63
51
|
switch (key) {
|
|
64
52
|
case "stashDir":
|
|
65
53
|
case "semanticSearch":
|
|
66
|
-
case "searchPaths":
|
|
67
54
|
case "embedding":
|
|
68
55
|
case "llm":
|
|
69
56
|
case "registries":
|
|
@@ -108,8 +95,6 @@ export function listConfig(config) {
|
|
|
108
95
|
result.embedding = config.embedding;
|
|
109
96
|
if (config.llm)
|
|
110
97
|
result.llm = config.llm;
|
|
111
|
-
if (config.searchPaths?.length)
|
|
112
|
-
result.searchPaths = config.searchPaths;
|
|
113
98
|
return result;
|
|
114
99
|
}
|
|
115
100
|
function mergeConfigValue(config, partial) {
|
package/dist/config.js
CHANGED
|
@@ -4,7 +4,6 @@ import { getConfigDir as _getConfigDir, getConfigPath as _getConfigPath } from "
|
|
|
4
4
|
// ── Defaults ────────────────────────────────────────────────────────────────
|
|
5
5
|
export const DEFAULT_CONFIG = {
|
|
6
6
|
semanticSearch: true,
|
|
7
|
-
searchPaths: [],
|
|
8
7
|
registries: [
|
|
9
8
|
{ url: "https://raw.githubusercontent.com/itlackey/akm-registry/main/index.json", name: "official" },
|
|
10
9
|
{ url: "https://skills.sh", name: "skills.sh", provider: "skills-sh" },
|
|
@@ -93,8 +92,6 @@ function sanitizeConfigForWrite(config) {
|
|
|
93
92
|
sanitized.llm = rest;
|
|
94
93
|
}
|
|
95
94
|
// Drop empty keys to keep config clean
|
|
96
|
-
if (!config.searchPaths?.length)
|
|
97
|
-
delete sanitized.searchPaths;
|
|
98
95
|
return sanitized;
|
|
99
96
|
}
|
|
100
97
|
export function updateConfig(partial) {
|
|
@@ -125,8 +122,18 @@ function pickKnownKeys(raw) {
|
|
|
125
122
|
if (typeof raw.semanticSearch === "boolean") {
|
|
126
123
|
config.semanticSearch = raw.semanticSearch;
|
|
127
124
|
}
|
|
125
|
+
// Migrate legacy searchPaths into stashes
|
|
128
126
|
if (Array.isArray(raw.searchPaths)) {
|
|
129
|
-
|
|
127
|
+
const legacyPaths = raw.searchPaths.filter((d) => typeof d === "string");
|
|
128
|
+
if (legacyPaths.length > 0) {
|
|
129
|
+
const existing = config.stashes ?? [];
|
|
130
|
+
const migrated = legacyPaths
|
|
131
|
+
.filter((p) => !existing.some((s) => s.type === "filesystem" && s.path === p))
|
|
132
|
+
.map((p) => ({ type: "filesystem", path: p }));
|
|
133
|
+
if (migrated.length > 0) {
|
|
134
|
+
config.stashes = [...existing, ...migrated];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
130
137
|
}
|
|
131
138
|
const embedding = parseEmbeddingConfig(raw.embedding);
|
|
132
139
|
if (embedding)
|
|
@@ -270,7 +277,7 @@ function parseEmbeddingConfig(value) {
|
|
|
270
277
|
if (typeof obj.endpoint !== "string" || !obj.endpoint)
|
|
271
278
|
return undefined;
|
|
272
279
|
if (!obj.endpoint.startsWith("http://") && !obj.endpoint.startsWith("https://")) {
|
|
273
|
-
console.warn(`[
|
|
280
|
+
console.warn(`[akm] Ignoring embedding config: endpoint must start with http:// or https://, got "${obj.endpoint}"`);
|
|
274
281
|
return undefined;
|
|
275
282
|
}
|
|
276
283
|
if (typeof obj.model !== "string" || !obj.model)
|
|
@@ -303,7 +310,7 @@ function parseLlmConfig(value) {
|
|
|
303
310
|
if (typeof obj.endpoint !== "string" || !obj.endpoint)
|
|
304
311
|
return undefined;
|
|
305
312
|
if (!obj.endpoint.startsWith("http://") && !obj.endpoint.startsWith("https://")) {
|
|
306
|
-
console.warn(`[
|
|
313
|
+
console.warn(`[akm] Ignoring llm config: endpoint must start with http:// or https://, got "${obj.endpoint}"`);
|
|
307
314
|
return undefined;
|
|
308
315
|
}
|
|
309
316
|
if (typeof obj.model !== "string" || !obj.model)
|
package/dist/github.js
CHANGED
|
@@ -1,12 +1,31 @@
|
|
|
1
1
|
export const GITHUB_API_BASE = "https://api.github.com";
|
|
2
|
-
|
|
2
|
+
const GITHUB_TOKEN_DOMAINS = new Set(["api.github.com", "github.com", "uploads.github.com"]);
|
|
3
|
+
/**
|
|
4
|
+
* Build headers for GitHub API requests.
|
|
5
|
+
* When a `url` is provided, the Authorization header is only included if the
|
|
6
|
+
* URL points to a known GitHub domain, preventing token leakage on redirects
|
|
7
|
+
* to third-party hosts.
|
|
8
|
+
*/
|
|
9
|
+
export function githubHeaders(url) {
|
|
3
10
|
const token = process.env.GITHUB_TOKEN?.trim();
|
|
4
11
|
const headers = {
|
|
5
12
|
Accept: "application/vnd.github+json",
|
|
6
13
|
"User-Agent": "akm-registry",
|
|
7
14
|
};
|
|
8
|
-
if (token)
|
|
9
|
-
|
|
15
|
+
if (token) {
|
|
16
|
+
let includeToken = true;
|
|
17
|
+
if (url) {
|
|
18
|
+
try {
|
|
19
|
+
const hostname = new URL(url).hostname;
|
|
20
|
+
includeToken = GITHUB_TOKEN_DOMAINS.has(hostname);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
includeToken = false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (includeToken)
|
|
27
|
+
headers.Authorization = `Bearer ${token}`;
|
|
28
|
+
}
|
|
10
29
|
return headers;
|
|
11
30
|
}
|
|
12
31
|
export function asRecord(value) {
|
package/dist/indexer.js
CHANGED
|
@@ -7,12 +7,12 @@ import { getDbPath } from "./paths";
|
|
|
7
7
|
import { walkStashFlat } from "./walker";
|
|
8
8
|
import { warn } from "./warn";
|
|
9
9
|
// ── Indexer ──────────────────────────────────────────────────────────────────
|
|
10
|
-
export async function
|
|
10
|
+
export async function akmIndex(options) {
|
|
11
11
|
const stashDir = options?.stashDir || resolveStashDir();
|
|
12
12
|
// Load config and resolve all stash sources
|
|
13
13
|
const { loadConfig } = await import("./config.js");
|
|
14
14
|
const config = loadConfig();
|
|
15
|
-
const { resolveAllStashDirs } = await import("./
|
|
15
|
+
const { resolveAllStashDirs } = await import("./search-source.js");
|
|
16
16
|
const allStashDirs = resolveAllStashDirs(stashDir);
|
|
17
17
|
const t0 = Date.now();
|
|
18
18
|
// Open database — pass embedding dimension from config if available
|
package/dist/init.js
CHANGED
|
@@ -10,7 +10,7 @@ import { TYPE_DIRS } from "./asset-spec";
|
|
|
10
10
|
import { getConfigPath, loadConfig, saveConfig } from "./config";
|
|
11
11
|
import { getBinDir, getDefaultStashDir } from "./paths";
|
|
12
12
|
import { ensureRg } from "./ripgrep-install";
|
|
13
|
-
export async function
|
|
13
|
+
export async function akmInit(options) {
|
|
14
14
|
const stashDir = options?.dir ? path.resolve(options.dir) : getDefaultStashDir();
|
|
15
15
|
let created = false;
|
|
16
16
|
if (!fs.existsSync(stashDir)) {
|
package/dist/installed-kits.js
CHANGED
|
@@ -13,11 +13,11 @@ import fs from "node:fs";
|
|
|
13
13
|
import { resolveStashDir } from "./common";
|
|
14
14
|
import { loadConfig } from "./config";
|
|
15
15
|
import { NotFoundError, UsageError } from "./errors";
|
|
16
|
-
import {
|
|
16
|
+
import { akmIndex } from "./indexer";
|
|
17
17
|
import { removeLockEntry, upsertLockEntry } from "./lockfile";
|
|
18
18
|
import { installRegistryRef, removeInstalledRegistryEntry, upsertInstalledRegistryEntry } from "./registry-install";
|
|
19
19
|
import { parseRegistryRef } from "./registry-resolve";
|
|
20
|
-
export async function
|
|
20
|
+
export async function akmList(input) {
|
|
21
21
|
const stashDir = input?.stashDir ?? resolveStashDir();
|
|
22
22
|
const config = loadConfig();
|
|
23
23
|
const installed = config.installed ?? [];
|
|
@@ -34,22 +34,22 @@ export async function agentikitList(input) {
|
|
|
34
34
|
totalInstalled: installed.length,
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
-
export async function
|
|
37
|
+
export async function akmRemove(input) {
|
|
38
38
|
const target = input.target.trim();
|
|
39
39
|
if (!target)
|
|
40
|
-
throw new UsageError("Target is required.");
|
|
40
|
+
throw new UsageError("Target is required. Provide the kit id or ref (e.g. `akm remove npm:@scope/kit` or `akm remove owner/repo`).");
|
|
41
41
|
const stashDir = input.stashDir ?? resolveStashDir();
|
|
42
42
|
const config = loadConfig();
|
|
43
43
|
const installed = config.installed ?? [];
|
|
44
44
|
const entry = resolveInstalledTarget(installed, target);
|
|
45
45
|
const updatedConfig = removeInstalledRegistryEntry(entry.id);
|
|
46
|
-
removeLockEntry(entry.id);
|
|
46
|
+
await removeLockEntry(entry.id);
|
|
47
47
|
// Only clean up cache for non-local sources — local sources point to the
|
|
48
48
|
// user's real directory on disk and must never be deleted.
|
|
49
49
|
if (entry.source !== "local") {
|
|
50
50
|
cleanupDirectoryBestEffort(entry.cacheDir);
|
|
51
51
|
}
|
|
52
|
-
const index = await
|
|
52
|
+
const index = await akmIndex({ stashDir });
|
|
53
53
|
return {
|
|
54
54
|
schemaVersion: 1,
|
|
55
55
|
stashDir,
|
|
@@ -62,7 +62,7 @@ export async function agentikitRemove(input) {
|
|
|
62
62
|
stashRoot: entry.stashRoot,
|
|
63
63
|
},
|
|
64
64
|
config: {
|
|
65
|
-
|
|
65
|
+
stashCount: updatedConfig.stashes?.length ?? 0,
|
|
66
66
|
installedKitCount: updatedConfig.installed?.length ?? 0,
|
|
67
67
|
},
|
|
68
68
|
index: {
|
|
@@ -73,7 +73,7 @@ export async function agentikitRemove(input) {
|
|
|
73
73
|
},
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
|
-
export async function
|
|
76
|
+
export async function akmUpdate(input) {
|
|
77
77
|
const stashDir = input?.stashDir ?? resolveStashDir();
|
|
78
78
|
const target = input?.target?.trim();
|
|
79
79
|
const all = input?.all === true;
|
|
@@ -117,7 +117,7 @@ export async function agentikitUpdate(input) {
|
|
|
117
117
|
},
|
|
118
118
|
});
|
|
119
119
|
}
|
|
120
|
-
const index = await
|
|
120
|
+
const index = await akmIndex({ stashDir });
|
|
121
121
|
const config = loadConfig();
|
|
122
122
|
return {
|
|
123
123
|
schemaVersion: 1,
|
|
@@ -126,7 +126,7 @@ export async function agentikitUpdate(input) {
|
|
|
126
126
|
all,
|
|
127
127
|
processed,
|
|
128
128
|
config: {
|
|
129
|
-
|
|
129
|
+
stashCount: config.stashes?.length ?? 0,
|
|
130
130
|
installedKitCount: config.installed?.length ?? 0,
|
|
131
131
|
},
|
|
132
132
|
index: {
|
package/dist/kit-include.js
CHANGED
|
@@ -2,8 +2,8 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { isWithin } from "./common";
|
|
4
4
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
5
|
-
/**
|
|
6
|
-
const INCLUDE_CONFIG_KEYS = ["akm"
|
|
5
|
+
/** Key to check in package.json for akm include configuration. */
|
|
6
|
+
const INCLUDE_CONFIG_KEYS = ["akm"];
|
|
7
7
|
function readPackageJsonAt(dirPath) {
|
|
8
8
|
try {
|
|
9
9
|
const raw = fs.readFileSync(path.join(dirPath, "package.json"), "utf8");
|
|
@@ -39,7 +39,7 @@ function extractIncludeList(pkg) {
|
|
|
39
39
|
// ── Public API ───────────────────────────────────────────────────────────────
|
|
40
40
|
/**
|
|
41
41
|
* Walk up the directory tree from `startDir` to `boundary` (inclusive) looking
|
|
42
|
-
* for a package.json that declares an `akm.include`
|
|
42
|
+
* for a package.json that declares an `akm.include` list.
|
|
43
43
|
* Returns the first config found, or `undefined` if none is found within the
|
|
44
44
|
* boundary.
|
|
45
45
|
*/
|
package/dist/local-search.js
CHANGED
|
@@ -15,8 +15,8 @@ import { getRenderer } from "./file-context";
|
|
|
15
15
|
import { buildSearchText } from "./indexer";
|
|
16
16
|
import { generateMetadataFlat, loadStashFile } from "./metadata";
|
|
17
17
|
import { getDbPath } from "./paths";
|
|
18
|
+
import { buildEditHint, findSourceForPath, isEditable } from "./search-source";
|
|
18
19
|
import { makeAssetRef } from "./stash-ref";
|
|
19
|
-
import { buildEditHint, findSourceForPath, isEditable } from "./stash-source";
|
|
20
20
|
import { walkStashFlat } from "./walker";
|
|
21
21
|
import { warn } from "./warn";
|
|
22
22
|
// ── Type renderer/action maps (re-exported so stash-search.ts can register) ──
|
package/dist/lockfile.js
CHANGED
|
@@ -23,6 +23,10 @@ async function acquireLockSentinel() {
|
|
|
23
23
|
catch (err) {
|
|
24
24
|
if (err.code !== "EEXIST")
|
|
25
25
|
throw err;
|
|
26
|
+
// Check for stale lock — if the owning PID is no longer running, reclaim it
|
|
27
|
+
if (tryReclaimStaleSentinel(sentinelPath)) {
|
|
28
|
+
continue; // Sentinel removed — retry immediately
|
|
29
|
+
}
|
|
26
30
|
// Another process holds the lock — wait briefly before retrying
|
|
27
31
|
if (attempt < LOCK_MAX_RETRIES - 1) {
|
|
28
32
|
await new Promise((resolve) => setTimeout(resolve, LOCK_RETRY_DELAY_MS));
|
|
@@ -32,6 +36,34 @@ async function acquireLockSentinel() {
|
|
|
32
36
|
// Best-effort: proceed without the lock rather than failing the install
|
|
33
37
|
return false;
|
|
34
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Check if the sentinel was left by a dead process and remove it if so.
|
|
41
|
+
* Returns true if the sentinel was reclaimed (removed).
|
|
42
|
+
*/
|
|
43
|
+
function tryReclaimStaleSentinel(sentinelPath) {
|
|
44
|
+
try {
|
|
45
|
+
const content = fs.readFileSync(sentinelPath, "utf8").trim();
|
|
46
|
+
const pid = parseInt(content, 10);
|
|
47
|
+
if (Number.isNaN(pid) || pid <= 0) {
|
|
48
|
+
// Invalid PID in sentinel — reclaim it
|
|
49
|
+
fs.unlinkSync(sentinelPath);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
// Check if the process is still alive (signal 0 doesn't kill, just checks)
|
|
53
|
+
try {
|
|
54
|
+
process.kill(pid, 0);
|
|
55
|
+
return false; // Process is alive — lock is valid
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Process is dead — reclaim the stale lock
|
|
59
|
+
fs.unlinkSync(sentinelPath);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false; // Can't read or remove — leave it alone
|
|
65
|
+
}
|
|
66
|
+
}
|
|
35
67
|
function releaseLockSentinel() {
|
|
36
68
|
try {
|
|
37
69
|
fs.unlinkSync(getLockSentinelPath());
|
|
@@ -84,9 +116,16 @@ export async function upsertLockEntry(entry) {
|
|
|
84
116
|
releaseLockSentinel();
|
|
85
117
|
}
|
|
86
118
|
}
|
|
87
|
-
export function removeLockEntry(id) {
|
|
88
|
-
const
|
|
89
|
-
|
|
119
|
+
export async function removeLockEntry(id) {
|
|
120
|
+
const acquired = await acquireLockSentinel();
|
|
121
|
+
try {
|
|
122
|
+
const entries = readLockfile();
|
|
123
|
+
writeLockfile(entries.filter((e) => e.id !== id));
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
if (acquired)
|
|
127
|
+
releaseLockSentinel();
|
|
128
|
+
}
|
|
90
129
|
}
|
|
91
130
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
92
131
|
function isValidLockfileEntry(value) {
|
|
@@ -11,17 +11,10 @@ import { walkStashFlat } from "./walker";
|
|
|
11
11
|
const DEFAULT_NPM_REGISTRY_BASE = "https://registry.npmjs.org";
|
|
12
12
|
const DEFAULT_MANUAL_ENTRIES_PATH = path.resolve("manual-entries.json");
|
|
13
13
|
const DEFAULT_OUTPUT_PATH = path.resolve("index.json");
|
|
14
|
-
const REQUIRED_KEYWORDS = ["
|
|
15
|
-
const GITHUB_TOPICS = ["
|
|
16
|
-
const EXCLUDED_REPOS = new Set(["itlackey/agentikit
|
|
17
|
-
const EXCLUDED_NPM_PACKAGES = new Set([
|
|
18
|
-
"agentikit",
|
|
19
|
-
"agentikit-claude",
|
|
20
|
-
"agentikit-opencode",
|
|
21
|
-
"agentikit-plugins",
|
|
22
|
-
"akm-cli",
|
|
23
|
-
"akm-opencode",
|
|
24
|
-
]);
|
|
14
|
+
const REQUIRED_KEYWORDS = ["akm-kit"];
|
|
15
|
+
const GITHUB_TOPICS = ["akm-kit"];
|
|
16
|
+
const EXCLUDED_REPOS = new Set(["itlackey/agentikit"]);
|
|
17
|
+
const EXCLUDED_NPM_PACKAGES = new Set(["akm-cli"]);
|
|
25
18
|
const EMPTY_INSPECTION = {};
|
|
26
19
|
export async function buildRegistryIndex(options) {
|
|
27
20
|
const manualEntriesPath = path.resolve(options?.manualEntriesPath ?? DEFAULT_MANUAL_ENTRIES_PATH);
|
package/dist/registry-install.js
CHANGED
|
@@ -60,7 +60,7 @@ export async function installRegistryRef(ref, options) {
|
|
|
60
60
|
integrity = await computeFileHash(archivePath);
|
|
61
61
|
extractTarGzSecure(archivePath, extractedDir);
|
|
62
62
|
provisionalKitRoot = detectStashRoot(extractedDir);
|
|
63
|
-
installRoot =
|
|
63
|
+
installRoot = applyAkmIncludeConfig(provisionalKitRoot, cacheDir, extractedDir) ?? provisionalKitRoot;
|
|
64
64
|
stashRoot = detectStashRoot(installRoot);
|
|
65
65
|
}
|
|
66
66
|
catch (err) {
|
|
@@ -118,7 +118,7 @@ async function installGitRegistryRef(parsed, options) {
|
|
|
118
118
|
if (isDirectory(extractedDir)) {
|
|
119
119
|
try {
|
|
120
120
|
const provisionalKitRoot = detectStashRoot(extractedDir);
|
|
121
|
-
const installRoot =
|
|
121
|
+
const installRoot = applyAkmIncludeConfig(provisionalKitRoot, cacheDir, extractedDir) ?? provisionalKitRoot;
|
|
122
122
|
const stashRoot = detectStashRoot(installRoot);
|
|
123
123
|
if (stashRoot) {
|
|
124
124
|
return {
|
|
@@ -164,7 +164,7 @@ async function installGitRegistryRef(parsed, options) {
|
|
|
164
164
|
// Clean up the clone dir
|
|
165
165
|
fs.rmSync(cloneDir, { recursive: true, force: true });
|
|
166
166
|
provisionalKitRoot = detectStashRoot(extractedDir);
|
|
167
|
-
installRoot =
|
|
167
|
+
installRoot = applyAkmIncludeConfig(provisionalKitRoot, cacheDir, extractedDir) ?? provisionalKitRoot;
|
|
168
168
|
stashRoot = detectStashRoot(installRoot);
|
|
169
169
|
}
|
|
170
170
|
catch (err) {
|
|
@@ -226,10 +226,6 @@ export function detectStashRoot(extractedDir) {
|
|
|
226
226
|
if (hasStashDirs(root)) {
|
|
227
227
|
return root;
|
|
228
228
|
}
|
|
229
|
-
const opencodeDir = path.join(root, "opencode");
|
|
230
|
-
if (hasStashDirs(opencodeDir)) {
|
|
231
|
-
return opencodeDir;
|
|
232
|
-
}
|
|
233
229
|
const shallowest = findShallowestStashRoot(root);
|
|
234
230
|
if (shallowest)
|
|
235
231
|
return shallowest;
|
|
@@ -242,7 +238,7 @@ function buildInstallCacheDir(cacheRootDir, source, id, version) {
|
|
|
242
238
|
: (version?.replace(/[^a-zA-Z0-9_.-]+/g, "-") ?? `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`);
|
|
243
239
|
return path.join(cacheRootDir, slug || source, versionSlug);
|
|
244
240
|
}
|
|
245
|
-
function
|
|
241
|
+
function applyAkmIncludeConfig(sourceRoot, cacheDir, searchRoot = sourceRoot) {
|
|
246
242
|
const includeConfig = findNearestIncludeConfig(sourceRoot, searchRoot);
|
|
247
243
|
if (!includeConfig)
|
|
248
244
|
return undefined;
|
|
@@ -400,13 +396,15 @@ function countStashDirs(dirPath) {
|
|
|
400
396
|
*
|
|
401
397
|
* Skips `root` itself since the caller already checked it via `hasStashDirs`.
|
|
402
398
|
*/
|
|
399
|
+
const BFS_MAX_DEPTH = 5;
|
|
403
400
|
function findShallowestStashRoot(root) {
|
|
404
|
-
const queue = [root];
|
|
401
|
+
const queue = [{ dir: root, depth: 0 }];
|
|
405
402
|
while (queue.length > 0) {
|
|
406
|
-
const
|
|
407
|
-
if (!
|
|
403
|
+
const item = queue.shift();
|
|
404
|
+
if (!item) {
|
|
408
405
|
continue;
|
|
409
406
|
}
|
|
407
|
+
const { dir: current, depth } = item;
|
|
410
408
|
if (current !== root) {
|
|
411
409
|
// .stash directory is a strong stash marker
|
|
412
410
|
if (isDirectory(path.join(current, ".stash"))) {
|
|
@@ -418,6 +416,8 @@ function findShallowestStashRoot(root) {
|
|
|
418
416
|
return current;
|
|
419
417
|
}
|
|
420
418
|
}
|
|
419
|
+
if (depth >= BFS_MAX_DEPTH)
|
|
420
|
+
continue;
|
|
421
421
|
let children;
|
|
422
422
|
try {
|
|
423
423
|
children = fs.readdirSync(current, { withFileTypes: true });
|
|
@@ -430,7 +430,7 @@ function findShallowestStashRoot(root) {
|
|
|
430
430
|
continue;
|
|
431
431
|
if (child.name === ".git" || child.name === "node_modules")
|
|
432
432
|
continue;
|
|
433
|
-
queue.push(path.join(current, child.name));
|
|
433
|
+
queue.push({ dir: path.join(current, child.name), depth: depth + 1 });
|
|
434
434
|
}
|
|
435
435
|
}
|
|
436
436
|
return undefined;
|
package/dist/registry-resolve.js
CHANGED
|
@@ -422,7 +422,7 @@ function fileUriToPath(ref) {
|
|
|
422
422
|
}
|
|
423
423
|
/**
|
|
424
424
|
* Build a human-readable local ID from an absolute path.
|
|
425
|
-
* /home/user
|
|
425
|
+
* /home/user/akm/skills → ~/akm/skills
|
|
426
426
|
* /tmp/my-kit → /tmp/my-kit
|
|
427
427
|
*/
|
|
428
428
|
function toReadableLocalId(absolutePath) {
|
package/dist/registry-search.js
CHANGED
|
@@ -70,7 +70,7 @@ export function resolveRegistries(configRegistries) {
|
|
|
70
70
|
if (!url)
|
|
71
71
|
continue;
|
|
72
72
|
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
|
73
|
-
console.warn(`[
|
|
73
|
+
console.warn(`[akm] Ignoring AKM_REGISTRY_URL entry: must start with http:// or https://, got "${url}"`);
|
|
74
74
|
continue;
|
|
75
75
|
}
|
|
76
76
|
entries.push({ url });
|
|
@@ -5,13 +5,13 @@ import { loadConfig } from "./config";
|
|
|
5
5
|
import { warn } from "./warn";
|
|
6
6
|
// ── Resolution ──────────────────────────────────────────────────────────────
|
|
7
7
|
/**
|
|
8
|
-
* Build the ordered list of stash sources
|
|
8
|
+
* Build the ordered list of stash sources:
|
|
9
9
|
* 1. Primary stash dir (user's own, destination for clone)
|
|
10
|
-
* 2. Additional
|
|
10
|
+
* 2. Additional stashes (filesystem and remote providers)
|
|
11
11
|
* 3. Installed kit paths (cache-managed, from registry)
|
|
12
12
|
*
|
|
13
13
|
* The first entry is always the primary stash. Additional entries come
|
|
14
|
-
* from `
|
|
14
|
+
* from `stashes` config and `installed` kit entries.
|
|
15
15
|
*/
|
|
16
16
|
export function resolveStashSources(overrideStashDir, existingConfig) {
|
|
17
17
|
const stashDir = overrideStashDir ?? resolveStashDir();
|
|
@@ -30,10 +30,6 @@ export function resolveStashSources(overrideStashDir, existingConfig) {
|
|
|
30
30
|
sources.push({ path: resolved, ...(registryId ? { registryId } : {}) });
|
|
31
31
|
}
|
|
32
32
|
};
|
|
33
|
-
// Legacy: searchPaths[]
|
|
34
|
-
for (const dir of config.searchPaths) {
|
|
35
|
-
addSource(dir);
|
|
36
|
-
}
|
|
37
33
|
// Filesystem entries from stashes[]
|
|
38
34
|
for (const entry of config.stashes ?? []) {
|
|
39
35
|
if (entry.type === "filesystem" && entry.path && entry.enabled !== false) {
|
|
@@ -78,7 +74,7 @@ export function getPrimarySource(sources) {
|
|
|
78
74
|
* managed by the package manager (`installed[].cacheDir`). These
|
|
79
75
|
* will be overwritten by `akm update` without warning.
|
|
80
76
|
*
|
|
81
|
-
* Everything else — working stash,
|
|
77
|
+
* Everything else — working stash, additional stashes, local project dirs — is
|
|
82
78
|
* the user's domain to manage.
|
|
83
79
|
*/
|
|
84
80
|
export function isEditable(filePath, config) {
|
package/dist/stash-add.js
CHANGED
|
@@ -3,14 +3,33 @@ import path from "node:path";
|
|
|
3
3
|
import { resolveStashDir } from "./common";
|
|
4
4
|
import { loadConfig, saveConfig } from "./config";
|
|
5
5
|
import { UsageError } from "./errors";
|
|
6
|
-
import {
|
|
6
|
+
import { akmIndex } from "./indexer";
|
|
7
7
|
import { upsertLockEntry } from "./lockfile";
|
|
8
8
|
import { detectStashRoot, installRegistryRef, upsertInstalledRegistryEntry } from "./registry-install";
|
|
9
9
|
import { parseRegistryRef } from "./registry-resolve";
|
|
10
|
-
export async function
|
|
10
|
+
export async function akmKitAdd(input) {
|
|
11
11
|
const ref = input.ref.trim();
|
|
12
12
|
if (!ref)
|
|
13
|
-
throw new UsageError("
|
|
13
|
+
throw new UsageError("Registry ref is required. " + "Examples: `akm kit add @scope/kit`, `akm kit add github:owner/repo`");
|
|
14
|
+
const stashDir = resolveStashDir();
|
|
15
|
+
try {
|
|
16
|
+
const parsed = parseRegistryRef(ref);
|
|
17
|
+
if (parsed.source === "local") {
|
|
18
|
+
throw new UsageError(`Local directories should be added as stashes, not kits. Use \`akm stash add ${ref}\` instead.`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
if (err instanceof UsageError)
|
|
23
|
+
throw err;
|
|
24
|
+
// Not a local ref — fall through to registry install
|
|
25
|
+
}
|
|
26
|
+
return addRegistryKit(ref, stashDir);
|
|
27
|
+
}
|
|
28
|
+
export async function akmAdd(input) {
|
|
29
|
+
const ref = input.ref.trim();
|
|
30
|
+
if (!ref)
|
|
31
|
+
throw new UsageError("Install ref or local directory is required. " +
|
|
32
|
+
"Examples: `akm add @scope/kit`, `akm add github:owner/repo`, `akm add ./local/path`");
|
|
14
33
|
const stashDir = resolveStashDir();
|
|
15
34
|
// Detect local directory refs and route them to stashes[] instead of installed[]
|
|
16
35
|
try {
|
|
@@ -44,7 +63,7 @@ async function addLocalStashSource(ref, sourcePath, stashDir) {
|
|
|
44
63
|
stashes.push(entry);
|
|
45
64
|
saveConfig({ ...config, stashes });
|
|
46
65
|
}
|
|
47
|
-
const index = await
|
|
66
|
+
const index = await akmIndex({ stashDir });
|
|
48
67
|
const updatedConfig = loadConfig();
|
|
49
68
|
return {
|
|
50
69
|
schemaVersion: 1,
|
|
@@ -57,7 +76,7 @@ async function addLocalStashSource(ref, sourcePath, stashDir) {
|
|
|
57
76
|
stashRoot: resolvedPath,
|
|
58
77
|
},
|
|
59
78
|
config: {
|
|
60
|
-
|
|
79
|
+
stashCount: updatedConfig.stashes?.length ?? 0,
|
|
61
80
|
installedKitCount: updatedConfig.installed?.length ?? 0,
|
|
62
81
|
},
|
|
63
82
|
index: {
|
|
@@ -102,7 +121,7 @@ async function addRegistryKit(ref, stashDir) {
|
|
|
102
121
|
// Best-effort cleanup only.
|
|
103
122
|
}
|
|
104
123
|
}
|
|
105
|
-
const index = await
|
|
124
|
+
const index = await akmIndex({ stashDir });
|
|
106
125
|
return {
|
|
107
126
|
schemaVersion: 1,
|
|
108
127
|
stashDir,
|
|
@@ -120,7 +139,7 @@ async function addRegistryKit(ref, stashDir) {
|
|
|
120
139
|
installedAt: installed.installedAt,
|
|
121
140
|
},
|
|
122
141
|
config: {
|
|
123
|
-
|
|
142
|
+
stashCount: config.stashes?.length ?? 0,
|
|
124
143
|
installedKitCount: config.installed?.length ?? 0,
|
|
125
144
|
},
|
|
126
145
|
index: {
|
package/dist/stash-clone.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { TYPE_DIRS } from "./asset-spec";
|
|
4
|
+
import { UsageError } from "./errors";
|
|
4
5
|
import { isRemoteOrigin, resolveSourcesForOrigin } from "./origin-resolve";
|
|
5
6
|
import { installRegistryRef } from "./registry-install";
|
|
7
|
+
import { findSourceForPath, getPrimarySource, resolveStashSources } from "./search-source";
|
|
6
8
|
import { makeAssetRef, parseAssetRef } from "./stash-ref";
|
|
7
9
|
import { resolveAssetPath } from "./stash-resolve";
|
|
8
|
-
|
|
9
|
-
export async function agentikitClone(options) {
|
|
10
|
+
export async function akmClone(options) {
|
|
10
11
|
const parsed = parseAssetRef(options.sourceRef);
|
|
11
12
|
// When --dest is provided, the working stash is optional
|
|
12
13
|
let allSources;
|
|
@@ -61,6 +62,31 @@ export async function agentikitClone(options) {
|
|
|
61
62
|
const sourceSource = findSourceForPath(sourcePath, allSources);
|
|
62
63
|
const destName = options.newName ?? parsed.name;
|
|
63
64
|
const typeDir = TYPE_DIRS[parsed.type];
|
|
65
|
+
// Validate destName to prevent path traversal (parsed.name is already
|
|
66
|
+
// validated by parseAssetRef, but newName comes directly from user input).
|
|
67
|
+
// Run whenever newName is provided, including empty string.
|
|
68
|
+
if (options.newName !== undefined) {
|
|
69
|
+
if (destName === "") {
|
|
70
|
+
throw new UsageError("Clone name must not be empty.");
|
|
71
|
+
}
|
|
72
|
+
const normalized = path.posix.normalize(destName.replace(/\\/g, "/"));
|
|
73
|
+
if (path.isAbsolute(destName) ||
|
|
74
|
+
normalized === "." ||
|
|
75
|
+
normalized.startsWith("../") ||
|
|
76
|
+
normalized === ".." ||
|
|
77
|
+
destName.includes("\0")) {
|
|
78
|
+
throw new UsageError(`Unsafe clone name "${destName}": must not contain path traversal or absolute paths.`);
|
|
79
|
+
}
|
|
80
|
+
// Ensure the resolved destination is strictly inside the type directory,
|
|
81
|
+
// not equal to it (which can happen with crafted multi-segment names).
|
|
82
|
+
// path.relative() is used instead of startsWith() for cross-platform safety.
|
|
83
|
+
const destTypeDir = path.resolve(path.join(destRoot, typeDir));
|
|
84
|
+
const resolvedDest = path.resolve(path.join(destRoot, typeDir, destName));
|
|
85
|
+
const rel = path.relative(destTypeDir, resolvedDest);
|
|
86
|
+
if (rel === "" || rel.startsWith("..")) {
|
|
87
|
+
throw new UsageError(`Unsafe clone name "${destName}": resolves outside the target type directory.`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
64
90
|
const destLabel = options.dest ? "at destination" : "in working stash";
|
|
65
91
|
// Guard against self-clone
|
|
66
92
|
if (parsed.type === "skill") {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { resolveStashDir } from "../common";
|
|
2
2
|
import { loadConfig } from "../config";
|
|
3
3
|
import { searchLocal } from "../local-search";
|
|
4
|
+
import { resolveStashSources } from "../search-source";
|
|
4
5
|
import { registerStashProvider } from "../stash-provider-factory";
|
|
5
6
|
import { showLocal } from "../stash-show";
|
|
6
|
-
import { resolveStashSources } from "../stash-source";
|
|
7
7
|
class FilesystemStashProvider {
|
|
8
8
|
type = "filesystem";
|
|
9
9
|
name;
|
|
@@ -16,7 +16,7 @@ const QUERY_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
|
16
16
|
/** Maximum age before query cache is considered stale but still usable (1 hour). */
|
|
17
17
|
const QUERY_CACHE_STALE_MS = 60 * 60 * 1000;
|
|
18
18
|
/**
|
|
19
|
-
* Single source of truth for OpenViking type →
|
|
19
|
+
* Single source of truth for OpenViking type → akm asset type mapping.
|
|
20
20
|
* Used by both search hit mapping and show response mapping.
|
|
21
21
|
*/
|
|
22
22
|
const OV_TYPE_MAP = {
|
package/dist/stash-search.js
CHANGED
|
@@ -5,9 +5,9 @@ import { resolveStashProviders } from "./stash-provider-factory";
|
|
|
5
5
|
import "./stash-providers/index";
|
|
6
6
|
import { UsageError } from "./errors";
|
|
7
7
|
import { searchRegistry } from "./registry-search";
|
|
8
|
-
import { resolveStashSources } from "./
|
|
8
|
+
import { resolveStashSources } from "./search-source";
|
|
9
9
|
const DEFAULT_LIMIT = 20;
|
|
10
|
-
export async function
|
|
10
|
+
export async function akmSearch(input) {
|
|
11
11
|
const t0 = Date.now();
|
|
12
12
|
const query = input.query.trim();
|
|
13
13
|
const normalizedQuery = query.toLowerCase();
|
|
@@ -24,7 +24,7 @@ export async function agentikitSearch(input) {
|
|
|
24
24
|
stashDir: "",
|
|
25
25
|
source,
|
|
26
26
|
hits: [],
|
|
27
|
-
warnings: ["No
|
|
27
|
+
warnings: ["No stashes configured. Run `akm init` to create your working stash."],
|
|
28
28
|
timing: { totalMs: Date.now() - t0 },
|
|
29
29
|
};
|
|
30
30
|
}
|
package/dist/stash-show.js
CHANGED
|
@@ -2,17 +2,17 @@ import { loadConfig } from "./config";
|
|
|
2
2
|
import { NotFoundError, UsageError } from "./errors";
|
|
3
3
|
import { buildFileContext, buildRenderContext, getRenderer, runMatchers } from "./file-context";
|
|
4
4
|
import { resolveSourcesForOrigin } from "./origin-resolve";
|
|
5
|
+
import { buildEditHint, findSourceForPath, isEditable, resolveStashSources } from "./search-source";
|
|
5
6
|
import { resolveStashProviders } from "./stash-provider-factory";
|
|
6
7
|
import { parseAssetRef } from "./stash-ref";
|
|
7
8
|
import { resolveAssetPath } from "./stash-resolve";
|
|
8
|
-
import { buildEditHint, findSourceForPath, isEditable, resolveStashSources } from "./stash-source";
|
|
9
9
|
// Eagerly import stash providers to trigger self-registration
|
|
10
10
|
import "./stash-providers/index";
|
|
11
11
|
/**
|
|
12
12
|
* Unified show: routes to the first stash provider that can handle the ref.
|
|
13
13
|
* viking:// refs are handled by OpenViking provider; everything else by filesystem show.
|
|
14
14
|
*/
|
|
15
|
-
export async function
|
|
15
|
+
export async function akmShowUnified(input) {
|
|
16
16
|
const ref = input.ref.trim();
|
|
17
17
|
// Try stash providers first (e.g. OpenViking for viking:// URIs)
|
|
18
18
|
const config = loadConfig();
|
|
@@ -23,7 +23,7 @@ export async function agentikitShowUnified(input) {
|
|
|
23
23
|
// Default: local filesystem show
|
|
24
24
|
return showLocal(input);
|
|
25
25
|
}
|
|
26
|
-
/** @internal Use
|
|
26
|
+
/** @internal Use akmShowUnified() for all external callers. */
|
|
27
27
|
export async function showLocal(input) {
|
|
28
28
|
const parsed = parseAssetRef(input.ref);
|
|
29
29
|
const displayType = parsed.type;
|
|
@@ -48,12 +48,15 @@ export async function showLocal(input) {
|
|
|
48
48
|
`Kit "${parsed.origin}" is not installed. Run: ${installCmd}`);
|
|
49
49
|
}
|
|
50
50
|
if (!assetPath) {
|
|
51
|
-
throw lastError ??
|
|
51
|
+
throw (lastError ??
|
|
52
|
+
new NotFoundError(`Stash asset not found for ref: ${displayType}:${parsed.name}. ` +
|
|
53
|
+
"Check the name with `akm search` or verify the asset exists in your stash."));
|
|
52
54
|
}
|
|
53
55
|
const source = findSourceForPath(assetPath, allSources);
|
|
54
56
|
const sourceStashDir = source?.path ?? allStashDirs[0];
|
|
55
57
|
if (!sourceStashDir) {
|
|
56
|
-
throw new UsageError(`Could not determine stash root for asset: ${displayType}:${parsed.name}`
|
|
58
|
+
throw new UsageError(`Could not determine stash root for asset: ${displayType}:${parsed.name}. ` +
|
|
59
|
+
"Run `akm init` to create the stash directory, or check `akm stash list` for configured paths.");
|
|
57
60
|
}
|
|
58
61
|
const fileCtx = buildFileContext(sourceStashDir, assetPath);
|
|
59
62
|
const match = await runMatchers(fileCtx);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { loadConfig, saveConfig } from "./config";
|
|
3
3
|
import { UsageError } from "./errors";
|
|
4
|
-
import { resolveStashSources } from "./
|
|
4
|
+
import { resolveStashSources } from "./search-source";
|
|
5
5
|
// ── Operations ──────────────────────────────────────────────────────────────
|
|
6
6
|
/**
|
|
7
7
|
* Add a stash source (filesystem path or remote provider URL) to config.
|
|
@@ -10,7 +10,7 @@ import { resolveStashSources } from "./stash-source";
|
|
|
10
10
|
* `http://` or `https://`. URL sources require a `providerType` option
|
|
11
11
|
* (e.g. "openviking").
|
|
12
12
|
*/
|
|
13
|
-
export function
|
|
13
|
+
export function addStash(opts) {
|
|
14
14
|
const { target, name, providerType, options: providerOptions } = opts;
|
|
15
15
|
const config = loadConfig();
|
|
16
16
|
const stashes = [...(config.stashes ?? [])];
|
|
@@ -48,7 +48,7 @@ export function addStashSource(opts) {
|
|
|
48
48
|
* Remove a stash source by URL, path, or name.
|
|
49
49
|
* Match priority: URL > path > name (most specific first).
|
|
50
50
|
*/
|
|
51
|
-
export function
|
|
51
|
+
export function removeStash(target) {
|
|
52
52
|
const config = loadConfig();
|
|
53
53
|
const stashes = [...(config.stashes ?? [])];
|
|
54
54
|
const isUrl = target.startsWith("http://") || target.startsWith("https://");
|
|
@@ -74,7 +74,7 @@ export function removeStashSource(target) {
|
|
|
74
74
|
/**
|
|
75
75
|
* List all stash sources (local filesystem + configured stashes).
|
|
76
76
|
*/
|
|
77
|
-
export function
|
|
77
|
+
export function listStashes() {
|
|
78
78
|
const config = loadConfig();
|
|
79
79
|
const localSources = resolveStashSources();
|
|
80
80
|
const stashes = config.stashes ?? [];
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-cli",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI tool to search, open, and run extension assets from an akm stash directory.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"akm",
|
|
8
|
-
"
|
|
8
|
+
"akm-kit",
|
|
9
9
|
"ai-agent",
|
|
10
10
|
"agent-framework",
|
|
11
11
|
"developer-tools",
|