@tikoci/quickchr 0.2.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/DESIGN.md +193 -0
- package/LICENSE +21 -0
- package/README.md +334 -0
- package/package.json +61 -0
- package/src/cli/format.ts +154 -0
- package/src/cli/index.ts +2625 -0
- package/src/cli/wizard.ts +727 -0
- package/src/index.ts +124 -0
- package/src/lib/auth.ts +68 -0
- package/src/lib/channels.ts +222 -0
- package/src/lib/completions.ts +513 -0
- package/src/lib/console.ts +473 -0
- package/src/lib/credentials.ts +99 -0
- package/src/lib/device-mode.ts +283 -0
- package/src/lib/disk.ts +400 -0
- package/src/lib/exec.ts +101 -0
- package/src/lib/images.ts +167 -0
- package/src/lib/license.ts +292 -0
- package/src/lib/log.ts +41 -0
- package/src/lib/network.ts +635 -0
- package/src/lib/packages.ts +225 -0
- package/src/lib/password.ts +21 -0
- package/src/lib/platform.ts +458 -0
- package/src/lib/provision.ts +422 -0
- package/src/lib/qemu.ts +526 -0
- package/src/lib/qga.ts +667 -0
- package/src/lib/quickchr.ts +1871 -0
- package/src/lib/rest.ts +244 -0
- package/src/lib/secrets.ts +133 -0
- package/src/lib/socket-registry.ts +140 -0
- package/src/lib/state.ts +173 -0
- package/src/lib/storage.ts +163 -0
- package/src/lib/types.ts +690 -0
- package/src/lib/versions.ts +181 -0
- package/src/lib/zip.ts +44 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell completions — generation, detection, install, and uninstall.
|
|
3
|
+
* Supports bash, zsh, and fish. No external dependencies.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
7
|
+
import { join, dirname } from "node:path";
|
|
8
|
+
import { listMachineNames, getMachinesDir } from "./state.ts";
|
|
9
|
+
|
|
10
|
+
export type SupportedShell = "bash" | "zsh" | "fish";
|
|
11
|
+
const SUPPORTED_SHELLS: SupportedShell[] = ["bash", "zsh", "fish"];
|
|
12
|
+
|
|
13
|
+
// ─── Shell detection ────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export interface ShellInfo {
|
|
16
|
+
/** Value of $SHELL env var or parent-process name. */
|
|
17
|
+
shell: string;
|
|
18
|
+
/** Version string from `<shell> --version`, if available. */
|
|
19
|
+
version: string | undefined;
|
|
20
|
+
/** True if shell is one quickchr can install completions for. */
|
|
21
|
+
supported: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Detect the current interactive shell.
|
|
26
|
+
*
|
|
27
|
+
* $SHELL reflects the *login* shell, not the currently running one — e.g. it stays
|
|
28
|
+
* "/bin/zsh" even inside a fish session. Shell-specific env vars exported by the
|
|
29
|
+
* running shell are more reliable: fish exports FISH_VERSION, zsh exports ZSH_VERSION,
|
|
30
|
+
* bash exports BASH_VERSION. We check those first, then fall back to $SHELL.
|
|
31
|
+
*/
|
|
32
|
+
export function detectCurrentShell(): ShellInfo {
|
|
33
|
+
// Shell-specific env vars take priority over $SHELL (login shell).
|
|
34
|
+
// These are exported by the *currently running* interactive shell.
|
|
35
|
+
let detectedBinary: string | undefined;
|
|
36
|
+
let version: string | undefined;
|
|
37
|
+
|
|
38
|
+
if (process.env.FISH_VERSION) {
|
|
39
|
+
detectedBinary = "fish";
|
|
40
|
+
version = process.env.FISH_VERSION;
|
|
41
|
+
} else if (process.env.ZSH_VERSION) {
|
|
42
|
+
detectedBinary = "zsh";
|
|
43
|
+
version = process.env.ZSH_VERSION;
|
|
44
|
+
} else if (process.env.BASH_VERSION) {
|
|
45
|
+
detectedBinary = "bash";
|
|
46
|
+
const m = process.env.BASH_VERSION.match(/(\d+\.\d+[\w.]*)/);
|
|
47
|
+
version = m?.[1] ?? process.env.BASH_VERSION.slice(0, 60);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Resolve full path for the detected binary (or fall back to $SHELL).
|
|
51
|
+
let shell: string;
|
|
52
|
+
if (detectedBinary) {
|
|
53
|
+
const probe = process.platform === "win32" ? ["where.exe", detectedBinary] : ["which", detectedBinary];
|
|
54
|
+
const which = Bun.spawnSync(probe, { stdout: "pipe", stderr: "pipe" });
|
|
55
|
+
shell = which.exitCode === 0
|
|
56
|
+
? new TextDecoder().decode(which.stdout).trim().split(/\r?\n/)[0] ?? detectedBinary
|
|
57
|
+
: detectedBinary;
|
|
58
|
+
} else {
|
|
59
|
+
const shellEnv = process.env.SHELL ?? "";
|
|
60
|
+
shell = shellEnv || "unknown";
|
|
61
|
+
// Get version from the login shell binary.
|
|
62
|
+
if (shellEnv) {
|
|
63
|
+
try {
|
|
64
|
+
const result = Bun.spawnSync([shellEnv, "--version"], { stdout: "pipe", stderr: "pipe" });
|
|
65
|
+
if (result.exitCode === 0) {
|
|
66
|
+
const out = new TextDecoder().decode(result.stdout).trim();
|
|
67
|
+
const firstLine = out.split("\n")[0] ?? "";
|
|
68
|
+
const match = firstLine.match(/(\d+\.\d+[\w.]*)/);
|
|
69
|
+
version = (match?.[1] ?? firstLine.slice(0, 60)) || undefined;
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
// version unavailable
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Handle both POSIX (/) and Windows (\) separators — `where.exe bash` on
|
|
78
|
+
// windows-latest returns paths like "C:\Program Files\Git\bin\bash.exe".
|
|
79
|
+
const binary = (shell.split(/[\\/]/).at(-1) ?? shell).replace(/\.exe$/i, "");
|
|
80
|
+
const supported = (SUPPORTED_SHELLS as string[]).includes(binary);
|
|
81
|
+
|
|
82
|
+
return { shell, version, supported };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Parse the shell binary name from a full path or name string. */
|
|
86
|
+
export function shellBinary(shellInfo: ShellInfo): string {
|
|
87
|
+
const raw = shellInfo.shell.split(/[\\/]/).at(-1) ?? shellInfo.shell;
|
|
88
|
+
return raw.replace(/\.exe$/i, "");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ─── Install paths ──────────────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
export interface CompletionInstallPath {
|
|
94
|
+
/** Absolute path where the completion file should be written. */
|
|
95
|
+
file: string;
|
|
96
|
+
/** If the rc file needs modification, this is the line to append. */
|
|
97
|
+
rcLine?: string;
|
|
98
|
+
/** The rc file to modify. */
|
|
99
|
+
rcFile?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function homeDir(): string {
|
|
103
|
+
return process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function brewPrefix(): string | undefined {
|
|
107
|
+
// Check common Homebrew prefix locations; avoid spawning brew for speed.
|
|
108
|
+
for (const p of ["/opt/homebrew", "/usr/local"]) {
|
|
109
|
+
if (existsSync(p)) return p;
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function quickchrCompletionsDir(): string {
|
|
115
|
+
const dataDir = join(homeDir(), ".local", "share", "quickchr", "completions");
|
|
116
|
+
return dataDir;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Resolve the install path (and optional rc-file line) for a given shell.
|
|
121
|
+
* Prefers Homebrew locations when available (no rc modification needed).
|
|
122
|
+
*/
|
|
123
|
+
export function completionInstallPath(shell: SupportedShell): CompletionInstallPath {
|
|
124
|
+
const home = homeDir();
|
|
125
|
+
const brew = brewPrefix();
|
|
126
|
+
|
|
127
|
+
if (shell === "bash") {
|
|
128
|
+
// Homebrew: files sourced automatically by bash-completion
|
|
129
|
+
if (brew) {
|
|
130
|
+
const brewDir = `${brew}/etc/bash_completion.d`;
|
|
131
|
+
if (existsSync(brewDir)) {
|
|
132
|
+
return { file: join(brewDir, "quickchr") };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// XDG user dir: bash-completion ≥2 sources these automatically
|
|
136
|
+
const xdgDir = join(home, ".local", "share", "bash-completion", "completions");
|
|
137
|
+
if (existsSync(xdgDir)) {
|
|
138
|
+
return { file: join(xdgDir, "quickchr") };
|
|
139
|
+
}
|
|
140
|
+
// Fallback: write to quickchr data dir and source from rc
|
|
141
|
+
const file = join(quickchrCompletionsDir(), "quickchr.bash");
|
|
142
|
+
const rcFile = process.platform === "darwin"
|
|
143
|
+
? join(home, ".bash_profile")
|
|
144
|
+
: join(home, ".bashrc");
|
|
145
|
+
return {
|
|
146
|
+
file,
|
|
147
|
+
rcLine: `[ -f "${file}" ] && . "${file}"`,
|
|
148
|
+
rcFile,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (shell === "zsh") {
|
|
153
|
+
// Homebrew: files in site-functions are in default fpath
|
|
154
|
+
if (brew) {
|
|
155
|
+
const brewDir = `${brew}/share/zsh/site-functions`;
|
|
156
|
+
if (existsSync(brewDir)) {
|
|
157
|
+
return { file: join(brewDir, "_quickchr") };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// User dir: must be added to fpath before compinit
|
|
161
|
+
const file = join(quickchrCompletionsDir(), "_quickchr");
|
|
162
|
+
const completionsDir = quickchrCompletionsDir();
|
|
163
|
+
const rcFile = join(home, ".zshrc");
|
|
164
|
+
return {
|
|
165
|
+
file,
|
|
166
|
+
rcLine: `fpath=("${completionsDir}" $fpath)`,
|
|
167
|
+
rcFile,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// fish: auto-sourced from this directory, no rc modification needed
|
|
172
|
+
const fishDir = join(home, ".config", "fish", "completions");
|
|
173
|
+
return { file: join(fishDir, "quickchr.fish") };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ─── Status ─────────────────────────────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
export interface CompletionStatus {
|
|
179
|
+
shell: SupportedShell;
|
|
180
|
+
installed: boolean;
|
|
181
|
+
/** Path where the completion file was found (or expected). */
|
|
182
|
+
path: string;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Check whether completions are installed for a given shell. */
|
|
186
|
+
export function completionStatusFor(shell: SupportedShell): CompletionStatus {
|
|
187
|
+
const { file } = completionInstallPath(shell);
|
|
188
|
+
return {
|
|
189
|
+
shell,
|
|
190
|
+
installed: existsSync(file),
|
|
191
|
+
path: file,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Check completion status for all supported shells. */
|
|
196
|
+
export function allCompletionStatuses(): CompletionStatus[] {
|
|
197
|
+
return SUPPORTED_SHELLS.map(completionStatusFor);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ─── Install / Uninstall ────────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
export interface InstallResult {
|
|
203
|
+
shell: SupportedShell;
|
|
204
|
+
file: string;
|
|
205
|
+
/** Line appended to rc file, if any. */
|
|
206
|
+
rcLine?: string;
|
|
207
|
+
rcFile?: string;
|
|
208
|
+
/** True if the file was already present (skipped rewrite). */
|
|
209
|
+
alreadyInstalled: boolean;
|
|
210
|
+
/** True when --dry-run: nothing was written. */
|
|
211
|
+
dryRun: boolean;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Install completions for the given shell. Returns what was done. */
|
|
215
|
+
export function installCompletions(
|
|
216
|
+
shell: SupportedShell,
|
|
217
|
+
opts: { dryRun?: boolean } = {},
|
|
218
|
+
): InstallResult {
|
|
219
|
+
const { file, rcLine, rcFile } = completionInstallPath(shell);
|
|
220
|
+
const alreadyInstalled = existsSync(file);
|
|
221
|
+
const dryRun = opts.dryRun ?? false;
|
|
222
|
+
|
|
223
|
+
if (!dryRun) {
|
|
224
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
225
|
+
writeFileSync(file, generateCompletionScript(shell), "utf8");
|
|
226
|
+
|
|
227
|
+
if (rcLine && rcFile) {
|
|
228
|
+
appendRcLine(rcFile, rcLine);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return { shell, file, rcLine, rcFile, alreadyInstalled, dryRun };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Remove completions for the given shell. Returns true if the file existed. */
|
|
236
|
+
export function uninstallCompletions(shell: SupportedShell): { removed: boolean; file: string; rcLine?: string; rcFile?: string } {
|
|
237
|
+
const { file, rcLine, rcFile } = completionInstallPath(shell);
|
|
238
|
+
const removed = existsSync(file);
|
|
239
|
+
if (removed) {
|
|
240
|
+
unlinkSync(file);
|
|
241
|
+
}
|
|
242
|
+
if (rcLine && rcFile && existsSync(rcFile)) {
|
|
243
|
+
removeRcLine(rcFile, rcLine);
|
|
244
|
+
}
|
|
245
|
+
return { removed, file, rcLine, rcFile };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Append a line to an rc file, only if it's not already present. */
|
|
249
|
+
function appendRcLine(rcFile: string, line: string): void {
|
|
250
|
+
let content = "";
|
|
251
|
+
if (existsSync(rcFile)) {
|
|
252
|
+
content = readFileSync(rcFile, "utf8");
|
|
253
|
+
}
|
|
254
|
+
if (content.includes(line)) return;
|
|
255
|
+
const sep = content.endsWith("\n") || content.length === 0 ? "" : "\n";
|
|
256
|
+
writeFileSync(rcFile, `${content}${sep}${line}\n`, "utf8");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Remove a previously-appended line from an rc file (exact match). */
|
|
260
|
+
function removeRcLine(rcFile: string, line: string): void {
|
|
261
|
+
if (!existsSync(rcFile)) return;
|
|
262
|
+
const content = readFileSync(rcFile, "utf8");
|
|
263
|
+
const filtered = content
|
|
264
|
+
.split("\n")
|
|
265
|
+
.filter((l) => l !== line)
|
|
266
|
+
.join("\n");
|
|
267
|
+
writeFileSync(rcFile, filtered, "utf8");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ─── Machine name helpers (used by completion scripts) ──────────────────────
|
|
271
|
+
|
|
272
|
+
/** List all machine names for completion. */
|
|
273
|
+
export function listMachineNamesForCompletion(): string[] {
|
|
274
|
+
try {
|
|
275
|
+
return listMachineNames();
|
|
276
|
+
} catch {
|
|
277
|
+
return [];
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** List running machine names for completion. */
|
|
282
|
+
export function listRunningMachineNamesForCompletion(): string[] {
|
|
283
|
+
try {
|
|
284
|
+
const machinesDir = getMachinesDir();
|
|
285
|
+
if (!existsSync(machinesDir)) return [];
|
|
286
|
+
|
|
287
|
+
const names: string[] = [];
|
|
288
|
+
for (const entry of readdirSync(machinesDir, { withFileTypes: true })) {
|
|
289
|
+
if (!entry.isDirectory()) continue;
|
|
290
|
+
const mjPath = join(machinesDir, entry.name, "machine.json");
|
|
291
|
+
if (!existsSync(mjPath)) continue;
|
|
292
|
+
try {
|
|
293
|
+
const state = JSON.parse(readFileSync(mjPath, "utf8")) as { status?: string };
|
|
294
|
+
if (state.status === "running") names.push(entry.name);
|
|
295
|
+
} catch {
|
|
296
|
+
// skip unreadable
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return names;
|
|
300
|
+
} catch {
|
|
301
|
+
return [];
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ─── Completion script generators ───────────────────────────────────────────
|
|
306
|
+
|
|
307
|
+
/** Generate a completion script for the given shell. */
|
|
308
|
+
export function generateCompletionScript(shell: SupportedShell): string {
|
|
309
|
+
switch (shell) {
|
|
310
|
+
case "bash": return generateBashCompletion();
|
|
311
|
+
case "zsh": return generateZshCompletion();
|
|
312
|
+
case "fish": return generateFishCompletion();
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function generateBashCompletion(): string {
|
|
317
|
+
// Use $ as a variable to embed ${...} patterns without triggering JS template interpolation.
|
|
318
|
+
const D = "$";
|
|
319
|
+
return `# quickchr bash completion
|
|
320
|
+
# Generated by quickchr. Source this file or let bash-completion do it automatically.
|
|
321
|
+
|
|
322
|
+
_quickchr() {
|
|
323
|
+
local cur prev words cword
|
|
324
|
+
_init_completion 2>/dev/null || {
|
|
325
|
+
cur="${D}{COMP_WORDS[COMP_CWORD]}"
|
|
326
|
+
prev="${D}{COMP_WORDS[COMP_CWORD-1]}"
|
|
327
|
+
words=("${D}{COMP_WORDS[@]}")
|
|
328
|
+
cword=${D}COMP_CWORD
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
local commands="add start stop list ls status remove rm clean console exec qga license disk snapshot snap networks net doctor setup completions version help"
|
|
332
|
+
|
|
333
|
+
if [[ ${D}cword -eq 1 ]]; then
|
|
334
|
+
COMPREPLY=( $(compgen -W "${D}commands" -- "${D}cur") )
|
|
335
|
+
return
|
|
336
|
+
fi
|
|
337
|
+
|
|
338
|
+
local cmd="${D}{words[1]}"
|
|
339
|
+
case "${D}cmd" in
|
|
340
|
+
start|remove|rm|clean|console|exec|qga|license|disk|snapshot|snap|status)
|
|
341
|
+
local machines
|
|
342
|
+
machines=$(quickchr completions --machines 2>/dev/null)
|
|
343
|
+
COMPREPLY=( $(compgen -W "${D}machines" -- "${D}cur") )
|
|
344
|
+
;;
|
|
345
|
+
stop)
|
|
346
|
+
local machines
|
|
347
|
+
machines=$(quickchr completions --running 2>/dev/null)
|
|
348
|
+
[[ -z "${D}machines" ]] && machines=$(quickchr completions --machines 2>/dev/null)
|
|
349
|
+
COMPREPLY=( $(compgen -W "${D}machines" -- "${D}cur") )
|
|
350
|
+
;;
|
|
351
|
+
completions)
|
|
352
|
+
local opts="--install --uninstall --status --dry-run --shell"
|
|
353
|
+
COMPREPLY=( $(compgen -W "${D}opts" -- "${D}cur") )
|
|
354
|
+
;;
|
|
355
|
+
help)
|
|
356
|
+
COMPREPLY=( $(compgen -W "${D}commands" -- "${D}cur") )
|
|
357
|
+
;;
|
|
358
|
+
esac
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
complete -F _quickchr quickchr
|
|
362
|
+
`;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function generateZshCompletion(): string {
|
|
366
|
+
// Use $ as a variable to embed ${...} patterns without triggering JS template interpolation.
|
|
367
|
+
const D = "$";
|
|
368
|
+
return `#compdef quickchr
|
|
369
|
+
# quickchr zsh completion
|
|
370
|
+
# Generated by quickchr.
|
|
371
|
+
|
|
372
|
+
_quickchr() {
|
|
373
|
+
local state
|
|
374
|
+
|
|
375
|
+
_arguments \\
|
|
376
|
+
'(- *)'{-h,--help}'[Show help]' \\
|
|
377
|
+
'(- *)'{-v,--version}'[Show version]' \\
|
|
378
|
+
'1: :->command' \\
|
|
379
|
+
'*: :->args'
|
|
380
|
+
|
|
381
|
+
case ${D}state in
|
|
382
|
+
command)
|
|
383
|
+
local commands=(
|
|
384
|
+
'add:Create a new CHR machine (without starting)'
|
|
385
|
+
'start:Start or restart a CHR instance'
|
|
386
|
+
'stop:Stop instance(s)'
|
|
387
|
+
'list:List all instances'
|
|
388
|
+
'ls:List all instances (alias)'
|
|
389
|
+
'status:Detailed status'
|
|
390
|
+
'remove:Remove instance(s) and disk'
|
|
391
|
+
'rm:Remove instance(s) (alias)'
|
|
392
|
+
'clean:Reset instance disk to fresh image'
|
|
393
|
+
'console:Attach to serial console'
|
|
394
|
+
'exec:Run a RouterOS CLI command'
|
|
395
|
+
'qga:Guest agent command'
|
|
396
|
+
'license:Apply/renew CHR trial license'
|
|
397
|
+
'disk:Show disk details'
|
|
398
|
+
'snapshot:Manage snapshots'
|
|
399
|
+
'snap:Manage snapshots (alias)'
|
|
400
|
+
'networks:Network discovery and socket management'
|
|
401
|
+
'net:Network discovery (alias)'
|
|
402
|
+
'doctor:Check prerequisites'
|
|
403
|
+
'setup:Interactive setup wizard'
|
|
404
|
+
'completions:Manage shell completions'
|
|
405
|
+
'version:Show version info'
|
|
406
|
+
'help:Show help'
|
|
407
|
+
)
|
|
408
|
+
_describe 'command' commands
|
|
409
|
+
;;
|
|
410
|
+
args)
|
|
411
|
+
local cmd="${D}{words[2]}"
|
|
412
|
+
case ${D}cmd in
|
|
413
|
+
start|remove|rm|clean|console|exec|qga|license|disk|snapshot|snap|status)
|
|
414
|
+
local machines
|
|
415
|
+
machines=(${D}{(f)"${D}(quickchr completions --machines 2>/dev/null)"})
|
|
416
|
+
_describe 'machine' machines
|
|
417
|
+
;;
|
|
418
|
+
stop)
|
|
419
|
+
local machines
|
|
420
|
+
machines=(${D}{(f)"${D}(quickchr completions --running 2>/dev/null)"})
|
|
421
|
+
(( ${D}{#machines} == 0 )) && machines=(${D}{(f)"${D}(quickchr completions --machines 2>/dev/null)"})
|
|
422
|
+
_describe 'machine' machines
|
|
423
|
+
;;
|
|
424
|
+
completions)
|
|
425
|
+
local opts=(
|
|
426
|
+
'--install:Install completions for the current shell'
|
|
427
|
+
'--uninstall:Remove completions'
|
|
428
|
+
'--status:Show install status for all shells'
|
|
429
|
+
'--dry-run:Show what would happen without writing'
|
|
430
|
+
'--shell:Specify shell (bash, zsh, fish)'
|
|
431
|
+
)
|
|
432
|
+
_describe 'option' opts
|
|
433
|
+
;;
|
|
434
|
+
help)
|
|
435
|
+
local commands=(add start stop list status remove clean console exec qga license disk snapshot networks doctor setup completions version)
|
|
436
|
+
_describe 'command' commands
|
|
437
|
+
;;
|
|
438
|
+
esac
|
|
439
|
+
;;
|
|
440
|
+
esac
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
_quickchr "${D}@"
|
|
444
|
+
`;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function generateFishCompletion(): string {
|
|
448
|
+
return `# quickchr fish completion
|
|
449
|
+
# Generated by quickchr. Fish auto-loads files from ~/.config/fish/completions/.
|
|
450
|
+
|
|
451
|
+
set -l quickchr_commands add start stop list ls status remove rm clean console exec qga license disk snapshot snap networks net doctor setup completions version help
|
|
452
|
+
|
|
453
|
+
# Disable file completions globally for quickchr
|
|
454
|
+
complete -c quickchr -f
|
|
455
|
+
|
|
456
|
+
# Subcommands
|
|
457
|
+
complete -c quickchr -n "not __fish_seen_subcommand_from $quickchr_commands" \\
|
|
458
|
+
-a "add" -d "Create a new CHR machine"
|
|
459
|
+
complete -c quickchr -n "not __fish_seen_subcommand_from $quickchr_commands" \\
|
|
460
|
+
-a "start" -d "Start or restart a CHR instance"
|
|
461
|
+
complete -c quickchr -n "not __fish_seen_subcommand_from $quickchr_commands" \\
|
|
462
|
+
-a "stop" -d "Stop instance(s)"
|
|
463
|
+
complete -c quickchr -n "not __fish_seen_subcommand_from $quickchr_commands" \\
|
|
464
|
+
-a "list ls" -d "List all instances"
|
|
465
|
+
complete -c quickchr -n "not __fish_seen_subcommand_from $quickchr_commands" \\
|
|
466
|
+
-a "status" -d "Detailed status"
|
|
467
|
+
complete -c quickchr -n "not __fish_seen_subcommand_from $quickchr_commands" \\
|
|
468
|
+
-a "remove rm" -d "Remove instance(s) and disk"
|
|
469
|
+
complete -c quickchr -n "not __fish_seen_subcommand_from $quickchr_commands" \\
|
|
470
|
+
-a "clean" -d "Reset instance disk to fresh image"
|
|
471
|
+
complete -c quickchr -n "not __fish_seen_subcommand_from $quickchr_commands" \\
|
|
472
|
+
-a "console" -d "Attach to serial console"
|
|
473
|
+
complete -c quickchr -n "not __fish_seen_subcommand_from $quickchr_commands" \\
|
|
474
|
+
-a "exec" -d "Run a RouterOS CLI command"
|
|
475
|
+
complete -c quickchr -n "not __fish_seen_subcommand_from $quickchr_commands" \\
|
|
476
|
+
-a "qga" -d "Guest agent command"
|
|
477
|
+
complete -c quickchr -n "not __fish_seen_subcommand_from $quickchr_commands" \\
|
|
478
|
+
-a "license" -d "Apply/renew CHR trial license"
|
|
479
|
+
complete -c quickchr -n "not __fish_seen_subcommand_from $quickchr_commands" \\
|
|
480
|
+
-a "disk" -d "Show disk details"
|
|
481
|
+
complete -c quickchr -n "not __fish_seen_subcommand_from $quickchr_commands" \\
|
|
482
|
+
-a "snapshot snap" -d "Manage snapshots"
|
|
483
|
+
complete -c quickchr -n "not __fish_seen_subcommand_from $quickchr_commands" \\
|
|
484
|
+
-a "networks net" -d "Network discovery and socket management"
|
|
485
|
+
complete -c quickchr -n "not __fish_seen_subcommand_from $quickchr_commands" \\
|
|
486
|
+
-a "doctor" -d "Check prerequisites"
|
|
487
|
+
complete -c quickchr -n "not __fish_seen_subcommand_from $quickchr_commands" \\
|
|
488
|
+
-a "setup" -d "Interactive setup wizard"
|
|
489
|
+
complete -c quickchr -n "not __fish_seen_subcommand_from $quickchr_commands" \\
|
|
490
|
+
-a "completions" -d "Manage shell completions"
|
|
491
|
+
complete -c quickchr -n "not __fish_seen_subcommand_from $quickchr_commands" \\
|
|
492
|
+
-a "version" -d "Show version info"
|
|
493
|
+
complete -c quickchr -n "not __fish_seen_subcommand_from $quickchr_commands" \\
|
|
494
|
+
-a "help" -d "Show help"
|
|
495
|
+
|
|
496
|
+
# Machine name completions for commands that take a running machine
|
|
497
|
+
complete -c quickchr -n "__fish_seen_subcommand_from stop" \\
|
|
498
|
+
-a "(quickchr completions --running 2>/dev/null; or quickchr completions --machines 2>/dev/null)" \\
|
|
499
|
+
-d "machine"
|
|
500
|
+
|
|
501
|
+
# Machine name completions for commands that take any machine
|
|
502
|
+
complete -c quickchr -n "__fish_seen_subcommand_from start remove rm clean console exec qga license disk snapshot snap status" \\
|
|
503
|
+
-a "(quickchr completions --machines 2>/dev/null)" \\
|
|
504
|
+
-d "machine"
|
|
505
|
+
|
|
506
|
+
# completions subcommand options
|
|
507
|
+
complete -c quickchr -n "__fish_seen_subcommand_from completions" -l install -d "Install completions"
|
|
508
|
+
complete -c quickchr -n "__fish_seen_subcommand_from completions" -l uninstall -d "Remove completions"
|
|
509
|
+
complete -c quickchr -n "__fish_seen_subcommand_from completions" -l status -d "Show install status"
|
|
510
|
+
complete -c quickchr -n "__fish_seen_subcommand_from completions" -l dry-run -d "Show what would happen"
|
|
511
|
+
complete -c quickchr -n "__fish_seen_subcommand_from completions" -l shell -d "Specify shell" -a "bash zsh fish"
|
|
512
|
+
`;
|
|
513
|
+
}
|