@technomoron/agent-run 0.99.26 → 0.99.28

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 CHANGED
@@ -1,6 +1,24 @@
1
1
  CHANGES
2
2
  =======
3
3
 
4
+ Version 0.99.28 (2026-08-30)
5
+ ----------------------------
6
+ * Add interactive `agent-run setup [org/repo]`, make `agent-run generate` the
7
+ profile-initialization command, and retain `init` as a deprecated alias.
8
+ * Use `~/.agent-run` as the single default config directory, overridable with
9
+ `--configdir`, `AGENT_CONFIG_DIR`, or a project-local `.agent-run.env`.
10
+ * Resolve profiles from `package.json.repository` and avoid rewriting unchanged
11
+ generated files.
12
+
13
+ Version 0.99.27 (2026-08-11)
14
+ ----------------------------
15
+ * Rename the unattended-run option from `--danger` to `--yolo` for Codex and
16
+ Claude.
17
+ * Add `agent-run setup [config-root]` to create the packaged starter agent
18
+ configuration files without overwriting local edits.
19
+ * Resolve plain directories without Git metadata or a package name as
20
+ `[parent]/[current]` profiles.
21
+
4
22
  Version 0.99.26 (2026-07-31)
5
23
  ----------------------------
6
24
  * Add `--danger` support for Claude so unattended runs pass
package/README.md CHANGED
@@ -3,8 +3,7 @@
3
3
  Small wrapper for AI coding CLIs like Codex and Claude.
4
4
 
5
5
  It keeps agent files out of normal repos and stores them in a separate
6
- agent config tree. By default, that tree lives beside owner/repo checkouts
7
- under the same code root.
6
+ agent config tree. By default, that tree lives at `~/.agent-run`.
8
7
 
9
8
  ## Model
10
9
 
@@ -17,31 +16,29 @@ There are three things:
17
16
  Example:
18
17
 
19
18
  ```text
20
- ~/source/
21
- agent-config/
22
- agent-run.defaults.jsonc
23
- org/
24
- my-api/
25
- agent-run.jsonc
26
- local.md.njk # optional
27
- overrides/ # optional
28
- live/
29
- CLAUDE.md
30
- .claude/
31
- bin/
32
- memories/
33
- codex-home/
34
- AGENTS.md
35
- config.toml
36
- skills/
19
+ ~/.agent-run/
20
+ agent-run.defaults.jsonc
37
21
  org/
38
22
  my-api/
23
+ agent-run.jsonc
24
+ local.md.njk # optional
25
+ overrides/ # optional
26
+ live/
27
+ CLAUDE.md
28
+ .claude/
29
+ bin/
30
+ memories/
31
+ codex-home/
32
+ AGENTS.md
33
+ config.toml
34
+ skills/
35
+ ~/source/org/my-api/
39
36
  ```
40
37
 
41
38
  If your source repo is `~/source/org/my-api`, `agent-run` maps it to:
42
39
 
43
40
  ```text
44
- ~/source/agent-config/org/my-api
41
+ ~/.agent-run/org/my-api
45
42
  ```
46
43
 
47
44
  The source repo stays clean. The agent files live in the matching path under
@@ -96,19 +93,24 @@ The mapped path is:
96
93
 
97
94
  1. `AGENT_RUN_PROFILE` in `.agent-run.env`
98
95
  2. GitHub `origin` remote
99
- 3. `package.json.name`
96
+ 3. `package.json.repository` (string or object `url`)
97
+ 4. `package.json.name`
98
+ 5. the project path as `[parent]/[current]`
100
99
 
101
100
  Examples:
102
101
 
103
102
  - `AGENT_RUN_PROFILE=org/my-api` -> `org/my-api`
104
103
  - `AGENT_RUN_PROFILE=unrelated/hello` -> `unrelated/hello`
104
+ - `package.json.repository = "github:org/my-api"` -> `org/my-api`
105
105
  - `package.json.name = "@org/my-api"` -> `org/my-api`
106
106
  - `package.json.name = "my-api"` -> `my-api`
107
+ - `/work/org/my-api` with none of the above -> `org/my-api`
107
108
 
108
109
  `AGENT_RUN_PROFILE` is relative to the config root. It is not a filesystem
