@ouro.bot/cli 0.1.0-alpha.863 → 0.1.0-alpha.864

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/changelog.json CHANGED
@@ -1,6 +1,13 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.864",
6
+ "changes": [
7
+ "Butler media status, diagnosis and queue now share one stall classification (never_started, unfinished_for_days, no_peers): a partial download with no ETA is reported as stalled with its percent, age and unchanged-since time instead of as downloading, and the diagnosis asks the owner (keep_waiting, find_another_release, give_up) instead of steering to a blocklist that would delete the partial.",
8
+ "A partial download older than 72 hours that still reports a real ETA is now reported as slow but moving, not stalled, and a stalled partial reported beside a never-started download keeps the blocklist advice for the latter."
9
+ ]
10
+ },
4
11
  {
5
12
  "version": "0.1.0-alpha.863",
6
13
  "changes": [
@@ -1,5 +1,5 @@
1
1
  {
2
- "runtimeVersion": "0.1.0-alpha.863",
2
+ "runtimeVersion": "0.1.0-alpha.864",
3
3
  "bundleSchemaVersion": 3,
4
4
  "lastUpdated": "2026-09-03T00:00:00.000Z"
5
5
  }
@@ -571,7 +571,8 @@ export async function mediaRequestStatus(a) {
571
571
  last_grab_title: grabs[0]?.sourceTitle ?? null,
572
572
  monitored: entity ? Boolean(entity.monitored) : null,
573
573
  }
574
- result.download = computeDownloadState(queueRecs, Date.now())
574
+ const statusNow = Date.now()
575
+ result.download = computeDownloadState(queueRecs, statusNow, (r) => observeProgress(progressKey(r, kind), r.sizeleft ?? 0, statusNow))
575
576
 
576
577
  const chain = await chainHealth()
577
578
  result.chain = chain
@@ -604,21 +605,51 @@ export function isStalledRow(r, nowMs) {
604
605
  return left > 0 && ageHours >= STUCK_INCOMPLETE_AFTER_HOURS
605
606
  }
606
607
 
607
- export function computeDownloadState(queueRecs, nowMs) {
608
+ const rowPercent = (r) => ((r.size ?? 0) > 0 ? Math.round((((r.size ?? 0) - (r.sizeleft ?? 0)) / r.size) * 100) : null)
609
+
610
+ // The one stall classification, shared by the status, diagnosis, queue, fill and blocklist
611
+ // surfaces. `previous` is the persisted progress memory for this download (or undefined).
612
+ // Returns null while the download may still be moving; otherwise the kind, whether the stall
613
+ // is confirmed (only confirmed stalls are acted on) and the facts the owner is told.
614
+ export function classifyStall(row, nowMs, previous) {
615
+ const why = stallReason(row, nowMs, previous)
616
+ if (!why) return null
617
+ const left = row.sizeleft ?? 0
618
+ const noEta = row.timeleft === undefined || row.timeleft === null || row.timeleft === "00:00:00"
619
+ const unchanged = previous && previous.sizeleft === left ? previous.at : null
620
+ return {
621
+ kind: why.stall, confirmed: why.confirmed, state: why.confirmed ? "stalled" : "suspected",
622
+ percent_complete: rowPercent(row),
623
+ age_hours: row.added ? Math.round((nowMs - Date.parse(row.added)) / 3_600_000) : null,
624
+ no_eta: noEta,
625
+ unchanged_since: unchanged === null ? null : new Date(unchanged).toISOString(),
626
+ unchanged_hours: unchanged === null ? null : Math.round((nowMs - unchanged) / 3_600_000),
627
+ }
628
+ }
629
+
630
+ // `previousFor(row)` reads the progress memory for a queue row; without it nothing is confirmed
631
+ // beyond the age rules.
632
+ export function computeDownloadState(queueRecs, nowMs, previousFor = () => undefined) {
608
633
  const byDownload = new Map()
609
634
  for (const r of queueRecs) byDownload.set(r.downloadId ?? `row:${r.id}`, r)
610
635
  const downloads = [...byDownload.values()]
611
636
  const sizeLeft = downloads.reduce((s, r) => s + (r.sizeleft ?? 0), 0)
612
637
  const sizeTotal = downloads.reduce((s, r) => s + (r.size ?? 0), 0)
613
- const stalledItems = downloads
614
- .filter((r) => isStalledRow(r, nowMs))
615
- .map((r) => ({
638
+ const classified = downloads
639
+ .map((r) => ({ r, c: classifyStall(r, nowMs, previousFor(r)) }))
640
+ .filter((x) => x.c)
641
+ .map(({ r, c }) => ({
616
642
  title: r.title ?? null,
617
643
  added_at: r.added ?? null,
618
- age_hours: Math.round((nowMs - Date.parse(r.added)) / 3_600_000),
619
644
  size_gb: Number(((r.size ?? 0) / 1073741824).toFixed(2)),
620
645
  queue_id: r.id ?? null,
646
+ ...c,
621
647
  }))
648
+ const slowItems = downloads
649
+ .filter((r) => isStalledRow(r, nowMs) && (r.sizeleft ?? 0) < (r.size ?? 0) && !classifyStall(r, nowMs, previousFor(r)))
650
+ .map((r) => ({ title: r.title ?? null, percent_complete: rowPercent(r), timeleft: r.timeleft ?? null, age_hours: Math.round((nowMs - Date.parse(r.added)) / 3_600_000) }))
651
+ const stalledItems = classified.filter((i) => i.confirmed)
652
+ const suspectedItems = classified.filter((i) => !i.confirmed)
622
653
  return {
623
654
  active_downloads: downloads.length,
624
655
  active_items: queueRecs.length,
@@ -630,6 +661,8 @@ export function computeDownloadState(queueRecs, nowMs) {
630
661
  percent_complete: sizeTotal > 0 ? Math.round(((sizeTotal - sizeLeft) / sizeTotal) * 100) : null,
631
662
  stalled: stalledItems.length > 0,
632
663
  stalled_items: stalledItems,
664
+ suspected_items: suspectedItems,
665
+ slow_items: slowItems,
633
666
  }
634
667
  }
635
668
 
@@ -645,12 +678,29 @@ export function diagnose({ shelf, result, chain, entity, kind }) {
645
678
  }
646
679
  if (result.download.stalled) {
647
680
  const items = result.download.stalled_items
681
+ const partials = items.filter((i) => i.kind !== "never_started")
682
+ const neverStarted = items.filter((i) => i.kind === "never_started")
683
+ if (partials.length) {
684
+ // A partial download holds progress that removing it loses, and the owner's standing
685
+ // instruction for this case is to be asked, so nothing here acts on its own.
686
+ const facts = partials.map((i) => {
687
+ const since = i.unchanged_since ? ` since ${i.unchanged_since} (${i.unchanged_hours} hours)` : ""
688
+ return `${i.title ?? "A release"} is stuck at ${i.percent_complete}% with no download activity${since}; no seeders are serving the rest`
689
+ })
690
+ return { stuck_stage: "download", stuck_reason: "stalled_partial_download",
691
+ likely_fix: "ask_owner", human_action_required: true,
692
+ choices: ["keep_waiting", "find_another_release", "give_up"],
693
+ percent_complete: result.download.percent_complete,
694
+ detail: items,
695
+ ...(neverStarted.length ? { never_started: { likely_fix: "blocklist_and_research", items: neverStarted } } : {}),
696
+ summary: `${facts.join(". ")}. Ask the owner whether to keep waiting, find another release, or give up; do not remove it on your own, because removing a partial download loses its progress.${neverStarted.length ? ` Separately, ${neverStarted.length} download(s) never started (${neverStarted.map((i) => i.title ?? "a release").join("; ")}); those can be blocklisted and searched again (blocklist_and_research) without asking.` : ""} media_fill_missing can look for a seeded alternative without deleting the partial first; it removes the partial only after a seeded replacement is grabbed.` }
697
+ }
648
698
  const oldest = Math.max(...items.map((i) => i.age_hours))
649
699
  return { stuck_stage: "download", stuck_reason: "stalled_no_progress",
650
700
  likely_fix: "blocklist_and_research", human_action_required: false,
651
701
  percent_complete: result.download.percent_complete,
652
702
  detail: items,
653
- summary: `Stalled, not slow. ${items.length === 1 ? "The release" : `${items.length} releases`} ${items.length === 1 ? "has" : "have"} not downloaded a single byte in ${oldest} hours, which means no seeders rather than a quiet queue. Waiting will not fix it; blocklist the release and search again for a different one.` }
703
+ summary: `Stalled, not slow. ${items.length === 1 ? "The release" : `${items.length} releases`} ${items.length === 1 ? "has" : "have"} not downloaded anything in ${oldest} hours, which means no seeders rather than a quiet queue. Nothing was lost by waiting this long; blocklist the release and search again for a different one.` }
654
704
  }
655
705
  if (result.download.active_downloads > 0) {
656
706
  const left = result.download.size_left_bytes
@@ -660,9 +710,12 @@ export function diagnose({ shelf, result, chain, entity, kind }) {
660
710
  const what = result.download.active_downloads === 1 && result.download.active_items > 1
661
711
  ? `a ${result.download.active_items}-episode pack`
662
712
  : `${result.download.active_downloads} download(s)`
713
+ const suspected = result.download.suspected_items ?? []
714
+ const slow = result.download.slow_items ?? []
663
715
  return { stuck_stage: null, stuck_reason: null, likely_fix: null, human_action_required: false,
664
716
  percent_complete: pct,
665
- summary: `Downloading now — ${what}${pct === null ? "" : `, ${pct}% done`}, about ${gb} GB to go.` }
717
+ ...(suspected.length ? { stall_suspected: true, detail: suspected } : {}),
718
+ summary: `Downloading now — ${what}${pct === null ? "" : `, ${pct}% done`}, about ${gb} GB to go.${slow.length ? ` Slow but still moving: ${slow.map((i) => `${i.title ?? "a release"} is ${i.percent_complete}% done after ${i.age_hours} hours with an ETA of ${i.timeleft}`).join("; ")}.` : ""}${suspected.length ? ` It reports no ETA after ${Math.max(...suspected.map((i) => i.age_hours))} hours, so it may be stalled; the next check 30 minutes or more later confirms it. Do not promise an ETA.` : ""}` }
666
719
  }
667
720
  if (result.download.errors.length) {
668
721
  return { stuck_stage: "download", stuck_reason: "download_client_error", likely_fix: "retry_or_replace_release",
@@ -707,6 +760,12 @@ async function mediaDiagnoseAndFix(a) {
707
760
  human_action_required: true,
708
761
  human_action_reason: "The acquisition chain itself is down (see before.chain.problems). Re-searching cannot help until indexers are restored." }
709
762
  }
763
+ if (action === "ask_owner") {
764
+ return { action, result: "owner_decision_needed", before, after: null, human_action_required: true,
765
+ choices: before.diagnosis?.choices ?? ["keep_waiting", "find_another_release", "give_up"],
766
+ ...(before.diagnosis?.never_started ? { never_started: before.diagnosis.never_started } : {}),
767
+ human_action_reason: before.diagnosis?.summary ?? "The owner decides." }
768
+ }
710
769
  if (a.dry_run) {
711
770
  return { action, dry_run: true, result: "would_apply", before,
712
771
  would_do: describeAction(action), human_action_required: false }
@@ -731,9 +790,10 @@ async function mediaDiagnoseAndFix(a) {
731
790
  // Removing with blocklist=true is what stops the same dead release being
732
791
  // grabbed straight back. The search that follows is then free to pick a
733
792
  // different one.
734
- const stalled = before.download.stalled_items ?? []
793
+ // Only a download that never started: a partial holds progress and is the owner's call.
794
+ const stalled = (before.download.stalled_items ?? []).filter((i) => i.kind === "never_started")
735
795
  if (!stalled.length) return { action, result: "nothing_to_blocklist", before, after: null, human_action_required: false,
736
- human_action_reason: "No download has been sitting at zero long enough to call it stalled." }
796
+ human_action_reason: "No download has been sitting at zero long enough to call it stalled. A partial download is not removed this way: ask the owner." }
737
797
  for (const item of stalled) {
738
798
  if (item.queue_id === null) continue
739
799
  const client = kind === "series" ? sonarr : radarr
@@ -857,10 +917,13 @@ async function itemQueue(kind, serviceId) {
857
917
  return rows.filter((r) => r[idKey] === serviceId)
858
918
  }
859
919
 
860
- function queueReport(rows) {
920
+ function queueReport(rows, kind = "series") {
861
921
  const now = Date.now()
862
- return rows.map((r) => ({ queue_id: r.id ?? null, title: r.title ?? null, status: r.status ?? null,
863
- size_left_bytes: r.sizeleft ?? 0, stalled: isStalledRow(r, now) }))
922
+ return rows.map((r) => {
923
+ const c = classifyStall(r, now, peekProgress(progressKey(r, kind), r.sizeleft ?? 0, now))
924
+ return { queue_id: r.id ?? null, title: r.title ?? null, status: r.status ?? null,
925
+ size_left_bytes: r.sizeleft ?? 0, stalled: Boolean(c?.confirmed), ...(c ? { stall_kind: c.kind } : {}) }
926
+ })
864
927
  }
865
928
 
866
929
  // ------------------------------------------------------------ tool: media_queue
@@ -868,13 +931,15 @@ function queueReport(rows) {
868
931
  const QUEUE_VIEW_MAX_ROWS = 100
869
932
 
870
933
  // One queue row as the owner's torrent or usenet client reports it through Sonarr/Radarr.
871
- export function queueRowView(r, nowMs) {
934
+ export function queueRowView(r, nowMs, previous) {
935
+ const c = classifyStall(r, nowMs, previous)
872
936
  return {
873
937
  queue_id: r.id ?? null, title: r.title ?? null, status: r.status ?? null,
874
938
  tracked_download_state: r.trackedDownloadState ?? null, tracked_download_status: r.trackedDownloadStatus ?? null,
875
939
  percent: pct(r), timeleft: r.timeleft ?? null, size_left_bytes: r.sizeleft ?? 0, size_bytes: r.size ?? 0,
876
940
  protocol: r.protocol ?? null, download_client: r.downloadClient ?? null, indexer: r.indexer ?? null,
877
- added: r.added ?? null, stalled: isStalledRow(r, nowMs),
941
+ added: r.added ?? null, stalled: Boolean(c?.confirmed),
942
+ ...(c ? { stall: { kind: c.kind, state: c.state, percent_complete: c.percent_complete, age_hours: c.age_hours, no_eta: c.no_eta, unchanged_since: c.unchanged_since } } : {}),
878
943
  ...(r.errorMessage ? { error_message: String(r.errorMessage).slice(0, 300) } : {}),
879
944
  }
880
945
  }
@@ -903,12 +968,13 @@ export async function mediaQueue(a) {
903
968
  }
904
969
  total = Math.max(total ?? 0, rows.length)
905
970
  const now = Date.now()
906
- const shown = rows.slice(0, QUEUE_VIEW_MAX_ROWS).map((r) => queueRowView(r, now))
971
+ // Looking records the sighting in the local progress memory (never in Sonarr/Radarr), so this call can confirm a stall too.
972
+ const shown = rows.slice(0, QUEUE_VIEW_MAX_ROWS).map((r) => queueRowView(r, now, observeProgress(progressKey(r, a.kind), r.sizeleft ?? 0, now)))
907
973
  return {
908
974
  kind: a.kind, ...(item ? { service_id: item.id, title: item.title } : {}),
909
975
  count: total, shown: shown.length, ...(total > shown.length ? { truncated: true } : {}), queue: shown,
910
976
  summary: !total ? "Nothing for this is in the Sonarr/Radarr queue."
911
- : `${total} queue row${total === 1 ? "" : "s"}${total > shown.length ? ` (showing ${shown.length})` : ""}; ${shown.filter((r) => r.stalled).length} stalled.`,
977
+ : `${total} queue row${total === 1 ? "" : "s"}${total > shown.length ? ` (showing ${shown.length})` : ""}; ${shown.filter((r) => r.stalled).length} stalled${shown.some((r) => r.stall?.state === "suspected") ? `, ${shown.filter((r) => r.stall?.state === "suspected").length} suspected (no ETA, to be confirmed by a check 30 minutes later)` : ""}.`,
912
978
  note: "Sonarr/Radarr do not report seeders or peers. A row stuck at the same percent with no timeleft is the stall signal; for a release's seeder count use media_indexer_search.",
913
979
  }
914
980
  }
@@ -925,7 +991,7 @@ export async function mediaSearchNow(a) {
925
991
  if (!found.item) return { result: found.result, kind: a.kind, candidates: found.candidates }
926
992
  const { item } = found
927
993
  const command = await runSearch(a.kind, item.id)
928
- return { kind: a.kind, service_id: item.id, title: item.title, command, queue: queueReport(await itemQueue(a.kind, item.id)) }
994
+ return { kind: a.kind, service_id: item.id, title: item.title, command, queue: queueReport(await itemQueue(a.kind, item.id), a.kind) }
929
995
  }
930
996
 
931
997
  // Whether a download may be removed without the owner naming it. A download that has never
@@ -952,10 +1018,11 @@ export async function mediaBlocklistStalled(a) {
952
1018
  if (Array.isArray(a.queue_ids) && a.queue_ids.length) {
953
1019
  const own = new Set(rows.map((r) => r.id))
954
1020
  const foreign = a.queue_ids.filter((id) => !own.has(id))
955
- if (foreign.length) return { result: "queue_id_not_for_item", foreign_queue_ids: foreign, queue: queueReport(rows) }
1021
+ if (foreign.length) return { result: "queue_id_not_for_item", foreign_queue_ids: foreign, queue: queueReport(rows, a.kind) }
956
1022
  targets = rows.filter((r) => a.queue_ids.includes(r.id))
957
1023
  } else {
958
- targets = rows.filter((r) => isStalledRow(r, now))
1024
+ // Candidates only: removalVerdict below confirms a no-ETA partial against the progress memory.
1025
+ targets = rows.filter((r) => stallReason(r, now, undefined) !== null)
959
1026
  }
960
1027
  // A season pack is many queue rows sharing one downloadId; deleting one removes
961
1028
  // the download for all, so delete once per download.
@@ -972,7 +1039,7 @@ export async function mediaBlocklistStalled(a) {
972
1039
  // deleted on "yes please get them").
973
1040
  const ownerWords = typeof a.owner_words === "string" ? a.owner_words.trim() : ""
974
1041
  if (ownerWords && !(Array.isArray(a.queue_ids) && a.queue_ids.length)) {
975
- return { result: "owner_words_need_queue_ids", message: "owner_words removes only downloads the owner named: pass their queue_ids too.", queue: queueReport(rows) }
1042
+ return { result: "owner_words_need_queue_ids", message: "owner_words removes only downloads the owner named: pass their queue_ids too.", queue: queueReport(rows, a.kind) }
976
1043
  }
977
1044
  const kept = []
978
1045
  if (!ownerWords) {
@@ -984,10 +1051,10 @@ export async function mediaBlocklistStalled(a) {
984
1051
  })
985
1052
  }
986
1053
  if (!targets.length && kept.length) {
987
- return { result: "kept_downloads", kept, queue: queueReport(rows),
1054
+ return { result: "kept_downloads", kept, queue: queueReport(rows, a.kind),
988
1055
  message: "Nothing removed. These downloads are not confirmed dead, and removing one deletes its progress. A partial download counts as dead only after two calls at least 30 minutes apart see no progress and no ETA; call again later to confirm. For missing episodes call media_fill_missing instead: it replaces a stalled download only when a seeded alternative exists. Pass owner_words only with the owner's own words asking to remove, clear or cancel this download; 'get them' is not that." }
989
1056
  }
990
- if (!targets.length) return { result: "nothing_to_blocklist", queue: queueReport(rows) }
1057
+ if (!targets.length) return { result: "nothing_to_blocklist", queue: queueReport(rows, a.kind) }
991
1058
  const removed = []
992
1059
  const failed = []
993
1060
  for (const row of targets) {
@@ -1001,7 +1068,7 @@ export async function mediaBlocklistStalled(a) {
1001
1068
  }
1002
1069
  }
1003
1070
  const command = a.research === false ? null : await runSearch(a.kind, serviceId)
1004
- return { removed, ...(failed.length ? { failed } : {}), ...(kept.length ? { kept } : {}), ...(ownerWords ? { removed_on_owner_words: ownerWords } : {}), command, queue: queueReport(await itemQueue(a.kind, serviceId)) }
1071
+ return { removed, ...(failed.length ? { failed } : {}), ...(kept.length ? { kept } : {}), ...(ownerWords ? { removed_on_owner_words: ownerWords } : {}), command, queue: queueReport(await itemQueue(a.kind, serviceId), a.kind) }
1005
1072
  }
1006
1073
 
1007
1074
  // ------------------------------- tools: episodes / releases / blocklist / import
@@ -1332,11 +1399,16 @@ function matchUnblocked(release, { seriesId, seriesTitle, seriesYear, wanted, ha
1332
1399
  // `confirmed` is false when the only sign is a missing ETA not yet seen to persist; such a
1333
1400
  // download is reported but never replaced.
1334
1401
  export function stallReason(row, nowMs, previous) {
1335
- if (isStalledRow(row, nowMs)) return { stall: (row.sizeleft ?? 0) >= (row.size ?? 0) ? "never_started" : "unfinished_for_days", confirmed: true }
1336
1402
  const left = row.sizeleft ?? 0
1403
+ const noEta = row.timeleft === undefined || row.timeleft === null || row.timeleft === "00:00:00"
1404
+ const unchangedFor30 = Boolean(previous && previous.sizeleft === left && nowMs - previous.at >= FILL_STALL_CONFIRM_MS)
1405
+ if (isStalledRow(row, nowMs)) {
1406
+ if (left >= (row.size ?? 0)) return { stall: "never_started", confirmed: true }
1407
+ // Old and unfinished is not dead by age alone: a slow torrent with a real ETA is still moving.
1408
+ return noEta || unchangedFor30 ? { stall: "unfinished_for_days", confirmed: true } : null
1409
+ }
1337
1410
  const ageHours = row.added ? (nowMs - Date.parse(row.added)) / 3_600_000 : 0
1338
1411
  const moving = row.status === "downloading" || row.status === "warning"
1339
- const noEta = row.timeleft === undefined || row.timeleft === null || row.timeleft === "00:00:00"
1340
1412
  if (!(moving && left > 0 && noEta && ageHours >= STALL_AFTER_HOURS)) return null
1341
1413
  const unchanged = previous && previous.sizeleft === left && nowMs - previous.at >= FILL_STALL_CONFIRM_MS
1342
1414
  return { stall: "no_peers", confirmed: Boolean(unchanged) }
@@ -1381,6 +1453,11 @@ function saveProgress(nowMs) {
1381
1453
  process.stderr.write(`media-mcp: download progress not saved: ${e.message}\n`)
1382
1454
  }
1383
1455
  }
1456
+ // Read-only look at the memory: what observeProgress would return, without recording a sighting.
1457
+ function peekProgress(key, sizeleft, nowMs) {
1458
+ const previous = loadProgress().get(key)
1459
+ return previous && nowMs - previous.seen <= PROGRESS_CONTINUITY_MS ? previous : undefined
1460
+ }
1384
1461
  export const progressKey = (row, kind = "series") => `${kind}:${row.downloadId ?? `row:${row.id}`}`.toUpperCase()
1385
1462
  function observeProgress(downloadId, sizeleft, nowMs) {
1386
1463
  const seen = loadProgress()
@@ -1,7 +1,7 @@
1
1
  <?xml version="1.0"?>
2
2
  <Container version="2">
3
3
  <Name>ouro-butler</Name>
4
- <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.863</Repository>
4
+ <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.864</Repository>
5
5
  <Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
6
6
  <Network>host</Network>
7
7
  <Shell>sh</Shell>
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.863",
3
+ "version": "0.1.0-alpha.864",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@ouro.bot/cli",
9
- "version": "0.1.0-alpha.863",
9
+ "version": "0.1.0-alpha.864",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sdk": "^0.78.0",
12
12
  "@azure/identity": "^4.13.0",
@@ -7034,9 +7034,9 @@
7034
7034
  }
7035
7035
  },
7036
7036
  "node_modules/shell-quote": {
7037
- "version": "1.10.0",
7038
- "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/shell-quote/-/shell-quote-1.10.0.tgz",
7039
- "integrity": "sha1-SCAz4ZLk9cBxUVIf+gNADscbGw8=",
7037
+ "version": "1.12.0",
7038
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.12.0.tgz",
7039
+ "integrity": "sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==",
7040
7040
  "license": "MIT",
7041
7041
  "engines": {
7042
7042
  "node": ">= 0.4"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.863",
3
+ "version": "0.1.0-alpha.864",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },