otter-axi 0.1.0
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/LICENSE +21 -0
- package/README.md +86 -0
- package/dist/bin/otter-axi.d.ts +2 -0
- package/dist/bin/otter-axi.js +4 -0
- package/dist/bin/otter-axi.js.map +1 -0
- package/dist/src/cli.d.ts +5 -0
- package/dist/src/cli.js +50 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/commands/auth.d.ts +3 -0
- package/dist/src/commands/auth.js +144 -0
- package/dist/src/commands/auth.js.map +1 -0
- package/dist/src/commands/doctor.d.ts +3 -0
- package/dist/src/commands/doctor.js +47 -0
- package/dist/src/commands/doctor.js.map +1 -0
- package/dist/src/commands/fetch.d.ts +5 -0
- package/dist/src/commands/fetch.js +132 -0
- package/dist/src/commands/fetch.js.map +1 -0
- package/dist/src/commands/home.d.ts +7 -0
- package/dist/src/commands/home.js +28 -0
- package/dist/src/commands/home.js.map +1 -0
- package/dist/src/commands/search.d.ts +3 -0
- package/dist/src/commands/search.js +105 -0
- package/dist/src/commands/search.js.map +1 -0
- package/dist/src/commands/setup.d.ts +6 -0
- package/dist/src/commands/setup.js +25 -0
- package/dist/src/commands/setup.js.map +1 -0
- package/dist/src/config.d.ts +62 -0
- package/dist/src/config.js +89 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/dates.d.ts +5 -0
- package/dist/src/dates.js +36 -0
- package/dist/src/dates.js.map +1 -0
- package/dist/src/flags.d.ts +19 -0
- package/dist/src/flags.js +45 -0
- package/dist/src/flags.js.map +1 -0
- package/dist/src/meta.d.ts +3 -0
- package/dist/src/meta.js +22 -0
- package/dist/src/meta.js.map +1 -0
- package/dist/src/otter/client.d.ts +30 -0
- package/dist/src/otter/client.js +120 -0
- package/dist/src/otter/client.js.map +1 -0
- package/dist/src/otter/loopback.d.ts +13 -0
- package/dist/src/otter/loopback.js +68 -0
- package/dist/src/otter/loopback.js.map +1 -0
- package/dist/src/otter/oauth.d.ts +32 -0
- package/dist/src/otter/oauth.js +183 -0
- package/dist/src/otter/oauth.js.map +1 -0
- package/dist/src/output.d.ts +20 -0
- package/dist/src/output.js +44 -0
- package/dist/src/output.js.map +1 -0
- package/dist/src/transcript.d.ts +21 -0
- package/dist/src/transcript.js +61 -0
- package/dist/src/transcript.js.map +1 -0
- package/package.json +50 -0
- package/skills/otter-axi/SKILL.md +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jarvus Innovations
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# otter-axi
|
|
2
|
+
|
|
3
|
+
An agent-facing CLI ([AXI](https://github.com/JarvusInnovations/axi) tool) that wraps
|
|
4
|
+
Otter.ai's hosted **MCP server** to **find and pull meeting transcripts** from any shell —
|
|
5
|
+
headlessly, after a one-time browser login. Token-efficient [TOON](https://toonformat.dev)
|
|
6
|
+
output, built for agents.
|
|
7
|
+
|
|
8
|
+
It does find-and-pull only; analysis is left to whatever consumes the output.
|
|
9
|
+
|
|
10
|
+
## Why
|
|
11
|
+
|
|
12
|
+
Otter's hosted MCP connector re-prompts for auth and can't be scripted, and its Public REST
|
|
13
|
+
API is gated to Enterprise workspaces. otter-axi turns the same sanctioned MCP server into a
|
|
14
|
+
scriptable CLI: approve once in the browser, then `search`/`fetch` run non-interactively from
|
|
15
|
+
any agent session or shell, with refresh handled silently.
|
|
16
|
+
|
|
17
|
+
## Install & authenticate
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npx -y otter-axi auth login # one-time browser approval
|
|
21
|
+
otter-axi doctor # config → credentials → MCP reachable
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Tokens are stored in `~/.config/otter-axi/config.json` (mode `0600`) and refreshed
|
|
25
|
+
automatically; they are never printed. Read-only scopes: `profile:read`, `conversations:read`.
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
**Find or browse meetings** (returns metadata: title, date, duration, summary, action-item
|
|
30
|
+
count, id). Empty query + a date range is browse mode:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
otter-axi search "pricing discussion" --after 30d
|
|
34
|
+
otter-axi search --after 2026/05/01 --before 2026/05/07
|
|
35
|
+
otter-axi search --in-transcript "roadmap,milestones" --attended-by "Jane Doe" --mine
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Flags: `-q/--query`, `--after`/`--before` (ISO or relative `7d`/`2w`/`3m`/`1y`),
|
|
39
|
+
`--title-contains`, `--in-transcript`, `--attended-by`, `--channel`, `--folder`, `--mine`,
|
|
40
|
+
`--limit`, `--full`.
|
|
41
|
+
|
|
42
|
+
**Pull a transcript** by id (from search) or `otter.ai/u/<id>` URL:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
otter-axi fetch <id> # metadata + preview
|
|
46
|
+
otter-axi fetch <id> --full # verbatim transcript to stdout (for piping)
|
|
47
|
+
otter-axi fetch <id> --text-out t.txt # verbatim transcript to a file
|
|
48
|
+
otter-axi fetch <id> --json-out t.json # parsed segments [{start,speaker,text}]
|
|
49
|
+
otter-axi fetch <id> --csv-out t.csv # parsed segments as CSV (--tsv-out for TSV)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The segment formats (`--json-out`/`--csv-out`/`--tsv-out`) parse the `[H:MM:SS] Speaker N: …`
|
|
53
|
+
transcript into `{start, speaker, text}` rows — otter-axi owns one tested, lossless parser so
|
|
54
|
+
agents don't re-derive it. Verbatim text stays the default; the parse never drops content.
|
|
55
|
+
|
|
56
|
+
## Commands
|
|
57
|
+
|
|
58
|
+
| Command | Purpose |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `otter-axi` | Content-first home / session status |
|
|
61
|
+
| `auth login [--no-wait\|--wait]` | One-time OAuth; `--no-wait`/`--wait` for agent relay |
|
|
62
|
+
| `auth status [--offline]` | Show the connected account |
|
|
63
|
+
| `auth logout` | Revoke + clear stored tokens |
|
|
64
|
+
| `search [query…] [flags]` | Find/browse meetings (metadata only) |
|
|
65
|
+
| `fetch <id\|url> [--full\|--text-out\|--json-out\|--csv-out\|--tsv-out]` | Pull a transcript (verbatim or parsed segments) |
|
|
66
|
+
| `doctor` | Tiered connectivity diagnostics |
|
|
67
|
+
| `setup hooks` | Install the SessionStart hook (Claude Code / Codex / OpenCode) |
|
|
68
|
+
|
|
69
|
+
## Development
|
|
70
|
+
|
|
71
|
+
Built on `axi-sdk-js` + `@toon-format/toon` + `@modelcontextprotocol/sdk`, TypeScript/ESM,
|
|
72
|
+
bun for dev, `tsc`→node to ship.
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
bun install
|
|
76
|
+
bun run dev <command> # run from source
|
|
77
|
+
bun run test # vitest
|
|
78
|
+
bun run build # tsc → dist/
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Spec-driven: `specs/` is the source of truth, `plans/` tracks the work that built it. See
|
|
82
|
+
[`specs/README.md`](specs/README.md).
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otter-axi.js","sourceRoot":"","sources":["../../bin/otter-axi.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC,IAAI,EAAE,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type AxiCliOptions } from "axi-sdk-js";
|
|
2
|
+
export declare const TOP_HELP = "usage: otter-axi [command] [args] [flags]\ncommands[6]:\n (none)=home, auth, doctor, setup, search, fetch\nflags:\n --help, -v/--version\nexamples:\n otter-axi auth login\n otter-axi search \"roadmap review\" --after 30d\n otter-axi search --after 2026/05/01 --before 2026/05/07\n otter-axi fetch aBcDeFgHiJkLmNoPqRsTuVwXyZ0\n otter-axi --help\n otter-axi search --help\n";
|
|
3
|
+
/** Build the CLI options. Overrides (e.g. `argv`, `stdout`) let tests drive dispatch. */
|
|
4
|
+
export declare function cliOptions(overrides?: Partial<AxiCliOptions>): AxiCliOptions;
|
|
5
|
+
export declare function main(): Promise<void>;
|
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { runAxiCli } from "axi-sdk-js";
|
|
2
|
+
import { DESCRIPTION, readVersion } from "./meta.js";
|
|
3
|
+
import { homeCommand } from "./commands/home.js";
|
|
4
|
+
import { authCommand, AUTH_HELP } from "./commands/auth.js";
|
|
5
|
+
import { doctorCommand, DOCTOR_HELP } from "./commands/doctor.js";
|
|
6
|
+
import { setupCommand, SETUP_HELP } from "./commands/setup.js";
|
|
7
|
+
import { searchCommand, SEARCH_HELP } from "./commands/search.js";
|
|
8
|
+
import { fetchCommand, FETCH_HELP } from "./commands/fetch.js";
|
|
9
|
+
export const TOP_HELP = `usage: otter-axi [command] [args] [flags]
|
|
10
|
+
commands[6]:
|
|
11
|
+
(none)=home, auth, doctor, setup, search, fetch
|
|
12
|
+
flags:
|
|
13
|
+
--help, -v/--version
|
|
14
|
+
examples:
|
|
15
|
+
otter-axi auth login
|
|
16
|
+
otter-axi search "roadmap review" --after 30d
|
|
17
|
+
otter-axi search --after 2026/05/01 --before 2026/05/07
|
|
18
|
+
otter-axi fetch aBcDeFgHiJkLmNoPqRsTuVwXyZ0
|
|
19
|
+
otter-axi --help
|
|
20
|
+
otter-axi search --help
|
|
21
|
+
`;
|
|
22
|
+
const COMMAND_HELP = {
|
|
23
|
+
auth: AUTH_HELP,
|
|
24
|
+
doctor: DOCTOR_HELP,
|
|
25
|
+
setup: SETUP_HELP,
|
|
26
|
+
search: SEARCH_HELP,
|
|
27
|
+
fetch: FETCH_HELP,
|
|
28
|
+
};
|
|
29
|
+
/** Build the CLI options. Overrides (e.g. `argv`, `stdout`) let tests drive dispatch. */
|
|
30
|
+
export function cliOptions(overrides = {}) {
|
|
31
|
+
return {
|
|
32
|
+
description: DESCRIPTION,
|
|
33
|
+
version: readVersion(),
|
|
34
|
+
topLevelHelp: TOP_HELP,
|
|
35
|
+
home: () => homeCommand(),
|
|
36
|
+
commands: {
|
|
37
|
+
auth: (args) => authCommand(args),
|
|
38
|
+
doctor: (args) => doctorCommand(args),
|
|
39
|
+
setup: (args) => setupCommand(args),
|
|
40
|
+
search: (args) => searchCommand(args),
|
|
41
|
+
fetch: (args) => fetchCommand(args),
|
|
42
|
+
},
|
|
43
|
+
getCommandHelp: (command) => COMMAND_HELP[command],
|
|
44
|
+
...overrides,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export async function main() {
|
|
48
|
+
await runAxiCli(cliOptions());
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAsB,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAE/D,MAAM,CAAC,MAAM,QAAQ,GAAG;;;;;;;;;;;;CAYvB,CAAC;AAEF,MAAM,YAAY,GAA2B;IAC3C,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,WAAW;IACnB,KAAK,EAAE,UAAU;IACjB,MAAM,EAAE,WAAW;IACnB,KAAK,EAAE,UAAU;CAClB,CAAC;AAEF,yFAAyF;AACzF,MAAM,UAAU,UAAU,CACxB,YAAoC,EAAE;IAEtC,OAAO;QACL,WAAW,EAAE,WAAW;QACxB,OAAO,EAAE,WAAW,EAAE;QACtB,YAAY,EAAE,QAAQ;QACtB,IAAI,EAAE,GAAG,EAAE,CAAC,WAAW,EAAE;QACzB,QAAQ,EAAE;YACR,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC;YACjC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC;YACrC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC;YACnC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC;YACrC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC;SACpC;QACD,cAAc,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC;QAClD,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI;IACxB,MAAM,SAAS,CAAC,UAAU,EAAE,CAAC,CAAC;AAChC,CAAC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { StructuredOutput } from "../output.js";
|
|
2
|
+
export declare const AUTH_HELP = "usage: otter-axi auth <login|status|logout> [flags]\nsubcommands:\n login one-time browser approval; stores refreshable OAuth tokens\n --no-wait prepare only: print the authorize URL and return immediately\n --wait bind the loopback and block for the callback (pairs with --no-wait)\n status show the connected account (--offline to skip the live probe)\n logout revoke and clear stored tokens\nnotes:\n Default `login` opens your browser and blocks (~5 min) until you approve.\n Agents: run `auth login --no-wait` to get the URL, relay it, then `auth login\n --wait` in a separate turn. Authorization codes are short-lived \u2014 approve promptly.";
|
|
3
|
+
export declare function authCommand(args: string[]): Promise<StructuredOutput>;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { AxiError } from "axi-sdk-js";
|
|
3
|
+
import { parseArgs, hasFlag } from "../flags.js";
|
|
4
|
+
import { clearPending, clearTokens, isLoggedIn, readConfig, readPending, writeConfig, } from "../config.js";
|
|
5
|
+
import { getUser } from "../otter/client.js";
|
|
6
|
+
import { completeLogin, LOOPBACK_PORTS, prepareLogin, revokeToken, } from "../otter/oauth.js";
|
|
7
|
+
import { pickPort, startLoopback } from "../otter/loopback.js";
|
|
8
|
+
export const AUTH_HELP = `usage: otter-axi auth <login|status|logout> [flags]
|
|
9
|
+
subcommands:
|
|
10
|
+
login one-time browser approval; stores refreshable OAuth tokens
|
|
11
|
+
--no-wait prepare only: print the authorize URL and return immediately
|
|
12
|
+
--wait bind the loopback and block for the callback (pairs with --no-wait)
|
|
13
|
+
status show the connected account (--offline to skip the live probe)
|
|
14
|
+
logout revoke and clear stored tokens
|
|
15
|
+
notes:
|
|
16
|
+
Default \`login\` opens your browser and blocks (~5 min) until you approve.
|
|
17
|
+
Agents: run \`auth login --no-wait\` to get the URL, relay it, then \`auth login
|
|
18
|
+
--wait\` in a separate turn. Authorization codes are short-lived — approve promptly.`;
|
|
19
|
+
const SUBCOMMANDS = new Set(["login", "status", "logout"]);
|
|
20
|
+
function openBrowser(url) {
|
|
21
|
+
const cmd = process.platform === "darwin"
|
|
22
|
+
? "open"
|
|
23
|
+
: process.platform === "win32"
|
|
24
|
+
? "start"
|
|
25
|
+
: "xdg-open";
|
|
26
|
+
try {
|
|
27
|
+
spawn(cmd, [url], { stdio: "ignore", detached: true }).unref();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// best-effort; the URL is also printed to stderr
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function cacheProfile() {
|
|
34
|
+
const u = await getUser();
|
|
35
|
+
writeConfig({
|
|
36
|
+
...readConfig(),
|
|
37
|
+
user: { name: u.name, email: u.email, cached_at: new Date().toISOString() },
|
|
38
|
+
});
|
|
39
|
+
return u;
|
|
40
|
+
}
|
|
41
|
+
async function bindWaitComplete(port) {
|
|
42
|
+
const lb = await startLoopback(port);
|
|
43
|
+
try {
|
|
44
|
+
const code = await lb.waitForCode(300_000);
|
|
45
|
+
await completeLogin(code);
|
|
46
|
+
const u = await cacheProfile();
|
|
47
|
+
return {
|
|
48
|
+
status: "logged in",
|
|
49
|
+
account: u.email ?? u.name ?? "connected",
|
|
50
|
+
help: [
|
|
51
|
+
'Run `otter-axi search "<query>" --after 30d` to find meetings',
|
|
52
|
+
"Run `otter-axi fetch <id>` to pull a transcript",
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
lb.close();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function login(args) {
|
|
61
|
+
const parsed = parseArgs(args);
|
|
62
|
+
const noWait = hasFlag(parsed, "no-wait");
|
|
63
|
+
const waitOnly = hasFlag(parsed, "wait");
|
|
64
|
+
if (waitOnly) {
|
|
65
|
+
const pending = readPending();
|
|
66
|
+
if (!pending) {
|
|
67
|
+
throw new AxiError("No pending login to wait on", "AUTH", [
|
|
68
|
+
"Run `otter-axi auth login --no-wait` first",
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
return await bindWaitComplete(pending.port);
|
|
72
|
+
}
|
|
73
|
+
const port = await pickPort(LOOPBACK_PORTS);
|
|
74
|
+
const { url } = await prepareLogin(port);
|
|
75
|
+
if (noWait) {
|
|
76
|
+
return {
|
|
77
|
+
action: "authorize",
|
|
78
|
+
url,
|
|
79
|
+
help: [
|
|
80
|
+
"Open the URL above, sign in to Otter, and click Authorize access",
|
|
81
|
+
"Then run `otter-axi auth login --wait` (within ~5 min) to capture the redirect",
|
|
82
|
+
],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
process.stderr.write(`\nAuthorize otter-axi in your browser:\n${url}\n\n`);
|
|
86
|
+
openBrowser(url);
|
|
87
|
+
return await bindWaitComplete(port);
|
|
88
|
+
}
|
|
89
|
+
async function status(args) {
|
|
90
|
+
const parsed = parseArgs(args);
|
|
91
|
+
const offline = hasFlag(parsed, "offline");
|
|
92
|
+
const cfg = readConfig();
|
|
93
|
+
if (!isLoggedIn(cfg)) {
|
|
94
|
+
process.exitCode = 1;
|
|
95
|
+
return {
|
|
96
|
+
status: "not logged in",
|
|
97
|
+
help: ["Run `otter-axi auth login` to connect your Otter account"],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const account = cfg.user?.email ?? cfg.user?.name ?? "unknown";
|
|
101
|
+
if (offline)
|
|
102
|
+
return { status: "logged in", account, token: "not probed (--offline)" };
|
|
103
|
+
try {
|
|
104
|
+
const u = await getUser();
|
|
105
|
+
writeConfig({
|
|
106
|
+
...readConfig(),
|
|
107
|
+
user: { name: u.name, email: u.email, cached_at: new Date().toISOString() },
|
|
108
|
+
});
|
|
109
|
+
return { status: "logged in", account: u.email ?? u.name ?? account, token: "valid" };
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
process.exitCode = 1;
|
|
113
|
+
return {
|
|
114
|
+
status: "logged in",
|
|
115
|
+
account,
|
|
116
|
+
token: "invalid",
|
|
117
|
+
help: ["Run `otter-axi auth login` to re-authenticate"],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async function logout() {
|
|
122
|
+
const cfg = readConfig();
|
|
123
|
+
clearPending();
|
|
124
|
+
if (!isLoggedIn(cfg))
|
|
125
|
+
return { status: "already logged out" };
|
|
126
|
+
await revokeToken();
|
|
127
|
+
// Keep the registered client_id so re-login skips DCR; drop tokens + cached profile.
|
|
128
|
+
const { user: _user, ...rest } = clearTokens(cfg);
|
|
129
|
+
writeConfig(rest);
|
|
130
|
+
return { status: "logged out" };
|
|
131
|
+
}
|
|
132
|
+
export async function authCommand(args) {
|
|
133
|
+
const sub = args[0];
|
|
134
|
+
if (sub === undefined || !SUBCOMMANDS.has(sub)) {
|
|
135
|
+
throw new AxiError(sub ? `Unknown auth subcommand: ${sub}` : "Missing auth subcommand", "VALIDATION_ERROR", ["Run `otter-axi auth login`", "Run `otter-axi auth --help`"]);
|
|
136
|
+
}
|
|
137
|
+
const rest = args.slice(1);
|
|
138
|
+
if (sub === "login")
|
|
139
|
+
return await login(rest);
|
|
140
|
+
if (sub === "status")
|
|
141
|
+
return await status(rest);
|
|
142
|
+
return await logout();
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/commands/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { StructuredOutput } from "../output.js";
|
|
2
|
+
export declare const DOCTOR_HELP = "usage: otter-axi doctor\nchecks (tiered):\n config present \u2192 credentials stored \u2192 token valid / Otter MCP reachable\nexit:\n 0 when authenticated and reachable; 1 on a hard failure";
|
|
3
|
+
export declare function doctorCommand(_args: string[]): Promise<StructuredOutput>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { configPath, isLoggedIn, readConfig } from "../config.js";
|
|
2
|
+
import { getUser } from "../otter/client.js";
|
|
3
|
+
export const DOCTOR_HELP = `usage: otter-axi doctor
|
|
4
|
+
checks (tiered):
|
|
5
|
+
config present → credentials stored → token valid / Otter MCP reachable
|
|
6
|
+
exit:
|
|
7
|
+
0 when authenticated and reachable; 1 on a hard failure`;
|
|
8
|
+
export async function doctorCommand(_args) {
|
|
9
|
+
const cfg = readConfig();
|
|
10
|
+
const checks = [{ name: "config", status: "ok", detail: configPath() }];
|
|
11
|
+
if (!isLoggedIn(cfg)) {
|
|
12
|
+
process.exitCode = 1;
|
|
13
|
+
checks.push({ name: "credentials", status: "fail", detail: "no credentials stored" });
|
|
14
|
+
return { checks, help: ["Run `otter-axi auth login` to connect your Otter account"] };
|
|
15
|
+
}
|
|
16
|
+
checks.push({
|
|
17
|
+
name: "credentials",
|
|
18
|
+
status: "ok",
|
|
19
|
+
detail: `stored for ${cfg.user?.email ?? cfg.user?.name ?? "account"}`,
|
|
20
|
+
});
|
|
21
|
+
try {
|
|
22
|
+
const u = await getUser();
|
|
23
|
+
checks.push({
|
|
24
|
+
name: "mcp",
|
|
25
|
+
status: "ok",
|
|
26
|
+
detail: `reachable; authenticated as ${u.email ?? u.name ?? "user"}`,
|
|
27
|
+
});
|
|
28
|
+
return { checks };
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
const err = e;
|
|
33
|
+
const authProblem = err.code === "AUTH";
|
|
34
|
+
checks.push({
|
|
35
|
+
name: authProblem ? "token" : "mcp",
|
|
36
|
+
status: "fail",
|
|
37
|
+
detail: err.message,
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
checks,
|
|
41
|
+
help: authProblem
|
|
42
|
+
? ["Run `otter-axi auth login` to re-authenticate"]
|
|
43
|
+
: ["Check connectivity to https://mcp.otter.ai and retry"],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../src/commands/doctor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAG7C,MAAM,CAAC,MAAM,WAAW,GAAG;;;;0DAI+B,CAAC;AAQ3D,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAe;IACjD,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,MAAM,MAAM,GAAY,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;IAEjF,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,uBAAuB,EAAE,CAAC,CAAC;QACtF,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,0DAA0D,CAAC,EAAE,CAAC;IACxF,CAAC;IACD,MAAM,CAAC,IAAI,CAAC;QACV,IAAI,EAAE,aAAa;QACnB,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,cAAc,GAAG,CAAC,IAAI,EAAE,KAAK,IAAI,GAAG,CAAC,IAAI,EAAE,IAAI,IAAI,SAAS,EAAE;KACvE,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,MAAM,OAAO,EAAE,CAAC;QAC1B,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,KAAK;YACX,MAAM,EAAE,IAAI;YACZ,MAAM,EAAE,+BAA+B,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,IAAI,MAAM,EAAE;SACrE,CAAC,CAAC;QACH,OAAO,EAAE,MAAM,EAAE,CAAC;IACpB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,MAAM,GAAG,GAAG,CAAa,CAAC;QAC1B,MAAM,WAAW,GAAG,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK;YACnC,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,GAAG,CAAC,OAAO;SACpB,CAAC,CAAC;QACH,OAAO;YACL,MAAM;YACN,IAAI,EAAE,WAAW;gBACf,CAAC,CAAC,CAAC,+CAA+C,CAAC;gBACnD,CAAC,CAAC,CAAC,sDAAsD,CAAC;SAC7D,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { StructuredOutput } from "../output.js";
|
|
2
|
+
export declare const FETCH_HELP = "usage: otter-axi fetch <id|url> [output-mode]\nargs:\n <id|url> conversation slug or an https://otter.ai/u/<id> URL\noutput modes (at most one; default is a preview):\n --full print the entire verbatim transcript to stdout (for piping)\n --text-out <path> write the verbatim transcript text to a file (alias: --out)\n --json-out <path> write parsed segments [{start,speaker,text}] as JSON\n --csv-out <path> write parsed segments as CSV (start,speaker,text)\n --tsv-out <path> write parsed segments as TSV\nnotes:\n otter-axi owns the transcript parse; segment formats are lossless on content.";
|
|
3
|
+
/** Normalize a conversation slug or otter.ai URL to a bare id. */
|
|
4
|
+
export declare function normalizeConversationId(input: string): string;
|
|
5
|
+
export declare function fetchCommand(args: string[]): Promise<StructuredOutput | string>;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
import { AxiError } from "axi-sdk-js";
|
|
4
|
+
import { hasFlag, parseArgs, strFlag } from "../flags.js";
|
|
5
|
+
import { fetchTranscript } from "../otter/client.js";
|
|
6
|
+
import { truncateCell } from "../output.js";
|
|
7
|
+
import { parseTranscript, segmentsToCsv, segmentsToTsv, } from "../transcript.js";
|
|
8
|
+
export const FETCH_HELP = `usage: otter-axi fetch <id|url> [output-mode]
|
|
9
|
+
args:
|
|
10
|
+
<id|url> conversation slug or an https://otter.ai/u/<id> URL
|
|
11
|
+
output modes (at most one; default is a preview):
|
|
12
|
+
--full print the entire verbatim transcript to stdout (for piping)
|
|
13
|
+
--text-out <path> write the verbatim transcript text to a file (alias: --out)
|
|
14
|
+
--json-out <path> write parsed segments [{start,speaker,text}] as JSON
|
|
15
|
+
--csv-out <path> write parsed segments as CSV (start,speaker,text)
|
|
16
|
+
--tsv-out <path> write parsed segments as TSV
|
|
17
|
+
notes:
|
|
18
|
+
otter-axi owns the transcript parse; segment formats are lossless on content.`;
|
|
19
|
+
const VALUED = ["out", "text-out", "json-out", "csv-out", "tsv-out"];
|
|
20
|
+
const PREVIEW_CHARS = 1200;
|
|
21
|
+
/** Normalize a conversation slug or otter.ai URL to a bare id. */
|
|
22
|
+
export function normalizeConversationId(input) {
|
|
23
|
+
const trimmed = input.trim();
|
|
24
|
+
const match = trimmed.match(/otter\.ai\/u\/([^/?#]+)/i);
|
|
25
|
+
return match ? match[1] : trimmed;
|
|
26
|
+
}
|
|
27
|
+
function outPath(parsed, flag) {
|
|
28
|
+
if (!hasFlag(parsed, flag))
|
|
29
|
+
return undefined;
|
|
30
|
+
const p = strFlag(parsed, flag);
|
|
31
|
+
if (!p) {
|
|
32
|
+
throw new AxiError(`--${flag} needs a file path`, "VALIDATION_ERROR", [
|
|
33
|
+
`Run \`otter-axi fetch <id> --${flag} <path>\``,
|
|
34
|
+
]);
|
|
35
|
+
}
|
|
36
|
+
return p;
|
|
37
|
+
}
|
|
38
|
+
export async function fetchCommand(args) {
|
|
39
|
+
const parsed = parseArgs(args, { valued: VALUED });
|
|
40
|
+
const raw = parsed.positionals[0];
|
|
41
|
+
if (!raw) {
|
|
42
|
+
throw new AxiError("Missing conversation id or URL", "VALIDATION_ERROR", [
|
|
43
|
+
"Run `otter-axi fetch <id|url>`",
|
|
44
|
+
"Run `otter-axi search …` to find an id",
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
const id = normalizeConversationId(raw);
|
|
48
|
+
const textPath = outPath(parsed, "text-out") ?? outPath(parsed, "out");
|
|
49
|
+
const jsonPath = outPath(parsed, "json-out");
|
|
50
|
+
const csvPath = outPath(parsed, "csv-out");
|
|
51
|
+
const tsvPath = outPath(parsed, "tsv-out");
|
|
52
|
+
const full = hasFlag(parsed, "full");
|
|
53
|
+
const modeCount = [textPath, jsonPath, csvPath, tsvPath].filter((p) => p !== undefined).length +
|
|
54
|
+
(full ? 1 : 0);
|
|
55
|
+
if (modeCount > 1) {
|
|
56
|
+
throw new AxiError("Choose a single output mode", "VALIDATION_ERROR", [
|
|
57
|
+
"Use one of --full | --text-out | --json-out | --csv-out | --tsv-out",
|
|
58
|
+
]);
|
|
59
|
+
}
|
|
60
|
+
let t;
|
|
61
|
+
try {
|
|
62
|
+
t = await fetchTranscript(id);
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
const err = e;
|
|
66
|
+
throw new AxiError(err.message, err.code ?? "UPSTREAM", [
|
|
67
|
+
...(err.suggestions ?? []),
|
|
68
|
+
`Run \`otter-axi search …\` to find a valid id (checked: ${id})`,
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
const text = t.text ?? "";
|
|
72
|
+
const meta = {
|
|
73
|
+
id: t.id ?? id,
|
|
74
|
+
title: t.title ?? "",
|
|
75
|
+
url: t.url ?? `https://otter.ai/u/${t.id ?? id}`,
|
|
76
|
+
start: (t.metadata?.start_time ?? "").slice(0, 10),
|
|
77
|
+
dur: t.metadata?.duration ?? "—",
|
|
78
|
+
summary: t.metadata?.short_summary ?? "",
|
|
79
|
+
action_items: Array.isArray(t.metadata?.action_items)
|
|
80
|
+
? t.metadata.action_items.length
|
|
81
|
+
: 0,
|
|
82
|
+
};
|
|
83
|
+
// Verbatim text → file (byte-exact, no parsing).
|
|
84
|
+
if (textPath) {
|
|
85
|
+
writeFileSync(textPath, text);
|
|
86
|
+
return { ...meta, format: "text", saved: textPath, bytes: Buffer.byteLength(text) };
|
|
87
|
+
}
|
|
88
|
+
// Parsed segment formats → file.
|
|
89
|
+
if (jsonPath || csvPath || tsvPath) {
|
|
90
|
+
const segments = parseTranscript(text);
|
|
91
|
+
const speakers = [...new Set(segments.map((s) => s.speaker).filter(Boolean))];
|
|
92
|
+
let body;
|
|
93
|
+
let path;
|
|
94
|
+
let format;
|
|
95
|
+
if (jsonPath) {
|
|
96
|
+
body = `${JSON.stringify(segments, null, 2)}\n`;
|
|
97
|
+
path = jsonPath;
|
|
98
|
+
format = "json";
|
|
99
|
+
}
|
|
100
|
+
else if (csvPath) {
|
|
101
|
+
body = segmentsToCsv(segments);
|
|
102
|
+
path = csvPath;
|
|
103
|
+
format = "csv";
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
body = segmentsToTsv(segments);
|
|
107
|
+
path = tsvPath;
|
|
108
|
+
format = "tsv";
|
|
109
|
+
}
|
|
110
|
+
writeFileSync(path, body);
|
|
111
|
+
return {
|
|
112
|
+
...meta,
|
|
113
|
+
format,
|
|
114
|
+
saved: path,
|
|
115
|
+
bytes: Buffer.byteLength(body),
|
|
116
|
+
segments: segments.length,
|
|
117
|
+
speakers,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
// Raw transcript to stdout for piping — return a string so the SDK passes it through as-is.
|
|
121
|
+
if (full)
|
|
122
|
+
return text;
|
|
123
|
+
return {
|
|
124
|
+
...meta,
|
|
125
|
+
chars: text.length,
|
|
126
|
+
preview: truncateCell(text, PREVIEW_CHARS),
|
|
127
|
+
help: [
|
|
128
|
+
"Pass --text-out/--json-out/--csv-out/--tsv-out <path> to export, or --full to print all",
|
|
129
|
+
],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../../src/commands/fetch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { StructuredOutput } from "../output.js";
|
|
2
|
+
/**
|
|
3
|
+
* Content-first home view; doubles as the SessionStart payload. Must never throw and must
|
|
4
|
+
* not block on the network (live state lands in plan 04). Degrades to onboarding when no
|
|
5
|
+
* credentials are stored.
|
|
6
|
+
*/
|
|
7
|
+
export declare function homeCommand(): Promise<StructuredOutput>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { isLoggedIn, readConfig } from "../config.js";
|
|
2
|
+
/**
|
|
3
|
+
* Content-first home view; doubles as the SessionStart payload. Must never throw and must
|
|
4
|
+
* not block on the network (live state lands in plan 04). Degrades to onboarding when no
|
|
5
|
+
* credentials are stored.
|
|
6
|
+
*/
|
|
7
|
+
export async function homeCommand() {
|
|
8
|
+
const cfg = readConfig();
|
|
9
|
+
if (!isLoggedIn(cfg)) {
|
|
10
|
+
return {
|
|
11
|
+
status: "not logged in",
|
|
12
|
+
help: [
|
|
13
|
+
"Run `otter-axi auth login` to connect your Otter account (one-time browser approval)",
|
|
14
|
+
],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const account = cfg.user?.email ?? cfg.user?.name ?? "connected";
|
|
18
|
+
return {
|
|
19
|
+
status: "logged in",
|
|
20
|
+
account,
|
|
21
|
+
help: [
|
|
22
|
+
'Run `otter-axi search "<query>" --after 30d` to find meetings',
|
|
23
|
+
"Run `otter-axi search --after 2026/05/01 --before 2026/05/07` to browse a date range",
|
|
24
|
+
"Run `otter-axi fetch <id>` to pull a transcript",
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=home.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"home.js","sourceRoot":"","sources":["../../../src/commands/home.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAGtD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IAEzB,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO;YACL,MAAM,EAAE,eAAe;YACvB,IAAI,EAAE;gBACJ,sFAAsF;aACvF;SACF,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,KAAK,IAAI,GAAG,CAAC,IAAI,EAAE,IAAI,IAAI,WAAW,CAAC;IACjE,OAAO;QACL,MAAM,EAAE,WAAW;QACnB,OAAO;QACP,IAAI,EAAE;YACJ,+DAA+D;YAC/D,sFAAsF;YACtF,iDAAiD;SAClD;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { StructuredOutput } from "../output.js";
|
|
2
|
+
export declare const SEARCH_HELP = "usage: otter-axi search [query...] [flags]\nflags:\n -q, --query <str> keyword/semantic query (or pass positionally; may be empty)\n --after <date> created on/after \u2014 ISO (2026-05-01) or relative (7d, 30d)\n --before <date> created on/before \u2014 same formats\n --title-contains <str> space-separated keywords matched against the title\n --in-transcript <str> comma-separated keywords searched within transcripts\n --attended-by <name> comma-separated attendee names (not emails)\n --channel <name> comma-separated channel name(s) to search within\n --folder <name> comma-separated folder name(s) to search within\n --mine restrict to your own meetings (exclude shared)\n --limit <n> cap rows shown (default 20)\n --full show all rows, untruncated\nnotes:\n empty query + a date range is browse mode \u2014 list meetings in a window";
|
|
3
|
+
export declare function searchCommand(args: string[]): Promise<StructuredOutput>;
|