@mikado-ai/cli 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/README.md +229 -0
- package/dist/chunk-5RICPX62.js +94 -0
- package/dist/chunk-6RKQ24OP.js +94 -0
- package/dist/chunk-DC4BEVL3.js +94 -0
- package/dist/chunk-L27HHHAB.js +95 -0
- package/dist/chunk-TJ7OWJIM.js +94 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +597 -0
- package/dist/mcp.d.ts +4 -0
- package/dist/mcp.js +208 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# @mikado-ai/cli
|
|
2
|
+
|
|
3
|
+
Command-line interface and MCP server for [Mikado AI](https://mikadoai.app) — submit conversation transcripts, extract structured insights, and integrate with AI agents.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @mikado-ai/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or install globally:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g @mikado-ai/cli
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# 1. Authenticate (get your API key from Mikado AI → Settings → API Keys)
|
|
21
|
+
mikado auth
|
|
22
|
+
|
|
23
|
+
# 2. See available campaigns
|
|
24
|
+
mikado campaigns
|
|
25
|
+
|
|
26
|
+
# 3. Submit a transcript
|
|
27
|
+
mikado submit transcript.txt --campaign abcd1234
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Authentication
|
|
31
|
+
|
|
32
|
+
### Interactive
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
mikado auth
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
You'll be prompted for your API key and server URL.
|
|
39
|
+
|
|
40
|
+
### Non-Interactive
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
mikado auth --key mkd_your_api_key --url https://mikadoai.app
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Environment Variable
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
export MIKADO_API_KEY=mkd_your_api_key
|
|
50
|
+
export MIKADO_URL=https://mikadoai.app
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Environment variables take precedence over the config file.
|
|
54
|
+
|
|
55
|
+
### Config File
|
|
56
|
+
|
|
57
|
+
Credentials are stored in `~/.mikado/config.json`. The file is created with restricted permissions (owner-only read/write).
|
|
58
|
+
|
|
59
|
+
## Commands
|
|
60
|
+
|
|
61
|
+
### `mikado submit <file>`
|
|
62
|
+
|
|
63
|
+
Submit a conversation transcript for processing. By default, waits for results.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Submit and wait for results (default)
|
|
67
|
+
mikado submit transcript.txt --campaign abcd1234
|
|
68
|
+
|
|
69
|
+
# Submit without waiting
|
|
70
|
+
mikado submit transcript.txt --campaign abcd1234 --no-wait
|
|
71
|
+
|
|
72
|
+
# Read from stdin
|
|
73
|
+
cat transcript.txt | mikado submit -
|
|
74
|
+
|
|
75
|
+
# JSON output for scripting
|
|
76
|
+
mikado submit transcript.txt --campaign abcd1234 --json
|
|
77
|
+
|
|
78
|
+
# Custom timeout (default: 300s)
|
|
79
|
+
mikado submit transcript.txt --timeout 60
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Options:**
|
|
83
|
+
|
|
84
|
+
| Flag | Description |
|
|
85
|
+
|------|-------------|
|
|
86
|
+
| `-c, --campaign <id>` | Campaign short ID, UUID, or name |
|
|
87
|
+
| `--no-wait` | Return immediately with job ID |
|
|
88
|
+
| `--timeout <seconds>` | Max wait time (default: 300) |
|
|
89
|
+
| `--json` | Output JSON to stdout |
|
|
90
|
+
|
|
91
|
+
### `mikado campaigns`
|
|
92
|
+
|
|
93
|
+
List available campaigns in your organization.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
mikado campaigns
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
ID Name Status Templates Conversations
|
|
101
|
+
abcd1234 Sales Calls ACTIVE 2 142
|
|
102
|
+
efgh5678 Support Tickets ACTIVE 1 89
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### `mikado status <job-id>`
|
|
106
|
+
|
|
107
|
+
Check the processing status of a submitted transcript.
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
mikado status 3f8a2b1c-...
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
Job: 3f8a2b1c-...
|
|
115
|
+
Status: ✓ SUCCESS
|
|
116
|
+
Duration: 11.2s
|
|
117
|
+
Insights: 1 generated
|
|
118
|
+
Sentiment: positive (0.82)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### `mikado result <conversation-id>`
|
|
122
|
+
|
|
123
|
+
Retrieve full results for a processed conversation.
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
mikado result 550e8400-...
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### `mikado auth`
|
|
130
|
+
|
|
131
|
+
Set up or update authentication credentials.
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
mikado auth
|
|
135
|
+
mikado auth --key mkd_... --url https://api.mikado.ai
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## JSON Output
|
|
139
|
+
|
|
140
|
+
All commands support `--json` for machine-readable output. Errors go to stderr, data goes to stdout.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# Pipe to jq
|
|
144
|
+
mikado submit transcript.txt --campaign abcd1234 --json | jq '.insights[0]'
|
|
145
|
+
|
|
146
|
+
# Batch processing
|
|
147
|
+
for f in transcripts/*.txt; do
|
|
148
|
+
mikado submit "$f" --campaign abcd1234 --json >> results.jsonl
|
|
149
|
+
done
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Exit Codes
|
|
153
|
+
|
|
154
|
+
| Code | Meaning |
|
|
155
|
+
|------|---------|
|
|
156
|
+
| `0` | Success |
|
|
157
|
+
| `1` | General error (auth failure, network error) |
|
|
158
|
+
| `2` | Invalid arguments |
|
|
159
|
+
| `3` | Processing failed (pipeline error) |
|
|
160
|
+
| `4` | Timeout (polling exceeded `--timeout`) |
|
|
161
|
+
|
|
162
|
+
## MCP Server
|
|
163
|
+
|
|
164
|
+
Use Mikado AI as a tool in AI agents via the [Model Context Protocol](https://modelcontextprotocol.io/).
|
|
165
|
+
|
|
166
|
+
### Configuration
|
|
167
|
+
|
|
168
|
+
**Claude Desktop** (`claude_desktop_config.json`):
|
|
169
|
+
|
|
170
|
+
```json
|
|
171
|
+
{
|
|
172
|
+
"mcpServers": {
|
|
173
|
+
"mikado": {
|
|
174
|
+
"command": "npx",
|
|
175
|
+
"args": ["@mikado-ai/cli", "mcp"],
|
|
176
|
+
"env": {
|
|
177
|
+
"MIKADO_API_KEY": "mkd_your_api_key",
|
|
178
|
+
"MIKADO_URL": "https://mikadoai.app"
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Claude Code:**
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
claude mcp add mikado -- npx @mikado-ai/cli mcp
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Available Tools
|
|
192
|
+
|
|
193
|
+
| Tool | Description |
|
|
194
|
+
|------|-------------|
|
|
195
|
+
| `mikado_submit_and_wait` | Submit a transcript and wait for full results. Primary tool for AI agents. |
|
|
196
|
+
| `mikado_submit` | Submit a transcript, return immediately with job ID. |
|
|
197
|
+
| `mikado_status` | Check processing status of a job. |
|
|
198
|
+
| `mikado_result` | Get results for a completed conversation. |
|
|
199
|
+
| `mikado_campaigns` | List available campaigns. |
|
|
200
|
+
|
|
201
|
+
### Example Agent Workflow
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
1. Call mikado_campaigns to see available campaigns
|
|
205
|
+
2. Call mikado_submit_and_wait with transcript + campaign
|
|
206
|
+
3. Use the returned insights (scores, labels, extracted data) in your response
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Local Development
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
cd cli
|
|
213
|
+
npm install
|
|
214
|
+
npm run build # Build to dist/
|
|
215
|
+
npm run dev # Watch mode
|
|
216
|
+
|
|
217
|
+
# Test locally
|
|
218
|
+
node dist/index.js --help
|
|
219
|
+
node dist/index.js auth --key mkd_... --url http://localhost:8100
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Requirements
|
|
223
|
+
|
|
224
|
+
- Node.js 18 or later
|
|
225
|
+
- A [Mikado AI](https://mikadoai.app) account with an API key
|
|
226
|
+
|
|
227
|
+
## License
|
|
228
|
+
|
|
229
|
+
MIT
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// src/core/client.ts
|
|
2
|
+
var MikadoClient = class {
|
|
3
|
+
apiKey;
|
|
4
|
+
baseUrl;
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.apiKey = config.apiKey;
|
|
7
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
8
|
+
}
|
|
9
|
+
async request(path, options = {}) {
|
|
10
|
+
const url = `${this.baseUrl}${path}`;
|
|
11
|
+
const headers = {
|
|
12
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
13
|
+
"Content-Type": "application/json",
|
|
14
|
+
...options.headers
|
|
15
|
+
};
|
|
16
|
+
const response = await fetch(url, {
|
|
17
|
+
...options,
|
|
18
|
+
headers
|
|
19
|
+
});
|
|
20
|
+
if (!response.ok) {
|
|
21
|
+
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
22
|
+
try {
|
|
23
|
+
const errorBody = await response.json();
|
|
24
|
+
if (errorBody.message) {
|
|
25
|
+
errorMessage = errorBody.message;
|
|
26
|
+
} else if (errorBody.detail) {
|
|
27
|
+
errorMessage = errorBody.detail;
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
throw new Error(errorMessage);
|
|
32
|
+
}
|
|
33
|
+
return response.json();
|
|
34
|
+
}
|
|
35
|
+
async whoami() {
|
|
36
|
+
return this.request("/api/cli/whoami");
|
|
37
|
+
}
|
|
38
|
+
async submit(content, options) {
|
|
39
|
+
const body = { content };
|
|
40
|
+
if (options?.campaign) {
|
|
41
|
+
body.campaign = options.campaign;
|
|
42
|
+
}
|
|
43
|
+
if (options?.filename) {
|
|
44
|
+
body.filename = options.filename;
|
|
45
|
+
}
|
|
46
|
+
return this.request("/api/cli/submit", {
|
|
47
|
+
method: "POST",
|
|
48
|
+
body: JSON.stringify(body)
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
async getJobStatus(jobId) {
|
|
52
|
+
return this.request(`/api/cli/jobs/${jobId}`);
|
|
53
|
+
}
|
|
54
|
+
async getConversation(conversationId) {
|
|
55
|
+
return this.request(
|
|
56
|
+
`/api/cli/conversations/${conversationId}`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
async listCampaigns() {
|
|
60
|
+
return this.request("/api/cli/campaigns");
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// src/core/poller.ts
|
|
65
|
+
async function pollJob(client, jobId, options = {}) {
|
|
66
|
+
const {
|
|
67
|
+
interval = 1e3,
|
|
68
|
+
timeout = 3e5,
|
|
69
|
+
onUpdate
|
|
70
|
+
} = options;
|
|
71
|
+
const startTime = Date.now();
|
|
72
|
+
let currentInterval = interval;
|
|
73
|
+
const maxInterval = 5e3;
|
|
74
|
+
while (true) {
|
|
75
|
+
const elapsed = Date.now() - startTime;
|
|
76
|
+
if (elapsed > timeout) {
|
|
77
|
+
throw new Error(`Job polling timed out after ${timeout}ms`);
|
|
78
|
+
}
|
|
79
|
+
const status = await client.getJobStatus(jobId);
|
|
80
|
+
if (onUpdate) {
|
|
81
|
+
onUpdate(status);
|
|
82
|
+
}
|
|
83
|
+
if (status.status === "SUCCESS" || status.status === "FAILED") {
|
|
84
|
+
return status;
|
|
85
|
+
}
|
|
86
|
+
await new Promise((resolve) => setTimeout(resolve, currentInterval));
|
|
87
|
+
currentInterval = Math.min(currentInterval * 2, maxInterval);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export {
|
|
92
|
+
MikadoClient,
|
|
93
|
+
pollJob
|
|
94
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// src/core/client.ts
|
|
2
|
+
var MikadoClient = class {
|
|
3
|
+
apiKey;
|
|
4
|
+
baseUrl;
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.apiKey = config.apiKey;
|
|
7
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
8
|
+
}
|
|
9
|
+
async request(path, options = {}) {
|
|
10
|
+
const url = `${this.baseUrl}${path}`;
|
|
11
|
+
const headers = {
|
|
12
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
13
|
+
"Content-Type": "application/json",
|
|
14
|
+
...options.headers
|
|
15
|
+
};
|
|
16
|
+
const response = await fetch(url, {
|
|
17
|
+
...options,
|
|
18
|
+
headers
|
|
19
|
+
});
|
|
20
|
+
if (!response.ok) {
|
|
21
|
+
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
22
|
+
try {
|
|
23
|
+
const errorBody = await response.json();
|
|
24
|
+
if (errorBody.message) {
|
|
25
|
+
errorMessage = errorBody.message;
|
|
26
|
+
} else if (errorBody.detail) {
|
|
27
|
+
errorMessage = errorBody.detail;
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
throw new Error(errorMessage);
|
|
32
|
+
}
|
|
33
|
+
return response.json();
|
|
34
|
+
}
|
|
35
|
+
async whoami() {
|
|
36
|
+
return this.request("/api/cli/whoami");
|
|
37
|
+
}
|
|
38
|
+
async submit(content, options) {
|
|
39
|
+
const body = { content };
|
|
40
|
+
if (options?.campaign) {
|
|
41
|
+
body.campaign_id = options.campaign;
|
|
42
|
+
}
|
|
43
|
+
if (options?.filename) {
|
|
44
|
+
body.filename = options.filename;
|
|
45
|
+
}
|
|
46
|
+
return this.request("/api/cli/submit", {
|
|
47
|
+
method: "POST",
|
|
48
|
+
body: JSON.stringify(body)
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
async getJobStatus(jobId) {
|
|
52
|
+
return this.request(`/api/cli/jobs/${jobId}`);
|
|
53
|
+
}
|
|
54
|
+
async getConversation(conversationId) {
|
|
55
|
+
return this.request(
|
|
56
|
+
`/api/cli/conversations/${conversationId}`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
async listCampaigns() {
|
|
60
|
+
return this.request("/api/cli/campaigns");
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// src/core/poller.ts
|
|
65
|
+
async function pollJob(client, jobId, options = {}) {
|
|
66
|
+
const {
|
|
67
|
+
interval = 1e3,
|
|
68
|
+
timeout = 3e5,
|
|
69
|
+
onUpdate
|
|
70
|
+
} = options;
|
|
71
|
+
const startTime = Date.now();
|
|
72
|
+
let currentInterval = interval;
|
|
73
|
+
const maxInterval = 5e3;
|
|
74
|
+
while (true) {
|
|
75
|
+
const elapsed = Date.now() - startTime;
|
|
76
|
+
if (elapsed > timeout) {
|
|
77
|
+
throw new Error(`Job polling timed out after ${timeout}ms`);
|
|
78
|
+
}
|
|
79
|
+
const status = await client.getJobStatus(jobId);
|
|
80
|
+
if (onUpdate) {
|
|
81
|
+
onUpdate(status);
|
|
82
|
+
}
|
|
83
|
+
if (status.status === "COMPLETED" || status.status === "FAILED") {
|
|
84
|
+
return status;
|
|
85
|
+
}
|
|
86
|
+
await new Promise((resolve) => setTimeout(resolve, currentInterval));
|
|
87
|
+
currentInterval = Math.min(currentInterval * 2, maxInterval);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export {
|
|
92
|
+
MikadoClient,
|
|
93
|
+
pollJob
|
|
94
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// src/core/client.ts
|
|
2
|
+
var MikadoClient = class {
|
|
3
|
+
apiKey;
|
|
4
|
+
baseUrl;
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.apiKey = config.apiKey;
|
|
7
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
8
|
+
}
|
|
9
|
+
async request(path, options = {}) {
|
|
10
|
+
const url = `${this.baseUrl}${path}`;
|
|
11
|
+
const headers = {
|
|
12
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
13
|
+
"Content-Type": "application/json",
|
|
14
|
+
...options.headers
|
|
15
|
+
};
|
|
16
|
+
const response = await fetch(url, {
|
|
17
|
+
...options,
|
|
18
|
+
headers
|
|
19
|
+
});
|
|
20
|
+
if (!response.ok) {
|
|
21
|
+
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
22
|
+
try {
|
|
23
|
+
const errorBody = await response.json();
|
|
24
|
+
if (errorBody.message) {
|
|
25
|
+
errorMessage = errorBody.message;
|
|
26
|
+
} else if (errorBody.detail) {
|
|
27
|
+
errorMessage = errorBody.detail;
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
throw new Error(errorMessage);
|
|
32
|
+
}
|
|
33
|
+
return response.json();
|
|
34
|
+
}
|
|
35
|
+
async whoami() {
|
|
36
|
+
return this.request("/api/v1/cli/whoami");
|
|
37
|
+
}
|
|
38
|
+
async submit(content, options) {
|
|
39
|
+
const body = { content };
|
|
40
|
+
if (options?.campaign) {
|
|
41
|
+
body.campaign = options.campaign;
|
|
42
|
+
}
|
|
43
|
+
if (options?.filename) {
|
|
44
|
+
body.filename = options.filename;
|
|
45
|
+
}
|
|
46
|
+
return this.request("/api/v1/cli/submit", {
|
|
47
|
+
method: "POST",
|
|
48
|
+
body: JSON.stringify(body)
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
async getJobStatus(jobId) {
|
|
52
|
+
return this.request(`/api/v1/cli/jobs/${jobId}`);
|
|
53
|
+
}
|
|
54
|
+
async getConversation(conversationId) {
|
|
55
|
+
return this.request(
|
|
56
|
+
`/api/v1/cli/conversations/${conversationId}`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
async listCampaigns() {
|
|
60
|
+
return this.request("/api/v1/cli/campaigns");
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// src/core/poller.ts
|
|
65
|
+
async function pollJob(client, jobId, options = {}) {
|
|
66
|
+
const {
|
|
67
|
+
interval = 1e3,
|
|
68
|
+
timeout = 3e5,
|
|
69
|
+
onUpdate
|
|
70
|
+
} = options;
|
|
71
|
+
const startTime = Date.now();
|
|
72
|
+
let currentInterval = interval;
|
|
73
|
+
const maxInterval = 5e3;
|
|
74
|
+
while (true) {
|
|
75
|
+
const elapsed = Date.now() - startTime;
|
|
76
|
+
if (elapsed > timeout) {
|
|
77
|
+
throw new Error(`Job polling timed out after ${timeout}ms`);
|
|
78
|
+
}
|
|
79
|
+
const status = await client.getJobStatus(jobId);
|
|
80
|
+
if (onUpdate) {
|
|
81
|
+
onUpdate(status);
|
|
82
|
+
}
|
|
83
|
+
if (status.status === "SUCCESS" || status.status === "FAILED") {
|
|
84
|
+
return status;
|
|
85
|
+
}
|
|
86
|
+
await new Promise((resolve) => setTimeout(resolve, currentInterval));
|
|
87
|
+
currentInterval = Math.min(currentInterval * 2, maxInterval);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export {
|
|
92
|
+
MikadoClient,
|
|
93
|
+
pollJob
|
|
94
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// src/core/client.ts
|
|
2
|
+
var MikadoClient = class {
|
|
3
|
+
apiKey;
|
|
4
|
+
baseUrl;
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.apiKey = config.apiKey;
|
|
7
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
8
|
+
}
|
|
9
|
+
async request(path, options = {}) {
|
|
10
|
+
const url = `${this.baseUrl}${path}`;
|
|
11
|
+
const headers = {
|
|
12
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
13
|
+
"Content-Type": "application/json",
|
|
14
|
+
...options.headers
|
|
15
|
+
};
|
|
16
|
+
const response = await fetch(url, {
|
|
17
|
+
...options,
|
|
18
|
+
headers
|
|
19
|
+
});
|
|
20
|
+
if (!response.ok) {
|
|
21
|
+
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
22
|
+
try {
|
|
23
|
+
const errorBody = await response.json();
|
|
24
|
+
if (errorBody.message) {
|
|
25
|
+
errorMessage = errorBody.message;
|
|
26
|
+
} else if (errorBody.detail) {
|
|
27
|
+
errorMessage = errorBody.detail;
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
throw new Error(errorMessage);
|
|
32
|
+
}
|
|
33
|
+
return response.json();
|
|
34
|
+
}
|
|
35
|
+
// All /api/v1/* routes go via nginx directly to FastAPI (see nginx/nginx.conf)
|
|
36
|
+
async whoami() {
|
|
37
|
+
return this.request("/api/v1/cli/whoami");
|
|
38
|
+
}
|
|
39
|
+
async submit(content, options) {
|
|
40
|
+
const body = { content };
|
|
41
|
+
if (options?.campaign) {
|
|
42
|
+
body.campaign = options.campaign;
|
|
43
|
+
}
|
|
44
|
+
if (options?.filename) {
|
|
45
|
+
body.filename = options.filename;
|
|
46
|
+
}
|
|
47
|
+
return this.request("/api/v1/cli/submit", {
|
|
48
|
+
method: "POST",
|
|
49
|
+
body: JSON.stringify(body)
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async getJobStatus(jobId) {
|
|
53
|
+
return this.request(`/api/v1/cli/jobs/${jobId}`);
|
|
54
|
+
}
|
|
55
|
+
async getConversation(conversationId) {
|
|
56
|
+
return this.request(
|
|
57
|
+
`/api/v1/cli/conversations/${conversationId}`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
async listCampaigns() {
|
|
61
|
+
return this.request("/api/v1/cli/campaigns");
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// src/core/poller.ts
|
|
66
|
+
async function pollJob(client, jobId, options = {}) {
|
|
67
|
+
const {
|
|
68
|
+
interval = 1e3,
|
|
69
|
+
timeout = 3e5,
|
|
70
|
+
onUpdate
|
|
71
|
+
} = options;
|
|
72
|
+
const startTime = Date.now();
|
|
73
|
+
let currentInterval = interval;
|
|
74
|
+
const maxInterval = 5e3;
|
|
75
|
+
while (true) {
|
|
76
|
+
const elapsed = Date.now() - startTime;
|
|
77
|
+
if (elapsed > timeout) {
|
|
78
|
+
throw new Error(`Job polling timed out after ${timeout}ms`);
|
|
79
|
+
}
|
|
80
|
+
const status = await client.getJobStatus(jobId);
|
|
81
|
+
if (onUpdate) {
|
|
82
|
+
onUpdate(status);
|
|
83
|
+
}
|
|
84
|
+
if (status.status === "SUCCESS" || status.status === "FAILED") {
|
|
85
|
+
return status;
|
|
86
|
+
}
|
|
87
|
+
await new Promise((resolve) => setTimeout(resolve, currentInterval));
|
|
88
|
+
currentInterval = Math.min(currentInterval * 2, maxInterval);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export {
|
|
93
|
+
MikadoClient,
|
|
94
|
+
pollJob
|
|
95
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// src/core/client.ts
|
|
2
|
+
var MikadoClient = class {
|
|
3
|
+
apiKey;
|
|
4
|
+
baseUrl;
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.apiKey = config.apiKey;
|
|
7
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
8
|
+
}
|
|
9
|
+
async request(path, options = {}) {
|
|
10
|
+
const url = `${this.baseUrl}${path}`;
|
|
11
|
+
const headers = {
|
|
12
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
13
|
+
"Content-Type": "application/json",
|
|
14
|
+
...options.headers
|
|
15
|
+
};
|
|
16
|
+
const response = await fetch(url, {
|
|
17
|
+
...options,
|
|
18
|
+
headers
|
|
19
|
+
});
|
|
20
|
+
if (!response.ok) {
|
|
21
|
+
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
22
|
+
try {
|
|
23
|
+
const errorBody = await response.json();
|
|
24
|
+
if (errorBody.message) {
|
|
25
|
+
errorMessage = errorBody.message;
|
|
26
|
+
} else if (errorBody.detail) {
|
|
27
|
+
errorMessage = errorBody.detail;
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
throw new Error(errorMessage);
|
|
32
|
+
}
|
|
33
|
+
return response.json();
|
|
34
|
+
}
|
|
35
|
+
async whoami() {
|
|
36
|
+
return this.request("/api/cli/whoami");
|
|
37
|
+
}
|
|
38
|
+
async submit(content, options) {
|
|
39
|
+
const body = { content };
|
|
40
|
+
if (options?.campaign) {
|
|
41
|
+
body.campaign = options.campaign;
|
|
42
|
+
}
|
|
43
|
+
if (options?.filename) {
|
|
44
|
+
body.filename = options.filename;
|
|
45
|
+
}
|
|
46
|
+
return this.request("/api/cli/submit", {
|
|
47
|
+
method: "POST",
|
|
48
|
+
body: JSON.stringify(body)
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
async getJobStatus(jobId) {
|
|
52
|
+
return this.request(`/api/cli/jobs/${jobId}`);
|
|
53
|
+
}
|
|
54
|
+
async getConversation(conversationId) {
|
|
55
|
+
return this.request(
|
|
56
|
+
`/api/cli/conversations/${conversationId}`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
async listCampaigns() {
|
|
60
|
+
return this.request("/api/cli/campaigns");
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// src/core/poller.ts
|
|
65
|
+
async function pollJob(client, jobId, options = {}) {
|
|
66
|
+
const {
|
|
67
|
+
interval = 1e3,
|
|
68
|
+
timeout = 3e5,
|
|
69
|
+
onUpdate
|
|
70
|
+
} = options;
|
|
71
|
+
const startTime = Date.now();
|
|
72
|
+
let currentInterval = interval;
|
|
73
|
+
const maxInterval = 5e3;
|
|
74
|
+
while (true) {
|
|
75
|
+
const elapsed = Date.now() - startTime;
|
|
76
|
+
if (elapsed > timeout) {
|
|
77
|
+
throw new Error(`Job polling timed out after ${timeout}ms`);
|
|
78
|
+
}
|
|
79
|
+
const status = await client.getJobStatus(jobId);
|
|
80
|
+
if (onUpdate) {
|
|
81
|
+
onUpdate(status);
|
|
82
|
+
}
|
|
83
|
+
if (status.status === "COMPLETED" || status.status === "FAILED") {
|
|
84
|
+
return status;
|
|
85
|
+
}
|
|
86
|
+
await new Promise((resolve) => setTimeout(resolve, currentInterval));
|
|
87
|
+
currentInterval = Math.min(currentInterval * 2, maxInterval);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export {
|
|
92
|
+
MikadoClient,
|
|
93
|
+
pollJob
|
|
94
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|