109
110
  path, so values like `/tmp/foo`, `C:/tmp/foo`, or `../foo` are rejected.
110
111
 
111
- If neither exists, `agent-run` fails instead of guessing.
112
+ The path fallback lets plain directories work without a Git repository or a
113
+ `package.json`. The explicit profile sources above always take precedence.
112
114
 
113
115
  ## Overrides
114
116
 
@@ -121,20 +123,10 @@ AGENT_RUN_PROFILE=org/my-api
121
123
 
122
124
  Config root:
123
125
 
124
- - default for a project at `[code]/owner/repo`: `[code]/agent-config`
125
- - override with `--config-root /path/to/agent-configs`
126
+ - default: `~/.agent-run`
127
+ - override with `--configdir=/path/to/agent-configs`
126
128
  - override with `AGENT_CONFIG_DIR=/path/to/agent-configs`
127
- - override with `AGENT_CONFIG_ROOT=/path/to/agent-configs`
128
- - or set `AGENT_CONFIG_DIR=/path/to/agent-configs` or `AGENT_CONFIG_ROOT=/path/to/agent-configs` in `.agent-run.env`
129
-
130
- For commands without a project root, `agent-run` looks for an existing config
131
- root in these locations:
132
-
133
- - Windows: `~/Documents/code/agent-config`, `~/Documents/code/agent-configs`,
134
- `~/Desktop/code/agent-config`, `~/Desktop/code/agent-configs`,
135
- `C:\code\agent-config`, `C:\code\agent-configs`
136
- - Unix: `~/code/agent-config`, `~/code/agent-configs`, `~/agent-config`,
137
- `~/agent-configs`, `~/.agent-config`, `~/.agent-configs`
129
+ - or set `AGENT_CONFIG_DIR=/path/to/agent-configs` in `.agent-run.env`
138
130
 
139
131
  ## Commands
140
132
 
@@ -168,7 +160,7 @@ replace the user's normal `CLAUDE_CONFIG_DIR`. If Claude writes remembered
168
160
  permissions to `.claude/settings.local.json`, `agent-run` removes that file
169
161
  after the session unless `--local` was used.
170
162
 
171
- `--danger` runs either tool unattended, with no approval prompts: Codex with
163
+ `--yolo` runs either tool unattended, with no approval prompts: Codex with
172
164
  `-a never -s danger-full-access`, Claude with `--dangerously-skip-permissions`.
173
165
  Only use it when the agent may change or delete anything it can reach. Claude
174
166
  refuses `--dangerously-skip-permissions` when it runs as root, so run it as a
@@ -183,12 +175,12 @@ to warn and continue for a specific invocation.
183
175
  Initialize mapped files for the current repo:
184
176
 
185
177
  ```sh
186
- agent-run init
178
+ agent-run generate
187
179
  ```
188
180
 
189
181
  This creates `<config-root>/agent-run.defaults.jsonc` when needed and a minimal
190
182
  `agent-run.jsonc` marker for the mapped profile. It does not create empty local
191
- instruction or override files.
183
+ instruction or override files. `agent-run init` remains as a deprecated alias.
192
184
 
193
185
  Edit the source file for the current repo:
194
186
 
@@ -206,19 +198,18 @@ agent-run update
206
198
  ```
207
199
 
208
200
  `update` requires an existing profile marker or local/legacy instruction file;
209
- it does not silently scaffold an unconfigured project. Use `agent-run init` or
201
+ it does not silently scaffold an unconfigured project. Use `agent-run generate` or
210
202
  the tool command's `--create` option first.
211
203
 
212
204
  Regenerate every profile under the config root without requiring matching code
213
205
  checkouts:
214
206
 
215
207
  ```sh
