@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/bin/attest-it.js +288 -37
- package/dist/bin/attest-it.js.map +1 -1
- package/dist/index.cjs +288 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +288 -37
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/bin/attest-it.js
CHANGED
|
@@ -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
|
|
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 =
|
|
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
|
-
|
|
3982
|
+
tabtab2.logFiles();
|
|
3790
3983
|
return;
|
|
3791
3984
|
}
|
|
3792
3985
|
if (lastWord.startsWith("-")) {
|
|
3793
|
-
|
|
3986
|
+
tabtab2.log(globalOptions2, shell, console.log);
|
|
3794
3987
|
return;
|
|
3795
3988
|
}
|
|
3796
3989
|
const commandIndex = words.findIndex(
|
|
3797
|
-
(w) => !w.startsWith("-") && w
|
|
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
|
-
|
|
4001
|
+
tabtab2.log(identities, shell, console.log);
|
|
3809
4002
|
return;
|
|
3810
4003
|
}
|
|
3811
4004
|
}
|
|
3812
4005
|
if (!subcommand || subcommandIndex < 0) {
|
|
3813
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4026
|
+
tabtab2.log(["bash", "zsh", "fish"], shell, console.log);
|
|
3834
4027
|
return;
|
|
3835
4028
|
}
|
|
3836
4029
|
if (!subcommand || subcommandIndex < 0) {
|
|
3837
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4044
|
+
tabtab2.log(suites, shell, console.log);
|
|
3852
4045
|
return;
|
|
3853
4046
|
}
|
|
3854
4047
|
}
|
|
3855
|
-
|
|
3856
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
3900
|
-
name:
|
|
3901
|
-
completer:
|
|
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(
|
|
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
|
-
|
|
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
|
|
3926
|
-
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 =
|
|
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
|
-
|
|
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 = {};
|