@xmemo/client 0.4.131 → 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 +89 -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.131",
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.131';
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
 
@@ -647,6 +662,11 @@ function writeCredentialStatus(report, io, { mode }) {
647
662
  writeLine(io.stdout, `${PRODUCT_NAME} auth status`);
648
663
  writeLine(io.stdout, `Logged in: ${report.loggedIn ? 'yes' : 'no'}`);
649
664
  writeLine(io.stdout, `Credential source: ${report.tokenSource}`);
665
+ if (report.account) {
666
+ writeLine(io.stdout, `Account: ${formatAccount(report.account)}`);
667
+ }
668
+ writeLine(io.stdout, report.loggedIn ? 'Credential is ready; token value remains hidden.' : `Run \`${COMMAND_NAME} login\` to sign in.`);
669
+ return;
650
670
  }
651
671
  writeLine(io.stdout, `Environment token: ${report.environmentToken.present ? 'present' : 'missing'} (${report.environmentToken.variable})`);
652
672
  writeLine(io.stdout, `User credential file: ${report.userCredentialFile.present ? 'present' : 'missing'} (${report.userCredentialFile.path})`);
@@ -1405,6 +1425,28 @@ function clientSetupPlan(clientId, client, mcpUrl, env, identity) {
1405
1425
  };
1406
1426
  }
1407
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
+
1408
1450
  function writeSetupSummary(plan, io) {
1409
1451
  writeLine(io.stdout, `${PRODUCT_NAME} setup discovery: ${plan.baseUrl}`);
1410
1452
  writeLine(io.stdout, ` API: ${plan.apiBase}`);
@@ -1431,7 +1473,16 @@ function writeSetupSummary(plan, io) {
1431
1473
  writeLine(io.stdout, ` Written: ${plan.selectedClient.written}`);
1432
1474
  writeLine(io.stdout, ` Token value embedded: ${plan.selectedClient.writesTokenValue}`);
1433
1475
  writeLine(io.stdout, ` Agent ID: ${plan.selectedClient.agentId}`);
1434
- 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
+ }
1435
1486
  if (plan.selectedClient.codexProfile) {
1436
1487
  const profile = plan.selectedClient.codexProfile;
1437
1488
  writeLine(io.stdout, ` Codex profile target: ${profile.targetPath}`);
@@ -1442,7 +1493,7 @@ function writeSetupSummary(plan, io) {
1442
1493
  }
1443
1494
  }
1444
1495
  if (!plan.selectedClient.written) {
1445
- 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`);
1446
1497
  }
1447
1498
  return;
1448
1499
  }
@@ -1450,7 +1501,7 @@ function writeSetupSummary(plan, io) {
1450
1501
  writeLine(io.stdout, '');
1451
1502
  writeLine(io.stdout, 'Next steps:');
1452
1503
  writeLine(io.stdout, ` 1. Create a scoped token in the token portal and store it in ${plan.tokenEnvVar}.`);
1453
- 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`);
1454
1505
  writeLine(io.stdout, ` 3. Run ${COMMAND_NAME} status to smoke-test the service without sending the token.`);
1455
1506
  }
1456
1507
 
@@ -1899,6 +1950,10 @@ function supportedMcpClientIds() {
1899
1950
  return Array.from(MCP_CLIENTS.keys());
1900
1951
  }
1901
1952
 
1953
+ function supportedSetupClientIds() {
1954
+ return ['codex', 'cursor', 'copilot'];
1955
+ }
1956
+
1902
1957
  function credentialsPath(env) {
1903
1958
  return path.join(configRoot(env), 'credentials.json');
1904
1959
  }
@@ -1985,11 +2040,20 @@ function positionalClientArg(args) {
1985
2040
  return null;
1986
2041
  }
1987
2042
 
1988
- if (!MCP_CLIENTS.has(candidate)) {
1989
- 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(', ')}.`);
1990
2054
  }
1991
2055
 
1992
- return candidate;
2056
+ return normalized;
1993
2057
  }
1994
2058
 
1995
2059
  function optionValue(args, name) {