@erdoai/cli 0.4.0 → 0.5.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
@@ -293,26 +293,32 @@ var ErdoClient = class {
293
293
  runHeartbeat(id) {
294
294
  return this.request("POST", `/v1/heartbeats/${encodeURIComponent(id)}/run`, {});
295
295
  }
296
- // --- collections ---
297
- listCollections() {
298
- return this.request("GET", "/v1/collections");
296
+ // --- kv (named key/value stores, a.k.a. collections) ---
297
+ listKVStores() {
298
+ return this.request("GET", "/v1/kv");
299
299
  }
300
- createCollection(slug) {
301
- return this.request("POST", "/v1/collections", { slug });
300
+ createKVStore(slug) {
301
+ return this.request("POST", "/v1/kv", { slug });
302
302
  }
303
- getCollectionItem(slug, key) {
303
+ getKVItem(slug, key) {
304
304
  return this.request(
305
305
  "GET",
306
- `/v1/collections/${encodeURIComponent(slug)}/items/${encodeURIComponent(key)}`
306
+ `/v1/kv/${encodeURIComponent(slug)}/items/${encodeURIComponent(key)}`
307
307
  );
308
308
  }
309
- setCollectionItem(slug, key, value) {
309
+ setKVItem(slug, key, value) {
310
310
  return this.request(
311
311
  "PUT",
312
- `/v1/collections/${encodeURIComponent(slug)}/items/${encodeURIComponent(key)}`,
312
+ `/v1/kv/${encodeURIComponent(slug)}/items/${encodeURIComponent(key)}`,
313
313
  { value }
314
314
  );
315
315
  }
316
+ deleteKVItem(slug, key) {
317
+ return this.request(
318
+ "DELETE",
319
+ `/v1/kv/${encodeURIComponent(slug)}/items/${encodeURIComponent(key)}`
320
+ );
321
+ }
316
322
  // --- agent runs ---
317
323
  listAgentRuns(opts) {
318
324
  const params = new URLSearchParams();
@@ -438,19 +444,30 @@ async function browserLogin() {
438
444
  import { spawnSync } from "child_process";
439
445
  var PKG = "@erdoai/cli";
440
446
  var MANUAL_HINT = `npm install -g ${PKG}@latest`;
447
+ function parseVersion(v) {
448
+ const [core, pre = ""] = v.split("-", 2);
449
+ const nums = core.split(".").map((s) => Number.parseInt(s, 10) || 0);
450
+ return { nums, pre };
451
+ }
441
452
  function isNewer(latest, current) {
442
- const a = latest.split(".").map(Number);
443
- const b = current.split(".").map(Number);
453
+ const a = parseVersion(latest);
454
+ const b = parseVersion(current);
444
455
  for (let i = 0; i < 3; i++) {
445
- if ((a[i] ?? 0) > (b[i] ?? 0)) return true;
446
- if ((a[i] ?? 0) < (b[i] ?? 0)) return false;
447
- }
448
- return false;
456
+ const x = a.nums[i] ?? 0;
457
+ const y = b.nums[i] ?? 0;
458
+ if (x > y) return true;
459
+ if (x < y) return false;
460
+ }
461
+ if (!a.pre && b.pre) return true;
462
+ if (a.pre && !b.pre) return false;
463
+ return a.pre > b.pre;
449
464
  }
450
465
  async function update(current) {
451
466
  let latest;
452
467
  try {
453
- const res = await fetch(`https://registry.npmjs.org/${PKG}/latest`);
468
+ const res = await fetch(`https://registry.npmjs.org/${PKG}/latest`, {
469
+ signal: AbortSignal.timeout(1e4)
470
+ });
454
471
  if (res.ok) latest = (await res.json()).version;
455
472
  } catch {
456
473
  }
@@ -468,6 +485,10 @@ async function update(current) {
468
485
  // npm is npm.cmd on Windows; needs a shell to resolve.
469
486
  shell: process.platform === "win32"
470
487
  });
488
+ if (result.error) {
489
+ console.error(`Update failed: ${result.error.message}. Try manually: ${MANUAL_HINT}`);
490
+ process.exit(1);
491
+ }
471
492
  if (result.status !== 0) {
472
493
  console.error(`Update failed. Try manually: ${MANUAL_HINT}`);
473
494
  process.exit(result.status ?? 1);
@@ -945,7 +966,16 @@ function readMaybeFile(v) {
945
966
  if (!v) return void 0;
946
967
  return v.startsWith("@") ? readFileSync2(v.slice(1), "utf8") : v;
947
968
  }
948
- pagesCmd.command("deploy").description("Deploy a page (HTML/React); --html/--js/--css accept @file").requiredOption("--title <title>", "page title").requiredOption("--html <htmlOr@file>", "HTML (or @path to a file)").option("--js <jsOr@file>", "JS/JSX (or @path)").option("--css <cssOr@file>", "CSS (or @path)").option("--runtime <runtime>", "react-tailwind (default) or none").option("--datasets <csv>", "dataset slugs the page reads").option("--writable-datasets <csv>", "dataset slugs the page may write via erdo.insertRows").option("--collections <csv>", "named collection slugs the page reads").option("--writable-collections <csv>", "named collection slugs the page may write").option("--public", "make the page publicly viewable").action(
969
+ function grantList(v) {
970
+ if (v === void 0) return void 0;
971
+ if (v.trim() === "[]") return [];
972
+ const slugs = v.split(",").map((s) => s.trim()).filter(Boolean);
973
+ if (slugs.length === 0) {
974
+ throw new Error(`expected comma-separated slugs, or "[]" to clear \u2014 got ${JSON.stringify(v)}`);
975
+ }
976
+ return slugs;
977
+ }
978
+ pagesCmd.command("deploy").description("Deploy a page (HTML/React); --html/--js/--css accept @file").requiredOption("--title <title>", "page title").requiredOption("--html <htmlOr@file>", "HTML (or @path to a file)").option("--js <jsOr@file>", "JS/JSX (or @path)").option("--css <cssOr@file>", "CSS (or @path)").option("--runtime <runtime>", "react-tailwind (default) or none").option("--datasets <csv>", "dataset slugs the page reads").option("--writable-datasets <csv>", "dataset slugs the page may write via erdo.insertRows").option("--kv <csv>", "named KV-store slugs the page reads").option("--writable-kv <csv>", "named KV-store slugs the page may write").option("--public", "make the page publicly viewable").action(
949
979
  async (opts) => {
950
980
  try {
951
981
  const csv = (v) => v ? v.split(",").map((s) => s.trim()) : void 0;
@@ -957,8 +987,8 @@ pagesCmd.command("deploy").description("Deploy a page (HTML/React); --html/--js/
957
987
  runtime: opts.runtime,
958
988
  dataset_slugs: csv(opts.datasets),
959
989
  writable_dataset_slugs: csv(opts.writableDatasets),
960
- collection_slugs: csv(opts.collections),
961
- writable_collection_slugs: csv(opts.writableCollections),
990
+ kv_slugs: csv(opts.kv),
991
+ writable_kv_slugs: csv(opts.writableKv),
962
992
  public: !!opts.public
963
993
  });
964
994
  console.log(`${res.id} ${res.public_url || res.url}`);
@@ -968,6 +998,29 @@ pagesCmd.command("deploy").description("Deploy a page (HTML/React); --html/--js/
968
998
  }
969
999
  }
970
1000
  );
1001
+ pagesCmd.command("update <id>").description(
1002
+ "Update an existing page by id; provided fields are merged (update just --js to keep html/css). --html/--js/--css accept @file"
1003
+ ).option("--title <title>", "new title").option("--html <htmlOr@file>", "HTML (or @path to a file)").option("--js <jsOr@file>", "JS/JSX (or @path)").option("--css <cssOr@file>", "CSS (or @path)").option("--datasets <csv>", 'replacement read-dataset slugs ("[]" clears)').option("--writable-datasets <csv>", 'replacement writable-dataset slugs ("[]" clears)').option("--kv <csv>", 'replacement read KV-store slugs ("[]" clears)').option("--writable-kv <csv>", 'replacement writable KV-store slugs ("[]" clears)').option("--public", "make the page publicly viewable").option("--private", "make the page private").action(
1004
+ async (id, opts) => {
1005
+ try {
1006
+ const res = await new ErdoClient().updatePage(id, {
1007
+ title: opts.title,
1008
+ html: readMaybeFile(opts.html),
1009
+ js: readMaybeFile(opts.js),
1010
+ css: readMaybeFile(opts.css),
1011
+ dataset_slugs: grantList(opts.datasets),
1012
+ writable_dataset_slugs: grantList(opts.writableDatasets),
1013
+ kv_slugs: grantList(opts.kv),
1014
+ writable_kv_slugs: grantList(opts.writableKv),
1015
+ public: opts.public ? true : opts.private ? false : void 0
1016
+ });
1017
+ console.log(`${res.id} ${res.public_url || res.url}`);
1018
+ if (res.warning) console.error(`warning: ${res.warning}`);
1019
+ } catch (e) {
1020
+ fail(e);
1021
+ }
1022
+ }
1023
+ );
971
1024
  pagesCmd.command("validate").description("Validate page content without deploying").requiredOption("--html <htmlOr@file>", "HTML (or @path)").option("--js <jsOr@file>", "JS/JSX (or @path)").option("--css <cssOr@file>", "CSS (or @path)").option("--runtime <runtime>", "react-tailwind (default) or none").action(async (opts) => {
972
1025
  try {
973
1026
  print(
@@ -1044,29 +1097,29 @@ autoCmd.command("run <id>").description("Trigger an automation now").action(asyn
1044
1097
  fail(e);
1045
1098
  }
1046
1099
  });
1047
- var collCmd = program.command("collections").description("Named key/value collections");
1048
- collCmd.command("list").description("List collections").action(async () => {
1100
+ var kvCmd = program.command("kv").description("Named key/value stores");
1101
+ kvCmd.command("list").description("List KV stores").action(async () => {
1049
1102
  try {
1050
- print(await new ErdoClient().listCollections());
1103
+ print(await new ErdoClient().listKVStores());
1051
1104
  } catch (e) {
1052
1105
  fail(e);
1053
1106
  }
1054
1107
  });
1055
- collCmd.command("create <slug>").description("Create a named collection (slug: lowercase letters, digits, hyphens)").action(async (slug) => {
1108
+ kvCmd.command("create <slug>").description("Create a named KV store (slug: lowercase letters, digits, hyphens)").action(async (slug) => {
1056
1109
  try {
1057
- print(await new ErdoClient().createCollection(slug));
1110
+ print(await new ErdoClient().createKVStore(slug));
1058
1111
  } catch (e) {
1059
1112
  fail(e);
1060
1113
  }
1061
1114
  });
1062
- collCmd.command("get <slug> <key>").description("Get a collection item").action(async (slug, key) => {
1115
+ kvCmd.command("get <slug> <key>").description("Get a KV item").action(async (slug, key) => {
1063
1116
  try {
1064
- print(await new ErdoClient().getCollectionItem(slug, key));
1117
+ print(await new ErdoClient().getKVItem(slug, key));
1065
1118
  } catch (e) {
1066
1119
  fail(e);
1067
1120
  }
1068
1121
  });
1069
- collCmd.command("set <slug> <key> <jsonValue>").description("Set a collection item (value is parsed as JSON, else stored as string)").action(async (slug, key, jsonValue) => {
1122
+ kvCmd.command("set <slug> <key> <jsonValue>").description("Set a KV item (value is parsed as JSON, else stored as string)").action(async (slug, key, jsonValue) => {
1070
1123
  try {
1071
1124
  let value;
1072
1125
  try {
@@ -1074,7 +1127,14 @@ collCmd.command("set <slug> <key> <jsonValue>").description("Set a collection it
1074
1127
  } catch {
1075
1128
  value = jsonValue;
1076
1129
  }
1077
- print(await new ErdoClient().setCollectionItem(slug, key, value));
1130
+ print(await new ErdoClient().setKVItem(slug, key, value));
1131
+ } catch (e) {
1132
+ fail(e);
1133
+ }
1134
+ });
1135
+ kvCmd.command("delete <slug> <key>").description("Delete a KV item").action(async (slug, key) => {
1136
+ try {
1137
+ print(await new ErdoClient().deleteKVItem(slug, key));
1078
1138
  } catch (e) {
1079
1139
  fail(e);
1080
1140
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erdoai/cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Erdo CLI — drive datasets, pages, and evals from the terminal or CI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,19 +1,39 @@
1
1
  // Friendly getting-started hint, printed once after `npm install -g @erdoai/cli`.
2
2
  // Stays quiet in CI and non-interactive installs (and never fails the install).
3
- import { existsSync } from "node:fs";
3
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import { join } from "node:path";
4
6
 
5
7
  try {
6
8
  // In our own source checkout the package ships `dist` + this script but never
7
9
  // `src`, so a sibling `src/` means this is a dev `npm install` — stay quiet.
8
10
  const devCheckout = existsSync(new URL("../src", import.meta.url));
9
11
 
12
+ // Print once per machine: a sentinel under the CLI's config dir (same dir as
13
+ // config.json) suppresses the banner on later installs — notably the global
14
+ // reinstall `erdo update` performs, which would otherwise re-trigger it.
15
+ const configDir = join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "erdo");
16
+ const sentinel = join(configDir, ".welcomed");
17
+
10
18
  const quiet =
11
19
  devCheckout ||
20
+ // Only greet on a global install — a local/devDependency install must not
21
+ // print the banner or write the sentinel (which would silence the real
22
+ // `npm install -g` welcome later). npm sets this to "true" for `-g`.
23
+ process.env.npm_config_global !== "true" ||
12
24
  process.env.CI ||
13
25
  process.env.ERDO_NO_POSTINSTALL ||
14
- !process.stdout.isTTY;
26
+ !process.stdout.isTTY ||
27
+ existsSync(sentinel);
15
28
 
16
29
  if (!quiet) {
30
+ try {
31
+ mkdirSync(configDir, { recursive: true });
32
+ writeFileSync(sentinel, "");
33
+ } catch {
34
+ // a read-only home shouldn't suppress the hint or fail the install
35
+ }
36
+
17
37
  const c = (code, s) => `\x1b[${code}m${s}\x1b[0m`;
18
38
  const b = (s) => c("1", s);
19
39
  const dim = (s) => c("2", s);