@afffun/codexbot 1.0.97 → 1.0.98

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.97",
3
+ "version": "1.0.98",
4
4
  "description": "Thin npm bootstrap CLI for installing and operating Codexbot nodes",
5
5
  "type": "module",
6
6
  "author": "john88188 <john88188@outlook.com>",
@@ -42,25 +42,46 @@ function mapAuthModeToCodexArgs(mode) {
42
42
  function buildProjectionScript(layout) {
43
43
  const controlDir = trim(layout.codexHomeDir);
44
44
  const agentHomeDir = trim(layout.agentHomeDir);
45
+ const agentUser = trim(layout.agentUser);
46
+ const agentGroup = trim(layout.agentGroup);
45
47
  if (!controlDir || !agentHomeDir) return '';
46
48
  const agentDir = path.join(agentHomeDir, '.codex');
47
49
  const lines = [
48
50
  'set -euo pipefail',
49
51
  `control_dir=${JSON.stringify(controlDir)}`,
52
+ `agent_home_dir=${JSON.stringify(agentHomeDir)}`,
50
53
  `agent_dir=${JSON.stringify(agentDir)}`,
51
- 'if [[ -d "$agent_dir" ]]; then',
52
- ' :',
53
- 'else',
54
+ `agent_user=${JSON.stringify(agentUser)}`,
55
+ `agent_group=${JSON.stringify(agentGroup)}`,
56
+ 'parent_gid="$(stat -c \'%g\' "$agent_home_dir" 2>/dev/null || true)"',
57
+ 'if [[ ! -d "$agent_dir" ]]; then',
54
58
  ' mkdir -p "$agent_dir"',
55
59
  'fi',
60
+ 'if [[ "$(id -u)" == "0" && -n "$agent_user" && -n "$agent_group" ]]; then',
61
+ ' chown "$agent_user:$agent_group" "$agent_dir" 2>/dev/null || true',
62
+ 'elif [[ -n "$agent_group" ]]; then',
63
+ ' chgrp "$agent_group" "$agent_dir" 2>/dev/null || true',
64
+ 'elif [[ -n "$parent_gid" ]]; then',
65
+ ' chgrp "$parent_gid" "$agent_dir" 2>/dev/null || true',
66
+ 'fi',
67
+ 'chmod 2770 "$agent_dir" 2>/dev/null || chmod 0770 "$agent_dir" 2>/dev/null || true',
56
68
  ];
57
69
  for (const entry of PROJECTED_FILES) {
58
70
  lines.push(`src="$control_dir/${entry.name}"`);
59
71
  lines.push(`dst="$agent_dir/${entry.name}"`);
60
72
  lines.push('if [[ -f "$src" ]]; then');
61
73
  lines.push(' rm -f "$dst"');
62
- lines.push(' cp "$src" "$dst"');
63
- lines.push(` chmod ${entry.mode} "$dst"`);
74
+ lines.push(' if [[ "$(id -u)" == "0" && -n "$agent_user" && -n "$agent_group" ]]; then');
75
+ lines.push(` install -D -m ${entry.mode} -o "$agent_user" -g "$agent_group" "$src" "$dst"`);
76
+ lines.push(' else');
77
+ lines.push(' cp "$src" "$dst"');
78
+ lines.push(' if [[ -n "$agent_group" ]]; then');
79
+ lines.push(' chgrp "$agent_group" "$dst" 2>/dev/null || true');
80
+ lines.push(' elif [[ -n "$parent_gid" ]]; then');
81
+ lines.push(' chgrp "$parent_gid" "$dst" 2>/dev/null || true');
82
+ lines.push(' fi');
83
+ lines.push(` chmod ${entry.mode} "$dst"`);
84
+ lines.push(' fi');
64
85
  lines.push('fi');
65
86
  }
66
87
  return lines.join('\n');
@@ -116,7 +137,15 @@ export function createNpmDistributionAuthService(deps = {}) {
116
137
  const projectionScript = buildProjectionScript(layout);
117
138
  if (projectionScript) {
118
139
  const projectionArgs = ['-u', layout.controlUser, 'env', `CODEX_HOME=${layout.codexHomeDir}`, `PATH=${pathModule.dirname(layout.codexBinPath)}:${processImpl.env.PATH || ''}`, 'bash', '-lc', projectionScript];
119
- if (isRoot || !(currentUser && currentUser === layout.controlUser)) {
140
+ if (isRoot) {
141
+ await runChild(spawnFn, 'bash', ['-lc', projectionScript], {
142
+ env: {
143
+ ...processImpl.env,
144
+ CODEX_HOME: layout.codexHomeDir,
145
+ PATH: `${pathModule.dirname(layout.codexBinPath)}:${processImpl.env.PATH || ''}`,
146
+ },
147
+ });
148
+ } else if (!(currentUser && currentUser === layout.controlUser)) {
120
149
  await runChild(spawnFn, 'sudo', projectionArgs);
121
150
  } else {
122
151
  await runChild(spawnFn, 'bash', ['-lc', projectionScript], {
@@ -68,7 +68,11 @@ function renderUsage() {
68
68
 
69
69
  export async function runCodexbotCli(argv = [], deps = {}) {
70
70
  const logger = deps.logger || console;
71
- const installService = deps.installService || createNpmDistributionInstallService({ logger });
71
+ const packageVersion = readPackageVersion();
72
+ const installService = deps.installService || createNpmDistributionInstallService({
73
+ logger,
74
+ cliVersion: packageVersion,
75
+ });
72
76
  const activationService = deps.activationService || createNpmDistributionActivationService({ logger });
73
77
  const authService = deps.authService || createNpmDistributionAuthService({ logger });
74
78
  const cli = parseCliArgs(argv);
@@ -77,7 +81,6 @@ export async function runCodexbotCli(argv = [], deps = {}) {
77
81
  return 0;
78
82
  }
79
83
  if (cli.action === 'version') {
80
- const packageVersion = readPackageVersion();
81
84
  let installedVersion = '';
82
85
  try {
83
86
  installedVersion = authService.resolveInstalledVersion({});
@@ -9,6 +9,7 @@ import {
9
9
  } from './config_service.mjs';
10
10
  import { createNpmDistributionActivationService } from './activation_service.mjs';
11
11
  import { createNpmDistributionInstallSeedService } from './install_seed_service.mjs';
12
+ import { compareVersionLike } from './version_compare_service.mjs';
12
13
 
13
14
  function trim(value, fallback = '') {
14
15
  const text = String(value ?? '').trim();
@@ -102,6 +103,55 @@ function runChild(spawnFn, command, args, options = {}) {
102
103
  });
103
104
  }
104
105
 
106
+ async function readJsonResponse(response) {
107
+ const text = await response.text();
108
+ try {
109
+ return text ? JSON.parse(text) : {};
110
+ } catch {
111
+ return {};
112
+ }
113
+ }
114
+
115
+ function buildMinimumCliVersionMessage({
116
+ currentCliVersion,
117
+ minimumCliVersion,
118
+ manifestVersion,
119
+ channel,
120
+ } = {}) {
121
+ const lines = [
122
+ `This Codexbot CLI is too old for the current public ${channel || 'stable'} install chain.`,
123
+ `- current CLI: ${trim(currentCliVersion, '(unknown)')}`,
124
+ `- minimum supported CLI: ${trim(minimumCliVersion, '(unknown)')}`,
125
+ ];
126
+ if (trim(manifestVersion)) {
127
+ lines.push(`- current public runtime: ${manifestVersion}`);
128
+ }
129
+ lines.push('');
130
+ lines.push('Update the npm CLI first, then retry:');
131
+ lines.push(`- npm install -g @afffun/codexbot@latest`);
132
+ lines.push(`- codexbot install`);
133
+ return lines.join('\n');
134
+ }
135
+
136
+ function assertMinimumCliVersion({
137
+ cliVersion = '',
138
+ manifest = null,
139
+ channel = '',
140
+ } = {}) {
141
+ const minCliVersion = trim(manifest?.publicInstall?.minimumCliVersion || manifest?.minimumCliVersion);
142
+ const currentCliVersion = trim(cliVersion);
143
+ if (!minCliVersion || !currentCliVersion) return;
144
+ if (compareVersionLike(currentCliVersion, minCliVersion) >= 0) return;
145
+ const err = new Error(buildMinimumCliVersionMessage({
146
+ currentCliVersion,
147
+ minimumCliVersion: minCliVersion,
148
+ manifestVersion: trim(manifest?.version),
149
+ channel,
150
+ }));
151
+ err.code = 'CLI_VERSION_UNSUPPORTED';
152
+ throw err;
153
+ }
154
+
105
155
  export function createNpmDistributionInstallService(deps = {}) {
106
156
  const fetchFn = deps.fetchFn || fetch;
107
157
  const fsPromises = deps.fsPromises || fs;
@@ -110,6 +160,7 @@ export function createNpmDistributionInstallService(deps = {}) {
110
160
  const spawnFn = deps.spawnFn || spawn;
111
161
  const processImpl = deps.processImpl || process;
112
162
  const logger = deps.logger || console;
163
+ const cliVersion = trim(deps.cliVersion);
113
164
  const installSeedService = deps.installSeedService || createNpmDistributionInstallSeedService({
114
165
  fsPromises,
115
166
  osModule,
@@ -195,6 +246,23 @@ export function createNpmDistributionInstallService(deps = {}) {
195
246
  });
196
247
  await writeResponseToFile(keyResponse, tempKeyPath, fsPromises);
197
248
  }
249
+ if (distributionMode === 'install') {
250
+ logger.log('==> Fetch manifest');
251
+ const manifestResponse = await ensureOkResponse(await fetchFn(urls.manifestUrl, { headers }), {
252
+ label: 'manifest',
253
+ installBaseUrl: urls.installBaseUrl,
254
+ channel: urls.channel,
255
+ updateBaseUrl: urls.updateBaseUrl,
256
+ authTokenPresent: Boolean(authToken),
257
+ distributionMode,
258
+ });
259
+ const manifestPayload = await readJsonResponse(manifestResponse);
260
+ assertMinimumCliVersion({
261
+ cliVersion,
262
+ manifest: manifestPayload,
263
+ channel: urls.channel,
264
+ });
265
+ }
198
266
 
199
267
  const remoteMode = distributionMode === 'install' ? 'auto' : trim(mode, 'auto');
200
268
  const args = [
@@ -7,6 +7,9 @@ const SPLIT_DEFAULTS = Object.freeze({
7
7
  appDir: '/usr/local/lib/codexbot',
8
8
  controlEnvFile: '/etc/codexbot/control.env',
9
9
  controlUser: 'codexbotd',
10
+ controlGroup: 'codexbotd',
11
+ agentUser: 'codexagent',
12
+ agentGroup: 'codexagent',
10
13
  codexHomeDir: '/var/lib/codexbot/control/home/.codex',
11
14
  agentHomeDir: '/var/lib/codexbot/agent/home',
12
15
  });
@@ -16,6 +19,9 @@ const LEGACY_DEFAULTS = Object.freeze({
16
19
  appDir: '/home/codexbot/codexbot-telegram',
17
20
  controlEnvFile: '/home/codexbot/codexbot-telegram/.env',
18
21
  controlUser: 'codexbot',
22
+ controlGroup: 'codexbot',
23
+ agentUser: 'codexbot',
24
+ agentGroup: 'codexbot',
19
25
  codexHomeDir: '/home/codexbot/codexbot-home/.codex',
20
26
  agentHomeDir: '',
21
27
  });
@@ -67,6 +73,9 @@ export function resolveInstalledControlLayout({
67
73
  const fileEnv = readEnvFile(fsSync, envFile);
68
74
  const resolvedAppDir = pathModule.resolve(trim(appDir || fileEnv.CODEXBOT_APP_DIR || fileEnv.CODEXBOT_SYSTEM_ROOT, defaults.appDir));
69
75
  const resolvedControlUser = trim(controlUser || fileEnv.CODEXBOT_CONTROL_USER, defaults.controlUser);
76
+ const resolvedControlGroup = trim(fileEnv.CODEXBOT_CONTROL_GROUP, defaults.controlGroup);
77
+ const resolvedAgentUser = trim(fileEnv.CODEXBOT_AGENT_USER, defaults.agentUser);
78
+ const resolvedAgentGroup = trim(fileEnv.CODEXBOT_AGENT_GROUP, defaults.agentGroup);
70
79
  const resolvedCodexHomeDir = pathModule.resolve(trim(
71
80
  codexHomeDir
72
81
  || fileEnv.CODEXBOT_CONTROL_CODEX_HOME_DIR
@@ -83,6 +92,9 @@ export function resolveInstalledControlLayout({
83
92
  appDir: resolvedAppDir,
84
93
  controlEnvFile: envFile,
85
94
  controlUser: resolvedControlUser,
95
+ controlGroup: resolvedControlGroup,
96
+ agentUser: resolvedAgentUser,
97
+ agentGroup: resolvedAgentGroup,
86
98
  codexHomeDir: resolvedCodexHomeDir,
87
99
  agentHomeDir: resolvedAgentHomeDir,
88
100
  codexBinPath,
@@ -0,0 +1,31 @@
1
+ function trimText(value) {
2
+ return String(value || '').trim();
3
+ }
4
+
5
+ function tokenizeVersionLike(value) {
6
+ return trimText(value)
7
+ .split(/[.\-+_]/g)
8
+ .map((item) => item.trim())
9
+ .filter(Boolean)
10
+ .map((item) => (/^\d+$/.test(item) ? Number.parseInt(item, 10) : item.toLowerCase()));
11
+ }
12
+
13
+ export function compareVersionLike(a, b) {
14
+ const left = tokenizeVersionLike(a);
15
+ const right = tokenizeVersionLike(b);
16
+ const limit = Math.max(left.length, right.length);
17
+ for (let index = 0; index < limit; index += 1) {
18
+ const lv = left[index] ?? 0;
19
+ const rv = right[index] ?? 0;
20
+ if (typeof lv === 'number' && typeof rv === 'number') {
21
+ if (lv < rv) return -1;
22
+ if (lv > rv) return 1;
23
+ continue;
24
+ }
25
+ const ls = String(lv);
26
+ const rs = String(rv);
27
+ if (ls < rs) return -1;
28
+ if (ls > rs) return 1;
29
+ }
30
+ return 0;
31
+ }