@autopo/mcp 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +119 -2
- package/dist/api.d.ts +51 -0
- package/dist/api.js +107 -0
- package/dist/api.js.map +1 -0
- package/dist/apps.d.ts +10 -0
- package/dist/apps.js +146 -0
- package/dist/apps.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +63 -0
- package/dist/cli.js.map +1 -0
- package/dist/guides.d.ts +3 -0
- package/dist/guides.js +52 -0
- package/dist/guides.js.map +1 -0
- package/dist/http.d.ts +20 -0
- package/dist/http.js +119 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/kit.d.ts +149 -0
- package/dist/kit.js +206 -0
- package/dist/kit.js.map +1 -0
- package/dist/prompts.d.ts +3 -0
- package/dist/prompts.js +44 -0
- package/dist/prompts.js.map +1 -0
- package/dist/resources.d.ts +4 -0
- package/dist/resources.js +40 -0
- package/dist/resources.js.map +1 -0
- package/dist/server.d.ts +24 -0
- package/dist/server.js +40 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/company.d.ts +4 -0
- package/dist/tools/company.js +431 -0
- package/dist/tools/company.js.map +1 -0
- package/dist/tools/marketing.d.ts +4 -0
- package/dist/tools/marketing.js +214 -0
- package/dist/tools/marketing.js.map +1 -0
- package/dist/tools/studio.d.ts +9 -0
- package/dist/tools/studio.js +363 -0
- package/dist/tools/studio.js.map +1 -0
- package/dist/ui/gallery.html +124 -0
- package/dist/ui/home.html +124 -0
- package/dist/ui/inbox.html +124 -0
- package/dist/ui/money.html +124 -0
- package/dist/ui/storyboard.html +124 -0
- package/package.json +71 -4
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { trimmed } from '../api.js';
|
|
3
|
+
import { COMPANY_ARG, PAGE_ARGS, answer, askToOpen, confirm, define, isInputRequired, resolveCompany, text } from '../kit.js';
|
|
4
|
+
/** Where things live in the web app, by what the person wants to open. */
|
|
5
|
+
const PLACES = {
|
|
6
|
+
home: (c) => `/c/${c}/overview`,
|
|
7
|
+
hive: (c) => `/c/${c}/hive`,
|
|
8
|
+
inbox: (c, id) => (id ? `/c/${c}/inbox?card=${id}` : `/c/${c}/inbox`),
|
|
9
|
+
task: (c, id) => (id ? `/c/${c}/work?task=${id}` : `/c/${c}/work`),
|
|
10
|
+
work: (c) => `/c/${c}/work`,
|
|
11
|
+
team: (c) => `/c/${c}/team`,
|
|
12
|
+
chat: (c, id) => (id ? `/c/${c}/dm/${id}` : `/c/${c}/dm`),
|
|
13
|
+
studio: (c) => `/c/${c}/studio`,
|
|
14
|
+
video: (c, id) => (id ? `/c/${c}/studio/${id}` : `/c/${c}/studio`),
|
|
15
|
+
marketing: (c) => `/c/${c}/marketing`,
|
|
16
|
+
social: (c) => `/c/${c}/social`,
|
|
17
|
+
money: (c) => `/c/${c}/money`,
|
|
18
|
+
connectors: (c) => `/c/${c}/connectors`,
|
|
19
|
+
settings: (c) => `/c/${c}/settings`,
|
|
20
|
+
};
|
|
21
|
+
export function registerMarketingAndApiTools(server, s) {
|
|
22
|
+
// --- marketing and social ------------------------------------------------------------------------------------------
|
|
23
|
+
define(server, s, 'brand_brain', {
|
|
24
|
+
title: 'Brand brain',
|
|
25
|
+
description: 'The brand brain: what the company says about itself, for whom, against whom, in what voice (every writer reads it).',
|
|
26
|
+
input: { ...COMPANY_ARG },
|
|
27
|
+
group: 'marketing',
|
|
28
|
+
}, async (args, ctx) => {
|
|
29
|
+
const cid = await resolveCompany(s, args, ctx);
|
|
30
|
+
if (isInputRequired(cid))
|
|
31
|
+
return cid;
|
|
32
|
+
return s.api.get(`/api/companies/${cid}/marketing/brain`);
|
|
33
|
+
});
|
|
34
|
+
define(server, s, 'social_accounts', {
|
|
35
|
+
title: 'Social accounts',
|
|
36
|
+
description: 'The company’s social accounts and networks, and what each account may post on its own.',
|
|
37
|
+
input: { ...COMPANY_ARG },
|
|
38
|
+
group: 'marketing',
|
|
39
|
+
}, async (args, ctx) => {
|
|
40
|
+
const cid = await resolveCompany(s, args, ctx);
|
|
41
|
+
if (isInputRequired(cid))
|
|
42
|
+
return cid;
|
|
43
|
+
return s.api.get(`/api/companies/${cid}/social`);
|
|
44
|
+
});
|
|
45
|
+
define(server, s, 'list_posts', {
|
|
46
|
+
title: 'List posts',
|
|
47
|
+
description: 'Social posts: upcoming, published or failed (`status`), per `account`.',
|
|
48
|
+
input: { ...COMPANY_ARG, status: z.string().optional(), account: z.string().optional(), limit: z.number().int().optional() },
|
|
49
|
+
group: 'marketing',
|
|
50
|
+
}, async (args, ctx) => {
|
|
51
|
+
const cid = await resolveCompany(s, args, ctx);
|
|
52
|
+
if (isInputRequired(cid))
|
|
53
|
+
return cid;
|
|
54
|
+
return trimmed(await s.api.get(`/api/companies/${cid}/social/posts`, { status: args.status, account: args.account, limit: args.limit }), 30);
|
|
55
|
+
});
|
|
56
|
+
define(server, s, 'check_posts', {
|
|
57
|
+
title: 'Check posts',
|
|
58
|
+
description: 'Run the pre-publish gate on posts you wrote (nothing is posted): each post’s review against the brand.',
|
|
59
|
+
input: { ...COMPANY_ARG, posts: z.array(z.record(z.string(), z.unknown())) },
|
|
60
|
+
group: 'marketing',
|
|
61
|
+
kind: 'write',
|
|
62
|
+
}, async (args, ctx) => {
|
|
63
|
+
const cid = await resolveCompany(s, args, ctx);
|
|
64
|
+
if (isInputRequired(cid))
|
|
65
|
+
return cid;
|
|
66
|
+
return s.api.post(`/api/companies/${cid}/marketing/check`, { posts: args.posts });
|
|
67
|
+
});
|
|
68
|
+
define(server, s, 'create_posts', {
|
|
69
|
+
title: 'Post to social',
|
|
70
|
+
description: 'Post as yourself (no card): `posts` [{account, text, media?}], now or at `schedule_at` (ISO). You confirm in a dialog: a post cannot be unpublished from here.',
|
|
71
|
+
input: { ...COMPANY_ARG, posts: z.array(z.record(z.string(), z.unknown())).min(1), schedule_at: z.string().optional() },
|
|
72
|
+
group: 'marketing',
|
|
73
|
+
kind: 'destructive',
|
|
74
|
+
}, async (args, ctx) => {
|
|
75
|
+
const cid = await resolveCompany(s, args, ctx);
|
|
76
|
+
if (isInputRequired(cid))
|
|
77
|
+
return cid;
|
|
78
|
+
const preview = args.posts.map((p) => `• ${String(p.account ?? '?')}: ${String(p.text ?? '').slice(0, 120)}`).join('\n');
|
|
79
|
+
const when = args.schedule_at ? `at ${args.schedule_at}` : 'now';
|
|
80
|
+
const ok = confirm(ctx, 'post', `Publish ${args.posts.length} post${args.posts.length > 1 ? 's' : ''} ${when}?\n${preview}`, 'Yes, post', 'No');
|
|
81
|
+
if (ok !== true)
|
|
82
|
+
return ok;
|
|
83
|
+
const body = { posts: args.posts };
|
|
84
|
+
if (args.schedule_at)
|
|
85
|
+
body.schedule_at = args.schedule_at;
|
|
86
|
+
return s.api.post(`/api/companies/${cid}/social/posts`, body);
|
|
87
|
+
});
|
|
88
|
+
define(server, s, 'marketing_learnings', {
|
|
89
|
+
title: 'Marketing learnings',
|
|
90
|
+
description: 'What worked and what didn’t: the learnings log, newest first.',
|
|
91
|
+
input: { ...COMPANY_ARG, status: z.string().optional() },
|
|
92
|
+
group: 'marketing',
|
|
93
|
+
}, async (args, ctx) => {
|
|
94
|
+
const cid = await resolveCompany(s, args, ctx);
|
|
95
|
+
if (isInputRequired(cid))
|
|
96
|
+
return cid;
|
|
97
|
+
return trimmed(await s.api.get(`/api/companies/${cid}/marketing/learnings`, { status: args.status }), 30);
|
|
98
|
+
});
|
|
99
|
+
// --- money -----------------------------------------------------------------------------------------------------------
|
|
100
|
+
define(server, s, 'budget', {
|
|
101
|
+
title: 'Budget',
|
|
102
|
+
description: 'The month’s spend against the budget, by agent and kind, and the credits left.',
|
|
103
|
+
input: { ...COMPANY_ARG },
|
|
104
|
+
group: 'money',
|
|
105
|
+
ui: 'ui://autopoiesis/money.html',
|
|
106
|
+
}, async (args, ctx) => {
|
|
107
|
+
const cid = await resolveCompany(s, args, ctx);
|
|
108
|
+
if (isInputRequired(cid))
|
|
109
|
+
return cid;
|
|
110
|
+
const [budget, credits] = await Promise.all([s.api.get(`/api/companies/${cid}/budget`), s.api.get(`/api/companies/${cid}/credits`).catch(() => null)]);
|
|
111
|
+
return { company: cid, budget, credits };
|
|
112
|
+
});
|
|
113
|
+
define(server, s, 'ledger', {
|
|
114
|
+
title: 'Ledger',
|
|
115
|
+
description: 'Every charge, newest first (`kind`, `agent` filter).',
|
|
116
|
+
input: { ...COMPANY_ARG, kind: z.string().optional(), agent: z.string().optional(), ...PAGE_ARGS },
|
|
117
|
+
group: 'money',
|
|
118
|
+
}, async (args, ctx) => {
|
|
119
|
+
const cid = await resolveCompany(s, args, ctx);
|
|
120
|
+
if (isInputRequired(cid))
|
|
121
|
+
return cid;
|
|
122
|
+
return trimmed(await s.api.get(`/api/companies/${cid}/ledger`, { kind: args.kind, agent: args.agent, page: args.page, page_size: args.page_size }), 40);
|
|
123
|
+
});
|
|
124
|
+
define(server, s, 'buy_credits', {
|
|
125
|
+
title: 'Buy credits',
|
|
126
|
+
description: 'Open a checkout in your browser to add credits to a company (1 credit = $1). Nothing is charged until you pay there.',
|
|
127
|
+
input: { ...COMPANY_ARG, amount: z.number().int().min(5).max(10000).optional().describe('credits to buy') },
|
|
128
|
+
group: 'money',
|
|
129
|
+
kind: 'write',
|
|
130
|
+
}, async (args, ctx) => {
|
|
131
|
+
const cid = await resolveCompany(s, args, ctx);
|
|
132
|
+
if (isInputRequired(cid))
|
|
133
|
+
return cid;
|
|
134
|
+
const opened = answer(ctx, 'checkout');
|
|
135
|
+
if (opened !== undefined)
|
|
136
|
+
return text(opened === null ? 'Checkout not opened.' : 'Checkout opened in your browser. Credits land when the payment goes through.');
|
|
137
|
+
const checkout = (await s.api.post(`/api/companies/${cid}/credits/checkout`, args.amount ? { amount: args.amount } : {}));
|
|
138
|
+
const url = checkout.url ?? checkout.checkout_url;
|
|
139
|
+
if (!url)
|
|
140
|
+
return text('The platform gave no checkout link. Buy credits on the Money page of the app.', { link: s.link(PLACES.money(cid)) });
|
|
141
|
+
return askToOpen('checkout', url, `Open the checkout for ${args.amount ? `${args.amount} credits` : 'credits'}?`);
|
|
142
|
+
});
|
|
143
|
+
// --- everything else: the whole API ----------------------------------------------------------------------------------
|
|
144
|
+
define(server, s, 'api_request', {
|
|
145
|
+
title: 'Call the API',
|
|
146
|
+
description: 'Call any endpoint of the Autopoiesis REST API as yourself: `method`, `path` (starts with /api/, ids filled in), optional JSON `body` and `query`. api_reference is the manual: use this for anything the other tools don’t cover. Writes are real.',
|
|
147
|
+
input: {
|
|
148
|
+
method: z.enum(['GET', 'POST', 'PATCH', 'PUT', 'DELETE']),
|
|
149
|
+
path: z.string(),
|
|
150
|
+
body: z.record(z.string(), z.unknown()).optional(),
|
|
151
|
+
query: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional(),
|
|
152
|
+
},
|
|
153
|
+
group: 'api',
|
|
154
|
+
kind: 'write',
|
|
155
|
+
}, async (args) => {
|
|
156
|
+
const data = await s.api.request(args.method, args.path, args.body, args.query);
|
|
157
|
+
return data === null || data === undefined ? { ok: true } : data;
|
|
158
|
+
});
|
|
159
|
+
define(server, s, 'api_reference', {
|
|
160
|
+
title: 'API reference',
|
|
161
|
+
description: 'The REST API’s reference (OpenAPI): with no arguments, every endpoint in brief (method, path, summary); `search` narrows by words in the path or summary; `path` (+ `method`) gives one endpoint’s parameters and body.',
|
|
162
|
+
input: { search: z.string().optional(), path: z.string().optional(), method: z.string().optional() },
|
|
163
|
+
group: 'api',
|
|
164
|
+
}, async (args) => {
|
|
165
|
+
const schema = await s.schema();
|
|
166
|
+
const paths = (schema.paths ?? {});
|
|
167
|
+
if (args.path) {
|
|
168
|
+
const entry = paths[args.path] ?? paths[args.path.replace(/\/$/, '')] ?? paths[`${args.path}/`];
|
|
169
|
+
if (!entry)
|
|
170
|
+
return { error: `No endpoint ${args.path}. Try search.` };
|
|
171
|
+
const ops = Object.entries(entry).filter(([m]) => !args.method || m.toUpperCase() === args.method.toUpperCase());
|
|
172
|
+
return { path: args.path, operations: ops.map(([m, op]) => ({ method: m.toUpperCase(), summary: op.summary, description: op.description, tags: op.tags, parameters: op.parameters, requestBody: op.requestBody })) };
|
|
173
|
+
}
|
|
174
|
+
const needle = (args.search ?? '').toLowerCase().trim();
|
|
175
|
+
const out = [];
|
|
176
|
+
for (const [p, ops] of Object.entries(paths)) {
|
|
177
|
+
for (const [m, op] of Object.entries(ops)) {
|
|
178
|
+
if (!['get', 'post', 'patch', 'put', 'delete'].includes(m))
|
|
179
|
+
continue;
|
|
180
|
+
const line = `${m} ${p} ${op.summary ?? ''} ${(op.tags ?? []).join(' ')}`.toLowerCase();
|
|
181
|
+
if (needle && !line.includes(needle))
|
|
182
|
+
continue;
|
|
183
|
+
const row = { method: m.toUpperCase(), path: p };
|
|
184
|
+
if (op.summary)
|
|
185
|
+
row.summary = op.summary;
|
|
186
|
+
if (op.tags?.length)
|
|
187
|
+
row.tags = op.tags;
|
|
188
|
+
out.push(row);
|
|
189
|
+
if (out.length >= 200)
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
if (out.length >= 200)
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
return { count: out.length, endpoints: out, note: out.length >= 200 ? 'Cut at 200: narrow the search.' : undefined };
|
|
196
|
+
});
|
|
197
|
+
define(server, s, 'open_in_app', {
|
|
198
|
+
title: 'Open in the app',
|
|
199
|
+
description: 'Open a place in the Autopoiesis web app in your browser: `what` is home, hive, inbox, task, work, team, chat, studio, video, marketing, social, money, connectors or settings; `id` the card, task, chat or video when there is one.',
|
|
200
|
+
input: { ...COMPANY_ARG, what: z.enum(Object.keys(PLACES)), id: z.string().optional() },
|
|
201
|
+
group: 'company',
|
|
202
|
+
kind: 'write',
|
|
203
|
+
}, async (args, ctx) => {
|
|
204
|
+
const cid = await resolveCompany(s, args, ctx);
|
|
205
|
+
if (isInputRequired(cid))
|
|
206
|
+
return cid;
|
|
207
|
+
const url = s.link(PLACES[args.what](cid, args.id));
|
|
208
|
+
const opened = answer(ctx, 'open');
|
|
209
|
+
if (opened !== undefined)
|
|
210
|
+
return text(opened === null ? `Not opened. The link: ${url}` : `Opened ${url}`, { url });
|
|
211
|
+
return askToOpen('open', url, `Open ${args.what} in the app?`);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=marketing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"marketing.js","sourceRoot":"","sources":["../../src/tools/marketing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Studio: images, clips, voice and music; videos as storyboards made and directed with the Creative Director. The
|
|
3
|
+
* teaser loop from a terminal: create_video → get_video → ask_agent creative (production) → update_video models →
|
|
4
|
+
* video_keyframes (confirm) → critique_video → make_video_media (confirm) → wait_for_video → render_video → wait → open.
|
|
5
|
+
* Anything that spends credits first says what it will cost and asks, through the host's own dialog.
|
|
6
|
+
*/
|
|
7
|
+
import type { McpServer } from '@modelcontextprotocol/server';
|
|
8
|
+
import { Session } from '../kit.js';
|
|
9
|
+
export declare function registerStudioTools(server: McpServer, s: Session): void;
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { pick, rows, trimmed } from '../api.js';
|
|
3
|
+
import { COMPANY_ARG, PAGE_ARGS, confirm, define, isInputRequired, money, resolveCompany } from '../kit.js';
|
|
4
|
+
const MEDIA_KEYS = ['id', 'kind', 'status', 'title', 'model', 'source', 'purpose', 'url', 'thumb_url', 'duration_seconds', 'width', 'height', 'cost_cents', 'error', 'created_at'];
|
|
5
|
+
const MODEL_KEYS = ['key', 'kind', 'purpose', 'label', 'maker', 'short', 'quality', 'speed', 'cents', 'unit', 'aspect_ratios', 'durations', 'available'];
|
|
6
|
+
const VOICE_KEYS = ['key', 'label', 'description', 'gender', 'age', 'accent'];
|
|
7
|
+
const VIDEO_KEYS = ['id', 'title', 'status', 'aspect_ratio', 'running_seconds', 'shot_count', 'updated_at'];
|
|
8
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
|
+
const busy = (status) => Boolean(status && ['queued', 'generating', 'running', 'rendering', 'processing', 'pending'].includes(status));
|
|
10
|
+
/** What a video still waits for: shots whose media, voice or takes are being made; a running render; and what no one
|
|
11
|
+
* has made yet (a generate/footage shot without a picture, a line without its voice). */
|
|
12
|
+
function pending(video) {
|
|
13
|
+
const shots = video.shots ?? [];
|
|
14
|
+
const generating = shots.filter((sh) => busy(sh.media?.status) || busy(sh.voice?.status) || (sh.takes ?? []).some((t) => busy(t.media?.status)));
|
|
15
|
+
const rendering = video.status === 'rendering' || busy(video.output?.status);
|
|
16
|
+
const missing = [];
|
|
17
|
+
for (const sh of shots) {
|
|
18
|
+
const n = sh.order ?? '?';
|
|
19
|
+
if ((sh.visual === 'generate' || sh.visual === 'footage') && !sh.media)
|
|
20
|
+
missing.push(`shot ${n}: no picture yet`);
|
|
21
|
+
if (sh.voiceover && !sh.voice)
|
|
22
|
+
missing.push(`shot ${n}: line not voiced`);
|
|
23
|
+
}
|
|
24
|
+
return { generating: generating.length, rendering, missing };
|
|
25
|
+
}
|
|
26
|
+
/** The brief of a video for a tool result: enough to decide the next step without the whole storyboard. */
|
|
27
|
+
function brief(video) {
|
|
28
|
+
const p = pending(video);
|
|
29
|
+
return {
|
|
30
|
+
id: video.id,
|
|
31
|
+
title: video.title,
|
|
32
|
+
status: video.status,
|
|
33
|
+
shots: (video.shots ?? []).map((sh) => ({ id: sh.id, order: sh.order, visual: sh.visual, picture: sh.media?.status ?? null, voice: sh.voice?.status ?? null, keyframes: (sh.takes ?? []).filter((t) => t.stage === 'keyframe').length, clips: (sh.takes ?? []).filter((t) => t.stage === 'clip').length, seconds: sh.duration_seconds })),
|
|
34
|
+
generating: p.generating,
|
|
35
|
+
rendering: p.rendering,
|
|
36
|
+
missing: p.missing,
|
|
37
|
+
render: video.output ?? null,
|
|
38
|
+
render_error: video.render_error ?? null,
|
|
39
|
+
models: video.style?.models ?? null,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function registerStudioTools(server, s) {
|
|
43
|
+
define(server, s, 'studio_overview', {
|
|
44
|
+
title: 'Studio overview',
|
|
45
|
+
description: "The Studio: every model this company can run (what it is for, price, shapes, availability), the voices, the default models and narrator, providers and the team's allowance.",
|
|
46
|
+
input: { ...COMPANY_ARG },
|
|
47
|
+
group: 'studio',
|
|
48
|
+
}, async (args, ctx) => {
|
|
49
|
+
const cid = await resolveCompany(s, args, ctx);
|
|
50
|
+
if (isInputRequired(cid))
|
|
51
|
+
return cid;
|
|
52
|
+
const data = (await s.api.get(`/api/companies/${cid}/studio`));
|
|
53
|
+
return {
|
|
54
|
+
company: cid,
|
|
55
|
+
...pick(data, ['defaults', 'settings', 'allowance', 'providers', 'placeholder_mode', 'can_render']),
|
|
56
|
+
models: (data.models ?? []).map((m) => pick(m, MODEL_KEYS)),
|
|
57
|
+
voices: (data.voices ?? []).map((v) => pick(v, VOICE_KEYS)),
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
define(server, s, 'studio_settings', {
|
|
61
|
+
title: 'Studio settings',
|
|
62
|
+
description: "Change the Studio's defaults: `default_models` by kind (image, video, voice, music: model keys from studio_overview) and the default narrator `voice` (a voice key).",
|
|
63
|
+
input: { ...COMPANY_ARG, default_models: z.record(z.string(), z.string()).optional(), voice: z.string().optional() },
|
|
64
|
+
group: 'studio',
|
|
65
|
+
kind: 'write',
|
|
66
|
+
}, async (args, ctx) => {
|
|
67
|
+
const cid = await resolveCompany(s, args, ctx);
|
|
68
|
+
if (isInputRequired(cid))
|
|
69
|
+
return cid;
|
|
70
|
+
const body = {};
|
|
71
|
+
if (args.default_models)
|
|
72
|
+
body.default_models = args.default_models;
|
|
73
|
+
if (args.voice)
|
|
74
|
+
body.voice = args.voice;
|
|
75
|
+
return s.api.patch(`/api/companies/${cid}/studio/settings`, body);
|
|
76
|
+
});
|
|
77
|
+
define(server, s, 'list_media', {
|
|
78
|
+
title: 'Media library',
|
|
79
|
+
description: 'The media library: images, clips, voice lines and music, with filters (`kind` image|video|audio, `source` generated|uploaded|recorded|rendered, `q`, `starred`).',
|
|
80
|
+
input: { ...COMPANY_ARG, kind: z.string().optional(), source: z.string().optional(), q: z.string().optional(), starred: z.boolean().optional(), purpose: z.string().optional(), ...PAGE_ARGS },
|
|
81
|
+
group: 'studio',
|
|
82
|
+
ui: 'ui://autopoiesis/gallery.html',
|
|
83
|
+
}, async (args, ctx) => {
|
|
84
|
+
const cid = await resolveCompany(s, args, ctx);
|
|
85
|
+
if (isInputRequired(cid))
|
|
86
|
+
return cid;
|
|
87
|
+
const data = await s.api.get(`/api/companies/${cid}/studio/media`, { kind: args.kind, source: args.source, q: args.q, purpose: args.purpose, starred: args.starred ? 1 : undefined, page: args.page, page_size: args.page_size });
|
|
88
|
+
const out = trimmed(data, 30);
|
|
89
|
+
return { company: cid, ...out, results: out.results.map((m) => pick(m, MEDIA_KEYS)) };
|
|
90
|
+
});
|
|
91
|
+
define(server, s, 'get_media', {
|
|
92
|
+
title: 'Get media',
|
|
93
|
+
description: 'One piece of media: its prompt, model, settings, link and state.',
|
|
94
|
+
input: { media: z.string() },
|
|
95
|
+
group: 'studio',
|
|
96
|
+
ui: 'ui://autopoiesis/gallery.html',
|
|
97
|
+
}, async (args) => {
|
|
98
|
+
const m = (await s.api.get(`/api/studio/media/${args.media}`));
|
|
99
|
+
return { results: [m], count: 1, more: false, single: true, ...pick(m, ['prompt', 'params', 'model', 'status', 'url']) };
|
|
100
|
+
});
|
|
101
|
+
define(server, s, 'generate_media', {
|
|
102
|
+
title: 'Generate media',
|
|
103
|
+
description: 'Make an image, a clip, a voice line or music in the Studio (spends credits; you confirm the cost in a dialog): `kind`, a `prompt`, optionally a `model` (studio_overview), `aspect_ratio`, `duration` (seconds, clips), `count` (images), `voice` (a voice key, for kind audio with purpose voice), `purpose` (voice|music for audio), `references` (media ids). Returns the media ids (status queued): read them later with get_media.',
|
|
104
|
+
input: {
|
|
105
|
+
...COMPANY_ARG,
|
|
106
|
+
kind: z.enum(['image', 'video', 'audio']),
|
|
107
|
+
prompt: z.string(),
|
|
108
|
+
model: z.string().optional(),
|
|
109
|
+
aspect_ratio: z.string().optional(),
|
|
110
|
+
duration: z.number().optional(),
|
|
111
|
+
count: z.number().int().min(1).max(4).optional(),
|
|
112
|
+
voice: z.string().optional(),
|
|
113
|
+
purpose: z.enum(['voice', 'music']).optional(),
|
|
114
|
+
references: z.array(z.string()).optional(),
|
|
115
|
+
title: z.string().optional(),
|
|
116
|
+
production: z.string().optional(),
|
|
117
|
+
shot: z.string().optional(),
|
|
118
|
+
},
|
|
119
|
+
group: 'studio',
|
|
120
|
+
kind: 'spend',
|
|
121
|
+
}, async (args, ctx) => {
|
|
122
|
+
const cid = await resolveCompany(s, args, ctx);
|
|
123
|
+
if (isInputRequired(cid))
|
|
124
|
+
return cid;
|
|
125
|
+
const { models, defaults } = await s.studio(cid);
|
|
126
|
+
const key = args.model ?? defaults[args.kind === 'audio' ? (args.purpose ?? 'voice') : args.kind];
|
|
127
|
+
const model = models.find((m) => m.key === key);
|
|
128
|
+
const count = args.count ?? 1;
|
|
129
|
+
const each = model?.cents ?? null;
|
|
130
|
+
const total = each === null ? null : args.kind === 'video' && model?.unit === 'second' ? each * (args.duration ?? 5) * count : each * count;
|
|
131
|
+
const what = `${count > 1 ? `${count} ` : ''}${args.kind === 'audio' ? (args.purpose ?? 'voice') : args.kind}${count > 1 ? 's' : ''}`;
|
|
132
|
+
const ok = confirm(ctx, 'spend', `Make ${what} with ${model?.label ?? key ?? 'the default model'} for about ${money(total)}? Credits leave your balance.`, 'Yes, make it', 'No');
|
|
133
|
+
if (ok !== true)
|
|
134
|
+
return ok;
|
|
135
|
+
const params = {};
|
|
136
|
+
for (const k of ['aspect_ratio', 'duration', 'count', 'voice', 'purpose'])
|
|
137
|
+
if (args[k] !== undefined)
|
|
138
|
+
params[k] = args[k];
|
|
139
|
+
const body = { kind: args.kind, prompt: args.prompt, params, inputs: args.references ?? [] };
|
|
140
|
+
for (const k of ['model', 'title', 'production', 'shot'])
|
|
141
|
+
if (args[k] !== undefined)
|
|
142
|
+
body[k] = args[k];
|
|
143
|
+
return s.api.post(`/api/companies/${cid}/studio/generate`, body);
|
|
144
|
+
});
|
|
145
|
+
define(server, s, 'list_videos', {
|
|
146
|
+
title: 'List videos',
|
|
147
|
+
description: "The Studio's videos (productions): title, status, shape, length.",
|
|
148
|
+
input: { ...COMPANY_ARG },
|
|
149
|
+
group: 'studio',
|
|
150
|
+
}, async (args, ctx) => {
|
|
151
|
+
const cid = await resolveCompany(s, args, ctx);
|
|
152
|
+
if (isInputRequired(cid))
|
|
153
|
+
return cid;
|
|
154
|
+
return { company: cid, results: rows(await s.api.get(`/api/companies/${cid}/studio/productions`)).map((p) => pick(p, VIDEO_KEYS)) };
|
|
155
|
+
});
|
|
156
|
+
define(server, s, 'get_video', {
|
|
157
|
+
title: 'Get video',
|
|
158
|
+
description: "A video's storyboard in full: every shot (picture, line, text, seconds, direction, takes), the style and models it is made with, the presenter, cards, the finished render, and what is missing before it can render.",
|
|
159
|
+
input: { video: z.string() },
|
|
160
|
+
group: 'studio',
|
|
161
|
+
ui: 'ui://autopoiesis/storyboard.html',
|
|
162
|
+
}, async (args) => s.api.get(`/api/studio/productions/${args.video}`));
|
|
163
|
+
define(server, s, 'create_video', {
|
|
164
|
+
title: 'Create video',
|
|
165
|
+
description: "Start a video: a title, the brief (who it's for, what it must show, the one thing to feel), the shape (16:9, 9:16, 1:1, 4:5) and the length in seconds. The platform drafts the storyboard from the brief (free); then shape it with set_video_shots or ask the Creative Director (ask_agent creative, with `production`). `draft: false` makes an empty video to fill yourself.",
|
|
166
|
+
input: {
|
|
167
|
+
...COMPANY_ARG,
|
|
168
|
+
title: z.string(),
|
|
169
|
+
brief: z.string(),
|
|
170
|
+
aspect_ratio: z.enum(['16:9', '9:16', '1:1', '4:5']).optional(),
|
|
171
|
+
seconds: z.number().int().min(10).max(180).optional(),
|
|
172
|
+
draft: z.boolean().optional(),
|
|
173
|
+
style: z.record(z.string(), z.unknown()).optional(),
|
|
174
|
+
},
|
|
175
|
+
group: 'studio',
|
|
176
|
+
kind: 'write',
|
|
177
|
+
}, async (args, ctx) => {
|
|
178
|
+
const cid = await resolveCompany(s, args, ctx);
|
|
179
|
+
if (isInputRequired(cid))
|
|
180
|
+
return cid;
|
|
181
|
+
const body = { title: args.title, brief: args.brief, aspect_ratio: args.aspect_ratio ?? '16:9' };
|
|
182
|
+
if (args.style)
|
|
183
|
+
body.style = args.style;
|
|
184
|
+
let production = (await s.api.post(`/api/companies/${cid}/studio/productions`, body));
|
|
185
|
+
if (args.draft !== false) {
|
|
186
|
+
production = (await s.api.post(`/api/studio/productions/${production.id}/draft`, { brief: args.brief, seconds: args.seconds ?? 30 }));
|
|
187
|
+
}
|
|
188
|
+
return { company: cid, ...production };
|
|
189
|
+
});
|
|
190
|
+
define(server, s, 'set_video_shots', {
|
|
191
|
+
title: 'Set the storyboard',
|
|
192
|
+
description: "Replace a video's storyboard: shots in order, each {visual: generate|footage|title|presenter, prompt, voiceover, on_screen_text, duration_seconds, media?, voice?, direction?, keep_audio?, notes?}. Send a shot's `id` to keep its media; a shot without one is new.",
|
|
193
|
+
input: { video: z.string(), shots: z.array(z.record(z.string(), z.unknown())).max(60) },
|
|
194
|
+
group: 'studio',
|
|
195
|
+
kind: 'write',
|
|
196
|
+
}, async (args) => s.api.put(`/api/studio/productions/${args.video}/shots`, { shots: args.shots }));
|
|
197
|
+
define(server, s, 'update_video', {
|
|
198
|
+
title: 'Update video',
|
|
199
|
+
description: 'Change a video: title, brief, shape, or its `style`: music, captions, transition (cut|fade), colours, the narrator `voice`, the `look` (palette, light, camera, texture, mood, type, avoid, brand light|balanced|strong), the music cue (`music_prompt`, `music_bpm`, `beat_sync`), and `models` {clip, keyframe, voice, music, speaking}.',
|
|
200
|
+
input: { video: z.string(), title: z.string().optional(), brief: z.string().optional(), aspect_ratio: z.string().optional(), style: z.record(z.string(), z.unknown()).optional() },
|
|
201
|
+
group: 'studio',
|
|
202
|
+
kind: 'write',
|
|
203
|
+
}, async (args) => {
|
|
204
|
+
const body = {};
|
|
205
|
+
for (const k of ['title', 'brief', 'aspect_ratio', 'style'])
|
|
206
|
+
if (args[k] !== undefined)
|
|
207
|
+
body[k] = args[k];
|
|
208
|
+
return s.api.patch(`/api/studio/productions/${args.video}`, body);
|
|
209
|
+
});
|
|
210
|
+
define(server, s, 'make_video_media', {
|
|
211
|
+
title: 'Make the video’s media',
|
|
212
|
+
description: 'Make what a video still lacks (spends credits; you confirm the cost in a dialog): `what` all (clips and voice lines), visuals, voice, or music (composed from the style’s music cue). `model` overrides the clip model for this call. Then wait_for_video.',
|
|
213
|
+
input: { video: z.string(), what: z.enum(['all', 'visuals', 'voice', 'music']).optional(), model: z.string().optional(), voice: z.string().optional() },
|
|
214
|
+
group: 'studio',
|
|
215
|
+
kind: 'spend',
|
|
216
|
+
}, async (args, ctx) => {
|
|
217
|
+
const video = (await s.api.get(`/api/studio/productions/${args.video}`));
|
|
218
|
+
const shots = video.shots ?? [];
|
|
219
|
+
const lacking = shots.filter((sh) => sh.visual === 'generate' && !sh.media && sh.direction?.motion !== 'still');
|
|
220
|
+
const companyId = video.company_id ?? video.company;
|
|
221
|
+
const { models } = companyId ? await s.studio(companyId) : { models: [] };
|
|
222
|
+
const clipKey = args.model ?? video.style?.models?.clip;
|
|
223
|
+
const clip = models.find((m) => m.key === clipKey);
|
|
224
|
+
const seconds = lacking.reduce((sum, sh) => sum + (sh.duration_seconds ?? 5), 0);
|
|
225
|
+
const clipCost = clip?.cents != null ? (clip.unit === 'second' ? clip.cents * seconds : clip.cents * lacking.length) : null;
|
|
226
|
+
const what = args.what ?? 'all';
|
|
227
|
+
const parts = [
|
|
228
|
+
what === 'all' || what === 'visuals' ? `${lacking.length} clips (${seconds}s${clip ? `, ${clip.label ?? clip.key}` : ''})` : '',
|
|
229
|
+
what === 'all' || what === 'voice' ? 'the voice lines' : '',
|
|
230
|
+
what === 'music' ? 'the music' : '',
|
|
231
|
+
].filter(Boolean);
|
|
232
|
+
const ok = confirm(ctx, 'spend', `Make ${parts.join(' and ')} for "${video.title ?? args.video}"${clipCost !== null ? `, about ${money(clipCost)} for the clips` : ''}? Credits leave your balance.`, 'Yes, make it', 'No');
|
|
233
|
+
if (ok !== true)
|
|
234
|
+
return ok;
|
|
235
|
+
const body = {};
|
|
236
|
+
for (const k of ['what', 'model', 'voice'])
|
|
237
|
+
if (args[k] !== undefined)
|
|
238
|
+
body[k] = args[k];
|
|
239
|
+
return s.api.post(`/api/studio/productions/${args.video}/generate`, body);
|
|
240
|
+
});
|
|
241
|
+
define(server, s, 'video_keyframes', {
|
|
242
|
+
title: 'Make keyframes',
|
|
243
|
+
description: 'Make the keyframes first (a still of each generate shot’s first frame, cents each; you confirm in a dialog) so the look is locked before clips are paid for. `shots`: shot ids, default every shot without one.',
|
|
244
|
+
input: { video: z.string(), shots: z.array(z.string()).optional() },
|
|
245
|
+
group: 'studio',
|
|
246
|
+
kind: 'spend',
|
|
247
|
+
}, async (args, ctx) => {
|
|
248
|
+
const video = (await s.api.get(`/api/studio/productions/${args.video}`));
|
|
249
|
+
const targets = (video.shots ?? []).filter((sh) => (args.shots ? args.shots.includes(sh.id ?? '') : sh.visual === 'generate' && !(sh.takes ?? []).some((t) => t.stage === 'keyframe')));
|
|
250
|
+
const companyId = video.company_id ?? video.company;
|
|
251
|
+
const { models } = companyId ? await s.studio(companyId) : { models: [] };
|
|
252
|
+
const key = video.style?.models?.keyframe;
|
|
253
|
+
const model = models.find((m) => m.key === key);
|
|
254
|
+
const cost = model?.cents != null ? model.cents * targets.length : null;
|
|
255
|
+
const ok = confirm(ctx, 'spend', `Make ${targets.length} keyframes for "${video.title ?? args.video}" for about ${money(cost)}?`, 'Yes, make them', 'No');
|
|
256
|
+
if (ok !== true)
|
|
257
|
+
return ok;
|
|
258
|
+
return s.api.post(`/api/studio/productions/${args.video}/keyframes`, args.shots ? { shots: args.shots } : {});
|
|
259
|
+
});
|
|
260
|
+
define(server, s, 'critique_video', {
|
|
261
|
+
title: 'Critique takes',
|
|
262
|
+
description: 'The critic judges the latest takes (`stage` keyframes or clips) against each shot’s direction and puts the best on the shot.',
|
|
263
|
+
input: { video: z.string(), stage: z.enum(['keyframes', 'clips']) },
|
|
264
|
+
group: 'studio',
|
|
265
|
+
kind: 'write',
|
|
266
|
+
}, async (args) => s.api.post(`/api/studio/productions/${args.video}/critique`, { stage: args.stage }));
|
|
267
|
+
define(server, s, 'wait_for_video', {
|
|
268
|
+
title: 'Wait for the video',
|
|
269
|
+
description: 'Wait while a video’s media or render is being made (up to `wait_seconds`, default 60, max 120) and return where it stands: shots generating, the render, what is still missing. Call it again if it is still going.',
|
|
270
|
+
input: { video: z.string(), wait_seconds: z.number().int().min(0).max(120).optional() },
|
|
271
|
+
group: 'studio',
|
|
272
|
+
}, async (args, ctx) => {
|
|
273
|
+
const deadline = Date.now() + (args.wait_seconds ?? 60) * 1000;
|
|
274
|
+
const signal = ctx.signal;
|
|
275
|
+
for (;;) {
|
|
276
|
+
const video = (await s.api.get(`/api/studio/productions/${args.video}`));
|
|
277
|
+
const p = pending(video);
|
|
278
|
+
if ((p.generating === 0 && !p.rendering) || Date.now() >= deadline || signal?.aborted) {
|
|
279
|
+
return { ...brief(video), ready: p.generating === 0 && !p.rendering && p.missing.length === 0, still_going: p.generating > 0 || p.rendering };
|
|
280
|
+
}
|
|
281
|
+
await sleep(3000);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
define(server, s, 'render_video', {
|
|
285
|
+
title: 'Render video',
|
|
286
|
+
description: 'Cut the video into one MP4 (free). get_video says what is missing when it refuses; the render takes a minute or two: wait_for_video.',
|
|
287
|
+
input: { video: z.string() },
|
|
288
|
+
group: 'studio',
|
|
289
|
+
kind: 'write',
|
|
290
|
+
}, async (args) => s.api.post(`/api/studio/productions/${args.video}/render`));
|
|
291
|
+
define(server, s, 'design_video_cards', {
|
|
292
|
+
title: 'Design the cards',
|
|
293
|
+
description: 'Make (or re-sync) the cards drawn over a video’s shots, one scene per shot, editable in the Studio’s design editor. `remove: true` takes them off.',
|
|
294
|
+
input: { video: z.string(), remove: z.boolean().optional() },
|
|
295
|
+
group: 'studio',
|
|
296
|
+
kind: 'write',
|
|
297
|
+
}, async (args) => (args.remove ? s.api.delete(`/api/studio/productions/${args.video}/overlay`) : s.api.post(`/api/studio/productions/${args.video}/overlay`)));
|
|
298
|
+
define(server, s, 'video_plan', {
|
|
299
|
+
title: 'Video plan',
|
|
300
|
+
description: 'A video as a plan (JSON): its shots, presenter, style, media ids. Change it and import it back with import_video_plan.',
|
|
301
|
+
input: { video: z.string() },
|
|
302
|
+
group: 'studio',
|
|
303
|
+
}, async (args) => s.api.get(`/api/studio/productions/${args.video}/plan`));
|
|
304
|
+
define(server, s, 'import_video_plan', {
|
|
305
|
+
title: 'Import a video plan',
|
|
306
|
+
description: 'A whole video at once from a plan (the shape video_plan returns): title, brief, aspect_ratio, style, presenter, shots.',
|
|
307
|
+
input: { ...COMPANY_ARG, plan: z.record(z.string(), z.unknown()) },
|
|
308
|
+
group: 'studio',
|
|
309
|
+
kind: 'write',
|
|
310
|
+
}, async (args, ctx) => {
|
|
311
|
+
const cid = await resolveCompany(s, args, ctx);
|
|
312
|
+
if (isInputRequired(cid))
|
|
313
|
+
return cid;
|
|
314
|
+
return s.api.post(`/api/companies/${cid}/studio/productions/import`, { plan: args.plan });
|
|
315
|
+
});
|
|
316
|
+
define(server, s, 'export_video', {
|
|
317
|
+
title: 'Export video',
|
|
318
|
+
description: 'A link to a zip of the video’s plan and every file it names (works for a week).',
|
|
319
|
+
input: { video: z.string() },
|
|
320
|
+
group: 'studio',
|
|
321
|
+
kind: 'write',
|
|
322
|
+
}, async (args) => s.api.post(`/api/studio/productions/${args.video}/export`));
|
|
323
|
+
define(server, s, 'delete_video', {
|
|
324
|
+
title: 'Delete video',
|
|
325
|
+
description: 'Delete a video (its media stay in the library). You confirm in a dialog.',
|
|
326
|
+
input: { video: z.string() },
|
|
327
|
+
group: 'studio',
|
|
328
|
+
kind: 'destructive',
|
|
329
|
+
}, async (args, ctx) => {
|
|
330
|
+
const video = (await s.api.get(`/api/studio/productions/${args.video}`));
|
|
331
|
+
const ok = confirm(ctx, 'delete', `Delete the video "${video.title ?? args.video}"? Its media stay in the library.`, 'Yes, delete it', 'No, keep it');
|
|
332
|
+
if (ok !== true)
|
|
333
|
+
return ok;
|
|
334
|
+
await s.api.delete(`/api/studio/productions/${args.video}`);
|
|
335
|
+
return { deleted: args.video };
|
|
336
|
+
});
|
|
337
|
+
define(server, s, 'update_media', {
|
|
338
|
+
title: 'Update media',
|
|
339
|
+
description: 'Rename, star or delete a piece of media; `retry` makes a failed generation again; `cancel` stops one.',
|
|
340
|
+
input: { media: z.string(), title: z.string().optional(), starred: z.boolean().optional(), action: z.enum(['delete', 'retry', 'cancel']).optional() },
|
|
341
|
+
group: 'studio',
|
|
342
|
+
kind: 'write',
|
|
343
|
+
}, async (args, ctx) => {
|
|
344
|
+
if (args.action === 'delete') {
|
|
345
|
+
const ok = confirm(ctx, 'delete', 'Delete this piece of media from the library?', 'Yes, delete it', 'No, keep it');
|
|
346
|
+
if (ok !== true)
|
|
347
|
+
return ok;
|
|
348
|
+
await s.api.delete(`/api/studio/media/${args.media}`);
|
|
349
|
+
return { deleted: args.media };
|
|
350
|
+
}
|
|
351
|
+
if (args.action === 'retry')
|
|
352
|
+
return s.api.post(`/api/studio/media/${args.media}/retry`);
|
|
353
|
+
if (args.action === 'cancel')
|
|
354
|
+
return s.api.post(`/api/studio/media/${args.media}/cancel`);
|
|
355
|
+
const body = {};
|
|
356
|
+
if (args.title !== undefined)
|
|
357
|
+
body.title = args.title;
|
|
358
|
+
if (args.starred !== undefined)
|
|
359
|
+
body.starred = args.starred;
|
|
360
|
+
return s.api.patch(`/api/studio/media/${args.media}`, body);
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
//# sourceMappingURL=studio.js.map
|