@microsoft/inshellisense 0.0.1-rc.2 → 0.0.1-rc.20

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.
Files changed (53) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +80 -6
  3. package/SECURITY.md +41 -41
  4. package/build/commands/complete.js +16 -0
  5. package/build/commands/doctor.js +11 -0
  6. package/build/commands/init.js +24 -0
  7. package/build/commands/root.js +27 -30
  8. package/build/commands/specs/list.js +26 -0
  9. package/build/commands/specs/root.js +8 -0
  10. package/build/commands/uninstall.js +1 -1
  11. package/build/index.js +20 -7
  12. package/build/isterm/commandManager.js +290 -0
  13. package/build/isterm/index.js +4 -0
  14. package/build/isterm/pty.js +372 -0
  15. package/build/runtime/alias.js +61 -0
  16. package/build/runtime/generator.js +24 -11
  17. package/build/runtime/parser.js +86 -16
  18. package/build/runtime/runtime.js +103 -45
  19. package/build/runtime/suggestion.js +70 -22
  20. package/build/runtime/template.js +33 -18
  21. package/build/runtime/utils.js +111 -12
  22. package/build/ui/suggestionManager.js +162 -0
  23. package/build/ui/ui-doctor.js +69 -0
  24. package/build/ui/ui-root.js +130 -64
  25. package/build/ui/ui-uninstall.js +3 -5
  26. package/build/ui/utils.js +57 -0
  27. package/build/utils/ansi.js +37 -0
  28. package/build/utils/config.js +132 -0
  29. package/build/utils/log.js +39 -0
  30. package/build/utils/shell.js +316 -0
  31. package/package.json +39 -6
  32. package/scripts/postinstall.js +9 -0
  33. package/shell/bash-preexec.sh +380 -0
  34. package/shell/shellIntegration-env.zsh +12 -0
  35. package/shell/shellIntegration-login.zsh +9 -0
  36. package/shell/shellIntegration-profile.zsh +9 -0
  37. package/shell/shellIntegration-rc.zsh +66 -0
  38. package/shell/shellIntegration.bash +125 -0
  39. package/shell/shellIntegration.fish +28 -0
  40. package/shell/shellIntegration.nu +36 -0
  41. package/shell/shellIntegration.ps1 +27 -0
  42. package/shell/shellIntegration.xsh +37 -0
  43. package/build/commands/bind.js +0 -12
  44. package/build/ui/input.js +0 -55
  45. package/build/ui/suggestions.js +0 -84
  46. package/build/ui/ui-bind.js +0 -69
  47. package/build/utils/bindings.js +0 -216
  48. package/build/utils/cache.js +0 -21
  49. package/shell/key-bindings-powershell.ps1 +0 -27
  50. package/shell/key-bindings-pwsh.ps1 +0 -27
  51. package/shell/key-bindings.bash +0 -7
  52. package/shell/key-bindings.fish +0 -8
  53. package/shell/key-bindings.zsh +0 -10
