@afffun/codexbot 1.0.94 → 1.0.96

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afffun/codexbot",
3
- "version": "1.0.94",
3
+ "version": "1.0.96",
4
4
  "description": "Thin npm bootstrap CLI for installing and operating Codexbot nodes",
5
5
  "type": "module",
6
6
  "author": "john88188 <john88188@outlook.com>",
@@ -36,6 +36,7 @@ function renderUsage() {
36
36
  ' --openai-api-key <key>',
37
37
  ' --license-key <key>',
38
38
  ' install will auto-claim and activate when a license key is provided.',
39
+ ' on an existing install, current connector/API key/license settings are preserved unless explicit replacement flags are provided.',
39
40
  ' --license-file </path/to/license.json>',
40
41
  ' --license-public-key-file </path/to/license-public.pem>',
41
42
  ' --license-server-public-key-file </path/to/license-server-public.pem>',
@@ -1,4 +1,5 @@
1
1
  import fs from 'node:fs/promises';
2
+ import fsSync from 'node:fs';
2
3
  import os from 'node:os';
3
4
  import path from 'node:path';
4
5
  import {
@@ -9,6 +10,7 @@ import {
9
10
  promptUntilValid,
10
11
  trim,
11
12
  } from './input_contract_service.mjs';
13
+ import { resolveInstalledControlLayout } from './installed_layout_service.mjs';
12
14
 
13
15
  async function promptForConnectorChoice(prompter, logger) {
14
16
  return promptUntilValid(prompter, {
@@ -218,12 +220,32 @@ function renderEnvFile(values = {}) {
218
220
 
219
221
  export function createNpmDistributionInstallSeedService(deps = {}) {
220
222
  const fsPromises = deps.fsPromises || fs;
223
+ const fsSyncModule = deps.fsSyncModule || fsSync;
221
224
  const osModule = deps.osModule || os;
222
225
  const pathModule = deps.pathModule || path;
223
226
  const processImpl = deps.processImpl || process;
224
227
  const logger = deps.logger || console;
225
228
  const nowIso = deps.nowIso || (() => new Date().toISOString());
226
229
  const promptFactory = deps.promptFactory || createPrompter;
230
+ const resolveInstalledLayout = deps.resolveInstalledLayout || resolveInstalledControlLayout;
231
+
232
+ function detectExistingInstall() {
233
+ try {
234
+ const layout = resolveInstalledLayout({
235
+ fsSync: fsSyncModule,
236
+ pathModule,
237
+ env: processImpl.env,
238
+ });
239
+ const hasControlEnv = Boolean(layout.controlEnvFile) && fsSyncModule.existsSync(layout.controlEnvFile);
240
+ const hasAppDir = Boolean(layout.appDir) && fsSyncModule.existsSync(layout.appDir);
241
+ if (!hasControlEnv && !hasAppDir) {
242
+ return { installed: false, layout };
243
+ }
244
+ return { installed: true, layout };
245
+ } catch {
246
+ return { installed: false, layout: null };
247
+ }
248
+ }
227
249
 
228
250
  async function prepareInstallSeed(options = {}) {
229
251
  const explicitSeedFiles = Boolean(
@@ -243,6 +265,8 @@ export function createNpmDistributionInstallSeedService(deps = {}) {
243
265
  };
244
266
 
245
267
  try {
268
+ const installState = detectExistingInstall();
269
+ const existingInstall = Boolean(installState.installed);
246
270
  const result = {
247
271
  seedEnvFile: trim(options.seedEnvFile),
248
272
  seedConnectorsStateFile: trim(options.seedConnectorsStateFile),
@@ -257,6 +281,7 @@ export function createNpmDistributionInstallSeedService(deps = {}) {
257
281
  connectorSeeded: false,
258
282
  openAiSeeded: false,
259
283
  licenseDeferred: false,
284
+ existingInstall,
260
285
  },
261
286
  cleanup,
262
287
  };
@@ -290,13 +315,22 @@ export function createNpmDistributionInstallSeedService(deps = {}) {
290
315
  if (!connector && hasExplicitConnectorArgs) {
291
316
  connector = trim(options.wecomBotId || options.wecomSecret || options.wecomTencentDocsApiKey) ? 'wecom' : 'telegram';
292
317
  }
293
- if (!connector && !nonInteractive) {
318
+ const preserveExistingConnector = existingInstall && !hasExplicitConnectorArgs;
319
+ if (preserveExistingConnector) {
320
+ logger.log('==> Existing install detected; keeping current connector settings');
321
+ } else if (!connector && !nonInteractive) {
294
322
  logger.log('==> Remote control channel setup');
295
323
  connector = await promptForConnectorChoice(prompter, logger);
296
324
  }
297
325
  if (!connector) connector = 'skip';
298
326
 
299
- const openAiApiKey = trim(options.openAiApiKey || (!nonInteractive ? await prompter.ask('OPENAI_API_KEY (optional, blank to skip): ') : ''));
327
+ const preserveExistingOpenAi = existingInstall && !trim(options.openAiApiKey);
328
+ if (preserveExistingOpenAi) {
329
+ logger.log('==> Existing install detected; keeping current OpenAI API key setting');
330
+ }
331
+ const openAiApiKey = preserveExistingOpenAi
332
+ ? ''
333
+ : trim(options.openAiApiKey || (!nonInteractive ? await prompter.ask('OPENAI_API_KEY (optional, blank to skip): ') : ''));
300
334
  if (openAiApiKey) {
301
335
  const envFile = pathModule.join(tempDir, 'seed.env');
302
336
  await fsPromises.writeFile(envFile, renderEnvFile({ OPENAI_API_KEY: openAiApiKey }), 'utf8');
@@ -304,7 +338,9 @@ export function createNpmDistributionInstallSeedService(deps = {}) {
304
338
  result.summary.openAiSeeded = true;
305
339
  }
306
340
 
307
- if (connector === 'telegram') {
341
+ if (preserveExistingConnector) {
342
+ result.summary.connector = 'preserve';
343
+ } else if (connector === 'telegram') {
308
344
  const telegramBotToken = trim(options.telegramBotToken || (
309
345
  !nonInteractive
310
346
  ? await promptUntilValid(prompter, {
@@ -388,13 +424,22 @@ export function createNpmDistributionInstallSeedService(deps = {}) {
388
424
  let licenseFile = trim(options.licenseFile);
389
425
  let licensePublicKeyFile = trim(options.licensePublicKeyFile);
390
426
  let licenseServerPublicKeyFile = trim(options.licenseServerPublicKeyFile);
427
+ const hasExplicitLicenseInputs = Boolean(
428
+ licenseKey
429
+ || licenseFile
430
+ || licensePublicKeyFile
431
+ || licenseServerPublicKeyFile,
432
+ );
391
433
  if (licenseKey && (licenseFile || licensePublicKeyFile || licenseServerPublicKeyFile)) {
392
434
  throw new Error('license key cannot be combined with direct license file inputs');
393
435
  }
394
- if (!licenseKey && !licenseFile && !licensePublicKeyFile && !licenseServerPublicKeyFile && !nonInteractive) {
436
+ const preserveExistingLicense = existingInstall && !hasExplicitLicenseInputs;
437
+ if (preserveExistingLicense) {
438
+ logger.log('==> Existing install detected; keeping current license state');
439
+ } else if (!licenseKey && !licenseFile && !licensePublicKeyFile && !licenseServerPublicKeyFile && !nonInteractive) {
395
440
  licenseKey = trim(await prompter.ask('License key (optional, blank to skip and activate later): '));
396
441
  }
397
- if (!licenseKey && !licenseFile && !licensePublicKeyFile && !licenseServerPublicKeyFile) {
442
+ if (!preserveExistingLicense && !licenseKey && !licenseFile && !licensePublicKeyFile && !licenseServerPublicKeyFile) {
398
443
  result.deferLicenseActivation = true;
399
444
  }
400
445
 
@@ -200,6 +200,12 @@ export function createNpmDistributionInstallService(deps = {}) {
200
200
  const args = [
201
201
  tempInstallPath,
202
202
  '--manifest-url', urls.manifestUrl,
203
+ ...(distributionMode === 'install'
204
+ ? ['--install-base-url', urls.installBaseUrl]
205
+ : []),
206
+ ...(distributionMode === 'install'
207
+ ? ['--update-base-url', urls.updateBaseUrl]
208
+ : []),
203
209
  ...(distributionMode === 'install'
204
210
  ? ['--runtime-remote-update-manifest-url', urls.runtimeManifestUrl]
205
211
  : []),