@atollhq/skill-codex 0.4.22 → 0.4.24

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/README.md CHANGED
@@ -6,13 +6,27 @@ Gives your Codex agent the ability to manage tasks, goals, KPIs, initiatives, mi
6
6
 
7
7
  ## Install
8
8
 
9
+ Install or refresh the local skill files without configuring credentials:
10
+
11
+ ```bash
12
+ npx --yes @atollhq/skill-codex@latest
13
+ ```
14
+
15
+ Bare invocation and `--install-only` only update `~/.codex/skills/atoll/`, the profile-neutral routing hint, and compatibility references. They do not inspect or change Atoll credentials, profiles, shell files, or repo configuration, even when `ATOLL_*` variables are present.
16
+
17
+ Configure a named profile explicitly:
18
+
19
+ ```bash
20
+ npx --yes @atollhq/skill-codex@latest --profile agent-a --key sk_atoll_... --org org-uuid --project project-id --team team-id
21
+ ```
22
+
23
+ For intentional environment configuration, use `--configure`:
24
+
9
25
  ```bash
10
- npx @atollhq/skill-codex@latest --profile agent-a --key sk_atoll_... --org your-org-id --project project-id --team team-id
11
- # or
12
- ATOLL_API_KEY=sk_atoll_... ATOLL_ORG_ID=your-org-id npx @atollhq/skill-codex@latest
26
+ ATOLL_API_KEY=sk_atoll_... ATOLL_ORG_ID=org-uuid npx --yes @atollhq/skill-codex@latest --configure
13
27
  ```
14
28
 
15
- Optional defaults: `--project`, `--team`, and `--base-url` are stored with the selected mode. Use `--no-project`, `--no-team`, or `--no-base-url` to clear previously saved defaults. Pass `--profile` to store credentials and defaults only in that named Atoll CLI profile. The installer does not write global `ATOLL_*` exports to `~/.zshrc` or `~/.bashrc` in profile mode. Use `atoll --profile agent-a ...` for profile-scoped commands, or omit `--profile` to use env-var mode.
29
+ Configuration mode supports `--project`, `--team`, and `--base-url`. Use `--no-project`, `--no-team`, or `--no-base-url` to clear saved defaults. Pass `--profile` to store credentials and defaults only in that named Atoll CLI profile. The installer does not write global `ATOLL_*` exports in profile mode. Use `atoll --profile agent-a ...` for profile-scoped commands.
16
30
 
17
31
  For multi-org setups, keep global Codex instructions neutral and bind profiles per repo:
18
32
 
@@ -23,18 +37,18 @@ npx @atollhq/skill-codex@latest --profile agent-a --key sk_atoll_... --org your-
23
37
 
24
38
  `--write-project-instructions` writes a small managed block to repo-local instruction files. It requires `--profile`, defaults to the current directory, writes `AGENTS.md` by default, and can also write `CLAUDE.md` with `--instruction-files agents,claude`. If the target file already has unmanaged Atoll profile guidance, pass `--force-project-instructions` after reviewing the replacement.
25
39
 
26
- Get an agent API key from **Agents** in the Atoll app. Integration keys are still managed from **Settings > Members**.
40
+ Get an agent API key from **Agents** in the Atoll app. Integration keys are managed from **Settings > Integrations**.
27
41
 
28
- This does six things:
42
+ Every invocation installs the local skill assets, routing hint, and compatibility references. Configuration mode additionally:
29
43
 
30
44
  1. Installs the `atoll` skill to `~/.codex/skills/atoll/`
31
45
  2. Appends (or updates) a small profile-neutral Atoll skill routing hint in `~/.codex/AGENTS.md`
32
46
  3. Copies API reference files to `~/.codex/atoll-references/`
33
47
  4. Creates or updates the named Atoll CLI profile when `--profile` is provided
34
- 5. Appends Atoll env var exports to your shell profile (`~/.zshrc` or `~/.bashrc`) only when no profile is provided
48
+ 5. Appends Atoll env var exports to your shell profile (`~/.zshrc` or `~/.bashrc`) when `--configure` is used without a profile
35
49
  6. Optionally writes repo-local profile guidance when `--write-project-instructions` is provided
36
50
 
