@kl-c/matrixos 0.3.26 → 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.26",
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",
@@ -167053,6 +167053,226 @@ function executeRgpdConsentCommand(args) {
167053
167053
  }
167054
167054
  }
167055
167055
 
167056
+ // packages/omo-opencode/src/cli/service.ts
167057
+ var exports_service = {};
167058
+ __export(exports_service, {
167059
+ serviceInstallCommand: () => serviceInstallCommand
167060
+ });
167061
+ import * as os15 from "os";
167062
+ import * as fs24 from "fs";
167063
+ import * as path29 from "path";
167064
+ import { execSync as execSync3 } from "child_process";
167065
+ function resolveMatrixosBin2() {
167066
+ try {
167067
+ const p2 = execSync3("command -v matrixos", { encoding: "utf-8" }).trim();
167068
+ if (p2)
167069
+ return p2;
167070
+ } catch {}
167071
+ for (const cand of ["/usr/local/bin/matrixos", "/usr/bin/matrixos", path29.join(os15.homedir(), ".bun/bin/matrixos")]) {
167072
+ if (fs24.existsSync(cand))
167073
+ return cand;
167074
+ }
167075
+ return "matrixos";
167076
+ }
167077
+ function resolveBunBin() {
167078
+ try {
167079
+ const p2 = execSync3("command -v bun", { encoding: "utf-8" }).trim();
167080
+ if (p2)
167081
+ return p2;
167082
+ } catch {}
167083
+ return "/usr/bin/bun";
167084
+ }
167085
+ function writeFileEnsureDir(filePath, content) {
167086
+ fs24.mkdirSync(path29.dirname(filePath), { recursive: true });
167087
+ fs24.writeFileSync(filePath, content, "utf-8");
167088
+ console.log(`[service] written: ${filePath}`);
167089
+ }
167090
+ function installSystemd(opts) {
167091
+ const bin = resolveMatrixosBin2();
167092
+ const bun = resolveBunBin();
167093
+ const scope = opts.user ? "--user" : "";
167094
+ const unitDir = opts.user ? path29.join(os15.homedir(), ".config", "systemd", "user") : "/etc/systemd/system";
167095
+ const envPath = `/usr/local/bin:/usr/bin:/bin:${path29.dirname(bun)}`;
167096
+ if (opts.dashboard !== false) {
167097
+ const unit = `[Unit]
167098
+ Description=MaTrixOS Dashboard
167099
+ After=network.target
167100
+
167101
+ [Service]
167102
+ Type=simple
167103
+ User=${os15.userInfo().username}
167104
+ WorkingDirectory=${os15.homedir()}
167105
+ Environment=PATH=${envPath}
167106
+ Environment=HOME=${os15.homedir()}
167107
+ ExecStart=${bin} dashboard --host 0.0.0.0 --daemon
167108
+ Restart=always
167109
+ RestartSec=5
167110
+
167111
+ [Install]
167112
+ WantedBy=${opts.user ? "default.target" : "multi-user.target"}
167113
+ `;
167114
+ writeFileEnsureDir(path29.join(unitDir, "matrixos-dashboard.service"), unit);
167115
+ }
167116
+ if (opts.gateway !== false) {
167117
+ const unit = `[Unit]
167118
+ Description=MaTrixOS Telegram Gateway
167119
+ After=network.target
167120
+
167121
+ [Service]
167122
+ Type=simple
167123
+ User=${os15.userInfo().username}
167124
+ WorkingDirectory=${os15.homedir()}
167125
+ Environment=PATH=${envPath}
167126
+ Environment=HOME=${os15.homedir()}
167127
+ ExecStart=${bin} gateway start --command "${bin} run"
167128
+ Restart=always
167129
+ RestartSec=5
167130
+
167131
+ [Install]
167132
+ WantedBy=${opts.user ? "default.target" : "multi-user.target"}
167133
+ `;
167134
+ writeFileEnsureDir(path29.join(unitDir, "matrixos-gateway.service"), unit);
167135
+ }
167136
+ console.log("[service] reloading systemd...");
167137
+ if (scope) {
167138
+ execSync3(`${scope} systemctl --user daemon-reload`, { stdio: "inherit" });
167139
+ if (opts.dashboard !== false)
167140
+ execSync3(`${scope} systemctl --user enable matrixos-dashboard.service`, { stdio: "inherit" });
167141
+ if (opts.gateway !== false)
167142
+ execSync3(`${scope} systemctl --user enable matrixos-gateway.service`, { stdio: "inherit" });
167143
+ if (opts.dashboard !== false)
167144
+ execSync3(`${scope} systemctl --user start matrixos-dashboard.service`, { stdio: "inherit" });
167145
+ if (opts.gateway !== false)
167146
+ execSync3(`${scope} systemctl --user start matrixos-gateway.service`, { stdio: "inherit" });
167147
+ } else {
167148
+ execSync3("systemctl daemon-reload", { stdio: "inherit" });
167149
+ if (opts.dashboard !== false)
167150
+ execSync3("systemctl enable matrixos-dashboard.service", { stdio: "inherit" });
167151
+ if (opts.gateway !== false)
167152
+ execSync3("systemctl enable matrixos-gateway.service", { stdio: "inherit" });
167153
+ if (opts.dashboard !== false)
167154
+ execSync3("systemctl start matrixos-dashboard.service", { stdio: "inherit" });
167155
+ if (opts.gateway !== false)
167156
+ execSync3("systemctl start matrixos-gateway.service", { stdio: "inherit" });
167157
+ }
167158
+ console.log("[service] systemd units installed and started.");
167159
+ }
167160
+ function installLaunchd(opts) {
167161
+ const bin = resolveMatrixosBin2();
167162
+ const bun = resolveBunBin();
167163
+ const launchDir = path29.join(os15.homedir(), "Library", "LaunchAgents");
167164
+ const envPath = `/usr/local/bin:/usr/bin:/bin:${path29.dirname(bun)}`;
167165
+ if (opts.dashboard !== false) {
167166
+ const label = "com.klc.matrixos.dashboard";
167167
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
167168
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
167169
+ <plist version="1.0">
167170
+ <dict>
167171
+ <key>Label</key>
167172
+ <string>${label}</string>
167173
+ <key>ProgramArguments</key>
167174
+ <array>
167175
+ <string>${bin}</string>
167176
+ <string>dashboard</string>
167177
+ <string>--host</string>
167178
+ <string>0.0.0.0</string>
167179
+ <string>--daemon</string>
167180
+ </array>
167181
+ <key>WorkingDirectory</key>
167182
+ <string>${os15.homedir()}</string>
167183
+ <key>EnvironmentVariables</key>
167184
+ <dict>
167185
+ <key>PATH</key>
167186
+ <string>${envPath}</string>
167187
+ <key>HOME</key>
167188
+ <string>${os15.homedir()}</string>
167189
+ </dict>
167190
+ <key>RunAtLoad</key>
167191
+ <true/>
167192
+ <key>KeepAlive</key>
167193
+ <true/>
167194
+ <key>StandardOutPath</key>
167195
+ <string>${os15.homedir()}/Library/Logs/matrixos-dashboard.log</string>
167196
+ <key>StandardErrorPath</key>
167197
+ <string>${os15.homedir()}/Library/Logs/matrixos-dashboard.log</string>
167198
+ </dict>
167199
+ </plist>
167200
+ `;
167201
+ writeFileEnsureDir(path29.join(launchDir, `${label}.plist`), plist);
167202
+ execSync3(`launchctl load ${path29.join(launchDir, `${label}.plist`)}`, { stdio: "inherit" });
167203
+ }
167204
+ if (opts.gateway !== false) {
167205
+ const label = "com.klc.matrixos.gateway";
167206
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
167207
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
167208
+ <plist version="1.0">
167209
+ <dict>
167210
+ <key>Label</key>
167211
+ <string>${label}</string>
167212
+ <key>ProgramArguments</key>
167213
+ <array>
167214
+ <string>${bin}</string>
167215
+ <string>gateway</string>
167216
+ <string>start</string>
167217
+ <string>--command</string>
167218
+ <string>${bin} run</string>
167219
+ </array>
167220
+ <key>WorkingDirectory</key>
167221
+ <string>${os15.homedir()}</string>
167222
+ <key>EnvironmentVariables</key>
167223
+ <dict>
167224
+ <key>PATH</key>
167225
+ <string>${envPath}</string>
167226
+ <key>HOME</key>
167227
+ <string>${os15.homedir()}</string>
167228
+ </dict>
167229
+ <key>RunAtLoad</key>
167230
+ <true/>
167231
+ <key>KeepAlive</key>
167232
+ <true/>
167233
+ <key>StandardOutPath</key>
167234
+ <string>${os15.homedir()}/Library/Logs/matrixos-gateway.log</string>
167235
+ <key>StandardErrorPath</key>
167236
+ <string>${os15.homedir()}/Library/Logs/matrixos-gateway.log</string>
167237
+ </dict>
167238
+ </plist>
167239
+ `;
167240
+ writeFileEnsureDir(path29.join(launchDir, `${label}.plist`), plist);
167241
+ execSync3(`launchctl load ${path29.join(launchDir, `${label}.plist`)}`, { stdio: "inherit" });
167242
+ }
167243
+ console.log("[service] launchd agents installed and loaded.");
167244
+ }
167245
+ function installWindowsNote() {
167246
+ console.log("[service] Windows: no auto-service installer yet.");
167247
+ console.log(" Use Task Scheduler to run `matrixos dashboard --host 0.0.0.0 --daemon`");
167248
+ console.log(' and `matrixos gateway start --command "matrixos run"` at logon.');
167249
+ console.log(" Or run them in the background via a startup script.");
167250
+ }
167251
+ async function serviceInstallCommand(options) {
167252
+ const platform2 = process.platform;
167253
+ console.log(`[service] detected platform: ${platform2}`);
167254
+ if (platform2 === "linux") {
167255
+ if (!fs24.existsSync("/run/systemd/system") && !fs24.existsSync("/etc/systemd/system")) {
167256
+ console.error("[service] systemd not detected \u2014 cannot install service automatically.");
167257
+ console.error(" Start manually: matrixos dashboard --host 0.0.0.0 --daemon");
167258
+ return 1;
167259
+ }
167260
+ installSystemd(options);
167261
+ return 0;
167262
+ }
167263
+ if (platform2 === "darwin") {
167264
+ installLaunchd(options);
167265
+ return 0;
167266
+ }
167267
+ if (platform2 === "win32") {
167268
+ installWindowsNote();
167269
+ return 0;
167270
+ }
167271
+ console.error(`[service] unsupported platform: ${platform2}`);
167272
+ return 1;
167273
+ }
167274
+ var init_service = () => {};
167275
+
167056
167276
  // node_modules/.bun/commander@14.0.3/node_modules/commander/esm.mjs