@@ -0,0 +1,61 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+ import log from "../utils/log.js";
4
+ import { gitBashPath, Shell } from "../utils/shell.js";
5
+ import { parseCommand } from "./parser.js";
6
+ import { buildExecuteShellCommand } from "./utils.js";
7
+ import os from "node:os";
8
+ const loadedAliases = {};
9
+ const platform = os.platform();
10
+ const executeShellCommand = await buildExecuteShellCommand(5000);
11
+ const loadBashAliases = async () => {
12
+ const shellTarget = platform == "win32" ? await gitBashPath() : Shell.Bash;
13
+ const { stdout, stderr, status } = await executeShellCommand({ command: shellTarget, args: ["-i", "-c", "alias"], cwd: process.cwd(), env: { ISTERM: "1" } });
14
+ if (status !== 0) {
15
+ log.debug({ msg: "failed to load bash aliases", stderr, status });
16
+ return;
17
+ }
18
+ return stdout
19
+ .trim()
20
+ .split("\n")
21
+ .forEach((line) => {
22
+ const [alias, ...commandSegments] = line.replace("alias ", "").replaceAll("'\\''", "'").split("=");
23
+ loadedAliases[alias] = parseCommand(commandSegments.join("=").slice(1, -1) + " ", Shell.Bash);
24
+ });
25
+ };
26
+ const loadZshAliases = async () => {
27
+ const { stdout, stderr, status } = await executeShellCommand({ command: Shell.Zsh, args: ["-i", "-c", "alias"], cwd: process.cwd(), env: { ISTERM: "1" } });
28
+ if (status !== 0) {
29
+ log.debug({ msg: "failed to load zsh aliases", stderr, status });
30
+ return;
31
+ }
32
+ return stdout
33
+ .trim()
34
+ .split("\n")
35
+ .forEach((line) => {
36
+ const [alias, ...commandSegments] = line.replaceAll("'\\''", "'").split("=");
37
+ loadedAliases[alias] = parseCommand(commandSegments.join("=").slice(1, -1) + " ", Shell.Zsh);
38
+ });
39
+ };
40
+ export const loadAliases = async (shell) => {
41
+ switch (shell) {
42
+ case Shell.Bash:
43
+ await loadBashAliases();
44
+ break;
45
+ case Shell.Zsh:
46
+ await loadZshAliases();
47
+ break;
48
+ }
49
+ return [];
50
+ };
51
+ export const getAliasNames = () => Object.keys(loadedAliases);
52
+ export const aliasExpand = (command) => {
53
+ if (!command.at(0)?.complete)
54
+ return command;
55
+ const alias = loadedAliases[command.at(0)?.token ?? ""];
56
+ if (alias) {
57
+ log.debug({ msg: "expanding alias", alias, command: command.slice(1) });
58
+ return [...alias, ...command.slice(1)];
59
+ }
60
+ return command;
61
+ };
@@ -1,11 +1,12 @@
1
1
  // Copyright (c) Microsoft Corporation.
2
2
  // Licensed under the MIT License.
3
+ import log from "../utils/log.js";
3
4
  import { runTemplates } from "./template.js";
4
5
  import { buildExecuteShellCommand } from "./utils.js";
5
- const getGeneratorContext = () => {
6
+ const getGeneratorContext = (cwd) => {
6
7
  return {
7
8
  environmentVariables: Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] != null)),
8
- currentWorkingDirectory: process.cwd(),
9
+ currentWorkingDirectory: cwd,
9
10
  currentProcess: "",
10
11
  sshPrefix: "",
11
12
  isDangerous: false,
@@ -13,30 +14,42 @@ const getGeneratorContext = () => {
13
14
  };
14
15
  };
15
16
  // TODO: add support for caching, trigger, & getQueryTerm
