@startsimpli/api 0.5.21 → 0.5.22

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@startsimpli/api",
3
- "version": "0.5.21",
3
+ "version": "0.5.22",
4
4
  "description": "Type-safe Django REST API client for StartSimpli apps",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
package/src/index.ts CHANGED
@@ -18,6 +18,21 @@ export { ContactsApi } from './lib/contacts-api';
18
18
  export { OrganizationsApi } from './lib/organizations-api';
19
19
  export { EntitiesApi } from './lib/entities-api';
20
20
  export { WorkflowsApi } from './lib/workflows-api';
21
+ export type {
22
+ Workflow,
23
+ WorkflowNode,
24
+ WorkflowInput,
25
+ WorkflowListParams,
26
+ NodeTypeDef,
27
+ NodeStatus,
28
+ RunStatus,
29
+ ExecutionMode,
30
+ WorkflowExecution,
31
+ WorkflowNodeExecution,
32
+ WorkflowExecutionDetail,
33
+ ExecuteWorkflowResponse,
34
+ ExecutionListParams,
35
+ } from './lib/workflows-api';
21
36
  export { MessagesApi } from './lib/messages-api';
22
37
  export type { Message, MessageStatus as MessageApiStatus, MessageRecipient, MessagingChannel as MessagingChannelType, MessageFilters, CreateMessageInput, ScheduleMessageInput, SendTestInput } from './lib/messages-api';
23
38
  export { calculateStatsFromMessages } from './lib/message-stats';
@@ -164,6 +179,7 @@ import { TargetListsApi } from './lib/target-lists-api';
164
179
  import { MessageTemplatesApi } from './lib/message-templates-api';
165
180
  import { MarketsApi } from './lib/markets-api';
166
181
  import { VaultApi } from './lib/vault-api';
182
+ import { PresentationsApi } from './lib/presentations-api';
167
183
  import { CompaniesApi } from './lib/companies-api';
168
184
  import { TeamsApi } from './lib/teams-api';
169
185
  import { TeamInvitationsApi } from './lib/team-invitations-api';
