@technomoron/agent-run 0.99.27 → 0.99.29
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/CHANGES +22 -0
- package/README.md +106 -52
- package/dist/checks.js +4 -0
- package/dist/cli.js +47 -19
- package/dist/commands.js +110 -14
- package/dist/config-tree.js +254 -2
- package/dist/constants.js +1 -1
- package/dist/defaults.js +34 -3
- package/dist/manifest.js +2 -1
- package/dist/project.js +31 -22
- package/dist/renderer.js +30 -4
- package/dist/templates.js +3 -0
- package/dist/tools.js +6 -0
- package/examples/basic-config/README.md +2 -1
- package/examples/basic-config/agent-config/agent-run.defaults.jsonc +2 -1
- package/examples/basic-config/agent-config/global/tool-templates/AGENTS.md.njk +2 -0
- package/examples/basic-config/agent-config/global/tool-templates/CLAUDE.md.njk +1 -0
- package/examples/basic-config/agent-config/global/tool-templates/claude-settings.json.njk +2 -0
- package/examples/basic-config/agent-config/global/tool-templates/codex-config.toml.njk +2 -1
- package/examples/basic-config/agent-config/starter/basic-project/notes/memory/README.md +8 -0
- package/examples/basic-config/agent-config/starter/basic-project/overrides/claude-settings.json.njk +2 -0
- package/examples/basic-config/agent-config/starter/basic-project/overrides/codex-config.toml.njk +2 -1
- package/ops/systemd/ai-tools-update.service +1 -1
- package/package.json +1 -1
- package/scripts/install-systemd-jobs.sh +15 -5
- package/scripts/update-ai-tools.sh +37 -2
package/dist/project.js
CHANGED
|
@@ -164,6 +164,11 @@ function profileFromPackage(projectRoot) {
|
|
|
164
164
|
if (fs.existsSync(packagePath)) {
|
|
165
165
|
try {
|
|
166
166
|
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
167
|
+
const repositoryProfile = parsePackageRepositoryProfile(pkg.repository);
|
|
168
|
+
if (repositoryProfile !== null) {
|
|
169
|
+
(0, utils_1.verbose)(`using package.json repository profile=${repositoryProfile}`);
|
|
170
|
+
return parseProfile(repositoryProfile, `${packagePath} repository`);
|
|
171
|
+
}
|
|
167
172
|
if (typeof pkg.name === 'string' && pkg.name.length > 0) {
|
|
168
173
|
(0, utils_1.verbose)(`using package.json name=${pkg.name}`);
|
|
169
174
|
return parseProfile(pkg.name.startsWith('@') ? pkg.name.slice(1) : pkg.name, `${packagePath} name`);
|
|
@@ -183,6 +188,28 @@ function profileFromPackage(projectRoot) {
|
|
|
183
188
|
}
|
|
184
189
|
return { profile: null, reason: `cannot resolve agent profile from project path: ${projectRoot}` };
|
|
185
190
|
}
|
|
191
|
+
function parsePackageRepositoryProfile(repository) {
|
|
192
|
+
let repositoryPath = null;
|
|
193
|
+
if (typeof repository === 'string') {
|
|
194
|
+
repositoryPath = repository;
|
|
195
|
+
}
|
|
196
|
+
else if (repository !== null && typeof repository === 'object' && 'url' in repository) {
|
|
197
|
+
const url = repository.url;
|
|
198
|
+
if (typeof url === 'string') {
|
|
199
|
+
repositoryPath = url;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (repositoryPath === null) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
const value = repositoryPath.trim();
|
|
206
|
+
const githubProfile = parseGitHubRemoteProfile(value.replace(/^git\+/, ''));
|
|
207
|
+
if (githubProfile !== null) {
|
|
208
|
+
return githubProfile;
|
|
209
|
+
}
|
|
210
|
+
const shorthand = /^(?:github:)?([^/:\s]+)\/([^/\s]+?)(?:\.git)?$/.exec(value);
|
|
211
|
+
return shorthand?.[1] && shorthand[2] ? `${shorthand[1]}/${shorthand[2]}` : null;
|
|
212
|
+
}
|
|
186
213
|
function resolveGitHubProfile(projectRoot) {
|
|
187
214
|
const result = childProcess.spawnSync('git', ['-C', projectRoot, 'config', '--get', 'remote.origin.url'], {
|
|
188
215
|
encoding: 'utf8',
|
|
@@ -271,17 +298,11 @@ function defaultConfigRoot(projectRoot, options = {}) {
|
|
|
271
298
|
if (explicit !== null) {
|
|
272
299
|
return explicit;
|
|
273
300
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if (options.preferProjectRoot || fs.existsSync(projectConfigRoot)) {
|
|
277
|
-
return projectConfigRoot;
|
|
278
|
-
}
|
|
279
|
-
return findExistingConfigRoot(defaultConfigRootSearchCandidates()) ?? projectConfigRoot;
|
|
280
|
-
}
|
|
281
|
-
return findExistingConfigRoot(defaultConfigRootSearchCandidates()) ?? path.join(os.homedir(), '.agent-config');
|
|
301
|
+
void options;
|
|
302
|
+
return path.join(os.homedir(), '.agent-run');
|
|
282
303
|
}
|
|
283
304
|
function explicitConfigRoot(projectRoot) {
|
|
284
|
-
for (const value of [process.env[constants_1.CONFIG_ROOT_OVERRIDE_ENV], process.env[constants_1.CONFIG_DIR_ENV]
|
|
305
|
+
for (const value of [process.env[constants_1.CONFIG_ROOT_OVERRIDE_ENV], process.env[constants_1.CONFIG_DIR_ENV]]) {
|
|
285
306
|
if (value) {
|
|
286
307
|
return path.resolve(value);
|
|
287
308
|
}
|
|
@@ -290,21 +311,9 @@ function explicitConfigRoot(projectRoot) {
|
|
|
290
311
|
return null;
|
|
291
312
|
}
|
|
292
313
|
const env = readAgentRunEnv(projectRoot);
|
|
293
|
-
const localValue = env[constants_1.CONFIG_DIR_ENV]?.trim()
|
|
314
|
+
const localValue = env[constants_1.CONFIG_DIR_ENV]?.trim();
|
|
294
315
|
return localValue ? path.resolve(projectRoot, localValue) : null;
|
|
295
316
|
}
|
|
296
|
-
function defaultCodeConfigRoot(projectRoot) {
|
|
297
|
-
const resolvedProjectRoot = path.resolve(projectRoot);
|
|
298
|
-
const ownerDir = path.dirname(resolvedProjectRoot);
|
|
299
|
-
const codeRoot = path.dirname(ownerDir);
|
|
300
|
-
if (ownerDir === resolvedProjectRoot || codeRoot === ownerDir) {
|
|
301
|
-
return path.join(os.homedir(), '.agent-config');
|
|
302
|
-
}
|
|
303
|
-
return path.join(codeRoot, 'agent-config');
|
|
304
|
-
}
|
|
305
|
-
function findExistingConfigRoot(candidates) {
|
|
306
|
-
return candidates.find((candidate) => fs.existsSync(candidate)) ?? null;
|
|
307
|
-
}
|
|
308
317
|
function defaultConfigRootSearchCandidates(_projectRoot, options = {}) {
|
|
309
318
|
const platform = options.platform ?? process.platform;
|
|
310
319
|
const pathApi = platform === 'win32' ? path.win32 : path.posix;
|
package/dist/renderer.js
CHANGED
|
@@ -51,11 +51,23 @@ function renderAgentSections(env, configRoot, manifest, context, trace) {
|
|
|
51
51
|
}
|
|
52
52
|
sections.push((0, templates_1.renderTemplateFile)(env, configRoot, include, context, trace));
|
|
53
53
|
}
|
|
54
|
-
|
|
54
|
+
const renderedSections = sections.map((section, index) => {
|
|
55
55
|
const label = index === 0 ? manifest.agent.base : manifest.agent.includes[index - 1] ?? `section ${index}`;
|
|
56
56
|
(0, templates_1.assertNoUnexpandedTemplateVars)(label, section);
|
|
57
57
|
return section.trimEnd();
|
|
58
58
|
});
|
|
59
|
+
renderedSections.push(projectMemoryInstructions(context));
|
|
60
|
+
return renderedSections;
|
|
61
|
+
}
|
|
62
|
+
function projectMemoryInstructions(context) {
|
|
63
|
+
return [
|
|
64
|
+
'## Project Memory',
|
|
65
|
+
'',
|
|
66
|
+
`Project memory is stored in ${context.paths.projectMemoryDir}.`,
|
|
67
|
+
'Read README.md there before starting work when prior project context may matter, then read only the linked files relevant to the task.',
|
|
68
|
+
'Do not store secrets, raw chat transcripts, or temporary task state there.',
|
|
69
|
+
'Update project memory only when the user explicitly asks you to remember or update something for this project.'
|
|
70
|
+
].join('\n');
|
|
59
71
|
}
|
|
60
72
|
function renderProfileFiles(env, configRoot, context, targetTool, trace) {
|
|
61
73
|
const files = [];
|
|
@@ -101,11 +113,18 @@ function syncAgentProfile(projectRoot, agentDir, options) {
|
|
|
101
113
|
}
|
|
102
114
|
function writeGeneratedFile(filePath, content, executable) {
|
|
103
115
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
104
|
-
fs.
|
|
116
|
+
const unchanged = fs.existsSync(filePath) && fs.readFileSync(filePath, 'utf8') === content;
|
|
117
|
+
if (!unchanged) {
|
|
118
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
119
|
+
(0, utils_1.verbose)(`write ${filePath}`);
|
|
120
|
+
}
|
|
105
121
|
if (executable && !constants_1.IS_WINDOWS) {
|
|
106
|
-
fs.
|
|
122
|
+
const currentMode = fs.statSync(filePath).mode & 0o777;
|
|
123
|
+
if (currentMode !== 0o755) {
|
|
124
|
+
fs.chmodSync(filePath, 0o755);
|
|
125
|
+
(0, utils_1.verbose)(`chmod 755 ${filePath}`);
|
|
126
|
+
}
|
|
107
127
|
}
|
|
108
|
-
(0, utils_1.verbose)(`write ${filePath}`);
|
|
109
128
|
}
|
|
110
129
|
function removeStaleGeneratedEntries(rendered) {
|
|
111
130
|
const expectedFiles = new Set(rendered.files.map((file) => path.resolve(file.path)));
|
|
@@ -203,6 +222,7 @@ function checkRenderedFiles(rendered, findings) {
|
|
|
203
222
|
function checkRuntimeDirectories(context, findings) {
|
|
204
223
|
for (const dir of [
|
|
205
224
|
context.paths.reviewDir,
|
|
225
|
+
context.paths.projectMemoryDir,
|
|
206
226
|
context.paths.memoriesDir,
|
|
207
227
|
context.paths.codexHomeDir,
|
|
208
228
|
context.paths.liveDir,
|
|
@@ -296,6 +316,7 @@ function resolveProfileOverrideTemplate(configRoot, context, overrideFileName, g
|
|
|
296
316
|
function syncRuntimeDirs(context) {
|
|
297
317
|
for (const dir of [
|
|
298
318
|
context.paths.reviewDir,
|
|
319
|
+
context.paths.projectMemoryDir,
|
|
299
320
|
context.paths.memoriesDir,
|
|
300
321
|
context.paths.codexHomeDir,
|
|
301
322
|
context.paths.codexSkillsDir,
|
|
@@ -305,6 +326,11 @@ function syncRuntimeDirs(context) {
|
|
|
305
326
|
]) {
|
|
306
327
|
fs.mkdirSync(dir, { recursive: true });
|
|
307
328
|
}
|
|
329
|
+
const projectMemoryIndex = path.join(context.paths.projectMemoryDir, 'README.md');
|
|
330
|
+
if (!fs.existsSync(projectMemoryIndex)) {
|
|
331
|
+
fs.writeFileSync(projectMemoryIndex, (0, defaults_1.defaultProjectMemoryIndex)(), 'utf8');
|
|
332
|
+
(0, utils_1.verbose)(`created ${projectMemoryIndex}`);
|
|
333
|
+
}
|
|
308
334
|
migrateLiveReviewFiles(context);
|
|
309
335
|
removeLegacyCodexSkillDirs(context);
|
|
310
336
|
removeLegacyCodeReviewSkillDirs(context);
|
package/dist/templates.js
CHANGED
|
@@ -35,6 +35,7 @@ function buildRenderContext(projectRoot, profileDir, configRoot, manifest, env)
|
|
|
35
35
|
};
|
|
36
36
|
const reviewDir = resolveRuntimePath(configRoot, manifest.paths.reviewDir, env, baseContext);
|
|
37
37
|
const memoriesDir = resolveRuntimePath(configRoot, manifest.paths.memoriesDir, env, baseContext);
|
|
38
|
+
const projectMemoryDir = resolveRuntimePath(configRoot, manifest.paths.projectMemoryDir, env, baseContext);
|
|
38
39
|
const codexHomeDir = path.join(memoriesDir, 'codex-home');
|
|
39
40
|
const paths = {
|
|
40
41
|
profileDir,
|
|
@@ -47,6 +48,8 @@ function buildRenderContext(projectRoot, profileDir, configRoot, manifest, env)
|
|
|
47
48
|
: path.join(reviewDir, 'REVIEW.md'),
|
|
48
49
|
memoriesDir,
|
|
49
50
|
globalMemoryDir: path.join(configRoot, 'notes', 'memory'),
|
|
51
|
+
projectMemoryDir,
|
|
52
|
+
nativeMemoryDir: path.join(codexHomeDir, 'memories'),
|
|
50
53
|
codexHomeDir,
|
|
51
54
|
overridesDir: path.join(profileDir, 'overrides'),
|
|
52
55
|
codexSkillsDir: path.join(codexHomeDir, 'skills'),
|
package/dist/tools.js
CHANGED
|
@@ -23,6 +23,7 @@ function runCodex(realBinary, permissionArgs, context, args, wrapperArgs) {
|
|
|
23
23
|
AGENT_DIR: liveDir,
|
|
24
24
|
AGENT_PROFILE_DIR: context.profileDir,
|
|
25
25
|
AGENT_RUN_PROJECT_ROOT: projectRoot,
|
|
26
|
+
AGENT_PROJECT_MEMORY_DIR: context.paths.projectMemoryDir,
|
|
26
27
|
AGENT_RUN_REAL_PATH: realPath,
|
|
27
28
|
PATH: `${path.join(liveDir, 'bin')}${path.delimiter}${realPath}`
|
|
28
29
|
};
|
|
@@ -30,6 +31,8 @@ function runCodex(realBinary, permissionArgs, context, args, wrapperArgs) {
|
|
|
30
31
|
(0, process_1.execCommand)(realBinary, [
|
|
31
32
|
...permissionArgs,
|
|
32
33
|
...runtimeArgs,
|
|
34
|
+
'--add-dir',
|
|
35
|
+
context.paths.projectMemoryDir,
|
|
33
36
|
'-C',
|
|
34
37
|
projectRoot,
|
|
35
38
|
...(wrapperArgs.sandboxMode !== 'danger' && wrapperArgs.codexNetwork
|
|
@@ -51,12 +54,15 @@ function runClaude(realBinary, permissionArgs, context, args, wrapperArgs) {
|
|
|
51
54
|
AGENT_DIR: liveDir,
|
|
52
55
|
AGENT_PROFILE_DIR: context.profileDir,
|
|
53
56
|
AGENT_RUN_PROJECT_ROOT: context.projectRoot,
|
|
57
|
+
AGENT_PROJECT_MEMORY_DIR: context.paths.projectMemoryDir,
|
|
54
58
|
AGENT_RUN_REAL_PATH: realPath,
|
|
55
59
|
PATH: `${path.join(liveDir, 'bin')}${path.delimiter}${realPath}`
|
|
56
60
|
};
|
|
57
61
|
const runtimeArgs = wrapperArgs.sandboxMode === 'danger' ? getDangerArgs('claude') : permissionArgs;
|
|
58
62
|
(0, process_1.execCommand)(realBinary, [
|
|
59
63
|
...runtimeArgs,
|
|
64
|
+
'--add-dir',
|
|
65
|
+
context.paths.projectMemoryDir,
|
|
60
66
|
'--append-system-prompt-file',
|
|
61
67
|
claudePath,
|
|
62
68
|
'--settings',
|
|
@@ -9,7 +9,8 @@ It contains two pieces:
|
|
|
9
9
|
config root.
|
|
10
10
|
- `agent-config/`: a complete config root with shared manifest defaults, global
|
|
11
11
|
templates, snippets, skill templates, a personal memory skill, a sparse
|
|
12
|
-
profile manifest, profile-local instructions, and
|
|
12
|
+
profile manifest, durable project memory, profile-local instructions, and
|
|
13
|
+
profile overrides.
|
|
13
14
|
|
|
14
15
|
Try it from this repository after building:
|
|
15
16
|
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"changesFile": "{{ projectRoot }}/CHANGES",
|
|
34
34
|
"reviewDir": "{{ profileDir }}/reviews",
|
|
35
35
|
"reviewFile": "{{ profileDir }}/reviews/REVIEW-{{ date }}.md",
|
|
36
|
-
"memoriesDir": "{{ agentDir }}/memories"
|
|
36
|
+
"memoriesDir": "{{ agentDir }}/memories",
|
|
37
|
+
"projectMemoryDir": "{{ profileDir }}/notes/memory"
|
|
37
38
|
}
|
|
38
39
|
}
|
|
@@ -14,6 +14,8 @@ Do not edit this generated file. Edit root defaults or profile-specific source f
|
|
|
14
14
|
- reviewFile: `{{ paths.reviewFile }}`
|
|
15
15
|
- reviewConsolidatedFile: `{{ paths.reviewConsolidatedFile }}`
|
|
16
16
|
- memoriesDir: `{{ paths.memoriesDir }}`
|
|
17
|
+
- nativeMemoryDir: `{{ paths.nativeMemoryDir }}`
|
|
18
|
+
- projectMemoryDir: `{{ paths.projectMemoryDir }}`
|
|
17
19
|
- globalMemoryDir: `{{ paths.globalMemoryDir }}`
|
|
18
20
|
|
|
19
21
|
## Skills
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"AGENT_DIR": {{ agentDir | dump }},
|
|
4
4
|
"AGENT_PROFILE_DIR": {{ profileDir | dump }},
|
|
5
5
|
"AGENT_RUN_PROJECT_ROOT": {{ projectRoot | dump }},
|
|
6
|
+
"AGENT_PROJECT_MEMORY_DIR": {{ paths.projectMemoryDir | dump }},
|
|
6
7
|
"AGENT_GLOBAL_MEMORY_DIR": {{ paths.globalMemoryDir | dump }},
|
|
7
8
|
"STARTER_PROFILE": {{ profile | dump }}
|
|
8
9
|
},
|
|
@@ -15,6 +16,7 @@
|
|
|
15
16
|
"AGENT_DIR": {{ agentDir | dump }},
|
|
16
17
|
"AGENT_PROFILE_DIR": {{ profileDir | dump }},
|
|
17
18
|
"AGENT_RUN_PROJECT_ROOT": {{ projectRoot | dump }},
|
|
19
|
+
"AGENT_PROJECT_MEMORY_DIR": {{ paths.projectMemoryDir | dump }},
|
|
18
20
|
"AGENT_GLOBAL_MEMORY_DIR": {{ paths.globalMemoryDir | dump }},
|
|
19
21
|
"STARTER_PROFILE": {{ profile | dump }}
|
|
20
22
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Project memory
|
|
2
|
+
|
|
3
|
+
This directory contains durable notes for this project across agent sessions and machines.
|
|
4
|
+
Keep the notes short, factual, and free of secrets. Link additional topic files from this index.
|
|
5
|
+
|
|
6
|
+
## Files
|
|
7
|
+
|
|
8
|
+
- Add project memory files here as they are needed.
|
package/examples/basic-config/agent-config/starter/basic-project/overrides/claude-settings.json.njk
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"AGENT_DIR": {{ agentDir | dump }},
|
|
4
4
|
"AGENT_PROFILE_DIR": {{ profileDir | dump }},
|
|
5
5
|
"AGENT_RUN_PROJECT_ROOT": {{ projectRoot | dump }},
|
|
6
|
+
"AGENT_PROJECT_MEMORY_DIR": {{ paths.projectMemoryDir | dump }},
|
|
6
7
|
"AGENT_GLOBAL_MEMORY_DIR": {{ paths.globalMemoryDir | dump }},
|
|
7
8
|
"STARTER_PROFILE": {{ profile | dump }},
|
|
8
9
|
"STARTER_OVERRIDE": "true"
|
|
@@ -16,6 +17,7 @@
|
|
|
16
17
|
"AGENT_DIR": {{ agentDir | dump }},
|
|
17
18
|
"AGENT_PROFILE_DIR": {{ profileDir | dump }},
|
|
18
19
|
"AGENT_RUN_PROJECT_ROOT": {{ projectRoot | dump }},
|
|
20
|
+
"AGENT_PROJECT_MEMORY_DIR": {{ paths.projectMemoryDir | dump }},
|
|
19
21
|
"AGENT_GLOBAL_MEMORY_DIR": {{ paths.globalMemoryDir | dump }},
|
|
20
22
|
"STARTER_PROFILE": {{ profile | dump }},
|
|
21
23
|
"STARTER_OVERRIDE": "true"
|
|
@@ -13,4 +13,4 @@ Environment=XDG_CONFIG_HOME=/root/.config
|
|
|
13
13
|
Environment=NPM_CONFIG_PREFIX=/usr/local
|
|
14
14
|
Environment=PNPM_HOME=/usr/local
|
|
15
15
|
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
16
|
-
ExecStart=/usr/local/
|
|
16
|
+
ExecStart=/usr/local/libexec/agent-run/update-ai-tools
|
package/package.json
CHANGED
|
@@ -19,10 +19,20 @@ USAGE
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
22
|
-
|
|
22
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
23
|
+
INSTALL_LIBEXEC_DIR="${INSTALL_LIBEXEC_DIR:-${INSTALL_LIB_DIR:-/usr/local/libexec/agent-run}}"
|
|
23
24
|
SYSTEMD_DIR="${SYSTEMD_DIR:-/etc/systemd/system}"
|
|
24
25
|
ORIGINAL_ARGS=("$@")
|
|
25
26
|
|
|
27
|
+
UPDATE_SCRIPT="$ROOT/scripts/update-ai-tools.sh"
|
|
28
|
+
SERVICE_FILE="$ROOT/ops/systemd/ai-tools-update.service"
|
|
29
|
+
TIMER_FILE="$ROOT/ops/systemd/ai-tools-update.timer"
|
|
30
|
+
if [ ! -f "$UPDATE_SCRIPT" ]; then
|
|
31
|
+
UPDATE_SCRIPT="$SCRIPT_DIR/update-ai-tools.sh"
|
|
32
|
+
SERVICE_FILE="$SCRIPT_DIR/ai-tools-update.service"
|
|
33
|
+
TIMER_FILE="$SCRIPT_DIR/ai-tools-update.timer"
|
|
34
|
+
fi
|
|
35
|
+
|
|
26
36
|
INSTALL_AI_TOOLS=0
|
|
27
37
|
DRY_RUN=0
|
|
28
38
|
|
|
@@ -71,10 +81,10 @@ run() {
|
|
|
71
81
|
"$@"
|
|
72
82
|
}
|
|
73
83
|
|
|
74
|
-
run install -d -m 0755 "$
|
|
75
|
-
run install -m 0755 "$
|
|
76
|
-
run install -m 0644 "$
|
|
77
|
-
run install -m 0644 "$
|
|
84
|
+
run install -d -m 0755 "$INSTALL_LIBEXEC_DIR" "$SYSTEMD_DIR"
|
|
85
|
+
run install -m 0755 "$UPDATE_SCRIPT" "$INSTALL_LIBEXEC_DIR/update-ai-tools"
|
|
86
|
+
run install -m 0644 "$SERVICE_FILE" "$SYSTEMD_DIR/ai-tools-update.service"
|
|
87
|
+
run install -m 0644 "$TIMER_FILE" "$SYSTEMD_DIR/ai-tools-update.timer"
|
|
78
88
|
|
|
79
89
|
if [ "$INSTALL_AI_TOOLS" -eq 1 ]; then
|
|
80
90
|
run systemctl daemon-reload
|
|
@@ -6,18 +6,44 @@ export NPM_CONFIG_PREFIX="${NPM_CONFIG_PREFIX:-/usr/local}"
|
|
|
6
6
|
export PNPM_HOME="${PNPM_HOME:-/usr/local}"
|
|
7
7
|
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH:-}"
|
|
8
8
|
|
|
9
|
-
NPM_BIN="${AI_TOOLS_NPM_BIN:-/usr/
|
|
9
|
+
NPM_BIN="${AI_TOOLS_NPM_BIN:-/usr/bin/npm}"
|
|
10
|
+
NODE_BIN="${AI_TOOLS_NODE_BIN:-/usr/bin/node}"
|
|
10
11
|
APT_GET_BIN="${AI_TOOLS_APT_GET_BIN:-/usr/bin/apt-get}"
|
|
11
12
|
|
|
12
13
|
AI_TOOLS_NPM_PACKAGES="${AI_TOOLS_NPM_PACKAGES:-npm@latest pnpm@latest corepack@latest fallow@latest ripgrep@latest pm2@latest tsx@latest typescript@latest @openai/codex@latest @anthropic-ai/claude-code@latest @technomoron/agent-run@latest}"
|
|
13
14
|
AI_TOOLS_PNPM_PACKAGE="${AI_TOOLS_PNPM_PACKAGE:-pnpm@latest}"
|
|
14
15
|
AI_TOOLS_APT_PACKAGES="${AI_TOOLS_APT_PACKAGES:-gh}"
|
|
16
|
+
AI_TOOLS_CLEAN_BINS="${AI_TOOLS_CLEAN_BINS:-agent-run claude codex}"
|
|
17
|
+
AI_TOOLS_SYSTEM_BIN_DIRS="${AI_TOOLS_SYSTEM_BIN_DIRS:-/usr/local/bin /usr/bin}"
|
|
15
18
|
|
|
16
19
|
if [ "$(id -u)" -ne 0 ]; then
|
|
17
20
|
echo "update-ai-tools.sh must run as root" >&2
|
|
18
21
|
exit 1
|
|
19
22
|
fi
|
|
20
23
|
|
|
24
|
+
clean_tool_bins() {
|
|
25
|
+
local bin_dir="$1"
|
|
26
|
+
local bin_name
|
|
27
|
+
local -a bin_names
|
|
28
|
+
|
|
29
|
+
[ -d "$bin_dir" ] || return 0
|
|
30
|
+
read -r -a bin_names <<<"$AI_TOOLS_CLEAN_BINS"
|
|
31
|
+
for bin_name in "${bin_names[@]}"; do
|
|
32
|
+
case "$bin_name" in
|
|
33
|
+
agent-run|claude|codex)
|
|
34
|
+
rm -f -- "$bin_dir/$bin_name"
|
|
35
|
+
;;
|
|
36
|
+
*)
|
|
37
|
+
echo "Refusing to clean unexpected AI tool bin: $bin_name" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
;;
|
|
40
|
+
esac
|
|
41
|
+
done
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
target_bin_dir="${NPM_CONFIG_PREFIX%/}/bin"
|
|
45
|
+
clean_tool_bins "$target_bin_dir"
|
|
46
|
+
|
|
21
47
|
if [ -n "$AI_TOOLS_NPM_PACKAGES" ]; then
|
|
22
48
|
read -r -a npm_packages <<<"$AI_TOOLS_NPM_PACKAGES"
|
|
23
49
|
"$NPM_BIN" install -g --force "${npm_packages[@]}"
|
|
@@ -30,9 +56,18 @@ if [ -n "$AI_TOOLS_PNPM_PACKAGE" ]; then
|
|
|
30
56
|
"$NPM_BIN" install -g --force "${pnpm_packages[@]}"
|
|
31
57
|
fi
|
|
32
58
|
|
|
59
|
+
# Once the new launchers are installed, remove exact-name duplicates from the
|
|
60
|
+
# other conventional system prefix so PATH ordering cannot select a stale copy.
|
|
61
|
+
read -r -a system_bin_dirs <<<"$AI_TOOLS_SYSTEM_BIN_DIRS"
|
|
62
|
+
for system_bin_dir in "${system_bin_dirs[@]}"; do
|
|
63
|
+
if [ "${system_bin_dir%/}" != "${target_bin_dir%/}" ]; then
|
|
64
|
+
clean_tool_bins "$system_bin_dir"
|
|
65
|
+
fi
|
|
66
|
+
done
|
|
67
|
+
|
|
33
68
|
CLAUDE_INSTALL="$("$NPM_BIN" root -g)/@anthropic-ai/claude-code/install.cjs"
|
|
34
69
|
if [ -f "$CLAUDE_INSTALL" ]; then
|
|
35
|
-
|
|
70
|
+
"$NODE_BIN" "$CLAUDE_INSTALL"
|
|
36
71
|
fi
|
|
37
72
|
|
|
38
73
|
if [ -n "$AI_TOOLS_APT_PACKAGES" ] && command -v "$APT_GET_BIN" >/dev/null 2>&1; then
|