@volcengine/tls-observer-claude-code-install 0.0.1
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/CHANGELOG.md +7 -0
- package/README.md +70 -0
- package/bin/tls-observer-claude-code-install.mjs +9 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +627 -0
- package/dist/installer/installer-runner.d.ts +2 -0
- package/package.json +41 -0
- package/scripts/sync-plugin.mjs +31 -0
- package/src/index.ts +1 -0
- package/src/installer/installer-runner.ts +779 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
import { execFile, 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-claude-code';
|
|
11
|
+
const PLUGIN_PACKAGE_NAME = '@volcengine/tls-observer-claude-code';
|
|
12
|
+
const PLUGIN_ID = `${PLUGIN_NAME}@skills-dir`;
|
|
13
|
+
const DEFAULT_REGISTRY = 'https://registry.npmjs.org';
|
|
14
|
+
const DEFAULT_SETTINGS_PATH = __rspack_external_node_path_c5b9b54f.join(homedir(), '.claude', 'settings.json');
|
|
15
|
+
const DEFAULT_SKILLS_ROOT = __rspack_external_node_path_c5b9b54f.join(homedir(), '.claude', 'skills');
|
|
16
|
+
const DEFAULT_ENV_TARGET = __rspack_external_node_path_c5b9b54f.join(homedir(), '.claude', 'tls-observer-claude-code.env');
|
|
17
|
+
const DEFAULT_DATA_ROOT = __rspack_external_node_path_c5b9b54f.join(homedir(), '.claude', 'plugins', 'data', PLUGIN_NAME);
|
|
18
|
+
const DEFAULT_REGION = 'cn-guilin-boe';
|
|
19
|
+
const DEFAULT_PROJECT_NAME = 'claude_code_observability';
|
|
20
|
+
const DEFAULT_APP_NAME_PREFIX = 'claude-code-observability';
|
|
21
|
+
const DEFAULT_TLS_ENDPOINT = 'tls-cn-guilin-boe.volces.com';
|
|
22
|
+
const DEFAULT_LOG_APP_TYPE = 'Claude';
|
|
23
|
+
const DEFAULT_TEMPLATE_NAME = 'Claude';
|
|
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 normalizeEndpointHost(endpoint) {
|
|
41
|
+
return String(endpoint || '').trim().replace(/^https?:\/\//i, '').replace(/\/.*$/, '').replace(/:\d+$/, '');
|
|
42
|
+
}
|
|
43
|
+
function defaultOtelEndpointForRegion(region, tlsEndpoint) {
|
|
44
|
+
return `https://${normalizeEndpointHost(tlsEndpoint) || defaultTlsEndpointForRegion(region)}:4318/v1/traces`;
|
|
45
|
+
}
|
|
46
|
+
function randomSuffix() {
|
|
47
|
+
const alphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
|
|
48
|
+
let value = '';
|
|
49
|
+
for(let index = 0; index < 8; index += 1)value += alphabet[randomInt(alphabet.length)];
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
function defaultAppName() {
|
|
53
|
+
return `${DEFAULT_APP_NAME_PREFIX}_${randomSuffix()}`;
|
|
54
|
+
}
|
|
55
|
+
function applyDerivedDefaults(options) {
|
|
56
|
+
if (options.region) {
|
|
57
|
+
if (!options.tlsEndpoint) options.tlsEndpoint = defaultTlsEndpointForRegion(options.region);
|
|
58
|
+
if (!options.otelEndpoint) options.otelEndpoint = defaultOtelEndpointForRegion(options.region, options.tlsEndpoint);
|
|
59
|
+
}
|
|
60
|
+
if (!options.projectName && !options.projectId) options.projectName = DEFAULT_PROJECT_NAME;
|
|
61
|
+
if (!options.logAppName) options.logAppName = defaultAppName();
|
|
62
|
+
}
|
|
63
|
+
function parseCliOptions(argv = process.argv.slice(2)) {
|
|
64
|
+
const skillsRoot = readEnvString('CLAUDE_CODE_SKILLS_ROOT') || DEFAULT_SKILLS_ROOT;
|
|
65
|
+
const options = {
|
|
66
|
+
help: false,
|
|
67
|
+
nonInteractive: readEnvBool('CLAUDE_CODE_TLS_INSTALL_NON_INTERACTIVE'),
|
|
68
|
+
force: readEnvBool('CLAUDE_CODE_TLS_INSTALL_FORCE'),
|
|
69
|
+
beta: readEnvBool('CLAUDE_CODE_TLS_INSTALL_BETA'),
|
|
70
|
+
skipTlsConfig: readEnvBool('CLAUDE_CODE_TLS_INSTALL_SKIP_TLS_CONFIG'),
|
|
71
|
+
advancedPaths: readEnvBool('CLAUDE_CODE_TLS_INSTALL_ADVANCED_PATHS'),
|
|
72
|
+
registry: readEnvString('CLAUDE_CODE_TLS_NPM_REGISTRY') || readEnvString('npm_config_registry') || readEnvString('NPM_CONFIG_REGISTRY') || DEFAULT_REGISTRY,
|
|
73
|
+
pluginPackage: readEnvString('CLAUDE_CODE_TLS_PLUGIN_PACKAGE') || PLUGIN_PACKAGE_NAME,
|
|
74
|
+
pluginVersion: readEnvString('CLAUDE_CODE_TLS_PLUGIN_VERSION'),
|
|
75
|
+
settingsPath: readEnvString('CLAUDE_CODE_SETTINGS_PATH') || DEFAULT_SETTINGS_PATH,
|
|
76
|
+
skillsRoot,
|
|
77
|
+
pluginTarget: readEnvString('CLAUDE_CODE_PLUGIN_TARGET') || __rspack_external_node_path_c5b9b54f.join(skillsRoot, PLUGIN_NAME),
|
|
78
|
+
envTarget: readEnvString('CLAUDE_CODE_TLS_ENV_TARGET') || DEFAULT_ENV_TARGET,
|
|
79
|
+
dataRoot: readEnvString('CLAUDE_CODE_TLS_DATA_ROOT') || DEFAULT_DATA_ROOT,
|
|
80
|
+
region: readEnvString('CLAUDE_CODE_TLS_REGION'),
|
|
81
|
+
otelEndpoint: readEnvString('CLAUDE_CODE_TLS_OTEL_ENDPOINT'),
|
|
82
|
+
traceTopicId: readEnvString('CLAUDE_CODE_TLS_TRACE_TOPIC_ID'),
|
|
83
|
+
apiKey: readEnvString('CLAUDE_CODE_TLS_API_KEY'),
|
|
84
|
+
ak: readEnvString('CLAUDE_CODE_TLS_AK'),
|
|
85
|
+
sk: readEnvString('CLAUDE_CODE_TLS_SK'),
|
|
86
|
+
authMode: readEnvString('CLAUDE_CODE_TLS_AUTH_MODE') || 'header',
|
|
87
|
+
exportTimeoutMs: readEnvString('CLAUDE_CODE_TLS_EXPORT_TIMEOUT_MS') || '20000',
|
|
88
|
+
exportTimeoutRetries: readEnvString('CLAUDE_CODE_TLS_EXPORT_TIMEOUT_RETRIES') || '1',
|
|
89
|
+
captureContent: readEnvString('CLAUDE_CODE_TLS_CAPTURE_CONTENT') || '1',
|
|
90
|
+
tlsEndpoint: readEnvString('CLAUDE_CODE_TLS_ENDPOINT'),
|
|
91
|
+
projectId: readEnvString('CLAUDE_CODE_TLS_PROJECT_ID'),
|
|
92
|
+
projectName: readEnvString('CLAUDE_CODE_TLS_PROJECT_NAME'),
|
|
93
|
+
logAppId: readEnvString('CLAUDE_CODE_TLS_LOG_APP_ID'),
|
|
94
|
+
logAppName: readEnvString('CLAUDE_CODE_TLS_LOG_APP_NAME'),
|
|
95
|
+
logAppType: readEnvString('CLAUDE_CODE_TLS_LOG_APP_TYPE') || DEFAULT_LOG_APP_TYPE,
|
|
96
|
+
templateName: readEnvString('CLAUDE_CODE_TLS_TEMPLATE_NAME') || DEFAULT_TEMPLATE_NAME,
|
|
97
|
+
templateId: readEnvString('CLAUDE_CODE_TLS_TEMPLATE_ID')
|
|
98
|
+
};
|
|
99
|
+
for(let index = 0; index < argv.length; index += 1){
|
|
100
|
+
const current = argv[index];
|
|
101
|
+
if ('--help' === current || '-h' === current) options.help = true;
|
|
102
|
+
else if ('--non-interactive' === current) options.nonInteractive = true;
|
|
103
|
+
else if ('--force' === current) options.force = true;
|
|
104
|
+
else if ('--beta' === current) options.beta = true;
|
|
105
|
+
else if ('--skip-tls-config' === current) options.skipTlsConfig = true;
|
|
106
|
+
else if ('--advanced-paths' === current) options.advancedPaths = true;
|
|
107
|
+
else if ('--registry' === current) options.registry = takeArg(argv, index++, current);
|
|
108
|
+
else if ('--plugin-package' === current) options.pluginPackage = takeArg(argv, index++, current);
|
|
109
|
+
else if ('--plugin-version' === current) options.pluginVersion = takeArg(argv, index++, current);
|
|
110
|
+
else if ('--settings-path' === current) options.settingsPath = takeArg(argv, index++, current);
|
|
111
|
+
else if ('--skills-root' === current) {
|
|
112
|
+
options.skillsRoot = takeArg(argv, index++, current);
|
|
113
|
+
options.pluginTarget = __rspack_external_node_path_c5b9b54f.join(options.skillsRoot, PLUGIN_NAME);
|
|
114
|
+
} else if ('--plugin-target' === current) options.pluginTarget = takeArg(argv, index++, current);
|
|
115
|
+
else if ('--env-target' === current) options.envTarget = takeArg(argv, index++, current);
|
|
116
|
+
else if ('--data-root' === current) options.dataRoot = takeArg(argv, index++, current);
|
|
117
|
+
else if ('--region' === current) options.region = takeArg(argv, index++, current);
|
|
118
|
+
else if ('--otel-endpoint' === current) options.otelEndpoint = takeArg(argv, index++, current);
|
|
119
|
+
else if ('--trace-topic-id' === current) options.traceTopicId = takeArg(argv, index++, current);
|
|
120
|
+
else if ('--api-key' === current) options.apiKey = takeArg(argv, index++, current);
|
|
121
|
+
else if ('--ak' === current) options.ak = takeArg(argv, index++, current);
|
|
122
|
+
else if ('--sk' === current) options.sk = takeArg(argv, index++, current);
|
|
123
|
+
else if ('--auth-mode' === current) options.authMode = takeArg(argv, index++, current);
|
|
124
|
+
else if ('--export-timeout-ms' === current) options.exportTimeoutMs = takeArg(argv, index++, current);
|
|
125
|
+
else if ('--export-timeout-retries' === current) options.exportTimeoutRetries = takeArg(argv, index++, current);
|
|
126
|
+
else if ('--capture-content' === current) options.captureContent = takeArg(argv, index++, current);
|
|
127
|
+
else if ('--tls-endpoint' === current) options.tlsEndpoint = takeArg(argv, index++, current);
|
|
128
|
+
else if ('--project-id' === current) options.projectId = takeArg(argv, index++, current);
|
|
129
|
+
else if ('--project-name' === current) options.projectName = takeArg(argv, index++, current);
|
|
130
|
+
else if ('--app-name' === current || '--log-app-name' === current) options.logAppName = takeArg(argv, index++, current);
|
|
131
|
+
else if ('--log-app-id' === current) options.logAppId = takeArg(argv, index++, current);
|
|
132
|
+
else if ('--log-app-type' === current) options.logAppType = takeArg(argv, index++, current);
|
|
133
|
+
else if ('--template-name' === current) options.templateName = takeArg(argv, index++, current);
|
|
134
|
+
else if ('--template-id' === current) options.templateId = takeArg(argv, index++, current);
|
|
135
|
+
else throw new Error(`Unknown option: ${current}`);
|
|
136
|
+
}
|
|
137
|
+
return options;
|
|
138
|
+
}
|
|
139
|
+
function printHelp() {
|
|
140
|
+
console.log(`TLS Observer Claude Code installer
|
|
141
|
+
|
|
142
|
+
Usage:
|
|
143
|
+
tls-observer-claude-code-install [options]
|
|
144
|
+
npm exec -y --package=@volcengine/tls-observer-claude-code-install -- tls-observer-claude-code-install [options]
|
|
145
|
+
|
|
146
|
+
Options:
|
|
147
|
+
--non-interactive Read required inputs from flags/env
|
|
148
|
+
--force Overwrite existing plugin/env files
|
|
149
|
+
--beta Install @volcengine/tls-observer-claude-code@beta instead of @latest
|
|
150
|
+
--skip-tls-config Install plugin without writing TLS env
|
|
151
|
+
--advanced-paths Prompt for Claude path overrides
|
|
152
|
+
--registry <url> NPM registry for plugin package. Default: https://registry.npmjs.org
|
|
153
|
+
--plugin-version <version> Exact plugin version or dist-tag. Overrides --beta
|
|
154
|
+
--region <region> TLS region
|
|
155
|
+
--otel-endpoint <url> OTLP traces endpoint
|
|
156
|
+
--trace-topic-id <id> TLS trace topic ID
|
|
157
|
+
--api-key <value> TLS API Key
|
|
158
|
+
--ak <value> TLS Access Key
|
|
159
|
+
--sk <value> TLS Secret Key
|
|
160
|
+
--project-name <name> TLS Project name for AK/SK auto creation
|
|
161
|
+
--app-name <name> TLS LogApp name for AK/SK auto creation
|
|
162
|
+
--capture-content <0|1> Default: 1
|
|
163
|
+
--settings-path <path> Default: ~/.claude/settings.json
|
|
164
|
+
--plugin-target <path> Default: ~/.claude/skills/tls-observer-claude-code
|
|
165
|
+
--env-target <path> Default: ~/.claude/tls-observer-claude-code.env
|
|
166
|
+
`);
|
|
167
|
+
}
|
|
168
|
+
function createReadline() {
|
|
169
|
+
return promises.createInterface({
|
|
170
|
+
input: process.stdin,
|
|
171
|
+
output: process.stdout,
|
|
172
|
+
terminal: false
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
async function promptText(rl, message, defaultValue = '') {
|
|
176
|
+
const suffix = defaultValue ? `\n default: ${defaultValue}\n> ` : '\n> ';
|
|
177
|
+
const answer = await rl.question(`${message}${suffix}`);
|
|
178
|
+
return answer.trim() || defaultValue;
|
|
179
|
+
}
|
|
180
|
+
async function promptRequired(rl, message, defaultValue = '') {
|
|
181
|
+
while(true){
|
|
182
|
+
const value = await promptText(rl, message, defaultValue);
|
|
183
|
+
if (value.trim()) return value.trim();
|
|
184
|
+
console.log('This value is required.');
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async function promptSecret(rl, message, defaultValue = '') {
|
|
188
|
+
return promptRequired(rl, message, defaultValue);
|
|
189
|
+
}
|
|
190
|
+
function detectAuthType(options) {
|
|
191
|
+
if (options.apiKey) return 'apiKey';
|
|
192
|
+
if (options.ak || options.sk) return 'aksk';
|
|
193
|
+
}
|
|
194
|
+
function validateBaseTlsConfig(options) {
|
|
195
|
+
const missing = [];
|
|
196
|
+
if (!options.region) missing.push('--region or CLAUDE_CODE_TLS_REGION');
|
|
197
|
+
const authType = detectAuthType(options);
|
|
198
|
+
if (!authType) missing.push('--api-key or --ak/--sk');
|
|
199
|
+
if ('apiKey' === authType && (options.ak || options.sk)) throw new Error('API Key and AK/SK cannot be used together');
|
|
200
|
+
if ('aksk' === authType && (!options.ak || !options.sk)) missing.push('--ak and --sk');
|
|
201
|
+
if (missing.length > 0) throw new Error(`Missing required TLS config: ${missing.join(', ')}`);
|
|
202
|
+
}
|
|
203
|
+
function validateTlsConfig(options) {
|
|
204
|
+
validateBaseTlsConfig(options);
|
|
205
|
+
const missing = [];
|
|
206
|
+
if (!options.otelEndpoint) missing.push('--otel-endpoint or CLAUDE_CODE_TLS_OTEL_ENDPOINT');
|
|
207
|
+
if (!options.traceTopicId) missing.push('--trace-topic-id or CLAUDE_CODE_TLS_TRACE_TOPIC_ID');
|
|
208
|
+
if (missing.length > 0) throw new Error(`Missing required TLS config: ${missing.join(', ')}`);
|
|
209
|
+
}
|
|
210
|
+
async function collectInteractiveOptions(options) {
|
|
211
|
+
if (options.nonInteractive) {
|
|
212
|
+
if (!options.skipTlsConfig) {
|
|
213
|
+
applyDerivedDefaults(options);
|
|
214
|
+
validateBaseTlsConfig(options);
|
|
215
|
+
}
|
|
216
|
+
return options;
|
|
217
|
+
}
|
|
218
|
+
const rl = createReadline();
|
|
219
|
+
try {
|
|
220
|
+
console.log('TLS Observer Claude Code installer');
|
|
221
|
+
if (options.advancedPaths) {
|
|
222
|
+
options.settingsPath = await promptRequired(rl, 'Claude settings path', options.settingsPath);
|
|
223
|
+
options.pluginTarget = await promptRequired(rl, 'Claude plugin target', options.pluginTarget);
|
|
224
|
+
options.envTarget = await promptRequired(rl, 'TLS env output path', options.envTarget);
|
|
225
|
+
options.dataRoot = await promptRequired(rl, 'Plugin data root', options.dataRoot);
|
|
226
|
+
}
|
|
227
|
+
options.force = true;
|
|
228
|
+
if (!options.skipTlsConfig) {
|
|
229
|
+
options.region = await promptRequired(rl, 'TLS region', options.region || DEFAULT_REGION);
|
|
230
|
+
applyDerivedDefaults(options);
|
|
231
|
+
const authType = await promptRequired(rl, 'Auth type, apiKey or aksk', detectAuthType(options) || 'apiKey');
|
|
232
|
+
if ('apiKey' === authType) {
|
|
233
|
+
options.otelEndpoint = await promptRequired(rl, 'OTLP trace endpoint', options.otelEndpoint);
|
|
234
|
+
options.traceTopicId = await promptRequired(rl, 'Trace topic ID', options.traceTopicId || '');
|
|
235
|
+
options.apiKey = await promptSecret(rl, 'TLS API Key', options.apiKey || '');
|
|
236
|
+
options.ak = void 0;
|
|
237
|
+
options.sk = void 0;
|
|
238
|
+
} else if ('aksk' === authType) {
|
|
239
|
+
options.projectName = await promptRequired(rl, 'TLS Project name', options.projectName || DEFAULT_PROJECT_NAME);
|
|
240
|
+
options.logAppName = await promptRequired(rl, 'TLS LogApp name', options.logAppName || defaultAppName());
|
|
241
|
+
options.ak = await promptSecret(rl, 'TLS AK', options.ak || '');
|
|
242
|
+
options.sk = await promptSecret(rl, 'TLS SK', options.sk || '');
|
|
243
|
+
options.apiKey = void 0;
|
|
244
|
+
} else throw new Error('Auth type must be apiKey or aksk');
|
|
245
|
+
}
|
|
246
|
+
return options;
|
|
247
|
+
} finally{
|
|
248
|
+
rl.close();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function moduleSpecifier(specifier) {
|
|
252
|
+
if (specifier.startsWith('.') || specifier.startsWith('/') || /^[A-Za-z]:[\\/]/.test(specifier)) return pathToFileURL(__rspack_external_node_path_c5b9b54f.resolve(specifier)).href;
|
|
253
|
+
return specifier;
|
|
254
|
+
}
|
|
255
|
+
async function loadOpenapiModule() {
|
|
256
|
+
return import(moduleSpecifier(readEnvString('CLAUDE_CODE_TLS_OPENAPI_MODULE') || '@volcengine/openapi'));
|
|
257
|
+
}
|
|
258
|
+
function createTlsService(options, openapiModule) {
|
|
259
|
+
if (!openapiModule?.tlsOpenapi?.TlsService) throw new Error('@volcengine/openapi must export tlsOpenapi.TlsService');
|
|
260
|
+
const isAkSk = !options.apiKey;
|
|
261
|
+
const tlsService = new openapiModule.tlsOpenapi.TlsService({
|
|
262
|
+
host: options.tlsEndpoint || defaultTlsEndpointForRegion(options.region),
|
|
263
|
+
region: options.region,
|
|
264
|
+
accessKeyId: isAkSk ? options.ak : 'anonymous',
|
|
265
|
+
secretKey: isAkSk ? options.sk : 'anonymous',
|
|
266
|
+
protocol: 'https:',
|
|
267
|
+
version: '0.3.0'
|
|
268
|
+
});
|
|
269
|
+
const axiosConfig = options.apiKey ? {
|
|
270
|
+
proxy: false,
|
|
271
|
+
headers: {
|
|
272
|
+
'x-tls-anonymous-identity': options.apiKey
|
|
273
|
+
}
|
|
274
|
+
} : {
|
|
275
|
+
proxy: false
|
|
276
|
+
};
|
|
277
|
+
return {
|
|
278
|
+
isAkSk,
|
|
279
|
+
DescribeProjects: (payload)=>tlsService.DescribeProjects(payload, axiosConfig),
|
|
280
|
+
CreateProject: (payload)=>tlsService.CreateProject(payload, axiosConfig),
|
|
281
|
+
DescribeApps: (payload)=>tlsService.createAPI('DescribeApps', {
|
|
282
|
+
method: 'GET'
|
|
283
|
+
})(payload, axiosConfig),
|
|
284
|
+
CreateApp: (payload)=>tlsService.createAPI('CreateApp', {
|
|
285
|
+
method: 'POST'
|
|
286
|
+
})(payload, axiosConfig),
|
|
287
|
+
DescribeLogApps: (payload)=>tlsService.createAPI('DescribeLogApps', {
|
|
288
|
+
method: 'GET'
|
|
289
|
+
})(payload, axiosConfig),
|
|
290
|
+
DescribeTemplates: (payload)=>tlsService.createAPI('DescribeTemplates', {
|
|
291
|
+
method: 'GET'
|
|
292
|
+
})(payload, axiosConfig)
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
async function ensureTemplateId(tlsService, options) {
|
|
296
|
+
if (options.templateId) return options.templateId;
|
|
297
|
+
const response = await tlsService.DescribeTemplates({
|
|
298
|
+
TemplateName: options.templateName,
|
|
299
|
+
PageNumber: 1,
|
|
300
|
+
PageSize: 20
|
|
301
|
+
});
|
|
302
|
+
const template = response.Templates?.find((item)=>item.TemplateName === options.templateName);
|
|
303
|
+
const templateId = String(template?.TemplateId || '').trim();
|
|
304
|
+
if (!templateId) throw new Error(`DescribeTemplates did not return TemplateId for ${options.templateName}. Pass --template-id or --trace-topic-id.`);
|
|
305
|
+
return templateId;
|
|
306
|
+
}
|
|
307
|
+
async function ensureProjectId(tlsService, options) {
|
|
308
|
+
if (options.projectId) return options.projectId;
|
|
309
|
+
const response = await tlsService.DescribeProjects({
|
|
310
|
+
ProjectName: options.projectName,
|
|
311
|
+
PageSize: 100,
|
|
312
|
+
PageNumber: 1
|
|
313
|
+
});
|
|
314
|
+
const project = response.Projects?.find((item)=>item.ProjectName === options.projectName);
|
|
315
|
+
const existingProjectId = String(project?.ProjectId || '').trim();
|
|
316
|
+
if (existingProjectId) return existingProjectId;
|
|
317
|
+
const created = await tlsService.CreateProject({
|
|
318
|
+
ProjectName: options.projectName,
|
|
319
|
+
Description: 'Claude Code observability',
|
|
320
|
+
Region: options.region
|
|
321
|
+
});
|
|
322
|
+
const projectId = String(created.ProjectId || '').trim();
|
|
323
|
+
if (!projectId) throw new Error('CreateProject succeeded but did not return ProjectId');
|
|
324
|
+
return projectId;
|
|
325
|
+
}
|
|
326
|
+
async function ensureLogApp(tlsService, options, projectId) {
|
|
327
|
+
if (options.logAppId) return options.logAppId;
|
|
328
|
+
const appResponse = await tlsService.DescribeApps({
|
|
329
|
+
AppName: options.logAppName,
|
|
330
|
+
AppType: 'LogApp'
|
|
331
|
+
});
|
|
332
|
+
const total = appResponse.total ?? appResponse.Total ?? 0;
|
|
333
|
+
if (total > 0) {
|
|
334
|
+
const app = appResponse.apps?.[0] || appResponse.Apps?.[0];
|
|
335
|
+
const logAppId = String(app?.Resources?.[0]?.Id || app?.Sources?.[0]?.Id || '').trim();
|
|
336
|
+
if (logAppId) return logAppId;
|
|
337
|
+
}
|
|
338
|
+
const templateId = await ensureTemplateId(tlsService, options);
|
|
339
|
+
const created = await tlsService.CreateApp({
|
|
340
|
+
AppName: options.logAppName,
|
|
341
|
+
AppType: 'LogApp',
|
|
342
|
+
LogAppReq: {
|
|
343
|
+
LogAppName: options.logAppName,
|
|
344
|
+
LogAppType: options.logAppType,
|
|
345
|
+
ProjectId: projectId
|
|
346
|
+
},
|
|
347
|
+
TemplateId: templateId
|
|
348
|
+
});
|
|
349
|
+
const logAppId = String(created.Sources?.[0]?.Id || '').trim();
|
|
350
|
+
if (!logAppId) throw new Error('CreateApp succeeded but did not return Sources[0].Id');
|
|
351
|
+
return logAppId;
|
|
352
|
+
}
|
|
353
|
+
function topicMapFromLogApps(response) {
|
|
354
|
+
const topics = response?.LogApps?.[0]?.RelatedResourceList || [];
|
|
355
|
+
const topicMap = {};
|
|
356
|
+
for (const topic of topics){
|
|
357
|
+
const resourceName = String(topic?.ResourceName || '');
|
|
358
|
+
const topicName = resourceName.split('_').filter(Boolean).at(-1);
|
|
359
|
+
const resourceId = String(topic?.ResourceID || '').trim();
|
|
360
|
+
if (topicName && resourceId) topicMap[topicName] = resourceId;
|
|
361
|
+
}
|
|
362
|
+
return topicMap;
|
|
363
|
+
}
|
|
364
|
+
async function ensureTlsResources(options) {
|
|
365
|
+
applyDerivedDefaults(options);
|
|
366
|
+
if (options.skipTlsConfig || options.apiKey || options.traceTopicId || !options.ak || !options.sk) return {
|
|
367
|
+
created: false
|
|
368
|
+
};
|
|
369
|
+
const openapiModule = await loadOpenapiModule();
|
|
370
|
+
const tlsService = createTlsService(options, openapiModule);
|
|
371
|
+
const projectId = await ensureProjectId(tlsService, options);
|
|
372
|
+
const logAppId = await ensureLogApp(tlsService, options, projectId);
|
|
373
|
+
options.logAppId = logAppId;
|
|
374
|
+
const logApps = await tlsService.DescribeLogApps({
|
|
375
|
+
PageNumber: 1,
|
|
376
|
+
PageSize: 20,
|
|
377
|
+
LogAppId: logAppId,
|
|
378
|
+
LogAppType: options.logAppType
|
|
379
|
+
});
|
|
380
|
+
const topics = topicMapFromLogApps(logApps);
|
|
381
|
+
if (!options.traceTopicId) options.traceTopicId = topics.trace;
|
|
382
|
+
return {
|
|
383
|
+
created: true,
|
|
384
|
+
logAppId,
|
|
385
|
+
traceTopicId: options.traceTopicId
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
async function pathExists(filePath) {
|
|
389
|
+
try {
|
|
390
|
+
await __rspack_external_node_fs_promises_153e37e0.stat(filePath);
|
|
391
|
+
return true;
|
|
392
|
+
} catch (error) {
|
|
393
|
+
if ('ENOENT' === error.code) return false;
|
|
394
|
+
throw error;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
async function assertPluginRuntime(pluginSource) {
|
|
398
|
+
const manifest = __rspack_external_node_path_c5b9b54f.join(pluginSource, '.claude-plugin', 'plugin.json');
|
|
399
|
+
const hooks = __rspack_external_node_path_c5b9b54f.join(pluginSource, 'hooks', 'hooks.json');
|
|
400
|
+
const entry = __rspack_external_node_path_c5b9b54f.join(pluginSource, 'dist', 'index.js');
|
|
401
|
+
if (await pathExists(manifest) && await pathExists(hooks) && await pathExists(entry)) return;
|
|
402
|
+
throw new Error(`Plugin package is missing runtime files: ${pluginSource}. Expected dist/index.js, hooks/hooks.json, and .claude-plugin/plugin.json.`);
|
|
403
|
+
}
|
|
404
|
+
function pluginPackagePath(tempRoot, packageName) {
|
|
405
|
+
return __rspack_external_node_path_c5b9b54f.join(tempRoot, 'node_modules', ...packageName.split('/'));
|
|
406
|
+
}
|
|
407
|
+
function pluginPackageSpec(options) {
|
|
408
|
+
const version = options.pluginVersion || (options.beta ? 'beta' : 'latest');
|
|
409
|
+
return `${options.pluginPackage}@${version}`;
|
|
410
|
+
}
|
|
411
|
+
async function installPluginPackageToTemp(options) {
|
|
412
|
+
const root = await __rspack_external_node_fs_promises_153e37e0.mkdtemp(__rspack_external_node_path_c5b9b54f.join(tmpdir(), 'tls-observer-claude-code-plugin-'));
|
|
413
|
+
const packageSpec = pluginPackageSpec(options);
|
|
414
|
+
const npm = 'win32' === process.platform ? 'npm.cmd' : 'npm';
|
|
415
|
+
const args = [
|
|
416
|
+
'install',
|
|
417
|
+
'--prefix',
|
|
418
|
+
root,
|
|
419
|
+
"--ignore-scripts",
|
|
420
|
+
'--no-audit',
|
|
421
|
+
'--no-fund',
|
|
422
|
+
'--omit=dev',
|
|
423
|
+
'--registry',
|
|
424
|
+
options.registry,
|
|
425
|
+
packageSpec
|
|
426
|
+
];
|
|
427
|
+
try {
|
|
428
|
+
await execFileAsync(npm, args, {
|
|
429
|
+
timeout: 120000
|
|
430
|
+
});
|
|
431
|
+
} catch (error) {
|
|
432
|
+
const detail = error && 'object' == typeof error && 'stderr' in error ? String(error.stderr || '') : error instanceof Error ? error.message : String(error);
|
|
433
|
+
throw new Error(`Failed to install plugin package ${packageSpec} from ${options.registry}: ${detail}`);
|
|
434
|
+
}
|
|
435
|
+
const source = pluginPackagePath(root, options.pluginPackage);
|
|
436
|
+
await assertPluginRuntime(source);
|
|
437
|
+
return {
|
|
438
|
+
root,
|
|
439
|
+
source,
|
|
440
|
+
packageSpec
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
async function findLocalPluginSource() {
|
|
444
|
+
const explicit = readEnvString('CLAUDE_CODE_PLUGIN_SOURCE');
|
|
445
|
+
if (!explicit) return;
|
|
446
|
+
const candidates = [
|
|
447
|
+
explicit
|
|
448
|
+
].filter((candidate)=>Boolean(candidate));
|
|
449
|
+
for (const candidate of candidates)try {
|
|
450
|
+
await assertPluginRuntime(candidate);
|
|
451
|
+
return candidate;
|
|
452
|
+
} catch {}
|
|
453
|
+
throw new Error(`CLAUDE_CODE_PLUGIN_SOURCE does not point to a built plugin: ${explicit}`);
|
|
454
|
+
}
|
|
455
|
+
async function readPluginVersion(pluginSource) {
|
|
456
|
+
const manifest = await readJsonFile(__rspack_external_node_path_c5b9b54f.join(pluginSource, 'package.json'), {});
|
|
457
|
+
return manifest.version;
|
|
458
|
+
}
|
|
459
|
+
async function installPlugin(options) {
|
|
460
|
+
if (await pathExists(options.pluginTarget) && !options.force) throw new Error(`Plugin target exists: ${options.pluginTarget}. Re-run with --force.`);
|
|
461
|
+
const localSource = await findLocalPluginSource();
|
|
462
|
+
const tempInstall = localSource ? void 0 : await installPluginPackageToTemp(options);
|
|
463
|
+
const source = localSource || tempInstall.source;
|
|
464
|
+
await __rspack_external_node_fs_promises_153e37e0.rm(options.pluginTarget, {
|
|
465
|
+
recursive: true,
|
|
466
|
+
force: true
|
|
467
|
+
});
|
|
468
|
+
await __rspack_external_node_fs_promises_153e37e0.mkdir(__rspack_external_node_path_c5b9b54f.dirname(options.pluginTarget), {
|
|
469
|
+
recursive: true
|
|
470
|
+
});
|
|
471
|
+
await __rspack_external_node_fs_promises_153e37e0.mkdir(options.pluginTarget, {
|
|
472
|
+
recursive: true
|
|
473
|
+
});
|
|
474
|
+
const runtimeEntries = [
|
|
475
|
+
'dist',
|
|
476
|
+
'hooks',
|
|
477
|
+
'.claude-plugin',
|
|
478
|
+
'README.md',
|
|
479
|
+
'package.json'
|
|
480
|
+
];
|
|
481
|
+
try {
|
|
482
|
+
for (const entry of runtimeEntries)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), {
|
|
483
|
+
recursive: true
|
|
484
|
+
});
|
|
485
|
+
const npm = 'win32' === process.platform ? 'npm.cmd' : 'npm';
|
|
486
|
+
console.log('插件依赖安装中,请稍后...');
|
|
487
|
+
try {
|
|
488
|
+
await new Promise((resolve, reject)=>{
|
|
489
|
+
const child = spawn(npm, [
|
|
490
|
+
'install',
|
|
491
|
+
'--omit=dev',
|
|
492
|
+
'--no-audit',
|
|
493
|
+
'--no-fund'
|
|
494
|
+
], {
|
|
495
|
+
cwd: options.pluginTarget,
|
|
496
|
+
stdio: 'inherit'
|
|
497
|
+
});
|
|
498
|
+
const timer = setTimeout(()=>{
|
|
499
|
+
child.kill();
|
|
500
|
+
reject(new Error("npm install timed out after 120s"));
|
|
501
|
+
}, 120000);
|
|
502
|
+
child.on('exit', (code)=>{
|
|
503
|
+
clearTimeout(timer);
|
|
504
|
+
if (0 === code) resolve();
|
|
505
|
+
else reject(new Error(`npm install exited with code ${code}`));
|
|
506
|
+
});
|
|
507
|
+
child.on('error', reject);
|
|
508
|
+
});
|
|
509
|
+
} catch (error) {
|
|
510
|
+
const detail = error && 'object' == typeof error && 'stderr' in error ? String(error.stderr || '') : error instanceof Error ? error.message : String(error);
|
|
511
|
+
throw new Error(`Failed to install plugin dependencies at ${options.pluginTarget}: ${detail}`);
|
|
512
|
+
}
|
|
513
|
+
return {
|
|
514
|
+
source,
|
|
515
|
+
target: options.pluginTarget,
|
|
516
|
+
packageSpec: tempInstall?.packageSpec,
|
|
517
|
+
version: await readPluginVersion(source)
|
|
518
|
+
};
|
|
519
|
+
} finally{
|
|
520
|
+
if (tempInstall) await __rspack_external_node_fs_promises_153e37e0.rm(tempInstall.root, {
|
|
521
|
+
recursive: true,
|
|
522
|
+
force: true
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
async function readJsonFile(filePath, fallback) {
|
|
527
|
+
try {
|
|
528
|
+
return JSON.parse(await __rspack_external_node_fs_promises_153e37e0.readFile(filePath, 'utf8'));
|
|
529
|
+
} catch (error) {
|
|
530
|
+
if ('ENOENT' === error.code) return fallback;
|
|
531
|
+
throw error;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
async function writeJsonFile(filePath, value, mode) {
|
|
535
|
+
await __rspack_external_node_fs_promises_153e37e0.mkdir(__rspack_external_node_path_c5b9b54f.dirname(filePath), {
|
|
536
|
+
recursive: true
|
|
537
|
+
});
|
|
538
|
+
if (mode) await __rspack_external_node_fs_promises_153e37e0.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, {
|
|
539
|
+
mode
|
|
540
|
+
});
|
|
541
|
+
else await __rspack_external_node_fs_promises_153e37e0.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
542
|
+
}
|
|
543
|
+
async function writeSettings(options) {
|
|
544
|
+
const settings = await readJsonFile(options.settingsPath, {});
|
|
545
|
+
if (!settings.enabledPlugins || 'object' != typeof settings.enabledPlugins || Array.isArray(settings.enabledPlugins)) settings.enabledPlugins = {};
|
|
546
|
+
if (!settings.env || 'object' != typeof settings.env || Array.isArray(settings.env)) settings.env = {};
|
|
547
|
+
settings.enabledPlugins[PLUGIN_ID] = true;
|
|
548
|
+
settings.env.CLAUDE_CODE_TLS_ENV_FILE = options.envTarget;
|
|
549
|
+
await writeJsonFile(options.settingsPath, settings);
|
|
550
|
+
}
|
|
551
|
+
function envLine(key, value) {
|
|
552
|
+
if (null == value || '' === value) return;
|
|
553
|
+
return `${key}=${String(value)}`;
|
|
554
|
+
}
|
|
555
|
+
function buildTlsEnv(options) {
|
|
556
|
+
const lines = [
|
|
557
|
+
'# Generated by tls-observer-claude-code installer.',
|
|
558
|
+
'# Keep this file local because it can contain credentials.',
|
|
559
|
+
envLine('CLAUDE_CODE_TLS_REGION', options.region),
|
|
560
|
+
envLine('CLAUDE_CODE_TLS_OTEL_ENDPOINT', options.otelEndpoint),
|
|
561
|
+
envLine('CLAUDE_CODE_TLS_TRACE_TOPIC_ID', options.traceTopicId),
|
|
562
|
+
envLine('CLAUDE_CODE_TLS_API_KEY', options.apiKey),
|
|
563
|
+
envLine('CLAUDE_CODE_TLS_AK', options.ak),
|
|
564
|
+
envLine('CLAUDE_CODE_TLS_SK', options.sk),
|
|
565
|
+
envLine('CLAUDE_CODE_TLS_AUTH_MODE', options.authMode),
|
|
566
|
+
envLine('CLAUDE_CODE_TLS_EXPORT_TIMEOUT_MS', options.exportTimeoutMs),
|
|
567
|
+
envLine('CLAUDE_CODE_TLS_EXPORT_TIMEOUT_RETRIES', options.exportTimeoutRetries),
|
|
568
|
+
envLine('CLAUDE_CODE_TLS_CAPTURE_CONTENT', options.captureContent),
|
|
569
|
+
envLine('CLAUDE_CODE_TLS_DATA_ROOT', options.dataRoot)
|
|
570
|
+
].filter(Boolean);
|
|
571
|
+
return `${lines.join('\n')}\n`;
|
|
572
|
+
}
|
|
573
|
+
async function writeTlsEnv(options) {
|
|
574
|
+
if (options.skipTlsConfig) return null;
|
|
575
|
+
if (await pathExists(options.envTarget) && !options.force) throw new Error(`TLS env target exists: ${options.envTarget}. Re-run with --force.`);
|
|
576
|
+
await writeJsonFile(__rspack_external_node_path_c5b9b54f.join(options.dataRoot, 'state', 'install.json'), {
|
|
577
|
+
installed_at: new Date().toISOString(),
|
|
578
|
+
plugin_id: PLUGIN_ID,
|
|
579
|
+
plugin_target: options.pluginTarget
|
|
580
|
+
});
|
|
581
|
+
await __rspack_external_node_fs_promises_153e37e0.mkdir(__rspack_external_node_path_c5b9b54f.dirname(options.envTarget), {
|
|
582
|
+
recursive: true
|
|
583
|
+
});
|
|
584
|
+
await __rspack_external_node_fs_promises_153e37e0.writeFile(options.envTarget, buildTlsEnv(options), {
|
|
585
|
+
mode: 384
|
|
586
|
+
});
|
|
587
|
+
return options.envTarget;
|
|
588
|
+
}
|
|
589
|
+
function printCompletion({ plugin, settingsPath, envTarget }) {
|
|
590
|
+
console.log('');
|
|
591
|
+
console.log('Install complete');
|
|
592
|
+
console.log(` Plugin: ${plugin.target}`);
|
|
593
|
+
if (plugin.packageSpec) console.log(` Plugin package: ${plugin.packageSpec}`);
|
|
594
|
+
if (plugin.version) console.log(` Plugin version: ${plugin.version}`);
|
|
595
|
+
console.log(` Settings: ${settingsPath}`);
|
|
596
|
+
console.log(` TLS env: ${envTarget || 'skipped'}`);
|
|
597
|
+
console.log('');
|
|
598
|
+
console.log('数据访问说明');
|
|
599
|
+
console.log('安装后,插件会读取会话文件');
|
|
600
|
+
console.log('会话文件中的用户请求、模型回复、工具调用记录和 token 用量会整理成诊断数据,上传到你配置的火山引擎 TLS');
|
|
601
|
+
console.log('如配置 API Key 或 AK/SK,凭据只写入本机 env 文件,用于上传鉴权');
|
|
602
|
+
console.log('');
|
|
603
|
+
console.log('Next steps:');
|
|
604
|
+
console.log(' 1. Restart Claude Code or run /reload-plugins.');
|
|
605
|
+
console.log(' 2. Run one Claude Code turn.');
|
|
606
|
+
console.log(' 3. Check TLS Trace Topic for agent.turn.');
|
|
607
|
+
}
|
|
608
|
+
async function run(argv = process.argv.slice(2)) {
|
|
609
|
+
const parsed = parseCliOptions(argv);
|
|
610
|
+
if (parsed.help) return void printHelp();
|
|
611
|
+
const options = await collectInteractiveOptions(parsed);
|
|
612
|
+
if (!options.skipTlsConfig) {
|
|
613
|
+
const resourceResult = await ensureTlsResources(options);
|
|
614
|
+
if (resourceResult.created && !options.traceTopicId) throw new Error('TLS LogApp was created but trace topic discovery failed. Pass --trace-topic-id.');
|
|
615
|
+
applyDerivedDefaults(options);
|
|
616
|
+
validateTlsConfig(options);
|
|
617
|
+
}
|
|
618
|
+
const plugin = await installPlugin(options);
|
|
619
|
+
await writeSettings(options);
|
|
620
|
+
const envTarget = await writeTlsEnv(options);
|
|
621
|
+
printCompletion({
|
|
622
|
+
plugin,
|
|
623
|
+
settingsPath: options.settingsPath,
|
|
624
|
+
envTarget
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
export { run };
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@volcengine/tls-observer-claude-code-install",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "One-step installer for the TLS Observer Claude Code plugin.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"tls-observer-claude-code-install": "bin/tls-observer-claude-code-install.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"dist",
|
|
12
|
+
"src",
|
|
13
|
+
"scripts",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"CHANGELOG.md"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "rslib build",
|
|
20
|
+
"check": "tsc --noEmit",
|
|
21
|
+
"format": "prettier --write \"bin/**/*.mjs\" \"scripts/**/*.mjs\" \"src/**/*.ts\" \"*.json\"",
|
|
22
|
+
"format:check": "prettier --check \"bin/**/*.mjs\" \"scripts/**/*.mjs\" \"src/**/*.ts\" \"*.json\"",
|
|
23
|
+
"sync-plugin": "node scripts/sync-plugin.mjs"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@volcengine/openapi": "^1.36.1"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@rslib/core": "^0.21.5",
|
|
33
|
+
"@types/node": "^24.10.9",
|
|
34
|
+
"prettier": "^3.4.2",
|
|
35
|
+
"typescript": "^6.0.3"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"registry": "https://registry.npmjs.org/",
|
|
39
|
+
"access": "public"
|
|
40
|
+
}
|
|
41
|
+
}
|