mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-11 07:18:03 -05:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f59e5081f3 | ||
|
|
6a504c7422 | ||
|
|
89a0abcbe4 | ||
|
|
2dfd78ef0b | ||
|
|
2200b6ea08 | ||
|
|
82f9ebaf99 | ||
|
|
704ad3067a | ||
|
|
6f7e3c04d7 | ||
|
|
79f763456e | ||
|
|
9d4f7f1571 | ||
|
|
8e7373b308 | ||
|
|
7a39742507 | ||
|
|
02ac68834d | ||
|
|
0ae41116aa |
@@ -87,6 +87,12 @@ Fabric is graciously supported by…
|
||||
## Updates
|
||||
|
||||
> [!NOTE]
|
||||
>June 11, 2025
|
||||
>
|
||||
> - Fabric's YouTube transcription now needs `yt-dlp` to be installed. Make sure to install the latest
|
||||
> version (2025.06.09 as of this note). The YouTube API key is only needed for comments (the `--comments` flag)
|
||||
> and metadata extraction (the `--metadata` flag).
|
||||
>
|
||||
> May 22, 2025
|
||||
>
|
||||
> - Fabric now supports Anthropic's Claude 4. Read the [blog post from Anthropic](https://www.anthropic.com/news/claude-4).
|
||||
|
||||
@@ -159,7 +159,7 @@ func Init() (ret *Flags, err error) {
|
||||
// Handle stdin and messages
|
||||
// Handle stdin and messages
|
||||
info, _ := os.Stdin.Stat()
|
||||
pipedToStdin := (info.Mode()&os.ModeCharDevice) == 0 && !debug
|
||||
pipedToStdin := (info.Mode() & os.ModeCharDevice) == 0
|
||||
|
||||
// Append positional arguments to the message (custom message)
|
||||
if len(args) > 0 {
|
||||
|
||||
@@ -1 +1 @@
|
||||
"1.4.197"
|
||||
"1.4.201"
|
||||
|
||||
@@ -138,7 +138,7 @@ func (an *Client) buildMessageParams(msgs []anthropic.MessageParam, opts *common
|
||||
// MaxUses: anthropic.Opt[int64](5),
|
||||
}
|
||||
|
||||
if an.WebToolLocation.Value == "" {
|
||||
if an.WebToolLocation.Value != "" {
|
||||
webTool.UserLocation.Type = "approximate"
|
||||
webTool.UserLocation.Timezone = anthropic.Opt(an.WebToolLocation.Value)
|
||||
}
|
||||
|
||||
@@ -33,16 +33,20 @@ func NewClient() (ret *Client) {
|
||||
ret.ApiUrl.Value = defaultBaseUrl
|
||||
ret.ApiKey = ret.PluginBase.AddSetupQuestion("API key", false)
|
||||
ret.ApiKey.Value = ""
|
||||
ret.ApiHttpTimeout = ret.AddSetupQuestionCustom("HTTP Timeout", true,
|
||||
"Specify HTTP timeout duration for Ollama requests (e.g. 30s, 5m, 1h)")
|
||||
ret.ApiHttpTimeout.Value = "20m"
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
*plugins.PluginBase
|
||||
ApiUrl *plugins.SetupQuestion
|
||||
ApiKey *plugins.SetupQuestion
|
||||
apiUrl *url.URL
|
||||
client *ollamaapi.Client
|
||||
ApiUrl *plugins.SetupQuestion
|
||||
ApiKey *plugins.SetupQuestion
|
||||
apiUrl *url.URL
|
||||
client *ollamaapi.Client
|
||||
ApiHttpTimeout *plugins.SetupQuestion
|
||||
}
|
||||
|
||||
type transport_sec struct {
|
||||
@@ -63,7 +67,19 @@ func (o *Client) configure() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
o.client = ollamaapi.NewClient(o.apiUrl, &http.Client{Timeout: 1200000 * time.Millisecond, Transport: &transport_sec{underlyingTransport: http.DefaultTransport, ApiKey: o.ApiKey}})
|
||||
timeout := 20 * time.Minute // Default timeout
|
||||
|
||||
if o.ApiHttpTimeout != nil {
|
||||
parsed, err := time.ParseDuration(o.ApiHttpTimeout.Value)
|
||||
if err == nil && o.ApiHttpTimeout.Value != "" {
|
||||
timeout = parsed
|
||||
} else if o.ApiHttpTimeout.Value != "" {
|
||||
fmt.Printf("Invalid HTTP timeout format (%q), using default (20m): %v\n", o.ApiHttpTimeout.Value, err)
|
||||
}
|
||||
}
|
||||
|
||||
o.client = ollamaapi.NewClient(o.apiUrl, &http.Client{Timeout: timeout, Transport: &transport_sec{underlyingTransport: http.DefaultTransport, ApiKey: o.ApiKey}})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
// Package youtube provides YouTube video transcript and comment extraction functionality.
|
||||
//
|
||||
// Requirements:
|
||||
// - yt-dlp: Required for transcript extraction (must be installed separately)
|
||||
// - YouTube API key: Optional, only needed for comments and metadata extraction
|
||||
//
|
||||
// The implementation uses yt-dlp for reliable transcript extraction and the YouTube API
|
||||
// for comments/metadata. Old YouTube scraping methods have been removed due to
|
||||
// frequent changes and rate limiting.
|
||||
package youtube
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/anaskhan96/soup"
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/api/youtube/v3"
|
||||
@@ -27,7 +35,7 @@ func NewYouTube() (ret *YouTube) {
|
||||
|
||||
ret.PluginBase = &plugins.PluginBase{
|
||||
Name: label,
|
||||
SetupDescription: label + " - to grab video transcripts and comments",
|
||||
SetupDescription: label + " - to grab video transcripts (via yt-dlp) and comments/metadata (via YouTube API)",
|
||||
EnvNamePrefix: plugins.BuildEnvVariablePrefix(label),
|
||||
}
|
||||
|
||||
@@ -46,6 +54,10 @@ type YouTube struct {
|
||||
|
||||
func (o *YouTube) initService() (err error) {
|
||||
if o.service == nil {
|
||||
if o.ApiKey.Value == "" {
|
||||
err = fmt.Errorf("YouTube API key required for comments and metadata. Run 'fabric --setup' to configure")
|
||||
return
|
||||
}
|
||||
o.normalizeRegex = regexp.MustCompile(`[^a-zA-Z0-9]+`)
|
||||
ctx := context.Background()
|
||||
o.service, err = youtube.NewService(ctx, option.WithAPIKey(o.ApiKey.Value))
|
||||
@@ -54,10 +66,6 @@ func (o *YouTube) initService() (err error) {
|
||||
}
|
||||
|
||||
func (o *YouTube) GetVideoOrPlaylistId(url string) (videoId string, playlistId string, err error) {
|
||||
if err = o.initService(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Video ID pattern
|
||||
videoPattern := `(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:live\/|[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|(?:s(?:horts)\/)|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]*)`
|
||||
videoRe := regexp.MustCompile(videoPattern)
|
||||
@@ -94,112 +102,253 @@ func (o *YouTube) GrabTranscriptForUrl(url string, language string) (ret string,
|
||||
}
|
||||
|
||||
func (o *YouTube) GrabTranscript(videoId string, language string) (ret string, err error) {
|
||||
var transcript string
|
||||
if transcript, err = o.GrabTranscriptBase(videoId, language); err != nil {
|
||||
err = fmt.Errorf("transcript not available. (%v)", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse the XML transcript
|
||||
doc := soup.HTMLParse(transcript)
|
||||
// Extract the text content from the <text> tags
|
||||
textTags := doc.FindAll("text")
|
||||
var textBuilder strings.Builder
|
||||
for _, textTag := range textTags {
|
||||
textBuilder.WriteString(strings.ReplaceAll(textTag.Text(), "'", "'"))
|
||||
textBuilder.WriteString(" ")
|
||||
ret = textBuilder.String()
|
||||
}
|
||||
return
|
||||
// Use yt-dlp for reliable transcript extraction
|
||||
return o.tryMethodYtDlp(videoId, language)
|
||||
}
|
||||
|
||||
func (o *YouTube) GrabTranscriptWithTimestamps(videoId string, language string) (ret string, err error) {
|
||||
var transcript string
|
||||
if transcript, err = o.GrabTranscriptBase(videoId, language); err != nil {
|
||||
err = fmt.Errorf("transcript not available. (%v)", err)
|
||||
// Use yt-dlp for reliable transcript extraction with timestamps
|
||||
return o.tryMethodYtDlpWithTimestamps(videoId, language)
|
||||
}
|
||||
|
||||
func (o *YouTube) tryMethodYtDlp(videoId string, language string) (ret string, err error) {
|
||||
// Check if yt-dlp is available
|
||||
if _, err = exec.LookPath("yt-dlp"); err != nil {
|
||||
err = fmt.Errorf("yt-dlp not found in PATH. Please install yt-dlp to use YouTube transcript functionality")
|
||||
return
|
||||
}
|
||||
|
||||
// Parse the XML transcript
|
||||
doc := soup.HTMLParse(transcript)
|
||||
// Extract the text content from the <text> tags
|
||||
textTags := doc.FindAll("text")
|
||||
var textBuilder strings.Builder
|
||||
for _, textTag := range textTags {
|
||||
// Extract the start and duration attributes
|
||||
start := textTag.Attrs()["start"]
|
||||
dur := textTag.Attrs()["dur"]
|
||||
end := fmt.Sprintf("%f", parseFloat(start)+parseFloat(dur))
|
||||
// Format the timestamps
|
||||
startFormatted := formatTimestamp(parseFloat(start))
|
||||
endFormatted := formatTimestamp(parseFloat(end))
|
||||
text := strings.ReplaceAll(textTag.Text(), "'", "'")
|
||||
textBuilder.WriteString(fmt.Sprintf("[%s - %s] %s\n", startFormatted, endFormatted, text))
|
||||
// Create a temporary directory for yt-dlp output
|
||||
tempDir := "/tmp/fabric-youtube-" + videoId
|
||||
if err = os.MkdirAll(tempDir, 0755); err != nil {
|
||||
err = fmt.Errorf("failed to create temp directory: %v", err)
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Use yt-dlp to get transcript
|
||||
videoURL := "https://www.youtube.com/watch?v=" + videoId
|
||||
cmd := exec.Command("yt-dlp",
|
||||
"--write-auto-subs",
|
||||
"--sub-lang", language,
|
||||
"--skip-download",
|
||||
"--sub-format", "vtt",
|
||||
"--quiet",
|
||||
"--no-warnings",
|
||||
"-o", tempDir+"/%(title)s.%(ext)s",
|
||||
videoURL)
|
||||
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err = cmd.Run(); err != nil {
|
||||
err = fmt.Errorf("yt-dlp failed: %v, stderr: %s", err, stderr.String())
|
||||
return
|
||||
}
|
||||
|
||||
// Find the VTT file in the temp directory
|
||||
cmd2 := exec.Command("find", tempDir, "-name", "*."+language+".vtt", "-type", "f")
|
||||
var findOut bytes.Buffer
|
||||
cmd2.Stdout = &findOut
|
||||
|
||||
if err = cmd2.Run(); err != nil {
|
||||
err = fmt.Errorf("failed to find VTT file: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
vttFiles := strings.Split(strings.TrimSpace(findOut.String()), "\n")
|
||||
if len(vttFiles) == 0 || vttFiles[0] == "" {
|
||||
// Try without language suffix
|
||||
cmd3 := exec.Command("find", tempDir, "-name", "*.vtt", "-type", "f")
|
||||
cmd3.Stdout = &findOut
|
||||
findOut.Reset()
|
||||
if err = cmd3.Run(); err != nil {
|
||||
err = fmt.Errorf("no VTT files found")
|
||||
return
|
||||
}
|
||||
vttFiles = strings.Split(strings.TrimSpace(findOut.String()), "\n")
|
||||
if len(vttFiles) == 0 || vttFiles[0] == "" {
|
||||
err = fmt.Errorf("no VTT files found in temp directory")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return o.readAndCleanVTTFile(vttFiles[0])
|
||||
}
|
||||
|
||||
func (o *YouTube) tryMethodYtDlpWithTimestamps(videoId string, language string) (ret string, err error) {
|
||||
// Check if yt-dlp is available
|
||||
if _, err = exec.LookPath("yt-dlp"); err != nil {
|
||||
err = fmt.Errorf("yt-dlp not found in PATH. Please install yt-dlp to use YouTube transcript functionality")
|
||||
return
|
||||
}
|
||||
|
||||
// Create a temporary directory for yt-dlp output
|
||||
tempDir := "/tmp/fabric-youtube-" + videoId
|
||||
if err = os.MkdirAll(tempDir, 0755); err != nil {
|
||||
err = fmt.Errorf("failed to create temp directory: %v", err)
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Use yt-dlp to get transcript
|
||||
videoURL := "https://www.youtube.com/watch?v=" + videoId
|
||||
cmd := exec.Command("yt-dlp",
|
||||
"--write-auto-subs",
|
||||
"--sub-lang", language,
|
||||
"--skip-download",
|
||||
"--sub-format", "vtt",
|
||||
"--quiet",
|
||||
"--no-warnings",
|
||||
"-o", tempDir+"/%(title)s.%(ext)s",
|
||||
videoURL)
|
||||
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err = cmd.Run(); err != nil {
|
||||
err = fmt.Errorf("yt-dlp failed: %v, stderr: %s", err, stderr.String())
|
||||
return
|
||||
}
|
||||
|
||||
// Find the VTT file in the temp directory
|
||||
cmd2 := exec.Command("find", tempDir, "-name", "*."+language+".vtt", "-type", "f")
|
||||
var findOut bytes.Buffer
|
||||
cmd2.Stdout = &findOut
|
||||
|
||||
if err = cmd2.Run(); err != nil {
|
||||
err = fmt.Errorf("failed to find VTT file: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
vttFiles := strings.Split(strings.TrimSpace(findOut.String()), "\n")
|
||||
if len(vttFiles) == 0 || vttFiles[0] == "" {
|
||||
// Try without language suffix
|
||||
cmd3 := exec.Command("find", tempDir, "-name", "*.vtt", "-type", "f")
|
||||
cmd3.Stdout = &findOut
|
||||
findOut.Reset()
|
||||
if err = cmd3.Run(); err != nil {
|
||||
err = fmt.Errorf("no VTT files found")
|
||||
return
|
||||
}
|
||||
vttFiles = strings.Split(strings.TrimSpace(findOut.String()), "\n")
|
||||
if len(vttFiles) == 0 || vttFiles[0] == "" {
|
||||
err = fmt.Errorf("no VTT files found in temp directory")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return o.readAndFormatVTTWithTimestamps(vttFiles[0])
|
||||
}
|
||||
|
||||
func (o *YouTube) readAndCleanVTTFile(filename string) (ret string, err error) {
|
||||
var content []byte
|
||||
if content, err = os.ReadFile(filename); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Convert VTT to plain text
|
||||
lines := strings.Split(string(content), "\n")
|
||||
var textBuilder strings.Builder
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
// Skip WEBVTT header, timestamps, and empty lines
|
||||
if line == "" || line == "WEBVTT" || strings.Contains(line, "-->") ||
|
||||
strings.HasPrefix(line, "NOTE") || strings.HasPrefix(line, "STYLE") ||
|
||||
strings.HasPrefix(line, "Kind:") || strings.HasPrefix(line, "Language:") ||
|
||||
isTimeStamp(line) {
|
||||
continue
|
||||
}
|
||||
// Remove VTT formatting tags
|
||||
line = removeVTTTags(line)
|
||||
if line != "" {
|
||||
textBuilder.WriteString(line)
|
||||
textBuilder.WriteString(" ")
|
||||
}
|
||||
}
|
||||
|
||||
ret = strings.TrimSpace(textBuilder.String())
|
||||
if ret == "" {
|
||||
err = fmt.Errorf("no transcript content found in VTT file")
|
||||
}
|
||||
ret = textBuilder.String()
|
||||
return
|
||||
}
|
||||
|
||||
func parseFloat(s string) float64 {
|
||||
f, _ := strconv.ParseFloat(s, 64)
|
||||
return f
|
||||
}
|
||||
|
||||
func formatTimestamp(seconds float64) string {
|
||||
hours := int(seconds) / 3600
|
||||
minutes := (int(seconds) % 3600) / 60
|
||||
secs := int(seconds) % 60
|
||||
return fmt.Sprintf("%02d:%02d:%02d", hours, minutes, secs)
|
||||
}
|
||||
|
||||
func (o *YouTube) GrabTranscriptBase(videoId string, language string) (ret string, err error) {
|
||||
if err = o.initService(); err != nil {
|
||||
func (o *YouTube) readAndFormatVTTWithTimestamps(filename string) (ret string, err error) {
|
||||
var content []byte
|
||||
if content, err = os.ReadFile(filename); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
watchUrl := "https://www.youtube.com/watch?v=" + videoId
|
||||
var resp string
|
||||
if resp, err = soup.Get(watchUrl); err != nil {
|
||||
return
|
||||
}
|
||||
// Parse VTT and preserve timestamps
|
||||
lines := strings.Split(string(content), "\n")
|
||||
var textBuilder strings.Builder
|
||||
var currentTimestamp string
|
||||
|
||||
doc := soup.HTMLParse(resp)
|
||||
scriptTags := doc.FindAll("script")
|
||||
for _, scriptTag := range scriptTags {
|
||||
if strings.Contains(scriptTag.Text(), "captionTracks") {
|
||||
regex := regexp.MustCompile(`"captionTracks":(\[.*?\])`)
|
||||
match := regex.FindStringSubmatch(scriptTag.Text())
|
||||
if len(match) > 1 {
|
||||
var captionTracks []struct {
|
||||
BaseURL string `json:"baseUrl"`
|
||||
}
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
if err = json.Unmarshal([]byte(match[1]), &captionTracks); err != nil {
|
||||
return
|
||||
}
|
||||
// Skip WEBVTT header and empty lines
|
||||
if line == "" || line == "WEBVTT" || strings.HasPrefix(line, "NOTE") ||
|
||||
strings.HasPrefix(line, "STYLE") || strings.HasPrefix(line, "Kind:") ||
|
||||
strings.HasPrefix(line, "Language:") {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(captionTracks) > 0 {
|
||||
transcriptURL := captionTracks[0].BaseURL
|
||||
for _, captionTrack := range captionTracks {
|
||||
parsedUrl, error := url.Parse(captionTrack.BaseURL)
|
||||
if error != nil {
|
||||
err = fmt.Errorf("error parsing caption track")
|
||||
}
|
||||
parsedUrlParams, _ := url.ParseQuery(parsedUrl.RawQuery)
|
||||
if parsedUrlParams["lang"][0] == language {
|
||||
transcriptURL = captionTrack.BaseURL
|
||||
}
|
||||
}
|
||||
ret, err = soup.Get(transcriptURL)
|
||||
return
|
||||
}
|
||||
// Check if this line is a timestamp
|
||||
if strings.Contains(line, "-->") {
|
||||
// Extract start time for this segment
|
||||
parts := strings.Split(line, " --> ")
|
||||
if len(parts) >= 1 {
|
||||
currentTimestamp = formatVTTTimestamp(parts[0])
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip numeric sequence identifiers
|
||||
if isTimeStamp(line) && !strings.Contains(line, ":") {
|
||||
continue
|
||||
}
|
||||
|
||||
// This should be transcript text
|
||||
if line != "" {
|
||||
// Remove VTT formatting tags
|
||||
cleanText := removeVTTTags(line)
|
||||
if cleanText != "" && currentTimestamp != "" {
|
||||
textBuilder.WriteString(fmt.Sprintf("[%s] %s\n", currentTimestamp, cleanText))
|
||||
}
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("transcript not found")
|
||||
|
||||
ret = strings.TrimSpace(textBuilder.String())
|
||||
if ret == "" {
|
||||
err = fmt.Errorf("no transcript content found in VTT file")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func formatVTTTimestamp(vttTime string) string {
|
||||
// VTT timestamps are in format "00:00:01.234" - convert to "00:00:01"
|
||||
parts := strings.Split(vttTime, ".")
|
||||
if len(parts) > 0 {
|
||||
return parts[0]
|
||||
}
|
||||
return vttTime
|
||||
}
|
||||
|
||||
func isTimeStamp(s string) bool {
|
||||
// Match timestamps like "00:00:01.234" or just numbers
|
||||
timestampRegex := regexp.MustCompile(`^\d+$|^\d{2}:\d{2}:\d{2}`)
|
||||
return timestampRegex.MatchString(s)
|
||||
}
|
||||
|
||||
func removeVTTTags(s string) string {
|
||||
// Remove VTT tags like <c.colorE5E5E5>, </c>, etc.
|
||||
tagRegex := regexp.MustCompile(`<[^>]*>`)
|
||||
return tagRegex.ReplaceAllString(s, "")
|
||||
}
|
||||
|
||||
func (o *YouTube) GrabComments(videoId string) (ret []string, err error) {
|
||||
if err = o.initService(); err != nil {
|
||||
return
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package main
|
||||
|
||||
var version = "v1.4.197"
|
||||
var version = "v1.4.201"
|
||||
|
||||
66
web/pnpm-lock.yaml
generated
66
web/pnpm-lock.yaml
generated
@@ -608,6 +608,9 @@ packages:
|
||||
'@types/estree@1.0.7':
|
||||
resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
|
||||
|
||||
'@types/estree@1.0.8':
|
||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||
|
||||
'@types/hast@3.0.4':
|
||||
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
|
||||
|
||||
@@ -654,6 +657,11 @@ packages:
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
acorn@8.15.0:
|
||||
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
agent-base@6.0.2:
|
||||
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
@@ -746,11 +754,11 @@ packages:
|
||||
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
brace-expansion@1.1.11:
|
||||
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
|
||||
brace-expansion@1.1.12:
|
||||
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
||||
|
||||
brace-expansion@2.0.1:
|
||||
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
|
||||
brace-expansion@2.0.2:
|
||||
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
|
||||
|
||||
braces@3.0.3:
|
||||
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
||||
@@ -1019,16 +1027,16 @@ packages:
|
||||
resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
|
||||
eslint-scope@8.3.0:
|
||||
resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==}
|
||||
eslint-scope@8.4.0:
|
||||
resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
eslint-visitor-keys@3.4.3:
|
||||
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
|
||||
eslint-visitor-keys@4.2.0:
|
||||
resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==}
|
||||
eslint-visitor-keys@4.2.1:
|
||||
resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
eslint@9.17.0:
|
||||
@@ -1047,8 +1055,8 @@ packages:
|
||||
esm-env@1.2.2:
|
||||
resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==}
|
||||
|
||||
espree@10.3.0:
|
||||
resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==}
|
||||
espree@10.4.0:
|
||||
resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
espree@9.6.1:
|
||||
@@ -2472,7 +2480,7 @@ snapshots:
|
||||
dependencies:
|
||||
ajv: 6.12.6
|
||||
debug: 4.4.1
|
||||
espree: 10.3.0
|
||||
espree: 10.4.0
|
||||
globals: 14.0.0
|
||||
ignore: 5.3.2
|
||||
import-fresh: 3.3.1
|
||||
@@ -2747,6 +2755,8 @@ snapshots:
|
||||
|
||||
'@types/estree@1.0.7': {}
|
||||
|
||||
'@types/estree@1.0.8': {}
|
||||
|
||||
'@types/hast@3.0.4':
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
@@ -2782,8 +2792,14 @@ snapshots:
|
||||
dependencies:
|
||||
acorn: 8.14.1
|
||||
|
||||
acorn-jsx@5.3.2(acorn@8.15.0):
|
||||
dependencies:
|
||||
acorn: 8.15.0
|
||||
|
||||
acorn@8.14.1: {}
|
||||
|
||||
acorn@8.15.0: {}
|
||||
|
||||
agent-base@6.0.2:
|
||||
dependencies:
|
||||
debug: 4.4.1
|
||||
@@ -2866,12 +2882,12 @@ snapshots:
|
||||
|
||||
binary-extensions@2.3.0: {}
|
||||
|
||||
brace-expansion@1.1.11:
|
||||
brace-expansion@1.1.12:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
concat-map: 0.0.1
|
||||
|
||||
brace-expansion@2.0.1:
|
||||
brace-expansion@2.0.2:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
|
||||
@@ -3146,14 +3162,14 @@ snapshots:
|
||||
esrecurse: 4.3.0
|
||||
estraverse: 5.3.0
|
||||
|
||||
eslint-scope@8.3.0:
|
||||
eslint-scope@8.4.0:
|
||||
dependencies:
|
||||
esrecurse: 4.3.0
|
||||
estraverse: 5.3.0
|
||||
|
||||
eslint-visitor-keys@3.4.3: {}
|
||||
|
||||
eslint-visitor-keys@4.2.0: {}
|
||||
eslint-visitor-keys@4.2.1: {}
|
||||
|
||||
eslint@9.17.0(jiti@1.21.7):
|
||||
dependencies:
|
||||
@@ -3167,16 +3183,16 @@ snapshots:
|
||||
'@humanfs/node': 0.16.6
|
||||
'@humanwhocodes/module-importer': 1.0.1
|
||||
'@humanwhocodes/retry': 0.4.3
|
||||
'@types/estree': 1.0.7
|
||||
'@types/estree': 1.0.8
|
||||
'@types/json-schema': 7.0.15
|
||||
ajv: 6.12.6
|
||||
chalk: 4.1.2
|
||||
cross-spawn: 7.0.6
|
||||
debug: 4.4.1
|
||||
escape-string-regexp: 4.0.0
|
||||
eslint-scope: 8.3.0
|
||||
eslint-visitor-keys: 4.2.0
|
||||
espree: 10.3.0
|
||||
eslint-scope: 8.4.0
|
||||
eslint-visitor-keys: 4.2.1
|
||||
espree: 10.4.0
|
||||
esquery: 1.6.0
|
||||
esutils: 2.0.3
|
||||
fast-deep-equal: 3.1.3
|
||||
@@ -3200,11 +3216,11 @@ snapshots:
|
||||
|
||||
esm-env@1.2.2: {}
|
||||
|
||||
espree@10.3.0:
|
||||
espree@10.4.0:
|
||||
dependencies:
|
||||
acorn: 8.14.1
|
||||
acorn-jsx: 5.3.2(acorn@8.14.1)
|
||||
eslint-visitor-keys: 4.2.0
|
||||
acorn: 8.15.0
|
||||
acorn-jsx: 5.3.2(acorn@8.15.0)
|
||||
eslint-visitor-keys: 4.2.1
|
||||
|
||||
espree@9.6.1:
|
||||
dependencies:
|
||||
@@ -3710,11 +3726,11 @@ snapshots:
|
||||
|
||||
minimatch@3.1.2:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.11
|
||||
brace-expansion: 1.1.12
|
||||
|
||||
minimatch@9.0.5:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
brace-expansion: 2.0.2
|
||||
|
||||
minimist@1.2.8: {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user