@attest-it/cli 0.5.0 → 0.6.1

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)}`);
@@ -1621,7 +1714,7 @@ function KeygenInteractive(props) {
1621
1714
  } else if (value === "1password") {
1622
1715
  setSelectedProvider("1password");
1623
1716
  if (accounts.length === 1 && accounts[0]) {
1624
- setSelectedAccount(accounts[0].email);
1717
+ setSelectedAccount(accounts[0].user_uuid);
1625
1718
  setStep("select-vault");
1626
1719
  } else {
1627
1720
  setStep("select-account");
@@ -1668,8 +1761,12 @@ function KeygenInteractive(props) {
1668
1761
  if (!selectedVault || !itemName) {
1669
1762
  throw new Error("Vault and item name are required for 1Password");
1670
1763
  }
1764
+ const vault = vaults.find((v) => v.id === selectedVault);
1765
+ if (!vault) {
1766
+ throw new Error("Selected vault not found");
1767
+ }
1671
1768
  const providerOptions = {
1672
- vault: selectedVault,
1769
+ vault: vault.name,
1673
1770
  itemName
1674
1771
  };
1675
1772
  if (selectedAccount !== void 0) {
@@ -1686,7 +1783,7 @@ function KeygenInteractive(props) {
1686
1783
  publicKeyPath: result.publicKeyPath,
1687
1784
  privateKeyRef: result.privateKeyRef,
1688
1785
  storageDescription: result.storageDescription,
1689
- vault: selectedVault,
1786
+ vault: vault.name,
1690
1787
  itemName
1691
1788
  };
1692
1789
  if (selectedAccount !== void 0) {
@@ -1752,7 +1849,7 @@ function KeygenInteractive(props) {
1752
1849
  if (step === "select-account") {
1753
1850
  const options = accounts.map((account) => ({
1754
1851
  label: account.email,
1755
- value: account.email
1852
+ value: account.user_uuid
1756
1853
  }));
1757
1854
  return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
1758
1855
  /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Select 1Password account:" }),
@@ -1769,7 +1866,7 @@ function KeygenInteractive(props) {
1769
1866
  }
1770
1867
  const options = vaults.map((vault) => ({
1771
1868
  label: vault.name,
1772
- value: vault.name
1869
+ value: vault.id
1773
1870
  }));
1774
1871
  return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
1775
1872
  /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Select vault for private key storage:" }),
@@ -2408,6 +2505,15 @@ function createKeyProviderFromIdentity2(identity) {
2408
2505
  field: privateKey.field
2409
2506
  }
2410
2507
  });
2508
+ case "yubikey":
2509
+ return core.KeyProviderRegistry.create({
2510
+ type: "yubikey",
2511
+ options: {
2512
+ encryptedKeyPath: privateKey.encryptedKeyPath,
2513
+ slot: privateKey.slot,
2514
+ serial: privateKey.serial
2515
+ }
2516
+ });
2411
2517
  default: {
2412
2518
  const _exhaustiveCheck = privateKey;
2413
2519
  throw new Error(`Unsupported private key type: ${String(_exhaustiveCheck)}`);
@@ -2423,6 +2529,8 @@ function getKeyRefFromIdentity2(identity) {
2423
2529
  return privateKey.service;
2424
2530
  case "1password":
2425
2531
  return privateKey.item;
2532
+ case "yubikey":
2533
+ return privateKey.encryptedKeyPath;
2426
2534
  default: {
2427
2535
  const _exhaustiveCheck = privateKey;
2428
2536
  throw new Error(`Unsupported private key type: ${String(_exhaustiveCheck)}`);
@@ -2462,6 +2570,9 @@ async function runList() {
2462
2570
  case "1password":
2463
2571
  keyType = "1password";
2464
2572
  break;
2573
+ case "yubikey":
2574
+ keyType = "yubikey";
2575
+ break;
2465
2576
  }
2466
2577
  log(`${marker} ${theme3.blue(slug)}`);
2467
2578
  log(` Name: ${nameDisplay}`);
@@ -2551,6 +2662,8 @@ async function runCreate() {
2551
2662
  info("Checking available key storage providers...");
2552
2663
  const opAvailable = await core.OnePasswordKeyProvider.isInstalled();
2553
2664
  const keychainAvailable = core.MacOSKeychainKeyProvider.isAvailable();
2665
+ const yubikeyInstalled = await core.YubiKeyProvider.isInstalled();
2666
+ const yubikeyConnected = yubikeyInstalled ? await core.YubiKeyProvider.isConnected() : false;
2554
2667
  const configDir = core.getAttestItConfigDir();
2555
2668
  const storageChoices = [
2556
2669
  { name: `File system (${path.join(configDir, "keys")})`, value: "file" }
@@ -2561,6 +2674,10 @@ async function runCreate() {
2561
2674
  if (opAvailable) {
2562
2675
  storageChoices.push({ name: "1Password", value: "1password" });
2563
2676
  }
2677
+ if (yubikeyInstalled) {
2678
+ const yubikeyLabel = yubikeyConnected ? "YubiKey (encrypted with challenge-response)" : "YubiKey (not connected - insert YubiKey first)";
2679
+ storageChoices.push({ name: yubikeyLabel, value: "yubikey" });
2680
+ }
2564
2681
  const keyStorageType = await prompts.select({
2565
2682
  message: "Where should the private key be stored?",
2566
2683
  choices: storageChoices
@@ -2760,6 +2877,75 @@ async function runCreate() {
2760
2877
  keyStorageDescription = `1Password (${selectedVault}/${item})`;
2761
2878
  break;
2762
2879
  }
2880
+ case "yubikey": {
2881
+ if (!await core.YubiKeyProvider.isConnected()) {
2882
+ error("No YubiKey detected. Please insert your YubiKey and try again.");
2883
+ process.exit(ExitCode.CONFIG_ERROR);
2884
+ }
2885
+ const yubikeys = await core.YubiKeyProvider.listDevices();
2886
+ if (yubikeys.length === 0) {
2887
+ throw new Error("No YubiKeys detected. Please insert a YubiKey and try again.");
2888
+ }
2889
+ const formatYubiKeyChoice = (yk) => {
2890
+ return `${theme3.blue.bold()(yk.type)} ${theme3.muted(`(Serial: ${yk.serial}, FW: ${yk.firmware})`)}`;
2891
+ };
2892
+ let selectedSerial;
2893
+ if (yubikeys.length === 1 && yubikeys[0]) {
2894
+ selectedSerial = yubikeys[0].serial;
2895
+ info(`Using YubiKey: ${formatYubiKeyChoice(yubikeys[0])}`);
2896
+ } else {
2897
+ selectedSerial = await prompts.select({
2898
+ message: "Select YubiKey:",
2899
+ choices: yubikeys.map((yk) => ({
2900
+ name: formatYubiKeyChoice(yk),
2901
+ value: yk.serial
2902
+ }))
2903
+ });
2904
+ }
2905
+ const slot = 2;
2906
+ const isChallengeResponseConfigured = await core.YubiKeyProvider.isChallengeResponseConfigured(
2907
+ slot,
2908
+ selectedSerial
2909
+ );
2910
+ if (!isChallengeResponseConfigured) {
2911
+ log("");
2912
+ error(`YubiKey slot ${String(slot)} is not configured for HMAC challenge-response.`);
2913
+ log("");
2914
+ log("To configure it, run:");
2915
+ log(theme3.blue(` ykman otp chalresp --generate ${String(slot)}`));
2916
+ log("");
2917
+ log("This will configure slot 2 with a randomly generated secret.");
2918
+ log(theme3.muted("Note: Make sure to back up the secret if needed for recovery."));
2919
+ process.exit(ExitCode.CONFIG_ERROR);
2920
+ }
2921
+ const encryptedKeyName = await prompts.input({
2922
+ message: "Encrypted key file name:",
2923
+ default: `${slug}.enc`,
2924
+ validate: (value) => {
2925
+ if (!value || value.trim().length === 0) {
2926
+ return "File name cannot be empty";
2927
+ }
2928
+ return true;
2929
+ }
2930
+ });
2931
+ const keysDir = path.join(core.getAttestItConfigDir(), "keys");
2932
+ await promises.mkdir(keysDir, { recursive: true });
2933
+ const encryptedKeyPath = path.join(keysDir, encryptedKeyName);
2934
+ const result = await core.YubiKeyProvider.encryptPrivateKey({
2935
+ privateKey: keyPair.privateKey,
2936
+ encryptedKeyPath,
2937
+ slot,
2938
+ serial: selectedSerial
2939
+ });
2940
+ privateKeyRef = {
2941
+ type: "yubikey",
2942
+ encryptedKeyPath: result.encryptedKeyPath,
2943
+ slot,
2944
+ serial: selectedSerial
2945
+ };
2946
+ keyStorageDescription = result.storageDescription;
2947
+ break;
2948
+ }
2763
2949
  default:
2764
2950
  throw new Error(`Unknown key storage type: ${keyStorageType}`);
2765
2951
  }
@@ -2809,6 +2995,7 @@ async function runCreate() {
2809
2995
  log(`To use this identity, run: attest-it identity use ${slug}`);
2810
2996
  log("");
2811
2997
  }
2998
+ await offerCompletionInstall();
2812
2999
  } catch (err) {
2813
3000
  if (err instanceof Error) {
2814
3001
  error(err.message);
@@ -3091,6 +3278,11 @@ function formatKeyLocation(privateKey) {
3091
3278
  }
3092
3279
  return `${theme3.blue.bold()("1Password")}: ${theme3.muted(parts.join("/"))}`;
3093
3280
  }
3281
+ case "yubikey": {
3282
+ const slotInfo = privateKey.slot ? ` (slot ${String(privateKey.slot)})` : "";
3283
+ const serialInfo = privateKey.serial ? ` [${privateKey.serial}]` : "";
3284
+ return `${theme3.blue.bold()("YubiKey")}${serialInfo}${slotInfo}: ${theme3.muted(privateKey.encryptedKeyPath)}`;
3285
+ }
3094
3286
  default:
3095
3287
  return "Unknown storage";
3096
3288
  }
@@ -3764,11 +3956,16 @@ async function runRemove2(slug, options) {
3764
3956
 
3765
3957
  // src/commands/team/index.ts
3766
3958
  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";
3959
+ var PROGRAM_NAME2 = "attest-it";
3960
+ var PROGRAM_ALIAS2 = "attest";
3961
+ var PROGRAM_NAMES = [PROGRAM_NAME2, PROGRAM_ALIAS2];
3962
+ function isSupportedShell(value) {
3963
+ return value === "bash" || value === "zsh" || value === "fish";
3964
+ }
3768
3965
  async function getCompletions(env) {
3769
3966
  let shell;
3770
3967
  try {
3771
- const detectedShell = tabtab__default.default.getShellFromEnv(process.env);
3968
+ const detectedShell = tabtab2__default.default.getShellFromEnv(process.env);
3772
3969
  shell = detectedShell === "pwsh" ? "bash" : detectedShell;
3773
3970
  } catch {
3774
3971
  shell = "bash";
@@ -3812,15 +4009,15 @@ async function getCompletions(env) {
3812
4009
  const lastWord = env.last;
3813
4010
  const prevWord = env.prev;
3814
4011
  if (prevWord === "--config" || prevWord === "-c") {
3815
- tabtab__default.default.logFiles();
4012
+ tabtab2__default.default.logFiles();
3816
4013
  return;
3817
4014
  }
3818
4015
  if (lastWord.startsWith("-")) {
3819
- tabtab__default.default.log(globalOptions2, shell, console.log);
4016
+ tabtab2__default.default.log(globalOptions2, shell, console.log);
3820
4017
  return;
3821
4018
  }
3822
4019
  const commandIndex = words.findIndex(
3823
- (w) => !w.startsWith("-") && w !== PROGRAM_NAME && w !== "npx"
4020
+ (w) => !w.startsWith("-") && !PROGRAM_NAMES.includes(w) && w !== "npx"
3824
4021
  );
3825
4022
  const currentCommand = commandIndex >= 0 ? words[commandIndex] ?? null : null;
3826
4023
  if (currentCommand === "identity") {
@@ -3831,12 +4028,12 @@ async function getCompletions(env) {
3831
4028
  if (subcommand === "use" || subcommand === "remove") {
3832
4029
  const identities = await getIdentitySlugs();
3833
4030
  if (identities.length > 0) {
3834
- tabtab__default.default.log(identities, shell, console.log);
4031
+ tabtab2__default.default.log(identities, shell, console.log);
3835
4032
  return;
3836
4033
  }
3837
4034
  }
3838
4035
  if (!subcommand || subcommandIndex < 0) {
3839
- tabtab__default.default.log(identitySubcommands, shell, console.log);
4036
+ tabtab2__default.default.log(identitySubcommands, shell, console.log);
3840
4037
  return;
3841
4038
  }
3842
4039
  }
@@ -3846,7 +4043,7 @@ async function getCompletions(env) {
3846
4043
  );
3847
4044
  const subcommand = subcommandIndex >= 0 ? words[subcommandIndex] ?? null : null;
3848
4045
  if (!subcommand || subcommandIndex < 0) {
3849
- tabtab__default.default.log(teamSubcommands, shell, console.log);
4046
+ tabtab2__default.default.log(teamSubcommands, shell, console.log);
3850
4047
  return;
3851
4048
  }
3852
4049
  }
@@ -3856,30 +4053,43 @@ async function getCompletions(env) {
3856
4053
  );
3857
4054
  const subcommand = subcommandIndex >= 0 ? words[subcommandIndex] ?? null : null;
3858
4055
  if (subcommand === "install") {
3859
- tabtab__default.default.log(["bash", "zsh", "fish"], shell, console.log);
4056
+ tabtab2__default.default.log(["bash", "zsh", "fish"], shell, console.log);
3860
4057
  return;
3861
4058
  }
3862
4059
  if (!subcommand || subcommandIndex < 0) {
3863
- tabtab__default.default.log(completionSubcommands, shell, console.log);
4060
+ tabtab2__default.default.log(completionSubcommands, shell, console.log);
3864
4061
  return;
3865
4062
  }
3866
4063
  }
3867
4064
  if (currentCommand === "status" || currentCommand === "verify" || currentCommand === "seal") {
3868
4065
  const gates = await getGateNames();
3869
4066
  if (gates.length > 0) {
3870
- tabtab__default.default.log(gates, shell, console.log);
4067
+ tabtab2__default.default.log(gates, shell, console.log);
3871
4068
  return;
3872
4069
  }
3873
4070
  }
3874
4071
  if (currentCommand === "run") {
3875
4072
  const suites = await getSuiteNames();
3876
4073
  if (suites.length > 0) {
3877
- tabtab__default.default.log(suites, shell, console.log);
4074
+ tabtab2__default.default.log(suites, shell, console.log);
3878
4075
  return;
3879
4076
  }
3880
4077
  }
3881
- if (!currentCommand) {
3882
- tabtab__default.default.log([...commands, ...globalOptions2], shell, console.log);
4078
+ const knownCommands = [
4079
+ "init",
4080
+ "status",
4081
+ "run",
4082
+ "verify",
4083
+ "seal",
4084
+ "keygen",
4085
+ "prune",
4086
+ "identity",
4087
+ "team",
4088
+ "whoami",
4089
+ "completion"
4090
+ ];
4091
+ if (!currentCommand || !knownCommands.includes(currentCommand)) {
4092
+ tabtab2__default.default.log([...commands, ...globalOptions2], shell, console.log);
3883
4093
  }
3884
4094
  }
3885
4095
  async function getIdentitySlugs() {
@@ -3911,35 +4121,65 @@ async function getSuiteNames() {
3911
4121
  return [];
3912
4122
  }
3913
4123
  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) => {
4124
+ function detectCurrentShell2() {
4125
+ const shellPath = process.env.SHELL ?? "";
4126
+ if (shellPath.endsWith("/bash") || shellPath.endsWith("/bash.exe")) {
4127
+ return "bash";
4128
+ }
4129
+ if (shellPath.endsWith("/zsh") || shellPath.endsWith("/zsh.exe")) {
4130
+ return "zsh";
4131
+ }
4132
+ if (shellPath.endsWith("/fish") || shellPath.endsWith("/fish.exe")) {
4133
+ return "fish";
4134
+ }
4135
+ return null;
4136
+ }
4137
+ function getSourceCommand2(shell) {
4138
+ switch (shell) {
4139
+ case "bash":
4140
+ return "source ~/.bashrc";
4141
+ case "zsh":
4142
+ return "source ~/.zshrc";
4143
+ case "fish":
4144
+ return "source ~/.config/fish/config.fish";
4145
+ }
4146
+ }
4147
+ completionCommand.command("install [shell]").description("Install shell completion (auto-detects shell, or specify bash/zsh/fish)").action(async (shellArg) => {
3915
4148
  try {
3916
4149
  let shell;
3917
4150
  if (shellArg !== void 0) {
3918
- if (tabtab__default.default.isShellSupported(shellArg)) {
3919
- shell = shellArg;
3920
- } else {
4151
+ if (!isSupportedShell(shellArg)) {
3921
4152
  error(`Shell "${shellArg}" is not supported. Use bash, zsh, or fish.`);
3922
4153
  process.exit(ExitCode.CONFIG_ERROR);
3923
4154
  }
4155
+ shell = shellArg;
4156
+ } else {
4157
+ const detected = detectCurrentShell2();
4158
+ if (!detected) {
4159
+ error(
4160
+ "Could not detect your shell. Please specify: attest-it completion install <bash|zsh|fish>"
4161
+ );
4162
+ process.exit(ExitCode.CONFIG_ERROR);
4163
+ }
4164
+ shell = detected;
4165
+ info(`Detected shell: ${shell}`);
3924
4166
  }
3925
- await tabtab__default.default.install({
3926
- name: PROGRAM_NAME,
3927
- completer: PROGRAM_NAME,
4167
+ await tabtab2__default.default.install({
4168
+ name: PROGRAM_NAME2,
4169
+ completer: PROGRAM_NAME2,
4170
+ shell
4171
+ });
4172
+ await tabtab2__default.default.install({
4173
+ name: PROGRAM_ALIAS2,
4174
+ completer: PROGRAM_ALIAS2,
3928
4175
  shell
3929
4176
  });
3930
4177
  log("");
3931
- success("Shell completion installed!");
4178
+ success(`Shell completion installed for ${shell}!`);
4179
+ info(`Completions enabled for both "${PROGRAM_NAME2}" and "${PROGRAM_ALIAS2}" commands.`);
3932
4180
  log("");
3933
4181
  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
- }
4182
+ log(` ${getSourceCommand2(shell)}`);
3943
4183
  log("");
3944
4184
  } catch (err) {
3945
4185
  error(`Failed to install completion: ${err instanceof Error ? err.message : String(err)}`);
@@ -3948,8 +4188,11 @@ completionCommand.command("install [shell]").description("Install shell completi
3948
4188
  });
3949
4189
  completionCommand.command("uninstall").description("Uninstall shell completion").action(async () => {
3950
4190
  try {
3951
- await tabtab__default.default.uninstall({
3952
- name: PROGRAM_NAME
4191
+ await tabtab2__default.default.uninstall({
4192
+ name: PROGRAM_NAME2
4193
+ });
4194
+ await tabtab2__default.default.uninstall({
4195
+ name: PROGRAM_ALIAS2
3953
4196
  });
3954
4197
  log("");
3955
4198
  success("Shell completion uninstalled!");
@@ -3960,11 +4203,19 @@ completionCommand.command("uninstall").description("Uninstall shell completion")
3960
4203
  }
3961
4204
  });
3962
4205
  completionCommand.command("server", { hidden: true }).description("Completion server (internal)").action(async () => {
3963
- const env = tabtab__default.default.parseEnv(process.env);
4206
+ const env = tabtab2__default.default.parseEnv(process.env);
3964
4207
  if (env.complete) {
3965
4208
  await getCompletions(env);
3966
4209
  }
3967
4210
  });
4211
+ function createCompletionServerCommand() {
4212
+ return new commander.Command("completion-server").allowUnknownOption().allowExcessArguments(true).action(async () => {
4213
+ const env = tabtab2__default.default.parseEnv(process.env);
4214
+ if (env.complete) {
4215
+ await getCompletions(env);
4216
+ }
4217
+ });
4218
+ }
3968
4219
  function hasVersion(data) {
3969
4220
  return typeof data === "object" && data !== null && "version" in data && // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
3970
4221
  typeof data.version === "string";
@@ -4009,6 +4260,7 @@ program.addCommand(identityCommand);
4009
4260
  program.addCommand(teamCommand);
4010
4261
  program.addCommand(whoamiCommand);
4011
4262
  program.addCommand(completionCommand);
4263
+ program.addCommand(createCompletionServerCommand(), { hidden: true });
4012
4264
  function processHomeDirOption() {
4013
4265
  const homeDirIndex = process.argv.indexOf("--home-dir");
4014
4266
  if (homeDirIndex !== -1 && homeDirIndex + 1 < process.argv.length) {
@@ -4025,7 +4277,10 @@ async function run() {
4025
4277
  console.log(getPackageVersion());
4026
4278
  process.exit(0);
4027
4279
  }
4028
- await initTheme();
4280
+ const isCompletionServer = process.argv.includes("completion-server");
4281
+ if (!isCompletionServer) {
4282
+ await initTheme();
4283
+ }
4029
4284
  program.parse();
4030
4285
  const options = program.opts();
4031
4286
  const outputOptions = {};