mirror of
https://github.com/magnus919/agent-skills.git
synced 2026-09-16 14:06:27 +03:00
Fixes #289 - transcripts list: drop removed TranscriptsQueryScope type (scope is a String in the live schema), require [String!] for organizers and participants, add title/organizer-email/participant-email filters - bites create: use the live transcript_Id argument name and the BitePrivacy enum (public, team, participants) - add ergonomic commands for documented gaps found in the audit: askfred get, meetings update-channel, meetings share --expiry-days, live add-to (addToLiveMeeting), live soundbite (createLiveSoundbite), audio create-upload/confirm-upload (two-phase upload), users set-role - add eval manifest (5 cases) to satisfy the modified-skill eval ratchet - update SKILL.md, cli-reference, api-reference, source-index, workflows to match the audited surface and record the 2026-08-05 schema audit
285 lines
30 KiB
Python
Executable File
285 lines
30 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Fireflies GraphQL CLI. Requires Python 3.9+ and no third-party packages."""
|
|
import argparse
|
|
import hashlib
|
|
import hmac
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
from urllib.error import HTTPError, URLError
|
|
from urllib.request import Request, urlopen
|
|
|
|
DEFAULT_ENDPOINT = "https://api.fireflies.ai/graphql"
|
|
EX_USAGE, EX_CONFIG, EX_TRANSPORT, EX_GRAPHQL, EX_CONFIRM = 2, 3, 4, 5, 6
|
|
|
|
DOCS = {
|
|
"transcripts_list": "query Transcripts($title: String, $keyword: String, $scope: String, $fromDate: DateTime, $toDate: DateTime, $limit: Int, $skip: Int, $hostEmail: String, $userId: String, $mine: Boolean, $organizers: [String!], $participants: [String!], $channelId: String, $organizerEmail: String, $participantEmail: String) { transcripts(title: $title, keyword: $keyword, scope: $scope, fromDate: $fromDate, toDate: $toDate, limit: $limit, skip: $skip, host_email: $hostEmail, user_id: $userId, mine: $mine, organizers: $organizers, participants: $participants, channel_id: $channelId, organizer_email: $organizerEmail, participant_email: $participantEmail) { id title date duration organizer_email participants transcript_url } }",
|
|
"transcript": "query Transcript($id: String!) { transcript(id: $id) { id title date duration organizer_email participants transcript_url summary { overview } speakers { id name } sentences { index text speaker_name start_time end_time } analytics { sentiments { negative_pct neutral_pct positive_pct } } } }",
|
|
"users": "query Users { users { user_id email name is_admin integrations } }",
|
|
"me": "query User { user { user_id email name is_admin integrations } }",
|
|
"user": "query User($id: String!) { user(id: $id) { user_id email name is_admin integrations } }",
|
|
"contacts": "query Contacts { contacts { email name picture last_meeting_date } }",
|
|
"channels": "query Channels { channels { id title is_private created_at updated_at created_by members { user_id email name } } }",
|
|
"channel": "query Channel($id: ID!) { channel(id: $id) { id title is_private created_at updated_at created_by members { user_id email name } } }",
|
|
"groups": "query UserGroups($mine: Boolean) { user_groups(mine: $mine) { id name handle members { user_id first_name last_name email } } }",
|
|
"bites": "query Bites($mine: Boolean, $transcriptId: ID, $myTeam: Boolean, $limit: Int, $skip: Int) { bites(mine: $mine, transcript_id: $transcriptId, my_team: $myTeam, limit: $limit, skip: $skip) { id transcript_id name status summary start_time end_time } }",
|
|
"bite": "query Bite($id: ID!) { bite(id: $id) { id transcript_id name status summary start_time end_time } }",
|
|
"apps": "query GetAIAppsOutputs($appId: String, $transcriptId: String, $skip: Float, $limit: Float) { apps(app_id: $appId, transcript_id: $transcriptId, skip: $skip, limit: $limit) { outputs { transcript_id user_id app_id created_at title prompt response } } }",
|
|
"analytics": "query Analytics($startTime: String, $endTime: String) { analytics(start_time: $startTime, end_time: $endTime) { team { conversation { average_filler_words } meeting { count duration } } users { user_id user_name user_email conversation { talk_listen_pct } meeting { count duration } } } }",
|
|
"active": "query ActiveMeetings { active_meetings { id title organizer_email meeting_link start_time end_time privacy state } }",
|
|
"live_items": "query LiveActionItems($id: ID!) { live_action_items(meeting_id: $id) { name action_item } }",
|
|
"audit": "query AuditEvents($limit: Int, $cursor: String, $filters: AuditEventFiltersInput!) { auditEvents(limit: $limit, cursor: $cursor, filters: $filters) { events { id time action actor { user_id } resource { type id } } has_more next_cursor } }",
|
|
"rules": "query RuleExecutions($limit: Int, $cursor: String, $filters: RuleExecutionFiltersInput) { rule_executions_by_meeting(limit: $limit, cursor: $cursor, filters: $filters) { meetings { meeting_id meeting { id title organizer_email } executions { extension_id extension_title stopped_at user_name } } has_more next_cursor } }",
|
|
"askfred_threads": "query GetAskFredThreads { askfred_threads { id title transcript_id user_id created_at } }",
|
|
"delete": "mutation DeleteTranscript($id: String!) { deleteTranscript(id: $id) { id title organizer_email date duration } }",
|
|
"rename": "mutation UpdateMeetingTitle($input: UpdateMeetingTitleInput!) { updateMeetingTitle(input: $input) { title } }",
|
|
"privacy": "mutation UpdateMeetingPrivacy($input: UpdateMeetingPrivacyInput!) { updateMeetingPrivacy(input: $input) { id title privacy } }",
|
|
"state": "mutation UpdateMeetingState($input: UpdateMeetingStateInput!) { updateMeetingState(input: $input) { success action } }",
|
|
"share": "mutation ShareMeeting($input: ShareMeetingInput!) { shareMeeting(input: $input) { success message } }",
|
|
"revoke": "mutation RevokeSharedMeetingAccess($input: RevokeSharedMeetingAccessInput!) { revokeSharedMeetingAccess(input: $input) { success message } }",
|
|
"bite_create": "mutation CreateBite($transcriptId: ID!, $start: Float!, $end: Float!, $name: String, $type: String, $summary: String, $privacy: [BitePrivacy!]) { createBite(transcript_Id: $transcriptId, start_time: $start, end_time: $end, name: $name, media_type: $type, summary: $summary, privacies: $privacy) { id name status } }",
|
|
"live_add": "mutation CreateLiveActionItem($input: CreateLiveActionItemInput!) { createLiveActionItem(input: $input) { success } }",
|
|
"upload": "mutation UploadAudio($input: AudioUploadInput) { uploadAudio(input: $input) { success title message } }",
|
|
"askfred_create": "mutation CreateThread($input: CreateAskFredThreadInput!) { createAskFredThread(input: $input) { message { id thread_id query answer suggested_queries status created_at } } }",
|
|
"askfred_continue": "mutation ContinueThread($input: ContinueAskFredThreadInput!) { continueAskFredThread(input: $input) { message { id thread_id query answer suggested_queries status created_at } } }",
|
|
"askfred_delete": "mutation DeleteThread($id: String!) { deleteAskFredThread(id: $id) { id title transcript_id user_id created_at } }",
|
|
"askfred_thread": "query AskFredThread($id: String!) { askfred_thread(id: $id) { id title transcript_id user_id created_at } }",
|
|
"channel_update": "mutation UpdateMeetingChannel($input: UpdateMeetingChannelInput!) { updateMeetingChannel(input: $input) { id title organizer_email } }",
|
|
"live_add_to": "mutation AddToLiveMeeting($meetingLink: String!, $title: String, $meetingPassword: String, $duration: Int, $language: String) { addToLiveMeeting(meeting_link: $meetingLink, title: $title, meeting_password: $meetingPassword, duration: $duration, language: $language) { message success } }",
|
|
"live_soundbite": "mutation CreateLiveSoundbite($input: CreateLiveSoundbiteInput!) { createLiveSoundbite(input: $input) { success } }",
|
|
"upload_url": "mutation CreateUploadUrl($input: CreateUploadUrlInput!) { createUploadUrl(input: $input) { upload_url meeting_id expires_at } }",
|
|
"upload_confirm": "mutation ConfirmUpload($input: ConfirmUploadInput!) { confirmUpload(input: $input) { success meeting_id message } }",
|
|
"user_role": "mutation SetUserRole($userId: String!, $role: Role!) { setUserRole(user_id: $userId, role: $role) { user_id email name is_admin } }",
|
|
"introspection": "query IntrospectionQuery { __schema { queryType { name } mutationType { name } types { name kind } } }",
|
|
}
|
|
|
|
def fail(message, code=EX_USAGE):
|
|
print("Error: " + message, file=sys.stderr)
|
|
raise SystemExit(code)
|
|
|
|
def json_value(value, label):
|
|
try: return json.loads(value)
|
|
except json.JSONDecodeError as exc: fail(f"{label} must be valid JSON: {exc}")
|
|
|
|
def globals_anywhere(argv):
|
|
values = {"json": False, "dry_run": False, "quiet": False, "verbose": False,
|
|
"api_key": None, "endpoint": None, "timeout": 30.0}
|
|
result, i = [argv[0]], 1
|
|
flags = {"--json": ("json", False), "--dry-run": ("dry_run", False), "--quiet": ("quiet", False), "--verbose": ("verbose", False), "--api-key": ("api_key", True), "--endpoint": ("endpoint", True), "--timeout": ("timeout", True)}
|
|
while i < len(argv):
|
|
arg = argv[i]
|
|
if arg in flags:
|
|
key, takes_value = flags[arg]
|
|
if takes_value:
|
|
if i + 1 == len(argv): fail(f"{arg} requires a value")
|
|
values[key] = argv[i + 1]; i += 2
|
|
else: values[key] = True; i += 1
|
|
else: result.append(arg); i += 1
|
|
try: values["timeout"] = float(values["timeout"])
|
|
except ValueError: fail("--timeout must be a number")
|
|
return values, result
|
|
|
|
def operation_kind(document):
|
|
cleaned = re.sub(r"(?:#.*$|\s)+", " ", document).lstrip()
|
|
match = re.match(r"(?:query|mutation)\b", cleaned, re.I)
|
|
if match: return match.group(0).lower()
|
|
return "query" if cleaned.startswith("{") else None
|
|
|
|
class Client:
|
|
def __init__(self, options): self.options = options
|
|
def run(self, document, variables=None, operation_name=None):
|
|
payload = {"query": document, "variables": variables or {}}
|
|
if operation_name: payload["operationName"] = operation_name
|
|
if self.options["dry_run"]: return {"dry_run": True, "endpoint": self.endpoint, "payload": payload}, 0
|
|
if not self.api_key: fail("FIREFLIES_API_KEY is not set; use --api-key or --dry-run.", EX_CONFIG)
|
|
body = json.dumps(payload).encode("utf-8")
|
|
request = Request(self.endpoint, body, {"Authorization": "Bearer " + self.api_key, "Content-Type": "application/json", "Accept": "application/json"})
|
|
try:
|
|
with urlopen(request, timeout=self.options["timeout"]) as response: raw = response.read().decode("utf-8")
|
|
except HTTPError as exc:
|
|
raw = exc.read().decode("utf-8", "replace")
|
|
try:
|
|
data = json.loads(raw)
|
|
return data, EX_GRAPHQL if data.get("errors") else EX_TRANSPORT
|
|
except json.JSONDecodeError: fail(f"HTTP {exc.code}: {raw[:300]}", EX_TRANSPORT)
|
|
except URLError as exc: fail(f"transport error: {exc.reason}", EX_TRANSPORT)
|
|
try: data = json.loads(raw)
|
|
except json.JSONDecodeError: fail("server returned invalid JSON", EX_TRANSPORT)
|
|
return data, EX_GRAPHQL if data.get("errors") else 0
|
|
@property
|
|
def endpoint(self): return self.options["endpoint"] or os.getenv("FIREFLIES_ENDPOINT") or DEFAULT_ENDPOINT
|
|
@property
|
|
def api_key(self): return self.options["api_key"] or os.getenv("FIREFLIES_API_KEY")
|
|
|
|
def emit(data, options, code=0):
|
|
print(json.dumps(data, indent=None if options["json"] else 2, sort_keys=True, default=str))
|
|
if code == EX_GRAPHQL:
|
|
errors = data.get("errors", []) if isinstance(data, dict) else []
|
|
print("GraphQL error: " + str(errors[0].get("message", "unknown error") if errors else "unknown error"), file=sys.stderr)
|
|
raise SystemExit(code)
|
|
|
|
def add_example(parser, text): parser.epilog = "Examples:\n " + text
|
|
def add_confirm(parser): parser.add_argument("--confirm", action="store_true", help="Required literal confirmation for a real mutation.")
|
|
def add_doc_args(parser):
|
|
parser.add_argument("--document", required=True, help="GraphQL document."); parser.add_argument("--variables", help="Variables JSON object."); parser.add_argument("--variables-file", help="Path to variables JSON object."); parser.add_argument("--operation-name", help="GraphQL operationName.")
|
|
def ensure_confirm(args, options):
|
|
if not options["dry_run"] and not args.confirm: fail("a real mutation requires --confirm", EX_CONFIRM)
|
|
def run_doc(client, options, document, variables=None, operation_name=None):
|
|
data, code = client.run(document, variables, operation_name); emit(data, options, code)
|
|
def transcript_vars(args):
|
|
values = {"title": args.title, "keyword": args.keyword, "scope": args.scope, "fromDate": args.from_date, "toDate": args.to_date, "limit": args.limit, "skip": args.skip, "hostEmail": args.host_email, "userId": args.user_id, "mine": args.mine, "organizers": split_csv(args.organizers), "participants": split_csv(args.participants), "channelId": args.channel_id, "organizerEmail": args.organizer_email, "participantEmail": args.participant_email}
|
|
if args.limit > 50: fail("--limit cannot exceed Fireflies' documented maximum of 50")
|
|
return {k:v for k,v in values.items() if v is not None}
|
|
def split_csv(value): return value.split(",") if value else None
|
|
|
|
def build_parser():
|
|
parser = argparse.ArgumentParser(prog="fireflies", description="Source-faithful Fireflies.ai GraphQL CLI.", formatter_class=argparse.RawDescriptionHelpFormatter, epilog="Global flags work before or after commands. Example: fireflies transcripts list --limit 10 --json")
|
|
parser.add_argument("--json", action="store_true", help="Emit one JSON document."); parser.add_argument("--api-key", help="Override FIREFLIES_API_KEY."); parser.add_argument("--endpoint", help="Override FIREFLIES_ENDPOINT."); parser.add_argument("--timeout", type=float, help="HTTP timeout seconds."); parser.add_argument("--dry-run", action="store_true", help="Show exact GraphQL payload without HTTP."); parser.add_argument("--quiet", action="store_true", help="Suppress optional diagnostics."); parser.add_argument("--verbose", action="store_true", help="Enable diagnostics on stderr.")
|
|
root = parser.add_subparsers(dest="root", required=True)
|
|
for name, kind in (("query", "query"), ("mutation", "mutation")):
|
|
p = root.add_parser(name, help=f"Run a generic GraphQL {kind}."); add_doc_args(p)
|
|
if kind == "mutation": add_confirm(p)
|
|
add_example(p, f"fireflies {name} --document '{kind} {{ ... }}'" + (" --confirm" if kind == "mutation" else ""))
|
|
t = root.add_parser("transcripts", help="Read or delete transcripts."); ts = t.add_subparsers(dest="action", required=True)
|
|
p = ts.add_parser("list", help="List transcripts.");
|
|
for flag, dest in (("--title","title"),("--keyword","keyword"),("--scope","scope"),("--from-date","from_date"),("--to-date","to_date"),("--host-email","host_email"),("--user-id","user_id"),("--organizers","organizers"),("--participants","participants"),("--channel-id","channel_id"),("--organizer-email","organizer_email"),("--participant-email","participant_email")): p.add_argument(flag, dest=dest)
|
|
p.add_argument("--limit", type=int, default=10); p.add_argument("--skip", type=int, default=0); p.add_argument("--mine", action="store_true", default=None); add_example(p, "fireflies transcripts list --keyword roadmap --limit 10")
|
|
p=ts.add_parser("get", help="Get transcript details."); p.add_argument("id"); add_example(p,"fireflies transcripts get transcript-id")
|
|
p=ts.add_parser("delete", help="Delete a transcript."); p.add_argument("id"); add_confirm(p); add_example(p,"fireflies transcripts delete transcript-id --confirm")
|
|
u=root.add_parser("users", help="Read users."); us=u.add_subparsers(dest="action",required=True)
|
|
for action in ("me","list","get","set-role"):
|
|
p=us.add_parser(action);
|
|
if action == "get": p.add_argument("id")
|
|
if action == "set-role": p.add_argument("--user-id",required=True); p.add_argument("--role",required=True,choices=("admin","user")); add_confirm(p)
|
|
add_example(p,f"fireflies users {action}" + (" user-id" if action=="get" else "") + (" --user-id X --role admin" if action=="set-role" else ""))
|
|
simple = {"contacts":"Contacts","channels":"Channels","groups":"User groups","apps":"AI app outputs"}
|
|
for name, help_text in simple.items():
|
|
p=root.add_parser(name, help=help_text); sub=p.add_subparsers(dest="action", required=True); q=sub.add_parser("list"); add_example(q,f"fireflies {name} list")
|
|
if name == "channels": q=sub.add_parser("get"); q.add_argument("id"); add_example(q,"fireflies channels get channel-id")
|
|
if name == "apps": q.add_argument("--app-id"); q.add_argument("--transcript-id"); q.add_argument("--limit",type=float); q.add_argument("--skip",type=float)
|
|
if name == "groups": q.add_argument("--mine",action="store_true",default=None)
|
|
b=root.add_parser("bites",help="Read or create Bites."); bs=b.add_subparsers(dest="action",required=True); p=bs.add_parser("list"); p.add_argument("--mine",action="store_true",default=None);p.add_argument("--transcript-id");p.add_argument("--my-team",action="store_true",default=None);p.add_argument("--limit",type=int);p.add_argument("--skip",type=int);add_example(p,"fireflies bites list --mine")
|
|
p=bs.add_parser("get");p.add_argument("id");add_example(p,"fireflies bites get bite-id")
|
|
p=bs.add_parser("create");p.add_argument("--transcript-id",required=True);p.add_argument("--start",type=float,required=True);p.add_argument("--end",type=float,required=True);p.add_argument("--name");p.add_argument("--type");p.add_argument("--summary");p.add_argument("--privacy",action="append",choices=("public","team","participants"));add_confirm(p);add_example(p,"fireflies bites create --transcript-id id --start 0 --end 30 --confirm")
|
|
p=root.add_parser("analytics",help="Get team/user analytics.");p.add_argument("--start",required=True);p.add_argument("--end",required=True);add_example(p,"fireflies analytics --start 2026-01-01 --end 2026-01-31")
|
|
m=root.add_parser("meetings",help="Manage documented meeting properties."); ms=m.add_subparsers(dest="action",required=True)
|
|
for action in ("active","rename","privacy","state","share","revoke-share","update-channel"):
|
|
p=ms.add_parser(action)
|
|
example = f"fireflies meetings {action} meeting-id" if action != "update-channel" else "fireflies meetings update-channel --channel-id ID --transcript-ids A,B,C"
|
|
add_example(p, example + (" --confirm" if action not in ("active","update-channel") else ""))
|
|
if action not in ("active","update-channel"): p.add_argument("id");add_confirm(p)
|
|
if action=="rename":p.add_argument("--title",required=True)
|
|
if action=="privacy":p.add_argument("--privacy",required=True)
|
|
if action=="state":p.add_argument("--state",required=True,choices=("pause_recording","resume_recording"))
|
|
if action=="share":p.add_argument("--emails",required=True);p.add_argument("--expiry-days",type=int)
|
|
if action=="revoke-share":p.add_argument("--email",required=True)
|
|
if action=="update-channel":p.add_argument("--channel-id",required=True);p.add_argument("--transcript-ids",required=True);add_confirm(p)
|
|
p=root.add_parser("live-action-items",help="List a meeting's live action items.");p.add_argument("meeting_id");add_example(p,"fireflies live-action-items meeting-id")
|
|
p=root.add_parser("live",help="Create action items or soundbites during a live meeting, or add a live meeting.");ls=p.add_subparsers(dest="action",required=True);p=ls.add_parser("add",help="Create a live action item.");p.add_argument("--meeting-id",required=True,help="Live meeting ID.");p.add_argument("--action-item",required=True,help="Natural-language action-item prompt.");add_confirm(p);add_example(p,"fireflies live add --meeting-id meeting-id --action-item 'Follow up' --confirm")
|
|
p=ls.add_parser("add-to",help="Add a live meeting by link.");p.add_argument("--meeting-link",required=True);p.add_argument("--title");p.add_argument("--meeting-password");p.add_argument("--duration",type=int);p.add_argument("--language");add_confirm(p);add_example(p,"fireflies live add-to --meeting-link https://meet.google.com/xxx --confirm")
|
|
p=ls.add_parser("soundbite",help="Create a soundbite clip from a live meeting.");p.add_argument("--meeting-id",required=True);p.add_argument("--prompt",required=True);add_confirm(p);add_example(p,"fireflies live soundbite --meeting-id meeting-id --prompt 'Opening statement' --confirm")
|
|
a=root.add_parser("audio",help="Upload remote audio/video.");au=a.add_subparsers(dest="action",required=True);p=au.add_parser("upload");p.add_argument("--url",required=True);p.add_argument("--title");p.add_argument("--webhook");p.add_argument("--language");p.add_argument("--save-video",action="store_true",default=None);p.add_argument("--client-reference-id");add_confirm(p);add_example(p,"fireflies audio upload --url https://example.com/call.mp3 --confirm")
|
|
p=au.add_parser("create-upload",help="Create a signed upload URL for a file (two-phase upload step 1).");p.add_argument("--title");p.add_argument("--content-type",required=True);p.add_argument("--file-size",type=int,required=True);p.add_argument("--custom-language");add_confirm(p);add_example(p,"fireflies audio create-upload --content-type audio/wav --file-size 5242880 --confirm")
|
|
p=au.add_parser("confirm-upload",help="Confirm a two-phase upload (step 3).");p.add_argument("--meeting-id",required=True);add_confirm(p);add_example(p,"fireflies audio confirm-upload --meeting-id meeting-id --confirm")
|
|
af=root.add_parser("askfred",help="Use AskFred; requires AI credits for mutations.");afs=af.add_subparsers(dest="action",required=True);p=afs.add_parser("threads");add_example(p,"fireflies askfred threads")
|
|
p=afs.add_parser("create");p.add_argument("--question",required=True);p.add_argument("--transcript-id");p.add_argument("--transcript-ids");p.add_argument("--language");add_confirm(p);add_example(p,"fireflies askfred create --question 'What changed?' --transcript-id id --confirm")
|
|
p=afs.add_parser("get");p.add_argument("thread_id");add_example(p,"fireflies askfred get thread-id")
|
|
p=afs.add_parser("continue");p.add_argument("thread_id");p.add_argument("--question",required=True);add_confirm(p);add_example(p,"fireflies askfred continue thread-id --question 'Why?' --confirm")
|
|
p=afs.add_parser("delete");p.add_argument("thread_id");add_confirm(p);add_example(p,"fireflies askfred delete thread-id --confirm")
|
|
for name, title in (("audit-events","Enterprise/admin audit event query"),("rule-executions","Enterprise/admin rule execution query")):
|
|
p=root.add_parser(name,help=title);p.add_argument("--limit",type=int);p.add_argument("--cursor");p.add_argument("--filter",required=True,help="Documented filter JSON.");add_example(p,f"fireflies {name} --filter '{{\"category\":\"...\"}}'")
|
|
w=root.add_parser("webhook",help="Local Webhooks V2 verification.");ws=w.add_subparsers(dest="action",required=True);p=ws.add_parser("verify");p.add_argument("--secret",required=True);p.add_argument("--signature",required=True);p.add_argument("--body",required=True);add_example(p,"fireflies webhook verify --secret secret --signature sha256=... --body payload.json")
|
|
s=root.add_parser("schema",help="Explicit schema tools.");ss=s.add_subparsers(dest="action",required=True);p=ss.add_parser("introspect",help="Run GraphQL introspection if deployment permits it.");add_example(p,"fireflies schema introspect")
|
|
def add_fallback_examples(current):
|
|
for action in current._actions:
|
|
if isinstance(action, argparse._SubParsersAction):
|
|
for child in action.choices.values():
|
|
if not child.epilog:
|
|
child.epilog = "Examples:\n fireflies --help"
|
|
add_fallback_examples(child)
|
|
add_fallback_examples(parser)
|
|
return parser
|
|
|
|
def main(argv=None):
|
|
options, filtered = globals_anywhere(argv or sys.argv)
|
|
parser = build_parser()
|
|
args = parser.parse_args(filtered[1:])
|
|
client = Client(options)
|
|
if args.root in ("query", "mutation"):
|
|
document = args.document
|
|
kind = operation_kind(document)
|
|
if kind != args.root: fail(f"{args.root} accepts only GraphQL {args.root} documents")
|
|
if args.root == "mutation": ensure_confirm(args, options)
|
|
if args.variables and args.variables_file: fail("use only one of --variables and --variables-file")
|
|
variables = json_value(args.variables, "--variables") if args.variables else (json_value(Path(args.variables_file).read_text(), "variables file") if args.variables_file else {})
|
|
if not isinstance(variables, dict): fail("variables must be a JSON object")
|
|
run_doc(client, options, document, variables, args.operation_name)
|
|
if args.root == "transcripts":
|
|
if args.action == "list": run_doc(client, options, DOCS["transcripts_list"], transcript_vars(args))
|
|
if args.action == "get": run_doc(client, options, DOCS["transcript"], {"id":args.id})
|
|
ensure_confirm(args, options); run_doc(client, options, DOCS["delete"], {"id":args.id})
|
|
if args.root == "users":
|
|
if args.action == "set-role":
|
|
ensure_confirm(args, options); run_doc(client, options, DOCS["user_role"], {"userId": args.user_id, "role": args.role})
|
|
else: run_doc(client, options, DOCS["users"] if args.action=="list" else (DOCS["me"] if args.action=="me" else DOCS["user"]), {} if args.action!="get" else {"id":args.id})
|
|
if args.root in ("contacts","channels","groups","apps"):
|
|
key = args.root if args.root != "channels" or args.action=="list" else "channel"; variables = {}
|
|
if args.root=="channels" and args.action=="get":variables={"id":args.id}
|
|
if args.root=="groups":variables={"mine":args.mine}
|
|
if args.root=="apps":variables={"appId":args.app_id,"transcriptId":args.transcript_id,"limit":args.limit,"skip":args.skip}
|
|
run_doc(client,options,DOCS[key],{k:v for k,v in variables.items() if v is not None})
|
|
if args.root == "bites":
|
|
if args.action=="list":
|
|
if not any((args.mine,args.transcript_id,args.my_team)): fail("bites list requires --mine, --transcript-id, or --my-team")
|
|
run_doc(client,options,DOCS["bites"],{k:v for k,v in {"mine":args.mine,"transcriptId":args.transcript_id,"myTeam":args.my_team,"limit":args.limit,"skip":args.skip}.items() if v is not None})
|
|
if args.action=="get":run_doc(client,options,DOCS["bite"],{"id":args.id})
|
|
ensure_confirm(args,options);run_doc(client,options,DOCS["bite_create"],{"transcriptId":args.transcript_id,"start":args.start,"end":args.end,"name":args.name,"type":args.type,"summary":args.summary,"privacy":args.privacy})
|
|
if args.root=="analytics":run_doc(client,options,DOCS["analytics"],{"startTime":args.start,"endTime":args.end})
|
|
if args.root=="meetings":
|
|
if args.action=="active":run_doc(client,options,DOCS["active"])
|
|
ensure_confirm(args,options)
|
|
if args.action=="rename": run_doc(client,options,DOCS["rename"],{"input":{"id":args.id,"title":args.title}})
|
|
if args.action=="privacy": run_doc(client,options,DOCS["privacy"],{"input":{"id":args.id,"privacy":args.privacy}})
|
|
if args.action=="state": run_doc(client,options,DOCS["state"],{"input":{"meeting_id":args.id,"action":args.state}})
|
|
if args.action=="share":
|
|
inp={"meeting_id":args.id,"emails":split_csv(args.emails)}
|
|
if args.expiry_days is not None: inp["expiry_days"]=args.expiry_days
|
|
run_doc(client,options,DOCS["share"],{"input":inp})
|
|
if args.action=="revoke-share": run_doc(client,options,DOCS["revoke"],{"input":{"meeting_id":args.id,"email":args.email}})
|
|
if args.action=="update-channel": run_doc(client,options,DOCS["channel_update"],{"input":{"transcript_ids":split_csv(args.transcript_ids),"channel_id":args.channel_id}})
|
|
if args.root=="live-action-items":run_doc(client,options,DOCS["live_items"],{"id":args.meeting_id})
|
|
if args.root=="live":
|
|
ensure_confirm(args,options)
|
|
if args.action=="add": run_doc(client,options,DOCS["live_add"],{"input":{"meeting_id":args.meeting_id,"prompt":args.action_item}})
|
|
if args.action=="add-to": run_doc(client,options,DOCS["live_add_to"],{k:v for k,v in {"meeting_link":args.meeting_link,"title":args.title,"meeting_password":args.meeting_password,"duration":args.duration,"language":args.language}.items() if v is not None})
|
|
if args.action=="soundbite": run_doc(client,options,DOCS["live_soundbite"],{"input":{"meeting_id":args.meeting_id,"prompt":args.prompt}})
|
|
if args.root=="audio":
|
|
ensure_confirm(args,options)
|
|
if args.action=="upload":
|
|
inp={"url":args.url,"title":args.title,"webhook":args.webhook,"custom_language":args.language,"save_video":args.save_video,"client_reference_id":args.client_reference_id};run_doc(client,options,DOCS["upload"],{"input":{k:v for k,v in inp.items() if v is not None}})
|
|
if args.action=="create-upload":
|
|
inp={"title":args.title,"content_type":args.content_type,"file_size":args.file_size,"custom_language":args.custom_language};run_doc(client,options,DOCS["upload_url"],{"input":{k:v for k,v in inp.items() if v is not None}})
|
|
if args.action=="confirm-upload": run_doc(client,options,DOCS["upload_confirm"],{"input":{"meeting_id":args.meeting_id}})
|
|
if args.root=="askfred":
|
|
if args.action=="threads":run_doc(client,options,DOCS["askfred_threads"])
|
|
if args.action=="get":run_doc(client,options,DOCS["askfred_thread"],{"id":args.thread_id})
|
|
ensure_confirm(args,options)
|
|
if args.action=="create":
|
|
inp={"query":args.question,"transcript_id":args.transcript_id,"response_language":args.language}
|
|
if args.transcript_ids:inp["filters"]={"transcript_ids":json_value(args.transcript_ids,"--transcript-ids")}
|
|
run_doc(client,options,DOCS["askfred_create"],{"input":{k:v for k,v in inp.items() if v is not None}})
|
|
if args.action=="continue":run_doc(client,options,DOCS["askfred_continue"],{"input":{"thread_id":args.thread_id,"query":args.question}})
|
|
if args.action=="delete":run_doc(client,options,DOCS["askfred_delete"],{"id":args.thread_id})
|
|
if args.root in ("audit-events","rule-executions"):run_doc(client,options,DOCS["audit" if args.root=="audit-events" else "rules"],{"limit":args.limit,"cursor":args.cursor,"filters":json_value(args.filter,"--filter")})
|
|
if args.root=="webhook":
|
|
body=sys.stdin.buffer.read() if args.body=="-" else Path(args.body).read_bytes(); prefix="sha256="; supplied=args.signature[len(prefix):] if args.signature.startswith(prefix) else ""; expected=hmac.new(args.secret.encode(),body,hashlib.sha256).hexdigest(); result={"valid":bool(supplied) and hmac.compare_digest(expected,supplied)}
|
|
try: result["event"]=json.loads(body).get("event")
|
|
except (json.JSONDecodeError,AttributeError): pass
|
|
emit(result,options)
|
|
if args.root=="schema":run_doc(client,options,DOCS["introspection"],{},"IntrospectionQuery")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|