@empirical-gauzza/cli 1.0.36 → 1.0.38

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,670 +1,722 @@
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
- empirical workspace use <number|name|id> --global
302
- empirical workspace unbind
303
-
304
- Friendly alias for \`workspace select\`. It accepts the list number, exact case-insensitive name, or workspace ID.
305
-
306
- 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.
307
-
308
- 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.
309
-
310
- ### Query across several workspaces
311
-
312
- empirical memory query --match "<query>" --workspaces "Default Workspace","Project Atlas"
313
- empirical memory query --match "<query>" --all-workspaces
314
-
315
- \`--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.
316
-
317
- ### Share an active workspace
318
-
319
- empirical workspace invite create --role writer --expires-in-days 7 --max-uses 3
320
- empirical workspace invite list
321
- empirical workspace invite revoke <token>
322
-
323
- Invite commands operate on the active team workspace. Create prints a shareable URL.
324
-
325
- ### Soft-delete a demo workspace
326
-
327
- empirical workspace delete <number|name|id>
328
- empirical workspace delete <number|name|id> --confirm
329
-
330
- 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.
331
- `,
332
-
333
- doctor: `# Empirical Doctor
334
-
335
- empirical doctor [--output json] [--deep]
336
-
337
- Checks the health of your Empirical CLI setup, including why MCP tools might not be showing up
338
- in a host CLI (the most common failure report).
339
-
340
- ## Checks performed
341
-
342
- 1. **Token presence** — verifies a token exists via EMPIRICAL_ACCESS_TOKEN env, apiKey field,
343
- or accessToken field in the token file
344
- 2. **Token type** — detects API key (long alphanumeric, no dots) vs JWT (3 dot-separated parts)
345
- 3. **Token expiry** — if JWT, decodes the payload and checks the \`exp\` field; warns if no \`exp\`
346
- 4. **API connectivity** — calls GET /api/v1/users/me; 200 = OK, anything else = FAIL
347
- 5. **Workspace** — reads workspaceId from the /api/v1/users/me response; warns if null
348
- 6. **MCP hosts detected** — which of codex/claude/copilot were found on this machine
349
- 7. **MCP config registration (per detected host)** — reads that host's own config file
350
- (\`~/.claude.json\`, \`<CODEX_HOME>/config.toml\`, \`~/.copilot/mcp-config.json\`) and confirms
351
- the Empirical MCP server is actually registered there with the current URL
352
- 8. **MCP reachability** — POSTs a real MCP \`initialize\` request to the MCP endpoint with the
353
- resolved access token and confirms a valid JSON-RPC response comes back
354
- 9. **Native plugin presence/version (per detected host)** — runs \`<host> plugin list\` and
355
- reports the installed empirical-memory (and optional) plugin versions
356
- 10. **Hooks presence (per detected host)** — confirms the SessionStart/UserPromptSubmit/Stop
357
- hook scripts exist on disk and are registered in that host's hook manifest
358
-
359
- ## --deep
360
-
361
- Adds a live write/query/delete round trip: records a \`doctor-probe-<timestamp>\` memory, queries
362
- it back, deletes it, and reports elapsed time for each leg. Requires a valid token; writes and
363
- deletes exactly one throwaway memory tagged \`doctor-probe\`.
364
-
365
- ## Output format
366
-
367
- Default: human-readable lines prefixed with [OK], [WARN], or [FAIL].
368
-
369
- empirical doctor --output json
370
-
371
- JSON output: \`{ "checks": [{ "name", "status", "message" }], "overall": "ok"|"warn"|"fail" }\`
372
- Useful for agent/automation use.
373
-
374
- ## Exit codes
375
- - 0: all checks passed (ok or warn)
376
- - 1: one or more checks failed
377
- `,
378
-
379
- agents: `# Empirical Agent Integration
380
-
381
- Empirical memory integrates with three AI coding CLIs.
382
-
383
- ## Supported agents
384
-
385
- | Agent | Config dir | Instruction file | MCP config |
386
- |-------------|---------------------|-------------------------------|-------------------------------------|
387
- | Claude Code | ~/.claude/ | CLAUDE.md | ~/.claude.json (mcpServers) |
388
- | Codex CLI | ~/.codex/ | AGENTS.md | codex mcp add |
389
- | Copilot CLI | ~/.copilot/ | copilot-instructions.md | ~/.copilot/mcp-config.json |
390
-
391
- ## Integration modes
392
-
393
- **Mode 1: MCP tool (recommended)**
394
- Empirical is registered as an MCP server. Agents call memory tools (query_memories,
395
- record_graph_memory, etc.) directly via the MCP protocol. Best for interactive sessions.
396
-
397
- **Mode 2: CLI commands**
398
- Agents run \`empirical memory <subcommand>\` shell commands. No MCP registration required.
399
- Suitable for headless/scripted environments or when MCP is unavailable.
400
-
401
- **Mode 3: Both**
402
- MCP is primary; CLI commands are available as fallback. Maximizes reliability.
403
-
404
- ## Inventory commands
405
-
406
- empirical skill list
407
- empirical device list
408
- empirical device show <deviceId>
409
-
410
- Use these to inspect Skills across Kits and the effective Skills installed for a device.
411
-
412
- The native \`empirical-memory\` plugin owns its Skills and MCP connection. Run
413
- \`empirical install\` to install it and configure detected agents.
414
- `,
415
-
416
- kit: `# Empirical Skills Kits
417
-
418
- Kits are named collections of agent Skills. A Kit can contain Skills authored in Empirical
419
- or discovered from a git repository, then sync the resolved set to your local agents.
420
-
421
- ## Commands
422
-
423
- ### Create and list Kits
424
-
425
- empirical kit create "My Team Kit"
426
- empirical kit list
427
-
428
- ### Add or remove a git source
429
-
430
- empirical kit add-source "My Team Kit" https://github.com/example/skills.git
431
- empirical kit add-source "My Team Kit" https://github.com/example/skills.git --ref main --path skills
432
- empirical kit remove-source "My Team Kit" <source-id>
433
-
434
- Each source is scanned for valid \`SKILL.md\` files. A Skill file must begin with YAML
435
- frontmatter containing both \`name\` and \`description\`, for example:
436
-
437
- ---
438
- name: my-skill
439
- description: Explain when this skill should be used.
440
- ---
441
-
442
- ### Activate and sync
443
-
444
- empirical kit use "My Team Kit"
445
- empirical kit sync
446
- empirical kit sync --dry-run
447
-
448
- \`kit use\` activates a Kit on this device and syncs its Skills into detected agent
449
- directories. \`kit sync\` refreshes the active Kit. Use \`--quiet\` for unattended syncs
450
- and \`--force\` only when you intentionally want to bypass the local-edit guard.
451
-
452
- ### Inventory
453
-
454
- empirical skill list
455
- empirical kit list
456
- empirical kit devices "My Team Kit"
457
- empirical device list
458
- empirical device show <deviceId>
459
-
460
- \`skill list\` shows Skills resolved from owned Kits. \`device list\` shows registered devices and
461
- their active Kits; \`device show\` shows the effective resolved Skills for one device.
462
-
463
- ### Manage devices and autosync
464
-
465
- empirical kit autosync "My Team Kit" on
466
- empirical kit devices "My Team Kit"
467
- empirical kit deactivate "My Team Kit" <device-id>
468
-
469
- Autosync keeps a registered device current. Interactive syncs review new or changed external
470
- Skills; unattended syncs apply them and leave a review marker for the next interactive sync.
471
- `,
472
-
473
- skill: `# Empirical Skills
474
-
475
- Skills are agent-facing \`SKILL.md\` instruction files. Authored Skills can be pushed into a
476
- Kit so they follow you across machines and projects.
477
-
478
- ## Push an authored Skill
479
-
480
- empirical skill list
481
-
482
- empirical skill push path/to/SKILL.md
483
- empirical skill push path/to/SKILL.md --kit "My Team Kit"
484
-
485
- The file must contain YAML frontmatter at the top with both required fields:
486
-
487
- ---
488
- name: my-skill
489
- description: Explain when this skill should be used.
490
- ---
491
-
492
- The remainder of the file is the Skill body. The CLI validates the frontmatter before upload.
493
- After pushing, activate or sync the target Kit with \`empirical kit use\` or \`empirical kit sync\`.
494
-
495
- ## Review and rollback
496
-
497
- empirical skill history <authored-skill-id>
498
- empirical skill rollback <authored-skill-id> --to-version 2
499
-
500
- Use history to inspect versions and rollback to a known-good version when a Skill needs to be
501
- reverted. External git-sourced Skills are reviewed during Kit sync and are not authored with
502
- this command.
503
- `,
504
-
505
- auth: `# Empirical Auth
506
-
507
- ## Token types
508
-
509
- **API key**: Long alphanumeric string, no dots. Never expires. Preferred for automation.
510
- **JWT**: Three dot-separated base64 parts. Has an expiry (\`exp\` claim). Used for browser OAuth flows.
511
-
512
- ## Browser OAuth login
513
-
514
- empirical auth login
515
-
516
- Opens the browser, completes OAuth with the Empirical account, and saves a CLI session at
517
- \`~/.empirical/oauth-session.json\`. Use this for normal interactive CLI use when you do not
518
- want to provision a permanent API key.
519
-
520
- ## Logging out (and actually revoking)
521
-
522
- empirical auth logout # revoke refresh tokens server-side, then clear locally
523
- empirical auth logout --local-only # forget this machine only, leave other clients signed in
524
- empirical auth logout --client-id <id> # revoke just one client's refresh tokens
525
-
526
- Plain \`logout\` calls \`DELETE /api/v1/users/me/refresh-tokens\`, so the session ends on the
527
- server and not merely on disk. This matters because refresh tokens are written without an
528
- expiry: before this existed, a leaked refresh token stayed redeemable forever and no command,
529
- endpoint or portal screen could revoke it.
530
-
531
- ## Managing API keys
532
-
533
- empirical auth list-keys # ids, labels, created dates
534
- empirical auth rotate-key [--label <name>] # mint an ADDITIONAL key, others keep working
535
- empirical auth rotate-key --replace-all # destructive: wipe every key, mint exactly one
536
- empirical auth revoke-key --key-id <id> # revoke one key
537
- empirical auth revoke-key --all # revoke every key on the account
538
-
539
- Rotating without \`--replace-all\` is the safe cutover: mint the new key, move your integrations
540
- over, then \`revoke-key --key-id\` the old one. A key is displayed once at mint time; only its
541
- hash is stored, so there is no way to read it back later.
542
-
543
- Minting requires a signed-in session (browser login or a fresh OAuth session), because a
544
- permanent API key must not be able to mint more credentials. Revoking does not: it only ever
545
- reduces access, and the CLI needs to be able to revoke with the credential it already holds.
546
-
547
- ## Bootstrap command (headless / non-interactive)
548
-
549
- empirical oauth bootstrap headless --write-env
550
-
551
- Exchanges account credentials (client ID + secret, i.e. your userId + API key) for an access
552
- token via \`grant_type=client_credentials\` — this is NOT a dynamically-registered OAuth client;
553
- the server treats client_id/client_secret as userId/apiKey directly.
554
- Writes token to \`.empirical/headless-token.json\` and \`.empirical/headless.env\`.
555
-
556
- No portal visit or manual copy/paste needed: if no client ID/secret is found via
557
- \`EMPIRICAL_CLIENT_ID\`/\`EMPIRICAL_CLIENT_SECRET\` env vars or \`~/.empirical/oauth-client.json\`
558
- (set via \`empirical oauth client set\`), the CLI self-provisions your account's API key via
559
- \`POST /api/v1/users/me/credentials\`, authenticated with whatever bearer token it already has
560
- (existing token file, or — only if nothing exists yet — one browser login). If you already have
561
- an API key on the account, the CLI will never silently rotate it (that would break other
562
- integrations using it); it asks for explicit confirmation in a TTY, or gives up quietly in
563
- non-interactive contexts. If a stored client is rejected by the server ("Invalid client
564
- credentials"), the CLI discards it and re-provisions automatically on the same run — unless
565
- \`--client-id\`/\`--client-secret\` was passed explicitly, in which case the error is surfaced
566
- as-is.
567
-
568
- ## Refresh command
569
-
570
- empirical oauth refresh headless
571
-
572
- Exchanges a stored refresh token for a new access token. Token file must exist.
573
-
574
- ## Token resolution priority (resolveAccessToken)
575
-
576
- 1. \`EMPIRICAL_ACCESS_TOKEN\` environment variable (skipped if it's an expired JWT, so a
577
- stale session token can never permanently shadow a valid credential below it)
578
- 2. \`apiKey\` field in token file
579
- 3. \`accessToken\` field in token file
580
- 4. \`~/.empirical/oauth-session.json\` browser OAuth session
581
- 5. \`EMPIRICAL_API_KEY\` or \`EMPIRICAL_CLIENT_SECRET\` environment variable, used directly
582
- as a permanent Bearer credential — lets a stateless agent environment authenticate from
583
- client_id/secret alone with no token file and no bootstrap step
584
-
585
- Token file lookup order:
586
- 1. Path in \`EMPIRICAL_TOKEN_OUTPUT\` env var
587
- 2. \`.empirical/headless-token.json\` (current working directory)
588
- 3. \`~/.empirical/headless-token.json\` (home directory)
589
-
590
- The browser OAuth session is stored separately at \`~/.empirical/oauth-session.json\`.
591
-
592
- ## Verify auth status
593
-
594
- empirical auth status
595
- empirical whoami
596
-
597
- Prints a single JSON object: \`{ authenticated, tokenPresent, tokenType, tokenExpiresAt,
598
- tokenExpired, user: { id, email } | null, workspaceId, error }\`. \`whoami\` is an alias for
599
- \`auth status\`. Exits 1 if not authenticated.
600
-
601
- empirical doctor
602
-
603
- Checks token presence, type, expiry, and API connectivity across a broader set of checks
604
- (host MCP config registration, hooks, plugin versions).
605
- `,
606
-
607
- user: `# Empirical User Identity
608
-
609
- ## Via MCP (when Empirical MCP server is connected)
610
-
611
- Use the \`get_user\` MCP tool. Returns: id, email, name, workspaceId, tier, and plan details.
612
-
613
- ## Via CLI
614
-
615
- empirical doctor --output json
616
-
617
- Returns a JSON object with a \`checks\` array. The connectivity check (name: "api")
618
- includes the user record from /api/users/me when successful.
619
-
620
- empirical workspace current
621
-
622
- Prints the active workspace name.
623
-
624
- ## Notes
625
-
626
- - \`get_user\` has no standalone CLI subcommand; use \`empirical doctor --output json\` to
627
- inspect identity from the shell.
628
- - If MCP is unavailable, parse the \`api\` check from doctor JSON output for user context.
629
- `
630
- };
631
-
632
- let cachedCliVersion = null;
633
-
634
- function getCliVersion() {
635
- if (cachedCliVersion) return cachedCliVersion;
636
- try {
637
- const cliPackagePath = path.join(__dirname, '..', 'package.json');
638
- const parsed = JSON.parse(fs.readFileSync(cliPackagePath, 'utf8'));
639
- if (parsed && typeof parsed.version === 'string' && parsed.version.trim()) {
640
- cachedCliVersion = parsed.version.trim();
641
- return cachedCliVersion;
642
- }
643
- } catch (_error) {
644
- // ignore and use fallback
645
- }
646
- cachedCliVersion = 'unknown';
647
- return cachedCliVersion;
648
- }
649
-
650
- function deriveBaseUrlFromMcpUrl() {
651
- try {
652
- const parsed = new URL(EMPIRICAL_MCP_URL);
653
- parsed.pathname = parsed.pathname.replace(/\/mcp\/?$/, '');
654
- return parsed.origin + parsed.pathname.replace(/\/$/, '');
655
- } catch (_error) {
656
- return '';
657
- }
658
- }
659
-
660
- module.exports = {
661
- EMPIRICAL_MCP_NAME,
662
- EMPIRICAL_MCP_LEGACY_NAMES,
663
- EMPIRICAL_MCP_URL,
664
- COPILOT_OAUTH_CLIENT_ID,
665
- MEMORY_CATEGORIES,
666
- NODE_TYPES,
667
- DOC_TOPICS,
668
- getCliVersion,
669
- deriveBaseUrlFromMcpUrl
670
- };
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
+ ### tree
187
+ One item and everything nested under it, as a readable document. The server assembles it in
188
+ one query, so there is no walking the graph level by level.
189
+
190
+ empirical memory tree (--memory-id <id> | --match <q>) [--depth 10] [--max-nodes 500] [--no-notes] [--format markdown|json] [--out <path>]
191
+
192
+ Flags:
193
+ - \`--depth\`: how many levels below the starting item to include, default 10. 0 is the item alone
194
+ - \`--max-nodes\`: cap on items in the document, default 500. When it bites, stderr carries a
195
+ truncated warning and the document itself stays clean for redirection
196
+ - \`--no-notes\`: leave each item's notes out, for a bare outline
197
+ - \`--format json\`: print the nodes and metadata instead of the document (markdown is the default
198
+ here, unlike other memory subcommands, because the point of this one is something to read)
199
+ - \`--out <path>\`: write the document to a file instead of stdout
200
+
201
+ Items nest only in a workspace that has a shape. For an ordinary memory this returns that one
202
+ memory as a one-section document.
203
+
204
+ ### attachment
205
+ Files attached to a memory. Up to 20 files per memory, 25MB each. Every subcommand takes the
206
+ target memory as \`--memory-id <id>\` or \`--match <query>\`, the same way \`note\` and \`update\` do.
207
+
208
+ empirical memory attachment list (--memory-id <id> | --match <q>) [--include-deleted] [--format table] [--pretty]
209
+ empirical memory attachment add (--memory-id <id> | --match <q>) --file <path> [--author-type user|agent]
210
+ empirical memory attachment get (--memory-id <id> | --match <q>) --attachment-id <id> [--out <path>] [--force]
211
+ empirical memory attachment delete (--memory-id <id> | --match <q>) --attachment-id <id> [--confirm]
212
+ empirical memory attachment restore (--memory-id <id> | --match <q>) --attachment-id <id>
213
+
214
+ \`empirical memory attach\` is shorthand for \`attachment add\`, and \`empirical memory attachments\`
215
+ for \`attachment list\`.
216
+
217
+ Notes:
218
+ - \`delete\` is a soft delete: the file stays restorable for 30 days, then the purge worker
219
+ removes the bytes. Without \`--confirm\` it is a dry run, like \`memory delete\`.
220
+ - \`list --include-deleted\` shows deleted rows with the date their bytes get purged.
221
+ - \`get\` writes bytes to a file, not to stdout: \`--out <path>\` chooses where (a directory is
222
+ allowed), and without it the server's filename is used in the current directory. An existing
223
+ file is never overwritten without \`--force\`. \`--out -\` writes the bytes to stdout for piping.
224
+ - Attachments need a paid plan; the free tier's attachment pool is 0 bytes.
225
+
226
+ ### export / import
227
+ Export memories to a versioned, round-trippable JSON document and re-import them later.
228
+
229
+ empirical memory export --scope <personal|workspace> [--out <path>] [--workspace-id <id>]
230
+ empirical memory import <file> [--dry-run]
231
+
232
+ Export writes \`{ schemaVersion: 1, exportedAt, memories: [...] }\`. Import reads that same
233
+ shape and POSTs each row through the normal create endpoint, so imported memories get the same
234
+ dedupe and auto-link treatment as any other write. \`--dry-run\` on import prints the count that
235
+ would be imported without writing anything.
236
+
237
+ ### stats
238
+ Aggregated counts, scoped to the caller.
239
+
240
+ empirical memory stats [--format table]
241
+
242
+ Returns total count, counts by category, counts by tag, and oldest/newest memory timestamps.
243
+
244
+ ### compact
245
+ On-demand near-duplicate scan (the same logic the nightly compaction job runs), scoped to the
246
+ caller only. Dry-run by default, matching a terraform plan/apply model.
247
+
248
+ empirical memory compact [--category <cat>]
249
+ empirical memory compact --apply [--category <cat>]
250
+
251
+ Without \`--apply\`, reports proposed merge/link candidate pairs and counts with no writes.
252
+ With \`--apply\`, executes the merges and links for real and reports what was applied.
253
+
254
+ ## Error output contract
255
+ - Errors → stderr as JSON: \`{ "error": true, "code": "...", "message": "..." }\`
256
+ - Success → stdout as JSON (compact by default)
257
+ `,
258
+
259
+ install: `# Empirical Install
260
+
261
+ empirical install
262
+
263
+ Runs the interactive installer to connect Empirical memory to your AI coding CLI(s).
264
+
265
+ ## What it does
266
+ 1. Detects installed CLIs: Codex, GitHub Copilot, Claude Code
267
+ 2. Prompts for CLI target selection
268
+ 3. Asks how agents should interact with Empirical:
269
+ - Mode 1: Via MCP tool (recommended) — registers Empirical as an MCP server
270
+ - Mode 2: Via CLI commands — writes CLI-direct instruction block, no MCP registration
271
+ - Mode 3: Both — MCP primary with CLI fallback
272
+ 4. Stores the personalized behavior policy for the native Empirical Memory Skill; global
273
+ instruction files are left unchanged
274
+ 5. Offers optional browser OAuth login for CLI use, optional headless/API-key bootstrap for
275
+ automation, containers, and external runtimes, and optional work-history/scar-tissue guidance
276
+ 6. Installs a \`SessionStart\` hook for each selected client that calls the \`empirical\` CLI
277
+ directly once per session — deterministic baseline context that doesn't depend on the model
278
+ deciding to call a tool. Claude: \`~/.claude/settings.json\`. Codex: \`<CODEX_HOME>/hooks.json\`
279
+ (defaults to \`~/.codex\`). Copilot: \`~/.copilot/hooks/empirical-session-start.json\`.
280
+ 7. Installs the native \`empirical-memory\` plugin for each selected client. The plugin owns
281
+ the Empirical MCP connection and all agent-facing Skills; the CLI does not generate duplicate
282
+ \`SKILL.md\` or \`EMPIRICAL_SKILL.md\` files.
283
+
284
+ ## Native plugin only
285
+
286
+ empirical plugin install <codex|copilot|claude|all> [--all-optional]
287
+
288
+ Optional plugins: --work-history, --scar-tissue, --todo, --voice (writes as you once a voice is saved)
289
+
290
+ empirical plugin check [--force]
291
+
292
+ Installs or refreshes only the native marketplace plugin. It does not run the MCP/auth questionnaire
293
+ or modify global instruction files. With no target, the command installs for detected CLIs.
294
+
295
+ The CLI performs a best-effort native plugin version check during interactive use (at most once per
296
+ day). It is silent for noninteractive/headless commands. Use \`empirical plugin check --force\` to
297
+ check immediately.
298
+
299
+ Use \`empirical policy show\` to inspect the current native-skill behavior policy and
300
+ \`empirical policy set --json '{"allowDiscretionaryUse":false}'\` to change it without rerunning
301
+ the installer. The policy is cached locally and refreshed when stale.
302
+ `,
303
+
304
+ workspace: `# Empirical Workspace Commands
305
+
306
+ Manage disposable team workspaces and which Empirical workspace is active for this OAuth client.
307
+
308
+ ## Commands
309
+
310
+ ### List workspaces
311
+
312
+ empirical workspace list
313
+
314
+ Shows all available workspaces and which one is currently selected.
315
+
316
+ ### Show current workspace
317
+
318
+ empirical workspace current
319
+
320
+ Prints the currently selected workspace name.
321
+
322
+ ### Select a workspace
323
+
324
+ empirical workspace select [<number|name>]
325
+
326
+ Interactive workspace selector (prompts if no argument given).
327
+ Pass a number (from \`list\`) or an exact workspace name.
328
+
329
+ empirical workspace select 2
330
+ empirical workspace select "My Workspace"
331
+
332
+ Note: workspace selection may be admin-locked for some OAuth clients.
333
+
334
+ ### Create a demo workspace
335
+
336
+ empirical workspace create "Demo Workspace" --description "Temporary shared context"
337
+
338
+ Creates a team workspace using your existing plan quota and prints its workspace ID.
339
+
340
+ ### Set up or re-shape a workspace by interview
341
+
342
+ empirical workspace create --interview
343
+ empirical workspace shape --workspace-id <id>
344
+ empirical setup abandon <sessionId>
345
+
346
+ The interview asks what the workspace is for, offers kinds of item, statuses, fields and rules to pick from, shows a draft, and creates the workspace (or saves the new shape) and emails any invites only after you confirm. At the first review type next to choose who to invite (a bare yes is not a choice there), or confirm to finish with nobody invited, or type a change. On a new workspace a change can be its name (for example: change the name to Wellness on Balance); the name of an existing workspace is changed in its settings, not here. After the invitations, a yes confirms. In a re-shape the workspace's current kinds of item, statuses, fields and rules are listed first as Now in use: say keep, or only add things after a bar, to start from them as they are. Adding works; taking something away is not possible yet. In a script, the draft answer may be a list sent in order, for example for a new workspace {"draft": ["change the name to Leads", "next"], "invites": "ana@example.com writer", "confirm": "confirm"}. \`create --interview\` always makes a new workspace and takes no name. \`shape\` re-shapes an existing team workspace you manage: pass \`--workspace-id\`. Without it, at a terminal only, the command names the workspace this directory is bound to and asks you to confirm it; in a script (\`--answers\`), with \`--accept-cost\`, or with no terminal it refuses and needs \`--workspace-id\`. \`--answers <file.json>\` runs it from a map of question key to answer, and \`--accept-cost\` agrees to the Empirical Tokens cost in a script (a person at a terminal is asked instead).
347
+
348
+ A new interview is charged. \`setup abandon\` stops one but does not return what it cost. When the interview is not switched on for your account, the start answers that nothing was started or charged.
349
+
350
+ ### Use a workspace
351
+
352
+ empirical workspace use <number|name|id>
353
+ empirical workspace use <number|name|id> --global
354
+ empirical workspace unbind
355
+
356
+ Friendly alias for \`workspace select\`. It accepts the list number, exact case-insensitive name, or workspace ID.
357
+
358
+ 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.
359
+
360
+ 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.
361
+
362
+ ### Query across several workspaces
363
+
364
+ empirical memory query --match "<query>" --workspaces "Default Workspace","Project Atlas"
365
+ empirical memory query --match "<query>" --all-workspaces
366
+
367
+ \`--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.
368
+
369
+ ### Share an active workspace
370
+
371
+ empirical workspace invite create --role writer --expires-in-days 7 --max-uses 3
372
+ empirical workspace invite list
373
+ empirical workspace invite revoke <token>
374
+
375
+ Invite commands operate on the active team workspace. Create prints a shareable URL.
376
+
377
+ ### Soft-delete a demo workspace
378
+
379
+ empirical workspace delete <number|name|id>
380
+ empirical workspace delete <number|name|id> --confirm
381
+
382
+ 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.
383
+ `,
384
+
385
+ doctor: `# Empirical Doctor
386
+
387
+ empirical doctor [--output json] [--deep]
388
+
389
+ Checks the health of your Empirical CLI setup, including why MCP tools might not be showing up
390
+ in a host CLI (the most common failure report).
391
+
392
+ ## Checks performed
393
+
394
+ 1. **Token presence** — verifies a token exists via EMPIRICAL_ACCESS_TOKEN env, apiKey field,
395
+ or accessToken field in the token file
396
+ 2. **Token type** — detects API key (long alphanumeric, no dots) vs JWT (3 dot-separated parts)
397
+ 3. **Token expiry** — if JWT, decodes the payload and checks the \`exp\` field; warns if no \`exp\`
398
+ 4. **API connectivity** — calls GET /api/v1/users/me; 200 = OK, anything else = FAIL
399
+ 5. **Workspace** — reads workspaceId from the /api/v1/users/me response; warns if null
400
+ 6. **MCP hosts detected** — which of codex/claude/copilot were found on this machine
401
+ 7. **MCP config registration (per detected host)** — reads that host's own config file
402
+ (\`~/.claude.json\`, \`<CODEX_HOME>/config.toml\`, \`~/.copilot/mcp-config.json\`) and confirms
403
+ the Empirical MCP server is actually registered there with the current URL
404
+ 8. **MCP reachability** — POSTs a real MCP \`initialize\` request to the MCP endpoint with the
405
+ resolved access token and confirms a valid JSON-RPC response comes back
406
+ 9. **Native plugin presence/version (per detected host)** — runs \`<host> plugin list\` and
407
+ reports the installed empirical-memory (and optional) plugin versions
408
+ 10. **Hooks presence (per detected host)** — confirms the SessionStart/UserPromptSubmit/Stop
409
+ hook scripts exist on disk and are registered in that host's hook manifest
410
+
411
+ ## --deep
412
+
413
+ Adds a live write/query/delete round trip: records a \`doctor-probe-<timestamp>\` memory, queries
414
+ it back, deletes it, and reports elapsed time for each leg. Requires a valid token; writes and
415
+ deletes exactly one throwaway memory tagged \`doctor-probe\`.
416
+
417
+ ## Output format
418
+
419
+ Default: human-readable lines prefixed with [OK], [WARN], or [FAIL].
420
+
421
+ empirical doctor --output json
422
+
423
+ JSON output: \`{ "checks": [{ "name", "status", "message" }], "overall": "ok"|"warn"|"fail" }\`
424
+ Useful for agent/automation use.
425
+
426
+ ## Exit codes
427
+ - 0: all checks passed (ok or warn)
428
+ - 1: one or more checks failed
429
+ `,
430
+
431
+ agents: `# Empirical Agent Integration
432
+
433
+ Empirical memory integrates with three AI coding CLIs.
434
+
435
+ ## Supported agents
436
+
437
+ | Agent | Config dir | Instruction file | MCP config |
438
+ |-------------|---------------------|-------------------------------|-------------------------------------|
439
+ | Claude Code | ~/.claude/ | CLAUDE.md | ~/.claude.json (mcpServers) |
440
+ | Codex CLI | ~/.codex/ | AGENTS.md | codex mcp add |
441
+ | Copilot CLI | ~/.copilot/ | copilot-instructions.md | ~/.copilot/mcp-config.json |
442
+
443
+ ## Integration modes
444
+
445
+ **Mode 1: MCP tool (recommended)**
446
+ Empirical is registered as an MCP server. Agents call memory tools (query_memories,
447
+ record_graph_memory, etc.) directly via the MCP protocol. Best for interactive sessions.
448
+
449
+ **Mode 2: CLI commands**
450
+ Agents run \`empirical memory <subcommand>\` shell commands. No MCP registration required.
451
+ Suitable for headless/scripted environments or when MCP is unavailable.
452
+
453
+ **Mode 3: Both**
454
+ MCP is primary; CLI commands are available as fallback. Maximizes reliability.
455
+
456
+ ## Inventory commands
457
+
458
+ empirical skill list
459
+ empirical device list
460
+ empirical device show <deviceId>
461
+
462
+ Use these to inspect Skills across Kits and the effective Skills installed for a device.
463
+
464
+ The native \`empirical-memory\` plugin owns its Skills and MCP connection. Run
465
+ \`empirical install\` to install it and configure detected agents.
466
+ `,
467
+
468
+ kit: `# Empirical Skills Kits
469
+
470
+ Kits are named collections of agent Skills. A Kit can contain Skills authored in Empirical
471
+ or discovered from a git repository, then sync the resolved set to your local agents.
472
+
473
+ ## Commands
474
+
475
+ ### Create and list Kits
476
+
477
+ empirical kit create "My Team Kit"
478
+ empirical kit list
479
+
480
+ ### Add or remove a git source
481
+
482
+ empirical kit add-source "My Team Kit" https://github.com/example/skills.git
483
+ empirical kit add-source "My Team Kit" https://github.com/example/skills.git --ref main --path skills
484
+ empirical kit remove-source "My Team Kit" <source-id>
485
+
486
+ Each source is scanned for valid \`SKILL.md\` files. A Skill file must begin with YAML
487
+ frontmatter containing both \`name\` and \`description\`, for example:
488
+
489
+ ---
490
+ name: my-skill
491
+ description: Explain when this skill should be used.
492
+ ---
493
+
494
+ ### Activate and sync
495
+
496
+ empirical kit use "My Team Kit"
497
+ empirical kit sync
498
+ empirical kit sync --dry-run
499
+
500
+ \`kit use\` activates a Kit on this device and syncs its Skills into detected agent
501
+ directories. \`kit sync\` refreshes the active Kit. Use \`--quiet\` for unattended syncs
502
+ and \`--force\` only when you intentionally want to bypass the local-edit guard.
503
+
504
+ ### Inventory
505
+
506
+ empirical skill list
507
+ empirical kit list
508
+ empirical kit devices "My Team Kit"
509
+ empirical device list
510
+ empirical device show <deviceId>
511
+
512
+ \`skill list\` shows Skills resolved from owned Kits. \`device list\` shows registered devices and
513
+ their active Kits; \`device show\` shows the effective resolved Skills for one device.
514
+
515
+ ### Manage devices and autosync
516
+
517
+ empirical kit autosync "My Team Kit" on
518
+ empirical kit devices "My Team Kit"
519
+ empirical kit deactivate "My Team Kit" <device-id>
520
+
521
+ Autosync keeps a registered device current. Interactive syncs review new or changed external
522
+ Skills; unattended syncs apply them and leave a review marker for the next interactive sync.
523
+ `,
524
+
525
+ skill: `# Empirical Skills
526
+
527
+ Skills are agent-facing \`SKILL.md\` instruction files. Authored Skills can be pushed into a
528
+ Kit so they follow you across machines and projects.
529
+
530
+ ## Push an authored Skill
531
+
532
+ empirical skill list
533
+
534
+ empirical skill push path/to/SKILL.md
535
+ empirical skill push path/to/SKILL.md --kit "My Team Kit"
536
+
537
+ The file must contain YAML frontmatter at the top with both required fields:
538
+
539
+ ---
540
+ name: my-skill
541
+ description: Explain when this skill should be used.
542
+ ---
543
+
544
+ The remainder of the file is the Skill body. The CLI validates the frontmatter before upload.
545
+ After pushing, activate or sync the target Kit with \`empirical kit use\` or \`empirical kit sync\`.
546
+
547
+ ## Review and rollback
548
+
549
+ empirical skill history <authored-skill-id>
550
+ empirical skill rollback <authored-skill-id> --to-version 2
551
+
552
+ Use history to inspect versions and rollback to a known-good version when a Skill needs to be
553
+ reverted. External git-sourced Skills are reviewed during Kit sync and are not authored with
554
+ this command.
555
+ `,
556
+
557
+ auth: `# Empirical Auth
558
+
559
+ ## Token types
560
+
561
+ **API key**: Long alphanumeric string, no dots. Never expires. Preferred for automation.
562
+ **JWT**: Three dot-separated base64 parts. Has an expiry (\`exp\` claim). Used for browser OAuth flows.
563
+
564
+ ## Browser OAuth login
565
+
566
+ empirical auth login
567
+
568
+ Opens the browser, completes OAuth with the Empirical account, and saves a CLI session at
569
+ \`~/.empirical/oauth-session.json\`. Use this for normal interactive CLI use when you do not
570
+ want to provision a permanent API key.
571
+
572
+ ## Logging out (and actually revoking)
573
+
574
+ empirical auth logout # revoke refresh tokens server-side, then clear locally
575
+ empirical auth logout --local-only # forget this machine only, leave other clients signed in
576
+ empirical auth logout --client-id <id> # revoke just one client's refresh tokens
577
+
578
+ Plain \`logout\` calls \`DELETE /api/v1/users/me/refresh-tokens\`, so the session ends on the
579
+ server and not merely on disk. This matters because refresh tokens are written without an
580
+ expiry: before this existed, a leaked refresh token stayed redeemable forever and no command,
581
+ endpoint or portal screen could revoke it.
582
+
583
+ ## Managing API keys
584
+
585
+ empirical auth list-keys # ids, labels, created dates
586
+ empirical auth rotate-key [--label <name>] # mint an ADDITIONAL key, others keep working
587
+ empirical auth rotate-key --replace-all # destructive: wipe every key, mint exactly one
588
+ empirical auth revoke-key --key-id <id> # revoke one key
589
+ empirical auth revoke-key --all # revoke every key on the account
590
+
591
+ Rotating without \`--replace-all\` is the safe cutover: mint the new key, move your integrations
592
+ over, then \`revoke-key --key-id\` the old one. A key is displayed once at mint time; only its
593
+ hash is stored, so there is no way to read it back later.
594
+
595
+ Minting requires a signed-in session (browser login or a fresh OAuth session), because a
596
+ permanent API key must not be able to mint more credentials. Revoking does not: it only ever
597
+ reduces access, and the CLI needs to be able to revoke with the credential it already holds.
598
+
599
+ ## Bootstrap command (headless / non-interactive)
600
+
601
+ empirical oauth bootstrap headless --write-env
602
+
603
+ Exchanges account credentials (client ID + secret, i.e. your userId + API key) for an access
604
+ token via \`grant_type=client_credentials\` — this is NOT a dynamically-registered OAuth client;
605
+ the server treats client_id/client_secret as userId/apiKey directly.
606
+ Writes token to \`.empirical/headless-token.json\` and \`.empirical/headless.env\`.
607
+
608
+ No portal visit or manual copy/paste needed: if no client ID/secret is found via
609
+ \`EMPIRICAL_CLIENT_ID\`/\`EMPIRICAL_CLIENT_SECRET\` env vars or \`~/.empirical/oauth-client.json\`
610
+ (set via \`empirical oauth client set\`), the CLI self-provisions your account's API key via
611
+ \`POST /api/v1/users/me/credentials\`, authenticated with whatever bearer token it already has
612
+ (existing token file, or — only if nothing exists yet — one browser login). If you already have
613
+ an API key on the account, the CLI will never silently rotate it (that would break other
614
+ integrations using it); it asks for explicit confirmation in a TTY, or gives up quietly in
615
+ non-interactive contexts. If a stored client is rejected by the server ("Invalid client
616
+ credentials"), the CLI discards it and re-provisions automatically on the same run — unless
617
+ \`--client-id\`/\`--client-secret\` was passed explicitly, in which case the error is surfaced
618
+ as-is.
619
+
620
+ ## Refresh command
621
+
622
+ empirical oauth refresh headless
623
+
624
+ Exchanges a stored refresh token for a new access token. Token file must exist.
625
+
626
+ ## Token resolution priority (resolveAccessToken)
627
+
628
+ 1. \`EMPIRICAL_ACCESS_TOKEN\` environment variable (skipped if it's an expired JWT, so a
629
+ stale session token can never permanently shadow a valid credential below it)
630
+ 2. \`apiKey\` field in token file
631
+ 3. \`accessToken\` field in token file
632
+ 4. \`~/.empirical/oauth-session.json\` browser OAuth session
633
+ 5. \`EMPIRICAL_API_KEY\` or \`EMPIRICAL_CLIENT_SECRET\` environment variable, used directly
634
+ as a permanent Bearer credential — lets a stateless agent environment authenticate from
635
+ client_id/secret alone with no token file and no bootstrap step
636
+
637
+ Token file lookup order:
638
+ 1. Path in \`EMPIRICAL_TOKEN_OUTPUT\` env var
639
+ 2. \`.empirical/headless-token.json\` (current working directory)
640
+ 3. \`~/.empirical/headless-token.json\` (home directory)
641
+
642
+ The browser OAuth session is stored separately at \`~/.empirical/oauth-session.json\`.
643
+
644
+ ## Verify auth status
645
+
646
+ empirical auth status
647
+ empirical whoami
648
+
649
+ Prints a single JSON object: \`{ authenticated, tokenPresent, tokenType, tokenExpiresAt,
650
+ tokenExpired, user: { id, email } | null, workspaceId, error }\`. \`whoami\` is an alias for
651
+ \`auth status\`. Exits 1 if not authenticated.
652
+
653
+ empirical doctor
654
+
655
+ Checks token presence, type, expiry, and API connectivity across a broader set of checks
656
+ (host MCP config registration, hooks, plugin versions).
657
+ `,
658
+
659
+ user: `# Empirical User Identity
660
+
661
+ ## Via MCP (when Empirical MCP server is connected)
662
+
663
+ Use the \`get_user\` MCP tool. Returns: id, email, name, workspaceId, tier, and plan details.
664
+
665
+ ## Via CLI
666
+
667
+ empirical doctor --output json
668
+
669
+ Returns a JSON object with a \`checks\` array. The connectivity check (name: "api")
670
+ includes the user record from /api/users/me when successful.
671
+
672
+ empirical workspace current
673
+
674
+ Prints the active workspace name.
675
+
676
+ ## Notes
677
+
678
+ - \`get_user\` has no standalone CLI subcommand; use \`empirical doctor --output json\` to
679
+ inspect identity from the shell.
680
+ - If MCP is unavailable, parse the \`api\` check from doctor JSON output for user context.
681
+ `
682
+ };
683
+
684
+ let cachedCliVersion = null;
685
+
686
+ function getCliVersion() {
687
+ if (cachedCliVersion) return cachedCliVersion;
688
+ try {
689
+ const cliPackagePath = path.join(__dirname, '..', 'package.json');
690
+ const parsed = JSON.parse(fs.readFileSync(cliPackagePath, 'utf8'));
691
+ if (parsed && typeof parsed.version === 'string' && parsed.version.trim()) {
692
+ cachedCliVersion = parsed.version.trim();
693
+ return cachedCliVersion;
694
+ }
695
+ } catch (_error) {
696
+ // ignore and use fallback
697
+ }
698
+ cachedCliVersion = 'unknown';
699
+ return cachedCliVersion;
700
+ }
701
+
702
+ function deriveBaseUrlFromMcpUrl() {
703
+ try {
704
+ const parsed = new URL(EMPIRICAL_MCP_URL);
705
+ parsed.pathname = parsed.pathname.replace(/\/mcp\/?$/, '');
706
+ return parsed.origin + parsed.pathname.replace(/\/$/, '');
707
+ } catch (_error) {
708
+ return '';
709
+ }
710
+ }
711
+
712
+ module.exports = {
713
+ EMPIRICAL_MCP_NAME,
714
+ EMPIRICAL_MCP_LEGACY_NAMES,
715
+ EMPIRICAL_MCP_URL,
716
+ COPILOT_OAUTH_CLIENT_ID,
717
+ MEMORY_CATEGORIES,
718
+ NODE_TYPES,
719
+ DOC_TOPICS,
720
+ getCliVersion,
721
+ deriveBaseUrlFromMcpUrl
722
+ };