@compilr-dev/sdk 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Canvas contract barrel — exposed at `@compilr-dev/sdk/canvas` (renderer-safe:
3
+ * pure types + validation, no node deps). Tools live in the platform module.
4
+ */
5
+ export * from './types.js';
6
+ export * from './validate.js';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Canvas contract barrel — exposed at `@compilr-dev/sdk/canvas` (renderer-safe:
3
+ * pure types + validation, no node deps). Tools live in the platform module.
4
+ */
5
+ export * from './types.js';
6
+ export * from './validate.js';
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Canvas — shared contract types.
3
+ *
4
+ * A Canvas is a persisted visual artifact the agent authors as HTML/SVG, with an
5
+ * optional controls manifest that surfaces live parameters ("Tweaks"). These
6
+ * types are the host-agnostic contract: the SDK owns them + the repository + the
7
+ * tools; hosts (Desktop) own the renderer.
8
+ *
9
+ * Intentionally dependency-free so this module can be exposed at the renderer-safe
10
+ * subpath `@compilr-dev/sdk/canvas` (the Desktop renderer needs the control types
11
+ * + validation without pulling node-only deps — same pattern as `.../errors`).
12
+ */
13
+ export type CanvasType = 'infographic' | 'carousel' | 'board';
14
+ /** A control's runtime value. */
15
+ export type ParamValue = string | number | boolean;
16
+ export interface SliderControl {
17
+ type: 'slider';
18
+ param: string;
19
+ label: string;
20
+ default: number;
21
+ min: number;
22
+ max: number;
23
+ step?: number;
24
+ unit?: string;
25
+ group?: string;
26
+ }
27
+ export interface NumberControl {
28
+ type: 'number';
29
+ param: string;
30
+ label: string;
31
+ default: number;
32
+ min?: number;
33
+ max?: number;
34
+ step?: number;
35
+ group?: string;
36
+ }
37
+ export interface ToggleControl {
38
+ type: 'toggle';
39
+ param: string;
40
+ label: string;
41
+ default: boolean;
42
+ group?: string;
43
+ }
44
+ export interface SelectControl {
45
+ type: 'select';
46
+ param: string;
47
+ label: string;
48
+ default: string;
49
+ options: string[];
50
+ group?: string;
51
+ }
52
+ export interface ColorControl {
53
+ type: 'color';
54
+ param: string;
55
+ label: string;
56
+ default: string;
57
+ group?: string;
58
+ }
59
+ export interface TextControl {
60
+ type: 'text';
61
+ param: string;
62
+ label: string;
63
+ default: string;
64
+ placeholder?: string;
65
+ group?: string;
66
+ }
67
+ export type Control = SliderControl | NumberControl | ToggleControl | SelectControl | ColorControl | TextControl;
68
+ export interface ControlManifest {
69
+ controls: Control[];
70
+ }
71
+ /**
72
+ * Full persisted canvas. `content` is the agent's raw HTML/SVG only — the CSP +
73
+ * bridge are injected by the host renderer, never stored.
74
+ */
75
+ export interface CanvasRecord {
76
+ id: number;
77
+ projectId: number;
78
+ type: CanvasType;
79
+ title: string;
80
+ content: string;
81
+ controls: ControlManifest;
82
+ /** Current tweak state; seeded from control defaults on create. */
83
+ values: Record<string, ParamValue>;
84
+ createdAt: Date;
85
+ updatedAt: Date;
86
+ }
87
+ /** Lightweight list entry (no content). */
88
+ export interface CanvasSummary {
89
+ id: number;
90
+ projectId: number;
91
+ type: CanvasType;
92
+ title: string;
93
+ updatedAt: Date;
94
+ }
95
+ export interface CreateCanvasInput {
96
+ projectId: number;
97
+ type: CanvasType;
98
+ title: string;
99
+ content: string;
100
+ controls?: ControlManifest;
101
+ values?: Record<string, ParamValue>;
102
+ }
103
+ export type UpdateCanvasInput = Partial<Pick<CanvasRecord, 'title' | 'content' | 'controls' | 'values'>>;
104
+ /** Seed a values map from a manifest's control defaults. */
105
+ export declare function seedValues(manifest: ControlManifest | undefined): Record<string, ParamValue>;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Canvas — shared contract types.
3
+ *
4
+ * A Canvas is a persisted visual artifact the agent authors as HTML/SVG, with an
5
+ * optional controls manifest that surfaces live parameters ("Tweaks"). These
6
+ * types are the host-agnostic contract: the SDK owns them + the repository + the
7
+ * tools; hosts (Desktop) own the renderer.
8
+ *
9
+ * Intentionally dependency-free so this module can be exposed at the renderer-safe
10
+ * subpath `@compilr-dev/sdk/canvas` (the Desktop renderer needs the control types
11
+ * + validation without pulling node-only deps — same pattern as `.../errors`).
12
+ */
13
+ /** Seed a values map from a manifest's control defaults. */
14
+ export function seedValues(manifest) {
15
+ const values = {};
16
+ if (!manifest)
17
+ return values;
18
+ for (const c of manifest.controls)
19
+ values[c.param] = c.default;
20
+ return values;
21
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Canvas controls-manifest validation. Dependency-free (renderer-safe subpath).
3
+ */
4
+ import type { ControlManifest } from './types.js';
5
+ export interface ManifestValidationResult {
6
+ ok: boolean;
7
+ errors: string[];
8
+ }
9
+ /**
10
+ * Validate a controls manifest: array shape, unique + well-formed param names,
11
+ * and per-type default sanity (in range / in options / correct primitive).
12
+ */
13
+ export declare function validateControlManifest(manifest: ControlManifest): ManifestValidationResult;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Canvas controls-manifest validation. Dependency-free (renderer-safe subpath).
3
+ */
4
+ /** Param names must be usable as a CSS custom property suffix and a JS key. */
5
+ const PARAM_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
6
+ const HEX_RE = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
7
+ /**
8
+ * Validate a controls manifest: array shape, unique + well-formed param names,
9
+ * and per-type default sanity (in range / in options / correct primitive).
10
+ */
11
+ export function validateControlManifest(manifest) {
12
+ const errors = [];
13
+ // Treat the manifest as untrusted (agent JSON), not the static type.
14
+ const rawControls = manifest.controls;
15
+ if (!Array.isArray(rawControls)) {
16
+ return { ok: false, errors: ['manifest.controls must be an array'] };
17
+ }
18
+ const seen = new Set();
19
+ for (const raw of rawControls) {
20
+ const param = raw.param;
21
+ if (typeof param !== 'string' || !PARAM_RE.test(param)) {
22
+ errors.push(`invalid param name "${String(param)}" (must match ${PARAM_RE.source})`);
23
+ }
24
+ else if (seen.has(param)) {
25
+ errors.push(`duplicate param "${param}"`);
26
+ }
27
+ if (typeof param === 'string')
28
+ seen.add(param);
29
+ validateDefault(raw, errors);
30
+ }
31
+ return { ok: errors.length === 0, errors };
32
+ }
33
+ function validateDefault(control, errors) {
34
+ const c = control;
35
+ const p = c.param;
36
+ switch (c.type) {
37
+ case 'slider': {
38
+ const { min, max, default: d } = c;
39
+ if (typeof min === 'number' && typeof max === 'number' && min > max) {
40
+ errors.push(`${p}: min ${String(min)} > max ${String(max)}`);
41
+ }
42
+ if (typeof d !== 'number') {
43
+ errors.push(`${p}: slider default must be a number`);
44
+ }
45
+ else if (typeof min === 'number' && typeof max === 'number' && (d < min || d > max)) {
46
+ errors.push(`${p}: default ${String(d)} out of range [${String(min)}, ${String(max)}]`);
47
+ }
48
+ break;
49
+ }
50
+ case 'number': {
51
+ const { min, max, default: d } = c;
52
+ if (typeof d !== 'number') {
53
+ errors.push(`${p}: number default must be a number`);
54
+ }
55
+ else {
56
+ if (typeof min === 'number' && d < min)
57
+ errors.push(`${p}: default < min`);
58
+ if (typeof max === 'number' && d > max)
59
+ errors.push(`${p}: default > max`);
60
+ }
61
+ break;
62
+ }
63
+ case 'toggle':
64
+ if (typeof c.default !== 'boolean')
65
+ errors.push(`${p}: toggle default must be boolean`);
66
+ break;
67
+ case 'select': {
68
+ const opts = c.options;
69
+ if (!Array.isArray(opts) || opts.length === 0) {
70
+ errors.push(`${p}: select needs a non-empty options array`);
71
+ }
72
+ else if (!opts.includes(c.default)) {
73
+ errors.push(`${p}: default "${String(c.default)}" is not in options`);
74
+ }
75
+ break;
76
+ }
77
+ case 'color':
78
+ if (typeof c.default !== 'string' || !HEX_RE.test(c.default)) {
79
+ errors.push(`${p}: color default must be a hex color (e.g. #FF5722)`);
80
+ }
81
+ break;
82
+ case 'text':
83
+ if (typeof c.default !== 'string')
84
+ errors.push(`${p}: text default must be a string`);
85
+ break;
86
+ default:
87
+ errors.push(`${p}: unknown control type "${c.type}"`);
88
+ }
89
+ }
package/dist/index.d.ts CHANGED
@@ -53,7 +53,7 @@ export { CapabilityManager, CapabilityContext, createCapabilityHook, autoDetectC
53
53
  export type { CapabilityPack, CapabilityTier, LoadedCapability, CapabilityCatalogEntry, CapabilityLoadResult, CapabilityManagerConfig, CapabilityContextConfig, CapabilityHookConfig, ConditionalModule, AutoDetectResult, } from './capabilities/index.js';
54
54
  export { SystemPromptBuilder, buildSystemPrompt, detectGitRepository, getModuleStats, ALL_MODULES, IDENTITY_MODULE, STYLE_MODULE, TASK_EXECUTION_MODULE, TODO_MANAGEMENT_MODULE, TOOL_USAGE_DIRECT_MODULE, TOOL_USAGE_HINTS_MODULE, PLATFORM_TOOL_HINTS_MODULE, FACTORY_TOOL_HINTS_MODULE, TOOL_USAGE_META_MODULE, DELEGATION_MODULE, GIT_SAFETY_MODULE, SUGGEST_MODULE, IMPORTANT_RULES_MODULE, VISUAL_OUTPUT_MODULE, ENVIRONMENT_MODULE, shouldIncludeModule, getEstimatedTokensForConditions, getTotalEstimatedTokens, } from './system-prompt/index.js';
55
55
  export type { SystemPromptContext, BuildResult, SystemPromptModule, ModuleConditions, } from './system-prompt/index.js';
56
- export type { ProjectType, ProjectStatus, RepoPattern, WorkflowMode, LifecycleState, WorkItemType, WorkItemStatus, WorkItemPriority, GuidedStep, DocumentType, PlanStatus, Project, WorkItem, ProjectDocument, Plan, PlanSummary, PlanWithWorkItem, HistoryEntry, CreateProjectInput, UpdateProjectInput, ProjectListOptions, CreateWorkItemInput, UpdateWorkItemInput, QueryWorkItemsInput, CreateDocumentInput, UpdateDocumentInput, CreatePlanInput, UpdatePlanInput, ListPlansOptions, WorkItemQueryResult, ProjectListResult, BulkCreateItem, WorkItemComment, CreateCommentInput, UpdateCommentInput, IProjectRepository, IWorkItemRepository, IDocumentRepository, IPlanRepository, ICommentRepository, IAnchorService, IArtifactService, IEpisodeService, AnchorData, ArtifactType, ArtifactData, ArtifactSummaryData, WorkEpisode, ProjectWorkSummary, PlatformContext, PlatformToolsConfig, PlatformHooks, StepCriteria, } from './platform/index.js';
56
+ export type { ProjectType, ProjectStatus, RepoPattern, WorkflowMode, LifecycleState, WorkItemType, WorkItemStatus, WorkItemPriority, GuidedStep, DocumentType, PlanStatus, Project, WorkItem, ProjectDocument, Plan, PlanSummary, PlanWithWorkItem, HistoryEntry, CreateProjectInput, UpdateProjectInput, ProjectListOptions, CreateWorkItemInput, UpdateWorkItemInput, QueryWorkItemsInput, CreateDocumentInput, UpdateDocumentInput, CreatePlanInput, UpdatePlanInput, ListPlansOptions, WorkItemQueryResult, ProjectListResult, BulkCreateItem, WorkItemComment, CreateCommentInput, UpdateCommentInput, IProjectRepository, IWorkItemRepository, IDocumentRepository, IPlanRepository, ICommentRepository, ICanvasRepository, IAnchorService, IArtifactService, IEpisodeService, AnchorData, ArtifactType, ArtifactData, ArtifactSummaryData, WorkEpisode, ProjectWorkSummary, PlatformContext, PlatformToolsConfig, PlatformHooks, StepCriteria, } from './platform/index.js';
57
57
  export { createSQLiteRepositories, SQLiteProjectRepository, SQLiteWorkItemRepository, SQLiteDocumentRepository, SQLitePlanRepository, SQLiteCommentRepository, getDatabase, closeDatabase, closeAllDatabases, databaseExists, SCHEMA_VERSION, SCHEMA_SQL, } from './platform/index.js';
58
58
  export type { SQLiteRepositories, CreateSQLiteRepositoriesOptions, ProjectDeleteHooks, ProjectRecord, WorkItemRecord, ProjectDocumentRecord, WorkItemCommentRecord, } from './platform/index.js';
59
59
  export { createAskUserTool, createAskUserSimpleTool, createProposeAlternativesTool, createInteractiveFlowTool, validateFlow, INTERACTIVE_FLOW_INPUT_SCHEMA, } from './tools/index.js';
@@ -64,7 +64,7 @@ export { detectProject, suggestProjectType, detectCommon } from './detection/ind
64
64
  export type { DetectProjectOptions, DetectionResult, ContentSummary } from './detection/index.js';
65
65
  export { createGuideTool, SHARED_GUIDE_ENTRIES, searchGuideEntries, topicToGuideEntry, } from './guide/index.js';
66
66
  export type { GuideEntry, ContentTopic, ContentSection, GuideToolConfig } from './guide/index.js';
67
- export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createImageTools, ProjectAnchorStore, FileArtifactService, } from './platform/index.js';
67
+ export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createCanvasTools, createImageTools, ProjectAnchorStore, FileArtifactService, } from './platform/index.js';
68
68
  export type { ProjectAnchorStoreConfig, FileArtifactServiceConfig, ImageToolsConfig, ImageResizer, } from './platform/index.js';
