@koda-sl/baker-cli 0.204.0 → 0.209.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 +56 -3
- package/dist/{chunk-UZYVSPCA.js → chunk-3E33AGNR.js} +2 -2
- package/dist/{chunk-SVHFT7AN.js → chunk-3WODSPNZ.js} +4 -4
- package/dist/{chunk-2R2DEPG7.js → chunk-CRXSLYEX.js} +3 -3
- package/dist/{chunk-5MPIOGRO.js → chunk-E2WYWHJJ.js} +370 -221
- package/dist/chunk-E2WYWHJJ.js.map +1 -0
- package/dist/{chunk-KHHD26LZ.js → chunk-FUSTYLP5.js} +3 -3
- package/dist/{chunk-6NZG2TCM.js → chunk-IRNM3T6B.js} +6 -1
- package/dist/{chunk-6NZG2TCM.js.map → chunk-IRNM3T6B.js.map} +1 -1
- package/dist/cli.js +732 -121
- package/dist/cli.js.map +1 -1
- package/dist/client-CF6ZORCO.js +15 -0
- package/dist/engine/index.js +3 -3
- package/dist/{env-FF7ZB5W5.js → env-7SCX7HAH.js} +4 -2
- package/dist/{output-YGPWST77.js → output-4YIOSWOG.js} +5 -5
- package/dist/{shared-CRWB5B7P.js → shared-EBF5DEDJ.js} +6 -6
- package/package.json +1 -1
- package/dist/chunk-5MPIOGRO.js.map +0 -1
- package/dist/client-MP7BKFKM.js +0 -15
- /package/dist/{chunk-UZYVSPCA.js.map → chunk-3E33AGNR.js.map} +0 -0
- /package/dist/{chunk-SVHFT7AN.js.map → chunk-3WODSPNZ.js.map} +0 -0
- /package/dist/{chunk-2R2DEPG7.js.map → chunk-CRXSLYEX.js.map} +0 -0
- /package/dist/{chunk-KHHD26LZ.js.map → chunk-FUSTYLP5.js.map} +0 -0
- /package/dist/{client-MP7BKFKM.js.map → client-CF6ZORCO.js.map} +0 -0
- /package/dist/{env-FF7ZB5W5.js.map → env-7SCX7HAH.js.map} +0 -0
- /package/dist/{output-YGPWST77.js.map → output-4YIOSWOG.js.map} +0 -0
- /package/dist/{shared-CRWB5B7P.js.map → shared-EBF5DEDJ.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -18,18 +18,23 @@ import {
|
|
|
18
18
|
SEEDANCE_PROFILE,
|
|
19
19
|
VIDEO_GENERATE_MODELS,
|
|
20
20
|
ValidationError,
|
|
21
|
+
YtDlpError,
|
|
22
|
+
canonicalUrl,
|
|
21
23
|
clipParamRecipe,
|
|
22
24
|
clipProfileFor,
|
|
23
25
|
collectAssetRefLikes,
|
|
24
26
|
createEngineFromEnv,
|
|
25
27
|
defaultRegistry,
|
|
26
28
|
describeFailureReason,
|
|
29
|
+
durationSeconds,
|
|
27
30
|
elementMentionKeywords,
|
|
28
31
|
estimateVideoCredits,
|
|
29
32
|
fetchExternalBytes,
|
|
30
33
|
generateCatalog,
|
|
31
34
|
imageProfileFor,
|
|
35
|
+
isAudioOnly,
|
|
32
36
|
isPersistedAssetRef,
|
|
37
|
+
isVimeoAuthFailure,
|
|
33
38
|
looksLikeHttpUrl,
|
|
34
39
|
maxInputReferences,
|
|
35
40
|
maxInputSlot,
|
|
@@ -38,9 +43,11 @@ import {
|
|
|
38
43
|
nearestSupportedImageSize,
|
|
39
44
|
parseRefExpr,
|
|
40
45
|
platformFormats,
|
|
46
|
+
probeYtDlp,
|
|
41
47
|
promptMaxLength,
|
|
42
48
|
requireCredentialsFromEnv,
|
|
43
49
|
resolveConcurrency,
|
|
50
|
+
runYtDlp,
|
|
44
51
|
sha256Hex,
|
|
45
52
|
spineInputFlags,
|
|
46
53
|
spineInputOps,
|
|
@@ -49,8 +56,9 @@ import {
|
|
|
49
56
|
supportsReferenceToVideo,
|
|
50
57
|
toModelSafeImage,
|
|
51
58
|
ulid,
|
|
52
|
-
validateCanvasDeep
|
|
53
|
-
|
|
59
|
+
validateCanvasDeep,
|
|
60
|
+
ytDlpBlockSignal
|
|
61
|
+
} from "./chunk-E2WYWHJJ.js";
|
|
54
62
|
import {
|
|
55
63
|
csvOrJson,
|
|
56
64
|
daysAgoIso,
|
|
@@ -59,7 +67,7 @@ import {
|
|
|
59
67
|
resolveAccountIdArg,
|
|
60
68
|
resolveEffectiveStatus,
|
|
61
69
|
todayIso
|
|
62
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-3WODSPNZ.js";
|
|
63
71
|
import {
|
|
64
72
|
buildQueryCacheKey,
|
|
65
73
|
cacheGet,
|
|
@@ -75,20 +83,21 @@ import {
|
|
|
75
83
|
writeAdsJson,
|
|
76
84
|
writeAdsOutput,
|
|
77
85
|
writeJsonEnvelope
|
|
78
|
-
} from "./chunk-
|
|
86
|
+
} from "./chunk-FUSTYLP5.js";
|
|
79
87
|
import {
|
|
80
88
|
ApiError,
|
|
81
89
|
apiGet,
|
|
82
90
|
apiPost,
|
|
83
91
|
validateConvexId
|
|
84
|
-
} from "./chunk-
|
|
92
|
+
} from "./chunk-CRXSLYEX.js";
|
|
85
93
|
import {
|
|
86
94
|
installStreamTaps,
|
|
87
95
|
logInvocation
|
|
88
|
-
} from "./chunk-
|
|
96
|
+
} from "./chunk-3E33AGNR.js";
|
|
89
97
|
import {
|
|
90
98
|
captureBudgetMs,
|
|
91
99
|
captureProxyCredentials,
|
|
100
|
+
captureVimeoCookie,
|
|
92
101
|
getEnv,
|
|
93
102
|
isProxyFailure,
|
|
94
103
|
plannedRoutes,
|
|
@@ -96,10 +105,10 @@ import {
|
|
|
96
105
|
requireChatId,
|
|
97
106
|
resolveChatId,
|
|
98
107
|
shouldEscalate
|
|
99
|
-
} from "./chunk-
|
|
108
|
+
} from "./chunk-IRNM3T6B.js";
|
|
100
109
|
|
|
101
110
|
// src/cli.ts
|
|
102
|
-
import { defineCommand as
|
|
111
|
+
import { defineCommand as defineCommand217, runMain } from "citty";
|
|
103
112
|
|
|
104
113
|
// src/commands/actions/index.ts
|
|
105
114
|
import { defineCommand as defineCommand18 } from "citty";
|
|
@@ -4785,6 +4794,60 @@ var hubspotWorkflowsListResponseSchema = z12.object({
|
|
|
4785
4794
|
ok: z12.literal(true),
|
|
4786
4795
|
data: z12.object({ workflows: z12.array(hubspotWorkflowSummarySchema) })
|
|
4787
4796
|
});
|
|
4797
|
+
var hubspotWorkflowsViewRequestSchema = z12.object({
|
|
4798
|
+
/** The id from `workflows list`. Resolved to HubSpot's internal flow id server-side. */
|
|
4799
|
+
workflowId: z12.string().min(1),
|
|
4800
|
+
/**
|
|
4801
|
+
* Include the untouched HubSpot payload — the only place the branch filter
|
|
4802
|
+
* criteria (which property is compared to which value) can be read.
|
|
4803
|
+
*/
|
|
4804
|
+
full: z12.boolean().optional()
|
|
4805
|
+
});
|
|
4806
|
+
var hubspotWorkflowBranchSchema = z12.object({
|
|
4807
|
+
/**
|
|
4808
|
+
* 1-based position in the order HubSpot lists the branches. On a `LIST_BRANCH`
|
|
4809
|
+
* that is precedence — branch 1 wins over branch 2. On a `STATIC_BRANCH` it is
|
|
4810
|
+
* only display order; those match on the value in `name`, not top down.
|
|
4811
|
+
*/
|
|
4812
|
+
order: z12.number().int(),
|
|
4813
|
+
name: z12.string(),
|
|
4814
|
+
nextActionId: z12.string().nullable()
|
|
4815
|
+
});
|
|
4816
|
+
var hubspotWorkflowStepSchema = z12.object({
|
|
4817
|
+
actionId: z12.string(),
|
|
4818
|
+
/** HubSpot's action kind: LIST_BRANCH, SINGLE_CONNECTION, CUSTOM_CODE, WEBHOOK... */
|
|
4819
|
+
type: z12.string(),
|
|
4820
|
+
actionTypeId: z12.string().nullable(),
|
|
4821
|
+
/** The step's configured inputs — for a property-setting step, what it writes and where. */
|
|
4822
|
+
fields: z12.record(z12.string(), z12.unknown()).nullable(),
|
|
4823
|
+
nextActionId: z12.string().nullable(),
|
|
4824
|
+
/** Present only on branch steps, in evaluation order. */
|
|
4825
|
+
branches: z12.array(hubspotWorkflowBranchSchema).nullable(),
|
|
4826
|
+
defaultBranch: z12.object({ name: z12.string().nullable(), nextActionId: z12.string().nullable() }).nullable()
|
|
4827
|
+
});
|
|
4828
|
+
var hubspotWorkflowDetailSchema = z12.object({
|
|
4829
|
+
/** HubSpot's internal flow id, which differs from the workflow id. */
|
|
4830
|
+
id: z12.string(),
|
|
4831
|
+
/** The id this workflow is known by in `workflows list` and in HubSpot's own URLs. */
|
|
4832
|
+
workflowId: z12.string(),
|
|
4833
|
+
name: z12.string(),
|
|
4834
|
+
enabled: z12.boolean(),
|
|
4835
|
+
flowType: z12.string().nullable(),
|
|
4836
|
+
/** The record type it runs on: "0-1" contacts, "0-2" companies, "0-3" deals. */
|
|
4837
|
+
objectTypeId: z12.string().nullable(),
|
|
4838
|
+
enrollment: z12.object({
|
|
4839
|
+
type: z12.string().nullable(),
|
|
4840
|
+
/** Null means HubSpot's criteria shape does not report it — unknown, not off. */
|
|
4841
|
+
reEnrollment: z12.boolean().nullable()
|
|
4842
|
+
}),
|
|
4843
|
+
/** Every step. Execution order follows `nextActionId`, not the array index. */
|
|
4844
|
+
steps: z12.array(hubspotWorkflowStepSchema),
|
|
4845
|
+
raw: z12.record(z12.string(), z12.unknown()).optional()
|
|
4846
|
+
});
|
|
4847
|
+
var hubspotWorkflowsViewResponseSchema = z12.object({
|
|
4848
|
+
ok: z12.literal(true),
|
|
4849
|
+
data: z12.object({ workflow: hubspotWorkflowDetailSchema })
|
|
4850
|
+
});
|
|
4788
4851
|
var hubspotContactsSummaryRequestSchema = z12.object({
|
|
4789
4852
|
/** Window in days, counted back from now, over contact creation date. */
|
|
4790
4853
|
days: z12.number().int().min(1).max(365).default(30)
|
|
@@ -5761,6 +5824,30 @@ var testimonialsOutscraperWebhookResponseSchema = z18.object({
|
|
|
5761
5824
|
// ../api/src/videos.ts
|
|
5762
5825
|
import { z as z19 } from "zod";
|
|
5763
5826
|
var videoStatusSchema = z19.enum(["uploading", "uploaded", "processing", "ready", "error"]);
|
|
5827
|
+
var videoSourceSchema = z19.enum([
|
|
5828
|
+
"uploaded",
|
|
5829
|
+
"url",
|
|
5830
|
+
"bilibili",
|
|
5831
|
+
"bluesky",
|
|
5832
|
+
"dailymotion",
|
|
5833
|
+
"facebook",
|
|
5834
|
+
"instagram",
|
|
5835
|
+
"loom",
|
|
5836
|
+
"newgrounds",
|
|
5837
|
+
"ok",
|
|
5838
|
+
"pinterest",
|
|
5839
|
+
"reddit",
|
|
5840
|
+
"rutube",
|
|
5841
|
+
"snapchat",
|
|
5842
|
+
"streamable",
|
|
5843
|
+
"tiktok",
|
|
5844
|
+
"tumblr",
|
|
5845
|
+
"twitch",
|
|
5846
|
+
"twitter",
|
|
5847
|
+
"vimeo",
|
|
5848
|
+
"vk",
|
|
5849
|
+
"youtube"
|
|
5850
|
+
]);
|
|
5764
5851
|
var videoTranscriptSegmentSchema = z19.object({
|
|
5765
5852
|
text: z19.string(),
|
|
5766
5853
|
startSecond: z19.number(),
|
|
@@ -5861,6 +5948,25 @@ var videosUploadRequestSchema = z19.object({
|
|
|
5861
5948
|
descriptionContext: z19.string().optional()
|
|
5862
5949
|
});
|
|
5863
5950
|
var videosUploadResponseSchema = z19.object({ uploadUrl: z19.string(), videoId: z19.string() });
|
|
5951
|
+
var videosIngestRequestSchema = z19.object({
|
|
5952
|
+
// Must be a direct media file Mux can pull (mp4/mov/webm/…). A page URL —
|
|
5953
|
+
// a YouTube watch link, a TikTok post — is not one: the CLI resolves those to
|
|
5954
|
+
// a media file before calling this route.
|
|
5955
|
+
url: z19.string().url(),
|
|
5956
|
+
source: videoSourceSchema,
|
|
5957
|
+
externalId: z19.string().optional(),
|
|
5958
|
+
externalUrl: z19.string().optional(),
|
|
5959
|
+
// What the person adding the clip knew that the frames cannot show — which
|
|
5960
|
+
// campaign it belongs to, whether the person on camera is a real customer.
|
|
5961
|
+
// The upload route has carried this since it existed; ingest silently dropped
|
|
5962
|
+
// it, so a link-added clip was analysed with strictly less to go on than the
|
|
5963
|
+
// same file uploaded by hand.
|
|
5964
|
+
descriptionContext: z19.string().optional()
|
|
5965
|
+
});
|
|
5966
|
+
var videosIngestResponseSchema = z19.object({
|
|
5967
|
+
videoId: z19.string(),
|
|
5968
|
+
deduped: z19.boolean()
|
|
5969
|
+
});
|
|
5864
5970
|
var videosDeleteRequestSchema = z19.object({ id: z19.string().min(1, "Missing video ID") });
|
|
5865
5971
|
var videosDeleteResponseSchema = z19.object({ ok: z19.literal(true) });
|
|
5866
5972
|
|
|
@@ -8821,11 +8927,11 @@ function rawTextEntries(value) {
|
|
|
8821
8927
|
const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
8822
8928
|
return values.filter((v) => typeof v === "string").flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
|
|
8823
8929
|
}
|
|
8824
|
-
function rawFileEntries(
|
|
8825
|
-
if (typeof
|
|
8930
|
+
function rawFileEntries(path38) {
|
|
8931
|
+
if (typeof path38 !== "string" || path38.length === 0) {
|
|
8826
8932
|
return [];
|
|
8827
8933
|
}
|
|
8828
|
-
return readFileSync2(
|
|
8934
|
+
return readFileSync2(path38, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
8829
8935
|
}
|
|
8830
8936
|
function keywordEntries(args) {
|
|
8831
8937
|
const defaultMatch = typeof args["match-type"] === "string" ? args["match-type"].toUpperCase() : void 0;
|
|
@@ -8848,19 +8954,19 @@ function keywordEntries(args) {
|
|
|
8848
8954
|
}
|
|
8849
8955
|
return entries;
|
|
8850
8956
|
}
|
|
8851
|
-
function loadJsonFileArg(
|
|
8852
|
-
if (typeof
|
|
8957
|
+
function loadJsonFileArg(path38) {
|
|
8958
|
+
if (typeof path38 !== "string" || path38.length === 0) {
|
|
8853
8959
|
return {};
|
|
8854
8960
|
}
|
|
8855
8961
|
try {
|
|
8856
|
-
const parsed = JSON.parse(readFileSync2(
|
|
8962
|
+
const parsed = JSON.parse(readFileSync2(path38, "utf8"));
|
|
8857
8963
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8858
|
-
failWriteValidation(`${
|
|
8964
|
+
failWriteValidation(`${path38} must contain a JSON object`);
|
|
8859
8965
|
}
|
|
8860
8966
|
return parsed;
|
|
8861
8967
|
} catch (err) {
|
|
8862
8968
|
if (err instanceof SyntaxError) {
|
|
8863
|
-
failWriteValidation(`${
|
|
8969
|
+
failWriteValidation(`${path38} is not valid JSON: ${err.message}`);
|
|
8864
8970
|
}
|
|
8865
8971
|
throw err;
|
|
8866
8972
|
}
|
|
@@ -8990,10 +9096,10 @@ async function stageUpdate(kind, customerId, target, payload, hints) {
|
|
|
8990
9096
|
async function stageTarget(kind, customerId, target, hints) {
|
|
8991
9097
|
await stageGoogleOp({ kind, customerId, target }, hints);
|
|
8992
9098
|
}
|
|
8993
|
-
async function draftAction(
|
|
9099
|
+
async function draftAction(path38, body, chat) {
|
|
8994
9100
|
try {
|
|
8995
9101
|
const chatId = resolveChatId(chat);
|
|
8996
|
-
const response = await apiPost(
|
|
9102
|
+
const response = await apiPost(path38, { chatId, ...body });
|
|
8997
9103
|
writeJsonEnvelope(response);
|
|
8998
9104
|
} catch (err) {
|
|
8999
9105
|
handleGoogleError(err);
|
|
@@ -11851,19 +11957,19 @@ function failWriteValidation2(message) {
|
|
|
11851
11957
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
11852
11958
|
process.exit(1);
|
|
11853
11959
|
}
|
|
11854
|
-
function loadJsonFileArg2(
|
|
11855
|
-
if (typeof
|
|
11960
|
+
function loadJsonFileArg2(path38) {
|
|
11961
|
+
if (typeof path38 !== "string" || path38.length === 0) {
|
|
11856
11962
|
return {};
|
|
11857
11963
|
}
|
|
11858
11964
|
try {
|
|
11859
|
-
const parsed = JSON.parse(readFileSync4(
|
|
11965
|
+
const parsed = JSON.parse(readFileSync4(path38, "utf8"));
|
|
11860
11966
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
11861
|
-
failWriteValidation2(`${
|
|
11967
|
+
failWriteValidation2(`${path38} must contain a JSON object`);
|
|
11862
11968
|
}
|
|
11863
11969
|
return parsed;
|
|
11864
11970
|
} catch (err) {
|
|
11865
11971
|
if (err instanceof SyntaxError) {
|
|
11866
|
-
failWriteValidation2(`${
|
|
11972
|
+
failWriteValidation2(`${path38} is not valid JSON: ${err.message}`);
|
|
11867
11973
|
}
|
|
11868
11974
|
throw err;
|
|
11869
11975
|
}
|
|
@@ -11948,15 +12054,15 @@ function parseLocaleFlag(value) {
|
|
|
11948
12054
|
}
|
|
11949
12055
|
return { language: match[1], country: match[2].toUpperCase() };
|
|
11950
12056
|
}
|
|
11951
|
-
function loadTargetingFileArg(
|
|
11952
|
-
if (typeof
|
|
12057
|
+
function loadTargetingFileArg(path38) {
|
|
12058
|
+
if (typeof path38 !== "string" || path38.length === 0) {
|
|
11953
12059
|
return void 0;
|
|
11954
12060
|
}
|
|
11955
|
-
const parsed = loadJsonFileArg2(
|
|
12061
|
+
const parsed = loadJsonFileArg2(path38);
|
|
11956
12062
|
const criteria = parsed.targetingCriteria ?? parsed;
|
|
11957
12063
|
if (!criteria.include) {
|
|
11958
12064
|
failWriteValidation2(
|
|
11959
|
-
`${
|
|
12065
|
+
`${path38} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
|
|
11960
12066
|
);
|
|
11961
12067
|
}
|
|
11962
12068
|
return criteria;
|
|
@@ -11991,14 +12097,14 @@ function parseCsvLine(line) {
|
|
|
11991
12097
|
cells.push(current);
|
|
11992
12098
|
return cells.map((cell) => cell.trim());
|
|
11993
12099
|
}
|
|
11994
|
-
function parseListFileArg(
|
|
11995
|
-
if (typeof
|
|
12100
|
+
function parseListFileArg(path38, maxRows) {
|
|
12101
|
+
if (typeof path38 !== "string" || path38.length === 0) {
|
|
11996
12102
|
return void 0;
|
|
11997
12103
|
}
|
|
11998
|
-
const raw = readFileSync4(
|
|
12104
|
+
const raw = readFileSync4(path38, "utf8");
|
|
11999
12105
|
const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
12000
12106
|
if (lines.length < 2) {
|
|
12001
|
-
failWriteValidation2(`${
|
|
12107
|
+
failWriteValidation2(`${path38} needs a header row and at least one data row`);
|
|
12002
12108
|
}
|
|
12003
12109
|
const columns = parseCsvLine(lines[0]).map((column) => column.trim());
|
|
12004
12110
|
const rows = [];
|
|
@@ -12017,7 +12123,7 @@ function parseListFileArg(path37, maxRows) {
|
|
|
12017
12123
|
}
|
|
12018
12124
|
}
|
|
12019
12125
|
if (rows.length > maxRows) {
|
|
12020
|
-
failWriteValidation2(`${
|
|
12126
|
+
failWriteValidation2(`${path38} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
|
|
12021
12127
|
}
|
|
12022
12128
|
return { columns, rows };
|
|
12023
12129
|
}
|
|
@@ -12113,11 +12219,11 @@ function readPositionals(args) {
|
|
|
12113
12219
|
function splitIdList(raw) {
|
|
12114
12220
|
return raw.split(",").map((id) => id.trim()).filter(Boolean);
|
|
12115
12221
|
}
|
|
12116
|
-
function idsFileEntries(
|
|
12117
|
-
if (typeof
|
|
12222
|
+
function idsFileEntries(path38) {
|
|
12223
|
+
if (typeof path38 !== "string" || path38.length === 0) {
|
|
12118
12224
|
return [];
|
|
12119
12225
|
}
|
|
12120
|
-
return readFileSync4(
|
|
12226
|
+
return readFileSync4(path38, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).flatMap(splitIdList);
|
|
12121
12227
|
}
|
|
12122
12228
|
function requireTargets(args, entity) {
|
|
12123
12229
|
const positionals = readPositionals(args);
|
|
@@ -14740,9 +14846,9 @@ function compactRow(row) {
|
|
|
14740
14846
|
...destination.postUrn ? { postUrn: destination.postUrn } : {}
|
|
14741
14847
|
};
|
|
14742
14848
|
}
|
|
14743
|
-
function readPath(row,
|
|
14849
|
+
function readPath(row, path38) {
|
|
14744
14850
|
let current = row;
|
|
14745
|
-
for (const segment of
|
|
14851
|
+
for (const segment of path38.split(".")) {
|
|
14746
14852
|
const record = asRecord2(current);
|
|
14747
14853
|
if (!record) return void 0;
|
|
14748
14854
|
current = record[segment];
|
|
@@ -14752,10 +14858,10 @@ function readPath(row, path37) {
|
|
|
14752
14858
|
function projectFields(rows, paths) {
|
|
14753
14859
|
return rows.map((row) => {
|
|
14754
14860
|
const projected = {};
|
|
14755
|
-
for (const
|
|
14756
|
-
const value = readPath(row,
|
|
14861
|
+
for (const path38 of paths) {
|
|
14862
|
+
const value = readPath(row, path38);
|
|
14757
14863
|
if (value !== void 0) {
|
|
14758
|
-
projected[
|
|
14864
|
+
projected[path38] = value;
|
|
14759
14865
|
}
|
|
14760
14866
|
}
|
|
14761
14867
|
return projected;
|
|
@@ -16049,11 +16155,11 @@ var updateStatusSchema = z21.enum(UPDATE_STATUSES);
|
|
|
16049
16155
|
function currencyMinimums2(currencyCode) {
|
|
16050
16156
|
return CURRENCY_MINIMUMS2[currencyCode] ?? DEFAULT_CURRENCY_MINIMUM2;
|
|
16051
16157
|
}
|
|
16052
|
-
function validateDailyBudgetFloor(money, ctx,
|
|
16158
|
+
function validateDailyBudgetFloor(money, ctx, path38) {
|
|
16053
16159
|
if (money?.currencyCode) {
|
|
16054
16160
|
const min = currencyMinimums2(money.currencyCode).dailyBudgetMin;
|
|
16055
16161
|
if (Number(money.amount) < min) {
|
|
16056
|
-
ctx.addIssue({ code: "custom", path:
|
|
16162
|
+
ctx.addIssue({ code: "custom", path: path38, message: `below the ${min} ${money.currencyCode} daily minimum` });
|
|
16057
16163
|
}
|
|
16058
16164
|
}
|
|
16059
16165
|
}
|
|
@@ -16715,19 +16821,19 @@ function failWriteValidation3(message) {
|
|
|
16715
16821
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
16716
16822
|
process.exit(1);
|
|
16717
16823
|
}
|
|
16718
|
-
function loadJsonFileArg3(
|
|
16719
|
-
if (typeof
|
|
16824
|
+
function loadJsonFileArg3(path38) {
|
|
16825
|
+
if (typeof path38 !== "string" || path38.length === 0) {
|
|
16720
16826
|
return {};
|
|
16721
16827
|
}
|
|
16722
16828
|
try {
|
|
16723
|
-
const parsed = JSON.parse(readFileSync8(
|
|
16829
|
+
const parsed = JSON.parse(readFileSync8(path38, "utf8"));
|
|
16724
16830
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
16725
|
-
failWriteValidation3(`${
|
|
16831
|
+
failWriteValidation3(`${path38} must contain a JSON object`);
|
|
16726
16832
|
}
|
|
16727
16833
|
return parsed;
|
|
16728
16834
|
} catch (err) {
|
|
16729
16835
|
if (err instanceof SyntaxError) {
|
|
16730
|
-
failWriteValidation3(`${
|
|
16836
|
+
failWriteValidation3(`${path38} is not valid JSON: ${err.message}`);
|
|
16731
16837
|
}
|
|
16732
16838
|
throw err;
|
|
16733
16839
|
}
|
|
@@ -17270,10 +17376,10 @@ function duplicateCommand2(entity, label) {
|
|
|
17270
17376
|
replace: { type: "boolean", description: "Pause the original once the copy publishes" }
|
|
17271
17377
|
},
|
|
17272
17378
|
run: async ({ args }) => {
|
|
17273
|
-
const { apiPost: apiPost2 } = await import("./client-
|
|
17274
|
-
const { requireChatId: requireChatId2 } = await import("./env-
|
|
17275
|
-
const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-
|
|
17276
|
-
const { handleMetaError: handleMetaError2 } = await import("./shared-
|
|
17379
|
+
const { apiPost: apiPost2 } = await import("./client-CF6ZORCO.js");
|
|
17380
|
+
const { requireChatId: requireChatId2 } = await import("./env-7SCX7HAH.js");
|
|
17381
|
+
const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-4YIOSWOG.js");
|
|
17382
|
+
const { handleMetaError: handleMetaError2 } = await import("./shared-EBF5DEDJ.js");
|
|
17277
17383
|
try {
|
|
17278
17384
|
const accountId = bareAccountId2(args);
|
|
17279
17385
|
const chatId = requireChatId2();
|
|
@@ -27898,12 +28004,12 @@ function listFlowSlugs() {
|
|
|
27898
28004
|
return readdirSync2(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_") && entry.name !== ".gitkeep").map((entry) => entry.name).sort();
|
|
27899
28005
|
}
|
|
27900
28006
|
function readFlowTree(slug) {
|
|
27901
|
-
const
|
|
27902
|
-
if (!existsSync5(
|
|
28007
|
+
const path38 = join3(flowsDir(), slug, "_data.json");
|
|
28008
|
+
if (!existsSync5(path38)) {
|
|
27903
28009
|
failLocal(`No form "${slug}". Run "baker flows list" to see the forms in this workspace.`);
|
|
27904
28010
|
}
|
|
27905
28011
|
try {
|
|
27906
|
-
return JSON.parse(readFileSync9(
|
|
28012
|
+
return JSON.parse(readFileSync9(path38, "utf-8"));
|
|
27907
28013
|
} catch (error) {
|
|
27908
28014
|
failLocal(`Could not read form "${slug}": ${error instanceof Error ? error.message : String(error)}`);
|
|
27909
28015
|
}
|
|
@@ -28316,10 +28422,10 @@ async function stageOps(ops) {
|
|
|
28316
28422
|
handleError(err);
|
|
28317
28423
|
}
|
|
28318
28424
|
}
|
|
28319
|
-
async function draftAction2(
|
|
28425
|
+
async function draftAction2(path38, body, chat) {
|
|
28320
28426
|
const chatId = resolveChatId(chat);
|
|
28321
28427
|
try {
|
|
28322
|
-
const data = await apiPost(
|
|
28428
|
+
const data = await apiPost(path38, { chatId, ...body });
|
|
28323
28429
|
writeJsonEnvelope({ ok: true, data });
|
|
28324
28430
|
return data;
|
|
28325
28431
|
} catch (err) {
|
|
@@ -29820,6 +29926,23 @@ registerSchema({
|
|
|
29820
29926
|
"enabled-only": { type: "boolean", description: "Only workflows that are on", required: false, default: false }
|
|
29821
29927
|
}
|
|
29822
29928
|
});
|
|
29929
|
+
registerSchema({
|
|
29930
|
+
command: "hubspot.workflows.view",
|
|
29931
|
+
description: "One workflow's actual configuration: what enrols a record, whether it re-enrols, and every step with the property it writes. Read it before saying a workflow is correct \u2014 `workflows list` only tells you it exists and whether it is on. Branches come back in HubSpot's order, and on a LIST_BRANCH step that is evaluation order (`order: 1` wins over `order: 2`) \u2014 which is where this kind of setup silently breaks: a broad branch placed above a narrow one swallows the records the narrow one was written for, and nothing reports it. A STATIC_BRANCH matches on a value instead, so its order carries no precedence. Steps are NOT in execution order \u2014 follow `nextActionId` from step to step. Add --full for the branch filter criteria (which property is compared to which value).",
|
|
29932
|
+
args: {
|
|
29933
|
+
workflowId: {
|
|
29934
|
+
type: "positional",
|
|
29935
|
+
description: "Workflow id (from `baker hubspot workflows list`)",
|
|
29936
|
+
required: true
|
|
29937
|
+
},
|
|
29938
|
+
full: {
|
|
29939
|
+
type: "boolean",
|
|
29940
|
+
description: "Include the raw HubSpot payload \u2014 the only place the branch filter criteria can be read",
|
|
29941
|
+
required: false,
|
|
29942
|
+
default: false
|
|
29943
|
+
}
|
|
29944
|
+
}
|
|
29945
|
+
});
|
|
29823
29946
|
registerSchema({
|
|
29824
29947
|
command: "hubspot.pipelines.list",
|
|
29825
29948
|
description: "The account's deal pipelines and their stages, in the order they run. Use it to answer where a lead lands once captured and whether it moved \u2014 the 'have these leads been worked?' half of a form audit.",
|
|
@@ -30119,6 +30242,62 @@ var workflowsListCommand = defineCommand120({
|
|
|
30119
30242
|
}
|
|
30120
30243
|
}
|
|
30121
30244
|
});
|
|
30245
|
+
function workflowViewHints(workflow) {
|
|
30246
|
+
const hints = [];
|
|
30247
|
+
if (!workflow.enabled) {
|
|
30248
|
+
hints.push(
|
|
30249
|
+
"This workflow is switched off, so nothing below it is running now \u2014 it may still have run while it was on. Say so before reporting on what it does."
|
|
30250
|
+
);
|
|
30251
|
+
}
|
|
30252
|
+
const ordered = workflow.steps.filter((step) => step.type === "LIST_BRANCH" && (step.branches?.length ?? 0) > 1);
|
|
30253
|
+
if (ordered.length > 0) {
|
|
30254
|
+
hints.push(
|
|
30255
|
+
"Branches are listed in evaluation order and the first match wins. Check that no broad branch sits above a narrower one \u2014 if it does, the narrow branch never runs and nothing in HubSpot reports it."
|
|
30256
|
+
);
|
|
30257
|
+
}
|
|
30258
|
+
if (workflow.raw === void 0 && ordered.length > 0) {
|
|
30259
|
+
hints.push(
|
|
30260
|
+
"This view names the branches but not what they compare. Re-run with --full to read the filter criteria before judging whether a branch matches what it was meant to match."
|
|
30261
|
+
);
|
|
30262
|
+
}
|
|
30263
|
+
if (workflow.enrollment.reEnrollment === false) {
|
|
30264
|
+
hints.push(
|
|
30265
|
+
"Re-enrolment is off: a record that meets the criteria a second time will not run through this again. That is a real choice, not necessarily a fault \u2014 but confirm it is the intended one."
|
|
30266
|
+
);
|
|
30267
|
+
} else if (workflow.enrollment.reEnrollment === null) {
|
|
30268
|
+
hints.push(
|
|
30269
|
+
"Whether this re-enrols is not reported for this kind of enrolment, so do not state it either way \u2014 read it in HubSpot if it matters."
|
|
30270
|
+
);
|
|
30271
|
+
}
|
|
30272
|
+
return hints;
|
|
30273
|
+
}
|
|
30274
|
+
var workflowsViewCommand = defineCommand120({
|
|
30275
|
+
meta: {
|
|
30276
|
+
name: "view",
|
|
30277
|
+
description: "Read one workflow's real configuration \u2014 enrolment, branches in order, and what each step writes. Example: baker hubspot workflows view 121183594 --full"
|
|
30278
|
+
},
|
|
30279
|
+
args: {
|
|
30280
|
+
workflowId: {
|
|
30281
|
+
type: "positional",
|
|
30282
|
+
description: "Workflow id (from `baker hubspot workflows list`)",
|
|
30283
|
+
required: true
|
|
30284
|
+
},
|
|
30285
|
+
full: { type: "boolean", description: "Include the raw HubSpot payload with the filter criteria", required: false }
|
|
30286
|
+
},
|
|
30287
|
+
run: async ({ args }) => {
|
|
30288
|
+
try {
|
|
30289
|
+
const response = await apiPost("/api/hubspot/workflows/view", {
|
|
30290
|
+
workflowId: args.workflowId,
|
|
30291
|
+
...args.full ? { full: true } : {}
|
|
30292
|
+
});
|
|
30293
|
+
const { workflow } = response.data;
|
|
30294
|
+
const hints = workflowViewHints(workflow);
|
|
30295
|
+
writeJson({ ...response, meta: { count: workflow.steps.length }, ...hints.length > 0 ? { hints } : {} });
|
|
30296
|
+
} catch (err) {
|
|
30297
|
+
failNotConnected(err);
|
|
30298
|
+
}
|
|
30299
|
+
}
|
|
30300
|
+
});
|
|
30122
30301
|
var pipelinesListCommand = defineCommand120({
|
|
30123
30302
|
meta: {
|
|
30124
30303
|
name: "list",
|
|
@@ -30240,7 +30419,7 @@ var formsCommand = defineCommand120({
|
|
|
30240
30419
|
});
|
|
30241
30420
|
var workflowsCommand = defineCommand120({
|
|
30242
30421
|
meta: { name: "workflows", description: "HubSpot workflows on the connected account." },
|
|
30243
|
-
subCommands: { list: workflowsListCommand }
|
|
30422
|
+
subCommands: { list: workflowsListCommand, view: workflowsViewCommand }
|
|
30244
30423
|
});
|
|
30245
30424
|
var pipelinesCommand = defineCommand120({
|
|
30246
30425
|
meta: { name: "pipelines", description: "HubSpot deal pipelines on the connected account." },
|
|
@@ -30263,6 +30442,7 @@ Examples:
|
|
|
30263
30442
|
baker hubspot forms view <formId> --as-node # the form.external blob -> write into the step
|
|
30264
30443
|
baker hubspot forms submissions <formId> # how many leads it got, and when
|
|
30265
30444
|
baker hubspot workflows list --enabled-only # what runs after a lead is captured
|
|
30445
|
+
baker hubspot workflows view <workflowId> # its real branches, in order, and what each step writes
|
|
30266
30446
|
baker hubspot pipelines list # where a lead lands, and the stages after it
|
|
30267
30447
|
baker hubspot contacts summary --days 30 # are recent leads being worked? (counts only)
|
|
30268
30448
|
baker hubspot contacts lookup a@b.com # was this one lead picked up?
|
|
@@ -30340,9 +30520,9 @@ async function readImageBuffer(pathOrUrl) {
|
|
|
30340
30520
|
}
|
|
30341
30521
|
return readFile19(pathOrUrl);
|
|
30342
30522
|
}
|
|
30343
|
-
async function isDirectory(
|
|
30523
|
+
async function isDirectory(path38) {
|
|
30344
30524
|
try {
|
|
30345
|
-
const s = await stat4(
|
|
30525
|
+
const s = await stat4(path38);
|
|
30346
30526
|
return s.isDirectory();
|
|
30347
30527
|
} catch {
|
|
30348
30528
|
return false;
|
|
@@ -30626,13 +30806,13 @@ function resolveDownloadPath({ baseName, extension, out, outIsDirectory: outIsDi
|
|
|
30626
30806
|
}
|
|
30627
30807
|
function disambiguate(paths) {
|
|
30628
30808
|
const taken = /* @__PURE__ */ new Set();
|
|
30629
|
-
return paths.map((
|
|
30630
|
-
if (!taken.has(
|
|
30631
|
-
taken.add(
|
|
30632
|
-
return
|
|
30809
|
+
return paths.map((path38) => {
|
|
30810
|
+
if (!taken.has(path38)) {
|
|
30811
|
+
taken.add(path38);
|
|
30812
|
+
return path38;
|
|
30633
30813
|
}
|
|
30634
|
-
const ext = extname3(
|
|
30635
|
-
const stem =
|
|
30814
|
+
const ext = extname3(path38);
|
|
30815
|
+
const stem = path38.slice(0, path38.length - ext.length);
|
|
30636
30816
|
let n = 2;
|
|
30637
30817
|
while (taken.has(`${stem}-${n}${ext}`)) n += 1;
|
|
30638
30818
|
const unique = `${stem}-${n}${ext}`;
|
|
@@ -30759,10 +30939,10 @@ async function runDownloads(plan) {
|
|
|
30759
30939
|
const paths = disambiguate(fetched.map((item) => item.path));
|
|
30760
30940
|
const downloaded = [];
|
|
30761
30941
|
for (const [index, item] of fetched.entries()) {
|
|
30762
|
-
const
|
|
30942
|
+
const path38 = paths[index] ?? item.path;
|
|
30763
30943
|
try {
|
|
30764
|
-
await atomicWrite(
|
|
30765
|
-
downloaded.push({ input: item.input, output:
|
|
30944
|
+
await atomicWrite(path38, item.buffer);
|
|
30945
|
+
downloaded.push({ input: item.input, output: path38, bytes: item.buffer.length, contentType: item.contentType });
|
|
30766
30946
|
} catch (err) {
|
|
30767
30947
|
failed.push({ input: item.input, error: failureMessage(err, "Write failed") });
|
|
30768
30948
|
}
|
|
@@ -32513,14 +32693,14 @@ async function processInternal(inputBuffer, isSVG, options) {
|
|
|
32513
32693
|
processedData = result.data;
|
|
32514
32694
|
info = result.info;
|
|
32515
32695
|
}
|
|
32516
|
-
let
|
|
32696
|
+
let pipeline2 = sharp2(processedData, {
|
|
32517
32697
|
raw: { channels: 4, width: info.width, height: info.height }
|
|
32518
32698
|
}).png();
|
|
32519
32699
|
if (options.shrinkToContent) {
|
|
32520
32700
|
stages.push("trim");
|
|
32521
|
-
|
|
32701
|
+
pipeline2 = pipeline2.trim();
|
|
32522
32702
|
}
|
|
32523
|
-
const outputBuffer = await
|
|
32703
|
+
const outputBuffer = await pipeline2.toBuffer();
|
|
32524
32704
|
return { buffer: outputBuffer, stages };
|
|
32525
32705
|
}
|
|
32526
32706
|
function applyResize(buffer, resize) {
|
|
@@ -40493,7 +40673,7 @@ function parseImageRefs(spec) {
|
|
|
40493
40673
|
}
|
|
40494
40674
|
var defaultDeps = {
|
|
40495
40675
|
ingest: (url) => apiPost("/api/images/ingest", { url, source: "uploaded" }),
|
|
40496
|
-
upload: (
|
|
40676
|
+
upload: (path38) => uploadLocalImage({ file: path38, contentType: detectImageContentType(path38), source: "uploaded" })
|
|
40497
40677
|
};
|
|
40498
40678
|
async function resolveLibraryImageIds(spec, limit, deps = defaultDeps) {
|
|
40499
40679
|
const refs = parseImageRefs(spec);
|
|
@@ -40513,10 +40693,10 @@ async function resolveLibraryImageIds(spec, limit, deps = defaultDeps) {
|
|
|
40513
40693
|
}
|
|
40514
40694
|
return { imageIds, added };
|
|
40515
40695
|
}
|
|
40516
|
-
function uploadFailure(
|
|
40696
|
+
function uploadFailure(path38) {
|
|
40517
40697
|
return (error) => {
|
|
40518
40698
|
if (error instanceof ApiError) throw error;
|
|
40519
|
-
throw new ApiError("VALIDATION_ERROR", `Could not read "${
|
|
40699
|
+
throw new ApiError("VALIDATION_ERROR", `Could not read "${path38}" as an image.`);
|
|
40520
40700
|
};
|
|
40521
40701
|
}
|
|
40522
40702
|
|
|
@@ -41566,10 +41746,10 @@ async function stageOp4(op) {
|
|
|
41566
41746
|
handleError4(err);
|
|
41567
41747
|
}
|
|
41568
41748
|
}
|
|
41569
|
-
async function draftAction3(
|
|
41749
|
+
async function draftAction3(path38, body, chat) {
|
|
41570
41750
|
const chatId = resolveChatId(chat);
|
|
41571
41751
|
try {
|
|
41572
|
-
const data = await apiPost(
|
|
41752
|
+
const data = await apiPost(path38, { chatId, ...body });
|
|
41573
41753
|
writeJsonEnvelope({ ok: true, data });
|
|
41574
41754
|
return data;
|
|
41575
41755
|
} catch (err) {
|
|
@@ -42542,7 +42722,7 @@ Full guide: __tooling__/docs/tools/baker/testimonials.md`
|
|
|
42542
42722
|
});
|
|
42543
42723
|
|
|
42544
42724
|
// src/commands/videos/index.ts
|
|
42545
|
-
import { defineCommand as
|
|
42725
|
+
import { defineCommand as defineCommand203 } from "citty";
|
|
42546
42726
|
|
|
42547
42727
|
// src/commands/videos/delete.ts
|
|
42548
42728
|
import { defineCommand as defineCommand197 } from "citty";
|
|
@@ -42673,8 +42853,420 @@ var groupCommand2 = defineCommand199({
|
|
|
42673
42853
|
}
|
|
42674
42854
|
});
|
|
42675
42855
|
|
|
42676
|
-
// src/commands/videos/
|
|
42856
|
+
// src/commands/videos/ingest.ts
|
|
42857
|
+
import { mkdtemp as mkdtemp2, rm as rm7, stat as stat7 } from "fs/promises";
|
|
42858
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
42859
|
+
import path37 from "path";
|
|
42677
42860
|
import { defineCommand as defineCommand200 } from "citty";
|
|
42861
|
+
|
|
42862
|
+
// src/lib/streamUpload.ts
|
|
42863
|
+
import { createHash as createHash2 } from "crypto";
|
|
42864
|
+
import { createReadStream } from "fs";
|
|
42865
|
+
import { Readable } from "stream";
|
|
42866
|
+
import { pipeline } from "stream/promises";
|
|
42867
|
+
async function hashFile(filePath) {
|
|
42868
|
+
const hash = createHash2("sha256");
|
|
42869
|
+
await pipeline(createReadStream(filePath), hash);
|
|
42870
|
+
return hash.digest("hex");
|
|
42871
|
+
}
|
|
42872
|
+
async function putFileStreamed(args) {
|
|
42873
|
+
const body = Readable.toWeb(createReadStream(args.filePath));
|
|
42874
|
+
return await fetch(args.putUrl, {
|
|
42875
|
+
method: "PUT",
|
|
42876
|
+
body,
|
|
42877
|
+
headers: {
|
|
42878
|
+
"Content-Type": args.contentType,
|
|
42879
|
+
"Content-Length": String(args.sizeBytes)
|
|
42880
|
+
},
|
|
42881
|
+
duplex: "half"
|
|
42882
|
+
});
|
|
42883
|
+
}
|
|
42884
|
+
|
|
42885
|
+
// src/commands/videos/videoPlatforms.ts
|
|
42886
|
+
var PLATFORM_DOMAINS = {
|
|
42887
|
+
youtube: ["youtube.com", "youtu.be"],
|
|
42888
|
+
instagram: ["instagram.com", "ddinstagram.com"],
|
|
42889
|
+
tiktok: ["tiktok.com"],
|
|
42890
|
+
twitter: ["twitter.com", "x.com", "vxtwitter.com", "fixvx.com"],
|
|
42891
|
+
facebook: ["facebook.com", "fb.watch", "fb.com"],
|
|
42892
|
+
reddit: ["reddit.com", "redd.it"],
|
|
42893
|
+
vimeo: ["vimeo.com"],
|
|
42894
|
+
dailymotion: ["dailymotion.com", "dai.ly"],
|
|
42895
|
+
twitch: ["twitch.tv"],
|
|
42896
|
+
bluesky: ["bsky.app"],
|
|
42897
|
+
bilibili: ["bilibili.com", "bilibili.tv", "b23.tv"],
|
|
42898
|
+
vk: ["vk.com", "vk.ru", "vkvideo.ru"],
|
|
42899
|
+
ok: ["ok.ru"],
|
|
42900
|
+
pinterest: ["pinterest.com", "pin.it"],
|
|
42901
|
+
tumblr: ["tumblr.com"],
|
|
42902
|
+
snapchat: ["snapchat.com"],
|
|
42903
|
+
loom: ["loom.com"],
|
|
42904
|
+
streamable: ["streamable.com"],
|
|
42905
|
+
newgrounds: ["newgrounds.com"],
|
|
42906
|
+
rutube: ["rutube.ru"]
|
|
42907
|
+
};
|
|
42908
|
+
var VIDEO_PLATFORMS = Object.keys(PLATFORM_DOMAINS);
|
|
42909
|
+
function platformFromUrl(rawUrl) {
|
|
42910
|
+
let host;
|
|
42911
|
+
try {
|
|
42912
|
+
host = new URL(rawUrl).hostname.toLowerCase();
|
|
42913
|
+
} catch {
|
|
42914
|
+
return "url";
|
|
42915
|
+
}
|
|
42916
|
+
for (const [platform, domains] of Object.entries(PLATFORM_DOMAINS)) {
|
|
42917
|
+
if (domains.some((domain) => host === domain || host.endsWith(`.${domain}`))) {
|
|
42918
|
+
return platform;
|
|
42919
|
+
}
|
|
42920
|
+
}
|
|
42921
|
+
return "url";
|
|
42922
|
+
}
|
|
42923
|
+
|
|
42924
|
+
// src/commands/videos/ingestUrl.ts
|
|
42925
|
+
var MAX_VIDEO_INGEST_BYTES = 16 * 1024 * 1024 * 1024;
|
|
42926
|
+
var MAX_VIDEO_INGEST_SECONDS = 3 * 60 * 60;
|
|
42927
|
+
function refuseByDuration(seconds, maxSeconds) {
|
|
42928
|
+
if (seconds === void 0 || seconds <= maxSeconds) return null;
|
|
42929
|
+
return `That video is ${(seconds / 3600).toFixed(1)} hours long \u2014 the limit is ${Math.round(maxSeconds / 3600)} hours.`;
|
|
42930
|
+
}
|
|
42931
|
+
var DOWNLOAD_TIMEOUT_FLOOR_MS = 10 * 60 * 1e3;
|
|
42932
|
+
var DOWNLOAD_TIMEOUT_CEILING_MS = 15 * 60 * 1e3;
|
|
42933
|
+
function downloadTimeoutMs(seconds) {
|
|
42934
|
+
if (seconds === void 0) return DOWNLOAD_TIMEOUT_FLOOR_MS;
|
|
42935
|
+
const scaled = seconds * 2 * 1e3;
|
|
42936
|
+
return Math.min(Math.max(scaled, DOWNLOAD_TIMEOUT_FLOOR_MS), DOWNLOAD_TIMEOUT_CEILING_MS);
|
|
42937
|
+
}
|
|
42938
|
+
var INGEST_MAX_HEIGHT = 1080;
|
|
42939
|
+
var DIRECT_MEDIA_EXTENSIONS = [".mp4", ".mov", ".webm", ".m4v", ".mkv", ".avi"];
|
|
42940
|
+
function isDirectMediaUrl(rawUrl) {
|
|
42941
|
+
const pathname = safePathname(rawUrl);
|
|
42942
|
+
return DIRECT_MEDIA_EXTENSIONS.some((ext) => pathname.endsWith(ext));
|
|
42943
|
+
}
|
|
42944
|
+
function resolveIngestSource(explicit, rawUrl) {
|
|
42945
|
+
if (explicit) return explicit;
|
|
42946
|
+
return platformFromUrl(rawUrl);
|
|
42947
|
+
}
|
|
42948
|
+
var TOOLING_OR_BLOCK_SIGNATURES = [
|
|
42949
|
+
"requested format is not available",
|
|
42950
|
+
"only images are available",
|
|
42951
|
+
"nsig",
|
|
42952
|
+
"sign in to confirm",
|
|
42953
|
+
// bot check — distinct from "Private video. Sign in if…"
|
|
42954
|
+
"unable to download api page",
|
|
42955
|
+
"http error 403",
|
|
42956
|
+
// yt-dlp's own "the site changed under me" signature. It prints a GitHub issue
|
|
42957
|
+
// link whenever a page parsed nothing like the shape the extractor expects —
|
|
42958
|
+
// which is precisely a downloader that has aged out, not a bad link. Observed
|
|
42959
|
+
// live on TikTok, which probed fine and then broke ninety minutes later from
|
|
42960
|
+
// the same machine, identically through direct, datacenter and residential
|
|
42961
|
+
// exits. Without this it fell through to "check the link is public", sending
|
|
42962
|
+
// the reader to inspect a video that was never the problem.
|
|
42963
|
+
"please report this issue"
|
|
42964
|
+
];
|
|
42965
|
+
function isToolingOrBlockFailure(detail) {
|
|
42966
|
+
const haystack = detail.toLowerCase();
|
|
42967
|
+
return TOOLING_OR_BLOCK_SIGNATURES.some((signature) => haystack.includes(signature));
|
|
42968
|
+
}
|
|
42969
|
+
var REGION_BLOCK_SIGNATURES = [
|
|
42970
|
+
"in your country",
|
|
42971
|
+
"geo-restricted",
|
|
42972
|
+
"geo restricted",
|
|
42973
|
+
"not available from your location",
|
|
42974
|
+
"blocked in your country"
|
|
42975
|
+
];
|
|
42976
|
+
function isDrmProtected(detail) {
|
|
42977
|
+
return detail.toLowerCase().includes("drm protected");
|
|
42978
|
+
}
|
|
42979
|
+
function isRegionBlocked(detail) {
|
|
42980
|
+
const haystack = detail.toLowerCase();
|
|
42981
|
+
return REGION_BLOCK_SIGNATURES.some((signature) => haystack.includes(signature));
|
|
42982
|
+
}
|
|
42983
|
+
function worthDownloadingInstead(errorCode) {
|
|
42984
|
+
return errorCode === "VALIDATION_ERROR";
|
|
42985
|
+
}
|
|
42986
|
+
function directDedupeKey(source, rawUrl) {
|
|
42987
|
+
return `${source}:${rawUrl}`;
|
|
42988
|
+
}
|
|
42989
|
+
function safePathname(rawUrl) {
|
|
42990
|
+
try {
|
|
42991
|
+
return new URL(rawUrl).pathname.toLowerCase();
|
|
42992
|
+
} catch {
|
|
42993
|
+
return "";
|
|
42994
|
+
}
|
|
42995
|
+
}
|
|
42996
|
+
|
|
42997
|
+
// src/commands/videos/ingest.ts
|
|
42998
|
+
registerSchema({
|
|
42999
|
+
command: "videos.ingest",
|
|
43000
|
+
description: "Add a video to the library from a URL \u2014 a direct file, or a YouTube/TikTok/Vimeo page.",
|
|
43001
|
+
args: {
|
|
43002
|
+
url: { type: "string", description: "Video URL (direct file or a watch/post page)", required: true },
|
|
43003
|
+
source: { type: "string", description: "Provenance: url | youtube", required: false },
|
|
43004
|
+
"external-id": { type: "string", description: "Provider asset id (deduped on)", required: false },
|
|
43005
|
+
"external-url": { type: "string", description: "Canonical page URL", required: false },
|
|
43006
|
+
download: {
|
|
43007
|
+
type: "boolean",
|
|
43008
|
+
description: "Force the download path instead of letting Baker fetch the URL directly",
|
|
43009
|
+
required: false,
|
|
43010
|
+
default: false
|
|
43011
|
+
},
|
|
43012
|
+
country: {
|
|
43013
|
+
type: "string",
|
|
43014
|
+
description: "Two-letter country to fetch from (e.g. US, DE, JP) \u2014 use when a video is blocked in your region",
|
|
43015
|
+
required: false
|
|
43016
|
+
},
|
|
43017
|
+
context: {
|
|
43018
|
+
type: "string",
|
|
43019
|
+
description: "What you know that the video cannot show \u2014 the campaign, whether the person on camera is a real customer, what it is for. Highest-leverage flag here: it is what makes the clip findable later.",
|
|
43020
|
+
required: false
|
|
43021
|
+
},
|
|
43022
|
+
"dry-run": { type: "boolean", description: "Preview the operation without executing", required: false }
|
|
43023
|
+
}
|
|
43024
|
+
});
|
|
43025
|
+
var ingestCommand2 = defineCommand200({
|
|
43026
|
+
meta: {
|
|
43027
|
+
name: "ingest",
|
|
43028
|
+
description: "Add a video to the library from a URL. A direct file URL is handed straight to Baker, which fetches it. A page URL (YouTube, TikTok, Vimeo, Instagram) is downloaded here first, then uploaded \u2014 and a direct URL that Baker cannot fetch falls back to that same path automatically.\n\nExample: baker videos ingest https://www.youtube.com/watch?v=abc123"
|
|
43029
|
+
},
|
|
43030
|
+
args: {
|
|
43031
|
+
url: { type: "positional", description: "Video URL", required: false },
|
|
43032
|
+
source: { type: "string", description: "Provenance: url | youtube", required: false },
|
|
43033
|
+
"external-id": { type: "string", description: "Provider asset id", required: false },
|
|
43034
|
+
"external-url": { type: "string", description: "Canonical page URL", required: false },
|
|
43035
|
+
download: { type: "boolean", description: "Force the download path", required: false, default: false },
|
|
43036
|
+
country: { type: "string", description: "Two-letter country to fetch from, e.g. US", required: false },
|
|
43037
|
+
context: {
|
|
43038
|
+
type: "string",
|
|
43039
|
+
description: "Context for the analysis \u2014 campaign, who is on camera, intended use",
|
|
43040
|
+
required: false
|
|
43041
|
+
},
|
|
43042
|
+
"dry-run": { type: "boolean", description: "Preview without executing", required: false, default: false }
|
|
43043
|
+
},
|
|
43044
|
+
run: ({ args }) => runVideoIngest({
|
|
43045
|
+
url: args.url,
|
|
43046
|
+
source: args.source,
|
|
43047
|
+
externalId: args["external-id"],
|
|
43048
|
+
externalUrl: args["external-url"],
|
|
43049
|
+
forceDownload: args.download === true,
|
|
43050
|
+
country: args.country,
|
|
43051
|
+
context: args.context,
|
|
43052
|
+
dryRun: args["dry-run"] === true
|
|
43053
|
+
})
|
|
43054
|
+
});
|
|
43055
|
+
async function runVideoIngest(opts) {
|
|
43056
|
+
const url = opts.url;
|
|
43057
|
+
if (!url) {
|
|
43058
|
+
writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "URL is required" } });
|
|
43059
|
+
process.exit(1);
|
|
43060
|
+
}
|
|
43061
|
+
const direct = !opts.forceDownload && isDirectMediaUrl(url);
|
|
43062
|
+
const country = opts.country?.trim() || void 0;
|
|
43063
|
+
const args = {
|
|
43064
|
+
url,
|
|
43065
|
+
source: resolveIngestSource(opts.source, url),
|
|
43066
|
+
externalId: opts.externalId,
|
|
43067
|
+
// Deliberately NOT pre-filled with the pasted URL. Doing so shadowed the
|
|
43068
|
+
// canonical URL the probe resolves (`?? canonicalUrl(...)` below could never
|
|
43069
|
+
// run), so the same video filed under a different `externalUrl` depending on
|
|
43070
|
+
// whether the link was copied from a share sheet, a shorts page or the
|
|
43071
|
+
// address bar. `downloadThenIngest` falls back to `args.url` when the probe
|
|
43072
|
+
// resolves nothing, which is the value this used to hard-code.
|
|
43073
|
+
externalUrl: opts.externalUrl,
|
|
43074
|
+
descriptionContext: opts.context?.trim() || void 0
|
|
43075
|
+
};
|
|
43076
|
+
const validation = videosIngestRequestSchema.safeParse({ ...args, url });
|
|
43077
|
+
if (!validation.success) {
|
|
43078
|
+
const problem = validation.error.issues[0];
|
|
43079
|
+
writeJson({
|
|
43080
|
+
ok: false,
|
|
43081
|
+
error: {
|
|
43082
|
+
code: "VALIDATION_ERROR",
|
|
43083
|
+
message: problem ? `${problem.path.join(".") || "request"}: ${problem.message}` : "Invalid request"
|
|
43084
|
+
}
|
|
43085
|
+
});
|
|
43086
|
+
process.exit(1);
|
|
43087
|
+
}
|
|
43088
|
+
if (opts.dryRun) {
|
|
43089
|
+
reportDryRun(args, direct, country);
|
|
43090
|
+
return;
|
|
43091
|
+
}
|
|
43092
|
+
try {
|
|
43093
|
+
writeJson({ ok: true, data: await ingestByRoute(args, direct, country) });
|
|
43094
|
+
} catch (err) {
|
|
43095
|
+
reportIngestFailure(err);
|
|
43096
|
+
}
|
|
43097
|
+
}
|
|
43098
|
+
function reportDryRun(args, direct, country) {
|
|
43099
|
+
writeJson({
|
|
43100
|
+
ok: true,
|
|
43101
|
+
dryRun: true,
|
|
43102
|
+
operation: "videos.ingest",
|
|
43103
|
+
params: {
|
|
43104
|
+
url: args.url,
|
|
43105
|
+
source: args.source,
|
|
43106
|
+
// Known up front on the direct route, because it is derived from the URL
|
|
43107
|
+
// alone. The download route's key comes from the probe, which a dry run
|
|
43108
|
+
// does not perform, so there it stays null rather than becoming a guess.
|
|
43109
|
+
externalId: args.externalId ?? (direct ? directDedupeKey(args.source, args.url) : null),
|
|
43110
|
+
// A page URL's canonical form is only known after the probe, which a dry
|
|
43111
|
+
// run does not perform — so this is null rather than a guess.
|
|
43112
|
+
externalUrl: args.externalUrl ?? null,
|
|
43113
|
+
direct,
|
|
43114
|
+
country: country ?? null,
|
|
43115
|
+
descriptionContext: args.descriptionContext ?? null
|
|
43116
|
+
}
|
|
43117
|
+
});
|
|
43118
|
+
}
|
|
43119
|
+
async function ingestByRoute(args, direct, country) {
|
|
43120
|
+
if (!direct) {
|
|
43121
|
+
return { ...await downloadThenIngest(args, country), via: "download" };
|
|
43122
|
+
}
|
|
43123
|
+
try {
|
|
43124
|
+
const externalId = args.externalId ?? directDedupeKey(args.source, args.url);
|
|
43125
|
+
return { ...await ingestUrl({ ...args, externalId }), via: "direct" };
|
|
43126
|
+
} catch (err) {
|
|
43127
|
+
if (!(err instanceof ApiError) || !worthDownloadingInstead(err.code)) throw err;
|
|
43128
|
+
return { ...await downloadThenIngest(args, country), via: "download", fallbackFrom: err.message };
|
|
43129
|
+
}
|
|
43130
|
+
}
|
|
43131
|
+
function reportYtDlpFailure(err) {
|
|
43132
|
+
const detail = `${err.stderrTail} ${err.message}`;
|
|
43133
|
+
if (isProxyFailure(ytDlpBlockSignal(detail))) {
|
|
43134
|
+
fail7(
|
|
43135
|
+
"Couldn't download that video: our connection to the internet was refused.",
|
|
43136
|
+
"This is Baker's egress proxy, not the link \u2014 its credentials look wrong or expired. The link is probably fine. Report it; retrying won't help until the proxy is fixed."
|
|
43137
|
+
);
|
|
43138
|
+
}
|
|
43139
|
+
if (isDrmProtected(detail)) {
|
|
43140
|
+
fail7(
|
|
43141
|
+
"That video is copy-protected, so it can't be added to the library.",
|
|
43142
|
+
"The publisher encrypted this one against downloading. Nothing will change that \u2014 not retrying, not a different region. Ask whoever owns the video for the original file and add it with `baker videos upload <file>`."
|
|
43143
|
+
);
|
|
43144
|
+
}
|
|
43145
|
+
if (isVimeoAuthFailure(detail)) {
|
|
43146
|
+
fail7(
|
|
43147
|
+
"Vimeo wouldn't hand over that video \u2014 Baker isn't signed in to Vimeo.",
|
|
43148
|
+
"Vimeo now refuses anonymous downloads entirely, so this needs a signed-in session and the one Baker holds has either expired or was never set. Nothing is wrong with the link. Report it so the Vimeo cookie can be refreshed; meanwhile download the file and use `baker videos upload <file>`."
|
|
43149
|
+
);
|
|
43150
|
+
}
|
|
43151
|
+
if (isRegionBlocked(detail)) {
|
|
43152
|
+
fail7(
|
|
43153
|
+
`That video isn't available in the region we're fetching from. ${err.stderrTail || err.message}`,
|
|
43154
|
+
"Re-run with `--country <two-letter code>` for a country where the video plays \u2014 e.g. `--country US`. Pick the one the video belongs to (the uploader's country is the usual answer); each attempt costs a paid connection, so choose rather than work through the list."
|
|
43155
|
+
);
|
|
43156
|
+
}
|
|
43157
|
+
fail7(
|
|
43158
|
+
`Couldn't download that video. ${err.stderrTail || err.message}`,
|
|
43159
|
+
isToolingOrBlockFailure(detail) ? "The site wouldn't hand over the video \u2014 usually the downloader being out of date against a site that changed, or the request being blocked. Retrying won't help, and the link is probably fine. Report it so the tool can be updated; meanwhile download the file and use `baker videos upload <file>`." : "Check the link is public and playable. Private or age-restricted videos can't be downloaded \u2014 download the file yourself and use `baker videos upload <file>`."
|
|
43160
|
+
);
|
|
43161
|
+
}
|
|
43162
|
+
function fail7(message, fix, code = "INGEST_FAILED") {
|
|
43163
|
+
writeJson({ ok: false, error: { code, message, fix } });
|
|
43164
|
+
process.exit(1);
|
|
43165
|
+
}
|
|
43166
|
+
function reportIngestFailure(err) {
|
|
43167
|
+
if (err instanceof ApiError) {
|
|
43168
|
+
writeJson({ ok: false, error: { code: err.code, message: err.message } });
|
|
43169
|
+
process.exit(1);
|
|
43170
|
+
}
|
|
43171
|
+
if (err instanceof YtDlpError) reportYtDlpFailure(err);
|
|
43172
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
43173
|
+
if (detail.includes("timed out after")) {
|
|
43174
|
+
fail7(
|
|
43175
|
+
"That video took too long to download and was stopped part-way.",
|
|
43176
|
+
"Long or slow-serving videos can outrun the download window. Try a shorter clip, or download this one yourself and use `baker videos upload <file>`.",
|
|
43177
|
+
"INTERNAL_ERROR"
|
|
43178
|
+
);
|
|
43179
|
+
}
|
|
43180
|
+
fail7(
|
|
43181
|
+
`Couldn't add that video. ${detail}`,
|
|
43182
|
+
"This is a fault on Baker's side, not a problem with the link. Report it.",
|
|
43183
|
+
"INTERNAL_ERROR"
|
|
43184
|
+
);
|
|
43185
|
+
}
|
|
43186
|
+
function dedupeKey(args, info) {
|
|
43187
|
+
const id = typeof info.id === "string" ? info.id : void 0;
|
|
43188
|
+
if (!id) return void 0;
|
|
43189
|
+
if (info.extractor === "generic") return `${args.source}:${canonicalUrl(info) ?? args.url}`;
|
|
43190
|
+
return `${args.source}:${id}`;
|
|
43191
|
+
}
|
|
43192
|
+
function ingestUrl(args) {
|
|
43193
|
+
const body = {
|
|
43194
|
+
url: args.url,
|
|
43195
|
+
source: args.source
|
|
43196
|
+
};
|
|
43197
|
+
if (args.externalId) body.externalId = args.externalId;
|
|
43198
|
+
if (args.externalUrl) body.externalUrl = args.externalUrl;
|
|
43199
|
+
if (args.descriptionContext) body.descriptionContext = args.descriptionContext;
|
|
43200
|
+
return apiPost("/api/videos/ingest", body);
|
|
43201
|
+
}
|
|
43202
|
+
async function downloadThenIngest(args, country) {
|
|
43203
|
+
const vimeoCookie = captureVimeoCookie();
|
|
43204
|
+
const workDir = await mkdtemp2(path37.join(tmpdir3(), "videos-ingest-"));
|
|
43205
|
+
try {
|
|
43206
|
+
const probe = await probeYtDlp({ url: args.url, country, vimeoCookie, cookieDir: workDir });
|
|
43207
|
+
if (isAudioOnly(probe.info)) {
|
|
43208
|
+
throw new ApiError(
|
|
43209
|
+
"VALIDATION_ERROR",
|
|
43210
|
+
"That link is audio, not video, so it can't go in the video library. Music and podcast links have no picture to store."
|
|
43211
|
+
);
|
|
43212
|
+
}
|
|
43213
|
+
const tooLong = refuseByDuration(durationSeconds(probe.info), MAX_VIDEO_INGEST_SECONDS);
|
|
43214
|
+
if (tooLong) {
|
|
43215
|
+
throw new ApiError("VALIDATION_ERROR", tooLong);
|
|
43216
|
+
}
|
|
43217
|
+
const externalId = args.externalId ?? dedupeKey(args, probe.info);
|
|
43218
|
+
const { filePath, tier } = await runYtDlp({
|
|
43219
|
+
url: args.url,
|
|
43220
|
+
audioOnly: false,
|
|
43221
|
+
workDir,
|
|
43222
|
+
maxHeight: INGEST_MAX_HEIGHT,
|
|
43223
|
+
country,
|
|
43224
|
+
vimeoCookie,
|
|
43225
|
+
// Budgeted from the probed length, so the duration we accept and the time
|
|
43226
|
+
// we allow to fetch it cannot drift apart.
|
|
43227
|
+
timeoutMs: downloadTimeoutMs(durationSeconds(probe.info))
|
|
43228
|
+
});
|
|
43229
|
+
const stats = await stat7(filePath);
|
|
43230
|
+
if (stats.size > MAX_VIDEO_INGEST_BYTES) {
|
|
43231
|
+
throw new ApiError(
|
|
43232
|
+
"VALIDATION_ERROR",
|
|
43233
|
+
`That video is ${(stats.size / 1024 ** 3).toFixed(1)} GB, over the ${Math.round(MAX_VIDEO_INGEST_BYTES / 1024 ** 3)} GB limit for this route.`
|
|
43234
|
+
);
|
|
43235
|
+
}
|
|
43236
|
+
const publicUrl = await uploadToAssetStore(filePath, stats.size);
|
|
43237
|
+
return {
|
|
43238
|
+
...await ingestUrl({
|
|
43239
|
+
...args,
|
|
43240
|
+
url: publicUrl,
|
|
43241
|
+
externalId,
|
|
43242
|
+
externalUrl: args.externalUrl ?? canonicalUrl(probe.info) ?? args.url
|
|
43243
|
+
}),
|
|
43244
|
+
egress: tier,
|
|
43245
|
+
// Named in the result for the same reason `egress` is: a geo-targeted exit
|
|
43246
|
+
// is a deliberate, billable choice and must not be invisible afterwards.
|
|
43247
|
+
...country ? { country } : {}
|
|
43248
|
+
};
|
|
43249
|
+
} finally {
|
|
43250
|
+
await rm7(workDir, { recursive: true, force: true }).catch(() => {
|
|
43251
|
+
});
|
|
43252
|
+
}
|
|
43253
|
+
}
|
|
43254
|
+
async function uploadToAssetStore(filePath, sizeBytes) {
|
|
43255
|
+
const sha256 = await hashFile(filePath);
|
|
43256
|
+
const { putUrl, publicUrl } = await apiPost("/api/canvas/assets/presign", {
|
|
43257
|
+
sha256,
|
|
43258
|
+
mime: "video/mp4",
|
|
43259
|
+
purpose: "source"
|
|
43260
|
+
});
|
|
43261
|
+
const putRes = await putFileStreamed({ putUrl, filePath, contentType: "video/mp4", sizeBytes });
|
|
43262
|
+
if (!putRes.ok) {
|
|
43263
|
+
throw new ApiError("INTERNAL_ERROR", `Storing the downloaded video failed: HTTP ${putRes.status}`);
|
|
43264
|
+
}
|
|
43265
|
+
return publicUrl;
|
|
43266
|
+
}
|
|
43267
|
+
|
|
43268
|
+
// src/commands/videos/search.ts
|
|
43269
|
+
import { defineCommand as defineCommand201 } from "citty";
|
|
42678
43270
|
registerSchema({
|
|
42679
43271
|
command: "videos.search",
|
|
42680
43272
|
description: "Search videos by text query. Only returns ready videos.",
|
|
@@ -42684,7 +43276,7 @@ registerSchema({
|
|
|
42684
43276
|
tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
|
|
42685
43277
|
}
|
|
42686
43278
|
});
|
|
42687
|
-
var searchCommand4 =
|
|
43279
|
+
var searchCommand4 = defineCommand201({
|
|
42688
43280
|
meta: {
|
|
42689
43281
|
name: "search",
|
|
42690
43282
|
description: "Semantic search videos by text query. Uses hybrid BM25 + vector + reranking. Example: baker videos search 'product demo' --tags tutorial"
|
|
@@ -42734,9 +43326,9 @@ var searchCommand4 = defineCommand200({
|
|
|
42734
43326
|
var tagsCommand5 = makeTagsCommand("videos", "video", "/api/videos/tags");
|
|
42735
43327
|
|
|
42736
43328
|
// src/commands/videos/upload.ts
|
|
42737
|
-
import { readFile as readFile24, stat as
|
|
43329
|
+
import { readFile as readFile24, stat as stat8 } from "fs/promises";
|
|
42738
43330
|
import { basename as basename3, extname as extname4 } from "path";
|
|
42739
|
-
import { defineCommand as
|
|
43331
|
+
import { defineCommand as defineCommand202 } from "citty";
|
|
42740
43332
|
var MIME_MAP = {
|
|
42741
43333
|
".mp4": "video/mp4",
|
|
42742
43334
|
".mov": "video/quicktime",
|
|
@@ -42746,9 +43338,9 @@ var MIME_MAP = {
|
|
|
42746
43338
|
};
|
|
42747
43339
|
registerSchema({
|
|
42748
43340
|
command: "videos.upload",
|
|
42749
|
-
description: "Upload a video
|
|
43341
|
+
description: "Upload a video to Baker \u2014 a local file (via Mux direct upload) or a remote http(s) URL.",
|
|
42750
43342
|
args: {
|
|
42751
|
-
file: { type: "string", description: "
|
|
43343
|
+
file: { type: "string", description: "Local video file path, or a remote http(s) URL", required: true },
|
|
42752
43344
|
"content-type": {
|
|
42753
43345
|
type: "string",
|
|
42754
43346
|
description: "MIME type (auto-detected from extension if omitted)",
|
|
@@ -42775,33 +43367,50 @@ function detectContentType(filePath) {
|
|
|
42775
43367
|
}
|
|
42776
43368
|
return mime;
|
|
42777
43369
|
}
|
|
42778
|
-
|
|
43370
|
+
function isRemoteUrl3(value) {
|
|
43371
|
+
return /^https?:\/\//i.test(value);
|
|
43372
|
+
}
|
|
43373
|
+
var uploadCommand2 = defineCommand202({
|
|
42779
43374
|
meta: {
|
|
42780
43375
|
name: "upload",
|
|
42781
|
-
description: "Upload a video
|
|
43376
|
+
description: "Upload a video to Baker \u2014 accepts a local file path OR a remote http(s) URL.\n\nLocal: auto-detects content type and uploads via Mux direct upload.\nRemote: hands off to `videos ingest` (direct fetch, or download-then-upload for a YouTube/TikTok/Vimeo page).\n\nExamples:\n baker videos upload ./demo.mp4\n baker videos upload https://www.youtube.com/watch?v=abc123"
|
|
42782
43377
|
},
|
|
42783
43378
|
args: {
|
|
42784
|
-
file: { type: "positional", description: "
|
|
42785
|
-
"content-type": { type: "string", description: "MIME type (auto-detected
|
|
43379
|
+
file: { type: "positional", description: "Local video file path or remote http(s) URL", required: false },
|
|
43380
|
+
"content-type": { type: "string", description: "MIME type (local only \u2014 auto-detected)", required: false },
|
|
42786
43381
|
context: {
|
|
42787
43382
|
type: "string",
|
|
42788
43383
|
description: "What this clip is and what it is for \u2014 who is on camera, which campaign, how it was shot. Start here: the analysis reads it, so it is what makes the clip findable later.",
|
|
42789
43384
|
required: false
|
|
42790
43385
|
},
|
|
43386
|
+
source: { type: "string", description: "Provenance (URL mode only): url | youtube", required: false },
|
|
43387
|
+
"external-id": { type: "string", description: "Provider asset id (URL mode only)", required: false },
|
|
43388
|
+
"external-url": { type: "string", description: "Canonical page URL (URL mode only)", required: false },
|
|
42791
43389
|
"dry-run": { type: "boolean", description: "Preview without executing", required: false, default: false }
|
|
42792
43390
|
},
|
|
42793
43391
|
run: async ({ args }) => {
|
|
42794
43392
|
try {
|
|
42795
43393
|
const filePath = args.file;
|
|
42796
43394
|
if (!filePath) {
|
|
42797
|
-
writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "File path is required" } });
|
|
43395
|
+
writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "File path or URL is required" } });
|
|
42798
43396
|
process.exit(1);
|
|
42799
43397
|
}
|
|
43398
|
+
if (isRemoteUrl3(filePath)) {
|
|
43399
|
+
await runVideoIngest({
|
|
43400
|
+
url: filePath,
|
|
43401
|
+
source: args.source,
|
|
43402
|
+
externalId: args["external-id"],
|
|
43403
|
+
externalUrl: args["external-url"],
|
|
43404
|
+
context: args.context,
|
|
43405
|
+
dryRun: args["dry-run"] === true
|
|
43406
|
+
});
|
|
43407
|
+
return;
|
|
43408
|
+
}
|
|
42800
43409
|
const contentType = args["content-type"] || detectContentType(filePath);
|
|
42801
43410
|
const originalFilename = basename3(filePath);
|
|
42802
43411
|
const descriptionContext = args.context;
|
|
42803
43412
|
if (args["dry-run"]) {
|
|
42804
|
-
const fileStats = await
|
|
43413
|
+
const fileStats = await stat8(filePath);
|
|
42805
43414
|
writeJson({
|
|
42806
43415
|
ok: true,
|
|
42807
43416
|
dryRun: true,
|
|
@@ -42845,16 +43454,17 @@ var uploadCommand2 = defineCommand201({
|
|
|
42845
43454
|
});
|
|
42846
43455
|
|
|
42847
43456
|
// src/commands/videos/index.ts
|
|
42848
|
-
var videosCommand =
|
|
43457
|
+
var videosCommand = defineCommand203({
|
|
42849
43458
|
meta: {
|
|
42850
43459
|
name: "videos",
|
|
42851
|
-
description: `Find and manage videos in Baker. Subcommands: search, get, upload, delete, tags.
|
|
43460
|
+
description: `Find and manage videos in Baker. Subcommands: search, get, upload, ingest, delete, tags.
|
|
42852
43461
|
|
|
42853
43462
|
Examples:
|
|
42854
43463
|
baker videos search "product demo" --limit 5
|
|
42855
43464
|
baker videos search "tutorial" --tags explainer
|
|
42856
43465
|
baker videos get <video-id>
|
|
42857
43466
|
baker videos upload ./demo.mp4
|
|
43467
|
+
baker videos ingest https://www.youtube.com/watch?v=abc123
|
|
42858
43468
|
baker videos delete <video-id> --dry-run
|
|
42859
43469
|
baker videos tags
|
|
42860
43470
|
Full guide: __tooling__/docs/tools/baker/videos.md`
|
|
@@ -42864,16 +43474,17 @@ Full guide: __tooling__/docs/tools/baker/videos.md`
|
|
|
42864
43474
|
group: groupCommand2,
|
|
42865
43475
|
search: searchCommand4,
|
|
42866
43476
|
upload: uploadCommand2,
|
|
43477
|
+
ingest: ingestCommand2,
|
|
42867
43478
|
delete: deleteCommand3,
|
|
42868
43479
|
tags: tagsCommand5
|
|
42869
43480
|
}
|
|
42870
43481
|
});
|
|
42871
43482
|
|
|
42872
43483
|
// src/commands/winning-ads/index.ts
|
|
42873
|
-
import { defineCommand as
|
|
43484
|
+
import { defineCommand as defineCommand216 } from "citty";
|
|
42874
43485
|
|
|
42875
43486
|
// src/commands/winning-ads/advertisers.ts
|
|
42876
|
-
import { defineCommand as
|
|
43487
|
+
import { defineCommand as defineCommand204 } from "citty";
|
|
42877
43488
|
|
|
42878
43489
|
// src/commands/winning-ads/shared.ts
|
|
42879
43490
|
function splitList2(value) {
|
|
@@ -42926,7 +43537,7 @@ function advertiserNormalizer(record, full) {
|
|
|
42926
43537
|
last_synced_at: record.last_synced_at ?? null
|
|
42927
43538
|
};
|
|
42928
43539
|
}
|
|
42929
|
-
var advertisersCommand2 =
|
|
43540
|
+
var advertisersCommand2 = defineCommand204({
|
|
42930
43541
|
meta: {
|
|
42931
43542
|
name: "advertisers",
|
|
42932
43543
|
description: 'List corpus advertisers by name or domain. Find your own advertiser for --exclude-advertiser, or a competitor for --advertiser-id / winners. Example: baker winning-ads advertisers "Deel" --output md'
|
|
@@ -42984,7 +43595,7 @@ var advertisersCommand2 = defineCommand203({
|
|
|
42984
43595
|
});
|
|
42985
43596
|
|
|
42986
43597
|
// src/commands/winning-ads/brief.ts
|
|
42987
|
-
import { defineCommand as
|
|
43598
|
+
import { defineCommand as defineCommand205 } from "citty";
|
|
42988
43599
|
registerSchema({
|
|
42989
43600
|
command: "winning-ads.brief",
|
|
42990
43601
|
description: "Generate a creative brief grounded in strategically-similar winning ads. Optionally describe the target creative with --dna (JSON) and steer with --notes.",
|
|
@@ -43030,7 +43641,7 @@ function parseDna(raw) {
|
|
|
43030
43641
|
}
|
|
43031
43642
|
return parsed;
|
|
43032
43643
|
}
|
|
43033
|
-
var briefCommand =
|
|
43644
|
+
var briefCommand = defineCommand205({
|
|
43034
43645
|
meta: {
|
|
43035
43646
|
name: "brief",
|
|
43036
43647
|
description: `Generate a creative brief from winning references. Example: baker winning-ads brief --dna '{"angle":"cost savings"}' --notes "B2B, LinkedIn video" --k 8`
|
|
@@ -43066,7 +43677,7 @@ var briefCommand = defineCommand204({
|
|
|
43066
43677
|
});
|
|
43067
43678
|
|
|
43068
43679
|
// src/commands/winning-ads/content.ts
|
|
43069
|
-
import { defineCommand as
|
|
43680
|
+
import { defineCommand as defineCommand206 } from "citty";
|
|
43070
43681
|
registerSchema({
|
|
43071
43682
|
command: "winning-ads.content",
|
|
43072
43683
|
description: "Read what's INSIDE one winning ad: the spoken transcript, the on-screen text, and the ad copy. Use this after `search`/`winners`/`feed` return a shortlist \u2014 pass an ad_id to understand a reference before reproducing it. Add --full for speech, pacing, and soundtrack detail. Video ads carry the transcript/on-screen text; static ads carry only the copy.",
|
|
@@ -43079,7 +43690,7 @@ registerSchema({
|
|
|
43079
43690
|
}
|
|
43080
43691
|
}
|
|
43081
43692
|
});
|
|
43082
|
-
var contentCommand =
|
|
43693
|
+
var contentCommand = defineCommand206({
|
|
43083
43694
|
meta: {
|
|
43084
43695
|
name: "content",
|
|
43085
43696
|
description: "Read the transcript + on-screen text + copy of one winning ad. Example: baker winning-ads content adg_123 --platform meta --full --output md"
|
|
@@ -43128,7 +43739,7 @@ var contentCommand = defineCommand205({
|
|
|
43128
43739
|
});
|
|
43129
43740
|
|
|
43130
43741
|
// src/commands/winning-ads/feed.ts
|
|
43131
|
-
import { defineCommand as
|
|
43742
|
+
import { defineCommand as defineCommand207 } from "citty";
|
|
43132
43743
|
function buildFeedParams(input) {
|
|
43133
43744
|
const params = {};
|
|
43134
43745
|
const advertiser = splitList2(input.advertiser);
|
|
@@ -43180,7 +43791,7 @@ registerSchema({
|
|
|
43180
43791
|
format: { type: "string", description: "Comma-separated formats to include (e.g. static,video)", required: false }
|
|
43181
43792
|
}
|
|
43182
43793
|
});
|
|
43183
|
-
var feedCommand =
|
|
43794
|
+
var feedCommand = defineCommand207({
|
|
43184
43795
|
meta: {
|
|
43185
43796
|
name: "feed",
|
|
43186
43797
|
description: "Winners across every brand you follow (browse, then trim per advertiser). Example: baker winning-ads feed --per-advertiser 5 --output md"
|
|
@@ -43265,7 +43876,7 @@ var feedCommand = defineCommand206({
|
|
|
43265
43876
|
});
|
|
43266
43877
|
|
|
43267
43878
|
// src/commands/winning-ads/follow.ts
|
|
43268
|
-
import { defineCommand as
|
|
43879
|
+
import { defineCommand as defineCommand208 } from "citty";
|
|
43269
43880
|
var PLATFORMS = ["meta", "linkedin"];
|
|
43270
43881
|
registerSchema({
|
|
43271
43882
|
command: "winning-ads.follow",
|
|
@@ -43280,7 +43891,7 @@ registerSchema({
|
|
|
43280
43891
|
label: { type: "string", description: "Optional display label (defaults to the resolved name)", required: false }
|
|
43281
43892
|
}
|
|
43282
43893
|
});
|
|
43283
|
-
var followCommand =
|
|
43894
|
+
var followCommand = defineCommand208({
|
|
43284
43895
|
meta: {
|
|
43285
43896
|
name: "follow",
|
|
43286
43897
|
description: 'Follow a brand to track ALL its ads \u2014 every platform and country. --platform is how we read your input, not a limit. A domain tracks both Meta + LinkedIn. Example: baker winning-ads follow "deel.com" --platform meta'
|
|
@@ -43327,7 +43938,7 @@ var followCommand = defineCommand207({
|
|
|
43327
43938
|
});
|
|
43328
43939
|
|
|
43329
43940
|
// src/commands/winning-ads/follow-competitors.ts
|
|
43330
|
-
import { defineCommand as
|
|
43941
|
+
import { defineCommand as defineCommand209 } from "citty";
|
|
43331
43942
|
var PLATFORMS2 = ["meta", "linkedin"];
|
|
43332
43943
|
var BATCH_TIMEOUT_MS = 3e5;
|
|
43333
43944
|
function buildFollowBatchBody(input) {
|
|
@@ -43360,7 +43971,7 @@ registerSchema({
|
|
|
43360
43971
|
}
|
|
43361
43972
|
}
|
|
43362
43973
|
});
|
|
43363
|
-
var followCompetitorsCommand =
|
|
43974
|
+
var followCompetitorsCommand = defineCommand209({
|
|
43364
43975
|
meta: {
|
|
43365
43976
|
name: "follow-competitors",
|
|
43366
43977
|
description: 'Follow many brands at once by domain \u2014 add every competitor in one call. Example: baker winning-ads follow-competitors "deel.com,notion.so,hubspot.com"'
|
|
@@ -43435,7 +44046,7 @@ var followCompetitorsCommand = defineCommand208({
|
|
|
43435
44046
|
});
|
|
43436
44047
|
|
|
43437
44048
|
// src/commands/winning-ads/following.ts
|
|
43438
|
-
import { defineCommand as
|
|
44049
|
+
import { defineCommand as defineCommand210 } from "citty";
|
|
43439
44050
|
registerSchema({
|
|
43440
44051
|
command: "winning-ads.following",
|
|
43441
44052
|
description: "List the brands you follow in your ad-dna library, with each one's status (ready vs still adding) and cached ad counts.",
|
|
@@ -43468,7 +44079,7 @@ function followingNormalizer(record, full) {
|
|
|
43468
44079
|
platforms: Array.isArray(record.platforms) ? record.platforms : []
|
|
43469
44080
|
};
|
|
43470
44081
|
}
|
|
43471
|
-
var followingCommand =
|
|
44082
|
+
var followingCommand = defineCommand210({
|
|
43472
44083
|
meta: {
|
|
43473
44084
|
name: "following",
|
|
43474
44085
|
description: "List brands you follow, with status (ready / adding\u2026) and cached counts. Example: baker winning-ads following --output md"
|
|
@@ -43503,7 +44114,7 @@ var followingCommand = defineCommand209({
|
|
|
43503
44114
|
});
|
|
43504
44115
|
|
|
43505
44116
|
// src/commands/winning-ads/patterns.ts
|
|
43506
|
-
import { defineCommand as
|
|
44117
|
+
import { defineCommand as defineCommand211 } from "citty";
|
|
43507
44118
|
registerSchema({
|
|
43508
44119
|
command: "winning-ads.patterns",
|
|
43509
44120
|
description: "Mine what separates two cohorts of ads: pass a comma-list of winning ad ids (--winners) and a comma-list of weaker ad ids (--duds). Returns the discriminating DNA fields.",
|
|
@@ -43542,7 +44153,7 @@ function discriminatorRow(record) {
|
|
|
43542
44153
|
top_values_duds: Array.isArray(record.top_values_b) ? record.top_values_b.join(", ") : ""
|
|
43543
44154
|
};
|
|
43544
44155
|
}
|
|
43545
|
-
var patternsCommand =
|
|
44156
|
+
var patternsCommand = defineCommand211({
|
|
43546
44157
|
meta: {
|
|
43547
44158
|
name: "patterns",
|
|
43548
44159
|
description: "Discover what separates winning ads from weak ones. Example: baker winning-ads patterns --winners a_1,a_2,a_3 --duds a_9,a_8 --output md"
|
|
@@ -43598,7 +44209,7 @@ var patternsCommand = defineCommand210({
|
|
|
43598
44209
|
});
|
|
43599
44210
|
|
|
43600
44211
|
// src/commands/winning-ads/search.ts
|
|
43601
|
-
import { defineCommand as
|
|
44212
|
+
import { defineCommand as defineCommand212 } from "citty";
|
|
43602
44213
|
registerSchema({
|
|
43603
44214
|
command: "winning-ads.search",
|
|
43604
44215
|
description: "Search the ad-dna corpus of scored winning ads. Returns a lean shortlist (advertiser, summary, scores, media_url) to pick a reference to reproduce.",
|
|
@@ -43706,7 +44317,7 @@ function buildSearchBody2(args) {
|
|
|
43706
44317
|
}
|
|
43707
44318
|
return body;
|
|
43708
44319
|
}
|
|
43709
|
-
var searchCommand5 =
|
|
44320
|
+
var searchCommand5 = defineCommand212({
|
|
43710
44321
|
meta: {
|
|
43711
44322
|
name: "search",
|
|
43712
44323
|
description: "Search winning reference ads. Example: baker winning-ads search 'B2B SaaS before/after AI automation' --platform meta --format static --winner-category winner --exclude-advertiser adv_123 --output md"
|
|
@@ -43821,7 +44432,7 @@ var searchCommand5 = defineCommand211({
|
|
|
43821
44432
|
});
|
|
43822
44433
|
|
|
43823
44434
|
// src/commands/winning-ads/seeds.ts
|
|
43824
|
-
import { defineCommand as
|
|
44435
|
+
import { defineCommand as defineCommand213 } from "citty";
|
|
43825
44436
|
function leanRow(r) {
|
|
43826
44437
|
return {
|
|
43827
44438
|
key: r.key,
|
|
@@ -43849,7 +44460,7 @@ function makeSeedCommand(opts) {
|
|
|
43849
44460
|
limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
|
|
43850
44461
|
}
|
|
43851
44462
|
});
|
|
43852
|
-
return
|
|
44463
|
+
return defineCommand213({
|
|
43853
44464
|
meta: { name: opts.name, description: opts.description },
|
|
43854
44465
|
args: {
|
|
43855
44466
|
platform: { type: "string", description: "Single platform to segment on", required: false },
|
|
@@ -43898,7 +44509,7 @@ var formatsCommand = makeSeedCommand({
|
|
|
43898
44509
|
});
|
|
43899
44510
|
|
|
43900
44511
|
// src/commands/winning-ads/unfollow.ts
|
|
43901
|
-
import { defineCommand as
|
|
44512
|
+
import { defineCommand as defineCommand214 } from "citty";
|
|
43902
44513
|
registerSchema({
|
|
43903
44514
|
command: "winning-ads.unfollow",
|
|
43904
44515
|
description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
|
|
@@ -43906,7 +44517,7 @@ registerSchema({
|
|
|
43906
44517
|
advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
|
|
43907
44518
|
}
|
|
43908
44519
|
});
|
|
43909
|
-
var unfollowCommand =
|
|
44520
|
+
var unfollowCommand = defineCommand214({
|
|
43910
44521
|
meta: {
|
|
43911
44522
|
name: "unfollow",
|
|
43912
44523
|
description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
|
|
@@ -43927,7 +44538,7 @@ var unfollowCommand = defineCommand213({
|
|
|
43927
44538
|
});
|
|
43928
44539
|
|
|
43929
44540
|
// src/commands/winning-ads/winners.ts
|
|
43930
|
-
import { defineCommand as
|
|
44541
|
+
import { defineCommand as defineCommand215 } from "citty";
|
|
43931
44542
|
registerSchema({
|
|
43932
44543
|
command: "winning-ads.winners",
|
|
43933
44544
|
description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
|
|
@@ -43937,7 +44548,7 @@ registerSchema({
|
|
|
43937
44548
|
platform: { type: "string", description: "Filter to a single platform: meta|linkedin", required: false }
|
|
43938
44549
|
}
|
|
43939
44550
|
});
|
|
43940
|
-
var winnersCommand =
|
|
44551
|
+
var winnersCommand = defineCommand215({
|
|
43941
44552
|
meta: {
|
|
43942
44553
|
name: "winners",
|
|
43943
44554
|
description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
|
|
@@ -43987,7 +44598,7 @@ var winnersCommand = defineCommand214({
|
|
|
43987
44598
|
});
|
|
43988
44599
|
|
|
43989
44600
|
// src/commands/winning-ads/index.ts
|
|
43990
|
-
var winningAdsCommand =
|
|
44601
|
+
var winningAdsCommand = defineCommand216({
|
|
43991
44602
|
meta: {
|
|
43992
44603
|
name: "winning-ads",
|
|
43993
44604
|
description: `Search the ad-dna corpus of scored "winning" ads for reference creatives to reproduce, and manage the brands your library tracks. Proxied through the Baker backend (BAKER_API_KEY) \u2014 no separate token needed.
|
|
@@ -44177,7 +44788,7 @@ function unknownFlagEnvelope(unknown, commandPath, suggestion) {
|
|
|
44177
44788
|
};
|
|
44178
44789
|
}
|
|
44179
44790
|
function commandPathOf(root, argv) {
|
|
44180
|
-
const
|
|
44791
|
+
const path38 = [];
|
|
44181
44792
|
let command = root;
|
|
44182
44793
|
for (const token of argv) {
|
|
44183
44794
|
if (token === "--" || token.startsWith("-")) {
|
|
@@ -44188,10 +44799,10 @@ function commandPathOf(root, argv) {
|
|
|
44188
44799
|
if (next === void 0 || typeof next !== "object") {
|
|
44189
44800
|
break;
|
|
44190
44801
|
}
|
|
44191
|
-
|
|
44802
|
+
path38.push(token);
|
|
44192
44803
|
command = next;
|
|
44193
44804
|
}
|
|
44194
|
-
return
|
|
44805
|
+
return path38.join(" ");
|
|
44195
44806
|
}
|
|
44196
44807
|
function refuseUnknownFlags(root, argv) {
|
|
44197
44808
|
const unknown = findUnknownFlags(root, argv);
|
|
@@ -44225,7 +44836,7 @@ function getCliVersion() {
|
|
|
44225
44836
|
}
|
|
44226
44837
|
|
|
44227
44838
|
// src/cli.ts
|
|
44228
|
-
var main =
|
|
44839
|
+
var main = defineCommand217({
|
|
44229
44840
|
meta: {
|
|
44230
44841
|
name: "baker",
|
|
44231
44842
|
version: getCliVersion(),
|