@tinycloud/cli 0.7.0-beta.1 → 0.7.0-beta.3

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
@@ -973,11 +973,19 @@ Open this URL in a browser to authenticate:
973
973
 
974
974
  // src/commands/init.ts
975
975
  function registerInitCommand(program2) {
976
- program2.command("init").description("Initialize a new TinyCloud profile").option("--name <profile>", "Profile name", "default").option("--key-only", "Only generate key, skip authentication").option("--host <url>", "TinyCloud node URL").option("--paste", "Use manual paste mode for authentication").option("--no-popup", "Print the OpenKey URL without opening a browser").action(async (options, cmd) => {
976
+ program2.command("init").description("Initialize a new TinyCloud profile").option("--name <profile>", "Profile name", "default").option("--key-only", "Only generate key, skip authentication").option("--host <url>", "TinyCloud node URL").option("--paste", "Use manual paste mode for authentication").option("--no-popup", "Print the OpenKey URL without opening a browser").option("--default-space <name>", "Default space used when --space is omitted (e.g. applications)").action(async (options, cmd) => {
977
977
  try {
978
978
  const globalOpts = cmd.optsWithGlobals();
979
979
  const profileName = options.name;
980
980
  const host = options.host ?? globalOpts.host ?? DEFAULT_HOST;
981
+ const defaultSpace = options.defaultSpace;
982
+ if (defaultSpace !== void 0 && !/^[A-Za-z0-9_-]+$/.test(defaultSpace)) {
983
+ throw new CLIError(
984
+ "INVALID_SPACE",
985
+ `Invalid --default-space "${defaultSpace}". Use a short name ([A-Za-z0-9_-]).`,
986
+ ExitCode.USAGE_ERROR
987
+ );
988
+ }
981
989
  if (await ProfileManager.profileExists(profileName)) {
982
990
  throw new CLIError(
983
991
  "PROFILE_EXISTS",
@@ -996,7 +1004,8 @@ function registerInitCommand(program2) {
996
1004
  chainId: DEFAULT_CHAIN_ID,
997
1005
  spaceName: "default",
998
1006
  did,
999
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
1007
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1008
+ ...defaultSpace ? { defaultSpace } : {}
1000
1009
  };
1001
1010
  await ProfileManager.setProfile(profileName, profileConfig);
1002
1011
  const config = await ProfileManager.getConfig();
@@ -5781,6 +5790,38 @@ var KVService = class extends BaseService {
5781
5790
  signal: this.combineSignals(signal)
5782
5791
  });
5783
5792
  }
5793
+ /**
5794
+ * Serialize a single put value into a fetch body.
5795
+ *
5796
+ * Binary values (Blob/ArrayBuffer/typed-array, incl. Node Buffer) are sent as
5797
+ * raw bytes (as a Blob) so they round-trip byte-identically — without this a
5798
+ * Buffer would be JSON.stringify'd into `{"type":"Buffer","data":[...]}`.
5799
+ * Strings are returned unchanged (preserving prior behavior); other values are
5800
+ * JSON-encoded. `contentType` overrides the inferred type for binary values.
5801
+ */
5802
+ serializePutValue(value, contentType) {
5803
+ if (value instanceof Blob) {
5804
+ if (!contentType || value.type === contentType) {
5805
+ return value;
5806
+ }
5807
+ return new Blob([value], { type: contentType });
5808
+ }
5809
+ if (value instanceof ArrayBuffer) {
5810
+ return new Blob([value], {
5811
+ type: contentType ?? "application/octet-stream"
5812
+ });
5813
+ }
5814
+ if (ArrayBuffer.isView(value)) {
5815
+ const view = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
5816
+ return new Blob([view], {
5817
+ type: contentType ?? "application/octet-stream"
5818
+ });
5819
+ }
5820
+ if (typeof value === "string") {
5821
+ return contentType ? new Blob([value], { type: contentType }) : value;
5822
+ }
5823
+ return JSON.stringify(value);
5824
+ }
5784
5825
  serializeBatchPutValue(item) {
5785
5826
  const contentType = item.contentType;
5786
5827
  if (item.value instanceof Blob) {
@@ -5850,10 +5891,13 @@ var KVService = class extends BaseService {
5850
5891
  * @param raw - Whether to return raw text
5851
5892
  * @returns Parsed data
5852
5893
  */
5853
- async parseResponse(response, raw = false) {
5894
+ async parseResponse(response, raw = false, binary = false) {
5854
5895
  if (!response.ok) {
5855
5896
  return void 0;
5856
5897
  }
5898
+ if (binary) {
5899
+ return new Uint8Array(await response.arrayBuffer());
5900
+ }
5857
5901
  if (raw) {
5858
5902
  return await response.text();
5859
5903
  }
@@ -5960,7 +6004,11 @@ var KVService = class extends BaseService {
5960
6004
  )
5961
6005
  );
5962
6006
  }
5963
- const data = await this.parseResponse(response, options?.raw);
6007
+ const data = await this.parseResponse(
6008
+ response,
6009
+ options?.raw,
6010
+ options?.binary
6011
+ );
5964
6012
  return ok({
5965
6013
  data,
5966
6014
  headers: this.createResponseHeaders(response.headers)
@@ -5979,12 +6027,7 @@ var KVService = class extends BaseService {
5979
6027
  return err(authRequiredError("kv"));
5980
6028
  }
5981
6029
  const path = this.getFullPath(key, options?.prefix);
5982
- let body;
5983
- if (typeof value === "string") {
5984
- body = value;
5985
- } else {
5986
- body = JSON.stringify(value);
5987
- }
6030
+ const body = this.serializePutValue(value, options?.contentType);
5988
6031
  try {
5989
6032
  const response = await this.invokeOperation(
5990
6033
  path,
@@ -10733,30 +10776,31 @@ function resolveChainId(profile, session) {
10733
10776
  return profile.chainId;
10734
10777
  }
10735
10778
  async function resolveSpaceUri(input, profileName) {
10736
- if (!input) return void 0;
10737
- if (input.startsWith("tinycloud:")) {
10738
- const parsed = parseSpaceUri(input);
10779
+ const profile = await ProfileManager.getProfile(profileName);
10780
+ const effective = input || profile.defaultSpace;
10781
+ if (!effective) return void 0;
10782
+ if (effective.startsWith("tinycloud:")) {
10783
+ const parsed = parseSpaceUri(effective);
10739
10784
  if (!parsed) {
10740
10785
  throw new CLIError(
10741
10786
  "INVALID_SPACE",
10742
- `Invalid --space "${input}". Use a short name ([A-Za-z0-9_-]) or a full tinycloud:... URI.`,
10787
+ `Invalid space "${effective}". Use a short name ([A-Za-z0-9_-]) or a full tinycloud:... URI.`,
10743
10788
  ExitCode.USAGE_ERROR
10744
10789
  );
10745
10790
  }
10746
10791
  return buildSpaceUri(parsed.owner, parsed.name);
10747
10792
  }
10748
- if (!/^[A-Za-z0-9_-]+$/.test(input)) {
10793
+ if (!/^[A-Za-z0-9_-]+$/.test(effective)) {
10749
10794
  throw new CLIError(
10750
10795
  "INVALID_SPACE",
10751
- `Invalid --space "${input}". Use a short name ([A-Za-z0-9_-]) or a full tinycloud:... URI.`,
10796
+ `Invalid space "${effective}". Use a short name ([A-Za-z0-9_-]) or a full tinycloud:... URI.`,
10752
10797
  ExitCode.USAGE_ERROR
10753
10798
  );
10754
10799
  }
10755
- const profile = await ProfileManager.getProfile(profileName);
10756
10800
  const session = await ProfileManager.getSession(profileName);
10757
10801
  const address = resolveAddress(profile, session);
10758
10802
  const chainId = resolveChainId(profile, session);
10759
- return makePkhSpaceId(address, chainId, input);
10803
+ return makePkhSpaceId(address, chainId, effective);
10760
10804
  }
10761
10805
 
10762
10806
  // src/lib/permissions.ts
@@ -11874,9 +11918,10 @@ function groupPermissionsBySpace(permissions) {
11874
11918
  rawEntries.push(permission);
11875
11919
  continue;
11876
11920
  }
11877
- const group = groups.get(permission.space) ?? [];
11921
+ const key = normalizeSpaceForCompare(permission.space);
11922
+ const group = groups.get(key) ?? [];
11878
11923
  group.push(permission);
11879
- groups.set(permission.space, group);
11924
+ groups.set(key, group);
11880
11925
  }
11881
11926
  const grouped = Array.from(groups.values());
11882
11927
  if (grouped.length === 0) {
@@ -11888,8 +11933,16 @@ function groupPermissionsBySpace(permissions) {
11888
11933
  function isRawPermission(permission) {
11889
11934
  return permission.service === "tinycloud.encryption" && permission.path.startsWith("urn:tinycloud:encryption:");
11890
11935
  }
11936
+ function normalizeSpaceForCompare(space) {
11937
+ return space.replace(
11938
+ /(eip155:\d+:)(0x[0-9a-fA-F]{40})/,
11939
+ (_match, prefix, addr) => prefix + addr.toLowerCase()
11940
+ );
11941
+ }
11891
11942
  function returnedSpaceMatchesExpected(returnedSpace, expectedSpace) {
11892
- if (returnedSpace === expectedSpace) return true;
11943
+ if (normalizeSpaceForCompare(returnedSpace) === normalizeSpaceForCompare(expectedSpace)) {
11944
+ return true;
11945
+ }
11893
11946
  if (!returnedSpace.startsWith("tinycloud:")) return false;
11894
11947
  const returnedName = returnedSpace.slice(returnedSpace.lastIndexOf(":") + 1);
11895
11948
  return returnedName === expectedSpace;
@@ -11898,7 +11951,7 @@ function portableFromOpenKeyDelegation(data, permissions, host) {
11898
11951
  const primary = permissions.find((permission) => !isRawPermission(permission)) ?? permissions[0];
11899
11952
  const returnedSpace = String(data.spaceId ?? primary.space ?? "encryption");
11900
11953
  const expectedSpaces = new Set(
11901
- permissions.filter((permission) => !isRawPermission(permission)).map((permission) => permission.space)
11954
+ permissions.filter((permission) => !isRawPermission(permission)).map((permission) => normalizeSpaceForCompare(permission.space))
11902
11955
  );
11903
11956
  const matchesExpectedSpace = expectedSpaces.size === 1 && returnedSpaceMatchesExpected(returnedSpace, Array.from(expectedSpaces)[0]);
11904
11957
  if (expectedSpaces.size > 0 && !matchesExpectedSpace) {
@@ -12144,7 +12197,11 @@ function registerKvCommand(program2) {
12144
12197
  const ctx = await ProfileManager.resolveContext(globalOpts);
12145
12198
  const node = await ensureAuthenticated(ctx);
12146
12199
  const kv2 = await kvHandle(node, options.space, ctx.profile);
12147
- const result = await withSpinner(`Getting ${key}...`, () => kv2.get(key));
12200
+ const wantBytes = !!options.output || !!options.raw;
12201
+ const result = await withSpinner(
12202
+ `Getting ${key}...`,
12203
+ () => kv2.get(key, wantBytes ? { binary: true } : void 0)
12204
+ );
12148
12205
  if (!result.ok) {
12149
12206
  if (result.error.code === "KV_NOT_FOUND" || result.error.code === "NOT_FOUND") {
12150
12207
  throw new CLIError("NOT_FOUND", `Key "${key}" not found`, ExitCode.NOT_FOUND);
@@ -12154,14 +12211,12 @@ function registerKvCommand(program2) {
12154
12211
  const data = result.data.data;
12155
12212
  const metadata = result.data.headers ?? {};
12156
12213
  if (options.output) {
12157
- const content = typeof data === "string" ? data : JSON.stringify(data);
12158
- await writeFile3(options.output, content);
12214
+ await writeFile3(options.output, data);
12159
12215
  outputJson({ key, written: options.output });
12160
12216
  return;
12161
12217
  }
12162
12218
  if (options.raw) {
12163
- const content = typeof data === "string" ? data : JSON.stringify(data);
12164
- process.stdout.write(content);
12219
+ process.stdout.write(data);
12165
12220
  return;
12166
12221
  }
12167
12222
  if (shouldOutputJson()) {
@@ -12178,7 +12233,7 @@ function registerKvCommand(program2) {
12178
12233
  handleError(error);
12179
12234
  }
12180
12235
  });
12181
- kv.command("put <key> [value]").description("Set a value").option("--file <path>", "Read value from file").option("--stdin", "Read value from stdin").action(async (key, value, options, cmd) => {
12236
+ kv.command("put <key> [value]").description("Set a value").option("--file <path>", "Read value from file").option("--stdin", "Read value from stdin").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").action(async (key, value, options, cmd) => {
12182
12237
  try {
12183
12238
  const globalOpts = cmd.optsWithGlobals();
12184
12239
  const ctx = await ProfileManager.resolveContext(globalOpts);
@@ -12202,7 +12257,8 @@ function registerKvCommand(program2) {
12202
12257
  putValue = value;
12203
12258
  }
12204
12259
  }
12205
- const result = await withSpinner(`Writing ${key}...`, () => node.kv.put(key, putValue));
12260
+ const kv2 = await kvHandle(node, options.space, ctx.profile);
12261
+ const result = await withSpinner(`Writing ${key}...`, () => kv2.put(key, putValue));
12206
12262
  if (!result.ok) {
12207
12263
  throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
12208
12264
  }
@@ -12211,12 +12267,13 @@ function registerKvCommand(program2) {
12211
12267
  handleError(error);
12212
12268
  }
12213
12269
  });
12214
- kv.command("delete <key>").description("Delete a key").action(async (key, _options, cmd) => {
12270
+ kv.command("delete <key>").description("Delete a key").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").action(async (key, options, cmd) => {
12215
12271
  try {
12216
12272
  const globalOpts = cmd.optsWithGlobals();
12217
12273
  const ctx = await ProfileManager.resolveContext(globalOpts);
12218
12274
  const node = await ensureAuthenticated(ctx);
12219
- const result = await withSpinner(`Deleting ${key}...`, () => node.kv.delete(key));
12275
+ const kv2 = await kvHandle(node, options.space, ctx.profile);
12276
+ const result = await withSpinner(`Deleting ${key}...`, () => kv2.delete(key));
12220
12277
  if (!result.ok) {
12221
12278
  throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
12222
12279
  }
@@ -12315,16 +12372,13 @@ function registerSpaceCommand(program2) {
12315
12372
  handleError(error);
12316
12373
  }
12317
12374
  });
12318
- space.command("create <name>").description("Create a new space").action(async (name, _options, cmd) => {
12375
+ space.command("create <name>").alias("host").description("Create (host) one of your owned spaces by name").action(async (name, _options, cmd) => {
12319
12376
  try {
12320
12377
  const globalOpts = cmd.optsWithGlobals();
12321
12378
  const ctx = await ProfileManager.resolveContext(globalOpts);
12322
12379
  const node = await ensureAuthenticated(ctx);
12323
- const result = await node.spaces.create(name);
12324
- if (!result.ok) {
12325
- throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
12326
- }
12327
- outputJson({ spaceId: result.data.id, name });
12380
+ const spaceId = await node.hostOwnedSpace(name);
12381
+ outputJson({ spaceId, name, hosted: true });
12328
12382
  } catch (error) {
12329
12383
  handleError(error);
12330
12384
  }
@@ -12776,6 +12830,7 @@ function registerProfileCommand(program2) {
12776
12830
  process.stdout.write(formatField("Posture", posture) + "\n");
12777
12831
  process.stdout.write(formatField("Operator", operatorType) + "\n");
12778
12832
  process.stdout.write(formatField("Space", p.spaceId || null) + "\n");
12833
+ process.stdout.write(formatField("Default Space", p.defaultSpace || null) + "\n");
12779
12834
  process.stdout.write(formatField("Key", hasKey) + "\n");
12780
12835
  process.stdout.write(formatField("Session", hasSession) + "\n");
12781
12836
  process.stdout.write(formatField("Created", p.createdAt) + "\n");
@@ -12796,6 +12851,46 @@ function registerProfileCommand(program2) {
12796
12851
  handleError(error);
12797
12852
  }
12798
12853
  });
12854
+ profile.command("set-default-space [name]").description("Set (or clear) the default space used when --space is omitted").option("--profile <name>", "Profile to modify (defaults to the active profile)").option("--unset", "Clear the default space so commands fall back to the primary space").addHelpText("after", `
12855
+
12856
+ The default space is a short space NAME (e.g. "applications"), resolved per
12857
+ profile at command time. Precedence for every kv/sql command:
12858
+ explicit --space flag > profile defaultSpace > primary space.
12859
+
12860
+ Examples:
12861
+ $ tc profile set-default-space applications
12862
+ $ tc profile set-default-space applications --profile cli-test
12863
+ $ tc profile set-default-space --unset
12864
+ `).action(async (name, options, cmd) => {
12865
+ try {
12866
+ const globalOpts = cmd.optsWithGlobals();
12867
+ const ctx = await ProfileManager.resolveContext({
12868
+ ...globalOpts,
12869
+ profile: options.profile ?? globalOpts.profile
12870
+ });
12871
+ const profileName = ctx.profile;
12872
+ if (!options.unset && (name === void 0 || name === "")) {
12873
+ throw new CLIError(
12874
+ "USAGE_ERROR",
12875
+ "Provide a space name (e.g. `tc profile set-default-space applications`) or pass --unset.",
12876
+ ExitCode.USAGE_ERROR
12877
+ );
12878
+ }
12879
+ if (!options.unset && !/^[A-Za-z0-9_-]+$/.test(name)) {
12880
+ throw new CLIError(
12881
+ "INVALID_SPACE",
12882
+ `Invalid space name "${name}". Use a short name ([A-Za-z0-9_-]).`,
12883
+ ExitCode.USAGE_ERROR
12884
+ );
12885
+ }
12886
+ const p = await ProfileManager.getProfile(profileName);
12887
+ const defaultSpace = options.unset ? void 0 : name;
12888
+ await ProfileManager.setProfile(profileName, { ...p, defaultSpace });
12889
+ outputJson({ profile: profileName, defaultSpace: defaultSpace ?? null, updated: true });
12890
+ } catch (error) {
12891
+ handleError(error);
12892
+ }
12893
+ });
12799
12894
  profile.command("delete <name>").description("Delete a profile").action(async (name, _options, cmd) => {
12800
12895
  try {
12801
12896
  if (isInteractive()) {