@xmemo/client 0.4.132 → 0.4.133

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 +18 -10
  2. package/package.json +1 -1
  3. package/src/cli.js +84 -25
package/README.md CHANGED
@@ -31,7 +31,9 @@ to print the exact command without changing anything.
31
31
  xmemo update
32
32
  xmemo setup codex
33
33
  xmemo setup codex --yes
34
- xmemo smoke --client codex
34
+ xmemo setup cursor
35
+ xmemo setup cursor --yes
36
+ xmemo setup copilot
35
37
  xmemo doctor
36
38
  xmemo discovery show
37
39
  xmemo setup
@@ -44,7 +46,7 @@ xmemo env example --shell bash
44
46
  xmemo mcp list
45
47
  xmemo mcp config --client generic
46
48
  xmemo profile status codex
47
- xmemo mcp add cursor --url "https://your-private-service.example"
49
+ xmemo smoke --client codex
48
50
  xmemo privacy
49
51
  ```
50
52
 
@@ -142,12 +144,13 @@ Generate and write a client config from discovery:
142
144
  ```bash
143
145
  xmemo setup codex --yes
144
146
  xmemo setup codex --url "https://your-private-service.example" --yes
145
- xmemo setup --url "https://your-private-service.example" --client codex --write
146
- xmemo setup --url "https://your-private-service.example" --client cursor --write
147
+ xmemo setup cursor --yes
148
+ xmemo setup copilot
147
149
  ```
148
150
 
149
- `--write` requires an explicit `--client` so the CLI never performs broad config
150
- writes implicitly. Generated config references `XMEMO_KEY`; it does not embed
151
+ `xmemo setup <client>` is the unified setup entry point. Without `--yes` it
152
+ previews what will happen. With `--yes`, write-capable clients apply the
153
+ user-scoped config. Generated config references `XMEMO_KEY`; it does not embed
151
154
  the token value. Write-capable client configs also include stable non-secret
152
155
  agent identity headers where the client format supports them.
153
156
 
@@ -192,7 +195,7 @@ directly. The recommended personal-user path is therefore local proxy mode:
192
195
 
193
196
  ```bash
194
197
  xmemo login
195
- xmemo mcp config --client copilot-cli
198
+ xmemo setup copilot
196
199
  xmemo mcp proxy
197
200
  ```
198
201
 
@@ -200,6 +203,9 @@ The generated Copilot CLI template points at `http://127.0.0.1:8765/mcp` and
200
203
  does not include token or identity headers. `xmemo mcp proxy` reads the token
201
204
  saved by `xmemo login` or `xmemo token add --from-stdin`, adds the XMemo bearer
202
205
  token and local agent identity, then forwards requests to `https://xmemo.dev/mcp`.
206
+ Copilot CLI does not currently document a stable cross-platform config file for
207
+ third-party tools to edit, so `xmemo setup copilot` prints the template and next
208
+ command instead of writing directly.
203
209
  If you specifically want the older environment-variable template, run:
204
210
 
205
211
  ```bash
@@ -267,13 +273,15 @@ absence of embedded token values.
267
273
 
268
274
  ### Cursor
269
275
 
270
- Generate a Cursor MCP config snippet:
276
+ Recommended Cursor setup:
271
277
 
272
278
  ```bash
273
- xmemo mcp add cursor --url "$XMEMO_URL"
279
+ xmemo setup cursor
280
+ xmemo setup cursor --yes
274
281
  ```
275
282
 
276
- Merge it into the default Cursor user config path:
283
+ `setup cursor` previews the Cursor MCP config. `setup cursor --yes` merges it
284
+ into the default Cursor user config path. The lower-level equivalent remains:
277
285
 
278
286
  ```bash
279
287
  xmemo mcp add cursor --url "$XMEMO_URL" --write
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmemo/client",
3
- "version": "0.4.132",
3
+ "version": "0.4.133",
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.132';
13
+ const CLI_VERSION = '0.4.133';
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';
@@ -44,6 +44,13 @@ const MCP_CLIENTS = new Map([
44
44
  }]
