@kl-c/matrixos 0.3.27 → 0.3.28

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/dist/cli/index.js CHANGED
@@ -2163,7 +2163,7 @@ var package_default;
2163
2163
  var init_package = __esm(() => {
2164
2164
  package_default = {
2165
2165
  name: "@kl-c/matrixos",
2166
- version: "0.3.27",
2166
+ version: "0.3.28",
2167
2167
  description: "MaTrixOS \u2014 Agentic OS for OpenCode. Personalizable, communicating, self-improving, resilient.",
2168
2168
  main: "./dist/index.js",
2169
2169
  types: "dist/index.d.ts",
@@ -186929,6 +186929,30 @@ function getDb() {
186929
186929
  return;
186930
186930
  }
186931
186931
  }
186932
+ function getDbWrite() {
186933
+ if (!fs21.existsSync(OPENCODE_DB_PATH))
186934
+ return;
186935
+ try {
186936
+ const db = new Database5(OPENCODE_DB_PATH);
186937
+ db.exec("PRAGMA journal_mode = WAL");
186938
+ return db;
186939
+ } catch {
186940
+ return;
186941
+ }
186942
+ }
186943
+ function ensureKanbanTable(db) {
186944
+ db.exec(`
186945
+ CREATE TABLE IF NOT EXISTS matrixos_kanban (
186946
+ id TEXT PRIMARY KEY,
186947
+ title TEXT NOT NULL,
186948
+ agent TEXT NOT NULL DEFAULT 'Unknown',
186949
+ status TEXT NOT NULL DEFAULT 'pending',
186950
+ priority TEXT NOT NULL DEFAULT 'medium',
186951
+ created_at INTEGER NOT NULL,
186952
+ updated_at INTEGER NOT NULL
186953
+ )
186954
+ `);
186955
+ }
186932
186956
  function midnightMs() {
186933
186957
  const d = new Date;
186934
186958
  d.setHours(0, 0, 0, 0);
@@ -187191,24 +187215,24 @@ function createDataProvider(config5) {
187191
187215
  },
187192
187216
  async getKanbanTasks() {
187193
187217
  const board = { columns: { pending: [], in_progress: [], completed: [], failed: [] } };
187194
- const db = getDb();
187218
+ const db = getDbWrite();
187195
187219
  if (!db)
187196
187220
  return board;
187197
187221
  try {
187198
- const rows = db.query("SELECT content, status, priority, time_created, time_updated FROM todo ORDER BY time_updated DESC").all();
187199
- let idx = 0;
187222
+ ensureKanbanTable(db);
187223
+ const rows = db.query("SELECT id, title, agent, status, priority, created_at, updated_at FROM matrixos_kanban ORDER BY updated_at DESC").all();
187200
187224
  for (const row of rows) {
187201
187225
  const status2 = row.status;
187202
187226
  if (!board.columns[status2])
187203
187227
  continue;
187204
187228
  const task2 = {
187205
- id: `todo_${idx++}`,
187206
- title: row.content,
187207
- agent: "Unknown",
187229
+ id: row.id,
187230
+ title: row.title,
187231
+ agent: row.agent || "Unknown",
187208
187232
  priority: row.priority ?? "medium",
187209
187233
  status: status2,
187210
- createdAt: new Date(row.time_created).toISOString(),
187211
- updatedAt: new Date(row.time_updated).toISOString()
187234
+ createdAt: new Date(row.created_at).toISOString(),
187235
+ updatedAt: new Date(row.updated_at).toISOString()
187212
187236
  };
187213
187237
  board.columns[status2].push(task2);
187214
187238
  }
@@ -187217,6 +187241,76 @@ function createDataProvider(config5) {
187217
187241
  }
187218
187242
  return board;
187219
187243
  },
187244
+ async createKanbanTask(input) {
187245
+ const db = getDbWrite();
187246
+ if (!db)
187247
+ return { ok: false, error: "database unavailable" };
187248
+ try {
187249
+ ensureKanbanTable(db);
187250
+ const id = `kb_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
187251
+ const now = Date.now();
187252
+ const status2 = input.status ?? "pending";
187253
+ const priority = input.priority ?? "medium";
187254
+ db.query("INSERT INTO matrixos_kanban (id, title, agent, status, priority, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)").run(id, input.title, input.agent ?? "Unknown", status2, priority, now, now);
187255
+ return { ok: true, id };
187256
+ } catch (e) {
187257
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
187258
+ } finally {
187259
+ db.close();
187260
+ }
187261
+ },
187262
+ async updateKanbanTask(id, input) {
187263
+ const db = getDbWrite();
187264
+ if (!db)
187265
+ return { ok: false, error: "database unavailable" };
187266
+ try {
187267
+ ensureKanbanTable(db);
187268
+ const existing = db.query("SELECT id FROM matrixos_kanban WHERE id = ?").get(id);
187269
+ if (!existing)
187270
+ return { ok: false, error: "task not found" };
187271
+ const sets = [];
187272
+ const params = [];
187273
+ if (input.title !== undefined) {
187274
+ sets.push("title = ?");
187275
+ params.push(input.title);
187276
+ }
187277
+ if (input.agent !== undefined) {
187278
+ sets.push("agent = ?");
187279
+ params.push(input.agent);
187280
+ }
187281
+ if (input.status !== undefined) {
187282
+ sets.push("status = ?");
187283
+ params.push(input.status);
187284
+ }
187285
+ if (input.priority !== undefined) {
187286
+ sets.push("priority = ?");
187287
+ params.push(input.priority);
187288
+ }
187289
+ sets.push("updated_at = ?");
187290
+ params.push(String(Date.now()));
187291
+ params.push(id);
187292
+ db.query(`UPDATE matrixos_kanban SET ${sets.join(", ")} WHERE id = ?`).run(...params);
187293
+ return { ok: true };
187294
+ } catch (e) {
187295
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
187296
+ } finally {
187297
+ db.close();
187298
+ }
187299
+ },
187300
+ async deleteKanbanTask(id) {
187301
+ const db = getDbWrite();
187302
+ if (!db)
187303
+ return { ok: false, error: "database unavailable" };
187304
+ try {
187305
+ ensureKanbanTable(db);
187306
+ db.query("DELETE FROM matrixos_kanban WHERE id = ?").run(id);
187307
+ return { ok: true };
187308
+ } catch (e) {
187309
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
187310
+ } finally {
187311
+ db.close();
187312
+ }
187313
+ },
187220
187314
  async getIncidents(limit = 50) {
187221
187315
  const lines = readJsonl("anti-loop.jsonl");
187222
187316
  if (lines.length === 0) {
@@ -187530,6 +187624,34 @@ function createDashboardServer(dataProvider, config5) {
187530
187624
  return Response.json(await dataProvider.getCost(), { headers: jsonHeaders });
187531
187625
  case "/api/kanban":
187532
187626
  return Response.json(await dataProvider.getKanbanTasks(), { headers: jsonHeaders });
187627
+ case "/api/kanban/task": {
187628
+ if (req.method === "POST") {
187629
+ const body = await req.json();
187630
+ if (!body.title || !body.title.trim()) {
187631
+ return Response.json({ ok: false, error: "title required" }, { status: 400, headers: jsonHeaders });
187632
+ }
187633
+ const result = await dataProvider.createKanbanTask(body);
187634
+ return Response.json(result, { status: result.ok ? 201 : 500, headers: jsonHeaders });
187635
+ }
187636
+ if (req.method === "PUT") {
187637
+ const body = await req.json();
187638
+ if (!body.id) {
187639
+ return Response.json({ ok: false, error: "id required" }, { status: 400, headers: jsonHeaders });
187640
+ }
187641
+ const result = await dataProvider.updateKanbanTask(body.id, body);
187642
+ return Response.json(result, { status: result.ok ? 200 : 500, headers: jsonHeaders });
187643
+ }
187644
+ if (req.method === "DELETE") {
187645
+ const url22 = new URL(req.url);
187646
+ const id = url22.searchParams.get("id");
187647
+ if (!id) {
187648
+ return Response.json({ ok: false, error: "id required" }, { status: 400, headers: jsonHeaders });
187649
+ }
187650
+ const result = await dataProvider.deleteKanbanTask(id);
187651
+ return Response.json(result, { status: result.ok ? 200 : 500, headers: jsonHeaders });
187652
+ }
187653
+ return new Response("Method Not Allowed", { status: 405, headers: corsHeaders });
187654
+ }
187533
187655
  case "/api/incidents": {
187534
187656
  const limit = parseInt(url3.searchParams.get("limit") ?? "50", 10);
187535
187657
  return Response.json(await dataProvider.getIncidents(limit), { headers: jsonHeaders });
@@ -2163,7 +2163,7 @@ var package_default;
2163
2163
  var init_package = __esm(() => {
2164
2164
  package_default = {
2165
2165
  name: "@kl-c/matrixos",
2166
- version: "0.3.27",
2166
+ version: "0.3.28",
2167
2167
  description: "MaTrixOS \u2014 Agentic OS for OpenCode. Personalizable, communicating, self-improving, resilient.",
2168
2168
  main: "./dist/index.js",
2169
2169
  types: "dist/index.d.ts",
@@ -186984,6 +186984,30 @@ function getDb() {
186984
186984
  return;
186985
186985
  }
186986
186986
  }
186987
+ function getDbWrite() {
186988
+ if (!fs21.existsSync(OPENCODE_DB_PATH))
186989
+ return;
186990
+ try {
186991
+ const db = new Database5(OPENCODE_DB_PATH);
186992
+ db.exec("PRAGMA journal_mode = WAL");
186993
+ return db;
186994
+ } catch {
186995
+ return;
186996
+ }
186997
+ }
186998
+ function ensureKanbanTable(db) {
186999
+ db.exec(`
187000
+ CREATE TABLE IF NOT EXISTS matrixos_kanban (
187001
+ id TEXT PRIMARY KEY,
187002
+ title TEXT NOT NULL,
187003
+ agent TEXT NOT NULL DEFAULT 'Unknown',
187004
+ status TEXT NOT NULL DEFAULT 'pending',
187005
+ priority TEXT NOT NULL DEFAULT 'medium',
187006
+ created_at INTEGER NOT NULL,
187007
+ updated_at INTEGER NOT NULL
187008
+ )
187009
+ `);
187010
+ }
186987
187011
  function midnightMs() {
186988
187012
  const d = new Date;
186989
187013
  d.setHours(0, 0, 0, 0);
@@ -187246,24 +187270,24 @@ function createDataProvider(config5) {
187246
187270
  },
187247
187271
  async getKanbanTasks() {
187248
187272
  const board = { columns: { pending: [], in_progress: [], completed: [], failed: [] } };
187249
- const db = getDb();
187273
+ const db = getDbWrite();
187250
187274
  if (!db)
187251
187275
  return board;
187252
187276
  try {
187253
- const rows = db.query("SELECT content, status, priority, time_created, time_updated FROM todo ORDER BY time_updated DESC").all();
187254
- let idx = 0;
187277
+ ensureKanbanTable(db);
187278
+ const rows = db.query("SELECT id, title, agent, status, priority, created_at, updated_at FROM matrixos_kanban ORDER BY updated_at DESC").all();
187255
187279
  for (const row of rows) {
187256
187280
  const status2 = row.status;
187257
187281
  if (!board.columns[status2])
187258
187282
  continue;
187259
187283
  const task2 = {
187260
- id: `todo_${idx++}`,
187261
- title: row.content,
187262
- agent: "Unknown",
187284
+ id: row.id,
187285
+ title: row.title,
187286
+ agent: row.agent || "Unknown",
187263
187287
  priority: row.priority ?? "medium",
187264
187288
  status: status2,
187265
- createdAt: new Date(row.time_created).toISOString(),
187266
- updatedAt: new Date(row.time_updated).toISOString()
187289
+ createdAt: new Date(row.created_at).toISOString(),
187290
+ updatedAt: new Date(row.updated_at).toISOString()
187267
187291
  };
187268
187292
  board.columns[status2].push(task2);
187269
187293
  }
@@ -187272,6 +187296,76 @@ function createDataProvider(config5) {
187272
187296
  }
187273
187297
  return board;
187274
187298
  },
187299
+ async createKanbanTask(input) {
187300
+ const db = getDbWrite();
187301
+ if (!db)
187302
+ return { ok: false, error: "database unavailable" };
187303
+ try {
187304
+ ensureKanbanTable(db);
187305
+ const id = `kb_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
187306
+ const now = Date.now();
187307
+ const status2 = input.status ?? "pending";
187308
+ const priority = input.priority ?? "medium";
187309
+ db.query("INSERT INTO matrixos_kanban (id, title, agent, status, priority, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)").run(id, input.title, input.agent ?? "Unknown", status2, priority, now, now);
187310
+ return { ok: true, id };
187311
+ } catch (e) {
187312
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
187313
+ } finally {
187314
+ db.close();
187315
+ }
187316
+ },
187317
+ async updateKanbanTask(id, input) {
187318
+ const db = getDbWrite();
187319
+ if (!db)
187320
+ return { ok: false, error: "database unavailable" };
187321
+ try {
187322
+ ensureKanbanTable(db);
187323
+ const existing = db.query("SELECT id FROM matrixos_kanban WHERE id = ?").get(id);
187324
+ if (!existing)
187325
+ return { ok: false, error: "task not found" };
187326
+ const sets = [];
187327
+ const params = [];
187328
+ if (input.title !== undefined) {
187329
+ sets.push("title = ?");
187330
+ params.push(input.title);
187331
+ }
187332
+ if (input.agent !== undefined) {
187333
+ sets.push("agent = ?");
187334
+ params.push(input.agent);
187335
+ }
187336
+ if (input.status !== undefined) {
187337
+ sets.push("status = ?");
187338
+ params.push(input.status);
187339
+ }
187340
+ if (input.priority !== undefined) {
187341
+ sets.push("priority = ?");
187342
+ params.push(input.priority);
187343
+ }
187344
+ sets.push("updated_at = ?");
187345
+ params.push(String(Date.now()));
187346
+ params.push(id);
187347
+ db.query(`UPDATE matrixos_kanban SET ${sets.join(", ")} WHERE id = ?`).run(...params);
187348
+ return { ok: true };
187349
+ } catch (e) {
187350
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
187351
+ } finally {
187352
+ db.close();
187353
+ }
187354
+ },
187355
+ async deleteKanbanTask(id) {
187356
+ const db = getDbWrite();
187357
+ if (!db)
187358
+ return { ok: false, error: "database unavailable" };
187359
+ try {
187360
+ ensureKanbanTable(db);
187361
+ db.query("DELETE FROM matrixos_kanban WHERE id = ?").run(id);
187362
+ return { ok: true };
187363
+ } catch (e) {
187364
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
187365
+ } finally {
187366
+ db.close();
187367
+ }
187368
+ },
187275
187369
  async getIncidents(limit = 50) {
187276
187370
  const lines = readJsonl("anti-loop.jsonl");
187277
187371
  if (lines.length === 0) {
@@ -187585,6 +187679,34 @@ function createDashboardServer(dataProvider, config5) {
187585
187679
  return Response.json(await dataProvider.getCost(), { headers: jsonHeaders });
187586
187680
  case "/api/kanban":
187587
187681
  return Response.json(await dataProvider.getKanbanTasks(), { headers: jsonHeaders });
187682
+ case "/api/kanban/task": {
187683
+ if (req.method === "POST") {
187684
+ const body = await req.json();
187685
+ if (!body.title || !body.title.trim()) {
187686
+ return Response.json({ ok: false, error: "title required" }, { status: 400, headers: jsonHeaders });
187687
+ }
187688
+ const result = await dataProvider.createKanbanTask(body);
187689
+ return Response.json(result, { status: result.ok ? 201 : 500, headers: jsonHeaders });
187690
+ }
187691
+ if (req.method === "PUT") {
187692
+ const body = await req.json();
187693
+ if (!body.id) {
187694
+ return Response.json({ ok: false, error: "id required" }, { status: 400, headers: jsonHeaders });
187695
+ }
187696
+ const result = await dataProvider.updateKanbanTask(body.id, body);
187697
+ return Response.json(result, { status: result.ok ? 200 : 500, headers: jsonHeaders });
187698
+ }
187699
+ if (req.method === "DELETE") {
187700
+ const url22 = new URL(req.url);
187701
+ const id = url22.searchParams.get("id");
187702
+ if (!id) {
187703
+ return Response.json({ ok: false, error: "id required" }, { status: 400, headers: jsonHeaders });
187704
+ }
187705
+ const result = await dataProvider.deleteKanbanTask(id);
187706
+ return Response.json(result, { status: result.ok ? 200 : 500, headers: jsonHeaders });
187707
+ }
187708
+ return new Response("Method Not Allowed", { status: 405, headers: corsHeaders });
187709
+ }
187588
187710
  case "/api/incidents": {
187589
187711
  const limit = parseInt(url3.searchParams.get("limit") ?? "50", 10);
187590
187712
  return Response.json(await dataProvider.getIncidents(limit), { headers: jsonHeaders });
@@ -13,6 +13,29 @@ export interface DataProvider {
13
13
  getSessions(limit?: number): Promise<SessionSummary[]>;
14
14
  getCost(): Promise<CostSummary>;
15
15
  getKanbanTasks(): Promise<KanbanBoard>;
16
+ createKanbanTask(input: {
17
+ title: string;
18
+ agent?: string;
19
+ status?: string;
20
+ priority?: string;
21
+ }): Promise<{
22
+ ok: boolean;
23
+ id?: string;
24
+ error?: string;
25
+ }>;
26
+ updateKanbanTask(id: string, input: {
27
+ title?: string;
28
+ agent?: string;
29
+ status?: string;
30
+ priority?: string;
31
+ }): Promise<{
32
+ ok: boolean;
33
+ error?: string;
34
+ }>;
35
+ deleteKanbanTask(id: string): Promise<{
36
+ ok: boolean;
37
+ error?: string;
38
+ }>;
16
39
  getIncidents(limit?: number): Promise<IncidentEntry[]>;
17
40
  getMetrics(range?: string): Promise<MetricsSnapshot[]>;
18
41
  getLogs(): LogEntry[];
@@ -225,5 +225,22 @@ body{font-family:var(--font-body);font-size:var(--text-base);color:var(--text-pr
225
225
  .agent-model-select:focus{outline:none;border-color:var(--accent-primary)}
226
226
  .agent-model-select option{background:var(--bg-elevated);color:var(--text-primary)}
227
227
 
228
+ .btn-primary{background:var(--accent-primary);color:#fff;border:none;border-radius:var(--radius-md);padding:8px 16px;font-size:var(--text-sm);font-weight:600;cursor:pointer;transition:background .15s}
229
+ .btn-primary:hover{background:var(--accent-hover)}
230
+ .btn-secondary{background:var(--bg-elevated);color:var(--text-primary);border:1px solid var(--border-default);border-radius:var(--radius-md);padding:8px 16px;font-size:var(--text-sm);cursor:pointer}
231
+ .kanban-del{position:absolute;top:6px;right:6px;background:transparent;border:none;color:var(--text-tertiary);font-size:18px;line-height:1;cursor:pointer;opacity:0;transition:opacity .15s}
232
+ .kanban-card{position:relative}
233
+ .kanban-card:hover .kanban-del{opacity:1}
234
+ .kanban-card[draggable=true]{cursor:grab}
235
+ .kanban-card[draggable=true]:active{cursor:grabbing}
236
+ .kanban-col.drag-over{outline:2px dashed var(--accent-primary);outline-offset:-4px}
237
+ .modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;z-index:1000}
238
+ .modal-box{background:var(--bg-card);border:1px solid var(--border-default);border-radius:var(--radius-xl);padding:24px;width:380px;max-width:90vw;display:flex;flex-direction:column;gap:14px}
239
+ .modal-box h3{font-size:var(--text-lg);font-weight:700;margin-bottom:4px}
240
+ .modal-box label{display:flex;flex-direction:column;gap:6px;font-size:var(--text-xs);color:var(--text-secondary);text-transform:uppercase;letter-spacing:.04em}
241
+ .modal-box .input{background:var(--bg-secondary);border:1px solid var(--border-default);border-radius:var(--radius-md);padding:10px 12px;color:var(--text-primary);font-size:var(--text-sm);font-family:var(--font-body)}
242
+ .modal-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:8px}
243
+
244
+
228
245
  @media(max-width:1024px){.kpi-grid,.grid-2{grid-template-columns:repeat(2,1fr)}.kanban-board{grid-template-columns:repeat(2,1fr)}.gauge-grid{grid-template-columns:repeat(2,1fr)}}
229
246
  @media(max-width:768px){.kpi-grid,.grid-2{grid-template-columns:1fr}.kanban-board{grid-template-columns:1fr}.agent-grid{grid-template-columns:1fr}}
@@ -1,2 +1,22 @@
1
- const API={health:()=>fetchJSON('/health'),agents:()=>fetchJSON('/agents'),models:()=>fetchJSON('/models'),sessions:(l)=>fetchJSON(`/sessions?limit=${l}`),cost:()=>fetchJSON('/cost'),kanban:()=>fetchJSON('/kanban'),incidents:(l)=>fetchJSON(`/incidents?limit=${l}`),metrics:()=>fetchJSON('/metrics'),config:()=>fetchJSON('/config'),memory:()=>fetchJSON('/memory'),skills:()=>fetchJSON('/skills'),gatewayToken:(type,token,passphrase)=>fetch('/api/gateway/token',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type,token,passphrase})}).then(r=>r.json())}
2
- async function fetchJSON(e){const r=await fetch(`/api${e}`);if(!r.ok)throw new Error(`API ${e}: ${r.status}`);return r.json()}
1
+ const API={
2
+ health:()=>fetchJSON('/health'),
3
+ agents:()=>fetchJSON('/agents'),
4
+ models:()=>fetchJSON('/models'),
5
+ sessions:(l)=>fetchJSON(`/sessions?limit=${l}`),
6
+ cost:()=>fetchJSON('/cost'),
7
+ kanban:()=>fetchJSON('/kanban'),
8
+ kanbanCreate:(body)=>fetchJSON('/kanban/task',{method:'POST',body}),
9
+ kanbanUpdate:(body)=>fetchJSON('/kanban/task',{method:'PUT',body}),
10
+ kanbanDelete:(id)=>fetchJSON(`/kanban/task?id=${id}`,{method:'DELETE'}),
11
+ incidents:(l)=>fetchJSON(`/incidents?limit=${l}`),
12
+ metrics:()=>fetchJSON('/metrics'),
13
+ config:()=>fetchJSON('/config'),
14
+ memory:()=>fetchJSON('/memory'),
15
+ skills:()=>fetchJSON('/skills'),
16
+ gatewayToken:(type,token,passphrase)=>fetch('/api/gateway/token',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type,token,passphrase})}).then(r=>r.json())
17
+ }
18
+ async function fetchJSON(e,opts){
19
+ const r=await fetch(`/api${e}`,opts)
20
+ if(!r.ok)throw new Error(`API ${e}: ${r.status}`)
21
+ return r.json()
22
+ }
@@ -183,20 +183,111 @@ async function loadCost(){
183
183
  }
184
184
 
185
185
  // ===== KANBAN =====
186
+ let kanbanData={columns:{}}
187
+ const KANBAN_STATUSES=['pending','in_progress','completed','failed']
188
+ const KANBAN_LABELS={pending:'Pending',in_progress:'In Progress',completed:'Completed',failed:'Failed'}
189
+
186
190
  async function loadKanban(){
187
191
  const board=document.getElementById('kanbanBoard')
188
192
  try{
189
- const data=await API.kanban()
190
- const labels={pending:'Pending',in_progress:'In Progress',completed:'Completed',failed:'Failed'}
191
- const colors={pending:'dim',in_progress:'info',completed:'success',failed:'danger'}
192
- const cols=data.columns||{}
193
- board.innerHTML=Object.entries(labels).map(([key,label])=>{
194
- const tasks=cols[key]||[]
195
- return `<div class="kanban-col"><div class="kanban-header"><span>${label}</span><span class="kanban-count">${tasks.length}</span></div><div class="kanban-body">${
196
- tasks.length?tasks.map(t=>`<div class="kanban-card"><div class="kanban-card-title">${t.title}</div><div class="kanban-card-meta"><span>${t.agent}</span><span class="prio ${t.priority}">${t.priority}</span></div></div>`).join(''):'<div style="padding:20px;text-align:center;color:var(--text-tertiary);font-size:var(--text-xs)">No tasks</div>'
197
- }</div></div>`
193
+ kanbanData=await API.kanban()
194
+ renderKanban()
195
+ }catch(e){board.innerHTML=`<div class="error" style="grid-column:1/-1"><p>Failed to load Kanban</p></div>`}
196
+ }
197
+
198
+ function renderKanban(){
199
+ const board=document.getElementById('kanbanBoard')
200
+ const cols=kanbanData.columns||{}
201
+ board.innerHTML=`
202
+ <div style="grid-column:1/-1;display:flex;justify-content:flex-end;margin-bottom:12px">
203
+ <button class="btn-primary" onclick="openKanbanModal()">+ New Task</button>
204
+ </div>`+
205
+ KANBAN_STATUSES.map(status=>{
206
+ const tasks=cols[status]||[]
207
+ return `<div class="kanban-col" data-status="${status}" ondragover="onKanbanDragOver(event)" ondrop="onKanbanDrop(event,'${status}')">
208
+ <div class="kanban-header"><span>${KANBAN_LABELS[status]}</span><span class="kanban-count">${tasks.length}</span></div>
209
+ <div class="kanban-body">${
210
+ tasks.length?tasks.map(t=>`<div class="kanban-card" draggable="true" ondragstart="onKanbanDragStart(event,'${t.id}')" ondblclick="openKanbanModal('${t.id}')">
211
+ <div class="kanban-card-title">${escapeHtml(t.title)}</div>
212
+ <div class="kanban-card-meta"><span>${escapeHtml(t.agent)}</span><span class="prio ${t.priority}">${t.priority}</span></div>
213
+ <button class="kanban-del" onclick="deleteKanbanTask('${t.id}')" title="Delete">×</button>
214
+ </div>`).join(''):'<div style="padding:20px;text-align:center;color:var(--text-tertiary);font-size:var(--text-xs)">No tasks</div>'
215
+ }</div>
216
+ </div>`
198
217
  }).join('')
199
- }catch(e){board.innerHTML=`<div class="error" style="grid-column:1/-1"><svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg><p>Failed to load Kanban</p></div>`}
218
+ }
219
+
220
+ function escapeHtml(s){return (s||'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]))}
221
+
222
+ let dragTaskId=null
223
+ function onKanbanDragStart(e,id){dragTaskId=id;e.dataTransfer.setData('text/plain',id)}
224
+ function onKanbanDragOver(e){e.preventDefault()}
225
+ async function onKanbanDrop(e,status){
226
+ e.preventDefault()
227
+ if(!dragTaskId)return
228
+ const id=dragTaskId;dragTaskId=null
229
+ const task=findKanbanTask(id)
230
+ if(!task||task.status===status)return
231
+ try{
232
+ await API.kanbanUpdate({id,status})
233
+ task.status=status
234
+ renderKanban()
235
+ }catch(err){alert('Erreur: '+err.message)}
236
+ }
237
+
238
+ function findKanbanTask(id){
239
+ for(const s of KANBAN_STATUSES){const t=(kanbanData.columns[s]||[]).find(x=>x.id===id);if(t)return t}
240
+ return null
241
+ }
242
+
243
+ async function deleteKanbanTask(id){
244
+ if(!confirm('Delete this task?'))return
245
+ try{
246
+ await API.kanbanDelete(id)
247
+ for(const s of KANBAN_STATUSES){kanbanData.columns[s]=(kanbanData.columns[s]||[]).filter(t=>t.id!==id)}
248
+ renderKanban()
249
+ }catch(err){alert('Erreur: '+err.message)}
250
+ }
251
+
252
+ function openKanbanModal(id){
253
+ const task=id?findKanbanTask(id):null
254
+ const modal=document.createElement('div')
255
+ modal.className='modal-overlay'
256
+ modal.innerHTML=`
257
+ <div class="modal-box">
258
+ <h3>${task?'Edit Task':'New Task'}</h3>
259
+ <label>Title<input id="kbTitle" class="input" value="${task?escapeHtml(task.title):''}"></label>
260
+ <label>Agent<input id="kbAgent" class="input" value="${task?escapeHtml(task.agent):''}" placeholder="e.g. Morpheus"></label>
261
+ <label>Priority<select id="kbPriority" class="input">
262
+ ${['low','medium','high','urgent'].map(p=>`<option value="${p}" ${task&&task.priority===p?'selected':''}>${p}</option>`).join('')}
263
+ </select></label>
264
+ <label>Status<select id="kbStatus" class="input">
265
+ ${KANBAN_STATUSES.map(s=>`<option value="${s}" ${task&&task.status===s?'selected':( !task&&s==='pending'?'selected':'')}>${KANBAN_LABELS[s]}</option>`).join('')}
266
+ </select></label>
267
+ <div class="modal-actions">
268
+ <button class="btn-secondary" onclick="this.closest('.modal-overlay').remove()">Cancel</button>
269
+ <button class="btn-primary" onclick="saveKanban('${id||''}')">${task?'Save':'Create'}</button>
270
+ </div>
271
+ </div>`
272
+ document.body.appendChild(modal)
273
+ }
274
+
275
+ async function saveKanban(id){
276
+ const title=document.getElementById('kbTitle').value.trim()
277
+ const agent=document.getElementById('kbAgent').value.trim()
278
+ const priority=document.getElementById('kbPriority').value
279
+ const status=document.getElementById('kbStatus').value
280
+ if(!title){alert('Title required');return}
281
+ try{
282
+ if(id){
283
+ await API.kanbanUpdate({id,title,agent,priority,status})
284
+ }else{
285
+ const res=await API.kanbanCreate({title,agent,priority,status})
286
+ if(!res.ok){throw new Error(res.error||'create failed')}
287
+ }
288
+ document.querySelector('.modal-overlay')?.remove()
289
+ await loadKanban()
290
+ }catch(err){alert('Erreur: '+err.message)}
200
291
  }
201
292
 
202
293
  // ===== LOGS =====
package/dist/index.js CHANGED
@@ -368019,7 +368019,7 @@ function getCachedVersion(options = {}) {
368019
368019
  // package.json
368020
368020
  var package_default = {
368021
368021
  name: "@kl-c/matrixos",
368022
- version: "0.3.27",
368022
+ version: "0.3.28",
368023
368023
  description: "MaTrixOS \u2014 Agentic OS for OpenCode. Personalizable, communicating, self-improving, resilient.",
368024
368024
  main: "./dist/index.js",
368025
368025
  types: "dist/index.d.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kl-c/matrixos",
3
- "version": "0.3.27",
3
+ "version": "0.3.28",
4
4
  "description": "MaTrixOS \u2014 Agentic OS for OpenCode. Personalizable, communicating, self-improving, resilient.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "dist/index.d.ts",