posterly-mcp-server 0.48.1 → 0.48.3
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 +1 -1
- package/dist/lib/api-client.d.ts +22 -0
- package/dist/lib/api-client.js +12 -0
- package/dist/lib/version.d.ts +1 -1
- package/dist/lib/version.js +1 -1
- package/dist/tools/create-post.js +6 -0
- package/dist/tools/create-posts-batch.js +6 -0
- package/dist/tools/find-slot.d.ts +4 -0
- package/dist/tools/find-slot.js +39 -2
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/lib/api-client.ts +30 -0
- package/src/lib/version.ts +1 -1
- package/src/tools/create-post.ts +6 -0
- package/src/tools/create-posts-batch.ts +7 -0
- package/src/tools/find-slot.ts +45 -2
package/README.md
CHANGED
|
@@ -123,7 +123,7 @@ Add the same server definition to your Cursor MCP settings:
|
|
|
123
123
|
|
|
124
124
|
## Available tools
|
|
125
125
|
|
|
126
|
-
`posterly-mcp-server@0.48.
|
|
126
|
+
`posterly-mcp-server@0.48.3` exposes 62 tools covering 95 actions. The hosted HTTP endpoint exposes the same catalog minus the three signup tools: 59 tools covering 92 actions.
|
|
127
127
|
|
|
128
128
|
Many tools pick what to do with one parameter: `view` (reads), `action` (writes), or `type`. A few pick their mode from which parameters you pass (for example `update_post` with `status` changes the status instead of editing). Every tool description names all of its views and actions.
|
|
129
129
|
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -237,6 +237,10 @@ export type CreatePostResponse = {
|
|
|
237
237
|
is_personal: boolean;
|
|
238
238
|
resolved_from: 'explicit' | 'account' | 'personal';
|
|
239
239
|
};
|
|
240
|
+
warnings?: Array<{
|
|
241
|
+
code: string;
|
|
242
|
+
message: string;
|
|
243
|
+
}>;
|
|
240
244
|
};
|
|
241
245
|
export type ValidatePostResponse = {
|
|
242
246
|
dry_run: true;
|
|
@@ -551,6 +555,17 @@ export interface Slot {
|
|
|
551
555
|
time: string;
|
|
552
556
|
local_time: string;
|
|
553
557
|
}
|
|
558
|
+
export type SuggestedSlotBasis = 'yours' | 'platform' | 'default';
|
|
559
|
+
export interface SuggestedSlot extends Slot {
|
|
560
|
+
basis: SuggestedSlotBasis;
|
|
561
|
+
reason: string;
|
|
562
|
+
}
|
|
563
|
+
export interface SuggestedSlotsResponse {
|
|
564
|
+
timezone: string;
|
|
565
|
+
basis: SuggestedSlotBasis;
|
|
566
|
+
basis_label: string;
|
|
567
|
+
slots: SuggestedSlot[];
|
|
568
|
+
}
|
|
554
569
|
export interface Workspace {
|
|
555
570
|
id: string;
|
|
556
571
|
name: string;
|
|
@@ -1360,6 +1375,13 @@ export declare class PosterlyClient {
|
|
|
1360
1375
|
count?: number;
|
|
1361
1376
|
workspace_id?: string;
|
|
1362
1377
|
}): Promise<Slot[]>;
|
|
1378
|
+
/** Best times by engagement for the given accounts (GET /slots/suggested). */
|
|
1379
|
+
findSuggestedSlots(params: {
|
|
1380
|
+
account_ids: string[];
|
|
1381
|
+
timezone?: string;
|
|
1382
|
+
count?: number;
|
|
1383
|
+
workspace_id?: string;
|
|
1384
|
+
}): Promise<SuggestedSlotsResponse>;
|
|
1363
1385
|
getAccountAnalytics(params: {
|
|
1364
1386
|
account_id: number;
|
|
1365
1387
|
from?: string;
|
package/dist/lib/api-client.js
CHANGED
|
@@ -452,6 +452,18 @@ export class PosterlyClient {
|
|
|
452
452
|
const data = await this.request('GET', `/slots/next${qs ? `?${qs}` : ''}`);
|
|
453
453
|
return data.slots;
|
|
454
454
|
}
|
|
455
|
+
/** Best times by engagement for the given accounts (GET /slots/suggested). */
|
|
456
|
+
async findSuggestedSlots(params) {
|
|
457
|
+
const searchParams = new URLSearchParams();
|
|
458
|
+
searchParams.set('account_ids', params.account_ids.join(','));
|
|
459
|
+
if (params.timezone)
|
|
460
|
+
searchParams.set('timezone', params.timezone);
|
|
461
|
+
if (params.count)
|
|
462
|
+
searchParams.set('count', String(params.count));
|
|
463
|
+
if (params.workspace_id)
|
|
464
|
+
searchParams.set('workspace_id', params.workspace_id);
|
|
465
|
+
return this.request('GET', `/slots/suggested?${searchParams.toString()}`);
|
|
466
|
+
}
|
|
455
467
|
async getAccountAnalytics(params) {
|
|
456
468
|
const searchParams = new URLSearchParams();
|
|
457
469
|
searchParams.set('account_id', String(params.account_id));
|
package/dist/lib/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const POSTERLY_MCP_VERSION = "0.48.
|
|
1
|
+
export declare const POSTERLY_MCP_VERSION = "0.48.3";
|
package/dist/lib/version.js
CHANGED
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
// tool set (minus the intentional pre-auth signup tools that only this stdio
|
|
6
6
|
// package exposes). `npm run check:mcp-parity` enforces both the version match
|
|
7
7
|
// and the tool-list match, and runs in the pre-commit hook.
|
|
8
|
-
export const POSTERLY_MCP_VERSION = '0.48.
|
|
8
|
+
export const POSTERLY_MCP_VERSION = '0.48.3';
|
|
@@ -382,6 +382,12 @@ export const createPostTool = {
|
|
|
382
382
|
if (ws) {
|
|
383
383
|
lines.push(`• Workspace: ${ws.name} (${ws.id}) - resolved from ${ws.resolved_from}`);
|
|
384
384
|
}
|
|
385
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
386
|
+
lines.push('• Warnings:');
|
|
387
|
+
for (const warning of result.warnings) {
|
|
388
|
+
lines.push(` - ${warning.message || warning.code}`);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
385
391
|
lines.push('', 'Next step for the AI agent: tell the user the post is in posterly, share the dashboard link above, then ask whether they want to create another post, adjust this one, or connect another account.');
|
|
386
392
|
return lines.join('\n');
|
|
387
393
|
},
|
|
@@ -54,6 +54,12 @@ export const createPostsBatchTool = {
|
|
|
54
54
|
for (const error of result.errors.slice(0, 25)) {
|
|
55
55
|
lines.push(`• [${error.index}] ERROR ${error.status}: ${error.error}`);
|
|
56
56
|
}
|
|
57
|
+
for (const item of result.posts.slice(0, 25)) {
|
|
58
|
+
const warnings = item.warnings || [];
|
|
59
|
+
for (const warning of warnings) {
|
|
60
|
+
lines.push(`• [${item.index}] WARNING ${warning.message || warning.code}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
57
63
|
lines.push('', 'Next step for the AI agent: tell the user which posts were created, share the dashboard link above, and ask whether they want to review the queue, adjust anything, or create the next post.');
|
|
58
64
|
return lines.join('\n');
|
|
59
65
|
},
|
|
@@ -8,21 +8,25 @@ export declare const findSlotTool: {
|
|
|
8
8
|
timezone: z.ZodOptional<z.ZodString>;
|
|
9
9
|
count: z.ZodOptional<z.ZodNumber>;
|
|
10
10
|
workspace_id: z.ZodOptional<z.ZodString>;
|
|
11
|
+
strategy: z.ZodOptional<z.ZodEnum<["next_free", "best_engagement"]>>;
|
|
11
12
|
}, "strip", z.ZodTypeAny, {
|
|
12
13
|
workspace_id?: string | undefined;
|
|
13
14
|
account_ids?: string[] | undefined;
|
|
14
15
|
timezone?: string | undefined;
|
|
15
16
|
count?: number | undefined;
|
|
17
|
+
strategy?: "next_free" | "best_engagement" | undefined;
|
|
16
18
|
}, {
|
|
17
19
|
workspace_id?: string | undefined;
|
|
18
20
|
account_ids?: string[] | undefined;
|
|
19
21
|
timezone?: string | undefined;
|
|
20
22
|
count?: number | undefined;
|
|
23
|
+
strategy?: "next_free" | "best_engagement" | undefined;
|
|
21
24
|
}>;
|
|
22
25
|
execute(client: PosterlyClient, input: {
|
|
23
26
|
account_ids?: string[];
|
|
24
27
|
timezone?: string;
|
|
25
28
|
count?: number;
|
|
26
29
|
workspace_id?: string;
|
|
30
|
+
strategy?: "next_free" | "best_engagement";
|
|
27
31
|
}): Promise<string>;
|
|
28
32
|
};
|
package/dist/tools/find-slot.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { formatDate, mdEmpty, mdTable, mdTitle } from '../lib/format.js';
|
|
3
|
+
const SUGGESTED_BASIS_LABELS = {
|
|
4
|
+
yours: 'Based on your posts',
|
|
5
|
+
platform: 'Popular on this platform',
|
|
6
|
+
default: 'Good default',
|
|
7
|
+
};
|
|
3
8
|
export const findSlotTool = {
|
|
4
9
|
name: 'find_available_slot',
|
|
5
|
-
description: 'Find available time slots for posting. Respects a 1-hour gap between posts and preferred hours (8am-10pm in the given timezone). Returns up to 10 slots. IMPORTANT: pass a timezone explicitly - default is America/New_York and slots will be off if the user is elsewhere. Pass workspace_id to only avoid collisions with posts in that workspace.',
|
|
10
|
+
description: 'Find available time slots for posting. Respects a 1-hour gap between posts and preferred hours (8am-10pm in the given timezone). Returns up to 10 slots. IMPORTANT: pass a timezone explicitly - default is America/New_York and slots will be off if the user is elsewhere. Pass workspace_id to only avoid collisions with posts in that workspace. strategy "best_engagement" (needs account_ids) ranks the next 7 days by when these accounts get the most engagement, with a reason per slot.',
|
|
6
11
|
inputSchema: z.object({
|
|
7
12
|
account_ids: z
|
|
8
13
|
.array(z.string())
|
|
@@ -17,13 +22,45 @@ export const findSlotTool = {
|
|
|
17
22
|
.min(1)
|
|
18
23
|
.max(10)
|
|
19
24
|
.optional()
|
|
20
|
-
.describe('Number of slots to return (
|
|
25
|
+
.describe('Number of slots to return (max 10). Default 5 for next_free, 3 for best_engagement.'),
|
|
21
26
|
workspace_id: z
|
|
22
27
|
.string()
|
|
23
28
|
.optional()
|
|
24
29
|
.describe('Scope slot-finding to a single workspace (get IDs via whoami).'),
|
|
30
|
+
strategy: z
|
|
31
|
+
.enum(['next_free', 'best_engagement'])
|
|
32
|
+
.optional()
|
|
33
|
+
.describe('next_free (default): earliest free slots. best_engagement: best times by engagement (requires account_ids).'),
|
|
25
34
|
}),
|
|
26
35
|
async execute(client, input) {
|
|
36
|
+
if (input.strategy === 'best_engagement') {
|
|
37
|
+
const accountIds = (input.account_ids || []).filter(Boolean);
|
|
38
|
+
if (accountIds.length === 0) {
|
|
39
|
+
throw new Error('strategy "best_engagement" needs account_ids (get them from list_accounts).');
|
|
40
|
+
}
|
|
41
|
+
const result = await client.findSuggestedSlots({
|
|
42
|
+
account_ids: accountIds,
|
|
43
|
+
timezone: input.timezone,
|
|
44
|
+
count: input.count,
|
|
45
|
+
workspace_id: input.workspace_id,
|
|
46
|
+
});
|
|
47
|
+
const slots = Array.isArray(result.slots) ? result.slots : [];
|
|
48
|
+
if (slots.length === 0) {
|
|
49
|
+
return mdEmpty('suggested slots', 'No suggested slots are free in the next 7 days.');
|
|
50
|
+
}
|
|
51
|
+
const basis = result.basis_label || SUGGESTED_BASIS_LABELS[result.basis] || 'Good default';
|
|
52
|
+
return [
|
|
53
|
+
mdTitle(`Best times to post (${slots.length})`),
|
|
54
|
+
`Basis: ${basis}${result.timezone ? ` (${result.timezone})` : ''}`,
|
|
55
|
+
// local_time already carries the date in the requested timezone, so
|
|
56
|
+
// no separate Date column (a machine-local date could disagree).
|
|
57
|
+
mdTable(['Option', 'Local time', 'Why'], slots.map((slot, index) => [
|
|
58
|
+
index + 1,
|
|
59
|
+
slot.local_time,
|
|
60
|
+
slot.reason || 'n/a',
|
|
61
|
+
])),
|
|
62
|
+
].join('\n\n');
|
|
63
|
+
}
|
|
27
64
|
const slots = await client.findAvailableSlots(input);
|
|
28
65
|
if (slots.length === 0) {
|
|
29
66
|
return mdEmpty('available slots', 'No available posting slots were found in the next 14 days.');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "posterly-mcp-server",
|
|
3
|
-
"version": "0.48.
|
|
3
|
+
"version": "0.48.3",
|
|
4
4
|
"mcpName": "io.github.awpthorp/posterly",
|
|
5
5
|
"description": "MCP server for posterly: schedule and publish social media posts across 18 platforms from any MCP client (Claude, ChatGPT, Cursor, Windsurf, Cline, and more)",
|
|
6
6
|
"license": "MIT",
|
package/server.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "io.github.awpthorp/posterly",
|
|
4
4
|
"title": "posterly",
|
|
5
5
|
"description": "Validate, schedule, publish, and analyze social content across 18 platforms with posterly.",
|
|
6
|
-
"version": "0.48.
|
|
6
|
+
"version": "0.48.3",
|
|
7
7
|
"websiteUrl": "https://www.poster.ly/mcp",
|
|
8
8
|
"repository": {
|
|
9
9
|
"url": "https://github.com/awpthorp/posterly",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
{
|
|
15
15
|
"registryType": "npm",
|
|
16
16
|
"identifier": "posterly-mcp-server",
|
|
17
|
-
"version": "0.48.
|
|
17
|
+
"version": "0.48.3",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|
|
20
20
|
},
|
package/src/lib/api-client.ts
CHANGED
|
@@ -269,6 +269,7 @@ export type CreatePostPayload = {
|
|
|
269
269
|
export type CreatePostResponse = {
|
|
270
270
|
post: Post;
|
|
271
271
|
workspace: { id: string; name: string; is_personal: boolean; resolved_from: 'explicit' | 'account' | 'personal' };
|
|
272
|
+
warnings?: Array<{ code: string; message: string }>;
|
|
272
273
|
};
|
|
273
274
|
|
|
274
275
|
export type ValidatePostResponse = {
|
|
@@ -605,6 +606,20 @@ export interface Slot {
|
|
|
605
606
|
local_time: string;
|
|
606
607
|
}
|
|
607
608
|
|
|
609
|
+
export type SuggestedSlotBasis = 'yours' | 'platform' | 'default';
|
|
610
|
+
|
|
611
|
+
export interface SuggestedSlot extends Slot {
|
|
612
|
+
basis: SuggestedSlotBasis;
|
|
613
|
+
reason: string;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
export interface SuggestedSlotsResponse {
|
|
617
|
+
timezone: string;
|
|
618
|
+
basis: SuggestedSlotBasis;
|
|
619
|
+
basis_label: string;
|
|
620
|
+
slots: SuggestedSlot[];
|
|
621
|
+
}
|
|
622
|
+
|
|
608
623
|
export interface Workspace {
|
|
609
624
|
id: string;
|
|
610
625
|
name: string;
|
|
@@ -1787,6 +1802,21 @@ export class PosterlyClient {
|
|
|
1787
1802
|
return data.slots;
|
|
1788
1803
|
}
|
|
1789
1804
|
|
|
1805
|
+
/** Best times by engagement for the given accounts (GET /slots/suggested). */
|
|
1806
|
+
async findSuggestedSlots(params: {
|
|
1807
|
+
account_ids: string[];
|
|
1808
|
+
timezone?: string;
|
|
1809
|
+
count?: number;
|
|
1810
|
+
workspace_id?: string;
|
|
1811
|
+
}): Promise<SuggestedSlotsResponse> {
|
|
1812
|
+
const searchParams = new URLSearchParams();
|
|
1813
|
+
searchParams.set('account_ids', params.account_ids.join(','));
|
|
1814
|
+
if (params.timezone) searchParams.set('timezone', params.timezone);
|
|
1815
|
+
if (params.count) searchParams.set('count', String(params.count));
|
|
1816
|
+
if (params.workspace_id) searchParams.set('workspace_id', params.workspace_id);
|
|
1817
|
+
return this.request<SuggestedSlotsResponse>('GET', `/slots/suggested?${searchParams.toString()}`);
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1790
1820
|
async getAccountAnalytics(params: {
|
|
1791
1821
|
account_id: number;
|
|
1792
1822
|
from?: string;
|
package/src/lib/version.ts
CHANGED
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
// tool set (minus the intentional pre-auth signup tools that only this stdio
|
|
6
6
|
// package exposes). `npm run check:mcp-parity` enforces both the version match
|
|
7
7
|
// and the tool-list match, and runs in the pre-commit hook.
|
|
8
|
-
export const POSTERLY_MCP_VERSION = '0.48.
|
|
8
|
+
export const POSTERLY_MCP_VERSION = '0.48.3';
|
package/src/tools/create-post.ts
CHANGED
|
@@ -415,6 +415,12 @@ export const createPostTool = {
|
|
|
415
415
|
if (ws) {
|
|
416
416
|
lines.push(`• Workspace: ${ws.name} (${ws.id}) - resolved from ${ws.resolved_from}`);
|
|
417
417
|
}
|
|
418
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
419
|
+
lines.push('• Warnings:');
|
|
420
|
+
for (const warning of result.warnings) {
|
|
421
|
+
lines.push(` - ${warning.message || warning.code}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
418
424
|
lines.push(
|
|
419
425
|
'',
|
|
420
426
|
'Next step for the AI agent: tell the user the post is in posterly, share the dashboard link above, then ask whether they want to create another post, adjust this one, or connect another account.',
|
|
@@ -63,6 +63,13 @@ export const createPostsBatchTool = {
|
|
|
63
63
|
lines.push(`• [${error.index}] ERROR ${error.status}: ${error.error}`);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
for (const item of result.posts.slice(0, 25)) {
|
|
67
|
+
const warnings = item.warnings || [];
|
|
68
|
+
for (const warning of warnings) {
|
|
69
|
+
lines.push(`• [${item.index}] WARNING ${warning.message || warning.code}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
66
73
|
lines.push(
|
|
67
74
|
'',
|
|
68
75
|
'Next step for the AI agent: tell the user which posts were created, share the dashboard link above, and ask whether they want to review the queue, adjust anything, or create the next post.',
|
package/src/tools/find-slot.ts
CHANGED
|
@@ -2,10 +2,16 @@ import { z } from 'zod';
|
|
|
2
2
|
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
3
|
import { formatDate, mdEmpty, mdTable, mdTitle } from '../lib/format.js';
|
|
4
4
|
|
|
5
|
+
const SUGGESTED_BASIS_LABELS: Record<string, string> = {
|
|
6
|
+
yours: 'Based on your posts',
|
|
7
|
+
platform: 'Popular on this platform',
|
|
8
|
+
default: 'Good default',
|
|
9
|
+
};
|
|
10
|
+
|
|
5
11
|
export const findSlotTool = {
|
|
6
12
|
name: 'find_available_slot',
|
|
7
13
|
description:
|
|
8
|
-
'Find available time slots for posting. Respects a 1-hour gap between posts and preferred hours (8am-10pm in the given timezone). Returns up to 10 slots. IMPORTANT: pass a timezone explicitly - default is America/New_York and slots will be off if the user is elsewhere. Pass workspace_id to only avoid collisions with posts in that workspace.',
|
|
14
|
+
'Find available time slots for posting. Respects a 1-hour gap between posts and preferred hours (8am-10pm in the given timezone). Returns up to 10 slots. IMPORTANT: pass a timezone explicitly - default is America/New_York and slots will be off if the user is elsewhere. Pass workspace_id to only avoid collisions with posts in that workspace. strategy "best_engagement" (needs account_ids) ranks the next 7 days by when these accounts get the most engagement, with a reason per slot.',
|
|
9
15
|
inputSchema: z.object({
|
|
10
16
|
account_ids: z
|
|
11
17
|
.array(z.string())
|
|
@@ -20,11 +26,15 @@ export const findSlotTool = {
|
|
|
20
26
|
.min(1)
|
|
21
27
|
.max(10)
|
|
22
28
|
.optional()
|
|
23
|
-
.describe('Number of slots to return (
|
|
29
|
+
.describe('Number of slots to return (max 10). Default 5 for next_free, 3 for best_engagement.'),
|
|
24
30
|
workspace_id: z
|
|
25
31
|
.string()
|
|
26
32
|
.optional()
|
|
27
33
|
.describe('Scope slot-finding to a single workspace (get IDs via whoami).'),
|
|
34
|
+
strategy: z
|
|
35
|
+
.enum(['next_free', 'best_engagement'])
|
|
36
|
+
.optional()
|
|
37
|
+
.describe('next_free (default): earliest free slots. best_engagement: best times by engagement (requires account_ids).'),
|
|
28
38
|
}),
|
|
29
39
|
|
|
30
40
|
async execute(
|
|
@@ -34,8 +44,41 @@ export const findSlotTool = {
|
|
|
34
44
|
timezone?: string;
|
|
35
45
|
count?: number;
|
|
36
46
|
workspace_id?: string;
|
|
47
|
+
strategy?: 'next_free' | 'best_engagement';
|
|
37
48
|
}
|
|
38
49
|
) {
|
|
50
|
+
if (input.strategy === 'best_engagement') {
|
|
51
|
+
const accountIds = (input.account_ids || []).filter(Boolean);
|
|
52
|
+
if (accountIds.length === 0) {
|
|
53
|
+
throw new Error('strategy "best_engagement" needs account_ids (get them from list_accounts).');
|
|
54
|
+
}
|
|
55
|
+
const result = await client.findSuggestedSlots({
|
|
56
|
+
account_ids: accountIds,
|
|
57
|
+
timezone: input.timezone,
|
|
58
|
+
count: input.count,
|
|
59
|
+
workspace_id: input.workspace_id,
|
|
60
|
+
});
|
|
61
|
+
const slots = Array.isArray(result.slots) ? result.slots : [];
|
|
62
|
+
if (slots.length === 0) {
|
|
63
|
+
return mdEmpty('suggested slots', 'No suggested slots are free in the next 7 days.');
|
|
64
|
+
}
|
|
65
|
+
const basis = result.basis_label || SUGGESTED_BASIS_LABELS[result.basis] || 'Good default';
|
|
66
|
+
return [
|
|
67
|
+
mdTitle(`Best times to post (${slots.length})`),
|
|
68
|
+
`Basis: ${basis}${result.timezone ? ` (${result.timezone})` : ''}`,
|
|
69
|
+
// local_time already carries the date in the requested timezone, so
|
|
70
|
+
// no separate Date column (a machine-local date could disagree).
|
|
71
|
+
mdTable(
|
|
72
|
+
['Option', 'Local time', 'Why'],
|
|
73
|
+
slots.map((slot, index) => [
|
|
74
|
+
index + 1,
|
|
75
|
+
slot.local_time,
|
|
76
|
+
slot.reason || 'n/a',
|
|
77
|
+
]),
|
|
78
|
+
),
|
|
79
|
+
].join('\n\n');
|
|
80
|
+
}
|
|
81
|
+
|
|
39
82
|
const slots = await client.findAvailableSlots(input);
|
|
40
83
|
|
|
41
84
|
if (slots.length === 0) {
|