attaform 0.27.0 → 0.27.1
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/bin/attaform.mjs +55 -8
- package/package.json +1 -1
package/bin/attaform.mjs
CHANGED
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
* lockstep with the installed Attaform version: run through `npx` in a
|
|
9
9
|
* project that already has Attaform and it copies that version's skill.
|
|
10
10
|
*
|
|
11
|
+
* With no `[dir]`, it places the skill next to whichever assistants the
|
|
12
|
+
* project already uses (`.claude/`, `.cursor/`, `.codex/`, `.agents/`)
|
|
13
|
+
* and falls back to the vendor-neutral `.agents/skills/` when none is
|
|
14
|
+
* present. `SKILL.md` is one portable format, so a single copy is read
|
|
15
|
+
* natively by Claude Code, Cursor, Codex, OpenCode, and the rest.
|
|
16
|
+
*
|
|
11
17
|
* Zero dependencies (Node built-ins only), so it ships verbatim with no
|
|
12
18
|
* build step.
|
|
13
19
|
*/
|
|
@@ -20,7 +26,22 @@ import { fileURLToPath } from 'node:url'
|
|
|
20
26
|
// package.json `files` entry, so it is present in both the git checkout
|
|
21
27
|
// and the installed package.
|
|
22
28
|
const SKILL_SOURCE = fileURLToPath(new URL('../skills/attaform/', import.meta.url))
|
|
23
|
-
|
|
29
|
+
|
|
30
|
+
// The vendor-neutral skills directory: read by OpenCode, Cursor, Cline,
|
|
31
|
+
// and others, and the destination when a project has not committed to a
|
|
32
|
+
// single assistant yet.
|
|
33
|
+
const NEUTRAL_SKILLS_DIR = '.agents/skills'
|
|
34
|
+
|
|
35
|
+
// Marker directory an assistant creates -> the skills directory it reads.
|
|
36
|
+
// `SKILL.md` is one portable format, so the same files drop into each.
|
|
37
|
+
// Detection is intentionally narrow: only paths a project would not have
|
|
38
|
+
// for another reason (unlike `.github/`, which nearly every repo carries).
|
|
39
|
+
const ASSISTANT_SKILL_DIRS = [
|
|
40
|
+
{ marker: '.claude', skillsDir: '.claude/skills' },
|
|
41
|
+
{ marker: '.cursor', skillsDir: '.cursor/skills' },
|
|
42
|
+
{ marker: '.codex', skillsDir: '.codex/skills' },
|
|
43
|
+
{ marker: '.agents', skillsDir: NEUTRAL_SKILLS_DIR },
|
|
44
|
+
]
|
|
24
45
|
|
|
25
46
|
function usage() {
|
|
26
47
|
console.log(
|
|
@@ -30,9 +51,12 @@ function usage() {
|
|
|
30
51
|
" Copy Attaform's Agent Skill into your project so an AI assistant",
|
|
31
52
|
' loads it while working on a form.',
|
|
32
53
|
'',
|
|
33
|
-
|
|
34
|
-
'
|
|
35
|
-
|
|
54
|
+
' With no [dir], the skill lands next to each assistant the project',
|
|
55
|
+
' already uses (.claude, .cursor, .codex, .agents), or',
|
|
56
|
+
` ${NEUTRAL_SKILLS_DIR}/ when none is present.`,
|
|
57
|
+
'',
|
|
58
|
+
' [dir] Skills directory to install into, e.g. attaform skill .cursor/skills',
|
|
59
|
+
].join('\n')
|
|
36
60
|
)
|
|
37
61
|
}
|
|
38
62
|
|
|
@@ -44,6 +68,21 @@ function countFiles(dir) {
|
|
|
44
68
|
return total
|
|
45
69
|
}
|
|
46
70
|
|
|
71
|
+
// The skills directories to install into. An explicit arg wins verbatim.
|
|
72
|
+
// Otherwise place next to each assistant the project already uses, and
|
|
73
|
+
// fall back to the vendor-neutral directory when none is detected.
|
|
74
|
+
function resolveTargets(dirArg) {
|
|
75
|
+
if (dirArg !== undefined) return { dirs: [dirArg], detected: true }
|
|
76
|
+
|
|
77
|
+
const dirs = ASSISTANT_SKILL_DIRS.filter(({ marker }) =>
|
|
78
|
+
existsSync(resolve(process.cwd(), marker))
|
|
79
|
+
).map(({ skillsDir }) => skillsDir)
|
|
80
|
+
|
|
81
|
+
return dirs.length > 0
|
|
82
|
+
? { dirs, detected: true }
|
|
83
|
+
: { dirs: [NEUTRAL_SKILLS_DIR], detected: false }
|
|
84
|
+
}
|
|
85
|
+
|
|
47
86
|
function installSkill(skillsDir) {
|
|
48
87
|
const dest = resolve(skillsDir, 'attaform')
|
|
49
88
|
const existed = existsSync(dest)
|
|
@@ -52,9 +91,8 @@ function installSkill(skillsDir) {
|
|
|
52
91
|
const where = relative(process.cwd(), dest) || dest
|
|
53
92
|
const count = countFiles(dest)
|
|
54
93
|
console.log(
|
|
55
|
-
`${existed ? 'Updated' : 'Installed'} the Attaform skill (${count} file${count === 1 ? '' : 's'}) at ${where}
|
|
94
|
+
`${existed ? 'Updated' : 'Installed'} the Attaform skill (${count} file${count === 1 ? '' : 's'}) at ${where}/`
|
|
56
95
|
)
|
|
57
|
-
console.log('Your assistant will load it while it works on a form in this project.')
|
|
58
96
|
}
|
|
59
97
|
|
|
60
98
|
const [command, dirArg] = process.argv.slice(2)
|
|
@@ -71,8 +109,17 @@ if (command !== 'skill') {
|
|
|
71
109
|
}
|
|
72
110
|
|
|
73
111
|
try {
|
|
74
|
-
|
|
112
|
+
const { dirs, detected } = resolveTargets(dirArg)
|
|
113
|
+
for (const dir of dirs) installSkill(dir)
|
|
114
|
+
if (!detected) {
|
|
115
|
+
console.log(
|
|
116
|
+
`No assistant directory found, so the skill went to the vendor-neutral ${NEUTRAL_SKILLS_DIR}/. Pass a path (e.g. attaform skill .claude/skills) to target a specific assistant.`
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
console.log('Your assistant will load it while it works on a form in this project.')
|
|
75
120
|
} catch (error) {
|
|
76
|
-
console.error(
|
|
121
|
+
console.error(
|
|
122
|
+
`attaform: could not install the skill: ${error instanceof Error ? error.message : String(error)}`
|
|
123
|
+
)
|
|
77
124
|
process.exit(1)
|
|
78
125
|
}
|