vault-cortex 0.11.0 → 0.12.0-beta.61
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/README.md +7 -7
- package/dist/docker.js +1 -39
- package/dist/env.js +42 -20
- package/dist/get-sync-token.js +94 -104
- package/dist/init.js +13 -18
- package/dist/main.js +1 -1
- package/dist/messages.js +1 -1
- package/dist/program.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,7 +61,8 @@ Re-running init where a setup already exists asks first — declining leaves
|
|
|
61
61
|
everything unchanged and points you at [`configure`](#configure), the right
|
|
62
62
|
tool for changing settings in place. Existing files are never overwritten
|
|
63
63
|
without asking. During a remote setup, init offers to run
|
|
64
|
-
[`get-sync-token`](#get-sync-token) for you
|
|
64
|
+
[`get-sync-token`](#get-sync-token) for you — sign in to your Obsidian
|
|
65
|
+
account right from the terminal.
|
|
65
66
|
|
|
66
67
|
Flags:
|
|
67
68
|
|
|
@@ -194,17 +195,16 @@ remote setups — without leaving the CLI:
|
|
|
194
195
|
npx vault-cortex@latest get-sync-token
|
|
195
196
|
```
|
|
196
197
|
|
|
197
|
-
The command
|
|
198
|
-
|
|
199
|
-
Use `--dir <path>` to write the token straight into an existing
|
|
200
|
-
instead:
|
|
198
|
+
The command prompts for your Obsidian account email, password, and MFA code
|
|
199
|
+
(if enabled), signs in via the Obsidian API, and prints the token. No Docker
|
|
200
|
+
required. Use `--dir <path>` to write the token straight into an existing
|
|
201
|
+
`.env` instead:
|
|
201
202
|
|
|
202
203
|
```bash
|
|
203
204
|
npx vault-cortex@latest get-sync-token --dir ./vault-cortex
|
|
204
205
|
```
|
|
205
206
|
|
|
206
|
-
During `init --mode remote`, this flow is offered automatically
|
|
207
|
-
is available.
|
|
207
|
+
During `init --mode remote`, this flow is offered automatically.
|
|
208
208
|
|
|
209
209
|
## Requirements
|
|
210
210
|
|
package/dist/docker.js
CHANGED
|
@@ -2,36 +2,6 @@ import { spawn, spawnSync } from "node:child_process";
|
|
|
2
2
|
export const LOCAL_IMAGE = "ghcr.io/aliasunder/vault-cortex:latest";
|
|
3
3
|
export const REMOTE_IMAGE = "ghcr.io/aliasunder/vault-cortex:remote";
|
|
4
4
|
export const CONTAINER_NAME = "vault-cortex";
|
|
5
|
-
/**
|
|
6
|
-
* Builds the `docker run` args for the Obsidian login with a volume mount
|
|
7
|
-
* that captures the auth token file. Runs `ob login` directly instead of
|
|
8
|
-
* the image's get-sync-token script: the script's additions are locating and
|
|
9
|
-
* printing the token, and the mount makes both unnecessary — the CLI reads
|
|
10
|
-
* the token file itself, and not echoing a credential keeps it out of
|
|
11
|
-
* terminal scrollback. Pure function for testability.
|
|
12
|
-
*
|
|
13
|
-
* On Linux, includes `--user uid:gid` when uid/gid are provided — Node
|
|
14
|
-
* exposes process.getuid/getgid on every POSIX platform, so in practice the
|
|
15
|
-
* flag is always set there — keeping the token file host-user-owned. macOS
|
|
16
|
-
* Docker Desktop translates UIDs automatically, so no flag is needed.
|
|
17
|
-
*/
|
|
18
|
-
export const buildObsidianLoginArgs = (params) => {
|
|
19
|
-
const { configMountPath, platform = process.platform, uid, gid } = params;
|
|
20
|
-
const args = [
|
|
21
|
-
"run",
|
|
22
|
-
"--rm",
|
|
23
|
-
"-it",
|
|
24
|
-
"--entrypoint",
|
|
25
|
-
"ob",
|
|
26
|
-
"-v",
|
|
27
|
-
`${configMountPath}:/home/obsidian/.config`,
|
|
28
|
-
];
|
|
29
|
-
if (platform === "linux" && uid !== undefined && gid !== undefined) {
|
|
30
|
-
args.push("--user", `${uid}:${gid}`);
|
|
31
|
-
}
|
|
32
|
-
args.push(REMOTE_IMAGE, "login");
|
|
33
|
-
return args;
|
|
34
|
-
};
|
|
35
5
|
/**
|
|
36
6
|
* Container-internal env vars that must override the user's .env values.
|
|
37
7
|
* VAULT_PATH in .env is the host path (for the -v mount); the container
|
|
@@ -130,10 +100,7 @@ export const createDockerRunner = () => ({
|
|
|
130
100
|
// stdout is discarded: `docker run -d` prints only the container ID there,
|
|
131
101
|
// which lands as a raw hex line between the wizard's prompts. stderr stays
|
|
132
102
|
// inherited — image-pull progress and error output print live, which the
|
|
133
|
-
// "see output above" failure messages rely on.
|
|
134
|
-
// buildDockerRunArgs always runs detached (never -it), and the prompt
|
|
135
|
-
// library owns the terminal's stdin — interactive flows go through
|
|
136
|
-
// runObsidianLogin, which inherits all three streams.
|
|
103
|
+
// "see output above" failure messages rely on.
|
|
137
104
|
dockerRun: (params) => spawnSync("docker", buildDockerRunArgs(params), {
|
|
138
105
|
stdio: ["ignore", "ignore", "inherit"],
|
|
139
106
|
}).status === 0,
|
|
@@ -166,11 +133,6 @@ export const createDockerRunner = () => ({
|
|
|
166
133
|
// convention for ctrl-C (128 + SIGINT = 130).
|
|
167
134
|
child.once("close", (code) => resolveExitCode(code ?? 130));
|
|
168
135
|
}),
|
|
169
|
-
runObsidianLogin: (configMountPath) => spawnSync("docker", buildObsidianLoginArgs({
|
|
170
|
-
configMountPath,
|
|
171
|
-
uid: process.getuid?.(),
|
|
172
|
-
gid: process.getgid?.(),
|
|
173
|
-
}), { stdio: "inherit" }).status === 0,
|
|
174
136
|
});
|
|
175
137
|
/** Default bound on a single health request (shared by probe and poll). */
|
|
176
138
|
const PROBE_TIMEOUT_MS = 10_000;
|
package/dist/env.js
CHANGED
|
@@ -94,17 +94,28 @@ MEMORY_DIR=About Me
|
|
|
94
94
|
# Host port to expose (default: 8000).
|
|
95
95
|
PORT=8000
|
|
96
96
|
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
97
|
+
# These two settings choose the client IP used for OAuth rate limiting and
|
|
98
|
+
# logs (defaults shown below). Without the right values, every visitor
|
|
99
|
+
# behind a proxy shares one rate-limit budget. TRUST_PROXY_HOPS counts the
|
|
100
|
+
# proxies you control that write X-Forwarded-For; TRUST_FORWARDED_HOPS
|
|
101
|
+
# counts the ones that write the RFC 7239 Forwarded header
|
|
102
|
+
# (https://www.rfc-editor.org/rfc/rfc7239), and 0 ignores that header.
|
|
103
|
+
#
|
|
104
|
+
# What sits in front of the server TRUST_PROXY_HOPS TRUST_FORWARDED_HOPS Client IP is
|
|
105
|
+
# Nothing (default) 0 0 the visitor (socket peer)
|
|
106
|
+
# A proxy or tunnel you control 1 0 the visitor (X-Forwarded-For)
|
|
107
|
+
# (Caddy, nginx, Cloudflare Tunnel)
|
|
108
|
+
# A proxy that reports visitors in 1 1 the visitor (Forwarded)
|
|
109
|
+
# Forwarded (e.g. AWS API Gateway)
|
|
110
|
+
# A CDN in front of that proxy, and 1 2 the visitor (Forwarded)
|
|
111
|
+
# the CDN is the only way to reach it
|
|
112
|
+
#
|
|
113
|
+
# When the Forwarded header is read (TRUST_FORWARDED_HOPS above 0), it wins
|
|
114
|
+
# and TRUST_PROXY_HOPS is not consulted; TRUST_PROXY_HOPS is the fallback
|
|
115
|
+
# when that header is ignored or absent. Faked headers from visitors are
|
|
116
|
+
# ignored in every row — only the counted proxies are trusted.
|
|
106
117
|
# TRUST_PROXY_HOPS=0
|
|
107
|
-
#
|
|
118
|
+
# TRUST_FORWARDED_HOPS=0
|
|
108
119
|
|
|
109
120
|
# Log verbosity: debug | info | warn | error (default: info).
|
|
110
121
|
LOG_LEVEL=info
|
|
@@ -221,17 +232,28 @@ MEMORY_DIR=About Me
|
|
|
221
232
|
# Host port to expose (default: 8000).
|
|
222
233
|
PORT=8000
|
|
223
234
|
|
|
224
|
-
#
|
|
225
|
-
#
|
|
226
|
-
#
|
|
227
|
-
#
|
|
228
|
-
#
|
|
229
|
-
#
|
|
230
|
-
#
|
|
231
|
-
#
|
|
232
|
-
#
|
|
235
|
+
# These two settings choose the client IP used for OAuth rate limiting and
|
|
236
|
+
# logs (defaults shown below). Without the right values, every visitor
|
|
237
|
+
# behind a proxy shares one rate-limit budget. TRUST_PROXY_HOPS counts the
|
|
238
|
+
# proxies you control that write X-Forwarded-For; TRUST_FORWARDED_HOPS
|
|
239
|
+
# counts the ones that write the RFC 7239 Forwarded header
|
|
240
|
+
# (https://www.rfc-editor.org/rfc/rfc7239), and 0 ignores that header.
|
|
241
|
+
#
|
|
242
|
+
# What sits in front of the server TRUST_PROXY_HOPS TRUST_FORWARDED_HOPS Client IP is
|
|
243
|
+
# Nothing (default) 0 0 the visitor (socket peer)
|
|
244
|
+
# A proxy or tunnel you control 1 0 the visitor (X-Forwarded-For)
|
|
245
|
+
# (Caddy, nginx, Cloudflare Tunnel)
|
|
246
|
+
# A proxy that reports visitors in 1 1 the visitor (Forwarded)
|
|
247
|
+
# Forwarded (e.g. AWS API Gateway)
|
|
248
|
+
# A CDN in front of that proxy, and 1 2 the visitor (Forwarded)
|
|
249
|
+
# the CDN is the only way to reach it
|
|
250
|
+
#
|
|
251
|
+
# When the Forwarded header is read (TRUST_FORWARDED_HOPS above 0), it wins
|
|
252
|
+
# and TRUST_PROXY_HOPS is not consulted; TRUST_PROXY_HOPS is the fallback
|
|
253
|
+
# when that header is ignored or absent. Faked headers from visitors are
|
|
254
|
+
# ignored in every row — only the counted proxies are trusted.
|
|
233
255
|
# TRUST_PROXY_HOPS=0
|
|
234
|
-
#
|
|
256
|
+
# TRUST_FORWARDED_HOPS=0
|
|
235
257
|
|
|
236
258
|
# Log verbosity: debug | info | warn | error (default: info).
|
|
237
259
|
LOG_LEVEL=info
|
package/dist/get-sync-token.js
CHANGED
|
@@ -1,140 +1,130 @@
|
|
|
1
|
-
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
1
|
import { join, resolve } from "node:path";
|
|
4
|
-
import { buildDaemonNotRunningMessage, buildDockerNotInstalledMessage, } from "./messages.js";
|
|
5
2
|
import { patchEnvObsidianToken } from "./scaffold.js";
|
|
6
3
|
import { expandTilde } from "./vault.js";
|
|
7
|
-
|
|
4
|
+
const OBSIDIAN_SIGNIN_URL = "https://api.obsidian.md/user/signin";
|
|
5
|
+
const SIGNIN_TIMEOUT_MS = 30_000;
|
|
8
6
|
const describeError = (error) => error instanceof Error ? error.message : String(error);
|
|
9
7
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* Calls the Obsidian Sync signin API. Returns the parsed JSON on success,
|
|
9
|
+
* or throws on HTTP/network errors. The API returns { error: string } for
|
|
10
|
+
* auth failures (200 with an error field), and non-200 for server errors.
|
|
12
11
|
*/
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
const callSigninApi = async (params, fetchFn) => {
|
|
13
|
+
const response = await fetchFn(OBSIDIAN_SIGNIN_URL, {
|
|
14
|
+
method: "POST",
|
|
15
|
+
headers: {
|
|
16
|
+
"Content-Type": "application/json",
|
|
17
|
+
Origin: "https://obsidian.md",
|
|
18
|
+
},
|
|
19
|
+
body: JSON.stringify({
|
|
20
|
+
email: params.email,
|
|
21
|
+
password: params.password,
|
|
22
|
+
mfa: params.mfa,
|
|
23
|
+
}),
|
|
24
|
+
signal: AbortSignal.timeout(SIGNIN_TIMEOUT_MS),
|
|
25
|
+
});
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
throw new Error(`HTTP Error ${response.status}`);
|
|
16
28
|
}
|
|
17
|
-
|
|
18
|
-
prompts.warn(`Could not create a temp directory for token capture — ${describeError(error)}`);
|
|
19
|
-
return undefined;
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
/**
|
|
23
|
-
* Runs the interactive Obsidian login container. A throw from the Docker
|
|
24
|
-
* runner is reported and treated the same as a non-zero exit.
|
|
25
|
-
*/
|
|
26
|
-
const runLoginContainer = (configMountPath, deps) => {
|
|
27
|
-
const { docker, prompts } = deps;
|
|
29
|
+
let body;
|
|
28
30
|
try {
|
|
29
|
-
|
|
31
|
+
body = await response.json();
|
|
30
32
|
}
|
|
31
|
-
catch
|
|
32
|
-
|
|
33
|
-
return false;
|
|
33
|
+
catch {
|
|
34
|
+
throw new Error("Unexpected response from Obsidian API (not JSON)");
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
* when the file is missing, empty, or unreadable — the caller treats all
|
|
39
|
-
* three as "no token captured".
|
|
40
|
-
*/
|
|
41
|
-
const readCapturedTokenFile = (configMountPath) => {
|
|
42
|
-
const tokenPath = join(configMountPath, "obsidian-headless", "auth_token");
|
|
43
|
-
try {
|
|
44
|
-
if (!existsSync(tokenPath))
|
|
45
|
-
return undefined;
|
|
46
|
-
const token = readFileSync(tokenPath, "utf8").trim();
|
|
47
|
-
return token || undefined;
|
|
36
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
37
|
+
if (!isRecord(body)) {
|
|
38
|
+
throw new Error("Unexpected response from Obsidian API (not JSON)");
|
|
48
39
|
}
|
|
49
|
-
|
|
50
|
-
|
|
40
|
+
if ("error" in body && typeof body.error === "string") {
|
|
41
|
+
throw new ObsidianApiError(body.error);
|
|
42
|
+
}
|
|
43
|
+
const token = "token" in body && typeof body.token === "string" ? body.token : undefined;
|
|
44
|
+
if (!token) {
|
|
45
|
+
throw new Error("Unexpected response from Obsidian API (no token)");
|
|
51
46
|
}
|
|
47
|
+
const name = "name" in body && typeof body.name === "string" ? body.name : "";
|
|
48
|
+
const email = "email" in body && typeof body.email === "string" ? body.email : "";
|
|
49
|
+
return { token, name, email };
|
|
52
50
|
};
|
|
51
|
+
class ObsidianApiError extends Error {
|
|
52
|
+
constructor(message) {
|
|
53
|
+
super(message);
|
|
54
|
+
this.name = "ObsidianApiError";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
53
57
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
58
|
+
* Signs in to the user's Obsidian account via the Sync API and returns
|
|
59
|
+
* the auth token. Prompts for email, password, and MFA code (when 2FA
|
|
60
|
+
* is enabled). Returns the token on success, undefined on any failure.
|
|
57
61
|
*/
|
|
58
|
-
const
|
|
62
|
+
export const captureObsidianToken = async (deps) => {
|
|
63
|
+
const { prompts, fetchFn } = deps;
|
|
64
|
+
const email = await prompts.text("Obsidian account email:", {
|
|
65
|
+
placeholder: "you@example.com",
|
|
66
|
+
});
|
|
67
|
+
const password = await prompts.password("Password:");
|
|
68
|
+
const spinner = prompts.spinner();
|
|
69
|
+
spinner.start("Signing in to Obsidian...");
|
|
59
70
|
try {
|
|
60
|
-
|
|
71
|
+
const result = await callSigninApi({ email, password, mfa: "" }, fetchFn);
|
|
72
|
+
spinner.stop(`Signed in as ${result.name} (${result.email}).`);
|
|
73
|
+
return result.token;
|
|
61
74
|
}
|
|
62
75
|
catch (error) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
prompts.log("Handing the terminal to the Obsidian login — it will ask for your " +
|
|
90
|
-
`account email, password, and MFA code. ${tokenDestinationMessage}`);
|
|
91
|
-
const loginSucceeded = runLoginContainer(configMountPath, deps);
|
|
92
|
-
if (!loginSucceeded) {
|
|
93
|
-
prompts.warn("The Obsidian login did not complete — you can run it later with:\n" +
|
|
94
|
-
" npx vault-cortex@latest get-sync-token");
|
|
95
|
-
return undefined;
|
|
76
|
+
// MFA required: the API returns an error containing "2FA code" — prompt
|
|
77
|
+
// and retry. "2FA code is incorrect" is a wrong-code rejection, not a
|
|
78
|
+
// prompt-for-code signal. Mirrors the obsidian-headless v0.0.14 logic.
|
|
79
|
+
if (error instanceof ObsidianApiError &&
|
|
80
|
+
error.message.includes("2FA code") &&
|
|
81
|
+
!error.message.includes("2FA code is incorrect")) {
|
|
82
|
+
spinner.stop("Two-factor authentication required.");
|
|
83
|
+
const mfaCode = await prompts.text("2FA code:");
|
|
84
|
+
spinner.start("Verifying...");
|
|
85
|
+
try {
|
|
86
|
+
const result = await callSigninApi({ email, password, mfa: mfaCode }, fetchFn);
|
|
87
|
+
spinner.stop(`Signed in as ${result.name} (${result.email}).`);
|
|
88
|
+
return result.token;
|
|
89
|
+
}
|
|
90
|
+
catch (retryError) {
|
|
91
|
+
spinner.stop("Sign-in failed.");
|
|
92
|
+
if (retryError instanceof Error && retryError.name === "TimeoutError") {
|
|
93
|
+
prompts.warn("Request timed out — check your internet connection and try again.");
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
const retryHint = retryError instanceof ObsidianApiError
|
|
97
|
+
? "\n Check your 2FA code and try again."
|
|
98
|
+
: "";
|
|
99
|
+
prompts.warn(`Could not sign in: ${describeError(retryError)}${retryHint}`);
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
96
102
|
}
|
|
97
|
-
|
|
98
|
-
if (
|
|
99
|
-
prompts.warn("
|
|
100
|
-
"token file was missing, empty, or unreadable. You can retry with:\n" +
|
|
101
|
-
" npx vault-cortex@latest get-sync-token");
|
|
103
|
+
spinner.stop("Sign-in failed.");
|
|
104
|
+
if (error instanceof Error && error.name === "TimeoutError") {
|
|
105
|
+
prompts.warn("Request timed out — check your internet connection and try again.");
|
|
102
106
|
return undefined;
|
|
103
107
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
finally {
|
|
107
|
-
removeTempMountDir(configMountPath, prompts);
|
|
108
|
+
prompts.warn(`Could not sign in: ${describeError(error)}`);
|
|
109
|
+
return undefined;
|
|
108
110
|
}
|
|
109
111
|
};
|
|
110
112
|
/**
|
|
111
|
-
* Subcommand entry: generate an Obsidian Sync token via
|
|
113
|
+
* Subcommand entry: generate an Obsidian Sync token via the Obsidian API.
|
|
112
114
|
* Without --dir, prints the token to stdout.
|
|
113
115
|
* With --dir, writes it directly to `<dir>/.env`.
|
|
114
116
|
*/
|
|
115
117
|
export const runGetSyncToken = async (flags, deps) => {
|
|
116
|
-
const { prompts
|
|
117
|
-
const daemonStatus = docker.daemonStatus();
|
|
118
|
-
if (daemonStatus !== "running") {
|
|
119
|
-
prompts.error(daemonStatus === "not-installed"
|
|
120
|
-
? buildDockerNotInstalledMessage({ nextStep: "\nThen try again." })
|
|
121
|
-
: buildDaemonNotRunningMessage(" and try again."));
|
|
122
|
-
return 1;
|
|
123
|
-
}
|
|
118
|
+
const { prompts } = deps;
|
|
124
119
|
prompts.intro("vault-cortex get-sync-token");
|
|
125
|
-
|
|
126
|
-
// the user where the token will end up.
|
|
127
|
-
const envFilePath = flags.dir
|
|
128
|
-
? join(resolve(expandTilde(flags.dir)), ".env")
|
|
129
|
-
: undefined;
|
|
130
|
-
const tokenDestinationMessage = envFilePath
|
|
131
|
-
? `The token is captured automatically and written to ${envFilePath}.`
|
|
132
|
-
: "The token is captured automatically and printed at the end.";
|
|
133
|
-
const token = captureObsidianToken({ docker, prompts }, tokenDestinationMessage);
|
|
120
|
+
const token = await captureObsidianToken(deps);
|
|
134
121
|
if (!token) {
|
|
135
122
|
prompts.error("Could not capture the auth token.");
|
|
136
123
|
return 1;
|
|
137
124
|
}
|
|
125
|
+
const envFilePath = flags.dir
|
|
126
|
+
? join(resolve(expandTilde(flags.dir)), ".env")
|
|
127
|
+
: undefined;
|
|
138
128
|
if (!envFilePath) {
|
|
139
129
|
prompts.log("Your OBSIDIAN_AUTH_TOKEN:");
|
|
140
130
|
prompts.print(`\n ${token}\n`);
|
package/dist/init.js
CHANGED
|
@@ -27,15 +27,15 @@ const askMode = async (prompts) => {
|
|
|
27
27
|
return isMode(selected) ? selected : "local";
|
|
28
28
|
};
|
|
29
29
|
/**
|
|
30
|
-
* Offers to
|
|
31
|
-
* Returns the captured token string, or undefined when the user declines
|
|
32
|
-
* the capture fails (the caller falls back to a paste prompt).
|
|
30
|
+
* Offers to sign in to the Obsidian account and capture the Sync token.
|
|
31
|
+
* Returns the captured token string, or undefined when the user declines
|
|
32
|
+
* or the capture fails (the caller falls back to a paste prompt).
|
|
33
33
|
*/
|
|
34
|
-
const offerSyncTokenCapture = async (prompts,
|
|
34
|
+
const offerSyncTokenCapture = async (prompts, fetchFn) => {
|
|
35
35
|
const runNow = await prompts.confirm("Generate the token now?", true);
|
|
36
36
|
if (!runNow)
|
|
37
37
|
return undefined;
|
|
38
|
-
return captureObsidianToken({
|
|
38
|
+
return captureObsidianToken({ prompts, fetchFn });
|
|
39
39
|
};
|
|
40
40
|
/**
|
|
41
41
|
* Asks for the vault path, recursing to re-prompt until it gets a usable
|
|
@@ -285,12 +285,12 @@ const runLocalInit = async (flags, deps) => {
|
|
|
285
285
|
return 0;
|
|
286
286
|
};
|
|
287
287
|
// Remote flow (VPS + Obsidian Sync): resolve target dir → PUBLIC_URL →
|
|
288
|
-
// VAULT_NAME → Obsidian Sync token (
|
|
289
|
-
//
|
|
290
|
-
//
|
|
291
|
-
//
|
|
288
|
+
// VAULT_NAME → Obsidian Sync token (sign in via the Obsidian API) → optional
|
|
289
|
+
// E2E vault password → generate token → write .env → optionally start → print
|
|
290
|
+
// connect instructions. Always interactive — the sync-token step can't be
|
|
291
|
+
// defaulted.
|
|
292
292
|
const runRemoteInit = async (flags, deps) => {
|
|
293
|
-
const { prompts,
|
|
293
|
+
const { prompts, fetchFn } = deps;
|
|
294
294
|
// expandTilde before resolve: resolve() treats a leading `~` as a literal
|
|
295
295
|
// path segment, so a quoted "~/path" would create a directory named "~".
|
|
296
296
|
const targetDir = resolve(expandTilde(flags.dir ??
|
|
@@ -303,14 +303,9 @@ const runRemoteInit = async (flags, deps) => {
|
|
|
303
303
|
return 0;
|
|
304
304
|
const publicUrl = await askPublicUrl(prompts);
|
|
305
305
|
const vaultName = await askVaultName(prompts);
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
|
|
309
|
-
// the paste fallback is fully functional without Docker, and the start
|
|
310
|
-
// offer surfaces the differentiated runtime guidance later in the flow.
|
|
311
|
-
const capturedToken = docker.daemonStatus() === "running"
|
|
312
|
-
? await offerSyncTokenCapture(prompts, docker)
|
|
313
|
-
: undefined;
|
|
306
|
+
// Sign in to Obsidian and capture the Sync token directly via the API.
|
|
307
|
+
// Falls back to a paste prompt when the user declines or capture fails.
|
|
308
|
+
const capturedToken = await offerSyncTokenCapture(prompts, fetchFn);
|
|
314
309
|
// Masked prompt: the sync token is a credential and must not echo into
|
|
315
310
|
// the terminal or scrollback. An empty submission still means "fill in
|
|
316
311
|
// .env later" — clack's password prompt accepts blank input.
|
package/dist/main.js
CHANGED
package/dist/messages.js
CHANGED
|
@@ -33,7 +33,7 @@ const dockerInstallLine = (platform) => {
|
|
|
33
33
|
/**
|
|
34
34
|
* "No runtime at all" guidance — distinct from the daemon-stopped message so
|
|
35
35
|
* the user isn't told to start something that isn't installed. platform is a
|
|
36
|
-
* defaulted param
|
|
36
|
+
* defaulted param so each branch stays
|
|
37
37
|
* testable; `nextStep` is appended verbatim, as in
|
|
38
38
|
* buildDaemonNotRunningMessage.
|
|
39
39
|
*/
|
package/dist/program.js
CHANGED
|
@@ -61,7 +61,7 @@ export const buildProgram = (options) => {
|
|
|
61
61
|
});
|
|
62
62
|
program
|
|
63
63
|
.command("get-sync-token")
|
|
64
|
-
.description("
|
|
64
|
+
.description("Sign in to your Obsidian account and print the Sync auth token, or write it to .env")
|
|
65
65
|
.option("--dir <path>", "directory containing .env to update with the token")
|
|
66
66
|
.action(async (flags) => {
|
|
67
67
|
process.exitCode = await options.runGetSyncToken(flags);
|
package/package.json
CHANGED