@chatbotkit/cli 1.29.0 → 1.29.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.
@@ -80,15 +80,24 @@ function getClient() {
80
80
  exports.command = new commander_1.Command()
81
81
  .name('agent')
82
82
  .description('Run an agent as a background worker with a prompt')
83
+ .addOption(new commander_1.Option('-a, --agent <agent>', 'Path to an agent markdown file'))
83
84
  .addOption(new commander_1.Option('-b, --bot <bot>', 'Bot id'))
84
85
  .addOption(new commander_1.Option('-m, --model <model>', 'Model name'))
86
+ .addOption(new commander_1.Option('--skillset <skillset>', 'Skillset id'))
87
+ .addOption(new commander_1.Option('--dataset <dataset>', 'Dataset id'))
85
88
  .addOption(new commander_1.Option('-p, --prompt <prompt>', 'The prompt to execute (or path to a file containing the prompt)').makeOptionMandatory())
86
89
  .addOption(new commander_1.Option('-t, --tools <tools...>', 'Specific tools to enable').choices((0, tools_js_1.getToolNames)()))
90
+ .addOption(new commander_1.Option('-s, --skills <dirs...>', 'Directories to load skills from'))
87
91
  .addOption(new commander_1.Option('-i, --max-iterations <maxIterations>', 'Maximum number of iterations')
88
- .default(50)
92
+ .default(100)
89
93
  .argParser((value) => parseInt(value, 10)))
94
+ .addOption(new commander_1.Option('-d, --debug', 'Print raw stream items to stderr'))
90
95
  .action(async (options) => {
91
96
  const client = getClient();
97
+ const agentDef = options.agent ? await (0, agent_1.loadAgent)(options.agent) : null;
98
+ const { skills, close: closeSkills } = options.skills
99
+ ? await (0, agent_1.loadSkills)(options.skills, { watch: false })
100
+ : { skills: [], close: () => { } };
92
101
  const tools = (0, tools_js_1.getTools)(options.tools);
93
102
  let prompt = options.prompt;
94
103
  {
@@ -109,16 +118,33 @@ exports.command = new commander_1.Command()
109
118
  let hasOutput = false;
110
119
  for await (const { type, data } of (0, agent_1.execute)({
111
120
  client,
112
- ...(options.bot ? { botId: options.bot } : { model: options.model }),
121
+ ...(options.bot
122
+ ? { botId: options.bot }
123
+ : agentDef?.botId
124
+ ? { botId: agentDef.botId }
125
+ : { model: options.model ?? agentDef?.model }),
126
+ ...(agentDef?.backstory ? { backstory: agentDef.backstory } : {}),
127
+ ...(options.skillset ?? agentDef?.skillsetId
128
+ ? { skillsetId: options.skillset ?? agentDef?.skillsetId }
129
+ : {}),
130
+ ...(options.dataset ?? agentDef?.datasetId
131
+ ? { datasetId: options.dataset ?? agentDef?.datasetId }
132
+ : {}),
113
133
  messages: [{ type: 'user', text: prompt }],
114
134
  tools,
115
135
  maxIterations: options.maxIterations,
136
+ ...(skills.length > 0
137
+ ? { extensions: { features: [(0, agent_1.createSkillsFeature)(skills)] } }
138
+ : {}),
116
139
  })) {
140
+ if (options.debug) {
141
+ process.stderr.write(`[debug] ${JSON.stringify({ type, data })}\n`);
142
+ }
117
143
  if (type === 'iteration') {
118
144
  if (isInteractive) {
119
145
  const iterationNum = data.iteration - 1;
120
146
  output.writeLine((0, color_js_1.formatBlue)(`\n╭─ Iteration ${iterationNum} ─╮`));
121
- if (spinner) {
147
+ if (spinner && !options.debug) {
122
148
  spinner.start();
123
149
  }
124
150
  }
@@ -134,7 +160,7 @@ exports.command = new commander_1.Command()
134
160
  status: 'running',
135
161
  args: data.args,
136
162
  });
137
- if (spinner) {
163
+ if (spinner && !options.debug) {
138
164
  spinner.start();
139
165
  }
140
166
  }
@@ -144,7 +170,7 @@ exports.command = new commander_1.Command()
144
170
  status: 'completed',
145
171
  result: data.result,
146
172
  });
147
- if (spinner) {
173
+ if (spinner && !options.debug) {
148
174
  spinner.start();
149
175
  }
150
176
  }
@@ -154,7 +180,7 @@ exports.command = new commander_1.Command()
154
180
  status: 'error',
155
181
  error: data.error,
156
182
  });
157
- if (spinner) {
183
+ if (spinner && !options.debug) {
158
184
  spinner.start();
159
185
  }
160
186
  }
@@ -180,6 +206,7 @@ exports.command = new commander_1.Command()
180
206
  }
