@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/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)}`);
|
|
@@ -2408,6 +2501,15 @@ function createKeyProviderFromIdentity2(identity) {
|
|
|
2408
2501
|
field: privateKey.field
|
|
2409
2502
|
}
|
|
2410
2503
|
});
|
|
2504
|
+
case "yubikey":
|
|
2505
|
+
return core.KeyProviderRegistry.create({
|
|
2506
|
+
type: "yubikey",
|
|
2507
|
+
options: {
|
|
2508
|
+
encryptedKeyPath: privateKey.encryptedKeyPath,
|
|
2509
|
+
slot: privateKey.slot,
|
|
2510
|
+
serial: privateKey.serial
|
|
2511
|
+
}
|
|
2512
|
+
});
|
|
2411
2513
|
default: {
|
|
2412
2514
|
const _exhaustiveCheck = privateKey;
|
|
2413
2515
|
throw new Error(`Unsupported private key type: ${String(_exhaustiveCheck)}`);
|
|
@@ -2423,6 +2525,8 @@ function getKeyRefFromIdentity2(identity) {
|
|
|
2423
2525
|
return privateKey.service;
|
|
2424
2526
|
case "1password":
|
|
2425
2527
|
return privateKey.item;
|
|
2528
|
+
case "yubikey":
|
|
2529
|
+
return privateKey.encryptedKeyPath;
|
|
2426
2530
|
default: {
|
|
2427
2531
|
const _exhaustiveCheck = privateKey;
|
|
2428
2532
|
throw new Error(`Unsupported private key type: ${String(_exhaustiveCheck)}`);
|
|
@@ -2462,6 +2566,9 @@ async function runList() {
|
|
|
2462
2566
|
case "1password":
|
|
2463
2567
|
keyType = "1password";
|
|
2464
2568
|
break;
|
|
2569
|
+
case "yubikey":
|
|
2570
|
+
keyType = "yubikey";
|
|
2571
|
+
break;
|
|
2465
2572
|
}
|
|
2466
2573
|
log(`${marker} ${theme3.blue(slug)}`);
|
|
2467
2574
|
log(` Name: ${nameDisplay}`);
|
|
@@ -2551,6 +2658,8 @@ async function runCreate() {
|
|
|
2551
2658
|
info("Checking available key storage providers...");
|
|
2552
2659
|
const opAvailable = await core.OnePasswordKeyProvider.isInstalled();
|
|
2553
2660
|
const keychainAvailable = core.MacOSKeychainKeyProvider.isAvailable();
|
|
2661
|
+
const yubikeyInstalled = await core.YubiKeyProvider.isInstalled();
|
|
2662
|
+
const yubikeyConnected = yubikeyInstalled ? await core.YubiKeyProvider.isConnected() : false;
|
|
2554
2663
|
const configDir = core.getAttestItConfigDir();
|
|
2555
2664
|
const storageChoices = [
|
|
2556
2665
|
{ name: `File system (${path.join(configDir, "keys")})`, value: "file" }
|
|
@@ -2561,6 +2670,10 @@ async function runCreate() {
|
|
|
2561
2670
|
if (opAvailable) {
|
|
2562
2671
|
storageChoices.push({ name: "1Password", value: "1password" });
|
|
2563
2672
|
}
|
|
2673
|
+
if (yubikeyInstalled) {
|
|
2674
|
+
const yubikeyLabel = yubikeyConnected ? "YubiKey (encrypted with challenge-response)" : "YubiKey (not connected - insert YubiKey first)";
|
|
2675
|
+
storageChoices.push({ name: yubikeyLabel, value: "yubikey" });
|
|
2676
|
+
}
|
|
2564
2677
|
const keyStorageType = await prompts.select({
|
|
2565
2678
|
message: "Where should the private key be stored?",
|
|
2566
2679
|
choices: storageChoices
|
|
@@ -2760,6 +2873,75 @@ async function runCreate() {
|
|
|
2760
2873
|
keyStorageDescription = `1Password (${selectedVault}/${item})`;
|
|
2761
2874
|
break;
|
|
2762
2875
|
}
|
|
2876
|
+
case "yubikey": {
|
|
2877
|
+
if (!await core.YubiKeyProvider.isConnected()) {
|
|
2878
|
+
error("No YubiKey detected. Please insert your YubiKey and try again.");
|
|
2879
|
+
process.exit(ExitCode.CONFIG_ERROR);
|
|
2880
|
+
}
|
|
2881
|
+
const yubikeys = await core.YubiKeyProvider.listDevices();
|
|
2882
|
+
if (yubikeys.length === 0) {
|
|
2883
|
+
throw new Error("No YubiKeys detected. Please insert a YubiKey and try again.");
|
|
2884
|
+
}
|
|
2885
|
+
const formatYubiKeyChoice = (yk) => {
|
|
2886
|
+
return `${theme3.blue.bold()(yk.type)} ${theme3.muted(`(Serial: ${yk.serial}, FW: ${yk.firmware})`)}`;
|
|
2887
|
+
};
|
|
2888
|
+
let selectedSerial;
|
|
2889
|
+
if (yubikeys.length === 1 && yubikeys[0]) {
|
|
2890
|
+
selectedSerial = yubikeys[0].serial;
|
|
2891
|
+
info(`Using YubiKey: ${formatYubiKeyChoice(yubikeys[0])}`);
|
|
2892
|
+
} else {
|
|
2893
|
+
selectedSerial = await prompts.select({
|
|
2894
|
+
message: "Select YubiKey:",
|
|
2895
|
+
choices: yubikeys.map((yk) => ({
|
|
2896
|
+
name: formatYubiKeyChoice(yk),
|
|
2897
|
+
value: yk.serial
|
|
2898
|
+
}))
|
|
2899
|
+
});
|
|
2900
|
+
}
|
|
2901
|
+
const slot = 2;
|
|
2902
|
+
const isChallengeResponseConfigured = await core.YubiKeyProvider.isChallengeResponseConfigured(
|
|
2903
|
+
slot,
|
|
2904
|
+
selectedSerial
|
|
2905
|
+
);
|
|
2906
|
+
if (!isChallengeResponseConfigured) {
|
|
2907
|
+
log("");
|
|
2908
|
+
error(`YubiKey slot ${String(slot)} is not configured for HMAC challenge-response.`);
|
|
2909
|
+
log("");
|
|
2910
|
+
log("To configure it, run:");
|
|
2911
|
+
log(theme3.blue(` ykman otp chalresp --generate ${String(slot)}`));
|
|
2912
|
+
log("");
|
|
2913
|
+
log("This will configure slot 2 with a randomly generated secret.");
|
|
2914
|
+
log(theme3.muted("Note: Make sure to back up the secret if needed for recovery."));
|
|
2915
|
+
process.exit(ExitCode.CONFIG_ERROR);
|
|
2916
|
+
}
|
|
2917
|
+
const encryptedKeyName = await prompts.input({
|
|
2918
|
+
message: "Encrypted key file name:",
|
|
2919
|
+
default: `${slug}.enc`,
|
|
2920
|
+
validate: (value) => {
|
|
2921
|
+
if (!value || value.trim().length === 0) {
|
|
2922
|
+
return "File name cannot be empty";
|
|
2923
|
+
}
|
|
2924
|
+
return true;
|
|
2925
|
+
}
|
|
2926
|
+
});
|
|
2927
|
+
const keysDir = path.join(core.getAttestItConfigDir(), "keys");
|
|
2928
|
+
await promises.mkdir(keysDir, { recursive: true });
|
|
2929
|
+
const encryptedKeyPath = path.join(keysDir, encryptedKeyName);
|
|
2930
|
+
const result = await core.YubiKeyProvider.encryptPrivateKey({
|
|
2931
|
+
privateKey: keyPair.privateKey,
|
|
2932
|
+
encryptedKeyPath,
|
|
2933
|
+
slot,
|
|
2934
|
+
serial: selectedSerial
|
|
2935
|
+
});
|
|
2936
|
+
privateKeyRef = {
|
|
2937
|
+
type: "yubikey",
|
|
2938
|
+
encryptedKeyPath: result.encryptedKeyPath,
|
|
2939
|
+
slot,
|
|
2940
|
+
serial: selectedSerial
|
|
2941
|
+
};
|
|
2942
|
+
keyStorageDescription = result.storageDescription;
|
|
2943
|
+
break;
|
|
2944
|
+
}
|
|
2763
2945
|
default:
|
|
2764
2946
|
throw new Error(`Unknown key storage type: ${keyStorageType}`);
|
|
2765
2947
|
}
|
|
@@ -2809,6 +2991,7 @@ async function runCreate() {
|
|
|
2809
2991
|
log(`To use this identity, run: attest-it identity use ${slug}`);
|
|
2810
2992
|
log("");
|
|
2811
2993
|
}
|
|
2994
|
+
await offerCompletionInstall();
|
|
2812
2995
|
} catch (err) {
|
|
2813
2996
|
if (err instanceof Error) {
|
|
2814
2997
|
error(err.message);
|
|
@@ -3091,6 +3274,11 @@ function formatKeyLocation(privateKey) {
|
|
|
3091
3274
|
}
|
|
3092
3275
|
return `${theme3.blue.bold()("1Password")}: ${theme3.muted(parts.join("/"))}`;
|
|
3093
3276
|
}
|
|
3277
|
+
case "yubikey": {
|
|
3278
|
+
const slotInfo = privateKey.slot ? ` (slot ${String(privateKey.slot)})` : "";
|
|
3279
|
+
const serialInfo = privateKey.serial ? ` [${privateKey.serial}]` : "";
|
|
3280
|
+
return `${theme3.blue.bold()("YubiKey")}${serialInfo}${slotInfo}: ${theme3.muted(privateKey.encryptedKeyPath)}`;
|
|
3281
|
+
}
|
|
3094
3282
|
default:
|
|
3095
3283
|
return "Unknown storage";
|
|
3096
3284
|
}
|
|
@@ -3764,11 +3952,16 @@ async function runRemove2(slug, options) {
|
|
|
3764
3952
|
|
|
3765
3953
|
// src/commands/team/index.ts
|
|
3766
3954
|
var teamCommand = new commander.Command("team").description("Manage team members and authorizations").addCommand(listCommand2).addCommand(addCommand).addCommand(editCommand2).addCommand(removeCommand2);
|
|
3767
|
-
var
|
|
3955
|
+
var PROGRAM_NAME2 = "attest-it";
|
|
3956
|
+
var PROGRAM_ALIAS2 = "attest";
|
|
3957
|
+
var PROGRAM_NAMES = [PROGRAM_NAME2, PROGRAM_ALIAS2];
|
|
3958
|
+
function isSupportedShell(value) {
|
|
3959
|
+
return value === "bash" || value === "zsh" || value === "fish";
|
|
3960
|
+
}
|
|
3768
3961
|
async function getCompletions(env) {
|
|
3769
3962
|
let shell;
|
|
3770
3963
|
try {
|
|
3771
|
-
const detectedShell =
|
|
3964
|
+
const detectedShell = tabtab2__default.default.getShellFromEnv(process.env);
|
|
3772
3965
|
shell = detectedShell === "pwsh" ? "bash" : detectedShell;
|
|
3773
3966
|
} catch {
|
|
3774
3967
|
shell = "bash";
|
|
@@ -3812,15 +4005,15 @@ async function getCompletions(env) {
|
|
|
3812
4005
|
const lastWord = env.last;
|
|
3813
4006
|
const prevWord = env.prev;
|
|
3814
4007
|
if (prevWord === "--config" || prevWord === "-c") {
|
|
3815
|
-
|
|
4008
|
+
tabtab2__default.default.logFiles();
|
|
3816
4009
|
return;
|
|
3817
4010
|
}
|
|
3818
4011
|
if (lastWord.startsWith("-")) {
|
|
3819
|
-
|
|
4012
|
+
tabtab2__default.default.log(globalOptions2, shell, console.log);
|
|
3820
4013
|
return;
|
|
3821
4014
|
}
|
|
3822
4015
|
const commandIndex = words.findIndex(
|
|
3823
|
-
(w) => !w.startsWith("-") && w
|
|
4016
|
+
(w) => !w.startsWith("-") && !PROGRAM_NAMES.includes(w) && w !== "npx"
|
|
3824
4017
|
);
|
|
3825
4018
|
const currentCommand = commandIndex >= 0 ? words[commandIndex] ?? null : null;
|
|
3826
4019
|
if (currentCommand === "identity") {
|
|
@@ -3831,12 +4024,12 @@ async function getCompletions(env) {
|
|
|
3831
4024
|
if (subcommand === "use" || subcommand === "remove") {
|
|
3832
4025
|
const identities = await getIdentitySlugs();
|
|
3833
4026
|
if (identities.length > 0) {
|
|
3834
|
-
|
|
4027
|
+
tabtab2__default.default.log(identities, shell, console.log);
|
|
3835
4028
|
return;
|
|
3836
4029
|
}
|
|
3837
4030
|
}
|
|
3838
4031
|
if (!subcommand || subcommandIndex < 0) {
|
|
3839
|
-
|
|
4032
|
+
tabtab2__default.default.log(identitySubcommands, shell, console.log);
|
|
3840
4033
|
return;
|
|
3841
4034
|
}
|
|
3842
4035
|
}
|
|
@@ -3846,7 +4039,7 @@ async function getCompletions(env) {
|
|
|
3846
4039
|
);
|
|
3847
4040
|
const subcommand = subcommandIndex >= 0 ? words[subcommandIndex] ?? null : null;
|
|
3848
4041
|
if (!subcommand || subcommandIndex < 0) {
|
|
3849
|
-
|
|
4042
|
+
tabtab2__default.default.log(teamSubcommands, shell, console.log);
|
|
3850
4043
|
return;
|
|
3851
4044
|
}
|
|
3852
4045
|
}
|
|
@@ -3856,30 +4049,43 @@ async function getCompletions(env) {
|
|
|
3856
4049
|
);
|
|
3857
4050
|
const subcommand = subcommandIndex >= 0 ? words[subcommandIndex] ?? null : null;
|
|
3858
4051
|
if (subcommand === "install") {
|
|
3859
|
-
|
|
4052
|
+
tabtab2__default.default.log(["bash", "zsh", "fish"], shell, console.log);
|
|
3860
4053
|
return;
|
|
3861
4054
|
}
|
|
3862
4055
|
if (!subcommand || subcommandIndex < 0) {
|
|
3863
|
-
|
|
4056
|
+
tabtab2__default.default.log(completionSubcommands, shell, console.log);
|
|
3864
4057
|
return;
|
|
3865
4058
|
}
|
|
3866
4059
|
}
|
|
3867
4060
|
if (currentCommand === "status" || currentCommand === "verify" || currentCommand === "seal") {
|
|
3868
4061
|
const gates = await getGateNames();
|
|
3869
4062
|
if (gates.length > 0) {
|
|
3870
|
-
|
|
4063
|
+
tabtab2__default.default.log(gates, shell, console.log);
|
|
3871
4064
|
return;
|
|
3872
4065
|
}
|
|
3873
4066
|
}
|
|
3874
4067
|
if (currentCommand === "run") {
|
|
3875
4068
|
const suites = await getSuiteNames();
|
|
3876
4069
|
if (suites.length > 0) {
|
|
3877
|
-
|
|
4070
|
+
tabtab2__default.default.log(suites, shell, console.log);
|
|
3878
4071
|
return;
|
|
3879
4072
|
}
|
|
3880
4073
|
}
|
|
3881
|
-
|
|
3882
|
-
|
|
4074
|
+
const knownCommands = [
|
|
4075
|
+
"init",
|
|
4076
|
+
"status",
|
|
4077
|
+
"run",
|
|
4078
|
+
"verify",
|
|
4079
|
+
"seal",
|
|
4080
|
+
"keygen",
|
|
4081
|
+
"prune",
|
|
4082
|
+
"identity",
|
|
4083
|
+
"team",
|
|
4084
|
+
"whoami",
|
|
4085
|
+
"completion"
|
|
4086
|
+
];
|
|
4087
|
+
if (!currentCommand || !knownCommands.includes(currentCommand)) {
|
|
4088
|
+
tabtab2__default.default.log([...commands, ...globalOptions2], shell, console.log);
|
|
3883
4089
|
}
|
|
3884
4090
|
}
|
|
3885
4091
|
async function getIdentitySlugs() {
|
|
@@ -3911,35 +4117,65 @@ async function getSuiteNames() {
|
|
|
3911
4117
|
return [];
|
|
3912
4118
|
}
|
|
3913
4119
|
var completionCommand = new commander.Command("completion").description("Shell completion commands");
|
|
3914
|
-
|
|
4120
|
+
function detectCurrentShell2() {
|
|
4121
|
+
const shellPath = process.env.SHELL ?? "";
|
|
4122
|
+
if (shellPath.endsWith("/bash") || shellPath.endsWith("/bash.exe")) {
|
|
4123
|
+
return "bash";
|
|
4124
|
+
}
|
|
4125
|
+
if (shellPath.endsWith("/zsh") || shellPath.endsWith("/zsh.exe")) {
|
|
4126
|
+
return "zsh";
|
|
4127
|
+
}
|
|
4128
|
+
if (shellPath.endsWith("/fish") || shellPath.endsWith("/fish.exe")) {
|
|
4129
|
+
return "fish";
|
|
4130
|
+
}
|
|
4131
|
+
return null;
|
|
4132
|
+
}
|
|
4133
|
+
function getSourceCommand2(shell) {
|
|
4134
|
+
switch (shell) {
|
|
4135
|
+
case "bash":
|
|
4136
|
+
return "source ~/.bashrc";
|
|
4137
|
+
case "zsh":
|
|
4138
|
+
return "source ~/.zshrc";
|
|
4139
|
+
case "fish":
|
|
4140
|
+
return "source ~/.config/fish/config.fish";
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4143
|
+
completionCommand.command("install [shell]").description("Install shell completion (auto-detects shell, or specify bash/zsh/fish)").action(async (shellArg) => {
|
|
3915
4144
|
try {
|
|
3916
4145
|
let shell;
|
|
3917
4146
|
if (shellArg !== void 0) {
|
|
3918
|
-
if (
|
|
3919
|
-
shell = shellArg;
|
|
3920
|
-
} else {
|
|
4147
|
+
if (!isSupportedShell(shellArg)) {
|
|
3921
4148
|
error(`Shell "${shellArg}" is not supported. Use bash, zsh, or fish.`);
|
|
3922
4149
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
3923
4150
|
}
|
|
4151
|
+
shell = shellArg;
|
|
4152
|
+
} else {
|
|
4153
|
+
const detected = detectCurrentShell2();
|
|
4154
|
+
if (!detected) {
|
|
4155
|
+
error(
|
|
4156
|
+
"Could not detect your shell. Please specify: attest-it completion install <bash|zsh|fish>"
|
|
4157
|
+
);
|
|
4158
|
+
process.exit(ExitCode.CONFIG_ERROR);
|
|
4159
|
+
}
|
|
4160
|
+
shell = detected;
|
|
4161
|
+
info(`Detected shell: ${shell}`);
|
|
3924
4162
|
}
|
|
3925
|
-
await
|
|
3926
|
-
name:
|
|
3927
|
-
completer:
|
|
4163
|
+
await tabtab2__default.default.install({
|
|
4164
|
+
name: PROGRAM_NAME2,
|
|
4165
|
+
completer: PROGRAM_NAME2,
|
|
4166
|
+
shell
|
|
4167
|
+
});
|
|
4168
|
+
await tabtab2__default.default.install({
|
|
4169
|
+
name: PROGRAM_ALIAS2,
|
|
4170
|
+
completer: PROGRAM_ALIAS2,
|
|
3928
4171
|
shell
|
|
3929
4172
|
});
|
|
3930
4173
|
log("");
|
|
3931
|
-
success(
|
|
4174
|
+
success(`Shell completion installed for ${shell}!`);
|
|
4175
|
+
info(`Completions enabled for both "${PROGRAM_NAME2}" and "${PROGRAM_ALIAS2}" commands.`);
|
|
3932
4176
|
log("");
|
|
3933
4177
|
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
|
-
}
|
|
4178
|
+
log(` ${getSourceCommand2(shell)}`);
|
|
3943
4179
|
log("");
|
|
3944
4180
|
} catch (err) {
|
|
3945
4181
|
error(`Failed to install completion: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3948,8 +4184,11 @@ completionCommand.command("install [shell]").description("Install shell completi
|
|
|
3948
4184
|
});
|
|
3949
4185
|
completionCommand.command("uninstall").description("Uninstall shell completion").action(async () => {
|
|
3950
4186
|
try {
|
|
3951
|
-
await
|
|
3952
|
-
name:
|
|
4187
|
+
await tabtab2__default.default.uninstall({
|
|
4188
|
+
name: PROGRAM_NAME2
|
|
4189
|
+
});
|
|
4190
|
+
await tabtab2__default.default.uninstall({
|
|
4191
|
+
name: PROGRAM_ALIAS2
|
|
3953
4192
|
});
|
|
3954
4193
|
log("");
|
|
3955
4194
|
success("Shell completion uninstalled!");
|
|
@@ -3960,11 +4199,19 @@ completionCommand.command("uninstall").description("Uninstall shell completion")
|
|
|
3960
4199
|
}
|
|
3961
4200
|
});
|
|
3962
4201
|
completionCommand.command("server", { hidden: true }).description("Completion server (internal)").action(async () => {
|
|
3963
|
-
const env =
|
|
4202
|
+
const env = tabtab2__default.default.parseEnv(process.env);
|
|
3964
4203
|
if (env.complete) {
|
|
3965
4204
|
await getCompletions(env);
|
|
3966
4205
|
}
|
|
3967
4206
|
});
|
|
4207
|
+
function createCompletionServerCommand() {
|
|
4208
|
+
return new commander.Command("completion-server").allowUnknownOption().allowExcessArguments(true).action(async () => {
|
|
4209
|
+
const env = tabtab2__default.default.parseEnv(process.env);
|
|
4210
|
+
if (env.complete) {
|
|
4211
|
+
await getCompletions(env);
|
|
4212
|
+
}
|
|
4213
|
+
});
|
|
4214
|
+
}
|
|
3968
4215
|
function hasVersion(data) {
|
|
3969
4216
|
return typeof data === "object" && data !== null && "version" in data && // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
3970
4217
|
typeof data.version === "string";
|
|
@@ -4009,6 +4256,7 @@ program.addCommand(identityCommand);
|
|
|
4009
4256
|
program.addCommand(teamCommand);
|
|
4010
4257
|
program.addCommand(whoamiCommand);
|
|
4011
4258
|
program.addCommand(completionCommand);
|
|
4259
|
+
program.addCommand(createCompletionServerCommand(), { hidden: true });
|
|
4012
4260
|
function processHomeDirOption() {
|
|
4013
4261
|
const homeDirIndex = process.argv.indexOf("--home-dir");
|
|
4014
4262
|
if (homeDirIndex !== -1 && homeDirIndex + 1 < process.argv.length) {
|
|
@@ -4025,7 +4273,10 @@ async function run() {
|
|
|
4025
4273
|
console.log(getPackageVersion());
|
|
4026
4274
|
process.exit(0);
|
|
4027
4275
|
}
|
|
4028
|
-
|
|
4276
|
+
const isCompletionServer = process.argv.includes("completion-server");
|
|
4277
|
+
if (!isCompletionServer) {
|
|
4278
|
+
await initTheme();
|
|
4279
|
+
}
|
|
4029
4280
|
program.parse();
|
|
4030
4281
|
const options = program.opts();
|
|
4031
4282
|
const outputOptions = {};
|