ai4kanban 0.6.0 → 0.7.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
@@ -24,6 +24,11 @@ without a chat session and without a browser.
24
24
 
25
25
  ## Get the command
26
26
 
27
+ The [board app](https://ai4kanban.dev/download) brings `akb` with it — the first open puts
28
+ it on your PATH and it updates when the app does, so if you have the app you have the
29
+ command. Install it on its own where the app can't run — a server, a container, or a Linux
30
+ box, whose AppImage leaves no command behind:
31
+
27
32
  ```bash
28
33
  npm install -g ai4kanban
29
34
  ```
@@ -60,12 +65,17 @@ akb skill install # add it, or bring an older copy up to date
60
65
  That writes one file into `.claude/skills/kanban/` (Claude Code) and
61
66
  `.agents/skills/kanban/` (Codex): `SKILL.md`, a short note telling a coding agent the board
62
67
  is here and that `akb` owns it. The board app does the same thing from a button:
63
- **Configuration → Skill**.
68
+ **Configuration → Agent setup**.
64
69
 
65
70
  Nothing else is copied in. The flows ship inside the command (`akb guide`), so a newer
66
71
  command is newer flows in every project at once — and the command itself stays where npm put
67
- it, so a project's git history never carries 350 kB of it. An agent that finds no `akb` on
68
- the PATH runs `npx --yes ai4kanban@latest <command>` instead; the note says so.
72
+ it, so a project's git history never carries 350 kB of it.
73
+
74
+ An agent that finds no `akb` on the PATH doesn't stop: the note's first section says what to
75
+ run instead — the copy in this project where there is one, and otherwise `npx --yes
76
+ ai4kanban@<the version that wrote the note>`, pinned so the rules match the board. A run
77
+ started from the app or the CLI is told the same thing in its own words, and `akb skill`
78
+ says it to you at the moment the note lands.
69
79
 
70
80
  ## Update
71
81
 
@@ -124,7 +134,8 @@ akb stop 3f2a1b04 # end one
124
134
  akb resume 3f2a1b04 # continue one that failed
125
135
  ```
126
136
 
127
- Which agent runs them — Claude Code, Codex, Cursor or OpenCode — and what it is set to:
137
+ Which agent runs them — Claude Code, Codex, Cursor, OpenCode or DeepSeek Harness — and
138
+ what it is set to:
128
139
 
129
140
  ```bash
130
141
  akb agent # what runs, and how it is set up
package/bin/ai4kanban.mjs CHANGED
@@ -4,8 +4,8 @@
4
4
  // `npx --yes ai4kanban@latest <command>` runs it without installing anything.
5
5
  //
6
6
  // akb install [--tracks a,b,c] scaffold docs/kanban/
7
- // akb skill [install] add the coding agent skill to this project, or say
8
- // whether it is there
7
+ // akb skill [install|refresh] add the coding agent skill to this project, rewrite the
8
+ // copy it has, or say whether it is there
9
9
  // akb update refresh an installed skill, repair the board, and say
10
10
  // the one line that puts a newer command on your path
11
11
  // akb board <move> the board's own bookkeeping — the agent's commands
@@ -44,6 +44,50 @@ const REPO = 'https://github.com/ai4kanban/ai4kanban'
44
44
  const GET_LINE = `npm install -g ${NAME}`
45
45
  const NEWER_LINE = `npm install -g ${NAME}@latest`
46
46
 
47
+ // How this command was reached, spelled so what it prints can be pasted straight back.
48
+ //
49
+ // A global install puts it on the PATH as `akb`, and every example everywhere spells it
50
+ // that way. It is also run as a path — `node cli/bin/ai4kanban.mjs` in a source checkout,
51
+ // the copy inside the desktop app — and printing `akb` to a reader who has none is printing
52
+ // a line that ends in `command not found`. Whoever typed it is right here in argv, so ask
53
+ // that rather than guessing: only the PATH spellings are `akb`, `.mjs` never is.
54
+ //
55
+ // Not what's on the PATH, which is a different question with a different answer: `npx` puts
56
+ // an `akb` on the PATH of THIS process and nothing else, so the reader would still be given
57
+ // a line their own shell can't run. That one is spotted by the cache it runs out of, and
58
+ // answered with the same fetch pinned to this version — never `@latest`, which is how a
59
+ // board comes to be driven by two versions of its own rules.
60
+ //
61
+ // AI4KANBAN_COMMAND is how the desktop app's launcher says what it was typed as. That
62
+ // launcher is reached as `akb` on the PATH and then runs this file by its path, so argv
63
+ // alone would report the copy inside the app — a line no reader can paste.
64
+ const PROGRAM = (() => {
65
+ const named = (process.env.AI4KANBAN_COMMAND || '').trim()
66
+ if (named) return named
67
+ const entry = process.argv[1] || ''
68
+ if (/[\\/](_npx|dlx)[\\/]/.test(entry)) return `npx --yes ${NAME}@${VERSION}`
69
+ const base = path.basename(entry).toLowerCase().replace(/\.(cmd|ps1|exe)$/, '')
70
+ if (base === 'akb' || base === NAME) return 'akb'
71
+ // Relative to where it was typed when that is shorter and still runs — a checkout says
72
+ // `node cli/bin/ai4kanban.mjs`, which reads inside a sentence; an absolute path doesn't.
73
+ const near = path.relative(process.cwd(), entry)
74
+ return `node ${near && !near.startsWith('..') ? near : entry}`
75
+ })()
76
+
77
+ // One line, when this copy isn't `akb`, for the text that spells it `akb` throughout — the
78
+ // help, and the flows a `--print` hands over. Cheaper than rewriting either, and it holds
79
+ // for the lines inside them that this command never wrote.
80
+ const SPELLED = PROGRAM === 'akb' ? '' : `This copy isn't on your PATH as \`akb\` — every \`akb\` below is \`${PROGRAM}\` here.`
81
+
82
+ // How the help opens: what the command is called, plus that line when it isn't called that.
83
+ const INTRO = [
84
+ 'Installed, it is `akb`. Without installing anything, every line below also works as\n' +
85
+ '`npx --yes ai4kanban@latest <command>` — the same command, fetched each time.',
86
+ SPELLED,
87
+ ]
88
+ .filter(Boolean)
89
+ .join('\n\n')
90
+
47
91
  // The memory set used to sit at the board root before it moved into `memory/`.
48
92
  const MEMORY_FILES = ['readme.md', 'goal.md', 'decisions.md', 'redesign.md', 'rejected.md']
49
93
 
@@ -127,7 +171,10 @@ async function placeSkill(root, mode) {
127
171
  for (const folder of before) {
128
172
  if (folder.state !== 'absent' && folder.state !== 'linked') rescueSkillConfig(root, path.join(root, folder.path))
129
173
  }
130
- const result = installSkill(root, mode === 'update' ? 'present' : undefined)
174
+ // How this command was typed goes into the note: an install run through `npx` is the one
175
+ // case the rules can't read off the machine, because npx's `akb` is on this process's
176
+ // PATH and no shell the agent will ever open.
177
+ const result = installSkill(root, mode === 'update' ? 'present' : undefined, PROGRAM)
131
178
  for (const w of result.wrote) did.push(`${w.refreshed ? 'refreshed' : 'wrote'} ${w.path}/ — ${w.files} (${w.agent})`)
132
179
  for (const s of result.skipped) notes.push(`${s.path} — ${s.why}`)
133
180
  // A project that has one agent's folder but not the other's. Update never writes a folder
@@ -282,27 +329,41 @@ function cmdInstall(root, tracks) {
282
329
  say('That is the board. Nothing was written outside docs/kanban/.')
283
330
  say('')
284
331
  say('To drive this board from your coding agent, add the skill — from the button in the')
285
- say('board UI (Configuration → Skill), or here:')
332
+ say('board UI (Configuration → Agent setup), or here:')
286
333
  say('')
287
- say(' akb skill')
334
+ say(` ${PROGRAM} skill`)
288
335
  }
289
336
 
290
- // Add the skill to a project, or say where it stands. The whole move belongs to the built
291
- // rules; this prints what they did.
292
- async function cmdSkill(root, install) {
337
+ // Add the skill to a project, rewrite the copy it has, or say where it stands. The whole
338
+ // move belongs to the built rules; this prints what they did.
339
+ //
340
+ // `refresh` writes no folder that isn't already there. It is how a note learns a new
341
+ // spelling of the command without a project gaining a skill it never asked for — the
342
+ // desktop app runs it right after putting `akb` on the PATH, so the note stops naming the
343
+ // copy inside the app.
344
+ async function cmdSkill(root, mode) {
293
345
  const { readSkillState } = await rules()
294
346
  if (typeof readSkillState !== 'function') {
295
347
  fail('this copy of the board rules is too old to install the skill — `npm install -g ai4kanban@latest`')
296
348
  }
297
- if (!install) {
349
+ if (mode === 'refresh') {
350
+ const { result } = await placeSkill(root, 'update')
351
+ sayDid()
352
+ sayNotes()
353
+ if (!result.ok && result.error) fail(result.error)
354
+ if (!result.wrote.length) say('No skill in this project — nothing to rewrite.')
355
+ return
356
+ }
357
+ if (!mode) {
298
358
  const state = readSkillState(root)
299
359
  say(`ai4kanban ${VERSION} — the coding agent skill in ${root}`)
300
360
  say('')
301
361
  for (const folder of state.folders) say(` ${folder.path}/ — ${sayFolder(folder)} (${folder.agent})`)
302
362
  say('')
303
- if (!state.installed) say('Not installed. `akb skill install` writes it, and so does the board UI\'s button.')
304
- else if (state.outdated) say('Older than this command. `akb skill install` brings it up to date.')
363
+ if (!state.installed) say(`Not installed. \`${PROGRAM} skill install\` writes it, and so does the board UI's button.`)
364
+ else if (state.outdated) say(`Older than this command. \`${PROGRAM} skill install\` brings it up to date.`)
305
365
  else say('Up to date. Your coding agent can drive this board.')
366
+ await sayPathState()
306
367
  return
307
368
  }
308
369
  say(`ai4kanban ${VERSION} — adding the coding agent skill to ${root}`)
@@ -312,14 +373,33 @@ async function cmdSkill(root, install) {
312
373
  sayNotes()
313
374
  if (!result.ok) fail(result.error || 'nothing was written')
314
375
  say('')
315
- say('The flows the agent works by ship with the command — `akb guide` — so they upgrade')
316
- say('with it and no copy in this repo can fall behind.')
376
+ say(`The flows the agent works by ship with the command — \`${PROGRAM} guide\` — so they`)
377
+ say('upgrade with it and no copy in this repo can fall behind.')
378
+ await sayPathState()
317
379
  say('')
318
380
  say('Now say this to your coding agent to try it:')
319
381
  say('')
320
382
  say(` ${SETUP_INSTRUCTION}`)
321
383
  }
322
384
 
385
+ // Whether the agent that reads the note it just got will find the command the note tells it
386
+ // to type. Said here, where the skill lands, rather than left for the agent's first board
387
+ // command to discover — that one comes back `command not found`, and an agent that meets
388
+ // that mid-task stops and asks instead of doing the work.
389
+ //
390
+ // The note itself carries the fallback, so nothing is broken either way. This is so the
391
+ // user knows what their agent is about to do, and what one line would spare it.
392
+ async function sayPathState() {
393
+ const { readCommandState } = await rules()
394
+ const command = typeof readCommandState === 'function' ? readCommandState() : null
395
+ if (!command || command.onPath) return
396
+ say('')
397
+ say('There is no `akb` on your PATH. The note tells your agent what to run instead — the')
398
+ say(`copy in this project, or \`npx --yes ${NAME}@${VERSION}\`. One line makes it direct:`)
399
+ say('')
400
+ say(` ${GET_LINE}`)
401
+ }
402
+
323
403
  function sayFolder(folder) {
324
404
  if (folder.state === 'absent') return 'not installed'
325
405
  if (folder.state === 'linked') return 'a symlink into a source checkout — never written over'
@@ -438,13 +518,15 @@ const HELP = `ai4kanban ${VERSION} — set up and update the AI4Kanban board.
438
518
  Get the command: ${GET_LINE}
439
519
  Move to a newer one: ${NEWER_LINE}
440
520
 
441
- Installed, it is \`akb\`. Without installing anything, every line below also works as
442
- \`npx --yes ai4kanban@latest <command>\` — the same command, fetched each time.
521
+ ${INTRO}
443
522
 
444
523
  akb install [--tracks a,b,c] scaffold docs/kanban/ — the board, and nothing else
445
524
  akb skill whether a coding agent can drive this board
446
- akb skill install add the skill: SKILL.md and kanban.mjs into
447
- .claude/skills/kanban/ and .agents/skills/kanban/
525
+ akb skill install add the skill: SKILL.md into .claude/skills/kanban/
526
+ and .agents/skills/kanban/
527
+ akb skill refresh rewrite a skill that is already here, and write none
528
+ that isn't — how the note learns a new spelling of the
529
+ command
448
530
  akb update refresh an installed skill, repair a board written by an
449
531
  older version, and say if a newer command is out
450
532
  akb version print this version
@@ -456,7 +538,7 @@ Options
456
538
 
457
539
  Installing writes the board and nothing outside docs/kanban/. Driving that board from a
458
540
  coding agent is a later extra — \`akb skill install\`, or the button in the board UI under
459
- Configuration → Skill. The flows the agent works by are not copied anywhere either: they
541
+ Configuration → Agent setup. The flows the agent works by are not copied anywhere either: they
460
542
  ship inside this command (\`akb guide\`), so updating the command updates every flow in
461
543
  every project at once.
462
544
 
@@ -522,9 +604,9 @@ function parse(argv) {
522
604
  async function cmdBoard(args) {
523
605
  const { runBoard } = await rules()
524
606
  return runBoard(args, {
525
- program: 'akb board',
607
+ program: `${PROGRAM} board`,
526
608
  style: 'board',
527
- installHint: '`akb install`',
609
+ installHint: `\`${PROGRAM} install\``,
528
610
  })
529
611
  }
530
612
 
@@ -545,6 +627,8 @@ const RUN_COMMANDS = new Set([
545
627
  'setup',
546
628
  'archive',
547
629
  'reject',
630
+ // Put a spec agent on a card — a named agent that fills one part of its spec (#187).
631
+ 'spec',
548
632
  'runs',
549
633
  'log',
550
634
  'stop',
@@ -561,7 +645,7 @@ async function cmdRun(args) {
561
645
  if (typeof runAgent !== 'function') {
562
646
  fail('this copy of the board rules is too old to run agents — `npm install -g ai4kanban@latest`, then `akb update`')
563
647
  }
564
- return runAgent(args, { program: 'akb', installHint: '`akb install`' })
648
+ return runAgent(args, { program: PROGRAM, installHint: `\`${PROGRAM} install\`` })
565
649
  }
566
650
 
567
651
  async function main() {
@@ -601,7 +685,7 @@ async function main() {
601
685
  // one on purpose — the same rule the run commands follow, where the move that changes
602
686
  // something is asked for by name.
603
687
  case 'skill':
604
- return cmdSkill(opts.dir, rest[1] === 'install')
688
+ return cmdSkill(opts.dir, rest[1] === 'install' ? 'install' : rest[1] === 'refresh' ? 'refresh' : null)
605
689
  case 'update':
606
690
  return cmdUpdate(opts.dir)
607
691
  case 'version':