@roarkanalytics/cli 0.28.0 → 0.31.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.
@@ -30,6 +30,9 @@ Optional.
30
30
  \fB\-\-preceded\-by\-customer\-flow\-variant\-id\fR \fI<value>\fR
31
31
  Optional.
32
32
  .TP
33
+ \fB\-\-additional\-expectations\fR \fI<value>\fR
34
+ The expectations graded on this variant only, on top of the flow's own. Sent as the complete set: it replaces whatever the variant had, so omit it to leave the set alone and send [] to clear it. Improv flows only: a scripted variant's expectations come from the agent turns on its path and are rewritten on every graph edit. Optional.
35
+ .TP
33
36
  \fB\-\-persona\-override\-id\fR \fI<value>\fR
34
37
  The persona this runs as. Omit to inherit the happy path's. Optional.
35
38
  .SH "GLOBAL OPTIONS"
@@ -37,7 +37,7 @@ Optional.
37
37
  The persona this runs as. Null on an edge case inherits the happy path's. Optional.
38
38
  .TP
39
39
  \fB\-\-additional\-expectations\fR \fI<value>\fR
40
- Replaces the expectations that apply to this variant on top of the flow's. Omit to leave them alone, send [] to clear. Improv flows only: a scripted variant's expectations come from the agent turns on its path and are rewritten on the next graph edit. Optional.
40
+ The expectations graded on this variant only, on top of the flow's own. Sent as the complete set: it replaces whatever the variant had, so omit it to leave the set alone and send [] to clear it. Improv flows only: a scripted variant's expectations come from the agent turns on its path and are rewritten on every graph edit. Optional.
41
41
  .SH "GLOBAL OPTIONS"
42
42
  .TP
43
43
  \fB\-\-data\fR \fI<json>\fR
@@ -34,7 +34,7 @@ Optional.
34
34
  The persona this runs as. Null on an edge case inherits the happy path's. Optional.
35
35
  .TP
36
36
  \fB\-\-additional\-expectations\fR \fI<value>\fR
37
- Replaces the expectations that apply to this variant on top of the flow's. Omit to leave them alone, send [] to clear. Improv flows only: a scripted variant's expectations come from the agent turns on its path and are rewritten on the next graph edit. Optional.
37
+ The expectations graded on this variant only, on top of the flow's own. Sent as the complete set: it replaces whatever the variant had, so omit it to leave the set alone and send [] to clear it. Improv flows only: a scripted variant's expectations come from the agent turns on its path and are rewritten on every graph edit. Optional.
38
38
  .SH "GLOBAL OPTIONS"
39
39
  .TP
40
40
  \fB\-\-data\fR \fI<json>\fR
