@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.
@@ -6,7 +6,8 @@ import * as path from 'path';
6
6
  import { join, dirname } from 'path';
7
7
  import { detectTheme } from 'chromaterm';
8
8
  import { input, select, confirm, checkbox } from '@inquirer/prompts';
9
- import { loadConfig, toAttestItConfig, readSealsSync, computeFingerprintSync, verifyGateSeal, verifyAllSeals, computeFingerprint, createAttestation, readAttestations, upsertAttestation, KeyProviderRegistry, getDefaultPrivateKeyPath, FilesystemKeyProvider, writeSignedAttestations, loadLocalConfigSync, getActiveIdentity, isAuthorizedSigner, createSeal, writeSealsSync, checkOpenSSL, getDefaultPublicKeyPath, OnePasswordKeyProvider, MacOSKeychainKeyProvider, generateKeyPair, setKeyPermissions, getGate, loadLocalConfig, getAttestItConfigDir, generateEd25519KeyPair, saveLocalConfig, findConfigPath, findAttestation, setAttestItHomeDir } from '@attest-it/core';
9
+ import { loadConfig, toAttestItConfig, readSealsSync, computeFingerprintSync, verifyGateSeal, verifyAllSeals, computeFingerprint, createAttestation, readAttestations, upsertAttestation, KeyProviderRegistry, getDefaultPrivateKeyPath, FilesystemKeyProvider, writeSignedAttestations, loadLocalConfigSync, getActiveIdentity, isAuthorizedSigner, createSeal, writeSealsSync, checkOpenSSL, getDefaultPublicKeyPath, OnePasswordKeyProvider, MacOSKeychainKeyProvider, generateKeyPair, setKeyPermissions, getGate, loadLocalConfig, YubiKeyProvider, getAttestItConfigDir, generateEd25519KeyPair, saveLocalConfig, findConfigPath, loadPreferences, savePreferences, findAttestation, setAttestItHomeDir } from '@attest-it/core';
10
+ import tabtab2 from '@pnpm/tabtab';
10
11
  import { spawn } from 'child_process';
11
12
  import * as os from 'os';
12
13
  import { parse } from 'shell-quote';
@@ -17,7 +18,6 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
17
18
  import { mkdir, writeFile, unlink, readFile } from 'fs/promises';
18
19
  import { Spinner, Select, TextInput } from '@inkjs/ui';
19
20
  import { stringify } from 'yaml';
20
- import tabtab from '@pnpm/tabtab';
21
21
  import { fileURLToPath } from 'url';
22
22
 
23
23
  var globalOptions = {};
@@ -171,6 +171,87 @@ var ExitCode = {
171
171
  /** Missing required key file */
172
172
  MISSING_KEY: 5
173
173
  };
