@circleback/cli 0.2.3 → 0.3.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 CHANGED
@@ -14,7 +14,7 @@ Requires Node.js 18 or later. The package installs two binaries: `circleback` an
14
14
 
15
15
  ```bash
16
16
  # Authenticate via browser
17
- cb login
17
+ cb auth login
18
18
 
19
19
  # Search recent meetings
20
20
  cb meetings
@@ -39,8 +39,9 @@ cb meetings --json
39
39
 
40
40
  | Command | Description |
41
41
  | ------------------------------ | ------------------------------------------------------------ |
42
- | `cb login` | Authenticate with Circleback via browser |
43
- | `cb logout` | Clear stored authentication tokens |
42
+ | `cb auth login` | Login with Circleback via browser |
43
+ | `cb auth login --api-key` | Authenticate with a Public API key |
44
+ | `cb auth logout` | Clear stored login credentials |
44
45
  | `cb update` | Update CLI to the latest version |
45
46
  | `cb meetings [search]` | Search meetings |
46
47
  | `cb meetings read <ids...>` | Read detailed meeting info, notes, and action items (max 50) |
@@ -88,10 +89,21 @@ cb meetings "standup" --json | jq '.[].name'
88
89
  ## Authentication
89
90
 
90
91
  ```bash
91
- cb login # Opens browser to authenticate
92
- cb logout # Clears stored tokens
92
+ cb auth login # Opens the browser for OAuth
93
+ cb auth login --api-key # Prompts securely for a Public API key
94
+ cb auth logout # Clears stored credentials
93
95
  ```
94
96
 
97
+ API-key login links to [Settings → API keys](https://circleback.ai/settings?tab=api-access), verifies the key, and stores it locally on your device.
98
+
99
+ For stateless automation, set `CIRCLEBACK_API_KEY` instead of logging in:
100
+
101
+ ```bash
102
+ CIRCLEBACK_API_KEY=cb_your_api_key cb meetings --json
103
+ ```
104
+
105
+ `CIRCLEBACK_API_KEY` takes precedence over stored credentials. Unset it before running `cb auth login`; `cb auth logout` cannot clear an environment variable from your shell.
106
+
95
107
  ## Agents
96
108
 
97
109
  The CLI is designed to work well with AI agents and tool-use systems:
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateApiKey = exports.promptForApiKey = void 0;
4
+ const node_readline_1 = require("node:readline");
5
+ const constants_1 = require("../constants");
6
+ const promptForApiKey = async () => {
7
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
8
+ throw new Error(`API key login requires an interactive terminal. Set ${constants_1.API_KEY_ENVIRONMENT_VARIABLE} for automation.`);
9
+ }
10
+ process.stdout.write('API key: ');
11
+ (0, node_readline_1.emitKeypressEvents)(process.stdin);
12
+ process.stdin.setRawMode(true);
13
+ return new Promise((resolve, reject) => {
14
+ const characters = [];
15
+ const finish = (result) => {
16
+ process.stdin.off('keypress', handleKeypress);
17
+ process.stdin.setRawMode(false);
18
+ process.stdin.pause();
19
+ process.stdout.write('\n');
20
+ if (result instanceof Error)
21
+ reject(result);
22
+ else
23
+ resolve(result);
24
+ };
25
+ const handleKeypress = (input, key) => {
26
+ if (key.ctrl && key.name === 'c') {
27
+ finish(new Error('API key login canceled.'));
28
+ return;
29
+ }
30
+ if (key.name === 'return' || key.name === 'enter') {
31
+ const apiKey = characters.join('').trim();
32
+ finish(apiKey || new Error('API key is required.'));
33
+ return;
34
+ }
35
+ if (key.name === 'backspace' ||
36
+ key.name === 'delete' ||
37
+ input === '\b' ||
38
+ input === '\x7f') {
39
+ if (characters.pop())
40
+ process.stdout.write('\b \b');
41
+ return;
42
+ }
43
+ if (!input || key.ctrl || key.meta || /[\u0000-\u001f\u007f]/.test(input))
44
+ return;
45
+ characters.push(...input);
46
+ process.stdout.write('*'.repeat([...input].length));
47
+ };
48
+ process.stdin.on('keypress', handleKeypress);
49
+ });
50
+ };
51
+ exports.promptForApiKey = promptForApiKey;
52
+ const validateApiKey = async (apiKey) => {
53
+ if (!apiKey.startsWith(constants_1.API_KEY_PREFIX)) {
54
+ throw new Error('The provided API key is invalid or has been revoked.');
55
+ }
56
+ try {
57
+ const response = await fetch(`${constants_1.BASE_URL}/api/meetings`, {
58
+ headers: { Authorization: `Bearer ${apiKey}` },
59
+ });
60
+ if (response.ok)
61
+ return;
62
+ if (response.status === 401) {
63
+ throw new Error('The provided API key is invalid or has been revoked.');
64
+ }
65
+ if (response.status === 403) {
66
+ throw new Error('The provided API key does not have read access.');
67
+ }
68
+ throw new Error(`API key verification failed with status ${response.status}.`);
69
+ }
70
+ catch (error) {
71
+ if (error instanceof TypeError && error.message.includes('fetch failed')) {
72
+ throw new Error('Unable to verify the API key. Check your internet connection and try again.');
73
+ }
74
+ throw error;
75
+ }
76
+ };
77
+ exports.validateApiKey = validateApiKey;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCredentialSnapshot = exports.getEnvironmentApiKey = void 0;
4
+ const constants_1 = require("../constants");
5
+ const types_1 = require("../types");
6
+ const oauth_1 = require("./oauth");
7
+ const tokenStorage_1 = require("./tokenStorage");
8
+ const getEnvironmentApiKey = () => {
9
+ const apiKey = process.env[constants_1.API_KEY_ENVIRONMENT_VARIABLE]?.trim();
10
+ return apiKey || undefined;
11
+ };
12
+ exports.getEnvironmentApiKey = getEnvironmentApiKey;
13
+ const requireCredentials = () => {
14
+ const environmentApiKey = (0, exports.getEnvironmentApiKey)();
15
+ if (environmentApiKey) {
16
+ if (!environmentApiKey.startsWith(constants_1.API_KEY_PREFIX)) {
17
+ throw new Error(`${constants_1.API_KEY_ENVIRONMENT_VARIABLE} must start with ${constants_1.API_KEY_PREFIX}.`);
18
+ }
19
+ return {
20
+ type: types_1.CredentialType.ApiKey,
21
+ apiKey: environmentApiKey,
22
+ source: types_1.CredentialSource.Environment,
23
+ };
24
+ }
25
+ const storedCredentials = (0, tokenStorage_1.loadCredentials)();
26
+ if (!storedCredentials) {
27
+ throw new Error(`Not authenticated. Run \`circleback auth login\` or set ${constants_1.API_KEY_ENVIRONMENT_VARIABLE}.`);
28
+ }
29
+ if ((0, types_1.isStoredApiKey)(storedCredentials)) {
30
+ return {
31
+ ...storedCredentials,
32
+ source: types_1.CredentialSource.Stored,
33
+ };
34
+ }
35
+ return { type: types_1.CredentialType.OAuth, tokens: storedCredentials };
36
+ };
37
+ const getCredentialSnapshot = async () => {
38
+ const credentials = requireCredentials();
39
+ if (credentials.type === types_1.CredentialType.OAuth) {
40
+ const tokens = await (0, oauth_1.getValidOAuthTokens)(credentials.tokens);
41
+ return {
42
+ bearerCredential: tokens.accessToken,
43
+ refresh: () => (0, oauth_1.forceRefreshOAuthToken)(tokens),
44
+ };
45
+ }
46
+ const source = credentials.source === types_1.CredentialSource.Environment
47
+ ? constants_1.API_KEY_ENVIRONMENT_VARIABLE
48
+ : 'The stored API key';
49
+ return {
50
+ bearerCredential: credentials.apiKey,
51
+ refresh: () => {
52
+ throw new Error(`${source} is invalid or has been revoked.`);
53
+ },
54
+ };
55
+ };
56
+ exports.getCredentialSnapshot = getCredentialSnapshot;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.forceRefreshToken = exports.getValidAccessToken = exports.login = void 0;
6
+ exports.forceRefreshOAuthToken = exports.getValidOAuthTokens = exports.login = void 0;
7
7
  const node_child_process_1 = require("node:child_process");