@@ -0,0 +1,89 @@
1
+ .\" File generated from our OpenAPI spec by app-agent-codegen. Do not edit.
2
+ .TH "ROARK\-PROJECT\-LIST" "1" "" "roark-project-list" "Roark Analytics API Manual"
3
+ .SH "NAME"
4
+ roark\-project\-list \- List projects available to this credential
5
+ .SH "SYNOPSIS"
6
+ .B roark project list
7
+ [\fIoptions\fR]
8
+ .SH "DESCRIPTION"
9
+ Returns every project the calling credential may act on, with the permissions it holds in each. A project\-scoped API key returns the single project it is bound to. A user\-scoped credential returns the projects in its organization where the holder is an active member. Use the returned id in the X\-Roark\-Project\-Id header.
10
+ .SH "API"
11
+ GET /v1/projects
12
+ .SH "GLOBAL OPTIONS"
13
+ .TP
14
+ \fB\-\-data\fR \fI<json>\fR
15
+ Request body as JSON, or @file to read one (@\- for stdin).
16
+ .TP
17
+ \fB\-y\fR, \fB\-\-yes\fR
18
+ Skip confirmation prompts.
19
+ .TP
20
+ \fB\-\-no\-input\fR
21
+ Never prompt; fail instead.
22
+ .TP
23
+ \fB\-\-base\-url\fR \fI<url>\fR
24
+ API base URL.
25
+ .TP
26
+ \fB\-\-token\fR \fI<token>\fR
27
+ Bearer token. Prefer ROARK_API_BEARER_TOKEN or the stored credential.
28
+ .TP
29
+ \fB\-\-timeout\fR \fI<ms>\fR
30
+ Request timeout in milliseconds.
31
+ .TP
32
+ \fB\-\-max\-retries\fR \fI<count>\fR
33
+ Retries for retryable failures.
34
+ .TP
35
+ \fB\-\-format\fR \fI<format>\fR
36
+ Output format: auto, json, jsonl, plain.
37
+ .TP
38
+ \fB\-\-json\fR
39
+ Shorthand for \-\-format json.
40
+ .TP
41
+ \fB\-\-no\-color\fR
42
+ Disable colour.
43
+ .TP
44
+ \fB\-q\fR, \fB\-\-quiet\fR
45
+ Suppress non\-essential output.
46
+ .TP
47
+ \fB\-\-allow\-project\-base\-url\fR
48
+ Send the stored credential to a base URL set by a project .roark.json. Refused by default.
49
+ .SH "EXAMPLES"
50
+ .PP
51
+ .RS 4
52
+ .nf
53
+ roark project list
54
+ .fi
55
+ .RE
56
+ .SH "ENVIRONMENT"
57
+ .TP
58
+ \fBROARK_API_BEARER_TOKEN\fR
59
+ Bearer token used when \-\-token is not given.
60
+ .TP
61
+ \fBROARK_BASE_URL\fR
62
+ API base URL.
63
+ .TP
64
+ \fBROARK_ALLOW_PROJECT_BASE_URL\fR
65
+ Trusts a base URL set by a project .roark.json, like \-\-allow\-project\-base\-url.
66
+ .TP
67
+ \fBNO_COLOR\fR
68
+ Disables colour when set to any value.
69
+ .SH "EXIT STATUS"
70
+ .TP
71
+ \fB0\fR
72
+ The command succeeded.
73
+ .TP
74
+ \fB1\fR
75
+ The API rejected the request.
76
+ .TP
77
+ \fB2\fR
78
+ The command line was wrong.
79
+ .TP
80
+ \fB3\fR
81
+ No credential, or the credential was refused.
82
+ .TP
83
+ \fB4\fR
84
+ The addressed resource does not exist.
85
+ .TP
86
+ \fB5\fR
87
+ The request never completed: connection, timeout, or abort.
88
+ .SH "SEE ALSO"
89
+ .BR roark (1)
@@ -0,0 +1,89 @@
1
+ .\" File generated from our OpenAPI spec by app-agent-codegen. Do not edit.
2
+ .TH "ROARK\-WHOAMI" "1" "" "roark-whoami" "Roark Analytics API Manual"
3
+ .SH "NAME"
4
+ roark\-whoami \- Describe the current credential
5
+ .SH "SYNOPSIS"
6
+ .B roark whoami
7
+ [\fIoptions\fR]
8
+ .SH "DESCRIPTION"
9
+ Returns the scope of the credential making the request, the organization it acts in, its default project (if any) and the permissions it was granted. Works for both project\-scoped API keys and user\-scoped credentials from the CLI or an MCP connector.
10
+ .SH "API"
11
+ GET /v1/me
12
+ .SH "GLOBAL OPTIONS"
13
+ .TP
14
+ \fB\-\-data\fR \fI<json>\fR
15
+ Request body as JSON, or @file to read one (@\- for stdin).
16
+ .TP
17
+ \fB\-y\fR, \fB\-\-yes\fR
18
+ Skip confirmation prompts.
19
+ .TP
20
+ \fB\-\-no\-input\fR
21
+ Never prompt; fail instead.
22
+ .TP
23
+ \fB\-\-base\-url\fR \fI<url>\fR
24
+ API base URL.
25
+ .TP
26
+ \fB\-\-token\fR \fI<token>\fR
27
+ Bearer token. Prefer ROARK_API_BEARER_TOKEN or the stored credential.
28
+ .TP
29
+ \fB\-\-timeout\fR \fI<ms>\fR
30
+ Request timeout in milliseconds.
31
+ .TP
32
+ \fB\-\-max\-retries\fR \fI<count>\fR
33
+ Retries for retryable failures.
34
+ .TP
35
+ \fB\-\-format\fR \fI<format>\fR
36
+ Output format: auto, json, jsonl, plain.
37
+ .TP
38
+ \fB\-\-json\fR
39
+ Shorthand for \-\-format json.
40
+ .TP
41
+ \fB\-\-no\-color\fR
42
+ Disable colour.
43
+ .TP
44
+ \fB\-q\fR, \fB\-\-quiet\fR
45
+ Suppress non\-essential output.
46
+ .TP
47
+ \fB\-\-allow\-project\-base\-url\fR
48
+ Send the stored credential to a base URL set by a project .roark.json. Refused by default.
49
+ .SH "EXAMPLES"
50
+ .PP
51
+ .RS 4
52
+ .nf
53
+ roark whoami
54
+ .fi
55
+ .RE
56
+ .SH "ENVIRONMENT"
57
+ .TP
58
+ \fBROARK_API_BEARER_TOKEN\fR
59
+ Bearer token used when \-\-token is not given.
60
+ .TP
61
+ \fBROARK_BASE_URL\fR
62
+ API base URL.
63
+ .TP
64
+ \fBROARK_ALLOW_PROJECT_BASE_URL\fR
65
+ Trusts a base URL set by a project .roark.json, like \-\-allow\-project\-base\-url.
66
+ .TP
67
+ \fBNO_COLOR\fR
68
+ Disables colour when set to any value.
69
+ .SH "EXIT STATUS"
70
+ .TP
71
+ \fB0\fR
72
+ The command succeeded.
73
+ .TP
74
+ \fB1\fR
75
+ The API rejected the request.
76
+ .TP
77
+ \fB2\fR
78
+ The command line was wrong.
79
+ .TP
80
+ \fB3\fR
81
+ No credential, or the credential was refused.
82
+ .TP
83
+ \fB4\fR
84
+ The addressed resource does not exist.
85
+ .TP
86
+ \fB5\fR
87
+ The request never completed: connection, timeout, or abort.
88
+ .SH "SEE ALSO"
89
+ .BR roark (1)
package/man/roark.1 CHANGED
@@ -282,6 +282,9 @@ List a metric’s variants See roark\-metric\-variant\-list(1).
282
282
  \fBroark metric variant update\fR
