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 +21 -0
- package/bin/kanon.js +266 -0
- package/package.json +34 -0
- package/src/commands/attachment.js +98 -0
- package/src/commands/boards.js +39 -0
- package/src/commands/card.js +260 -0
- package/src/commands/cards.js +79 -0
- package/src/commands/checklist.js +129 -0
- package/src/commands/dashboard.js +24 -0
- package/src/commands/init.js +89 -0
- package/src/commands/label.js +61 -0
- package/src/commands/list.js +78 -0
- package/src/commands/login.js +91 -0
- package/src/commands/watch.js +224 -0
- package/src/dashboard/dist/assets/index-Dcbpx-Xz.js +186 -0
- package/src/dashboard/dist/assets/index-DhFfv70f.css +1 -0
- package/src/dashboard/dist/index.html +13 -0
- package/src/dashboard/dist/kanon.png +0 -0
- package/src/dashboard/package.json +26 -0
- package/src/dashboard/server/agent.js +201 -0
- package/src/dashboard/server/index.js +85 -0
- package/src/dashboard/server/proxy.js +54 -0
- package/src/dashboard/server/settings.js +236 -0
- package/src/lib/admin.js +330 -0
- package/src/lib/api.js +225 -0
- package/src/lib/claude.js +161 -0
- package/src/lib/config.js +112 -0
- package/src/lib/pipeline.js +133 -0
- package/src/lib/websocket.js +194 -0
- package/src/prompts/templates.js +127 -0
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
|
+
}
|