Build a project-environment provider
A project-environment provider lets Terminay create and manage one kind of environment in a project: an SSH host, a VM, a cloud workspace, or your own service. The provider describes its host-rendered forms and implements the lifecycle work.
What you build
This guide registers a minimal preview provider. It saves an endpoint in a
profile, validates it, creates an available environment, and reports its status. Adapt the
runtime methods to call your service.
Declare the contribution
The manifest makes the provider visible to Terminay before extension code starts. The
contribution ID must correspond to the registered providerId.
{
"manifestVersion": 1,
"id": "acme.preview-environment",
"displayName": "Preview environments",
"api": "^1.2.0",
"engines": { "terminay": "^1.2.0", "node": ">=20" },
"entrypoint": "dist/index.js",
"permissions": ["secrets:resolve"],
"contributes": {
"projectEnvironments": [{
"id": "preview",
"displayName": "Preview environment",
"capabilities": ["terminal", "filesystem"],
"profileSave": { "createEnvironment": true }
}]
}
}
Use only the environment capabilities you can actually provide. profileSave is
opt-in: without it, saving a profile never creates an environment.
Register the definition and runtime
import {
defineExtension,
type ProviderRegistration,
} from '@terminay/extension-api';
const preview: ProviderRegistration = {
definition: {
providerId: 'preview',
displayName: 'Preview environment',
icon: 'cloud',
capabilities: ['terminal', 'filesystem'],
profileForm: {
id: 'preview-account',
title: 'Preview account',
submitLabel: 'Save account',
sections: [{
id: 'connection', title: 'Connection', fields: [{
id: 'endpoint', label: 'API endpoint', type: 'url', required: true,
}] }],
},
},
runtime: {
async testProfile({ values }) {
return typeof values.endpoint === 'string' ? [] : [{
fieldId: 'endpoint', code: 'required', message: 'Enter an API endpoint.',
}];
},
async resolveOptions() { return { options: [] }; },
async createEnvironment(request) {
return {
state: 'ready',
providerState: { endpoint: request.values.endpoint },
status: {
state: 'available',
defaultRoot: '/workspace',
revision: 1,
},
};
},
async resumeOperation() { throw new Error('No operation to resume.'); },
async getStatus(request) {
return { state: 'available', revision: 1 };
},
async invokeAction(request) {
return {
state: 'complete',
providerState: request.providerState,
status: { state: 'available', revision: 1 },
};
},
},
};
export default defineExtension({
activate(context) {
context.registerProjectEnvironmentProvider(preview);
},
}); profileForm and createForm are declarative. Terminay renders the
fields, validation, progress, confirmations, actions, and status cards; extensions do not
supply UI components or styles.
Implement the lifecycle
| Method | Use it for |
|---|---|
testProfile | Return field-level validation before a profile is saved. |
resolveOptions | Supply async options for a declared form source. |
createEnvironment | Create the environment and return ready or pending. |
resumeOperation | Continue a pending provisioning operation. |
getStatus | Return its current state, facts, actions, and revision. |
invokeAction | Run an action declared in the environment status card. |
updateEnvironment, deleteEnvironment | Optional update and deletion flows. |
invokeService | Optional environment-bound capability operation; it is not a general command API. |
Every callback receives a deadline, cancellation signal, and—for retried or concurrent mutations—an idempotency key and expected revision. Honour cancellation and use the revision to avoid overwriting a newer environment state.
Next steps
Add credentials or a dependent provider through scoped services. For the complete API surface, see the environment provider reference.