@todoist/outline-cli 0.0.0-stage → 3.0.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 +359 -0
- package/README.md +160 -2
- package/dist/commands/account.d.ts +2 -0
- package/dist/commands/account.js +107 -0
- package/dist/commands/account.js.map +1 -0
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.js +169 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/changelog.d.ts +2 -0
- package/dist/commands/changelog.js +15 -0
- package/dist/commands/changelog.js.map +1 -0
- package/dist/commands/collection.d.ts +2 -0
- package/dist/commands/collection.js +106 -0
- package/dist/commands/collection.js.map +1 -0
- package/dist/commands/document.d.ts +2 -0
- package/dist/commands/document.js +291 -0
- package/dist/commands/document.js.map +1 -0
- package/dist/commands/search.d.ts +2 -0
- package/dist/commands/search.js +49 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/skill.d.ts +2 -0
- package/dist/commands/skill.js +80 -0
- package/dist/commands/skill.js.map +1 -0
- package/dist/commands/update/index.d.ts +2 -0
- package/dist/commands/update/index.js +14 -0
- package/dist/commands/update/index.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api.d.ts +18 -0
- package/dist/lib/api.js +107 -0
- package/dist/lib/api.js.map +1 -0
- 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-output.d.ts +21 -0
- package/dist/lib/auth-output.js +38 -0
- package/dist/lib/auth-output.js.map +1 -0
- package/dist/lib/auth-pages.d.ts +2 -0
- package/dist/lib/auth-pages.js +101 -0
- package/dist/lib/auth-pages.js.map +1 -0
- package/dist/lib/auth-provider.d.ts +85 -0
- package/dist/lib/auth-provider.js +363 -0
- package/dist/lib/auth-provider.js.map +1 -0
- package/dist/lib/auth.d.ts +73 -0
- package/dist/lib/auth.js +188 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/config.d.ts +45 -0
- package/dist/lib/config.js +20 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/errors.d.ts +8 -0
- package/dist/lib/errors.js +9 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/global-args.d.ts +30 -0
- package/dist/lib/global-args.js +66 -0
- package/dist/lib/global-args.js.map +1 -0
- package/dist/lib/markdown.d.ts +2 -0
- package/dist/lib/markdown.js +12 -0
- package/dist/lib/markdown.js.map +1 -0
- 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 +32 -0
- package/dist/lib/outline-account.js +22 -0
- package/dist/lib/outline-account.js.map +1 -0
- package/dist/lib/output.d.ts +15 -0
- package/dist/lib/output.js +85 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/lib/refs.d.ts +33 -0
- package/dist/lib/refs.js +182 -0
- package/dist/lib/refs.js.map +1 -0
- package/dist/lib/skills/content.d.ts +3 -0
- package/dist/lib/skills/content.js +160 -0
- package/dist/lib/skills/content.js.map +1 -0
- package/dist/lib/skills/create-installer.d.ts +9 -0
- package/dist/lib/skills/create-installer.js +77 -0
- package/dist/lib/skills/create-installer.js.map +1 -0
- package/dist/lib/skills/index.d.ts +5 -0
- package/dist/lib/skills/index.js +40 -0
- package/dist/lib/skills/index.js.map +1 -0
- package/dist/lib/skills/types.d.ts +10 -0
- package/dist/lib/skills/types.js +2 -0
- package/dist/lib/skills/types.js.map +1 -0
- package/dist/lib/skills/update-installed.d.ts +6 -0
- package/dist/lib/skills/update-installed.js +23 -0
- package/dist/lib/skills/update-installed.js.map +1 -0
- package/dist/lib/spinner.d.ts +2 -0
- package/dist/lib/spinner.js +5 -0
- package/dist/lib/spinner.js.map +1 -0
- package/dist/lib/user-records.d.ts +21 -0
- package/dist/lib/user-records.js +126 -0
- package/dist/lib/user-records.js.map +1 -0
- package/dist/lib/user-ref-store.d.ts +16 -0
- package/dist/lib/user-ref-store.js +37 -0
- package/dist/lib/user-ref-store.js.map +1 -0
- package/dist/postinstall.d.ts +1 -0
- package/dist/postinstall.js +16 -0
- package/dist/postinstall.js.map +1 -0
- package/dist/transport/fetch-with-retry.d.ts +15 -0
- package/dist/transport/fetch-with-retry.js +144 -0
- package/dist/transport/fetch-with-retry.js.map +1 -0
- package/dist/transport/http-dispatcher.d.ts +37 -0
- package/dist/transport/http-dispatcher.js +95 -0
- package/dist/transport/http-dispatcher.js.map +1 -0
- package/package.json +72 -5
- package/scripts/check-skill-sync.js +40 -0
- package/scripts/postinstall.js +12 -0
- package/scripts/sync-skill.js +30 -0
package/dist/lib/auth.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { refreshAccessToken, SecureStoreUnavailableError, } from '@todoist/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 { getRequestedUserRef } from './global-args.js';
|
|
7
|
+
import { DEFAULT_BASE_URL } from './outline-account.js';
|
|
8
|
+
import { getDefaultUserRecord, recordForRef } from './user-records.js';
|
|
9
|
+
import { withUserRefAware } from './user-ref-store.js';
|
|
10
|
+
export { SecureStoreUnavailableError, getActiveTokenSource, TOKEN_ENV_VAR };
|
|
11
|
+
export class NoTokenError extends CliError {
|
|
12
|
+
constructor() {
|
|
13
|
+
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');
|
|
14
|
+
this.name = 'NoTokenError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Module-level token-store singleton. Built lazily on first call; reused
|
|
19
|
+
* across every `apiRequest` so the request hot path doesn't reconstruct
|
|
20
|
+
* the keyring + user-record adapters per POST.
|
|
21
|
+
*/
|
|
22
|
+
let storeSingleton;
|
|
23
|
+
function tokenStore() {
|
|
24
|
+
// Wrapped so a global `--user <ref>` flows into every no-ref store read on
|
|
25
|
+
// the request hot path (token lookup + refresh). Without `--user`,
|
|
26
|
+
// `getRequestedUserRef()` is undefined and the wrap is a pass-through.
|
|
27
|
+
if (!storeSingleton)
|
|
28
|
+
storeSingleton = withUserRefAware(createOutlineTokenStore());
|
|
29
|
+
return storeSingleton;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The `--user` ref to apply on the request path, or `undefined` when an
|
|
33
|
+
* `OUTLINE_API_TOKEN` is set — an env token wins outright, so base URL and
|
|
34
|
+
* client id resolve from the default account rather than the named one,
|
|
35
|
+
* keeping token / base URL / client id pinned to the same source.
|
|
36
|
+
*/
|
|
37
|
+
function requestPathUserRef() {
|
|
38
|
+
if (process.env[TOKEN_ENV_VAR]?.trim())
|
|
39
|
+
return undefined;
|
|
40
|
+
return getRequestedUserRef();
|
|
41
|
+
}
|
|
42
|
+
let providerSingleton;
|
|
43
|
+
function authProvider() {
|
|
44
|
+
if (!providerSingleton)
|
|
45
|
+
providerSingleton = createOutlineAuthProvider();
|
|
46
|
+
return providerSingleton;
|
|
47
|
+
}
|
|
48
|
+
// Caller-provided O_EXCL lock so concurrent `ol` invocations don't issue
|
|
49
|
+
// parallel refresh grants. Resolved per-call (not cached) to honour test
|
|
50
|
+
// config-path mocking, mirroring `getConfigPath`.
|
|
51
|
+
function refreshLockPath() {
|
|
52
|
+
return `${getConfigPath()}.refresh.lock`;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Best-effort proactive rotation before a request. Returns the token to use
|
|
56
|
+
* (the rotated one, or the current one if no rotation was needed) so the
|
|
57
|
+
* caller doesn't re-read the store; `undefined` when the account isn't
|
|
58
|
+
* refreshable (no refresh token / env / failure) — the caller then falls back
|
|
59
|
+
* to `getApiToken()` and the 401 path stays authoritative. The wrapped store
|
|
60
|
+
* scopes the bundle to any global `--user`; `handshake` pins the token
|
|
61
|
+
* endpoint to that account's instance + client id.
|
|
62
|
+
*/
|
|
63
|
+
export async function proactiveRefresh(handshake) {
|
|
64
|
+
try {
|
|
65
|
+
const { bundle } = await refreshAccessToken({
|
|
66
|
+
store: tokenStore(),
|
|
67
|
+
provider: authProvider(),
|
|
68
|
+
lockPath: refreshLockPath(),
|
|
69
|
+
handshake,
|
|
70
|
+
});
|
|
71
|
+
return bundle.accessToken;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Refresh wiring for cli-core's `auth status` / `auth token view` attachers,
|
|
79
|
+
* so both hand back a token that is usable right now rather than whatever
|
|
80
|
+
* was last stored. The attachers pass their own (`--user`-aware) store, so
|
|
81
|
+
* the handshake is resolved from the account cli-core actually selected —
|
|
82
|
+
* pinning the refresh to that account's instance and client id. Env, legacy
|
|
83
|
+
* and record-less accounts carry no refresh token and are served as-is.
|
|
84
|
+
*/
|
|
85
|
+
export function getTokenRefreshOptions() {
|
|
86
|
+
return {
|
|
87
|
+
provider: authProvider(),
|
|
88
|
+
lockPath: refreshLockPath(),
|
|
89
|
+
handshake: ({ account }) => ({ baseUrl: account.baseUrl, clientId: account.oauthClientId }),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Reactive rotation after a 401. Returns `true` when the token rotated (the
|
|
94
|
+
* caller retries once). A rejected/absent refresh token surfaces as
|
|
95
|
+
* `NoTokenError` (re-login); a transient failure propagates unchanged.
|
|
96
|
+
*/
|
|
97
|
+
export async function reactiveRefresh(handshake) {
|
|
98
|
+
try {
|
|
99
|
+
const result = await refreshAccessToken({
|
|
100
|
+
store: tokenStore(),
|
|
101
|
+
provider: authProvider(),
|
|
102
|
+
lockPath: refreshLockPath(),
|
|
103
|
+
force: true,
|
|
104
|
+
handshake,
|
|
105
|
+
});
|
|
106
|
+
return result.rotated;
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
// Match on the structural `.code` rather than `instanceof`: cli-core
|
|
110
|
+
// throws its own CliError, and class identity isn't reliable across
|
|
111
|
+
// package boundaries (duplicate module instances under linking).
|
|
112
|
+
const code = err?.code;
|
|
113
|
+
if (code === 'AUTH_REFRESH_EXPIRED' || code === 'AUTH_REFRESH_UNAVAILABLE') {
|
|
114
|
+
throw new NoTokenError();
|
|
115
|
+
}
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Read the active token. Hot path: when `OUTLINE_API_TOKEN` is set we
|
|
121
|
+
* return it directly without consulting the token store, since
|
|
122
|
+
* `apiRequest` already resolves the base URL separately — going through
|
|
123
|
+
* `store.active()` here would trigger a redundant `getBaseUrl()` lookup
|
|
124
|
+
* per request just to synthesise an account we don't need.
|
|
125
|
+
*/
|
|
126
|
+
export async function getApiToken() {
|
|
127
|
+
const envToken = process.env[TOKEN_ENV_VAR]?.trim();
|
|
128
|
+
if (envToken)
|
|
129
|
+
return envToken;
|
|
130
|
+
const snapshot = await tokenStore().active();
|
|
131
|
+
if (!snapshot?.token)
|
|
132
|
+
throw new NoTokenError();
|
|
133
|
+
return snapshot.token;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Account-agnostic base URL cascade: env var → default user record (v2) →
|
|
137
|
+
* legacy `base_url` config (v1) → built-in default. Deliberately ignores the
|
|
138
|
+
* global `--user` selector so the login flow (which resolves its default
|
|
139
|
+
* prompt through here) isn't skewed by account selection; the request path
|
|
140
|
+
* applies `--user` separately via {@link getRequestContext}.
|
|
141
|
+
*/
|
|
142
|
+
export async function getBaseUrl() {
|
|
143
|
+
const envUrl = process.env.OUTLINE_URL;
|
|
144
|
+
if (envUrl)
|
|
145
|
+
return envUrl.replace(/\/$/, '');
|
|
146
|
+
const config = await getConfig();
|
|
147
|
+
const record = getDefaultUserRecord(config);
|
|
148
|
+
if (record?.account.baseUrl)
|
|
149
|
+
return record.account.baseUrl.replace(/\/$/, '');
|
|
150
|
+
if (config.base_url)
|
|
151
|
+
return config.base_url.replace(/\/$/, '');
|
|
152
|
+
return DEFAULT_BASE_URL;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Account-agnostic OAuth client id cascade: env var → default user record
|
|
156
|
+
* (v2) → legacy `oauth_client_id` config (v1) → undefined (caller prompts).
|
|
157
|
+
* Like {@link getBaseUrl}, ignores `--user` so login stays default-driven.
|
|
158
|
+
*/
|
|
159
|
+
export async function getOAuthClientId() {
|
|
160
|
+
const envClientId = process.env.OUTLINE_OAUTH_CLIENT_ID;
|
|
161
|
+
if (envClientId)
|
|
162
|
+
return envClientId;
|
|
163
|
+
const config = await getConfig();
|
|
164
|
+
const record = getDefaultUserRecord(config);
|
|
165
|
+
if (record?.account.oauthClientId)
|
|
166
|
+
return record.account.oauthClientId;
|
|
167
|
+
return config.oauth_client_id;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Resolve the base URL — and, when a `--user` account is selected, the refresh
|
|
171
|
+
* handshake (its instance + client id) — for a single managed request. Falls
|
|
172
|
+
* back to the account-agnostic {@link getBaseUrl} cascade without `--user`.
|
|
173
|
+
* An unknown ref resolves no handshake and the default base URL; the wrapped
|
|
174
|
+
* token store then raises `ACCOUNT_NOT_FOUND` when the token is read, so the
|
|
175
|
+
* request never fires.
|
|
176
|
+
*/
|
|
177
|
+
export async function getRequestContext() {
|
|
178
|
+
const ref = requestPathUserRef();
|
|
179
|
+
if (ref !== undefined) {
|
|
180
|
+
const record = recordForRef(await getConfig(), ref);
|
|
181
|
+
if (record) {
|
|
182
|
+
const baseUrl = record.account.baseUrl.replace(/\/$/, '');
|
|
183
|
+
return { baseUrl, handshake: { baseUrl, clientId: record.account.oauthClientId } };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return { baseUrl: await getBaseUrl() };
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type CoreConfig } from '@todoist/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
|
+
};
|
|
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
|
+
*/
|
|
30
|
+
api_token?: string;
|
|
31
|
+
base_url?: string;
|
|
32
|
+
oauth_client_id?: string;
|
|
33
|
+
auth_user_id?: string;
|
|
34
|
+
auth_user_name?: string;
|
|
35
|
+
auth_team_name?: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Resolve the canonical config path on every call. Caching at module load
|
|
39
|
+
* would pin the path to the unmocked `node:os.homedir` before vitest's
|
|
40
|
+
* `vi.mock('node:os', …)` hoist takes effect in a fresh test file.
|
|
41
|
+
*/
|
|
42
|
+
export declare function getConfigPath(): string;
|
|
43
|
+
export declare function getConfig(): Promise<Partial<Config>>;
|
|
44
|
+
export declare function setConfig(config: Config): Promise<void>;
|
|
45
|
+
export declare function updateConfig(updates: Partial<Config>): Promise<void>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getConfigPath as coreGetConfigPath, readConfig as coreReadConfig, updateConfig as coreUpdateConfig, writeConfig as coreWriteConfig, } from '@todoist/cli-core';
|
|
2
|
+
const APP_NAME = 'outline-cli';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the canonical config path on every call. Caching at module load
|
|
5
|
+
* would pin the path to the unmocked `node:os.homedir` before vitest's
|
|
6
|
+
* `vi.mock('node:os', …)` hoist takes effect in a fresh test file.
|
|
7
|
+
*/
|
|
8
|
+
export function getConfigPath() {
|
|
9
|
+
return coreGetConfigPath(APP_NAME);
|
|
10
|
+
}
|
|
11
|
+
export async function getConfig() {
|
|
12
|
+
return coreReadConfig(getConfigPath());
|
|
13
|
+
}
|
|
14
|
+
export async function setConfig(config) {
|
|
15
|
+
return coreWriteConfig(getConfigPath(), config);
|
|
16
|
+
}
|
|
17
|
+
export async function updateConfig(updates) {
|
|
18
|
+
return coreUpdateConfig(getConfigPath(), updates);
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +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,mBAAmB,CAAA;AAE1B,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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { CliError as CoreCliError, type CliErrorCode, type ErrorType } from '@todoist/cli-core';
|
|
2
|
+
export { CoreCliError as BaseCliError };
|
|
3
|
+
export type { ErrorType } from '@todoist/cli-core';
|
|
4
|
+
export type ErrorCode = 'ACCOUNT_NOT_FOUND' | 'AUTH_VERIFICATION_FAILED' | 'CONFIRMATION_REQUIRED' | 'CONFLICTING_OPTIONS' | 'DOCUMENT_CHANGED' | 'INVALID_PARENT' | 'INVALID_TIMESTAMP' | 'INVALID_USER_FLAG' | 'MISSING_OPTION' | 'NO_TOKEN' | 'OAUTH_CALLBACK_PORT_INVALID' | 'OAUTH_CALLBACK_SERVER_FAILED' | 'OAUTH_CLIENT_ID_REQUIRED' | 'OAUTH_LOGIN_FAILED' | 'UNKNOWN_AGENT';
|
|
5
|
+
export declare class CliError extends CoreCliError<ErrorCode> {
|
|
6
|
+
constructor(code: ErrorCode | CliErrorCode, message: string, hints?: string[], type?: ErrorType);
|
|
7
|
+
}
|
|
8
|
+
export { getErrorMessage } from '@todoist/cli-core';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CliError as CoreCliError } from '@todoist/cli-core';
|
|
2
|
+
export { CoreCliError as BaseCliError };
|
|
3
|
+
export class CliError extends CoreCliError {
|
|
4
|
+
constructor(code, message, hints, type = 'error') {
|
|
5
|
+
super(code, message, { hints, type });
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export { getErrorMessage } from '@todoist/cli-core';
|
|
9
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,YAAY,EAAqC,MAAM,mBAAmB,CAAA;AAE/F,OAAO,EAAE,YAAY,IAAI,YAAY,EAAE,CAAA;AAoBvC,MAAM,OAAO,QAAS,SAAQ,YAAuB;IACjD,YACI,IAA8B,EAC9B,OAAe,EACf,KAAgB,EAChB,IAAI,GAAc,OAAO;QAEzB,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IACzC,CAAC;CACJ;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export declare const getGlobalArgs: () => import("@todoist/cli-core").GlobalArgs;
|
|
2
|
+
export declare const resetGlobalArgs: () => void;
|
|
3
|
+
export declare const isAccessible: () => boolean;
|
|
4
|
+
export declare const shouldDisableSpinner: () => boolean;
|
|
5
|
+
export declare function isJsonMode(): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Pre-subcommand `ol --user <ref>` selector. cli-core's `parseGlobalArgs`
|
|
8
|
+
* already extracts it from argv; `applyUserSelector` strips the flag before
|
|
9
|
+
* Commander parses, so this reads the value off the warmed cache.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getRequestedUserRef(): string | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Guard a root `--user` against the two common footguns before it's stripped:
|
|
14
|
+
* a value-less flag (`--user`, `--user=`) and a forgotten value where the next
|
|
15
|
+
* token is actually a command (`ol --user document list`). Pure — pass the
|
|
16
|
+
* pre-strip argv and the set of registered command names. Silent when `--user`
|
|
17
|
+
* is absent, or when it appears *after* a command (a late flag is left for
|
|
18
|
+
* Commander to reject as an unknown option, matching what `stripUserFlag`
|
|
19
|
+
* leaves in argv).
|
|
20
|
+
*/
|
|
21
|
+
export declare function validateRootUserFlag(argv: string[], knownCommands: ReadonlySet<string>): void;
|
|
22
|
+
/**
|
|
23
|
+
* Entrypoint wiring for the global `--user` selector, kept here so the
|
|
24
|
+
* validate → warm-cache → strip order is exercised by tests rather than living
|
|
25
|
+
* untested in `src/index.ts`. Validates the root flag, warms the global-args
|
|
26
|
+
* cache off the *original* argv (so the ref survives), then rewrites
|
|
27
|
+
* `process.argv` with the flag stripped for Commander. Throws `INVALID_USER_FLAG`
|
|
28
|
+
* on a malformed root flag.
|
|
29
|
+
*/
|
|
30
|
+
export declare function applyUserSelector(knownCommands: ReadonlySet<string>): void;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { createAccessibleGate, createGlobalArgsStore, createSpinnerGate, parseGlobalArgs, stripUserFlag, } from '@todoist/cli-core';
|
|
2
|
+
import { CliError } from './errors.js';
|
|
3
|
+
const store = createGlobalArgsStore();
|
|
4
|
+
export const getGlobalArgs = store.get;
|
|
5
|
+
export const resetGlobalArgs = store.reset;
|
|
6
|
+
export const isAccessible = createAccessibleGate({
|
|
7
|
+
envVar: 'OL_ACCESSIBLE',
|
|
8
|
+
getArgs: store.get,
|
|
9
|
+
});
|
|
10
|
+
export const shouldDisableSpinner = createSpinnerGate({
|
|
11
|
+
envVar: 'OL_SPINNER',
|
|
12
|
+
getArgs: store.get,
|
|
13
|
+
});
|
|
14
|
+
export function isJsonMode() {
|
|
15
|
+
const args = store.get();
|
|
16
|
+
return args.json || args.ndjson;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Pre-subcommand `ol --user <ref>` selector. cli-core's `parseGlobalArgs`
|
|
20
|
+
* already extracts it from argv; `applyUserSelector` strips the flag before
|
|
21
|
+
* Commander parses, so this reads the value off the warmed cache.
|
|
22
|
+
*/
|
|
23
|
+
export function getRequestedUserRef() {
|
|
24
|
+
return store.get().user;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Guard a root `--user` against the two common footguns before it's stripped:
|
|
28
|
+
* a value-less flag (`--user`, `--user=`) and a forgotten value where the next
|
|
29
|
+
* token is actually a command (`ol --user document list`). Pure — pass the
|
|
30
|
+
* pre-strip argv and the set of registered command names. Silent when `--user`
|
|
31
|
+
* is absent, or when it appears *after* a command (a late flag is left for
|
|
32
|
+
* Commander to reject as an unknown option, matching what `stripUserFlag`
|
|
33
|
+
* leaves in argv).
|
|
34
|
+
*/
|
|
35
|
+
export function validateRootUserFlag(argv, knownCommands) {
|
|
36
|
+
const userIdx = argv.findIndex((a) => a === '--user' || a.startsWith('--user='));
|
|
37
|
+
if (userIdx === -1)
|
|
38
|
+
return;
|
|
39
|
+
const firstCmdIdx = argv.findIndex((a) => knownCommands.has(a));
|
|
40
|
+
if (firstCmdIdx !== -1 && userIdx > firstCmdIdx)
|
|
41
|
+
return;
|
|
42
|
+
const ref = parseGlobalArgs(argv).user;
|
|
43
|
+
if (!ref) {
|
|
44
|
+
throw new CliError('INVALID_USER_FLAG', '--user requires a value: <id|name>.', [
|
|
45
|
+
'Example: ol --user scott@example.com document list',
|
|
46
|
+
]);
|
|
47
|
+
}
|
|
48
|
+
if (knownCommands.has(ref)) {
|
|
49
|
+
throw new CliError('INVALID_USER_FLAG', `--user requires a value: got "${ref}", which looks like a command — did you forget the value?`, [`Example: ol --user scott@example.com ${ref}`]);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Entrypoint wiring for the global `--user` selector, kept here so the
|
|
54
|
+
* validate → warm-cache → strip order is exercised by tests rather than living
|
|
55
|
+
* untested in `src/index.ts`. Validates the root flag, warms the global-args
|
|
56
|
+
* cache off the *original* argv (so the ref survives), then rewrites
|
|
57
|
+
* `process.argv` with the flag stripped for Commander. Throws `INVALID_USER_FLAG`
|
|
58
|
+
* on a malformed root flag.
|
|
59
|
+
*/
|
|
60
|
+
export function applyUserSelector(knownCommands) {
|
|
61
|
+
const original = process.argv.slice(2);
|
|
62
|
+
validateRootUserFlag(original, knownCommands);
|
|
63
|
+
getRequestedUserRef();
|
|
64
|
+
process.argv = [process.argv[0], process.argv[1], ...stripUserFlag(original)];
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=global-args.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"global-args.js","sourceRoot":"","sources":["../../src/lib/global-args.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,oBAAoB,EACpB,qBAAqB,EACrB,iBAAiB,EACjB,eAAe,EACf,aAAa,GAChB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEtC,MAAM,KAAK,GAAG,qBAAqB,EAAE,CAAA;AAErC,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,CAAC,GAAG,CAAA;AACtC,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAC,KAAK,CAAA;AAE1C,MAAM,CAAC,MAAM,YAAY,GAAG,oBAAoB,CAAC;IAC7C,MAAM,EAAE,eAAe;IACvB,OAAO,EAAE,KAAK,CAAC,GAAG;CACrB,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAAG,iBAAiB,CAAC;IAClD,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,KAAK,CAAC,GAAG;CACrB,CAAC,CAAA;AAEF,MAAM,UAAU,UAAU;IACtB,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,EAAE,CAAA;IACxB,OAAO,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,CAAA;AACnC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB;IAC/B,OAAO,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAA;AAC3B,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAc,EAAE,aAAkC;IACnF,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAA;IAChF,IAAI,OAAO,KAAK,CAAC,CAAC;QAAE,OAAM;IAC1B,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IAC/D,IAAI,WAAW,KAAK,CAAC,CAAC,IAAI,OAAO,GAAG,WAAW;QAAE,OAAM;IAEvD,MAAM,GAAG,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,IAAI,CAAA;IACtC,IAAI,CAAC,GAAG,EAAE,CAAC;QACP,MAAM,IAAI,QAAQ,CAAC,mBAAmB,EAAE,qCAAqC,EAAE;YAC3E,oDAAoD;SACvD,CAAC,CAAA;IACN,CAAC;IACD,IAAI,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,QAAQ,CACd,mBAAmB,EACnB,iCAAiC,GAAG,2DAA2D,EAC/F,CAAC,wCAAwC,GAAG,EAAE,CAAC,CAClD,CAAA;IACL,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,aAAkC;IAChE,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IACtC,oBAAoB,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAA;IAC7C,mBAAmB,EAAE,CAAA;IACrB,OAAO,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAA;AACjF,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { preloadMarkdown as corePreloadMarkdown, renderMarkdown as coreRenderMarkdown, } from '@todoist/cli-core/markdown';
|
|
2
|
+
let preloadPromise = null;
|
|
3
|
+
export async function preloadMarkdown() {
|
|
4
|
+
if (!preloadPromise)
|
|
5
|
+
preloadPromise = corePreloadMarkdown();
|
|
6
|
+
return preloadPromise;
|
|
7
|
+
}
|
|
8
|
+
export async function renderMarkdown(text) {
|
|
9
|
+
await preloadMarkdown();
|
|
10
|
+
return coreRenderMarkdown(text);
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../src/lib/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,eAAe,IAAI,mBAAmB,EACtC,cAAc,IAAI,kBAAkB,GACvC,MAAM,4BAA4B,CAAA;AAEnC,IAAI,cAAc,GAAyB,IAAI,CAAA;AAE/C,MAAM,CAAC,KAAK,UAAU,eAAe;IACjC,IAAI,CAAC,cAAc;QAAE,cAAc,GAAG,mBAAmB,EAAE,CAAA;IAC3D,OAAO,cAAc,CAAA;AACzB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAY;IAC7C,MAAM,eAAe,EAAE,CAAA;IACvB,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAA;AACnC,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type MigrateAuthResult } from '@todoist/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 '@todoist/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,32 @@
|
|
|
1
|
+
import type { AccountRef, AuthAccount } from '@todoist/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;
|
|
27
|
+
/**
|
|
28
|
+
* Accepts the Outline user UUID or display name. Id matches are
|
|
29
|
+
* case-sensitive (UUIDs are canonical); label matches are case-insensitive
|
|
30
|
+
* so users can pass the name they see in `auth status`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function matchOutlineAccount(account: OutlineAccount, ref: AccountRef): boolean;
|
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
/**
|
|
13
|
+
* Accepts the Outline user UUID or display name. Id matches are
|
|
14
|
+
* case-sensitive (UUIDs are canonical); label matches are case-insensitive
|
|
15
|
+
* so users can pass the name they see in `auth status`.
|
|
16
|
+
*/
|
|
17
|
+
export function matchOutlineAccount(account, ref) {
|
|
18
|
+
if (account.id === ref)
|
|
19
|
+
return true;
|
|
20
|
+
return account.label.toLowerCase() === ref.toLowerCase();
|
|
21
|
+
}
|
|
22
|
+
//# 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;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAuB,EAAE,GAAe;IACxE,IAAI,OAAO,CAAC,EAAE,KAAK,GAAG;QAAE,OAAO,IAAI,CAAA;IACnC,OAAO,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,GAAG,CAAC,WAAW,EAAE,CAAA;AAC5D,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ViewOptions } from '@todoist/cli-core';
|
|
2
|
+
import type { Pagination } from './api.js';
|
|
3
|
+
import { BaseCliError } from './errors.js';
|
|
4
|
+
type AnyCliError = BaseCliError<string>;
|
|
5
|
+
export type OutputOptions = ViewOptions & {
|
|
6
|
+
full?: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare function getOutputOptions(opts: Record<string, unknown>): OutputOptions;
|
|
9
|
+
export declare function outputItem<T extends object>(item: T, humanFormatter: (item: T) => string, essentialKeys?: (keyof T)[], opts?: OutputOptions): void;
|
|
10
|
+
export declare function outputList<T extends object>(items: T[], humanFormatter: (item: T) => string, essentialKeys?: (keyof T)[], opts?: OutputOptions, pagination?: Pagination, emptyMessage?: string): void;
|
|
11
|
+
export declare function formatError(error: AnyCliError): string;
|
|
12
|
+
export declare function formatError(code: string, message: string, hints?: string[]): string;
|
|
13
|
+
export declare function formatErrorJson(error: AnyCliError): string;
|
|
14
|
+
export declare function formatErrorJson(code: string, message: string, hints?: string[]): string;
|
|
15
|
+
export {};
|