8
8
  const node_crypto_1 = __importDefault(require("node:crypto"));
9
9
  const constants_1 = require("../constants");
@@ -125,7 +125,7 @@ const login = async (options = {}) => {
125
125
  codeVerifier: verifier,
126
126
  });
127
127
  const tokens = (0, tokenStorage_1.buildStoredTokens)(tokenResponse, clientId);
128
- (0, tokenStorage_1.saveTokens)(tokens);
128
+ (0, tokenStorage_1.saveCredentials)(tokens);
129
129
  return tokens;
130
130
  }
131
131
  catch (error) {
@@ -134,30 +134,19 @@ const login = async (options = {}) => {
134
134
  }
135
135
  };
136
136
  exports.login = login;
137
- const requireTokens = () => {
138
- const tokens = (0, tokenStorage_1.loadTokens)();
139
- if (!tokens) {
140
- throw new Error('Not authenticated. Run `circleback login` to authenticate.');
141
- }
142
- return tokens;
143
- };
144
137
  const refreshAndSave = async (tokens) => {
145
138
  const refreshed = await refreshAccessToken(tokens.refreshToken, tokens.clientId);
146
139
  const updatedTokens = (0, tokenStorage_1.buildStoredTokens)(refreshed, tokens.clientId);
147
- (0, tokenStorage_1.saveTokens)(updatedTokens);
148
- return updatedTokens.accessToken;
140
+ (0, tokenStorage_1.saveCredentials)(updatedTokens);
141
+ return updatedTokens;
149
142
  };
