kanon-cli 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kanon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/bin/kanon.js ADDED
@@ -0,0 +1,266 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { loginCommand } from '../src/commands/login.js';
5
+ import { boardsCommand } from '../src/commands/boards.js';
6
+ import { initCommand } from '../src/commands/init.js';
7
+ import { cardsCommand } from '../src/commands/cards.js';
8
+ import { watchCommand } from '../src/commands/watch.js';
9
+ import { dashboardCommand } from '../src/commands/dashboard.js';
10
+ import { showCardCommand, createCardCommand, updateCardCommand, archiveCardCommand, boardCommand } from '../src/commands/card.js';
11
+ import { checklistCreateCommand, checklistAddCommand, checklistCheckCommand, checklistUncheckCommand, checklistRemoveCommand } from '../src/commands/checklist.js';
12
+ import { listCreateCommand, listRenameCommand, listDeleteCommand } from '../src/commands/list.js';
13
+ import { labelCreateCommand, labelDeleteCommand } from '../src/commands/label.js';
14
+ import { attachCommand, attachmentsCommand, downloadCommand } from '../src/commands/attachment.js';
15
+
16
+ const program = new Command();
17
+
18
+ program
19
+ .name('kanon')
20
+ .description('Kanon board CLI')
21
+ .version('0.2.0');
22
+
23
+ // --- Auth & Setup ---
24
+
25
+ program
26
+ .command('login')
27
+ .description('Login to Kanon server')
28
+ .action(loginCommand);
29
+
30
+ program
31
+ .command('boards')
32
+ .description('List all boards')
33
+ .action(boardsCommand);
34
+
35
+ program
36
+ .command('init')
37
+ .description('Initialize kanon.config.yaml for the current project')
38
+ .action(initCommand);
39
+
40
+ program
41
+ .command('board')
42
+ .description('Show board context (lists, labels, members)')
43
+ .action(boardCommand);
44
+
45
+ // --- Cards list ---
46
+
47
+ program
48
+ .command('cards')
49
+ .description('List cards on the configured board')
50
+ .option('-l, --label <name>', 'Filter by label name')
51
+ .option('--list <name>', 'Filter by list name')
52
+ .option('-m, --mine', 'Show only cards assigned to me')
53
+ .option('-a, --all', 'Show empty lists too')
54
+ .action(cardsCommand);
55
+
56
+ // --- Card (subcommands) ---
57
+
58
+ const card = program
59
+ .command('card')
60
+ .description('Card operations');
61
+
62
+ card
63
+ .command('show <cardId>')
64
+ .description('Show card details')
65
+ .action(showCardCommand);
66
+
67
+ // Also allow `kanon card <id>` as shortcut for show
68
+ card
69
+ .argument('[cardId]')
70
+ .action((cardId, options, cmd) => {
71
+ // Only handle if cardId looks like an ID (not a subcommand name)
72
+ if (cardId && !['create', 'update', 'archive', 'show'].includes(cardId)) {
73
+ return showCardCommand(cardId);
74
+ }
75
+ });
76
+
77
+ card
78
+ .command('create <title> <listName>')
79
+ .description('Create a new card in a list')
80
+ .option('-d, --description <text>', 'Card description')
81
+ .option('--label <name...>', 'Add labels (repeatable)')
82
+ .option('--assign <member...>', 'Assign members (repeatable)')
83
+ .option('--due <date>', 'Due date (YYYY-MM-DD)')
84
+ .action(createCardCommand);
85
+
86
+ card
87
+ .command('update <cardId>')
88
+ .description('Update a card (all flags optional, combinable)')
89
+ .option('-t, --title <text>', 'New title')
90
+ .option('-d, --description <text>', 'New description')
91
+ .option('--comment <text>', 'Add a comment')
92
+ .option('--add-label <name...>', 'Add labels')
93
+ .option('--remove-label <name...>', 'Remove labels')
94
+ .option('--move <listName>', 'Move to list')
95
+ .option('--assign <member...>', 'Assign members')
96
+ .option('--unassign <member...>', 'Unassign members')
97
+ .option('--due <date>', 'Set due date (YYYY-MM-DD)')
98
+ .option('--clear-due', 'Remove due date')
99
+ .option('--done', 'Mark as done')
100
+ .option('--undone', 'Mark as not done')
101
+ .action(updateCardCommand);
102
+
103
+ card
104
+ .command('archive <cardId>')
105
+ .description('Archive a card')
106
+ .action(archiveCardCommand);
107
+
108
+ // --- Checklist ---
109
+ // Commander can't cleanly handle `checklist <cardId> <subcommand> <arg>`,
110
+ // so we use a single command with variadic args and route manually.
111
+
112
+ program
113
+ .command('checklist')
114
+ .description('Checklist operations: checklist <cardId> create|add|check|uncheck|remove <text>')
115
+ .argument('<args...>')
116
+ .action((args) => {
117
+ if (args.length < 3) {
118
+ console.error('Usage: kanon checklist <cardId> create|add|check|uncheck|remove <text>');
119
+ process.exit(1);
120
+ }
121
+ const [cardId, action, ...rest] = args;
122
+ const text = rest.join(' ');
123
+ switch (action) {
124
+ case 'create': return checklistCreateCommand(cardId, text);
125
+ case 'add': return checklistAddCommand(cardId, text);
126
+ case 'check': return checklistCheckCommand(cardId, text);
127
+ case 'uncheck': return checklistUncheckCommand(cardId, text);
128
+ case 'remove': return checklistRemoveCommand(cardId, text);
129
+ default:
130
+ console.error(`Unknown checklist action: ${action}`);
131
+ console.error('Available: create, add, check, uncheck, remove');
132
+ process.exit(1);
133
+ }
134
+ });
135
+
136
+ // --- List ---
137
+
138
+ const list = program
139
+ .command('list')
140
+ .description('List operations');
141
+
142
+ list
143
+ .command('create <title>')
144
+ .description('Create a new list on the board')
145
+ .action(listCreateCommand);
146
+
147
+ list
148
+ .command('rename <oldName> <newName>')
149
+ .description('Rename a list')
150
+ .action(listRenameCommand);
151
+
152
+ list
153
+ .command('delete <name>')
154
+ .description('Archive a list')
155
+ .action(listDeleteCommand);
156
+
157
+ // --- Label ---
158
+
159
+ const label = program
160
+ .command('label')
161
+ .description('Label operations');
162
+
163
+ label
164
+ .command('create <name>')
165
+ .description('Create a board label')
166
+ .option('--color <hex>', 'Label color (#hex)', '#888888')
167
+ .action(labelCreateCommand);
168
+
169
+ label
170
+ .command('delete <name>')
171
+ .description('Delete a board label')
172
+ .action(labelDeleteCommand);
173
+
174
+ // --- Attachments ---
175
+
176
+ program
177
+ .command('attach <cardId> <filepath>')
178
+ .description('Upload a file attachment to a card')
179
+ .action(attachCommand);
180
+
181
+ program
182
+ .command('attachments <cardId>')
183
+ .description('List attachments on a card')
184
+ .action(attachmentsCommand);
185
+
186
+ program
187
+ .command('download <cardId> <filename>')
188
+ .description('Download an attachment from a card')
189
+ .option('--out <path>', 'Output file path')
190
+ .action(downloadCommand);
191
+
192
+ // --- Watch & Dashboard ---
193
+
194
+ program
195
+ .command('watch')
196
+ .description('Start the watch daemon')
197
+ .option('--dry-run', 'Show events without spawning Claude')
198
+ .action(watchCommand);
199
+
200
+ program
201
+ .command('dashboard')
202
+ .description('Start the web dashboard')
203
+ .option('-p, --port <port>', 'Dashboard port', '3737')
204
+ .option('--no-browser', 'Do not open browser automatically')
205
+ .action(dashboardCommand);
206
+
207
+ // --- Help all ---
208
+
209
+ program
210
+ .command('help-all')
211
+ .description('Show all commands with full details')
212
+ .action(() => {
213
+ console.log(`kanon CLI — Full Command Reference
214
+
215
+ ── Read ───────────────────────────────────────────────────
216
+ kanon board Show board context (lists, labels, members)
217
+ kanon cards [--list X] [--label X] [-m] List cards (--mine for assigned to you)
218
+ kanon card <id> Read card details
219
+
220
+ ── Card Operations ────────────────────────────────────────
221
+ kanon card create <title> <list> Create card (list is required)
222
+ --description "text" Card description
223
+ --label "name" Add label (repeatable)
224
+ --assign "member" Assign member (repeatable)
225
+ --due "YYYY-MM-DD" Set due date
226
+
227
+ kanon card update <id> Update card (all flags combinable)
228
+ --title "text" Change title
229
+ --description "text" Change description
230
+ --comment "text" Add a comment
231
+ --add-label "name" Add label (repeatable)
232
+ --remove-label "name" Remove label (repeatable)
233
+ --move "list name" Move to list
234
+ --assign "member" Assign member (repeatable)
235
+ --unassign "member" Unassign member (repeatable)
236
+ --due "YYYY-MM-DD" Set due date
237
+ --clear-due Remove due date
238
+ --done Mark as done
239
+ --undone Mark as not done
240
+
241
+ kanon card archive <id> Archive a card
242
+
243
+ ── Checklists ─────────────────────────────────────────────
244
+ kanon checklist <cardId> create "title" Create checklist on card
245
+ kanon checklist <cardId> add "text" Add item (auto-creates checklist)
246
+ kanon checklist <cardId> check "text" Check item (partial text match)
247
+ kanon checklist <cardId> uncheck "text" Uncheck item
248
+ kanon checklist <cardId> remove "text" Remove item
249
+
250
+ ── Lists (Board) ──────────────────────────────────────────
251
+ kanon list create "title" Create list
252
+ kanon list rename "old" "new" Rename list
253
+ kanon list delete "name" Archive list
254
+
255
+ ── Labels (Board) ─────────────────────────────────────────
256
+ kanon label create "name" [--color #hex] Create label
257
+ kanon label delete "name" Delete label
258
+
259
+ ── Attachments ────────────────────────────────────────────
260
+ kanon attach <cardId> <filepath> Upload file to card
261
+ kanon attachments <cardId> List attachments
262
+ kanon download <cardId> <filename> Download attachment (--out path)
263
+ `);
264
+ });
265
+
266
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "kanon-cli",
3
+ "version": "0.1.0",
4
+ "description": "Kanon board CLI — watch boards, spawn Claude agents, manage cards",
5
+ "type": "module",
6
+ "bin": {
7
+ "kanon": "./bin/kanon.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node bin/kanon.js",
11
+ "dev": "node bin/kanon.js"
12
+ },
13
+ "dependencies": {
14
+ "chalk": "^5.4.1",
15
+ "commander": "^13.1.0",
16
+ "express": "^5.1.0",
17
+ "ws": "^8.18.0",
18
+ "yaml": "^2.7.0"
19
+ },
20
+ "engines": {
21
+ "node": ">=18.0.0"
22
+ },
23
+ "files": [
24
+ "bin/",
25
+ "src/commands/",
26
+ "src/lib/",
27
+ "src/prompts/",
28
+ "src/dashboard/server/",
29
+ "src/dashboard/dist/",
30
+ "src/dashboard/package.json"
31
+ ],
32
+ "license": "MIT",
33
+ "private": false
34
+ }
@@ -0,0 +1,98 @@
1
+ import chalk from 'chalk';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import api from '../lib/api.js';
5
+ import { getToken } from '../lib/config.js';
6
+
7
+ function requireAuth() {
8
+ if (!getToken()) {
9
+ console.error(chalk.red('Not logged in. Run: kanon login'));
10
+ process.exit(1);
11
+ }
12
+ }
13
+
14
+ // --- kanon attach <cardId> <filepath> ---
15
+
16
+ export async function attachCommand(cardId, filepath) {
17
+ requireAuth();
18
+ try {
19
+ const resolved = path.resolve(filepath);
20
+ if (!fs.existsSync(resolved)) {
21
+ console.error(chalk.red(`File not found: ${resolved}`));
22
+ process.exit(1);
23
+ }
24
+ const result = await api.uploadAttachment(cardId, resolved);
25
+ const name = result.attachment?.filename || result.filename || path.basename(resolved);
26
+ console.log(chalk.green(`Attached "${name}" to card ${cardId}.`));
27
+ } catch (err) {
28
+ console.error(chalk.red(`Failed to upload attachment: ${err.message}`));
29
+ process.exit(1);
30
+ }
31
+ }
32
+
33
+ // --- kanon attachments <cardId> ---
34
+
35
+ export async function attachmentsCommand(cardId) {
36
+ requireAuth();
37
+ try {
38
+ const data = await api.getAttachments(cardId);
39
+ const attachments = data.attachments || data || [];
40
+
41
+ if (!attachments.length) {
42
+ console.log(chalk.yellow('No attachments on this card.'));
43
+ return;
44
+ }
45
+
46
+ console.log(chalk.dim(`\nAttachments (${attachments.length}):`));
47
+ for (const a of attachments) {
48
+ const name = a.filename || a.name;
49
+ const size = a.size ? chalk.dim(` (${formatSize(a.size)})`) : '';
50
+ console.log(` ${name}${size}`);
51
+ }
52
+ console.log();
53
+ } catch (err) {
54
+ console.error(chalk.red(`Failed to list attachments: ${err.message}`));
55
+ process.exit(1);
56
+ }
57
+ }
58
+
59
+ // --- kanon download <cardId> <filename> [--out "path"] ---
60
+
61
+ export async function downloadCommand(cardId, filename, options) {
62
+ requireAuth();
63
+ try {
64
+ const data = await api.getAttachments(cardId);
65
+ const attachments = data.attachments || data || [];
66
+ const lower = filename.toLowerCase();
67
+
68
+ const attachment = attachments.find(a =>
69
+ (a.filename || a.name || '').toLowerCase().includes(lower)
70
+ );
71
+ if (!attachment) {
72
+ console.error(chalk.red(`Attachment "${filename}" not found on card ${cardId}.`));
73
+ process.exit(1);
74
+ }
75
+
76
+ const urlData = await api.getAttachmentUrl(cardId, attachment.id);
77
+ const url = urlData.url || urlData;
78
+
79
+ const res = await fetch(url);
80
+ if (!res.ok) throw new Error(`Download failed (${res.status})`);
81
+
82
+ const buffer = Buffer.from(await res.arrayBuffer());
83
+ const outName = attachment.filename || attachment.name || filename;
84
+ const outPath = options.out ? path.resolve(options.out) : path.resolve(outName);
85
+
86
+ fs.writeFileSync(outPath, buffer);
87
+ console.log(chalk.green(`Downloaded "${outName}" to ${outPath} (${formatSize(buffer.length)}).`));
88
+ } catch (err) {
89
+ console.error(chalk.red(`Failed to download attachment: ${err.message}`));
90
+ process.exit(1);
91
+ }
92
+ }
93
+
94
+ function formatSize(bytes) {
95
+ if (bytes < 1024) return `${bytes}B`;
96
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
97
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
98
+ }
@@ -0,0 +1,39 @@
1
+ import chalk from 'chalk';
2
+ import api from '../lib/api.js';
3
+ import { getToken } from '../lib/config.js';
4
+
5
+ export async function boardsCommand() {
6
+ if (!getToken()) {
7
+ console.error(chalk.red('Not logged in. Run: kanon login'));
8
+ process.exit(1);
9
+ }
10
+
11
+ try {
12
+ const data = await api.getTeams();
13
+ const teams = data.teams || data;
14
+
15
+ if (!teams?.length) {
16
+ console.log(chalk.yellow('No teams found.'));
17
+ return;
18
+ }
19
+
20
+ for (const team of teams) {
21
+ console.log(chalk.bold(`\n${team.name}`));
22
+
23
+ const boards = team.boards || [];
24
+ if (!boards.length) {
25
+ console.log(chalk.dim(' No boards'));
26
+ continue;
27
+ }
28
+
29
+ for (const board of boards) {
30
+ const memberCount = board.member_count || board.members?.length || '?';
31
+ console.log(` ${chalk.cyan(board.title || board.name)} ${chalk.dim(board.id)} (${memberCount} members)`);
32
+ }
33
+ }
34
+ console.log();
35
+ } catch (err) {
36
+ console.error(chalk.red(`Failed to fetch boards: ${err.message}`));
37
+ process.exit(1);
38
+ }
39
+ }