@circleback/cli 0.3.5 → 0.4.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.
- package/README.md +8 -8
- package/dist/auth/credentials.js +47 -2
- package/dist/auth/server.js +3 -3
- package/dist/commands/auth.js +14 -14
- package/dist/commands/meetings.js +1 -1
- package/dist/commands/registry.js +2 -2
- package/dist/helpers/help.js +2 -1
- package/dist/helpers/style.js +30 -11
- package/dist/index.js +31 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Requires Node.js 18 or later. The package installs two binaries: `circleback` an
|
|
|
13
13
|
## Quick start
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
#
|
|
16
|
+
# Log in with your browser
|
|
17
17
|
cb auth login
|
|
18
18
|
|
|
19
19
|
# Search recent meetings
|
|
@@ -39,9 +39,9 @@ cb meetings --json
|
|
|
39
39
|
|
|
40
40
|
| Command | Description |
|
|
41
41
|
| ------------------------------ | ------------------------------------------------------------ |
|
|
42
|
-
| `cb auth login` |
|
|
43
|
-
| `cb auth login --api-key` |
|
|
44
|
-
| `cb auth logout` |
|
|
42
|
+
| `cb auth login` | Log in to Circleback with your browser |
|
|
43
|
+
| `cb auth login --api-key` | Log in with an API key |
|
|
44
|
+
| `cb auth logout` | Sign out on this device |
|
|
45
45
|
| `cb update` | Update CLI to the latest version |
|
|
46
46
|
| `cb meetings [search]` | Search meetings |
|
|
47
47
|
| `cb meetings read <ids...>` | Read detailed meeting info, notes, and action items (max 50) |
|
|
@@ -86,12 +86,12 @@ Pass `--json` to any command to get raw JSON instead of formatted tables. Useful
|
|
|
86
86
|
cb meetings "standup" --json | jq '.[].name'
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
##
|
|
89
|
+
## Login
|
|
90
90
|
|
|
91
91
|
```bash
|
|
92
|
-
cb auth login # Opens
|
|
93
|
-
cb auth login --api-key #
|
|
94
|
-
cb auth logout #
|
|
92
|
+
cb auth login # Opens your browser to log in
|
|
93
|
+
cb auth login --api-key # Asks for your API key securely
|
|
94
|
+
cb auth logout # Signs out on this device
|
|
95
95
|
```
|
|
96
96
|
|
|
97
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.
|
package/dist/auth/credentials.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getCredentialSnapshot = exports.getEnvironmentApiKey = void 0;
|
|
3
|
+
exports.getCredentialSnapshot = exports.getLoginStatus = exports.getEnvironmentApiKey = void 0;
|
|
4
|
+
const node_util_1 = require("node:util");
|
|
5
|
+
const zod_1 = require("zod");
|
|
4
6
|
const constants_1 = require("../constants");
|
|
5
7
|
const types_1 = require("../types");
|
|
6
8
|
const oauth_1 = require("./oauth");
|
|
@@ -10,6 +12,49 @@ const getEnvironmentApiKey = () => {
|
|
|
10
12
|
return apiKey || undefined;
|
|
11
13
|
};
|
|
12
14
|
exports.getEnvironmentApiKey = getEnvironmentApiKey;
|
|
15
|
+
const fetchLoginStatus = async (credentials) => {
|
|
16
|
+
if (credentials.type === types_1.CredentialType.OAuth &&
|
|
17
|
+
Math.floor(Date.now() / 1000) >= credentials.tokens.expiresAt) {
|
|
18
|
+
return 'Unable to check login';
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const bearerCredential = credentials.type === types_1.CredentialType.OAuth
|
|
22
|
+
? credentials.tokens.accessToken
|
|
23
|
+
: credentials.apiKey;
|
|
24
|
+
const response = await fetch(`${constants_1.BASE_URL}/api/oauth/test`, {
|
|
25
|
+
headers: { Authorization: `Bearer ${bearerCredential}` },
|
|
26
|
+
signal: AbortSignal.timeout(5_000),
|
|
27
|
+
});
|
|
28
|
+
if (response.status === 401)
|
|
29
|
+
return 'Not logged in';
|
|
30
|
+
if (!response.ok)
|
|
31
|
+
return 'Unable to check login';
|
|
32
|
+
const account = zod_1.z
|
|
33
|
+
.object({ email: zod_1.z.email(), apiKeyName: zod_1.z.string().optional() })
|
|
34
|
+
.parse(await response.json());
|
|
35
|
+
if (credentials.type === types_1.CredentialType.ApiKey) {
|
|
36
|
+
const name = (0, node_util_1.stripVTControlCharacters)(account.apiKeyName ?? '')
|
|
37
|
+
.replace(/[\p{Cc}\p{Cf}]/gu, '')
|
|
38
|
+
.trim();
|
|
39
|
+
return name
|
|
40
|
+
? `Logged in with API key “${name}”`
|
|
41
|
+
: 'Logged in with an API key';
|
|
42
|
+
}
|
|
43
|
+
return `Logged in as ${account.email}`;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return 'Unable to check login';
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const getLoginStatus = async () => {
|
|
50
|
+
try {
|
|
51
|
+
return await fetchLoginStatus(requireCredentials());
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return 'Not logged in';
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
exports.getLoginStatus = getLoginStatus;
|
|
13
58
|
const requireCredentials = () => {
|
|
14
59
|
const environmentApiKey = (0, exports.getEnvironmentApiKey)();
|
|
15
60
|
if (environmentApiKey) {
|
|
@@ -24,7 +69,7 @@ const requireCredentials = () => {
|
|
|
24
69
|
}
|
|
25
70
|
const storedCredentials = (0, tokenStorage_1.loadCredentials)();
|
|
26
71
|
if (!storedCredentials) {
|
|
27
|
-
throw new Error(`Not
|
|
72
|
+
throw new Error(`Not logged in. Run \`circleback auth login\` or set ${constants_1.API_KEY_ENVIRONMENT_VARIABLE}.`);
|
|
28
73
|
}
|
|
29
74
|
if ((0, types_1.isStoredApiKey)(storedCredentials)) {
|
|
30
75
|
return {
|
package/dist/auth/server.js
CHANGED
|
@@ -13,7 +13,7 @@ const SUCCESS_HTML = `
|
|
|
13
13
|
<head><title>Circleback CLI</title></head>
|
|
14
14
|
<body style="font-family: system-ui, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0;">
|
|
15
15
|
<div style="text-align: center;">
|
|
16
|
-
<h1>
|
|
16
|
+
<h1>Login complete</h1>
|
|
17
17
|
<p>You can close this tab and return to the terminal.</p>
|
|
18
18
|
</div>
|
|
19
19
|
</body>
|
|
@@ -44,7 +44,7 @@ const startCallbackServer = () => new Promise((resolve, reject) => {
|
|
|
44
44
|
.replace(/>/g, '>')
|
|
45
45
|
.replace(/"/g, '"')
|
|
46
46
|
.replace(/'/g, ''');
|
|
47
|
-
response.end(`<h1>
|
|
47
|
+
response.end(`<h1>Login failed: ${escapedError}</h1>`);
|
|
48
48
|
callbackReject(new Error(`OAuth error: ${error}.`));
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
@@ -72,7 +72,7 @@ const startCallbackServer = () => new Promise((resolve, reject) => {
|
|
|
72
72
|
waitForCallback: async () => {
|
|
73
73
|
const timeout = setTimeout(() => {
|
|
74
74
|
shutdown();
|
|
75
|
-
callbackReject(new Error('
|
|
75
|
+
callbackReject(new Error('Login timed out. Please try again.'));
|
|
76
76
|
}, constants_1.CALLBACK_TIMEOUT_MILLISECONDS);
|
|
77
77
|
server.once('close', () => clearTimeout(timeout));
|
|
78
78
|
const result = await callbackPromise;
|
package/dist/commands/auth.js
CHANGED
|
@@ -14,16 +14,16 @@ const constants_1 = require("../constants");
|
|
|
14
14
|
const output_1 = require("../helpers/output");
|
|
15
15
|
const types_1 = require("../types");
|
|
16
16
|
const loginCommand = new commander_1.Command('login')
|
|
17
|
-
.description('
|
|
18
|
-
.option('--api-key', '
|
|
17
|
+
.description('Log in to Circleback.')
|
|
18
|
+
.option('--api-key', 'Log in with an API key')
|
|
19
19
|
.action(async (options) => {
|
|
20
20
|
try {
|
|
21
21
|
if ((0, credentials_1.getEnvironmentApiKey)()) {
|
|
22
|
-
(0, output_1.printText)(`${constants_1.API_KEY_ENVIRONMENT_VARIABLE} is active. Unset it before
|
|
22
|
+
(0, output_1.printText)(`${constants_1.API_KEY_ENVIRONMENT_VARIABLE} is active. Unset it before logging in with another account or API key.`, output_1.TextType.Warning);
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
25
|
if ((0, tokenStorage_1.loadCredentials)()) {
|
|
26
|
-
(0, output_1.printText)('Already
|
|
26
|
+
(0, output_1.printText)('Already logged in. Run `cb auth logout` first to log in again.', output_1.TextType.Warning);
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
29
29
|
if (options.apiKey) {
|
|
@@ -33,39 +33,39 @@ const loginCommand = new commander_1.Command('login')
|
|
|
33
33
|
const apiKey = await (0, apiKey_1.promptForApiKey)();
|
|
34
34
|
await (0, apiKey_1.validateApiKey)(apiKey);
|
|
35
35
|
(0, tokenStorage_1.saveCredentials)({ type: types_1.CredentialType.ApiKey, apiKey });
|
|
36
|
-
(0, output_1.printText)('API key
|
|
36
|
+
(0, output_1.printText)('Logged in with your API key.', output_1.TextType.Success);
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
|
-
(0, output_1.printText)('Opening your browser
|
|
39
|
+
(0, output_1.printText)('Opening your browser to log in…', output_1.TextType.Default);
|
|
40
40
|
await (0, oauth_1.login)();
|
|
41
|
-
(0, output_1.printText)('
|
|
41
|
+
(0, output_1.printText)('Logged in successfully.', output_1.TextType.Success);
|
|
42
42
|
}
|
|
43
43
|
catch (error) {
|
|
44
|
-
const message = error instanceof Error ? error.message : '
|
|
44
|
+
const message = error instanceof Error ? error.message : 'Login failed.';
|
|
45
45
|
(0, output_1.printText)(message, output_1.TextType.Error);
|
|
46
46
|
process.exit(1);
|
|
47
47
|
}
|
|
48
48
|
});
|
|
49
49
|
const logoutCommand = new commander_1.Command('logout')
|
|
50
|
-
.description('
|
|
50
|
+
.description('Sign out on this device.')
|
|
51
51
|
.action(() => {
|
|
52
52
|
const environmentApiKey = (0, credentials_1.getEnvironmentApiKey)();
|
|
53
53
|
const didClearCredentials = (0, tokenStorage_1.clearCredentials)();
|
|
54
54
|
if (!didClearCredentials) {
|
|
55
55
|
if (environmentApiKey) {
|
|
56
|
-
(0, output_1.printText)(`No
|
|
56
|
+
(0, output_1.printText)(`No saved login to clear. ${constants_1.API_KEY_ENVIRONMENT_VARIABLE} is still active.`, output_1.TextType.Warning);
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
59
|
-
(0, output_1.printText)('Not
|
|
59
|
+
(0, output_1.printText)('Not logged in.', output_1.TextType.Warning);
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
if (environmentApiKey) {
|
|
63
|
-
(0, output_1.printText)(`
|
|
63
|
+
(0, output_1.printText)(`Saved login cleared. ${constants_1.API_KEY_ENVIRONMENT_VARIABLE} is still active.`, output_1.TextType.Warning);
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
|
-
(0, output_1.printText)('
|
|
66
|
+
(0, output_1.printText)('Signed out successfully.', output_1.TextType.Success);
|
|
67
67
|
});
|
|
68
68
|
exports.authCommand = new commander_1.Command('auth')
|
|
69
|
-
.description('
|
|
69
|
+
.description('Log in or sign out.')
|
|
70
70
|
.addCommand(loginCommand)
|
|
71
71
|
.addCommand(logoutCommand);
|
|
@@ -33,7 +33,7 @@ exports.searchMeetingsCommand = new commander_1.Command('search')
|
|
|
33
33
|
intent: query ?? 'Listing recent meetings.',
|
|
34
34
|
pageIndex,
|
|
35
35
|
};
|
|
36
|
-
if (query)
|
|
36
|
+
if (typeof query === 'string')
|
|
37
37
|
args['searchTerm'] = query;
|
|
38
38
|
if (options.last) {
|
|
39
39
|
args['startDate'] = (0, date_1.getStartDateFromDaysAgo)((0, parse_1.parseIntOption)(options.last, '--last'));
|
|
@@ -17,12 +17,12 @@ const standaloneCommands = [auth_1.authCommand, update_1.updateCommand];
|
|
|
17
17
|
const commandGroups = [
|
|
18
18
|
{
|
|
19
19
|
name: 'meetings',
|
|
20
|
-
description: '
|
|
20
|
+
description: 'Find meetings and read notes.',
|
|
21
21
|
subcommands: [meetings_1.searchMeetingsCommand, meetings_1.readMeetingsCommand],
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
name: 'transcripts',
|
|
25
|
-
description: '
|
|
25
|
+
description: 'Find and read meeting transcripts.',
|
|
26
26
|
subcommands: [transcripts_1.searchTranscriptsCommand, transcripts_1.getTranscriptsCommand],
|
|
27
27
|
},
|
|
28
28
|
{
|
package/dist/helpers/help.js
CHANGED
|
@@ -9,6 +9,7 @@ const SECTION_HEADERS = {
|
|
|
9
9
|
'Options:': 'options',
|
|
10
10
|
'Commands:': 'commands',
|
|
11
11
|
'Arguments:': 'arguments',
|
|
12
|
+
'Examples:': 'commands',
|
|
12
13
|
};
|
|
13
14
|
const styleLine = (line, section) => {
|
|
14
15
|
if (/^Usage:/.test(line)) {
|
|
@@ -25,7 +26,7 @@ const styleLine = (line, section) => {
|
|
|
25
26
|
const match = line.match(/^(\s+)(.+?)( +)(.*)$/);
|
|
26
27
|
if (match) {
|
|
27
28
|
return [
|
|
28
|
-
`${match[1]}${chalk_1.default.
|
|
29
|
+
`${match[1]}${chalk_1.default.cyan(match[2])}${match[3]}${chalk_1.default.dim(match[4])}`,
|
|
29
30
|
section,
|
|
30
31
|
];
|
|
31
32
|
}
|
package/dist/helpers/style.js
CHANGED
|
@@ -3,19 +3,38 @@ 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.
|
|
6
|
+
exports.formatWelcomeBanner = exports.tableStyle = exports.brand = void 0;
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const constants_1 = require("../constants");
|
|
9
9
|
exports.brand = chalk_1.default.hex(constants_1.BRAND_COLOR);
|
|
10
10
|
const secondary = chalk_1.default.hex(constants_1.SECONDARY_COLOR);
|
|
11
11
|
exports.tableStyle = { head: [], border: [] };
|
|
12
|
-
|
|
13
|
-
''
|
|
14
|
-
|
|
15
|
-
constants_1.
|
|
16
|
-
|
|
17
|
-
.
|
|
18
|
-
|
|
19
|
-
.
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
const formatWelcomeBanner = (width, loginStatus) => {
|
|
13
|
+
const logo = constants_1.ASCII_LOGO.trimEnd().split('\n').slice(1);
|
|
14
|
+
const logoWidth = Math.max(...logo.map((line) => line.length));
|
|
15
|
+
const title = `${constants_1.CLIENT_NAME} v${constants_1.CLIENT_VERSION}`;
|
|
16
|
+
const details = [
|
|
17
|
+
`${exports.brand.bold(constants_1.CLIENT_NAME)} ${chalk_1.default.dim(`v${constants_1.CLIENT_VERSION}`)}`,
|
|
18
|
+
'',
|
|
19
|
+
chalk_1.default.bold(loginStatus),
|
|
20
|
+
];
|
|
21
|
+
const isWide = width >= logoWidth + 4 + Math.max(title.length, loginStatus.length);
|
|
22
|
+
const detailsStart = Math.floor((logo.length - details.length) / 2);
|
|
23
|
+
const artwork = logo.map((line, index) => {
|
|
24
|
+
const colored = line
|
|
25
|
+
.replace(/=+/g, (match) => (0, exports.brand)(match))
|
|
26
|
+
.replace(/\++/g, (match) => secondary(match));
|
|
27
|
+
const detail = isWide ? details[index - detailsStart] : undefined;
|
|
28
|
+
return detail
|
|
29
|
+
? `${colored}${' '.repeat(logoWidth - line.length + 4)}${detail}`
|
|
30
|
+
: colored;
|
|
31
|
+
});
|
|
32
|
+
return [
|
|
33
|
+
'',
|
|
34
|
+
...artwork,
|
|
35
|
+
'',
|
|
36
|
+
...(isWide ? [] : details.map((line) => ` ${line}`)),
|
|
37
|
+
'',
|
|
38
|
+
].join('\n');
|
|
39
|
+
};
|
|
40
|
+
exports.formatWelcomeBanner = formatWelcomeBanner;
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
const commander_1 = require("commander");
|
|
5
|
+
const credentials_1 = require("./auth/credentials");
|
|
5
6
|
const registry_1 = require("./commands/registry");
|
|
6
7
|
const constants_1 = require("./constants");
|
|
7
8
|
const help_1 = require("./helpers/help");
|
|
@@ -11,19 +12,23 @@ const update_1 = require("./helpers/update");
|
|
|
11
12
|
const program = new commander_1.Command();
|
|
12
13
|
const updateCheck = (0, update_1.getUpdateCheckResult)(constants_1.CLIENT_PACKAGE_NAME);
|
|
13
14
|
const configureCommandOutput = (command) => {
|
|
15
|
+
command.helpOption('-h, --help', 'Display help for command');
|
|
16
|
+
if (command.commands.length > 0) {
|
|
17
|
+
command.addHelpCommand('help [command]', 'Display help for command');
|
|
18
|
+
}
|
|
14
19
|
command.configureOutput({
|
|
15
|
-
writeOut: (text) =>
|
|
16
|
-
writeErr: (text) =>
|
|
20
|
+
writeOut: (text) => process.stdout.write((0, help_1.styleHelpOutput)(text)),
|
|
21
|
+
writeErr: (text) => process.stderr.write((0, help_1.styleHelpOutput)(text)),
|
|
17
22
|
outputError: (text) => (0, output_1.printText)((0, help_1.styleHelpOutput)(text).trim(), output_1.TextType.Error),
|
|
18
23
|
});
|
|
19
24
|
command.commands.forEach(configureCommandOutput);
|
|
20
25
|
};
|
|
21
26
|
program
|
|
22
27
|
.name('circleback')
|
|
23
|
-
.
|
|
24
|
-
.version(constants_1.CLIENT_VERSION)
|
|
28
|
+
.version(constants_1.CLIENT_VERSION, '-V, --version', 'Output the version number')
|
|
25
29
|
.option('--json', 'Output raw JSON instead of formatted text')
|
|
26
|
-
.
|
|
30
|
+
.allowExcessArguments(false)
|
|
31
|
+
.action(() => program.help())
|
|
27
32
|
.exitOverride();
|
|
28
33
|
(0, registry_1.getAllCommands)().reduce((p, command) => p.addCommand(command), program);
|
|
29
34
|
configureCommandOutput(program);
|
|
@@ -35,7 +40,27 @@ program.hook('postAction', async (_thisCommand, actionCommand) => {
|
|
|
35
40
|
}
|
|
36
41
|
catch { }
|
|
37
42
|
});
|
|
38
|
-
|
|
43
|
+
const run = async () => {
|
|
44
|
+
const isRootHelp = process.argv
|
|
45
|
+
.slice(2)
|
|
46
|
+
.every((argument) => ['--help', '-h', '--json', 'help', '--'].includes(argument));
|
|
47
|
+
if (isRootHelp) {
|
|
48
|
+
const loginStatus = await (0, credentials_1.getLoginStatus)();
|
|
49
|
+
program
|
|
50
|
+
.addHelpText('before', () => (0, style_1.formatWelcomeBanner)(process.stdout.columns ?? 80, loginStatus))
|
|
51
|
+
.addHelpText('before', [
|
|
52
|
+
'Examples:',
|
|
53
|
+
' cb auth login Log in to Circleback',
|
|
54
|
+
' cb meetings See recent meetings',
|
|
55
|
+
' cb meetings "product review" Find a meeting',
|
|
56
|
+
' cb transcripts "pricing" Search transcript text',
|
|
57
|
+
' cb meetings --json Get JSON for scripts',
|
|
58
|
+
'',
|
|
59
|
+
].join('\n'));
|
|
60
|
+
}
|
|
61
|
+
await program.parseAsync();
|
|
62
|
+
};
|
|
63
|
+
run().catch((error) => {
|
|
39
64
|
if (error instanceof commander_1.CommanderError)
|
|
40
65
|
process.exit(error.exitCode);
|
|
41
66
|
(0, output_1.printText)(error instanceof Error ? error.message : String(error), output_1.TextType.Error);
|
package/package.json
CHANGED