@xmemo/client 0.4.132 → 0.4.134

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 +31 -26
  2. package/package.json +1 -1
  3. package/src/cli.js +87 -27
package/README.md CHANGED
@@ -30,8 +30,10 @@ to print the exact command without changing anything.
30
30
  ```bash
31
31
  xmemo update
32
32
  xmemo setup codex
33
- xmemo setup codex --yes
34
- xmemo smoke --client codex
33
+ xmemo setup codex --dry-run
34
+ xmemo setup cursor
35
+ xmemo setup cursor --dry-run
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
 
@@ -140,23 +142,23 @@ accepted as a compatibility alias.
140
142
  Generate and write a client config from discovery:
141
143
 
142
144
  ```bash
143
- xmemo setup codex --yes
144
- 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
145
+ xmemo setup codex
146
+ xmemo setup codex --url "https://your-private-service.example"
147
+ xmemo setup cursor
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
- the token value. Write-capable client configs also include stable non-secret
152
- agent identity headers where the client format supports them.
151
+ `xmemo setup <client>` is the unified setup entry point. For write-capable
152
+ clients, it applies the user-scoped config directly; use `--dry-run` to preview
153
+ without writing. Generated config references `XMEMO_KEY`; it does not embed the
154
+ token value. Write-capable client configs also include stable non-secret agent
155
+ identity headers where the client format supports them. `--yes` remains accepted
156
+ for Codex and Cursor as a compatibility no-op.
153
157
 
154
- `xmemo setup codex` is the recommended Codex path. Without `--yes` it
155
- previews the Codex MCP config and the project-scoped `AGENTS.md` memory profile.
156
- With `--yes`, it writes the Codex MCP config and installs the profile into the
157
- current project's `AGENTS.md` marker block. Use `--profile-target <path>` to
158
- choose a different project instruction file, or `--no-profile` to configure MCP
159
- only.
158
+ `xmemo setup codex` is the recommended Codex path. It writes the Codex MCP
159
+ config and installs the profile into the current project's `AGENTS.md` marker
160
+ block. Use `--dry-run` to preview, `--profile-target <path>` to choose a
161
+ different project instruction file, or `--no-profile` to configure MCP only.
160
162
 
161
163
  ## MCP setup
162
164
 
@@ -192,7 +194,7 @@ directly. The recommended personal-user path is therefore local proxy mode:
192
194
 
193
195
  ```bash
194
196
  xmemo login
195
- xmemo mcp config --client copilot-cli
197
+ xmemo setup copilot
196
198
  xmemo mcp proxy
197
199
  ```
198
200
 
@@ -200,6 +202,9 @@ The generated Copilot CLI template points at `http://127.0.0.1:8765/mcp` and
200
202
  does not include token or identity headers. `xmemo mcp proxy` reads the token
201
203
  saved by `xmemo login` or `xmemo token add --from-stdin`, adds the XMemo bearer
202
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
208
  If you specifically want the older environment-variable template, run:
204
209
 
205
210
  ```bash
@@ -212,14 +217,12 @@ Recommended Codex setup:
212
217
 
213
218
  ```bash
214
219
  xmemo setup codex
215
- xmemo setup codex --yes
216
220
  xmemo smoke --client codex
217
221
  ```
218
222
 
219
- `setup codex` is visible and opt-in: the first command previews the writes, and
220
- `--yes` performs them. The MCP config stays in user-scoped Codex config, while
221
- the XMemo Codex behavior profile is installed into the current project's
222
- `AGENTS.md` between these markers:
223
+ `setup codex` writes the MCP config to user-scoped Codex config and installs the
224
+ XMemo Codex behavior profile into the current project's `AGENTS.md` between
225
+ these markers. Use `xmemo setup codex --dry-run` to preview without writing.
223
226
 
224
227
  ```html
225
228
  <!-- memory-os:codex-profile:start -->
@@ -267,13 +270,15 @@ absence of embedded token values.
267
270
 
268
271
  ### Cursor
269
272
 
270
- Generate a Cursor MCP config snippet:
273
+ Recommended Cursor setup:
271
274
 
272
275
  ```bash
273
- xmemo mcp add cursor --url "$XMEMO_URL"
276
+ xmemo setup cursor
274
277
  ```
275
278
 
276
- Merge it into the default Cursor user config path:
279
+ `setup cursor` merges the Cursor MCP config into the default Cursor user config
280
+ path. Use `xmemo setup cursor --dry-run` to preview without writing. The
281
+ lower-level equivalent remains:
277
282
 
