@officexapp/catalogs-cli 0.1.0 → 0.2.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.
Files changed (3) hide show
  1. package/README.md +9 -5
  2. package/dist/index.js +158 -77
  3. package/package.json +5 -5
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @officexapp/catalogs-cli
2
2
 
3
- CLI for Catalog Funnel — upload videos, push catalog schemas, manage assets.
3
+ CLI for Catalog Kit — upload videos, push catalog schemas, manage assets.
4
4
 
5
5
  ## Setup
6
6
 
@@ -11,15 +11,19 @@ npm install -g @officexapp/catalogs-cli
11
11
  Configure authentication:
12
12
 
13
13
  ```bash
14
- export CATALOGS_API_URL="https://catalog-funnel-api-staging.cloud.zoomgtm.com"
15
- export CATALOGS_TOKEN="cfk_your_api_key_here"
14
+ export CATALOG_KIT_TOKEN="cfk_your_api_key_here"
16
15
  ```
17
16
 
18
- Or create `~/.catalogs-cli/config.json`:
17
+ The API URL defaults to `https://api.catalogkit.cc`. To override (e.g. for staging):
18
+
19
+ ```bash
20
+ export CATALOG_KIT_API_URL="https://api-staging.catalogkit.cc"
21
+ ```
22
+
23
+ Or create `~/.catalog-kit/config.json`:
19
24
 
20
25
  ```json
21
26
  {
22
- "api_url": "https://catalog-funnel-api-staging.cloud.zoomgtm.com",
23
27
  "token": "cfk_your_api_key_here"
24
28
  }
25
29
  ```
package/dist/index.js CHANGED
@@ -3,71 +3,33 @@
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
5
 
6
- // src/commands/video-upload.ts
7
- import { readFileSync as readFileSync2, statSync } from "fs";
8
- import { basename } from "path";
9
- import ora from "ora";
10
-
11
6
  // src/config.ts
