kanon-cli 0.1.1 → 0.1.3
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 +123 -1
- package/package.json +3 -2
- package/src/commands/canvas.js +45 -0
- package/src/commands/card.js +14 -1
- package/src/commands/cards.js +3 -2
- package/src/commands/label.js +1 -1
- package/src/commands/list.js +1 -1
- package/src/commands/note.js +79 -0
- package/src/commands/sheet.js +137 -0
- package/src/commands/subcard.js +59 -0
- package/src/commands/watch.js +9 -0
- package/src/dashboard/dist/assets/index-ClOAcx9M.css +1 -0
- package/src/dashboard/dist/assets/{index-7UwkIyFn.js → index-DrHjrBfj.js} +53 -43
- package/src/dashboard/dist/index.html +2 -2
- package/src/dashboard/server/index.js +94 -0
- package/src/dashboard/server/proxy.js +5 -1
- package/src/dashboard/server/settings.js +11 -7
- package/src/lib/admin.js +29 -3
- package/src/lib/api.js +55 -0
- package/src/lib/claude.js +65 -9
- package/src/prompts/templates.js +55 -19
- package/src/dashboard/dist/assets/index-DBQ473Y5.css +0 -1
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Kanon Dashboard</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-DrHjrBfj.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-ClOAcx9M.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body class="h-full text-gray-100">
|
|
11
11
|
<div id="root" class="h-full"></div>
|
|
@@ -116,5 +116,99 @@ export function createDashboardServer(port = 3737) {
|
|
|
116
116
|
console.log(`Dashboard server running on http://localhost:${port}`);
|
|
117
117
|
});
|
|
118
118
|
|
|
119
|
+
// --- Nightly auto-update (4:00 AM) ---
|
|
120
|
+
// Checks every 15 min; when hour=4, checks for update, waits for idle workers, then updates & restarts.
|
|
121
|
+
let autoUpdateDone = null; // date string of last auto-update to avoid repeating same night
|
|
122
|
+
|
|
123
|
+
const autoUpdateTimer = setInterval(async () => {
|
|
124
|
+
try {
|
|
125
|
+
const now = new Date();
|
|
126
|
+
// Use local date string to match local getHours() check
|
|
127
|
+
const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
128
|
+
if (now.getHours() !== 4 || autoUpdateDone === today) return;
|
|
129
|
+
|
|
130
|
+
// Check for update
|
|
131
|
+
const pkgPath = path.resolve(__dirname, '../../../package.json');
|
|
132
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
133
|
+
const current = pkg.version;
|
|
134
|
+
|
|
135
|
+
const resp = await fetch('https://registry.npmjs.org/kanon-cli/latest', {
|
|
136
|
+
headers: { 'Accept': 'application/json' },
|
|
137
|
+
signal: AbortSignal.timeout(5000),
|
|
138
|
+
});
|
|
139
|
+
if (!resp.ok) return;
|
|
140
|
+
const { version: latest } = await resp.json();
|
|
141
|
+
if (!latest || latest === current) {
|
|
142
|
+
autoUpdateDone = today;
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Check if workers are idle (active or queued)
|
|
147
|
+
let busy = false;
|
|
148
|
+
try {
|
|
149
|
+
const lockPath = getAgentLockPath();
|
|
150
|
+
if (fs.existsSync(lockPath)) {
|
|
151
|
+
const lock = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
|
|
152
|
+
// Only query daemon if its process is alive
|
|
153
|
+
let alive = false;
|
|
154
|
+
try { process.kill(lock.pid, 0); alive = true; } catch {}
|
|
155
|
+
if (alive) {
|
|
156
|
+
const statusRes = await fetch(`http://127.0.0.1:${lock.port}/status`, {
|
|
157
|
+
signal: AbortSignal.timeout(3000),
|
|
158
|
+
});
|
|
159
|
+
if (statusRes.ok) {
|
|
160
|
+
const status = await statusRes.json();
|
|
161
|
+
busy = (status.activeWorkers || 0) > 0 || (status.queueLength || 0) > 0;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} catch {}
|
|
166
|
+
|
|
167
|
+
if (busy) {
|
|
168
|
+
// Workers or queued tasks — try again next interval
|
|
169
|
+
console.log(`[auto-update] Update ${current} → ${latest} available, but workers/queue active. Retrying later.`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// All clear — install update
|
|
174
|
+
console.log(`[auto-update] Updating kanon-cli ${current} → ${latest}...`);
|
|
175
|
+
autoUpdateDone = today;
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
execSync('npm install -g kanon-cli@latest', { stdio: 'pipe', timeout: 60000 });
|
|
179
|
+
console.log(`[auto-update] Updated to ${latest}. Restarting...`);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
console.error('[auto-update] npm install failed:', err.message);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Stop watch daemon before restart
|
|
186
|
+
try {
|
|
187
|
+
const lockPath = getAgentLockPath();
|
|
188
|
+
if (fs.existsSync(lockPath)) {
|
|
189
|
+
const lock = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
|
|
190
|
+
process.kill(lock.pid, 'SIGTERM');
|
|
191
|
+
}
|
|
192
|
+
} catch {}
|
|
193
|
+
|
|
194
|
+
// Restart dashboard
|
|
195
|
+
setTimeout(() => {
|
|
196
|
+
const binPath = path.resolve(__dirname, '../../../bin/kanon.js');
|
|
197
|
+
const child = spawn(process.execPath, [binPath, 'dashboard', '--no-browser', '-p', String(port)], {
|
|
198
|
+
stdio: 'ignore',
|
|
199
|
+
detached: true,
|
|
200
|
+
cwd: process.cwd(),
|
|
201
|
+
});
|
|
202
|
+
child.unref();
|
|
203
|
+
process.exit(0);
|
|
204
|
+
}, 1000);
|
|
205
|
+
} catch (err) {
|
|
206
|
+
console.error('[auto-update] Error:', err.message);
|
|
207
|
+
}
|
|
208
|
+
}, 15 * 60 * 1000); // check every 15 minutes
|
|
209
|
+
|
|
210
|
+
// Cleanup on server close
|
|
211
|
+
server.on('close', () => clearInterval(autoUpdateTimer));
|
|
212
|
+
|
|
119
213
|
return server;
|
|
120
214
|
}
|
|
@@ -10,8 +10,12 @@ export function createProxyRoutes() {
|
|
|
10
10
|
const serverUrl = getServerUrl();
|
|
11
11
|
|
|
12
12
|
try {
|
|
13
|
+
if (!config.token) {
|
|
14
|
+
return res.status(401).json({ error: 'Agent not configured. Set up your agent on the Credentials page first.' });
|
|
15
|
+
}
|
|
16
|
+
|
|
13
17
|
const headers = { 'Content-Type': 'application/json' };
|
|
14
|
-
|
|
18
|
+
headers['Authorization'] = `Bearer ${config.token}`;
|
|
15
19
|
|
|
16
20
|
const opts = { method, headers };
|
|
17
21
|
if (req.body && Object.keys(req.body).length) {
|
|
@@ -208,13 +208,17 @@ export function createSettingsRoutes() {
|
|
|
208
208
|
router.put('/credentials', (req, res) => {
|
|
209
209
|
try {
|
|
210
210
|
const current = loadGlobalConfig();
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
211
|
+
const email = req.body.email ?? current.email ?? '';
|
|
212
|
+
// If email is cleared, also clear auth fields
|
|
213
|
+
const clearAuth = !email;
|
|
214
|
+
saveGlobalConfig({
|
|
215
|
+
server_url: req.body.server_url ?? current.server_url ?? '',
|
|
216
|
+
email,
|
|
217
|
+
password: clearAuth ? '' : (req.body.password ?? current.password ?? ''),
|
|
218
|
+
token: clearAuth ? '' : (req.body.token ?? current.token ?? ''),
|
|
219
|
+
user_id: clearAuth ? '' : (req.body.user_id ?? current.user_id ?? ''),
|
|
220
|
+
user_name: clearAuth ? '' : (req.body.user_name ?? current.user_name ?? ''),
|
|
221
|
+
});
|
|
218
222
|
res.json({ ok: true });
|
|
219
223
|
} catch (err) {
|
|
220
224
|
res.status(500).json({ error: err.message });
|
package/src/lib/admin.js
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import fs from 'fs';
|
|
3
|
-
import { getStatusPath } from './config.js';
|
|
3
|
+
import { getStatusPath, getServerUrl, getToken } from './config.js';
|
|
4
4
|
import { getActiveWorkers, killWorker, spawnClaude } from './claude.js';
|
|
5
5
|
import { buildBundlePrompt } from '../prompts/templates.js';
|
|
6
6
|
|
|
7
|
+
/** Fire-and-forget POST to server for agent status changes */
|
|
8
|
+
function _notifyAgentStatus(cardId, boardId, action, extra = {}) {
|
|
9
|
+
const serverUrl = getServerUrl();
|
|
10
|
+
const token = getToken();
|
|
11
|
+
if (!serverUrl || !token || !boardId) return;
|
|
12
|
+
fetch(`${serverUrl}/api/agents/status`, {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
|
|
15
|
+
body: JSON.stringify({ cardId, boardId, action, ...extra }),
|
|
16
|
+
}).catch(() => {});
|
|
17
|
+
}
|
|
18
|
+
|
|
7
19
|
export class AgentController {
|
|
8
20
|
constructor(config = {}) {
|
|
9
21
|
this.checkInterval = (config.check_interval_seconds || 60) * 1000;
|
|
@@ -113,6 +125,7 @@ export class AgentController {
|
|
|
113
125
|
}
|
|
114
126
|
|
|
115
127
|
this.queue.push({ cardId, prompt, config: claudeConfig, priority, addedAt: Date.now(), card, extraPrompt, boardId });
|
|
128
|
+
_notifyAgentStatus(cardId, boardId, 'queue');
|
|
116
129
|
// Sort by priority (higher first), then by time (older first)
|
|
117
130
|
this.queue.sort((a, b) => b.priority - a.priority || a.addedAt - b.addedAt);
|
|
118
131
|
|
|
@@ -202,8 +215,16 @@ export class AgentController {
|
|
|
202
215
|
}
|
|
203
216
|
|
|
204
217
|
this.log('info', null, `Starting bundled worker for ${tasks.length} cards: ${cardIds.map(id => id.substring(0, 8)).join(', ')}`);
|
|
205
|
-
const worker = spawnClaude(bundleId, combinedPrompt, tasks[0].config);
|
|
218
|
+
const worker = spawnClaude(bundleId, combinedPrompt, { ...tasks[0].config, boardId: tasks[0].boardId, outputCardId: tasks[0].cardId });
|
|
206
219
|
if (worker?.process) {
|
|
220
|
+
// Notify primary card as active, others as bundled
|
|
221
|
+
for (const t of tasks) {
|
|
222
|
+
const isPrimary = t === tasks[0];
|
|
223
|
+
_notifyAgentStatus(t.cardId, t.boardId, 'start', {
|
|
224
|
+
bundleId,
|
|
225
|
+
primaryCardId: isPrimary ? null : tasks[0].cardId,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
207
228
|
worker.process.on('close', (code) => {
|
|
208
229
|
this._decBoard(boardKey);
|
|
209
230
|
this.completedWorkers.push({
|
|
@@ -217,6 +238,9 @@ export class AgentController {
|
|
|
217
238
|
output: worker.output.slice(-50),
|
|
218
239
|
});
|
|
219
240
|
if (this.completedWorkers.length > 10) this.completedWorkers.shift();
|
|
241
|
+
for (const cid of cardIds) {
|
|
242
|
+
_notifyAgentStatus(cid, tasks[0].boardId, 'stop', { exitCode: code });
|
|
243
|
+
}
|
|
220
244
|
this.log(code === 0 ? 'info' : 'warn', null, `Bundled worker finished (exit ${code}, ${Math.round((Date.now() - worker.startedAt) / 1000)}s) — cards: ${cardIds.map(id => id.substring(0, 8)).join(', ')}`);
|
|
221
245
|
this._processQueue();
|
|
222
246
|
});
|
|
@@ -225,8 +249,9 @@ export class AgentController {
|
|
|
225
249
|
|
|
226
250
|
_startSingleWorker(task, boardKey) {
|
|
227
251
|
this.log('info', task.cardId, 'Starting worker from queue');
|
|
228
|
-
const worker = spawnClaude(task.cardId, task.prompt, task.config);
|
|
252
|
+
const worker = spawnClaude(task.cardId, task.prompt, { ...task.config, boardId: task.boardId });
|
|
229
253
|
if (worker?.process) {
|
|
254
|
+
_notifyAgentStatus(task.cardId, task.boardId, 'start');
|
|
230
255
|
worker.process.on('close', (code) => {
|
|
231
256
|
if (boardKey) this._decBoard(boardKey);
|
|
232
257
|
this.completedWorkers.push({
|
|
@@ -238,6 +263,7 @@ export class AgentController {
|
|
|
238
263
|
output: worker.output.slice(-50),
|
|
239
264
|
});
|
|
240
265
|
if (this.completedWorkers.length > 10) this.completedWorkers.shift();
|
|
266
|
+
_notifyAgentStatus(task.cardId, task.boardId, 'stop', { exitCode: code });
|
|
241
267
|
this.log(code === 0 ? 'info' : 'warn', task.cardId, `Worker finished (exit ${code}, ${Math.round((Date.now() - worker.startedAt) / 1000)}s)`);
|
|
242
268
|
this._processQueue();
|
|
243
269
|
});
|
package/src/lib/api.js
CHANGED
|
@@ -219,6 +219,61 @@ class KanonAPI {
|
|
|
219
219
|
}
|
|
220
220
|
return res.json();
|
|
221
221
|
}
|
|
222
|
+
// --- Subcards ---
|
|
223
|
+
|
|
224
|
+
async setParent(cardId, parentCardId) {
|
|
225
|
+
return this.request('PUT', `/cards/${cardId}/set-parent`, { parentCardId });
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// --- Sheets ---
|
|
229
|
+
|
|
230
|
+
async getSheet(cardId) {
|
|
231
|
+
return this.request('GET', `/cards/${cardId}/sheet`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async createSheet(cardId) {
|
|
235
|
+
return this.request('POST', `/cards/${cardId}/sheet`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async updateSheet(cardId, data) {
|
|
239
|
+
return this.request('PUT', `/cards/${cardId}/sheet`, { data });
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async updateSheetCell(cardId, { tab, row, col, value }) {
|
|
243
|
+
return this.request('PUT', `/cards/${cardId}/sheet/cell`, { tab, row, col, value });
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async deleteSheet(cardId) {
|
|
247
|
+
return this.request('DELETE', `/cards/${cardId}/sheet`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// --- Notes (plaintext) ---
|
|
251
|
+
|
|
252
|
+
async getNoteText(cardId) {
|
|
253
|
+
return this.request('GET', `/cards/${cardId}/note/text`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async setNoteText(cardId, text) {
|
|
257
|
+
return this.request('PUT', `/cards/${cardId}/note/text`, { text });
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async appendNoteText(cardId, text) {
|
|
261
|
+
return this.request('POST', `/cards/${cardId}/note/text/append`, { text });
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async createNote(cardId) {
|
|
265
|
+
return this.request('POST', `/cards/${cardId}/note`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async deleteNote(cardId) {
|
|
269
|
+
return this.request('DELETE', `/cards/${cardId}/note`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// --- Canvas/Whiteboard ---
|
|
273
|
+
|
|
274
|
+
async getCanvasSummary(cardId) {
|
|
275
|
+
return this.request('GET', `/cards/${cardId}/whiteboard/summary`);
|
|
276
|
+
}
|
|
222
277
|
}
|
|
223
278
|
|
|
224
279
|
export const api = new KanonAPI();
|
package/src/lib/claude.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
|
+
import fs from 'fs';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
import { fileURLToPath } from 'url';
|
|
4
5
|
import chalk from 'chalk';
|
|
6
|
+
import { getServerUrl, getToken } from './config.js';
|
|
5
7
|
|
|
6
8
|
// Resolve the bin/ directory so `kanon` is in PATH for spawned Claude sessions
|
|
7
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -10,6 +12,19 @@ const kanonBinDir = path.resolve(__dirname, '../../bin');
|
|
|
10
12
|
/** Active Claude processes indexed by cardId */
|
|
11
13
|
const activeWorkers = new Map();
|
|
12
14
|
|
|
15
|
+
/** Fire-and-forget POST to server for agent output streaming */
|
|
16
|
+
function _flushToServer(cardId, boardId, lines, config) {
|
|
17
|
+
const serverUrl = getServerUrl();
|
|
18
|
+
const token = getToken();
|
|
19
|
+
if (!serverUrl || !token) return;
|
|
20
|
+
fetch(`${serverUrl}/api/agents/output`, {
|
|
21
|
+
method: 'POST',
|
|
22
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
|
|
23
|
+
body: JSON.stringify({ cardId, boardId, lines }),
|
|
24
|
+
}).catch(() => {}); // fire-and-forget
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
13
28
|
/**
|
|
14
29
|
* Spawn a Claude Code process for a card.
|
|
15
30
|
* Returns a WorkerInfo object tracked by the agent controller.
|
|
@@ -35,6 +50,16 @@ export function spawnClaude(cardId, prompt, config = {}) {
|
|
|
35
50
|
|
|
36
51
|
const projectDir = config.project_dir || process.cwd();
|
|
37
52
|
|
|
53
|
+
// Write a temporary kanon.config.yaml so `kanon board/cards/list` work inside the agent
|
|
54
|
+
let tempConfigPath = null;
|
|
55
|
+
if (config.boardId) {
|
|
56
|
+
const configPath = path.join(projectDir, 'kanon.config.yaml');
|
|
57
|
+
if (!fs.existsSync(configPath)) {
|
|
58
|
+
tempConfigPath = configPath;
|
|
59
|
+
fs.writeFileSync(configPath, `board_id: "${config.boardId}"\n`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
38
63
|
const worker = {
|
|
39
64
|
cardId,
|
|
40
65
|
startedAt: Date.now(),
|
|
@@ -42,6 +67,10 @@ export function spawnClaude(cardId, prompt, config = {}) {
|
|
|
42
67
|
output: [],
|
|
43
68
|
exitCode: null,
|
|
44
69
|
process: null,
|
|
70
|
+
_pendingServerLines: [],
|
|
71
|
+
_boardId: config.boardId || null,
|
|
72
|
+
_outputCardId: config.outputCardId || null,
|
|
73
|
+
_tempConfigPath: tempConfigPath,
|
|
45
74
|
};
|
|
46
75
|
|
|
47
76
|
const proc = spawn(claudeCmd, args, {
|
|
@@ -53,6 +82,14 @@ export function spawnClaude(cardId, prompt, config = {}) {
|
|
|
53
82
|
|
|
54
83
|
worker.process = proc;
|
|
55
84
|
|
|
85
|
+
// Flush pending output to server every 3 seconds + poll for kill requests
|
|
86
|
+
worker._flushTimer = setInterval(() => {
|
|
87
|
+
if (worker._pendingServerLines.length > 0 && worker._boardId) {
|
|
88
|
+
const lines = worker._pendingServerLines.splice(0);
|
|
89
|
+
_flushToServer(worker._outputCardId || worker.cardId, worker._boardId, lines, config);
|
|
90
|
+
}
|
|
91
|
+
}, 3000);
|
|
92
|
+
|
|
56
93
|
// Buffer for incomplete JSON lines from stream-json output
|
|
57
94
|
let stdoutBuf = '';
|
|
58
95
|
|
|
@@ -73,7 +110,8 @@ export function spawnClaude(cardId, prompt, config = {}) {
|
|
|
73
110
|
// Not valid JSON – store raw line as-is
|
|
74
111
|
worker.lastOutput = Date.now();
|
|
75
112
|
worker.output.push(trimmed);
|
|
76
|
-
|
|
113
|
+
worker._pendingServerLines.push(trimmed);
|
|
114
|
+
if (worker.output.length > 500) worker.output.shift();
|
|
77
115
|
continue;
|
|
78
116
|
}
|
|
79
117
|
|
|
@@ -84,21 +122,28 @@ export function spawnClaude(cardId, prompt, config = {}) {
|
|
|
84
122
|
for (const block of parsed.message.content) {
|
|
85
123
|
if (block.type === 'text' && block.text) {
|
|
86
124
|
worker.output.push(block.text);
|
|
125
|
+
worker._pendingServerLines.push(block.text);
|
|
87
126
|
} else if (block.type === 'tool_use') {
|
|
88
127
|
const name = block.name || 'unknown';
|
|
89
128
|
const inputPreview = block.input
|
|
90
129
|
? JSON.stringify(block.input).substring(0, 120)
|
|
91
130
|
: '';
|
|
92
|
-
|
|
131
|
+
const toolLine = `[Tool: ${name}] ${inputPreview}`;
|
|
132
|
+
worker.output.push(toolLine);
|
|
133
|
+
worker._pendingServerLines.push(toolLine);
|
|
93
134
|
}
|
|
94
|
-
if (worker.output.length >
|
|
135
|
+
if (worker.output.length > 500) worker.output.shift();
|
|
95
136
|
}
|
|
96
137
|
} else if (parsed.type === 'result' && parsed.result) {
|
|
97
|
-
|
|
98
|
-
|
|
138
|
+
const resultLine = `[Result] ${parsed.result.substring(0, 200)}`;
|
|
139
|
+
worker.output.push(resultLine);
|
|
140
|
+
worker._pendingServerLines.push(resultLine);
|
|
141
|
+
if (worker.output.length > 500) worker.output.shift();
|
|
99
142
|
} else if (parsed.type === 'error') {
|
|
100
|
-
|
|
101
|
-
|
|
143
|
+
const errorLine = `[Error] ${parsed.error?.message || JSON.stringify(parsed)}`;
|
|
144
|
+
worker.output.push(errorLine);
|
|
145
|
+
worker._pendingServerLines.push(errorLine);
|
|
146
|
+
if (worker.output.length > 500) worker.output.shift();
|
|
102
147
|
}
|
|
103
148
|
// Silently skip other message types (system, etc.)
|
|
104
149
|
}
|
|
@@ -107,12 +152,23 @@ export function spawnClaude(cardId, prompt, config = {}) {
|
|
|
107
152
|
proc.stderr.on('data', (data) => {
|
|
108
153
|
const line = data.toString();
|
|
109
154
|
worker.lastOutput = Date.now();
|
|
110
|
-
|
|
111
|
-
|
|
155
|
+
const stderrLine = `[stderr] ${line}`;
|
|
156
|
+
worker.output.push(stderrLine);
|
|
157
|
+
worker._pendingServerLines.push(stderrLine);
|
|
158
|
+
if (worker.output.length > 500) worker.output.shift();
|
|
112
159
|
});
|
|
113
160
|
|
|
114
161
|
proc.on('close', (code) => {
|
|
115
162
|
worker.exitCode = code;
|
|
163
|
+
clearInterval(worker._flushTimer);
|
|
164
|
+
// Final flush
|
|
165
|
+
if (worker._pendingServerLines.length > 0 && worker._boardId) {
|
|
166
|
+
_flushToServer(worker._outputCardId || worker.cardId, worker._boardId, worker._pendingServerLines.splice(0), config);
|
|
167
|
+
}
|
|
168
|
+
// Clean up temp config
|
|
169
|
+
if (worker._tempConfigPath) {
|
|
170
|
+
try { fs.unlinkSync(worker._tempConfigPath); } catch {}
|
|
171
|
+
}
|
|
116
172
|
activeWorkers.delete(cardId);
|
|
117
173
|
console.log(chalk.dim(`Worker for card ${cardId} exited with code ${code}`));
|
|
118
174
|
});
|
package/src/prompts/templates.js
CHANGED
|
@@ -5,31 +5,44 @@
|
|
|
5
5
|
|
|
6
6
|
export const DEFAULT_SYSTEM = `You are a Kanon board agent. You interact with the board using the \`kanon\` CLI which is in your PATH. The CLI is already authenticated.
|
|
7
7
|
|
|
8
|
+
The card details below already contain the full card content — do NOT re-read the card with \`kanon card <id>\` unless you need to refresh after making changes.
|
|
9
|
+
|
|
8
10
|
## Key Commands
|
|
9
11
|
\`\`\`
|
|
10
|
-
kanon card <id>
|
|
11
|
-
kanon card
|
|
12
|
-
kanon card update <id>
|
|
13
|
-
kanon
|
|
14
|
-
kanon
|
|
15
|
-
kanon
|
|
12
|
+
kanon card update <id> --comment "text" — Add a comment
|
|
13
|
+
kanon card update <id> --move "List" — Move card to a list (by name)
|
|
14
|
+
kanon card update <id> --done — Mark card as done
|
|
15
|
+
kanon card update <id> --description "x" — Set description
|
|
16
|
+
kanon card update <id> --add-label "x" — Add a label
|
|
17
|
+
kanon card <id> — Re-read card (only if needed)
|
|
18
|
+
kanon board — Board context (only if you need list names or members)
|
|
19
|
+
kanon cards — List all cards (only if task requires it)
|
|
20
|
+
kanon subcard <parentId> — List subcards
|
|
21
|
+
kanon subcard <parentId> nest <cardId> — Nest card under parent
|
|
22
|
+
kanon subcard <parentId> unnest <cardId> — Unnest card from parent
|
|
23
|
+
kanon card create <title> <list> --parent <id> — Create as subcard
|
|
24
|
+
kanon sheet <id> — Read sheet data
|
|
25
|
+
kanon sheet <id> set <tab> <row> <col> "val" — Set cell (0-indexed)
|
|
26
|
+
kanon sheet <id> export — CSV output
|
|
27
|
+
kanon note <id> — Read note text
|
|
28
|
+
kanon note <id> set "text" — Set note text
|
|
29
|
+
kanon note <id> append "text" — Append to note
|
|
30
|
+
kanon canvas <id> — Read canvas objects & connections
|
|
31
|
+
kanon help-all — Full command reference
|
|
16
32
|
\`\`\`
|
|
17
33
|
|
|
18
|
-
Run \`kanon help-all\` to see all available commands including checklists, labels, lists, and attachments.
|
|
19
34
|
Be concise and fast. Do not explain what you will do — just do it.`;
|
|
20
35
|
|
|
21
|
-
export const DEFAULT_TASK =
|
|
22
|
-
1.
|
|
23
|
-
2. If the
|
|
24
|
-
3.
|
|
25
|
-
4.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
7. When the task is fully complete, move the card to a "Review" list if one exists and mark it as done.
|
|
29
|
-
8. If something is unclear, add a comment with your questions instead of guessing.
|
|
36
|
+
export const DEFAULT_TASK = `## Instructions
|
|
37
|
+
1. The card details above are already complete — start working immediately.
|
|
38
|
+
2. If the task involves code changes, implement them, then commit and push.
|
|
39
|
+
3. When done, add a brief comment summarizing what you did.
|
|
40
|
+
4. Move the card to "Review" and mark it done:
|
|
41
|
+
\`kanon card update <id> --move "Review" --done\`
|
|
42
|
+
5. If something is unclear, add a comment with your questions instead of guessing.
|
|
30
43
|
|
|
31
|
-
Do not
|
|
32
|
-
|
|
44
|
+
Do not run \`kanon board\`, \`kanon cards\`, or \`kanon help-all\` unless the task specifically requires that information.
|
|
45
|
+
Do not modify cards you were not asked to work on.`;
|
|
33
46
|
|
|
34
47
|
export const DEFAULT_BUNDLE = `You have multiple cards to work on. Review each card below and process them.
|
|
35
48
|
Use sub-agents (the Agent tool) to work on cards in parallel where possible.
|
|
@@ -40,7 +53,10 @@ For each card, follow the task instructions above.`;
|
|
|
40
53
|
* Format card details into a readable block.
|
|
41
54
|
*/
|
|
42
55
|
export function formatCard(card) {
|
|
43
|
-
const parts = [`Card ID: ${card.id}
|
|
56
|
+
const parts = [`Card ID: ${card.id}`];
|
|
57
|
+
if (card.boardId) parts.push(`Board ID: ${card.boardId}`);
|
|
58
|
+
if (card.list_name) parts.push(`List: ${card.list_name}`);
|
|
59
|
+
parts.push(`Title: ${card.title}`);
|
|
44
60
|
|
|
45
61
|
if (card.description) {
|
|
46
62
|
parts.push(`Description: ${card.description}`);
|
|
@@ -60,6 +76,26 @@ export function formatCard(card) {
|
|
|
60
76
|
}
|
|
61
77
|
}
|
|
62
78
|
|
|
79
|
+
if (card.subcards?.length) {
|
|
80
|
+
parts.push(`\nSubcards:`);
|
|
81
|
+
for (const sc of card.subcards) {
|
|
82
|
+
const done = sc.is_done ? ' [done]' : '';
|
|
83
|
+
parts.push(`- ${sc.title} (${sc.id})${done}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (card.parent_card_id) {
|
|
88
|
+
parts.push(`Parent card: ${card.parent_card_id}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const attachments = [];
|
|
92
|
+
if (card.has_sheet) attachments.push('sheet');
|
|
93
|
+
if (card.has_note) attachments.push('note');
|
|
94
|
+
if (card.has_whiteboard) attachments.push('canvas');
|
|
95
|
+
if (attachments.length) {
|
|
96
|
+
parts.push(`Attachments: ${attachments.join(', ')} (use \`kanon ${attachments[0]} ${card.id}\` to read)`);
|
|
97
|
+
}
|
|
98
|
+
|
|
63
99
|
if (card.comments?.length) {
|
|
64
100
|
parts.push(`\nComments (newest first):`);
|
|
65
101
|
for (const c of card.comments.slice(0, 10)) {
|
|
@@ -1 +0,0 @@
|
|
|
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}.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-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}.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-10{width:2.5rem}.w-12{width:3rem}.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-56{width:14rem}.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}.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-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-5{grid-template-columns:repeat(5,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-r-2{border-right-width:2px}.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-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-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}}
|