174
+ var PROGRAM_NAME = "attest-it";
175
+ var PROGRAM_ALIAS = "attest";
176
+ function detectCurrentShell() {
177
+ const shellPath = process.env.SHELL ?? "";
178
+ if (shellPath.endsWith("/bash") || shellPath.endsWith("/bash.exe")) {
179
+ return "bash";
180
+ }
181
+ if (shellPath.endsWith("/zsh") || shellPath.endsWith("/zsh.exe")) {
182
+ return "zsh";
183
+ }
184
+ if (shellPath.endsWith("/fish") || shellPath.endsWith("/fish.exe")) {
185
+ return "fish";
186
+ }
187
+ return null;
188
+ }
189
+ function getSourceCommand(shell) {
190
+ switch (shell) {
191
+ case "bash":
192
+ return "source ~/.bashrc";
193
+ case "zsh":
194
+ return "source ~/.zshrc";
195
+ case "fish":
196
+ return "source ~/.config/fish/config.fish";
197
+ }
198
+ }
199
+ async function offerCompletionInstall() {
200
+ try {
201
+ const prefs = await loadPreferences();
202
+ if (prefs.cliExperience?.declinedCompletionInstall) {
203
+ return false;
204
+ }
205
+ const shell = detectCurrentShell();
206
+ if (!shell) {
207
+ return false;
208
+ }
209
+ log("");
210
+ const shouldInstall = await confirm({
211
+ message: `Would you like to enable shell completions for ${shell}?`,
212
+ default: true
213
+ });
214
+ if (!shouldInstall) {
215
+ await savePreferences({
216
+ ...prefs,
217
+ cliExperience: {
218
+ ...prefs.cliExperience,
219
+ declinedCompletionInstall: true
220
+ }
221
+ });
222
+ log("");
223
+ info("No problem! If you change your mind, you can run:");
224
+ log(" attest-it completion install");
225
+ log("");
226
+ return false;
227
+ }
228
+ await tabtab2.install({
229
+ name: PROGRAM_NAME,
230
+ completer: PROGRAM_NAME,
231
+ shell
232
+ });
233
+ await tabtab2.install({
234
+ name: PROGRAM_ALIAS,
235
+ completer: PROGRAM_ALIAS,
236
+ shell
237
+ });
238
+ log("");
239
+ success(`Shell completions installed for ${shell}!`);
240
+ info(`Completions enabled for both "${PROGRAM_NAME}" and "${PROGRAM_ALIAS}" commands.`);
241
+ log("");
242
+ info("Restart your shell or run:");
243
+ log(` ${getSourceCommand(shell)}`);
244
+ log("");
245
+ return true;
246
+ } catch (err) {
247
+ error(`Failed to install completions: ${err instanceof Error ? err.message : String(err)}`);
248
+ log("");
249
+ info("You can try again later with:");
250
+ log(" attest-it completion install");
251
+ log("");
252
+ return false;
253
+ }
254
+ }
174
255
 
175
256
  // src/commands/init.ts