216
- agent-run update --all ~/.agent-config
208
+ agent-run update --all ~/.agent-run
217
209
  ```
218
210
 
219
211
  If the config root argument is omitted, `update --all` uses the normal configured
220
- root. Without a project path, that falls back to `$HOME/.agent-config`; for
221
- project commands, the default is `[code]/agent-config`.
212
+ root. Without an override, the root is `$HOME/.agent-run`.
222
213
 
223
214
  You can also generate from a tool command and stop before launch:
224
215
 
@@ -242,27 +233,28 @@ Per-profile override templates can be placed in:
242
233
  - `overrides/codex-config.toml.njk`
243
234
  - `overrides/claude-settings.json.njk`
244
235
 
245
- ## Starter Config
236
+ ## Default Config Tree
246
237
 
247
- A complete starter lives in `examples/basic-config`. It includes a minimal
238
+ A complete default tree lives in `examples/basic-config`. It includes a minimal
248
239
  project, `.agent-run.env`, root manifest defaults, global templates and
249
240
  snippets, profile-local templates, skill templates, a personal memory skill,
250
241
  profile skill overrides, tool config overrides, guardrails, checks, and
251
242
  generated runtime paths.
252
243
 
253
- To copy the packaged starter config root into your default config location:
244
+ To install the default tree and detected profile in your resolved config root:
254
245
 
255
246
  ```sh
256
- agent-run --init
247
+ agent-run setup
257
248
  ```
258
249
 
259
- Or choose a destination:
250
+ Or select a profile non-interactively:
260
251
 
261
252
  ```sh
262
- agent-run --init ~/.agent-config
253
+ agent-run setup myorg/myrepo
263
254
  ```
264
255
 
265
- The copy skips files that already exist, so local edits are preserved.
256
+ Use `--configdir=DIR` to choose a different destination. The copy skips files
257
+ that already exist, so local edits are preserved.
266
258
 
267
259
  Try it from a checkout:
268
260
 
@@ -295,7 +287,7 @@ agent-run check --all ~/source
295
287
  Migrate an existing config tree to the manifest/template layout:
296
288
 
297
289
  ```sh