278
283
  ```bash
279
284
  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.134",
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.134';
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>] [--dry-run] [--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]`);
@@ -360,8 +367,9 @@ async function setupCommand(args, io) {
360
367
  const baseUrl = normalizeBaseUrl(baseUrlOption(optionArgs, io.env));
361
368
  const outputJson = hasFlag(optionArgs, '--json');
362
369
  const shortClientSetup = Boolean(positionalClientId);
363
- const writeConfig = hasFlag(optionArgs, '--write') || (shortClientSetup && hasFlag(optionArgs, '--yes'));
364
- const clientId = positionalClientId ?? optionValue(optionArgs, '--client');
370
+ const clientId = normalizeSetupClientId(positionalClientId ?? optionValue(optionArgs, '--client'));
371
+ const dryRun = hasFlag(optionArgs, '--dry-run') || hasFlag(optionArgs, '--preview');
372
+ const writeConfig = !dryRun && (hasFlag(optionArgs, '--write') || (shortClientSetup && clientId !== 'copilot-cli'));
365
373
  const timeoutMs = parsePositiveInteger(optionValue(optionArgs, '--timeout-ms') ?? '5000', '--timeout-ms');
366
374
  const installProfile = shortClientSetup
367
375
  && clientId === 'codex'
@@ -383,24 +391,32 @@ async function setupCommand(args, io) {
383
391
  const setupPlan = buildSetupPlan({ baseUrl, discoveryUrl, statusUrl, discovery, status });
384
392
 
385
393
  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
- }
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
+ const proxyPort = parsePositiveInteger(optionValue(optionArgs, '--port') ?? String(DEFAULT_PROXY_PORT), '--port');
399
+ setupPlan.selectedClient = copilotSetupPlan(setupPlan.mcpUrl, proxyPort);
400
+ } else {
401
+ const client = MCP_CLIENTS.get(clientId);
402
+ if (!client) {
403
+ throw new UsageError(`Unsupported MCP client: ${clientId}. Supported clients: ${supportedSetupClientIds().join(', ')}.`);
404
+ }
390
405
 
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
- }
406
+ const identity = writeConfig ? await agentIdentity(clientId, io.env) : envReferenceIdentity(clientId);
407
+ setupPlan.selectedClient = clientSetupPlan(clientId, client, setupPlan.mcpUrl, io.env, identity);
408
+ if (writeConfig) {
409
+ await client.writeConfig(setupPlan.selectedClient.configPath, setupPlan.mcpUrl, identity);
410
+ setupPlan.selectedClient.written = true;
411
+ }
397
412
 
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;
413
+ if (clientId === 'codex' && shortClientSetup) {
414
+ const profileTarget = optionValue(optionArgs, '--profile-target')
415
+ ?? optionValue(optionArgs, '--target')
416
+ ?? defaultCodexProfileTarget();
417
+ const profileResult = await codexProfileInstallResult(profileTarget, { write: installProfile });
418
+ setupPlan.selectedClient.codexProfile = profileResult;
419
+ }
404
420
  }
405
421
  }
406
422
 
@@ -1410,6 +1426,28 @@ function clientSetupPlan(clientId, client, mcpUrl, env, identity) {
1410
1426
  };
1411
1427
  }
1412
1428
 