@@ -193,6 +209,7 @@ export function createStartSimpliApi(config: ApiClientConfig = {}) {
193
209
  messageTemplates: new MessageTemplatesApi(client),
194
210
  markets: new MarketsApi(client),
195
211
  vault: new VaultApi(client),
212
+ presentations: new PresentationsApi(client),
196
213
  companies: new CompaniesApi(client),
197
214
  teams: new TeamsApi(client),
198
215
  teamInvitations: new TeamInvitationsApi(client),
@@ -222,6 +239,23 @@ export type {
222
239
  VaultListParams,
223
240
  } from './lib/vault-api';
224
241
 
242
+ // Presentations API
243
+ export { PresentationsApi } from './lib/presentations-api';
244
+ export type {
245
+ Deck,
246
+ DeckInput,
247
+ DeckStatus,
248
+ DeckListParams,
249
+ DeckGenerationStatus,
250
+ GenerateDeckInput,
251
+ Slide,
252
+ SlideInput,
253
+ SlideStatus,
254
+ RegenerateSlideInput,
255
+ RenderMode,
256
+ CompileResult,
257
+ } from './lib/presentations-api';
258
+
225
259
  // Markets API
226
260
  export { MarketsApi } from './lib/markets-api';
227
261
  export type {
@@ -0,0 +1,104 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { PresentationsApi } from './presentations-api';
3
+
4
+ function makeApi() {
5
+ const fetch = { get: vi.fn(), post: vi.fn(), patch: vi.fn(), delete: vi.fn() };
6
+ const api = new PresentationsApi({ fetch } as never);
7
+ return { api, fetch };
8
+ }
9
+
10
+ describe('PresentationsApi', () => {
11
+ let api: PresentationsApi;
12
+ let fetch: { get: ReturnType<typeof vi.fn>; post: ReturnType<typeof vi.fn>; patch: ReturnType<typeof vi.fn>; delete: ReturnType<typeof vi.fn> };
13
+
14
+ beforeEach(() => {
15
+ ({ api, fetch } = makeApi());
16
+ });
17
+
18
+ it('listDecks hits the decks endpoint with params', async () => {
19
+ fetch.get.mockResolvedValue({ results: [], count: 0 });
20
+ await api.listDecks({ page: 2 });
21
+ expect(fetch.get).toHaveBeenCalledWith('api/v1/presentations/decks', { params: { page: 2 } });
22
+ });
23
+
24
+ it('getDeck fetches a single deck by id', async () => {
25
+ fetch.get.mockResolvedValue({ id: 'd1', title: 'Pitch' });
26
+ const d = await api.getDeck('d1');
27
+ expect(fetch.get).toHaveBeenCalledWith('api/v1/presentations/decks/d1');
28
+ expect(d.title).toBe('Pitch');
29
+ });
30
+
31
+ it('createDeck posts the deck input', async () => {
32
+ fetch.post.mockResolvedValue({ id: 'd1', title: 'Pitch' });
33
+ await api.createDeck({ title: 'Pitch', outline: 'Slide 1...', audience: 'VCs' });
34
+ expect(fetch.post).toHaveBeenCalledWith('api/v1/presentations/decks', {
35
+ title: 'Pitch',
36
+ outline: 'Slide 1...',
37
+ audience: 'VCs',
38
+ });
39
+ });
40
+
41
+ it('updateDeck patches by id', async () => {
42
+ fetch.patch.mockResolvedValue({ id: 'd1', title: 'New' });
43
+ await api.updateDeck('d1', { title: 'New' });
44
+ expect(fetch.patch).toHaveBeenCalledWith('api/v1/presentations/decks/d1', { title: 'New' });
45
+ });
46
+
47
+ it('deleteDeck deletes by id', async () => {
48
+ fetch.delete.mockResolvedValue(undefined);
49
+ await api.deleteDeck('d1');
50
+ expect(fetch.delete).toHaveBeenCalledWith('api/v1/presentations/decks/d1');
51
+ });
52
+
53
+ it('generateDeck kicks off generation (derive style + slides)', async () => {
54
+ fetch.post.mockResolvedValue({ id: 'd1', status: 'generating' });
55
+ await api.generateDeck('d1', { outline: 'Slide 1: Title' });
56
+ expect(fetch.post).toHaveBeenCalledWith('api/v1/presentations/decks/d1/generate', {
57
+ outline: 'Slide 1: Title',
58
+ });
59
+ });
60
+
61
+ it('getDeckStatus polls the status endpoint', async () => {
62
+ fetch.get.mockResolvedValue({ status: 'generating', slides: [] });
63
+ await api.getDeckStatus('d1');
64
+ expect(fetch.get).toHaveBeenCalledWith('api/v1/presentations/decks/d1/status');
65
+ });
66
+
67
+ it('listSlides fetches the deck slides', async () => {
68
+ fetch.get.mockResolvedValue([{ id: 's1', slideNumber: 1 }]);
69
+ await api.listSlides('d1');
70
+ expect(fetch.get).toHaveBeenCalledWith('api/v1/presentations/decks/d1/slides');
71
+ });
72
+
73
+ it('updateSlide patches a slide by number', async () => {
74
+ fetch.patch.mockResolvedValue({ id: 's2', slideNumber: 2 });
75
+ await api.updateSlide('d1', 2, { content: 'Revised' });
76
+ expect(fetch.patch).toHaveBeenCalledWith('api/v1/presentations/decks/d1/slides/2', {
77
+ content: 'Revised',
78
+ });
79
+ });
80
+
81
+ it('regenerateSlide posts an instruction for one slide', async () => {
82
+ fetch.post.mockResolvedValue({ id: 's2', slideNumber: 2, status: 'generating' });
83
+ await api.regenerateSlide('d1', 2, { instruction: 'make it punchier' });
84
+ expect(fetch.post).toHaveBeenCalledWith('api/v1/presentations/decks/d1/slides/2/regenerate', {
85
+ instruction: 'make it punchier',
86
+ });
87
+ });
88
+
89
+ it('reorderSlides posts the new order', async () => {
90
+ fetch.post.mockResolvedValue(undefined);
91
+ await api.reorderSlides('d1', [3, 1, 2]);
92
+ expect(fetch.post).toHaveBeenCalledWith('api/v1/presentations/decks/d1/slides/reorder', {
93
+ order: [3, 1, 2],
94
+ });
95
+ });
96
+
97
+ it('compileDeck builds PPTX + PDF and returns artifact urls', async () => {
98
+ fetch.post.mockResolvedValue({ status: 'ready', pptxUrl: 'p.pptx', pdfUrl: 'p.pdf' });
99
+ const r = await api.compileDeck('d1');
100
+ expect(fetch.post).toHaveBeenCalledWith('api/v1/presentations/decks/d1/compile', undefined);
101
+ expect(r.pptxUrl).toBe('p.pptx');
102
+ expect(r.pdfUrl).toBe('p.pdf');
103
+ });
104
+ });
@@ -0,0 +1,166 @@
1
+ /** Presentations API client — AI-generated slide decks (epic startsim-3ks).
2
+
3
+ Mirrors the other @startsimpli/api wrappers and the proven n8n "Slide Deck
4
+ Agent v9" pipeline: a deck has ONE derived style spec applied verbatim to every
5
+ slide; each slide is a full-bleed 16:9 image (image render mode), compiled into
6
+ PPTX + PDF. The client auto-transforms snake_case <-> camelCase, so types here
7
+ are camelCase.
8
+ */
9
+
10
+ import type { PaginatedResponse } from '../types';
11
+ import type { ApiClient } from './api-client';
12
+
13
+ const E = {
14
+ decks: 'api/v1/presentations/decks',
15
+ deck: (id: string) => `api/v1/presentations/decks/${id}`,
16
+ generate: (id: string) => `api/v1/presentations/decks/${id}/generate`,
17
+ compile: (id: string) => `api/v1/presentations/decks/${id}/compile`,
18
+ status: (id: string) => `api/v1/presentations/decks/${id}/status`,
19
+ slides: (id: string) => `api/v1/presentations/decks/${id}/slides`,
20
+ slide: (id: string, n: number) => `api/v1/presentations/decks/${id}/slides/${n}`,
21
+ regenerate: (id: string, n: number) => `api/v1/presentations/decks/${id}/slides/${n}/regenerate`,
22
+ reorder: (id: string) => `api/v1/presentations/decks/${id}/slides/reorder`,
23
+ };
24
+
25
+ export type DeckStatus = 'draft' | 'generating' | 'ready' | 'error';
26
+ export type SlideStatus = 'pending' | 'generating' | 'ready' | 'error';
27
+ export type RenderMode = 'image' | 'structured';
28
+
29
+ export interface Slide {
30
+ id: string;
31
+ deckId: string;
32
+ slideNumber: number;
33
+ title?: string;
34
+ /** Raw text content / outline for this slide. */
35
+ content: string;
36
+ /** The full image-generation prompt (style spec + slide content). */
37
+ imagePrompt?: string;
38
+ /** Full-bleed slide image (image render mode). */
39
+ imageUrl?: string;
40
+ status: SlideStatus;
41
+ renderMode: RenderMode;
42
+ createdAt: string;
43
+ updatedAt: string;
44
+ }
45
+
46
+ export interface Deck {
47
+ id: string;
48
+ title: string;
49
+ /** Pasted outline the deck was generated from. */
50
+ outline: string;
51
+ /** Free-text audience / context that informs the style spec. */
52
+ audience: string;
53
+ /** The single derived STYLE SPEC, reused verbatim on every slide. */
54
+ styleSpec: string;
55
+ status: DeckStatus;
56
+ renderMode: RenderMode;
57
+ slideCount: number;
58
+ /** Compiled artifacts (present once compiled). */
59
+ pptxUrl?: string | null;
60
+ pdfUrl?: string | null;
61
+ createdAt: string;
62
+ updatedAt: string;
63
+ /** Present on detail responses. */
64
+ slides?: Slide[];
65
+ }
66
+
67
+ export interface DeckInput {
68
+ title: string;
69
+ outline?: string;
70
+ audience?: string;
71
+ renderMode?: RenderMode;
72
+ }
73
+
74
+ export interface GenerateDeckInput {
75
+ /** Outline to (re)generate from; defaults to the deck's stored outline. */
76
+ outline?: string;
77
+ /** Optional explicit style spec; otherwise the backend derives one. */
78
+ styleSpec?: string;
79
+ }
80
+
81
+ export interface SlideInput {
82
+ title?: string;
83
+ content?: string;
84
+ imagePrompt?: string;
85
+ }
86
+
87
+ export interface RegenerateSlideInput {
88
+ /** Revised content for the slide. */
89
+ content?: string;
90
+ /** Natural-language change request ("make it punchier"). */
91
+ instruction?: string;
92
+ }
93
+
94
+ /** Lightweight per-slide progress for polling during generation. */
95
+ export interface DeckGenerationStatus {
96
+ status: DeckStatus;
97
+ slides: Array<{
98
+ slideNumber: number;
99
+ status: SlideStatus;
100
+ imageUrl?: string;
101
+ }>;
102
+ }
103
+
104
+ export interface CompileResult {
105
+ status: DeckStatus;
106
+ pptxUrl: string;
107
+ pdfUrl: string;
108
+ }
109
+
110
+ export interface DeckListParams {
111
+ page?: number;
112
+ pageSize?: number;
113
+ search?: string;
114
+ ordering?: string;
115
+ [key: string]: unknown;
116
+ }
117
+
118
+ export class PresentationsApi {
119
+ constructor(private client: ApiClient) {}
120
+
121
+ // --- Decks ---
122
+ listDecks(params?: DeckListParams): Promise<PaginatedResponse<Deck>> {
123
+ return this.client.fetch.get<PaginatedResponse<Deck>>(E.decks, { params });
124
+ }
125
+ getDeck(id: string): Promise<Deck> {
126
+ return this.client.fetch.get<Deck>(E.deck(id));
127
+ }
128
+ createDeck(data: DeckInput): Promise<Deck> {
129
+ return this.client.fetch.post<Deck>(E.decks, data);
130
+ }
131
+ updateDeck(id: string, data: Partial<DeckInput>): Promise<Deck> {
132
+ return this.client.fetch.patch<Deck>(E.deck(id), data);
133
+ }
134
+ deleteDeck(id: string): Promise<void> {
135
+ return this.client.fetch.delete<void>(E.deck(id));
136
+ }
137
+
138
+ // --- Generation pipeline (mirrors the n8n agent tools) ---
139
+ /** Derive the style spec (if absent) and generate every slide image async. */
140
+ generateDeck(id: string, data?: GenerateDeckInput): Promise<Deck> {
141
+ return this.client.fetch.post<Deck>(E.generate(id), data);
142
+ }
143
+ /** Per-slide progress, for polling while generation/compile runs. */
144
+ getDeckStatus(id: string): Promise<DeckGenerationStatus> {
145
+ return this.client.fetch.get<DeckGenerationStatus>(E.status(id));
146
+ }
147
+ /** Build the Google-Slides-equivalent deck and export PPTX + PDF. */
148
+ compileDeck(id: string): Promise<CompileResult> {
149
+ return this.client.fetch.post<CompileResult>(E.compile(id), undefined);
150
+ }
151
+
152
+ // --- Slides ---
153
+ listSlides(id: string): Promise<Slide[]> {
154
+ return this.client.fetch.get<Slide[]>(E.slides(id));
155
+ }
156
+ updateSlide(id: string, slideNumber: number, data: SlideInput): Promise<Slide> {
157
+ return this.client.fetch.patch<Slide>(E.slide(id, slideNumber), data);
158
+ }
159
+ /** Regenerate a single slide image (keeps the deck style spec). */
160
+ regenerateSlide(id: string, slideNumber: number, data?: RegenerateSlideInput): Promise<Slide> {
161
+ return this.client.fetch.post<Slide>(E.regenerate(id, slideNumber), data);
162
+ }
163
+ reorderSlides(id: string, order: number[]): Promise<void> {
164
+ return this.client.fetch.post<void>(E.reorder(id), { order });
165
+ }
166
+ }
@@ -0,0 +1,129 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { WorkflowsApi } from './workflows-api';
3
+
4
+ function makeApi() {
5
+ const fetch = { get: vi.fn(), post: vi.fn(), patch: vi.fn(), delete: vi.fn() };
6
+ const api = new WorkflowsApi({ fetch } as never);
7
+ return { api, fetch };
8
+ }
9
+
10
+ describe('WorkflowsApi', () => {
11
+ let api: WorkflowsApi;
12
+ let fetch: {
13
+ get: ReturnType<typeof vi.fn>;
14
+ post: ReturnType<typeof vi.fn>;
15
+ patch: ReturnType<typeof vi.fn>;
16
+ delete: ReturnType<typeof vi.fn>;
17
+ };
18
+
19
+ beforeEach(() => {
20
+ ({ api, fetch } = makeApi());
21
+ });
22
+
23
+ // --- Workflows CRUD ---
24
+ it('listWorkflows hits the workflows endpoint with params', async () => {
25
+ fetch.get.mockResolvedValue({ results: [], count: 0 });
26
+ await api.listWorkflows({ page: 2 });
27
+ expect(fetch.get).toHaveBeenCalledWith('api/v1/workflows', { params: { page: 2 } });
28
+ });
29
+
30
+ it('getWorkflow fetches a single workflow by id', async () => {
31
+ fetch.get.mockResolvedValue({ id: 'w1', name: 'Onboarding' });
32
+ const w = await api.getWorkflow('w1');
33
+ expect(fetch.get).toHaveBeenCalledWith('api/v1/workflows/w1');
34
+ expect(w.name).toBe('Onboarding');
35
+ });
36
+
37
+ it('createWorkflow posts the workflow input', async () => {
38
+ fetch.post.mockResolvedValue({ id: 'w1', name: 'Onboarding' });
39
+ await api.createWorkflow({ name: 'Onboarding', description: 'desc' });
40
+ expect(fetch.post).toHaveBeenCalledWith('api/v1/workflows', {
41
+ name: 'Onboarding',
42
+ description: 'desc',
43
+ });
44
+ });
45
+
46
+ it('updateWorkflow patches by id', async () => {
47
+ fetch.patch.mockResolvedValue({ id: 'w1', name: 'New' });
48
+ await api.updateWorkflow('w1', { name: 'New' });
49
+ expect(fetch.patch).toHaveBeenCalledWith('api/v1/workflows/w1', { name: 'New' });
50
+ });
51
+
52
+ it('deleteWorkflow deletes by id', async () => {
53
+ fetch.delete.mockResolvedValue(undefined);
54
+ await api.deleteWorkflow('w1');
55
+ expect(fetch.delete).toHaveBeenCalledWith('api/v1/workflows/w1');
56
+ });
57
+
58
+ // --- Node types ---
59
+ it('getNodeTypes fetches the node-type registry', async () => {
60
+ fetch.get.mockResolvedValue([
61
+ { slug: 'http', category: 'action', label: 'HTTP Request', parameterSchema: {}, outputLabels: ['main'] },
62
+ ]);
63
+ const types = await api.getNodeTypes();
64
+ expect(fetch.get).toHaveBeenCalledWith('api/v1/workflows/node-types');
65
+ expect(types[0].slug).toBe('http');
66
+ });
67
+
68
+ // --- Execution ---
69
+ it('executeWorkflow posts context and returns an executionId', async () => {
70
+ fetch.post.mockResolvedValue({ executionId: 'e1', status: 'running' });
71
+ const res = await api.executeWorkflow('w1', { foo: 'bar' });
72
+ expect(fetch.post).toHaveBeenCalledWith('api/v1/workflows/w1/execute', { contextData: { foo: 'bar' } });
73
+ expect(res.executionId).toBe('e1');
74
+ expect(res.status).toBe('running');
75
+ });
76
+
77
+ it('executeWorkflow works with no context data', async () => {
78
+ fetch.post.mockResolvedValue({ executionId: 'e2', status: 'pending' });
79
+ await api.executeWorkflow('w1');
80
+ expect(fetch.post).toHaveBeenCalledWith('api/v1/workflows/w1/execute', { contextData: undefined });
81
+ });
82
+
83
+ it('getExecution fetches a single execution detail by executionId', async () => {
84
+ fetch.get.mockResolvedValue({ status: 'running', state: {}, nodeExecutions: [] });
85
+ const e = await api.getExecution('e1');
86
+ expect(fetch.get).toHaveBeenCalledWith('api/v1/workflows/executions/e1');
87
+ expect(e.status).toBe('running');
88
+ });
89
+
90
+ it('listExecutions fetches executions for a workflow with params', async () => {
91
+ fetch.get.mockResolvedValue({ results: [], count: 0 });
92
+ await api.listExecutions('w1', { page: 1 });
93
+ expect(fetch.get).toHaveBeenCalledWith('api/v1/workflows/w1/executions', { params: { page: 1 } });
94
+ });
95
+
96
+ it('cancelExecution posts to the cancel endpoint', async () => {
97
+ fetch.post.mockResolvedValue({ id: 'e1', status: 'cancelled' });
98
+ await api.cancelExecution('e1');
99
+ expect(fetch.post).toHaveBeenCalledWith('api/v1/workflows/executions/e1/cancel', undefined);
100
+ });
101
+
102
+ it('retryExecution posts to the retry endpoint and returns an executionId', async () => {
103
+ fetch.post.mockResolvedValue({ executionId: 'e2', status: 'pending' });
104
+ const res = await api.retryExecution('e1');
105
+ expect(fetch.post).toHaveBeenCalledWith('api/v1/workflows/executions/e1/retry', undefined);
106
+ expect(res.executionId).toBe('e2');
107
+ });
108
+
109
+ // --- Lifecycle ---
110
+ it('cloneWorkflow posts to the clone endpoint', async () => {
111
+ fetch.post.mockResolvedValue({ id: 'w2', name: 'Onboarding (copy)' });
112
+ const w = await api.cloneWorkflow('w1');
113
+ expect(fetch.post).toHaveBeenCalledWith('api/v1/workflows/w1/clone', undefined);
114
+ expect(w.id).toBe('w2');
115
+ });
116
+
117
+ it('publishWorkflow posts to the publish endpoint', async () => {
118
+ fetch.post.mockResolvedValue({ id: 'w1', isActive: true });
119
+ await api.publishWorkflow('w1');
120
+ expect(fetch.post).toHaveBeenCalledWith('api/v1/workflows/w1/publish', undefined);
121
+ });
122
+
123
+ it('getVersions fetches the workflow version history', async () => {
124
+ fetch.get.mockResolvedValue([{ id: 'w1', version: 1 }]);
125
+ const versions = await api.getVersions('w1');
126
+ expect(fetch.get).toHaveBeenCalledWith('api/v1/workflows/w1/versions');
127
+ expect(versions[0].version).toBe(1);
128
+ });
129
+ });
@@ -1,159 +1,205 @@
1
- /**
2
- * Workflows API wrapper
1
+ /** Workflows API client — generic node-based workflow engine (epic startsim-xsh.11).
3
2
  *
4
- * Provides type-safe access to Django Workflows API
3
+ * Mirrors the other @startsimpli/api wrappers: an E-map of endpoints under
4
+ * `api/v1/workflows/`, the `client.fetch.{get,post,patch,delete}` convention,
5
+ * and the auto snake_case <-> camelCase transform (so all types here are
6
+ * camelCase).
7
+ *
8
+ * The execution detail type (`WorkflowExecutionDetail`) is deliberately shaped
9
+ * to match present-web's `run-to-builder-state.ts` `WorkflowRunDetail`, so a
10
+ * `getExecution(...)` response can be fed straight into `runToBuilderState`.
5
11
  */
6
12
 
13
+ import type { PaginatedResponse } from '../types';
7
14
  import type { ApiClient } from './api-client';
8
15
 
9
- export type WorkflowStatus = 'draft' | 'active' | 'paused';
16
+ const E = {
17
+ workflows: 'api/v1/workflows',
18
+ workflow: (id: string) => `api/v1/workflows/${id}`,
19
+ nodeTypes: 'api/v1/workflows/node-types',
20
+ execute: (id: string) => `api/v1/workflows/${id}/execute`,
21
+ executions: (id: string) => `api/v1/workflows/${id}/executions`,
22
+ execution: (executionId: string) => `api/v1/workflows/executions/${executionId}`,
23
+ cancel: (executionId: string) => `api/v1/workflows/executions/${executionId}/cancel`,
24
+ retry: (executionId: string) => `api/v1/workflows/executions/${executionId}/retry`,
25
+ clone: (id: string) => `api/v1/workflows/${id}/clone`,
26
+ publish: (id: string) => `api/v1/workflows/${id}/publish`,
27
+ versions: (id: string) => `api/v1/workflows/${id}/versions`,
28
+ };
29
+
30
+ /** Run lifecycle status — matches run-to-builder-state's `WorkflowRunStatus`. */
31
+ export type RunStatus =
32
+ | 'pending'
33
+ | 'running'
34
+ | 'completed'
35
+ | 'failed'
36
+ | 'cancelled'
37
+ | 'waiting';
38
+
39
+ /** Per-node status — matches run-to-builder-state's `WorkflowNodeStatus`. */
40
+ export type NodeStatus =
41
+ | 'idle'
42
+ | 'pending'
43
+ | 'running'
44
+ | 'success'
45
+ | 'error'
46
+ | 'skipped';
47
+
48
+ export type ExecutionMode = 'manual' | 'trigger' | 'webhook';
49
+
50
+ /** A node placed in a workflow graph (engine-defined shape). */
51
+ export interface WorkflowNode {
52
+ id: string;
53
+ type: string;
54
+ parameters?: Record<string, unknown>;
55
+ position?: [number, number];
56
+ [key: string]: unknown;
57
+ }
10
58
 
11
59
  export interface Workflow {
12
60
  id: string;
13
61
  name: string;
14
62
  description: string;
15
- team: string;
16
- createdBy: string | null;
17
- nodes: any[];
18
- connections: Record<string, any>;
19
- settings: Record<string, any>;
20
- staticData: Record<string, any>;
63
+ nodes: WorkflowNode[];
64
+ connections: Record<string, unknown>;
21
65
  isActive: boolean;
22
- version: number;
23
- isTemplate: boolean;
24
- templateSource: string | null;
25
- executionsCount: number;
26
- createdAt: string;
27
- updatedAt: string;
66
+ version?: number;
67
+ isTemplate?: boolean;
68
+ settings?: Record<string, unknown>;
69
+ executionsCount?: number;
70
+ createdAt?: string;
71
+ updatedAt?: string;
72
+ }
73
+
74
+ /** A registered node type the builder can place. */
75
+ export interface NodeTypeDef {
76
+ slug: string;
77
+ category: string;
78
+ label: string;
79
+ /** JSON-schema-ish description of the node's parameters. */
80
+ parameterSchema: Record<string, unknown>;
81
+ /** Names of the node's output connectors. */
82
+ outputLabels: string[];
28
83
  }
29
84
 
85
+ /** Lightweight execution summary (list rows). */
30
86
  export interface WorkflowExecution {
31
87
  id: string;
32
- workflow: string;
33
- workflowVersion: number;
34
- triggeredBy: string;
35
- status: 'pending' | 'running' | 'completed' | 'failed' | 'paused';
36
- mode: 'manual' | 'trigger' | 'webhook';
37
- contextData: Record<string, any>;
38
- state: Record<string, any>;
88
+ workflowId: string;
89
+ status: RunStatus;
90
+ mode: ExecutionMode;
91
+ source?: string;
39
92
  startedAt: string | null;
40
93
  completedAt: string | null;
41
- waitUntil: string | null;
42
- errorMessage: string | null;
43
- createdAt: string;
44
- updatedAt: string;
45
94
  }
46
95
 
47
- export interface WorkflowFilters {
48
- team?: string;
49
- isActive?: boolean;
50
- isTemplate?: boolean;
51
- page?: number;
52
- pageSize?: number;
53
- ordering?: string;
96
+ /** One node's execution record — matches run-to-builder-state's `RunNodeExecution`. */
97
+ export interface WorkflowNodeExecution {
98
+ nodeId: string;
99
+ nodeName?: string;
100
+ nodeType?: string;
101
+ status: NodeStatus;
102
+ executionOrder?: number;
103
+ output?: Record<string, unknown> | null;
104
+ }
105
+
106
+ /** Full execution detail. Structurally matches present-web's `WorkflowRunDetail`
107
+ * so a response can be passed directly to `runToBuilderState`. */
108
+ export interface WorkflowExecutionDetail {
109
+ status: RunStatus;
110
+ state: Record<string, unknown>;
111
+ nodeExecutions: WorkflowNodeExecution[];
112
+ contextData?: Record<string, unknown>;
54
113
  }
55
114
 
56
- export interface ExecuteWorkflowInput {
57
- contextData?: Record<string, any>;
58
- mode?: 'manual' | 'trigger' | 'webhook';
115
+ /** The contract returned by POST execute/retry: carries the executionId. */
116
+ export interface ExecuteWorkflowResponse {
117
+ executionId: string;
118
+ status: RunStatus;
59
119
  }
60
120
 
61
- export interface CreateWorkflowInput {
121
+ export interface WorkflowInput {
62
122
  name: string;
63
123
  description?: string;
64
- team: string;
65
- nodes?: any[];
66
- connections?: Record<string, any>;
67
- settings?: Record<string, any>;
124
+ nodes?: WorkflowNode[];
125
+ connections?: Record<string, unknown>;
68
126
  isActive?: boolean;
127
+ settings?: Record<string, unknown>;
128
+ }
129
+
130
+ export interface WorkflowListParams {
131
+ page?: number;
132
+ pageSize?: number;
133
+ search?: string;
134
+ ordering?: string;
135
+ isActive?: boolean;
136
+ isTemplate?: boolean;
137
+ [key: string]: unknown;
138
+ }
139
+
140
+ export interface ExecutionListParams {
141
+ page?: number;
142
+ pageSize?: number;
143
+ status?: RunStatus;
144
+ ordering?: string;
145
+ [key: string]: unknown;
69
146
  }
70
147
 
71
148
  export class WorkflowsApi {
72
149
  constructor(private client: ApiClient) {}
73
150
 
74
- /**
75
- * List workflows with optional filters
76
- */
77
- async list(filters?: WorkflowFilters) {
78
- const params = new URLSearchParams();
79
-
80
- if (filters?.team) params.append('team', filters.team);
81
- if (filters?.isActive !== undefined) params.append('isActive', String(filters.isActive));
82
- if (filters?.isTemplate !== undefined) params.append('isTemplate', String(filters.isTemplate));
83
- if (filters?.page) params.append('page', String(filters.page));
84
- if (filters?.pageSize) params.append('pageSize', String(filters.pageSize));
85
- if (filters?.ordering) params.append('ordering', filters.ordering);
86
-
87
- return this.client.get<{ results: Workflow[]; count: number; next: string | null; previous: string | null }>(
88
- `/api/v1/workflows/?${params.toString()}`
89
- );
151
+ // --- Workflows ---
152
+ listWorkflows(params?: WorkflowListParams): Promise<PaginatedResponse<Workflow>> {
153
+ return this.client.fetch.get<PaginatedResponse<Workflow>>(E.workflows, { params });
90
154
  }
91
-
92
- /**
93
- * Get workflow by ID
94
- */
95
- async get(id: string) {
96
- return this.client.get<Workflow>(`/api/v1/workflows/${id}/`);
155
+ getWorkflow(id: string): Promise<Workflow> {
156
+ return this.client.fetch.get<Workflow>(E.workflow(id));
97
157
  }
98
-
99
- /**
100
- * Create a new workflow
101
- */
102
- async create(data: CreateWorkflowInput) {
103
- return this.client.post<Workflow>('/api/v1/workflows/', data);
158
+ createWorkflow(data: WorkflowInput): Promise<Workflow> {
159
+ return this.client.fetch.post<Workflow>(E.workflows, data);
104
160
  }
105
-
106
- /**
107
- * Update workflow
108
- */
109
- async update(id: string, data: Partial<CreateWorkflowInput>) {
110
- return this.client.patch<Workflow>(`/api/v1/workflows/${id}/`, data);
161
+ updateWorkflow(id: string, data: Partial<WorkflowInput>): Promise<Workflow> {
162
+ return this.client.fetch.patch<Workflow>(E.workflow(id), data);
111
163
  }
112
-
113
- /**
114
- * Delete workflow
115
- */
116
- async delete(id: string) {
117
- return this.client.delete(`/api/v1/workflows/${id}/`);
164
+ deleteWorkflow(id: string): Promise<void> {
165
+ return this.client.fetch.delete<void>(E.workflow(id));
118
166
  }
119
167
 
120
- /**
121
- * Execute workflow
122
- */
123
- async execute(id: string, input?: ExecuteWorkflowInput) {
124
- return this.client.post<WorkflowExecution>(
125
- `/api/v1/workflows/${id}/execute/`,
126
- input || {}
127
- );
168
+ // --- Node type registry ---
169
+ getNodeTypes(): Promise<NodeTypeDef[]> {
170
+ return this.client.fetch.get<NodeTypeDef[]>(E.nodeTypes);
128
171
  }
129
172
 
130
- /**
131
- * Get workflow templates
132
- */
133
- async templates() {
134
- return this.client.get<Workflow[]>('/api/v1/workflows/templates/');
173
+ // --- Execution ---
174
+ /** Kick off a run. The response carries the executionId (the contract). */
175
+ executeWorkflow(id: string, contextData?: Record<string, unknown>): Promise<ExecuteWorkflowResponse> {
176
+ return this.client.fetch.post<ExecuteWorkflowResponse>(E.execute(id), { contextData });
135
177
  }
136
-
137
- /**
138
- * List workflow executions
139
- */
140
- async listExecutions(filters?: { workflow?: string; status?: string; page?: number; pageSize?: number }) {
141
- const params = new URLSearchParams();
142
-
143
- if (filters?.workflow) params.append('workflow', filters.workflow);
144
- if (filters?.status) params.append('status', filters.status);
145
- if (filters?.page) params.append('page', String(filters.page));
146
- if (filters?.pageSize) params.append('pageSize', String(filters.pageSize));
147
-
148
- return this.client.get<{ results: WorkflowExecution[]; count: number }>(
149
- `/api/v1/workflow-executions/?${params.toString()}`
150
- );
178
+ getExecution(executionId: string): Promise<WorkflowExecutionDetail> {
179
+ return this.client.fetch.get<WorkflowExecutionDetail>(E.execution(executionId));
180
+ }
181
+ listExecutions(
182
+ workflowId: string,
183
+ params?: ExecutionListParams,
184
+ ): Promise<PaginatedResponse<WorkflowExecution>> {
185
+ return this.client.fetch.get<PaginatedResponse<WorkflowExecution>>(E.executions(workflowId), { params });
186
+ }
187
+ cancelExecution(executionId: string): Promise<WorkflowExecution> {
188
+ return this.client.fetch.post<WorkflowExecution>(E.cancel(executionId), undefined);
189
+ }
190
+ /** Re-run a failed/cancelled execution; response carries the new executionId. */
191
+ retryExecution(executionId: string): Promise<ExecuteWorkflowResponse> {
192
+ return this.client.fetch.post<ExecuteWorkflowResponse>(E.retry(executionId), undefined);
151
193
  }
152
194
 
153
- /**
154
- * Get workflow execution details
155
- */
156
- async getExecution(id: string) {
157
- return this.client.get<WorkflowExecution>(`/api/v1/workflow-executions/${id}/`);
195
+ // --- Lifecycle ---
196
+ cloneWorkflow(id: string): Promise<Workflow> {
197
+ return this.client.fetch.post<Workflow>(E.clone(id), undefined);
198
+ }
199
+ publishWorkflow(id: string): Promise<Workflow> {
200
+ return this.client.fetch.post<Workflow>(E.publish(id), undefined);
201
+ }
202
+ getVersions(id: string): Promise<Workflow[]> {
203
+ return this.client.fetch.get<Workflow[]>(E.versions(id));
158
204
  }
159
205
  }