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,74 @@
|
|
|
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
|
+
export default class ConversationsToolResult extends AgentCommand {
|
|
7
|
+
static summary = 'Submit a client-side tool result to a chat turn';
|
|
8
|
+
static description = 'Submit the result of a client-side tool call so the paused chat ' +
|
|
9
|
+
'turn can continue. The tool call ID comes from the tool-call part ' +
|
|
10
|
+
'of the chat response that requested the execution.';
|
|
11
|
+
static examples = [
|
|
12
|
+
'<%= config.bin %> conversations tool-result conv_123 --tool-call-id tc_1 --output \'{"temperature": 72}\' -a agt_123',
|
|
13
|
+
'<%= config.bin %> conversations tool-result conv_123 --tool-call-id tc_1 --output @result.json -a agt_123',
|
|
14
|
+
'<%= config.bin %> conversations tool-result conv_123 --tool-call-id tc_1 -a agt_123'
|
|
15
|
+
];
|
|
16
|
+
static args = {
|
|
17
|
+
conversationId: Args.string({
|
|
18
|
+
required: false,
|
|
19
|
+
description: 'Conversation ID (alternative to --conversation)'
|
|
20
|
+
})
|
|
21
|
+
};
|
|
22
|
+
static flags = {
|
|
23
|
+
...AgentCommand.baseFlags,
|
|
24
|
+
conversation: Flags.string({
|
|
25
|
+
description: 'Conversation ID'
|
|
26
|
+
}),
|
|
27
|
+
'tool-call-id': Flags.string({
|
|
28
|
+
required: true,
|
|
29
|
+
description: 'The toolCallId from the tool-call part in the chat response'
|
|
30
|
+
}),
|
|
31
|
+
output: Flags.string({
|
|
32
|
+
description: 'Result of executing the tool (inline JSON, @file, or @-); ' +
|
|
33
|
+
'a value that is not valid JSON is sent as a plain string'
|
|
34
|
+
})
|
|
35
|
+
};
|
|
36
|
+
async run() {
|
|
37
|
+
const { args, flags } = await this.parse(ConversationsToolResult);
|
|
38
|
+
// Positional and flag are alternatives — `agents get <id>` set the
|
|
39
|
+
// positional convention, the flag predates it and stays supported.
|
|
40
|
+
const conversationId = args.conversationId ?? flags.conversation;
|
|
41
|
+
if (!conversationId) {
|
|
42
|
+
throw new UsageError('Missing conversation ID. Pass it positionally (`conversations tool-result <id> ...`) or via --conversation.');
|
|
43
|
+
}
|
|
44
|
+
if (args.conversationId && flags.conversation) {
|
|
45
|
+
throw new UsageError('Pass the conversation ID either positionally or via --conversation, not both.');
|
|
46
|
+
}
|
|
47
|
+
const body = {
|
|
48
|
+
toolCallId: flags['tool-call-id']
|
|
49
|
+
};
|
|
50
|
+
if (flags.output !== undefined) {
|
|
51
|
+
const raw = await readTextInput(flags.output, '--output');
|
|
52
|
+
// Tool outputs are arbitrary values in the spec: parse JSON when
|
|
53
|
+
// it is JSON, otherwise pass the text through as a string.
|
|
54
|
+
try {
|
|
55
|
+
body.output = JSON.parse(raw);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
body.output = raw;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const client = this.apiClient(flags);
|
|
62
|
+
const agentId = await this.agentId(flags, client);
|
|
63
|
+
const { data, error, response } = await client.POST('/agents/{agentId}/conversations/{conversationId}/tool-result', {
|
|
64
|
+
params: { path: { agentId, conversationId } },
|
|
65
|
+
body
|
|
66
|
+
});
|
|
67
|
+
throwIfError(response, error);
|
|
68
|
+
if (flags.json) {
|
|
69
|
+
process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
this.success(flags, `Tool result submitted for ${flags['tool-call-id']}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { BaseCommand } from '../base/base-command.js';
|
|
2
|
+
import { throwIfError } from '../client/client.js';
|
|
3
|
+
export default class Health extends BaseCommand {
|
|
4
|
+
static description = 'Check that the Chatbase API is reachable';
|
|
5
|
+
static examples = [
|
|
6
|
+
'<%= config.bin %> health',
|
|
7
|
+
'<%= config.bin %> health --json'
|
|
8
|
+
];
|
|
9
|
+
static flags = { ...BaseCommand.baseFlags };
|
|
10
|
+
requireAuth = false;
|
|
11
|
+
async run() {
|
|
12
|
+
const { flags } = await this.parse(Health);
|
|
13
|
+
const client = this.apiClient(flags);
|
|
14
|
+
const { data, error, response } = await client.GET('/health');
|
|
15
|
+
throwIfError(response, error);
|
|
16
|
+
if (this.mode(flags) === 'json') {
|
|
17
|
+
process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
this.success(flags, `API is up (status: ${data?.status})`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { AgentCommand } from '../../base/agent-command.js';
|
|
2
|
+
import { throwIfError } from '../../client/client.js';
|
|
3
|
+
const COLUMNS = [
|
|
4
|
+
{ key: 'id', header: 'ID' },
|
|
5
|
+
{ key: 'category', header: 'CATEGORY' },
|
|
6
|
+
{ key: 'label', header: 'LABEL' }
|
|
7
|
+
];
|
|
8
|
+
export default class HelpdeskStatuses extends AgentCommand {
|
|
9
|
+
static description = 'List ticket statuses for an agent';
|
|
10
|
+
static examples = [
|
|
11
|
+
'<%= config.bin %> helpdesk statuses -a agt_123'
|
|
12
|
+
];
|
|
13
|
+
static flags = { ...AgentCommand.baseFlags };
|
|
14
|
+
async run() {
|
|
15
|
+
const { flags } = await this.parse(HelpdeskStatuses);
|
|
16
|
+
const client = this.apiClient(flags);
|
|
17
|
+
const agentId = await this.agentId(flags, client);
|
|
18
|
+
const { data, error, response } = await client.GET('/agents/{agentId}/helpdesk/ticket-statuses', { params: { path: { agentId } } });
|
|
19
|
+
throwIfError(response, error);
|
|
20
|
+
// TicketStatusList is a bare array, not wrapped in {data, pagination}.
|
|
21
|
+
const statuses = data;
|
|
22
|
+
const rows = statuses.map((s) => ({
|
|
23
|
+
id: String(s.id ?? ''),
|
|
24
|
+
category: String(s.category ?? ''),
|
|
25
|
+
// internalLabel is the dashboard/agent-facing label — the more
|
|
26
|
+
// relevant of the two for a CLI operator picking a statusId.
|
|
27
|
+
label: String(s.internalLabel ?? '')
|
|
28
|
+
}));
|
|
29
|
+
this.printData(flags, data, rows, COLUMNS);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { AgentCommand } from '../../base/agent-command.js';
|
|
2
|
+
import { throwIfError } from '../../client/client.js';
|
|
3
|
+
const COLUMNS = [
|
|
4
|
+
{ key: 'id', header: 'ID' },
|
|
5
|
+
{ key: 'name', header: 'NAME' }
|
|
6
|
+
];
|
|
7
|
+
export default class HelpdeskTeams extends AgentCommand {
|
|
8
|
+
static description = 'List helpdesk teams for an agent';
|
|
9
|
+
static examples = ['<%= config.bin %> helpdesk teams -a agt_123'];
|
|
10
|
+
static flags = { ...AgentCommand.baseFlags };
|
|
11
|
+
async run() {
|
|
12
|
+
const { flags } = await this.parse(HelpdeskTeams);
|
|
13
|
+
const client = this.apiClient(flags);
|
|
14
|
+
const agentId = await this.agentId(flags, client);
|
|
15
|
+
const { data, error, response } = await client.GET('/agents/{agentId}/helpdesk/teams', { params: { path: { agentId } } });
|
|
16
|
+
throwIfError(response, error);
|
|
17
|
+
// TeamListResponse is a bare array, not wrapped in {data, pagination}.
|
|
18
|
+
const teams = data;
|
|
19
|
+
const rows = teams.map((t) => ({
|
|
20
|
+
id: String(t.id ?? ''),
|
|
21
|
+
name: String(t.name ?? '')
|
|
22
|
+
}));
|
|
23
|
+
this.printData(flags, data, rows, COLUMNS);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
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 MessagesFeedback extends AgentCommand {
|
|
6
|
+
static description = 'Set or clear user feedback on an assistant message';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> messages feedback msg_1 --conversation conv_123 --rating positive -a agt_123',
|
|
9
|
+
'<%= config.bin %> messages feedback --conversation conv_123 --message msg_1 --rating clear -a agt_123'
|
|
10
|
+
];
|
|
11
|
+
static args = {
|
|
12
|
+
messageId: Args.string({
|
|
13
|
+
required: false,
|
|
14
|
+
description: 'Message ID (alternative to --message)'
|
|
15
|
+
})
|
|
16
|
+
};
|
|
17
|
+
static flags = {
|
|
18
|
+
...AgentCommand.baseFlags,
|
|
19
|
+
conversation: Flags.string({
|
|
20
|
+
required: true,
|
|
21
|
+
description: 'Conversation ID'
|
|
22
|
+
}),
|
|
23
|
+
message: Flags.string({
|
|
24
|
+
description: 'Message ID'
|
|
25
|
+
}),
|
|
26
|
+
rating: Flags.string({
|
|
27
|
+
required: true,
|
|
28
|
+
options: ['positive', 'negative', 'clear'],
|
|
29
|
+
description: 'Feedback value ("clear" removes existing feedback)'
|
|
30
|
+
})
|
|
31
|
+
};
|
|
32
|
+
async run() {
|
|
33
|
+
const { args, flags } = await this.parse(MessagesFeedback);
|
|
34
|
+
// Positional and flag are alternatives — `agents get <id>` set the
|
|
35
|
+
// positional convention, the flag predates it and stays supported.
|
|
36
|
+
const messageId = args.messageId ?? flags.message;
|
|
37
|
+
if (!messageId) {
|
|
38
|
+
throw new UsageError('Missing message ID. Pass it positionally (`messages feedback <id> ...`) or via --message.');
|
|
39
|
+
}
|
|
40
|
+
if (args.messageId && flags.message) {
|
|
41
|
+
throw new UsageError('Pass the message ID either positionally or via --message, not both.');
|
|
42
|
+
}
|
|
43
|
+
const client = this.apiClient(flags);
|
|
44
|
+
const agentId = await this.agentId(flags, client);
|
|
45
|
+
const feedback = flags.rating === 'positive'
|
|
46
|
+
? 'positive'
|
|
47
|
+
: flags.rating === 'negative'
|
|
48
|
+
? 'negative'
|
|
49
|
+
: null;
|
|
50
|
+
const body = { feedback };
|
|
51
|
+
const { error, response } = await client.PATCH('/agents/{agentId}/conversations/{conversationId}/messages/{messageId}/feedback', {
|
|
52
|
+
params: {
|
|
53
|
+
path: {
|
|
54
|
+
agentId,
|
|
55
|
+
conversationId: flags.conversation,
|
|
56
|
+
messageId
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
body
|
|
60
|
+
});
|
|
61
|
+
throwIfError(response, error);
|
|
62
|
+
this.success(flags, `Feedback updated for message ${messageId}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { ListCommand } from '../../base/list-command.js';
|
|
3
|
+
import { fetchPages } from '../../client/paginate.js';
|
|
4
|
+
import { formatEpochSeconds } from '../../output/render.js';
|
|
5
|
+
const COLUMNS = [
|
|
6
|
+
{ key: 'id', header: 'ID' },
|
|
7
|
+
{ key: 'role', header: 'ROLE' },
|
|
8
|
+
{ key: 'createdAt', header: 'CREATED' }
|
|
9
|
+
];
|
|
10
|
+
export default class MessagesList extends ListCommand {
|
|
11
|
+
static description = 'List messages in a conversation';
|
|
12
|
+
static examples = [
|
|
13
|
+
'<%= config.bin %> messages list --conversation conv_123 -a agt_123',
|
|
14
|
+
'<%= config.bin %> messages list --conversation conv_123 -a agt_123 --all --json'
|
|
15
|
+
];
|
|
16
|
+
static flags = {
|
|
17
|
+
...ListCommand.baseFlags,
|
|
18
|
+
conversation: Flags.string({
|
|
19
|
+
required: true,
|
|
20
|
+
description: 'Conversation ID'
|
|
21
|
+
})
|
|
22
|
+
};
|
|
23
|
+
async run() {
|
|
24
|
+
const { flags } = await this.parse(MessagesList);
|
|
25
|
+
const client = this.apiClient(flags);
|
|
26
|
+
const agentId = await this.agentId(flags, client);
|
|
27
|
+
const { pages, items } = await fetchPages((query) => client.GET('/agents/{agentId}/conversations/{conversationId}/messages', {
|
|
28
|
+
params: {
|
|
29
|
+
path: {
|
|
30
|
+
agentId,
|
|
31
|
+
conversationId: flags.conversation
|
|
32
|
+
},
|
|
33
|
+
query
|
|
34
|
+
}
|
|
35
|
+
}), { limit: flags.limit, cursor: flags.cursor, all: flags.all });
|
|
36
|
+
// Humans read ISO dates; --plain keeps the raw epoch for scripts.
|
|
37
|
+
const formatTimestamp = this.mode(flags) === 'pretty'
|
|
38
|
+
? formatEpochSeconds
|
|
39
|
+
: (v) => String(v ?? '');
|
|
40
|
+
const rows = items.map((m) => ({
|
|
41
|
+
id: String(m.id ?? ''),
|
|
42
|
+
role: String(m.role ?? ''),
|
|
43
|
+
createdAt: formatTimestamp(m.createdAt)
|
|
44
|
+
}));
|
|
45
|
+
const last = pages.at(-1);
|
|
46
|
+
// --json must stay the raw API shape even when --all merges pages
|
|
47
|
+
const raw = pages.length === 1
|
|
48
|
+
? pages[0]
|
|
49
|
+
: { data: items, pagination: last?.pagination };
|
|
50
|
+
this.printData(flags, raw, rows, COLUMNS);
|
|
51
|
+
if (!flags.all && last?.pagination.hasMore && last.pagination.cursor) {
|
|
52
|
+
this.note(flags, `More results: rerun with --cursor ${last.pagination.cursor} (or use --all)`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { AgentCommand } from '../../base/agent-command.js';
|
|
3
|
+
import { assertFileReadable } from '../../base/assert-file.js';
|
|
4
|
+
import { bodyFieldFlags } from '../../base/base-command.js';
|
|
5
|
+
import { readBodyData, readTextInput } from '../../base/body-input.js';
|
|
6
|
+
import { throwIfError } from '../../client/client.js';
|
|
7
|
+
import { filesHostMismatchWarning, uploadFileSource } from '../../client/files.js';
|
|
8
|
+
import { resolveApiKey } from '../../config/resolve.js';
|
|
9
|
+
import { UsageError } from '../../errors/errors.js';
|
|
10
|
+
import { maybeSpinner } from '../../output/spinner.js';
|
|
11
|
+
/** Merges --data (base) with per-type flags (win). Link fields default to
|
|
12
|
+
* empty/false because the API schema marks them required despite having defaults. */
|
|
13
|
+
async function buildSourceBody(flags) {
|
|
14
|
+
const body = {
|
|
15
|
+
...(await readBodyData(flags.data, flags.field)),
|
|
16
|
+
type: flags.type
|
|
17
|
+
};
|
|
18
|
+
if (flags.type === 'text') {
|
|
19
|
+
if (flags.name)
|
|
20
|
+
body.name = flags.name;
|
|
21
|
+
if (flags.content)
|
|
22
|
+
body.content = await readTextInput(flags.content);
|
|
23
|
+
}
|
|
24
|
+
else if (flags.type === 'qna') {
|
|
25
|
+
if (flags.name)
|
|
26
|
+
body.name = flags.name;
|
|
27
|
+
}
|
|
28
|
+
else if (flags.type === 'link') {
|
|
29
|
+
if (flags.url)
|
|
30
|
+
body.url = flags.url;
|
|
31
|
+
if (flags['link-type'])
|
|
32
|
+
body.linkType = flags['link-type'];
|
|
33
|
+
body.excludePaths ??= [];
|
|
34
|
+
body.includeOnlyPaths ??= [];
|
|
35
|
+
body.slowScraping ??= false;
|
|
36
|
+
}
|
|
37
|
+
return body;
|
|
38
|
+
}
|
|
39
|
+
export default class SourcesCreate extends AgentCommand {
|
|
40
|
+
static description = 'Create a source: text/qna/link (JSON) or a file upload';
|
|
41
|
+
static examples = [
|
|
42
|
+
'<%= config.bin %> sources create --type text --name Guide --content "hello" -a agt_123',
|
|
43
|
+
'<%= config.bin %> sources create --type link --url https://example.com --link-type crawl -a agt_123',
|
|
44
|
+
'<%= config.bin %> sources create --type qna --data \'{"questions":["Q1"],"answer":"A1"}\' -a agt_123',
|
|
45
|
+
'<%= config.bin %> sources create --file ./guide.pdf -a agt_123'
|
|
46
|
+
];
|
|
47
|
+
static flags = {
|
|
48
|
+
...AgentCommand.baseFlags,
|
|
49
|
+
...bodyFieldFlags,
|
|
50
|
+
type: Flags.string({
|
|
51
|
+
options: ['text', 'qna', 'link'],
|
|
52
|
+
description: 'JSON source type (mutually exclusive with --file)',
|
|
53
|
+
exclusive: ['file']
|
|
54
|
+
}),
|
|
55
|
+
file: Flags.string({
|
|
56
|
+
description: 'Path to a file to upload as a source (mutually exclusive with --type)',
|
|
57
|
+
exclusive: ['type', 'data', 'content', 'url', 'link-type']
|
|
58
|
+
}),
|
|
59
|
+
name: Flags.string({
|
|
60
|
+
description: 'Source name (--type text/qna, or a file upload)'
|
|
61
|
+
}),
|
|
62
|
+
content: Flags.string({
|
|
63
|
+
description: 'Text content for --type text (@file, @-, or inline)'
|
|
64
|
+
}),
|
|
65
|
+
url: Flags.string({ description: 'URL for --type link' }),
|
|
66
|
+
'link-type': Flags.string({
|
|
67
|
+
options: ['individual', 'sitemap', 'crawl'],
|
|
68
|
+
description: 'Link crawl mode for --type link'
|
|
69
|
+
}),
|
|
70
|
+
data: Flags.string({
|
|
71
|
+
description: 'JSON body (@file, @-, or inline); per-type flags override matching keys'
|
|
72
|
+
})
|
|
73
|
+
};
|
|
74
|
+
async run() {
|
|
75
|
+
const { flags } = await this.parse(SourcesCreate);
|
|
76
|
+
if (!flags.type && !flags.file) {
|
|
77
|
+
throw new UsageError('Specify --type <text|qna|link> for a JSON source, or --file <path> to upload a file.');
|
|
78
|
+
}
|
|
79
|
+
// Validated before any network call, including agent-name resolution below.
|
|
80
|
+
if (flags.file)
|
|
81
|
+
assertFileReadable(flags.file);
|
|
82
|
+
const client = this.apiClient(flags);
|
|
83
|
+
const agentId = await this.agentId(flags, client);
|
|
84
|
+
let id;
|
|
85
|
+
if (flags.file) {
|
|
86
|
+
const resolved = resolveApiKey();
|
|
87
|
+
if (!resolved) {
|
|
88
|
+
// Unreachable in practice: this.apiClient() above already
|
|
89
|
+
// required auth and would have thrown first.
|
|
90
|
+
throw new UsageError('Not authenticated. Run `chatbase auth login`, or set CHATBASE_API_KEY.');
|
|
91
|
+
}
|
|
92
|
+
const mismatch = filesHostMismatchWarning();
|
|
93
|
+
if (mismatch) {
|
|
94
|
+
this.note(flags, this.palette(flags).yellow(mismatch));
|
|
95
|
+
}
|
|
96
|
+
const stop = maybeSpinner(flags.quiet, `Uploading ${flags.file}…`);
|
|
97
|
+
try {
|
|
98
|
+
const uploaded = await uploadFileSource({
|
|
99
|
+
agentId,
|
|
100
|
+
filePath: flags.file,
|
|
101
|
+
name: flags.name,
|
|
102
|
+
apiKey: resolved.value,
|
|
103
|
+
verbose: flags.verbose
|
|
104
|
+
});
|
|
105
|
+
id = uploaded.id;
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
stop();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
const body = await buildSourceBody(flags);
|
|
113
|
+
// The API requires a name for qna sources (unlike text, which
|
|
114
|
+
// defaults one) — fail fast locally instead of a server 400.
|
|
115
|
+
if (flags.type === 'qna' && !body.name) {
|
|
116
|
+
throw new UsageError('--name is required for --type qna (or include "name" in --data).');
|
|
117
|
+
}
|
|
118
|
+
const { data, error, response } = await client.POST('/agents/{agentId}/sources', {
|
|
119
|
+
params: { path: { agentId } },
|
|
120
|
+
body: body
|
|
121
|
+
});
|
|
122
|
+
throwIfError(response, error);
|
|
123
|
+
id = data.id;
|
|
124
|
+
}
|
|
125
|
+
if (flags.json) {
|
|
126
|
+
process.stdout.write(`${JSON.stringify({ id }, null, 2)}\n`);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
this.success(flags, `Created source ${id} (untrained)`);
|
|
130
|
+
this.note(flags, `→ chatbase sources get ${id} -a ${agentId}`);
|
|
131
|
+
process.stdout.write(`${id}\n`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import { AgentCommand } from '../../base/agent-command.js';
|
|
3
|
+
import { throwIfError } from '../../client/client.js';
|
|
4
|
+
export default class SourcesDelete extends AgentCommand {
|
|
5
|
+
static description = 'Delete a source (restorable via restore command)';
|
|
6
|
+
static examples = [
|
|
7
|
+
'<%= config.bin %> sources delete src_1 -a agt_1'
|
|
8
|
+
];
|
|
9
|
+
static args = {
|
|
10
|
+
sourceId: Args.string({ required: true, description: 'Source ID' })
|
|
11
|
+
};
|
|
12
|
+
async run() {
|
|
13
|
+
const { args, flags } = await this.parse(SourcesDelete);
|
|
14
|
+
const client = this.apiClient(flags);
|
|
15
|
+
const agentId = await this.agentId(flags, client);
|
|
16
|
+
const { data, error, response } = await client.DELETE('/agents/{agentId}/sources/{sourceId}', {
|
|
17
|
+
params: { path: { agentId, sourceId: args.sourceId } }
|
|
18
|
+
});
|
|
19
|
+
throwIfError(response, error);
|
|
20
|
+
this.success(flags, `Deleted source ${args.sourceId}`);
|
|
21
|
+
// Never-trained sources are hard-deleted (status "deleted") — only
|
|
22
|
+
// trained ones get the restorable "toBeDeleted" mark.
|
|
23
|
+
const status = data?.status;
|
|
24
|
+
if (status === 'toBeDeleted') {
|
|
25
|
+
this.note(flags, `↩ restore with: chatbase sources restore ${args.sourceId} -a ${agentId}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import { AgentCommand } from '../../base/agent-command.js';
|
|
3
|
+
import { SOURCE_COLUMNS, toSourceRow } from '../../base/sources.js';
|
|
4
|
+
import { throwIfError } from '../../client/client.js';
|
|
5
|
+
export default class SourcesGet extends AgentCommand {
|
|
6
|
+
static description = 'Show one source';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> sources get src_123 -a agt_123'
|
|
9
|
+
];
|
|
10
|
+
static args = {
|
|
11
|
+
sourceId: Args.string({ required: true, description: 'Source ID' })
|
|
12
|
+
};
|
|
13
|
+
static flags = { ...AgentCommand.baseFlags };
|
|
14
|
+
async run() {
|
|
15
|
+
const { args, flags } = await this.parse(SourcesGet);
|
|
16
|
+
const client = this.apiClient(flags);
|
|
17
|
+
const agentId = await this.agentId(flags, client);
|
|
18
|
+
const { data, error, response } = await client.GET('/agents/{agentId}/sources/{sourceId}', {
|
|
19
|
+
params: { path: { agentId, sourceId: args.sourceId } }
|
|
20
|
+
});
|
|
21
|
+
throwIfError(response, error);
|
|
22
|
+
const source = data;
|
|
23
|
+
const row = toSourceRow(source, this.mode(flags));
|
|
24
|
+
const str = (v) => (v == null ? '' : String(v));
|
|
25
|
+
this.printDetail(flags, data, row, SOURCE_COLUMNS, [
|
|
26
|
+
['ID', row.id],
|
|
27
|
+
['Name', row.name],
|
|
28
|
+
['Type', row.type],
|
|
29
|
+
['Status', row.status],
|
|
30
|
+
['Size', row.size],
|
|
31
|
+
['Created', str(source.createdAt)],
|
|
32
|
+
['URL', str(source.url)]
|
|
33
|
+
]);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { ListCommand } from '../../base/list-command.js';
|
|
3
|
+
import { SOURCE_COLUMNS, toSourceRow } from '../../base/sources.js';
|
|
4
|
+
import { fetchPages } from '../../client/paginate.js';
|
|
5
|
+
export default class SourcesList extends ListCommand {
|
|
6
|
+
static description = 'List sources for an agent';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> sources list -a agt_123',
|
|
9
|
+
'<%= config.bin %> sources list -a agt_123 --all --json'
|
|
10
|
+
];
|
|
11
|
+
static flags = {
|
|
12
|
+
...ListCommand.baseFlags,
|
|
13
|
+
type: Flags.string({
|
|
14
|
+
description: 'Filter by source type'
|
|
15
|
+
}),
|
|
16
|
+
name: Flags.string({
|
|
17
|
+
description: 'Filter by source name'
|
|
18
|
+
})
|
|
19
|
+
};
|
|
20
|
+
async run() {
|
|
21
|
+
const { flags } = await this.parse(SourcesList);
|
|
22
|
+
const client = this.apiClient(flags);
|
|
23
|
+
const agentId = await this.agentId(flags, client);
|
|
24
|
+
const mode = this.mode(flags);
|
|
25
|
+
const extraQuery = {};
|
|
26
|
+
if (flags.type)
|
|
27
|
+
extraQuery.type = flags.type;
|
|
28
|
+
if (flags.name)
|
|
29
|
+
extraQuery.name = flags.name;
|
|
30
|
+
const { pages, items } = await fetchPages((query) => client.GET('/agents/{agentId}/sources', {
|
|
31
|
+
params: {
|
|
32
|
+
path: { agentId },
|
|
33
|
+
query: { ...query, ...extraQuery }
|
|
34
|
+
}
|
|
35
|
+
}), { limit: flags.limit, cursor: flags.cursor, all: flags.all });
|
|
36
|
+
const rows = items.map((s) => toSourceRow(s, mode));
|
|
37
|
+
const last = pages.at(-1);
|
|
38
|
+
// --json must stay the raw API shape even when --all merges pages
|
|
39
|
+
const raw = pages.length === 1
|
|
40
|
+
? pages[0]
|
|
41
|
+
: { data: items, pagination: last?.pagination };
|
|
42
|
+
this.printData(flags, raw, rows, SOURCE_COLUMNS);
|
|
43
|
+
if (!flags.all && last?.pagination.hasMore && last.pagination.cursor) {
|
|
44
|
+
this.note(flags, `More results: rerun with --cursor ${last.pagination.cursor} (or use --all)`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import { AgentCommand } from '../../base/agent-command.js';
|
|
3
|
+
import { throwIfError } from '../../client/client.js';
|
|
4
|
+
export default class SourcesRestore extends AgentCommand {
|
|
5
|
+
static description = 'Restore a deleted source';
|
|
6
|
+
static examples = [
|
|
7
|
+
'<%= config.bin %> sources restore src_1 -a agt_1'
|
|
8
|
+
];
|
|
9
|
+
static args = {
|
|
10
|
+
sourceId: Args.string({ required: true, description: 'Source ID' })
|
|
11
|
+
};
|
|
12
|
+
async run() {
|
|
13
|
+
const { args, flags } = await this.parse(SourcesRestore);
|
|
14
|
+
const client = this.apiClient(flags);
|
|
15
|
+
const agentId = await this.agentId(flags, client);
|
|
16
|
+
const { error, response } = await client.POST('/agents/{agentId}/sources/{sourceId}/restore', {
|
|
17
|
+
params: { path: { agentId, sourceId: args.sourceId } }
|
|
18
|
+
});
|
|
19
|
+
throwIfError(response, error);
|
|
20
|
+
this.success(flags, `Restored source ${args.sourceId}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { AgentCommand } from '../../base/agent-command.js';
|
|
2
|
+
import { throwIfError } from '../../client/client.js';
|
|
3
|
+
const COLUMNS = [
|
|
4
|
+
{ key: 'type', header: 'TYPE' },
|
|
5
|
+
{ key: 'count', header: 'COUNT' },
|
|
6
|
+
{ key: 'size', header: 'SIZE' }
|
|
7
|
+
];
|
|
8
|
+
export default class SourcesSummary extends AgentCommand {
|
|
9
|
+
static description = 'Show aggregated source counts and sizes for an agent';
|
|
10
|
+
static examples = ['<%= config.bin %> sources summary -a agt_123'];
|
|
11
|
+
static flags = { ...AgentCommand.baseFlags };
|
|
12
|
+
async run() {
|
|
13
|
+
const { flags } = await this.parse(SourcesSummary);
|
|
14
|
+
const client = this.apiClient(flags);
|
|
15
|
+
const agentId = await this.agentId(flags, client);
|
|
16
|
+
const { data, error, response } = await client.GET('/agents/{agentId}/sources/summary', { params: { path: { agentId } } });
|
|
17
|
+
throwIfError(response, error);
|
|
18
|
+
const summary = data;
|
|
19
|
+
// Per-type {count, size} objects flatten to table columns; the one
|
|
20
|
+
// non-object field (shouldRetrain) reads better as a note than a row.
|
|
21
|
+
const rows = Object.entries(summary)
|
|
22
|
+
.filter((entry) => typeof entry[1] === 'object' && entry[1] !== null)
|
|
23
|
+
.map(([type, value]) => ({
|
|
24
|
+
type,
|
|
25
|
+
count: String(value.count ?? 0),
|
|
26
|
+
size: String(value.size ?? 0)
|
|
27
|
+
}));
|
|
28
|
+
this.printData(flags, data, rows, COLUMNS);
|
|
29
|
+
if (!flags.json && summary.shouldRetrain === true) {
|
|
30
|
+
this.note(flags, 'Sources changed since the last training — run `chatbase agents train` to retrain.');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { AgentCommand } from '../../base/agent-command.js';
|
|
3
|
+
import { assertFileReadable } from '../../base/assert-file.js';
|
|
4
|
+
import { bodyFieldFlags } from '../../base/base-command.js';
|
|
5
|
+
import { readBodyData } from '../../base/body-input.js';
|
|
6
|
+
import { throwIfError } from '../../client/client.js';
|
|
7
|
+
import { filesHostMismatchWarning, uploadFileSource } from '../../client/files.js';
|
|
8
|
+
import { resolveApiKey } from '../../config/resolve.js';
|
|
9
|
+
import { UsageError } from '../../errors/errors.js';
|
|
10
|
+
import { maybeSpinner } from '../../output/spinner.js';
|
|
11
|
+
export default class SourcesUpdate extends AgentCommand {
|
|
12
|
+
static description = 'Update an existing source (text, qna, link, or file)';
|
|
13
|
+
static examples = [
|
|
14
|
+
'<%= config.bin %> sources update src_1 --data \'{"type":"text","content":"new"}\' -a agt_1',
|
|
15
|
+
'<%= config.bin %> sources update src_1 --file ./updated.pdf -a agt_1'
|
|
16
|
+
];
|
|
17
|
+
static args = {
|
|
18
|
+
sourceId: Args.string({ required: true, description: 'Source ID' })
|
|
19
|
+
};
|
|
20
|
+
static flags = {
|
|
21
|
+
...AgentCommand.baseFlags,
|
|
22
|
+
...bodyFieldFlags,
|
|
23
|
+
data: Flags.string({
|
|
24
|
+
description: 'JSON body for text/qna/link sources (@file, @-, or inline)',
|
|
25
|
+
exclusive: ['file']
|
|
26
|
+
}),
|
|
27
|
+
file: Flags.string({
|
|
28
|
+
description: 'Path to a file to upload as a replacement (mutually exclusive with --data)',
|
|
29
|
+
exclusive: ['data', 'field']
|
|
30
|
+
})
|
|
31
|
+
};
|
|
32
|
+
async run() {
|
|
33
|
+
const { args, flags } = await this.parse(SourcesUpdate);
|
|
34
|
+
if (!flags.data && !flags.file) {
|
|
35
|
+
throw new UsageError('Specify --data for JSON source updates, or --file to upload a replacement file.');
|
|
36
|
+
}
|
|
37
|
+
// Validated before any network call, including agent-name
|
|
38
|
+
// resolution below — matches the guard in sources/create.ts.
|
|
39
|
+
if (flags.file)
|
|
40
|
+
assertFileReadable(flags.file);
|
|
41
|
+
const client = this.apiClient(flags);
|
|
42
|
+
const agentId = await this.agentId(flags, client);
|
|
43
|
+
if (flags.file) {
|
|
44
|
+
const resolved = resolveApiKey();
|
|
45
|
+
if (!resolved) {
|
|
46
|
+
throw new UsageError('Not authenticated. Run `chatbase auth login`, or set CHATBASE_API_KEY.');
|
|
47
|
+
}
|
|
48
|
+
const mismatch = filesHostMismatchWarning();
|
|
49
|
+
if (mismatch) {
|
|
50
|
+
this.note(flags, this.palette(flags).yellow(mismatch));
|
|
51
|
+
}
|
|
52
|
+
const stop = maybeSpinner(flags.quiet, `Uploading ${flags.file}…`);
|
|
53
|
+
try {
|
|
54
|
+
await uploadFileSource({
|
|
55
|
+
agentId,
|
|
56
|
+
filePath: flags.file,
|
|
57
|
+
sourceId: args.sourceId,
|
|
58
|
+
apiKey: resolved.value,
|
|
59
|
+
verbose: flags.verbose
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
stop();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
const body = await readBodyData(flags.data, flags.field);
|
|
68
|
+
const { error, response } = await client.PUT('/agents/{agentId}/sources/{sourceId}', {
|
|
69
|
+
params: { path: { agentId, sourceId: args.sourceId } },
|
|
70
|
+
body: body
|
|
71
|
+
});
|
|
72
|
+
throwIfError(response, error);
|
|
73
|
+
}
|
|
74
|
+
this.success(flags, `Updated source ${args.sourceId}`);
|
|
75
|
+
}
|
|
76
|
+
}
|