167057
167277
  var import__ = __toESM(require_commander(), 1);
167058
167278
  var {
@@ -186709,6 +186929,30 @@ function getDb() {
186709
186929
  return;
186710
186930
  }
186711
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
+ }
186712
186956
  function midnightMs() {
186713
186957
  const d = new Date;
186714
186958
  d.setHours(0, 0, 0, 0);
@@ -186971,24 +187215,24 @@ function createDataProvider(config5) {
186971
187215
  },
186972
187216
  async getKanbanTasks() {
186973
187217
  const board = { columns: { pending: [], in_progress: [], completed: [], failed: [] } };
186974
- const db = getDb();
187218
+ const db = getDbWrite();
186975
187219
  if (!db)
186976
187220
  return board;
186977
187221
  try {
186978
- const rows = db.query("SELECT content, status, priority, time_created, time_updated FROM todo ORDER BY time_updated DESC").all();
186979
- 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();
186980
187224
  for (const row of rows) {
186981
187225
  const status2 = row.status;
186982
187226
  if (!board.columns[status2])
186983
187227
  continue;
186984
187228
  const task2 = {
186985
- id: `todo_${idx++}`,
186986
- title: row.content,
186987
- agent: "Unknown",
187229
+ id: row.id,
187230
+ title: row.title,
187231
+ agent: row.agent || "Unknown",
186988
187232
  priority: row.priority ?? "medium",
186989
187233
  status: status2,
186990
- createdAt: new Date(row.time_created).toISOString(),
186991
- updatedAt: new Date(row.time_updated).toISOString()
187234
+ createdAt: new Date(row.created_at).toISOString(),
187235
+ updatedAt: new Date(row.updated_at).toISOString()
186992
187236
  };
186993
187237
  board.columns[status2].push(task2);
186994
187238
  }
@@ -186997,6 +187241,76 @@ function createDataProvider(config5) {
186997
187241
  }
186998
187242
  return board;
186999
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
+ },
187000
187314
  async getIncidents(limit = 50) {
187001
187315
  const lines = readJsonl("anti-loop.jsonl");
187002
187316
  if (lines.length === 0) {
@@ -187310,6 +187624,34 @@ function createDashboardServer(dataProvider, config5) {
187310
187624
  return Response.json(await dataProvider.getCost(), { headers: jsonHeaders });
187311
187625
  case "/api/kanban":
187312
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
+ }
187313
187655
  case "/api/incidents": {
187314
187656
  const limit = parseInt(url3.searchParams.get("limit") ?? "50", 10);
187315
187657
  return Response.json(await dataProvider.getIncidents(limit), { headers: jsonHeaders });
@@ -188224,6 +188566,23 @@ Examples:
188224
188566
  process.stdout.write(result.stdout);
188225
188567
  process.exit(result.exitCode);
188226
188568
  });
188569
+ program2.command("service").description("Install MaTrixOS as a persistent OS service (dashboard + gateway)").argument("<action>", "install | uninstall").option("--no-dashboard", "Skip dashboard service").option("--no-gateway", "Skip gateway service").option("--user", "Install as current user (systemd --user / launchd user agent)").action(async (action, options) => {
188570
+ if (action !== "install" && action !== "uninstall") {
188571
+ console.error(`[service] unknown action: ${action}. Use 'install' or 'uninstall'.`);
188572
+ process.exit(1);
188573
+ }
188574
+ if (action === "uninstall") {
188575
+ console.log("[service] uninstall not yet implemented \u2014 remove units manually (systemctl disable / launchctl unload).");
188576
+ process.exit(0);
188577
+ }
188578
+ const { serviceInstallCommand: serviceInstallCommand2 } = await Promise.resolve().then(() => (init_service(), exports_service));
188579
+ const exitCode = await serviceInstallCommand2({
188580
+ dashboard: options.dashboard ?? true,
188581
+ gateway: options.gateway ?? true,
188582
+ user: options.user ?? false
188583
+ });
188584
+ process.exit(exitCode);
188585
+ });
188227
188586
 
188228
188587
  // packages/omo-opencode/src/cli/index.ts
188229
188588
  runCli();
@@ -0,0 +1,6 @@
1
+ export interface ServiceInstallOptions {
2
+ readonly dashboard?: boolean;
3
+ readonly gateway?: boolean;
4
+ readonly user?: boolean;
5
+ }
6
+ export declare function serviceInstallCommand(options: ServiceInstallOptions): Promise<number>;
@@ -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.26",
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",
@@ -167108,6 +167108,226 @@ function executeRgpdConsentCommand(args) {
167108
167108
  }
167109
167109
  }
167110
167110
 
167111
+ // packages/omo-opencode/src/cli/service.ts
167112
+ var exports_service = {};
167113
+ __export(exports_service, {
167114
+ serviceInstallCommand: () => serviceInstallCommand
167115
+ });
167116
+ import * as os15 from "os";
167117
+ import * as fs24 from "fs";
167118
+ import * as path29 from "path";
167119
+ import { execSync as execSync3 } from "child_process";
167120
+ function resolveMatrixosBin2() {
167121
+ try {
167122
+ const p2 = execSync3("command -v matrixos", { encoding: "utf-8" }).trim();
167123
+ if (p2)
167124
+ return p2;
167125
+ } catch {}
167126
+ for (const cand of ["/usr/local/bin/matrixos", "/usr/bin/matrixos", path29.join(os15.homedir(), ".bun/bin/matrixos")]) {
167127
+ if (fs24.existsSync(cand))
167128
+ return cand;
167129
+ }
167130
+ return "matrixos";
167131
+ }
167132
+ function resolveBunBin() {
167133
+ try {
167134
+ const p2 = execSync3("command -v bun", { encoding: "utf-8" }).trim();
167135
+ if (p2)
167136
+ return p2;
167137
+ } catch {}
167138
+ return "/usr/bin/bun";
167139
+ }
167140
+ function writeFileEnsureDir(filePath, content) {
167141
+ fs24.mkdirSync(path29.dirname(filePath), { recursive: true });
167142
+ fs24.writeFileSync(filePath, content, "utf-8");
167143
+ console.log(`[service] written: ${filePath}`);
167144
+ }
167145
+ function installSystemd(opts) {
167146
+ const bin = resolveMatrixosBin2();
167147
+ const bun = resolveBunBin();
167148
+ const scope = opts.user ? "--user" : "";
167149
+ const unitDir = opts.user ? path29.join(os15.homedir(), ".config", "systemd", "user") : "/etc/systemd/system";
167150
+ const envPath = `/usr/local/bin:/usr/bin:/bin:${path29.dirname(bun)}`;
167151
+ if (opts.dashboard !== false) {
167152
+ const unit = `[Unit]
167153
+ Description=MaTrixOS Dashboard
167154
+ After=network.target
167155
+
167156
+ [Service]
167157
+ Type=simple
167158
+ User=${os15.userInfo().username}
167159
+ WorkingDirectory=${os15.homedir()}
167160
+ Environment=PATH=${envPath}
167161
+ Environment=HOME=${os15.homedir()}
167162
+ ExecStart=${bin} dashboard --host 0.0.0.0 --daemon
167163
+ Restart=always
167164
+ RestartSec=5
167165
+
167166
+ [Install]
167167
+ WantedBy=${opts.user ? "default.target" : "multi-user.target"}
167168
+ `;
167169
+ writeFileEnsureDir(path29.join(unitDir, "matrixos-dashboard.service"), unit);
167170
+ }
167171
+ if (opts.gateway !== false) {
167172
+ const unit = `[Unit]
167173
+ Description=MaTrixOS Telegram Gateway
167174
+ After=network.target
167175
+
167176
+ [Service]
167177
+ Type=simple
167178
+ User=${os15.userInfo().username}
167179
+ WorkingDirectory=${os15.homedir()}
167180
+ Environment=PATH=${envPath}
167181
+ Environment=HOME=${os15.homedir()}
167182
+ ExecStart=${bin} gateway start --command "${bin} run"
167183
+ Restart=always
167184
+ RestartSec=5
167185
+
167186
+ [Install]
167187
+ WantedBy=${opts.user ? "default.target" : "multi-user.target"}
167188
+ `;
167189
+ writeFileEnsureDir(path29.join(unitDir, "matrixos-gateway.service"), unit);
167190
+ }
167191
+ console.log("[service] reloading systemd...");
167192
+ if (scope) {
167193
+ execSync3(`${scope} systemctl --user daemon-reload`, { stdio: "inherit" });
167194
+ if (opts.dashboard !== false)
167195
+ execSync3(`${scope} systemctl --user enable matrixos-dashboard.service`, { stdio: "inherit" });
167196
+ if (opts.gateway !== false)
167197
+ execSync3(`${scope} systemctl --user enable matrixos-gateway.service`, { stdio: "inherit" });
167198
+ if (opts.dashboard !== false)
167199
+ execSync3(`${scope} systemctl --user start matrixos-dashboard.service`, { stdio: "inherit" });
167200
+ if (opts.gateway !== false)
167201
+ execSync3(`${scope} systemctl --user start matrixos-gateway.service`, { stdio: "inherit" });
167202
+ } else {
167203
+ execSync3("systemctl daemon-reload", { stdio: "inherit" });
167204
+ if (opts.dashboard !== false)
167205
+ execSync3("systemctl enable matrixos-dashboard.service", { stdio: "inherit" });
167206
+ if (opts.gateway !== false)
167207
+ execSync3("systemctl enable matrixos-gateway.service", { stdio: "inherit" });
167208
+ if (opts.dashboard !== false)
167209
+ execSync3("systemctl start matrixos-dashboard.service", { stdio: "inherit" });
167210
+ if (opts.gateway !== false)
167211
+ execSync3("systemctl start matrixos-gateway.service", { stdio: "inherit" });
167212
+ }
167213
+ console.log("[service] systemd units installed and started.");
167214
+ }
167215
+ function installLaunchd(opts) {
167216
+ const bin = resolveMatrixosBin2();
167217
+ const bun = resolveBunBin();
167218
+ const launchDir = path29.join(os15.homedir(), "Library", "LaunchAgents");
167219
+ const envPath = `/usr/local/bin:/usr/bin:/bin:${path29.dirname(bun)}`;
167220
+ if (opts.dashboard !== false) {
167221
+ const label = "com.klc.matrixos.dashboard";
167222
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
167223
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
167224
+ <plist version="1.0">
167225
+ <dict>
167226
+ <key>Label</key>
167227
+ <string>${label}</string>
167228
+ <key>ProgramArguments</key>
167229
+ <array>
167230
+ <string>${bin}</string>
167231
+ <string>dashboard</string>
167232
+ <string>--host</string>
167233
+ <string>0.0.0.0</string>
167234
+ <string>--daemon</string>
167235
+ </array>
167236
+ <key>WorkingDirectory</key>
167237
+ <string>${os15.homedir()}</string>
167238
+ <key>EnvironmentVariables</key>
167239
+ <dict>
167240
+ <key>PATH</key>
167241
+ <string>${envPath}</string>
167242
+ <key>HOME</key>
167243
+ <string>${os15.homedir()}</string>
167244
+ </dict>
167245
+ <key>RunAtLoad</key>
167246
+ <true/>
167247
+ <key>KeepAlive</key>
167248
+ <true/>
167249
+ <key>StandardOutPath</key>
167250
+ <string>${os15.homedir()}/Library/Logs/matrixos-dashboard.log</string>
167251
+ <key>StandardErrorPath</key>
167252
+ <string>${os15.homedir()}/Library/Logs/matrixos-dashboard.log</string>
167253
+ </dict>
167254
+ </plist>
167255
+ `;
167256
+ writeFileEnsureDir(path29.join(launchDir, `${label}.plist`), plist);
167257
+ execSync3(`launchctl load ${path29.join(launchDir, `${label}.plist`)}`, { stdio: "inherit" });
167258
+ }
167259
+ if (opts.gateway !== false) {
167260
+ const label = "com.klc.matrixos.gateway";
167261
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
167262
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
167263
+ <plist version="1.0">
167264
+ <dict>
167265
+ <key>Label</key>
167266
+ <string>${label}</string>
167267
+ <key>ProgramArguments</key>
167268
+ <array>
167269
+ <string>${bin}</string>
167270
+ <string>gateway</string>
167271
+ <string>start</string>
167272
+ <string>--command</string>
167273
+ <string>${bin} run</string>
167274
+ </array>
167275
+ <key>WorkingDirectory</key>
167276
+ <string>${os15.homedir()}</string>
167277
+ <key>EnvironmentVariables</key>
167278
+ <dict>
167279
+ <key>PATH</key>
167280
+ <string>${envPath}</string>
167281
+ <key>HOME</key>
167282
+ <string>${os15.homedir()}</string>
167283
+ </dict>
167284
+ <key>RunAtLoad</key>
167285
+ <true/>
167286
+ <key>KeepAlive</key>
167287
+ <true/>
167288
+ <key>StandardOutPath</key>
167289
+ <string>${os15.homedir()}/Library/Logs/matrixos-gateway.log</string>
167290
+ <key>StandardErrorPath</key>
167291
+ <string>${os15.homedir()}/Library/Logs/matrixos-gateway.log</string>
167292
+ </dict>
167293
+ </plist>
167294
+ `;
167295
+ writeFileEnsureDir(path29.join(launchDir, `${label}.plist`), plist);
167296
+ execSync3(`launchctl load ${path29.join(launchDir, `${label}.plist`)}`, { stdio: "inherit" });
167297
+ }
167298
+ console.log("[service] launchd agents installed and loaded.");
167299
+ }
167300
+ function installWindowsNote() {
167301
+ console.log("[service] Windows: no auto-service installer yet.");
167302
+ console.log(" Use Task Scheduler to run `matrixos dashboard --host 0.0.0.0 --daemon`");
167303
+ console.log(' and `matrixos gateway start --command "matrixos run"` at logon.');
167304
+ console.log(" Or run them in the background via a startup script.");
167305
+ }
167306
+ async function serviceInstallCommand(options) {
167307
+ const platform2 = process.platform;
167308
+ console.log(`[service] detected platform: ${platform2}`);
167309
+ if (platform2 === "linux") {
167310
+ if (!fs24.existsSync("/run/systemd/system") && !fs24.existsSync("/etc/systemd/system")) {
167311
+ console.error("[service] systemd not detected \u2014 cannot install service automatically.");
167312
+ console.error(" Start manually: matrixos dashboard --host 0.0.0.0 --daemon");
167313
+ return 1;
167314
+ }
167315
+ installSystemd(options);
167316
+ return 0;
167317
+ }
167318
+ if (platform2 === "darwin") {
167319
+ installLaunchd(options);
167320
+ return 0;
167321
+ }
167322
+ if (platform2 === "win32") {
167323
+ installWindowsNote();
167324
+ return 0;
167325
+ }
167326
+ console.error(`[service] unsupported platform: ${platform2}`);
167327
+ return 1;
167328
+ }
167329
+ var init_service = () => {};
167330
+
167111
167331
  // node_modules/.bun/commander@14.0.3/node_modules/commander/esm.mjs
167112
167332
  var import__ = __toESM(require_commander(), 1);
167113
167333
  var {
@@ -186764,6 +186984,30 @@ function getDb() {
186764
186984
  return;
186765
186985
  }
186766
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
+ }
186767
187011
  function midnightMs() {
186768
187012
  const d = new Date;
186769
187013
  d.setHours(0, 0, 0, 0);
@@ -187026,24 +187270,24 @@ function createDataProvider(config5) {
187026
187270
  },
187027
187271
  async getKanbanTasks() {
187028
187272
  const board = { columns: { pending: [], in_progress: [], completed: [], failed: [] } };
187029
- const db = getDb();
187273
+ const db = getDbWrite();
187030
187274
  if (!db)
187031
187275
  return board;
187032
187276
  try {
187033
- const rows = db.query("SELECT content, status, priority, time_created, time_updated FROM todo ORDER BY time_updated DESC").all();
187034
- 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();
187035
187279
  for (const row of rows) {
187036
187280
  const status2 = row.status;
187037
187281
  if (!board.columns[status2])
187038
187282
  continue;
187039
187283
  const task2 = {
187040
- id: `todo_${idx++}`,
187041
- title: row.content,
187042
- agent: "Unknown",
187284
+ id: row.id,
187285
+ title: row.title,
187286
+ agent: row.agent || "Unknown",
187043
187287
  priority: row.priority ?? "medium",
187044
187288
  status: status2,
187045
- createdAt: new Date(row.time_created).toISOString(),
187046
- updatedAt: new Date(row.time_updated).toISOString()
187289
+ createdAt: new Date(row.created_at).toISOString(),
187290
+ updatedAt: new Date(row.updated_at).toISOString()
187047
187291
  };
187048
187292
  board.columns[status2].push(task2);
187049
187293
  }
@@ -187052,6 +187296,76 @@ function createDataProvider(config5) {
187052
187296
  }
187053
187297
  return board;
187054
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
+ },
187055
187369
  async getIncidents(limit = 50) {
187056
187370
  const lines = readJsonl("anti-loop.jsonl");
187057
187371
  if (lines.length === 0) {
@@ -187365,6 +187679,34 @@ function createDashboardServer(dataProvider, config5) {
187365
187679
  return Response.json(await dataProvider.getCost(), { headers: jsonHeaders });
187366
187680
  case "/api/kanban":
187367
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
+ }
187368
187710
  case "/api/incidents": {
187369
187711
  const limit = parseInt(url3.searchParams.get("limit") ?? "50", 10);
187370
187712
  return Response.json(await dataProvider.getIncidents(limit), { headers: jsonHeaders });
@@ -188279,6 +188621,23 @@ Examples:
188279
188621
  process.stdout.write(result.stdout);
188280
188622
  process.exit(result.exitCode);
188281
188623
  });
188624
+ program2.command("service").description("Install MaTrixOS as a persistent OS service (dashboard + gateway)").argument("<action>", "install | uninstall").option("--no-dashboard", "Skip dashboard service").option("--no-gateway", "Skip gateway service").option("--user", "Install as current user (systemd --user / launchd user agent)").action(async (action, options) => {
188625
+ if (action !== "install" && action !== "uninstall") {
188626
+ console.error(`[service] unknown action: ${action}. Use 'install' or 'uninstall'.`);
188627
+ process.exit(1);
188628
+ }
188629
+ if (action === "uninstall") {
188630
+ console.log("[service] uninstall not yet implemented \u2014 remove units manually (systemctl disable / launchctl unload).");
188631
+ process.exit(0);
188632
+ }
188633
+ const { serviceInstallCommand: serviceInstallCommand2 } = await Promise.resolve().then(() => (init_service(), exports_service));
188634
+ const exitCode = await serviceInstallCommand2({
188635
+ dashboard: options.dashboard ?? true,
188636
+ gateway: options.gateway ?? true,
188637
+ user: options.user ?? false
188638
+ });
188639
+ process.exit(exitCode);
188640
+ });
188282
188641
 
188283
188642
  // packages/omo-opencode/src/cli/index.ts
188284
188643
  runCli();
@@ -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.26",
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.26",
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",