283
283
  Update a metric variant See roark\-metric\-variant\-update(1).
284
284
  .TP
285
+ \fBroark project list\fR
286
+ List projects available to this credential See roark\-project\-list(1).
287
+ .TP
285
288
  \fBroark simulation environment create\fR
286
289
  Create an environment See roark\-simulation\-environment\-create(1).
287
290
  .TP
@@ -377,6 +380,9 @@ Get webhook by ID See roark\-webhook\-get(1).
377
380
  .TP
378
381
  \fBroark webhook list\fR
379
382
  List webhooks See roark\-webhook\-list(1).
383
+ .TP
384
+ \fBroark whoami\fR
385
+ Describe the current credential See roark\-whoami(1).
380
386
  .SH "CONFIGURATION"
381
387
  Settings are resolved highest precedence first: command line flag, environment variable, project .roark.json found by walking up from the working directory, then the user config file. The stored credential is written with mode 0600.
382
388
  .SH "ENVIRONMENT"
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@roarkanalytics/cli",
3
- "version": "0.28.0",
3
+ "version": "0.31.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@roarkanalytics/cli",
9
- "version": "0.28.0",
9
+ "version": "0.31.1",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
- "@roarkanalytics/sdk": "^4.4.0",
12
+ "@roarkanalytics/sdk": "^4.15.0",
13
13
  "commander": "^14.0.3",
