@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/output-schemas.ts
CHANGED
|
@@ -41,6 +41,15 @@
|
|
|
41
41
|
*/
|
|
42
42
|
|
|
43
43
|
import { z } from "zod";
|
|
44
|
+
import { generationSummarySchema } from "./generation-summary.js";
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Fallback contract for tools whose server-owned payload is intentionally
|
|
48
|
+
* open-ended. Advertising an object schema is still materially better than
|
|
49
|
+
* omitting outputSchema: clients can request structured output consistently,
|
|
50
|
+
* while the stable/high-value tools below keep their precise schemas.
|
|
51
|
+
*/
|
|
52
|
+
export const genericToolOutput = z.object({}).passthrough();
|
|
44
53
|
|
|
45
54
|
/** A presigned URL or a local-null field that may be absent/variable. */
|
|
46
55
|
const urlish = z.unknown().optional();
|
|
@@ -62,9 +71,16 @@ export const getStatusOutput = z
|
|
|
62
71
|
ready: z.boolean().optional(),
|
|
63
72
|
video_url: z.union([z.string(), z.null()]).optional(),
|
|
64
73
|
error: z.union([z.string(), z.null()]).optional(),
|
|
74
|
+
generation_summary: generationSummarySchema.optional(),
|
|
75
|
+
run_id: z.union([z.string(), z.null()]).optional(),
|
|
76
|
+
workflow: z.union([z.string(), z.null()]).optional(),
|
|
77
|
+
revision: z.union([z.number(), z.null()]).optional(),
|
|
78
|
+
poll_after_ms: z.union([z.number(), z.null()]).optional(),
|
|
79
|
+
available_actions: z.array(z.string()).optional(),
|
|
65
80
|
stale: z.boolean().optional(),
|
|
66
81
|
active_segment_ids: z.array(z.union([z.number(), z.string()])).optional(),
|
|
67
82
|
stale_segment_ids: z.array(z.union([z.number(), z.string()])).optional(),
|
|
83
|
+
autopilot_contract_satisfied: z.boolean().optional(),
|
|
68
84
|
// Short only, additive (normalizeStatus forwards each gated on its typeof,
|
|
69
85
|
// so the precise type can never reject a payload).
|
|
70
86
|
failed_stage: z
|
|
@@ -91,10 +107,6 @@ export const getStatusOutput = z
|
|
|
91
107
|
.number()
|
|
92
108
|
.optional()
|
|
93
109
|
.describe("Short only: paid generation attempt number that failed."),
|
|
94
|
-
generation_attempt: z
|
|
95
|
-
.number()
|
|
96
|
-
.optional()
|
|
97
|
-
.describe("Short only: current paid attempt; echo when cancelling."),
|
|
98
110
|
segments_completed: z.number().optional(),
|
|
99
111
|
segments_total: z.number().optional(),
|
|
100
112
|
active_segment_positions: z.array(z.number()).optional(),
|
|
@@ -103,6 +115,24 @@ export const getStatusOutput = z
|
|
|
103
115
|
})
|
|
104
116
|
.passthrough();
|
|
105
117
|
|
|
118
|
+
/**
|
|
119
|
+
* get_editor -> full or concise Editor state.
|
|
120
|
+
*
|
|
121
|
+
* The payload is intentionally otherwise permissive because the full Editor
|
|
122
|
+
* graph is additive and much larger than the MCP review projection. The
|
|
123
|
+
* lifecycle authority is the one field that is never optional: advertising it
|
|
124
|
+
* here makes strict MCP clients see the same requirement that the handler
|
|
125
|
+
* enforces at runtime.
|
|
126
|
+
*/
|
|
127
|
+
export const getEditorOutput = z
|
|
128
|
+
.object({
|
|
129
|
+
generation_summary: generationSummarySchema,
|
|
130
|
+
slug: z.string().optional(),
|
|
131
|
+
segments: z.array(z.unknown()).optional(),
|
|
132
|
+
latest_render: z.unknown().optional(),
|
|
133
|
+
})
|
|
134
|
+
.passthrough();
|
|
135
|
+
|
|
106
136
|
/** wait_for_completion → get_status plus the download/timeout bookkeeping. */
|
|
107
137
|
export const waitForCompletionOutput = z
|
|
108
138
|
.object({
|
|
@@ -113,9 +143,16 @@ export const waitForCompletionOutput = z
|
|
|
113
143
|
ready: z.boolean().optional(),
|
|
114
144
|
video_url: z.union([z.string(), z.null()]).optional(),
|
|
115
145
|
error: z.union([z.string(), z.null()]).optional(),
|
|
146
|
+
generation_summary: generationSummarySchema.optional(),
|
|
147
|
+
run_id: z.union([z.string(), z.null()]).optional(),
|
|
148
|
+
workflow: z.union([z.string(), z.null()]).optional(),
|
|
149
|
+
revision: z.union([z.number(), z.null()]).optional(),
|
|
150
|
+
poll_after_ms: z.union([z.number(), z.null()]).optional(),
|
|
151
|
+
available_actions: z.array(z.string()).optional(),
|
|
116
152
|
stale: z.boolean().optional(),
|
|
117
153
|
active_segment_ids: z.array(z.union([z.number(), z.string()])).optional(),
|
|
118
154
|
stale_segment_ids: z.array(z.union([z.number(), z.string()])).optional(),
|
|
155
|
+
autopilot_contract_satisfied: z.boolean().optional(),
|
|
119
156
|
// Short only, additive — mirrors getStatusOutput (see there).
|
|
120
157
|
failed_stage: z
|
|
121
158
|
.string()
|
|
@@ -141,10 +178,6 @@ export const waitForCompletionOutput = z
|
|
|
141
178
|
.number()
|
|
142
179
|
.optional()
|
|
143
180
|
.describe("Short only: paid generation attempt number that failed."),
|
|
144
|
-
generation_attempt: z
|
|
145
|
-
.number()
|
|
146
|
-
.optional()
|
|
147
|
-
.describe("Short only: current paid attempt; echo when cancelling."),
|
|
148
181
|
segments_completed: z.number().optional(),
|
|
149
182
|
segments_total: z.number().optional(),
|
|
150
183
|
active_segment_positions: z.array(z.number()).optional(),
|
|
@@ -152,6 +185,7 @@ export const waitForCompletionOutput = z
|
|
|
152
185
|
last_free_rerender_failed: z.boolean().optional(),
|
|
153
186
|
saved_to: z.union([z.string(), z.null()]).optional(),
|
|
154
187
|
timed_out: z.boolean().optional(),
|
|
188
|
+
polling_stopped: z.boolean().optional(),
|
|
155
189
|
})
|
|
156
190
|
.passthrough();
|
|
157
191
|
|
|
@@ -212,26 +246,63 @@ export const getSliderOutput = z
|
|
|
212
246
|
|
|
213
247
|
// ── Projects listing ────────────────────────────────────────────────────────────
|
|
214
248
|
|
|
215
|
-
/** A normalized status row
|
|
249
|
+
/** A permissive normalized status row reused by nested create-tool results. */
|
|
216
250
|
const projectRow = z
|
|
217
251
|
.object({
|
|
218
252
|
kind: z.string().optional(),
|
|
219
253
|
slug: z.string().optional(),
|
|
220
254
|
stage: z.string().optional(),
|
|
255
|
+
kind_detail: z.union([z.string(), z.null()]).optional(),
|
|
221
256
|
terminal: z.boolean().optional(),
|
|
222
257
|
ready: z.boolean().optional(),
|
|
223
258
|
video_url: z.union([z.string(), z.null()]).optional(),
|
|
224
259
|
error: z.union([z.string(), z.null()]).optional(),
|
|
260
|
+
generation_summary: generationSummarySchema.optional(),
|
|
261
|
+
run_id: z.union([z.string(), z.null()]).optional(),
|
|
262
|
+
workflow: z.union([z.string(), z.null()]).optional(),
|
|
263
|
+
revision: z.union([z.number(), z.null()]).optional(),
|
|
264
|
+
poll_after_ms: z.union([z.number(), z.null()]).optional(),
|
|
265
|
+
available_actions: z.array(z.string()).optional(),
|
|
266
|
+
segments_completed: z.number().optional(),
|
|
267
|
+
segments_total: z.number().optional(),
|
|
268
|
+
active_segment_positions: z.array(z.number()).optional(),
|
|
225
269
|
latest_render_free: z.boolean().optional(),
|
|
226
270
|
last_free_rerender_failed: z.boolean().optional(),
|
|
227
271
|
})
|
|
228
272
|
.passthrough();
|
|
229
273
|
|
|
230
|
-
/**
|
|
274
|
+
/** Summary-bearing Short row emitted by normalizeStatus in list_projects. */
|
|
275
|
+
const listedShortProject = projectRow.extend({
|
|
276
|
+
kind: z.literal("short"),
|
|
277
|
+
slug: z.string(),
|
|
278
|
+
stage: z.string(),
|
|
279
|
+
terminal: z.boolean(),
|
|
280
|
+
ready: z.boolean(),
|
|
281
|
+
video_url: z.union([z.string(), z.null()]),
|
|
282
|
+
error: z.union([z.string(), z.null()]),
|
|
283
|
+
generation_summary: generationSummarySchema,
|
|
284
|
+
run_id: z.union([z.string(), z.null()]),
|
|
285
|
+
workflow: z.union([z.string(), z.null()]),
|
|
286
|
+
revision: z.union([z.number(), z.null()]),
|
|
287
|
+
poll_after_ms: z.union([z.number(), z.null()]),
|
|
288
|
+
available_actions: z.array(z.string()),
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
/** Summary-free mixed-kind discovery row emitted by normalizeFactoryIndexProject. */
|
|
292
|
+
const listedGenericProject = z
|
|
293
|
+
.object({
|
|
294
|
+
slug: z.string(),
|
|
295
|
+
kind_detail: z.union([z.string(), z.null()]),
|
|
296
|
+
stage: z.string(),
|
|
297
|
+
video_url: z.union([z.string(), z.null()]),
|
|
298
|
+
})
|
|
299
|
+
.passthrough();
|
|
300
|
+
|
|
301
|
+
/** list_projects → two always-present lanes plus an optional truncation note. */
|
|
231
302
|
export const listProjectsOutput = z
|
|
232
303
|
.object({
|
|
233
|
-
shorts: z.array(
|
|
234
|
-
projects: z.array(
|
|
304
|
+
shorts: z.array(listedShortProject),
|
|
305
|
+
projects: z.array(listedGenericProject),
|
|
235
306
|
note: z.string().optional(),
|
|
236
307
|
})
|
|
237
308
|
.passthrough();
|
|
@@ -292,6 +363,8 @@ export const makeVideoOutput = z
|
|
|
292
363
|
charged: z.boolean().optional(),
|
|
293
364
|
saved_to: z.union([z.string(), z.null()]).optional(),
|
|
294
365
|
timed_out: z.boolean().optional(),
|
|
366
|
+
replayed: z.boolean().optional(),
|
|
367
|
+
polling_stopped: z.boolean().optional(),
|
|
295
368
|
note: z.string().optional(),
|
|
296
369
|
})
|
|
297
370
|
.passthrough();
|
package/src/polling.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export interface PollOptions<T> {
|
|
2
|
+
read: (deadline: number) => Promise<T>;
|
|
3
|
+
isTerminal: (value: T) => boolean;
|
|
4
|
+
/** False means the server has explicitly told this client to stop polling. */
|
|
5
|
+
shouldContinue?: (value: T) => boolean;
|
|
6
|
+
budgetMs: number;
|
|
7
|
+
intervalMs: number | ((value: T) => number);
|
|
8
|
+
onPoll?: (value: T, pollNumber: number) => Promise<void>;
|
|
9
|
+
now?: () => number;
|
|
10
|
+
sleep?: (milliseconds: number) => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Poll within a fixed budget, then perform one separately bounded
|
|
15
|
+
* reconciliation read. The final read closes the common race where work
|
|
16
|
+
* finishes during the last partial interval and the caller otherwise returns
|
|
17
|
+
* the stale pre-completion snapshot.
|
|
18
|
+
*/
|
|
19
|
+
export async function pollWithFinalReconciliation<T>({
|
|
20
|
+
read,
|
|
21
|
+
isTerminal,
|
|
22
|
+
shouldContinue = () => true,
|
|
23
|
+
budgetMs,
|
|
24
|
+
intervalMs,
|
|
25
|
+
onPoll,
|
|
26
|
+
now = Date.now,
|
|
27
|
+
sleep = (milliseconds) =>
|
|
28
|
+
new Promise<void>((resolve) => setTimeout(resolve, milliseconds)),
|
|
29
|
+
}: PollOptions<T>): Promise<T> {
|
|
30
|
+
const deadline = now() + budgetMs;
|
|
31
|
+
let value = await read(deadline);
|
|
32
|
+
let pollNumber = 0;
|
|
33
|
+
let lastIntervalMs = resolveInterval(intervalMs, value);
|
|
34
|
+
|
|
35
|
+
while (!isTerminal(value) && shouldContinue(value)) {
|
|
36
|
+
const remainingMs = deadline - now();
|
|
37
|
+
if (remainingMs <= 0) break;
|
|
38
|
+
await onPoll?.(value, ++pollNumber);
|
|
39
|
+
lastIntervalMs = resolveInterval(intervalMs, value);
|
|
40
|
+
await sleep(Math.min(lastIntervalMs, remainingMs));
|
|
41
|
+
if (now() >= deadline) break;
|
|
42
|
+
value = await read(deadline);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!isTerminal(value) && shouldContinue(value)) {
|
|
46
|
+
const finalReadDeadline =
|
|
47
|
+
now() + Math.min(5_000, Math.max(1_000, lastIntervalMs));
|
|
48
|
+
try {
|
|
49
|
+
value = await read(finalReadDeadline);
|
|
50
|
+
} catch {
|
|
51
|
+
// The normal timeout/resume contract uses the last successful state.
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resolveInterval<T>(
|
|
59
|
+
interval: PollOptions<T>["intervalMs"],
|
|
60
|
+
value: T,
|
|
61
|
+
): number {
|
|
62
|
+
const resolved = typeof interval === "function" ? interval(value) : interval;
|
|
63
|
+
return Number.isFinite(resolved) && resolved > 0 ? resolved : 1_000;
|
|
64
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { access, cp, mkdir, rm } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { VERSION } from "./version.js";
|
|
6
|
+
|
|
7
|
+
export const SKILL_CLIENTS = [
|
|
8
|
+
"codex",
|
|
9
|
+
"claude-code",
|
|
10
|
+
"opencode",
|
|
11
|
+
"pi",
|
|
12
|
+
] as const;
|
|
13
|
+
export type SkillClient = (typeof SKILL_CLIENTS)[number];
|
|
14
|
+
|
|
15
|
+
const sourceDirectory = fileURLToPath(
|
|
16
|
+
new URL("../skill/hubfluencer-create", import.meta.url),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
function parseClient(value: string | undefined): SkillClient {
|
|
20
|
+
if (SKILL_CLIENTS.includes(value as SkillClient)) return value as SkillClient;
|
|
21
|
+
throw new Error(
|
|
22
|
+
`install-skill requires one client: ${SKILL_CLIENTS.join(", ")}.`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function skillTarget(
|
|
27
|
+
client: SkillClient,
|
|
28
|
+
project: boolean,
|
|
29
|
+
cwd = process.cwd(),
|
|
30
|
+
home = homedir(),
|
|
31
|
+
): string {
|
|
32
|
+
const root = project ? cwd : home;
|
|
33
|
+
switch (client) {
|
|
34
|
+
case "codex":
|
|
35
|
+
return join(root, ".agents", "skills", "hubfluencer-create");
|
|
36
|
+
case "claude-code":
|
|
37
|
+
return join(root, ".claude", "skills", "hubfluencer-create");
|
|
38
|
+
case "opencode":
|
|
39
|
+
return project
|
|
40
|
+
? join(root, ".opencode", "skills", "hubfluencer-create")
|
|
41
|
+
: join(root, ".config", "opencode", "skills", "hubfluencer-create");
|
|
42
|
+
case "pi":
|
|
43
|
+
return project
|
|
44
|
+
? join(root, ".pi", "skills", "hubfluencer-create")
|
|
45
|
+
: join(root, ".pi", "agent", "skills", "hubfluencer-create");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type InstallSkillOptions = {
|
|
50
|
+
client: SkillClient;
|
|
51
|
+
project: boolean;
|
|
52
|
+
force: boolean;
|
|
53
|
+
source?: string;
|
|
54
|
+
cwd?: string;
|
|
55
|
+
home?: string;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export async function installSkill(
|
|
59
|
+
options: InstallSkillOptions,
|
|
60
|
+
): Promise<string> {
|
|
61
|
+
const source = options.source ?? sourceDirectory;
|
|
62
|
+
const target = skillTarget(
|
|
63
|
+
options.client,
|
|
64
|
+
options.project,
|
|
65
|
+
options.cwd,
|
|
66
|
+
options.home,
|
|
67
|
+
);
|
|
68
|
+
await access(source);
|
|
69
|
+
try {
|
|
70
|
+
await access(target);
|
|
71
|
+
if (!options.force) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`Skill already exists at ${target}. Re-run with --force to replace it.`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
await rm(target, { recursive: true, force: true });
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
79
|
+
}
|
|
80
|
+
await mkdir(dirname(target), { recursive: true });
|
|
81
|
+
await cp(source, target, { recursive: true });
|
|
82
|
+
return target;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function runInstallSkill(args: string[]): Promise<void> {
|
|
86
|
+
const positional = args.filter((arg) => !arg.startsWith("--"));
|
|
87
|
+
const clientFlag = args.indexOf("--client");
|
|
88
|
+
const client = parseClient(
|
|
89
|
+
clientFlag >= 0 ? args[clientFlag + 1] : positional[1],
|
|
90
|
+
);
|
|
91
|
+
const target = await installSkill({
|
|
92
|
+
client,
|
|
93
|
+
project: args.includes("--project"),
|
|
94
|
+
force: args.includes("--force"),
|
|
95
|
+
});
|
|
96
|
+
console.log(`Installed hubfluencer-create for ${client}: ${target}`);
|
|
97
|
+
console.log(
|
|
98
|
+
`Restart the agent so it discovers the skill, then run \`npx -y @hubfluencer/mcp@${VERSION} doctor --profile creator\`.`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool discovery profiles.
|
|
3
|
+
*
|
|
4
|
+
* `full` remains the default so existing MCP configurations keep the exact
|
|
5
|
+
* surface they already use. `creator` is an opt-in, low-context surface for
|
|
6
|
+
* agents that primarily need to create, iterate, recover, and download assets.
|
|
7
|
+
* The server still owns the same implementation; this only changes tools/list.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const TOOL_PROFILES = ["full", "creator"] as const;
|
|
11
|
+
export type ToolProfile = (typeof TOOL_PROFILES)[number];
|
|
12
|
+
|
|
13
|
+
export const CREATOR_TOOL_NAMES = [
|
|
14
|
+
"make_video",
|
|
15
|
+
"get_credits",
|
|
16
|
+
"list_voices",
|
|
17
|
+
"list_projects",
|
|
18
|
+
"create_short",
|
|
19
|
+
"update_short",
|
|
20
|
+
"generate_short",
|
|
21
|
+
"cancel_short_generation",
|
|
22
|
+
"rerender_short",
|
|
23
|
+
"set_short_product",
|
|
24
|
+
"set_short_logo",
|
|
25
|
+
"set_short_poster",
|
|
26
|
+
"create_slider",
|
|
27
|
+
"generate_slider",
|
|
28
|
+
"get_slider",
|
|
29
|
+
"restyle_slider",
|
|
30
|
+
"edit_slider_slide",
|
|
31
|
+
"create_tracking_video",
|
|
32
|
+
"create_editor_ad",
|
|
33
|
+
"start_autopilot",
|
|
34
|
+
"provide_generation_input",
|
|
35
|
+
"get_editor",
|
|
36
|
+
"retry_segment_generation",
|
|
37
|
+
"cancel_segment_generation",
|
|
38
|
+
"retry_render",
|
|
39
|
+
"retry_editor_batch",
|
|
40
|
+
"cancel_editor_batch",
|
|
41
|
+
"cancel_autopilot",
|
|
42
|
+
"get_status",
|
|
43
|
+
"wait_for_completion",
|
|
44
|
+
"download_result",
|
|
45
|
+
] as const;
|
|
46
|
+
|
|
47
|
+
const creatorTools = new Set<string>(CREATOR_TOOL_NAMES);
|
|
48
|
+
|
|
49
|
+
export function parseProfile(value: string | undefined): ToolProfile {
|
|
50
|
+
const normalized = value?.trim().toLowerCase();
|
|
51
|
+
if (!normalized || normalized === "full") return "full";
|
|
52
|
+
if (normalized === "creator") return "creator";
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Unknown Hubfluencer MCP profile "${value}". Use "creator" or "full".`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function profileFromArgs(
|
|
59
|
+
args: string[],
|
|
60
|
+
envValue = process.env.HUBFLUENCER_MCP_PROFILE,
|
|
61
|
+
): ToolProfile {
|
|
62
|
+
const equalsArg = args.find((arg) => arg.startsWith("--profile="));
|
|
63
|
+
if (equalsArg) return parseProfile(equalsArg.slice("--profile=".length));
|
|
64
|
+
|
|
65
|
+
const index = args.indexOf("--profile");
|
|
66
|
+
if (index >= 0) {
|
|
67
|
+
const value = args[index + 1];
|
|
68
|
+
if (!value || value.startsWith("-")) {
|
|
69
|
+
throw new Error("--profile requires creator or full.");
|
|
70
|
+
}
|
|
71
|
+
return parseProfile(value);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return parseProfile(envValue);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function withoutProfileArgs(args: string[]): string[] {
|
|
78
|
+
const result: string[] = [];
|
|
79
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
80
|
+
const arg = args[index];
|
|
81
|
+
if (arg.startsWith("--profile=")) continue;
|
|
82
|
+
if (arg === "--profile") {
|
|
83
|
+
index += 1;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
result.push(arg);
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function toolEnabled(profile: ToolProfile, name: string): boolean {
|
|
92
|
+
return profile === "full" || creatorTools.has(name);
|
|
93
|
+
}
|