@volcengine/tls-observer-trae-install 0.0.2

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/dist/index.js ADDED
@@ -0,0 +1,749 @@
1
+ import { execFile, execFileSync, spawn } from "node:child_process";
2
+ import { homedir, tmpdir } from "node:os";
3
+ import { pathToFileURL } from "node:url";
4
+ import { promisify } from "node:util";
5
+ import promises from "node:readline/promises";
6
+ import { randomInt } from "node:crypto";
7
+ import * as __rspack_external_node_fs_promises_153e37e0 from "node:fs/promises";
8
+ import * as __rspack_external_node_path_c5b9b54f from "node:path";
9
+ const execFileAsync = promisify(execFile);
10
+ const PLUGIN_NAME = 'tls-observer-trae';
11
+ const PLUGIN_PACKAGE_NAME = '@volcengine/tls-observer-trae';
12
+ const DEFAULT_REGISTRY = 'https://registry.npmjs.org';
13
+ const DEFAULT_TRAE_ROOT = __rspack_external_node_path_c5b9b54f.join(homedir(), '.trae-cn');
14
+ const DEFAULT_PLUGIN_TARGET = __rspack_external_node_path_c5b9b54f.join(DEFAULT_TRAE_ROOT, 'plugins', PLUGIN_NAME);
15
+ const DEFAULT_ENV_TARGET = __rspack_external_node_path_c5b9b54f.join(DEFAULT_TRAE_ROOT, 'tls-observer-trae.env');
16
+ const DEFAULT_DATA_ROOT = __rspack_external_node_path_c5b9b54f.join(DEFAULT_TRAE_ROOT, 'plugins', 'data', PLUGIN_NAME);
17
+ const DEFAULT_HOOK_CONFIG_TARGET = __rspack_external_node_path_c5b9b54f.join(DEFAULT_TRAE_ROOT, 'hooks.json');
18
+ const DEFAULT_REGION = 'cn-beijing';
19
+ const DEFAULT_TLS_ENDPOINT = 'tls-cn-beijing.volces.com';
20
+ const DEFAULT_PROJECT_NAME = 'trae_observability';
21
+ const DEFAULT_APP_NAME_PREFIX = 'trae-observability';
22
+ const DEFAULT_LOG_APP_TYPE = 'Trae';
23
+ const DEFAULT_TEMPLATE_NAME = 'Trae';
24
+ function readEnvString(key) {
25
+ const value = process.env[key];
26
+ return 'string' == typeof value && value.trim() ? value.trim() : void 0;
27
+ }
28
+ function readEnvBool(key) {
29
+ const value = readEnvString(key);
30
+ return value ? /^(1|true|yes|y|on)$/i.test(value) : false;
31
+ }
32
+ function takeArg(argv, index, flag) {
33
+ const value = argv[index + 1];
34
+ if ('string' != typeof value || value.startsWith('--')) throw new Error(`${flag} requires a value`);
35
+ return value;
36
+ }
37
+ function defaultTlsEndpointForRegion(region) {
38
+ return region ? `tls-${region}.volces.com` : DEFAULT_TLS_ENDPOINT;
39
+ }
40
+ function randomSuffix() {
41
+ const alphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
42
+ let value = '';
43
+ for(let index = 0; index < 8; index += 1)value += alphabet[randomInt(alphabet.length)];
44
+ return value;
45
+ }
46
+ function defaultAppName() {
47
+ return `${DEFAULT_APP_NAME_PREFIX}_${randomSuffix()}`;
48
+ }
49
+ function applyDerivedDefaults(options) {
50
+ if (options.region && !options.tlsEndpoint && !options.otelEndpoint) options.tlsEndpoint = defaultTlsEndpointForRegion(options.region);
51
+ if (!options.projectName && !options.projectId) options.projectName = DEFAULT_PROJECT_NAME;
52
+ if (!options.logAppName) options.logAppName = defaultAppName();
53
+ }
54
+ function parseCliOptions(argv = process.argv.slice(2)) {
55
+ const options = {
56
+ help: false,
57
+ nonInteractive: readEnvBool('TRAE_TLS_INSTALL_NON_INTERACTIVE'),
58
+ force: readEnvBool('TRAE_TLS_INSTALL_FORCE'),
59
+ beta: readEnvBool('TRAE_TLS_INSTALL_BETA'),
60
+ skipTlsConfig: readEnvBool('TRAE_TLS_INSTALL_SKIP_TLS_CONFIG'),
61
+ healthCheck: readEnvBool('TRAE_TLS_INSTALL_HEALTH_CHECK'),
62
+ registry: readEnvString('TRAE_TLS_NPM_REGISTRY') || readEnvString('npm_config_registry') || readEnvString('NPM_CONFIG_REGISTRY') || DEFAULT_REGISTRY,
63
+ pluginPackage: readEnvString('TRAE_TLS_PLUGIN_PACKAGE') || PLUGIN_PACKAGE_NAME,
64
+ pluginVersion: readEnvString('TRAE_TLS_PLUGIN_VERSION'),
65
+ pluginTarget: readEnvString('TRAE_PLUGIN_TARGET') || DEFAULT_PLUGIN_TARGET,
66
+ envTarget: readEnvString('TRAE_TLS_ENV_TARGET') || DEFAULT_ENV_TARGET,
67
+ dataRoot: readEnvString('TRAE_TLS_DATA_ROOT') || DEFAULT_DATA_ROOT,
68
+ hookConfigTarget: readEnvString('TRAE_HOOK_CONFIG_TARGET') || DEFAULT_HOOK_CONFIG_TARGET,
69
+ region: readEnvString('TRAE_TLS_REGION') || DEFAULT_REGION,
70
+ tlsEndpoint: readEnvString('TRAE_TLS_ENDPOINT'),
71
+ otelEndpoint: readEnvString('TRAE_TLS_OTEL_ENDPOINT'),
72
+ traceTopicId: readEnvString('TRAE_TLS_TRACE_TOPIC_ID'),
73
+ apiKey: readEnvString('TRAE_TLS_API_KEY'),
74
+ ak: readEnvString('TRAE_TLS_AK'),
75
+ sk: readEnvString('TRAE_TLS_SK'),
76
+ authMode: readEnvString('TRAE_TLS_AUTH_MODE') || 'header',
77
+ exportTimeoutMs: readEnvString('TRAE_TLS_EXPORT_TIMEOUT_MS') || '20000',
78
+ exportTimeoutRetries: readEnvString('TRAE_TLS_EXPORT_TIMEOUT_RETRIES') || '1',
79
+ captureContent: readEnvString('TRAE_TLS_CAPTURE_CONTENT') || '1',
80
+ projectId: readEnvString('TRAE_TLS_PROJECT_ID'),
81
+ projectName: readEnvString('TRAE_TLS_PROJECT_NAME'),
82
+ logAppId: readEnvString('TRAE_TLS_LOG_APP_ID'),
83
+ logAppName: readEnvString('TRAE_TLS_LOG_APP_NAME'),
84
+ logAppType: readEnvString('TRAE_TLS_LOG_APP_TYPE') || DEFAULT_LOG_APP_TYPE,
85
+ templateName: readEnvString('TRAE_TLS_TEMPLATE_NAME') || DEFAULT_TEMPLATE_NAME,
86
+ templateId: readEnvString('TRAE_TLS_TEMPLATE_ID')
87
+ };
88
+ for(let index = 0; index < argv.length; index += 1){
89
+ const current = argv[index];
90
+ if ('--help' === current || '-h' === current) options.help = true;
91
+ else if ('--non-interactive' === current) options.nonInteractive = true;
92
+ else if ('--force' === current) options.force = true;
93
+ else if ('--beta' === current) options.beta = true;
94
+ else if ('--skip-tls-config' === current) options.skipTlsConfig = true;
95
+ else if ('--health-check' === current) options.healthCheck = true;
96
+ else if ('--registry' === current) options.registry = takeArg(argv, index++, current);
97
+ else if ('--plugin-package' === current) options.pluginPackage = takeArg(argv, index++, current);
98
+ else if ('--plugin-version' === current) options.pluginVersion = takeArg(argv, index++, current);
99
+ else if ('--plugin-target' === current) options.pluginTarget = takeArg(argv, index++, current);
100
+ else if ('--env-target' === current) options.envTarget = takeArg(argv, index++, current);
101
+ else if ('--data-root' === current) options.dataRoot = takeArg(argv, index++, current);
102
+ else if ('--hook-config-target' === current) options.hookConfigTarget = takeArg(argv, index++, current);
103
+ else if ('--region' === current) options.region = takeArg(argv, index++, current);
104
+ else if ('--tls-endpoint' === current) options.tlsEndpoint = takeArg(argv, index++, current);
105
+ else if ('--otel-endpoint' === current) options.otelEndpoint = takeArg(argv, index++, current);
106
+ else if ('--trace-topic-id' === current) options.traceTopicId = takeArg(argv, index++, current);
107
+ else if ('--api-key' === current) options.apiKey = takeArg(argv, index++, current);
108
+ else if ('--ak' === current) options.ak = takeArg(argv, index++, current);
109
+ else if ('--sk' === current) options.sk = takeArg(argv, index++, current);
110
+ else if ('--auth-mode' === current) options.authMode = takeArg(argv, index++, current);
111
+ else if ('--export-timeout-ms' === current) options.exportTimeoutMs = takeArg(argv, index++, current);
112
+ else if ('--export-timeout-retries' === current) options.exportTimeoutRetries = takeArg(argv, index++, current);
113
+ else if ('--capture-content' === current) options.captureContent = takeArg(argv, index++, current);
114
+ else if ('--project-id' === current) options.projectId = takeArg(argv, index++, current);
115
+ else if ('--project-name' === current) options.projectName = takeArg(argv, index++, current);
116
+ else if ('--app-name' === current || '--log-app-name' === current) options.logAppName = takeArg(argv, index++, current);
117
+ else if ('--log-app-id' === current) options.logAppId = takeArg(argv, index++, current);
118
+ else if ('--log-app-type' === current) options.logAppType = takeArg(argv, index++, current);
119
+ else if ('--template-name' === current) options.templateName = takeArg(argv, index++, current);
120
+ else if ('--template-id' === current) options.templateId = takeArg(argv, index++, current);
121
+ else throw new Error(`Unknown option: ${current}`);
122
+ }
123
+ return options;
124
+ }
125
+ function printHelp() {
126
+ console.log(`TLS Observer Trae installer
127
+
128
+ Usage:
129
+ tls-observer-trae-install [options]
130
+ npm exec -y --package=@volcengine/tls-observer-trae-install -- tls-observer-trae-install [options]
131
+
132
+ Options:
133
+ --non-interactive Read required inputs from flags/env
134
+ --force Overwrite existing runtime/env files
135
+ --beta Install @volcengine/tls-observer-trae@beta instead of @latest
136
+ --skip-tls-config Install runtime without writing TLS env
137
+ --health-check Check ctx-cli/CFS environment after install
138
+ --registry <url> NPM registry. Default: https://registry.npmjs.org
139
+ --plugin-version <version> Exact runtime version or dist-tag. Overrides --beta
140
+ --plugin-target <path> Default: ~/.trae-cn/plugins/tls-observer-trae
141
+ --env-target <path> Default: ~/.trae-cn/tls-observer-trae.env
142
+ --data-root <path> Default: ~/.trae-cn/plugins/data/tls-observer-trae
143
+ --hook-config-target <path> Default: ~/.trae-cn/hooks.json
144
+ --region <region> TLS region. Default: cn-beijing
145
+ --tls-endpoint <host> TLS Producer endpoint. Derived from region by default
146
+ --trace-topic-id <id> TLS trace topic ID
147
+ --api-key <value> TLS API Key
148
+ --ak <value> TLS Access Key
149
+ --sk <value> TLS Secret Key
150
+ --project-name <name> TLS Project name for AK/SK auto creation
151
+ --app-name <name> TLS LogApp name for AK/SK auto creation
152
+ --capture-content <0|1> Default: 1
153
+ `);
154
+ }
155
+ function createReadline() {
156
+ return promises.createInterface({
157
+ input: process.stdin,
158
+ output: process.stdout,
159
+ terminal: false
160
+ });
161
+ }
162
+ async function promptText(rl, message, defaultValue = '') {
163
+ const suffix = defaultValue ? `\n default: ${defaultValue}\n> ` : '\n> ';
164
+ const answer = await rl.question(`${message}${suffix}`);
165
+ return answer.trim() || defaultValue;
166
+ }
167
+ async function promptRequired(rl, message, defaultValue = '') {
168
+ while(true){
169
+ const value = await promptText(rl, message, defaultValue);
170
+ if (value.trim()) return value.trim();
171
+ console.log('This value is required.');
172
+ }
173
+ }
174
+ function setTerminalEcho(enabled) {
175
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
176
+ try {
177
+ execFileSync('stty', [
178
+ enabled ? 'echo' : '-echo'
179
+ ], {
180
+ stdio: [
181
+ 'inherit',
182
+ 'ignore',
183
+ 'ignore'
184
+ ]
185
+ });
186
+ return true;
187
+ } catch {
188
+ return false;
189
+ }
190
+ }
191
+ async function promptSecret(rl, message, defaultValue = '') {
192
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return promptRequired(rl, message, defaultValue);
193
+ rl.pause();
194
+ return new Promise((resolve, reject)=>{
195
+ const input = process.stdin;
196
+ let value = '';
197
+ let finished = false;
198
+ const previousRawMode = input.isRaw;
199
+ let echoDisabled = false;
200
+ const suffix = defaultValue ? '\n default: already set, press enter to keep\n> ' : '\n> ';
201
+ function cleanup() {
202
+ input.off('data', onData);
203
+ input.setRawMode(Boolean(previousRawMode));
204
+ if (echoDisabled) setTerminalEcho(true);
205
+ input.pause();
206
+ rl.resume();
207
+ }
208
+ function finish() {
209
+ if (finished) return;
210
+ finished = true;
211
+ cleanup();
212
+ process.stdout.write('\n');
213
+ const resolvedValue = value.trim() || defaultValue;
214
+ if (resolvedValue) resolve(resolvedValue);
215
+ else {
216
+ console.log('This value is required.');
217
+ resolve(promptSecret(rl, message, defaultValue));
218
+ }
219
+ }
220
+ function onData(buffer) {
221
+ for (const char of buffer.toString('utf8')){
222
+ if (finished) return;
223
+ if ('\u0003' === char) {
224
+ finished = true;
225
+ cleanup();
226
+ reject(new Error('Interrupted'));
227
+ return;
228
+ }
229
+ if ('\r' === char || '\n' === char) return void finish();
230
+ if ('\u007f' === char || '\b' === char) {
231
+ if (value.length > 0) {
232
+ value = [
233
+ ...value
234
+ ].slice(0, -1).join('');
235
+ process.stdout.write('\b \b');
236
+ }
237
+ continue;
238
+ }
239
+ value += char;
240
+ process.stdout.write('*');
241
+ }
242
+ }
243
+ input.resume();
244
+ input.setRawMode(true);
245
+ input.on('data', onData);
246
+ echoDisabled = setTerminalEcho(false);
247
+ process.stdout.write(`${message}${suffix}`);
248
+ });
249
+ }
250
+ function detectAuthType(options) {
251
+ if (options.apiKey) return 'apiKey';
252
+ if (options.ak || options.sk) return 'aksk';
253
+ }
254
+ function validateBaseTlsConfig(options) {
255
+ const missing = [];
256
+ if (!options.region) missing.push('--region or TRAE_TLS_REGION');
257
+ const authType = detectAuthType(options);
258
+ if (!authType) missing.push('--api-key or --ak/--sk');
259
+ if ('apiKey' === authType && (options.ak || options.sk)) throw new Error('API Key and AK/SK cannot be used together');
260
+ if ('aksk' === authType && (!options.ak || !options.sk)) missing.push('--ak and --sk');
261
+ if (missing.length > 0) throw new Error(`Missing required TLS config: ${missing.join(', ')}`);
262
+ }
263
+ function validateTlsConfig(options) {
264
+ validateBaseTlsConfig(options);
265
+ const missing = [];
266
+ if (!options.tlsEndpoint && !options.otelEndpoint) missing.push('--tls-endpoint or --region');
267
+ if (!options.traceTopicId) missing.push('--trace-topic-id or TRAE_TLS_TRACE_TOPIC_ID');
268
+ if (missing.length > 0) throw new Error(`Missing required TLS config: ${missing.join(', ')}`);
269
+ }
270
+ async function collectInteractiveOptions(options) {
271
+ if (options.nonInteractive) {
272
+ if (!options.skipTlsConfig) {
273
+ applyDerivedDefaults(options);
274
+ validateBaseTlsConfig(options);
275
+ }
276
+ return options;
277
+ }
278
+ const rl = createReadline();
279
+ try {
280
+ console.log('TLS Observer Trae installer');
281
+ options.force = true;
282
+ if (!options.skipTlsConfig) {
283
+ options.region = await promptRequired(rl, 'TLS region', options.region || DEFAULT_REGION);
284
+ applyDerivedDefaults(options);
285
+ const authType = await promptRequired(rl, 'Auth type, apiKey or aksk', detectAuthType(options) || 'apiKey');
286
+ if ('apiKey' === authType) {
287
+ options.traceTopicId = await promptRequired(rl, 'Trace topic ID', options.traceTopicId || '');
288
+ options.apiKey = await promptSecret(rl, 'TLS API Key', options.apiKey || '');
289
+ options.ak = void 0;
290
+ options.sk = void 0;
291
+ } else if ('aksk' === authType) {
292
+ options.projectName = await promptRequired(rl, 'TLS Project name', options.projectName || DEFAULT_PROJECT_NAME);
293
+ options.logAppName = await promptRequired(rl, 'TLS LogApp name', options.logAppName || defaultAppName());
294
+ options.ak = await promptSecret(rl, 'TLS AK', options.ak || '');
295
+ options.sk = await promptSecret(rl, 'TLS SK', options.sk || '');
296
+ options.apiKey = void 0;
297
+ } else throw new Error('Auth type must be apiKey or aksk');
298
+ }
299
+ return options;
300
+ } finally{
301
+ rl.close();
302
+ }
303
+ }
304
+ function moduleSpecifier(specifier) {
305
+ if (specifier.startsWith('.') || specifier.startsWith('/') || /^[A-Za-z]:[\\/]/.test(specifier)) return pathToFileURL(__rspack_external_node_path_c5b9b54f.resolve(specifier)).href;
306
+ return specifier;
307
+ }
308
+ async function loadOpenapiModule() {
309
+ return import(moduleSpecifier(readEnvString('TRAE_TLS_OPENAPI_MODULE') || '@volcengine/openapi'));
310
+ }
311
+ function createTlsService(options, openapiModule) {
312
+ if (!openapiModule?.tlsOpenapi?.TlsService) throw new Error('@volcengine/openapi must export tlsOpenapi.TlsService');
313
+ const isAkSk = !options.apiKey;
314
+ const tlsService = new openapiModule.tlsOpenapi.TlsService({
315
+ host: options.tlsEndpoint || defaultTlsEndpointForRegion(options.region),
316
+ region: options.region,
317
+ accessKeyId: isAkSk ? options.ak : 'anonymous',
318
+ secretKey: isAkSk ? options.sk : 'anonymous',
319
+ protocol: 'https:',
320
+ version: '0.3.0'
321
+ });
322
+ const axiosConfig = options.apiKey ? {
323
+ proxy: false,
324
+ headers: {
325
+ 'x-tls-anonymous-identity': options.apiKey
326
+ }
327
+ } : {
328
+ proxy: false
329
+ };
330
+ return {
331
+ isAkSk,
332
+ DescribeProjects: (payload)=>tlsService.DescribeProjects(payload, axiosConfig),
333
+ CreateProject: (payload)=>tlsService.CreateProject(payload, axiosConfig),
334
+ DescribeApps: (payload)=>tlsService.createAPI('DescribeApps', {
335
+ method: 'GET'
336
+ })(payload, axiosConfig),
337
+ CreateApp: (payload)=>tlsService.createAPI('CreateApp', {
338
+ method: 'POST'
339
+ })(payload, axiosConfig),
340
+ DescribeLogApps: (payload)=>tlsService.createAPI('DescribeLogApps', {
341
+ method: 'GET'
342
+ })(payload, axiosConfig),
343
+ DescribeTemplates: (payload)=>tlsService.createAPI('DescribeTemplates', {
344
+ method: 'GET'
345
+ })(payload, axiosConfig)
346
+ };
347
+ }
348
+ async function ensureTemplateId(tlsService, options) {
349
+ if (options.templateId) return options.templateId;
350
+ const response = await tlsService.DescribeTemplates({
351
+ TemplateName: options.templateName,
352
+ PageNumber: 1,
353
+ PageSize: 20
354
+ });
355
+ const template = response.Templates?.find((item)=>item.TemplateName === options.templateName);
356
+ const templateId = String(template?.TemplateId || '').trim();
357
+ if (!templateId) throw new Error(`DescribeTemplates did not return TemplateId for ${options.templateName}. Pass --template-id or --trace-topic-id.`);
358
+ return templateId;
359
+ }
360
+ async function ensureProjectId(tlsService, options) {
361
+ if (options.projectId) return options.projectId;
362
+ const response = await tlsService.DescribeProjects({
363
+ ProjectName: options.projectName,
364
+ PageSize: 100,
365
+ PageNumber: 1
366
+ });
367
+ const project = response.Projects?.find((item)=>item.ProjectName === options.projectName);
368
+ const existingProjectId = String(project?.ProjectId || '').trim();
369
+ if (existingProjectId) return existingProjectId;
370
+ const created = await tlsService.CreateProject({
371
+ ProjectName: options.projectName,
372
+ Description: 'Trae observability',
373
+ Region: options.region
374
+ });
375
+ const projectId = String(created.ProjectId || '').trim();
376
+ if (!projectId) throw new Error('CreateProject succeeded but did not return ProjectId');
377
+ return projectId;
378
+ }
379
+ async function ensureLogApp(tlsService, options, projectId) {
380
+ if (options.logAppId) return options.logAppId;
381
+ const appResponse = await tlsService.DescribeApps({
382
+ AppName: options.logAppName,
383
+ AppType: 'LogApp'
384
+ });
385
+ const total = appResponse.total ?? appResponse.Total ?? 0;
386
+ if (total > 0) {
387
+ const app = appResponse.apps?.[0] || appResponse.Apps?.[0];
388
+ const logAppId = String(app?.Resources?.[0]?.Id || app?.Sources?.[0]?.Id || '').trim();
389
+ if (logAppId) return logAppId;
390
+ }
391
+ const templateId = await ensureTemplateId(tlsService, options);
392
+ const created = await tlsService.CreateApp({
393
+ AppName: options.logAppName,
394
+ AppType: 'LogApp',
395
+ LogAppReq: {
396
+ LogAppName: options.logAppName,
397
+ LogAppType: options.logAppType,
398
+ ProjectId: projectId
399
+ },
400
+ TemplateId: templateId
401
+ });
402
+ const logAppId = String(created.Sources?.[0]?.Id || '').trim();
403
+ if (!logAppId) throw new Error('CreateApp succeeded but did not return Sources[0].Id');
404
+ return logAppId;
405
+ }
406
+ function topicMapFromLogApps(response) {
407
+ const topics = response?.LogApps?.[0]?.RelatedResourceList || [];
408
+ const topicMap = {};
409
+ for (const topic of topics){
410
+ const resourceName = String(topic?.ResourceName || '');
411
+ const topicName = resourceName.split('_').filter(Boolean).at(-1);
412
+ const resourceId = String(topic?.ResourceID || '').trim();
413
+ if (topicName && resourceId) topicMap[topicName] = resourceId;
414
+ }
415
+ return topicMap;
416
+ }
417
+ async function ensureTlsResources(options) {
418
+ applyDerivedDefaults(options);
419
+ if (options.skipTlsConfig || options.apiKey || options.traceTopicId || !options.ak || !options.sk) return {
420
+ created: false
421
+ };
422
+ const openapiModule = await loadOpenapiModule();
423
+ const tlsService = createTlsService(options, openapiModule);
424
+ const projectId = await ensureProjectId(tlsService, options);
425
+ const logAppId = await ensureLogApp(tlsService, options, projectId);
426
+ options.logAppId = logAppId;
427
+ const logApps = await tlsService.DescribeLogApps({
428
+ PageNumber: 1,
429
+ PageSize: 20,
430
+ LogAppId: logAppId,
431
+ LogAppType: options.logAppType
432
+ });
433
+ const topics = topicMapFromLogApps(logApps);
434
+ if (!options.traceTopicId) options.traceTopicId = topics.trace;
435
+ return {
436
+ created: true,
437
+ logAppId,
438
+ traceTopicId: options.traceTopicId
439
+ };
440
+ }
441
+ async function pathExists(filePath) {
442
+ try {
443
+ await __rspack_external_node_fs_promises_153e37e0.stat(filePath);
444
+ return true;
445
+ } catch (error) {
446
+ if ('ENOENT' === error.code) return false;
447
+ throw error;
448
+ }
449
+ }
450
+ async function readJsonFile(filePath, fallback) {
451
+ try {
452
+ return JSON.parse(await __rspack_external_node_fs_promises_153e37e0.readFile(filePath, 'utf8'));
453
+ } catch (error) {
454
+ if ('ENOENT' === error.code) return fallback;
455
+ throw error;
456
+ }
457
+ }
458
+ async function assertPluginRuntime(pluginSource) {
459
+ const entry = __rspack_external_node_path_c5b9b54f.join(pluginSource, 'dist', 'index.js');
460
+ const hooks = __rspack_external_node_path_c5b9b54f.join(pluginSource, 'hooks', 'hooks.json');
461
+ if (await pathExists(entry) && await pathExists(hooks)) return;
462
+ throw new Error(`Runtime package is missing files: ${pluginSource}. Expected dist/index.js and hooks/hooks.json.`);
463
+ }
464
+ function pluginPackagePath(tempRoot, packageName) {
465
+ return __rspack_external_node_path_c5b9b54f.join(tempRoot, 'node_modules', ...packageName.split('/'));
466
+ }
467
+ function pluginPackageSpec(options) {
468
+ const version = options.pluginVersion || (options.beta ? 'beta' : 'latest');
469
+ return `${options.pluginPackage}@${version}`;
470
+ }
471
+ async function installPluginPackageToTemp(options) {
472
+ const root = await __rspack_external_node_fs_promises_153e37e0.mkdtemp(__rspack_external_node_path_c5b9b54f.join(tmpdir(), 'tls-observer-trae-plugin-'));
473
+ const packageSpec = pluginPackageSpec(options);
474
+ const npm = 'win32' === process.platform ? 'npm.cmd' : 'npm';
475
+ const args = [
476
+ 'install',
477
+ '--prefix',
478
+ root,
479
+ "--ignore-scripts",
480
+ '--no-audit',
481
+ '--no-fund',
482
+ '--omit=dev',
483
+ '--registry',
484
+ options.registry,
485
+ packageSpec
486
+ ];
487
+ await execFileAsync(npm, args, {
488
+ timeout: 120000
489
+ });
490
+ const source = pluginPackagePath(root, options.pluginPackage);
491
+ await assertPluginRuntime(source);
492
+ return {
493
+ root,
494
+ source,
495
+ packageSpec
496
+ };
497
+ }
498
+ async function findLocalPluginSource() {
499
+ const explicit = readEnvString('TRAE_TLS_PLUGIN_SOURCE');
500
+ if (!explicit) return;
501
+ const manifest = await readJsonFile(__rspack_external_node_path_c5b9b54f.join(explicit, 'package.json'), {});
502
+ const publishDirectory = manifest.publishConfig?.directory;
503
+ const candidates = publishDirectory ? [
504
+ __rspack_external_node_path_c5b9b54f.join(explicit, publishDirectory)
505
+ ] : [
506
+ explicit
507
+ ];
508
+ for (const candidate of candidates)try {
509
+ await assertPluginRuntime(candidate);
510
+ return candidate;
511
+ } catch {}
512
+ throw new Error(`TRAE_TLS_PLUGIN_SOURCE does not point to a built runtime package: ${explicit}.`);
513
+ }
514
+ async function readPluginVersion(pluginSource) {
515
+ const manifest = await readJsonFile(__rspack_external_node_path_c5b9b54f.join(pluginSource, 'package.json'), {});
516
+ return manifest.version;
517
+ }
518
+ async function installPlugin(options) {
519
+ if (await pathExists(options.pluginTarget) && !options.force) throw new Error(`Plugin target exists: ${options.pluginTarget}. Re-run with --force.`);
520
+ const localSource = await findLocalPluginSource();
521
+ const tempInstall = localSource ? void 0 : await installPluginPackageToTemp(options);
522
+ const source = localSource || tempInstall.source;
523
+ await __rspack_external_node_fs_promises_153e37e0.rm(options.pluginTarget, {
524
+ recursive: true,
525
+ force: true
526
+ });
527
+ await __rspack_external_node_fs_promises_153e37e0.mkdir(options.pluginTarget, {
528
+ recursive: true
529
+ });
530
+ for (const entry of [
531
+ 'dist',
532
+ 'hooks',
533
+ 'README.md',
534
+ 'package.json'
535
+ ])await __rspack_external_node_fs_promises_153e37e0.cp(__rspack_external_node_path_c5b9b54f.join(source, entry), __rspack_external_node_path_c5b9b54f.join(options.pluginTarget, entry), {
536
+ recursive: true
537
+ });
538
+ const npm = 'win32' === process.platform ? 'npm.cmd' : 'npm';
539
+ await new Promise((resolve, reject)=>{
540
+ const child = spawn(npm, [
541
+ 'install',
542
+ '--omit=dev',
543
+ "--ignore-scripts",
544
+ '--no-audit',
545
+ '--no-fund'
546
+ ], {
547
+ cwd: options.pluginTarget,
548
+ stdio: 'inherit'
549
+ });
550
+ const timer = setTimeout(()=>{
551
+ child.kill();
552
+ reject(new Error('npm install timed out after 120s'));
553
+ }, 120000);
554
+ child.on('exit', (code)=>{
555
+ clearTimeout(timer);
556
+ if (0 === code) resolve();
557
+ else reject(new Error(`npm install exited with code ${code}`));
558
+ });
559
+ child.on('error', reject);
560
+ });
561
+ if (tempInstall) await __rspack_external_node_fs_promises_153e37e0.rm(tempInstall.root, {
562
+ recursive: true,
563
+ force: true
564
+ });
565
+ return {
566
+ source,
567
+ target: options.pluginTarget,
568
+ packageSpec: tempInstall?.packageSpec,
569
+ version: await readPluginVersion(source)
570
+ };
571
+ }
572
+ function envLine(key, value) {
573
+ if (null == value || '' === value) return;
574
+ return `${key}=${String(value)}`;
575
+ }
576
+ function buildTlsEnv(options) {
577
+ const lines = [
578
+ '# Generated by tls-observer-trae installer.',
579
+ '# Keep this file local because it can contain credentials.',
580
+ envLine('TRAE_TLS_REGION', options.region),
581
+ envLine('TRAE_TLS_ENDPOINT', options.tlsEndpoint),
582
+ envLine('TRAE_TLS_OTEL_ENDPOINT', options.otelEndpoint),
583
+ envLine('TRAE_TLS_TRACE_TOPIC_ID', options.traceTopicId),
584
+ envLine('TRAE_TLS_API_KEY', options.apiKey),
585
+ envLine('TRAE_TLS_AK', options.ak),
586
+ envLine('TRAE_TLS_SK', options.sk),
587
+ envLine('TRAE_TLS_AUTH_MODE', options.authMode),
588
+ envLine('TRAE_TLS_EXPORT_TIMEOUT_MS', options.exportTimeoutMs),
589
+ envLine('TRAE_TLS_EXPORT_TIMEOUT_RETRIES', options.exportTimeoutRetries),
590
+ envLine('TRAE_TLS_CAPTURE_CONTENT', options.captureContent),
591
+ envLine('TRAE_TLS_DATA_ROOT', options.dataRoot)
592
+ ].filter(Boolean);
593
+ return `${lines.join('\n')}\n`;
594
+ }
595
+ async function writeTlsEnv(options) {
596
+ if (options.skipTlsConfig) return null;
597
+ if (await pathExists(options.envTarget) && !options.force) throw new Error(`TLS env target exists: ${options.envTarget}. Re-run with --force.`);
598
+ await __rspack_external_node_fs_promises_153e37e0.mkdir(__rspack_external_node_path_c5b9b54f.dirname(options.envTarget), {
599
+ recursive: true
600
+ });
601
+ await __rspack_external_node_fs_promises_153e37e0.writeFile(options.envTarget, buildTlsEnv(options), {
602
+ mode: 384
603
+ });
604
+ return options.envTarget;
605
+ }
606
+ async function writeHookReferenceConfig(options) {
607
+ const shellQuote = (value)=>`'${value.replace(/'/g, "'\\''")}'`;
608
+ const command = `TRAE_TLS_ENV_FILE=${shellQuote(options.envTarget)} node ${shellQuote(__rspack_external_node_path_c5b9b54f.join(options.pluginTarget, 'dist', 'index.js'))}`;
609
+ const config = {
610
+ version: 1,
611
+ hooks: {
612
+ UserPromptSubmit: [
613
+ {
614
+ hooks: [
615
+ {
616
+ type: 'command',
617
+ command,
618
+ timeout: 30
619
+ }
620
+ ]
621
+ }
622
+ ],
623
+ PreToolUse: [
624
+ {
625
+ hooks: [
626
+ {
627
+ type: 'command',
628
+ command,
629
+ timeout: 30
630
+ }
631
+ ]
632
+ }
633
+ ],
634
+ PostToolUse: [
635
+ {
636
+ hooks: [
637
+ {
638
+ type: 'command',
639
+ command,
640
+ timeout: 30
641
+ }
642
+ ]
643
+ }
644
+ ],
645
+ Stop: [
646
+ {
647
+ hooks: [
648
+ {
649
+ type: 'command',
650
+ command,
651
+ timeout: 30
652
+ }
653
+ ]
654
+ }
655
+ ]
656
+ }
657
+ };
658
+ await __rspack_external_node_fs_promises_153e37e0.mkdir(__rspack_external_node_path_c5b9b54f.dirname(options.hookConfigTarget), {
659
+ recursive: true
660
+ });
661
+ await __rspack_external_node_fs_promises_153e37e0.writeFile(options.hookConfigTarget, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
662
+ return options.hookConfigTarget;
663
+ }
664
+ async function writeInstallState(options, plugin) {
665
+ const target = __rspack_external_node_path_c5b9b54f.join(options.dataRoot, 'state', 'install.json');
666
+ await __rspack_external_node_fs_promises_153e37e0.mkdir(__rspack_external_node_path_c5b9b54f.dirname(target), {
667
+ recursive: true
668
+ });
669
+ await __rspack_external_node_fs_promises_153e37e0.writeFile(target, `${JSON.stringify({
670
+ installed_at: new Date().toISOString(),
671
+ plugin_name: PLUGIN_NAME,
672
+ plugin_target: plugin.target,
673
+ env_target: options.envTarget,
674
+ hook_config_target: options.hookConfigTarget
675
+ }, null, 2)}\n`, 'utf8');
676
+ }
677
+ async function runHealthCheck(options) {
678
+ const missingCfsEnv = [
679
+ 'CFS_TOKEN',
680
+ 'CFS_IPC_NAME'
681
+ ].filter((key)=>!process.env[key]?.trim());
682
+ const result = {
683
+ ctx_cli_on_path: false,
684
+ contextfs_ready: 0 === missingCfsEnv.length,
685
+ contextfs_missing_env: missingCfsEnv,
686
+ cfs_token_present: Boolean(process.env.CFS_TOKEN),
687
+ cfs_ipc_name_present: Boolean(process.env.CFS_IPC_NAME),
688
+ cfs_transport: process.env.CFS_TRANSPORT || 'ipc'
689
+ };
690
+ try {
691
+ await execFileAsync('ctx-cli', [
692
+ '--help'
693
+ ], {
694
+ timeout: 5000,
695
+ env: {
696
+ ...process.env,
697
+ CFS_TRANSPORT: process.env.CFS_TRANSPORT || 'ipc'
698
+ }
699
+ });
700
+ result.ctx_cli_on_path = true;
701
+ } catch (error) {
702
+ result.ctx_cli_error = error instanceof Error ? error.message : String(error);
703
+ }
704
+ if (!result.contextfs_ready) result.note = 'ctx-cli can only read context:// resources inside Trae hook/toolhost environment with CFS_TOKEN and CFS_IPC_NAME.';
705
+ const target = __rspack_external_node_path_c5b9b54f.join(options.dataRoot, 'state', 'health-check.json');
706
+ await __rspack_external_node_fs_promises_153e37e0.mkdir(__rspack_external_node_path_c5b9b54f.dirname(target), {
707
+ recursive: true
708
+ });
709
+ await __rspack_external_node_fs_promises_153e37e0.writeFile(target, `${JSON.stringify(result, null, 2)}\n`, 'utf8');
710
+ return result;
711
+ }
712
+ function printCompletion({ plugin, envTarget, hookConfigTarget, health }) {
713
+ console.log('');
714
+ console.log('Install complete');
715
+ console.log(` Runtime: ${plugin.target}`);
716
+ if (plugin.packageSpec) console.log(` Runtime package: ${plugin.packageSpec}`);
717
+ if (plugin.version) console.log(` Runtime version: ${plugin.version}`);
718
+ console.log(` TLS env: ${envTarget || 'skipped'}`);
719
+ console.log(` Hook reference: ${hookConfigTarget}`);
720
+ if (health) console.log(` Health check: ${JSON.stringify(health)}`);
721
+ console.log('');
722
+ console.log('Next steps:');
723
+ console.log(' 1. Confirm Trae hook/plugin config path for your Trae build.');
724
+ console.log(' 2. Wire the command from the hook reference file.');
725
+ console.log(' 3. Run one Trae turn and check logs/send/latest.send-log.json under the data root.');
726
+ }
727
+ async function run(argv = process.argv.slice(2)) {
728
+ const parsed = parseCliOptions(argv);
729
+ if (parsed.help) return void printHelp();
730
+ const options = await collectInteractiveOptions(parsed);
731
+ if (!options.skipTlsConfig) {
732
+ const resourceResult = await ensureTlsResources(options);
733
+ if (resourceResult.created && !options.traceTopicId) throw new Error('TLS LogApp was created but trace topic discovery failed. Pass --trace-topic-id.');
734
+ applyDerivedDefaults(options);
735
+ validateTlsConfig(options);
736
+ }
737
+ const plugin = await installPlugin(options);
738
+ const envTarget = await writeTlsEnv(options);
739
+ const hookConfigTarget = await writeHookReferenceConfig(options);
740
+ await writeInstallState(options, plugin);
741
+ const health = options.healthCheck ? await runHealthCheck(options) : void 0;
742
+ printCompletion({
743
+ plugin,
744
+ envTarget,
745
+ hookConfigTarget,
746
+ health
747
+ });
748
+ }
749
+ export { run };