akm-cli 0.1.0 → 0.1.1

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/dist/cli.js CHANGED
@@ -3,6 +3,7 @@ import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { defineCommand, runMain } from "citty";
5
5
  import { resolveStashDir } from "./common";
6
+ import { generateBashCompletions, installBashCompletions } from "./completions";
6
7
  import { DEFAULT_CONFIG, getConfigPath, loadConfig, saveConfig } from "./config";
7
8
  import { getConfigValue, listConfig, setConfigValue, unsetConfigValue } from "./config-cli";
8
9
  import { ConfigError, NotFoundError, UsageError } from "./errors";
@@ -921,6 +922,38 @@ const hintsCommand = defineCommand({
921
922
  process.stdout.write(loadHints(detail));
922
923
  },
923
924
  });
925
+ const completionsCommand = defineCommand({
926
+ meta: {
927
+ name: "completions",
928
+ description: "Generate or install shell completion script",
929
+ },
930
+ args: {
931
+ install: {
932
+ type: "boolean",
933
+ description: "Install completions to the appropriate directory",
934
+ default: false,
935
+ },
936
+ shell: {
937
+ type: "string",
938
+ description: "Shell type (bash)",
939
+ default: "bash",
940
+ },
941
+ },
942
+ run({ args }) {
943
+ if (args.shell !== "bash") {
944
+ throw new UsageError(`Unsupported shell: ${args.shell}. Only bash is supported.`);
945
+ }
946
+ const script = generateBashCompletions(main);
947
+ if (args.install) {
948
+ const dest = installBashCompletions(script);
949
+ console.error(`Completions installed to ${dest}`);
950
+ console.error(`Restart your shell or run: source ${dest}`);
951
+ }
952
+ else {
953
+ process.stdout.write(script);
954
+ }
955
+ },
956
+ });
924
957
  const main = defineCommand({
925
958
  meta: {
926
959
  name: "akm",
@@ -948,6 +981,7 @@ const main = defineCommand({
948
981
  registry: registryCommand,
949
982
  config: configCommand,
950
983
  hints: hintsCommand,
984
+ completions: completionsCommand,
951
985
  },
952
986
  });
953
987
  const CONFIG_SUBCOMMAND_SET = new Set(["path", "list", "get", "set", "unset"]);
@@ -1238,6 +1272,8 @@ akm kit # Kit management (add, list, remov
1238
1272
  akm upgrade # Upgrade akm binary
1239
1273
  akm upgrade --check # Check for updates
1240
1274
  akm hints # Print this reference
1275
+ akm completions # Print bash completion script
1276
+ akm completions --install # Install completions
1241
1277
  \`\`\`
1242
1278
 
1243
1279
  ## Output Control
@@ -0,0 +1,137 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ // ── Known flag values ────────────────────────────────────────────────────────
5
+ const FLAG_VALUES = {
6
+ "--format": ["json", "text", "yaml"],
7
+ "--detail": ["brief", "normal", "full"],
8
+ "--type": ["skill", "command", "agent", "knowledge", "script", "memory", "any"],
9
+ "--source": ["stash", "registry", "both"],
10
+ "--shell": ["bash"],
11
+ };
12
+ function walkCommandTree(cmd, parentPath = "") {
13
+ const name = cmd.meta?.name ?? "";
14
+ const currentPath = parentPath ? `${parentPath} ${name}` : name;
15
+ const result = [];
16
+ const subcommands = Object.keys(cmd.subCommands ?? {});
17
+ const flags = [];
18
+ if (cmd.args) {
19
+ for (const [flagName, arg] of Object.entries(cmd.args)) {
20
+ if (arg.type === "positional")
21
+ continue;
22
+ flags.push(`--${flagName}`);
23
+ }
24
+ }
25
+ result.push({ path: currentPath, subcommands, flags });
26
+ if (cmd.subCommands) {
27
+ for (const sub of Object.values(cmd.subCommands)) {
28
+ result.push(...walkCommandTree(sub, currentPath));
29
+ }
30
+ }
31
+ return result;
32
+ }
33
+ // ── Bash completion script generator ─────────────────────────────────────────
34
+ export function generateBashCompletions(cmd) {
35
+ const commands = walkCommandTree(cmd);
36
+ const rootName = cmd.meta?.name ?? "akm";
37
+ // Collect global flags from root command
38
+ const rootInfo = commands.find((c) => c.path === rootName);
39
+ const globalFlags = rootInfo?.flags ?? [];
40
+ // Build the case blocks for subcommand completion
41
+ const caseBlocks = [];
42
+ for (const info of commands) {
43
+ const allFlags = [...new Set([...info.flags, ...globalFlags])];
44
+ if (info.subcommands.length > 0 || allFlags.length > 0) {
45
+ const matchPath = info.path;
46
+ const subcmdStr = info.subcommands.join(" ");
47
+ const flagStr = allFlags.join(" ");
48
+ caseBlocks.push(` "${matchPath}")
49
+ if [[ "\${cur}" == -* ]]; then
50
+ COMPREPLY=( $(compgen -W "${flagStr}" -- "\${cur}") )
51
+ else
52
+ COMPREPLY=( $(compgen -W "${subcmdStr}" -- "\${cur}") )
53
+ fi
54
+ return 0
55
+ ;;`);
56
+ }
57
+ }
58
+ // Build flag-value completion cases
59
+ const valueCases = [];
60
+ for (const [flag, values] of Object.entries(FLAG_VALUES)) {
61
+ valueCases.push(` ${flag})
62
+ COMPREPLY=( $(compgen -W "${values.join(" ")}" -- "\${cur}") )
63
+ return 0
64
+ ;;`);
65
+ }
66
+ const script = `#!/bin/bash
67
+ # Bash completion for ${rootName}
68
+ # Generated by ${rootName} completions
69
+
70
+ _${rootName}() {
71
+ local cur prev words cword
72
+ if type _init_completion &>/dev/null; then
73
+ _init_completion || return
74
+ else
75
+ cur="\${COMP_WORDS[COMP_CWORD]}"
76
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
77
+ words=("\${COMP_WORDS[@]}")
78
+ cword=\${COMP_CWORD}
79
+ fi
80
+
81
+ # Complete flag values
82
+ case "\${prev}" in
83
+ ${valueCases.join("\n")}
84
+ esac
85
+
86
+ # Build the command path from COMP_WORDS
87
+ local cmd_path="${rootName}"
88
+ for (( i=1; i < cword; i++ )); do
89
+ case "\${words[i]}" in
90
+ -*) continue ;;
91
+ *) cmd_path="\${cmd_path} \${words[i]}" ;;
92
+ esac
93
+ done
94
+
95
+ # Complete based on current command path
96
+ case "\${cmd_path}" in
97
+ ${caseBlocks.join("\n")}
98
+ esac
99
+
100
+ return 0
101
+ }
102
+
103
+ complete -F _${rootName} ${rootName}
104
+ `;
105
+ return script;
106
+ }
107
+ // ── Install ──────────────────────────────────────────────────────────────────
108
+ export function installBashCompletions(script) {
109
+ const dest = resolveInstallPath();
110
+ const dir = path.dirname(dest);
111
+ fs.mkdirSync(dir, { recursive: true });
112
+ fs.writeFileSync(dest, script, "utf8");
113
+ return dest;
114
+ }
115
+ function resolveInstallPath() {
116
+ const xdgData = process.env.XDG_DATA_HOME?.trim();
117
+ if (xdgData) {
118
+ return path.join(xdgData, "bash-completion", "completions", "akm");
119
+ }
120
+ const home = os.homedir();
121
+ // Default XDG location
122
+ const defaultXdg = path.join(home, ".local", "share", "bash-completion", "completions", "akm");
123
+ const defaultXdgDir = path.dirname(defaultXdg);
124
+ if (isDir(defaultXdgDir) || isDir(path.dirname(defaultXdgDir))) {
125
+ return defaultXdg;
126
+ }
127
+ // Fallback
128
+ return path.join(home, ".bash_completion.d", "akm");
129
+ }
130
+ function isDir(p) {
131
+ try {
132
+ return fs.statSync(p).isDirectory();
133
+ }
134
+ catch {
135
+ return false;
136
+ }
137
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akm-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "description": "CLI tool to search, open, and run extension assets from an akm stash directory.",
6
6
  "keywords": [