Appearance
Search
system.packages exposes one op: registry.search. This page is the precise behavior, including ranking, scoring, and filter intersection.
Op signature
typescript
// SystemPackageRegistryActor.ts:67-83
'registry.search': {
description: 'Search installable package discovery metadata from the Matrix package registry index',
schema: {
text: 'string?',
tag: 'string?',
inferredKind: 'string?',
declaredKind: 'string?',
capability: 'string?',
accepts: 'string?',
emits: 'string?',
skill: 'string?',
shells: 'string?',
registryRoot: 'string?',
limit: 'number?',
},
}Result shape
typescript
{
ok: true,
registryRoot: string,
results: Array<{
packageName: string,
version: string, // latest known version only
displayName?: string,
description?: string,
inferredKind?: string,
declaredKind?: string,
keywords: readonly string[], // union of inferredKind, declaredKind, tags, capabilities, shells, component skills/tags/capabilities
matchedFields: readonly string[], // which collected fields matched the text query
score: number, // text-match score; 0 when no text query
}>
}Filter semantics
All filters are AND'd. A package is returned only if every supplied filter matches.
Equality filters (case-insensitive)
| Filter | Compared against |
|---|---|
tag | metadata.tags[] |
capability | metadata.capabilities[] |
inferredKind | metadata.inferredKind |
declaredKind | metadata.declaredKind |
shells | metadata.webapp.shells[] |
Implemented by caseIncludes (compares lowercased strings).
Glob filters (case-insensitive, * wildcard)
| Filter | Compared against |
|---|---|
accepts | flatMap(component => component.accepts) |
emits | flatMap(component => component.emits) |
skill | flatMap(component => component.skills) |
Implemented by globMatches — * is converted to .*. Other regex metacharacters in the pattern are escaped. The match is ^pattern$ against each candidate value.
Text filter
text is split on whitespace into tokens. Each token must match (case-insensitive substring) at least one collected field. If any token matches nothing, the package is excluded.
Score per token-match:
packageNameordisplayName→ +5- everything else → +1
Total score is summed across tokens. A package's overall score is its sum.
Field collection (collectFields)
The corpus searched by text is:
packageName, displayName, description, inferredKind, declaredKind,
each tag, each capability,
webapp.appName, webapp.displayName, webapp.description, each webapp.shell,
component.type, component.exportName, component.mount, component.surface,
component.description, component.kind,
each component.accepts, each component.emits, each component.skills,
each component.tag, each component.capabilityResult ordering
typescript
.sort((left, right) => {
if (left.score !== right.score) return right.score - left.score;
return left.packageName.localeCompare(right.packageName);
})Highest score wins; ties broken alphabetically by packageName.
Limit and registry root
typescript
function normalizeLimit(value: unknown): number {
if (value === undefined || value === null) return 20;
if (typeof value !== 'number' || !Number.isInteger(value) || value <= 0 || value > 100) {
throw new Error('REGISTRY_SEARCH_INVALID: limit must be an integer from 1 to 100');
}
return value;
}Default limit is 20. Hard maximum is 100. Outside that range fails the op with REGISTRY_SEARCH_INVALID.
registryRoot defaults to ${MATRIX_HOST_HOME}/registry if MATRIX_HOST_HOME is set, else ${cwd}/.matrix/registry. An explicit registryRoot argument must be inside MATRIX_HOST_HOME if that env var is set; otherwise the op fails with REGISTRY_ROOT_INVALID.
Example queries
bash
# All packages
matrix invoke system.packages registry.search '{}'
# Webapps only
matrix invoke system.packages registry.search '{"inferredKind":"webapp"}'
# Anything that handles chat ops
matrix invoke system.packages registry.search '{"accepts":"chat.*"}'
# Free-text search, with limit
matrix invoke system.packages registry.search '{"text":"director","limit":5}'
# Combined filters
matrix invoke system.packages registry.search '{"shells":"platform","capability":"inference","limit":10}'What search does NOT do
- Multi-version search. Only the latest version per package is searched.
- Liveness check. Whether the package is currently mounted is not considered. Use
system.catalog.catalog.searchfor live state. - Authorization filter. Whether the caller may install is not considered.
- Network fetch. Search reads only the local
discovery-index.json. It does not contact the Gitea registry.
See also
Source:
projects/matrix-3/packages/system-catalog/src/SystemPackageRegistryActor.ts:65-110.