@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.
Files changed (3) hide show
  1. package/README.md +15 -13
  2. package/package.json +1 -1
  3. 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
- Only Codex and Cursor currently have write-capable helpers. Other client writes
187
- should only be added after their official user-scoped config format is verified.
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, but it does not currently document a stable
192
- cross-platform user config file path/format for third-party tools to edit
193
- directly. The recommended personal-user path is therefore local proxy mode:
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
- The generated Copilot CLI template points at `http://127.0.0.1:8765/mcp` and
202
- does not include token or identity headers. `xmemo mcp proxy` reads the token
203
- saved by `xmemo login` or `xmemo token add --from-stdin`, adds the XMemo bearer
204
- token and local agent identity, then forwards requests to `https://xmemo.dev/mcp`.
205
- Copilot CLI does not currently document a stable cross-platform config file for
206
- third-party tools to edit, so `xmemo setup copilot` prints the template and next
207
- command instead of writing directly.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmemo/client",
3
- "version": "0.4.134",
3
+ "version": "0.4.135",
4
4
  "description": "Privacy-first CLI and MCP setup helper for XMemo.",
5
5
  "type": "module",
6
6
  "bin": {
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.134';
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') || (shortClientSetup && clientId !== 'copilot-cli'));
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: 'Copilot CLI MCP config',
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: false,
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
- writeLine(io.stdout, ' MCP template:');
1483
- writeLine(io.stdout, JSON.stringify(plan.selectedClient.template, null, 2));
1484
- writeLine(io.stdout, ` Next: add the template with Copilot CLI MCP management, then keep \`${plan.selectedClient.requiresLocalCommand}\` running while you use Copilot CLI.`);
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
- return Array.from(MCP_CLIENTS.entries()).map(([id, client]) => ({
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);