@karis-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/bin/karis.js +2 -0
- package/dist/commands/brand/customize.d.ts +11 -0
- package/dist/commands/brand/customize.d.ts.map +1 -0
- package/dist/commands/brand/customize.js +115 -0
- package/dist/commands/brand/customize.js.map +1 -0
- package/dist/commands/brand/info.d.ts +3 -0
- package/dist/commands/brand/info.d.ts.map +1 -0
- package/dist/commands/brand/info.js +14 -0
- package/dist/commands/brand/info.js.map +1 -0
- package/dist/commands/brand/init.d.ts +7 -0
- package/dist/commands/brand/init.d.ts.map +1 -0
- package/dist/commands/brand/init.js +133 -0
- package/dist/commands/brand/init.js.map +1 -0
- package/dist/commands/brand/list.d.ts +4 -0
- package/dist/commands/brand/list.d.ts.map +1 -0
- package/dist/commands/brand/list.js +46 -0
- package/dist/commands/brand/list.js.map +1 -0
- package/dist/commands/brand/refresh.d.ts +7 -0
- package/dist/commands/brand/refresh.d.ts.map +1 -0
- package/dist/commands/brand/refresh.js +63 -0
- package/dist/commands/brand/refresh.js.map +1 -0
- package/dist/commands/brand/select.d.ts +6 -0
- package/dist/commands/brand/select.d.ts.map +1 -0
- package/dist/commands/brand/select.js +104 -0
- package/dist/commands/brand/select.js.map +1 -0
- package/dist/commands/brand/show.d.ts +4 -0
- package/dist/commands/brand/show.d.ts.map +1 -0
- package/dist/commands/brand/show.js +122 -0
- package/dist/commands/brand/show.js.map +1 -0
- package/dist/commands/capabilities.d.ts +3 -0
- package/dist/commands/capabilities.d.ts.map +1 -0
- package/dist/commands/capabilities.js +103 -0
- package/dist/commands/capabilities.js.map +1 -0
- package/dist/commands/chat.d.ts +3 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/chat.js +336 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +91 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +148 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/geo.d.ts +3 -0
- package/dist/commands/geo.d.ts.map +1 -0
- package/dist/commands/geo.js +22 -0
- package/dist/commands/geo.js.map +1 -0
- package/dist/commands/login.d.ts +8 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +173 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/memory.d.ts +3 -0
- package/dist/commands/memory.d.ts.map +1 -0
- package/dist/commands/memory.js +19 -0
- package/dist/commands/memory.js.map +1 -0
- package/dist/commands/reddit.d.ts +3 -0
- package/dist/commands/reddit.d.ts.map +1 -0
- package/dist/commands/reddit.js +61 -0
- package/dist/commands/reddit.js.map +1 -0
- package/dist/commands/schedule.d.ts +3 -0
- package/dist/commands/schedule.d.ts.map +1 -0
- package/dist/commands/schedule.js +15 -0
- package/dist/commands/schedule.js.map +1 -0
- package/dist/commands/setup.d.ts +11 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +303 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/show.d.ts +3 -0
- package/dist/commands/show.d.ts.map +1 -0
- package/dist/commands/show.js +46 -0
- package/dist/commands/show.js.map +1 -0
- package/dist/commands/tools.d.ts +3 -0
- package/dist/commands/tools.d.ts.map +1 -0
- package/dist/commands/tools.js +139 -0
- package/dist/commands/tools.js.map +1 -0
- package/dist/commands/web.d.ts +3 -0
- package/dist/commands/web.d.ts.map +1 -0
- package/dist/commands/web.js +29 -0
- package/dist/commands/web.js.map +1 -0
- package/dist/commands/x.d.ts +3 -0
- package/dist/commands/x.d.ts.map +1 -0
- package/dist/commands/x.js +29 -0
- package/dist/commands/x.js.map +1 -0
- package/dist/commands/youtube.d.ts +3 -0
- package/dist/commands/youtube.d.ts.map +1 -0
- package/dist/commands/youtube.js +16 -0
- package/dist/commands/youtube.js.map +1 -0
- package/dist/core/agent-factory.d.ts +20 -0
- package/dist/core/agent-factory.d.ts.map +1 -0
- package/dist/core/agent-factory.js +26 -0
- package/dist/core/agent-factory.js.map +1 -0
- package/dist/core/agent-interface.d.ts +68 -0
- package/dist/core/agent-interface.d.ts.map +1 -0
- package/dist/core/agent-interface.js +5 -0
- package/dist/core/agent-interface.js.map +1 -0
- package/dist/core/browser-bridge.d.ts +19 -0
- package/dist/core/browser-bridge.d.ts.map +1 -0
- package/dist/core/browser-bridge.js +56 -0
- package/dist/core/browser-bridge.js.map +1 -0
- package/dist/core/cli-context.d.ts +19 -0
- package/dist/core/cli-context.d.ts.map +1 -0
- package/dist/core/cli-context.js +43 -0
- package/dist/core/cli-context.js.map +1 -0
- package/dist/core/client.d.ts +219 -0
- package/dist/core/client.d.ts.map +1 -0
- package/dist/core/client.js +638 -0
- package/dist/core/client.js.map +1 -0
- package/dist/core/errors.d.ts +18 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +107 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/remote-agent.d.ts +36 -0
- package/dist/core/remote-agent.d.ts.map +1 -0
- package/dist/core/remote-agent.js +195 -0
- package/dist/core/remote-agent.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +178 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/agent-helper.d.ts +14 -0
- package/dist/utils/agent-helper.d.ts.map +1 -0
- package/dist/utils/agent-helper.js +54 -0
- package/dist/utils/agent-helper.js.map +1 -0
- package/dist/utils/config.d.ts +26 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +93 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/form-prompt.d.ts +15 -0
- package/dist/utils/form-prompt.d.ts.map +1 -0
- package/dist/utils/form-prompt.js +153 -0
- package/dist/utils/form-prompt.js.map +1 -0
- package/dist/utils/formatter.d.ts +7 -0
- package/dist/utils/formatter.d.ts.map +1 -0
- package/dist/utils/formatter.js +132 -0
- package/dist/utils/formatter.js.map +1 -0
- package/dist/utils/index-cache.d.ts +10 -0
- package/dist/utils/index-cache.d.ts.map +1 -0
- package/dist/utils/index-cache.js +22 -0
- package/dist/utils/index-cache.js.map +1 -0
- package/dist/utils/interactive.d.ts +19 -0
- package/dist/utils/interactive.d.ts.map +1 -0
- package/dist/utils/interactive.js +73 -0
- package/dist/utils/interactive.js.map +1 -0
- package/dist/utils/output.d.ts +41 -0
- package/dist/utils/output.d.ts.map +1 -0
- package/dist/utils/output.js +259 -0
- package/dist/utils/output.js.map +1 -0
- package/dist/utils/run-command.d.ts +2 -0
- package/dist/utils/run-command.d.ts.map +1 -0
- package/dist/utils/run-command.js +12 -0
- package/dist/utils/run-command.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
import { loadResolvedConfig } from '../utils/config.js';
|
|
2
|
+
const EXIT_AUTH = 78;
|
|
3
|
+
const EXIT_RUNTIME = 1;
|
|
4
|
+
export class KarisApiError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
statusCode;
|
|
7
|
+
exitCode;
|
|
8
|
+
constructor(message, code, statusCode, exitCode) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.statusCode = statusCode;
|
|
12
|
+
this.exitCode = exitCode;
|
|
13
|
+
this.name = 'KarisApiError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export class KarisClient {
|
|
17
|
+
apiKey;
|
|
18
|
+
apiUrl;
|
|
19
|
+
static CHAT_CONNECT_TIMEOUT_MS = 120000;
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
this.apiKey = options.apiKey || '';
|
|
22
|
+
this.apiUrl = options.apiUrl || 'https://api.karis.im';
|
|
23
|
+
}
|
|
24
|
+
static async create() {
|
|
25
|
+
const resolved = await loadResolvedConfig();
|
|
26
|
+
return new KarisClient({
|
|
27
|
+
apiKey: resolved.apiKey.value || '',
|
|
28
|
+
apiUrl: resolved.apiUrl.value || 'https://api.karis.im',
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
hasApiKey() {
|
|
32
|
+
return this.apiKey.length > 0;
|
|
33
|
+
}
|
|
34
|
+
async verifyKey() {
|
|
35
|
+
const response = await fetch(`${this.apiUrl}/api/api-keys/me`, {
|
|
36
|
+
method: 'GET',
|
|
37
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
38
|
+
});
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
throw this.buildError(response.status, await this.extractMessage(response));
|
|
41
|
+
}
|
|
42
|
+
const body = (await response.json());
|
|
43
|
+
if (!body.data) {
|
|
44
|
+
throw new KarisApiError('Unexpected response', 'INVALID_RESPONSE', 500, EXIT_RUNTIME);
|
|
45
|
+
}
|
|
46
|
+
return body.data;
|
|
47
|
+
}
|
|
48
|
+
async ensureConversation() {
|
|
49
|
+
const response = await fetch(`${this.apiUrl}/api/v1/agent/conversation`, {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: {
|
|
52
|
+
'Content-Type': 'application/json',
|
|
53
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
54
|
+
},
|
|
55
|
+
body: JSON.stringify({}),
|
|
56
|
+
});
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
throw this.buildError(response.status, await this.extractMessage(response));
|
|
59
|
+
}
|
|
60
|
+
const body = (await response.json());
|
|
61
|
+
const conversationId = body.data?.conversation_id;
|
|
62
|
+
if (!conversationId) {
|
|
63
|
+
throw new KarisApiError('Unexpected response', 'INVALID_RESPONSE', 500, EXIT_RUNTIME);
|
|
64
|
+
}
|
|
65
|
+
return conversationId;
|
|
66
|
+
}
|
|
67
|
+
async *chat(message, options = {}) {
|
|
68
|
+
const conversationId = options.conversationId;
|
|
69
|
+
if (!conversationId) {
|
|
70
|
+
throw new KarisApiError('conversationId is required. Call ensureConversation() before chat().', 'MISSING_CONVERSATION', 500, EXIT_RUNTIME);
|
|
71
|
+
}
|
|
72
|
+
const url = `${this.apiUrl}/api/v1/agent/convs/${conversationId}/message`;
|
|
73
|
+
const payload = {
|
|
74
|
+
message,
|
|
75
|
+
conversation_id: conversationId,
|
|
76
|
+
};
|
|
77
|
+
if (options.mode)
|
|
78
|
+
payload.mode_hint = options.mode;
|
|
79
|
+
if (options.skillHint)
|
|
80
|
+
payload.skill_hint = options.skillHint;
|
|
81
|
+
if (options.toolHint)
|
|
82
|
+
payload.tool_hint = options.toolHint;
|
|
83
|
+
if (options.toolArgs)
|
|
84
|
+
payload.tool_args = options.toolArgs;
|
|
85
|
+
if (options.direct)
|
|
86
|
+
payload.direct = true;
|
|
87
|
+
if (options.tz)
|
|
88
|
+
payload.tz = options.tz;
|
|
89
|
+
const connectController = new AbortController();
|
|
90
|
+
const connectTimer = setTimeout(() => connectController.abort(), KarisClient.CHAT_CONNECT_TIMEOUT_MS);
|
|
91
|
+
let response;
|
|
92
|
+
try {
|
|
93
|
+
response = await fetch(url, {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: {
|
|
96
|
+
'Content-Type': 'application/json',
|
|
97
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
98
|
+
},
|
|
99
|
+
body: JSON.stringify(payload),
|
|
100
|
+
signal: connectController.signal,
|
|
101
|
+
});
|
|
102
|
+
clearTimeout(connectTimer);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
clearTimeout(connectTimer);
|
|
106
|
+
if (this.isTimeoutError(error)) {
|
|
107
|
+
throw new KarisApiError('Timed out waiting for the chat response. The saved conversation may be stale, or the backend may be delayed.', 'REQUEST_TIMEOUT', 504, EXIT_RUNTIME);
|
|
108
|
+
}
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
throw this.buildError(response.status, await this.extractMessage(response));
|
|
113
|
+
}
|
|
114
|
+
if (!response.body) {
|
|
115
|
+
throw new KarisApiError('No response body', 'NO_BODY', 500, EXIT_RUNTIME);
|
|
116
|
+
}
|
|
117
|
+
yield* this.consumeSSE(response.body);
|
|
118
|
+
}
|
|
119
|
+
async *resumeEvents(conversationId, since) {
|
|
120
|
+
const url = `${this.apiUrl}/api/v1/agent/convs/${conversationId}/events`;
|
|
121
|
+
const response = await fetch(url, {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
headers: {
|
|
124
|
+
'Content-Type': 'application/json',
|
|
125
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
126
|
+
},
|
|
127
|
+
body: JSON.stringify(since ? { since } : {}),
|
|
128
|
+
});
|
|
129
|
+
if (!response.ok) {
|
|
130
|
+
throw this.buildError(response.status, await this.extractMessage(response));
|
|
131
|
+
}
|
|
132
|
+
if (!response.body) {
|
|
133
|
+
throw new KarisApiError('No response body', 'NO_BODY', 500, EXIT_RUNTIME);
|
|
134
|
+
}
|
|
135
|
+
yield* this.consumeSSE(response.body);
|
|
136
|
+
}
|
|
137
|
+
async getHistory(conversationId, options = {}) {
|
|
138
|
+
const params = new URLSearchParams();
|
|
139
|
+
if (options.before != null)
|
|
140
|
+
params.set('before', String(options.before));
|
|
141
|
+
if (options.limit != null)
|
|
142
|
+
params.set('limit', String(options.limit));
|
|
143
|
+
const url = `${this.apiUrl}/api/v1/agent/convs/${conversationId}/history?${params}`;
|
|
144
|
+
const response = await fetch(url, {
|
|
145
|
+
method: 'GET',
|
|
146
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
147
|
+
});
|
|
148
|
+
if (!response.ok) {
|
|
149
|
+
throw this.buildError(response.status, await this.extractMessage(response));
|
|
150
|
+
}
|
|
151
|
+
const body = (await response.json());
|
|
152
|
+
return body.data ?? { messages: [], has_more: false };
|
|
153
|
+
}
|
|
154
|
+
async clearHistory(conversationId) {
|
|
155
|
+
const url = `${this.apiUrl}/api/v1/agent/convs/${conversationId}/history`;
|
|
156
|
+
const response = await fetch(url, {
|
|
157
|
+
method: 'DELETE',
|
|
158
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
159
|
+
});
|
|
160
|
+
if (!response.ok) {
|
|
161
|
+
throw this.buildError(response.status, await this.extractMessage(response));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async interrupt(conversationId) {
|
|
165
|
+
const url = `${this.apiUrl}/api/v1/agent/convs/${conversationId}/interrupt`;
|
|
166
|
+
const response = await fetch(url, {
|
|
167
|
+
method: 'POST',
|
|
168
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
169
|
+
});
|
|
170
|
+
if (!response.ok) {
|
|
171
|
+
throw this.buildError(response.status, await this.extractMessage(response));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async respondHitl(conversationId, hitlId, payload) {
|
|
175
|
+
const url = `${this.apiUrl}/api/v1/agent/convs/${conversationId}/hitl`;
|
|
176
|
+
const response = await fetch(url, {
|
|
177
|
+
method: 'POST',
|
|
178
|
+
headers: {
|
|
179
|
+
'Content-Type': 'application/json',
|
|
180
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
181
|
+
},
|
|
182
|
+
body: JSON.stringify({ hitl_id: hitlId, payload }),
|
|
183
|
+
});
|
|
184
|
+
if (!response.ok) {
|
|
185
|
+
throw this.buildError(response.status, await this.extractMessage(response));
|
|
186
|
+
}
|
|
187
|
+
const body = (await response.json());
|
|
188
|
+
return body;
|
|
189
|
+
}
|
|
190
|
+
async toolDirect(toolName, args, conversationId) {
|
|
191
|
+
const convId = conversationId ?? crypto.randomUUID();
|
|
192
|
+
const url = `${this.apiUrl}/api/v1/agent/convs/${convId}/message`;
|
|
193
|
+
const response = await fetch(url, {
|
|
194
|
+
method: 'POST',
|
|
195
|
+
headers: {
|
|
196
|
+
'Content-Type': 'application/json',
|
|
197
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
198
|
+
},
|
|
199
|
+
body: JSON.stringify({
|
|
200
|
+
tool_hint: toolName,
|
|
201
|
+
tool_args: args,
|
|
202
|
+
direct: true,
|
|
203
|
+
conversation_id: convId,
|
|
204
|
+
}),
|
|
205
|
+
});
|
|
206
|
+
if (!response.ok) {
|
|
207
|
+
throw this.buildError(response.status, await this.extractMessage(response));
|
|
208
|
+
}
|
|
209
|
+
return response.json();
|
|
210
|
+
}
|
|
211
|
+
async listTools() {
|
|
212
|
+
const url = `${this.apiUrl}/api/v1/agent/tools`;
|
|
213
|
+
const response = await fetch(url, {
|
|
214
|
+
method: 'GET',
|
|
215
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
216
|
+
});
|
|
217
|
+
if (!response.ok) {
|
|
218
|
+
throw this.buildError(response.status, await this.extractMessage(response));
|
|
219
|
+
}
|
|
220
|
+
return response.json();
|
|
221
|
+
}
|
|
222
|
+
// --- Brand Assets API ---
|
|
223
|
+
async getBrand() {
|
|
224
|
+
const url = `${this.apiUrl}/api/v1/brand-assets/selection`;
|
|
225
|
+
const response = await fetch(url, {
|
|
226
|
+
method: 'GET',
|
|
227
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
228
|
+
});
|
|
229
|
+
if (response.status === 404) {
|
|
230
|
+
return null; // No brand profile yet
|
|
231
|
+
}
|
|
232
|
+
if (!response.ok) {
|
|
233
|
+
throw this.buildError(response.status, await this.extractMessage(response));
|
|
234
|
+
}
|
|
235
|
+
const body = (await response.json());
|
|
236
|
+
if (!body.data)
|
|
237
|
+
return null;
|
|
238
|
+
const data = body.data;
|
|
239
|
+
const profile = data.brand_profile?.profile;
|
|
240
|
+
// Transform API response to BrandProfile
|
|
241
|
+
return {
|
|
242
|
+
id: data.brand_id,
|
|
243
|
+
domain: data.canonical_domain,
|
|
244
|
+
name: data.display_name || profile?.name || data.canonical_domain,
|
|
245
|
+
description: profile?.one_liner,
|
|
246
|
+
longDescription: profile?.tagline,
|
|
247
|
+
claimed: data.binding_status === 'active',
|
|
248
|
+
tagline: profile?.tagline,
|
|
249
|
+
one_liner: profile?.one_liner,
|
|
250
|
+
category: profile?.categories?.[0],
|
|
251
|
+
categories: profile?.categories,
|
|
252
|
+
industries: profile?.inferred_industries,
|
|
253
|
+
audience: profile?.primary_audiences ? {
|
|
254
|
+
primary: profile.primary_audiences[0],
|
|
255
|
+
secondary: profile.primary_audiences[1],
|
|
256
|
+
} : undefined,
|
|
257
|
+
value_propositions: profile?.core_value_props,
|
|
258
|
+
competitors: profile?.competitive_landscape?.direct_competitor?.map(c => ({
|
|
259
|
+
name: c.name,
|
|
260
|
+
domain: c.domain,
|
|
261
|
+
})),
|
|
262
|
+
keywords: profile?.categories, // Using categories as keywords for now
|
|
263
|
+
channels: undefined, // Not in current API response
|
|
264
|
+
tone: undefined, // Not in current API response
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
async createBrand(domain) {
|
|
268
|
+
const url = `${this.apiUrl}/api/v1/brand-assets/analyze`;
|
|
269
|
+
const response = await fetch(url, {
|
|
270
|
+
method: 'POST',
|
|
271
|
+
headers: {
|
|
272
|
+
'Content-Type': 'application/json',
|
|
273
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
274
|
+
},
|
|
275
|
+
body: JSON.stringify({ domain }),
|
|
276
|
+
});
|
|
277
|
+
if (!response.ok) {
|
|
278
|
+
throw this.buildError(response.status, await this.extractMessage(response));
|
|
279
|
+
}
|
|
280
|
+
const body = (await response.json());
|
|
281
|
+
if (!body.data) {
|
|
282
|
+
throw new KarisApiError('Unexpected response', 'INVALID_RESPONSE', 500, EXIT_RUNTIME);
|
|
283
|
+
}
|
|
284
|
+
const data = body.data;
|
|
285
|
+
const profile = data.brand_profile?.profile;
|
|
286
|
+
return {
|
|
287
|
+
id: data.brand_id,
|
|
288
|
+
domain: data.canonical_domain,
|
|
289
|
+
name: data.display_name || profile?.name || data.canonical_domain,
|
|
290
|
+
description: profile?.one_liner,
|
|
291
|
+
longDescription: profile?.tagline,
|
|
292
|
+
claimed: data.binding_status === 'active',
|
|
293
|
+
tagline: profile?.tagline,
|
|
294
|
+
one_liner: profile?.one_liner,
|
|
295
|
+
category: profile?.categories?.[0],
|
|
296
|
+
categories: profile?.categories,
|
|
297
|
+
industries: profile?.inferred_industries,
|
|
298
|
+
audience: profile?.primary_audiences ? {
|
|
299
|
+
primary: profile.primary_audiences[0],
|
|
300
|
+
secondary: profile.primary_audiences[1],
|
|
301
|
+
} : undefined,
|
|
302
|
+
value_propositions: profile?.core_value_props,
|
|
303
|
+
competitors: profile?.competitive_landscape?.direct_competitor?.map(c => ({
|
|
304
|
+
name: c.name,
|
|
305
|
+
domain: c.domain,
|
|
306
|
+
})),
|
|
307
|
+
keywords: profile?.categories,
|
|
308
|
+
channels: undefined,
|
|
309
|
+
tone: undefined,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/** List all brands for the current user */
|
|
313
|
+
async listBrands() {
|
|
314
|
+
const url = `${this.apiUrl}/api/v1/brand-assets`;
|
|
315
|
+
const response = await fetch(url, {
|
|
316
|
+
method: 'GET',
|
|
317
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
318
|
+
});
|
|
319
|
+
if (!response.ok) {
|
|
320
|
+
throw this.buildError(response.status, await this.extractMessage(response));
|
|
321
|
+
}
|
|
322
|
+
const body = (await response.json());
|
|
323
|
+
if (!body.data)
|
|
324
|
+
return [];
|
|
325
|
+
return body.data.map(data => {
|
|
326
|
+
const profile = data.brand_profile?.profile;
|
|
327
|
+
return {
|
|
328
|
+
id: data.brand_id,
|
|
329
|
+
domain: data.canonical_domain,
|
|
330
|
+
name: data.display_name || profile?.name || data.canonical_domain,
|
|
331
|
+
description: profile?.one_liner,
|
|
332
|
+
longDescription: profile?.tagline,
|
|
333
|
+
claimed: data.binding_status === 'active',
|
|
334
|
+
tagline: profile?.tagline,
|
|
335
|
+
one_liner: profile?.one_liner,
|
|
336
|
+
category: profile?.categories?.[0],
|
|
337
|
+
categories: profile?.categories,
|
|
338
|
+
industries: profile?.inferred_industries,
|
|
339
|
+
audience: profile?.primary_audiences ? {
|
|
340
|
+
primary: profile.primary_audiences[0],
|
|
341
|
+
secondary: profile.primary_audiences[1],
|
|
342
|
+
} : undefined,
|
|
343
|
+
value_propositions: profile?.core_value_props,
|
|
344
|
+
competitors: profile?.competitive_landscape?.direct_competitor?.map(c => ({
|
|
345
|
+
name: c.name,
|
|
346
|
+
domain: c.domain,
|
|
347
|
+
})),
|
|
348
|
+
keywords: profile?.categories,
|
|
349
|
+
channels: undefined,
|
|
350
|
+
tone: undefined,
|
|
351
|
+
};
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
/** Set the selected brand (switch active brand) */
|
|
355
|
+
async setBrandSelection(brandId) {
|
|
356
|
+
const url = `${this.apiUrl}/api/v1/brand-assets/selection`;
|
|
357
|
+
const response = await fetch(url, {
|
|
358
|
+
method: 'POST',
|
|
359
|
+
headers: {
|
|
360
|
+
'Content-Type': 'application/json',
|
|
361
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
362
|
+
},
|
|
363
|
+
body: JSON.stringify({ brand_id: brandId }),
|
|
364
|
+
});
|
|
365
|
+
if (!response.ok) {
|
|
366
|
+
throw this.buildError(response.status, await this.extractMessage(response));
|
|
367
|
+
}
|
|
368
|
+
const body = (await response.json());
|
|
369
|
+
if (!body.data) {
|
|
370
|
+
throw new KarisApiError('Unexpected response', 'INVALID_RESPONSE', 500, EXIT_RUNTIME);
|
|
371
|
+
}
|
|
372
|
+
const data = body.data;
|
|
373
|
+
const profile = data.brand_profile?.profile;
|
|
374
|
+
return {
|
|
375
|
+
id: data.brand_id,
|
|
376
|
+
domain: data.canonical_domain,
|
|
377
|
+
name: data.display_name || profile?.name || data.canonical_domain,
|
|
378
|
+
description: profile?.one_liner,
|
|
379
|
+
longDescription: profile?.tagline,
|
|
380
|
+
claimed: data.binding_status === 'active',
|
|
381
|
+
tagline: profile?.tagline,
|
|
382
|
+
one_liner: profile?.one_liner,
|
|
383
|
+
category: profile?.categories?.[0],
|
|
384
|
+
categories: profile?.categories,
|
|
385
|
+
industries: profile?.inferred_industries,
|
|
386
|
+
audience: profile?.primary_audiences ? {
|
|
387
|
+
primary: profile.primary_audiences[0],
|
|
388
|
+
secondary: profile.primary_audiences[1],
|
|
389
|
+
} : undefined,
|
|
390
|
+
value_propositions: profile?.core_value_props,
|
|
391
|
+
competitors: profile?.competitive_landscape?.direct_competitor?.map(c => ({
|
|
392
|
+
name: c.name,
|
|
393
|
+
domain: c.domain,
|
|
394
|
+
})),
|
|
395
|
+
keywords: profile?.categories,
|
|
396
|
+
channels: undefined,
|
|
397
|
+
tone: undefined,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
async updateBrand(customizations) {
|
|
401
|
+
// Get current brand to obtain brand_id
|
|
402
|
+
const currentBrand = await this.getBrand();
|
|
403
|
+
if (!currentBrand) {
|
|
404
|
+
throw new KarisApiError('No brand profile found', 'NO_BRAND', 404, EXIT_RUNTIME);
|
|
405
|
+
}
|
|
406
|
+
// Build override_profile by mapping CLI field names to API profile field names
|
|
407
|
+
const overrideProfile = {};
|
|
408
|
+
if (customizations.category !== undefined) {
|
|
409
|
+
overrideProfile['categories'] = [customizations.category];
|
|
410
|
+
}
|
|
411
|
+
if (customizations.categories !== undefined) {
|
|
412
|
+
overrideProfile['categories'] = customizations.categories;
|
|
413
|
+
}
|
|
414
|
+
if (customizations.industries !== undefined) {
|
|
415
|
+
overrideProfile['inferred_industries'] = customizations.industries;
|
|
416
|
+
}
|
|
417
|
+
if (customizations.audience !== undefined) {
|
|
418
|
+
const audiences = [];
|
|
419
|
+
if (customizations.audience.primary)
|
|
420
|
+
audiences.push(customizations.audience.primary);
|
|
421
|
+
if (customizations.audience.secondary)
|
|
422
|
+
audiences.push(customizations.audience.secondary);
|
|
423
|
+
if (audiences.length > 0)
|
|
424
|
+
overrideProfile['primary_audiences'] = audiences;
|
|
425
|
+
}
|
|
426
|
+
if (customizations.value_propositions !== undefined) {
|
|
427
|
+
overrideProfile['core_value_props'] = customizations.value_propositions;
|
|
428
|
+
}
|
|
429
|
+
if (customizations.competitors !== undefined) {
|
|
430
|
+
overrideProfile['competitive_landscape'] = {
|
|
431
|
+
direct_competitor: customizations.competitors.map((c) => ({
|
|
432
|
+
name: c.name,
|
|
433
|
+
domain: c.domain,
|
|
434
|
+
confidence: 'high',
|
|
435
|
+
})),
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
const url = `${this.apiUrl}/api/v1/brand-assets/customizations`;
|
|
439
|
+
const response = await fetch(url, {
|
|
440
|
+
method: 'POST',
|
|
441
|
+
headers: {
|
|
442
|
+
'Content-Type': 'application/json',
|
|
443
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
444
|
+
},
|
|
445
|
+
body: JSON.stringify({
|
|
446
|
+
brand_id: currentBrand.id,
|
|
447
|
+
override_profile: overrideProfile,
|
|
448
|
+
}),
|
|
449
|
+
});
|
|
450
|
+
if (!response.ok) {
|
|
451
|
+
throw this.buildError(response.status, await this.extractMessage(response));
|
|
452
|
+
}
|
|
453
|
+
const body = (await response.json());
|
|
454
|
+
if (!body.data) {
|
|
455
|
+
throw new KarisApiError('Unexpected response', 'INVALID_RESPONSE', 500, EXIT_RUNTIME);
|
|
456
|
+
}
|
|
457
|
+
const data = body.data;
|
|
458
|
+
const profile = data.brand_profile?.profile;
|
|
459
|
+
return {
|
|
460
|
+
id: data.brand_id,
|
|
461
|
+
domain: data.canonical_domain,
|
|
462
|
+
name: data.display_name || profile?.name || data.canonical_domain,
|
|
463
|
+
description: profile?.one_liner,
|
|
464
|
+
longDescription: profile?.tagline,
|
|
465
|
+
claimed: data.binding_status === 'active',
|
|
466
|
+
tagline: profile?.tagline,
|
|
467
|
+
one_liner: profile?.one_liner,
|
|
468
|
+
category: profile?.categories?.[0],
|
|
469
|
+
categories: profile?.categories,
|
|
470
|
+
industries: profile?.inferred_industries,
|
|
471
|
+
audience: profile?.primary_audiences ? {
|
|
472
|
+
primary: profile.primary_audiences[0],
|
|
473
|
+
secondary: profile.primary_audiences[1],
|
|
474
|
+
} : undefined,
|
|
475
|
+
value_propositions: profile?.core_value_props,
|
|
476
|
+
competitors: profile?.competitive_landscape?.direct_competitor?.map(c => ({
|
|
477
|
+
name: c.name,
|
|
478
|
+
domain: c.domain,
|
|
479
|
+
})),
|
|
480
|
+
keywords: profile?.categories,
|
|
481
|
+
channels: undefined,
|
|
482
|
+
tone: undefined,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
/** Refresh brand data from source (Brandfetch) */
|
|
486
|
+
async refreshBrand(brandId, scopes) {
|
|
487
|
+
const url = `${this.apiUrl}/api/v1/brand-assets/refresh`;
|
|
488
|
+
const response = await fetch(url, {
|
|
489
|
+
method: 'POST',
|
|
490
|
+
headers: {
|
|
491
|
+
'Content-Type': 'application/json',
|
|
492
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
493
|
+
},
|
|
494
|
+
body: JSON.stringify({
|
|
495
|
+
brand_id: brandId,
|
|
496
|
+
scopes: scopes || ['all'],
|
|
497
|
+
}),
|
|
498
|
+
});
|
|
499
|
+
if (!response.ok) {
|
|
500
|
+
throw this.buildError(response.status, await this.extractMessage(response));
|
|
501
|
+
}
|
|
502
|
+
const body = (await response.json());
|
|
503
|
+
if (!body.data) {
|
|
504
|
+
throw new KarisApiError('Unexpected response', 'INVALID_RESPONSE', 500, EXIT_RUNTIME);
|
|
505
|
+
}
|
|
506
|
+
const data = body.data;
|
|
507
|
+
const profile = data.brand_profile?.profile;
|
|
508
|
+
return {
|
|
509
|
+
id: data.brand_id,
|
|
510
|
+
domain: data.canonical_domain,
|
|
511
|
+
name: data.display_name || profile?.name || data.canonical_domain,
|
|
512
|
+
description: profile?.one_liner,
|
|
513
|
+
longDescription: profile?.tagline,
|
|
514
|
+
claimed: data.binding_status === 'active',
|
|
515
|
+
tagline: profile?.tagline,
|
|
516
|
+
one_liner: profile?.one_liner,
|
|
517
|
+
category: profile?.categories?.[0],
|
|
518
|
+
categories: profile?.categories,
|
|
519
|
+
industries: profile?.inferred_industries,
|
|
520
|
+
audience: profile?.primary_audiences ? {
|
|
521
|
+
primary: profile.primary_audiences[0],
|
|
522
|
+
secondary: profile.primary_audiences[1],
|
|
523
|
+
} : undefined,
|
|
524
|
+
value_propositions: profile?.core_value_props,
|
|
525
|
+
competitors: profile?.competitive_landscape?.direct_competitor?.map(c => ({
|
|
526
|
+
name: c.name,
|
|
527
|
+
domain: c.domain,
|
|
528
|
+
})),
|
|
529
|
+
keywords: profile?.categories,
|
|
530
|
+
channels: undefined,
|
|
531
|
+
tone: undefined,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
// --- SSE parsing ---
|
|
535
|
+
async *consumeSSE(body) {
|
|
536
|
+
const reader = body.getReader();
|
|
537
|
+
const decoder = new TextDecoder();
|
|
538
|
+
let buffer = '';
|
|
539
|
+
while (true) {
|
|
540
|
+
const { done, value } = await reader.read();
|
|
541
|
+
if (done)
|
|
542
|
+
break;
|
|
543
|
+
buffer += decoder.decode(value, { stream: true });
|
|
544
|
+
const chunks = buffer.split('\n\n');
|
|
545
|
+
buffer = chunks.pop() || '';
|
|
546
|
+
for (const chunk of chunks) {
|
|
547
|
+
if (!chunk.trim())
|
|
548
|
+
continue;
|
|
549
|
+
let eventType = '';
|
|
550
|
+
let dataStr = '';
|
|
551
|
+
for (const line of chunk.split('\n')) {
|
|
552
|
+
if (line.startsWith('event: '))
|
|
553
|
+
eventType = line.slice(7).trim();
|
|
554
|
+
else if (line.startsWith('data: '))
|
|
555
|
+
dataStr = line.slice(6).trim();
|
|
556
|
+
}
|
|
557
|
+
if (!eventType || !dataStr)
|
|
558
|
+
continue;
|
|
559
|
+
if (eventType === 'heartbeat')
|
|
560
|
+
continue;
|
|
561
|
+
if (eventType === 'done') {
|
|
562
|
+
yield { type: 'done' };
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
try {
|
|
566
|
+
yield this.parseEvent(eventType, JSON.parse(dataStr));
|
|
567
|
+
}
|
|
568
|
+
catch {
|
|
569
|
+
// skip malformed JSON
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
parseEvent(eventType, data) {
|
|
575
|
+
switch (eventType) {
|
|
576
|
+
case 'text':
|
|
577
|
+
case 'text_delta':
|
|
578
|
+
return { type: 'text', data: { text: data.text ?? '' } };
|
|
579
|
+
case 'tool_start':
|
|
580
|
+
return { type: 'tool_start', data: { tool: data.tool, title: data.title, args: data.args } };
|
|
581
|
+
case 'tool_end':
|
|
582
|
+
return { type: 'tool_end', data: { tool: data.tool, result: data.result_summary, latency_ms: data.latency_ms } };
|
|
583
|
+
case 'hitl_request':
|
|
584
|
+
return {
|
|
585
|
+
type: 'hitl_request',
|
|
586
|
+
data: {
|
|
587
|
+
hitl_id: data.hitl_id,
|
|
588
|
+
hitl_type: data.type,
|
|
589
|
+
prompt: data.prompt,
|
|
590
|
+
form_data: data.form_data,
|
|
591
|
+
options: data.options,
|
|
592
|
+
auth_url: data.form_data?.authUrl
|
|
593
|
+
?? data.form_data?.auth_url,
|
|
594
|
+
},
|
|
595
|
+
};
|
|
596
|
+
case 'error':
|
|
597
|
+
return { type: 'error', data: { message: data.message, recoverable: data.recoverable } };
|
|
598
|
+
default:
|
|
599
|
+
return { type: 'text', data: { text: '' } };
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
// --- Error classification ---
|
|
603
|
+
async extractMessage(response) {
|
|
604
|
+
try {
|
|
605
|
+
const body = (await response.json());
|
|
606
|
+
return body?.message || '';
|
|
607
|
+
}
|
|
608
|
+
catch {
|
|
609
|
+
return '';
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
buildError(status, msg) {
|
|
613
|
+
if (status === 401) {
|
|
614
|
+
if (/revoked|disabled/i.test(msg) && !/invalid/i.test(msg)) {
|
|
615
|
+
return new KarisApiError('API key has been disabled. Enable it at https://karis.im/settings/api-keys', 'KEY_DISABLED', status, EXIT_AUTH);
|
|
616
|
+
}
|
|
617
|
+
if (/\bexpired\b/i.test(msg) && !/invalid/i.test(msg)) {
|
|
618
|
+
return new KarisApiError('API key has expired. Create a new key at https://karis.im/settings/api-keys', 'KEY_EXPIRED', status, EXIT_AUTH);
|
|
619
|
+
}
|
|
620
|
+
return new KarisApiError('Invalid API key. Check your key or create a new one at https://karis.im/settings/api-keys', 'INVALID_KEY', status, EXIT_AUTH);
|
|
621
|
+
}
|
|
622
|
+
if (status === 403) {
|
|
623
|
+
if (/insufficient.?credits/i.test(msg)) {
|
|
624
|
+
return new KarisApiError('Insufficient credits. Your API key has reached its credit limit.', 'INSUFFICIENT_CREDITS', status, EXIT_AUTH);
|
|
625
|
+
}
|
|
626
|
+
const missingScope = msg.match(/required scope:\s*([a-z0-9:_-]+)/i)?.[1];
|
|
627
|
+
if (missingScope) {
|
|
628
|
+
return new KarisApiError(`API key is missing required scope: ${missingScope}`, 'MISSING_SCOPE', status, EXIT_AUTH);
|
|
629
|
+
}
|
|
630
|
+
return new KarisApiError(`Access denied: ${msg || 'insufficient permissions'}`, 'ACCESS_DENIED', status, EXIT_AUTH);
|
|
631
|
+
}
|
|
632
|
+
return new KarisApiError(`API error ${status}: ${msg || 'unknown'}`, 'API_ERROR', status, EXIT_RUNTIME);
|
|
633
|
+
}
|
|
634
|
+
isTimeoutError(error) {
|
|
635
|
+
return error instanceof Error && (error.name === 'TimeoutError' || error.name === 'AbortError');
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
//# sourceMappingURL=client.js.map
|