@atollhq/skill-claude 0.4.23 → 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 +19 -5
- package/bin/install.mjs +166 -42
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,17 +6,31 @@ Gives your Claude Code agent the ability to manage tasks, goals, KPIs, initiativ
|
|
|
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-claude@latest
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Bare invocation and `--install-only` only update `~/.claude/skills/atoll/`. They do not inspect or change Atoll credentials, profiles, shell files, or Claude settings, even when `ATOLL_*` variables are present.
|
|
16
|
+
|
|
17
|
+
Configure a named profile explicitly:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx --yes @atollhq/skill-claude@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-claude@latest --
|
|
11
|
-
# or
|
|
12
|
-
ATOLL_API_KEY=sk_atoll_... ATOLL_ORG_ID=your-org-id npx @atollhq/skill-claude@latest
|
|
26
|
+
ATOLL_API_KEY=sk_atoll_... ATOLL_ORG_ID=org-uuid npx --yes @atollhq/skill-claude@latest --configure
|
|
13
27
|
```
|
|
14
28
|
|
|
15
|
-
|
|
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 a global `ATOLL_PROFILE` or `ATOLL_*` credential setting in profile mode, so direct CLI commands should use `atoll --profile agent-a ...`.
|
|
16
30
|
|
|
17
31
|
Get an agent API key from **Agents** in the Atoll app. Integration keys are managed from **Settings > Integrations**.
|
|
18
32
|
|
|
19
|
-
|
|
33
|
+
Every invocation copies the skill. Configuration mode additionally:
|
|
20
34
|
|
|
21
35
|
1. Copies the skill into `~/.claude/skills/atoll/`
|
|
22
36
|
2. Removes stale Atoll credential/profile env from `~/.claude/settings.json` when profile mode is used
|
package/bin/install.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
readFileSync,
|
|
12
12
|
renameSync,
|
|
13
13
|
rmSync,
|
|
14
|
+
statSync,
|
|
14
15
|
writeFileSync,
|
|
15
16
|
} from 'node:fs'
|
|
16
17
|
import { basename, dirname, join } from 'node:path'
|
|
@@ -20,30 +21,60 @@ import { fileURLToPath } from 'node:url'
|
|
|
20
21
|
|
|
21
22
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
22
23
|
const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'))
|
|
24
|
+
const configurationFlags = new Set([
|
|
25
|
+
'--configure',
|
|
26
|
+
'--profile',
|
|
27
|
+
'--key',
|
|
28
|
+
'--org',
|
|
29
|
+
'--project',
|
|
30
|
+
'--no-project',
|
|
31
|
+
'--team',
|
|
32
|
+
'--no-team',
|
|
33
|
+
'--base-url',
|
|
34
|
+
'--no-base-url',
|
|
35
|
+
])
|
|
36
|
+
const valueFlags = {
|
|
37
|
+
'--profile': 'profile',
|
|
38
|
+
'--key': 'key',
|
|
39
|
+
'--org': 'org',
|
|
40
|
+
'--project': 'project',
|
|
41
|
+
'--team': 'team',
|
|
42
|
+
'--base-url': 'baseUrl',
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function hasConfigurationIntent(argv) {
|
|
46
|
+
return argv.slice(2).some((arg) => configurationFlags.has(arg.split('=', 1)[0]))
|
|
47
|
+
}
|
|
23
48
|
|
|
24
49
|
function parseArgs(argv) {
|
|
25
50
|
const args = {}
|
|
26
51
|
for (let i = 2; i < argv.length; i++) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
52
|
+
const argument = argv[i]
|
|
53
|
+
const separator = argument.indexOf('=')
|
|
54
|
+
const flag = separator === -1 ? argument : argument.slice(0, separator)
|
|
55
|
+
const inlineValue = separator === -1 ? undefined : argument.slice(separator + 1)
|
|
56
|
+
const nextValue = inlineValue ?? argv[i + 1]
|
|
57
|
+
const valueKey = valueFlags[flag]
|
|
58
|
+
if (valueKey) {
|
|
59
|
+
if (!nextValue || nextValue.startsWith('-')) {
|
|
60
|
+
throw new Error(`${flag} requires a non-empty value`)
|
|
61
|
+
}
|
|
62
|
+
args[valueKey] = nextValue
|
|
63
|
+
if (inlineValue === undefined) i++
|
|
64
|
+
} else if (flag === '--no-project') {
|
|
36
65
|
args.clearProject = true
|
|
37
|
-
} else if (
|
|
38
|
-
args.team = argv[++i]
|
|
39
|
-
} else if (argv[i] === '--no-team') {
|
|
66
|
+
} else if (flag === '--no-team') {
|
|
40
67
|
args.clearTeam = true
|
|
41
|
-
} else if (
|
|
42
|
-
args.baseUrl = argv[++i]
|
|
43
|
-
} else if (argv[i] === '--no-base-url') {
|
|
68
|
+
} else if (flag === '--no-base-url') {
|
|
44
69
|
args.clearBaseUrl = true
|
|
45
|
-
} else if (
|
|
70
|
+
} else if (flag === '--install-only') {
|
|
71
|
+
args.installOnly = true
|
|
72
|
+
} else if (flag === '--configure') {
|
|
73
|
+
args.configure = true
|
|
74
|
+
} else if (flag === '--help' || flag === '-h') {
|
|
46
75
|
args.help = true
|
|
76
|
+
} else {
|
|
77
|
+
throw new Error(`Unknown option: ${flag}`)
|
|
47
78
|
}
|
|
48
79
|
}
|
|
49
80
|
return args
|
|
@@ -51,50 +82,67 @@ function parseArgs(argv) {
|
|
|
51
82
|
|
|
52
83
|
function printUsage() {
|
|
53
84
|
console.log(`
|
|
54
|
-
Usage: npx @atollhq/skill-claude@latest
|
|
55
|
-
or:
|
|
85
|
+
Usage: npx @atollhq/skill-claude@latest --install-only
|
|
86
|
+
or: npx @atollhq/skill-claude@latest --configure [--profile <name>] --key <api-key> --org <org-id> [--project <id>] [--team <id-or-slug>] [--base-url <url>]
|
|
87
|
+
or: ATOLL_API_KEY=<api-key> ATOLL_ORG_ID=<org-id> npx @atollhq/skill-claude@latest --configure
|
|
56
88
|
|
|
57
89
|
Options:
|
|
58
|
-
--profile Atoll CLI profile name to create/update.
|
|
59
|
-
--key Atoll API key (sk_atoll_...).
|
|
60
|
-
--org Organization ID.
|
|
61
|
-
--project Default project ID.
|
|
90
|
+
--profile Atoll CLI profile name to create/update. Enables configuration mode.
|
|
91
|
+
--key Atoll API key (sk_atoll_...). Used in configuration mode; defaults to ATOLL_API_KEY.
|
|
92
|
+
--org Organization ID. Used in configuration mode; defaults to ATOLL_ORG_ID.
|
|
93
|
+
--project Default project ID. Used in configuration mode; defaults to ATOLL_PROJECT.
|
|
62
94
|
--no-project Clear any saved default project.
|
|
63
|
-
--team Default team ID or slug.
|
|
95
|
+
--team Default team ID or slug. Used in configuration mode; defaults to ATOLL_TEAM.
|
|
64
96
|
--no-team Clear any saved default team.
|
|
65
|
-
--base-url Atoll base URL.
|
|
97
|
+
--base-url Atoll base URL. Used in configuration mode; defaults to ATOLL_BASE_URL.
|
|
66
98
|
--no-base-url Clear any saved custom base URL.
|
|
99
|
+
--install-only
|
|
100
|
+
Install or refresh local skill files without reading or changing credentials.
|
|
101
|
+
--configure Explicitly enable profile or environment credential configuration.
|
|
67
102
|
--help Show this help message
|
|
68
103
|
|
|
69
104
|
This installs the Atoll skill for Claude Code, giving your agent
|
|
70
|
-
the ability to manage tasks, goals, KPIs, and initiatives.
|
|
105
|
+
the ability to manage tasks, goals, KPIs, and initiatives. Authentication
|
|
106
|
+
and profile configuration remain unchanged unless --configure or another
|
|
107
|
+
configuration flag is provided.
|
|
71
108
|
`)
|
|
72
109
|
}
|
|
73
110
|
|
|
74
111
|
const args = parseArgs(process.argv)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (args.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
else args.baseUrl ??= process.env.ATOLL_BASE_URL
|
|
112
|
+
const configurationIntent = hasConfigurationIntent(process.argv)
|
|
113
|
+
|
|
114
|
+
if (args.installOnly && configurationIntent) {
|
|
115
|
+
console.error('Error: --install-only cannot be combined with credential or profile configuration flags')
|
|
116
|
+
printUsage()
|
|
117
|
+
process.exit(1)
|
|
118
|
+
}
|
|
83
119
|
|
|
84
120
|
if (args.help) {
|
|
85
121
|
printUsage()
|
|
86
122
|
process.exit(0)
|
|
87
123
|
}
|
|
88
124
|
|
|
125
|
+
const configureMode = configurationIntent
|
|
126
|
+
if (configureMode) {
|
|
127
|
+
args.key ??= process.env.ATOLL_API_KEY
|
|
128
|
+
args.org ??= process.env.ATOLL_ORG_ID
|
|
129
|
+
if (args.clearProject) delete args.project
|
|
130
|
+
else args.project ??= process.env.ATOLL_PROJECT
|
|
131
|
+
if (args.clearTeam) delete args.team
|
|
132
|
+
else args.team ??= process.env.ATOLL_TEAM
|
|
133
|
+
if (args.clearBaseUrl) delete args.baseUrl
|
|
134
|
+
else args.baseUrl ??= process.env.ATOLL_BASE_URL
|
|
135
|
+
}
|
|
136
|
+
|
|
89
137
|
console.log(`Running @atollhq/skill-claude installer v${packageJson.version}`)
|
|
90
138
|
|
|
91
|
-
if (!args.key || !args.org) {
|
|
139
|
+
if (configureMode && (!args.key || !args.org)) {
|
|
92
140
|
console.error('Error: provide --key and --org, or set ATOLL_API_KEY and ATOLL_ORG_ID\n')
|
|
93
141
|
printUsage()
|
|
94
142
|
process.exit(1)
|
|
95
143
|
}
|
|
96
144
|
|
|
97
|
-
if (!args.key.startsWith('sk_atoll_')) {
|
|
145
|
+
if (configureMode && !args.key.startsWith('sk_atoll_')) {
|
|
98
146
|
console.error('Error: API key must start with sk_atoll_')
|
|
99
147
|
process.exit(1)
|
|
100
148
|
}
|
|
@@ -134,6 +182,52 @@ function ensurePrivateDirectory(path) {
|
|
|
134
182
|
chmodSync(path, 0o700)
|
|
135
183
|
}
|
|
136
184
|
|
|
185
|
+
function prepareSkillDirectory(path, source) {
|
|
186
|
+
const existing = lstatIfExists(path)
|
|
187
|
+
if (existing) {
|
|
188
|
+
if (existing.isSymbolicLink()) {
|
|
189
|
+
throw new Error(`Refusing symbolic link for skill directory: ${path}`)
|
|
190
|
+
}
|
|
191
|
+
if (!existing.isDirectory()) {
|
|
192
|
+
throw new Error(`Refusing non-directory skill path: ${path}`)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const parent = dirname(path)
|
|
197
|
+
mkdirSync(parent, { recursive: true })
|
|
198
|
+
const tempPath = join(parent, `.${basename(path)}.atoll-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}.tmp`)
|
|
199
|
+
const backupPath = `${tempPath}.backup`
|
|
200
|
+
let movedExisting = false
|
|
201
|
+
let installed = false
|
|
202
|
+
try {
|
|
203
|
+
cpSync(source, tempPath, { recursive: true })
|
|
204
|
+
|
|
205
|
+
const current = lstatIfExists(path)
|
|
206
|
+
if (current) {
|
|
207
|
+
if (current.isSymbolicLink()) {
|
|
208
|
+
throw new Error(`Refusing symbolic link for skill directory: ${path}`)
|
|
209
|
+
}
|
|
210
|
+
if (!current.isDirectory()) {
|
|
211
|
+
throw new Error(`Refusing non-directory skill path: ${path}`)
|
|
212
|
+
}
|
|
213
|
+
renameSync(path, backupPath)
|
|
214
|
+
movedExisting = true
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
renameSync(tempPath, path)
|
|
218
|
+
installed = true
|
|
219
|
+
if (movedExisting) rmSync(backupPath, { recursive: true, force: true })
|
|
220
|
+
} catch (error) {
|
|
221
|
+
if (!installed) {
|
|
222
|
+
if (lstatIfExists(tempPath)) rmSync(tempPath, { recursive: true, force: true })
|
|
223
|
+
if (movedExisting && !lstatIfExists(path) && lstatIfExists(backupPath)) {
|
|
224
|
+
renameSync(backupPath, path)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
throw error
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
137
231
|
function readJson(path) {
|
|
138
232
|
assertSafeFile(path)
|
|
139
233
|
if (!existsSync(path)) return {}
|
|
@@ -163,10 +257,22 @@ function writePrivateFile(path, content) {
|
|
|
163
257
|
}
|
|
164
258
|
}
|
|
165
259
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
260
|
+
const defaultInstallerLockTarget = join(homedir(), '.atoll', 'installer.lock-target')
|
|
261
|
+
const sharedInstallerLockTarget = join(homedir(), '.atoll-skill-installer.lock-target')
|
|
262
|
+
|
|
263
|
+
function ensureLockDirectory(path) {
|
|
264
|
+
const entry = lstatIfExists(path)
|
|
265
|
+
if (entry) {
|
|
266
|
+
const stat = entry.isSymbolicLink() ? statSync(path) : entry
|
|
267
|
+
if (!stat.isDirectory()) throw new Error(`Refusing non-directory installer lock path: ${path}`)
|
|
268
|
+
} else {
|
|
269
|
+
mkdirSync(path, { recursive: true, mode: 0o700 })
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function acquireInstallerLock(target = defaultInstallerLockTarget) {
|
|
274
|
+
if (target === defaultInstallerLockTarget) ensurePrivateDirectory(dirname(target))
|
|
275
|
+
else ensureLockDirectory(dirname(target))
|
|
170
276
|
try {
|
|
171
277
|
writeFileSync(target, '', { flag: 'wx', mode: 0o600 })
|
|
172
278
|
} catch (error) {
|
|
@@ -222,7 +328,19 @@ function acquireInstallerLock() {
|
|
|
222
328
|
}
|
|
223
329
|
}
|
|
224
330
|
|
|
225
|
-
const
|
|
331
|
+
const releaseSharedInstallerLock = acquireInstallerLock(sharedInstallerLockTarget)
|
|
332
|
+
let releaseConfigInstallerLock = null
|
|
333
|
+
try {
|
|
334
|
+
if (configureMode) releaseConfigInstallerLock = acquireInstallerLock()
|
|
335
|
+
} catch (error) {
|
|
336
|
+
releaseSharedInstallerLock()
|
|
337
|
+
throw error
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function releaseInstallerLock() {
|
|
341
|
+
releaseConfigInstallerLock?.()
|
|
342
|
+
releaseSharedInstallerLock()
|
|
343
|
+
}
|
|
226
344
|
|
|
227
345
|
function writeAtollProfile() {
|
|
228
346
|
if (!args.profile) return
|
|
@@ -255,14 +373,20 @@ const skillDest = join(homedir(), '.claude', 'skills', 'atoll')
|
|
|
255
373
|
const legacySkillDest = join(homedir(), '.claude', 'skills', 'atoll-api')
|
|
256
374
|
ensurePrivateDirectory(join(homedir(), '.claude'))
|
|
257
375
|
|
|
258
|
-
|
|
259
|
-
cpSync(skillSrc, skillDest, { recursive: true })
|
|
376
|
+
prepareSkillDirectory(skillDest, skillSrc)
|
|
260
377
|
console.log(`Installed skill to ${skillDest}`)
|
|
261
378
|
if (existsSync(legacySkillDest)) {
|
|
262
379
|
rmSync(legacySkillDest, { recursive: true, force: true })
|
|
263
380
|
console.log(`Removed legacy Atoll skill at ${legacySkillDest}`)
|
|
264
381
|
}
|
|
265
382
|
|
|
383
|
+
if (!configureMode) {
|
|
384
|
+
console.log('Authentication/profile configuration left unchanged.')
|
|
385
|
+
console.log('\nDone! Start Claude Code and the atoll skill will be available.')
|
|
386
|
+
releaseInstallerLock()
|
|
387
|
+
process.exit(0)
|
|
388
|
+
}
|
|
389
|
+
|
|
266
390
|
// 2. Configure ~/.claude/settings.json. Profile mode stores credentials only
|
|
267
391
|
// in the Atoll CLI profile and removes stale credential/profile env from
|
|
268
392
|
// earlier installs so a new profile install does not become globally active.
|