zinkee 0.1.10 → 0.1.12

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/index.js CHANGED
@@ -732,6 +732,197 @@ var commandRegistry = {
732
732
  }
733
733
  };
734
734
 
735
+ // src/utils/argv-rewrite.ts
736
+ var HOISTABLE_GLOBAL_FLAGS = {
737
+ "--profile": "value",
738
+ "--base-url": "value",
739
+ "--api-key": "value",
740
+ "--read-only": "boolean",
741
+ "--json": "boolean"
742
+ };
743
+ function isHoistableFlag(flagName) {
744
+ return Object.hasOwn(HOISTABLE_GLOBAL_FLAGS, flagName);
745
+ }
746
+ function getCommandPath(command) {
747
+ const parts = [];
748
+ let cursor = command;
749
+ while (cursor && cursor.parent) {
750
+ parts.unshift(cursor.name());
751
+ cursor = cursor.parent;
752
+ }
753
+ return parts.join(" ");
754
+ }
755
+ function buildOptionMetadata(program) {
756
+ const valueFlags = /* @__PURE__ */ new Set();
757
+ const booleanFlags = /* @__PURE__ */ new Set();
758
+ const localOptionsByCommand = /* @__PURE__ */ new Map();
759
+ function visit(command, isRoot) {
760
+ const localFlags = /* @__PURE__ */ new Set();
761
+ for (const option of command.options) {
762
+ if (!option.long) continue;
763
+ if (option.required || option.optional) {
764
+ valueFlags.add(option.long);
765
+ } else {
766
+ booleanFlags.add(option.long);
767
+ }
768
+ if (!isRoot) {
769
+ localFlags.add(option.long);
770
+ }
771
+ }
772
+ if (!isRoot) {
773
+ localOptionsByCommand.set(getCommandPath(command), localFlags);
774
+ }
775
+ for (const sub of command.commands) {
776
+ visit(sub, false);
777
+ }
778
+ }
779
+ visit(program, true);
780
+ return { valueFlags, booleanFlags, localOptionsByCommand };
781
+ }
782
+ function createDefaultOptionMetadata() {
783
+ return {
784
+ valueFlags: /* @__PURE__ */ new Set([
785
+ "--profile",
786
+ "--base-url",
787
+ "--api-key",
788
+ "--install-completion",
789
+ "--show-completion"
790
+ ]),
791
+ booleanFlags: /* @__PURE__ */ new Set([
792
+ "--read-only",
793
+ "--json",
794
+ "--example",
795
+ "--help"
796
+ ]),
797
+ localOptionsByCommand: /* @__PURE__ */ new Map()
798
+ };
799
+ }
800
+ function isFlagToken(token) {
801
+ return token.length > 1 && token.startsWith("-");
802
+ }
803
+ function splitFlagToken(token) {
804
+ if (token.startsWith("--") && token.includes("=")) {
805
+ const eqIdx = token.indexOf("=");
806
+ return {
807
+ flagName: token.slice(0, eqIdx),
808
+ inlineValue: token.slice(eqIdx + 1)
809
+ };
810
+ }
811
+ return { flagName: token, inlineValue: void 0 };
812
+ }
813
+ function flagConsumesValue(flagName, optionMeta) {
814
+ if (optionMeta.valueFlags.has(flagName)) {
815
+ return true;
816
+ }
817
+ if (optionMeta.booleanFlags.has(flagName)) {
818
+ return false;
819
+ }
820
+ return true;
821
+ }
822
+ function collectOperandTokens(userArgs, optionMeta, options = {}) {
823
+ const operands = [];
824
+ let i = 0;
825
+ while (i < userArgs.length) {
826
+ const token = userArgs[i];
827
+ if (token === "--") {
828
+ break;
829
+ }
830
+ if (isFlagToken(token)) {
831
+ const { flagName, inlineValue } = splitFlagToken(token);
832
+ if (options.skipFlag !== void 0 && flagName === options.skipFlag) {
833
+ i += 1;
834
+ continue;
835
+ }
836
+ if (inlineValue !== void 0) {
837
+ i += 1;
838
+ continue;
839
+ }
840
+ const consumes = flagConsumesValue(flagName, optionMeta);
841
+ const next = userArgs[i + 1];
842
+ if (consumes && next !== void 0 && !isFlagToken(next)) {
843
+ i += 2;
844
+ } else {
845
+ i += 1;
846
+ }
847
+ continue;
848
+ }
849
+ operands.push({ index: i, token });
850
+ i += 1;
851
+ }
852
+ return operands;
853
+ }
854
+ function findCommandPath(operands, registry) {
855
+ if (operands.length === 0) return null;
856
+ const commandsByLength = Object.keys(registry).sort(
857
+ (left, right) => right.split(" ").length - left.split(" ").length
858
+ );
859
+ for (let start = 0; start < operands.length; start += 1) {
860
+ for (const commandName of commandsByLength) {
861
+ const parts = commandName.split(" ");
862
+ if (start + parts.length > operands.length) continue;
863
+ let matches = true;
864
+ for (let j = 0; j < parts.length; j += 1) {
865
+ if (operands[start + j].token !== parts[j]) {
866
+ matches = false;
867
+ break;
868
+ }
869
+ }
870
+ if (matches) {
871
+ return {
872
+ commandName,
873
+ firstArgvIndex: operands[start].index,
874
+ lastArgvIndex: operands[start + parts.length - 1].index
875
+ };
876
+ }
877
+ }
878
+ }
879
+ return null;
880
+ }
881
+ function hoistGlobalOptions(argv, optionMeta, registry) {
882
+ if (argv.length < 3) return [...argv];
883
+ const userArgs = argv.slice(2);
884
+ const operands = collectOperandTokens(userArgs, optionMeta);
885
+ const cmdPath = findCommandPath(operands, registry);
886
+ if (!cmdPath) return [...argv];
887
+ const localOptions = optionMeta.localOptionsByCommand.get(cmdPath.commandName) ?? /* @__PURE__ */ new Set();
888
+ const hoisted = [];
889
+ const remaining = [];
890
+ let i = 0;
891
+ while (i < userArgs.length) {
892
+ const token = userArgs[i];
893
+ if (token === "--") {
894
+ remaining.push(...userArgs.slice(i));
895
+ break;
896
+ }
897
+ if (!isFlagToken(token)) {
898
+ remaining.push(token);
899
+ i += 1;
900
+ continue;
901
+ }
902
+ const { flagName, inlineValue } = splitFlagToken(token);
903
+ if (!isHoistableFlag(flagName)) {
904
+ remaining.push(token);
905
+ i += 1;
906
+ continue;
907
+ }
908
+ const isAfterCommandPath = i > cmdPath.lastArgvIndex;
909
+ const collidesWithLocal = localOptions.has(flagName);
910
+ const target = isAfterCommandPath && collidesWithLocal ? remaining : hoisted;
911
+ target.push(token);
912
+ const spec = HOISTABLE_GLOBAL_FLAGS[flagName];
913
+ if (spec === "value" && inlineValue === void 0) {
914
+ const next = userArgs[i + 1];
915
+ if (next !== void 0 && !isFlagToken(next)) {
916
+ target.push(next);
917
+ i += 2;
918
+ continue;
919
+ }
920
+ }
921
+ i += 1;
922
+ }
923
+ return [argv[0], argv[1], ...hoisted, ...remaining];
924
+ }
925
+
735
926
  // src/client.ts
