@hubfluencer/mcp 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.
- package/README.md +107 -50
- package/dist/index.js +13071 -7803
- package/package.json +12 -6
- package/server.json +56 -0
- package/skill/hubfluencer-create/SKILL.md +169 -0
- package/skill/hubfluencer-create/references/full-guide.md +228 -0
- package/src/client.ts +2 -2
- package/src/core.ts +311 -398
- package/src/doctor.ts +96 -0
- package/src/generation-summary.ts +262 -0
- package/src/index.ts +1075 -290
- package/src/output-schemas.ts +85 -12
- package/src/polling.ts +64 -0
- package/src/skill-installer.ts +100 -0
- package/src/tool-profile.ts +93 -0
package/src/doctor.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { clientFromEnv, type HubfluencerClient } from "./client.js";
|
|
2
|
+
import { readStoredCredentials } from "./credentials.js";
|
|
3
|
+
import type { ToolProfile } from "./tool-profile.js";
|
|
4
|
+
import { VERSION } from "./version.js";
|
|
5
|
+
|
|
6
|
+
type DoctorClient = Pick<HubfluencerClient, "get" | "origin">;
|
|
7
|
+
|
|
8
|
+
export type DoctorReport = {
|
|
9
|
+
ok: true;
|
|
10
|
+
version: string;
|
|
11
|
+
profile: ToolProfile;
|
|
12
|
+
api_origin: string;
|
|
13
|
+
credential_source: "environment" | "device_login";
|
|
14
|
+
checks: {
|
|
15
|
+
credentials: "ok";
|
|
16
|
+
secure_origin: "ok";
|
|
17
|
+
api_connection: "ok";
|
|
18
|
+
credits_read: "ok";
|
|
19
|
+
};
|
|
20
|
+
credits: unknown;
|
|
21
|
+
input_directory: string;
|
|
22
|
+
output_directory: string;
|
|
23
|
+
next: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function credentialSource(): DoctorReport["credential_source"] {
|
|
27
|
+
if (process.env.HUBFLUENCER_API_TOKEN) return "environment";
|
|
28
|
+
if (readStoredCredentials().token) return "device_login";
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Not connected. Run \`npx -y @hubfluencer/mcp@${VERSION} login\` or set HUBFLUENCER_API_TOKEN.`,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function extractCredits(response: unknown): unknown {
|
|
35
|
+
if (!response || typeof response !== "object" || Array.isArray(response)) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const record = response as Record<string, unknown>;
|
|
39
|
+
const data =
|
|
40
|
+
record.data &&
|
|
41
|
+
typeof record.data === "object" &&
|
|
42
|
+
!Array.isArray(record.data)
|
|
43
|
+
? (record.data as Record<string, unknown>)
|
|
44
|
+
: record;
|
|
45
|
+
return (
|
|
46
|
+
data.spendable_credits ?? data.credits ?? data.remaining_credits ?? null
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function inspectDoctor(
|
|
51
|
+
profile: ToolProfile,
|
|
52
|
+
client: DoctorClient = clientFromEnv(),
|
|
53
|
+
): Promise<DoctorReport> {
|
|
54
|
+
const source = credentialSource();
|
|
55
|
+
const response = await client.get<unknown>("/studio/credits");
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
ok: true,
|
|
59
|
+
version: VERSION,
|
|
60
|
+
profile,
|
|
61
|
+
api_origin: client.origin,
|
|
62
|
+
credential_source: source,
|
|
63
|
+
checks: {
|
|
64
|
+
credentials: "ok",
|
|
65
|
+
secure_origin: "ok",
|
|
66
|
+
api_connection: "ok",
|
|
67
|
+
credits_read: "ok",
|
|
68
|
+
},
|
|
69
|
+
credits: extractCredits(response),
|
|
70
|
+
input_directory: process.env.HUBFLUENCER_INPUT_DIR || process.cwd(),
|
|
71
|
+
output_directory: process.env.HUBFLUENCER_OUTPUT_DIR || process.cwd(),
|
|
72
|
+
next: 'Ask your agent to call make_video({prompt:"...",dry_run:true}) for a free quote before approving spend.',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function runDoctor(
|
|
77
|
+
profile: ToolProfile,
|
|
78
|
+
json = false,
|
|
79
|
+
): Promise<void> {
|
|
80
|
+
const report = await inspectDoctor(profile);
|
|
81
|
+
if (json) {
|
|
82
|
+
console.log(JSON.stringify(report, null, 2));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log(`✓ Hubfluencer MCP ${report.version}`);
|
|
87
|
+
console.log(`✓ profile: ${report.profile}`);
|
|
88
|
+
console.log(
|
|
89
|
+
`✓ credentials: ${report.credential_source === "environment" ? "HUBFLUENCER_API_TOKEN" : "device login"}`,
|
|
90
|
+
);
|
|
91
|
+
console.log(`✓ API connection: ${report.api_origin}`);
|
|
92
|
+
console.log(`✓ credits endpoint: ${String(report.credits ?? "available")}`);
|
|
93
|
+
console.log(`✓ local reads confined to: ${report.input_directory}`);
|
|
94
|
+
console.log(`✓ MP4 writes confined to: ${report.output_directory}`);
|
|
95
|
+
console.log(`Next: ${report.next}`);
|
|
96
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const generationStatuses = [
|
|
4
|
+
"idle",
|
|
5
|
+
"queued",
|
|
6
|
+
"running",
|
|
7
|
+
"needs_input",
|
|
8
|
+
"completed",
|
|
9
|
+
"failed",
|
|
10
|
+
"cancelled",
|
|
11
|
+
] as const;
|
|
12
|
+
|
|
13
|
+
export const knownGenerationActionTypes = [
|
|
14
|
+
"start_generation",
|
|
15
|
+
"cancel_generation",
|
|
16
|
+
"retry_generation",
|
|
17
|
+
"abandon_generation",
|
|
18
|
+
"edit_required_input",
|
|
19
|
+
"review_spend_cap",
|
|
20
|
+
"download_result",
|
|
21
|
+
"review_scenes",
|
|
22
|
+
"review_scene",
|
|
23
|
+
"review_narration",
|
|
24
|
+
"review_audio",
|
|
25
|
+
"review_project",
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
28
|
+
export type KnownGenerationActionType =
|
|
29
|
+
(typeof knownGenerationActionTypes)[number];
|
|
30
|
+
|
|
31
|
+
const detailsSchema = z.record(z.string(), z.unknown());
|
|
32
|
+
|
|
33
|
+
export const generationActionSchema = z
|
|
34
|
+
.object({
|
|
35
|
+
type: z.string(),
|
|
36
|
+
label: z.string(),
|
|
37
|
+
destructive: z.boolean(),
|
|
38
|
+
step_key: z.string().nullable(),
|
|
39
|
+
})
|
|
40
|
+
.passthrough();
|
|
41
|
+
|
|
42
|
+
const generationFailureSchema = z
|
|
43
|
+
.object({
|
|
44
|
+
code: z.string(),
|
|
45
|
+
message: z.string(),
|
|
46
|
+
details: detailsSchema,
|
|
47
|
+
})
|
|
48
|
+
.passthrough();
|
|
49
|
+
|
|
50
|
+
export const generationSummarySchema = z
|
|
51
|
+
.object({
|
|
52
|
+
run_id: z.string().uuid().nullable(),
|
|
53
|
+
workflow: z.string().nullable(),
|
|
54
|
+
workflow_version: z.number().int().nullable(),
|
|
55
|
+
revision: z.number().int().nullable(),
|
|
56
|
+
status: z.enum(generationStatuses),
|
|
57
|
+
settling: z.boolean(),
|
|
58
|
+
phase: z.string(),
|
|
59
|
+
label: z.string(),
|
|
60
|
+
progress: z
|
|
61
|
+
.object({
|
|
62
|
+
completed: z.number().int().nonnegative(),
|
|
63
|
+
total: z.number().int().nonnegative(),
|
|
64
|
+
percent: z.number().int().min(0).max(100),
|
|
65
|
+
})
|
|
66
|
+
.passthrough(),
|
|
67
|
+
current_step: z
|
|
68
|
+
.object({
|
|
69
|
+
key: z.string(),
|
|
70
|
+
type: z.string(),
|
|
71
|
+
status: z.string(),
|
|
72
|
+
attempt: z.number().int().nonnegative(),
|
|
73
|
+
max_attempts: z.number().int().positive(),
|
|
74
|
+
})
|
|
75
|
+
.passthrough()
|
|
76
|
+
.nullable(),
|
|
77
|
+
blockers: z.array(
|
|
78
|
+
z
|
|
79
|
+
.object({
|
|
80
|
+
code: z.string(),
|
|
81
|
+
message: z.string(),
|
|
82
|
+
step_key: z.string().nullable(),
|
|
83
|
+
details: detailsSchema,
|
|
84
|
+
})
|
|
85
|
+
.passthrough(),
|
|
86
|
+
),
|
|
87
|
+
available_actions: z.array(generationActionSchema),
|
|
88
|
+
failure: generationFailureSchema.nullable(),
|
|
89
|
+
cost: z
|
|
90
|
+
.object({
|
|
91
|
+
quoted_credits: z.number().int().nonnegative(),
|
|
92
|
+
charged_credits: z.number().int().nonnegative(),
|
|
93
|
+
refunded_credits: z.number().int().nonnegative(),
|
|
94
|
+
net_credits: z.number().int().nonnegative(),
|
|
95
|
+
max_credits: z.number().int().nonnegative(),
|
|
96
|
+
})
|
|
97
|
+
.passthrough(),
|
|
98
|
+
timing: z
|
|
99
|
+
.object({
|
|
100
|
+
started_at: z.string().nullable(),
|
|
101
|
+
updated_at: z.string().nullable(),
|
|
102
|
+
deadline_at: z.string().nullable(),
|
|
103
|
+
poll_after_ms: z.number().int().nonnegative().nullable(),
|
|
104
|
+
})
|
|
105
|
+
.passthrough(),
|
|
106
|
+
})
|
|
107
|
+
.passthrough()
|
|
108
|
+
.superRefine((summary, context) => {
|
|
109
|
+
if (summary.status === "idle") {
|
|
110
|
+
if (
|
|
111
|
+
summary.run_id !== null ||
|
|
112
|
+
summary.workflow !== null ||
|
|
113
|
+
summary.workflow_version !== null ||
|
|
114
|
+
summary.revision !== null
|
|
115
|
+
) {
|
|
116
|
+
context.addIssue({
|
|
117
|
+
code: z.ZodIssueCode.custom,
|
|
118
|
+
message: "idle summaries cannot carry workflow identity",
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (
|
|
125
|
+
summary.run_id === null ||
|
|
126
|
+
summary.workflow === null ||
|
|
127
|
+
summary.workflow_version !== 1 ||
|
|
128
|
+
summary.revision === null
|
|
129
|
+
) {
|
|
130
|
+
context.addIssue({
|
|
131
|
+
code: z.ZodIssueCode.custom,
|
|
132
|
+
message:
|
|
133
|
+
"non-idle summaries require canonical workflow version 1 identity",
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
export type GenerationSummary = z.infer<typeof generationSummarySchema>;
|
|
139
|
+
|
|
140
|
+
const knownActionTypes = new Set<string>(knownGenerationActionTypes);
|
|
141
|
+
|
|
142
|
+
export function knownGenerationActionTypesFrom(
|
|
143
|
+
summary: GenerationSummary,
|
|
144
|
+
): KnownGenerationActionType[] {
|
|
145
|
+
return summary.available_actions
|
|
146
|
+
.map((action) => action.type)
|
|
147
|
+
.filter((type): type is KnownGenerationActionType =>
|
|
148
|
+
knownActionTypes.has(type),
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface GenerationProductOperation {
|
|
153
|
+
tool: string;
|
|
154
|
+
required_fences: readonly string[];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const operation = (
|
|
158
|
+
tool: string,
|
|
159
|
+
required_fences: readonly string[] = [],
|
|
160
|
+
): GenerationProductOperation => ({ tool, required_fences });
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* MCP-side contract mirror of the API's product action table. Availability
|
|
164
|
+
* always comes from the summary; this table only selects the product tool that
|
|
165
|
+
* can carry out an already-advertised intent.
|
|
166
|
+
*/
|
|
167
|
+
export const generationProductOperations = {
|
|
168
|
+
"short:short.generation:cancel_generation": operation(
|
|
169
|
+
"cancel_short_generation",
|
|
170
|
+
["generation_run_id", "idempotency_key"],
|
|
171
|
+
),
|
|
172
|
+
"short:*:start_generation": operation("generate_short", ["idempotency_key"]),
|
|
173
|
+
"short:*:download_result": operation("download_result"),
|
|
174
|
+
"short:*:edit_required_input": operation("provide_generation_input", [
|
|
175
|
+
"generation_run_id",
|
|
176
|
+
"generation_revision",
|
|
177
|
+
"step_key",
|
|
178
|
+
"idempotency_key",
|
|
179
|
+
]),
|
|
180
|
+
"short:*:review_spend_cap": operation("provide_generation_input", [
|
|
181
|
+
"generation_run_id",
|
|
182
|
+
"generation_revision",
|
|
183
|
+
"step_key",
|
|
184
|
+
"idempotency_key",
|
|
185
|
+
]),
|
|
186
|
+
"editor:editor.autopilot:cancel_generation": operation("cancel_autopilot", [
|
|
187
|
+
"generation_run_id",
|
|
188
|
+
"idempotency_key",
|
|
189
|
+
]),
|
|
190
|
+
"editor:editor.scene.generate:cancel_generation": operation(
|
|
191
|
+
"cancel_segment_generation",
|
|
192
|
+
["expected_generation_claim_token", "idempotency_key"],
|
|
193
|
+
),
|
|
194
|
+
"editor:editor.scene.regenerate:cancel_generation": operation(
|
|
195
|
+
"cancel_segment_generation",
|
|
196
|
+
["expected_generation_claim_token", "idempotency_key"],
|
|
197
|
+
),
|
|
198
|
+
"editor:editor.scene.generate:retry_generation": operation(
|
|
199
|
+
"retry_segment_generation",
|
|
200
|
+
["generation_run_id", "generation_revision", "step_key", "idempotency_key"],
|
|
201
|
+
),
|
|
202
|
+
"editor:editor.scene.regenerate:retry_generation": operation(
|
|
203
|
+
"retry_segment_generation",
|
|
204
|
+
["generation_run_id", "generation_revision", "step_key", "idempotency_key"],
|
|
205
|
+
),
|
|
206
|
+
"editor:editor.render:retry_generation": operation("retry_render", [
|
|
207
|
+
"generation_revision",
|
|
208
|
+
"idempotency_key",
|
|
209
|
+
]),
|
|
210
|
+
"editor:editor.render.retry:retry_generation": operation("retry_render", [
|
|
211
|
+
"generation_revision",
|
|
212
|
+
"idempotency_key",
|
|
213
|
+
]),
|
|
214
|
+
"editor:editor.batch:retry_generation": operation("retry_editor_batch", [
|
|
215
|
+
"generation_run_id",
|
|
216
|
+
"generation_revision",
|
|
217
|
+
"idempotency_key",
|
|
218
|
+
]),
|
|
219
|
+
"editor:editor.batch:cancel_generation": operation("cancel_editor_batch", [
|
|
220
|
+
"generation_run_id",
|
|
221
|
+
"idempotency_key",
|
|
222
|
+
]),
|
|
223
|
+
"editor:*:start_generation": operation("start_autopilot", [
|
|
224
|
+
"idempotency_key",
|
|
225
|
+
]),
|
|
226
|
+
"editor:*:edit_required_input": operation("provide_generation_input", [
|
|
227
|
+
"generation_run_id",
|
|
228
|
+
"generation_revision",
|
|
229
|
+
"step_key",
|
|
230
|
+
"idempotency_key",
|
|
231
|
+
]),
|
|
232
|
+
"editor:*:review_spend_cap": operation("provide_generation_input", [
|
|
233
|
+
"generation_run_id",
|
|
234
|
+
"generation_revision",
|
|
235
|
+
"step_key",
|
|
236
|
+
"idempotency_key",
|
|
237
|
+
]),
|
|
238
|
+
"editor:*:download_result": operation("download_result"),
|
|
239
|
+
"editor:*:review_scenes": operation("get_editor"),
|
|
240
|
+
"editor:*:review_scene": operation("get_editor"),
|
|
241
|
+
"editor:*:review_narration": operation("get_editor"),
|
|
242
|
+
"editor:*:review_audio": operation("get_editor"),
|
|
243
|
+
"editor:*:review_project": operation("get_editor"),
|
|
244
|
+
"editor:*:abandon_generation": operation("get_editor"),
|
|
245
|
+
} as const satisfies Record<string, GenerationProductOperation>;
|
|
246
|
+
|
|
247
|
+
export function generationProductOperation(
|
|
248
|
+
product: "editor" | "short",
|
|
249
|
+
workflow: string | null,
|
|
250
|
+
actionType: KnownGenerationActionType,
|
|
251
|
+
): GenerationProductOperation | undefined {
|
|
252
|
+
const exact = `${product}:${workflow ?? ""}:${actionType}`;
|
|
253
|
+
const wildcard = `${product}:*:${actionType}`;
|
|
254
|
+
return (
|
|
255
|
+
generationProductOperations[
|
|
256
|
+
exact as keyof typeof generationProductOperations
|
|
257
|
+
] ??
|
|
258
|
+
generationProductOperations[
|
|
259
|
+
wildcard as keyof typeof generationProductOperations
|
|
260
|
+
]
|
|
261
|
+
);
|
|
262
|
+
}
|