@kl-c/matrixos 0.1.23 → 0.1.24

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.
@@ -5,6 +5,7 @@
5
5
  * - Warns and returns if the file is missing or unreadable — never throws.
6
6
  * - Parses `KEY=VALUE` lines tolerating an optional leading `export ` and
7
7
  * surrounding quotes.
8
- * - Only sets keys that are NOT already present in `process.env`.
8
+ * - By default, only sets keys that are NOT already present in `process.env`.
9
+ * Pass `force: true` to overwrite existing values (used during hot-reload).
9
10
  */
10
- export declare function loadGatewayEnvFile(configDir?: string): void;
11
+ export declare function loadGatewayEnvFile(configDir?: string, force?: boolean): void;
package/dist/cli/index.js CHANGED
@@ -2145,7 +2145,7 @@ var package_default;
2145
2145
  var init_package = __esm(() => {
2146
2146
  package_default = {
2147
2147
  name: "@kl-c/matrixos",
2148
- version: "0.1.23",
2148
+ version: "0.1.24",
2149
2149
  description: "MaTrixOS \u2014 Agentic OS for OpenCode. Personalizable, communicating, self-improving, resilient.",
2150
2150
  main: "./dist/index.js",
2151
2151
  types: "dist/index.d.ts",
@@ -117621,19 +117621,19 @@ __export(exports_generate, {
117621
117621
  executeGenerateCommand: () => executeGenerateCommand,
117622
117622
  ProfileNotFoundError: () => ProfileNotFoundError
117623
117623
  });
117624
- import { existsSync as existsSync70, readFileSync as readFileSync55, realpathSync as realpathSync10 } from "fs";
117625
- import { dirname as dirname28, isAbsolute as isAbsolute6, join as join75, resolve as resolve16 } from "path";
117624
+ import { existsSync as existsSync70, readFileSync as readFileSync56, realpathSync as realpathSync10 } from "fs";
117625
+ import { dirname as dirname28, isAbsolute as isAbsolute6, join as join76, resolve as resolve16 } from "path";
117626
117626
  function profilesDirFor(opts) {
117627
117627
  if (opts.profilesDir)
117628
117628
  return opts.profilesDir;
117629
117629
  if (opts.cwd)
117630
- return join75(opts.cwd, PROFILES_RELATIVE);
117631
- return join75(REPO_ROOT, PROFILES_RELATIVE);
117630
+ return join76(opts.cwd, PROFILES_RELATIVE);
117631
+ return join76(REPO_ROOT, PROFILES_RELATIVE);
117632
117632
  }
117633
117633
  function loadProfile(name2, options = {}) {
117634
117634
  const fs21 = options.fs ?? defaultFS2;
117635
117635
  const dir = profilesDirFor(options);
117636
- const path27 = join75(dir, `${name2}.json`);
117636
+ const path27 = join76(dir, `${name2}.json`);
117637
117637
  if (!fs21.existsSync(path27)) {
117638
117638
  throw new ProfileNotFoundError(name2);
117639
117639
  }
@@ -117646,7 +117646,7 @@ function listProfiles(options = {}) {
117646
117646
  const known = options.knownNames ?? ["trader", "plumber"];
117647
117647
  const out = [];
117648
117648
  for (const name2 of known) {
117649
- const path27 = join75(dir, `${name2}.json`);
117649
+ const path27 = join76(dir, `${name2}.json`);
117650
117650
  if (fs21.existsSync(path27)) {
117651
117651
  try {
117652
117652
  const raw = JSON.parse(fs21.readFileSync(path27, "utf-8"));
@@ -117719,7 +117719,7 @@ var init_generate = __esm(() => {
117719
117719
  init_src4();
117720
117720
  init_src4();
117721
117721
  REPO_ROOT = resolve16(dirname28(new URL(import.meta.url).pathname), "..", "..", "..", "..", "..");
117722
- defaultFS2 = { existsSync: existsSync70, readFileSync: readFileSync55, realpathSync: realpathSync10 };
117722
+ defaultFS2 = { existsSync: existsSync70, readFileSync: readFileSync56, realpathSync: realpathSync10 };
117723
117723
  ProfileNotFoundError = class ProfileNotFoundError extends Error {
117724
117724
  profileName;
117725
117725
  constructor(profileName) {
@@ -117871,8 +117871,8 @@ var exports_profile_resolve = {};
117871
117871
  __export(exports_profile_resolve, {
117872
117872
  executeProfileResolveCommand: () => executeProfileResolveCommand
117873
117873
  });
117874
- import { existsSync as existsSync72, readdirSync as readdirSync15, readFileSync as readFileSync56 } from "fs";
117875
- import { join as join77, resolve as resolve17, dirname as dirname29 } from "path";
117874
+ import { existsSync as existsSync72, readdirSync as readdirSync15, readFileSync as readFileSync57 } from "fs";
117875
+ import { join as join78, resolve as resolve17, dirname as dirname29 } from "path";
117876
117876
  function executeProfileResolveCommand(args) {
117877
117877
  if (args.options.help) {
117878
117878
  return {
@@ -117897,7 +117897,7 @@ function executeProfileResolveCommand(args) {
117897
117897
  ` };
117898
117898
  }
117899
117899
  const name2 = nameRaw.endsWith(".json") ? nameRaw.slice(0, -5) : nameRaw;
117900
- const profilesDir = join77(REPO_ROOT2, PROFILES_RELATIVE2);
117900
+ const profilesDir = join78(REPO_ROOT2, PROFILES_RELATIVE2);
117901
117901
  if (!existsSync72(profilesDir)) {
117902
117902
  return { exitCode: 1, stdout: `error: profiles directory not found at ${profilesDir}
117903
117903
  ` };
@@ -117917,8 +117917,8 @@ function executeProfileResolveCommand(args) {
117917
117917
  const registry2 = {};
117918
117918
  for (const file3 of profileFiles) {
117919
117919
  try {
117920
- const filePath = join77(profilesDir, file3);
117921
- const raw = JSON.parse(readFileSync56(filePath, "utf-8"));
117920
+ const filePath = join78(profilesDir, file3);
117921
+ const raw = JSON.parse(readFileSync57(filePath, "utf-8"));
117922
117922
  const profile2 = ProfileSchema.parse(raw);
117923
117923
  registry2[profile2.name] = profile2;
117924
117924
  } catch {}
@@ -117980,8 +117980,8 @@ __export(exports_export, {
117980
117980
  getShell: () => getShell,
117981
117981
  executeExportCommand: () => executeExportCommand
117982
117982
  });
117983
- import { existsSync as existsSync73, readFileSync as readFileSync57, writeFileSync as writeFileSync25, copyFileSync as copyFileSync7, realpathSync as realpathSync11, mkdirSync as mkdirSync26 } from "fs";
117984
- import { isAbsolute as isAbsolute7, join as join78, resolve as resolve18, dirname as dirname30 } from "path";
117983
+ import { existsSync as existsSync73, readFileSync as readFileSync58, writeFileSync as writeFileSync25, copyFileSync as copyFileSync7, realpathSync as realpathSync11, mkdirSync as mkdirSync26 } from "fs";
117984
+ import { isAbsolute as isAbsolute7, join as join79, resolve as resolve18, dirname as dirname30 } from "path";
117985
117985
  import { spawnSync as spawnSync3 } from "child_process";
117986
117986
  function setShell(shell) {
117987
117987
  activeShell = shell;
@@ -118022,7 +118022,7 @@ function executeExportCommand(args) {
118022
118022
  return { exitCode: 1, stdout: `error: generate failed: ${message}
118023
118023
  ` };
118024
118024
  }
118025
- const pkgPath = join78(absoluteTargetDir, "package.json");
118025
+ const pkgPath = join79(absoluteTargetDir, "package.json");
118026
118026
  let pkg;
118027
118027
  try {
118028
118028
  pkg = JSON.parse(fs21.readFileSync(pkgPath, "utf-8"));
@@ -118062,7 +118062,7 @@ ${packResult.stdout}
118062
118062
  return { exitCode: 1, stdout: `error: npm pack produced no tarball name in output
118063
118063
  ` };
118064
118064
  }
118065
- let tgzPath = join78(absoluteTargetDir, tgzName);
118065
+ let tgzPath = join79(absoluteTargetDir, tgzName);
118066
118066
  if (args.options.output) {
118067
118067
  const outputPath = args.options.output;
118068
118068
  const absoluteOutput = isAbsolute7(outputPath) ? outputPath : resolve18(process.cwd(), outputPath);
@@ -118104,7 +118104,7 @@ var init_export = __esm(() => {
118104
118104
  activeShell = defaultShell;
118105
118105
  defaultExportFS = {
118106
118106
  existsSync: existsSync73,
118107
- readFileSync: readFileSync57,
118107
+ readFileSync: readFileSync58,
118108
118108
  writeFileSync: writeFileSync25,
118109
118109
  copyFileSync: copyFileSync7,
118110
118110
  realpathSync: realpathSync11,
@@ -122216,6 +122216,13 @@ init_zod();
122216
122216
  var CronConfigSchema = exports_external.object({
122217
122217
  enabled: exports_external.boolean().default(true)
122218
122218
  }).strict();
122219
+ // packages/omo-opencode/src/config/schema/dashboard.ts
122220
+ init_zod();
122221
+ var DashboardConfigSchema = exports_external.object({
122222
+ enabled: exports_external.boolean().optional(),
122223
+ port: exports_external.number().int().min(1).max(65535).optional(),
122224
+ gateway_passphrase: exports_external.string().optional()
122225
+ });
122219
122226
  // packages/omo-opencode/src/config/schema/default-mode.ts
122220
122227
  init_zod();
122221
122228
  var DefaultModeConfigSchema = exports_external.object({
@@ -122723,6 +122730,7 @@ var OhMyOpenCodeConfigSchema = exports_external.object({
122723
122730
  morpheus: MorpheusConfigSchema.optional(),
122724
122731
  start_work: StartWorkConfigSchema.optional(),
122725
122732
  gateway: GatewayConfigSectionSchema,
122733
+ dashboard: DashboardConfigSchema.optional(),
122726
122734
  watchdog: WatchdogConfigSchema.optional(),
122727
122735
  egress: EgressConfigSectionSchema,
122728
122736
  cron: CronConfigSchema.optional(),
@@ -141144,6 +141152,7 @@ async function traceCli(sessionId, options = {}) {
141144
141152
 
141145
141153
  // packages/omo-opencode/src/cli/dashboard.ts
141146
141154
  import * as path25 from "path";
141155
+ import { readFileSync as readFileSync55 } from "fs";
141147
141156
 
141148
141157
  // packages/omo-opencode/src/features/dashboard/data-provider.ts
141149
141158
  import * as os12 from "os";
@@ -141703,6 +141712,7 @@ function createDataProvider(config4) {
141703
141712
  }
141704
141713
 
141705
141714
  // packages/omo-opencode/src/features/dashboard/server.ts
141715
+ init_write_gateway_env();
141706
141716
  var MIME = {
141707
141717
  ".html": "text/html; charset=utf-8",
141708
141718
  ".css": "text/css; charset=utf-8",
@@ -141726,8 +141736,8 @@ function createDashboardServer(dataProvider, config4) {
141726
141736
  const path25 = url3.pathname;
141727
141737
  const corsHeaders = {
141728
141738
  "Access-Control-Allow-Origin": "*",
141729
- "Access-Control-Allow-Methods": "GET, OPTIONS",
141730
- "Access-Control-Allow-Headers": "Content-Type"
141739
+ "Access-Control-Allow-Methods": "GET, POST, PUT, OPTIONS",
141740
+ "Access-Control-Allow-Headers": "Content-Type, x-passphrase"
141731
141741
  };
141732
141742
  if (req.method === "OPTIONS") {
141733
141743
  return new Response(null, { headers: corsHeaders });
@@ -141799,6 +141809,30 @@ function createDashboardServer(dataProvider, config4) {
141799
141809
  return Response.json(dataProvider.getMemory(), { headers: jsonHeaders });
141800
141810
  case "/api/skills":
141801
141811
  return Response.json(dataProvider.getSkills(), { headers: jsonHeaders });
141812
+ case "/api/gateway/token": {
141813
+ if (req.method !== "POST") {
141814
+ return new Response("Method Not Allowed", { status: 405, headers: corsHeaders });
141815
+ }
141816
+ const body = await req.json();
141817
+ const type2 = body.type;
141818
+ const token = body.token;
141819
+ const passphrase = body.passphrase;
141820
+ if (type2 !== "telegram" && type2 !== "discord") {
141821
+ return Response.json({ error: "Invalid type. Must be 'telegram' or 'discord'" }, { status: 400, headers: jsonHeaders });
141822
+ }
141823
+ if (typeof token !== "string" || token.length === 0) {
141824
+ return Response.json({ error: "Missing or empty token" }, { status: 400, headers: jsonHeaders });
141825
+ }
141826
+ if (!config4.gatewayPassphrase) {
141827
+ return Response.json({ error: "Passphrase not configured" }, { status: 400, headers: jsonHeaders });
141828
+ }
141829
+ if (passphrase !== config4.gatewayPassphrase) {
141830
+ return Response.json({ error: "Invalid passphrase" }, { status: 401, headers: jsonHeaders });
141831
+ }
141832
+ const key = type2 === "telegram" ? "TELEGRAM_BOT_TOKEN" : "DISCORD_BOT_TOKEN";
141833
+ const envPath = writeGatewayEnvToken(key, token);
141834
+ return Response.json({ ok: true, path: envPath }, { headers: jsonHeaders });
141835
+ }
141802
141836
  default:
141803
141837
  return new Response("Not Found", { status: 404, headers: corsHeaders });
141804
141838
  }
@@ -141834,21 +141868,36 @@ function createDashboardServer(dataProvider, config4) {
141834
141868
  server2 = null;
141835
141869
  },
141836
141870
  get port() {
141837
- return config4.port;
141871
+ return server2?.port ?? config4.port;
141838
141872
  }
141839
141873
  };
141840
141874
  }
141841
141875
 
141842
141876
  // packages/omo-opencode/src/cli/dashboard.ts
141877
+ init_config_context();
141878
+ init_jsonc_parser2();
141879
+ function loadGatewayPassphrase() {
141880
+ try {
141881
+ const configDir = getConfigDir();
141882
+ const configPath = path25.join(configDir, "matrixos.jsonc");
141883
+ const raw = readFileSync55(configPath, "utf-8");
141884
+ const config4 = parseJsonc(raw);
141885
+ const dashboard2 = config4.dashboard;
141886
+ return typeof dashboard2?.gateway_passphrase === "string" ? dashboard2.gateway_passphrase : undefined;
141887
+ } catch {
141888
+ return;
141889
+ }
141890
+ }
141843
141891
  async function dashboardCli(options) {
141844
141892
  const port = options.port ?? 9123;
141845
141893
  const host = options.host ?? "0.0.0.0";
141846
141894
  const frontendDir = path25.resolve(import.meta.dir, "../features/dashboard/frontend");
141895
+ const gatewayPassphrase = loadGatewayPassphrase();
141847
141896
  console.log(`[dashboard] starting MaTrixOS dashboard on http://${host}:${port}`);
141848
141897
  console.log(`[dashboard] frontend: ${frontendDir}`);
141849
141898
  try {
141850
141899
  const dataProvider = createDataProvider();
141851
- const server2 = createDashboardServer(dataProvider, { port, host, frontendDir });
141900
+ const server2 = createDashboardServer(dataProvider, { port, host, frontendDir, gatewayPassphrase });
141852
141901
  server2.start();
141853
141902
  console.log(`[dashboard] ready \u2014 http://${host}:${port}`);
141854
141903
  if (!options.daemon) {
@@ -141875,11 +141924,11 @@ async function dashboardCli(options) {
141875
141924
  }
141876
141925
 
141877
141926
  // packages/omo-opencode/src/cli/architect.ts
141878
- import { join as join73 } from "path";
141927
+ import { join as join74 } from "path";
141879
141928
  var IMPROVEMENTS_DIR = ".matrixos/improvements";
141880
141929
  function getStore(directory) {
141881
141930
  const baseDir = directory ?? process.cwd();
141882
- return createImprovementStore(join73(baseDir, IMPROVEMENTS_DIR));
141931
+ return createImprovementStore(join74(baseDir, IMPROVEMENTS_DIR));
141883
141932
  }
141884
141933
  async function architectReview(options) {
141885
141934
  const store4 = getStore(options.directory);
@@ -2145,7 +2145,7 @@ var package_default;
2145
2145
  var init_package = __esm(() => {
2146
2146
  package_default = {
2147
2147
  name: "@kl-c/matrixos",
2148
- version: "0.1.23",
2148
+ version: "0.1.24",
2149
2149
  description: "MaTrixOS \u2014 Agentic OS for OpenCode. Personalizable, communicating, self-improving, resilient.",
2150
2150
  main: "./dist/index.js",
2151
2151
  types: "dist/index.d.ts",
@@ -117676,19 +117676,19 @@ __export(exports_generate, {
117676
117676
  executeGenerateCommand: () => executeGenerateCommand,
117677
117677
  ProfileNotFoundError: () => ProfileNotFoundError
117678
117678
  });
117679
- import { existsSync as existsSync70, readFileSync as readFileSync55, realpathSync as realpathSync10 } from "fs";
117680
- import { dirname as dirname28, isAbsolute as isAbsolute6, join as join75, resolve as resolve16 } from "path";
117679
+ import { existsSync as existsSync70, readFileSync as readFileSync56, realpathSync as realpathSync10 } from "fs";
117680
+ import { dirname as dirname28, isAbsolute as isAbsolute6, join as join76, resolve as resolve16 } from "path";
117681
117681
  function profilesDirFor(opts) {
117682
117682
  if (opts.profilesDir)
117683
117683
  return opts.profilesDir;
117684
117684
  if (opts.cwd)
117685
- return join75(opts.cwd, PROFILES_RELATIVE);
117686
- return join75(REPO_ROOT, PROFILES_RELATIVE);
117685
+ return join76(opts.cwd, PROFILES_RELATIVE);
117686
+ return join76(REPO_ROOT, PROFILES_RELATIVE);
117687
117687
  }
117688
117688
  function loadProfile(name2, options = {}) {
117689
117689
  const fs21 = options.fs ?? defaultFS2;
117690
117690
  const dir = profilesDirFor(options);
117691
- const path27 = join75(dir, `${name2}.json`);
117691
+ const path27 = join76(dir, `${name2}.json`);
117692
117692
  if (!fs21.existsSync(path27)) {
117693
117693
  throw new ProfileNotFoundError(name2);
117694
117694
  }
@@ -117701,7 +117701,7 @@ function listProfiles(options = {}) {
117701
117701
  const known = options.knownNames ?? ["trader", "plumber"];
117702
117702
  const out = [];
117703
117703
  for (const name2 of known) {
117704
- const path27 = join75(dir, `${name2}.json`);
117704
+ const path27 = join76(dir, `${name2}.json`);
117705
117705
  if (fs21.existsSync(path27)) {
117706
117706
  try {
117707
117707
  const raw = JSON.parse(fs21.readFileSync(path27, "utf-8"));
@@ -117774,7 +117774,7 @@ var init_generate = __esm(() => {
117774
117774
  init_src4();
117775
117775
  init_src4();
117776
117776
  REPO_ROOT = resolve16(dirname28(new URL(import.meta.url).pathname), "..", "..", "..", "..", "..");
117777
- defaultFS2 = { existsSync: existsSync70, readFileSync: readFileSync55, realpathSync: realpathSync10 };
117777
+ defaultFS2 = { existsSync: existsSync70, readFileSync: readFileSync56, realpathSync: realpathSync10 };
117778
117778
  ProfileNotFoundError = class ProfileNotFoundError extends Error {
117779
117779
  profileName;
117780
117780
  constructor(profileName) {
@@ -117926,8 +117926,8 @@ var exports_profile_resolve = {};
117926
117926
  __export(exports_profile_resolve, {
117927
117927
  executeProfileResolveCommand: () => executeProfileResolveCommand
117928
117928
  });
117929
- import { existsSync as existsSync72, readdirSync as readdirSync15, readFileSync as readFileSync56 } from "fs";
117930
- import { join as join77, resolve as resolve17, dirname as dirname29 } from "path";
117929
+ import { existsSync as existsSync72, readdirSync as readdirSync15, readFileSync as readFileSync57 } from "fs";
117930
+ import { join as join78, resolve as resolve17, dirname as dirname29 } from "path";
117931
117931
  function executeProfileResolveCommand(args) {
117932
117932
  if (args.options.help) {
117933
117933
  return {
@@ -117952,7 +117952,7 @@ function executeProfileResolveCommand(args) {
117952
117952
  ` };
117953
117953
  }
117954
117954
  const name2 = nameRaw.endsWith(".json") ? nameRaw.slice(0, -5) : nameRaw;
117955
- const profilesDir = join77(REPO_ROOT2, PROFILES_RELATIVE2);
117955
+ const profilesDir = join78(REPO_ROOT2, PROFILES_RELATIVE2);
117956
117956
  if (!existsSync72(profilesDir)) {
117957
117957
  return { exitCode: 1, stdout: `error: profiles directory not found at ${profilesDir}
117958
117958
  ` };
@@ -117972,8 +117972,8 @@ function executeProfileResolveCommand(args) {
117972
117972
  const registry2 = {};
117973
117973
  for (const file3 of profileFiles) {
117974
117974
  try {
117975
- const filePath = join77(profilesDir, file3);
117976
- const raw = JSON.parse(readFileSync56(filePath, "utf-8"));
117975
+ const filePath = join78(profilesDir, file3);
117976
+ const raw = JSON.parse(readFileSync57(filePath, "utf-8"));
117977
117977
  const profile2 = ProfileSchema.parse(raw);
117978
117978
  registry2[profile2.name] = profile2;
117979
117979
  } catch {}
@@ -118035,8 +118035,8 @@ __export(exports_export, {
118035
118035
  getShell: () => getShell,
118036
118036
  executeExportCommand: () => executeExportCommand
118037
118037
  });
118038
- import { existsSync as existsSync73, readFileSync as readFileSync57, writeFileSync as writeFileSync25, copyFileSync as copyFileSync7, realpathSync as realpathSync11, mkdirSync as mkdirSync26 } from "fs";
118039
- import { isAbsolute as isAbsolute7, join as join78, resolve as resolve18, dirname as dirname30 } from "path";
118038
+ import { existsSync as existsSync73, readFileSync as readFileSync58, writeFileSync as writeFileSync25, copyFileSync as copyFileSync7, realpathSync as realpathSync11, mkdirSync as mkdirSync26 } from "fs";
118039
+ import { isAbsolute as isAbsolute7, join as join79, resolve as resolve18, dirname as dirname30 } from "path";
118040
118040
  import { spawnSync as spawnSync3 } from "child_process";
118041
118041
  function setShell(shell) {
118042
118042
  activeShell = shell;
@@ -118077,7 +118077,7 @@ function executeExportCommand(args) {
118077
118077
  return { exitCode: 1, stdout: `error: generate failed: ${message}
118078
118078
  ` };
118079
118079
  }
118080
- const pkgPath = join78(absoluteTargetDir, "package.json");
118080
+ const pkgPath = join79(absoluteTargetDir, "package.json");
118081
118081
  let pkg;
118082
118082
  try {
118083
118083
  pkg = JSON.parse(fs21.readFileSync(pkgPath, "utf-8"));
@@ -118117,7 +118117,7 @@ ${packResult.stdout}
118117
118117
  return { exitCode: 1, stdout: `error: npm pack produced no tarball name in output
118118
118118
  ` };
118119
118119
  }
118120
- let tgzPath = join78(absoluteTargetDir, tgzName);
118120
+ let tgzPath = join79(absoluteTargetDir, tgzName);
118121
118121
  if (args.options.output) {
118122
118122
  const outputPath = args.options.output;
118123
118123
  const absoluteOutput = isAbsolute7(outputPath) ? outputPath : resolve18(process.cwd(), outputPath);
@@ -118159,7 +118159,7 @@ var init_export = __esm(() => {
118159
118159
  activeShell = defaultShell;
118160
118160
  defaultExportFS = {
118161
118161
  existsSync: existsSync73,
118162
- readFileSync: readFileSync57,
118162
+ readFileSync: readFileSync58,
118163
118163
  writeFileSync: writeFileSync25,
118164
118164
  copyFileSync: copyFileSync7,
118165
118165
  realpathSync: realpathSync11,
@@ -122271,6 +122271,13 @@ init_zod();
122271
122271
  var CronConfigSchema = exports_external.object({
122272
122272
  enabled: exports_external.boolean().default(true)
122273
122273
  }).strict();
122274
+ // packages/omo-opencode/src/config/schema/dashboard.ts
122275
+ init_zod();
122276
+ var DashboardConfigSchema = exports_external.object({
122277
+ enabled: exports_external.boolean().optional(),
122278
+ port: exports_external.number().int().min(1).max(65535).optional(),
122279
+ gateway_passphrase: exports_external.string().optional()
122280
+ });
122274
122281
  // packages/omo-opencode/src/config/schema/default-mode.ts
122275
122282
  init_zod();
122276
122283
  var DefaultModeConfigSchema = exports_external.object({
@@ -122778,6 +122785,7 @@ var OhMyOpenCodeConfigSchema = exports_external.object({
122778
122785
  morpheus: MorpheusConfigSchema.optional(),
122779
122786
  start_work: StartWorkConfigSchema.optional(),
122780
122787
  gateway: GatewayConfigSectionSchema,
122788
+ dashboard: DashboardConfigSchema.optional(),
122781
122789
  watchdog: WatchdogConfigSchema.optional(),
122782
122790
  egress: EgressConfigSectionSchema,
122783
122791
  cron: CronConfigSchema.optional(),
@@ -141199,6 +141207,7 @@ async function traceCli(sessionId, options = {}) {
141199
141207
 
141200
141208
  // packages/omo-opencode/src/cli/dashboard.ts
141201
141209
  import * as path25 from "path";
141210
+ import { readFileSync as readFileSync55 } from "fs";
141202
141211
 
141203
141212
  // packages/omo-opencode/src/features/dashboard/data-provider.ts
141204
141213
  import * as os12 from "os";
@@ -141758,6 +141767,7 @@ function createDataProvider(config4) {
141758
141767
  }
141759
141768
 
141760
141769
  // packages/omo-opencode/src/features/dashboard/server.ts
141770
+ init_write_gateway_env();
141761
141771
  var MIME = {
141762
141772
  ".html": "text/html; charset=utf-8",
141763
141773
  ".css": "text/css; charset=utf-8",
@@ -141781,8 +141791,8 @@ function createDashboardServer(dataProvider, config4) {
141781
141791
  const path25 = url3.pathname;
141782
141792
  const corsHeaders = {
141783
141793
  "Access-Control-Allow-Origin": "*",
141784
- "Access-Control-Allow-Methods": "GET, OPTIONS",
141785
- "Access-Control-Allow-Headers": "Content-Type"
141794
+ "Access-Control-Allow-Methods": "GET, POST, PUT, OPTIONS",
141795
+ "Access-Control-Allow-Headers": "Content-Type, x-passphrase"
141786
141796
  };
141787
141797
  if (req.method === "OPTIONS") {
141788
141798
  return new Response(null, { headers: corsHeaders });
@@ -141854,6 +141864,30 @@ function createDashboardServer(dataProvider, config4) {
141854
141864
  return Response.json(dataProvider.getMemory(), { headers: jsonHeaders });
141855
141865
  case "/api/skills":
141856
141866
  return Response.json(dataProvider.getSkills(), { headers: jsonHeaders });
141867
+ case "/api/gateway/token": {
141868
+ if (req.method !== "POST") {
141869
+ return new Response("Method Not Allowed", { status: 405, headers: corsHeaders });
141870
+ }
141871
+ const body = await req.json();
141872
+ const type2 = body.type;
141873
+ const token = body.token;
141874
+ const passphrase = body.passphrase;
141875
+ if (type2 !== "telegram" && type2 !== "discord") {
141876
+ return Response.json({ error: "Invalid type. Must be 'telegram' or 'discord'" }, { status: 400, headers: jsonHeaders });
141877
+ }
141878
+ if (typeof token !== "string" || token.length === 0) {
141879
+ return Response.json({ error: "Missing or empty token" }, { status: 400, headers: jsonHeaders });
141880
+ }
141881
+ if (!config4.gatewayPassphrase) {
141882
+ return Response.json({ error: "Passphrase not configured" }, { status: 400, headers: jsonHeaders });
141883
+ }
141884
+ if (passphrase !== config4.gatewayPassphrase) {
141885
+ return Response.json({ error: "Invalid passphrase" }, { status: 401, headers: jsonHeaders });
141886
+ }
141887
+ const key = type2 === "telegram" ? "TELEGRAM_BOT_TOKEN" : "DISCORD_BOT_TOKEN";
141888
+ const envPath = writeGatewayEnvToken(key, token);
141889
+ return Response.json({ ok: true, path: envPath }, { headers: jsonHeaders });
141890
+ }
141857
141891
  default:
141858
141892
  return new Response("Not Found", { status: 404, headers: corsHeaders });
141859
141893
  }
@@ -141889,21 +141923,36 @@ function createDashboardServer(dataProvider, config4) {
141889
141923
  server2 = null;
141890
141924
  },
141891
141925
  get port() {
141892
- return config4.port;
141926
+ return server2?.port ?? config4.port;
141893
141927
  }
141894
141928
  };
141895
141929
  }
141896
141930
 
141897
141931
  // packages/omo-opencode/src/cli/dashboard.ts
141932
+ init_config_context();
141933
+ init_jsonc_parser2();
141934
+ function loadGatewayPassphrase() {
141935
+ try {
141936
+ const configDir = getConfigDir();
141937
+ const configPath = path25.join(configDir, "matrixos.jsonc");
141938
+ const raw = readFileSync55(configPath, "utf-8");
141939
+ const config4 = parseJsonc(raw);
141940
+ const dashboard2 = config4.dashboard;
141941
+ return typeof dashboard2?.gateway_passphrase === "string" ? dashboard2.gateway_passphrase : undefined;
141942
+ } catch {
141943
+ return;
141944
+ }
141945
+ }
141898
141946
  async function dashboardCli(options) {
141899
141947
  const port = options.port ?? 9123;
141900
141948
  const host = options.host ?? "0.0.0.0";
141901
141949
  const frontendDir = path25.resolve(import.meta.dir, "../features/dashboard/frontend");
141950
+ const gatewayPassphrase = loadGatewayPassphrase();
141902
141951
  console.log(`[dashboard] starting MaTrixOS dashboard on http://${host}:${port}`);
141903
141952
  console.log(`[dashboard] frontend: ${frontendDir}`);
141904
141953
  try {
141905
141954
  const dataProvider = createDataProvider();
141906
- const server2 = createDashboardServer(dataProvider, { port, host, frontendDir });
141955
+ const server2 = createDashboardServer(dataProvider, { port, host, frontendDir, gatewayPassphrase });
141907
141956
  server2.start();
141908
141957
  console.log(`[dashboard] ready \u2014 http://${host}:${port}`);
141909
141958
  if (!options.daemon) {
@@ -141930,11 +141979,11 @@ async function dashboardCli(options) {
141930
141979
  }
141931
141980
 
141932
141981
  // packages/omo-opencode/src/cli/architect.ts
141933
- import { join as join73 } from "path";
141982
+ import { join as join74 } from "path";
141934
141983
  var IMPROVEMENTS_DIR = ".matrixos/improvements";
141935
141984
  function getStore(directory) {
141936
141985
  const baseDir = directory ?? process.cwd();
141937
- return createImprovementStore(join73(baseDir, IMPROVEMENTS_DIR));
141986
+ return createImprovementStore(join74(baseDir, IMPROVEMENTS_DIR));
141938
141987
  }
141939
141988
  async function architectReview(options) {
141940
141989
  const store4 = getStore(options.directory);
@@ -0,0 +1,7 @@
1
+ import { z } from "zod";
2
+ export declare const DashboardConfigSchema: z.ZodObject<{
3
+ enabled: z.ZodOptional<z.ZodBoolean>;
4
+ port: z.ZodOptional<z.ZodNumber>;
5
+ gateway_passphrase: z.ZodOptional<z.ZodString>;
6
+ }, z.core.$strip>;
7
+ export type DashboardConfig = z.infer<typeof DashboardConfigSchema>;
@@ -2446,6 +2446,11 @@ export declare const OhMyOpenCodeConfigSchema: z.ZodObject<{
2446
2446
  auditLogPath: z.ZodOptional<z.ZodString>;
2447
2447
  enabled: z.ZodDefault<z.ZodBoolean>;
2448
2448
  }, z.core.$strict>>;
2449
+ dashboard: z.ZodOptional<z.ZodObject<{
2450
+ enabled: z.ZodOptional<z.ZodBoolean>;
2451
+ port: z.ZodOptional<z.ZodNumber>;
2452
+ gateway_passphrase: z.ZodOptional<z.ZodString>;
2453
+ }, z.core.$strip>>;
2449
2454
  watchdog: z.ZodOptional<z.ZodObject<{
2450
2455
  enabled: z.ZodDefault<z.ZodBoolean>;
2451
2456
  doomLoopThreshold: z.ZodDefault<z.ZodNumber>;
@@ -9,6 +9,7 @@ export * from "./schema/codegraph";
9
9
  export * from "./schema/comment-checker";
10
10
  export * from "./schema/commands";
11
11
  export * from "./schema/cron";
12
+ export * from "./schema/dashboard";
12
13
  export * from "./schema/default-mode";
13
14
  export * from "./schema/dynamic-context-pruning";
14
15
  export * from "./schema/egress";
@@ -207,5 +207,17 @@ body{font-family:var(--font-body);font-size:var(--text-base);color:var(--text-pr
207
207
  .config-editor{width:100%;min-height:400px;background:var(--bg-base);color:var(--text-primary);font-family:var(--font-mono);font-size:var(--text-sm);border:none;padding:var(--space-4);resize:vertical;tab-size:2;line-height:1.5}
208
208
  .config-editor:focus{outline:1px solid var(--accent-primary)}
209
209
 
210
+ /* FORM CONTROLS (gateway + future forms) */
211
+ .label{display:block;font-size:var(--text-xs);color:var(--text-secondary);font-weight:500;margin-bottom:var(--space-1);text-transform:uppercase;letter-spacing:.05em}
212
+ .input,.select{width:100%;background:var(--bg-base);color:var(--text-primary);border:1px solid var(--border-default);border-radius:var(--radius-lg);padding:var(--space-2) var(--space-3);font-size:var(--text-sm);font-family:var(--font-body);transition:border-color .15s}
213
+ .input:focus,.select:focus{outline:none;border-color:var(--accent-primary)}
214
+ .input::placeholder{color:var(--text-tertiary)}
215
+ .select{cursor:pointer;appearance:none;-webkit-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' fill='none' stroke='%238B8D97' stroke-width='1.5'><path d='M3 5l3 3 3-3'/></svg>");background-repeat:no-repeat;background-position:right var(--space-3) center;padding-right:var(--space-6)}
216
+ .gateway-form{display:flex;flex-direction:column;gap:var(--space-4);max-width:480px}
217
+ .gateway-help{font-size:var(--text-xs);color:var(--text-secondary);line-height:1.5;margin-bottom:var(--space-1)}
218
+ .form-row{display:flex;flex-direction:column}
219
+ .form-actions{display:flex;align-items:center;gap:var(--space-3);margin-top:var(--space-2)}
220
+ .gateway-status{font-size:var(--text-sm);color:var(--text-tertiary)}
221
+
210
222
  @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)}}
211
223
  @media(max-width:768px){.kpi-grid,.grid-2{grid-template-columns:1fr}.kanban-board{grid-template-columns:1fr}.agent-grid{grid-template-columns:1fr}}
@@ -166,6 +166,7 @@
166
166
  </div>
167
167
  </div>
168
168
  <div class="card"><div class="card-header"><span class="card-title">matrixos.jsonc</span><span id="configStatus" style="font-size:var(--text-xs);color:var(--text-tertiary)"></span></div><div class="card-body"><textarea id="configEditor" class="config-editor" spellcheck="false"></textarea></div></div>
169
+ <div class="card"><div class="card-header"><span class="card-title">Connecter un canal de messagerie</span><span class="badge badge-purple">Gateway</span></div><div class="card-body" id="gatewayBody"></div></div>
169
170
  </div>
170
171
 
171
172
  <div id="tab-schedule" class="tab-panel">
@@ -1,2 +1,2 @@
1
- const API={health:()=>fetchJSON('/health'),agents:()=>fetchJSON('/agents'),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')}
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={health:()=>fetchJSON('/health'),agents:()=>fetchJSON('/agents'),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()}
@@ -266,6 +266,66 @@ async function loadConfig(){
266
266
  }catch(e){status.textContent=`Invalid JSON: ${e.message}`;status.style.color='var(--danger)'}
267
267
  }
268
268
  document.getElementById('configRefreshBtn').onclick=loadConfig
269
+ loadGateway()
270
+ }
271
+
272
+ // ===== GATEWAY (Connecter un canal) =====
273
+ function loadGateway(){
274
+ const body=document.getElementById('gatewayBody')
275
+ if(!body)return
276
+ body.innerHTML=`
277
+ <div class="gateway-form">
278
+ <p class="gateway-help">Renseignez le token du bot et la passphrase pour connecter un canal de messagerie. Le token est envoyé au backend et n'est jamais stocké côté navigateur.</p>
279
+ <div class="form-row">
280
+ <label class="label" for="gwType">Type</label>
281
+ <select class="select" id="gwType">
282
+ <option value="telegram">Telegram</option>
283
+ <option value="discord">Discord</option>
284
+ </select>
285
+ </div>
286
+ <div class="form-row">
287
+ <label class="label" for="gwToken">Token du bot</label>
288
+ <input class="input" type="password" id="gwToken" autocomplete="off" placeholder="123456:ABC-DEF...">
289
+ </div>
290
+ <div class="form-row">
291
+ <label class="label" for="gwPass">Passphrase</label>
292
+ <input class="input" type="password" id="gwPass" autocomplete="off" placeholder="Passphrase configurée côté serveur">
293
+ </div>
294
+ <div class="form-actions">
295
+ <button class="btn btn-primary" id="gwSubmit">Enregistrer</button>
296
+ <span class="gateway-status" id="gwStatus"></span>
297
+ </div>
298
+ </div>`
299
+ const submit=document.getElementById('gwSubmit')
300
+ const status=document.getElementById('gwStatus')
301
+ submit.onclick=async()=>{
302
+ const type=document.getElementById('gwType').value
303
+ const token=document.getElementById('gwToken').value
304
+ const passphrase=document.getElementById('gwPass').value
305
+ if(!token){status.textContent='Token requis';status.style.color='var(--danger)';return}
306
+ if(!passphrase){status.textContent='Passphrase requise';status.style.color='var(--danger)';return}
307
+ submit.disabled=true
308
+ status.textContent='Envoi en cours...'
309
+ status.style.color='var(--text-tertiary)'
310
+ try{
311
+ const res=await API.gatewayToken(type,token,passphrase)
312
+ if(res&&res.ok){
313
+ status.textContent='Token enregistré ✓'
314
+ status.style.color='var(--success)'
315
+ document.getElementById('gwToken').value=''
316
+ document.getElementById('gwPass').value=''
317
+ }else{
318
+ const msg=(res&&res.error)||(Array.isArray(res)&&res[0]&&res[0].error)||'Erreur inconnue'
319
+ status.textContent='Erreur : '+msg
320
+ status.style.color='var(--danger)'
321
+ }
322
+ }catch(e){
323
+ status.textContent='Erreur : '+e.message
324
+ status.style.color='var(--danger)'
325
+ }finally{
326
+ submit.disabled=false
327
+ }
328
+ }
269
329
  }
270
330
 
271
331
  // ===== MEMORY =====