@technomoron/agent-run 0.99.24 → 0.99.25

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.
@@ -0,0 +1,316 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveAgentDir = resolveAgentDir;
4
+ exports.findLocalAiFiles = findLocalAiFiles;
5
+ exports.findSourceRepos = findSourceRepos;
6
+ exports.isIgnoredDir = isIgnoredDir;
7
+ exports.warnForLocalAiFiles = warnForLocalAiFiles;
8
+ exports.failForLocalAiFiles = failForLocalAiFiles;
9
+ exports.findProjectRoot = findProjectRoot;
10
+ exports.resolveProfile = resolveProfile;
11
+ exports.resolveProfileResult = resolveProfileResult;
12
+ exports.parseProfile = parseProfile;
13
+ exports.projectRootForProfile = projectRootForProfile;
14
+ exports.defaultConfigRoot = defaultConfigRoot;
15
+ exports.defaultConfigRootSearchCandidates = defaultConfigRootSearchCandidates;
16
+ const childProcess = require("child_process");
17
+ const fs = require("fs");
18
+ const os = require("os");
19
+ const path = require("path");
20
+ const constants_1 = require("./constants");
21
+ const utils_1 = require("./utils");
22
+ const walk_tree_1 = require("./walk-tree");
23
+ const envCache = new Map();
24
+ const PRUNED_SOURCE_DIRS = new Set(['node_modules', '.pnpm-store', 'dist', 'build']);
25
+ function resolveAgentDir(projectRoot) {
26
+ const profile = resolveProfile(projectRoot);
27
+ const configRoot = defaultConfigRoot(projectRoot);
28
+ const agentDir = path.join(configRoot, profile);
29
+ (0, utils_1.verbose)(`profile=${profile} configRoot=${configRoot} agentPath=${agentDir}`);
30
+ return agentDir;
31
+ }
32
+ function findLocalAiFiles(projectRoot, excludedDir = null) {
33
+ const matches = [];
34
+ const resolvedExcludedDir = excludedDir ? path.resolve(excludedDir) : null;
35
+ (0, walk_tree_1.walkTree)(projectRoot, (fullPath, entry) => {
36
+ if (resolvedExcludedDir !== null && (0, utils_1.isSamePathOrDescendant)(fullPath, resolvedExcludedDir)) {
37
+ return entry.isDirectory() ? 'skip' : undefined;
38
+ }
39
+ if (entry.isDirectory() && constants_1.LOCAL_AI_DIRECTORY_NAMES.has(entry.name)) {
40
+ matches.push(fullPath);
41
+ return 'skip';
42
+ }
43
+ if (constants_1.LOCAL_AI_FILE_NAMES.has(entry.name)) {
44
+ matches.push(fullPath);
45
+ }
46
+ return undefined;
47
+ }, {
48
+ shouldPrune: (entry) => entry.isDirectory() && (entry.name === '.git' || PRUNED_SOURCE_DIRS.has(entry.name)),
49
+ shouldSkipDirectory: isIgnoredDir
50
+ });
51
+ return matches.sort();
52
+ }
53
+ function findSourceRepos(rootPath) {
54
+ const resolvedRoot = path.resolve(rootPath);
55
+ if (isIgnoredDir(resolvedRoot)) {
56
+ return [];
57
+ }
58
+ if (looksLikeRepoRoot(resolvedRoot)) {
59
+ return [resolvedRoot];
60
+ }
61
+ const repos = new Set();
62
+ (0, walk_tree_1.walkTree)(resolvedRoot, (fullPath, entry) => {
63
+ if (!entry.isDirectory()) {
64
+ return undefined;
65
+ }
66
+ if (fs.existsSync(path.join(fullPath, '.git'))) {
67
+ repos.add(fullPath);
68
+ return 'skip';
69
+ }
70
+ return undefined;
71
+ }, { shouldPrune: (entry) => PRUNED_SOURCE_DIRS.has(entry.name), shouldSkipDirectory: isIgnoredDir });
72
+ return [...repos].sort();
73
+ }
74
+ function looksLikeRepoRoot(dir) {
75
+ return fs.existsSync(path.join(dir, '.git')) || fs.existsSync(path.join(dir, 'package.json'));
76
+ }
77
+ function isIgnoredDir(dir) {
78
+ if (fs.existsSync(path.join(dir, constants_1.IGNORE_FILE_NAME))) {
79
+ return true;
80
+ }
81
+ return (0, utils_1.parseBooleanEnv)(readAgentRunEnv(dir).AGENT_RUN_IGNORE);
82
+ }
83
+ function warnForLocalAiFiles(tool, projectRoot, localAiFiles) {
84
+ process.stderr.write(`WARNING: local AI files found in project root ${projectRoot}\n`);
85
+ for (const file of localAiFiles) {
86
+ process.stderr.write(`WARNING: ${path.relative(projectRoot, file)}\n`);
87
+ }
88
+ if (tool !== null) {
89
+ process.stderr.write(`WARNING: consider moving these files out of the project, or run \`agent-run ${tool} --none\` to bypass the wrapper.\n`);
90
+ }
91
+ }
92
+ function failForLocalAiFiles(tool, projectRoot, localAiFiles) {
93
+ process.stderr.write(`agent-run: found local AI files in project root ${projectRoot}\n`);
94
+ for (const file of localAiFiles) {
95
+ process.stderr.write(`agent-run: ${file}\n`);
96
+ }
97
+ process.stderr.write(`agent-run: move these files manually out of the project, run \`agent-run ${tool} --local\` to warn and continue, or run \`agent-run ${tool} --none\` to bypass the wrapper for this invocation.\n`);
98
+ process.exit(1);
99
+ }
100
+ function findProjectRoot(cwd) {
101
+ let dir = path.resolve(cwd);
102
+ let nearestPackageRoot = '';
103
+ let workspaceRoot = '';
104
+ let gitRoot = '';
105
+ for (;;) {
106
+ if (isWorkspaceRoot(dir)) {
107
+ workspaceRoot = dir;
108
+ }
109
+ if (!nearestPackageRoot && fs.existsSync(path.join(dir, 'package.json'))) {
110
+ nearestPackageRoot = dir;
111
+ }
112
+ if (fs.existsSync(path.join(dir, '.git'))) {
113
+ gitRoot = dir;
114
+ break;
115
+ }
116
+ const parent = path.dirname(dir);
117
+ if (parent === dir) {
118
+ break;
119
+ }
120
+ dir = parent;
121
+ }
122
+ const resolvedRoot = workspaceRoot || nearestPackageRoot || gitRoot || path.resolve(cwd);
123
+ (0, utils_1.verbose)(`findProjectRoot cwd=${path.resolve(cwd)} workspaceRoot=${workspaceRoot || '-'} nearestPackageRoot=${nearestPackageRoot || '-'} gitRoot=${gitRoot || '-'} resolved=${resolvedRoot}`);
124
+ return resolvedRoot;
125
+ }
126
+ function isWorkspaceRoot(dir) {
127
+ if (fs.existsSync(path.join(dir, 'pnpm-workspace.yaml'))) {
128
+ return true;
129
+ }
130
+ const packagePath = path.join(dir, 'package.json');
131
+ if (!fs.existsSync(packagePath)) {
132
+ return false;
133
+ }
134
+ try {
135
+ const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
136
+ return Array.isArray(pkg.workspaces) || (pkg.workspaces !== null && typeof pkg.workspaces === 'object');
137
+ }
138
+ catch {
139
+ return false;
140
+ }
141
+ }
142
+ function resolveProfile(projectRoot) {
143
+ const result = resolveProfileResult(projectRoot);
144
+ if (result.profile === null) {
145
+ (0, utils_1.fail)(result.reason);
146
+ }
147
+ return result.profile;
148
+ }
149
+ function resolveProfileResult(projectRoot) {
150
+ const envProfile = readAgentRunEnv(projectRoot).AGENT_RUN_PROFILE?.trim();
151
+ if (envProfile) {
152
+ (0, utils_1.verbose)(`using ${constants_1.ENV_FILE_NAME} AGENT_RUN_PROFILE=${envProfile}`);
153
+ return parseProfile(envProfile, `${constants_1.ENV_FILE_NAME} AGENT_RUN_PROFILE`);
154
+ }
155
+ const githubProfile = resolveGitHubProfile(projectRoot);
156
+ if (githubProfile !== null) {
157
+ (0, utils_1.verbose)(`using GitHub origin profile=${githubProfile}`);
158
+ return parseProfile(githubProfile, 'GitHub remote origin');
159
+ }
160
+ return profileFromPackage(projectRoot);
161
+ }
162
+ function profileFromPackage(projectRoot) {
163
+ const packagePath = path.join(projectRoot, 'package.json');
164
+ if (fs.existsSync(packagePath)) {
165
+ try {
166
+ const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
167
+ if (typeof pkg.name === 'string' && pkg.name.length > 0) {
168
+ (0, utils_1.verbose)(`using package.json name=${pkg.name}`);
169
+ return parseProfile(pkg.name.startsWith('@') ? pkg.name.slice(1) : pkg.name, `${packagePath} name`);
170
+ }
171
+ }
172
+ catch {
173
+ return { profile: null, reason: `cannot parse package.json: ${packagePath}` };
174
+ }
175
+ }
176
+ return {
177
+ profile: null,
178
+ reason: `cannot resolve agent profile for ${projectRoot}; add ${constants_1.ENV_FILE_NAME} with AGENT_RUN_PROFILE=<org/project>, add a GitHub origin remote, or set package.json.name`
179
+ };
180
+ }
181
+ function resolveGitHubProfile(projectRoot) {
182
+ const result = childProcess.spawnSync('git', ['-C', projectRoot, 'config', '--get', 'remote.origin.url'], {
183
+ encoding: 'utf8',
184
+ shell: false,
185
+ stdio: ['ignore', 'pipe', 'ignore']
186
+ });
187
+ if (result.status !== 0) {
188
+ return null;
189
+ }
190
+ const remote = result.stdout.trim();
191
+ return remote ? parseGitHubRemoteProfile(remote) : null;
192
+ }
193
+ function parseGitHubRemoteProfile(remote) {
194
+ const patterns = [
195
+ /^git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/,
196
+ /^https:\/\/github\.com\/([^/]+)\/(.+?)(?:\.git)?$/,
197
+ /^ssh:\/\/git@github\.com\/([^/]+)\/(.+?)(?:\.git)?$/
198
+ ];
199
+ for (const pattern of patterns) {
200
+ const match = pattern.exec(remote.trim());
201
+ if (match?.[1] && match[2]) {
202
+ return `${match[1]}/${match[2].replace(/\.git$/, '')}`;
203
+ }
204
+ }
205
+ return null;
206
+ }
207
+ function parseProfile(profile, source) {
208
+ const normalized = profile.trim().replace(/\\/g, '/');
209
+ if (!normalized) {
210
+ return { profile: null, reason: `${source} must be a non-empty path relative to the config root` };
211
+ }
212
+ if (normalized.startsWith('/') || normalized.startsWith('\\') || /^[A-Za-z]:\//.test(normalized)) {
213
+ return { profile: null, reason: `${source} must be relative to the config root, not an absolute path` };
214
+ }
215
+ const segments = normalized.split('/').filter(Boolean);
216
+ if (segments.length === 0 ||
217
+ segments.some((segment) => segment === '.' || segment === '..' || !/^[A-Za-z0-9._-]+$/.test(segment))) {
218
+ return { profile: null, reason: `${source} must be a clean relative path like org/my-project` };
219
+ }
220
+ return { profile: segments.join('/'), reason: '' };
221
+ }
222
+ function projectRootForProfile(profile, configRoot) {
223
+ if (configRoot && path.basename(configRoot) === 'agent-config') {
224
+ return path.join(path.dirname(configRoot), ...profile.split('/'));
225
+ }
226
+ return path.join(os.homedir(), ...profile.split('/'));
227
+ }
228
+ function readAgentRunEnv(dir) {
229
+ const resolvedDir = path.resolve(dir);
230
+ const cached = envCache.get(resolvedDir);
231
+ if (cached) {
232
+ return cached;
233
+ }
234
+ const filePath = path.join(resolvedDir, constants_1.ENV_FILE_NAME);
235
+ if (!fs.existsSync(filePath)) {
236
+ const empty = {};
237
+ envCache.set(resolvedDir, empty);
238
+ return empty;
239
+ }
240
+ const env = parseEnvFile(fs.readFileSync(filePath, 'utf8'));
241
+ envCache.set(resolvedDir, env);
242
+ return env;
243
+ }
244
+ function parseEnvFile(content) {
245
+ const env = {};
246
+ for (const rawLine of content.replace(/\r\n/g, '\n').split('\n')) {
247
+ const line = rawLine.trim();
248
+ if (!line || line.startsWith('#')) {
249
+ continue;
250
+ }
251
+ const equalsIndex = line.indexOf('=');
252
+ if (equalsIndex <= 0) {
253
+ continue;
254
+ }
255
+ const key = line.slice(0, equalsIndex).trim();
256
+ let value = line.slice(equalsIndex + 1).trim();
257
+ if (value.length >= 2 && ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")))) {
258
+ value = value.slice(1, -1);
259
+ }
260
+ env[key] = value;
261
+ }
262
+ return env;
263
+ }
264
+ function defaultConfigRoot(projectRoot, options = {}) {
265
+ const explicit = explicitConfigRoot(projectRoot);
266
+ if (explicit !== null) {
267
+ return explicit;
268
+ }
269
+ if (projectRoot) {
270
+ const projectConfigRoot = defaultCodeConfigRoot(projectRoot);
271
+ if (options.preferProjectRoot || fs.existsSync(projectConfigRoot)) {
272
+ return projectConfigRoot;
273
+ }
274
+ return findExistingConfigRoot(defaultConfigRootSearchCandidates()) ?? projectConfigRoot;
275
+ }
276
+ return findExistingConfigRoot(defaultConfigRootSearchCandidates()) ?? path.join(os.homedir(), '.agent-config');
277
+ }
278
+ function explicitConfigRoot(projectRoot) {
279
+ for (const value of [process.env[constants_1.CONFIG_ROOT_OVERRIDE_ENV], process.env[constants_1.CONFIG_DIR_ENV], process.env.AGENT_CONFIG_ROOT]) {
280
+ if (value) {
281
+ return path.resolve(value);
282
+ }
283
+ }
284
+ if (!projectRoot) {
285
+ return null;
286
+ }
287
+ const env = readAgentRunEnv(projectRoot);
288
+ const localValue = env[constants_1.CONFIG_DIR_ENV]?.trim() || env.AGENT_CONFIG_ROOT?.trim();
289
+ return localValue ? path.resolve(projectRoot, localValue) : null;
290
+ }
291
+ function defaultCodeConfigRoot(projectRoot) {
292
+ const resolvedProjectRoot = path.resolve(projectRoot);
293
+ const ownerDir = path.dirname(resolvedProjectRoot);
294
+ const codeRoot = path.dirname(ownerDir);
295
+ if (ownerDir === resolvedProjectRoot || codeRoot === ownerDir) {
296
+ return path.join(os.homedir(), '.agent-config');
297
+ }
298
+ return path.join(codeRoot, 'agent-config');
299
+ }
300
+ function findExistingConfigRoot(candidates) {
301
+ return candidates.find((candidate) => fs.existsSync(candidate)) ?? null;
302
+ }
303
+ function defaultConfigRootSearchCandidates(_projectRoot, options = {}) {
304
+ const platform = options.platform ?? process.platform;
305
+ const pathApi = platform === 'win32' ? path.win32 : path.posix;
306
+ const homeDir = options.homeDir ?? os.homedir();
307
+ const names = ['agent-config', 'agent-configs'];
308
+ if (platform === 'win32') {
309
+ return [pathApi.join(homeDir, 'Documents', 'code'), pathApi.join(homeDir, 'Desktop', 'code'), 'C:\\code'].flatMap((root) => names.map((name) => pathApi.join(root, name)));
310
+ }
311
+ return [
312
+ ...names.map((name) => pathApi.join(homeDir, 'code', name)),
313
+ ...names.map((name) => pathApi.join(homeDir, name)),
314
+ ...names.map((name) => pathApi.join(homeDir, `.${name}`))
315
+ ];
316
+ }