176
257
  var initCommand = new 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) => {
@@ -234,6 +315,7 @@ async function runInit(options) {
234
315
  log(` 1. Edit ${options.path} to define your test suites`);
235
316
  log(" 2. Run: attest-it keygen");
236
317
  log(" 3. Run: attest-it status");
318
+ await offerCompletionInstall();
237
319
  } catch (err) {
238
320
  if (err instanceof Error) {
239
321
  error(err.message);
@@ -1524,6 +1606,15 @@ function createKeyProviderFromIdentity(identity) {
1524
1606
  field: privateKey.field
1525
1607
  }
1526
1608
  });
1609
+ case "yubikey":
1610
+ return KeyProviderRegistry.create({
1611
+ type: "yubikey",
1612
+ options: {
1613
+ encryptedKeyPath: privateKey.encryptedKeyPath,
1614
+ slot: privateKey.slot,
1615
+ serial: privateKey.serial
1616
+ }
1617
+ });
1527
1618
  default: {
1528
1619
  const _exhaustiveCheck = privateKey;
1529
1620
  throw new Error(`Unsupported private key type: ${String(_exhaustiveCheck)}`);
@@ -1539,6 +1630,8 @@ function getKeyRefFromIdentity(identity) {
1539
1630
  return `${privateKey.service}:${privateKey.account}`;
1540
1631
  case "1password":
1541
1632
  return privateKey.item;
1633
+ case "yubikey":
1634
+ return privateKey.encryptedKeyPath;
1542
1635
  default: {
1543
1636
  const _exhaustiveCheck = privateKey;
1544
1637
  throw new Error(`Unsupported private key type: ${String(_exhaustiveCheck)}`);
@@ -2382,6 +2475,15 @@ function createKeyProviderFromIdentity2(identity) {
2382
2475
  field: privateKey.field
2383
2476
  }
2384
2477
  });
2478
+ case "yubikey":
2479
+ return KeyProviderRegistry.create({
2480
+ type: "yubikey",
2481
+ options: {
2482
+ encryptedKeyPath: privateKey.encryptedKeyPath,
2483
+ slot: privateKey.slot,
2484
+ serial: privateKey.serial
2485
+ }
2486
+ });
2385
2487
  default: {
2386
2488
  const _exhaustiveCheck = privateKey;
2387
2489
  throw new Error(`Unsupported private key type: ${String(_exhaustiveCheck)}`);
@@ -2397,6 +2499,8 @@ function getKeyRefFromIdentity2(identity) {
2397
2499
  return privateKey.service;
2398
2500
  case "1password":
2399
2501
  return privateKey.item;
2502
+ case "yubikey":
2503
+ return privateKey.encryptedKeyPath;
2400
2504
  default: {
2401
2505
  const _exhaustiveCheck = privateKey;
2402
2506
  throw new Error(`Unsupported private key type: ${String(_exhaustiveCheck)}`);
@@ -2436,6 +2540,9 @@ async function runList() {
2436
2540
  case "1password":
2437
2541
  keyType = "1password";
2438
2542
  break;
2543
+ case "yubikey":
2544
+ keyType = "yubikey";
2545
+ break;
2439
2546
  }
2440
2547
  log(`${marker} ${theme3.blue(slug)}`);
2441
2548
  log(` Name: ${nameDisplay}`);
@@ -2525,6 +2632,8 @@ async function runCreate() {
2525
2632
  info("Checking available key storage providers...");
2526
2633
  const opAvailable = await OnePasswordKeyProvider.isInstalled();
2527
2634
  const keychainAvailable = MacOSKeychainKeyProvider.isAvailable();
2635
+ const yubikeyInstalled = await YubiKeyProvider.isInstalled();
2636
+ const yubikeyConnected = yubikeyInstalled ? await YubiKeyProvider.isConnected() : false;
2528
2637
  const configDir = getAttestItConfigDir();
2529
2638
  const storageChoices = [
2530
2639
  { name: `File system (${join(configDir, "keys")})`, value: "file" }
@@ -2535,6 +2644,10 @@ async function runCreate() {
2535
2644
  if (opAvailable) {
2536
2645
  storageChoices.push({ name: "1Password", value: "1password" });
2537
2646
  }
2647
+ if (yubikeyInstalled) {
2648
+ const yubikeyLabel = yubikeyConnected ? "YubiKey (encrypted with challenge-response)" : "YubiKey (not connected - insert YubiKey first)";
2649
+ storageChoices.push({ name: yubikeyLabel, value: "yubikey" });
2650
+ }
2538
2651
  const keyStorageType = await select({
2539
2652
  message: "Where should the private key be stored?",
2540
2653
  choices: storageChoices
@@ -2734,6 +2847,75 @@ async function runCreate() {
2734
2847
  keyStorageDescription = `1Password (${selectedVault}/${item})`;
2735
2848
  break;
2736
2849
  }
2850
+ case "yubikey": {
2851
+ if (!await YubiKeyProvider.isConnected()) {
2852
+ error("No YubiKey detected. Please insert your YubiKey and try again.");
2853
+ process.exit(ExitCode.CONFIG_ERROR);
2854
+ }
2855
+ const yubikeys = await YubiKeyProvider.listDevices();
2856
+ if (yubikeys.length === 0) {
2857
+ throw new Error("No YubiKeys detected. Please insert a YubiKey and try again.");
2858
+ }
2859
+ const formatYubiKeyChoice = (yk) => {
2860
+ return `${theme3.blue.bold()(yk.type)} ${theme3.muted(`(Serial: ${yk.serial}, FW: ${yk.firmware})`)}`;
2861
+ };
2862
+ let selectedSerial;
2863
+ if (yubikeys.length === 1 && yubikeys[0]) {
2864
+ selectedSerial = yubikeys[0].serial;
2865
+ info(`Using YubiKey: ${formatYubiKeyChoice(yubikeys[0])}`);
2866
+ } else {
2867
+ selectedSerial = await select({
2868
+ message: "Select YubiKey:",
2869
+ choices: yubikeys.map((yk) => ({
2870
+ name: formatYubiKeyChoice(yk),
2871
+ value: yk.serial
2872
+ }))
2873
+ });
2874
+ }
2875
+ const slot = 2;
2876
+ const isChallengeResponseConfigured = await YubiKeyProvider.isChallengeResponseConfigured(
2877
+ slot,
2878
+ selectedSerial
2879
+ );
2880
+ if (!isChallengeResponseConfigured) {
2881
+ log("");
2882
+ error(`YubiKey slot ${String(slot)} is not configured for HMAC challenge-response.`);
2883
+ log("");
2884
+ log("To configure it, run:");
2885
+ log(theme3.blue(` ykman otp chalresp --generate ${String(slot)}`));
2886
+ log("");
2887
+ log("This will configure slot 2 with a randomly generated secret.");
2888
+ log(theme3.muted("Note: Make sure to back up the secret if needed for recovery."));
2889
+ process.exit(ExitCode.CONFIG_ERROR);
2890
+ }
2891
+ const encryptedKeyName = await input({
2892
+ message: "Encrypted key file name:",
2893
+ default: `${slug}.enc`,
2894
+ validate: (value) => {
2895
+ if (!value || value.trim().length === 0) {
2896
+ return "File name cannot be empty";
2897
+ }
2898
+ return true;
2899
+ }
2900
+ });
2901
+ const keysDir = join(getAttestItConfigDir(), "keys");
2902
+ await mkdir(keysDir, { recursive: true });
2903
+ const encryptedKeyPath = join(keysDir, encryptedKeyName);
2904
+ const result = await YubiKeyProvider.encryptPrivateKey({
2905
+ privateKey: keyPair.privateKey,
2906
+ encryptedKeyPath,
2907
+ slot,
2908
+ serial: selectedSerial
2909
+ });
2910
+ privateKeyRef = {
2911
+ type: "yubikey",
2912
+ encryptedKeyPath: result.encryptedKeyPath,
2913
+ slot,
2914
+ serial: selectedSerial
2915
+ };
2916
+ keyStorageDescription = result.storageDescription;
2917
+ break;
2918
+ }
2737
2919
  default:
2738
2920
  throw new Error(`Unknown key storage type: ${keyStorageType}`);
2739
2921
  }
@@ -2783,6 +2965,7 @@ async function runCreate() {
2783
2965
  log(`To use this identity, run: attest-it identity use ${slug}`);
2784
2966
  log("");
2785
2967
  }
2968
+ await offerCompletionInstall();
2786
2969
  } catch (err) {
2787
2970
  if (err instanceof Error) {
2788
2971
  error(err.message);
@@ -3065,6 +3248,11 @@ function formatKeyLocation(privateKey) {
3065
3248
  }
3066
3249
  return `${theme3.blue.bold()("1Password")}: ${theme3.muted(parts.join("/"))}`;
3067
3250
  }
3251
+ case "yubikey": {
3252
+ const slotInfo = privateKey.slot ? ` (slot ${String(privateKey.slot)})` : "";
3253
+ const serialInfo = privateKey.serial ? ` [${privateKey.serial}]` : "";
3254
+ return `${theme3.blue.bold()("YubiKey")}${serialInfo}${slotInfo}: ${theme3.muted(privateKey.encryptedKeyPath)}`;
3255
+ }
3068
3256
  default:
3069
3257
  return "Unknown storage";
3070
3258
  }
@@ -3738,11 +3926,16 @@ async function runRemove2(slug, options) {
3738
3926
 
3739
3927
  // src/commands/team/index.ts
3740
3928
  var teamCommand = new Command("team").description("Manage team members and authorizations").addCommand(listCommand2).addCommand(addCommand).addCommand(editCommand2).addCommand(removeCommand2);
3741
- var PROGRAM_NAME = "attest-it";
3929
+ var PROGRAM_NAME2 = "attest-it";
3930
+ var PROGRAM_ALIAS2 = "attest";
3931
+ var PROGRAM_NAMES = [PROGRAM_NAME2, PROGRAM_ALIAS2];
3932
+ function isSupportedShell(value) {
3933
+ return value === "bash" || value === "zsh" || value === "fish";
3934
+ }
3742
3935
  async function getCompletions(env) {
3743
3936
  let shell;
3744
3937
  try {
3745
- const detectedShell = tabtab.getShellFromEnv(process.env);
3938
+ const detectedShell = tabtab2.getShellFromEnv(process.env);
3746
3939
  shell = detectedShell === "pwsh" ? "bash" : detectedShell;
3747
3940
  } catch {
3748
3941
  shell = "bash";
@@ -3786,15 +3979,15 @@ async function getCompletions(env) {
3786
3979
  const lastWord = env.last;
3787
3980
  const prevWord = env.prev;
3788
3981
  if (prevWord === "--config" || prevWord === "-c") {
3789
- tabtab.logFiles();
3982
+ tabtab2.logFiles();
3790
3983
  return;
3791
3984
  }
3792
3985
  if (lastWord.startsWith("-")) {
3793
- tabtab.log(globalOptions2, shell, console.log);
3986
+ tabtab2.log(globalOptions2, shell, console.log);
3794
3987
  return;
3795
3988
  }
3796
3989
  const commandIndex = words.findIndex(
3797
- (w) => !w.startsWith("-") && w !== PROGRAM_NAME && w !== "npx"
3990
+ (w) => !w.startsWith("-") && !PROGRAM_NAMES.includes(w) && w !== "npx"
3798
3991
  );
3799
3992
  const currentCommand = commandIndex >= 0 ? words[commandIndex] ?? null : null;
3800
3993
  if (currentCommand === "identity") {
@@ -3805,12 +3998,12 @@ async function getCompletions(env) {
3805
3998
  if (subcommand === "use" || subcommand === "remove") {
3806
3999
  const identities = await getIdentitySlugs();
3807
4000
  if (identities.length > 0) {
3808
- tabtab.log(identities, shell, console.log);
4001
+ tabtab2.log(identities, shell, console.log);
3809
4002
  return;
3810
4003
  }
3811
4004
  }
3812
4005
  if (!subcommand || subcommandIndex < 0) {
3813
- tabtab.log(identitySubcommands, shell, console.log);
4006
+ tabtab2.log(identitySubcommands, shell, console.log);
3814
4007
  return;
3815
4008
  }
3816
4009
  }
@@ -3820,7 +4013,7 @@ async function getCompletions(env) {
3820
4013
  );
3821
4014
  const subcommand = subcommandIndex >= 0 ? words[subcommandIndex] ?? null : null;
3822
4015
  if (!subcommand || subcommandIndex < 0) {
3823
- tabtab.log(teamSubcommands, shell, console.log);
4016
+ tabtab2.log(teamSubcommands, shell, console.log);
3824
4017
  return;
3825
4018
  }
3826
4019
  }
@@ -3830,30 +4023,43 @@ async function getCompletions(env) {
3830
4023
  );
3831
4024
  const subcommand = subcommandIndex >= 0 ? words[subcommandIndex] ?? null : null;
3832
4025
  if (subcommand === "install") {
3833
- tabtab.log(["bash", "zsh", "fish"], shell, console.log);
4026
+ tabtab2.log(["bash", "zsh", "fish"], shell, console.log);
3834
4027
  return;
3835
4028
  }
3836
4029
  if (!subcommand || subcommandIndex < 0) {
3837
- tabtab.log(completionSubcommands, shell, console.log);
4030
+ tabtab2.log(completionSubcommands, shell, console.log);
3838
4031
  return;
3839
4032
  }
3840
4033
  }
3841
4034
  if (currentCommand === "status" || currentCommand === "verify" || currentCommand === "seal") {
3842
4035
  const gates = await getGateNames();
3843
4036
  if (gates.length > 0) {
3844
- tabtab.log(gates, shell, console.log);
4037
+ tabtab2.log(gates, shell, console.log);
3845
4038
  return;
3846
4039
  }
3847
4040
  }
3848
4041
  if (currentCommand === "run") {
3849
4042
  const suites = await getSuiteNames();
3850
4043
  if (suites.length > 0) {
3851
- tabtab.log(suites, shell, console.log);
4044
+ tabtab2.log(suites, shell, console.log);
3852
4045
  return;
3853
4046
  }
3854
4047
  }
3855
- if (!currentCommand) {
3856
- tabtab.log([...commands, ...globalOptions2], shell, console.log);
4048
+ const knownCommands = [
4049
+ "init",
4050
+ "status",
4051
+ "run",
4052
+ "verify",
4053
+ "seal",
4054
+ "keygen",
4055
+ "prune",
4056
+ "identity",
4057
+ "team",
4058
+ "whoami",
4059
+ "completion"
4060
+ ];
4061
+ if (!currentCommand || !knownCommands.includes(currentCommand)) {
4062
+ tabtab2.log([...commands, ...globalOptions2], shell, console.log);
3857
4063
  }
3858
4064
  }
3859
4065
  async function getIdentitySlugs() {
@@ -3885,35 +4091,65 @@ async function getSuiteNames() {
3885
4091
  return [];
3886
4092
  }
3887
4093
  var completionCommand = new Command("completion").description("Shell completion commands");
3888
- completionCommand.command("install [shell]").description("Install shell completion (bash, zsh, or fish)").action(async (shellArg) => {
4094
+ function detectCurrentShell2() {
4095
+ const shellPath = process.env.SHELL ?? "";
4096
+ if (shellPath.endsWith("/bash") || shellPath.endsWith("/bash.exe")) {
4097
+ return "bash";
4098
+ }
4099
+ if (shellPath.endsWith("/zsh") || shellPath.endsWith("/zsh.exe")) {
4100
+ return "zsh";
4101
+ }
4102
+ if (shellPath.endsWith("/fish") || shellPath.endsWith("/fish.exe")) {
4103
+ return "fish";
4104
+ }
4105
+ return null;
4106
+ }
4107
+ function getSourceCommand2(shell) {
4108
+ switch (shell) {
4109
+ case "bash":
4110
+ return "source ~/.bashrc";
4111
+ case "zsh":
4112
+ return "source ~/.zshrc";
4113
+ case "fish":
4114
+ return "source ~/.config/fish/config.fish";
4115
+ }
4116
+ }
4117
+ completionCommand.command("install [shell]").description("Install shell completion (auto-detects shell, or specify bash/zsh/fish)").action(async (shellArg) => {
3889
4118
  try {
3890
4119
  let shell;
3891
4120
  if (shellArg !== void 0) {
3892
- if (tabtab.isShellSupported(shellArg)) {
3893
- shell = shellArg;
3894
- } else {
4121
+ if (!isSupportedShell(shellArg)) {
3895
4122
  error(`Shell "${shellArg}" is not supported. Use bash, zsh, or fish.`);
3896
4123
  process.exit(ExitCode.CONFIG_ERROR);
3897
4124
  }
4125
+ shell = shellArg;
4126
+ } else {
4127
+ const detected = detectCurrentShell2();
4128
+ if (!detected) {
4129
+ error(
4130
+ "Could not detect your shell. Please specify: attest-it completion install <bash|zsh|fish>"
4131
+ );
4132
+ process.exit(ExitCode.CONFIG_ERROR);
4133
+ }
4134
+ shell = detected;
4135
+ info(`Detected shell: ${shell}`);
3898
4136
  }
3899
- await tabtab.install({
3900
- name: PROGRAM_NAME,
3901
- completer: PROGRAM_NAME,
4137
+ await tabtab2.install({
4138
+ name: PROGRAM_NAME2,
4139
+ completer: PROGRAM_NAME2,
4140
+ shell
4141
+ });
4142
+ await tabtab2.install({
4143
+ name: PROGRAM_ALIAS2,
4144
+ completer: PROGRAM_ALIAS2,
3902
4145
  shell
3903
4146
  });
3904
4147
  log("");
3905
- success("Shell completion installed!");
4148
+ success(`Shell completion installed for ${shell}!`);
4149
+ info(`Completions enabled for both "${PROGRAM_NAME2}" and "${PROGRAM_ALIAS2}" commands.`);
3906
4150
  log("");
3907
4151
  info("Restart your shell or run:");
3908
- if (shell === "bash" || !shell) {
3909
- log(" source ~/.bashrc");
3910
- }
3911
- if (shell === "zsh" || !shell) {
3912
- log(" source ~/.zshrc");
3913
- }
3914
- if (shell === "fish" || !shell) {
3915
- log(" source ~/.config/fish/config.fish");
3916
- }
4152
+ log(` ${getSourceCommand2(shell)}`);
3917
4153
  log("");
3918
4154
  } catch (err) {
3919
4155
  error(`Failed to install completion: ${err instanceof Error ? err.message : String(err)}`);
@@ -3922,8 +4158,11 @@ completionCommand.command("install [shell]").description("Install shell completi
3922
4158
  });
3923
4159
  completionCommand.command("uninstall").description("Uninstall shell completion").action(async () => {
3924
4160
  try {
3925
- await tabtab.uninstall({
3926
- name: PROGRAM_NAME
4161
+ await tabtab2.uninstall({
4162
+ name: PROGRAM_NAME2
4163
+ });
4164
+ await tabtab2.uninstall({
4165
+ name: PROGRAM_ALIAS2
3927
4166
  });
3928
4167
  log("");
3929
4168
  success("Shell completion uninstalled!");
@@ -3934,11 +4173,19 @@ completionCommand.command("uninstall").description("Uninstall shell completion")
3934
4173
  }
3935
4174
  });
3936
4175
  completionCommand.command("server", { hidden: true }).description("Completion server (internal)").action(async () => {
3937
- const env = tabtab.parseEnv(process.env);
4176
+ const env = tabtab2.parseEnv(process.env);
3938
4177
  if (env.complete) {
3939
4178
  await getCompletions(env);
3940
4179
  }
3941
4180
  });
4181
+ function createCompletionServerCommand() {
4182
+ return new Command("completion-server").allowUnknownOption().allowExcessArguments(true).action(async () => {
4183
+ const env = tabtab2.parseEnv(process.env);
4184
+ if (env.complete) {
4185
+ await getCompletions(env);
4186
+ }
4187
+ });
4188
+ }
3942
4189
  function hasVersion(data) {
3943
4190
  return typeof data === "object" && data !== null && "version" in data && // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
3944
4191
  typeof data.version === "string";
@@ -3983,6 +4230,7 @@ program.addCommand(identityCommand);
3983
4230
  program.addCommand(teamCommand);
3984
4231
  program.addCommand(whoamiCommand);
3985
4232
  program.addCommand(completionCommand);
4233
+ program.addCommand(createCompletionServerCommand(), { hidden: true });
3986
4234
  function processHomeDirOption() {
3987
4235
  const homeDirIndex = process.argv.indexOf("--home-dir");
3988
4236
  if (homeDirIndex !== -1 && homeDirIndex + 1 < process.argv.length) {
@@ -3999,7 +4247,10 @@ async function run() {
3999
4247
  console.log(getPackageVersion());
4000
4248
  process.exit(0);
4001
4249
  }
4002
- await initTheme();
4250
+ const isCompletionServer = process.argv.includes("completion-server");
4251
+ if (!isCompletionServer) {
4252
+ await initTheme();
4253
+ }
4003
4254
  program.parse();
4004
4255
  const options = program.opts();
4005
4256
  const outputOptions = {};