@circleback/cli 0.1.0 → 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/README.md +104 -0
- package/dist/commands/meetings.js +1 -1
- package/dist/commands/transcripts.js +5 -5
- package/dist/helpers/formatter.js +2 -2
- package/dist/helpers/table.js +29 -20
- package/package.json +2 -1
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Circleback CLI
|
|
2
|
+
|
|
3
|
+
Search and access your meetings, transcripts, emails, calendar events, and more from the terminal.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @circleback/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Node.js 18 or later. The package installs two binaries: `circleback` and `cb` (shorthand).
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Authenticate via browser
|
|
17
|
+
cb login
|
|
18
|
+
|
|
19
|
+
# Search recent meetings
|
|
20
|
+
cb meetings
|
|
21
|
+
|
|
22
|
+
# Search by keyword
|
|
23
|
+
cb meetings "product review"
|
|
24
|
+
|
|
25
|
+
# Read full meeting details
|
|
26
|
+
cb meetings read 12345
|
|
27
|
+
|
|
28
|
+
# Search transcripts
|
|
29
|
+
cb transcripts "pricing discussion"
|
|
30
|
+
|
|
31
|
+
# Search emails
|
|
32
|
+
cb emails "from:alice@example.com after:2026-01-01"
|
|
33
|
+
|
|
34
|
+
# Get raw JSON for scripting
|
|
35
|
+
cb meetings --json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Commands
|
|
39
|
+
|
|
40
|
+
| Command | Description |
|
|
41
|
+
| ------------------------------ | --------------------------------------------------- |
|
|
42
|
+
| `cb login` | Authenticate with Circleback via browser |
|
|
43
|
+
| `cb logout` | Clear stored authentication tokens |
|
|
44
|
+
| `cb update` | Update CLI to the latest version |
|
|
45
|
+
| `cb meetings [search]` | Search meetings |
|
|
46
|
+
| `cb meetings read <ids...>` | Read detailed meeting info, notes, and action items |
|
|
47
|
+
| `cb transcripts <search>` | Search transcript content |
|
|
48
|
+
| `cb transcripts read <ids...>` | Read full transcripts |
|
|
49
|
+
| `cb calendar` | Search calendar events |
|
|
50
|
+
| `cb people <names...>` | Search people by name |
|
|
51
|
+
| `cb companies <terms...>` | Search companies by domain |
|
|
52
|
+
| `cb emails [search]` | Search connected email accounts |
|
|
53
|
+
| `cb support [search]` | Search Circleback support articles |
|
|
54
|
+
|
|
55
|
+
Run `cb --help` or `cb <command> --help` for full usage details.
|
|
56
|
+
|
|
57
|
+
## Filtering
|
|
58
|
+
|
|
59
|
+
Meeting, transcript, and calendar searches support filtering by date range. Meeting and transcript searches also support filtering by tag, person, and company:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
cb meetings --tags 1,2 --profiles 42 --domains acme.com
|
|
63
|
+
cb meetings --last 30 # Last 30 days
|
|
64
|
+
cb meetings --from 2026-01-01 --to 2026-03-01
|
|
65
|
+
cb calendar --last 7 # Next 7 days of events
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Email search supports inline filter syntax:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
cb emails "from:alice@example.com"
|
|
72
|
+
cb emails "to:bob@example.com after:2026-01-01"
|
|
73
|
+
cb emails "participant:carol@example.com before:2026-06-01"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## JSON output
|
|
77
|
+
|
|
78
|
+
Pass `--json` to any command to get raw JSON instead of formatted tables. Useful for piping to `jq` or integrating with other tools:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
cb meetings "standup" --json | jq '.[].name'
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Authentication
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
cb login # Opens browser to authenticate
|
|
88
|
+
cb logout # Clears stored tokens
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Agents
|
|
92
|
+
|
|
93
|
+
The CLI is designed to work well with AI agents and tool-use systems:
|
|
94
|
+
|
|
95
|
+
- Every command accepts `--json` for structured, parseable output
|
|
96
|
+
- Commands follow a consistent `<resource> <action>` pattern
|
|
97
|
+
- All options have long-form names for clarity
|
|
98
|
+
- Error messages include actionable instructions
|
|
99
|
+
- Exit codes follow standard conventions (0 = success, 1 = error)
|
|
100
|
+
|
|
101
|
+
## Links
|
|
102
|
+
|
|
103
|
+
- [Circleback](https://circleback.ai)
|
|
104
|
+
- [Support](https://support.circleback.ai)
|
|
@@ -64,7 +64,7 @@ exports.searchMeetingsCommand = new commander_1.Command('search')
|
|
|
64
64
|
});
|
|
65
65
|
exports.readMeetingsCommand = new commander_1.Command('read')
|
|
66
66
|
.description('Get detailed information for meetings.')
|
|
67
|
-
.argument('<ids
|
|
67
|
+
.argument('<ids...>', 'Meeting IDs')
|
|
68
68
|
.action(async (ids) => {
|
|
69
69
|
try {
|
|
70
70
|
const jsonMode = exports.readMeetingsCommand.parent?.parent?.opts().json ?? false;
|
|
@@ -12,18 +12,18 @@ const formatter_1 = require("../helpers/formatter");
|
|
|
12
12
|
const parse_1 = require("../helpers/parse");
|
|
13
13
|
exports.searchTranscriptsCommand = new commander_1.Command('search')
|
|
14
14
|
.description('Search meeting transcripts.')
|
|
15
|
-
.argument('
|
|
15
|
+
.argument('<search>', 'Search term')
|
|
16
16
|
.option('--tags <ids>', 'Comma-separated tag IDs')
|
|
17
17
|
.option('--profiles <ids>', 'Comma-separated profile IDs')
|
|
18
18
|
.option('--domains <domains>', 'Comma-separated domains')
|
|
19
|
+
.showHelpAfterError(true)
|
|
19
20
|
.action(async (query, options) => {
|
|
20
21
|
try {
|
|
21
22
|
const jsonMode = exports.searchTranscriptsCommand.parent?.parent?.opts().json ?? false;
|
|
22
23
|
const arguments_ = {
|
|
23
|
-
intent: query
|
|
24
|
+
intent: query,
|
|
25
|
+
searchTerm: query,
|
|
24
26
|
};
|
|
25
|
-
if (query)
|
|
26
|
-
arguments_['searchTerm'] = query;
|
|
27
27
|
if (options.tags)
|
|
28
28
|
arguments_['tags'] = (0, parse_1.parseNumericIds)(options.tags, 'tag ID');
|
|
29
29
|
if (options.profiles)
|
|
@@ -41,7 +41,7 @@ exports.searchTranscriptsCommand = new commander_1.Command('search')
|
|
|
41
41
|
});
|
|
42
42
|
exports.getTranscriptsCommand = new commander_1.Command('read')
|
|
43
43
|
.description('Get full transcripts for meetings.')
|
|
44
|
-
.argument('<meetingId
|
|
44
|
+
.argument('<meetingId...>', 'Meeting IDs (max 50)')
|
|
45
45
|
.showHelpAfterError(true)
|
|
46
46
|
.action(async (ids) => {
|
|
47
47
|
try {
|
|
@@ -11,7 +11,7 @@ const safeParse = (rawText) => {
|
|
|
11
11
|
}
|
|
12
12
|
};
|
|
13
13
|
const stringify = (value) => typeof value === 'string' ? value : JSON.stringify(value, null, 2);
|
|
14
|
-
const
|
|
14
|
+
const formatForTool = (toolName, data) => {
|
|
15
15
|
if (!Array.isArray(data))
|
|
16
16
|
return;
|
|
17
17
|
switch (toolName) {
|
|
@@ -43,7 +43,7 @@ const formatOutput = (toolName, rawText, jsonMode) => {
|
|
|
43
43
|
console.log(stringify(parsed));
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
|
-
const formatted =
|
|
46
|
+
const formatted = formatForTool(toolName, parsed);
|
|
47
47
|
console.log(formatted ?? stringify(parsed));
|
|
48
48
|
};
|
|
49
49
|
exports.formatOutput = formatOutput;
|
package/dist/helpers/table.js
CHANGED
|
@@ -13,8 +13,21 @@ const idColumnWidth = (ids) => {
|
|
|
13
13
|
const maxLength = ids.reduce((max, id) => Math.max(max, String(id).length), 2);
|
|
14
14
|
return maxLength + 2;
|
|
15
15
|
};
|
|
16
|
-
const formatAttendee = (attendee) =>
|
|
16
|
+
const formatAttendee = (attendee) => {
|
|
17
|
+
if (attendee.name && attendee.email)
|
|
18
|
+
return `${attendee.name} (${attendee.email})`;
|
|
19
|
+
return attendee.name ?? attendee.email ?? 'Unknown';
|
|
20
|
+
};
|
|
21
|
+
const formatAssignee = (assignee) => {
|
|
22
|
+
if (!assignee)
|
|
23
|
+
return 'Unassigned';
|
|
24
|
+
if (assignee.name && assignee.email)
|
|
25
|
+
return `${assignee.name} (${assignee.email})`;
|
|
26
|
+
return assignee.name ?? assignee.email ?? 'Unassigned';
|
|
27
|
+
};
|
|
17
28
|
const formatSender = (sender) => {
|
|
29
|
+
if (!sender)
|
|
30
|
+
return 'Unknown';
|
|
18
31
|
if (typeof sender === 'string')
|
|
19
32
|
return sender;
|
|
20
33
|
if (sender.name && sender.email)
|
|
@@ -62,9 +75,9 @@ const formatMeetingDetails = (meetings) => {
|
|
|
62
75
|
meeting.duration
|
|
63
76
|
? `${chalk_1.default.bold('Duration:')} ${Math.round(meeting.duration / 60)} min`
|
|
64
77
|
: null,
|
|
65
|
-
`${chalk_1.default.bold('Attendees:')}\n${meeting.attendees.map((attendee) => ` ${formatAttendee(attendee)}`).join('\n')}`,
|
|
66
|
-
meeting.tags.length > 0
|
|
67
|
-
? `${chalk_1.default.bold('Tags:')} ${meeting.tags
|
|
78
|
+
`${chalk_1.default.bold('Attendees:')}\n${(meeting.attendees ?? []).map((attendee) => ` ${formatAttendee(attendee)}`).join('\n')}`,
|
|
79
|
+
(meeting.tags ?? []).length > 0
|
|
80
|
+
? `${chalk_1.default.bold('Tags:')} ${(meeting.tags ?? []).join(', ')}`
|
|
68
81
|
: null,
|
|
69
82
|
]
|
|
70
83
|
.filter(Boolean)
|
|
@@ -73,7 +86,7 @@ const formatMeetingDetails = (meetings) => {
|
|
|
73
86
|
? `\n${chalk_1.default.bold('Notes:')}\n${meeting.notes}`
|
|
74
87
|
: '';
|
|
75
88
|
const actionItems = meeting.actionItems && meeting.actionItems.length > 0
|
|
76
|
-
? `\n${chalk_1.default.bold('Action Items:')}\n${meeting.actionItems.map((item) => ` - ${item.
|
|
89
|
+
? `\n${chalk_1.default.bold('Action Items:')}\n${meeting.actionItems.map((item) => ` - ${item.title} [${formatAssignee(item.assignee)}]`).join('\n')}`
|
|
77
90
|
: '';
|
|
78
91
|
return `${header}\n${meta}${notes}${actionItems}`;
|
|
79
92
|
})
|
|
@@ -108,7 +121,7 @@ const formatTranscriptSearchResults = (results) => {
|
|
|
108
121
|
});
|
|
109
122
|
results.forEach((result) => {
|
|
110
123
|
table.push([
|
|
111
|
-
(0, string_1.truncate)(result.meetingName
|
|
124
|
+
(0, string_1.truncate)(result.meetingName || 'New meeting', meetingWidth - 2),
|
|
112
125
|
result.meetingId,
|
|
113
126
|
(0, string_1.truncate)(result.content, contentWidth - 2),
|
|
114
127
|
]);
|
|
@@ -137,7 +150,7 @@ const formatCalendarEvents = (events) => {
|
|
|
137
150
|
(0, date_1.formatDateTime)(event.startTime),
|
|
138
151
|
(0, date_1.formatDateTime)(event.endTime),
|
|
139
152
|
event.platform ?? '-',
|
|
140
|
-
event.attendees
|
|
153
|
+
event.attendees?.map(formatAttendee).join('\n') ?? '-',
|
|
141
154
|
]);
|
|
142
155
|
});
|
|
143
156
|
return `${chalk_1.default.bold((0, string_1.pluralize)(events.length, 'event', 'events'))}\n${table.toString()}`;
|
|
@@ -189,23 +202,19 @@ const formatSupportArticles = (articles) => {
|
|
|
189
202
|
.join('\n\n');
|
|
190
203
|
};
|
|
191
204
|
exports.formatSupportArticles = formatSupportArticles;
|
|
205
|
+
const formatMessage = (message) => {
|
|
206
|
+
const header = chalk_1.default.bold.cyan(message.subject ?? 'No subject');
|
|
207
|
+
const meta = `${chalk_1.default.bold('From:')} ${formatSender(message.sender)} ${chalk_1.default.dim((0, date_1.formatDateTime)(message.receivedOn))}`;
|
|
208
|
+
const preview = message.content
|
|
209
|
+
? (0, string_1.truncate)(message.content.replace(/\s+/g, ' ').trim(), 200)
|
|
210
|
+
: '';
|
|
211
|
+
return `${header}\n${meta}\n${preview}`;
|
|
212
|
+
};
|
|
192
213
|
const formatEmails = (threads) => {
|
|
193
214
|
if (threads.length === 0)
|
|
194
215
|
return chalk_1.default.yellow('No emails found.');
|
|
195
216
|
return threads
|
|
196
|
-
.
|
|
197
|
-
const latestMessage = thread.messages[0];
|
|
198
|
-
if (!latestMessage)
|
|
199
|
-
return '';
|
|
200
|
-
const header = chalk_1.default.bold.cyan(latestMessage.subject);
|
|
201
|
-
const meta = `${chalk_1.default.bold('From:')} ${formatSender(latestMessage.sender)} ${chalk_1.default.dim((0, date_1.formatDateTime)(latestMessage.receivedOn))}`;
|
|
202
|
-
const messageCount = thread.messages.length > 1
|
|
203
|
-
? chalk_1.default.dim(`(${thread.messages.length} messages in thread)`)
|
|
204
|
-
: '';
|
|
205
|
-
const preview = (0, string_1.truncate)(latestMessage.content.replace(/\s+/g, ' ').trim(), 200);
|
|
206
|
-
return `${header}\n${meta} ${messageCount}\n${preview}`;
|
|
207
|
-
})
|
|
208
|
-
.filter(Boolean)
|
|
217
|
+
.flatMap((thread) => thread.messages.map(formatMessage))
|
|
209
218
|
.join('\n\n---\n');
|
|
210
219
|
};
|
|
211
220
|
exports.formatEmails = formatEmails;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@circleback/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Circleback CLI. Search and access meetings, emails, calendar events, and more.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Circleback <support@circleback.ai> (https://circleback.ai)",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"prebuild": "rm -rf dist",
|
|
26
26
|
"build": "tsc && chmod +x dist/index.js",
|
|
27
27
|
"typecheck": "tsc --noEmit",
|
|
28
|
+
"postinstall": "echo '\n Welcome to the Circleback CLI. Run `circleback` or `cb` to get started.\n'",
|
|
28
29
|
"prepublishOnly": "npm run build"
|
|
29
30
|
},
|
|
30
31
|
"publishConfig": {
|