skillflow-ai 0.1.0 → 0.3.0

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
@@ -25,6 +25,11 @@ npx skillflow-ai add <slug> --agent codex # into ~/.codex/skills
25
25
  npx skillflow-ai add <slug> --project # into ./.claude/skills of this folder
26
26
  npx skillflow-ai add <slug> --dir <folder> # any folder (Antigravity and others)
27
27
  npx skillflow-ai update # update everything you installed
28
+ npx skillflow-ai themes # colour themes for the result
29
+ npx skillflow-ai add <slug> --theme hijau-daun # result in that theme (THEME.md in the skill)
30
+ npx skillflow-ai add <slug> --colors "#1F5E3B,#F2B33D" # or your own two colours
31
+ npx skillflow-ai add <slug> --antislop=core,ui,copywriting # Anti Slop rules in the skill (antislop/)
32
+ npx skillflow-ai antislop # the Anti Slop parts
28
33
  npx skillflow-ai remove <slug>
29
34
  npx skillflow-ai whoami
30
35
  npx skillflow-ai logout
@@ -32,4 +37,6 @@ npx skillflow-ai logout
32
37
 
33
38
  If you have a brand profile on the directory, every skill you add carries it as `BRAND.md`.
34
39
 
40
+ `--antislop` adds the [Anti Slop](https://github.com/miqdadbadjuber/anti-slop) rules (MIT) as an `antislop/` folder the skill reads before it starts. The core is always included; parts: `ui`, `copywriting`, `human`, `mobile`, `code`. `update` keeps the choice; `--antislop=off` removes it.
41
+
35
42
  The token stops working when your account leaves Pro, or when you revoke it on the directory.
package/bin/skillflow.mjs CHANGED
@@ -36,7 +36,7 @@ function parse(argv) {
36
36
  if (a.startsWith('--')) {
37
37
  const [k, v] = a.slice(2).split('=')
38
38
  if (v !== undefined) opts[k] = v
39
- else if (argv[i + 1] && !argv[i + 1].startsWith('-') && ['agent', 'dir', 'server', 'kind', 'token'].includes(k)) opts[k] = argv[++i]
39
+ else if (argv[i + 1] && !argv[i + 1].startsWith('-') && ['agent', 'dir', 'server', 'kind', 'token', 'theme', 'colors', 'antislop'].includes(k)) opts[k] = argv[++i]
40
40
  else opts[k] = true
41
41
  } else if (a === '-h') opts.help = true
42
42
  else args.push(a)
@@ -76,9 +76,37 @@ function targetDir(opts) {
76
76
  return AGENTS[agent]
77
77
  }
78
78
 
79
- async function install(slug, dir) {
79
+ // Colour theme of the result, sent as the same `?tema=` the directory's ZIP link uses. undefined = not given,
80
+ // '' = no theme (the skill's own colours; also how --theme default clears a saved one).
81
+ function themeQuery(opts) {
82
+ if (opts.colors && opts.colors !== true) {
83
+ const c = String(opts.colors).split(',').map(s => s.trim())
84
+ if (c.length !== 2 || !c.every(x => /^#?[0-9a-fA-F]{6}$/.test(x))) fail('--colors takes two hex colours: --colors "#1F5E3B,#F2B33D"')
85
+ const hex = c.map(x => (x.startsWith('#') ? x : '#' + x).toUpperCase())
86
+ return `?tema=custom&utama=${encodeURIComponent(hex[0])}&aksen=${encodeURIComponent(hex[1])}`
87
+ }
88
+ if (opts.theme === undefined) return undefined
89
+ if (opts.theme === true) fail('Name a theme: --theme <name> (see: npx skillflow-ai themes)')
90
+ if (['default', 'none'].includes(opts.theme)) return ''
91
+ if (!/^[a-z-]{1,40}$/.test(opts.theme)) fail(`"${opts.theme}" is not a theme name. See: npx skillflow-ai themes`)
92
+ return `?tema=${opts.theme}`
93
+ }
94
+
95
+ // Anti Slop rules (antislop/ folder in the skill), sent as the same `?antislop=` the directory's ZIP link uses.
96
+ // undefined = not given, '' = off (also how --antislop=off clears a saved choice). The core is always part of it.
97
+ function antislopValue(opts) {
98
+ if (opts.antislop === undefined) return undefined
99
+ if (opts.antislop === true) return 'core'
100
+ const v = String(opts.antislop).toLowerCase().replace(/\s+/g, '')
101
+ if (['off', 'none', 'no'].includes(v)) return ''
102
+ if (!/^[a-z]+(,[a-z]+)*$/.test(v)) fail('--antislop takes parts separated by commas: --antislop=core,ui,copywriting (see: npx skillflow-ai antislop)')
103
+ return v
104
+ }
105
+
106
+ async function install(slug, dir, theme = '', antislop = '') {
80
107
  if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(slug)) fail(`"${slug}" is not a skill name.`)
81
- const { buf, version } = await api(`/api/skills/v1/skills/${slug}/bundle`, { raw: true })
108
+ const q = [theme.replace(/^\?/, ''), antislop ? 'antislop=' + encodeURIComponent(antislop) : ''].filter(Boolean).join('&')
109
+ const { buf, version } = await api(`/api/skills/v1/skills/${slug}/bundle${q ? '?' + q : ''}`, { raw: true })
82
110
  const entries = readZip(buf)
83
111
  // Bundles hold "<slug>/…". Anything outside that folder is ignored.
84
112
  const own = entries.filter(e => e.name === slug + '/' || e.name.startsWith(slug + '/'))
@@ -98,7 +126,7 @@ async function install(slug, dir) {
98
126
  rmSync(final, { recursive: true, force: true })
99
127
  renameSync(tmp, final)
100
128
  const list = readJson(INSTALLED, {})
101
- list[final] = { slug, version: version || null, dir, at: new Date().toISOString() }
129
+ list[final] = { slug, version: version || null, dir, theme: theme || null, antislop: antislop || null, at: new Date().toISOString() }
102
130
  writePrivate(INSTALLED, list)
103
131
  return { final, version }
104
132
  }
@@ -109,10 +137,16 @@ const HELP = `skillflow ${VERSION}: install SkillFlow skills into your AI agent
109
137
  npx skillflow-ai whoami who the token belongs to
110
138
  npx skillflow-ai list [--kind video] skills you can install
111
139
  npx skillflow-ai add <slug> [<slug>…] install into ~/.claude/skills
140
+ --theme <name> | --colors "#hex,#hex" colour theme of the result (THEME.md)
141
+ --antislop=core,ui,copywriting Anti Slop rules in the skill (antislop/)
112
142
  --agent codex ~/.codex/skills instead
113
143
  --project ./.claude/skills (or ./.codex/skills) in this folder
114
144
  --dir <folder> any folder (Antigravity and other agents)
115
145
  npx skillflow-ai update [<slug>] update installed skills to the latest version
146
+ --theme <name> | --colors … | --theme default change the colour theme while updating
147
+ --antislop=<parts> | --antislop=off change the Anti Slop rules while updating
148
+ npx skillflow-ai themes list the colour themes
149
+ npx skillflow-ai antislop list the Anti Slop parts
116
150
  npx skillflow-ai remove <slug> [--dir <folder>] delete an installed skill
117
151
  npx skillflow-ai logout forget the token on this computer
118
152
  `
@@ -153,8 +187,10 @@ async function main() {
153
187
  case 'add': {
154
188
  if (!args.length) fail('Name a skill: npx skillflow-ai add <slug>')
155
189
  const dir = targetDir(opts)
190
+ const theme = themeQuery(opts) ?? ''
191
+ const antislop = antislopValue(opts) ?? ''
156
192
  for (const slug of args) {
157
- const r = await install(slug, dir)
193
+ const r = await install(slug, dir, theme, antislop)
158
194
  say(`✓ ${slug}${r.version ? ' v' + r.version : ''} → ${r.final}`)
159
195
  }
160
196
  break
@@ -165,16 +201,36 @@ async function main() {
165
201
  if (!want.length) { say('Nothing installed yet.'); break }
166
202
  const { skills } = await api('/api/skills/v1/catalog')
167
203
  const latest = Object.fromEntries(skills.map(s => [s.slug, s.version]))
204
+ // The theme a skill was installed with is kept; --theme / --colors replaces it (and reinstalls).
205
+ const given = themeQuery(opts)
206
+ const givenAs = antislopValue(opts)
168
207
  let n = 0
169
208
  for (const [, r] of want) {
170
209
  if (!latest[r.slug]) { say(`- ${r.slug}: no longer in the catalog (left as it is)`); continue }
171
- if (latest[r.slug] === r.version && !opts.force) continue
172
- const res = await install(r.slug, r.dir)
210
+ const theme = given ?? (r.theme || '')
211
+ const antislop = givenAs ?? (r.antislop || '')
212
+ if (latest[r.slug] === r.version && !opts.force && theme === (r.theme || '') && antislop === (r.antislop || '')) continue
213
+ const res = await install(r.slug, r.dir, theme, antislop)
173
214
  say(`✓ ${r.slug} ${r.version || '?'} → ${res.version || latest[r.slug]}`); n++
174
215
  }
175
216
  say(n ? `${n} skill(s) updated.` : 'Everything is up to date.')
176
217
  break
177
218
  }
219
+ case 'antislop': {
220
+ const { antislop } = await api('/api/skills/v1/catalog')
221
+ if (!antislop || !antislop.length) { say('This directory has no Anti Slop rules yet (update the server).'); break }
222
+ for (const k of antislop) say(' ' + k)
223
+ say('\nUse with: npx skillflow-ai add <slug> --antislop=core,ui (the core is always included). Rules: https://github.com/miqdadbadjuber/anti-slop (MIT)')
224
+ break
225
+ }
226
+ case 'themes': {
227
+ const { themes } = await api('/api/skills/v1/catalog')
228
+ if (!themes || !themes.length) { say('This directory has no colour themes yet.'); break }
229
+ for (const t of themes) say(' ' + t)
230
+ say('\nUse one with: npx skillflow-ai add <slug> --theme ' + themes[0])
231
+ say('Or your own two colours: --colors "#1F5E3B,#F2B33D". Without either, the skill keeps its own colours.')
232
+ break
233
+ }
178
234
  case 'remove': {
179
235
  if (!args.length) fail('Name a skill: npx skillflow-ai remove <slug>')
180
236
  const list = readJson(INSTALLED, {})
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillflow-ai",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Install SkillFlow AI skills into Claude Code, Codex or any agent folder with one command.",
5
5
  "type": "module",
6
6
  "bin": { "skillflow-ai": "bin/skillflow.mjs", "skillflow": "bin/skillflow.mjs" },