69
69
  export { STEP_ORDER, GUIDED_STEP_CRITERIA, getNextStep, isValidTransition, getStepCriteria, formatStepDisplay, getStepNumber, } from './platform/index.js';
70
70
  export type { CustomSkill, CompilrSkillExtension, ForkedFromMarker, SkillEligibilityContext, SkillCollision, SkillDiffLine, SkillValidationIssue, ScopeConfig, SkillResolution, } from './skills/index.js';
@@ -80,6 +80,8 @@ export type { ProjectTypeConfig, ProjectPhase, SuggestedAgent, DocumentTemplate,
80
80
  export { defineTool, createSuccessResult, createErrorResult, mergeHooks, createLoggingHooks, createClaudeProvider, createOpenAIProvider, createGeminiNativeProvider, createOllamaProvider, createTogetherProvider, createGroqProvider, createFireworksProvider, createPerplexityProvider, createOpenRouterProvider, createMockProvider, MockProvider, Agent, ContextManager, DEFAULT_CONTEXT_CONFIG, createTaskTool, createSuggestTool, defaultAgentTypes, TOOL_SETS, BUILTIN_GUARDRAILS, TOOL_NAMES, getDefaultShellManager, builtinSkills, AnchorManager, MCPManager, AgentError, ProviderError, ToolError, ToolTimeoutError, MaxIterationsError, AbortError, } from '@compilr-dev/agents';
81
81
  export { classifyAgentError, isApiKeyError } from './errors/classify.js';
82
82
  export type { AgentErrorCategory, AgentErrorInfo } from './errors/classify.js';
83
+ export { validateControlManifest, seedValues } from './canvas/index.js';
84
+ export type { CanvasType, ParamValue, Control, ControlManifest, CanvasRecord, CanvasSummary, CreateCanvasInput, UpdateCanvasInput, SliderControl, NumberControl, ToggleControl, SelectControl, ColorControl, TextControl, ManifestValidationResult, } from './canvas/index.js';
83
85
  export type { Tool, HooksConfig, AgentEvent, Message, LLMProvider, AnchorInput, ToolExecutionResult, AgentRunResult, PermissionHandler, PermissionHandlerResponse, ToolPermission, AgentTypeConfig, GuardrailTriggeredHandler, BeforeLLMHookResult, BeforeToolHook, BeforeToolHookResult, AfterToolHook, AgentState, AgentConfig, SessionInfo, Anchor, AnchorScope, AnchorClearOptions, AnchorPriority, AnchorQueryOptions, FileAccessType, FileAccess, GuardrailResult, GuardrailContext, MCPClient, MCPToolDefinition, } from '@compilr-dev/agents';
84
86
  export { DEFAULT_PERMISSION_RULES, WRITE_TOOLS, findMatchingRule, permissionModeLabel, permissionLevelLabel, } from './permissions.js';
85
87
  export type { PermissionRule, PermissionMode, PermissionLevel } from './permissions.js';
package/dist/index.js CHANGED
@@ -147,7 +147,7 @@ export { createGuideTool, SHARED_GUIDE_ENTRIES, searchGuideEntries, topicToGuide
147
147
  // =============================================================================
148
148
  // Platform Tools (runtime — createPlatformTools factory + individual factories)
149
149
  // =============================================================================
150
- export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createImageTools, ProjectAnchorStore, FileArtifactService, } from './platform/index.js';
150
+ export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createCanvasTools, createImageTools, ProjectAnchorStore, FileArtifactService, } from './platform/index.js';
151
151
  // =============================================================================