45
45
  ]);
46
46
 
47
+ const SETUP_CLIENT_ALIASES = new Map([
48
+ ['codex', 'codex'],
49
+ ['cursor', 'cursor'],
50
+ ['copilot', 'copilot-cli'],
51
+ ['copilot-cli', 'copilot-cli']
52
+ ]);
53
+
47
54
  class UsageError extends Error {
48
55
  constructor(message) {
49
56
  super(message);
@@ -150,7 +157,7 @@ function writeHelp(io) {
150
157
  writeLine(io.stdout, ` ${COMMAND_NAME} update [--dry-run] [--json]`);
151
158
  writeLine(io.stdout, ` ${COMMAND_NAME} doctor [--base-url <https://api.example.com>] [--json]`);
152
159
  writeLine(io.stdout, ` ${COMMAND_NAME} discovery show [--base-url <https://api.example.com>] [--json]`);
153
- writeLine(io.stdout, ` ${COMMAND_NAME} setup [codex|cursor] [--url <https://api.example.com>] [--write|--yes] [--json]`);
160
+ writeLine(io.stdout, ` ${COMMAND_NAME} setup <codex|cursor|copilot> [--url <https://api.example.com>] [--write|--yes] [--json]`);
154
161
  writeLine(io.stdout, ` ${COMMAND_NAME} login [--from-stdin] [--base-url <url>] [--timeout-ms <ms>] [--http-timeout-ms <ms>] [--json]`);
155
162
  writeLine(io.stdout, ` ${COMMAND_NAME} auth status [--verify] [--base-url <url>] [--json]`);
156
163
  writeLine(io.stdout, ` ${COMMAND_NAME} status [--url <https://api.example.com>] [--json]`);
@@ -361,7 +368,7 @@ async function setupCommand(args, io) {
361
368
  const outputJson = hasFlag(optionArgs, '--json');
362
369
  const shortClientSetup = Boolean(positionalClientId);
363
370
  const writeConfig = hasFlag(optionArgs, '--write') || (shortClientSetup && hasFlag(optionArgs, '--yes'));
364
- const clientId = positionalClientId ?? optionValue(optionArgs, '--client');
371
+ const clientId = normalizeSetupClientId(positionalClientId ?? optionValue(optionArgs, '--client'));
365
372
  const timeoutMs = parsePositiveInteger(optionValue(optionArgs, '--timeout-ms') ?? '5000', '--timeout-ms');
366
373
  const installProfile = shortClientSetup
367
374
  && clientId === 'codex'
@@ -383,24 +390,32 @@ async function setupCommand(args, io) {
383
390
  const setupPlan = buildSetupPlan({ baseUrl, discoveryUrl, statusUrl, discovery, status });
384
391
 
385
392
  if (clientId) {
386
- const client = MCP_CLIENTS.get(clientId);
387
- if (!client) {
388
- throw new UsageError(`Unsupported MCP client: ${clientId}. Supported clients: ${supportedMcpClientIds().join(', ')}.`);
389
- }
393
+ if (clientId === 'copilot-cli') {
394
+ if (writeConfig) {
395
+ 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.`);
396
+ }
397
+ const proxyPort = parsePositiveInteger(optionValue(optionArgs, '--port') ?? String(DEFAULT_PROXY_PORT), '--port');
398
+ setupPlan.selectedClient = copilotSetupPlan(setupPlan.mcpUrl, proxyPort);
399
+ } else {
400
+ const client = MCP_CLIENTS.get(clientId);
401
+ if (!client) {
402
+ throw new UsageError(`Unsupported MCP client: ${clientId}. Supported clients: ${supportedSetupClientIds().join(', ')}.`);
403
+ }
390
404
 
391
- const identity = writeConfig ? await agentIdentity(clientId, io.env) : envReferenceIdentity(clientId);
392
- setupPlan.selectedClient = clientSetupPlan(clientId, client, setupPlan.mcpUrl, io.env, identity);
393
- if (writeConfig) {
394
- await client.writeConfig(setupPlan.selectedClient.configPath, setupPlan.mcpUrl, identity);
395
- setupPlan.selectedClient.written = true;
396
- }
405
+ const identity = writeConfig ? await agentIdentity(clientId, io.env) : envReferenceIdentity(clientId);
406
+ setupPlan.selectedClient = clientSetupPlan(clientId, client, setupPlan.mcpUrl, io.env, identity);
407
+ if (writeConfig) {
408
+ await client.writeConfig(setupPlan.selectedClient.configPath, setupPlan.mcpUrl, identity);
409
+ setupPlan.selectedClient.written = true;
410
+ }
397
411
 
398
- if (clientId === 'codex' && shortClientSetup) {
399
- const profileTarget = optionValue(optionArgs, '--profile-target')
400
- ?? optionValue(optionArgs, '--target')
401
- ?? defaultCodexProfileTarget();
402
- const profileResult = await codexProfileInstallResult(profileTarget, { write: installProfile });
403
- setupPlan.selectedClient.codexProfile = profileResult;
412
+ if (clientId === 'codex' && shortClientSetup) {
413
+ const profileTarget = optionValue(optionArgs, '--profile-target')
414
+ ?? optionValue(optionArgs, '--target')
415
+ ?? defaultCodexProfileTarget();
416
+ const profileResult = await codexProfileInstallResult(profileTarget, { write: installProfile });
417
+ setupPlan.selectedClient.codexProfile = profileResult;
418
+ }
404
419
  }
405
420
  }
406
421
 
@@ -1410,6 +1425,28 @@ function clientSetupPlan(clientId, client, mcpUrl, env, identity) {
1410
1425
  };
1411
1426
  }
1412
1427
 
1428
+ function copilotSetupPlan(mcpUrl, proxyPort) {
1429
+ const proxyUrl = `http://${DEFAULT_PROXY_HOST}:${proxyPort}/mcp`;
1430
+ const template = mcpLocalProxyTemplate('copilot-cli', proxyUrl);
1431
+ return {
1432
+ id: 'copilot-cli',
1433
+ label: 'Copilot CLI',
1434
+ configKind: 'local-proxy',
1435
+ configPath: 'Copilot CLI MCP config',
1436
+ serverName: template.serverName,
1437
+ mcpUrl,
1438
+ proxyUrl,
1439
+ tokenEnvVar: TOKEN_ENV_VAR,
1440
+ requiresCredential: template.requiresCredential,
1441
+ requiresLocalCommand: template.requiresLocalCommand,
1442
+ template: template.snippet,
1443
+ agentId: template.agentIdentity.agentId,
1444
+ writesTokenValue: false,
1445
+ writeSupported: false,
1446
+ written: false
1447
+ };
1448
+ }
1449
+
1413
1450
  function writeSetupSummary(plan, io) {
1414
1451
  writeLine(io.stdout, `${PRODUCT_NAME} setup discovery: ${plan.baseUrl}`);
1415
1452
  writeLine(io.stdout, ` API: ${plan.apiBase}`);
@@ -1436,7 +1473,16 @@ function writeSetupSummary(plan, io) {
1436
1473
  writeLine(io.stdout, ` Written: ${plan.selectedClient.written}`);
1437
1474
  writeLine(io.stdout, ` Token value embedded: ${plan.selectedClient.writesTokenValue}`);
1438
1475
  writeLine(io.stdout, ` Agent ID: ${plan.selectedClient.agentId}`);
1439
- writeLine(io.stdout, ` Agent instance ID stored: ${plan.selectedClient.agentInstanceIdPath}`);
1476
+ if (plan.selectedClient.agentInstanceIdPath) {
1477
+ writeLine(io.stdout, ` Agent instance ID stored: ${plan.selectedClient.agentInstanceIdPath}`);
1478
+ }
1479
+ if (plan.selectedClient.configKind === 'local-proxy') {
1480
+ writeLine(io.stdout, ` Local proxy: ${plan.selectedClient.requiresLocalCommand}`);
1481
+ writeLine(io.stdout, ' MCP template:');
1482
+ writeLine(io.stdout, JSON.stringify(plan.selectedClient.template, null, 2));
1483
+ writeLine(io.stdout, ` Next: add the template with Copilot CLI MCP management, then keep \`${plan.selectedClient.requiresLocalCommand}\` running while you use Copilot CLI.`);
1484
+ return;
1485
+ }
1440
1486
  if (plan.selectedClient.codexProfile) {
1441
1487
  const profile = plan.selectedClient.codexProfile;
1442
1488
  writeLine(io.stdout, ` Codex profile target: ${profile.targetPath}`);
@@ -1447,7 +1493,7 @@ function writeSetupSummary(plan, io) {
1447
1493
  }
1448
1494
  }
1449
1495
  if (!plan.selectedClient.written) {
1450
- writeLine(io.stdout, ` Next: ${COMMAND_NAME} mcp add ${plan.selectedClient.id} --url ${plan.apiBase} --write`);
1496
+ writeLine(io.stdout, ` Next: ${COMMAND_NAME} setup ${plan.selectedClient.id} --url ${plan.baseUrl} --yes`);
1451
1497
  }
1452
1498
  return;
1453
1499
  }
@@ -1455,7 +1501,7 @@ function writeSetupSummary(plan, io) {
1455
1501
  writeLine(io.stdout, '');
1456
1502
  writeLine(io.stdout, 'Next steps:');
1457
1503
  writeLine(io.stdout, ` 1. Create a scoped token in the token portal and store it in ${plan.tokenEnvVar}.`);
1458
- writeLine(io.stdout, ` 2. Configure a client, for example: ${COMMAND_NAME} setup --url ${plan.baseUrl} --client codex --write`);
1504
+ writeLine(io.stdout, ` 2. Configure a client, for example: ${COMMAND_NAME} setup codex --url ${plan.baseUrl} --yes`);
1459
1505
  writeLine(io.stdout, ` 3. Run ${COMMAND_NAME} status to smoke-test the service without sending the token.`);
1460
1506
  }
1461
1507
 
@@ -1904,6 +1950,10 @@ function supportedMcpClientIds() {
1904
1950
  return Array.from(MCP_CLIENTS.keys());
1905
1951
  }
1906
1952
 
1953
+ function supportedSetupClientIds() {
1954
+ return ['codex', 'cursor', 'copilot'];
1955
+ }
1956
+
1907
1957
  function credentialsPath(env) {
1908
1958
  return path.join(configRoot(env), 'credentials.json');
1909
1959
  }
@@ -1990,11 +2040,20 @@ function positionalClientArg(args) {
1990
2040
  return null;
1991
2041
  }
1992
2042
 
1993
- if (!MCP_CLIENTS.has(candidate)) {
1994
- throw new UsageError(`Unsupported setup client: ${candidate}. Supported clients: ${supportedMcpClientIds().join(', ')}.`);
2043
+ return normalizeSetupClientId(candidate);
2044
+ }
2045
+
2046
+ function normalizeSetupClientId(candidate) {
2047
+ if (!candidate) {
2048
+ return null;
2049
+ }
2050
+
2051
+ const normalized = SETUP_CLIENT_ALIASES.get(candidate);
2052
+ if (!normalized) {
2053
+ throw new UsageError(`Unsupported setup client: ${candidate}. Supported clients: ${supportedSetupClientIds().join(', ')}.`);
1995
2054
  }
1996
2055
 
1997
- return candidate;
2056
+ return normalized;
1998
2057
  }
1999
2058
 
2000
2059
  function optionValue(args, name) {