150
- const getValidAccessToken = async () => {
151
- const tokens = requireTokens();
143
+ const getValidOAuthTokens = async (tokens) => {
152
144
  const bufferSeconds = 60;
153
145
  const isExpired = Math.floor(Date.now() / 1000) >= tokens.expiresAt - bufferSeconds;
154
146
  if (!isExpired)
155
- return tokens.accessToken;
156
- return refreshAndSave(tokens);
157
- };
158
- exports.getValidAccessToken = getValidAccessToken;
159
- const forceRefreshToken = async () => {
160
- const tokens = requireTokens();
147
+ return tokens;
161
148
  return refreshAndSave(tokens);
162
149
  };
163
- exports.forceRefreshToken = forceRefreshToken;
150
+ exports.getValidOAuthTokens = getValidOAuthTokens;
151
+ const forceRefreshOAuthToken = async (tokens) => (await refreshAndSave(tokens)).accessToken;
152
+ exports.forceRefreshOAuthToken = forceRefreshOAuthToken;
@@ -3,12 +3,23 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.clearTokens = exports.saveTokens = exports.loadTokens = exports.buildStoredTokens = void 0;
6
+ exports.clearCredentials = exports.saveCredentials = exports.loadCredentials = exports.buildStoredTokens = void 0;
7
7
  const node_fs_1 = __importDefault(require("node:fs"));
8
8
  const constants_1 = require("../constants");
9
9
  const types_1 = require("../types");
10
10
  const DIRECTORY_PERMISSIONS_MODE = 0o700;
11
11
  const FILE_PERMISSIONS_MODE = 0o600;
12
+ const isFileNotFoundError = (error) => error instanceof Error && 'code' in error && error.code === 'ENOENT';
13
+ const openCredentialsFile = () => {
14
+ try {
15
+ return node_fs_1.default.openSync(constants_1.TOKENS_FILE, 'r+');
16
+ }
17
+ catch (error) {
18
+ if (!isFileNotFoundError(error))
19
+ throw error;
20
+ return node_fs_1.default.openSync(constants_1.TOKENS_FILE, 'wx+', FILE_PERMISSIONS_MODE);
21
+ }
22
+ };
12
23
  const buildStoredTokens = (response, clientId) => ({
13
24
  accessToken: response.access_token,
14
25
  refreshToken: response.refresh_token,
@@ -17,35 +28,43 @@ const buildStoredTokens = (response, clientId) => ({
17
28
  scope: response.scope,
18
29
  });
19
30
  exports.buildStoredTokens = buildStoredTokens;
20
- const loadTokens = () => {
31
+ const loadCredentials = () => {
21
32
  try {
22
33
  const content = node_fs_1.default.readFileSync(constants_1.TOKENS_FILE, 'utf-8');
23
34
  const parsed = JSON.parse(content);
24
- if (!(0, types_1.isStoredTokens)(parsed))
25
- return;
26
- return parsed;
35
+ return (0, types_1.parseStoredCredentials)(parsed);
27
36
  }
28
37
  catch {
29
38
  return;
30
39
  }
31
40
  };
32
- exports.loadTokens = loadTokens;
33
- const saveTokens = (tokens) => {
41
+ exports.loadCredentials = loadCredentials;
42
+ const saveCredentials = (credentials) => {
34
43
  node_fs_1.default.mkdirSync(constants_1.CONFIG_DIRECTORY, {
35
44
  recursive: true,
36
45
  mode: DIRECTORY_PERMISSIONS_MODE,
37
46
  });
38
- node_fs_1.default.writeFileSync(constants_1.TOKENS_FILE, JSON.stringify(tokens, null, 2), {
39
- mode: FILE_PERMISSIONS_MODE,
40
- });
47
+ node_fs_1.default.chmodSync(constants_1.CONFIG_DIRECTORY, DIRECTORY_PERMISSIONS_MODE);
48
+ const fileDescriptor = openCredentialsFile();
49
+ try {
50
+ node_fs_1.default.fchmodSync(fileDescriptor, FILE_PERMISSIONS_MODE);
51
+ node_fs_1.default.ftruncateSync(fileDescriptor);
52
+ node_fs_1.default.writeFileSync(fileDescriptor, JSON.stringify(credentials, null, 2));
53
+ }
54
+ finally {
55
+ node_fs_1.default.closeSync(fileDescriptor);
56
+ }
41
57
  };
42
- exports.saveTokens = saveTokens;
43
- const clearTokens = () => {
58
+ exports.saveCredentials = saveCredentials;
59
+ const clearCredentials = () => {
44
60
  try {
45
61
  node_fs_1.default.unlinkSync(constants_1.TOKENS_FILE);
62
+ return true;
46
63
  }
47
- catch {
48
- // File may not exist.
64
+ catch (error) {
65
+ if (isFileNotFoundError(error))
66
+ return false;
67
+ throw error;
49
68
  }
50
69
  };
51
- exports.clearTokens = clearTokens;
70
+ exports.clearCredentials = clearCredentials;
@@ -1,30 +1,32 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.callTool = void 0;
4
+ const zod_1 = require("zod");
4
5
  const constants_1 = require("../constants");
5
6
  const types_1 = require("../types");
6
- const oauth_1 = require("../auth/oauth");
7
+ const credentials_1 = require("../auth/credentials");
7
8
  const TOOLS_CALL_METHOD = 'tools/call';
9
+ const errorResponseSchema = zod_1.z.object({
10
+ error: zod_1.z.string().optional(),
11
+ message: zod_1.z.string().optional(),
12
+ });
8
13
  let requestId = 0;
9
14
  const nextId = () => ++requestId;
10
15
  const extractErrorMessage = async (response) => {
11
16
  const text = await response.text();
12
17
  try {
13
18
  const body = JSON.parse(text);
14
- if (typeof body !== 'object' || body === null)
15
- return text;
16
- const error = 'error' in body && typeof body.error === 'string' ? body.error : undefined;
17
- const message = 'message' in body && typeof body.message === 'string'
18
- ? body.message
19
- : undefined;
20
- return error ?? message ?? text;
19
+ const result = errorResponseSchema.safeParse(body);
20
+ return result.success
21
+ ? (result.data.error ?? result.data.message ?? text)
22
+ : text;
21
23
  }
22
24
  catch {
23
25
  return text;
24
26
  }
25
27
  };
26
28
  const HTTP_ERROR_MESSAGES = {
27
- 401: 'Authentication expired. Run `circleback login` to re-authenticate.',
29
+ 401: 'Authentication expired. Run `circleback auth login` to re-authenticate.',
28
30
  403: 'Permission denied.',
29
31
  404: 'Resource not found.',
30
32
  429: 'Rate limit exceeded. Please try again later.',
@@ -101,8 +103,8 @@ const sendRequestWithNetworkError = async (method, params, accessToken) => {
101
103
  throw error;
102
104
  }
103
105
  };
104
- const retryWithRefresh = async (method, params) => {
105
- const accessToken = await (0, oauth_1.forceRefreshToken)();
106
+ const retryWithRefresh = async (method, params, refresh) => {
107
+ const accessToken = await refresh();
106
108
  const response = await sendRequestWithNetworkError(method, params, accessToken);
107
109
  if (!response.ok) {
108
110
  const message = await extractErrorMessage(response);
@@ -111,11 +113,11 @@ const retryWithRefresh = async (method, params) => {
111
113
  return parseResponse(response);
112
114
  };
113
115
  const callTool = async (toolName, toolArguments) => {
114
- const accessToken = await (0, oauth_1.getValidAccessToken)();
116
+ const credentialSnapshot = await (0, credentials_1.getCredentialSnapshot)();
115
117
  const params = { name: toolName, arguments: toolArguments };
116
- const response = await sendRequestWithNetworkError(TOOLS_CALL_METHOD, params, accessToken);
118
+ const response = await sendRequestWithNetworkError(TOOLS_CALL_METHOD, params, credentialSnapshot.bearerCredential);
117
119
  if (response.status === 401) {
118
- return retryWithRefresh(TOOLS_CALL_METHOD, params);
120
+ return retryWithRefresh(TOOLS_CALL_METHOD, params, credentialSnapshot.refresh);
119
121
  }
120
122
  if (!response.ok) {
121
123
  const fallback = HTTP_ERROR_MESSAGES[response.status];
@@ -1,15 +1,12 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.searchActionItemsCommand = void 0;
7
4
  const commander_1 = require("commander");
8
- const chalk_1 = __importDefault(require("chalk"));
9
5
  const jsonRpc_1 = require("../client/jsonRpc");
10
6
  const constants_1 = require("../constants");
11
7
  const date_1 = require("../helpers/date");
12
8
  const error_1 = require("../helpers/error");
9
+ const output_1 = require("../helpers/output");
13
10
  const formatter_1 = require("../helpers/formatter");
14
11
  const parse_1 = require("../helpers/parse");
15
12
  const tag_1 = require("../helpers/tag");
@@ -27,7 +24,7 @@ exports.searchActionItemsCommand = new commander_1.Command('search')
27
24
  .action(async (query, options) => {
28
25
  try {
29
26
  if (options.last && options.from) {
30
- console.error(chalk_1.default.red(constants_1.LAST_AND_FROM_CONFLICT_ERROR));
27
+ (0, output_1.printText)(constants_1.LAST_AND_FROM_CONFLICT_ERROR, output_1.TextType.Error);
31
28
  process.exit(1);
32
29
  }
33
30
  const jsonMode = exports.searchActionItemsCommand.parent?.parent?.opts().json ?? false;
@@ -61,7 +58,7 @@ exports.searchActionItemsCommand = new commander_1.Command('search')
61
58
  }
62
59
  catch (error) {
63
60
  const message = error instanceof Error ? error.message : constants_1.SEARCH_FAILED_ERROR;
64
- console.error(chalk_1.default.red((0, error_1.formatMcpError)(message) ?? message));
61
+ (0, output_1.printText)((0, error_1.formatMcpError)(message) ?? message, output_1.TextType.Error);
65
62
  process.exit(1);
66
63
  }
67
64
  });
@@ -3,11 +3,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.logoutCommand = exports.loginCommand = void 0;
6
+ exports.authCommand = void 0;
7
7
  const commander_1 = require("commander");
8
8
  const chalk_1 = __importDefault(require("chalk"));
9
+ const apiKey_1 = require("../auth/apiKey");
10
+ const credentials_1 = require("../auth/credentials");
9
11
  const oauth_1 = require("../auth/oauth");
10
12
  const tokenStorage_1 = require("../auth/tokenStorage");
13
+ const constants_1 = require("../constants");
14
+ const output_1 = require("../helpers/output");
15
+ const types_1 = require("../types");
11
16
  const AUTHORIZATION_URL_FALLBACK_DELAY_MILLISECONDS = 500;
12
17
  const getOpenUrlInstruction = () => process.platform === 'darwin' ? '⌘-click' : 'Ctrl-click';
13
18
  const createAuthorizationUrlFallback = () => {
@@ -16,8 +21,8 @@ const createAuthorizationUrlFallback = () => {
16
21
  const timeout = setTimeout(() => {
17
22
  if (abortController.signal.aborted)
18
23
  return;
19
- console.log(`Browser didn't open? ${getOpenUrlInstruction()} the URL below to log in.\n`);
20
- console.log(chalk_1.default.cyan(url));
24
+ (0, output_1.printText)(`Browser didn't open? ${getOpenUrlInstruction()} the URL below to log in.\n`, output_1.TextType.Default);
25
+ (0, output_1.printText)(chalk_1.default.cyan(url), output_1.TextType.Default);
21
26
  }, AUTHORIZATION_URL_FALLBACK_DELAY_MILLISECONDS);
22
27
  abortController.signal.addEventListener('abort', () => clearTimeout(timeout), { once: true });
23
28
  };
@@ -26,16 +31,30 @@ const createAuthorizationUrlFallback = () => {
26
31
  cleanup: () => abortController.abort(),
27
32
  };
28
33
  };
29
- exports.loginCommand = new commander_1.Command('login')
30
- .description('Authenticate with Circleback via browser.')
31
- .action(async () => {
34
+ const loginCommand = new commander_1.Command('login')
35
+ .description('Authenticate with Circleback.')
36
+ .option('--api-key', 'Authenticate with a Public API key')
37
+ .action(async (options) => {
32
38
  try {
33
- const existing = (0, tokenStorage_1.loadTokens)();
34
- if (existing) {
35
- console.log(chalk_1.default.yellow('Already authenticated. Run `circleback logout` first to re-authenticate.'));
39
+ if ((0, credentials_1.getEnvironmentApiKey)()) {
40
+ (0, output_1.printText)(`${constants_1.API_KEY_ENVIRONMENT_VARIABLE} is active. Unset it before changing stored authentication.`, output_1.TextType.Warning);
36
41
  return;
37
42
  }
38
- console.log('Opening your browser for authentication…\n');
43
+ if ((0, tokenStorage_1.loadCredentials)()) {
44
+ (0, output_1.printText)('Already authenticated. Run `circleback auth logout` first to re-authenticate.', output_1.TextType.Warning);
45
+ return;
46
+ }
47
+ if (options.apiKey) {
48
+ (0, output_1.printText)('Create an API key at:\n', output_1.TextType.Default);
49
+ (0, output_1.printText)(chalk_1.default.cyan(constants_1.API_KEY_SETTINGS_URL), output_1.TextType.Default);
50
+ (0, output_1.printText)('', output_1.TextType.Default);
51
+ const apiKey = await (0, apiKey_1.promptForApiKey)();
52
+ await (0, apiKey_1.validateApiKey)(apiKey);
53
+ (0, tokenStorage_1.saveCredentials)({ type: types_1.CredentialType.ApiKey, apiKey });
54
+ (0, output_1.printText)('API key authentication successful.', output_1.TextType.Success);
55
+ return;
56
+ }
57
+ (0, output_1.printText)('Opening your browser for authentication…\n', output_1.TextType.Default);
39
58
  const authorizationUrlFallback = createAuthorizationUrlFallback();
40
59
  try {
41
60
  await (0, oauth_1.login)({
@@ -45,22 +64,34 @@ exports.loginCommand = new commander_1.Command('login')
45
64
  finally {
46
65
  authorizationUrlFallback.cleanup();
47
66
  }
48
- console.log(chalk_1.default.green('Authentication successful.'));
67
+ (0, output_1.printText)('Authentication successful.', output_1.TextType.Success);
49
68
  }
50
69
  catch (error) {
51
70
  const message = error instanceof Error ? error.message : 'Authentication failed.';
52
- console.error(chalk_1.default.red(message));
71
+ (0, output_1.printText)(message, output_1.TextType.Error);
53
72
  process.exit(1);
54
73
  }
55
74
  });
56
- exports.logoutCommand = new commander_1.Command('logout')
57
- .description('Clear stored authentication tokens.')
75
+ const logoutCommand = new commander_1.Command('logout')
76
+ .description('Clear stored authentication credentials.')
58
77
  .action(() => {
59
- const existing = (0, tokenStorage_1.loadTokens)();
60
- if (!existing) {
61
- console.log(chalk_1.default.yellow('Not currently authenticated.'));
78
+ const environmentApiKey = (0, credentials_1.getEnvironmentApiKey)();
79
+ const didClearCredentials = (0, tokenStorage_1.clearCredentials)();
80
+ if (!didClearCredentials) {
81
+ if (environmentApiKey) {
82
+ (0, output_1.printText)(`No stored credentials to clear. ${constants_1.API_KEY_ENVIRONMENT_VARIABLE} is still active.`, output_1.TextType.Warning);
83
+ return;
84
+ }
85
+ (0, output_1.printText)('Not currently authenticated.', output_1.TextType.Warning);
86
+ return;
87
+ }
88
+ if (environmentApiKey) {
89
+ (0, output_1.printText)(`Stored credentials cleared. ${constants_1.API_KEY_ENVIRONMENT_VARIABLE} is still active.`, output_1.TextType.Warning);
62
90
  return;
63
91
  }
64
- (0, tokenStorage_1.clearTokens)();
65
- console.log(chalk_1.default.green('Logged out successfully.'));
92
+ (0, output_1.printText)('Logged out successfully.', output_1.TextType.Success);
66
93
  });
94
+ exports.authCommand = new commander_1.Command('auth')
95
+ .description('Manage authentication.')
96
+ .addCommand(loginCommand)
97
+ .addCommand(logoutCommand);
@@ -1,15 +1,12 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.searchCalendarCommand = void 0;
7
4
  const commander_1 = require("commander");
8
- const chalk_1 = __importDefault(require("chalk"));
9
5
  const jsonRpc_1 = require("../client/jsonRpc");
10
6
  const constants_1 = require("../constants");
11
7
  const date_1 = require("../helpers/date");
12
8
  const error_1 = require("../helpers/error");
9
+ const output_1 = require("../helpers/output");
13
10
  const formatter_1 = require("../helpers/formatter");
14
11
  const parse_1 = require("../helpers/parse");
15
12
  exports.searchCalendarCommand = new commander_1.Command('search')
@@ -20,7 +17,7 @@ exports.searchCalendarCommand = new commander_1.Command('search')
20
17
  .action(async (options) => {
21
18
  try {
22
19
  if (options.last && options.from) {
23
- console.error(chalk_1.default.red(constants_1.LAST_AND_FROM_CONFLICT_ERROR));
20
+ (0, output_1.printText)(constants_1.LAST_AND_FROM_CONFLICT_ERROR, output_1.TextType.Error);
24
21
  process.exit(1);
25
22
  }
26
23
  const jsonMode = exports.searchCalendarCommand.parent?.parent?.opts().json ?? false;
@@ -41,7 +38,7 @@ exports.searchCalendarCommand = new commander_1.Command('search')
41
38
  }
42
39
  catch (error) {
43
40
  const message = error instanceof Error ? error.message : 'Calendar search failed.';
44
- console.error(chalk_1.default.red((0, error_1.formatMcpError)(message) ?? message));
41
+ (0, output_1.printText)((0, error_1.formatMcpError)(message) ?? message, output_1.TextType.Error);
45
42
  process.exit(1);
46
43
  }
47
44
  });
@@ -1,13 +1,10 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.searchCompaniesCommand = void 0;
7
4
  const commander_1 = require("commander");
8
- const chalk_1 = __importDefault(require("chalk"));
9
5
  const jsonRpc_1 = require("../client/jsonRpc");
10
6
  const error_1 = require("../helpers/error");
7
+ const output_1 = require("../helpers/output");
11
8
  const formatter_1 = require("../helpers/formatter");
12
9
  exports.searchCompaniesCommand = new commander_1.Command('search')
13
10
  .description('Search companies.')
@@ -22,7 +19,7 @@ exports.searchCompaniesCommand = new commander_1.Command('search')
22
19
  }
23
20
  catch (error) {
24
21
  const message = error instanceof Error ? error.message : 'Company search failed.';
25
- console.error(chalk_1.default.red((0, error_1.formatMcpError)(message) ?? message));
22
+ (0, output_1.printText)((0, error_1.formatMcpError)(message) ?? message, output_1.TextType.Error);
26
23
  process.exit(1);
27
24
  }
28
25
  });
@@ -1,14 +1,11 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.searchEmailsCommand = void 0;
7
4
  const commander_1 = require("commander");
8
- const chalk_1 = __importDefault(require("chalk"));
9
5
  const jsonRpc_1 = require("../client/jsonRpc");
10
6
  const constants_1 = require("../constants");
11
7
  const error_1 = require("../helpers/error");
8
+ const output_1 = require("../helpers/output");
12
9
  const formatter_1 = require("../helpers/formatter");
13
10
  const parse_1 = require("../helpers/parse");
14
11
  exports.searchEmailsCommand = new commander_1.Command('search')
@@ -18,7 +15,7 @@ exports.searchEmailsCommand = new commander_1.Command('search')
18
15
  .action(async (query, options) => {
19
16
  try {
20
17
  if (query !== undefined && query.trim() === '') {
21
- console.error(chalk_1.default.red('Search term cannot be empty.'));
18
+ (0, output_1.printText)('Search term cannot be empty.', output_1.TextType.Error);
22
19
  process.exit(1);
23
20
  }
24
21
  const jsonMode = exports.searchEmailsCommand.parent?.parent?.opts().json ?? false;
@@ -33,7 +30,7 @@ exports.searchEmailsCommand = new commander_1.Command('search')
33
30
  }
34
31
  catch (error) {
35
32
  const message = error instanceof Error ? error.message : 'Email search failed.';
36
- console.error(chalk_1.default.red((0, error_1.formatMcpError)(message) ?? message));
33
+ (0, output_1.printText)((0, error_1.formatMcpError)(message) ?? message, output_1.TextType.Error);
37
34
  process.exit(1);
38
35
  }
39
36
  });
@@ -1,15 +1,12 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.readMeetingsCommand = exports.searchMeetingsCommand = void 0;
7
4
  const commander_1 = require("commander");
8
- const chalk_1 = __importDefault(require("chalk"));
9
5
  const jsonRpc_1 = require("../client/jsonRpc");
10
6
  const constants_1 = require("../constants");
11
7
  const date_1 = require("../helpers/date");
12
8
  const error_1 = require("../helpers/error");
9
+ const output_1 = require("../helpers/output");
13
10
  const formatter_1 = require("../helpers/formatter");
14
11
  const parse_1 = require("../helpers/parse");
15
12
  const tag_1 = require("../helpers/tag");
@@ -27,7 +24,7 @@ exports.searchMeetingsCommand = new commander_1.Command('search')
27
24
  .action(async (query, options) => {
28
25
  try {
29
26
  if (options.last && options.from) {
30
- console.error(chalk_1.default.red(constants_1.LAST_AND_FROM_CONFLICT_ERROR));
27
+ (0, output_1.printText)(constants_1.LAST_AND_FROM_CONFLICT_ERROR, output_1.TextType.Error);
31
28
  process.exit(1);
32
29
  }
33
30
  const jsonMode = exports.searchMeetingsCommand.parent?.parent?.opts().json ?? false;
@@ -61,7 +58,7 @@ exports.searchMeetingsCommand = new commander_1.Command('search')
61
58
  }
62
59
  catch (error) {
63
60
  const message = error instanceof Error ? error.message : constants_1.SEARCH_FAILED_ERROR;
64
- console.error(chalk_1.default.red((0, error_1.formatMcpError)(message) ?? message));
61
+ (0, output_1.printText)((0, error_1.formatMcpError)(message) ?? message, output_1.TextType.Error);
65
62
  process.exit(1);
66
63
  }
67
64
  });
@@ -80,7 +77,7 @@ exports.readMeetingsCommand = new commander_1.Command('read')
80
77
  }
81
78
  catch (error) {
82
79
  const message = error instanceof Error ? error.message : constants_1.READ_FAILED_ERROR;
83
- console.error(chalk_1.default.red((0, error_1.formatMcpError)(message) ?? message));
80
+ (0, output_1.printText)((0, error_1.formatMcpError)(message) ?? message, output_1.TextType.Error);
84
81
  process.exit(1);
85
82
  }
86
83
  });
@@ -1,13 +1,10 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.searchProfilesCommand = void 0;
7
4
  const commander_1 = require("commander");
8
- const chalk_1 = __importDefault(require("chalk"));
9
5
  const jsonRpc_1 = require("../client/jsonRpc");
10
6
  const error_1 = require("../helpers/error");
7
+ const output_1 = require("../helpers/output");
11
8
  const formatter_1 = require("../helpers/formatter");
12
9
  exports.searchProfilesCommand = new commander_1.Command('search')
13
10
  .description('Search people by name.')
@@ -22,7 +19,7 @@ exports.searchProfilesCommand = new commander_1.Command('search')
22
19
  }
23
20
  catch (error) {
24
21
  const message = error instanceof Error ? error.message : 'People search failed.';
25
- console.error(chalk_1.default.red((0, error_1.formatMcpError)(message) ?? message));
22
+ (0, output_1.printText)((0, error_1.formatMcpError)(message) ?? message, output_1.TextType.Error);
26
23
  process.exit(1);
27
24
  }
28
25
  });
@@ -13,11 +13,7 @@ const support_1 = require("./support");
13
13
  const tags_1 = require("./tags");
14
14
  const transcripts_1 = require("./transcripts");
15
15
  const update_1 = require("./update");
16
- const standaloneCommands = [
17
- auth_1.loginCommand,
18
- auth_1.logoutCommand,
19
- update_1.updateCommand,
20
- ];
16
+ const standaloneCommands = [auth_1.authCommand, update_1.updateCommand];
21
17
  const commandGroups = [
22
18
  {
23
19
  name: 'meetings',
@@ -1,13 +1,10 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.searchSupportCommand = void 0;
7
4
  const commander_1 = require("commander");
8
- const chalk_1 = __importDefault(require("chalk"));
9
5
  const jsonRpc_1 = require("../client/jsonRpc");
10
6
  const error_1 = require("../helpers/error");
7
+ const output_1 = require("../helpers/output");
11
8
  const formatter_1 = require("../helpers/formatter");
12
9
  exports.searchSupportCommand = new commander_1.Command('search')
13
10
  .description('Search Circleback support articles.')
@@ -24,7 +21,7 @@ exports.searchSupportCommand = new commander_1.Command('search')
24
21
  }
25
22
  catch (error) {
26
23
  const message = error instanceof Error ? error.message : 'Support search failed.';
27
- console.error(chalk_1.default.red((0, error_1.formatMcpError)(message) ?? message));
24
+ (0, output_1.printText)((0, error_1.formatMcpError)(message) ?? message, output_1.TextType.Error);
28
25
  process.exit(1);
29
26
  }
30
27
  });
@@ -1,13 +1,10 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.listTagsCommand = void 0;
7
4
  const commander_1 = require("commander");
8
- const chalk_1 = __importDefault(require("chalk"));
9
5
  const jsonRpc_1 = require("../client/jsonRpc");
10
6
  const error_1 = require("../helpers/error");
7
+ const output_1 = require("../helpers/output");
11
8
  const formatter_1 = require("../helpers/formatter");
12
9
  exports.listTagsCommand = new commander_1.Command('list')
13
10
  .description('List all tags.')
@@ -21,7 +18,7 @@ exports.listTagsCommand = new commander_1.Command('list')
21
18
  }
22
19
  catch (error) {
23
20
  const message = error instanceof Error ? error.message : 'Failed to list tags.';
24
- console.error(chalk_1.default.red((0, error_1.formatMcpError)(message) ?? message));
21
+ (0, output_1.printText)((0, error_1.formatMcpError)(message) ?? message, output_1.TextType.Error);
25
22
  process.exit(1);
26
23
  }
27
24
  });
@@ -1,14 +1,11 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.getTranscriptsCommand = exports.searchTranscriptsCommand = void 0;
7
4
  const commander_1 = require("commander");
8
- const chalk_1 = __importDefault(require("chalk"));
9
5
  const jsonRpc_1 = require("../client/jsonRpc");
10
6
  const constants_1 = require("../constants");
11
7
  const error_1 = require("../helpers/error");
8
+ const output_1 = require("../helpers/output");
12
9
  const formatter_1 = require("../helpers/formatter");
13
10
  const parse_1 = require("../helpers/parse");
14
11
  const tag_1 = require("../helpers/tag");
@@ -38,7 +35,7 @@ exports.searchTranscriptsCommand = new commander_1.Command('search')
38
35
  }
39
36
  catch (error) {
40
37
  const message = error instanceof Error ? error.message : 'Transcript search failed.';
41
- console.error(chalk_1.default.red((0, error_1.formatMcpError)(message) ?? message));
38
+ (0, output_1.printText)((0, error_1.formatMcpError)(message) ?? message, output_1.TextType.Error);
42
39
  process.exit(1);
43
40
  }
44
41
  });
@@ -58,7 +55,7 @@ exports.getTranscriptsCommand = new commander_1.Command('read')
58
55
  }
59
56
  catch (error) {
60
57
  const message = error instanceof Error ? error.message : 'Transcript retrieval failed.';
61
- console.error(chalk_1.default.red((0, error_1.formatMcpError)(message) ?? message));
58
+ (0, output_1.printText)((0, error_1.formatMcpError)(message) ?? message, output_1.TextType.Error);
62
59
  process.exit(1);
63
60
  }
64
61
  });