736
927
  var ApiClientError = class extends Error {
737
928
  name = "ApiClientError";
@@ -6392,26 +6583,6 @@ async function getResolvedCommandExamples(commandName, filters = {}, options = {
6392
6583
  function getStdout(stdout) {
6393
6584
  return stdout ?? process.stdout;
6394
6585
  }
6395
- function resolveExampleCommand(tokens) {
6396
- const commands = Object.keys(commandRegistry).sort(
6397
- (left, right) => right.split(" ").length - left.split(" ").length
6398
- );
6399
- for (const commandName of commands) {
6400
- const commandTokens2 = commandName.split(" ");
6401
- const matches = commandTokens2.every((token, index) => tokens[index] === token);
6402
- if (matches) {
6403
- return {
6404
- commandName,
6405
- remainingTokens: tokens.slice(commandTokens2.length)
6406
- };
6407
- }
6408
- }
6409
- const commandTokens = tokens.filter((token) => !token.startsWith("-"));
6410
- return {
6411
- commandName: commandTokens.join(" "),
6412
- remainingTokens: []
6413
- };
6414
- }
6415
6586
  function extractExampleFilters(tokens) {
6416
6587
  const filters = {};
6417
6588
  for (let index = 0; index < tokens.length; index += 1) {
@@ -6451,18 +6622,19 @@ function extractExampleOverrides(tokens) {
6451
6622
  }
6452
6623
  return overrides;
6453
6624
  }
6454
- function extractExampleRequest(argv) {
6455
- const exampleIndex = argv.indexOf("--example");
6456
- if (exampleIndex === -1) {
6625
+ function extractExampleRequest(argv, optionMeta = createDefaultOptionMetadata()) {
6626
+ if (!argv.includes("--example")) {
6457
6627
  return null;
6458
6628
  }
6459
- const requestTokens = argv.slice(exampleIndex + 1);
6460
- const { commandName, remainingTokens } = resolveExampleCommand(requestTokens);
6461
- if (commandName.length === 0) {
6629
+ const userArgs = argv.slice(2);
6630
+ const operands = collectOperandTokens(userArgs, optionMeta, { skipFlag: "--example" });
6631
+ const cmdPath = findCommandPath(operands, commandRegistry);
6632
+ if (!cmdPath) {
6462
6633
  throw new CliError("invalid_cli_usage", 'Provide a command after "--example".');
6463
6634
  }
6635
+ const remainingTokens = userArgs.slice(cmdPath.lastArgvIndex + 1);
6464
6636
  return {
6465
- commandName,
6637
+ commandName: cmdPath.commandName,
6466
6638
  filters: extractExampleFilters(remainingTokens),
6467
6639
  overrides: extractExampleOverrides(argv),
6468
6640
  json: argv.includes("--json")
@@ -10961,7 +11133,7 @@ var require2 = createRequire(import.meta.url);
10961
11133
  var { version } = require2("../package.json");
10962
11134
  function buildProgram(options = {}) {
10963
11135
  const program = new Command10();
10964
- program.name("zinkee").description("CLI for Zinkee API v2").version(version).enablePositionalOptions().showHelpAfterError().option("--profile <name>", "select a profile from config").option("--base-url <url>", "override the configured base URL").option("--api-key <token>", "override the configured API key").option("--read-only", "run in read-only mode").option("--example", "show examples for a command").option("--json", "emit JSON output");
11136
+ program.name("zinkee").description("CLI for Zinkee API v2").version(version).enablePositionalOptions().showHelpAfterError().option("--profile <name>", "select a profile from config").option("--base-url <url>", "override the configured base URL").option("--api-key <token>", "override the configured API key").option("--read-only", "run in read-only mode").option("--example", "show examples for a command").option("--json", "emit JSON output").option("--install-completion [shell]", "install shell completion (bash, zsh, fish, powershell)").option("--show-completion [shell]", "print shell completion script for the current shell");
10965
11137
  registerProfilesCommands(program, options);
10966
11138
  registerAutomationsCommands(program, options);
10967
11139
  registerConfigCommands(program, options);
@@ -10976,6 +11148,539 @@ function buildProgram(options = {}) {
10976
11148
  return program;
10977
11149
  }
10978
11150
 
11151
+ // src/completion/engine.ts
11152
+ function readProfileNamesFromConfig(configPath) {
11153
+ try {
11154
+ const config = readConfigIfExists(configPath);
11155
+ if (!config) {
11156
+ return [];
11157
+ }
11158
+ return Object.keys(config.profiles).sort();
11159
+ } catch {
11160
+ return [];
11161
+ }
11162
+ }
11163
+ function findSubcommand(command, name) {
11164
+ return command.commands.find((sub) => {
11165
+ if (sub.name() === name) {
11166
+ return true;
11167
+ }
11168
+ const aliases = sub.aliases();
11169
+ return aliases.includes(name);
11170
+ });
11171
+ }
11172
+ function collectFlagsThatTakeValues(root) {
11173
+ const flags = /* @__PURE__ */ new Set();
11174
+ const walk = (command) => {
11175
+ for (const option of command.options) {
11176
+ if (!option.required && !option.optional) {
11177
+ continue;
11178
+ }
11179
+ if (option.long) {
11180
+ flags.add(option.long);
11181
+ }
11182
+ if (option.short) {
11183
+ flags.add(option.short);
11184
+ }
11185
+ }
11186
+ for (const sub of command.commands) {
11187
+ walk(sub);
11188
+ }
11189
+ };
11190
+ walk(root);
11191
+ return flags;
11192
+ }
11193
+ function descend(root, words) {
11194
+ const flagsWithValues = collectFlagsThatTakeValues(root);
11195
+ let current = root;
11196
+ let index = 0;
11197
+ while (index < words.length) {
11198
+ const word = words[index];
11199
+ if (word.startsWith("-")) {
11200
+ const [flagPart] = word.split("=", 1);
11201
+ if (flagPart && flagsWithValues.has(flagPart) && !word.includes("=")) {
11202
+ index += 2;
11203
+ } else {
11204
+ index += 1;
11205
+ }
11206
+ continue;
11207
+ }
11208
+ const next = findSubcommand(current, word);
11209
+ if (!next) {
11210
+ break;
11211
+ }
11212
+ current = next;
11213
+ index += 1;
11214
+ }
11215
+ return current;
11216
+ }
11217
+ function collectVisibleSubcommands(command) {
11218
+ const names = [];
11219
+ for (const sub of command.commands) {
11220
+ if (sub.name().startsWith("__")) {
11221
+ continue;
11222
+ }
11223
+ names.push(sub.name());
11224
+ }
11225
+ return names;
11226
+ }
11227
+ function collectVisibleLongFlags(command) {
11228
+ const flags = [];
11229
+ let current = command;
11230
+ while (current) {
11231
+ for (const option of current.options) {
11232
+ if (option.hidden || !option.long) {
11233
+ continue;
11234
+ }
11235
+ if (!flags.includes(option.long)) {
11236
+ flags.push(option.long);
11237
+ }
11238
+ }
11239
+ current = current.parent;
11240
+ }
11241
+ if (!flags.includes("--help")) {
11242
+ flags.push("--help");
11243
+ }
11244
+ return flags;
11245
+ }
11246
+ function getCompletions(program, request, deps = {}) {
11247
+ const { words, cwordIndex } = request;
11248
+ const safeIndex = Math.max(0, Math.min(cwordIndex, words.length));
11249
+ const current = words[safeIndex] ?? "";
11250
+ const prevToken = safeIndex > 0 ? words[safeIndex - 1] : void 0;
11251
+ const readProfiles = deps.readProfileNames ?? readProfileNamesFromConfig;
11252
+ if (prevToken === "--profile" || prevToken === "-p") {
11253
+ try {
11254
+ return readProfiles(deps.configPath).filter((name) => name.startsWith(current)).slice().sort();
11255
+ } catch {
11256
+ return [];
11257
+ }
11258
+ }
11259
+ const tokensBeforeCurrent = words.slice(1, safeIndex);
11260
+ const command = descend(program, tokensBeforeCurrent);
11261
+ const candidates = /* @__PURE__ */ new Set();
11262
+ if (current.startsWith("-")) {
11263
+ for (const flag of collectVisibleLongFlags(command)) {
11264
+ if (flag.startsWith(current)) {
11265
+ candidates.add(flag);
11266
+ }
11267
+ }
11268
+ return [...candidates].sort();
11269
+ }
11270
+ for (const subcommand of collectVisibleSubcommands(command)) {
11271
+ if (subcommand.startsWith(current)) {
11272
+ candidates.add(subcommand);
11273
+ }
11274
+ }
11275
+ for (const flag of collectVisibleLongFlags(command)) {
11276
+ if (flag.startsWith(current)) {
11277
+ candidates.add(flag);
11278
+ }
11279
+ }
11280
+ return [...candidates].sort();
11281
+ }
11282
+
11283
+ // src/completion/install.ts
11284
+ import { execFileSync } from "child_process";
11285
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync3 } from "fs";
11286
+ import { homedir as homedir2, platform } from "os";
11287
+ import { basename as basename2, dirname as dirname2, join as join2 } from "path";
11288
+
11289
+ // src/completion/scripts.ts
11290
+ var PROGRAM = "zinkee";
11291
+ var ENV_VAR = "_ZINKEE_COMPLETE";
11292
+ function renderBashScript() {
11293
+ return `_zinkee_completion() {
11294
+ local IFS=$'\\n'
11295
+ local response
11296
+
11297
+ response=$(env COMP_WORDS="\${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD ${ENV_VAR}=bash_complete ${PROGRAM} 2>/dev/null)
11298
+
11299
+ COMPREPLY=( \${response} )
11300
+ return 0
11301
+ }
11302
+
11303
+ complete -o default -F _zinkee_completion ${PROGRAM}
11304
+ `;
11305
+ }
11306
+ function renderZshScript() {
11307
+ return `#compdef ${PROGRAM}
11308
+
11309
+ _zinkee_completion() {
11310
+ local -a response
11311
+ (( ! $+commands[${PROGRAM}] )) && return 1
11312
+
11313
+ response=("\${(@f)$(env COMP_WORDS="\${words[*]}" COMP_CWORD=$((CURRENT - 1)) ${ENV_VAR}=zsh_complete ${PROGRAM} 2>/dev/null)}")
11314
+
11315
+ if [ -n "$response" ]; then
11316
+ compadd -U -V unsorted -a response
11317
+ fi
11318
+ }
11319
+
11320
+ if [[ $zsh_eval_context[-1] == loadautofunc ]]; then
11321
+ _zinkee_completion "$@"
11322
+ else
11323
+ compdef _zinkee_completion ${PROGRAM}
11324
+ fi
11325
+ `;
11326
+ }
11327
+ function renderFishScript() {
11328
+ return `function __zinkee_completion
11329
+ set -l tokens (commandline -opc) (commandline -ct)
11330
+ set -l cword (math (count $tokens) - 1)
11331
+ set -l joined (string join " " -- $tokens)
11332
+
11333
+ env COMP_WORDS="$joined" COMP_CWORD=$cword ${ENV_VAR}=fish_complete ${PROGRAM} 2>/dev/null
11334
+ end
11335
+
11336
+ complete --no-files --command ${PROGRAM} --arguments '(__zinkee_completion)'
11337
+ `;
11338
+ }
11339
+ function renderPowerShellScript() {
11340
+ return `Register-ArgumentCompleter -Native -CommandName ${PROGRAM} -ScriptBlock {
11341
+ param($wordToComplete, $commandAst, $cursorPosition)
11342
+
11343
+ $tokens = @($commandAst.CommandElements | ForEach-Object { $_.ToString() })
11344
+ $cword = [Math]::Max(0, $tokens.Count - 1)
11345
+
11346
+ $env:COMP_WORDS = ($tokens -join ' ')
11347
+ $env:COMP_CWORD = $cword
11348
+ $env:${ENV_VAR} = 'powershell_complete'
11349
+
11350
+ try {
11351
+ & ${PROGRAM} 2>$null | ForEach-Object {
11352
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
11353
+ }
11354
+ } finally {
11355
+ Remove-Item Env:COMP_WORDS -ErrorAction SilentlyContinue
11356
+ Remove-Item Env:COMP_CWORD -ErrorAction SilentlyContinue
11357
+ Remove-Item Env:${ENV_VAR} -ErrorAction SilentlyContinue
11358
+ }
11359
+ }
11360
+ `;
11361
+ }
11362
+ function renderScript(shell) {
11363
+ switch (shell) {
11364
+ case "bash":
11365
+ return renderBashScript();
11366
+ case "zsh":
11367
+ return renderZshScript();
11368
+ case "fish":
11369
+ return renderFishScript();
11370
+ case "powershell":
11371
+ return renderPowerShellScript();
11372
+ }
11373
+ }
11374
+
11375
+ // src/completion/install.ts
11376
+ var SUPPORTED_SHELLS = [
11377
+ "bash",
11378
+ "zsh",
11379
+ "fish",
11380
+ "powershell"
11381
+ ];
11382
+ var BLOCK_BEGIN = "# >>> zinkee completion >>>";
11383
+ var BLOCK_END = "# <<< zinkee completion <<<";
11384
+ var CompletionShellError = class extends Error {
11385
+ name = "CompletionShellError";
11386
+ };
11387
+ function defaultPowerShellProfile(env) {
11388
+ for (const exe of ["pwsh", "powershell.exe"]) {
11389
+ try {
11390
+ const out = execFileSync(exe, ["-NoProfile", "-Command", "$PROFILE"], {
11391
+ encoding: "utf8",
11392
+ stdio: ["ignore", "pipe", "ignore"]
11393
+ });
11394
+ const path = out.trim();
11395
+ if (path) {
11396
+ return path;
11397
+ }
11398
+ } catch {
11399
+ }
11400
+ }
11401
+ if (env.platform === "win32") {
11402
+ return join2(env.home, "Documents", "PowerShell", "Microsoft.PowerShell_profile.ps1");
11403
+ }
11404
+ return join2(env.home, ".config", "powershell", "Microsoft.PowerShell_profile.ps1");
11405
+ }
11406
+ function detectShell(env) {
11407
+ const fromEnv = env.shellEnv;
11408
+ if (fromEnv) {
11409
+ const name = basename2(fromEnv).toLowerCase().replace(/\.exe$/, "");
11410
+ if (name === "bash") {
11411
+ return "bash";
11412
+ }
11413
+ if (name === "zsh") {
11414
+ return "zsh";
11415
+ }
11416
+ if (name === "fish") {
11417
+ return "fish";
11418
+ }
11419
+ if (name === "pwsh" || name === "powershell") {
11420
+ return "powershell";
11421
+ }
11422
+ }
11423
+ if (env.platform === "win32") {
11424
+ return "powershell";
11425
+ }
11426
+ throw new CompletionShellError(
11427
+ `Could not detect a supported shell from $SHELL=${fromEnv ?? ""}. Pass one of: ${SUPPORTED_SHELLS.join(", ")}.`
11428
+ );
11429
+ }
11430
+ function parseShell(value) {
11431
+ const normalized = value.toLowerCase();
11432
+ if (normalized === "bash" || normalized === "zsh" || normalized === "fish" || normalized === "powershell") {
11433
+ return normalized;
11434
+ }
11435
+ if (normalized === "pwsh") {
11436
+ return "powershell";
11437
+ }
11438
+ throw new CompletionShellError(
11439
+ `Unsupported shell "${value}". Supported: ${SUPPORTED_SHELLS.join(", ")}.`
11440
+ );
11441
+ }
11442
+ function resolveTarget(shell, env) {
11443
+ switch (shell) {
11444
+ case "bash":
11445
+ return {
11446
+ shell,
11447
+ scriptPath: join2(env.home, ".bash_completions", "zinkee.sh"),
11448
+ rcPath: join2(env.home, ".bashrc")
11449
+ };
11450
+ case "zsh":
11451
+ return {
11452
+ shell,
11453
+ scriptPath: join2(env.home, ".zfunc", "_zinkee"),
11454
+ rcPath: join2(env.home, ".zshrc")
11455
+ };
11456
+ case "fish":
11457
+ return {
11458
+ shell,
11459
+ scriptPath: join2(env.home, ".config", "fish", "completions", "zinkee.fish")
11460
+ };
11461
+ case "powershell": {
11462
+ const resolver = env.resolvePowerShellProfile ?? (() => defaultPowerShellProfile(env));
11463
+ const profilePath = resolver();
11464
+ if (!profilePath) {
11465
+ throw new CompletionShellError(
11466
+ "Could not resolve PowerShell $PROFILE path. Install PowerShell or use --show-completion."
11467
+ );
11468
+ }
11469
+ return {
11470
+ shell,
11471
+ scriptPath: profilePath,
11472
+ rcPath: profilePath
11473
+ };
11474
+ }
11475
+ }
11476
+ }
11477
+ function buildRcSnippet(shell, scriptPath) {
11478
+ switch (shell) {
11479
+ case "bash":
11480
+ return `source "${scriptPath}"`;
11481
+ case "zsh":
11482
+ return [
11483
+ `fpath=("${dirname2(scriptPath)}" $fpath)`,
11484
+ "autoload -Uz compinit && compinit"
11485
+ ].join("\n");
11486
+ default:
11487
+ return "";
11488
+ }
11489
+ }
11490
+ function applyBlock(existing, block) {
11491
+ const wrapped = `${BLOCK_BEGIN}
11492
+ ${block.trimEnd()}
11493
+ ${BLOCK_END}
11494
+ `;
11495
+ const blockPattern = new RegExp(
11496
+ `${BLOCK_BEGIN.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${BLOCK_END.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`,
11497
+ "m"
11498
+ );
11499
+ if (blockPattern.test(existing)) {
11500
+ return existing.replace(blockPattern, wrapped);
11501
+ }
11502
+ const separator = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
11503
+ return `${existing}${separator}${wrapped}`;
11504
+ }
11505
+ function readFileOrEmpty(path) {
11506
+ try {
11507
+ return readFileSync10(path, "utf8");
11508
+ } catch (error) {
11509
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
11510
+ return "";
11511
+ }
11512
+ throw error;
11513
+ }
11514
+ }
11515
+ function writeFileEnsuringDir(path, content) {
11516
+ mkdirSync2(dirname2(path), { recursive: true });
11517
+ writeFileSync3(path, content, "utf8");
11518
+ }
11519
+ function installCompletion(shell, env) {
11520
+ const target = resolveTarget(shell, env);
11521
+ const script = renderScript(shell);
11522
+ if (shell === "powershell") {
11523
+ const existing = readFileOrEmpty(target.scriptPath);
11524
+ const updated = applyBlock(existing, script);
11525
+ writeFileEnsuringDir(target.scriptPath, updated);
11526
+ return {
11527
+ shell,
11528
+ scriptPath: target.scriptPath,
11529
+ rcPath: target.scriptPath,
11530
+ rcUpdated: true
11531
+ };
11532
+ }
11533
+ writeFileEnsuringDir(target.scriptPath, script);
11534
+ if (!target.rcPath) {
11535
+ return {
11536
+ shell,
11537
+ scriptPath: target.scriptPath,
11538
+ rcUpdated: false
11539
+ };
11540
+ }
11541
+ const snippet = buildRcSnippet(shell, target.scriptPath);
11542
+ if (!snippet) {
11543
+ return {
11544
+ shell,
11545
+ scriptPath: target.scriptPath,
11546
+ rcPath: target.rcPath,
11547
+ rcUpdated: false
11548
+ };
11549
+ }
11550
+ const existingRc = readFileOrEmpty(target.rcPath);
11551
+ const updatedRc = applyBlock(existingRc, snippet);
11552
+ if (updatedRc !== existingRc) {
11553
+ writeFileEnsuringDir(target.rcPath, updatedRc);
11554
+ return {
11555
+ shell,
11556
+ scriptPath: target.scriptPath,
11557
+ rcPath: target.rcPath,
11558
+ rcUpdated: true
11559
+ };
11560
+ }
11561
+ return {
11562
+ shell,
11563
+ scriptPath: target.scriptPath,
11564
+ rcPath: target.rcPath,
11565
+ rcUpdated: false
11566
+ };
11567
+ }
11568
+ function currentEnvironment() {
11569
+ return {
11570
+ home: homedir2(),
11571
+ platform: platform(),
11572
+ shellEnv: process.env.SHELL
11573
+ };
11574
+ }
11575
+
11576
+ // src/completion/runtime.ts
11577
+ var COMPLETION_ENV_VAR = "_ZINKEE_COMPLETE";
11578
+ function getStream(stream, fallback) {
11579
+ return stream ?? fallback;
11580
+ }
11581
+ function parseEnvWords(env) {
11582
+ const raw = env.COMP_WORDS ?? "";
11583
+ if (raw.trim().length === 0) {
11584
+ return [];
11585
+ }
11586
+ return raw.split(/\s+/).filter((token) => token.length > 0);
11587
+ }
11588
+ function parseEnvCwordIndex(env) {
11589
+ const raw = env.COMP_CWORD ?? "0";
11590
+ const parsed = Number.parseInt(raw, 10);
11591
+ if (Number.isNaN(parsed) || parsed < 0) {
11592
+ return 0;
11593
+ }
11594
+ return parsed;
11595
+ }
11596
+ function isCompletionRequest(env = process.env) {
11597
+ return typeof env[COMPLETION_ENV_VAR] === "string" && env[COMPLETION_ENV_VAR].length > 0;
11598
+ }
11599
+ function runCompletionRequest(env = process.env, stdout) {
11600
+ const out = getStream(stdout, process.stdout);
11601
+ try {
11602
+ const program = buildProgram();
11603
+ const candidates = getCompletions(program, {
11604
+ words: parseEnvWords(env),
11605
+ cwordIndex: parseEnvCwordIndex(env)
11606
+ });
11607
+ if (candidates.length > 0) {
11608
+ out.write(`${candidates.join("\n")}
11609
+ `);
11610
+ }
11611
+ } catch {
11612
+ }
11613
+ }
11614
+ function extractCompletionFlag(argv) {
11615
+ for (let index = 0; index < argv.length; index += 1) {
11616
+ const token = argv[index];
11617
+ if (!token) {
11618
+ continue;
11619
+ }
11620
+ if (token === "--install-completion" || token === "--show-completion") {
11621
+ const mode = token === "--install-completion" ? "install" : "show";
11622
+ const next = argv[index + 1];
11623
+ const shellOverride = next && !next.startsWith("-") ? next : void 0;
11624
+ return { mode, shellOverride };
11625
+ }
11626
+ if (token.startsWith("--install-completion=")) {
11627
+ const value = token.slice("--install-completion=".length);
11628
+ return { mode: "install", shellOverride: value || void 0 };
11629
+ }
11630
+ if (token.startsWith("--show-completion=")) {
11631
+ const value = token.slice("--show-completion=".length);
11632
+ return { mode: "show", shellOverride: value || void 0 };
11633
+ }
11634
+ }
11635
+ return null;
11636
+ }
11637
+ function resolveShell(request, env) {
11638
+ if (request.shellOverride) {
11639
+ return parseShell(request.shellOverride);
11640
+ }
11641
+ return detectShell(env);
11642
+ }
11643
+ function handleCompletionFlag(request, deps = {}) {
11644
+ const env = deps.environment ?? currentEnvironment();
11645
+ const out = getStream(deps.stdout, process.stdout);
11646
+ const err = getStream(deps.stderr, process.stderr);
11647
+ let shell;
11648
+ try {
11649
+ shell = resolveShell(request, env);
11650
+ } catch (error) {
11651
+ const message = error instanceof CompletionShellError ? error.message : String(error);
11652
+ err.write(`${message}
11653
+ `);
11654
+ return 1;
11655
+ }
11656
+ if (request.mode === "show") {
11657
+ out.write(renderScript(shell));
11658
+ return 0;
11659
+ }
11660
+ try {
11661
+ const result = installCompletion(shell, env);
11662
+ err.write(`zinkee completion installed for ${shell}.
11663
+ `);
11664
+ err.write(` script: ${result.scriptPath}
11665
+ `);
11666
+ if (result.rcPath && result.rcPath !== result.scriptPath) {
11667
+ err.write(
11668
+ result.rcUpdated ? ` updated: ${result.rcPath}
11669
+ ` : ` unchanged: ${result.rcPath}
11670
+ `
11671
+ );
11672
+ }
11673
+ err.write(`Restart your shell or open a new session to enable completion.
11674
+ `);
11675
+ return 0;
11676
+ } catch (error) {
11677
+ const message = error instanceof Error ? error.message : String(error);
11678
+ err.write(`Failed to install zinkee completion: ${message}
11679
+ `);
11680
+ return 1;
11681
+ }
11682
+ }
11683
+
10979
11684
  // src/cli-runner.ts
10980
11685
  var ExampleDisplayed = class extends Error {
10981
11686
  name = "ExampleDisplayed";
@@ -10998,7 +11703,19 @@ function isExecutedAsEntry(argvEntry, moduleUrl) {
10998
11703
  }
10999
11704
  async function runCli(argv) {
11000
11705
  try {
11001
- const exampleRequest = extractExampleRequest(argv);
11706
+ if (isCompletionRequest()) {
11707
+ runCompletionRequest();
11708
+ return;
11709
+ }
11710
+ const completionFlag = extractCompletionFlag(argv);
11711
+ if (completionFlag) {
11712
+ const exitCode = handleCompletionFlag(completionFlag);
11713
+ process.exitCode = exitCode;
11714
+ return;
11715
+ }
11716
+ const program = buildProgram();
11717
+ const optionMeta = buildOptionMetadata(program);
11718
+ const exampleRequest = extractExampleRequest(argv, optionMeta);
11002
11719
  if (exampleRequest) {
11003
11720
  await showExamples(
11004
11721
  exampleRequest.commandName,
@@ -11012,7 +11729,8 @@ async function runCli(argv) {
11012
11729
  );
11013
11730
  throw new ExampleDisplayed();
11014
11731
  }
11015
- await buildProgram().parseAsync(argv);
11732
+ const rewrittenArgv = hoistGlobalOptions(argv, optionMeta, commandRegistry);
11733
+ await program.parseAsync(rewrittenArgv);
11016
11734
  } catch (error) {
11017
11735
  if (error instanceof ExampleDisplayed) {
11018
11736
  process.exitCode = 0;