@xmemo/client 0.4.155 → 0.4.157

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 (45) hide show
  1. package/README.md +37 -7
  2. package/package.json +3 -2
  3. package/plugins/kiro/.kiro-plugin/power.json +35 -0
  4. package/plugins/kiro/CHANGELOG.md +9 -0
  5. package/plugins/kiro/LICENSE +7 -0
  6. package/plugins/kiro/POWER.md +147 -0
  7. package/plugins/kiro/README.md +31 -0
  8. package/plugins/kiro/SETUP.md +234 -0
  9. package/plugins/kiro/assets/logo.svg +27 -0
  10. package/plugins/kiro/mcp.json +7 -0
  11. package/plugins/kiro/steering/xmemo-memory.md +32 -0
  12. package/src/cli.js +23 -3996
  13. package/src/commands/auth.js +230 -0
  14. package/src/commands/diagnostics.js +197 -0
  15. package/src/commands/mcp.js +188 -0
  16. package/src/commands/profile.js +57 -0
  17. package/src/commands/setup.js +191 -0
  18. package/src/commands/update.js +58 -0
  19. package/src/config/env.js +82 -0
  20. package/src/config/paths.js +26 -0
  21. package/src/config/profile.js +533 -0
  22. package/src/core/args.js +63 -0
  23. package/src/core/constants.js +32 -0
  24. package/src/core/errors.js +6 -0
  25. package/src/core/io.js +16 -0
  26. package/src/core/runtime.js +144 -0
  27. package/src/core/version.js +1 -0
  28. package/src/mcp/clients/detect.js +51 -0
  29. package/src/mcp/clients/registry.js +68 -0
  30. package/src/mcp/clients.js +81 -0
  31. package/src/mcp/core/names.js +13 -0
  32. package/src/mcp/core/templates.js +156 -0
  33. package/src/mcp/formats/json.js +355 -0
  34. package/src/mcp/formats/toml.js +148 -0
  35. package/src/mcp/formats/yaml.js +72 -0
  36. package/src/mcp/identity/device.js +78 -0
  37. package/src/mcp/identity/paths.js +155 -0
  38. package/src/mcp/proxy/copilot.js +44 -0
  39. package/src/mcp/proxy/server.js +112 -0
  40. package/src/network/auth.js +200 -0
  41. package/src/network/base-url.js +13 -0
  42. package/src/network/discovery.js +103 -0
  43. package/src/network/http.js +161 -0
  44. package/src/ui/help.js +59 -0
  45. package/src/ui/setup.js +244 -0
