openhome-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 +470 -0
- package/bin/openhome.js +2 -0
- package/dist/chunk-Q4UKUXDB.js +164 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +3184 -0
- package/dist/store-DR7EKQ5T.js +16 -0
- package/package.json +44 -0
- package/src/api/client.ts +231 -0
- package/src/api/contracts.ts +103 -0
- package/src/api/endpoints.ts +19 -0
- package/src/api/mock-client.ts +145 -0
- package/src/cli.ts +339 -0
- package/src/commands/agents.ts +88 -0
- package/src/commands/assign.ts +123 -0
- package/src/commands/chat.ts +265 -0
- package/src/commands/config-edit.ts +163 -0
- package/src/commands/delete.ts +107 -0
- package/src/commands/deploy.ts +430 -0
- package/src/commands/init.ts +895 -0
- package/src/commands/list.ts +78 -0
- package/src/commands/login.ts +54 -0
- package/src/commands/logout.ts +14 -0
- package/src/commands/logs.ts +174 -0
- package/src/commands/status.ts +174 -0
- package/src/commands/toggle.ts +118 -0
- package/src/commands/trigger.ts +193 -0
- package/src/commands/validate.ts +53 -0
- package/src/commands/whoami.ts +54 -0
- package/src/config/keychain.ts +62 -0
- package/src/config/store.ts +137 -0
- package/src/ui/format.ts +95 -0
- package/src/util/zip.ts +74 -0
- package/src/validation/rules.ts +71 -0
- package/src/validation/validator.ts +204 -0
- package/tasks/feature-request-sdk-api.md +246 -0
- package/tasks/prd-openhome-cli.md +605 -0
- package/templates/api/README.md.tmpl +11 -0
- package/templates/api/__init__.py.tmpl +0 -0
- package/templates/api/config.json.tmpl +4 -0
- package/templates/api/main.py.tmpl +30 -0
- package/templates/basic/README.md.tmpl +7 -0
- package/templates/basic/__init__.py.tmpl +0 -0
- package/templates/basic/config.json.tmpl +4 -0
- package/templates/basic/main.py.tmpl +22 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getApiKey,
|
|
3
|
+
getConfig,
|
|
4
|
+
getTrackedAbilities,
|
|
5
|
+
registerAbility,
|
|
6
|
+
saveApiKey,
|
|
7
|
+
saveConfig
|
|
8
|
+
} from "./chunk-Q4UKUXDB.js";
|
|
9
|
+
export {
|
|
10
|
+
getApiKey,
|
|
11
|
+
getConfig,
|
|
12
|
+
getTrackedAbilities,
|
|
13
|
+
registerAbility,
|
|
14
|
+
saveApiKey,
|
|
15
|
+
saveConfig
|
|
16
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openhome-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for managing OpenHome voice AI abilities",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"openhome": "./bin/openhome.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/cli.js",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsup src/cli.ts --format esm --dts --clean",
|
|
12
|
+
"dev": "tsx src/cli.ts",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"lint": "tsc --noEmit"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"openhome",
|
|
21
|
+
"voice",
|
|
22
|
+
"ai",
|
|
23
|
+
"cli",
|
|
24
|
+
"abilities"
|
|
25
|
+
],
|
|
26
|
+
"author": "Bradymck <mckmuze@gmail.com>",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@clack/prompts": "^1.1.0",
|
|
30
|
+
"archiver": "^7.0.1",
|
|
31
|
+
"chalk": "^5.3.0",
|
|
32
|
+
"commander": "^12.1.0",
|
|
33
|
+
"ws": "^8.19.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/archiver": "^6.0.3",
|
|
37
|
+
"@types/node": "^22.0.0",
|
|
38
|
+
"@types/ws": "^8.18.1",
|
|
39
|
+
"tsup": "^8.3.0",
|
|
40
|
+
"tsx": "^4.19.2",
|
|
41
|
+
"typescript": "^5.7.3",
|
|
42
|
+
"vitest": "^2.1.9"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
GetPersonalitiesResponse,
|
|
3
|
+
Personality,
|
|
4
|
+
UploadAbilityResponse,
|
|
5
|
+
UploadAbilityMetadata,
|
|
6
|
+
ListAbilitiesResponse,
|
|
7
|
+
GetAbilityResponse,
|
|
8
|
+
ApiErrorResponse,
|
|
9
|
+
VerifyApiKeyResponse,
|
|
10
|
+
DeleteCapabilityResponse,
|
|
11
|
+
ToggleCapabilityResponse,
|
|
12
|
+
AssignCapabilitiesResponse,
|
|
13
|
+
} from "./contracts.js";
|
|
14
|
+
import { API_BASE, ENDPOINTS } from "./endpoints.js";
|
|
15
|
+
|
|
16
|
+
export class NotImplementedError extends Error {
|
|
17
|
+
constructor(endpoint: string) {
|
|
18
|
+
super(`API endpoint not yet implemented: ${endpoint}`);
|
|
19
|
+
this.name = "NotImplementedError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class ApiError extends Error {
|
|
24
|
+
constructor(
|
|
25
|
+
public readonly code: string,
|
|
26
|
+
message: string,
|
|
27
|
+
public readonly details?: Record<string, unknown>,
|
|
28
|
+
) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = "ApiError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface IApiClient {
|
|
35
|
+
getPersonalities(): Promise<Personality[]>;
|
|
36
|
+
verifyApiKey(apiKey: string): Promise<VerifyApiKeyResponse>;
|
|
37
|
+
uploadAbility(
|
|
38
|
+
zipBuffer: Buffer,
|
|
39
|
+
imageBuffer: Buffer | null,
|
|
40
|
+
imageName: string | null,
|
|
41
|
+
metadata: UploadAbilityMetadata,
|
|
42
|
+
): Promise<UploadAbilityResponse>;
|
|
43
|
+
listAbilities(): Promise<ListAbilitiesResponse>;
|
|
44
|
+
getAbility(id: string): Promise<GetAbilityResponse>;
|
|
45
|
+
deleteCapability(id: string): Promise<DeleteCapabilityResponse>;
|
|
46
|
+
toggleCapability(
|
|
47
|
+
id: string,
|
|
48
|
+
enabled: boolean,
|
|
49
|
+
): Promise<ToggleCapabilityResponse>;
|
|
50
|
+
assignCapabilities(
|
|
51
|
+
personalityId: string,
|
|
52
|
+
capabilityIds: number[],
|
|
53
|
+
): Promise<AssignCapabilitiesResponse>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class ApiClient implements IApiClient {
|
|
57
|
+
private readonly baseUrl: string;
|
|
58
|
+
|
|
59
|
+
constructor(
|
|
60
|
+
private readonly apiKey: string,
|
|
61
|
+
baseUrl?: string,
|
|
62
|
+
) {
|
|
63
|
+
this.baseUrl = baseUrl ?? API_BASE;
|
|
64
|
+
if (!this.baseUrl.startsWith("https://")) {
|
|
65
|
+
throw new Error("API base URL must use HTTPS. Got: " + this.baseUrl);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private async request<T>(
|
|
70
|
+
path: string,
|
|
71
|
+
options: RequestInit = {},
|
|
72
|
+
): Promise<T> {
|
|
73
|
+
const url = `${this.baseUrl}${path}`;
|
|
74
|
+
const response = await fetch(url, {
|
|
75
|
+
...options,
|
|
76
|
+
headers: {
|
|
77
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
78
|
+
...(options.headers ?? {}),
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (!response.ok) {
|
|
83
|
+
let body: ApiErrorResponse | null = null;
|
|
84
|
+
try {
|
|
85
|
+
body = (await response.json()) as ApiErrorResponse;
|
|
86
|
+
} catch {
|
|
87
|
+
// ignore parse errors
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (body?.error?.code === "NOT_IMPLEMENTED" || response.status === 404) {
|
|
91
|
+
throw new NotImplementedError(path);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
throw new ApiError(
|
|
95
|
+
body?.error?.code ?? String(response.status),
|
|
96
|
+
body?.error?.message ?? response.statusText,
|
|
97
|
+
body?.error?.details,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return response.json() as Promise<T>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async getPersonalities(): Promise<Personality[]> {
|
|
105
|
+
const data = await this.request<GetPersonalitiesResponse>(
|
|
106
|
+
ENDPOINTS.getPersonalities,
|
|
107
|
+
{
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: { "Content-Type": "application/json" },
|
|
110
|
+
body: JSON.stringify({ api_key: this.apiKey, with_image: true }),
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
return data.personalities;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async uploadAbility(
|
|
117
|
+
zipBuffer: Buffer,
|
|
118
|
+
imageBuffer: Buffer | null,
|
|
119
|
+
imageName: string | null,
|
|
120
|
+
metadata: UploadAbilityMetadata,
|
|
121
|
+
): Promise<UploadAbilityResponse> {
|
|
122
|
+
const form = new FormData();
|
|
123
|
+
form.append(
|
|
124
|
+
"zip_file",
|
|
125
|
+
new Blob([zipBuffer as unknown as ArrayBuffer], {
|
|
126
|
+
type: "application/zip",
|
|
127
|
+
}),
|
|
128
|
+
"ability.zip",
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
if (imageBuffer && imageName) {
|
|
132
|
+
const imageExt = imageName.split(".").pop()?.toLowerCase() ?? "png";
|
|
133
|
+
const imageMime =
|
|
134
|
+
imageExt === "jpg" || imageExt === "jpeg" ? "image/jpeg" : "image/png";
|
|
135
|
+
form.append(
|
|
136
|
+
"image_file",
|
|
137
|
+
new Blob([imageBuffer as unknown as ArrayBuffer], { type: imageMime }),
|
|
138
|
+
imageName,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
form.append("name", metadata.name);
|
|
143
|
+
form.append("description", metadata.description);
|
|
144
|
+
form.append("category", metadata.category);
|
|
145
|
+
form.append("trigger_words", metadata.matching_hotwords.join(", "));
|
|
146
|
+
if (metadata.personality_id) {
|
|
147
|
+
form.append("personality_id", metadata.personality_id);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return this.request<UploadAbilityResponse>(ENDPOINTS.uploadCapability, {
|
|
151
|
+
method: "POST",
|
|
152
|
+
body: form,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async listAbilities(): Promise<ListAbilitiesResponse> {
|
|
157
|
+
return this.request<ListAbilitiesResponse>(ENDPOINTS.listCapabilities, {
|
|
158
|
+
method: "GET",
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async getAbility(id: string): Promise<GetAbilityResponse> {
|
|
163
|
+
return this.request<GetAbilityResponse>(ENDPOINTS.getCapability(id), {
|
|
164
|
+
method: "GET",
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async verifyApiKey(apiKey: string): Promise<VerifyApiKeyResponse> {
|
|
169
|
+
return this.request<VerifyApiKeyResponse>(ENDPOINTS.verifyApiKey, {
|
|
170
|
+
method: "POST",
|
|
171
|
+
headers: { "Content-Type": "application/json" },
|
|
172
|
+
body: JSON.stringify({ api_key: apiKey }),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async deleteCapability(id: string): Promise<DeleteCapabilityResponse> {
|
|
177
|
+
// Try the new bulk POST endpoint first (staging → production soon).
|
|
178
|
+
// Falls back to the legacy DELETE endpoint automatically if the new one
|
|
179
|
+
// isn't live yet (404 or NOT_IMPLEMENTED). Once the new endpoint is
|
|
180
|
+
// fully deployed, the fallback becomes unreachable dead code.
|
|
181
|
+
try {
|
|
182
|
+
return await this.request<DeleteCapabilityResponse>(
|
|
183
|
+
ENDPOINTS.bulkDeleteCapabilities,
|
|
184
|
+
{
|
|
185
|
+
method: "POST",
|
|
186
|
+
headers: { "Content-Type": "application/json" },
|
|
187
|
+
body: JSON.stringify({
|
|
188
|
+
ids: [Number.isNaN(Number(id)) ? id : Number(id)],
|
|
189
|
+
}),
|
|
190
|
+
},
|
|
191
|
+
);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
if (err instanceof NotImplementedError) {
|
|
194
|
+
// New endpoint not live yet — fall back to legacy DELETE
|
|
195
|
+
return this.request<DeleteCapabilityResponse>(
|
|
196
|
+
ENDPOINTS.deleteCapability(id),
|
|
197
|
+
{ method: "DELETE" },
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
throw err;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async toggleCapability(
|
|
205
|
+
id: string,
|
|
206
|
+
enabled: boolean,
|
|
207
|
+
): Promise<ToggleCapabilityResponse> {
|
|
208
|
+
return this.request<ToggleCapabilityResponse>(
|
|
209
|
+
ENDPOINTS.editInstalledCapability(id),
|
|
210
|
+
{
|
|
211
|
+
method: "POST",
|
|
212
|
+
headers: { "Content-Type": "application/json" },
|
|
213
|
+
body: JSON.stringify({ enabled }),
|
|
214
|
+
},
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async assignCapabilities(
|
|
219
|
+
personalityId: string,
|
|
220
|
+
capabilityIds: number[],
|
|
221
|
+
): Promise<AssignCapabilitiesResponse> {
|
|
222
|
+
return this.request<AssignCapabilitiesResponse>(ENDPOINTS.editPersonality, {
|
|
223
|
+
method: "POST",
|
|
224
|
+
headers: { "Content-Type": "application/json" },
|
|
225
|
+
body: JSON.stringify({
|
|
226
|
+
personality_id: personalityId,
|
|
227
|
+
matching_capabilities: capabilityIds,
|
|
228
|
+
}),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Existing endpoints
|
|
2
|
+
|
|
3
|
+
export interface GetPersonalitiesRequest {
|
|
4
|
+
api_key: string;
|
|
5
|
+
with_image?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface Personality {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
image?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface GetPersonalitiesResponse {
|
|
16
|
+
personalities: Personality[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Upload request metadata
|
|
20
|
+
export type AbilityCategory = "skill" | "brain" | "daemon";
|
|
21
|
+
|
|
22
|
+
export interface UploadAbilityMetadata {
|
|
23
|
+
name: string;
|
|
24
|
+
description: string;
|
|
25
|
+
category: AbilityCategory;
|
|
26
|
+
matching_hotwords: string[];
|
|
27
|
+
personality_id?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface UploadAbilityResponse {
|
|
31
|
+
ability_id: string;
|
|
32
|
+
unique_name: string;
|
|
33
|
+
version: number;
|
|
34
|
+
status: "processing" | "active" | "failed";
|
|
35
|
+
validation_errors: string[];
|
|
36
|
+
created_at: string;
|
|
37
|
+
message: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface AbilitySummary {
|
|
41
|
+
ability_id: string;
|
|
42
|
+
unique_name: string;
|
|
43
|
+
display_name: string;
|
|
44
|
+
version: number;
|
|
45
|
+
status: "processing" | "active" | "failed" | "disabled";
|
|
46
|
+
personality_ids: string[];
|
|
47
|
+
created_at: string;
|
|
48
|
+
updated_at: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ListAbilitiesResponse {
|
|
52
|
+
abilities: AbilitySummary[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface GetAbilityResponse extends AbilitySummary {
|
|
56
|
+
validation_errors: string[];
|
|
57
|
+
deploy_history: DeployEvent[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface DeployEvent {
|
|
61
|
+
version: number;
|
|
62
|
+
status: "success" | "failed";
|
|
63
|
+
timestamp: string;
|
|
64
|
+
message: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ApiErrorResponse {
|
|
68
|
+
error: {
|
|
69
|
+
code:
|
|
70
|
+
| "UNAUTHORIZED"
|
|
71
|
+
| "VALIDATION_FAILED"
|
|
72
|
+
| "NOT_FOUND"
|
|
73
|
+
| "NOT_IMPLEMENTED";
|
|
74
|
+
message: string;
|
|
75
|
+
details?: Record<string, unknown>;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Verify API key
|
|
80
|
+
export interface VerifyApiKeyResponse {
|
|
81
|
+
valid: boolean;
|
|
82
|
+
message?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Delete capability
|
|
86
|
+
export interface DeleteCapabilityResponse {
|
|
87
|
+
message?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Toggle capability enabled/disabled
|
|
91
|
+
export interface ToggleCapabilityResponse {
|
|
92
|
+
enabled?: boolean;
|
|
93
|
+
message?: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Assign capabilities to a personality
|
|
97
|
+
export interface AssignCapabilitiesRequest {
|
|
98
|
+
matching_capabilities: number[];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface AssignCapabilitiesResponse {
|
|
102
|
+
message?: string;
|
|
103
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const API_BASE = "https://app.openhome.com";
|
|
2
|
+
|
|
3
|
+
export const WS_BASE = "wss://app.openhome.com";
|
|
4
|
+
|
|
5
|
+
export const ENDPOINTS = {
|
|
6
|
+
getPersonalities: "/api/sdk/get_personalities",
|
|
7
|
+
verifyApiKey: "/api/sdk/verify_apikey/",
|
|
8
|
+
uploadCapability: "/api/capabilities/add-capability/",
|
|
9
|
+
listCapabilities: "/api/capabilities/get-all-capability/",
|
|
10
|
+
getCapability: (id: string) => `/api/capabilities/get-capability/${id}/`,
|
|
11
|
+
deleteCapability: (id: string) =>
|
|
12
|
+
`/api/capabilities/delete-capability/${id}/`,
|
|
13
|
+
bulkDeleteCapabilities: "/api/capabilities/delete-capability/",
|
|
14
|
+
editInstalledCapability: (id: string) =>
|
|
15
|
+
`/api/capabilities/edit-installed-capability/${id}/`,
|
|
16
|
+
editPersonality: "/api/personalities/edit-personality/",
|
|
17
|
+
voiceStream: (apiKey: string, agentId: string) =>
|
|
18
|
+
`/websocket/voice-stream/${apiKey}/${agentId}`,
|
|
19
|
+
} as const;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Personality,
|
|
3
|
+
UploadAbilityResponse,
|
|
4
|
+
UploadAbilityMetadata,
|
|
5
|
+
ListAbilitiesResponse,
|
|
6
|
+
GetAbilityResponse,
|
|
7
|
+
VerifyApiKeyResponse,
|
|
8
|
+
DeleteCapabilityResponse,
|
|
9
|
+
ToggleCapabilityResponse,
|
|
10
|
+
AssignCapabilitiesResponse,
|
|
11
|
+
} from "./contracts.js";
|
|
12
|
+
import type { IApiClient } from "./client.js";
|
|
13
|
+
|
|
14
|
+
const MOCK_PERSONALITIES: Personality[] = [
|
|
15
|
+
{ id: "pers_alice", name: "Alice", description: "Friendly assistant" },
|
|
16
|
+
{ id: "pers_bob", name: "Bob", description: "Technical expert" },
|
|
17
|
+
{ id: "pers_cara", name: "Cara", description: "Creative companion" },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const MOCK_ABILITIES = [
|
|
21
|
+
{
|
|
22
|
+
ability_id: "abl_weather_001",
|
|
23
|
+
unique_name: "weather-check",
|
|
24
|
+
display_name: "Weather Check",
|
|
25
|
+
version: 3,
|
|
26
|
+
status: "active" as const,
|
|
27
|
+
personality_ids: ["pers_alice", "pers_bob"],
|
|
28
|
+
created_at: "2026-01-10T12:00:00Z",
|
|
29
|
+
updated_at: "2026-03-01T09:30:00Z",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
ability_id: "abl_timer_002",
|
|
33
|
+
unique_name: "pomodoro-timer",
|
|
34
|
+
display_name: "Pomodoro Timer",
|
|
35
|
+
version: 1,
|
|
36
|
+
status: "processing" as const,
|
|
37
|
+
personality_ids: ["pers_cara"],
|
|
38
|
+
created_at: "2026-03-18T08:00:00Z",
|
|
39
|
+
updated_at: "2026-03-18T08:05:00Z",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
ability_id: "abl_news_003",
|
|
43
|
+
unique_name: "news-briefing",
|
|
44
|
+
display_name: "News Briefing",
|
|
45
|
+
version: 2,
|
|
46
|
+
status: "failed" as const,
|
|
47
|
+
personality_ids: [],
|
|
48
|
+
created_at: "2026-02-20T14:00:00Z",
|
|
49
|
+
updated_at: "2026-02-21T10:00:00Z",
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
export class MockApiClient implements IApiClient {
|
|
54
|
+
async getPersonalities(): Promise<Personality[]> {
|
|
55
|
+
return Promise.resolve(MOCK_PERSONALITIES);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async uploadAbility(
|
|
59
|
+
_zipBuffer: Buffer,
|
|
60
|
+
_imageBuffer: Buffer | null,
|
|
61
|
+
_imageName: string | null,
|
|
62
|
+
_metadata: UploadAbilityMetadata,
|
|
63
|
+
): Promise<UploadAbilityResponse> {
|
|
64
|
+
return Promise.resolve({
|
|
65
|
+
ability_id: `abl_mock_${Date.now()}`,
|
|
66
|
+
unique_name: "mock-ability",
|
|
67
|
+
version: 1,
|
|
68
|
+
status: "processing",
|
|
69
|
+
validation_errors: [],
|
|
70
|
+
created_at: new Date().toISOString(),
|
|
71
|
+
message: "[MOCK] Ability uploaded successfully and is being processed.",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async listAbilities(): Promise<ListAbilitiesResponse> {
|
|
76
|
+
return Promise.resolve({ abilities: MOCK_ABILITIES });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async verifyApiKey(_apiKey: string): Promise<VerifyApiKeyResponse> {
|
|
80
|
+
return Promise.resolve({
|
|
81
|
+
valid: true,
|
|
82
|
+
message: "[MOCK] API key is valid.",
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async deleteCapability(id: string): Promise<DeleteCapabilityResponse> {
|
|
87
|
+
return Promise.resolve({
|
|
88
|
+
message: `[MOCK] Capability ${id} deleted successfully.`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async toggleCapability(
|
|
93
|
+
id: string,
|
|
94
|
+
enabled: boolean,
|
|
95
|
+
): Promise<ToggleCapabilityResponse> {
|
|
96
|
+
return Promise.resolve({
|
|
97
|
+
enabled,
|
|
98
|
+
message: `[MOCK] Capability ${id} ${enabled ? "enabled" : "disabled"}.`,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async assignCapabilities(
|
|
103
|
+
personalityId: string,
|
|
104
|
+
capabilityIds: number[],
|
|
105
|
+
): Promise<AssignCapabilitiesResponse> {
|
|
106
|
+
return Promise.resolve({
|
|
107
|
+
message: `[MOCK] Agent ${personalityId} updated with ${capabilityIds.length} capability(s).`,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async getAbility(id: string): Promise<GetAbilityResponse> {
|
|
112
|
+
const found = MOCK_ABILITIES.find(
|
|
113
|
+
(a) => a.ability_id === id || a.unique_name === id,
|
|
114
|
+
);
|
|
115
|
+
const base = found ?? MOCK_ABILITIES[0];
|
|
116
|
+
return Promise.resolve({
|
|
117
|
+
...base,
|
|
118
|
+
validation_errors:
|
|
119
|
+
base.status === "failed"
|
|
120
|
+
? ["Missing resume_normal_flow() call in main.py"]
|
|
121
|
+
: [],
|
|
122
|
+
deploy_history: [
|
|
123
|
+
{
|
|
124
|
+
version: base.version,
|
|
125
|
+
status: base.status === "active" ? "success" : "failed",
|
|
126
|
+
timestamp: base.updated_at,
|
|
127
|
+
message:
|
|
128
|
+
base.status === "active"
|
|
129
|
+
? "Deployed successfully"
|
|
130
|
+
: "Validation failed",
|
|
131
|
+
},
|
|
132
|
+
...(base.version > 1
|
|
133
|
+
? [
|
|
134
|
+
{
|
|
135
|
+
version: base.version - 1,
|
|
136
|
+
status: "success" as const,
|
|
137
|
+
timestamp: base.created_at,
|
|
138
|
+
message: "Deployed successfully",
|
|
139
|
+
},
|
|
140
|
+
]
|
|
141
|
+
: []),
|
|
142
|
+
],
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|