@empirical-gauzza/cli 1.0.28 → 1.0.30

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