152
152
  // Platform Workflow (pure step-criteria logic)
153
153
  // =============================================================================
@@ -196,6 +196,9 @@ AgentError, ProviderError, ToolError, ToolTimeoutError, MaxIterationsError, Abor
196
196
  // Error classification — shared by hosts to map provider/agent failures
197
197
  // (esp. missing/invalid API key) into stable categories + host-specific copy.
198
198
  export { classifyAgentError, isApiKeyError } from './errors/classify.js';
199
+ // Canvas contract — shared visual-canvas types + controls-manifest validation.
200
+ // Also exposed at the renderer-safe subpath `@compilr-dev/sdk/canvas`.
201
+ export { validateControlManifest, seedValues } from './canvas/index.js';
199
202
  // =============================================================================
200
203
  // Shared Permission Defaults & Utilities
201
204
  // =============================================================================
@@ -4,7 +4,7 @@
4
4
  * Provides a single entry point for data access, abstracting over the
5
5
  * underlying storage backend (SQLite in CLI, PostgreSQL in web/API).
6
6
  */
7
- import type { IProjectRepository, IWorkItemRepository, IDocumentRepository, IPlanRepository, ICommentRepository } from './repositories.js';
7
+ import type { IProjectRepository, IWorkItemRepository, IDocumentRepository, IPlanRepository, ICommentRepository, ICanvasRepository } from './repositories.js';
8
8
  import type { IAnchorService, IArtifactService, IEpisodeService } from './services.js';
9
9
  export interface PlatformContext {
10
10
  readonly projects: IProjectRepository;
@@ -15,6 +15,7 @@ export interface PlatformContext {
15
15
  readonly anchors?: IAnchorService;
16
16
  readonly artifacts?: IArtifactService;
17
17
  readonly episodes?: IEpisodeService;
18
+ readonly canvases?: ICanvasRepository;
18
19
  readonly comments?: ICommentRepository;
19
20
  }
20
21
  export interface PlatformHooks {
@@ -2,10 +2,10 @@
2
2
  * Platform — Repository interfaces, data models, tools, and workflow.
3
3
  */
4
4
  export type { ProjectType, ProjectStatus, RepoPattern, WorkflowMode, LifecycleState, WorkItemType, WorkItemStatus, WorkItemPriority, GuidedStep, DocumentType, PlanStatus, Project, WorkItem, ProjectDocument, Plan, PlanSummary, PlanWithWorkItem, HistoryEntry, CreateProjectInput, UpdateProjectInput, ProjectListOptions, CreateWorkItemInput, UpdateWorkItemInput, QueryWorkItemsInput, CreateDocumentInput, UpdateDocumentInput, CreatePlanInput, UpdatePlanInput, ListPlansOptions, WorkItemQueryResult, ProjectListResult, BulkCreateItem, WorkItemComment, CreateCommentInput, UpdateCommentInput, } from './types.js';
5
- export type { IProjectRepository, IWorkItemRepository, IDocumentRepository, IPlanRepository, ICommentRepository, } from './repositories.js';
5
+ export type { IProjectRepository, IWorkItemRepository, IDocumentRepository, IPlanRepository, ICommentRepository, ICanvasRepository, } from './repositories.js';
6
6
  export type { IAnchorService, IArtifactService, IEpisodeService, AnchorData, ArtifactType, ArtifactData, ArtifactSummaryData, WorkEpisode, ProjectWorkSummary, } from './services.js';
7
7
  export type { PlatformContext, PlatformToolsConfig, PlatformHooks } from './context.js';
8
- export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createImageTools, } from './tools/index.js';
8
+ export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createCanvasTools, createImageTools, } from './tools/index.js';
9
9
  export type { ImageToolsConfig, ImageResizer } from './tools/index.js';
10
10
  export { createSQLiteRepositories, SQLiteProjectRepository, SQLiteWorkItemRepository, SQLiteDocumentRepository, SQLitePlanRepository, SQLiteCommentRepository, getDatabase, closeDatabase, closeAllDatabases, databaseExists, SCHEMA_VERSION, SCHEMA_SQL, } from './sqlite/index.js';
11
11
  export type { SQLiteRepositories, CreateSQLiteRepositoriesOptions, ProjectDeleteHooks, ProjectRecord, WorkItemRecord, ProjectDocumentRecord, WorkItemCommentRecord, } from './sqlite/index.js';
@@ -2,7 +2,7 @@
2
2
  * Platform — Repository interfaces, data models, tools, and workflow.
3
3
  */
4
4
  // Platform tools (runtime)
5
- export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createImageTools, } from './tools/index.js';
5
+ export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createCanvasTools, createImageTools, } from './tools/index.js';
6
6
  // SQLite implementations (concrete repositories)
7
7
  export { createSQLiteRepositories, SQLiteProjectRepository, SQLiteWorkItemRepository, SQLiteDocumentRepository, SQLitePlanRepository, SQLiteCommentRepository, getDatabase, closeDatabase, closeAllDatabases, databaseExists, SCHEMA_VERSION, SCHEMA_SQL, } from './sqlite/index.js';
8
8
  // File-based anchor service (shared by CLI and Desktop)
@@ -6,6 +6,7 @@
6
6
  * backends (PostgreSQL, HTTP APIs).
7
7
  */
