@doist/outline-cli 1.6.0 → 1.8.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/CHANGELOG.md +12 -0
- package/dist/commands/auth.d.ts +12 -0
- package/dist/commands/auth.js +101 -35
- package/dist/commands/auth.js.map +1 -1
- package/dist/lib/api.js +32 -4
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/auth-constants.d.ts +18 -0
- package/dist/lib/auth-constants.js +25 -0
- package/dist/lib/auth-constants.js.map +1 -0
- package/dist/lib/auth-provider.d.ts +57 -8
- package/dist/lib/auth-provider.js +226 -129
- package/dist/lib/auth-provider.js.map +1 -1
- package/dist/lib/auth.d.ts +47 -11
- package/dist/lib/auth.js +135 -35
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/config.d.ts +27 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/errors.d.ts +1 -1
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/migrate-auth.d.ts +11 -0
- package/dist/lib/migrate-auth.js +92 -0
- package/dist/lib/migrate-auth.js.map +1 -0
- package/dist/lib/outline-account.d.ts +26 -0
- package/dist/lib/outline-account.js +12 -0
- package/dist/lib/outline-account.js.map +1 -0
- package/dist/lib/skills/content.d.ts +1 -1
- package/dist/lib/skills/content.js +2 -0
- package/dist/lib/skills/content.js.map +1 -1
- package/dist/lib/user-records.d.ts +14 -0
- package/dist/lib/user-records.js +110 -0
- package/dist/lib/user-records.js.map +1 -0
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [1.8.0](https://github.com/Doist/outline-cli/compare/v1.7.0...v1.8.0) (2026-05-21)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* **auth:** silent OAuth token refresh ([#75](https://github.com/Doist/outline-cli/issues/75)) ([83996d5](https://github.com/Doist/outline-cli/commit/83996d56810e28e608d91a981fd3b313dcdeb4d3))
|
|
6
|
+
|
|
7
|
+
## [1.7.0](https://github.com/Doist/outline-cli/compare/v1.6.0...v1.7.0) (2026-05-17)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **auth:** store API tokens in the OS keyring via @doist/cli-core ([#73](https://github.com/Doist/outline-cli/issues/73)) ([b353aea](https://github.com/Doist/outline-cli/commit/b353aeae3cdcc63308ec8de899f08c1dda23f22c))
|
|
12
|
+
|
|
1
13
|
## [1.6.0](https://github.com/Doist/outline-cli/compare/v1.5.3...v1.6.0) (2026-05-15)
|
|
2
14
|
|
|
3
15
|
### Features
|
package/dist/commands/auth.d.ts
CHANGED
|
@@ -1,2 +1,14 @@
|
|
|
1
|
+
import { type TokenStorageResult } from '@doist/cli-core/auth';
|
|
1
2
|
import type { Command } from 'commander';
|
|
3
|
+
/**
|
|
4
|
+
* Surface a `TokenStorageResult` from a save/clear: the human-readable
|
|
5
|
+
* confirmation goes to stdout, any keyring-fallback warning goes to stderr.
|
|
6
|
+
* Pass `isMachineOutput: true` to suppress the stdout confirmation in
|
|
7
|
+
* `--json` / `--ndjson` mode while still routing the warning to stderr.
|
|
8
|
+
*
|
|
9
|
+
* Exported for direct unit testing — the alternative (driving this via
|
|
10
|
+
* mocked cli-core login/logout hooks) would require stubbing the entire
|
|
11
|
+
* store contract just to assert two console calls.
|
|
12
|
+
*/
|
|
13
|
+
export declare function logTokenStorageResult(result: TokenStorageResult, secureStoreMessage: string, isMachineOutput?: boolean): void;
|
|
2
14
|
export declare function registerAuthCommand(program: Command): void;
|
package/dist/commands/auth.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { attachLoginCommand } from '@doist/cli-core/auth';
|
|
1
|
+
import { attachLoginCommand, attachLogoutCommand, attachStatusCommand, } from '@doist/cli-core/auth';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { apiRequest } from '../lib/api.js';
|
|
4
4
|
import { renderError, renderSuccess } from '../lib/auth-pages.js';
|
|
5
|
-
import { createOutlineAuthProvider, createOutlineTokenStore } from '../lib/auth-provider.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { createOutlineAuthProvider, createOutlineTokenStore, getActiveTokenSource, } from '../lib/auth-provider.js';
|
|
6
|
+
import { refreshedTokenForStatus } from '../lib/auth.js';
|
|
7
|
+
import { CliError } from '../lib/errors.js';
|
|
8
8
|
const DEFAULT_OAUTH_CALLBACK_PORT = 54969;
|
|
9
9
|
function resolvePreferredCallbackPort() {
|
|
10
10
|
const raw = process.env.OUTLINE_OAUTH_CALLBACK_PORT?.trim();
|
|
@@ -16,6 +16,24 @@ function resolvePreferredCallbackPort() {
|
|
|
16
16
|
}
|
|
17
17
|
return parsed;
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Surface a `TokenStorageResult` from a save/clear: the human-readable
|
|
21
|
+
* confirmation goes to stdout, any keyring-fallback warning goes to stderr.
|
|
22
|
+
* Pass `isMachineOutput: true` to suppress the stdout confirmation in
|
|
23
|
+
* `--json` / `--ndjson` mode while still routing the warning to stderr.
|
|
24
|
+
*
|
|
25
|
+
* Exported for direct unit testing — the alternative (driving this via
|
|
26
|
+
* mocked cli-core login/logout hooks) would require stubbing the entire
|
|
27
|
+
* store contract just to assert two console calls.
|
|
28
|
+
*/
|
|
29
|
+
export function logTokenStorageResult(result, secureStoreMessage, isMachineOutput = false) {
|
|
30
|
+
if (!isMachineOutput && result.storage === 'secure-store') {
|
|
31
|
+
console.log(chalk.dim(secureStoreMessage));
|
|
32
|
+
}
|
|
33
|
+
if (result.warning) {
|
|
34
|
+
console.error(chalk.yellow('Warning:'), result.warning);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
19
37
|
export function registerAuthCommand(program) {
|
|
20
38
|
const auth = program.command('auth').description('Manage authentication');
|
|
21
39
|
const provider = createOutlineAuthProvider();
|
|
@@ -28,43 +46,91 @@ export function registerAuthCommand(program) {
|
|
|
28
46
|
renderSuccess,
|
|
29
47
|
renderError,
|
|
30
48
|
onSuccess({ view, account }) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
49
|
+
const isMachineOutput = view.json || view.ndjson;
|
|
50
|
+
if (!isMachineOutput) {
|
|
51
|
+
console.log(chalk.green(`Authenticated to ${account.teamName} as ${account.label}`));
|
|
52
|
+
}
|
|
53
|
+
const result = store.getLastStorageResult();
|
|
54
|
+
if (result) {
|
|
55
|
+
logTokenStorageResult(result, 'Token stored securely in the system credential manager', isMachineOutput);
|
|
56
|
+
}
|
|
34
57
|
},
|
|
35
58
|
})
|
|
36
59
|
.description('Authenticate with an Outline instance via OAuth')
|
|
37
60
|
.option('--base-url <url>', 'Outline base URL to use for this login (saved for future logins)')
|
|
38
61
|
.option('--client-id <clientId>', 'OAuth client ID to use for this login (saved for future logins)');
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
// `attachStatusCommand` guarantees `fetchLive` runs before `renderText` /
|
|
63
|
+
// `renderJson` within a single invocation, so the stash is always
|
|
64
|
+
// populated by the time the render hooks read it.
|
|
65
|
+
let statusData = null;
|
|
66
|
+
attachStatusCommand(auth, {
|
|
67
|
+
store,
|
|
68
|
+
description: 'Show current authentication state',
|
|
69
|
+
async fetchLive({ account, token }) {
|
|
70
|
+
try {
|
|
71
|
+
// Refresh the *selected* account before the check (scoped via
|
|
72
|
+
// its id + base URL/client id), then probe with the rotated
|
|
73
|
+
// token. Outline access tokens last ~an hour; without this
|
|
74
|
+
// `auth status` can't self-heal even though normal commands
|
|
75
|
+
// do. Passing the resolved token keeps the check tied to the
|
|
76
|
+
// requested account rather than the default.
|
|
77
|
+
const liveToken = await refreshedTokenForStatus(account, token);
|
|
78
|
+
const [{ data: info }, source] = await Promise.all([
|
|
79
|
+
apiRequest('auth.info', {}, { token: liveToken, baseUrl: account.baseUrl }),
|
|
80
|
+
getActiveTokenSource(),
|
|
81
|
+
]);
|
|
82
|
+
statusData = { email: info.user.email, source };
|
|
83
|
+
return {
|
|
84
|
+
...account,
|
|
85
|
+
id: info.user.id,
|
|
86
|
+
label: info.user.name,
|
|
87
|
+
teamName: info.team.name,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
const message = err instanceof Error ? err.message : '';
|
|
92
|
+
if (/\b401\b/.test(message) || /Authentication required/i.test(message)) {
|
|
93
|
+
throw new CliError('NO_TOKEN', 'Not authenticated (token expired or invalid)', [
|
|
94
|
+
'Run `ol auth login` to re-authenticate',
|
|
95
|
+
]);
|
|
96
|
+
}
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
renderText({ account }) {
|
|
101
|
+
if (!statusData)
|
|
102
|
+
throw new Error('status renderText called before fetchLive');
|
|
103
|
+
return [
|
|
104
|
+
`${chalk.green('✓')} Authenticated`,
|
|
105
|
+
` Team: ${chalk.bold(account.teamName ?? '')}`,
|
|
106
|
+
` User: ${account.label} (${statusData.email})`,
|
|
107
|
+
` Base URL: ${account.baseUrl}`,
|
|
108
|
+
` Token source: ${statusData.source}`,
|
|
109
|
+
];
|
|
110
|
+
},
|
|
111
|
+
renderJson({ account }) {
|
|
112
|
+
if (!statusData)
|
|
113
|
+
throw new Error('status renderJson called before fetchLive');
|
|
114
|
+
return {
|
|
115
|
+
id: account.id,
|
|
116
|
+
team: account.teamName,
|
|
117
|
+
baseUrl: account.baseUrl,
|
|
118
|
+
source: statusData.source,
|
|
119
|
+
};
|
|
120
|
+
},
|
|
121
|
+
onNotAuthenticated() {
|
|
122
|
+
throw new CliError('NOT_AUTHENTICATED', 'Not authenticated. Run: ol auth login');
|
|
123
|
+
},
|
|
62
124
|
});
|
|
63
|
-
auth
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
125
|
+
attachLogoutCommand(auth, {
|
|
126
|
+
store,
|
|
127
|
+
description: 'Clear saved authentication',
|
|
128
|
+
onCleared({ view }) {
|
|
129
|
+
const result = store.getLastClearResult();
|
|
130
|
+
if (!result)
|
|
131
|
+
return;
|
|
132
|
+
logTokenStorageResult(result, 'Stored token removed from the system credential manager', view.json || view.ndjson);
|
|
133
|
+
},
|
|
68
134
|
});
|
|
69
135
|
}
|
|
70
136
|
//# sourceMappingURL=auth.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/api.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { fetchWithRetry } from '../transport/fetch-with-retry.js';
|
|
2
|
-
import {
|
|
2
|
+
import { TOKEN_ENV_VAR } from './auth-constants.js';
|
|
3
|
+
import { getApiToken, getBaseUrl, proactiveRefresh, reactiveRefresh } from './auth.js';
|
|
3
4
|
import { withSpinner } from './spinner.js';
|
|
4
5
|
/**
|
|
5
6
|
* Spinner configuration mapping API paths to spinner options.
|
|
@@ -22,25 +23,52 @@ const API_SPINNER_CONFIG = {
|
|
|
22
23
|
'collections.update': { text: 'Updating collection...', color: 'yellow' },
|
|
23
24
|
'collections.delete': { text: 'Deleting collection...', color: 'yellow' },
|
|
24
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the token for a request. A caller-supplied override is used as-is.
|
|
28
|
+
* On the managed path, prefer the token `proactiveRefresh` resolved (rotated
|
|
29
|
+
* or current) so unrefreshable/access-only accounts stay on a single store
|
|
30
|
+
* read; only fall back to `getApiToken` when proactive refresh bows out.
|
|
31
|
+
*/
|
|
32
|
+
async function resolveRequestToken(managed, override) {
|
|
33
|
+
if (override)
|
|
34
|
+
return override;
|
|
35
|
+
if (managed) {
|
|
36
|
+
const refreshed = await proactiveRefresh();
|
|
37
|
+
if (refreshed)
|
|
38
|
+
return refreshed;
|
|
39
|
+
}
|
|
40
|
+
return getApiToken();
|
|
41
|
+
}
|
|
25
42
|
/**
|
|
26
43
|
* Core API request function without spinner wrapping.
|
|
27
44
|
*/
|
|
28
45
|
async function rawApiRequest(path, body = {}, overrides = {}) {
|
|
46
|
+
// Only stored credentials can be refreshed: a caller-supplied token
|
|
47
|
+
// override or the `OUTLINE_API_TOKEN` env var is taken as-is.
|
|
48
|
+
const managed = !overrides.token && !process.env[TOKEN_ENV_VAR]?.trim();
|
|
49
|
+
// Resolve the base URL and the (proactively refreshed) token in parallel.
|
|
29
50
|
const [resolvedBaseUrl, resolvedToken] = await Promise.all([
|
|
30
51
|
overrides.baseUrl ? Promise.resolve(overrides.baseUrl.replace(/\/$/, '')) : getBaseUrl(),
|
|
31
|
-
|
|
52
|
+
resolveRequestToken(managed, overrides.token),
|
|
32
53
|
]);
|
|
33
|
-
const
|
|
54
|
+
const performRequest = (token) => fetchWithRetry({
|
|
34
55
|
url: `${resolvedBaseUrl}/api/${path}`,
|
|
35
56
|
options: {
|
|
36
57
|
method: 'POST',
|
|
37
58
|
headers: {
|
|
38
59
|
'Content-Type': 'application/json',
|
|
39
|
-
Authorization: `Bearer ${
|
|
60
|
+
Authorization: `Bearer ${token}`,
|
|
40
61
|
},
|
|
41
62
|
body: JSON.stringify(body),
|
|
42
63
|
},
|
|
43
64
|
});
|
|
65
|
+
let res = await performRequest(resolvedToken);
|
|
66
|
+
// Reactive path: a 401 on a managed token triggers a forced rotation and
|
|
67
|
+
// a single retry. `reactiveRefresh` throws `NoTokenError` when the refresh
|
|
68
|
+
// token is gone, so an unrecoverable 401 surfaces the re-login hint.
|
|
69
|
+
if (res.status === 401 && managed && (await reactiveRefresh())) {
|
|
70
|
+
res = await performRequest(await getApiToken());
|
|
71
|
+
}
|
|
44
72
|
if (!res.ok) {
|
|
45
73
|
let message = `API error: ${res.status} ${res.statusText}`;
|
|
46
74
|
try {
|
package/dist/lib/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAA;AACjE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Config } from './config.js';
|
|
2
|
+
/** OS keyring `service` identifier for every outline-cli secret. */
|
|
3
|
+
export declare const SECURE_STORE_SERVICE = "outline-cli";
|
|
4
|
+
/** Env var that short-circuits the token store with a manually-supplied token. */
|
|
5
|
+
export declare const TOKEN_ENV_VAR = "OUTLINE_API_TOKEN";
|
|
6
|
+
/**
|
|
7
|
+
* Legacy single-user keyring slot. Outline never wrote a token to the
|
|
8
|
+
* keyring before this migration, so there is no real legacy slot to read —
|
|
9
|
+
* `migrateLegacyAuth` still expects the option, and its best-effort delete
|
|
10
|
+
* after migration is a harmless no-op on an empty entry.
|
|
11
|
+
*/
|
|
12
|
+
export declare const LEGACY_KEYRING_ACCOUNT = "api-token";
|
|
13
|
+
/**
|
|
14
|
+
* `updateConfig` payload that wipes every v1 auth key. Used by both the
|
|
15
|
+
* one-shot migration `cleanupLegacyConfig` and the runtime
|
|
16
|
+
* `dischargeLegacyState` so both paths stay in lockstep.
|
|
17
|
+
*/
|
|
18
|
+
export declare const LEGACY_CLEAR_PAYLOAD: Partial<Config>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** OS keyring `service` identifier for every outline-cli secret. */
|
|
2
|
+
export const SECURE_STORE_SERVICE = 'outline-cli';
|
|
3
|
+
/** Env var that short-circuits the token store with a manually-supplied token. */
|
|
4
|
+
export const TOKEN_ENV_VAR = 'OUTLINE_API_TOKEN';
|
|
5
|
+
/**
|
|
6
|
+
* Legacy single-user keyring slot. Outline never wrote a token to the
|
|
7
|
+
* keyring before this migration, so there is no real legacy slot to read —
|
|
8
|
+
* `migrateLegacyAuth` still expects the option, and its best-effort delete
|
|
9
|
+
* after migration is a harmless no-op on an empty entry.
|
|
10
|
+
*/
|
|
11
|
+
export const LEGACY_KEYRING_ACCOUNT = 'api-token';
|
|
12
|
+
/**
|
|
13
|
+
* `updateConfig` payload that wipes every v1 auth key. Used by both the
|
|
14
|
+
* one-shot migration `cleanupLegacyConfig` and the runtime
|
|
15
|
+
* `dischargeLegacyState` so both paths stay in lockstep.
|
|
16
|
+
*/
|
|
17
|
+
export const LEGACY_CLEAR_PAYLOAD = {
|
|
18
|
+
api_token: undefined,
|
|
19
|
+
base_url: undefined,
|
|
20
|
+
oauth_client_id: undefined,
|
|
21
|
+
auth_user_id: undefined,
|
|
22
|
+
auth_user_name: undefined,
|
|
23
|
+
auth_team_name: undefined,
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=auth-constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-constants.js","sourceRoot":"","sources":["../../src/lib/auth-constants.ts"],"names":[],"mappings":"AAEA,oEAAoE;AACpE,MAAM,CAAC,MAAM,oBAAoB,GAAG,aAAa,CAAA;AAEjD,kFAAkF;AAClF,MAAM,CAAC,MAAM,aAAa,GAAG,mBAAmB,CAAA;AAEhD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,WAAW,CAAA;AAEjD;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAoB;IACjD,SAAS,EAAE,SAAS;IACpB,QAAQ,EAAE,SAAS;IACnB,eAAe,EAAE,SAAS;IAC1B,YAAY,EAAE,SAAS;IACvB,cAAc,EAAE,SAAS;IACzB,cAAc,EAAE,SAAS;CAC5B,CAAA"}
|
|
@@ -1,10 +1,59 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { type AccountRef, type AuthProvider, type KeyringTokenStore } from '@doist/cli-core/auth';
|
|
2
|
+
import { type OutlineAccount } from './outline-account.js';
|
|
3
|
+
export type { OutlineAccount } from './outline-account.js';
|
|
4
|
+
export type AuthInfoResponse = {
|
|
5
|
+
user: {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
email: string;
|
|
9
|
+
};
|
|
10
|
+
team: {
|
|
11
|
+
name: string;
|
|
12
|
+
subdomain: string;
|
|
13
|
+
};
|
|
8
14
|
};
|
|
15
|
+
export type OutlineTokenStore = KeyringTokenStore<OutlineAccount>;
|
|
9
16
|
export declare function createOutlineAuthProvider(): AuthProvider<OutlineAccount>;
|
|
10
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Accepts the Outline user UUID or display name. Id matches are
|
|
19
|
+
* case-sensitive (UUIDs are canonical); label matches are
|
|
20
|
+
* case-insensitive so users can pass the name they see in `auth status`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function matchOutlineAccount(account: OutlineAccount, ref: AccountRef): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* True when the v2 store is empty but a legacy v1 token snapshot is still
|
|
25
|
+
* the only thing keeping the CLI authenticated — typically because
|
|
26
|
+
* `migrateLegacyAuth` couldn't reach the Outline API to identify the
|
|
27
|
+
* account (`MigrateSkipReason: 'identify-failed'`) or the OS keyring is
|
|
28
|
+
* unreachable (`'legacy-keyring-unreachable'`, although outline has no
|
|
29
|
+
* v1 keyring slot to read from). Useful for downstream diagnostics.
|
|
30
|
+
*/
|
|
31
|
+
export declare function isLegacyAuthActive(): Promise<boolean>;
|
|
32
|
+
/**
|
|
33
|
+
* `OUTLINE_API_TOKEN` short-circuits `active()` only when no explicit ref
|
|
34
|
+
* is supplied — cli-core's `KeyringTokenStore` doesn't know about the env
|
|
35
|
+
* var, and an explicit ref means the caller targets a specific stored
|
|
36
|
+
* account.
|
|
37
|
+
*
|
|
38
|
+
* `ensureMigrated()` runs **before** every mutating v2 op so the post-op
|
|
39
|
+
* legacy discharge can't race a still-pending migration into re-grabbing
|
|
40
|
+
* the legacy `api_token` we just consumed. `set()` / `clear()` then
|
|
41
|
+
* discharge legacy state **after** the v2 op succeeds. `set()` swallows
|
|
42
|
+
* the cleanup failure (v2 wins in `active()` regardless); `clear()`
|
|
43
|
+
* propagates it so a failed logout fails loudly instead of leaving the
|
|
44
|
+
* user authenticated via the legacy fallback.
|
|
45
|
+
*/
|
|
46
|
+
export declare function createOutlineTokenStore(): OutlineTokenStore;
|
|
47
|
+
/**
|
|
48
|
+
* Where the currently-active token lives. Mirrors `active()`'s resolution
|
|
49
|
+
* order — env → v2 record → legacy plaintext — so the answer can never
|
|
50
|
+
* contradict the token the runtime is actually using.
|
|
51
|
+
*
|
|
52
|
+
* The precedence cascade is intentionally duplicated with `active()`:
|
|
53
|
+
* the only true dedupe options either (a) add an extra config read on
|
|
54
|
+
* every `apiRequest` call (regressing the request hot path) or (b)
|
|
55
|
+
* augment cli-core's `TokenStore` contract. The drift the duplication
|
|
56
|
+
* invites is guarded by the `getActiveTokenSource` regression test that
|
|
57
|
+
* asserts v2-record presence wins over a lingering v1 `api_token`.
|
|
58
|
+
*/
|
|
59
|
+
export declare function getActiveTokenSource(): Promise<'env' | 'secure-store' | 'config-file'>;
|