@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 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…>', 'Meeting 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('[search]', 'Search term')
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 ?? 'Listing recent transcripts.',
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…>', 'Meeting IDs (max 50)')
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 formatArrayForTool = (toolName, data) => {
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 = formatArrayForTool(toolName, parsed);
46
+ const formatted = formatForTool(toolName, parsed);
47
47
  console.log(formatted ?? stringify(parsed));
48
48
  };
49
49
  exports.formatOutput = formatOutput;
@@ -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) => attendee.email ? `${attendee.name} (${attendee.email})` : attendee.name;
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.map((tag) => tag.name).join(', ')}`
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.text}${item.assignee ? ` (${item.assignee})` : ''}`).join('\n')}`
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 ?? 'Unknown', meetingWidth - 2),
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.map(formatAttendee).join('\n'),
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
- .map((thread) => {
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.0",
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": {