37
- For profile mode, Codex has the Atoll skill immediately, global Codex guidance points to that skill without embedding the full Atoll guide, and terminal commands can use `atoll --profile agent-a ...`. For env-var mode, the installer writes `ATOLL_ENV_MODE=1` with the credential exports; open a fresh shell or `source` your profile.
51
+ For profile mode, Codex has the Atoll skill immediately, global Codex guidance points to that skill without embedding the full Atoll guide, and terminal commands can use `atoll --profile agent-a ...`. For explicitly requested env-var mode, the installer writes `ATOLL_ENV_MODE=1` with the credential exports; open a fresh shell or `source` your profile.
38
52
 
39
53
  Use `@latest` in the `npx` command so npm does not reuse a stale cached installer. In profile mode, the installer prints its package version and a verification command; run `atoll --profile agent-a agent-context --json` if you need to confirm the profile was created.
40
54
 
package/bin/install.mjs CHANGED
@@ -22,45 +22,86 @@ import { fileURLToPath } from 'node:url'
22
22
 
23
23
  const __dirname = dirname(fileURLToPath(import.meta.url))
24
24
  const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'))
25
+ const configurationFlags = new Set([
26
+ '--configure',
27
+ '--profile',
28
+ '--key',
29
+ '--org',
30
+ '--project',
31
+ '--no-project',
32
+ '--team',
33
+ '--no-team',
34
+ '--base-url',
35
+ '--no-base-url',
36
+ '--write-project-instructions',
37
+ '--project-dir',
38
+ '--instruction-files',
39
+ '--force-project-instructions',
40
+ ])
41
+ const valueFlags = {
42
+ '--profile': 'profile',
43
+ '--key': 'key',
44
+ '--org': 'org',
45
+ '--project': 'project',
46
+ '--team': 'team',
47
+ '--base-url': 'baseUrl',
48
+ '--project-dir': 'projectDir',
49
+ '--instruction-files': 'instructionFiles',
50
+ }
51
+
52
+ function hasConfigurationIntent(argv) {
53
+ return argv.slice(2).some((arg) => configurationFlags.has(arg.split('=', 1)[0]))
54
+ }
25
55
 
26
56
  function parseArgs(argv) {
27
57
  const args = {}
28
58
  for (let i = 2; i < argv.length; i++) {
29
- if (argv[i] === '--profile' && argv[i + 1]) args.profile = argv[++i]
30
- else if (argv[i] === '--key' && argv[i + 1]) args.key = argv[++i]
31
- else if (argv[i] === '--org' && argv[i + 1]) args.org = argv[++i]
32
- else if (argv[i] === '--project' && argv[i + 1]) args.project = argv[++i]
33
- else if (argv[i] === '--no-project') args.clearProject = true
34
- else if (argv[i] === '--team' && argv[i + 1]) args.team = argv[++i]
35
- else if (argv[i] === '--no-team') args.clearTeam = true
36
- else if (argv[i] === '--base-url' && argv[i + 1]) args.baseUrl = argv[++i]
37
- else if (argv[i] === '--no-base-url') args.clearBaseUrl = true
38
- else if (argv[i] === '--write-project-instructions') args.writeProjectInstructions = true
39
- else if (argv[i] === '--project-dir' && argv[i + 1]) args.projectDir = argv[++i]
40
- else if (argv[i] === '--instruction-files' && argv[i + 1]) args.instructionFiles = argv[++i]
41
- else if (argv[i] === '--force-project-instructions') args.forceProjectInstructions = true
42
- else if (argv[i] === '--help' || argv[i] === '-h') args.help = true
59
+ const argument = argv[i]
60
+ const separator = argument.indexOf('=')
61
+ const flag = separator === -1 ? argument : argument.slice(0, separator)
62
+ const inlineValue = separator === -1 ? undefined : argument.slice(separator + 1)
63
+ const nextValue = inlineValue ?? argv[i + 1]
64
+ const valueKey = valueFlags[flag]
65
+ if (valueKey) {
66
+ if (!nextValue || nextValue.startsWith('-')) {
67
+ throw new Error(`${flag} requires a non-empty value`)
68
+ }
69
+ args[valueKey] = nextValue
70
+ if (inlineValue === undefined) i++
71
+ } else if (flag === '--no-project') args.clearProject = true
72
+ else if (flag === '--no-team') args.clearTeam = true
73
+ else if (flag === '--no-base-url') args.clearBaseUrl = true
74
+ else if (flag === '--install-only') args.installOnly = true
75
+ else if (flag === '--configure') args.configure = true
76
+ else if (flag === '--write-project-instructions') args.writeProjectInstructions = true
77
+ else if (flag === '--force-project-instructions') args.forceProjectInstructions = true
78
+ else if (flag === '--help' || flag === '-h') args.help = true
79
+ else throw new Error(`Unknown option: ${flag}`)
43
80
  }
44
81
  return args
45
82
  }
