zernio-cli 0.4.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 +145 -0
- package/SKILL.md +98 -0
- package/claude/plugin.json +23 -0
- package/claude/skills/zernio/SKILL.md +83 -0
- package/claude/skills/zernio/references/zernio-api-surface.md +48 -0
- package/claude/skills/zernio/references/zernio-best-practices.md +33 -0
- package/claude/skills/zernio/references/zernio-workflows.md +58 -0
- package/dist/client.d.ts +6 -0
- package/dist/client.js +14 -0
- package/dist/commands/accounts.d.ts +3 -0
- package/dist/commands/accounts.js +53 -0
- package/dist/commands/analytics.d.ts +3 -0
- package/dist/commands/analytics.js +85 -0
- package/dist/commands/api.d.ts +2 -0
- package/dist/commands/api.js +108 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.js +138 -0
- package/dist/commands/automations.d.ts +5 -0
- package/dist/commands/automations.js +139 -0
- package/dist/commands/broadcasts.d.ts +5 -0
- package/dist/commands/broadcasts.js +184 -0
- package/dist/commands/contacts.d.ts +6 -0
- package/dist/commands/contacts.js +198 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +51 -0
- package/dist/commands/inbox.d.ts +6 -0
- package/dist/commands/inbox.js +222 -0
- package/dist/commands/media.d.ts +3 -0
- package/dist/commands/media.js +76 -0
- package/dist/commands/platforms.d.ts +2 -0
- package/dist/commands/platforms.js +27 -0
- package/dist/commands/posts.d.ts +3 -0
- package/dist/commands/posts.js +129 -0
- package/dist/commands/profiles.d.ts +3 -0
- package/dist/commands/profiles.js +82 -0
- package/dist/commands/sequences.d.ts +5 -0
- package/dist/commands/sequences.js +178 -0
- package/dist/generated/openapi-catalog.d.ts +8961 -0
- package/dist/generated/openapi-catalog.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +61 -0
- package/dist/utils/api-request.d.ts +31 -0
- package/dist/utils/api-request.js +115 -0
- package/dist/utils/argument-parsing.d.ts +3 -0
- package/dist/utils/argument-parsing.js +27 -0
- package/dist/utils/config.d.ts +27 -0
- package/dist/utils/config.js +111 -0
- package/dist/utils/errors.d.ts +5 -0
- package/dist/utils/errors.js +12 -0
- package/dist/utils/openapi-catalog.d.ts +16 -0
- package/dist/utils/openapi-catalog.js +58 -0
- package/dist/utils/output.d.ts +9 -0
- package/dist/utils/output.js +24 -0
- package/docs/architecture.md +37 -0
- package/docs/cli.md +99 -0
- package/docs/code-standards.md +11 -0
- package/docs/contributing.md +34 -0
- package/docs/development-roadmap.md +32 -0
- package/docs/openapi/zernio-api-openapi.yaml +30967 -0
- package/docs/project-changelog.md +15 -0
- package/docs/project-overview-pdr.md +25 -0
- package/docs/system-architecture.md +28 -0
- package/package.json +82 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { createClient } from '../client.js';
|
|
2
|
+
import { output, outputError } from '../utils/output.js';
|
|
3
|
+
import { handleError } from '../utils/errors.js';
|
|
4
|
+
/** Register post commands: posts:create, posts:list, posts:get, posts:delete, posts:retry */
|
|
5
|
+
export function registerPostCommands(yargs) {
|
|
6
|
+
return yargs
|
|
7
|
+
.command('posts:create', 'Create or schedule a post', (y) => y
|
|
8
|
+
.option('text', { type: 'string', describe: 'Post content text', demandOption: true })
|
|
9
|
+
.option('accounts', { type: 'string', describe: 'Comma-separated account IDs', demandOption: true })
|
|
10
|
+
.option('scheduledAt', { type: 'string', describe: 'ISO 8601 date to schedule (omit to publish now)' })
|
|
11
|
+
.option('draft', { type: 'boolean', describe: 'Save as draft', default: false })
|
|
12
|
+
.option('media', { type: 'string', describe: 'Comma-separated media URLs' })
|
|
13
|
+
.option('title', { type: 'string', describe: 'Post title (YouTube, Reddit, etc.)' })
|
|
14
|
+
.option('tags', { type: 'string', describe: 'Comma-separated tags' })
|
|
15
|
+
.option('hashtags', { type: 'string', describe: 'Comma-separated hashtags' })
|
|
16
|
+
.option('timezone', { type: 'string', describe: 'Timezone (e.g. America/New_York)' }), async (argv) => {
|
|
17
|
+
try {
|
|
18
|
+
const late = createClient();
|
|
19
|
+
// Look up accounts to resolve platform types
|
|
20
|
+
const { data: accountsData } = await late.accounts.listAccounts();
|
|
21
|
+
const allAccounts = accountsData?.accounts || [];
|
|
22
|
+
const accountIds = argv.accounts.split(',').map((s) => s.trim()).filter(Boolean);
|
|
23
|
+
const platforms = accountIds.map((id) => {
|
|
24
|
+
const account = allAccounts.find((a) => (a._id || a.id) === id);
|
|
25
|
+
if (!account) {
|
|
26
|
+
outputError(`Account ${id} not found. Run "late accounts:list" to see available accounts.`, 404);
|
|
27
|
+
}
|
|
28
|
+
return { platform: account.platform, accountId: id };
|
|
29
|
+
});
|
|
30
|
+
// Build media items
|
|
31
|
+
const mediaItems = argv.media
|
|
32
|
+
? argv.media.split(',').map((url) => {
|
|
33
|
+
const trimmed = url.trim();
|
|
34
|
+
const isVideo = /\.(mp4|mov|avi|webm|m4v)$/i.test(trimmed);
|
|
35
|
+
return { type: (isVideo ? 'video' : 'image'), url: trimmed };
|
|
36
|
+
})
|
|
37
|
+
: undefined;
|
|
38
|
+
const body = {
|
|
39
|
+
content: argv.text,
|
|
40
|
+
platforms,
|
|
41
|
+
};
|
|
42
|
+
if (mediaItems?.length)
|
|
43
|
+
body.mediaItems = mediaItems;
|
|
44
|
+
if (argv.title)
|
|
45
|
+
body.title = argv.title;
|
|
46
|
+
if (argv.timezone)
|
|
47
|
+
body.timezone = argv.timezone;
|
|
48
|
+
if (argv.tags)
|
|
49
|
+
body.tags = argv.tags.split(',').map((s) => s.trim());
|
|
50
|
+
if (argv.hashtags)
|
|
51
|
+
body.hashtags = argv.hashtags.split(',').map((s) => s.trim());
|
|
52
|
+
if (argv.draft) {
|
|
53
|
+
body.isDraft = true;
|
|
54
|
+
}
|
|
55
|
+
else if (argv.scheduledAt) {
|
|
56
|
+
body.scheduledFor = argv.scheduledAt;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
body.publishNow = true;
|
|
60
|
+
}
|
|
61
|
+
const { data } = await late.posts.createPost({ body });
|
|
62
|
+
output(data, argv.pretty);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
handleError(err);
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
.command('posts:list', 'List posts', (y) => y
|
|
69
|
+
.option('profileId', { type: 'string', describe: 'Filter by profile ID' })
|
|
70
|
+
.option('status', { type: 'string', describe: 'Filter by status (scheduled, published, failed, draft)' })
|
|
71
|
+
.option('platform', { type: 'string', describe: 'Filter by platform' })
|
|
72
|
+
.option('from', { type: 'string', describe: 'Start date (ISO 8601)' })
|
|
73
|
+
.option('to', { type: 'string', describe: 'End date (ISO 8601)' })
|
|
74
|
+
.option('page', { type: 'number', describe: 'Page number', default: 1 })
|
|
75
|
+
.option('limit', { type: 'number', describe: 'Results per page', default: 10 }), async (argv) => {
|
|
76
|
+
try {
|
|
77
|
+
const late = createClient();
|
|
78
|
+
const query = {
|
|
79
|
+
page: argv.page,
|
|
80
|
+
limit: argv.limit,
|
|
81
|
+
};
|
|
82
|
+
if (argv.profileId)
|
|
83
|
+
query.profileId = argv.profileId;
|
|
84
|
+
if (argv.status)
|
|
85
|
+
query.status = argv.status;
|
|
86
|
+
if (argv.platform)
|
|
87
|
+
query.platform = argv.platform;
|
|
88
|
+
if (argv.from)
|
|
89
|
+
query.dateFrom = argv.from;
|
|
90
|
+
if (argv.to)
|
|
91
|
+
query.dateTo = argv.to;
|
|
92
|
+
const { data } = await late.posts.listPosts({ query });
|
|
93
|
+
output(data, argv.pretty);
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
handleError(err);
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
.command('posts:get <id>', 'Get post details', (y) => y.positional('id', { type: 'string', describe: 'Post ID', demandOption: true }), async (argv) => {
|
|
100
|
+
try {
|
|
101
|
+
const late = createClient();
|
|
102
|
+
const { data } = await late.posts.getPost({ path: { postId: argv.id } });
|
|
103
|
+
output(data, argv.pretty);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
handleError(err);
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
.command('posts:delete <id>', 'Delete a post', (y) => y.positional('id', { type: 'string', describe: 'Post ID', demandOption: true }), async (argv) => {
|
|
110
|
+
try {
|
|
111
|
+
const late = createClient();
|
|
112
|
+
const { data } = await late.posts.deletePost({ path: { postId: argv.id } });
|
|
113
|
+
output(data, argv.pretty);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
handleError(err);
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
.command('posts:retry <id>', 'Retry a failed post', (y) => y.positional('id', { type: 'string', describe: 'Post ID', demandOption: true }), async (argv) => {
|
|
120
|
+
try {
|
|
121
|
+
const late = createClient();
|
|
122
|
+
const { data } = await late.posts.retryPost({ path: { postId: argv.id } });
|
|
123
|
+
output(data, argv.pretty);
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
handleError(err);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createClient } from '../client.js';
|
|
2
|
+
import { output } from '../utils/output.js';
|
|
3
|
+
import { handleError } from '../utils/errors.js';
|
|
4
|
+
/** Register profile commands: profiles:list, profiles:create, profiles:get, profiles:update, profiles:delete */
|
|
5
|
+
export function registerProfileCommands(yargs) {
|
|
6
|
+
return yargs
|
|
7
|
+
.command('profiles:list', 'List all profiles', (y) => y, async (argv) => {
|
|
8
|
+
try {
|
|
9
|
+
const late = createClient();
|
|
10
|
+
const { data } = await late.profiles.listProfiles();
|
|
11
|
+
output(data, argv.pretty);
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
handleError(err);
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
.command('profiles:create', 'Create a new profile', (y) => y
|
|
18
|
+
.option('name', { type: 'string', describe: 'Profile name', demandOption: true })
|
|
19
|
+
.option('description', { type: 'string', describe: 'Profile description' })
|
|
20
|
+
.option('color', { type: 'string', describe: 'Profile color' }), async (argv) => {
|
|
21
|
+
try {
|
|
22
|
+
const late = createClient();
|
|
23
|
+
const body = { name: argv.name };
|
|
24
|
+
if (argv.description)
|
|
25
|
+
body.description = argv.description;
|
|
26
|
+
if (argv.color)
|
|
27
|
+
body.color = argv.color;
|
|
28
|
+
const { data } = await late.profiles.createProfile({ body });
|
|
29
|
+
output(data, argv.pretty);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
handleError(err);
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
.command('profiles:get <id>', 'Get profile details', (y) => y.positional('id', { type: 'string', describe: 'Profile ID', demandOption: true }), async (argv) => {
|
|
36
|
+
try {
|
|
37
|
+
const late = createClient();
|
|
38
|
+
const { data } = await late.profiles.getProfile({ path: { profileId: argv.id } });
|
|
39
|
+
output(data, argv.pretty);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
handleError(err);
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
.command('profiles:update <id>', 'Update a profile', (y) => y
|
|
46
|
+
.positional('id', { type: 'string', describe: 'Profile ID', demandOption: true })
|
|
47
|
+
.option('name', { type: 'string', describe: 'New name' })
|
|
48
|
+
.option('description', { type: 'string', describe: 'New description' })
|
|
49
|
+
.option('color', { type: 'string', describe: 'New color' })
|
|
50
|
+
.option('isDefault', { type: 'boolean', describe: 'Set as default profile' }), async (argv) => {
|
|
51
|
+
try {
|
|
52
|
+
const late = createClient();
|
|
53
|
+
const body = {};
|
|
54
|
+
if (argv.name !== undefined)
|
|
55
|
+
body.name = argv.name;
|
|
56
|
+
if (argv.description !== undefined)
|
|
57
|
+
body.description = argv.description;
|
|
58
|
+
if (argv.color !== undefined)
|
|
59
|
+
body.color = argv.color;
|
|
60
|
+
if (argv.isDefault !== undefined)
|
|
61
|
+
body.isDefault = argv.isDefault;
|
|
62
|
+
const { data } = await late.profiles.updateProfile({
|
|
63
|
+
path: { profileId: argv.id },
|
|
64
|
+
body: body,
|
|
65
|
+
});
|
|
66
|
+
output(data, argv.pretty);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
handleError(err);
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
.command('profiles:delete <id>', 'Delete a profile', (y) => y.positional('id', { type: 'string', describe: 'Profile ID', demandOption: true }), async (argv) => {
|
|
73
|
+
try {
|
|
74
|
+
const late = createClient();
|
|
75
|
+
const { data } = await late.profiles.deleteProfile({ path: { profileId: argv.id } });
|
|
76
|
+
output(data, argv.pretty);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
handleError(err);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { createClient } from '../client.js';
|
|
2
|
+
import { output } from '../utils/output.js';
|
|
3
|
+
import { handleError } from '../utils/errors.js';
|
|
4
|
+
/**
|
|
5
|
+
* Register sequence commands: CRUD, activate, pause, enroll, unenroll, enrollments.
|
|
6
|
+
*/
|
|
7
|
+
export function registerSequenceCommands(yargs) {
|
|
8
|
+
return yargs
|
|
9
|
+
.command('sequences:list', 'List sequences', (y) => y
|
|
10
|
+
.option('profileId', { type: 'string', describe: 'Filter by profile ID' })
|
|
11
|
+
.option('status', { type: 'string', describe: 'Filter by status (draft, active, paused)' })
|
|
12
|
+
.option('limit', { type: 'number', describe: 'Max results', default: 20 })
|
|
13
|
+
.option('skip', { type: 'number', describe: 'Skip N results', default: 0 }), async (argv) => {
|
|
14
|
+
try {
|
|
15
|
+
const late = createClient();
|
|
16
|
+
const query = {
|
|
17
|
+
limit: argv.limit,
|
|
18
|
+
skip: argv.skip,
|
|
19
|
+
};
|
|
20
|
+
if (argv.profileId)
|
|
21
|
+
query.profileId = argv.profileId;
|
|
22
|
+
if (argv.status)
|
|
23
|
+
query.status = argv.status;
|
|
24
|
+
const { data } = await late.sequences.listSequences({ query });
|
|
25
|
+
output(data, argv.pretty);
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
handleError(err);
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
.command('sequences:create', 'Create a sequence', (y) => y
|
|
32
|
+
.option('profileId', { type: 'string', describe: 'Profile ID', demandOption: true })
|
|
33
|
+
.option('accountId', { type: 'string', describe: 'Account ID', demandOption: true })
|
|
34
|
+
.option('platform', { type: 'string', describe: 'Platform', demandOption: true })
|
|
35
|
+
.option('name', { type: 'string', describe: 'Sequence name', demandOption: true })
|
|
36
|
+
.option('stepsFile', { type: 'string', describe: 'Path to JSON file with steps array' })
|
|
37
|
+
.option('exitOnReply', { type: 'boolean', describe: 'Exit sequence when contact replies', default: true })
|
|
38
|
+
.option('exitOnUnsubscribe', { type: 'boolean', describe: 'Exit sequence when contact unsubscribes', default: true }), async (argv) => {
|
|
39
|
+
try {
|
|
40
|
+
const late = createClient();
|
|
41
|
+
const body = {
|
|
42
|
+
profileId: argv.profileId,
|
|
43
|
+
accountId: argv.accountId,
|
|
44
|
+
platform: argv.platform,
|
|
45
|
+
name: argv.name,
|
|
46
|
+
exitOnReply: argv.exitOnReply,
|
|
47
|
+
exitOnUnsubscribe: argv.exitOnUnsubscribe,
|
|
48
|
+
};
|
|
49
|
+
// Steps can be provided via a JSON file for complex sequences
|
|
50
|
+
if (argv.stepsFile) {
|
|
51
|
+
const { readFileSync } = await import('fs');
|
|
52
|
+
const raw = readFileSync(argv.stepsFile, 'utf-8');
|
|
53
|
+
body.steps = JSON.parse(raw);
|
|
54
|
+
}
|
|
55
|
+
const { data } = await late.sequences.createSequence({ body: body });
|
|
56
|
+
output(data, argv.pretty);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
handleError(err);
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
.command('sequences:get <id>', 'Get sequence details with steps', (y) => y.positional('id', { type: 'string', describe: 'Sequence ID', demandOption: true }), async (argv) => {
|
|
63
|
+
try {
|
|
64
|
+
const late = createClient();
|
|
65
|
+
const { data } = await late.sequences.getSequence({ path: { sequenceId: argv.id } });
|
|
66
|
+
output(data, argv.pretty);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
handleError(err);
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
.command('sequences:update <id>', 'Update a sequence', (y) => y
|
|
73
|
+
.positional('id', { type: 'string', describe: 'Sequence ID', demandOption: true })
|
|
74
|
+
.option('name', { type: 'string', describe: 'Sequence name' })
|
|
75
|
+
.option('stepsFile', { type: 'string', describe: 'Path to JSON file with steps array' })
|
|
76
|
+
.option('exitOnReply', { type: 'boolean', describe: 'Exit on reply' })
|
|
77
|
+
.option('exitOnUnsubscribe', { type: 'boolean', describe: 'Exit on unsubscribe' }), async (argv) => {
|
|
78
|
+
try {
|
|
79
|
+
const late = createClient();
|
|
80
|
+
const { data } = await late.sequences.updateSequence({
|
|
81
|
+
path: { sequenceId: argv.id },
|
|
82
|
+
});
|
|
83
|
+
output(data, argv.pretty);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
handleError(err);
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
.command('sequences:delete <id>', 'Delete a sequence', (y) => y.positional('id', { type: 'string', describe: 'Sequence ID', demandOption: true }), async (argv) => {
|
|
90
|
+
try {
|
|
91
|
+
const late = createClient();
|
|
92
|
+
const { data } = await late.sequences.deleteSequence({ path: { sequenceId: argv.id } });
|
|
93
|
+
output(data, argv.pretty);
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
handleError(err);
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
.command('sequences:activate <id>', 'Activate a sequence', (y) => y.positional('id', { type: 'string', describe: 'Sequence ID', demandOption: true }), async (argv) => {
|
|
100
|
+
try {
|
|
101
|
+
const late = createClient();
|
|
102
|
+
const { data } = await late.sequences.activateSequence({ path: { sequenceId: argv.id } });
|
|
103
|
+
output(data, argv.pretty);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
handleError(err);
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
.command('sequences:pause <id>', 'Pause a sequence', (y) => y.positional('id', { type: 'string', describe: 'Sequence ID', demandOption: true }), async (argv) => {
|
|
110
|
+
try {
|
|
111
|
+
const late = createClient();
|
|
112
|
+
const { data } = await late.sequences.pauseSequence({ path: { sequenceId: argv.id } });
|
|
113
|
+
output(data, argv.pretty);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
handleError(err);
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
.command('sequences:enroll <id>', 'Enroll contacts into a sequence', (y) => y
|
|
120
|
+
.positional('id', { type: 'string', describe: 'Sequence ID', demandOption: true })
|
|
121
|
+
.option('contactIds', { type: 'string', describe: 'Comma-separated contact IDs', demandOption: true })
|
|
122
|
+
.option('channelIds', { type: 'string', describe: 'Comma-separated channel IDs (optional, uses default channel per contact)' }), async (argv) => {
|
|
123
|
+
try {
|
|
124
|
+
const late = createClient();
|
|
125
|
+
const body = {
|
|
126
|
+
contactIds: argv.contactIds.split(',').map((s) => s.trim()),
|
|
127
|
+
};
|
|
128
|
+
if (argv.channelIds) {
|
|
129
|
+
body.channelIds = argv.channelIds.split(',').map((s) => s.trim());
|
|
130
|
+
}
|
|
131
|
+
const { data } = await late.sequences.enrollContacts({
|
|
132
|
+
path: { sequenceId: argv.id },
|
|
133
|
+
body: body,
|
|
134
|
+
});
|
|
135
|
+
output(data, argv.pretty);
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
handleError(err);
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
.command('sequences:unenroll <id> <contactId>', 'Unenroll a contact from a sequence', (y) => y
|
|
142
|
+
.positional('id', { type: 'string', describe: 'Sequence ID', demandOption: true })
|
|
143
|
+
.positional('contactId', { type: 'string', describe: 'Contact ID', demandOption: true }), async (argv) => {
|
|
144
|
+
try {
|
|
145
|
+
const late = createClient();
|
|
146
|
+
const { data } = await late.sequences.unenrollContact({
|
|
147
|
+
path: { sequenceId: argv.id, contactId: argv.contactId },
|
|
148
|
+
});
|
|
149
|
+
output(data, argv.pretty);
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
handleError(err);
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
.command('sequences:enrollments <id>', 'List enrollments for a sequence', (y) => y
|
|
156
|
+
.positional('id', { type: 'string', describe: 'Sequence ID', demandOption: true })
|
|
157
|
+
.option('status', { type: 'string', describe: 'Filter by status (active, completed, exited, paused)' })
|
|
158
|
+
.option('limit', { type: 'number', describe: 'Max results', default: 50 })
|
|
159
|
+
.option('skip', { type: 'number', describe: 'Skip N results', default: 0 }), async (argv) => {
|
|
160
|
+
try {
|
|
161
|
+
const late = createClient();
|
|
162
|
+
const query = {
|
|
163
|
+
limit: argv.limit,
|
|
164
|
+
skip: argv.skip,
|
|
165
|
+
};
|
|
166
|
+
if (argv.status)
|
|
167
|
+
query.status = argv.status;
|
|
168
|
+
const { data } = await late.sequences.listSequenceEnrollments({
|
|
169
|
+
path: { sequenceId: argv.id },
|
|
170
|
+
query,
|
|
171
|
+
});
|
|
172
|
+
output(data, argv.pretty);
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
handleError(err);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|