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.fileTypeskey was removed — it duplicated per-appfileAssociationsand 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.