@@ -8,28 +8,28 @@ const commander_1 = require("commander");
8
8
  const node_child_process_1 = require("node:child_process");
9
9
  const chalk_1 = __importDefault(require("chalk"));
10
10
  const constants_1 = require("../constants");
11
+ const output_1 = require("../helpers/output");
11
12
  const update_1 = require("../helpers/update");
12
13
  exports.updateCommand = new commander_1.Command('update')
13
14
  .description('Update Circleback CLI to the latest version.')
14
15
  .action(async () => {
15
- console.log(chalk_1.default.dim(`Current version: ${constants_1.CLIENT_VERSION}`));
16
- console.log(chalk_1.default.dim('Checking for updates…'));
16
+ (0, output_1.printText)(chalk_1.default.dim(`Current version: ${constants_1.CLIENT_VERSION}`), output_1.TextType.Default);
17
+ (0, output_1.printText)(chalk_1.default.dim('Checking for updates…'), output_1.TextType.Default);
17
18
  const result = await (0, update_1.getUpdateCheckResult)(constants_1.CLIENT_PACKAGE_NAME);
18
19
  if (!result.updateAvailable) {
19
- console.log(chalk_1.default.green('Already on the latest version.'));
20
+ (0, output_1.printText)('Already on the latest version.', output_1.TextType.Success);
20
21
  return;
21
22
  }
22
- console.log(chalk_1.default.yellow(`New version available: ${result.currentVersion} → ${result.latestVersion}`));
23
- console.log(chalk_1.default.dim(`Running: npm install -g ${constants_1.CLIENT_PACKAGE_NAME}@latest`));
23
+ (0, output_1.printText)(`New version available: ${result.currentVersion} → ${result.latestVersion}`, output_1.TextType.Warning);
24
+ (0, output_1.printText)(chalk_1.default.dim(`Running: npm install -g ${constants_1.CLIENT_PACKAGE_NAME}@latest`), output_1.TextType.Default);
24
25
  try {
25
26
  (0, node_child_process_1.execSync)(`npm install -g ${constants_1.CLIENT_PACKAGE_NAME}@latest`, {
26
27
  stdio: 'inherit',
27
28
  });
28
- console.log(chalk_1.default.green(`\nSuccessfully updated to ${result.latestVersion}.`));
29
+ (0, output_1.printText)(`Successfully updated to ${result.latestVersion}.`, output_1.TextType.Success);
29
30
  }
30
31
  catch {
31
- console.error(chalk_1.default.red('\nUpdate failed. Try running manually with sudo:'));
32
- console.error(chalk_1.default.cyan(` sudo npm install -g ${constants_1.CLIENT_PACKAGE_NAME}@latest`));
32
+ (0, output_1.printText)(`Update failed. Try running manually with sudo:\n sudo npm install -g ${constants_1.CLIENT_PACKAGE_NAME}@latest`, output_1.TextType.Error);
33
33
  process.exit(1);
34
34
  }
35
35
  });
