@attest-it/cli 0.4.0 → 0.5.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 +448 -66
- package/dist/bin/attest-it.js.map +1 -1
- package/dist/index.cjs +450 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +448 -66
- package/dist/index.js.map +1 -1
- package/package.json +7 -3
package/dist/index.cjs
CHANGED
|
@@ -15,9 +15,12 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
15
15
|
var promises = require('fs/promises');
|
|
16
16
|
var ui = require('@inkjs/ui');
|
|
17
17
|
var yaml = require('yaml');
|
|
18
|
+
var tabtab = require('@pnpm/tabtab');
|
|
18
19
|
var url = require('url');
|
|
19
20
|
|
|
20
21
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
22
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
23
|
+
|
|
21
24
|
function _interopNamespace(e) {
|
|
22
25
|
if (e && e.__esModule) return e;
|
|
23
26
|
var n = Object.create(null);
|
|
@@ -40,6 +43,7 @@ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
|
|
|
40
43
|
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
41
44
|
var os__namespace = /*#__PURE__*/_interopNamespace(os);
|
|
42
45
|
var React7__namespace = /*#__PURE__*/_interopNamespace(React7);
|
|
46
|
+
var tabtab__default = /*#__PURE__*/_interopDefault(tabtab);
|
|
43
47
|
|
|
44
48
|
// src/index.ts
|
|
45
49
|
var globalOptions = {};
|
|
@@ -2486,31 +2490,46 @@ async function runList() {
|
|
|
2486
2490
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
2487
2491
|
}
|
|
2488
2492
|
}
|
|
2493
|
+
|
|
2494
|
+
// src/commands/identity/validation.ts
|
|
2495
|
+
function validateSlug(value, existingIdentities) {
|
|
2496
|
+
const trimmed = value.trim();
|
|
2497
|
+
if (!trimmed) {
|
|
2498
|
+
return "Slug cannot be empty";
|
|
2499
|
+
}
|
|
2500
|
+
if (!/^[a-z0-9-]+$/.test(trimmed)) {
|
|
2501
|
+
return "Slug must contain only lowercase letters, numbers, and hyphens";
|
|
2502
|
+
}
|
|
2503
|
+
if (existingIdentities?.[trimmed]) {
|
|
2504
|
+
return `Identity "${trimmed}" already exists`;
|
|
2505
|
+
}
|
|
2506
|
+
return true;
|
|
2507
|
+
}
|
|
2508
|
+
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2509
|
+
function validateEmail(value, required = false) {
|
|
2510
|
+
const trimmed = value.trim();
|
|
2511
|
+
if (!trimmed) {
|
|
2512
|
+
return required ? "Email cannot be empty" : true;
|
|
2513
|
+
}
|
|
2514
|
+
if (!EMAIL_REGEX.test(trimmed)) {
|
|
2515
|
+
return "Please enter a valid email address";
|
|
2516
|
+
}
|
|
2517
|
+
return true;
|
|
2518
|
+
}
|
|
2489
2519
|
var createCommand = new commander.Command("create").description("Create a new identity with Ed25519 keypair").action(async () => {
|
|
2490
2520
|
await runCreate();
|
|
2491
2521
|
});
|
|
2492
2522
|
async function runCreate() {
|
|
2493
2523
|
try {
|
|
2494
|
-
const theme3 =
|
|
2524
|
+
const theme3 = getTheme();
|
|
2495
2525
|
log("");
|
|
2496
2526
|
log(theme3.blue.bold()("Create New Identity"));
|
|
2497
2527
|
log("");
|
|
2498
2528
|
const existingConfig = await core.loadLocalConfig();
|
|
2499
|
-
const slug = await prompts.input({
|
|
2529
|
+
const slug = (await prompts.input({
|
|
2500
2530
|
message: "Identity slug (unique identifier):",
|
|
2501
|
-
validate: (value) =>
|
|
2502
|
-
|
|
2503
|
-
return "Slug cannot be empty";
|
|
2504
|
-
}
|
|
2505
|
-
if (!/^[a-z0-9-]+$/.test(value)) {
|
|
2506
|
-
return "Slug must contain only lowercase letters, numbers, and hyphens";
|
|
2507
|
-
}
|
|
2508
|
-
if (existingConfig?.identities[value]) {
|
|
2509
|
-
return `Identity "${value}" already exists`;
|
|
2510
|
-
}
|
|
2511
|
-
return true;
|
|
2512
|
-
}
|
|
2513
|
-
});
|
|
2531
|
+
validate: (value) => validateSlug(value, existingConfig?.identities)
|
|
2532
|
+
})).trim();
|
|
2514
2533
|
const name = await prompts.input({
|
|
2515
2534
|
message: "Display name:",
|
|
2516
2535
|
validate: (value) => {
|
|
@@ -2520,21 +2539,31 @@ async function runCreate() {
|
|
|
2520
2539
|
return true;
|
|
2521
2540
|
}
|
|
2522
2541
|
});
|
|
2523
|
-
const email = await prompts.input({
|
|
2542
|
+
const email = (await prompts.input({
|
|
2524
2543
|
message: "Email (optional):",
|
|
2525
|
-
default: ""
|
|
2526
|
-
|
|
2544
|
+
default: "",
|
|
2545
|
+
validate: validateEmail
|
|
2546
|
+
})).trim();
|
|
2527
2547
|
const github = await prompts.input({
|
|
2528
2548
|
message: "GitHub username (optional):",
|
|
2529
2549
|
default: ""
|
|
2530
2550
|
});
|
|
2551
|
+
info("Checking available key storage providers...");
|
|
2552
|
+
const opAvailable = await core.OnePasswordKeyProvider.isInstalled();
|
|
2553
|
+
const keychainAvailable = core.MacOSKeychainKeyProvider.isAvailable();
|
|
2554
|
+
const configDir = core.getAttestItConfigDir();
|
|
2555
|
+
const storageChoices = [
|
|
2556
|
+
{ name: `File system (${path.join(configDir, "keys")})`, value: "file" }
|
|
2557
|
+
];
|
|
2558
|
+
if (keychainAvailable) {
|
|
2559
|
+
storageChoices.push({ name: "macOS Keychain", value: "keychain" });
|
|
2560
|
+
}
|
|
2561
|
+
if (opAvailable) {
|
|
2562
|
+
storageChoices.push({ name: "1Password", value: "1password" });
|
|
2563
|
+
}
|
|
2531
2564
|
const keyStorageType = await prompts.select({
|
|
2532
2565
|
message: "Where should the private key be stored?",
|
|
2533
|
-
choices:
|
|
2534
|
-
{ name: "File system (~/.config/attest-it/keys/)", value: "file" },
|
|
2535
|
-
{ name: "macOS Keychain", value: "keychain" },
|
|
2536
|
-
{ name: "1Password", value: "1password" }
|
|
2537
|
-
]
|
|
2566
|
+
choices: storageChoices
|
|
2538
2567
|
});
|
|
2539
2568
|
log("");
|
|
2540
2569
|
log("Generating Ed25519 keypair...");
|
|
@@ -2543,7 +2572,7 @@ async function runCreate() {
|
|
|
2543
2572
|
let keyStorageDescription;
|
|
2544
2573
|
switch (keyStorageType) {
|
|
2545
2574
|
case "file": {
|
|
2546
|
-
const keysDir = path.join(
|
|
2575
|
+
const keysDir = path.join(core.getAttestItConfigDir(), "keys");
|
|
2547
2576
|
await promises.mkdir(keysDir, { recursive: true });
|
|
2548
2577
|
const keyPath = path.join(keysDir, `${slug}.pem`);
|
|
2549
2578
|
await promises.writeFile(keyPath, keyPair.privateKey, { mode: 384 });
|
|
@@ -2552,44 +2581,138 @@ async function runCreate() {
|
|
|
2552
2581
|
break;
|
|
2553
2582
|
}
|
|
2554
2583
|
case "keychain": {
|
|
2555
|
-
|
|
2556
|
-
if (!MacOSKeychainKeyProvider3.isAvailable()) {
|
|
2584
|
+
if (!core.MacOSKeychainKeyProvider.isAvailable()) {
|
|
2557
2585
|
error("macOS Keychain is not available on this system");
|
|
2558
2586
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
2559
2587
|
}
|
|
2588
|
+
const keychains = await core.MacOSKeychainKeyProvider.listKeychains();
|
|
2589
|
+
if (keychains.length === 0) {
|
|
2590
|
+
throw new Error("No keychains found on this system");
|
|
2591
|
+
}
|
|
2592
|
+
const formatKeychainChoice = (kc) => {
|
|
2593
|
+
return `${theme3.blue.bold()(kc.name)} ${theme3.muted(`(${kc.path})`)}`;
|
|
2594
|
+
};
|
|
2595
|
+
let selectedKeychain;
|
|
2596
|
+
if (keychains.length === 1 && keychains[0]) {
|
|
2597
|
+
selectedKeychain = keychains[0];
|
|
2598
|
+
info(`Using keychain: ${formatKeychainChoice(selectedKeychain)}`);
|
|
2599
|
+
} else {
|
|
2600
|
+
const selectedPath = await prompts.select({
|
|
2601
|
+
message: "Select keychain:",
|
|
2602
|
+
choices: keychains.map((kc) => ({
|
|
2603
|
+
name: formatKeychainChoice(kc),
|
|
2604
|
+
value: kc.path
|
|
2605
|
+
}))
|
|
2606
|
+
});
|
|
2607
|
+
const foundKeychain = keychains.find((kc) => kc.path === selectedPath);
|
|
2608
|
+
if (!foundKeychain) {
|
|
2609
|
+
throw new Error("Selected keychain not found");
|
|
2610
|
+
}
|
|
2611
|
+
selectedKeychain = foundKeychain;
|
|
2612
|
+
}
|
|
2613
|
+
const keychainItemName = await prompts.input({
|
|
2614
|
+
message: "Keychain item name:",
|
|
2615
|
+
default: `attest-it-${slug}`,
|
|
2616
|
+
validate: (value) => {
|
|
2617
|
+
if (!value || value.trim().length === 0) {
|
|
2618
|
+
return "Item name cannot be empty";
|
|
2619
|
+
}
|
|
2620
|
+
return true;
|
|
2621
|
+
}
|
|
2622
|
+
});
|
|
2560
2623
|
const { execFile } = await import('child_process');
|
|
2561
2624
|
const { promisify } = await import('util');
|
|
2562
2625
|
const execFileAsync = promisify(execFile);
|
|
2563
2626
|
const encodedKey = Buffer.from(keyPair.privateKey).toString("base64");
|
|
2564
2627
|
try {
|
|
2565
|
-
|
|
2628
|
+
const addArgs = [
|
|
2566
2629
|
"add-generic-password",
|
|
2567
2630
|
"-a",
|
|
2568
2631
|
"attest-it",
|
|
2569
2632
|
"-s",
|
|
2570
|
-
|
|
2633
|
+
keychainItemName,
|
|
2571
2634
|
"-w",
|
|
2572
2635
|
encodedKey,
|
|
2573
|
-
"-U"
|
|
2574
|
-
|
|
2636
|
+
"-U",
|
|
2637
|
+
selectedKeychain.path
|
|
2638
|
+
];
|
|
2639
|
+
await execFileAsync("security", addArgs);
|
|
2575
2640
|
} catch (err) {
|
|
2576
2641
|
throw new Error(
|
|
2577
2642
|
`Failed to store key in macOS Keychain: ${err instanceof Error ? err.message : String(err)}`
|
|
2578
2643
|
);
|
|
2579
2644
|
}
|
|
2580
|
-
privateKeyRef = {
|
|
2581
|
-
|
|
2645
|
+
privateKeyRef = {
|
|
2646
|
+
type: "keychain",
|
|
2647
|
+
service: keychainItemName,
|
|
2648
|
+
account: "attest-it",
|
|
2649
|
+
keychain: selectedKeychain.path
|
|
2650
|
+
};
|
|
2651
|
+
keyStorageDescription = `macOS Keychain: ${selectedKeychain.name}/${keychainItemName}`;
|
|
2582
2652
|
break;
|
|
2583
2653
|
}
|
|
2584
2654
|
case "1password": {
|
|
2585
|
-
const
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2655
|
+
const accounts = await core.OnePasswordKeyProvider.listAccounts();
|
|
2656
|
+
if (accounts.length === 0) {
|
|
2657
|
+
throw new Error(
|
|
2658
|
+
'1Password CLI is installed but no accounts are signed in. Run "op signin" first.'
|
|
2659
|
+
);
|
|
2660
|
+
}
|
|
2661
|
+
const { execFile } = await import('child_process');
|
|
2662
|
+
const { promisify } = await import('util');
|
|
2663
|
+
const execFileAsync = promisify(execFile);
|
|
2664
|
+
const accountDetails = await Promise.all(
|
|
2665
|
+
accounts.map(async (acc) => {
|
|
2666
|
+
try {
|
|
2667
|
+
const { stdout } = await execFileAsync("op", [
|
|
2668
|
+
"account",
|
|
2669
|
+
"get",
|
|
2670
|
+
"--account",
|
|
2671
|
+
acc.user_uuid,
|
|
2672
|
+
"--format=json"
|
|
2673
|
+
]);
|
|
2674
|
+
const details = JSON.parse(stdout);
|
|
2675
|
+
const name2 = details !== null && typeof details === "object" && "name" in details && typeof details.name === "string" ? details.name : acc.url;
|
|
2676
|
+
return {
|
|
2677
|
+
url: acc.url,
|
|
2678
|
+
email: acc.email,
|
|
2679
|
+
name: name2
|
|
2680
|
+
};
|
|
2681
|
+
} catch {
|
|
2682
|
+
return {
|
|
2683
|
+
url: acc.url,
|
|
2684
|
+
email: acc.email,
|
|
2685
|
+
name: acc.url
|
|
2686
|
+
};
|
|
2590
2687
|
}
|
|
2591
|
-
|
|
2592
|
-
|
|
2688
|
+
})
|
|
2689
|
+
);
|
|
2690
|
+
const formatAccountChoice = (acc) => {
|
|
2691
|
+
return `${theme3.blue.bold()(acc.name)} ${theme3.muted(`(${acc.url})`)}`;
|
|
2692
|
+
};
|
|
2693
|
+
let selectedAccount;
|
|
2694
|
+
if (accountDetails.length === 1 && accountDetails[0]) {
|
|
2695
|
+
selectedAccount = accountDetails[0].url;
|
|
2696
|
+
info(`Using 1Password account: ${formatAccountChoice(accountDetails[0])}`);
|
|
2697
|
+
} else {
|
|
2698
|
+
selectedAccount = await prompts.select({
|
|
2699
|
+
message: "Select 1Password account:",
|
|
2700
|
+
choices: accountDetails.map((acc) => ({
|
|
2701
|
+
name: formatAccountChoice(acc),
|
|
2702
|
+
value: acc.url
|
|
2703
|
+
}))
|
|
2704
|
+
});
|
|
2705
|
+
}
|
|
2706
|
+
const vaults = await core.OnePasswordKeyProvider.listVaults(selectedAccount);
|
|
2707
|
+
if (vaults.length === 0) {
|
|
2708
|
+
throw new Error(`No vaults found in 1Password account: ${selectedAccount}`);
|
|
2709
|
+
}
|
|
2710
|
+
const selectedVault = await prompts.select({
|
|
2711
|
+
message: "Select vault for private key storage:",
|
|
2712
|
+
choices: vaults.map((v) => ({
|
|
2713
|
+
name: v.name,
|
|
2714
|
+
value: v.name
|
|
2715
|
+
}))
|
|
2593
2716
|
});
|
|
2594
2717
|
const item = await prompts.input({
|
|
2595
2718
|
message: "1Password item name:",
|
|
@@ -2601,26 +2724,40 @@ async function runCreate() {
|
|
|
2601
2724
|
return true;
|
|
2602
2725
|
}
|
|
2603
2726
|
});
|
|
2604
|
-
const {
|
|
2605
|
-
const
|
|
2606
|
-
|
|
2727
|
+
const { tmpdir } = await import('os');
|
|
2728
|
+
const tempDir = path.join(tmpdir(), `attest-it-${String(Date.now())}`);
|
|
2729
|
+
await promises.mkdir(tempDir, { recursive: true });
|
|
2730
|
+
const tempPrivatePath = path.join(tempDir, "private.pem");
|
|
2607
2731
|
try {
|
|
2608
|
-
await
|
|
2609
|
-
|
|
2732
|
+
await promises.writeFile(tempPrivatePath, keyPair.privateKey, { mode: 384 });
|
|
2733
|
+
const { execFile: execFile2 } = await import('child_process');
|
|
2734
|
+
const { promisify: promisify2 } = await import('util');
|
|
2735
|
+
const execFileAsync2 = promisify2(execFile2);
|
|
2736
|
+
const opArgs = [
|
|
2737
|
+
"document",
|
|
2610
2738
|
"create",
|
|
2611
|
-
|
|
2739
|
+
tempPrivatePath,
|
|
2740
|
+
"--title",
|
|
2741
|
+
item,
|
|
2612
2742
|
"--vault",
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
);
|
|
2743
|
+
selectedVault
|
|
2744
|
+
];
|
|
2745
|
+
if (selectedAccount) {
|
|
2746
|
+
opArgs.push("--account", selectedAccount);
|
|
2747
|
+
}
|
|
2748
|
+
await execFileAsync2("op", opArgs);
|
|
2749
|
+
} finally {
|
|
2750
|
+
const { rm } = await import('fs/promises');
|
|
2751
|
+
await rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
2752
|
+
});
|
|
2621
2753
|
}
|
|
2622
|
-
privateKeyRef = {
|
|
2623
|
-
|
|
2754
|
+
privateKeyRef = {
|
|
2755
|
+
type: "1password",
|
|
2756
|
+
vault: selectedVault,
|
|
2757
|
+
item,
|
|
2758
|
+
...selectedAccount && { account: selectedAccount }
|
|
2759
|
+
};
|
|
2760
|
+
keyStorageDescription = `1Password (${selectedVault}/${item})`;
|
|
2624
2761
|
break;
|
|
2625
2762
|
}
|
|
2626
2763
|
default:
|
|
@@ -2932,6 +3069,32 @@ async function runEdit(slug) {
|
|
|
2932
3069
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
2933
3070
|
}
|
|
2934
3071
|
}
|
|
3072
|
+
|
|
3073
|
+
// src/utils/format-key-location.ts
|
|
3074
|
+
function formatKeyLocation(privateKey) {
|
|
3075
|
+
const theme3 = getTheme();
|
|
3076
|
+
switch (privateKey.type) {
|
|
3077
|
+
case "file":
|
|
3078
|
+
return `${theme3.blue.bold()("File")}: ${theme3.muted(privateKey.path)}`;
|
|
3079
|
+
case "keychain": {
|
|
3080
|
+
let keychainName = "default";
|
|
3081
|
+
if (privateKey.keychain) {
|
|
3082
|
+
const filename = privateKey.keychain.split("/").pop() ?? privateKey.keychain;
|
|
3083
|
+
keychainName = filename.replace(/\.keychain(-db)?$/, "");
|
|
3084
|
+
}
|
|
3085
|
+
return `${theme3.blue.bold()("macOS Keychain")}: ${theme3.muted(`${keychainName}/${privateKey.service}`)}`;
|
|
3086
|
+
}
|
|
3087
|
+
case "1password": {
|
|
3088
|
+
const parts = [privateKey.vault, privateKey.item];
|
|
3089
|
+
if (privateKey.account) {
|
|
3090
|
+
parts.unshift(privateKey.account);
|
|
3091
|
+
}
|
|
3092
|
+
return `${theme3.blue.bold()("1Password")}: ${theme3.muted(parts.join("/"))}`;
|
|
3093
|
+
}
|
|
3094
|
+
default:
|
|
3095
|
+
return "Unknown storage";
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
2935
3098
|
var removeCommand = new commander.Command("remove").description("Delete identity and optionally delete private key").argument("<slug>", "Identity slug to remove").action(async (slug) => {
|
|
2936
3099
|
await runRemove(slug);
|
|
2937
3100
|
});
|
|
@@ -2947,7 +3110,7 @@ async function runRemove(slug) {
|
|
|
2947
3110
|
error(`Identity "${slug}" not found`);
|
|
2948
3111
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
2949
3112
|
}
|
|
2950
|
-
const theme3 =
|
|
3113
|
+
const theme3 = getTheme();
|
|
2951
3114
|
log("");
|
|
2952
3115
|
log(theme3.blue.bold()(`Remove Identity: ${slug}`));
|
|
2953
3116
|
log("");
|
|
@@ -2964,6 +3127,9 @@ async function runRemove(slug) {
|
|
|
2964
3127
|
log("Cancelled");
|
|
2965
3128
|
process.exit(ExitCode.CANCELLED);
|
|
2966
3129
|
}
|
|
3130
|
+
const keyLocation = formatKeyLocation(identity.privateKey);
|
|
3131
|
+
log(` Private key: ${keyLocation}`);
|
|
3132
|
+
log("");
|
|
2967
3133
|
const deletePrivateKey = await prompts.confirm({
|
|
2968
3134
|
message: "Also delete the private key from storage?",
|
|
2969
3135
|
default: false
|
|
@@ -2986,13 +3152,17 @@ async function runRemove(slug) {
|
|
|
2986
3152
|
const { promisify } = await import('util');
|
|
2987
3153
|
const execFileAsync = promisify(execFile);
|
|
2988
3154
|
try {
|
|
2989
|
-
|
|
3155
|
+
const deleteArgs = [
|
|
2990
3156
|
"delete-generic-password",
|
|
2991
3157
|
"-s",
|
|
2992
3158
|
identity.privateKey.service,
|
|
2993
3159
|
"-a",
|
|
2994
3160
|
identity.privateKey.account
|
|
2995
|
-
]
|
|
3161
|
+
];
|
|
3162
|
+
if (identity.privateKey.keychain) {
|
|
3163
|
+
deleteArgs.push(identity.privateKey.keychain);
|
|
3164
|
+
}
|
|
3165
|
+
await execFileAsync("security", deleteArgs);
|
|
2996
3166
|
log(` Deleted private key from macOS Keychain`);
|
|
2997
3167
|
} catch (err) {
|
|
2998
3168
|
if (err instanceof Error && !err.message.includes("could not be found") && !err.message.includes("does not exist")) {
|
|
@@ -3130,17 +3300,20 @@ async function runWhoami() {
|
|
|
3130
3300
|
error("Active identity not found");
|
|
3131
3301
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
3132
3302
|
}
|
|
3133
|
-
const theme3 =
|
|
3303
|
+
const theme3 = getTheme();
|
|
3134
3304
|
log("");
|
|
3135
|
-
log(theme3.
|
|
3305
|
+
log(theme3.blue.bold()("Active Identity"));
|
|
3306
|
+
log("");
|
|
3307
|
+
log(` Slug: ${theme3.green.bold()(config.activeIdentity)}`);
|
|
3308
|
+
log(` Name: ${identity.name}`);
|
|
3136
3309
|
if (identity.email) {
|
|
3137
|
-
log(theme3.muted(identity.email));
|
|
3310
|
+
log(` Email: ${theme3.muted(identity.email)}`);
|
|
3138
3311
|
}
|
|
3139
3312
|
if (identity.github) {
|
|
3140
|
-
log(theme3.muted("@" + identity.github));
|
|
3313
|
+
log(` GitHub: ${theme3.muted("@" + identity.github)}`);
|
|
3141
3314
|
}
|
|
3142
|
-
log("");
|
|
3143
|
-
log(`
|
|
3315
|
+
log(` Public Key: ${theme3.muted(identity.publicKey.slice(0, 24) + "...")}`);
|
|
3316
|
+
log(` Key Store: ${formatKeyLocation(identity.privateKey)}`);
|
|
3144
3317
|
log("");
|
|
3145
3318
|
} catch (err) {
|
|
3146
3319
|
if (err instanceof Error) {
|
|
@@ -3591,6 +3764,207 @@ async function runRemove2(slug, options) {
|
|
|
3591
3764
|
|
|
3592
3765
|
// src/commands/team/index.ts
|
|
3593
3766
|
var teamCommand = new commander.Command("team").description("Manage team members and authorizations").addCommand(listCommand2).addCommand(addCommand).addCommand(editCommand2).addCommand(removeCommand2);
|
|
3767
|
+
var PROGRAM_NAME = "attest-it";
|
|
3768
|
+
async function getCompletions(env) {
|
|
3769
|
+
let shell;
|
|
3770
|
+
try {
|
|
3771
|
+
const detectedShell = tabtab__default.default.getShellFromEnv(process.env);
|
|
3772
|
+
shell = detectedShell === "pwsh" ? "bash" : detectedShell;
|
|
3773
|
+
} catch {
|
|
3774
|
+
shell = "bash";
|
|
3775
|
+
}
|
|
3776
|
+
const commands = [
|
|
3777
|
+
{ name: "init", description: "Initialize a new config file" },
|
|
3778
|
+
{ name: "status", description: "Show status of all gates" },
|
|
3779
|
+
{ name: "run", description: "Run test suites interactively" },
|
|
3780
|
+
{ name: "verify", description: "Verify all seals are valid" },
|
|
3781
|
+
{ name: "seal", description: "Create a seal for a gate" },
|
|
3782
|
+
{ name: "keygen", description: "Generate a new keypair" },
|
|
3783
|
+
{ name: "prune", description: "Remove stale attestations" },
|
|
3784
|
+
{ name: "identity", description: "Manage identities" },
|
|
3785
|
+
{ name: "team", description: "Manage team members" },
|
|
3786
|
+
{ name: "whoami", description: "Show active identity" },
|
|
3787
|
+
{ name: "completion", description: "Shell completion commands" }
|
|
3788
|
+
];
|
|
3789
|
+
const globalOptions2 = [
|
|
3790
|
+
{ name: "--help", description: "Show help" },
|
|
3791
|
+
{ name: "--version", description: "Show version" },
|
|
3792
|
+
{ name: "--verbose", description: "Verbose output" },
|
|
3793
|
+
{ name: "--quiet", description: "Minimal output" },
|
|
3794
|
+
{ name: "--config", description: "Path to config file" }
|
|
3795
|
+
];
|
|
3796
|
+
const identitySubcommands = [
|
|
3797
|
+
{ name: "create", description: "Create a new identity" },
|
|
3798
|
+
{ name: "list", description: "List all identities" },
|
|
3799
|
+
{ name: "use", description: "Switch active identity" },
|
|
3800
|
+
{ name: "remove", description: "Remove an identity" }
|
|
3801
|
+
];
|
|
3802
|
+
const teamSubcommands = [
|
|
3803
|
+
{ name: "add", description: "Add yourself to the team" },
|
|
3804
|
+
{ name: "list", description: "List team members" },
|
|
3805
|
+
{ name: "remove", description: "Remove a team member" }
|
|
3806
|
+
];
|
|
3807
|
+
const completionSubcommands = [
|
|
3808
|
+
{ name: "install", description: "Install shell completion" },
|
|
3809
|
+
{ name: "uninstall", description: "Uninstall shell completion" }
|
|
3810
|
+
];
|
|
3811
|
+
const words = env.line.split(/\s+/).filter(Boolean);
|
|
3812
|
+
const lastWord = env.last;
|
|
3813
|
+
const prevWord = env.prev;
|
|
3814
|
+
if (prevWord === "--config" || prevWord === "-c") {
|
|
3815
|
+
tabtab__default.default.logFiles();
|
|
3816
|
+
return;
|
|
3817
|
+
}
|
|
3818
|
+
if (lastWord.startsWith("-")) {
|
|
3819
|
+
tabtab__default.default.log(globalOptions2, shell, console.log);
|
|
3820
|
+
return;
|
|
3821
|
+
}
|
|
3822
|
+
const commandIndex = words.findIndex(
|
|
3823
|
+
(w) => !w.startsWith("-") && w !== PROGRAM_NAME && w !== "npx"
|
|
3824
|
+
);
|
|
3825
|
+
const currentCommand = commandIndex >= 0 ? words[commandIndex] ?? null : null;
|
|
3826
|
+
if (currentCommand === "identity") {
|
|
3827
|
+
const subcommandIndex = words.findIndex(
|
|
3828
|
+
(w, i) => i > commandIndex && !w.startsWith("-")
|
|
3829
|
+
);
|
|
3830
|
+
const subcommand = subcommandIndex >= 0 ? words[subcommandIndex] ?? null : null;
|
|
3831
|
+
if (subcommand === "use" || subcommand === "remove") {
|
|
3832
|
+
const identities = await getIdentitySlugs();
|
|
3833
|
+
if (identities.length > 0) {
|
|
3834
|
+
tabtab__default.default.log(identities, shell, console.log);
|
|
3835
|
+
return;
|
|
3836
|
+
}
|
|
3837
|
+
}
|
|
3838
|
+
if (!subcommand || subcommandIndex < 0) {
|
|
3839
|
+
tabtab__default.default.log(identitySubcommands, shell, console.log);
|
|
3840
|
+
return;
|
|
3841
|
+
}
|
|
3842
|
+
}
|
|
3843
|
+
if (currentCommand === "team") {
|
|
3844
|
+
const subcommandIndex = words.findIndex(
|
|
3845
|
+
(w, i) => i > commandIndex && !w.startsWith("-")
|
|
3846
|
+
);
|
|
3847
|
+
const subcommand = subcommandIndex >= 0 ? words[subcommandIndex] ?? null : null;
|
|
3848
|
+
if (!subcommand || subcommandIndex < 0) {
|
|
3849
|
+
tabtab__default.default.log(teamSubcommands, shell, console.log);
|
|
3850
|
+
return;
|
|
3851
|
+
}
|
|
3852
|
+
}
|
|
3853
|
+
if (currentCommand === "completion") {
|
|
3854
|
+
const subcommandIndex = words.findIndex(
|
|
3855
|
+
(w, i) => i > commandIndex && !w.startsWith("-")
|
|
3856
|
+
);
|
|
3857
|
+
const subcommand = subcommandIndex >= 0 ? words[subcommandIndex] ?? null : null;
|
|
3858
|
+
if (subcommand === "install") {
|
|
3859
|
+
tabtab__default.default.log(["bash", "zsh", "fish"], shell, console.log);
|
|
3860
|
+
return;
|
|
3861
|
+
}
|
|
3862
|
+
if (!subcommand || subcommandIndex < 0) {
|
|
3863
|
+
tabtab__default.default.log(completionSubcommands, shell, console.log);
|
|
3864
|
+
return;
|
|
3865
|
+
}
|
|
3866
|
+
}
|
|
3867
|
+
if (currentCommand === "status" || currentCommand === "verify" || currentCommand === "seal") {
|
|
3868
|
+
const gates = await getGateNames();
|
|
3869
|
+
if (gates.length > 0) {
|
|
3870
|
+
tabtab__default.default.log(gates, shell, console.log);
|
|
3871
|
+
return;
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
if (currentCommand === "run") {
|
|
3875
|
+
const suites = await getSuiteNames();
|
|
3876
|
+
if (suites.length > 0) {
|
|
3877
|
+
tabtab__default.default.log(suites, shell, console.log);
|
|
3878
|
+
return;
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3881
|
+
if (!currentCommand) {
|
|
3882
|
+
tabtab__default.default.log([...commands, ...globalOptions2], shell, console.log);
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
async function getIdentitySlugs() {
|
|
3886
|
+
try {
|
|
3887
|
+
const config = await core.loadLocalConfig();
|
|
3888
|
+
if (config?.identities) {
|
|
3889
|
+
return Object.keys(config.identities);
|
|
3890
|
+
}
|
|
3891
|
+
} catch {
|
|
3892
|
+
}
|
|
3893
|
+
return [];
|
|
3894
|
+
}
|
|
3895
|
+
async function getGateNames() {
|
|
3896
|
+
try {
|
|
3897
|
+
const config = await core.loadConfig();
|
|
3898
|
+
if (config.gates) {
|
|
3899
|
+
return Object.keys(config.gates);
|
|
3900
|
+
}
|
|
3901
|
+
} catch {
|
|
3902
|
+
}
|
|
3903
|
+
return [];
|
|
3904
|
+
}
|
|
3905
|
+
async function getSuiteNames() {
|
|
3906
|
+
try {
|
|
3907
|
+
const config = await core.loadConfig();
|
|
3908
|
+
return Object.keys(config.suites);
|
|
3909
|
+
} catch {
|
|
3910
|
+
}
|
|
3911
|
+
return [];
|
|
3912
|
+
}
|
|
3913
|
+
var completionCommand = new commander.Command("completion").description("Shell completion commands");
|
|
3914
|
+
completionCommand.command("install [shell]").description("Install shell completion (bash, zsh, or fish)").action(async (shellArg) => {
|
|
3915
|
+
try {
|
|
3916
|
+
let shell;
|
|
3917
|
+
if (shellArg !== void 0) {
|
|
3918
|
+
if (tabtab__default.default.isShellSupported(shellArg)) {
|
|
3919
|
+
shell = shellArg;
|
|
3920
|
+
} else {
|
|
3921
|
+
error(`Shell "${shellArg}" is not supported. Use bash, zsh, or fish.`);
|
|
3922
|
+
process.exit(ExitCode.CONFIG_ERROR);
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3925
|
+
await tabtab__default.default.install({
|
|
3926
|
+
name: PROGRAM_NAME,
|
|
3927
|
+
completer: PROGRAM_NAME,
|
|
3928
|
+
shell
|
|
3929
|
+
});
|
|
3930
|
+
log("");
|
|
3931
|
+
success("Shell completion installed!");
|
|
3932
|
+
log("");
|
|
3933
|
+
info("Restart your shell or run:");
|
|
3934
|
+
if (shell === "bash" || !shell) {
|
|
3935
|
+
log(" source ~/.bashrc");
|
|
3936
|
+
}
|
|
3937
|
+
if (shell === "zsh" || !shell) {
|
|
3938
|
+
log(" source ~/.zshrc");
|
|
3939
|
+
}
|
|
3940
|
+
if (shell === "fish" || !shell) {
|
|
3941
|
+
log(" source ~/.config/fish/config.fish");
|
|
3942
|
+
}
|
|
3943
|
+
log("");
|
|
3944
|
+
} catch (err) {
|
|
3945
|
+
error(`Failed to install completion: ${err instanceof Error ? err.message : String(err)}`);
|
|
3946
|
+
process.exit(ExitCode.CONFIG_ERROR);
|
|
3947
|
+
}
|
|
3948
|
+
});
|
|
3949
|
+
completionCommand.command("uninstall").description("Uninstall shell completion").action(async () => {
|
|
3950
|
+
try {
|
|
3951
|
+
await tabtab__default.default.uninstall({
|
|
3952
|
+
name: PROGRAM_NAME
|
|
3953
|
+
});
|
|
3954
|
+
log("");
|
|
3955
|
+
success("Shell completion uninstalled!");
|
|
3956
|
+
log("");
|
|
3957
|
+
} catch (err) {
|
|
3958
|
+
error(`Failed to uninstall completion: ${err instanceof Error ? err.message : String(err)}`);
|
|
3959
|
+
process.exit(ExitCode.CONFIG_ERROR);
|
|
3960
|
+
}
|
|
3961
|
+
});
|
|
3962
|
+
completionCommand.command("server", { hidden: true }).description("Completion server (internal)").action(async () => {
|
|
3963
|
+
const env = tabtab__default.default.parseEnv(process.env);
|
|
3964
|
+
if (env.complete) {
|
|
3965
|
+
await getCompletions(env);
|
|
3966
|
+
}
|
|
3967
|
+
});
|
|
3594
3968
|
function hasVersion(data) {
|
|
3595
3969
|
return typeof data === "object" && data !== null && "version" in data && // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
3596
3970
|
typeof data.version === "string";
|
|
@@ -3634,7 +4008,19 @@ program.addCommand(sealCommand);
|
|
|
3634
4008
|
program.addCommand(identityCommand);
|
|
3635
4009
|
program.addCommand(teamCommand);
|
|
3636
4010
|
program.addCommand(whoamiCommand);
|
|
4011
|
+
program.addCommand(completionCommand);
|
|
4012
|
+
function processHomeDirOption() {
|
|
4013
|
+
const homeDirIndex = process.argv.indexOf("--home-dir");
|
|
4014
|
+
if (homeDirIndex !== -1 && homeDirIndex + 1 < process.argv.length) {
|
|
4015
|
+
const homeDir = process.argv[homeDirIndex + 1];
|
|
4016
|
+
if (homeDir && !homeDir.startsWith("-")) {
|
|
4017
|
+
core.setAttestItHomeDir(homeDir);
|
|
4018
|
+
process.argv.splice(homeDirIndex, 2);
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
4021
|
+
}
|
|
3637
4022
|
async function run() {
|
|
4023
|
+
processHomeDirOption();
|
|
3638
4024
|
if (process.argv.includes("--version") || process.argv.includes("-V")) {
|
|
3639
4025
|
console.log(getPackageVersion());
|
|
3640
4026
|
process.exit(0);
|