1429
+ function copilotSetupPlan(mcpUrl, proxyPort) {
1430
+ const proxyUrl = `http://${DEFAULT_PROXY_HOST}:${proxyPort}/mcp`;
1431
+ const template = mcpLocalProxyTemplate('copilot-cli', proxyUrl);
1432
+ return {
1433
+ id: 'copilot-cli',
1434
+ label: 'Copilot CLI',
1435
+ configKind: 'local-proxy',
1436
+ configPath: 'Copilot CLI MCP config',
1437
+ serverName: template.serverName,
1438
+ mcpUrl,
1439
+ proxyUrl,
1440
+ tokenEnvVar: TOKEN_ENV_VAR,
1441
+ requiresCredential: template.requiresCredential,
1442
+ requiresLocalCommand: template.requiresLocalCommand,
1443
+ template: template.snippet,
1444
+ agentId: template.agentIdentity.agentId,
1445
+ writesTokenValue: false,
1446
+ writeSupported: false,
1447
+ written: false
1448
+ };
1449
+ }
1450
+
1413
1451
  function writeSetupSummary(plan, io) {
1414
1452
  writeLine(io.stdout, `${PRODUCT_NAME} setup discovery: ${plan.baseUrl}`);
1415
1453
  writeLine(io.stdout, ` API: ${plan.apiBase}`);
@@ -1436,7 +1474,16 @@ function writeSetupSummary(plan, io) {
1436
1474
  writeLine(io.stdout, ` Written: ${plan.selectedClient.written}`);
1437
1475
  writeLine(io.stdout, ` Token value embedded: ${plan.selectedClient.writesTokenValue}`);
1438
1476
  writeLine(io.stdout, ` Agent ID: ${plan.selectedClient.agentId}`);
1439
- writeLine(io.stdout, ` Agent instance ID stored: ${plan.selectedClient.agentInstanceIdPath}`);
1477
+ if (plan.selectedClient.agentInstanceIdPath) {
1478
+ writeLine(io.stdout, ` Agent instance ID stored: ${plan.selectedClient.agentInstanceIdPath}`);
1479
+ }
1480
+ if (plan.selectedClient.configKind === 'local-proxy') {
1481
+ 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.`);
1485
+ return;
1486
+ }
1440
1487
  if (plan.selectedClient.codexProfile) {
1441
1488
  const profile = plan.selectedClient.codexProfile;
1442
1489
  writeLine(io.stdout, ` Codex profile target: ${profile.targetPath}`);
@@ -1447,7 +1494,7 @@ function writeSetupSummary(plan, io) {
1447
1494
  }
1448
1495
  }
1449
1496
  if (!plan.selectedClient.written) {
1450
- writeLine(io.stdout, ` Next: ${COMMAND_NAME} mcp add ${plan.selectedClient.id} --url ${plan.apiBase} --write`);
1497
+ writeLine(io.stdout, ` Next: ${COMMAND_NAME} setup ${plan.selectedClient.id} --url ${plan.baseUrl}`);
1451
1498
  }
1452
1499
  return;
1453
1500
  }
@@ -1455,7 +1502,7 @@ function writeSetupSummary(plan, io) {
1455
1502
  writeLine(io.stdout, '');
1456
1503
  writeLine(io.stdout, 'Next steps:');
1457
1504
  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`);
1505
+ writeLine(io.stdout, ` 2. Configure a client, for example: ${COMMAND_NAME} setup codex --url ${plan.baseUrl}`);
1459
1506
  writeLine(io.stdout, ` 3. Run ${COMMAND_NAME} status to smoke-test the service without sending the token.`);
1460
1507
  }
1461
1508
 
@@ -1520,7 +1567,7 @@ function codexMemoryProfile() {
1520
1567
  'For routine or low-signal output, skip durable writes. Prefer summarized procedural or semantic memories over verbose logs.',
1521
1568
  'Keep XMemo authentication through the XMEMO_KEY environment variable; do not paste token values into prompts, config files, or logs.'
1522
1569
  ],
1523
- setupCommand: `${COMMAND_NAME} setup codex --url "$XMEMO_URL" --yes`,
1570
+ setupCommand: `${COMMAND_NAME} setup codex --url "$XMEMO_URL"`,
1524
1571
  smokeCommand: `${COMMAND_NAME} smoke --client codex`
1525
1572
  };
1526
1573
  }
@@ -1904,6 +1951,10 @@ function supportedMcpClientIds() {
1904
1951
  return Array.from(MCP_CLIENTS.keys());
1905
1952
  }
1906
1953
 
1954
+ function supportedSetupClientIds() {
1955
+ return ['codex', 'cursor', 'copilot'];
1956
+ }
1957
+
1907
1958
  function credentialsPath(env) {
1908
1959
  return path.join(configRoot(env), 'credentials.json');
1909
1960
  }
@@ -1990,11 +2041,20 @@ function positionalClientArg(args) {
1990
2041
  return null;
1991
2042
  }
1992
2043
 
1993
- if (!MCP_CLIENTS.has(candidate)) {
1994
- throw new UsageError(`Unsupported setup client: ${candidate}. Supported clients: ${supportedMcpClientIds().join(', ')}.`);
2044
+ return normalizeSetupClientId(candidate);
2045
+ }
2046
+
2047
+ function normalizeSetupClientId(candidate) {
2048
+ if (!candidate) {
2049
+ return null;
2050
+ }
2051
+
2052
+ const normalized = SETUP_CLIENT_ALIASES.get(candidate);
2053
+ if (!normalized) {
2054
+ throw new UsageError(`Unsupported setup client: ${candidate}. Supported clients: ${supportedSetupClientIds().join(', ')}.`);
1995
2055
  }
1996
2056
 
1997
- return candidate;
2057
+ return normalized;
1998
2058
  }
1999
2059
 
2000
2060
  function optionValue(args, name) {