@respan/cli 0.3.0 → 0.3.2
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/dist/commands/auth/login.d.ts +1 -1
- package/dist/commands/auth/login.js +24 -14
- package/dist/commands/auth/status.js +1 -1
- package/dist/commands/logs/list.d.ts +3 -0
- package/dist/commands/logs/list.js +30 -22
- package/dist/commands/logs/summary.d.ts +3 -0
- package/dist/commands/logs/summary.js +24 -2
- package/dist/commands/traces/list.js +22 -12
- package/dist/lib/auth.d.ts +2 -0
- package/dist/lib/auth.js +2 -1
- package/dist/lib/banner.d.ts +1 -1
- package/dist/lib/banner.js +7 -2
- package/dist/lib/filters.d.ts +24 -0
- package/dist/lib/filters.js +100 -0
- package/oclif.manifest.json +234 -193
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ export default class AuthLogin extends BaseCommand {
|
|
|
4
4
|
static flags: {
|
|
5
5
|
'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
6
|
'profile-name': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
-
'base-url': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
8
|
enterprise: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
9
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
10
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
@@ -3,12 +3,14 @@ import { select, password } from '@inquirer/prompts';
|
|
|
3
3
|
import * as http from 'node:http';
|
|
4
4
|
import * as open from 'node:child_process';
|
|
5
5
|
import { BaseCommand } from '../../lib/base-command.js';
|
|
6
|
-
import { setCredential } from '../../lib/config.js';
|
|
7
|
-
import { printLoginSuccess } from '../../lib/banner.js';
|
|
6
|
+
import { setCredential, setActiveProfile } from '../../lib/config.js';
|
|
7
|
+
import { printBanner, printLoginSuccess } from '../../lib/banner.js';
|
|
8
|
+
import { DEFAULT_BASE_URL, ENTERPRISE_BASE_URL } from '../../lib/auth.js';
|
|
8
9
|
const CALLBACK_PORT = 18392;
|
|
9
10
|
const CALLBACK_PATH = '/callback';
|
|
10
11
|
const LOGIN_TIMEOUT_MS = 120_000;
|
|
11
12
|
const LOGIN_URL_BASE = 'https://platform.respan.ai/login';
|
|
13
|
+
const ENTERPRISE_LOGIN_URL_BASE = 'https://enterprise.respan.ai/login';
|
|
12
14
|
function openBrowser(url) {
|
|
13
15
|
const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
14
16
|
open.exec(`${cmd} "${url}"`);
|
|
@@ -71,12 +73,10 @@ function waitForBrowserLogin(enterprise) {
|
|
|
71
73
|
});
|
|
72
74
|
server.listen(CALLBACK_PORT, '127.0.0.1', () => {
|
|
73
75
|
const redirectUri = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
|
|
74
|
-
const
|
|
76
|
+
const loginUrlBase = enterprise ? ENTERPRISE_LOGIN_URL_BASE : LOGIN_URL_BASE;
|
|
77
|
+
const loginUrl = new URL(loginUrlBase);
|
|
75
78
|
loginUrl.searchParams.set('mode', 'cli');
|
|
76
79
|
loginUrl.searchParams.set('redirect_uri', redirectUri);
|
|
77
|
-
if (enterprise) {
|
|
78
|
-
loginUrl.searchParams.set('enterprise', 'true');
|
|
79
|
-
}
|
|
80
80
|
console.log('');
|
|
81
81
|
console.log(' Opening browser to log in...');
|
|
82
82
|
console.log(` If the browser doesn't open, visit:`);
|
|
@@ -92,34 +92,44 @@ class AuthLogin extends BaseCommand {
|
|
|
92
92
|
const { flags } = await this.parse(AuthLogin);
|
|
93
93
|
this.globalFlags = flags;
|
|
94
94
|
const profile = flags['profile-name'] || 'default';
|
|
95
|
-
|
|
95
|
+
setActiveProfile(profile);
|
|
96
|
+
await printBanner();
|
|
97
|
+
// Step 1: Determine environment (skip if --enterprise flag or --api-key with flag)
|
|
98
|
+
const enterprise = flags.enterprise || (!flags['api-key'] && await select({
|
|
99
|
+
message: 'Select your environment:',
|
|
100
|
+
choices: [
|
|
101
|
+
{ name: 'Respan Platform', value: false },
|
|
102
|
+
{ name: 'Enterprise', value: true },
|
|
103
|
+
],
|
|
104
|
+
}));
|
|
105
|
+
const baseUrl = flags['base-url'] || (enterprise ? ENTERPRISE_BASE_URL : DEFAULT_BASE_URL);
|
|
106
|
+
// If --api-key passed directly, skip auth method prompt
|
|
96
107
|
if (flags['api-key']) {
|
|
97
|
-
setCredential(profile, { type: 'api_key', apiKey: flags['api-key'], baseUrl
|
|
108
|
+
setCredential(profile, { type: 'api_key', apiKey: flags['api-key'], baseUrl });
|
|
98
109
|
await printLoginSuccess(undefined, profile);
|
|
99
110
|
return;
|
|
100
111
|
}
|
|
112
|
+
// Step 2: Choose auth method
|
|
101
113
|
const method = await select({
|
|
102
|
-
message: 'How would you like to
|
|
114
|
+
message: 'How would you like to authenticate?',
|
|
103
115
|
choices: [
|
|
104
116
|
{ name: 'Browser login (recommended)', value: 'browser' },
|
|
105
|
-
{ name: 'Enterprise SSO', value: 'enterprise' },
|
|
106
117
|
{ name: 'API key', value: 'api_key' },
|
|
107
118
|
],
|
|
108
119
|
});
|
|
109
120
|
if (method === 'api_key') {
|
|
110
121
|
const apiKey = await password({ message: 'Enter your Respan API key:' });
|
|
111
|
-
setCredential(profile, { type: 'api_key', apiKey, baseUrl
|
|
122
|
+
setCredential(profile, { type: 'api_key', apiKey, baseUrl });
|
|
112
123
|
await printLoginSuccess(undefined, profile);
|
|
113
124
|
return;
|
|
114
125
|
}
|
|
115
|
-
const enterprise = method === 'enterprise' || flags.enterprise;
|
|
116
126
|
const result = await waitForBrowserLogin(enterprise);
|
|
117
127
|
setCredential(profile, {
|
|
118
128
|
type: 'jwt',
|
|
119
129
|
accessToken: result.token,
|
|
120
130
|
refreshToken: result.refreshToken,
|
|
121
131
|
email: result.email || '',
|
|
122
|
-
baseUrl
|
|
132
|
+
baseUrl,
|
|
123
133
|
});
|
|
124
134
|
await printLoginSuccess(result.email, profile);
|
|
125
135
|
}
|
|
@@ -129,7 +139,7 @@ AuthLogin.flags = {
|
|
|
129
139
|
...BaseCommand.baseFlags,
|
|
130
140
|
'api-key': Flags.string({ description: 'API key to store (skips interactive prompt)' }),
|
|
131
141
|
'profile-name': Flags.string({ description: 'Profile name', default: 'default' }),
|
|
132
|
-
'base-url': Flags.string({ description: 'API base URL
|
|
142
|
+
'base-url': Flags.string({ description: 'API base URL (auto-detected from login method if not set)' }),
|
|
133
143
|
enterprise: Flags.boolean({ description: 'Use enterprise SSO login', default: false }),
|
|
134
144
|
};
|
|
135
145
|
export default AuthLogin;
|
|
@@ -4,7 +4,7 @@ class AuthStatus extends BaseCommand {
|
|
|
4
4
|
async run() {
|
|
5
5
|
const { flags } = await this.parse(AuthStatus);
|
|
6
6
|
this.globalFlags = flags;
|
|
7
|
-
const profile = getActiveProfile();
|
|
7
|
+
const profile = flags.profile || getActiveProfile();
|
|
8
8
|
const cred = getCredential(profile);
|
|
9
9
|
if (!cred) {
|
|
10
10
|
this.log('Not authenticated. Run `respan auth login`.');
|
|
@@ -8,6 +8,9 @@ export default class LogsList extends BaseCommand {
|
|
|
8
8
|
'start-time': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
9
|
'end-time': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
10
|
filter: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
'all-envs': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
'is-test': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
'include-fields': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
14
|
'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
15
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
16
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import { BaseCommand } from '../../lib/base-command.js';
|
|
3
|
+
import { parseFilters, FILTER_SYNTAX_HELP, LOG_FIELDS_HELP, FILTER_EXAMPLES } from '../../lib/filters.js';
|
|
3
4
|
import { extractPagination, formatPaginationInfo } from '../../lib/pagination.js';
|
|
4
5
|
class LogsList extends BaseCommand {
|
|
5
6
|
async run() {
|
|
@@ -7,27 +8,23 @@ class LogsList extends BaseCommand {
|
|
|
7
8
|
this.globalFlags = flags;
|
|
8
9
|
try {
|
|
9
10
|
const client = this.getClient();
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
if (flags['sort-by'])
|
|
15
|
-
params.sort_by = flags['sort-by'];
|
|
16
|
-
if (flags['start-time'])
|
|
17
|
-
params.start_time = flags['start-time'];
|
|
18
|
-
if (flags['end-time'])
|
|
19
|
-
params.end_time = flags['end-time'];
|
|
20
|
-
if (flags.filter && flags.filter.length > 0)
|
|
21
|
-
params.filters = flags.filter;
|
|
22
|
-
// listSpans requires start_time, end_time, sort_by, operator
|
|
11
|
+
let filters;
|
|
12
|
+
if (flags.filter && flags.filter.length > 0) {
|
|
13
|
+
filters = parseFilters(flags.filter);
|
|
14
|
+
}
|
|
23
15
|
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
|
24
16
|
const data = await this.spin('Fetching logs', () => client.logs.listSpans({
|
|
25
|
-
start_time:
|
|
26
|
-
end_time:
|
|
27
|
-
sort_by:
|
|
17
|
+
start_time: flags['start-time'] || oneHourAgo,
|
|
18
|
+
end_time: flags['end-time'] || new Date().toISOString(),
|
|
19
|
+
sort_by: flags['sort-by'] || '-id',
|
|
28
20
|
operator: '',
|
|
29
|
-
page_size:
|
|
30
|
-
page:
|
|
21
|
+
page_size: flags.limit,
|
|
22
|
+
page: flags.page,
|
|
23
|
+
is_test: flags['is-test'],
|
|
24
|
+
all_envs: flags['all-envs'],
|
|
25
|
+
fetch_filters: 'false',
|
|
26
|
+
include_fields: flags['include-fields'],
|
|
27
|
+
filters,
|
|
31
28
|
}));
|
|
32
29
|
this.outputResult(data, ['id', 'model', 'prompt_tokens', 'completion_tokens', 'cost', 'latency', 'timestamp']);
|
|
33
30
|
const pagination = extractPagination(data, flags.page);
|
|
@@ -38,14 +35,25 @@ class LogsList extends BaseCommand {
|
|
|
38
35
|
}
|
|
39
36
|
}
|
|
40
37
|
}
|
|
41
|
-
LogsList.description =
|
|
38
|
+
LogsList.description = `List and filter LLM request logs (spans).
|
|
39
|
+
|
|
40
|
+
Supports pagination, sorting, time range, and server-side filtering.
|
|
41
|
+
|
|
42
|
+
${FILTER_SYNTAX_HELP}
|
|
43
|
+
|
|
44
|
+
${LOG_FIELDS_HELP}
|
|
45
|
+
|
|
46
|
+
${FILTER_EXAMPLES}`;
|
|
42
47
|
LogsList.flags = {
|
|
43
48
|
...BaseCommand.baseFlags,
|
|
44
|
-
limit: Flags.integer({ description: 'Number of results per page', default: 50 }),
|
|
49
|
+
limit: Flags.integer({ description: 'Number of results per page (max 1000)', default: 50 }),
|
|
45
50
|
page: Flags.integer({ description: 'Page number', default: 1 }),
|
|
46
|
-
'sort-by': Flags.string({ description: 'Sort field' }),
|
|
51
|
+
'sort-by': Flags.string({ description: 'Sort field (prefix with - for descending, e.g. -cost, -latency)' }),
|
|
47
52
|
'start-time': Flags.string({ description: 'Start time filter (ISO 8601)' }),
|
|
48
53
|
'end-time': Flags.string({ description: 'End time filter (ISO 8601)' }),
|
|
49
|
-
filter: Flags.string({ description: 'Filter
|
|
54
|
+
filter: Flags.string({ description: 'Filter in field:operator:value format (repeatable)', multiple: true }),
|
|
55
|
+
'all-envs': Flags.string({ description: 'Include all environments (true/false)' }),
|
|
56
|
+
'is-test': Flags.string({ description: 'Filter by test (true) or production (false) environment' }),
|
|
57
|
+
'include-fields': Flags.string({ description: 'Comma-separated fields to include in response' }),
|
|
50
58
|
};
|
|
51
59
|
export default LogsList;
|
|
@@ -4,6 +4,9 @@ export default class LogsSummary extends BaseCommand {
|
|
|
4
4
|
static flags: {
|
|
5
5
|
'start-time': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
6
|
'end-time': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
filter: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
'all-envs': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
'is-test': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
10
|
'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
11
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
12
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import { BaseCommand } from '../../lib/base-command.js';
|
|
3
|
+
import { parseFilters, FILTER_SYNTAX_HELP, LOG_FIELDS_HELP, FILTER_EXAMPLES } from '../../lib/filters.js';
|
|
3
4
|
class LogsSummary extends BaseCommand {
|
|
4
5
|
async run() {
|
|
5
6
|
const { flags } = await this.parse(LogsSummary);
|
|
6
7
|
this.globalFlags = flags;
|
|
7
8
|
try {
|
|
8
9
|
const client = this.getClient();
|
|
9
|
-
|
|
10
|
+
let filters;
|
|
11
|
+
if (flags.filter && flags.filter.length > 0) {
|
|
12
|
+
filters = parseFilters(flags.filter);
|
|
13
|
+
}
|
|
14
|
+
const data = await this.spin('Fetching summary', () => client.logs.getSpansSummary({
|
|
15
|
+
start_time: flags['start-time'],
|
|
16
|
+
end_time: flags['end-time'],
|
|
17
|
+
is_test: flags['is-test'],
|
|
18
|
+
all_envs: flags['all-envs'],
|
|
19
|
+
filters,
|
|
20
|
+
}));
|
|
10
21
|
this.log(JSON.stringify(data, null, 2));
|
|
11
22
|
}
|
|
12
23
|
catch (error) {
|
|
@@ -14,10 +25,21 @@ class LogsSummary extends BaseCommand {
|
|
|
14
25
|
}
|
|
15
26
|
}
|
|
16
27
|
}
|
|
17
|
-
LogsSummary.description =
|
|
28
|
+
LogsSummary.description = `Get aggregated summary statistics for log spans in a time range.
|
|
29
|
+
|
|
30
|
+
Returns total cost, total tokens, request count, and score summaries.
|
|
31
|
+
|
|
32
|
+
${FILTER_SYNTAX_HELP}
|
|
33
|
+
|
|
34
|
+
${LOG_FIELDS_HELP}
|
|
35
|
+
|
|
36
|
+
${FILTER_EXAMPLES}`;
|
|
18
37
|
LogsSummary.flags = {
|
|
19
38
|
...BaseCommand.baseFlags,
|
|
20
39
|
'start-time': Flags.string({ description: 'Start time (ISO 8601)', required: true }),
|
|
21
40
|
'end-time': Flags.string({ description: 'End time (ISO 8601)', required: true }),
|
|
41
|
+
filter: Flags.string({ description: 'Filter in field:operator:value format (repeatable)', multiple: true }),
|
|
42
|
+
'all-envs': Flags.string({ description: 'Include all environments (true/false)' }),
|
|
43
|
+
'is-test': Flags.string({ description: 'Filter by test (true) or production (false) environment' }),
|
|
22
44
|
};
|
|
23
45
|
export default LogsSummary;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import { BaseCommand } from '../../lib/base-command.js';
|
|
3
|
+
import { parseFilters, FILTER_SYNTAX_HELP, TRACE_FIELDS_HELP, FILTER_EXAMPLES } from '../../lib/filters.js';
|
|
3
4
|
import { extractPagination, formatPaginationInfo } from '../../lib/pagination.js';
|
|
4
5
|
class TracesList extends BaseCommand {
|
|
5
6
|
async run() {
|
|
@@ -7,21 +8,22 @@ class TracesList extends BaseCommand {
|
|
|
7
8
|
this.globalFlags = flags;
|
|
8
9
|
try {
|
|
9
10
|
const client = this.getClient();
|
|
10
|
-
|
|
11
|
+
let bodyFilters = {};
|
|
12
|
+
if (flags.filter && flags.filter.length > 0) {
|
|
13
|
+
bodyFilters = parseFilters(flags.filter);
|
|
14
|
+
}
|
|
15
|
+
const queryParams = {
|
|
11
16
|
page_size: flags.limit,
|
|
12
17
|
page: flags.page,
|
|
18
|
+
sort_by: flags['sort-by'],
|
|
13
19
|
};
|
|
14
|
-
if (flags['sort-by'])
|
|
15
|
-
params.sort_by = flags['sort-by'];
|
|
16
20
|
if (flags['start-time'])
|
|
17
|
-
|
|
21
|
+
queryParams.start_time = flags['start-time'];
|
|
18
22
|
if (flags['end-time'])
|
|
19
|
-
|
|
23
|
+
queryParams.end_time = flags['end-time'];
|
|
20
24
|
if (flags.environment)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
params.filters = flags.filter;
|
|
24
|
-
const data = await this.spin('Fetching traces', () => client.traces.list(params));
|
|
25
|
+
queryParams.environment = flags.environment;
|
|
26
|
+
const data = await this.spin('Fetching traces', () => client.traces.list({ filters: bodyFilters }, { queryParams }));
|
|
25
27
|
this.outputResult(data, [
|
|
26
28
|
'trace_unique_id', 'name', 'duration', 'span_count', 'total_cost', 'error_count', 'start_time',
|
|
27
29
|
]);
|
|
@@ -33,15 +35,23 @@ class TracesList extends BaseCommand {
|
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
|
-
TracesList.description =
|
|
38
|
+
TracesList.description = `List and filter traces.
|
|
39
|
+
|
|
40
|
+
A trace represents a complete workflow execution containing multiple spans.
|
|
41
|
+
|
|
42
|
+
${FILTER_SYNTAX_HELP}
|
|
43
|
+
|
|
44
|
+
${TRACE_FIELDS_HELP}
|
|
45
|
+
|
|
46
|
+
${FILTER_EXAMPLES}`;
|
|
37
47
|
TracesList.flags = {
|
|
38
48
|
...BaseCommand.baseFlags,
|
|
39
49
|
limit: Flags.integer({ description: 'Number of results per page', default: 10 }),
|
|
40
50
|
page: Flags.integer({ description: 'Page number', default: 1 }),
|
|
41
|
-
'sort-by': Flags.string({ description: 'Sort field', default: '-timestamp' }),
|
|
51
|
+
'sort-by': Flags.string({ description: 'Sort field (prefix with - for descending)', default: '-timestamp' }),
|
|
42
52
|
'start-time': Flags.string({ description: 'Start time filter (ISO 8601)' }),
|
|
43
53
|
'end-time': Flags.string({ description: 'End time filter (ISO 8601)' }),
|
|
44
54
|
environment: Flags.string({ description: 'Environment filter' }),
|
|
45
|
-
filter: Flags.string({ description: 'Filter
|
|
55
|
+
filter: Flags.string({ description: 'Filter in field:operator:value format (repeatable)', multiple: true }),
|
|
46
56
|
};
|
|
47
57
|
export default TracesList;
|
package/dist/lib/auth.d.ts
CHANGED
package/dist/lib/auth.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getCredential } from './config.js';
|
|
2
|
-
const DEFAULT_BASE_URL = 'https://api.respan.ai
|
|
2
|
+
export const DEFAULT_BASE_URL = 'https://api.respan.ai';
|
|
3
|
+
export const ENTERPRISE_BASE_URL = 'https://endpoint.respan.ai';
|
|
3
4
|
export function resolveAuth(flags) {
|
|
4
5
|
if (flags['api-key']) {
|
|
5
6
|
return { apiKey: flags['api-key'], baseUrl: DEFAULT_BASE_URL };
|
package/dist/lib/banner.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function printBanner(): void
|
|
1
|
+
export declare function printBanner(): Promise<void>;
|
|
2
2
|
export declare function printLoginSuccess(email?: string, profile?: string): Promise<void>;
|
package/dist/lib/banner.js
CHANGED
|
@@ -90,12 +90,17 @@ function renderBanner(lines) {
|
|
|
90
90
|
return result;
|
|
91
91
|
}
|
|
92
92
|
const PC = '\x1b[38;2;100;131;240m'; // primary color
|
|
93
|
-
|
|
93
|
+
function sleep(ms) {
|
|
94
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
95
|
+
}
|
|
96
|
+
export async function printBanner() {
|
|
94
97
|
if (!process.stdout.isTTY || process.env.NO_COLOR)
|
|
95
98
|
return;
|
|
99
|
+
const lines = renderBanner(BANNER_LINES);
|
|
96
100
|
console.log('');
|
|
97
|
-
for (const line of
|
|
101
|
+
for (const line of lines) {
|
|
98
102
|
console.log(line);
|
|
103
|
+
await sleep(80);
|
|
99
104
|
}
|
|
100
105
|
console.log('');
|
|
101
106
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared filter parser for CLI commands.
|
|
3
|
+
*
|
|
4
|
+
* Parses --filter strings in `field:operator:value` format into the API's
|
|
5
|
+
* `{ field: { operator, value } }` body format.
|
|
6
|
+
*
|
|
7
|
+
* Syntax: field:operator:value
|
|
8
|
+
* - field::value → exact match (empty operator)
|
|
9
|
+
* - field:gt:value → greater than
|
|
10
|
+
* - field:in:a,b,c → value in list
|
|
11
|
+
* - field:isnull:true → null check
|
|
12
|
+
*
|
|
13
|
+
* Numeric values are auto-detected and converted.
|
|
14
|
+
* Multiple --filter flags are merged into a single filters object.
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseFilters(filterStrings: string[]): Record<string, {
|
|
17
|
+
operator: string;
|
|
18
|
+
value: unknown[];
|
|
19
|
+
}>;
|
|
20
|
+
/** Filter syntax documentation for use in command descriptions */
|
|
21
|
+
export declare const FILTER_SYNTAX_HELP = "FILTER SYNTAX: field:operator:value\n\nOPERATORS:\n (empty) Exact match model::gpt-4\n not Not equal status_code:not:200\n gt Greater than cost:gt:0.01\n gte Greater than/equal latency:gte:1.0\n lt Less than cost:lt:0.5\n lte Less than/equal prompt_tokens:lte:100\n contains Contains substring error_message:contains:timeout\n icontains Case-insensitive model:icontains:gpt\n startswith Starts with model:startswith:gpt\n endswith Ends with model:endswith:mini\n in Value in list model:in:gpt-4,gpt-4o\n isnull Is null error_message:isnull:true\n iexact Case-insens. exact status:iexact:success";
|
|
22
|
+
export declare const LOG_FIELDS_HELP = "FILTERABLE FIELDS (logs):\n model, status_code, status, cost, latency, prompt_tokens,\n completion_tokens, customer_identifier, custom_identifier,\n thread_identifier, trace_unique_id, span_name, span_workflow_name,\n environment, log_type, error_message, failed, provider_id,\n deployment_name, prompt_name, prompt_id, unique_id, stream,\n temperature, max_tokens, tokens_per_second, time_to_first_token,\n total_request_tokens, metadata__<key>, scores__<evaluator_id>";
|
|
23
|
+
export declare const TRACE_FIELDS_HELP = "FILTERABLE FIELDS (traces):\n trace_unique_id, customer_identifier, environment, span_count,\n llm_call_count, error_count, total_cost, total_tokens,\n total_prompt_tokens, total_completion_tokens, duration,\n span_workflow_name, metadata__<key>";
|
|
24
|
+
export declare const FILTER_EXAMPLES = "EXAMPLES:\n --filter model::gpt-4o --filter cost:gt:0.01\n --filter status_code:not:200\n --filter metadata__env::production\n --filter model:in:gpt-4,gpt-4o";
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared filter parser for CLI commands.
|
|
3
|
+
*
|
|
4
|
+
* Parses --filter strings in `field:operator:value` format into the API's
|
|
5
|
+
* `{ field: { operator, value } }` body format.
|
|
6
|
+
*
|
|
7
|
+
* Syntax: field:operator:value
|
|
8
|
+
* - field::value → exact match (empty operator)
|
|
9
|
+
* - field:gt:value → greater than
|
|
10
|
+
* - field:in:a,b,c → value in list
|
|
11
|
+
* - field:isnull:true → null check
|
|
12
|
+
*
|
|
13
|
+
* Numeric values are auto-detected and converted.
|
|
14
|
+
* Multiple --filter flags are merged into a single filters object.
|
|
15
|
+
*/
|
|
16
|
+
const VALID_OPERATORS = new Set([
|
|
17
|
+
'', 'not', 'lt', 'lte', 'gt', 'gte',
|
|
18
|
+
'contains', 'icontains', 'startswith', 'endswith',
|
|
19
|
+
'in', 'isnull', 'iexact',
|
|
20
|
+
]);
|
|
21
|
+
function coerceValue(v) {
|
|
22
|
+
if (v === 'true')
|
|
23
|
+
return true;
|
|
24
|
+
if (v === 'false')
|
|
25
|
+
return false;
|
|
26
|
+
const n = Number(v);
|
|
27
|
+
if (!Number.isNaN(n) && v.trim() !== '')
|
|
28
|
+
return n;
|
|
29
|
+
return v;
|
|
30
|
+
}
|
|
31
|
+
export function parseFilters(filterStrings) {
|
|
32
|
+
const result = {};
|
|
33
|
+
for (const raw of filterStrings) {
|
|
34
|
+
// Split on first two colons: field:operator:value
|
|
35
|
+
// field::value means operator is empty string
|
|
36
|
+
const firstColon = raw.indexOf(':');
|
|
37
|
+
if (firstColon === -1) {
|
|
38
|
+
throw new Error(`Invalid filter format: "${raw}". Expected field:operator:value (e.g. model::gpt-4 or cost:gt:0.01)`);
|
|
39
|
+
}
|
|
40
|
+
const field = raw.slice(0, firstColon);
|
|
41
|
+
const rest = raw.slice(firstColon + 1);
|
|
42
|
+
const secondColon = rest.indexOf(':');
|
|
43
|
+
if (secondColon === -1) {
|
|
44
|
+
throw new Error(`Invalid filter format: "${raw}". Expected field:operator:value (e.g. model::gpt-4 or cost:gt:0.01)`);
|
|
45
|
+
}
|
|
46
|
+
const operator = rest.slice(0, secondColon);
|
|
47
|
+
const valueStr = rest.slice(secondColon + 1);
|
|
48
|
+
if (!VALID_OPERATORS.has(operator)) {
|
|
49
|
+
throw new Error(`Unknown filter operator: "${operator}". Valid operators: ${[...VALID_OPERATORS].filter(Boolean).join(', ')} (or empty for exact match)`);
|
|
50
|
+
}
|
|
51
|
+
if (!field) {
|
|
52
|
+
throw new Error(`Filter field cannot be empty: "${raw}"`);
|
|
53
|
+
}
|
|
54
|
+
// For "in" operator, split on comma to create array
|
|
55
|
+
let values;
|
|
56
|
+
if (operator === 'in') {
|
|
57
|
+
values = valueStr.split(',').map((v) => coerceValue(v.trim()));
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
values = [coerceValue(valueStr)];
|
|
61
|
+
}
|
|
62
|
+
result[field] = { operator, value: values };
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
/** Filter syntax documentation for use in command descriptions */
|
|
67
|
+
export const FILTER_SYNTAX_HELP = `FILTER SYNTAX: field:operator:value
|
|
68
|
+
|
|
69
|
+
OPERATORS:
|
|
70
|
+
(empty) Exact match model::gpt-4
|
|
71
|
+
not Not equal status_code:not:200
|
|
72
|
+
gt Greater than cost:gt:0.01
|
|
73
|
+
gte Greater than/equal latency:gte:1.0
|
|
74
|
+
lt Less than cost:lt:0.5
|
|
75
|
+
lte Less than/equal prompt_tokens:lte:100
|
|
76
|
+
contains Contains substring error_message:contains:timeout
|
|
77
|
+
icontains Case-insensitive model:icontains:gpt
|
|
78
|
+
startswith Starts with model:startswith:gpt
|
|
79
|
+
endswith Ends with model:endswith:mini
|
|
80
|
+
in Value in list model:in:gpt-4,gpt-4o
|
|
81
|
+
isnull Is null error_message:isnull:true
|
|
82
|
+
iexact Case-insens. exact status:iexact:success`;
|
|
83
|
+
export const LOG_FIELDS_HELP = `FILTERABLE FIELDS (logs):
|
|
84
|
+
model, status_code, status, cost, latency, prompt_tokens,
|
|
85
|
+
completion_tokens, customer_identifier, custom_identifier,
|
|
86
|
+
thread_identifier, trace_unique_id, span_name, span_workflow_name,
|
|
87
|
+
environment, log_type, error_message, failed, provider_id,
|
|
88
|
+
deployment_name, prompt_name, prompt_id, unique_id, stream,
|
|
89
|
+
temperature, max_tokens, tokens_per_second, time_to_first_token,
|
|
90
|
+
total_request_tokens, metadata__<key>, scores__<evaluator_id>`;
|
|
91
|
+
export const TRACE_FIELDS_HELP = `FILTERABLE FIELDS (traces):
|
|
92
|
+
trace_unique_id, customer_identifier, environment, span_count,
|
|
93
|
+
llm_call_count, error_count, total_cost, total_tokens,
|
|
94
|
+
total_prompt_tokens, total_completion_tokens, duration,
|
|
95
|
+
span_workflow_name, metadata__<key>`;
|
|
96
|
+
export const FILTER_EXAMPLES = `EXAMPLES:
|
|
97
|
+
--filter model::gpt-4o --filter cost:gt:0.01
|
|
98
|
+
--filter status_code:not:200
|
|
99
|
+
--filter metadata__env::production
|
|
100
|
+
--filter model:in:gpt-4,gpt-4o`;
|