@@ -0,0 +1,144 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import { spawn } from 'node:child_process';
4
+
5
+ import { UsageError } from './errors.js';
6
+ import { writeLine } from './io.js';
7
+
8
+ export function npmExecutable() {
9
+ return os.platform() === 'win32' ? 'npm.cmd' : 'npm';
10
+ }
11
+
12
+ export async function runProcess(command, args, io, { stream = true } = {}) {
13
+ const spawnFn = io.spawn ?? spawn;
14
+ return await new Promise((resolve, reject) => {
15
+ const child = spawnFn(command, args, {
16
+ stdio: ['ignore', 'pipe', 'pipe'],
17
+ shell: os.platform() === 'win32'
18
+ });
19
+ let stdout = '';
20
+ let stderr = '';
21
+ child.stdout?.on('data', (chunk) => {
22
+ const text = String(chunk);
23
+ stdout += text;
24
+ if (stream) {
25
+ io.stdout.write(text);
26
+ }
27
+ });
28
+ child.stderr?.on('data', (chunk) => {
29
+ const text = String(chunk);
30
+ stderr += text;
31
+ if (stream) {
32
+ io.stderr.write(text);
33
+ }
34
+ });
35
+ child.on('error', reject);
36
+ child.on('close', (code) => {
37
+ resolve({ code: code ?? 0, stdout, stderr });
38
+ });
39
+ });
40
+ }
41
+
42
+ export async function waitForShutdown(server, io) {
43
+ await new Promise((resolve) => {
44
+ let resolved = false;
45
+ const finish = async () => {
46
+ if (resolved) {
47
+ return;
48
+ }
49
+ resolved = true;
50
+ await closeServer(server);
51
+ resolve();
52
+ };
53
+ const onSigint = () => {
54
+ writeLine(io.stdout, 'Shutting down XMemo MCP proxy...');
55
+ void finish();
56
+ };
57
+ process.once('SIGINT', onSigint);
58
+ process.once('SIGTERM', onSigint);
59
+ server.once('close', () => {
60
+ process.off('SIGINT', onSigint);
61
+ process.off('SIGTERM', onSigint);
62
+ if (!resolved) {
63
+ resolved = true;
64
+ resolve();
65
+ }
66
+ });
67
+ });
68
+ }
69
+
70
+ export async function closeServer(server) {
71
+ await new Promise((resolve, reject) => {
72
+ server.close((error) => {
73
+ if (error) {
74
+ reject(error);
75
+ } else {
76
+ resolve();
77
+ }
78
+ });
79
+ });
80
+ }
81
+
82
+ export async function readAll(stream) {
83
+ let content = '';
84
+ for await (const chunk of stream) {
85
+ content += chunk;
86
+ }
87
+ return content;
88
+ }
89
+
90
+ export async function fileExists(filePath) {
91
+ try {
92
+ await fs.access(filePath);
93
+ return true;
94
+ } catch {
95
+ return false;
96
+ }
97
+ }
98
+
99
+ export async function readTextIfExists(filePath) {
100
+ try {
101
+ return await fs.readFile(filePath, 'utf8');
102
+ } catch (error) {
103
+ if (error.code === 'ENOENT') {
104
+ return '';
105
+ }
106
+ throw error;
107
+ }
108
+ }
109
+
110
+ export function parseJsonConfig(content, configPath) {
111
+ try {
112
+ return JSON.parse(content);
113
+ } catch (error) {
114
+ throw new UsageError(`Invalid JSON in ${configPath}: ${error.message}`);
115
+ }
116
+ }
117
+
118
+ export async function bestEffortChmod(filePath, mode) {
119
+ try {
120
+ await fs.chmod(filePath, mode);
121
+ } catch {
122
+ // Windows and managed environments may ignore POSIX chmod.
123
+ }
124
+ }
125
+
126
+ export function isPlainObject(value) {
127
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
128
+ }
129
+
130
+ export function escapeTomlString(value) {
131
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
132
+ }
133
+
134
+ export function unescapeTomlString(value) {
135
+ return value.replace(/\\"/g, '"').replace(/\\\\/g, '\\');
136
+ }
137
+
138
+ export function escapeRegExp(value) {
139
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
140
+ }
141
+
142
+ export async function sleep(ms) {
143
+ await new Promise((resolve) => setTimeout(resolve, ms));
144
+ }
@@ -0,0 +1 @@
1
+ export const CLI_VERSION = '0.4.156';
@@ -0,0 +1,51 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+
4
+ import { fileExists } from '../../core/runtime.js';
5
+ import { defaultCopilotConfigPath } from '../identity/paths.js';
6
+
7
+ export async function detectClient(clientId, env, mcpClients) {
8
+ let filePaths = [];
9
+ if (clientId === 'copilot-cli' || clientId === 'copilot') {
10
+ if (process.platform === 'win32' && env.APPDATA) {
11
+ filePaths.push(path.join(env.APPDATA, 'Code', 'User', 'mcp.json'));
12
+ } else {
13
+ const home = env.HOME || os.homedir();
14
+ if (process.platform === 'darwin') {
15
+ filePaths.push(path.join(home, 'Library', 'Application Support', 'Code', 'User', 'mcp.json'));
16
+ }
17
+ filePaths.push(path.join(home, '.config', 'Code', 'User', 'mcp.json'));
18
+ }
19
+ filePaths.push(defaultCopilotConfigPath(env));
20
+ } else {
21
+ const client = mcpClients.get(clientId);
22
+ if (client) {
23
+ filePaths.push(client.defaultConfigPath(env));
24
+ }
25
+ }
26
+
27
+ if (clientId === 'cline') {
28
+ if (process.platform === 'win32' && env.APPDATA) {
29
+ filePaths.push(path.join(env.APPDATA, 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'));
30
+ } else {
31
+ const home = env.HOME || os.homedir();
32
+ if (process.platform === 'darwin') {
33
+ filePaths.push(path.join(home, 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'));
34
+ }
35
+ filePaths.push(path.join(home, '.config', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'));
36
+ }
37
+ }
38
+
39
+ for (const filePath of filePaths) {
40
+ if (await fileExists(filePath)) {
41
+ return { detected: true, path: filePath };
42
+ }
43
+ const parentDir = path.dirname(filePath);
44
+ if (await fileExists(parentDir)) {
45
+ return { detected: true, path: filePath };
46
+ }
47
+ }
48
+
49
+ return { detected: false };
50
+ }
51
+
@@ -0,0 +1,68 @@
1
+ import { JSON_MCP_CLIENT_DEFINITIONS } from '../formats/json.js';
2
+
3
+ export function createMcpClients(deps) {
4
+ const clients = new Map();
5
+
6
+ clients.set('codex', {
7
+ label: 'Codex',
8
+ defaultConfigPath: deps.defaultCodexConfigPath,
9
+ buildSnippet: deps.codexTomlSnippet,
10
+ writeConfig: deps.appendTomlServerConfig,
11
+ configKind: 'toml'
12
+ });
13
+
14
+ for (const definition of deps.JSON_MCP_CLIENT_DEFINITIONS) {
15
+ if (definition.id === 'qwen') {
16
+ clients.set('hermes', hermesClient(deps));
17
+ }
18
+ clients.set(definition.id, jsonClient(definition, deps));
19
+ }
20
+
21
+ if (!clients.has('hermes')) {
22
+ clients.set('hermes', hermesClient(deps));
23
+ }
24
+
25
+ return clients;
26
+ }
27
+
28
+ function jsonClient(definition, deps) {
29
+ return {
30
+ label: definition.label,
31
+ defaultConfigPath: deps[definition.defaultConfigPath],
32
+ buildSnippet: (mcpUrl, identity) => deps.jsonClientSnippet(definition.id, mcpUrl, identity),
33
+ writeConfig: (configPath, mcpUrl, identity) => deps.mergeJsonClientMcpConfig(definition.id, configPath, mcpUrl, identity),
34
+ configKind: definition.configKind,
35
+ authentication: definition.authentication
36
+ };
37
+ }
38
+
39
+ function hermesClient(deps) {
40
+ return {
41
+ label: 'Hermes',
42
+ defaultConfigPath: deps.defaultHermesConfigPath,
43
+ buildSnippet: deps.hermesYamlSnippet,
44
+ writeConfig: deps.mergeHermesMcpConfig,
45
+ configKind: 'yaml'
46
+ };
47
+ }
48
+
49
+ export function supportedMcpClients(mcpClients) {
50
+ const clients = Array.from(mcpClients.entries()).map(([id, client]) => ({
51
+ id,
52
+ label: client.label,
53
+ configKind: client.configKind
54
+ }));
55
+ clients.push({ id: 'copilot-cli', label: 'Copilot CLI', configKind: 'local-proxy' });
56
+ return clients;
57
+ }
58
+
59
+ export function supportedMcpClientIds(mcpClients) {
60
+ return Array.from(mcpClients.keys());
61
+ }
62
+
63
+ export function usesClientOAuth(clientId) {
64
+ return JSON_MCP_CLIENT_DEFINITIONS.some(
65
+ (definition) => definition.id === clientId && definition.authentication === 'oauth'
66
+ );
67
+ }
68
+
@@ -0,0 +1,81 @@
1
+ import {
2
+ appendTomlServerConfig,
3
+ codexTomlSnippet
4
+ } from './formats/toml.js';
5
+ import {
6
+ hermesYamlSnippet,
7
+ mergeHermesMcpConfig
8
+ } from './formats/yaml.js';
9
+ import {
10
+ JSON_MCP_CLIENT_DEFINITIONS,
11
+ jsonClientSnippet,
12
+ mergeJsonClientMcpConfig
13
+ } from './formats/json.js';
14
+ import {
15
+ defaultAntigravity2ConfigPath,
16
+ defaultAntigravityCliConfigPath,
17
+ defaultAntigravityConfigPath,
18
+ defaultAntigravityIdeConfigPath,
19
+ defaultClaudeConfigPath,
20
+ defaultClaudecodeConfigPath,
21
+ defaultClineConfigPath,
22
+ defaultCodexConfigPath,
23
+ defaultContinueConfigPath,
24
+ defaultCursorConfigPath,
25
+ defaultGeminiConfigPath,
26
+ defaultHermesConfigPath,
27
+ defaultJetbrainsConfigPath,
28
+ defaultKiroConfigPath,
29
+ defaultOpencodeConfigPath,
30
+ defaultOpenclawConfigPath,
31
+ defaultQwenConfigPath,
32
+ defaultTraeConfigPath,
33
+ defaultTraeSoloConfigPath,
34
+ defaultWindsurfConfigPath,
35
+ defaultZedConfigPath
36
+ } from '../config/paths.js';
37
+ import {
38
+ createMcpClients,
39
+ supportedMcpClientIds as registrySupportedMcpClientIds,
40
+ supportedMcpClients as registrySupportedMcpClients
41
+ } from './clients/registry.js';
42
+
43
+ export const MCP_CLIENTS = createMcpClients({
44
+ JSON_MCP_CLIENT_DEFINITIONS,
45
+ defaultCodexConfigPath,
46
+ codexTomlSnippet,
47
+ appendTomlServerConfig,
48
+ defaultHermesConfigPath,
49
+ hermesYamlSnippet,
50
+ mergeHermesMcpConfig,
51
+ jsonClientSnippet,
52
+ mergeJsonClientMcpConfig,
53
+ defaultCursorConfigPath,
54
+ defaultGeminiConfigPath,
55
+ defaultAntigravityConfigPath,
56
+ defaultAntigravityIdeConfigPath,
57
+ defaultAntigravity2ConfigPath,
58
+ defaultAntigravityCliConfigPath,
59
+ defaultWindsurfConfigPath,
60
+ defaultClineConfigPath,
61
+ defaultContinueConfigPath,
62
+ defaultClaudeConfigPath,
63
+ defaultOpenclawConfigPath,
64
+ defaultKiroConfigPath,
65
+ defaultZedConfigPath,
66
+ defaultJetbrainsConfigPath,
67
+ defaultOpencodeConfigPath,
68
+ defaultQwenConfigPath,
69
+ defaultTraeConfigPath,
70
+ defaultTraeSoloConfigPath,
71
+ defaultClaudecodeConfigPath
72
+ });
73
+
74
+ export function supportedMcpClients() {
75
+ return registrySupportedMcpClients(MCP_CLIENTS);
76
+ }
77
+
78
+ export function supportedMcpClientIds() {
79
+ return registrySupportedMcpClientIds(MCP_CLIENTS);
80
+ }
81
+
@@ -0,0 +1,13 @@
1
+ import {
2
+ LEGACY_MCP_SERVER_NAMES,
3
+ MCP_SERVER_NAME
4
+ } from '../../core/constants.js';
5
+
6
+ export function knownMcpServerNames() {
7
+ return [MCP_SERVER_NAME, ...LEGACY_MCP_SERVER_NAMES];
8
+ }
9
+
10
+ export function existingJsonMcpServerName(mcpServers) {
11
+ return knownMcpServerNames().find((name) => mcpServers[name]);
12
+ }
13
+
@@ -0,0 +1,156 @@
1
+ import {
2
+ AGENT_ID_HEADER,
3
+ AGENT_INSTANCE_ENV_VAR,
4
+ AGENT_INSTANCE_HEADER,
5
+ COMMAND_NAME,
6
+ DEFAULT_PROXY_PORT,
7
+ MCP_SERVER_NAME,
8
+ TOKEN_ENV_VAR
9
+ } from '../../core/constants.js';
10
+ import { codexTomlSnippet } from '../formats/toml.js';
11
+ import {
12
+ jsonClientConfig,
13
+ jsonMcpClientDefinition
14
+ } from '../formats/json.js';
15
+
16
+ export function mcpConfigTemplate(clientId, mcpUrl, options = {}) {
17
+ if (clientId === 'codex') {
18
+ return {
19
+ client: clientId,
20
+ serverName: MCP_SERVER_NAME,
21
+ snippetFormat: 'toml',
22
+ snippet: codexTomlSnippet(mcpUrl),
23
+ requiresEnv: [TOKEN_ENV_VAR],
24
+ optionalEnv: [AGENT_INSTANCE_ENV_VAR],
25
+ agentIdentity: {
26
+ agentId: 'codex',
27
+ agentIdHeader: AGENT_ID_HEADER,
28
+ agentInstanceEnvVar: AGENT_INSTANCE_ENV_VAR,
29
+ agentInstanceHeader: AGENT_INSTANCE_HEADER
30
+ },
31
+ agentInstanceGeneration: agentInstanceGenerationPolicy(clientId, options),
32
+ writesTokenValue: false
33
+ };
34
+ }
35
+
36
+ const jsonDefinition = jsonMcpClientDefinition(clientId);
37
+ if (jsonDefinition) {
38
+ return jsonDefinition.authentication === 'oauth'
39
+ ? oauthJsonMcpTemplate(clientId, mcpUrl, jsonClientConfig(clientId, mcpUrl), options)
40
+ : bearerJsonMcpTemplate(clientId, mcpUrl, jsonClientConfig(clientId, mcpUrl), options);
41
+ }
42
+
43
+ return {
44
+ client: clientId,
45
+ serverName: MCP_SERVER_NAME,
46
+ snippetFormat: 'json',
47
+ snippet: {
48
+ mcpServers: {
49
+ [MCP_SERVER_NAME]: {
50
+ type: 'http',
51
+ url: mcpUrl,
52
+ headers: {
53
+ Authorization: `Bearer \${${TOKEN_ENV_VAR}}`,
54
+ [AGENT_ID_HEADER]: clientId,
55
+ [AGENT_INSTANCE_HEADER]: `\${${AGENT_INSTANCE_ENV_VAR}}`
56
+ }
57
+ }
58
+ }
59
+ },
60
+ requiresEnv: [TOKEN_ENV_VAR],
61
+ optionalEnv: [AGENT_INSTANCE_ENV_VAR],
62
+ agentIdentity: {
63
+ agentId: clientId,
64
+ agentIdHeader: AGENT_ID_HEADER,
65
+ agentInstanceEnvVar: AGENT_INSTANCE_ENV_VAR,
66
+ agentInstanceHeader: AGENT_INSTANCE_HEADER
67
+ },
68
+ agentInstanceGeneration: agentInstanceGenerationPolicy(clientId, options),
69
+ writesTokenValue: false
70
+ };
71
+ }
72
+
73
+ export function mcpLocalProxyTemplate(clientId, proxyUrl, options = {}) {
74
+ return {
75
+ client: clientId,
76
+ serverName: MCP_SERVER_NAME,
77
+ snippetFormat: 'json',
78
+ snippet: {
79
+ mcpServers: {
80
+ [MCP_SERVER_NAME]: {
81
+ type: 'http',
82
+ url: proxyUrl
83
+ }
84
+ }
85
+ },
86
+ requiresCredential: [`${COMMAND_NAME} login`, `${COMMAND_NAME} token add --from-stdin`],
87
+ requiresLocalCommand: `${COMMAND_NAME} mcp proxy --port ${new URL(proxyUrl).port || DEFAULT_PROXY_PORT}`,
88
+ agentIdentity: {
89
+ agentId: clientId,
90
+ agentIdHeader: AGENT_ID_HEADER,
91
+ agentInstanceEnvVar: AGENT_INSTANCE_ENV_VAR,
92
+ agentInstanceHeader: AGENT_INSTANCE_HEADER
93
+ },
94
+ agentInstanceGeneration: agentInstanceGenerationPolicy(clientId, options),
95
+ writesTokenValue: false
96
+ };
97
+ }
98
+
99
+ export function agentInstanceGenerationPolicy(clientId, options = {}) {
100
+ const automaticCommand = options.mcpClients?.has(clientId)
101
+ ? `${COMMAND_NAME} mcp add ${clientId} --write`
102
+ : clientId === 'copilot-cli'
103
+ ? `${COMMAND_NAME} setup copilot --write`
104
+ : null;
105
+ return {
106
+ requiredForHeaders: true,
107
+ stablePerInstall: true,
108
+ automaticCommand,
109
+ generatedPattern: `xmemo-${clientId}-<uuid>`,
110
+ storagePath: `~/.config/xmemo/agent-instances/${clientId}.json`,
111
+ manualEnvVar: AGENT_INSTANCE_ENV_VAR
112
+ };
113
+ }
114
+
115
+ function bearerJsonMcpTemplate(clientId, mcpUrl, snippet, options) {
116
+ return {
117
+ client: clientId,
118
+ serverName: MCP_SERVER_NAME,
119
+ snippetFormat: 'json',
120
+ snippet,
121
+ requiresEnv: [TOKEN_ENV_VAR],
122
+ optionalEnv: [AGENT_INSTANCE_ENV_VAR],
123
+ authentication: 'env-bearer',
124
+ agentIdentity: {
125
+ agentId: clientId,
126
+ agentIdHeader: AGENT_ID_HEADER,
127
+ agentInstanceEnvVar: AGENT_INSTANCE_ENV_VAR,
128
+ agentInstanceHeader: AGENT_INSTANCE_HEADER
129
+ },
130
+ agentInstanceGeneration: agentInstanceGenerationPolicy(clientId, options),
131
+ mcpUrl,
132
+ writesTokenValue: false
133
+ };
134
+ }
135
+
136
+ function oauthJsonMcpTemplate(clientId, mcpUrl, snippet, options) {
137
+ return {
138
+ client: clientId,
139
+ serverName: MCP_SERVER_NAME,
140
+ snippetFormat: 'json',
141
+ snippet,
142
+ requiresEnv: [],
143
+ optionalEnv: [AGENT_INSTANCE_ENV_VAR],
144
+ authentication: 'oauth',
145
+ agentIdentity: {
146
+ agentId: clientId,
147
+ agentIdHeader: AGENT_ID_HEADER,
148
+ agentInstanceEnvVar: AGENT_INSTANCE_ENV_VAR,
149
+ agentInstanceHeader: AGENT_INSTANCE_HEADER
150
+ },
151
+ agentInstanceGeneration: agentInstanceGenerationPolicy(clientId, options),
152
+ mcpUrl,
153
+ writesTokenValue: false
154
+ };
155
+ }
156
+