package/dist/constants.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.READ_FAILED_ERROR = exports.SEARCH_FAILED_ERROR = exports.LAST_AND_FROM_CONFLICT_ERROR = exports.DOMAINS_OPTION_DESCRIPTION = exports.PROFILES_OPTION_DESCRIPTION = exports.TAGS_OPTION_DESCRIPTION = exports.END_DATE_OPTION_DESCRIPTION = exports.START_DATE_OPTION_DESCRIPTION = exports.PAGE_OPTION_DESCRIPTION = exports.UNASSIGNED_ACTION_ITEM_ASSIGNEE_LABEL = exports.DEFAULT_MEETING_NAME = exports.CALLBACK_TIMEOUT_MILLISECONDS = exports.OAUTH_SCOPE = exports.TOKENS_FILE = exports.CONFIG_DIRECTORY = exports.ASCII_LOGO = exports.SECONDARY_COLOR = exports.BRAND_COLOR = exports.CLIENT_VERSION_HEADER = exports.CLIENT_VERSION = exports.CLIENT_PACKAGE_NAME = exports.CLIENT_NAME = exports.BASE_URL = void 0;
6
+ exports.READ_FAILED_ERROR = exports.SEARCH_FAILED_ERROR = exports.LAST_AND_FROM_CONFLICT_ERROR = exports.DOMAINS_OPTION_DESCRIPTION = exports.PROFILES_OPTION_DESCRIPTION = exports.TAGS_OPTION_DESCRIPTION = exports.END_DATE_OPTION_DESCRIPTION = exports.START_DATE_OPTION_DESCRIPTION = exports.PAGE_OPTION_DESCRIPTION = exports.UNASSIGNED_ACTION_ITEM_ASSIGNEE_LABEL = exports.DEFAULT_MEETING_NAME = exports.CALLBACK_TIMEOUT_MILLISECONDS = exports.OAUTH_SCOPE = exports.TOKENS_FILE = exports.CONFIG_DIRECTORY = exports.ASCII_LOGO = exports.SECONDARY_COLOR = exports.BRAND_COLOR = exports.API_KEY_SETTINGS_URL = exports.API_KEY_ENVIRONMENT_VARIABLE = exports.API_KEY_PREFIX = exports.CLIENT_VERSION_HEADER = exports.CLIENT_VERSION = exports.CLIENT_PACKAGE_NAME = exports.CLIENT_NAME = exports.BASE_URL = void 0;
7
7
  const node_fs_1 = __importDefault(require("node:fs"));
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
9
  const node_os_1 = __importDefault(require("node:os"));
