@zeegme/cli 0.1.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 +9 -2
- package/dist/commands/agents.js +6 -6
- package/dist/commands/auth.js +1 -1
- package/dist/commands/bookings.js +26 -9
- 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 +3 -2
package/README.md
CHANGED
|
@@ -56,6 +56,7 @@ Plain `http` is accepted for `localhost` only. Each origin keeps its own login.
|
|
|
56
56
|
| `zeeg bookings cancel <uuid> [--reason]` | Cancel a booking |
|
|
57
57
|
| `zeeg bookings reschedule <uuid> --date --time` | Move a booking |
|
|
58
58
|
| `zeeg bookings hand-over <uuid> [--to]` | Hand a round-robin booking to another host |
|
|
59
|
+
| `zeeg bookings no-show <inviteeUuid> [--undo]` | Mark an invitee as a no-show, or undo it |
|
|
59
60
|
| `zeeg notes list <bookingUuid>` / `add <bookingUuid> <text>` | Notes on a booking |
|
|
60
61
|
| `zeeg crm list <people\|companies\|slug> [--search <text>]` | CRM records |
|
|
61
62
|
| `zeeg crm get <object> <id>` | One CRM record |
|
|
@@ -66,7 +67,7 @@ Plain `http` is accepted for `localhost` only. Each origin keeps its own login.
|
|
|
66
67
|
|
|
67
68
|
`zeeg <command> --help` lists every option. Times are in your machine's time zone unless you pass `--tz`.
|
|
68
69
|
|
|
69
|
-
Commands that reach other people or spend AI minutes (`bookings create`, `cancel`, `reschedule`, `hand-over`, `calls start`) ask before they act. Pass `--yes` to skip the question, which scripts must do: without a terminal to ask in, they stop instead.
|
|
70
|
+
Commands that reach other people or spend AI minutes (`bookings create`, `cancel`, `reschedule`, `hand-over`, `no-show`, `calls start`) ask before they act. Pass `--yes` to skip the question, which scripts must do: without a terminal to ask in, they stop instead.
|
|
70
71
|
|
|
71
72
|
```sh
|
|
72
73
|
zeeg availability 5c7e… --from 2026-10-05 --to 2026-10-09 --tz Europe/Berlin
|
|
@@ -77,7 +78,13 @@ zeeg crm upsert people --match emails --data '{"firstName":"Sophie","emails":["s
|
|
|
77
78
|
|
|
78
79
|
## JSON output
|
|
79
80
|
|
|
80
|
-
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:
|
|
81
88
|
|
|
82
89
|
```sh
|
|
83
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,24 @@ 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
|
+
});
|
|
124
|
+
bookings
|
|
125
|
+
.command('no-show <inviteeUuid>')
|
|
126
|
+
.description('Mark an invitee as a no-show once the booking has started (the invitee uuid is in `zeeg bookings get`)')
|
|
127
|
+
.option('--undo', 'withdraw the marking instead')
|
|
128
|
+
.option('-y, --yes', 'mark without asking')
|
|
129
|
+
.action(async (inviteeUuid, options) => {
|
|
130
|
+
const ctx = context();
|
|
131
|
+
const params = { params: { path: { uuid: inviteeUuid } } };
|
|
132
|
+
if (options.undo) {
|
|
133
|
+
const data = asJson(await unwrap((await ctx.api()).DELETE('/scheduled-invitees/{uuid}/no-show', params)));
|
|
134
|
+
ctx.print.record(data.resource, `${inviteeUuid} is no longer marked as a no-show.`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
await ctx.confirm(`Mark invitee ${inviteeUuid} as a no-show? Your no-show workflows may email them.`, options.yes);
|
|
138
|
+
const data = asJson(await unwrap((await ctx.api()).POST('/scheduled-invitees/{uuid}/no-show', params)));
|
|
139
|
+
ctx.print.record(data.resource, `Marked ${inviteeUuid} as a no-show.`);
|
|
123
140
|
});
|
|
124
141
|
bookings
|
|
125
142
|
.command('hand-over <uuid>')
|
|
@@ -139,6 +156,6 @@ export function registerBookings(program, context) {
|
|
|
139
156
|
fallbackToReschedule: options.fallbackToReschedule,
|
|
140
157
|
},
|
|
141
158
|
}));
|
|
142
|
-
ctx.print.
|
|
159
|
+
ctx.print.record(asJson(data).resource, `Handed over ${uuid}.`);
|
|
143
160
|
});
|
|
144
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,8 @@
|
|
|
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
|
+
"prepublishOnly": "npm run build",
|
|
21
22
|
"check-types": "tsc --noEmit",
|
|
22
23
|
"generate:api": "openapi-typescript ../../apps/backend/tools/api-docs/openapi.yaml -o src/api/schema.d.ts",
|
|
23
24
|
"test": "vitest run"
|