zinkee 0.1.10 → 0.1.11
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/.claude/settings.local.json +29 -1
- package/AGENTS.md +1 -0
- package/README.md +12 -0
- package/dist/index.js +544 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli-runner.ts +18 -0
- package/src/completion/engine.test.ts +138 -0
- package/src/completion/engine.ts +168 -0
- package/src/completion/install.test.ts +179 -0
- package/src/completion/install.ts +260 -0
- package/src/completion/runtime.ts +150 -0
- package/src/completion/scripts.ts +91 -0
- package/src/program.ts +3 -1
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir, platform } from "node:os";
|
|
4
|
+
import { basename, dirname, join } from "node:path";
|
|
5
|
+
|
|
6
|
+
import { renderScript, type SupportedShell } from "./scripts.js";
|
|
7
|
+
|
|
8
|
+
export const SUPPORTED_SHELLS: readonly SupportedShell[] = [
|
|
9
|
+
"bash",
|
|
10
|
+
"zsh",
|
|
11
|
+
"fish",
|
|
12
|
+
"powershell",
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const BLOCK_BEGIN = "# >>> zinkee completion >>>";
|
|
16
|
+
const BLOCK_END = "# <<< zinkee completion <<<";
|
|
17
|
+
|
|
18
|
+
export interface InstallEnvironment {
|
|
19
|
+
home: string;
|
|
20
|
+
platform: NodeJS.Platform;
|
|
21
|
+
shellEnv: string | undefined;
|
|
22
|
+
resolvePowerShellProfile?: () => string | undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface InstallTarget {
|
|
26
|
+
shell: SupportedShell;
|
|
27
|
+
scriptPath: string;
|
|
28
|
+
rcPath?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface InstallResult {
|
|
32
|
+
shell: SupportedShell;
|
|
33
|
+
scriptPath: string;
|
|
34
|
+
rcPath?: string;
|
|
35
|
+
rcUpdated: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class CompletionShellError extends Error {
|
|
39
|
+
override name = "CompletionShellError";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function defaultPowerShellProfile(env: InstallEnvironment): string | undefined {
|
|
43
|
+
for (const exe of ["pwsh", "powershell.exe"]) {
|
|
44
|
+
try {
|
|
45
|
+
const out = execFileSync(exe, ["-NoProfile", "-Command", "$PROFILE"], {
|
|
46
|
+
encoding: "utf8",
|
|
47
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
48
|
+
});
|
|
49
|
+
const path = out.trim();
|
|
50
|
+
if (path) {
|
|
51
|
+
return path;
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
// try the next executable
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (env.platform === "win32") {
|
|
59
|
+
return join(env.home, "Documents", "PowerShell", "Microsoft.PowerShell_profile.ps1");
|
|
60
|
+
}
|
|
61
|
+
return join(env.home, ".config", "powershell", "Microsoft.PowerShell_profile.ps1");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function detectShell(env: InstallEnvironment): SupportedShell {
|
|
65
|
+
const fromEnv = env.shellEnv;
|
|
66
|
+
if (fromEnv) {
|
|
67
|
+
const name = basename(fromEnv).toLowerCase().replace(/\.exe$/, "");
|
|
68
|
+
if (name === "bash") {
|
|
69
|
+
return "bash";
|
|
70
|
+
}
|
|
71
|
+
if (name === "zsh") {
|
|
72
|
+
return "zsh";
|
|
73
|
+
}
|
|
74
|
+
if (name === "fish") {
|
|
75
|
+
return "fish";
|
|
76
|
+
}
|
|
77
|
+
if (name === "pwsh" || name === "powershell") {
|
|
78
|
+
return "powershell";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (env.platform === "win32") {
|
|
82
|
+
return "powershell";
|
|
83
|
+
}
|
|
84
|
+
throw new CompletionShellError(
|
|
85
|
+
`Could not detect a supported shell from $SHELL=${fromEnv ?? ""}. ` +
|
|
86
|
+
`Pass one of: ${SUPPORTED_SHELLS.join(", ")}.`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function parseShell(value: string): SupportedShell {
|
|
91
|
+
const normalized = value.toLowerCase();
|
|
92
|
+
if (
|
|
93
|
+
normalized === "bash" ||
|
|
94
|
+
normalized === "zsh" ||
|
|
95
|
+
normalized === "fish" ||
|
|
96
|
+
normalized === "powershell"
|
|
97
|
+
) {
|
|
98
|
+
return normalized;
|
|
99
|
+
}
|
|
100
|
+
if (normalized === "pwsh") {
|
|
101
|
+
return "powershell";
|
|
102
|
+
}
|
|
103
|
+
throw new CompletionShellError(
|
|
104
|
+
`Unsupported shell "${value}". Supported: ${SUPPORTED_SHELLS.join(", ")}.`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function resolveTarget(
|
|
109
|
+
shell: SupportedShell,
|
|
110
|
+
env: InstallEnvironment,
|
|
111
|
+
): InstallTarget {
|
|
112
|
+
switch (shell) {
|
|
113
|
+
case "bash":
|
|
114
|
+
return {
|
|
115
|
+
shell,
|
|
116
|
+
scriptPath: join(env.home, ".bash_completions", "zinkee.sh"),
|
|
117
|
+
rcPath: join(env.home, ".bashrc"),
|
|
118
|
+
};
|
|
119
|
+
case "zsh":
|
|
120
|
+
return {
|
|
121
|
+
shell,
|
|
122
|
+
scriptPath: join(env.home, ".zfunc", "_zinkee"),
|
|
123
|
+
rcPath: join(env.home, ".zshrc"),
|
|
124
|
+
};
|
|
125
|
+
case "fish":
|
|
126
|
+
return {
|
|
127
|
+
shell,
|
|
128
|
+
scriptPath: join(env.home, ".config", "fish", "completions", "zinkee.fish"),
|
|
129
|
+
};
|
|
130
|
+
case "powershell": {
|
|
131
|
+
const resolver = env.resolvePowerShellProfile ?? (() => defaultPowerShellProfile(env));
|
|
132
|
+
const profilePath = resolver();
|
|
133
|
+
if (!profilePath) {
|
|
134
|
+
throw new CompletionShellError(
|
|
135
|
+
"Could not resolve PowerShell $PROFILE path. Install PowerShell or use --show-completion.",
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
shell,
|
|
140
|
+
scriptPath: profilePath,
|
|
141
|
+
rcPath: profilePath,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function buildRcSnippet(shell: SupportedShell, scriptPath: string): string {
|
|
148
|
+
switch (shell) {
|
|
149
|
+
case "bash":
|
|
150
|
+
return `source "${scriptPath}"`;
|
|
151
|
+
case "zsh":
|
|
152
|
+
return [
|
|
153
|
+
`fpath=("${dirname(scriptPath)}" $fpath)`,
|
|
154
|
+
"autoload -Uz compinit && compinit",
|
|
155
|
+
].join("\n");
|
|
156
|
+
default:
|
|
157
|
+
return "";
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function applyBlock(existing: string, block: string): string {
|
|
162
|
+
const wrapped = `${BLOCK_BEGIN}\n${block.trimEnd()}\n${BLOCK_END}\n`;
|
|
163
|
+
const blockPattern = new RegExp(
|
|
164
|
+
`${BLOCK_BEGIN.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${BLOCK_END.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`,
|
|
165
|
+
"m",
|
|
166
|
+
);
|
|
167
|
+
if (blockPattern.test(existing)) {
|
|
168
|
+
return existing.replace(blockPattern, wrapped);
|
|
169
|
+
}
|
|
170
|
+
const separator = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
|
|
171
|
+
return `${existing}${separator}${wrapped}`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function readFileOrEmpty(path: string): string {
|
|
175
|
+
try {
|
|
176
|
+
return readFileSync(path, "utf8");
|
|
177
|
+
} catch (error) {
|
|
178
|
+
if (
|
|
179
|
+
typeof error === "object" &&
|
|
180
|
+
error !== null &&
|
|
181
|
+
"code" in error &&
|
|
182
|
+
(error as NodeJS.ErrnoException).code === "ENOENT"
|
|
183
|
+
) {
|
|
184
|
+
return "";
|
|
185
|
+
}
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function writeFileEnsuringDir(path: string, content: string): void {
|
|
191
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
192
|
+
writeFileSync(path, content, "utf8");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function installCompletion(
|
|
196
|
+
shell: SupportedShell,
|
|
197
|
+
env: InstallEnvironment,
|
|
198
|
+
): InstallResult {
|
|
199
|
+
const target = resolveTarget(shell, env);
|
|
200
|
+
const script = renderScript(shell);
|
|
201
|
+
|
|
202
|
+
if (shell === "powershell") {
|
|
203
|
+
const existing = readFileOrEmpty(target.scriptPath);
|
|
204
|
+
const updated = applyBlock(existing, script);
|
|
205
|
+
writeFileEnsuringDir(target.scriptPath, updated);
|
|
206
|
+
return {
|
|
207
|
+
shell,
|
|
208
|
+
scriptPath: target.scriptPath,
|
|
209
|
+
rcPath: target.scriptPath,
|
|
210
|
+
rcUpdated: true,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
writeFileEnsuringDir(target.scriptPath, script);
|
|
215
|
+
|
|
216
|
+
if (!target.rcPath) {
|
|
217
|
+
return {
|
|
218
|
+
shell,
|
|
219
|
+
scriptPath: target.scriptPath,
|
|
220
|
+
rcUpdated: false,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const snippet = buildRcSnippet(shell, target.scriptPath);
|
|
225
|
+
if (!snippet) {
|
|
226
|
+
return {
|
|
227
|
+
shell,
|
|
228
|
+
scriptPath: target.scriptPath,
|
|
229
|
+
rcPath: target.rcPath,
|
|
230
|
+
rcUpdated: false,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const existingRc = readFileOrEmpty(target.rcPath);
|
|
235
|
+
const updatedRc = applyBlock(existingRc, snippet);
|
|
236
|
+
if (updatedRc !== existingRc) {
|
|
237
|
+
writeFileEnsuringDir(target.rcPath, updatedRc);
|
|
238
|
+
return {
|
|
239
|
+
shell,
|
|
240
|
+
scriptPath: target.scriptPath,
|
|
241
|
+
rcPath: target.rcPath,
|
|
242
|
+
rcUpdated: true,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
shell,
|
|
248
|
+
scriptPath: target.scriptPath,
|
|
249
|
+
rcPath: target.rcPath,
|
|
250
|
+
rcUpdated: false,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function currentEnvironment(): InstallEnvironment {
|
|
255
|
+
return {
|
|
256
|
+
home: homedir(),
|
|
257
|
+
platform: platform(),
|
|
258
|
+
shellEnv: process.env.SHELL,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { Writable } from "node:stream";
|
|
2
|
+
|
|
3
|
+
import { buildProgram } from "../program.js";
|
|
4
|
+
import { getCompletions } from "./engine.js";
|
|
5
|
+
import {
|
|
6
|
+
CompletionShellError,
|
|
7
|
+
currentEnvironment,
|
|
8
|
+
detectShell,
|
|
9
|
+
installCompletion,
|
|
10
|
+
parseShell,
|
|
11
|
+
type InstallEnvironment,
|
|
12
|
+
} from "./install.js";
|
|
13
|
+
import { renderScript, type SupportedShell } from "./scripts.js";
|
|
14
|
+
|
|
15
|
+
export const COMPLETION_ENV_VAR = "_ZINKEE_COMPLETE";
|
|
16
|
+
|
|
17
|
+
interface Stream {
|
|
18
|
+
write(chunk: string): unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getStream(stream: Stream | undefined, fallback: NodeJS.WriteStream): Stream {
|
|
22
|
+
return stream ?? (fallback as unknown as Writable);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseEnvWords(env: NodeJS.ProcessEnv): string[] {
|
|
26
|
+
const raw = env.COMP_WORDS ?? "";
|
|
27
|
+
if (raw.trim().length === 0) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
return raw.split(/\s+/).filter((token) => token.length > 0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseEnvCwordIndex(env: NodeJS.ProcessEnv): number {
|
|
34
|
+
const raw = env.COMP_CWORD ?? "0";
|
|
35
|
+
const parsed = Number.parseInt(raw, 10);
|
|
36
|
+
if (Number.isNaN(parsed) || parsed < 0) {
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isCompletionRequest(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
43
|
+
return typeof env[COMPLETION_ENV_VAR] === "string" && env[COMPLETION_ENV_VAR]!.length > 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function runCompletionRequest(
|
|
47
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
48
|
+
stdout?: Stream,
|
|
49
|
+
): void {
|
|
50
|
+
const out = getStream(stdout, process.stdout);
|
|
51
|
+
try {
|
|
52
|
+
const program = buildProgram();
|
|
53
|
+
const candidates = getCompletions(program, {
|
|
54
|
+
words: parseEnvWords(env),
|
|
55
|
+
cwordIndex: parseEnvCwordIndex(env),
|
|
56
|
+
});
|
|
57
|
+
if (candidates.length > 0) {
|
|
58
|
+
out.write(`${candidates.join("\n")}\n`);
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
// Completion must never crash the user's shell. Swallow.
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface CompletionFlagRequest {
|
|
66
|
+
mode: "install" | "show";
|
|
67
|
+
shellOverride?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function extractCompletionFlag(argv: readonly string[]): CompletionFlagRequest | null {
|
|
71
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
72
|
+
const token = argv[index];
|
|
73
|
+
if (!token) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (token === "--install-completion" || token === "--show-completion") {
|
|
77
|
+
const mode = token === "--install-completion" ? "install" : "show";
|
|
78
|
+
const next = argv[index + 1];
|
|
79
|
+
const shellOverride = next && !next.startsWith("-") ? next : undefined;
|
|
80
|
+
return { mode, shellOverride };
|
|
81
|
+
}
|
|
82
|
+
if (token.startsWith("--install-completion=")) {
|
|
83
|
+
const value = token.slice("--install-completion=".length);
|
|
84
|
+
return { mode: "install", shellOverride: value || undefined };
|
|
85
|
+
}
|
|
86
|
+
if (token.startsWith("--show-completion=")) {
|
|
87
|
+
const value = token.slice("--show-completion=".length);
|
|
88
|
+
return { mode: "show", shellOverride: value || undefined };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function resolveShell(
|
|
95
|
+
request: CompletionFlagRequest,
|
|
96
|
+
env: InstallEnvironment,
|
|
97
|
+
): SupportedShell {
|
|
98
|
+
if (request.shellOverride) {
|
|
99
|
+
return parseShell(request.shellOverride);
|
|
100
|
+
}
|
|
101
|
+
return detectShell(env);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface CompletionFlagDeps {
|
|
105
|
+
environment?: InstallEnvironment;
|
|
106
|
+
stdout?: Stream;
|
|
107
|
+
stderr?: Stream;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function handleCompletionFlag(
|
|
111
|
+
request: CompletionFlagRequest,
|
|
112
|
+
deps: CompletionFlagDeps = {},
|
|
113
|
+
): number {
|
|
114
|
+
const env = deps.environment ?? currentEnvironment();
|
|
115
|
+
const out = getStream(deps.stdout, process.stdout);
|
|
116
|
+
const err = getStream(deps.stderr, process.stderr);
|
|
117
|
+
|
|
118
|
+
let shell: SupportedShell;
|
|
119
|
+
try {
|
|
120
|
+
shell = resolveShell(request, env);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
const message = error instanceof CompletionShellError ? error.message : String(error);
|
|
123
|
+
err.write(`${message}\n`);
|
|
124
|
+
return 1;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (request.mode === "show") {
|
|
128
|
+
out.write(renderScript(shell));
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const result = installCompletion(shell, env);
|
|
134
|
+
err.write(`zinkee completion installed for ${shell}.\n`);
|
|
135
|
+
err.write(` script: ${result.scriptPath}\n`);
|
|
136
|
+
if (result.rcPath && result.rcPath !== result.scriptPath) {
|
|
137
|
+
err.write(
|
|
138
|
+
result.rcUpdated
|
|
139
|
+
? ` updated: ${result.rcPath}\n`
|
|
140
|
+
: ` unchanged: ${result.rcPath}\n`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
err.write(`Restart your shell or open a new session to enable completion.\n`);
|
|
144
|
+
return 0;
|
|
145
|
+
} catch (error) {
|
|
146
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
147
|
+
err.write(`Failed to install zinkee completion: ${message}\n`);
|
|
148
|
+
return 1;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export type SupportedShell = "bash" | "zsh" | "fish" | "powershell";
|
|
2
|
+
|
|
3
|
+
const PROGRAM = "zinkee";
|
|
4
|
+
const ENV_VAR = "_ZINKEE_COMPLETE";
|
|
5
|
+
|
|
6
|
+
export function renderBashScript(): string {
|
|
7
|
+
return `_zinkee_completion() {
|
|
8
|
+
local IFS=$'\\n'
|
|
9
|
+
local response
|
|
10
|
+
|
|
11
|
+
response=$(env COMP_WORDS="\${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD ${ENV_VAR}=bash_complete ${PROGRAM} 2>/dev/null)
|
|
12
|
+
|
|
13
|
+
COMPREPLY=( \${response} )
|
|
14
|
+
return 0
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
complete -o default -F _zinkee_completion ${PROGRAM}
|
|
18
|
+
`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function renderZshScript(): string {
|
|
22
|
+
return `#compdef ${PROGRAM}
|
|
23
|
+
|
|
24
|
+
_zinkee_completion() {
|
|
25
|
+
local -a response
|
|
26
|
+
(( ! $+commands[${PROGRAM}] )) && return 1
|
|
27
|
+
|
|
28
|
+
response=("\${(@f)$(env COMP_WORDS="\${words[*]}" COMP_CWORD=$((CURRENT - 1)) ${ENV_VAR}=zsh_complete ${PROGRAM} 2>/dev/null)}")
|
|
29
|
+
|
|
30
|
+
if [ -n "$response" ]; then
|
|
31
|
+
compadd -U -V unsorted -a response
|
|
32
|
+
fi
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if [[ $zsh_eval_context[-1] == loadautofunc ]]; then
|
|
36
|
+
_zinkee_completion "$@"
|
|
37
|
+
else
|
|
38
|
+
compdef _zinkee_completion ${PROGRAM}
|
|
39
|
+
fi
|
|
40
|
+
`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function renderFishScript(): string {
|
|
44
|
+
return `function __zinkee_completion
|
|
45
|
+
set -l tokens (commandline -opc) (commandline -ct)
|
|
46
|
+
set -l cword (math (count $tokens) - 1)
|
|
47
|
+
set -l joined (string join " " -- $tokens)
|
|
48
|
+
|
|
49
|
+
env COMP_WORDS="$joined" COMP_CWORD=$cword ${ENV_VAR}=fish_complete ${PROGRAM} 2>/dev/null
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
complete --no-files --command ${PROGRAM} --arguments '(__zinkee_completion)'
|
|
53
|
+
`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function renderPowerShellScript(): string {
|
|
57
|
+
return `Register-ArgumentCompleter -Native -CommandName ${PROGRAM} -ScriptBlock {
|
|
58
|
+
param($wordToComplete, $commandAst, $cursorPosition)
|
|
59
|
+
|
|
60
|
+
$tokens = @($commandAst.CommandElements | ForEach-Object { $_.ToString() })
|
|
61
|
+
$cword = [Math]::Max(0, $tokens.Count - 1)
|
|
62
|
+
|
|
63
|
+
$env:COMP_WORDS = ($tokens -join ' ')
|
|
64
|
+
$env:COMP_CWORD = $cword
|
|
65
|
+
$env:${ENV_VAR} = 'powershell_complete'
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
& ${PROGRAM} 2>$null | ForEach-Object {
|
|
69
|
+
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
|
|
70
|
+
}
|
|
71
|
+
} finally {
|
|
72
|
+
Remove-Item Env:COMP_WORDS -ErrorAction SilentlyContinue
|
|
73
|
+
Remove-Item Env:COMP_CWORD -ErrorAction SilentlyContinue
|
|
74
|
+
Remove-Item Env:${ENV_VAR} -ErrorAction SilentlyContinue
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function renderScript(shell: SupportedShell): string {
|
|
81
|
+
switch (shell) {
|
|
82
|
+
case "bash":
|
|
83
|
+
return renderBashScript();
|
|
84
|
+
case "zsh":
|
|
85
|
+
return renderZshScript();
|
|
86
|
+
case "fish":
|
|
87
|
+
return renderFishScript();
|
|
88
|
+
case "powershell":
|
|
89
|
+
return renderPowerShellScript();
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/program.ts
CHANGED
|
@@ -40,7 +40,9 @@ export function buildProgram(options: BuildProgramOptions = {}): Command {
|
|
|
40
40
|
.option("--api-key <token>", "override the configured API key")
|
|
41
41
|
.option("--read-only", "run in read-only mode")
|
|
42
42
|
.option("--example", "show examples for a command")
|
|
43
|
-
.option("--json", "emit JSON output")
|
|
43
|
+
.option("--json", "emit JSON output")
|
|
44
|
+
.option("--install-completion [shell]", "install shell completion (bash, zsh, fish, powershell)")
|
|
45
|
+
.option("--show-completion [shell]", "print shell completion script for the current shell");
|
|
44
46
|
|
|
45
47
|
registerProfilesCommands(program, options);
|
|
46
48
|
registerAutomationsCommands(program, options);
|