@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/dist/lib/auth.js
CHANGED
|
@@ -1,56 +1,156 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { refreshAccessToken, SecureStoreUnavailableError } from '@doist/cli-core/auth';
|
|
2
|
+
import { TOKEN_ENV_VAR } from './auth-constants.js';
|
|
3
|
+
import { createOutlineAuthProvider, createOutlineTokenStore, getActiveTokenSource, } from './auth-provider.js';
|
|
4
|
+
import { getConfig, getConfigPath } from './config.js';
|
|
5
|
+
import { CliError } from './errors.js';
|
|
6
|
+
import { DEFAULT_BASE_URL } from './outline-account.js';
|
|
7
|
+
import { getDefaultUserRecord } from './user-records.js';
|
|
8
|
+
export { SecureStoreUnavailableError, getActiveTokenSource, TOKEN_ENV_VAR };
|
|
9
|
+
export class NoTokenError extends CliError {
|
|
10
|
+
constructor() {
|
|
11
|
+
super('NO_TOKEN', `No API token found. Set ${TOKEN_ENV_VAR} env var or run: ol auth login`, [`Set ${TOKEN_ENV_VAR} or run: ol auth login`], 'info');
|
|
12
|
+
this.name = 'NoTokenError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Module-level token-store singleton. Built lazily on first call; reused
|
|
17
|
+
* across every `apiRequest` so the request hot path doesn't reconstruct
|
|
18
|
+
* the keyring + user-record adapters per POST.
|
|
19
|
+
*/
|
|
20
|
+
let storeSingleton;
|
|
21
|
+
function tokenStore() {
|
|
22
|
+
if (!storeSingleton)
|
|
23
|
+
storeSingleton = createOutlineTokenStore();
|
|
24
|
+
return storeSingleton;
|
|
25
|
+
}
|
|
26
|
+
let providerSingleton;
|
|
27
|
+
function authProvider() {
|
|
28
|
+
if (!providerSingleton)
|
|
29
|
+
providerSingleton = createOutlineAuthProvider();
|
|
30
|
+
return providerSingleton;
|
|
31
|
+
}
|
|
32
|
+
// Caller-provided O_EXCL lock so concurrent `ol` invocations don't issue
|
|
33
|
+
// parallel refresh grants. Resolved per-call (not cached) to honour test
|
|
34
|
+
// config-path mocking, mirroring `getConfigPath`.
|
|
35
|
+
function refreshLockPath() {
|
|
36
|
+
return `${getConfigPath()}.refresh.lock`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Best-effort proactive rotation before a request. Returns the token to use
|
|
40
|
+
* (the rotated one, or the current one if no rotation was needed) so the
|
|
41
|
+
* caller doesn't re-read the store; `undefined` when the default account
|
|
42
|
+
* isn't refreshable (no refresh token / env / failure) — the caller then
|
|
43
|
+
* falls back to `getApiToken()` and the 401 path stays authoritative.
|
|
44
|
+
*/
|
|
45
|
+
export async function proactiveRefresh() {
|
|
46
|
+
try {
|
|
47
|
+
const { bundle } = await refreshAccessToken({
|
|
48
|
+
store: tokenStore(),
|
|
49
|
+
provider: authProvider(),
|
|
50
|
+
lockPath: refreshLockPath(),
|
|
51
|
+
});
|
|
52
|
+
return bundle.accessToken;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Refresh the *selected* account (not the default) for `auth status`, returning
|
|
60
|
+
* its current-or-rotated token. Scoped via the account's id + base URL/client
|
|
61
|
+
* id through the refresh handshake, so `--user <other>` checks and rotates the
|
|
62
|
+
* right account at the right instance. Env / legacy / record-less accounts
|
|
63
|
+
* aren't refreshable — the snapshot `fallback` token is returned as-is.
|
|
64
|
+
*/
|
|
65
|
+
export async function refreshedTokenForStatus(account, fallback) {
|
|
66
|
+
if (process.env[TOKEN_ENV_VAR]?.trim() || !account.id)
|
|
67
|
+
return fallback;
|
|
68
|
+
try {
|
|
69
|
+
const { bundle } = await refreshAccessToken({
|
|
70
|
+
store: tokenStore(),
|
|
71
|
+
provider: authProvider(),
|
|
72
|
+
lockPath: refreshLockPath(),
|
|
73
|
+
ref: account.id,
|
|
74
|
+
handshake: { baseUrl: account.baseUrl, clientId: account.oauthClientId },
|
|
75
|
+
});
|
|
76
|
+
return bundle.accessToken;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return fallback;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Reactive rotation after a 401. Returns `true` when the token rotated (the
|
|
84
|
+
* caller retries once). A rejected/absent refresh token surfaces as
|
|
85
|
+
* `NoTokenError` (re-login); a transient failure propagates unchanged.
|
|
86
|
+
*/
|
|
87
|
+
export async function reactiveRefresh() {
|
|
88
|
+
try {
|
|
89
|
+
const result = await refreshAccessToken({
|
|
90
|
+
store: tokenStore(),
|
|
91
|
+
provider: authProvider(),
|
|
92
|
+
lockPath: refreshLockPath(),
|
|
93
|
+
force: true,
|
|
94
|
+
});
|
|
95
|
+
return result.rotated;
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
// Match on the structural `.code` rather than `instanceof`: cli-core
|
|
99
|
+
// throws its own CliError, and class identity isn't reliable across
|
|
100
|
+
// package boundaries (duplicate module instances under linking).
|
|
101
|
+
const code = err?.code;
|
|
102
|
+
if (code === 'AUTH_REFRESH_EXPIRED' || code === 'AUTH_REFRESH_UNAVAILABLE') {
|
|
103
|
+
throw new NoTokenError();
|
|
104
|
+
}
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Read the active token. Hot path: when `OUTLINE_API_TOKEN` is set we
|
|
110
|
+
* return it directly without consulting the token store, since
|
|
111
|
+
* `apiRequest` already resolves the base URL separately — going through
|
|
112
|
+
* `store.active()` here would trigger a redundant `getBaseUrl()` lookup
|
|
113
|
+
* per request just to synthesise an account we don't need.
|
|
114
|
+
*/
|
|
3
115
|
export async function getApiToken() {
|
|
4
|
-
const envToken = process.env
|
|
116
|
+
const envToken = process.env[TOKEN_ENV_VAR]?.trim();
|
|
5
117
|
if (envToken)
|
|
6
118
|
return envToken;
|
|
7
|
-
const
|
|
8
|
-
if (
|
|
9
|
-
|
|
10
|
-
|
|
119
|
+
const snapshot = await tokenStore().active();
|
|
120
|
+
if (!snapshot?.token)
|
|
121
|
+
throw new NoTokenError();
|
|
122
|
+
return snapshot.token;
|
|
11
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Base URL cascade: env var → default user record (v2) → legacy
|
|
126
|
+
* `base_url` config (v1) → built-in default. The record takes priority
|
|
127
|
+
* over the legacy slot so post-migration logins keep defaulting to the
|
|
128
|
+
* same Outline instance.
|
|
129
|
+
*/
|
|
12
130
|
export async function getBaseUrl() {
|
|
13
131
|
const envUrl = process.env.OUTLINE_URL;
|
|
14
132
|
if (envUrl)
|
|
15
133
|
return envUrl.replace(/\/$/, '');
|
|
16
134
|
const config = await getConfig();
|
|
135
|
+
const record = getDefaultUserRecord(config);
|
|
136
|
+
if (record?.account.baseUrl)
|
|
137
|
+
return record.account.baseUrl.replace(/\/$/, '');
|
|
17
138
|
if (config.base_url)
|
|
18
139
|
return config.base_url.replace(/\/$/, '');
|
|
19
140
|
return DEFAULT_BASE_URL;
|
|
20
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* OAuth client id cascade: env var → default user record (v2) → legacy
|
|
144
|
+
* `oauth_client_id` config (v1) → undefined (caller prompts).
|
|
145
|
+
*/
|
|
21
146
|
export async function getOAuthClientId() {
|
|
22
147
|
const envClientId = process.env.OUTLINE_OAUTH_CLIENT_ID;
|
|
23
148
|
if (envClientId)
|
|
24
149
|
return envClientId;
|
|
25
150
|
const config = await getConfig();
|
|
151
|
+
const record = getDefaultUserRecord(config);
|
|
152
|
+
if (record?.account.oauthClientId)
|
|
153
|
+
return record.account.oauthClientId;
|
|
26
154
|
return config.oauth_client_id;
|
|
27
155
|
}
|
|
28
|
-
export async function getTokenSource() {
|
|
29
|
-
if (process.env.OUTLINE_API_TOKEN)
|
|
30
|
-
return 'env';
|
|
31
|
-
const config = await getConfig();
|
|
32
|
-
if (config.api_token)
|
|
33
|
-
return 'config';
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Clear the auth-related keys without deleting the file. The config is now
|
|
38
|
-
* shared with non-auth settings (notably `update_channel`); a blanket unlink
|
|
39
|
-
* would silently reset the user's update-channel preference too.
|
|
40
|
-
*
|
|
41
|
-
* Pass `existing` to skip the read when the caller has already loaded the
|
|
42
|
-
* config (e.g. ref-validated logout) — keeps that flow at one read + one
|
|
43
|
-
* write instead of two reads.
|
|
44
|
-
*/
|
|
45
|
-
export async function clearConfig(existing) {
|
|
46
|
-
const config = existing ?? (await getConfig());
|
|
47
|
-
const { api_token, base_url, oauth_client_id, auth_user_id, auth_user_name, auth_team_name, ...rest } = config;
|
|
48
|
-
void api_token;
|
|
49
|
-
void base_url;
|
|
50
|
-
void oauth_client_id;
|
|
51
|
-
void auth_user_id;
|
|
52
|
-
void auth_user_name;
|
|
53
|
-
void auth_team_name;
|
|
54
|
-
await setConfig(rest);
|
|
55
|
-
}
|
|
56
156
|
//# sourceMappingURL=auth.js.map
|
package/dist/lib/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
import { type CoreConfig } from '@doist/cli-core';
|
|
2
|
+
/**
|
|
3
|
+
* One row of the `users[]` array. `id` is the Outline user UUID. `token` is
|
|
4
|
+
* a plaintext fallback persisted only when the OS keyring is unavailable at
|
|
5
|
+
* write time (WSL, headless Linux, missing native binary).
|
|
6
|
+
*/
|
|
7
|
+
export type StoredUser = {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
base_url?: string;
|
|
11
|
+
oauth_client_id?: string;
|
|
12
|
+
team_name?: string;
|
|
13
|
+
/** Plaintext access-token fallback, present only when the keyring was unavailable at write time. */
|
|
14
|
+
token?: string;
|
|
15
|
+
/** Access-token expiry (unix-epoch ms); drives proactive refresh. */
|
|
16
|
+
access_token_expires_at?: number;
|
|
17
|
+
/** Refresh-token expiry (unix-epoch ms), when the server returns one. */
|
|
18
|
+
refresh_token_expires_at?: number;
|
|
19
|
+
/** Whether a refresh token is stored (keyring slot or `refresh_token`). */
|
|
20
|
+
has_refresh_token?: boolean;
|
|
21
|
+
};
|
|
2
22
|
export type Config = CoreConfig & {
|
|
23
|
+
config_version?: number;
|
|
24
|
+
users?: StoredUser[];
|
|
25
|
+
default_user_id?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Legacy v1 single-user fields. Read by the migration helper; removed
|
|
28
|
+
* from disk by `cleanupLegacyConfig` after the v2 record write succeeds.
|
|
29
|
+
*/
|
|
3
30
|
api_token?: string;
|
|
4
31
|
base_url?: string;
|
|
5
32
|
oauth_client_id?: string;
|
package/dist/lib/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,aAAa,IAAI,iBAAiB,EAClC,UAAU,IAAI,cAAc,EAC5B,YAAY,IAAI,gBAAgB,EAChC,WAAW,IAAI,eAAe,GACjC,MAAM,iBAAiB,CAAA;AAExB,MAAM,QAAQ,GAAG,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,aAAa,IAAI,iBAAiB,EAClC,UAAU,IAAI,cAAc,EAC5B,YAAY,IAAI,gBAAgB,EAChC,WAAW,IAAI,eAAe,GACjC,MAAM,iBAAiB,CAAA;AAExB,MAAM,QAAQ,GAAG,aAAa,CAAA;AAwC9B;;;;GAIG;AACH,MAAM,UAAU,aAAa;IACzB,OAAO,iBAAiB,CAAC,QAAQ,CAAC,CAAA;AACtC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS;IAC3B,OAAO,cAAc,CAAS,aAAa,EAAE,CAAC,CAAA;AAClD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,MAAc;IAC1C,OAAO,eAAe,CAAC,aAAa,EAAE,EAAE,MAAM,CAAC,CAAA;AACnD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAwB;IACvD,OAAO,gBAAgB,CAAS,aAAa,EAAE,EAAE,OAAO,CAAC,CAAA;AAC7D,CAAC"}
|
package/dist/lib/errors.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CliError as CoreCliError, type CliErrorCode, type ErrorType } from '@doist/cli-core';
|
|
2
2
|
export { CoreCliError as BaseCliError };
|
|
3
3
|
export type { ErrorType } from '@doist/cli-core';
|
|
4
|
-
export type ErrorCode = 'ACCOUNT_NOT_FOUND' | 'AUTH_VERIFICATION_FAILED' | 'CONFIRMATION_REQUIRED' | 'CONFLICTING_OPTIONS' | 'INVALID_PARENT' | 'MISSING_OPTION' | 'OAUTH_CALLBACK_PORT_INVALID' | 'OAUTH_CALLBACK_SERVER_FAILED' | 'OAUTH_CLIENT_ID_REQUIRED' | 'OAUTH_LOGIN_FAILED' | 'UNKNOWN_AGENT';
|
|
4
|
+
export type ErrorCode = 'ACCOUNT_NOT_FOUND' | 'AUTH_VERIFICATION_FAILED' | 'CONFIRMATION_REQUIRED' | 'CONFLICTING_OPTIONS' | 'INVALID_PARENT' | 'MISSING_OPTION' | 'NO_TOKEN' | 'OAUTH_CALLBACK_PORT_INVALID' | 'OAUTH_CALLBACK_SERVER_FAILED' | 'OAUTH_CLIENT_ID_REQUIRED' | 'OAUTH_LOGIN_FAILED' | 'UNKNOWN_AGENT';
|
|
5
5
|
export declare class CliError extends CoreCliError<ErrorCode> {
|
|
6
6
|
constructor(code: ErrorCode | CliErrorCode, message: string, hints?: string[], type?: ErrorType);
|
|
7
7
|
}
|
package/dist/lib/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,YAAY,EAAqC,MAAM,iBAAiB,CAAA;AAE7F,OAAO,EAAE,YAAY,IAAI,YAAY,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,YAAY,EAAqC,MAAM,iBAAiB,CAAA;AAE7F,OAAO,EAAE,YAAY,IAAI,YAAY,EAAE,CAAA;AAiBvC,MAAM,OAAO,QAAS,SAAQ,YAAuB;IACjD,YACI,IAA8B,EAC9B,OAAe,EACf,KAAgB,EAChB,OAAkB,OAAO;QAEzB,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IACzC,CAAC;CACJ;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type MigrateAuthResult } from '@doist/cli-core/auth';
|
|
2
|
+
import { type OutlineAccount } from './outline-account.js';
|
|
3
|
+
/**
|
|
4
|
+
* One-time migration of v1 plaintext auth state into the v2 `users[]`
|
|
5
|
+
* shape. Called lazily by `createOutlineTokenStore` on first store access.
|
|
6
|
+
* Idempotent via the `config_version` marker — once set to `>= 2`, this
|
|
7
|
+
* helper short-circuits with `already-migrated`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function runMigrateLegacyAuth(options?: {
|
|
10
|
+
silent: boolean;
|
|
11
|
+
}): Promise<MigrateAuthResult<OutlineAccount>>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { migrateLegacyAuth } from '@doist/cli-core/auth';
|
|
2
|
+
import { fetchWithRetry } from '../transport/fetch-with-retry.js';
|
|
3
|
+
import { LEGACY_CLEAR_PAYLOAD, LEGACY_KEYRING_ACCOUNT, SECURE_STORE_SERVICE, } from './auth-constants.js';
|
|
4
|
+
import { getConfig, updateConfig } from './config.js';
|
|
5
|
+
import { DEFAULT_BASE_URL, makeOutlineAccount } from './outline-account.js';
|
|
6
|
+
import { createOutlineUserRecordStore } from './user-records.js';
|
|
7
|
+
/**
|
|
8
|
+
* Pinned to this migration's target schema. Decoupled from any future
|
|
9
|
+
* `config_version` bump so this helper doesn't re-run for users already
|
|
10
|
+
* on v2 or beyond.
|
|
11
|
+
*/
|
|
12
|
+
const V2_SCHEMA_VERSION = 2;
|
|
13
|
+
/**
|
|
14
|
+
* Cap migration's `auth.info` lookup so a stalled connection can't hang
|
|
15
|
+
* the CLI indefinitely on first command. cli-core treats the resulting
|
|
16
|
+
* timeout as `identify-failed`, falling back to the legacy snapshot.
|
|
17
|
+
*/
|
|
18
|
+
const IDENTIFY_TIMEOUT_MS = 10_000;
|
|
19
|
+
/**
|
|
20
|
+
* Direct `auth.info` call used only by the migration path. We bypass
|
|
21
|
+
* `apiRequest` (and its spinner + `getApiToken`/`getBaseUrl` cascade) so
|
|
22
|
+
* this module stays out of the runtime auth/token-store import graph and
|
|
23
|
+
* so the silent migration doesn't render a spinner during postinstall-style
|
|
24
|
+
* invocations.
|
|
25
|
+
*/
|
|
26
|
+
async function identifyOutlineAccount(token) {
|
|
27
|
+
const config = await getConfig();
|
|
28
|
+
const baseUrl = (config.base_url ?? DEFAULT_BASE_URL).replace(/\/$/, '');
|
|
29
|
+
const res = await fetchWithRetry({
|
|
30
|
+
url: `${baseUrl}/api/auth.info`,
|
|
31
|
+
options: {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: {
|
|
34
|
+
'Content-Type': 'application/json',
|
|
35
|
+
Authorization: `Bearer ${token}`,
|
|
36
|
+
},
|
|
37
|
+
body: JSON.stringify({}),
|
|
38
|
+
timeout: IDENTIFY_TIMEOUT_MS,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
throw new Error(`auth.info failed: ${res.status} ${res.statusText}`);
|
|
43
|
+
}
|
|
44
|
+
const json = await res.json();
|
|
45
|
+
return makeOutlineAccount({
|
|
46
|
+
id: json.data.user.id,
|
|
47
|
+
label: json.data.user.name,
|
|
48
|
+
baseUrl,
|
|
49
|
+
oauthClientId: config.oauth_client_id ?? '',
|
|
50
|
+
teamName: json.data.team.name,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* One-time migration of v1 plaintext auth state into the v2 `users[]`
|
|
55
|
+
* shape. Called lazily by `createOutlineTokenStore` on first store access.
|
|
56
|
+
* Idempotent via the `config_version` marker — once set to `>= 2`, this
|
|
57
|
+
* helper short-circuits with `already-migrated`.
|
|
58
|
+
*/
|
|
59
|
+
export async function runMigrateLegacyAuth(options = { silent: true }) {
|
|
60
|
+
return migrateLegacyAuth({
|
|
61
|
+
serviceName: SECURE_STORE_SERVICE,
|
|
62
|
+
legacyAccount: LEGACY_KEYRING_ACCOUNT,
|
|
63
|
+
userRecords: createOutlineUserRecordStore(),
|
|
64
|
+
hasMigrated: async () => {
|
|
65
|
+
const config = await getConfig();
|
|
66
|
+
// Two signals of "already on v2": the explicit marker, or
|
|
67
|
+
// the structural presence of `users[]` (a fresh install that
|
|
68
|
+
// logged in directly via cli-core writes records without ever
|
|
69
|
+
// setting `config_version`, so the marker alone would
|
|
70
|
+
// miscount that case as "needs migration").
|
|
71
|
+
if ((config.config_version ?? 0) >= V2_SCHEMA_VERSION)
|
|
72
|
+
return true;
|
|
73
|
+
if ((config.users ?? []).length > 0)
|
|
74
|
+
return true;
|
|
75
|
+
return false;
|
|
76
|
+
},
|
|
77
|
+
markMigrated: async () => {
|
|
78
|
+
await updateConfig({ config_version: V2_SCHEMA_VERSION });
|
|
79
|
+
},
|
|
80
|
+
loadLegacyPlaintextToken: async () => {
|
|
81
|
+
const config = await getConfig();
|
|
82
|
+
return config.api_token?.trim() || null;
|
|
83
|
+
},
|
|
84
|
+
identifyAccount: identifyOutlineAccount,
|
|
85
|
+
cleanupLegacyConfig: async () => {
|
|
86
|
+
await updateConfig(LEGACY_CLEAR_PAYLOAD);
|
|
87
|
+
},
|
|
88
|
+
silent: options.silent,
|
|
89
|
+
logPrefix: 'outline-cli',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=migrate-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate-auth.js","sourceRoot":"","sources":["../../src/lib/migrate-auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AuthAccount } from '@doist/cli-core/auth';
|
|
2
|
+
/**
|
|
3
|
+
* Narrow account shape persisted by the keyring-backed token store.
|
|
4
|
+
*
|
|
5
|
+
* `id` is the Outline user UUID; `label` is the user's display name.
|
|
6
|
+
* `baseUrl` and `oauthClientId` ride along on the account because Outline
|
|
7
|
+
* is self-hostable — every persisted token is tied to the instance it was
|
|
8
|
+
* issued for, not to a global default. `teamName` is rendered by
|
|
9
|
+
* `ol auth status`; it's the only field we cache for display.
|
|
10
|
+
*/
|
|
11
|
+
export type OutlineAccount = AuthAccount & {
|
|
12
|
+
id: string;
|
|
13
|
+
label: string;
|
|
14
|
+
baseUrl: string;
|
|
15
|
+
oauthClientId: string;
|
|
16
|
+
teamName?: string;
|
|
17
|
+
};
|
|
18
|
+
export declare const DEFAULT_BASE_URL = "https://app.getoutline.com";
|
|
19
|
+
/** Canonical `OutlineAccount` factory. Applies the `baseUrl` default. */
|
|
20
|
+
export declare function makeOutlineAccount(input: {
|
|
21
|
+
id: string;
|
|
22
|
+
label: string;
|
|
23
|
+
baseUrl?: string;
|
|
24
|
+
oauthClientId?: string;
|
|
25
|
+
teamName?: string;
|
|
26
|
+
}): OutlineAccount;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const DEFAULT_BASE_URL = 'https://app.getoutline.com';
|
|
2
|
+
/** Canonical `OutlineAccount` factory. Applies the `baseUrl` default. */
|
|
3
|
+
export function makeOutlineAccount(input) {
|
|
4
|
+
return {
|
|
5
|
+
id: input.id,
|
|
6
|
+
label: input.label,
|
|
7
|
+
baseUrl: input.baseUrl ?? DEFAULT_BASE_URL,
|
|
8
|
+
oauthClientId: input.oauthClientId ?? '',
|
|
9
|
+
teamName: input.teamName,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=outline-account.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outline-account.js","sourceRoot":"","sources":["../../src/lib/outline-account.ts"],"names":[],"mappings":"AAmBA,MAAM,CAAC,MAAM,gBAAgB,GAAG,4BAA4B,CAAA;AAE5D,yEAAyE;AACzE,MAAM,UAAU,kBAAkB,CAAC,KAMlC;IACG,OAAO;QACH,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,gBAAgB;QAC1C,aAAa,EAAE,KAAK,CAAC,aAAa,IAAI,EAAE;QACxC,QAAQ,EAAE,KAAK,CAAC,QAAQ;KAC3B,CAAA;AACL,CAAC"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare const SKILL_NAME = "outline-cli";
|
|
2
2
|
export declare const SKILL_DESCRIPTION = "Search and manage Outline wiki documents and collections via the ol CLI";
|
|
3
|
-
export declare const SKILL_CONTENT = "# Outline CLI (ol)\n\nUse this skill when the user wants to interact with their Outline wiki/knowledge base.\n\n## Quick Reference\n\n- `ol search \"query\"` - Search documents\n- `ol doc list` - List documents\n- `ol doc get <id>` - Read a document\n- `ol doc open <id>` - Open document in browser\n- `ol doc create --title \"Title\" --collection <id>` - Create document\n- `ol col list` - List collections\n\n## Output Formats\n\nAll list commands support:\n- `--json` - JSON output (essential fields)\n- `--ndjson` - Newline-delimited JSON (streaming)\n- `--full` - Include all fields in JSON\n\n## Document References\n\nDocuments can be referenced by:\n- URL ID (the slug suffix after the last hyphen)\n- Full Outline URL (auto-extracted)\n- Document ID\n\n## Commands\n\n### Search\n```bash\nol search \"query\"\nol search \"query\" --limit 10\nol search \"query\" --collection <id>\nol search \"query\" --status published\nol search \"query\" --json\n```\n\n### Documents\n```bash\nol doc list --collection <id> --limit 25\nol doc list --sort title --direction ASC\nol doc get <id> # Rendered markdown\nol doc get <id> --raw # Raw markdown\nol doc get <id> --json\nol doc open <id> # Open in browser\nol doc create --title \"Title\" --collection <id> --text \"# Content\"\nol doc create --title \"Title\" --parent <ref> --text \"# Content\" # Nest under parent (collection inferred)\nol doc create --title \"Title\" --collection <id> --file ./doc.md\nol doc update <id> --title \"New Title\"\nol doc update <id> --file ./updated.md\nol doc move <id> --collection <target-id> # Move to collection root\nol doc move <id> --parent <ref> # Nest under parent (collection inferred)\nol doc archive <id>\nol doc unarchive <id>\nol doc delete <id> --confirm\n```\n\n### Collections\n```bash\nol col list\nol col get <id>\nol col create --name \"Name\" --description \"Desc\" --color \"#hex\"\nol col create --name \"Private\" --private\nol col update <id> --name \"New Name\"\nol col delete <id> --confirm\n```\n\n### Authentication\n```bash\nol auth login # OAuth login (opens browser); prompts for base URL + client ID if needed\nol auth login --base-url <url> # Specify Outline base URL for this login (saved for future use)\nol auth login --client-id <id> # Specify OAuth client ID for this login (saved for future use)\nol auth login --callback-port <port> # Override local OAuth callback port\nol auth login --read-only # Request read-only scopes (where supported by the Outline instance)\nol auth login --json | --ndjson # Machine-readable success envelope\nol auth status # Show current auth state\nol auth logout # Clear saved credentials\n```\n\n### Update & Changelog\n```bash\nol update # Update CLI to latest version\nol update --check # Check for updates without installing, show channel\nol update --check --json # Check for updates as a JSON envelope\nol update --check --ndjson # Check for updates as NDJSON output\nol update --channel # Show current update channel\nol update switch --stable # Switch to stable release channel\nol update switch --pre-release # Switch to pre-release (next) channel\nol changelog # Show recent changelog entries\nol changelog -n 3 # Show last 3 versions\n```\n\n## Examples\n\n### Find and read a document\n```bash\nol search \"onboarding\" --json | jq '.[0].document.urlId'\nol doc get <urlId>\n```\n\n### Create a document from a file\n```bash\nol doc create --title \"Meeting Notes\" --collection <id> --file ./notes.md --publish\n```\n\n### List all collections and their documents\n```bash\nol col list --json\nol doc list --collection <id> --sort title --direction ASC\n```\n\n### Bulk export with ndjson\n```bash\nol doc list --ndjson --full | jq -r '.title'\n```\n";
|
|
3
|
+
export declare const SKILL_CONTENT = "# Outline CLI (ol)\n\nUse this skill when the user wants to interact with their Outline wiki/knowledge base.\n\n## Quick Reference\n\n- `ol search \"query\"` - Search documents\n- `ol doc list` - List documents\n- `ol doc get <id>` - Read a document\n- `ol doc open <id>` - Open document in browser\n- `ol doc create --title \"Title\" --collection <id>` - Create document\n- `ol col list` - List collections\n\n## Output Formats\n\nAll list commands support:\n- `--json` - JSON output (essential fields)\n- `--ndjson` - Newline-delimited JSON (streaming)\n- `--full` - Include all fields in JSON\n\n## Document References\n\nDocuments can be referenced by:\n- URL ID (the slug suffix after the last hyphen)\n- Full Outline URL (auto-extracted)\n- Document ID\n\n## Commands\n\n### Search\n```bash\nol search \"query\"\nol search \"query\" --limit 10\nol search \"query\" --collection <id>\nol search \"query\" --status published\nol search \"query\" --json\n```\n\n### Documents\n```bash\nol doc list --collection <id> --limit 25\nol doc list --sort title --direction ASC\nol doc get <id> # Rendered markdown\nol doc get <id> --raw # Raw markdown\nol doc get <id> --json\nol doc open <id> # Open in browser\nol doc create --title \"Title\" --collection <id> --text \"# Content\"\nol doc create --title \"Title\" --parent <ref> --text \"# Content\" # Nest under parent (collection inferred)\nol doc create --title \"Title\" --collection <id> --file ./doc.md\nol doc update <id> --title \"New Title\"\nol doc update <id> --file ./updated.md\nol doc move <id> --collection <target-id> # Move to collection root\nol doc move <id> --parent <ref> # Nest under parent (collection inferred)\nol doc archive <id>\nol doc unarchive <id>\nol doc delete <id> --confirm\n```\n\n### Collections\n```bash\nol col list\nol col get <id>\nol col create --name \"Name\" --description \"Desc\" --color \"#hex\"\nol col create --name \"Private\" --private\nol col update <id> --name \"New Name\"\nol col delete <id> --confirm\n```\n\n### Authentication\n```bash\nol auth login # OAuth login (opens browser); prompts for base URL + client ID if needed\nol auth login --base-url <url> # Specify Outline base URL for this login (saved for future use)\nol auth login --client-id <id> # Specify OAuth client ID for this login (saved for future use)\nol auth login --callback-port <port> # Override local OAuth callback port\nol auth login --read-only # Request read-only scopes (where supported by the Outline instance)\nol auth login --json | --ndjson # Machine-readable success envelope\nol auth status # Show current auth state\nol auth status --json | --ndjson # Machine-readable status envelope ({id, team, baseUrl, source})\nol auth logout # Clear saved credentials\nol auth logout --json | --ndjson # Machine-readable logout envelope ({ok: true}; --ndjson is silent)\n```\n\n### Update & Changelog\n```bash\nol update # Update CLI to latest version\nol update --check # Check for updates without installing, show channel\nol update --check --json # Check for updates as a JSON envelope\nol update --check --ndjson # Check for updates as NDJSON output\nol update --channel # Show current update channel\nol update switch --stable # Switch to stable release channel\nol update switch --pre-release # Switch to pre-release (next) channel\nol changelog # Show recent changelog entries\nol changelog -n 3 # Show last 3 versions\n```\n\n## Examples\n\n### Find and read a document\n```bash\nol search \"onboarding\" --json | jq '.[0].document.urlId'\nol doc get <urlId>\n```\n\n### Create a document from a file\n```bash\nol doc create --title \"Meeting Notes\" --collection <id> --file ./notes.md --publish\n```\n\n### List all collections and their documents\n```bash\nol col list --json\nol doc list --collection <id> --sort title --direction ASC\n```\n\n### Bulk export with ndjson\n```bash\nol doc list --ndjson --full | jq -r '.title'\n```\n";
|
|
@@ -77,7 +77,9 @@ ol auth login --callback-port <port> # Override local OAuth callback port
|
|
|
77
77
|
ol auth login --read-only # Request read-only scopes (where supported by the Outline instance)
|
|
78
78
|
ol auth login --json | --ndjson # Machine-readable success envelope
|
|
79
79
|
ol auth status # Show current auth state
|
|
80
|
+
ol auth status --json | --ndjson # Machine-readable status envelope ({id, team, baseUrl, source})
|
|
80
81
|
ol auth logout # Clear saved credentials
|
|
82
|
+
ol auth logout --json | --ndjson # Machine-readable logout envelope ({ok: true}; --ndjson is silent)
|
|
81
83
|
\`\`\`
|
|
82
84
|
|
|
83
85
|
### Update & Changelog
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content.js","sourceRoot":"","sources":["../../../src/lib/skills/content.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,UAAU,GAAG,aAAa,CAAA;AACvC,MAAM,CAAC,MAAM,iBAAiB,GAC1B,yEAAyE,CAAA;AAE7E,MAAM,CAAC,MAAM,aAAa,GAAG
|
|
1
|
+
{"version":3,"file":"content.js","sourceRoot":"","sources":["../../../src/lib/skills/content.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,UAAU,GAAG,aAAa,CAAA;AACvC,MAAM,CAAC,MAAM,iBAAiB,GAC1B,yEAAyE,CAAA;AAE7E,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsH5B,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { UserRecord, UserRecordStore } from '@doist/cli-core/auth';
|
|
2
|
+
import { type Config } from './config.js';
|
|
3
|
+
import { type OutlineAccount } from './outline-account.js';
|
|
4
|
+
/**
|
|
5
|
+
* `UserRecordStore` adapter over `config.users[]`. `StoredUser.token` is
|
|
6
|
+
* surfaced as `fallbackToken` — cli-core's name for the plaintext copy
|
|
7
|
+
* persisted only when the keyring is unreachable.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createOutlineUserRecordStore(): UserRecordStore<OutlineAccount>;
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the default-or-first `UserRecord` from an already-loaded config.
|
|
12
|
+
* Returns `null` when no users are stored.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getDefaultUserRecord(config: Partial<Config>): UserRecord<OutlineAccount> | null;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { getConfig, updateConfig } from './config.js';
|
|
2
|
+
import { makeOutlineAccount } from './outline-account.js';
|
|
3
|
+
/**
|
|
4
|
+
* `UserRecordStore` adapter over `config.users[]`. `StoredUser.token` is
|
|
5
|
+
* surfaced as `fallbackToken` — cli-core's name for the plaintext copy
|
|
6
|
+
* persisted only when the keyring is unreachable.
|
|
7
|
+
*/
|
|
8
|
+
export function createOutlineUserRecordStore() {
|
|
9
|
+
return {
|
|
10
|
+
async list() {
|
|
11
|
+
const config = await getConfig();
|
|
12
|
+
return (config.users ?? []).map(toRecord);
|
|
13
|
+
},
|
|
14
|
+
async upsert(record) {
|
|
15
|
+
const config = await getConfig();
|
|
16
|
+
const existing = config.users ?? [];
|
|
17
|
+
const next = fromRecord(record);
|
|
18
|
+
const index = existing.findIndex((u) => u.id === record.account.id);
|
|
19
|
+
const users = index >= 0
|
|
20
|
+
? [...existing.slice(0, index), next, ...existing.slice(index + 1)]
|
|
21
|
+
: [...existing, next];
|
|
22
|
+
await updateConfig({ users });
|
|
23
|
+
},
|
|
24
|
+
async remove(id) {
|
|
25
|
+
const config = await getConfig();
|
|
26
|
+
const existing = config.users ?? [];
|
|
27
|
+
const index = existing.findIndex((u) => u.id === id);
|
|
28
|
+
if (index < 0)
|
|
29
|
+
return;
|
|
30
|
+
const users = [...existing.slice(0, index), ...existing.slice(index + 1)];
|
|
31
|
+
const updates = { users };
|
|
32
|
+
if (config.default_user_id === id)
|
|
33
|
+
updates.default_user_id = undefined;
|
|
34
|
+
await updateConfig(updates);
|
|
35
|
+
},
|
|
36
|
+
async getDefaultId() {
|
|
37
|
+
const config = await getConfig();
|
|
38
|
+
return config.default_user_id ?? null;
|
|
39
|
+
},
|
|
40
|
+
async setDefaultId(id) {
|
|
41
|
+
await updateConfig({ default_user_id: id ?? undefined });
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the default-or-first `UserRecord` from an already-loaded config.
|
|
47
|
+
* Returns `null` when no users are stored.
|
|
48
|
+
*/
|
|
49
|
+
export function getDefaultUserRecord(config) {
|
|
50
|
+
const users = config.users ?? [];
|
|
51
|
+
if (users.length === 0)
|
|
52
|
+
return null;
|
|
53
|
+
const defaultId = config.default_user_id;
|
|
54
|
+
const user = (defaultId && users.find((u) => u.id === defaultId)) || users[0];
|
|
55
|
+
return toRecord(user);
|
|
56
|
+
}
|
|
57
|
+
function toRecord(user) {
|
|
58
|
+
const account = makeOutlineAccount({
|
|
59
|
+
id: user.id,
|
|
60
|
+
label: user.name,
|
|
61
|
+
baseUrl: user.base_url,
|
|
62
|
+
oauthClientId: user.oauth_client_id,
|
|
63
|
+
teamName: user.team_name,
|
|
64
|
+
});
|
|
65
|
+
const record = { account };
|
|
66
|
+
const token = user.token?.trim();
|
|
67
|
+
if (token)
|
|
68
|
+
record.fallbackToken = token;
|
|
69
|
+
if (user.access_token_expires_at !== undefined) {
|
|
70
|
+
record.accessTokenExpiresAt = user.access_token_expires_at;
|
|
71
|
+
}
|
|
72
|
+
if (user.refresh_token_expires_at !== undefined) {
|
|
73
|
+
record.refreshTokenExpiresAt = user.refresh_token_expires_at;
|
|
74
|
+
}
|
|
75
|
+
if (user.has_refresh_token !== undefined)
|
|
76
|
+
record.hasRefreshToken = user.has_refresh_token;
|
|
77
|
+
return record;
|
|
78
|
+
}
|
|
79
|
+
function fromRecord(record) {
|
|
80
|
+
// Replace, don't merge: absent fields strip the corresponding slots so a
|
|
81
|
+
// stale value can't shadow a fresh keyring-backed write. cli-core contract.
|
|
82
|
+
const next = {
|
|
83
|
+
id: record.account.id,
|
|
84
|
+
name: record.account.label,
|
|
85
|
+
};
|
|
86
|
+
if (record.account.baseUrl)
|
|
87
|
+
next.base_url = record.account.baseUrl;
|
|
88
|
+
if (record.account.oauthClientId)
|
|
89
|
+
next.oauth_client_id = record.account.oauthClientId;
|
|
90
|
+
if (record.account.teamName)
|
|
91
|
+
next.team_name = record.account.teamName;
|
|
92
|
+
const token = record.fallbackToken?.trim();
|
|
93
|
+
if (token)
|
|
94
|
+
next.token = token;
|
|
95
|
+
// Deliberately NOT persisting `fallbackRefreshToken`: the refresh token is
|
|
96
|
+
// a long-lived credential and must stay in the secure store only. If the
|
|
97
|
+
// keyring is offline at write time, the refresh token isn't persisted, so
|
|
98
|
+
// that account fails closed (re-auth on next expiry) rather than leaving a
|
|
99
|
+
// long-lived secret in plaintext config. (Doist secrets-management standard.)
|
|
100
|
+
if (record.accessTokenExpiresAt !== undefined) {
|
|
101
|
+
next.access_token_expires_at = record.accessTokenExpiresAt;
|
|
102
|
+
}
|
|
103
|
+
if (record.refreshTokenExpiresAt !== undefined) {
|
|
104
|
+
next.refresh_token_expires_at = record.refreshTokenExpiresAt;
|
|
105
|
+
}
|
|
106
|
+
if (record.hasRefreshToken !== undefined)
|
|
107
|
+
next.has_refresh_token = record.hasRefreshToken;
|
|
108
|
+
return next;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=user-records.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-records.js","sourceRoot":"","sources":["../../src/lib/user-records.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doist/outline-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "CLI for the Outline wiki/knowledge base API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -48,11 +48,12 @@
|
|
|
48
48
|
"node": ">=20.18.1"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@doist/cli-core": "0.
|
|
51
|
+
"@doist/cli-core": "0.19.0",
|
|
52
52
|
"chalk": "5.6.2",
|
|
53
53
|
"commander": "14.0.2",
|
|
54
54
|
"marked": "18.0.3",
|
|
55
55
|
"marked-terminal-renderer": "2.2.0",
|
|
56
|
+
"oauth4webapi": "3.8.6",
|
|
56
57
|
"open": "10.2.0",
|
|
57
58
|
"undici": "7.22.0"
|
|
58
59
|
},
|