@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/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].
|
|
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:
|
|
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:
|
|
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.
|
|
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.
|
|
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
|
|
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 =
|
|
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
|
-
|
|
4012
|
+
tabtab2__default.default.logFiles();
|
|
3816
4013
|
return;
|
|
3817
4014
|
}
|
|
3818
4015
|
if (lastWord.startsWith("-")) {
|
|
3819
|
-
|
|
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
|
|
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
|
-
|
|
4031
|
+
tabtab2__default.default.log(identities, shell, console.log);
|
|
3835
4032
|
return;
|
|
3836
4033
|
}
|
|
3837
4034
|
}
|
|
3838
4035
|
if (!subcommand || subcommandIndex < 0) {
|
|
3839
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4056
|
+
tabtab2__default.default.log(["bash", "zsh", "fish"], shell, console.log);
|
|
3860
4057
|
return;
|
|
3861
4058
|
}
|
|
3862
4059
|
if (!subcommand || subcommandIndex < 0) {
|
|
3863
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4074
|
+
tabtab2__default.default.log(suites, shell, console.log);
|
|
3878
4075
|
return;
|
|
3879
4076
|
}
|
|
3880
4077
|
}
|
|
3881
|
-
|
|
3882
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
3926
|
-
name:
|
|
3927
|
-
completer:
|
|
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(
|
|
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
|
-
|
|
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
|
|
3952
|
-
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 =
|
|
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
|
-
|
|
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 = {};
|