vault-cortex 0.5.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -21
- package/dist/docker.js +35 -1
- package/dist/env.js +1 -3
- package/dist/get-sync-token.js +150 -0
- package/dist/init.js +21 -34
- package/dist/main.js +5 -0
- package/dist/program.js +7 -0
- package/dist/scaffold.js +19 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,28 +7,62 @@ for your Obsidian vault in one command:
|
|
|
7
7
|
npx vault-cortex@latest init
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
Vault Cortex is a standalone, remote-capable MCP server
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
review, daily review). It runs as a Docker
|
|
17
|
-
container; this CLI scaffolds the config so you don't have to.
|
|
10
|
+
Vault Cortex is a standalone, remote-capable MCP server that gives any AI
|
|
11
|
+
agent hybrid search, task management, structured memory, and read/write
|
|
12
|
+
access to your Obsidian vault — see the
|
|
13
|
+
[full feature overview](https://github.com/aliasunder/vault-cortex#what-you-get).
|
|
14
|
+
The server runs as a Docker container; this CLI scaffolds the config and
|
|
15
|
+
manages the container so you don't have to.
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+

|
|
18
|
+
|
|
19
|
+
## Commands
|
|
20
|
+
|
|
21
|
+
- [`init`](#init) — interactive setup: scaffold the config, generate the auth
|
|
22
|
+
token, start the server
|
|
23
|
+
- [`upgrade`](#upgrade) — pull the latest image and re-create the container;
|
|
24
|
+
your data stays
|
|
25
|
+
- [`get-sync-token`](#get-sync-token) — generate an Obsidian Sync auth token
|
|
26
|
+
for remote setups
|
|
27
|
+
|
|
28
|
+
Run `npx vault-cortex <command> --help` for all flags.
|
|
29
|
+
|
|
30
|
+
## init
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx vault-cortex@latest init
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
What it does:
|
|
20
37
|
|
|
21
38
|
1. Asks how you want to run it:
|
|
22
39
|
- **Local** — Docker on this machine, your vault folder bind-mounted
|
|
23
|
-
- **Remote** — a VPS with [Obsidian Sync](https://obsidian.md/sync),
|
|
40
|
+
- **Remote** — a VPS with [Obsidian Sync](https://obsidian.md/sync),
|
|
41
|
+
reachable from any device
|
|
24
42
|
2. Generates a `.env` file with a securely generated `MCP_AUTH_TOKEN`
|
|
25
43
|
3. Optionally starts the container and waits for the health check
|
|
26
44
|
4. Prints your connection details — the MCP URL, your auth token, and how to
|
|
27
45
|
connect your client
|
|
28
46
|
|
|
29
|
-
Existing files are never overwritten without asking.
|
|
47
|
+
Existing files are never overwritten without asking. During a remote setup,
|
|
48
|
+
init offers to run [`get-sync-token`](#get-sync-token) for you when Docker is
|
|
49
|
+
available.
|
|
30
50
|
|
|
31
|
-
|
|
51
|
+
Flags:
|
|
52
|
+
|
|
53
|
+
- `--mode local|remote` — skip the mode prompt
|
|
54
|
+
- `--vault-path <path>` — absolute path to your vault (local mode)
|
|
55
|
+
- `--dir <path>` — directory to write config files into (default
|
|
56
|
+
`./vault-cortex`)
|
|
57
|
+
- `--yes` — non-interactive local setup with defaults; requires `--vault-path`
|
|
58
|
+
|
|
59
|
+
Non-interactive example:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npx vault-cortex@latest init --yes --vault-path /path/to/YourVault
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## upgrade
|
|
32
66
|
|
|
33
67
|
Pull the latest image, re-create the container, and verify health:
|
|
34
68
|
|
|
@@ -40,27 +74,45 @@ Run it from the same directory where you ran `init` — it looks for your
|
|
|
40
74
|
config in `./vault-cortex/.env` (pass `--dir <path>` if you scaffolded
|
|
41
75
|
somewhere else).
|
|
42
76
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
77
|
+
Safe by design:
|
|
78
|
+
|
|
79
|
+
- Your vault data, search index, and `.env` settings are preserved across
|
|
80
|
+
upgrades — only the server image is replaced.
|
|
81
|
+
- Any edits you've made to `.env` are applied on the way up (`docker restart`
|
|
82
|
+
alone does not re-read env files).
|
|
46
83
|
|
|
47
84
|
Prefer Docker Compose? The CLI uses `docker run` for simplicity, but the
|
|
48
85
|
[deploy guides](https://github.com/aliasunder/vault-cortex/blob/main/deploy/)
|
|
49
|
-
include Compose files you can use directly.
|
|
86
|
+
include Compose files you can use directly. If you set up with Compose, stick
|
|
87
|
+
with Compose for updates too (`docker compose pull && docker compose up -d`)
|
|
88
|
+
— the CLI and Compose manage the container independently.
|
|
89
|
+
|
|
90
|
+
## get-sync-token
|
|
50
91
|
|
|
51
|
-
|
|
92
|
+
Generate an [Obsidian Sync](https://obsidian.md/sync) auth token — needed for
|
|
93
|
+
remote setups — without leaving the CLI:
|
|
52
94
|
|
|
53
95
|
```bash
|
|
54
|
-
npx vault-cortex
|
|
96
|
+
npx vault-cortex get-sync-token
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The command opens the Obsidian login inside Docker. Once you've signed in, it
|
|
100
|
+
captures your token and prints it — nothing to dig out of the login output.
|
|
101
|
+
Use `--dir <path>` to write the token straight into an existing `.env`
|
|
102
|
+
instead:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
npx vault-cortex get-sync-token --dir ./vault-cortex
|
|
55
106
|
```
|
|
56
107
|
|
|
57
|
-
|
|
58
|
-
|
|
108
|
+
During `init --mode remote`, this flow is offered automatically when Docker
|
|
109
|
+
is available.
|
|
59
110
|
|
|
60
111
|
## Requirements
|
|
61
112
|
|
|
62
113
|
- Node.js >= 20.12 (only for this CLI — the server itself runs in Docker)
|
|
63
|
-
- [Docker](https://docs.docker.com/get-docker/) (or any OCI-compatible
|
|
114
|
+
- [Docker](https://docs.docker.com/get-docker/) (or any OCI-compatible
|
|
115
|
+
runtime, e.g. Podman) to run the server
|
|
64
116
|
|
|
65
117
|
## Docs
|
|
66
118
|
|
package/dist/docker.js
CHANGED
|
@@ -2,6 +2,36 @@ import { 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
|
+
};
|
|
5
35
|
/**
|
|
6
36
|
* Container-internal env vars that must override the user's .env values.
|
|
7
37
|
* VAULT_PATH in .env is the host path (for the -v mount); the container
|
|
@@ -70,7 +100,11 @@ export const createDockerRunner = () => ({
|
|
|
70
100
|
.status === 0,
|
|
71
101
|
pullImage: (image) => spawnSync("docker", ["pull", image], { stdio: "inherit" }).status === 0,
|
|
72
102
|
stopAndRemoveContainer: () => spawnSync("docker", ["rm", "-f", CONTAINER_NAME]).status === 0,
|
|
73
|
-
|
|
103
|
+
runObsidianLogin: (configMountPath) => spawnSync("docker", buildObsidianLoginArgs({
|
|
104
|
+
configMountPath,
|
|
105
|
+
uid: process.getuid?.(),
|
|
106
|
+
gid: process.getgid?.(),
|
|
107
|
+
}), { stdio: "inherit" }).status === 0,
|
|
74
108
|
});
|
|
75
109
|
/**
|
|
76
110
|
* Polls the health endpoint until it responds OK or the timeout elapses.
|
package/dist/env.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { REMOTE_IMAGE } from "./docker.js";
|
|
2
1
|
// Optional env blocks are synced from deploy/<mode>/.env.example by
|
|
3
2
|
// npm run sync:cli-env-blocks. Edit the deploy/ files, then re-run the script.
|
|
4
3
|
// cli/src/templates.test.ts asserts the CLI optional block vars match the
|
|
@@ -183,8 +182,7 @@ VAULT_PASSWORD=${answers.vaultPassword}`;
|
|
|
183
182
|
const obsidianTokenComment = answers.obsidianAuthToken === ""
|
|
184
183
|
? `# Obsidian Sync auth token — FILL THIS IN before starting the server.
|
|
185
184
|
# Generate once with:
|
|
186
|
-
#
|
|
187
|
-
# ${REMOTE_IMAGE}`
|
|
185
|
+
# npx vault-cortex get-sync-token`
|
|
188
186
|
: `# Obsidian Sync auth token.`;
|
|
189
187
|
return `# vault-cortex — remote quickstart (Obsidian Sync)
|
|
190
188
|
# Generated by \`npx vault-cortex init\`. Full option reference:
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { patchEnvObsidianToken } from "./scaffold.js";
|
|
5
|
+
import { expandTilde } from "./vault.js";
|
|
6
|
+
/** Message from an unknown throw — Error instances keep their message. */
|
|
7
|
+
const describeError = (error) => error instanceof Error ? error.message : String(error);
|
|
8
|
+
/**
|
|
9
|
+
* Creates the temp dir the container's config mount writes into.
|
|
10
|
+
* Returns undefined (after warning) when creation fails.
|
|
11
|
+
*/
|
|
12
|
+
const makeTempMountDir = (prompts) => {
|
|
13
|
+
try {
|
|
14
|
+
return mkdtempSync(join(tmpdir(), "vault-cortex-sync-token-"));
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
prompts.warn(`Could not create a temp directory for token capture — ${describeError(error)}`);
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Runs the interactive Obsidian login container. A throw from the Docker
|
|
23
|
+
* runner is reported and treated the same as a non-zero exit.
|
|
24
|
+
*/
|
|
25
|
+
const runLoginContainer = (configMountPath, deps) => {
|
|
26
|
+
const { docker, prompts } = deps;
|
|
27
|
+
try {
|
|
28
|
+
return docker.runObsidianLogin(configMountPath);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
prompts.warn(`Docker run failed — ${describeError(error)}`);
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Reads the captured token file from the config mount. Returns undefined
|
|
37
|
+
* when the file is missing, empty, or unreadable — the caller treats all
|
|
38
|
+
* three as "no token captured".
|
|
39
|
+
*/
|
|
40
|
+
const readCapturedTokenFile = (configMountPath) => {
|
|
41
|
+
const tokenPath = join(configMountPath, "obsidian-headless", "auth_token");
|
|
42
|
+
try {
|
|
43
|
+
if (!existsSync(tokenPath))
|
|
44
|
+
return undefined;
|
|
45
|
+
const token = readFileSync(tokenPath, "utf8").trim();
|
|
46
|
+
return token || undefined;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Best-effort removal of the temp mount dir. Failing to remove it (e.g.
|
|
54
|
+
* root-owned files left by the container) must not turn a successful
|
|
55
|
+
* capture into a failure, so it warns instead of throwing.
|
|
56
|
+
*/
|
|
57
|
+
const removeTempMountDir = (configMountPath, prompts) => {
|
|
58
|
+
try {
|
|
59
|
+
rmSync(configMountPath, { recursive: true, force: true });
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
prompts.warn(`Could not remove temp directory ${configMountPath} — ${describeError(error)}`);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Runs the Obsidian login (`ob login`) inside a Docker container with a
|
|
67
|
+
* volume mount that captures the auth token file. The interactive login
|
|
68
|
+
* (email, password, MFA) shows in the terminal, but the resulting token is
|
|
69
|
+
* read from the mounted config dir — never printed, so it stays out of
|
|
70
|
+
* terminal scrollback.
|
|
71
|
+
*
|
|
72
|
+
* tokenDestinationMessage finishes the handoff message by telling the user
|
|
73
|
+
* where the captured token ends up — the destination differs per flow
|
|
74
|
+
* (init stores it in the generated .env; the subcommand prints it, or
|
|
75
|
+
* writes it to an existing .env with --dir).
|
|
76
|
+
*
|
|
77
|
+
* Returns the token string on success, undefined on any failure — each
|
|
78
|
+
* fallible operation is wrapped individually by the helpers above, so no
|
|
79
|
+
* catch-all is needed here. The bare try/finally only scopes the temp dir
|
|
80
|
+
* (acquire → release); it has no catch and swallows nothing.
|
|
81
|
+
*/
|
|
82
|
+
export const captureObsidianToken = (deps, tokenDestinationMessage) => {
|
|
83
|
+
const { prompts } = deps;
|
|
84
|
+
const configMountPath = makeTempMountDir(prompts);
|
|
85
|
+
if (!configMountPath)
|
|
86
|
+
return undefined;
|
|
87
|
+
try {
|
|
88
|
+
prompts.log("Handing the terminal to the Obsidian login — it will ask for your " +
|
|
89
|
+
`account email, password, and MFA code. ${tokenDestinationMessage}`);
|
|
90
|
+
const loginSucceeded = runLoginContainer(configMountPath, deps);
|
|
91
|
+
if (!loginSucceeded) {
|
|
92
|
+
prompts.warn("The Obsidian login did not complete — you can run it later with:\n" +
|
|
93
|
+
" npx vault-cortex get-sync-token");
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
const token = readCapturedTokenFile(configMountPath);
|
|
97
|
+
if (!token) {
|
|
98
|
+
prompts.warn("The Obsidian login finished, but no token was captured — the " +
|
|
99
|
+
"token file was missing, empty, or unreadable. You can retry with:\n" +
|
|
100
|
+
" npx vault-cortex get-sync-token");
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
return token;
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
removeTempMountDir(configMountPath, prompts);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Subcommand entry: generate an Obsidian Sync token via Docker.
|
|
111
|
+
* Without --dir, prints the token to stdout.
|
|
112
|
+
* With --dir, writes it directly to `<dir>/.env`.
|
|
113
|
+
*/
|
|
114
|
+
export const runGetSyncToken = async (flags, deps) => {
|
|
115
|
+
const { prompts, docker } = deps;
|
|
116
|
+
if (!docker.isDaemonRunning()) {
|
|
117
|
+
prompts.error("Container runtime not running — start Docker Desktop, Colima,\n" +
|
|
118
|
+
"OrbStack, or another Docker-compatible runtime and try again.");
|
|
119
|
+
return 1;
|
|
120
|
+
}
|
|
121
|
+
prompts.intro("vault-cortex get-sync-token");
|
|
122
|
+
// Resolve the destination up front so the login handoff message can tell
|
|
123
|
+
// the user where the token will end up.
|
|
124
|
+
const envFilePath = flags.dir
|
|
125
|
+
? join(resolve(expandTilde(flags.dir)), ".env")
|
|
126
|
+
: undefined;
|
|
127
|
+
const tokenDestinationMessage = envFilePath
|
|
128
|
+
? `The token is captured automatically and written to ${envFilePath}.`
|
|
129
|
+
: "The token is captured automatically and printed at the end.";
|
|
130
|
+
const token = captureObsidianToken({ docker, prompts }, tokenDestinationMessage);
|
|
131
|
+
if (!token) {
|
|
132
|
+
prompts.error("Could not capture the auth token.");
|
|
133
|
+
return 1;
|
|
134
|
+
}
|
|
135
|
+
if (!envFilePath) {
|
|
136
|
+
prompts.log("Your OBSIDIAN_AUTH_TOKEN:");
|
|
137
|
+
prompts.print(`\n ${token}\n`);
|
|
138
|
+
prompts.outro("Done.");
|
|
139
|
+
return 0;
|
|
140
|
+
}
|
|
141
|
+
const patched = patchEnvObsidianToken(envFilePath, token);
|
|
142
|
+
if (!patched) {
|
|
143
|
+
prompts.error(`Could not patch ${envFilePath} — the file is missing or has no ` +
|
|
144
|
+
"OBSIDIAN_AUTH_TOKEN line. Run init first.");
|
|
145
|
+
return 1;
|
|
146
|
+
}
|
|
147
|
+
prompts.log(`Token written to ${envFilePath}`);
|
|
148
|
+
prompts.outro("Done.");
|
|
149
|
+
return 0;
|
|
150
|
+
};
|
package/dist/init.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { join, resolve } from "node:path";
|
|
2
2
|
import { buildLocalEnv, buildRemoteEnv } from "./env.js";
|
|
3
|
+
import { captureObsidianToken } from "./get-sync-token.js";
|
|
3
4
|
import { buildLocalConnectMessage, buildRemoteConnectMessage, } from "./messages.js";
|
|
4
|
-
import {
|
|
5
|
+
import { pollHealth } from "./docker.js";
|
|
5
6
|
import { buildFilesToWrite, readEnvPort, writeFiles, } from "./scaffold.js";
|
|
6
7
|
import { generateToken } from "./token.js";
|
|
7
8
|
import { expandTilde, validateVaultPath } from "./vault.js";
|
|
@@ -22,26 +23,16 @@ const askMode = async (prompts) => {
|
|
|
22
23
|
], "local");
|
|
23
24
|
return isMode(selected) ? selected : "local";
|
|
24
25
|
};
|
|
25
|
-
const GET_TOKEN_COMMAND = `docker run --rm -it --entrypoint get-token \\
|
|
26
|
-
${REMOTE_IMAGE}`;
|
|
27
26
|
/**
|
|
28
|
-
* Offers to
|
|
29
|
-
* Returns
|
|
30
|
-
*
|
|
31
|
-
* way to raw docker output — image pull, then the tool's own login prompts.
|
|
27
|
+
* Offers to auto-capture the Obsidian Sync token via a Docker volume mount.
|
|
28
|
+
* Returns the captured token string, or undefined when the user declines or
|
|
29
|
+
* the capture fails (the caller falls back to a paste prompt).
|
|
32
30
|
*/
|
|
33
|
-
const
|
|
34
|
-
const runNow = await prompts.confirm("
|
|
31
|
+
const offerSyncTokenCapture = async (prompts, docker) => {
|
|
32
|
+
const runNow = await prompts.confirm("Generate the token now?", true);
|
|
35
33
|
if (!runNow)
|
|
36
|
-
return
|
|
37
|
-
|
|
38
|
-
"account login and print a token at the end.");
|
|
39
|
-
const tokenGenerated = docker.runGetToken();
|
|
40
|
-
if (!tokenGenerated) {
|
|
41
|
-
prompts.warn("get-token did not complete — you can run it later and edit .env.");
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
return true;
|
|
34
|
+
return undefined;
|
|
35
|
+
return captureObsidianToken({ docker, prompts }, "The token is captured automatically and stored in your .env — nothing to copy.");
|
|
45
36
|
};
|
|
46
37
|
/**
|
|
47
38
|
* Asks for the vault path, recursing to re-prompt until it gets a usable
|
|
@@ -240,7 +231,7 @@ const runLocalInit = async (flags, deps) => {
|
|
|
240
231
|
return 0;
|
|
241
232
|
};
|
|
242
233
|
// Remote flow (VPS + Obsidian Sync): resolve target dir → PUBLIC_URL →
|
|
243
|
-
// VAULT_NAME → Obsidian Sync token (optionally running
|
|
234
|
+
// VAULT_NAME → Obsidian Sync token (optionally running the Obsidian login via
|
|
244
235
|
// Docker) → optional E2E vault password → generate token → write .env →
|
|
245
236
|
// optionally start → print connect instructions. Always interactive —
|
|
246
237
|
// the sync-token step can't be defaulted.
|
|
@@ -255,21 +246,17 @@ const runRemoteInit = async (flags, deps) => {
|
|
|
255
246
|
}))));
|
|
256
247
|
const publicUrl = await askPublicUrl(prompts);
|
|
257
248
|
const vaultName = await askVaultName(prompts);
|
|
258
|
-
//
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const pastePrompt = getTokenRan
|
|
270
|
-
? "Paste the Obsidian Sync token printed above (leave blank to fill in .env later):"
|
|
271
|
-
: "Paste the Obsidian Sync token (leave blank to fill in .env later):";
|
|
272
|
-
const obsidianAuthToken = (await prompts.text(pastePrompt, { defaultValue: "" })).trim();
|
|
249
|
+
// Auto-capture the Obsidian Sync token via a Docker volume mount when
|
|
250
|
+
// the daemon is reachable. Falls back to a paste prompt when capture
|
|
251
|
+
// fails or the user declines.
|
|
252
|
+
const capturedToken = docker.isDaemonRunning()
|
|
253
|
+
? await offerSyncTokenCapture(prompts, docker)
|
|
254
|
+
: undefined;
|
|
255
|
+
// Masked prompt: the sync token is a credential and must not echo into
|
|
256
|
+
// the terminal or scrollback. An empty submission still means "fill in
|
|
257
|
+
// .env later" — clack's password prompt accepts blank input.
|
|
258
|
+
const obsidianAuthToken = capturedToken ??
|
|
259
|
+
(await prompts.password("Paste the Obsidian Sync token (leave blank to fill in .env later):")).trim();
|
|
273
260
|
const usesEncryption = await prompts.confirm("Does your vault use end-to-end encryption?", false);
|
|
274
261
|
const vaultPassword = usesEncryption
|
|
275
262
|
? await prompts.password("Vault encryption password:")
|
package/dist/main.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createDockerRunner } from "./docker.js";
|
|
2
|
+
import { runGetSyncToken } from "./get-sync-token.js";
|
|
2
3
|
import { runInit } from "./init.js";
|
|
3
4
|
import { buildProgram } from "./program.js";
|
|
4
5
|
import { createPrompts } from "./prompts.js";
|
|
@@ -16,6 +17,10 @@ export const run = async (version) => {
|
|
|
16
17
|
docker: createDockerRunner(),
|
|
17
18
|
fetchFn: fetch,
|
|
18
19
|
}),
|
|
20
|
+
runGetSyncToken: (flags) => runGetSyncToken(flags, {
|
|
21
|
+
prompts: createPrompts(),
|
|
22
|
+
docker: createDockerRunner(),
|
|
23
|
+
}),
|
|
19
24
|
});
|
|
20
25
|
await program.parseAsync();
|
|
21
26
|
};
|
package/dist/program.js
CHANGED
|
@@ -22,6 +22,13 @@ export const buildProgram = (options) => {
|
|
|
22
22
|
.action(async (flags) => {
|
|
23
23
|
process.exitCode = await options.runUpgrade(flags);
|
|
24
24
|
});
|
|
25
|
+
program
|
|
26
|
+
.command("get-sync-token")
|
|
27
|
+
.description("Generate an Obsidian Sync auth token via Docker and print it or write it to .env")
|
|
28
|
+
.option("--dir <path>", "directory containing .env to update with the token")
|
|
29
|
+
.action(async (flags) => {
|
|
30
|
+
process.exitCode = await options.runGetSyncToken(flags);
|
|
31
|
+
});
|
|
25
32
|
program.action(() => {
|
|
26
33
|
program.help();
|
|
27
34
|
});
|
package/dist/scaffold.js
CHANGED
|
@@ -57,6 +57,25 @@ export const detectMode = (envFilePath) => {
|
|
|
57
57
|
const content = readFileSync(envFilePath, "utf8");
|
|
58
58
|
return OBSIDIAN_AUTH_TOKEN_LINE.test(content) ? "remote" : "local";
|
|
59
59
|
};
|
|
60
|
+
/**
|
|
61
|
+
* Patches the OBSIDIAN_AUTH_TOKEN value in an existing .env file.
|
|
62
|
+
* Returns true when the patch succeeded, false when the file is missing
|
|
63
|
+
* or has no active OBSIDIAN_AUTH_TOKEN line (e.g. a local-mode .env).
|
|
64
|
+
*/
|
|
65
|
+
export const patchEnvObsidianToken = (envFilePath, token) => {
|
|
66
|
+
if (!existsSync(envFilePath))
|
|
67
|
+
return false;
|
|
68
|
+
const content = readFileSync(envFilePath, "utf8");
|
|
69
|
+
/** Matches the full OBSIDIAN_AUTH_TOKEN line for replacement. */
|
|
70
|
+
const fullTokenLine = /^OBSIDIAN_AUTH_TOKEN=.*$/m;
|
|
71
|
+
if (!fullTokenLine.test(content))
|
|
72
|
+
return false;
|
|
73
|
+
// Function replacement avoids $ pattern interpretation ($&, $', etc.)
|
|
74
|
+
// that String.prototype.replace applies to string replacements.
|
|
75
|
+
const patched = content.replace(fullTokenLine, () => `OBSIDIAN_AUTH_TOKEN=${token}`);
|
|
76
|
+
writeFileSync(envFilePath, patched);
|
|
77
|
+
return true;
|
|
78
|
+
};
|
|
60
79
|
/**
|
|
61
80
|
* Writes the files into targetDir (created if missing). Existing files
|
|
62
81
|
* are never overwritten silently: identical content is skipped, and differing
|