46
83
 
47
84
  function printUsage() {
48
85
  console.log(`
49
- Usage: npx @atollhq/skill-codex@latest [--profile <name>] --key <api-key> --org <org-id> [--project <id>] [--team <id-or-slug>] [--base-url <url>]
50
- or: ATOLL_API_KEY=<api-key> ATOLL_ORG_ID=<org-id> npx @atollhq/skill-codex@latest
86
+ Usage: npx @atollhq/skill-codex@latest --install-only
87
+ or: npx @atollhq/skill-codex@latest --configure [--profile <name>] --key <api-key> --org <org-id> [--project <id>] [--team <id-or-slug>] [--base-url <url>]
88
+ or: ATOLL_API_KEY=<api-key> ATOLL_ORG_ID=<org-id> npx @atollhq/skill-codex@latest --configure
51
89
 
52
90
  Options:
53
- --profile Atoll CLI profile name to create/update. Profile mode does not write shell env vars.
54
- --key Atoll API key (sk_atoll_...). Defaults to ATOLL_API_KEY.
55
- --org Organization ID. Defaults to ATOLL_ORG_ID.
56
- --project Default project ID. Defaults to ATOLL_PROJECT.
91
+ --profile Atoll CLI profile name to create/update. Enables configuration mode.
92
+ --key Atoll API key (sk_atoll_...). Used in configuration mode; defaults to ATOLL_API_KEY.
93
+ --org Organization ID. Used in configuration mode; defaults to ATOLL_ORG_ID.
94
+ --project Default project ID. Used in configuration mode; defaults to ATOLL_PROJECT.
57
95
  --no-project Clear any saved default project.
58
- --team Default team ID or slug. Defaults to ATOLL_TEAM.
96
+ --team Default team ID or slug. Used in configuration mode; defaults to ATOLL_TEAM.
59
97
  --no-team Clear any saved default team.
60
- --base-url Atoll base URL. Defaults to ATOLL_BASE_URL.
98
+ --base-url Atoll base URL. Used in configuration mode; defaults to ATOLL_BASE_URL.
61
99
  --no-base-url Clear any saved custom base URL.
100
+ --install-only
101
+ Install or refresh local skill files without reading or changing credentials.
102
+ --configure Explicitly enable profile or environment credential configuration.
62
103
  --write-project-instructions
63
- Write a small repo-local Atoll profile block. Requires --profile.
104
+ Write a small repo-local Atoll profile block. Requires --configure and --profile.
64
105
  --project-dir
65
106
  Directory for repo-local instructions. Defaults to the current directory.
66
107
  --instruction-files
@@ -72,24 +113,35 @@ Options:
72
113
  Installs the Atoll integration for Codex CLI:
73
114
  - Installs the atoll skill to ~/.codex/skills/atoll/
74
115
  - Writes a small AGENTS.md routing hint to ~/.codex/
75
- - Creates/updates an Atoll CLI profile when --profile is provided
116
+ - Leaves authentication and profile configuration unchanged by default
117
+ - Creates/updates credentials only when --configure or another configuration flag is provided
76
118
  - Optionally writes repo-local AGENTS.md/CLAUDE.md profile guidance with --write-project-instructions
77
- - Otherwise writes Atoll env vars to your shell profile for env-var mode
78
119
  `)
79
120
  }
80
121
 
81
122
  const args = parseArgs(process.argv)
82
- args.key ??= process.env.ATOLL_API_KEY
83
- args.org ??= process.env.ATOLL_ORG_ID
84
- if (args.clearProject) delete args.project
85
- else args.project ??= process.env.ATOLL_PROJECT
86
- if (args.clearTeam) delete args.team
87
- else args.team ??= process.env.ATOLL_TEAM
88
- if (args.clearBaseUrl) delete args.baseUrl
89
- else args.baseUrl ??= process.env.ATOLL_BASE_URL
123
+ const configurationIntent = hasConfigurationIntent(process.argv)
124
+
125
+ if (args.installOnly && configurationIntent) {
126
+ console.error('Error: --install-only cannot be combined with credential or profile configuration flags')
127
+ printUsage()
128
+ process.exit(1)
129
+ }
90
130
 
