@circleback/cli 0.1.0

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.
@@ -0,0 +1,162 @@
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.forceRefreshToken = exports.getValidAccessToken = exports.login = exports.refreshAccessToken = void 0;
7
+ const node_child_process_1 = require("node:child_process");
8
+ const node_crypto_1 = __importDefault(require("node:crypto"));
9
+ const constants_1 = require("../constants");
10
+ const types_1 = require("../types");
11
+ const server_1 = require("./server");
12
+ const tokenStorage_1 = require("./tokenStorage");
13
+ const generatePkce = () => {
14
+ const verifier = node_crypto_1.default.randomBytes(32).toString('base64url');
15
+ const challenge = node_crypto_1.default
16
+ .createHash('sha256')
17
+ .update(verifier)
18
+ .digest('base64url');
19
+ return { verifier, challenge };
20
+ };
21
+ const generateState = () => node_crypto_1.default.randomBytes(16).toString('base64url');
22
+ const registerClient = async (redirectUri) => {
23
+ const response = await fetch(`${constants_1.BASE_URL}/api/oauth/register`, {
24
+ method: 'POST',
25
+ headers: { 'Content-Type': 'application/json' },
26
+ body: JSON.stringify({
27
+ client_name: constants_1.CLIENT_NAME,
28
+ client_type: 'CLI',
29
+ redirect_uris: [redirectUri],
30
+ token_endpoint_auth_method: 'none',
31
+ grant_types: ['authorization_code', 'refresh_token'],
32
+ response_types: ['code'],
33
+ }),
34
+ });
35
+ if (!response.ok) {
36
+ const body = await response.text();
37
+ throw new Error(`Client registration failed: ${body}.`);
38
+ }
39
+ const data = await response.json();
40
+ if (typeof data !== 'object' ||
41
+ data === null ||
42
+ !('client_id' in data) ||
43
+ typeof data.client_id !== 'string') {
44
+ throw new Error('Invalid client registration response.');
45
+ }
46
+ return data.client_id;
47
+ };
48
+ const exchangeCode = async (params) => {
49
+ const body = new URLSearchParams({
50
+ grant_type: 'authorization_code',
51
+ code: params.code,
52
+ redirect_uri: params.redirectUri,
53
+ client_id: params.clientId,
54
+ code_verifier: params.codeVerifier,
55
+ });
56
+ const response = await fetch(`${constants_1.BASE_URL}/api/oauth/access-token`, {
57
+ method: 'POST',
58
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
59
+ body: body.toString(),
60
+ });
61
+ if (!response.ok) {
62
+ const errorBody = await response.text();
63
+ throw new Error(`Token exchange failed: ${errorBody}.`);
64
+ }
65
+ const tokenData = await response.json();
66
+ if (!(0, types_1.isOAuthTokenResponse)(tokenData)) {
67
+ throw new Error('Invalid token exchange response.');
68
+ }
69
+ return tokenData;
70
+ };
71
+ const refreshAccessToken = async (refreshToken, clientId) => {
72
+ const body = new URLSearchParams({
73
+ grant_type: 'refresh_token',
74
+ refresh_token: refreshToken,
75
+ client_id: clientId,
76
+ });
77
+ const response = await fetch(`${constants_1.BASE_URL}/api/oauth/access-token`, {
78
+ method: 'POST',
79
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
80
+ body: body.toString(),
81
+ });
82
+ if (!response.ok) {
83
+ const errorBody = await response.text();
84
+ throw new Error(`Token refresh failed: ${errorBody}.`);
85
+ }
86
+ const tokenData = await response.json();
87
+ if (!(0, types_1.isOAuthTokenResponse)(tokenData)) {
88
+ throw new Error('Invalid token refresh response.');
89
+ }
90
+ return tokenData;
91
+ };
92
+ exports.refreshAccessToken = refreshAccessToken;
93
+ const login = async () => {
94
+ const { port, waitForCallback, shutdown } = await (0, server_1.startCallbackServer)();
95
+ const redirectUri = `http://127.0.0.1:${port}`;
96
+ try {
97
+ const clientId = await registerClient(redirectUri);
98
+ const { verifier, challenge } = generatePkce();
99
+ const state = generateState();
100
+ const authorizeUrl = new URL(`${constants_1.BASE_URL}/api/oauth/authorize`);
101
+ authorizeUrl.searchParams.set('client_id', clientId);
102
+ authorizeUrl.searchParams.set('redirect_uri', redirectUri);
103
+ authorizeUrl.searchParams.set('response_type', 'code');
104
+ authorizeUrl.searchParams.set('scope', constants_1.OAUTH_SCOPE);
105
+ authorizeUrl.searchParams.set('state', state);
106
+ authorizeUrl.searchParams.set('code_challenge', challenge);
107
+ authorizeUrl.searchParams.set('code_challenge_method', 'S256');
108
+ const url = authorizeUrl.toString();
109
+ const isWindows = process.platform === 'win32';
110
+ const [command, ...arguments_] = isWindows
111
+ ? ['start', '', url]
112
+ : process.platform === 'darwin'
113
+ ? ['open', url]
114
+ : ['xdg-open', url];
115
+ (0, node_child_process_1.spawn)(command, arguments_, { stdio: 'ignore', shell: isWindows });
116
+ const callback = await waitForCallback();
117
+ if (callback.state !== state) {
118
+ throw new Error('State mismatch. Possible CSRF attack.');
119
+ }
120
+ const tokenResponse = await exchangeCode({
121
+ code: callback.code,
122
+ redirectUri,
123
+ clientId,
124
+ codeVerifier: verifier,
125
+ });
126
+ const tokens = (0, tokenStorage_1.buildStoredTokens)(tokenResponse, clientId);
127
+ (0, tokenStorage_1.saveTokens)(tokens);
128
+ return tokens;
129
+ }
130
+ catch (error) {
131
+ shutdown();
132
+ throw error;
133
+ }
134
+ };
135
+ exports.login = login;
136
+ const requireTokens = () => {
137
+ const tokens = (0, tokenStorage_1.loadTokens)();
138
+ if (!tokens) {
139
+ throw new Error('Not authenticated. Run `circleback login` to authenticate.');
140
+ }
141
+ return tokens;
142
+ };
143
+ const refreshAndSave = async (tokens) => {
144
+ const refreshed = await (0, exports.refreshAccessToken)(tokens.refreshToken, tokens.clientId);
145
+ const updatedTokens = (0, tokenStorage_1.buildStoredTokens)(refreshed, tokens.clientId);
146
+ (0, tokenStorage_1.saveTokens)(updatedTokens);
147
+ return updatedTokens.accessToken;
148
+ };
149
+ const getValidAccessToken = async () => {
150
+ const tokens = requireTokens();
151
+ const bufferSeconds = 60;
152
+ const isExpired = Math.floor(Date.now() / 1000) >= tokens.expiresAt - bufferSeconds;
153
+ if (!isExpired)
154
+ return tokens.accessToken;
155
+ return refreshAndSave(tokens);
156
+ };
157
+ exports.getValidAccessToken = getValidAccessToken;
158
+ const forceRefreshToken = async () => {
159
+ const tokens = requireTokens();
160
+ return refreshAndSave(tokens);
161
+ };
162
+ exports.forceRefreshToken = forceRefreshToken;
@@ -0,0 +1,89 @@
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.startCallbackServer = void 0;
7
+ const node_http_1 = __importDefault(require("node:http"));
8
+ const node_url_1 = require("node:url");
9
+ const constants_1 = require("../constants");
10
+ const SUCCESS_HTML = `
11
+ <!DOCTYPE html>
12
+ <html>
13
+ <head><title>Circleback CLI</title></head>
14
+ <body style="font-family: system-ui, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0;">
15
+ <div style="text-align: center;">
16
+ <h1>Authentication successful</h1>
17
+ <p>You can close this tab and return to the terminal.</p>
18
+ </div>
19
+ </body>
20
+ </html>
21
+ `;
22
+ const startCallbackServer = () => new Promise((resolve, reject) => {
23
+ let callbackResolve;
24
+ let callbackReject;
25
+ const callbackPromise = new Promise((resolveCallback, rejectCallback) => {
26
+ callbackResolve = resolveCallback;
27
+ callbackReject = rejectCallback;
28
+ });
29
+ const server = node_http_1.default.createServer((request, response) => {
30
+ if (!request.url) {
31
+ response.writeHead(400);
32
+ response.end('Bad request.');
33
+ return;
34
+ }
35
+ const url = new node_url_1.URL(request.url, `http://127.0.0.1`);
36
+ const code = url.searchParams.get('code');
37
+ const state = url.searchParams.get('state');
38
+ const error = url.searchParams.get('error');
39
+ if (error) {
40
+ response.writeHead(400, { 'Content-Type': 'text/html' });
41
+ const escapedError = error
42
+ .replace(/&/g, '&amp;')
43
+ .replace(/</g, '&lt;')
44
+ .replace(/>/g, '&gt;')
45
+ .replace(/"/g, '&quot;')
46
+ .replace(/'/g, '&#039;');
47
+ response.end(`<h1>Authentication failed: ${escapedError}</h1>`);
48
+ callbackReject(new Error(`OAuth error: ${error}.`));
49
+ return;
50
+ }
51
+ if (!code || !state) {
52
+ response.writeHead(400, { 'Content-Type': 'text/html' });
53
+ response.end('<h1>Missing authorization code.</h1>');
54
+ return;
55
+ }
56
+ response.writeHead(200, { 'Content-Type': 'text/html' });
57
+ response.end(SUCCESS_HTML);
58
+ callbackResolve({ code, state });
59
+ });
60
+ const timeout = setTimeout(() => {
61
+ shutdown();
62
+ callbackReject(new Error('Authentication timed out. Please try again.'));
63
+ }, constants_1.CALLBACK_TIMEOUT_MILLISECONDS);
64
+ let isShutdown = false;
65
+ const shutdown = () => {
66
+ if (isShutdown)
67
+ return;
68
+ isShutdown = true;
69
+ clearTimeout(timeout);
70
+ server.close();
71
+ };
72
+ server.listen(0, '127.0.0.1', () => {
73
+ const address = server.address();
74
+ if (!address || typeof address === 'string') {
75
+ reject(new Error('Failed to start callback server.'));
76
+ return;
77
+ }
78
+ resolve({
79
+ port: address.port,
80
+ waitForCallback: async () => {
81
+ const result = await callbackPromise;
82
+ shutdown();
83
+ return result;
84
+ },
85
+ shutdown,
86
+ });
87
+ });
88
+ });
89
+ exports.startCallbackServer = startCallbackServer;
@@ -0,0 +1,51 @@
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.clearTokens = exports.saveTokens = exports.loadTokens = exports.buildStoredTokens = void 0;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const constants_1 = require("../constants");
9
+ const types_1 = require("../types");
10
+ const DIRECTORY_PERMISSIONS_MODE = 0o700;
11
+ const FILE_PERMISSIONS_MODE = 0o600;
12
+ const buildStoredTokens = (response, clientId) => ({
13
+ accessToken: response.access_token,
14
+ refreshToken: response.refresh_token,
15
+ expiresAt: Math.floor(Date.now() / 1000) + response.expires_in,
16
+ clientId,
17
+ scope: response.scope,
18
+ });
19
+ exports.buildStoredTokens = buildStoredTokens;
20
+ const loadTokens = () => {
21
+ try {
22
+ const content = node_fs_1.default.readFileSync(constants_1.TOKENS_FILE, 'utf-8');
23
+ const parsed = JSON.parse(content);
24
+ if (!(0, types_1.isStoredTokens)(parsed))
25
+ return;
26
+ return parsed;
27
+ }
28
+ catch {
29
+ return;
30
+ }
31
+ };
32
+ exports.loadTokens = loadTokens;
33
+ const saveTokens = (tokens) => {
34
+ node_fs_1.default.mkdirSync(constants_1.CONFIG_DIRECTORY, {
35
+ recursive: true,
36
+ mode: DIRECTORY_PERMISSIONS_MODE,
37
+ });
38
+ node_fs_1.default.writeFileSync(constants_1.TOKENS_FILE, JSON.stringify(tokens, null, 2), {
39
+ mode: FILE_PERMISSIONS_MODE,
40
+ });
41
+ };
42
+ exports.saveTokens = saveTokens;
43
+ const clearTokens = () => {
44
+ try {
45
+ node_fs_1.default.unlinkSync(constants_1.TOKENS_FILE);
46
+ }
47
+ catch {
48
+ // File may not exist.
49
+ }
50
+ };
51
+ exports.clearTokens = clearTokens;
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.callTool = void 0;
4
+ const constants_1 = require("../constants");
5
+ const types_1 = require("../types");
6
+ const oauth_1 = require("../auth/oauth");
7
+ const TOOLS_CALL_METHOD = 'tools/call';
8
+ let requestId = 0;
9
+ const nextId = () => ++requestId;
10
+ const extractErrorMessage = async (response) => {
11
+ const text = await response.text();
12
+ try {
13
+ 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;
21
+ }
22
+ catch {
23
+ return text;
24
+ }
25
+ };
26
+ const HTTP_ERROR_MESSAGES = {
27
+ 401: 'Authentication expired. Run `circleback login` to re-authenticate.',
28
+ 403: 'Permission denied.',
29
+ 404: 'Resource not found.',
30
+ 429: 'Rate limit exceeded. Please try again later.',
31
+ };
32
+ const sendRequest = async (method, params, accessToken) => fetch(`${constants_1.BASE_URL}/api/mcp`, {
33
+ method: 'POST',
34
+ headers: {
35
+ 'Content-Type': 'application/json',
36
+ Accept: 'application/json, text/event-stream',
37
+ Authorization: `Bearer ${accessToken}`,
38
+ },
39
+ body: JSON.stringify({
40
+ jsonrpc: '2.0',
41
+ id: nextId(),
42
+ method,
43
+ params,
44
+ }),
45
+ });
46
+ const parseResponse = async (response) => {
47
+ const contentType = response.headers.get('content-type') ?? '';
48
+ if (contentType.includes('text/event-stream')) {
49
+ return parseSSEResponse(response);
50
+ }
51
+ const body = await response.json();
52
+ if (!(0, types_1.isJsonRpcResponse)(body)) {
53
+ throw new Error('Invalid JSON-RPC response.');
54
+ }
55
+ if (body.error) {
56
+ throw new Error(body.error.message);
57
+ }
58
+ const textContent = body.result?.content?.find((item) => item.type === 'text');
59
+ if (!textContent) {
60
+ throw new Error('No text content in response.');
61
+ }
62
+ return textContent.text;
63
+ };
64
+ const parseSSEResponse = async (response) => {
65
+ const text = await response.text();
66
+ const results = text
67
+ .split('\n')
68
+ .filter((line) => line.startsWith('data: '))
69
+ .flatMap((line) => {
70
+ const data = line.slice(6);
71
+ try {
72
+ const parsed = JSON.parse(data);
73
+ if (!(0, types_1.isJsonRpcResponse)(parsed))
74
+ return [];
75
+ if (parsed.error) {
76
+ throw new Error(parsed.error.message);
77
+ }
78
+ const textContent = parsed.result?.content?.find((item) => item.type === 'text');
79
+ return textContent ? [textContent.text] : [];
80
+ }
81
+ catch (error) {
82
+ if (error instanceof SyntaxError)
83
+ return [];
84
+ throw error;
85
+ }
86
+ });
87
+ if (results.length === 0) {
88
+ throw new Error('No text content in SSE response.');
89
+ }
90
+ return results.join('');
91
+ };
92
+ const sendRequestWithNetworkError = async (method, params, accessToken) => {
93
+ try {
94
+ return await sendRequest(method, params, accessToken);
95
+ }
96
+ catch (error) {
97
+ if (error instanceof TypeError && error.message.includes('fetch failed')) {
98
+ throw new Error('Network error. Check your internet connection and try again.');
99
+ }
100
+ throw error;
101
+ }
102
+ };
103
+ const retryWithRefresh = async (method, params) => {
104
+ const accessToken = await (0, oauth_1.forceRefreshToken)();
105
+ const response = await sendRequestWithNetworkError(method, params, accessToken);
106
+ if (!response.ok) {
107
+ const message = await extractErrorMessage(response);
108
+ throw new Error(message);
109
+ }
110
+ return parseResponse(response);
111
+ };
112
+ const callTool = async (toolName, toolArguments) => {
113
+ const accessToken = await (0, oauth_1.getValidAccessToken)();
114
+ const params = { name: toolName, arguments: toolArguments };
115
+ const response = await sendRequestWithNetworkError(TOOLS_CALL_METHOD, params, accessToken);
116
+ if (response.status === 401) {
117
+ return retryWithRefresh(TOOLS_CALL_METHOD, params);
118
+ }
119
+ if (!response.ok) {
120
+ const fallback = HTTP_ERROR_MESSAGES[response.status];
121
+ const serverMessage = await extractErrorMessage(response);
122
+ throw new Error(serverMessage ||
123
+ fallback ||
124
+ `Request failed with status ${response.status}.`);
125
+ }
126
+ return parseResponse(response);
127
+ };
128
+ exports.callTool = callTool;
@@ -0,0 +1,40 @@
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.logoutCommand = exports.loginCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const oauth_1 = require("../auth/oauth");
10
+ const tokenStorage_1 = require("../auth/tokenStorage");
11
+ exports.loginCommand = new commander_1.Command('login')
12
+ .description('Authenticate with Circleback via browser.')
13
+ .action(async () => {
14
+ try {
15
+ const existing = (0, tokenStorage_1.loadTokens)();
16
+ if (existing) {
17
+ console.log(chalk_1.default.yellow('Already authenticated. Run `circleback logout` first to re-authenticate.'));
18
+ return;
19
+ }
20
+ console.log('Opening browser for authentication…');
21
+ await (0, oauth_1.login)();
22
+ console.log(chalk_1.default.green('Authentication successful.'));
23
+ }
24
+ catch (error) {
25
+ const message = error instanceof Error ? error.message : 'Authentication failed.';
26
+ console.error(chalk_1.default.red(message));
27
+ process.exit(1);
28
+ }
29
+ });
30
+ exports.logoutCommand = new commander_1.Command('logout')
31
+ .description('Clear stored authentication tokens.')
32
+ .action(() => {
33
+ const existing = (0, tokenStorage_1.loadTokens)();
34
+ if (!existing) {
35
+ console.log(chalk_1.default.yellow('Not currently authenticated.'));
36
+ return;
37
+ }
38
+ (0, tokenStorage_1.clearTokens)();
39
+ console.log(chalk_1.default.green('Logged out successfully.'));
40
+ });
@@ -0,0 +1,46 @@
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.searchCalendarCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const jsonRpc_1 = require("../client/jsonRpc");
10
+ const date_1 = require("../helpers/date");
11
+ const error_1 = require("../helpers/error");
12
+ const formatter_1 = require("../helpers/formatter");
13
+ const parse_1 = require("../helpers/parse");
14
+ exports.searchCalendarCommand = new commander_1.Command('search')
15
+ .description('Search calendar events.')
16
+ .option('--last <days>', 'Show events from the last N days')
17
+ .option('--from <date>', 'Start date (YYYY-MM-DD)')
18
+ .option('--to <date>', 'End date (YYYY-MM-DD)')
19
+ .action(async (options) => {
20
+ try {
21
+ if (options.last && options.from) {
22
+ console.error(chalk_1.default.red('Cannot use --last and --from together.'));
23
+ process.exit(1);
24
+ }
25
+ const jsonMode = exports.searchCalendarCommand.parent?.parent?.opts().json ?? false;
26
+ const arguments_ = {
27
+ intent: 'Listing calendar events.',
28
+ pageIndex: 0,
29
+ };
30
+ if (options.last) {
31
+ arguments_['startDate'] = (0, date_1.getStartDateFromDaysAgo)((0, parse_1.parseIntOption)(options.last, '--last'));
32
+ }
33
+ else if (options.from) {
34
+ arguments_['startDate'] = options.from;
35
+ }
36
+ if (options.to)
37
+ arguments_['endDate'] = options.to;
38
+ const result = await (0, jsonRpc_1.callTool)('SearchCalendarEvents', arguments_);
39
+ (0, formatter_1.formatOutput)('SearchCalendarEvents', result, jsonMode);
40
+ }
41
+ catch (error) {
42
+ const message = error instanceof Error ? error.message : 'Calendar search failed.';
43
+ console.error(chalk_1.default.red((0, error_1.formatMcpError)(message) ?? message));
44
+ process.exit(1);
45
+ }
46
+ });
@@ -0,0 +1,28 @@
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.searchDomainsCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const jsonRpc_1 = require("../client/jsonRpc");
10
+ const error_1 = require("../helpers/error");
11
+ const formatter_1 = require("../helpers/formatter");
12
+ exports.searchDomainsCommand = new commander_1.Command('search')
13
+ .description('Search companies.')
14
+ .argument('<terms...>', 'Search terms for domains')
15
+ .action(async (terms) => {
16
+ try {
17
+ const jsonMode = exports.searchDomainsCommand.parent?.parent?.opts().json ?? false;
18
+ const result = await (0, jsonRpc_1.callTool)('FindDomains', {
19
+ searchTerms: terms,
20
+ });
21
+ (0, formatter_1.formatOutput)('FindDomains', result, jsonMode);
22
+ }
23
+ catch (error) {
24
+ const message = error instanceof Error ? error.message : 'Company search failed.';
25
+ console.error(chalk_1.default.red((0, error_1.formatMcpError)(message) ?? message));
26
+ process.exit(1);
27
+ }
28
+ });
@@ -0,0 +1,38 @@
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.searchEmailsCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const jsonRpc_1 = require("../client/jsonRpc");
10
+ const error_1 = require("../helpers/error");
11
+ const formatter_1 = require("../helpers/formatter");
12
+ const parse_1 = require("../helpers/parse");
13
+ exports.searchEmailsCommand = new commander_1.Command('search')
14
+ .description('Search connected email accounts. Supports filters: from:email, to:email, participant:email, before:YYYY-MM-DD, after:YYYY-MM-DD.')
15
+ .argument('[search]', 'Search term')
16
+ .option('-p, --page <index>', 'Page index (starts at 0)', '0')
17
+ .action(async (query, options) => {
18
+ try {
19
+ if (query !== undefined && query.trim() === '') {
20
+ console.error(chalk_1.default.red('Search term cannot be empty.'));
21
+ process.exit(1);
22
+ }
23
+ const jsonMode = exports.searchEmailsCommand.parent?.parent?.opts().json ?? false;
24
+ const pageIndex = (0, parse_1.parseIntOption)(options.page, '--page');
25
+ const arguments_ = {
26
+ intent: query ?? 'Listing recent emails.',
27
+ pageIndex,
28
+ };
29
+ arguments_['searchTerm'] = query ?? '';
30
+ const result = await (0, jsonRpc_1.callTool)('SearchEmails', arguments_);
31
+ (0, formatter_1.formatOutput)('SearchEmails', result, jsonMode);
32
+ }
33
+ catch (error) {
34
+ const message = error instanceof Error ? error.message : 'Email search failed.';
35
+ console.error(chalk_1.default.red((0, error_1.formatMcpError)(message) ?? message));
36
+ process.exit(1);
37
+ }
38
+ });
@@ -0,0 +1,83 @@
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.readMeetingsCommand = exports.searchMeetingsCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const jsonRpc_1 = require("../client/jsonRpc");
10
+ const date_1 = require("../helpers/date");
11
+ const error_1 = require("../helpers/error");
12
+ const formatter_1 = require("../helpers/formatter");
13
+ const parse_1 = require("../helpers/parse");
14
+ const DEFAULT_MEETINGS_SEARCH_LAST_DAYS = 7;
15
+ exports.searchMeetingsCommand = new commander_1.Command('search')
16
+ .description('Search for meetings.')
17
+ .argument('[search]', 'Search term')
18
+ .option('-p, --page <index>', 'Page index (starts at 0)', '0')
19
+ .option('--last <days>', 'Show meetings from the last N days')
20
+ .option('--from <date>', 'Start date (YYYY-MM-DD)')
21
+ .option('--to <date>', 'End date (YYYY-MM-DD)')
22
+ .option('--tags <ids>', 'Comma-separated tag IDs')
23
+ .option('--profiles <ids>', 'Comma-separated profile IDs')
24
+ .option('--domains <domains>', 'Comma-separated domains')
25
+ .action(async (query, options) => {
26
+ try {
27
+ if (options.last && options.from) {
28
+ console.error(chalk_1.default.red('Cannot use --last and --from together.'));
29
+ process.exit(1);
30
+ }
31
+ const jsonMode = exports.searchMeetingsCommand.parent?.parent?.opts().json ?? false;
32
+ const pageIndex = (0, parse_1.parseIntOption)(options.page, '--page');
33
+ const arguments_ = {
34
+ intent: query ?? 'Listing recent meetings.',
35
+ pageIndex,
36
+ };
37
+ if (query)
38
+ arguments_['searchTerm'] = query;
39
+ if (options.last) {
40
+ arguments_['startDate'] = (0, date_1.getStartDateFromDaysAgo)((0, parse_1.parseIntOption)(options.last, '--last'));
41
+ }
42
+ else if (options.from) {
43
+ arguments_['startDate'] = options.from;
44
+ }
45
+ else if (!query) {
46
+ arguments_['startDate'] = (0, date_1.getStartDateFromDaysAgo)(DEFAULT_MEETINGS_SEARCH_LAST_DAYS);
47
+ }
48
+ if (options.to)
49
+ arguments_['endDate'] = options.to;
50
+ if (options.tags)
51
+ arguments_['tags'] = (0, parse_1.parseNumericIds)(options.tags, 'tag ID');
52
+ if (options.profiles)
53
+ arguments_['profiles'] = (0, parse_1.parseNumericIds)(options.profiles, 'profile ID');
54
+ if (options.domains)
55
+ arguments_['domains'] = options.domains.split(',');
56
+ const result = await (0, jsonRpc_1.callTool)('SearchMeetings', arguments_);
57
+ (0, formatter_1.formatOutput)('SearchMeetings', result, jsonMode);
58
+ }
59
+ catch (error) {
60
+ const message = error instanceof Error ? error.message : 'Search failed.';
61
+ console.error(chalk_1.default.red((0, error_1.formatMcpError)(message) ?? message));
62
+ process.exit(1);
63
+ }
64
+ });
65
+ exports.readMeetingsCommand = new commander_1.Command('read')
66
+ .description('Get detailed information for meetings.')
67
+ .argument('<ids…>', 'Meeting IDs')
68
+ .action(async (ids) => {
69
+ try {
70
+ const jsonMode = exports.readMeetingsCommand.parent?.parent?.opts().json ?? false;
71
+ const meetingIds = (0, parse_1.validateNumericIds)(ids, 'meeting ID');
72
+ const result = await (0, jsonRpc_1.callTool)('ReadMeetings', {
73
+ intent: `Reading meeting details for IDs: ${meetingIds.join(', ')}.`,
74
+ meetingIds,
75
+ });
76
+ (0, formatter_1.formatOutput)('ReadMeetings', result, jsonMode);
77
+ }
78
+ catch (error) {
79
+ const message = error instanceof Error ? error.message : 'Read failed.';
80
+ console.error(chalk_1.default.red((0, error_1.formatMcpError)(message) ?? message));
81
+ process.exit(1);
82
+ }
83
+ });