@europeum-ebsi/cli 0.0.0 → 1.0.0-rc.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/LICENSE.txt +289 -289
- package/LICENSES.txt +538 -0
- package/README.md +1140 -98
- package/dist/api/client.d.ts +4 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +14 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/fetch.d.ts +13 -0
- package/dist/api/fetch.d.ts.map +1 -0
- package/dist/api/fetch.js +30 -0
- package/dist/api/fetch.js.map +1 -0
- package/dist/api/params.d.ts +4 -0
- package/dist/api/params.d.ts.map +1 -0
- package/dist/api/params.js +9 -0
- package/dist/api/params.js.map +1 -0
- package/dist/auth/dcql.d.ts +5 -0
- package/dist/auth/dcql.d.ts.map +1 -0
- package/dist/auth/dcql.js +41 -0
- package/dist/auth/dcql.js.map +1 -0
- package/dist/auth/presentation.d.ts +30 -0
- package/dist/auth/presentation.d.ts.map +1 -0
- package/dist/auth/presentation.js +99 -0
- package/dist/auth/presentation.js.map +1 -0
- package/dist/auth/token.d.ts +11 -0
- package/dist/auth/token.d.ts.map +1 -0
- package/dist/auth/token.js +116 -0
- package/dist/auth/token.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +96 -10
- package/dist/cli.js.map +1 -1
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +99 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/config-cmd.d.ts +3 -0
- package/dist/commands/config-cmd.d.ts.map +1 -0
- package/dist/commands/config-cmd.js +86 -0
- package/dist/commands/config-cmd.js.map +1 -0
- package/dist/commands/contracts-registry.d.ts +3 -0
- package/dist/commands/contracts-registry.d.ts.map +1 -0
- package/dist/commands/contracts-registry.js +132 -0
- package/dist/commands/contracts-registry.js.map +1 -0
- package/dist/commands/did-registry.d.ts +3 -0
- package/dist/commands/did-registry.d.ts.map +1 -0
- package/dist/commands/did-registry.js +110 -0
- package/dist/commands/did-registry.js.map +1 -0
- package/dist/commands/faucet.d.ts +3 -0
- package/dist/commands/faucet.d.ts.map +1 -0
- package/dist/commands/faucet.js +78 -0
- package/dist/commands/faucet.js.map +1 -0
- package/dist/commands/health.d.ts +3 -0
- package/dist/commands/health.d.ts.map +1 -0
- package/dist/commands/health.js +20 -0
- package/dist/commands/health.js.map +1 -0
- package/dist/commands/issuers-registry.d.ts +3 -0
- package/dist/commands/issuers-registry.d.ts.map +1 -0
- package/dist/commands/issuers-registry.js +340 -0
- package/dist/commands/issuers-registry.js.map +1 -0
- package/dist/commands/ledger.d.ts +3 -0
- package/dist/commands/ledger.d.ts.map +1 -0
- package/dist/commands/ledger.js +549 -372
- package/dist/commands/ledger.js.map +1 -1
- package/dist/commands/onboarding.d.ts +3 -0
- package/dist/commands/onboarding.d.ts.map +1 -0
- package/dist/commands/onboarding.js +301 -0
- package/dist/commands/onboarding.js.map +1 -0
- package/dist/commands/openapi.d.ts +3 -0
- package/dist/commands/openapi.d.ts.map +1 -0
- package/dist/commands/openapi.js +37 -0
- package/dist/commands/openapi.js.map +1 -0
- package/dist/commands/policies-registry.d.ts +3 -0
- package/dist/commands/policies-registry.d.ts.map +1 -0
- package/dist/commands/policies-registry.js +198 -0
- package/dist/commands/policies-registry.js.map +1 -0
- package/dist/commands/registry.d.ts +27 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +62 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/commands/schemas-registry.d.ts +3 -0
- package/dist/commands/schemas-registry.d.ts.map +1 -0
- package/dist/commands/schemas-registry.js +231 -0
- package/dist/commands/schemas-registry.js.map +1 -0
- package/dist/commands/shared/args.d.ts +2 -0
- package/dist/commands/shared/args.d.ts.map +1 -0
- package/dist/commands/shared/args.js +4 -0
- package/dist/commands/shared/args.js.map +1 -0
- package/dist/commands/shared/wallet-opts.d.ts +9 -0
- package/dist/commands/shared/wallet-opts.d.ts.map +1 -0
- package/dist/commands/shared/wallet-opts.js +44 -0
- package/dist/commands/shared/wallet-opts.js.map +1 -0
- package/dist/commands/timestamp.d.ts +3 -0
- package/dist/commands/timestamp.d.ts.map +1 -0
- package/dist/commands/timestamp.js +250 -0
- package/dist/commands/timestamp.js.map +1 -0
- package/dist/commands/track-and-trace.d.ts +3 -0
- package/dist/commands/track-and-trace.d.ts.map +1 -0
- package/dist/commands/track-and-trace.js +235 -0
- package/dist/commands/track-and-trace.js.map +1 -0
- package/dist/commands/wallet.d.ts +3 -0
- package/dist/commands/wallet.d.ts.map +1 -0
- package/dist/commands/wallet.js +123 -0
- package/dist/commands/wallet.js.map +1 -0
- package/dist/config/config.d.ts +45 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +111 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/ebsi-env.d.ts +4 -0
- package/dist/config/ebsi-env.d.ts.map +1 -0
- package/dist/config/ebsi-env.js +37 -0
- package/dist/config/ebsi-env.js.map +1 -0
- package/dist/crypto/wallet.d.ts +34 -0
- package/dist/crypto/wallet.d.ts.map +1 -0
- package/dist/crypto/wallet.js +100 -0
- package/dist/crypto/wallet.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -6
- package/dist/index.js.map +1 -1
- package/dist/ledger/read.d.ts +11 -0
- package/dist/ledger/read.d.ts.map +1 -0
- package/dist/ledger/read.js +24 -0
- package/dist/ledger/read.js.map +1 -0
- package/dist/ledger/registries.d.ts +25 -0
- package/dist/ledger/registries.d.ts.map +1 -0
- package/dist/ledger/registries.js +111 -0
- package/dist/ledger/registries.js.map +1 -0
- package/dist/ledger/write.d.ts +43 -0
- package/dist/ledger/write.d.ts.map +1 -0
- package/dist/ledger/write.js +119 -0
- package/dist/ledger/write.js.map +1 -0
- package/dist/onboarding/vc.d.ts +18 -0
- package/dist/onboarding/vc.d.ts.map +1 -0
- package/dist/onboarding/vc.js +72 -0
- package/dist/onboarding/vc.js.map +1 -0
- package/dist/output/format.d.ts +7 -0
- package/dist/output/format.d.ts.map +1 -0
- package/dist/output/format.js +94 -0
- package/dist/output/format.js.map +1 -0
- package/dist/tui/App.d.ts +3 -0
- package/dist/tui/App.d.ts.map +1 -0
- package/dist/tui/App.js +99 -0
- package/dist/tui/App.js.map +1 -0
- package/dist/tui/CommandPalette.d.ts +8 -0
- package/dist/tui/CommandPalette.d.ts.map +1 -0
- package/dist/tui/CommandPalette.js +105 -0
- package/dist/tui/CommandPalette.js.map +1 -0
- package/package.json +66 -108
- package/src/api/client.ts +26 -0
- package/src/api/fetch.ts +53 -0
- package/src/api/params.ts +17 -0
- package/src/auth/dcql.ts +84 -0
- package/src/auth/presentation.ts +255 -0
- package/src/auth/token.ts +244 -0
- package/src/cli.ts +164 -0
- package/src/commands/auth.ts +119 -0
- package/src/commands/config-cmd.ts +122 -0
- package/src/commands/contracts-registry.ts +156 -0
- package/src/commands/did-registry.ts +126 -0
- package/src/commands/faucet.ts +112 -0
- package/src/commands/health.ts +21 -0
- package/src/commands/issuers-registry.ts +385 -0
- package/src/commands/ledger.ts +777 -0
- package/src/commands/onboarding.ts +411 -0
- package/src/commands/openapi.ts +42 -0
- package/src/commands/policies-registry.ts +218 -0
- package/src/commands/registry.ts +152 -0
- package/src/commands/schemas-registry.ts +255 -0
- package/src/commands/shared/args.ts +14 -0
- package/src/commands/shared/wallet-opts.ts +85 -0
- package/src/commands/timestamp.ts +272 -0
- package/src/commands/track-and-trace.ts +263 -0
- package/src/commands/wallet.ts +175 -0
- package/src/config/config.ts +275 -0
- package/src/config/ebsi-env.ts +67 -0
- package/src/crypto/wallet.ts +291 -0
- package/src/index.ts +24 -0
- package/src/ledger/read.ts +70 -0
- package/src/ledger/registries.ts +285 -0
- package/src/ledger/write.ts +381 -0
- package/src/onboarding/vc.ts +205 -0
- package/src/output/format.ts +157 -0
- package/src/tui/App.tsx +282 -0
- package/src/tui/CommandPalette.tsx +223 -0
- package/bin/cli.js +0 -13
- package/dist/abi/pilot/DidRegistry.js +0 -568
- package/dist/abi/pilot/DidRegistry.js.map +0 -1
- package/dist/abi/pilot/Ptr.js +0 -666
- package/dist/abi/pilot/Ptr.js.map +0 -1
- package/dist/abi/pilot/SchemaSCRegistry.js +0 -264
- package/dist/abi/pilot/SchemaSCRegistry.js.map +0 -1
- package/dist/abi/pilot/Tcr.js +0 -715
- package/dist/abi/pilot/Tcr.js.map +0 -1
- package/dist/abi/pilot/Timestamp.js +0 -658
- package/dist/abi/pilot/Timestamp.js.map +0 -1
- package/dist/abi/pilot/Tir.js +0 -428
- package/dist/abi/pilot/Tir.js.map +0 -1
- package/dist/abi/pilot/TnT.js +0 -842
- package/dist/abi/pilot/TnT.js.map +0 -1
- package/dist/abi/pilot/Tpr.js +0 -492
- package/dist/abi/pilot/Tpr.js.map +0 -1
- package/dist/abi/test/DidRegistry.js +0 -568
- package/dist/abi/test/DidRegistry.js.map +0 -1
- package/dist/abi/test/Ptr.js +0 -666
- package/dist/abi/test/Ptr.js.map +0 -1
- package/dist/abi/test/SchemaSCRegistry.js +0 -300
- package/dist/abi/test/SchemaSCRegistry.js.map +0 -1
- package/dist/abi/test/Tcr.js +0 -715
- package/dist/abi/test/Tcr.js.map +0 -1
- package/dist/abi/test/Timestamp.js +0 -658
- package/dist/abi/test/Timestamp.js.map +0 -1
- package/dist/abi/test/Tir.js +0 -428
- package/dist/abi/test/Tir.js.map +0 -1
- package/dist/abi/test/TnT.js +0 -842
- package/dist/abi/test/TnT.js.map +0 -1
- package/dist/abi/test/Tpr.js +0 -492
- package/dist/abi/test/Tpr.js.map +0 -1
- package/dist/app.js +0 -659
- package/dist/app.js.map +0 -1
- package/dist/buildParam/didr.js +0 -374
- package/dist/buildParam/didr.js.map +0 -1
- package/dist/buildParam/index.js +0 -53
- package/dist/buildParam/index.js.map +0 -1
- package/dist/buildParam/ptr.js +0 -80
- package/dist/buildParam/ptr.js.map +0 -1
- package/dist/buildParam/tcr.js +0 -36
- package/dist/buildParam/tcr.js.map +0 -1
- package/dist/buildParam/timestamp.js +0 -350
- package/dist/buildParam/timestamp.js.map +0 -1
- package/dist/buildParam/tir.js +0 -110
- package/dist/buildParam/tir.js.map +0 -1
- package/dist/buildParam/tnt.js +0 -214
- package/dist/buildParam/tnt.js.map +0 -1
- package/dist/buildParam/tpr.js +0 -91
- package/dist/buildParam/tpr.js.map +0 -1
- package/dist/buildParam/tsr.js +0 -115
- package/dist/buildParam/tsr.js.map +0 -1
- package/dist/commands/authorisation.js +0 -144
- package/dist/commands/authorisation.js.map +0 -1
- package/dist/commands/compute.js +0 -680
- package/dist/commands/compute.js.map +0 -1
- package/dist/commands/conformance.js +0 -813
- package/dist/commands/conformance.js.map +0 -1
- package/dist/commands/hardwarewallet.js +0 -41
- package/dist/commands/hardwarewallet.js.map +0 -1
- package/dist/commands/index.js +0 -8
- package/dist/commands/index.js.map +0 -1
- package/dist/commands/ptr.js +0 -54
- package/dist/commands/ptr.js.map +0 -1
- package/dist/commands/tcr.js +0 -50
- package/dist/commands/tcr.js.map +0 -1
- package/dist/commands/tir.js +0 -64
- package/dist/commands/tir.js.map +0 -1
- package/dist/commands/tnl.js +0 -81
- package/dist/commands/tnl.js.map +0 -1
- package/dist/commands/tsr.js +0 -45
- package/dist/commands/tsr.js.map +0 -1
- package/dist/commands/view.js +0 -53
- package/dist/commands/view.js.map +0 -1
- package/dist/config.js +0 -497
- package/dist/config.js.map +0 -1
- package/dist/interfaces/context.js +0 -2
- package/dist/interfaces/context.js.map +0 -1
- package/dist/interfaces/contracts/ProxyFactory.js +0 -2
- package/dist/interfaces/contracts/ProxyFactory.js.map +0 -1
- package/dist/interfaces/contracts/ProxyTemplateRegistry.js +0 -2
- package/dist/interfaces/contracts/ProxyTemplateRegistry.js.map +0 -1
- package/dist/interfaces/contracts/SchemaSCRegistry.js +0 -2
- package/dist/interfaces/contracts/SchemaSCRegistry.js.map +0 -1
- package/dist/interfaces/contracts/common.js +0 -2
- package/dist/interfaces/contracts/common.js.map +0 -1
- package/dist/interfaces/index.js +0 -5
- package/dist/interfaces/index.js.map +0 -1
- package/dist/interfaces/jsonrpc.interface.js +0 -2
- package/dist/interfaces/jsonrpc.interface.js.map +0 -1
- package/dist/interfaces/paginated-list.interface.js +0 -2
- package/dist/interfaces/paginated-list.interface.js.map +0 -1
- package/dist/interfaces/unsigned-transaction.interface.js +0 -2
- package/dist/interfaces/unsigned-transaction.interface.js.map +0 -1
- package/dist/interfaces/utils.interface.js +0 -2
- package/dist/interfaces/utils.interface.js.map +0 -1
- package/dist/programs/migrateDid.js +0 -159
- package/dist/programs/migrateDid.js.map +0 -1
- package/dist/programs/migrateDids.js +0 -232
- package/dist/programs/migrateDids.js.map +0 -1
- package/dist/programs/migrateTsr.js +0 -201
- package/dist/programs/migrateTsr.js.map +0 -1
- package/dist/scripts/accreditAndAuthorize/conformance/step1 +0 -34
- package/dist/scripts/accreditAndAuthorize/conformance/step2 +0 -18
- package/dist/scripts/accreditAndAuthorize/conformance/step3 +0 -9
- package/dist/scripts/accreditAndAuthorize/conformance/step4 +0 -27
- package/dist/scripts/accreditAndAuthorize/conformance/step5 +0 -13
- package/dist/scripts/accreditAndAuthorize/conformance/step6 +0 -14
- package/dist/scripts/accreditAndAuthorize/conformance/step7 +0 -7
- package/dist/scripts/accreditAndAuthorize/test/step1 +0 -34
- package/dist/scripts/accreditAndAuthorize/test/step2 +0 -18
- package/dist/scripts/accreditAndAuthorize/test/step3 +0 -9
- package/dist/scripts/accreditAndAuthorize/test/step4 +0 -27
- package/dist/scripts/accreditAndAuthorize/test/step5 +0 -13
- package/dist/scripts/accreditAndAuthorize/test/step6 +0 -14
- package/dist/scripts/accreditAndAuthorize/test/step7 +0 -7
- package/dist/scripts/accreditTI +0 -21
- package/dist/scripts/assets/Nodes.json +0 -27
- package/dist/scripts/assets/vcdm1.1/CTRevocableCredential.json +0 -30
- package/dist/scripts/assets/vcdm1.1/CredentialToAttestVerifiableAuthorisationForTrustChain.json +0 -29
- package/dist/scripts/assets/vcdm1.1/Nodes.json +0 -27
- package/dist/scripts/assets/vcdm1.1/TrustedNodesList.json +0 -35
- package/dist/scripts/assets/vcdm1.1/VerifiableAccreditationToAccredit.json +0 -35
- package/dist/scripts/assets/vcdm1.1/VerifiableAccreditationToAttest.json +0 -33
- package/dist/scripts/assets/vcdm1.1/VerifiableAuthorisationForTrustChain.json +0 -22
- package/dist/scripts/assets/vcdm1.1/VerifiableAuthorisationToInvoke.json +0 -24
- package/dist/scripts/assets/vcdm1.1/VerifiableAuthorisationToOnboard.json +0 -24
- package/dist/scripts/assets/vcdm2.0/CredentialToAttestVerifiableAuthorisationForTrustChain.json +0 -29
- package/dist/scripts/assets/vcdm2.0/TrustedNodesList.json +0 -36
- package/dist/scripts/assets/vcdm2.0/VerifiableAccreditationToAccredit.json +0 -40
- package/dist/scripts/assets/vcdm2.0/VerifiableAccreditationToAttest.json +0 -38
- package/dist/scripts/assets/vcdm2.0/VerifiableAuthorisationForTrustChain.json +0 -30
- package/dist/scripts/assets/vcdm2.0/VerifiableAuthorisationToInvoke.json +0 -24
- package/dist/scripts/assets/vcdm2.0/VerifiableAuthorisationToOnboard.json +0 -23
- package/dist/scripts/bootstrap/0a-loadTPROperator +0 -5
- package/dist/scripts/bootstrap/0b-loadAdmin +0 -8
- package/dist/scripts/bootstrap/1-populateTPR +0 -92
- package/dist/scripts/bootstrap/2-populateDID +0 -6
- package/dist/scripts/bootstrap/3-populateTimestamp +0 -9
- package/dist/scripts/bootstrap/4-populateTIR +0 -8
- package/dist/scripts/bootstrap/5-populateTSR +0 -85
- package/dist/scripts/bootstrap/6-setupConformanceIssuer +0 -35
- package/dist/scripts/bootstrap/README.md +0 -218
- package/dist/scripts/issueVcInvoke +0 -10
- package/dist/scripts/issueVcOnboard +0 -9
- package/dist/scripts/issueVcRootTAO +0 -11
- package/dist/scripts/issueVcTAO +0 -12
- package/dist/scripts/issueVcTI +0 -12
- package/dist/scripts/issueVcTnl +0 -32
- package/dist/scripts/issue_CTRevocableCredential +0 -12
- package/dist/scripts/issue_SelfAttestationSupportOffice +0 -10
- package/dist/scripts/issue_VerifiableAccreditationToAccredit +0 -11
- package/dist/scripts/issue_VerifiableAccreditationToAttest +0 -11
- package/dist/scripts/issue_VerifiableAuthorisationForTrustChain +0 -10
- package/dist/scripts/issue_VerifiableAuthorisationToOnboard +0 -9
- package/dist/scripts/preregisterIssuer +0 -6
- package/dist/scripts/registerDidDocument +0 -15
- package/dist/scripts/registerIssuer +0 -6
- package/dist/scripts/updateVcRootTAO +0 -10
- package/dist/scripts/updateVcTAO +0 -11
- package/dist/scripts/updateVcTI +0 -11
- package/dist/scripts/vcdm2.0/issueSelfAttestationSupportOffice +0 -10
- package/dist/scripts/vcdm2.0/issueVcInvoke +0 -12
- package/dist/scripts/vcdm2.0/issueVcOnboard +0 -11
- package/dist/scripts/vcdm2.0/issueVcRootTAO +0 -14
- package/dist/scripts/vcdm2.0/issueVcTAO +0 -15
- package/dist/scripts/vcdm2.0/issueVcTI +0 -15
- package/dist/scripts/vcdm2.0/issueVcTnl +0 -34
- package/dist/scripts/vcdm2.0/registerDidDocument +0 -15
- package/dist/scripts/vcdm2.0/updateVcRootTAO +0 -13
- package/dist/scripts/vcdm2.0/updateVcTAO +0 -14
- package/dist/scripts/vcdm2.0/updateVcTI +0 -14
- package/dist/scripts/vcdm2.0/verifyVcTnl +0 -10
- package/dist/scripts/verifyVcTnl +0 -10
- package/dist/scripts/wct/accreditAndAuthorize +0 -133
- package/dist/scripts/wct/accreditAndAuthorize2 +0 -89
- package/dist/scripts/wct/holderWallet +0 -39
- package/dist/scripts/wct/holderWallet2 +0 -39
- package/dist/scripts/wct/issueToHolder +0 -38
- package/dist/scripts/wct/issueToHolder2 +0 -38
- package/dist/scripts/wct/pda1 +0 -3
- package/dist/scripts/wct/verifier +0 -8
- package/dist/scripts/wct/verifier2 +0 -7
- package/dist/utils/Client.js +0 -155
- package/dist/utils/Client.js.map +0 -1
- package/dist/utils/HardwareWallet.js +0 -326
- package/dist/utils/HardwareWallet.js.map +0 -1
- package/dist/utils/http.js +0 -132
- package/dist/utils/http.js.map +0 -1
- package/dist/utils/index.js +0 -8
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/jsonrpc.js +0 -38
- package/dist/utils/jsonrpc.js.map +0 -1
- package/dist/utils/print.js +0 -56
- package/dist/utils/print.js.map +0 -1
- package/dist/utils/utils.js +0 -427
- package/dist/utils/utils.js.map +0 -1
- package/dist/utils/vcdm.js +0 -47
- package/dist/utils/vcdm.js.map +0 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
|
|
3
|
+
import type { OutputFormat } from "../config/config.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Adds terminal colours to a JSON string (keys=cyan, strings=green, numbers=yellow).
|
|
7
|
+
* @param isTTY Optional override — defaults to `process.stdout.isTTY`. Inject `true`
|
|
8
|
+
* in tests to bypass the TTY detection without mocking `process.stdout`.
|
|
9
|
+
*/
|
|
10
|
+
export function colouriseJson(
|
|
11
|
+
json: string,
|
|
12
|
+
isTTY = process.stdout.isTTY,
|
|
13
|
+
): string {
|
|
14
|
+
// Only colourise when stdout is a TTY
|
|
15
|
+
if (!isTTY) return json;
|
|
16
|
+
|
|
17
|
+
return json.replaceAll(
|
|
18
|
+
/"(?:\\u[\dA-Fa-f]{4}|\\[^u]|[^"\\])*"(?:\s*:)?|\b(?:true|false|null)\b|-?\d+(?:\.\d*)?(?:[Ee][+-]?\d+)?/g,
|
|
19
|
+
(match) => {
|
|
20
|
+
if (match.startsWith('"')) {
|
|
21
|
+
if (match.endsWith(":")) {
|
|
22
|
+
// Key
|
|
23
|
+
return pc.cyan(match);
|
|
24
|
+
}
|
|
25
|
+
// String value
|
|
26
|
+
return pc.green(match);
|
|
27
|
+
}
|
|
28
|
+
if (/true|false/.test(match)) return pc.magenta(match);
|
|
29
|
+
if (match.includes("null")) return pc.dim(match);
|
|
30
|
+
// Number
|
|
31
|
+
return pc.yellow(match);
|
|
32
|
+
},
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Formats an API response for display.
|
|
38
|
+
*
|
|
39
|
+
* - `json` → compact JSON (useful for piping)
|
|
40
|
+
* - `table` → key/value or array table
|
|
41
|
+
* - `pretty` → coloured, indented JSON (default on a TTY)
|
|
42
|
+
*/
|
|
43
|
+
export function formatOutput(data: unknown, format: OutputFormat): string {
|
|
44
|
+
switch (format) {
|
|
45
|
+
case "json": {
|
|
46
|
+
return JSON.stringify(data);
|
|
47
|
+
}
|
|
48
|
+
case "table": {
|
|
49
|
+
return formatTable(data);
|
|
50
|
+
}
|
|
51
|
+
default: {
|
|
52
|
+
return formatPretty(data);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Print an error message to stderr
|
|
59
|
+
*/
|
|
60
|
+
export function printError(message: string, detail?: unknown): void {
|
|
61
|
+
const prefix = pc.red("✖ Error:");
|
|
62
|
+
process.stderr.write(`${prefix} ${message}\n`);
|
|
63
|
+
if (detail === undefined) return;
|
|
64
|
+
|
|
65
|
+
const detailStr =
|
|
66
|
+
typeof detail === "string" ? detail : JSON.stringify(detail, undefined, 2);
|
|
67
|
+
process.stderr.write(pc.dim(detailStr) + "\n");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Print to stdout respecting the output format
|
|
72
|
+
*/
|
|
73
|
+
export function printResult(data: unknown, format: OutputFormat): void {
|
|
74
|
+
console.log(formatOutput(data, format));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Print a warning message to stderr
|
|
79
|
+
*/
|
|
80
|
+
export function printWarning(message: string): void {
|
|
81
|
+
const prefix = pc.yellow("⚠ Warning:");
|
|
82
|
+
process.stderr.write(`${prefix} ${message}\n`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function formatArrayTable(items: Record<string, unknown>[]): string {
|
|
86
|
+
if (items.length === 0) return pc.dim("(empty)");
|
|
87
|
+
|
|
88
|
+
// Collect all keys across all items
|
|
89
|
+
const keys = [...new Set(items.flatMap((item) => Object.keys(item)))];
|
|
90
|
+
const colWidths = keys.map((k) =>
|
|
91
|
+
Math.max(
|
|
92
|
+
k.length,
|
|
93
|
+
...items.map((item) => {
|
|
94
|
+
const val = item[k];
|
|
95
|
+
return (
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
97
|
+
(typeof val === "object" ? JSON.stringify(val) : String(val ?? ""))
|
|
98
|
+
.length
|
|
99
|
+
);
|
|
100
|
+
}),
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const headerCells = keys.map((k, i) => {
|
|
105
|
+
/* v8 ignore next -- colWidths has exactly one entry per key, same index */
|
|
106
|
+
return pc.bold(pc.cyan(k.padEnd(colWidths[i] ?? 0)));
|
|
107
|
+
});
|
|
108
|
+
const header = headerCells.join(" ");
|
|
109
|
+
const divider = colWidths.map((w) => "─".repeat(w)).join(" ");
|
|
110
|
+
|
|
111
|
+
const bodyRows = items.map((item) =>
|
|
112
|
+
keys
|
|
113
|
+
.map((k, i) => {
|
|
114
|
+
const val = item[k];
|
|
115
|
+
const str =
|
|
116
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
117
|
+
typeof val === "object" ? JSON.stringify(val) : String(val ?? "");
|
|
118
|
+
/* v8 ignore next -- colWidths has exactly one entry per key, same index */
|
|
119
|
+
return str.padEnd(colWidths[i] ?? 0);
|
|
120
|
+
})
|
|
121
|
+
.join(" "),
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
return [header, divider, ...bodyRows].join("\n");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function formatObjectTable(obj: Record<string, unknown>): string {
|
|
128
|
+
const rows = Object.entries(obj).map(([key, val]) => {
|
|
129
|
+
const valStr =
|
|
130
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
131
|
+
typeof val === "object" ? JSON.stringify(val) : String(val ?? "");
|
|
132
|
+
return [key, valStr] as [string, string];
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const keyWidth = Math.max(...rows.map(([k]) => k.length), 3);
|
|
136
|
+
const header =
|
|
137
|
+
pc.bold(pc.cyan("KEY".padEnd(keyWidth))) + " " + pc.bold(pc.cyan("VALUE"));
|
|
138
|
+
const divider = "─".repeat(keyWidth + 2 + 60);
|
|
139
|
+
const body = rows
|
|
140
|
+
.map(([k, v]) => pc.cyan(k.padEnd(keyWidth)) + " " + v)
|
|
141
|
+
.join("\n");
|
|
142
|
+
|
|
143
|
+
return [header, divider, body].join("\n");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function formatPretty(data: unknown): string {
|
|
147
|
+
return colouriseJson(JSON.stringify(data, undefined, 2));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function formatTable(data: unknown): string {
|
|
151
|
+
if (Array.isArray(data)) {
|
|
152
|
+
return formatArrayTable(data as Record<string, unknown>[]);
|
|
153
|
+
}
|
|
154
|
+
return data !== null && typeof data === "object"
|
|
155
|
+
? formatObjectTable(data as Record<string, unknown>)
|
|
156
|
+
: String(data);
|
|
157
|
+
}
|
package/src/tui/App.tsx
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { Box, Static, Text } from "ink";
|
|
2
|
+
import React, { useCallback, useState as useLocalState, useState } from "react";
|
|
3
|
+
|
|
4
|
+
import type { CommandArg, CommandDescriptor } from "../commands/registry.ts";
|
|
5
|
+
import type { CliConfig } from "../config/config.ts";
|
|
6
|
+
|
|
7
|
+
import { resolveConfig } from "../config/config.ts";
|
|
8
|
+
import { colouriseJson } from "../output/format.ts";
|
|
9
|
+
import { CommandPalette } from "./CommandPalette.tsx";
|
|
10
|
+
|
|
11
|
+
type AppResult =
|
|
12
|
+
| {
|
|
13
|
+
args: Record<string, string>;
|
|
14
|
+
cmd: CommandDescriptor;
|
|
15
|
+
data: unknown;
|
|
16
|
+
key: string;
|
|
17
|
+
kind: "ok";
|
|
18
|
+
}
|
|
19
|
+
| {
|
|
20
|
+
args: Record<string, string>;
|
|
21
|
+
cmd: CommandDescriptor;
|
|
22
|
+
key: string;
|
|
23
|
+
kind: "error";
|
|
24
|
+
message: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
interface ArgPromptProps {
|
|
28
|
+
arg: CommandArg;
|
|
29
|
+
onSubmit: (value: string) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface ArgsPhaseProps {
|
|
33
|
+
onNext: (next: Phase) => void;
|
|
34
|
+
onRun: (
|
|
35
|
+
cmd: CommandDescriptor,
|
|
36
|
+
args: Record<string, string>,
|
|
37
|
+
) => Promise<void>;
|
|
38
|
+
phase: ArgsPhaseState;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type ArgsPhaseState = Extract<Phase, { name: "args" }>;
|
|
42
|
+
|
|
43
|
+
type Phase =
|
|
44
|
+
| {
|
|
45
|
+
argIndex: number;
|
|
46
|
+
argsCollected: Record<string, string>;
|
|
47
|
+
cmd: CommandDescriptor;
|
|
48
|
+
name: "args";
|
|
49
|
+
}
|
|
50
|
+
| { cmd: CommandDescriptor; name: "running" }
|
|
51
|
+
| { name: "palette" };
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// ArgsPhase — collects required positional arguments one by one
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
import TextInput from "ink-text-input";
|
|
58
|
+
|
|
59
|
+
interface ResultBoxProps {
|
|
60
|
+
result: AppResult;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Root Ink application component.
|
|
65
|
+
*
|
|
66
|
+
* Results are printed via <Static> so they stay permanently in the terminal
|
|
67
|
+
* buffer and are never redrawn. Only the live palette section re-renders on
|
|
68
|
+
* each keypress, giving a stable layout with no layout shifts.
|
|
69
|
+
*/
|
|
70
|
+
export function App(): React.ReactElement {
|
|
71
|
+
const [phase, setPhase] = useState<Phase>({ name: "palette" });
|
|
72
|
+
const [results, setResults] = useState<AppResult[]>([]);
|
|
73
|
+
|
|
74
|
+
// resolveConfig can throw (e.g. the legacy EBSI_PRIVATE_KEY migration
|
|
75
|
+
// error) -- caught here rather than left to crash the whole Ink render,
|
|
76
|
+
// which has no error boundary of its own. `config` falls back to a dummy
|
|
77
|
+
// value so the hooks below stay unconditional (React's rules of hooks);
|
|
78
|
+
// it's never actually used, since a `configError` short-circuits the
|
|
79
|
+
// render before the palette (and `runCommand`) can run.
|
|
80
|
+
let config: CliConfig;
|
|
81
|
+
let configError: string | undefined;
|
|
82
|
+
try {
|
|
83
|
+
config = resolveConfig({});
|
|
84
|
+
} catch (error) {
|
|
85
|
+
configError = error instanceof Error ? error.message : "Unknown error";
|
|
86
|
+
config = { baseUrl: "", env: "test", output: "pretty" };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const runCommand = useCallback(
|
|
90
|
+
async (cmd: CommandDescriptor, args: Record<string, string>) => {
|
|
91
|
+
setPhase({ cmd, name: "running" });
|
|
92
|
+
try {
|
|
93
|
+
const data = await cmd.handler(args, {}, config);
|
|
94
|
+
setResults((prev) => [
|
|
95
|
+
...prev,
|
|
96
|
+
{ args, cmd, data, key: `${cmd.id}-${Date.now()}`, kind: "ok" },
|
|
97
|
+
]);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
const message =
|
|
100
|
+
error instanceof Error ? error.message : "Unknown error";
|
|
101
|
+
setResults((prev) => [
|
|
102
|
+
...prev,
|
|
103
|
+
{
|
|
104
|
+
args,
|
|
105
|
+
cmd,
|
|
106
|
+
key: `${cmd.id}-${Date.now()}-err`,
|
|
107
|
+
kind: "error",
|
|
108
|
+
message,
|
|
109
|
+
},
|
|
110
|
+
]);
|
|
111
|
+
}
|
|
112
|
+
setPhase({ name: "palette" });
|
|
113
|
+
},
|
|
114
|
+
[config],
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const handleSelect = useCallback(
|
|
118
|
+
(cmd: CommandDescriptor, _input: string) => {
|
|
119
|
+
const requiredArgs = cmd.args.filter((a) => a.required);
|
|
120
|
+
if (requiredArgs.length === 0) {
|
|
121
|
+
void runCommand(cmd, {});
|
|
122
|
+
} else {
|
|
123
|
+
setPhase({ argIndex: 0, argsCollected: {}, cmd, name: "args" });
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
[runCommand],
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
if (configError) {
|
|
130
|
+
return (
|
|
131
|
+
<Box flexDirection="column">
|
|
132
|
+
<Text bold color="red">
|
|
133
|
+
✖ Error resolving config
|
|
134
|
+
</Text>
|
|
135
|
+
<Text>{configError}</Text>
|
|
136
|
+
<Text dimColor>Fix this and restart the CLI.</Text>
|
|
137
|
+
</Box>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<>
|
|
143
|
+
{/*
|
|
144
|
+
* Static: each result is printed once and locked in the terminal buffer.
|
|
145
|
+
* Ink never re-renders these, so palette scrolling causes zero redraws here.
|
|
146
|
+
*/}
|
|
147
|
+
<Static items={results}>
|
|
148
|
+
{(result) => <ResultBox key={result.key} result={result} />}
|
|
149
|
+
</Static>
|
|
150
|
+
|
|
151
|
+
{/* Live area — the only part Ink redraws on each keypress */}
|
|
152
|
+
{phase.name === "palette" && (
|
|
153
|
+
<Box flexDirection="column">
|
|
154
|
+
<Box marginBottom={1}>
|
|
155
|
+
<Text bold color="cyan">
|
|
156
|
+
EBSI CLI
|
|
157
|
+
</Text>
|
|
158
|
+
<Text dimColor>{` ${config.env} · ${config.baseUrl}`}</Text>
|
|
159
|
+
</Box>
|
|
160
|
+
<CommandPalette onSelect={handleSelect} />
|
|
161
|
+
</Box>
|
|
162
|
+
)}
|
|
163
|
+
|
|
164
|
+
{phase.name === "args" && (
|
|
165
|
+
<ArgsPhase
|
|
166
|
+
onNext={(next) => setPhase(next)}
|
|
167
|
+
onRun={runCommand}
|
|
168
|
+
phase={phase}
|
|
169
|
+
/>
|
|
170
|
+
)}
|
|
171
|
+
|
|
172
|
+
{phase.name === "running" && (
|
|
173
|
+
<Box paddingX={1}>
|
|
174
|
+
<Text color="yellow">{"⠋ "}</Text>
|
|
175
|
+
<Text>{`Running ${phase.cmd.segments.join(" ")}…`}</Text>
|
|
176
|
+
</Box>
|
|
177
|
+
)}
|
|
178
|
+
</>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function ArgPrompt({ arg, onSubmit }: ArgPromptProps): React.ReactElement {
|
|
183
|
+
const [value, setValue] = useLocalState("");
|
|
184
|
+
return (
|
|
185
|
+
<Box>
|
|
186
|
+
<Text bold color="cyan">
|
|
187
|
+
{` ${arg.name}: `}
|
|
188
|
+
</Text>
|
|
189
|
+
<TextInput
|
|
190
|
+
onChange={setValue}
|
|
191
|
+
onSubmit={(v) => {
|
|
192
|
+
if (v.trim()) onSubmit(v.trim());
|
|
193
|
+
}}
|
|
194
|
+
placeholder={arg.example ?? `Enter ${arg.name}…`}
|
|
195
|
+
value={value}
|
|
196
|
+
/>
|
|
197
|
+
</Box>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function ArgsPhase({
|
|
202
|
+
onNext,
|
|
203
|
+
onRun,
|
|
204
|
+
phase,
|
|
205
|
+
}: ArgsPhaseProps): React.ReactElement {
|
|
206
|
+
const { argIndex, argsCollected, cmd } = phase;
|
|
207
|
+
const requiredArgs = cmd.args.filter((a) => a.required);
|
|
208
|
+
const currentArg = requiredArgs[argIndex];
|
|
209
|
+
|
|
210
|
+
if (!currentArg) {
|
|
211
|
+
// All args collected — kick off the run
|
|
212
|
+
void onRun(cmd, argsCollected);
|
|
213
|
+
return (
|
|
214
|
+
<Box paddingX={1}>
|
|
215
|
+
<Text color="cyan">Running…</Text>
|
|
216
|
+
</Box>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const handleArgSubmit = (value: string) => {
|
|
221
|
+
const next = { ...argsCollected, [currentArg.name]: value };
|
|
222
|
+
if (argIndex + 1 >= requiredArgs.length) {
|
|
223
|
+
void onRun(cmd, next);
|
|
224
|
+
} else {
|
|
225
|
+
onNext({
|
|
226
|
+
argIndex: argIndex + 1,
|
|
227
|
+
argsCollected: next,
|
|
228
|
+
cmd,
|
|
229
|
+
name: "args",
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<Box flexDirection="column" paddingX={1}>
|
|
236
|
+
<Text bold color="cyan">
|
|
237
|
+
{`› ${cmd.segments.join(" ")}`}
|
|
238
|
+
</Text>
|
|
239
|
+
{Object.entries(argsCollected).map(([k, v]) => (
|
|
240
|
+
<Text dimColor key={k}>
|
|
241
|
+
{` ${k}: ${v}`}
|
|
242
|
+
</Text>
|
|
243
|
+
))}
|
|
244
|
+
<ArgPrompt arg={currentArg} onSubmit={handleArgSubmit} />
|
|
245
|
+
</Box>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
// ResultBox — rendered via <Static>, so must be a standalone component
|
|
251
|
+
// ---------------------------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
function ResultBox({ result }: ResultBoxProps): React.ReactElement {
|
|
254
|
+
const { args, cmd } = result;
|
|
255
|
+
const argSuffix = cmd.args
|
|
256
|
+
.map((a) => args[a.name])
|
|
257
|
+
.filter(Boolean)
|
|
258
|
+
.join(" ");
|
|
259
|
+
const header = ` › ${cmd.segments.join(" ")}${argSuffix ? " " + argSuffix : ""} `;
|
|
260
|
+
const isOk = result.kind === "ok";
|
|
261
|
+
|
|
262
|
+
return (
|
|
263
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
264
|
+
<Text
|
|
265
|
+
backgroundColor={isOk ? "green" : "red"}
|
|
266
|
+
bold
|
|
267
|
+
color={isOk ? "black" : "white"}
|
|
268
|
+
>
|
|
269
|
+
{header}
|
|
270
|
+
</Text>
|
|
271
|
+
<Box paddingLeft={1} paddingTop={1}>
|
|
272
|
+
{isOk ? (
|
|
273
|
+
<Text>
|
|
274
|
+
{colouriseJson(JSON.stringify(result.data, undefined, 2))}
|
|
275
|
+
</Text>
|
|
276
|
+
) : (
|
|
277
|
+
<Text color="red">{"✖ " + result.message}</Text>
|
|
278
|
+
)}
|
|
279
|
+
</Box>
|
|
280
|
+
</Box>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { Box, Text, useInput } from "ink";
|
|
2
|
+
import TextInput from "ink-text-input";
|
|
3
|
+
import React, { useCallback, useRef, useState } from "react";
|
|
4
|
+
|
|
5
|
+
import type { CommandDescriptor } from "../commands/registry.ts";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
commandPath,
|
|
9
|
+
COMMANDS,
|
|
10
|
+
matchesQuery,
|
|
11
|
+
queryScore,
|
|
12
|
+
} from "../commands/registry.ts";
|
|
13
|
+
|
|
14
|
+
const MAX_VISIBLE = 8;
|
|
15
|
+
const HINT_COLOR = "gray";
|
|
16
|
+
const MATCH_COLOR = "cyan";
|
|
17
|
+
const TIP_COLOR = "yellow";
|
|
18
|
+
const SELECTED_BG = "blue";
|
|
19
|
+
|
|
20
|
+
interface CommandPaletteProps {
|
|
21
|
+
onSelect: (cmd: CommandDescriptor, input: string) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Interactive command palette rendered inside the Ink TUI.
|
|
26
|
+
*
|
|
27
|
+
* Features:
|
|
28
|
+
* - Live fuzzy-filter over all COMMANDS as the user types
|
|
29
|
+
* - Scrollable list (↑/↓) — all commands reachable, MAX_VISIBLE shown at once
|
|
30
|
+
* - Tab auto-completes the input with the currently selected suggestion path
|
|
31
|
+
* - Enter confirms the selection (or runs the first match if none highlighted)
|
|
32
|
+
*/
|
|
33
|
+
export function CommandPalette({
|
|
34
|
+
onSelect,
|
|
35
|
+
}: CommandPaletteProps): React.ReactElement {
|
|
36
|
+
const [input, setInput] = useState("");
|
|
37
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
38
|
+
const [scrollOffset, setScrollOffset] = useState(0);
|
|
39
|
+
// Incremented on each Tab completion to force TextInput remount, which
|
|
40
|
+
// reinitialises cursorOffset to value.length (end of the completed text).
|
|
41
|
+
const [inputKey, setInputKey] = useState(0);
|
|
42
|
+
|
|
43
|
+
const filtered = COMMANDS.filter((cmd) => matchesQuery(cmd, input)).toSorted(
|
|
44
|
+
(a: CommandDescriptor, b: CommandDescriptor) =>
|
|
45
|
+
queryScore(b, input) - queryScore(a, input),
|
|
46
|
+
);
|
|
47
|
+
const visible = filtered.slice(scrollOffset, scrollOffset + MAX_VISIBLE);
|
|
48
|
+
|
|
49
|
+
// When a special key overrides the input value, ink-text-input's onChange still
|
|
50
|
+
// fires for the same keypress (e.g. deleting one char, inserting 'u' for Ctrl+U).
|
|
51
|
+
// We intercept that call here and apply our value instead.
|
|
52
|
+
const inputOverride = useRef<null | string>(null);
|
|
53
|
+
|
|
54
|
+
const handleChange = useCallback((value: string) => {
|
|
55
|
+
if (inputOverride.current !== null) {
|
|
56
|
+
const override = inputOverride.current;
|
|
57
|
+
// eslint-disable-next-line unicorn/no-null
|
|
58
|
+
inputOverride.current = null;
|
|
59
|
+
setInput(override);
|
|
60
|
+
setSelectedIndex(0);
|
|
61
|
+
setScrollOffset(0);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
setInput(value);
|
|
65
|
+
setSelectedIndex(0);
|
|
66
|
+
setScrollOffset(0);
|
|
67
|
+
}, []);
|
|
68
|
+
|
|
69
|
+
useInput((char, key) => {
|
|
70
|
+
// Ctrl+U / ⌘Delete equivalent: clear the whole input.
|
|
71
|
+
// Must be checked before the filtered guard so it works even with no matches.
|
|
72
|
+
if (char === "u" && key.ctrl) {
|
|
73
|
+
inputOverride.current = "";
|
|
74
|
+
setInput("");
|
|
75
|
+
setSelectedIndex(0);
|
|
76
|
+
setScrollOffset(0);
|
|
77
|
+
setInputKey((k) => k + 1);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Alt/Option+Delete: remove the last word.
|
|
82
|
+
// Terminal.app sends \x17 (Ctrl+W); iTerm2 sends \x1b\x7f (meta+delete).
|
|
83
|
+
// Both sequences map to the readline "backward-kill-word" action.
|
|
84
|
+
if ((char === "w" && key.ctrl) || (key.meta && key.delete)) {
|
|
85
|
+
if (input.length > 0) {
|
|
86
|
+
const newValue = input.replace(/\S+\s*$/, "");
|
|
87
|
+
inputOverride.current = newValue;
|
|
88
|
+
setInput(newValue);
|
|
89
|
+
setSelectedIndex(0);
|
|
90
|
+
setScrollOffset(0);
|
|
91
|
+
setInputKey((k) => k + 1);
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (filtered.length === 0) return;
|
|
97
|
+
|
|
98
|
+
if (key.upArrow) {
|
|
99
|
+
const next = Math.max(0, selectedIndex - 1);
|
|
100
|
+
setSelectedIndex(next);
|
|
101
|
+
// Scroll window up if selection moved above the visible range
|
|
102
|
+
setScrollOffset((off) => Math.min(off, next));
|
|
103
|
+
} else if (key.downArrow) {
|
|
104
|
+
const next = Math.min(filtered.length - 1, selectedIndex + 1);
|
|
105
|
+
setSelectedIndex(next);
|
|
106
|
+
// Scroll window down if selection moved below the visible range
|
|
107
|
+
setScrollOffset((off) => Math.max(off, next - MAX_VISIBLE + 1));
|
|
108
|
+
} else if (key.tab) {
|
|
109
|
+
// Tab: advance one word at a time along the selected command path.
|
|
110
|
+
const selected = filtered[selectedIndex];
|
|
111
|
+
if (selected) {
|
|
112
|
+
const trimmed = input.trim();
|
|
113
|
+
const words = selected.segments;
|
|
114
|
+
// Find the smallest prefix of the command path that is longer than what
|
|
115
|
+
// the user has already typed — that is the next word to complete to.
|
|
116
|
+
let targetCount = 1;
|
|
117
|
+
for (let i = 0; i < words.length; i++) {
|
|
118
|
+
const prefix = words.slice(0, i + 1).join(" ");
|
|
119
|
+
if (prefix.length > trimmed.length) {
|
|
120
|
+
targetCount = i + 1;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
if (trimmed === prefix) {
|
|
124
|
+
targetCount = Math.min(i + 2, words.length);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
setInput(words.slice(0, targetCount).join(" ") + " ");
|
|
128
|
+
setSelectedIndex(0);
|
|
129
|
+
setScrollOffset(0);
|
|
130
|
+
setInputKey((k) => k + 1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const handleSubmit = useCallback(
|
|
136
|
+
(value: string) => {
|
|
137
|
+
const selected = filtered[selectedIndex] ?? filtered[0];
|
|
138
|
+
if (selected) {
|
|
139
|
+
onSelect(selected, value);
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
[filtered, onSelect, selectedIndex],
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const aboveCount = scrollOffset;
|
|
146
|
+
const belowCount = Math.max(0, filtered.length - scrollOffset - MAX_VISIBLE);
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<Box flexDirection="column" paddingX={1}>
|
|
150
|
+
{/* Search input */}
|
|
151
|
+
<Box marginBottom={1}>
|
|
152
|
+
<Text bold color={MATCH_COLOR}>
|
|
153
|
+
{"› "}
|
|
154
|
+
</Text>
|
|
155
|
+
<TextInput
|
|
156
|
+
key={inputKey}
|
|
157
|
+
onChange={handleChange}
|
|
158
|
+
onSubmit={handleSubmit}
|
|
159
|
+
placeholder="type a command… (↑↓ to navigate, Tab to complete, Enter to run)"
|
|
160
|
+
value={input}
|
|
161
|
+
/>
|
|
162
|
+
</Box>
|
|
163
|
+
|
|
164
|
+
{/* Suggestions */}
|
|
165
|
+
{filtered.length === 0 ? (
|
|
166
|
+
<Text color={HINT_COLOR} dimColor>
|
|
167
|
+
No matching commands. Try "health", "did-registry identifiers list", …
|
|
168
|
+
</Text>
|
|
169
|
+
) : (
|
|
170
|
+
<Box flexDirection="column">
|
|
171
|
+
{/* Always render — prevents layout shift when indicator appears/disappears */}
|
|
172
|
+
<Text color={HINT_COLOR} dimColor>
|
|
173
|
+
{aboveCount > 0 ? ` ↑ ${aboveCount} more` : " "}
|
|
174
|
+
</Text>
|
|
175
|
+
|
|
176
|
+
{visible.map((cmd, i) => {
|
|
177
|
+
const absoluteIndex = scrollOffset + i;
|
|
178
|
+
const isSelected = absoluteIndex === selectedIndex;
|
|
179
|
+
const path = commandPath(cmd);
|
|
180
|
+
const argHints = cmd.args
|
|
181
|
+
.map((a) => (a.required ? `<${a.name}>` : `[${a.name}]`))
|
|
182
|
+
.join(" ");
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<Box key={cmd.id} paddingX={1}>
|
|
186
|
+
<Text
|
|
187
|
+
{...(isSelected && { backgroundColor: SELECTED_BG })}
|
|
188
|
+
bold={isSelected}
|
|
189
|
+
color={isSelected ? "white" : MATCH_COLOR}
|
|
190
|
+
>
|
|
191
|
+
{(isSelected ? "● " : "○ ") + path}
|
|
192
|
+
{argHints ? " " : ""}
|
|
193
|
+
</Text>
|
|
194
|
+
{argHints && (
|
|
195
|
+
<Text color={HINT_COLOR} dimColor>
|
|
196
|
+
{argHints + " "}
|
|
197
|
+
</Text>
|
|
198
|
+
)}
|
|
199
|
+
<Text color={TIP_COLOR} dimColor>
|
|
200
|
+
{"— " + cmd.summary}
|
|
201
|
+
</Text>
|
|
202
|
+
</Box>
|
|
203
|
+
);
|
|
204
|
+
})}
|
|
205
|
+
|
|
206
|
+
{/* Always render — prevents layout shift when indicator appears/disappears */}
|
|
207
|
+
<Text color={HINT_COLOR} dimColor>
|
|
208
|
+
{belowCount > 0 ? ` ↓ ${belowCount} more` : " "}
|
|
209
|
+
</Text>
|
|
210
|
+
</Box>
|
|
211
|
+
)}
|
|
212
|
+
|
|
213
|
+
{/* Keyboard shortcut legend */}
|
|
214
|
+
<Box marginTop={1}>
|
|
215
|
+
<Text color={HINT_COLOR} dimColor>
|
|
216
|
+
{
|
|
217
|
+
"↑↓ navigate Tab complete Enter run ⌥⌫ del word ⌘⌫ clear ^C quit"
|
|
218
|
+
}
|
|
219
|
+
</Text>
|
|
220
|
+
</Box>
|
|
221
|
+
</Box>
|
|
222
|
+
);
|
|
223
|
+
}
|
package/bin/cli.js
DELETED