@koda-sl/baker-cli 0.204.0 → 0.208.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -2
- 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 +603 -120
- 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";
|
|
@@ -5761,6 +5770,30 @@ var testimonialsOutscraperWebhookResponseSchema = z18.object({
|
|
|
5761
5770
|
// ../api/src/videos.ts
|
|
5762
5771
|
import { z as z19 } from "zod";
|
|
5763
5772
|
var videoStatusSchema = z19.enum(["uploading", "uploaded", "processing", "ready", "error"]);
|
|
5773
|
+
var videoSourceSchema = z19.enum([
|
|
5774
|
+
"uploaded",
|
|
5775
|
+
"url",
|
|
5776
|
+
"bilibili",
|
|
5777
|
+
"bluesky",
|
|
5778
|
+
"dailymotion",
|
|
5779
|
+
"facebook",
|
|
5780
|
+
"instagram",
|
|
5781
|
+
"loom",
|
|
5782
|
+
"newgrounds",
|
|
5783
|
+
"ok",
|
|
5784
|
+
"pinterest",
|
|
5785
|
+
"reddit",
|
|
5786
|
+
"rutube",
|
|
5787
|
+
"snapchat",
|
|
5788
|
+
"streamable",
|
|
5789
|
+
"tiktok",
|
|
5790
|
+
"tumblr",
|
|
5791
|
+
"twitch",
|
|
5792
|
+
"twitter",
|
|
5793
|
+
"vimeo",
|
|
5794
|
+
"vk",
|
|
5795
|
+
"youtube"
|
|
5796
|
+
]);
|
|
5764
5797
|
var videoTranscriptSegmentSchema = z19.object({
|
|
5765
5798
|
text: z19.string(),
|
|
5766
5799
|
startSecond: z19.number(),
|
|
@@ -5861,6 +5894,25 @@ var videosUploadRequestSchema = z19.object({
|
|
|
5861
5894
|
descriptionContext: z19.string().optional()
|
|
5862
5895
|
});
|
|
5863
5896
|
var videosUploadResponseSchema = z19.object({ uploadUrl: z19.string(), videoId: z19.string() });
|
|
5897
|
+
var videosIngestRequestSchema = z19.object({
|
|
5898
|
+
// Must be a direct media file Mux can pull (mp4/mov/webm/…). A page URL —
|
|
5899
|
+
// a YouTube watch link, a TikTok post — is not one: the CLI resolves those to
|
|
5900
|
+
// a media file before calling this route.
|
|
5901
|
+
url: z19.string().url(),
|
|
5902
|
+
source: videoSourceSchema,
|
|
5903
|
+
externalId: z19.string().optional(),
|
|
5904
|
+
externalUrl: z19.string().optional(),
|
|
5905
|
+
// What the person adding the clip knew that the frames cannot show — which
|
|
5906
|
+
// campaign it belongs to, whether the person on camera is a real customer.
|
|
5907
|
+
// The upload route has carried this since it existed; ingest silently dropped
|
|
5908
|
+
// it, so a link-added clip was analysed with strictly less to go on than the
|
|
5909
|
+
// same file uploaded by hand.
|
|
5910
|
+
descriptionContext: z19.string().optional()
|
|
5911
|
+
});
|
|
5912
|
+
var videosIngestResponseSchema = z19.object({
|
|
5913
|
+
videoId: z19.string(),
|
|
5914
|
+
deduped: z19.boolean()
|
|
5915
|
+
});
|
|
5864
5916
|
var videosDeleteRequestSchema = z19.object({ id: z19.string().min(1, "Missing video ID") });
|
|
5865
5917
|
var videosDeleteResponseSchema = z19.object({ ok: z19.literal(true) });
|
|
5866
5918
|
|
|
@@ -8821,11 +8873,11 @@ function rawTextEntries(value) {
|
|
|
8821
8873
|
const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
8822
8874
|
return values.filter((v) => typeof v === "string").flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
|
|
8823
8875
|
}
|
|
8824
|
-
function rawFileEntries(
|
|
8825
|
-
if (typeof
|
|
8876
|
+
function rawFileEntries(path38) {
|
|
8877
|
+
if (typeof path38 !== "string" || path38.length === 0) {
|
|
8826
8878
|
return [];
|
|
8827
8879
|
}
|
|
8828
|
-
return readFileSync2(
|
|
8880
|
+
return readFileSync2(path38, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
8829
8881
|
}
|
|
8830
8882
|
function keywordEntries(args) {
|
|
8831
8883
|
const defaultMatch = typeof args["match-type"] === "string" ? args["match-type"].toUpperCase() : void 0;
|
|
@@ -8848,19 +8900,19 @@ function keywordEntries(args) {
|
|
|
8848
8900
|
}
|
|
8849
8901
|
return entries;
|
|
8850
8902
|
}
|
|
8851
|
-
function loadJsonFileArg(
|
|
8852
|
-
if (typeof
|
|
8903
|
+
function loadJsonFileArg(path38) {
|
|
8904
|
+
if (typeof path38 !== "string" || path38.length === 0) {
|
|
8853
8905
|
return {};
|
|
8854
8906
|
}
|
|
8855
8907
|
try {
|
|
8856
|
-
const parsed = JSON.parse(readFileSync2(
|
|
8908
|
+
const parsed = JSON.parse(readFileSync2(path38, "utf8"));
|
|
8857
8909
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8858
|
-
failWriteValidation(`${
|
|
8910
|
+
failWriteValidation(`${path38} must contain a JSON object`);
|
|
8859
8911
|
}
|
|
8860
8912
|
return parsed;
|
|
8861
8913
|
} catch (err) {
|
|
8862
8914
|
if (err instanceof SyntaxError) {
|
|
8863
|
-
failWriteValidation(`${
|
|
8915
|
+
failWriteValidation(`${path38} is not valid JSON: ${err.message}`);
|
|
8864
8916
|
}
|
|
8865
8917
|
throw err;
|
|
8866
8918
|
}
|
|
@@ -8990,10 +9042,10 @@ async function stageUpdate(kind, customerId, target, payload, hints) {
|
|
|
8990
9042
|
async function stageTarget(kind, customerId, target, hints) {
|
|
8991
9043
|
await stageGoogleOp({ kind, customerId, target }, hints);
|
|
8992
9044
|
}
|
|
8993
|
-
async function draftAction(
|
|
9045
|
+
async function draftAction(path38, body, chat) {
|
|
8994
9046
|
try {
|
|
8995
9047
|
const chatId = resolveChatId(chat);
|
|
8996
|
-
const response = await apiPost(
|
|
9048
|
+
const response = await apiPost(path38, { chatId, ...body });
|
|
8997
9049
|
writeJsonEnvelope(response);
|
|
8998
9050
|
} catch (err) {
|
|
8999
9051
|
handleGoogleError(err);
|
|
@@ -11851,19 +11903,19 @@ function failWriteValidation2(message) {
|
|
|
11851
11903
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
11852
11904
|
process.exit(1);
|
|
11853
11905
|
}
|
|
11854
|
-
function loadJsonFileArg2(
|
|
11855
|
-
if (typeof
|
|
11906
|
+
function loadJsonFileArg2(path38) {
|
|
11907
|
+
if (typeof path38 !== "string" || path38.length === 0) {
|
|
11856
11908
|
return {};
|
|
11857
11909
|
}
|
|
11858
11910
|
try {
|
|
11859
|
-
const parsed = JSON.parse(readFileSync4(
|
|
11911
|
+
const parsed = JSON.parse(readFileSync4(path38, "utf8"));
|
|
11860
11912
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
11861
|
-
failWriteValidation2(`${
|
|
11913
|
+
failWriteValidation2(`${path38} must contain a JSON object`);
|
|
11862
11914
|
}
|
|
11863
11915
|
return parsed;
|
|
11864
11916
|
} catch (err) {
|
|
11865
11917
|
if (err instanceof SyntaxError) {
|
|
11866
|
-
failWriteValidation2(`${
|
|
11918
|
+
failWriteValidation2(`${path38} is not valid JSON: ${err.message}`);
|
|
11867
11919
|
}
|
|
11868
11920
|
throw err;
|
|
11869
11921
|
}
|
|
@@ -11948,15 +12000,15 @@ function parseLocaleFlag(value) {
|
|
|
11948
12000
|
}
|
|
11949
12001
|
return { language: match[1], country: match[2].toUpperCase() };
|
|
11950
12002
|
}
|
|
11951
|
-
function loadTargetingFileArg(
|
|
11952
|
-
if (typeof
|
|
12003
|
+
function loadTargetingFileArg(path38) {
|
|
12004
|
+
if (typeof path38 !== "string" || path38.length === 0) {
|
|
11953
12005
|
return void 0;
|
|
11954
12006
|
}
|
|
11955
|
-
const parsed = loadJsonFileArg2(
|
|
12007
|
+
const parsed = loadJsonFileArg2(path38);
|
|
11956
12008
|
const criteria = parsed.targetingCriteria ?? parsed;
|
|
11957
12009
|
if (!criteria.include) {
|
|
11958
12010
|
failWriteValidation2(
|
|
11959
|
-
`${
|
|
12011
|
+
`${path38} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
|
|
11960
12012
|
);
|
|
11961
12013
|
}
|
|
11962
12014
|
return criteria;
|
|
@@ -11991,14 +12043,14 @@ function parseCsvLine(line) {
|
|
|
11991
12043
|
cells.push(current);
|
|
11992
12044
|
return cells.map((cell) => cell.trim());
|
|
11993
12045
|
}
|
|
11994
|
-
function parseListFileArg(
|
|
11995
|
-
if (typeof
|
|
12046
|
+
function parseListFileArg(path38, maxRows) {
|
|
12047
|
+
if (typeof path38 !== "string" || path38.length === 0) {
|
|
11996
12048
|
return void 0;
|
|
11997
12049
|
}
|
|
11998
|
-
const raw = readFileSync4(
|
|
12050
|
+
const raw = readFileSync4(path38, "utf8");
|
|
11999
12051
|
const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
12000
12052
|
if (lines.length < 2) {
|
|
12001
|
-
failWriteValidation2(`${
|
|
12053
|
+
failWriteValidation2(`${path38} needs a header row and at least one data row`);
|
|
12002
12054
|
}
|
|
12003
12055
|
const columns = parseCsvLine(lines[0]).map((column) => column.trim());
|
|
12004
12056
|
const rows = [];
|
|
@@ -12017,7 +12069,7 @@ function parseListFileArg(path37, maxRows) {
|
|
|
12017
12069
|
}
|
|
12018
12070
|
}
|
|
12019
12071
|
if (rows.length > maxRows) {
|
|
12020
|
-
failWriteValidation2(`${
|
|
12072
|
+
failWriteValidation2(`${path38} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
|
|
12021
12073
|
}
|
|
12022
12074
|
return { columns, rows };
|
|
12023
12075
|
}
|
|
@@ -12113,11 +12165,11 @@ function readPositionals(args) {
|
|
|
12113
12165
|
function splitIdList(raw) {
|
|
12114
12166
|
return raw.split(",").map((id) => id.trim()).filter(Boolean);
|
|
12115
12167
|
}
|
|
12116
|
-
function idsFileEntries(
|
|
12117
|
-
if (typeof
|
|
12168
|
+
function idsFileEntries(path38) {
|
|
12169
|
+
if (typeof path38 !== "string" || path38.length === 0) {
|
|
12118
12170
|
return [];
|
|
12119
12171
|
}
|
|
12120
|
-
return readFileSync4(
|
|
12172
|
+
return readFileSync4(path38, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).flatMap(splitIdList);
|
|
12121
12173
|
}
|
|
12122
12174
|
function requireTargets(args, entity) {
|
|
12123
12175
|
const positionals = readPositionals(args);
|
|
@@ -14740,9 +14792,9 @@ function compactRow(row) {
|
|
|
14740
14792
|
...destination.postUrn ? { postUrn: destination.postUrn } : {}
|
|
14741
14793
|
};
|
|
14742
14794
|
}
|
|
14743
|
-
function readPath(row,
|
|
14795
|
+
function readPath(row, path38) {
|
|
14744
14796
|
let current = row;
|
|
14745
|
-
for (const segment of
|
|
14797
|
+
for (const segment of path38.split(".")) {
|
|
14746
14798
|
const record = asRecord2(current);
|
|
14747
14799
|
if (!record) return void 0;
|
|
14748
14800
|
current = record[segment];
|
|
@@ -14752,10 +14804,10 @@ function readPath(row, path37) {
|
|
|
14752
14804
|
function projectFields(rows, paths) {
|
|
14753
14805
|
return rows.map((row) => {
|
|
14754
14806
|
const projected = {};
|
|
14755
|
-
for (const
|
|
14756
|
-
const value = readPath(row,
|
|
14807
|
+
for (const path38 of paths) {
|
|
14808
|
+
const value = readPath(row, path38);
|
|
14757
14809
|
if (value !== void 0) {
|
|
14758
|
-
projected[
|
|
14810
|
+
projected[path38] = value;
|
|
14759
14811
|
}
|
|
14760
14812
|
}
|
|
14761
14813
|
return projected;
|
|
@@ -16049,11 +16101,11 @@ var updateStatusSchema = z21.enum(UPDATE_STATUSES);
|
|
|
16049
16101
|
function currencyMinimums2(currencyCode) {
|
|
16050
16102
|
return CURRENCY_MINIMUMS2[currencyCode] ?? DEFAULT_CURRENCY_MINIMUM2;
|
|
16051
16103
|
}
|
|
16052
|
-
function validateDailyBudgetFloor(money, ctx,
|
|
16104
|
+
function validateDailyBudgetFloor(money, ctx, path38) {
|
|
16053
16105
|
if (money?.currencyCode) {
|
|
16054
16106
|
const min = currencyMinimums2(money.currencyCode).dailyBudgetMin;
|
|
16055
16107
|
if (Number(money.amount) < min) {
|
|
16056
|
-
ctx.addIssue({ code: "custom", path:
|
|
16108
|
+
ctx.addIssue({ code: "custom", path: path38, message: `below the ${min} ${money.currencyCode} daily minimum` });
|
|
16057
16109
|
}
|
|
16058
16110
|
}
|
|
16059
16111
|
}
|
|
@@ -16715,19 +16767,19 @@ function failWriteValidation3(message) {
|
|
|
16715
16767
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
16716
16768
|
process.exit(1);
|
|
16717
16769
|
}
|
|
16718
|
-
function loadJsonFileArg3(
|
|
16719
|
-
if (typeof
|
|
16770
|
+
function loadJsonFileArg3(path38) {
|
|
16771
|
+
if (typeof path38 !== "string" || path38.length === 0) {
|
|
16720
16772
|
return {};
|
|
16721
16773
|
}
|
|
16722
16774
|
try {
|
|
16723
|
-
const parsed = JSON.parse(readFileSync8(
|
|
16775
|
+
const parsed = JSON.parse(readFileSync8(path38, "utf8"));
|
|
16724
16776
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
16725
|
-
failWriteValidation3(`${
|
|
16777
|
+
failWriteValidation3(`${path38} must contain a JSON object`);
|
|
16726
16778
|
}
|
|
16727
16779
|
return parsed;
|
|
16728
16780
|
} catch (err) {
|
|
16729
16781
|
if (err instanceof SyntaxError) {
|
|
16730
|
-
failWriteValidation3(`${
|
|
16782
|
+
failWriteValidation3(`${path38} is not valid JSON: ${err.message}`);
|
|
16731
16783
|
}
|
|
16732
16784
|
throw err;
|
|
16733
16785
|
}
|
|
@@ -17270,10 +17322,10 @@ function duplicateCommand2(entity, label) {
|
|
|
17270
17322
|
replace: { type: "boolean", description: "Pause the original once the copy publishes" }
|
|
17271
17323
|
},
|
|
17272
17324
|
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-
|
|
17325
|
+
const { apiPost: apiPost2 } = await import("./client-CF6ZORCO.js");
|
|
17326
|
+
const { requireChatId: requireChatId2 } = await import("./env-7SCX7HAH.js");
|
|
17327
|
+
const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-4YIOSWOG.js");
|
|
17328
|
+
const { handleMetaError: handleMetaError2 } = await import("./shared-EBF5DEDJ.js");
|
|
17277
17329
|
try {
|
|
17278
17330
|
const accountId = bareAccountId2(args);
|
|
17279
17331
|
const chatId = requireChatId2();
|
|
@@ -27898,12 +27950,12 @@ function listFlowSlugs() {
|
|
|
27898
27950
|
return readdirSync2(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_") && entry.name !== ".gitkeep").map((entry) => entry.name).sort();
|
|
27899
27951
|
}
|
|
27900
27952
|
function readFlowTree(slug) {
|
|
27901
|
-
const
|
|
27902
|
-
if (!existsSync5(
|
|
27953
|
+
const path38 = join3(flowsDir(), slug, "_data.json");
|
|
27954
|
+
if (!existsSync5(path38)) {
|
|
27903
27955
|
failLocal(`No form "${slug}". Run "baker flows list" to see the forms in this workspace.`);
|
|
27904
27956
|
}
|
|
27905
27957
|
try {
|
|
27906
|
-
return JSON.parse(readFileSync9(
|
|
27958
|
+
return JSON.parse(readFileSync9(path38, "utf-8"));
|
|
27907
27959
|
} catch (error) {
|
|
27908
27960
|
failLocal(`Could not read form "${slug}": ${error instanceof Error ? error.message : String(error)}`);
|
|
27909
27961
|
}
|
|
@@ -28316,10 +28368,10 @@ async function stageOps(ops) {
|
|
|
28316
28368
|
handleError(err);
|
|
28317
28369
|
}
|
|
28318
28370
|
}
|
|
28319
|
-
async function draftAction2(
|
|
28371
|
+
async function draftAction2(path38, body, chat) {
|
|
28320
28372
|
const chatId = resolveChatId(chat);
|
|
28321
28373
|
try {
|
|
28322
|
-
const data = await apiPost(
|
|
28374
|
+
const data = await apiPost(path38, { chatId, ...body });
|
|
28323
28375
|
writeJsonEnvelope({ ok: true, data });
|
|
28324
28376
|
return data;
|
|
28325
28377
|
} catch (err) {
|
|
@@ -30340,9 +30392,9 @@ async function readImageBuffer(pathOrUrl) {
|
|
|
30340
30392
|
}
|
|
30341
30393
|
return readFile19(pathOrUrl);
|
|
30342
30394
|
}
|
|
30343
|
-
async function isDirectory(
|
|
30395
|
+
async function isDirectory(path38) {
|
|
30344
30396
|
try {
|
|
30345
|
-
const s = await stat4(
|
|
30397
|
+
const s = await stat4(path38);
|
|
30346
30398
|
return s.isDirectory();
|
|
30347
30399
|
} catch {
|
|
30348
30400
|
return false;
|
|
@@ -30626,13 +30678,13 @@ function resolveDownloadPath({ baseName, extension, out, outIsDirectory: outIsDi
|
|
|
30626
30678
|
}
|
|
30627
30679
|
function disambiguate(paths) {
|
|
30628
30680
|
const taken = /* @__PURE__ */ new Set();
|
|
30629
|
-
return paths.map((
|
|
30630
|
-
if (!taken.has(
|
|
30631
|
-
taken.add(
|
|
30632
|
-
return
|
|
30681
|
+
return paths.map((path38) => {
|
|
30682
|
+
if (!taken.has(path38)) {
|
|
30683
|
+
taken.add(path38);
|
|
30684
|
+
return path38;
|
|
30633
30685
|
}
|
|
30634
|
-
const ext = extname3(
|
|
30635
|
-
const stem =
|
|
30686
|
+
const ext = extname3(path38);
|
|
30687
|
+
const stem = path38.slice(0, path38.length - ext.length);
|
|
30636
30688
|
let n = 2;
|
|
30637
30689
|
while (taken.has(`${stem}-${n}${ext}`)) n += 1;
|
|
30638
30690
|
const unique = `${stem}-${n}${ext}`;
|
|
@@ -30759,10 +30811,10 @@ async function runDownloads(plan) {
|
|
|
30759
30811
|
const paths = disambiguate(fetched.map((item) => item.path));
|
|
30760
30812
|
const downloaded = [];
|
|
30761
30813
|
for (const [index, item] of fetched.entries()) {
|
|
30762
|
-
const
|
|
30814
|
+
const path38 = paths[index] ?? item.path;
|
|
30763
30815
|
try {
|
|
30764
|
-
await atomicWrite(
|
|
30765
|
-
downloaded.push({ input: item.input, output:
|
|
30816
|
+
await atomicWrite(path38, item.buffer);
|
|
30817
|
+
downloaded.push({ input: item.input, output: path38, bytes: item.buffer.length, contentType: item.contentType });
|
|
30766
30818
|
} catch (err) {
|
|
30767
30819
|
failed.push({ input: item.input, error: failureMessage(err, "Write failed") });
|
|
30768
30820
|
}
|
|
@@ -32513,14 +32565,14 @@ async function processInternal(inputBuffer, isSVG, options) {
|
|
|
32513
32565
|
processedData = result.data;
|
|
32514
32566
|
info = result.info;
|
|
32515
32567
|
}
|
|
32516
|
-
let
|
|
32568
|
+
let pipeline2 = sharp2(processedData, {
|
|
32517
32569
|
raw: { channels: 4, width: info.width, height: info.height }
|
|
32518
32570
|
}).png();
|
|
32519
32571
|
if (options.shrinkToContent) {
|
|
32520
32572
|
stages.push("trim");
|
|
32521
|
-
|
|
32573
|
+
pipeline2 = pipeline2.trim();
|
|
32522
32574
|
}
|
|
32523
|
-
const outputBuffer = await
|
|
32575
|
+
const outputBuffer = await pipeline2.toBuffer();
|
|
32524
32576
|
return { buffer: outputBuffer, stages };
|
|
32525
32577
|
}
|
|
32526
32578
|
function applyResize(buffer, resize) {
|
|
@@ -40493,7 +40545,7 @@ function parseImageRefs(spec) {
|
|
|
40493
40545
|
}
|
|
40494
40546
|
var defaultDeps = {
|
|
40495
40547
|
ingest: (url) => apiPost("/api/images/ingest", { url, source: "uploaded" }),
|
|
40496
|
-
upload: (
|
|
40548
|
+
upload: (path38) => uploadLocalImage({ file: path38, contentType: detectImageContentType(path38), source: "uploaded" })
|
|
40497
40549
|
};
|
|
40498
40550
|
async function resolveLibraryImageIds(spec, limit, deps = defaultDeps) {
|
|
40499
40551
|
const refs = parseImageRefs(spec);
|
|
@@ -40513,10 +40565,10 @@ async function resolveLibraryImageIds(spec, limit, deps = defaultDeps) {
|
|
|
40513
40565
|
}
|
|
40514
40566
|
return { imageIds, added };
|
|
40515
40567
|
}
|
|
40516
|
-
function uploadFailure(
|
|
40568
|
+
function uploadFailure(path38) {
|
|
40517
40569
|
return (error) => {
|
|
40518
40570
|
if (error instanceof ApiError) throw error;
|
|
40519
|
-
throw new ApiError("VALIDATION_ERROR", `Could not read "${
|
|
40571
|
+
throw new ApiError("VALIDATION_ERROR", `Could not read "${path38}" as an image.`);
|
|
40520
40572
|
};
|
|
40521
40573
|
}
|
|
40522
40574
|
|
|
@@ -41566,10 +41618,10 @@ async function stageOp4(op) {
|
|
|
41566
41618
|
handleError4(err);
|
|
41567
41619
|
}
|
|
41568
41620
|
}
|
|
41569
|
-
async function draftAction3(
|
|
41621
|
+
async function draftAction3(path38, body, chat) {
|
|
41570
41622
|
const chatId = resolveChatId(chat);
|
|
41571
41623
|
try {
|
|
41572
|
-
const data = await apiPost(
|
|
41624
|
+
const data = await apiPost(path38, { chatId, ...body });
|
|
41573
41625
|
writeJsonEnvelope({ ok: true, data });
|
|
41574
41626
|
return data;
|
|
41575
41627
|
} catch (err) {
|
|
@@ -42542,7 +42594,7 @@ Full guide: __tooling__/docs/tools/baker/testimonials.md`
|
|
|
42542
42594
|
});
|
|
42543
42595
|
|
|
42544
42596
|
// src/commands/videos/index.ts
|
|
42545
|
-
import { defineCommand as
|
|
42597
|
+
import { defineCommand as defineCommand203 } from "citty";
|
|
42546
42598
|
|
|
42547
42599
|
// src/commands/videos/delete.ts
|
|
42548
42600
|
import { defineCommand as defineCommand197 } from "citty";
|
|
@@ -42673,8 +42725,420 @@ var groupCommand2 = defineCommand199({
|
|
|
42673
42725
|
}
|
|
42674
42726
|
});
|
|
42675
42727
|
|
|
42676
|
-
// src/commands/videos/
|
|
42728
|
+
// src/commands/videos/ingest.ts
|
|
42729
|
+
import { mkdtemp as mkdtemp2, rm as rm7, stat as stat7 } from "fs/promises";
|
|
42730
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
42731
|
+
import path37 from "path";
|
|
42677
42732
|
import { defineCommand as defineCommand200 } from "citty";
|
|
42733
|
+
|
|
42734
|
+
// src/lib/streamUpload.ts
|
|
42735
|
+
import { createHash as createHash2 } from "crypto";
|
|
42736
|
+
import { createReadStream } from "fs";
|
|
42737
|
+
import { Readable } from "stream";
|
|
42738
|
+
import { pipeline } from "stream/promises";
|
|
42739
|
+
async function hashFile(filePath) {
|
|
42740
|
+
const hash = createHash2("sha256");
|
|
42741
|
+
await pipeline(createReadStream(filePath), hash);
|
|
42742
|
+
return hash.digest("hex");
|
|
42743
|
+
}
|
|
42744
|
+
async function putFileStreamed(args) {
|
|
42745
|
+
const body = Readable.toWeb(createReadStream(args.filePath));
|
|
42746
|
+
return await fetch(args.putUrl, {
|
|
42747
|
+
method: "PUT",
|
|
42748
|
+
body,
|
|
42749
|
+
headers: {
|
|
42750
|
+
"Content-Type": args.contentType,
|
|
42751
|
+
"Content-Length": String(args.sizeBytes)
|
|
42752
|
+
},
|
|
42753
|
+
duplex: "half"
|
|
42754
|
+
});
|
|
42755
|
+
}
|
|
42756
|
+
|
|
42757
|
+
// src/commands/videos/videoPlatforms.ts
|
|
42758
|
+
var PLATFORM_DOMAINS = {
|
|
42759
|
+
youtube: ["youtube.com", "youtu.be"],
|
|
42760
|
+
instagram: ["instagram.com", "ddinstagram.com"],
|
|
42761
|
+
tiktok: ["tiktok.com"],
|
|
42762
|
+
twitter: ["twitter.com", "x.com", "vxtwitter.com", "fixvx.com"],
|
|
42763
|
+
facebook: ["facebook.com", "fb.watch", "fb.com"],
|
|
42764
|
+
reddit: ["reddit.com", "redd.it"],
|
|
42765
|
+
vimeo: ["vimeo.com"],
|
|
42766
|
+
dailymotion: ["dailymotion.com", "dai.ly"],
|
|
42767
|
+
twitch: ["twitch.tv"],
|
|
42768
|
+
bluesky: ["bsky.app"],
|
|
42769
|
+
bilibili: ["bilibili.com", "bilibili.tv", "b23.tv"],
|
|
42770
|
+
vk: ["vk.com", "vk.ru", "vkvideo.ru"],
|
|
42771
|
+
ok: ["ok.ru"],
|
|
42772
|
+
pinterest: ["pinterest.com", "pin.it"],
|
|
42773
|
+
tumblr: ["tumblr.com"],
|
|
42774
|
+
snapchat: ["snapchat.com"],
|
|
42775
|
+
loom: ["loom.com"],
|
|
42776
|
+
streamable: ["streamable.com"],
|
|
42777
|
+
newgrounds: ["newgrounds.com"],
|
|
42778
|
+
rutube: ["rutube.ru"]
|
|
42779
|
+
};
|
|
42780
|
+
var VIDEO_PLATFORMS = Object.keys(PLATFORM_DOMAINS);
|
|
42781
|
+
function platformFromUrl(rawUrl) {
|
|
42782
|
+
let host;
|
|
42783
|
+
try {
|
|
42784
|
+
host = new URL(rawUrl).hostname.toLowerCase();
|
|
42785
|
+
} catch {
|
|
42786
|
+
return "url";
|
|
42787
|
+
}
|
|
42788
|
+
for (const [platform, domains] of Object.entries(PLATFORM_DOMAINS)) {
|
|
42789
|
+
if (domains.some((domain) => host === domain || host.endsWith(`.${domain}`))) {
|
|
42790
|
+
return platform;
|
|
42791
|
+
}
|
|
42792
|
+
}
|
|
42793
|
+
return "url";
|
|
42794
|
+
}
|
|
42795
|
+
|
|
42796
|
+
// src/commands/videos/ingestUrl.ts
|
|
42797
|
+
var MAX_VIDEO_INGEST_BYTES = 16 * 1024 * 1024 * 1024;
|
|
42798
|
+
var MAX_VIDEO_INGEST_SECONDS = 3 * 60 * 60;
|
|
42799
|
+
function refuseByDuration(seconds, maxSeconds) {
|
|
42800
|
+
if (seconds === void 0 || seconds <= maxSeconds) return null;
|
|
42801
|
+
return `That video is ${(seconds / 3600).toFixed(1)} hours long \u2014 the limit is ${Math.round(maxSeconds / 3600)} hours.`;
|
|
42802
|
+
}
|
|
42803
|
+
var DOWNLOAD_TIMEOUT_FLOOR_MS = 10 * 60 * 1e3;
|
|
42804
|
+
var DOWNLOAD_TIMEOUT_CEILING_MS = 15 * 60 * 1e3;
|
|
42805
|
+
function downloadTimeoutMs(seconds) {
|
|
42806
|
+
if (seconds === void 0) return DOWNLOAD_TIMEOUT_FLOOR_MS;
|
|
42807
|
+
const scaled = seconds * 2 * 1e3;
|
|
42808
|
+
return Math.min(Math.max(scaled, DOWNLOAD_TIMEOUT_FLOOR_MS), DOWNLOAD_TIMEOUT_CEILING_MS);
|
|
42809
|
+
}
|
|
42810
|
+
var INGEST_MAX_HEIGHT = 1080;
|
|
42811
|
+
var DIRECT_MEDIA_EXTENSIONS = [".mp4", ".mov", ".webm", ".m4v", ".mkv", ".avi"];
|
|
42812
|
+
function isDirectMediaUrl(rawUrl) {
|
|
42813
|
+
const pathname = safePathname(rawUrl);
|
|
42814
|
+
return DIRECT_MEDIA_EXTENSIONS.some((ext) => pathname.endsWith(ext));
|
|
42815
|
+
}
|
|
42816
|
+
function resolveIngestSource(explicit, rawUrl) {
|
|
42817
|
+
if (explicit) return explicit;
|
|
42818
|
+
return platformFromUrl(rawUrl);
|
|
42819
|
+
}
|
|
42820
|
+
var TOOLING_OR_BLOCK_SIGNATURES = [
|
|
42821
|
+
"requested format is not available",
|
|
42822
|
+
"only images are available",
|
|
42823
|
+
"nsig",
|
|
42824
|
+
"sign in to confirm",
|
|
42825
|
+
// bot check — distinct from "Private video. Sign in if…"
|
|
42826
|
+
"unable to download api page",
|
|
42827
|
+
"http error 403",
|
|
42828
|
+
// yt-dlp's own "the site changed under me" signature. It prints a GitHub issue
|
|
42829
|
+
// link whenever a page parsed nothing like the shape the extractor expects —
|
|
42830
|
+
// which is precisely a downloader that has aged out, not a bad link. Observed
|
|
42831
|
+
// live on TikTok, which probed fine and then broke ninety minutes later from
|
|
42832
|
+
// the same machine, identically through direct, datacenter and residential
|
|
42833
|
+
// exits. Without this it fell through to "check the link is public", sending
|
|
42834
|
+
// the reader to inspect a video that was never the problem.
|
|
42835
|
+
"please report this issue"
|
|
42836
|
+
];
|
|
42837
|
+
function isToolingOrBlockFailure(detail) {
|
|
42838
|
+
const haystack = detail.toLowerCase();
|
|
42839
|
+
return TOOLING_OR_BLOCK_SIGNATURES.some((signature) => haystack.includes(signature));
|
|
42840
|
+
}
|
|
42841
|
+
var REGION_BLOCK_SIGNATURES = [
|
|
42842
|
+
"in your country",
|
|
42843
|
+
"geo-restricted",
|
|
42844
|
+
"geo restricted",
|
|
42845
|
+
"not available from your location",
|
|
42846
|
+
"blocked in your country"
|
|
42847
|
+
];
|
|
42848
|
+
function isDrmProtected(detail) {
|
|
42849
|
+
return detail.toLowerCase().includes("drm protected");
|
|
42850
|
+
}
|
|
42851
|
+
function isRegionBlocked(detail) {
|
|
42852
|
+
const haystack = detail.toLowerCase();
|
|
42853
|
+
return REGION_BLOCK_SIGNATURES.some((signature) => haystack.includes(signature));
|
|
42854
|
+
}
|
|
42855
|
+
function worthDownloadingInstead(errorCode) {
|
|
42856
|
+
return errorCode === "VALIDATION_ERROR";
|
|
42857
|
+
}
|
|
42858
|
+
function directDedupeKey(source, rawUrl) {
|
|
42859
|
+
return `${source}:${rawUrl}`;
|
|
42860
|
+
}
|
|
42861
|
+
function safePathname(rawUrl) {
|
|
42862
|
+
try {
|
|
42863
|
+
return new URL(rawUrl).pathname.toLowerCase();
|
|
42864
|
+
} catch {
|
|
42865
|
+
return "";
|
|
42866
|
+
}
|
|
42867
|
+
}
|
|
42868
|
+
|
|
42869
|
+
// src/commands/videos/ingest.ts
|
|
42870
|
+
registerSchema({
|
|
42871
|
+
command: "videos.ingest",
|
|
42872
|
+
description: "Add a video to the library from a URL \u2014 a direct file, or a YouTube/TikTok/Vimeo page.",
|
|
42873
|
+
args: {
|
|
42874
|
+
url: { type: "string", description: "Video URL (direct file or a watch/post page)", required: true },
|
|
42875
|
+
source: { type: "string", description: "Provenance: url | youtube", required: false },
|
|
42876
|
+
"external-id": { type: "string", description: "Provider asset id (deduped on)", required: false },
|
|
42877
|
+
"external-url": { type: "string", description: "Canonical page URL", required: false },
|
|
42878
|
+
download: {
|
|
42879
|
+
type: "boolean",
|
|
42880
|
+
description: "Force the download path instead of letting Baker fetch the URL directly",
|
|
42881
|
+
required: false,
|
|
42882
|
+
default: false
|
|
42883
|
+
},
|
|
42884
|
+
country: {
|
|
42885
|
+
type: "string",
|
|
42886
|
+
description: "Two-letter country to fetch from (e.g. US, DE, JP) \u2014 use when a video is blocked in your region",
|
|
42887
|
+
required: false
|
|
42888
|
+
},
|
|
42889
|
+
context: {
|
|
42890
|
+
type: "string",
|
|
42891
|
+
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.",
|
|
42892
|
+
required: false
|
|
42893
|
+
},
|
|
42894
|
+
"dry-run": { type: "boolean", description: "Preview the operation without executing", required: false }
|
|
42895
|
+
}
|
|
42896
|
+
});
|
|
42897
|
+
var ingestCommand2 = defineCommand200({
|
|
42898
|
+
meta: {
|
|
42899
|
+
name: "ingest",
|
|
42900
|
+
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"
|
|
42901
|
+
},
|
|
42902
|
+
args: {
|
|
42903
|
+
url: { type: "positional", description: "Video URL", required: false },
|
|
42904
|
+
source: { type: "string", description: "Provenance: url | youtube", required: false },
|
|
42905
|
+
"external-id": { type: "string", description: "Provider asset id", required: false },
|
|
42906
|
+
"external-url": { type: "string", description: "Canonical page URL", required: false },
|
|
42907
|
+
download: { type: "boolean", description: "Force the download path", required: false, default: false },
|
|
42908
|
+
country: { type: "string", description: "Two-letter country to fetch from, e.g. US", required: false },
|
|
42909
|
+
context: {
|
|
42910
|
+
type: "string",
|
|
42911
|
+
description: "Context for the analysis \u2014 campaign, who is on camera, intended use",
|
|
42912
|
+
required: false
|
|
42913
|
+
},
|
|
42914
|
+
"dry-run": { type: "boolean", description: "Preview without executing", required: false, default: false }
|
|
42915
|
+
},
|
|
42916
|
+
run: ({ args }) => runVideoIngest({
|
|
42917
|
+
url: args.url,
|
|
42918
|
+
source: args.source,
|
|
42919
|
+
externalId: args["external-id"],
|
|
42920
|
+
externalUrl: args["external-url"],
|
|
42921
|
+
forceDownload: args.download === true,
|
|
42922
|
+
country: args.country,
|
|
42923
|
+
context: args.context,
|
|
42924
|
+
dryRun: args["dry-run"] === true
|
|
42925
|
+
})
|
|
42926
|
+
});
|
|
42927
|
+
async function runVideoIngest(opts) {
|
|
42928
|
+
const url = opts.url;
|
|
42929
|
+
if (!url) {
|
|
42930
|
+
writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "URL is required" } });
|
|
42931
|
+
process.exit(1);
|
|
42932
|
+
}
|
|
42933
|
+
const direct = !opts.forceDownload && isDirectMediaUrl(url);
|
|
42934
|
+
const country = opts.country?.trim() || void 0;
|
|
42935
|
+
const args = {
|
|
42936
|
+
url,
|
|
42937
|
+
source: resolveIngestSource(opts.source, url),
|
|
42938
|
+
externalId: opts.externalId,
|
|
42939
|
+
// Deliberately NOT pre-filled with the pasted URL. Doing so shadowed the
|
|
42940
|
+
// canonical URL the probe resolves (`?? canonicalUrl(...)` below could never
|
|
42941
|
+
// run), so the same video filed under a different `externalUrl` depending on
|
|
42942
|
+
// whether the link was copied from a share sheet, a shorts page or the
|
|
42943
|
+
// address bar. `downloadThenIngest` falls back to `args.url` when the probe
|
|
42944
|
+
// resolves nothing, which is the value this used to hard-code.
|
|
42945
|
+
externalUrl: opts.externalUrl,
|
|
42946
|
+
descriptionContext: opts.context?.trim() || void 0
|
|
42947
|
+
};
|
|
42948
|
+
const validation = videosIngestRequestSchema.safeParse({ ...args, url });
|
|
42949
|
+
if (!validation.success) {
|
|
42950
|
+
const problem = validation.error.issues[0];
|
|
42951
|
+
writeJson({
|
|
42952
|
+
ok: false,
|
|
42953
|
+
error: {
|
|
42954
|
+
code: "VALIDATION_ERROR",
|
|
42955
|
+
message: problem ? `${problem.path.join(".") || "request"}: ${problem.message}` : "Invalid request"
|
|
42956
|
+
}
|
|
42957
|
+
});
|
|
42958
|
+
process.exit(1);
|
|
42959
|
+
}
|
|
42960
|
+
if (opts.dryRun) {
|
|
42961
|
+
reportDryRun(args, direct, country);
|
|
42962
|
+
return;
|
|
42963
|
+
}
|
|
42964
|
+
try {
|
|
42965
|
+
writeJson({ ok: true, data: await ingestByRoute(args, direct, country) });
|
|
42966
|
+
} catch (err) {
|
|
42967
|
+
reportIngestFailure(err);
|
|
42968
|
+
}
|
|
42969
|
+
}
|
|
42970
|
+
function reportDryRun(args, direct, country) {
|
|
42971
|
+
writeJson({
|
|
42972
|
+
ok: true,
|
|
42973
|
+
dryRun: true,
|
|
42974
|
+
operation: "videos.ingest",
|
|
42975
|
+
params: {
|
|
42976
|
+
url: args.url,
|
|
42977
|
+
source: args.source,
|
|
42978
|
+
// Known up front on the direct route, because it is derived from the URL
|
|
42979
|
+
// alone. The download route's key comes from the probe, which a dry run
|
|
42980
|
+
// does not perform, so there it stays null rather than becoming a guess.
|
|
42981
|
+
externalId: args.externalId ?? (direct ? directDedupeKey(args.source, args.url) : null),
|
|
42982
|
+
// A page URL's canonical form is only known after the probe, which a dry
|
|
42983
|
+
// run does not perform — so this is null rather than a guess.
|
|
42984
|
+
externalUrl: args.externalUrl ?? null,
|
|
42985
|
+
direct,
|
|
42986
|
+
country: country ?? null,
|
|
42987
|
+
descriptionContext: args.descriptionContext ?? null
|
|
42988
|
+
}
|
|
42989
|
+
});
|
|
42990
|
+
}
|
|
42991
|
+
async function ingestByRoute(args, direct, country) {
|
|
42992
|
+
if (!direct) {
|
|
42993
|
+
return { ...await downloadThenIngest(args, country), via: "download" };
|
|
42994
|
+
}
|
|
42995
|
+
try {
|
|
42996
|
+
const externalId = args.externalId ?? directDedupeKey(args.source, args.url);
|
|
42997
|
+
return { ...await ingestUrl({ ...args, externalId }), via: "direct" };
|
|
42998
|
+
} catch (err) {
|
|
42999
|
+
if (!(err instanceof ApiError) || !worthDownloadingInstead(err.code)) throw err;
|
|
43000
|
+
return { ...await downloadThenIngest(args, country), via: "download", fallbackFrom: err.message };
|
|
43001
|
+
}
|
|
43002
|
+
}
|
|
43003
|
+
function reportYtDlpFailure(err) {
|
|
43004
|
+
const detail = `${err.stderrTail} ${err.message}`;
|
|
43005
|
+
if (isProxyFailure(ytDlpBlockSignal(detail))) {
|
|
43006
|
+
fail7(
|
|
43007
|
+
"Couldn't download that video: our connection to the internet was refused.",
|
|
43008
|
+
"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."
|
|
43009
|
+
);
|
|
43010
|
+
}
|
|
43011
|
+
if (isDrmProtected(detail)) {
|
|
43012
|
+
fail7(
|
|
43013
|
+
"That video is copy-protected, so it can't be added to the library.",
|
|
43014
|
+
"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>`."
|
|
43015
|
+
);
|
|
43016
|
+
}
|
|
43017
|
+
if (isVimeoAuthFailure(detail)) {
|
|
43018
|
+
fail7(
|
|
43019
|
+
"Vimeo wouldn't hand over that video \u2014 Baker isn't signed in to Vimeo.",
|
|
43020
|
+
"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>`."
|
|
43021
|
+
);
|
|
43022
|
+
}
|
|
43023
|
+
if (isRegionBlocked(detail)) {
|
|
43024
|
+
fail7(
|
|
43025
|
+
`That video isn't available in the region we're fetching from. ${err.stderrTail || err.message}`,
|
|
43026
|
+
"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."
|
|
43027
|
+
);
|
|
43028
|
+
}
|
|
43029
|
+
fail7(
|
|
43030
|
+
`Couldn't download that video. ${err.stderrTail || err.message}`,
|
|
43031
|
+
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>`."
|
|
43032
|
+
);
|
|
43033
|
+
}
|
|
43034
|
+
function fail7(message, fix, code = "INGEST_FAILED") {
|
|
43035
|
+
writeJson({ ok: false, error: { code, message, fix } });
|
|
43036
|
+
process.exit(1);
|
|
43037
|
+
}
|
|
43038
|
+
function reportIngestFailure(err) {
|
|
43039
|
+
if (err instanceof ApiError) {
|
|
43040
|
+
writeJson({ ok: false, error: { code: err.code, message: err.message } });
|
|
43041
|
+
process.exit(1);
|
|
43042
|
+
}
|
|
43043
|
+
if (err instanceof YtDlpError) reportYtDlpFailure(err);
|
|
43044
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
43045
|
+
if (detail.includes("timed out after")) {
|
|
43046
|
+
fail7(
|
|
43047
|
+
"That video took too long to download and was stopped part-way.",
|
|
43048
|
+
"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>`.",
|
|
43049
|
+
"INTERNAL_ERROR"
|
|
43050
|
+
);
|
|
43051
|
+
}
|
|
43052
|
+
fail7(
|
|
43053
|
+
`Couldn't add that video. ${detail}`,
|
|
43054
|
+
"This is a fault on Baker's side, not a problem with the link. Report it.",
|
|
43055
|
+
"INTERNAL_ERROR"
|
|
43056
|
+
);
|
|
43057
|
+
}
|
|
43058
|
+
function dedupeKey(args, info) {
|
|
43059
|
+
const id = typeof info.id === "string" ? info.id : void 0;
|
|
43060
|
+
if (!id) return void 0;
|
|
43061
|
+
if (info.extractor === "generic") return `${args.source}:${canonicalUrl(info) ?? args.url}`;
|
|
43062
|
+
return `${args.source}:${id}`;
|
|
43063
|
+
}
|
|
43064
|
+
function ingestUrl(args) {
|
|
43065
|
+
const body = {
|
|
43066
|
+
url: args.url,
|
|
43067
|
+
source: args.source
|
|
43068
|
+
};
|
|
43069
|
+
if (args.externalId) body.externalId = args.externalId;
|
|
43070
|
+
if (args.externalUrl) body.externalUrl = args.externalUrl;
|
|
43071
|
+
if (args.descriptionContext) body.descriptionContext = args.descriptionContext;
|
|
43072
|
+
return apiPost("/api/videos/ingest", body);
|
|
43073
|
+
}
|
|
43074
|
+
async function downloadThenIngest(args, country) {
|
|
43075
|
+
const vimeoCookie = captureVimeoCookie();
|
|
43076
|
+
const workDir = await mkdtemp2(path37.join(tmpdir3(), "videos-ingest-"));
|
|
43077
|
+
try {
|
|
43078
|
+
const probe = await probeYtDlp({ url: args.url, country, vimeoCookie, cookieDir: workDir });
|
|
43079
|
+
if (isAudioOnly(probe.info)) {
|
|
43080
|
+
throw new ApiError(
|
|
43081
|
+
"VALIDATION_ERROR",
|
|
43082
|
+
"That link is audio, not video, so it can't go in the video library. Music and podcast links have no picture to store."
|
|
43083
|
+
);
|
|
43084
|
+
}
|
|
43085
|
+
const tooLong = refuseByDuration(durationSeconds(probe.info), MAX_VIDEO_INGEST_SECONDS);
|
|
43086
|
+
if (tooLong) {
|
|
43087
|
+
throw new ApiError("VALIDATION_ERROR", tooLong);
|
|
43088
|
+
}
|
|
43089
|
+
const externalId = args.externalId ?? dedupeKey(args, probe.info);
|
|
43090
|
+
const { filePath, tier } = await runYtDlp({
|
|
43091
|
+
url: args.url,
|
|
43092
|
+
audioOnly: false,
|
|
43093
|
+
workDir,
|
|
43094
|
+
maxHeight: INGEST_MAX_HEIGHT,
|
|
43095
|
+
country,
|
|
43096
|
+
vimeoCookie,
|
|
43097
|
+
// Budgeted from the probed length, so the duration we accept and the time
|
|
43098
|
+
// we allow to fetch it cannot drift apart.
|
|
43099
|
+
timeoutMs: downloadTimeoutMs(durationSeconds(probe.info))
|
|
43100
|
+
});
|
|
43101
|
+
const stats = await stat7(filePath);
|
|
43102
|
+
if (stats.size > MAX_VIDEO_INGEST_BYTES) {
|
|
43103
|
+
throw new ApiError(
|
|
43104
|
+
"VALIDATION_ERROR",
|
|
43105
|
+
`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.`
|
|
43106
|
+
);
|
|
43107
|
+
}
|
|
43108
|
+
const publicUrl = await uploadToAssetStore(filePath, stats.size);
|
|
43109
|
+
return {
|
|
43110
|
+
...await ingestUrl({
|
|
43111
|
+
...args,
|
|
43112
|
+
url: publicUrl,
|
|
43113
|
+
externalId,
|
|
43114
|
+
externalUrl: args.externalUrl ?? canonicalUrl(probe.info) ?? args.url
|
|
43115
|
+
}),
|
|
43116
|
+
egress: tier,
|
|
43117
|
+
// Named in the result for the same reason `egress` is: a geo-targeted exit
|
|
43118
|
+
// is a deliberate, billable choice and must not be invisible afterwards.
|
|
43119
|
+
...country ? { country } : {}
|
|
43120
|
+
};
|
|
43121
|
+
} finally {
|
|
43122
|
+
await rm7(workDir, { recursive: true, force: true }).catch(() => {
|
|
43123
|
+
});
|
|
43124
|
+
}
|
|
43125
|
+
}
|
|
43126
|
+
async function uploadToAssetStore(filePath, sizeBytes) {
|
|
43127
|
+
const sha256 = await hashFile(filePath);
|
|
43128
|
+
const { putUrl, publicUrl } = await apiPost("/api/canvas/assets/presign", {
|
|
43129
|
+
sha256,
|
|
43130
|
+
mime: "video/mp4",
|
|
43131
|
+
purpose: "source"
|
|
43132
|
+
});
|
|
43133
|
+
const putRes = await putFileStreamed({ putUrl, filePath, contentType: "video/mp4", sizeBytes });
|
|
43134
|
+
if (!putRes.ok) {
|
|
43135
|
+
throw new ApiError("INTERNAL_ERROR", `Storing the downloaded video failed: HTTP ${putRes.status}`);
|
|
43136
|
+
}
|
|
43137
|
+
return publicUrl;
|
|
43138
|
+
}
|
|
43139
|
+
|
|
43140
|
+
// src/commands/videos/search.ts
|
|
43141
|
+
import { defineCommand as defineCommand201 } from "citty";
|
|
42678
43142
|
registerSchema({
|
|
42679
43143
|
command: "videos.search",
|
|
42680
43144
|
description: "Search videos by text query. Only returns ready videos.",
|
|
@@ -42684,7 +43148,7 @@ registerSchema({
|
|
|
42684
43148
|
tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
|
|
42685
43149
|
}
|
|
42686
43150
|
});
|
|
42687
|
-
var searchCommand4 =
|
|
43151
|
+
var searchCommand4 = defineCommand201({
|
|
42688
43152
|
meta: {
|
|
42689
43153
|
name: "search",
|
|
42690
43154
|
description: "Semantic search videos by text query. Uses hybrid BM25 + vector + reranking. Example: baker videos search 'product demo' --tags tutorial"
|
|
@@ -42734,9 +43198,9 @@ var searchCommand4 = defineCommand200({
|
|
|
42734
43198
|
var tagsCommand5 = makeTagsCommand("videos", "video", "/api/videos/tags");
|
|
42735
43199
|
|
|
42736
43200
|
// src/commands/videos/upload.ts
|
|
42737
|
-
import { readFile as readFile24, stat as
|
|
43201
|
+
import { readFile as readFile24, stat as stat8 } from "fs/promises";
|
|
42738
43202
|
import { basename as basename3, extname as extname4 } from "path";
|
|
42739
|
-
import { defineCommand as
|
|
43203
|
+
import { defineCommand as defineCommand202 } from "citty";
|
|
42740
43204
|
var MIME_MAP = {
|
|
42741
43205
|
".mp4": "video/mp4",
|
|
42742
43206
|
".mov": "video/quicktime",
|
|
@@ -42746,9 +43210,9 @@ var MIME_MAP = {
|
|
|
42746
43210
|
};
|
|
42747
43211
|
registerSchema({
|
|
42748
43212
|
command: "videos.upload",
|
|
42749
|
-
description: "Upload a video
|
|
43213
|
+
description: "Upload a video to Baker \u2014 a local file (via Mux direct upload) or a remote http(s) URL.",
|
|
42750
43214
|
args: {
|
|
42751
|
-
file: { type: "string", description: "
|
|
43215
|
+
file: { type: "string", description: "Local video file path, or a remote http(s) URL", required: true },
|
|
42752
43216
|
"content-type": {
|
|
42753
43217
|
type: "string",
|
|
42754
43218
|
description: "MIME type (auto-detected from extension if omitted)",
|
|
@@ -42775,33 +43239,50 @@ function detectContentType(filePath) {
|
|
|
42775
43239
|
}
|
|
42776
43240
|
return mime;
|
|
42777
43241
|
}
|
|
42778
|
-
|
|
43242
|
+
function isRemoteUrl3(value) {
|
|
43243
|
+
return /^https?:\/\//i.test(value);
|
|
43244
|
+
}
|
|
43245
|
+
var uploadCommand2 = defineCommand202({
|
|
42779
43246
|
meta: {
|
|
42780
43247
|
name: "upload",
|
|
42781
|
-
description: "Upload a video
|
|
43248
|
+
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
43249
|
},
|
|
42783
43250
|
args: {
|
|
42784
|
-
file: { type: "positional", description: "
|
|
42785
|
-
"content-type": { type: "string", description: "MIME type (auto-detected
|
|
43251
|
+
file: { type: "positional", description: "Local video file path or remote http(s) URL", required: false },
|
|
43252
|
+
"content-type": { type: "string", description: "MIME type (local only \u2014 auto-detected)", required: false },
|
|
42786
43253
|
context: {
|
|
42787
43254
|
type: "string",
|
|
42788
43255
|
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
43256
|
required: false
|
|
42790
43257
|
},
|
|
43258
|
+
source: { type: "string", description: "Provenance (URL mode only): url | youtube", required: false },
|
|
43259
|
+
"external-id": { type: "string", description: "Provider asset id (URL mode only)", required: false },
|
|
43260
|
+
"external-url": { type: "string", description: "Canonical page URL (URL mode only)", required: false },
|
|
42791
43261
|
"dry-run": { type: "boolean", description: "Preview without executing", required: false, default: false }
|
|
42792
43262
|
},
|
|
42793
43263
|
run: async ({ args }) => {
|
|
42794
43264
|
try {
|
|
42795
43265
|
const filePath = args.file;
|
|
42796
43266
|
if (!filePath) {
|
|
42797
|
-
writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "File path is required" } });
|
|
43267
|
+
writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message: "File path or URL is required" } });
|
|
42798
43268
|
process.exit(1);
|
|
42799
43269
|
}
|
|
43270
|
+
if (isRemoteUrl3(filePath)) {
|
|
43271
|
+
await runVideoIngest({
|
|
43272
|
+
url: filePath,
|
|
43273
|
+
source: args.source,
|
|
43274
|
+
externalId: args["external-id"],
|
|
43275
|
+
externalUrl: args["external-url"],
|
|
43276
|
+
context: args.context,
|
|
43277
|
+
dryRun: args["dry-run"] === true
|
|
43278
|
+
});
|
|
43279
|
+
return;
|
|
43280
|
+
}
|
|
42800
43281
|
const contentType = args["content-type"] || detectContentType(filePath);
|
|
42801
43282
|
const originalFilename = basename3(filePath);
|
|
42802
43283
|
const descriptionContext = args.context;
|
|
42803
43284
|
if (args["dry-run"]) {
|
|
42804
|
-
const fileStats = await
|
|
43285
|
+
const fileStats = await stat8(filePath);
|
|
42805
43286
|
writeJson({
|
|
42806
43287
|
ok: true,
|
|
42807
43288
|
dryRun: true,
|
|
@@ -42845,16 +43326,17 @@ var uploadCommand2 = defineCommand201({
|
|
|
42845
43326
|
});
|
|
42846
43327
|
|
|
42847
43328
|
// src/commands/videos/index.ts
|
|
42848
|
-
var videosCommand =
|
|
43329
|
+
var videosCommand = defineCommand203({
|
|
42849
43330
|
meta: {
|
|
42850
43331
|
name: "videos",
|
|
42851
|
-
description: `Find and manage videos in Baker. Subcommands: search, get, upload, delete, tags.
|
|
43332
|
+
description: `Find and manage videos in Baker. Subcommands: search, get, upload, ingest, delete, tags.
|
|
42852
43333
|
|
|
42853
43334
|
Examples:
|
|
42854
43335
|
baker videos search "product demo" --limit 5
|
|
42855
43336
|
baker videos search "tutorial" --tags explainer
|
|
42856
43337
|
baker videos get <video-id>
|
|
42857
43338
|
baker videos upload ./demo.mp4
|
|
43339
|
+
baker videos ingest https://www.youtube.com/watch?v=abc123
|
|
42858
43340
|
baker videos delete <video-id> --dry-run
|
|
42859
43341
|
baker videos tags
|
|
42860
43342
|
Full guide: __tooling__/docs/tools/baker/videos.md`
|
|
@@ -42864,16 +43346,17 @@ Full guide: __tooling__/docs/tools/baker/videos.md`
|
|
|
42864
43346
|
group: groupCommand2,
|
|
42865
43347
|
search: searchCommand4,
|
|
42866
43348
|
upload: uploadCommand2,
|
|
43349
|
+
ingest: ingestCommand2,
|
|
42867
43350
|
delete: deleteCommand3,
|
|
42868
43351
|
tags: tagsCommand5
|
|
42869
43352
|
}
|
|
42870
43353
|
});
|
|
42871
43354
|
|
|
42872
43355
|
// src/commands/winning-ads/index.ts
|
|
42873
|
-
import { defineCommand as
|
|
43356
|
+
import { defineCommand as defineCommand216 } from "citty";
|
|
42874
43357
|
|
|
42875
43358
|
// src/commands/winning-ads/advertisers.ts
|
|
42876
|
-
import { defineCommand as
|
|
43359
|
+
import { defineCommand as defineCommand204 } from "citty";
|
|
42877
43360
|
|
|
42878
43361
|
// src/commands/winning-ads/shared.ts
|
|
42879
43362
|
function splitList2(value) {
|
|
@@ -42926,7 +43409,7 @@ function advertiserNormalizer(record, full) {
|
|
|
42926
43409
|
last_synced_at: record.last_synced_at ?? null
|
|
42927
43410
|
};
|
|
42928
43411
|
}
|
|
42929
|
-
var advertisersCommand2 =
|
|
43412
|
+
var advertisersCommand2 = defineCommand204({
|
|
42930
43413
|
meta: {
|
|
42931
43414
|
name: "advertisers",
|
|
42932
43415
|
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 +43467,7 @@ var advertisersCommand2 = defineCommand203({
|
|
|
42984
43467
|
});
|
|
42985
43468
|
|
|
42986
43469
|
// src/commands/winning-ads/brief.ts
|
|
42987
|
-
import { defineCommand as
|
|
43470
|
+
import { defineCommand as defineCommand205 } from "citty";
|
|
42988
43471
|
registerSchema({
|
|
42989
43472
|
command: "winning-ads.brief",
|
|
42990
43473
|
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 +43513,7 @@ function parseDna(raw) {
|
|
|
43030
43513
|
}
|
|
43031
43514
|
return parsed;
|
|
43032
43515
|
}
|
|
43033
|
-
var briefCommand =
|
|
43516
|
+
var briefCommand = defineCommand205({
|
|
43034
43517
|
meta: {
|
|
43035
43518
|
name: "brief",
|
|
43036
43519
|
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 +43549,7 @@ var briefCommand = defineCommand204({
|
|
|
43066
43549
|
});
|
|
43067
43550
|
|
|
43068
43551
|
// src/commands/winning-ads/content.ts
|
|
43069
|
-
import { defineCommand as
|
|
43552
|
+
import { defineCommand as defineCommand206 } from "citty";
|
|
43070
43553
|
registerSchema({
|
|
43071
43554
|
command: "winning-ads.content",
|
|
43072
43555
|
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 +43562,7 @@ registerSchema({
|
|
|
43079
43562
|
}
|
|
43080
43563
|
}
|
|
43081
43564
|
});
|
|
43082
|
-
var contentCommand =
|
|
43565
|
+
var contentCommand = defineCommand206({
|
|
43083
43566
|
meta: {
|
|
43084
43567
|
name: "content",
|
|
43085
43568
|
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 +43611,7 @@ var contentCommand = defineCommand205({
|
|
|
43128
43611
|
});
|
|
43129
43612
|
|
|
43130
43613
|
// src/commands/winning-ads/feed.ts
|
|
43131
|
-
import { defineCommand as
|
|
43614
|
+
import { defineCommand as defineCommand207 } from "citty";
|
|
43132
43615
|
function buildFeedParams(input) {
|
|
43133
43616
|
const params = {};
|
|
43134
43617
|
const advertiser = splitList2(input.advertiser);
|
|
@@ -43180,7 +43663,7 @@ registerSchema({
|
|
|
43180
43663
|
format: { type: "string", description: "Comma-separated formats to include (e.g. static,video)", required: false }
|
|
43181
43664
|
}
|
|
43182
43665
|
});
|
|
43183
|
-
var feedCommand =
|
|
43666
|
+
var feedCommand = defineCommand207({
|
|
43184
43667
|
meta: {
|
|
43185
43668
|
name: "feed",
|
|
43186
43669
|
description: "Winners across every brand you follow (browse, then trim per advertiser). Example: baker winning-ads feed --per-advertiser 5 --output md"
|
|
@@ -43265,7 +43748,7 @@ var feedCommand = defineCommand206({
|
|
|
43265
43748
|
});
|
|
43266
43749
|
|
|
43267
43750
|
// src/commands/winning-ads/follow.ts
|
|
43268
|
-
import { defineCommand as
|
|
43751
|
+
import { defineCommand as defineCommand208 } from "citty";
|
|
43269
43752
|
var PLATFORMS = ["meta", "linkedin"];
|
|
43270
43753
|
registerSchema({
|
|
43271
43754
|
command: "winning-ads.follow",
|
|
@@ -43280,7 +43763,7 @@ registerSchema({
|
|
|
43280
43763
|
label: { type: "string", description: "Optional display label (defaults to the resolved name)", required: false }
|
|
43281
43764
|
}
|
|
43282
43765
|
});
|
|
43283
|
-
var followCommand =
|
|
43766
|
+
var followCommand = defineCommand208({
|
|
43284
43767
|
meta: {
|
|
43285
43768
|
name: "follow",
|
|
43286
43769
|
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 +43810,7 @@ var followCommand = defineCommand207({
|
|
|
43327
43810
|
});
|
|
43328
43811
|
|
|
43329
43812
|
// src/commands/winning-ads/follow-competitors.ts
|
|
43330
|
-
import { defineCommand as
|
|
43813
|
+
import { defineCommand as defineCommand209 } from "citty";
|
|
43331
43814
|
var PLATFORMS2 = ["meta", "linkedin"];
|
|
43332
43815
|
var BATCH_TIMEOUT_MS = 3e5;
|
|
43333
43816
|
function buildFollowBatchBody(input) {
|
|
@@ -43360,7 +43843,7 @@ registerSchema({
|
|
|
43360
43843
|
}
|
|
43361
43844
|
}
|
|
43362
43845
|
});
|
|
43363
|
-
var followCompetitorsCommand =
|
|
43846
|
+
var followCompetitorsCommand = defineCommand209({
|
|
43364
43847
|
meta: {
|
|
43365
43848
|
name: "follow-competitors",
|
|
43366
43849
|
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 +43918,7 @@ var followCompetitorsCommand = defineCommand208({
|
|
|
43435
43918
|
});
|
|
43436
43919
|
|
|
43437
43920
|
// src/commands/winning-ads/following.ts
|
|
43438
|
-
import { defineCommand as
|
|
43921
|
+
import { defineCommand as defineCommand210 } from "citty";
|
|
43439
43922
|
registerSchema({
|
|
43440
43923
|
command: "winning-ads.following",
|
|
43441
43924
|
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 +43951,7 @@ function followingNormalizer(record, full) {
|
|
|
43468
43951
|
platforms: Array.isArray(record.platforms) ? record.platforms : []
|
|
43469
43952
|
};
|
|
43470
43953
|
}
|
|
43471
|
-
var followingCommand =
|
|
43954
|
+
var followingCommand = defineCommand210({
|
|
43472
43955
|
meta: {
|
|
43473
43956
|
name: "following",
|
|
43474
43957
|
description: "List brands you follow, with status (ready / adding\u2026) and cached counts. Example: baker winning-ads following --output md"
|
|
@@ -43503,7 +43986,7 @@ var followingCommand = defineCommand209({
|
|
|
43503
43986
|
});
|
|
43504
43987
|
|
|
43505
43988
|
// src/commands/winning-ads/patterns.ts
|
|
43506
|
-
import { defineCommand as
|
|
43989
|
+
import { defineCommand as defineCommand211 } from "citty";
|
|
43507
43990
|
registerSchema({
|
|
43508
43991
|
command: "winning-ads.patterns",
|
|
43509
43992
|
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 +44025,7 @@ function discriminatorRow(record) {
|
|
|
43542
44025
|
top_values_duds: Array.isArray(record.top_values_b) ? record.top_values_b.join(", ") : ""
|
|
43543
44026
|
};
|
|
43544
44027
|
}
|
|
43545
|
-
var patternsCommand =
|
|
44028
|
+
var patternsCommand = defineCommand211({
|
|
43546
44029
|
meta: {
|
|
43547
44030
|
name: "patterns",
|
|
43548
44031
|
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 +44081,7 @@ var patternsCommand = defineCommand210({
|
|
|
43598
44081
|
});
|
|
43599
44082
|
|
|
43600
44083
|
// src/commands/winning-ads/search.ts
|
|
43601
|
-
import { defineCommand as
|
|
44084
|
+
import { defineCommand as defineCommand212 } from "citty";
|
|
43602
44085
|
registerSchema({
|
|
43603
44086
|
command: "winning-ads.search",
|
|
43604
44087
|
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 +44189,7 @@ function buildSearchBody2(args) {
|
|
|
43706
44189
|
}
|
|
43707
44190
|
return body;
|
|
43708
44191
|
}
|
|
43709
|
-
var searchCommand5 =
|
|
44192
|
+
var searchCommand5 = defineCommand212({
|
|
43710
44193
|
meta: {
|
|
43711
44194
|
name: "search",
|
|
43712
44195
|
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 +44304,7 @@ var searchCommand5 = defineCommand211({
|
|
|
43821
44304
|
});
|
|
43822
44305
|
|
|
43823
44306
|
// src/commands/winning-ads/seeds.ts
|
|
43824
|
-
import { defineCommand as
|
|
44307
|
+
import { defineCommand as defineCommand213 } from "citty";
|
|
43825
44308
|
function leanRow(r) {
|
|
43826
44309
|
return {
|
|
43827
44310
|
key: r.key,
|
|
@@ -43849,7 +44332,7 @@ function makeSeedCommand(opts) {
|
|
|
43849
44332
|
limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
|
|
43850
44333
|
}
|
|
43851
44334
|
});
|
|
43852
|
-
return
|
|
44335
|
+
return defineCommand213({
|
|
43853
44336
|
meta: { name: opts.name, description: opts.description },
|
|
43854
44337
|
args: {
|
|
43855
44338
|
platform: { type: "string", description: "Single platform to segment on", required: false },
|
|
@@ -43898,7 +44381,7 @@ var formatsCommand = makeSeedCommand({
|
|
|
43898
44381
|
});
|
|
43899
44382
|
|
|
43900
44383
|
// src/commands/winning-ads/unfollow.ts
|
|
43901
|
-
import { defineCommand as
|
|
44384
|
+
import { defineCommand as defineCommand214 } from "citty";
|
|
43902
44385
|
registerSchema({
|
|
43903
44386
|
command: "winning-ads.unfollow",
|
|
43904
44387
|
description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
|
|
@@ -43906,7 +44389,7 @@ registerSchema({
|
|
|
43906
44389
|
advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
|
|
43907
44390
|
}
|
|
43908
44391
|
});
|
|
43909
|
-
var unfollowCommand =
|
|
44392
|
+
var unfollowCommand = defineCommand214({
|
|
43910
44393
|
meta: {
|
|
43911
44394
|
name: "unfollow",
|
|
43912
44395
|
description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
|
|
@@ -43927,7 +44410,7 @@ var unfollowCommand = defineCommand213({
|
|
|
43927
44410
|
});
|
|
43928
44411
|
|
|
43929
44412
|
// src/commands/winning-ads/winners.ts
|
|
43930
|
-
import { defineCommand as
|
|
44413
|
+
import { defineCommand as defineCommand215 } from "citty";
|
|
43931
44414
|
registerSchema({
|
|
43932
44415
|
command: "winning-ads.winners",
|
|
43933
44416
|
description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
|
|
@@ -43937,7 +44420,7 @@ registerSchema({
|
|
|
43937
44420
|
platform: { type: "string", description: "Filter to a single platform: meta|linkedin", required: false }
|
|
43938
44421
|
}
|
|
43939
44422
|
});
|
|
43940
|
-
var winnersCommand =
|
|
44423
|
+
var winnersCommand = defineCommand215({
|
|
43941
44424
|
meta: {
|
|
43942
44425
|
name: "winners",
|
|
43943
44426
|
description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
|
|
@@ -43987,7 +44470,7 @@ var winnersCommand = defineCommand214({
|
|
|
43987
44470
|
});
|
|
43988
44471
|
|
|
43989
44472
|
// src/commands/winning-ads/index.ts
|
|
43990
|
-
var winningAdsCommand =
|
|
44473
|
+
var winningAdsCommand = defineCommand216({
|
|
43991
44474
|
meta: {
|
|
43992
44475
|
name: "winning-ads",
|
|
43993
44476
|
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 +44660,7 @@ function unknownFlagEnvelope(unknown, commandPath, suggestion) {
|
|
|
44177
44660
|
};
|
|
44178
44661
|
}
|
|
44179
44662
|
function commandPathOf(root, argv) {
|
|
44180
|
-
const
|
|
44663
|
+
const path38 = [];
|
|
44181
44664
|
let command = root;
|
|
44182
44665
|
for (const token of argv) {
|
|
44183
44666
|
if (token === "--" || token.startsWith("-")) {
|
|
@@ -44188,10 +44671,10 @@ function commandPathOf(root, argv) {
|
|
|
44188
44671
|
if (next === void 0 || typeof next !== "object") {
|
|
44189
44672
|
break;
|
|
44190
44673
|
}
|
|
44191
|
-
|
|
44674
|
+
path38.push(token);
|
|
44192
44675
|
command = next;
|
|
44193
44676
|
}
|
|
44194
|
-
return
|
|
44677
|
+
return path38.join(" ");
|
|
44195
44678
|
}
|
|
44196
44679
|
function refuseUnknownFlags(root, argv) {
|
|
44197
44680
|
const unknown = findUnknownFlags(root, argv);
|
|
@@ -44225,7 +44708,7 @@ function getCliVersion() {
|
|
|
44225
44708
|
}
|
|
44226
44709
|
|
|
44227
44710
|
// src/cli.ts
|
|
44228
|
-
var main =
|
|
44711
|
+
var main = defineCommand217({
|
|
44229
44712
|
meta: {
|
|
44230
44713
|
name: "baker",
|
|
44231
44714
|
version: getCliVersion(),
|