@meeting-ai/cli 0.1.0-nightly.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +213 -0
- package/SKILL.md +63 -0
- package/dist/bin/meeting.js +85 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# meeting — CLI for Meeting.ai
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
Beautiful, agent-friendly command-line interface for Meeting.ai. Wraps the Audyno REST API.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @meeting-ai/cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or from source:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
git clone <repo> && cd meeting-cli
|
|
17
|
+
npm install && npm run build && npm link
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Show welcome banner
|
|
24
|
+
meeting
|
|
25
|
+
|
|
26
|
+
# Log in
|
|
27
|
+
meeting auth login
|
|
28
|
+
|
|
29
|
+
# List recent meetings (pretty output)
|
|
30
|
+
meeting list --limit 10
|
|
31
|
+
|
|
32
|
+
# Same thing, JSON output (for agents/scripts)
|
|
33
|
+
meeting list --limit 10 --json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Authentication
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Interactive login (email OTP) — beautiful styled prompts
|
|
40
|
+
meeting auth login
|
|
41
|
+
|
|
42
|
+
# Check who you're logged in as
|
|
43
|
+
meeting auth whoami
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
### Meetings
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# List recent meetings — pretty card layout with emoji, duration, summaries
|
|
52
|
+
meeting list --limit 10
|
|
53
|
+
|
|
54
|
+
# Read meeting notes (own or shared via URL)
|
|
55
|
+
meeting read <meeting_id>
|
|
56
|
+
|
|
57
|
+
# Search meetings
|
|
58
|
+
meeting search "quarterly review"
|
|
59
|
+
|
|
60
|
+
# Delete a meeting
|
|
61
|
+
meeting delete <meeting_id>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Human output:**
|
|
65
|
+
```
|
|
66
|
+
Recent Meetings 42 total
|
|
67
|
+
|
|
68
|
+
📋 Q1 Product Roadmap Review
|
|
69
|
+
Mar 17 · Google Meet · 45 min · 4 speakers
|
|
70
|
+
Reviewed upcoming features, timeline adjustments, and resource allocation
|
|
71
|
+
|
|
72
|
+
🎯 Weekly Engineering Standup
|
|
73
|
+
Mar 14 · Zoom · 30 min · 6 speakers
|
|
74
|
+
Sprint progress, blockers, and deployment schedule...
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**JSON output** (`--json`):
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"total_item": 42,
|
|
81
|
+
"items_per_page": 10,
|
|
82
|
+
"meeting": [
|
|
83
|
+
{ "id": "abc123", "title": "Q1 Product Roadmap Review", "status": "done", "..." : "..." }
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Bot
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# Send bot to a live meeting
|
|
92
|
+
meeting bot send --url https://meet.google.com/abc-defg-hij
|
|
93
|
+
|
|
94
|
+
# Send bot and wait for transcript
|
|
95
|
+
meeting bot send --url <url> --wait --json
|
|
96
|
+
|
|
97
|
+
# Check bot status
|
|
98
|
+
meeting bot status <meeting_id>
|
|
99
|
+
|
|
100
|
+
# Signal bot to leave
|
|
101
|
+
meeting bot leave <meeting_id>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Calendar
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# List upcoming events
|
|
108
|
+
meeting calendar events --upcoming
|
|
109
|
+
|
|
110
|
+
# List events in a date range
|
|
111
|
+
meeting calendar events --from 2024-01-01 --to 2024-01-31
|
|
112
|
+
|
|
113
|
+
# Trigger calendar sync
|
|
114
|
+
meeting calendar sync
|
|
115
|
+
|
|
116
|
+
# List connected accounts
|
|
117
|
+
meeting calendar accounts
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Upload
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# Upload a local file
|
|
124
|
+
meeting upload recording.mp4
|
|
125
|
+
|
|
126
|
+
# Upload and wait for transcript
|
|
127
|
+
meeting upload recording.mp4 --wait --json
|
|
128
|
+
|
|
129
|
+
# Submit a URL
|
|
130
|
+
meeting upload https://example.com/recording.mp4
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Transcript
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Get AI-processed notes and transcript
|
|
137
|
+
meeting transcript <meeting_id>
|
|
138
|
+
|
|
139
|
+
# Show complete raw transcript
|
|
140
|
+
meeting transcript <meeting_id> --full
|
|
141
|
+
|
|
142
|
+
# Search within transcript
|
|
143
|
+
meeting transcript <meeting_id> --search "action items"
|
|
144
|
+
|
|
145
|
+
# Filter by speaker
|
|
146
|
+
meeting transcript <meeting_id> --speaker "John"
|
|
147
|
+
|
|
148
|
+
# Filter by time range (minutes)
|
|
149
|
+
meeting transcript <meeting_id> --from-time 10 --to-time 30
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Contacts
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# List contacts — name, role, meeting count
|
|
156
|
+
meeting contacts list
|
|
157
|
+
|
|
158
|
+
# Search contacts
|
|
159
|
+
meeting contacts list --search "John"
|
|
160
|
+
|
|
161
|
+
# Get contact details
|
|
162
|
+
meeting contacts get <contact_id>
|
|
163
|
+
|
|
164
|
+
# List meetings with a contact
|
|
165
|
+
meeting contacts meetings <contact_id>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Schema Introspection
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
# List all commands and their schemas
|
|
172
|
+
meeting schema
|
|
173
|
+
|
|
174
|
+
# Get schema for a specific command
|
|
175
|
+
meeting schema meetings.list --json
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Agent-Friendly Features
|
|
179
|
+
|
|
180
|
+
Every command supports:
|
|
181
|
+
|
|
182
|
+
- **`--json`** — structured JSON output to stdout (auto-enabled when piped/non-TTY)
|
|
183
|
+
- **`--fields`** — select specific fields: `meeting list --json --fields meeting,total_item`
|
|
184
|
+
- **`--wait`** — poll async operations until complete (bot send, upload)
|
|
185
|
+
- **`--wait-timeout`** — configurable timeout in seconds (default 300)
|
|
186
|
+
|
|
187
|
+
### Exit Codes
|
|
188
|
+
|
|
189
|
+
| Code | Meaning |
|
|
190
|
+
|------|---------|
|
|
191
|
+
| 0 | Success |
|
|
192
|
+
| 1 | General error |
|
|
193
|
+
| 2 | Auth error |
|
|
194
|
+
| 3 | Not found |
|
|
195
|
+
| 4 | Validation error |
|
|
196
|
+
|
|
197
|
+
### Error Format (JSON mode)
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{"error": true, "code": "AUTH_EXPIRED", "message": "Token expired, run meeting auth login", "exit_code": 2}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Development
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
npm install
|
|
207
|
+
npm run build # type check + esbuild bundle -> dist/bin/meeting.js
|
|
208
|
+
npm run dev # tsx watch
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## License
|
|
212
|
+
|
|
213
|
+
MIT
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: meeting-cli
|
|
3
|
+
description: "Interact with Meeting.ai via the `meeting` CLI. Use when user asks about meetings, meeting notes, transcripts, login to meeting.ai, or anything related to Meeting.ai. IMPORTANT: All Meeting.ai operations (login, reading notes, uploading, etc.) are done through the `meeting` CLI command — never use a browser. Login uses `meeting auth login --email <email>` which sends an OTP to email."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Meeting.ai CLI
|
|
7
|
+
|
|
8
|
+
All Meeting.ai operations go through the `meeting` CLI. Never use a browser.
|
|
9
|
+
|
|
10
|
+
## Authentication
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
meeting auth login --email user@example.com # Sends OTP to email, then prompts for code
|
|
14
|
+
meeting auth whoami # Check who you're logged in as
|
|
15
|
+
meeting auth logout # Clear stored tokens
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Login flow: run `meeting auth login --email <email>` → OTP is sent to email → ask user for the OTP code → enter it when prompted. If OTP is wrong, it retries up to 3 times.
|
|
19
|
+
|
|
20
|
+
## Commands
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
meeting read <url_or_id> [--pin <pin>] Read meeting notes (own or shared)
|
|
24
|
+
meeting search <query> Search meetings by keyword
|
|
25
|
+
meeting list [--limit N] List recent meetings
|
|
26
|
+
meeting transcript <id> Speakers + AI summary (default)
|
|
27
|
+
meeting transcript <id> --full Full word-by-word transcript
|
|
28
|
+
meeting transcript <id> --search "query" Search within transcript
|
|
29
|
+
meeting upload <file_or_url> Upload recording for transcription
|
|
30
|
+
meeting upload <file> --wait Upload and wait for completion
|
|
31
|
+
meeting contacts list List contacts
|
|
32
|
+
meeting calendar events --upcoming View upcoming calendar events
|
|
33
|
+
meeting bot send --url <meeting_url> Send bot to a live meeting
|
|
34
|
+
meeting delete <id> Delete a meeting (irreversible)
|
|
35
|
+
meeting auth whoami Check current login
|
|
36
|
+
meeting schema List all commands and params
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Key Behaviors
|
|
40
|
+
|
|
41
|
+
- Output is human-readable by default. Use `--json` only when piping to other tools.
|
|
42
|
+
- `meeting read` works with both meeting IDs and `https://app.meeting.ai/m/...` URLs.
|
|
43
|
+
- Shared meetings require `--pin`. Own meetings don't.
|
|
44
|
+
- `meeting list` returns 5 most recent meetings with IDs — use these IDs for other commands.
|
|
45
|
+
- After `meeting upload`, the CLI prints status. If status is "created" or "processing_audio", transcription is in progress — no need to wait.
|
|
46
|
+
- `meeting transcript` without flags shows speakers + AI summary. Add `--full` for the raw transcript.
|
|
47
|
+
|
|
48
|
+
## Typical Workflows
|
|
49
|
+
|
|
50
|
+
**User asks "what was discussed in my last meeting?"**
|
|
51
|
+
1. `meeting list --limit 1` → get the meeting ID
|
|
52
|
+
2. `meeting read <id>` → read the full notes
|
|
53
|
+
|
|
54
|
+
**User shares a meeting.ai link + PIN**
|
|
55
|
+
1. `meeting read <url> --pin <pin>` → read the shared notes
|
|
56
|
+
|
|
57
|
+
**User wants to upload a recording**
|
|
58
|
+
1. `meeting upload recording.mp4` → upload and let it process
|
|
59
|
+
2. Wait a few minutes, then `meeting read <id>` to check notes
|
|
60
|
+
|
|
61
|
+
**User searches for a topic across meetings**
|
|
62
|
+
1. `meeting search "budget review"` → find matching meetings
|
|
63
|
+
2. `meeting read <id>` → read the relevant one
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var rt=Object.defineProperty;var st=(t,e)=>()=>(t&&(e=t(t=0)),e);var at=(t,e)=>{for(var n in e)rt(t,n,{get:e[n],enumerable:!0})};var Te={};at(Te,{prompt:()=>R});import{createInterface as mt}from"readline";function R(t){let e=mt({input:process.stdin,output:process.stderr});return new Promise(n=>{e.question(t,i=>{e.close(),n(i.trim())})})}var V=st(()=>{"use strict"});import{Command as nn}from"commander";import{Command as ht}from"commander";import I from"chalk";import lt from"os";import{chmodSync as Z}from"fs";import ct from"conf";var M=new ct({projectName:"meeting",schema:{tokens:{type:"object",properties:{access:{type:"string"},refresh:{type:"string"},expires_at:{type:"number"}}},api_url:{type:"string"},email:{type:"string"}}});try{Z(M.path,384)}catch{}function ue(){return M}function D(){let t=M.get("api_url")||"https://api.meeting.ai";if(!t.startsWith("https://"))throw new Error("Refusing to connect over insecure HTTP.");return t}function Oe(){return M.get("tokens")}function X(t){M.set("tokens",t);try{Z(M.path,384)}catch{}}function Q(){M.delete("tokens"),M.delete("email");try{Z(M.path,384)}catch{}}function ee(){return M.get("email")}function te(t){M.set("email",t);try{Z(M.path,384)}catch{}}function fe(t){try{let e=JSON.parse(Buffer.from(t.split(".")[1],"base64url").toString());if(typeof e.exp=="number")return e.exp*1e3}catch{}return Date.now()+900*60*1e3}async function ne(){let t=Oe();if(!t)throw new F("Not logged in. Run: meeting auth login");return gt(t)?(await dt(t)).access:t.access}function gt(t){return Date.now()>=t.expires_at-3e4}async function dt(t){let e=`${D()}/api/v1/auth/token/refresh`,n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({access:t.access,refresh:t.refresh})});if(!n.ok)throw new F("Token refresh failed. Run: meeting auth login");let i=await n.json(),r={access:i.jwt.token,refresh:i.jwt.refreshToken,expires_at:fe(i.jwt.token)};return X(r),r}async function B(t){let e=`${D()}/api/v1/auth/otp/request`,n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:t})});if(!n.ok){let r=await n.text();throw new Error(`OTP request failed: ${r}`)}return(await n.json()).challenge_id}async function oe(t,e,n){let i=`${D()}/api/v1/auth/login/email/mobile`,r=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({login:{grantType:"mail_otp",email:t,otp:e,challenge_id:n},device_info:{device_id:lt.hostname(),os_name:"cli",app_version:"0.0.1"}})});if(!r.ok){let a=await r.text();throw new Error(`Login failed: ${a}`)}let o=await r.json();if(o.mfa_required&&o.challenge)return{mfa_required:!0,challenge_id:o.challenge.challenge_id,mfa_type:o.mfa?.type||o.challenge.challenge_type,mfa_id:o.mfa?.id||"",session_id:o.session_id||""};if(o.is_new_user)throw new Error("Account not found. Please sign up at https://meeting.ai first.");if(!o.jwt?.token||!o.jwt?.refreshToken)throw new Error("Login response missing JWT tokens");let s={access:o.jwt.token,refresh:o.jwt.refreshToken,expires_at:fe(o.jwt.token)};return X(s),{mfa_required:!1,tokens:s}}async function z(t,e){let n=`${D()}/api/v1/auth/mfa`,i=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({mfa:{id:t,session_id:e}})});if(!i.ok){let r=await i.text();throw new Error(`Failed to send 2FA email: ${r}`)}}async function ie(t,e,n,i){let r=`${D()}/api/v1/auth/mfa/verify`,o={session_id:n,challenge_id:t};i==="password"?o.password=e:o.totp=e;let s=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({mfa:o})});if(!s.ok){let l=await s.text();throw new Error(`MFA verification failed: ${l}`)}let a=await s.json(),c={access:a.jwt.token,refresh:a.jwt.refreshToken,expires_at:fe(a.jwt.token)};return X(c),c}var F=class extends Error{constructor(e){super(e),this.name="AuthError"}};V();function N(t){if(!/^[a-zA-Z0-9\-]{10,30}$/.test(t)){let e=new Error("Invalid meeting ID format (expected alphanumeric+hyphen, 10-30 chars)");throw Object.assign(e,{exit_code:4,code:"VALIDATION_ERROR"}),e}}function he(t){if(!/^[a-zA-Z0-9\-]{10,30}$/.test(t)){let e=new Error("Invalid contact ID format (expected alphanumeric+hyphen, 10-30 chars)");throw Object.assign(e,{exit_code:4,code:"VALIDATION_ERROR"}),e}}function ve(t){if(!t.includes("@")){let e=new Error("Invalid email format");throw Object.assign(e,{exit_code:4,code:"VALIDATION_ERROR"}),e}}import U from"chalk";var h=U.hex("#2563EB"),yn=U.hex("#22C55E"),g=U.hex("#6B7280"),_n=U.hex("#F59E0B");function p(t){return t.json===!0||!process.stdout.isTTY}function pt(t,e){if(!e)return t;let n=e.split(",").map(i=>i.trim());return Array.isArray(t)?t.map(i=>Ce(i,n)):Ce(t,n)}function Ce(t,e){if(!t||typeof t!="object")return{};let n={};for(let i of e){let r=i.split("."),o=t;for(let s of r)if(o&&typeof o=="object"&&s in o)o=o[s];else{o=void 0;break}o!==void 0&&(n[i]=o)}return n}function w(t,e){let n=pt(t,e.fields);process.stdout.write(JSON.stringify(n,null,2)+`
|
|
3
|
+
`)}function ut(t){let e={error:!0,code:t.code||"UNKNOWN_ERROR",message:t.message,exit_code:t.exit_code||1};process.stderr.write(JSON.stringify(e)+`
|
|
4
|
+
`)}function J(t,e){if(t.length===0){console.log(g(" No results."));return}let n=e.map(r=>{let o=t.map(s=>Me(s[r.key],r.format).length);return Math.max(r.label.length,...o)}),i=e.map((r,o)=>U.bold(r.label.padEnd(n[o]))).join(" ");console.log(i),console.log(g("\u2500".repeat(i.length)));for(let r of t){let o=e.map((s,a)=>Me(r[s.key],s.format).padEnd(n[a])).join(" ");console.log(o)}}function Me(t,e){return e?e(t):t==null?"-":String(t)}function v(t){let e=Math.max(...Object.keys(t).map(n=>n.length));for(let[n,i]of Object.entries(t)){let r=U.bold(n.padEnd(e)),o=i==null?g("-"):String(i);console.log(` ${r} ${o}`)}}function b(t,e){let n=t,i=n.exit_code||1,r=n.message||"Unknown error",o=n.code||"UNKNOWN_ERROR";p(e)?ut({code:o,message:r,exit_code:i}):console.error(U.red(`Error: ${r}`)+" "+g(`(${o})`)),process.exit(i)}function E(t){if(!t||t<=0)return"-";let e=Math.floor(t/3600),n=Math.floor(t%3600/60);return e>0?`${e}h ${n}m`:`${n} min`}function P(t){return t?new Date(t).toLocaleDateString("en-US",{weekday:"short",month:"short",day:"numeric",year:"numeric",hour:"numeric",minute:"2-digit"}):"-"}function re(t){return t?new Date(t).toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"}):"-"}function se(t){return{zoom:"\u{1F4F9}",gmeet:"\u{1F7E2}",teams:"\u{1F512}",webex:"\u{1F310}",upload:"\u{1F4E4}",livestream:"\u{1F30D}",record:"\u{1F399}\uFE0F"}[t?.toLowerCase()]||"\u{1F4CB}"}function W(t){return{"google meet":"gmeet",googlemeet:"gmeet",google:"gmeet",meet:"gmeet",gmeet:"gmeet",zoom:"zoom",teams:"teams","microsoft teams":"teams",msteams:"teams",webex:"webex",upload:"upload",livestream:"livestream",live:"livestream",record:"record",recording:"record"}[t.toLowerCase()]||t.toLowerCase()}function ae(t){return{zoom:"Zoom",gmeet:"Google Meet",teams:"Teams",webex:"Webex",upload:"Upload",livestream:"Livestream",record:"Recording"}[t?.toLowerCase()]||t||"-"}function q(t){let e=typeof t=="string"?parseInt(t,10):t;return isNaN(e)||e<5?5:e}import{Command as ft}from"commander";import A from"chalk";var ce=new Map;function k(t,e){ce.set(t,e)}function Re(){let t=new ft("schema").description("Introspect command schemas");return t.argument("[command]","Command name (e.g., meetings.list)").option("--json","Output as JSON").option("--fields <fields>","Select output fields").action((e,n)=>{try{if(!e){let r=Object.fromEntries(ce);if(p(n))w(r,n);else{console.log(A.bold(`Available commands:
|
|
5
|
+
`));for(let[o,s]of ce)console.log(` ${A.bold(o)} \u2014 ${s.description}`)}return}let i=ce.get(e);if(!i)throw Object.assign(new Error(`Unknown command: ${e}. Run 'meeting schema' to list all.`),{exit_code:3,code:"NOT_FOUND"});if(p(n))w({command:e,...i},n);else{if(console.log(A.bold(e)+" \u2014 "+i.description+`
|
|
6
|
+
`),Object.keys(i.params).length>0){console.log(A.bold("Parameters:"));for(let[r,o]of Object.entries(i.params)){let s=o.required?A.red("*"):"";console.log(` ${A.cyan(r)}${s} (${o.type}) \u2014 ${o.description}`)}console.log()}console.log(A.bold("Output schema:")),v(i.output)}}catch(i){b(i,n)}}),t}var le=I.hex("#22C55E");function Ee(){let t=new ht("auth").description("Authentication commands");return t.command("login").description("Log in with email OTP").option("--json","Output as JSON").option("--email <email>","Email address (skip prompt)").action(async e=>{try{p(e)||(console.log(),console.log(` ${h.bold("\u{1F511} Log in to meeting.ai")}`),console.log());let n=e.email||await R(` ${I.bold("Email:")} `);if(!n)throw Object.assign(new Error("Email is required"),{exit_code:4,code:"VALIDATION_ERROR"});ve(n);let i;if(p(e))i=await B(n);else{let a=(await import("ora")).default,c=a({text:"Sending OTP code...",color:"blue"}).start();i=await B(n),c.stop(),console.log(` ${le("\u2713")} OTP sent! Check your email`),console.log()}let r=3,o;for(let a=1;a<=r;a++){let c=!p(e)&&a>1?g(` (attempt ${a}/${r})`):"",l=await R(` ${I.bold("OTP Code:")}${c} `);if(!l){if(a<r){p(e)||console.log(` ${I.yellow("\u26A0")} OTP cannot be empty, try again`);continue}throw Object.assign(new Error("OTP code is required (max attempts exceeded)"),{exit_code:4,code:"VALIDATION_ERROR"})}try{o=await oe(n,l,i);break}catch(d){if(a<r){p(e)||console.log(` ${I.yellow("\u26A0")} Invalid OTP, try again`);continue}throw d}}let s;if(o.mfa_required){let a=o.mfa_type;if(a==="webauthn")throw Object.assign(new Error("WebAuthn/passkey 2FA is not supported in CLI. Please log in via the web app at https://meeting.ai"),{exit_code:1,code:"MFA_NOT_SUPPORTED"});let c;if(a==="totp")c=` ${I.bold("Authenticator code:")} `;else if(a==="password")c=` ${I.bold("Password:")} `;else{if(p(e))await z(o.mfa_id,o.session_id);else{let d=(await import("ora")).default,m=d({text:"Sending 2FA code to email...",color:"blue"}).start();await z(o.mfa_id,o.session_id),m.stop(),console.log(` ${le("\u2713")} 2FA code sent to your email`),console.log()}c=` ${I.bold("2FA code (check email):")} `}let l=await R(c);if(!l)throw Object.assign(new Error("2FA code is required"),{exit_code:4,code:"VALIDATION_ERROR"});s=await ie(o.challenge_id,l,o.session_id,a)}else s=o.tokens;te(n),p(e)?w({success:!0,email:n,expires_at:s.expires_at},e):(console.log(` ${le("\u2713")} Logged in as ${I.bold(n)}`),console.log(),console.log(g(" Your token is stored at ~/.config/meeting/config.json")),console.log())}catch(n){b(n,e)}}),t.command("whoami").description("Show current user info").option("--json","Output as JSON").option("--fields <fields>","Select output fields").action(async e=>{try{let n=await ne(),i=JSON.parse(Buffer.from(n.split(".")[1],"base64url").toString()),r={id:i.id,name:i.name,email:i.email||ee()||"unknown",session_id:i.sessionId,token_expires:new Date(i.exp*1e3).toISOString()};p(e)?w(r,e):(console.log(),console.log(` ${h.bold("\u{1F464} Current User")}`),console.log(),v({Id:r.id,Name:r.name,Email:r.email,"Token Expires":r.token_expires}),console.log())}catch(n){b(n,e)}}),t.command("logout").description("Clear stored tokens").option("--json","Output as JSON").action(e=>{Q(),p(e)?w({success:!0,message:"Logged out"},e):console.log(` ${le("\u2713")} Logged out successfully.`)}),k("auth.login",{description:"Log in with email OTP",params:{email:{type:"string",description:"Email address",required:!1}},output:{type:"object",properties:{success:{type:"boolean"},email:{type:"string"},expires_at:{type:"number"}}}}),k("auth.whoami",{description:"Show current user info (decoded from JWT)",params:{},output:{type:"object",properties:{id:{type:"string"},name:{type:"string"},email:{type:"string"},session_id:{type:"string"},token_expires:{type:"string",description:"ISO 8601 timestamp"}}}}),k("auth.logout",{description:"Clear stored tokens",params:{},output:{type:"object",properties:{success:{type:"boolean"},message:{type:"string"}}}}),t}import{Command as Ie}from"commander";import L from"chalk";var yt="cli-0.1.0-nightly.1";async function _(t,e={}){let{method:n="GET",body:i,query:r,headers:o={},auth:s=!0,multipart:a}=e,c=D(),l=new URL(t,c);if(r){for(let[f,y]of Object.entries(r))if(y!==void 0)if(Array.isArray(y))for(let $ of y)l.searchParams.append(f,$);else l.searchParams.set(f,String(y))}let d={"x-client-app-version":yt,...o};if(s)try{let f=await ne();d.Authorization=`Bearer ${f}`}catch(f){throw f instanceof F?Object.assign(f,{exit_code:2}):f}let m;a?m=a:i&&(d["Content-Type"]="application/json",m=JSON.stringify(i));let u=await fetch(l.toString(),{method:n,headers:d,body:m});if(!u.ok){let f=u.status,y;try{let S=await u.json(),T=C=>{if(typeof C=="string")return C;if(C&&typeof C=="object"&&"message"in C){let j=C.message;if(typeof j=="string")return j}};y=T(S.message)||T(S.error)||(S.message?JSON.stringify(S.message):void 0)||u.statusText}catch{y=u.statusText}let $=f===401||f===403?2:f===404?3:f===422?4:1,x=f===401?"AUTH_EXPIRED":f===403?"FORBIDDEN":f===404?"NOT_FOUND":f===422?"VALIDATION_ERROR":"API_ERROR",O=new Error(y);throw O.error=!0,O.code=x,O.message=y,O.exit_code=$,O}if(u.status!==204)return u.json()}V();function Y(t){return t.is_title_edited&&t.title?t.title:t.autogenerated_title||t.title||"(untitled)"}function ye(t){return t.autogenerated_title_emoji?t.autogenerated_title_emoji:se(t.integration)}function _t(t){return t.meeting_participants?.length?t.meeting_participants.length:t.speaker_count||0}function _e(t){return(t.meeting_participants||t.participants||[]).map(n=>n.name).filter(n=>!!n)}function we(t){return t.replace(/<em>(.*?)<\/em>/g,(e,n)=>L.bold.white(n))}function wt(t,e=200){let n=t.split(`
|
|
7
|
+
|
|
8
|
+
`)[0].trim();if(n.length<=e)return n;let i=n.slice(0,e),r=i.lastIndexOf(". ");if(r>e*.5)return i.slice(0,r+1);let o=i.lastIndexOf(" ");return o>e*.5?i.slice(0,o)+"...":i+"..."}function bt(t,e=4){let i=(t.meeting_participants||[]).filter(s=>s.name);return{names:[...i].sort((s,a)=>(a.speaking_seconds||0)-(s.speaking_seconds||0)).slice(0,e).map(s=>s.name).filter(s=>!!s),more:Math.max(0,i.length-e)}}function ge(t,e){let n=ye(t),i=Y(t),r=ae(t.integration),o=E(t.duration_in_seconds),s=P(t.started_at||t.created_at),a=_t(t),c=a?`${a} speaker${a>1?"s":""}`:"",l=[s,r,o,c].filter(Boolean).join(g(" \xB7 "));if(console.log(` ${n} ${L.bold(i)}`),console.log(` ${g(t.id)}`),t.status&&t.status!=="finished"){let y=L.yellow(`[${t.status}]`);console.log(` ${l} ${y}`)}else console.log(` ${l}`);t.role==="read"&&console.log(` ${L.cyan("[shared]")}`);let{names:d,more:m}=bt(t);if(d.length>0){let y=d.join(", ")+(m>0?` ${g(`+${m} more`)}`:"");console.log(` ${g(y)}`)}let u=t.meeting_section,f=Array.isArray(u)?u[0]?.text:u?.text;if(f){let y=wt(f);console.log(` ${g(L.italic(y))}`)}e&&e(),console.log()}function Le(){let t=new Ie("list").description("Show recent meetings").option("--json","Output as JSON").option("--fields <fields>","Select output fields").option("--from <date>",'Filter from date (e.g. 2026-03-01, "last week")').option("--to <date>","Filter to date (e.g. 2026-03-18)").option("--after <date>","Alias for --from").option("--before <date>","Alias for --to").option("--role <role>","Filter by role (owner/read/all)").option("--integration <type>","Filter by integration (zoom/gmeet/teams/livestream/upload)").option("--speaker <name>","Filter by participant name").option("--sort <order>","Sort order (newest/oldest)","newest").option("--status <status>","Filter by status (finished/recording/processing)").option("--limit <n>","Items per page","10").option("--offset <n>","Cursor offset").action(async e=>{try{let n=e.from||e.after,i=e.to||e.before,r=q(e.limit),o;if(!p(e)){let a=(await import("ora")).default;o=a({text:"Loading meetings...",color:"blue"}).start()}let s=await _("/api/v1/meetings",{query:{start_date:n,end_date:i,role:e.role,integration:e.integration?W(e.integration):void 0,status:e.status,sort:e.sort==="oldest"?"asc":void 0,items_per_page:String(r),cursor:e.offset}});if(o?.stop(),p(e))w(s,e);else{let a=s.meeting||[];if(e.speaker){let c=e.speaker.toLowerCase();a=a.filter(l=>_e(l).some(m=>m.toLowerCase().includes(c)))}console.log(),console.log(` ${h.bold("Recent Meetings")}${g(` ${s.total_item.toLocaleString()} total`)}`),console.log();for(let c of a)ge(c);s.next_cursor&&(console.log(g(` More results available. Use --offset ${s.next_cursor}`)),console.log()),console.log(g(" Filters: --from, --to, --integration, --speaker, --role, --sort, --status")),console.log(g(` Search: ${h("meeting search <query>")}`)),console.log()}}catch(n){b(n,e)}});return k("list",{description:"List recent meetings",params:{from:{type:"string",description:"Filter from date (ISO 8601)",required:!1},to:{type:"string",description:"Filter to date (ISO 8601)",required:!1},role:{type:"string",description:"Filter by role (owner/read/all)",required:!1},integration:{type:"string",description:"Filter by integration (zoom/gmeet/teams/livestream/upload)",required:!1},speaker:{type:"string",description:"Filter by participant name (client-side)",required:!1},sort:{type:"string",description:"Sort order (newest/oldest)",required:!1},status:{type:"string",description:"Filter by status (finished/recording/processing)",required:!1},limit:{type:"number",description:"Items per page (default 10, min 5)",required:!1},offset:{type:"number",description:"Cursor offset",required:!1}},output:{type:"object",properties:{total_item:{type:"number"},items_per_page:{type:"number"},next_cursor:{type:"number"},prev_cursor:{type:"number"},meeting:{type:"array",items:{type:"object"}}}}}),t}function je(){let t=new Ie("delete").description("Delete a meeting").argument("[id]","Meeting ID").option("--json","Output as JSON").option("--yes","Skip confirmation prompt").action(async(e,n)=>{if(!e){let i=(await import("chalk")).default,r=i.hex("#2563EB"),o=i.hex("#6B7280");console.error(),console.error(i.red(" \u2717 Missing meeting ID.")),console.error(),console.error(` ${r("Usage:")} meeting delete ${o("<meeting_id>")}`),console.error(),console.error(` ${o("Example:")}`),console.error(` ${r("meeting delete")} 01abc...xyz`),console.error(),console.error(` ${o("\u{1F4A1} Run")} ${r("meeting list")} ${o("to see your meetings and their IDs.")}`),console.error(` ${o("\u26A0\uFE0F This action is permanent and cannot be undone.")}`),console.error(),process.exit(1)}try{if(N(e),!p(n)){let i=e;try{let r=await _(`/api/v1/meeting/${e}`);i=Y(r)}catch{}if(n.yes)console.log(L.yellow(" \u26A0\uFE0F Skipped confirmation via --yes flag"));else if(console.log(),console.log(L.yellow(` \u26A0\uFE0F Delete meeting "${i}" (${e})?`)),console.log(),console.log(" This action cannot be undone."),console.log(g(" If you are an AI agent, please ask your human to confirm this deletion.")),console.log(),await R(` Type ${L.bold("DELETE")} to confirm: `)!=="DELETE"){console.log(g(" Cancelled."));return}}await _(`/api/v1/meeting/${e}`,{method:"DELETE"}),p(n)?w({success:!0,id:e},n):console.log(L.hex("#22C55E")(` \u2713 Meeting ${e} deleted.`))}catch(i){b(i,n)}});return k("delete",{description:"Delete a meeting (soft delete)",params:{id:{type:"string",description:"Meeting ID",required:!0},yes:{type:"boolean",description:"Skip confirmation",required:!1}},output:{type:"object",properties:{success:{type:"boolean"},id:{type:"string"}}}}),t}import{Command as kt}from"commander";import K from"chalk";async function De(t){let e=[],n,r=0;for(;r<10;){let o={items_per_page:"100",cursor:n},s=await _(`/api/v1/meeting/${t}/transcript`,{query:o}),a=s.transcribe||[];if(e.push(...a),!s.next_cursor)break;n=String(s.next_cursor),r++}return e}function $t(t){let e=Math.floor(t/3600),n=Math.floor(t%3600/60),i=Math.floor(t%60);return e>0?`${e}:${String(n).padStart(2,"0")}:${String(i).padStart(2,"0")}`:`${n}:${String(i).padStart(2,"0")}`}function St(t,e){let n=new RegExp(`(${e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")})`,"gi");return t.replace(n,(i,r)=>K.bold.yellow(r))}function Pe(){let t=new kt("search").description("Search meetings (notes & transcripts)").argument("[query]","Search keyword").option("--json","Output as JSON").option("--fields <fields>","Select output fields").option("--transcript","Search within transcript text").option("--limit <n>","Items per page","10").option("--offset <n>","Cursor offset").option("--from <date>","Start from date").option("--to <date>","End before date").option("--integration <type>","Filter by integration (zoom/gmeet/teams/livestream/upload)").option("--role <role>","Filter by role (owner/read)").option("--speaker <name>","Filter by participant name").option("--no-highlight","Hide search highlights").action(async(e,n)=>{e||(console.error(),console.error(K.red(" \u2717 Missing search query.")),console.error(),console.error(` ${h("Usage:")} meeting search ${g("<query>")}`),console.error(),console.error(` ${g("Examples:")}`),console.error(` ${h("meeting search")} "quarterly review"`),console.error(` ${h("meeting search")} marketing`),console.error(),console.error(` ${g("\u{1F4A1} Search looks through meeting titles, notes, and transcripts.")}`),console.error(),process.exit(1));try{let i=q(n.limit),r;if(!p(n)){let a=(await import("ora")).default;r=a({text:`Searching "${e}"...`,color:"blue"}).start()}let o=await _("/api/v1/meetings/search",{query:{keyword:e,items_per_page:String(n.transcript?Math.min(i,5):i),cursor:n.offset,start_from_date:n.from,end_before_date:n.to,integration:n.integration?W(n.integration):void 0,role:n.role}});r?.stop();let s=o.meeting||[];if(n.speaker){let a=n.speaker.toLowerCase();s=s.filter(c=>_e(c).some(d=>d.toLowerCase().includes(a)))}if(n.transcript&&!p(n)){let a=s.slice(0,5);if(a.length===0){console.log(),console.log(g(" No meetings found.")),console.log();return}if(!p(n)){let d=(await import("ora")).default;r=d({text:`Searching transcripts (${a.length} meetings)...`,color:"blue"}).start()}let c=await Promise.allSettled(a.map(d=>De(d.id).then(m=>({meeting:d,transcript:m}))));r?.stop(),console.log(),console.log(` ${h.bold("\u{1F50D} Transcript Search")}${g(` "${e}"`)}`),console.log();let l=!1;for(let d of c){if(d.status!=="fulfilled")continue;let{meeting:m,transcript:u}=d.value;if(u.length===0)continue;let f=e.toLowerCase(),y=[];for(let S=0;S<u.length;S++)u[S].text.toLowerCase().includes(f)&&y.push(S);if(y.length===0)continue;l=!0;let $=ye(m),x=Y(m),O=P(m.started_at||m.created_at);console.log(` ${$} ${K.bold(x)} ${g(`(${O})`)}`);for(let S of y.slice(0,3)){let T=u[S],C=$t(T.start),j=St(T.text.length>200?T.text.slice(0,200)+"...":T.text,e);console.log(` ${g('"')}${j}${g('"')}`),console.log(` ${g(`\u2014 ${T.speaker}, ${C}`)}`)}y.length>3&&console.log(` ${g(`+${y.length-3} more matches`)}`),console.log()}l||(console.log(g(" No transcript matches found.")),console.log());return}if(n.transcript&&p(n)){let a=s.slice(0,5),c=[],l=await Promise.allSettled(a.map(d=>De(d.id).then(m=>({meeting:d,transcript:m}))));for(let d of l){if(d.status!=="fulfilled")continue;let{meeting:m,transcript:u}=d.value,f=e.toLowerCase(),y=u.filter($=>$.text.toLowerCase().includes(f)).map($=>({speaker:$.speaker,text:$.text,start:$.start}));y.length>0&&c.push({meeting_id:m.id,title:Y(m),matches:y})}w({query:e,results:c,total_meetings_searched:a.length},n);return}if(p(n))w(o,n);else{console.log(),console.log(` ${h.bold("Search Results")}${g(` ${o.total_item} total`)}`),console.log();for(let a of s)ge(a,()=>{if(n.highlight!==!1){let c=a.highlights||[];for(let l of c.slice(0,3)){let d=l.content||l.text||"";if(!d)continue;let m=d.replace(/\[\d+\|\d+\.\d+-\d+\.\d+\]\s*/g,""),u=we(m.length>200?m.slice(0,200)+"...":m),y={transcription:"\u{1F4DD} Transcript",notes:"\u{1F4CB} Notes",template_section:"\u{1F4CB} Notes",title:"\u{1F4CC} Title"}[l.field||""]||l.field||"Unknown",$=l.reference?.timestamp_start?` ${g(`at ${Math.floor(l.reference.timestamp_start/60)}:${String(Math.floor(l.reference.timestamp_start%60)).padStart(2,"0")}`)}`:"";console.log(` ${K.cyan(y)}${$}`),console.log(` ${g("\u2192")} ${u}`)}}});if(o.next_cursor){if(console.log(g(` Showing ${s.length} of ${o.total_item} results`)),console.log(),process.stdout.isTTY){let{prompt:a}=await Promise.resolve().then(()=>(V(),Te)),c=await a(` ${h("\u2192")} Show more results? ${g("(y/n)")} `);if(c?.toLowerCase()==="y"||c?.toLowerCase()==="yes"){let l=await _("/api/v1/meetings/search",{query:{keyword:e,items_per_page:String(i),cursor:String(o.next_cursor),start_from_date:n.from,end_before_date:n.to,integration:n.integration?W(n.integration):void 0,role:n.role}}),d=l.meeting||[];console.log();for(let m of d)ge(m,()=>{let u=m.highlights||[];for(let f of u.slice(0,3)){let y=f.content||f.text||"";if(!y)continue;let $=y.replace(/\[\d+\|\d+\.\d+-\d+\.\d+\]\s*/g,""),x=we($.length>200?$.slice(0,200)+"...":$),S={transcription:"\u{1F4DD} Transcript",notes:"\u{1F4CB} Notes",template_section:"\u{1F4CB} Notes",title:"\u{1F4CC} Title"}[f.field||""]||f.field||"Unknown";console.log(` ${K.cyan(S)}`),console.log(` ${g("\u2192")} ${x}`)}});l.next_cursor&&console.log(g(` More available. Use: meeting search "${e}" --offset ${l.next_cursor}`))}}else console.log(g(` Next page: meeting search "${e}" --offset ${o.next_cursor}`));console.log()}console.log(g(` Tip: Use ${h("--transcript")} to search within transcript text`)),console.log()}}catch(i){b(i,n)}});return k("search",{description:"Search meetings by keyword (notes & transcripts)",params:{query:{type:"string",description:"Search keyword",required:!0},transcript:{type:"boolean",description:"Search within transcript text",required:!1},limit:{type:"number",description:"Items per page",required:!1},offset:{type:"number",description:"Cursor offset",required:!1},from:{type:"string",description:"Start from date",required:!1},to:{type:"string",description:"End before date",required:!1},integration:{type:"string",description:"Filter by integration",required:!1},role:{type:"string",description:"Filter by role",required:!1},speaker:{type:"string",description:"Filter by participant name",required:!1}},output:{type:"object",properties:{total_item:{type:"number"},meeting:{type:"array",items:{type:"object"}}}}}),t}import{Command as Ot}from"commander";import Tt from"chalk";async function de(t,e,n={}){let{interval:i=3e3,timeout:r=3e5}=n,o=Date.now()+r;for(;;){let s=await t();if(e(s))return s;if(Date.now()+i>o)throw new Error(`Polling timed out after ${r/1e3}s`);await xt(i)}}function xt(t){return new Promise(e=>setTimeout(e,t))}var Ne=Tt.hex("#22C55E");function Ue(){let t=new Ot("bot").description("Meeting bot commands");return t.command("send").description("Send bot to a meeting URL").requiredOption("--url <url>","Meeting URL to join").option("--json","Output as JSON").option("--fields <fields>","Select output fields").option("--wait","Wait until meeting is complete").option("--wait-timeout <seconds>","Wait timeout in seconds","300").action(async e=>{try{let n;if(!p(e)){let o=(await import("ora")).default;n=o({text:"Sending bot to meeting...",color:"blue"}).start()}let i=await _("/api/v1/meeting",{method:"POST",query:{device:"cli"},body:{meeting:{url:e.url,integration:vt(e.url),started_at:new Date().toISOString(),language:"auto",participants:[]}}}),r=i.meeting||i;if(await _(`/api/v1/meeting/${r.id}/record-meeting`,{method:"POST",body:{is_user_rejoin:!1}}),n?.stop(),e.wait){if(!p(e)){let s=(await import("ora")).default;n=s({text:"Waiting for meeting to complete...",color:"blue"}).start()}let o=await de(()=>_(`/api/v1/meeting/${r.id}`),s=>s.status==="done"||s.status==="failed",{timeout:parseInt(e.waitTimeout)*1e3});n?.stop(),p(e)?w(o,e):(console.log(),v({ID:o.id,Title:o.title,Status:o.status,URL:o.url}),console.log())}else p(e)?w(r,e):(console.log(` ${Ne("\u2713")} Bot sent successfully.`),console.log(),v({ID:r.id,Title:r.title||"(pending)",Status:r.status}),console.log())}catch(n){b(n,e)}}),t.command("status <meeting_id>").description("Check bot/meeting status").option("--json","Output as JSON").option("--fields <fields>","Select output fields").action(async(e,n)=>{try{N(e);let i;if(!p(n)){let o=(await import("ora")).default;i=o({text:"Checking status...",color:"blue"}).start()}let r=await _(`/api/v1/meeting/${e}/progress`);i?.stop(),p(n)?w(r,n):(console.log(),console.log(` ${h.bold("\u{1F916} Bot Status")}`),console.log(),v({ID:r.id||e,Title:r.title,Status:r.status,Integration:r.integration}),console.log())}catch(i){b(i,n)}}),t.command("leave <meeting_id>").description("Signal bot to leave meeting").option("--json","Output as JSON").action(async(e,n)=>{try{N(e),await _("/api/v1/recall/leave",{method:"POST",body:{id:e}}),p(n)?w({success:!0,meeting_id:e},n):console.log(` ${Ne("\u2713")} Bot leaving meeting ${g(e)}.`)}catch(i){b(i,n)}}),k("bot.send",{description:"Send bot to a meeting URL",params:{url:{type:"string",description:"Meeting URL to join",required:!0},wait:{type:"boolean",description:"Wait until complete",required:!1},"wait-timeout":{type:"number",description:"Wait timeout in seconds (default 300)",required:!1}},output:{type:"object",properties:{id:{type:"string"},title:{type:"string"},status:{type:"string"},url:{type:"string"}}}}),k("bot.status",{description:"Check bot/meeting status",params:{meeting_id:{type:"string",description:"Meeting ID",required:!0}},output:{type:"object",properties:{id:{type:"string"},title:{type:"string"},status:{type:"string"},integration:{type:"string"},url:{type:"string"}}}}),k("bot.leave",{description:"Signal bot to leave meeting",params:{meeting_id:{type:"string",description:"Meeting ID",required:!0}},output:{type:"object",properties:{success:{type:"boolean"},meeting_id:{type:"string"}}}}),t}function vt(t){return t.includes("zoom.us")?"zoom":t.includes("meet.google.com")?"gmeet":t.includes("teams.microsoft.com")?"teams":t.includes("webex.com")?"webex":"gmeet"}import{Command as Ct}from"commander";import Mt from"chalk";var Rt=Mt.hex("#22C55E");function qe(){let t=new Ct("calendar").description("Calendar commands");return t.command("events").description("List calendar events").option("--json","Output as JSON").option("--fields <fields>","Select output fields").option("--upcoming","Show upcoming events from today").option("--from <date>","Start date (YYYY-MM-DD)").option("--to <date>","End date (YYYY-MM-DD)").option("--limit <n>","Items per page","50").option("--timezone <tz>","Timezone (default: local)",Intl.DateTimeFormat().resolvedOptions().timeZone).action(async e=>{try{let n;if(!p(e)){let o=(await import("ora")).default;n=o({text:"Loading events...",color:"blue"}).start()}let i={items_per_page:e.limit,timezone:e.timezone};e.upcoming?i.date=new Date().toISOString().split("T")[0]:e.from&&e.to?(i.start_date=e.from,i.end_date=e.to):e.from&&(i.date=e.from);let r=await _("/api/v1/calendar/calendar_events",{query:i});if(n?.stop(),p(e))w(r,e);else{let o=r.calendar_events||[];console.log(),console.log(` ${h.bold("\u{1F4C5} Calendar Events")}${g(` ${o.length} shown`)}`),console.log(),J(o,[{key:"title",label:"Title"},{key:"status",label:"Status"},{key:"start_time",label:"Start",format:s=>s?new Date(s).toLocaleString():"-"},{key:"end_time",label:"End",format:s=>s?new Date(s).toLocaleString():"-"}]),console.log()}}catch(n){b(n,e)}}),t.command("sync").description("Trigger calendar sync").option("--json","Output as JSON").option("--fields <fields>","Select output fields").option("--from <date>","Start date").option("--to <date>","End date").action(async e=>{try{let n;if(!p(e)){let r=(await import("ora")).default;n=r({text:"Syncing calendar...",color:"blue"}).start()}let i=await _("/api/v1/calendar/sync",{method:"POST",query:{start_date:e.from,end_date:e.to}});n?.stop(),p(e)?w(i,e):console.log(` ${Rt("\u2713")} Sync status: ${i.sync_status}`)}catch(n){b(n,e)}}),t.command("accounts").description("List calendar accounts").option("--json","Output as JSON").option("--fields <fields>","Select output fields").action(async e=>{try{let n;if(!p(e)){let r=(await import("ora")).default;n=r({text:"Loading accounts...",color:"blue"}).start()}let i=await _("/api/v1/calendar/accounts");if(n?.stop(),p(e))w(i,e);else{let r=i.calendar_accounts||[];console.log(),console.log(` ${h.bold("\u{1F517} Calendar Accounts")}${g(` ${r.length} connected`)}`),console.log(),J(r,[{key:"email",label:"Email"},{key:"integration_id",label:"Integration"},{key:"status",label:"Status"}]),console.log()}}catch(n){b(n,e)}}),k("calendar.events",{description:"List calendar events",params:{upcoming:{type:"boolean",description:"Show upcoming events from today",required:!1},from:{type:"string",description:"Start date (YYYY-MM-DD)",required:!1},to:{type:"string",description:"End date (YYYY-MM-DD)",required:!1},limit:{type:"number",description:"Items per page (default 50)",required:!1},timezone:{type:"string",description:"Timezone (default: local)",required:!1}},output:{type:"object",properties:{calendar_events:{type:"array",items:{type:"object"}},next_cursor:{type:"string"}}}}),k("calendar.sync",{description:"Trigger calendar sync",params:{from:{type:"string",description:"Start date (YYYY-MM-DD)",required:!1},to:{type:"string",description:"End date (YYYY-MM-DD)",required:!1}},output:{type:"object",properties:{sync_status:{type:"string"},synced_accounts:{type:"array"}}}}),k("calendar.accounts",{description:"List connected calendar accounts",params:{},output:{type:"object",properties:{user_id:{type:"string"},calendar_accounts:{type:"array",items:{type:"object"}}}}}),t}import{Command as Et}from"commander";import H from"chalk";import{open as It,stat as Lt}from"fs/promises";import{basename as Ae,extname as jt}from"path";var be=H.hex("#22C55E"),me=5*1024*1024;function Dt(t){return t.startsWith("http://")||t.startsWith("https://")}function Pt(t){let e=jt(t).toLowerCase();return{".mp3":"audio/mpeg",".wav":"audio/wav",".m4a":"audio/mp4",".aac":"audio/aac",".ogg":"audio/ogg",".flac":"audio/flac",".wma":"audio/x-ms-wma",".mp4":"video/mp4",".webm":"video/webm",".mkv":"video/x-matroska",".avi":"video/x-msvideo",".mov":"video/quicktime",".wmv":"video/x-ms-wmv",".flv":"video/x-flv"}[e]||"audio/mpeg"}function Nt(t){return t.startsWith("video/")?"video":"audio"}function ke(t){return t<1024?`${t}B`:t<1024*1024?`${(t/1024).toFixed(0)}KB`:t<1024*1024*1024?`${(t/(1024*1024)).toFixed(1)}MB`:`${(t/(1024*1024*1024)).toFixed(1)}GB`}function Ut(t,e,n){let i=Math.round(e/n*100),r=20,o=Math.round(e/n*r),s="\u2588".repeat(o)+"\u2591".repeat(r-o),a=` \u2B06 Uploading ${t} ${s} ${i}% (${ke(e)} / ${ke(n)})`;process.stdout.write(`\r${a}`)}function Fe(){let t=new Et("upload").description("Upload recording for transcription").argument("[source]","Local file path or URL").option("--json","Output as JSON").option("--fields <fields>","Select output fields").option("--title <title>","Meeting title").option("--language <lang>","Language code (default: auto-detect)","auto").option("--wait","Wait until transcription is complete").option("--wait-timeout <seconds>","Wait timeout in seconds","300").action(async(e,n)=>{e||(console.error(),console.error(H.red(" \u2717 Missing file path or URL.")),console.error(),console.error(` ${H.hex("#2563EB")("Usage:")} meeting upload ${g("<file_or_url>")}`),console.error(),console.error(` ${g("Examples:")}`),console.error(` ${H.hex("#2563EB")("meeting upload")} recording.mp4`),console.error(` ${H.hex("#2563EB")("meeting upload")} https://example.com/audio.mp3`),console.error(),console.error(` ${g("\u{1F4A1} Supported formats: mp3, mp4, m4a, wav, webm, ogg, mov, avi.")}`),console.error(` ${g("\u{1F4A1} Language is auto-detected. Override with --language en.")}`),console.error(),process.exit(1));try{let i;if(Dt(e)){let r;if(!p(n)){let s=(await import("ora")).default;r=s({text:"Submitting URL for transcription...",color:"blue"}).start()}let o=await _("/api/v1/meeting",{method:"POST",query:{device:"cli"},body:{meeting:{url:e,title:n.title,integration:"upload",language:n.language,started_at:new Date().toISOString(),participants:["CLI User"]}}});i="meeting"in o&&o.meeting?o.meeting:o,r?.stop(),!p(n)&&!n.wait&&(console.log(` ${be("\u2713")} URL submitted for transcription.`),console.log())}else{let o=(await Lt(e)).size,s=Ae(e),a=Pt(e),c=Nt(a),l=Math.ceil(o/me);p(n)||(console.log(),console.log(` ${g("File:")} ${s} ${g(ke(o))} ${g(a)}`),console.log());let d;if(!p(n)){let x=(await import("ora")).default;d=x({text:"Creating meeting...",color:"blue"}).start()}let m=await _("/api/v1/meeting",{method:"POST",query:{device:"cli"},body:{meeting:{participants:["CLI User"],started_at:new Date().toISOString(),language:n.language,integration:"upload",...n.title?{title:n.title}:{}}}});i="meeting"in m&&m.meeting?m.meeting:m,d?.stop();let u=i.id;if(!p(n)){let x=(await import("ora")).default;d=x({text:"Initializing upload...",color:"blue"}).start()}let f=await _(`/api/v1/meeting/${u}/media2`,{method:"POST",body:{media:{file_name:s,file_size:o,mime_type:a,total_part:l,type:c}}});d?.stop();let y=f.meeting_file.id,$=await It(e,"r");try{let x=Buffer.alloc(me),O=0;for(let S=0;S<l;S++){let T=S*me,C=Math.min(me,o-T),{bytesRead:j}=await $.read(x,0,C,T),ot=x.subarray(0,j),it=T+j-1,xe=new FormData;xe.append("upload",new Blob([new Uint8Array(ot)])),await _(`/api/v1/meeting/${u}/media/${y}`,{method:"POST",multipart:xe,headers:{"First-Byte":String(T),"Last-Byte":String(it),"Total-Byte":String(o),"Part-Number":String(S)}}),O+=j,p(n)||Ut(s,O,o)}}finally{await $.close()}await _(`/api/v1/meeting/${u}`,{method:"PATCH",body:{meeting:{finished_at:new Date().toISOString()}}}),p(n)||(process.stdout.write(`
|
|
9
|
+
`),console.log(` ${be("\u2713")} Upload complete \u2014 transcription started.`),console.log())}if(n.wait){let r;if(!p(n)){let s=(await import("ora")).default;r=s({text:"Processing transcription...",color:"blue"}).start()}let o=await de(()=>_(`/api/v1/meeting/${i.id}`),s=>s.status==="done"||s.status==="finished"||s.status==="failed",{timeout:parseInt(n.waitTimeout)*1e3});r?.stop(),p(n)?w(o,n):(console.log(),v({ID:o.id,Title:o.title,Status:o.status}),console.log())}else if(p(n))w(i,n);else{v({ID:i.id,Title:i.title||Ae(e),Status:i.status}),console.log();let r=i.status;r==="created"||r==="uploading"?console.log(" \u23F3 File uploaded. Processing will start shortly \u2014 you can close this now."):r==="processing_audio"||r==="processing"?console.log(" \u23F3 Transcription in progress \u2014 you can close this now. It will take a few minutes."):r==="finished"||r==="done"?console.log(` ${be("\u2713")} Transcription complete \u2014 notes are ready!`):r==="failed"&&console.log(" \u274C Processing failed."),console.log(),console.log(g(` Check status: meeting read ${i.id}`)),console.log()}}catch(i){b(i,n)}});return k("upload",{description:"Upload a local file or URL for transcription",params:{source:{type:"string",description:"Local file path or URL",required:!0},title:{type:"string",description:"Meeting title",required:!1},language:{type:"string",description:"Language code (default: auto-detect)",required:!1},wait:{type:"boolean",description:"Wait until complete",required:!1},"wait-timeout":{type:"number",description:"Wait timeout in seconds",required:!1}},output:{type:"object",properties:{id:{type:"string"},title:{type:"string"},status:{type:"string"}}}}),t}import{Command as qt}from"commander";import G from"chalk";async function Je(t){let e=[],n;for(;;){let i={items_per_page:"100"};n&&(i.cursor=n);let r=await _(`/api/v1/meeting/${t}/transcript`,{query:i}),s=(r.transcribe||r.transcript||[]).map(a=>({...a,start:typeof a.start=="string"?parseFloat(a.start):a.start,end:typeof a.end=="string"?parseFloat(a.end):a.end,speaker:a.speaker||a.meeting_participants?.[0]?.name||a.contacts?.[0]?.name||"Unknown Speaker"}));if(e.push(...s),r.next_cursor)n=String(r.next_cursor);else break}return e}function $e(t){let e=Math.floor(t/3600),n=Math.floor(t%3600/60),i=Math.floor(t%60);return e>0?`${e}:${String(n).padStart(2,"0")}:${String(i).padStart(2,"0")}`:`${n}:${String(i).padStart(2,"0")}`}function At(t,e){let n=new RegExp(`(${e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")})`,"gi");return t.replace(n,(i,r)=>G.bold.yellow(r))}function Ft(t,e,n=5){let i=n*60,r=[];for(let c=0;c<t.length;c++)t[c].text.toLowerCase().includes(e.toLowerCase())&&r.push(c);if(r.length===0)return{sections:[],matchCount:0};let o=[];for(let c of r){let l=t[c].start;o.push({start:Math.max(0,l-i),end:l+i})}let s=[o[0]];for(let c=1;c<o.length;c++){let l=s[s.length-1];o[c].start<=l.end?l.end=Math.max(l.end,o[c].end):s.push(o[c])}let a=[];for(let c of s){let l=t.filter(d=>d.start>=c.start&&d.start<=c.end);l.length>0&&a.push(l)}return{sections:a,matchCount:r.length}}function Be(t,e){let n="";for(let i of t){i.speaker!==n&&(n&&console.log(),console.log(` ${G.bold(i.speaker)}`),console.log(g(" "+"\u2500".repeat(35))),n=i.speaker);let r=$e(i.start),o=e?At(i.text,e):i.text;console.log(` ${g(r)} ${o}`)}}function ze(){let t=new qt("transcript").description("Meeting transcript & speakers").argument("[meeting_id]","Meeting ID").option("--json","Output as JSON").option("--fields <fields>","Select output fields").option("--search <query>","Search within transcript (shows \xB15min context)").option("--full","Show complete raw transcript").option("--speaker <name>","Filter by speaker name").option("--from-time <minutes>","Start from time (minutes)").option("--to-time <minutes>","End at time (minutes)").action(async(e,n)=>{e||(console.error(),console.error(G.red(" \u2717 Missing meeting ID.")),console.error(),console.error(` ${h("Usage:")} meeting transcript ${g("<meeting_id>")}`),console.error(),console.error(` ${g("Examples:")}`),console.error(` ${h("meeting transcript")} 01abc...xyz`),console.error(` ${h("meeting transcript")} 01abc...xyz --full`),console.error(` ${h("meeting transcript")} 01abc...xyz --search "action items"`),console.error(),console.error(` ${g("\u{1F4A1} Without flags, shows speakers and AI summary.")}`),console.error(` ${g("\u{1F4A1} Use --full for the complete word-by-word transcript.")}`),console.error(),process.exit(1));try{if(N(e),n.search){let s;if(!p(n)){let m=(await import("ora")).default;s=m({text:`Searching transcript for "${n.search}"...`,color:"blue"}).start()}let a=await Je(e);s?.stop();let c=a;if(n.speaker){let m=n.speaker.toLowerCase();c=c.filter(u=>u.speaker.toLowerCase().includes(m))}let{sections:l,matchCount:d}=Ft(c,n.search);if(p(n))w({matchCount:d,sections:l.flat()},n);else if(console.log(),console.log(` ${h.bold("\u{1F50D} Transcript Search")}${g(` ${d} match${d!==1?"es":""} for "${n.search}"`)}`),console.log(),l.length===0)console.log(g(" No matches found.")),console.log();else for(let m=0;m<l.length;m++){m>0&&(console.log(g(" \xB7\xB7\xB7 ")),console.log());let u=l[m][0],f=l[m][l[m].length-1];console.log(g(` [${$e(u.start)} \u2013 ${$e(f.end||f.start)}]`)),console.log(),Be(l[m],n.search),console.log()}return}if(n.full){let s;if(!p(n)){let c=(await import("ora")).default;s=c({text:"Loading transcript...",color:"blue"}).start()}let a=await Je(e);if(s?.stop(),n.speaker){let c=n.speaker.toLowerCase();a=a.filter(l=>l.speaker.toLowerCase().includes(c))}if(n.fromTime||n.toTime){let c=n.fromTime?parseFloat(n.fromTime):void 0,l=n.toTime?parseFloat(n.toTime):void 0;a=a.filter(d=>{let m=d.start/60;return!(c!==void 0&&m<c||l!==void 0&&m>l)})}p(n)?w({transcript:a,total:a.length},n):(console.log(),console.log(` ${h.bold("\u{1F399}\uFE0F Full Transcript")}`),console.log(),Be(a),console.log());return}let i;if(!p(n)){let s=(await import("ora")).default;i=s({text:"Loading transcript summary...",color:"blue"}).start()}let[r,o]=await Promise.all([_(`/api/v1/meeting/${e}/sections-complete`),_(`/api/v1/meeting/${e}/contacts`).catch(()=>null)]);if(i?.stop(),p(n))w({sections:r,speakers:o},n);else{let s=o?Array.isArray(o)?o:o.contacts||[]:[];if(s.length>0){console.log(),console.log(` ${h.bold("\u{1F465} Speakers")}`),console.log();let c=[...s].sort((l,d)=>(d.total_talk_time||0)-(l.total_talk_time||0));for(let l of c){let d=l.total_talk_time?E(l.total_talk_time):"",m=[l.role,l.company].filter(Boolean).join(" @ "),u=[d,m].filter(Boolean).join(g(" \xB7 "));console.log(` ${G.bold(l.name||"(unknown)")}${u?" "+g(u):""}`)}}let a=r.meeting_sections||r.sections||[];if(a.length>0){console.log(),console.log(` ${h.bold("\u{1F4DD} Summary")}`),console.log();for(let c of a){if(console.log(` ${G.bold(c.title||"Untitled Section")}`),console.log(g(" "+"\u2500".repeat(40))),c.content){let l=c.content.split(`
|
|
10
|
+
`);for(let d of l)console.log(` ${d}`)}console.log()}}console.log(g(` Use ${h("--search <query>")} to search transcript, or ${h("--full")} for raw transcript`)),console.log()}}catch(i){b(i,n)}});return k("transcript",{description:"Meeting transcript & speakers (summary by default, --search or --full for transcript)",params:{meeting_id:{type:"string",description:"Meeting ID",required:!0},search:{type:"string",description:"Search keyword (shows \xB15min context)",required:!1},full:{type:"boolean",description:"Show complete raw transcript",required:!1},speaker:{type:"string",description:"Filter by speaker name",required:!1},from_time:{type:"number",description:"Start from time in minutes",required:!1},to_time:{type:"number",description:"End at time in minutes",required:!1}},output:{type:"object",properties:{sections:{type:"object"},speakers:{type:"array",items:{type:"object"}}}}}),t}import{Command as Jt}from"commander";import Ve from"chalk";function We(){let t=new Jt("contacts").description("Manage contacts");return t.command("list").description("List contacts").option("--json","Output as JSON").option("--fields <fields>","Select output fields").option("--search <keyword>","Search by name, role, or company").option("--sort <order>","Sort by (name/meetings/recent)","recent").option("--min-meetings <n>","Only contacts with at least N meetings").option("--limit <n>","Items per page","20").option("--offset <cursor>","Cursor for pagination").action(async e=>{try{let i=q(e.limit),r;if(!p(e)){let s=(await import("ora")).default;r=s({text:"Loading contacts...",color:"blue"}).start()}let o=await _("/api/v1/contacts",{query:{keyword:e.search,items_per_page:String(i),cursor:e.offset}});if(r?.stop(),p(e))w(o,e);else{let a=function(c,l){let d=l?`${g(`${l}.`)} `:" ",m=c.total_meeting_count?`${c.total_meeting_count} meetings`:"",u=c.last_meeting_at?`last met ${P(c.last_meeting_at)}`:"",f=[m,u].filter(Boolean).join(g(" \xB7 "));console.log(`${d}${Ve.bold(c.name||"(unknown)")}`),console.log(` ${g(c.id)}`),(c.role||c.company)&&console.log(` ${g([c.role,c.company].filter(Boolean).join(" @ "))}`),f&&console.log(` ${g(f)}`),console.log()};var n=a;let s=o.contacts||o.contact||[];if(e.minMeetings){let c=parseInt(e.minMeetings,10);isNaN(c)||(s=s.filter(l=>l.total_meeting_count>=c))}if(e.sort==="name"?s.sort((c,l)=>(c.name||"").localeCompare(l.name||"")):e.sort==="meetings"&&s.sort((c,l)=>(l.total_meeting_count||0)-(c.total_meeting_count||0)),console.log(),console.log(` ${h.bold("\u{1F465} Contacts")}${g(` ${o.total_item.toLocaleString()} total`)}`),!e.sort&&!e.search&&!e.minMeetings){let c=[...s].sort((d,m)=>(m.total_meeting_count||0)-(d.total_meeting_count||0)).slice(0,10),l=[...s].sort((d,m)=>{let u=d.last_meeting_at?new Date(d.last_meeting_at).getTime():0;return(m.last_meeting_at?new Date(m.last_meeting_at).getTime():0)-u}).slice(0,10);console.log(),console.log(h.bold(" \u{1F3C6} Top by Meeting Count")),console.log(g(" "+"\u2500".repeat(40))),console.log(),c.forEach((d,m)=>a(d,m+1)),console.log(h.bold(" \u{1F550} Recently Met")),console.log(g(" "+"\u2500".repeat(40))),console.log(),l.forEach((d,m)=>a(d,m+1))}else{console.log();for(let c of s)a(c)}o.next_cursor&&(console.log(g(` More results available. Use --offset ${o.next_cursor}`)),console.log()),console.log(g(" Filters: --sort (name/meetings/recent), --min-meetings, --keyword")),console.log()}}catch(i){b(i,e)}}),t.command("get <id>").description("Get contact details").option("--json","Output as JSON").option("--fields <fields>","Select output fields").action(async(e,n)=>{try{he(e);let i;if(!p(n)){let o=(await import("ora")).default;i=o({text:"Loading contact...",color:"blue"}).start()}let r=await _(`/api/v1/contact/${e}`);if(i?.stop(),p(n))w(r,n);else{let o=r.contact;console.log(),console.log(` ${Ve.bold(o.name||"(unknown)")}`),(o.role||o.company)&&console.log(` ${g([o.role,o.company].filter(Boolean).join(" @ "))}`),console.log(),v({ID:o.id,Emails:o.emails?.map(s=>s.email).join(", ")||"-","Total Meetings":String(o.total_meetings),"Total Time":o.total_meeting_seconds?E(o.total_meeting_seconds):"-"}),r.recent_meetings?.length&&(console.log(),console.log(h.bold(" Recent Meetings")),console.log(g(" "+"\u2500".repeat(40))),J(r.recent_meetings,[{key:"title",label:"Title"},{key:"status",label:"Status"},{key:"created_at",label:"Date",format:s=>s?P(s):"-"},{key:"duration_in_seconds",label:"Duration",format:s=>s?E(s):"-"}])),console.log()}}catch(i){b(i,n)}}),t.command("meetings <id>").description("List meetings for a contact").option("--json","Output as JSON").option("--fields <fields>","Select output fields").option("--limit <n>","Items per page","20").option("--offset <cursor>","Cursor for pagination").action(async(e,n)=>{try{he(e);let i=q(n.limit),r;if(!p(n)){let s=(await import("ora")).default;r=s({text:"Loading meetings...",color:"blue"}).start()}let o=await _(`/api/v1/contact/${e}/meetings`,{query:{items_per_page:String(i),cursor:n.offset}});if(r?.stop(),p(n))w(o,n);else{let s=o.meetings||[];console.log(),console.log(` ${h.bold("\u{1F4CB} Meetings with Contact")}`),console.log(),J(s,[{key:"title",label:"Title"},{key:"status",label:"Status"},{key:"started_at",label:"Date",format:a=>a?P(a):"-"},{key:"duration_in_seconds",label:"Duration",format:a=>a?E(a):"-"}]),console.log()}}catch(i){b(i,n)}}),k("contacts.list",{description:"List contacts",params:{search:{type:"string",description:"Search by name, role, or company",required:!1},sort:{type:"string",description:"Sort by (name/meetings/recent)",required:!1},min_meetings:{type:"number",description:"Only contacts with at least N meetings",required:!1},limit:{type:"number",description:"Items per page (default 20, min 5)",required:!1},offset:{type:"string",description:"Cursor for pagination",required:!1}},output:{type:"object",properties:{total_item:{type:"number"},contact:{type:"array",items:{type:"object"}},next_cursor:{type:"string"}}}}),k("contacts.get",{description:"Get contact details",params:{id:{type:"string",description:"Contact ID",required:!0}},output:{type:"object",properties:{contact:{type:"object"},recent_meetings:{type:"array"}}}}),k("contacts.meetings",{description:"List meetings for a contact",params:{id:{type:"string",description:"Contact ID",required:!0},limit:{type:"number",description:"Items per page",required:!1},offset:{type:"string",description:"Cursor for pagination",required:!1}},output:{type:"object",properties:{meetings:{type:"array",items:{type:"object"}},next_cursor:{type:"string"}}}}),t}import{Command as Bt}from"commander";import Se from"chalk";function zt(t){if(t.includes("meeting.ai/m/")){let e=t.match(/meeting\.ai\/m\/([^/?#]+)/);if(e)return e[1]}return t}function Vt(t){return t.is_title_edited&&t.title?t.title:t.autogenerated_title||t.title||"(untitled)"}function Wt(t){return t.autogenerated_title_emoji?t.autogenerated_title_emoji:se(t.integration)}function Yt(t,e=4){let i=(t.meeting_participants||[]).filter(s=>s.name);return{names:[...i].sort((s,a)=>(a.speaking_seconds||0)-(s.speaking_seconds||0)).slice(0,e).map(s=>s.name).filter(s=>!!s),more:Math.max(0,i.length-e)}}function Ke(){let t=new Bt("read").description("Read meeting notes (URL, ID, or link)").argument("[url_or_id]","Meeting URL (meeting.ai/m/...) or meeting ID").option("--pin <pin>","Access PIN for shared meetings").option("--json","Output as JSON").option("--fields <fields>","Select output fields").action(async(e,n)=>{e||(console.error(),console.error(Se.red(" \u2717 Missing meeting URL or ID.")),console.error(),console.error(` ${h("Usage:")} meeting read ${g("<url_or_id>")} ${g("[--pin <pin>]")}`),console.error(),console.error(` ${g("Examples:")}`),console.error(` ${h("meeting read")} 01abc...xyz`),console.error(` ${h("meeting read")} https://app.meeting.ai/m/01abc...xyz --pin 123456`),console.error(),console.error(` ${g("\u{1F4A1} Run")} ${h("meeting list")} ${g("to see your recent meetings and their IDs.")}`),console.error(` ${g("\u{1F4A1} To read a shared meeting, you need the URL and PIN from the sender.")}`),console.error(),process.exit(1));try{let i=zt(e),r;if(!p(n)){let o=(await import("ora")).default;r=o({text:"Loading meeting...",color:"blue"}).start()}if(n.pin){let o={"access-pin":n.pin},s=await _(`/api/v1/meeting/${i}`,{headers:o}),c=(await _(`/api/v1/meeting/${i}/sections`,{headers:o}).catch(()=>({meeting_sections:[]}))).meeting_sections||[],l=await Promise.all(c.map(d=>_(`/api/v1/meeting/${i}/section/${d.id}/result`,{headers:o}).then(m=>m.section_result||{title:d.variable_values_snapshot?.title||d.title}).catch(()=>({title:d.variable_values_snapshot?.title||d.title}))));r?.stop(),p(n)?w({...s,notes:l},n):Ye(s,l)}else{let[o,s]=await Promise.all([_(`/api/v1/meeting/${i}`),_(`/api/v1/meeting/${i}/sections-complete`).catch(()=>null)]),a=o.public_id,c=a?`https://app.meeting.ai/m/${a}`:null,d=o.is_pin_enabled?o.meeting_access_pin:null;r?.stop();let m=s?.meeting_sections||[];p(n)?w({...o,notes:m,share_url:c,pin:d||null},n):Ye(o,m,c,d)}}catch(i){b(i,n)}});return k("read",{description:"Read meeting notes (URL, ID, or link)",params:{url_or_id:{type:"string",description:"Meeting URL (meeting.ai/m/...) or meeting ID",required:!0},pin:{type:"string",description:"Access PIN for shared meetings",required:!1}},output:{type:"object",properties:{id:{type:"string"},title:{type:"string"},status:{type:"string"},integration:{type:"string"},started_at:{type:"string"},finished_at:{type:"string"},duration_in_seconds:{type:"number"},share_url:{type:"string"},pin:{type:"string"},notes:{type:"array",items:{type:"object"}}}}}),t}function Ye(t,e,n,i){let r=Wt(t),o=Vt(t),s=ae(t.integration),a=E(t.duration_in_seconds),c=re(t.started_at||t.created_at);console.log(),console.log(` ${r} ${Se.bold(o)}`),console.log(` ${g(`${c} \xB7 ${s} \xB7 ${a}`)}`),console.log();let l={ID:t.id,Status:t.status,Integration:s,"Started at":t.started_at?re(t.started_at):"-","Finished at":t.finished_at?re(t.finished_at):"-",Duration:a};n!==void 0&&(l.Share=n||"Not generated",l.PIN=i||"-"),v(l);let{names:d,more:m}=Yt(t);if(d.length>0){console.log(),console.log(h.bold(" Speakers"));let u=d.join(", ")+(m>0?` ${g(`+${m} more`)}`:"");console.log(` ${u}`)}if(e.length>0){console.log(),console.log(h.bold(" Meeting Notes")),console.log(g(" "+"\u2500".repeat(50)));for(let u of e){if(u.title&&(console.log(),console.log(` ${Se.bold(u.title)}`)),u.text){let f=u.text.split(`
|
|
11
|
+
`);for(let y of f)console.log(` ${y}`)}console.log()}}console.log()}import{Command as Kt}from"commander";var Ht=`---
|
|
12
|
+
name: meeting-cli
|
|
13
|
+
description: "Interact with Meeting.ai via the \`meeting\` CLI. Use when user asks about meetings, meeting notes, transcripts, login to meeting.ai, or anything related to Meeting.ai. IMPORTANT: All Meeting.ai operations (login, reading notes, uploading, etc.) are done through the \`meeting\` CLI command \u2014 never use a browser. Login uses \`meeting auth login --email <email>\` which sends an OTP to email."
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Meeting.ai CLI
|
|
17
|
+
|
|
18
|
+
All Meeting.ai operations go through the \`meeting\` CLI. Never use a browser.
|
|
19
|
+
|
|
20
|
+
## Authentication
|
|
21
|
+
|
|
22
|
+
\`\`\`
|
|
23
|
+
meeting auth login --email user@example.com # Sends OTP to email, then prompts for code
|
|
24
|
+
meeting auth whoami # Check who you're logged in as
|
|
25
|
+
meeting auth logout # Clear stored tokens
|
|
26
|
+
\`\`\`
|
|
27
|
+
|
|
28
|
+
Login flow: run \`meeting auth login --email <email>\` \u2192 OTP is sent to email \u2192 ask user for the OTP code \u2192 enter it when prompted. If OTP is wrong, it retries up to 3 times.
|
|
29
|
+
|
|
30
|
+
## Commands
|
|
31
|
+
|
|
32
|
+
\`\`\`
|
|
33
|
+
meeting read <url_or_id> [--pin <pin>] Read meeting notes (own or shared)
|
|
34
|
+
meeting search <query> Search meetings by keyword
|
|
35
|
+
meeting list [--limit N] List recent meetings
|
|
36
|
+
meeting transcript <id> Speakers + AI summary (default)
|
|
37
|
+
meeting transcript <id> --full Full word-by-word transcript
|
|
38
|
+
meeting transcript <id> --search "query" Search within transcript
|
|
39
|
+
meeting upload <file_or_url> Upload recording for transcription
|
|
40
|
+
meeting upload <file> --wait Upload and wait for completion
|
|
41
|
+
meeting contacts list List contacts
|
|
42
|
+
meeting calendar events --upcoming View upcoming calendar events
|
|
43
|
+
meeting bot send --url <meeting_url> Send bot to a live meeting
|
|
44
|
+
meeting delete <id> Delete a meeting (irreversible)
|
|
45
|
+
meeting auth whoami Check current login
|
|
46
|
+
meeting schema List all commands and params
|
|
47
|
+
\`\`\`
|
|
48
|
+
|
|
49
|
+
## Key Behaviors
|
|
50
|
+
|
|
51
|
+
- Output is human-readable by default. Use \`--json\` only when piping to other tools.
|
|
52
|
+
- \`meeting read\` works with both meeting IDs and \`https://app.meeting.ai/m/...\` URLs.
|
|
53
|
+
- Shared meetings require \`--pin\`. Own meetings don't.
|
|
54
|
+
- \`meeting list\` returns 5 most recent meetings with IDs \u2014 use these IDs for other commands.
|
|
55
|
+
- After \`meeting upload\`, the CLI prints status. If status is "created" or "processing_audio", transcription is in progress \u2014 no need to wait.
|
|
56
|
+
- \`meeting transcript\` without flags shows speakers + AI summary. Add \`--full\` for the raw transcript.
|
|
57
|
+
|
|
58
|
+
## Typical Workflows
|
|
59
|
+
|
|
60
|
+
**User asks "what was discussed in my last meeting?"**
|
|
61
|
+
1. \`meeting list --limit 1\` \u2192 get the meeting ID
|
|
62
|
+
2. \`meeting read <id>\` \u2192 read the full notes
|
|
63
|
+
|
|
64
|
+
**User shares a meeting.ai link + PIN**
|
|
65
|
+
1. \`meeting read <url> --pin <pin>\` \u2192 read the shared notes
|
|
66
|
+
|
|
67
|
+
**User wants to upload a recording**
|
|
68
|
+
1. \`meeting upload recording.mp4\` \u2192 upload and let it process
|
|
69
|
+
2. Wait a few minutes, then \`meeting read <id>\` to check notes
|
|
70
|
+
|
|
71
|
+
**User searches for a topic across meetings**
|
|
72
|
+
1. \`meeting search "budget review"\` \u2192 find matching meetings
|
|
73
|
+
2. \`meeting read <id>\` \u2192 read the relevant one
|
|
74
|
+
|
|
75
|
+
## Adopt as Agent Skill
|
|
76
|
+
|
|
77
|
+
To use this as an agent skill, save the content above as SKILL.md in your skills directory.`;function He(){return new Kt("help").description("Full syntax and agent integration guide").action(()=>{process.stdout.write(Ht+`
|
|
78
|
+
`)})}import{Command as Gt}from"commander";import{execSync as Ge}from"child_process";function Ze(){return new Gt("update").description("Update meeting CLI to the latest version").action(async()=>{let e=(await import("chalk")).default,n=(await import("ora")).default,i=e.hex("#22C55E"),r=e.hex("#2563EB"),o=e.hex("#6B7280");console.log(),console.log(` ${r.bold("meeting update")}`),console.log();let s=n({text:"Checking for updates...",color:"blue"}).start();try{let a=Ge("npm update -g @meeting-ai/cli 2>&1",{encoding:"utf-8",timeout:6e4});s.stop();try{let c=Ge("npm list -g @meeting-ai/cli --depth=0 --json 2>/dev/null",{encoding:"utf-8",timeout:1e4}),d=JSON.parse(c).dependencies?.["@meeting-ai/cli"]?.version;console.log(d?` ${i("\u2713")} Updated to ${e.bold(`v${d}`)}`:` ${i("\u2713")} Up to date`)}catch{console.log(` ${i("\u2713")} Update complete`)}}catch{s.stop(),console.log(e.red(" \u2717 Update failed. Try manually:")),console.log(` ${r("npm update -g @meeting-ai/cli")}`)}console.log()})}var Zt=3600*1e3,Xt="https://registry.npmjs.org/@meeting-ai/cli/latest";function Qt(){return"0.1.0-nightly.1"}function Xe(){return ue().get("update")}function en(t){ue().set("update",t)}function tn(t,e){let n=t.split(".").map(Number),i=e.split(".").map(Number);for(let r=0;r<3;r++){if((n[r]||0)>(i[r]||0))return!0;if((n[r]||0)<(i[r]||0))return!1}return!1}function Qe(){let t=Xe();t&&Date.now()-t.checked_at<Zt||fetch(Xt).then(e=>e.ok?e.json():null).then(e=>{e?.version&&en({latest_version:e.version,checked_at:Date.now()})}).catch(()=>{})}function pe(){let t=Xe();if(!t)return null;let e=Qt();return tn(t.latest_version,e)?`Update available: ${e} \u2192 ${t.latest_version} \u2014 run: npm update -g @meeting-ai/cli`:null}V();var nt="0.1.0-nightly.1";function on(){let t=new nn;return t.name("meeting").description("CLI for Meeting.ai \u2014 your meetings, at your fingertips").version(nt),t.addCommand(Pe()),t.addCommand(Le()),t.addCommand(Ke()),t.addCommand(ze()),t.addCommand(Fe()),t.addCommand(je()),t.addCommand(Ee()),t.addCommand(Ue()),t.addCommand(qe()),t.addCommand(We()),t.addCommand(Re()),t.addCommand(He()),t.addCommand(Ze()),t.command("logout").description("Log out and clear stored tokens").action(()=>{Q(),console.log(" \u2713 Logged out successfully.")}),t}async function et(t,e){console.log(` ${e("Commands:")}`),console.log(` ${t("meeting search")} ${e("<query>")} ${e("Search meetings (notes & transcripts)")}`),console.log(` ${t("meeting list")} ${e("Show 10 recent meetings")}`),console.log(` ${t("meeting read")} ${e("<url_or_id>")} ${e("Read meeting notes (URL, ID, or link)")}`),console.log(` ${t("meeting transcript")} ${e("<id>")} ${e("Meeting transcript & speakers")}`),console.log(` ${t("meeting upload")} ${e("<file|url>")} ${e("Upload recording for transcription")}`),console.log(` ${t("meeting bot send")} ${e("--url <url>")} ${e("Send bot to a meeting")}`),console.log(` ${t("meeting calendar events")} ${e("View calendar events")}`),console.log(` ${t("meeting contacts list")} ${e("Browse contacts")}`),console.log(` ${t("meeting delete")} ${e("<id>")} ${e("Delete a meeting")}`),pe()&&console.log(` ${t("meeting update")} ${e("Update CLI")}`),console.log(),console.log(` ${e("Run")} ${t("meeting help")} ${e("for full syntax and agent integration guide.")}`),console.log();try{let r=((await _("/api/v1/meetings",{query:{items_per_page:"5"}})).meeting||[]).slice(0,5);if(r.length>0){console.log(` ${e("Recent meetings:")}`);for(let o of r){let s=o.is_title_edited&&o.title?o.title:o.autogenerated_title||o.title||"(untitled)",a=o.autogenerated_title_emoji||"",c=o.started_at?new Date(o.started_at).toLocaleDateString("en-GB",{day:"numeric",month:"short",year:"numeric"}):"",l=o.duration_in_seconds?o.duration_in_seconds>=3600?`${Math.floor(o.duration_in_seconds/3600)}h ${Math.floor(o.duration_in_seconds%3600/60)}m`:`${Math.floor(o.duration_in_seconds/60)} min`:"",d=o.integration==="gmeet"?"Google Meet":o.integration==="zoom"?"Zoom":o.integration==="livestream"?"In-person":o.integration==="upload"?"Upload":o.integration==="teams"?"Teams":o.integration||"",m=[c,d,l].filter(Boolean).join(" \xB7 ");console.log(` ${a?a+" ":""}${t(s)}`),console.log(` ${e(m)} ${e("ID:")} ${o.id}`)}console.log()}}catch{}}function rn(){let t=pe();t&&import("chalk").then(({default:e})=>{console.log(` ${e.hex("#F59E0B")(t)}`),console.log()}).catch(()=>{})}async function sn(){let t=(await import("chalk")).default,e=t.hex("#2563EB"),n=t.hex("#22C55E"),i=t.hex("#6B7280"),r=t.hex("#EA8232"),o=t.hex("#22C55E"),s=e.bold(`
|
|
79
|
+
\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
80
|
+
\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
|
|
81
|
+
\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557
|
|
82
|
+
\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
83
|
+
\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D${i(".ai")}
|
|
84
|
+
\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D ${i("v"+nt)}
|
|
85
|
+
`);console.log(s);let a=pe();a&&(console.log(` ${t.hex("#F59E0B")(a)}`),console.log());let c=ee();if(c)console.log(` ${n("\u25CF")} Logged in as ${t.bold(c)}`),console.log(),await et(e,i);else{console.log(` ${r("\u25CF")} Log in to get started`),console.log();try{let l=await R(` ${t.bold("Email:")} `);if(!l){console.log(i(" No email provided. Run: meeting auth login")),console.log();return}let d=(await import("ora")).default,m=d({text:"Sending OTP code...",color:"blue"}).start(),u=await B(l);m.stop(),console.log(` ${o("\u2713")} OTP sent! Check your email`),console.log();let f=await R(` ${t.bold("OTP Code:")} `);if(!f){console.log(i(" No OTP provided. Run: meeting auth login")),console.log();return}let y=await oe(l,f,u),$;if(y.mfa_required){let x=y.mfa_type;if(x==="webauthn"){console.log(t.red(" WebAuthn/passkey 2FA is not supported in CLI.")),console.log(i(" Please log in via the web app at https://meeting.ai")),console.log();return}let O;if(x==="totp")O=` ${t.bold("Authenticator code:")} `;else if(x==="password")O=` ${t.bold("Password:")} `;else{let T=(await import("ora")).default,C=T({text:"Sending 2FA code to email...",color:"blue"}).start();await z(y.mfa_id,y.session_id),C.stop(),console.log(` ${o("\u2713")} 2FA code sent to your email`),console.log(),O=` ${t.bold("2FA code (check email):")} `}let S=await R(O);if(!S){console.log(i(" No 2FA code provided. Run: meeting auth login")),console.log();return}$=await ie(y.challenge_id,S,y.session_id,x)}else $=y.tokens;te(l),console.log(` ${o("\u2713")} Logged in as ${t.bold(l)}`),console.log(),et(e,i)}catch(l){let d=l instanceof Error?l.message:"Login failed";console.log(t.red(` Error: ${d}`)),console.log(i(" Run: meeting auth login")),console.log()}}}Qe();var tt=on();process.argv.length<=2?process.stdout.isTTY?sn():tt.help():tt.parseAsync(process.argv).then(()=>{rn()}).catch(t=>{console.error(t.message||"Unknown error"),process.exit(1)});
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@meeting-ai/cli",
|
|
3
|
+
"version": "0.1.0-nightly.1",
|
|
4
|
+
"description": "Agent-friendly CLI for Meeting.ai — read meeting notes, search, upload, and manage meetings from the terminal.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Meeting.ai <dev@meeting.ai>",
|
|
8
|
+
"homepage": "https://meeting.ai",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"meeting",
|
|
11
|
+
"meeting.ai",
|
|
12
|
+
"cli",
|
|
13
|
+
"transcript",
|
|
14
|
+
"notes",
|
|
15
|
+
"ai",
|
|
16
|
+
"agent"
|
|
17
|
+
],
|
|
18
|
+
"bin": {
|
|
19
|
+
"meeting": "dist/bin/meeting.js"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc --noEmit && rm -rf dist && node scripts/bundle.js",
|
|
23
|
+
"build:dev": "node scripts/bundle.js",
|
|
24
|
+
"dev": "tsx watch src/meeting.ts",
|
|
25
|
+
"link": "npm link"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"boxen": "^8.0.1",
|
|
29
|
+
"chalk": "^5.6.2",
|
|
30
|
+
"commander": "^14.0.3",
|
|
31
|
+
"conf": "^15.1.0",
|
|
32
|
+
"ora": "^9.3.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^25.5.0",
|
|
36
|
+
"esbuild": "^0.27.4",
|
|
37
|
+
"tsx": "^4.21.0",
|
|
38
|
+
"typescript": "^5.9.3"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist/bin/meeting.js",
|
|
42
|
+
"package.json",
|
|
43
|
+
"README.md",
|
|
44
|
+
"SKILL.md"
|
|
45
|
+
],
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=22"
|
|
48
|
+
}
|
|
49
|
+
}
|