12
- import { readFileSync, existsSync } from "fs";
13
- import { join } from "path";
14
- import { homedir } from "os";
15
- var CONFIG_DIR = join(homedir(), ".catalogs-cli");
16
- var CONFIG_FILE = join(CONFIG_DIR, "config.json");
7
+ var DEFAULT_API_URL = "https://api.catalogkit.cc";
8
+ var globalTokenOverride;
9
+ function setGlobalToken(token) {
10
+ globalTokenOverride = token;
11
+ }
17
12
  function getConfig() {
18
- const envUrl = process.env.CATALOGS_API_URL;
19
- const envToken = process.env.CATALOGS_TOKEN;
20
- if (envUrl && envToken) {
21
- return { api_url: envUrl, token: envToken };
22
- }
23
- if (existsSync(CONFIG_FILE)) {
24
- try {
25
- const raw = readFileSync(CONFIG_FILE, "utf-8");
26
- const parsed = JSON.parse(raw);
27
- return {
28
- api_url: envUrl || parsed.api_url || "",
29
- token: envToken || parsed.token || ""
30
- };
31
- } catch {
32
- }
33
- }
34
- const dotenv = join(process.cwd(), ".env");
35
- if (existsSync(dotenv)) {
36
- const lines = readFileSync(dotenv, "utf-8").split("\n");
37
- const env = {};
38
- for (const line of lines) {
39
- const match = line.match(/^([A-Z_]+)=["']?(.+?)["']?\s*$/);
40
- if (match) env[match[1]] = match[2];
41
- }
42
- if (env.CATALOGS_API_URL || env.CATALOGS_TOKEN) {
43
- return {
44
- api_url: envUrl || env.CATALOGS_API_URL || "",
45
- token: envToken || env.CATALOGS_TOKEN || ""
46
- };
47
- }
48
- }
49
- return {
50
- api_url: envUrl || "",
51
- token: envToken || ""
52
- };
13
+ const envUrl = process.env.CATALOG_KIT_API_URL;
14
+ const token = globalTokenOverride || process.env.CATALOG_KIT_TOKEN || "";
15
+ return { api_url: envUrl || DEFAULT_API_URL, token };
53
16
  }
54
17
  function requireConfig() {
55
18
  const config = getConfig();
56
- if (!config.api_url) {
57
- console.error(
58
- "Missing API URL. Set CATALOGS_API_URL env var or create ~/.catalogs-cli/config.json"
59
- );
60
- process.exit(1);
61
- }
62
19
  if (!config.token) {
63
20
  console.error(
64
- "Missing auth token. Set CATALOGS_TOKEN env var or create ~/.catalogs-cli/config.json"
21
+ '\nMissing auth token.\n\n Set the CATALOG_KIT_TOKEN environment variable:\n export CATALOG_KIT_TOKEN="cfk_..."\n\n Or pass --token on the command line:\n catalogs --token cfk_... catalog push schema.json\n'
65
22
  );
66
23
  process.exit(1);
67
24
  }
68
25
  return config;
69
26
  }
70
27
 
28
+ // src/commands/video-upload.ts
29
+ import { readFileSync, statSync } from "fs";
30
+ import { basename } from "path";
31
+ import ora from "ora";
32
+
71
33
  // src/api.ts
72
34
  var ApiClient = class {
73
35
  constructor(config) {
@@ -116,6 +78,26 @@ var ApiClient = class {
116
78
  }
117
79
  };
118
80
 
81
+ // src/lib/identity.ts
82
+ async function printIdentity(api) {
83
+ try {
84
+ const res = await api.get("/api/v1/me");
85
+ if (res.ok && res.data) {
86
+ const d = res.data;
87
+ const parts = [`Authenticated as ${d.subdomain || d.user_id}`];
88
+ if (d.email) parts.push(`(${d.email})`);
89
+ console.log(`
90
+ ${parts.join(" ")}`);
91
+ if (d.subdomain) {
92
+ console.log(` Subdomain: ${d.subdomain}.catalogkit.cc`);
93
+ }
94
+ console.log();
95
+ }
96
+ } catch {
97
+ console.warn("\n Warning: Could not verify identity (API unreachable)\n");
98
+ }
99
+ }
100
+
119
101
  // src/commands/video-upload.ts
120
102
  var MIME_MAP = {
121
103
  ".mp4": "video/mp4",
@@ -140,6 +122,7 @@ function formatBytes(bytes) {
140
122
  async function videoUpload(file, opts) {
141
123
  const config = requireConfig();
142
124
  const api = new ApiClient(config);
125
+ await printIdentity(api);
143
126
  const skipTranscode = opts.transcode === false;
144
127
  let stat;
145
128
  try {
@@ -175,7 +158,7 @@ File: ${filename} (${formatBytes(sizeBytes)})`);
175
158
  }
176
159
  const uploadSpinner = ora("Uploading to S3...").start();
177
160
  try {
178
- const fileBuffer = readFileSync2(file);
161
+ const fileBuffer = readFileSync(file);
179
162
  const res = await fetch(uploadUrl, {
180
163
  method: "PUT",
181
164
  headers: { "Content-Type": contentType },
@@ -247,7 +230,7 @@ Check status later: catalogs video status ${videoId}
247
230
  `);
248
231
  }
249
232
  function sleep(ms) {
250
- return new Promise((resolve) => setTimeout(resolve, ms));
233
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
251
234
  }
252
235
 
253
236
  // src/commands/video-status.ts
@@ -279,25 +262,93 @@ async function videoStatus(videoId) {
279
262
  }
280
263
 
281
264
  // src/commands/catalog-push.ts
282
- import { readFileSync as readFileSync3 } from "fs";
265
+ import { readFileSync as readFileSync2 } from "fs";
266
+ import { resolve, extname } from "path";
267
+ import { pathToFileURL } from "url";
283
268
  import ora3 from "ora";
269
+
270
+ // src/lib/serialize.ts
271
+ var MAX_CATALOG_SIZE_KB = 512;
272
+ function serializeCatalog(obj) {
273
+ if (obj === null || obj === void 0) return obj;
274
+ if (typeof obj === "function") {
275
+ return obj.toString();
276
+ }
277
+ if (Array.isArray(obj)) {
278
+ return obj.map((item) => serializeCatalog(item));
279
+ }
280
+ if (typeof obj === "object") {
281
+ const result = {};
282
+ for (const [key, value] of Object.entries(obj)) {
283
+ result[key] = serializeCatalog(value);
284
+ }
285
+ return result;
286
+ }
287
+ return obj;
288
+ }
289
+ function validateCatalog(schema) {
290
+ const errors = [];
291
+ if (!schema.slug) {
292
+ errors.push("Schema must have a 'slug' field");
293
+ }
294
+ if (!schema.schema_version) {
295
+ errors.push("Schema must have a 'schema_version' field");
296
+ }
297
+ if (!schema.settings?.theme) {
298
+ errors.push("Schema must have 'settings.theme'");
299
+ }
300
+ if (!schema.pages || Object.keys(schema.pages).length === 0) {
301
+ errors.push("Schema must have at least one page in 'pages'");
302
+ }
303
+ if (!schema.routing) {
304
+ errors.push("Schema must have a 'routing' object");
305
+ }
306
+ const json = JSON.stringify(schema);
307
+ const sizeKB = Math.round(json.length / 1024);
308
+ if (sizeKB > MAX_CATALOG_SIZE_KB) {
309
+ errors.push(
310
+ `Catalog too large (${sizeKB}KB). Max: ${MAX_CATALOG_SIZE_KB}KB. Move heavy assets to settings.scripts[] CDN imports.`
311
+ );
312
+ }
313
+ return errors;
314
+ }
315
+
316
+ // src/commands/catalog-push.ts
317
+ async function loadTsFile(file) {
318
+ const abs = resolve(file);
319
+ const { register } = await import("module");
320
+ register("tsx/esm", pathToFileURL("./"));
321
+ const mod = await import(pathToFileURL(abs).href);
322
+ return mod.default ?? mod;
323
+ }
284
324
  async function catalogPush(file, opts) {
285
325
  const config = requireConfig();
286
326
  const api = new ApiClient(config);
327
+ await printIdentity(api);
328
+ const ext = extname(file).toLowerCase();
329
+ const isTs = ext === ".ts" || ext === ".mts";
287
330
  let schema;
288
331
  try {
289
- const raw = readFileSync3(file, "utf-8");
290
- schema = JSON.parse(raw);
332
+ if (isTs) {
333
+ const rawCatalog = await loadTsFile(file);
334
+ schema = serializeCatalog(rawCatalog);
335
+ } else {
336
+ const raw = readFileSync2(file, "utf-8");
337
+ schema = JSON.parse(raw);
338
+ }
291
339
  } catch (err) {
292
340
  console.error(`Failed to read ${file}: ${err.message}`);
293
341
  process.exit(1);
294
342
  }
295
- const slug = schema.slug;
296
- const name = schema.catalog_id || schema.slug || file;
297
- if (!slug) {
298
- console.error("Schema must have a 'slug' field");
343
+ const errors = validateCatalog(schema);
344
+ if (errors.length > 0) {
345
+ for (const err of errors) {
346
+ console.error(`Error: ${err}`);
347
+ }
299
348
  process.exit(1);
300
349
  }
350
+ const slug = schema.slug;
351
+ const name = schema.catalog_id || schema.slug || file;
301
352
  const status = opts.publish ? "published" : "draft";
302
353
  const spinner = ora3(`Pushing catalog "${slug}"...`).start();
303
354
  try {
@@ -367,13 +418,14 @@ async function catalogList() {
367
418
  // src/commands/whoami.ts
368
419
  async function whoami() {
369
420
  const config = getConfig();
370
- if (!config.api_url || !config.token) {
421
+ if (!config.token) {
371
422
  console.log("\nNot configured.\n");
372
- console.log("Set these environment variables:");
373
- console.log(" CATALOGS_API_URL \u2014 API base URL (e.g. https://catalog-funnel-api-staging.cloud.zoomgtm.com)");
374
- console.log(" CATALOGS_TOKEN \u2014 API key (cfk_... or base64 legacy token)");
375
- console.log("\nOr create ~/.catalogs-cli/config.json:");
376
- console.log(' { "api_url": "...", "token": "..." }\n');
423
+ console.log("Set this environment variable:");
424
+ console.log(" CATALOG_KIT_TOKEN \u2014 API key (cfk_... or base64 legacy token)");
425
+ console.log("\nOptionally override the API URL (defaults to https://api.catalogkit.cc):");
426
+ console.log(" CATALOG_KIT_API_URL \u2014 API base URL");
427
+ console.log("\nOr create ~/.catalog-kit/config.json:");
428
+ console.log(' { "token": "cfk_..." }\n');
377
429
  return;
378
430
  }
379
431
  const tokenPreview = config.token.length > 12 ? config.token.slice(0, 8) + "..." + config.token.slice(-4) : "***";
@@ -381,28 +433,57 @@ async function whoami() {
381
433
  api_url: ${config.api_url}`);
382
434
  console.log(` token: ${tokenPreview}`);
383
435
  try {
384
- const res = await fetch(`${config.api_url}/health`);
385
- if (res.ok) {
386
- const data = await res.json();
387
- console.log(` stage: ${data.stage || "unknown"}`);
388
- console.log(` status: connected`);
389
- } else {
390
- console.log(` status: error (${res.status})`);
436
+ const api = new ApiClient(config);
437
+ const res = await api.get("/api/v1/me");
438
+ if (res.ok && res.data) {
439
+ const d = res.data;
440
+ console.log(` status: authenticated`);
441
+ console.log();
442
+ console.log(` user_id: ${d.user_id}`);
443
+ if (d.email) console.log(` email: ${d.email}`);
444
+ if (d.username) console.log(` username: ${d.username}`);
445
+ if (d.subdomain) console.log(` subdomain: ${d.subdomain}`);
446
+ if (d.custom_domain) console.log(` custom_domain: ${d.custom_domain}`);
447
+ if (d.catalog_url) console.log(` catalog_url: ${d.catalog_url}`);
448
+ console.log(` credits: ${d.credits}`);
449
+ console.log(` auth_method: ${d.auth_method}`);
450
+ if (d.api_key_id) console.log(` api_key_id: ${d.api_key_id}`);
451
+ console.log(` is_superadmin: ${d.is_superadmin}`);
452
+ console.log(` stripe_key_set: ${d.stripe_key_set}`);
453
+ if (d.stripe_key_last4) console.log(` stripe_key_last4: ...${d.stripe_key_last4}`);
454
+ console.log(` officex: ${d.officex_connected ? "connected" : "not connected"}`);
455
+ console.log(` created_at: ${d.created_at}`);
391
456
  }
392
457
  } catch (err) {
393
- console.log(` status: unreachable (${err.message})`);
458
+ try {
459
+ const res = await fetch(`${config.api_url}/health`);
460
+ if (res.ok) {
461
+ const data = await res.json();
462
+ console.log(` stage: ${data.stage || "unknown"}`);
463
+ console.log(` status: connected (could not fetch user: ${err.message})`);
464
+ } else {
465
+ console.log(` status: error (${res.status})`);
466
+ }
467
+ } catch (healthErr) {
468
+ console.log(` status: unreachable (${healthErr.message})`);
469
+ }
394
470
  }
395
471
  console.log();
396
472
  }
397
473
 
398
474
  // src/index.ts
399
475
  var program = new Command();
400
- program.name("catalogs").description("CLI for Catalog Funnel \u2014 upload videos, push catalogs, manage assets").version("0.1.0");
476
+ program.name("catalogs").description("CLI for Catalog Kit \u2014 upload videos, push catalogs, manage assets").version("0.1.0").option("--token <token>", "Auth token (overrides CATALOG_KIT_TOKEN env var)").hook("preAction", (thisCommand) => {
477
+ const opts = thisCommand.opts();
478
+ if (opts.token) {
479
+ setGlobalToken(opts.token);
480
+ }
481
+ });
401
482
  var video = program.command("video").description("Video upload & transcoding");
402
483
  video.command("upload <file>").description("Upload a video file \u2192 transcode to HLS \u2192 return hls_url").option("--no-transcode", "Skip transcoding (upload only)").action(videoUpload);
403
484
  video.command("status <videoId>").description("Check transcode status for a video").action(videoStatus);
404
485
  var catalog = program.command("catalog").description("Catalog schema management");
405
- catalog.command("push <file>").description("Create or update a catalog from a JSON schema file").option("--publish", "Set status to published (default: draft)").action(catalogPush);
486
+ catalog.command("push <file>").description("Create or update a catalog from a JSON or TypeScript schema file").option("--publish", "Set status to published (default: draft)").action(catalogPush);
406
487
  catalog.command("list").description("List all catalogs").action(catalogList);
407
488
  program.command("whoami").description("Show current authentication info").action(whoami);
408
489
  program.parse();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@officexapp/catalogs-cli",
3
- "version": "0.1.0",
4
- "description": "CLI for Catalog Funnel — upload videos, push catalogs, manage assets",
3
+ "version": "0.2.0",
4
+ "description": "CLI for Catalog Kit — upload videos, push catalogs, manage assets",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "catalogs": "./dist/index.js"
@@ -15,7 +15,7 @@
15
15
  "dist"
16
16
  ],
17
17
  "keywords": [
18
- "catalog-funnel",
18
+ "catalog-kit",
19
19
  "officex",
20
20
  "cli",
21
21
  "video",
@@ -31,12 +31,12 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "commander": "^12.1.0",
34
- "ora": "^8.1.0"
34
+ "ora": "^8.1.0",
35
+ "tsx": "^4.19.0"
35
36
  },
36
37
  "devDependencies": {
37
38
  "@types/node": "^22.0.0",
38
39
  "tsup": "^8.3.0",
39
- "tsx": "^4.19.0",
40
40
  "typescript": "^5.6.3"
41
41
  }
42
42
  }