181
207
  }
182
208
  }
209
+ closeSkills();
183
210
  if (exitResult) {
184
211
  output.printStructured({
185
212
  status: exitResult.code === 0 ? 'success' : 'failed',
@@ -12,9 +12,10 @@ const index_js_6 = tslib_1.__importDefault(require("./file/index.cjs"));
12
12
  const index_js_7 = tslib_1.__importDefault(require("./integration/index.cjs"));
13
13
  const index_js_8 = tslib_1.__importDefault(require("./memory/index.cjs"));
14
14
  const index_js_9 = tslib_1.__importDefault(require("./partner/index.cjs"));
15
- const index_js_10 = tslib_1.__importDefault(require("./secret/index.cjs"));
16
- const index_js_11 = tslib_1.__importDefault(require("./skillset/index.cjs"));
17
- const index_js_12 = tslib_1.__importDefault(require("./team/index.cjs"));
15
+ const index_js_10 = tslib_1.__importDefault(require("./platform/index.cjs"));
16
+ const index_js_11 = tslib_1.__importDefault(require("./secret/index.cjs"));
17
+ const index_js_12 = tslib_1.__importDefault(require("./skillset/index.cjs"));
18
+ const index_js_13 = tslib_1.__importDefault(require("./team/index.cjs"));
18
19
  const commander_1 = require("commander");
19
20
  const commands = {
20
21
  blueprint: index_js_1.default,
@@ -26,9 +27,10 @@ const commands = {
26
27
  integration: index_js_7.default,
27
28
  memory: index_js_8.default,
28
29
  partner: index_js_9.default,
29
- secret: index_js_10.default,
30
- skillset: index_js_11.default,
31
- team: index_js_12.default,
30
+ platform: index_js_10.default,
31
+ secret: index_js_11.default,
32
+ skillset: index_js_12.default,
33
+ team: index_js_13.default,
32
34
  };
33
35
  exports.command = new commander_1.Command()
34
36
  .name('api')
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.command = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const index_js_1 = tslib_1.__importDefault(require("./model/index.cjs"));
6
+ const commander_1 = require("commander");
7
+ const commands = {
8
+ model: index_js_1.default,
9
+ };
10
+ exports.command = new commander_1.Command()
11
+ .name('platform')
12
+ .description('Platform tools for ChatBotKit');
13
+ for (const cmd of Object.values(commands)) {
14
+ exports.command.addCommand(cmd);
15
+ }
16
+ exports.default = exports.command;
@@ -0,0 +1,3 @@
1
+ export const command: Command;
2
+ export default command;
3
+ import { Command } from 'commander';
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.command = exports.modelList = void 0;
4
+ const env_js_1 = require("../../../../env.cjs");
5
+ const output_js_1 = require("../../../../output.cjs");
6
+ const index_js_1 = require("@chatbotkit/sdk/platform/model/index.js");
7
+ const commander_1 = require("commander");
8
+ function getClient() {
9
+ return new index_js_1.PlatformModelClient({
10
+ secret: (0, env_js_1.getSECRET)(),
11
+ runAsUserId: (0, env_js_1.getRUNAS_USERID)(),
12
+ });
13
+ }
14
+ exports.modelList = new commander_1.Command()
15
+ .name('list')
16
+ .description('List platform models')
17
+ .option('-s, --stream', 'Stream models')
18
+ .action(async (str, options) => {
19
+ const { stream } = options;
20
+ const client = getClient();
21
+ if (stream) {
22
+ for await (const model of client.list().stream()) {
23
+ (0, output_js_1.print)(model);
24
+ }
25
+ }
26
+ else {
27
+ const { items } = await client.list();
28
+ for (const model of items) {
29
+ (0, output_js_1.print)(model);
30
+ }
31
+ }
32
+ });
33
+ const commands = {
34
+ list: exports.modelList,
35
+ };
36
+ exports.command = new commander_1.Command()
37
+ .name('model')
38
+ .description('Model tools for ChatBotKit platform');
39
+ for (const cmd of Object.values(commands)) {
40
+ exports.command.addCommand(cmd);
41
+ }
42
+ exports.default = exports.command;
@@ -0,0 +1,4 @@
1
+ export const modelList: Command;
2
+ export const command: Command;
3
+ export default command;
4
+ import { Command } from 'commander';
@@ -18,15 +18,29 @@ function getClient() {
18
18
  exports.command = new commander_1.Command()
19
19
  .name('chat')
20
20
  .description('Start a chat session')
21
+ .addOption(new commander_1.Option('-a, --agent <agent>', 'Path to an agent markdown file'))
21
22
  .addOption(new commander_1.Option('-b, --bot <bot>', 'Bot id'))
22
23
  .addOption(new commander_1.Option('-m, --model <model>', 'Model name'))
24
+ .addOption(new commander_1.Option('--skillset <skillset>', 'Skillset id'))
25
+ .addOption(new commander_1.Option('--dataset <dataset>', 'Dataset id'))
23
26
  .addOption(new commander_1.Option('-t, --tools <tools...>', 'Specific tools to enable').choices((0, tools_js_1.getToolNames)()))
27
+ .addOption(new commander_1.Option('-s, --skills <dirs...>', 'Directories to load skills from'))
28
+ .addOption(new commander_1.Option('-d, --debug', 'Print raw stream items to stderr'))
24
29
  .action(async (options) => {
25
30
  const client = getClient();
31
+ const agent = options.agent ? await (0, agent_1.loadAgent)(options.agent) : null;
32
+ const { skills, close: closeSkills } = options.skills
33
+ ? await (0, agent_1.loadSkills)(options.skills, { watch: true })
34
+ : { skills: [], close: () => { } };
26
35
  const rl = promises_1.default.createInterface({
27
36
  input: process.stdin,
28
37
  output: process.stdout,
29
38
  });
39
+ rl.on('close', () => {
40
+ process.stdout.write('\n');
41
+ closeSkills();
42
+ process.exit(0);
43
+ });
30
44
  const messages = [];
31
45
  const tools = (0, tools_js_1.getTools)(options.tools);
32
46
  const colors = {
@@ -34,33 +48,131 @@ exports.command = new commander_1.Command()
34
48
  bold: '\x1b[1m',
35
49
  cyan: '\x1b[36m',
36
50
  green: '\x1b[32m',
51
+ dim: '\x1b[2m',
52
+ gray: '\x1b[90m',
37
53
  };
54
+ const effectiveBot = options.bot || agent?.botId;
55
+ const effectiveModel = options.model ?? agent?.model;
56
+ const effectiveSkillset = options.skillset ?? agent?.skillsetId;
57
+ const effectiveDataset = options.dataset ?? agent?.datasetId;
58
+ const effectiveSkills = options.skills;
59
+ const metaLines = [];
60
+ if (agent?.name) {
61
+ metaLines.push(`agent ${agent.name}`);
62
+ }
63
+ if (effectiveBot) {
64
+ metaLines.push(`bot ${effectiveBot}`);
65
+ }
66
+ if (effectiveModel) {
67
+ metaLines.push(`model ${effectiveModel}`);
68
+ }
69
+ if (effectiveSkillset) {
70
+ metaLines.push(`skillset ${effectiveSkillset}`);
71
+ }
72
+ if (effectiveDataset) {
73
+ metaLines.push(`dataset ${effectiveDataset}`);
74
+ }
75
+ if (effectiveSkills) {
76
+ metaLines.push(`skills ${effectiveSkills.join(', ')}`);
77
+ }
78
+ if (metaLines.length > 0) {
79
+ process.stdout.write('\n');
80
+ for (const line of metaLines) {
81
+ process.stdout.write(`${colors.gray} ${line}${colors.reset}\n`);
82
+ }
83
+ }
84
+ process.stdout.write(`\n${colors.gray} shortcuts Ctrl+C re-prompt · Ctrl+C×2 abort response · Ctrl+D exit${colors.reset}\n`);
85
+ let agentAbort = null;
86
+ let promptAbort = null;
87
+ let sigintCount = 0;
88
+ rl.on('SIGINT', () => {
89
+ if (agentAbort) {
90
+ sigintCount++;
91
+ if (sigintCount === 1) {
92
+ process.stdout.write(`\n ${colors.dim}(Press Ctrl+C again to cancel)${colors.reset}\n`);
93
+ }
94
+ else {
95
+ sigintCount = 0;
96
+ agentAbort.abort();
97
+ }
98
+ }
99
+ else {
100
+ process.stdout.write('^C\n');
101
+ promptAbort?.abort();
102
+ }
103
+ });
38
104
  for (;;) {
39
105
  process.stdout.write('\n');
40
- const user = await rl.question(`${colors.cyan}●${colors.reset} ${colors.bold}You${colors.reset}\n\n> `);
41
- messages.push({ type: 'user', text: user });
106
+ promptAbort = new AbortController();
107
+ let text;
108
+ try {
109
+ text = await rl.question(`${colors.cyan}●${colors.reset} ${colors.bold}You${colors.reset}\n\n> `, { signal: promptAbort.signal });
110
+ }
111
+ catch {
112
+ continue;
113
+ }
114
+ finally {
115
+ promptAbort = null;
116
+ }
117
+ text = text?.trim();
118
+ if (!text) {
119
+ continue;
120
+ }
121
+ messages.push({ type: 'user', text: text });
42
122
  process.stdout.write(`\n${colors.green}●${colors.reset} ${colors.bold}Assistant${colors.reset}\n\n `);
43
123
  const spinner = new spinner_js_1.Spinner('');
44
- spinner.start();
124
+ if (!options.debug) {
125
+ spinner.start();
126
+ }
45
127
  let firstToken = true;
46
- for await (const { type, data } of (0, agent_1.complete)({
47
- client,
48
- ...(options.bot ? { botId: options.bot } : { model: options.model }),
49
- messages,
50
- tools,
51
- })) {
52
- if (type === 'token') {
53
- if (firstToken) {
54
- spinner.stop();
55
- firstToken = false;
128
+ sigintCount = 0;
129
+ agentAbort = new AbortController();
130
+ try {
131
+ for await (const item of (0, agent_1.execute)({
132
+ client,
133
+ ...(options.bot
134
+ ? { botId: options.bot }
135
+ : agent?.botId
136
+ ? { botId: agent.botId }
137
+ : { model: options.model ?? agent?.model }),
138
+ ...(agent?.backstory ? { backstory: agent.backstory } : {}),
139
+ ...(options.skillset ?? agent?.skillsetId
140
+ ? { skillsetId: options.skillset ?? agent?.skillsetId }
141
+ : {}),
142
+ ...(options.dataset ?? agent?.datasetId
143
+ ? { datasetId: options.dataset ?? agent?.datasetId }
144
+ : {}),
145
+ messages,
146
+ tools,
147
+ abortSignal: agentAbort.signal,
148
+ ...(skills.length > 0
149
+ ? { extensions: { features: [(0, agent_1.createSkillsFeature)(skills)] } }
150
+ : {}),
151
+ })) {
152
+ const { type, data } = item;
153
+ if (options.debug) {
154
+ process.stderr.write(`${colors.dim}[debug] ${JSON.stringify(item)}${colors.reset}\n`);
155
+ }
156
+ else if (type === 'token') {
157
+ if (firstToken) {
158
+ spinner.stop();
159
+ firstToken = false;
160
+ }
161
+ process.stdout.write(data.token);
162
+ }
163
+ if (type === 'message') {
164
+ messages.push(data);
56
165
  }
57
- process.stdout.write(data.token);
58
- }
59
- else if (type === 'result') {
60
- messages.push({ type: 'bot', text: data.text });
61
166
  }
62
167
  }
63
- spinner.stop();
168
+ catch {
169
+ }
170
+ finally {
171
+ agentAbort = null;
172
+ }
173
+ if (!options.debug) {
174
+ spinner.stop();
175
+ }
64
176
  process.stdout.write('\n');
65
177
  }
66
178
  });
@@ -3,7 +3,7 @@ import { getRUNAS_USERID, getSECRET } from '../../env.js';
3
3
  import { print, printError } from '../../output.js';
4
4
  import { Spinner } from '../../spinner.js';
5
5
  import { getToolNames, getTools } from '../../tools.js';
6
- import { execute } from '@chatbotkit/agent';
6
+ import { createSkillsFeature, execute, loadAgent, loadSkills, } from '@chatbotkit/agent';
7
7
  import { ChatBotKit } from '@chatbotkit/sdk';
8
8
  import { Command, Option } from 'commander';
9
9
  import { existsSync, readFileSync } from 'fs';
@@ -77,15 +77,24 @@ function getClient() {
77
77
  export const command = new Command()
78
78
  .name('agent')
79
79
  .description('Run an agent as a background worker with a prompt')
80
+ .addOption(new Option('-a, --agent <agent>', 'Path to an agent markdown file'))
80
81
  .addOption(new Option('-b, --bot <bot>', 'Bot id'))
81
82
  .addOption(new Option('-m, --model <model>', 'Model name'))
83
+ .addOption(new Option('--skillset <skillset>', 'Skillset id'))
84
+ .addOption(new Option('--dataset <dataset>', 'Dataset id'))
82
85
  .addOption(new Option('-p, --prompt <prompt>', 'The prompt to execute (or path to a file containing the prompt)').makeOptionMandatory())
83
86
  .addOption(new Option('-t, --tools <tools...>', 'Specific tools to enable').choices(getToolNames()))
87
+ .addOption(new Option('-s, --skills <dirs...>', 'Directories to load skills from'))
84
88
  .addOption(new Option('-i, --max-iterations <maxIterations>', 'Maximum number of iterations')
85
- .default(50)
89
+ .default(100)
86
90
  .argParser((value) => parseInt(value, 10)))
91
+ .addOption(new Option('-d, --debug', 'Print raw stream items to stderr'))
87
92
  .action(async (options) => {
88
93
  const client = getClient();
94
+ const agentDef = options.agent ? await loadAgent(options.agent) : null;
95
+ const { skills, close: closeSkills } = options.skills
96
+ ? await loadSkills(options.skills, { watch: false })
97
+ : { skills: [], close: () => { } };
89
98
  const tools = getTools(options.tools);
90
99
  let prompt = options.prompt;
91
100
  {
@@ -106,16 +115,33 @@ export const command = new Command()
106
115
  let hasOutput = false;
107
116
  for await (const { type, data } of execute({
108
117
  client,
109
- ...(options.bot ? { botId: options.bot } : { model: options.model }),
118
+ ...(options.bot
119
+ ? { botId: options.bot }
120
+ : agentDef?.botId
121
+ ? { botId: agentDef.botId }
122
+ : { model: options.model ?? agentDef?.model }),
123
+ ...(agentDef?.backstory ? { backstory: agentDef.backstory } : {}),
124
+ ...(options.skillset ?? agentDef?.skillsetId
125
+ ? { skillsetId: options.skillset ?? agentDef?.skillsetId }
126
+ : {}),
127
+ ...(options.dataset ?? agentDef?.datasetId
128
+ ? { datasetId: options.dataset ?? agentDef?.datasetId }
129
+ : {}),
110
130
  messages: [{ type: 'user', text: prompt }],
111
131
  tools,
112
132
  maxIterations: options.maxIterations,
133
+ ...(skills.length > 0
134
+ ? { extensions: { features: [createSkillsFeature(skills)] } }
135
+ : {}),
113
136
  })) {
137
+ if (options.debug) {
138
+ process.stderr.write(`[debug] ${JSON.stringify({ type, data })}\n`);
139
+ }
114
140
  if (type === 'iteration') {
115
141
  if (isInteractive) {
116
142
  const iterationNum = data.iteration - 1;
117
143
  output.writeLine(formatBlue(`\n╭─ Iteration ${iterationNum} ─╮`));
118
- if (spinner) {
144
+ if (spinner && !options.debug) {
119
145
  spinner.start();
120
146
  }
121
147
  }
@@ -131,7 +157,7 @@ export const command = new Command()
131
157
  status: 'running',
132
158
  args: data.args,
133
159
  });
134
- if (spinner) {
160
+ if (spinner && !options.debug) {
135
161
  spinner.start();
136
162
  }
137
163
  }
@@ -141,7 +167,7 @@ export const command = new Command()
141
167
  status: 'completed',
142
168
  result: data.result,
143
169
  });
144
- if (spinner) {
170
+ if (spinner && !options.debug) {
145
171
  spinner.start();
146
172
  }
147
173
  }
@@ -151,7 +177,7 @@ export const command = new Command()
151
177
  status: 'error',
152
178
  error: data.error,
153
179
  });
154
- if (spinner) {
180
+ if (spinner && !options.debug) {
155
181
  spinner.start();
156
182
  }
157
183
  }
@@ -177,6 +203,7 @@ export const command = new Command()
177
203
  }
178
204
  }
179
205
  }
206
+ closeSkills();
180
207
  if (exitResult) {
181
208
  output.printStructured({
182
209
  status: exitResult.code === 0 ? 'success' : 'failed',
@@ -8,6 +8,7 @@ import file from './file/index.js';
8
8
  import integration from './integration/index.js';
9
9
  import memory from './memory/index.js';
10
10
  import partner from './partner/index.js';
11
+ import platform from './platform/index.js';
11
12
  import secret from './secret/index.js';
12
13
  import skillset from './skillset/index.js';
13
14
  import team from './team/index.js';
@@ -22,6 +23,7 @@ const commands = {
22
23
  integration,
23
24
  memory,
24
25
  partner,
26
+ platform,
25
27
  secret,
26
28
  skillset,
27
29
  team,
@@ -0,0 +1,3 @@
1
+ export const command: Command;
2
+ export default command;
3
+ import { Command } from 'commander';
@@ -0,0 +1,12 @@
1
+ import model from './model/index.js';
2
+ import { Command } from 'commander';
3
+ const commands = {
4
+ model,
5
+ };
6
+ export const command = new Command()
7
+ .name('platform')
8
+ .description('Platform tools for ChatBotKit');
9
+ for (const cmd of Object.values(commands)) {
10
+ command.addCommand(cmd);
11
+ }
12
+ export default command;
@@ -0,0 +1,4 @@
1
+ export const modelList: Command;
2
+ export const command: Command;
3
+ export default command;
4
+ import { Command } from 'commander';
@@ -0,0 +1,39 @@
1
+ import { getRUNAS_USERID, getSECRET } from '../../../../env.js';
2
+ import { print } from '../../../../output.js';
3
+ import { PlatformModelClient } from '@chatbotkit/sdk/platform/model/index.js';
4
+ import { Command } from 'commander';
5
+ function getClient() {
6
+ return new PlatformModelClient({
7
+ secret: getSECRET(),
8
+ runAsUserId: getRUNAS_USERID(),
9
+ });
10
+ }
11
+ export const modelList = new Command()
12
+ .name('list')
13
+ .description('List platform models')
14
+ .option('-s, --stream', 'Stream models')
15
+ .action(async (str, options) => {
16
+ const { stream } = options;
17
+ const client = getClient();
18
+ if (stream) {
19
+ for await (const model of client.list().stream()) {
20
+ print(model);
21
+ }
22
+ }
23
+ else {
24
+ const { items } = await client.list();
25
+ for (const model of items) {
26
+ print(model);
27
+ }
28
+ }
29
+ });
30
+ const commands = {
31
+ list: modelList,
32
+ };
33
+ export const command = new Command()
34
+ .name('model')
35
+ .description('Model tools for ChatBotKit platform');
36
+ for (const cmd of Object.values(commands)) {
37
+ command.addCommand(cmd);
38
+ }
39
+ export default command;
@@ -1,7 +1,7 @@
1
1
  import { getRUNAS_USERID, getSECRET } from '../../env.js';
2
2
  import { Spinner } from '../../spinner.js';
3
3
  import { getToolNames, getTools } from '../../tools.js';
4
- import { complete } from '@chatbotkit/agent';
4
+ import { createSkillsFeature, execute, loadAgent, loadSkills, } from '@chatbotkit/agent';
5
5
  import { ChatBotKit } from '@chatbotkit/sdk';
6
6
  import { Command, Option } from 'commander';
7
7
  import readline from 'readline/promises';
@@ -14,15 +14,29 @@ function getClient() {
14
14
  export const command = new Command()
15
15
  .name('chat')
16
16
  .description('Start a chat session')
17
+ .addOption(new Option('-a, --agent <agent>', 'Path to an agent markdown file'))
17
18
  .addOption(new Option('-b, --bot <bot>', 'Bot id'))
18
19
  .addOption(new Option('-m, --model <model>', 'Model name'))
20
+ .addOption(new Option('--skillset <skillset>', 'Skillset id'))
21
+ .addOption(new Option('--dataset <dataset>', 'Dataset id'))
19
22
  .addOption(new Option('-t, --tools <tools...>', 'Specific tools to enable').choices(getToolNames()))
23
+ .addOption(new Option('-s, --skills <dirs...>', 'Directories to load skills from'))
24
+ .addOption(new Option('-d, --debug', 'Print raw stream items to stderr'))
20
25
  .action(async (options) => {
21
26
  const client = getClient();
27
+ const agent = options.agent ? await loadAgent(options.agent) : null;
28
+ const { skills, close: closeSkills } = options.skills
29
+ ? await loadSkills(options.skills, { watch: true })
30
+ : { skills: [], close: () => { } };
22
31
  const rl = readline.createInterface({
23
32
  input: process.stdin,
24
33
  output: process.stdout,
25
34
  });
35
+ rl.on('close', () => {
36
+ process.stdout.write('\n');
37
+ closeSkills();
38
+ process.exit(0);
39
+ });
26
40
  const messages = [];
27
41
  const tools = getTools(options.tools);
28
42
  const colors = {
@@ -30,33 +44,131 @@ export const command = new Command()
30
44
  bold: '\x1b[1m',
31
45
  cyan: '\x1b[36m',
32
46
  green: '\x1b[32m',
47
+ dim: '\x1b[2m',
48
+ gray: '\x1b[90m',
33
49
  };
50
+ const effectiveBot = options.bot || agent?.botId;
51
+ const effectiveModel = options.model ?? agent?.model;
52
+ const effectiveSkillset = options.skillset ?? agent?.skillsetId;
53
+ const effectiveDataset = options.dataset ?? agent?.datasetId;
54
+ const effectiveSkills = options.skills;
55
+ const metaLines = [];
56
+ if (agent?.name) {
57
+ metaLines.push(`agent ${agent.name}`);
58
+ }
59
+ if (effectiveBot) {
60
+ metaLines.push(`bot ${effectiveBot}`);
61
+ }
62
+ if (effectiveModel) {
63
+ metaLines.push(`model ${effectiveModel}`);
64
+ }
65
+ if (effectiveSkillset) {
66
+ metaLines.push(`skillset ${effectiveSkillset}`);
67
+ }
68
+ if (effectiveDataset) {
69
+ metaLines.push(`dataset ${effectiveDataset}`);
70
+ }
71
+ if (effectiveSkills) {
72
+ metaLines.push(`skills ${effectiveSkills.join(', ')}`);
73
+ }
74
+ if (metaLines.length > 0) {
75
+ process.stdout.write('\n');
76
+ for (const line of metaLines) {
77
+ process.stdout.write(`${colors.gray} ${line}${colors.reset}\n`);
78
+ }
79
+ }
80
+ process.stdout.write(`\n${colors.gray} shortcuts Ctrl+C re-prompt · Ctrl+C×2 abort response · Ctrl+D exit${colors.reset}\n`);
81
+ let agentAbort = null;
82
+ let promptAbort = null;
83
+ let sigintCount = 0;
84
+ rl.on('SIGINT', () => {
85
+ if (agentAbort) {
86
+ sigintCount++;
87
+ if (sigintCount === 1) {
88
+ process.stdout.write(`\n ${colors.dim}(Press Ctrl+C again to cancel)${colors.reset}\n`);
89
+ }
90
+ else {
91
+ sigintCount = 0;
92
+ agentAbort.abort();
93
+ }
94
+ }
95
+ else {
96
+ process.stdout.write('^C\n');
97
+ promptAbort?.abort();
98
+ }
99
+ });
34
100
  for (;;) {
35
101
  process.stdout.write('\n');
36
- const user = await rl.question(`${colors.cyan}●${colors.reset} ${colors.bold}You${colors.reset}\n\n> `);
37
- messages.push({ type: 'user', text: user });
102
+ promptAbort = new AbortController();
103
+ let text;
104
+ try {
105
+ text = await rl.question(`${colors.cyan}●${colors.reset} ${colors.bold}You${colors.reset}\n\n> `, { signal: promptAbort.signal });
106
+ }
107
+ catch {
108
+ continue;
109
+ }
110
+ finally {
111
+ promptAbort = null;
112
+ }
113
+ text = text?.trim();
114
+ if (!text) {
115
+ continue;
116
+ }
117
+ messages.push({ type: 'user', text: text });
38
118
  process.stdout.write(`\n${colors.green}●${colors.reset} ${colors.bold}Assistant${colors.reset}\n\n `);
39
119
  const spinner = new Spinner('');
40
- spinner.start();
120
+ if (!options.debug) {
121
+ spinner.start();
122
+ }
41
123
  let firstToken = true;
42
- for await (const { type, data } of complete({
43
- client,
44
- ...(options.bot ? { botId: options.bot } : { model: options.model }),
45
- messages,
46
- tools,
47
- })) {
48
- if (type === 'token') {
49
- if (firstToken) {
50
- spinner.stop();
51
- firstToken = false;
124
+ sigintCount = 0;
125
+ agentAbort = new AbortController();
126
+ try {
127
+ for await (const item of execute({
128
+ client,
129
+ ...(options.bot
130
+ ? { botId: options.bot }
131
+ : agent?.botId
132
+ ? { botId: agent.botId }
133
+ : { model: options.model ?? agent?.model }),
134
+ ...(agent?.backstory ? { backstory: agent.backstory } : {}),
135
+ ...(options.skillset ?? agent?.skillsetId
136
+ ? { skillsetId: options.skillset ?? agent?.skillsetId }
137
+ : {}),
138
+ ...(options.dataset ?? agent?.datasetId
139
+ ? { datasetId: options.dataset ?? agent?.datasetId }
140
+ : {}),
141
+ messages,
142
+ tools,
143
+ abortSignal: agentAbort.signal,
144
+ ...(skills.length > 0
145
+ ? { extensions: { features: [createSkillsFeature(skills)] } }
146
+ : {}),
147
+ })) {
148
+ const { type, data } = item;
149
+ if (options.debug) {
150
+ process.stderr.write(`${colors.dim}[debug] ${JSON.stringify(item)}${colors.reset}\n`);
151
+ }
152
+ else if (type === 'token') {
153
+ if (firstToken) {
154
+ spinner.stop();
155
+ firstToken = false;
156
+ }
157
+ process.stdout.write(data.token);
158
+ }
159
+ if (type === 'message') {
160
+ messages.push(data);
52
161
  }
53
- process.stdout.write(data.token);
54
- }
55
- else if (type === 'result') {
56
- messages.push({ type: 'bot', text: data.text });
57
162
  }
58
163
  }
59
- spinner.stop();
164
+ catch {
165
+ }
166
+ finally {
167
+ agentAbort = null;
168
+ }
169
+ if (!options.debug) {
170
+ spinner.stop();
171
+ }
60
172
  process.stdout.write('\n');
61
173
  }
62
174
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatbotkit/cli",
3
- "version": "1.29.0",
3
+ "version": "1.29.1",
4
4
  "description": "ChatBotKit command line tools",
5
5
  "license": "ISC",
6
6
  "engines": {
@@ -945,6 +945,66 @@
945
945
  "default": "./dist/cjs/command/api/partner/user/index.cjs"
946
946
  }
947
947
  },
948
+ "./command/api/platform": {
949
+ "import": {
950
+ "types": "./dist/esm/command/api/platform/index.d.ts",
951
+ "default": "./dist/esm/command/api/platform/index.js"
952
+ },
953
+ "require": {
954
+ "types": "./dist/cjs/command/api/platform/index.d.ts",
955
+ "default": "./dist/cjs/command/api/platform/index.cjs"
956
+ }
957
+ },
958
+ "./command/api/platform/index": {
959
+ "import": {
960
+ "types": "./dist/esm/command/api/platform/index.d.ts",
961
+ "default": "./dist/esm/command/api/platform/index.js"
962
+ },
963
+ "require": {
964
+ "types": "./dist/cjs/command/api/platform/index.d.ts",
965
+ "default": "./dist/cjs/command/api/platform/index.cjs"
966
+ }
967
+ },
968
+ "./command/api/platform/index.js": {
969
+ "import": {
970
+ "types": "./dist/esm/command/api/platform/index.d.ts",
971
+ "default": "./dist/esm/command/api/platform/index.js"
972
+ },
973
+ "require": {
974
+ "types": "./dist/cjs/command/api/platform/index.d.ts",
975
+ "default": "./dist/cjs/command/api/platform/index.cjs"
976
+ }
977
+ },
978
+ "./command/api/platform/model": {
979
+ "import": {
980
+ "types": "./dist/esm/command/api/platform/model/index.d.ts",
981
+ "default": "./dist/esm/command/api/platform/model/index.js"
982
+ },
983
+ "require": {
984
+ "types": "./dist/cjs/command/api/platform/model/index.d.ts",
985
+ "default": "./dist/cjs/command/api/platform/model/index.cjs"
986
+ }
987
+ },
988
+ "./command/api/platform/model/index": {
989
+ "import": {
990
+ "types": "./dist/esm/command/api/platform/model/index.d.ts",
991
+ "default": "./dist/esm/command/api/platform/model/index.js"
992
+ },
993
+ "require": {
994
+ "types": "./dist/cjs/command/api/platform/model/index.d.ts",
995
+ "default": "./dist/cjs/command/api/platform/model/index.cjs"
996
+ }
997
+ },
998
+ "./command/api/platform/model/index.js": {
999
+ "import": {
1000
+ "types": "./dist/esm/command/api/platform/model/index.d.ts",
1001
+ "default": "./dist/esm/command/api/platform/model/index.js"
1002
+ },
1003
+ "require": {
1004
+ "types": "./dist/cjs/command/api/platform/model/index.d.ts",
1005
+ "default": "./dist/cjs/command/api/platform/model/index.cjs"
1006
+ }
1007
+ },
948
1008
  "./command/api/secret": {
949
1009
  "import": {
950
1010
  "types": "./dist/esm/command/api/secret/index.d.ts",
@@ -1404,7 +1464,7 @@
1404
1464
  "js-yaml": "^4.1.0",
1405
1465
  "tslib": "^2.6.2",
1406
1466
  "zod": "^3.25.76",
1407
- "@chatbotkit/agent": "1.29.0",
1467
+ "@chatbotkit/agent": "1.29.1",
1408
1468
  "@chatbotkit/sdk": "1.29.0"
1409
1469
  },
1410
1470
  "devDependencies": {