@attest-it/cli 0.5.0 → 0.6.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/dist/index.cjs CHANGED
@@ -6,6 +6,7 @@ var path = require('path');
6
6
  var chromaterm = require('chromaterm');
7
7
  var prompts = require('@inquirer/prompts');
8
8
  var core = require('@attest-it/core');
9
+ var tabtab2 = require('@pnpm/tabtab');
9
10
  var child_process = require('child_process');
10
11
  var os = require('os');
11
12
  var shellQuote = require('shell-quote');
@@ -15,7 +16,6 @@ var jsxRuntime = require('react/jsx-runtime');
15
16
  var promises = require('fs/promises');
16
17
  var ui = require('@inkjs/ui');
17
18
  var yaml = require('yaml');
18
- var tabtab = require('@pnpm/tabtab');
19
19
  var url = require('url');
20
20
 
21
21
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
@@ -41,9 +41,9 @@ function _interopNamespace(e) {
41
41
 
42
42
  var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
43
43
  var path__namespace = /*#__PURE__*/_interopNamespace(path);
44
+ var tabtab2__default = /*#__PURE__*/_interopDefault(tabtab2);
44
45
  var os__namespace = /*#__PURE__*/_interopNamespace(os);
45
46
  var React7__namespace = /*#__PURE__*/_interopNamespace(React7);
46
- var tabtab__default = /*#__PURE__*/_interopDefault(tabtab);
47
47
 
48
48
  // src/index.ts
49
49
  var globalOptions = {};
@@ -197,6 +197,87 @@ var ExitCode = {
197
197
  /** Missing required key file */
198
198
  MISSING_KEY: 5
199
199
  };
200
+ var PROGRAM_NAME = "attest-it";
201
+ var PROGRAM_ALIAS = "attest";
202
+ function detectCurrentShell() {
203
+ const shellPath = process.env.SHELL ?? "";
204
+ if (shellPath.endsWith("/bash") || shellPath.endsWith("/bash.exe")) {
205
+ return "bash";
206
+ }
207
+ if (shellPath.endsWith("/zsh") || shellPath.endsWith("/zsh.exe")) {
208
+ return "zsh";
209
+ }
210
+ if (shellPath.endsWith("/fish") || shellPath.endsWith("/fish.exe")) {
211
+ return "fish";
212
+ }
213
+ return null;
214
+ }
215
+ function getSourceCommand(shell) {
216
+ switch (shell) {
217
+ case "bash":
218
+ return "source ~/.bashrc";
219
+ case "zsh":
220
+ return "source ~/.zshrc";
221
+ case "fish":
222
+ return "source ~/.config/fish/config.fish";
223
+ }
224
+ }
225
+ async function offerCompletionInstall() {
226
+ try {
227
+ const prefs = await core.loadPreferences();
228
+ if (prefs.cliExperience?.declinedCompletionInstall) {
229
+ return false;
230
+ }
231
+ const shell = detectCurrentShell();
232
+ if (!shell) {
233
+ return false;
234
+ }
235
+ log("");
236
+ const shouldInstall = await prompts.confirm({
237
+ message: `Would you like to enable shell completions for ${shell}?`,
238
+ default: true
239
+ });
240
+ if (!shouldInstall) {
241
+ await core.savePreferences({
242
+ ...prefs,
243
+ cliExperience: {
244
+ ...prefs.cliExperience,
245
+ declinedCompletionInstall: true
246
+ }
247
+ });
248
+ log("");
249
+ info("No problem! If you change your mind, you can run:");
250
+ log(" attest-it completion install");
251
+ log("");
252
+ return false;
253
+ }
254
+ await tabtab2__default.default.install({
255
+ name: PROGRAM_NAME,
256
+ completer: PROGRAM_NAME,
257
+ shell
258
+ });
259
+ await tabtab2__default.default.install({
260
+ name: PROGRAM_ALIAS,
261
+ completer: PROGRAM_ALIAS,
262
+ shell
263
+ });
264
+ log("");
265
+ success(`Shell completions installed for ${shell}!`);
266
+ info(`Completions enabled for both "${PROGRAM_NAME}" and "${PROGRAM_ALIAS}" commands.`);
267
+ log("");
268
+ info("Restart your shell or run:");
269
+ log(` ${getSourceCommand(shell)}`);
270
+ log("");
271
+ return true;
272
+ } catch (err) {
273
+ error(`Failed to install completions: ${err instanceof Error ? err.message : String(err)}`);
274
+ log("");
275
+ info("You can try again later with:");
276
+ log(" attest-it completion install");
277
+ log("");
278
+ return false;
279
+ }
280
+ }
200
281
 
201
282
  // src/commands/init.ts
202
283
  var initCommand = new commander.Command("init").description("Initialize attest-it configuration").option("-p, --path <path>", "Config file path", ".attest-it/config.yaml").option("-f, --force", "Overwrite existing config").action(async (options) => {
@@ -260,6 +341,7 @@ async function runInit(options) {
260
341
  log(` 1. Edit ${options.path} to define your test suites`);
261
342
  log(" 2. Run: attest-it keygen");
262
343
  log(" 3. Run: attest-it status");
344
+ await offerCompletionInstall();
263
345
  } catch (err) {
264
346
  if (err instanceof Error) {
265
347
  error(err.message);
@@ -1550,6 +1632,15 @@ function createKeyProviderFromIdentity(identity) {
1550
1632
  field: privateKey.field
1551
1633
  }
1552
1634
  });
1635
+ case "yubikey":
1636
+ return core.KeyProviderRegistry.create({
1637
+ type: "yubikey",
1638
+ options: {
1639
+ encryptedKeyPath: privateKey.encryptedKeyPath,
1640
+ slot: privateKey.slot,
1641
+ serial: privateKey.serial
1642
+ }
1643
+ });
1553
1644
  default: {
1554
1645
  const _exhaustiveCheck = privateKey;
1555
1646
  throw new Error(`Unsupported private key type: ${String(_exhaustiveCheck)}`);
@@ -1565,6 +1656,8 @@ function getKeyRefFromIdentity(identity) {
1565
1656
  return `${privateKey.service}:${privateKey.account}`;
1566
1657
  case "1password":
1567
1658
  return privateKey.item;
1659
+ case "yubikey":
1660
+ return privateKey.encryptedKeyPath;
1568
1661
  default: {
1569
1662
  const _exhaustiveCheck = privateKey;
1570
1663
  throw new Error(`Unsupported private key type: ${String(_exhaustiveCheck)}`);
@@ -2408,6 +2501,15 @@ function createKeyProviderFromIdentity2(identity) {
2408
2501
  field: privateKey.field
2409
2502
  }
2410
2503
  });
2504
+ case "yubikey":
2505
+ return core.KeyProviderRegistry.create({
2506
+ type: "yubikey",
2507
+ options: {
2508
+ encryptedKeyPath: privateKey.encryptedKeyPath,
2509
+ slot: privateKey.slot,
2510
+ serial: privateKey.serial
2511
+ }
2512
+ });
2411
2513
  default: {
2412
2514
  const _exhaustiveCheck = privateKey;
2413
2515
  throw new Error(`Unsupported private key type: ${String(_exhaustiveCheck)}`);
@@ -2423,6 +2525,8 @@ function getKeyRefFromIdentity2(identity) {
2423
2525
  return privateKey.service;
2424
2526
  case "1password":
2425
2527
  return privateKey.item;
2528
+ case "yubikey":
2529
+ return privateKey.encryptedKeyPath;
2426
2530
  default: {
2427
2531
  const _exhaustiveCheck = privateKey;
2428
2532
  throw new Error(`Unsupported private key type: ${String(_exhaustiveCheck)}`);
@@ -2462,6 +2566,9 @@ async function runList() {
2462
2566
  case "1password":
2463
2567
  keyType = "1password";
2464
2568
  break;
2569
+ case "yubikey":
2570
+ keyType = "yubikey";
2571
+ break;
2465
2572
  }
2466
2573
  log(`${marker} ${theme3.blue(slug)}`);
2467
2574
  log(` Name: ${nameDisplay}`);
@@ -2551,6 +2658,8 @@ async function runCreate() {
2551
2658
  info("Checking available key storage providers...");
2552
2659
  const opAvailable = await core.OnePasswordKeyProvider.isInstalled();
2553
2660
  const keychainAvailable = core.MacOSKeychainKeyProvider.isAvailable();
2661
+ const yubikeyInstalled = await core.YubiKeyProvider.isInstalled();
2662
+ const yubikeyConnected = yubikeyInstalled ? await core.YubiKeyProvider.isConnected() : false;
2554
2663
  const configDir = core.getAttestItConfigDir();
2555
2664
  const storageChoices = [
2556
2665
  { name: `File system (${path.join(configDir, "keys")})`, value: "file" }
@@ -2561,6 +2670,10 @@ async function runCreate() {
2561
2670
  if (opAvailable) {
2562
2671
  storageChoices.push({ name: "1Password", value: "1password" });
2563
2672
  }
2673
+ if (yubikeyInstalled) {
2674
+ const yubikeyLabel = yubikeyConnected ? "YubiKey (encrypted with challenge-response)" : "YubiKey (not connected - insert YubiKey first)";
2675
+ storageChoices.push({ name: yubikeyLabel, value: "yubikey" });
2676
+ }
2564
2677
  const keyStorageType = await prompts.select({
2565
2678
  message: "Where should the private key be stored?",
2566
2679
  choices: storageChoices
@@ -2760,6 +2873,75 @@ async function runCreate() {
2760
2873
  keyStorageDescription = `1Password (${selectedVault}/${item})`;
2761
2874
  break;
2762
2875
  }
2876
+ case "yubikey": {
2877
+ if (!await core.YubiKeyProvider.isConnected()) {
2878
+ error("No YubiKey detected. Please insert your YubiKey and try again.");
2879
+ process.exit(ExitCode.CONFIG_ERROR);
2880
+ }
2881
+ const yubikeys = await core.YubiKeyProvider.listDevices();
2882
+ if (yubikeys.length === 0) {
2883
+ throw new Error("No YubiKeys detected. Please insert a YubiKey and try again.");
2884
+ }
2885
+ const formatYubiKeyChoice = (yk) => {
2886
+ return `${theme3.blue.bold()(yk.type)} ${theme3.muted(`(Serial: ${yk.serial}, FW: ${yk.firmware})`)}`;
2887
+ };
2888
+ let selectedSerial;
2889
+ if (yubikeys.length === 1 && yubikeys[0]) {
2890
+ selectedSerial = yubikeys[0].serial;
2891
+ info(`Using YubiKey: ${formatYubiKeyChoice(yubikeys[0])}`);
2892
+ } else {
2893
+ selectedSerial = await prompts.select({
2894
+ message: "Select YubiKey:",
2895
+ choices: yubikeys.map((yk) => ({
2896
+ name: formatYubiKeyChoice(yk),
2897
+ value: yk.serial
2898
+ }))
2899
+ });
2900
+ }
2901
+ const slot = 2;
2902
+ const isChallengeResponseConfigured = await core.YubiKeyProvider.isChallengeResponseConfigured(
2903
+ slot,
2904
+ selectedSerial
2905
+ );
2906
+ if (!isChallengeResponseConfigured) {
2907
+ log("");
2908
+ error(`YubiKey slot ${String(slot)} is not configured for HMAC challenge-response.`);
2909
+ log("");
2910
+ log("To configure it, run:");
2911
+ log(theme3.blue(` ykman otp chalresp --generate ${String(slot)}`));
2912
+ log("");
2913
+ log("This will configure slot 2 with a randomly generated secret.");
2914
+ log(theme3.muted("Note: Make sure to back up the secret if needed for recovery."));
2915
+ process.exit(ExitCode.CONFIG_ERROR);
2916
+ }
2917
+ const encryptedKeyName = await prompts.input({
2918
+ message: "Encrypted key file name:",
2919
+ default: `${slug}.enc`,
2920
+ validate: (value) => {
2921
+ if (!value || value.trim().length === 0) {
2922
+ return "File name cannot be empty";
2923
+ }
2924
+ return true;
2925
+ }
2926
+ });
2927
+ const keysDir = path.join(core.getAttestItConfigDir(), "keys");
2928
+ await promises.mkdir(keysDir, { recursive: true });
2929
+ const encryptedKeyPath = path.join(keysDir, encryptedKeyName);
2930
+ const result = await core.YubiKeyProvider.encryptPrivateKey({
2931
+ privateKey: keyPair.privateKey,
2932
+ encryptedKeyPath,
2933
+ slot,
2934
+ serial: selectedSerial
2935
+ });
2936
+ privateKeyRef = {
2937
+ type: "yubikey",
2938
+ encryptedKeyPath: result.encryptedKeyPath,
2939
+ slot,
2940
+ serial: selectedSerial
2941
+ };
2942
+ keyStorageDescription = result.storageDescription;
2943
+ break;
2944
+ }
2763
2945
  default:
2764
2946
  throw new Error(`Unknown key storage type: ${keyStorageType}`);
2765
2947
  }
@@ -2809,6 +2991,7 @@ async function runCreate() {
2809
2991
  log(`To use this identity, run: attest-it identity use ${slug}`);
2810
2992
  log("");
2811
2993
  }
2994
+ await offerCompletionInstall();
2812
2995
  } catch (err) {
2813
2996
  if (err instanceof Error) {
2814
2997
  error(err.message);
@@ -3091,6 +3274,11 @@ function formatKeyLocation(privateKey) {
3091
3274
  }
3092
3275
  return `${theme3.blue.bold()("1Password")}: ${theme3.muted(parts.join("/"))}`;
3093
3276
  }
3277
+ case "yubikey": {
3278
+ const slotInfo = privateKey.slot ? ` (slot ${String(privateKey.slot)})` : "";
3279
+ const serialInfo = privateKey.serial ? ` [${privateKey.serial}]` : "";
3280
+ return `${theme3.blue.bold()("YubiKey")}${serialInfo}${slotInfo}: ${theme3.muted(privateKey.encryptedKeyPath)}`;
3281
+ }
3094
3282
  default:
3095
3283
  return "Unknown storage";
3096
3284
  }
@@ -3764,11 +3952,16 @@ async function runRemove2(slug, options) {
3764
3952
 
3765
3953
  // src/commands/team/index.ts
3766
3954
  var teamCommand = new commander.Command("team").description("Manage team members and authorizations").addCommand(listCommand2).addCommand(addCommand).addCommand(editCommand2).addCommand(removeCommand2);
3767
- var PROGRAM_NAME = "attest-it";
3955
+ var PROGRAM_NAME2 = "attest-it";
3956
+ var PROGRAM_ALIAS2 = "attest";
3957
+ var PROGRAM_NAMES = [PROGRAM_NAME2, PROGRAM_ALIAS2];
3958
+ function isSupportedShell(value) {
3959
+ return value === "bash" || value === "zsh" || value === "fish";
3960
+ }
3768
3961
  async function getCompletions(env) {
3769
3962
  let shell;
3770
3963
  try {
3771
- const detectedShell = tabtab__default.default.getShellFromEnv(process.env);
3964
+ const detectedShell = tabtab2__default.default.getShellFromEnv(process.env);
3772
3965
  shell = detectedShell === "pwsh" ? "bash" : detectedShell;
3773
3966
  } catch {
3774
3967
  shell = "bash";
@@ -3812,15 +4005,15 @@ async function getCompletions(env) {
3812
4005
  const lastWord = env.last;
3813
4006
  const prevWord = env.prev;
3814
4007
  if (prevWord === "--config" || prevWord === "-c") {
3815
- tabtab__default.default.logFiles();
4008
+ tabtab2__default.default.logFiles();
3816
4009
  return;
3817
4010
  }
3818
4011
  if (lastWord.startsWith("-")) {
3819
- tabtab__default.default.log(globalOptions2, shell, console.log);
4012
+ tabtab2__default.default.log(globalOptions2, shell, console.log);
3820
4013
  return;
3821
4014
  }
3822
4015
  const commandIndex = words.findIndex(
3823
- (w) => !w.startsWith("-") && w !== PROGRAM_NAME && w !== "npx"
4016
+ (w) => !w.startsWith("-") && !PROGRAM_NAMES.includes(w) && w !== "npx"
3824
4017
  );
3825
4018
  const currentCommand = commandIndex >= 0 ? words[commandIndex] ?? null : null;
3826
4019
  if (currentCommand === "identity") {
@@ -3831,12 +4024,12 @@ async function getCompletions(env) {
3831
4024
  if (subcommand === "use" || subcommand === "remove") {
3832
4025
  const identities = await getIdentitySlugs();
3833
4026
  if (identities.length > 0) {
3834
- tabtab__default.default.log(identities, shell, console.log);
4027
+ tabtab2__default.default.log(identities, shell, console.log);
3835
4028
  return;
3836
4029
  }
3837
4030
  }
3838
4031
  if (!subcommand || subcommandIndex < 0) {
3839
- tabtab__default.default.log(identitySubcommands, shell, console.log);
4032
+ tabtab2__default.default.log(identitySubcommands, shell, console.log);
3840
4033
  return;
3841
4034
  }
3842
4035
  }
@@ -3846,7 +4039,7 @@ async function getCompletions(env) {
3846
4039
  );
3847
4040
  const subcommand = subcommandIndex >= 0 ? words[subcommandIndex] ?? null : null;
3848
4041
  if (!subcommand || subcommandIndex < 0) {
3849
- tabtab__default.default.log(teamSubcommands, shell, console.log);
4042
+ tabtab2__default.default.log(teamSubcommands, shell, console.log);
3850
4043
  return;
3851
4044
  }
3852
4045
  }
@@ -3856,30 +4049,43 @@ async function getCompletions(env) {
3856
4049
  );
3857
4050
  const subcommand = subcommandIndex >= 0 ? words[subcommandIndex] ?? null : null;
3858
4051
  if (subcommand === "install") {
3859
- tabtab__default.default.log(["bash", "zsh", "fish"], shell, console.log);
4052
+ tabtab2__default.default.log(["bash", "zsh", "fish"], shell, console.log);
3860
4053
  return;
3861
4054
  }
3862
4055
  if (!subcommand || subcommandIndex < 0) {
3863
- tabtab__default.default.log(completionSubcommands, shell, console.log);
4056
+ tabtab2__default.default.log(completionSubcommands, shell, console.log);
3864
4057
  return;
3865
4058
  }
3866
4059
  }
3867
4060
  if (currentCommand === "status" || currentCommand === "verify" || currentCommand === "seal") {
3868
4061
  const gates = await getGateNames();
3869
4062
  if (gates.length > 0) {
3870
- tabtab__default.default.log(gates, shell, console.log);
4063
+ tabtab2__default.default.log(gates, shell, console.log);
3871
4064
  return;
3872
4065
  }
3873
4066
  }
3874
4067
  if (currentCommand === "run") {
3875
4068
  const suites = await getSuiteNames();
3876
4069
  if (suites.length > 0) {
3877
- tabtab__default.default.log(suites, shell, console.log);
4070
+ tabtab2__default.default.log(suites, shell, console.log);
3878
4071
  return;
3879
4072
  }
3880
4073
  }
3881
- if (!currentCommand) {
3882
- tabtab__default.default.log([...commands, ...globalOptions2], shell, console.log);
4074
+ const knownCommands = [
4075
+ "init",
4076
+ "status",
4077
+ "run",
4078
+ "verify",
4079
+ "seal",
4080
+ "keygen",
4081
+ "prune",
4082
+ "identity",
4083
+ "team",
4084
+ "whoami",
4085
+ "completion"
4086
+ ];
4087
+ if (!currentCommand || !knownCommands.includes(currentCommand)) {
4088
+ tabtab2__default.default.log([...commands, ...globalOptions2], shell, console.log);
3883
4089
  }
3884
4090
  }
3885
4091
  async function getIdentitySlugs() {
@@ -3911,35 +4117,65 @@ async function getSuiteNames() {
3911
4117
  return [];
3912
4118
  }
3913
4119
  var completionCommand = new commander.Command("completion").description("Shell completion commands");
3914
- completionCommand.command("install [shell]").description("Install shell completion (bash, zsh, or fish)").action(async (shellArg) => {
4120
+ function detectCurrentShell2() {
4121
+ const shellPath = process.env.SHELL ?? "";
4122
+ if (shellPath.endsWith("/bash") || shellPath.endsWith("/bash.exe")) {
4123
+ return "bash";
4124
+ }
4125
+ if (shellPath.endsWith("/zsh") || shellPath.endsWith("/zsh.exe")) {
4126
+ return "zsh";
4127
+ }
4128
+ if (shellPath.endsWith("/fish") || shellPath.endsWith("/fish.exe")) {
4129
+ return "fish";
4130
+ }
4131
+ return null;
4132
+ }
4133
+ function getSourceCommand2(shell) {
4134
+ switch (shell) {
4135
+ case "bash":
4136
+ return "source ~/.bashrc";
4137
+ case "zsh":
4138
+ return "source ~/.zshrc";
4139
+ case "fish":
4140
+ return "source ~/.config/fish/config.fish";
4141
+ }
4142
+ }
4143
+ completionCommand.command("install [shell]").description("Install shell completion (auto-detects shell, or specify bash/zsh/fish)").action(async (shellArg) => {
3915
4144
  try {
3916
4145
  let shell;
3917
4146
  if (shellArg !== void 0) {
3918
- if (tabtab__default.default.isShellSupported(shellArg)) {
3919
- shell = shellArg;
3920
- } else {
4147
+ if (!isSupportedShell(shellArg)) {
3921
4148
  error(`Shell "${shellArg}" is not supported. Use bash, zsh, or fish.`);
3922
4149
  process.exit(ExitCode.CONFIG_ERROR);
3923
4150
  }
4151
+ shell = shellArg;
4152
+ } else {
4153
+ const detected = detectCurrentShell2();
4154
+ if (!detected) {
4155
+ error(
4156
+ "Could not detect your shell. Please specify: attest-it completion install <bash|zsh|fish>"
4157
+ );
4158
+ process.exit(ExitCode.CONFIG_ERROR);
4159
+ }
4160
+ shell = detected;
4161
+ info(`Detected shell: ${shell}`);
3924
4162
  }
3925
- await tabtab__default.default.install({
3926
- name: PROGRAM_NAME,
3927
- completer: PROGRAM_NAME,
4163
+ await tabtab2__default.default.install({
4164
+ name: PROGRAM_NAME2,
4165
+ completer: PROGRAM_NAME2,
4166
+ shell
4167
+ });
4168
+ await tabtab2__default.default.install({
4169
+ name: PROGRAM_ALIAS2,
4170
+ completer: PROGRAM_ALIAS2,
3928
4171
  shell
3929
4172
  });
3930
4173
  log("");
3931
- success("Shell completion installed!");
4174
+ success(`Shell completion installed for ${shell}!`);
4175
+ info(`Completions enabled for both "${PROGRAM_NAME2}" and "${PROGRAM_ALIAS2}" commands.`);
3932
4176
  log("");
3933
4177
  info("Restart your shell or run:");
3934
- if (shell === "bash" || !shell) {
3935
- log(" source ~/.bashrc");
3936
- }
3937
- if (shell === "zsh" || !shell) {
3938
- log(" source ~/.zshrc");
3939
- }
3940
- if (shell === "fish" || !shell) {
3941
- log(" source ~/.config/fish/config.fish");
3942
- }
4178
+ log(` ${getSourceCommand2(shell)}`);
3943
4179
  log("");
3944
4180
  } catch (err) {
3945
4181
  error(`Failed to install completion: ${err instanceof Error ? err.message : String(err)}`);
@@ -3948,8 +4184,11 @@ completionCommand.command("install [shell]").description("Install shell completi
3948
4184
  });
3949
4185
  completionCommand.command("uninstall").description("Uninstall shell completion").action(async () => {
3950
4186
  try {
3951
- await tabtab__default.default.uninstall({
3952
- name: PROGRAM_NAME
4187
+ await tabtab2__default.default.uninstall({
4188
+ name: PROGRAM_NAME2
4189
+ });
4190
+ await tabtab2__default.default.uninstall({
4191
+ name: PROGRAM_ALIAS2
3953
4192
  });
3954
4193
  log("");
3955
4194
  success("Shell completion uninstalled!");
@@ -3960,11 +4199,19 @@ completionCommand.command("uninstall").description("Uninstall shell completion")
3960
4199
  }
3961
4200
  });
3962
4201
  completionCommand.command("server", { hidden: true }).description("Completion server (internal)").action(async () => {
3963
- const env = tabtab__default.default.parseEnv(process.env);
4202
+ const env = tabtab2__default.default.parseEnv(process.env);
3964
4203
  if (env.complete) {
3965
4204
  await getCompletions(env);
3966
4205
  }
3967
4206
  });
4207
+ function createCompletionServerCommand() {
4208
+ return new commander.Command("completion-server").allowUnknownOption().allowExcessArguments(true).action(async () => {
4209
+ const env = tabtab2__default.default.parseEnv(process.env);
4210
+ if (env.complete) {
4211
+ await getCompletions(env);
4212
+ }
4213
+ });
4214
+ }
3968
4215
  function hasVersion(data) {
3969
4216
  return typeof data === "object" && data !== null && "version" in data && // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
3970
4217
  typeof data.version === "string";
@@ -4009,6 +4256,7 @@ program.addCommand(identityCommand);
4009
4256
  program.addCommand(teamCommand);
4010
4257
  program.addCommand(whoamiCommand);
4011
4258
  program.addCommand(completionCommand);
4259
+ program.addCommand(createCompletionServerCommand(), { hidden: true });
4012
4260
  function processHomeDirOption() {
4013
4261
  const homeDirIndex = process.argv.indexOf("--home-dir");
4014
4262
  if (homeDirIndex !== -1 && homeDirIndex + 1 < process.argv.length) {
@@ -4025,7 +4273,10 @@ async function run() {
4025
4273
  console.log(getPackageVersion());
4026
4274
  process.exit(0);
4027
4275
  }
4028
- await initTheme();
4276
+ const isCompletionServer = process.argv.includes("completion-server");
4277
+ if (!isCompletionServer) {
4278
+ await initTheme();
4279
+ }
4029
4280
  program.parse();
4030
4281
  const options = program.opts();
4031
4282
  const outputOptions = {};