@andocorp/cli 0.2.0 → 0.3.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 +83 -141
- package/dist/agent-commands.js +297 -0
- package/dist/api-command.js +187 -0
- package/dist/api-inputs.js +223 -0
- package/dist/api-operations.js +344 -0
- package/dist/args.js +71 -0
- package/dist/auth-commands.js +362 -0
- package/dist/cli-helpers.js +67 -0
- package/dist/cli-login-browser.js +60 -0
- package/dist/cli-login-errors.js +10 -0
- package/dist/cli-login-paths.js +8 -0
- package/dist/cli-login-revoke.js +100 -0
- package/dist/cli-login.js +335 -0
- package/dist/client.js +104 -0
- package/dist/commands.js +155 -0
- package/dist/config-credential-metadata.js +68 -0
- package/dist/config-keyring.js +61 -0
- package/dist/config-logout-credentials.js +171 -0
- package/dist/config-paths.js +41 -0
- package/dist/config-types.js +1 -0
- package/dist/config.js +333 -0
- package/dist/format.js +297 -0
- package/dist/help.js +70 -0
- package/dist/index.js +77 -34266
- package/dist/output.js +7 -0
- package/dist/session.js +58 -0
- package/dist/timeouts.js +1 -0
- package/dist/types.js +1 -0
- package/dist/watch-commands.js +120 -0
- package/package.json +15 -16
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ando Corp
|
|
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
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
# Ando CLI
|
|
1
|
+
# Ando CLI
|
|
2
2
|
|
|
3
|
-
`ando`
|
|
4
|
-
|
|
5
|
-
This README documents the agent-first command surface where commands are predictable, flag-driven, and easy for automation to script. The classic human-friendly commands and interactive TUI are still supported.
|
|
3
|
+
`ando` provides agent-first terminal commands for Ando. It is built on the
|
|
4
|
+
public `@andocorp/sdk` package and authenticates with an Ando API key.
|
|
6
5
|
|
|
7
6
|
## Installation
|
|
8
7
|
|
|
9
|
-
### npm (Recommended)
|
|
10
|
-
|
|
11
8
|
```sh
|
|
12
9
|
npm install -g @andocorp/cli
|
|
13
10
|
```
|
|
@@ -18,93 +15,102 @@ Or run without installing:
|
|
|
18
15
|
npx @andocorp/cli
|
|
19
16
|
```
|
|
20
17
|
|
|
21
|
-
### Alternative: Standalone Binaries
|
|
22
|
-
|
|
23
|
-
Standalone binaries are available for users who prefer not to install Node.js/npm. Contact hello@ando.so for access.
|
|
24
|
-
|
|
25
18
|
## Authentication
|
|
26
19
|
|
|
27
|
-
|
|
20
|
+
Log in once:
|
|
28
21
|
|
|
29
22
|
```sh
|
|
30
|
-
ando login
|
|
23
|
+
ando login
|
|
31
24
|
```
|
|
32
25
|
|
|
33
|
-
|
|
26
|
+
The command opens a browser authorization page, prints a verification code to
|
|
27
|
+
confirm in the browser, and stores the returned API key in the system keyring
|
|
28
|
+
by default. Non-secret metadata is stored at `~/.config/ando/config.json`.
|
|
34
29
|
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
-
- `--api-host`: override backend API host used for agent-first commands
|
|
30
|
+
For remote shells or agents that cannot open a browser directly, start a
|
|
31
|
+
two-step login:
|
|
38
32
|
|
|
39
|
-
|
|
33
|
+
```sh
|
|
34
|
+
ando login --no-browser
|
|
35
|
+
```
|
|
40
36
|
|
|
41
|
-
|
|
37
|
+
Open the printed URL, confirm the verification code, then run the printed
|
|
38
|
+
`ando login poll` command in the original shell to finish authentication.
|
|
42
39
|
|
|
43
|
-
|
|
40
|
+
For headless scripts and CI, provide an API key directly:
|
|
44
41
|
|
|
45
42
|
```sh
|
|
46
|
-
ando
|
|
47
|
-
ando messages -c engineering -m 10
|
|
48
|
-
ando messages --dm alex --before <cursor> --json
|
|
49
|
-
ando messages --conversation <conversation-id>
|
|
43
|
+
ando login --api-key ando_sk_...
|
|
50
44
|
```
|
|
51
45
|
|
|
52
|
-
|
|
46
|
+
You can also set `ANDO_API_KEY` for one-off or CI usage.
|
|
47
|
+
|
|
48
|
+
Set `ANDO_KEYRING=0` to use file-based auth instead of the system keyring. In
|
|
49
|
+
that mode the API key is stored in `auth.json` under the Ando config directory.
|
|
50
|
+
Set `ANDO_HOME` to override the config and auth state directory; otherwise the
|
|
51
|
+
CLI uses the platform config directory.
|
|
52
|
+
|
|
53
|
+
Optional endpoint flags:
|
|
53
54
|
|
|
54
|
-
- `--
|
|
55
|
-
- `--
|
|
56
|
-
- `--
|
|
57
|
-
- `--limit` / `-m`
|
|
58
|
-
- `--before`
|
|
59
|
-
- `--json`
|
|
55
|
+
- `--base-url`: login and SDK REST base URL
|
|
56
|
+
- `--api-host`: public API base URL for agent-first commands
|
|
57
|
+
- `--realtime-host`: realtime host for SDK subscriptions
|
|
60
58
|
|
|
61
|
-
|
|
59
|
+
Check local auth health or remove local credentials:
|
|
62
60
|
|
|
63
61
|
```sh
|
|
64
|
-
ando
|
|
65
|
-
ando
|
|
66
|
-
ando
|
|
67
|
-
ando
|
|
68
|
-
ando
|
|
62
|
+
ando doctor
|
|
63
|
+
ando doctor --json
|
|
64
|
+
ando whoami
|
|
65
|
+
ando whoami --json
|
|
66
|
+
ando logout
|
|
69
67
|
```
|
|
70
68
|
|
|
71
|
-
`
|
|
69
|
+
`ando whoami` prints the member and workspace resolved by the active API key.
|
|
70
|
+
`ando logout` revokes browser-created CLI credentials before removing local auth
|
|
71
|
+
state. API keys supplied manually or through `ANDO_API_KEY` remain local-only.
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
## Commands
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
- `clipboard`
|
|
79
|
-
- `calls`
|
|
80
|
-
|
|
81
|
-
#### Message search filters
|
|
75
|
+
The raw public API escape hatch mirrors the generated Ando API surface. It is
|
|
76
|
+
useful for scripts and agents that need an endpoint before a dedicated CLI
|
|
77
|
+
workflow exists.
|
|
82
78
|
|
|
83
79
|
```sh
|
|
84
|
-
ando
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
80
|
+
ando api ls
|
|
81
|
+
ando api ls --json
|
|
82
|
+
ando api searchMessages q==incident mode==semantic
|
|
83
|
+
ando api getMember memberId=<member-id>
|
|
84
|
+
ando api /v1/search/messages q==incident mode==semantic
|
|
85
|
+
ando api /v1/conversations/<conversation-id>/messages \
|
|
86
|
+
markdown_content="hello" \
|
|
87
|
+
Idempotency-Key:<key>
|
|
88
|
+
echo '{"markdown_content":"hello"}' | ando api /v1/conversations/<conversation-id>/messages --data -
|
|
89
|
+
ando api /v1/search/messages --help
|
|
90
|
+
ando api /v1/search/messages --spec
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
`ando api` accepts `name==value` query parameters, `Header:Value` declared
|
|
94
|
+
request headers, `field=value` JSON string body fields, `field:=json` typed JSON
|
|
95
|
+
body fields, and `--data <json|->` for complete request bodies. The target can
|
|
96
|
+
be a public path or an operation ID from `ando api ls`; query and header names
|
|
97
|
+
are validated against the generated public API metadata. For operation ID
|
|
98
|
+
targets, supply required path parameters as `name=value`.
|
|
97
99
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
-
|
|
101
|
-
|
|
102
|
-
-
|
|
103
|
-
|
|
104
|
-
- `--before`
|
|
105
|
-
- `--mode` (`full-text` or `semantic`, message search only)
|
|
100
|
+
```sh
|
|
101
|
+
ando messages --channel engineering --limit 10
|
|
102
|
+
ando messages -c engineering -m 10
|
|
103
|
+
ando messages --dm alex --before <cursor> --json
|
|
104
|
+
ando messages --conversation <conversation-id>
|
|
105
|
+
```
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
```sh
|
|
108
|
+
ando search "incident"
|
|
109
|
+
ando search "alice" --type members
|
|
110
|
+
ando search "standup" --type conversations
|
|
111
|
+
ando search "launch checklist" --type clipboard
|
|
112
|
+
ando search "migration" --type calls --after 2026-01-01T00:00:00.000Z
|
|
113
|
+
```
|
|
108
114
|
|
|
109
115
|
```sh
|
|
110
116
|
ando get message <message-id>
|
|
@@ -114,88 +120,24 @@ ando get call <call-id>
|
|
|
114
120
|
ando get transcript <call-id> --limit 100 --cursor <cursor>
|
|
115
121
|
```
|
|
116
122
|
|
|
117
|
-
Entities:
|
|
118
|
-
|
|
119
|
-
- `message`
|
|
120
|
-
- `member`
|
|
121
|
-
- `clipboard`
|
|
122
|
-
- `call`
|
|
123
|
-
- `transcript`
|
|
124
|
-
|
|
125
|
-
Transcript flags:
|
|
126
|
-
|
|
127
|
-
- `--limit`
|
|
128
|
-
- `--cursor`
|
|
129
|
-
|
|
130
|
-
### 4) `thread` — fetch thread replies
|
|
131
|
-
|
|
132
123
|
```sh
|
|
133
124
|
ando thread <thread-root-message-id>
|
|
134
125
|
ando thread <thread-root-message-id> --limit 50 --after <cursor>
|
|
135
126
|
ando thread -m <message-id> --json
|
|
136
127
|
```
|
|
137
128
|
|
|
138
|
-
|
|
129
|
+
Every agent-first command supports `--json` for machine-readable output.
|
|
130
|
+
Without `--json`, commands print tab-separated rows.
|
|
139
131
|
|
|
140
|
-
|
|
141
|
-
- `--limit`
|
|
142
|
-
- `--after`
|
|
143
|
-
- `--json`
|
|
132
|
+
## Environment
|
|
144
133
|
|
|
145
|
-
|
|
134
|
+
- `ANDO_API_KEY`: API key for SDK and public API requests
|
|
135
|
+
- `ANDO_KEYRING`: set to `0` to use file-based auth instead of keyring
|
|
136
|
+
- `ANDO_HOME`: config and auth state directory
|
|
137
|
+
- `ANDO_BASE_URL`: login and SDK REST base URL
|
|
138
|
+
- `ANDO_API_HOST`: public API base URL for agent-first commands
|
|
139
|
+
- `ANDO_REALTIME_HOST`: realtime host for SDK subscriptions
|
|
140
|
+
- `XDG_CONFIG_HOME`: fallback base directory for config/auth state
|
|
146
141
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
## Human-friendly commands (still supported)
|
|
150
|
-
|
|
151
|
-
The following commands are still available for humans working in a terminal:
|
|
152
|
-
|
|
153
|
-
- `ando` — interactive terminal UI
|
|
154
|
-
- `ando list-channels [--limit|-n <n>]` — 10 most recently active channels by default
|
|
155
|
-
- `ando list-dms [--limit|-n <n>]` — 10 most recently active DMs by default
|
|
156
|
-
- `ando list-messages` — requires `--channel`, `--dm`, or `--conversation`
|
|
157
|
-
- `ando post-message` — requires `--channel/--dm/--conversation` and `--text`
|
|
158
|
-
- `ando reply` — requires `--message-id` and `--text`
|
|
159
|
-
- `ando react` — requires `--message-id` and `--emoji`
|
|
160
|
-
|
|
161
|
-
Examples:
|
|
162
|
-
|
|
163
|
-
```sh
|
|
164
|
-
ando list-channels
|
|
165
|
-
ando list-dms --json
|
|
166
|
-
ando list-messages -c engineering -n 20
|
|
167
|
-
ando post-message -c engineering -t "hello"
|
|
168
|
-
ando reply -m <message-id> -t "on it"
|
|
169
|
-
ando react -m <message-id> -e 👍
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
## Environment variables
|
|
173
|
-
|
|
174
|
-
- `ANDO_CONVEX_URL`: Convex deployment URL
|
|
175
|
-
- `ANDO_API_HOST`: backend API host for agent-first commands (default: `https://api.app.ando.so`)
|
|
176
|
-
- `VITE_CONVEX_URL`: fallback Convex URL
|
|
177
|
-
- `NEXT_PUBLIC_CONVEX_URL`: fallback Convex URL
|
|
178
|
-
- `ANDO_SESSION_JWT`: override saved session token for one run
|
|
179
|
-
|
|
180
|
-
Flags take precedence over environment variables, and environment variables take precedence over the saved config.
|
|
181
|
-
|
|
182
|
-
## Interactive mode
|
|
183
|
-
|
|
184
|
-
Running plain `ando` opens a keyboard-driven terminal client.
|
|
185
|
-
|
|
186
|
-
```sh
|
|
187
|
-
ando
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
Keys:
|
|
191
|
-
|
|
192
|
-
- `[c]` Channels, `[d]` DMs, `[/]` Search, `[Tab]` Focus panes
|
|
193
|
-
- `[Enter]` Open, `[t]` Thread, `[u]` Older, `[n]` Newer
|
|
194
|
-
- `[p]` Post, `[r]` Reply, `[a]` React, `[b]` Back, `[q]` Quit
|
|
195
|
-
|
|
196
|
-
## Help
|
|
197
|
-
|
|
198
|
-
```sh
|
|
199
|
-
ando help
|
|
200
|
-
ando --help
|
|
201
|
-
```
|
|
142
|
+
Flags take precedence over environment variables, and environment variables
|
|
143
|
+
take precedence over saved config.
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { getStringFlag, hasFlag } from "./args.js";
|
|
2
|
+
import { resolveConversation } from "./cli-helpers.js";
|
|
3
|
+
import { printJson } from "./output.js";
|
|
4
|
+
async function resolveConversationId(cliClient, parsedArgs) {
|
|
5
|
+
const channelQuery = getStringFlag(parsedArgs, "channel", "c");
|
|
6
|
+
const dmQuery = getStringFlag(parsedArgs, "dm", "d");
|
|
7
|
+
const conversationQuery = getStringFlag(parsedArgs, "conversation");
|
|
8
|
+
if (conversationQuery != null && channelQuery == null && dmQuery == null) {
|
|
9
|
+
return conversationQuery;
|
|
10
|
+
}
|
|
11
|
+
const query = channelQuery ?? dmQuery ?? conversationQuery;
|
|
12
|
+
if (query == null) {
|
|
13
|
+
throw new Error("messages requires --channel, --dm, or --conversation.");
|
|
14
|
+
}
|
|
15
|
+
const memberships = await cliClient.getAllMemberships();
|
|
16
|
+
const membership = resolveConversation(memberships, {
|
|
17
|
+
allowChannels: dmQuery == null,
|
|
18
|
+
allowDms: channelQuery == null,
|
|
19
|
+
query,
|
|
20
|
+
});
|
|
21
|
+
return membership.conversation.id;
|
|
22
|
+
}
|
|
23
|
+
export async function runMessagesCommand(params) {
|
|
24
|
+
const conversationId = await resolveConversationId(params.cliClient, params.parsedArgs);
|
|
25
|
+
const limit = getStringFlag(params.parsedArgs, "limit", "m") ??
|
|
26
|
+
getStringFlag(params.parsedArgs, "n", "n");
|
|
27
|
+
const before = getStringFlag(params.parsedArgs, "before");
|
|
28
|
+
const messageParams = {
|
|
29
|
+
conversationId,
|
|
30
|
+
};
|
|
31
|
+
if (limit != null) {
|
|
32
|
+
messageParams.limit = limit;
|
|
33
|
+
}
|
|
34
|
+
if (before != null) {
|
|
35
|
+
messageParams.before = before;
|
|
36
|
+
}
|
|
37
|
+
const response = await params.apiClient.listConversationMessages(messageParams);
|
|
38
|
+
if (hasFlag(params.parsedArgs, "json")) {
|
|
39
|
+
printJson(response);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
printMessageResults(response.items);
|
|
43
|
+
}
|
|
44
|
+
function printMessageResults(items) {
|
|
45
|
+
for (const item of items) {
|
|
46
|
+
const author = item.author_name ?? "unknown";
|
|
47
|
+
const content = (item.content ?? "").replace(/\s+/g, " ").trim();
|
|
48
|
+
process.stdout.write(`${item.id}\t${item.created_at}\t${author}\t${content}\n`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function runSearchMessages(apiClient, parsedArgs, query, jsonFlag) {
|
|
52
|
+
const searchParams = { q: query };
|
|
53
|
+
const author = getStringFlag(parsedArgs, "author");
|
|
54
|
+
const conversation = getStringFlag(parsedArgs, "conversation");
|
|
55
|
+
const thread = getStringFlag(parsedArgs, "thread");
|
|
56
|
+
const after = getStringFlag(parsedArgs, "after");
|
|
57
|
+
const before = getStringFlag(parsedArgs, "before");
|
|
58
|
+
const mode = parseSearchMode(getStringFlag(parsedArgs, "mode"));
|
|
59
|
+
if (author != null) {
|
|
60
|
+
searchParams.author = author;
|
|
61
|
+
}
|
|
62
|
+
if (conversation != null) {
|
|
63
|
+
searchParams.conversation = conversation;
|
|
64
|
+
}
|
|
65
|
+
if (thread != null) {
|
|
66
|
+
searchParams.thread = thread;
|
|
67
|
+
}
|
|
68
|
+
if (after != null) {
|
|
69
|
+
searchParams.after = after;
|
|
70
|
+
}
|
|
71
|
+
if (before != null) {
|
|
72
|
+
searchParams.before = before;
|
|
73
|
+
}
|
|
74
|
+
if (mode != null) {
|
|
75
|
+
searchParams.mode = mode;
|
|
76
|
+
}
|
|
77
|
+
const response = await apiClient.searchMessages(searchParams);
|
|
78
|
+
if (jsonFlag) {
|
|
79
|
+
printJson(response);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
printMessageResults(response.items);
|
|
83
|
+
}
|
|
84
|
+
async function runSearchMembers(apiClient, query, jsonFlag) {
|
|
85
|
+
const response = await apiClient.searchMembers({ q: query });
|
|
86
|
+
if (jsonFlag) {
|
|
87
|
+
printJson(response);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
for (const item of response.items) {
|
|
91
|
+
const title = item.title == null || item.title === "" ? "" : ` (${item.title})`;
|
|
92
|
+
process.stdout.write(`${item.id}\t${item.display_name ?? ""}\t${item.email ?? ""}${title}\n`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function runSearchConversations(apiClient, query, jsonFlag) {
|
|
96
|
+
const response = await apiClient.searchConversations({ q: query });
|
|
97
|
+
if (jsonFlag) {
|
|
98
|
+
printJson(response);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
for (const item of response.items) {
|
|
102
|
+
process.stdout.write(`${item.id}\t${item.type}\t${item.name ?? ""}\t${item.human_members_count} members\n`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function runSearchClipboard(apiClient, query, jsonFlag) {
|
|
106
|
+
const response = await apiClient.searchClipboards({ q: query });
|
|
107
|
+
if (jsonFlag) {
|
|
108
|
+
printJson(response);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
for (const item of response.items) {
|
|
112
|
+
process.stdout.write(`${item.id}\t${item.created_at}\t${item.title ?? ""}\t${item.item_count} items\n`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function runSearchCalls(apiClient, parsedArgs, query, jsonFlag) {
|
|
116
|
+
const searchParams = { q: query };
|
|
117
|
+
const conversation = getStringFlag(parsedArgs, "conversation");
|
|
118
|
+
const after = getStringFlag(parsedArgs, "after");
|
|
119
|
+
const before = getStringFlag(parsedArgs, "before");
|
|
120
|
+
if (conversation != null) {
|
|
121
|
+
searchParams.conversation = conversation;
|
|
122
|
+
}
|
|
123
|
+
if (after != null) {
|
|
124
|
+
searchParams.after = after;
|
|
125
|
+
}
|
|
126
|
+
if (before != null) {
|
|
127
|
+
searchParams.before = before;
|
|
128
|
+
}
|
|
129
|
+
const response = await apiClient.searchCalls(searchParams);
|
|
130
|
+
if (jsonFlag) {
|
|
131
|
+
printJson(response);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
for (const item of response.items) {
|
|
135
|
+
process.stdout.write(`${item.id}\t${item.status}\t${item.conversation_name ?? ""}\t${item.duration_seconds ?? "-"}s\n`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
export async function runSearchCommand(params) {
|
|
139
|
+
const query = params.parsedArgs.positionals[1];
|
|
140
|
+
const type = (getStringFlag(params.parsedArgs, "type", "t") ?? "messages").toLowerCase();
|
|
141
|
+
const jsonFlag = hasFlag(params.parsedArgs, "json");
|
|
142
|
+
if (query == null || query.trim() === "") {
|
|
143
|
+
throw new Error('search requires a query, e.g. ando search "incident".');
|
|
144
|
+
}
|
|
145
|
+
switch (type) {
|
|
146
|
+
case "messages": {
|
|
147
|
+
await runSearchMessages(params.apiClient, params.parsedArgs, query, jsonFlag);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
case "members": {
|
|
151
|
+
await runSearchMembers(params.apiClient, query, jsonFlag);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
case "conversations": {
|
|
155
|
+
await runSearchConversations(params.apiClient, query, jsonFlag);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
case "clipboard": {
|
|
159
|
+
await runSearchClipboard(params.apiClient, query, jsonFlag);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
case "calls": {
|
|
163
|
+
await runSearchCalls(params.apiClient, params.parsedArgs, query, jsonFlag);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
default:
|
|
167
|
+
throw new Error(`Unknown search --type "${type}". Expected messages, members, conversations, clipboard, or calls.`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function parseSearchMode(value) {
|
|
171
|
+
if (value == null) {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
if (value === "full-text" || value === "semantic") {
|
|
175
|
+
return value;
|
|
176
|
+
}
|
|
177
|
+
throw new Error(`Unknown --mode "${value}". Expected full-text or semantic.`);
|
|
178
|
+
}
|
|
179
|
+
async function runGetMessage(apiClient, id, jsonFlag) {
|
|
180
|
+
const response = await apiClient.getConversationMessageResult(id);
|
|
181
|
+
if (jsonFlag) {
|
|
182
|
+
printJson(response);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
printMessageResults([response]);
|
|
186
|
+
}
|
|
187
|
+
async function runGetMember(apiClient, id, jsonFlag) {
|
|
188
|
+
const response = await apiClient.getMember(id);
|
|
189
|
+
if (jsonFlag) {
|
|
190
|
+
printJson(response);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const title = response.title == null ? "" : ` (${response.title})`;
|
|
194
|
+
process.stdout.write(`${response.id}\t${response.display_name ?? ""}\t${response.email ?? ""}${title}\n`);
|
|
195
|
+
}
|
|
196
|
+
async function runGetClipboard(apiClient, id, jsonFlag) {
|
|
197
|
+
const response = await apiClient.getClipboard(id);
|
|
198
|
+
if (jsonFlag) {
|
|
199
|
+
printJson(response);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
process.stdout.write(`${response.id}\t${response.title ?? ""}\t${response.created_at}\n`);
|
|
203
|
+
for (const item of response.items) {
|
|
204
|
+
process.stdout.write(` - ${item.type}\t${item.id}\t${item.name ?? item.display_name ?? item.author_name ?? ""}\n`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async function runGetCall(apiClient, id, jsonFlag) {
|
|
208
|
+
const response = await apiClient.getCall(id);
|
|
209
|
+
if (jsonFlag) {
|
|
210
|
+
printJson(response);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
process.stdout.write(`${response.id}\t${response.status}\t${response.conversation_name ?? ""}\t${response.duration_seconds ?? "-"}s\n`);
|
|
214
|
+
for (const participant of response.participants) {
|
|
215
|
+
process.stdout.write(` - ${participant.id}\t${participant.name ?? ""}\n`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async function runGetTranscript(apiClient, parsedArgs, id, jsonFlag) {
|
|
219
|
+
const transcriptParams = {
|
|
220
|
+
callId: id,
|
|
221
|
+
};
|
|
222
|
+
const limit = getStringFlag(parsedArgs, "limit");
|
|
223
|
+
const cursor = getStringFlag(parsedArgs, "cursor");
|
|
224
|
+
if (limit != null) {
|
|
225
|
+
transcriptParams.limit = limit;
|
|
226
|
+
}
|
|
227
|
+
if (cursor != null) {
|
|
228
|
+
transcriptParams.cursor = cursor;
|
|
229
|
+
}
|
|
230
|
+
const response = await apiClient.getCallTranscript(transcriptParams);
|
|
231
|
+
if (jsonFlag) {
|
|
232
|
+
printJson(response);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
for (const segment of response.segments) {
|
|
236
|
+
process.stdout.write(`${segment.start_ms}\t${segment.end_ms}\t${segment.speaker_name ?? ""}\t${segment.content}\n`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
export async function runGetCommand(params) {
|
|
240
|
+
const entity = params.parsedArgs.positionals[1];
|
|
241
|
+
const id = params.parsedArgs.positionals[2];
|
|
242
|
+
const jsonFlag = hasFlag(params.parsedArgs, "json");
|
|
243
|
+
if (entity == null || id == null || id.trim() === "") {
|
|
244
|
+
throw new Error("get requires an entity type and id, e.g. ando get message <id>.");
|
|
245
|
+
}
|
|
246
|
+
switch (entity) {
|
|
247
|
+
case "message": {
|
|
248
|
+
await runGetMessage(params.apiClient, id, jsonFlag);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
case "member": {
|
|
252
|
+
await runGetMember(params.apiClient, id, jsonFlag);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
case "clipboard": {
|
|
256
|
+
await runGetClipboard(params.apiClient, id, jsonFlag);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
case "call": {
|
|
260
|
+
await runGetCall(params.apiClient, id, jsonFlag);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
case "transcript": {
|
|
264
|
+
await runGetTranscript(params.apiClient, params.parsedArgs, id, jsonFlag);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
default:
|
|
268
|
+
throw new Error(`Unknown get entity "${entity}". Expected message, member, clipboard, call, or transcript.`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
export async function runAgentThreadCommand(params) {
|
|
272
|
+
const positionalId = params.parsedArgs.positionals[1];
|
|
273
|
+
const messageId = positionalId ?? getStringFlag(params.parsedArgs, "message-id", "m");
|
|
274
|
+
if (messageId == null || messageId.trim() === "") {
|
|
275
|
+
throw new Error("thread requires a thread root message id.");
|
|
276
|
+
}
|
|
277
|
+
const threadParams = {
|
|
278
|
+
messageId,
|
|
279
|
+
};
|
|
280
|
+
const limit = getStringFlag(params.parsedArgs, "limit");
|
|
281
|
+
const after = getStringFlag(params.parsedArgs, "after");
|
|
282
|
+
if (limit != null) {
|
|
283
|
+
threadParams.limit = limit;
|
|
284
|
+
}
|
|
285
|
+
if (after != null) {
|
|
286
|
+
threadParams.after = after;
|
|
287
|
+
}
|
|
288
|
+
const response = await params.apiClient.listThreadReplies(threadParams);
|
|
289
|
+
if (hasFlag(params.parsedArgs, "json")) {
|
|
290
|
+
printJson(response);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
for (const reply of response.items) {
|
|
294
|
+
const content = (reply.content ?? "").replace(/\s+/g, " ").trim();
|
|
295
|
+
process.stdout.write(`${reply.id}\t${reply.created_at}\t${reply.author_name ?? ""}\t${content}\n`);
|
|
296
|
+
}
|
|
297
|
+
}
|