@@ -16,6 +16,10 @@ exports.CLIENT_PACKAGE_NAME = '@circleback/cli';
16
16
  exports.CLIENT_VERSION = packageJson.version;
17
17
  // Must match CIRCLEBACK_CLI_VERSION_HEADER in apps/web/helpers/api/mcp/version.ts.
18
18
  exports.CLIENT_VERSION_HEADER = 'x-circleback-cli-version';
19
+ // Must match API_KEY_PREFIX in packages/constants/auth.ts.
20
+ exports.API_KEY_PREFIX = 'cb_';
21
+ exports.API_KEY_ENVIRONMENT_VARIABLE = 'CIRCLEBACK_API_KEY';
22
+ exports.API_KEY_SETTINGS_URL = `${exports.BASE_URL}/settings?tab=api-access`;
19
23
  exports.BRAND_COLOR = '#F24E1D';
20
24
  exports.SECONDARY_COLOR = '#4262FF';
21
25
  exports.ASCII_LOGO = `
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.formatOutput = void 0;
4
4
  const table_1 = require("./table");
5
5
  const parse_1 = require("./parse");
6
+ const output_1 = require("./output");
6
7
  const zod_1 = require("zod");
7
8
  const stringify = (value) => typeof value === 'string' ? value : JSON.stringify(value, null, 2);
8
9
  const formatForTool = (toolName, data) => {
@@ -38,10 +39,12 @@ const formatForTool = (toolName, data) => {
38
39
  const formatOutput = (toolName, rawText, jsonMode) => {
39
40
  const parsed = (0, parse_1.extractAndValidateObjectFromString)(rawText, zod_1.z.json());
40
41
  if (jsonMode) {
41
- console.log(stringify(parsed));
42
+ (0, output_1.printText)(stringify(parsed), output_1.TextType.Default);
42
43
  return;
43
44
  }
44
45
  const formatted = formatForTool(toolName, parsed);
45
- console.log(formatted ?? stringify(parsed));
46
+ (0, output_1.printText)(formatted ?? stringify(parsed), Array.isArray(parsed) && parsed.length === 0
47
+ ? output_1.TextType.Warning
48
+ : output_1.TextType.Default);
46
49
  };
47
50
  exports.formatOutput = formatOutput;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.printTextToStderr = exports.printText = exports.formatText = exports.TextType = void 0;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ var TextType;
9
+ (function (TextType) {
10
+ TextType["Default"] = "Default";
11
+ TextType["Error"] = "Error";
12
+ TextType["Success"] = "Success";
13
+ TextType["Warning"] = "Warning";
14
+ })(TextType || (exports.TextType = TextType = {}));
15
+ const TEXT_STYLES = {
16
+ [TextType.Default]: (message) => message,
17
+ [TextType.Error]: chalk_1.default.red,
18
+ [TextType.Success]: chalk_1.default.green,
19
+ [TextType.Warning]: chalk_1.default.yellow,
20
+ };
21
+ const formatText = (message, type) => {
22
+ const text = TEXT_STYLES[type](message);
23
+ return type === TextType.Default ? `${text}\n` : `\n${text}\n\n`;
24
+ };
25
+ exports.formatText = formatText;
26
+ const printText = (message, type) => {
27
+ const stream = type === TextType.Error ? process.stderr : process.stdout;
28
+ stream.write((0, exports.formatText)(message, type));
29
+ };
30
+ exports.printText = printText;
31
+ const printTextToStderr = (message, type) => {
32
+ process.stderr.write((0, exports.formatText)(message, type));
33
+ };
34
+ exports.printTextToStderr = printTextToStderr;
@@ -1,18 +1,15 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.validateMeetingIdentifiers = exports.parseNumericIds = exports.parseIntOption = exports.extractAndValidateObjectFromString = void 0;
7
- const chalk_1 = __importDefault(require("chalk"));
8
4
  const zod_1 = require("zod");
5
+ const output_1 = require("./output");
9
6
  const extractAndValidateObjectFromString = (rawText, schema) => {
10
7
  try {
11
8
  const json = JSON.parse(rawText);
12
9
  const result = schema.safeParse(json);
13
10
  if (result.success)
14
11
  return result.data;
15
- console.error(chalk_1.default.red('Failed to parse response.'));
12
+ (0, output_1.printText)('Failed to parse response.', output_1.TextType.Error);
16
13
  process.exit(1);
17
14
  }
18
15
  catch {
@@ -25,7 +22,7 @@ const numericIdsSchema = zod_1.z.array(intSchema);
25
22
  const parseIntOption = (value, flagName) => {
26
23
  const result = intSchema.safeParse(value);
27
24
  if (!result.success) {
28
- console.error(chalk_1.default.red(`Invalid number for ${flagName}.`));
25
+ (0, output_1.printText)(`Invalid number for ${flagName}.`, output_1.TextType.Error);
29
26
  process.exit(1);
30
27
  }
31
28
  return result.data;
@@ -34,7 +31,7 @@ exports.parseIntOption = parseIntOption;
34
31
  const parseNumericIds = (value, label) => {
35
32
  const result = numericIdsSchema.safeParse(value.split(','));
36
33
  if (!result.success) {
37
- console.error(chalk_1.default.red(`Invalid ${label}. Expected numeric IDs.`));
34
+ (0, output_1.printText)(`Invalid ${label}. Expected numeric IDs.`, output_1.TextType.Error);
38
35
  process.exit(1);
39
36
  }
40
37
  return result.data;
@@ -45,7 +42,7 @@ const validateMeetingIdentifiers = (values) => {
45
42
  const isValidIdentifier = /^[A-Za-z0-9_-]{1,64}$/;
46
43
  if (cleanedValues.length === 0 ||
47
44
  cleanedValues.some((value) => !isValidIdentifier.test(value))) {
48
- console.error(chalk_1.default.red('Invalid meeting identifiers. Expected one or more meeting IDs.'));
45
+ (0, output_1.printText)('Invalid meeting identifiers. Expected one or more meeting IDs.', output_1.TextType.Error);
49
46
  process.exit(1);
50
47
  }
51
48
  return cleanedValues;
@@ -38,7 +38,7 @@ const formatSender = (sender) => {
38
38
  };
39
39
  const formatMeetingsList = (meetings) => {
40
40
  if (meetings.length === 0)
41
- return chalk_1.default.yellow('No meetings found.');
41
+ return 'No meetings found.';
42
42
  const idWidth = idColumnWidth(meetings.map((meeting) => meeting.id));
43
43
  const nameWidth = 40;
44
44
  const dateWidth = 14;
@@ -65,7 +65,7 @@ const formatMeetingsList = (meetings) => {
65
65
  exports.formatMeetingsList = formatMeetingsList;
66
66
  const formatMeetingDetails = (meetings) => {
67
67
  if (meetings.length === 0)
68
- return chalk_1.default.yellow('No meetings found.');
68
+ return 'No meetings found.';
69
69
  return meetings
70
70
  .map((meeting) => {
71
71
  const header = chalk_1.default.bold.cyan(`\n# ${meeting.name}`);