16
- export const runGenerator = async (generator, tokens) => {
17
- const { script, postProcess, scriptTimeout, splitOn, custom, template } = generator;
18
- const executeShellCommand = buildExecuteShellCommand(scriptTimeout ?? 5000);
17
+ export const runGenerator = async (generator, tokens, cwd) => {
18
+ // TODO: support trigger
19
+ const { script, postProcess, scriptTimeout, splitOn, custom, template, filterTemplateSuggestions } = generator;
20
+ const executeShellCommand = await buildExecuteShellCommand(scriptTimeout ?? 5000);
19
21
  const suggestions = [];
20
22
  try {
21
23
  if (script) {
22
- const scriptOutput = typeof script === "function" ? script(tokens) : script != null ? await executeShellCommand(script) : "";
24
+ const shellInput = typeof script === "function" ? script(tokens) : script;
25
+ const scriptOutput = Array.isArray(shellInput)
26
+ ? await executeShellCommand({ command: shellInput.at(0) ?? "", args: shellInput.slice(1), cwd })
27
+ : await executeShellCommand({ ...shellInput, cwd });
28
+ const scriptStdout = scriptOutput.stdout.trim();
23
29
  if (postProcess) {
24
- suggestions.push(...postProcess(scriptOutput, tokens));
30
+ suggestions.push(...postProcess(scriptStdout, tokens));
25
31
  }
26
32
  else if (splitOn) {
27
- suggestions.push(...scriptOutput.split(splitOn).map((s) => ({ name: s })));
33
+ suggestions.push(...scriptStdout.split(splitOn).map((s) => ({ name: s })));
28
34
  }
29
35
  }
30
36
  if (custom) {
31
- suggestions.push(...(await custom(tokens, executeShellCommand, getGeneratorContext())));
37
+ suggestions.push(...(await custom(tokens, executeShellCommand, getGeneratorContext(cwd))));
32
38
  }
33
39
  if (template != null) {
34
- suggestions.push(...(await runTemplates(template)));
40
+ const templateSuggestions = await runTemplates(template, cwd);
41
+ if (filterTemplateSuggestions) {
42
+ suggestions.push(...filterTemplateSuggestions(templateSuggestions));
43
+ }
44
+ else {
45
+ suggestions.push(...templateSuggestions);
46
+ }
35
47
  }
36
48
  return suggestions;
37
49
  }
38
50
  catch (e) {
39
- /* empty */
51
+ const err = typeof e === "string" ? e : e instanceof Error ? e.message : e;
52
+ log.debug({ msg: "generator failed", err, script, splitOn, template });
40
53
  }
41
54
  return suggestions;
42
55
  };
@@ -1,19 +1,48 @@
1
1
  // Copyright (c) Microsoft Corporation.
2
2
  // Licensed under the MIT License.
3
- const cmdDelim = /(\|\|)|(&&)|(;)/;
3
+ import wcwidth from "wcwidth";
4
+ import { getShellWhitespaceEscapeChar } from "./utils.js";
5
+ const cmdDelim = /(\|\|)|(&&)|(;)|(\|)/;
4
6
  const spaceRegex = /\s/;
5
- export const parseCommand = (command) => {
7
+ export const parseCommand = (command, shell) => {
6
8
  const lastCommand = command.split(cmdDelim).at(-1)?.trimStart();
7
- return lastCommand ? lex(lastCommand) : [];
9
+ const tokens = lastCommand ? lex(lastCommand, shell) : [];
10
+ return sanitizeTokens(tokens, shell);
8
11
  };
9
- const lex = (command) => {
12
+ const sanitizeTokens = (cmdTokens, shell) => unwrapQuotedTokens(unescapeSpaceTokens(cmdTokens, shell), shell);
13
+ // remove escapes around spaces
14
+ const unescapeSpaceTokens = (cmdTokens, shell) => {
15
+ const escapeChar = getShellWhitespaceEscapeChar(shell);
16
+ return cmdTokens.map((cmdToken) => {
17
+ const { token, isQuoted } = cmdToken;
18
+ if (!isQuoted && token.includes(`${escapeChar} `)) {
19
+ return { ...cmdToken, token: token.replaceAll(`${escapeChar} `, " ") };
20
+ }
21
+ return cmdToken;
22
+ });
23
+ };
24
+ // need to unwrap tokens that are quoted with content after the quotes like `"hello"world`
25
+ const unwrapQuotedTokens = (cmdTokens, shell) => {
26
+ const escapeChar = getShellWhitespaceEscapeChar(shell);
27
+ return cmdTokens.map((cmdToken) => {
28
+ const { token, isQuoteContinued } = cmdToken;
29
+ if (isQuoteContinued) {
30
+ const quoteChar = token[0];
31
+ const unquotedToken = token.replaceAll(`${escapeChar}${quoteChar}`, "\u001B").replaceAll(quoteChar, "").replaceAll("\u001B", quoteChar);
32
+ return { ...cmdToken, token: unquotedToken };
33
+ }
34
+ return cmdToken;
35
+ });
36
+ };
37
+ const lex = (command, shell) => {
10
38
  const tokens = [];
11
- let [readingQuotedString, readingFlag, readingCmd] = [false, false, false];
39
+ const escapeChar = getShellWhitespaceEscapeChar(shell);
40
+ let [readingQuotedString, readingQuoteContinuedString, readingFlag, readingCmd] = [false, false, false, false];
12
41
  let readingIdx = 0;
13
42
  let readingQuoteChar = "";
14
43
  [...command].forEach((char, idx) => {
15
- const reading = readingQuotedString || readingFlag || readingCmd;
16
- if (!reading && (char === `'` || char === `"`)) {
44
+ const reading = readingQuotedString || readingQuoteContinuedString || readingFlag || readingCmd;
45
+ if (!reading && (char === `'` || char === `"` || char == "`")) {
17
46
  [readingQuotedString, readingIdx, readingQuoteChar] = [true, idx, char];
18
47
  return;
19
48
  }
@@ -25,39 +54,80 @@ const lex = (command) => {
25
54
  [readingCmd, readingIdx] = [true, idx];
26
55
  return;
27
56
  }
28
- if (readingQuotedString && char === readingQuoteChar && command.at(idx - 1) !== "\\") {
57
+ if (readingQuotedString && char === readingQuoteChar && command.at(idx - 1) !== escapeChar && !spaceRegex.test(command.at(idx + 1) ?? " ")) {
58
+ readingQuotedString = false;
59
+ readingQuoteContinuedString = true;
60
+ }
61
+ else if (readingQuotedString && char === readingQuoteChar && command.at(idx - 1) !== escapeChar) {
29
62
  readingQuotedString = false;
30
63
  const complete = idx + 1 < command.length && spaceRegex.test(command[idx + 1]);
31
64
  tokens.push({
32
- token: command.slice(readingIdx, idx + 1),
65
+ token: command.slice(readingIdx + 1, idx),
66
+ tokenLength: wcwidth(command.slice(readingIdx + 1, idx)) + 2,
33
67
  complete,
34
68
  isOption: false,
69
+ isQuoted: true,
70
+ });
71
+ }
72
+ else if (readingQuoteContinuedString && spaceRegex.test(char) && command.at(idx - 1) !== escapeChar) {
73
+ readingQuoteContinuedString = false;
74
+ tokens.push({
75
+ token: command.slice(readingIdx, idx),
76
+ tokenLength: wcwidth(command.slice(readingIdx, idx)),
77
+ complete: true,
78
+ isOption: false,
79
+ isQuoted: true,
80
+ isQuoteContinued: true,
35
81
  });
36
82
  }
37
83
  else if ((readingFlag && spaceRegex.test(char)) || char === "=") {
38
84
  readingFlag = false;
39
85
  tokens.push({
40
86
  token: command.slice(readingIdx, idx),
87
+ tokenLength: wcwidth(command.slice(readingIdx, idx)),
41
88
  complete: true,
42
89
  isOption: true,
43
90
  });
44
91
  }
45
- else if (readingCmd && spaceRegex.test(char)) {
92
+ else if (readingCmd && spaceRegex.test(char) && command.at(idx - 1) !== escapeChar) {
46
93
  readingCmd = false;
47
94
  tokens.push({
48
95
  token: command.slice(readingIdx, idx),
96
+ tokenLength: wcwidth(command.slice(readingIdx, idx)),
49
97
  complete: true,
50
98
  isOption: false,
51
99
  });
52
100
  }
53
101
  });
54
- const reading = readingQuotedString || readingFlag || readingCmd;
102
+ const reading = readingQuotedString || readingQuoteContinuedString || readingFlag || readingCmd;
55
103
  if (reading) {
56
- tokens.push({
57
- token: command.slice(readingIdx),
58
- complete: false,
59
- isOption: false,
60
- });
104
+ if (readingQuotedString) {
105
+ tokens.push({
106
+ token: command.slice(readingIdx + 1),
107
+ tokenLength: wcwidth(command.slice(readingIdx + 1)) + 1,
108
+ complete: false,
109
+ isOption: false,
110
+ isQuoted: true,
111
+ });
112
+ }
113
+ else if (readingQuoteContinuedString) {
114
+ tokens.push({
115
+ token: command.slice(readingIdx),
116
+ tokenLength: wcwidth(command.slice(readingIdx)),
117
+ complete: false,
118
+ isOption: false,
119
+ isQuoted: true,
120
+ isQuoteContinued: true,
121
+ });
122
+ }
123
+ else {
124
+ tokens.push({
125
+ token: command.slice(readingIdx),
126
+ tokenLength: wcwidth(command.slice(readingIdx)),
127
+ complete: false,
128
+ isOption: readingFlag,
129
+ });
130
+ }
61
131
  }
62
132
  return tokens;
63
133
  };
@@ -4,25 +4,35 @@ import speclist, { diffVersionedCompletions as versionedSpeclist,
4
4
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
5
5
  // @ts-ignore
6
6
  } from "@withfig/autocomplete/build/index.js";
7
+ import path from "node:path";
7
8
  import { parseCommand } from "./parser.js";
8
9
  import { getArgDrivenRecommendation, getSubcommandDrivenRecommendation } from "./suggestion.js";
9
- import { buildExecuteShellCommand } from "./utils.js";
10
+ import { buildExecuteShellCommand, resolveCwd } from "./utils.js";
11
+ import { aliasExpand } from "./alias.js";
12
+ import { getConfig } from "../utils/config.js";
13
+ import log from "../utils/log.js";
10
14
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- recursive type, setting as any
11
15
  const specSet = {};
12
- speclist.forEach((s) => {
13
- let activeSet = specSet;
14
- const specRoutes = s.split("/");
15
- specRoutes.forEach((route, idx) => {
16
- if (idx === specRoutes.length - 1) {
17
- const prefix = versionedSpeclist.includes(s) ? "/index.js" : `.js`;
18
- activeSet[route] = `@withfig/autocomplete/build/${s}${prefix}`;
19
- }
20
- else {
21
- activeSet[route] = activeSet[route] || {};
22
- activeSet = activeSet[route];
23
- }
16
+ function loadSpecsSet(speclist, versionedSpeclist, specsPath) {
17
+ speclist.forEach((s) => {
18
+ let activeSet = specSet;
19
+ const specRoutes = s.split("/");
20
+ specRoutes.forEach((route, idx) => {
21
+ if (typeof activeSet !== "object") {
22
+ return;
23
+ }
24
+ if (idx === specRoutes.length - 1) {
25
+ const prefix = versionedSpeclist.includes(s) ? "/index.js" : `.js`;
26
+ activeSet[route] = `${specsPath}/${s}${prefix}`;
27
+ }
28
+ else {
29
+ activeSet[route] = activeSet[route] || {};
30
+ activeSet = activeSet[route];
31
+ }
32
+ });
24
33
  });
25
- });
34
+ }
35
+ loadSpecsSet(speclist, versionedSpeclist, `@withfig/autocomplete/build`);
26
36
  const loadedSpecs = {};
27
37
  const loadSpec = async (cmd) => {
28
38
  const rootToken = cmd.at(0);
@@ -46,25 +56,53 @@ const lazyLoadSpec = async (key) => {
46
56
  const lazyLoadSpecLocation = async (location) => {
47
57
  return; //TODO: implement spec location loading
48
58
  };
49
- export const getSuggestions = async (cmd) => {
50
- const activeCmd = parseCommand(cmd);
59
+ export const loadLocalSpecsSet = async () => {
60
+ const specsPath = getConfig()?.specs?.path;
61
+ if (!specsPath) {
62
+ return;
63
+ }
64
+ try {
65
+ await Promise.allSettled(specsPath.map((specPath) => import(path.join(specPath, "index.js"))
66
+ .then((res) => {
67
+ const { default: speclist, diffVersionedCompletions: versionedSpeclist } = res;
68
+ loadSpecsSet(speclist, versionedSpeclist, specPath);
69
+ })
70
+ .catch((e) => {
71
+ log.debug({ msg: "load local spec failed", e: e.message, specPath });
72
+ })));
73
+ }
74
+ catch (e) {
75
+ log.debug({ msg: "load local specs failed", e: e.message, specsPath });
76
+ }
77
+ };
78
+ export const getSuggestions = async (cmd, cwd, shell) => {
79
+ let activeCmd = parseCommand(cmd, shell);
51
80
  const rootToken = activeCmd.at(0);
52
81
  if (activeCmd.length === 0 || !rootToken?.complete) {
53
82
  return;
54
83
  }
84
+ activeCmd = aliasExpand(activeCmd);
55
85
  const spec = await loadSpec(activeCmd);
56
86
  if (spec == null)
57
87
  return;
58
88
  const subcommand = getSubcommand(spec);
59
89
  if (subcommand == null)
60
90
  return;
61
- const result = await runSubcommand(activeCmd.slice(1), subcommand);
91
+ const lastCommand = activeCmd.at(-1);
92
+ const { cwd: resolvedCwd, pathy, complete: pathyComplete } = await resolveCwd(lastCommand, cwd, shell);
93
+ if (pathy && lastCommand) {
94
+ lastCommand.isPath = true;
95
+ lastCommand.isPathComplete = pathyComplete;
96
+ }
97
+ const result = await runSubcommand(activeCmd.slice(1), subcommand, resolvedCwd, shell);
62
98
  if (result == null)
63
99
  return;
64
- const lastCommand = activeCmd.at(-1);
65
- const charactersToDrop = lastCommand?.complete ? 0 : lastCommand?.token.length ?? 0;
100
+ const charactersToDrop = lastCommand?.complete ? 0 : lastCommand?.tokenLength;
66
101
  return { ...result, charactersToDrop };
67
102
  };
103
+ export const getSpecNames = () => {
104
+ return Object.keys(specSet).filter((spec) => !spec.startsWith("@") && spec != "-");
105
+ };
68
106
  const getPersistentOptions = (persistentOptions, options) => {
69
107
  const persistentOptionNames = new Set(persistentOptions.map((o) => (typeof o.name === "string" ? [o.name] : o.name)).flat());
70
108
  return persistentOptions.concat((options ?? []).filter((o) => (typeof o.name == "string" ? !persistentOptionNames.has(o.name) : o.name.some((n) => !persistentOptionNames.has(n))) && o.isPersistent === true));
@@ -82,14 +120,14 @@ const getSubcommand = (spec) => {
82
120
  }
83
121
  return spec;
84
122
  };
85
- const executeShellCommand = buildExecuteShellCommand(5000);
123
+ const executeShellCommand = await buildExecuteShellCommand(5000);
86
124
  const genSubcommand = async (command, parentCommand) => {
87
- const subcommandIdx = parentCommand.subcommands?.findIndex((s) => s.name === command);
88
- if (subcommandIdx == null)
125
+ if (!parentCommand.subcommands || parentCommand.subcommands.length === 0)
89
126
  return;
90
- const subcommand = parentCommand.subcommands?.at(subcommandIdx);
91
- if (subcommand == null)
127
+ const subcommandIdx = parentCommand.subcommands.findIndex((s) => (Array.isArray(s.name) ? s.name.includes(command) : s.name === command));
128
+ if (subcommandIdx === -1)
92
129
  return;
130
+ const subcommand = parentCommand.subcommands[subcommandIdx];
93
131
  // this pulls in the spec from the load spec and overwrites the subcommand in the parent with the loaded spec.
94
132
  // then it returns the subcommand and clears the loadSpec field so that it doesn't get called again
95
133
  switch (typeof subcommand.loadSpec) {
@@ -115,17 +153,29 @@ const genSubcommand = async (command, parentCommand) => {
115
153
  return parentCommand.subcommands[subcommandIdx];
116
154
  }
117
155
  else {
118
- parentCommand.subcommands[subcommandIdx] = { ...subcommand, ...partSpec, loadSpec: undefined };
156
+ parentCommand.subcommands[subcommandIdx] = {
157
+ ...subcommand,
158
+ ...partSpec,
159
+ loadSpec: undefined,
160
+ };
119
161
  return parentCommand.subcommands[subcommandIdx];
120
162
  }
121
163
  }
122
164
  case "string": {
123
165
  const spec = await lazyLoadSpec(subcommand.loadSpec);
124
- parentCommand.subcommands[subcommandIdx] = { ...subcommand, ...(getSubcommand(spec) ?? []), loadSpec: undefined };
166
+ parentCommand.subcommands[subcommandIdx] = {
167
+ ...subcommand,
168
+ ...(getSubcommand(spec) ?? []),
169
+ loadSpec: undefined,
170
+ };
125
171
  return parentCommand.subcommands[subcommandIdx];
126
172
  }
127
173
  case "object": {
128
- parentCommand.subcommands[subcommandIdx] = { ...subcommand, ...(subcommand.loadSpec ?? {}), loadSpec: undefined };
174
+ parentCommand.subcommands[subcommandIdx] = {
175
+ ...subcommand,
176
+ ...(subcommand.loadSpec ?? {}),
177
+ loadSpec: undefined,
178
+ };
129
179
  return parentCommand.subcommands[subcommandIdx];
130
180
  }
131
181
  case "undefined": {
@@ -142,7 +192,7 @@ const getPersistentTokens = (tokens) => {
142
192
  const getArgs = (args) => {
143
193
  return args instanceof Array ? args : args != null ? [args] : [];
144
194
  };
145
- const runOption = async (tokens, option, subcommand, persistentOptions, acceptedTokens) => {
195
+ const runOption = async (tokens, option, subcommand, cwd, shell, persistentOptions, acceptedTokens) => {
146
196
  if (tokens.length === 0) {
147
197
  throw new Error("invalid state reached, option expected but no tokens found");
148
198
  }
@@ -150,37 +200,40 @@ const runOption = async (tokens, option, subcommand, persistentOptions, accepted
150
200
  const isPersistent = persistentOptions.some((o) => (typeof o.name === "string" ? o.name === activeToken.token : o.name.includes(activeToken.token)));
151
201
  if ((option.args instanceof Array && option.args.length > 0) || option.args != null) {
152
202
  const args = option.args instanceof Array ? option.args : [option.args];
153
- return runArg(tokens.slice(1), args, subcommand, persistentOptions, acceptedTokens.concat(activeToken), true, false);
203
+ return runArg(tokens.slice(1), args, subcommand, cwd, shell, persistentOptions, acceptedTokens.concat(activeToken), true, false);
154
204
  }
155
- return runSubcommand(tokens.slice(1), subcommand, persistentOptions, acceptedTokens.concat({ ...activeToken, isPersistent }));
205
+ return runSubcommand(tokens.slice(1), subcommand, cwd, shell, persistentOptions, acceptedTokens.concat({
206
+ ...activeToken,
207
+ isPersistent,
208
+ }));
156
209
  };
157
- const runArg = async (tokens, args, subcommand, persistentOptions, acceptedTokens, fromOption, fromVariadic) => {
210
+ const runArg = async (tokens, args, subcommand, cwd, shell, persistentOptions, acceptedTokens, fromOption, fromVariadic) => {
158
211
  if (args.length === 0) {
159
- return runSubcommand(tokens, subcommand, persistentOptions, acceptedTokens, true, !fromOption);
212
+ return runSubcommand(tokens, subcommand, cwd, shell, persistentOptions, acceptedTokens, true, !fromOption);
160
213
  }
161
214
  else if (tokens.length === 0) {
162
- return await getArgDrivenRecommendation(args, subcommand, persistentOptions, undefined, acceptedTokens, fromVariadic);
215
+ return await getArgDrivenRecommendation(args, subcommand, persistentOptions, undefined, acceptedTokens, fromVariadic, cwd, shell);
163
216
  }
164
217
  else if (!tokens.at(0)?.complete) {
165
- return await getArgDrivenRecommendation(args, subcommand, persistentOptions, tokens[0].token, acceptedTokens, fromVariadic);
218
+ return await getArgDrivenRecommendation(args, subcommand, persistentOptions, tokens[0], acceptedTokens, fromVariadic, cwd, shell);
166
219
  }
167
220
  const activeToken = tokens[0];
168
221
  if (args.every((a) => a.isOptional)) {
169
222
  if (activeToken.isOption) {
170
223
  const option = getOption(activeToken, persistentOptions.concat(subcommand.options ?? []));
171
224
  if (option != null) {
172
- return runOption(tokens, option, subcommand, persistentOptions, acceptedTokens);
225
+ return runOption(tokens, option, subcommand, cwd, shell, persistentOptions, acceptedTokens);
173
226
  }
174
227
  return;
175
228
  }
176
229
  const nextSubcommand = await genSubcommand(activeToken.token, subcommand);
177
230
  if (nextSubcommand != null) {
178
- return runSubcommand(tokens.slice(1), nextSubcommand, persistentOptions, getPersistentTokens(acceptedTokens.concat(activeToken)));
231
+ return runSubcommand(tokens.slice(1), nextSubcommand, cwd, shell, persistentOptions, getPersistentTokens(acceptedTokens.concat(activeToken)));
179
232
  }
180
233
  }
181
234
  const activeArg = args[0];
182
235
  if (activeArg.isVariadic) {
183
- return runArg(tokens.slice(1), args, subcommand, persistentOptions, acceptedTokens.concat(activeToken), fromOption, true);
236
+ return runArg(tokens.slice(1), args, subcommand, cwd, shell, persistentOptions, acceptedTokens.concat(activeToken), fromOption, true);
184
237
  }
185
238
  else if (activeArg.isCommand) {
186
239
  if (tokens.length <= 0) {
@@ -192,16 +245,16 @@ const runArg = async (tokens, args, subcommand, persistentOptions, acceptedToken
192
245
  const subcommand = getSubcommand(spec);
193
246
  if (subcommand == null)
194
247
  return;
195
- return runSubcommand(tokens.slice(1), subcommand);
248
+ return runSubcommand(tokens.slice(1), subcommand, cwd, shell);
196
249
  }
197
- return runArg(tokens.slice(1), args.slice(1), subcommand, persistentOptions, acceptedTokens.concat(activeToken), fromOption, false);
250
+ return runArg(tokens.slice(1), args.slice(1), subcommand, cwd, shell, persistentOptions, acceptedTokens.concat(activeToken), fromOption, false);
198
251
  };
199
- const runSubcommand = async (tokens, subcommand, persistentOptions = [], acceptedTokens = [], argsDepleted = false, argsUsed = false) => {
252
+ const runSubcommand = async (tokens, subcommand, cwd, shell, persistentOptions = [], acceptedTokens = [], argsDepleted = false, argsUsed = false) => {
200
253
  if (tokens.length === 0) {
201
- return getSubcommandDrivenRecommendation(subcommand, persistentOptions, undefined, argsDepleted, argsUsed, acceptedTokens);
254
+ return getSubcommandDrivenRecommendation(subcommand, persistentOptions, undefined, argsDepleted, argsUsed, acceptedTokens, cwd, shell);
202
255
  }
203
256
  else if (!tokens.at(0)?.complete) {
204
- return getSubcommandDrivenRecommendation(subcommand, persistentOptions, tokens[0].token, argsDepleted, argsUsed, acceptedTokens);
257
+ return getSubcommandDrivenRecommendation(subcommand, persistentOptions, tokens[0], argsDepleted, argsUsed, acceptedTokens, cwd, shell);
205
258
  }
206
259
  const activeToken = tokens[0];
207
260
  const activeArgsLength = subcommand.args instanceof Array ? subcommand.args.length : 1;
@@ -209,16 +262,21 @@ const runSubcommand = async (tokens, subcommand, persistentOptions = [], accepte
209
262
  if (activeToken.isOption) {
210
263
  const option = getOption(activeToken, allOptions);
211
264
  if (option != null) {
212
- return runOption(tokens, option, subcommand, persistentOptions, acceptedTokens);
265
+ return runOption(tokens, option, subcommand, cwd, shell, persistentOptions, acceptedTokens);
213
266
  }
214
267
  return;
215
268
  }
216
269
  const nextSubcommand = await genSubcommand(activeToken.token, subcommand);
217
270
  if (nextSubcommand != null) {
218
- return runSubcommand(tokens.slice(1), nextSubcommand, getPersistentOptions(persistentOptions, subcommand.options), getPersistentTokens(acceptedTokens.concat(activeToken)));
271
+ return runSubcommand(tokens.slice(1), nextSubcommand, cwd, shell, getPersistentOptions(persistentOptions, subcommand.options), getPersistentTokens(acceptedTokens.concat(activeToken)));
219
272
  }
220
273
  if (activeArgsLength <= 0) {
221
274
  return; // not subcommand or option & no args exist
222
275
  }
223
- return runArg(tokens, getArgs(subcommand.args), subcommand, allOptions, acceptedTokens, false, false);
276
+ const args = getArgs(subcommand.args);
277
+ if (args.length != 0) {
278
+ return runArg(tokens, args, subcommand, cwd, shell, allOptions, acceptedTokens, false, false);
279
+ }
280
+ // if the subcommand has no args specified, fallback to the subcommand and ignore this item
281
+ return runSubcommand(tokens.slice(1), subcommand, cwd, shell, persistentOptions, acceptedTokens.concat(activeToken));
224
282
  };