@tinycloud/cli 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +205 -36
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -462,6 +462,8 @@ var ProfileManager = class _ProfileManager {
|
|
|
462
462
|
|
|
463
463
|
// src/auth/local-key.ts
|
|
464
464
|
import { TCWSessionManager, initPanicHook } from "@tinycloud/node-sdk-wasm";
|
|
465
|
+
import { PrivateKeySigner } from "@tinycloud/node-sdk";
|
|
466
|
+
import { randomBytes } from "crypto";
|
|
465
467
|
var wasmInitialized = false;
|
|
466
468
|
function ensureWasm() {
|
|
467
469
|
if (!wasmInitialized) {
|
|
@@ -479,6 +481,38 @@ function generateKey() {
|
|
|
479
481
|
const did = mgr.getDID(keyId);
|
|
480
482
|
return { jwk, did };
|
|
481
483
|
}
|
|
484
|
+
function generateEthereumPrivateKey() {
|
|
485
|
+
const keyBytes = randomBytes(32);
|
|
486
|
+
return "0x" + keyBytes.toString("hex");
|
|
487
|
+
}
|
|
488
|
+
async function deriveAddress(privateKey) {
|
|
489
|
+
const signer = new PrivateKeySigner(privateKey);
|
|
490
|
+
return signer.getAddress();
|
|
491
|
+
}
|
|
492
|
+
function addressToDID(address, chainId = 1) {
|
|
493
|
+
return `did:pkh:eip155:${chainId}:${address}`;
|
|
494
|
+
}
|
|
495
|
+
async function generateLocalIdentity(chainId = 1) {
|
|
496
|
+
const privateKey = generateEthereumPrivateKey();
|
|
497
|
+
const address = await deriveAddress(privateKey);
|
|
498
|
+
const did = addressToDID(address, chainId);
|
|
499
|
+
return { privateKey, address, did };
|
|
500
|
+
}
|
|
501
|
+
async function localKeySignIn(options) {
|
|
502
|
+
const { TinyCloudNode: TinyCloudNode2 } = await import("@tinycloud/node-sdk");
|
|
503
|
+
const node = new TinyCloudNode2({
|
|
504
|
+
privateKey: options.privateKey,
|
|
505
|
+
host: options.host,
|
|
506
|
+
autoCreateSpace: true
|
|
507
|
+
});
|
|
508
|
+
await node.signIn();
|
|
509
|
+
const address = await new PrivateKeySigner(options.privateKey).getAddress();
|
|
510
|
+
return {
|
|
511
|
+
spaceId: node.spaceId ?? "",
|
|
512
|
+
address,
|
|
513
|
+
chainId: 1
|
|
514
|
+
};
|
|
515
|
+
}
|
|
482
516
|
|
|
483
517
|
// src/auth/browser-auth.ts
|
|
484
518
|
import { createServer } from "http";
|
|
@@ -713,40 +747,57 @@ function registerInitCommand(program2) {
|
|
|
713
747
|
}
|
|
714
748
|
|
|
715
749
|
// src/commands/auth.ts
|
|
750
|
+
import { createInterface as createInterface2 } from "readline";
|
|
751
|
+
async function promptAuthMethod() {
|
|
752
|
+
if (!isInteractive()) {
|
|
753
|
+
return "local";
|
|
754
|
+
}
|
|
755
|
+
const rl = createInterface2({
|
|
756
|
+
input: process.stdin,
|
|
757
|
+
output: process.stderr
|
|
758
|
+
});
|
|
759
|
+
return new Promise((resolve3) => {
|
|
760
|
+
process.stderr.write("\n" + theme.heading("Choose authentication method:") + "\n");
|
|
761
|
+
process.stderr.write(` ${theme.accent("1)")} OpenKey ${theme.muted("(browser-based, for interactive use)")}
|
|
762
|
+
`);
|
|
763
|
+
process.stderr.write(` ${theme.accent("2)")} Local key ${theme.muted("(Ethereum private key, for agents/CI)")}
|
|
764
|
+
|
|
765
|
+
`);
|
|
766
|
+
rl.question("Enter choice [1]: ", (answer) => {
|
|
767
|
+
rl.close();
|
|
768
|
+
const trimmed = answer.trim();
|
|
769
|
+
if (trimmed === "2" || trimmed.toLowerCase() === "local") {
|
|
770
|
+
resolve3("local");
|
|
771
|
+
} else {
|
|
772
|
+
resolve3("openkey");
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
});
|
|
776
|
+
}
|
|
716
777
|
function registerAuthCommand(program2) {
|
|
717
778
|
const auth = program2.command("auth").description("Authentication management");
|
|
718
|
-
auth.command("login").description("Authenticate with
|
|
779
|
+
auth.command("login").description("Authenticate with TinyCloud").option("--paste", "Use manual paste mode instead of browser callback").option("--method <method>", "Authentication method: local or openkey").action(async (options, cmd) => {
|
|
719
780
|
try {
|
|
720
781
|
const globalOpts = cmd.optsWithGlobals();
|
|
721
782
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
722
|
-
|
|
723
|
-
if (
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
783
|
+
let method;
|
|
784
|
+
if (options.method) {
|
|
785
|
+
if (options.method !== "local" && options.method !== "openkey") {
|
|
786
|
+
throw new CLIError(
|
|
787
|
+
"INVALID_METHOD",
|
|
788
|
+
`Invalid auth method "${options.method}". Use "local" or "openkey".`,
|
|
789
|
+
ExitCode.USAGE_ERROR
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
method = options.method;
|
|
793
|
+
} else {
|
|
794
|
+
method = await promptAuthMethod();
|
|
729
795
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
host: ctx.host
|
|
735
|
-
});
|
|
736
|
-
await ProfileManager.setSession(ctx.profile, delegationData);
|
|
737
|
-
if (delegationData.spaceId) {
|
|
738
|
-
await ProfileManager.setProfile(ctx.profile, {
|
|
739
|
-
...profile,
|
|
740
|
-
spaceId: delegationData.spaceId,
|
|
741
|
-
primaryDid: delegationData.primaryDid
|
|
742
|
-
});
|
|
796
|
+
if (method === "local") {
|
|
797
|
+
await handleLocalAuth(ctx.profile, ctx.host);
|
|
798
|
+
} else {
|
|
799
|
+
await handleOpenKeyAuth(ctx.profile, ctx.host, options.paste);
|
|
743
800
|
}
|
|
744
|
-
outputJson({
|
|
745
|
-
authenticated: true,
|
|
746
|
-
profile: ctx.profile,
|
|
747
|
-
did: profile.did,
|
|
748
|
-
spaceId: delegationData.spaceId
|
|
749
|
-
});
|
|
750
801
|
} catch (error) {
|
|
751
802
|
handleError(error);
|
|
752
803
|
}
|
|
@@ -782,15 +833,19 @@ function registerAuthCommand(program2) {
|
|
|
782
833
|
spaceId: profile?.spaceId ?? null,
|
|
783
834
|
host: ctx.host,
|
|
784
835
|
profile: ctx.profile,
|
|
785
|
-
hasKey: hasKey !== null
|
|
836
|
+
hasKey: hasKey !== null,
|
|
837
|
+
authMethod: profile?.authMethod ?? null,
|
|
838
|
+
address: profile?.address ?? null
|
|
786
839
|
});
|
|
787
840
|
} else {
|
|
788
841
|
process.stdout.write(theme.heading("Authentication Status") + "\n");
|
|
789
842
|
process.stdout.write(formatField("Profile", ctx.profile) + "\n");
|
|
790
843
|
process.stdout.write(formatField("Authenticated", authenticated) + "\n");
|
|
844
|
+
process.stdout.write(formatField("Auth Method", profile?.authMethod ?? null) + "\n");
|
|
791
845
|
process.stdout.write(formatField("Host", ctx.host) + "\n");
|
|
792
846
|
process.stdout.write(formatField("DID", profile?.did ?? null) + "\n");
|
|
793
847
|
process.stdout.write(formatField("Primary DID", profile?.primaryDid ?? null) + "\n");
|
|
848
|
+
process.stdout.write(formatField("Address", profile?.address ?? null) + "\n");
|
|
794
849
|
process.stdout.write(formatField("Space ID", profile?.spaceId ?? null) + "\n");
|
|
795
850
|
process.stdout.write(formatField("Has Key", hasKey !== null) + "\n");
|
|
796
851
|
}
|
|
@@ -812,13 +867,17 @@ function registerAuthCommand(program2) {
|
|
|
812
867
|
primaryDid: profile.primaryDid ?? null,
|
|
813
868
|
spaceId: profile.spaceId ?? null,
|
|
814
869
|
host: profile.host,
|
|
815
|
-
authenticated
|
|
870
|
+
authenticated,
|
|
871
|
+
authMethod: profile.authMethod ?? null,
|
|
872
|
+
address: profile.address ?? null
|
|
816
873
|
});
|
|
817
874
|
} else {
|
|
818
875
|
process.stdout.write(theme.heading("Identity") + "\n");
|
|
819
876
|
process.stdout.write(formatField("Profile", ctx.profile) + "\n");
|
|
820
877
|
process.stdout.write(formatField("DID", profile.did) + "\n");
|
|
821
878
|
process.stdout.write(formatField("Primary DID", profile.primaryDid ?? null) + "\n");
|
|
879
|
+
process.stdout.write(formatField("Auth Method", profile.authMethod ?? null) + "\n");
|
|
880
|
+
process.stdout.write(formatField("Address", profile.address ?? null) + "\n");
|
|
822
881
|
process.stdout.write(formatField("Space ID", profile.spaceId ?? null) + "\n");
|
|
823
882
|
process.stdout.write(formatField("Host", profile.host) + "\n");
|
|
824
883
|
process.stdout.write(formatField("Authenticated", authenticated) + "\n");
|
|
@@ -828,6 +887,103 @@ function registerAuthCommand(program2) {
|
|
|
828
887
|
}
|
|
829
888
|
});
|
|
830
889
|
}
|
|
890
|
+
async function handleLocalAuth(profileName, host) {
|
|
891
|
+
const profile = await ProfileManager.getProfile(profileName).catch(() => null);
|
|
892
|
+
let privateKey;
|
|
893
|
+
let address;
|
|
894
|
+
let did;
|
|
895
|
+
if (profile?.authMethod === "local" && profile.privateKey && profile.address) {
|
|
896
|
+
privateKey = profile.privateKey;
|
|
897
|
+
address = profile.address;
|
|
898
|
+
did = profile.did;
|
|
899
|
+
if (isInteractive()) {
|
|
900
|
+
process.stderr.write(theme.muted("Using existing local key") + "\n");
|
|
901
|
+
process.stderr.write(formatField("Address", address) + "\n");
|
|
902
|
+
}
|
|
903
|
+
} else {
|
|
904
|
+
const identity = await withSpinner("Generating Ethereum key...", async () => {
|
|
905
|
+
return generateLocalIdentity(DEFAULT_CHAIN_ID);
|
|
906
|
+
});
|
|
907
|
+
privateKey = identity.privateKey;
|
|
908
|
+
address = identity.address;
|
|
909
|
+
did = identity.did;
|
|
910
|
+
if (isInteractive()) {
|
|
911
|
+
process.stderr.write("\n" + theme.heading("Local Key Generated") + "\n");
|
|
912
|
+
process.stderr.write(formatField("Address", address) + "\n");
|
|
913
|
+
process.stderr.write(formatField("DID", did) + "\n\n");
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
const hasKey = await ProfileManager.getKey(profileName);
|
|
917
|
+
if (!hasKey) {
|
|
918
|
+
const { jwk } = await withSpinner("Generating session key...", async () => {
|
|
919
|
+
return generateKey();
|
|
920
|
+
});
|
|
921
|
+
await ProfileManager.setKey(profileName, jwk);
|
|
922
|
+
}
|
|
923
|
+
const sessionResult = await withSpinner("Signing in...", async () => {
|
|
924
|
+
return localKeySignIn({ privateKey, host });
|
|
925
|
+
});
|
|
926
|
+
await ProfileManager.setSession(profileName, {
|
|
927
|
+
authMethod: "local",
|
|
928
|
+
address,
|
|
929
|
+
chainId: DEFAULT_CHAIN_ID,
|
|
930
|
+
spaceId: sessionResult.spaceId
|
|
931
|
+
});
|
|
932
|
+
await ProfileManager.setProfile(profileName, {
|
|
933
|
+
name: profileName,
|
|
934
|
+
host,
|
|
935
|
+
chainId: DEFAULT_CHAIN_ID,
|
|
936
|
+
spaceName: "default",
|
|
937
|
+
did,
|
|
938
|
+
primaryDid: did,
|
|
939
|
+
spaceId: sessionResult.spaceId,
|
|
940
|
+
createdAt: profile?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
941
|
+
authMethod: "local",
|
|
942
|
+
privateKey,
|
|
943
|
+
address
|
|
944
|
+
});
|
|
945
|
+
outputJson({
|
|
946
|
+
authenticated: true,
|
|
947
|
+
profile: profileName,
|
|
948
|
+
did,
|
|
949
|
+
address,
|
|
950
|
+
spaceId: sessionResult.spaceId,
|
|
951
|
+
authMethod: "local"
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
async function handleOpenKeyAuth(profileName, host, paste) {
|
|
955
|
+
const key = await ProfileManager.getKey(profileName);
|
|
956
|
+
if (!key) {
|
|
957
|
+
throw new CLIError(
|
|
958
|
+
"NO_KEY",
|
|
959
|
+
`No key found for profile "${profileName}". Run \`tc init\` first.`,
|
|
960
|
+
ExitCode.AUTH_REQUIRED
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
const profile = await ProfileManager.getProfile(profileName);
|
|
964
|
+
const delegationData = await startAuthFlow(profile.did, {
|
|
965
|
+
paste,
|
|
966
|
+
jwk: key,
|
|
967
|
+
host
|
|
968
|
+
});
|
|
969
|
+
await ProfileManager.setSession(profileName, delegationData);
|
|
970
|
+
const updatedProfile = {
|
|
971
|
+
...profile,
|
|
972
|
+
authMethod: "openkey"
|
|
973
|
+
};
|
|
974
|
+
if (delegationData.spaceId) {
|
|
975
|
+
updatedProfile.spaceId = delegationData.spaceId;
|
|
976
|
+
updatedProfile.primaryDid = delegationData.primaryDid;
|
|
977
|
+
}
|
|
978
|
+
await ProfileManager.setProfile(profileName, updatedProfile);
|
|
979
|
+
outputJson({
|
|
980
|
+
authenticated: true,
|
|
981
|
+
profile: profileName,
|
|
982
|
+
did: profile.did,
|
|
983
|
+
spaceId: delegationData.spaceId,
|
|
984
|
+
authMethod: "openkey"
|
|
985
|
+
});
|
|
986
|
+
}
|
|
831
987
|
|
|
832
988
|
// src/commands/kv.ts
|
|
833
989
|
import { readFile as readFile2 } from "fs/promises";
|
|
@@ -839,13 +995,22 @@ async function createSDKInstance(ctx, options) {
|
|
|
839
995
|
const profile = await ProfileManager.getProfile(ctx.profile);
|
|
840
996
|
const session = await ProfileManager.getSession(ctx.profile);
|
|
841
997
|
const key = await ProfileManager.getKey(ctx.profile);
|
|
842
|
-
|
|
998
|
+
const effectivePrivateKey = options?.privateKey ?? profile.privateKey;
|
|
999
|
+
if (!key && !effectivePrivateKey) {
|
|
843
1000
|
throw new CLIError(
|
|
844
1001
|
"AUTH_REQUIRED",
|
|
845
1002
|
`No key found for profile "${ctx.profile}". Run \`tc init\` first.`,
|
|
846
1003
|
ExitCode.AUTH_REQUIRED
|
|
847
1004
|
);
|
|
848
1005
|
}
|
|
1006
|
+
if (profile.authMethod === "local" && effectivePrivateKey) {
|
|
1007
|
+
const node2 = new TinyCloudNode({
|
|
1008
|
+
host: ctx.host,
|
|
1009
|
+
privateKey: effectivePrivateKey
|
|
1010
|
+
});
|
|
1011
|
+
await node2.signIn();
|
|
1012
|
+
return node2;
|
|
1013
|
+
}
|
|
849
1014
|
const node = new TinyCloudNode({
|
|
850
1015
|
host: ctx.host,
|
|
851
1016
|
privateKey: options?.privateKey
|
|
@@ -866,6 +1031,10 @@ async function createSDKInstance(ctx, options) {
|
|
|
866
1031
|
return node;
|
|
867
1032
|
}
|
|
868
1033
|
async function ensureAuthenticated(ctx, options) {
|
|
1034
|
+
const profile = await ProfileManager.getProfile(ctx.profile).catch(() => null);
|
|
1035
|
+
if (profile?.authMethod === "local" && profile.privateKey) {
|
|
1036
|
+
return createSDKInstance(ctx, { privateKey: profile.privateKey });
|
|
1037
|
+
}
|
|
869
1038
|
const session = await ProfileManager.getSession(ctx.profile);
|
|
870
1039
|
if (!session) {
|
|
871
1040
|
throw new CLIError(
|
|
@@ -1394,7 +1563,7 @@ function registerNodeCommand(program2) {
|
|
|
1394
1563
|
}
|
|
1395
1564
|
|
|
1396
1565
|
// src/commands/profile.ts
|
|
1397
|
-
import { createInterface as
|
|
1566
|
+
import { createInterface as createInterface3 } from "readline";
|
|
1398
1567
|
function registerProfileCommand(program2) {
|
|
1399
1568
|
const profile = program2.command("profile").description("Profile management");
|
|
1400
1569
|
profile.command("list").description("List all profiles").action(async (_options, cmd) => {
|
|
@@ -1504,7 +1673,7 @@ function registerProfileCommand(program2) {
|
|
|
1504
1673
|
profile.command("delete <name>").description("Delete a profile").action(async (name, _options, cmd) => {
|
|
1505
1674
|
try {
|
|
1506
1675
|
if (isInteractive()) {
|
|
1507
|
-
const rl =
|
|
1676
|
+
const rl = createInterface3({ input: process.stdin, output: process.stderr });
|
|
1508
1677
|
const answer = await new Promise((resolve3) => {
|
|
1509
1678
|
rl.question(`Delete profile "${name}"? This cannot be undone. [y/N] `, resolve3);
|
|
1510
1679
|
});
|
|
@@ -1655,7 +1824,7 @@ complete -c tc -l quiet -s q -d "Suppress non-essential output"
|
|
|
1655
1824
|
// src/commands/vault.ts
|
|
1656
1825
|
import { readFile as readFile3 } from "fs/promises";
|
|
1657
1826
|
import { writeFile as writeFile3 } from "fs/promises";
|
|
1658
|
-
import { PrivateKeySigner } from "@tinycloud/node-sdk";
|
|
1827
|
+
import { PrivateKeySigner as PrivateKeySigner2 } from "@tinycloud/node-sdk";
|
|
1659
1828
|
async function readStdin2() {
|
|
1660
1829
|
const chunks = [];
|
|
1661
1830
|
for await (const chunk of process.stdin) {
|
|
@@ -1675,7 +1844,7 @@ function resolvePrivateKey(options) {
|
|
|
1675
1844
|
return key;
|
|
1676
1845
|
}
|
|
1677
1846
|
async function unlockVault(node, privateKey) {
|
|
1678
|
-
const signer = new
|
|
1847
|
+
const signer = new PrivateKeySigner2(privateKey);
|
|
1679
1848
|
const result = await node.vault.unlock(signer);
|
|
1680
1849
|
if (result && !result.ok) {
|
|
1681
1850
|
throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
|
|
@@ -1828,7 +1997,7 @@ function registerVaultCommand(program2) {
|
|
|
1828
1997
|
// src/commands/secrets.ts
|
|
1829
1998
|
import { readFile as readFile4 } from "fs/promises";
|
|
1830
1999
|
import { writeFile as writeFile4 } from "fs/promises";
|
|
1831
|
-
import { PrivateKeySigner as
|
|
2000
|
+
import { PrivateKeySigner as PrivateKeySigner3 } from "@tinycloud/node-sdk";
|
|
1832
2001
|
var SECRETS_PREFIX = "secrets/";
|
|
1833
2002
|
async function readStdin3() {
|
|
1834
2003
|
const chunks = [];
|
|
@@ -1849,7 +2018,7 @@ function resolvePrivateKey2(options) {
|
|
|
1849
2018
|
return key;
|
|
1850
2019
|
}
|
|
1851
2020
|
async function unlockVault2(node, privateKey) {
|
|
1852
|
-
const signer = new
|
|
2021
|
+
const signer = new PrivateKeySigner3(privateKey);
|
|
1853
2022
|
const result = await node.vault.unlock(signer);
|
|
1854
2023
|
if (result && !result.ok) {
|
|
1855
2024
|
throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
|