@@ -98,7 +98,7 @@ const formatMeetingDetails = (meetings) => {
98
98
  exports.formatMeetingDetails = formatMeetingDetails;
99
99
  const formatTranscriptSearchResults = (results) => {
100
100
  if (results.length === 0)
101
- return chalk_1.default.yellow('No transcript matches found.');
101
+ return 'No transcript matches found.';
102
102
  const idWidth = idColumnWidth(results.map((result) => result.meetingId));
103
103
  const meetingWidth = 25;
104
104
  const contentWidth = 60;
@@ -118,7 +118,7 @@ const formatTranscriptSearchResults = (results) => {
118
118
  exports.formatTranscriptSearchResults = formatTranscriptSearchResults;
119
119
  const formatTranscriptReadResults = (results) => {
120
120
  if (results.length === 0)
121
- return chalk_1.default.yellow('No transcripts found.');
121
+ return 'No transcripts found.';
122
122
  return results
123
123
  .map((result) => {
124
124
  const header = chalk_1.default.bold.cyan(`\n# ${result.meetingName}`);
@@ -136,7 +136,7 @@ const formatTranscriptReadResults = (results) => {
136
136
  exports.formatTranscriptReadResults = formatTranscriptReadResults;
137
137
  const formatCalendarEvents = (events) => {
138
138
  if (events.length === 0)
139
- return chalk_1.default.yellow('No calendar events found.');
139
+ return 'No calendar events found.';
140
140
  const table = new cli_table3_1.default({
141
141
  head: [
142
142
  style_1.brand.bold('Title'),
@@ -161,7 +161,7 @@ const formatCalendarEvents = (events) => {
161
161
  exports.formatCalendarEvents = formatCalendarEvents;
162
162
  const formatProfiles = (profiles) => {
163
163
  if (profiles.length === 0)
164
- return chalk_1.default.yellow('No people found.');
164
+ return 'No people found.';
165
165
  const idWidth = idColumnWidth(profiles.map((profile) => profile.id));
166
166
  const nameWidth = 25;
167
167
  const emailWidth = 30;
@@ -191,7 +191,7 @@ const formatProfiles = (profiles) => {
191
191
  exports.formatProfiles = formatProfiles;
192
192
  const formatCompanies = (companies) => {
193
193
  if (companies.length === 0)
194
- return chalk_1.default.yellow('No companies found.');
194
+ return 'No companies found.';
195
195
  const table = new cli_table3_1.default({
196
196
  head: [style_1.brand.bold('Company'), style_1.brand.bold('Domain')],
197
197
  style: style_1.tableStyle,
@@ -202,7 +202,7 @@ const formatCompanies = (companies) => {
202
202
  exports.formatCompanies = formatCompanies;
203
203
  const formatSupportArticles = (articles) => {
204
204
  if (articles.length === 0)
205
- return chalk_1.default.yellow('No articles found.');
205
+ return 'No articles found.';
206
206
  return articles
207
207
  .map((article) => `${(0, string_1.link)(chalk_1.default.bold.cyan(article.title), article.url)}\n${(0, string_1.link)(chalk_1.default.dim.underline(article.url), article.url)}\n${article.description}`)
208
208
  .join('\n\n');
@@ -210,7 +210,7 @@ const formatSupportArticles = (articles) => {
210
210
  exports.formatSupportArticles = formatSupportArticles;
211
211
  const formatActionItems = (actionItems) => {
212
212
  if (actionItems.length === 0)
213
- return chalk_1.default.yellow('No action items found.');
213
+ return 'No action items found.';
214
214
  const idWidth = idColumnWidth(actionItems.map((item) => item.id));
215
215
  const titleWidth = 35;
216
216
  const statusWidth = 10;
@@ -250,7 +250,7 @@ const formatActionItems = (actionItems) => {
250
250
  exports.formatActionItems = formatActionItems;
251
251
  const formatTags = (tags) => {
252
252
  if (tags.length === 0)
253
- return chalk_1.default.yellow('No tags found.');
253
+ return 'No tags found.';
254
254
  const idWidth = idColumnWidth(tags.map((tag) => tag.id));
255
255
  const nameWidth = 40;
256
256
  const table = new cli_table3_1.default({
@@ -273,7 +273,7 @@ const formatMessage = (message) => {
273
273
  };
274
274
  const formatEmails = (threads) => {
275
275
  if (threads.length === 0)
276
- return chalk_1.default.yellow('No emails found.');
276
+ return 'No emails found.';
277
277
  return threads
278
278
  .flatMap((thread) => thread.messages.map(formatMessage))
279
279
  .join('\n\n---\n');
@@ -1,12 +1,9 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.resolveTagValues = void 0;
7
- const chalk_1 = __importDefault(require("chalk"));
8
4
  const zod_1 = require("zod");
9
5
  const jsonRpc_1 = require("../client/jsonRpc");
6
+ const output_1 = require("./output");
10
7
  const parse_1 = require("./parse");
11
8
  const tagItemSchema = zod_1.z.object({
12
9
  id: zod_1.z.number(),
@@ -25,12 +22,12 @@ const resolveTagValues = async (value) => {
25
22
  .map((part) => part.trim())
26
23
  .filter((part) => part.length > 0);
27
24
  if (parts.length === 0) {
28
- console.error(chalk_1.default.red('No tag values provided.'));
25
+ (0, output_1.printText)('No tag values provided.', output_1.TextType.Error);
29
26
  process.exit(1);
30
27
  }
31
28
  const tagList = await fetchUserTags();
32
29
  if (typeof tagList === 'string') {
33
- console.error(chalk_1.default.red('Failed to fetch tags.'));
30
+ (0, output_1.printText)('Failed to fetch tags.', output_1.TextType.Error);
34
31
  process.exit(1);
35
32
  }
36
33
  return parts.map((part) => {
@@ -41,7 +38,7 @@ const resolveTagValues = async (value) => {
41
38
  if (!isNaN(numericId) && tagList.some((tag) => tag.id === numericId)) {
42
39
  return numericId;
43
40
  }
44
- console.error(chalk_1.default.red(`Tag "${part}" not found.`));
41
+ (0, output_1.printText)(`Tag "${part}" not found.`, output_1.TextType.Error);
45
42
  process.exit(1);
46
43
  });
47
44
  };
@@ -8,6 +8,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
9
  const chalk_1 = __importDefault(require("chalk"));
10
10
  const constants_1 = require("../constants");
11
+ const output_1 = require("./output");
11
12
  const CACHE_FILE = node_path_1.default.join(constants_1.CONFIG_DIRECTORY, 'update-check.json');
12
13
  const CHECK_INTERVAL_MILLISECONDS = 60 * 60 * 1000; // 1 hour
13
14
  const REGISTRY_TIMEOUT_MILLISECONDS = 3_000;
@@ -96,11 +97,9 @@ const printUpdateNotification = (result) => {
96
97
  if (!result.updateAvailable)
97
98
  return;
98
99
  const message = [
99
- '',
100
- chalk_1.default.yellow(` Update available: ${chalk_1.default.dim(result.currentVersion)} → ${chalk_1.default.green(result.latestVersion)}`),
101
- chalk_1.default.yellow(` Run ${chalk_1.default.cyan('circleback update')} or ${chalk_1.default.cyan('cb update')} to update.`),
102
- '',
100
+ ` Update available: ${chalk_1.default.dim(result.currentVersion)} → ${chalk_1.default.green(result.latestVersion)}`,
101
+ ` Run ${chalk_1.default.cyan('circleback update')} or ${chalk_1.default.cyan('cb update')} to update.`,
103
102
  ].join('\n');
104
- console.error(message);
103
+ process.stderr.write((0, output_1.formatText)(message, output_1.TextType.Warning));
105
104
  };
106
105
  exports.printUpdateNotification = printUpdateNotification;
package/dist/index.js CHANGED
@@ -5,22 +5,28 @@ const commander_1 = require("commander");
5
5
  const registry_1 = require("./commands/registry");
6
6
  const constants_1 = require("./constants");
7
7
  const help_1 = require("./helpers/help");
8
+ const output_1 = require("./helpers/output");
8
9
  const style_1 = require("./helpers/style");
9
10
  const update_1 = require("./helpers/update");
10
11
  const program = new commander_1.Command();
11
12
  const updateCheck = (0, update_1.getUpdateCheckResult)(constants_1.CLIENT_PACKAGE_NAME);
13
+ const configureCommandOutput = (command) => {
14
+ command.configureOutput({
15
+ writeOut: (text) => (0, output_1.printText)((0, help_1.styleHelpOutput)(text).trimEnd(), output_1.TextType.Default),
16
+ writeErr: (text) => (0, output_1.printTextToStderr)((0, help_1.styleHelpOutput)(text).trimEnd(), output_1.TextType.Default),
17
+ outputError: (text) => (0, output_1.printText)((0, help_1.styleHelpOutput)(text).trim(), output_1.TextType.Error),
18
+ });
19
+ command.commands.forEach(configureCommandOutput);
20
+ };
12
21
  program
13
22
  .name('circleback')
14
23
  .description('Search and access meetings, emails, calendar events, and more.')
15
24
  .version(constants_1.CLIENT_VERSION)
16
25
  .option('--json', 'Output raw JSON instead of formatted text')
17
26
  .addHelpText('before', style_1.BANNER)
18
- .exitOverride()
19
- .configureOutput({
20
- writeOut: (text) => process.stdout.write((0, help_1.styleHelpOutput)(text)),
21
- writeErr: (text) => process.stderr.write((0, help_1.styleHelpOutput)(text)),
22
- });
27
+ .exitOverride();
23
28
  (0, registry_1.getAllCommands)().reduce((p, command) => p.addCommand(command), program);
29
+ configureCommandOutput(program);
24
30
  program.hook('postAction', async (_thisCommand, actionCommand) => {
25
31
  if (actionCommand.name() === 'update')
26
32
  return;
@@ -32,6 +38,6 @@ program.hook('postAction', async (_thisCommand, actionCommand) => {
32
38
  program.parseAsync().catch((error) => {
33
39
  if (error instanceof commander_1.CommanderError)
34
40
  process.exit(error.exitCode);
35
- console.error(error);
41
+ (0, output_1.printText)(error instanceof Error ? error.message : String(error), output_1.TextType.Error);
36
42
  process.exit(1);
37
43
  });
package/dist/types.js CHANGED
@@ -1,19 +1,39 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isJsonRpcResponse = exports.isOAuthTokenResponse = exports.isStoredTokens = void 0;
4
- const isStoredTokens = (value) => typeof value === 'object' &&
5
- value !== null &&
6
- 'accessToken' in value &&
7
- typeof value.accessToken === 'string' &&
8
- 'refreshToken' in value &&
9
- typeof value.refreshToken === 'string' &&
10
- 'expiresAt' in value &&
11
- typeof value.expiresAt === 'number' &&
12
- 'clientId' in value &&
13
- typeof value.clientId === 'string' &&
14
- 'scope' in value &&
15
- typeof value.scope === 'string';
16
- exports.isStoredTokens = isStoredTokens;
3
+ exports.isJsonRpcResponse = exports.isOAuthTokenResponse = exports.isStoredApiKey = exports.parseStoredCredentials = exports.CredentialSource = exports.CredentialType = void 0;
4
+ const zod_1 = require("zod");
5
+ var CredentialType;
6
+ (function (CredentialType) {
7
+ CredentialType["ApiKey"] = "apiKey";
8
+ CredentialType["OAuth"] = "oauth";
9
+ })(CredentialType || (exports.CredentialType = CredentialType = {}));
10
+ var CredentialSource;
11
+ (function (CredentialSource) {
12
+ CredentialSource["Environment"] = "environment";
13
+ CredentialSource["Stored"] = "stored";
14
+ })(CredentialSource || (exports.CredentialSource = CredentialSource = {}));
15
+ const storedTokensSchema = zod_1.z.object({
16
+ accessToken: zod_1.z.string(),
17
+ refreshToken: zod_1.z.string(),
18
+ expiresAt: zod_1.z.number(),
19
+ clientId: zod_1.z.string(),
20
+ scope: zod_1.z.string(),
21
+ });
22
+ const storedApiKeySchema = zod_1.z.object({
23
+ type: zod_1.z.literal(CredentialType.ApiKey),
24
+ apiKey: zod_1.z.string().trim().min(1),
25
+ });
26
+ const storedCredentialsSchema = zod_1.z.union([
27
+ storedApiKeySchema,
28
+ storedTokensSchema,
29
+ ]);
30
+ const parseStoredCredentials = (value) => {
31
+ const result = storedCredentialsSchema.safeParse(value);
32
+ return result.success ? result.data : undefined;
33
+ };
34
+ exports.parseStoredCredentials = parseStoredCredentials;
35
+ const isStoredApiKey = (value) => storedApiKeySchema.safeParse(value).success;
36
+ exports.isStoredApiKey = isStoredApiKey;
17
37
  const isOAuthTokenResponse = (value) => typeof value === 'object' &&
18
38
  value !== null &&
19
39
  'access_token' in value &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@circleback/cli",
3
- "version": "0.2.3",
3
+ "version": "0.3.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)",