vault-cortex 0.9.0-beta.45 → 0.10.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/README.md CHANGED
@@ -25,6 +25,8 @@ manages the container so you don't have to.
25
25
  restart to apply them
26
26
  - [`upgrade`](#upgrade) — pull the latest image and re-create the container;
27
27
  your data stays
28
+ - [`start`](#start) — start the server with your saved settings, e.g. after
29
+ `down`
28
30
  - [`restart`](#restart) — re-create the container so your `.env` edits take
29
31
  effect; no image pull
30
32
  - [`logs`](#logs) — show the server's logs, live or after the fact
@@ -49,16 +51,17 @@ What it does:
49
51
  2. Offers the most common optional settings — memory layer and folder,
50
52
  file tools, semantic search, port, timezone (plus sync direction for
51
53
  remote) — press enter to keep the defaults, or pick the ones you want to
52
- change. Skipped when the target directory already has a `.env` (use
53
- [`configure`](#configure) instead)
54
+ change
54
55
  3. Generates a `.env` file with a securely generated `MCP_AUTH_TOKEN`
55
56
  4. Optionally starts the container and waits for the health check
56
57
  5. Prints your connection details — the MCP URL, your auth token, and how to
57
58
  connect your client
58
59
 
59
- Existing files are never overwritten without asking. During a remote setup,
60
- init offers to run [`get-sync-token`](#get-sync-token) for you when Docker is
61
- available.
60
+ Re-running init where a setup already exists asks first declining leaves
61
+ everything unchanged and points you at [`configure`](#configure), the right
62
+ tool for changing settings in place. Existing files are never overwritten
63
+ without asking. During a remote setup, init offers to run
64
+ [`get-sync-token`](#get-sync-token) for you when Docker is available.
62
65
 
63
66
  Flags:
64
67
 
@@ -115,6 +118,22 @@ include Compose files you can use directly. If you set up with Compose, stick
115
118
  with Compose for updates too (`docker compose pull && docker compose up -d`)
116
119
  — the CLI and Compose manage the container independently.
117
120
 
121
+ ## start
122
+
123
+ Start the server with your saved settings and verify health:
124
+
125
+ ```bash
126
+ npx vault-cortex@latest start
127
+ ```
128
+
129
+ This is the command the CLI's own guidance points to whenever the server
130
+ isn't running — after [`down`](#down), or an `init` where the server wasn't
131
+ started. It's the same cycle as [`restart`](#restart) under the name you'd
132
+ look for: any existing container is replaced, and Docker pulls the server
133
+ image automatically on a first start.
134
+
135
+ Use `--dir <path>` if your config isn't in `./vault-cortex`.
136
+
118
137
  ## restart
119
138
 
120
139
  Re-create the container from your `.env` and verify health:
@@ -125,7 +144,7 @@ npx vault-cortex@latest restart
125
144
 
126
145
  Use it after editing `.env` — settings are only read when the container is
127
146
  created, so a plain `docker restart` won't pick them up, but this will. Unlike
128
- [`upgrade`](#upgrade), it never pulls a new image: you get the same server
147
+ [`upgrade`](#upgrade), it never updates the server image: you get the same
129
148
  version back, with your current settings applied.
130
149
 
131
150
  Use `--dir <path>` if your config isn't in `./vault-cortex`.
@@ -159,7 +178,7 @@ npx vault-cortex@latest down
159
178
 
160
179
  Safe by design: your vault, search index, and `.env` settings all live
161
180
  outside the container, so nothing is lost. Start again any time with
162
- [`restart`](#restart). Running `down` when nothing is running is fine — it
181
+ [`start`](#start). Running `down` when nothing is running is fine — it
163
182
  just tells you there's nothing to stop.
164
183
 
165
184
  Use `--dir <path>` if your config isn't in `./vault-cortex`.
package/dist/init.js CHANGED
@@ -2,7 +2,7 @@ import { existsSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
3
  import { buildLocalEnv, buildRemoteEnv } from "./env.js";
4
4
  import { captureObsidianToken } from "./get-sync-token.js";
5
- import { buildDaemonNotRunningMessage, buildDockerNotInstalledMessage, buildLocalConnectMessage, buildRemoteConnectMessage, } from "./messages.js";
5
+ import { buildDaemonNotRunningMessage, buildDockerNotInstalledMessage, buildLocalConnectMessage, buildRemoteConnectMessage, startCommand, } from "./messages.js";
6
6
  import { pollHealth } from "./docker.js";
7
7
  import { reportPublicUrlProbe } from "./lifecycle.js";
8
8
  import { applyOptionalSettings, askOptionalSettings, derivePublicUrlOverride, } from "./optional-settings.js";
@@ -162,12 +162,12 @@ const offerDockerRun = async (params, deps) => {
162
162
  const { prompts, docker, fetchFn } = deps;
163
163
  const daemonStatus = docker.daemonStatus();
164
164
  if (daemonStatus !== "running") {
165
- const upgradeHint = `npx vault-cortex@latest upgrade --dir "${targetDir}"`;
165
+ const startHint = startCommand(targetDir);
166
166
  prompts.warn(daemonStatus === "not-installed"
167
167
  ? buildDockerNotInstalledMessage({
168
- nextStep: `\nThen start the server with:\n ${upgradeHint}`,
168
+ nextStep: `\nThen start the server with:\n ${startHint}`,
169
169
  })
170
- : buildDaemonNotRunningMessage(`, then run:\n ${upgradeHint}`));
170
+ : buildDaemonNotRunningMessage(`, then run:\n ${startHint}`));
171
171
  return false;
172
172
  }
173
173
  const startNow = await prompts.confirm("Start the server now?", true);
@@ -239,18 +239,15 @@ const runLocalInit = async (flags, deps) => {
239
239
  const token = generateToken();
240
240
  // Guided optional settings: the chooser reads current values from the
241
241
  // generated defaults; enter with nothing picked keeps them all. --yes
242
- // skips the chooser (non-interactive by contract), and so does an existing
243
- // .env the conflict prompt defaults to keeping it, which would discard
244
- // the answers; settings on an existing deployment are configure's job.
242
+ // skips the chooser (non-interactive by contract). An existing .env does
243
+ // NOT skip it: every interactive path here passed the re-init guard, so the
244
+ // user asked for a full re-run the answers land in the regenerated file
245
+ // when they overwrite at the conflict prompt (keeping it discards them,
246
+ // which the write report states). In-place edits stay configure's job.
245
247
  const defaultEnvContent = buildLocalEnv({ mcpAuthToken: token, vaultPath });
246
- const envAlreadyExists = existsSync(join(targetDir, ".env"));
247
- const offerSettingsChooser = !flags.yes && !envAlreadyExists;
248
- if (!flags.yes && envAlreadyExists) {
249
- prompts.log('Found an existing .env — settings prompts skipped. Adjust settings with "npx vault-cortex@latest configure".');
250
- }
251
- const optionalOverrides = offerSettingsChooser
252
- ? await askOptionalSettings({ mode: "local", envContent: defaultEnvContent }, prompts)
253
- : {};
248
+ const optionalOverrides = flags.yes
249
+ ? {}
250
+ : await askOptionalSettings({ mode: "local", envContent: defaultEnvContent }, prompts);
254
251
  const envContent = applyOptionalSettings(defaultEnvContent, derivePublicUrlOverride(defaultEnvContent, optionalOverrides));
255
252
  // Conflict policy: identical existing files are skipped silently;
256
253
  // differing ones prompt per file (default keep). --yes never overwrites —
@@ -317,8 +314,10 @@ const runRemoteInit = async (flags, deps) => {
317
314
  : undefined;
318
315
  const token = generateToken();
319
316
  // Guided optional settings, mirroring the local flow — remote also offers
320
- // SYNC_MODE. Remote init is always interactive (no --yes), so only an
321
- // existing .env skips the chooser here.
317
+ // SYNC_MODE. Remote init is always interactive (no --yes), and any existing
318
+ // .env passed the re-init guard — a consented re-run gets the full setup,
319
+ // chooser included (see the local flow's comment for the overwrite/keep
320
+ // semantics).
322
321
  const defaultEnvContent = buildRemoteEnv({
323
322
  mcpAuthToken: token,
324
323
  publicUrl,
@@ -326,13 +325,7 @@ const runRemoteInit = async (flags, deps) => {
326
325
  vaultName,
327
326
  vaultPassword,
328
327
  });
329
- const envAlreadyExists = existsSync(join(targetDir, ".env"));
330
- if (envAlreadyExists) {
331
- prompts.log('Found an existing .env — settings prompts skipped. Adjust settings with "npx vault-cortex@latest configure".');
332
- }
333
- const optionalOverrides = envAlreadyExists
334
- ? {}
335
- : await askOptionalSettings({ mode: "remote", envContent: defaultEnvContent }, prompts);
328
+ const optionalOverrides = await askOptionalSettings({ mode: "remote", envContent: defaultEnvContent }, prompts);
336
329
  const envContent = applyOptionalSettings(defaultEnvContent, derivePublicUrlOverride(defaultEnvContent, optionalOverrides));
337
330
  const files = buildFilesToWrite(envContent);
338
331
  const results = await writeFiles({ targetDir, files }, confirmOverwrite(prompts));
package/dist/lifecycle.js CHANGED
@@ -41,9 +41,7 @@ export const resolveDeployment = (dirFlag, prompts) => {
41
41
  `Add this line to your .env:\n PUBLIC_URL=http://localhost:${port}`);
42
42
  return undefined;
43
43
  }
44
- // Local's PUBLIC_URL is the derived localhost URL — probing it would
45
- // duplicate the health check recreateContainer just ran, so remote-only.
46
- const publicUrl = mode === "remote" ? readEnvPublicUrl(envFilePath) : undefined;
44
+ const publicUrl = readEnvPublicUrl(envFilePath);
47
45
  return { mode, targetDir, envFilePath, port, vaultPath, publicUrl };
48
46
  };
49
47
  /**
@@ -59,6 +57,15 @@ export const ensureDaemonRunning = (docker, prompts) => {
59
57
  : buildDaemonNotRunningMessage("."));
60
58
  return false;
61
59
  };
60
+ /**
61
+ * Whether the post-start public-URL probe should run — and, when it should,
62
+ * proof that publicUrl is set. Local's PUBLIC_URL is the derived localhost
63
+ * URL, so probing it would only duplicate the health check the start cycle
64
+ * just ran.
65
+ */
66
+ const shouldRunPostStartProbe = (deployment) => {
67
+ return deployment.mode === "remote" && deployment.publicUrl !== undefined;
68
+ };
62
69
  /**
63
70
  * One-shot informational probe of the public /healthz after a confirmed
64
71
  * container start. Never a gate: a failure warns and the command still
@@ -127,7 +134,7 @@ export const recreateContainer = async (params, deps) => {
127
134
  spinner.stop("Server is up — health check passed.");
128
135
  // Informational only — the container is confirmed healthy above, so the
129
136
  // public-URL result never changes the exit code.
130
- if (deployment.mode === "remote" && deployment.publicUrl) {
137
+ if (shouldRunPostStartProbe(deployment)) {
131
138
  await reportPublicUrlProbe(deployment.publicUrl, { prompts, fetchFn });
132
139
  }
133
140
  return 0;
@@ -157,7 +164,7 @@ export const runDown = async (flags, deps) => {
157
164
  return 1;
158
165
  }
159
166
  prompts.log("Container stopped and removed. Your vault data, search index, and settings are untouched.");
160
- prompts.outro(`Start again with: npx vault-cortex@latest restart --dir "${initialized.targetDir}"`);
167
+ prompts.outro(`Start again with: npx vault-cortex@latest start --dir "${initialized.targetDir}"`);
161
168
  return 0;
162
169
  };
163
170
  /**
@@ -174,7 +181,7 @@ export const runLogs = async (flags, deps) => {
174
181
  if (!ensureDaemonRunning(docker, prompts))
175
182
  return 1;
176
183
  if (!docker.containerExists()) {
177
- prompts.error("No vault-cortex container — start it with `npx vault-cortex@latest restart`.");
184
+ prompts.error(`No vault-cortex container — start it with: npx vault-cortex@latest start --dir "${initialized.targetDir}"`);
178
185
  return 1;
179
186
  }
180
187
  return await docker.streamLogs({
@@ -183,13 +190,13 @@ export const runLogs = async (flags, deps) => {
183
190
  });
184
191
  };
185
192
  /**
186
- * Re-creates the container from the .env on disk and verifies health.
187
- * Unlike `docker restart`, this applies .env edits (the env-file is only
188
- * read at container creation); unlike upgrade, it never pulls an image.
193
+ * Shared start/restart cycle: re-create the container from the .env on disk
194
+ * and verify health. One implementation, two command names — the labels are
195
+ * the only divergence, phrased for the intent each name serves.
189
196
  */
190
- export const runRestart = async (flags, deps) => {
197
+ const runRecreateFromEnv = async (flags, deps, labels) => {
191
198
  const { prompts, docker, fetchFn } = deps;
192
- prompts.intro("vault-cortex restart");
199
+ prompts.intro(labels.introTitle);
193
200
  const deployment = resolveDeployment(flags.dir, prompts);
194
201
  if (!deployment)
195
202
  return 1;
@@ -198,7 +205,33 @@ export const runRestart = async (flags, deps) => {
198
205
  const exitCode = await recreateContainer({ deployment, healthTimeoutMs: deps.healthTimeoutMs }, { prompts, docker, fetchFn });
199
206
  if (exitCode !== 0)
200
207
  return exitCode;
201
- prompts.log("Applied the current .env settings.");
202
- prompts.outro("Restart complete.");
208
+ prompts.log(labels.successLog);
209
+ prompts.outro(labels.outroMessage);
203
210
  return 0;
204
211
  };
212
+ /**
213
+ * Starts the server from the saved .env — the command name users reach for
214
+ * when nothing is running yet (after `down`, or an init that skipped the
215
+ * start offer). Same cycle as restart: if a container is already running it
216
+ * is safely replaced, and `docker run` pulls the image when it's missing.
217
+ */
218
+ export const runStart = async (flags, deps) => {
219
+ return runRecreateFromEnv(flags, deps, {
220
+ introTitle: "vault-cortex start",
221
+ successLog: "Started with the settings from .env.",
222
+ outroMessage: "Start complete.",
223
+ });
224
+ };
225
+ /**
226
+ * Re-creates the container from the .env on disk and verifies health.
227
+ * Unlike `docker restart`, this applies .env edits (the env-file is only
228
+ * read at container creation); unlike upgrade, it never replaces an image
229
+ * you already have (`docker run` still pulls when none exists locally).
230
+ */
231
+ export const runRestart = async (flags, deps) => {
232
+ return runRecreateFromEnv(flags, deps, {
233
+ introTitle: "vault-cortex restart",
234
+ successLog: "Applied the current .env settings.",
235
+ outroMessage: "Restart complete.",
236
+ });
237
+ };
package/dist/main.js CHANGED
@@ -2,7 +2,7 @@ import { runConfigure } from "./configure.js";
2
2
  import { createDockerRunner } from "./docker.js";
3
3
  import { runGetSyncToken } from "./get-sync-token.js";
4
4
  import { runInit } from "./init.js";
5
- import { runDown, runLogs, runRestart } from "./lifecycle.js";
5
+ import { runDown, runLogs, runRestart, runStart } from "./lifecycle.js";
6
6
  import { buildProgram } from "./program.js";
7
7
  import { createPrompts } from "./prompts.js";
8
8
  import { runUpgrade } from "./upgrade.js";
@@ -24,6 +24,11 @@ export const run = async (version) => {
24
24
  docker: createDockerRunner(),
25
25
  fetchFn: fetch,
26
26
  }),
27
+ runStart: (flags) => runStart(flags, {
28
+ prompts: createPrompts(),
29
+ docker: createDockerRunner(),
30
+ fetchFn: fetch,
31
+ }),
27
32
  runRestart: (flags) => runRestart(flags, {
28
33
  prompts: createPrompts(),
29
34
  docker: createDockerRunner(),
package/dist/messages.js CHANGED
@@ -45,14 +45,18 @@ export const buildDockerNotInstalledMessage = (params) => {
45
45
  // targetDir is quoted: these lines are meant to be copy-pasted into a
46
46
  // shell, and an unquoted path breaks on spaces or special characters.
47
47
  const upgradeCommand = (targetDir) => `npx vault-cortex@latest upgrade --dir "${targetDir}"`;
48
- const startServerLine = (targetDir) => `Start the server:\n ${upgradeCommand(targetDir)}`;
48
+ // Start guidance prints `start`, not `upgrade` telling a user who has never
49
+ // started anything to run "upgrade" reads as updating something they don't
50
+ // have. `start` runs the same re-create cycle and pulls the image on demand.
51
+ export const startCommand = (targetDir) => `npx vault-cortex@latest start --dir "${targetDir}"`;
52
+ const startServerLine = (targetDir) => `Start the server:\n ${startCommand(targetDir)}`;
49
53
  /** Remote start line: running, blocked on the missing sync token, or ready to start. */
50
54
  const remoteStartLine = (params) => {
51
55
  const { targetDir, started, obsidianTokenMissing } = params;
52
56
  if (started)
53
57
  return "The server is running.";
54
58
  if (obsidianTokenMissing) {
55
- return `Fill in OBSIDIAN_AUTH_TOKEN in ${targetDir}/.env, then start the server:\n ${upgradeCommand(targetDir)}`;
59
+ return `Fill in OBSIDIAN_AUTH_TOKEN in ${targetDir}/.env, then start the server:\n ${startCommand(targetDir)}`;
56
60
  }
57
61
  return startServerLine(targetDir);
58
62
  };
package/dist/program.js CHANGED
@@ -29,6 +29,13 @@ export const buildProgram = (options) => {
29
29
  .action(async (flags) => {
30
30
  process.exitCode = await options.runUpgrade(flags);
31
31
  });
32
+ program
33
+ .command("start")
34
+ .description("Start the server with the saved settings and verify health (same cycle as restart)")
35
+ .option("--dir <path>", "directory containing .env (default: ./vault-cortex)")
36
+ .action(async (flags) => {
37
+ process.exitCode = await options.runStart(flags);
38
+ });
32
39
  program
33
40
  .command("restart")
34
41
  .description("Re-create the container from .env and verify health (applies .env edits; no image pull)")
package/dist/scaffold.js CHANGED
@@ -40,8 +40,11 @@ export const readEnvVaultPath = (envFilePath) => {
40
40
  };
41
41
  /**
42
42
  * Returns true when the .env file has an active (uncommented) PUBLIC_URL line.
43
- * Used by upgrade to detect .env files from the old compose-based CLI, where
44
- * PUBLIC_URL was provided by docker-compose defaults rather than the .env.
43
+ * It's the FALSE result that carries the signal: the old compose-based CLI's
44
+ * generated .env never held a PUBLIC_URL line (the compose file's environment
45
+ * defaults supplied it), so a local .env without one predates the docker-run
46
+ * migration — callers use the negation to ask the user to add the line
47
+ * instead of starting a server missing a required variable.
45
48
  */
46
49
  export const hasEnvPublicUrl = (envFilePath) => {
47
50
  if (!existsSync(envFilePath))
@@ -61,7 +64,11 @@ export const readEnvPublicUrl = (envFilePath) => {
61
64
  // A whitespace-only line matches the regex and trims to "" — normalize to
62
65
  // undefined so the non-empty contract holds ("" is never a legitimate URL).
63
66
  const publicUrlValue = match?.[1].trim();
64
- return publicUrlValue || undefined;
67
+ // Strip trailing slashes (mirroring askPublicUrl's prompt-side
68
+ // normalization): consumers append paths to this base, and a hand-edited
69
+ // `https://host/` would otherwise print broken `//mcp` connect URLs.
70
+ const normalizedPublicUrl = publicUrlValue?.replace(/\/+$/, "");
71
+ return normalizedPublicUrl || undefined;
65
72
  };
66
73
  /**
67
74
  * Detects the deployment mode from a .env file. Remote mode requires
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vault-cortex",
3
- "version": "0.9.0-beta.45",
3
+ "version": "0.10.0",
4
4
  "description": "Set up a Vault Cortex MCP server for your Obsidian vault in one command: npx vault-cortex init",
5
5
  "license": "MIT",
6
6
  "type": "module",