@xmemo/client 0.4.134 → 0.4.135
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 +15 -13
- package/package.json +1 -1
- package/src/cli.js +51 -13
package/README.md
CHANGED
|
@@ -34,6 +34,7 @@ xmemo setup codex --dry-run
|
|
|
34
34
|
xmemo setup cursor
|
|
35
35
|
xmemo setup cursor --dry-run
|
|
36
36
|
xmemo setup copilot
|
|
37
|
+
xmemo setup copilot --dry-run
|
|
37
38
|
xmemo doctor
|
|
38
39
|
xmemo discovery show
|
|
39
40
|
xmemo setup
|
|
@@ -173,24 +174,25 @@ Current write-capable clients:
|
|
|
173
174
|
```text
|
|
174
175
|
codex ~/.codex/config.toml
|
|
175
176
|
cursor ~/.cursor/mcp.json
|
|
177
|
+
copilot ~/.copilot/mcp-config.json
|
|
176
178
|
```
|
|
177
179
|
|
|
178
180
|
For clients without a verified user-scoped write path, generate a read-only
|
|
179
181
|
template and apply it manually after review:
|
|
180
182
|
|
|
181
183
|
```bash
|
|
182
|
-
xmemo mcp config --client copilot-cli
|
|
183
184
|
xmemo mcp config --client generic --base-url "https://your-private-service.example" --json
|
|
184
185
|
```
|
|
185
186
|
|
|
186
|
-
|
|
187
|
-
should only be added after their official user-scoped config format is
|
|
187
|
+
Codex, Cursor, and Copilot CLI have write-capable setup helpers. Other client
|
|
188
|
+
writes should only be added after their official user-scoped config format is
|
|
189
|
+
verified.
|
|
188
190
|
|
|
189
191
|
### Copilot CLI
|
|
190
192
|
|
|
191
|
-
Copilot CLI has `/mcp` management
|
|
192
|
-
|
|
193
|
-
|
|
193
|
+
Copilot CLI has `/mcp` management and reads user MCP configuration from
|
|
194
|
+
`~/.copilot/mcp-config.json` (or `$COPILOT_HOME/mcp-config.json`). XMemo writes
|
|
195
|
+
a local proxy server entry there:
|
|
194
196
|
|
|
195
197
|
```bash
|
|
196
198
|
xmemo login
|
|
@@ -198,13 +200,13 @@ xmemo setup copilot
|
|
|
198
200
|
xmemo mcp proxy
|
|
199
201
|
```
|
|
200
202
|
|
|
201
|
-
|
|
202
|
-
does not include token or identity headers. `xmemo
|
|
203
|
-
|
|
204
|
-
token
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
203
|
+
`xmemo setup copilot` writes `memory-os` to Copilot CLI's user MCP config and
|
|
204
|
+
does not include token or identity headers. Use `xmemo setup copilot --dry-run`
|
|
205
|
+
to preview without writing. `xmemo mcp proxy` reads the token saved by
|
|
206
|
+
`xmemo login` or `xmemo token add --from-stdin`, adds the XMemo bearer token and
|
|
207
|
+
local agent identity, then forwards requests to `https://xmemo.dev/mcp`. If
|
|
208
|
+
Copilot CLI is already open, reload MCP config or restart Copilot CLI after
|
|
209
|
+
setup.
|
|
208
210
|
If you specifically want the older environment-variable template, run:
|
|
209
211
|
|
|
210
212
|
```bash
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ const PACKAGE_NAME = '@xmemo/client';
|
|
|
10
10
|
const FALLBACK_PACKAGE_NAME = '@yonro/xmemo-client';
|
|
11
11
|
const COMMAND_NAME = 'xmemo';
|
|
12
12
|
const LEGACY_COMMAND_NAME = 'memory-os';
|
|
13
|
-
const CLI_VERSION = '0.4.
|
|
13
|
+
const CLI_VERSION = '0.4.135';
|
|
14
14
|
const DEFAULT_SERVICE_URL = 'https://xmemo.dev';
|
|
15
15
|
const TOKEN_ENV_VAR = 'XMEMO_KEY';
|
|
16
16
|
const LEGACY_TOKEN_ENV_VAR = 'MEMORY_OS_MCP_TOKEN';
|
|
@@ -369,7 +369,7 @@ async function setupCommand(args, io) {
|
|
|
369
369
|
const shortClientSetup = Boolean(positionalClientId);
|
|
370
370
|
const clientId = normalizeSetupClientId(positionalClientId ?? optionValue(optionArgs, '--client'));
|
|
371
371
|
const dryRun = hasFlag(optionArgs, '--dry-run') || hasFlag(optionArgs, '--preview');
|
|
372
|
-
const writeConfig = !dryRun && (hasFlag(optionArgs, '--write') || (
|
|
372
|
+
const writeConfig = !dryRun && (hasFlag(optionArgs, '--write') || hasFlag(optionArgs, '--yes') || shortClientSetup);
|
|
373
373
|
const timeoutMs = parsePositiveInteger(optionValue(optionArgs, '--timeout-ms') ?? '5000', '--timeout-ms');
|
|
374
374
|
const installProfile = shortClientSetup
|
|
375
375
|
&& clientId === 'codex'
|
|
@@ -392,11 +392,12 @@ async function setupCommand(args, io) {
|
|
|
392
392
|
|
|
393
393
|
if (clientId) {
|
|
394
394
|
if (clientId === 'copilot-cli') {
|
|
395
|
-
if (hasFlag(optionArgs, '--write') || hasFlag(optionArgs, '--yes')) {
|
|
396
|
-
throw new UsageError(`Copilot CLI setup cannot be written automatically yet. Run \`${COMMAND_NAME} setup copilot\` to print the local proxy template, then add it with Copilot CLI MCP management.`);
|
|
397
|
-
}
|
|
398
395
|
const proxyPort = parsePositiveInteger(optionValue(optionArgs, '--port') ?? String(DEFAULT_PROXY_PORT), '--port');
|
|
399
|
-
setupPlan.selectedClient = copilotSetupPlan(setupPlan.mcpUrl, proxyPort);
|
|
396
|
+
setupPlan.selectedClient = copilotSetupPlan(setupPlan.mcpUrl, proxyPort, io.env);
|
|
397
|
+
if (writeConfig) {
|
|
398
|
+
await mergeCopilotMcpConfig(setupPlan.selectedClient.configPath, setupPlan.selectedClient.proxyUrl);
|
|
399
|
+
setupPlan.selectedClient.written = true;
|
|
400
|
+
}
|
|
400
401
|
} else {
|
|
401
402
|
const client = MCP_CLIENTS.get(clientId);
|
|
402
403
|
if (!client) {
|
|
@@ -1426,14 +1427,14 @@ function clientSetupPlan(clientId, client, mcpUrl, env, identity) {
|
|
|
1426
1427
|
};
|
|
1427
1428
|
}
|
|
1428
1429
|
|
|
1429
|
-
function copilotSetupPlan(mcpUrl, proxyPort) {
|
|
1430
|
+
function copilotSetupPlan(mcpUrl, proxyPort, env) {
|
|
1430
1431
|
const proxyUrl = `http://${DEFAULT_PROXY_HOST}:${proxyPort}/mcp`;
|
|
1431
1432
|
const template = mcpLocalProxyTemplate('copilot-cli', proxyUrl);
|
|
1432
1433
|
return {
|
|
1433
1434
|
id: 'copilot-cli',
|
|
1434
1435
|
label: 'Copilot CLI',
|
|
1435
1436
|
configKind: 'local-proxy',
|
|
1436
|
-
configPath:
|
|
1437
|
+
configPath: defaultCopilotConfigPath(env),
|
|
1437
1438
|
serverName: template.serverName,
|
|
1438
1439
|
mcpUrl,
|
|
1439
1440
|
proxyUrl,
|
|
@@ -1443,7 +1444,7 @@ function copilotSetupPlan(mcpUrl, proxyPort) {
|
|
|
1443
1444
|
template: template.snippet,
|
|
1444
1445
|
agentId: template.agentIdentity.agentId,
|
|
1445
1446
|
writesTokenValue: false,
|
|
1446
|
-
writeSupported:
|
|
1447
|
+
writeSupported: true,
|
|
1447
1448
|
written: false
|
|
1448
1449
|
};
|
|
1449
1450
|
}
|
|
@@ -1479,9 +1480,14 @@ function writeSetupSummary(plan, io) {
|
|
|
1479
1480
|
}
|
|
1480
1481
|
if (plan.selectedClient.configKind === 'local-proxy') {
|
|
1481
1482
|
writeLine(io.stdout, ` Local proxy: ${plan.selectedClient.requiresLocalCommand}`);
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1483
|
+
if (plan.selectedClient.written) {
|
|
1484
|
+
writeLine(io.stdout, ` Next: keep \`${plan.selectedClient.requiresLocalCommand}\` running while you use Copilot CLI.`);
|
|
1485
|
+
writeLine(io.stdout, ' If Copilot CLI is already open, reload MCP config or restart Copilot CLI.');
|
|
1486
|
+
} else {
|
|
1487
|
+
writeLine(io.stdout, ' MCP template:');
|
|
1488
|
+
writeLine(io.stdout, JSON.stringify(plan.selectedClient.template, null, 2));
|
|
1489
|
+
writeLine(io.stdout, ` Next: ${COMMAND_NAME} setup copilot --url ${plan.baseUrl}`);
|
|
1490
|
+
}
|
|
1485
1491
|
return;
|
|
1486
1492
|
}
|
|
1487
1493
|
if (plan.selectedClient.codexProfile) {
|
|
@@ -1874,6 +1880,31 @@ async function mergeJsonMcpConfig(configPath, mcpUrl, identity) {
|
|
|
1874
1880
|
await bestEffortChmod(configPath, 0o600);
|
|
1875
1881
|
}
|
|
1876
1882
|
|
|
1883
|
+
async function mergeCopilotMcpConfig(configPath, proxyUrl) {
|
|
1884
|
+
const existing = await readTextIfExists(configPath);
|
|
1885
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
1886
|
+
|
|
1887
|
+
if (!isPlainObject(parsed)) {
|
|
1888
|
+
throw new UsageError(`Copilot MCP JSON config must be an object: ${configPath}`);
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
if (!isPlainObject(parsed.mcpServers)) {
|
|
1892
|
+
parsed.mcpServers = {};
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
parsed.mcpServers['memory-os'] = copilotLocalProxyServerConfig(proxyUrl);
|
|
1896
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
1897
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
1898
|
+
await bestEffortChmod(configPath, 0o600);
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
function copilotLocalProxyServerConfig(proxyUrl) {
|
|
1902
|
+
return {
|
|
1903
|
+
type: 'http',
|
|
1904
|
+
url: proxyUrl
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1877
1908
|
function cursorJsonConfig(mcpUrl, identity = envReferenceIdentity('cursor')) {
|
|
1878
1909
|
return {
|
|
1879
1910
|
mcpServers: {
|
|
@@ -1940,11 +1971,13 @@ function envReferenceIdentity(clientId) {
|
|
|
1940
1971
|
}
|
|
1941
1972
|
|
|
1942
1973
|
function supportedMcpClients() {
|
|
1943
|
-
|
|
1974
|
+
const clients = Array.from(MCP_CLIENTS.entries()).map(([id, client]) => ({
|
|
1944
1975
|
id,
|
|
1945
1976
|
label: client.label,
|
|
1946
1977
|
configKind: client.configKind
|
|
1947
1978
|
}));
|
|
1979
|
+
clients.push({ id: 'copilot-cli', label: 'Copilot CLI', configKind: 'local-proxy' });
|
|
1980
|
+
return clients;
|
|
1948
1981
|
}
|
|
1949
1982
|
|
|
1950
1983
|
function supportedMcpClientIds() {
|
|
@@ -1990,6 +2023,11 @@ function defaultCursorConfigPath(env) {
|
|
|
1990
2023
|
return path.join(home, '.cursor', 'mcp.json');
|
|
1991
2024
|
}
|
|
1992
2025
|
|
|
2026
|
+
function defaultCopilotConfigPath(env) {
|
|
2027
|
+
const home = env.USERPROFILE || env.HOME || os.homedir();
|
|
2028
|
+
return path.join(env.COPILOT_HOME ?? path.join(home, '.copilot'), 'mcp-config.json');
|
|
2029
|
+
}
|
|
2030
|
+
|
|
1993
2031
|
async function writePlaintextCredential(credentialPath, token, metadata = {}) {
|
|
1994
2032
|
await fs.mkdir(path.dirname(credentialPath), { recursive: true, mode: 0o700 });
|
|
1995
2033
|
await bestEffortChmod(path.dirname(credentialPath), 0o700);
|