298
- agent-run migrate-config ~/.agent-config
290
+ agent-run migrate-config ~/.agent-run
299
291
  ```
300
292
 
301
293
  This preserves `AGENTS-MODS.md`, creates `local.md.njk` and a sparse
package/dist/cli.js CHANGED
@@ -10,15 +10,6 @@ function parseInvocation(invokedTool, argv) {
10
10
  let command = normalizeCommandName(invokedTool);
11
11
  const options = extractGlobalOptions(argv);
12
12
  applyGlobalOptions(options);
13
- if (options.initConfig) {
14
- if (options.args.length > 0) {
15
- (0, utils_1.fail)('--init cannot be combined with a command');
16
- }
17
- return {
18
- command: 'init-config',
19
- targetPath: path.resolve(options.initConfigTargetPath ?? (0, project_1.defaultConfigRoot)())
20
- };
21
- }
22
13
  if (command === null) {
23
14
  command = extractCommand(options.args);
24
15
  }
@@ -34,7 +25,7 @@ function applyGlobalOptions(options) {
34
25
  }
35
26
  function extractCommand(args) {
36
27
  if (args.length === 0) {
37
- (0, utils_1.fail)('usage: agent-run <codex|claude|check|init|edit|update> [options] (run with --help for details)');
28
+ (0, utils_1.fail)('usage: agent-run <codex|claude|check|setup|generate|edit|update> [options] (run with --help for details)');
38
29
  }
39
30
  if (isHelpFlag(args[0])) {
40
31
  printHelp('general');
@@ -59,6 +50,10 @@ function parseCommand(command, args) {
59
50
  return parseCheckCommand(args);
60
51
  case 'init':
61
52
  return parseInitCommand(args);
53
+ case 'generate':
54
+ return parseGenerateCommand(args);
55
+ case 'setup':
56
+ return parseSetupCommand(args);
62
57
  case 'edit':
63
58
  return parseEditCommand(args);
64
59
  case 'update':
@@ -73,8 +68,6 @@ function extractGlobalOptions(argv) {
73
68
  const options = {
74
69
  args: [],
75
70
  configRootOverride: null,
76
- initConfig: false,
77
- initConfigTargetPath: null,
78
71
  verbose: false
79
72
  };
80
73
  let passthrough = false;
@@ -94,26 +87,18 @@ function extractGlobalOptions(argv) {
94
87
  else if (arg === '-v' || arg === '--verbose') {
95
88
  options.verbose = true;
96
89
  }
97
- else if (arg === '--init') {
98
- options.initConfig = true;
99
- const nextArg = argv[index + 1];
100
- if (nextArg !== undefined && !nextArg.startsWith('-')) {
101
- options.initConfigTargetPath = nextArg;
102
- index += 1;
103
- }
104
- }
105
- else if (arg === '--config-root') {
90
+ else if (arg === '--configdir') {
106
91
  const nextArg = argv[index + 1];
107
92
  if (nextArg === undefined) {
108
- (0, utils_1.fail)('missing value for --config-root');
93
+ (0, utils_1.fail)('missing value for --configdir');
109
94
  }
110
95
  options.configRootOverride = path.resolve(nextArg);
111
96
  index += 1;
112
97
  }
113
- else if (arg.startsWith('--config-root=')) {
114
- const rootValue = arg.slice('--config-root='.length);
98
+ else if (arg.startsWith('--configdir=')) {
99
+ const rootValue = arg.slice('--configdir='.length);
115
100
  if (!rootValue) {
116
- (0, utils_1.fail)('missing value for --config-root');
101
+ (0, utils_1.fail)('missing value for --configdir');
117
102
  }
118
103
  options.configRootOverride = path.resolve(rootValue);
119
104
  }
@@ -154,7 +139,7 @@ function parseRunCommand(command, inputArgs) {
154
139
  args.push(arg);
155
140
  }
156
141
  if (wrapperArgs.codexNetwork && wrapperArgs.sandboxMode === 'danger') {
157
- (0, utils_1.fail)('--network cannot be combined with agent-run codex --danger');
142
+ (0, utils_1.fail)('--network cannot be combined with agent-run codex --yolo');
158
143
  }
159
144
  return { args, command, wrapperArgs };
160
145
  }
@@ -174,19 +159,19 @@ function applyCommonRunOption(arg, options) {
174
159
  return true;
175
160
  }
176
161
  function applySandboxRunOption(command, arg, options) {
177
- if (!['--danger', '--sandboxed', '--network'].includes(arg)) {
162
+ if (!['--yolo', '--sandboxed', '--network'].includes(arg)) {
178
163
  return false;
179
164
  }
180
- if (arg !== '--danger' && command !== 'codex') {
165
+ if (arg !== '--yolo' && command !== 'codex') {
181
166
  (0, utils_1.fail)(`${arg} is only supported for agent-run codex`);
182
167
  }
183
168
  if (arg === '--network') {
184
169
  options.codexNetwork = true;
185
170
  return true;
186
171
  }
187
- const mode = arg === '--danger' ? 'danger' : 'sandboxed';
172
+ const mode = arg === '--yolo' ? 'danger' : 'sandboxed';
188
173
  if (options.sandboxMode !== null && options.sandboxMode !== mode) {
189
- (0, utils_1.fail)('cannot combine --danger and --sandboxed');
174
+ (0, utils_1.fail)('cannot combine --yolo and --sandboxed');
190
175
  }
191
176
  options.sandboxMode = mode;
192
177
  return true;
@@ -213,11 +198,30 @@ function parseCheckCommand(inputArgs) {
213
198
  function parseInitCommand(inputArgs) {
214
199
  return { command: 'init', targetPath: parseSinglePath(inputArgs, 'init') };
215
200
  }
201
+ function parseGenerateCommand(inputArgs) {
202
+ return { command: 'generate', targetPath: parseSinglePath(inputArgs, 'generate') };
203
+ }
204
+ function parseSetupCommand(inputArgs) {
205
+ let profile = null;
206
+ for (const arg of inputArgs) {
207
+ if (isHelpFlag(arg)) {
208
+ printHelp('setup');
209
+ }
210
+ if (arg.startsWith('--')) {
211
+ (0, utils_1.fail)(`unknown setup option: ${arg}`);
212
+ }
213
+ if (profile !== null) {
214
+ (0, utils_1.fail)('setup accepts at most one profile argument');
215
+ }
216
+ profile = arg;
217
+ }
218
+ return { command: 'setup', profile };
219
+ }
216
220
  function parseEditCommand(inputArgs) {
217
221
  return { command: 'edit', targetPath: parseSinglePath(inputArgs, 'edit') };
218
222
  }
219
- function parseSinglePath(inputArgs, command) {
220
- let targetPath = process.cwd();
223
+ function parseSinglePath(inputArgs, command, defaultPath = process.cwd()) {
224
+ let targetPath = defaultPath;
221
225
  for (const arg of inputArgs) {
222
226
  if (isHelpFlag(arg)) {
223
227
  printHelp(command);
@@ -288,7 +292,9 @@ function renderHelp(topic) {
288
292
  ' --all Check every repo under path',
289
293
  ''
290
294
  ],
291
- init: ['Usage:', ' agent-run init [path]', '', 'Create a minimal mapped profile marker and render generated files.', ''],
295
+ init: ['Usage:', ' agent-run init [path]', '', 'Deprecated alias for `agent-run generate [path]`.', ''],
296
+ generate: ['Usage:', ' agent-run generate [path]', '', 'Create a minimal mapped profile marker and render generated files.', ''],
297
+ setup: ['Usage:', ' agent-run setup [org/repo]', '', 'Install the default config tree and selected profile.', ''],
292
298
  edit: ['Usage:', ' agent-run edit [path]', '', 'Create or open the editable local profile template.', ''],
293
299
  update: [
294
300
  'Usage:',
@@ -313,16 +319,16 @@ function renderHelp(topic) {
313
319
  `agent-run ${agentRunVersion()}`,
314
320
  '',
315
321
  'Usage:',
316
- ' agent-run <codex|claude|check|init|edit|update> [options]',
317
- ' agent-run --init [config-root]',
322
+ ' agent-run <codex|claude|check|setup|generate|edit|update> [options]',
318
323
  '',
319
324
  'Commands:',
320
- ' codex [--none] [--create] [--local] [--show] [--generate] [--danger|--sandboxed] [--network] [args...]',
325
+ ' codex [--none] [--create] [--local] [--show] [--generate] [--yolo|--sandboxed] [--network] [args...]',
321
326
  ' Run codex with generated private config',
322
- ' claude [--none] [--create] [--local] [--show] [--generate] [--danger] [args...]',
327
+ ' claude [--none] [--create] [--local] [--show] [--generate] [--yolo] [args...]',
323
328
  ' Run claude with generated private config',
329
+ ' setup [org/repo] Install the default tree and selected profile',
324
330
  ' check [--all] [path] Validate generated profile output',
325
- ' init [path] Create a sparse profile marker and render output',
331
+ ' generate [path] Create a sparse profile marker and render output',
326
332
  ' edit [path] Create or open local.md.njk',
327
333
  ' update [--all] [path] Regenerate existing profile output',
328
334
  ' migrate-config [config-root] Convert existing config tree layout',
@@ -331,14 +337,13 @@ function renderHelp(topic) {
331
337
  ' -h, --help Show this help text',
332
338
  ' -V, --version Show the agent-run version',
333
339
  ' -v, --verbose Print path resolution and wrapper actions',
334
- ' --config-root DIR Override the agent-config root',
335
- ' --init [DIR] Copy the packaged starter config root to DIR (default ~/.agent-config)',
340
+ ' --configdir=DIR Override the config directory',
336
341
  '',
337
342
  'Run wrapper options:',
338
343
  ' --local Warn about local AI files instead of failing',
339
344
  ' --show Show read/include and generated files without running the tool',
340
345
  ' --generate Generate files without running the tool',
341
- ' --danger Run unattended without approval prompts:',
346
+ ' --yolo Run unattended without approval prompts:',
342
347
  ' Codex with -a never -s danger-full-access,',
343
348
  ' Claude with --dangerously-skip-permissions',
344
349
  '',
@@ -365,7 +370,7 @@ function normalizeCommandName(value) {
365
370
  if (base === 'agent-run') {
366
371
  return null;
367
372
  }
368
- return ['codex', 'claude', 'check', 'init', 'edit', 'update', 'migrate-config'].includes(base)
373
+ return ['codex', 'claude', 'check', 'setup', 'generate', 'init', 'edit', 'update', 'migrate-config'].includes(base)
369
374
  ? base
370
375
  : null;
371
376
  }
package/dist/commands.js CHANGED
@@ -22,10 +22,13 @@ function dispatch(command) {
22
22
  runCheck(command);
23
23
  return;
24
24
  case 'init':
25
- runInit(command);
25
+ runGenerate(command);
26
26
  return;
27
- case 'init-config':
28
- runInitConfig(command);
27
+ case 'generate':
28
+ runGenerate(command);
29
+ return;
30
+ case 'setup':
31
+ runSetup(command);
29
32
  return;
30
33
  case 'edit':
31
34
  runEdit(command);
@@ -65,7 +68,7 @@ function runTool(parsed) {
65
68
  return;
66
69
  }
67
70
  if (wrapperArgs.create) {
68
- runInit({ command: 'init', targetPath: projectRoot });
71
+ runGenerate({ command: 'generate', targetPath: projectRoot });
69
72
  }
70
73
  const configRoot = (0, project_1.defaultConfigRoot)(projectRoot);
71
74
  const agentDir = path.join(configRoot, profile);
@@ -110,7 +113,7 @@ function requireProfile(tool, agentDir) {
110
113
  }
111
114
  const label = tool ?? 'agent';
112
115
  process.stderr.write(`agent-run: no ${label} profile found for this project: ${agentDir}\n`);
113
- process.stderr.write('agent-run: run `agent-run init` or use the tool with `--create`.\n');
116
+ process.stderr.write('agent-run: run `agent-run generate` or use the tool with `--create`.\n');
114
117
  process.exit(1);
115
118
  }
116
119
  function checkLocalAiFiles(command, projectRoot, agentDir, context, allowLocal) {
@@ -146,21 +149,84 @@ function ensureToolEnabled(context, command) {
146
149
  (0, utils_1.fail)(`${command} is disabled for agent-run profile ${context.profile}`);
147
150
  }
148
151
  }
149
- function runInit(parsed) {
150
- const projectRoot = activeProject(parsed.targetPath, 'init');
152
+ function runGenerate(parsed) {
153
+ if (parsed.command === 'init') {
154
+ process.stderr.write('agent-run: `init` is deprecated; use `agent-run generate [path]` instead.\n');
155
+ }
156
+ const projectRoot = activeProject(parsed.targetPath, 'generate');
151
157
  if (projectRoot === null) {
152
158
  return;
153
159
  }
154
160
  const agentDir = initializeProfileSource(projectRoot, true);
155
161
  printUpdateSummary((0, renderer_1.syncAgentProfile)(projectRoot, agentDir));
156
162
  }
157
- function runInitConfig(parsed) {
163
+ function runSetup(parsed) {
164
+ const projectRoot = (0, project_1.findProjectRoot)(process.cwd());
165
+ const detectedProfile = (0, project_1.resolveProfile)(projectRoot);
166
+ const profile = parsed.profile === null ? confirmProfile(detectedProfile) : requireValidProfile(parsed.profile);
167
+ const configRoot = (0, project_1.defaultConfigRoot)(projectRoot);
158
168
  const sourcePath = (0, config_tree_1.starterConfigRootPath)();
159
169
  if (!fs.existsSync(sourcePath)) {
160
170
  (0, utils_1.fail)(`starter config skeleton not found: ${sourcePath}`);
161
171
  }
162
- (0, config_tree_1.copySkeletonTree)(sourcePath, path.resolve(parsed.targetPath));
163
- process.stdout.write(`OK copied starter config to ${path.resolve(parsed.targetPath)}\n`);
172
+ (0, config_tree_1.copySkeletonTree)(sourcePath, configRoot, new Set(['starter']));
173
+ const agentDir = path.join(configRoot, profile);
174
+ (0, config_tree_1.copySkeletonTree)(path.join(sourcePath, 'starter', 'basic-project'), agentDir);
175
+ if (profile !== detectedProfile) {
176
+ writeProjectProfileMapping(projectRoot, profile);
177
+ }
178
+ printUpdateSummary((0, renderer_1.syncAgentProfile)(projectRoot, agentDir, { configRoot, profile }));
179
+ process.stdout.write(`OK installed default config tree at ${configRoot}\n`);
180
+ }
181
+ function requireValidProfile(value) {
182
+ const parsed = (0, project_1.parseProfile)(value, 'setup profile');
183
+ if (parsed.profile === null) {
184
+ (0, utils_1.fail)(parsed.reason);
185
+ }
186
+ return parsed.profile;
187
+ }
188
+ function confirmProfile(detectedProfile) {
189
+ process.stdout.write(`Detected profile: ${detectedProfile}\n`);
190
+ const confirmation = promptLine('Is this correct? [Y/n] ').trim().toLowerCase();
191
+ if (confirmation === '' || confirmation === 'y' || confirmation === 'yes') {
192
+ return detectedProfile;
193
+ }
194
+ return requireValidProfile(promptLine('Profile (org/repo): '));
195
+ }
196
+ function promptLine(prompt) {
197
+ if (!process.stdin.isTTY) {
198
+ (0, utils_1.fail)('setup requires a profile argument when input is not interactive');
199
+ }
200
+ process.stdout.write(prompt);
201
+ const bytes = [];
202
+ const buffer = Buffer.alloc(1);
203
+ while (fs.readSync(process.stdin.fd, buffer, 0, 1, null) === 1) {
204
+ if (buffer[0] === 10) {
205
+ break;
206
+ }
207
+ if (buffer[0] !== 13) {
208
+ bytes.push(buffer[0] ?? 0);
209
+ }
210
+ }
211
+ return Buffer.from(bytes).toString('utf8');
212
+ }
213
+ function writeProjectProfileMapping(projectRoot, profile) {
214
+ const envPath = path.join(projectRoot, constants_1.ENV_FILE_NAME);
215
+ const existing = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf8').replace(/\r\n/g, '\n') : '';
216
+ const lines = existing.length > 0 ? existing.replace(/\n+$/, '').split('\n') : [];
217
+ const profileLine = `AGENT_RUN_PROFILE=${profile}`;
218
+ const index = lines.findIndex((line) => /^\s*AGENT_RUN_PROFILE\s*=/.test(line));
219
+ if (index >= 0) {
220
+ lines[index] = profileLine;
221
+ }
222
+ else {
223
+ lines.push(profileLine);
224
+ }
225
+ const content = `${lines.join('\n')}\n`;
226
+ if (content !== existing) {
227
+ fs.writeFileSync(envPath, content, 'utf8');
228
+ (0, utils_1.verbose)(`write ${envPath}`);
229
+ }
164
230
  }
165
231
  function runEdit(parsed) {
166
232
  const projectRoot = activeProject(parsed.targetPath, 'edit');
@@ -153,14 +153,17 @@ function migrateLooseFiles(agentDir, pattern, targetDirName) {
153
153
  function starterConfigRootPath() {
154
154
  return path.resolve(__dirname, '..', 'examples', 'basic-config', 'agent-config');
155
155
  }
156
- function copySkeletonTree(sourceDir, targetDir) {
156
+ function copySkeletonTree(sourceDir, targetDir, excludedNames = new Set()) {
157
157
  fs.mkdirSync(targetDir, { recursive: true });
158
158
  const entries = fs.readdirSync(sourceDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
159
159
  for (const entry of entries) {
160
+ if (excludedNames.has(entry.name)) {
161
+ continue;
162
+ }
160
163
  const sourcePath = path.join(sourceDir, entry.name);
161
164
  const targetPath = path.join(targetDir, entry.name === 'gitignore' ? '.gitignore' : entry.name);
162
165
  if (entry.isDirectory()) {
163
- copySkeletonTree(sourcePath, targetPath);
166
+ copySkeletonTree(sourcePath, targetPath, excludedNames);
164
167
  }
165
168
  else if (entry.isFile() && !fs.existsSync(targetPath)) {
166
169
  fs.mkdirSync(path.dirname(targetPath), { recursive: true });
package/dist/constants.js CHANGED
@@ -11,7 +11,7 @@ exports.LIVE_DIR_NAME = 'live';
11
11
  exports.CONFIG_ROOT_OVERRIDE_ENV = 'AGENT_RUN_CONFIG_ROOT_OVERRIDE';
12
12
  exports.CONFIG_DIR_ENV = 'AGENT_CONFIG_DIR';
13
13
  exports.VERBOSE_ENV = 'AGENT_RUN_VERBOSE';
14
- exports.PACKAGE_VERSION = '0.99.25';
14
+ exports.PACKAGE_VERSION = '0.99.28';
15
15
  exports.UNEXPANDED_TEMPLATE_RE = /\{\{[^}]+\}\}|\{%[^%]+%\}/;
16
16
  exports.LOCAL_AI_FILE_NAMES = new Set([
17
17
  'AGENTS.md',
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`);
@@ -173,10 +178,37 @@ function profileFromPackage(projectRoot) {
173
178
  return { profile: null, reason: `cannot parse package.json: ${packagePath}` };
174
179
  }
