@empirical-gauzza/cli 1.0.9
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/bin/empirical.js +125 -0
- package/lib/auth.js +804 -0
- package/lib/constants.js +491 -0
- package/lib/deviceConfig.js +94 -0
- package/lib/doc.js +50 -0
- package/lib/doctor.js +116 -0
- package/lib/env.js +49 -0
- package/lib/fsUtils.js +90 -0
- package/lib/help.js +113 -0
- package/lib/http.js +179 -0
- package/lib/installWizard.js +383 -0
- package/lib/installers/claude.js +89 -0
- package/lib/installers/codex.js +78 -0
- package/lib/installers/copilot.js +77 -0
- package/lib/installers/detect.js +47 -0
- package/lib/installers/hooks.js +128 -0
- package/lib/installers/nativePlugin.js +87 -0
- package/lib/installers/promptBlocks.js +403 -0
- package/lib/kitCommands.js +622 -0
- package/lib/memoryCommands.js +528 -0
- package/lib/oauthClient.js +222 -0
- package/lib/pluginCommands.js +81 -0
- package/lib/pluginUpdateCheck.js +143 -0
- package/lib/policyCommands.js +144 -0
- package/lib/scheduledSync.js +331 -0
- package/lib/skillSync.js +508 -0
- package/lib/ui.js +280 -0
- package/lib/workspaceCommands.js +323 -0
- package/lib/workspaceContext.js +76 -0
- package/package.json +25 -0
package/lib/constants.js
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
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
|
+
const MEMORY_CATEGORIES = [
|
|
12
|
+
'health', 'fitness', 'food', 'supplement', 'mood', 'idea', 'business',
|
|
13
|
+
'build', 'growth', 'learning', 'leadership', 'operations', 'workflow',
|
|
14
|
+
'lead', 'project', 'recipe', 'preference', 'reflection', 'relationship', 'heartbeat'
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const DOC_TOPICS = {
|
|
18
|
+
memory: `# Empirical Memory Commands
|
|
19
|
+
|
|
20
|
+
Use the \`empirical memory\` subcommands to query and manage memories via the REST API.
|
|
21
|
+
All commands require a valid access token (see \`empirical doc auth\`).
|
|
22
|
+
Errors are written to stderr as JSON. Successful output is written to stdout as JSON.
|
|
23
|
+
|
|
24
|
+
## Subcommands
|
|
25
|
+
|
|
26
|
+
### query
|
|
27
|
+
Search memories by natural language.
|
|
28
|
+
|
|
29
|
+
empirical memory query --match "coding style preferences" [--top-k 3] [--category build] [--tags ts,node] [--pretty]
|
|
30
|
+
|
|
31
|
+
Flags:
|
|
32
|
+
- \`--match\` (required): natural language search string
|
|
33
|
+
- \`--top-k\`: number of results, 1–100, default 3
|
|
34
|
+
- \`--category\`: filter by category
|
|
35
|
+
- \`--tags\`: comma-separated tags filter
|
|
36
|
+
- \`--pretty\`: pretty-print JSON output
|
|
37
|
+
|
|
38
|
+
### get
|
|
39
|
+
Retrieve one memory by exact ID.
|
|
40
|
+
|
|
41
|
+
empirical memory get --memory-id <id> [--pretty]
|
|
42
|
+
|
|
43
|
+
### list
|
|
44
|
+
Browse memories deterministically. Use \`--cursor\` from the previous response while \`pagination.hasMore\` is true.
|
|
45
|
+
|
|
46
|
+
empirical memory list [--category project] [--tags canonical,launch] [--start-date <ISO>] [--end-date <ISO>] [--limit 20] [--cursor <cursor>] [--oldest] [--pretty]
|
|
47
|
+
|
|
48
|
+
Flags:
|
|
49
|
+
- \`--category\`, \`--tags\`: optional filters
|
|
50
|
+
- \`--start-date\`, \`--end-date\`: createdAt bounds
|
|
51
|
+
- \`--limit\`: page size, 1–100, default 20
|
|
52
|
+
- \`--cursor\`: continuation cursor from a previous page
|
|
53
|
+
- \`--oldest\`: sort oldest first; default is newest first
|
|
54
|
+
- \`--pretty\`: pretty-print JSON output
|
|
55
|
+
|
|
56
|
+
### record
|
|
57
|
+
Save a new memory.
|
|
58
|
+
|
|
59
|
+
empirical memory record --category build --summary "Use ESM modules for all new packages" [--tags ts,node] [--node-type observation]
|
|
60
|
+
|
|
61
|
+
Flags:
|
|
62
|
+
- \`--category\` (required unless --json-file): memory category
|
|
63
|
+
- \`--summary\` (required unless --json-file): memory summary text
|
|
64
|
+
- \`--tags\`: comma-separated tags
|
|
65
|
+
- \`--node-type\`: node type, default \`observation\`
|
|
66
|
+
- \`--json-file\`: path to JSON file with full memory payload
|
|
67
|
+
|
|
68
|
+
### note
|
|
69
|
+
Add a note to an existing memory.
|
|
70
|
+
|
|
71
|
+
empirical memory note --match "ESM modules" --text "confirmed in monorepo migration" [--min-score 0.35]
|
|
72
|
+
|
|
73
|
+
Flags:
|
|
74
|
+
- \`--match\` (required unless --memory-id): search string to find the memory
|
|
75
|
+
- \`--text\` (required): note text to append
|
|
76
|
+
- \`--memory-id\`: use exact memory ID (skips fuzzy search)
|
|
77
|
+
- \`--min-score\`: minimum similarity score, default 0.35
|
|
78
|
+
|
|
79
|
+
### update
|
|
80
|
+
Update an existing memory.
|
|
81
|
+
|
|
82
|
+
empirical memory update --match "ESM modules" --patch-json '{"summary":"Updated summary"}' [--min-score 0.35]
|
|
83
|
+
|
|
84
|
+
Flags:
|
|
85
|
+
- \`--match\` (required unless --memory-id): search string
|
|
86
|
+
- \`--patch-json\`: JSON string with fields to update
|
|
87
|
+
- \`--json-file\`: path to JSON patch file
|
|
88
|
+
- \`--memory-id\`: use exact memory ID
|
|
89
|
+
- \`--min-score\`: minimum similarity score, default 0.35
|
|
90
|
+
|
|
91
|
+
### delete
|
|
92
|
+
Delete a memory. Without --confirm, runs a dry-run and prints what would be deleted.
|
|
93
|
+
|
|
94
|
+
empirical memory delete --match "old preference" --confirm [--min-score 0.35]
|
|
95
|
+
|
|
96
|
+
Flags:
|
|
97
|
+
- \`--match\` (required unless --memory-id): search string
|
|
98
|
+
- \`--confirm\`: actually delete (without this, dry-run only)
|
|
99
|
+
- \`--memory-id\`: use exact memory ID
|
|
100
|
+
- \`--min-score\`: minimum similarity score, default 0.35
|
|
101
|
+
|
|
102
|
+
## Error output contract
|
|
103
|
+
- Errors → stderr as JSON: \`{ "error": true, "code": "...", "message": "..." }\`
|
|
104
|
+
- Success → stdout as JSON (compact by default)
|
|
105
|
+
`,
|
|
106
|
+
|
|
107
|
+
install: `# Empirical Install
|
|
108
|
+
|
|
109
|
+
empirical install
|
|
110
|
+
|
|
111
|
+
Runs the interactive installer to connect Empirical memory to your AI coding CLI(s).
|
|
112
|
+
|
|
113
|
+
## What it does
|
|
114
|
+
1. Detects installed CLIs: Codex, GitHub Copilot, Claude Code
|
|
115
|
+
2. Prompts for CLI target selection
|
|
116
|
+
3. Asks how agents should interact with Empirical:
|
|
117
|
+
- Mode 1: Via MCP tool (recommended) — registers Empirical as an MCP server
|
|
118
|
+
- Mode 2: Via CLI commands — writes CLI-direct instruction block, no MCP registration
|
|
119
|
+
- Mode 3: Both — MCP primary with CLI fallback
|
|
120
|
+
4. Stores the personalized behavior policy for the native Empirical Memory Skill; global
|
|
121
|
+
instruction files are left unchanged
|
|
122
|
+
5. Offers optional browser OAuth login for CLI use, optional headless/API-key bootstrap for
|
|
123
|
+
automation, containers, and external runtimes, and optional work-history/scar-tissue guidance
|
|
124
|
+
6. For Claude, installs a \`SessionStart\` hook in \`~/.claude/settings.json\` that calls the
|
|
125
|
+
\`empirical\` CLI directly once per session — deterministic baseline context that doesn't
|
|
126
|
+
depend on the model deciding to call a tool. Codex/Copilot have no equivalent hook mechanism.
|
|
127
|
+
7. Installs the native \`empirical-memory\` plugin for each selected client. The plugin owns
|
|
128
|
+
the Empirical MCP connection and all agent-facing Skills; the CLI does not generate duplicate
|
|
129
|
+
\`SKILL.md\` or \`EMPIRICAL_SKILL.md\` files.
|
|
130
|
+
|
|
131
|
+
## Native plugin only
|
|
132
|
+
|
|
133
|
+
empirical plugin install <codex|copilot|claude|all> [--all-optional]
|
|
134
|
+
|
|
135
|
+
empirical plugin check [--force]
|
|
136
|
+
|
|
137
|
+
Installs or refreshes only the native marketplace plugin. It does not run the MCP/auth questionnaire
|
|
138
|
+
or modify global instruction files. With no target, the command installs for detected CLIs.
|
|
139
|
+
|
|
140
|
+
The CLI performs a best-effort native plugin version check during interactive use (at most once per
|
|
141
|
+
day). It is silent for noninteractive/headless commands. Use \`empirical plugin check --force\` to
|
|
142
|
+
check immediately.
|
|
143
|
+
|
|
144
|
+
Use \`empirical policy show\` to inspect the current native-skill behavior policy and
|
|
145
|
+
\`empirical policy set --json '{"allowDiscretionaryUse":false}'\` to change it without rerunning
|
|
146
|
+
the installer. The policy is cached locally and refreshed when stale.
|
|
147
|
+
`,
|
|
148
|
+
|
|
149
|
+
workspace: `# Empirical Workspace Commands
|
|
150
|
+
|
|
151
|
+
Manage disposable team workspaces and which Empirical workspace is active for this OAuth client.
|
|
152
|
+
|
|
153
|
+
## Commands
|
|
154
|
+
|
|
155
|
+
### List workspaces
|
|
156
|
+
|
|
157
|
+
empirical workspace list
|
|
158
|
+
|
|
159
|
+
Shows all available workspaces and which one is currently selected.
|
|
160
|
+
|
|
161
|
+
### Show current workspace
|
|
162
|
+
|
|
163
|
+
empirical workspace current
|
|
164
|
+
|
|
165
|
+
Prints the currently selected workspace name.
|
|
166
|
+
|
|
167
|
+
### Select a workspace
|
|
168
|
+
|
|
169
|
+
empirical workspace select [<number|name>]
|
|
170
|
+
|
|
171
|
+
Interactive workspace selector (prompts if no argument given).
|
|
172
|
+
Pass a number (from \`list\`) or an exact workspace name.
|
|
173
|
+
|
|
174
|
+
empirical workspace select 2
|
|
175
|
+
empirical workspace select "My Workspace"
|
|
176
|
+
|
|
177
|
+
Note: workspace selection may be admin-locked for some OAuth clients.
|
|
178
|
+
|
|
179
|
+
### Create a demo workspace
|
|
180
|
+
|
|
181
|
+
empirical workspace create "Demo Workspace" --description "Temporary shared context"
|
|
182
|
+
|
|
183
|
+
Creates a team workspace using your existing plan quota and prints its workspace ID.
|
|
184
|
+
|
|
185
|
+
### Use a workspace
|
|
186
|
+
|
|
187
|
+
empirical workspace use <number|name|id>
|
|
188
|
+
|
|
189
|
+
Friendly alias for \`workspace select\`. It accepts the list number, exact case-insensitive name, or workspace ID.
|
|
190
|
+
|
|
191
|
+
### Share an active workspace
|
|
192
|
+
|
|
193
|
+
empirical workspace invite create --role writer --expires-in-days 7 --max-uses 3
|
|
194
|
+
empirical workspace invite list
|
|
195
|
+
empirical workspace invite revoke <token>
|
|
196
|
+
|
|
197
|
+
Invite commands operate on the active team workspace. Create prints a shareable URL.
|
|
198
|
+
|
|
199
|
+
### Soft-delete a demo workspace
|
|
200
|
+
|
|
201
|
+
empirical workspace delete <number|name|id>
|
|
202
|
+
empirical workspace delete <number|name|id> --confirm
|
|
203
|
+
|
|
204
|
+
Deletion dry-runs by default. \`--confirm\` calls the existing soft-delete endpoint; the Default Brain cannot be deleted. If the active workspace is deleted, the CLI selects the Default Brain.
|
|
205
|
+
`,
|
|
206
|
+
|
|
207
|
+
doctor: `# Empirical Doctor
|
|
208
|
+
|
|
209
|
+
empirical doctor [--output json]
|
|
210
|
+
|
|
211
|
+
Checks the health of your Empirical CLI setup. Useful for diagnosing auth and connectivity issues.
|
|
212
|
+
|
|
213
|
+
## Checks performed (in order)
|
|
214
|
+
|
|
215
|
+
1. **Token presence** — verifies a token exists via EMPIRICAL_ACCESS_TOKEN env, apiKey field,
|
|
216
|
+
or accessToken field in the token file
|
|
217
|
+
2. **Token type** — detects API key (long alphanumeric, no dots) vs JWT (3 dot-separated parts)
|
|
218
|
+
3. **Token expiry** — if JWT, decodes the payload and checks the \`exp\` field; warns if no \`exp\`
|
|
219
|
+
4. **API connectivity** — calls GET /api/users/me; 200 = OK, anything else = FAIL
|
|
220
|
+
5. **Workspace** — reads workspaceId from the /api/users/me response; warns if null
|
|
221
|
+
|
|
222
|
+
## Output format
|
|
223
|
+
|
|
224
|
+
Default: human-readable lines prefixed with [OK], [WARN], or [FAIL].
|
|
225
|
+
|
|
226
|
+
empirical doctor --output json
|
|
227
|
+
|
|
228
|
+
JSON output: \`{ "checks": [{ "name", "status", "message" }], "overall": "ok"|"warn"|"fail" }\`
|
|
229
|
+
Useful for agent/automation use.
|
|
230
|
+
|
|
231
|
+
## Exit codes
|
|
232
|
+
- 0: all checks passed (ok or warn)
|
|
233
|
+
- 1: one or more checks failed
|
|
234
|
+
`,
|
|
235
|
+
|
|
236
|
+
agents: `# Empirical Agent Integration
|
|
237
|
+
|
|
238
|
+
Empirical memory integrates with three AI coding CLIs.
|
|
239
|
+
|
|
240
|
+
## Supported agents
|
|
241
|
+
|
|
242
|
+
| Agent | Config dir | Instruction file | MCP config |
|
|
243
|
+
|-------------|---------------------|-------------------------------|-------------------------------------|
|
|
244
|
+
| Claude Code | ~/.claude/ | CLAUDE.md | ~/.claude.json (mcpServers) |
|
|
245
|
+
| Codex CLI | ~/.codex/ | AGENTS.md | codex mcp add |
|
|
246
|
+
| Copilot CLI | ~/.copilot/ | copilot-instructions.md | ~/.copilot/mcp-config.json |
|
|
247
|
+
|
|
248
|
+
## Integration modes
|
|
249
|
+
|
|
250
|
+
**Mode 1: MCP tool (recommended)**
|
|
251
|
+
Empirical is registered as an MCP server. Agents call memory tools (query_memories,
|
|
252
|
+
record_graph_memory, etc.) directly via the MCP protocol. Best for interactive sessions.
|
|
253
|
+
|
|
254
|
+
**Mode 2: CLI commands**
|
|
255
|
+
Agents run \`empirical memory <subcommand>\` shell commands. No MCP registration required.
|
|
256
|
+
Suitable for headless/scripted environments or when MCP is unavailable.
|
|
257
|
+
|
|
258
|
+
**Mode 3: Both**
|
|
259
|
+
MCP is primary; CLI commands are available as fallback. Maximizes reliability.
|
|
260
|
+
|
|
261
|
+
## Inventory commands
|
|
262
|
+
|
|
263
|
+
empirical skill list
|
|
264
|
+
empirical device list
|
|
265
|
+
empirical device show <deviceId>
|
|
266
|
+
|
|
267
|
+
Use these to inspect Skills across Kits and the effective Skills installed for a device.
|
|
268
|
+
|
|
269
|
+
The native \`empirical-memory\` plugin owns its Skills and MCP connection. Run
|
|
270
|
+
\`empirical install\` to install it and configure detected agents.
|
|
271
|
+
`,
|
|
272
|
+
|
|
273
|
+
kit: `# Empirical Skills Kits
|
|
274
|
+
|
|
275
|
+
Kits are named collections of agent Skills. A Kit can contain Skills authored in Empirical
|
|
276
|
+
or discovered from a git repository, then sync the resolved set to your local agents.
|
|
277
|
+
|
|
278
|
+
## Commands
|
|
279
|
+
|
|
280
|
+
### Create and list Kits
|
|
281
|
+
|
|
282
|
+
empirical kit create "My Team Kit"
|
|
283
|
+
empirical kit list
|
|
284
|
+
|
|
285
|
+
### Add or remove a git source
|
|
286
|
+
|
|
287
|
+
empirical kit add-source "My Team Kit" https://github.com/example/skills.git
|
|
288
|
+
empirical kit add-source "My Team Kit" https://github.com/example/skills.git --ref main --path skills
|
|
289
|
+
empirical kit remove-source "My Team Kit" <source-id>
|
|
290
|
+
|
|
291
|
+
Each source is scanned for valid \`SKILL.md\` files. A Skill file must begin with YAML
|
|
292
|
+
frontmatter containing both \`name\` and \`description\`, for example:
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
name: my-skill
|
|
296
|
+
description: Explain when this skill should be used.
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
### Activate and sync
|
|
300
|
+
|
|
301
|
+
empirical kit use "My Team Kit"
|
|
302
|
+
empirical kit sync
|
|
303
|
+
empirical kit sync --dry-run
|
|
304
|
+
|
|
305
|
+
\`kit use\` activates a Kit on this device and syncs its Skills into detected agent
|
|
306
|
+
directories. \`kit sync\` refreshes the active Kit. Use \`--quiet\` for unattended syncs
|
|
307
|
+
and \`--force\` only when you intentionally want to bypass the local-edit guard.
|
|
308
|
+
|
|
309
|
+
### Inventory
|
|
310
|
+
|
|
311
|
+
empirical skill list
|
|
312
|
+
empirical kit list
|
|
313
|
+
empirical kit devices "My Team Kit"
|
|
314
|
+
empirical device list
|
|
315
|
+
empirical device show <deviceId>
|
|
316
|
+
|
|
317
|
+
\`skill list\` shows Skills resolved from owned Kits. \`device list\` shows registered devices and
|
|
318
|
+
their active Kits; \`device show\` shows the effective resolved Skills for one device.
|
|
319
|
+
|
|
320
|
+
### Manage devices and autosync
|
|
321
|
+
|
|
322
|
+
empirical kit autosync "My Team Kit" on
|
|
323
|
+
empirical kit devices "My Team Kit"
|
|
324
|
+
empirical kit deactivate "My Team Kit" <device-id>
|
|
325
|
+
|
|
326
|
+
Autosync keeps a registered device current. Interactive syncs review new or changed external
|
|
327
|
+
Skills; unattended syncs apply them and leave a review marker for the next interactive sync.
|
|
328
|
+
`,
|
|
329
|
+
|
|
330
|
+
skill: `# Empirical Skills
|
|
331
|
+
|
|
332
|
+
Skills are agent-facing \`SKILL.md\` instruction files. Authored Skills can be pushed into a
|
|
333
|
+
Kit so they follow you across machines and projects.
|
|
334
|
+
|
|
335
|
+
## Push an authored Skill
|
|
336
|
+
|
|
337
|
+
empirical skill list
|
|
338
|
+
|
|
339
|
+
empirical skill push path/to/SKILL.md
|
|
340
|
+
empirical skill push path/to/SKILL.md --kit "My Team Kit"
|
|
341
|
+
|
|
342
|
+
The file must contain YAML frontmatter at the top with both required fields:
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
name: my-skill
|
|
346
|
+
description: Explain when this skill should be used.
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
The remainder of the file is the Skill body. The CLI validates the frontmatter before upload.
|
|
350
|
+
After pushing, activate or sync the target Kit with \`empirical kit use\` or \`empirical kit sync\`.
|
|
351
|
+
|
|
352
|
+
## Review and rollback
|
|
353
|
+
|
|
354
|
+
empirical skill history <authored-skill-id>
|
|
355
|
+
empirical skill rollback <authored-skill-id> --to-version 2
|
|
356
|
+
|
|
357
|
+
Use history to inspect versions and rollback to a known-good version when a Skill needs to be
|
|
358
|
+
reverted. External git-sourced Skills are reviewed during Kit sync and are not authored with
|
|
359
|
+
this command.
|
|
360
|
+
`,
|
|
361
|
+
|
|
362
|
+
auth: `# Empirical Auth
|
|
363
|
+
|
|
364
|
+
## Token types
|
|
365
|
+
|
|
366
|
+
**API key**: Long alphanumeric string, no dots. Never expires. Preferred for automation.
|
|
367
|
+
**JWT**: Three dot-separated base64 parts. Has an expiry (\`exp\` claim). Used for browser OAuth flows.
|
|
368
|
+
|
|
369
|
+
## Browser OAuth login
|
|
370
|
+
|
|
371
|
+
empirical auth login
|
|
372
|
+
|
|
373
|
+
Opens the browser, completes OAuth with the Empirical account, and saves a CLI session at
|
|
374
|
+
\`~/.empirical/oauth-session.json\`. Use this for normal interactive CLI use when you do not
|
|
375
|
+
want to provision a permanent API key.
|
|
376
|
+
|
|
377
|
+
## Bootstrap command (headless / non-interactive)
|
|
378
|
+
|
|
379
|
+
empirical oauth bootstrap headless --write-env
|
|
380
|
+
|
|
381
|
+
Exchanges account credentials (client ID + secret, i.e. your userId + API key) for an access
|
|
382
|
+
token via \`grant_type=client_credentials\` — this is NOT a dynamically-registered OAuth client;
|
|
383
|
+
the server treats client_id/client_secret as userId/apiKey directly.
|
|
384
|
+
Writes token to \`.empirical/headless-token.json\` and \`.empirical/headless.env\`.
|
|
385
|
+
|
|
386
|
+
No portal visit or manual copy/paste needed: if no client ID/secret is found via
|
|
387
|
+
\`EMPIRICAL_CLIENT_ID\`/\`EMPIRICAL_CLIENT_SECRET\` env vars or \`~/.empirical/oauth-client.json\`
|
|
388
|
+
(set via \`empirical oauth client set\`), the CLI self-provisions your account's API key via
|
|
389
|
+
\`POST /api/v1/users/me/credentials\`, authenticated with whatever bearer token it already has
|
|
390
|
+
(existing token file, or — only if nothing exists yet — one browser login). If you already have
|
|
391
|
+
an API key on the account, the CLI will never silently rotate it (that would break other
|
|
392
|
+
integrations using it); it asks for explicit confirmation in a TTY, or gives up quietly in
|
|
393
|
+
non-interactive contexts. If a stored client is rejected by the server ("Invalid client
|
|
394
|
+
credentials"), the CLI discards it and re-provisions automatically on the same run — unless
|
|
395
|
+
\`--client-id\`/\`--client-secret\` was passed explicitly, in which case the error is surfaced
|
|
396
|
+
as-is.
|
|
397
|
+
|
|
398
|
+
## Refresh command
|
|
399
|
+
|
|
400
|
+
empirical oauth refresh headless
|
|
401
|
+
|
|
402
|
+
Exchanges a stored refresh token for a new access token. Token file must exist.
|
|
403
|
+
|
|
404
|
+
## Token resolution priority (resolveAccessToken)
|
|
405
|
+
|
|
406
|
+
1. \`EMPIRICAL_ACCESS_TOKEN\` environment variable (skipped if it's an expired JWT, so a
|
|
407
|
+
stale session token can never permanently shadow a valid credential below it)
|
|
408
|
+
2. \`apiKey\` field in token file
|
|
409
|
+
3. \`accessToken\` field in token file
|
|
410
|
+
4. \`~/.empirical/oauth-session.json\` browser OAuth session
|
|
411
|
+
5. \`EMPIRICAL_API_KEY\` or \`EMPIRICAL_CLIENT_SECRET\` environment variable, used directly
|
|
412
|
+
as a permanent Bearer credential — lets a stateless agent environment authenticate from
|
|
413
|
+
client_id/secret alone with no token file and no bootstrap step
|
|
414
|
+
|
|
415
|
+
Token file lookup order:
|
|
416
|
+
1. Path in \`EMPIRICAL_TOKEN_OUTPUT\` env var
|
|
417
|
+
2. \`.empirical/headless-token.json\` (current working directory)
|
|
418
|
+
3. \`~/.empirical/headless-token.json\` (home directory)
|
|
419
|
+
|
|
420
|
+
The browser OAuth session is stored separately at \`~/.empirical/oauth-session.json\`.
|
|
421
|
+
|
|
422
|
+
## Verify auth status
|
|
423
|
+
|
|
424
|
+
empirical doctor
|
|
425
|
+
|
|
426
|
+
Checks token presence, type, expiry, and API connectivity.
|
|
427
|
+
`,
|
|
428
|
+
|
|
429
|
+
user: `# Empirical User Identity
|
|
430
|
+
|
|
431
|
+
## Via MCP (when Empirical MCP server is connected)
|
|
432
|
+
|
|
433
|
+
Use the \`get_user\` MCP tool. Returns: id, email, name, workspaceId, tier, and plan details.
|
|
434
|
+
|
|
435
|
+
## Via CLI
|
|
436
|
+
|
|
437
|
+
empirical doctor --output json
|
|
438
|
+
|
|
439
|
+
Returns a JSON object with a \`checks\` array. The connectivity check (name: "api")
|
|
440
|
+
includes the user record from /api/users/me when successful.
|
|
441
|
+
|
|
442
|
+
empirical workspace current
|
|
443
|
+
|
|
444
|
+
Prints the active workspace name.
|
|
445
|
+
|
|
446
|
+
## Notes
|
|
447
|
+
|
|
448
|
+
- \`get_user\` has no standalone CLI subcommand; use \`empirical doctor --output json\` to
|
|
449
|
+
inspect identity from the shell.
|
|
450
|
+
- If MCP is unavailable, parse the \`api\` check from doctor JSON output for user context.
|
|
451
|
+
`
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
let cachedCliVersion = null;
|
|
455
|
+
|
|
456
|
+
function getCliVersion() {
|
|
457
|
+
if (cachedCliVersion) return cachedCliVersion;
|
|
458
|
+
try {
|
|
459
|
+
const cliPackagePath = path.join(__dirname, '..', 'package.json');
|
|
460
|
+
const parsed = JSON.parse(fs.readFileSync(cliPackagePath, 'utf8'));
|
|
461
|
+
if (parsed && typeof parsed.version === 'string' && parsed.version.trim()) {
|
|
462
|
+
cachedCliVersion = parsed.version.trim();
|
|
463
|
+
return cachedCliVersion;
|
|
464
|
+
}
|
|
465
|
+
} catch (_error) {
|
|
466
|
+
// ignore and use fallback
|
|
467
|
+
}
|
|
468
|
+
cachedCliVersion = 'unknown';
|
|
469
|
+
return cachedCliVersion;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function deriveBaseUrlFromMcpUrl() {
|
|
473
|
+
try {
|
|
474
|
+
const parsed = new URL(EMPIRICAL_MCP_URL);
|
|
475
|
+
parsed.pathname = parsed.pathname.replace(/\/mcp\/?$/, '');
|
|
476
|
+
return parsed.origin + parsed.pathname.replace(/\/$/, '');
|
|
477
|
+
} catch (_error) {
|
|
478
|
+
return '';
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
module.exports = {
|
|
483
|
+
EMPIRICAL_MCP_NAME,
|
|
484
|
+
EMPIRICAL_MCP_LEGACY_NAMES,
|
|
485
|
+
EMPIRICAL_MCP_URL,
|
|
486
|
+
COPILOT_OAUTH_CLIENT_ID,
|
|
487
|
+
MEMORY_CATEGORIES,
|
|
488
|
+
DOC_TOPICS,
|
|
489
|
+
getCliVersion,
|
|
490
|
+
deriveBaseUrlFromMcpUrl
|
|
491
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// US-011: local device config (~/.empirical/config.json) — deviceId bootstrap/persistence,
|
|
4
|
+
// activeKit, and lastSyncedVersion. Follows the same `os.homedir()/.empirical/` convention
|
|
5
|
+
// as the headless-token.json file in packages/cli/lib/auth.js.
|
|
6
|
+
const crypto = require('crypto');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { readJsonIfExists, writeFile } = require('./fsUtils');
|
|
10
|
+
|
|
11
|
+
function getEmpiricalDir() {
|
|
12
|
+
return path.join(os.homedir(), '.empirical');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getDeviceConfigPath() {
|
|
16
|
+
return path.join(getEmpiricalDir(), 'config.json');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function readDeviceConfig() {
|
|
20
|
+
return readJsonIfExists(getDeviceConfigPath()) || {};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function writeDeviceConfig(config) {
|
|
24
|
+
writeFile(getDeviceConfigPath(), `${JSON.stringify(config, null, 2)}\n`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readPolicyCache() {
|
|
28
|
+
const config = readDeviceConfig();
|
|
29
|
+
return config.empiricalPolicy || null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function writePolicyCache(policyCache) {
|
|
33
|
+
const config = readDeviceConfig();
|
|
34
|
+
config.empiricalPolicy = policyCache;
|
|
35
|
+
writeDeviceConfig(config);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Ensures a deviceId exists in the local config, generating+persisting one on first use.
|
|
39
|
+
// Reused across all future CLI invocations on this machine.
|
|
40
|
+
function ensureDeviceId() {
|
|
41
|
+
const config = readDeviceConfig();
|
|
42
|
+
if (config.deviceId) {
|
|
43
|
+
return config.deviceId;
|
|
44
|
+
}
|
|
45
|
+
const deviceId = crypto.randomUUID();
|
|
46
|
+
config.deviceId = deviceId;
|
|
47
|
+
writeDeviceConfig(config);
|
|
48
|
+
return deviceId;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getSyncManifestPath() {
|
|
52
|
+
return path.join(getEmpiricalDir(), 'sync-manifest.json');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function readSyncManifest() {
|
|
56
|
+
return readJsonIfExists(getSyncManifestPath()) || {};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function writeSyncManifest(manifest) {
|
|
60
|
+
writeFile(getSyncManifestPath(), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// US-016: local record of git_custom-sourced skills whose upstream content changed and was
|
|
64
|
+
// applied unattended (during a `--quiet` sync) without a human reviewing it first. Shape:
|
|
65
|
+
// { [skillName]: { from, firstDetectedAt, source, status } } — see skillSync.js for the gate
|
|
66
|
+
// that populates/clears this. Same `os.homedir()/.empirical/` convention as config.json and
|
|
67
|
+
// sync-manifest.json.
|
|
68
|
+
function getPendingReviewPath() {
|
|
69
|
+
return path.join(getEmpiricalDir(), 'pending-review.json');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function readPendingReview() {
|
|
73
|
+
return readJsonIfExists(getPendingReviewPath()) || {};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function writePendingReview(pendingReview) {
|
|
77
|
+
writeFile(getPendingReviewPath(), `${JSON.stringify(pendingReview, null, 2)}\n`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
getEmpiricalDir,
|
|
82
|
+
getDeviceConfigPath,
|
|
83
|
+
readDeviceConfig,
|
|
84
|
+
writeDeviceConfig,
|
|
85
|
+
readPolicyCache,
|
|
86
|
+
writePolicyCache,
|
|
87
|
+
ensureDeviceId,
|
|
88
|
+
getSyncManifestPath,
|
|
89
|
+
readSyncManifest,
|
|
90
|
+
writeSyncManifest,
|
|
91
|
+
getPendingReviewPath,
|
|
92
|
+
readPendingReview,
|
|
93
|
+
writePendingReview
|
|
94
|
+
};
|
package/lib/doc.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { log, warn, ANSI, colorize, supportsColor } = require('./ui');
|
|
4
|
+
const { DOC_TOPICS } = require('./constants');
|
|
5
|
+
|
|
6
|
+
function runDoc(args) {
|
|
7
|
+
const topic = (args[0] || '').trim().toLowerCase();
|
|
8
|
+
|
|
9
|
+
const descriptions = {
|
|
10
|
+
memory: 'Query, record, update, delete memories via CLI',
|
|
11
|
+
user: 'Get current user identity (MCP: get_user; CLI: doctor --output json)',
|
|
12
|
+
workspace: 'List, view, and switch workspaces',
|
|
13
|
+
doctor: 'Check token, connectivity, and workspace health',
|
|
14
|
+
agents: 'Supported agents, integration modes, and native plugin setup',
|
|
15
|
+
auth: 'Token types, bootstrap, refresh, resolution order',
|
|
16
|
+
install: 'Install Empirical for Codex / Copilot / Claude Code',
|
|
17
|
+
kit: 'Create, source, activate, and sync Skills Kits',
|
|
18
|
+
skill: 'Push, inspect, and roll back authored Skills'
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
if (!topic) {
|
|
22
|
+
log('Available documentation topics:');
|
|
23
|
+
log('');
|
|
24
|
+
for (const [key, desc] of Object.entries(descriptions)) {
|
|
25
|
+
const padded = key.padEnd(12);
|
|
26
|
+
if (supportsColor()) {
|
|
27
|
+
log(` ${colorize(ANSI.cyan, padded)} ${desc}`);
|
|
28
|
+
} else {
|
|
29
|
+
log(` ${padded} ${desc}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
log('');
|
|
33
|
+
log('Run: empirical doc <topic>');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!DOC_TOPICS[topic]) {
|
|
38
|
+
warn(`Unknown topic: "${topic}"`);
|
|
39
|
+
log('');
|
|
40
|
+
log('Available topics: ' + Object.keys(descriptions).join(', '));
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
log(DOC_TOPICS[topic]);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = {
|
|
49
|
+
runDoc
|
|
50
|
+
};
|