14
14
  "yaml": "^2.9.0"
15
15
  },
@@ -21,9 +21,9 @@
21
21
  }
22
22
  },
23
23
  "node_modules/@roarkanalytics/sdk": {
24
- "version": "4.8.0",
25
- "resolved": "https://registry.npmjs.org/@roarkanalytics/sdk/-/sdk-4.8.0.tgz",
26
- "integrity": "sha512-j7awV7Fq8zJgZOYBoU+I7HOoBzQbWMsZFpmN601j1lt6j50vC1g3Lz7FKmrvP4X31EZBOvFDrGktnfRNQANIew==",
24
+ "version": "4.15.0",
25
+ "resolved": "https://registry.npmjs.org/@roarkanalytics/sdk/-/sdk-4.15.0.tgz",
26
+ "integrity": "sha512-H5bHFILsMVMUG7+6yi3gO4JrAvvoFPLn82zZp0tS1A6UugH/hzQgwuMFSkjbJCl29p9GcVjAFhLDaZa7845gmA==",
27
27
  "license": "Apache-2.0"
28
28
  },
29
29
  "node_modules/commander": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roarkanalytics/cli",
3
- "version": "0.28.0",
3
+ "version": "0.31.1",
4
4
  "description": "The official command line interface for the Roark API",
5
5
  "author": "Roark <james@roark.ai>",
6
6
  "type": "commonjs",
@@ -29,7 +29,7 @@
29
29
  "fix": "eslint --fix . && prettier --write ."
30
30
  },
31
31
  "dependencies": {
32
- "@roarkanalytics/sdk": "^4.4.0",
32
+ "@roarkanalytics/sdk": "^4.15.0",
33
33
  "commander": "^14.0.3",
34
34
  "yaml": "^2.9.0"
35
35
  },
@@ -109,6 +109,7 @@
109
109
  "./man/roark-metric-variant-get.1",
110
110
  "./man/roark-metric-variant-list.1",
111
111
  "./man/roark-metric-variant-update.1",
112
+ "./man/roark-project-list.1",
112
113
  "./man/roark-simulation-environment-create.1",
113
114
  "./man/roark-simulation-environment-delete.1",
114
115
  "./man/roark-simulation-environment-get.1",
@@ -140,6 +141,7 @@
140
141
  "./man/roark-webhook-create.1",
141
142
  "./man/roark-webhook-delete.1",
142
143
  "./man/roark-webhook-get.1",
143
- "./man/roark-webhook-list.1"
144
+ "./man/roark-webhook-list.1",
145
+ "./man/roark-whoami.1"
144
146
  ]
145
147
  }
@@ -23,4 +23,18 @@ export interface BrowserLoginOptions {
23
23
  onAuthorizeUrl?: (url: string) => void;
24
24
  open?: (url: string) => void;
25
25
  }
26
- export declare const browserLogin: (options: BrowserLoginOptions) => Promise<string>;
26
+ /** What the token exchange hands back. */
27
+ export interface BrowserLoginResult {
28
+ /** The minted bearer token. */
29
+ token: string;
30
+ /**
31
+ * The project chosen on the consent screen, when the server reports one.
32
+ *
33
+ * A user-scoped credential stores no project of its own, so this is the only moment the choice
34
+ * is communicated: `auth login` writes it to the user config so the next command works without
35
+ * a `--project`. Absent when the server minted a project-scoped key, which needs no project
36
+ * setting at all.
37
+ */
38
+ project?: string;
39
+ }
40
+ export declare const browserLogin: (options: BrowserLoginOptions) => Promise<BrowserLoginResult>;
@@ -127,6 +127,7 @@ const browserLogin = async (options) => {
127
127
  const token = body.data?.token;
128
128
  if (!token)
129
129
  throw new Error('token exchange returned no token');
130
- return token;
130
+ const project = body.data?.defaultProjectId;
131
+ return { token, ...(typeof project === 'string' && project.length > 0 ? { project } : {}) };
131
132
  };
