ai4kanban 0.4.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/README.md +58 -0
- package/bin/ai4kanban.mjs +379 -0
- package/package.json +37 -0
- package/skill/SKILL.md +253 -0
- package/skill/config.md +32 -0
- package/skill/kanban.mjs +1670 -0
- package/skill/references/add-task.md +79 -0
- package/skill/references/auto-refine.md +32 -0
- package/skill/references/document-feature.md +31 -0
- package/skill/references/local-ui.md +34 -0
- package/skill/references/module-map.md +29 -0
- package/skill/references/presets/indie-hacker.md +24 -0
- package/skill/references/presets/validate-on-reddit.md +82 -0
- package/skill/references/propose.md +67 -0
- package/skill/references/prune-memory.md +29 -0
- package/skill/references/recurring-task.md +93 -0
- package/skill/references/refine.md +69 -0
- package/skill/references/resolve.md +56 -0
- package/skill/references/update.md +70 -0
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# ai4kanban
|
|
2
|
+
|
|
3
|
+
Set up and update [AI4Kanban](https://ai4kanban.dev/) in one command.
|
|
4
|
+
|
|
5
|
+
AI4Kanban is AI project management that grows with you: you give the agent a vague idea, it
|
|
6
|
+
breaks the idea down, settles what it can on its own, asks you the rest, and keeps going
|
|
7
|
+
until the spec is clear enough to build. The board is plain Markdown in `docs/kanban/`,
|
|
8
|
+
versioned in git.
|
|
9
|
+
|
|
10
|
+
This package is the setup command. The board and the skill are the product.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
From your project root:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx ai4kanban install
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
That copies the skill into `.claude/skills/kanban/` (Claude Code) and
|
|
21
|
+
`.agents/skills/kanban/` (Codex), then scaffolds `docs/kanban/` — the track folders, the
|
|
22
|
+
board index, the memory set, and a blank `config.md`.
|
|
23
|
+
|
|
24
|
+
Pass the tracks your project actually splits into:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx ai4kanban install --tracks feature,bug,research
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Normally you don't run this by hand. You paste the install prompt from
|
|
31
|
+
<https://ai4kanban.dev/INSTALL_PROMPT.txt> and your agent reads the repo, picks the tracks,
|
|
32
|
+
runs this command, and fills in the config afterwards.
|
|
33
|
+
|
|
34
|
+
## Update
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx ai4kanban update
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Overwrites every skill folder it finds with this version, repairs a board written by an
|
|
41
|
+
older release, and prints which version you moved from and to with a link to everything
|
|
42
|
+
that changed in between. Your cards, config, and memory are never touched.
|
|
43
|
+
|
|
44
|
+
## What it won't do
|
|
45
|
+
|
|
46
|
+
Reading your repo, filling in `docs/kanban/config.md`, writing the module map, proposing
|
|
47
|
+
tasks — all of that needs a judgement call, so it stays the agent's job. When this command
|
|
48
|
+
hits something it can't decide, it prints it under **Needs your attention** and leaves it
|
|
49
|
+
alone.
|
|
50
|
+
|
|
51
|
+
Both commands are safe to run twice.
|
|
52
|
+
|
|
53
|
+
## Also
|
|
54
|
+
|
|
55
|
+
- The board UI: `npx ai4kanban-ui` — a local page over the same Markdown files.
|
|
56
|
+
- Source and docs: <https://github.com/ai4kanban/ai4kanban>
|
|
57
|
+
|
|
58
|
+
Node 18+. No dependencies.
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
//
|
|
3
|
+
// ai4kanban — the one command that sets up and updates the board.
|
|
4
|
+
//
|
|
5
|
+
// npx ai4kanban install [--tracks a,b,c] copy the skill in, scaffold docs/kanban/
|
|
6
|
+
// npx ai4kanban update overwrite every installed skill folder, repair the board
|
|
7
|
+
// npx ai4kanban version print this package's version
|
|
8
|
+
//
|
|
9
|
+
// Why this exists: setup used to be a list of shell commands (`git clone`, `mkdir`, `cp -R`,
|
|
10
|
+
// a `printf`) that the user had to approve one at a time. This is one command instead.
|
|
11
|
+
//
|
|
12
|
+
// What it deliberately does NOT do: anything that needs a judgement call. Reading the repo,
|
|
13
|
+
// filling in `docs/kanban/config.md`, writing the module map, proposing the first tasks —
|
|
14
|
+
// those stay the agent's job. When this script meets something it can't decide, it says so
|
|
15
|
+
// under "Needs your attention" and leaves it alone.
|
|
16
|
+
//
|
|
17
|
+
// Node 18+, no dependencies, same behaviour on macOS, Linux and Windows.
|
|
18
|
+
|
|
19
|
+
import fs from 'node:fs'
|
|
20
|
+
import path from 'node:path'
|
|
21
|
+
import { fileURLToPath } from 'node:url'
|
|
22
|
+
import { spawnSync } from 'node:child_process'
|
|
23
|
+
|
|
24
|
+
const PKG_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
25
|
+
const VERSION = JSON.parse(fs.readFileSync(path.join(PKG_DIR, 'package.json'), 'utf8')).version
|
|
26
|
+
const REPO = 'https://github.com/ai4kanban/ai4kanban'
|
|
27
|
+
|
|
28
|
+
// Every harness that reads skills from a folder in the repo. Install writes both, so the
|
|
29
|
+
// same board works whichever agent the user opens tomorrow; update only touches the ones
|
|
30
|
+
// that are already there.
|
|
31
|
+
const SKILL_TARGETS = [
|
|
32
|
+
{ rel: path.join('.claude', 'skills', 'kanban'), agent: 'Claude Code' },
|
|
33
|
+
{ rel: path.join('.agents', 'skills', 'kanban'), agent: 'Codex' },
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
// The memory set used to sit at the board root before it moved into `memory/`.
|
|
37
|
+
const MEMORY_FILES = ['readme.md', 'goal.md', 'decisions.md', 'redesign.md', 'rejected.md']
|
|
38
|
+
|
|
39
|
+
// Both templates ship with this marker until someone fills them in.
|
|
40
|
+
const UNFILLED = '_(not filled in yet'
|
|
41
|
+
|
|
42
|
+
// ---- output ----------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
const did = []
|
|
45
|
+
const notes = []
|
|
46
|
+
|
|
47
|
+
function say(line) {
|
|
48
|
+
console.log(line)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function fail(msg) {
|
|
52
|
+
console.error(`ai4kanban: ${msg}`)
|
|
53
|
+
process.exit(1)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ---- files -----------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
function copyDir(src, dest) {
|
|
59
|
+
fs.mkdirSync(dest, { recursive: true })
|
|
60
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
61
|
+
const from = path.join(src, entry.name)
|
|
62
|
+
const to = path.join(dest, entry.name)
|
|
63
|
+
if (entry.isDirectory()) copyDir(from, to)
|
|
64
|
+
else fs.copyFileSync(from, to)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function read(file) {
|
|
69
|
+
return fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function statOf(p) {
|
|
73
|
+
try {
|
|
74
|
+
return fs.lstatSync(p)
|
|
75
|
+
} catch {
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// The skill folder to copy from: `skill/` inside the published package, or the repo's own
|
|
81
|
+
// `skill/` when this file is run straight out of a source checkout.
|
|
82
|
+
function bundledSkill() {
|
|
83
|
+
for (const dir of [path.join(PKG_DIR, 'skill'), path.join(PKG_DIR, '..', 'skill')]) {
|
|
84
|
+
if (fs.existsSync(path.join(dir, 'SKILL.md'))) return dir
|
|
85
|
+
}
|
|
86
|
+
fail('no skill folder inside this package — the published tarball is incomplete')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// The released version baked into an installed `kanban.mjs`, so update can say where the
|
|
90
|
+
// user is coming from. Null for an install made before the version was baked in.
|
|
91
|
+
function installedVersion(skillDir) {
|
|
92
|
+
const src = read(path.join(skillDir, 'kanban.mjs'))
|
|
93
|
+
const m = src && src.match(/const SKILL_VERSION = '([^']+)'/)
|
|
94
|
+
return m ? m[1] : null
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ---- placing the skill -----------------------------------------------------
|
|
98
|
+
|
|
99
|
+
// Copy the skill into each target folder, wholesale: the old folder is removed first, so a
|
|
100
|
+
// file upstream deleted doesn't linger. `mode` is 'install' (write every target) or
|
|
101
|
+
// 'update' (only refresh what's already there).
|
|
102
|
+
function placeSkill(root, mode) {
|
|
103
|
+
const source = bundledSkill()
|
|
104
|
+
const placed = []
|
|
105
|
+
for (const target of SKILL_TARGETS) {
|
|
106
|
+
const dest = path.join(root, target.rel)
|
|
107
|
+
const st = statOf(dest)
|
|
108
|
+
if (st && st.isSymbolicLink()) {
|
|
109
|
+
// A source checkout of this repo symlinks its skill folder at the real `skill/`.
|
|
110
|
+
// Copying over it would overwrite the source, so leave it alone and say so.
|
|
111
|
+
notes.push(`${target.rel} is a symlink — left untouched (it points at a source checkout)`)
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
114
|
+
if (!st && mode === 'update') continue
|
|
115
|
+
const before = st ? installedVersion(dest) : null
|
|
116
|
+
if (st) rescueSkillConfig(root, dest)
|
|
117
|
+
if (st) fs.rmSync(dest, { recursive: true, force: true })
|
|
118
|
+
copyDir(source, dest)
|
|
119
|
+
did.push(`${st ? 'replaced' : 'installed'} the skill in ${target.rel}/ (${target.agent})`)
|
|
120
|
+
placed.push({ dest, before, existed: Boolean(st) })
|
|
121
|
+
}
|
|
122
|
+
return placed
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// An install made before the config moved out of the skill folder keeps the user's filled-in
|
|
126
|
+
// `config.md` there — and the folder is about to be wiped. Move it to where it belongs now.
|
|
127
|
+
// It's the one file in the skill folder that was ever the user's.
|
|
128
|
+
function rescueSkillConfig(root, skillDir) {
|
|
129
|
+
const old = read(path.join(skillDir, 'config.md'))
|
|
130
|
+
if (!old || old.includes('{{')) return // still the blank template — nothing of the user's in it
|
|
131
|
+
const boardConfig = path.join(root, 'docs', 'kanban', 'config.md')
|
|
132
|
+
const current = read(boardConfig)
|
|
133
|
+
if (current === null) {
|
|
134
|
+
fs.mkdirSync(path.dirname(boardConfig), { recursive: true })
|
|
135
|
+
fs.writeFileSync(boardConfig, old)
|
|
136
|
+
did.push('moved your filled-in config.md out of the skill folder to docs/kanban/config.md')
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
if (current === old) return
|
|
140
|
+
const aside = path.join(root, 'docs', 'kanban', 'config.from-skill.md')
|
|
141
|
+
if (fs.existsSync(aside)) return
|
|
142
|
+
fs.writeFileSync(aside, old)
|
|
143
|
+
notes.push(
|
|
144
|
+
'the old skill folder held a different filled-in config.md — saved as docs/kanban/config.from-skill.md;' +
|
|
145
|
+
' fold anything worth keeping into docs/kanban/config.md and delete it',
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ---- the board -------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
function runKanban(skillDir, root, args) {
|
|
152
|
+
const result = spawnSync(process.execPath, [path.join(skillDir, 'kanban.mjs'), ...args], {
|
|
153
|
+
cwd: root,
|
|
154
|
+
stdio: 'inherit',
|
|
155
|
+
})
|
|
156
|
+
if (result.status !== 0) fail(`\`kanban.mjs ${args.join(' ')}\` failed — nothing else was changed`)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// The repair steps an update has to run on a board written by an older version. Each one is
|
|
160
|
+
// mechanical; anything with a choice in it becomes a note instead.
|
|
161
|
+
function repairBoard(skillDir, root) {
|
|
162
|
+
const board = path.join(root, 'docs', 'kanban')
|
|
163
|
+
if (!fs.existsSync(board)) {
|
|
164
|
+
notes.push('no docs/kanban/ here — run `npx ai4kanban install` to scaffold the board')
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
moveLegacyMemory(board)
|
|
168
|
+
sayDid()
|
|
169
|
+
// `init` on an existing board is the repair step: it adds what an older version never
|
|
170
|
+
// wrote (config.md, modules.md, the memory paths, the goal's `reviewed:` field) and never
|
|
171
|
+
// touches a file that's already filled in.
|
|
172
|
+
runKanban(skillDir, root, ['init'])
|
|
173
|
+
dropModuleGoals(board)
|
|
174
|
+
checkConfig(board)
|
|
175
|
+
checkModules(board)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// An older layout kept the memory set at the board root. Move each file into `memory/`.
|
|
179
|
+
function moveLegacyMemory(board) {
|
|
180
|
+
const memory = path.join(board, 'memory')
|
|
181
|
+
const moved = []
|
|
182
|
+
for (const name of MEMORY_FILES) {
|
|
183
|
+
const from = path.join(board, name)
|
|
184
|
+
const st = statOf(from)
|
|
185
|
+
if (!st || !st.isFile()) continue
|
|
186
|
+
fs.mkdirSync(memory, { recursive: true })
|
|
187
|
+
const to = path.join(memory, name)
|
|
188
|
+
const there = read(to)
|
|
189
|
+
if (there === null) {
|
|
190
|
+
fs.renameSync(from, to)
|
|
191
|
+
moved.push(name)
|
|
192
|
+
} else if (there === read(from)) {
|
|
193
|
+
fs.rmSync(from)
|
|
194
|
+
moved.push(name)
|
|
195
|
+
} else {
|
|
196
|
+
notes.push(`both docs/kanban/${name} and docs/kanban/memory/${name} exist and differ — merge them by hand`)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (moved.length) did.push(`moved the memory set into docs/kanban/memory/: ${moved.join(', ')}`)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// `goal.md` lives at the board root of `memory/` only. An older layout gave every module a
|
|
203
|
+
// copy; drop the ones that say nothing the root one doesn't, and report the rest.
|
|
204
|
+
function dropModuleGoals(board) {
|
|
205
|
+
const memory = path.join(board, 'memory')
|
|
206
|
+
if (!fs.existsSync(memory)) return
|
|
207
|
+
const root = read(path.join(memory, 'goal.md'))
|
|
208
|
+
let dropped = 0
|
|
209
|
+
for (const entry of fs.readdirSync(memory, { withFileTypes: true })) {
|
|
210
|
+
if (!entry.isDirectory()) continue
|
|
211
|
+
const file = path.join(memory, entry.name, 'goal.md')
|
|
212
|
+
const text = read(file)
|
|
213
|
+
if (text === null) continue
|
|
214
|
+
if (text === root || text.includes(UNFILLED)) {
|
|
215
|
+
fs.rmSync(file)
|
|
216
|
+
dropped++
|
|
217
|
+
} else {
|
|
218
|
+
notes.push(
|
|
219
|
+
`docs/kanban/memory/${entry.name}/goal.md says something the root goal doesn't —` +
|
|
220
|
+
' fold that into docs/kanban/memory/goal.md, then delete it',
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (dropped) did.push(`removed ${dropped} leftover per-module goal.md (the goal lives at docs/kanban/memory/goal.md)`)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// A setting this release ships that the user's config has never heard of. Naming it is all
|
|
228
|
+
// a script can do — the value is the user's to choose.
|
|
229
|
+
function checkConfig(board) {
|
|
230
|
+
const template = read(path.join(bundledSkill(), 'config.md'))
|
|
231
|
+
const current = read(path.join(board, 'config.md'))
|
|
232
|
+
if (!template || !current) return
|
|
233
|
+
const keys = (text) => [...text.matchAll(/^- \*\*(.+?)\*\*/gm)].map((m) => m[1])
|
|
234
|
+
const added = keys(template).filter((k) => !keys(current).includes(k))
|
|
235
|
+
if (added.length) {
|
|
236
|
+
notes.push(`this release adds ${added.map((k) => `**${k}**`).join(', ')} to the config — add the line to docs/kanban/config.md and fill it in`)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function checkModules(board) {
|
|
241
|
+
const map = read(path.join(board, 'modules.md'))
|
|
242
|
+
if (map && map.includes(UNFILLED)) {
|
|
243
|
+
notes.push('docs/kanban/modules.md is still blank — write it from the repo (the skill\'s references/module-map.md), then re-run this command so every module gets a memory path')
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ---- commands --------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
function cmdInstall(root, tracks) {
|
|
250
|
+
say(`ai4kanban ${VERSION} — installing into ${root}`)
|
|
251
|
+
const placed = placeSkill(root, 'install')
|
|
252
|
+
if (!placed.length) {
|
|
253
|
+
sayNotes()
|
|
254
|
+
fail('nothing to install — every skill folder here is a symlink')
|
|
255
|
+
}
|
|
256
|
+
say('')
|
|
257
|
+
sayDid()
|
|
258
|
+
say('')
|
|
259
|
+
runKanban(placed[0].dest, root, ['init', ...tracks])
|
|
260
|
+
sayNotes()
|
|
261
|
+
say('')
|
|
262
|
+
say('Next, and only an agent can do these:')
|
|
263
|
+
say(' 1. fill in docs/kanban/config.md from what the repo tells you')
|
|
264
|
+
say(' 2. write docs/kanban/modules.md (the skill\'s references/module-map.md says how)')
|
|
265
|
+
say(' 3. propose the first 3 tasks')
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function cmdUpdate(root) {
|
|
269
|
+
say(`ai4kanban ${VERSION} — updating ${root}`)
|
|
270
|
+
const placed = placeSkill(root, 'update')
|
|
271
|
+
const from = placed.map((p) => p.before).find(Boolean)
|
|
272
|
+
if (!placed.length) {
|
|
273
|
+
// Nothing copied is a normal outcome: a plugin install keeps the skill in a read-only
|
|
274
|
+
// cache and a source checkout symlinks it. The board still needs repairing either way,
|
|
275
|
+
// so carry on with the skill this package ships.
|
|
276
|
+
notes.push('no skill folder to overwrite here — expected for a plugin install; repairing the board only')
|
|
277
|
+
}
|
|
278
|
+
say('')
|
|
279
|
+
sayDid()
|
|
280
|
+
repairBoard(placed[0]?.dest || bundledSkill(), root)
|
|
281
|
+
sayDid()
|
|
282
|
+
sayNotes()
|
|
283
|
+
say('')
|
|
284
|
+
if (!placed.length) {
|
|
285
|
+
say(`This package is ${VERSION}. Every release: ${REPO}/releases`)
|
|
286
|
+
} else if (!from) {
|
|
287
|
+
say(`Now at ${VERSION}. Every release: ${REPO}/releases`)
|
|
288
|
+
} else if (from === VERSION) {
|
|
289
|
+
say(`Already at ${VERSION} — the files were re-copied anyway, which changes nothing.`)
|
|
290
|
+
} else {
|
|
291
|
+
say(`Moved from ${from} to ${VERSION}.`)
|
|
292
|
+
say(`Everything that changed: ${REPO}/compare/v${from}...v${VERSION}`)
|
|
293
|
+
}
|
|
294
|
+
say('Your cards, config, and memory were left alone. Review `git diff` before committing.')
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Print the changes made since the last call, so each block of output sits next to the step
|
|
298
|
+
// that produced it instead of all landing at the end.
|
|
299
|
+
let reported = 0
|
|
300
|
+
function sayDid() {
|
|
301
|
+
for (const line of did.slice(reported)) say(` · ${line}`)
|
|
302
|
+
reported = did.length
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function sayNotes() {
|
|
306
|
+
if (!notes.length) return
|
|
307
|
+
say('')
|
|
308
|
+
say('Needs your attention:')
|
|
309
|
+
for (const line of notes) say(` ! ${line}`)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const HELP = `ai4kanban ${VERSION} — set up and update the AI4Kanban board.
|
|
313
|
+
|
|
314
|
+
npx ai4kanban install [--tracks a,b,c] copy the skill into .claude/skills/kanban/ and
|
|
315
|
+
.agents/skills/kanban/, then scaffold docs/kanban/
|
|
316
|
+
npx ai4kanban update overwrite every skill folder that's already here
|
|
317
|
+
and repair a board written by an older version
|
|
318
|
+
npx ai4kanban version print this version
|
|
319
|
+
npx ai4kanban help this text
|
|
320
|
+
|
|
321
|
+
Options
|
|
322
|
+
--tracks a,b,c the board's tracks (install only). Default: feature,bug,research
|
|
323
|
+
--dir <path> the project to work on. Default: the current folder
|
|
324
|
+
|
|
325
|
+
Both commands are safe to run twice. Neither one edits your cards, your config, or your
|
|
326
|
+
memory — filling those in is the agent's job.
|
|
327
|
+
|
|
328
|
+
Docs: ${REPO}
|
|
329
|
+
`
|
|
330
|
+
|
|
331
|
+
// ---- entry -----------------------------------------------------------------
|
|
332
|
+
|
|
333
|
+
function parse(argv) {
|
|
334
|
+
const opts = { tracks: [], dir: process.cwd() }
|
|
335
|
+
const rest = []
|
|
336
|
+
for (let i = 0; i < argv.length; i++) {
|
|
337
|
+
const arg = argv[i]
|
|
338
|
+
if (arg === '--tracks' || arg === '--track') {
|
|
339
|
+
opts.tracks = String(argv[++i] || '')
|
|
340
|
+
.split(',')
|
|
341
|
+
.map((t) => t.trim())
|
|
342
|
+
.filter(Boolean)
|
|
343
|
+
} else if (arg.startsWith('--tracks=')) {
|
|
344
|
+
opts.tracks = arg.slice('--tracks='.length).split(',').map((t) => t.trim()).filter(Boolean)
|
|
345
|
+
} else if (arg === '--dir') {
|
|
346
|
+
opts.dir = path.resolve(String(argv[++i] || '.'))
|
|
347
|
+
} else if (arg.startsWith('--dir=')) {
|
|
348
|
+
opts.dir = path.resolve(arg.slice('--dir='.length))
|
|
349
|
+
} else {
|
|
350
|
+
rest.push(arg)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return { opts, rest }
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function main() {
|
|
357
|
+
const { opts, rest } = parse(process.argv.slice(2))
|
|
358
|
+
const command = rest[0]
|
|
359
|
+
if (!fs.existsSync(opts.dir)) fail(`no such folder: ${opts.dir}`)
|
|
360
|
+
switch (command) {
|
|
361
|
+
case 'install':
|
|
362
|
+
return cmdInstall(opts.dir, opts.tracks)
|
|
363
|
+
case 'update':
|
|
364
|
+
return cmdUpdate(opts.dir)
|
|
365
|
+
case 'version':
|
|
366
|
+
case '--version':
|
|
367
|
+
case '-v':
|
|
368
|
+
return say(VERSION)
|
|
369
|
+
case undefined:
|
|
370
|
+
case 'help':
|
|
371
|
+
case '--help':
|
|
372
|
+
case '-h':
|
|
373
|
+
return say(HELP)
|
|
374
|
+
default:
|
|
375
|
+
fail(`unknown command "${command}" — try \`npx ai4kanban help\``)
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
main()
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ai4kanban",
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"description": "Set up and update AI4Kanban in one command — copies the skill into your project and scaffolds the Markdown board under docs/kanban/.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"kanban",
|
|
7
|
+
"claude",
|
|
8
|
+
"claude-code",
|
|
9
|
+
"codex",
|
|
10
|
+
"agent",
|
|
11
|
+
"skill",
|
|
12
|
+
"markdown",
|
|
13
|
+
"board"
|
|
14
|
+
],
|
|
15
|
+
"license": "Apache-2.0",
|
|
16
|
+
"homepage": "https://ai4kanban.dev/",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/ai4kanban/ai4kanban.git",
|
|
20
|
+
"directory": "cli"
|
|
21
|
+
},
|
|
22
|
+
"type": "module",
|
|
23
|
+
"bin": {
|
|
24
|
+
"ai4kanban": "bin/ai4kanban.mjs"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"bin/",
|
|
28
|
+
"skill/"
|
|
29
|
+
],
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18.0.0"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"bundle": "node scripts/bundle-skill.mjs",
|
|
35
|
+
"prepublishOnly": "node ../scripts/sync-version.mjs --check && npm run bundle"
|
|
36
|
+
}
|
|
37
|
+
}
|