@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/bin/attest-it.js +297 -42
- package/dist/bin/attest-it.js.map +1 -1
- package/dist/index.cjs +297 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +297 -42
- 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)}`);
|
|
@@ -1595,7 +1688,7 @@ function KeygenInteractive(props) {
|
|
|
1595
1688
|
} else if (value === "1password") {
|
|
1596
1689
|
setSelectedProvider("1password");
|
|
1597
1690
|
if (accounts.length === 1 && accounts[0]) {
|
|
1598
|
-
setSelectedAccount(accounts[0].
|
|
1691
|
+
setSelectedAccount(accounts[0].user_uuid);
|
|
1599
1692
|
setStep("select-vault");
|
|
1600
1693
|
} else {
|
|
1601
1694
|
setStep("select-account");
|
|
@@ -1642,8 +1735,12 @@ function KeygenInteractive(props) {
|
|
|
1642
1735
|
if (!selectedVault || !itemName) {
|
|
1643
1736
|
throw new Error("Vault and item name are required for 1Password");
|
|
1644
1737
|
}
|
|
1738
|
+
const vault = vaults.find((v) => v.id === selectedVault);
|
|
1739
|
+
if (!vault) {
|
|
1740
|
+
throw new Error("Selected vault not found");
|
|
1741
|
+
}
|
|
1645
1742
|
const providerOptions = {
|
|
1646
|
-
vault:
|
|
1743
|
+
vault: vault.name,
|
|
1647
1744
|
itemName
|
|
1648
1745
|
};
|
|
1649
1746
|
if (selectedAccount !== void 0) {
|
|
@@ -1660,7 +1757,7 @@ function KeygenInteractive(props) {
|
|
|
1660
1757
|
publicKeyPath: result.publicKeyPath,
|
|
1661
1758
|
privateKeyRef: result.privateKeyRef,
|
|
1662
1759
|
storageDescription: result.storageDescription,
|
|
1663
|
-
vault:
|
|
1760
|
+
vault: vault.name,
|
|
1664
1761
|
itemName
|
|
1665
1762
|
};
|
|
1666
1763
|
if (selectedAccount !== void 0) {
|
|
@@ -1726,7 +1823,7 @@ function KeygenInteractive(props) {
|
|
|
1726
1823
|
if (step === "select-account") {
|
|
1727
1824
|
const options = accounts.map((account) => ({
|
|
1728
1825
|
label: account.email,
|
|
1729
|
-
value: account.
|
|
1826
|
+
value: account.user_uuid
|
|
1730
1827
|
}));
|
|
1731
1828
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
1732
1829
|
/* @__PURE__ */ jsx(Text, { bold: true, children: "Select 1Password account:" }),
|
|
@@ -1743,7 +1840,7 @@ function KeygenInteractive(props) {
|
|
|
1743
1840
|
}
|
|
1744
1841
|
const options = vaults.map((vault) => ({
|
|
1745
1842
|
label: vault.name,
|
|
1746
|
-
value: vault.
|
|
1843
|
+
value: vault.id
|
|
1747
1844
|
}));
|
|
1748
1845
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
1749
1846
|
/* @__PURE__ */ jsx(Text, { bold: true, children: "Select vault for private key storage:" }),
|
|
@@ -2382,6 +2479,15 @@ function createKeyProviderFromIdentity2(identity) {
|
|
|
2382
2479
|
field: privateKey.field
|
|
2383
2480
|
}
|
|
2384
2481
|
});
|
|
2482
|
+
case "yubikey":
|
|
2483
|
+
return KeyProviderRegistry.create({
|
|
2484
|
+
type: "yubikey",
|
|
2485
|
+
options: {
|
|
2486
|
+
encryptedKeyPath: privateKey.encryptedKeyPath,
|
|
2487
|
+
slot: privateKey.slot,
|
|
2488
|
+
serial: privateKey.serial
|
|
2489
|
+
}
|
|
2490
|
+
});
|
|
2385
2491
|
default: {
|
|
2386
2492
|
const _exhaustiveCheck = privateKey;
|
|
2387
2493
|
throw new Error(`Unsupported private key type: ${String(_exhaustiveCheck)}`);
|
|
@@ -2397,6 +2503,8 @@ function getKeyRefFromIdentity2(identity) {
|
|
|
2397
2503
|
return privateKey.service;
|
|
2398
2504
|
case "1password":
|
|
2399
2505
|
return privateKey.item;
|
|
2506
|
+
case "yubikey":
|
|
2507
|
+
return privateKey.encryptedKeyPath;
|
|
2400
2508
|
default: {
|
|
2401
2509
|
const _exhaustiveCheck = privateKey;
|
|
2402
2510
|
throw new Error(`Unsupported private key type: ${String(_exhaustiveCheck)}`);
|
|
@@ -2436,6 +2544,9 @@ async function runList() {
|
|
|
2436
2544
|
case "1password":
|
|
2437
2545
|
keyType = "1password";
|
|
2438
2546
|
break;
|
|
2547
|
+
case "yubikey":
|
|
2548
|
+
keyType = "yubikey";
|
|
2549
|
+
break;
|
|
2439
2550
|
}
|
|
2440
2551
|
log(`${marker} ${theme3.blue(slug)}`);
|
|
2441
2552
|
log(` Name: ${nameDisplay}`);
|
|
@@ -2525,6 +2636,8 @@ async function runCreate() {
|
|
|
2525
2636
|
info("Checking available key storage providers...");
|
|
2526
2637
|
const opAvailable = await OnePasswordKeyProvider.isInstalled();
|
|
2527
2638
|
const keychainAvailable = MacOSKeychainKeyProvider.isAvailable();
|
|
2639
|
+
const yubikeyInstalled = await YubiKeyProvider.isInstalled();
|
|
2640
|
+
const yubikeyConnected = yubikeyInstalled ? await YubiKeyProvider.isConnected() : false;
|
|
2528
2641
|
const configDir = getAttestItConfigDir();
|
|
2529
2642
|
const storageChoices = [
|
|
2530
2643
|
{ name: `File system (${join(configDir, "keys")})`, value: "file" }
|
|
@@ -2535,6 +2648,10 @@ async function runCreate() {
|
|
|
2535
2648
|
if (opAvailable) {
|
|
2536
2649
|
storageChoices.push({ name: "1Password", value: "1password" });
|
|
2537
2650
|
}
|
|
2651
|
+
if (yubikeyInstalled) {
|
|
2652
|
+
const yubikeyLabel = yubikeyConnected ? "YubiKey (encrypted with challenge-response)" : "YubiKey (not connected - insert YubiKey first)";
|
|
2653
|
+
storageChoices.push({ name: yubikeyLabel, value: "yubikey" });
|
|
2654
|
+
}
|
|
2538
2655
|
const keyStorageType = await select({
|
|
2539
2656
|
message: "Where should the private key be stored?",
|
|
2540
2657
|
choices: storageChoices
|
|
@@ -2734,6 +2851,75 @@ async function runCreate() {
|
|
|
2734
2851
|
keyStorageDescription = `1Password (${selectedVault}/${item})`;
|
|
2735
2852
|
break;
|
|
2736
2853
|
}
|
|
2854
|
+
case "yubikey": {
|
|
2855
|
+
if (!await YubiKeyProvider.isConnected()) {
|
|
2856
|
+
error("No YubiKey detected. Please insert your YubiKey and try again.");
|
|
2857
|
+
process.exit(ExitCode.CONFIG_ERROR);
|
|
2858
|
+
}
|
|
2859
|
+
const yubikeys = await YubiKeyProvider.listDevices();
|
|
2860
|
+
if (yubikeys.length === 0) {
|
|
2861
|
+
throw new Error("No YubiKeys detected. Please insert a YubiKey and try again.");
|
|
2862
|
+
}
|
|
2863
|
+
const formatYubiKeyChoice = (yk) => {
|
|
2864
|
+
return `${theme3.blue.bold()(yk.type)} ${theme3.muted(`(Serial: ${yk.serial}, FW: ${yk.firmware})`)}`;
|
|
2865
|
+
};
|
|
2866
|
+
let selectedSerial;
|
|
2867
|
+
if (yubikeys.length === 1 && yubikeys[0]) {
|
|
2868
|
+
selectedSerial = yubikeys[0].serial;
|
|
2869
|
+
info(`Using YubiKey: ${formatYubiKeyChoice(yubikeys[0])}`);
|
|
2870
|
+
} else {
|
|
2871
|
+
selectedSerial = await select({
|
|
2872
|
+
message: "Select YubiKey:",
|
|
2873
|
+
choices: yubikeys.map((yk) => ({
|
|
2874
|
+
name: formatYubiKeyChoice(yk),
|
|
2875
|
+
value: yk.serial
|
|
2876
|
+
}))
|
|
2877
|
+
});
|
|
2878
|
+
}
|
|
2879
|
+
const slot = 2;
|
|
2880
|
+
const isChallengeResponseConfigured = await YubiKeyProvider.isChallengeResponseConfigured(
|
|
2881
|
+
slot,
|
|
2882
|
+
selectedSerial
|
|
2883
|
+
);
|
|
2884
|
+
if (!isChallengeResponseConfigured) {
|
|
2885
|
+
log("");
|
|
2886
|
+
error(`YubiKey slot ${String(slot)} is not configured for HMAC challenge-response.`);
|
|
2887
|
+
log("");
|
|
2888
|
+
log("To configure it, run:");
|
|
2889
|
+
log(theme3.blue(` ykman otp chalresp --generate ${String(slot)}`));
|
|
2890
|
+
log("");
|
|
2891
|
+
log("This will configure slot 2 with a randomly generated secret.");
|
|
2892
|
+
log(theme3.muted("Note: Make sure to back up the secret if needed for recovery."));
|
|
2893
|
+
process.exit(ExitCode.CONFIG_ERROR);
|
|
2894
|
+
}
|
|
2895
|
+
const encryptedKeyName = await input({
|
|
2896
|
+
message: "Encrypted key file name:",
|
|
2897
|
+
default: `${slug}.enc`,
|
|
2898
|
+
validate: (value) => {
|
|
2899
|
+
if (!value || value.trim().length === 0) {
|
|
2900
|
+
return "File name cannot be empty";
|
|
2901
|
+
}
|
|
2902
|
+
return true;
|
|
2903
|
+
}
|
|
2904
|
+
});
|
|
2905
|
+
const keysDir = join(getAttestItConfigDir(), "keys");
|
|
2906
|
+
await mkdir(keysDir, { recursive: true });
|
|
2907
|
+
const encryptedKeyPath = join(keysDir, encryptedKeyName);
|
|
2908
|
+
const result = await YubiKeyProvider.encryptPrivateKey({
|
|
2909
|
+
privateKey: keyPair.privateKey,
|
|
2910
|
+
encryptedKeyPath,
|
|
2911
|
+
slot,
|
|
2912
|
+
serial: selectedSerial
|
|
2913
|
+
});
|
|
2914
|
+
privateKeyRef = {
|
|
2915
|
+
type: "yubikey",
|
|
2916
|
+
encryptedKeyPath: result.encryptedKeyPath,
|
|
2917
|
+
slot,
|
|
2918
|
+
serial: selectedSerial
|
|
2919
|
+
};
|
|
2920
|
+
keyStorageDescription = result.storageDescription;
|
|
2921
|
+
break;
|
|
2922
|
+
}
|
|
2737
2923
|
default:
|
|
2738
2924
|
throw new Error(`Unknown key storage type: ${keyStorageType}`);
|
|
2739
2925
|
}
|
|
@@ -2783,6 +2969,7 @@ async function runCreate() {
|
|
|
2783
2969
|
log(`To use this identity, run: attest-it identity use ${slug}`);
|
|
2784
2970
|
log("");
|
|
2785
2971
|
}
|
|
2972
|
+
await offerCompletionInstall();
|
|
2786
2973
|
} catch (err) {
|
|
2787
2974
|
if (err instanceof Error) {
|
|
2788
2975
|
error(err.message);
|
|
@@ -3065,6 +3252,11 @@ function formatKeyLocation(privateKey) {
|
|
|
3065
3252
|
}
|
|
3066
3253
|
return `${theme3.blue.bold()("1Password")}: ${theme3.muted(parts.join("/"))}`;
|
|
3067
3254
|
}
|
|
3255
|
+
case "yubikey": {
|
|
3256
|
+
const slotInfo = privateKey.slot ? ` (slot ${String(privateKey.slot)})` : "";
|
|
3257
|
+
const serialInfo = privateKey.serial ? ` [${privateKey.serial}]` : "";
|
|
3258
|
+
return `${theme3.blue.bold()("YubiKey")}${serialInfo}${slotInfo}: ${theme3.muted(privateKey.encryptedKeyPath)}`;
|
|
3259
|
+
}
|
|
3068
3260
|
default:
|
|
3069
3261
|
return "Unknown storage";
|
|
3070
3262
|
}
|
|
@@ -3738,11 +3930,16 @@ async function runRemove2(slug, options) {
|
|
|
3738
3930
|
|
|
3739
3931
|
// src/commands/team/index.ts
|
|
3740
3932
|
var teamCommand = new Command("team").description("Manage team members and authorizations").addCommand(listCommand2).addCommand(addCommand).addCommand(editCommand2).addCommand(removeCommand2);
|
|
3741
|
-
var
|
|
3933
|
+
var PROGRAM_NAME2 = "attest-it";
|
|
3934
|
+
var PROGRAM_ALIAS2 = "attest";
|
|
3935
|
+
var PROGRAM_NAMES = [PROGRAM_NAME2, PROGRAM_ALIAS2];
|
|
3936
|
+
function isSupportedShell(value) {
|
|
3937
|
+
return value === "bash" || value === "zsh" || value === "fish";
|
|
3938
|
+
}
|
|
3742
3939
|
async function getCompletions(env) {
|
|
3743
3940
|
let shell;
|
|
3744
3941
|
try {
|
|
3745
|
-
const detectedShell =
|
|
3942
|
+
const detectedShell = tabtab2.getShellFromEnv(process.env);
|
|
3746
3943
|
shell = detectedShell === "pwsh" ? "bash" : detectedShell;
|
|
3747
3944
|
} catch {
|
|
3748
3945
|
shell = "bash";
|
|
@@ -3786,15 +3983,15 @@ async function getCompletions(env) {
|
|
|
3786
3983
|
const lastWord = env.last;
|
|
3787
3984
|
const prevWord = env.prev;
|
|
3788
3985
|
if (prevWord === "--config" || prevWord === "-c") {
|
|
3789
|
-
|
|
3986
|
+
tabtab2.logFiles();
|
|
3790
3987
|
return;
|
|
3791
3988
|
}
|
|
3792
3989
|
if (lastWord.startsWith("-")) {
|
|
3793
|
-
|
|
3990
|
+
tabtab2.log(globalOptions2, shell, console.log);
|
|
3794
3991
|
return;
|
|
3795
3992
|
}
|
|
3796
3993
|
const commandIndex = words.findIndex(
|
|
3797
|
-
(w) => !w.startsWith("-") && w
|
|
3994
|
+
(w) => !w.startsWith("-") && !PROGRAM_NAMES.includes(w) && w !== "npx"
|
|
3798
3995
|
);
|
|
3799
3996
|
const currentCommand = commandIndex >= 0 ? words[commandIndex] ?? null : null;
|
|
3800
3997
|
if (currentCommand === "identity") {
|
|
@@ -3805,12 +4002,12 @@ async function getCompletions(env) {
|
|
|
3805
4002
|
if (subcommand === "use" || subcommand === "remove") {
|
|
3806
4003
|
const identities = await getIdentitySlugs();
|
|
3807
4004
|
if (identities.length > 0) {
|
|
3808
|
-
|
|
4005
|
+
tabtab2.log(identities, shell, console.log);
|
|
3809
4006
|
return;
|
|
3810
4007
|
}
|
|
3811
4008
|
}
|
|
3812
4009
|
if (!subcommand || subcommandIndex < 0) {
|
|
3813
|
-
|
|
4010
|
+
tabtab2.log(identitySubcommands, shell, console.log);
|
|
3814
4011
|
return;
|
|
3815
4012
|
}
|
|
3816
4013
|
}
|
|
@@ -3820,7 +4017,7 @@ async function getCompletions(env) {
|
|
|
3820
4017
|
);
|
|
3821
4018
|
const subcommand = subcommandIndex >= 0 ? words[subcommandIndex] ?? null : null;
|
|
3822
4019
|
if (!subcommand || subcommandIndex < 0) {
|
|
3823
|
-
|
|
4020
|
+
tabtab2.log(teamSubcommands, shell, console.log);
|
|
3824
4021
|
return;
|
|
3825
4022
|
}
|
|
3826
4023
|
}
|
|
@@ -3830,30 +4027,43 @@ async function getCompletions(env) {
|
|
|
3830
4027
|
);
|
|
3831
4028
|
const subcommand = subcommandIndex >= 0 ? words[subcommandIndex] ?? null : null;
|
|
3832
4029
|
if (subcommand === "install") {
|
|
3833
|
-
|
|
4030
|
+
tabtab2.log(["bash", "zsh", "fish"], shell, console.log);
|
|
3834
4031
|
return;
|
|
3835
4032
|
}
|
|
3836
4033
|
if (!subcommand || subcommandIndex < 0) {
|
|
3837
|
-
|
|
4034
|
+
tabtab2.log(completionSubcommands, shell, console.log);
|
|
3838
4035
|
return;
|
|
3839
4036
|
}
|
|
3840
4037
|
}
|
|
3841
4038
|
if (currentCommand === "status" || currentCommand === "verify" || currentCommand === "seal") {
|
|
3842
4039
|
const gates = await getGateNames();
|
|
3843
4040
|
if (gates.length > 0) {
|
|
3844
|
-
|
|
4041
|
+
tabtab2.log(gates, shell, console.log);
|
|
3845
4042
|
return;
|
|
3846
4043
|
}
|
|
3847
4044
|
}
|
|
3848
4045
|
if (currentCommand === "run") {
|
|
3849
4046
|
const suites = await getSuiteNames();
|
|
3850
4047
|
if (suites.length > 0) {
|
|
3851
|
-
|
|
4048
|
+
tabtab2.log(suites, shell, console.log);
|
|
3852
4049
|
return;
|
|
3853
4050
|
}
|
|
3854
4051
|
}
|
|
3855
|
-
|
|
3856
|
-
|
|
4052
|
+
const knownCommands = [
|
|
4053
|
+
"init",
|
|
4054
|
+
"status",
|
|
4055
|
+
"run",
|
|
4056
|
+
"verify",
|
|
4057
|
+
"seal",
|
|
4058
|
+
"keygen",
|
|
4059
|
+
"prune",
|
|
4060
|
+
"identity",
|
|
4061
|
+
"team",
|
|
4062
|
+
"whoami",
|
|
4063
|
+
"completion"
|
|
4064
|
+
];
|
|
4065
|
+
if (!currentCommand || !knownCommands.includes(currentCommand)) {
|
|
4066
|
+
tabtab2.log([...commands, ...globalOptions2], shell, console.log);
|
|
3857
4067
|
}
|
|
3858
4068
|
}
|
|
3859
4069
|
async function getIdentitySlugs() {
|
|
@@ -3885,35 +4095,65 @@ async function getSuiteNames() {
|
|
|
3885
4095
|
return [];
|
|
3886
4096
|
}
|
|
3887
4097
|
var completionCommand = new Command("completion").description("Shell completion commands");
|
|
3888
|
-
|
|
4098
|
+
function detectCurrentShell2() {
|
|
4099
|
+
const shellPath = process.env.SHELL ?? "";
|
|
4100
|
+
if (shellPath.endsWith("/bash") || shellPath.endsWith("/bash.exe")) {
|
|
4101
|
+
return "bash";
|
|
4102
|
+
}
|
|
4103
|
+
if (shellPath.endsWith("/zsh") || shellPath.endsWith("/zsh.exe")) {
|
|
4104
|
+
return "zsh";
|
|
4105
|
+
}
|
|
4106
|
+
if (shellPath.endsWith("/fish") || shellPath.endsWith("/fish.exe")) {
|
|
4107
|
+
return "fish";
|
|
4108
|
+
}
|
|
4109
|
+
return null;
|
|
4110
|
+
}
|
|
4111
|
+
function getSourceCommand2(shell) {
|
|
4112
|
+
switch (shell) {
|
|
4113
|
+
case "bash":
|
|
4114
|
+
return "source ~/.bashrc";
|
|
4115
|
+
case "zsh":
|
|
4116
|
+
return "source ~/.zshrc";
|
|
4117
|
+
case "fish":
|
|
4118
|
+
return "source ~/.config/fish/config.fish";
|
|
4119
|
+
}
|
|
4120
|
+
}
|
|
4121
|
+
completionCommand.command("install [shell]").description("Install shell completion (auto-detects shell, or specify bash/zsh/fish)").action(async (shellArg) => {
|
|
3889
4122
|
try {
|
|
3890
4123
|
let shell;
|
|
3891
4124
|
if (shellArg !== void 0) {
|
|
3892
|
-
if (
|
|
3893
|
-
shell = shellArg;
|
|
3894
|
-
} else {
|
|
4125
|
+
if (!isSupportedShell(shellArg)) {
|
|
3895
4126
|
error(`Shell "${shellArg}" is not supported. Use bash, zsh, or fish.`);
|
|
3896
4127
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
3897
4128
|
}
|
|
4129
|
+
shell = shellArg;
|
|
4130
|
+
} else {
|
|
4131
|
+
const detected = detectCurrentShell2();
|
|
4132
|
+
if (!detected) {
|
|
4133
|
+
error(
|
|
4134
|
+
"Could not detect your shell. Please specify: attest-it completion install <bash|zsh|fish>"
|
|
4135
|
+
);
|
|
4136
|
+
process.exit(ExitCode.CONFIG_ERROR);
|
|
4137
|
+
}
|
|
4138
|
+
shell = detected;
|
|
4139
|
+
info(`Detected shell: ${shell}`);
|
|
3898
4140
|
}
|
|
3899
|
-
await
|
|
3900
|
-
name:
|
|
3901
|
-
completer:
|
|
4141
|
+
await tabtab2.install({
|
|
4142
|
+
name: PROGRAM_NAME2,
|
|
4143
|
+
completer: PROGRAM_NAME2,
|
|
4144
|
+
shell
|
|
4145
|
+
});
|
|
4146
|
+
await tabtab2.install({
|
|
4147
|
+
name: PROGRAM_ALIAS2,
|
|
4148
|
+
completer: PROGRAM_ALIAS2,
|
|
3902
4149
|
shell
|
|
3903
4150
|
});
|
|
3904
4151
|
log("");
|
|
3905
|
-
success(
|
|
4152
|
+
success(`Shell completion installed for ${shell}!`);
|
|
4153
|
+
info(`Completions enabled for both "${PROGRAM_NAME2}" and "${PROGRAM_ALIAS2}" commands.`);
|
|
3906
4154
|
log("");
|
|
3907
4155
|
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
|
-
}
|
|
4156
|
+
log(` ${getSourceCommand2(shell)}`);
|
|
3917
4157
|
log("");
|
|
3918
4158
|
} catch (err) {
|
|
3919
4159
|
error(`Failed to install completion: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3922,8 +4162,11 @@ completionCommand.command("install [shell]").description("Install shell completi
|
|
|
3922
4162
|
});
|
|
3923
4163
|
completionCommand.command("uninstall").description("Uninstall shell completion").action(async () => {
|
|
3924
4164
|
try {
|
|
3925
|
-
await
|
|
3926
|
-
name:
|
|
4165
|
+
await tabtab2.uninstall({
|
|
4166
|
+
name: PROGRAM_NAME2
|
|
4167
|
+
});
|
|
4168
|
+
await tabtab2.uninstall({
|
|
4169
|
+
name: PROGRAM_ALIAS2
|
|
3927
4170
|
});
|
|
3928
4171
|
log("");
|
|
3929
4172
|
success("Shell completion uninstalled!");
|
|
@@ -3934,11 +4177,19 @@ completionCommand.command("uninstall").description("Uninstall shell completion")
|
|
|
3934
4177
|
}
|
|
3935
4178
|
});
|
|
3936
4179
|
completionCommand.command("server", { hidden: true }).description("Completion server (internal)").action(async () => {
|
|
3937
|
-
const env =
|
|
4180
|
+
const env = tabtab2.parseEnv(process.env);
|
|
3938
4181
|
if (env.complete) {
|
|
3939
4182
|
await getCompletions(env);
|
|
3940
4183
|
}
|
|
3941
4184
|
});
|
|
4185
|
+
function createCompletionServerCommand() {
|
|
4186
|
+
return new Command("completion-server").allowUnknownOption().allowExcessArguments(true).action(async () => {
|
|
4187
|
+
const env = tabtab2.parseEnv(process.env);
|
|
4188
|
+
if (env.complete) {
|
|
4189
|
+
await getCompletions(env);
|
|
4190
|
+
}
|
|
4191
|
+
});
|
|
4192
|
+
}
|
|
3942
4193
|
function hasVersion(data) {
|
|
3943
4194
|
return typeof data === "object" && data !== null && "version" in data && // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
3944
4195
|
typeof data.version === "string";
|
|
@@ -3983,6 +4234,7 @@ program.addCommand(identityCommand);
|
|
|
3983
4234
|
program.addCommand(teamCommand);
|
|
3984
4235
|
program.addCommand(whoamiCommand);
|
|
3985
4236
|
program.addCommand(completionCommand);
|
|
4237
|
+
program.addCommand(createCompletionServerCommand(), { hidden: true });
|
|
3986
4238
|
function processHomeDirOption() {
|
|
3987
4239
|
const homeDirIndex = process.argv.indexOf("--home-dir");
|
|
3988
4240
|
if (homeDirIndex !== -1 && homeDirIndex + 1 < process.argv.length) {
|
|
@@ -3999,7 +4251,10 @@ async function run() {
|
|
|
3999
4251
|
console.log(getPackageVersion());
|
|
4000
4252
|
process.exit(0);
|
|
4001
4253
|
}
|
|
4002
|
-
|
|
4254
|
+
const isCompletionServer = process.argv.includes("completion-server");
|
|
4255
|
+
if (!isCompletionServer) {
|
|
4256
|
+
await initTheme();
|
|
4257
|
+
}
|
|
4003
4258
|
program.parse();
|
|
4004
4259
|
const options = program.opts();
|
|
4005
4260
|
const outputOptions = {};
|