@tinycloud/cli 0.3.1 → 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 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 OpenKey").option("--paste", "Use manual paste mode instead of browser callback").action(async (options, cmd) => {
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
- const key = await ProfileManager.getKey(ctx.profile);
723
- if (!key) {
724
- throw new CLIError(
725
- "NO_KEY",
726
- `No key found for profile "${ctx.profile}". Run \`tc init\` first.`,
727
- ExitCode.AUTH_REQUIRED
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
- const profile = await ProfileManager.getProfile(ctx.profile);
731
- const delegationData = await startAuthFlow(profile.did, {
732
- paste: options.paste,
733
- jwk: key,
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
- if (!key) {
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 createInterface2 } from "readline";
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 = createInterface2({ input: process.stdin, output: process.stderr });
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 PrivateKeySigner(privateKey);
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 PrivateKeySigner2 } from "@tinycloud/node-sdk";
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 PrivateKeySigner2(privateKey);
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);
@@ -2484,6 +2653,63 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
2484
2653
  });
2485
2654
  }
2486
2655
 
2656
+ // src/commands/upgrade.ts
2657
+ import { execSync as execSync2 } from "child_process";
2658
+ import { createRequire } from "module";
2659
+ var PACKAGE_NAME = "@tinycloud/cli";
2660
+ function getCurrentVersion() {
2661
+ const require2 = createRequire(import.meta.url);
2662
+ const pkg = require2("../../package.json");
2663
+ return pkg.version;
2664
+ }
2665
+ async function getLatestVersion() {
2666
+ const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`);
2667
+ if (!res.ok) {
2668
+ throw new Error(`Failed to fetch latest version: ${res.status} ${res.statusText}`);
2669
+ }
2670
+ const data = await res.json();
2671
+ return data.version;
2672
+ }
2673
+ function detectPackageManager() {
2674
+ try {
2675
+ const bunGlobals = execSync2("bun pm ls -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
2676
+ if (bunGlobals.includes(PACKAGE_NAME)) {
2677
+ return "bun";
2678
+ }
2679
+ } catch {
2680
+ }
2681
+ try {
2682
+ const npmGlobals = execSync2("npm ls -g --depth=0", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
2683
+ if (npmGlobals.includes(PACKAGE_NAME)) {
2684
+ return "npm";
2685
+ }
2686
+ } catch {
2687
+ }
2688
+ return "bun";
2689
+ }
2690
+ function registerUpgradeCommand(program2) {
2691
+ program2.command("upgrade").description("Upgrade the TinyCloud CLI to the latest version").action(async () => {
2692
+ try {
2693
+ const current = getCurrentVersion();
2694
+ process.stderr.write(theme.muted("Checking for updates...") + "\n");
2695
+ const latest = await getLatestVersion();
2696
+ if (current === latest) {
2697
+ process.stdout.write(theme.success(`Already on latest version (${current})`) + "\n");
2698
+ return;
2699
+ }
2700
+ process.stdout.write(`Current: ${theme.warn(current)} \u2192 Latest: ${theme.success(latest)}
2701
+ `);
2702
+ const pm = detectPackageManager();
2703
+ const cmd = pm === "bun" ? `bun install -g ${PACKAGE_NAME}@latest` : `npm install -g ${PACKAGE_NAME}@latest`;
2704
+ process.stderr.write(theme.muted(`Upgrading via ${pm}...`) + "\n\n");
2705
+ execSync2(cmd, { stdio: "inherit" });
2706
+ process.stdout.write("\n" + theme.success(`Upgraded to ${latest}`) + "\n");
2707
+ } catch (error) {
2708
+ handleError(error);
2709
+ }
2710
+ });
2711
+ }
2712
+
2487
2713
  // src/index.ts
2488
2714
  var program = new Command();
2489
2715
  program.name("tc").description("TinyCloud CLI \u2014 self-sovereign storage from the terminal").version("0.1.0").option("-p, --profile <name>", "Profile to use").option("-H, --host <url>", "TinyCloud node URL").option("-v, --verbose", "Enable verbose output").option("--no-cache", "Disable caching").option("-q, --quiet", "Suppress non-essential output").option("--json", "Force JSON output");
@@ -2495,7 +2721,7 @@ program.hook("preAction", async (thisCommand) => {
2495
2721
  const commandName = thisCommand.name();
2496
2722
  const parentName = thisCommand.parent?.name();
2497
2723
  const fullCommand = parentName && parentName !== "tc" ? `${parentName} ${commandName}` : commandName;
2498
- const skipGuard = ["tc", "init", "doctor", "completion", "help"].includes(commandName) || fullCommand === "profile create";
2724
+ const skipGuard = ["tc", "init", "doctor", "completion", "help", "upgrade"].includes(commandName) || fullCommand === "profile create";
2499
2725
  if (!skipGuard && !opts.quiet && isInteractive()) {
2500
2726
  try {
2501
2727
  const config = await ProfileManager.getConfig();
@@ -2528,6 +2754,7 @@ registerVarsCommand(program);
2528
2754
  registerDoctorCommand(program);
2529
2755
  registerSqlCommand(program);
2530
2756
  registerDuckdbCommand(program);
2757
+ registerUpgradeCommand(program);
2531
2758
  program.addHelpText("afterAll", () => {
2532
2759
  if (!process.stdout.isTTY) return "";
2533
2760
  return `