@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.
- package/README.md +37 -7
- package/package.json +3 -2
- package/plugins/kiro/.kiro-plugin/power.json +35 -0
- package/plugins/kiro/CHANGELOG.md +9 -0
- package/plugins/kiro/LICENSE +7 -0
- package/plugins/kiro/POWER.md +147 -0
- package/plugins/kiro/README.md +31 -0
- package/plugins/kiro/SETUP.md +234 -0
- package/plugins/kiro/assets/logo.svg +27 -0
- package/plugins/kiro/mcp.json +7 -0
- package/plugins/kiro/steering/xmemo-memory.md +32 -0
- package/src/cli.js +23 -3996
- package/src/commands/auth.js +230 -0
- package/src/commands/diagnostics.js +197 -0
- package/src/commands/mcp.js +188 -0
- package/src/commands/profile.js +57 -0
- package/src/commands/setup.js +191 -0
- package/src/commands/update.js +58 -0
- package/src/config/env.js +82 -0
- package/src/config/paths.js +26 -0
- package/src/config/profile.js +533 -0
- package/src/core/args.js +63 -0
- package/src/core/constants.js +32 -0
- package/src/core/errors.js +6 -0
- package/src/core/io.js +16 -0
- package/src/core/runtime.js +144 -0
- package/src/core/version.js +1 -0
- package/src/mcp/clients/detect.js +51 -0
- package/src/mcp/clients/registry.js +68 -0
- package/src/mcp/clients.js +81 -0
- package/src/mcp/core/names.js +13 -0
- package/src/mcp/core/templates.js +156 -0
- package/src/mcp/formats/json.js +355 -0
- package/src/mcp/formats/toml.js +148 -0
- package/src/mcp/formats/yaml.js +72 -0
- package/src/mcp/identity/device.js +78 -0
- package/src/mcp/identity/paths.js +155 -0
- package/src/mcp/proxy/copilot.js +44 -0
- package/src/mcp/proxy/server.js +112 -0
- package/src/network/auth.js +200 -0
- package/src/network/base-url.js +13 -0
- package/src/network/discovery.js +103 -0
- package/src/network/http.js +161 -0
- package/src/ui/help.js +59 -0
- package/src/ui/setup.js +244 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
AGENT_INSTANCE_ENV_VAR,
|
|
6
|
+
AGENT_ID_HEADER,
|
|
7
|
+
AGENT_INSTANCE_HEADER,
|
|
8
|
+
MCP_SERVER_NAME,
|
|
9
|
+
TOKEN_ENV_VAR
|
|
10
|
+
} from '../../core/constants.js';
|
|
11
|
+
import { UsageError } from '../../core/errors.js';
|
|
12
|
+
import {
|
|
13
|
+
bestEffortChmod,
|
|
14
|
+
isPlainObject,
|
|
15
|
+
parseJsonConfig,
|
|
16
|
+
readTextIfExists
|
|
17
|
+
} from '../../core/runtime.js';
|
|
18
|
+
import { envReferenceIdentity } from '../identity/device.js';
|
|
19
|
+
import { existingJsonMcpServerName } from '../core/names.js';
|
|
20
|
+
|
|
21
|
+
export const JSON_MCP_CLIENT_DEFINITIONS = Object.freeze([
|
|
22
|
+
httpClientDefinition('cursor', 'Cursor', 'defaultCursorConfigPath', { urlKey: 'url', authentication: 'env-bearer' }),
|
|
23
|
+
httpClientDefinition('gemini-cli', 'Gemini CLI', 'defaultGeminiConfigPath', { urlKey: 'httpUrl', authentication: 'oauth' }),
|
|
24
|
+
httpClientDefinition('antigravity', 'Antigravity', 'defaultAntigravityConfigPath', { urlKey: 'serverUrl', authentication: 'oauth' }),
|
|
25
|
+
httpClientDefinition('antigravity-ide', 'Antigravity IDE', 'defaultAntigravityIdeConfigPath', { urlKey: 'url', authentication: 'oauth', defaultIdentityId: 'antigravity', extra: { type: 'http' } }),
|
|
26
|
+
httpClientDefinition('antigravity2', 'Antigravity 2.0', 'defaultAntigravity2ConfigPath', { urlKey: 'url', authentication: 'oauth', defaultIdentityId: 'antigravity', extra: { type: 'http' } }),
|
|
27
|
+
httpClientDefinition('antigravity-cli', 'Antigravity CLI', 'defaultAntigravityCliConfigPath', { urlKey: 'httpUrl', authentication: 'oauth', defaultIdentityId: 'antigravity' }),
|
|
28
|
+
httpClientDefinition('windsurf', 'Windsurf', 'defaultWindsurfConfigPath', { urlKey: 'serverUrl', authentication: 'env-bearer' }),
|
|
29
|
+
httpClientDefinition('cline', 'Cline', 'defaultClineConfigPath', { urlKey: 'httpUrl', authentication: 'env-bearer' }),
|
|
30
|
+
nestedTransportClientDefinition('continue', 'Continue', 'defaultContinueConfigPath'),
|
|
31
|
+
commandClientDefinition('claude-desktop', 'Claude Desktop', 'defaultClaudeConfigPath'),
|
|
32
|
+
httpClientDefinition('openclaw', 'OpenClaw', 'defaultOpenclawConfigPath', { urlKey: 'url', authentication: 'env-bearer' }),
|
|
33
|
+
httpClientDefinition('kiro', 'Kiro', 'defaultKiroConfigPath', { urlKey: 'url', authentication: 'env-bearer' }),
|
|
34
|
+
commandClientDefinition('zed', 'Zed', 'defaultZedConfigPath', { section: 'context_servers' }),
|
|
35
|
+
nestedTransportClientDefinition('jetbrains', 'JetBrains', 'defaultJetbrainsConfigPath'),
|
|
36
|
+
remoteClientDefinition('opencode', 'OpenCode', 'defaultOpencodeConfigPath'),
|
|
37
|
+
httpClientDefinition('qwen', 'Qwen', 'defaultQwenConfigPath', { urlKey: 'httpUrl', authentication: 'oauth' }),
|
|
38
|
+
commandClientDefinition('trae', 'Trae', 'defaultTraeConfigPath'),
|
|
39
|
+
commandClientDefinition('trae-solo', 'Trae Solo', 'defaultTraeSoloConfigPath'),
|
|
40
|
+
commandClientDefinition('claude-code', 'Claude Code', 'defaultClaudecodeConfigPath')
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
const JSON_MCP_CLIENTS_BY_ID = new Map(JSON_MCP_CLIENT_DEFINITIONS.map((definition) => [definition.id, definition]));
|
|
44
|
+
|
|
45
|
+
function httpClientDefinition(id, label, defaultConfigPath, options) {
|
|
46
|
+
return {
|
|
47
|
+
id,
|
|
48
|
+
label,
|
|
49
|
+
defaultConfigPath,
|
|
50
|
+
configKind: 'json',
|
|
51
|
+
section: 'mcpServers',
|
|
52
|
+
serverKind: 'http',
|
|
53
|
+
bearerSyntax: 'env-colon',
|
|
54
|
+
...options
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function nestedTransportClientDefinition(id, label, defaultConfigPath) {
|
|
59
|
+
return {
|
|
60
|
+
id,
|
|
61
|
+
label,
|
|
62
|
+
defaultConfigPath,
|
|
63
|
+
configKind: 'json',
|
|
64
|
+
section: 'mcpServers',
|
|
65
|
+
serverKind: 'nested-transport',
|
|
66
|
+
authentication: 'env-bearer',
|
|
67
|
+
bearerSyntax: 'plain',
|
|
68
|
+
mergeExperimentalModelContextProtocolServers: true
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function commandClientDefinition(id, label, defaultConfigPath, options = {}) {
|
|
73
|
+
return {
|
|
74
|
+
id,
|
|
75
|
+
label,
|
|
76
|
+
defaultConfigPath,
|
|
77
|
+
configKind: 'json',
|
|
78
|
+
section: 'mcpServers',
|
|
79
|
+
serverKind: 'mcp-remote-command',
|
|
80
|
+
authentication: 'env-bearer',
|
|
81
|
+
...options
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function remoteClientDefinition(id, label, defaultConfigPath) {
|
|
86
|
+
return {
|
|
87
|
+
id,
|
|
88
|
+
label,
|
|
89
|
+
defaultConfigPath,
|
|
90
|
+
configKind: 'json',
|
|
91
|
+
section: 'mcp',
|
|
92
|
+
serverKind: 'remote',
|
|
93
|
+
authentication: 'oauth'
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function jsonMcpClientDefinition(clientId) {
|
|
98
|
+
return JSON_MCP_CLIENTS_BY_ID.get(clientId) ?? null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function jsonMcpClientIds() {
|
|
102
|
+
return JSON_MCP_CLIENT_DEFINITIONS.map((definition) => definition.id);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function jsonClientConfig(clientId, mcpUrl, identity) {
|
|
106
|
+
const definition = requireJsonMcpClientDefinition(clientId);
|
|
107
|
+
return sectionConfig(definition.section, jsonClientServerConfig(clientId, mcpUrl, identity));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function jsonClientSnippet(clientId, mcpUrl, identity) {
|
|
111
|
+
return `${JSON.stringify(jsonClientConfig(clientId, mcpUrl, identity), null, 2)}\n`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function jsonClientServerConfig(clientId, mcpUrl, identity) {
|
|
115
|
+
const definition = requireJsonMcpClientDefinition(clientId);
|
|
116
|
+
const resolvedIdentity = identity ?? envReferenceIdentity(definition.defaultIdentityId ?? definition.id);
|
|
117
|
+
return serverConfigFromDefinition(definition, mcpUrl, resolvedIdentity);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function mergeJsonClientMcpConfig(clientId, configPath, mcpUrl, identity) {
|
|
121
|
+
const definition = requireJsonMcpClientDefinition(clientId);
|
|
122
|
+
const serverConfig = serverConfigFromDefinition(definition, mcpUrl, identity);
|
|
123
|
+
await mergeJsonSectionConfig(configPath, definition.section, serverConfig, definition.section, (parsed) => {
|
|
124
|
+
if (definition.mergeExperimentalModelContextProtocolServers && isPlainObject(parsed.experimental)) {
|
|
125
|
+
mergeExperimentalModelContextProtocolServers(parsed, serverConfig, mcpUrl);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function requireJsonMcpClientDefinition(clientId) {
|
|
131
|
+
const definition = jsonMcpClientDefinition(clientId);
|
|
132
|
+
if (!definition) {
|
|
133
|
+
throw new UsageError(`Unsupported JSON MCP client: ${clientId}`);
|
|
134
|
+
}
|
|
135
|
+
return definition;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function sectionConfig(sectionName, serverConfig) {
|
|
139
|
+
return {
|
|
140
|
+
[sectionName]: {
|
|
141
|
+
[MCP_SERVER_NAME]: serverConfig
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function serverConfigFromDefinition(definition, mcpUrl, identity) {
|
|
147
|
+
if (definition.serverKind === 'mcp-remote-command') {
|
|
148
|
+
return mcpRemoteCommandJsonServerConfig(mcpUrl, identity);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (definition.serverKind === 'nested-transport') {
|
|
152
|
+
return {
|
|
153
|
+
transport: {
|
|
154
|
+
type: 'streamable-http',
|
|
155
|
+
url: mcpUrl,
|
|
156
|
+
headers: headersForDefinition(definition, identity)
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (definition.serverKind === 'remote') {
|
|
162
|
+
return {
|
|
163
|
+
type: 'remote',
|
|
164
|
+
url: mcpUrl,
|
|
165
|
+
enabled: true,
|
|
166
|
+
headers: headersForDefinition(definition, identity)
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
...(definition.extra ?? {}),
|
|
172
|
+
[definition.urlKey]: mcpUrl,
|
|
173
|
+
headers: headersForDefinition(definition, identity)
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function headersForDefinition(definition, identity) {
|
|
178
|
+
const headers = {
|
|
179
|
+
[AGENT_ID_HEADER]: identity.agentId,
|
|
180
|
+
[AGENT_INSTANCE_HEADER]: identity.agentInstanceId
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
if (definition.authentication === 'env-bearer') {
|
|
184
|
+
headers.Authorization = authorizationHeader(definition.bearerSyntax);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return orderedHeaders(headers);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function orderedHeaders(headers) {
|
|
191
|
+
if (!headers.Authorization) {
|
|
192
|
+
return headers;
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
Authorization: headers.Authorization,
|
|
196
|
+
[AGENT_ID_HEADER]: headers[AGENT_ID_HEADER],
|
|
197
|
+
[AGENT_INSTANCE_HEADER]: headers[AGENT_INSTANCE_HEADER]
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function authorizationHeader(syntax) {
|
|
202
|
+
if (syntax === 'plain') {
|
|
203
|
+
return `Bearer \${${TOKEN_ENV_VAR}}`;
|
|
204
|
+
}
|
|
205
|
+
return `Bearer \${env:${TOKEN_ENV_VAR}}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function mcpRemoteCommandJsonServerConfig(mcpUrl, identity) {
|
|
209
|
+
return {
|
|
210
|
+
command: 'npx',
|
|
211
|
+
args: [
|
|
212
|
+
'-y',
|
|
213
|
+
'mcp-remote',
|
|
214
|
+
mcpUrl,
|
|
215
|
+
'--header',
|
|
216
|
+
`Authorization:Bearer \${${TOKEN_ENV_VAR}}`,
|
|
217
|
+
'--header',
|
|
218
|
+
`${AGENT_ID_HEADER}:${identity.agentId}`,
|
|
219
|
+
'--header',
|
|
220
|
+
`${AGENT_INSTANCE_HEADER}:\${${AGENT_INSTANCE_ENV_VAR}}`
|
|
221
|
+
],
|
|
222
|
+
env: {
|
|
223
|
+
[TOKEN_ENV_VAR]: `\${env:${TOKEN_ENV_VAR}}`,
|
|
224
|
+
[AGENT_INSTANCE_ENV_VAR]: identity.agentInstanceId
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function mergeJsonSectionConfig(configPath, sectionName, serverConfig, duplicatePath = sectionName, afterMerge) {
|
|
230
|
+
const existing = await readTextIfExists(configPath);
|
|
231
|
+
const parsed = existing.trim().length === 0 ? {} : parseJsonConfig(existing, configPath);
|
|
232
|
+
if (!isPlainObject(parsed)) {
|
|
233
|
+
throw new UsageError(`MCP JSON config must be an object: ${configPath}`);
|
|
234
|
+
}
|
|
235
|
+
if (!isPlainObject(parsed[sectionName])) {
|
|
236
|
+
parsed[sectionName] = {};
|
|
237
|
+
}
|
|
238
|
+
const existingName = existingJsonMcpServerName(parsed[sectionName]);
|
|
239
|
+
if (existingName) {
|
|
240
|
+
throw new UsageError(`MCP config already contains ${duplicatePath}.${existingName}. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
241
|
+
}
|
|
242
|
+
parsed[sectionName][MCP_SERVER_NAME] = serverConfig;
|
|
243
|
+
afterMerge?.(parsed);
|
|
244
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
245
|
+
await fs.writeFile(configPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 });
|
|
246
|
+
await bestEffortChmod(configPath, 0o600);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function mergeExperimentalModelContextProtocolServers(parsed, serverConfig, mcpUrl) {
|
|
250
|
+
if (!Array.isArray(parsed.experimental.modelContextProtocolServers)) {
|
|
251
|
+
parsed.experimental.modelContextProtocolServers = [];
|
|
252
|
+
}
|
|
253
|
+
const hasXMemo = parsed.experimental.modelContextProtocolServers.some(
|
|
254
|
+
(srv) => srv.transport && srv.transport.url === mcpUrl
|
|
255
|
+
);
|
|
256
|
+
if (!hasXMemo) {
|
|
257
|
+
parsed.experimental.modelContextProtocolServers.push(serverConfig);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export const cursorJsonConfig = (mcpUrl, identity = envReferenceIdentity('cursor')) => jsonClientConfig('cursor', mcpUrl, identity);
|
|
262
|
+
export const cursorJsonSnippet = (mcpUrl, identity = envReferenceIdentity('cursor')) => jsonClientSnippet('cursor', mcpUrl, identity);
|
|
263
|
+
export const cursorJsonServerConfig = (mcpUrl, identity = envReferenceIdentity('cursor')) => jsonClientServerConfig('cursor', mcpUrl, identity);
|
|
264
|
+
export const mergeJsonMcpConfig = (configPath, mcpUrl, identity) => mergeJsonClientMcpConfig('cursor', configPath, mcpUrl, identity);
|
|
265
|
+
|
|
266
|
+
export const geminiJsonConfig = (mcpUrl, identity = envReferenceIdentity('gemini-cli')) => jsonClientConfig('gemini-cli', mcpUrl, identity);
|
|
267
|
+
export const geminiJsonSnippet = (mcpUrl, identity = envReferenceIdentity('gemini-cli')) => jsonClientSnippet('gemini-cli', mcpUrl, identity);
|
|
268
|
+
export const geminiJsonServerConfig = (mcpUrl, identity = envReferenceIdentity('gemini-cli')) => jsonClientServerConfig('gemini-cli', mcpUrl, identity);
|
|
269
|
+
export const mergeGeminiMcpConfig = (configPath, mcpUrl, identity) => mergeJsonClientMcpConfig('gemini-cli', configPath, mcpUrl, identity);
|
|
270
|
+
|
|
271
|
+
export const antigravityJsonConfig = (mcpUrl, identity = envReferenceIdentity('antigravity')) => jsonClientConfig('antigravity', mcpUrl, identity);
|
|
272
|
+
export const antigravityJsonSnippet = (mcpUrl, identity = envReferenceIdentity('antigravity')) => jsonClientSnippet('antigravity', mcpUrl, identity);
|
|
273
|
+
export const antigravityJsonServerConfig = (mcpUrl, identity = envReferenceIdentity('antigravity')) => jsonClientServerConfig('antigravity', mcpUrl, identity);
|
|
274
|
+
export const mergeAntigravityMcpConfig = (configPath, mcpUrl, identity) => mergeJsonClientMcpConfig('antigravity', configPath, mcpUrl, identity);
|
|
275
|
+
|
|
276
|
+
export const antigravityIdeJsonConfig = (mcpUrl, identity = envReferenceIdentity('antigravity')) => jsonClientConfig('antigravity-ide', mcpUrl, identity);
|
|
277
|
+
export const antigravityIdeJsonSnippet = (mcpUrl, identity = envReferenceIdentity('antigravity')) => jsonClientSnippet('antigravity-ide', mcpUrl, identity);
|
|
278
|
+
export const antigravityIdeJsonServerConfig = (mcpUrl, identity = envReferenceIdentity('antigravity')) => jsonClientServerConfig('antigravity-ide', mcpUrl, identity);
|
|
279
|
+
export const mergeAntigravityIdeMcpConfig = (configPath, mcpUrl, identity) => mergeJsonClientMcpConfig('antigravity-ide', configPath, mcpUrl, identity);
|
|
280
|
+
|
|
281
|
+
export const antigravity2JsonConfig = (mcpUrl, identity = envReferenceIdentity('antigravity')) => jsonClientConfig('antigravity2', mcpUrl, identity);
|
|
282
|
+
export const antigravity2JsonSnippet = (mcpUrl, identity = envReferenceIdentity('antigravity')) => jsonClientSnippet('antigravity2', mcpUrl, identity);
|
|
283
|
+
export const antigravity2JsonServerConfig = (mcpUrl, identity = envReferenceIdentity('antigravity')) => jsonClientServerConfig('antigravity2', mcpUrl, identity);
|
|
284
|
+
export const mergeAntigravity2McpConfig = (configPath, mcpUrl, identity) => mergeJsonClientMcpConfig('antigravity2', configPath, mcpUrl, identity);
|
|
285
|
+
|
|
286
|
+
export const antigravityCliJsonConfig = (mcpUrl, identity = envReferenceIdentity('antigravity')) => jsonClientConfig('antigravity-cli', mcpUrl, identity);
|
|
287
|
+
export const antigravityCliJsonSnippet = (mcpUrl, identity = envReferenceIdentity('antigravity')) => jsonClientSnippet('antigravity-cli', mcpUrl, identity);
|
|
288
|
+
export const antigravityCliJsonServerConfig = (mcpUrl, identity = envReferenceIdentity('antigravity')) => jsonClientServerConfig('antigravity-cli', mcpUrl, identity);
|
|
289
|
+
export const mergeAntigravityCliMcpConfig = (configPath, mcpUrl, identity) => mergeJsonClientMcpConfig('antigravity-cli', configPath, mcpUrl, identity);
|
|
290
|
+
|
|
291
|
+
export const windsurfJsonConfig = (mcpUrl, identity = envReferenceIdentity('windsurf')) => jsonClientConfig('windsurf', mcpUrl, identity);
|
|
292
|
+
export const windsurfJsonSnippet = (mcpUrl, identity = envReferenceIdentity('windsurf')) => jsonClientSnippet('windsurf', mcpUrl, identity);
|
|
293
|
+
export const windsurfJsonServerConfig = (mcpUrl, identity = envReferenceIdentity('windsurf')) => jsonClientServerConfig('windsurf', mcpUrl, identity);
|
|
294
|
+
export const mergeWindsurfMcpConfig = (configPath, mcpUrl, identity) => mergeJsonClientMcpConfig('windsurf', configPath, mcpUrl, identity);
|
|
295
|
+
|
|
296
|
+
export const clineJsonConfig = (mcpUrl, identity = envReferenceIdentity('cline')) => jsonClientConfig('cline', mcpUrl, identity);
|
|
297
|
+
export const clineJsonSnippet = (mcpUrl, identity = envReferenceIdentity('cline')) => jsonClientSnippet('cline', mcpUrl, identity);
|
|
298
|
+
export const clineJsonServerConfig = (mcpUrl, identity = envReferenceIdentity('cline')) => jsonClientServerConfig('cline', mcpUrl, identity);
|
|
299
|
+
export const mergeClineMcpConfig = (configPath, mcpUrl, identity) => mergeJsonClientMcpConfig('cline', configPath, mcpUrl, identity);
|
|
300
|
+
|
|
301
|
+
export const continueJsonConfig = (mcpUrl, identity = envReferenceIdentity('continue')) => jsonClientConfig('continue', mcpUrl, identity);
|
|
302
|
+
export const continueJsonSnippet = (mcpUrl, identity = envReferenceIdentity('continue')) => jsonClientSnippet('continue', mcpUrl, identity);
|
|
303
|
+
export const continueJsonServerConfig = (mcpUrl, identity = envReferenceIdentity('continue')) => jsonClientServerConfig('continue', mcpUrl, identity);
|
|
304
|
+
export const mergeContinueMcpConfig = (configPath, mcpUrl, identity) => mergeJsonClientMcpConfig('continue', configPath, mcpUrl, identity);
|
|
305
|
+
|
|
306
|
+
export const claudeJsonConfig = (mcpUrl, identity = envReferenceIdentity('claude-desktop')) => jsonClientConfig('claude-desktop', mcpUrl, identity);
|
|
307
|
+
export const claudeJsonSnippet = (mcpUrl, identity = envReferenceIdentity('claude-desktop')) => jsonClientSnippet('claude-desktop', mcpUrl, identity);
|
|
308
|
+
export const claudeJsonServerConfig = (mcpUrl, identity = envReferenceIdentity('claude-desktop')) => jsonClientServerConfig('claude-desktop', mcpUrl, identity);
|
|
309
|
+
export const mergeClaudeMcpConfig = (configPath, mcpUrl, identity) => mergeJsonClientMcpConfig('claude-desktop', configPath, mcpUrl, identity);
|
|
310
|
+
|
|
311
|
+
export const openclawJsonConfig = (mcpUrl, identity = envReferenceIdentity('openclaw')) => jsonClientConfig('openclaw', mcpUrl, identity);
|
|
312
|
+
export const openclawJsonSnippet = (mcpUrl, identity = envReferenceIdentity('openclaw')) => jsonClientSnippet('openclaw', mcpUrl, identity);
|
|
313
|
+
export const openclawJsonServerConfig = (mcpUrl, identity = envReferenceIdentity('openclaw')) => jsonClientServerConfig('openclaw', mcpUrl, identity);
|
|
314
|
+
export const mergeOpenclawMcpConfig = (configPath, mcpUrl, identity) => mergeJsonClientMcpConfig('openclaw', configPath, mcpUrl, identity);
|
|
315
|
+
|
|
316
|
+
export const kiroJsonConfig = (mcpUrl, identity = envReferenceIdentity('kiro')) => jsonClientConfig('kiro', mcpUrl, identity);
|
|
317
|
+
export const kiroJsonSnippet = (mcpUrl, identity = envReferenceIdentity('kiro')) => jsonClientSnippet('kiro', mcpUrl, identity);
|
|
318
|
+
export const kiroJsonServerConfig = (mcpUrl, identity = envReferenceIdentity('kiro')) => jsonClientServerConfig('kiro', mcpUrl, identity);
|
|
319
|
+
export const mergeKiroMcpConfig = (configPath, mcpUrl, identity) => mergeJsonClientMcpConfig('kiro', configPath, mcpUrl, identity);
|
|
320
|
+
|
|
321
|
+
export const zedJsonConfig = (mcpUrl, identity = envReferenceIdentity('zed')) => jsonClientConfig('zed', mcpUrl, identity);
|
|
322
|
+
export const zedJsonSnippet = (mcpUrl, identity = envReferenceIdentity('zed')) => jsonClientSnippet('zed', mcpUrl, identity);
|
|
323
|
+
export const zedJsonServerConfig = (mcpUrl, identity = envReferenceIdentity('zed')) => jsonClientServerConfig('zed', mcpUrl, identity);
|
|
324
|
+
export const mergeZedMcpConfig = (configPath, mcpUrl, identity) => mergeJsonClientMcpConfig('zed', configPath, mcpUrl, identity);
|
|
325
|
+
|
|
326
|
+
export const jetbrainsJsonConfig = (mcpUrl, identity = envReferenceIdentity('jetbrains')) => jsonClientConfig('jetbrains', mcpUrl, identity);
|
|
327
|
+
export const jetbrainsJsonSnippet = (mcpUrl, identity = envReferenceIdentity('jetbrains')) => jsonClientSnippet('jetbrains', mcpUrl, identity);
|
|
328
|
+
export const jetbrainsJsonServerConfig = (mcpUrl, identity = envReferenceIdentity('jetbrains')) => jsonClientServerConfig('jetbrains', mcpUrl, identity);
|
|
329
|
+
export const mergeJetbrainsMcpConfig = (configPath, mcpUrl, identity) => mergeJsonClientMcpConfig('jetbrains', configPath, mcpUrl, identity);
|
|
330
|
+
|
|
331
|
+
export const opencodeJsonConfig = (mcpUrl, identity = envReferenceIdentity('opencode')) => jsonClientConfig('opencode', mcpUrl, identity);
|
|
332
|
+
export const opencodeJsonSnippet = (mcpUrl, identity = envReferenceIdentity('opencode')) => jsonClientSnippet('opencode', mcpUrl, identity);
|
|
333
|
+
export const opencodeJsonServerConfig = (mcpUrl, identity = envReferenceIdentity('opencode')) => jsonClientServerConfig('opencode', mcpUrl, identity);
|
|
334
|
+
export const mergeOpencodeMcpConfig = (configPath, mcpUrl, identity) => mergeJsonClientMcpConfig('opencode', configPath, mcpUrl, identity);
|
|
335
|
+
|
|
336
|
+
export const qwenJsonConfig = (mcpUrl, identity = envReferenceIdentity('qwen')) => jsonClientConfig('qwen', mcpUrl, identity);
|
|
337
|
+
export const qwenJsonSnippet = (mcpUrl, identity = envReferenceIdentity('qwen')) => jsonClientSnippet('qwen', mcpUrl, identity);
|
|
338
|
+
export const qwenJsonServerConfig = (mcpUrl, identity = envReferenceIdentity('qwen')) => jsonClientServerConfig('qwen', mcpUrl, identity);
|
|
339
|
+
export const mergeQwenMcpConfig = (configPath, mcpUrl, identity) => mergeJsonClientMcpConfig('qwen', configPath, mcpUrl, identity);
|
|
340
|
+
|
|
341
|
+
export const traeJsonConfig = (mcpUrl, identity = envReferenceIdentity('trae')) => jsonClientConfig('trae', mcpUrl, identity);
|
|
342
|
+
export const traeJsonSnippet = (mcpUrl, identity = envReferenceIdentity('trae')) => jsonClientSnippet('trae', mcpUrl, identity);
|
|
343
|
+
export const traeJsonServerConfig = (mcpUrl, identity = envReferenceIdentity('trae')) => jsonClientServerConfig('trae', mcpUrl, identity);
|
|
344
|
+
export const mergeTraeMcpConfig = (configPath, mcpUrl, identity) => mergeJsonClientMcpConfig('trae', configPath, mcpUrl, identity);
|
|
345
|
+
|
|
346
|
+
export const traeSoloJsonConfig = (mcpUrl, identity = envReferenceIdentity('trae-solo')) => jsonClientConfig('trae-solo', mcpUrl, identity);
|
|
347
|
+
export const traeSoloJsonSnippet = (mcpUrl, identity = envReferenceIdentity('trae-solo')) => jsonClientSnippet('trae-solo', mcpUrl, identity);
|
|
348
|
+
export const traeSoloJsonServerConfig = (mcpUrl, identity = envReferenceIdentity('trae-solo')) => jsonClientServerConfig('trae-solo', mcpUrl, identity);
|
|
349
|
+
export const mergeTraeSoloMcpConfig = (configPath, mcpUrl, identity) => mergeJsonClientMcpConfig('trae-solo', configPath, mcpUrl, identity);
|
|
350
|
+
|
|
351
|
+
export const claudecodeJsonConfig = (mcpUrl, identity = envReferenceIdentity('claude-code')) => jsonClientConfig('claude-code', mcpUrl, identity);
|
|
352
|
+
export const claudecodeJsonSnippet = (mcpUrl, identity = envReferenceIdentity('claude-code')) => jsonClientSnippet('claude-code', mcpUrl, identity);
|
|
353
|
+
export const claudecodeJsonServerConfig = (mcpUrl, identity = envReferenceIdentity('claude-code')) => jsonClientServerConfig('claude-code', mcpUrl, identity);
|
|
354
|
+
export const mergeClaudecodeMcpConfig = (configPath, mcpUrl, identity) => mergeJsonClientMcpConfig('claude-code', configPath, mcpUrl, identity);
|
|
355
|
+
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
COMMAND_NAME,
|
|
6
|
+
LEGACY_MCP_SERVER_NAMES,
|
|
7
|
+
MCP_SERVER_NAME,
|
|
8
|
+
TOKEN_ENV_VAR
|
|
9
|
+
} from '../../core/constants.js';
|
|
10
|
+
import { UsageError } from '../../core/errors.js';
|
|
11
|
+
import {
|
|
12
|
+
bestEffortChmod,
|
|
13
|
+
escapeRegExp,
|
|
14
|
+
escapeTomlString,
|
|
15
|
+
fileExists,
|
|
16
|
+
readTextIfExists,
|
|
17
|
+
unescapeTomlString
|
|
18
|
+
} from '../../core/runtime.js';
|
|
19
|
+
import { agentInstanceIdentityPath } from '../identity/device.js';
|
|
20
|
+
|
|
21
|
+
export function codexTomlSnippet(mcpUrl) {
|
|
22
|
+
return `[mcp_servers.${MCP_SERVER_NAME}]
|
|
23
|
+
url = "${escapeTomlString(mcpUrl)}"
|
|
24
|
+
bearer_token_env_var = "${TOKEN_ENV_VAR}"
|
|
25
|
+
`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function codexSmokeReport(configPath, env) {
|
|
29
|
+
const configText = await readTextIfExists(configPath);
|
|
30
|
+
const serverBlock = findTomlServerBlock(configText);
|
|
31
|
+
const block = serverBlock.block;
|
|
32
|
+
const mcpUrl = block ? tomlStringValue(block, 'url') : null;
|
|
33
|
+
const bearerTokenEnvVar = block ? tomlStringValue(block, 'bearer_token_env_var') : null;
|
|
34
|
+
const tokenValue = env[TOKEN_ENV_VAR] ?? '';
|
|
35
|
+
const identityPath = agentInstanceIdentityPath(env, 'codex');
|
|
36
|
+
const identityPresent = await fileExists(identityPath);
|
|
37
|
+
const checks = [
|
|
38
|
+
{
|
|
39
|
+
name: 'config_present',
|
|
40
|
+
ok: configText.trim().length > 0,
|
|
41
|
+
required: true,
|
|
42
|
+
detail: configText.trim().length > 0 ? 'found' : 'missing'
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'memory_os_server_present',
|
|
46
|
+
ok: Boolean(block),
|
|
47
|
+
required: true,
|
|
48
|
+
detail: block ? `[mcp_servers.${serverBlock.name}]` : `missing [mcp_servers.${MCP_SERVER_NAME}]`
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'mcp_url_present',
|
|
52
|
+
ok: Boolean(mcpUrl),
|
|
53
|
+
required: true,
|
|
54
|
+
detail: mcpUrl ?? 'missing url'
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'bearer_token_env_var',
|
|
58
|
+
ok: bearerTokenEnvVar === TOKEN_ENV_VAR,
|
|
59
|
+
required: true,
|
|
60
|
+
detail: bearerTokenEnvVar ?? 'missing bearer_token_env_var'
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'token_env_present',
|
|
64
|
+
ok: Boolean(env[TOKEN_ENV_VAR]),
|
|
65
|
+
required: true,
|
|
66
|
+
detail: env[TOKEN_ENV_VAR] ? 'present' : `missing ${TOKEN_ENV_VAR}`
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'token_not_embedded_in_config',
|
|
70
|
+
ok: !tokenValue || !configText.includes(tokenValue),
|
|
71
|
+
required: true,
|
|
72
|
+
detail: 'token value not printed or embedded'
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'agent_instance_identity_file',
|
|
76
|
+
ok: identityPresent,
|
|
77
|
+
required: false,
|
|
78
|
+
detail: identityPresent ? identityPath : `optional; create with ${COMMAND_NAME} mcp add codex --write (${identityPath})`
|
|
79
|
+
}
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
ok: checks.every((check) => !check.required || check.ok),
|
|
84
|
+
client: 'codex',
|
|
85
|
+
configPath,
|
|
86
|
+
serverName: serverBlock.name ?? MCP_SERVER_NAME,
|
|
87
|
+
mcpUrl,
|
|
88
|
+
tokenEnvVar: TOKEN_ENV_VAR,
|
|
89
|
+
agentInstanceIdPath: identityPath,
|
|
90
|
+
checks
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function appendTomlServerConfig(configPath, mcpUrl) {
|
|
95
|
+
const snippet = codexTomlSnippet(mcpUrl);
|
|
96
|
+
const existing = await readTextIfExists(configPath);
|
|
97
|
+
const existingName = existingTomlMcpServerName(existing);
|
|
98
|
+
if (existingName) {
|
|
99
|
+
throw new UsageError(`MCP config already contains [mcp_servers.${existingName}]. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
103
|
+
const prefix = existing.trim().length === 0 ? '' : '\n\n';
|
|
104
|
+
await fs.appendFile(configPath, `${prefix}${snippet}`, { mode: 0o600 });
|
|
105
|
+
await bestEffortChmod(configPath, 0o600);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function knownMcpServerNames() {
|
|
109
|
+
return [MCP_SERVER_NAME, ...LEGACY_MCP_SERVER_NAMES];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function existingTomlMcpServerName(content) {
|
|
113
|
+
return knownMcpServerNames().find((name) => content.includes(`[mcp_servers.${name}]`));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function findTomlServerBlock(content) {
|
|
117
|
+
const name = existingTomlMcpServerName(content);
|
|
118
|
+
return {
|
|
119
|
+
name: name ?? null,
|
|
120
|
+
block: name ? tomlServerBlock(content, name) : ''
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function tomlServerBlock(content, serverName) {
|
|
125
|
+
const header = `[mcp_servers.${serverName}]`;
|
|
126
|
+
const lines = content.split(/\r?\n/);
|
|
127
|
+
const start = lines.findIndex((line) => line.trim() === header);
|
|
128
|
+
if (start === -1) {
|
|
129
|
+
return '';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const block = [];
|
|
133
|
+
for (let index = start + 1; index < lines.length; index += 1) {
|
|
134
|
+
const line = lines[index];
|
|
135
|
+
if (/^\s*\[/.test(line)) {
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
block.push(line);
|
|
139
|
+
}
|
|
140
|
+
return block.join('\n');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function tomlStringValue(block, key) {
|
|
144
|
+
const pattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*"((?:\\\\.|[^"\\\\])*)"\\s*$`, 'm');
|
|
145
|
+
const match = block.match(pattern);
|
|
146
|
+
return match ? unescapeTomlString(match[1]) : null;
|
|
147
|
+
}
|
|
148
|
+
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
AGENT_INSTANCE_ENV_VAR,
|
|
6
|
+
MCP_SERVER_NAME,
|
|
7
|
+
TOKEN_ENV_VAR
|
|
8
|
+
} from '../../core/constants.js';
|
|
9
|
+
import { UsageError } from '../../core/errors.js';
|
|
10
|
+
import {
|
|
11
|
+
bestEffortChmod,
|
|
12
|
+
readTextIfExists
|
|
13
|
+
} from '../../core/runtime.js';
|
|
14
|
+
import { envReferenceIdentity } from '../identity/device.js';
|
|
15
|
+
|
|
16
|
+
export function hermesYamlSnippet(mcpUrl, identity = envReferenceIdentity('hermes')) {
|
|
17
|
+
return `mcp_servers:
|
|
18
|
+
${MCP_SERVER_NAME}:
|
|
19
|
+
command: npx
|
|
20
|
+
args:
|
|
21
|
+
- -y
|
|
22
|
+
- mcp-remote
|
|
23
|
+
- ${mcpUrl}
|
|
24
|
+
- --header
|
|
25
|
+
- "Authorization:Bearer \${${TOKEN_ENV_VAR}}"
|
|
26
|
+
- --header
|
|
27
|
+
- "X-Memory-OS-Agent-ID:${identity.agentId}"
|
|
28
|
+
- --header
|
|
29
|
+
- "X-Memory-OS-Agent-Instance-ID:\${${AGENT_INSTANCE_ENV_VAR}}"
|
|
30
|
+
env:
|
|
31
|
+
${TOKEN_ENV_VAR}: "\${env:${TOKEN_ENV_VAR}}"
|
|
32
|
+
${AGENT_INSTANCE_ENV_VAR}: "${identity.agentInstanceId}"
|
|
33
|
+
`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function mergeHermesMcpConfig(configPath, mcpUrl, identity) {
|
|
37
|
+
const existing = await readTextIfExists(configPath);
|
|
38
|
+
|
|
39
|
+
if (existing.includes(`${MCP_SERVER_NAME}:`) || existing.includes('memory_os:') || existing.includes('memory-os:')) {
|
|
40
|
+
throw new UsageError(`MCP config already contains ${MCP_SERVER_NAME} in mcp_servers. Edit ${configPath} manually to avoid duplicate server definitions.`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
|
|
44
|
+
|
|
45
|
+
if (existing.trim().length === 0) {
|
|
46
|
+
await fs.writeFile(configPath, hermesYamlSnippet(mcpUrl, identity), { mode: 0o600 });
|
|
47
|
+
} else if (existing.includes('mcp_servers:')) {
|
|
48
|
+
const replacement = `mcp_servers:
|
|
49
|
+
${MCP_SERVER_NAME}:
|
|
50
|
+
command: npx
|
|
51
|
+
args:
|
|
52
|
+
- -y
|
|
53
|
+
- mcp-remote
|
|
54
|
+
- ${mcpUrl}
|
|
55
|
+
- --header
|
|
56
|
+
- "Authorization:Bearer \${${TOKEN_ENV_VAR}}"
|
|
57
|
+
- --header
|
|
58
|
+
- "X-Memory-OS-Agent-ID:${identity.agentId}"
|
|
59
|
+
- --header
|
|
60
|
+
- "X-Memory-OS-Agent-Instance-ID:\${${AGENT_INSTANCE_ENV_VAR}}"
|
|
61
|
+
env:
|
|
62
|
+
${TOKEN_ENV_VAR}: "\${env:${TOKEN_ENV_VAR}}"
|
|
63
|
+
${AGENT_INSTANCE_ENV_VAR}: "${identity.agentInstanceId}"`;
|
|
64
|
+
const updated = existing.replace('mcp_servers:', replacement);
|
|
65
|
+
await fs.writeFile(configPath, updated, { mode: 0o600 });
|
|
66
|
+
} else {
|
|
67
|
+
const prefix = existing.endsWith('\n') ? '' : '\n';
|
|
68
|
+
await fs.appendFile(configPath, `${prefix}${hermesYamlSnippet(mcpUrl, identity)}`, { mode: 0o600 });
|
|
69
|
+
}
|
|
70
|
+
await bestEffortChmod(configPath, 0o600);
|
|
71
|
+
}
|
|
72
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { AGENT_INSTANCE_ENV_VAR } from '../../core/constants.js';
|
|
6
|
+
import { bestEffortChmod, parseJsonConfig, readTextIfExists } from '../../core/runtime.js';
|
|
7
|
+
import { stringValue } from '../../core/args.js';
|
|
8
|
+
import { configRoot } from './paths.js';
|
|
9
|
+
|
|
10
|
+
function identityClientId(clientId) {
|
|
11
|
+
return (clientId === 'antigravity-ide' || clientId === 'antigravity2' || clientId === 'antigravity-cli')
|
|
12
|
+
? 'antigravity'
|
|
13
|
+
: clientId;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function agentIdentity(clientId, env) {
|
|
17
|
+
const targetClientId = identityClientId(clientId);
|
|
18
|
+
const configuredInstanceId = env[AGENT_INSTANCE_ENV_VAR];
|
|
19
|
+
if (configuredInstanceId) {
|
|
20
|
+
return {
|
|
21
|
+
agentId: targetClientId,
|
|
22
|
+
agentInstanceId: configuredInstanceId,
|
|
23
|
+
path: `${AGENT_INSTANCE_ENV_VAR} environment variable`
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const identityPath = deviceInstanceIdentityPath(env);
|
|
28
|
+
const existing = await readDeviceInstanceIdentity(identityPath);
|
|
29
|
+
if (existing) {
|
|
30
|
+
return { agentId: targetClientId, agentInstanceId: existing, path: identityPath };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const hostname = os.hostname().toLowerCase().replace(/[^a-z0-9-]/g, '-').substring(0, 32);
|
|
34
|
+
const generated = `xmemo-${hostname}`;
|
|
35
|
+
|
|
36
|
+
await fs.mkdir(path.dirname(identityPath), { recursive: true, mode: 0o700 });
|
|
37
|
+
await bestEffortChmod(path.dirname(identityPath), 0o700);
|
|
38
|
+
await fs.writeFile(identityPath, `${JSON.stringify({
|
|
39
|
+
version: 1,
|
|
40
|
+
deviceInstanceId: generated,
|
|
41
|
+
hostname: os.hostname(),
|
|
42
|
+
createdAt: new Date().toISOString()
|
|
43
|
+
}, null, 2)}\n`, { mode: 0o600 });
|
|
44
|
+
await bestEffortChmod(identityPath, 0o600);
|
|
45
|
+
return { agentId: targetClientId, agentInstanceId: generated, path: identityPath };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function readDeviceInstanceIdentity(identityPath) {
|
|
49
|
+
const existing = await readTextIfExists(identityPath);
|
|
50
|
+
if (!existing.trim()) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const parsed = parseJsonConfig(existing, identityPath);
|
|
54
|
+
const value = stringValue(parsed, ['deviceInstanceId']) || stringValue(parsed, ['agentInstanceId']);
|
|
55
|
+
return value || null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function deviceInstanceIdentityPath(env) {
|
|
59
|
+
return path.join(configRoot(env), 'device-instance.json');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function readAgentInstanceIdentity(identityPath) {
|
|
63
|
+
return readDeviceInstanceIdentity(identityPath);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function agentInstanceIdentityPath(env) {
|
|
67
|
+
return deviceInstanceIdentityPath(env);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function envReferenceIdentity(clientId) {
|
|
71
|
+
const targetClientId = identityClientId(clientId);
|
|
72
|
+
return {
|
|
73
|
+
agentId: targetClientId,
|
|
74
|
+
agentInstanceId: `\${${AGENT_INSTANCE_ENV_VAR}}`,
|
|
75
|
+
path: `${AGENT_INSTANCE_ENV_VAR} environment variable`
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|