@empirical-gauzza/cli 1.0.25 → 1.0.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -63
- package/lib/constants.js +6 -1
- package/lib/doc.js +1 -1
- package/lib/env.js +49 -49
- package/lib/http.js +187 -179
- package/lib/installers/coreMemoryBlock.js +5 -0
- package/lib/installers/detect.js +47 -47
- package/lib/installers/skill-variants/SKILL.both.md +13 -0
- package/lib/installers/skill-variants/SKILL.cli.md +13 -0
- package/lib/installers/skill-variants/SKILL.mcp.md +13 -0
- package/lib/memoryCommands.js +25 -2
- package/lib/oauthClient.js +222 -222
- package/lib/policyCommands.js +144 -144
- package/lib/workspaceContext.js +47 -47
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
# @empirical-gauzza/cli
|
|
2
|
-
|
|
3
|
-
Command-line installer and local MCP wiring helper for [Empirical](https://empirical.gauzza.com) — portable memory infrastructure for AI agents, assistants, and coding tools.
|
|
4
|
-
|
|
5
|
-
Full documentation: **[empirical.gauzza.com/docs/cli-details](https://empirical.gauzza.com/docs/cli-details)**
|
|
6
|
-
|
|
7
|
-
## Install
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install -g @empirical-gauzza/cli
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## Quick start
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
empirical install # interactive setup for Claude Code, Codex, or Copilot CLI
|
|
17
|
-
empirical auth login # browser-based OAuth login
|
|
18
|
-
empirical doctor # verify token, API connectivity, and workspace status
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Full walkthrough: [empirical.gauzza.com/docs/install](https://empirical.gauzza.com/docs/install)
|
|
22
|
-
|
|
23
|
-
## What it does
|
|
24
|
-
|
|
25
|
-
- Installs and configures the Empirical MCP plugin for supported coding agents (Claude Code, Codex CLI, Copilot CLI)
|
|
26
|
-
- Manages OAuth login, including headless/CI bootstrap and Docker token flows
|
|
27
|
-
- Reads, writes, and maintains memories from the command line
|
|
28
|
-
- Manages Skills Kits — create, sync, and push authored Skills across machines
|
|
29
|
-
- Manages workspaces, invites, and registered devices
|
|
30
|
-
- Exposes agent-readable docs and a connectivity `doctor` check
|
|
31
|
-
|
|
32
|
-
## Commands
|
|
33
|
-
|
|
34
|
-
| Command | Docs |
|
|
35
|
-
| --- | --- |
|
|
36
|
-
| `empirical install` | [/docs/install](https://empirical.gauzza.com/docs/install#connect) |
|
|
37
|
-
| `empirical plugin install / check` | [/docs/cli-plugins](https://empirical.gauzza.com/docs/cli-plugins) |
|
|
38
|
-
| `empirical auth login / logout` | [/docs/cli-auth](https://empirical.gauzza.com/docs/cli-auth) |
|
|
39
|
-
| `empirical oauth bootstrap / refresh` | [/docs/cli-oauth](https://empirical.gauzza.com/docs/cli-oauth) |
|
|
40
|
-
| `empirical policy show / set` | [/docs/cli-policy](https://empirical.gauzza.com/docs/cli-policy) |
|
|
41
|
-
| `empirical memory query / record / update / delete / export ...` | [/docs/cli-memory](https://empirical.gauzza.com/docs/cli-memory) |
|
|
42
|
-
| `empirical workspace list / create / invite ...` | [/docs/cli-workspaces](https://empirical.gauzza.com/docs/cli-workspaces) |
|
|
43
|
-
| `empirical kit create / sync / use ...` | [/docs/cli-kit](https://empirical.gauzza.com/docs/cli-kit) |
|
|
44
|
-
| `empirical skill push / history / rollback` | [/docs/cli-skill](https://empirical.gauzza.com/docs/cli-skill) |
|
|
45
|
-
| `empirical device list / show` | [/docs/cli-devices](https://empirical.gauzza.com/docs/cli-devices) |
|
|
46
|
-
| `empirical doc [topic]` | [/docs/cli-doc-command](https://empirical.gauzza.com/docs/cli-doc-command) |
|
|
47
|
-
| `empirical doctor` | [/docs/cli-doctor](https://empirical.gauzza.com/docs/cli-doctor) |
|
|
48
|
-
|
|
49
|
-
Run `empirical --help` at any time for the full, current command list, or `empirical doc` for agent-readable topic docs.
|
|
50
|
-
|
|
51
|
-
## Security model
|
|
52
|
-
|
|
53
|
-
API keys and OAuth tokens are stored locally and are never printed in full after initial issuance. See [empirical.gauzza.com/docs/security-model](https://empirical.gauzza.com/docs/security-model) for details on credential handling and headless/CI usage.
|
|
54
|
-
|
|
55
|
-
## Links
|
|
56
|
-
|
|
57
|
-
- [Documentation](https://empirical.gauzza.com/docs/install)
|
|
58
|
-
- [Homepage](https://empirical.gauzza.com)
|
|
59
|
-
- [Support](https://empirical.gauzza.com/support/)
|
|
60
|
-
|
|
61
|
-
## License
|
|
62
|
-
|
|
63
|
-
MIT
|
|
1
|
+
# @empirical-gauzza/cli
|
|
2
|
+
|
|
3
|
+
Command-line installer and local MCP wiring helper for [Empirical](https://empirical.gauzza.com) — portable memory infrastructure for AI agents, assistants, and coding tools.
|
|
4
|
+
|
|
5
|
+
Full documentation: **[empirical.gauzza.com/docs/cli-details](https://empirical.gauzza.com/docs/cli-details)**
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @empirical-gauzza/cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
empirical install # interactive setup for Claude Code, Codex, or Copilot CLI
|
|
17
|
+
empirical auth login # browser-based OAuth login
|
|
18
|
+
empirical doctor # verify token, API connectivity, and workspace status
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Full walkthrough: [empirical.gauzza.com/docs/install](https://empirical.gauzza.com/docs/install)
|
|
22
|
+
|
|
23
|
+
## What it does
|
|
24
|
+
|
|
25
|
+
- Installs and configures the Empirical MCP plugin for supported coding agents (Claude Code, Codex CLI, Copilot CLI)
|
|
26
|
+
- Manages OAuth login, including headless/CI bootstrap and Docker token flows
|
|
27
|
+
- Reads, writes, and maintains memories from the command line
|
|
28
|
+
- Manages Skills Kits — create, sync, and push authored Skills across machines
|
|
29
|
+
- Manages workspaces, invites, and registered devices
|
|
30
|
+
- Exposes agent-readable docs and a connectivity `doctor` check
|
|
31
|
+
|
|
32
|
+
## Commands
|
|
33
|
+
|
|
34
|
+
| Command | Docs |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| `empirical install` | [/docs/install](https://empirical.gauzza.com/docs/install#connect) |
|
|
37
|
+
| `empirical plugin install / check` | [/docs/cli-plugins](https://empirical.gauzza.com/docs/cli-plugins) |
|
|
38
|
+
| `empirical auth login / logout` | [/docs/cli-auth](https://empirical.gauzza.com/docs/cli-auth) |
|
|
39
|
+
| `empirical oauth bootstrap / refresh` | [/docs/cli-oauth](https://empirical.gauzza.com/docs/cli-oauth) |
|
|
40
|
+
| `empirical policy show / set` | [/docs/cli-policy](https://empirical.gauzza.com/docs/cli-policy) |
|
|
41
|
+
| `empirical memory query / record / update / delete / export ...` | [/docs/cli-memory](https://empirical.gauzza.com/docs/cli-memory) |
|
|
42
|
+
| `empirical workspace list / create / invite ...` | [/docs/cli-workspaces](https://empirical.gauzza.com/docs/cli-workspaces) |
|
|
43
|
+
| `empirical kit create / sync / use ...` | [/docs/cli-kit](https://empirical.gauzza.com/docs/cli-kit) |
|
|
44
|
+
| `empirical skill push / history / rollback` | [/docs/cli-skill](https://empirical.gauzza.com/docs/cli-skill) |
|
|
45
|
+
| `empirical device list / show` | [/docs/cli-devices](https://empirical.gauzza.com/docs/cli-devices) |
|
|
46
|
+
| `empirical doc [topic]` | [/docs/cli-doc-command](https://empirical.gauzza.com/docs/cli-doc-command) |
|
|
47
|
+
| `empirical doctor` | [/docs/cli-doctor](https://empirical.gauzza.com/docs/cli-doctor) |
|
|
48
|
+
|
|
49
|
+
Run `empirical --help` at any time for the full, current command list, or `empirical doc` for agent-readable topic docs.
|
|
50
|
+
|
|
51
|
+
## Security model
|
|
52
|
+
|
|
53
|
+
API keys and OAuth tokens are stored locally and are never printed in full after initial issuance. See [empirical.gauzza.com/docs/security-model](https://empirical.gauzza.com/docs/security-model) for details on credential handling and headless/CI usage.
|
|
54
|
+
|
|
55
|
+
## Links
|
|
56
|
+
|
|
57
|
+
- [Documentation](https://empirical.gauzza.com/docs/install)
|
|
58
|
+
- [Homepage](https://empirical.gauzza.com)
|
|
59
|
+
- [Support](https://empirical.gauzza.com/support/)
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
|
|
63
|
+
MIT
|
package/lib/constants.js
CHANGED
|
@@ -58,7 +58,12 @@ Flags:
|
|
|
58
58
|
### record
|
|
59
59
|
Save a new memory.
|
|
60
60
|
|
|
61
|
-
empirical memory record --category build --summary "Use ESM modules for all new packages" [--tags ts,node] [--node-type observation]
|
|
61
|
+
empirical memory record --category build --summary "Use ESM modules for all new packages" [--tags ts,node] [--node-type observation] [--author-type user|agent]
|
|
62
|
+
|
|
63
|
+
\`--author-type\` records WHO the fact belongs to, which the transport cannot know. Use
|
|
64
|
+
\`user\` for something the user said, decided or prefers; \`agent\` for your own session notes
|
|
65
|
+
and findings. Omitted, it is left unresolved rather than guessed. Retrieval reads it to keep
|
|
66
|
+
agent notes out of answers about the user's own life and work.
|
|
62
67
|
|
|
63
68
|
Passing text that contains code (backticks, \${...}, $(...)): do NOT put it in a double-quoted
|
|
64
69
|
\`--summary "..."\` -- the shell substitutes those away before the CLI runs and the write still
|
package/lib/doc.js
CHANGED
|
@@ -11,7 +11,7 @@ function runDoc(args) {
|
|
|
11
11
|
user: 'Get current user identity (MCP: get_user; CLI: doctor --output json)',
|
|
12
12
|
workspace: 'List, view, and switch workspaces',
|
|
13
13
|
doctor: 'Check token, connectivity, and workspace health',
|
|
14
|
-
agents: 'Supported agents, integration modes, and native plugin setup',
|
|
14
|
+
agents: 'Supported agents, integration modes, and native plugin setup',
|
|
15
15
|
auth: 'Token types, bootstrap, refresh, resolution order',
|
|
16
16
|
install: 'Install Empirical for Codex / Copilot / Claude Code',
|
|
17
17
|
kit: 'Create, source, activate, and sync Skills Kits',
|
package/lib/env.js
CHANGED
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { ask } = require('./ui');
|
|
4
|
-
|
|
5
|
-
function getEnv(name, fallback = '') {
|
|
6
|
-
return String(process.env[name] ?? fallback).trim();
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function requireEnv(name) {
|
|
10
|
-
const value = getEnv(name);
|
|
11
|
-
if (!value) {
|
|
12
|
-
throw new Error(`Missing required env var: ${name}`);
|
|
13
|
-
}
|
|
14
|
-
return value;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async function resolveRequiredValueFromEnvOrPrompt(envName, promptLabel) {
|
|
18
|
-
const value = getEnv(envName);
|
|
19
|
-
if (value) return value;
|
|
20
|
-
|
|
21
|
-
if (!process.stdin.isTTY) {
|
|
22
|
-
throw new Error(`Missing required env var: ${envName}`);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const answer = (await ask(`${promptLabel}: `)).trim();
|
|
26
|
-
if (!answer) {
|
|
27
|
-
throw new Error(`${promptLabel} is required`);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return answer;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function parseFlag(args, flagName) {
|
|
34
|
-
return args.includes(flagName);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function parseOption(args, optionName, fallback = '') {
|
|
38
|
-
const index = args.indexOf(optionName);
|
|
39
|
-
if (index === -1) return fallback;
|
|
40
|
-
return String(args[index + 1] || '').trim() || fallback;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
module.exports = {
|
|
44
|
-
getEnv,
|
|
45
|
-
requireEnv,
|
|
46
|
-
resolveRequiredValueFromEnvOrPrompt,
|
|
47
|
-
parseFlag,
|
|
48
|
-
parseOption
|
|
49
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { ask } = require('./ui');
|
|
4
|
+
|
|
5
|
+
function getEnv(name, fallback = '') {
|
|
6
|
+
return String(process.env[name] ?? fallback).trim();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function requireEnv(name) {
|
|
10
|
+
const value = getEnv(name);
|
|
11
|
+
if (!value) {
|
|
12
|
+
throw new Error(`Missing required env var: ${name}`);
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function resolveRequiredValueFromEnvOrPrompt(envName, promptLabel) {
|
|
18
|
+
const value = getEnv(envName);
|
|
19
|
+
if (value) return value;
|
|
20
|
+
|
|
21
|
+
if (!process.stdin.isTTY) {
|
|
22
|
+
throw new Error(`Missing required env var: ${envName}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const answer = (await ask(`${promptLabel}: `)).trim();
|
|
26
|
+
if (!answer) {
|
|
27
|
+
throw new Error(`${promptLabel} is required`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return answer;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseFlag(args, flagName) {
|
|
34
|
+
return args.includes(flagName);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseOption(args, optionName, fallback = '') {
|
|
38
|
+
const index = args.indexOf(optionName);
|
|
39
|
+
if (index === -1) return fallback;
|
|
40
|
+
return String(args[index + 1] || '').trim() || fallback;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
getEnv,
|
|
45
|
+
requireEnv,
|
|
46
|
+
resolveRequiredValueFromEnvOrPrompt,
|
|
47
|
+
parseFlag,
|
|
48
|
+
parseOption
|
|
49
|
+
};
|
package/lib/http.js
CHANGED
|
@@ -1,179 +1,187 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const http = require('http');
|
|
4
|
-
const https = require('https');
|
|
5
|
-
const { warn } = require('./ui');
|
|
6
|
-
|
|
7
|
-
function httpGet(urlString, timeoutMs = 8000) {
|
|
8
|
-
return new Promise((resolve, reject) => {
|
|
9
|
-
const parsed = new URL(urlString);
|
|
10
|
-
const lib = parsed.protocol === 'https:' ? https : http;
|
|
11
|
-
const req = lib.request(
|
|
12
|
-
parsed,
|
|
13
|
-
{
|
|
14
|
-
method: 'GET',
|
|
15
|
-
headers: {
|
|
16
|
-
'User-Agent': 'empirical-
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
(res) => {
|
|
20
|
-
let body = '';
|
|
21
|
-
res.setEncoding('utf8');
|
|
22
|
-
res.on('data', (chunk) => {
|
|
23
|
-
body += chunk;
|
|
24
|
-
});
|
|
25
|
-
res.on('end', () => {
|
|
26
|
-
resolve({
|
|
27
|
-
statusCode: res.statusCode || 0,
|
|
28
|
-
headers: res.headers || {},
|
|
29
|
-
body
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
req.setTimeout(timeoutMs, () => {
|
|
36
|
-
req.destroy(new Error('request timeout'));
|
|
37
|
-
});
|
|
38
|
-
req.on('error', reject);
|
|
39
|
-
req.end();
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function httpErrorFromResponse(method, urlString, statusCode, data) {
|
|
44
|
-
const baseMsg = data.error_description || data.message || data.error || `HTTP ${statusCode}`;
|
|
45
|
-
const upgradeUrl = data.upgrade_url || data.details?.upgradeUrl || null;
|
|
46
|
-
const retryAfterSeconds = data.retry_after_seconds ?? null;
|
|
47
|
-
|
|
48
|
-
const parts = [`${method} ${urlString} failed: ${baseMsg}`];
|
|
49
|
-
if (retryAfterSeconds != null) parts.push(`Retry after ${retryAfterSeconds}s.`);
|
|
50
|
-
if (upgradeUrl) parts.push(`Upgrade: ${upgradeUrl}`);
|
|
51
|
-
|
|
52
|
-
const error = new Error(parts.join(' '));
|
|
53
|
-
error.statusCode = statusCode;
|
|
54
|
-
error.code = data.code || null;
|
|
55
|
-
error.upgradeUrl = upgradeUrl;
|
|
56
|
-
error.retryAfterSeconds = retryAfterSeconds;
|
|
57
|
-
return error;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Prints a structured JSON error line to stderr so scripts/agents parsing CLI
|
|
61
|
-
// output can branch on `code` (e.g. RATE_LIMITED, PLAN_LIMIT_MEMORY_REACHED)
|
|
62
|
-
// and act on `upgradeUrl`/`retryAfterSeconds` without string-parsing `message`.
|
|
63
|
-
function printApiError(error) {
|
|
64
|
-
warn(JSON.stringify({
|
|
65
|
-
error: true,
|
|
66
|
-
code: error.code || 'api_error',
|
|
67
|
-
message: error.message,
|
|
68
|
-
...(error.retryAfterSeconds != null && { retryAfterSeconds: error.retryAfterSeconds }),
|
|
69
|
-
...(error.upgradeUrl && { upgradeUrl: error.upgradeUrl })
|
|
70
|
-
}));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function httpJson(urlString, method, payload, headers = {}, timeoutMs = 0) {
|
|
74
|
-
return new Promise((resolve, reject) => {
|
|
75
|
-
const parsed = new URL(urlString);
|
|
76
|
-
const lib = parsed.protocol === 'https:' ? https : http;
|
|
77
|
-
const body = payload ? JSON.stringify(payload) : null;
|
|
78
|
-
|
|
79
|
-
const req = lib.request(
|
|
80
|
-
parsed,
|
|
81
|
-
{
|
|
82
|
-
method,
|
|
83
|
-
headers: {
|
|
84
|
-
Accept: 'application/json',
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
res.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const http = require('http');
|
|
4
|
+
const https = require('https');
|
|
5
|
+
const { warn } = require('./ui');
|
|
6
|
+
|
|
7
|
+
function httpGet(urlString, timeoutMs = 8000) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const parsed = new URL(urlString);
|
|
10
|
+
const lib = parsed.protocol === 'https:' ? https : http;
|
|
11
|
+
const req = lib.request(
|
|
12
|
+
parsed,
|
|
13
|
+
{
|
|
14
|
+
method: 'GET',
|
|
15
|
+
headers: {
|
|
16
|
+
'User-Agent': 'empirical-cli/1.0'
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
(res) => {
|
|
20
|
+
let body = '';
|
|
21
|
+
res.setEncoding('utf8');
|
|
22
|
+
res.on('data', (chunk) => {
|
|
23
|
+
body += chunk;
|
|
24
|
+
});
|
|
25
|
+
res.on('end', () => {
|
|
26
|
+
resolve({
|
|
27
|
+
statusCode: res.statusCode || 0,
|
|
28
|
+
headers: res.headers || {},
|
|
29
|
+
body
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
req.setTimeout(timeoutMs, () => {
|
|
36
|
+
req.destroy(new Error('request timeout'));
|
|
37
|
+
});
|
|
38
|
+
req.on('error', reject);
|
|
39
|
+
req.end();
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function httpErrorFromResponse(method, urlString, statusCode, data) {
|
|
44
|
+
const baseMsg = data.error_description || data.message || data.error || `HTTP ${statusCode}`;
|
|
45
|
+
const upgradeUrl = data.upgrade_url || data.details?.upgradeUrl || null;
|
|
46
|
+
const retryAfterSeconds = data.retry_after_seconds ?? null;
|
|
47
|
+
|
|
48
|
+
const parts = [`${method} ${urlString} failed: ${baseMsg}`];
|
|
49
|
+
if (retryAfterSeconds != null) parts.push(`Retry after ${retryAfterSeconds}s.`);
|
|
50
|
+
if (upgradeUrl) parts.push(`Upgrade: ${upgradeUrl}`);
|
|
51
|
+
|
|
52
|
+
const error = new Error(parts.join(' '));
|
|
53
|
+
error.statusCode = statusCode;
|
|
54
|
+
error.code = data.code || null;
|
|
55
|
+
error.upgradeUrl = upgradeUrl;
|
|
56
|
+
error.retryAfterSeconds = retryAfterSeconds;
|
|
57
|
+
return error;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Prints a structured JSON error line to stderr so scripts/agents parsing CLI
|
|
61
|
+
// output can branch on `code` (e.g. RATE_LIMITED, PLAN_LIMIT_MEMORY_REACHED)
|
|
62
|
+
// and act on `upgradeUrl`/`retryAfterSeconds` without string-parsing `message`.
|
|
63
|
+
function printApiError(error) {
|
|
64
|
+
warn(JSON.stringify({
|
|
65
|
+
error: true,
|
|
66
|
+
code: error.code || 'api_error',
|
|
67
|
+
message: error.message,
|
|
68
|
+
...(error.retryAfterSeconds != null && { retryAfterSeconds: error.retryAfterSeconds }),
|
|
69
|
+
...(error.upgradeUrl && { upgradeUrl: error.upgradeUrl })
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function httpJson(urlString, method, payload, headers = {}, timeoutMs = 0) {
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const parsed = new URL(urlString);
|
|
76
|
+
const lib = parsed.protocol === 'https:' ? https : http;
|
|
77
|
+
const body = payload ? JSON.stringify(payload) : null;
|
|
78
|
+
|
|
79
|
+
const req = lib.request(
|
|
80
|
+
parsed,
|
|
81
|
+
{
|
|
82
|
+
method,
|
|
83
|
+
headers: {
|
|
84
|
+
Accept: 'application/json',
|
|
85
|
+
// Identifies the client so the server can tell a CLI write from a portal write
|
|
86
|
+
// (services/memoryAuthorship.ts). Writes also send source: 'cli' in the body,
|
|
87
|
+
// which is authoritative; this covers every other endpoint.
|
|
88
|
+
'User-Agent': 'empirical-cli/1.0',
|
|
89
|
+
...(body ? { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } : {}),
|
|
90
|
+
...headers
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
(res) => {
|
|
94
|
+
let raw = '';
|
|
95
|
+
res.setEncoding('utf8');
|
|
96
|
+
res.on('data', (chunk) => {
|
|
97
|
+
raw += chunk;
|
|
98
|
+
});
|
|
99
|
+
res.on('end', () => {
|
|
100
|
+
let data = {};
|
|
101
|
+
try {
|
|
102
|
+
data = raw ? JSON.parse(raw) : {};
|
|
103
|
+
} catch (_error) {
|
|
104
|
+
data = { raw };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const statusCode = res.statusCode || 0;
|
|
108
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
109
|
+
reject(httpErrorFromResponse(method, urlString, statusCode, data));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
resolve(data);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
req.on('error', reject);
|
|
119
|
+
if (Number.isFinite(timeoutMs) && timeoutMs > 0) {
|
|
120
|
+
req.setTimeout(timeoutMs, () => {
|
|
121
|
+
req.destroy(new Error(`request timeout after ${timeoutMs}ms`));
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (body) req.write(body);
|
|
125
|
+
req.end();
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function httpForm(urlString, method, params, headers = {}) {
|
|
130
|
+
return new Promise((resolve, reject) => {
|
|
131
|
+
const parsed = new URL(urlString);
|
|
132
|
+
const lib = parsed.protocol === 'https:' ? https : http;
|
|
133
|
+
const body = new URLSearchParams(params).toString();
|
|
134
|
+
|
|
135
|
+
const req = lib.request(
|
|
136
|
+
parsed,
|
|
137
|
+
{
|
|
138
|
+
method,
|
|
139
|
+
headers: {
|
|
140
|
+
Accept: 'application/json',
|
|
141
|
+
// Identifies the client so the server can tell a CLI write from a portal write
|
|
142
|
+
// (services/memoryAuthorship.ts). Writes also send source: 'cli' in the body,
|
|
143
|
+
// which is authoritative; this covers every other endpoint.
|
|
144
|
+
'User-Agent': 'empirical-cli/1.0',
|
|
145
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
146
|
+
'Content-Length': Buffer.byteLength(body),
|
|
147
|
+
...headers
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
(res) => {
|
|
151
|
+
let raw = '';
|
|
152
|
+
res.setEncoding('utf8');
|
|
153
|
+
res.on('data', (chunk) => {
|
|
154
|
+
raw += chunk;
|
|
155
|
+
});
|
|
156
|
+
res.on('end', () => {
|
|
157
|
+
let data = {};
|
|
158
|
+
try {
|
|
159
|
+
data = raw ? JSON.parse(raw) : {};
|
|
160
|
+
} catch (_error) {
|
|
161
|
+
data = { raw };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const statusCode = res.statusCode || 0;
|
|
165
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
166
|
+
reject(httpErrorFromResponse(method, urlString, statusCode, data));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
resolve(data);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
req.on('error', reject);
|
|
176
|
+
req.write(body);
|
|
177
|
+
req.end();
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = {
|
|
182
|
+
httpGet,
|
|
183
|
+
httpErrorFromResponse,
|
|
184
|
+
printApiError,
|
|
185
|
+
httpJson,
|
|
186
|
+
httpForm
|
|
187
|
+
};
|
|
@@ -51,6 +51,11 @@ picking up previously completed work), query Empirical first - do not assume you
|
|
|
51
51
|
enough context. After completing meaningful work, or discovering a mistake worth not repeating,
|
|
52
52
|
record it in Empirical, not in a local file.
|
|
53
53
|
|
|
54
|
+
When you record, say who authored the fact: \`authorType: "user"\` for something the user said,
|
|
55
|
+
decided or prefers, \`"agent"\` for your own work and findings. Nothing infers this - not the
|
|
56
|
+
transport, not the client - so an omitted value stays unknown forever, and a wrong one puts your
|
|
57
|
+
session notes into answers about the user's own life.
|
|
58
|
+
|
|
54
59
|
Installed Empirical skills in this environment:
|
|
55
60
|
${skillLines}
|
|
56
61
|
|