Themes, files & services

Theme (contributes.themes / contributes.agxos.themes)

What it is. A token bundle (+ optional wallpaper/cursor) that shows up as a selectable option in Settings → Themes / the Agxos theme switcher. Use it to ship a color/visual theme without any UI code.

Status. Supported and wired end to end in Glixo Code and Glixo OS. The maintained source-only tutorial is glixo-extensions-docs/samples/themes/theme.

Schema shape (themeContribution, glixo-platform/catalog/schema/glixo.module.schema.json:719-751 — required: id, name, tokens):

"contributes": {
  "themes": [
    {
      "id": "acme.midnight",
      "name": "Midnight",
      "tokens": { "accent": "#7c5cff", "surface": "#14151a", "windowBar": "#1c1d24" },
      "wallpaper": "assets/midnight-wallpaper.png",
      "cursor": "assets/midnight-cursor.png",
      "license": { "spdx": "CC0-1.0", "source": "https://example.com/wallpaper", "attribution": "Jane Doe" }
    }
  ]
}

Note there are two valid slots for the same themeContribution shape: top-level contributes.themes[] (schema line 504-508: "Theme modules: a token bundle... selectable in Settings → Themes") and contributes.agxos.themes[] (schema line 478-482: "Agxos themes contributed from agxos-scoped manifests"). The host reads both and merges them (see consumer below).

The code you write. None required beyond the manifest — tokens is a flat Record<string, string> keyed by whatever token names the host's theme renderer expects (accent, surface, windowBar are the ones the marketplace preview extracts, per glixo-code/sources/extensions/marketplaceCatalog.ts:54). Type: ExtensionThemeContribution (glixo-sdk/packages/nodejs/src/extensionRegistry.ts:324-331):

export interface ExtensionThemeContribution {
  id: string;
  name: string;
  tokens: Record<string, string>;
  wallpaper?: string;
  cursor?: string;
  license?: ExtensionThemeLicense;
}

How the host consumes it. getInstalledThemes(state) merges contributes.themes[] and contributes.agxos.themes[] across installed manifests (glixo-code/sources/extensions/store.ts:346-357), and it's actually called: AgxosIframeHost.tsx:794 builds agxosExtensionThemes from it, normalizing/validating each entry via toAgxosThemeRegistration (glixo-code/sources/agxos/AgxosIframeHost.tsx:2302-2316) before it reaches the theme switcher. Marketplace listing/categorization treats any manifest with a non-empty themes or agxos.themes array as kind: 'theme'-equivalent (glixo-code/sources/extensions/marketplaceCatalog.ts:12-17,41).

Sample to study. glixo-extensions-docs/samples/themes/theme. It is declarative, has no runtime process, and is intentionally not a marketplace product.


File associations (a property of an Agxos app)

File-type handling is not a standalone extension type — it's a property of an Agxos app. An app declares the extensions it can open (and whether it can be the default handler) via fileAssociations on its contributes.agxos.apps[] entry; see Agxos coded apps. Glixo Code's IDE Changes/Files rail uses this manifest field for Open with and file default routing; the built-in Code editor remains the fallback for every file.

The former top-level contributes.fileTypes key was removed — it duplicated per-app fileAssociations and had no consumer.

Service module (kind: "service")

What it is. A standalone backend process — its own runtime, own port, own DB — that the host supervises (start/health-check/config) but that contributes no UI itself. Use it for integrations that need a long-running server (OAuth token refresh, realtime sync workers, a local SQLite-backed API) with a separate companion extension providing the UI.

Real, live (non-archived) manifest — glixo-community-modules/integrations/teams/glixo.module.json:

{
  "id": "glixo.messaging.teams",
  "name": "Microsoft Teams",
  "description": "Teams messaging service - native OAuth, module DB, sync, contract events. UI is a separate extension.",
  "kind": "service",
  "version": "0.1.0-alpha",
  "protocolVersion": "0",
  "runtime": { "kind": "node", "version": "20" },
  "entry": { "command": "node", "args": ["dist/index.js"], "workingDirectory": "." },
  "capabilities": [
    "glixo.message.sync",
    "glixo.message.send",
    "glixo.presence.track",
    "glixo.teams.conversations"
  ],
  "requires": [
    "logging", "health", "storage", "relationalDb.module", "config",
    "secrets.teams", "jobs", "notifications", "events",
    "capability.glixo.messages.ingest"
  ],
  "permissions": ["network.microsoft-graph"],
  "health": { "path": "/health", "intervalSeconds": 60 },
  "config": {
    "fields": [
      { "key": "realtimeEnabled", "label": "Realtime sync (Trouter)", "type": "checkbox", "defaultValue": "true", "helpText": "Subscribe to Microsoft Trouter for live messages." },
      { "key": "backfillOnConnect", "label": "Backfill history on connect", "type": "checkbox", "defaultValue": "true" },
      { "key": "port", "label": "HTTP port", "type": "number", "defaultValue": "6120" }
    ]
  },
  "ui": { "settingsComponent": "TeamsAuthSettings", "schema": "settings/auth.schema.json" }
}

Note kind is only an "authoring role hint" per the schema (glixo-platform/catalog/schema/glixo.module.schema.json:23-26: "Runtime ownership is determined by components, requires, capabilities, and contributions") — there is no contributes.* block here at all; a service exposes capabilities and consumes requires, it doesn't contribute UI surfaces.

The code you write. A plain HTTP server matching entry.command/args/workingDirectory, with a route for whatever health.path you declared. The Teams module's actual entry (glixo-community-modules/integrations/teams/src/index.ts:1-27):

const port = Number(process.env.TEAMS_MODULE_PORT ?? process.env.PORT ?? 6120);
const dataDir = process.env.TEAMS_MODULE_DATA ?? path.join(os.homedir(), '.glixo', 'modules', 'teams');
const db = openModuleDb(dataDir);
const runtime = new TeamsServiceRuntime(db, log, DEFAULT_USER);
const server = http.createServer((req, res) => {
  void handleRequest(req, res, { db, runtime, log });
});
server.listen(port, '127.0.0.1', () => {
  log('teams.service.started', { port, dataDir });
  void runtime.bootstrap();
});

...and the health route it registers, matching the manifest's health.path (glixo-community-modules/integrations/teams/src/api/routes.ts:67):

if (method === 'GET' && url.pathname === '/health') {
  const auth = getAuthStatus(ctx.db, DEFAULT_USER);
  // ...responds with { auth: auth.status, workers: ctx.runtime.healthWorkers() }
}

How the host consumes it. ModuleLifecycleManager.CreateStartPlanAsync reads manifest.Entry.Command/Args/WorkingDirectory to build the launch command, manifest.Health.Path to wire the health-check step, and manifest.Config/Permissions for the config/runtime-policy steps — all kind-agnostic (glixo-platform/src/Glixo.ServiceCatalog/ModuleLifecycleManager.cs:88-144).

Sample to study. glixo-community-modules/integrations/teams/ — live (not archived), has a real src/ tree, package.json, migrations, and a README describing dev run instructions (README.md:1-15: "Alpha — ... Not listed on the public marketplace catalog yet"). Its UI counterpart is a separate extension per the manifest description ("UI is a separate extension") — not itself in this repo checkout.