@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,161 @@
1
+ import {
2
+ CLI_VERSION,
3
+ COMMAND_NAME
4
+ } from '../core/constants.js';
5
+ import { UsageError } from '../core/errors.js';
6
+
7
+ export async function verifyTokenWithMcp(baseUrl, token, timeoutMs, io) {
8
+ const url = endpointUrl(baseUrl, '/mcp');
9
+ const controller = new AbortController();
10
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
11
+ try {
12
+ const response = await io.fetch(url, {
13
+ method: 'POST',
14
+ headers: {
15
+ accept: 'application/json, text/event-stream',
16
+ 'content-type': 'application/json',
17
+ authorization: `Bearer ${token}`,
18
+ 'user-agent': `XMemo-CLI/${CLI_VERSION} (+https://github.com/yonro/memory-os-cli)`
19
+ },
20
+ body: JSON.stringify({
21
+ jsonrpc: '2.0',
22
+ id: 1,
23
+ method: 'initialize',
24
+ params: {
25
+ protocolVersion: '2024-11-05',
26
+ capabilities: {},
27
+ clientInfo: { name: COMMAND_NAME, version: CLI_VERSION }
28
+ }
29
+ }),
30
+ signal: controller.signal
31
+ });
32
+ return {
33
+ ok: response.ok,
34
+ detail: response.ok ? `HTTP ${response.status}` : `HTTP ${response.status}`
35
+ };
36
+ } catch (error) {
37
+ return {
38
+ ok: false,
39
+ detail: error.name === 'AbortError' ? `timeout after ${timeoutMs}ms` : error.message
40
+ };
41
+ } finally {
42
+ clearTimeout(timeout);
43
+ }
44
+ }
45
+
46
+ export async function probe(url, timeoutMs, io) {
47
+ if (typeof io.fetch !== 'function') {
48
+ return { url, ok: false, error: 'fetch unavailable in this Node runtime' };
49
+ }
50
+
51
+ const controller = new AbortController();
52
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
53
+
54
+ try {
55
+ const response = await io.fetch(url, {
56
+ headers: { accept: 'application/json' },
57
+ signal: controller.signal
58
+ });
59
+ return { url, ok: response.ok, status: response.status };
60
+ } catch (error) {
61
+ return {
62
+ url,
63
+ ok: false,
64
+ error: error.name === 'AbortError' ? `timeout after ${timeoutMs}ms` : error.message
65
+ };
66
+ } finally {
67
+ clearTimeout(timeout);
68
+ }
69
+ }
70
+
71
+ export async function fetchJson(url, timeoutMs, io) {
72
+ if (typeof io.fetch !== 'function') {
73
+ throw new UsageError('This Node runtime does not provide fetch; use Node.js 20 or newer.');
74
+ }
75
+
76
+ const controller = new AbortController();
77
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
78
+
79
+ try {
80
+ const response = await io.fetch(url, {
81
+ headers: { accept: 'application/json' },
82
+ signal: controller.signal
83
+ });
84
+ if (!response.ok) {
85
+ throw new UsageError(`Discovery request failed with HTTP ${response.status}: ${url}`);
86
+ }
87
+ return await response.json();
88
+ } catch (error) {
89
+ if (error instanceof UsageError) {
90
+ throw error;
91
+ }
92
+ const reason = error.name === 'AbortError' ? `timeout after ${timeoutMs}ms` : error.message;
93
+ throw new UsageError(`Discovery request failed: ${url} (${reason})`);
94
+ } finally {
95
+ clearTimeout(timeout);
96
+ }
97
+ }
98
+
99
+ export async function postJson(url, payload, timeoutMs, io, options = {}) {
100
+ if (typeof io.fetch !== 'function') {
101
+ throw new UsageError('This Node runtime does not provide fetch; use Node.js 20 or newer.');
102
+ }
103
+
104
+ const controller = new AbortController();
105
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
106
+
107
+ try {
108
+ const response = await io.fetch(url, {
109
+ method: 'POST',
110
+ headers: {
111
+ accept: 'application/json',
112
+ 'content-type': 'application/json'
113
+ },
114
+ body: JSON.stringify(payload),
115
+ signal: controller.signal
116
+ });
117
+ const responsePayload = await response.json();
118
+ if (!response.ok) {
119
+ const error = responsePayload?.error ?? responsePayload?.detail ?? `HTTP ${response.status}`;
120
+ if (options.allowDevicePending && (error === 'authorization_pending' || error === 'slow_down')) {
121
+ return { error };
122
+ }
123
+ throw new UsageError(`Request failed with HTTP ${response.status}: ${url} (${error})`);
124
+ }
125
+ return responsePayload;
126
+ } catch (error) {
127
+ if (error instanceof UsageError) {
128
+ throw error;
129
+ }
130
+ const reason = error.name === 'AbortError' ? `timeout after ${timeoutMs}ms` : error.message;
131
+ throw new UsageError(`Request failed: ${url} (${reason})`);
132
+ } finally {
133
+ clearTimeout(timeout);
134
+ }
135
+ }
136
+
137
+ export function normalizeBaseUrl(input) {
138
+ try {
139
+ const parsed = new URL(input);
140
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
141
+ throw new UsageError('URL must use http or https.');
142
+ }
143
+ parsed.hash = '';
144
+ parsed.search = '';
145
+ return parsed.toString().replace(/\/$/, '');
146
+ } catch (error) {
147
+ if (error instanceof UsageError) {
148
+ throw error;
149
+ }
150
+ throw new UsageError(`Invalid URL: ${input}`);
151
+ }
152
+ }
153
+
154
+ export function endpointUrl(baseUrl, pathname) {
155
+ const url = new URL(baseUrl);
156
+ url.pathname = pathname;
157
+ url.hash = '';
158
+ url.search = '';
159
+ return url.toString();
160
+ }
161
+
package/src/ui/help.js ADDED
@@ -0,0 +1,59 @@
1
+ import {
2
+ CLI_VERSION,
3
+ COMMAND_NAME,
4
+ LEGACY_COMMAND_NAME,
5
+ PACKAGE_NAME,
6
+ PRODUCT_NAME
7
+ } from '../core/constants.js';
8
+ import { writeLine } from '../core/io.js';
9
+
10
+ export function writeHelp(io) {
11
+ writeLine(io.stdout, `======================================================================`);
12
+ writeLine(io.stdout, ` 🧠 ${PRODUCT_NAME} CLI (Version ${CLI_VERSION}) — Cloud Memory Orchestration Utility`);
13
+ writeLine(io.stdout, `======================================================================`);
14
+ writeLine(io.stdout, `Official package: ${PACKAGE_NAME} | Legacy command: ${LEGACY_COMMAND_NAME}`);
15
+ writeLine(io.stdout, '');
16
+ writeLine(io.stdout, '💡 CORE ONBOARDING & SETUP COMMANDS:');
17
+ writeLine(io.stdout, ` ${COMMAND_NAME} setup --all [--write] [--profile]`);
18
+ writeLine(io.stdout, ` Auto-detects all local client installations (Cursor, VS Code, Continue, Trae, etc.).`);
19
+ writeLine(io.stdout, ` Merges XMemo MCP configs. Pass --profile to auto-inject workspace prompt rules.`);
20
+ writeLine(io.stdout, ` *Dry-run by default unless --write (or --yes/-y) is specified for safety.*`);
21
+ writeLine(io.stdout, '');
22
+ writeLine(io.stdout, ` ${COMMAND_NAME} setup <client-id> [--url <url>] [--no-profile] [--json]`);
23
+ writeLine(io.stdout, ` Runs interactive setup wizard for a single client (e.g. cursor, gemini, antigravity).`);
24
+ writeLine(io.stdout, ` Detects active workspace to auto-inject project-scoped instruction rules.`);
25
+ writeLine(io.stdout, '');
26
+ writeLine(io.stdout, ` ${COMMAND_NAME} login [--from-stdin] [--base-url <url>]`);
27
+ writeLine(io.stdout, ` Starts secure OAuth2 browser-based device login flow to register the CLI.`);
28
+ writeLine(io.stdout, '');
29
+ writeLine(io.stdout, '🛡️ DIAGNOSTICS & SYSTEM AUDIT:');
30
+ writeLine(io.stdout, ` ${COMMAND_NAME} doctor [--base-url <url>] [--json]`);
31
+ writeLine(io.stdout, ` Performs structural diagnostics (Node version, Cloud connectivity, API compatibility, security).`);
32
+ writeLine(io.stdout, '');
33
+ writeLine(io.stdout, ` ${COMMAND_NAME} status [--url <url>] [--json]`);
34
+ writeLine(io.stdout, ` Probes and audits XMemo core service endpoints, readiness states, and network health.`);
35
+ writeLine(io.stdout, '');
36
+ writeLine(io.stdout, '📋 MCP & CREDENTIAL MANAGEMENT:');
37
+ writeLine(io.stdout, ` ${COMMAND_NAME} mcp list`);
38
+ writeLine(io.stdout, ` Lists all natively supported client integrations and configurations.`);
39
+ writeLine(io.stdout, '');
40
+ writeLine(io.stdout, ` ${COMMAND_NAME} mcp config --client <client-id> [--base-url <url>] [--json]`);
41
+ writeLine(io.stdout, ` Generates and outputs raw MCP config snippet templates without writing to files.`);
42
+ writeLine(io.stdout, '');
43
+ writeLine(io.stdout, ` ${COMMAND_NAME} mcp add <client-id> [--write] [--config <path>]`);
44
+ writeLine(io.stdout, ` Directly adds XMemo MCP server config snippet to the specified client settings file.`);
45
+ writeLine(io.stdout, '');
46
+ writeLine(io.stdout, ` ${COMMAND_NAME} profile install <client-id> [--target <path>] [--dry-run]`);
47
+ writeLine(io.stdout, ` Injects/updates instruction rules prompt in target workspace rules files (Cursor/Gemini).`);
48
+ writeLine(io.stdout, '');
49
+ writeLine(io.stdout, ` ${COMMAND_NAME} token status [--verify] | ${COMMAND_NAME} token add --from-stdin`);
50
+ writeLine(io.stdout, ` Checks local static credential states or manually saves XMEMO_KEY for key-auth fallbacks.`);
51
+ writeLine(io.stdout, '');
52
+ writeLine(io.stdout, '🔐 SECURITY & PRIVACY BY DESIGN:');
53
+ writeLine(io.stdout, ' - ZERO Telemetry: We never collect private workspace data or usage metrics.');
54
+ writeLine(io.stdout, ' - Git Protection: API tokens are kept securely in system environment variables (XMEMO_KEY)');
55
+ writeLine(io.stdout, ' or in user-scoped credentials.json file. They are never written to project configs.');
56
+ writeLine(io.stdout, ' - AST Merge Safety: Config writes only touch and append the XMemo keys, preserving all other servers.');
57
+ writeLine(io.stdout, '======================================================================');
58
+ }
59
+
@@ -0,0 +1,244 @@
1
+ import { optionValue } from '../core/args.js';
2
+ import {
3
+ COMMAND_NAME,
4
+ DEFAULT_PROXY_HOST,
5
+ MCP_SERVER_NAME,
6
+ PRODUCT_NAME,
7
+ TOKEN_ENV_VAR
8
+ } from '../core/constants.js';
9
+ import { UsageError } from '../core/errors.js';
10
+ import { writeLine } from '../core/io.js';
11
+ import { supportedMcpClientIds, usesClientOAuth } from '../mcp/clients/registry.js';
12
+ import { mcpLocalProxyTemplate } from '../mcp/core/templates.js';
13
+ import { defaultCopilotConfigPath } from '../mcp/identity/paths.js';
14
+ import { profileClientConfig } from '../config/profile.js';
15
+
16
+ const SETUP_CLIENT_ALIASES = new Map([
17
+ ['codex', 'codex'],
18
+ ['cursor', 'cursor'],
19
+ ['copilot', 'copilot-cli'],
20
+ ['copilot-cli', 'copilot-cli'],
21
+ ['gemini', 'gemini-cli'],
22
+ ['gemini-cli', 'gemini-cli'],
23
+ ['antigravity', 'antigravity'],
24
+ ['antigravity-ide', 'antigravity-ide'],
25
+ ['antigravity2', 'antigravity2'],
26
+ ['antigravity-cli', 'antigravity-cli'],
27
+ ['windsurf', 'windsurf'],
28
+ ['cline', 'cline'],
29
+ ['continue', 'continue'],
30
+ ['claude', 'claude-desktop'],
31
+ ['claude-desktop', 'claude-desktop'],
32
+ ['openclaw', 'openclaw'],
33
+ ['kiro', 'kiro'],
34
+ ['zed', 'zed'],
35
+ ['jetbrains', 'jetbrains'],
36
+ ['opencode', 'opencode'],
37
+ ['hermes', 'hermes'],
38
+ ['qwen', 'qwen'],
39
+ ['qwencli', 'qwen'],
40
+ ['qwen-cli', 'qwen'],
41
+ ['trae', 'trae'],
42
+ ['traesolo', 'trae-solo'],
43
+ ['trae-solo', 'trae-solo'],
44
+ ['claude-code', 'claude-code'],
45
+ ['claudecode', 'claude-code'],
46
+ ['claude-cli', 'claude-code'],
47
+ ['claudecode-cli', 'claude-code']
48
+ ]);
49
+
50
+ export function supportedSetupClientIds(mcpClients) {
51
+ return [...supportedMcpClientIds(mcpClients), 'copilot-cli'];
52
+ }
53
+
54
+ export function requiredOption(args, name) {
55
+ const value = optionValue(args, name);
56
+ if (!value) {
57
+ throw new UsageError(`Missing required option ${name}.`);
58
+ }
59
+ return value;
60
+ }
61
+
62
+ export function positionalClientArg(args, mcpClients) {
63
+ const candidate = args[0];
64
+ if (!candidate || candidate.startsWith('--')) {
65
+ return null;
66
+ }
67
+
68
+ return normalizeSetupClientId(candidate, mcpClients);
69
+ }
70
+
71
+ export function normalizeSetupClientId(candidate, mcpClients) {
72
+ if (!candidate) {
73
+ return null;
74
+ }
75
+
76
+ const normalized = SETUP_CLIENT_ALIASES.get(candidate);
77
+ if (!normalized) {
78
+ throw new UsageError(`Unsupported setup client: ${candidate}. Supported clients: ${supportedSetupClientIds(mcpClients).join(', ')}.`);
79
+ }
80
+
81
+ return normalized;
82
+ }
83
+
84
+ export function clientSetupPlan(clientId, client, mcpUrl, env, identity) {
85
+ return {
86
+ id: clientId,
87
+ label: client.label,
88
+ configKind: client.configKind,
89
+ configPath: client.defaultConfigPath(env),
90
+ serverName: MCP_SERVER_NAME,
91
+ mcpUrl,
92
+ tokenEnvVar: TOKEN_ENV_VAR,
93
+ agentId: identity.agentId,
94
+ agentInstanceId: identity.agentInstanceId,
95
+ agentInstanceIdPath: identity.path,
96
+ writesTokenValue: false,
97
+ written: false
98
+ };
99
+ }
100
+
101
+ export function copilotSetupPlan(mcpUrl, proxyPort, env) {
102
+ const proxyUrl = `http://${DEFAULT_PROXY_HOST}:${proxyPort}/mcp`;
103
+ const template = mcpLocalProxyTemplate('copilot-cli', proxyUrl);
104
+ return {
105
+ id: 'copilot-cli',
106
+ label: 'Copilot CLI',
107
+ configKind: 'local-proxy',
108
+ configPath: defaultCopilotConfigPath(env),
109
+ serverName: template.serverName,
110
+ mcpUrl,
111
+ proxyUrl,
112
+ tokenEnvVar: TOKEN_ENV_VAR,
113
+ requiresCredential: template.requiresCredential,
114
+ requiresLocalCommand: template.requiresLocalCommand,
115
+ template: template.snippet,
116
+ agentId: template.agentIdentity.agentId,
117
+ writesTokenValue: false,
118
+ writeSupported: true,
119
+ written: false
120
+ };
121
+ }
122
+
123
+ export function writeSetupSummary(plan, io) {
124
+ writeLine(io.stdout, `${PRODUCT_NAME} setup discovery: ${plan.baseUrl}`);
125
+ writeLine(io.stdout, ` API: ${plan.apiBase}`);
126
+ writeLine(io.stdout, ` MCP: ${plan.mcpUrl}`);
127
+ writeLine(io.stdout, ` Guide: ${plan.guideUrl}`);
128
+ if (plan.docsUrl) {
129
+ writeLine(io.stdout, ` Docs: ${plan.docsUrl}`);
130
+ }
131
+ if (plan.tokenPortalUrl) {
132
+ writeLine(io.stdout, ` Token portal: ${plan.tokenPortalUrl}`);
133
+ }
134
+ writeLine(io.stdout, ` Token env var: ${plan.tokenEnvVar}`);
135
+ writeLine(io.stdout, ` Onboarding ready: ${plan.onboardingReady === null ? 'unknown' : plan.onboardingReady}`);
136
+ writeLine(io.stdout, 'Privacy: telemetry disabled; no token sent; generated config references env vars only.');
137
+
138
+ if (plan.boundaries.adminRequired.length > 0) {
139
+ writeLine(io.stdout, `Admin-only actions: ${plan.boundaries.adminRequired.join(', ')}`);
140
+ }
141
+
142
+ if (plan.detectedClients) {
143
+ writeLine(io.stdout, '');
144
+ if (plan.detectedClients.length === 0) {
145
+ writeLine(io.stdout, 'No local IDE or CLI client configurations were detected.');
146
+ writeLine(io.stdout, `Run \`${COMMAND_NAME} setup <client>\` to configure a client manually.`);
147
+ } else {
148
+ writeLine(io.stdout, `Auto-detected ${plan.detectedClients.length} client(s):`);
149
+ for (const client of plan.detectedClients) {
150
+ writeLine(io.stdout, ` [${client.written ? '✔' : ' '}] ${client.label}`);
151
+ writeLine(io.stdout, ` Config: ${client.configPath}`);
152
+ writeLine(io.stdout, ` Agent ID: ${client.agentId}`);
153
+ }
154
+ if (plan.detectedClients.some(c => c.written)) {
155
+ writeLine(io.stdout, '');
156
+ writeLine(io.stdout, 'Successfully applied XMemo MCP configuration to all detected clients!');
157
+ writeLine(io.stdout, 'Restart your IDEs or reload their MCP configurations to apply the changes.');
158
+ } else {
159
+ writeLine(io.stdout, '');
160
+ writeLine(io.stdout, `Run \`${COMMAND_NAME} setup --all --write\` to write configurations for all detected clients.`);
161
+ }
162
+ }
163
+ return;
164
+ }
165
+
166
+ if (plan.selectedClient) {
167
+ writeLine(io.stdout, '');
168
+ writeLine(io.stdout, `Selected client: ${plan.selectedClient.label}`);
169
+ writeLine(io.stdout, ` Config path: ${plan.selectedClient.configPath}`);
170
+ writeLine(io.stdout, ` Written: ${plan.selectedClient.written}`);
171
+ writeLine(io.stdout, ` Token value embedded: ${plan.selectedClient.writesTokenValue}`);
172
+ writeLine(io.stdout, ` Agent ID: ${plan.selectedClient.agentId}`);
173
+ if (plan.selectedClient.agentInstanceIdPath) {
174
+ writeLine(io.stdout, ` Agent instance ID stored: ${plan.selectedClient.agentInstanceIdPath}`);
175
+ }
176
+ if (plan.selectedClient.configKind === 'local-proxy') {
177
+ writeLine(io.stdout, ` Local proxy: ${plan.selectedClient.requiresLocalCommand}`);
178
+ if (plan.selectedClient.written) {
179
+ writeLine(io.stdout, ` Next: keep \`${plan.selectedClient.requiresLocalCommand}\` running while you use Copilot CLI.`);
180
+ writeLine(io.stdout, ' If Copilot CLI is already open, reload MCP config or restart Copilot CLI.');
181
+ } else {
182
+ writeLine(io.stdout, ' MCP template:');
183
+ writeLine(io.stdout, JSON.stringify(plan.selectedClient.template, null, 2));
184
+ writeLine(io.stdout, ` Next: ${COMMAND_NAME} setup copilot --url ${plan.baseUrl}`);
185
+ }
186
+ return;
187
+ }
188
+ if (plan.selectedClient.behaviorProfile) {
189
+ const profile = plan.selectedClient.behaviorProfile;
190
+ const profileClient = profileClientConfig(profile.client);
191
+ writeLine(io.stdout, ` Behavior profile target: ${profile.targetPath}`);
192
+ writeLine(io.stdout, ` Behavior profile client: ${profileClient?.label ?? profile.client}`);
193
+ writeLine(io.stdout, ` Behavior profile installed: ${profile.written}`);
194
+ writeLine(io.stdout, ` Behavior profile changed: ${profile.changed}`);
195
+ if (!profile.written) {
196
+ writeLine(io.stdout, ` Profile preview: ${COMMAND_NAME} profile install ${profile.client} --target ${profile.targetPath}`);
197
+ }
198
+ }
199
+ if (plan.selectedClient.written) {
200
+ writeLine(io.stdout, '');
201
+ const cid = plan.selectedClient.id;
202
+ if (cid === 'opencode') {
203
+ writeLine(io.stdout, '💡 Next steps for OpenCode:');
204
+ writeLine(io.stdout, ' 1. Open or restart OpenCode.');
205
+ writeLine(io.stdout, ' 2. Trigger any XMemo tool call, or manually run `opencode mcp auth XMemo` in your terminal.');
206
+ writeLine(io.stdout, ' 3. A browser window will automatically pop up requesting XMemo OAuth authorization.');
207
+ writeLine(io.stdout, ' 4. Log in or register on the webpage, then click "Authorize" to link OpenCode.');
208
+ } else if (cid === 'qwen') {
209
+ writeLine(io.stdout, '💡 Next steps for Qwen:');
210
+ writeLine(io.stdout, ' 1. Open or restart Qwen.');
211
+ writeLine(io.stdout, ' 2. When Qwen connects to XMemo MCP, a browser window will automatically pop up requesting OAuth authorization.');
212
+ writeLine(io.stdout, ' 3. Follow the page prompts to sign in and click "Authorize" to link Qwen.');
213
+ } else if (cid === 'trae' || cid === 'trae-solo') {
214
+ writeLine(io.stdout, `💡 Next steps for ${plan.selectedClient.label}:`);
215
+ writeLine(io.stdout, ` 1. Restart ${plan.selectedClient.label} to load the new MCP configuration.`);
216
+ writeLine(io.stdout, ` 2. Make sure the ${TOKEN_ENV_VAR} environment variable is set in your user environment.`);
217
+ if (plan.tokenPortalUrl) {
218
+ writeLine(io.stdout, ` (Token portal: ${plan.tokenPortalUrl})`);
219
+ }
220
+ } else if (usesClientOAuth(cid)) {
221
+ writeLine(io.stdout, `💡 Next steps for ${plan.selectedClient.label}:`);
222
+ 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.');
223
+ writeLine(io.stdout, ' 2. Follow the page prompts to sign in and click "Authorize".');
224
+ } else {
225
+ writeLine(io.stdout, `💡 Next steps for ${plan.selectedClient.label}:`);
226
+ writeLine(io.stdout, ' 1. Restart your editor/client to load the new MCP configuration.');
227
+ writeLine(io.stdout, ` 2. Make sure the ${TOKEN_ENV_VAR} environment variable is set in your user environment.`);
228
+ if (plan.tokenPortalUrl) {
229
+ writeLine(io.stdout, ` (Token portal: ${plan.tokenPortalUrl})`);
230
+ }
231
+ }
232
+ } else {
233
+ writeLine(io.stdout, ` Next: ${COMMAND_NAME} setup ${plan.selectedClient.id} --url ${plan.baseUrl}`);
234
+ }
235
+ return;
236
+ }
237
+
238
+ writeLine(io.stdout, '');
239
+ writeLine(io.stdout, 'Next steps:');
240
+ writeLine(io.stdout, ` 1. Create a scoped token in the token portal and store it in ${plan.tokenEnvVar}.`);
241
+ writeLine(io.stdout, ` 2. Configure a client, for example: ${COMMAND_NAME} setup codex --url ${plan.baseUrl}`);
242
+ writeLine(io.stdout, ` 3. Run ${COMMAND_NAME} status to smoke-test the service without sending the token.`);
243
+ }
244
+