Northlight
Plugin

Bundles and documents

Generate install archives and include agent-facing documents in a plugin through its `memory` array.

createFetchHandler builds and serves the install archive automatically. Use buildBundle when you need to inspect or save the generated bytes during an application build. Pass the same mount path where the handler is served.

import { writeFile } from 'node:fs/promises';
import { buildBundle } from '@north-light/plugin';
import { plugin } from './crtr-plugin.js';

const bundle = await buildBundle(plugin, { mountPath: '/crtr' });
await writeFile('commands.json', bundle.commandsJson);
await writeFile('acme.tar', bundle.tar);

buildBundle returns commandsJson, bundleJson, generated memory/ members, uncompressed tar bytes, and an ETag. It runs the manifest validator and throws ManifestInvalidError when the generated command manifest is invalid. buildCommandManifest(plugin, { mountPath }) returns the manifest object when archive bytes are not needed.

The tar has exactly bundle.json, commands.json, and memory/<name>.md members for the documents declared in the memory array of definePlugin. It does not contain plugin.json. For an endpoint install, crouter creates .crouter-plugin/plugin.json from the endpoint, --name, --auth-env, and archive hash.

Documents in the memory array

Add a memory array to definePlugin when the app needs to give agents reusable operating knowledge. Each entry has name, kind, whenAndWhyToRead, body, and optional unlisted and rules. name is a safe relative path without the .md suffix. The package writes the frontmatter required by the archive installer, so the author supplies no YAML.

whenAndWhyToRead is one routing sentence in the form When <circumstance>, this <kind> should be read because <payoff>. Keep the body to knowledge the agent needs to use the application. When the plugin is installed, crouter imports each entry as a public, read-only document owned by an app named after the plugin (an installed plugin is an app on the canvas), together with the generated commands. An agent reads it with crtr canvas read <plugin>/<name>, and whenAndWhyToRead becomes the document's preview. The memory array and whenAndWhyToRead are the plugin API's names; the result is an ordinary canvas document.

Loading a document on its own

A document without rules loads nowhere on its own: an agent reaches it only through a listing, crtr canvas search, or a read it was pointed at. Add rules when the document must reach an agent because of what that agent is doing. Each rule has the shape of a crtr doc write --rule rule:

  • on — boot (every session start), file-read, document-read, command or pre-command.
  • deliver — name, preview (the whenAndWhyToRead line) or content (the whole body).
  • match — globs the activity must match. It is required on every event except boot, which takes none. A file-read rule may give matchFrontmatter instead.
  • if — an optional condition on the receiving node over kind, mode, lifecycle, cwd, repo, profile and depth, in the matcher language crtr doc write --rule -h describes.
memory: [{
  name: 'texting',
  kind: 'knowledge',
  whenAndWhyToRead: 'When replying to a text conversation, this knowledge should be read because a reply that leaks a message label confuses the person reading it.',
  body: '# Texting\n\nNever put a message label such as `[m3]` in a text.\n',
  rules: [{ on: 'boot', deliver: 'content', if: { cwd: { matches: '^/apps/acme/' } } }],
}],

definePlugin rejects a rule the installer would drop. Every rule costs context in every node it reaches, so prefer the narrowest trigger, and keep a boot rule at content for short, always-relevant guidance. An installation's profile can lower what a plugin's documents deliver at boot.

On this page