91
131
  if (args.help) { printUsage(); process.exit(0) }
92
132
 
133
+ const configureMode = configurationIntent
134
+ if (configureMode) {
135
+ args.key ??= process.env.ATOLL_API_KEY
136
+ args.org ??= process.env.ATOLL_ORG_ID
137
+ if (args.clearProject) delete args.project
138
+ else args.project ??= process.env.ATOLL_PROJECT
139
+ if (args.clearTeam) delete args.team
140
+ else args.team ??= process.env.ATOLL_TEAM
141
+ if (args.clearBaseUrl) delete args.baseUrl
142
+ else args.baseUrl ??= process.env.ATOLL_BASE_URL
143
+ }
144
+
93
145
  console.log(`Running @atollhq/skill-codex installer v${packageJson.version}`)
94
146
 
95
147
  const instructionFileChoices = (args.instructionFiles ?? 'agents')
@@ -118,13 +170,13 @@ if (args.writeProjectInstructions && !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(args.p
118
170
  process.exit(1)
119
171
  }
120
172
 
121
- if (!args.key || !args.org) {
173
+ if (configureMode && (!args.key || !args.org)) {
122
174
  console.error('Error: provide --key and --org, or set ATOLL_API_KEY and ATOLL_ORG_ID\n')
123
175
  printUsage()
124
176
  process.exit(1)
125
177
  }
126
178
 
127
- if (!args.key.startsWith('sk_atoll_')) {
179
+ if (configureMode && !args.key.startsWith('sk_atoll_')) {
128
180
  console.error('Error: API key must start with sk_atoll_')
129
181
  process.exit(1)
130
182
  }
@@ -164,6 +216,52 @@ function ensurePrivateDirectory(path) {
164
216
  chmodSync(path, 0o700)
165
217
  }
166
218
 
219
+ function prepareSkillDirectory(path, source) {
220
+ const existing = lstatIfExists(path)
221
+ if (existing) {
222
+ if (existing.isSymbolicLink()) {
223
+ throw new Error(`Refusing symbolic link for skill directory: ${path}`)
224
+ }
225
+ if (!existing.isDirectory()) {
226
+ throw new Error(`Refusing non-directory skill path: ${path}`)
227
+ }
228
+ }
229
+
230
+ const parent = dirname(path)
231
+ mkdirSync(parent, { recursive: true })
232
+ const tempPath = join(parent, `.${basename(path)}.atoll-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}.tmp`)
233
+ const backupPath = `${tempPath}.backup`
234
+ let movedExisting = false
235
+ let installed = false
236
+ try {
237
+ cpSync(source, tempPath, { recursive: true })
238
+
239
+ const current = lstatIfExists(path)
240
+ if (current) {
241
+ if (current.isSymbolicLink()) {
242
+ throw new Error(`Refusing symbolic link for skill directory: ${path}`)
243
+ }
244
+ if (!current.isDirectory()) {
245
+ throw new Error(`Refusing non-directory skill path: ${path}`)
246
+ }
247
+ renameSync(path, backupPath)
248
+ movedExisting = true
249
+ }
250
+
251
+ renameSync(tempPath, path)
252
+ installed = true
253
+ if (movedExisting) rmSync(backupPath, { recursive: true, force: true })
254
+ } catch (error) {
255
+ if (!installed) {
256
+ if (lstatIfExists(tempPath)) rmSync(tempPath, { recursive: true, force: true })
257
+ if (movedExisting && !lstatIfExists(path) && lstatIfExists(backupPath)) {
258
+ renameSync(backupPath, path)
259
+ }
260
+ }
261
+ throw error
262
+ }
263
+ }
264
+
167
265
  function readJson(path) {
168
266
  assertSafeFile(path)
169
267
  if (!existsSync(path)) return {}
@@ -198,10 +296,27 @@ function writePrivateFile(path, content) {
198
296
  }
199
297
  }
200
298
 
201
- function acquireInstallerLock() {
202
- const atollDir = join(homedir(), '.atoll')
203
- const target = join(atollDir, 'installer.lock-target')
204
- ensurePrivateDirectory(atollDir)
299
+ const codexDir = resolve(process.env.CODEX_HOME || join(homedir(), '.codex'))
300
+ const defaultInstallerLockTarget = join(homedir(), '.atoll', 'installer.lock-target')
301
+ const sharedInstallerLockTarget = join(
302
+ homedir(),
303
+ '.atoll-skill-installer.lock-target',
304
+ )
305
+ const codexSkillLockTarget = join(codexDir, '.atoll-skill-installer.lock-target')
306
+
307
+ function ensureLockDirectory(path) {
308
+ const entry = lstatIfExists(path)
309
+ if (entry) {
310
+ const stat = entry.isSymbolicLink() ? statSync(path) : entry
311
+ if (!stat.isDirectory()) throw new Error(`Refusing non-directory installer lock path: ${path}`)
312
+ } else {
313
+ mkdirSync(path, { recursive: true, mode: 0o700 })
314
+ }
315
+ }
316
+
317
+ function acquireInstallerLock(target = defaultInstallerLockTarget) {
318
+ if (target === defaultInstallerLockTarget) ensurePrivateDirectory(dirname(target))
319
+ else ensureLockDirectory(dirname(target))
205
320
  try {
206
321
  writeFileSync(target, '', { flag: 'wx', mode: 0o600 })
207
322
  } catch (error) {
@@ -257,7 +372,23 @@ function acquireInstallerLock() {
257
372
  }
258
373
  }
259
374
 
260
- const releaseInstallerLock = acquireInstallerLock()
375
+ const releaseSharedInstallerLock = acquireInstallerLock(sharedInstallerLockTarget)
376
+ let releaseCodexSkillInstallerLock = null
377
+ let releaseConfigInstallerLock = null
378
+ try {
379
+ releaseCodexSkillInstallerLock = acquireInstallerLock(codexSkillLockTarget)
380
+ if (configureMode) releaseConfigInstallerLock = acquireInstallerLock()
381
+ } catch (error) {
382
+ releaseCodexSkillInstallerLock?.()
383
+ releaseSharedInstallerLock()
384
+ throw error
385
+ }
386
+
387
+ function releaseInstallerLock() {
388
+ releaseConfigInstallerLock?.()
389
+ releaseCodexSkillInstallerLock?.()
390
+ releaseSharedInstallerLock()
391
+ }
261
392
 
262
393
  function writeAtollProfile() {
263
394
  if (!args.profile) return null
@@ -422,14 +553,12 @@ function writeGlobalInstructions(targetPath) {
422
553
  }
423
554
 
424
555
  // 1. Install the Codex skill to ~/.codex/skills/atoll/
425
- const codexDir = join(homedir(), '.codex')
426
556
  mkdirSync(codexDir, { recursive: true })
427
557
 
428
558
  const skillDir = join(__dirname, '..', 'skill')
429
559
  const skillDest = join(codexDir, 'skills', 'atoll')
430
560
  const legacySkillDest = join(codexDir, 'skills', 'atoll-api')
431
- mkdirSync(skillDest, { recursive: true })
432
- cpSync(skillDir, skillDest, { recursive: true })
561
+ prepareSkillDirectory(skillDest, skillDir)
433
562
  console.log(`Installed Atoll skill to ${skillDest}`)
434
563
  if (existsSync(legacySkillDest)) {
435
564
  rmSync(legacySkillDest, { recursive: true, force: true })
@@ -449,6 +578,13 @@ for (const file of ['api-endpoints.md', 'api-fields.md']) {
449
578
  }
450
579
  console.log(`Copied API references to ${refsDir}`)
451
580
 
581
+ if (!configureMode) {
582
+ console.log('Authentication/profile configuration left unchanged.')
583
+ console.log('\nDone! Codex has the Atoll skill installed.')
584
+ releaseInstallerLock()
585
+ process.exit(0)
586
+ }
587
+
452
588
  const shell = process.env.SHELL || '/bin/bash'
453
589
  const profilePath = shell.includes('zsh')
454
590
  ? join(homedir(), '.zshrc')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atollhq/skill-codex",
3
- "version": "0.4.22",
3
+ "version": "0.4.24",
4
4
  "description": "Install the Atoll project management integration for Codex CLI",
5
5
  "bin": {
6
6
  "skill-codex": "bin/install.mjs"