@polderlabs/bizar 2.3.0 → 2.6.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.
Files changed (48) hide show
  1. package/cli/bin.mjs +73 -0
  2. package/cli/copy.mjs +42 -2
  3. package/cli/dashboard/api.mjs +473 -0
  4. package/cli/dashboard/browser.mjs +40 -0
  5. package/cli/dashboard/server.mjs +366 -0
  6. package/cli/dashboard/state.mjs +438 -0
  7. package/cli/dashboard/tasks-store.mjs +203 -0
  8. package/cli/dashboard/watcher.mjs +81 -0
  9. package/cli/dashboard.mjs +97 -0
  10. package/cli/install.mjs +17 -4
  11. package/config/commands/bizar.md +18 -0
  12. package/config/commands/plan.md +26 -0
  13. package/config/commands/visual-plan.md +15 -0
  14. package/config/opencode.json +259 -1
  15. package/dist/assets/index-BVvY22Gt.css +1 -0
  16. package/dist/assets/index-CO3c8O32.js +285 -0
  17. package/dist/assets/index-CO3c8O32.js.map +1 -0
  18. package/dist/index.html +18 -0
  19. package/package.json +26 -2
  20. package/src/App.tsx +233 -0
  21. package/src/components/Button.tsx +55 -0
  22. package/src/components/Card.tsx +40 -0
  23. package/src/components/EmptyState.tsx +30 -0
  24. package/src/components/Modal.tsx +137 -0
  25. package/src/components/Spinner.tsx +19 -0
  26. package/src/components/StatusBadge.tsx +25 -0
  27. package/src/components/Tag.tsx +28 -0
  28. package/src/components/Toast.tsx +142 -0
  29. package/src/components/Topbar.tsx +88 -0
  30. package/src/index.html +17 -0
  31. package/src/lib/api.ts +71 -0
  32. package/src/lib/markdown.tsx +59 -0
  33. package/src/lib/types.ts +200 -0
  34. package/src/lib/utils.ts +79 -0
  35. package/src/lib/ws.ts +132 -0
  36. package/src/main.tsx +12 -0
  37. package/src/styles/main.css +2324 -0
  38. package/src/views/Agents.tsx +199 -0
  39. package/src/views/Chat.tsx +255 -0
  40. package/src/views/Config.tsx +250 -0
  41. package/src/views/Overview.tsx +267 -0
  42. package/src/views/Plans.tsx +667 -0
  43. package/src/views/Projects.tsx +155 -0
  44. package/src/views/Settings.tsx +253 -0
  45. package/src/views/Tasks.tsx +567 -0
  46. package/tsconfig.json +23 -0
  47. package/vite.config.ts +24 -0
  48. package/config/opencode.json.template +0 -52
