kandown 0.1.3 β 0.2.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/bin/kandown.js +117 -55
- package/bin/tui.js +18977 -729
- package/dist/index.html +350 -345
- package/package.json +5 -3
package/bin/kandown.js
CHANGED
|
@@ -25,6 +25,16 @@
|
|
|
25
25
|
* @exports none
|
|
26
26
|
*/
|
|
27
27
|
/* eslint-disable no-console */
|
|
28
|
+
// π DEV=false prevents Ink from loading react-devtools-core (CJS-only, breaks ESM).
|
|
29
|
+
// Must be set BEFORE any imports because ESM hoists all import statements.
|
|
30
|
+
process.env.DEV = 'false';
|
|
31
|
+
// π Polyfill browser globals that some bundled modules expect.
|
|
32
|
+
if (typeof globalThis.self === 'undefined') Object.defineProperty(globalThis, 'self', { value: globalThis });
|
|
33
|
+
if (typeof globalThis.window === 'undefined') Object.defineProperty(globalThis, 'window', { value: globalThis });
|
|
34
|
+
// π Make require() available in this ESM module so bundled __require() shims work.
|
|
35
|
+
// tsup's __require checks `typeof require !== "undefined"` β this makes it truthy.
|
|
36
|
+
import { createRequire } from 'node:module';
|
|
37
|
+
globalThis.require = createRequire(import.meta.url);
|
|
28
38
|
|
|
29
39
|
import { fileURLToPath } from 'node:url';
|
|
30
40
|
import { createServer } from 'node:http';
|
|
@@ -47,6 +57,51 @@ const PKG_ROOT = resolve(__dirname, '..');
|
|
|
47
57
|
const DEFAULT_SERVE_PORT = 2048;
|
|
48
58
|
const MAX_SERVE_PORT = 2060;
|
|
49
59
|
|
|
60
|
+
// π Get current CLI version from package.json at PKG_ROOT
|
|
61
|
+
function getCurrentVersion() {
|
|
62
|
+
try {
|
|
63
|
+
const pkg = JSON.parse(readFileSync(join(PKG_ROOT, 'package.json'), 'utf8'));
|
|
64
|
+
return pkg.version;
|
|
65
|
+
} catch { return null; }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// π Check npm for a newer version and auto-update if outdated.
|
|
69
|
+
// Runs in background β does not block startup. Only activates when running from
|
|
70
|
+
// an installed npm package (not local dev source, where src/ exists).
|
|
71
|
+
// π Uses npm install -g to self-upgrade, then re-spawns with the same arguments.
|
|
72
|
+
async function checkForUpdate(argv = process.argv) {
|
|
73
|
+
if (existsSync(join(PKG_ROOT, 'src'))) return; // local dev β skip
|
|
74
|
+
const current = getCurrentVersion();
|
|
75
|
+
if (!current) return;
|
|
76
|
+
try {
|
|
77
|
+
const { execSync } = await import('node:child_process');
|
|
78
|
+
const latest = String(execSync('npm view kandown version --json 2>/dev/null', {
|
|
79
|
+
timeout: 8000, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
80
|
+
})).trim().replace(/^"|"$/g, '');
|
|
81
|
+
if (!latest || latest === current) return;
|
|
82
|
+
|
|
83
|
+
log(`${c.yellow}β‘ Auto-updating kandown ${c.reset}${c.dim}${current}${c.reset} ${c.yellow}β${c.reset} ${c.green}${latest}${c.reset}β¦`);
|
|
84
|
+
try {
|
|
85
|
+
execSync('npm install -g kandown 2>/dev/null', {
|
|
86
|
+
timeout: 30000, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
87
|
+
});
|
|
88
|
+
const newVersion = String(execSync('npm view kandown version 2>/dev/null', {
|
|
89
|
+
timeout: 5000, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
90
|
+
})).trim().replace(/^"|"$/g, '');
|
|
91
|
+
if (newVersion === latest) {
|
|
92
|
+
log(`${c.green}β Updated to v${newVersion}${c.reset} β restartingβ¦`);
|
|
93
|
+
const child = spawn(process.argv[0], ['--experimental-vm-modules', ...argv.slice(1)], {
|
|
94
|
+
detached: true, stdio: 'ignore', env: { ...process.env } });
|
|
95
|
+
child.unref();
|
|
96
|
+
process.exit(0);
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
log(`${c.yellow}β Auto-update failed β will retry on next run${c.reset}`);
|
|
100
|
+
log(` Run ${c.cyan}npm install -g kandown${c.reset} to upgrade manually`);
|
|
101
|
+
}
|
|
102
|
+
} catch { /* offline or npm slow β silently skip */ }
|
|
103
|
+
}
|
|
104
|
+
|
|
50
105
|
const c = {
|
|
51
106
|
reset: '\x1b[0m',
|
|
52
107
|
bold: '\x1b[1m',
|
|
@@ -65,8 +120,10 @@ const warn = (msg) => log(`${c.yellow}β ${c.reset} ${msg}`);
|
|
|
65
120
|
const err = (msg) => log(`${c.red}β${c.reset} ${msg}`);
|
|
66
121
|
|
|
67
122
|
function help() {
|
|
123
|
+
const v = getCurrentVersion() ?? '?';
|
|
68
124
|
log(`
|
|
69
125
|
${c.bold}kandown${c.reset} ${c.dim}Β· file-based kanban backed by markdown${c.reset}
|
|
126
|
+
${c.dim}v${v}${c.reset}
|
|
70
127
|
|
|
71
128
|
${c.bold}Usage:${c.reset}
|
|
72
129
|
npx kandown [command]
|
|
@@ -130,7 +187,7 @@ function appendAgentReference(cwd, agentsFile, kandownPath) {
|
|
|
130
187
|
${marker}
|
|
131
188
|
## Task management
|
|
132
189
|
|
|
133
|
-
**IMPORTANT:** Before touching any task files, you MUST read
|
|
190
|
+
**IMPORTANT:** Before touching any task files, you MUST read \`${kandownPath}/AGENT_KANDOWN.md\`.
|
|
134
191
|
|
|
135
192
|
This project uses a file-based kanban:
|
|
136
193
|
- **Tasks live in \`${kandownPath}/tasks/t-xxx.md\`** β each task file owns its status
|
|
@@ -174,35 +231,40 @@ function parseArgs(argv) {
|
|
|
174
231
|
return args;
|
|
175
232
|
}
|
|
176
233
|
|
|
177
|
-
|
|
234
|
+
/**
|
|
235
|
+
* @returns {{ kandownDir: string, alreadyExisted: boolean }} β resolves the
|
|
236
|
+
* kandown directory and auto-inits it if it doesn't exist (no prompt, silent init).
|
|
237
|
+
*/
|
|
238
|
+
function ensureKandownDir(rawArgs) {
|
|
178
239
|
const args = parseArgs(rawArgs);
|
|
179
240
|
const cwd = process.cwd();
|
|
241
|
+
const explicitPath = rawArgs.includes('--path') || rawArgs.includes('-p');
|
|
180
242
|
const kandownDir = resolve(cwd, args.path);
|
|
181
|
-
const kandownPath = args.path;
|
|
182
243
|
|
|
183
|
-
|
|
184
|
-
info(`Installing kandown in ${c.bold}${kandownPath}/${c.reset}`);
|
|
185
|
-
log('');
|
|
244
|
+
if (existsSync(kandownDir)) return { kandownDir, alreadyExisted: true };
|
|
186
245
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
246
|
+
log('');
|
|
247
|
+
info(`No .kandown/ found β auto-installing...`);
|
|
248
|
+
doInit(args, cwd, args.path, kandownDir);
|
|
249
|
+
return { kandownDir, alreadyExisted: false };
|
|
250
|
+
}
|
|
192
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Performs the actual init work. Returns on error (does not exit).
|
|
254
|
+
* @returns {boolean} true if init succeeded, false otherwise.
|
|
255
|
+
*/
|
|
256
|
+
function doInit(args, cwd, kandownPath, kandownDir) {
|
|
193
257
|
mkdirSync(kandownDir, { recursive: true });
|
|
194
258
|
|
|
195
|
-
// Copy kandown.html from dist
|
|
196
259
|
const htmlSrc = join(PKG_ROOT, 'dist', 'index.html');
|
|
197
260
|
const htmlDest = join(kandownDir, 'kandown.html');
|
|
198
261
|
if (!existsSync(htmlSrc)) {
|
|
199
262
|
err(`Missing build output at ${htmlSrc}. Did you run 'npm run build'?`);
|
|
200
|
-
|
|
263
|
+
return false;
|
|
201
264
|
}
|
|
202
265
|
copyFileSync(htmlSrc, htmlDest);
|
|
203
266
|
success('kandown.html');
|
|
204
267
|
|
|
205
|
-
// Copy templates
|
|
206
268
|
const templatesDir = join(PKG_ROOT, 'templates');
|
|
207
269
|
if (!existsSync(join(kandownDir, 'AGENT.md'))) {
|
|
208
270
|
copyFileSync(join(templatesDir, 'AGENT.md'), join(kandownDir, 'AGENT.md'));
|
|
@@ -222,7 +284,6 @@ function cmdInit(rawArgs) {
|
|
|
222
284
|
info('tasks/ already exists (kept)');
|
|
223
285
|
}
|
|
224
286
|
|
|
225
|
-
// π Copy kandown.json config file (project preferences for UI, agent, fields)
|
|
226
287
|
if (!existsSync(join(kandownDir, 'kandown.json'))) {
|
|
227
288
|
copyFileSync(join(templatesDir, 'kandown.json'), join(kandownDir, 'kandown.json'));
|
|
228
289
|
success('kandown.json');
|
|
@@ -230,25 +291,15 @@ function cmdInit(rawArgs) {
|
|
|
230
291
|
info('kandown.json already exists (kept)');
|
|
231
292
|
}
|
|
232
293
|
|
|
233
|
-
// Copy AGENT_KANDOWN.md to project root (not inside .kandown/)
|
|
234
294
|
const agentKandownSrc = join(templatesDir, 'AGENT_KANDOWN.md');
|
|
235
|
-
const agentKandownDest = join(
|
|
295
|
+
const agentKandownDest = join(kandownDir, 'AGENT_KANDOWN.md');
|
|
236
296
|
if (!existsSync(agentKandownDest)) {
|
|
237
297
|
copyFileSync(agentKandownSrc, agentKandownDest);
|
|
238
|
-
success('AGENT_KANDOWN.md
|
|
298
|
+
success('AGENT_KANDOWN.md');
|
|
239
299
|
} else {
|
|
240
|
-
info('AGENT_KANDOWN.md already exists
|
|
300
|
+
info('AGENT_KANDOWN.md already exists (kept)');
|
|
241
301
|
}
|
|
242
302
|
|
|
243
|
-
// π Copy the compact agent doc β used by the CLI board launcher for system prompt injection
|
|
244
|
-
const compactSrc = join(templatesDir, 'AGENT_KANDOWN_COMPACT.md');
|
|
245
|
-
const compactDest = join(cwd, 'AGENT_KANDOWN_COMPACT.md');
|
|
246
|
-
if (existsSync(compactSrc) && !existsSync(compactDest)) {
|
|
247
|
-
copyFileSync(compactSrc, compactDest);
|
|
248
|
-
success('AGENT_KANDOWN_COMPACT.md (at project root)');
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Integrate with AGENTS.md / CLAUDE.md
|
|
252
303
|
if (!args.noAgents) {
|
|
253
304
|
log('');
|
|
254
305
|
const existingAgents = findAgentsFile(cwd);
|
|
@@ -261,6 +312,27 @@ function cmdInit(rawArgs) {
|
|
|
261
312
|
}
|
|
262
313
|
}
|
|
263
314
|
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function cmdInit(rawArgs) {
|
|
319
|
+
const args = parseArgs(rawArgs);
|
|
320
|
+
const cwd = process.cwd();
|
|
321
|
+
const kandownPath = args.path;
|
|
322
|
+
const kandownDir = resolve(cwd, kandownPath);
|
|
323
|
+
|
|
324
|
+
log('');
|
|
325
|
+
info(`Installing kandown in ${c.bold}${kandownPath}/${c.reset}`);
|
|
326
|
+
log('');
|
|
327
|
+
|
|
328
|
+
if (existsSync(kandownDir) && !args.force) {
|
|
329
|
+
err(`Directory ${c.bold}${kandownPath}/${c.reset} already exists.`);
|
|
330
|
+
log(` Use ${c.cyan}--force${c.reset} to overwrite or ${c.cyan}--path <dir>${c.reset} for another location.`);
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (!doInit(args, cwd, kandownPath, kandownDir)) process.exit(1);
|
|
335
|
+
|
|
264
336
|
log('');
|
|
265
337
|
log(`${c.green}${c.bold}Done.${c.reset}`);
|
|
266
338
|
log('');
|
|
@@ -410,21 +482,9 @@ async function listenOnAvailablePort(kandownDir, preferredPort) {
|
|
|
410
482
|
* the browser can keep talking to localhost while the terminal board is active.
|
|
411
483
|
*/
|
|
412
484
|
async function cmdServe(rawArgs) {
|
|
413
|
-
const
|
|
414
|
-
const cwd = process.cwd();
|
|
415
|
-
const hasExplicitPath = rawArgs.includes('--path') || rawArgs.includes('-p');
|
|
416
|
-
const explicitKandownDir = resolve(cwd, args.path);
|
|
417
|
-
const kandownDir = hasExplicitPath || existsSync(explicitKandownDir)
|
|
418
|
-
? explicitKandownDir
|
|
419
|
-
: findKandownDir(cwd);
|
|
420
|
-
if (!kandownDir || !existsSync(kandownDir)) {
|
|
421
|
-
const missingPath = hasExplicitPath ? args.path : '.kandown/';
|
|
422
|
-
err(`No ${c.bold}${missingPath}${c.reset} directory found.`);
|
|
423
|
-
log(` Run ${c.cyan}npx kandown init${c.reset} to set up kandown in this project.`);
|
|
424
|
-
process.exit(1);
|
|
425
|
-
}
|
|
485
|
+
const { kandownDir } = ensureKandownDir(rawArgs);
|
|
426
486
|
|
|
427
|
-
const preferredPort = parsePort(
|
|
487
|
+
const preferredPort = parsePort(parseArgs(rawArgs).port);
|
|
428
488
|
const { server, port } = await listenOnAvailablePort(kandownDir, preferredPort);
|
|
429
489
|
const url = `http://localhost:${port}`;
|
|
430
490
|
|
|
@@ -438,8 +498,7 @@ async function cmdServe(rawArgs) {
|
|
|
438
498
|
info(`Project: ${kandownDir}`);
|
|
439
499
|
openInBrowser(url);
|
|
440
500
|
try {
|
|
441
|
-
|
|
442
|
-
await cmdTui('board', tuiArgs);
|
|
501
|
+
await cmdTui('board', rawArgs);
|
|
443
502
|
} finally {
|
|
444
503
|
server.close();
|
|
445
504
|
}
|
|
@@ -477,28 +536,31 @@ function findKandownDir(cwd) {
|
|
|
477
536
|
|
|
478
537
|
// π Launches the fullscreen TUI for a given screen (settings, board, etc.)
|
|
479
538
|
async function cmdTui(screen, rawArgs) {
|
|
480
|
-
const
|
|
481
|
-
const
|
|
482
|
-
const kandownDir = resolve(cwd, args.path);
|
|
483
|
-
|
|
484
|
-
if (!existsSync(kandownDir)) {
|
|
485
|
-
err(`No ${c.bold}${args.path}/${c.reset} directory found.`);
|
|
486
|
-
log(` Run ${c.cyan}npx kandown init${c.reset} first.`);
|
|
487
|
-
process.exit(1);
|
|
488
|
-
}
|
|
539
|
+
const { kandownDir } = ensureKandownDir(rawArgs);
|
|
540
|
+
const version = getCurrentVersion();
|
|
489
541
|
|
|
490
542
|
try {
|
|
491
543
|
const { run } = await import(new URL('./tui.js', import.meta.url).href);
|
|
492
|
-
await run(screen, kandownDir);
|
|
544
|
+
await run(screen, kandownDir, version);
|
|
493
545
|
} catch (e) {
|
|
494
546
|
err(`Failed to launch TUI: ${e.message}`);
|
|
495
|
-
log(` Make sure the CLI is built: ${c.cyan}pnpm build:cli${c.reset}`);
|
|
496
547
|
process.exit(1);
|
|
497
548
|
}
|
|
498
549
|
}
|
|
499
550
|
|
|
500
551
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
501
552
|
|
|
553
|
+
// π Handle --version / -v before any command logic
|
|
554
|
+
if (cmd === '--version' || cmd === '-v') {
|
|
555
|
+
const v = getCurrentVersion() ?? 'unknown';
|
|
556
|
+
log(`kandown v${v}`);
|
|
557
|
+
process.exit(0);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// π Auto-update check runs before EVERY command (except --version).
|
|
561
|
+
// Uses a short timeout so startup is not noticeably slower.
|
|
562
|
+
await checkForUpdate(rest);
|
|
563
|
+
|
|
502
564
|
switch (cmd) {
|
|
503
565
|
case 'init':
|
|
504
566
|
cmdInit(rest);
|