36 KiB
CLI Printing Press Plugin — Fusion Integration Architecture
Task: FN-3762 Date: 2026-05-10 Status: Design (v1) Dependencies: FN-3761 (research, delivered)
1. Summary
This document specifies how Fusion integrates cli-printing-press (a Go-based CLI generator) as a bundled first-party plugin. Key one-liners:
- Package name:
@fusion-plugin-examples/cli-printing-press - Manifest ID:
fusion-plugin-cli-printing-press - Directory:
plugins/fusion-plugin-cli-printing-press/ - Storage model: Plugin-owned SQLite tables via
onSchemaInithook (mirrorsplugins/fusion-plugin-roadmap/src/roadmap-schema.ts) - Dashboard views: Wizard, service list, service detail, CLI detail, run/test panel (5 views, plugin-owned)
- Runtime integration: Generated CLIs invoked as child processes via
promisify(exec)with timeout + maxBuffer; binaries discovered via PATH or explicit path config - Auth in v1: Non-OAuth only (api_key, bearer_token, header injection, env-var); OAuth deferred with reserved
auth.kind: "oauth"escape hatch - Upstream binary:
cli-printing-pressis a Go binary, not an npm package — spawned as subprocess, never imported as JS
2. Goals and Non-Goals
Goals
- Guided wizard for defining external services and generating CLIs from OpenAPI specs or documentation URLs
- Plugin-owned dashboard views for managing generated service CLIs
- Plugin-owned dashboard actions to manually run/test generated CLIs
- Generated CLIs available in workflow steps (script mode)
- Generated CLIs available in executor runtime environments (PATH injection)
- Non-OAuth credential management with secrets in SQLite, redacted in API responses
- Bundled as a first-party plugin shipped with the published
@runfusion/fusionCLI
Non-Goals (v1)
- OAuth flows — upstream supports OAuth2 (authorization_code, client_credentials) but v1 does not implement the browser redirect/callback dance.
auth.kind: "oauth"is reserved in the schema but returns an error if used. - Mesh credential propagation — deferred to FN-3707 or equivalent follow-up task
- Browser-sniff / traffic-analysis generation — v1 scopes to
--spec(OpenAPI URL/file) and--docs(documentation URL) input modes only --planmode — markdown-plan-driven generation deferred to v2- In-process JS embedding of cli-printing-press — it is a Go binary; subprocess boundary is mandatory
3. Packaging and Registration
3.1 Package Identity
| Field | Value |
|---|---|
| Package name | @fusion-plugin-examples/cli-printing-press |
| Manifest ID | fusion-plugin-cli-printing-press |
| Directory | plugins/fusion-plugin-cli-printing-press/ |
| Private | true |
| Type | module |
Mirrors plugins/fusion-plugin-roadmap/package.json exactly: @fusion/core and @fusion/plugin-sdk as workspace dependencies (not peerDependencies).
3.2 Three Registration Points
The plugin must be registered at exactly three points to ship in the published CLI:
-
pnpm-workspace.yaml— add"plugins/fusion-plugin-cli-printing-press"to the explicit packages list (alongside other per-plugin entries). No glob — each plugin is enumerated individually. -
packages/cli/src/plugins/bundled-plugin-install.ts— add"fusion-plugin-cli-printing-press"to theBUNDLED_PLUGIN_IDSarray. -
packages/cli/tsup.config.ts— add acpSynccopy block in theonSuccesshandler that copiesmanifest.json,package.json, andsrc/todist/plugins/fusion-plugin-cli-printing-press/. Mirrors the existing roadmap block exactly. Does NOT add the plugin toRUNTIME_PLUGIN_IDS— it is not a runtime plugin and does not need esbuild bundling.
All three edits are already in place from Step 1 of this task.
3.3 Bundling and Import Strategy
- The plugin uses static
import { definePlugin } from "@fusion/plugin-sdk"— no dynamic imports. - tsup's
noExternal: [/^@fusion\//]inlines@fusion/*imports (perAGENTS.md"Importing across@fusion/*packages"). - The upstream
cli-printing-pressGo binary is never bundled — it is invoked as a child process at runtime. - Never reintroduce the
engineModule = "@fusion/engine"+await import(/* @vite-ignore */ engineModule)anti-pattern.
3.4 Upstream Binary Integration Mode
Decision: spawn as child process.
Rationale (grounded in FN-3761 research):
cli-printing-pressis a Go binary requiringgo 1.26.3+toolchain to build from source- No npm package exists on the registry
- Generation is a heavyweight operation (template rendering, Go validation gates)
- Must use
promisify(exec)withtimeoutandmaxBufferperAGENTS.md"Engine Process Rules" — noexecSync
Binary availability: v1 requires the user to have printing-press installed and accessible on PATH (or configured via plugin settings). The plugin's onLoad hook should probe for the binary and log a warning if missing. Future tasks may add a setup hook for automated installation.
3.5 tsup Copy Block (Reference Implementation)
The copy block in packages/cli/tsup.config.ts uses cpSync (not esbuild bundling):
const cliPrintingPressPluginSrc = join(__dirname, "..", "..", "plugins", "fusion-plugin-cli-printing-press");
const cliPrintingPressPluginDest = join(__dirname, "dist", "plugins", "fusion-plugin-cli-printing-press");
// ...inside onSuccess:
if (existsSync(cliPrintingPressPluginDest)) rmSync(cliPrintingPressPluginDest, { recursive: true, force: true });
if (existsSync(cliPrintingPressPluginSrc)) {
mkdirSync(cliPrintingPressPluginDest, { recursive: true });
cpSync(join(cliPrintingPressPluginSrc, "manifest.json"), join(cliPrintingPressPluginDest, "manifest.json"));
cpSync(join(cliPrintingPressPluginSrc, "package.json"), join(cliPrintingPressPluginDest, "package.json"));
cpSync(join(cliPrintingPressPluginSrc, "src"), join(cliPrintingPressPluginDest, "src"), { recursive: true });
console.log("Copied cli-printing-press plugin to dist/plugins/fusion-plugin-cli-printing-press/");
}
4. Storage and Config Model
4.1 Storage Decision: SQLite via onSchemaInit
Decision: Plugin-owned SQLite tables in the project's .fusion/fusion.db, created via the onSchemaInit hook. Mirrors the roadmap plugin pattern (plugins/fusion-plugin-roadmap/src/roadmap-schema.ts).
Rationale:
- Consistent with
docs/storage.md— structured metadata in SQLite, blobs on filesystem - Leverages Fusion's existing WAL-mode SQLite infrastructure
- Per-project isolation is automatic (each project has its own
.fusion/fusion.db) - Credentials can be stored with clear redaction rules at the route layer
Filesystem storage for generated artifacts: Generated CLI projects are stored under .fusion/plugins/cli-printing-press/library/<service-name>/. This mirrors upstream's default ~/printing-press/library/<name> but scopes it to the Fusion project.
4.2 Schema
Table: cpp_services
External service definitions — the user-configured inputs for CLI generation.
CREATE TABLE IF NOT EXISTS cpp_services (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
inputMode TEXT NOT NULL DEFAULT 'spec',
specSource TEXT,
docUrl TEXT,
transport TEXT NOT NULL DEFAULT 'standard',
clientPattern TEXT NOT NULL DEFAULT 'rest',
status TEXT NOT NULL DEFAULT 'defined',
generatedCliPath TEXT,
generatedCliVersion TEXT,
lastGeneratedAt TEXT,
lastGenerationLog TEXT,
createdAt TEXT NOT NULL,
updatedAt TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idxCppServicesStatus
ON cpp_services(status, createdAt, id);
| Column | Type | Description |
|---|---|---|
id |
TEXT | UUID primary key |
name |
TEXT | Human-readable service name (max 200 chars) |
description |
TEXT | Optional description (max 5000 chars) |
inputMode |
TEXT | "spec" or "docs" — which upstream input mode |
specSource |
TEXT | URL or local path to OpenAPI spec (used when inputMode=spec) |
docUrl |
TEXT | URL to documentation (used when inputMode=docs) |
transport |
TEXT | "standard", "browser-http", etc. (v1: "standard" only) |
clientPattern |
TEXT | "rest", "graphql", etc. (v1: "rest" only) |
status |
TEXT | "defined", "generating", "generated", "error" |
generatedCliPath |
TEXT | Absolute path to generated CLI project root |
generatedCliVersion |
TEXT | Upstream generation version/hash for change detection |
lastGeneratedAt |
TEXT | ISO timestamp of last successful generation |
lastGenerationLog |
TEXT | Stdout/stderr from last generation (truncated to 10KB) |
createdAt |
TEXT | ISO timestamp |
updatedAt |
TEXT | ISO timestamp |
Table: cpp_credentials
Non-OAuth credential storage per service. One row per credential type per service.
CREATE TABLE IF NOT EXISTS cpp_credentials (
id TEXT PRIMARY KEY,
serviceId TEXT NOT NULL,
kind TEXT NOT NULL,
name TEXT NOT NULL,
envVarName TEXT,
envVarValue TEXT NOT NULL,
headerName TEXT,
headerValue TEXT,
isSensitive INTEGER NOT NULL DEFAULT 1,
createdAt TEXT NOT NULL,
updatedAt TEXT NOT NULL,
FOREIGN KEY (serviceId) REFERENCES cpp_services(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idxCppCredentialsService
ON cpp_credentials(serviceId, kind, createdAt, id);
| Column | Type | Description |
|---|---|---|
id |
TEXT | UUID primary key |
serviceId |
TEXT | Foreign key to cpp_services.id |
kind |
TEXT | "api_key", "bearer_token", "header", "env_var", "none", or "oauth" (unsupported, reserved) |
name |
TEXT | Human-readable label (e.g., "Stripe API Key") |
envVarName |
TEXT | Environment variable name to inject (e.g., STRIPE_API_KEY) |
envVarValue |
TEXT | The secret value (encrypted at rest in future; plaintext in v1) |
headerName |
TEXT | HTTP header name (for kind="header") |
headerValue |
TEXT | HTTP header value (sensitive) |
isSensitive |
INTEGER | Whether to redact in API responses (default: 1) |
createdAt |
TEXT | ISO timestamp |
updatedAt |
TEXT | ISO timestamp |
4.3 TypeScript Types
// src/types.ts
export type ServiceInputMode = "spec" | "docs";
export type ServiceTransport = "standard"; // v1: only standard
export type ServiceClientPattern = "rest"; // v1: only rest
export type ServiceStatus = "defined" | "generating" | "generated" | "error";
export type CredentialKind = "api_key" | "bearer_token" | "header" | "env_var" | "none" | "oauth";
export interface ExternalService {
id: string;
name: string;
description?: string;
inputMode: ServiceInputMode;
specSource?: string;
docUrl?: string;
transport: ServiceTransport;
clientPattern: ServiceClientPattern;
status: ServiceStatus;
generatedCliPath?: string;
generatedCliVersion?: string;
lastGeneratedAt?: string;
lastGenerationLog?: string;
createdAt: string;
updatedAt: string;
}
export interface ServiceCredential {
id: string;
serviceId: string;
kind: CredentialKind;
name: string;
envVarName?: string;
envVarValue: string; // redacted in GET responses
headerName?: string;
headerValue?: string; // redacted in GET responses
isSensitive: boolean;
createdAt: string;
updatedAt: string;
}
export interface CreateServiceInput {
name: string;
description?: string;
inputMode: ServiceInputMode;
specSource?: string;
docUrl?: string;
transport?: ServiceTransport;
clientPattern?: ServiceClientPattern;
}
export interface UpdateServiceInput {
name?: string;
description?: string;
inputMode?: ServiceInputMode;
specSource?: string;
docUrl?: string;
transport?: ServiceTransport;
clientPattern?: ServiceClientPattern;
}
export interface CreateCredentialInput {
kind: CredentialKind;
name: string;
envVarName?: string;
envVarValue: string;
headerName?: string;
headerValue?: string;
isSensitive?: boolean;
}
export interface GenerationResult {
success: boolean;
cliPath: string;
version: string;
log: string;
error?: string;
}
4.4 Credential Storage and Security
Where secrets live: In the plugin's SQLite tables (.fusion/fusion.db) within the cpp_credentials table.
Redaction rules:
- GET endpoints that return credentials MUST replace
envVarValueandheaderValuewith"***REDACTED***"whenisSensitive === true - The full values are only available to the generation runner and executor runtime, never to dashboard API consumers
- Redaction happens at the route layer in
src/routes/cpp-routes.ts, not in the store
Encryption at rest: v1 stores credentials in plaintext within SQLite. A follow-up task should add encryption using a project-local key. Document this as a known limitation.
Mesh propagation: Explicitly out of scope for v1. Refer to FN-3707 (Sync credentials across mesh nodes) for future work.
OAuth escape hatch: kind: "oauth" is a reserved value. If a user attempts to create a credential with kind: "oauth", the route returns 400 Bad Request: OAuth credentials are not supported in v1. Use api_key, bearer_token, header, or env_var.
5. Dashboard Surface
5.1 Dashboard Views
Five plugin-owned dashboard views, each registered in manifest.json dashboardViews[]:
| viewId | Label | Icon | Placement | Order | Component Path |
|---|---|---|---|---|---|
cpp-wizard |
Add Service | Plus |
modal | — | ./dashboard/WizardView |
cpp-services |
CLI Services | Terminal |
primary | 40 | ./dashboard/ServicesView |
cpp-service-detail |
Service Detail | — | panel | — | ./dashboard/ServiceDetailView |
cpp-cli-detail |
CLI Detail | — | panel | — | ./dashboard/CliDetailView |
cpp-run-test |
Run / Test | Play |
modal | — | ./dashboard/RunTestView |
The wizard and run/test views are modals triggered from the services list or service detail views, not primary navigation destinations. The services list (cpp-services) is the primary navigation entry.
5.2 Route Design
All routes are under /api/plugins/fusion-plugin-cli-printing-press/....
Route registration uses createCliPrintingPressPluginRoutes() returning PluginRouteDefinition[] from @fusion/core, attached via definePlugin({ routes: ... }). The dashboard host wraps these in Express (see packages/dashboard/src/plugin-routes.ts lines 280–340) — the plugin does NOT export its own Express router.
Project scoping uses resolveProjectId(req) reading query.projectId / body.projectId, mirroring the roadmap pattern.
Service Routes
| Method | Path | Description |
|---|---|---|
| GET | /services |
List all services |
| POST | /services |
Create a new service |
| GET | /services/:id |
Get service with credentials (redacted) |
| PATCH | /services/:id |
Update service definition |
| DELETE | /services/:id |
Delete service and its credentials |
| POST | /services/:id/generate |
Trigger CLI generation |
| POST | /services/:id/regenerate |
Regenerate CLI (with force) |
| GET | /services/:id/generation-status |
Poll generation status |
Credential Routes
| Method | Path | Description |
|---|---|---|
| GET | /services/:id/credentials |
List credentials for a service (redacted) |
| POST | /services/:id/credentials |
Add a credential |
| PATCH | /credentials/:credId |
Update a credential |
| DELETE | /credentials/:credId |
Delete a credential |
Run/Test Routes
| Method | Path | Description |
|---|---|---|
| POST | /services/:id/run |
Execute a generated CLI command |
| POST | /services/:id/test |
Run a test command (e.g., --help, version, doctor) |
| GET | /services/:id/run-history |
List recent run results |
Health/Probe Route
| Method | Path | Description |
|---|---|---|
| GET | /health |
Check if printing-press binary is available and return version |
5.3 Component Files
src/
├── dashboard/
│ ├── WizardView.tsx # Guided wizard: name, input mode, spec/docs URL, auth config
│ ├── ServicesView.tsx # List view: cards for all services with status badges
│ ├── ServiceDetailView.tsx # Detail: service config + credential list + generation status
│ ├── CliDetailView.tsx # Detail: generated CLI info, file tree preview, version
│ └── RunTestView.tsx # Modal: command input, run button, output log viewer
├── routes/
│ └── cpp-routes.ts # createCliPrintingPressPluginRoutes()
├── store/
│ └── cpp-store.ts # CppStore class (mirrors RoadmapStore pattern)
├── generator/
│ └── generate.ts # Generation runner (spawns printing-press subprocess)
├── types.ts # TypeScript type definitions
├── schema.ts # ensureCliPrintingPressSchema(db)
└── index.ts # definePlugin({...})
5.4 Styling Rules
All dashboard views MUST follow the AGENTS.md "Dashboard UI Styling Guide":
- Design tokens only (
var(--space-md),var(--text-muted),var(--radius-md), etc.) - Reuse
.btn,.card,.input,.modalprimitives fromstyles.css - Status-color tokens for service status badges
- Mobile breakpoints at 768px
- No hardcoded pixel values, colors, or rgba — use
color-mixfor translucent backgrounds - Each component imports its co-located CSS file (
import "./WizardView.css")
5.5 Authentication/Authorization
Plugin routes inherit dashboard host auth — no plugin-level middleware required.
Confirmed by reading packages/dashboard/src/plugin-routes.ts: plugin-defined routes are wrapped in Express handlers at lines 280–340 with no additional auth middleware. The host's auth applies to all /api/plugins/* routes.
Risk: Credential-write endpoints (POST/PATCH/DELETE on /credentials) rely on whatever the host enforces. If the dashboard binds to a non-loopback address, downstream FN-3764 MUST surface a warning in the credential management UI. Document this as a security consideration in the credential view implementation.
6. Executor Runtime Integration
6.1 Generated CLI Availability in Worktrees
Decision: Explicit PATH injection.
When an executor session starts for a task, the plugin's runtime integration adds the directory containing generated CLI binaries to the worktree's PATH environment. This happens via:
- The plugin exposes a
toolsregistration (viadefinePlugin({ tools: [...] })) that lists available generated CLIs - The executor's runtime setup probes registered tools and adds their paths to the subprocess environment
- Alternatively, generated CLIs are symlinked into the worktree's
.fusion/bin/directory which is added to PATH
v1 simplification: Rather than implementing full tool registration, v1 uses a simpler approach:
- Generated CLIs are built as Go binaries and placed in
.fusion/plugins/cli-printing-press/bin/ - The plugin exposes a
getBinPaths()method that the executor can query - The executor adds these paths to the PATH environment when spawning task processes
- This avoids modifying
packages/engine— the plugin exposes its capabilities through existing plugin interfaces
6.2 CLI Invocation Model
All CLI invocations MUST use promisify(exec) with timeout and maxBuffer:
import { exec } from "node:child_process";
import { promisify } from "node:util";
const execAsync = promisify(exec);
const { stdout, stderr } = await execAsync(command, {
cwd: worktreePath,
timeout: 120_000,
maxBuffer: 10 * 1024 * 1024,
env: { ...process.env, ...credentialEnvVars },
});
Never use execSync for any CLI invocation (per AGENTS.md "Engine Process Rules").
6.3 Generation Lifecycle
- On wizard completion: Service created with
status: "defined" - On "Generate" button click: Status →
"generating", spawnprinting-press generate --spec <url> --name <name> --output <path>viapromisify(exec) - On generation success: Status →
"generated", recordgeneratedCliPath,generatedCliVersion,lastGeneratedAt,lastGenerationLog - On generation failure: Status →
"error", record error inlastGenerationLog - Regeneration: Uses
--forceflag to overwrite existing output. Detects stale artifacts by comparing upstream version hash with storedgeneratedCliVersion.
Concurrent generation: Only one generation per service at a time. The status: "generating" field acts as a lock — the /generate endpoint returns 409 Conflict if the service is already in "generating" state.
6.4 Generated Artifact Location
.fusion/
└── plugins/
└── cli-printing-press/
├── library/ # Generated Go project trees
│ └── stripe/
│ ├── cmd/
│ ├── internal/
│ ├── go.mod
│ └── Makefile
└── bin/ # Built CLI binaries (optional, post-build)
└── stripe-pp-cli
The library/ directory mirrors upstream's default output structure. The bin/ directory is for pre-built binaries if the plugin builds generated CLIs automatically.
6.5 Credential Injection Model
Decision: Environment variables only.
When the executor or run/test panel invokes a generated CLI:
- Load credentials for the service from
cpp_credentialstable - Construct an
envobject mappingenvVarName→envVarValuefor each credential - Pass as
envoption toexecAsync()— merged overprocess.env - Secrets MUST NOT appear in: command arguments, run-audit logs, or API responses
The run-audit integration (per docs/architecture.md) must redact environment variable values in the fs:exec domain when the variable name matches a credential's envVarName.
6.6 Security Boundary
Generated CLIs run inside the worktree with the same permissions as other executor commands. No escalation, no privileged access.
Command-injection guards on wizard inputs:
- Service names are validated against
^[a-z][a-z0-9-]*$(lowercase alphanumeric + hyphens) - Spec URLs are validated as HTTP/HTTPS only
- No user input flows directly into shell commands — all values are passed as arguments via
execFile-style argument arrays where possible, or properly escaped viashell: false
7. Workflow Step Integration
7.1 Step Types
Generated CLIs become available as "script" mode workflow steps. The plugin contributes a workflow step template that:
- Accepts a service ID and CLI subcommand as parameters
- Resolves the generated CLI binary path
- Injects credentials as environment variables
- Executes via
promisify(exec)with timeout + maxBuffer
7.2 Step Manifest Shape
interface CppWorkflowStep {
type: "script";
pluginId: "fusion-plugin-cli-printing-press";
label: string; // e.g., "Run Stripe CLI: validate"
config: {
serviceId: string; // References cpp_services.id
command: string; // Subcommand to run (e.g., "validate", "doctor")
timeout?: number; // Override default timeout (ms)
};
}
This is registered via definePlugin({ workflowStepTemplates: [...] }) following docs/PLUGIN_AUTHORING.md Section 16.
7.3 Execution Flow
- Workflow step executor resolves the step template by
pluginId - Delegates to the plugin's registered step handler
- Handler loads the service, resolves binary path, injects credentials
- Runs via
promisify(exec)with appropriate timeout - Returns
{ success: boolean, stdout: string, stderr: string }
8. Credentials and Security Model
8.1 v1 Credential Kinds
| Kind | Env Var | Description |
|---|---|---|
api_key |
✅ | API key passed as env var |
bearer_token |
✅ | Bearer token passed as env var |
header |
✅ | Custom HTTP header name + value |
env_var |
✅ | Arbitrary env var with name + value |
none |
— | No authentication needed |
oauth |
❌ | Reserved, unsupported in v1 |
8.2 Credential Lifecycle
- Create: Via wizard step or service detail credential form. Stored in
cpp_credentialstable. - Read: GET endpoints return redacted values (
"***REDACTED***") for sensitive fields. - Update: PATCH endpoint accepts new values; overwrites in SQLite.
- Delete: DELETE endpoint removes the credential row.
- Cascade: Deleting a service cascades to all its credentials (FK
ON DELETE CASCADE).
8.3 Executor Credential Access
The executor runtime accesses credentials through the plugin's store:
// In the generation runner or run/test handler:
const credentials = cppStore.getCredentialsForService(serviceId);
const env = Object.fromEntries(
credentials.map(c => [c.envVarName, c.envVarValue])
);
Credentials are loaded at invocation time, never cached long-term.
8.4 Audit and Logging
- Run-audit events: Generation and CLI execution emit
fs:execaudit events. Secret values in environment variables are redacted. - Generation logs:
lastGenerationLogincpp_servicescaptures stdout/stderr but the route layer must scrub credential patterns before returning. - Dashboard audit: No separate audit trail beyond what Fusion's run-audit system already provides.
8.5 OAuth Deferral
OAuth is explicitly deferred from v1 with the following escape hatch:
kind: "oauth"is a valid enum value in the TypeScript type- The
cpp_credentialstable acceptskind = "oauth" - The route layer rejects creation with
400 Bad Requestwhenkind === "oauth" - When OAuth is implemented in a future version, the schema is already ready — only the route validation and UI need updating
9. Cross-Cutting Concerns
9.1 Plugin Lifecycle
| Hook | Implementation |
|---|---|
onSchemaInit |
ensureCliPrintingPressSchema(db) — creates cpp_services and cpp_credentials tables |
onLoad |
Probe for printing-press binary, log warning if not found, emit cpp:loaded event |
onUnload |
No persistent state to clean up |
Settings registration: The plugin registers a settings schema with fields:
printingPressBinaryPath: Override path toprinting-pressbinary (default: auto-detect from PATH)defaultOutputDir: Override output directory (default:.fusion/plugins/cli-printing-press/library/)generationTimeout: Timeout for generation commands in ms (default: 120000)
Config migration: Not needed in v1. Future versions can add a onSettingsMigrate hook if schema changes.
Hot-reload: Follows existing plugin-hot-reload.test.ts patterns. The plugin's store is stateless (reads from SQLite on each request), so hot-reload is safe.
9.2 Telemetry and Run-Audit
| Action | Audit Domain | Event |
|---|---|---|
| Service create/update/delete | db |
task:create, task:update (via plugin store) |
| CLI generation | fs |
fs:exec (printing-press subprocess invocation) |
| CLI run/test | fs |
fs:exec (generated CLI invocation) |
| Credential write | db |
db:write (credential row creation/update) |
9.3 Mesh / Multi-Project Considerations
Decision: Per-project plugin DB.
Each Fusion project has its own .fusion/fusion.db with its own cpp_services and cpp_credentials tables. This means:
- Services and credentials are isolated per project
- No cross-project sharing in v1
- Mesh propagation of credentials is deferred to FN-3707
This aligns with the existing pattern where each project has its own task store and plugin state.
9.4 Test Strategy Summary (for FN-3769)
| Category | Test Files | Description |
|---|---|---|
| Unit | src/__tests__/store.test.ts |
CppStore CRUD operations with in-memory SQLite |
| Unit | src/__tests__/generator.test.ts |
Generation runner with mocked exec |
| Unit | src/__tests__/routes.test.ts |
Route handler tests with mocked store |
| Plugin load | src/__tests__/manifest.test.ts |
Manifest validation + plugin loads as no-op (already exists) |
| View | src/__tests__/WizardView.test.tsx |
Wizard flow with mocked API |
| View | src/__tests__/ServicesView.test.tsx |
Service list rendering |
| Integration | src/__tests__/generation-integration.test.ts |
End-to-end generation with real printing-press (optional, gated on binary availability) |
| Workflow step | src/__tests__/workflow-step.test.ts |
Step template execution with mocked CLI |
Reference test patterns:
packages/core/src/__tests__/plugin-store.test.ts— plugin registration/loadingpackages/core/src/__tests__/plugin-hot-reload.test.ts— hot-reload behaviorpackages/core/src/__tests__/plugin-types.test.ts— type validation
9.5 Open Questions (Left to Downstream Tasks)
- FN-3763 (Wizard): Exact wizard step sequence and form field validation UX
- FN-3763 (Wizard): Whether to offer "test connection" during wizard before saving
- FN-3764 (Management views): Real-time generation status polling interval and UI
- FN-3764 (Management views): Credential form UX (masked inputs, reveal toggle)
- FN-3765 (Run/test): Output log rendering (ANSI color support, streaming vs batch)
- FN-3766 (Storage): Whether to add encryption-at-rest for credentials in v1 or defer
- FN-3766 (Storage): Migration strategy if schema changes between plugin versions
- FN-3767 (Executor runtime): Exact mechanism for PATH injection (plugin tools API vs direct worktree setup)
- FN-3767 (Executor runtime): Whether to auto-build generated Go CLIs into binaries or require user to build
- FN-3768 (Workflow steps): Whether to support prompt-mode steps or only script-mode in v1
- FN-3770 (Changeset): Whether to bump
@runfusion/fusionas minor (new bundled plugin) or patch
10. Mapping to Downstream Tasks
FN-3763: Dashboard Wizard View
Inputs from this design:
- Section 5.1:
cpp-wizardview definition (viewId, placement, component path) - Section 5.3:
src/dashboard/WizardView.tsxfile location - Section 4.3:
CreateServiceInputTypeScript type - Section 4.4: Credential creation via
CreateCredentialInputtype - Section 8.1: Supported credential kinds and OAuth rejection
- Section 5.4: CSS styling rules (tokens, primitives, co-located CSS)
- Route:
POST /servicesandPOST /services/:id/credentials
Deliverable: WizardView.tsx, WizardView.css, wizard modal trigger from services list. Must implement multi-step wizard: service name → input mode selection → spec/docs URL → auth configuration → review → create.
FN-3764: Management Views (Service List, Detail, CLI Detail)
Inputs from this design:
- Section 5.1:
cpp-services,cpp-service-detail,cpp-cli-detailview definitions - Section 5.3: Component file paths
- Section 5.2: All service and credential routes
- Section 4.2: Schema for
cpp_servicesandcpp_credentials - Section 5.5: Auth inheritance note (no plugin middleware needed)
- Section 5.4: CSS styling rules
Deliverable: ServicesView.tsx, ServiceDetailView.tsx, CliDetailView.tsx, plus route handlers in cpp-routes.ts, store methods in cpp-store.ts, and schema in schema.ts. Must also update manifest.json to add dashboardViews[] entries.
FN-3765: Run/Test Actions
Inputs from this design:
- Section 5.1:
cpp-run-testview definition - Section 6.2: CLI invocation via
promisify(exec)with timeout + maxBuffer - Section 6.5: Credential injection via environment variables
- Section 6.6: Security boundary (no escalation, command-injection guards)
- Section 5.2: Run/test routes (
POST /services/:id/run,POST /services/:id/test) - Section 8.4: Audit event emission with secret redaction
Deliverable: RunTestView.tsx, run/test route handlers, generation runner in generator/generate.ts.
FN-3766: Storage/Config Model Implementation
Inputs from this design:
- Section 4.2: Complete SQL schema (tables, indexes)
- Section 4.3: TypeScript type definitions
- Section 4.4: Credential storage and redaction rules
- Section 9.1:
onSchemaInithook withensureCliPrintingPressSchema(db) - Section 4.1: Storage decision rationale (SQLite + filesystem)
Deliverable: schema.ts (SQL schema creation), store/cpp-store.ts (CppStore class with CRUD methods), types.ts (TypeScript types). Must implement redaction in store methods or route layer.
FN-3767: Executor Runtime Integration
Inputs from this design:
- Section 6.1: PATH injection approach for generated CLI binaries
- Section 6.2:
promisify(exec)invocation pattern - Section 6.3: Generation lifecycle (status transitions, concurrent locking)
- Section 6.4: Generated artifact directory structure
- Section 6.5: Credential injection via env vars
- Section 6.6: Security boundary documentation
Deliverable: Runtime adapter or tool registration that makes generated CLIs available in executor sessions. Generation runner integration. PATH setup logic.
FN-3768: Workflow Step Integration
Inputs from this design:
- Section 7.1: Script-mode step type definition
- Section 7.2:
CppWorkflowStepmanifest shape - Section 7.3: Execution flow (resolve → inject → run → return)
- Section 6.2:
promisify(exec)pattern
Deliverable: Workflow step template registration via definePlugin({ workflowStepTemplates: [...] }), step execution handler.
FN-3769: Tests
Inputs from this design:
- Section 9.4: Complete test strategy table
- All sections: TypeScript types, routes, store methods, and component contracts to test against
Deliverable: Test files per the test strategy table. Must achieve meaningful coverage of store, routes, generator, and workflow steps.
FN-3770: Changeset
Inputs from this design:
- Section 3: Packaging and registration details
- Section 9.5, item 11: Minor vs patch decision
Deliverable: .changeset file describing the new bundled plugin addition. Likely a minor bump since it adds a new bundled plugin to the published package.
11. References
Fusion Source Files
plugins/fusion-plugin-roadmap/manifest.json— manifest pattern referenceplugins/fusion-plugin-roadmap/src/index.ts— definePlugin pattern with hooks, routes, dashboardViewsplugins/fusion-plugin-roadmap/src/roadmap-schema.ts— onSchemaInit patternplugins/fusion-plugin-roadmap/src/routes/roadmap-routes.ts— PluginRouteDefinition pattern, resolveProjectId, routeHandler wrapperplugins/fusion-plugin-roadmap/src/store/roadmap-store.ts— store patternplugins/fusion-plugin-hermes-runtime/src/index.ts— runtime plugin pattern with onLoad/onUnloadpackages/dashboard/src/plugin-routes.ts— dashboard host route mounting (auth inherited, no plugin middleware)packages/cli/src/plugins/bundled-plugin-install.ts— BUNDLED_PLUGIN_IDSpackages/cli/tsup.config.ts— cpSync copy block for bundled pluginspackages/plugin-sdk/src/index.ts— definePlugin, FusionPlugin typespackages/core/src/plugin-store.ts— plugin registrationpackages/core/src/types.ts— PluginRouteDefinition, PluginContext, PluginManifest
Documentation
docs/research/cli-printing-press.md— FN-3761 research (upstream behavior, config model, runtime requirements)docs/PLUGIN_AUTHORING.md— plugin authoring contract (manifest fields, hooks, routes, workflow steps)docs/storage.md— Fusion storage architecturedocs/workflow-steps.md— workflow step systemdocs/architecture.md— run-audit system, API referenceAGENTS.md— Package Structure, Importing rules, Storage Model, Engine Process Rules, Dashboard UI Styling Guide
Upstream References (from FN-3761)
- Repository:
https://github.com/mvanhorn/cli-printing-press - Examined commit:
ecb35ab0d585693aa48550f2087191a287b35f61 - Go version:
1.26.3+ - Auth types:
api_key,oauth2,bearer_token,cookie,composed,session_handshake,none - Input modes:
--spec,--docs,--plan(v1 uses--spec+--docs) - Transport modes:
standard,browser-http,browser-chrome,browser-chrome-h3(v1 usesstandardonly)