175
180
  }
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
- };
181
+ const current = path.basename(projectRoot);
182
+ const parentDir = path.dirname(projectRoot);
183
+ const parent = path.basename(parentDir);
184
+ if (current && parent && parentDir !== projectRoot) {
185
+ const inferredProfile = `${parent}/${current}`;
186
+ (0, utils_1.verbose)(`using project path profile=${inferredProfile}`);
187
+ return parseProfile(inferredProfile, 'project parent and directory name');
188
+ }
189
+ return { profile: null, reason: `cannot resolve agent profile from project path: ${projectRoot}` };
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;
180
212
  }
181
213
  function resolveGitHubProfile(projectRoot) {
182
214
  const result = childProcess.spawnSync('git', ['-C', projectRoot, 'config', '--get', 'remote.origin.url'], {
@@ -266,17 +298,11 @@ function defaultConfigRoot(projectRoot, options = {}) {
266
298
  if (explicit !== null) {
267
299
  return explicit;
268
300
  }
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');
301
+ void options;
302
+ return path.join(os.homedir(), '.agent-run');
277
303
  }
278
304
  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]) {
305
+ for (const value of [process.env[constants_1.CONFIG_ROOT_OVERRIDE_ENV], process.env[constants_1.CONFIG_DIR_ENV]]) {
280
306
  if (value) {
281
307
  return path.resolve(value);
282
308
  }
@@ -285,21 +311,9 @@ function explicitConfigRoot(projectRoot) {
285
311
  return null;
286
312
  }
287
313
  const env = readAgentRunEnv(projectRoot);
288
- const localValue = env[constants_1.CONFIG_DIR_ENV]?.trim() || env.AGENT_CONFIG_ROOT?.trim();
314
+ const localValue = env[constants_1.CONFIG_DIR_ENV]?.trim();
289
315
  return localValue ? path.resolve(projectRoot, localValue) : null;
290
316
  }
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
317
  function defaultConfigRootSearchCandidates(_projectRoot, options = {}) {
304
318
  const platform = options.platform ?? process.platform;
305
319
  const pathApi = platform === 'win32' ? path.win32 : path.posix;
package/dist/renderer.js CHANGED
@@ -101,11 +101,18 @@ function syncAgentProfile(projectRoot, agentDir, options) {
101
101
  }
102
102
  function writeGeneratedFile(filePath, content, executable) {
103
103
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
104
- fs.writeFileSync(filePath, content, 'utf8');
104
+ const unchanged = fs.existsSync(filePath) && fs.readFileSync(filePath, 'utf8') === content;
105
+ if (!unchanged) {
106
+ fs.writeFileSync(filePath, content, 'utf8');
107
+ (0, utils_1.verbose)(`write ${filePath}`);
108
+ }
105
109
  if (executable && !constants_1.IS_WINDOWS) {
106
- fs.chmodSync(filePath, 0o755);
110
+ const currentMode = fs.statSync(filePath).mode & 0o777;
111
+ if (currentMode !== 0o755) {
112
+ fs.chmodSync(filePath, 0o755);
113
+ (0, utils_1.verbose)(`chmod 755 ${filePath}`);
114
+ }
107
115
  }
108
- (0, utils_1.verbose)(`write ${filePath}`);
109
116
  }
110
117
  function removeStaleGeneratedEntries(rendered) {
111
118
  const expectedFiles = new Set(rendered.files.map((file) => path.resolve(file.path)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@technomoron/agent-run",
3
- "version": "0.99.26",
3
+ "version": "0.99.28",
4
4
  "packageManager": "pnpm@10.28.1",
5
5
  "bin": {
6
6
  "agent-run": "dist/agent-run.js"