LogoCyanPrint
PluginsReference

Plugin Project Structure

Standard directory layout for plugin projects

Plugin Project Structure

A typical plugin project has a simple structure:

index.ts
package.json
bun.lockb
tsconfig.json
Dockerfile
.dockerignore
.gitignore
cyan.yaml
README.md

Always commit bun.lockb to ensure reproducible builds across environments.

Required Files

index.ts

The entry point containing plugin logic:

index.ts
import { StartPluginWithLambda } from '@atomicloud/cyan-sdk';
StartPluginWithLambda(async (input) => {
const { directory, config } = input;
// Plugin logic here
return { directory };
});
main.py
from atomicloud_cyan_sdk import start_plugin_with_fn, PluginInput, PluginOutput
@start_plugin_with_fn
async def main(input: PluginInput) -> PluginOutput:
directory = input.directory
config = input.config
# Plugin logic here
return {'directory': directory}
Plugin.cs
using Atomicloud.Cyan.Sdk;
[PluginMain]
public static class Plugin
{
public static async Task<PluginOutput> Main(PluginInput input)
{
var directory = input.Directory;
var config = input.Config;
// Plugin logic here
return new PluginOutput { Directory = directory };
}
}

package.json

Dependencies for the plugin:

package.json
{
"name": "my-plugin",
"module": "index.ts",
"type": "module",
"devDependencies": {
"bun-types": "latest"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"@atomicloud/cyan-sdk": "latest"
}
}

Dockerfile

Container definition:

Dockerfile
FROM oven/bun:1.0.11
WORKDIR /app
LABEL cyanprint.dev=true
COPY package.json .
COPY bun.lockb .
RUN bun install
COPY . .
CMD [ "bun", "run", "index.ts" ]

Optional Files

.dockerignore

Exclude unnecessary files from the Docker build:

.dockerignore
node_modules
.idea
.vscode

README.md

Documentation for your plugin:

README.md
# My Plugin
Description of what the plugin does.
## Configuration
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `option1` | `boolean` | `true` | Description |
## Usage
plugins:
- name: org/my-plugin
config:
option1: false

cyan.yaml

Plugin metadata file:

cyan.yaml
username: your-username
name: my-plugin
description: A plugin that does something
project: https://example.com
source: https://example.com
tags: []
readme: README.md

Multiple Files

For complex plugins, organize code into multiple files:

index.ts
git.ts
npm.ts
files.ts
package.json
bun.lockb
tsconfig.json
Dockerfile

index.ts

index.ts
import { StartPluginWithLambda } from '@atomicloud/cyan-sdk';
import { initGit } from './lib/git';
import { installDeps } from './lib/npm';
import { createFiles } from './lib/files';
import type { PluginConfig } from './types/config';
StartPluginWithLambda(async (input) => {
const { directory, config } = input;
const cfg = config as PluginConfig;
await initGit(directory, cfg);
await installDeps(directory, cfg);
await createFiles(directory, cfg);
return { directory };
});
main.py
from atomicloud_cyan_sdk import start_plugin_with_fn, PluginInput, PluginOutput
from lib.git import init_git
from lib.npm import install_deps
from lib.files import create_files
@start_plugin_with_fn
async def main(input: PluginInput) -> PluginOutput:
directory = input.directory
config = input.config
await init_git(directory, config)
await install_deps(directory, config)
await create_files(directory, config)
return {'directory': directory}
Plugin.cs
using Atomicloud.Cyan.Sdk;
using System.Text.Json;
[PluginMain]
public static class Plugin
{
public static async Task<PluginOutput> Main(PluginInput input)
{
var directory = input.Directory;
var config = JsonSerializer.Deserialize<PluginConfig>(
input.Config.ToString()
);
await GitHelper.InitGit(directory, config);
await NpmHelper.InstallDeps(directory, config);
await FileHelper.CreateFiles(directory, config);
return new PluginOutput { Directory = directory };
}
}

lib/git.ts

lib/git.ts
import { $ } from 'bun';
export async function initGit(directory: string, config: { git?: boolean }) {
if (config.git) {
await $`git -C ${directory} init`.quiet();
await $`git -C ${directory} add .`.quiet();
}
}
lib/git.py
import subprocess
async def init_git(directory: str, config: dict):
if config.get('git'):
subprocess.run(['git', '-C', directory, 'init'], capture_output=True)
subprocess.run(['git', '-C', directory, 'add', '.'], capture_output=True)
GitHelper.cs
using System.Diagnostics;
public static class GitHelper
{
public static async Task InitGit(string directory, PluginConfig config)
{
if (config?.Git == true)
{
Process.Start("git", $"-C {directory} init")?.WaitForExit();
Process.Start("git", $"-C {directory} add .")?.WaitForExit();
}
}
}