@xmemo/client 0.4.149 → 0.4.153

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +136 -20
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmemo/client",
3
- "version": "0.4.149",
3
+ "version": "0.4.153",
4
4
  "description": "Privacy-first CLI and MCP setup helper for XMemo.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -11,7 +11,7 @@ const PACKAGE_NAME = '@xmemo/client';
11
11
  const FALLBACK_PACKAGE_NAME = '@yonro/xmemo-client';
12
12
  const COMMAND_NAME = 'xmemo';
13
13
  const LEGACY_COMMAND_NAME = 'memory-os';
14
- const CLI_VERSION = '0.4.149';
14
+ const CLI_VERSION = '0.4.153';
15
15
  const DEFAULT_SERVICE_URL = 'https://xmemo.dev';
16
16
  const TOKEN_ENV_VAR = 'XMEMO_KEY';
17
17
  const LEGACY_TOKEN_ENV_VAR = 'MEMORY_OS_MCP_TOKEN';
@@ -171,6 +171,13 @@ const MCP_CLIENTS = new Map([
171
171
  writeConfig: mergeTraeMcpConfig,
172
172
  configKind: 'json'
173
173
  }],
174
+ ['trae-solo', {
175
+ label: 'Trae Solo',
176
+ defaultConfigPath: defaultTraeSoloConfigPath,
177
+ buildSnippet: traeSoloJsonSnippet,
178
+ writeConfig: mergeTraeSoloMcpConfig,
179
+ configKind: 'json'
180
+ }],
174
181
  ['claude-code', {
175
182
  label: 'Claude Code',
176
183
  defaultConfigPath: defaultClaudecodeConfigPath,
@@ -206,6 +213,8 @@ const SETUP_CLIENT_ALIASES = new Map([
206
213
  ['qwencli', 'qwen'],
207
214
  ['qwen-cli', 'qwen'],
208
215
  ['trae', 'trae'],
216
+ ['traesolo', 'trae-solo'],
217
+ ['trae-solo', 'trae-solo'],
209
218
  ['claude-code', 'claude-code'],
210
219
  ['claudecode', 'claude-code'],
211
220
  ['claude-cli', 'claude-code'],
@@ -583,7 +592,7 @@ async function setupCommand(args, io) {
583
592
 
584
593
  if (setupAll) {
585
594
  setupPlan.detectedClients = [];
586
- const scanIds = ['codex', 'cursor', 'copilot-cli', 'gemini-cli', 'antigravity', 'antigravity-ide', 'antigravity2', 'antigravity-cli', 'windsurf', 'cline', 'continue', 'claude-desktop', 'qwen'];
595
+ const scanIds = ['codex', 'cursor', 'copilot-cli', 'gemini-cli', 'antigravity', 'antigravity-ide', 'antigravity2', 'antigravity-cli', 'windsurf', 'cline', 'continue', 'claude-desktop', 'qwen', 'opencode', 'trae', 'trae-solo'];
587
596
  for (const scanId of scanIds) {
588
597
  const detection = await detectClient(scanId, io.env);
589
598
  if (detection.detected) {
@@ -684,9 +693,9 @@ async function profileCommand(args, io) {
684
693
  const subcommand = args[0] ?? 'help';
685
694
  if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
686
695
  writeLine(io.stdout, 'Profile commands:');
687
- writeLine(io.stdout, ` ${COMMAND_NAME} profile install <codex|cursor|gemini|antigravity|qwen> [--target <path>] [--dry-run|--json]`);
688
- writeLine(io.stdout, ` ${COMMAND_NAME} profile status <codex|cursor|gemini|antigravity|qwen> [--target <path>] [--json]`);
689
- writeLine(io.stdout, ` ${COMMAND_NAME} profile uninstall <codex|cursor|gemini|antigravity|qwen> [--target <path>] [--json]`);
696
+ writeLine(io.stdout, ` ${COMMAND_NAME} profile install <codex|cursor|gemini|antigravity|qwen|opencode> [--target <path>] [--dry-run|--json]`);
697
+ writeLine(io.stdout, ` ${COMMAND_NAME} profile status <codex|cursor|gemini|antigravity|qwen|opencode> [--target <path>] [--json]`);
698
+ writeLine(io.stdout, ` ${COMMAND_NAME} profile uninstall <codex|cursor|gemini|antigravity|qwen|opencode> [--target <path>] [--json]`);
690
699
  writeLine(io.stdout, '');
691
700
  writeLine(io.stdout, 'Profile installs are marker-scoped and never write token values.');
692
701
  return 0;
@@ -1658,6 +1667,10 @@ function mcpConfigTemplate(clientId, mcpUrl) {
1658
1667
  return oauthJsonMcpTemplate(clientId, mcpUrl, qwenJsonConfig(mcpUrl));
1659
1668
  }
1660
1669
 
1670
+ if (clientId === 'opencode') {
1671
+ return oauthJsonMcpTemplate(clientId, mcpUrl, opencodeJsonConfig(mcpUrl));
1672
+ }
1673
+
1661
1674
  return {
1662
1675
  client: clientId,
1663
1676
  serverName: MCP_SERVER_NAME,
@@ -1903,7 +1916,40 @@ function writeSetupSummary(plan, io) {
1903
1916
  writeLine(io.stdout, ` Profile preview: ${COMMAND_NAME} profile install ${profile.client} --target ${profile.targetPath}`);
1904
1917
  }
1905
1918
  }
1906
- if (!plan.selectedClient.written) {
1919
+ if (plan.selectedClient.written) {
1920
+ writeLine(io.stdout, '');
1921
+ const cid = plan.selectedClient.id;
1922
+ if (cid === 'opencode') {
1923
+ writeLine(io.stdout, '💡 Next steps for OpenCode:');
1924
+ writeLine(io.stdout, ' 1. Open or restart OpenCode.');
1925
+ writeLine(io.stdout, ' 2. Trigger any XMemo tool call, or manually run `opencode mcp auth XMemo` in your terminal.');
1926
+ writeLine(io.stdout, ' 3. A browser window will automatically pop up requesting XMemo OAuth authorization.');
1927
+ writeLine(io.stdout, ' 4. Log in or register on the webpage, then click "Authorize" to link OpenCode.');
1928
+ } else if (cid === 'qwen') {
1929
+ writeLine(io.stdout, '💡 Next steps for Qwen:');
1930
+ writeLine(io.stdout, ' 1. Open or restart Qwen.');
1931
+ writeLine(io.stdout, ' 2. When Qwen connects to XMemo MCP, a browser window will automatically pop up requesting OAuth authorization.');
1932
+ writeLine(io.stdout, ' 3. Follow the page prompts to sign in and click "Authorize" to link Qwen.');
1933
+ } else if (cid === 'trae' || cid === 'trae-solo') {
1934
+ writeLine(io.stdout, `💡 Next steps for ${plan.selectedClient.label}:`);
1935
+ writeLine(io.stdout, ` 1. Restart ${plan.selectedClient.label} to load the new MCP configuration.`);
1936
+ writeLine(io.stdout, ` 2. Make sure the ${TOKEN_ENV_VAR} environment variable is set in your user environment.`);
1937
+ if (plan.tokenPortalUrl) {
1938
+ writeLine(io.stdout, ` (Token portal: ${plan.tokenPortalUrl})`);
1939
+ }
1940
+ } else if (usesClientOAuth(cid)) {
1941
+ writeLine(io.stdout, `💡 Next steps for ${plan.selectedClient.label}:`);
1942
+ writeLine(io.stdout, ' 1. When the agent starts or first makes an XMemo tool call, a browser window will automatically pop up requesting OAuth authorization.');
1943
+ writeLine(io.stdout, ' 2. Follow the page prompts to sign in and click "Authorize".');
1944
+ } else {
1945
+ writeLine(io.stdout, `💡 Next steps for ${plan.selectedClient.label}:`);
1946
+ writeLine(io.stdout, ' 1. Restart your editor/client to load the new MCP configuration.');
1947
+ writeLine(io.stdout, ` 2. Make sure the ${TOKEN_ENV_VAR} environment variable is set in your user environment.`);
1948
+ if (plan.tokenPortalUrl) {
1949
+ writeLine(io.stdout, ` (Token portal: ${plan.tokenPortalUrl})`);
1950
+ }
1951
+ }
1952
+ } else {
1907
1953
  writeLine(io.stdout, ` Next: ${COMMAND_NAME} setup ${plan.selectedClient.id} --url ${plan.baseUrl}`);
1908
1954
  }
1909
1955
  return;
@@ -2106,13 +2152,28 @@ function profileClientConfig(clientId) {
2106
2152
  return path.join(userHome(env), '.qwen', 'QWEN.md');
2107
2153
  },
2108
2154
  authInstruction: `Keep XMemo authentication through the ${TOKEN_ENV_VAR} environment variable; do not paste token values into prompts, config files, or logs.`
2155
+ },
2156
+ opencode: {
2157
+ label: 'OpenCode',
2158
+ setupAlias: 'opencode',
2159
+ profileVersion: 'opencode-mcp-depth-v1',
2160
+ markerStart: `<!-- ${PROFILE_MARKER_PREFIX}:opencode:start -->`,
2161
+ markerEnd: `<!-- ${PROFILE_MARKER_PREFIX}:opencode:end -->`,
2162
+ defaultTarget: (env) => {
2163
+ const isTest = env.HOME && (env.HOME.includes('memory-os-') || env.HOME.includes('test'));
2164
+ if (!isTest && (existsSync(path.join(process.cwd(), '.git')) || existsSync(path.join(process.cwd(), 'package.json')))) {
2165
+ return path.join(process.cwd(), 'AGENTS.md');
2166
+ }
2167
+ return path.join(userHome(env), '.config', 'opencode', 'AGENTS.md');
2168
+ },
2169
+ authInstruction: 'Use the client-managed MCP OAuth credential; do not paste token values into prompts, config files, or logs.'
2109
2170
  }
2110
2171
  };
2111
2172
  return profileConfigs[clientId] ?? null;
2112
2173
  }
2113
2174
 
2114
2175
  function supportedProfileClientIds() {
2115
- return ['codex', 'cursor', 'gemini', 'antigravity', 'qwen'];
2176
+ return ['codex', 'cursor', 'gemini', 'antigravity', 'qwen', 'opencode'];
2116
2177
  }
2117
2178
 
2118
2179
  function defaultProfileTarget(clientId, env) {
@@ -2921,11 +2982,11 @@ function supportedMcpClientIds() {
2921
2982
  }
2922
2983
 
2923
2984
  function supportedSetupClientIds() {
2924
- return ['codex', 'cursor', 'copilot', 'gemini', 'antigravity', 'antigravity-ide', 'antigravity2', 'antigravity-cli', 'windsurf', 'cline', 'continue', 'claude', 'qwen'];
2985
+ return ['codex', 'cursor', 'copilot', 'gemini', 'antigravity', 'antigravity-ide', 'antigravity2', 'antigravity-cli', 'windsurf', 'cline', 'continue', 'claude', 'qwen', 'opencode', 'trae', 'trae-solo'];
2925
2986
  }
2926
2987
 
2927
2988
  function usesClientOAuth(clientId) {
2928
- return clientId === 'gemini-cli' || clientId === 'antigravity' || clientId === 'antigravity-ide' || clientId === 'antigravity2' || clientId === 'antigravity-cli' || clientId === 'qwen';
2989
+ return clientId === 'gemini-cli' || clientId === 'antigravity' || clientId === 'antigravity-ide' || clientId === 'antigravity2' || clientId === 'antigravity-cli' || clientId === 'qwen' || clientId === 'opencode';
2929
2990
  }
2930
2991
 
2931
2992
  function credentialsPath(env) {
@@ -3640,16 +3701,12 @@ function defaultOpencodeConfigPath(env) {
3640
3701
 
3641
3702
  function opencodeJsonServerConfig(mcpUrl, identity = envReferenceIdentity('opencode')) {
3642
3703
  return {
3643
- type: 'local',
3644
- command: [
3645
- 'npx',
3646
- '-y',
3647
- 'mcp-remote',
3648
- mcpUrl
3649
- ],
3650
- environment: {
3651
- [TOKEN_ENV_VAR]: `\${env:${TOKEN_ENV_VAR}}`,
3652
- [AGENT_INSTANCE_ENV_VAR]: identity.agentInstanceId
3704
+ type: 'remote',
3705
+ url: mcpUrl,
3706
+ enabled: true,
3707
+ headers: {
3708
+ [AGENT_ID_HEADER]: identity.agentId,
3709
+ [AGENT_INSTANCE_HEADER]: identity.agentInstanceId
3653
3710
  }
3654
3711
  };
3655
3712
  }
@@ -3783,8 +3840,14 @@ async function mergeQwenMcpConfig(configPath, mcpUrl, identity) {
3783
3840
  }
3784
3841
 
3785
3842
  function defaultTraeConfigPath(env) {
3843
+ if (process.platform === 'win32' && env.APPDATA) {
3844
+ return path.join(env.APPDATA, 'Trae', 'User', 'mcp.json');
3845
+ }
3786
3846
  const home = env.USERPROFILE || env.HOME || os.homedir();
3787
- return path.join(home, '.trae', 'mcp.json');
3847
+ if (process.platform === 'darwin') {
3848
+ return path.join(home, 'Library', 'Application Support', 'Trae', 'User', 'mcp.json');
3849
+ }
3850
+ return path.join(home, '.config', 'Trae', 'User', 'mcp.json');
3788
3851
  }
3789
3852
 
3790
3853
  function traeJsonServerConfig(mcpUrl, identity = envReferenceIdentity('trae')) {
@@ -3830,6 +3893,59 @@ async function mergeTraeMcpConfig(configPath, mcpUrl, identity) {
3830
3893
  await bestEffortChmod(configPath, 0o600);
3831
3894
  }
3832
3895
 
3896
+ function defaultTraeSoloConfigPath(env) {
3897
+ if (process.platform === 'win32' && env.APPDATA) {
3898
+ return path.join(env.APPDATA, 'TRAE SOLO', 'User', 'mcp.json');
3899
+ }
3900
+ const home = env.USERPROFILE || env.HOME || os.homedir();
3901
+ if (process.platform === 'darwin') {
3902
+ return path.join(home, 'Library', 'Application Support', 'TRAE SOLO', 'User', 'mcp.json');
3903
+ }
3904
+ return path.join(home, '.config', 'TRAE SOLO', 'User', 'mcp.json');
3905
+ }
3906
+
3907
+ function traeSoloJsonServerConfig(mcpUrl, identity = envReferenceIdentity('trae-solo')) {
3908
+ return {
3909
+ url: mcpUrl,
3910
+ headers: {
3911
+ Authorization: `Bearer \${env:${TOKEN_ENV_VAR}}`,
3912
+ [AGENT_ID_HEADER]: identity.agentId,
3913
+ [AGENT_INSTANCE_HEADER]: identity.agentInstanceId
3914
+ }
3915
+ };
3916
+ }
3917
+
3918
+ function traeSoloJsonConfig(mcpUrl, identity = envReferenceIdentity('trae-solo')) {
3919
+ return {
3920
+ mcpServers: {
3921
+ [MCP_SERVER_NAME]: traeSoloJsonServerConfig(mcpUrl, identity)
3922
+ }
3923
+ };
3924
+ }
3925
+
3926
+ function traeSoloJsonSnippet(mcpUrl, identity = envReferenceIdentity('trae-solo')) {
3927
+ return `${JSON.stringify(traeSoloJsonConfig(mcpUrl, identity), null, 2)}\n`;
3928
+ }
3929
+
3930
+ async function mergeTraeSoloMcpConfig(configPath, mcpUrl, identity) {
3931
+ const existing = await readTextIfExists(configPath);
3932
+ const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
3933
+ if (!isPlainObject(parsed)) {
3934
+ throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
3935
+ }
3936
+ if (!isPlainObject(parsed.mcpServers)) {
3937
+ parsed.mcpServers = {};
3938
+ }
3939
+ const existingName = existingJsonMcpServerName(parsed.mcpServers);
3940
+ if (existingName) {
3941
+ throw new UsageError(`MCP config already contains mcpServers.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
3942
+ }
3943
+ parsed.mcpServers[MCP_SERVER_NAME] = traeSoloJsonServerConfig(mcpUrl, identity);
3944
+ await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
3945
+ await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
3946
+ await bestEffortChmod(configPath, 0o600);
3947
+ }
3948
+
3833
3949
  function defaultClaudecodeConfigPath(env) {
3834
3950
  const home = env.USERPROFILE || env.HOME || os.homedir();
3835
3951
  return path.join(home, '.claude.json');