kanon-cli 0.1.0 → 0.1.2

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/kanon.js CHANGED
@@ -12,13 +12,28 @@ import { checklistCreateCommand, checklistAddCommand, checklistCheckCommand, che
12
12
  import { listCreateCommand, listRenameCommand, listDeleteCommand } from '../src/commands/list.js';
13
13
  import { labelCreateCommand, labelDeleteCommand } from '../src/commands/label.js';
14
14
  import { attachCommand, attachmentsCommand, downloadCommand } from '../src/commands/attachment.js';
15
+ import { sheetReadCommand, sheetCreateCommand, sheetSetCommand, sheetExportCommand, sheetDeleteCommand } from '../src/commands/sheet.js';
16
+ import { noteReadCommand, noteCreateCommand, noteSetCommand, noteAppendCommand, noteDeleteCommand } from '../src/commands/note.js';
17
+ import { canvasReadCommand } from '../src/commands/canvas.js';
18
+ import { subcardListCommand, subcardNestCommand, subcardUnnestCommand } from '../src/commands/subcard.js';
15
19
 
16
20
  const program = new Command();
17
21
 
18
22
  program
19
23
  .name('kanon')
20
24
  .description('Kanon board CLI')
21
- .version('0.2.0');
25
+ .version('0.1.2');
26
+
27
+ // Default action: launch dashboard when no command is given
28
+ program.action(async (options, cmd) => {
29
+ // Only trigger if no subcommand was matched
30
+ if (cmd.args.length === 0) {
31
+ console.log('\n Starting Kanon dashboard...\n');
32
+ console.log(' If nothing opens, go to: http://localhost:3737');
33
+ console.log(' For CLI commands, run: kanon --help\n');
34
+ await dashboardCommand({ port: '3737', browser: true });
35
+ }
36
+ });
22
37
 
23
38
  // --- Auth & Setup ---
24
39
 
@@ -81,6 +96,7 @@ card
81
96
  .option('--label <name...>', 'Add labels (repeatable)')
82
97
  .option('--assign <member...>', 'Assign members (repeatable)')
83
98
  .option('--due <date>', 'Due date (YYYY-MM-DD)')
99
+ .option('--parent <cardId>', 'Create as subcard of parent card')
84
100
  .action(createCardCommand);
85
101
 
86
102
  card
@@ -189,6 +205,100 @@ program
189
205
  .option('--out <path>', 'Output file path')
190
206
  .action(downloadCommand);
191
207
 
208
+ // --- Subcards ---
209
+
210
+ program
211
+ .command('subcard')
212
+ .description('Subcard operations: subcard <parentId> [nest|unnest <cardId>]')
213
+ .argument('<args...>')
214
+ .action((args) => {
215
+ if (args.length < 1) {
216
+ console.error('Usage: kanon subcard <parentId> [nest|unnest <cardId>]');
217
+ process.exit(1);
218
+ }
219
+ const [parentId, action, ...rest] = args;
220
+ if (!action) return subcardListCommand(parentId);
221
+ const cardId = rest[0];
222
+ switch (action) {
223
+ case 'nest':
224
+ if (!cardId) { console.error('Usage: kanon subcard <parentId> nest <cardId>'); process.exit(1); }
225
+ return subcardNestCommand(parentId, cardId);
226
+ case 'unnest':
227
+ if (!cardId) { console.error('Usage: kanon subcard <parentId> unnest <cardId>'); process.exit(1); }
228
+ return subcardUnnestCommand(parentId, cardId);
229
+ default:
230
+ console.error(`Unknown subcard action: ${action}`);
231
+ console.error('Available: nest, unnest');
232
+ process.exit(1);
233
+ }
234
+ });
235
+
236
+ // --- Sheet ---
237
+
238
+ program
239
+ .command('sheet')
240
+ .description('Sheet operations: sheet <cardId> [create|set|export|delete]')
241
+ .argument('<args...>')
242
+ .action((args) => {
243
+ if (args.length < 1) {
244
+ console.error('Usage: kanon sheet <cardId> [create|set|export|delete]');
245
+ process.exit(1);
246
+ }
247
+ const [cardId, action, ...rest] = args;
248
+ if (!action) return sheetReadCommand(cardId);
249
+ switch (action) {
250
+ case 'create': return sheetCreateCommand(cardId);
251
+ case 'set': {
252
+ if (rest.length < 4) {
253
+ console.error('Usage: kanon sheet <cardId> set <tab> <row> <col> "value"');
254
+ process.exit(1);
255
+ }
256
+ const [tab, row, col, ...valueParts] = rest;
257
+ return sheetSetCommand(cardId, tab, row, col, valueParts.join(' '));
258
+ }
259
+ case 'export': return sheetExportCommand(cardId);
260
+ case 'delete': return sheetDeleteCommand(cardId);
261
+ default:
262
+ console.error(`Unknown sheet action: ${action}`);
263
+ console.error('Available: create, set, export, delete');
264
+ process.exit(1);
265
+ }
266
+ });
267
+
268
+ // --- Note ---
269
+
270
+ program
271
+ .command('note')
272
+ .description('Note operations: note <cardId> [create|set|append|delete]')
273
+ .argument('<args...>')
274
+ .action((args) => {
275
+ if (args.length < 1) {
276
+ console.error('Usage: kanon note <cardId> [create|set|append|delete]');
277
+ process.exit(1);
278
+ }
279
+ const [cardId, action, ...rest] = args;
280
+ if (!action) return noteReadCommand(cardId);
281
+ const text = rest.join(' ');
282
+ switch (action) {
283
+ case 'create': return noteCreateCommand(cardId);
284
+ case 'set': return noteSetCommand(cardId, text);
285
+ case 'append': return noteAppendCommand(cardId, text);
286
+ case 'delete': return noteDeleteCommand(cardId);
287
+ default:
288
+ console.error(`Unknown note action: ${action}`);
289
+ console.error('Available: create, set, append, delete');
290
+ process.exit(1);
291
+ }
292
+ });
293
+
294
+ // --- Canvas ---
295
+
296
+ program
297
+ .command('canvas')
298
+ .description('Read canvas objects and connections')
299
+ .argument('<cardId>')
300
+ .action(canvasReadCommand);
301
+
192
302
  // --- Watch & Dashboard ---
193
303
 
194
304
  program
@@ -256,6 +366,29 @@ program
256
366
  kanon label create "name" [--color #hex] Create label
257
367
  kanon label delete "name" Delete label
258
368
 
369
+ ── Subcards ───────────────────────────────────────────────
370
+ kanon subcard <parentId> List subcards
371
+ kanon subcard <parentId> nest <cardId> Nest card under parent
372
+ kanon subcard <parentId> unnest <cardId> Unnest card from parent
373
+ kanon card create <title> <list> --parent <cardId> Create as subcard
374
+
375
+ ── Sheets ─────────────────────────────────────────────────
376
+ kanon sheet <cardId> Read sheet (formatted table)
377
+ kanon sheet <cardId> create Create empty sheet
378
+ kanon sheet <cardId> set <t> <r> <c> "v" Set cell (0-indexed tab/row/col)
379
+ kanon sheet <cardId> export Export as CSV
380
+ kanon sheet <cardId> delete Delete sheet
381
+
382
+ ── Notes ──────────────────────────────────────────────────
383
+ kanon note <cardId> Read note as plaintext
384
+ kanon note <cardId> create Create empty note
385
+ kanon note <cardId> set "text" Set/overwrite note content
386
+ kanon note <cardId> append "text" Append to note
387
+ kanon note <cardId> delete Delete note
388
+
389
+ ── Canvas ─────────────────────────────────────────────────
390
+ kanon canvas <cardId> Read canvas objects & connections
391
+
259
392
  ── Attachments ────────────────────────────────────────────
260
393
  kanon attach <cardId> <filepath> Upload file to card
261
394
  kanon attachments <cardId> List attachments
package/package.json CHANGED
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "kanon-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Kanon board CLI — watch boards, spawn Claude agents, manage cards",
5
5
  "type": "module",
6
6
  "bin": {
7
- "kanon": "./bin/kanon.js"
7
+ "kanon": "./bin/kanon.js",
8
+ "kanon-cli": "./bin/kanon.js"
8
9
  },
9
10
  "scripts": {
10
11
  "start": "node bin/kanon.js",
11
- "dev": "node bin/kanon.js"
12
+ "dev": "node bin/kanon.js",
13
+ "postinstall": "echo '\n ✓ kanon-cli installed successfully!\n\n Run \"kanon\" to open the dashboard.\n Run \"kanon --help\" for all commands.\n'"
12
14
  },
13
15
  "dependencies": {
14
16
  "chalk": "^5.4.1",
@@ -0,0 +1,45 @@
1
+ import chalk from 'chalk';
2
+ import api from '../lib/api.js';
3
+ import { getToken } from '../lib/config.js';
4
+
5
+ function requireAuth() {
6
+ if (!getToken()) {
7
+ console.error(chalk.red('Not logged in. Run: kanon login'));
8
+ process.exit(1);
9
+ }
10
+ }
11
+
12
+ // --- kanon canvas <cardId> --- (read)
13
+ export async function canvasReadCommand(cardId) {
14
+ requireAuth();
15
+ try {
16
+ const { objects, connections } = await api.getCanvasSummary(cardId);
17
+
18
+ if (objects.length === 0 && connections.length === 0) {
19
+ console.log('Canvas is empty.');
20
+ return;
21
+ }
22
+
23
+ if (objects.length > 0) {
24
+ console.log('Objects:');
25
+ for (const obj of objects) {
26
+ const text = obj.text ? ` "${obj.text}"` : '';
27
+ const id = obj.id ? ` (${obj.id.slice(0, 6)})` : '';
28
+ console.log(` [${obj.type}]${text}${id}`);
29
+ }
30
+ }
31
+
32
+ if (connections.length > 0) {
33
+ if (objects.length > 0) console.log('');
34
+ console.log('Connections:');
35
+ for (const conn of connections) {
36
+ const from = conn.fromText || conn.from?.slice(0, 6) || '?';
37
+ const to = conn.toText || conn.to?.slice(0, 6) || '?';
38
+ console.log(` "${from}" → "${to}"`);
39
+ }
40
+ }
41
+ } catch (err) {
42
+ console.error(chalk.red(`Failed to read canvas: ${err.message}`));
43
+ process.exit(1);
44
+ }
45
+ }
@@ -11,7 +11,7 @@ function requireAuth() {
11
11
 
12
12
  async function getBoardContext() {
13
13
  const config = loadProjectConfig();
14
- const boardId = config?.board_id;
14
+ const boardId = config?.board_id || config?.boards?.[0]?.id || process.env.KANON_BOARD_ID;
15
15
  if (!boardId) throw new Error('No board configured. Run: kanon init');
16
16
  const data = await api.getBoard(boardId, { cardLimit: 0 });
17
17
  return data.board || data;
@@ -83,6 +83,18 @@ export async function showCardCommand(cardId) {
83
83
  }
84
84
  }
85
85
 
86
+ if (card.subcards?.length) {
87
+ console.log(chalk.dim('\nSubcards:'));
88
+ for (const sc of card.subcards) {
89
+ const done = sc.is_done ? chalk.green(' [done]') : '';
90
+ console.log(` ${sc.title} ${chalk.dim(`(${sc.id})`)}${done}`);
91
+ }
92
+ }
93
+
94
+ if (card.parent_card_id) {
95
+ console.log(chalk.dim(`\nParent card: ${card.parent_card_id}`));
96
+ }
97
+
86
98
  if (card.attachments?.length) {
87
99
  console.log(chalk.dim(`\nAttachments (${card.attachments.length}):`));
88
100
  for (const a of card.attachments) console.log(` ${a.filename || a.name}`);
@@ -113,6 +125,7 @@ export async function createCardCommand(title, listName, options) {
113
125
  const data = { title };
114
126
  if (options.description) data.description = options.description;
115
127
  if (options.due) data.due_date = options.due;
128
+ if (options.parent) data.parent_card_id = options.parent;
116
129
 
117
130
  const result = await api.createCard(list.id, data);
118
131
  const cardId = result.card?.id || result.id;
@@ -13,13 +13,14 @@ export async function cardsCommand(options) {
13
13
  requireAuth();
14
14
 
15
15
  const config = loadProjectConfig();
16
- if (!config?.board_id) {
16
+ const boardId = config?.board_id || config?.boards?.[0]?.id || process.env.KANON_BOARD_ID;
17
+ if (!boardId) {
17
18
  console.error(chalk.red('No board configured. Run: kanon init'));
18
19
  process.exit(1);
19
20
  }
20
21
 
21
22
  try {
22
- const data = await api.getBoard(config.board_id, { cardLimit: 50 });
23
+ const data = await api.getBoard(boardId, { cardLimit: 50 });
23
24
  const lists = data.board?.lists || [];
24
25
 
25
26
  if (!lists.length) {
@@ -11,7 +11,7 @@ function requireAuth() {
11
11
 
12
12
  function getBoardId() {
13
13
  const config = loadProjectConfig();
14
- const boardId = config?.board_id;
14
+ const boardId = config?.board_id || config?.boards?.[0]?.id || process.env.KANON_BOARD_ID;
15
15
  if (!boardId) throw new Error('No board configured. Run: kanon init');
16
16
  return boardId;
17
17
  }
@@ -11,7 +11,7 @@ function requireAuth() {
11
11
 
12
12
  function getBoardId() {
13
13
  const config = loadProjectConfig();
14
- const boardId = config?.board_id;
14
+ const boardId = config?.board_id || config?.boards?.[0]?.id || process.env.KANON_BOARD_ID;
15
15
  if (!boardId) throw new Error('No board configured. Run: kanon init');
16
16
  return boardId;
17
17
  }
@@ -0,0 +1,79 @@
1
+ import chalk from 'chalk';
2
+ import api from '../lib/api.js';
3
+ import { getToken } from '../lib/config.js';
4
+
5
+ function requireAuth() {
6
+ if (!getToken()) {
7
+ console.error(chalk.red('Not logged in. Run: kanon login'));
8
+ process.exit(1);
9
+ }
10
+ }
11
+
12
+ // Unescape common escape sequences (shell passes \n as literal backslash-n)
13
+ function unescapeText(text) {
14
+ return text.replace(/\\n/g, '\n').replace(/\\t/g, '\t');
15
+ }
16
+
17
+ // --- kanon note <cardId> --- (read)
18
+ export async function noteReadCommand(cardId) {
19
+ requireAuth();
20
+ try {
21
+ const { text } = await api.getNoteText(cardId);
22
+ if (!text) {
23
+ console.log('(empty note)');
24
+ } else {
25
+ console.log(text);
26
+ }
27
+ } catch (err) {
28
+ console.error(chalk.red(`Failed to read note: ${err.message}`));
29
+ process.exit(1);
30
+ }
31
+ }
32
+
33
+ // --- kanon note <cardId> create ---
34
+ export async function noteCreateCommand(cardId) {
35
+ requireAuth();
36
+ try {
37
+ await api.createNote(cardId);
38
+ console.log(chalk.green(`Note created on card ${cardId}.`));
39
+ } catch (err) {
40
+ console.error(chalk.red(`Failed to create note: ${err.message}`));
41
+ process.exit(1);
42
+ }
43
+ }
44
+
45
+ // --- kanon note <cardId> set "text" ---
46
+ export async function noteSetCommand(cardId, text) {
47
+ requireAuth();
48
+ try {
49
+ await api.setNoteText(cardId, unescapeText(text));
50
+ console.log(chalk.green(`Note set on card ${cardId}.`));
51
+ } catch (err) {
52
+ console.error(chalk.red(`Failed to set note: ${err.message}`));
53
+ process.exit(1);
54
+ }
55
+ }
56
+
57
+ // --- kanon note <cardId> append "text" ---
58
+ export async function noteAppendCommand(cardId, text) {
59
+ requireAuth();
60
+ try {
61
+ await api.appendNoteText(cardId, unescapeText(text));
62
+ console.log(chalk.green(`Text appended to note on card ${cardId}.`));
63
+ } catch (err) {
64
+ console.error(chalk.red(`Failed to append to note: ${err.message}`));
65
+ process.exit(1);
66
+ }
67
+ }
68
+
69
+ // --- kanon note <cardId> delete ---
70
+ export async function noteDeleteCommand(cardId) {
71
+ requireAuth();
72
+ try {
73
+ await api.deleteNote(cardId);
74
+ console.log(chalk.green(`Note deleted from card ${cardId}.`));
75
+ } catch (err) {
76
+ console.error(chalk.red(`Failed to delete note: ${err.message}`));
77
+ process.exit(1);
78
+ }
79
+ }
@@ -0,0 +1,137 @@
1
+ import chalk from 'chalk';
2
+ import api from '../lib/api.js';
3
+ import { getToken } from '../lib/config.js';
4
+
5
+ function requireAuth() {
6
+ if (!getToken()) {
7
+ console.error(chalk.red('Not logged in. Run: kanon login'));
8
+ process.exit(1);
9
+ }
10
+ }
11
+
12
+ // --- kanon sheet <cardId> --- (read)
13
+ export async function sheetReadCommand(cardId) {
14
+ requireAuth();
15
+ try {
16
+ const { sheet } = await api.getSheet(cardId);
17
+ const tabs = sheet.data || [];
18
+
19
+ if (tabs.length === 0) {
20
+ console.log('Sheet is empty.');
21
+ return;
22
+ }
23
+
24
+ for (let t = 0; t < tabs.length; t++) {
25
+ const tab = tabs[t];
26
+ if (tabs.length > 1) console.log(chalk.bold(`\n[${tab.sheetName || `Sheet ${t + 1}`}]`));
27
+
28
+ const rows = tab.data || [];
29
+ if (rows.length === 0 || rows.every(r => r.length === 0 || r.every(c => !c))) {
30
+ console.log(' (empty)');
31
+ continue;
32
+ }
33
+
34
+ // Find actual extent of data (skip trailing empty rows/cols)
35
+ let maxCol = 0;
36
+ let maxRow = 0;
37
+ for (let r = 0; r < rows.length; r++) {
38
+ for (let c = 0; c < rows[r].length; c++) {
39
+ if (rows[r][c]) {
40
+ maxRow = r;
41
+ if (c > maxCol) maxCol = c;
42
+ }
43
+ }
44
+ }
45
+
46
+ // Calculate column widths
47
+ const colWidths = new Array(maxCol + 1).fill(3);
48
+ for (let r = 0; r <= maxRow; r++) {
49
+ for (let c = 0; c <= maxCol; c++) {
50
+ const val = String(rows[r]?.[c] || '');
51
+ if (val.length > colWidths[c]) colWidths[c] = Math.min(val.length, 40);
52
+ }
53
+ }
54
+
55
+ // Print rows
56
+ for (let r = 0; r <= maxRow; r++) {
57
+ const cells = [];
58
+ for (let c = 0; c <= maxCol; c++) {
59
+ const val = String(rows[r]?.[c] || '');
60
+ cells.push(val.padEnd(colWidths[c]));
61
+ }
62
+ console.log(' ' + cells.join(' | '));
63
+ }
64
+ }
65
+ } catch (err) {
66
+ console.error(chalk.red(`Failed to read sheet: ${err.message}`));
67
+ process.exit(1);
68
+ }
69
+ }
70
+
71
+ // --- kanon sheet <cardId> create ---
72
+ export async function sheetCreateCommand(cardId) {
73
+ requireAuth();
74
+ try {
75
+ await api.createSheet(cardId);
76
+ console.log(chalk.green(`Sheet created on card ${cardId}.`));
77
+ } catch (err) {
78
+ console.error(chalk.red(`Failed to create sheet: ${err.message}`));
79
+ process.exit(1);
80
+ }
81
+ }
82
+
83
+ // --- kanon sheet <cardId> set <tab> <row> <col> "value" ---
84
+ export async function sheetSetCommand(cardId, tab, row, col, value) {
85
+ requireAuth();
86
+ try {
87
+ await api.updateSheetCell(cardId, {
88
+ tab: parseInt(tab, 10),
89
+ row: parseInt(row, 10),
90
+ col: parseInt(col, 10),
91
+ value,
92
+ });
93
+ console.log(chalk.green(`Cell [${tab}][${row}][${col}] set to "${value}".`));
94
+ } catch (err) {
95
+ console.error(chalk.red(`Failed to set cell: ${err.message}`));
96
+ process.exit(1);
97
+ }
98
+ }
99
+
100
+ // --- kanon sheet <cardId> export ---
101
+ export async function sheetExportCommand(cardId) {
102
+ requireAuth();
103
+ try {
104
+ const { sheet } = await api.getSheet(cardId);
105
+ const tabs = sheet.data || [];
106
+
107
+ for (let t = 0; t < tabs.length; t++) {
108
+ const rows = tabs[t].data || [];
109
+ for (const row of rows) {
110
+ const cells = (row || []).map(c => {
111
+ const val = String(c || '');
112
+ // Escape CSV: quote if contains comma, quote, or newline
113
+ if (val.includes(',') || val.includes('"') || val.includes('\n')) {
114
+ return '"' + val.replace(/"/g, '""') + '"';
115
+ }
116
+ return val;
117
+ });
118
+ console.log(cells.join(','));
119
+ }
120
+ }
121
+ } catch (err) {
122
+ console.error(chalk.red(`Failed to export sheet: ${err.message}`));
123
+ process.exit(1);
124
+ }
125
+ }
126
+
127
+ // --- kanon sheet <cardId> delete ---
128
+ export async function sheetDeleteCommand(cardId) {
129
+ requireAuth();
130
+ try {
131
+ await api.deleteSheet(cardId);
132
+ console.log(chalk.green(`Sheet deleted from card ${cardId}.`));
133
+ } catch (err) {
134
+ console.error(chalk.red(`Failed to delete sheet: ${err.message}`));
135
+ process.exit(1);
136
+ }
137
+ }
@@ -0,0 +1,59 @@
1
+ import chalk from 'chalk';
2
+ import api from '../lib/api.js';
3
+ import { getToken } from '../lib/config.js';
4
+
5
+ function requireAuth() {
6
+ if (!getToken()) {
7
+ console.error(chalk.red('Not logged in. Run: kanon login'));
8
+ process.exit(1);
9
+ }
10
+ }
11
+
12
+ // --- kanon subcard <parentId> --- (list subcards)
13
+ export async function subcardListCommand(parentId) {
14
+ requireAuth();
15
+ try {
16
+ const data = await api.getCard(parentId);
17
+ const card = data.card || data;
18
+ const subcards = card.subcards || [];
19
+
20
+ if (subcards.length === 0) {
21
+ console.log('No subcards.');
22
+ return;
23
+ }
24
+
25
+ console.log(chalk.bold(`Subcards of "${card.title}":`));
26
+ for (const sc of subcards) {
27
+ const done = sc.is_done ? chalk.green(' [done]') : '';
28
+ const labels = sc.labels?.length ? chalk.dim(` (${sc.labels.map(l => l.name).join(', ')})`) : '';
29
+ console.log(` ${sc.title} ${chalk.dim(sc.id)}${done}${labels}`);
30
+ }
31
+ } catch (err) {
32
+ console.error(chalk.red(`Failed to list subcards: ${err.message}`));
33
+ process.exit(1);
34
+ }
35
+ }
36
+
37
+ // --- kanon subcard <parentId> nest <cardId> --- (make card a subcard)
38
+ export async function subcardNestCommand(parentId, cardId) {
39
+ requireAuth();
40
+ try {
41
+ await api.setParent(cardId, parentId);
42
+ console.log(chalk.green(`Card ${cardId} nested under ${parentId}.`));
43
+ } catch (err) {
44
+ console.error(chalk.red(`Failed to nest card: ${err.message}`));
45
+ process.exit(1);
46
+ }
47
+ }
48
+
49
+ // --- kanon subcard <parentId> unnest <cardId> --- (remove from parent)
50
+ export async function subcardUnnestCommand(parentId, cardId) {
51
+ requireAuth();
52
+ try {
53
+ await api.setParent(cardId, null);
54
+ console.log(chalk.green(`Card ${cardId} unnested from ${parentId}.`));
55
+ } catch (err) {
56
+ console.error(chalk.red(`Failed to unnest card: ${err.message}`));
57
+ process.exit(1);
58
+ }
59
+ }
@@ -76,6 +76,14 @@ export async function watchCommand(options) {
76
76
  // Start IPC server for dashboard
77
77
  const ipcServer = startIPCServer(admin);
78
78
 
79
+ // Listen for kill requests from the board UI
80
+ ws.on('agent_kill', (data) => {
81
+ if (data.cardId) {
82
+ console.log(chalk.yellow(`[kill] Terminate requested from board for card ${data.cardId}`));
83
+ killWorker(data.cardId);
84
+ }
85
+ });
86
+
79
87
  // Listen for card events
80
88
  ws.on('card_event', async (event) => {
81
89
  const { cardId, eventType, userId, eventData, boardId } = event;
@@ -126,6 +134,7 @@ export async function watchCommand(options) {
126
134
  try {
127
135
  const cardData = await api.getCard(cardId);
128
136
  const card = cardData.card || cardData;
137
+ card.boardId = boardId;
129
138
 
130
139
  console.log(chalk.dim(` Card: "${card.title}"`));
131
140
 
@@ -0,0 +1 @@
1
+ *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:#374151;border-radius:3px}::-webkit-scrollbar-thumb:hover{background:#4b5563}.pointer-events-none{pointer-events:none}.\!visible{visibility:visible!important}.visible{visibility:visible}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.-right-1{right:-.25rem}.-right-2{right:-.5rem}.-top-1{top:-.25rem}.left-0\.5{left:.125rem}.left-14{left:3.5rem}.left-3{left:.75rem}.left-\[18px\]{left:18px}.right-2{right:.5rem}.right-3{right:.75rem}.top-0\.5{top:.125rem}.top-1\/2{top:50%}.top-8{top:2rem}.top-full{top:100%}.z-50{z-index:50}.col-span-2{grid-column:span 2 / span 2}.mx-2{margin-left:.5rem;margin-right:.5rem}.mx-3{margin-left:.75rem;margin-right:.75rem}.my-1\.5{margin-top:.375rem;margin-bottom:.375rem}.mb-0\.5{margin-bottom:.125rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-16{height:4rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-full{height:100%}.max-h-40{max-height:10rem}.max-h-48{max-height:12rem}.max-h-60{max-height:15rem}.max-h-64{max-height:16rem}.max-h-\[60vh\]{max-height:60vh}.max-h-\[70vh\]{max-height:70vh}.w-12{width:3rem}.w-14{width:3.5rem}.w-16{width:4rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-20{width:5rem}.w-3{width:.75rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-9{width:2.25rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-40{min-width:10rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-90{--tw-rotate: 90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-110{--tw-scale-x: 1.1;--tw-scale-y: 1.1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.cursor-text{cursor:text}.select-text{-webkit-user-select:text;-moz-user-select:text;user-select:text}.resize-y{resize:vertical}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.125rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem * var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-gray-600{--tw-border-opacity: 1;border-color:rgb(75 85 99 / var(--tw-border-opacity, 1))}.border-gray-700{--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity, 1))}.border-gray-700\/30{border-color:#3741514d}.border-gray-700\/50{border-color:#37415180}.border-gray-800{--tw-border-opacity: 1;border-color:rgb(31 41 55 / var(--tw-border-opacity, 1))}.border-gray-800\/50{border-color:#1f293780}.border-green-500\/30{border-color:#22c55e4d}.border-kanon-500{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.border-kanon-500\/30{border-color:#3b82f64d}.border-red-500\/30{border-color:#ef44444d}.border-red-800\/40{border-color:#991b1b66}.border-transparent{border-color:transparent}.border-yellow-500\/30{border-color:#eab3084d}.bg-black\/50{background-color:#00000080}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-cyan-500\/10{background-color:#06b6d41a}.bg-gray-500{--tw-bg-opacity: 1;background-color:rgb(107 114 128 / var(--tw-bg-opacity, 1))}.bg-gray-700{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.bg-gray-800\/20{background-color:#1f293733}.bg-gray-800\/30{background-color:#1f29374d}.bg-gray-800\/50{background-color:#1f293780}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.bg-gray-950{--tw-bg-opacity: 1;background-color:rgb(3 7 18 / var(--tw-bg-opacity, 1))}.bg-gray-950\/50{background-color:#03071280}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-500\/10{background-color:#22c55e1a}.bg-green-500\/20{background-color:#22c55e33}.bg-green-600\/20{background-color:#16a34a33}.bg-kanon-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-kanon-500\/10{background-color:#3b82f61a}.bg-kanon-500\/20{background-color:#3b82f633}.bg-kanon-500\/5{background-color:#3b82f60d}.bg-kanon-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-orange-500{--tw-bg-opacity: 1;background-color:rgb(249 115 22 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-red-500\/10{background-color:#ef44441a}.bg-red-500\/20{background-color:#ef444433}.bg-red-600\/20{background-color:#dc262633}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity, 1))}.bg-yellow-500\/10{background-color:#eab3081a}.bg-yellow-500\/15{background-color:#eab30826}.bg-yellow-500\/20{background-color:#eab30833}.bg-yellow-600\/20{background-color:#ca8a0433}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-3{padding-bottom:.75rem}.pl-3{padding-left:.75rem}.pr-16{padding-right:4rem}.pt-2{padding-top:.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.italic{font-style:italic}.leading-tight{line-height:1.25}.tracking-wider{letter-spacing:.05em}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-cyan-400{--tw-text-opacity: 1;color:rgb(34 211 238 / var(--tw-text-opacity, 1))}.text-gray-100{--tw-text-opacity: 1;color:rgb(243 244 246 / var(--tw-text-opacity, 1))}.text-gray-200{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-green-400\/60{color:#4ade8099}.text-green-400\/70{color:#4ade80b3}.text-kanon-300{--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.text-kanon-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-kanon-400\/60{color:#60a5fa99}.text-purple-400{--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-transparent{color:transparent}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-400{--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}.placeholder-gray-600::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(75 85 99 / var(--tw-placeholder-opacity, 1))}.placeholder-gray-600::placeholder{--tw-placeholder-opacity: 1;color:rgb(75 85 99 / var(--tw-placeholder-opacity, 1))}.caret-gray-200{caret-color:#e5e7eb}.opacity-0{opacity:0}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.ring-2{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-white{--tw-ring-opacity: 1;--tw-ring-color: rgb(255 255 255 / var(--tw-ring-opacity, 1))}.ring-offset-2{--tw-ring-offset-width: 2px}.ring-offset-gray-900{--tw-ring-offset-color: #111827}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.last\:border-0:last-child{border-width:0px}.hover\:scale-110:hover{--tw-scale-x: 1.1;--tw-scale-y: 1.1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:border-gray-600\/50:hover{border-color:#4b556380}.hover\:border-kanon-500\/50:hover{border-color:#3b82f680}.hover\:bg-gray-700\/50:hover{background-color:#37415180}.hover\:bg-gray-800:hover{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-800\/30:hover{background-color:#1f29374d}.hover\:bg-gray-800\/50:hover{background-color:#1f293780}.hover\:bg-gray-800\/80:hover{background-color:#1f2937cc}.hover\:bg-green-600\/30:hover{background-color:#16a34a4d}.hover\:bg-kanon-500:hover{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.hover\:bg-kanon-500\/20:hover{background-color:#3b82f633}.hover\:bg-kanon-500\/5:hover{background-color:#3b82f60d}.hover\:bg-red-500\/10:hover{background-color:#ef44441a}.hover\:bg-red-500\/20:hover{background-color:#ef444433}.hover\:bg-red-600\/30:hover{background-color:#dc26264d}.hover\:bg-yellow-600\/30:hover{background-color:#ca8a044d}.hover\:text-gray-200:hover{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.hover\:text-gray-300:hover{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.hover\:text-gray-400:hover{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.hover\:text-green-300:hover{--tw-text-opacity: 1;color:rgb(134 239 172 / var(--tw-text-opacity, 1))}.hover\:text-kanon-300:hover{--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.hover\:text-kanon-400:hover{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.hover\:text-red-300:hover{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.hover\:text-red-400:hover{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.focus\:border-kanon-500:focus{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.focus\:border-kanon-500\/50:focus{border-color:#3b82f680}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-kanon-500\/30:focus{--tw-ring-color: rgb(59 130 246 / .3)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:opacity-100{opacity:1}@media(min-width:768px){.md\:block{display:block}.md\:inline{display:inline}.md\:flex{display:flex}.md\:hidden{display:none}.md\:w-56{width:14rem}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:justify-start{justify-content:flex-start}.md\:border-r-2{border-right-width:2px}.md\:p-4{padding:1rem}.md\:px-4{padding-left:1rem;padding-right:1rem}}@media(min-width:1024px){.lg\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}}