@empirical-gauzza/cli 1.0.35 → 1.0.37

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/lib/constants.js CHANGED
@@ -1,657 +1,672 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
-
6
- const EMPIRICAL_MCP_NAME = 'empirical';
7
- const EMPIRICAL_MCP_LEGACY_NAMES = ['empirical-live'];
8
- const EMPIRICAL_MCP_URL = process.env.EMPIRICAL_MCP_URL || 'https://empirical.gauzza.com/mcp';
9
- const COPILOT_OAUTH_CLIENT_ID = process.env.EMPIRICAL_COPILOT_OAUTH_CLIENT_ID || 'copilot-cli-empirical';
10
-
11
- // These two lists mirror the schema-level enums in packages/shared/src/memoryGraph.ts.
12
- // They are duplicated rather than imported on purpose: this CLI publishes to npm as
13
- // @empirical-gauzza/cli, and @empirical/shared is a private workspace package, so depending
14
- // on it would break `npm install` for end users. tests/unit/cliMemoryEnumParity.test.js
15
- // fails CI if the copies ever diverge.
16
- const MEMORY_CATEGORIES = [
17
- 'health', 'fitness', 'food', 'supplement', 'mood', 'idea', 'business',
18
- 'build', 'growth', 'learning', 'leadership', 'operations', 'workflow',
19
- 'lead', 'project', 'recipe', 'preference', 'reflection', 'relationship', 'heartbeat'
20
- ];
21
-
22
- const NODE_TYPES = [
23
- 'event', 'entity', 'observation', 'reflection', 'goal', 'concept'
24
- ];
25
-
26
- const DOC_TOPICS = {
27
- memory: `# Empirical Memory Commands
28
-
29
- Use the \`empirical memory\` subcommands to query and manage memories via the REST API.
30
- All commands require a valid access token (see \`empirical doc auth\`).
31
- Errors are written to stderr as JSON. Successful output is written to stdout as JSON.
32
-
33
- ## Subcommands
34
-
35
- ### query
36
- Search memories by natural language.
37
-
38
- empirical memory query --match "coding style preferences" [--top-k 3] [--category build] [--tags ts,node] [--pretty] [--format table]
39
-
40
- Flags:
41
- - \`--match\` (required): natural language search string
42
- - \`--top-k\`: number of results, 1–100, default 3
43
- - \`--category\`: filter by category
44
- - \`--tags\`: comma-separated tags filter
45
- - \`--tags-all\`: comma-separated tags that must ALL be present (AND match)
46
- - \`--status\`: comma-separated status values to keep (for example open,in_progress)
47
- - \`--due-before\`, \`--due-after\`: keep only reminders due in that window (ISO 8601 dates).
48
- "What is due?" is \`--due-before <now> --remind-state pending\`; dueness is a filter, never
49
- something semantic ranking finds on its own
50
- - \`--remind-state\`: comma-separated reminder states to keep: pending, notified, acknowledged, cancelled
51
- - \`--path\`: pin one retrieval pathway (see below)
52
- - \`--hops\`: 1 or 2, graph depth; only valid with \`--path graph\` or \`--path hebbian\`
53
- - \`--pretty\`: pretty-print JSON output
54
- - \`--format table\`: render results as a table instead of JSON (JSON stays the default)
55
-
56
- #### Retrieval pathways
57
-
58
- - \`--path semantic\`: ranked meaning match. Runs the embedding search and scores candidates.
59
- Use it when you are asking a question and want the closest few answers.
60
- - \`--path list\`: filtered browse. Skips ranking and returns the matching set in order, with
61
- paging. Use it when you want everything carrying a tag, status, or category, not a top few.
62
- - \`--path graph\`: relationship walk from the best entry points, out \`--hops\` steps. Use it
63
- when what you want is connected to what you can name rather than worded like it.
64
- - \`--path hebbian\`: co-recall walk, following the links that repeated retrieval strengthened.
65
- Use it when you want what usually comes up alongside this, not what merely resembles it.
66
-
67
- Omitting \`--path\` keeps the default, where the classifier decides which pathway answers.
68
-
69
- ### get
70
- Retrieve one memory by exact ID.
71
-
72
- empirical memory get --memory-id <id> [--pretty]
73
-
74
- ### list
75
- Browse memories deterministically. Use \`--cursor\` from the previous response while \`pagination.hasMore\` is true.
76
-
77
- empirical memory list [--category project] [--tags canonical,launch] [--start-date <ISO>] [--end-date <ISO>] [--limit 20] [--cursor <cursor>] [--oldest] [--pretty] [--format table]
78
-
79
- Flags:
80
- - \`--category\`, \`--tags\`: optional filters
81
- - \`--start-date\`, \`--end-date\`: createdAt bounds
82
- - \`--limit\`: page size, 1–100, default 20
83
- - \`--cursor\`: continuation cursor from a previous page
84
- - \`--oldest\`: sort oldest first; default is newest first
85
- - \`--pretty\`: pretty-print JSON output
86
- - \`--format table\`: render results as a table instead of JSON (JSON stays the default)
87
-
88
- ### acknowledge
89
- Close a due reminder from the terminal.
90
-
91
- empirical memory acknowledge --memory-id <id> [--cancel]
92
-
93
- Marks the reminder done (or dropped, with \`--cancel\`), frees its slot on your plan, and clears
94
- its alert on every surface so no agent keeps relaying it. The id is in the reminder banner.
95
-
96
- ### record
97
- Save a new memory.
98
-
99
- empirical memory record --category build --summary "Use ESM modules for all new packages" [--tags ts,node] [--node-type observation] [--author-type user|agent] [--due <ISO date>]
100
-
101
- \`--due <ISO date>\` turns the memory into a REMINDER: at that time you get one email with a
102
- calendar entry attached. Accepts 2026-10-14 or a full timestamp like 2026-10-14T09:00:00Z.
103
- Any memory can carry one. Plans cap CONCURRENT pending reminders rather than how many you
104
- create per month, so acknowledging or cancelling one frees a slot.
105
-
106
- \`--author-type\` records WHO the fact belongs to, which the transport cannot know. Use
107
- \`user\` for something the user said, decided or prefers; \`agent\` for your own session notes
108
- and findings. Omitted, it is left unresolved rather than guessed. Retrieval reads it to keep
109
- agent notes out of answers about the user's own life and work.
110
-
111
- Passing text that contains code (backticks, \${...}, $(...)): do NOT put it in a double-quoted
112
- \`--summary "..."\` -- the shell substitutes those away before the CLI runs and the write still
113
- succeeds with the code silently missing. Use one of:
114
-
115
- empirical memory record --category build --summary-file ./note.md --tags ts
116
- empirical memory record --category build --summary - --tags ts <<'EOF'
117
- Use \`createChat()\`; raw calls leak \${stage} and 400.
118
- EOF
119
-
120
- (a single-quoted heredoc delimiter, 'EOF', disables substitution). From an MCP-connected agent,
121
- prefer the record_graph_memory tool, which takes JSON and never touches a shell.
122
-
123
- Flags:
124
- - \`--category\` (required unless --json-file): memory category
125
- - \`--summary\` (required unless --summary-file/--json-file): memory summary text; \`-\` reads stdin
126
- - \`--summary-file\`: path to a file whose contents become the summary (safe for code)
127
- - \`--tags\`: comma-separated tags
128
- - \`--node-type\`: node type, default \`observation\`
129
- - \`--json-file\`: path to JSON file with full memory payload
130
-
131
- Both \`--category\` and \`--node-type\` are closed sets. Do not guess a value: anything outside
132
- these lists is rejected and the write does not happen.
133
-
134
- Valid categories (20): health, fitness, food, supplement, mood, idea, business, build, growth,
135
- learning, leadership, operations, workflow, lead, project, recipe, preference, reflection,
136
- relationship, heartbeat.
137
-
138
- Valid node types (6): event, entity, observation, reflection, goal, concept.
139
-
140
- There is no \`product\` category and no \`decision\` node type. Record a decision as
141
- \`--category project --node-type event\`.
142
-
143
- ### note
144
- Add a note to an existing memory.
145
-
146
- empirical memory note --match "ESM modules" --text "confirmed in monorepo migration" [--min-score 0.35]
147
-
148
- Flags:
149
- - \`--match\` (required unless --memory-id): search string to find the memory
150
- - \`--text\` (required): note text to append
151
- - \`--memory-id\`: use exact memory ID (skips fuzzy search)
152
- - \`--min-score\`: minimum similarity score, default 0.35
153
-
154
- ### update
155
- Update an existing memory.
156
-
157
- empirical memory update --match "ESM modules" --patch-json '{"summary":"Updated summary"}' [--min-score 0.35]
158
-
159
- Flags:
160
- - \`--match\` (required unless --memory-id): search string
161
- - \`--patch-json\`: JSON string with fields to update
162
- - \`--json-file\`: path to JSON patch file
163
- - \`--memory-id\`: use exact memory ID
164
- - \`--min-score\`: minimum similarity score, default 0.35
165
-
166
- ### delete
167
- Delete a memory. Without --confirm, runs a dry-run and prints what would be deleted.
168
-
169
- empirical memory delete --match "old preference" --confirm [--min-score 0.35]
170
-
171
- Flags:
172
- - \`--match\` (required unless --memory-id): search string
173
- - \`--confirm\`: actually delete (without this, dry-run only)
174
- - \`--memory-id\`: use exact memory ID
175
- - \`--min-score\`: minimum similarity score, default 0.35
176
-
177
- Bulk delete by tag, category, or everything (exactly one selector):
178
-
179
- empirical memory delete --tag old-preference --confirm
180
- empirical memory delete --category recipe --confirm
181
- empirical memory delete --all --confirm
182
-
183
- Without \`--confirm\`, always a dry-run that prints the count that would be deleted; no writes
184
- happen. Re-run with \`--confirm\` to actually delete.
185
-
186
- ### export / import
187
- Export memories to a versioned, round-trippable JSON document and re-import them later.
188
-
189
- empirical memory export --scope <personal|workspace> [--out <path>] [--workspace-id <id>]
190
- empirical memory import <file> [--dry-run]
191
-
192
- Export writes \`{ schemaVersion: 1, exportedAt, memories: [...] }\`. Import reads that same
193
- shape and POSTs each row through the normal create endpoint, so imported memories get the same
194
- dedupe and auto-link treatment as any other write. \`--dry-run\` on import prints the count that
195
- would be imported without writing anything.
196
-
197
- ### stats
198
- Aggregated counts, scoped to the caller.
199
-
200
- empirical memory stats [--format table]
201
-
202
- Returns total count, counts by category, counts by tag, and oldest/newest memory timestamps.
203
-
204
- ### compact
205
- On-demand near-duplicate scan (the same logic the nightly compaction job runs), scoped to the
206
- caller only. Dry-run by default, matching a terraform plan/apply model.
207
-
208
- empirical memory compact [--category <cat>]
209
- empirical memory compact --apply [--category <cat>]
210
-
211
- Without \`--apply\`, reports proposed merge/link candidate pairs and counts with no writes.
212
- With \`--apply\`, executes the merges and links for real and reports what was applied.
213
-
214
- ## Error output contract
215
- - Errors → stderr as JSON: \`{ "error": true, "code": "...", "message": "..." }\`
216
- - Success → stdout as JSON (compact by default)
217
- `,
218
-
219
- install: `# Empirical Install
220
-
221
- empirical install
222
-
223
- Runs the interactive installer to connect Empirical memory to your AI coding CLI(s).
224
-
225
- ## What it does
226
- 1. Detects installed CLIs: Codex, GitHub Copilot, Claude Code
227
- 2. Prompts for CLI target selection
228
- 3. Asks how agents should interact with Empirical:
229
- - Mode 1: Via MCP tool (recommended) — registers Empirical as an MCP server
230
- - Mode 2: Via CLI commands — writes CLI-direct instruction block, no MCP registration
231
- - Mode 3: Both — MCP primary with CLI fallback
232
- 4. Stores the personalized behavior policy for the native Empirical Memory Skill; global
233
- instruction files are left unchanged
234
- 5. Offers optional browser OAuth login for CLI use, optional headless/API-key bootstrap for
235
- automation, containers, and external runtimes, and optional work-history/scar-tissue guidance
236
- 6. Installs a \`SessionStart\` hook for each selected client that calls the \`empirical\` CLI
237
- directly once per session — deterministic baseline context that doesn't depend on the model
238
- deciding to call a tool. Claude: \`~/.claude/settings.json\`. Codex: \`<CODEX_HOME>/hooks.json\`
239
- (defaults to \`~/.codex\`). Copilot: \`~/.copilot/hooks/empirical-session-start.json\`.
240
- 7. Installs the native \`empirical-memory\` plugin for each selected client. The plugin owns
241
- the Empirical MCP connection and all agent-facing Skills; the CLI does not generate duplicate
242
- \`SKILL.md\` or \`EMPIRICAL_SKILL.md\` files.
243
-
244
- ## Native plugin only
245
-
246
- empirical plugin install <codex|copilot|claude|all> [--all-optional]
247
-
248
- empirical plugin check [--force]
249
-
250
- Installs or refreshes only the native marketplace plugin. It does not run the MCP/auth questionnaire
251
- or modify global instruction files. With no target, the command installs for detected CLIs.
252
-
253
- The CLI performs a best-effort native plugin version check during interactive use (at most once per
254
- day). It is silent for noninteractive/headless commands. Use \`empirical plugin check --force\` to
255
- check immediately.
256
-
257
- Use \`empirical policy show\` to inspect the current native-skill behavior policy and
258
- \`empirical policy set --json '{"allowDiscretionaryUse":false}'\` to change it without rerunning
259
- the installer. The policy is cached locally and refreshed when stale.
260
- `,
261
-
262
- workspace: `# Empirical Workspace Commands
263
-
264
- Manage disposable team workspaces and which Empirical workspace is active for this OAuth client.
265
-
266
- ## Commands
267
-
268
- ### List workspaces
269
-
270
- empirical workspace list
271
-
272
- Shows all available workspaces and which one is currently selected.
273
-
274
- ### Show current workspace
275
-
276
- empirical workspace current
277
-
278
- Prints the currently selected workspace name.
279
-
280
- ### Select a workspace
281
-
282
- empirical workspace select [<number|name>]
283
-
284
- Interactive workspace selector (prompts if no argument given).
285
- Pass a number (from \`list\`) or an exact workspace name.
286
-
287
- empirical workspace select 2
288
- empirical workspace select "My Workspace"
289
-
290
- Note: workspace selection may be admin-locked for some OAuth clients.
291
-
292
- ### Create a demo workspace
293
-
294
- empirical workspace create "Demo Workspace" --description "Temporary shared context"
295
-
296
- Creates a team workspace using your existing plan quota and prints its workspace ID.
297
-
298
- ### Use a workspace
299
-
300
- empirical workspace use <number|name|id>
301
-
302
- Friendly alias for \`workspace select\`. It accepts the list number, exact case-insensitive name, or workspace ID.
303
-
304
- ### Share an active workspace
305
-
306
- empirical workspace invite create --role writer --expires-in-days 7 --max-uses 3
307
- empirical workspace invite list
308
- empirical workspace invite revoke <token>
309
-
310
- Invite commands operate on the active team workspace. Create prints a shareable URL.
311
-
312
- ### Soft-delete a demo workspace
313
-
314
- empirical workspace delete <number|name|id>
315
- empirical workspace delete <number|name|id> --confirm
316
-
317
- Deletion dry-runs by default. \`--confirm\` calls the existing soft-delete endpoint; the Default Workspace cannot be deleted. If the active workspace is deleted, the CLI selects the Default Workspace.
318
- `,
319
-
320
- doctor: `# Empirical Doctor
321
-
322
- empirical doctor [--output json] [--deep]
323
-
324
- Checks the health of your Empirical CLI setup, including why MCP tools might not be showing up
325
- in a host CLI (the most common failure report).
326
-
327
- ## Checks performed
328
-
329
- 1. **Token presence** — verifies a token exists via EMPIRICAL_ACCESS_TOKEN env, apiKey field,
330
- or accessToken field in the token file
331
- 2. **Token type** — detects API key (long alphanumeric, no dots) vs JWT (3 dot-separated parts)
332
- 3. **Token expiry** — if JWT, decodes the payload and checks the \`exp\` field; warns if no \`exp\`
333
- 4. **API connectivity** — calls GET /api/v1/users/me; 200 = OK, anything else = FAIL
334
- 5. **Workspace** — reads workspaceId from the /api/v1/users/me response; warns if null
335
- 6. **MCP hosts detected** — which of codex/claude/copilot were found on this machine
336
- 7. **MCP config registration (per detected host)** — reads that host's own config file
337
- (\`~/.claude.json\`, \`<CODEX_HOME>/config.toml\`, \`~/.copilot/mcp-config.json\`) and confirms
338
- the Empirical MCP server is actually registered there with the current URL
339
- 8. **MCP reachability** — POSTs a real MCP \`initialize\` request to the MCP endpoint with the
340
- resolved access token and confirms a valid JSON-RPC response comes back
341
- 9. **Native plugin presence/version (per detected host)** — runs \`<host> plugin list\` and
342
- reports the installed empirical-memory (and optional) plugin versions
343
- 10. **Hooks presence (per detected host)** — confirms the SessionStart/UserPromptSubmit/Stop
344
- hook scripts exist on disk and are registered in that host's hook manifest
345
-
346
- ## --deep
347
-
348
- Adds a live write/query/delete round trip: records a \`doctor-probe-<timestamp>\` memory, queries
349
- it back, deletes it, and reports elapsed time for each leg. Requires a valid token; writes and
350
- deletes exactly one throwaway memory tagged \`doctor-probe\`.
351
-
352
- ## Output format
353
-
354
- Default: human-readable lines prefixed with [OK], [WARN], or [FAIL].
355
-
356
- empirical doctor --output json
357
-
358
- JSON output: \`{ "checks": [{ "name", "status", "message" }], "overall": "ok"|"warn"|"fail" }\`
359
- Useful for agent/automation use.
360
-
361
- ## Exit codes
362
- - 0: all checks passed (ok or warn)
363
- - 1: one or more checks failed
364
- `,
365
-
366
- agents: `# Empirical Agent Integration
367
-
368
- Empirical memory integrates with three AI coding CLIs.
369
-
370
- ## Supported agents
371
-
372
- | Agent | Config dir | Instruction file | MCP config |
373
- |-------------|---------------------|-------------------------------|-------------------------------------|
374
- | Claude Code | ~/.claude/ | CLAUDE.md | ~/.claude.json (mcpServers) |
375
- | Codex CLI | ~/.codex/ | AGENTS.md | codex mcp add |
376
- | Copilot CLI | ~/.copilot/ | copilot-instructions.md | ~/.copilot/mcp-config.json |
377
-
378
- ## Integration modes
379
-
380
- **Mode 1: MCP tool (recommended)**
381
- Empirical is registered as an MCP server. Agents call memory tools (query_memories,
382
- record_graph_memory, etc.) directly via the MCP protocol. Best for interactive sessions.
383
-
384
- **Mode 2: CLI commands**
385
- Agents run \`empirical memory <subcommand>\` shell commands. No MCP registration required.
386
- Suitable for headless/scripted environments or when MCP is unavailable.
387
-
388
- **Mode 3: Both**
389
- MCP is primary; CLI commands are available as fallback. Maximizes reliability.
390
-
391
- ## Inventory commands
392
-
393
- empirical skill list
394
- empirical device list
395
- empirical device show <deviceId>
396
-
397
- Use these to inspect Skills across Kits and the effective Skills installed for a device.
398
-
399
- The native \`empirical-memory\` plugin owns its Skills and MCP connection. Run
400
- \`empirical install\` to install it and configure detected agents.
401
- `,
402
-
403
- kit: `# Empirical Skills Kits
404
-
405
- Kits are named collections of agent Skills. A Kit can contain Skills authored in Empirical
406
- or discovered from a git repository, then sync the resolved set to your local agents.
407
-
408
- ## Commands
409
-
410
- ### Create and list Kits
411
-
412
- empirical kit create "My Team Kit"
413
- empirical kit list
414
-
415
- ### Add or remove a git source
416
-
417
- empirical kit add-source "My Team Kit" https://github.com/example/skills.git
418
- empirical kit add-source "My Team Kit" https://github.com/example/skills.git --ref main --path skills
419
- empirical kit remove-source "My Team Kit" <source-id>
420
-
421
- Each source is scanned for valid \`SKILL.md\` files. A Skill file must begin with YAML
422
- frontmatter containing both \`name\` and \`description\`, for example:
423
-
424
- ---
425
- name: my-skill
426
- description: Explain when this skill should be used.
427
- ---
428
-
429
- ### Activate and sync
430
-
431
- empirical kit use "My Team Kit"
432
- empirical kit sync
433
- empirical kit sync --dry-run
434
-
435
- \`kit use\` activates a Kit on this device and syncs its Skills into detected agent
436
- directories. \`kit sync\` refreshes the active Kit. Use \`--quiet\` for unattended syncs
437
- and \`--force\` only when you intentionally want to bypass the local-edit guard.
438
-
439
- ### Inventory
440
-
441
- empirical skill list
442
- empirical kit list
443
- empirical kit devices "My Team Kit"
444
- empirical device list
445
- empirical device show <deviceId>
446
-
447
- \`skill list\` shows Skills resolved from owned Kits. \`device list\` shows registered devices and
448
- their active Kits; \`device show\` shows the effective resolved Skills for one device.
449
-
450
- ### Manage devices and autosync
451
-
452
- empirical kit autosync "My Team Kit" on
453
- empirical kit devices "My Team Kit"
454
- empirical kit deactivate "My Team Kit" <device-id>
455
-
456
- Autosync keeps a registered device current. Interactive syncs review new or changed external
457
- Skills; unattended syncs apply them and leave a review marker for the next interactive sync.
458
- `,
459
-
460
- skill: `# Empirical Skills
461
-
462
- Skills are agent-facing \`SKILL.md\` instruction files. Authored Skills can be pushed into a
463
- Kit so they follow you across machines and projects.
464
-
465
- ## Push an authored Skill
466
-
467
- empirical skill list
468
-
469
- empirical skill push path/to/SKILL.md
470
- empirical skill push path/to/SKILL.md --kit "My Team Kit"
471
-
472
- The file must contain YAML frontmatter at the top with both required fields:
473
-
474
- ---
475
- name: my-skill
476
- description: Explain when this skill should be used.
477
- ---
478
-
479
- The remainder of the file is the Skill body. The CLI validates the frontmatter before upload.
480
- After pushing, activate or sync the target Kit with \`empirical kit use\` or \`empirical kit sync\`.
481
-
482
- ## Review and rollback
483
-
484
- empirical skill history <authored-skill-id>
485
- empirical skill rollback <authored-skill-id> --to-version 2
486
-
487
- Use history to inspect versions and rollback to a known-good version when a Skill needs to be
488
- reverted. External git-sourced Skills are reviewed during Kit sync and are not authored with
489
- this command.
490
- `,
491
-
492
- auth: `# Empirical Auth
493
-
494
- ## Token types
495
-
496
- **API key**: Long alphanumeric string, no dots. Never expires. Preferred for automation.
497
- **JWT**: Three dot-separated base64 parts. Has an expiry (\`exp\` claim). Used for browser OAuth flows.
498
-
499
- ## Browser OAuth login
500
-
501
- empirical auth login
502
-
503
- Opens the browser, completes OAuth with the Empirical account, and saves a CLI session at
504
- \`~/.empirical/oauth-session.json\`. Use this for normal interactive CLI use when you do not
505
- want to provision a permanent API key.
506
-
507
- ## Logging out (and actually revoking)
508
-
509
- empirical auth logout # revoke refresh tokens server-side, then clear locally
510
- empirical auth logout --local-only # forget this machine only, leave other clients signed in
511
- empirical auth logout --client-id <id> # revoke just one client's refresh tokens
512
-
513
- Plain \`logout\` calls \`DELETE /api/v1/users/me/refresh-tokens\`, so the session ends on the
514
- server and not merely on disk. This matters because refresh tokens are written without an
515
- expiry: before this existed, a leaked refresh token stayed redeemable forever and no command,
516
- endpoint or portal screen could revoke it.
517
-
518
- ## Managing API keys
519
-
520
- empirical auth list-keys # ids, labels, created dates
521
- empirical auth rotate-key [--label <name>] # mint an ADDITIONAL key, others keep working
522
- empirical auth rotate-key --replace-all # destructive: wipe every key, mint exactly one
523
- empirical auth revoke-key --key-id <id> # revoke one key
524
- empirical auth revoke-key --all # revoke every key on the account
525
-
526
- Rotating without \`--replace-all\` is the safe cutover: mint the new key, move your integrations
527
- over, then \`revoke-key --key-id\` the old one. A key is displayed once at mint time; only its
528
- hash is stored, so there is no way to read it back later.
529
-
530
- Minting requires a signed-in session (browser login or a fresh OAuth session), because a
531
- permanent API key must not be able to mint more credentials. Revoking does not: it only ever
532
- reduces access, and the CLI needs to be able to revoke with the credential it already holds.
533
-
534
- ## Bootstrap command (headless / non-interactive)
535
-
536
- empirical oauth bootstrap headless --write-env
537
-
538
- Exchanges account credentials (client ID + secret, i.e. your userId + API key) for an access
539
- token via \`grant_type=client_credentials\` — this is NOT a dynamically-registered OAuth client;
540
- the server treats client_id/client_secret as userId/apiKey directly.
541
- Writes token to \`.empirical/headless-token.json\` and \`.empirical/headless.env\`.
542
-
543
- No portal visit or manual copy/paste needed: if no client ID/secret is found via
544
- \`EMPIRICAL_CLIENT_ID\`/\`EMPIRICAL_CLIENT_SECRET\` env vars or \`~/.empirical/oauth-client.json\`
545
- (set via \`empirical oauth client set\`), the CLI self-provisions your account's API key via
546
- \`POST /api/v1/users/me/credentials\`, authenticated with whatever bearer token it already has
547
- (existing token file, or — only if nothing exists yet — one browser login). If you already have
548
- an API key on the account, the CLI will never silently rotate it (that would break other
549
- integrations using it); it asks for explicit confirmation in a TTY, or gives up quietly in
550
- non-interactive contexts. If a stored client is rejected by the server ("Invalid client
551
- credentials"), the CLI discards it and re-provisions automatically on the same run — unless
552
- \`--client-id\`/\`--client-secret\` was passed explicitly, in which case the error is surfaced
553
- as-is.
554
-
555
- ## Refresh command
556
-
557
- empirical oauth refresh headless
558
-
559
- Exchanges a stored refresh token for a new access token. Token file must exist.
560
-
561
- ## Token resolution priority (resolveAccessToken)
562
-
563
- 1. \`EMPIRICAL_ACCESS_TOKEN\` environment variable (skipped if it's an expired JWT, so a
564
- stale session token can never permanently shadow a valid credential below it)
565
- 2. \`apiKey\` field in token file
566
- 3. \`accessToken\` field in token file
567
- 4. \`~/.empirical/oauth-session.json\` browser OAuth session
568
- 5. \`EMPIRICAL_API_KEY\` or \`EMPIRICAL_CLIENT_SECRET\` environment variable, used directly
569
- as a permanent Bearer credential — lets a stateless agent environment authenticate from
570
- client_id/secret alone with no token file and no bootstrap step
571
-
572
- Token file lookup order:
573
- 1. Path in \`EMPIRICAL_TOKEN_OUTPUT\` env var
574
- 2. \`.empirical/headless-token.json\` (current working directory)
575
- 3. \`~/.empirical/headless-token.json\` (home directory)
576
-
577
- The browser OAuth session is stored separately at \`~/.empirical/oauth-session.json\`.
578
-
579
- ## Verify auth status
580
-
581
- empirical auth status
582
- empirical whoami
583
-
584
- Prints a single JSON object: \`{ authenticated, tokenPresent, tokenType, tokenExpiresAt,
585
- tokenExpired, user: { id, email } | null, workspaceId, error }\`. \`whoami\` is an alias for
586
- \`auth status\`. Exits 1 if not authenticated.
587
-
588
- empirical doctor
589
-
590
- Checks token presence, type, expiry, and API connectivity across a broader set of checks
591
- (host MCP config registration, hooks, plugin versions).
592
- `,
593
-
594
- user: `# Empirical User Identity
595
-
596
- ## Via MCP (when Empirical MCP server is connected)
597
-
598
- Use the \`get_user\` MCP tool. Returns: id, email, name, workspaceId, tier, and plan details.
599
-
600
- ## Via CLI
601
-
602
- empirical doctor --output json
603
-
604
- Returns a JSON object with a \`checks\` array. The connectivity check (name: "api")
605
- includes the user record from /api/users/me when successful.
606
-
607
- empirical workspace current
608
-
609
- Prints the active workspace name.
610
-
611
- ## Notes
612
-
613
- - \`get_user\` has no standalone CLI subcommand; use \`empirical doctor --output json\` to
614
- inspect identity from the shell.
615
- - If MCP is unavailable, parse the \`api\` check from doctor JSON output for user context.
616
- `
617
- };
618
-
619
- let cachedCliVersion = null;
620
-
621
- function getCliVersion() {
622
- if (cachedCliVersion) return cachedCliVersion;
623
- try {
624
- const cliPackagePath = path.join(__dirname, '..', 'package.json');
625
- const parsed = JSON.parse(fs.readFileSync(cliPackagePath, 'utf8'));
626
- if (parsed && typeof parsed.version === 'string' && parsed.version.trim()) {
627
- cachedCliVersion = parsed.version.trim();
628
- return cachedCliVersion;
629
- }
630
- } catch (_error) {
631
- // ignore and use fallback
632
- }
633
- cachedCliVersion = 'unknown';
634
- return cachedCliVersion;
635
- }
636
-
637
- function deriveBaseUrlFromMcpUrl() {
638
- try {
639
- const parsed = new URL(EMPIRICAL_MCP_URL);
640
- parsed.pathname = parsed.pathname.replace(/\/mcp\/?$/, '');
641
- return parsed.origin + parsed.pathname.replace(/\/$/, '');
642
- } catch (_error) {
643
- return '';
644
- }
645
- }
646
-
647
- module.exports = {
648
- EMPIRICAL_MCP_NAME,
649
- EMPIRICAL_MCP_LEGACY_NAMES,
650
- EMPIRICAL_MCP_URL,
651
- COPILOT_OAUTH_CLIENT_ID,
652
- MEMORY_CATEGORIES,
653
- NODE_TYPES,
654
- DOC_TOPICS,
655
- getCliVersion,
656
- deriveBaseUrlFromMcpUrl
657
- };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const EMPIRICAL_MCP_NAME = 'empirical';
7
+ const EMPIRICAL_MCP_LEGACY_NAMES = ['empirical-live'];
8
+ const EMPIRICAL_MCP_URL = process.env.EMPIRICAL_MCP_URL || 'https://empirical.gauzza.com/mcp';
9
+ const COPILOT_OAUTH_CLIENT_ID = process.env.EMPIRICAL_COPILOT_OAUTH_CLIENT_ID || 'copilot-cli-empirical';
10
+
11
+ // These two lists mirror the schema-level enums in packages/shared/src/memoryGraph.ts.
12
+ // They are duplicated rather than imported on purpose: this CLI publishes to npm as
13
+ // @empirical-gauzza/cli, and @empirical/shared is a private workspace package, so depending
14
+ // on it would break `npm install` for end users. tests/unit/cliMemoryEnumParity.test.js
15
+ // fails CI if the copies ever diverge.
16
+ const MEMORY_CATEGORIES = [
17
+ 'health', 'fitness', 'food', 'supplement', 'mood', 'idea', 'business',
18
+ 'build', 'growth', 'learning', 'leadership', 'operations', 'workflow',
19
+ 'lead', 'project', 'recipe', 'preference', 'reflection', 'relationship', 'heartbeat'
20
+ ];
21
+
22
+ const NODE_TYPES = [
23
+ 'event', 'entity', 'observation', 'reflection', 'goal', 'concept'
24
+ ];
25
+
26
+ const DOC_TOPICS = {
27
+ memory: `# Empirical Memory Commands
28
+
29
+ Use the \`empirical memory\` subcommands to query and manage memories via the REST API.
30
+ All commands require a valid access token (see \`empirical doc auth\`).
31
+ Errors are written to stderr as JSON. Successful output is written to stdout as JSON.
32
+
33
+ ## Subcommands
34
+
35
+ ### query
36
+ Search memories by natural language.
37
+
38
+ empirical memory query --match "coding style preferences" [--top-k 3] [--category build] [--tags ts,node] [--pretty] [--format table]
39
+
40
+ Flags:
41
+ - \`--match\` (required): natural language search string
42
+ - \`--top-k\`: number of results, 1–100, default 3
43
+ - \`--category\`: filter by category
44
+ - \`--tags\`: comma-separated tags filter
45
+ - \`--tags-all\`: comma-separated tags that must ALL be present (AND match)
46
+ - \`--status\`: comma-separated status values to keep (for example open,in_progress)
47
+ - \`--due-before\`, \`--due-after\`: keep only reminders due in that window (ISO 8601 dates).
48
+ "What is due?" is \`--due-before <now> --remind-state pending\`; dueness is a filter, never
49
+ something semantic ranking finds on its own
50
+ - \`--remind-state\`: comma-separated reminder states to keep: pending, notified, acknowledged, cancelled
51
+ - \`--path\`: pin one retrieval pathway (see below)
52
+ - \`--hops\`: 1 or 2, graph depth; only valid with \`--path graph\` or \`--path hebbian\`
53
+ - \`--pretty\`: pretty-print JSON output
54
+ - \`--format table\`: render results as a table instead of JSON (JSON stays the default)
55
+
56
+ #### Retrieval pathways
57
+
58
+ - \`--path semantic\`: ranked meaning match. Runs the embedding search and scores candidates.
59
+ Use it when you are asking a question and want the closest few answers.
60
+ - \`--path list\`: filtered browse. Skips ranking and returns the matching set in order, with
61
+ paging. Use it when you want everything carrying a tag, status, or category, not a top few.
62
+ - \`--path graph\`: relationship walk from the best entry points, out \`--hops\` steps. Use it
63
+ when what you want is connected to what you can name rather than worded like it.
64
+ - \`--path hebbian\`: co-recall walk, following the links that repeated retrieval strengthened.
65
+ Use it when you want what usually comes up alongside this, not what merely resembles it.
66
+
67
+ Omitting \`--path\` keeps the default, where the classifier decides which pathway answers.
68
+
69
+ ### get
70
+ Retrieve one memory by exact ID.
71
+
72
+ empirical memory get --memory-id <id> [--pretty]
73
+
74
+ ### list
75
+ Browse memories deterministically. Use \`--cursor\` from the previous response while \`pagination.hasMore\` is true.
76
+
77
+ empirical memory list [--category project] [--tags canonical,launch] [--start-date <ISO>] [--end-date <ISO>] [--limit 20] [--cursor <cursor>] [--oldest] [--pretty] [--format table]
78
+
79
+ Flags:
80
+ - \`--category\`, \`--tags\`: optional filters
81
+ - \`--start-date\`, \`--end-date\`: createdAt bounds
82
+ - \`--limit\`: page size, 1–100, default 20
83
+ - \`--cursor\`: continuation cursor from a previous page
84
+ - \`--oldest\`: sort oldest first; default is newest first
85
+ - \`--pretty\`: pretty-print JSON output
86
+ - \`--format table\`: render results as a table instead of JSON (JSON stays the default)
87
+
88
+ ### acknowledge
89
+ Close a due reminder from the terminal.
90
+
91
+ empirical memory acknowledge --memory-id <id> [--cancel]
92
+
93
+ Marks the reminder done (or dropped, with \`--cancel\`), frees its slot on your plan, and clears
94
+ its alert on every surface so no agent keeps relaying it. The id is in the reminder banner.
95
+
96
+ ### record
97
+ Save a new memory.
98
+
99
+ empirical memory record --category build --summary "Use ESM modules for all new packages" [--tags ts,node] [--node-type observation] [--author-type user|agent] [--due <ISO date>]
100
+
101
+ \`--due <ISO date>\` turns the memory into a REMINDER: at that time you get one email with a
102
+ calendar entry attached. Accepts 2026-10-14 or a full timestamp like 2026-10-14T09:00:00Z.
103
+ Any memory can carry one. Plans cap CONCURRENT pending reminders rather than how many you
104
+ create per month, so acknowledging or cancelling one frees a slot.
105
+
106
+ \`--author-type\` records WHO the fact belongs to, which the transport cannot know. Use
107
+ \`user\` for something the user said, decided or prefers; \`agent\` for your own session notes
108
+ and findings. Omitted, it is left unresolved rather than guessed. Retrieval reads it to keep
109
+ agent notes out of answers about the user's own life and work.
110
+
111
+ Passing text that contains code (backticks, \${...}, $(...)): do NOT put it in a double-quoted
112
+ \`--summary "..."\` -- the shell substitutes those away before the CLI runs and the write still
113
+ succeeds with the code silently missing. Use one of:
114
+
115
+ empirical memory record --category build --summary-file ./note.md --tags ts
116
+ empirical memory record --category build --summary - --tags ts <<'EOF'
117
+ Use \`createChat()\`; raw calls leak \${stage} and 400.
118
+ EOF
119
+
120
+ (a single-quoted heredoc delimiter, 'EOF', disables substitution). From an MCP-connected agent,
121
+ prefer the record_graph_memory tool, which takes JSON and never touches a shell.
122
+
123
+ Flags:
124
+ - \`--category\` (required unless --json-file): memory category
125
+ - \`--summary\` (required unless --summary-file/--json-file): memory summary text; \`-\` reads stdin
126
+ - \`--summary-file\`: path to a file whose contents become the summary (safe for code)
127
+ - \`--tags\`: comma-separated tags
128
+ - \`--node-type\`: node type, default \`observation\`
129
+ - \`--json-file\`: path to JSON file with full memory payload
130
+
131
+ Both \`--category\` and \`--node-type\` are closed sets. Do not guess a value: anything outside
132
+ these lists is rejected and the write does not happen.
133
+
134
+ Valid categories (20): health, fitness, food, supplement, mood, idea, business, build, growth,
135
+ learning, leadership, operations, workflow, lead, project, recipe, preference, reflection,
136
+ relationship, heartbeat.
137
+
138
+ Valid node types (6): event, entity, observation, reflection, goal, concept.
139
+
140
+ There is no \`product\` category and no \`decision\` node type. Record a decision as
141
+ \`--category project --node-type event\`.
142
+
143
+ ### note
144
+ Add a note to an existing memory.
145
+
146
+ empirical memory note --match "ESM modules" --text "confirmed in monorepo migration" [--min-score 0.35]
147
+
148
+ Flags:
149
+ - \`--match\` (required unless --memory-id): search string to find the memory
150
+ - \`--text\` (required): note text to append
151
+ - \`--memory-id\`: use exact memory ID (skips fuzzy search)
152
+ - \`--min-score\`: minimum similarity score, default 0.35
153
+
154
+ ### update
155
+ Update an existing memory.
156
+
157
+ empirical memory update --match "ESM modules" --patch-json '{"summary":"Updated summary"}' [--min-score 0.35]
158
+
159
+ Flags:
160
+ - \`--match\` (required unless --memory-id): search string
161
+ - \`--patch-json\`: JSON string with fields to update
162
+ - \`--json-file\`: path to JSON patch file
163
+ - \`--memory-id\`: use exact memory ID
164
+ - \`--min-score\`: minimum similarity score, default 0.35
165
+
166
+ ### delete
167
+ Delete a memory. Without --confirm, runs a dry-run and prints what would be deleted.
168
+
169
+ empirical memory delete --match "old preference" --confirm [--min-score 0.35]
170
+
171
+ Flags:
172
+ - \`--match\` (required unless --memory-id): search string
173
+ - \`--confirm\`: actually delete (without this, dry-run only)
174
+ - \`--memory-id\`: use exact memory ID
175
+ - \`--min-score\`: minimum similarity score, default 0.35
176
+
177
+ Bulk delete by tag, category, or everything (exactly one selector):
178
+
179
+ empirical memory delete --tag old-preference --confirm
180
+ empirical memory delete --category recipe --confirm
181
+ empirical memory delete --all --confirm
182
+
183
+ Without \`--confirm\`, always a dry-run that prints the count that would be deleted; no writes
184
+ happen. Re-run with \`--confirm\` to actually delete.
185
+
186
+ ### export / import
187
+ Export memories to a versioned, round-trippable JSON document and re-import them later.
188
+
189
+ empirical memory export --scope <personal|workspace> [--out <path>] [--workspace-id <id>]
190
+ empirical memory import <file> [--dry-run]
191
+
192
+ Export writes \`{ schemaVersion: 1, exportedAt, memories: [...] }\`. Import reads that same
193
+ shape and POSTs each row through the normal create endpoint, so imported memories get the same
194
+ dedupe and auto-link treatment as any other write. \`--dry-run\` on import prints the count that
195
+ would be imported without writing anything.
196
+
197
+ ### stats
198
+ Aggregated counts, scoped to the caller.
199
+
200
+ empirical memory stats [--format table]
201
+
202
+ Returns total count, counts by category, counts by tag, and oldest/newest memory timestamps.
203
+
204
+ ### compact
205
+ On-demand near-duplicate scan (the same logic the nightly compaction job runs), scoped to the
206
+ caller only. Dry-run by default, matching a terraform plan/apply model.
207
+
208
+ empirical memory compact [--category <cat>]
209
+ empirical memory compact --apply [--category <cat>]
210
+
211
+ Without \`--apply\`, reports proposed merge/link candidate pairs and counts with no writes.
212
+ With \`--apply\`, executes the merges and links for real and reports what was applied.
213
+
214
+ ## Error output contract
215
+ - Errors → stderr as JSON: \`{ "error": true, "code": "...", "message": "..." }\`
216
+ - Success → stdout as JSON (compact by default)
217
+ `,
218
+
219
+ install: `# Empirical Install
220
+
221
+ empirical install
222
+
223
+ Runs the interactive installer to connect Empirical memory to your AI coding CLI(s).
224
+
225
+ ## What it does
226
+ 1. Detects installed CLIs: Codex, GitHub Copilot, Claude Code
227
+ 2. Prompts for CLI target selection
228
+ 3. Asks how agents should interact with Empirical:
229
+ - Mode 1: Via MCP tool (recommended) — registers Empirical as an MCP server
230
+ - Mode 2: Via CLI commands — writes CLI-direct instruction block, no MCP registration
231
+ - Mode 3: Both — MCP primary with CLI fallback
232
+ 4. Stores the personalized behavior policy for the native Empirical Memory Skill; global
233
+ instruction files are left unchanged
234
+ 5. Offers optional browser OAuth login for CLI use, optional headless/API-key bootstrap for
235
+ automation, containers, and external runtimes, and optional work-history/scar-tissue guidance
236
+ 6. Installs a \`SessionStart\` hook for each selected client that calls the \`empirical\` CLI
237
+ directly once per session — deterministic baseline context that doesn't depend on the model
238
+ deciding to call a tool. Claude: \`~/.claude/settings.json\`. Codex: \`<CODEX_HOME>/hooks.json\`
239
+ (defaults to \`~/.codex\`). Copilot: \`~/.copilot/hooks/empirical-session-start.json\`.
240
+ 7. Installs the native \`empirical-memory\` plugin for each selected client. The plugin owns
241
+ the Empirical MCP connection and all agent-facing Skills; the CLI does not generate duplicate
242
+ \`SKILL.md\` or \`EMPIRICAL_SKILL.md\` files.
243
+
244
+ ## Native plugin only
245
+
246
+ empirical plugin install <codex|copilot|claude|all> [--all-optional]
247
+
248
+ Optional plugins: --work-history, --scar-tissue, --todo, --voice (writes as you once a voice is saved)
249
+
250
+ empirical plugin check [--force]
251
+
252
+ Installs or refreshes only the native marketplace plugin. It does not run the MCP/auth questionnaire
253
+ or modify global instruction files. With no target, the command installs for detected CLIs.
254
+
255
+ The CLI performs a best-effort native plugin version check during interactive use (at most once per
256
+ day). It is silent for noninteractive/headless commands. Use \`empirical plugin check --force\` to
257
+ check immediately.
258
+
259
+ Use \`empirical policy show\` to inspect the current native-skill behavior policy and
260
+ \`empirical policy set --json '{"allowDiscretionaryUse":false}'\` to change it without rerunning
261
+ the installer. The policy is cached locally and refreshed when stale.
262
+ `,
263
+
264
+ workspace: `# Empirical Workspace Commands
265
+
266
+ Manage disposable team workspaces and which Empirical workspace is active for this OAuth client.
267
+
268
+ ## Commands
269
+
270
+ ### List workspaces
271
+
272
+ empirical workspace list
273
+
274
+ Shows all available workspaces and which one is currently selected.
275
+
276
+ ### Show current workspace
277
+
278
+ empirical workspace current
279
+
280
+ Prints the currently selected workspace name.
281
+
282
+ ### Select a workspace
283
+
284
+ empirical workspace select [<number|name>]
285
+
286
+ Interactive workspace selector (prompts if no argument given).
287
+ Pass a number (from \`list\`) or an exact workspace name.
288
+
289
+ empirical workspace select 2
290
+ empirical workspace select "My Workspace"
291
+
292
+ Note: workspace selection may be admin-locked for some OAuth clients.
293
+
294
+ ### Create a demo workspace
295
+
296
+ empirical workspace create "Demo Workspace" --description "Temporary shared context"
297
+
298
+ Creates a team workspace using your existing plan quota and prints its workspace ID.
299
+
300
+ ### Use a workspace
301
+
302
+ empirical workspace use <number|name|id>
303
+ empirical workspace use <number|name|id> --global
304
+ empirical workspace unbind
305
+
306
+ Friendly alias for \`workspace select\`. It accepts the list number, exact case-insensitive name, or workspace ID.
307
+
308
+ Inside a git repository, \`use\` binds the workspace to THAT project by writing \`<repo>/.empirical/workspace.json\`; every command run inside the repo uses it, and other repositories are unaffected. \`--global\` sets the machine-wide default instead (the previous behavior). \`--project\` forces a project binding in the current directory even without git. \`unbind\` removes the project binding.
309
+
310
+ Resolution order for every memory command, first match wins: the \`EMPIRICAL_WORKSPACE_ID\` environment variable, the nearest project binding walking up from the current directory, the global selection, then the server's per-client selection. \`workspace current\` and \`whoami\` print which source applied.
311
+
312
+ ### Query across several workspaces
313
+
314
+ empirical memory query --match "<query>" --workspaces "Default Workspace","Project Atlas"
315
+ empirical memory query --match "<query>" --all-workspaces
316
+
317
+ \`--workspaces\` accepts list numbers, names, or workspace IDs (comma-separated) and returns one merged, ranked result set with each row's \`workspaceId\`. Paging (\`--cursor\`, offsets) is not available across several workspaces. Without the flag a query is scoped to the single active workspace exactly as before.
318
+
319
+ ### Share an active workspace
320
+
321
+ empirical workspace invite create --role writer --expires-in-days 7 --max-uses 3
322
+ empirical workspace invite list
323
+ empirical workspace invite revoke <token>
324
+
325
+ Invite commands operate on the active team workspace. Create prints a shareable URL.
326
+
327
+ ### Soft-delete a demo workspace
328
+
329
+ empirical workspace delete <number|name|id>
330
+ empirical workspace delete <number|name|id> --confirm
331
+
332
+ Deletion dry-runs by default. \`--confirm\` calls the existing soft-delete endpoint; the Default Workspace cannot be deleted. If the active workspace is deleted, the CLI selects the Default Workspace.
333
+ `,
334
+
335
+ doctor: `# Empirical Doctor
336
+
337
+ empirical doctor [--output json] [--deep]
338
+
339
+ Checks the health of your Empirical CLI setup, including why MCP tools might not be showing up
340
+ in a host CLI (the most common failure report).
341
+
342
+ ## Checks performed
343
+
344
+ 1. **Token presence** — verifies a token exists via EMPIRICAL_ACCESS_TOKEN env, apiKey field,
345
+ or accessToken field in the token file
346
+ 2. **Token type** — detects API key (long alphanumeric, no dots) vs JWT (3 dot-separated parts)
347
+ 3. **Token expiry** — if JWT, decodes the payload and checks the \`exp\` field; warns if no \`exp\`
348
+ 4. **API connectivity** — calls GET /api/v1/users/me; 200 = OK, anything else = FAIL
349
+ 5. **Workspace** — reads workspaceId from the /api/v1/users/me response; warns if null
350
+ 6. **MCP hosts detected** — which of codex/claude/copilot were found on this machine
351
+ 7. **MCP config registration (per detected host)** — reads that host's own config file
352
+ (\`~/.claude.json\`, \`<CODEX_HOME>/config.toml\`, \`~/.copilot/mcp-config.json\`) and confirms
353
+ the Empirical MCP server is actually registered there with the current URL
354
+ 8. **MCP reachability** — POSTs a real MCP \`initialize\` request to the MCP endpoint with the
355
+ resolved access token and confirms a valid JSON-RPC response comes back
356
+ 9. **Native plugin presence/version (per detected host)** — runs \`<host> plugin list\` and
357
+ reports the installed empirical-memory (and optional) plugin versions
358
+ 10. **Hooks presence (per detected host)** — confirms the SessionStart/UserPromptSubmit/Stop
359
+ hook scripts exist on disk and are registered in that host's hook manifest
360
+
361
+ ## --deep
362
+
363
+ Adds a live write/query/delete round trip: records a \`doctor-probe-<timestamp>\` memory, queries
364
+ it back, deletes it, and reports elapsed time for each leg. Requires a valid token; writes and
365
+ deletes exactly one throwaway memory tagged \`doctor-probe\`.
366
+
367
+ ## Output format
368
+
369
+ Default: human-readable lines prefixed with [OK], [WARN], or [FAIL].
370
+
371
+ empirical doctor --output json
372
+
373
+ JSON output: \`{ "checks": [{ "name", "status", "message" }], "overall": "ok"|"warn"|"fail" }\`
374
+ Useful for agent/automation use.
375
+
376
+ ## Exit codes
377
+ - 0: all checks passed (ok or warn)
378
+ - 1: one or more checks failed
379
+ `,
380
+
381
+ agents: `# Empirical Agent Integration
382
+
383
+ Empirical memory integrates with three AI coding CLIs.
384
+
385
+ ## Supported agents
386
+
387
+ | Agent | Config dir | Instruction file | MCP config |
388
+ |-------------|---------------------|-------------------------------|-------------------------------------|
389
+ | Claude Code | ~/.claude/ | CLAUDE.md | ~/.claude.json (mcpServers) |
390
+ | Codex CLI | ~/.codex/ | AGENTS.md | codex mcp add |
391
+ | Copilot CLI | ~/.copilot/ | copilot-instructions.md | ~/.copilot/mcp-config.json |
392
+
393
+ ## Integration modes
394
+
395
+ **Mode 1: MCP tool (recommended)**
396
+ Empirical is registered as an MCP server. Agents call memory tools (query_memories,
397
+ record_graph_memory, etc.) directly via the MCP protocol. Best for interactive sessions.
398
+
399
+ **Mode 2: CLI commands**
400
+ Agents run \`empirical memory <subcommand>\` shell commands. No MCP registration required.
401
+ Suitable for headless/scripted environments or when MCP is unavailable.
402
+
403
+ **Mode 3: Both**
404
+ MCP is primary; CLI commands are available as fallback. Maximizes reliability.
405
+
406
+ ## Inventory commands
407
+
408
+ empirical skill list
409
+ empirical device list
410
+ empirical device show <deviceId>
411
+
412
+ Use these to inspect Skills across Kits and the effective Skills installed for a device.
413
+
414
+ The native \`empirical-memory\` plugin owns its Skills and MCP connection. Run
415
+ \`empirical install\` to install it and configure detected agents.
416
+ `,
417
+
418
+ kit: `# Empirical Skills Kits
419
+
420
+ Kits are named collections of agent Skills. A Kit can contain Skills authored in Empirical
421
+ or discovered from a git repository, then sync the resolved set to your local agents.
422
+
423
+ ## Commands
424
+
425
+ ### Create and list Kits
426
+
427
+ empirical kit create "My Team Kit"
428
+ empirical kit list
429
+
430
+ ### Add or remove a git source
431
+
432
+ empirical kit add-source "My Team Kit" https://github.com/example/skills.git
433
+ empirical kit add-source "My Team Kit" https://github.com/example/skills.git --ref main --path skills
434
+ empirical kit remove-source "My Team Kit" <source-id>
435
+
436
+ Each source is scanned for valid \`SKILL.md\` files. A Skill file must begin with YAML
437
+ frontmatter containing both \`name\` and \`description\`, for example:
438
+
439
+ ---
440
+ name: my-skill
441
+ description: Explain when this skill should be used.
442
+ ---
443
+
444
+ ### Activate and sync
445
+
446
+ empirical kit use "My Team Kit"
447
+ empirical kit sync
448
+ empirical kit sync --dry-run
449
+
450
+ \`kit use\` activates a Kit on this device and syncs its Skills into detected agent
451
+ directories. \`kit sync\` refreshes the active Kit. Use \`--quiet\` for unattended syncs
452
+ and \`--force\` only when you intentionally want to bypass the local-edit guard.
453
+
454
+ ### Inventory
455
+
456
+ empirical skill list
457
+ empirical kit list
458
+ empirical kit devices "My Team Kit"
459
+ empirical device list
460
+ empirical device show <deviceId>
461
+
462
+ \`skill list\` shows Skills resolved from owned Kits. \`device list\` shows registered devices and
463
+ their active Kits; \`device show\` shows the effective resolved Skills for one device.
464
+
465
+ ### Manage devices and autosync
466
+
467
+ empirical kit autosync "My Team Kit" on
468
+ empirical kit devices "My Team Kit"
469
+ empirical kit deactivate "My Team Kit" <device-id>
470
+
471
+ Autosync keeps a registered device current. Interactive syncs review new or changed external
472
+ Skills; unattended syncs apply them and leave a review marker for the next interactive sync.
473
+ `,
474
+
475
+ skill: `# Empirical Skills
476
+
477
+ Skills are agent-facing \`SKILL.md\` instruction files. Authored Skills can be pushed into a
478
+ Kit so they follow you across machines and projects.
479
+
480
+ ## Push an authored Skill
481
+
482
+ empirical skill list
483
+
484
+ empirical skill push path/to/SKILL.md
485
+ empirical skill push path/to/SKILL.md --kit "My Team Kit"
486
+
487
+ The file must contain YAML frontmatter at the top with both required fields:
488
+
489
+ ---
490
+ name: my-skill
491
+ description: Explain when this skill should be used.
492
+ ---
493
+
494
+ The remainder of the file is the Skill body. The CLI validates the frontmatter before upload.
495
+ After pushing, activate or sync the target Kit with \`empirical kit use\` or \`empirical kit sync\`.
496
+
497
+ ## Review and rollback
498
+
499
+ empirical skill history <authored-skill-id>
500
+ empirical skill rollback <authored-skill-id> --to-version 2
501
+
502
+ Use history to inspect versions and rollback to a known-good version when a Skill needs to be
503
+ reverted. External git-sourced Skills are reviewed during Kit sync and are not authored with
504
+ this command.
505
+ `,
506
+
507
+ auth: `# Empirical Auth
508
+
509
+ ## Token types
510
+
511
+ **API key**: Long alphanumeric string, no dots. Never expires. Preferred for automation.
512
+ **JWT**: Three dot-separated base64 parts. Has an expiry (\`exp\` claim). Used for browser OAuth flows.
513
+
514
+ ## Browser OAuth login
515
+
516
+ empirical auth login
517
+
518
+ Opens the browser, completes OAuth with the Empirical account, and saves a CLI session at
519
+ \`~/.empirical/oauth-session.json\`. Use this for normal interactive CLI use when you do not
520
+ want to provision a permanent API key.
521
+
522
+ ## Logging out (and actually revoking)
523
+
524
+ empirical auth logout # revoke refresh tokens server-side, then clear locally
525
+ empirical auth logout --local-only # forget this machine only, leave other clients signed in
526
+ empirical auth logout --client-id <id> # revoke just one client's refresh tokens
527
+
528
+ Plain \`logout\` calls \`DELETE /api/v1/users/me/refresh-tokens\`, so the session ends on the
529
+ server and not merely on disk. This matters because refresh tokens are written without an
530
+ expiry: before this existed, a leaked refresh token stayed redeemable forever and no command,
531
+ endpoint or portal screen could revoke it.
532
+
533
+ ## Managing API keys
534
+
535
+ empirical auth list-keys # ids, labels, created dates
536
+ empirical auth rotate-key [--label <name>] # mint an ADDITIONAL key, others keep working
537
+ empirical auth rotate-key --replace-all # destructive: wipe every key, mint exactly one
538
+ empirical auth revoke-key --key-id <id> # revoke one key
539
+ empirical auth revoke-key --all # revoke every key on the account
540
+
541
+ Rotating without \`--replace-all\` is the safe cutover: mint the new key, move your integrations
542
+ over, then \`revoke-key --key-id\` the old one. A key is displayed once at mint time; only its
543
+ hash is stored, so there is no way to read it back later.
544
+
545
+ Minting requires a signed-in session (browser login or a fresh OAuth session), because a
546
+ permanent API key must not be able to mint more credentials. Revoking does not: it only ever
547
+ reduces access, and the CLI needs to be able to revoke with the credential it already holds.
548
+
549
+ ## Bootstrap command (headless / non-interactive)
550
+
551
+ empirical oauth bootstrap headless --write-env
552
+
553
+ Exchanges account credentials (client ID + secret, i.e. your userId + API key) for an access
554
+ token via \`grant_type=client_credentials\` — this is NOT a dynamically-registered OAuth client;
555
+ the server treats client_id/client_secret as userId/apiKey directly.
556
+ Writes token to \`.empirical/headless-token.json\` and \`.empirical/headless.env\`.
557
+
558
+ No portal visit or manual copy/paste needed: if no client ID/secret is found via
559
+ \`EMPIRICAL_CLIENT_ID\`/\`EMPIRICAL_CLIENT_SECRET\` env vars or \`~/.empirical/oauth-client.json\`
560
+ (set via \`empirical oauth client set\`), the CLI self-provisions your account's API key via
561
+ \`POST /api/v1/users/me/credentials\`, authenticated with whatever bearer token it already has
562
+ (existing token file, or — only if nothing exists yet — one browser login). If you already have
563
+ an API key on the account, the CLI will never silently rotate it (that would break other
564
+ integrations using it); it asks for explicit confirmation in a TTY, or gives up quietly in
565
+ non-interactive contexts. If a stored client is rejected by the server ("Invalid client
566
+ credentials"), the CLI discards it and re-provisions automatically on the same run — unless
567
+ \`--client-id\`/\`--client-secret\` was passed explicitly, in which case the error is surfaced
568
+ as-is.
569
+
570
+ ## Refresh command
571
+
572
+ empirical oauth refresh headless
573
+
574
+ Exchanges a stored refresh token for a new access token. Token file must exist.
575
+
576
+ ## Token resolution priority (resolveAccessToken)
577
+
578
+ 1. \`EMPIRICAL_ACCESS_TOKEN\` environment variable (skipped if it's an expired JWT, so a
579
+ stale session token can never permanently shadow a valid credential below it)
580
+ 2. \`apiKey\` field in token file
581
+ 3. \`accessToken\` field in token file
582
+ 4. \`~/.empirical/oauth-session.json\` browser OAuth session
583
+ 5. \`EMPIRICAL_API_KEY\` or \`EMPIRICAL_CLIENT_SECRET\` environment variable, used directly
584
+ as a permanent Bearer credential — lets a stateless agent environment authenticate from
585
+ client_id/secret alone with no token file and no bootstrap step
586
+
587
+ Token file lookup order:
588
+ 1. Path in \`EMPIRICAL_TOKEN_OUTPUT\` env var
589
+ 2. \`.empirical/headless-token.json\` (current working directory)
590
+ 3. \`~/.empirical/headless-token.json\` (home directory)
591
+
592
+ The browser OAuth session is stored separately at \`~/.empirical/oauth-session.json\`.
593
+
594
+ ## Verify auth status
595
+
596
+ empirical auth status
597
+ empirical whoami
598
+
599
+ Prints a single JSON object: \`{ authenticated, tokenPresent, tokenType, tokenExpiresAt,
600
+ tokenExpired, user: { id, email } | null, workspaceId, error }\`. \`whoami\` is an alias for
601
+ \`auth status\`. Exits 1 if not authenticated.
602
+
603
+ empirical doctor
604
+
605
+ Checks token presence, type, expiry, and API connectivity across a broader set of checks
606
+ (host MCP config registration, hooks, plugin versions).
607
+ `,
608
+
609
+ user: `# Empirical User Identity
610
+
611
+ ## Via MCP (when Empirical MCP server is connected)
612
+
613
+ Use the \`get_user\` MCP tool. Returns: id, email, name, workspaceId, tier, and plan details.
614
+
615
+ ## Via CLI
616
+
617
+ empirical doctor --output json
618
+
619
+ Returns a JSON object with a \`checks\` array. The connectivity check (name: "api")
620
+ includes the user record from /api/users/me when successful.
621
+
622
+ empirical workspace current
623
+
624
+ Prints the active workspace name.
625
+
626
+ ## Notes
627
+
628
+ - \`get_user\` has no standalone CLI subcommand; use \`empirical doctor --output json\` to
629
+ inspect identity from the shell.
630
+ - If MCP is unavailable, parse the \`api\` check from doctor JSON output for user context.
631
+ `
632
+ };
633
+
634
+ let cachedCliVersion = null;
635
+
636
+ function getCliVersion() {
637
+ if (cachedCliVersion) return cachedCliVersion;
638
+ try {
639
+ const cliPackagePath = path.join(__dirname, '..', 'package.json');
640
+ const parsed = JSON.parse(fs.readFileSync(cliPackagePath, 'utf8'));
641
+ if (parsed && typeof parsed.version === 'string' && parsed.version.trim()) {
642
+ cachedCliVersion = parsed.version.trim();
643
+ return cachedCliVersion;
644
+ }
645
+ } catch (_error) {
646
+ // ignore and use fallback
647
+ }
648
+ cachedCliVersion = 'unknown';
649
+ return cachedCliVersion;
650
+ }
651
+
652
+ function deriveBaseUrlFromMcpUrl() {
653
+ try {
654
+ const parsed = new URL(EMPIRICAL_MCP_URL);
655
+ parsed.pathname = parsed.pathname.replace(/\/mcp\/?$/, '');
656
+ return parsed.origin + parsed.pathname.replace(/\/$/, '');
657
+ } catch (_error) {
658
+ return '';
659
+ }
660
+ }
661
+
662
+ module.exports = {
663
+ EMPIRICAL_MCP_NAME,
664
+ EMPIRICAL_MCP_LEGACY_NAMES,
665
+ EMPIRICAL_MCP_URL,
666
+ COPILOT_OAUTH_CLIENT_ID,
667
+ MEMORY_CATEGORIES,
668
+ NODE_TYPES,
669
+ DOC_TOPICS,
670
+ getCliVersion,
671
+ deriveBaseUrlFromMcpUrl
672
+ };