@afffun/codexbot 1.0.72 → 1.0.74
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/package.json +1 -1
- package/src/activation_service.mjs +262 -0
- package/src/args_service.mjs +83 -3
- package/src/config_service.mjs +11 -0
- package/src/entrypoint.mjs +16 -0
- package/src/install_seed_service.mjs +16 -10
- package/src/install_service.mjs +21 -1
package/package.json
CHANGED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import fsSyncDefault from 'node:fs';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
6
|
+
import readline from 'node:readline/promises';
|
|
7
|
+
|
|
8
|
+
import { buildInstallLicenseClaimUrl, getDefaultDistributionConfig } from './config_service.mjs';
|
|
9
|
+
import { resolveInstalledControlLayout } from './installed_layout_service.mjs';
|
|
10
|
+
|
|
11
|
+
function trim(value, fallback = '') {
|
|
12
|
+
const text = String(value ?? '').trim();
|
|
13
|
+
return text || String(fallback ?? '').trim();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function createPrompter({ stdin, stdout } = {}) {
|
|
17
|
+
const input = stdin || process.stdin;
|
|
18
|
+
const output = stdout || process.stdout;
|
|
19
|
+
if (!input?.isTTY || !output?.isTTY) {
|
|
20
|
+
return {
|
|
21
|
+
interactive: false,
|
|
22
|
+
ask: async () => '',
|
|
23
|
+
close: async () => {},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const rl = readline.createInterface({ input, output });
|
|
27
|
+
return {
|
|
28
|
+
interactive: true,
|
|
29
|
+
ask: async (prompt) => trim(await rl.question(prompt)),
|
|
30
|
+
close: async () => {
|
|
31
|
+
rl.close();
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function runChildCapture(spawnFn, command, args, options = {}) {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
const child = spawnFn(command, args, {
|
|
39
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
40
|
+
...options,
|
|
41
|
+
});
|
|
42
|
+
let stdout = '';
|
|
43
|
+
let stderr = '';
|
|
44
|
+
child.stdout?.on('data', (chunk) => {
|
|
45
|
+
stdout += Buffer.from(chunk).toString('utf8');
|
|
46
|
+
});
|
|
47
|
+
child.stderr?.on('data', (chunk) => {
|
|
48
|
+
stderr += Buffer.from(chunk).toString('utf8');
|
|
49
|
+
});
|
|
50
|
+
child.on('error', reject);
|
|
51
|
+
child.on('exit', (code, signal) => {
|
|
52
|
+
if (signal) {
|
|
53
|
+
reject(new Error(`${command} exited via signal ${signal}`));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
resolve({ code: Number(code || 0), stdout, stderr });
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function runChildInherit(spawnFn, command, args, options = {}) {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const child = spawnFn(command, args, {
|
|
64
|
+
stdio: 'inherit',
|
|
65
|
+
...options,
|
|
66
|
+
});
|
|
67
|
+
child.on('error', reject);
|
|
68
|
+
child.on('exit', (code, signal) => {
|
|
69
|
+
if (signal) {
|
|
70
|
+
reject(new Error(`${command} exited via signal ${signal}`));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
resolve(Number(code || 0));
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function resolveControlLayout({ fsSync, pathModule, processImpl, options }) {
|
|
79
|
+
return resolveInstalledControlLayout({
|
|
80
|
+
fsSync,
|
|
81
|
+
pathModule,
|
|
82
|
+
env: processImpl.env,
|
|
83
|
+
profile: options.layoutProfile,
|
|
84
|
+
appDir: options.appDir,
|
|
85
|
+
controlEnvFile: options.controlEnvFile,
|
|
86
|
+
controlUser: options.controlUser,
|
|
87
|
+
codexHomeDir: options.codexHomeDir,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function readJsonResponse(response) {
|
|
92
|
+
const text = await response.text();
|
|
93
|
+
try {
|
|
94
|
+
return text ? JSON.parse(text) : {};
|
|
95
|
+
} catch {
|
|
96
|
+
return {};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function buildClaimFailureMessage(error, claimUrl) {
|
|
101
|
+
const lines = [trim(error?.message, 'license claim failed')];
|
|
102
|
+
lines.push('');
|
|
103
|
+
lines.push('The node remains installed but activation is still pending.');
|
|
104
|
+
lines.push(`Retry with: codexbot activate --license-key '<your-license-key>' --license-claim-url '${claimUrl}'`);
|
|
105
|
+
lines.push('Or complete activation manually after obtaining an activation package.');
|
|
106
|
+
return lines.join('\n');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function createNpmDistributionActivationService(deps = {}) {
|
|
110
|
+
const fsPromises = deps.fsPromises || fs;
|
|
111
|
+
const fsSync = deps.fsSync || fsSyncDefault;
|
|
112
|
+
const osModule = deps.osModule || os;
|
|
113
|
+
const pathModule = deps.pathModule || path;
|
|
114
|
+
const spawnFn = deps.spawnFn || spawn;
|
|
115
|
+
const processImpl = deps.processImpl || process;
|
|
116
|
+
const fetchFn = deps.fetchFn || fetch;
|
|
117
|
+
const logger = deps.logger || console;
|
|
118
|
+
const promptFactory = deps.promptFactory || createPrompter;
|
|
119
|
+
|
|
120
|
+
async function resolveLicenseKey(options = {}) {
|
|
121
|
+
const explicit = trim(options.licenseKey);
|
|
122
|
+
if (explicit) return explicit;
|
|
123
|
+
const prompter = promptFactory({
|
|
124
|
+
stdin: processImpl.stdin,
|
|
125
|
+
stdout: processImpl.stdout,
|
|
126
|
+
});
|
|
127
|
+
try {
|
|
128
|
+
if (Boolean(options.nonInteractive) || !prompter.interactive) {
|
|
129
|
+
throw new Error('license key is required');
|
|
130
|
+
}
|
|
131
|
+
const entered = trim(await prompter.ask('License key: '));
|
|
132
|
+
if (!entered) throw new Error('license key is required');
|
|
133
|
+
return entered;
|
|
134
|
+
} finally {
|
|
135
|
+
await prompter.close().catch(() => {});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function readInstalledNodeIdentity(layout, options = {}) {
|
|
140
|
+
const scriptPath = pathModule.join(layout.appDir, 'scripts', 'codexbot_node_identity.sh');
|
|
141
|
+
const isRoot = typeof processImpl.getuid === 'function' ? processImpl.getuid() === 0 : false;
|
|
142
|
+
const command = isRoot ? 'bash' : 'sudo';
|
|
143
|
+
const commandArgs = isRoot
|
|
144
|
+
? [scriptPath, '--control-env-file', layout.controlEnvFile]
|
|
145
|
+
: ['bash', scriptPath, '--control-env-file', layout.controlEnvFile];
|
|
146
|
+
const result = await runChildCapture(spawnFn, command, commandArgs);
|
|
147
|
+
if (result.code !== 0) {
|
|
148
|
+
throw new Error(trim(result.stderr || result.stdout, 'failed to read node identity'));
|
|
149
|
+
}
|
|
150
|
+
let payload = {};
|
|
151
|
+
try {
|
|
152
|
+
payload = JSON.parse(result.stdout);
|
|
153
|
+
} catch {
|
|
154
|
+
throw new Error('node identity output is not valid JSON');
|
|
155
|
+
}
|
|
156
|
+
return payload;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function fetchActivationPackage({ claimUrl, licenseKey, identity }) {
|
|
160
|
+
const response = await fetchFn(claimUrl, {
|
|
161
|
+
method: 'POST',
|
|
162
|
+
headers: {
|
|
163
|
+
'content-type': 'application/json',
|
|
164
|
+
'user-agent': 'codexbot-npm-cli/1.0',
|
|
165
|
+
},
|
|
166
|
+
body: JSON.stringify({
|
|
167
|
+
licenseKey,
|
|
168
|
+
fingerprint: trim(identity?.machineFingerprint),
|
|
169
|
+
node: {
|
|
170
|
+
id: trim(identity?.nodeId),
|
|
171
|
+
...(identity?.payload && typeof identity.payload === 'object' && !Array.isArray(identity.payload) ? identity.payload : {}),
|
|
172
|
+
},
|
|
173
|
+
}),
|
|
174
|
+
});
|
|
175
|
+
const payload = await readJsonResponse(response);
|
|
176
|
+
if (!response.ok) {
|
|
177
|
+
const err = new Error(trim(payload?.error?.message || payload?.message, `license claim http ${response.status}`));
|
|
178
|
+
err.code = trim(payload?.error?.code || payload?.code, `HTTP_${response.status}`);
|
|
179
|
+
err.statusCode = response.status;
|
|
180
|
+
err.payload = payload;
|
|
181
|
+
throw err;
|
|
182
|
+
}
|
|
183
|
+
return payload;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function writeActivationPackage(tempDir, activation = {}) {
|
|
187
|
+
const licenseFile = pathModule.join(tempDir, 'license.json');
|
|
188
|
+
const licensePublicKeyFile = pathModule.join(tempDir, 'license-public.pem');
|
|
189
|
+
const licenseServerPublicKeyFile = pathModule.join(tempDir, 'license-server-public.pem');
|
|
190
|
+
await fsPromises.writeFile(licenseFile, `${JSON.stringify(activation.license || {}, null, 2)}\n`, 'utf8');
|
|
191
|
+
await fsPromises.writeFile(licensePublicKeyFile, `${trim(activation.licensePublicKey)}\n`, 'utf8');
|
|
192
|
+
await fsPromises.writeFile(licenseServerPublicKeyFile, `${trim(activation.licenseServerPublicKey)}\n`, 'utf8');
|
|
193
|
+
return {
|
|
194
|
+
licenseFile,
|
|
195
|
+
licensePublicKeyFile,
|
|
196
|
+
licenseServerPublicKeyFile,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function runLocalActivationScript(layout, files) {
|
|
201
|
+
const scriptPath = pathModule.join(layout.appDir, 'scripts', 'codexbot_activate.sh');
|
|
202
|
+
const isRoot = typeof processImpl.getuid === 'function' ? processImpl.getuid() === 0 : false;
|
|
203
|
+
const baseArgs = [
|
|
204
|
+
scriptPath,
|
|
205
|
+
'--control-env-file', layout.controlEnvFile,
|
|
206
|
+
'--license-file', files.licenseFile,
|
|
207
|
+
'--license-public-key-file', files.licensePublicKeyFile,
|
|
208
|
+
'--license-server-public-key-file', files.licenseServerPublicKeyFile,
|
|
209
|
+
'--start',
|
|
210
|
+
];
|
|
211
|
+
const command = isRoot ? 'bash' : 'sudo';
|
|
212
|
+
const commandArgs = isRoot ? baseArgs : ['bash', ...baseArgs];
|
|
213
|
+
return runChildInherit(spawnFn, command, commandArgs);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function claimAndActivate(options = {}) {
|
|
217
|
+
const defaults = getDefaultDistributionConfig(processImpl.env);
|
|
218
|
+
const licenseKey = await resolveLicenseKey(options);
|
|
219
|
+
const claimUrl = buildInstallLicenseClaimUrl({
|
|
220
|
+
installBaseUrl: options.installBaseUrl || defaults.installBaseUrl,
|
|
221
|
+
claimUrl: options.licenseClaimUrl || defaults.licenseClaimUrl,
|
|
222
|
+
allowHttp: Boolean(options.allowHttp),
|
|
223
|
+
});
|
|
224
|
+
const layout = resolveControlLayout({
|
|
225
|
+
fsSync,
|
|
226
|
+
pathModule,
|
|
227
|
+
processImpl,
|
|
228
|
+
options,
|
|
229
|
+
});
|
|
230
|
+
const tempDir = await fsPromises.mkdtemp(pathModule.join(osModule.tmpdir(), 'codexbot-activation-'));
|
|
231
|
+
try {
|
|
232
|
+
logger.log('==> Read node identity');
|
|
233
|
+
const identity = await readInstalledNodeIdentity(layout, options);
|
|
234
|
+
logger.log(`==> Claim activation package from ${claimUrl}`);
|
|
235
|
+
const claim = await fetchActivationPackage({ claimUrl, licenseKey, identity });
|
|
236
|
+
const activation = claim?.activation || {};
|
|
237
|
+
if (!activation.license || !trim(activation.licensePublicKey) || !trim(activation.licenseServerPublicKey)) {
|
|
238
|
+
throw new Error('claim response is missing activation package files');
|
|
239
|
+
}
|
|
240
|
+
const files = await writeActivationPackage(tempDir, activation);
|
|
241
|
+
logger.log('==> Activate installed node');
|
|
242
|
+
const exitCode = await runLocalActivationScript(layout, files);
|
|
243
|
+
if (exitCode !== 0) {
|
|
244
|
+
throw new Error(`activation script exited with code ${exitCode}`);
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
ok: true,
|
|
248
|
+
claim,
|
|
249
|
+
};
|
|
250
|
+
} catch (error) {
|
|
251
|
+
const wrapped = new Error(buildClaimFailureMessage(error, claimUrl));
|
|
252
|
+
wrapped.cause = error;
|
|
253
|
+
throw wrapped;
|
|
254
|
+
} finally {
|
|
255
|
+
await fsPromises.rm(tempDir, { recursive: true, force: true }).catch(() => {});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
claimAndActivate,
|
|
261
|
+
};
|
|
262
|
+
}
|
package/src/args_service.mjs
CHANGED
|
@@ -30,6 +30,8 @@ function parseBootstrapOptions(argv = []) {
|
|
|
30
30
|
wecomTencentDocsApiKey: '',
|
|
31
31
|
openAiApiKey: '',
|
|
32
32
|
deferLicenseActivation: false,
|
|
33
|
+
licenseKey: '',
|
|
34
|
+
licenseClaimUrl: '',
|
|
33
35
|
licenseFile: '',
|
|
34
36
|
licensePublicKeyFile: '',
|
|
35
37
|
licenseServerPublicKeyFile: '',
|
|
@@ -130,6 +132,16 @@ function parseBootstrapOptions(argv = []) {
|
|
|
130
132
|
options.deferLicenseActivation = true;
|
|
131
133
|
continue;
|
|
132
134
|
}
|
|
135
|
+
if (token === '--license-key') {
|
|
136
|
+
options.licenseKey = readOptionValue(argv, i, token);
|
|
137
|
+
i += 1;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (token === '--license-claim-url') {
|
|
141
|
+
options.licenseClaimUrl = readOptionValue(argv, i, token);
|
|
142
|
+
i += 1;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
133
145
|
if (token === '--license-file') {
|
|
134
146
|
options.licenseFile = readOptionValue(argv, i, token);
|
|
135
147
|
i += 1;
|
|
@@ -171,7 +183,7 @@ function parseBootstrapOptions(argv = []) {
|
|
|
171
183
|
return options;
|
|
172
184
|
}
|
|
173
185
|
|
|
174
|
-
function
|
|
186
|
+
function parseLayoutOptions(argv = []) {
|
|
175
187
|
const options = {
|
|
176
188
|
layoutProfile: '',
|
|
177
189
|
appDir: '',
|
|
@@ -212,6 +224,71 @@ function parseAuthOptions(argv = []) {
|
|
|
212
224
|
return options;
|
|
213
225
|
}
|
|
214
226
|
|
|
227
|
+
function parseActivationOptions(argv = []) {
|
|
228
|
+
const options = {
|
|
229
|
+
...parseLayoutOptions([]),
|
|
230
|
+
installBaseUrl: '',
|
|
231
|
+
licenseClaimUrl: '',
|
|
232
|
+
allowHttp: false,
|
|
233
|
+
nonInteractive: false,
|
|
234
|
+
licenseKey: '',
|
|
235
|
+
};
|
|
236
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
237
|
+
const token = trim(argv[i]);
|
|
238
|
+
if (!token) continue;
|
|
239
|
+
if (token === '--license-key') {
|
|
240
|
+
options.licenseKey = readOptionValue(argv, i, token);
|
|
241
|
+
i += 1;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (token === '--install-base-url') {
|
|
245
|
+
options.installBaseUrl = readOptionValue(argv, i, token);
|
|
246
|
+
i += 1;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (token === '--license-claim-url') {
|
|
250
|
+
options.licenseClaimUrl = readOptionValue(argv, i, token);
|
|
251
|
+
i += 1;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (token === '--allow-http') {
|
|
255
|
+
options.allowHttp = true;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (token === '--non-interactive' || token === '--no-prompt') {
|
|
259
|
+
options.nonInteractive = true;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (token === '--layout-profile') {
|
|
263
|
+
options.layoutProfile = readOptionValue(argv, i, token);
|
|
264
|
+
i += 1;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (token === '--app-dir') {
|
|
268
|
+
options.appDir = readOptionValue(argv, i, token);
|
|
269
|
+
i += 1;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (token === '--control-env-file') {
|
|
273
|
+
options.controlEnvFile = readOptionValue(argv, i, token);
|
|
274
|
+
i += 1;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (token === '--control-user') {
|
|
278
|
+
options.controlUser = readOptionValue(argv, i, token);
|
|
279
|
+
i += 1;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (token === '--codex-home-dir') {
|
|
283
|
+
options.codexHomeDir = readOptionValue(argv, i, token);
|
|
284
|
+
i += 1;
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
throw new Error(`unknown activate arg: ${token}`);
|
|
288
|
+
}
|
|
289
|
+
return options;
|
|
290
|
+
}
|
|
291
|
+
|
|
215
292
|
export function parseCliArgs(argv = []) {
|
|
216
293
|
const args = Array.isArray(argv) ? argv.map((item) => String(item || '')) : [];
|
|
217
294
|
const command = trim(args[0]).toLowerCase();
|
|
@@ -222,7 +299,7 @@ export function parseCliArgs(argv = []) {
|
|
|
222
299
|
return { action: 'version' };
|
|
223
300
|
}
|
|
224
301
|
if (command === 'doctor') {
|
|
225
|
-
return { action: 'doctor', options:
|
|
302
|
+
return { action: 'doctor', options: parseLayoutOptions(args.slice(1)) };
|
|
226
303
|
}
|
|
227
304
|
if (command === 'install') {
|
|
228
305
|
return { action: 'install', options: parseBootstrapOptions(args.slice(1)) };
|
|
@@ -230,12 +307,15 @@ export function parseCliArgs(argv = []) {
|
|
|
230
307
|
if (command === 'upgrade') {
|
|
231
308
|
return { action: 'upgrade', options: parseBootstrapOptions(args.slice(1)) };
|
|
232
309
|
}
|
|
310
|
+
if (command === 'activate') {
|
|
311
|
+
return { action: 'activate', options: parseActivationOptions(args.slice(1)) };
|
|
312
|
+
}
|
|
233
313
|
if (command === 'auth') {
|
|
234
314
|
const sub = trim(args[1]).toLowerCase() || 'status';
|
|
235
315
|
if (!['status', 'login', 'login-link', 'logout'].includes(sub)) {
|
|
236
316
|
throw new Error(`unknown auth subcommand: ${sub}`);
|
|
237
317
|
}
|
|
238
|
-
return { action: 'auth', mode: sub, options:
|
|
318
|
+
return { action: 'auth', mode: sub, options: parseLayoutOptions(args.slice(2)) };
|
|
239
319
|
}
|
|
240
320
|
throw new Error(`unknown command: ${command}`);
|
|
241
321
|
}
|
package/src/config_service.mjs
CHANGED
|
@@ -33,9 +33,20 @@ export function getDefaultDistributionConfig(env = process.env) {
|
|
|
33
33
|
updateBaseUrl: normalizeBaseUrl(env.CODEXBOT_UPDATE_BASE_URL, DEFAULT_UPDATE_BASE_URL),
|
|
34
34
|
channel: normalizeChannel(env.CODEXBOT_INSTALL_CHANNEL, DEFAULT_CHANNEL),
|
|
35
35
|
authToken: trim(env.CODEXBOT_UPDATE_TOKEN || env.CODEX_REMOTE_UPDATE_AUTH_TOKEN, ''),
|
|
36
|
+
licenseClaimUrl: trim(env.CODEXBOT_LICENSE_CLAIM_URL, ''),
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
export function buildInstallLicenseClaimUrl({
|
|
41
|
+
installBaseUrl = DEFAULT_INSTALL_BASE_URL,
|
|
42
|
+
claimUrl = '',
|
|
43
|
+
allowHttp = false,
|
|
44
|
+
} = {}) {
|
|
45
|
+
const resolvedInstallBaseUrl = normalizeBaseUrl(installBaseUrl, DEFAULT_INSTALL_BASE_URL);
|
|
46
|
+
const resolvedClaimUrl = trim(claimUrl) || `${resolvedInstallBaseUrl}/v1/install/license/claim`;
|
|
47
|
+
return assertHttps(resolvedClaimUrl, { allowHttp, label: 'license claim url' });
|
|
48
|
+
}
|
|
49
|
+
|
|
39
50
|
export function buildPublicInstallUrls({
|
|
40
51
|
installBaseUrl = DEFAULT_INSTALL_BASE_URL,
|
|
41
52
|
updateBaseUrl = DEFAULT_UPDATE_BASE_URL,
|
package/src/entrypoint.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { parseCliArgs } from './args_service.mjs';
|
|
4
|
+
import { createNpmDistributionActivationService } from './activation_service.mjs';
|
|
4
5
|
import { createNpmDistributionInstallService } from './install_service.mjs';
|
|
5
6
|
import { createNpmDistributionAuthService } from './auth_service.mjs';
|
|
6
7
|
|
|
@@ -16,6 +17,7 @@ function renderUsage() {
|
|
|
16
17
|
'Usage:',
|
|
17
18
|
' codexbot install [bootstrap options] [install-remote flags...]',
|
|
18
19
|
' codexbot upgrade [bootstrap options] [install-remote flags...]',
|
|
20
|
+
' codexbot activate [activation options]',
|
|
19
21
|
' codexbot auth status|login|login-link|logout [layout overrides]',
|
|
20
22
|
' codexbot doctor [layout overrides]',
|
|
21
23
|
' codexbot version',
|
|
@@ -32,6 +34,8 @@ function renderUsage() {
|
|
|
32
34
|
' --wecom-secret <secret>',
|
|
33
35
|
' --wecom-tencent-docs-api-key <apiKey>',
|
|
34
36
|
' --openai-api-key <key>',
|
|
37
|
+
' --license-key <key>',
|
|
38
|
+
' install will auto-claim and activate when a license key is provided.',
|
|
35
39
|
' --license-file </path/to/license.json>',
|
|
36
40
|
' --license-public-key-file </path/to/license-public.pem>',
|
|
37
41
|
' --license-server-public-key-file </path/to/license-server-public.pem>',
|
|
@@ -45,6 +49,13 @@ function renderUsage() {
|
|
|
45
49
|
' --token <protected update server token>',
|
|
46
50
|
' --allow-http',
|
|
47
51
|
'',
|
|
52
|
+
'Activation options:',
|
|
53
|
+
' --license-key <key>',
|
|
54
|
+
' --install-base-url <https://codexbotinstall.example.com>',
|
|
55
|
+
' --license-claim-url <https://codexbotinstall.example.com/v1/install/license/claim>',
|
|
56
|
+
' --non-interactive',
|
|
57
|
+
' --allow-http',
|
|
58
|
+
'',
|
|
48
59
|
'Layout overrides:',
|
|
49
60
|
' --layout-profile <split|legacy>',
|
|
50
61
|
' --app-dir <path>',
|
|
@@ -57,6 +68,7 @@ function renderUsage() {
|
|
|
57
68
|
export async function runCodexbotCli(argv = [], deps = {}) {
|
|
58
69
|
const logger = deps.logger || console;
|
|
59
70
|
const installService = deps.installService || createNpmDistributionInstallService({ logger });
|
|
71
|
+
const activationService = deps.activationService || createNpmDistributionActivationService({ logger });
|
|
60
72
|
const authService = deps.authService || createNpmDistributionAuthService({ logger });
|
|
61
73
|
const cli = parseCliArgs(argv);
|
|
62
74
|
if (cli.action === 'help') {
|
|
@@ -86,6 +98,10 @@ export async function runCodexbotCli(argv = [], deps = {}) {
|
|
|
86
98
|
if (cli.action === 'upgrade') {
|
|
87
99
|
return installService.runUpgradeCommand(cli.options || {});
|
|
88
100
|
}
|
|
101
|
+
if (cli.action === 'activate') {
|
|
102
|
+
await activationService.claimAndActivate(cli.options || {});
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
89
105
|
if (cli.action === 'auth') {
|
|
90
106
|
return authService.runAuthCommand(cli.mode, cli.options || {});
|
|
91
107
|
}
|
|
@@ -283,6 +283,7 @@ export function createNpmDistributionInstallSeedService(deps = {}) {
|
|
|
283
283
|
seedLicenseFile: '',
|
|
284
284
|
seedLicensePublicKeyFile: '',
|
|
285
285
|
seedLicenseServerPublicKeyFile: '',
|
|
286
|
+
licenseKey: trim(options.licenseKey),
|
|
286
287
|
deferLicenseActivation: Boolean(options.deferLicenseActivation),
|
|
287
288
|
summary: {
|
|
288
289
|
connector: 'skip',
|
|
@@ -294,6 +295,9 @@ export function createNpmDistributionInstallSeedService(deps = {}) {
|
|
|
294
295
|
};
|
|
295
296
|
|
|
296
297
|
if (explicitSeedFiles) {
|
|
298
|
+
if (result.licenseKey) {
|
|
299
|
+
throw new Error('license key cannot be combined with explicit license seed files');
|
|
300
|
+
}
|
|
297
301
|
result.seedLicenseFile = trim(options.licenseFile);
|
|
298
302
|
result.seedLicensePublicKeyFile = trim(options.licensePublicKeyFile);
|
|
299
303
|
result.seedLicenseServerPublicKeyFile = trim(options.licenseServerPublicKeyFile);
|
|
@@ -373,22 +377,24 @@ export function createNpmDistributionInstallSeedService(deps = {}) {
|
|
|
373
377
|
result.summary.connectorSeeded = true;
|
|
374
378
|
}
|
|
375
379
|
|
|
380
|
+
let licenseKey = result.licenseKey;
|
|
376
381
|
let licenseFile = trim(options.licenseFile);
|
|
377
382
|
let licensePublicKeyFile = trim(options.licensePublicKeyFile);
|
|
378
383
|
let licenseServerPublicKeyFile = trim(options.licenseServerPublicKeyFile);
|
|
379
|
-
if (
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
result.deferLicenseActivation = true;
|
|
387
|
-
}
|
|
388
|
-
} else if (!licenseFile && !licensePublicKeyFile && !licenseServerPublicKeyFile) {
|
|
384
|
+
if (licenseKey && (licenseFile || licensePublicKeyFile || licenseServerPublicKeyFile)) {
|
|
385
|
+
throw new Error('license key cannot be combined with direct license file inputs');
|
|
386
|
+
}
|
|
387
|
+
if (!licenseKey && !licenseFile && !licensePublicKeyFile && !licenseServerPublicKeyFile && !nonInteractive) {
|
|
388
|
+
licenseKey = trim(await prompter.ask('License key (optional, blank to skip and activate later): '));
|
|
389
|
+
}
|
|
390
|
+
if (!licenseKey && !licenseFile && !licensePublicKeyFile && !licenseServerPublicKeyFile) {
|
|
389
391
|
result.deferLicenseActivation = true;
|
|
390
392
|
}
|
|
391
393
|
|
|
394
|
+
if (licenseKey) {
|
|
395
|
+
result.licenseKey = licenseKey;
|
|
396
|
+
result.deferLicenseActivation = true;
|
|
397
|
+
}
|
|
392
398
|
const licenseProvided = Boolean(licenseFile || licensePublicKeyFile || licenseServerPublicKeyFile);
|
|
393
399
|
if (licenseProvided) {
|
|
394
400
|
result.seedLicenseFile = await ensureReadableFile(fsPromises, licenseFile, 'license file');
|
package/src/install_service.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
buildRemoteInstallUrls,
|
|
8
8
|
getDefaultDistributionConfig,
|
|
9
9
|
} from './config_service.mjs';
|
|
10
|
+
import { createNpmDistributionActivationService } from './activation_service.mjs';
|
|
10
11
|
import { createNpmDistributionInstallSeedService } from './install_seed_service.mjs';
|
|
11
12
|
|
|
12
13
|
function trim(value, fallback = '') {
|
|
@@ -116,6 +117,14 @@ export function createNpmDistributionInstallService(deps = {}) {
|
|
|
116
117
|
processImpl,
|
|
117
118
|
logger,
|
|
118
119
|
});
|
|
120
|
+
const activationService = deps.activationService || createNpmDistributionActivationService({
|
|
121
|
+
fsPromises,
|
|
122
|
+
pathModule,
|
|
123
|
+
spawnFn,
|
|
124
|
+
processImpl,
|
|
125
|
+
fetchFn,
|
|
126
|
+
logger,
|
|
127
|
+
});
|
|
119
128
|
|
|
120
129
|
async function runRemoteInstall({
|
|
121
130
|
mode,
|
|
@@ -151,6 +160,7 @@ export function createNpmDistributionInstallService(deps = {}) {
|
|
|
151
160
|
effectiveOptions.licenseFile = trim(installSeed.seedLicenseFile, effectiveOptions.licenseFile);
|
|
152
161
|
effectiveOptions.licensePublicKeyFile = trim(installSeed.seedLicensePublicKeyFile, effectiveOptions.licensePublicKeyFile);
|
|
153
162
|
effectiveOptions.licenseServerPublicKeyFile = trim(installSeed.seedLicenseServerPublicKeyFile, effectiveOptions.licenseServerPublicKeyFile);
|
|
163
|
+
effectiveOptions.licenseKey = trim(installSeed.licenseKey, effectiveOptions.licenseKey);
|
|
154
164
|
effectiveOptions.deferLicenseActivation = Boolean(installSeed.deferLicenseActivation || effectiveOptions.deferLicenseActivation);
|
|
155
165
|
}
|
|
156
166
|
const authToken = trim(effectiveOptions.token, defaults.authToken);
|
|
@@ -211,7 +221,17 @@ export function createNpmDistributionInstallService(deps = {}) {
|
|
|
211
221
|
const isRoot = typeof processImpl.getuid === 'function' ? processImpl.getuid() === 0 : false;
|
|
212
222
|
const command = isRoot ? 'bash' : 'sudo';
|
|
213
223
|
const commandArgs = isRoot ? args : ['bash', ...args];
|
|
214
|
-
|
|
224
|
+
const exitCode = await runChild(spawnFn, command, commandArgs);
|
|
225
|
+
if (distributionMode === 'install' && exitCode === 0 && trim(effectiveOptions.licenseKey)) {
|
|
226
|
+
await activationService.claimAndActivate({
|
|
227
|
+
licenseKey: effectiveOptions.licenseKey,
|
|
228
|
+
installBaseUrl: urls.installBaseUrl,
|
|
229
|
+
licenseClaimUrl: effectiveOptions.licenseClaimUrl,
|
|
230
|
+
allowHttp: Boolean(effectiveOptions.allowHttp),
|
|
231
|
+
nonInteractive: true,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return exitCode;
|
|
215
235
|
} finally {
|
|
216
236
|
if (installSeed?.cleanup) {
|
|
217
237
|
await installSeed.cleanup().catch(() => {});
|