vidpipe 1.3.18 → 1.3.20
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 +3 -0
- package/dist/cli.js +1694 -343
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +864 -221
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/cli.js
CHANGED
|
@@ -698,6 +698,387 @@ var init_configLogger = __esm({
|
|
|
698
698
|
}
|
|
699
699
|
});
|
|
700
700
|
|
|
701
|
+
// src/L0-pure/pipelineSpec/types.ts
|
|
702
|
+
function isPresetName(value) {
|
|
703
|
+
return value === "full" || value === "clean" || value === "minimal";
|
|
704
|
+
}
|
|
705
|
+
var init_types = __esm({
|
|
706
|
+
"src/L0-pure/pipelineSpec/types.ts"() {
|
|
707
|
+
"use strict";
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
// src/L0-pure/pipelineSpec/presets.ts
|
|
712
|
+
function getPreset(name) {
|
|
713
|
+
return PRESETS[name];
|
|
714
|
+
}
|
|
715
|
+
var PRESET_FULL, PRESET_CLEAN, PRESET_MINIMAL, PRESETS;
|
|
716
|
+
var init_presets = __esm({
|
|
717
|
+
"src/L0-pure/pipelineSpec/presets.ts"() {
|
|
718
|
+
"use strict";
|
|
719
|
+
PRESET_FULL = {
|
|
720
|
+
name: "full",
|
|
721
|
+
description: "Full pipeline with hook-first shorts, per-platform optimization",
|
|
722
|
+
processing: {
|
|
723
|
+
silenceRemoval: true,
|
|
724
|
+
visualEnhancement: true,
|
|
725
|
+
captions: true,
|
|
726
|
+
introOutro: true
|
|
727
|
+
},
|
|
728
|
+
clips: {
|
|
729
|
+
shorts: {
|
|
730
|
+
enabled: true,
|
|
731
|
+
strategy: "hook-first",
|
|
732
|
+
duration: { min: 15, max: 60 },
|
|
733
|
+
minViralScore: 8,
|
|
734
|
+
maxClips: 5
|
|
735
|
+
},
|
|
736
|
+
medium: {
|
|
737
|
+
enabled: true,
|
|
738
|
+
strategy: "chronological",
|
|
739
|
+
duration: { min: 60, max: 180 },
|
|
740
|
+
minViralScore: 10,
|
|
741
|
+
maxClips: 5
|
|
742
|
+
}
|
|
743
|
+
},
|
|
744
|
+
content: {
|
|
745
|
+
chapters: true,
|
|
746
|
+
summary: true,
|
|
747
|
+
blog: true
|
|
748
|
+
},
|
|
749
|
+
distribution: {
|
|
750
|
+
enabled: true,
|
|
751
|
+
publish: true,
|
|
752
|
+
platforms: {
|
|
753
|
+
targets: ["youtube", "linkedin", "instagram", "x", "tiktok"],
|
|
754
|
+
toneStrategy: "per-platform",
|
|
755
|
+
variants: true
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
PRESET_CLEAN = {
|
|
760
|
+
name: "clean",
|
|
761
|
+
description: "Simple cleanup with longer clips and unified platform handling",
|
|
762
|
+
processing: {
|
|
763
|
+
silenceRemoval: true,
|
|
764
|
+
visualEnhancement: false,
|
|
765
|
+
captions: true,
|
|
766
|
+
introOutro: true
|
|
767
|
+
},
|
|
768
|
+
clips: {
|
|
769
|
+
shorts: {
|
|
770
|
+
enabled: false
|
|
771
|
+
},
|
|
772
|
+
medium: {
|
|
773
|
+
enabled: true,
|
|
774
|
+
strategy: "chronological",
|
|
775
|
+
duration: { min: 60, max: 600 },
|
|
776
|
+
minViralScore: 6,
|
|
777
|
+
maxClips: 5
|
|
778
|
+
}
|
|
779
|
+
},
|
|
780
|
+
content: {
|
|
781
|
+
chapters: true,
|
|
782
|
+
summary: true,
|
|
783
|
+
blog: true
|
|
784
|
+
},
|
|
785
|
+
distribution: {
|
|
786
|
+
enabled: true,
|
|
787
|
+
publish: true,
|
|
788
|
+
platforms: {
|
|
789
|
+
targets: ["youtube", "linkedin", "instagram", "x", "tiktok"],
|
|
790
|
+
toneStrategy: "unified",
|
|
791
|
+
variants: false
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
PRESET_MINIMAL = {
|
|
796
|
+
name: "minimal",
|
|
797
|
+
description: "Cleanup only \u2014 no clips, no social, no blog",
|
|
798
|
+
processing: {
|
|
799
|
+
silenceRemoval: true,
|
|
800
|
+
visualEnhancement: false,
|
|
801
|
+
captions: true,
|
|
802
|
+
introOutro: false
|
|
803
|
+
},
|
|
804
|
+
clips: {
|
|
805
|
+
shorts: {
|
|
806
|
+
enabled: false
|
|
807
|
+
},
|
|
808
|
+
medium: {
|
|
809
|
+
enabled: false
|
|
810
|
+
}
|
|
811
|
+
},
|
|
812
|
+
content: {
|
|
813
|
+
chapters: true,
|
|
814
|
+
summary: true,
|
|
815
|
+
blog: false
|
|
816
|
+
},
|
|
817
|
+
distribution: {
|
|
818
|
+
enabled: false,
|
|
819
|
+
publish: false,
|
|
820
|
+
platforms: {
|
|
821
|
+
targets: [],
|
|
822
|
+
toneStrategy: "unified",
|
|
823
|
+
variants: false
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
PRESETS = {
|
|
828
|
+
full: PRESET_FULL,
|
|
829
|
+
clean: PRESET_CLEAN,
|
|
830
|
+
minimal: PRESET_MINIMAL
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
// src/L0-pure/pipelineSpec/validation.ts
|
|
836
|
+
function isObject(v) {
|
|
837
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
838
|
+
}
|
|
839
|
+
function validateClipConfig(clip, path, errors) {
|
|
840
|
+
if (!isObject(clip)) return;
|
|
841
|
+
if ("enabled" in clip && typeof clip.enabled !== "boolean") {
|
|
842
|
+
errors.push({ path: `${path}.enabled`, message: "must be a boolean" });
|
|
843
|
+
}
|
|
844
|
+
if ("strategy" in clip && clip.strategy !== void 0) {
|
|
845
|
+
if (!VALID_STRATEGIES.includes(clip.strategy)) {
|
|
846
|
+
errors.push({ path: `${path}.strategy`, message: `must be one of: ${VALID_STRATEGIES.join(", ")}` });
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
if ("duration" in clip && clip.duration !== void 0) {
|
|
850
|
+
const dur = clip.duration;
|
|
851
|
+
if (!isObject(dur)) {
|
|
852
|
+
errors.push({ path: `${path}.duration`, message: "must be an object with min and max" });
|
|
853
|
+
} else {
|
|
854
|
+
if ("min" in dur && (typeof dur.min !== "number" || dur.min < 0)) {
|
|
855
|
+
errors.push({ path: `${path}.duration.min`, message: "must be a non-negative number" });
|
|
856
|
+
}
|
|
857
|
+
if ("max" in dur && (typeof dur.max !== "number" || dur.max < 0)) {
|
|
858
|
+
errors.push({ path: `${path}.duration.max`, message: "must be a non-negative number" });
|
|
859
|
+
}
|
|
860
|
+
if (typeof dur.min === "number" && typeof dur.max === "number" && dur.min > dur.max) {
|
|
861
|
+
errors.push({ path: `${path}.duration`, message: "min must be less than or equal to max" });
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
if ("minViralScore" in clip && clip.minViralScore !== void 0) {
|
|
866
|
+
const score = clip.minViralScore;
|
|
867
|
+
if (typeof score !== "number" || score < 1 || score > 20) {
|
|
868
|
+
errors.push({ path: `${path}.minViralScore`, message: "must be a number between 1 and 20" });
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
if ("maxClips" in clip && clip.maxClips !== void 0) {
|
|
872
|
+
const max = clip.maxClips;
|
|
873
|
+
if (typeof max !== "number" || max < 1 || !Number.isInteger(max)) {
|
|
874
|
+
errors.push({ path: `${path}.maxClips`, message: "must be a positive integer" });
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
function validateProcessing(proc, errors) {
|
|
879
|
+
if (!isObject(proc)) return;
|
|
880
|
+
const boolFields = ["silenceRemoval", "visualEnhancement", "captions", "introOutro"];
|
|
881
|
+
for (const field of boolFields) {
|
|
882
|
+
if (field in proc && typeof proc[field] !== "boolean") {
|
|
883
|
+
errors.push({ path: `processing.${field}`, message: "must be a boolean" });
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
function validateContent(content, errors) {
|
|
888
|
+
if (!isObject(content)) return;
|
|
889
|
+
const boolFields = ["chapters", "summary", "blog"];
|
|
890
|
+
for (const field of boolFields) {
|
|
891
|
+
if (field in content && typeof content[field] !== "boolean") {
|
|
892
|
+
errors.push({ path: `content.${field}`, message: "must be a boolean" });
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
function validateDistribution(dist, errors) {
|
|
897
|
+
if (!isObject(dist)) return;
|
|
898
|
+
if ("enabled" in dist && typeof dist.enabled !== "boolean") {
|
|
899
|
+
errors.push({ path: "distribution.enabled", message: "must be a boolean" });
|
|
900
|
+
}
|
|
901
|
+
if ("publish" in dist && typeof dist.publish !== "boolean") {
|
|
902
|
+
errors.push({ path: "distribution.publish", message: "must be a boolean" });
|
|
903
|
+
}
|
|
904
|
+
if ("platforms" in dist && dist.platforms !== void 0) {
|
|
905
|
+
const plat = dist.platforms;
|
|
906
|
+
if (!isObject(plat)) {
|
|
907
|
+
errors.push({ path: "distribution.platforms", message: "must be an object" });
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
if ("targets" in plat) {
|
|
911
|
+
if (!Array.isArray(plat.targets)) {
|
|
912
|
+
errors.push({ path: "distribution.platforms.targets", message: "must be an array" });
|
|
913
|
+
} else {
|
|
914
|
+
for (const target of plat.targets) {
|
|
915
|
+
if (!VALID_PLATFORMS.includes(target)) {
|
|
916
|
+
errors.push({
|
|
917
|
+
path: "distribution.platforms.targets",
|
|
918
|
+
message: `unknown platform '${String(target)}' \u2014 valid: ${VALID_PLATFORMS.join(", ")}`
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
if ("toneStrategy" in plat && plat.toneStrategy !== void 0) {
|
|
925
|
+
if (!VALID_TONE_STRATEGIES.includes(plat.toneStrategy)) {
|
|
926
|
+
errors.push({
|
|
927
|
+
path: "distribution.platforms.toneStrategy",
|
|
928
|
+
message: `must be one of: ${VALID_TONE_STRATEGIES.join(", ")}`
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
if ("variants" in plat && typeof plat.variants !== "boolean") {
|
|
933
|
+
errors.push({ path: "distribution.platforms.variants", message: "must be a boolean" });
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
function validateSpec(raw) {
|
|
938
|
+
const errors = [];
|
|
939
|
+
if (!isObject(raw)) {
|
|
940
|
+
errors.push({ path: "", message: "spec must be an object" });
|
|
941
|
+
return errors;
|
|
942
|
+
}
|
|
943
|
+
const spec = raw;
|
|
944
|
+
if ("name" in spec && spec.name !== void 0 && typeof spec.name !== "string") {
|
|
945
|
+
errors.push({ path: "name", message: "must be a string" });
|
|
946
|
+
}
|
|
947
|
+
if ("description" in spec && spec.description !== void 0 && typeof spec.description !== "string") {
|
|
948
|
+
errors.push({ path: "description", message: "must be a string" });
|
|
949
|
+
}
|
|
950
|
+
if ("processing" in spec) validateProcessing(spec.processing, errors);
|
|
951
|
+
if ("clips" in spec && spec.clips !== void 0) {
|
|
952
|
+
if (!isObject(spec.clips)) {
|
|
953
|
+
errors.push({ path: "clips", message: "must be an object" });
|
|
954
|
+
} else {
|
|
955
|
+
if ("shorts" in spec.clips) validateClipConfig(spec.clips.shorts, "clips.shorts", errors);
|
|
956
|
+
if ("medium" in spec.clips) validateClipConfig(spec.clips.medium, "clips.medium", errors);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
if ("content" in spec) validateContent(spec.content, errors);
|
|
960
|
+
if ("distribution" in spec) validateDistribution(spec.distribution, errors);
|
|
961
|
+
return errors;
|
|
962
|
+
}
|
|
963
|
+
var VALID_STRATEGIES, VALID_TONE_STRATEGIES, VALID_PLATFORMS;
|
|
964
|
+
var init_validation = __esm({
|
|
965
|
+
"src/L0-pure/pipelineSpec/validation.ts"() {
|
|
966
|
+
"use strict";
|
|
967
|
+
VALID_STRATEGIES = ["hook-first", "chronological"];
|
|
968
|
+
VALID_TONE_STRATEGIES = ["unified", "per-platform"];
|
|
969
|
+
VALID_PLATFORMS = ["youtube", "linkedin", "instagram", "x", "tiktok"];
|
|
970
|
+
}
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
// src/L0-pure/pipelineSpec/merger.ts
|
|
974
|
+
function mergeDuration(base, override) {
|
|
975
|
+
if (!override) return base;
|
|
976
|
+
if (!base) return override;
|
|
977
|
+
return {
|
|
978
|
+
min: override.min ?? base.min,
|
|
979
|
+
max: override.max ?? base.max
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
function mergeClipConfig(base, override) {
|
|
983
|
+
if (!override) return base;
|
|
984
|
+
return {
|
|
985
|
+
enabled: override.enabled ?? base.enabled,
|
|
986
|
+
strategy: override.strategy ?? base.strategy,
|
|
987
|
+
duration: mergeDuration(base.duration, override.duration),
|
|
988
|
+
minViralScore: override.minViralScore ?? base.minViralScore,
|
|
989
|
+
maxClips: override.maxClips ?? base.maxClips
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
function mergeWithDefaults(partial, base = PRESET_FULL) {
|
|
993
|
+
return {
|
|
994
|
+
name: partial.name ?? base.name,
|
|
995
|
+
description: partial.description ?? base.description,
|
|
996
|
+
processing: {
|
|
997
|
+
silenceRemoval: partial.processing?.silenceRemoval ?? base.processing.silenceRemoval,
|
|
998
|
+
visualEnhancement: partial.processing?.visualEnhancement ?? base.processing.visualEnhancement,
|
|
999
|
+
captions: partial.processing?.captions ?? base.processing.captions,
|
|
1000
|
+
introOutro: partial.processing?.introOutro ?? base.processing.introOutro
|
|
1001
|
+
},
|
|
1002
|
+
clips: {
|
|
1003
|
+
shorts: mergeClipConfig(base.clips.shorts, partial.clips?.shorts),
|
|
1004
|
+
medium: mergeClipConfig(base.clips.medium, partial.clips?.medium)
|
|
1005
|
+
},
|
|
1006
|
+
content: {
|
|
1007
|
+
chapters: partial.content?.chapters ?? base.content.chapters,
|
|
1008
|
+
summary: partial.content?.summary ?? base.content.summary,
|
|
1009
|
+
blog: partial.content?.blog ?? base.content.blog
|
|
1010
|
+
},
|
|
1011
|
+
distribution: {
|
|
1012
|
+
enabled: partial.distribution?.enabled ?? base.distribution.enabled,
|
|
1013
|
+
publish: partial.distribution?.publish ?? base.distribution.publish,
|
|
1014
|
+
platforms: {
|
|
1015
|
+
targets: partial.distribution?.platforms?.targets ?? base.distribution.platforms.targets,
|
|
1016
|
+
toneStrategy: partial.distribution?.platforms?.toneStrategy ?? base.distribution.platforms.toneStrategy,
|
|
1017
|
+
variants: partial.distribution?.platforms?.variants ?? base.distribution.platforms.variants
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
function applySkipFlags(spec, flags) {
|
|
1023
|
+
return {
|
|
1024
|
+
...spec,
|
|
1025
|
+
processing: {
|
|
1026
|
+
silenceRemoval: spec.processing.silenceRemoval && !flags.SKIP_SILENCE_REMOVAL,
|
|
1027
|
+
visualEnhancement: spec.processing.visualEnhancement && !flags.SKIP_VISUAL_ENHANCEMENT,
|
|
1028
|
+
captions: spec.processing.captions && !flags.SKIP_CAPTIONS,
|
|
1029
|
+
introOutro: spec.processing.introOutro && !flags.SKIP_INTRO_OUTRO
|
|
1030
|
+
},
|
|
1031
|
+
clips: {
|
|
1032
|
+
shorts: {
|
|
1033
|
+
...spec.clips.shorts,
|
|
1034
|
+
enabled: spec.clips.shorts.enabled && !flags.SKIP_SHORTS
|
|
1035
|
+
},
|
|
1036
|
+
medium: {
|
|
1037
|
+
...spec.clips.medium,
|
|
1038
|
+
enabled: spec.clips.medium.enabled && !flags.SKIP_MEDIUM_CLIPS
|
|
1039
|
+
}
|
|
1040
|
+
},
|
|
1041
|
+
distribution: {
|
|
1042
|
+
...spec.distribution,
|
|
1043
|
+
enabled: spec.distribution.enabled && !flags.SKIP_SOCIAL,
|
|
1044
|
+
publish: spec.distribution.publish && !flags.SKIP_SOCIAL_PUBLISH
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
function resolveFromFlags(flags) {
|
|
1049
|
+
return applySkipFlags(PRESET_FULL, flags);
|
|
1050
|
+
}
|
|
1051
|
+
var init_merger = __esm({
|
|
1052
|
+
"src/L0-pure/pipelineSpec/merger.ts"() {
|
|
1053
|
+
"use strict";
|
|
1054
|
+
init_presets();
|
|
1055
|
+
}
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
// src/L0-pure/pipelineSpec/index.ts
|
|
1059
|
+
var pipelineSpec_exports = {};
|
|
1060
|
+
__export(pipelineSpec_exports, {
|
|
1061
|
+
PRESETS: () => PRESETS,
|
|
1062
|
+
PRESET_CLEAN: () => PRESET_CLEAN,
|
|
1063
|
+
PRESET_FULL: () => PRESET_FULL,
|
|
1064
|
+
PRESET_MINIMAL: () => PRESET_MINIMAL,
|
|
1065
|
+
applySkipFlags: () => applySkipFlags,
|
|
1066
|
+
getPreset: () => getPreset,
|
|
1067
|
+
isPresetName: () => isPresetName,
|
|
1068
|
+
mergeWithDefaults: () => mergeWithDefaults,
|
|
1069
|
+
resolveFromFlags: () => resolveFromFlags,
|
|
1070
|
+
validateSpec: () => validateSpec
|
|
1071
|
+
});
|
|
1072
|
+
var init_pipelineSpec = __esm({
|
|
1073
|
+
"src/L0-pure/pipelineSpec/index.ts"() {
|
|
1074
|
+
"use strict";
|
|
1075
|
+
init_types();
|
|
1076
|
+
init_presets();
|
|
1077
|
+
init_validation();
|
|
1078
|
+
init_merger();
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
|
|
701
1082
|
// src/L0-pure/types/index.ts
|
|
702
1083
|
function getStageInfo(stage) {
|
|
703
1084
|
const info = PIPELINE_STAGES.find((s) => s.stage === stage);
|
|
@@ -729,9 +1110,10 @@ function isSupportedVideoExtension(ext) {
|
|
|
729
1110
|
return SUPPORTED_VIDEO_EXTENSIONS.includes(ext.toLowerCase());
|
|
730
1111
|
}
|
|
731
1112
|
var Platform, PIPELINE_STAGES, TOTAL_STAGES, PLATFORM_CHAR_LIMITS, SUPPORTED_VIDEO_EXTENSIONS;
|
|
732
|
-
var
|
|
1113
|
+
var init_types2 = __esm({
|
|
733
1114
|
"src/L0-pure/types/index.ts"() {
|
|
734
1115
|
"use strict";
|
|
1116
|
+
init_pipelineSpec();
|
|
735
1117
|
Platform = /* @__PURE__ */ ((Platform2) => {
|
|
736
1118
|
Platform2["TikTok"] = "tiktok";
|
|
737
1119
|
Platform2["YouTube"] = "youtube";
|
|
@@ -5837,23 +6219,63 @@ function createSessionRpc(connection, sessionId) {
|
|
|
5837
6219
|
readFile: async (params) => connection.sendRequest("session.workspace.readFile", { sessionId, ...params }),
|
|
5838
6220
|
createFile: async (params) => connection.sendRequest("session.workspace.createFile", { sessionId, ...params })
|
|
5839
6221
|
},
|
|
6222
|
+
/** @experimental */
|
|
5840
6223
|
fleet: {
|
|
5841
6224
|
start: async (params) => connection.sendRequest("session.fleet.start", { sessionId, ...params })
|
|
5842
6225
|
},
|
|
6226
|
+
/** @experimental */
|
|
5843
6227
|
agent: {
|
|
5844
6228
|
list: async () => connection.sendRequest("session.agent.list", { sessionId }),
|
|
5845
6229
|
getCurrent: async () => connection.sendRequest("session.agent.getCurrent", { sessionId }),
|
|
5846
6230
|
select: async (params) => connection.sendRequest("session.agent.select", { sessionId, ...params }),
|
|
5847
|
-
deselect: async () => connection.sendRequest("session.agent.deselect", { sessionId })
|
|
6231
|
+
deselect: async () => connection.sendRequest("session.agent.deselect", { sessionId }),
|
|
6232
|
+
reload: async () => connection.sendRequest("session.agent.reload", { sessionId })
|
|
6233
|
+
},
|
|
6234
|
+
/** @experimental */
|
|
6235
|
+
skills: {
|
|
6236
|
+
list: async () => connection.sendRequest("session.skills.list", { sessionId }),
|
|
6237
|
+
enable: async (params) => connection.sendRequest("session.skills.enable", { sessionId, ...params }),
|
|
6238
|
+
disable: async (params) => connection.sendRequest("session.skills.disable", { sessionId, ...params }),
|
|
6239
|
+
reload: async () => connection.sendRequest("session.skills.reload", { sessionId })
|
|
6240
|
+
},
|
|
6241
|
+
/** @experimental */
|
|
6242
|
+
mcp: {
|
|
6243
|
+
list: async () => connection.sendRequest("session.mcp.list", { sessionId }),
|
|
6244
|
+
enable: async (params) => connection.sendRequest("session.mcp.enable", { sessionId, ...params }),
|
|
6245
|
+
disable: async (params) => connection.sendRequest("session.mcp.disable", { sessionId, ...params }),
|
|
6246
|
+
reload: async () => connection.sendRequest("session.mcp.reload", { sessionId })
|
|
6247
|
+
},
|
|
6248
|
+
/** @experimental */
|
|
6249
|
+
plugins: {
|
|
6250
|
+
list: async () => connection.sendRequest("session.plugins.list", { sessionId })
|
|
5848
6251
|
},
|
|
6252
|
+
/** @experimental */
|
|
6253
|
+
extensions: {
|
|
6254
|
+
list: async () => connection.sendRequest("session.extensions.list", { sessionId }),
|
|
6255
|
+
enable: async (params) => connection.sendRequest("session.extensions.enable", { sessionId, ...params }),
|
|
6256
|
+
disable: async (params) => connection.sendRequest("session.extensions.disable", { sessionId, ...params }),
|
|
6257
|
+
reload: async () => connection.sendRequest("session.extensions.reload", { sessionId })
|
|
6258
|
+
},
|
|
6259
|
+
/** @experimental */
|
|
5849
6260
|
compaction: {
|
|
5850
6261
|
compact: async () => connection.sendRequest("session.compaction.compact", { sessionId })
|
|
5851
6262
|
},
|
|
5852
6263
|
tools: {
|
|
5853
6264
|
handlePendingToolCall: async (params) => connection.sendRequest("session.tools.handlePendingToolCall", { sessionId, ...params })
|
|
5854
6265
|
},
|
|
6266
|
+
commands: {
|
|
6267
|
+
handlePendingCommand: async (params) => connection.sendRequest("session.commands.handlePendingCommand", { sessionId, ...params })
|
|
6268
|
+
},
|
|
6269
|
+
ui: {
|
|
6270
|
+
elicitation: async (params) => connection.sendRequest("session.ui.elicitation", { sessionId, ...params })
|
|
6271
|
+
},
|
|
5855
6272
|
permissions: {
|
|
5856
6273
|
handlePendingPermissionRequest: async (params) => connection.sendRequest("session.permissions.handlePendingPermissionRequest", { sessionId, ...params })
|
|
6274
|
+
},
|
|
6275
|
+
log: async (params) => connection.sendRequest("session.log", { sessionId, ...params }),
|
|
6276
|
+
shell: {
|
|
6277
|
+
exec: async (params) => connection.sendRequest("session.shell.exec", { sessionId, ...params }),
|
|
6278
|
+
kill: async (params) => connection.sendRequest("session.shell.kill", { sessionId, ...params })
|
|
5857
6279
|
}
|
|
5858
6280
|
};
|
|
5859
6281
|
}
|
|
@@ -5875,13 +6297,30 @@ var init_sdkProtocolVersion = __esm({
|
|
|
5875
6297
|
}
|
|
5876
6298
|
});
|
|
5877
6299
|
|
|
6300
|
+
// node_modules/@github/copilot-sdk/dist/telemetry.js
|
|
6301
|
+
async function getTraceContext(provider) {
|
|
6302
|
+
if (!provider) return {};
|
|
6303
|
+
try {
|
|
6304
|
+
return await provider() ?? {};
|
|
6305
|
+
} catch {
|
|
6306
|
+
return {};
|
|
6307
|
+
}
|
|
6308
|
+
}
|
|
6309
|
+
var init_telemetry = __esm({
|
|
6310
|
+
"node_modules/@github/copilot-sdk/dist/telemetry.js"() {
|
|
6311
|
+
"use strict";
|
|
6312
|
+
}
|
|
6313
|
+
});
|
|
6314
|
+
|
|
5878
6315
|
// node_modules/@github/copilot-sdk/dist/session.js
|
|
5879
|
-
var import_node, CopilotSession;
|
|
6316
|
+
var import_node, NO_RESULT_PERMISSION_V2_ERROR, CopilotSession;
|
|
5880
6317
|
var init_session = __esm({
|
|
5881
6318
|
"node_modules/@github/copilot-sdk/dist/session.js"() {
|
|
5882
6319
|
"use strict";
|
|
5883
6320
|
import_node = __toESM(require_node(), 1);
|
|
5884
6321
|
init_rpc();
|
|
6322
|
+
init_telemetry();
|
|
6323
|
+
NO_RESULT_PERMISSION_V2_ERROR = "Permission handlers cannot return 'no-result' when connected to a protocol v2 server.";
|
|
5885
6324
|
CopilotSession = class {
|
|
5886
6325
|
/**
|
|
5887
6326
|
* Creates a new CopilotSession instance.
|
|
@@ -5889,12 +6328,14 @@ var init_session = __esm({
|
|
|
5889
6328
|
* @param sessionId - The unique identifier for this session
|
|
5890
6329
|
* @param connection - The JSON-RPC message connection to the Copilot CLI
|
|
5891
6330
|
* @param workspacePath - Path to the session workspace directory (when infinite sessions enabled)
|
|
6331
|
+
* @param traceContextProvider - Optional callback to get W3C Trace Context for outbound RPCs
|
|
5892
6332
|
* @internal This constructor is internal. Use {@link CopilotClient.createSession} to create sessions.
|
|
5893
6333
|
*/
|
|
5894
|
-
constructor(sessionId, connection, _workspacePath) {
|
|
6334
|
+
constructor(sessionId, connection, _workspacePath, traceContextProvider) {
|
|
5895
6335
|
this.sessionId = sessionId;
|
|
5896
6336
|
this.connection = connection;
|
|
5897
6337
|
this._workspacePath = _workspacePath;
|
|
6338
|
+
this.traceContextProvider = traceContextProvider;
|
|
5898
6339
|
}
|
|
5899
6340
|
eventHandlers = /* @__PURE__ */ new Set();
|
|
5900
6341
|
typedEventHandlers = /* @__PURE__ */ new Map();
|
|
@@ -5902,7 +6343,9 @@ var init_session = __esm({
|
|
|
5902
6343
|
permissionHandler;
|
|
5903
6344
|
userInputHandler;
|
|
5904
6345
|
hooks;
|
|
6346
|
+
transformCallbacks;
|
|
5905
6347
|
_rpc = null;
|
|
6348
|
+
traceContextProvider;
|
|
5906
6349
|
/**
|
|
5907
6350
|
* Typed session-scoped RPC methods.
|
|
5908
6351
|
*/
|
|
@@ -5940,6 +6383,7 @@ var init_session = __esm({
|
|
|
5940
6383
|
*/
|
|
5941
6384
|
async send(options) {
|
|
5942
6385
|
const response = await this.connection.sendRequest("session.send", {
|
|
6386
|
+
...await getTraceContext(this.traceContextProvider),
|
|
5943
6387
|
sessionId: this.sessionId,
|
|
5944
6388
|
prompt: options.prompt,
|
|
5945
6389
|
attachments: options.attachments,
|
|
@@ -6069,9 +6513,19 @@ var init_session = __esm({
|
|
|
6069
6513
|
const { requestId, toolName } = event.data;
|
|
6070
6514
|
const args = event.data.arguments;
|
|
6071
6515
|
const toolCallId = event.data.toolCallId;
|
|
6516
|
+
const traceparent = event.data.traceparent;
|
|
6517
|
+
const tracestate = event.data.tracestate;
|
|
6072
6518
|
const handler = this.toolHandlers.get(toolName);
|
|
6073
6519
|
if (handler) {
|
|
6074
|
-
void this._executeToolAndRespond(
|
|
6520
|
+
void this._executeToolAndRespond(
|
|
6521
|
+
requestId,
|
|
6522
|
+
toolName,
|
|
6523
|
+
toolCallId,
|
|
6524
|
+
args,
|
|
6525
|
+
handler,
|
|
6526
|
+
traceparent,
|
|
6527
|
+
tracestate
|
|
6528
|
+
);
|
|
6075
6529
|
}
|
|
6076
6530
|
} else if (event.type === "permission.requested") {
|
|
6077
6531
|
const { requestId, permissionRequest } = event.data;
|
|
@@ -6084,13 +6538,15 @@ var init_session = __esm({
|
|
|
6084
6538
|
* Executes a tool handler and sends the result back via RPC.
|
|
6085
6539
|
* @internal
|
|
6086
6540
|
*/
|
|
6087
|
-
async _executeToolAndRespond(requestId, toolName, toolCallId, args, handler) {
|
|
6541
|
+
async _executeToolAndRespond(requestId, toolName, toolCallId, args, handler, traceparent, tracestate) {
|
|
6088
6542
|
try {
|
|
6089
6543
|
const rawResult = await handler(args, {
|
|
6090
6544
|
sessionId: this.sessionId,
|
|
6091
6545
|
toolCallId,
|
|
6092
6546
|
toolName,
|
|
6093
|
-
arguments: args
|
|
6547
|
+
arguments: args,
|
|
6548
|
+
traceparent,
|
|
6549
|
+
tracestate
|
|
6094
6550
|
});
|
|
6095
6551
|
let result;
|
|
6096
6552
|
if (rawResult == null) {
|
|
@@ -6121,6 +6577,9 @@ var init_session = __esm({
|
|
|
6121
6577
|
const result = await this.permissionHandler(permissionRequest, {
|
|
6122
6578
|
sessionId: this.sessionId
|
|
6123
6579
|
});
|
|
6580
|
+
if (result.kind === "no-result") {
|
|
6581
|
+
return;
|
|
6582
|
+
}
|
|
6124
6583
|
await this.rpc.permissions.handlePendingPermissionRequest({ requestId, result });
|
|
6125
6584
|
} catch (_error) {
|
|
6126
6585
|
try {
|
|
@@ -6201,6 +6660,40 @@ var init_session = __esm({
|
|
|
6201
6660
|
registerHooks(hooks) {
|
|
6202
6661
|
this.hooks = hooks;
|
|
6203
6662
|
}
|
|
6663
|
+
/**
|
|
6664
|
+
* Registers transform callbacks for system message sections.
|
|
6665
|
+
*
|
|
6666
|
+
* @param callbacks - Map of section ID to transform callback, or undefined to clear
|
|
6667
|
+
* @internal This method is typically called internally when creating a session.
|
|
6668
|
+
*/
|
|
6669
|
+
registerTransformCallbacks(callbacks) {
|
|
6670
|
+
this.transformCallbacks = callbacks;
|
|
6671
|
+
}
|
|
6672
|
+
/**
|
|
6673
|
+
* Handles a systemMessage.transform request from the runtime.
|
|
6674
|
+
* Dispatches each section to its registered transform callback.
|
|
6675
|
+
*
|
|
6676
|
+
* @param sections - Map of section IDs to their current rendered content
|
|
6677
|
+
* @returns A promise that resolves with the transformed sections
|
|
6678
|
+
* @internal This method is for internal use by the SDK.
|
|
6679
|
+
*/
|
|
6680
|
+
async _handleSystemMessageTransform(sections) {
|
|
6681
|
+
const result = {};
|
|
6682
|
+
for (const [sectionId, { content }] of Object.entries(sections)) {
|
|
6683
|
+
const callback = this.transformCallbacks?.get(sectionId);
|
|
6684
|
+
if (callback) {
|
|
6685
|
+
try {
|
|
6686
|
+
const transformed = await callback(content);
|
|
6687
|
+
result[sectionId] = { content: transformed };
|
|
6688
|
+
} catch (_error) {
|
|
6689
|
+
result[sectionId] = { content };
|
|
6690
|
+
}
|
|
6691
|
+
} else {
|
|
6692
|
+
result[sectionId] = { content };
|
|
6693
|
+
}
|
|
6694
|
+
}
|
|
6695
|
+
return { sections: result };
|
|
6696
|
+
}
|
|
6204
6697
|
/**
|
|
6205
6698
|
* Handles a permission request in the v2 protocol format (synchronous RPC).
|
|
6206
6699
|
* Used as a back-compat adapter when connected to a v2 server.
|
|
@@ -6217,8 +6710,14 @@ var init_session = __esm({
|
|
|
6217
6710
|
const result = await this.permissionHandler(request, {
|
|
6218
6711
|
sessionId: this.sessionId
|
|
6219
6712
|
});
|
|
6713
|
+
if (result.kind === "no-result") {
|
|
6714
|
+
throw new Error(NO_RESULT_PERMISSION_V2_ERROR);
|
|
6715
|
+
}
|
|
6220
6716
|
return result;
|
|
6221
|
-
} catch (
|
|
6717
|
+
} catch (error) {
|
|
6718
|
+
if (error instanceof Error && error.message === NO_RESULT_PERMISSION_V2_ERROR) {
|
|
6719
|
+
throw error;
|
|
6720
|
+
}
|
|
6222
6721
|
return { kind: "denied-no-approval-rule-and-could-not-request-from-user" };
|
|
6223
6722
|
}
|
|
6224
6723
|
}
|
|
@@ -6374,14 +6873,35 @@ var init_session = __esm({
|
|
|
6374
6873
|
* The new model takes effect for the next message. Conversation history is preserved.
|
|
6375
6874
|
*
|
|
6376
6875
|
* @param model - Model ID to switch to
|
|
6876
|
+
* @param options - Optional settings for the new model
|
|
6377
6877
|
*
|
|
6378
6878
|
* @example
|
|
6379
6879
|
* ```typescript
|
|
6380
6880
|
* await session.setModel("gpt-4.1");
|
|
6881
|
+
* await session.setModel("claude-sonnet-4.6", { reasoningEffort: "high" });
|
|
6882
|
+
* ```
|
|
6883
|
+
*/
|
|
6884
|
+
async setModel(model, options) {
|
|
6885
|
+
await this.rpc.model.switchTo({ modelId: model, ...options });
|
|
6886
|
+
}
|
|
6887
|
+
/**
|
|
6888
|
+
* Log a message to the session timeline.
|
|
6889
|
+
* The message appears in the session event stream and is visible to SDK consumers
|
|
6890
|
+
* and (for non-ephemeral messages) persisted to the session event log on disk.
|
|
6891
|
+
*
|
|
6892
|
+
* @param message - Human-readable message text
|
|
6893
|
+
* @param options - Optional log level and ephemeral flag
|
|
6894
|
+
*
|
|
6895
|
+
* @example
|
|
6896
|
+
* ```typescript
|
|
6897
|
+
* await session.log("Processing started");
|
|
6898
|
+
* await session.log("Disk usage high", { level: "warning" });
|
|
6899
|
+
* await session.log("Connection failed", { level: "error" });
|
|
6900
|
+
* await session.log("Debug info", { ephemeral: true });
|
|
6381
6901
|
* ```
|
|
6382
6902
|
*/
|
|
6383
|
-
async
|
|
6384
|
-
await this.rpc.
|
|
6903
|
+
async log(message, options) {
|
|
6904
|
+
await this.rpc.log({ message, ...options });
|
|
6385
6905
|
}
|
|
6386
6906
|
};
|
|
6387
6907
|
}
|
|
@@ -6389,7 +6909,9 @@ var init_session = __esm({
|
|
|
6389
6909
|
|
|
6390
6910
|
// node_modules/@github/copilot-sdk/dist/client.js
|
|
6391
6911
|
import { spawn } from "child_process";
|
|
6912
|
+
import { randomUUID } from "crypto";
|
|
6392
6913
|
import { existsSync as existsSync4 } from "fs";
|
|
6914
|
+
import { createRequire as createRequire2 } from "module";
|
|
6393
6915
|
import { Socket } from "net";
|
|
6394
6916
|
import { dirname as dirname3, join as join6 } from "path";
|
|
6395
6917
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -6403,6 +6925,30 @@ function toJsonSchema(parameters) {
|
|
|
6403
6925
|
}
|
|
6404
6926
|
return parameters;
|
|
6405
6927
|
}
|
|
6928
|
+
function extractTransformCallbacks(systemMessage) {
|
|
6929
|
+
if (!systemMessage || systemMessage.mode !== "customize" || !systemMessage.sections) {
|
|
6930
|
+
return { wirePayload: systemMessage, transformCallbacks: void 0 };
|
|
6931
|
+
}
|
|
6932
|
+
const transformCallbacks = /* @__PURE__ */ new Map();
|
|
6933
|
+
const wireSections = {};
|
|
6934
|
+
for (const [sectionId, override] of Object.entries(systemMessage.sections)) {
|
|
6935
|
+
if (!override) continue;
|
|
6936
|
+
if (typeof override.action === "function") {
|
|
6937
|
+
transformCallbacks.set(sectionId, override.action);
|
|
6938
|
+
wireSections[sectionId] = { action: "transform" };
|
|
6939
|
+
} else {
|
|
6940
|
+
wireSections[sectionId] = { action: override.action, content: override.content };
|
|
6941
|
+
}
|
|
6942
|
+
}
|
|
6943
|
+
if (transformCallbacks.size === 0) {
|
|
6944
|
+
return { wirePayload: systemMessage, transformCallbacks: void 0 };
|
|
6945
|
+
}
|
|
6946
|
+
const wirePayload = {
|
|
6947
|
+
...systemMessage,
|
|
6948
|
+
sections: wireSections
|
|
6949
|
+
};
|
|
6950
|
+
return { wirePayload, transformCallbacks };
|
|
6951
|
+
}
|
|
6406
6952
|
function getNodeExecPath() {
|
|
6407
6953
|
if (process.versions.bun) {
|
|
6408
6954
|
return "node";
|
|
@@ -6410,9 +6956,22 @@ function getNodeExecPath() {
|
|
|
6410
6956
|
return process.execPath;
|
|
6411
6957
|
}
|
|
6412
6958
|
function getBundledCliPath() {
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6959
|
+
if (typeof import.meta.resolve === "function") {
|
|
6960
|
+
const sdkUrl = import.meta.resolve("@github/copilot/sdk");
|
|
6961
|
+
const sdkPath = fileURLToPath3(sdkUrl);
|
|
6962
|
+
return join6(dirname3(dirname3(sdkPath)), "index.js");
|
|
6963
|
+
}
|
|
6964
|
+
const req = createRequire2(__filename);
|
|
6965
|
+
const searchPaths = req.resolve.paths("@github/copilot") ?? [];
|
|
6966
|
+
for (const base of searchPaths) {
|
|
6967
|
+
const candidate = join6(base, "@github", "copilot", "index.js");
|
|
6968
|
+
if (existsSync4(candidate)) {
|
|
6969
|
+
return candidate;
|
|
6970
|
+
}
|
|
6971
|
+
}
|
|
6972
|
+
throw new Error(
|
|
6973
|
+
`Could not find @github/copilot package. Searched ${searchPaths.length} paths. Ensure it is installed, or pass cliPath/cliUrl to CopilotClient.`
|
|
6974
|
+
);
|
|
6416
6975
|
}
|
|
6417
6976
|
var import_node2, MIN_PROTOCOL_VERSION, CopilotClient;
|
|
6418
6977
|
var init_client = __esm({
|
|
@@ -6422,6 +6981,7 @@ var init_client = __esm({
|
|
|
6422
6981
|
init_rpc();
|
|
6423
6982
|
init_sdkProtocolVersion();
|
|
6424
6983
|
init_session();
|
|
6984
|
+
init_telemetry();
|
|
6425
6985
|
MIN_PROTOCOL_VERSION = 2;
|
|
6426
6986
|
CopilotClient = class {
|
|
6427
6987
|
cliProcess = null;
|
|
@@ -6436,6 +6996,8 @@ var init_client = __esm({
|
|
|
6436
6996
|
options;
|
|
6437
6997
|
isExternalServer = false;
|
|
6438
6998
|
forceStopping = false;
|
|
6999
|
+
onListModels;
|
|
7000
|
+
onGetTraceContext;
|
|
6439
7001
|
modelsCache = null;
|
|
6440
7002
|
modelsCacheLock = Promise.resolve();
|
|
6441
7003
|
sessionLifecycleHandlers = /* @__PURE__ */ new Set();
|
|
@@ -6501,8 +7063,10 @@ var init_client = __esm({
|
|
|
6501
7063
|
if (options.isChildProcess) {
|
|
6502
7064
|
this.isExternalServer = true;
|
|
6503
7065
|
}
|
|
7066
|
+
this.onListModels = options.onListModels;
|
|
7067
|
+
this.onGetTraceContext = options.onGetTraceContext;
|
|
6504
7068
|
this.options = {
|
|
6505
|
-
cliPath: options.cliPath || getBundledCliPath(),
|
|
7069
|
+
cliPath: options.cliUrl ? void 0 : options.cliPath || getBundledCliPath(),
|
|
6506
7070
|
cliArgs: options.cliArgs ?? [],
|
|
6507
7071
|
cwd: options.cwd ?? process.cwd(),
|
|
6508
7072
|
port: options.port || 0,
|
|
@@ -6512,11 +7076,12 @@ var init_client = __esm({
|
|
|
6512
7076
|
cliUrl: options.cliUrl,
|
|
6513
7077
|
logLevel: options.logLevel || "debug",
|
|
6514
7078
|
autoStart: options.autoStart ?? true,
|
|
6515
|
-
autoRestart:
|
|
7079
|
+
autoRestart: false,
|
|
6516
7080
|
env: options.env ?? process.env,
|
|
6517
7081
|
githubToken: options.githubToken,
|
|
6518
7082
|
// Default useLoggedInUser to false when githubToken is provided, otherwise true
|
|
6519
|
-
useLoggedInUser: options.useLoggedInUser ?? (options.githubToken ? false : true)
|
|
7083
|
+
useLoggedInUser: options.useLoggedInUser ?? (options.githubToken ? false : true),
|
|
7084
|
+
telemetry: options.telemetry
|
|
6520
7085
|
};
|
|
6521
7086
|
}
|
|
6522
7087
|
/**
|
|
@@ -6612,8 +7177,8 @@ var init_client = __esm({
|
|
|
6612
7177
|
} catch (error) {
|
|
6613
7178
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
6614
7179
|
if (attempt < 3) {
|
|
6615
|
-
const
|
|
6616
|
-
await new Promise((resolve4) => setTimeout(resolve4,
|
|
7180
|
+
const delay2 = 100 * Math.pow(2, attempt - 1);
|
|
7181
|
+
await new Promise((resolve4) => setTimeout(resolve4, delay2));
|
|
6617
7182
|
}
|
|
6618
7183
|
}
|
|
6619
7184
|
}
|
|
@@ -6768,36 +7333,13 @@ var init_client = __esm({
|
|
|
6768
7333
|
throw new Error("Client not connected. Call start() first.");
|
|
6769
7334
|
}
|
|
6770
7335
|
}
|
|
6771
|
-
const
|
|
6772
|
-
|
|
6773
|
-
sessionId
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
description: tool.description,
|
|
6779
|
-
parameters: toJsonSchema(tool.parameters),
|
|
6780
|
-
overridesBuiltInTool: tool.overridesBuiltInTool
|
|
6781
|
-
})),
|
|
6782
|
-
systemMessage: config2.systemMessage,
|
|
6783
|
-
availableTools: config2.availableTools,
|
|
6784
|
-
excludedTools: config2.excludedTools,
|
|
6785
|
-
provider: config2.provider,
|
|
6786
|
-
requestPermission: true,
|
|
6787
|
-
requestUserInput: !!config2.onUserInputRequest,
|
|
6788
|
-
hooks: !!(config2.hooks && Object.values(config2.hooks).some(Boolean)),
|
|
6789
|
-
workingDirectory: config2.workingDirectory,
|
|
6790
|
-
streaming: config2.streaming,
|
|
6791
|
-
mcpServers: config2.mcpServers,
|
|
6792
|
-
envValueMode: "direct",
|
|
6793
|
-
customAgents: config2.customAgents,
|
|
6794
|
-
configDir: config2.configDir,
|
|
6795
|
-
skillDirectories: config2.skillDirectories,
|
|
6796
|
-
disabledSkills: config2.disabledSkills,
|
|
6797
|
-
infiniteSessions: config2.infiniteSessions
|
|
6798
|
-
});
|
|
6799
|
-
const { sessionId, workspacePath } = response;
|
|
6800
|
-
const session = new CopilotSession(sessionId, this.connection, workspacePath);
|
|
7336
|
+
const sessionId = config2.sessionId ?? randomUUID();
|
|
7337
|
+
const session = new CopilotSession(
|
|
7338
|
+
sessionId,
|
|
7339
|
+
this.connection,
|
|
7340
|
+
void 0,
|
|
7341
|
+
this.onGetTraceContext
|
|
7342
|
+
);
|
|
6801
7343
|
session.registerTools(config2.tools);
|
|
6802
7344
|
session.registerPermissionHandler(config2.onPermissionRequest);
|
|
6803
7345
|
if (config2.onUserInputRequest) {
|
|
@@ -6806,7 +7348,54 @@ var init_client = __esm({
|
|
|
6806
7348
|
if (config2.hooks) {
|
|
6807
7349
|
session.registerHooks(config2.hooks);
|
|
6808
7350
|
}
|
|
7351
|
+
const { wirePayload: wireSystemMessage, transformCallbacks } = extractTransformCallbacks(
|
|
7352
|
+
config2.systemMessage
|
|
7353
|
+
);
|
|
7354
|
+
if (transformCallbacks) {
|
|
7355
|
+
session.registerTransformCallbacks(transformCallbacks);
|
|
7356
|
+
}
|
|
7357
|
+
if (config2.onEvent) {
|
|
7358
|
+
session.on(config2.onEvent);
|
|
7359
|
+
}
|
|
6809
7360
|
this.sessions.set(sessionId, session);
|
|
7361
|
+
try {
|
|
7362
|
+
const response = await this.connection.sendRequest("session.create", {
|
|
7363
|
+
...await getTraceContext(this.onGetTraceContext),
|
|
7364
|
+
model: config2.model,
|
|
7365
|
+
sessionId,
|
|
7366
|
+
clientName: config2.clientName,
|
|
7367
|
+
reasoningEffort: config2.reasoningEffort,
|
|
7368
|
+
tools: config2.tools?.map((tool) => ({
|
|
7369
|
+
name: tool.name,
|
|
7370
|
+
description: tool.description,
|
|
7371
|
+
parameters: toJsonSchema(tool.parameters),
|
|
7372
|
+
overridesBuiltInTool: tool.overridesBuiltInTool,
|
|
7373
|
+
skipPermission: tool.skipPermission
|
|
7374
|
+
})),
|
|
7375
|
+
systemMessage: wireSystemMessage,
|
|
7376
|
+
availableTools: config2.availableTools,
|
|
7377
|
+
excludedTools: config2.excludedTools,
|
|
7378
|
+
provider: config2.provider,
|
|
7379
|
+
requestPermission: true,
|
|
7380
|
+
requestUserInput: !!config2.onUserInputRequest,
|
|
7381
|
+
hooks: !!(config2.hooks && Object.values(config2.hooks).some(Boolean)),
|
|
7382
|
+
workingDirectory: config2.workingDirectory,
|
|
7383
|
+
streaming: config2.streaming,
|
|
7384
|
+
mcpServers: config2.mcpServers,
|
|
7385
|
+
envValueMode: "direct",
|
|
7386
|
+
customAgents: config2.customAgents,
|
|
7387
|
+
agent: config2.agent,
|
|
7388
|
+
configDir: config2.configDir,
|
|
7389
|
+
skillDirectories: config2.skillDirectories,
|
|
7390
|
+
disabledSkills: config2.disabledSkills,
|
|
7391
|
+
infiniteSessions: config2.infiniteSessions
|
|
7392
|
+
});
|
|
7393
|
+
const { workspacePath } = response;
|
|
7394
|
+
session["_workspacePath"] = workspacePath;
|
|
7395
|
+
} catch (e) {
|
|
7396
|
+
this.sessions.delete(sessionId);
|
|
7397
|
+
throw e;
|
|
7398
|
+
}
|
|
6810
7399
|
return session;
|
|
6811
7400
|
}
|
|
6812
7401
|
/**
|
|
@@ -6846,37 +7435,12 @@ var init_client = __esm({
|
|
|
6846
7435
|
throw new Error("Client not connected. Call start() first.");
|
|
6847
7436
|
}
|
|
6848
7437
|
}
|
|
6849
|
-
const
|
|
7438
|
+
const session = new CopilotSession(
|
|
6850
7439
|
sessionId,
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
availableTools: config2.availableTools,
|
|
6856
|
-
excludedTools: config2.excludedTools,
|
|
6857
|
-
tools: config2.tools?.map((tool) => ({
|
|
6858
|
-
name: tool.name,
|
|
6859
|
-
description: tool.description,
|
|
6860
|
-
parameters: toJsonSchema(tool.parameters),
|
|
6861
|
-
overridesBuiltInTool: tool.overridesBuiltInTool
|
|
6862
|
-
})),
|
|
6863
|
-
provider: config2.provider,
|
|
6864
|
-
requestPermission: true,
|
|
6865
|
-
requestUserInput: !!config2.onUserInputRequest,
|
|
6866
|
-
hooks: !!(config2.hooks && Object.values(config2.hooks).some(Boolean)),
|
|
6867
|
-
workingDirectory: config2.workingDirectory,
|
|
6868
|
-
configDir: config2.configDir,
|
|
6869
|
-
streaming: config2.streaming,
|
|
6870
|
-
mcpServers: config2.mcpServers,
|
|
6871
|
-
envValueMode: "direct",
|
|
6872
|
-
customAgents: config2.customAgents,
|
|
6873
|
-
skillDirectories: config2.skillDirectories,
|
|
6874
|
-
disabledSkills: config2.disabledSkills,
|
|
6875
|
-
infiniteSessions: config2.infiniteSessions,
|
|
6876
|
-
disableResume: config2.disableResume
|
|
6877
|
-
});
|
|
6878
|
-
const { sessionId: resumedSessionId, workspacePath } = response;
|
|
6879
|
-
const session = new CopilotSession(resumedSessionId, this.connection, workspacePath);
|
|
7440
|
+
this.connection,
|
|
7441
|
+
void 0,
|
|
7442
|
+
this.onGetTraceContext
|
|
7443
|
+
);
|
|
6880
7444
|
session.registerTools(config2.tools);
|
|
6881
7445
|
session.registerPermissionHandler(config2.onPermissionRequest);
|
|
6882
7446
|
if (config2.onUserInputRequest) {
|
|
@@ -6885,7 +7449,55 @@ var init_client = __esm({
|
|
|
6885
7449
|
if (config2.hooks) {
|
|
6886
7450
|
session.registerHooks(config2.hooks);
|
|
6887
7451
|
}
|
|
6888
|
-
|
|
7452
|
+
const { wirePayload: wireSystemMessage, transformCallbacks } = extractTransformCallbacks(
|
|
7453
|
+
config2.systemMessage
|
|
7454
|
+
);
|
|
7455
|
+
if (transformCallbacks) {
|
|
7456
|
+
session.registerTransformCallbacks(transformCallbacks);
|
|
7457
|
+
}
|
|
7458
|
+
if (config2.onEvent) {
|
|
7459
|
+
session.on(config2.onEvent);
|
|
7460
|
+
}
|
|
7461
|
+
this.sessions.set(sessionId, session);
|
|
7462
|
+
try {
|
|
7463
|
+
const response = await this.connection.sendRequest("session.resume", {
|
|
7464
|
+
...await getTraceContext(this.onGetTraceContext),
|
|
7465
|
+
sessionId,
|
|
7466
|
+
clientName: config2.clientName,
|
|
7467
|
+
model: config2.model,
|
|
7468
|
+
reasoningEffort: config2.reasoningEffort,
|
|
7469
|
+
systemMessage: wireSystemMessage,
|
|
7470
|
+
availableTools: config2.availableTools,
|
|
7471
|
+
excludedTools: config2.excludedTools,
|
|
7472
|
+
tools: config2.tools?.map((tool) => ({
|
|
7473
|
+
name: tool.name,
|
|
7474
|
+
description: tool.description,
|
|
7475
|
+
parameters: toJsonSchema(tool.parameters),
|
|
7476
|
+
overridesBuiltInTool: tool.overridesBuiltInTool,
|
|
7477
|
+
skipPermission: tool.skipPermission
|
|
7478
|
+
})),
|
|
7479
|
+
provider: config2.provider,
|
|
7480
|
+
requestPermission: true,
|
|
7481
|
+
requestUserInput: !!config2.onUserInputRequest,
|
|
7482
|
+
hooks: !!(config2.hooks && Object.values(config2.hooks).some(Boolean)),
|
|
7483
|
+
workingDirectory: config2.workingDirectory,
|
|
7484
|
+
configDir: config2.configDir,
|
|
7485
|
+
streaming: config2.streaming,
|
|
7486
|
+
mcpServers: config2.mcpServers,
|
|
7487
|
+
envValueMode: "direct",
|
|
7488
|
+
customAgents: config2.customAgents,
|
|
7489
|
+
agent: config2.agent,
|
|
7490
|
+
skillDirectories: config2.skillDirectories,
|
|
7491
|
+
disabledSkills: config2.disabledSkills,
|
|
7492
|
+
infiniteSessions: config2.infiniteSessions,
|
|
7493
|
+
disableResume: config2.disableResume
|
|
7494
|
+
});
|
|
7495
|
+
const { workspacePath } = response;
|
|
7496
|
+
session["_workspacePath"] = workspacePath;
|
|
7497
|
+
} catch (e) {
|
|
7498
|
+
this.sessions.delete(sessionId);
|
|
7499
|
+
throw e;
|
|
7500
|
+
}
|
|
6889
7501
|
return session;
|
|
6890
7502
|
}
|
|
6891
7503
|
/**
|
|
@@ -6946,15 +7558,15 @@ var init_client = __esm({
|
|
|
6946
7558
|
/**
|
|
6947
7559
|
* List available models with their metadata.
|
|
6948
7560
|
*
|
|
7561
|
+
* If an `onListModels` handler was provided in the client options,
|
|
7562
|
+
* it is called instead of querying the CLI server.
|
|
7563
|
+
*
|
|
6949
7564
|
* Results are cached after the first successful call to avoid rate limiting.
|
|
6950
7565
|
* The cache is cleared when the client disconnects.
|
|
6951
7566
|
*
|
|
6952
|
-
* @throws Error if not
|
|
7567
|
+
* @throws Error if not connected (when no custom handler is set)
|
|
6953
7568
|
*/
|
|
6954
7569
|
async listModels() {
|
|
6955
|
-
if (!this.connection) {
|
|
6956
|
-
throw new Error("Client not connected");
|
|
6957
|
-
}
|
|
6958
7570
|
await this.modelsCacheLock;
|
|
6959
7571
|
let resolveLock;
|
|
6960
7572
|
this.modelsCacheLock = new Promise((resolve4) => {
|
|
@@ -6964,10 +7576,18 @@ var init_client = __esm({
|
|
|
6964
7576
|
if (this.modelsCache !== null) {
|
|
6965
7577
|
return [...this.modelsCache];
|
|
6966
7578
|
}
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
7579
|
+
let models;
|
|
7580
|
+
if (this.onListModels) {
|
|
7581
|
+
models = await this.onListModels();
|
|
7582
|
+
} else {
|
|
7583
|
+
if (!this.connection) {
|
|
7584
|
+
throw new Error("Client not connected");
|
|
7585
|
+
}
|
|
7586
|
+
const result = await this.connection.sendRequest("models.list", {});
|
|
7587
|
+
const response = result;
|
|
7588
|
+
models = response.models;
|
|
7589
|
+
}
|
|
7590
|
+
this.modelsCache = [...models];
|
|
6971
7591
|
return [...models];
|
|
6972
7592
|
} finally {
|
|
6973
7593
|
resolveLock();
|
|
@@ -7180,6 +7800,27 @@ var init_client = __esm({
|
|
|
7180
7800
|
if (this.options.githubToken) {
|
|
7181
7801
|
envWithoutNodeDebug.COPILOT_SDK_AUTH_TOKEN = this.options.githubToken;
|
|
7182
7802
|
}
|
|
7803
|
+
if (!this.options.cliPath) {
|
|
7804
|
+
throw new Error(
|
|
7805
|
+
"Path to Copilot CLI is required. Please provide it via the cliPath option, or use cliUrl to rely on a remote CLI."
|
|
7806
|
+
);
|
|
7807
|
+
}
|
|
7808
|
+
if (this.options.telemetry) {
|
|
7809
|
+
const t = this.options.telemetry;
|
|
7810
|
+
envWithoutNodeDebug.COPILOT_OTEL_ENABLED = "true";
|
|
7811
|
+
if (t.otlpEndpoint !== void 0)
|
|
7812
|
+
envWithoutNodeDebug.OTEL_EXPORTER_OTLP_ENDPOINT = t.otlpEndpoint;
|
|
7813
|
+
if (t.filePath !== void 0)
|
|
7814
|
+
envWithoutNodeDebug.COPILOT_OTEL_FILE_EXPORTER_PATH = t.filePath;
|
|
7815
|
+
if (t.exporterType !== void 0)
|
|
7816
|
+
envWithoutNodeDebug.COPILOT_OTEL_EXPORTER_TYPE = t.exporterType;
|
|
7817
|
+
if (t.sourceName !== void 0)
|
|
7818
|
+
envWithoutNodeDebug.COPILOT_OTEL_SOURCE_NAME = t.sourceName;
|
|
7819
|
+
if (t.captureContent !== void 0)
|
|
7820
|
+
envWithoutNodeDebug.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = String(
|
|
7821
|
+
t.captureContent
|
|
7822
|
+
);
|
|
7823
|
+
}
|
|
7183
7824
|
if (!existsSync4(this.options.cliPath)) {
|
|
7184
7825
|
throw new Error(
|
|
7185
7826
|
`Copilot CLI not found at ${this.options.cliPath}. Ensure @github/copilot is installed.`
|
|
@@ -7279,8 +7920,6 @@ stderr: ${stderrOutput}`
|
|
|
7279
7920
|
} else {
|
|
7280
7921
|
reject(new Error(`CLI server exited with code ${code}`));
|
|
7281
7922
|
}
|
|
7282
|
-
} else if (this.options.autoRestart && this.state === "connected") {
|
|
7283
|
-
void this.reconnect();
|
|
7284
7923
|
}
|
|
7285
7924
|
});
|
|
7286
7925
|
setTimeout(() => {
|
|
@@ -7385,12 +8024,15 @@ stderr: ${stderrOutput}`
|
|
|
7385
8024
|
"hooks.invoke",
|
|
7386
8025
|
async (params) => await this.handleHooksInvoke(params)
|
|
7387
8026
|
);
|
|
8027
|
+
this.connection.onRequest(
|
|
8028
|
+
"systemMessage.transform",
|
|
8029
|
+
async (params) => await this.handleSystemMessageTransform(params)
|
|
8030
|
+
);
|
|
7388
8031
|
this.connection.onClose(() => {
|
|
7389
|
-
|
|
7390
|
-
void this.reconnect();
|
|
7391
|
-
}
|
|
8032
|
+
this.state = "disconnected";
|
|
7392
8033
|
});
|
|
7393
8034
|
this.connection.onError((_error) => {
|
|
8035
|
+
this.state = "disconnected";
|
|
7394
8036
|
});
|
|
7395
8037
|
}
|
|
7396
8038
|
handleSessionEventNotification(notification) {
|
|
@@ -7449,6 +8091,16 @@ stderr: ${stderrOutput}`
|
|
|
7449
8091
|
const output = await session._handleHooksInvoke(params.hookType, params.input);
|
|
7450
8092
|
return { output };
|
|
7451
8093
|
}
|
|
8094
|
+
async handleSystemMessageTransform(params) {
|
|
8095
|
+
if (!params || typeof params.sessionId !== "string" || !params.sections || typeof params.sections !== "object") {
|
|
8096
|
+
throw new Error("Invalid systemMessage.transform payload");
|
|
8097
|
+
}
|
|
8098
|
+
const session = this.sessions.get(params.sessionId);
|
|
8099
|
+
if (!session) {
|
|
8100
|
+
throw new Error(`Session not found: ${params.sessionId}`);
|
|
8101
|
+
}
|
|
8102
|
+
return await session._handleSystemMessageTransform(params.sections);
|
|
8103
|
+
}
|
|
7452
8104
|
// ========================================================================
|
|
7453
8105
|
// Protocol v2 backward-compatibility adapters
|
|
7454
8106
|
// ========================================================================
|
|
@@ -7477,11 +8129,15 @@ stderr: ${stderrOutput}`
|
|
|
7477
8129
|
};
|
|
7478
8130
|
}
|
|
7479
8131
|
try {
|
|
8132
|
+
const traceparent = params.traceparent;
|
|
8133
|
+
const tracestate = params.tracestate;
|
|
7480
8134
|
const invocation = {
|
|
7481
8135
|
sessionId: params.sessionId,
|
|
7482
8136
|
toolCallId: params.toolCallId,
|
|
7483
8137
|
toolName: params.toolName,
|
|
7484
|
-
arguments: params.arguments
|
|
8138
|
+
arguments: params.arguments,
|
|
8139
|
+
traceparent,
|
|
8140
|
+
tracestate
|
|
7485
8141
|
};
|
|
7486
8142
|
const result = await handler(params.arguments, invocation);
|
|
7487
8143
|
return { result: this.normalizeToolResultV2(result) };
|
|
@@ -7511,7 +8167,10 @@ stderr: ${stderrOutput}`
|
|
|
7511
8167
|
try {
|
|
7512
8168
|
const result = await session._handlePermissionRequestV2(params.permissionRequest);
|
|
7513
8169
|
return { result };
|
|
7514
|
-
} catch (
|
|
8170
|
+
} catch (error) {
|
|
8171
|
+
if (error instanceof Error && error.message === NO_RESULT_PERMISSION_V2_ERROR) {
|
|
8172
|
+
throw error;
|
|
8173
|
+
}
|
|
7515
8174
|
return {
|
|
7516
8175
|
result: {
|
|
7517
8176
|
kind: "denied-no-approval-rule-and-could-not-request-from-user"
|
|
@@ -7541,24 +8200,13 @@ stderr: ${stderrOutput}`
|
|
|
7541
8200
|
isToolResultObject(value) {
|
|
7542
8201
|
return typeof value === "object" && value !== null && "textResultForLlm" in value && typeof value.textResultForLlm === "string" && "resultType" in value;
|
|
7543
8202
|
}
|
|
7544
|
-
/**
|
|
7545
|
-
* Attempt to reconnect to the server
|
|
7546
|
-
*/
|
|
7547
|
-
async reconnect() {
|
|
7548
|
-
this.state = "disconnected";
|
|
7549
|
-
try {
|
|
7550
|
-
await this.stop();
|
|
7551
|
-
await this.start();
|
|
7552
|
-
} catch (_error) {
|
|
7553
|
-
}
|
|
7554
|
-
}
|
|
7555
8203
|
};
|
|
7556
8204
|
}
|
|
7557
8205
|
});
|
|
7558
8206
|
|
|
7559
8207
|
// node_modules/@github/copilot-sdk/dist/types.js
|
|
7560
8208
|
var approveAll;
|
|
7561
|
-
var
|
|
8209
|
+
var init_types3 = __esm({
|
|
7562
8210
|
"node_modules/@github/copilot-sdk/dist/types.js"() {
|
|
7563
8211
|
"use strict";
|
|
7564
8212
|
approveAll = () => ({ kind: "approved" });
|
|
@@ -7571,21 +8219,21 @@ var init_dist = __esm({
|
|
|
7571
8219
|
"use strict";
|
|
7572
8220
|
init_client();
|
|
7573
8221
|
init_session();
|
|
7574
|
-
|
|
8222
|
+
init_types3();
|
|
7575
8223
|
}
|
|
7576
8224
|
});
|
|
7577
8225
|
|
|
7578
8226
|
// src/L1-infra/ai/copilot.ts
|
|
7579
8227
|
import { existsSync as existsSync5 } from "fs";
|
|
7580
8228
|
import { join as join7, dirname as dirname4 } from "path";
|
|
7581
|
-
import { createRequire as
|
|
8229
|
+
import { createRequire as createRequire3 } from "module";
|
|
7582
8230
|
function resolveCopilotCliPath() {
|
|
7583
8231
|
const platform = process.platform;
|
|
7584
8232
|
const arch = process.arch;
|
|
7585
8233
|
const binaryName = platform === "win32" ? "copilot.exe" : "copilot";
|
|
7586
8234
|
const platformPkg = `@github/copilot-${platform}-${arch}`;
|
|
7587
8235
|
try {
|
|
7588
|
-
const require_ =
|
|
8236
|
+
const require_ = createRequire3(import.meta.url);
|
|
7589
8237
|
const searchPaths = require_.resolve.paths(platformPkg) ?? [];
|
|
7590
8238
|
for (const base of searchPaths) {
|
|
7591
8239
|
const candidate = join7(base, platformPkg, binaryName);
|
|
@@ -9804,7 +10452,7 @@ var STATUS_LABEL_PREFIX, PLATFORM_LABEL_PREFIX, PRIORITY_LABEL_PREFIX, COMMENT_M
|
|
|
9804
10452
|
var init_ideaService = __esm({
|
|
9805
10453
|
"src/L3-services/ideaService/ideaService.ts"() {
|
|
9806
10454
|
"use strict";
|
|
9807
|
-
|
|
10455
|
+
init_types2();
|
|
9808
10456
|
init_githubClient();
|
|
9809
10457
|
init_environment();
|
|
9810
10458
|
init_configLogger();
|
|
@@ -10229,7 +10877,7 @@ async function itemExists(id) {
|
|
|
10229
10877
|
var init_postStore = __esm({
|
|
10230
10878
|
"src/L3-services/postStore/postStore.ts"() {
|
|
10231
10879
|
"use strict";
|
|
10232
|
-
|
|
10880
|
+
init_types2();
|
|
10233
10881
|
init_environment();
|
|
10234
10882
|
init_configLogger();
|
|
10235
10883
|
init_fileSystem();
|
|
@@ -10396,6 +11044,31 @@ var init_lateApi = __esm({
|
|
|
10396
11044
|
}
|
|
10397
11045
|
return allPosts;
|
|
10398
11046
|
}
|
|
11047
|
+
// ── Queue management ─────────────────────────────────────────────────
|
|
11048
|
+
async listQueues(profileId, all = false) {
|
|
11049
|
+
const params = new URLSearchParams({ profileId });
|
|
11050
|
+
if (all) params.set("all", "true");
|
|
11051
|
+
return this.request(`/queue/slots?${params}`);
|
|
11052
|
+
}
|
|
11053
|
+
async createQueue(params) {
|
|
11054
|
+
return this.request("/queue/slots", { method: "POST", body: JSON.stringify(params) });
|
|
11055
|
+
}
|
|
11056
|
+
async updateQueue(params) {
|
|
11057
|
+
return this.request("/queue/slots", { method: "PUT", body: JSON.stringify(params) });
|
|
11058
|
+
}
|
|
11059
|
+
async deleteQueue(profileId, queueId) {
|
|
11060
|
+
return this.request(`/queue/slots?profileId=${profileId}&queueId=${queueId}`, { method: "DELETE" });
|
|
11061
|
+
}
|
|
11062
|
+
async previewQueue(profileId, queueId, count = 20) {
|
|
11063
|
+
const params = new URLSearchParams({ profileId, count: String(count) });
|
|
11064
|
+
if (queueId) params.set("queueId", queueId);
|
|
11065
|
+
return this.request(`/queue/preview?${params}`);
|
|
11066
|
+
}
|
|
11067
|
+
async getNextQueueSlot(profileId, queueId) {
|
|
11068
|
+
const params = new URLSearchParams({ profileId });
|
|
11069
|
+
if (queueId) params.set("queueId", queueId);
|
|
11070
|
+
return this.request(`/queue/next-slot?${params}`);
|
|
11071
|
+
}
|
|
10399
11072
|
// ── Helper ───────────────────────────────────────────────────────────
|
|
10400
11073
|
async validateConnection() {
|
|
10401
11074
|
try {
|
|
@@ -11254,81 +11927,396 @@ async function rescheduleAllPosts(options) {
|
|
|
11254
11927
|
}
|
|
11255
11928
|
if (!dryRun) {
|
|
11256
11929
|
try {
|
|
11257
|
-
await lateClient.schedulePost(latePostId, newSlotDatetime);
|
|
11258
|
-
} catch (scheduleErr) {
|
|
11259
|
-
const errMsg = scheduleErr instanceof Error ? scheduleErr.message : String(scheduleErr);
|
|
11260
|
-
if (errMsg.includes("Published posts can only have their recycling config updated")) {
|
|
11261
|
-
logger_default.info(`Skipping ${label}: post already published on platform`);
|
|
11262
|
-
result.details.push({ itemId: item.id, platform: itemPlatform, latePostId, oldSlot, newSlot: null, error: "Already published \u2014 skipped" });
|
|
11263
|
-
result.unchanged++;
|
|
11264
|
-
continue;
|
|
11265
|
-
}
|
|
11266
|
-
throw scheduleErr;
|
|
11930
|
+
await lateClient.schedulePost(latePostId, newSlotDatetime);
|
|
11931
|
+
} catch (scheduleErr) {
|
|
11932
|
+
const errMsg = scheduleErr instanceof Error ? scheduleErr.message : String(scheduleErr);
|
|
11933
|
+
if (errMsg.includes("Published posts can only have their recycling config updated")) {
|
|
11934
|
+
logger_default.info(`Skipping ${label}: post already published on platform`);
|
|
11935
|
+
result.details.push({ itemId: item.id, platform: itemPlatform, latePostId, oldSlot, newSlot: null, error: "Already published \u2014 skipped" });
|
|
11936
|
+
result.unchanged++;
|
|
11937
|
+
continue;
|
|
11938
|
+
}
|
|
11939
|
+
throw scheduleErr;
|
|
11940
|
+
}
|
|
11941
|
+
await updatePublishedItemSchedule(item.id, newSlotDatetime);
|
|
11942
|
+
}
|
|
11943
|
+
if (oldSlot) {
|
|
11944
|
+
const oldMs = normalizeDateTime(oldSlot);
|
|
11945
|
+
const oldBooked = bookedMap.get(oldMs);
|
|
11946
|
+
if (oldBooked?.postId === latePostId) {
|
|
11947
|
+
bookedMap.delete(oldMs);
|
|
11948
|
+
}
|
|
11949
|
+
}
|
|
11950
|
+
bookedMap.set(newSlotMs, {
|
|
11951
|
+
scheduledFor: newSlotDatetime,
|
|
11952
|
+
source: "late",
|
|
11953
|
+
postId: latePostId,
|
|
11954
|
+
platform: itemPlatform,
|
|
11955
|
+
ideaLinked: isIdea,
|
|
11956
|
+
ideaIds: item.metadata.ideaIds
|
|
11957
|
+
});
|
|
11958
|
+
logger_default.info(`Rescheduled ${label}: ${oldSlot ?? "unscheduled"} \u2192 ${newSlotDatetime}`);
|
|
11959
|
+
result.details.push({ itemId: item.id, platform: itemPlatform, latePostId, oldSlot, newSlot: newSlotDatetime });
|
|
11960
|
+
result.rescheduled++;
|
|
11961
|
+
} catch (err) {
|
|
11962
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
11963
|
+
logger_default.error(`Failed to reschedule ${label}: ${msg}`);
|
|
11964
|
+
result.details.push({ itemId: item.id, platform: itemPlatform, latePostId, oldSlot, newSlot: null, error: msg });
|
|
11965
|
+
result.failed++;
|
|
11966
|
+
}
|
|
11967
|
+
}
|
|
11968
|
+
logger_default.info(`Reschedule complete: ${result.rescheduled} moved, ${result.unchanged} unchanged, ${result.failed} failed`);
|
|
11969
|
+
return result;
|
|
11970
|
+
}
|
|
11971
|
+
async function rescheduleIdeaPosts(options) {
|
|
11972
|
+
return rescheduleAllPosts(options);
|
|
11973
|
+
}
|
|
11974
|
+
async function getScheduleCalendar(startDate, endDate) {
|
|
11975
|
+
const bookedMap = await buildBookedMap();
|
|
11976
|
+
let filtered = [...bookedMap.values()].filter((slot) => slot.source === "local" || slot.status === "scheduled").map((slot) => ({
|
|
11977
|
+
platform: slot.platform,
|
|
11978
|
+
scheduledFor: slot.scheduledFor,
|
|
11979
|
+
source: slot.source,
|
|
11980
|
+
postId: slot.postId,
|
|
11981
|
+
itemId: slot.itemId
|
|
11982
|
+
}));
|
|
11983
|
+
if (startDate) {
|
|
11984
|
+
const startMs = startDate.getTime();
|
|
11985
|
+
filtered = filtered.filter((slot) => normalizeDateTime(slot.scheduledFor) >= startMs);
|
|
11986
|
+
}
|
|
11987
|
+
if (endDate) {
|
|
11988
|
+
const endMs = endDate.getTime();
|
|
11989
|
+
filtered = filtered.filter((slot) => normalizeDateTime(slot.scheduledFor) <= endMs);
|
|
11990
|
+
}
|
|
11991
|
+
filtered.sort((left, right) => normalizeDateTime(left.scheduledFor) - normalizeDateTime(right.scheduledFor));
|
|
11992
|
+
return filtered;
|
|
11993
|
+
}
|
|
11994
|
+
var MAX_LOOKAHEAD_DAYS, DAY_MS, HOUR_MS;
|
|
11995
|
+
var init_scheduler = __esm({
|
|
11996
|
+
"src/L3-services/scheduler/scheduler.ts"() {
|
|
11997
|
+
"use strict";
|
|
11998
|
+
init_lateApi();
|
|
11999
|
+
init_configLogger();
|
|
12000
|
+
init_postStore();
|
|
12001
|
+
init_scheduleConfig();
|
|
12002
|
+
MAX_LOOKAHEAD_DAYS = 730;
|
|
12003
|
+
DAY_MS = 24 * 60 * 60 * 1e3;
|
|
12004
|
+
HOUR_MS = 60 * 60 * 1e3;
|
|
12005
|
+
}
|
|
12006
|
+
});
|
|
12007
|
+
|
|
12008
|
+
// src/L3-services/queueMapping/queueMapping.ts
|
|
12009
|
+
function cachePath() {
|
|
12010
|
+
return join(process.cwd(), CACHE_FILE);
|
|
12011
|
+
}
|
|
12012
|
+
function isCacheValid(cache2) {
|
|
12013
|
+
const fetchedAtTime = new Date(cache2.fetchedAt).getTime();
|
|
12014
|
+
if (Number.isNaN(fetchedAtTime)) {
|
|
12015
|
+
logger_default.warn("Invalid fetchedAt in queue cache; treating as stale", {
|
|
12016
|
+
fetchedAt: cache2.fetchedAt
|
|
12017
|
+
});
|
|
12018
|
+
return false;
|
|
12019
|
+
}
|
|
12020
|
+
const age = Date.now() - fetchedAtTime;
|
|
12021
|
+
return age < CACHE_TTL_MS;
|
|
12022
|
+
}
|
|
12023
|
+
async function readFileCache() {
|
|
12024
|
+
try {
|
|
12025
|
+
const raw = await readTextFile(cachePath());
|
|
12026
|
+
const cache2 = JSON.parse(raw);
|
|
12027
|
+
if (cache2.mappings && cache2.profileId && cache2.fetchedAt && isCacheValid(cache2)) {
|
|
12028
|
+
return cache2;
|
|
12029
|
+
}
|
|
12030
|
+
return null;
|
|
12031
|
+
} catch {
|
|
12032
|
+
return null;
|
|
12033
|
+
}
|
|
12034
|
+
}
|
|
12035
|
+
async function writeFileCache(cache2) {
|
|
12036
|
+
try {
|
|
12037
|
+
if (!cache2 || typeof cache2 !== "object" || !cache2.mappings || !cache2.profileId || !cache2.fetchedAt) {
|
|
12038
|
+
logger_default.warn("Invalid queue cache structure, skipping write");
|
|
12039
|
+
return;
|
|
12040
|
+
}
|
|
12041
|
+
const sanitized = {
|
|
12042
|
+
mappings: typeof cache2.mappings === "object" ? { ...cache2.mappings } : {},
|
|
12043
|
+
profileId: String(cache2.profileId),
|
|
12044
|
+
fetchedAt: String(cache2.fetchedAt)
|
|
12045
|
+
};
|
|
12046
|
+
for (const [name, id] of Object.entries(sanitized.mappings)) {
|
|
12047
|
+
if (typeof name !== "string" || typeof id !== "string" || /[\x00-\x1f]/.test(name) || /[\x00-\x1f]/.test(id)) {
|
|
12048
|
+
logger_default.warn("Invalid queue mapping data from API, skipping cache write");
|
|
12049
|
+
return;
|
|
12050
|
+
}
|
|
12051
|
+
}
|
|
12052
|
+
const resolvedCachePath = resolve(cachePath());
|
|
12053
|
+
if (!resolvedCachePath.startsWith(resolve(process.cwd()) + sep)) {
|
|
12054
|
+
throw new Error("Cache path outside working directory");
|
|
12055
|
+
}
|
|
12056
|
+
await writeTextFile(resolvedCachePath, JSON.stringify(sanitized, null, 2));
|
|
12057
|
+
} catch (err) {
|
|
12058
|
+
logger_default.warn("Failed to write queue cache file", { error: err });
|
|
12059
|
+
}
|
|
12060
|
+
}
|
|
12061
|
+
async function fetchAndCache() {
|
|
12062
|
+
const client = new LateApiClient();
|
|
12063
|
+
const profiles = await client.listProfiles();
|
|
12064
|
+
if (profiles.length === 0) {
|
|
12065
|
+
logger_default.warn("No Late API profiles found \u2014 queue mappings will be empty");
|
|
12066
|
+
const emptyCache = {
|
|
12067
|
+
mappings: {},
|
|
12068
|
+
profileId: "",
|
|
12069
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12070
|
+
};
|
|
12071
|
+
memoryCache = emptyCache;
|
|
12072
|
+
return emptyCache;
|
|
12073
|
+
}
|
|
12074
|
+
const profileId = profiles[0]._id;
|
|
12075
|
+
const { queues } = await client.listQueues(profileId, true);
|
|
12076
|
+
if (queues.length === 0) {
|
|
12077
|
+
logger_default.warn(
|
|
12078
|
+
"No queues found in Late API \u2014 run `vidpipe sync-queues` to create platform queues"
|
|
12079
|
+
);
|
|
12080
|
+
}
|
|
12081
|
+
const mappings = {};
|
|
12082
|
+
for (const queue2 of queues) {
|
|
12083
|
+
mappings[queue2.name] = queue2._id;
|
|
12084
|
+
}
|
|
12085
|
+
const cache2 = {
|
|
12086
|
+
mappings,
|
|
12087
|
+
profileId,
|
|
12088
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12089
|
+
};
|
|
12090
|
+
memoryCache = cache2;
|
|
12091
|
+
await writeFileCache(cache2);
|
|
12092
|
+
logger_default.info("Refreshed Late queue mappings", {
|
|
12093
|
+
queueCount: queues.length,
|
|
12094
|
+
queues: Object.keys(mappings)
|
|
12095
|
+
});
|
|
12096
|
+
return cache2;
|
|
12097
|
+
}
|
|
12098
|
+
async function ensureMappings() {
|
|
12099
|
+
if (memoryCache && isCacheValid(memoryCache)) {
|
|
12100
|
+
return memoryCache;
|
|
12101
|
+
}
|
|
12102
|
+
const fileCache = await readFileCache();
|
|
12103
|
+
if (fileCache) {
|
|
12104
|
+
memoryCache = fileCache;
|
|
12105
|
+
return fileCache;
|
|
12106
|
+
}
|
|
12107
|
+
try {
|
|
12108
|
+
return await fetchAndCache();
|
|
12109
|
+
} catch (err) {
|
|
12110
|
+
logger_default.error("Failed to fetch Late queue mappings", { error: err });
|
|
12111
|
+
return { mappings: {}, profileId: "", fetchedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
12112
|
+
}
|
|
12113
|
+
}
|
|
12114
|
+
async function getQueueId(platform, clipType) {
|
|
12115
|
+
const cache2 = await ensureMappings();
|
|
12116
|
+
const normalizedPlatform = platform === "twitter" ? "x" : platform;
|
|
12117
|
+
const queueName = `${normalizedPlatform}-${clipType}`;
|
|
12118
|
+
return cache2.mappings[queueName] ?? null;
|
|
12119
|
+
}
|
|
12120
|
+
async function getProfileId() {
|
|
12121
|
+
const cache2 = await ensureMappings();
|
|
12122
|
+
return cache2.profileId;
|
|
12123
|
+
}
|
|
12124
|
+
async function refreshQueueMappings() {
|
|
12125
|
+
memoryCache = null;
|
|
12126
|
+
const cache2 = await fetchAndCache();
|
|
12127
|
+
return { ...cache2.mappings };
|
|
12128
|
+
}
|
|
12129
|
+
var CACHE_FILE, CACHE_TTL_MS, memoryCache;
|
|
12130
|
+
var init_queueMapping = __esm({
|
|
12131
|
+
"src/L3-services/queueMapping/queueMapping.ts"() {
|
|
12132
|
+
"use strict";
|
|
12133
|
+
init_lateApi();
|
|
12134
|
+
init_configLogger();
|
|
12135
|
+
init_fileSystem();
|
|
12136
|
+
init_paths();
|
|
12137
|
+
CACHE_FILE = ".vidpipe-queue-cache.json";
|
|
12138
|
+
CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
12139
|
+
memoryCache = null;
|
|
12140
|
+
}
|
|
12141
|
+
});
|
|
12142
|
+
|
|
12143
|
+
// src/L3-services/queueSync/queueSync.ts
|
|
12144
|
+
var queueSync_exports = {};
|
|
12145
|
+
__export(queueSync_exports, {
|
|
12146
|
+
syncQueuesToLate: () => syncQueuesToLate
|
|
12147
|
+
});
|
|
12148
|
+
function normalizePlatformName(platform) {
|
|
12149
|
+
return PLATFORM_NAME_MAP[platform] ?? platform;
|
|
12150
|
+
}
|
|
12151
|
+
function slotsToSortedKey(slots) {
|
|
12152
|
+
const sorted = [...slots].sort(
|
|
12153
|
+
(a, b) => a.dayOfWeek !== b.dayOfWeek ? a.dayOfWeek - b.dayOfWeek : a.time.localeCompare(b.time)
|
|
12154
|
+
);
|
|
12155
|
+
return JSON.stringify(sorted);
|
|
12156
|
+
}
|
|
12157
|
+
function delay(ms) {
|
|
12158
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
12159
|
+
}
|
|
12160
|
+
function buildDesiredQueues(config2) {
|
|
12161
|
+
const queues = [];
|
|
12162
|
+
for (const [platformKey, platformSchedule] of Object.entries(config2.platforms)) {
|
|
12163
|
+
const normalizedPlatform = normalizePlatformName(platformKey);
|
|
12164
|
+
if (!platformSchedule.byClipType) continue;
|
|
12165
|
+
for (const [clipType, clipTypeSchedule] of Object.entries(platformSchedule.byClipType)) {
|
|
12166
|
+
const queueName = `${normalizedPlatform}-${clipType}`;
|
|
12167
|
+
const slots = [];
|
|
12168
|
+
for (const timeSlot of clipTypeSchedule.slots) {
|
|
12169
|
+
for (const day of timeSlot.days) {
|
|
12170
|
+
slots.push({ dayOfWeek: DAY_MAP[day], time: timeSlot.time });
|
|
12171
|
+
}
|
|
12172
|
+
}
|
|
12173
|
+
if (slots.length > 0) {
|
|
12174
|
+
queues.push({ name: queueName, slots });
|
|
12175
|
+
}
|
|
12176
|
+
}
|
|
12177
|
+
}
|
|
12178
|
+
return queues;
|
|
12179
|
+
}
|
|
12180
|
+
async function syncQueuesToLate(options) {
|
|
12181
|
+
const reshuffle = options?.reshuffle ?? false;
|
|
12182
|
+
const dryRun = options?.dryRun ?? false;
|
|
12183
|
+
const deleteOrphans = options?.deleteOrphans ?? false;
|
|
12184
|
+
const result = {
|
|
12185
|
+
created: [],
|
|
12186
|
+
updated: [],
|
|
12187
|
+
deleted: [],
|
|
12188
|
+
unchanged: [],
|
|
12189
|
+
errors: []
|
|
12190
|
+
};
|
|
12191
|
+
const config2 = await loadScheduleConfig();
|
|
12192
|
+
const timezone = config2.timezone;
|
|
12193
|
+
const client = new LateApiClient();
|
|
12194
|
+
const profiles = await client.listProfiles();
|
|
12195
|
+
if (profiles.length === 0) {
|
|
12196
|
+
throw new Error("No Late API profiles found \u2014 cannot sync queues");
|
|
12197
|
+
}
|
|
12198
|
+
const profileId = profiles[0]._id;
|
|
12199
|
+
logger_default.info(`Using Late profile "${profiles[0].name}" (${profileId})`);
|
|
12200
|
+
const desiredQueues = buildDesiredQueues(config2);
|
|
12201
|
+
logger_default.info(`Built ${desiredQueues.length} queue definitions from schedule.json`);
|
|
12202
|
+
const existingData = await client.listQueues(profileId, true);
|
|
12203
|
+
const existingQueues = existingData.queues.map((q) => ({
|
|
12204
|
+
_id: q._id,
|
|
12205
|
+
name: q.name,
|
|
12206
|
+
slots: q.slots,
|
|
12207
|
+
active: q.active
|
|
12208
|
+
}));
|
|
12209
|
+
logger_default.info(`Found ${existingQueues.length} existing queues in Late`);
|
|
12210
|
+
const existingByName = /* @__PURE__ */ new Map();
|
|
12211
|
+
for (const eq of existingQueues) {
|
|
12212
|
+
existingByName.set(eq.name, eq);
|
|
12213
|
+
}
|
|
12214
|
+
const desiredNames = /* @__PURE__ */ new Set();
|
|
12215
|
+
for (const desired of desiredQueues) {
|
|
12216
|
+
desiredNames.add(desired.name);
|
|
12217
|
+
const existing = existingByName.get(desired.name);
|
|
12218
|
+
if (existing) {
|
|
12219
|
+
const existingKey = slotsToSortedKey(existing.slots);
|
|
12220
|
+
const desiredKey = slotsToSortedKey(desired.slots);
|
|
12221
|
+
if (existingKey === desiredKey) {
|
|
12222
|
+
logger_default.debug(`Queue "${desired.name}" unchanged`);
|
|
12223
|
+
result.unchanged.push(desired.name);
|
|
12224
|
+
} else {
|
|
12225
|
+
logger_default.info(`Queue "${desired.name}" slots differ \u2014 updating`);
|
|
12226
|
+
if (!dryRun) {
|
|
12227
|
+
try {
|
|
12228
|
+
await client.updateQueue({
|
|
12229
|
+
profileId,
|
|
12230
|
+
queueId: existing._id,
|
|
12231
|
+
name: desired.name,
|
|
12232
|
+
timezone,
|
|
12233
|
+
slots: desired.slots,
|
|
12234
|
+
reshuffleExisting: reshuffle
|
|
12235
|
+
});
|
|
12236
|
+
result.updated.push(desired.name);
|
|
12237
|
+
} catch (err) {
|
|
12238
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
12239
|
+
logger_default.error(`Failed to update queue "${desired.name}": ${message}`);
|
|
12240
|
+
result.errors.push({ queueName: desired.name, error: message });
|
|
12241
|
+
}
|
|
12242
|
+
} else {
|
|
12243
|
+
logger_default.info(`[DRY RUN] Would update queue "${desired.name}"`);
|
|
12244
|
+
result.updated.push(desired.name);
|
|
12245
|
+
}
|
|
12246
|
+
await delay(RATE_LIMIT_DELAY_MS);
|
|
12247
|
+
}
|
|
12248
|
+
} else {
|
|
12249
|
+
logger_default.info(`Queue "${desired.name}" does not exist \u2014 creating`);
|
|
12250
|
+
if (!dryRun) {
|
|
12251
|
+
try {
|
|
12252
|
+
await client.createQueue({
|
|
12253
|
+
profileId,
|
|
12254
|
+
name: desired.name,
|
|
12255
|
+
timezone,
|
|
12256
|
+
slots: desired.slots,
|
|
12257
|
+
active: true
|
|
12258
|
+
});
|
|
12259
|
+
result.created.push(desired.name);
|
|
12260
|
+
} catch (err) {
|
|
12261
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
12262
|
+
logger_default.error(`Failed to create queue "${desired.name}": ${message}`);
|
|
12263
|
+
result.errors.push({ queueName: desired.name, error: message });
|
|
11267
12264
|
}
|
|
11268
|
-
|
|
12265
|
+
} else {
|
|
12266
|
+
logger_default.info(`[DRY RUN] Would create queue "${desired.name}"`);
|
|
12267
|
+
result.created.push(desired.name);
|
|
11269
12268
|
}
|
|
11270
|
-
|
|
11271
|
-
|
|
11272
|
-
|
|
11273
|
-
|
|
11274
|
-
|
|
12269
|
+
await delay(RATE_LIMIT_DELAY_MS);
|
|
12270
|
+
}
|
|
12271
|
+
}
|
|
12272
|
+
if (deleteOrphans) {
|
|
12273
|
+
for (const existing of existingQueues) {
|
|
12274
|
+
if (!desiredNames.has(existing.name)) {
|
|
12275
|
+
logger_default.info(`Orphan queue "${existing.name}" \u2014 deleting`);
|
|
12276
|
+
if (!dryRun) {
|
|
12277
|
+
try {
|
|
12278
|
+
await client.deleteQueue(profileId, existing._id);
|
|
12279
|
+
result.deleted.push(existing.name);
|
|
12280
|
+
} catch (err) {
|
|
12281
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
12282
|
+
logger_default.error(`Failed to delete orphan queue "${existing.name}": ${message}`);
|
|
12283
|
+
result.errors.push({ queueName: existing.name, error: message });
|
|
12284
|
+
}
|
|
12285
|
+
} else {
|
|
12286
|
+
logger_default.info(`[DRY RUN] Would delete orphan queue "${existing.name}"`);
|
|
12287
|
+
result.deleted.push(existing.name);
|
|
11275
12288
|
}
|
|
12289
|
+
await delay(RATE_LIMIT_DELAY_MS);
|
|
11276
12290
|
}
|
|
11277
|
-
bookedMap.set(newSlotMs, {
|
|
11278
|
-
scheduledFor: newSlotDatetime,
|
|
11279
|
-
source: "late",
|
|
11280
|
-
postId: latePostId,
|
|
11281
|
-
platform: itemPlatform,
|
|
11282
|
-
ideaLinked: isIdea,
|
|
11283
|
-
ideaIds: item.metadata.ideaIds
|
|
11284
|
-
});
|
|
11285
|
-
logger_default.info(`Rescheduled ${label}: ${oldSlot ?? "unscheduled"} \u2192 ${newSlotDatetime}`);
|
|
11286
|
-
result.details.push({ itemId: item.id, platform: itemPlatform, latePostId, oldSlot, newSlot: newSlotDatetime });
|
|
11287
|
-
result.rescheduled++;
|
|
11288
|
-
} catch (err) {
|
|
11289
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
11290
|
-
logger_default.error(`Failed to reschedule ${label}: ${msg}`);
|
|
11291
|
-
result.details.push({ itemId: item.id, platform: itemPlatform, latePostId, oldSlot, newSlot: null, error: msg });
|
|
11292
|
-
result.failed++;
|
|
11293
12291
|
}
|
|
11294
12292
|
}
|
|
11295
|
-
|
|
11296
|
-
|
|
11297
|
-
}
|
|
11298
|
-
async function rescheduleIdeaPosts(options) {
|
|
11299
|
-
return rescheduleAllPosts(options);
|
|
11300
|
-
}
|
|
11301
|
-
async function getScheduleCalendar(startDate, endDate) {
|
|
11302
|
-
const bookedMap = await buildBookedMap();
|
|
11303
|
-
let filtered = [...bookedMap.values()].filter((slot) => slot.source === "local" || slot.status === "scheduled").map((slot) => ({
|
|
11304
|
-
platform: slot.platform,
|
|
11305
|
-
scheduledFor: slot.scheduledFor,
|
|
11306
|
-
source: slot.source,
|
|
11307
|
-
postId: slot.postId,
|
|
11308
|
-
itemId: slot.itemId
|
|
11309
|
-
}));
|
|
11310
|
-
if (startDate) {
|
|
11311
|
-
const startMs = startDate.getTime();
|
|
11312
|
-
filtered = filtered.filter((slot) => normalizeDateTime(slot.scheduledFor) >= startMs);
|
|
11313
|
-
}
|
|
11314
|
-
if (endDate) {
|
|
11315
|
-
const endMs = endDate.getTime();
|
|
11316
|
-
filtered = filtered.filter((slot) => normalizeDateTime(slot.scheduledFor) <= endMs);
|
|
12293
|
+
if (!dryRun) {
|
|
12294
|
+
await refreshQueueMappings();
|
|
11317
12295
|
}
|
|
11318
|
-
|
|
11319
|
-
|
|
12296
|
+
logger_default.info(
|
|
12297
|
+
`Queue sync complete: ${result.created.length} created, ${result.updated.length} updated, ${result.deleted.length} deleted, ${result.unchanged.length} unchanged, ${result.errors.length} errors`
|
|
12298
|
+
);
|
|
12299
|
+
return result;
|
|
11320
12300
|
}
|
|
11321
|
-
var
|
|
11322
|
-
var
|
|
11323
|
-
"src/L3-services/
|
|
12301
|
+
var DAY_MAP, PLATFORM_NAME_MAP, RATE_LIMIT_DELAY_MS;
|
|
12302
|
+
var init_queueSync = __esm({
|
|
12303
|
+
"src/L3-services/queueSync/queueSync.ts"() {
|
|
11324
12304
|
"use strict";
|
|
11325
12305
|
init_lateApi();
|
|
11326
|
-
init_configLogger();
|
|
11327
|
-
init_postStore();
|
|
11328
12306
|
init_scheduleConfig();
|
|
11329
|
-
|
|
11330
|
-
|
|
11331
|
-
|
|
12307
|
+
init_queueMapping();
|
|
12308
|
+
init_configLogger();
|
|
12309
|
+
DAY_MAP = {
|
|
12310
|
+
sun: 0,
|
|
12311
|
+
mon: 1,
|
|
12312
|
+
tue: 2,
|
|
12313
|
+
wed: 3,
|
|
12314
|
+
thu: 4,
|
|
12315
|
+
fri: 5,
|
|
12316
|
+
sat: 6
|
|
12317
|
+
};
|
|
12318
|
+
PLATFORM_NAME_MAP = { twitter: "x" };
|
|
12319
|
+
RATE_LIMIT_DELAY_MS = 200;
|
|
11332
12320
|
}
|
|
11333
12321
|
});
|
|
11334
12322
|
|
|
@@ -11502,6 +12490,13 @@ function formatDate2(iso, timezone) {
|
|
|
11502
12490
|
}
|
|
11503
12491
|
async function runReschedule(options = {}) {
|
|
11504
12492
|
initConfig();
|
|
12493
|
+
if (options.queue) {
|
|
12494
|
+
const { syncQueuesToLate: syncQueuesToLate2 } = await Promise.resolve().then(() => (init_queueSync(), queueSync_exports));
|
|
12495
|
+
logger_default.info("Using Late API queue reshuffle for rescheduling...");
|
|
12496
|
+
const result2 = await syncQueuesToLate2({ reshuffle: true, dryRun: options.dryRun });
|
|
12497
|
+
logger_default.info(`Queue reshuffle complete: ${result2.updated.length} queues reshuffled`);
|
|
12498
|
+
return;
|
|
12499
|
+
}
|
|
11505
12500
|
const scheduleConfig = await loadScheduleConfig();
|
|
11506
12501
|
const { timezone } = scheduleConfig;
|
|
11507
12502
|
if (options.dryRun) {
|
|
@@ -11543,6 +12538,7 @@ var init_reschedule = __esm({
|
|
|
11543
12538
|
init_environment();
|
|
11544
12539
|
init_scheduler();
|
|
11545
12540
|
init_scheduleConfig();
|
|
12541
|
+
init_configLogger();
|
|
11546
12542
|
PLATFORM_ICON2 = {
|
|
11547
12543
|
tiktok: "\u{1F3B5}",
|
|
11548
12544
|
youtube: "\u25B6\uFE0F",
|
|
@@ -11554,6 +12550,132 @@ var init_reschedule = __esm({
|
|
|
11554
12550
|
}
|
|
11555
12551
|
});
|
|
11556
12552
|
|
|
12553
|
+
// src/L7-app/commands/syncQueues.ts
|
|
12554
|
+
var syncQueues_exports = {};
|
|
12555
|
+
__export(syncQueues_exports, {
|
|
12556
|
+
runSyncQueues: () => runSyncQueues
|
|
12557
|
+
});
|
|
12558
|
+
async function runSyncQueues(options = {}) {
|
|
12559
|
+
initConfig();
|
|
12560
|
+
logger_default.info("Syncing schedule.json to Late API queues...");
|
|
12561
|
+
if (options.dryRun) logger_default.info("[DRY RUN] No changes will be made");
|
|
12562
|
+
const result = await syncQueuesToLate({
|
|
12563
|
+
reshuffle: options.reshuffle,
|
|
12564
|
+
dryRun: options.dryRun,
|
|
12565
|
+
deleteOrphans: options.deleteOrphans
|
|
12566
|
+
});
|
|
12567
|
+
if (result.created.length > 0) {
|
|
12568
|
+
logger_default.info(`Created ${result.created.length} queues: ${result.created.join(", ")}`);
|
|
12569
|
+
}
|
|
12570
|
+
if (result.updated.length > 0) {
|
|
12571
|
+
logger_default.info(`Updated ${result.updated.length} queues: ${result.updated.join(", ")}`);
|
|
12572
|
+
}
|
|
12573
|
+
if (result.deleted.length > 0) {
|
|
12574
|
+
logger_default.info(`Deleted ${result.deleted.length} queues: ${result.deleted.join(", ")}`);
|
|
12575
|
+
}
|
|
12576
|
+
if (result.unchanged.length > 0) {
|
|
12577
|
+
logger_default.info(`Unchanged: ${result.unchanged.length} queues`);
|
|
12578
|
+
}
|
|
12579
|
+
if (result.errors.length > 0) {
|
|
12580
|
+
for (const err of result.errors) {
|
|
12581
|
+
logger_default.error(`Error syncing ${err.queueName}: ${err.error}`);
|
|
12582
|
+
}
|
|
12583
|
+
}
|
|
12584
|
+
const total = result.created.length + result.updated.length + result.deleted.length + result.unchanged.length;
|
|
12585
|
+
logger_default.info(`Sync complete: ${total} queues processed`);
|
|
12586
|
+
}
|
|
12587
|
+
var init_syncQueues = __esm({
|
|
12588
|
+
"src/L7-app/commands/syncQueues.ts"() {
|
|
12589
|
+
"use strict";
|
|
12590
|
+
init_environment();
|
|
12591
|
+
init_configLogger();
|
|
12592
|
+
init_queueSync();
|
|
12593
|
+
}
|
|
12594
|
+
});
|
|
12595
|
+
|
|
12596
|
+
// src/L1-infra/spec/specLoader.ts
|
|
12597
|
+
var specLoader_exports = {};
|
|
12598
|
+
__export(specLoader_exports, {
|
|
12599
|
+
loadSpec: () => loadSpec
|
|
12600
|
+
});
|
|
12601
|
+
import { readFile, readdir } from "fs/promises";
|
|
12602
|
+
import { join as join8, extname as extname2 } from "path";
|
|
12603
|
+
import { parse as parseYaml } from "yaml";
|
|
12604
|
+
function isFilePath(nameOrPath) {
|
|
12605
|
+
return nameOrPath.includes("/") || nameOrPath.includes("\\") || nameOrPath.endsWith(".yaml") || nameOrPath.endsWith(".yml") || nameOrPath.endsWith(".json");
|
|
12606
|
+
}
|
|
12607
|
+
function parseFileContent(raw, filePath) {
|
|
12608
|
+
const ext = extname2(filePath).toLowerCase();
|
|
12609
|
+
if (ext === ".json") {
|
|
12610
|
+
return JSON.parse(raw);
|
|
12611
|
+
}
|
|
12612
|
+
return parseYaml(raw);
|
|
12613
|
+
}
|
|
12614
|
+
async function fileExists4(filePath) {
|
|
12615
|
+
try {
|
|
12616
|
+
await readFile(filePath);
|
|
12617
|
+
return true;
|
|
12618
|
+
} catch {
|
|
12619
|
+
return false;
|
|
12620
|
+
}
|
|
12621
|
+
}
|
|
12622
|
+
async function listSpecFiles(specsDir) {
|
|
12623
|
+
try {
|
|
12624
|
+
const entries = await readdir(specsDir);
|
|
12625
|
+
return entries.filter((e) => e.endsWith(".yaml") || e.endsWith(".yml") || e.endsWith(".json"));
|
|
12626
|
+
} catch {
|
|
12627
|
+
return [];
|
|
12628
|
+
}
|
|
12629
|
+
}
|
|
12630
|
+
function validateAndMerge(raw, source) {
|
|
12631
|
+
const errors = validateSpec(raw);
|
|
12632
|
+
if (errors.length > 0) {
|
|
12633
|
+
const details = errors.map((e) => ` - ${e.path ? e.path + ": " : ""}${e.message}`).join("\n");
|
|
12634
|
+
throw new Error(`Invalid pipeline spec from ${source}:
|
|
12635
|
+
${details}`);
|
|
12636
|
+
}
|
|
12637
|
+
return mergeWithDefaults(raw);
|
|
12638
|
+
}
|
|
12639
|
+
async function loadSpec(nameOrPath, repoRoot) {
|
|
12640
|
+
if (isFilePath(nameOrPath)) {
|
|
12641
|
+
let raw;
|
|
12642
|
+
try {
|
|
12643
|
+
raw = await readFile(nameOrPath, "utf-8");
|
|
12644
|
+
} catch (err) {
|
|
12645
|
+
throw new Error(`Failed to read spec file '${nameOrPath}': ${err.message}`);
|
|
12646
|
+
}
|
|
12647
|
+
const parsed = parseFileContent(raw, nameOrPath);
|
|
12648
|
+
return validateAndMerge(parsed, nameOrPath);
|
|
12649
|
+
}
|
|
12650
|
+
if (isPresetName(nameOrPath)) {
|
|
12651
|
+
return getPreset(nameOrPath);
|
|
12652
|
+
}
|
|
12653
|
+
const specsDir = join8(repoRoot, "pipeline-specs");
|
|
12654
|
+
const conventionPath = join8(specsDir, `${nameOrPath}.yaml`);
|
|
12655
|
+
if (await fileExists4(conventionPath)) {
|
|
12656
|
+
const raw = await readFile(conventionPath, "utf-8");
|
|
12657
|
+
const parsed = parseFileContent(raw, conventionPath);
|
|
12658
|
+
return validateAndMerge(parsed, conventionPath);
|
|
12659
|
+
}
|
|
12660
|
+
const availableFiles = await listSpecFiles(specsDir);
|
|
12661
|
+
const presetList = ["full", "clean", "minimal"];
|
|
12662
|
+
const parts = [`Unknown spec '${nameOrPath}'.`];
|
|
12663
|
+
parts.push(` Built-in presets: ${presetList.join(", ")}`);
|
|
12664
|
+
if (availableFiles.length > 0) {
|
|
12665
|
+
const names = availableFiles.map((f) => f.replace(/\.(yaml|yml|json)$/, ""));
|
|
12666
|
+
parts.push(` Custom specs in pipeline-specs/: ${names.join(", ")}`);
|
|
12667
|
+
} else {
|
|
12668
|
+
parts.push(` No custom specs found in pipeline-specs/`);
|
|
12669
|
+
}
|
|
12670
|
+
throw new Error(parts.join("\n"));
|
|
12671
|
+
}
|
|
12672
|
+
var init_specLoader = __esm({
|
|
12673
|
+
"src/L1-infra/spec/specLoader.ts"() {
|
|
12674
|
+
"use strict";
|
|
12675
|
+
init_pipelineSpec();
|
|
12676
|
+
}
|
|
12677
|
+
});
|
|
12678
|
+
|
|
11557
12679
|
// src/L1-infra/cli/cli.ts
|
|
11558
12680
|
import { Command } from "commander";
|
|
11559
12681
|
import readline from "readline";
|
|
@@ -11577,7 +12699,7 @@ init_environment();
|
|
|
11577
12699
|
init_paths();
|
|
11578
12700
|
init_fileSystem();
|
|
11579
12701
|
init_configLogger();
|
|
11580
|
-
|
|
12702
|
+
init_types2();
|
|
11581
12703
|
var FileWatcher = class _FileWatcher extends EventEmitter {
|
|
11582
12704
|
watchFolder;
|
|
11583
12705
|
watcher = null;
|
|
@@ -13260,7 +14382,7 @@ var SocialPostAsset = class extends TextAsset {
|
|
|
13260
14382
|
// src/L5-assets/ShortVideoAsset.ts
|
|
13261
14383
|
init_paths();
|
|
13262
14384
|
init_fileSystem();
|
|
13263
|
-
|
|
14385
|
+
init_types2();
|
|
13264
14386
|
|
|
13265
14387
|
// src/L5-assets/thumbnailGeneration.ts
|
|
13266
14388
|
init_paths();
|
|
@@ -13520,7 +14642,7 @@ var ShortVideoAsset = class extends VideoAsset {
|
|
|
13520
14642
|
// src/L5-assets/MediumClipAsset.ts
|
|
13521
14643
|
init_paths();
|
|
13522
14644
|
init_fileSystem();
|
|
13523
|
-
|
|
14645
|
+
init_types2();
|
|
13524
14646
|
var MediumClipAsset = class extends VideoAsset {
|
|
13525
14647
|
/** Parent video this clip was extracted from */
|
|
13526
14648
|
parent;
|
|
@@ -13930,7 +15052,39 @@ init_videoOperations();
|
|
|
13930
15052
|
init_fileSystem();
|
|
13931
15053
|
init_paths();
|
|
13932
15054
|
init_configLogger();
|
|
13933
|
-
|
|
15055
|
+
function buildShortsSystemPrompt(clipConfig) {
|
|
15056
|
+
const minDuration = clipConfig?.duration?.min ?? 15;
|
|
15057
|
+
const maxDuration = clipConfig?.duration?.max ?? 60;
|
|
15058
|
+
const minViralScore = clipConfig?.minViralScore ?? 8;
|
|
15059
|
+
const maxClips = clipConfig?.maxClips ?? 5;
|
|
15060
|
+
const strategy = clipConfig?.strategy ?? "hook-first";
|
|
15061
|
+
const isHookFirst = strategy === "hook-first";
|
|
15062
|
+
const hookFirstSection = isHookFirst ? `
|
|
15063
|
+
### Hook-First Video Ordering
|
|
15064
|
+
|
|
15065
|
+
If a short's natural content flows A\u2192B\u2192C\u2192D, the final short should play as D\u2192A\u2192B\u2192C \u2014 the **payoff moment (D) is moved to the front as the hook**, then the content plays from the beginning up to that point. The hook does NOT repeat.
|
|
15066
|
+
|
|
15067
|
+
**How to implement:**
|
|
15068
|
+
1. Plan the content as normal (full story A\u2192D)
|
|
15069
|
+
2. Identify the single most arresting 2-5 second moment \u2014 usually the payoff, punchline, or emotional peak
|
|
15070
|
+
3. That moment becomes the FIRST segment in the segments array
|
|
15071
|
+
4. The remaining content plays chronologically from start to just before the hook
|
|
15072
|
+
5. Example: content [120s\u2013150s], best moment [145s\u2013150s] \u2192 segments: [{start: 145, end: 150}, {start: 120, end: 145}]
|
|
15073
|
+
|
|
15074
|
+
**Hook quality rules (NEVER violate):**
|
|
15075
|
+
- The hook segment MUST start and end on a **complete sentence or clause boundary**
|
|
15076
|
+
- The hook MUST be a **self-contained, complete thought** \u2014 understandable without prior context
|
|
15077
|
+
- If no moment qualifies as a clean hook, **keep segments chronological** and use hook text only` : `
|
|
15078
|
+
### Chronological Video Ordering
|
|
15079
|
+
|
|
15080
|
+
Segments MUST be ordered chronologically \u2014 preserve the original video timeline. Do NOT reorder segments for hook-first ordering. Instead, rely on a strong verbal hook (the \`hook\` text field) as a text overlay to capture attention in the first 3 seconds while the content plays in its natural order.
|
|
15081
|
+
|
|
15082
|
+
**How to implement:**
|
|
15083
|
+
1. Plan the content as normal \u2014 segments play in order from the video
|
|
15084
|
+
2. Identify the most compelling hook text that teases the payoff
|
|
15085
|
+
3. All segments in the segments array must be in ascending time order
|
|
15086
|
+
4. The hook text overlay provides the attention-grab, not segment reordering`;
|
|
15087
|
+
return `You are a viral short-form video strategist. Your job is to analyze a video transcript with word-level timestamps and extract the **most compelling, shareable moments** as shorts (${minDuration}\u2013${maxDuration} seconds each).
|
|
13934
15088
|
|
|
13935
15089
|
## Core Philosophy: Quality Over Quantity
|
|
13936
15090
|
|
|
@@ -13947,7 +15101,7 @@ Design every clip to maximize rewatches and shares, not passive likes.
|
|
|
13947
15101
|
## Your workflow
|
|
13948
15102
|
1. Read the transcript and note the total duration.
|
|
13949
15103
|
2. Work through the transcript **section by section**. For each chunk, identify moments with genuine viral potential.
|
|
13950
|
-
3. For each potential short, score it using the Viral Score Framework (see below). **Only extract clips scoring
|
|
15104
|
+
3. For each potential short, score it using the Viral Score Framework (see below). **Only extract clips scoring ${minViralScore} or higher.**
|
|
13951
15105
|
4. Call **add_shorts** for each batch of qualifying shorts. You can call it as many times as needed.
|
|
13952
15106
|
5. After your first pass, call **review_shorts** to see everything you've planned so far.
|
|
13953
15107
|
6. Review critically: Would YOU share each of these? Could any be combined into stronger composites? Are there moments you underscored?
|
|
@@ -13962,7 +15116,7 @@ Viral Score = (Hook Strength \xD7 3) + (Emotional Intensity \xD7 2) +
|
|
|
13962
15116
|
(Replay Potential \xD7 2)
|
|
13963
15117
|
|
|
13964
15118
|
Maximum score: 60 \u2192 Normalized to 1-20 scale (divide by 3)
|
|
13965
|
-
Minimum to extract:
|
|
15119
|
+
Minimum to extract: ${minViralScore}/20
|
|
13966
15120
|
\`\`\`
|
|
13967
15121
|
|
|
13968
15122
|
| Factor | 1 (Weak) | 3 (Moderate) | 5 (Strong) |
|
|
@@ -13998,22 +15152,7 @@ Minimum to extract: 8/20
|
|
|
13998
15152
|
| **result-first** | Show the outcome immediately, then explain how | Tutorials, before/after |
|
|
13999
15153
|
| **bold-claim** | Make a specific, surprising statement of fact | Data-driven, authority content |
|
|
14000
15154
|
| **question** | "Want to know why X?" \u2014 engage curiosity directly | Engagement-focused, relatable |
|
|
14001
|
-
|
|
14002
|
-
### Hook-First Video Ordering
|
|
14003
|
-
|
|
14004
|
-
If a short's natural content flows A\u2192B\u2192C\u2192D, the final short should play as D\u2192A\u2192B\u2192C \u2014 the **payoff moment (D) is moved to the front as the hook**, then the content plays from the beginning up to that point. The hook does NOT repeat.
|
|
14005
|
-
|
|
14006
|
-
**How to implement:**
|
|
14007
|
-
1. Plan the content as normal (full story A\u2192D)
|
|
14008
|
-
2. Identify the single most arresting 2-5 second moment \u2014 usually the payoff, punchline, or emotional peak
|
|
14009
|
-
3. That moment becomes the FIRST segment in the segments array
|
|
14010
|
-
4. The remaining content plays chronologically from start to just before the hook
|
|
14011
|
-
5. Example: content [120s\u2013150s], best moment [145s\u2013150s] \u2192 segments: [{start: 145, end: 150}, {start: 120, end: 145}]
|
|
14012
|
-
|
|
14013
|
-
**Hook quality rules (NEVER violate):**
|
|
14014
|
-
- The hook segment MUST start and end on a **complete sentence or clause boundary**
|
|
14015
|
-
- The hook MUST be a **self-contained, complete thought** \u2014 understandable without prior context
|
|
14016
|
-
- If no moment qualifies as a clean hook, **keep segments chronological** and use hook text only
|
|
15155
|
+
${hookFirstSection}
|
|
14017
15156
|
|
|
14018
15157
|
## Narrative structures (classify every short)
|
|
14019
15158
|
|
|
@@ -14064,14 +15203,14 @@ Composites (multi-segment shorts) often make the **best** shorts:
|
|
|
14064
15203
|
|
|
14065
15204
|
## Rules
|
|
14066
15205
|
|
|
14067
|
-
1. Each short must be
|
|
15206
|
+
1. Each short must be ${minDuration}-${maxDuration} seconds total duration.
|
|
14068
15207
|
2. Timestamps must align to word boundaries from the transcript.
|
|
14069
15208
|
3. Prefer natural sentence boundaries for clean cuts.
|
|
14070
15209
|
4. Every short needs a catchy, descriptive title (5-10 words).
|
|
14071
15210
|
5. Tags should be lowercase, no hashes, 3-6 per short.
|
|
14072
15211
|
6. A 1-second buffer is automatically added around each segment boundary.
|
|
14073
15212
|
7. Avoid significant timestamp overlap between shorts.
|
|
14074
|
-
8. **Minimum viral score of
|
|
15213
|
+
8. **Minimum viral score of ${minViralScore}/20 to extract.** Be ruthless about quality.
|
|
14075
15214
|
9. Every short MUST have a hook, hookType, emotionalTrigger, viralScore, narrativeStructure, and shareReason.
|
|
14076
15215
|
|
|
14077
15216
|
## Using Clip Direction
|
|
@@ -14084,8 +15223,10 @@ You may receive AI-generated clip direction with suggested shorts. Use these as
|
|
|
14084
15223
|
|
|
14085
15224
|
Before adding a short, ask yourself: **"Would I interrupt someone to show them this?"**
|
|
14086
15225
|
- If YES \u2192 strong clip, add it
|
|
14087
|
-
- If "maybe, it's interesting" \u2192 score it honestly and only keep if \
|
|
15226
|
+
- If "maybe, it's interesting" \u2192 score it honestly and only keep if \u2265${minViralScore}
|
|
14088
15227
|
- If NO \u2192 drop it, no matter how "complete" the topic coverage feels`;
|
|
15228
|
+
}
|
|
15229
|
+
var SYSTEM_PROMPT2 = buildShortsSystemPrompt();
|
|
14089
15230
|
var ADD_SHORTS_SCHEMA = {
|
|
14090
15231
|
type: "object",
|
|
14091
15232
|
properties: {
|
|
@@ -14235,20 +15376,22 @@ Review critically:
|
|
|
14235
15376
|
return this.isFinalized;
|
|
14236
15377
|
}
|
|
14237
15378
|
};
|
|
14238
|
-
async function generateShorts(video, transcript, model, clipDirection, webcamOverride, ideas) {
|
|
14239
|
-
const
|
|
15379
|
+
async function generateShorts(video, transcript, model, clipDirection, webcamOverride, ideas, clipConfig, generateVariants) {
|
|
15380
|
+
const basePrompt = clipConfig ? buildShortsSystemPrompt(clipConfig) : SYSTEM_PROMPT2;
|
|
15381
|
+
const systemPrompt = basePrompt + (ideas?.length ? buildIdeaContext(ideas) : "");
|
|
14240
15382
|
const agent = new ShortsAgent(systemPrompt, model);
|
|
14241
15383
|
const transcriptLines = transcript.segments.map((seg) => {
|
|
14242
15384
|
const words = seg.words.map((w) => `[${w.start.toFixed(2)}-${w.end.toFixed(2)}] ${w.word}`).join(" ");
|
|
14243
15385
|
return `[${seg.start.toFixed(2)}s \u2013 ${seg.end.toFixed(2)}s] ${seg.text}
|
|
14244
15386
|
Words: ${words}`;
|
|
14245
15387
|
});
|
|
15388
|
+
const minViralScore = clipConfig?.minViralScore ?? 8;
|
|
14246
15389
|
const promptParts = [
|
|
14247
15390
|
`Analyze the following transcript (${transcript.duration.toFixed(0)}s total) and find the most viral-worthy moments for shorts.
|
|
14248
15391
|
`,
|
|
14249
15392
|
`Video: ${video.filename}`,
|
|
14250
15393
|
`Duration: ${transcript.duration.toFixed(1)}s`,
|
|
14251
|
-
`Focus on quality over quantity \u2014 only extract clips scoring
|
|
15394
|
+
`Focus on quality over quantity \u2014 only extract clips scoring ${minViralScore}+ on the viral score framework. Every clip must have a hook, hookType, emotionalTrigger, viralScore, narrativeStructure, and shareReason.
|
|
14252
15395
|
`,
|
|
14253
15396
|
"--- TRANSCRIPT ---\n",
|
|
14254
15397
|
transcriptLines.join("\n\n"),
|
|
@@ -14300,23 +15443,27 @@ Words: ${words}`;
|
|
|
14300
15443
|
} else {
|
|
14301
15444
|
await extractCompositeClip2(video.repoPath, segments, outputPath);
|
|
14302
15445
|
}
|
|
14303
|
-
let
|
|
14304
|
-
|
|
14305
|
-
|
|
14306
|
-
|
|
14307
|
-
|
|
14308
|
-
|
|
14309
|
-
|
|
14310
|
-
|
|
14311
|
-
|
|
14312
|
-
|
|
14313
|
-
|
|
14314
|
-
|
|
14315
|
-
|
|
15446
|
+
let clipVariants;
|
|
15447
|
+
if (generateVariants !== false) {
|
|
15448
|
+
try {
|
|
15449
|
+
const defaultPlatforms = ["tiktok", "youtube-shorts", "instagram-reels", "instagram-feed", "linkedin"];
|
|
15450
|
+
const results = await generatePlatformVariants2(outputPath, shortsDir, shortSlug, defaultPlatforms, { webcamOverride });
|
|
15451
|
+
if (results.length > 0) {
|
|
15452
|
+
clipVariants = results.map((v) => ({
|
|
15453
|
+
path: v.path,
|
|
15454
|
+
aspectRatio: v.aspectRatio,
|
|
15455
|
+
platform: v.platform,
|
|
15456
|
+
width: v.width,
|
|
15457
|
+
height: v.height
|
|
15458
|
+
}));
|
|
15459
|
+
logger_default.info(`[ShortsAgent] Generated ${clipVariants.length} platform variants for: ${plan.title}`);
|
|
15460
|
+
}
|
|
15461
|
+
} catch (err) {
|
|
15462
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
15463
|
+
logger_default.warn(`[ShortsAgent] Platform variant generation failed for ${plan.title}: ${message}`);
|
|
14316
15464
|
}
|
|
14317
|
-
}
|
|
14318
|
-
|
|
14319
|
-
logger_default.warn(`[ShortsAgent] Platform variant generation failed for ${plan.title}: ${message}`);
|
|
15465
|
+
} else {
|
|
15466
|
+
logger_default.info(`[ShortsAgent] Skipping variant generation for: ${plan.title} (disabled by spec)`);
|
|
14320
15467
|
}
|
|
14321
15468
|
let captionedPath;
|
|
14322
15469
|
try {
|
|
@@ -14331,8 +15478,8 @@ Words: ${words}`;
|
|
|
14331
15478
|
logger_default.warn(`[ShortsAgent] Caption burning failed for ${plan.title}: ${message}`);
|
|
14332
15479
|
captionedPath = void 0;
|
|
14333
15480
|
}
|
|
14334
|
-
if (
|
|
14335
|
-
const portraitVariants =
|
|
15481
|
+
if (clipVariants) {
|
|
15482
|
+
const portraitVariants = clipVariants.filter((v) => v.aspectRatio === "9:16");
|
|
14336
15483
|
if (portraitVariants.length > 0) {
|
|
14337
15484
|
try {
|
|
14338
15485
|
const hookText = plan.hook ?? plan.title;
|
|
@@ -14350,7 +15497,7 @@ Words: ${words}`;
|
|
|
14350
15497
|
logger_default.warn(`[ShortsAgent] Portrait caption burning failed for ${plan.title}: ${message}`);
|
|
14351
15498
|
}
|
|
14352
15499
|
}
|
|
14353
|
-
const nonPortraitVariants =
|
|
15500
|
+
const nonPortraitVariants = clipVariants.filter((v) => v.aspectRatio !== "9:16");
|
|
14354
15501
|
for (const variant of nonPortraitVariants) {
|
|
14355
15502
|
try {
|
|
14356
15503
|
const variantAssContent = segments.length === 1 ? generateStyledASSForSegment(transcript, segments[0].start, segments[0].end) : generateStyledASSForComposite(transcript, segments);
|
|
@@ -14401,7 +15548,7 @@ Words: ${words}`;
|
|
|
14401
15548
|
description: plan.description,
|
|
14402
15549
|
tags: plan.tags,
|
|
14403
15550
|
hook: plan.hook,
|
|
14404
|
-
variants,
|
|
15551
|
+
variants: clipVariants,
|
|
14405
15552
|
hookType: plan.hookType,
|
|
14406
15553
|
emotionalTrigger: plan.emotionalTrigger,
|
|
14407
15554
|
viralScore: plan.viralScore,
|
|
@@ -14424,7 +15571,15 @@ init_videoOperations();
|
|
|
14424
15571
|
init_fileSystem();
|
|
14425
15572
|
init_paths();
|
|
14426
15573
|
init_configLogger();
|
|
14427
|
-
|
|
15574
|
+
function buildMediumSystemPrompt(clipConfig) {
|
|
15575
|
+
const minDuration = clipConfig?.duration?.min ?? 60;
|
|
15576
|
+
const maxDuration = clipConfig?.duration?.max ?? 180;
|
|
15577
|
+
const minViralScore = clipConfig?.minViralScore ?? 10;
|
|
15578
|
+
const maxClips = clipConfig?.maxClips ?? 5;
|
|
15579
|
+
const minMinutes = Math.floor(minDuration / 60);
|
|
15580
|
+
const maxMinutes = Math.ceil(maxDuration / 60);
|
|
15581
|
+
const durationLabel = `${minMinutes}-${maxMinutes} minute`;
|
|
15582
|
+
return `You are a medium-form video content strategist. Your job is to analyze a video transcript with word-level timestamps and extract the **most valuable, engaging ${minMinutes}-${maxMinutes} minute segments** as standalone medium-form clips.
|
|
14428
15583
|
|
|
14429
15584
|
## Core Philosophy: Value Density Over Coverage
|
|
14430
15585
|
|
|
@@ -14441,7 +15596,7 @@ Design every clip to maximize saves and shares.
|
|
|
14441
15596
|
## Your workflow
|
|
14442
15597
|
1. Read the transcript and note the total duration.
|
|
14443
15598
|
2. Work through the transcript **section by section** (roughly 5-8 minute chunks). For each chunk, identify segments with genuine standalone value.
|
|
14444
|
-
3. For each potential clip, score it using the Viral Score Framework (see below). **Only extract clips scoring
|
|
15599
|
+
3. For each potential clip, score it using the Viral Score Framework (see below). **Only extract clips scoring ${minViralScore} or higher.**
|
|
14445
15600
|
4. Call **add_medium_clips** for each batch of clips you find. You can call it as many times as needed.
|
|
14446
15601
|
5. After your first pass, call **review_medium_clips** to see everything you've planned so far.
|
|
14447
15602
|
6. Review critically: Does each clip deliver clear, standalone value? Would someone save it? Could segments be combined into something stronger?
|
|
@@ -14456,7 +15611,7 @@ Viral Score = (Hook Strength \xD7 3) + (Emotional Intensity \xD7 2) +
|
|
|
14456
15611
|
(Replay Potential \xD7 2)
|
|
14457
15612
|
|
|
14458
15613
|
Maximum score: 60 \u2192 Normalized to 1-20 scale (divide by 3)
|
|
14459
|
-
Minimum to extract:
|
|
15614
|
+
Minimum to extract: ${minViralScore}/20 (higher bar than shorts \u2014 medium clips cost more to produce)
|
|
14460
15615
|
\`\`\`
|
|
14461
15616
|
|
|
14462
15617
|
| Factor | 1 (Weak) | 3 (Moderate) | 5 (Strong) |
|
|
@@ -14535,10 +15690,10 @@ Identify the PRIMARY emotion driving engagement:
|
|
|
14535
15690
|
|
|
14536
15691
|
## Duration optimization
|
|
14537
15692
|
|
|
14538
|
-
- **Sweet spot**:
|
|
14539
|
-
- **Maximum**:
|
|
14540
|
-
- **Under
|
|
14541
|
-
- **Over 120 seconds**: Requires multiple micro-hooks and exceptional pacing
|
|
15693
|
+
- **Sweet spot**: ${minDuration}-${Math.min(maxDuration, 120)} seconds (${minMinutes}-${Math.min(maxMinutes, 2)} minutes)
|
|
15694
|
+
- **Maximum**: ${maxDuration} seconds \u2014 only if retention quality is exceptional
|
|
15695
|
+
- **Under ${minDuration} seconds**: Too short for medium format \u2014 should be a short instead
|
|
15696
|
+
- **Over ${Math.min(maxDuration, 120)} seconds**: Requires multiple micro-hooks and exceptional pacing
|
|
14542
15697
|
|
|
14543
15698
|
## Differences from shorts
|
|
14544
15699
|
|
|
@@ -14560,7 +15715,7 @@ For compilations, segments must be in chronological order.
|
|
|
14560
15715
|
|
|
14561
15716
|
## Rules
|
|
14562
15717
|
|
|
14563
|
-
1. Each clip must be
|
|
15718
|
+
1. Each clip must be ${minDuration}-${maxDuration} seconds total duration.
|
|
14564
15719
|
2. Timestamps must align to word boundaries from the transcript.
|
|
14565
15720
|
3. Prefer natural sentence and paragraph boundaries for clean entry/exit points.
|
|
14566
15721
|
4. Each clip must be self-contained \u2014 a viewer with no other context should get value.
|
|
@@ -14568,7 +15723,7 @@ For compilations, segments must be in chronological order.
|
|
|
14568
15723
|
6. For compilations, specify segments in **chronological order**.
|
|
14569
15724
|
7. Tags should be lowercase, no hashes, 3-6 per clip.
|
|
14570
15725
|
8. A 1-second buffer is automatically added around each segment boundary.
|
|
14571
|
-
9. **Minimum viral score of
|
|
15726
|
+
9. **Minimum viral score of ${minViralScore}/20 to extract.** Medium clips cost more to produce \u2014 quality bar is higher.
|
|
14572
15727
|
10. Every clip MUST have hook, hookType, emotionalTrigger, viralScore, narrativeStructure, clipType, saveReason, and microHooks.
|
|
14573
15728
|
11. Avoid significant overlap with content that would work better as a short.
|
|
14574
15729
|
|
|
@@ -14576,7 +15731,7 @@ For compilations, segments must be in chronological order.
|
|
|
14576
15731
|
|
|
14577
15732
|
Before adding a clip, ask yourself: **"Would I bookmark this to come back to later?"**
|
|
14578
15733
|
- If YES \u2192 strong clip, add it
|
|
14579
|
-
- If "it's informative but not reference-worthy" \u2192 score it honestly and only keep if \
|
|
15734
|
+
- If "it's informative but not reference-worthy" \u2192 score it honestly and only keep if \u2265${minViralScore}
|
|
14580
15735
|
- If NO \u2192 drop it or consider if it works better as a short
|
|
14581
15736
|
|
|
14582
15737
|
## Using Clip Direction
|
|
@@ -14585,6 +15740,8 @@ You may receive AI-generated clip direction with suggested medium clips. Use the
|
|
|
14585
15740
|
- Feel free to adjust timestamps, combine suggestions, or ignore ones that don't work
|
|
14586
15741
|
- You may also find good clips NOT in the suggestions \u2014 always analyze the full transcript
|
|
14587
15742
|
- Pay special attention to suggested hooks and topic arcs \u2014 they come from multimodal analysis`;
|
|
15743
|
+
}
|
|
15744
|
+
var SYSTEM_PROMPT3 = buildMediumSystemPrompt();
|
|
14588
15745
|
var ADD_MEDIUM_CLIPS_SCHEMA = {
|
|
14589
15746
|
type: "object",
|
|
14590
15747
|
properties: {
|
|
@@ -14742,20 +15899,26 @@ Review critically:
|
|
|
14742
15899
|
return this.isFinalized;
|
|
14743
15900
|
}
|
|
14744
15901
|
};
|
|
14745
|
-
async function generateMediumClips(video, transcript, model, clipDirection, ideas) {
|
|
14746
|
-
const
|
|
15902
|
+
async function generateMediumClips(video, transcript, model, clipDirection, ideas, clipConfig) {
|
|
15903
|
+
const basePrompt = clipConfig ? buildMediumSystemPrompt(clipConfig) : SYSTEM_PROMPT3;
|
|
15904
|
+
const systemPrompt = basePrompt + (ideas?.length ? buildIdeaContext(ideas) : "");
|
|
14747
15905
|
const agent = new MediumVideoAgent(systemPrompt, model);
|
|
14748
15906
|
const transcriptLines = transcript.segments.map((seg) => {
|
|
14749
15907
|
const words = seg.words.map((w) => `[${w.start.toFixed(2)}-${w.end.toFixed(2)}] ${w.word}`).join(" ");
|
|
14750
15908
|
return `[${seg.start.toFixed(2)}s \u2013 ${seg.end.toFixed(2)}s] ${seg.text}
|
|
14751
15909
|
Words: ${words}`;
|
|
14752
15910
|
});
|
|
15911
|
+
const minDuration = clipConfig?.duration?.min ?? 60;
|
|
15912
|
+
const maxDuration = clipConfig?.duration?.max ?? 180;
|
|
15913
|
+
const minViralScore = clipConfig?.minViralScore ?? 10;
|
|
15914
|
+
const minMinutes = Math.floor(minDuration / 60);
|
|
15915
|
+
const maxMinutes = Math.ceil(maxDuration / 60);
|
|
14753
15916
|
const promptParts = [
|
|
14754
|
-
`Analyze the following transcript (${transcript.duration.toFixed(0)}s total) and find the most valuable segments for medium-length clips (
|
|
15917
|
+
`Analyze the following transcript (${transcript.duration.toFixed(0)}s total) and find the most valuable segments for medium-length clips (${minMinutes}-${maxMinutes} minutes each).
|
|
14755
15918
|
`,
|
|
14756
15919
|
`Video: ${video.filename}`,
|
|
14757
15920
|
`Duration: ${transcript.duration.toFixed(1)}s`,
|
|
14758
|
-
`Focus on value density over coverage \u2014 only extract clips scoring
|
|
15921
|
+
`Focus on value density over coverage \u2014 only extract clips scoring ${minViralScore}+ on the viral score framework. Every clip must have hook, hookType, emotionalTrigger, viralScore, narrativeStructure, clipType, saveReason, and microHooks.
|
|
14759
15922
|
`,
|
|
14760
15923
|
"--- TRANSCRIPT ---\n",
|
|
14761
15924
|
transcriptLines.join("\n\n"),
|
|
@@ -15640,17 +16803,27 @@ init_paths();
|
|
|
15640
16803
|
init_BaseAgent();
|
|
15641
16804
|
init_configLogger();
|
|
15642
16805
|
init_environment();
|
|
15643
|
-
|
|
15644
|
-
var
|
|
15645
|
-
Given a video transcript and summary you MUST generate one post for each of the 5 platforms listed below.
|
|
15646
|
-
Each post must match the platform's tone, format, and constraints exactly.
|
|
15647
|
-
|
|
15648
|
-
Platform guidelines:
|
|
16806
|
+
init_types2();
|
|
16807
|
+
var PER_PLATFORM_GUIDELINES = `Platform guidelines:
|
|
15649
16808
|
1. **TikTok** \u2013 Casual, hook-driven, trending hashtags, 150 chars max, emoji-heavy.
|
|
15650
16809
|
2. **YouTube** \u2013 Descriptive, SEO-optimized title + description, relevant tags.
|
|
15651
16810
|
3. **Instagram** \u2013 Visual storytelling, emoji-rich, 30 hashtags max, engaging caption.
|
|
15652
16811
|
4. **LinkedIn** \u2013 Professional, thought-leadership, industry insights, 1-3 hashtags.
|
|
15653
|
-
5. **X (Twitter)** \u2013 Concise, punchy, 280 chars max, 2-5 hashtags, thread-ready
|
|
16812
|
+
5. **X (Twitter)** \u2013 Concise, punchy, 280 chars max, 2-5 hashtags, thread-ready.`;
|
|
16813
|
+
var UNIFIED_TONE_GUIDELINES = `Platform guidelines:
|
|
16814
|
+
For all platforms, use the same professional but approachable tone. Keep posts concise, informative, and authentic. Use the same core message \u2014 adapt only for platform-specific formatting constraints (character limits, hashtag placement).
|
|
16815
|
+
1. **TikTok** \u2013 150 chars max, include relevant hashtags.
|
|
16816
|
+
2. **YouTube** \u2013 SEO-optimized title + description, relevant tags.
|
|
16817
|
+
3. **Instagram** \u2013 30 hashtags max, engaging caption.
|
|
16818
|
+
4. **LinkedIn** \u2013 1-3 hashtags, professional framing.
|
|
16819
|
+
5. **X (Twitter)** \u2013 280 chars max, 2-5 hashtags.`;
|
|
16820
|
+
function buildSocialMediaSystemPrompt(toneStrategy) {
|
|
16821
|
+
const platformGuidelines = toneStrategy === "unified" ? UNIFIED_TONE_GUIDELINES : PER_PLATFORM_GUIDELINES;
|
|
16822
|
+
return `You are a viral social-media content strategist.
|
|
16823
|
+
Given a video transcript and summary you MUST generate one post for each of the 5 platforms listed below.
|
|
16824
|
+
Each post must match the platform's format and constraints exactly.
|
|
16825
|
+
|
|
16826
|
+
${platformGuidelines}
|
|
15654
16827
|
|
|
15655
16828
|
IMPORTANT \u2013 Content format:
|
|
15656
16829
|
The "content" field you provide must be the FINAL, ready-to-post text that can be directly copied and pasted onto the platform. Do NOT use markdown headers, bullet points, or any formatting inside the content. Include hashtags inline at the end of the post text where appropriate. The content is saved as-is for direct posting.
|
|
@@ -15662,6 +16835,8 @@ Workflow:
|
|
|
15662
16835
|
|
|
15663
16836
|
Include relevant links in posts when search results provide them.
|
|
15664
16837
|
Always call "create_posts" exactly once with all 5 platform posts.`;
|
|
16838
|
+
}
|
|
16839
|
+
var SYSTEM_PROMPT5 = buildSocialMediaSystemPrompt();
|
|
15665
16840
|
var SocialMediaAgent = class extends BaseAgent {
|
|
15666
16841
|
collectedPosts = [];
|
|
15667
16842
|
constructor(systemPrompt = SYSTEM_PROMPT5, model) {
|
|
@@ -15842,8 +17017,9 @@ async function generateShortPosts(video, short, transcript, model, summary) {
|
|
|
15842
17017
|
await agent.destroy();
|
|
15843
17018
|
}
|
|
15844
17019
|
}
|
|
15845
|
-
async function generateSocialPosts(video, transcript, summary, outputDir, model, ideas) {
|
|
15846
|
-
const
|
|
17020
|
+
async function generateSocialPosts(video, transcript, summary, outputDir, model, ideas, toneStrategy) {
|
|
17021
|
+
const basePrompt = toneStrategy ? buildSocialMediaSystemPrompt(toneStrategy) : SYSTEM_PROMPT5;
|
|
17022
|
+
const systemPrompt = basePrompt + (ideas?.length ? buildIdeaContextForPosts(ideas) : "");
|
|
15847
17023
|
const agent = new SocialMediaAgent(systemPrompt, model);
|
|
15848
17024
|
try {
|
|
15849
17025
|
const userMessage = [
|
|
@@ -16131,11 +17307,11 @@ async function markFailed(slug, error) {
|
|
|
16131
17307
|
init_fileSystem();
|
|
16132
17308
|
init_paths();
|
|
16133
17309
|
init_configLogger();
|
|
16134
|
-
|
|
16135
|
-
|
|
17310
|
+
init_types2();
|
|
17311
|
+
init_types2();
|
|
16136
17312
|
|
|
16137
17313
|
// src/L3-services/socialPosting/platformContentStrategy.ts
|
|
16138
|
-
|
|
17314
|
+
init_types2();
|
|
16139
17315
|
var CONTENT_MATRIX = {
|
|
16140
17316
|
["youtube" /* YouTube */]: {
|
|
16141
17317
|
video: { captions: true, variantKey: null },
|
|
@@ -16169,10 +17345,10 @@ function platformAcceptsMedia(platform, clipType) {
|
|
|
16169
17345
|
|
|
16170
17346
|
// src/L3-services/queueBuilder/queueBuilder.ts
|
|
16171
17347
|
init_postStore();
|
|
16172
|
-
function resolveShortMedia(clip, platform) {
|
|
17348
|
+
function resolveShortMedia(clip, platform, variantsEnabled) {
|
|
16173
17349
|
const rule = getMediaRule(platform, "short");
|
|
16174
17350
|
if (!rule) return null;
|
|
16175
|
-
if (rule.variantKey && clip.variants?.length) {
|
|
17351
|
+
if (variantsEnabled !== false && rule.variantKey && clip.variants?.length) {
|
|
16176
17352
|
const match = clip.variants.find((v) => v.platform === rule.variantKey);
|
|
16177
17353
|
if (match) return match.path;
|
|
16178
17354
|
if (platform === "instagram" /* Instagram */) {
|
|
@@ -16219,7 +17395,7 @@ async function parsePostFrontmatter(postPath) {
|
|
|
16219
17395
|
function stripFrontmatter(content) {
|
|
16220
17396
|
return content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").trim();
|
|
16221
17397
|
}
|
|
16222
|
-
async function buildPublishQueue(video, shorts, mediumClips, socialPosts, captionedVideoPath, ideaIds) {
|
|
17398
|
+
async function buildPublishQueue(video, shorts, mediumClips, socialPosts, captionedVideoPath, ideaIds, variantsEnabled) {
|
|
16223
17399
|
const result = { itemsCreated: 0, itemsSkipped: 0, errors: [] };
|
|
16224
17400
|
for (const post of socialPosts) {
|
|
16225
17401
|
try {
|
|
@@ -16238,7 +17414,7 @@ async function buildPublishQueue(video, shorts, mediumClips, socialPosts, captio
|
|
|
16238
17414
|
clipSlug = short.slug;
|
|
16239
17415
|
clipType = "short";
|
|
16240
17416
|
sourceClip = dirname(short.outputPath);
|
|
16241
|
-
mediaPath = resolveShortMedia(short, post.platform);
|
|
17417
|
+
mediaPath = resolveShortMedia(short, post.platform, variantsEnabled);
|
|
16242
17418
|
thumbnailPath = short.thumbnailPath ?? null;
|
|
16243
17419
|
clipIdeaIssueNumber = short.ideaIssueNumber;
|
|
16244
17420
|
} else if (medium) {
|
|
@@ -16991,7 +18167,7 @@ async function enhanceVideo(videoPath, transcript, video) {
|
|
|
16991
18167
|
// src/L5-assets/MainVideoAsset.ts
|
|
16992
18168
|
init_environment();
|
|
16993
18169
|
init_configLogger();
|
|
16994
|
-
|
|
18170
|
+
init_types2();
|
|
16995
18171
|
var MainVideoAsset = class _MainVideoAsset extends VideoAsset {
|
|
16996
18172
|
sourcePath;
|
|
16997
18173
|
videoDir;
|
|
@@ -17000,6 +18176,16 @@ var MainVideoAsset = class _MainVideoAsset extends VideoAsset {
|
|
|
17000
18176
|
_ideas = [];
|
|
17001
18177
|
/** Per-clip idea assignments from idea discovery (clipId → ideaIssueNumber) */
|
|
17002
18178
|
_clipIdeaMap = /* @__PURE__ */ new Map();
|
|
18179
|
+
/** Pipeline spec controlling clip and platform configuration */
|
|
18180
|
+
_spec;
|
|
18181
|
+
/** Set the pipeline spec for configuring agent behavior */
|
|
18182
|
+
setSpec(spec) {
|
|
18183
|
+
this._spec = spec;
|
|
18184
|
+
}
|
|
18185
|
+
/** Get the pipeline spec */
|
|
18186
|
+
get spec() {
|
|
18187
|
+
return this._spec;
|
|
18188
|
+
}
|
|
17003
18189
|
/** Set ideas for editorial direction */
|
|
17004
18190
|
setIdeas(ideas) {
|
|
17005
18191
|
this._ideas = ideas;
|
|
@@ -17549,7 +18735,18 @@ var MainVideoAsset = class _MainVideoAsset extends VideoAsset {
|
|
|
17549
18735
|
async generateShortsInternal() {
|
|
17550
18736
|
const transcript = await this.getTranscript();
|
|
17551
18737
|
const videoFile = await this.toVideoFile();
|
|
17552
|
-
const
|
|
18738
|
+
const shortsConfig = this._spec?.clips.shorts;
|
|
18739
|
+
const shouldGenerateVariants = this._spec?.distribution.platforms.variants;
|
|
18740
|
+
const shorts = await generateShorts(
|
|
18741
|
+
videoFile,
|
|
18742
|
+
transcript,
|
|
18743
|
+
void 0,
|
|
18744
|
+
void 0,
|
|
18745
|
+
void 0,
|
|
18746
|
+
void 0,
|
|
18747
|
+
shortsConfig,
|
|
18748
|
+
shouldGenerateVariants
|
|
18749
|
+
);
|
|
17553
18750
|
logger_default.info(`Generated ${shorts.length} short clips`);
|
|
17554
18751
|
return shorts;
|
|
17555
18752
|
}
|
|
@@ -17581,7 +18778,15 @@ var MainVideoAsset = class _MainVideoAsset extends VideoAsset {
|
|
|
17581
18778
|
async generateMediumClipsInternal() {
|
|
17582
18779
|
const transcript = await this.getTranscript();
|
|
17583
18780
|
const videoFile = await this.toVideoFile();
|
|
17584
|
-
const
|
|
18781
|
+
const mediumConfig = this._spec?.clips.medium;
|
|
18782
|
+
const clips = await generateMediumClips(
|
|
18783
|
+
videoFile,
|
|
18784
|
+
transcript,
|
|
18785
|
+
void 0,
|
|
18786
|
+
void 0,
|
|
18787
|
+
void 0,
|
|
18788
|
+
mediumConfig
|
|
18789
|
+
);
|
|
17585
18790
|
logger_default.info(`Generated ${clips.length} medium clips for ${this.slug}`);
|
|
17586
18791
|
return clips;
|
|
17587
18792
|
}
|
|
@@ -17604,7 +18809,16 @@ var MainVideoAsset = class _MainVideoAsset extends VideoAsset {
|
|
|
17604
18809
|
const summary = await this.getSummary();
|
|
17605
18810
|
const video = await this.toVideoFile();
|
|
17606
18811
|
await ensureDirectory(this.socialPostsDir);
|
|
17607
|
-
const
|
|
18812
|
+
const toneStrategy = this._spec?.distribution.platforms.toneStrategy;
|
|
18813
|
+
const posts = await generateSocialPosts(
|
|
18814
|
+
video,
|
|
18815
|
+
transcript,
|
|
18816
|
+
summary,
|
|
18817
|
+
this.socialPostsDir,
|
|
18818
|
+
void 0,
|
|
18819
|
+
void 0,
|
|
18820
|
+
toneStrategy
|
|
18821
|
+
);
|
|
17608
18822
|
await this.markSocialPostsComplete();
|
|
17609
18823
|
return posts;
|
|
17610
18824
|
}
|
|
@@ -17978,7 +19192,8 @@ var MainVideoAsset = class _MainVideoAsset extends VideoAsset {
|
|
|
17978
19192
|
async buildPublishQueueData(shorts, mediumClips, socialPosts, captionedVideoPath) {
|
|
17979
19193
|
const video = await this.toVideoFile();
|
|
17980
19194
|
const ideaIds = this._ideas.length > 0 ? this._ideas.map((idea) => String(idea.issueNumber)) : void 0;
|
|
17981
|
-
|
|
19195
|
+
const variantsEnabled = this._spec?.distribution.platforms.variants;
|
|
19196
|
+
return buildPublishQueue2(video, shorts, mediumClips, socialPosts, captionedVideoPath, ideaIds, variantsEnabled);
|
|
17982
19197
|
}
|
|
17983
19198
|
/**
|
|
17984
19199
|
* Build the publish queue (simplified wrapper for pipeline).
|
|
@@ -18199,6 +19414,7 @@ async function executeRealignPlan(plan, onProgress) {
|
|
|
18199
19414
|
}
|
|
18200
19415
|
|
|
18201
19416
|
// src/L4-agents/ScheduleAgent.ts
|
|
19417
|
+
init_queueMapping();
|
|
18202
19418
|
init_configLogger();
|
|
18203
19419
|
var TOOL_LABELS = {
|
|
18204
19420
|
list_posts: "\u{1F4CB} Listing posts",
|
|
@@ -18314,14 +19530,16 @@ var ScheduleAgent = class extends BaseAgent {
|
|
|
18314
19530
|
},
|
|
18315
19531
|
{
|
|
18316
19532
|
name: "reschedule_post",
|
|
18317
|
-
description: "Move a post to a new scheduled time.",
|
|
19533
|
+
description: "Move a post to a new scheduled time, or re-queue it to get the next available queue slot. If scheduledFor is provided, the post is moved to that exact time. If omitted, the post is re-queued using the Late API queue for its platform/clipType.",
|
|
18318
19534
|
parameters: {
|
|
18319
19535
|
type: "object",
|
|
18320
19536
|
properties: {
|
|
18321
19537
|
postId: { type: "string", description: "The Late post ID" },
|
|
18322
|
-
scheduledFor: { type: "string", description: "New scheduled datetime (ISO 8601)" }
|
|
19538
|
+
scheduledFor: { type: "string", description: "New scheduled datetime (ISO 8601). Omit to re-queue using the platform queue." },
|
|
19539
|
+
platform: { type: "string", description: "Platform for queue lookup when re-queuing without scheduledFor" },
|
|
19540
|
+
clipType: { type: "string", description: "Clip type for queue lookup: short, medium-clip, video" }
|
|
18323
19541
|
},
|
|
18324
|
-
required: ["postId"
|
|
19542
|
+
required: ["postId"]
|
|
18325
19543
|
},
|
|
18326
19544
|
handler: async (args) => this.handleToolCall("reschedule_post", args)
|
|
18327
19545
|
},
|
|
@@ -18511,9 +19729,23 @@ var ScheduleAgent = class extends BaseAgent {
|
|
|
18511
19729
|
try {
|
|
18512
19730
|
const postId = args.postId;
|
|
18513
19731
|
const scheduledFor = args.scheduledFor;
|
|
19732
|
+
const platform = args.platform;
|
|
19733
|
+
const clipType = args.clipType;
|
|
18514
19734
|
const client = createLateApiClient();
|
|
18515
|
-
|
|
18516
|
-
|
|
19735
|
+
if (scheduledFor) {
|
|
19736
|
+
const updated = await client.schedulePost(postId, scheduledFor);
|
|
19737
|
+
return { success: true, postId, scheduledFor: updated.scheduledFor, source: "manual" };
|
|
19738
|
+
}
|
|
19739
|
+
if (platform) {
|
|
19740
|
+
const normalized = platform === "twitter" ? "x" : platform;
|
|
19741
|
+
const queueId = await getQueueId(normalized, clipType ?? "short");
|
|
19742
|
+
if (queueId) {
|
|
19743
|
+
const profileId = await getProfileId();
|
|
19744
|
+
const updated = await client.updatePost(postId, { queuedFromProfile: profileId, queueId });
|
|
19745
|
+
return { success: true, postId, scheduledFor: updated.scheduledFor, source: "queue", queueId };
|
|
19746
|
+
}
|
|
19747
|
+
}
|
|
19748
|
+
return { error: "Either scheduledFor or platform must be provided. Provide scheduledFor for a specific time, or platform to re-queue." };
|
|
18517
19749
|
} catch (err) {
|
|
18518
19750
|
logger_default.error("reschedule_post failed", { error: err });
|
|
18519
19751
|
return { error: `Failed to reschedule post: ${err.message}` };
|
|
@@ -18535,9 +19767,22 @@ var ScheduleAgent = class extends BaseAgent {
|
|
|
18535
19767
|
const platform = args.platform;
|
|
18536
19768
|
const clipType = args.clipType;
|
|
18537
19769
|
const normalized = platform === "twitter" ? "x" : platform;
|
|
19770
|
+
const queueId = await getQueueId(normalized, clipType ?? "short");
|
|
19771
|
+
if (queueId) {
|
|
19772
|
+
try {
|
|
19773
|
+
const profileId = await getProfileId();
|
|
19774
|
+
const client = createLateApiClient();
|
|
19775
|
+
const preview = await client.previewQueue(profileId, queueId, 1);
|
|
19776
|
+
if (preview.slots?.length > 0) {
|
|
19777
|
+
return { platform: normalized, clipType: clipType ?? "any", nextSlot: preview.slots[0], source: "queue", queueId };
|
|
19778
|
+
}
|
|
19779
|
+
} catch (err) {
|
|
19780
|
+
logger_default.warn("Queue preview failed, falling back to local calculation", { queueId, error: err });
|
|
19781
|
+
}
|
|
19782
|
+
}
|
|
18538
19783
|
const slot = await findNextSlot(normalized, clipType);
|
|
18539
19784
|
if (!slot) return { error: `No available slot found for ${normalized}` };
|
|
18540
|
-
return { platform: normalized, clipType: clipType ?? "any", nextSlot: slot };
|
|
19785
|
+
return { platform: normalized, clipType: clipType ?? "any", nextSlot: slot, source: "local" };
|
|
18541
19786
|
} catch (err) {
|
|
18542
19787
|
logger_default.error("find_next_slot failed", { error: err });
|
|
18543
19788
|
return { error: `Failed to find next slot: ${err.message}` };
|
|
@@ -18610,6 +19855,7 @@ var ScheduleAgent = class extends BaseAgent {
|
|
|
18610
19855
|
skipped: plan.skipped,
|
|
18611
19856
|
unmatched: plan.unmatched
|
|
18612
19857
|
};
|
|
19858
|
+
logger_default.info("Queue-based reshuffle is also available via `vidpipe sync-queues --reshuffle`");
|
|
18613
19859
|
if (dryRun) {
|
|
18614
19860
|
job.status = "completed";
|
|
18615
19861
|
job.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -18651,7 +19897,7 @@ init_modelConfig();
|
|
|
18651
19897
|
init_configLogger();
|
|
18652
19898
|
init_ideaService();
|
|
18653
19899
|
init_providerFactory();
|
|
18654
|
-
|
|
19900
|
+
init_types2();
|
|
18655
19901
|
init_BaseAgent();
|
|
18656
19902
|
var BASE_SYSTEM_PROMPT = `You are a content strategist for a tech content creator. Your role is to research trending topics, analyze what's working, and generate compelling video ideas grounded in real-world data.
|
|
18657
19903
|
|
|
@@ -20071,7 +21317,7 @@ function createIdeaDiscoveryAgent(...args) {
|
|
|
20071
21317
|
}
|
|
20072
21318
|
|
|
20073
21319
|
// src/L6-pipeline/pipeline.ts
|
|
20074
|
-
|
|
21320
|
+
init_types2();
|
|
20075
21321
|
async function runStage(stageName, fn, stageResults) {
|
|
20076
21322
|
const start = Date.now();
|
|
20077
21323
|
if (progressEmitter.isEnabled()) {
|
|
@@ -20125,11 +21371,22 @@ async function runStage(stageName, fn, stageResults) {
|
|
|
20125
21371
|
return void 0;
|
|
20126
21372
|
}
|
|
20127
21373
|
}
|
|
20128
|
-
async function processVideo(videoPath, ideas, publishBy) {
|
|
21374
|
+
async function processVideo(videoPath, ideas, publishBy, spec) {
|
|
20129
21375
|
const pipelineStart = Date.now();
|
|
20130
21376
|
const stageResults = [];
|
|
20131
21377
|
const cfg = getConfig();
|
|
20132
21378
|
let stagesSkipped = 0;
|
|
21379
|
+
const skipFlags = {
|
|
21380
|
+
SKIP_SILENCE_REMOVAL: cfg.SKIP_SILENCE_REMOVAL,
|
|
21381
|
+
SKIP_VISUAL_ENHANCEMENT: cfg.SKIP_VISUAL_ENHANCEMENT,
|
|
21382
|
+
SKIP_CAPTIONS: cfg.SKIP_CAPTIONS,
|
|
21383
|
+
SKIP_INTRO_OUTRO: cfg.SKIP_INTRO_OUTRO,
|
|
21384
|
+
SKIP_SHORTS: cfg.SKIP_SHORTS,
|
|
21385
|
+
SKIP_MEDIUM_CLIPS: cfg.SKIP_MEDIUM_CLIPS,
|
|
21386
|
+
SKIP_SOCIAL: cfg.SKIP_SOCIAL,
|
|
21387
|
+
SKIP_SOCIAL_PUBLISH: cfg.SKIP_SOCIAL_PUBLISH
|
|
21388
|
+
};
|
|
21389
|
+
const effectiveSpec = spec ? applySkipFlags(spec, skipFlags) : resolveFromFlags(skipFlags);
|
|
20133
21390
|
costTracker3.reset();
|
|
20134
21391
|
function trackStage(stage, fn) {
|
|
20135
21392
|
costTracker3.setStage(stage);
|
|
@@ -20194,43 +21451,44 @@ async function processVideo(videoPath, ideas, publishBy) {
|
|
|
20194
21451
|
asset.setIdeas(ideas);
|
|
20195
21452
|
logger_default.info(`Pipeline using ${ideas.length} idea(s) for editorial direction`);
|
|
20196
21453
|
}
|
|
21454
|
+
asset.setSpec(effectiveSpec);
|
|
20197
21455
|
try {
|
|
20198
21456
|
const transcript = await trackStage("transcription" /* Transcription */, () => asset.getTranscript());
|
|
20199
21457
|
let editedVideoPath;
|
|
20200
|
-
if (
|
|
21458
|
+
if (effectiveSpec.processing.silenceRemoval) {
|
|
20201
21459
|
editedVideoPath = await trackStage("silence-removal" /* SilenceRemoval */, () => asset.getEditedVideo());
|
|
20202
21460
|
} else {
|
|
20203
|
-
skipStage("silence-removal" /* SilenceRemoval */, "
|
|
21461
|
+
skipStage("silence-removal" /* SilenceRemoval */, "SPEC_DISABLED");
|
|
20204
21462
|
}
|
|
20205
21463
|
let enhancedVideoPath;
|
|
20206
|
-
if (
|
|
21464
|
+
if (effectiveSpec.processing.visualEnhancement) {
|
|
20207
21465
|
enhancedVideoPath = await trackStage("visual-enhancement" /* VisualEnhancement */, () => asset.getEnhancedVideo());
|
|
20208
21466
|
} else {
|
|
20209
|
-
skipStage("visual-enhancement" /* VisualEnhancement */, "
|
|
21467
|
+
skipStage("visual-enhancement" /* VisualEnhancement */, "SPEC_DISABLED");
|
|
20210
21468
|
}
|
|
20211
21469
|
let captions;
|
|
20212
|
-
if (
|
|
21470
|
+
if (effectiveSpec.processing.captions) {
|
|
20213
21471
|
captions = await trackStage("captions" /* Captions */, () => asset.getCaptions());
|
|
20214
21472
|
} else {
|
|
20215
|
-
skipStage("captions" /* Captions */, "
|
|
21473
|
+
skipStage("captions" /* Captions */, "SPEC_DISABLED");
|
|
20216
21474
|
}
|
|
20217
21475
|
let captionedVideoPath;
|
|
20218
|
-
if (
|
|
21476
|
+
if (effectiveSpec.processing.captions) {
|
|
20219
21477
|
captionedVideoPath = await trackStage("caption-burn" /* CaptionBurn */, () => asset.getCaptionedVideo());
|
|
20220
21478
|
} else {
|
|
20221
|
-
skipStage("caption-burn" /* CaptionBurn */, "
|
|
21479
|
+
skipStage("caption-burn" /* CaptionBurn */, "SPEC_DISABLED");
|
|
20222
21480
|
}
|
|
20223
21481
|
let introOutroVideoPath;
|
|
20224
|
-
if (
|
|
21482
|
+
if (effectiveSpec.processing.introOutro) {
|
|
20225
21483
|
introOutroVideoPath = await trackStage("intro-outro" /* IntroOutro */, () => asset.getIntroOutroVideo());
|
|
20226
21484
|
} else {
|
|
20227
|
-
skipStage("intro-outro" /* IntroOutro */, "
|
|
21485
|
+
skipStage("intro-outro" /* IntroOutro */, "SPEC_DISABLED");
|
|
20228
21486
|
}
|
|
20229
21487
|
let shorts = [];
|
|
20230
|
-
if (
|
|
21488
|
+
if (effectiveSpec.clips.shorts.enabled) {
|
|
20231
21489
|
const shortAssets = await trackStage("shorts" /* Shorts */, async () => {
|
|
20232
21490
|
const assets = await asset.getShorts();
|
|
20233
|
-
if (
|
|
21491
|
+
if (effectiveSpec.processing.introOutro) {
|
|
20234
21492
|
for (const shortAsset of assets) {
|
|
20235
21493
|
const introOutroPath = await shortAsset.getIntroOutroVideo();
|
|
20236
21494
|
if (introOutroPath !== shortAsset.clip.outputPath) {
|
|
@@ -20257,13 +21515,13 @@ async function processVideo(videoPath, ideas, publishBy) {
|
|
|
20257
21515
|
}
|
|
20258
21516
|
}
|
|
20259
21517
|
} else {
|
|
20260
|
-
skipStage("shorts" /* Shorts */, "
|
|
21518
|
+
skipStage("shorts" /* Shorts */, "SPEC_DISABLED");
|
|
20261
21519
|
}
|
|
20262
21520
|
let mediumClips = [];
|
|
20263
|
-
if (
|
|
21521
|
+
if (effectiveSpec.clips.medium.enabled) {
|
|
20264
21522
|
const mediumAssets = await trackStage("medium-clips" /* MediumClips */, async () => {
|
|
20265
21523
|
const assets = await asset.getMediumClips();
|
|
20266
|
-
if (
|
|
21524
|
+
if (effectiveSpec.processing.introOutro) {
|
|
20267
21525
|
for (const clipAsset of assets) {
|
|
20268
21526
|
const introOutroPath = await clipAsset.getIntroOutroVideo();
|
|
20269
21527
|
if (introOutroPath !== clipAsset.clip.outputPath) {
|
|
@@ -20283,7 +21541,7 @@ async function processVideo(videoPath, ideas, publishBy) {
|
|
|
20283
21541
|
}
|
|
20284
21542
|
}
|
|
20285
21543
|
} else {
|
|
20286
|
-
skipStage("medium-clips" /* MediumClips */, "
|
|
21544
|
+
skipStage("medium-clips" /* MediumClips */, "SPEC_DISABLED");
|
|
20287
21545
|
}
|
|
20288
21546
|
const chapters = await trackStage("chapters" /* Chapters */, () => asset.getChapters());
|
|
20289
21547
|
const summary = await trackStage("summary" /* Summary */, () => asset.getSummary());
|
|
@@ -20302,7 +21560,7 @@ async function processVideo(videoPath, ideas, publishBy) {
|
|
|
20302
21560
|
skipStage("idea-discovery" /* IdeaDiscovery */, "NO_CLIPS");
|
|
20303
21561
|
}
|
|
20304
21562
|
let socialPosts = [];
|
|
20305
|
-
if (
|
|
21563
|
+
if (effectiveSpec.distribution.enabled) {
|
|
20306
21564
|
const mainPosts = await trackStage("social-media" /* SocialMedia */, () => asset.getSocialPosts()) ?? [];
|
|
20307
21565
|
socialPosts.push(...mainPosts);
|
|
20308
21566
|
if (shorts.length > 0) {
|
|
@@ -20326,14 +21584,14 @@ async function processVideo(videoPath, ideas, publishBy) {
|
|
|
20326
21584
|
skipStage("medium-clip-posts" /* MediumClipPosts */, "NO_MEDIUM_CLIPS");
|
|
20327
21585
|
}
|
|
20328
21586
|
} else {
|
|
20329
|
-
skipStage("social-media" /* SocialMedia */, "
|
|
20330
|
-
skipStage("short-posts" /* ShortPosts */, "
|
|
20331
|
-
skipStage("medium-clip-posts" /* MediumClipPosts */, "
|
|
21587
|
+
skipStage("social-media" /* SocialMedia */, "SPEC_DISABLED");
|
|
21588
|
+
skipStage("short-posts" /* ShortPosts */, "SPEC_DISABLED");
|
|
21589
|
+
skipStage("medium-clip-posts" /* MediumClipPosts */, "SPEC_DISABLED");
|
|
20332
21590
|
}
|
|
20333
|
-
if (
|
|
21591
|
+
if (effectiveSpec.distribution.publish && socialPosts.length > 0) {
|
|
20334
21592
|
await trackStage("queue-build" /* QueueBuild */, () => asset.buildQueue(shorts, mediumClips, socialPosts, introOutroVideoPath ?? captionedVideoPath));
|
|
20335
|
-
} else if (
|
|
20336
|
-
skipStage("queue-build" /* QueueBuild */, "
|
|
21593
|
+
} else if (!effectiveSpec.distribution.publish) {
|
|
21594
|
+
skipStage("queue-build" /* QueueBuild */, "SPEC_DISABLED");
|
|
20337
21595
|
} else {
|
|
20338
21596
|
skipStage("queue-build" /* QueueBuild */, "NO_SOCIAL_POSTS");
|
|
20339
21597
|
}
|
|
@@ -20425,13 +21683,13 @@ function generateCostMarkdown(report) {
|
|
|
20425
21683
|
}
|
|
20426
21684
|
return md;
|
|
20427
21685
|
}
|
|
20428
|
-
async function processVideoSafe(videoPath, ideas, publishBy) {
|
|
21686
|
+
async function processVideoSafe(videoPath, ideas, publishBy, spec) {
|
|
20429
21687
|
const filename = basename(videoPath);
|
|
20430
21688
|
const slug = filename.replace(/\.(mp4|mov|webm|avi|mkv)$/i, "");
|
|
20431
21689
|
await markPending3(slug, videoPath);
|
|
20432
21690
|
await markProcessing3(slug);
|
|
20433
21691
|
try {
|
|
20434
|
-
const result = await processVideo(videoPath, ideas, publishBy);
|
|
21692
|
+
const result = await processVideo(videoPath, ideas, publishBy, spec);
|
|
20435
21693
|
await markCompleted3(slug);
|
|
20436
21694
|
return result;
|
|
20437
21695
|
} catch (err) {
|
|
@@ -20909,6 +22167,7 @@ function getPlatformIcon(platform) {
|
|
|
20909
22167
|
|
|
20910
22168
|
// src/L7-app/commands/realign.ts
|
|
20911
22169
|
init_environment();
|
|
22170
|
+
init_configLogger();
|
|
20912
22171
|
function formatDate(iso) {
|
|
20913
22172
|
const d = new Date(iso);
|
|
20914
22173
|
return d.toLocaleDateString("en-US", {
|
|
@@ -20938,6 +22197,23 @@ var PLATFORM_ICON = {
|
|
|
20938
22197
|
async function runRealign(options = {}) {
|
|
20939
22198
|
initConfig();
|
|
20940
22199
|
console.log("\n\u{1F504} Realign Late Posts\n");
|
|
22200
|
+
if (options.queue) {
|
|
22201
|
+
console.log(" Using Late API queue reshuffle mode");
|
|
22202
|
+
if (options.dryRun) {
|
|
22203
|
+
console.log(" Mode: DRY RUN (no changes will be made)\n");
|
|
22204
|
+
}
|
|
22205
|
+
const { syncQueuesToLate: syncQueuesToLate2 } = await Promise.resolve().then(() => (init_queueSync(), queueSync_exports));
|
|
22206
|
+
const result2 = await syncQueuesToLate2({ reshuffle: true, dryRun: options.dryRun });
|
|
22207
|
+
logger_default.info(`Queue reshuffle complete: ${result2.updated.length} queues reshuffled`);
|
|
22208
|
+
console.log(` \u2705 Queue reshuffle complete: ${result2.updated.length} queues reshuffled`);
|
|
22209
|
+
if (result2.errors.length > 0) {
|
|
22210
|
+
for (const err of result2.errors) {
|
|
22211
|
+
console.log(` \u274C ${err.queueName}: ${err.error}`);
|
|
22212
|
+
}
|
|
22213
|
+
}
|
|
22214
|
+
console.log();
|
|
22215
|
+
return;
|
|
22216
|
+
}
|
|
20941
22217
|
if (options.platform) {
|
|
20942
22218
|
console.log(` Platform filter: ${options.platform}`);
|
|
20943
22219
|
}
|
|
@@ -21356,8 +22632,8 @@ async function discoverIdeas(input) {
|
|
|
21356
22632
|
}
|
|
21357
22633
|
|
|
21358
22634
|
// src/L7-app/commands/ideate.ts
|
|
21359
|
-
|
|
21360
|
-
var
|
|
22635
|
+
init_types2();
|
|
22636
|
+
var VALID_PLATFORMS2 = new Set(Object.values(Platform));
|
|
21361
22637
|
async function runIdeate(options = {}) {
|
|
21362
22638
|
initConfig();
|
|
21363
22639
|
if (options.add) {
|
|
@@ -21466,8 +22742,8 @@ function parsePlatforms(value) {
|
|
|
21466
22742
|
const platforms = [];
|
|
21467
22743
|
for (const name of names) {
|
|
21468
22744
|
const lower = name.toLowerCase();
|
|
21469
|
-
if (!
|
|
21470
|
-
throw new Error(`Invalid platform "${name}". Valid platforms: ${[...
|
|
22745
|
+
if (!VALID_PLATFORMS2.has(lower)) {
|
|
22746
|
+
throw new Error(`Invalid platform "${name}". Valid platforms: ${[...VALID_PLATFORMS2].join(", ")}`);
|
|
21471
22747
|
}
|
|
21472
22748
|
platforms.push(lower);
|
|
21473
22749
|
}
|
|
@@ -21760,7 +23036,7 @@ init_postStore();
|
|
|
21760
23036
|
init_fileSystem();
|
|
21761
23037
|
init_brand();
|
|
21762
23038
|
init_paths();
|
|
21763
|
-
|
|
23039
|
+
init_types2();
|
|
21764
23040
|
init_configLogger();
|
|
21765
23041
|
async function runDiscoverIdeas(options) {
|
|
21766
23042
|
const pendingItems = await getPendingItems();
|
|
@@ -22702,8 +23978,8 @@ function buildPromptFromContext(ctx, contentType) {
|
|
|
22702
23978
|
// src/L7-app/commands/ideaUpdate.ts
|
|
22703
23979
|
init_environment();
|
|
22704
23980
|
init_ideaService();
|
|
22705
|
-
|
|
22706
|
-
var
|
|
23981
|
+
init_types2();
|
|
23982
|
+
var VALID_PLATFORMS3 = new Set(Object.values(Platform));
|
|
22707
23983
|
var VALID_STATUSES = /* @__PURE__ */ new Set(["draft", "ready", "recorded", "published"]);
|
|
22708
23984
|
var VALID_URGENCIES = /* @__PURE__ */ new Map([
|
|
22709
23985
|
["hot", 3],
|
|
@@ -22717,7 +23993,7 @@ var VALID_URGENCIES = /* @__PURE__ */ new Map([
|
|
|
22717
23993
|
]);
|
|
22718
23994
|
function parsePlatforms2(raw) {
|
|
22719
23995
|
if (!raw) return void 0;
|
|
22720
|
-
return raw.split(",").map((p) => p.trim().toLowerCase()).filter((p) =>
|
|
23996
|
+
return raw.split(",").map((p) => p.trim().toLowerCase()).filter((p) => VALID_PLATFORMS3.has(p));
|
|
22721
23997
|
}
|
|
22722
23998
|
function parseCommaSeparated2(raw) {
|
|
22723
23999
|
if (!raw) return void 0;
|
|
@@ -22887,8 +24163,9 @@ init_paths();
|
|
|
22887
24163
|
init_postStore();
|
|
22888
24164
|
init_ideaService2();
|
|
22889
24165
|
init_scheduler();
|
|
22890
|
-
|
|
24166
|
+
init_types2();
|
|
22891
24167
|
init_configLogger();
|
|
24168
|
+
init_queueMapping();
|
|
22892
24169
|
|
|
22893
24170
|
// src/L7-app/review/approvalQueue.ts
|
|
22894
24171
|
init_fileSystem();
|
|
@@ -22898,21 +24175,21 @@ init_scheduler();
|
|
|
22898
24175
|
init_scheduleConfig();
|
|
22899
24176
|
|
|
22900
24177
|
// src/L3-services/socialPosting/accountMapping.ts
|
|
22901
|
-
|
|
24178
|
+
init_types2();
|
|
22902
24179
|
init_lateApi();
|
|
22903
24180
|
init_configLogger();
|
|
22904
24181
|
init_fileSystem();
|
|
22905
24182
|
init_paths();
|
|
22906
|
-
var
|
|
22907
|
-
var
|
|
22908
|
-
var
|
|
24183
|
+
var CACHE_FILE2 = ".vidpipe-cache.json";
|
|
24184
|
+
var CACHE_TTL_MS2 = 24 * 60 * 60 * 1e3;
|
|
24185
|
+
var memoryCache2 = null;
|
|
22909
24186
|
function toLatePlatform2(platform) {
|
|
22910
24187
|
return platform === "x" /* X */ ? "twitter" : platform;
|
|
22911
24188
|
}
|
|
22912
|
-
function
|
|
22913
|
-
return join(process.cwd(),
|
|
24189
|
+
function cachePath2() {
|
|
24190
|
+
return join(process.cwd(), CACHE_FILE2);
|
|
22914
24191
|
}
|
|
22915
|
-
function
|
|
24192
|
+
function isCacheValid2(cache2) {
|
|
22916
24193
|
const fetchedAtTime = new Date(cache2.fetchedAt).getTime();
|
|
22917
24194
|
if (Number.isNaN(fetchedAtTime)) {
|
|
22918
24195
|
logger_default.warn("Invalid fetchedAt in account cache; treating as stale", {
|
|
@@ -22921,13 +24198,13 @@ function isCacheValid(cache2) {
|
|
|
22921
24198
|
return false;
|
|
22922
24199
|
}
|
|
22923
24200
|
const age = Date.now() - fetchedAtTime;
|
|
22924
|
-
return age <
|
|
24201
|
+
return age < CACHE_TTL_MS2;
|
|
22925
24202
|
}
|
|
22926
|
-
async function
|
|
24203
|
+
async function readFileCache2() {
|
|
22927
24204
|
try {
|
|
22928
|
-
const raw = await readTextFile(
|
|
24205
|
+
const raw = await readTextFile(cachePath2());
|
|
22929
24206
|
const cache2 = JSON.parse(raw);
|
|
22930
|
-
if (cache2.accounts && cache2.fetchedAt &&
|
|
24207
|
+
if (cache2.accounts && cache2.fetchedAt && isCacheValid2(cache2)) {
|
|
22931
24208
|
return cache2;
|
|
22932
24209
|
}
|
|
22933
24210
|
return null;
|
|
@@ -22935,7 +24212,7 @@ async function readFileCache() {
|
|
|
22935
24212
|
return null;
|
|
22936
24213
|
}
|
|
22937
24214
|
}
|
|
22938
|
-
async function
|
|
24215
|
+
async function writeFileCache2(cache2) {
|
|
22939
24216
|
try {
|
|
22940
24217
|
if (!cache2 || typeof cache2 !== "object" || !cache2.accounts || !cache2.fetchedAt) {
|
|
22941
24218
|
logger_default.warn("Invalid cache structure, skipping write");
|
|
@@ -22951,7 +24228,7 @@ async function writeFileCache(cache2) {
|
|
|
22951
24228
|
return;
|
|
22952
24229
|
}
|
|
22953
24230
|
}
|
|
22954
|
-
const resolvedCachePath = resolve(
|
|
24231
|
+
const resolvedCachePath = resolve(cachePath2());
|
|
22955
24232
|
if (!resolvedCachePath.startsWith(resolve(process.cwd()) + sep)) {
|
|
22956
24233
|
throw new Error("Cache path outside working directory");
|
|
22957
24234
|
}
|
|
@@ -22960,7 +24237,7 @@ async function writeFileCache(cache2) {
|
|
|
22960
24237
|
logger_default.warn("Failed to write account cache file", { error: err });
|
|
22961
24238
|
}
|
|
22962
24239
|
}
|
|
22963
|
-
async function
|
|
24240
|
+
async function fetchAndCache2() {
|
|
22964
24241
|
const client = new LateApiClient();
|
|
22965
24242
|
const accounts = await client.listAccounts();
|
|
22966
24243
|
const mapping = {};
|
|
@@ -22973,37 +24250,38 @@ async function fetchAndCache() {
|
|
|
22973
24250
|
accounts: mapping,
|
|
22974
24251
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
22975
24252
|
};
|
|
22976
|
-
|
|
22977
|
-
await
|
|
24253
|
+
memoryCache2 = cache2;
|
|
24254
|
+
await writeFileCache2(cache2);
|
|
22978
24255
|
logger_default.info("Refreshed Late account mappings", {
|
|
22979
24256
|
platforms: Object.keys(mapping)
|
|
22980
24257
|
});
|
|
22981
24258
|
return mapping;
|
|
22982
24259
|
}
|
|
22983
|
-
async function
|
|
22984
|
-
if (
|
|
22985
|
-
return
|
|
24260
|
+
async function ensureMappings2() {
|
|
24261
|
+
if (memoryCache2 && isCacheValid2(memoryCache2)) {
|
|
24262
|
+
return memoryCache2.accounts;
|
|
22986
24263
|
}
|
|
22987
|
-
const fileCache = await
|
|
24264
|
+
const fileCache = await readFileCache2();
|
|
22988
24265
|
if (fileCache) {
|
|
22989
|
-
|
|
24266
|
+
memoryCache2 = fileCache;
|
|
22990
24267
|
return fileCache.accounts;
|
|
22991
24268
|
}
|
|
22992
24269
|
try {
|
|
22993
|
-
return await
|
|
24270
|
+
return await fetchAndCache2();
|
|
22994
24271
|
} catch (err) {
|
|
22995
24272
|
logger_default.error("Failed to fetch Late account mappings", { error: err });
|
|
22996
24273
|
return {};
|
|
22997
24274
|
}
|
|
22998
24275
|
}
|
|
22999
24276
|
async function getAccountId(platform) {
|
|
23000
|
-
const mappings = await
|
|
24277
|
+
const mappings = await ensureMappings2();
|
|
23001
24278
|
const latePlatform = toLatePlatform2(platform);
|
|
23002
24279
|
return mappings[latePlatform] ?? null;
|
|
23003
24280
|
}
|
|
23004
24281
|
|
|
23005
24282
|
// src/L7-app/review/approvalQueue.ts
|
|
23006
|
-
|
|
24283
|
+
init_queueMapping();
|
|
24284
|
+
init_types2();
|
|
23007
24285
|
init_configLogger();
|
|
23008
24286
|
var queue = [];
|
|
23009
24287
|
var processing = false;
|
|
@@ -23103,10 +24381,21 @@ async function processApprovalBatch(itemIds) {
|
|
|
23103
24381
|
}
|
|
23104
24382
|
const ideaIds = item.metadata.ideaIds;
|
|
23105
24383
|
const publishBy = publishByMap.get(itemId);
|
|
23106
|
-
const
|
|
23107
|
-
|
|
23108
|
-
|
|
23109
|
-
|
|
24384
|
+
const clipType = item.metadata.clipType || "short";
|
|
24385
|
+
const queueId = await getQueueId(latePlatform, clipType);
|
|
24386
|
+
let slot;
|
|
24387
|
+
let useQueue = false;
|
|
24388
|
+
if (queueId) {
|
|
24389
|
+
useQueue = true;
|
|
24390
|
+
logger_default.debug(`Using Late queue ${queueId} for ${latePlatform}/${clipType} (idea priority via batch order)`);
|
|
24391
|
+
} else {
|
|
24392
|
+
logger_default.debug(`No queue for ${latePlatform}/${clipType}, using local slot calculation`);
|
|
24393
|
+
const foundSlot = ideaIds?.length ? await findNextSlot(latePlatform, clipType, { ideaIds, publishBy }) : await findNextSlot(latePlatform, clipType);
|
|
24394
|
+
slot = foundSlot ?? void 0;
|
|
24395
|
+
if (!slot) {
|
|
24396
|
+
results.push({ itemId, success: false, error: `No available slot for ${latePlatform}` });
|
|
24397
|
+
continue;
|
|
24398
|
+
}
|
|
23110
24399
|
}
|
|
23111
24400
|
const platform = fromLatePlatform(latePlatform);
|
|
23112
24401
|
const accountId = item.metadata.accountId || await getAccountId(platform);
|
|
@@ -23151,23 +24440,30 @@ async function processApprovalBatch(itemIds) {
|
|
|
23151
24440
|
content_preview_confirmed: true,
|
|
23152
24441
|
express_consent_given: true
|
|
23153
24442
|
} : void 0;
|
|
23154
|
-
const
|
|
24443
|
+
const profileId = useQueue ? await getProfileId() : void 0;
|
|
24444
|
+
const createParams = {
|
|
23155
24445
|
content: item.postContent,
|
|
23156
24446
|
platforms: [{ platform: latePlatform, accountId }],
|
|
23157
|
-
scheduledFor: slot,
|
|
23158
24447
|
timezone: schedConfig.timezone,
|
|
23159
24448
|
isDraft: false,
|
|
23160
24449
|
mediaItems,
|
|
23161
24450
|
platformSpecificData,
|
|
23162
24451
|
tiktokSettings
|
|
23163
|
-
}
|
|
24452
|
+
};
|
|
24453
|
+
if (useQueue) {
|
|
24454
|
+
createParams.queuedFromProfile = profileId;
|
|
24455
|
+
createParams.queueId = queueId ?? void 0;
|
|
24456
|
+
} else {
|
|
24457
|
+
createParams.scheduledFor = slot;
|
|
24458
|
+
}
|
|
24459
|
+
const latePost = await client.createPost(createParams);
|
|
23164
24460
|
publishDataMap.set(itemId, {
|
|
23165
24461
|
latePostId: latePost._id,
|
|
23166
|
-
scheduledFor: slot,
|
|
24462
|
+
scheduledFor: latePost.scheduledFor ?? slot ?? "",
|
|
23167
24463
|
publishedUrl: void 0,
|
|
23168
24464
|
accountId
|
|
23169
24465
|
});
|
|
23170
|
-
results.push({ itemId, success: true, scheduledFor: slot, latePostId: latePost._id });
|
|
24466
|
+
results.push({ itemId, success: true, scheduledFor: latePost.scheduledFor ?? slot, latePostId: latePost._id });
|
|
23171
24467
|
} catch (itemErr) {
|
|
23172
24468
|
const itemMsg = itemErr instanceof Error ? itemErr.message : String(itemErr);
|
|
23173
24469
|
if (itemMsg.includes("429") || itemMsg.includes("Daily post limit")) {
|
|
@@ -23197,7 +24493,7 @@ async function processApprovalBatch(itemIds) {
|
|
|
23197
24493
|
}
|
|
23198
24494
|
|
|
23199
24495
|
// src/L7-app/review/routes.ts
|
|
23200
|
-
var
|
|
24496
|
+
var CACHE_TTL_MS3 = 5 * 60 * 1e3;
|
|
23201
24497
|
var cache = /* @__PURE__ */ new Map();
|
|
23202
24498
|
function getCached(key) {
|
|
23203
24499
|
const entry = cache.get(key);
|
|
@@ -23205,7 +24501,7 @@ function getCached(key) {
|
|
|
23205
24501
|
cache.delete(key);
|
|
23206
24502
|
return void 0;
|
|
23207
24503
|
}
|
|
23208
|
-
function setCache(key, data, ttl =
|
|
24504
|
+
function setCache(key, data, ttl = CACHE_TTL_MS3) {
|
|
23209
24505
|
cache.set(key, { data, expiry: Date.now() + ttl });
|
|
23210
24506
|
}
|
|
23211
24507
|
async function getEarliestPublishBy(ideaIds) {
|
|
@@ -23374,6 +24670,18 @@ function createRouter() {
|
|
|
23374
24670
|
try {
|
|
23375
24671
|
const normalized = normalizePlatformString(req.params.platform);
|
|
23376
24672
|
const clipType = typeof req.query.clipType === "string" ? req.query.clipType : void 0;
|
|
24673
|
+
const queueId = await getQueueId(normalized, clipType ?? "short");
|
|
24674
|
+
if (queueId) {
|
|
24675
|
+
try {
|
|
24676
|
+
const profileId = await getProfileId();
|
|
24677
|
+
const client = createLateApiClient();
|
|
24678
|
+
const preview = await client.previewQueue(profileId, queueId, 1);
|
|
24679
|
+
if (preview.slots?.length > 0) {
|
|
24680
|
+
return res.json({ platform: normalized, nextSlot: preview.slots[0], source: "queue" });
|
|
24681
|
+
}
|
|
24682
|
+
} catch {
|
|
24683
|
+
}
|
|
24684
|
+
}
|
|
23377
24685
|
const slot = await findNextSlot(normalized, clipType);
|
|
23378
24686
|
res.json({ platform: normalized, nextSlot: slot });
|
|
23379
24687
|
} catch (err) {
|
|
@@ -23545,13 +24853,22 @@ program.command("schedule").description("View the current posting schedule acros
|
|
|
23545
24853
|
await runSchedule({ platform: opts.platform });
|
|
23546
24854
|
process.exit(0);
|
|
23547
24855
|
});
|
|
23548
|
-
program.command("realign").description("Realign all Late scheduled, cancelled, and failed posts to match schedule.json slots").option("--platform <name>", "Filter by platform (tiktok, youtube, instagram, linkedin, twitter)").option("--dry-run", "Preview changes without updating posts").action(async (opts) => {
|
|
23549
|
-
await runRealign({ platform: opts.platform, dryRun: opts.dryRun });
|
|
24856
|
+
program.command("realign").description("Realign all Late scheduled, cancelled, and failed posts to match schedule.json slots").option("--platform <name>", "Filter by platform (tiktok, youtube, instagram, linkedin, twitter)").option("--dry-run", "Preview changes without updating posts").option("--queue", "Use Late API queue reshuffle instead of per-post reschedule").action(async (opts) => {
|
|
24857
|
+
await runRealign({ platform: opts.platform, dryRun: opts.dryRun, queue: opts.queue });
|
|
23550
24858
|
process.exit(0);
|
|
23551
24859
|
});
|
|
23552
|
-
program.command("reschedule").description("Reschedule idea-linked posts for optimal slot placement, displacing non-idea content").option("--dry-run", "Preview changes without updating posts").action(async (opts) => {
|
|
24860
|
+
program.command("reschedule").description("Reschedule idea-linked posts for optimal slot placement, displacing non-idea content").option("--dry-run", "Preview changes without updating posts").option("--queue", "Use Late API queue reshuffle instead of per-post reschedule").action(async (opts) => {
|
|
23553
24861
|
const { runReschedule: runReschedule2 } = await Promise.resolve().then(() => (init_reschedule(), reschedule_exports));
|
|
23554
|
-
await runReschedule2({ dryRun: opts.dryRun });
|
|
24862
|
+
await runReschedule2({ dryRun: opts.dryRun, queue: opts.queue });
|
|
24863
|
+
process.exit(0);
|
|
24864
|
+
});
|
|
24865
|
+
program.command("sync-queues").description("Sync schedule.json queue definitions to Late API queues").option("--reshuffle", "Reschedule existing queued posts to match new slot times").option("--dry-run", "Preview changes without making API calls").option("--delete-orphans", "Delete Late queues not in schedule.json").action(async (opts) => {
|
|
24866
|
+
const { runSyncQueues: runSyncQueues2 } = await Promise.resolve().then(() => (init_syncQueues(), syncQueues_exports));
|
|
24867
|
+
await runSyncQueues2({
|
|
24868
|
+
reshuffle: opts.reshuffle,
|
|
24869
|
+
dryRun: opts.dryRun,
|
|
24870
|
+
deleteOrphans: opts.deleteOrphans
|
|
24871
|
+
});
|
|
23555
24872
|
process.exit(0);
|
|
23556
24873
|
});
|
|
23557
24874
|
program.command("chat").description("Interactive chat session with the schedule management agent").action(async () => {
|
|
@@ -23627,7 +24944,29 @@ program.command("thumbnail").description("Generate a thumbnail for a recording f
|
|
|
23627
24944
|
});
|
|
23628
24945
|
process.exit(0);
|
|
23629
24946
|
});
|
|
23630
|
-
|
|
24947
|
+
program.command("specs").description("List available pipeline spec presets and custom specs").action(async () => {
|
|
24948
|
+
const { PRESETS: PRESETS2 } = await Promise.resolve().then(() => (init_pipelineSpec(), pipelineSpec_exports));
|
|
24949
|
+
console.log("\nBuilt-in presets:\n");
|
|
24950
|
+
for (const [name, preset] of Object.entries(PRESETS2)) {
|
|
24951
|
+
console.log(` ${name.padEnd(12)} ${preset.description}`);
|
|
24952
|
+
}
|
|
24953
|
+
try {
|
|
24954
|
+
const specsDir = join(projectRoot(), "pipeline-specs");
|
|
24955
|
+
const files = listDirectorySync(specsDir);
|
|
24956
|
+
const specFiles = files.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml") || f.endsWith(".json"));
|
|
24957
|
+
if (specFiles.length > 0) {
|
|
24958
|
+
console.log("\nCustom specs (pipeline-specs/):\n");
|
|
24959
|
+
for (const file of specFiles) {
|
|
24960
|
+
const name = file.replace(/\.(yaml|yml|json)$/, "");
|
|
24961
|
+
console.log(` ${name}`);
|
|
24962
|
+
}
|
|
24963
|
+
}
|
|
24964
|
+
} catch {
|
|
24965
|
+
}
|
|
24966
|
+
console.log("\nUsage: vidpipe process --spec <name-or-path> video.mp4\n");
|
|
24967
|
+
process.exit(0);
|
|
24968
|
+
});
|
|
24969
|
+
var defaultCmd = program.command("process", { isDefault: true }).argument("[video-path]", "Path to a video file to process (implies --once)").option("--watch-dir <path>", "Folder to watch for new recordings (default: env WATCH_FOLDER)").option("--output-dir <path>", "Output directory for processed videos (default: ./recordings)").option("--openai-key <key>", "OpenAI API key (default: env OPENAI_API_KEY)").option("--exa-key <key>", "Exa AI API key for web search (default: env EXA_API_KEY)").option("--youtube-key <key>", "YouTube API key (default: env YOUTUBE_API_KEY)").option("--perplexity-key <key>", "Perplexity API key (default: env PERPLEXITY_API_KEY)").option("--once", "Process a single video and exit (no watching)").option("--brand <path>", "Path to brand.json config (default: ./brand.json)").option("--no-silence-removal", "Skip silence removal stage").option("--no-shorts", "Skip shorts generation").option("--no-medium-clips", "Skip medium clip generation").option("--no-social", "Skip social media post generation").option("--no-captions", "Skip caption generation/burning").option("--no-visual-enhancement", "Skip visual enhancement (AI image overlays)").option("--no-intro-outro", "Skip intro/outro concatenation").option("--no-social-publish", "Skip social media publishing/queue-build stage").option("--spec <nameOrPath>", "Pipeline spec preset name or YAML file path").option("--late-api-key <key>", "Late API key (default: env LATE_API_KEY)").option("--late-profile-id <id>", "Late profile ID (default: env LATE_PROFILE_ID)").option("--ideas <ids>", "Comma-separated idea IDs to link to this video").option("--publish-by <date>", "Publish-by deadline for auto-created ideas (ISO date or +Nd for relative, default: +7d)").option("-v, --verbose", "Verbose logging").option("--progress", "Emit structured JSON progress events to stderr").option("--doctor", "Check all prerequisites and exit").action(async (videoPath) => {
|
|
23631
24970
|
const opts = defaultCmd.opts();
|
|
23632
24971
|
if (opts.doctor) {
|
|
23633
24972
|
await runDoctor();
|
|
@@ -23662,6 +25001,18 @@ var defaultCmd = program.command("process", { isDefault: true }).argument("[vide
|
|
|
23662
25001
|
const config2 = getConfig();
|
|
23663
25002
|
logger_default.info(`Watch folder: ${config2.WATCH_FOLDER}`);
|
|
23664
25003
|
logger_default.info(`Output dir: ${config2.OUTPUT_DIR}`);
|
|
25004
|
+
let pipelineSpec;
|
|
25005
|
+
if (opts.spec) {
|
|
25006
|
+
try {
|
|
25007
|
+
const { loadSpec: loadSpec2 } = await Promise.resolve().then(() => (init_specLoader(), specLoader_exports));
|
|
25008
|
+
pipelineSpec = await loadSpec2(opts.spec, config2.REPO_ROOT);
|
|
25009
|
+
logger_default.info(`Using pipeline spec: ${pipelineSpec.name}`);
|
|
25010
|
+
} catch (err) {
|
|
25011
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
25012
|
+
logger_default.error(`Failed to load pipeline spec: ${msg}`);
|
|
25013
|
+
process.exit(1);
|
|
25014
|
+
}
|
|
25015
|
+
}
|
|
23665
25016
|
let ideas;
|
|
23666
25017
|
if (opts.ideas) {
|
|
23667
25018
|
const { getIdeasByIds: getIdeasByIds2 } = await Promise.resolve().then(() => (init_ideaService2(), ideaService_exports2));
|
|
@@ -23692,7 +25043,7 @@ var defaultCmd = program.command("process", { isDefault: true }).argument("[vide
|
|
|
23692
25043
|
process.exit(1);
|
|
23693
25044
|
}
|
|
23694
25045
|
}
|
|
23695
|
-
await processVideoSafe(resolvedPath, ideas, publishBy);
|
|
25046
|
+
await processVideoSafe(resolvedPath, ideas, publishBy, pipelineSpec);
|
|
23696
25047
|
if (ideas && ideas.length > 0) {
|
|
23697
25048
|
try {
|
|
23698
25049
|
const { markRecorded: markRecorded3 } = await Promise.resolve().then(() => (init_ideaService(), ideaService_exports));
|
|
@@ -23720,7 +25071,7 @@ var defaultCmd = program.command("process", { isDefault: true }).argument("[vide
|
|
|
23720
25071
|
while (queue2.length > 0) {
|
|
23721
25072
|
const vp = queue2.shift();
|
|
23722
25073
|
logger_default.info(`Processing video: ${vp}`);
|
|
23723
|
-
await processVideoSafe(vp, ideas);
|
|
25074
|
+
await processVideoSafe(vp, ideas, void 0, pipelineSpec);
|
|
23724
25075
|
if (onceMode) {
|
|
23725
25076
|
logger_default.info("--once flag set, exiting after first video.");
|
|
23726
25077
|
await shutdown();
|