Project Settings Plugin Contributor Guide
AIditor Project Settings — Plugin Contributor Guide
Plugin Developer Agent Kit — documentation written for AI agents, readable by humans.
Audience: Plugin authors and AI agents adding a Project settings tab to a Jay Stack plugin.
Related guides: setup-guide.md (agent-kit handler), plugin-routes.md (devOnly routes), add-menu-guide.md (Add Menu — orthogonal surface). After jay-stack setup aiditor, see also agent-kit/plugin/aiditor-add-menu.md for AIditor runtime behavior (iframe, postMessage).
What is Project settings?
AIditor Project settings shows one tab per installed plugin that has a materialized discovery file under agent-kit/aiditor/settings/. Each tab embeds the plugin's settings route in an iframe (?_jay_embed=true).
This is separate from:
| Surface | Purpose | Plugin output |
|---|---|---|
| Add Menu | Attach agent context to a change request | agent-kit/aiditor/add-menu/*.yaml |
| Project settings | Configure the project; run backend ops from a UI | agent-kit/aiditor/settings/*.yaml + dev-only route |
| Headless on page | Site runtime | contracts + components |
Contributor checklist
Follow these steps in order. Skipping a step is the most common reason a tab never appears.
- Ship template in the npm package at
agent-kit/aiditor/settings.template.yaml(exact path — validated byjay-stack validate-plugin). - List
agent-kit/inpackage.jsonfilesso the template is included in the published npm tarball and available undernode_modulesafter install. - Declare
agentkitinplugin.yamland materialize the template in the agent-kit handler toagent-kit/aiditor/settings/<plugin-name>.yamlin the project root (not inside the package). - Declare a matching route in
plugin.yamlroutes[]withdevOnly: true(settings UIs are dev-server tooling). - Implement the settings page —
lib/pages/settings/page.jay-html+ page component; use plugin actions or jay-commands for mutations (never arbitrary shell). - Run
jay-stack agent-kitin the consuming project and confirm the YAML file exists. - Run
jay-stack validate-plugin— fix errors; heed warnings for missingdevOnlyor route mismatch.
Package layout
my-plugin/
├── agent-kit/
│ └── aiditor/
│ └── settings.template.yaml # shipped in package — source for copy/generate
├── lib/
│ ├── aiditor/
│ │ └── write-settings-contribution.ts # materialization helper
│ ├── pages/
│ │ └── settings/
│ │ ├── page.jay-html
│ │ └── page.ts
│ └── agentkit.ts # calls materialize + other agent-kit work
├── plugin.yaml
└── package.json # "files": [..., "agent-kit"]
Materialized output (project only, gitignored or committed per team policy):
<project-root>/agent-kit/aiditor/settings/my-plugin.yaml
Use <plugin>.generated.yaml only for fixture/dogfood plugins that intentionally differ from the shipped template name.
Settings template schema
# agent-kit/aiditor/settings.template.yaml
label: Media Manager # required — tab title in Project settings (text only in v1)
route: /my-plugin/settings # required — must match plugin.yaml routes[].path
pluginName: my-plugin # optional — defaults from output filename (my-plugin.yaml)
requires: # optional — tab blocked until dependency setup succeeds
- plugin: wix-server-client
status: configured # only "configured" is valid in v1
Validate locally with types from @jay-framework/plugin-validator:
import { validateAiditorSettingsFile } from '@jay-framework/plugin-validator';
plugin.yaml route
Settings pages are dev-only plugin routes. They are served on the dev server, hidden from page-navigation pickers that filter devOnly, and loaded by AIditor via explicit route URL.
name: my-plugin
agentkit: generateMyAgentKit
routes:
- path: /my-plugin/settings
jayHtml: ./lib/pages/settings/page.jay-html
component: myPluginSettingsPage
description: Project settings — configure my-plugin for this project
devOnly: true
See plugin-routes.md for route authoring, project override precedence, and standalone URL behavior.
Materialize in the agent-kit handler
Do not write settings YAML in the setup handler. Settings discovery is regenerated on jay-stack agent-kit, same as Add Menu catalogs.
import type {
PluginAgentKitContext,
PluginAgentKitResult,
} from '@jay-framework/stack-server-runtime';
import { materializeMyPluginAiditorSettings } from './aiditor/write-settings-contribution.js';
export async function generateMyAgentKit(
ctx: PluginAgentKitContext,
): Promise<PluginAgentKitResult> {
const created: string[] = [];
const settingsPath = materializeMyPluginAiditorSettings(ctx.projectRoot, ctx.force);
if (settingsPath) created.push(settingsPath);
// ... add-menu, references, etc.
return {
agentKitCreated: created,
message: created.length ? `Wrote ${created.join(', ')}` : 'Up to date',
};
}
Static copy vs generated YAML
| Case | Pattern |
|---|---|
| Fixed label + route | Copy settings.template.yaml → settings/<plugin>.yaml |
| Label/route depends on live data | Build YAML object in handler; write with yaml.stringify |
| Add Menu rebuild from settings UI | Write add-menu/<plugin>.generated.yaml only — never overwrite hand-authored add-menu/<plugin>.yaml |
Resolving the packaged template path (required)
Agent-kit handlers run from bundled dist/index.js in published packages. A fixed path.join(__dirname, '..') hop count breaks when the module lives under dist/ vs lib/ (Vitest) vs nested bundles.
Use a walk-up resolver until agent-kit/aiditor/settings.template.yaml exists under a parent directory:
import * as fs from 'node:fs';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';
export const AIDITOR_SETTINGS_OUTPUT_REL = 'agent-kit/aiditor/settings/my-plugin.yaml';
const SETTINGS_TEMPLATE_REL = 'agent-kit/aiditor/settings.template.yaml';
/**
* Resolve a file shipped inside the plugin package (agent-kit templates, etc.).
* Works from dist/index.js (production) and lib/aiditor/*.ts (Vitest).
*/
export function resolvePackagedAgentKitPath(
relativePath: string,
moduleUrl: string = import.meta.url,
): string | null {
let directory = path.dirname(fileURLToPath(moduleUrl));
for (let depth = 0; depth < 4; depth++) {
const candidate = path.join(directory, relativePath);
if (fs.existsSync(candidate)) {
return candidate;
}
const parent = path.dirname(directory);
if (parent === directory) break;
directory = parent;
}
return null;
}
export function writeAiditorSettingsContribution(
projectRoot: string,
templatePath: string,
force = false,
): string | null {
const outputPath = path.join(projectRoot, AIDITOR_SETTINGS_OUTPUT_REL);
if (fs.existsSync(outputPath) && !force) {
return null;
}
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
fs.copyFileSync(templatePath, outputPath);
return AIDITOR_SETTINGS_OUTPUT_REL;
}
export function materializeMyPluginAiditorSettings(
projectRoot: string,
force = false,
): string | null {
const templatePath = resolvePackagedAgentKitPath(SETTINGS_TEMPLATE_REL);
if (!templatePath) {
return null;
}
return writeAiditorSettingsContribution(projectRoot, templatePath, force);
}
Common mistakes
| Mistake | Symptom |
|---|---|
path.join(dirname(import.meta.url), '..', '..') from bundled code |
Walks to node_modules/@jay-framework/ — template not found, no settings YAML |
No client bundle (index.client.ts + vite build without --ssr) |
Settings iframe SSR works; Vite errors on hydrate — Failed to resolve import .../dist/index.client.js |
Writing YAML inside the package instead of ctx.projectRoot |
Tab missing in consumer project |
Route in template ≠ plugin.yaml routes[].path |
validate-plugin warning settings-route-missing; iframe 404 |
Missing devOnly: true |
settings-route-dev-only warning; route may appear in page pickers |
Template present but no agentkit handler |
settings-missing-agentkit-handler warning |
agent-kit/ not in package.json files |
Template missing after yarn add |
Reference implementations:
@jay-framework/design-system-validator—lib/aiditor/write-settings-contribution.ts(walk-up resolver)@jay-framework/wix-media— same pattern (prefer walk-up over single..fromdist/)
Settings page responsibilities
The embedded settings route should:
- About / onboarding — what the plugin does, which
config/files matter, links to agent-kit docs. - Run backend work via plugin actions (
makeJayAction) orjay-stack run <plugin>/<command>— notchild_processshell. - Write project-owned generated files only — e.g.
agent-kit/aiditor/add-menu/<plugin>.generated.yaml, reference data underagent-kit/references/. - Never collect API keys or OAuth secrets in settings forms — credentials belong in
setup→config/.
Notify AIditor when Add Menu catalog changes
After regenerating add-menu YAML from settings:
window.parent.postMessage({ type: 'aiditor:addMenuCatalogChanged' }, window.location.origin);
Optional — submit an agent task (explicit user click only)
window.parent.postMessage(
{
type: 'aiditor:submitAgentTask',
prompt: '…',
context: { pageRoute: '/', renderedUrl: window.location.origin },
},
window.location.origin,
);
Full iframe and discovery behavior: agent-kit/plugin/aiditor-add-menu.md (installed by jay-stack setup aiditor).
Validation (jay-stack validate-plugin)
When agent-kit/aiditor/settings.template.yaml exists, the validator:
- Parses schema (
label,route, optionalrequires,pluginName) - Warns if
routeis not inplugin.yamlroutes[] - Warns if matching route lacks
devOnly: true - Warns if template exists but
agentkitis not declared
Fix all errors before publish; treat warnings as required for AIditor-facing plugins.
Verification
In a project that depends on your plugin:
jay-stack agent-kit
ls agent-kit/aiditor/settings/my-plugin.yaml # must exist
jay-stack validate-plugin /path/to/my-plugin # or from package root
In AIditor: open Project settings — tab label matches label; iframe loads route with ?_jay_embed=true. If requires is set, tab stays blocked until dependency setup reports configured.
Dogfood fixture: jay-stack setup aiditor on a starter project with the plugin-settings-fixture local plugin (see aiditor package examples).
AI agent quick reference
When asked to "add Project settings for plugin X":
- Read this file and
plugin-routes.md(devOnly). - Add
agent-kit/aiditor/settings.template.yaml+ ensurepackage.jsonshipsagent-kit/. - Add
lib/aiditor/write-settings-contribution.tswithresolvePackagedAgentKitPath— do not hard-code..depth. - Wire materialization into existing
agentkithandler; return path inagentKitCreated. - Add
routes[]entry withdevOnly: trueand implement settings page. - Do not duplicate Add Menu schema here — use add-menu-guide.md for catalog items.
About this document
This page is part of the Jay Stack Agent Kit — documentation generated from the framework source and written primarily for AI agents. The language and structure are optimized for machine consumption — expect precise, specification-style prose rather than narrative documentation. Learn more about the Agent Kit →