@zeegme/cli 0.2.0 → 0.3.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/README.md +7 -1
- package/dist/commands/agents.js +6 -6
- package/dist/commands/auth.js +1 -1
- package/dist/commands/bookings.js +11 -11
- package/dist/commands/calls.js +1 -1
- package/dist/commands/crm.js +5 -5
- package/dist/commands/eventTypes.js +8 -6
- package/dist/commands/forms.js +5 -5
- package/dist/commands/notes.js +2 -2
- package/dist/commands/schedules.js +4 -4
- package/dist/commands/util.js +0 -11
- package/dist/config.js +1 -4
- package/dist/errors.js +7 -3
- package/dist/output.js +18 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -78,7 +78,13 @@ zeeg crm upsert people --match emails --data '{"firstName":"Sophie","emails":["s
|
|
|
78
78
|
|
|
79
79
|
## JSON output
|
|
80
80
|
|
|
81
|
-
In a terminal the CLI prints tables. When its output goes to a pipe or a file, or with `--json`, it prints
|
|
81
|
+
In a terminal the CLI prints tables. When its output goes to a pipe or a file, or with `--json`, it prints JSON you can hand to `jq` or an agent. Every command answers in one of these shapes:
|
|
82
|
+
|
|
83
|
+
- **Lists** print `{ "collection": [...], "pagination": {...} }`. `pagination` is `null` when the command has no pages, as for `notes list`.
|
|
84
|
+
- **One record** prints `{ "resource": {...} }`. That covers `get` commands and the record a write command created or changed. `resource` is `null` when the API returns no record, as for `bookings cancel`.
|
|
85
|
+
- **Login and logout** print their own small status objects.
|
|
86
|
+
|
|
87
|
+
For example:
|
|
82
88
|
|
|
83
89
|
```sh
|
|
84
90
|
zeeg bookings list --status confirmed | jq -r '.collection[].uuid'
|
package/dist/commands/agents.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { unwrap } from '../api/client.js';
|
|
2
|
-
import { asJson,
|
|
2
|
+
import { asJson, positiveInt } from './util.js';
|
|
3
3
|
export function registerAgents(program, context) {
|
|
4
4
|
const agents = program.command('agents').description('AI phone agents and their calls');
|
|
5
5
|
agents
|
|
@@ -10,13 +10,13 @@ export function registerAgents(program, context) {
|
|
|
10
10
|
.action(async (options) => {
|
|
11
11
|
const ctx = context();
|
|
12
12
|
const data = asJson(await unwrap((await ctx.api()).GET('/agents', { params: { query: options } })));
|
|
13
|
-
ctx.print.list(
|
|
13
|
+
ctx.print.list((data.data?.agents ?? []), [
|
|
14
14
|
{ header: 'id', value: (row) => row.id },
|
|
15
15
|
{ header: 'title', value: (row) => row.title },
|
|
16
16
|
{ header: 'active', value: (row) => (row.active ? 'yes' : 'no') },
|
|
17
17
|
{ header: 'phone', value: (row) => row.phoneNumber },
|
|
18
18
|
{ header: 'calls', value: (row) => row.totalCalls },
|
|
19
|
-
],
|
|
19
|
+
], data.data?.pagination);
|
|
20
20
|
});
|
|
21
21
|
agents
|
|
22
22
|
.command('calls <agentUuid>')
|
|
@@ -31,14 +31,14 @@ export function registerAgents(program, context) {
|
|
|
31
31
|
const data = asJson(await unwrap((await ctx.api()).GET('/agents/{agentUuid}/calls', {
|
|
32
32
|
params: { path: { agentUuid }, query: options },
|
|
33
33
|
})));
|
|
34
|
-
ctx.print.list(
|
|
34
|
+
ctx.print.list((data.data?.calls ?? []), [
|
|
35
35
|
{ header: 'id', value: (row) => row.id },
|
|
36
36
|
{ header: 'started', value: (row) => row.startedAt },
|
|
37
37
|
{ header: 'direction', value: (row) => row.direction },
|
|
38
38
|
{ header: 'status', value: (row) => row.status },
|
|
39
39
|
{ header: 'seconds', value: (row) => row.durationSeconds },
|
|
40
40
|
{ header: 'summary', value: (row) => row.callSummaryTitle },
|
|
41
|
-
],
|
|
41
|
+
], data.data?.pagination);
|
|
42
42
|
});
|
|
43
43
|
agents
|
|
44
44
|
.command('call <callId>')
|
|
@@ -48,6 +48,6 @@ export function registerAgents(program, context) {
|
|
|
48
48
|
const data = asJson(await unwrap((await ctx.api()).GET('/agent-calls/{callId}', {
|
|
49
49
|
params: { path: { callId } },
|
|
50
50
|
})));
|
|
51
|
-
ctx.print.record(data
|
|
51
|
+
ctx.print.record(data.call);
|
|
52
52
|
});
|
|
53
53
|
}
|
package/dist/commands/auth.js
CHANGED
|
@@ -55,6 +55,6 @@ export function registerAuth(program, context) {
|
|
|
55
55
|
const ctx = context();
|
|
56
56
|
const me = await unwrap((await ctx.api()).GET('/whoami'));
|
|
57
57
|
const tokens = await (await ctx.store()).load();
|
|
58
|
-
ctx.print.record({ email: me.email, baseUrl: ctx.baseUrl, scopes: tokens?.scopes ?? [] }
|
|
58
|
+
ctx.print.record({ email: me.email, baseUrl: ctx.baseUrl, scopes: tokens?.scopes ?? [] });
|
|
59
59
|
});
|
|
60
60
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { unwrap } from '../api/client.js';
|
|
2
2
|
import { UsageError } from '../errors.js';
|
|
3
|
-
import { asJson, clockTime, collect, isoDate, localTimeZone,
|
|
3
|
+
import { asJson, clockTime, collect, isoDate, localTimeZone, positiveInt } from './util.js';
|
|
4
4
|
const inviteeNames = (row) => (row.invitees ?? []).map((invitee) => invitee.fullName ?? invitee.email);
|
|
5
5
|
export function registerBookings(program, context) {
|
|
6
6
|
const bookings = program.command('bookings').description('Scheduled events (bookings)');
|
|
@@ -38,13 +38,13 @@ export function registerBookings(program, context) {
|
|
|
38
38
|
},
|
|
39
39
|
},
|
|
40
40
|
})));
|
|
41
|
-
ctx.print.list(
|
|
41
|
+
ctx.print.list((data.collection ?? []), [
|
|
42
42
|
{ header: 'uuid', value: (row) => row.uuid },
|
|
43
43
|
{ header: 'start', value: (row) => row.startTime },
|
|
44
44
|
{ header: 'title', value: (row) => row.title },
|
|
45
45
|
{ header: 'status', value: (row) => row.status },
|
|
46
46
|
{ header: 'invitees', value: inviteeNames },
|
|
47
|
-
],
|
|
47
|
+
], data.pagination);
|
|
48
48
|
});
|
|
49
49
|
bookings
|
|
50
50
|
.command('get <uuid>')
|
|
@@ -52,7 +52,7 @@ export function registerBookings(program, context) {
|
|
|
52
52
|
.action(async (uuid) => {
|
|
53
53
|
const ctx = context();
|
|
54
54
|
const data = asJson(await unwrap((await ctx.api()).GET('/scheduled-events/{uuid}', { params: { path: { uuid } } })));
|
|
55
|
-
ctx.print.record(data
|
|
55
|
+
ctx.print.record(data.resource);
|
|
56
56
|
});
|
|
57
57
|
bookings
|
|
58
58
|
.command('create <eventTypeUuid>')
|
|
@@ -87,7 +87,7 @@ export function registerBookings(program, context) {
|
|
|
87
87
|
locationKey: options.locationKey,
|
|
88
88
|
},
|
|
89
89
|
})));
|
|
90
|
-
ctx.print.
|
|
90
|
+
ctx.print.record(data, `Booked: ${data.title ?? 'meeting'} at ${data.startTime ?? `${options.date} ${options.time}`} (${data.uuid}).`);
|
|
91
91
|
});
|
|
92
92
|
bookings
|
|
93
93
|
.command('cancel <uuid>')
|
|
@@ -97,11 +97,11 @@ export function registerBookings(program, context) {
|
|
|
97
97
|
.action(async (uuid, options) => {
|
|
98
98
|
const ctx = context();
|
|
99
99
|
await ctx.confirm(`Cancel booking ${uuid}? The invitees are notified.`, options.yes);
|
|
100
|
-
|
|
100
|
+
await unwrap((await ctx.api()).PUT('/scheduled-events/{uuid}/cancel', {
|
|
101
101
|
params: { path: { uuid } },
|
|
102
102
|
body: { cancellationReason: options.reason },
|
|
103
103
|
}));
|
|
104
|
-
ctx.print.
|
|
104
|
+
ctx.print.record(null, `Cancelled ${uuid}.`);
|
|
105
105
|
});
|
|
106
106
|
bookings
|
|
107
107
|
.command('reschedule <uuid>')
|
|
@@ -119,7 +119,7 @@ export function registerBookings(program, context) {
|
|
|
119
119
|
params: { path: { uuid } },
|
|
120
120
|
body: { date: options.date, start: options.time, timeZone: options.tz, duration: options.duration, reason: options.reason },
|
|
121
121
|
})));
|
|
122
|
-
ctx.print.
|
|
122
|
+
ctx.print.record(data.resource, `Rescheduled ${uuid} to ${options.date} ${options.time}.`);
|
|
123
123
|
});
|
|
124
124
|
bookings
|
|
125
125
|
.command('no-show <inviteeUuid>')
|
|
@@ -131,12 +131,12 @@ export function registerBookings(program, context) {
|
|
|
131
131
|
const params = { params: { path: { uuid: inviteeUuid } } };
|
|
132
132
|
if (options.undo) {
|
|
133
133
|
const data = asJson(await unwrap((await ctx.api()).DELETE('/scheduled-invitees/{uuid}/no-show', params)));
|
|
134
|
-
ctx.print.
|
|
134
|
+
ctx.print.record(data.resource, `${inviteeUuid} is no longer marked as a no-show.`);
|
|
135
135
|
return;
|
|
136
136
|
}
|
|
137
137
|
await ctx.confirm(`Mark invitee ${inviteeUuid} as a no-show? Your no-show workflows may email them.`, options.yes);
|
|
138
138
|
const data = asJson(await unwrap((await ctx.api()).POST('/scheduled-invitees/{uuid}/no-show', params)));
|
|
139
|
-
ctx.print.
|
|
139
|
+
ctx.print.record(data.resource, `Marked ${inviteeUuid} as a no-show.`);
|
|
140
140
|
});
|
|
141
141
|
bookings
|
|
142
142
|
.command('hand-over <uuid>')
|
|
@@ -156,6 +156,6 @@ export function registerBookings(program, context) {
|
|
|
156
156
|
fallbackToReschedule: options.fallbackToReschedule,
|
|
157
157
|
},
|
|
158
158
|
}));
|
|
159
|
-
ctx.print.
|
|
159
|
+
ctx.print.record(asJson(data).resource, `Handed over ${uuid}.`);
|
|
160
160
|
});
|
|
161
161
|
}
|
package/dist/commands/calls.js
CHANGED
|
@@ -27,7 +27,7 @@ export function registerCalls(program, context) {
|
|
|
27
27
|
scheduledTime: options.at,
|
|
28
28
|
},
|
|
29
29
|
})));
|
|
30
|
-
ctx.print.
|
|
30
|
+
ctx.print.record(data.element, `Call ${options.at ? 'scheduled' : 'started'} (${asJson(data.element).status ?? 'queued'}).`);
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
/** The API takes the agent's extra context as one string, so a file is sent as its text. */
|
package/dist/commands/crm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { unwrap } from '../api/client.js';
|
|
2
2
|
import { UsageError } from '../errors.js';
|
|
3
|
-
import { asJson, jsonArgument,
|
|
3
|
+
import { asJson, jsonArgument, positiveInt } from './util.js';
|
|
4
4
|
/** `people` and `companies` have their own endpoints; anything else is a custom object slug. */
|
|
5
5
|
async function list(api, object, query) {
|
|
6
6
|
if (object === 'people') {
|
|
@@ -66,12 +66,12 @@ export function registerCrm(program, context) {
|
|
|
66
66
|
}
|
|
67
67
|
const ctx = context();
|
|
68
68
|
const { data, rows } = await list(await ctx.api(), object, options);
|
|
69
|
-
ctx.print.list(
|
|
69
|
+
ctx.print.list(rows, [
|
|
70
70
|
{ header: 'id', value: (row) => row.id },
|
|
71
71
|
{ header: 'name', value: displayName },
|
|
72
72
|
{ header: 'email', value: (row) => row.emails ?? row.domain },
|
|
73
73
|
{ header: 'updated', value: (row) => row.updatedAt },
|
|
74
|
-
],
|
|
74
|
+
], data.data?.pagination);
|
|
75
75
|
});
|
|
76
76
|
crm
|
|
77
77
|
.command('get <object> <id>')
|
|
@@ -79,7 +79,7 @@ export function registerCrm(program, context) {
|
|
|
79
79
|
.action(async (object, id) => {
|
|
80
80
|
const ctx = context();
|
|
81
81
|
const data = await get(await ctx.api(), object, id);
|
|
82
|
-
ctx.print.record(
|
|
82
|
+
ctx.print.record(recordOf(data));
|
|
83
83
|
});
|
|
84
84
|
crm
|
|
85
85
|
.command('upsert <object>')
|
|
@@ -91,6 +91,6 @@ export function registerCrm(program, context) {
|
|
|
91
91
|
const ctx = context();
|
|
92
92
|
const data = await upsert(await ctx.api(), object, options.match, body);
|
|
93
93
|
const record = recordOf(data);
|
|
94
|
-
ctx.print.
|
|
94
|
+
ctx.print.record(record, `Saved ${object} record ${record.id ?? ''}.`.replace(' .', '.'));
|
|
95
95
|
});
|
|
96
96
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { unwrap } from '../api/client.js';
|
|
2
|
-
import {
|
|
2
|
+
import { renderTable } from '../output.js';
|
|
3
|
+
import { addDays, asJson, isoDate, localTimeZone, positiveInt } from './util.js';
|
|
3
4
|
export function registerEventTypes(program, context) {
|
|
4
5
|
const eventTypes = program.command('event-types').description('Scheduling pages (event types)');
|
|
5
6
|
eventTypes
|
|
@@ -14,13 +15,13 @@ export function registerEventTypes(program, context) {
|
|
|
14
15
|
const data = asJson(await unwrap((await ctx.api()).GET('/event-types', {
|
|
15
16
|
params: { query: { userSlug: options.userSlug, email: options.email, page: options.page, count: options.count } },
|
|
16
17
|
})));
|
|
17
|
-
ctx.print.list(
|
|
18
|
+
ctx.print.list((data.collection ?? []), [
|
|
18
19
|
{ header: 'uuid', value: (row) => row.uuid },
|
|
19
20
|
{ header: 'title', value: (row) => row.title },
|
|
20
21
|
{ header: 'minutes', value: (row) => row.duration },
|
|
21
22
|
{ header: 'active', value: (row) => (row.isActive ? 'yes' : 'no') },
|
|
22
23
|
{ header: 'url', value: (row) => row.schedulingUrl },
|
|
23
|
-
],
|
|
24
|
+
], data.pagination);
|
|
24
25
|
});
|
|
25
26
|
eventTypes
|
|
26
27
|
.command('get <uuid>')
|
|
@@ -28,7 +29,7 @@ export function registerEventTypes(program, context) {
|
|
|
28
29
|
.action(async (uuid) => {
|
|
29
30
|
const ctx = context();
|
|
30
31
|
const data = asJson(await unwrap((await ctx.api()).GET('/event-types/{uuid}', { params: { path: { uuid } } })));
|
|
31
|
-
ctx.print.record(data
|
|
32
|
+
ctx.print.record(data.resource);
|
|
32
33
|
});
|
|
33
34
|
program
|
|
34
35
|
.command('availability <eventTypeUuid>')
|
|
@@ -47,10 +48,11 @@ export function registerEventTypes(program, context) {
|
|
|
47
48
|
query: { startDate: from, endDate: to, timeZone: options.tz ?? localTimeZone(), duration: options.duration },
|
|
48
49
|
},
|
|
49
50
|
})));
|
|
51
|
+
// One record, since the time zone and date range belong with the days; a terminal shows only the days with free times.
|
|
50
52
|
const days = (data.availability ?? []).filter((day) => Array.isArray(day.slots) && day.slots.length > 0);
|
|
51
|
-
ctx.print.
|
|
53
|
+
ctx.print.record(data, days.length === 0 ? 'Nothing found.' : renderTable(days, [
|
|
52
54
|
{ header: 'date', value: (day) => day.date },
|
|
53
55
|
{ header: `times (${data.timeZone ?? options.tz ?? localTimeZone()})`, full: true, value: (day) => day.slots.map((slot) => (typeof slot === 'string' ? slot : asJson(slot).time)).join(' ') },
|
|
54
|
-
]);
|
|
56
|
+
]));
|
|
55
57
|
});
|
|
56
58
|
}
|
package/dist/commands/forms.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { unwrap } from '../api/client.js';
|
|
2
|
-
import { asJson,
|
|
2
|
+
import { asJson, positiveInt } from './util.js';
|
|
3
3
|
export function registerForms(program, context) {
|
|
4
4
|
const forms = program.command('forms').description('Routing forms');
|
|
5
5
|
forms
|
|
@@ -10,12 +10,12 @@ export function registerForms(program, context) {
|
|
|
10
10
|
.action(async (options) => {
|
|
11
11
|
const ctx = context();
|
|
12
12
|
const data = asJson(await unwrap((await ctx.api()).GET('/routing-forms', { params: { query: options } })));
|
|
13
|
-
ctx.print.list(
|
|
13
|
+
ctx.print.list((data.data?.routingForms ?? []), [
|
|
14
14
|
{ header: 'id', value: (row) => row.id },
|
|
15
15
|
{ header: 'name', value: (row) => row.name },
|
|
16
16
|
{ header: 'active', value: (row) => (row.isActive ? 'yes' : 'no') },
|
|
17
17
|
{ header: 'updated', value: (row) => row.updatedAt },
|
|
18
|
-
],
|
|
18
|
+
], data.data?.pagination);
|
|
19
19
|
});
|
|
20
20
|
forms
|
|
21
21
|
.command('submissions')
|
|
@@ -39,11 +39,11 @@ export function registerForms(program, context) {
|
|
|
39
39
|
},
|
|
40
40
|
},
|
|
41
41
|
})));
|
|
42
|
-
ctx.print.list(
|
|
42
|
+
ctx.print.list((data.data?.submissions ?? []), [
|
|
43
43
|
{ header: 'id', value: (row) => row.id },
|
|
44
44
|
{ header: 'created', value: (row) => row.createdAt },
|
|
45
45
|
{ header: 'form', value: (row) => row.routingForm?.name ?? row.routingFormId },
|
|
46
46
|
{ header: 'route', value: (row) => row.routeType ?? row.route?.type },
|
|
47
|
-
],
|
|
47
|
+
], data.data?.pagination);
|
|
48
48
|
});
|
|
49
49
|
}
|
package/dist/commands/notes.js
CHANGED
|
@@ -8,7 +8,7 @@ export function registerNotes(program, context) {
|
|
|
8
8
|
.action(async (uuid) => {
|
|
9
9
|
const ctx = context();
|
|
10
10
|
const data = asJson(await unwrap((await ctx.api()).GET('/notes/event/{uuid}', { params: { path: { uuid } } })));
|
|
11
|
-
ctx.print.list(
|
|
11
|
+
ctx.print.list((data.list ?? []), [
|
|
12
12
|
{ header: 'id', value: (row) => row.id },
|
|
13
13
|
{ header: 'created', value: (row) => row.createdAt },
|
|
14
14
|
{ header: 'by', value: (row) => row.creator?.fullName ?? row.creator?.email },
|
|
@@ -23,6 +23,6 @@ export function registerNotes(program, context) {
|
|
|
23
23
|
const data = await unwrap((await ctx.api()).POST('/notes', {
|
|
24
24
|
body: { relation: 'event', uuid, note: text.join(' ') },
|
|
25
25
|
}));
|
|
26
|
-
ctx.print.
|
|
26
|
+
ctx.print.record(asJson(data).data, `Note added to ${uuid}.`);
|
|
27
27
|
});
|
|
28
28
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { unwrap } from '../api/client.js';
|
|
2
2
|
import { UsageError } from '../errors.js';
|
|
3
|
-
import { asJson, collect, isoDate,
|
|
3
|
+
import { asJson, collect, isoDate, positiveInt } from './util.js';
|
|
4
4
|
async function ownEmail(api) {
|
|
5
5
|
return (await unwrap(api.GET('/whoami'))).email;
|
|
6
6
|
}
|
|
@@ -18,12 +18,12 @@ export function registerSchedules(program, context) {
|
|
|
18
18
|
const data = asJson(await unwrap(api.GET('/schedules', {
|
|
19
19
|
params: { query: { email: options.email ?? await ownEmail(api), page: options.page, count: options.count } },
|
|
20
20
|
})));
|
|
21
|
-
ctx.print.list(
|
|
21
|
+
ctx.print.list((data.collection ?? []), [
|
|
22
22
|
{ header: 'uuid', value: (row) => row.uuid },
|
|
23
23
|
{ header: 'title', value: (row) => row.title },
|
|
24
24
|
{ header: 'time zone', value: (row) => row.timezone },
|
|
25
25
|
{ header: 'default', value: (row) => (row.isDefault ? 'yes' : 'no') },
|
|
26
|
-
],
|
|
26
|
+
], data.pagination);
|
|
27
27
|
});
|
|
28
28
|
const timeOff = program.command('time-off').description('Time off (vacation, out of office)');
|
|
29
29
|
timeOff
|
|
@@ -51,6 +51,6 @@ export function registerSchedules(program, context) {
|
|
|
51
51
|
note: options.note,
|
|
52
52
|
},
|
|
53
53
|
}));
|
|
54
|
-
ctx.print.
|
|
54
|
+
ctx.print.record(data, `Time off added from ${options.start} to ${options.end}.`);
|
|
55
55
|
});
|
|
56
56
|
}
|
package/dist/commands/util.js
CHANGED
|
@@ -55,14 +55,3 @@ export function addDays(date, days) {
|
|
|
55
55
|
copy.setUTCDate(copy.getUTCDate() + days);
|
|
56
56
|
return copy.toISOString().slice(0, 10);
|
|
57
57
|
}
|
|
58
|
-
/** "Page 2 of 5 (93 in total)" from either pagination shape the v2 API uses. */
|
|
59
|
-
export function pageFooter(pagination) {
|
|
60
|
-
const p = asJson(pagination);
|
|
61
|
-
const current = p.currentPage;
|
|
62
|
-
const last = p.totalPages ?? p.lastPage;
|
|
63
|
-
const total = p.total ?? p.totalItems;
|
|
64
|
-
if (current === undefined || last === undefined) {
|
|
65
|
-
return undefined;
|
|
66
|
-
}
|
|
67
|
-
return `Page ${current} of ${last}${total !== undefined ? ` (${total} in total)` : ''}. Use --page to see more.`;
|
|
68
|
-
}
|
package/dist/config.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { UsageError } from './errors.js';
|
|
2
2
|
export const DEFAULT_BASE_URL = 'https://api.zeeg.me';
|
|
3
|
-
/**
|
|
4
|
-
* The CLI's OAuth client. The backend inserts it with this id in every environment
|
|
5
|
-
* (config('mcp.cli.client_id')), so one build reaches local, staging and production.
|
|
6
|
-
*/
|
|
3
|
+
/** The CLI's public OAuth client, which has the same id on every Zeeg server it can sign in to. */
|
|
7
4
|
export const DEFAULT_CLIENT_ID = '1000001';
|
|
8
5
|
/** The v2 scopes the commands need. The server narrows them to what the plan allows. */
|
|
9
6
|
export const SCOPES = [
|
package/dist/errors.js
CHANGED
|
@@ -43,15 +43,19 @@ export function exitCodeFor(error) {
|
|
|
43
43
|
return ExitCode.Usage;
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
|
-
* The
|
|
47
|
-
*
|
|
46
|
+
* The API answers errors in two shapes: controllers and validation send `{ message, errors? }`,
|
|
47
|
+
* with validation messages listed per field, and the shared exception handler sends
|
|
48
|
+
* `{ error: { type, message } }` for not found, forbidden and similar. A 401 means the token was
|
|
49
|
+
* revoked or expired server-side, so it asks for a new login.
|
|
48
50
|
*/
|
|
49
51
|
export function apiErrorFrom(status, body) {
|
|
50
52
|
if (status === 401) {
|
|
51
53
|
return new AuthError('Your Zeeg session is no longer valid. Run `zeeg login` to sign in again.');
|
|
52
54
|
}
|
|
53
55
|
const record = typeof body === 'object' && body !== null ? body : {};
|
|
54
|
-
const
|
|
56
|
+
const nested = typeof record.error === 'object' && record.error !== null ? record.error : {};
|
|
57
|
+
const text = [record.message, nested.message].find((value) => typeof value === 'string' && value !== '');
|
|
58
|
+
const message = text ?? `The Zeeg API answered HTTP ${status}.`;
|
|
55
59
|
const details = record.errors && typeof record.errors === 'object'
|
|
56
60
|
? Object.values(record.errors).flat().filter((item) => typeof item === 'string')
|
|
57
61
|
: [];
|
package/dist/output.js
CHANGED
|
@@ -38,23 +38,36 @@ export function renderRecord(record) {
|
|
|
38
38
|
const width = Math.max(0, ...lines.map(([key]) => key.length));
|
|
39
39
|
return lines.map(([key, value]) => `${key.padEnd(width)} ${value}`).join('\n');
|
|
40
40
|
}
|
|
41
|
+
/** "Page 2 of 5 (93 in total)" from either pagination shape the API uses. */
|
|
42
|
+
export function pageFooter(pagination) {
|
|
43
|
+
const p = pagination !== null && typeof pagination === 'object' ? pagination : {};
|
|
44
|
+
const current = p.currentPage;
|
|
45
|
+
const last = p.totalPages ?? p.lastPage;
|
|
46
|
+
const total = p.total ?? p.totalItems;
|
|
47
|
+
if (current === undefined || last === undefined) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
return `Page ${String(current)} of ${String(last)}${total !== undefined ? ` (${String(total)} in total)` : ''}. Use --page to see more.`;
|
|
51
|
+
}
|
|
41
52
|
export function createPrinter(mode, write = (text) => process.stdout.write(text)) {
|
|
42
53
|
const json = (data) => write(`${JSON.stringify(data ?? null, null, 2)}\n`);
|
|
43
54
|
return {
|
|
44
|
-
list(
|
|
55
|
+
list(rows, columns, pagination) {
|
|
45
56
|
if (mode === 'json') {
|
|
46
|
-
return json(
|
|
57
|
+
return json({ collection: rows, pagination: pagination ?? null });
|
|
47
58
|
}
|
|
48
59
|
write(rows.length === 0 ? 'Nothing found.\n' : `${renderTable(rows, columns)}\n`);
|
|
60
|
+
const footer = pageFooter(pagination);
|
|
49
61
|
if (footer) {
|
|
50
62
|
write(`${footer}\n`);
|
|
51
63
|
}
|
|
52
64
|
},
|
|
53
|
-
record(
|
|
65
|
+
record(resource, text) {
|
|
54
66
|
if (mode === 'json') {
|
|
55
|
-
return json(
|
|
67
|
+
return json({ resource: resource ?? null });
|
|
56
68
|
}
|
|
57
|
-
|
|
69
|
+
const record = resource !== null && typeof resource === 'object' ? resource : {};
|
|
70
|
+
write(`${text ?? renderRecord(record)}\n`);
|
|
58
71
|
},
|
|
59
72
|
message(data, text) {
|
|
60
73
|
if (mode === 'json') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeegme/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "The Zeeg command line: bookings, availability, notes, CRM, routing forms and AI phone calls from your terminal.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
|
-
"build": "tsc -p tsconfig.build.json",
|
|
20
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
21
21
|
"prepublishOnly": "npm run build",
|
|
22
22
|
"check-types": "tsc --noEmit",
|
|
23
23
|
"generate:api": "openapi-typescript ../../apps/backend/tools/api-docs/openapi.yaml -o src/api/schema.d.ts",
|