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.
Files changed (2) hide show
  1. package/bin/attaform.mjs +55 -8
  2. 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
- const DEFAULT_SKILLS_DIR = '.claude/skills'
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
- ` [dir] Skills directory to install into (default: ${DEFAULT_SKILLS_DIR}).`,
34
- ' Point it at another runtime, e.g. attaform skill .cursor/skills',
35
- ].join('\n'),
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
- installSkill(dirArg ?? DEFAULT_SKILLS_DIR)
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(`attaform: could not install the skill: ${error instanceof Error ? error.message : String(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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "attaform",
3
- "version": "0.27.0",
3
+ "version": "0.27.1",
4
4
  "main": "./dist/index.mjs",
5
5
  "types": "./dist/index.d.mts",
6
6
  "files": [