threadnote 0.2.0 → 0.2.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 +109 -101
- package/dist/threadnote.cjs +113 -22
- package/docs/troubleshooting.md +43 -0
- package/package.json +1 -1
- package/scripts/install.sh +5 -1
package/README.md
CHANGED
|
@@ -4,8 +4,24 @@
|
|
|
4
4
|
It is intentionally scoped to curated docs, memories, skills, and handoffs. It is not a source-navigation replacement,
|
|
5
5
|
and it does not index whole repositories by default.
|
|
6
6
|
|
|
7
|
+
## Real-World Uses
|
|
8
|
+
|
|
9
|
+
Want to continue work in a fresh agent session? `threadnote install` adds user-level Codex and Claude instructions so
|
|
10
|
+
new agents automatically recall recent handoffs and relevant memories before they start changing code.
|
|
11
|
+
|
|
12
|
+
Implemented a feature a while ago and need to pick it up again? Ask the agent to recall the feature, branch, or repo.
|
|
13
|
+
Threadnote returns auditable `viking://` pointers that the agent can read before deciding what still matters.
|
|
14
|
+
|
|
15
|
+
Switching between Codex and Claude? Install the MCP adapter for both. The user-level instructions tell agents to store a
|
|
16
|
+
handoff before they pause, so the next agent can search the same local memory layer instead of reconstructing context
|
|
17
|
+
from chat history.
|
|
18
|
+
|
|
19
|
+
Found a durable workflow fact, like how a repo runs tests or where release notes live? Ask the agent to remember it.
|
|
20
|
+
Threadnote keeps that memory local and searchable without editing unrelated repo files.
|
|
21
|
+
|
|
7
22
|
## Safety Model
|
|
8
23
|
|
|
24
|
+
- Machine writes stay **locally** under `THREADNOTE_HOME`, which defaults to `~/.openviking`.
|
|
9
25
|
- Curated manifests only: seed commands import only paths listed in `config/seed-manifest.example.yaml` or an explicit
|
|
10
26
|
per-developer manifest.
|
|
11
27
|
- Ignore rules: `.threadnoteignore` excludes build output, binary artifacts, local auth files, env files, and logs.
|
|
@@ -15,7 +31,6 @@ and it does not index whole repositories by default.
|
|
|
15
31
|
- User instructions: `install` upserts a managed Threadnote block in `~/.codex/AGENTS.md` and `~/.claude/CLAUDE.md`
|
|
16
32
|
without replacing existing personal instructions.
|
|
17
33
|
- Agent config changes are explicit: `mcp-install` prints commands and snippets by default; use `--apply` to run them.
|
|
18
|
-
- Machine writes stay under `THREADNOTE_HOME`, which defaults to `~/.openviking`.
|
|
19
34
|
|
|
20
35
|
## Install
|
|
21
36
|
|
|
@@ -28,6 +43,15 @@ curl -fsSL https://raw.githubusercontent.com/Kashkovsky/threadnote/main/scripts/
|
|
|
28
43
|
This installs the published package from npmjs and runs `threadnote install`. It does not use npm `postinstall`,
|
|
29
44
|
because setup writes local machine config and should be an explicit action.
|
|
30
45
|
|
|
46
|
+
Start the local server and confirm it is healthy:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
threadnote start
|
|
50
|
+
threadnote doctor --dry-run
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
If install or health checks fail, see `docs/troubleshooting.md`.
|
|
54
|
+
|
|
31
55
|
To force a runtime:
|
|
32
56
|
|
|
33
57
|
```bash
|
|
@@ -40,91 +64,13 @@ Or install manually:
|
|
|
40
64
|
```bash
|
|
41
65
|
npm install --global threadnote
|
|
42
66
|
threadnote install
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
For a one-off check before installing globally:
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
npm exec --yes threadnote@latest -- doctor --dry-run
|
|
49
|
-
bunx threadnote@latest doctor --dry-run
|
|
50
|
-
deno run --allow-read --allow-env --allow-run --allow-net npm:threadnote@latest doctor --dry-run
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Avoid using `npm exec` for `threadnote install`; durable shims and MCP launchers should point at a stable global installation, not npm's temporary package cache.
|
|
54
|
-
|
|
55
|
-
## Source Checkout
|
|
56
|
-
|
|
57
|
-
For local development from this repo:
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
npm install
|
|
61
|
-
npm run build
|
|
62
|
-
npm run doctor -- --dry-run
|
|
63
|
-
npm run threadnote -- install
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
`install` writes a small command shim to `~/.local/bin/threadnote` by default and upserts user-level agent guidance in
|
|
67
|
-
`~/.codex/AGENTS.md` and `~/.claude/CLAUDE.md`. After that, use the short command from any repo or working directory:
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
threadnote doctor --dry-run
|
|
71
|
-
threadnote init-manifest --repo ~/src/my-service --repo ~/work/mobile-app
|
|
72
67
|
threadnote start
|
|
73
|
-
threadnote
|
|
74
|
-
threadnote seed-skills --dry-run
|
|
68
|
+
threadnote doctor --dry-run
|
|
75
69
|
```
|
|
76
70
|
|
|
77
|
-
|
|
78
|
-
After reviewing dry-run output, remove `--dry-run` for the operation you want to perform.
|
|
79
|
-
|
|
80
|
-
The bundled `config/seed-manifest.example.yaml` is only an example. Each developer should create a local manifest at
|
|
81
|
-
`~/.openviking/seed-manifest.yaml` with `threadnote init-manifest`; repo paths can be anywhere.
|
|
82
|
-
|
|
83
|
-
## Commands
|
|
84
|
-
|
|
85
|
-
- `doctor`: checks prerequisites, the generated command shim, manifest shape, templates, and local OpenViking health.
|
|
86
|
-
- `install`: installs `openviking[local-embed]==0.3.12` if missing, creates `~/.openviking` config files if absent,
|
|
87
|
-
writes the command shim, and upserts user-level agent instructions.
|
|
88
|
-
- `repair`: fixes install/config/shim/manifest/server health issues and rewrites Codex/Claude MCP configs from the
|
|
89
|
-
current checkout.
|
|
90
|
-
- `start`: starts `openviking-server` on `127.0.0.1:1933`.
|
|
91
|
-
- `stop`: stops the detached server pid or macOS LaunchAgent.
|
|
92
|
-
- `uninstall`: removes Threadnote shims, MCP config, launchd config, and managed user instructions. Memories are
|
|
93
|
-
preserved by default; pass `--erase-memories` to delete `THREADNOTE_HOME`.
|
|
94
|
-
- `init-manifest`: creates or updates `~/.openviking/seed-manifest.yaml` from one or more developer repo roots.
|
|
95
|
-
- `seed`: imports curated repo guidance and docs from the manifest.
|
|
96
|
-
- `seed-skills`: imports global and repo-local `SKILL.md` files as a searchable resource catalog. Use
|
|
97
|
-
`seed-skills --native` only after configuring a working VLM provider.
|
|
98
|
-
- `mcp-install codex|claude`: installs or prints OpenViking MCP configuration for Codex or Claude.
|
|
99
|
-
- `remember`: stores a durable memory.
|
|
100
|
-
- `recall`: searches shared OpenViking context. It infers repo or skill scope from queries like
|
|
101
|
-
`skills for api service`; use `--uri` or `--no-infer-scope` to override.
|
|
102
|
-
- `read`: reads a `viking://` URI returned by `recall` or `list`.
|
|
103
|
-
- `list` / `ls`: lists a `viking://` directory.
|
|
104
|
-
- `handoff`: stores current git state and next-step notes as a durable handoff.
|
|
105
|
-
- `forget`: removes a `viking://` URI.
|
|
106
|
-
- `export-pack` / `import-pack`: moves local context through `.ovpack` files.
|
|
107
|
-
|
|
108
|
-
## Configuration
|
|
109
|
-
|
|
110
|
-
Environment variables:
|
|
111
|
-
|
|
112
|
-
- `THREADNOTE_HOME`: local state directory, default `~/.openviking`.
|
|
113
|
-
- `THREADNOTE_MANIFEST`: seed manifest path. Defaults to `~/.openviking/seed-manifest.yaml` if present, otherwise
|
|
114
|
-
the bundled example manifest.
|
|
115
|
-
- `THREADNOTE_ACCOUNT`: OpenViking account header/config value, default `local`.
|
|
116
|
-
- `THREADNOTE_USER`: OpenViking user value, default local username.
|
|
117
|
-
- `THREADNOTE_AGENT_ID`: shared agent identity, default `threadnote`.
|
|
118
|
-
- `THREADNOTE_OPENVIKING_VERSION`: package version to install, default `0.3.12`.
|
|
119
|
-
- `THREADNOTE_BIN_DIR`: directory for the `threadnote` shim, default `~/.local/bin`.
|
|
120
|
-
- `THREADNOTE_HOST`: local bind host, default `127.0.0.1`.
|
|
121
|
-
- `THREADNOTE_PORT`: local bind port, default `1933`.
|
|
122
|
-
|
|
123
|
-
Local projects using `localhost:80` or `localhost:443` do not conflict with OpenViking on `127.0.0.1:1933`. A conflict
|
|
124
|
-
only occurs when another process already owns the same host and port. If that happens, choose a different
|
|
125
|
-
`THREADNOTE_PORT`.
|
|
71
|
+
### MCP
|
|
126
72
|
|
|
127
|
-
|
|
73
|
+
Make the agents you use aware of Threadnote. Use only the MCP install lines for agents you actually use. Open a fresh Codex or Claude session after installing MCP so the new server registration is loaded.
|
|
128
74
|
|
|
129
75
|
Dry-run examples:
|
|
130
76
|
|
|
@@ -166,47 +112,109 @@ If a future OpenViking build exposes a healthy native endpoint, install it expli
|
|
|
166
112
|
threadnote mcp-install claude --native-http --apply
|
|
167
113
|
```
|
|
168
114
|
|
|
169
|
-
|
|
115
|
+
### Seed Local Repos
|
|
116
|
+
|
|
117
|
+
Memories and handoffs work before seeding. Seed local repos when you want agents to recall repo guidance, docs, and
|
|
118
|
+
skills without reopening the same files by hand.
|
|
170
119
|
|
|
171
|
-
|
|
120
|
+
Create or update the local manifest with the repos you care about:
|
|
172
121
|
|
|
173
122
|
```bash
|
|
174
|
-
threadnote
|
|
123
|
+
threadnote init-manifest --repo ~/src/my-service --repo ~/work/mobile-app
|
|
175
124
|
```
|
|
176
125
|
|
|
177
|
-
|
|
126
|
+
Review what will be imported, then seed curated repo guidance:
|
|
178
127
|
|
|
179
128
|
```bash
|
|
180
|
-
threadnote
|
|
129
|
+
threadnote seed --dry-run
|
|
130
|
+
threadnote seed
|
|
181
131
|
```
|
|
182
132
|
|
|
183
|
-
|
|
133
|
+
Optionally seed shared and repo-local skills:
|
|
184
134
|
|
|
185
135
|
```bash
|
|
186
|
-
threadnote
|
|
136
|
+
threadnote seed-skills --dry-run
|
|
137
|
+
threadnote seed-skills
|
|
187
138
|
```
|
|
188
139
|
|
|
189
|
-
|
|
190
|
-
remove that with the package manager you used to install it.
|
|
140
|
+
When you add another local repo later, rerun `init-manifest --repo <path>` and seed again.
|
|
191
141
|
|
|
192
|
-
|
|
142
|
+
This is it! Start working with your agents as usual. The agent will automatically recall relevant memories and store the new ones. If you want to force it to recall/handoff something, just ask explicitly.
|
|
143
|
+
|
|
144
|
+
## Commands
|
|
193
145
|
|
|
194
|
-
|
|
195
|
-
|
|
146
|
+
- `doctor`: checks prerequisites, the generated command shim, manifest shape, templates, and local OpenViking health.
|
|
147
|
+
- `install`: installs `openviking[local-embed]==0.3.12` if missing, creates `~/.openviking` config files if absent,
|
|
148
|
+
writes the command shim, and upserts user-level agent instructions.
|
|
149
|
+
- `repair`: fixes install/config/shim/manifest/server health issues and rewrites Codex/Claude MCP configs from the
|
|
150
|
+
current checkout.
|
|
151
|
+
- `start`: starts `openviking-server` on `127.0.0.1:1933`.
|
|
152
|
+
- `stop`: stops the detached server pid or macOS LaunchAgent.
|
|
153
|
+
- `uninstall`: removes Threadnote shims, MCP config, launchd config, and managed user instructions. Memories are
|
|
154
|
+
preserved by default; pass `--erase-memories` to delete `THREADNOTE_HOME`.
|
|
155
|
+
- `init-manifest`: creates or updates `~/.openviking/seed-manifest.yaml` from one or more developer repo roots.
|
|
156
|
+
- `seed`: imports curated repo guidance and docs from the manifest.
|
|
157
|
+
- `seed-skills`: imports global and repo-local `SKILL.md` files as a searchable resource catalog. Use
|
|
158
|
+
`seed-skills --native` only after configuring a working VLM provider.
|
|
159
|
+
- `mcp-install codex|claude`: installs or prints OpenViking MCP configuration for Codex or Claude.
|
|
160
|
+
- `remember`: stores a durable memory.
|
|
161
|
+
- `recall`: searches shared OpenViking context. It infers repo or skill scope from queries like
|
|
162
|
+
`skills for api service`; use `--uri` or `--no-infer-scope` to override.
|
|
163
|
+
- `read`: reads a `viking://` URI returned by `recall` or `list`.
|
|
164
|
+
- `list` / `ls`: lists a `viking://` directory.
|
|
165
|
+
- `handoff`: stores current git state and next-step notes as a durable handoff.
|
|
166
|
+
- `forget`: removes a `viking://` URI.
|
|
167
|
+
- `export-pack` / `import-pack`: moves local context through `.ovpack` files.
|
|
196
168
|
|
|
197
|
-
##
|
|
169
|
+
## Source Checkout
|
|
198
170
|
|
|
199
|
-
|
|
200
|
-
source-checkout development. There is no published TypeScript runner and no runtime npm dependency tree. The generated
|
|
201
|
-
user shim tries Node, Bun, then Deno.
|
|
171
|
+
For local development from this repo:
|
|
202
172
|
|
|
203
173
|
```bash
|
|
204
|
-
npm
|
|
174
|
+
npm install
|
|
205
175
|
npm run build
|
|
206
|
-
npm
|
|
207
|
-
npm
|
|
176
|
+
npm run doctor -- --dry-run
|
|
177
|
+
npm run threadnote -- install
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`install` writes a small command shim to `~/.local/bin/threadnote` by default and upserts user-level agent guidance in
|
|
181
|
+
`~/.codex/AGENTS.md` and `~/.claude/CLAUDE.md`. After that, use the short command from any repo or working directory:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
threadnote doctor --dry-run
|
|
185
|
+
threadnote init-manifest --repo ~/src/my-service --repo ~/work/mobile-app
|
|
186
|
+
threadnote start
|
|
187
|
+
threadnote seed --dry-run
|
|
188
|
+
threadnote seed-skills --dry-run
|
|
208
189
|
```
|
|
209
190
|
|
|
191
|
+
If `~/.local/bin` is not on your `PATH`, either add it or set `THREADNOTE_BIN_DIR` before running `install`.
|
|
192
|
+
After reviewing dry-run output, remove `--dry-run` for the operation you want to perform.
|
|
193
|
+
|
|
194
|
+
The bundled `config/seed-manifest.example.yaml` is only an example. Each developer should create a local manifest at
|
|
195
|
+
`~/.openviking/seed-manifest.yaml` with `threadnote init-manifest`; repo paths can be anywhere.
|
|
196
|
+
|
|
197
|
+
## Configuration
|
|
198
|
+
|
|
199
|
+
Environment variables:
|
|
200
|
+
|
|
201
|
+
- `THREADNOTE_HOME`: local state directory, default `~/.openviking`.
|
|
202
|
+
- `THREADNOTE_MANIFEST`: seed manifest path. Defaults to `~/.openviking/seed-manifest.yaml` if present, otherwise
|
|
203
|
+
the bundled example manifest.
|
|
204
|
+
- `THREADNOTE_ACCOUNT`: OpenViking account header/config value, default `local`.
|
|
205
|
+
- `THREADNOTE_USER`: OpenViking user value, default local username.
|
|
206
|
+
- `THREADNOTE_AGENT_ID`: shared agent identity, default `threadnote`.
|
|
207
|
+
- `THREADNOTE_OPENVIKING_VERSION`: package version to install, default `0.3.12`.
|
|
208
|
+
- `THREADNOTE_BIN_DIR`: directory for the `threadnote` shim, default `~/.local/bin`.
|
|
209
|
+
- `THREADNOTE_HOST`: local bind host, default `127.0.0.1`.
|
|
210
|
+
- `THREADNOTE_PORT`: local bind port, default `1933`.
|
|
211
|
+
|
|
212
|
+
Local projects using `localhost:80` or `localhost:443` do not conflict with OpenViking on `127.0.0.1:1933`. A conflict
|
|
213
|
+
only occurs when another process already owns the same host and port. If that happens, choose a different
|
|
214
|
+
`THREADNOTE_PORT`.
|
|
215
|
+
|
|
216
|
+
## Misc
|
|
217
|
+
|
|
210
218
|
See `docs/migration.md` for switching an existing repo workflow to `threadnote` without deleting canonical
|
|
211
219
|
`AGENTS.md`, `CLAUDE.md`, `.claude/`, or `.agents/` files.
|
|
212
220
|
|
package/dist/threadnote.cjs
CHANGED
|
@@ -6131,8 +6131,12 @@ var DEFAULT_AGENT_ID = "threadnote";
|
|
|
6131
6131
|
var OPENVIKING_PACKAGE_NAME = "openviking[local-embed]";
|
|
6132
6132
|
var OPENVIKING_SERVER_COMMAND = "openviking-server";
|
|
6133
6133
|
var OPENVIKING_MCP_NAME = "threadnote";
|
|
6134
|
+
var PYTHON_SYSTEM_CERTS_MODULE = "pip_system_certs";
|
|
6135
|
+
var PYTHON_SYSTEM_CERTS_PACKAGE = "pip-system-certs";
|
|
6134
6136
|
var LAUNCHD_LABEL = "io.threadnote.openviking";
|
|
6135
6137
|
var MAX_SECRET_MATCHES_TO_PRINT = 5;
|
|
6138
|
+
var START_HEALTH_POLL_INTERVAL_MS = 500;
|
|
6139
|
+
var START_HEALTH_TIMEOUT_MS = 15e3;
|
|
6136
6140
|
var SHIM_MARKER = "Generated by threadnote";
|
|
6137
6141
|
var USER_INSTRUCTIONS_START_MARKER = "<!-- BEGIN THREADNOTE USER INSTRUCTIONS -->";
|
|
6138
6142
|
var USER_INSTRUCTIONS_END_MARKER = "<!-- END THREADNOTE USER INSTRUCTIONS -->";
|
|
@@ -6257,6 +6261,7 @@ async function runDoctor(config, options) {
|
|
|
6257
6261
|
checks.push(await commandCheck("openviking-server", ["--help"]));
|
|
6258
6262
|
checks.push(await firstCommandCheck("openviking cli", ["ov", "openviking"], ["--help"]));
|
|
6259
6263
|
checks.push(await localEmbeddingCheck());
|
|
6264
|
+
checks.push(await pythonSystemCertificatesCheck());
|
|
6260
6265
|
checks.push(await firstCommandCheck("python installer", ["pipx", "uv", "pip3"], ["--version"]));
|
|
6261
6266
|
checks.push(await commandPresenceCheck("codex", ["--version"]));
|
|
6262
6267
|
checks.push(await commandPresenceCheck("claude", ["--version"]));
|
|
@@ -6289,14 +6294,19 @@ async function runInstall(config, options) {
|
|
|
6289
6294
|
const serverPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
|
|
6290
6295
|
if (serverPath) {
|
|
6291
6296
|
console.log(`OpenViking server already installed: ${serverPath}`);
|
|
6297
|
+
const repairReasons = [];
|
|
6292
6298
|
if (await hasLocalEmbeddingDependency(serverPath) === false) {
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6299
|
+
repairReasons.push("local embedding extra is missing");
|
|
6300
|
+
}
|
|
6301
|
+
if (await hasPythonSystemCertificatesPatch(serverPath) === false) {
|
|
6302
|
+
repairReasons.push("Python system certificate bridge is missing");
|
|
6303
|
+
}
|
|
6304
|
+
if (repairReasons.length > 0) {
|
|
6305
|
+
console.log(`OpenViking install needs repair: ${repairReasons.join("; ")}.`);
|
|
6306
|
+
await runInstallCommands(config, options.packageManager, true, options.dryRun === true);
|
|
6296
6307
|
}
|
|
6297
6308
|
} else {
|
|
6298
|
-
|
|
6299
|
-
await maybeRun(options.dryRun === true, installCommand.executable, installCommand.args);
|
|
6309
|
+
await runInstallCommands(config, options.packageManager, false, options.dryRun === true);
|
|
6300
6310
|
}
|
|
6301
6311
|
await writeTemplateIfMissing({
|
|
6302
6312
|
config,
|
|
@@ -6312,9 +6322,9 @@ async function runInstall(config, options) {
|
|
|
6312
6322
|
shouldRepair: (content) => shouldRepairLegacyOvCliConfig(content) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
|
|
6313
6323
|
templatePath: (0, import_node_path4.join)(toolRoot(), "config", "ovcli.conf.template.json")
|
|
6314
6324
|
});
|
|
6315
|
-
console.log("Install complete. Run
|
|
6316
|
-
console.log(" threadnote doctor");
|
|
6325
|
+
console.log("Install complete. Run start, then doctor:");
|
|
6317
6326
|
console.log(" threadnote start");
|
|
6327
|
+
console.log(" threadnote doctor");
|
|
6318
6328
|
}
|
|
6319
6329
|
async function runRepair(config, options) {
|
|
6320
6330
|
const dryRun = options.dryRun === true;
|
|
@@ -6436,8 +6446,9 @@ async function runStart(config, options) {
|
|
|
6436
6446
|
return;
|
|
6437
6447
|
}
|
|
6438
6448
|
const existingHealth = await readOpenVikingHealthIfAvailable(config, 500);
|
|
6449
|
+
const healthUrl = openVikingHealthUrl(config);
|
|
6439
6450
|
if (existingHealth) {
|
|
6440
|
-
console.log(`OpenViking is already healthy at
|
|
6451
|
+
console.log(`OpenViking is already healthy at ${healthUrl}`);
|
|
6441
6452
|
return;
|
|
6442
6453
|
}
|
|
6443
6454
|
if (await isTcpPortOpen(config.host, config.port, 500)) {
|
|
@@ -6445,19 +6456,26 @@ async function runStart(config, options) {
|
|
|
6445
6456
|
`Port ${config.host}:${config.port} is already in use, but it is not a healthy OpenViking server. Set THREADNOTE_PORT or pass --port to use a different port.`
|
|
6446
6457
|
);
|
|
6447
6458
|
}
|
|
6448
|
-
|
|
6459
|
+
const logPath = openVikingLogPath(config);
|
|
6460
|
+
await ensureDirectory((0, import_node_path2.dirname)(logPath), false);
|
|
6449
6461
|
if (options.foreground === true) {
|
|
6450
6462
|
const result = await runInteractive(server, args);
|
|
6451
6463
|
process.exitCode = result;
|
|
6452
6464
|
return;
|
|
6453
6465
|
}
|
|
6454
|
-
const logPath = (0, import_node_path4.join)(config.agentContextHome, "logs", "server.log");
|
|
6455
6466
|
const logFd = (0, import_node_fs3.openSync)(logPath, "a");
|
|
6456
6467
|
const child = spawnDetachedServerWithLog(server, args, logFd);
|
|
6457
6468
|
child.unref();
|
|
6458
6469
|
await (0, import_promises10.writeFile)((0, import_node_path4.join)(config.agentContextHome, "openviking-server.pid"), `${child.pid}
|
|
6459
6470
|
`, "utf8");
|
|
6460
|
-
|
|
6471
|
+
const health = await waitForOpenVikingHealth(config, START_HEALTH_TIMEOUT_MS);
|
|
6472
|
+
if (health) {
|
|
6473
|
+
console.log(`Started OpenViking with pid ${child.pid}. Health OK at ${healthUrl}. Logs: ${logPath}`);
|
|
6474
|
+
return;
|
|
6475
|
+
}
|
|
6476
|
+
throw new Error(
|
|
6477
|
+
`Started OpenViking with pid ${child.pid}, but ${healthUrl} did not become healthy within ${START_HEALTH_TIMEOUT_MS / 1e3}s. Logs: ${logPath}`
|
|
6478
|
+
);
|
|
6461
6479
|
}
|
|
6462
6480
|
function spawnDetachedServerWithLog(server, args, logFd) {
|
|
6463
6481
|
try {
|
|
@@ -6824,6 +6842,21 @@ async function localEmbeddingCheck() {
|
|
|
6824
6842
|
detail: "llama_cpp missing; install will repair with openviking[local-embed]"
|
|
6825
6843
|
};
|
|
6826
6844
|
}
|
|
6845
|
+
async function pythonSystemCertificatesCheck() {
|
|
6846
|
+
const serverPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
|
|
6847
|
+
if (!serverPath) {
|
|
6848
|
+
return { name: "python system certs", status: "warn", detail: "openviking-server missing" };
|
|
6849
|
+
}
|
|
6850
|
+
const hasDependency = await hasPythonSystemCertificatesPatch(serverPath);
|
|
6851
|
+
if (hasDependency === void 0) {
|
|
6852
|
+
return { name: "python system certs", status: "warn", detail: "could not inspect tool Python" };
|
|
6853
|
+
}
|
|
6854
|
+
return hasDependency ? { name: "python system certs", status: "ok", detail: `${PYTHON_SYSTEM_CERTS_MODULE} import works` } : {
|
|
6855
|
+
name: "python system certs",
|
|
6856
|
+
status: "warn",
|
|
6857
|
+
detail: `${PYTHON_SYSTEM_CERTS_MODULE} missing; install will repair corporate TLS support`
|
|
6858
|
+
};
|
|
6859
|
+
}
|
|
6827
6860
|
async function commandShimCheck() {
|
|
6828
6861
|
const shimPath = (0, import_node_path4.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
|
|
6829
6862
|
const content = await readFileIfExists(shimPath);
|
|
@@ -6867,11 +6900,17 @@ async function userAgentInstructionsChecks() {
|
|
|
6867
6900
|
);
|
|
6868
6901
|
}
|
|
6869
6902
|
async function hasLocalEmbeddingDependency(serverPath) {
|
|
6903
|
+
return hasPythonModule(serverPath, "llama_cpp");
|
|
6904
|
+
}
|
|
6905
|
+
async function hasPythonSystemCertificatesPatch(serverPath) {
|
|
6906
|
+
return hasPythonModule(serverPath, PYTHON_SYSTEM_CERTS_MODULE);
|
|
6907
|
+
}
|
|
6908
|
+
async function hasPythonModule(serverPath, moduleName) {
|
|
6870
6909
|
const pythonPath = await siblingPythonForExecutable(serverPath);
|
|
6871
6910
|
if (!pythonPath) {
|
|
6872
6911
|
return void 0;
|
|
6873
6912
|
}
|
|
6874
|
-
const result = await runCommand(pythonPath, ["-c",
|
|
6913
|
+
const result = await runCommand(pythonPath, ["-c", `import ${moduleName}`], { allowFailure: true });
|
|
6875
6914
|
return result.exitCode === 0;
|
|
6876
6915
|
}
|
|
6877
6916
|
async function siblingPythonForExecutable(executablePath) {
|
|
@@ -6904,7 +6943,7 @@ async function healthCheck(config) {
|
|
|
6904
6943
|
}
|
|
6905
6944
|
}
|
|
6906
6945
|
async function readOpenVikingHealth(config, timeoutMs) {
|
|
6907
|
-
return httpGetText(
|
|
6946
|
+
return httpGetText(openVikingHealthUrl(config), timeoutMs);
|
|
6908
6947
|
}
|
|
6909
6948
|
async function readOpenVikingHealthIfAvailable(config, timeoutMs) {
|
|
6910
6949
|
try {
|
|
@@ -6913,24 +6952,62 @@ async function readOpenVikingHealthIfAvailable(config, timeoutMs) {
|
|
|
6913
6952
|
return void 0;
|
|
6914
6953
|
}
|
|
6915
6954
|
}
|
|
6916
|
-
|
|
6955
|
+
function openVikingHealthUrl(config) {
|
|
6956
|
+
return `http://${config.host}:${config.port}/health`;
|
|
6957
|
+
}
|
|
6958
|
+
function openVikingLogPath(config) {
|
|
6959
|
+
return (0, import_node_path4.join)(config.agentContextHome, "logs", "server.log");
|
|
6960
|
+
}
|
|
6961
|
+
async function waitForOpenVikingHealth(config, timeoutMs) {
|
|
6962
|
+
const deadline = Date.now() + timeoutMs;
|
|
6963
|
+
while (Date.now() <= deadline) {
|
|
6964
|
+
const requestTimeoutMs = Math.max(100, Math.min(1e3, deadline - Date.now()));
|
|
6965
|
+
const health = await readOpenVikingHealthIfAvailable(config, requestTimeoutMs);
|
|
6966
|
+
if (health) {
|
|
6967
|
+
return health;
|
|
6968
|
+
}
|
|
6969
|
+
const remainingMs = deadline - Date.now();
|
|
6970
|
+
if (remainingMs <= 0) {
|
|
6971
|
+
break;
|
|
6972
|
+
}
|
|
6973
|
+
await sleep(Math.min(START_HEALTH_POLL_INTERVAL_MS, remainingMs));
|
|
6974
|
+
}
|
|
6975
|
+
return void 0;
|
|
6976
|
+
}
|
|
6977
|
+
async function runInstallCommands(config, preferred, force, dryRun) {
|
|
6978
|
+
const installCommands = await getInstallCommands(config, preferred, force);
|
|
6979
|
+
for (const installCommand of installCommands) {
|
|
6980
|
+
await maybeRun(dryRun, installCommand.executable, installCommand.args);
|
|
6981
|
+
}
|
|
6982
|
+
}
|
|
6983
|
+
async function getInstallCommands(config, preferred, force) {
|
|
6917
6984
|
const packageSpec = `${OPENVIKING_PACKAGE_NAME}==${config.openVikingVersion}`;
|
|
6918
6985
|
const manager = preferred ?? await detectPackageManager();
|
|
6919
6986
|
if (manager === "pipx") {
|
|
6920
|
-
return
|
|
6987
|
+
return [
|
|
6988
|
+
{ executable: "pipx", args: force ? ["install", "--force", packageSpec] : ["install", packageSpec] },
|
|
6989
|
+
{
|
|
6990
|
+
executable: "pipx",
|
|
6991
|
+
args: force ? ["inject", "--force", "openviking", PYTHON_SYSTEM_CERTS_PACKAGE] : ["inject", "openviking", PYTHON_SYSTEM_CERTS_PACKAGE]
|
|
6992
|
+
}
|
|
6993
|
+
];
|
|
6921
6994
|
}
|
|
6922
6995
|
if (manager === "uv") {
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6996
|
+
const uvArgs = ["tool", "install", "--native-tls", "--with", PYTHON_SYSTEM_CERTS_PACKAGE];
|
|
6997
|
+
return [
|
|
6998
|
+
{
|
|
6999
|
+
executable: "uv",
|
|
7000
|
+
args: force ? [...uvArgs, "--force", packageSpec] : [...uvArgs, packageSpec]
|
|
7001
|
+
}
|
|
7002
|
+
];
|
|
6927
7003
|
}
|
|
6928
7004
|
const pipArgs = ["-m", "pip", "install", "--user"];
|
|
6929
7005
|
if (force) {
|
|
6930
7006
|
pipArgs.push("--upgrade", "--force-reinstall");
|
|
6931
7007
|
}
|
|
7008
|
+
pipArgs.push(PYTHON_SYSTEM_CERTS_PACKAGE);
|
|
6932
7009
|
pipArgs.push(packageSpec);
|
|
6933
|
-
return { executable: "python3", args: pipArgs };
|
|
7010
|
+
return [{ executable: "python3", args: pipArgs }];
|
|
6934
7011
|
}
|
|
6935
7012
|
async function detectPackageManager() {
|
|
6936
7013
|
if (await findExecutable(["pipx"])) {
|
|
@@ -7149,11 +7226,20 @@ async function installLaunchAgent(config, dryRun) {
|
|
|
7149
7226
|
return;
|
|
7150
7227
|
}
|
|
7151
7228
|
await ensureDirectory((0, import_node_path2.dirname)(destination), false);
|
|
7229
|
+
await ensureDirectory((0, import_node_path2.dirname)(openVikingLogPath(config)), false);
|
|
7152
7230
|
await (0, import_promises10.writeFile)(destination, rendered, "utf8");
|
|
7153
7231
|
await maybeRun(false, "launchctl", ["unload", destination], { allowFailure: true });
|
|
7154
7232
|
await maybeRun(false, "launchctl", ["load", destination]);
|
|
7155
7233
|
await maybeRun(false, "launchctl", ["start", LAUNCHD_LABEL]);
|
|
7156
|
-
|
|
7234
|
+
const healthUrl = openVikingHealthUrl(config);
|
|
7235
|
+
const health = await waitForOpenVikingHealth(config, START_HEALTH_TIMEOUT_MS);
|
|
7236
|
+
if (health) {
|
|
7237
|
+
console.log(`Installed and started ${LAUNCHD_LABEL}. Health OK at ${healthUrl}`);
|
|
7238
|
+
return;
|
|
7239
|
+
}
|
|
7240
|
+
throw new Error(
|
|
7241
|
+
`Installed and started ${LAUNCHD_LABEL}, but ${healthUrl} did not become healthy within ${START_HEALTH_TIMEOUT_MS / 1e3}s. Logs: ${openVikingLogPath(config)}`
|
|
7242
|
+
);
|
|
7157
7243
|
}
|
|
7158
7244
|
async function removeLaunchAgent(dryRun) {
|
|
7159
7245
|
const launchAgentPath = expandPath(`~/Library/LaunchAgents/${LAUNCHD_LABEL}.plist`);
|
|
@@ -7310,7 +7396,7 @@ function buildMcpInstallCommand(config, agent, name, options) {
|
|
|
7310
7396
|
if (agent === "codex") {
|
|
7311
7397
|
return {
|
|
7312
7398
|
executable: "codex",
|
|
7313
|
-
args: ["mcp", "add", ...env.flatMap((value) => ["--env", value]), name, "--", ...command]
|
|
7399
|
+
args: ["mcp", "add", ...env.flatMap((value) => ["--env", value]), name, "--", "/usr/bin/env", ...command]
|
|
7314
7400
|
};
|
|
7315
7401
|
}
|
|
7316
7402
|
return {
|
|
@@ -7813,6 +7899,11 @@ async function httpGetText(url, timeoutMs) {
|
|
|
7813
7899
|
request.on("error", rejectPromise);
|
|
7814
7900
|
});
|
|
7815
7901
|
}
|
|
7902
|
+
async function sleep(ms) {
|
|
7903
|
+
return new Promise((resolvePromise) => {
|
|
7904
|
+
setTimeout(resolvePromise, ms);
|
|
7905
|
+
});
|
|
7906
|
+
}
|
|
7816
7907
|
async function readHttpStatus(url, timeoutMs) {
|
|
7817
7908
|
return new Promise((resolvePromise) => {
|
|
7818
7909
|
const request = (0, import_node_http.get)(url, (response) => {
|
package/docs/troubleshooting.md
CHANGED
|
@@ -10,6 +10,38 @@ threadnote install
|
|
|
10
10
|
|
|
11
11
|
The installer prefers `pipx`, then `uv`, then `python3 -m pip install --user`.
|
|
12
12
|
|
|
13
|
+
## `uv` Fails With `UnknownIssuer`
|
|
14
|
+
|
|
15
|
+
Some corporate machines trust PyPI through certificates installed in the system keychain. Threadnote passes
|
|
16
|
+
`--native-tls` when it uses `uv` so those system certificates are loaded.
|
|
17
|
+
|
|
18
|
+
If an older install still fails with `invalid peer certificate: UnknownIssuer`, retry with:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
UV_NATIVE_TLS=1 threadnote install
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or use a different Python installer:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
threadnote install --package-manager pipx
|
|
28
|
+
threadnote install --package-manager pip
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Model Download Fails With `CERTIFICATE_VERIFY_FAILED`
|
|
32
|
+
|
|
33
|
+
On first start, OpenViking may download the local embedding model from Hugging Face. If `~/.openviking/logs/server.log`
|
|
34
|
+
shows `SSLCertVerificationError`, `self-signed certificate in certificate chain`, or `Failed to download local embedding
|
|
35
|
+
model`, repair the OpenViking Python environment and start again:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
threadnote repair --package-manager uv
|
|
39
|
+
threadnote start
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Threadnote installs `pip-system-certs` into the OpenViking environment so Python `requests` can use certificates trusted
|
|
43
|
+
by the operating system.
|
|
44
|
+
|
|
13
45
|
## Local Embedding Extra Missing
|
|
14
46
|
|
|
15
47
|
The default OpenViking config uses the local embedding backend. If the server log says `llama-cpp-python` is missing,
|
|
@@ -23,6 +55,14 @@ The installer repairs this by installing `openviking[local-embed]`.
|
|
|
23
55
|
|
|
24
56
|
## Server Health Fails
|
|
25
57
|
|
|
58
|
+
If `doctor` reports `WARN openviking health: connect ECONNREFUSED 127.0.0.1:1933`, the local server is not running.
|
|
59
|
+
Start it and recheck:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
threadnote start
|
|
63
|
+
threadnote doctor --dry-run
|
|
64
|
+
```
|
|
65
|
+
|
|
26
66
|
Check whether the server is running:
|
|
27
67
|
|
|
28
68
|
```bash
|
|
@@ -35,6 +75,9 @@ For detached starts, logs are written to:
|
|
|
35
75
|
~/.openviking/logs/server.log
|
|
36
76
|
```
|
|
37
77
|
|
|
78
|
+
If `start` reports that OpenViking did not become healthy, open that log. Certificate failures during the first embedding
|
|
79
|
+
model download are covered above.
|
|
80
|
+
|
|
38
81
|
## Port Already In Use
|
|
39
82
|
|
|
40
83
|
The default bind address is `127.0.0.1:1933`. This does not conflict with projects serving `localhost:80`,
|
package/package.json
CHANGED
package/scripts/install.sh
CHANGED
|
@@ -125,4 +125,8 @@ if ! have threadnote; then
|
|
|
125
125
|
fi
|
|
126
126
|
|
|
127
127
|
say ""
|
|
128
|
-
say "Threadnote is installed.
|
|
128
|
+
say "Threadnote is installed. Next:"
|
|
129
|
+
say " threadnote start"
|
|
130
|
+
say " threadnote doctor --dry-run"
|
|
131
|
+
say " threadnote mcp-install codex --apply # if you use Codex"
|
|
132
|
+
say " threadnote mcp-install claude --apply # if you use Claude"
|