chatbase 0.0.1 → 0.1.1
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/LICENSE +21 -0
- package/README.md +1651 -6
- package/bin/run.js +4 -0
- package/dist/base/agent-command.js +40 -0
- package/dist/base/agent-ref.js +16 -0
- package/dist/base/assert-file.js +21 -0
- package/dist/base/base-command.js +203 -0
- package/dist/base/body-input.js +77 -0
- package/dist/base/list-command.js +16 -0
- package/dist/base/sources.js +33 -0
- package/dist/client/chat-helpers.js +173 -0
- package/dist/client/client.js +175 -0
- package/dist/client/files.js +64 -0
- package/dist/client/paginate.js +40 -0
- package/dist/client/pairing.js +75 -0
- package/dist/client/retry.js +40 -0
- package/dist/client/signals.js +43 -0
- package/dist/client/stream.js +79 -0
- package/dist/commands/agents/auto-retrain.js +46 -0
- package/dist/commands/agents/clone.js +28 -0
- package/dist/commands/agents/create.js +43 -0
- package/dist/commands/agents/delete.js +41 -0
- package/dist/commands/agents/get.js +52 -0
- package/dist/commands/agents/list.js +48 -0
- package/dist/commands/agents/styles.js +44 -0
- package/dist/commands/agents/train.js +31 -0
- package/dist/commands/agents/update.js +47 -0
- package/dist/commands/api.js +69 -0
- package/dist/commands/auth/login.js +128 -0
- package/dist/commands/auth/logout.js +40 -0
- package/dist/commands/auth/status.js +88 -0
- package/dist/commands/chat/index.js +210 -0
- package/dist/commands/chat/retry.js +49 -0
- package/dist/commands/config/get.js +40 -0
- package/dist/commands/config/list.js +34 -0
- package/dist/commands/config/set.js +106 -0
- package/dist/commands/conversations/export.js +75 -0
- package/dist/commands/conversations/get.js +69 -0
- package/dist/commands/conversations/list.js +75 -0
- package/dist/commands/conversations/tool-result.js +74 -0
- package/dist/commands/health.js +22 -0
- package/dist/commands/helpdesk/statuses.js +31 -0
- package/dist/commands/helpdesk/teams.js +25 -0
- package/dist/commands/messages/feedback.js +64 -0
- package/dist/commands/messages/list.js +55 -0
- package/dist/commands/sources/create.js +134 -0
- package/dist/commands/sources/delete.js +28 -0
- package/dist/commands/sources/get.js +35 -0
- package/dist/commands/sources/list.js +47 -0
- package/dist/commands/sources/restore.js +22 -0
- package/dist/commands/sources/summary.js +33 -0
- package/dist/commands/sources/update.js +76 -0
- package/dist/commands/tickets/create.js +58 -0
- package/dist/commands/tickets/get.js +44 -0
- package/dist/commands/tickets/list.js +96 -0
- package/dist/commands/tickets/messages.js +87 -0
- package/dist/commands/tickets/reply.js +66 -0
- package/dist/commands/tickets/search.js +53 -0
- package/dist/commands/tickets/update.js +38 -0
- package/dist/commands/whatsapp/send-template.js +94 -0
- package/dist/commands/whatsapp/templates.js +55 -0
- package/dist/config/paths.js +22 -0
- package/dist/config/resolve.js +37 -0
- package/dist/config/store.js +38 -0
- package/dist/errors/errors.js +81 -0
- package/dist/hooks/chat-message-hint.js +21 -0
- package/dist/output/color.js +30 -0
- package/dist/output/mode.js +7 -0
- package/dist/output/render.js +0 -0
- package/dist/output/spinner.js +37 -0
- package/dist/repl/chat-repl.js +129 -0
- package/dist/version.js +3 -0
- package/oclif.manifest.json +4264 -0
- package/package.json +96 -4
- package/spec/openapi.json +11880 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { AgentCommand } from '../../base/agent-command.js';
|
|
3
|
+
import { bodyFieldFlags } from '../../base/base-command.js';
|
|
4
|
+
import { readBodyData } from '../../base/body-input.js';
|
|
5
|
+
import { throwIfError } from '../../client/client.js';
|
|
6
|
+
export default class TicketsCreate extends AgentCommand {
|
|
7
|
+
static description = 'Create a helpdesk ticket';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> tickets create --subject "Export failing" -f description="Customer cannot export." --customer-email jane@example.com -a agt_123',
|
|
10
|
+
'<%= config.bin %> tickets create --subject "Export failing" --data \'{"description":"Customer cannot export.","customer":{"email":"jane@example.com"}}\' -a agt_123'
|
|
11
|
+
];
|
|
12
|
+
static flags = {
|
|
13
|
+
...AgentCommand.baseFlags,
|
|
14
|
+
...bodyFieldFlags,
|
|
15
|
+
subject: Flags.string({ description: 'Ticket subject' }),
|
|
16
|
+
'customer-email': Flags.string({
|
|
17
|
+
description: 'Customer email — builds the required customer object (alternative to customer in --data)'
|
|
18
|
+
}),
|
|
19
|
+
'customer-name': Flags.string({
|
|
20
|
+
description: 'Customer display name, used only when the email creates a new customer record',
|
|
21
|
+
dependsOn: ['customer-email']
|
|
22
|
+
}),
|
|
23
|
+
data: Flags.string({
|
|
24
|
+
description: 'JSON body (@file, @-, or inline). Fields: subject, description, customer, statusId, statusCategory, assigneeId, assigneeEmail, teamId'
|
|
25
|
+
})
|
|
26
|
+
};
|
|
27
|
+
async run() {
|
|
28
|
+
const { flags } = await this.parse(TicketsCreate);
|
|
29
|
+
const customerEmail = flags['customer-email'];
|
|
30
|
+
const body = {
|
|
31
|
+
...(await readBodyData(flags.data, flags.field)),
|
|
32
|
+
...(flags.subject && { subject: flags.subject }),
|
|
33
|
+
...(customerEmail && {
|
|
34
|
+
customer: {
|
|
35
|
+
email: customerEmail,
|
|
36
|
+
...(flags['customer-name'] && {
|
|
37
|
+
name: flags['customer-name']
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
};
|
|
42
|
+
const client = this.apiClient(flags);
|
|
43
|
+
const agentId = await this.agentId(flags, client);
|
|
44
|
+
const { data, error, response } = await client.POST('/agents/{agentId}/helpdesk/tickets', {
|
|
45
|
+
params: { path: { agentId } },
|
|
46
|
+
body: body
|
|
47
|
+
});
|
|
48
|
+
throwIfError(response, error);
|
|
49
|
+
const ticketNumber = data.ticketNumber;
|
|
50
|
+
if (flags.json) {
|
|
51
|
+
process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
this.success(flags, `Created ticket ${ticketNumber}`);
|
|
55
|
+
process.stdout.write(`${ticketNumber}\n`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import { AgentCommand } from '../../base/agent-command.js';
|
|
3
|
+
import { throwIfError } from '../../client/client.js';
|
|
4
|
+
const COLUMNS = [
|
|
5
|
+
{ key: 'ticketNumber', header: 'TICKET' },
|
|
6
|
+
{ key: 'subject', header: 'SUBJECT' },
|
|
7
|
+
{ key: 'statusCategory', header: 'STATUS' },
|
|
8
|
+
{ key: 'channel', header: 'CHANNEL' },
|
|
9
|
+
{ key: 'createdAt', header: 'CREATED' }
|
|
10
|
+
];
|
|
11
|
+
export default class TicketsGet extends AgentCommand {
|
|
12
|
+
static description = 'Show one helpdesk ticket';
|
|
13
|
+
static examples = ['<%= config.bin %> tickets get 42 -a agt_123'];
|
|
14
|
+
static args = {
|
|
15
|
+
ticketNumber: Args.integer({
|
|
16
|
+
required: true,
|
|
17
|
+
description: 'Ticket number'
|
|
18
|
+
})
|
|
19
|
+
};
|
|
20
|
+
static flags = { ...AgentCommand.baseFlags };
|
|
21
|
+
async run() {
|
|
22
|
+
const { args, flags } = await this.parse(TicketsGet);
|
|
23
|
+
const client = this.apiClient(flags);
|
|
24
|
+
const agentId = await this.agentId(flags, client);
|
|
25
|
+
const { data, error, response } = await client.GET('/agents/{agentId}/helpdesk/tickets/{ticketNumber}', {
|
|
26
|
+
params: {
|
|
27
|
+
path: { agentId, ticketNumber: args.ticketNumber }
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
throwIfError(response, error);
|
|
31
|
+
// GET .../tickets/{ticketNumber} returns the Ticket directly (no
|
|
32
|
+
// {data, pagination} envelope) — --json prints it as-is.
|
|
33
|
+
const ticket = data;
|
|
34
|
+
this.printData(flags, data, [
|
|
35
|
+
{
|
|
36
|
+
ticketNumber: String(ticket.ticketNumber ?? ''),
|
|
37
|
+
subject: String(ticket.subject ?? ''),
|
|
38
|
+
statusCategory: String(ticket.statusCategory ?? ''),
|
|
39
|
+
channel: String(ticket.channel ?? ''),
|
|
40
|
+
createdAt: String(ticket.createdAt ?? '')
|
|
41
|
+
}
|
|
42
|
+
], COLUMNS);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { ListCommand } from '../../base/list-command.js';
|
|
3
|
+
import { fetchPages } from '../../client/paginate.js';
|
|
4
|
+
const COLUMNS = [
|
|
5
|
+
{ key: 'ticketNumber', header: 'TICKET' },
|
|
6
|
+
{ key: 'subject', header: 'SUBJECT' },
|
|
7
|
+
{ key: 'statusCategory', header: 'STATUS' },
|
|
8
|
+
{ key: 'channel', header: 'CHANNEL' },
|
|
9
|
+
{ key: 'createdAt', header: 'CREATED' }
|
|
10
|
+
];
|
|
11
|
+
export default class TicketsList extends ListCommand {
|
|
12
|
+
static description = 'List helpdesk tickets for an agent';
|
|
13
|
+
static examples = [
|
|
14
|
+
'<%= config.bin %> tickets list -a agt_123',
|
|
15
|
+
'<%= config.bin %> tickets list -a agt_123 --status new,on_you',
|
|
16
|
+
'<%= config.bin %> tickets list -a agt_123 --all --json'
|
|
17
|
+
];
|
|
18
|
+
static flags = {
|
|
19
|
+
...ListCommand.baseFlags,
|
|
20
|
+
status: Flags.string({
|
|
21
|
+
description: 'Filter by status (comma-separated): new, on_you, on_customer, on_hold, closed, cancelled'
|
|
22
|
+
}),
|
|
23
|
+
channel: Flags.string({
|
|
24
|
+
description: 'Filter by channel (comma-separated, e.g. email,api)'
|
|
25
|
+
}),
|
|
26
|
+
'assignee-id': Flags.string({
|
|
27
|
+
description: 'Filter by assignee UUID, or "none" for unassigned'
|
|
28
|
+
}),
|
|
29
|
+
'team-id': Flags.string({
|
|
30
|
+
description: 'Filter by team UUID, or "none" for no team'
|
|
31
|
+
}),
|
|
32
|
+
'created-after': Flags.string({
|
|
33
|
+
description: 'Only tickets created after this ISO 8601 date'
|
|
34
|
+
}),
|
|
35
|
+
'created-before': Flags.string({
|
|
36
|
+
description: 'Only tickets created before this ISO 8601 date'
|
|
37
|
+
}),
|
|
38
|
+
'sort-by': Flags.string({
|
|
39
|
+
description: 'Sort field',
|
|
40
|
+
options: ['createdAt', 'updatedAt', 'lastMessageAt']
|
|
41
|
+
}),
|
|
42
|
+
order: Flags.string({
|
|
43
|
+
description: 'Sort direction',
|
|
44
|
+
options: ['asc', 'desc']
|
|
45
|
+
}),
|
|
46
|
+
'include-total': Flags.boolean({
|
|
47
|
+
description: 'Include pagination.total in the response'
|
|
48
|
+
})
|
|
49
|
+
};
|
|
50
|
+
async run() {
|
|
51
|
+
const { flags } = await this.parse(TicketsList);
|
|
52
|
+
const client = this.apiClient(flags);
|
|
53
|
+
const agentId = await this.agentId(flags, client);
|
|
54
|
+
const extraQuery = {};
|
|
55
|
+
if (flags.status)
|
|
56
|
+
extraQuery.status = flags.status;
|
|
57
|
+
if (flags.channel)
|
|
58
|
+
extraQuery.channel = flags.channel;
|
|
59
|
+
if (flags['assignee-id'])
|
|
60
|
+
extraQuery.assigneeId = flags['assignee-id'];
|
|
61
|
+
if (flags['team-id'])
|
|
62
|
+
extraQuery.teamId = flags['team-id'];
|
|
63
|
+
if (flags['created-after'])
|
|
64
|
+
extraQuery.createdAfter = flags['created-after'];
|
|
65
|
+
if (flags['created-before'])
|
|
66
|
+
extraQuery.createdBefore = flags['created-before'];
|
|
67
|
+
if (flags['sort-by'])
|
|
68
|
+
extraQuery.sortBy = flags['sort-by'];
|
|
69
|
+
if (flags.order)
|
|
70
|
+
extraQuery.order = flags.order;
|
|
71
|
+
if (flags['include-total'])
|
|
72
|
+
extraQuery.includeTotal = true;
|
|
73
|
+
const { pages, items } = await fetchPages((query) => client.GET('/agents/{agentId}/helpdesk/tickets', {
|
|
74
|
+
params: {
|
|
75
|
+
path: { agentId },
|
|
76
|
+
query: { ...query, ...extraQuery }
|
|
77
|
+
}
|
|
78
|
+
}), { limit: flags.limit, cursor: flags.cursor, all: flags.all });
|
|
79
|
+
const rows = items.map((t) => ({
|
|
80
|
+
ticketNumber: String(t.ticketNumber ?? ''),
|
|
81
|
+
subject: String(t.subject ?? ''),
|
|
82
|
+
statusCategory: String(t.statusCategory ?? ''),
|
|
83
|
+
channel: String(t.channel ?? ''),
|
|
84
|
+
createdAt: String(t.createdAt ?? '')
|
|
85
|
+
}));
|
|
86
|
+
const last = pages.at(-1);
|
|
87
|
+
// --json must stay the raw API shape even when --all merges pages
|
|
88
|
+
const raw = pages.length === 1
|
|
89
|
+
? pages[0]
|
|
90
|
+
: { data: items, pagination: last?.pagination };
|
|
91
|
+
this.printData(flags, raw, rows, COLUMNS);
|
|
92
|
+
if (!flags.all && last?.pagination.hasMore && last.pagination.cursor) {
|
|
93
|
+
this.note(flags, `More results: rerun with --cursor ${last.pagination.cursor} (or use --all)`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { ListCommand } from '../../base/list-command.js';
|
|
3
|
+
import { fetchPages } from '../../client/paginate.js';
|
|
4
|
+
import { UsageError } from '../../errors/errors.js';
|
|
5
|
+
const COLUMNS = [
|
|
6
|
+
{ key: 'id', header: 'ID' },
|
|
7
|
+
{ key: 'type', header: 'TYPE' },
|
|
8
|
+
{ key: 'sender', header: 'SENDER' },
|
|
9
|
+
{ key: 'content', header: 'CONTENT' },
|
|
10
|
+
{ key: 'createdAt', header: 'CREATED' }
|
|
11
|
+
];
|
|
12
|
+
function senderLabel(sender) {
|
|
13
|
+
if (!sender)
|
|
14
|
+
return '';
|
|
15
|
+
return sender.name ?? sender.email ?? sender.type ?? '';
|
|
16
|
+
}
|
|
17
|
+
export default class TicketsMessages extends ListCommand {
|
|
18
|
+
static description = "List a ticket's message thread";
|
|
19
|
+
static examples = [
|
|
20
|
+
'<%= config.bin %> tickets messages 42 -a agt_123',
|
|
21
|
+
'<%= config.bin %> tickets messages 42 -a agt_123 --all --json'
|
|
22
|
+
];
|
|
23
|
+
static args = {
|
|
24
|
+
ticketNumber: Args.integer({
|
|
25
|
+
required: false,
|
|
26
|
+
description: 'Ticket number (alternative to --ticket)'
|
|
27
|
+
})
|
|
28
|
+
};
|
|
29
|
+
static flags = {
|
|
30
|
+
...ListCommand.baseFlags,
|
|
31
|
+
ticket: Flags.integer({
|
|
32
|
+
description: 'Ticket number'
|
|
33
|
+
}),
|
|
34
|
+
types: Flags.string({
|
|
35
|
+
description: 'Message types to include (comma-separated): reply, note, event (default: reply,note)'
|
|
36
|
+
}),
|
|
37
|
+
order: Flags.string({
|
|
38
|
+
description: 'Sort direction',
|
|
39
|
+
options: ['asc', 'desc']
|
|
40
|
+
})
|
|
41
|
+
};
|
|
42
|
+
async run() {
|
|
43
|
+
const { args, flags } = await this.parse(TicketsMessages);
|
|
44
|
+
// Positional and flag are alternatives — `tickets get <n>` and
|
|
45
|
+
// `tickets update <n>` set the positional convention, the flag
|
|
46
|
+
// predates it and stays supported.
|
|
47
|
+
const ticketNumber = args.ticketNumber ?? flags.ticket;
|
|
48
|
+
if (ticketNumber === undefined) {
|
|
49
|
+
throw new UsageError('Missing ticket number. Pass it positionally (`tickets messages <number>`) or via --ticket.');
|
|
50
|
+
}
|
|
51
|
+
if (args.ticketNumber !== undefined && flags.ticket !== undefined) {
|
|
52
|
+
throw new UsageError('Pass the ticket number either positionally or via --ticket, not both.');
|
|
53
|
+
}
|
|
54
|
+
const client = this.apiClient(flags);
|
|
55
|
+
const agentId = await this.agentId(flags, client);
|
|
56
|
+
const extraQuery = {};
|
|
57
|
+
if (flags.types)
|
|
58
|
+
extraQuery.types = flags.types;
|
|
59
|
+
if (flags.order)
|
|
60
|
+
extraQuery.order = flags.order;
|
|
61
|
+
const { pages, items } = await fetchPages((query) => client.GET('/agents/{agentId}/helpdesk/tickets/{ticketNumber}/messages', {
|
|
62
|
+
params: {
|
|
63
|
+
path: {
|
|
64
|
+
agentId,
|
|
65
|
+
ticketNumber
|
|
66
|
+
},
|
|
67
|
+
query: { ...query, ...extraQuery }
|
|
68
|
+
}
|
|
69
|
+
}), { limit: flags.limit, cursor: flags.cursor, all: flags.all });
|
|
70
|
+
const rows = items.map((m) => ({
|
|
71
|
+
id: String(m.id ?? ''),
|
|
72
|
+
type: String(m.type ?? ''),
|
|
73
|
+
sender: senderLabel(m.sender),
|
|
74
|
+
content: String(m.contentText ?? m.content ?? ''),
|
|
75
|
+
createdAt: String(m.createdAt ?? '')
|
|
76
|
+
}));
|
|
77
|
+
const last = pages.at(-1);
|
|
78
|
+
// --json must stay the raw API shape even when --all merges pages
|
|
79
|
+
const raw = pages.length === 1
|
|
80
|
+
? pages[0]
|
|
81
|
+
: { data: items, pagination: last?.pagination };
|
|
82
|
+
this.printData(flags, raw, rows, COLUMNS);
|
|
83
|
+
if (!flags.all && last?.pagination.hasMore && last.pagination.cursor) {
|
|
84
|
+
this.note(flags, `More results: rerun with --cursor ${last.pagination.cursor} (or use --all)`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { AgentCommand } from '../../base/agent-command.js';
|
|
3
|
+
import { throwIfError } from '../../client/client.js';
|
|
4
|
+
import { UsageError } from '../../errors/errors.js';
|
|
5
|
+
export default class TicketsReply extends AgentCommand {
|
|
6
|
+
static description = "Post an agent reply to a ticket's message thread";
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> tickets reply 42 -m "On it" --author-email sam@example.com -a agt_123'
|
|
9
|
+
];
|
|
10
|
+
static args = {
|
|
11
|
+
ticketNumber: Args.integer({
|
|
12
|
+
required: false,
|
|
13
|
+
description: 'Ticket number (alternative to --ticket)'
|
|
14
|
+
})
|
|
15
|
+
};
|
|
16
|
+
static flags = {
|
|
17
|
+
...AgentCommand.baseFlags,
|
|
18
|
+
ticket: Flags.integer({
|
|
19
|
+
description: 'Ticket number'
|
|
20
|
+
}),
|
|
21
|
+
message: Flags.string({
|
|
22
|
+
char: 'm',
|
|
23
|
+
required: true,
|
|
24
|
+
description: 'Reply body as GitHub-flavored Markdown'
|
|
25
|
+
}),
|
|
26
|
+
'author-id': Flags.string({
|
|
27
|
+
description: 'Platform user id of the team member the reply is attributed to (exactly one of --author-id/--author-email)',
|
|
28
|
+
exactlyOne: ['author-id', 'author-email']
|
|
29
|
+
}),
|
|
30
|
+
'author-email': Flags.string({
|
|
31
|
+
description: 'Email of the team member the reply is attributed to (exactly one of --author-id/--author-email)',
|
|
32
|
+
exactlyOne: ['author-id', 'author-email']
|
|
33
|
+
})
|
|
34
|
+
};
|
|
35
|
+
async run() {
|
|
36
|
+
const { args, flags } = await this.parse(TicketsReply);
|
|
37
|
+
// Positional and flag are alternatives — `tickets get <n>` and
|
|
38
|
+
// `tickets update <n>` set the positional convention, the flag
|
|
39
|
+
// predates it and stays supported.
|
|
40
|
+
const ticketNumber = args.ticketNumber ?? flags.ticket;
|
|
41
|
+
if (ticketNumber === undefined) {
|
|
42
|
+
throw new UsageError('Missing ticket number. Pass it positionally (`tickets reply <number>`) or via --ticket.');
|
|
43
|
+
}
|
|
44
|
+
if (args.ticketNumber !== undefined && flags.ticket !== undefined) {
|
|
45
|
+
throw new UsageError('Pass the ticket number either positionally or via --ticket, not both.');
|
|
46
|
+
}
|
|
47
|
+
const client = this.apiClient(flags);
|
|
48
|
+
const agentId = await this.agentId(flags, client);
|
|
49
|
+
const body = {
|
|
50
|
+
type: 'reply',
|
|
51
|
+
content: flags.message,
|
|
52
|
+
...(flags['author-id'] ? { authorId: flags['author-id'] } : {}),
|
|
53
|
+
...(flags['author-email']
|
|
54
|
+
? { authorEmail: flags['author-email'] }
|
|
55
|
+
: {})
|
|
56
|
+
};
|
|
57
|
+
const { error, response } = await client.POST('/agents/{agentId}/helpdesk/tickets/{ticketNumber}/messages', {
|
|
58
|
+
params: {
|
|
59
|
+
path: { agentId, ticketNumber }
|
|
60
|
+
},
|
|
61
|
+
body
|
|
62
|
+
});
|
|
63
|
+
throwIfError(response, error);
|
|
64
|
+
this.success(flags, `Reply posted to ticket ${ticketNumber}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { AgentCommand } from '../../base/agent-command.js';
|
|
3
|
+
import { throwIfError } from '../../client/client.js';
|
|
4
|
+
const COLUMNS = [
|
|
5
|
+
{ key: 'ticketNumber', header: 'TICKET' },
|
|
6
|
+
{ key: 'subject', header: 'SUBJECT' },
|
|
7
|
+
{ key: 'statusCategory', header: 'STATUS' },
|
|
8
|
+
{ key: 'channel', header: 'CHANNEL' },
|
|
9
|
+
{ key: 'createdAt', header: 'CREATED' }
|
|
10
|
+
];
|
|
11
|
+
export default class TicketsSearch extends AgentCommand {
|
|
12
|
+
static description = 'Search tickets by message content';
|
|
13
|
+
static examples = [
|
|
14
|
+
'<%= config.bin %> tickets search "refund not received" -a agt_123',
|
|
15
|
+
'<%= config.bin %> tickets search "refund" -a agt_123 --limit 10 --json'
|
|
16
|
+
];
|
|
17
|
+
static args = {
|
|
18
|
+
query: Args.string({
|
|
19
|
+
required: true,
|
|
20
|
+
description: 'Free-text search terms (matched against ticket messages)'
|
|
21
|
+
})
|
|
22
|
+
};
|
|
23
|
+
static flags = {
|
|
24
|
+
...AgentCommand.baseFlags,
|
|
25
|
+
limit: Flags.integer({
|
|
26
|
+
description: 'Number of results (1–50, default 20)',
|
|
27
|
+
min: 1,
|
|
28
|
+
max: 50
|
|
29
|
+
})
|
|
30
|
+
};
|
|
31
|
+
async run() {
|
|
32
|
+
const { args, flags } = await this.parse(TicketsSearch);
|
|
33
|
+
const client = this.apiClient(flags);
|
|
34
|
+
const agentId = await this.agentId(flags, client);
|
|
35
|
+
const { data, error, response } = await client.POST('/agents/{agentId}/helpdesk/tickets/search', {
|
|
36
|
+
params: { path: { agentId } },
|
|
37
|
+
body: {
|
|
38
|
+
query: args.query,
|
|
39
|
+
limit: flags.limit ?? 20
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
throwIfError(response, error);
|
|
43
|
+
const result = data;
|
|
44
|
+
const rows = result.data.map((t) => ({
|
|
45
|
+
ticketNumber: String(t.ticketNumber ?? ''),
|
|
46
|
+
subject: String(t.subject ?? ''),
|
|
47
|
+
statusCategory: String(t.statusCategory ?? ''),
|
|
48
|
+
channel: String(t.channel ?? ''),
|
|
49
|
+
createdAt: String(t.createdAt ?? '')
|
|
50
|
+
}));
|
|
51
|
+
this.printData(flags, data, rows, COLUMNS);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { AgentCommand } from '../../base/agent-command.js';
|
|
3
|
+
import { bodyFieldFlags } from '../../base/base-command.js';
|
|
4
|
+
import { readBodyData } from '../../base/body-input.js';
|
|
5
|
+
import { throwIfError } from '../../client/client.js';
|
|
6
|
+
export default class TicketsUpdate extends AgentCommand {
|
|
7
|
+
static description = "Update a ticket's status, assignee, and/or team";
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> tickets update 42 --data \'{"statusCategory":"closed"}\' -a agt_123'
|
|
10
|
+
];
|
|
11
|
+
static args = {
|
|
12
|
+
ticketNumber: Args.integer({
|
|
13
|
+
required: true,
|
|
14
|
+
description: 'Ticket number'
|
|
15
|
+
})
|
|
16
|
+
};
|
|
17
|
+
static flags = {
|
|
18
|
+
...AgentCommand.baseFlags,
|
|
19
|
+
...bodyFieldFlags,
|
|
20
|
+
data: Flags.string({
|
|
21
|
+
description: 'JSON body (@file, @-, or inline). Fields: statusId, statusCategory, assigneeId, assigneeEmail, teamId'
|
|
22
|
+
})
|
|
23
|
+
};
|
|
24
|
+
async run() {
|
|
25
|
+
const { args, flags } = await this.parse(TicketsUpdate);
|
|
26
|
+
const body = await readBodyData(flags.data, flags.field);
|
|
27
|
+
const client = this.apiClient(flags);
|
|
28
|
+
const agentId = await this.agentId(flags, client);
|
|
29
|
+
const { error, response } = await client.PATCH('/agents/{agentId}/helpdesk/tickets/{ticketNumber}', {
|
|
30
|
+
params: {
|
|
31
|
+
path: { agentId, ticketNumber: args.ticketNumber }
|
|
32
|
+
},
|
|
33
|
+
body: body
|
|
34
|
+
});
|
|
35
|
+
throwIfError(response, error);
|
|
36
|
+
this.success(flags, `Updated ticket ${args.ticketNumber}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { AgentCommand } from '../../base/agent-command.js';
|
|
3
|
+
import { readTextInput } from '../../base/body-input.js';
|
|
4
|
+
import { throwIfError } from '../../client/client.js';
|
|
5
|
+
import { UsageError } from '../../errors/errors.js';
|
|
6
|
+
/** Parse --variables: a JSON object grouped by component, the exact shape
|
|
7
|
+
* `whatsapp templates` reports per template. Leaf validation is the API's
|
|
8
|
+
* job — it returns field-level errors for bad keys or non-string values. */
|
|
9
|
+
async function parseVariables(value) {
|
|
10
|
+
const raw = await readTextInput(value, '--variables');
|
|
11
|
+
let parsed;
|
|
12
|
+
try {
|
|
13
|
+
parsed = JSON.parse(raw);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
throw new UsageError('--variables must be valid JSON (inline, @file, or @-).');
|
|
17
|
+
}
|
|
18
|
+
if (typeof parsed !== 'object' ||
|
|
19
|
+
parsed === null ||
|
|
20
|
+
Array.isArray(parsed)) {
|
|
21
|
+
throw new UsageError('--variables must be a JSON object grouped by component, e.g. \'{"body":{"1":"Jane"}}\'.');
|
|
22
|
+
}
|
|
23
|
+
return parsed;
|
|
24
|
+
}
|
|
25
|
+
export default class WhatsappSendTemplate extends AgentCommand {
|
|
26
|
+
static summary = 'Send an approved WhatsApp template message';
|
|
27
|
+
static description = 'Send an approved WhatsApp template to a phone number from one of the ' +
|
|
28
|
+
'agent’s connected numbers. No user ID is needed — a Chatbase user is ' +
|
|
29
|
+
'resolved or created from the recipient number, and replies flow ' +
|
|
30
|
+
'through the agent’s regular WhatsApp pipeline. Use `whatsapp ' +
|
|
31
|
+
'templates` to see available templates and the variables each expects.';
|
|
32
|
+
static examples = [
|
|
33
|
+
'<%= config.bin %> whatsapp send-template order_update --to 14155552671 -a agt_123',
|
|
34
|
+
'<%= config.bin %> whatsapp send-template order_update --to 14155552671 --language en_US --variables \'{"header":{"1":"#1042"},"body":{"1":"Jane","2":"Friday"}}\' -a agt_123'
|
|
35
|
+
];
|
|
36
|
+
static args = {
|
|
37
|
+
template: Args.string({
|
|
38
|
+
required: true,
|
|
39
|
+
description: 'Name of the approved template'
|
|
40
|
+
})
|
|
41
|
+
};
|
|
42
|
+
static flags = {
|
|
43
|
+
...AgentCommand.baseFlags,
|
|
44
|
+
to: Flags.string({
|
|
45
|
+
required: true,
|
|
46
|
+
description: 'Recipient phone number in international format (digits with country code)'
|
|
47
|
+
}),
|
|
48
|
+
from: Flags.string({
|
|
49
|
+
description: 'Which connected WhatsApp number to send from — optional when the agent has exactly one'
|
|
50
|
+
}),
|
|
51
|
+
language: Flags.string({
|
|
52
|
+
description: 'Template language code (e.g. en_US) — optional when the template exists in a single language'
|
|
53
|
+
}),
|
|
54
|
+
variables: Flags.string({
|
|
55
|
+
description: 'Template variable values as JSON grouped by component (@file, @-, or inline), e.g. \'{"body":{"1":"Jane"}}\''
|
|
56
|
+
})
|
|
57
|
+
};
|
|
58
|
+
async run() {
|
|
59
|
+
const { args, flags } = await this.parse(WhatsappSendTemplate);
|
|
60
|
+
const variables = flags.variables
|
|
61
|
+
? await parseVariables(flags.variables)
|
|
62
|
+
: undefined;
|
|
63
|
+
const client = this.apiClient(flags);
|
|
64
|
+
const agentId = await this.agentId(flags, client);
|
|
65
|
+
const body = {
|
|
66
|
+
to: flags.to,
|
|
67
|
+
...(flags.from ? { from: flags.from } : {}),
|
|
68
|
+
template: {
|
|
69
|
+
name: args.template,
|
|
70
|
+
...(flags.language ? { language: flags.language } : {}),
|
|
71
|
+
// The spec defaults variables to {} and the generated type
|
|
72
|
+
// makes it required — send the default explicitly.
|
|
73
|
+
variables: variables ?? {}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const { data, error, response } = await client.POST('/agents/{agentId}/whatsapp/messages/template', {
|
|
77
|
+
params: { path: { agentId } },
|
|
78
|
+
body
|
|
79
|
+
});
|
|
80
|
+
throwIfError(response, error);
|
|
81
|
+
const sent = data;
|
|
82
|
+
if (flags.json) {
|
|
83
|
+
process.stdout.write(`${JSON.stringify(sent, null, 2)}\n`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
this.success(flags, `Sent template "${args.template}" to ${sent.to}`);
|
|
87
|
+
if (sent.conversationId) {
|
|
88
|
+
this.note(flags, `Conversation: ${sent.conversationId}`);
|
|
89
|
+
}
|
|
90
|
+
if (sent.messageId) {
|
|
91
|
+
process.stdout.write(`${sent.messageId}\n`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { AgentCommand } from '../../base/agent-command.js';
|
|
2
|
+
import { throwIfError } from '../../client/client.js';
|
|
3
|
+
const COLUMNS = [
|
|
4
|
+
{ key: 'name', header: 'NAME' },
|
|
5
|
+
{ key: 'language', header: 'LANGUAGE' },
|
|
6
|
+
{ key: 'category', header: 'CATEGORY' },
|
|
7
|
+
{ key: 'format', header: 'FORMAT' },
|
|
8
|
+
{ key: 'wabaId', header: 'WABA' },
|
|
9
|
+
{ key: 'variables', header: 'VARIABLES' }
|
|
10
|
+
];
|
|
11
|
+
/** {header: ["1"], body: ["1","2"]} → "header:1 body:1,2" — the keys a send
|
|
12
|
+
* must supply, in the grouping `send-template --variables` expects. */
|
|
13
|
+
function variablesSummary(variables) {
|
|
14
|
+
return Object.entries(variables)
|
|
15
|
+
.map(([component, keys]) => `${component}:${keys.join(',')}`)
|
|
16
|
+
.join(' ');
|
|
17
|
+
}
|
|
18
|
+
export default class WhatsappTemplates extends AgentCommand {
|
|
19
|
+
static summary = 'List approved WhatsApp templates for an agent';
|
|
20
|
+
static description = 'List the approved WhatsApp templates available to the agent, across ' +
|
|
21
|
+
'all of its connected WhatsApp Business Accounts. A template can only ' +
|
|
22
|
+
'be sent from a number on its own Business Account — pick a sender ' +
|
|
23
|
+
'whose WABA matches the template’s WABA column.';
|
|
24
|
+
static examples = [
|
|
25
|
+
'<%= config.bin %> whatsapp templates -a agt_123',
|
|
26
|
+
'<%= config.bin %> whatsapp templates -a agt_123 --json'
|
|
27
|
+
];
|
|
28
|
+
static flags = { ...AgentCommand.baseFlags };
|
|
29
|
+
async run() {
|
|
30
|
+
const { flags } = await this.parse(WhatsappTemplates);
|
|
31
|
+
const client = this.apiClient(flags);
|
|
32
|
+
const agentId = await this.agentId(flags, client);
|
|
33
|
+
const { data, error, response } = await client.GET('/agents/{agentId}/whatsapp/templates', { params: { path: { agentId } } });
|
|
34
|
+
throwIfError(response, error);
|
|
35
|
+
const result = data;
|
|
36
|
+
const rows = result.templates.map((t) => ({
|
|
37
|
+
name: t.name,
|
|
38
|
+
language: t.language,
|
|
39
|
+
category: t.category,
|
|
40
|
+
format: t.parameterFormat,
|
|
41
|
+
wabaId: t.wabaId,
|
|
42
|
+
variables: variablesSummary(t.variables)
|
|
43
|
+
}));
|
|
44
|
+
this.printData(flags, result, rows, COLUMNS);
|
|
45
|
+
if (result.senders.length > 0) {
|
|
46
|
+
const senders = result.senders
|
|
47
|
+
.map((s) => `${s.from}${s.verifiedName ? ` (${s.verifiedName})` : ''} — waba ${s.wabaId}`)
|
|
48
|
+
.join('; ');
|
|
49
|
+
this.note(flags, `Senders: ${senders}`);
|
|
50
|
+
}
|
|
51
|
+
if (!result.complete) {
|
|
52
|
+
this.note(flags, `Warning: partial list — could not read WABA(s): ${result.unavailableWabaIds.join(', ')}. Retry to pick them up.`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every path the CLI touches on disk, per the XDG Base Directory spec —
|
|
3
|
+
* config (user-created, not regenerable), state/logs (machine-generated
|
|
4
|
+
* history), cache (disposable — cleanup tools may purge it anytime).
|
|
5
|
+
* These three directories are the CLI's ENTIRE disk footprint; the README's
|
|
6
|
+
* uninstall instructions promise exactly them, so never write anywhere else.
|
|
7
|
+
*
|
|
8
|
+
* Deliberate choices: XDG applies on macOS too (matching `gh`, not
|
|
9
|
+
* ~/Library), and these are functions rather than constants because the
|
|
10
|
+
* env overrides must be read at call time — tests stub XDG_* per test.
|
|
11
|
+
*/
|
|
12
|
+
import os from 'node:os';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
function xdg(envVar, fallback) {
|
|
15
|
+
const v = process.env[envVar];
|
|
16
|
+
return v && v.length > 0 ? v : path.join(os.homedir(), fallback);
|
|
17
|
+
}
|
|
18
|
+
export const configDir = () => path.join(xdg('XDG_CONFIG_HOME', '.config'), 'chatbase');
|
|
19
|
+
export const configFile = () => path.join(configDir(), 'config.json');
|
|
20
|
+
export const stateDir = () => path.join(xdg('XDG_STATE_HOME', '.local/state'), 'chatbase');
|
|
21
|
+
export const logsDir = () => path.join(stateDir(), 'logs');
|
|
22
|
+
export const cacheDir = () => path.join(xdg('XDG_CACHE_HOME', '.cache'), 'chatbase');
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readUserConfig } from './store.js';
|
|
2
|
+
export function resolveApiKey() {
|
|
3
|
+
const env = process.env.CHATBASE_API_KEY;
|
|
4
|
+
if (env && env.length > 0)
|
|
5
|
+
return { value: env, source: 'CHATBASE_API_KEY' };
|
|
6
|
+
const stored = readUserConfig().apiKey;
|
|
7
|
+
if (stored)
|
|
8
|
+
return { value: stored, source: 'user config' };
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
export function resolveAgent(flag) {
|
|
12
|
+
if (flag)
|
|
13
|
+
return { value: flag, source: 'flag' };
|
|
14
|
+
const env = process.env.CHATBASE_AGENT_ID;
|
|
15
|
+
if (env && env.length > 0)
|
|
16
|
+
return { value: env, source: 'CHATBASE_AGENT_ID' };
|
|
17
|
+
const stored = readUserConfig().agent;
|
|
18
|
+
if (stored)
|
|
19
|
+
return { value: stored, source: 'user config' };
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
export function resolveTimeoutMs() {
|
|
23
|
+
const env = process.env.CHATBASE_TIMEOUT;
|
|
24
|
+
if (env && /^\d+$/.test(env))
|
|
25
|
+
return Number(env);
|
|
26
|
+
return readUserConfig().timeoutMs ?? 30000;
|
|
27
|
+
}
|
|
28
|
+
/** Where resolveTimeoutMs()'s value came from — split out for `config get/list`,
|
|
29
|
+
* which need to name the source without duplicating the precedence logic above. */
|
|
30
|
+
export function resolveTimeoutSource() {
|
|
31
|
+
const env = process.env.CHATBASE_TIMEOUT;
|
|
32
|
+
if (env && /^\d+$/.test(env))
|
|
33
|
+
return 'CHATBASE_TIMEOUT';
|
|
34
|
+
if (readUserConfig().timeoutMs !== undefined)
|
|
35
|
+
return 'user config';
|
|
36
|
+
return 'default';
|
|
37
|
+
}
|