@zeegme/cli 0.1.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.
package/dist/errors.js ADDED
@@ -0,0 +1,69 @@
1
+ export const ExitCode = {
2
+ Ok: 0,
3
+ Usage: 1,
4
+ Auth: 2,
5
+ Api: 3,
6
+ };
7
+ export class CliError extends Error {
8
+ exitCode;
9
+ constructor(message, exitCode) {
10
+ super(message);
11
+ this.exitCode = exitCode;
12
+ this.name = new.target.name;
13
+ }
14
+ }
15
+ export class UsageError extends CliError {
16
+ constructor(message) {
17
+ super(message, ExitCode.Usage);
18
+ }
19
+ }
20
+ export class AuthError extends CliError {
21
+ constructor(message) {
22
+ super(message, ExitCode.Auth);
23
+ }
24
+ }
25
+ export class ApiError extends CliError {
26
+ constructor(message) {
27
+ super(message, ExitCode.Api);
28
+ }
29
+ }
30
+ /** Commander's own errors carry a `code` such as `commander.unknownOption`. */
31
+ export function isCommanderError(error) {
32
+ return typeof error === 'object' && error !== null && typeof error.code === 'string'
33
+ && error.code.startsWith('commander.');
34
+ }
35
+ export function exitCodeFor(error) {
36
+ if (error instanceof CliError) {
37
+ return error.exitCode;
38
+ }
39
+ if (isCommanderError(error)) {
40
+ // --help and --version end through the same path with exit code 0.
41
+ return error.exitCode === 0 ? ExitCode.Ok : ExitCode.Usage;
42
+ }
43
+ return ExitCode.Usage;
44
+ }
45
+ /**
46
+ * The v2 API answers errors as `{ message, errors? }`; validation errors list messages per
47
+ * field. A 401 means the token was revoked or expired server-side, so it asks for a new login.
48
+ */
49
+ export function apiErrorFrom(status, body) {
50
+ if (status === 401) {
51
+ return new AuthError('Your Zeeg session is no longer valid. Run `zeeg login` to sign in again.');
52
+ }
53
+ const record = typeof body === 'object' && body !== null ? body : {};
54
+ const message = typeof record.message === 'string' && record.message !== '' ? record.message : `The Zeeg API answered HTTP ${status}.`;
55
+ const details = record.errors && typeof record.errors === 'object'
56
+ ? Object.values(record.errors).flat().filter((item) => typeof item === 'string')
57
+ : [];
58
+ const unique = details.filter((detail) => detail !== message);
59
+ return new ApiError(unique.length > 0 ? `${message}\n- ${unique.join('\n- ')}` : message);
60
+ }
61
+ /** `fetch`, with a network failure reported as an ApiError that names the origin. */
62
+ export async function fetchFrom(fetchImpl, baseUrl, input, init) {
63
+ try {
64
+ return await fetchImpl(input, init);
65
+ }
66
+ catch (error) {
67
+ throw new ApiError(`Could not reach ${baseUrl}: ${error.message}`);
68
+ }
69
+ }
package/dist/output.js ADDED
@@ -0,0 +1,66 @@
1
+ /** JSON when asked for, or when stdout is not a terminal (piped into jq, a file, an agent). */
2
+ export function selectOutputMode(options) {
3
+ return options.json === true || options.isTTY !== true ? 'json' : 'table';
4
+ }
5
+ const MAX_CELL = 60;
6
+ function cell(value, full = false) {
7
+ if (value === null || value === undefined || value === '') {
8
+ return '-';
9
+ }
10
+ if (Array.isArray(value)) {
11
+ return value.map((item) => cell(item, full)).join(', ');
12
+ }
13
+ if (typeof value === 'object') {
14
+ return JSON.stringify(value);
15
+ }
16
+ const text = String(value).replace(/\s+/g, ' ').trim();
17
+ return !full && text.length > MAX_CELL ? `${text.slice(0, MAX_CELL - 1)}…` : text;
18
+ }
19
+ export function renderTable(rows, columns) {
20
+ const body = rows.map((row) => columns.map((column) => cell(column.value(row), column.full)));
21
+ const widths = columns.map((column, index) => Math.max(column.header.length, ...body.map((line) => line[index]?.length ?? 0)));
22
+ const format = (line) => line.map((text, index) => text.padEnd(widths[index] ?? 0)).join(' ').trimEnd();
23
+ return [format(columns.map((column) => column.header.toUpperCase())), ...body.map(format)].join('\n');
24
+ }
25
+ /** A single record as aligned `key value` lines, nested objects flattened to dotted keys. */
26
+ export function renderRecord(record) {
27
+ const lines = [];
28
+ const walk = (value, path) => {
29
+ if (value !== null && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length > 0) {
30
+ for (const [key, child] of Object.entries(value)) {
31
+ walk(child, path === '' ? key : `${path}.${key}`);
32
+ }
33
+ return;
34
+ }
35
+ lines.push([path, cell(value)]);
36
+ };
37
+ walk(record, '');
38
+ const width = Math.max(0, ...lines.map(([key]) => key.length));
39
+ return lines.map(([key, value]) => `${key.padEnd(width)} ${value}`).join('\n');
40
+ }
41
+ export function createPrinter(mode, write = (text) => process.stdout.write(text)) {
42
+ const json = (data) => write(`${JSON.stringify(data ?? null, null, 2)}\n`);
43
+ return {
44
+ list(data, rows, columns, footer) {
45
+ if (mode === 'json') {
46
+ return json(data);
47
+ }
48
+ write(rows.length === 0 ? 'Nothing found.\n' : `${renderTable(rows, columns)}\n`);
49
+ if (footer) {
50
+ write(`${footer}\n`);
51
+ }
52
+ },
53
+ record(data, record) {
54
+ if (mode === 'json') {
55
+ return json(data);
56
+ }
57
+ write(`${renderRecord(record)}\n`);
58
+ },
59
+ message(data, text) {
60
+ if (mode === 'json') {
61
+ return json(data);
62
+ }
63
+ write(`${text}\n`);
64
+ },
65
+ };
66
+ }
@@ -0,0 +1,3 @@
1
+ import { createRequire } from 'node:module';
2
+ const require = createRequire(import.meta.url);
3
+ export const VERSION = require('../package.json').version;
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@zeegme/cli",
3
+ "version": "0.1.0",
4
+ "description": "The Zeeg command line: bookings, availability, notes, CRM, routing forms and AI phone calls from your terminal.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "zeeg": "dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "engines": {
14
+ "node": ">=20"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.build.json",
21
+ "check-types": "tsc --noEmit",
22
+ "generate:api": "openapi-typescript ../../apps/backend/tools/api-docs/openapi.yaml -o src/api/schema.d.ts",
23
+ "test": "vitest run"
24
+ },
25
+ "dependencies": {
26
+ "@napi-rs/keyring": "^2.1.0",
27
+ "commander": "^13.1.0",
28
+ "openapi-fetch": "^0.17.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^25.9.1",
32
+ "openapi-typescript": "^7.13.0",
33
+ "typescript": "5.9.2",
34
+ "vitest": "^4.1.8"
35
+ }
36
+ }