@@ -0,0 +1,81 @@
1
+ /**
2
+ * cli/dashboard/watcher.mjs
3
+ *
4
+ * chokidar-backed file watcher. Translates low-level fs events into the
5
+ * shape the dashboard's WebSocket layer expects.
6
+ *
7
+ * The watcher ignores initial add events (the UI doesn't need a "you just
8
+ * started, here are 200 files that already existed" flood on connect).
9
+ */
10
+ import chokidar from 'chokidar';
11
+
12
+ /**
13
+ * @param {object} opts
14
+ * @param {string[]} opts.paths - files or directories to watch
15
+ * @param {(event: 'add'|'change'|'unlink', path: string) => void} opts.onChange
16
+ * @param {object} [opts.options] - extra chokidar options
17
+ */
18
+ export function createWatcher({ paths, onChange, options = {} }) {
19
+ if (!Array.isArray(paths) || paths.length === 0) {
20
+ throw new Error('createWatcher requires a non-empty paths array');
21
+ }
22
+ if (typeof onChange !== 'function') {
23
+ throw new Error('createWatcher requires an onChange callback');
24
+ }
25
+
26
+ const watcher = chokidar.watch(paths, {
27
+ ignoreInitial: true,
28
+ persistent: true,
29
+ awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 },
30
+ // Ignore: any path segment named `node_modules` (anywhere), and any
31
+ // file/dir whose IMMEDIATE basename starts with `.` (covers .DS_Store,
32
+ // .swp, .bak, etc.). We must NOT walk the whole path — the watched
33
+ // root `~/.config/opencode/agents` contains the segment `.config`
34
+ // which would otherwise match and silently disable the watch.
35
+ ignored: (p) => {
36
+ const s = String(p);
37
+ const parts = s.split(/[\\/]/);
38
+ // Walk all segments — `node_modules` is fine to match anywhere.
39
+ if (parts.includes('node_modules')) return true;
40
+ // Last segment is the basename — check that ONLY, so `.config`
41
+ // in parent dirs doesn't disqualify the watched root.
42
+ const basename = parts[parts.length - 1] || '';
43
+ return basename.startsWith('.');
44
+ },
45
+ ...options,
46
+ });
47
+
48
+ const safe = (event, p) => {
49
+ try {
50
+ onChange(event, p);
51
+ } catch (err) {
52
+ // A faulty onChange must not crash the watcher
53
+ console.error('[dashboard watcher] onChange error:', err);
54
+ }
55
+ };
56
+
57
+ watcher.on('add', (p) => safe('add', p));
58
+ watcher.on('change', (p) => safe('change', p));
59
+ watcher.on('unlink', (p) => safe('unlink', p));
60
+ watcher.on('error', (err) => {
61
+ console.error('[dashboard watcher] chokidar error:', err);
62
+ });
63
+
64
+ return {
65
+ /** @returns {chokidar.FSWatcher} */
66
+ start() {
67
+ return watcher;
68
+ },
69
+ async stop() {
70
+ try {
71
+ await watcher.close();
72
+ } catch {
73
+ /* ignore */
74
+ }
75
+ },
76
+ /** Force a synthetic broadcast — useful after a self-mutation. */
77
+ poke(event = 'change', path = '<synthetic>') {
78
+ safe(event, path);
79
+ },
80
+ };
81
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * cli/dashboard.mjs
3
+ *
4
+ * v2.5.0 — Dashboard launcher.
5
+ *
6
+ * Picks a free port (preferred: 4321), starts an Express + WebSocket server,
7
+ * writes port + PID files under ~/.config/bizar/, and opens the user's
8
+ * default browser. The server is created and returned; the caller decides
9
+ * whether to keep the process alive.
10
+ *
11
+ * Returns: { url, port, close }
12
+ */
13
+ import { createServer } from './dashboard/server.mjs';
14
+ import { launchBrowser } from './dashboard/browser.mjs';
15
+ import { writeFileSync, mkdirSync } from 'node:fs';
16
+ import { join } from 'node:path';
17
+ import { homedir } from 'node:os';
18
+ import net from 'node:net';
19
+
20
+ const DEFAULT_PORT = 4321;
21
+ const BIZAR_HOME = join(homedir(), '.config', 'bizar');
22
+ const PORT_FILE = join(BIZAR_HOME, 'dashboard.port');
23
+ const PID_FILE = join(BIZAR_HOME, 'dashboard.pid');
24
+
25
+ export async function launchDashboard(options = {}) {
26
+ // 1. Find a free port
27
+ const port = await findFreePort(options.port || DEFAULT_PORT);
28
+
29
+ // 2. Create the server (does not listen yet)
30
+ const { server, wss, close } = createServer({
31
+ port,
32
+ projectRoot: options.projectRoot || process.cwd(),
33
+ opencodeConfigDir:
34
+ options.opencodeConfigDir || join(homedir(), '.config', 'opencode'),
35
+ bizarRoot: options.bizarRoot || new URL('../..', import.meta.url).pathname,
36
+ });
37
+
38
+ // 3. Start listening on loopback only
39
+ await new Promise((resolve, reject) => {
40
+ server.once('error', reject);
41
+ server.listen(port, '127.0.0.1', () => {
42
+ server.off('error', reject);
43
+ resolve();
44
+ });
45
+ });
46
+
47
+ // 4. Persist port + PID so the CLI subcommand can find/stop us
48
+ mkdirSync(BIZAR_HOME, { recursive: true });
49
+ writeFileSync(PORT_FILE, String(port), 'utf8');
50
+ writeFileSync(PID_FILE, String(process.pid), 'utf8');
51
+
52
+ // 5. Open the browser (best effort — non-fatal)
53
+ const url = `http://localhost:${port}/`;
54
+ await launchBrowser(url);
55
+
56
+ // 6. Friendly log
57
+ console.log(`Bizar dashboard: ${url}`);
58
+
59
+ return {
60
+ url,
61
+ port,
62
+ close,
63
+ wss,
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Try the preferred port first, then walk upward up to 100 slots. If still
69
+ * no luck, surface an error so the caller can decide what to do.
70
+ */
71
+ async function findFreePort(preferred) {
72
+ for (let p = preferred; p < preferred + 100; p++) {
73
+ if (await isPortFree(p)) return p;
74
+ }
75
+ throw new Error(
76
+ `No free port found in range ${preferred}..${preferred + 99}`,
77
+ );
78
+ }
79
+
80
+ function isPortFree(port) {
81
+ return new Promise((resolve) => {
82
+ const server = net.createServer();
83
+ let settled = false;
84
+ const finish = (ok) => {
85
+ if (settled) return;
86
+ settled = true;
87
+ resolve(ok);
88
+ };
89
+ server.once('error', () => finish(false));
90
+ server.once('listening', () => server.close(() => finish(true)));
91
+ // Defensive timeout — don't hang on a wedged port
92
+ const timer = setTimeout(() => finish(false), 1000);
93
+ server.listen(port, '127.0.0.1', () => clearTimeout(timer));
94
+ });
95
+ }
96
+
97
+ export { PORT_FILE, PID_FILE, DEFAULT_PORT };
package/cli/install.mjs CHANGED
@@ -1,11 +1,12 @@
1
1
  import chalk from 'chalk';
2
2
  import boxen from 'boxen';
3
3
  import { existsSync } from 'node:fs';
4
+ import { join } from 'node:path';
4
5
 
5
6
  import { showBanner, showPantheon, sectionHeading } from './banner.mjs';
6
7
  import { promptComponents, promptInstallMode, promptAgents, promptSkillPacks, promptApiKeys, promptConfirmInstall, promptRestartOpenCode } from './prompts.mjs';
7
8
  import { detectOpenCode, detectRtk, detectSemble, detectSkillsCli, buildSummary, opencodeAgentsDir, opencodeConfigDir, repoPath } from './utils.mjs';
8
- import { installAgents, installAgentsMd, installSkill, installOpencodeJson, installBizarFolder, installPluginBizar, installRtk, installSemble, installSkillsCli, installCuratedSkills, installRules, installHooks, installCommands, mergeToolsIntoUserConfig } from './copy.mjs';
9
+ import { installAgents, installAgentsMd, installSkill, installOpencodeJson, installBizarFolder, installPluginBizar, installRtk, installSemble, installSkillsCli, installCuratedSkills, installRules, installHooks, installCommands, installCommandsBizar, mergeToolsIntoUserConfig } from './copy.mjs';
9
10
 
10
11
  const AGENT_FILES = [
11
12
  'odin.md', 'vor.md', 'frigg.md', 'quick.md',
@@ -295,13 +296,25 @@ export async function runInstaller() {
295
296
 
296
297
  export async function runPostInstall() {
297
298
  const { mkdirSync, copyFileSync, existsSync } = await import('node:fs');
298
- const { join } = await import('node:path');
299
299
  const { execSync } = await import('node:child_process');
300
300
 
301
+ const dest = join(opencodeConfigDir(), 'opencode.json');
302
+ const templateSrc = repoPath('config', 'opencode.json');
303
+ if (!existsSync(dest)) {
304
+ if (existsSync(templateSrc)) {
305
+ mkdirSync(opencodeConfigDir(), { recursive: true });
306
+ copyFileSync(templateSrc, dest);
307
+ console.log(' ✓ opencode.json bootstrapped from package template');
308
+ }
309
+ }
310
+
311
+ // Install Bizar commands to commands-bizar/ (separate from ECC's commands symlink)
312
+ await installCommandsBizar();
313
+
301
314
  const env = await detectOpenCode();
302
315
  if (!env.exists) {
303
- console.log('BizarHarness: opencode not detected — skipping auto-install.');
304
- return;
316
+ mkdirSync(opencodeConfigDir(), { recursive: true });
317
+ console.log('BizarHarness: created ~/.config/opencode/');
305
318
  }
306
319
 
307
320
  mkdirSync(opencodeAgentsDir(), { recursive: true });
@@ -0,0 +1,18 @@
1
+ # Bizar Dashboard
2
+
3
+ The `/bizar` command launches the Bizar dashboard in your browser — a fully integrated workspace with Overview, Chat, Agents, Plans, Projects, Config, and Settings panels. The dashboard binds to `127.0.0.1` only and runs as a local Express + WebSocket server on a free port (preferred: 4321).
4
+
5
+ If the user invoked `/bizar` with arguments, treat them as a request and route appropriately:
6
+
7
+ - "explain X" → invoke `/explain X`
8
+ - "plan Y" → invoke `/visual-plan on` and then `/plan new <slug>` with the user's intent as the slug
9
+ - "review PR" → invoke `/pr-review`
10
+ - "audit" → invoke `/audit`
11
+ - "learn" → invoke `/learn`
12
+ - "init" → invoke `/init`
13
+ - "dashboard" or "open dashboard" → `/bizar` (no args, will launch the dashboard)
14
+ - Otherwise: ask one clarifying question
15
+
16
+ If the user invoked `/bizar` with no arguments, the dashboard is launching in the background. Visit `http://localhost:<port>/` to access it. The plugin's `chat.message` hook spawns `bizar dashboard start` as a detached child process and surfaces the live URL in its response.
17
+
18
+ Common ports: 4321 is preferred; if it's taken, the launcher walks upward and picks the next free port. The PID and port are recorded under `~/.config/bizar/dashboard.{pid,port}` so `bizar dashboard stop` and `bizar dashboard status` can find the running instance.
@@ -0,0 +1,26 @@
1
+ Open or manage a Bizar visual plan. Plans are collaborative canvases for structuring work across agents.
2
+
3
+ ## Usage
4
+
5
+ ```
6
+ /plan new <slug> [template] — Create a new plan with a unique slug
7
+ /plan list — List all existing plans
8
+ /plan open <slug> — Open a plan (returns the canvas URL)
9
+ /plan get <slug> — Get full plan content as JSON
10
+ /plan add <slug> --title "..." — Add a new element to a plan
11
+ /plan update <slug> <id> ... — Update an element's content/title/status
12
+ /plan delete <slug> <id> — Delete an element from a plan
13
+ /plan comment <slug> [id] "..." — Add a comment to a plan or element
14
+ /plan comments <slug> [id] — List comments on a plan or element
15
+ /plan status <slug> <status> — Set plan status (draft|approved|rejected|in-progress|done)
16
+ /plan wait <slug> — Block until the plan receives feedback
17
+ ```
18
+
19
+ ## Routing
20
+
21
+ - If the user wants to create a new plan → `/plan new <slug>`
22
+ - If the user wants to see existing plans → `/plan list`
23
+ - If the user wants to open/view a plan → `/plan open <slug>`
24
+ - If the user wants to add content to a plan → `/plan add <slug> --title "..."`
25
+ - If the user wants to modify a plan element → `/plan update <slug> <id> ...`
26
+ - If the user wants to discuss/approve/reject → `/plan status <slug> <status>`
@@ -0,0 +1,15 @@
1
+ Toggle the Bizar visual plan canvas or check its current status.
2
+
3
+ ## Usage
4
+
5
+ ```
6
+ /visual-plan on — Enable the visual plan canvas in the current session
7
+ /visual-plan off — Disable the visual plan canvas
8
+ /visual-plan status — Show whether the canvas is currently enabled
9
+ ```
10
+
11
+ ## Notes
12
+
13
+ The visual plan canvas shows plans as interactive node graphs. When enabled, plan elements appear as draggable nodes on a canvas that agents can manipulate directly. This is useful for visual thinkers or when planning complex multi-step work.
14
+
15
+ This is a session-level toggle — it affects the current opencode session only.
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://opencode.ai/config.json",
3
3
  "model": "minimax/MiniMax-M3",
4
4
  "small_model": "minimax/MiniMax-M2.7",
5
- "default_agent": "quick",
5
+ "default_agent": "odin",
6
6
  "permission": "allow",
7
7
  "snapshot": false,
8
8
  "mcp": {
@@ -48,5 +48,263 @@
48
48
  "bizar_status": true,
49
49
  "bizar_collect": true,
50
50
  "bizar_kill": true
51
+ },
52
+ "agent": {
53
+ "odin": {
54
+ "description": "Odin — Pure router that delegates all work to subagents. Routes across Frigg (DeepSeek/Q&A), Vör (DeepSeek/clarify), Mimir (DeepSeek/research), Heimdall (DeepSeek/simple), Hermod (M2.7/git), Thor (M2.7/mid), Baldr (M2.7/design), Tyr (M3/top), Vidarr (GPT-5.5/ultra), Forseti (verifier/M3).",
55
+ "mode": "primary",
56
+ "model": "minimax/MiniMax-M3",
57
+ "color": "#6366f1",
58
+ "permission": {
59
+ "task": "allow",
60
+ "read": "allow",
61
+ "list": "allow",
62
+ "todowrite": "allow",
63
+ "webfetch": "allow",
64
+ "websearch": "allow"
65
+ }
66
+ },
67
+ "vor": {
68
+ "description": "Vör — The Questioning One. When a task is ambiguous, incomplete, or unclear, Vör asks the right clarifying questions before any work begins.",
69
+ "mode": "subagent",
70
+ "model": "minimax/MiniMax-M2.7",
71
+ "color": "#8b5cf6",
72
+ "permission": {
73
+ "read": "allow",
74
+ "list": "allow",
75
+ "question": "allow",
76
+ "hindsight_recall": "allow",
77
+ "hindsight_retain": "allow"
78
+ }
79
+ },
80
+ "frigg": {
81
+ "description": "Frigg — All-knowing Q&A agent. Read-only codebase questions and answers. Never edits, never writes, only answers.",
82
+ "mode": "primary",
83
+ "model": "minimax/MiniMax-M2.7",
84
+ "color": "#06b6d4",
85
+ "permission": {
86
+ "read": "allow",
87
+ "list": "allow",
88
+ "glob": "allow",
89
+ "grep": "allow",
90
+ "bash": "allow",
91
+ "webfetch": "allow",
92
+ "websearch": "allow",
93
+ "hindsight_recall": "allow",
94
+ "hindsight_retain": "allow",
95
+ "question": "allow"
96
+ }
97
+ },
98
+ "quick": {
99
+ "description": "Quick — Fast single-shot tasks. No delegation, no parallel streams. Use for small edits, mechanical changes, one-shot questions. Routes to no one.",
100
+ "mode": "primary",
101
+ "model": "minimax/MiniMax-M2.7",
102
+ "color": "#22d3ee",
103
+ "permission": {
104
+ "read": "allow",
105
+ "edit": "allow",
106
+ "write": "allow",
107
+ "bash": "allow",
108
+ "glob": "allow",
109
+ "grep": "allow",
110
+ "list": "allow",
111
+ "todowrite": "allow",
112
+ "webfetch": "allow",
113
+ "websearch": "allow",
114
+ "task": "deny"
115
+ }
116
+ },
117
+ "mimir": {
118
+ "description": "Mimir — Dedicated research and codebase exploration agent. Uses Semble as primary search tool. Deep codebase analysis, pattern discovery, and documentation research.",
119
+ "mode": "subagent",
120
+ "model": "minimax/MiniMax-M2.7",
121
+ "color": "#0ea5e9",
122
+ "permission": {
123
+ "read": "allow",
124
+ "write": "allow",
125
+ "edit": "allow",
126
+ "bash": "allow",
127
+ "glob": "allow",
128
+ "grep": "allow",
129
+ "list": "allow",
130
+ "webfetch": "allow",
131
+ "websearch": "allow",
132
+ "todowrite": "allow"
133
+ }
134
+ },
135
+ "heimdall": {
136
+ "description": "Heimdall — Simple, routine, and deterministic tasks using DeepSeek. Quick edits, mechanical work, file operations. The ever-watchful guardian.",
137
+ "mode": "subagent",
138
+ "model": "minimax/MiniMax-M2.7",
139
+ "color": "#10b981",
140
+ "permission": {
141
+ "read": "allow",
142
+ "edit": "allow",
143
+ "bash": "allow",
144
+ "glob": "allow",
145
+ "grep": "allow",
146
+ "list": "allow",
147
+ "todowrite": "allow",
148
+ "webfetch": "allow",
149
+ "websearch": "allow"
150
+ }
151
+ },
152
+ "hermod": {
153
+ "description": "Hermod — Git and GitHub operations specialist using MiniMax M2.7. Branching, commits, PRs, merge/rebase, conflict resolution, CI/CD, releases, gh CLI.",
154
+ "mode": "subagent",
155
+ "model": "minimax/MiniMax-M2.7",
156
+ "color": "#06b6d4",
157
+ "permission": {
158
+ "read": "allow",
159
+ "edit": "allow",
160
+ "bash": "allow",
161
+ "glob": "allow",
162
+ "grep": "allow",
163
+ "list": "allow",
164
+ "webfetch": "allow",
165
+ "websearch": "allow"
166
+ }
167
+ },
168
+ "thor": {
169
+ "description": "Thor — Handles medium-complexity tasks using MiniMax M2.7 from minimax.io. Strong and reliable, cheaper than Tyr but more capable than Heimdall.",
170
+ "mode": "subagent",
171
+ "model": "minimax/MiniMax-M2.7",
172
+ "color": "#a855f7",
173
+ "permission": {
174
+ "read": "allow",
175
+ "edit": "allow",
176
+ "bash": "allow",
177
+ "glob": "allow",
178
+ "grep": "allow",
179
+ "list": "allow",
180
+ "todowrite": "allow",
181
+ "webfetch": "allow",
182
+ "websearch": "allow"
183
+ }
184
+ },
185
+ "baldr": {
186
+ "description": "Baldr — UI/UX design system specialist. Creates DESIGN.md files using Google's design.md standard. Focuses on visual consistency, usability, accessibility, and design tokens.",
187
+ "mode": "subagent",
188
+ "model": "minimax/MiniMax-M2.7",
189
+ "color": "#ec4899",
190
+ "permission": {
191
+ "read": "allow",
192
+ "edit": "allow",
193
+ "bash": "allow",
194
+ "glob": "allow",
195
+ "grep": "allow",
196
+ "list": "allow",
197
+ "todowrite": "allow",
198
+ "webfetch": "allow",
199
+ "websearch": "allow"
200
+ }
201
+ },
202
+ "tyr": {
203
+ "description": "Tyr — Handles the most complex implementation, debugging, and architectural work using MiniMax M3 via minimax.io. Unmatched wisdom for the hardest problems.",
204
+ "mode": "subagent",
205
+ "model": "minimax/MiniMax-M3",
206
+ "color": "#f59e0b",
207
+ "permission": {
208
+ "read": "allow",
209
+ "edit": "allow",
210
+ "bash": "allow",
211
+ "glob": "allow",
212
+ "grep": "allow",
213
+ "list": "allow",
214
+ "todowrite": "allow",
215
+ "webfetch": "allow",
216
+ "websearch": "allow"
217
+ }
218
+ },
219
+ "vidarr": {
220
+ "description": "Vidarr — The ultimate fallback using GPT-5.5 via OpenAI ChatGPT subscription. For the hardest problems when debugging stalls or nothing else works. Use sparingly — highest cost.",
221
+ "mode": "subagent",
222
+ "model": "openai/gpt-5.5",
223
+ "color": "#dc2626",
224
+ "permission": {
225
+ "read": "allow",
226
+ "edit": "allow",
227
+ "bash": "allow",
228
+ "glob": "allow",
229
+ "grep": "allow",
230
+ "list": "allow",
231
+ "todowrite": "allow",
232
+ "webfetch": "allow",
233
+ "websearch": "allow"
234
+ }
235
+ },
236
+ "forseti": {
237
+ "description": "Forseti — Audits, criticizes, and corrects implementation plans before execution using MiniMax M3. No write permissions — review only.",
238
+ "mode": "subagent",
239
+ "model": "minimax/MiniMax-M3",
240
+ "color": "#ef4444",
241
+ "permission": {
242
+ "read": "allow",
243
+ "edit": "deny",
244
+ "bash": "allow",
245
+ "glob": "allow",
246
+ "grep": "allow",
247
+ "list": "allow",
248
+ "todowrite": "allow",
249
+ "question": "allow",
250
+ "webfetch": "allow",
251
+ "websearch": "allow"
252
+ }
253
+ },
254
+ "semble-search": {
255
+ "description": "Code search agent for exploring any codebase. Use for finding code by intent, locating implementations, understanding how something works.",
256
+ "mode": "subagent",
257
+ "permission": {
258
+ "bash": "allow",
259
+ "read": "allow"
260
+ }
261
+ }
262
+ },
263
+ "command": {
264
+ "audit": {
265
+ "description": "Run bizar audit to scan agent configuration for security issues.",
266
+ "agent": "forseti",
267
+ "template": "commands-bizar/audit.md"
268
+ },
269
+ "explain": {
270
+ "description": "Route to @frigg for read-only codebase Q&A. She will explore the code and answer without making any changes.",
271
+ "agent": "frigg",
272
+ "template": "commands-bizar/explain.md"
273
+ },
274
+ "init": {
275
+ "description": "Run bizar init to detect project stack, install relevant skills, and create .bizar/PROJECT.md.",
276
+ "agent": "heimdall",
277
+ "template": "commands-bizar/init.md"
278
+ },
279
+ "learn": {
280
+ "description": "Route to @heimdall. Extract patterns from the current session and append to .bizar/AGENTS_SELF_IMPROVEMENT.md.",
281
+ "agent": "heimdall",
282
+ "template": "commands-bizar/learn.md"
283
+ },
284
+ "plan": {
285
+ "description": "Open the Bizar visual plan canvas or manage existing plans.",
286
+ "agent": "odin",
287
+ "template": "commands-bizar/plan.md"
288
+ },
289
+ "pr-review": {
290
+ "description": "Route to @hermod for PR review mode. Launches @mimir (research) and @forseti (audit) in parallel, then posts the combined review as a PR comment.",
291
+ "agent": "hermod",
292
+ "template": "commands-bizar/pr-review.md"
293
+ },
294
+ "tailscale-serve": {
295
+ "description": "Authenticate Tailscale Serve and expose a local port on your tailnet.",
296
+ "agent": "heimdall",
297
+ "template": "commands-bizar/tailscale-serve.md"
298
+ },
299
+ "visual-plan": {
300
+ "description": "Toggle the Bizar visual plan canvas on or off, or check its current status.",
301
+ "agent": "odin",
302
+ "template": "commands-bizar/visual-plan.md"
303
+ },
304
+ "bizar": {
305
+ "description": "Bizar Plugin Menu — route to the right Bizar action based on the user's request.",
306
+ "agent": "odin",
307
+ "template": "commands-bizar/bizar.md\n\n$ARGUMENTS"
308
+ }
51
309
  }
52
310
  }
@@ -0,0 +1 @@
1
+ *,*:before,*:after{box-sizing:border-box}html,body,#root{margin:0;padding:0;height:100%}body{font-family:Inter var,Inter,system-ui,-apple-system,Segoe UI,Roboto,Helvetica Neue,sans-serif;font-feature-settings:"cv02","cv03","cv04","cv11";font-size:14px;line-height:1.5;background:var(--bg);color:var(--text);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}button,input,textarea,select{font-family:inherit;font-size:inherit;color:inherit}a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}code,pre,.mono{font-family:var(--mono)}::selection{background:var(--accent-bg);color:var(--accent-2)}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border);border-radius:6px;border:2px solid var(--bg)}::-webkit-scrollbar-thumb:hover{background:var(--border-strong)}:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:4px}:root{--bg: #0b0e14;--bg-elev: #12161f;--bg-elev-2: #1a1f2b;--bg-elev-3: #232a39;--border: #232a39;--border-strong: #2d3648;--text: #c9d1d9;--text-dim: #8b95a8;--text-strong: #f0f6fc;--accent: #8b5cf6;--accent-2: #a78bfa;--accent-3: #c4b5fd;--accent-bg: rgba(139, 92, 246, .12);--accent-border: rgba(139, 92, 246, .4);--success: #34d399;--warning: #fbbf24;--error: #f87171;--info: #60a5fa;--syntax-key: #79c0ff;--syntax-string: #a5d6ff;--syntax-number: #ffa657;--syntax-boolean: #ff7b72;--syntax-null: #ff7b72;--font-mono: "JetBrains Mono", "Fira Code", "SF Mono", "Cascadia Code", Consolas, monospace;--font-sans: "Inter var", "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-5: 20px;--space-6: 24px;--space-8: 32px;--space-10: 40px;--space-12: 48px;--radius-sm: 4px;--radius: 8px;--radius-md: 10px;--radius-lg: 12px;--radius-xl: 16px;--shadow-1: 0 1px 2px rgba(0, 0, 0, .4);--shadow-2: 0 4px 12px rgba(0, 0, 0, .45);--shadow-3: 0 12px 32px rgba(0, 0, 0, .55);--shadow-glow: 0 0 0 1px var(--accent-border), 0 8px 32px rgba(139, 92, 246, .15);--motion-fast: .12s;--motion-base: .18s;--motion-slow: .28s;--ease: cubic-bezier(.4, 0, .2, 1);color-scheme:dark}[data-theme=light]{--bg: #f7f8fa;--bg-elev: #ffffff;--bg-elev-2: #f0f3f8;--bg-elev-3: #e6ebf2;--border: #e2e8f0;--border-strong: #cbd5e1;--text: #1f2937;--text-dim: #64748b;--text-strong: #0f172a;--accent: #7c3aed;--accent-2: #6d28d9;--accent-3: #5b21b6;--accent-bg: rgba(124, 58, 237, .08);--accent-border: rgba(124, 58, 237, .3);--success: #059669;--warning: #d97706;--error: #dc2626;--info: #2563eb;--syntax-key: #0969da;--syntax-string: #0a3069;--syntax-number: #953800;--syntax-boolean: #cf222e;--syntax-null: #cf222e;--shadow-1: 0 1px 2px rgba(15, 23, 42, .06);--shadow-2: 0 4px 12px rgba(15, 23, 42, .08);--shadow-3: 0 12px 32px rgba(15, 23, 42, .12);color-scheme:light}.app{display:flex;flex-direction:column;height:100vh;background:radial-gradient(circle at 0% 0%,var(--accent-bg),transparent 50%),var(--bg);background-attachment:fixed}.content{flex:1;overflow:auto;padding:var(--space-6);position:relative}.view{max-width:1400px;margin:0 auto;display:flex;flex-direction:column;gap:var(--space-5);animation:view-in var(--motion-slow) var(--ease)}@keyframes view-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}.view-loading{display:flex;flex-direction:column;gap:var(--space-3);align-items:center;justify-content:center;min-height:60vh;color:var(--text-dim)}.loading{display:flex;flex-direction:column;gap:var(--space-3);align-items:center;justify-content:center;min-height:100vh;color:var(--text-dim)}.boot-error{max-width:560px;margin:var(--space-12) auto;padding:var(--space-6);background:var(--bg-elev);border:1px solid var(--error);border-radius:var(--radius-lg);box-shadow:var(--shadow-2)}.boot-error h2{margin:0 0 var(--space-3);color:var(--error)}.boot-error code{background:var(--bg);padding:2px 6px;border-radius:var(--radius-sm);font-size:12px}.topbar{display:flex;align-items:center;height:56px;flex-shrink:0;background:color-mix(in srgb,var(--bg-elev) 92%,transparent);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border-bottom:1px solid var(--border);padding:0 var(--space-5);gap:var(--space-5);position:sticky;top:0;z-index:10}.brand{display:flex;align-items:center;gap:var(--space-2);-webkit-user-select:none;user-select:none}.brand-logo{font-size:22px;line-height:1;filter:drop-shadow(0 0 12px rgba(139,92,246,.4))}.brand-title{font-weight:600;font-size:15px;letter-spacing:-.01em;color:var(--text-strong)}.brand-version{font-family:var(--font-mono);font-size:10px;color:var(--text-dim);padding:2px 7px;background:var(--bg-elev-2);border:1px solid var(--border);border-radius:999px;font-weight:500}.tabs{display:flex;gap:2px;flex:1;overflow-x:auto;scrollbar-width:none}.tabs::-webkit-scrollbar{display:none}.tab{display:inline-flex;align-items:center;gap:6px;background:transparent;border:1px solid transparent;color:var(--text-dim);padding:6px 12px;border-radius:999px;cursor:pointer;font-size:13px;font-weight:500;white-space:nowrap;transition:background var(--motion-base) var(--ease),color var(--motion-base) var(--ease),border-color var(--motion-base) var(--ease),transform var(--motion-fast) var(--ease)}.tab:hover{color:var(--text);background:var(--bg-elev-2)}.tab:active{transform:scale(.98)}.tab-active{color:var(--accent-3);background:var(--accent-bg);border-color:var(--accent-border)}.tab-icon{flex-shrink:0}.topbar-right{display:flex;align-items:center;gap:var(--space-3)}.ws-status{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-family:var(--font-mono);color:var(--text-dim);padding:4px 10px;border-radius:999px;background:var(--bg-elev-2);border:1px solid var(--border);text-transform:capitalize}.ws-dot{width:8px;height:8px;border-radius:50%;background:var(--text-dim);position:relative}.ws-connected .ws-dot{background:var(--success);box-shadow:0 0 0 3px #34d39926}.ws-connecting .ws-dot{background:var(--warning);animation:pulse 1.4s ease-in-out infinite}.ws-disconnected .ws-dot{background:var(--error)}@keyframes pulse{0%,to{opacity:1}50%{opacity:.4}}.view-header{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--space-4);flex-wrap:wrap}.view-header-text{display:flex;flex-direction:column;gap:4px;min-width:0}.view-title{display:inline-flex;align-items:center;gap:var(--space-2);font-size:22px;font-weight:700;letter-spacing:-.01em;color:var(--text-strong);margin:0}.view-subtitle{font-size:13px;color:var(--text-dim);margin:0}.view-actions{display:inline-flex;align-items:center;gap:var(--space-2);flex-wrap:wrap}.card{background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius-lg);padding:var(--space-4) var(--space-5);display:flex;flex-direction:column;gap:var(--space-3);transition:border-color var(--motion-base) var(--ease),transform var(--motion-base) var(--ease),box-shadow var(--motion-base) var(--ease)}.card-elevated{background:var(--bg-elev)}.card-outlined{background:transparent}.card-filled{background:var(--bg-elev-2);border-color:var(--border-strong)}.card-interactive{cursor:pointer}.card-interactive:hover{border-color:var(--border-strong);transform:translateY(-1px);box-shadow:var(--shadow-2)}.card-title{display:inline-flex;align-items:center;gap:6px;font-size:13px;font-weight:600;color:var(--text-strong);margin:0;letter-spacing:-.005em}.card-meta{font-size:12px;color:var(--text-dim)}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border-radius:var(--radius);cursor:pointer;font-weight:500;white-space:nowrap;transition:background var(--motion-fast) var(--ease),border-color var(--motion-fast) var(--ease),color var(--motion-fast) var(--ease),transform var(--motion-fast) var(--ease),box-shadow var(--motion-fast) var(--ease)}.btn:active:not(:disabled){transform:scale(.97)}.btn:disabled{opacity:.55;cursor:not-allowed}.btn-size-sm{padding:4px 10px;font-size:12px}.btn-size-md{padding:7px 14px;font-size:13px}.btn-size-lg{padding:10px 18px;font-size:14px}.btn-icon{padding:6px;width:30px;height:30px}.btn-primary{background:var(--accent);color:#fff;border:1px solid var(--accent)}.btn-primary:hover:not(:disabled){background:var(--accent-2);border-color:var(--accent-2)}.btn-accent{background:var(--accent-bg);color:var(--accent-2);border:1px solid var(--accent-border)}.btn-secondary{background:var(--bg-elev-2);color:var(--text);border:1px solid var(--border)}.btn-secondary:hover:not(:disabled){border-color:var(--border-strong);background:var(--bg-elev-3)}.btn-ghost{background:transparent;color:var(--text-dim);border:1px solid transparent}.btn-ghost:hover:not(:disabled){background:var(--bg-elev-2);color:var(--text)}.btn-danger{background:transparent;color:var(--error);border:1px solid var(--error)}.btn-danger:hover:not(:disabled){background:#f871711f}.btn-loading{pointer-events:none}.btn-spinner{width:12px;height:12px;border:2px solid currentColor;border-top-color:transparent;border-radius:50%;animation:spin .7s linear infinite}.hint-key{opacity:.7;margin-left:2px}.icon-btn{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:var(--radius-sm);background:transparent;color:var(--text-dim);border:1px solid transparent;cursor:pointer;transition:all var(--motion-fast) var(--ease)}.icon-btn:hover{background:var(--bg-elev-2);color:var(--text);border-color:var(--border)}.icon-btn-danger:hover{background:#f871711f;color:var(--error);border-color:var(--error)}.w-full{width:100%}.input,.textarea,.select{background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);padding:7px 12px;width:100%;font-size:13px;transition:border-color var(--motion-fast) var(--ease),box-shadow var(--motion-fast) var(--ease)}.textarea{resize:vertical;min-height:80px;font-family:var(--font-mono);font-size:12px;line-height:1.55}.select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'><path fill='%238b95a8' d='M2 4l4 4 4-4z'/></svg>");background-repeat:no-repeat;background-position:right 10px center;padding-right:30px}[data-theme=light] .select{background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'><path fill='%2364748b' d='M2 4l4 4 4-4z'/></svg>")}.select-sm{padding:4px 28px 4px 10px;font-size:12px;height:30px}.input:focus,.textarea:focus,.select:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-bg)}.textarea.invalid{border-color:var(--error)}.field{display:flex;flex-direction:column;gap:var(--space-2);margin-bottom:var(--space-4)}.field-label{font-size:12px;font-weight:500;color:var(--text-dim);letter-spacing:.01em}.field-help{font-size:11px;color:var(--text-dim)}.checkbox-row{display:flex;align-items:center;gap:var(--space-2);padding:6px 0;font-size:13px;cursor:pointer;color:var(--text)}.checkbox-row input{width:auto;cursor:pointer;accent-color:var(--accent)}.radio-row{display:flex;gap:var(--space-3)}.radio-label{display:inline-flex;align-items:center;gap:4px;font-size:13px;cursor:pointer}.radio-label input{width:auto;accent-color:var(--accent)}.capitalize{text-transform:capitalize}.tabular-nums{font-variant-numeric:tabular-nums}.text-error{color:var(--error)}.muted{color:var(--text-dim)}.text-sm{font-size:12px}.ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.badge{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:999px;font-size:10px;font-family:var(--font-mono);font-weight:500;background:var(--bg-elev-2);color:var(--text-dim);border:1px solid var(--border);text-transform:uppercase;letter-spacing:.04em}.badge-dot:before{content:"";display:inline-block;width:6px;height:6px;border-radius:50%;background:currentColor}.badge-success{color:var(--success);border-color:#34d39966}.badge-warning{color:var(--warning);border-color:#fbbf2466}.badge-error{color:var(--error);border-color:#f8717166}.badge-info{color:var(--info);border-color:#60a5fa66}.badge-accent{color:var(--accent-2);border-color:var(--accent-border)}.badge-neutral{color:var(--text-dim)}.tag{display:inline-flex;align-items:center;gap:4px;padding:1px 8px;border-radius:999px;font-size:10px;font-family:var(--font-mono);background:var(--accent-bg);color:var(--accent-2);border:1px solid var(--accent-border)}.tag-remove{background:transparent;border:none;color:inherit;cursor:pointer;padding:0 0 0 2px;font-size:12px;line-height:1;opacity:.7}.tag-remove:hover{opacity:1}.spinner{display:inline-block;border:2px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin .7s linear infinite}.spinner-sm{width:12px;height:12px}.spinner-md{width:18px;height:18px}.spinner-lg{width:32px;height:32px;border-width:3px}@keyframes spin{to{transform:rotate(360deg)}}.stat-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--space-4)}@media (max-width: 1100px){.stat-grid{grid-template-columns:repeat(2,1fr)}}@media (max-width: 600px){.stat-grid{grid-template-columns:1fr}}.stat-card{flex-direction:row;align-items:center;gap:var(--space-4);padding:var(--space-4) var(--space-5)}.stat-card-icon{width:40px;height:40px;border-radius:var(--radius);background:var(--accent-bg);color:var(--accent-2);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.stat-card-body{flex:1;display:flex;flex-direction:column;gap:2px;min-width:0}.stat-card-value{font-size:26px;font-weight:700;color:var(--text-strong);letter-spacing:-.02em;line-height:1.05}.stat-card-label{font-size:11px;color:var(--text-dim);text-transform:uppercase;letter-spacing:.06em}.stat-card-arrow{color:var(--text-dim);font-size:18px;transition:transform var(--motion-base) var(--ease)}.stat-card:hover .stat-card-arrow{transform:translate(2px);color:var(--accent-2)}.quick-actions{gap:var(--space-3)}.quick-actions-row{display:flex;flex-wrap:wrap;gap:var(--space-2)}.overview-cols{display:grid;grid-template-columns:1.4fr 1fr;gap:var(--space-4)}@media (max-width: 1000px){.overview-cols{grid-template-columns:1fr}}.activity-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px;max-height:360px;overflow-y:auto}.activity-item{display:grid;grid-template-columns:60px 130px 1fr;gap:var(--space-3);padding:8px var(--space-2);font-size:12px;border-radius:var(--radius-sm);font-family:var(--font-mono);border-left:2px solid var(--border);align-items:baseline}.activity-item:hover{background:var(--bg-elev-2)}.activity-ts{color:var(--text-dim);white-space:nowrap}.activity-kind{color:var(--accent-2)}.activity-msg{color:var(--text);word-break:break-word;overflow:hidden;text-overflow:ellipsis}.env-table,.about-table{display:grid;grid-template-columns:max-content 1fr;gap:6px var(--space-4);margin:0;font-size:13px}.env-table dt,.about-table dt{color:var(--text-dim);font-size:12px}.env-table dd,.about-table dd{margin:0;color:var(--text)}.overview-footer{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--text-dim);padding-top:var(--space-3);border-top:1px solid var(--border)}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--space-2);padding:var(--space-8) var(--space-4);text-align:center;color:var(--text-dim)}.empty-icon{color:var(--text-dim);opacity:.7}.empty-title{font-weight:600;color:var(--text);font-size:15px;margin-top:var(--space-2)}.empty-message{font-size:13px;max-width:420px}.empty-action{margin-top:var(--space-3)}.chat-container{display:flex;flex-direction:column;gap:var(--space-3);flex:1;min-height:0}.chat-list{flex:1;min-height:400px;max-height:calc(100vh - 280px);overflow-y:auto;padding:var(--space-3);background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius-lg);display:flex;flex-direction:column;gap:var(--space-3)}.chat-msg{background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-md);padding:var(--space-3) var(--space-4);max-width:85%;animation:msg-in var(--motion-base) var(--ease)}@keyframes msg-in{0%{opacity:0;transform:translateY(2px)}to{opacity:1;transform:none}}.chat-msg-user{align-self:flex-end;background:var(--accent-bg);border-color:var(--accent-border)}.chat-msg-assistant{align-self:flex-start}.chat-msg-system{align-self:center;max-width:100%;font-size:12px;color:var(--text-dim);border-style:dashed}.chat-msg-meta{display:flex;align-items:baseline;gap:var(--space-2);margin-bottom:6px;font-size:11px;font-family:var(--font-mono);color:var(--text-dim)}.chat-msg-role{text-transform:uppercase;font-weight:600;color:var(--accent-2);letter-spacing:.04em}.chat-msg-agent{color:var(--info)}.chat-msg-ts{margin-left:auto}.chat-msg-body{font-size:13px;line-height:1.6;word-break:break-word}.chat-msg-body p{margin:0 0 var(--space-2)}.chat-msg-body p:last-child{margin-bottom:0}.chat-msg-body code{font-family:var(--font-mono);background:var(--bg);padding:1px 6px;border-radius:var(--radius-sm);font-size:12px;border:1px solid var(--border)}.chat-msg-body pre{background:var(--bg);padding:var(--space-3);border-radius:var(--radius);overflow-x:auto;border:1px solid var(--border);font-size:12px}.chat-msg-body pre code{background:transparent;border:none;padding:0}.chat-msg-body a{color:var(--accent-2)}.chat-msg-body ul,.chat-msg-body ol{margin:0 0 var(--space-2);padding-left:var(--space-5)}.chat-msg-body blockquote{border-left:3px solid var(--accent-border);padding-left:var(--space-3);color:var(--text-dim);margin:0 0 var(--space-2)}.chat-composer{display:flex;flex-direction:column;gap:var(--space-2)}.chat-composer-row{display:flex;gap:var(--space-2);align-items:flex-end}.chat-composer-row .chat-input{flex:1;min-height:60px;max-height:200px}.chat-suggestions{display:flex;flex-direction:column;gap:2px;padding:var(--space-2);background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius);max-height:200px;overflow-y:auto}.chat-suggestion{display:flex;align-items:baseline;gap:var(--space-2);background:transparent;border:1px solid transparent;color:var(--text);padding:6px var(--space-2);border-radius:var(--radius-sm);text-align:left;cursor:pointer;font-size:12px;transition:background var(--motion-fast) var(--ease)}.chat-suggestion:hover{background:var(--bg-elev-2)}.chat-suggestion-desc{color:var(--text-dim);font-size:11px}.agent-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--space-4)}@media (max-width: 1100px){.agent-grid{grid-template-columns:repeat(2,1fr)}}@media (max-width: 700px){.agent-grid{grid-template-columns:1fr}}.agent-card{gap:var(--space-2)}.agent-card-head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-2)}.agent-card-name{font-weight:600;font-family:var(--font-mono);color:var(--accent-2);font-size:15px}.agent-card-desc{font-size:12px;color:var(--text-dim);line-height:1.5;min-height:36px}.agent-card-meta{display:flex;justify-content:space-between;font-size:11px;font-family:var(--font-mono);color:var(--text-dim)}.agent-card-actions{display:flex;gap:var(--space-2);margin-top:var(--space-2)}.invoke-form{display:flex;flex-direction:column;gap:var(--space-3)}.invoke-form-meta{font-size:11px}.invoke-form-desc{margin:0;font-size:13px;color:var(--text-dim)}.new-plan-form{display:flex;gap:var(--space-2);align-items:center;flex-wrap:wrap}.new-plan-form .input{flex:1;min-width:200px}.plans-layout{display:grid;grid-template-columns:340px 1fr;gap:var(--space-4);min-height:500px}@media (max-width: 1000px){.plans-layout{grid-template-columns:1fr}}.plans-list-col{display:flex;flex-direction:column;gap:var(--space-3);max-height:calc(100vh - 280px);overflow-y:auto;padding-right:4px}.plans-canvas-col{display:flex;flex-direction:column;background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius-lg);overflow:hidden;min-height:500px}.plan-card{gap:var(--space-2)}.plan-card-selected{border-color:var(--accent);box-shadow:var(--shadow-glow)}.plan-card-head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-2)}.plan-card-title{font-weight:600;color:var(--text-strong);font-size:14px}.plan-card-slug,.plan-card-meta{font-size:11px;color:var(--text-dim)}.plan-card-actions{display:flex;gap:var(--space-2)}.plan-canvas{display:flex;flex-direction:column;flex:1;min-height:0}.plan-canvas-header{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--border);background:var(--bg-elev-2)}.plan-canvas-title{font-weight:600;font-size:14px;color:var(--text-strong)}.plan-canvas-meta{font-size:11px;color:var(--text-dim);margin-top:2px;font-family:var(--font-mono)}.plan-canvas-actions{display:flex;gap:var(--space-2)}.plan-canvas-area{flex:1;position:relative;overflow:hidden;background:var(--bg);min-height:360px}.canvas-root{position:absolute;top:0;right:0;bottom:0;left:0;cursor:grab;overflow:hidden}.canvas-grid-bg{position:absolute;top:-2000px;right:-2000px;bottom:-2000px;left:-2000px;background-image:radial-gradient(circle,var(--border) 1px,transparent 1px);background-size:24px 24px;background-position:2000px 2000px;opacity:.5;pointer-events:none}.canvas-inner{position:absolute;top:0;left:0;transform-origin:0 0}.canvas-element{position:absolute;background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius);padding:var(--space-3);cursor:pointer;transition:border-color var(--motion-base) var(--ease),box-shadow var(--motion-base) var(--ease),transform var(--motion-fast) var(--ease);min-width:140px}.canvas-element:hover{border-color:var(--accent-border);transform:translateY(-1px);box-shadow:var(--shadow-2)}.canvas-element-selected{border-color:var(--accent);box-shadow:var(--shadow-glow)}.canvas-element-type{font-family:var(--font-mono);font-size:10px;color:var(--accent-2);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}.canvas-element-title{font-weight:600;font-size:13px;color:var(--text-strong);margin-bottom:4px}.canvas-element-content{font-size:11px;color:var(--text-dim);line-height:1.5;white-space:pre-wrap;word-break:break-word}.canvas-connections{position:absolute;pointer-events:none;overflow:visible}.canvas-hint{position:absolute;bottom:var(--space-3);right:var(--space-3);font-size:11px;color:var(--text-dim);background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius);padding:4px 8px;pointer-events:none}.plan-canvas-empty{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center;font-size:13px;color:var(--text-dim);text-align:center;padding:var(--space-6)}.plan-canvas-comments{border-top:1px solid var(--border);padding:var(--space-3) var(--space-4);background:var(--bg-elev-2);max-height:220px;overflow-y:auto}.plan-canvas-comments-title{display:flex;align-items:center;gap:6px;font-size:12px;font-weight:600;color:var(--text);margin:0 0 var(--space-2)}.comment-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--space-3)}.comment-item{padding:var(--space-2) var(--space-3);background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius)}.comment-head{display:flex;align-items:baseline;gap:var(--space-2);margin-bottom:4px}.comment-author{font-size:11px;font-weight:600;color:var(--accent-2)}.comment-time{font-size:11px}.comment-text{font-size:12px;color:var(--text);line-height:1.5}.comment-thread{margin-top:var(--space-2);padding-left:var(--space-3);border-left:2px solid var(--border);display:flex;flex-direction:column;gap:4px}.comment-reply{display:flex;gap:var(--space-2);font-size:11px;align-items:baseline}.comment-reply-text{color:var(--text-dim)}.project-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--space-4)}@media (max-width: 900px){.project-grid{grid-template-columns:1fr}}.project-card{gap:var(--space-2)}.project-card-active{border-color:var(--accent);box-shadow:var(--shadow-glow)}.project-card-head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-2)}.project-card-path{font-size:11px;color:var(--text-dim);word-break:break-all}.project-card-actions{display:flex;gap:var(--space-2);margin-top:var(--space-2)}.search-input{position:relative;display:inline-flex;align-items:center}.search-input>svg{position:absolute;left:10px;color:var(--text-dim)}.search-input .input{padding-left:32px;width:220px}.search-input>.icon-btn{position:absolute;right:4px;width:20px;height:20px}.kanban{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--space-4);align-items:start}@media (max-width: 900px){.kanban{grid-template-columns:1fr}}.kanban-column{background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius-lg);display:flex;flex-direction:column;max-height:calc(100vh - 240px);transition:border-color var(--motion-base) var(--ease),background var(--motion-base) var(--ease)}.kanban-column-drop{border-color:var(--accent);background:var(--accent-bg)}.kanban-col-header{display:flex;align-items:center;justify-content:space-between;padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--border);flex-shrink:0}.kanban-col-count{font-size:11px;color:var(--text-dim);background:var(--bg);padding:2px 8px;border-radius:999px;border:1px solid var(--border);font-family:var(--font-mono)}.kanban-col-body{flex:1;overflow-y:auto;padding:var(--space-3);display:flex;flex-direction:column;gap:var(--space-2);min-height:120px}.kanban-empty{text-align:center;color:var(--text-dim);font-size:12px;padding:var(--space-5) 0}.kanban-col-footer{padding:var(--space-2) var(--space-3);border-top:1px solid var(--border);flex-shrink:0}.task-card{background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:var(--space-3);cursor:pointer;border-left:3px solid transparent;transition:border-color var(--motion-base) var(--ease),box-shadow var(--motion-base) var(--ease),transform var(--motion-fast) var(--ease),background var(--motion-base) var(--ease)}.task-card:hover{border-color:var(--border-strong);transform:translateY(-1px);box-shadow:var(--shadow-2)}.task-card-focused{outline:2px solid var(--accent);outline-offset:-2px}.task-card.priority-high{border-left-color:var(--error)}.task-card.priority-low{border-left-color:var(--text-dim)}.task-card.priority-normal{border-left-color:var(--info)}.task-card-head{display:flex;align-items:flex-start;gap:var(--space-2);margin-bottom:6px}.priority-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;margin-top:6px}.task-card-title{font-weight:600;font-size:13px;color:var(--text-strong);flex:1;word-break:break-word;line-height:1.4}.task-card-desc{font-size:12px;color:var(--text-dim);line-height:1.5;margin-bottom:var(--space-2);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.task-card-tags{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:var(--space-2)}.task-card-footer{display:flex;align-items:center;justify-content:space-between;gap:var(--space-2)}.task-card-time{font-size:10px;font-family:var(--font-mono)}.task-card-actions{display:inline-flex;gap:2px}.task-form{display:flex;flex-direction:column;gap:var(--space-2)}.task-form-row{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)}.task-form-field{display:flex;flex-direction:column;gap:var(--space-2)}@keyframes task-move{0%{background:var(--accent-bg)}to{background:transparent}}.config-grid{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-4)}@media (max-width: 1000px){.config-grid{grid-template-columns:1fr}}.config-textarea{min-height:360px;font-family:var(--font-mono);font-size:12px;line-height:1.55;width:100%;white-space:pre}.config-textarea.invalid{border-color:var(--error)}.json-tree{font-family:var(--font-mono);font-size:12px;line-height:1.6;white-space:pre;overflow-x:auto;max-height:380px;overflow-y:auto;padding:var(--space-3);background:var(--bg);border:1px solid var(--border);border-radius:var(--radius)}.diff-card{gap:var(--space-2)}.diff-view{font-size:11px;line-height:1.5;max-height:300px;overflow-y:auto;padding:var(--space-3);background:var(--bg);border:1px solid var(--border);border-radius:var(--radius)}.diff-line{padding:0 var(--space-2);white-space:pre}.diff-line-removed{color:var(--error);background:#f8717114}.diff-line-added{color:var(--success);background:#34d39914}.settings-grid{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-4)}@media (max-width: 900px){.settings-grid{grid-template-columns:1fr}}.theme-row{display:flex;gap:var(--space-2);flex-wrap:wrap}.theme-card{flex:1;min-width:100px;display:flex;flex-direction:column;align-items:center;gap:var(--space-2);padding:var(--space-3);background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);cursor:pointer;color:var(--text-dim);transition:border-color var(--motion-base) var(--ease),background var(--motion-base) var(--ease),color var(--motion-base) var(--ease)}.theme-card:hover{border-color:var(--border-strong);color:var(--text)}.theme-card-active{border-color:var(--accent);background:var(--accent-bg);color:var(--accent-2)}.theme-card-label{font-size:12px;font-weight:500}.theme-card-swatch{width:100%;height:24px;border-radius:var(--radius-sm);border:1px solid var(--border)}.theme-card-swatch-dark{background:linear-gradient(135deg,#0b0e14,#1a1f2b)}.theme-card-swatch-light{background:linear-gradient(135deg,#f7f8fa,#e6ebf2)}.theme-card-swatch-system{background:linear-gradient(135deg,#0b0e14,#0b0e14 50%,#f7f8fa 50%,#f7f8fa)}.toast-stack{position:fixed;bottom:var(--space-5);right:var(--space-5);display:flex;flex-direction:column;gap:var(--space-2);z-index:1000;pointer-events:none;max-width:420px}.toast{display:inline-flex;align-items:center;gap:var(--space-2);background:var(--bg-elev);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:var(--radius);padding:10px 14px;box-shadow:var(--shadow-3);font-size:13px;pointer-events:auto;animation:toast-in var(--motion-slow) var(--ease);min-width:240px;color:var(--text)}.toast-icon{flex-shrink:0}.toast-message{flex:1;word-break:break-word}.toast-close{background:transparent;border:none;color:var(--text-dim);cursor:pointer;padding:2px;display:inline-flex}.toast-close:hover{color:var(--text)}.toast-success{border-left-color:var(--success)}.toast-success .toast-icon{color:var(--success)}.toast-error{border-left-color:var(--error)}.toast-error .toast-icon{color:var(--error)}.toast-warning{border-left-color:var(--warning)}.toast-warning .toast-icon{color:var(--warning)}.toast-info .toast-icon{color:var(--info)}@keyframes toast-in{0%{transform:translate(110%);opacity:0}to{transform:translate(0);opacity:1}}.modal-stack{position:fixed;top:0;right:0;bottom:0;left:0;z-index:999;pointer-events:none}.modal-backdrop{position:absolute;top:0;right:0;bottom:0;left:0;background:#0009;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;padding:var(--space-4);pointer-events:auto;animation:fade-in var(--motion-base) var(--ease)}@keyframes fade-in{0%{opacity:0}to{opacity:1}}.modal{background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius-lg);width:min(560px,100%);max-height:80vh;display:flex;flex-direction:column;box-shadow:var(--shadow-3);animation:modal-in var(--motion-slow) var(--ease);overflow:hidden}@keyframes modal-in{0%{opacity:0;transform:translateY(8px) scale(.98)}to{opacity:1;transform:none}}.modal-header{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:var(--space-4) var(--space-5);border-bottom:1px solid var(--border)}.modal-title{font-size:16px;font-weight:600;margin:0;color:var(--text-strong)}.modal-body{flex:1;overflow-y:auto;padding:var(--space-5);font-size:13px;line-height:1.55}.modal-footer{display:flex;align-items:center;justify-content:flex-end;gap:var(--space-2);padding:var(--space-3) var(--space-5);border-top:1px solid var(--border);background:var(--bg-elev-2)}.modal-footer-actions{display:flex;gap:var(--space-2);justify-content:flex-end;width:100%}.code-block{background:var(--bg);padding:var(--space-3);border-radius:var(--radius);border:1px solid var(--border);font-size:12px;margin:var(--space-2) 0 0;overflow-x:auto}kbd{display:inline-flex;align-items:center;padding:1px 6px;border-radius:var(--radius-sm);background:var(--bg-elev-2);border:1px solid var(--border);border-bottom-width:2px;font-family:var(--font-mono);font-size:10px;color:var(--text-dim)}@media (max-width: 800px){.topbar{padding:0 var(--space-3);gap:var(--space-3)}.brand-version,.tab-label{display:none}.content{padding:var(--space-4)}.ws-status .ws-label{display:none}}