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
package/src/cli.ts
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
|
|
6
|
+
import { loginCommand } from "./commands/login.js";
|
|
7
|
+
import { initCommand } from "./commands/init.js";
|
|
8
|
+
import { deployCommand } from "./commands/deploy.js";
|
|
9
|
+
import { deleteCommand } from "./commands/delete.js";
|
|
10
|
+
import { toggleCommand } from "./commands/toggle.js";
|
|
11
|
+
import { assignCommand } from "./commands/assign.js";
|
|
12
|
+
import { listCommand } from "./commands/list.js";
|
|
13
|
+
import { statusCommand } from "./commands/status.js";
|
|
14
|
+
import { agentsCommand } from "./commands/agents.js";
|
|
15
|
+
import { logoutCommand } from "./commands/logout.js";
|
|
16
|
+
import { chatCommand } from "./commands/chat.js";
|
|
17
|
+
import { triggerCommand } from "./commands/trigger.js";
|
|
18
|
+
import { whoamiCommand } from "./commands/whoami.js";
|
|
19
|
+
import { configEditCommand } from "./commands/config-edit.js";
|
|
20
|
+
import { logsCommand } from "./commands/logs.js";
|
|
21
|
+
import { p, handleCancel } from "./ui/format.js";
|
|
22
|
+
|
|
23
|
+
// Read version from package.json
|
|
24
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
25
|
+
const __dirname = dirname(__filename);
|
|
26
|
+
let version = "0.1.0";
|
|
27
|
+
try {
|
|
28
|
+
const pkg = JSON.parse(
|
|
29
|
+
readFileSync(join(__dirname, "..", "package.json"), "utf8"),
|
|
30
|
+
) as { version?: string };
|
|
31
|
+
version = pkg.version ?? version;
|
|
32
|
+
} catch {
|
|
33
|
+
// fallback to default
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ── Interactive menu (bare `openhome` with no args) ──────────────
|
|
37
|
+
|
|
38
|
+
async function ensureLoggedIn(): Promise<void> {
|
|
39
|
+
const { getApiKey } = await import("./config/store.js");
|
|
40
|
+
const key = getApiKey();
|
|
41
|
+
if (!key) {
|
|
42
|
+
await loginCommand();
|
|
43
|
+
console.log("");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function interactiveMenu(): Promise<void> {
|
|
48
|
+
p.intro(`🏠 OpenHome CLI v${version}`);
|
|
49
|
+
|
|
50
|
+
// Login first if not authenticated
|
|
51
|
+
await ensureLoggedIn();
|
|
52
|
+
|
|
53
|
+
let running = true;
|
|
54
|
+
while (running) {
|
|
55
|
+
const choice = await p.select({
|
|
56
|
+
message: "What would you like to do?",
|
|
57
|
+
options: [
|
|
58
|
+
{
|
|
59
|
+
value: "init",
|
|
60
|
+
label: "✨ Create Ability",
|
|
61
|
+
hint: "Scaffold a new ability from templates",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
value: "deploy",
|
|
65
|
+
label: "🚀 Deploy",
|
|
66
|
+
hint: "Upload ability to OpenHome",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
value: "chat",
|
|
70
|
+
label: "💬 Chat",
|
|
71
|
+
hint: "Talk to your agent",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
value: "trigger",
|
|
75
|
+
label: "⚡ Trigger",
|
|
76
|
+
hint: "Fire an ability remotely with a phrase",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
value: "list",
|
|
80
|
+
label: "📋 My Abilities",
|
|
81
|
+
hint: "List deployed abilities",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
value: "delete",
|
|
85
|
+
label: "🗑️ Delete Ability",
|
|
86
|
+
hint: "Remove a deployed ability",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
value: "toggle",
|
|
90
|
+
label: "⚡ Enable / Disable",
|
|
91
|
+
hint: "Toggle an ability on or off",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
value: "assign",
|
|
95
|
+
label: "🔗 Assign to Agent",
|
|
96
|
+
hint: "Link abilities to an agent",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
value: "agents",
|
|
100
|
+
label: "🤖 My Agents",
|
|
101
|
+
hint: "View agents and set default",
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
value: "status",
|
|
105
|
+
label: "🔍 Status",
|
|
106
|
+
hint: "Check ability status",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
value: "config",
|
|
110
|
+
label: "⚙️ Edit Config",
|
|
111
|
+
hint: "Update trigger words, description, category",
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
value: "logs",
|
|
115
|
+
label: "📡 Logs",
|
|
116
|
+
hint: "Stream live agent messages",
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
value: "whoami",
|
|
120
|
+
label: "👤 Who Am I",
|
|
121
|
+
hint: "Show auth, default agent, tracked abilities",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
value: "logout",
|
|
125
|
+
label: "🔓 Log Out",
|
|
126
|
+
hint: "Clear credentials and re-authenticate",
|
|
127
|
+
},
|
|
128
|
+
{ value: "exit", label: "👋 Exit", hint: "Quit" },
|
|
129
|
+
],
|
|
130
|
+
});
|
|
131
|
+
handleCancel(choice);
|
|
132
|
+
|
|
133
|
+
switch (choice) {
|
|
134
|
+
case "init":
|
|
135
|
+
await initCommand();
|
|
136
|
+
break;
|
|
137
|
+
case "deploy":
|
|
138
|
+
await deployCommand();
|
|
139
|
+
break;
|
|
140
|
+
case "chat":
|
|
141
|
+
await chatCommand();
|
|
142
|
+
break;
|
|
143
|
+
case "trigger":
|
|
144
|
+
await triggerCommand();
|
|
145
|
+
break;
|
|
146
|
+
case "list":
|
|
147
|
+
await listCommand();
|
|
148
|
+
break;
|
|
149
|
+
case "delete":
|
|
150
|
+
await deleteCommand();
|
|
151
|
+
break;
|
|
152
|
+
case "toggle":
|
|
153
|
+
await toggleCommand();
|
|
154
|
+
break;
|
|
155
|
+
case "assign":
|
|
156
|
+
await assignCommand();
|
|
157
|
+
break;
|
|
158
|
+
case "agents":
|
|
159
|
+
await agentsCommand();
|
|
160
|
+
break;
|
|
161
|
+
case "status":
|
|
162
|
+
await statusCommand();
|
|
163
|
+
break;
|
|
164
|
+
case "config":
|
|
165
|
+
await configEditCommand();
|
|
166
|
+
break;
|
|
167
|
+
case "logs":
|
|
168
|
+
await logsCommand();
|
|
169
|
+
break;
|
|
170
|
+
case "whoami":
|
|
171
|
+
await whoamiCommand();
|
|
172
|
+
break;
|
|
173
|
+
case "logout":
|
|
174
|
+
await logoutCommand();
|
|
175
|
+
await ensureLoggedIn();
|
|
176
|
+
break;
|
|
177
|
+
case "exit":
|
|
178
|
+
running = false;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (running) {
|
|
183
|
+
console.log(""); // spacing between commands
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
p.outro("See you next time!");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── Commander subcommands (direct usage) ─────────────────────────
|
|
191
|
+
|
|
192
|
+
const program = new Command();
|
|
193
|
+
|
|
194
|
+
program
|
|
195
|
+
.name("openhome")
|
|
196
|
+
.description("OpenHome CLI — manage abilities from your terminal")
|
|
197
|
+
.version(version, "-v, --version", "Output the current version");
|
|
198
|
+
|
|
199
|
+
program
|
|
200
|
+
.command("login")
|
|
201
|
+
.description("Authenticate with your OpenHome API key")
|
|
202
|
+
.action(async () => {
|
|
203
|
+
await loginCommand();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
program
|
|
207
|
+
.command("logout")
|
|
208
|
+
.description("Log out and clear stored credentials")
|
|
209
|
+
.action(async () => {
|
|
210
|
+
await logoutCommand();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
program
|
|
214
|
+
.command("init [name]")
|
|
215
|
+
.description("Scaffold a new ability from templates")
|
|
216
|
+
.action(async (name?: string) => {
|
|
217
|
+
await initCommand(name);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
program
|
|
221
|
+
.command("deploy [path]")
|
|
222
|
+
.description("Validate and deploy an ability to OpenHome")
|
|
223
|
+
.option("--dry-run", "Show what would be deployed without sending")
|
|
224
|
+
.option("--mock", "Use mock API client (no real network calls)")
|
|
225
|
+
.option("--personality <id>", "Agent ID to attach the ability to")
|
|
226
|
+
.action(
|
|
227
|
+
async (
|
|
228
|
+
path: string | undefined,
|
|
229
|
+
opts: { dryRun?: boolean; mock?: boolean; personality?: string },
|
|
230
|
+
) => {
|
|
231
|
+
await deployCommand(path, opts);
|
|
232
|
+
},
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
program
|
|
236
|
+
.command("chat [agent]")
|
|
237
|
+
.description("Chat with an agent via WebSocket")
|
|
238
|
+
.action(async (agent?: string) => {
|
|
239
|
+
await chatCommand(agent);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
program
|
|
243
|
+
.command("trigger [phrase]")
|
|
244
|
+
.description("Send a trigger phrase to fire an ability remotely")
|
|
245
|
+
.option("--agent <id>", "Agent ID (uses default if not set)")
|
|
246
|
+
.action(async (phrase?: string, opts?: { agent?: string }) => {
|
|
247
|
+
await triggerCommand(phrase, opts);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
program
|
|
251
|
+
.command("list")
|
|
252
|
+
.description("List all deployed abilities")
|
|
253
|
+
.option("--mock", "Use mock API client")
|
|
254
|
+
.action(async (opts: { mock?: boolean }) => {
|
|
255
|
+
await listCommand(opts);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
program
|
|
259
|
+
.command("delete [ability]")
|
|
260
|
+
.description("Delete a deployed ability")
|
|
261
|
+
.option("--mock", "Use mock API client")
|
|
262
|
+
.action(async (ability: string | undefined, opts: { mock?: boolean }) => {
|
|
263
|
+
await deleteCommand(ability, opts);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
program
|
|
267
|
+
.command("toggle [ability]")
|
|
268
|
+
.description("Enable or disable a deployed ability")
|
|
269
|
+
.option("--enable", "Enable the ability")
|
|
270
|
+
.option("--disable", "Disable the ability")
|
|
271
|
+
.option("--mock", "Use mock API client")
|
|
272
|
+
.action(
|
|
273
|
+
async (
|
|
274
|
+
ability: string | undefined,
|
|
275
|
+
opts: { mock?: boolean; enable?: boolean; disable?: boolean },
|
|
276
|
+
) => {
|
|
277
|
+
await toggleCommand(ability, opts);
|
|
278
|
+
},
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
program
|
|
282
|
+
.command("assign")
|
|
283
|
+
.description("Assign abilities to an agent")
|
|
284
|
+
.option("--mock", "Use mock API client")
|
|
285
|
+
.action(async (opts: { mock?: boolean }) => {
|
|
286
|
+
await assignCommand(opts);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
program
|
|
290
|
+
.command("agents")
|
|
291
|
+
.description("View your agents and set a default")
|
|
292
|
+
.option("--mock", "Use mock API client")
|
|
293
|
+
.action(async (opts: { mock?: boolean }) => {
|
|
294
|
+
await agentsCommand(opts);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
program
|
|
298
|
+
.command("status [ability]")
|
|
299
|
+
.description("Show detailed status of an ability")
|
|
300
|
+
.option("--mock", "Use mock API client")
|
|
301
|
+
.action(async (ability: string | undefined, opts: { mock?: boolean }) => {
|
|
302
|
+
await statusCommand(ability, opts);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
program
|
|
306
|
+
.command("config [path]")
|
|
307
|
+
.description("Edit trigger words, description, or category in config.json")
|
|
308
|
+
.action(async (path?: string) => {
|
|
309
|
+
await configEditCommand(path);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
program
|
|
313
|
+
.command("logs")
|
|
314
|
+
.description("Stream live agent messages and logs")
|
|
315
|
+
.option("--agent <id>", "Agent ID (uses default if not set)")
|
|
316
|
+
.action(async (opts: { agent?: string }) => {
|
|
317
|
+
await logsCommand(opts);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
program
|
|
321
|
+
.command("whoami")
|
|
322
|
+
.description("Show auth status, default agent, and tracked abilities")
|
|
323
|
+
.action(async () => {
|
|
324
|
+
await whoamiCommand();
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// ── Entry point: menu if no args, subcommand otherwise ───────────
|
|
328
|
+
|
|
329
|
+
if (process.argv.length <= 2) {
|
|
330
|
+
interactiveMenu().catch((err: unknown) => {
|
|
331
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
332
|
+
process.exit(1);
|
|
333
|
+
});
|
|
334
|
+
} else {
|
|
335
|
+
program.parseAsync(process.argv).catch((err: unknown) => {
|
|
336
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
337
|
+
process.exit(1);
|
|
338
|
+
});
|
|
339
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { ApiClient, NotImplementedError } from "../api/client.js";
|
|
2
|
+
import { MockApiClient } from "../api/mock-client.js";
|
|
3
|
+
import { getApiKey, getConfig, saveConfig } from "../config/store.js";
|
|
4
|
+
import { error, success, info, p, handleCancel } from "../ui/format.js";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
|
|
7
|
+
export async function agentsCommand(
|
|
8
|
+
opts: { mock?: boolean } = {},
|
|
9
|
+
): Promise<void> {
|
|
10
|
+
p.intro("🤖 Your Agents");
|
|
11
|
+
|
|
12
|
+
let client: ApiClient | MockApiClient;
|
|
13
|
+
|
|
14
|
+
if (opts.mock) {
|
|
15
|
+
client = new MockApiClient();
|
|
16
|
+
} else {
|
|
17
|
+
const apiKey = getApiKey();
|
|
18
|
+
if (!apiKey) {
|
|
19
|
+
error("Not authenticated. Run: openhome login");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
client = new ApiClient(apiKey, getConfig().api_base_url);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const s = p.spinner();
|
|
26
|
+
s.start("Fetching agents...");
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const personalities = await client.getPersonalities();
|
|
30
|
+
s.stop(`Found ${personalities.length} agent(s).`);
|
|
31
|
+
|
|
32
|
+
if (personalities.length === 0) {
|
|
33
|
+
info("No agents found. Create one at https://app.openhome.com");
|
|
34
|
+
p.outro("Done.");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
p.note(
|
|
39
|
+
personalities
|
|
40
|
+
.map((pers) => `${chalk.bold(pers.name)} ${chalk.gray(pers.id)}`)
|
|
41
|
+
.join("\n"),
|
|
42
|
+
"Agents",
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const config = getConfig();
|
|
46
|
+
const currentDefault = config.default_personality_id;
|
|
47
|
+
|
|
48
|
+
if (currentDefault) {
|
|
49
|
+
const match = personalities.find((p) => p.id === currentDefault);
|
|
50
|
+
info(`Default agent: ${match ? match.name : currentDefault}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const setDefault = await p.confirm({
|
|
54
|
+
message: "Set or change your default agent?",
|
|
55
|
+
});
|
|
56
|
+
handleCancel(setDefault);
|
|
57
|
+
|
|
58
|
+
if (setDefault) {
|
|
59
|
+
const selected = await p.select({
|
|
60
|
+
message: "Choose default agent",
|
|
61
|
+
options: personalities.map((pers) => ({
|
|
62
|
+
value: pers.id,
|
|
63
|
+
label: pers.name,
|
|
64
|
+
hint: pers.id,
|
|
65
|
+
})),
|
|
66
|
+
});
|
|
67
|
+
handleCancel(selected);
|
|
68
|
+
|
|
69
|
+
config.default_personality_id = selected as string;
|
|
70
|
+
saveConfig(config);
|
|
71
|
+
success(`Default agent set: ${String(selected)}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
p.outro("Done.");
|
|
75
|
+
} catch (err) {
|
|
76
|
+
s.stop("Failed.");
|
|
77
|
+
|
|
78
|
+
if (err instanceof NotImplementedError) {
|
|
79
|
+
p.note("Use --mock to see example output.", "API Not Available Yet");
|
|
80
|
+
p.outro("Agents endpoint not yet implemented.");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
error(
|
|
84
|
+
`Failed to fetch agents: ${err instanceof Error ? err.message : String(err)}`,
|
|
85
|
+
);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { ApiClient, NotImplementedError } from "../api/client.js";
|
|
2
|
+
import { MockApiClient } from "../api/mock-client.js";
|
|
3
|
+
import { getApiKey, getConfig } from "../config/store.js";
|
|
4
|
+
import { error, success, info, p, handleCancel } from "../ui/format.js";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
|
|
7
|
+
export async function assignCommand(
|
|
8
|
+
opts: { mock?: boolean } = {},
|
|
9
|
+
): Promise<void> {
|
|
10
|
+
p.intro("🔗 Assign abilities to agent");
|
|
11
|
+
|
|
12
|
+
let client: ApiClient | MockApiClient;
|
|
13
|
+
|
|
14
|
+
if (opts.mock) {
|
|
15
|
+
client = new MockApiClient();
|
|
16
|
+
} else {
|
|
17
|
+
const apiKey = getApiKey();
|
|
18
|
+
if (!apiKey) {
|
|
19
|
+
error("Not authenticated. Run: openhome login");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
client = new ApiClient(apiKey, getConfig().api_base_url);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const s = p.spinner();
|
|
26
|
+
s.start("Fetching agents and abilities...");
|
|
27
|
+
|
|
28
|
+
let personalities: Awaited<ReturnType<typeof client.getPersonalities>>;
|
|
29
|
+
let abilities: Awaited<ReturnType<typeof client.listAbilities>>["abilities"];
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
[personalities, { abilities }] = await Promise.all([
|
|
33
|
+
client.getPersonalities(),
|
|
34
|
+
client.listAbilities(),
|
|
35
|
+
]);
|
|
36
|
+
s.stop(
|
|
37
|
+
`Found ${personalities.length} agent(s), ${abilities.length} ability(s).`,
|
|
38
|
+
);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
s.stop("Failed to fetch data.");
|
|
41
|
+
error(err instanceof Error ? err.message : String(err));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (personalities.length === 0) {
|
|
46
|
+
p.outro("No agents found. Create one at https://app.openhome.com");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (abilities.length === 0) {
|
|
51
|
+
p.outro("No abilities found. Run: openhome deploy");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Pick agent
|
|
56
|
+
const agentId = await p.select({
|
|
57
|
+
message: "Which agent do you want to update?",
|
|
58
|
+
options: personalities.map((pers) => ({
|
|
59
|
+
value: pers.id,
|
|
60
|
+
label: pers.name,
|
|
61
|
+
hint: chalk.gray(pers.id),
|
|
62
|
+
})),
|
|
63
|
+
});
|
|
64
|
+
handleCancel(agentId);
|
|
65
|
+
|
|
66
|
+
const agentName =
|
|
67
|
+
personalities.find((p) => p.id === agentId)?.name ?? String(agentId);
|
|
68
|
+
|
|
69
|
+
// Show current assignments and let user pick which abilities to assign
|
|
70
|
+
info(
|
|
71
|
+
`Select abilities to assign to "${agentName}". Deselecting all unassigns everything.`,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const selectedIds = await p.multiselect({
|
|
75
|
+
message: `Abilities for "${agentName}"`,
|
|
76
|
+
options: abilities.map((a) => ({
|
|
77
|
+
value: a.ability_id,
|
|
78
|
+
label: a.unique_name,
|
|
79
|
+
hint: `${a.status} v${a.version}`,
|
|
80
|
+
})),
|
|
81
|
+
required: false,
|
|
82
|
+
});
|
|
83
|
+
handleCancel(selectedIds);
|
|
84
|
+
|
|
85
|
+
const chosenIds = selectedIds as string[];
|
|
86
|
+
|
|
87
|
+
// Convert ability_ids to numbers for the API payload
|
|
88
|
+
// The API expects numeric IDs; if the real API returns string IDs we send them as-is
|
|
89
|
+
const numericIds = chosenIds
|
|
90
|
+
.map((id) => Number(id))
|
|
91
|
+
.filter((id) => !Number.isNaN(id));
|
|
92
|
+
|
|
93
|
+
// If any ID couldn't be parsed as a number, fall back to the raw list
|
|
94
|
+
// (lets the server validate — better than silently dropping)
|
|
95
|
+
const capabilityIds =
|
|
96
|
+
numericIds.length === chosenIds.length
|
|
97
|
+
? numericIds
|
|
98
|
+
: (chosenIds as unknown as number[]);
|
|
99
|
+
|
|
100
|
+
s.start(`Assigning ${chosenIds.length} ability(s) to "${agentName}"...`);
|
|
101
|
+
try {
|
|
102
|
+
const result = await client.assignCapabilities(
|
|
103
|
+
agentId as string,
|
|
104
|
+
capabilityIds,
|
|
105
|
+
);
|
|
106
|
+
s.stop("Done.");
|
|
107
|
+
success(
|
|
108
|
+
result.message ??
|
|
109
|
+
`"${agentName}" updated with ${chosenIds.length} ability(s).`,
|
|
110
|
+
);
|
|
111
|
+
p.outro("Done.");
|
|
112
|
+
} catch (err) {
|
|
113
|
+
s.stop("Failed.");
|
|
114
|
+
|
|
115
|
+
if (err instanceof NotImplementedError) {
|
|
116
|
+
p.note("Assign endpoint not yet implemented.", "API Not Available Yet");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
error(`Assign failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|