8
8
  import type { Project, WorkItem, WorkItemComment, ProjectDocument, Plan, PlanSummary, PlanWithWorkItem, HistoryEntry, CreateProjectInput, UpdateProjectInput, ProjectListOptions, CreateWorkItemInput, UpdateWorkItemInput, QueryWorkItemsInput, CreateCommentInput, UpdateCommentInput, CreateDocumentInput, UpdateDocumentInput, CreatePlanInput, UpdatePlanInput, ListPlansOptions, WorkItemQueryResult, ProjectListResult, BulkCreateItem, ProjectStatus, WorkItemType, WorkItemStatus, DocumentType, PlanStatus } from './types.js';
9
+ import type { CanvasRecord, CanvasSummary, CreateCanvasInput, UpdateCanvasInput } from '../canvas/types.js';
9
10
  export interface IProjectRepository {
10
11
  create(input: CreateProjectInput): Promise<Project>;
11
12
  getById(id: number): Promise<Project | null>;
@@ -46,6 +47,13 @@ export interface IDocumentRepository {
46
47
  deleteByType(projectId: number, docType: DocumentType): Promise<boolean>;
47
48
  getTypeCounts(projectId: number): Promise<Record<string, number>>;
48
49
  }
50
+ export interface ICanvasRepository {
51
+ create(input: CreateCanvasInput): Promise<CanvasRecord>;
52
+ getById(id: number): Promise<CanvasRecord | null>;
53
+ listByProject(projectId: number): Promise<CanvasSummary[]>;
54
+ update(id: number, input: UpdateCanvasInput): Promise<CanvasRecord | null>;
55
+ delete(id: number): Promise<boolean>;
56
+ }
49
57
  export interface IPlanRepository {
50
58
  create(input: CreatePlanInput): Promise<Plan>;
51
59
  getById(id: number): Promise<Plan | null>;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * SQLite Canvas Repository — concrete implementation of ICanvasRepository.
3
+ *
4
+ * `controls` and `values` are stored as JSON text columns. Note `values` is a
5
+ * SQL reserved word, so it is always quoted as "values" in statements.
6
+ */
7
+ import type Database from 'better-sqlite3';
8
+ import type { ICanvasRepository } from '../repositories.js';
9
+ import type { CanvasRecord, CanvasSummary, CreateCanvasInput, UpdateCanvasInput } from '../../canvas/types.js';
10
+ export declare class SQLiteCanvasRepository implements ICanvasRepository {
11
+ private readonly db;
12
+ constructor(db: Database.Database);
13
+ create(input: CreateCanvasInput): Promise<CanvasRecord>;
14
+ getById(id: number): Promise<CanvasRecord | null>;
15
+ listByProject(projectId: number): Promise<CanvasSummary[]>;
16
+ update(id: number, input: UpdateCanvasInput): Promise<CanvasRecord | null>;
17
+ delete(id: number): Promise<boolean>;
18
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * SQLite Canvas Repository — concrete implementation of ICanvasRepository.
3
+ *
4
+ * `controls` and `values` are stored as JSON text columns. Note `values` is a
5
+ * SQL reserved word, so it is always quoted as "values" in statements.
6
+ */
7
+ function parseControls(json) {
8
+ try {
9
+ const parsed = JSON.parse(json);
10
+ return Array.isArray(parsed.controls) ? { controls: parsed.controls } : { controls: [] };
11
+ }
12
+ catch {
13
+ return { controls: [] };
14
+ }
15
+ }
16
+ function parseValues(json) {
17
+ try {
18
+ return JSON.parse(json);
19
+ }
20
+ catch {
21
+ return {};
22
+ }
23
+ }
24
+ function toCanvas(r) {
25
+ return {
26
+ id: r.id,
27
+ projectId: r.project_id,
28
+ type: r.type,
29
+ title: r.title,
30
+ content: r.content,
31
+ controls: parseControls(r.controls),
32
+ values: parseValues(r.values),
33
+ createdAt: new Date(r.created_at),
34
+ updatedAt: new Date(r.updated_at),
35
+ };
36
+ }
37
+ function toSummary(r) {
38
+ return {
39
+ id: r.id,
40
+ projectId: r.project_id,
41
+ type: r.type,
42
+ title: r.title,
43
+ updatedAt: new Date(r.updated_at),
44
+ };
45
+ }
46
+ export class SQLiteCanvasRepository {
47
+ db;
48
+ constructor(db) {
49
+ this.db = db;
50
+ }
51
+ create(input) {
52
+ const now = new Date().toISOString();
53
+ const controls = JSON.stringify(input.controls ?? { controls: [] });
54
+ const values = JSON.stringify(input.values ?? {});
55
+ const result = this.db
56
+ .prepare(`INSERT INTO canvases (project_id, type, title, content, controls, "values", created_at, updated_at)
57
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
58
+ .run(input.projectId, input.type, input.title, input.content, controls, values, now, now);
59
+ const record = this.db
60
+ .prepare('SELECT * FROM canvases WHERE id = ?')
61
+ .get(Number(result.lastInsertRowid));
62
+ return Promise.resolve(toCanvas(record));
63
+ }
64
+ getById(id) {
65
+ const record = this.db.prepare('SELECT * FROM canvases WHERE id = ?').get(id);
66
+ return Promise.resolve(record ? toCanvas(record) : null);
67
+ }
68
+ listByProject(projectId) {
69
+ const rows = this.db
70
+ .prepare('SELECT * FROM canvases WHERE project_id = ? ORDER BY updated_at DESC')
71
+ .all(projectId);
72
+ return Promise.resolve(rows.map(toSummary));
73
+ }
74
+ update(id, input) {
75
+ const existing = this.db.prepare('SELECT * FROM canvases WHERE id = ?').get(id);
76
+ if (!existing)
77
+ return Promise.resolve(null);
78
+ const now = new Date().toISOString();
79
+ const title = input.title ?? existing.title;
80
+ const content = input.content ?? existing.content;
81
+ const controls = input.controls ? JSON.stringify(input.controls) : existing.controls;
82
+ const values = input.values ? JSON.stringify(input.values) : existing.values;
83
+ this.db
84
+ .prepare(`UPDATE canvases SET title = ?, content = ?, controls = ?, "values" = ?, updated_at = ? WHERE id = ?`)
85
+ .run(title, content, controls, values, now, id);
86
+ const record = this.db.prepare('SELECT * FROM canvases WHERE id = ?').get(id);
87
+ return Promise.resolve(toCanvas(record));
88
+ }
89
+ delete(id) {
90
+ const result = this.db.prepare('DELETE FROM canvases WHERE id = ?').run(id);
91
+ return Promise.resolve(result.changes > 0);
92
+ }
93
+ }
@@ -157,4 +157,22 @@ function runMigrations(db, fromVersion, toVersion) {
157
157
  `);
158
158
  db.prepare('INSERT INTO schema_version (version) VALUES (?)').run(7);
159
159
  }
160
+ if (fromVersion < 8 && toVersion >= 8) {
161
+ db.exec(`
162
+ CREATE TABLE IF NOT EXISTS canvases (
163
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
164
+ project_id INTEGER NOT NULL,
165
+ type TEXT NOT NULL,
166
+ title TEXT NOT NULL,
167
+ content TEXT NOT NULL,
168
+ controls TEXT NOT NULL DEFAULT '{"controls":[]}',
169
+ values TEXT NOT NULL DEFAULT '{}',
170
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
171
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
172
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
173
+ );
174
+ CREATE INDEX IF NOT EXISTS idx_canvases_project ON canvases(project_id);
175
+ `);
176
+ db.prepare('INSERT INTO schema_version (version) VALUES (?)').run(8);
177
+ }
160
178
  }
@@ -17,13 +17,14 @@
17
17
  */
18
18
  import type Database from 'better-sqlite3';
19
19
  import { type ProjectDeleteHooks } from './project-repository.js';
20
- import type { IProjectRepository, IWorkItemRepository, IDocumentRepository, IPlanRepository, ICommentRepository } from '../repositories.js';
20
+ import type { IProjectRepository, IWorkItemRepository, IDocumentRepository, IPlanRepository, ICommentRepository, ICanvasRepository } from '../repositories.js';
21
21
  export interface SQLiteRepositories {
22
22
  projects: IProjectRepository;
23
23
  workItems: IWorkItemRepository;
24
24
  documents: IDocumentRepository;
25
25
  plans: IPlanRepository;
26
26
  comments: ICommentRepository;
27
+ canvases: ICanvasRepository;
27
28
  }
28
29
  export interface CreateSQLiteRepositoriesOptions {
29
30
  projectDeleteHooks?: ProjectDeleteHooks;
@@ -37,6 +38,7 @@ export { SQLiteWorkItemRepository } from './work-item-repository.js';
37
38
  export { SQLiteDocumentRepository } from './document-repository.js';
38
39
  export { SQLitePlanRepository } from './plan-repository.js';
39
40
  export { SQLiteCommentRepository } from './comment-repository.js';
41
+ export { SQLiteCanvasRepository } from './canvas-repository.js';
40
42
  export { getDatabase, closeDatabase, closeAllDatabases, databaseExists } from './db.js';
41
43
  export { SCHEMA_VERSION, SCHEMA_SQL } from './schema.js';
42
44
  export type { ProjectRecord, WorkItemRecord, ProjectDocumentRecord, WorkItemCommentRecord, } from './schema.js';
@@ -20,6 +20,7 @@ import { SQLiteWorkItemRepository } from './work-item-repository.js';
20
20
  import { SQLiteDocumentRepository } from './document-repository.js';
21
21
  import { SQLitePlanRepository } from './plan-repository.js';
22
22
  import { SQLiteCommentRepository } from './comment-repository.js';
23
+ import { SQLiteCanvasRepository } from './canvas-repository.js';
23
24
  /**
24
25
  * Create all 4 platform repositories backed by a single SQLite database.
25
26
  */
@@ -30,6 +31,7 @@ export function createSQLiteRepositories(db, options) {
30
31
  documents: new SQLiteDocumentRepository(db),
31
32
  plans: new SQLitePlanRepository(db),
32
33
  comments: new SQLiteCommentRepository(db),
34
+ canvases: new SQLiteCanvasRepository(db),
33
35
  };
34
36
  }
35
37
  // Re-export classes for advanced usage
@@ -38,6 +40,7 @@ export { SQLiteWorkItemRepository } from './work-item-repository.js';
38
40
  export { SQLiteDocumentRepository } from './document-repository.js';
39
41
  export { SQLitePlanRepository } from './plan-repository.js';
40
42
  export { SQLiteCommentRepository } from './comment-repository.js';
43
+ export { SQLiteCanvasRepository } from './canvas-repository.js';
41
44
  // Re-export database utilities
42
45
  export { getDatabase, closeDatabase, closeAllDatabases, databaseExists } from './db.js';
43
46
  // Re-export schema (for consumers that need direct access)
@@ -4,8 +4,8 @@
4
4
  * Shared between CLI and Desktop — both access ~/.compilr-dev/projects.db
5
5
  * Schema version must be kept in sync across all consumers.
6
6
  */
7
- export declare const SCHEMA_VERSION = 7;
8
- export declare const SCHEMA_SQL = "\n-- Schema version tracking\nCREATE TABLE IF NOT EXISTS schema_version (\n version INTEGER PRIMARY KEY,\n applied_at DATETIME DEFAULT CURRENT_TIMESTAMP\n);\n\n-- Projects table\nCREATE TABLE IF NOT EXISTS projects (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n name TEXT UNIQUE NOT NULL,\n display_name TEXT NOT NULL,\n description TEXT,\n type TEXT DEFAULT 'general',\n status TEXT DEFAULT 'active',\n path TEXT NOT NULL,\n docs_path TEXT,\n repo_pattern TEXT DEFAULT 'single',\n language TEXT,\n framework TEXT,\n package_manager TEXT,\n runtime_version TEXT,\n commands TEXT,\n git_remote TEXT,\n git_branch TEXT DEFAULT 'main',\n workflow_mode TEXT DEFAULT 'flexible',\n lifecycle_state TEXT DEFAULT 'setup',\n current_item_id TEXT,\n last_context TEXT,\n metadata TEXT,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n last_activity_at DATETIME\n);\n\n-- Work items (backlog items, tasks, bugs)\nCREATE TABLE IF NOT EXISTS work_items (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n project_id INTEGER NOT NULL,\n item_number INTEGER NOT NULL,\n item_id TEXT NOT NULL,\n type TEXT NOT NULL,\n status TEXT DEFAULT 'backlog',\n priority TEXT DEFAULT 'medium',\n guided_step TEXT,\n owner TEXT,\n title TEXT NOT NULL,\n description TEXT,\n estimated_effort TEXT,\n actual_minutes INTEGER,\n completed_at DATETIME,\n completed_by TEXT,\n commit_hash TEXT,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,\n UNIQUE (project_id, item_id)\n);\n\n-- Project documents (PRD, architecture, plans, etc.)\nCREATE TABLE IF NOT EXISTS project_documents (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n project_id INTEGER NOT NULL,\n doc_type TEXT NOT NULL,\n title TEXT NOT NULL,\n content TEXT NOT NULL,\n status TEXT,\n work_item_id INTEGER,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,\n FOREIGN KEY (work_item_id) REFERENCES work_items(id) ON DELETE SET NULL\n);\n\n-- Work item history (audit trail)\nCREATE TABLE IF NOT EXISTS work_item_history (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n work_item_id INTEGER NOT NULL,\n project_id INTEGER NOT NULL,\n action TEXT NOT NULL,\n old_value TEXT,\n new_value TEXT,\n notes TEXT,\n changed_by TEXT,\n changed_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (work_item_id) REFERENCES work_items(id) ON DELETE CASCADE,\n FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE\n);\n\n-- Indexes\nCREATE INDEX IF NOT EXISTS idx_projects_path ON projects(path);\nCREATE INDEX IF NOT EXISTS idx_projects_docs_path ON projects(docs_path);\nCREATE INDEX IF NOT EXISTS idx_projects_status ON projects(status);\nCREATE INDEX IF NOT EXISTS idx_work_items_project ON work_items(project_id);\nCREATE INDEX IF NOT EXISTS idx_work_items_status ON work_items(status);\nCREATE INDEX IF NOT EXISTS idx_work_items_priority ON work_items(priority);\nCREATE INDEX IF NOT EXISTS idx_work_items_owner ON work_items(owner);\nCREATE INDEX IF NOT EXISTS idx_project_documents_project ON project_documents(project_id);\nCREATE INDEX IF NOT EXISTS idx_project_documents_type ON project_documents(doc_type);\nCREATE INDEX IF NOT EXISTS idx_project_documents_status ON project_documents(status);\nCREATE INDEX IF NOT EXISTS idx_project_documents_work_item ON project_documents(work_item_id);\nCREATE INDEX IF NOT EXISTS idx_work_item_history_item ON work_item_history(work_item_id);\n\n-- Terminal sessions (multi-terminal awareness)\nCREATE TABLE IF NOT EXISTS terminal_sessions (\n id TEXT PRIMARY KEY,\n project_id INTEGER,\n pid INTEGER NOT NULL,\n tty_path TEXT,\n label TEXT,\n started_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n last_heartbeat DATETIME DEFAULT CURRENT_TIMESTAMP,\n active_agent TEXT DEFAULT 'default',\n agents_json TEXT DEFAULT '[]',\n status TEXT DEFAULT 'active',\n FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE SET NULL\n);\nCREATE INDEX IF NOT EXISTS idx_terminal_sessions_project ON terminal_sessions(project_id);\nCREATE INDEX IF NOT EXISTS idx_terminal_sessions_status ON terminal_sessions(status);\n\n-- File locks (multi-terminal file lock awareness)\nCREATE TABLE IF NOT EXISTS file_locks (\n path TEXT NOT NULL,\n project_id INTEGER NOT NULL,\n session_id TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n locked_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (path, project_id),\n FOREIGN KEY (session_id) REFERENCES terminal_sessions(id) ON DELETE CASCADE,\n FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE\n);\nCREATE INDEX IF NOT EXISTS idx_file_locks_session ON file_locks(session_id);\n\n-- Session notifications (cross-session notifications)\nCREATE TABLE IF NOT EXISTS session_notifications (\n id TEXT PRIMARY KEY,\n project_id INTEGER NOT NULL,\n from_session_id TEXT NOT NULL,\n to_session_id TEXT,\n type TEXT NOT NULL,\n title TEXT NOT NULL,\n message TEXT,\n payload_json TEXT,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n read_at DATETIME,\n FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,\n FOREIGN KEY (from_session_id) REFERENCES terminal_sessions(id) ON DELETE CASCADE\n);\nCREATE INDEX IF NOT EXISTS idx_session_notifications_project ON session_notifications(project_id);\nCREATE INDEX IF NOT EXISTS idx_session_notifications_to_session ON session_notifications(to_session_id);\nCREATE INDEX IF NOT EXISTS idx_session_notifications_unread ON session_notifications(read_at);\n\n-- Work item comments\nCREATE TABLE IF NOT EXISTS work_item_comments (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n work_item_id INTEGER NOT NULL,\n project_id INTEGER NOT NULL,\n author TEXT NOT NULL,\n content TEXT NOT NULL,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (work_item_id) REFERENCES work_items(id) ON DELETE CASCADE,\n FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE\n);\nCREATE INDEX IF NOT EXISTS idx_work_item_comments_work_item ON work_item_comments(work_item_id);\nCREATE INDEX IF NOT EXISTS idx_work_item_comments_project ON work_item_comments(project_id);\n";
7
+ export declare const SCHEMA_VERSION = 8;
8
+ export declare const SCHEMA_SQL = "\n-- Schema version tracking\nCREATE TABLE IF NOT EXISTS schema_version (\n version INTEGER PRIMARY KEY,\n applied_at DATETIME DEFAULT CURRENT_TIMESTAMP\n);\n\n-- Projects table\nCREATE TABLE IF NOT EXISTS projects (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n name TEXT UNIQUE NOT NULL,\n display_name TEXT NOT NULL,\n description TEXT,\n type TEXT DEFAULT 'general',\n status TEXT DEFAULT 'active',\n path TEXT NOT NULL,\n docs_path TEXT,\n repo_pattern TEXT DEFAULT 'single',\n language TEXT,\n framework TEXT,\n package_manager TEXT,\n runtime_version TEXT,\n commands TEXT,\n git_remote TEXT,\n git_branch TEXT DEFAULT 'main',\n workflow_mode TEXT DEFAULT 'flexible',\n lifecycle_state TEXT DEFAULT 'setup',\n current_item_id TEXT,\n last_context TEXT,\n metadata TEXT,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n last_activity_at DATETIME\n);\n\n-- Work items (backlog items, tasks, bugs)\nCREATE TABLE IF NOT EXISTS work_items (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n project_id INTEGER NOT NULL,\n item_number INTEGER NOT NULL,\n item_id TEXT NOT NULL,\n type TEXT NOT NULL,\n status TEXT DEFAULT 'backlog',\n priority TEXT DEFAULT 'medium',\n guided_step TEXT,\n owner TEXT,\n title TEXT NOT NULL,\n description TEXT,\n estimated_effort TEXT,\n actual_minutes INTEGER,\n completed_at DATETIME,\n completed_by TEXT,\n commit_hash TEXT,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,\n UNIQUE (project_id, item_id)\n);\n\n-- Project documents (PRD, architecture, plans, etc.)\nCREATE TABLE IF NOT EXISTS project_documents (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n project_id INTEGER NOT NULL,\n doc_type TEXT NOT NULL,\n title TEXT NOT NULL,\n content TEXT NOT NULL,\n status TEXT,\n work_item_id INTEGER,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,\n FOREIGN KEY (work_item_id) REFERENCES work_items(id) ON DELETE SET NULL\n);\n\n-- Work item history (audit trail)\nCREATE TABLE IF NOT EXISTS work_item_history (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n work_item_id INTEGER NOT NULL,\n project_id INTEGER NOT NULL,\n action TEXT NOT NULL,\n old_value TEXT,\n new_value TEXT,\n notes TEXT,\n changed_by TEXT,\n changed_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (work_item_id) REFERENCES work_items(id) ON DELETE CASCADE,\n FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE\n);\n\n-- Indexes\nCREATE INDEX IF NOT EXISTS idx_projects_path ON projects(path);\nCREATE INDEX IF NOT EXISTS idx_projects_docs_path ON projects(docs_path);\nCREATE INDEX IF NOT EXISTS idx_projects_status ON projects(status);\nCREATE INDEX IF NOT EXISTS idx_work_items_project ON work_items(project_id);\nCREATE INDEX IF NOT EXISTS idx_work_items_status ON work_items(status);\nCREATE INDEX IF NOT EXISTS idx_work_items_priority ON work_items(priority);\nCREATE INDEX IF NOT EXISTS idx_work_items_owner ON work_items(owner);\nCREATE INDEX IF NOT EXISTS idx_project_documents_project ON project_documents(project_id);\nCREATE INDEX IF NOT EXISTS idx_project_documents_type ON project_documents(doc_type);\nCREATE INDEX IF NOT EXISTS idx_project_documents_status ON project_documents(status);\nCREATE INDEX IF NOT EXISTS idx_project_documents_work_item ON project_documents(work_item_id);\nCREATE INDEX IF NOT EXISTS idx_work_item_history_item ON work_item_history(work_item_id);\n\n-- Terminal sessions (multi-terminal awareness)\nCREATE TABLE IF NOT EXISTS terminal_sessions (\n id TEXT PRIMARY KEY,\n project_id INTEGER,\n pid INTEGER NOT NULL,\n tty_path TEXT,\n label TEXT,\n started_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n last_heartbeat DATETIME DEFAULT CURRENT_TIMESTAMP,\n active_agent TEXT DEFAULT 'default',\n agents_json TEXT DEFAULT '[]',\n status TEXT DEFAULT 'active',\n FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE SET NULL\n);\nCREATE INDEX IF NOT EXISTS idx_terminal_sessions_project ON terminal_sessions(project_id);\nCREATE INDEX IF NOT EXISTS idx_terminal_sessions_status ON terminal_sessions(status);\n\n-- File locks (multi-terminal file lock awareness)\nCREATE TABLE IF NOT EXISTS file_locks (\n path TEXT NOT NULL,\n project_id INTEGER NOT NULL,\n session_id TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n locked_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (path, project_id),\n FOREIGN KEY (session_id) REFERENCES terminal_sessions(id) ON DELETE CASCADE,\n FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE\n);\nCREATE INDEX IF NOT EXISTS idx_file_locks_session ON file_locks(session_id);\n\n-- Session notifications (cross-session notifications)\nCREATE TABLE IF NOT EXISTS session_notifications (\n id TEXT PRIMARY KEY,\n project_id INTEGER NOT NULL,\n from_session_id TEXT NOT NULL,\n to_session_id TEXT,\n type TEXT NOT NULL,\n title TEXT NOT NULL,\n message TEXT,\n payload_json TEXT,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n read_at DATETIME,\n FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,\n FOREIGN KEY (from_session_id) REFERENCES terminal_sessions(id) ON DELETE CASCADE\n);\nCREATE INDEX IF NOT EXISTS idx_session_notifications_project ON session_notifications(project_id);\nCREATE INDEX IF NOT EXISTS idx_session_notifications_to_session ON session_notifications(to_session_id);\nCREATE INDEX IF NOT EXISTS idx_session_notifications_unread ON session_notifications(read_at);\n\n-- Work item comments\nCREATE TABLE IF NOT EXISTS work_item_comments (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n work_item_id INTEGER NOT NULL,\n project_id INTEGER NOT NULL,\n author TEXT NOT NULL,\n content TEXT NOT NULL,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (work_item_id) REFERENCES work_items(id) ON DELETE CASCADE,\n FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE\n);\nCREATE INDEX IF NOT EXISTS idx_work_item_comments_work_item ON work_item_comments(work_item_id);\nCREATE INDEX IF NOT EXISTS idx_work_item_comments_project ON work_item_comments(project_id);\n\n-- Canvases (visual reasoning surfaces: infographic / carousel / board)\nCREATE TABLE IF NOT EXISTS canvases (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n project_id INTEGER NOT NULL,\n type TEXT NOT NULL,\n title TEXT NOT NULL,\n content TEXT NOT NULL,\n controls TEXT NOT NULL DEFAULT '{\"controls\":[]}',\n values TEXT NOT NULL DEFAULT '{}',\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE\n);\nCREATE INDEX IF NOT EXISTS idx_canvases_project ON canvases(project_id);\n";
9
9
  export interface ProjectRecord {
10
10
  id: number;
11
11
  name: string;
@@ -63,6 +63,17 @@ export interface ProjectDocumentRecord {
63
63
  created_at: string;
64
64
  updated_at: string;
65
65
  }
66
+ export interface CanvasDbRecord {
67
+ id: number;
68
+ project_id: number;
69
+ type: string;
70
+ title: string;
71
+ content: string;
72
+ controls: string;
73
+ values: string;
74
+ created_at: string;
75
+ updated_at: string;
76
+ }
66
77
  export interface WorkItemCommentRecord {
67
78
  id: number;
68
79
  work_item_id: number;
@@ -4,7 +4,7 @@
4
4
  * Shared between CLI and Desktop — both access ~/.compilr-dev/projects.db
5
5
  * Schema version must be kept in sync across all consumers.
6
6
  */
7
- export const SCHEMA_VERSION = 7;
7
+ export const SCHEMA_VERSION = 8;
8
8
  export const SCHEMA_SQL = `
9
9
  -- Schema version tracking
10
10
  CREATE TABLE IF NOT EXISTS schema_version (
@@ -171,4 +171,19 @@ CREATE TABLE IF NOT EXISTS work_item_comments (
171
171
  );
172
172
  CREATE INDEX IF NOT EXISTS idx_work_item_comments_work_item ON work_item_comments(work_item_id);
173
173
  CREATE INDEX IF NOT EXISTS idx_work_item_comments_project ON work_item_comments(project_id);
174
+
175
+ -- Canvases (visual reasoning surfaces: infographic / carousel / board)
176
+ CREATE TABLE IF NOT EXISTS canvases (
177
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
178
+ project_id INTEGER NOT NULL,
179
+ type TEXT NOT NULL,
180
+ title TEXT NOT NULL,
181
+ content TEXT NOT NULL,
182
+ controls TEXT NOT NULL DEFAULT '{"controls":[]}',
183
+ values TEXT NOT NULL DEFAULT '{}',
184
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
185
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
186
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
187
+ );
188
+ CREATE INDEX IF NOT EXISTS idx_canvases_project ON canvases(project_id);
174
189
  `;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Canvas Tools — agent authoring + management of visual canvases.
3
+ *
4
+ * 4 tools: canvas_write (create/update), canvas_list, canvas_get, canvas_delete.
5
+ *
6
+ * `content` is the agent's raw HTML/SVG; the host renderer injects the CSP +
7
+ * bridge and renders it in a sandboxed iframe. `controls` is the optional Tweaks
8
+ * manifest (validated here). Persistence is delegated to ctx.canvases
9
+ * (ICanvasRepository); the current project is resolved like the document tools.
10
+ */
11
+ import type { PlatformToolsConfig } from '../context.js';
12
+ import type { ControlManifest } from '../../canvas/types.js';
13
+ export declare function createCanvasTools(config: PlatformToolsConfig): (import("@compilr-dev/agents").Tool<{
14
+ type: string;
15
+ title: string;
16
+ html: string;
17
+ controls?: ControlManifest;
18
+ canvas_id?: number;
19
+ project_id?: number;
20
+ }> | import("@compilr-dev/agents").Tool<{
21
+ canvas_id: number;
22
+ }>)[];
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Canvas Tools — agent authoring + management of visual canvases.
3
+ *
4
+ * 4 tools: canvas_write (create/update), canvas_list, canvas_get, canvas_delete.
5
+ *
6
+ * `content` is the agent's raw HTML/SVG; the host renderer injects the CSP +
7
+ * bridge and renders it in a sandboxed iframe. `controls` is the optional Tweaks
8
+ * manifest (validated here). Persistence is delegated to ctx.canvases
9
+ * (ICanvasRepository); the current project is resolved like the document tools.
10
+ */
11
+ import { defineTool, createSuccessResult, createErrorResult } from '@compilr-dev/agents';
12
+ import { truncateContent } from './truncate.js';
13
+ import { seedValues } from '../../canvas/types.js';
14
+ import { validateControlManifest } from '../../canvas/validate.js';
15
+ const CANVAS_TYPES = ['infographic', 'carousel', 'board'];
16
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
17
+ export function createCanvasTools(config) {
18
+ const ctx = config.context;
19
+ const canvases = ctx.canvases;
20
+ if (!canvases)
21
+ throw new Error('canvases repository required');
22
+ // ---------------------------------------------------------------------------
23
+ // canvas_write — create (no canvas_id) or update (canvas_id given)
24
+ // ---------------------------------------------------------------------------
25
+ const canvasWriteTool = defineTool({
26
+ name: 'canvas_write',
27
+ description: 'Create or update a visual canvas (infographic, carousel, or board) rendered from HTML/SVG. ' +
28
+ 'Optionally declare a controls manifest ("Tweaks") of live parameters the user can adjust: ' +
29
+ 'each control is { type: slider|number|toggle|select|color|text, param, label, default, ...type config }. ' +
30
+ 'Bind params in your HTML via CSS custom properties var(--param), [data-bind="param"] text, and ' +
31
+ '[data-show="param"] visibility; for computed updates define window.applyParams(values) in a <script>. ' +
32
+ 'Pass canvas_id to update an existing canvas; omit it to create a new one.',
33
+ inputSchema: {
34
+ type: 'object',
35
+ properties: {
36
+ type: {
37
+ type: 'string',
38
+ enum: CANVAS_TYPES,
39
+ description: 'Canvas kind: infographic (single composition), carousel (slide sheets), board (infinite 2D).',
40
+ },
41
+ title: { type: 'string', description: 'Canvas title (shown in the library and tab).' },
42
+ html: {
43
+ type: 'string',
44
+ description: 'The canvas content as HTML/SVG. Raw content only — the app injects the sandbox/CSP. ' +
45
+ 'May include a <script> defining window.applyParams(values) for live recompute.',
46
+ },
47
+ controls: {
48
+ type: 'object',
49
+ description: 'Optional Tweaks manifest: { "controls": [ { "type": "slider", "param": "headline", "label": "Headline", "default": 40, "min": 24, "max": 72 }, ... ] }.',
50
+ properties: {
51
+ controls: { type: 'array', items: { type: 'object' } },
52
+ },
53
+ },
54
+ canvas_id: {
55
+ type: 'number',
56
+ description: 'Existing canvas id to update. Omit to create a new canvas.',
57
+ },
58
+ project_id: {
59
+ type: 'number',
60
+ description: 'Optional project id. Defaults to the current project.',
61
+ },
62
+ },
63
+ required: ['type', 'title', 'html'],
64
+ },
65
+ execute: async (input) => {
66
+ try {
67
+ if (input.controls) {
68
+ const check = validateControlManifest(input.controls);
69
+ if (!check.ok) {
70
+ return createErrorResult(`Invalid controls manifest:\n- ${check.errors.join('\n- ')}`);
71
+ }
72
+ }
73
+ if (input.canvas_id !== undefined) {
74
+ const updated = await canvases.update(input.canvas_id, {
75
+ title: input.title,
76
+ content: input.html,
77
+ // Re-seed values from the new manifest when controls are (re)declared.
78
+ // Phase 1 keeps this simple; a later phase can preserve prior tweaks.
79
+ ...(input.controls
80
+ ? { controls: input.controls, values: seedValues(input.controls) }
81
+ : {}),
82
+ });
83
+ if (!updated)
84
+ return createErrorResult(`Canvas ${String(input.canvas_id)} not found.`);
85
+ return createSuccessResult(`Updated canvas "${updated.title}" (${updated.type})\nID: ${String(updated.id)}`);
86
+ }
87
+ const projectId = input.project_id ?? ctx.currentProjectId;
88
+ if (!projectId) {
89
+ return createErrorResult('No active project. Provide project_id or select a project.');
90
+ }
91
+ const controls = input.controls ?? { controls: [] };
92
+ const created = await canvases.create({
93
+ projectId,
94
+ type: input.type,
95
+ title: input.title,
96
+ content: input.html,
97
+ controls,
98
+ values: seedValues(controls),
99
+ });
100
+ return createSuccessResult(`Created canvas "${created.title}" (${created.type})\nID: ${String(created.id)}`);
101
+ }
102
+ catch (error) {
103
+ return createErrorResult(`Failed to write canvas: ${error instanceof Error ? error.message : String(error)}`);
104
+ }
105
+ },
106
+ });
107
+ // ---------------------------------------------------------------------------
108
+ // canvas_list
109
+ // ---------------------------------------------------------------------------
110
+ const canvasListTool = defineTool({
111
+ name: 'canvas_list',
112
+ description: 'List the canvases in the current (or given) project.',
113
+ inputSchema: {
114
+ type: 'object',
115
+ properties: {
116
+ project_id: { type: 'number', description: 'Optional project id. Defaults to current.' },
117
+ },
118
+ required: [],
119
+ },
120
+ execute: async (input) => {
121
+ try {
122
+ const projectId = input.project_id ?? ctx.currentProjectId;
123
+ if (!projectId) {
124
+ return createErrorResult('No active project. Provide project_id or select a project.');
125
+ }
126
+ const items = await canvases.listByProject(projectId);
127
+ if (items.length === 0)
128
+ return createSuccessResult('No canvases yet.');
129
+ const lines = items.map((c) => `- [${c.type}] ${c.title} (id: ${String(c.id)})`);
130
+ return createSuccessResult([`${String(items.length)} canvas(es):`, ...lines].join('\n'));
131
+ }
132
+ catch (error) {
133
+ return createErrorResult(`Failed to list canvases: ${error instanceof Error ? error.message : String(error)}`);
134
+ }
135
+ },
136
+ });
137
+ // ---------------------------------------------------------------------------
138
+ // canvas_get
139
+ // ---------------------------------------------------------------------------
140
+ const canvasGetTool = defineTool({
141
+ name: 'canvas_get',
142
+ description: 'Get a canvas by id, including its content, controls manifest, and current values.',
143
+ inputSchema: {
144
+ type: 'object',
145
+ properties: {
146
+ canvas_id: { type: 'number', description: 'Canvas id.' },
147
+ },
148
+ required: ['canvas_id'],
149
+ },
150
+ execute: async (input) => {
151
+ try {
152
+ const c = await canvases.getById(input.canvas_id);
153
+ if (!c)
154
+ return createErrorResult(`Canvas ${String(input.canvas_id)} not found.`);
155
+ const tc = truncateContent(c.content);
156
+ const output = `Canvas "${c.title}" (${c.type})\n` +
157
+ `ID: ${String(c.id)}\n` +
158
+ `Controls: ${String(c.controls.controls.length)}\n\n` +
159
+ `--- content ---\n` +
160
+ tc.content;
161
+ return createSuccessResult(output);
162
+ }
163
+ catch (error) {
164
+ return createErrorResult(`Failed to get canvas: ${error instanceof Error ? error.message : String(error)}`);
165
+ }
166
+ },
167
+ });
168
+ // ---------------------------------------------------------------------------
169
+ // canvas_delete
170
+ // ---------------------------------------------------------------------------
171
+ const canvasDeleteTool = defineTool({
172
+ name: 'canvas_delete',
173
+ description: 'Delete a canvas by id.',
174
+ inputSchema: {
175
+ type: 'object',
176
+ properties: {
177
+ canvas_id: { type: 'number', description: 'Canvas id.' },
178
+ },
179
+ required: ['canvas_id'],
180
+ },
181
+ execute: async (input) => {
182
+ try {
183
+ const ok = await canvases.delete(input.canvas_id);
184
+ if (!ok)
185
+ return createErrorResult(`Canvas ${String(input.canvas_id)} not found.`);
186
+ return createSuccessResult(`Deleted canvas ${String(input.canvas_id)}`);
187
+ }
188
+ catch (error) {
189
+ return createErrorResult(`Failed to delete canvas: ${error instanceof Error ? error.message : String(error)}`);
190
+ }
191
+ },
192
+ });
193
+ return [canvasWriteTool, canvasListTool, canvasGetTool, canvasDeleteTool];
194
+ }
@@ -31,5 +31,6 @@ export { createBacklogTools } from './backlog-tools.js';
31
31
  export { createAnchorTools } from './anchor-tools.js';
32
32
  export { createArtifactTools } from './artifact-tools.js';
33
33
  export { createEpisodeTools } from './episode-tools.js';
34
+ export { createCanvasTools } from './canvas-tools.js';
34
35
  export { createImageTools } from './image-tools.js';
35
36
  export type { ImageToolsConfig, ImageResizer } from './image-tools.js';
@@ -16,6 +16,7 @@ import { createBacklogTools } from './backlog-tools.js';
16
16
  import { createAnchorTools } from './anchor-tools.js';
17
17
  import { createArtifactTools } from './artifact-tools.js';
18
18
  import { createEpisodeTools } from './episode-tools.js';
19
+ import { createCanvasTools } from './canvas-tools.js';
19
20
  import { createImageTools } from './image-tools.js';
20
21
  /**
21
22
  * Create all platform tools operating against the given context.
@@ -54,6 +55,9 @@ export function createPlatformTools(config, options) {
54
55
  if (config.context.episodes) {
55
56
  tools.push(...createEpisodeTools(config));
56
57
  }
58
+ if (config.context.canvases) {
59
+ tools.push(...createCanvasTools(config));
60
+ }
57
61
  return tools;
58
62
  }
59
63
  // Re-export individual factory functions for selective use
@@ -65,4 +69,5 @@ export { createBacklogTools } from './backlog-tools.js';
65
69
  export { createAnchorTools } from './anchor-tools.js';
66
70
  export { createArtifactTools } from './artifact-tools.js';
67
71
  export { createEpisodeTools } from './episode-tools.js';
72
+ export { createCanvasTools } from './canvas-tools.js';
68
73
  export { createImageTools } from './image-tools.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/sdk",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "description": "Universal agent runtime for building AI-powered applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -21,6 +21,10 @@
21
21
  "./errors": {
22
22
  "types": "./dist/errors/classify.d.ts",
23
23
  "import": "./dist/errors/classify.js"
24
+ },
25
+ "./canvas": {
26
+ "types": "./dist/canvas/index.d.ts",
27
+ "import": "./dist/canvas/index.js"
24
28
  }
25
29
  },
26
30
  "files": [