132
133
  exports.browserLogin = browserLogin;
@@ -43,6 +43,10 @@ const registerApiCommand = (root, binaryName, clientFor, resolveOutput) => {
43
43
  .option('--data <json>', 'request body as JSON, or @file to read one (@- for stdin)')
44
44
  .option('--query <key=value>', 'query parameter; repeat for more than one', (value, previous) => [...(previous ?? []), value])
45
45
  .option('--base-url <url>', 'API base URL')
46
+ // `api` redeclares the connection options rather than inheriting the root ones, so this has to
47
+ // be listed here too. Without it the escape hatch is the one command a user credential cannot
48
+ // use: every project-scoped path 400s with no way to name a project.
49
+ .option('--project <id>', 'project to act on (or ROARK_PROJECT_ID, or `roark config set project`)')
46
50
  .option('--token <token>', 'bearer token')
47
51
  .option('--timeout <ms>', 'request timeout in milliseconds')
48
52
  .option('--max-retries <count>', 'retries for retryable failures')
@@ -24,6 +24,55 @@ const DEFAULT_BASE_URL = 'https://api.roark.ai';
24
24
  // a 401 while a valid one that merely lacks this endpoint's permission is a 403 — both prove the
25
25
  // token authenticates. `/v1/agent` is a stable GET that every project has.
26
26
  const AUTH_PROBE_PATH = '/v1/agent';
27
+ // What the credential itself says it is. Added with user-scoped credentials and the only endpoint
28
+ // that answers without naming a project, which is exactly what a liveness check needs. An older API
29
+ // does not have it; callers fall back to AUTH_PROBE_PATH on a 404.
30
+ const AUTH_DESCRIBE_PATH = '/v1/me';
31
+ /**
32
+ * Asks the API whether the effective credential still works.
33
+ *
34
+ * `auth status` used to answer from the config file alone, so it said "Authenticated" for a
35
+ * credential the server had already revoked. That was always wrong and is now easy to hit: a
36
+ * user-scoped credential is revoked from the web UI, in a different place from the machine
37
+ * holding it, so the CLI is routinely the last to know.
38
+ */
39
+ const describeCredential = async (clientFor) => {
40
+ let client;
41
+ try {
42
+ // Resolved the same way a real command would, so the base-url trust guard still applies and we
43
+ // are checking the credential that would actually be used.
44
+ client = clientFor({});
45
+ }
46
+ catch (error) {
47
+ return { kind: 'unknown', reason: error.message.split('\n')[0] ?? 'unknown error' };
48
+ }
49
+ const classify = (error) => {
50
+ const status = error.status;
51
+ if (status === 401)
52
+ return { kind: 'rejected' };
53
+ // 403 (authenticated, lacks a permission) and 400 (a user credential that named no project)
54
+ // both prove authentication succeeded.
55
+ if (status !== undefined && status < 500)
56
+ return { kind: 'live' };
57
+ return { kind: 'unknown', reason: error.message };
58
+ };
59
+ try {
60
+ const me = (await client.get(AUTH_DESCRIBE_PATH));
61
+ return { kind: 'live', scope: me.data?.tokenScope, email: me.data?.user?.email ?? null };
62
+ }
63
+ catch (error) {
64
+ if (error.status !== 404)
65
+ return classify(error);
66
+ }
67
+ // Older API without /v1/me: any authenticated endpoint still proves the token lives.
68
+ try {
69
+ await client.get(AUTH_PROBE_PATH, { query: { limit: '1' } });
70
+ return { kind: 'live' };
71
+ }
72
+ catch (error) {
73
+ return classify(error);
74
+ }
75
+ };
27
76
  /**
28
77
  * Confirm the stored credential actually authenticates, so a mistyped or half-pasted token is caught
29
78
  * here instead of on the user's next real command. Best-effort: it never fails the login (the token
@@ -112,10 +161,21 @@ const readToken = async (binaryName) => {
112
161
  return token;
113
162
  };
114
163
  // Save the token and report where it went (masked). Shared by the browser and paste paths.
115
- const persistToken = (token) => {
116
- const path = (0, config_1.writeUserConfig)({ ...(0, config_1.readUserConfig)(), bearerToken: token });
164
+ //
165
+ // `project` is written only when the browser flow reports one: a user credential stores no
166
+ // project of its own, so without this the very next command would need a `--project`. The paste
167
+ // path passes nothing, which leaves any existing setting alone rather than clearing it.
168
+ const persistToken = (token, project) => {
169
+ const path = (0, config_1.writeUserConfig)({
170
+ ...(0, config_1.readUserConfig)(),
171
+ bearerToken: token,
172
+ ...(project === undefined ? {} : { project }),
173
+ });
117
174
  const color = (0, output_1.supportsColor)(process.stderr);
118
175
  (0, output_1.write)(`${(0, output_1.paint)('Saved', 'green', color)} ${(0, config_1.maskToken)(token)} to ${path}`, process.stderr);
176
+ if (project !== undefined) {
177
+ (0, output_1.write)(`${(0, output_1.paint)('Project', 'green', color)} ${project} (change it with \`config set project\`)`, process.stderr);
178
+ }
119
179
  return { color };
120
180
  };
121
181
  // A set environment variable outranks the stored token (see the precedence in config.ts), so without
@@ -151,9 +211,9 @@ const registerAuthCommands = (root, binaryName, clientFor) => {
151
211
  const color = (0, output_1.supportsColor)(process.stderr);
152
212
  const apiBaseUrl = (0, config_1.resolveConfig)({}).config.baseURL ?? DEFAULT_BASE_URL;
153
213
  (0, output_1.write)((0, output_1.paint)('Opening your browser to authorize this CLI…', 'dim', color), process.stderr);
154
- let token;
214
+ let login;
155
215
  try {
156
- token = await (0, browser_login_1.browserLogin)({
216
+ login = await (0, browser_login_1.browserLogin)({
157
217
  apiBaseUrl,
158
218
  platformOrigin: (0, browser_login_1.platformOriginFor)(apiBaseUrl),
159
219
  clientName: `CLI on ${(0, node_os_1.hostname)()}`,
@@ -164,8 +224,8 @@ const registerAuthCommands = (root, binaryName, clientFor) => {
164
224
  throw new errors_1.UsageError(`Browser login failed: ${error.message}\n` +
165
225
  `Run \`${binaryName} auth login --paste\` to paste or pipe a token instead.`);
166
226
  }
167
- const { color: savedColor } = persistToken(token);
168
- warnEnvShadow(token, savedColor);
227
+ const { color: savedColor } = persistToken(login.token, login.project);
228
+ warnEnvShadow(login.token, savedColor);
169
229
  return;
170
230
  }
171
231
  const token = await readToken(binaryName);
@@ -184,24 +244,46 @@ const registerAuthCommands = (root, binaryName, clientFor) => {
184
244
  });
185
245
  auth
186
246
  .command('status')
187
- .description('Show which credential would be used, and where it came from')
188
- .action(() => {
247
+ .description('Show which credential would be used, where it came from, and whether it still works')
248
+ .action(async () => {
189
249
  const color = (0, output_1.supportsColor)(process.stderr);
190
250
  const fromEnv = process.env['ROARK_API_BEARER_TOKEN'];
191
251
  const stored = (0, config_1.readUserConfig)().bearerToken;
192
- if (fromEnv) {
193
- (0, output_1.write)(`${(0, output_1.paint)('Authenticated', 'green', color)} via ROARK_API_BEARER_TOKEN (${(0, config_1.maskToken)(fromEnv)})`, process.stderr);
194
- if (stored) {
195
- (0, output_1.write)((0, output_1.paint)(`A token is also stored in ${(0, config_1.userConfigPath)()}; the environment variable wins.`, 'dim', color), process.stderr);
196
- }
252
+ const token = fromEnv ?? stored;
253
+ if (!token) {
254
+ (0, output_1.write)(`${(0, output_1.paint)('Not authenticated.', 'yellow', color)} Run \`${binaryName} auth login\`.`, process.stderr);
255
+ process.exitCode = 3;
197
256
  return;
198
257
  }
199
- if (stored) {
200
- (0, output_1.write)(`${(0, output_1.paint)('Authenticated', 'green', color)} via ${(0, config_1.userConfigPath)()} (${(0, config_1.maskToken)(stored)})`, process.stderr);
258
+ const source = fromEnv ? 'ROARK_API_BEARER_TOKEN' : (0, config_1.userConfigPath)();
259
+ // Ask the API rather than trusting the file. Without this the command reports a revoked
260
+ // credential as authenticated, which is the opposite of what someone runs it to find out.
261
+ const state = clientFor ? await describeCredential(clientFor) : { kind: 'unknown', reason: 'no client' };
262
+ if (state.kind === 'rejected') {
263
+ (0, output_1.write)(`${(0, output_1.paint)('Rejected', 'red', color)} by the API (401): the credential in ${source} (${(0, config_1.maskToken)(token)}) is wrong, expired, or revoked.`, process.stderr);
264
+ (0, output_1.write)((0, output_1.paint)(`Run \`${binaryName} auth login\` to replace it.`, 'dim', color), process.stderr);
265
+ process.exitCode = 3;
201
266
  return;
202
267
  }
203
- (0, output_1.write)(`${(0, output_1.paint)('Not authenticated.', 'yellow', color)} Run \`${binaryName} auth login\`.`, process.stderr);
204
- process.exitCode = 3;
268
+ if (state.kind === 'unknown') {
269
+ // Deliberately not "Authenticated": we do not know. Saying so is the whole point.
270
+ (0, output_1.write)(`${(0, output_1.paint)('Unverified', 'yellow', color)} credential in ${source} (${(0, config_1.maskToken)(token)}): could not reach the API (${state.reason}).`, process.stderr);
271
+ }
272
+ else {
273
+ const who = state.email ? ` as ${state.email}` : '';
274
+ (0, output_1.write)(`${(0, output_1.paint)('Authenticated', 'green', color)}${who} via ${source} (${(0, config_1.maskToken)(token)})`, process.stderr);
275
+ if (state.scope === 'USER') {
276
+ // A user credential reaches every project you belong to, so which one it acts on is part
277
+ // of the answer, not a detail.
278
+ const project = (0, config_1.loadConfig)().project;
279
+ (0, output_1.write)((0, output_1.paint)(project ?
280
+ `Acting on project ${project}. Change it with \`${binaryName} config set project <id>\`.`
281
+ : `No project selected: commands will fail until you set one with \`${binaryName} config set project <id>\`.`, 'dim', color), process.stderr);
282
+ }
283
+ }
284
+ if (fromEnv && stored) {
285
+ (0, output_1.write)((0, output_1.paint)(`A token is also stored in ${(0, config_1.userConfigPath)()}; the environment variable wins.`, 'dim', color), process.stderr);
286
+ }
205
287
  });
206
288
  root.addCommand(auth);
207
289
  };
@@ -21,6 +21,7 @@ const output_1 = require("../output");
21
21
  /** Settable keys, as typed, mapped onto the config field they write. */
22
22
  const KEYS = {
23
23
  'base-url': 'baseURL',
24
+ project: 'project',
24
25
  timeout: 'timeout',
25
26
  'max-retries': 'maxRetries',
26
27
  };
@@ -14,6 +14,18 @@
14
14
  export interface CliConfig {
15
15
  bearerToken?: string;
16
16
  baseURL?: string;
17
+ /**
18
+ * The project commands act on, sent as `X-Roark-Project-Id`.
19
+ *
20
+ * Only meaningful for a user credential (what `auth login` now stores): it can reach every
21
+ * project you belong to, so each request has to name one. A project API key already names its
22
+ * own project and ignores this.
23
+ *
24
+ * Unlike `baseURL`, this is safe to take from a project file. It selects which of your own
25
+ * projects you act on; it cannot send your credential anywhere new. See `unsafeBaseUrlRedirect`
26
+ * for the key where that distinction matters.
27
+ */
28
+ project?: string;
17
29
  timeout?: number;
18
30
  maxRetries?: number;
19
31
  }
package/runtime/config.js CHANGED
@@ -62,6 +62,7 @@ const number = (value) => {
62
62
  const fromEnvironment = () => ({
63
63
  ...(process.env['ROARK_API_BEARER_TOKEN'] ? { bearerToken: process.env['ROARK_API_BEARER_TOKEN'] } : {}),
64
64
  ...(process.env['ROARK_BASE_URL'] ? { baseURL: process.env['ROARK_BASE_URL'] } : {}),
65
+ ...(process.env['ROARK_PROJECT_ID'] ? { project: process.env['ROARK_PROJECT_ID'] } : {}),
65
66
  ...(number(process.env['ROARK_TIMEOUT']) !== undefined ?
66
67
  { timeout: number(process.env['ROARK_TIMEOUT']) }
67
68
  : {}),
@@ -19,6 +19,7 @@ export interface ProgramOptions {
19
19
  }
20
20
  interface GlobalOptions {
21
21
  baseUrl?: string;
22
+ project?: string;
22
23
  timeout?: string;
23
24
  maxRetries?: string;
24
25
  token?: string;
@@ -37,6 +37,7 @@ const isDestructive = (command) => DESTRUCTIVE_VERBS.has(command.commandPath.at(
37
37
  const addGlobalOptions = (command, extra) => {
38
38
  command
39
39
  .option('--base-url <url>', 'API base URL')
40
+ .option('--project <id>', 'project to act on (or ROARK_PROJECT_ID, or `roark config set project`)')
40
41
  .option('--token <token>', 'bearer token (prefer ROARK_API_BEARER_TOKEN or `auth login`)')
41
42
  .option('--timeout <ms>', 'request timeout in milliseconds')
42
43
  .option('--max-retries <count>', 'retries for retryable failures')
@@ -81,6 +82,7 @@ const clientFor = (options, requiresAuth = true) => {
81
82
  const overrides = {
82
83
  ...(options.token === undefined ? {} : { bearerToken: options.token }),
83
84
  ...(options.baseUrl === undefined ? {} : { baseURL: options.baseUrl }),
85
+ ...(options.project === undefined ? {} : { project: options.project }),
84
86
  ...(options.timeout === undefined ? {} : { timeout: Number(options.timeout) }),
85
87
  ...(options.maxRetries === undefined ? {} : { maxRetries: Number(options.maxRetries) }),
86
88
  };
@@ -94,6 +96,9 @@ const clientFor = (options, requiresAuth = true) => {
94
96
  // The SDK constructor rejects an absent token, so a public endpoint is given
95
97
  // an empty one rather than being made to demand a credential it never sends.
96
98
  bearerToken: config.bearerToken ?? '',
99
+ // Sent as `X-Roark-Project-Id`. Absent unless configured, because a project key names its own
100
+ // project and an empty header is a 400 rather than a no-op.
101
+ ...(config.project === undefined ? {} : { project: config.project }),
97
102
  ...(config.baseURL === undefined ? {} : { baseURL: config.baseURL }),
98
103
  ...(config.timeout === undefined ? {} : { timeout: config.timeout }),
99
104
  ...(config.maxRetries === undefined ? {} : { maxRetries: config.maxRetries }),
package/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "0.28.0";
1
+ export declare const version = "0.31.1";
package/version.js CHANGED
@@ -10,4 +10,4 @@ exports.version = void 0;
10
10
  //
11
11
  // Do not edit: an edit here is overwritten, and the published number is whatever
12
12
  // package.json says.
13
- exports.version = '0.28.0'; // x-release-please-version
13
+ exports.version = '0.31.1'; // x-release-please-version