openhome-cli 0.1.2 → 0.1.4
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 +184 -171
- package/dist/cli.js +184 -78
- package/package.json +1 -1
- package/src/cli.ts +13 -37
- package/src/commands/login.ts +34 -3
- package/src/commands/set-jwt.ts +96 -22
- package/src/commands/status.ts +6 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Command-line tool for managing OpenHome voice AI abilities. Create and deploy abilities without leaving your terminal.
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**Version:** v0.1.2
|
|
6
6
|
**Node:** 18+
|
|
7
7
|
**Platform:** macOS (primary), Linux/Windows (config-file fallback for keychain)
|
|
8
8
|
|
|
@@ -11,14 +11,11 @@ Command-line tool for managing OpenHome voice AI abilities. Create and deploy ab
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
cd openhome-cli
|
|
17
|
-
npm install
|
|
18
|
-
npm run build
|
|
19
|
-
npm link
|
|
14
|
+
# Use directly without installing
|
|
15
|
+
npx openhome-cli
|
|
20
16
|
|
|
21
|
-
#
|
|
17
|
+
# Or install globally
|
|
18
|
+
npm install -g openhome-cli
|
|
22
19
|
openhome
|
|
23
20
|
```
|
|
24
21
|
|
|
@@ -49,21 +46,6 @@ Or just run `openhome` with no arguments for the interactive menu.
|
|
|
49
46
|
|
|
50
47
|
Opens an interactive menu. Use arrow keys to navigate, Enter to select. The menu loops after each command — pick another or choose Exit.
|
|
51
48
|
|
|
52
|
-
```
|
|
53
|
-
┌ OpenHome CLI v0.1.0
|
|
54
|
-
│
|
|
55
|
-
◆ What would you like to do?
|
|
56
|
-
│ ● Log Out
|
|
57
|
-
│ ○ Create Ability
|
|
58
|
-
│ ○ Deploy
|
|
59
|
-
│ ○ Chat
|
|
60
|
-
│ ○ My Abilities
|
|
61
|
-
│ ○ My Agents
|
|
62
|
-
│ ○ Status
|
|
63
|
-
│ ○ Exit
|
|
64
|
-
└
|
|
65
|
-
```
|
|
66
|
-
|
|
67
49
|
If you are not logged in, the CLI prompts for login before showing the menu.
|
|
68
50
|
|
|
69
51
|
All commands below also work directly from the terminal.
|
|
@@ -84,6 +66,20 @@ openhome login
|
|
|
84
66
|
|
|
85
67
|
---
|
|
86
68
|
|
|
69
|
+
### `openhome set-jwt [token]`
|
|
70
|
+
|
|
71
|
+
Save a session token to unlock management commands (`list`, `delete`, `toggle`, `assign`).
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
openhome set-jwt eyJ...
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
These management commands use OpenHome's web session API, which requires a JWT rather than the SDK API key. To get your token: open [app.openhome.com](https://app.openhome.com), open DevTools then Application then Local Storage then `token`, copy the value, and run `openhome set-jwt <token>`.
|
|
78
|
+
|
|
79
|
+
The token is saved to `~/.openhome/config.json`. You only need to do this once (until your session expires).
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
87
83
|
### `openhome init [name]`
|
|
88
84
|
|
|
89
85
|
Scaffold a new ability with all required files.
|
|
@@ -111,39 +107,7 @@ openhome init my-weather-bot
|
|
|
111
107
|
| `__init__.py` | Required by OpenHome (empty) |
|
|
112
108
|
| `README.md` | Description of your ability |
|
|
113
109
|
|
|
114
|
-
The generated code auto-validates after creation.
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
### `openhome logout`
|
|
119
|
-
|
|
120
|
-
Clear stored credentials and log out.
|
|
121
|
-
|
|
122
|
-
```bash
|
|
123
|
-
openhome logout
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
Removes the API key from macOS Keychain and clears the default agent from config. In the interactive menu, selecting Log Out immediately prompts you to log in again.
|
|
127
|
-
|
|
128
|
-
---
|
|
129
|
-
|
|
130
|
-
### `openhome chat [agent]`
|
|
131
|
-
|
|
132
|
-
Chat with an agent via WebSocket. Send text messages and trigger abilities with keywords.
|
|
133
|
-
|
|
134
|
-
```bash
|
|
135
|
-
# Pick an agent interactively
|
|
136
|
-
openhome chat
|
|
137
|
-
|
|
138
|
-
# Chat with a specific agent
|
|
139
|
-
openhome chat pers_abc123
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
Once connected, type messages and press Enter. The agent responds in real-time. Send trigger words (e.g. "play aquaprime") to activate abilities remotely.
|
|
143
|
-
|
|
144
|
-
Commands inside chat: `/quit`, `/exit`, or `/q` to disconnect. Ctrl+C also works.
|
|
145
|
-
|
|
146
|
-
> **Note:** Audio responses from the agent are not playable in the terminal. Text responses display normally.
|
|
110
|
+
The generated code auto-validates after creation. You're prompted to deploy immediately.
|
|
147
111
|
|
|
148
112
|
---
|
|
149
113
|
|
|
@@ -180,7 +144,7 @@ openhome deploy ./my-ability --personality pers_alice
|
|
|
180
144
|
3. Asks for confirmation
|
|
181
145
|
4. Uploads to OpenHome
|
|
182
146
|
|
|
183
|
-
> **Note:**
|
|
147
|
+
> **Note:** There is no update/overwrite endpoint yet. Re-deploying with the same name will fail with a naming conflict. Delete the old version first with `openhome delete`.
|
|
184
148
|
|
|
185
149
|
---
|
|
186
150
|
|
|
@@ -199,7 +163,64 @@ Shows a table with name, version, status, and last update date.
|
|
|
199
163
|
|
|
200
164
|
Status colors: green = active, yellow = processing, red = failed, gray = disabled.
|
|
201
165
|
|
|
202
|
-
> **
|
|
166
|
+
> **Requires session token.** Run `openhome set-jwt <token>` first. See [set-jwt](#openhome-set-jwt-token) above.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
### `openhome delete [ability]`
|
|
171
|
+
|
|
172
|
+
Delete a deployed ability.
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
# Pick from a list interactively
|
|
176
|
+
openhome delete
|
|
177
|
+
|
|
178
|
+
# Delete by name directly
|
|
179
|
+
openhome delete my-weather-bot
|
|
180
|
+
|
|
181
|
+
# Test with fake data
|
|
182
|
+
openhome delete --mock
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Prompts for confirmation before deleting.
|
|
186
|
+
|
|
187
|
+
> **Requires session token.** Run `openhome set-jwt <token>` first.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
### `openhome toggle [ability]`
|
|
192
|
+
|
|
193
|
+
Enable or disable a deployed ability.
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
# Interactive
|
|
197
|
+
openhome toggle
|
|
198
|
+
|
|
199
|
+
# By name with flag
|
|
200
|
+
openhome toggle my-weather-bot --enable
|
|
201
|
+
openhome toggle my-weather-bot --disable
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
| Flag | What it does |
|
|
205
|
+
|------|-------------|
|
|
206
|
+
| `--enable` | Enable the ability |
|
|
207
|
+
| `--disable` | Disable the ability |
|
|
208
|
+
|
|
209
|
+
> **Requires session token.** Run `openhome set-jwt <token>` first.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
### `openhome assign`
|
|
214
|
+
|
|
215
|
+
Assign abilities to an agent (multiselect).
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
openhome assign
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Fetches your agents and abilities, lets you pick an agent, then multiselect which abilities to assign to it.
|
|
222
|
+
|
|
223
|
+
> **Requires session token.** Run `openhome set-jwt <token>` first.
|
|
203
224
|
|
|
204
225
|
---
|
|
205
226
|
|
|
@@ -209,12 +230,40 @@ View your agents and set a default for deploys.
|
|
|
209
230
|
|
|
210
231
|
```bash
|
|
211
232
|
openhome agents
|
|
233
|
+
```
|
|
212
234
|
|
|
213
|
-
|
|
214
|
-
|
|
235
|
+
Shows all agents on your account with names and IDs. Optionally set or change your default agent (used by `deploy` when `--personality` is not specified).
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
### `openhome chat [agent]`
|
|
240
|
+
|
|
241
|
+
Chat with an agent via WebSocket. Send text messages and trigger abilities with keywords.
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
# Pick an agent interactively
|
|
245
|
+
openhome chat
|
|
246
|
+
|
|
247
|
+
# Chat with a specific agent
|
|
248
|
+
openhome chat pers_abc123
|
|
215
249
|
```
|
|
216
250
|
|
|
217
|
-
|
|
251
|
+
Once connected, type messages and press Enter. The agent responds in real-time.
|
|
252
|
+
|
|
253
|
+
Commands inside chat: `/quit`, `/exit`, or `/q` to disconnect. Ctrl+C also works.
|
|
254
|
+
|
|
255
|
+
> **Note:** Audio responses from the agent are not playable in the terminal. Text responses display normally.
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
### `openhome trigger [phrase]`
|
|
260
|
+
|
|
261
|
+
Send a trigger phrase to fire an ability remotely.
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
openhome trigger "play aquaprime"
|
|
265
|
+
openhome trigger --agent pers_abc123 "check weather"
|
|
266
|
+
```
|
|
218
267
|
|
|
219
268
|
---
|
|
220
269
|
|
|
@@ -233,9 +282,51 @@ openhome status
|
|
|
233
282
|
openhome status my-weather-bot --mock
|
|
234
283
|
```
|
|
235
284
|
|
|
236
|
-
|
|
285
|
+
> **Requires session token** (uses the same list endpoint internally). Run `openhome set-jwt <token>` first.
|
|
237
286
|
|
|
238
|
-
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
### `openhome logs`
|
|
290
|
+
|
|
291
|
+
Stream live agent messages and logs.
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
openhome logs
|
|
295
|
+
openhome logs --agent pers_abc123
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
### `openhome whoami`
|
|
301
|
+
|
|
302
|
+
Show auth status, default agent, and tracked abilities.
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
openhome whoami
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
### `openhome config [path]`
|
|
311
|
+
|
|
312
|
+
Edit trigger words, description, or category in a local `config.json`.
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
openhome config
|
|
316
|
+
openhome config ./my-ability
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
### `openhome logout`
|
|
322
|
+
|
|
323
|
+
Clear stored credentials and log out.
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
openhome logout
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Removes the API key from macOS Keychain and clears the default agent from config.
|
|
239
330
|
|
|
240
331
|
---
|
|
241
332
|
|
|
@@ -259,8 +350,6 @@ Must contain:
|
|
|
259
350
|
|
|
260
351
|
### main.py Required Patterns
|
|
261
352
|
|
|
262
|
-
Your main Python file must include:
|
|
263
|
-
|
|
264
353
|
| What | Why |
|
|
265
354
|
|------|-----|
|
|
266
355
|
| Class extending `MatchingCapability` | OpenHome ability base class |
|
|
@@ -272,8 +361,6 @@ Your main Python file must include:
|
|
|
272
361
|
|
|
273
362
|
### Blocked Patterns (Errors)
|
|
274
363
|
|
|
275
|
-
These are not allowed in any `.py` file:
|
|
276
|
-
|
|
277
364
|
| Pattern | Use Instead |
|
|
278
365
|
|---------|-------------|
|
|
279
366
|
| `print()` | `self.worker.editor_logging_handler` |
|
|
@@ -310,8 +397,7 @@ These are not allowed in any `.py` file:
|
|
|
310
397
|
|
|
311
398
|
```
|
|
312
399
|
~/.openhome/
|
|
313
|
-
config.json # Settings
|
|
314
|
-
last-deploy.zip # Saved when upload endpoint unavailable
|
|
400
|
+
config.json # Settings, fallback API key, session token
|
|
315
401
|
```
|
|
316
402
|
|
|
317
403
|
### Config Fields
|
|
@@ -321,6 +407,7 @@ These are not allowed in any `.py` file:
|
|
|
321
407
|
| `api_base_url` | Override API endpoint | `https://app.openhome.com` |
|
|
322
408
|
| `default_personality_id` | Default agent for deploys | (none) |
|
|
323
409
|
| `api_key` | Fallback key storage | (none — prefers Keychain) |
|
|
410
|
+
| `jwt` | Session token for management commands | (none — set via `set-jwt`) |
|
|
324
411
|
|
|
325
412
|
### API Key Storage
|
|
326
413
|
|
|
@@ -328,125 +415,51 @@ On macOS, your API key is stored in the system Keychain (service: `openhome-cli`
|
|
|
328
415
|
|
|
329
416
|
---
|
|
330
417
|
|
|
331
|
-
##
|
|
332
|
-
|
|
333
|
-
```
|
|
334
|
-
openhome-cli/
|
|
335
|
-
├── bin/openhome.js # Entry point shim
|
|
336
|
-
├── src/
|
|
337
|
-
│ ├── cli.ts # Menu + Commander setup
|
|
338
|
-
│ ├── commands/
|
|
339
|
-
│ │ ├── login.ts # API key auth
|
|
340
|
-
│ │ ├── logout.ts # Clear credentials
|
|
341
|
-
│ │ ├── init.ts # Scaffold new ability
|
|
342
|
-
│ │ ├── deploy.ts # Validate + zip + upload
|
|
343
|
-
│ │ ├── chat.ts # WebSocket chat with agent
|
|
344
|
-
│ │ ├── list.ts # List abilities table
|
|
345
|
-
│ │ ├── agents.ts # View agents + set default
|
|
346
|
-
│ │ └── status.ts # Ability detail view
|
|
347
|
-
│ ├── api/
|
|
348
|
-
│ │ ├── client.ts # HTTP client + error handling
|
|
349
|
-
│ │ ├── mock-client.ts # Fake responses for testing
|
|
350
|
-
│ │ ├── contracts.ts # TypeScript interfaces
|
|
351
|
-
│ │ └── endpoints.ts # URL constants
|
|
352
|
-
│ ├── validation/
|
|
353
|
-
│ │ ├── rules.ts # All validation rules
|
|
354
|
-
│ │ └── validator.ts # Rule runner
|
|
355
|
-
│ ├── config/
|
|
356
|
-
│ │ ├── store.ts # Config file + Keychain
|
|
357
|
-
│ │ └── keychain.ts # macOS Keychain helpers
|
|
358
|
-
│ ├── ui/
|
|
359
|
-
│ │ └── format.ts # Colors, tables, prompts
|
|
360
|
-
│ └── util/
|
|
361
|
-
│ └── zip.ts # ZIP creation (archiver)
|
|
362
|
-
└── templates/
|
|
363
|
-
├── basic/ # Simple ability template
|
|
364
|
-
└── api/ # API-calling ability template
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
---
|
|
368
|
-
|
|
369
|
-
## Development
|
|
370
|
-
|
|
371
|
-
```bash
|
|
372
|
-
# Install dependencies
|
|
373
|
-
npm install
|
|
374
|
-
|
|
375
|
-
# Build
|
|
376
|
-
npm run build
|
|
377
|
-
|
|
378
|
-
# Run without building (dev mode)
|
|
379
|
-
npm run dev
|
|
380
|
-
|
|
381
|
-
# Type check
|
|
382
|
-
npm run lint
|
|
383
|
-
|
|
384
|
-
# Run tests
|
|
385
|
-
npm run test
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
### Tech Stack
|
|
418
|
+
## What This Tool Does NOT Do
|
|
389
419
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
| chalk | 5.x | Terminal colors |
|
|
395
|
-
| ws | 8.x | WebSocket client for agent chat |
|
|
396
|
-
| archiver | 7.x | ZIP file creation |
|
|
397
|
-
| typescript | 5.x | Type safety |
|
|
398
|
-
| tsup | 8.x | Build tool |
|
|
399
|
-
| vitest | 2.x | Testing |
|
|
420
|
+
- **No local ability testing** — Abilities run on the OpenHome platform. Deploy and use "Start Live Test" in the web editor to test.
|
|
421
|
+
- **No ability editing** — The CLI does not modify deployed abilities. Edit locally, then re-deploy.
|
|
422
|
+
- **No update/redeploy** — There is no endpoint to overwrite an existing ability version. Deploy creates a new entry; delete the old one via `openhome delete`.
|
|
423
|
+
- **No Windows Keychain** — API key stored in plaintext config on non-macOS platforms.
|
|
400
424
|
|
|
401
425
|
---
|
|
402
426
|
|
|
403
427
|
## API Status
|
|
404
428
|
|
|
405
|
-
| Endpoint |
|
|
406
|
-
|
|
407
|
-
|
|
|
408
|
-
|
|
|
409
|
-
|
|
|
410
|
-
|
|
|
411
|
-
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
- **List/Status**: Suggests using `--mock` flag to preview the output format
|
|
429
|
+
| Command | Endpoint | Auth | Status |
|
|
430
|
+
|---------|----------|------|--------|
|
|
431
|
+
| `login` | `POST /api/sdk/verify_apikey/` | API key | Live |
|
|
432
|
+
| `agents` | `POST /api/sdk/get_personalities/` | API key | Live |
|
|
433
|
+
| `chat` | WebSocket `/websocket/voice-stream/` | API key | Live |
|
|
434
|
+
| `deploy` | `POST /api/capabilities/add-capability/` | API key | Live |
|
|
435
|
+
| `list` | `GET /api/capabilities/get-installed-capabilities/` | JWT | Live |
|
|
436
|
+
| `delete` | `POST /api/capabilities/delete-capability/` | JWT | Live |
|
|
437
|
+
| `toggle` | `PUT /api/capabilities/edit-installed-capability/:id/` | JWT | Live |
|
|
438
|
+
| `assign` | `PUT /api/personalities/edit-personality/` | JWT | Live |
|
|
416
439
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
---
|
|
420
|
-
|
|
421
|
-
## What This Tool Does NOT Do
|
|
422
|
-
|
|
423
|
-
- **No local ability testing** — Abilities run on the OpenHome platform. Deploy and use "Start Live Test" in the web editor to test.
|
|
424
|
-
- **No log streaming** — `openhome logs` is not yet implemented.
|
|
425
|
-
- **No ability deletion** — Must be done through the web dashboard.
|
|
426
|
-
- **No ability editing** — The CLI does not modify deployed abilities. Edit locally, then re-deploy.
|
|
427
|
-
- **No multi-agent deploy** — One ability deploys to one agent at a time.
|
|
428
|
-
- **No Windows Keychain** — API key stored in plaintext config on non-macOS platforms.
|
|
440
|
+
Commands marked **JWT** require `openhome set-jwt <token>` first. OpenHome currently uses separate auth for SDK operations (API key) vs. account management (web session JWT). Once OpenHome adds API key support to management endpoints, the `set-jwt` step will no longer be needed.
|
|
429
441
|
|
|
430
442
|
---
|
|
431
443
|
|
|
432
444
|
## Roadmap
|
|
433
445
|
|
|
434
|
-
### Planned
|
|
435
|
-
|
|
436
|
-
- [ ] `openhome logs [ability]` — Stream ability logs in real-time
|
|
437
|
-
- [ ] `openhome delete [ability]` — Remove a deployed ability
|
|
438
|
-
- [ ] `openhome update` — Re-deploy an existing ability (shortcut for deploy)
|
|
439
446
|
- [ ] `openhome watch` — Auto-deploy on file changes
|
|
447
|
+
- [ ] `openhome update` — Re-deploy/overwrite an existing ability (pending server-side update endpoint)
|
|
440
448
|
- [ ] Background Daemon and Brain Skill templates
|
|
441
449
|
- [ ] Cross-platform secure key storage (Windows Credential Manager, Linux Secret Service)
|
|
450
|
+
- [ ] Management commands without JWT (pending OpenHome API update)
|
|
451
|
+
|
|
452
|
+
---
|
|
442
453
|
|
|
443
|
-
|
|
454
|
+
## Development
|
|
444
455
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
456
|
+
```bash
|
|
457
|
+
npm install
|
|
458
|
+
npm run build # Build
|
|
459
|
+
npm run dev # Run without building
|
|
460
|
+
npm run lint # Type check
|
|
461
|
+
npm run test # Run tests
|
|
462
|
+
```
|
|
450
463
|
|
|
451
464
|
---
|
|
452
465
|
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getApiKey,
|
|
3
3
|
getConfig,
|
|
4
|
-
getJwt
|
|
4
|
+
getJwt,
|
|
5
5
|
getTrackedAbilities,
|
|
6
6
|
keychainDelete,
|
|
7
7
|
registerAbility,
|
|
@@ -16,6 +16,9 @@ import { fileURLToPath } from "url";
|
|
|
16
16
|
import { dirname, join as join6 } from "path";
|
|
17
17
|
import { readFileSync as readFileSync5 } from "fs";
|
|
18
18
|
|
|
19
|
+
// src/commands/login.ts
|
|
20
|
+
import { execFile } from "child_process";
|
|
21
|
+
|
|
19
22
|
// src/api/endpoints.ts
|
|
20
23
|
var API_BASE = "https://app.openhome.com";
|
|
21
24
|
var WS_BASE = "wss://app.openhome.com";
|
|
@@ -272,10 +275,38 @@ function handleCancel(value) {
|
|
|
272
275
|
|
|
273
276
|
// src/commands/login.ts
|
|
274
277
|
import chalk2 from "chalk";
|
|
278
|
+
var SETTINGS_URL = "https://app.openhome.com/dashboard/settings";
|
|
279
|
+
function openBrowser(url) {
|
|
280
|
+
try {
|
|
281
|
+
if (process.platform === "darwin") {
|
|
282
|
+
execFile("open", [url]);
|
|
283
|
+
} else if (process.platform === "win32") {
|
|
284
|
+
execFile("cmd", ["/c", "start", url]);
|
|
285
|
+
} else {
|
|
286
|
+
execFile("xdg-open", [url]);
|
|
287
|
+
}
|
|
288
|
+
} catch {
|
|
289
|
+
}
|
|
290
|
+
}
|
|
275
291
|
async function loginCommand() {
|
|
276
292
|
p.intro("\u{1F511} OpenHome Login");
|
|
293
|
+
const open = await p.confirm({
|
|
294
|
+
message: `Press Enter to open your browser and navigate to the ${chalk2.bold("API Keys")} tab`,
|
|
295
|
+
initialValue: true,
|
|
296
|
+
active: "Open browser",
|
|
297
|
+
inactive: "Skip"
|
|
298
|
+
});
|
|
299
|
+
handleCancel(open);
|
|
300
|
+
if (open) {
|
|
301
|
+
openBrowser(SETTINGS_URL);
|
|
302
|
+
console.log(
|
|
303
|
+
`
|
|
304
|
+
${chalk2.dim(`Opened ${chalk2.bold("app.openhome.com/dashboard/settings")} \u2014 click the ${chalk2.bold("API Keys")} tab`)}
|
|
305
|
+
`
|
|
306
|
+
);
|
|
307
|
+
}
|
|
277
308
|
const apiKey = await p.password({
|
|
278
|
-
message: "
|
|
309
|
+
message: "Paste your API key here",
|
|
279
310
|
validate: (val) => {
|
|
280
311
|
if (!val || !val.trim()) return "API key is required";
|
|
281
312
|
}
|
|
@@ -548,7 +579,7 @@ import archiver from "archiver";
|
|
|
548
579
|
import { createWriteStream } from "fs";
|
|
549
580
|
import { Writable } from "stream";
|
|
550
581
|
async function createAbilityZip(dirPath) {
|
|
551
|
-
return new Promise((
|
|
582
|
+
return new Promise((resolve6, reject) => {
|
|
552
583
|
const chunks = [];
|
|
553
584
|
const writable = new Writable({
|
|
554
585
|
write(chunk, _encoding, callback) {
|
|
@@ -557,7 +588,7 @@ async function createAbilityZip(dirPath) {
|
|
|
557
588
|
}
|
|
558
589
|
});
|
|
559
590
|
writable.on("finish", () => {
|
|
560
|
-
|
|
591
|
+
resolve6(Buffer.concat(chunks));
|
|
561
592
|
});
|
|
562
593
|
writable.on("error", reject);
|
|
563
594
|
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
@@ -1825,7 +1856,7 @@ async function deleteCommand(abilityArg, opts = {}) {
|
|
|
1825
1856
|
client = new MockApiClient();
|
|
1826
1857
|
} else {
|
|
1827
1858
|
const apiKey = getApiKey() ?? "";
|
|
1828
|
-
const jwt =
|
|
1859
|
+
const jwt = getJwt() ?? void 0;
|
|
1829
1860
|
if (!apiKey && !jwt) {
|
|
1830
1861
|
error("Not authenticated. Run: openhome login");
|
|
1831
1862
|
process.exit(1);
|
|
@@ -1914,7 +1945,7 @@ async function toggleCommand(abilityArg, opts = {}) {
|
|
|
1914
1945
|
client = new MockApiClient();
|
|
1915
1946
|
} else {
|
|
1916
1947
|
const apiKey = getApiKey() ?? "";
|
|
1917
|
-
const jwt =
|
|
1948
|
+
const jwt = getJwt() ?? void 0;
|
|
1918
1949
|
if (!apiKey && !jwt) {
|
|
1919
1950
|
error("Not authenticated. Run: openhome login");
|
|
1920
1951
|
process.exit(1);
|
|
@@ -2013,7 +2044,7 @@ async function assignCommand(opts = {}) {
|
|
|
2013
2044
|
client = new MockApiClient();
|
|
2014
2045
|
} else {
|
|
2015
2046
|
const apiKey = getApiKey() ?? "";
|
|
2016
|
-
const jwt =
|
|
2047
|
+
const jwt = getJwt() ?? void 0;
|
|
2017
2048
|
if (!apiKey && !jwt) {
|
|
2018
2049
|
error("Not authenticated. Run: openhome login");
|
|
2019
2050
|
process.exit(1);
|
|
@@ -2122,7 +2153,7 @@ async function listCommand(opts = {}) {
|
|
|
2122
2153
|
client = new MockApiClient();
|
|
2123
2154
|
} else {
|
|
2124
2155
|
const apiKey = getApiKey() ?? "";
|
|
2125
|
-
const jwt =
|
|
2156
|
+
const jwt = getJwt() ?? void 0;
|
|
2126
2157
|
if (!apiKey && !jwt) {
|
|
2127
2158
|
error("Not authenticated. Run: openhome login");
|
|
2128
2159
|
process.exit(1);
|
|
@@ -2422,7 +2453,7 @@ async function chatCommand(agentArg, opts = {}) {
|
|
|
2422
2453
|
}
|
|
2423
2454
|
const wsUrl = `${WS_BASE}${ENDPOINTS.voiceStream(apiKey, agentId)}`;
|
|
2424
2455
|
info(`Connecting to agent ${chalk9.bold(agentId)}...`);
|
|
2425
|
-
await new Promise((
|
|
2456
|
+
await new Promise((resolve6) => {
|
|
2426
2457
|
const ws = new WebSocket(wsUrl, {
|
|
2427
2458
|
perMessageDeflate: false,
|
|
2428
2459
|
headers: {
|
|
@@ -2550,7 +2581,7 @@ async function chatCommand(agentArg, opts = {}) {
|
|
|
2550
2581
|
console.error("");
|
|
2551
2582
|
error(`WebSocket error: ${err.message}`);
|
|
2552
2583
|
rl.close();
|
|
2553
|
-
|
|
2584
|
+
resolve6();
|
|
2554
2585
|
});
|
|
2555
2586
|
ws.on("close", (code) => {
|
|
2556
2587
|
if (pingInterval) clearInterval(pingInterval);
|
|
@@ -2561,7 +2592,7 @@ async function chatCommand(agentArg, opts = {}) {
|
|
|
2561
2592
|
info(`Connection closed (code: ${code})`);
|
|
2562
2593
|
}
|
|
2563
2594
|
rl.close();
|
|
2564
|
-
|
|
2595
|
+
resolve6();
|
|
2565
2596
|
});
|
|
2566
2597
|
rl.on("close", () => {
|
|
2567
2598
|
if (connected) {
|
|
@@ -2628,7 +2659,7 @@ async function triggerCommand(phraseArg, opts = {}) {
|
|
|
2628
2659
|
info(`Sending "${chalk10.bold(phrase)}" to agent ${chalk10.bold(agentId)}...`);
|
|
2629
2660
|
const s = p.spinner();
|
|
2630
2661
|
s.start("Waiting for response...");
|
|
2631
|
-
await new Promise((
|
|
2662
|
+
await new Promise((resolve6) => {
|
|
2632
2663
|
const ws = new WebSocket2(wsUrl, {
|
|
2633
2664
|
perMessageDeflate: false,
|
|
2634
2665
|
headers: {
|
|
@@ -2658,7 +2689,7 @@ async function triggerCommand(phraseArg, opts = {}) {
|
|
|
2658
2689
|
${chalk10.cyan("Agent:")} ${fullResponse}`);
|
|
2659
2690
|
}
|
|
2660
2691
|
cleanup();
|
|
2661
|
-
|
|
2692
|
+
resolve6();
|
|
2662
2693
|
}, RESPONSE_TIMEOUT);
|
|
2663
2694
|
});
|
|
2664
2695
|
ws.on("message", (raw) => {
|
|
@@ -2675,7 +2706,7 @@ ${chalk10.cyan("Agent:")} ${fullResponse}`);
|
|
|
2675
2706
|
${chalk10.cyan("Agent:")} ${fullResponse}
|
|
2676
2707
|
`);
|
|
2677
2708
|
cleanup();
|
|
2678
|
-
|
|
2709
|
+
resolve6();
|
|
2679
2710
|
}
|
|
2680
2711
|
}
|
|
2681
2712
|
break;
|
|
@@ -2692,7 +2723,7 @@ ${chalk10.cyan("Agent:")} ${fullResponse}
|
|
|
2692
2723
|
${chalk10.cyan("Agent:")} ${fullResponse}
|
|
2693
2724
|
`);
|
|
2694
2725
|
cleanup();
|
|
2695
|
-
|
|
2726
|
+
resolve6();
|
|
2696
2727
|
}
|
|
2697
2728
|
}
|
|
2698
2729
|
break;
|
|
@@ -2707,7 +2738,7 @@ ${chalk10.cyan("Agent:")} ${fullResponse}
|
|
|
2707
2738
|
`Server error: ${errData?.message || errData?.title || "Unknown"}`
|
|
2708
2739
|
);
|
|
2709
2740
|
cleanup();
|
|
2710
|
-
|
|
2741
|
+
resolve6();
|
|
2711
2742
|
break;
|
|
2712
2743
|
}
|
|
2713
2744
|
}
|
|
@@ -2717,12 +2748,12 @@ ${chalk10.cyan("Agent:")} ${fullResponse}
|
|
|
2717
2748
|
ws.on("error", (err) => {
|
|
2718
2749
|
s.stop("Connection error.");
|
|
2719
2750
|
error(err.message);
|
|
2720
|
-
|
|
2751
|
+
resolve6();
|
|
2721
2752
|
});
|
|
2722
2753
|
ws.on("close", () => {
|
|
2723
2754
|
if (pingInterval) clearInterval(pingInterval);
|
|
2724
2755
|
if (responseTimer) clearTimeout(responseTimer);
|
|
2725
|
-
|
|
2756
|
+
resolve6();
|
|
2726
2757
|
});
|
|
2727
2758
|
});
|
|
2728
2759
|
}
|
|
@@ -2947,7 +2978,7 @@ async function logsCommand(opts = {}) {
|
|
|
2947
2978
|
info(`Streaming logs from agent ${chalk12.bold(agentId)}...`);
|
|
2948
2979
|
info(`Press ${chalk12.bold("Ctrl+C")} to stop.
|
|
2949
2980
|
`);
|
|
2950
|
-
await new Promise((
|
|
2981
|
+
await new Promise((resolve6) => {
|
|
2951
2982
|
const ws = new WebSocket3(wsUrl, {
|
|
2952
2983
|
perMessageDeflate: false,
|
|
2953
2984
|
headers: {
|
|
@@ -3027,13 +3058,13 @@ async function logsCommand(opts = {}) {
|
|
|
3027
3058
|
});
|
|
3028
3059
|
ws.on("error", (err) => {
|
|
3029
3060
|
error(`WebSocket error: ${err.message}`);
|
|
3030
|
-
|
|
3061
|
+
resolve6();
|
|
3031
3062
|
});
|
|
3032
3063
|
ws.on("close", (code) => {
|
|
3033
3064
|
if (pingInterval) clearInterval(pingInterval);
|
|
3034
3065
|
console.log("");
|
|
3035
3066
|
info(`Connection closed (code: ${code})`);
|
|
3036
|
-
|
|
3067
|
+
resolve6();
|
|
3037
3068
|
});
|
|
3038
3069
|
process.on("SIGINT", () => {
|
|
3039
3070
|
console.log("");
|
|
@@ -3044,33 +3075,98 @@ async function logsCommand(opts = {}) {
|
|
|
3044
3075
|
}
|
|
3045
3076
|
|
|
3046
3077
|
// src/commands/set-jwt.ts
|
|
3078
|
+
import { execFile as execFile2 } from "child_process";
|
|
3079
|
+
import chalk13 from "chalk";
|
|
3080
|
+
var OPENHOME_URL = "https://app.openhome.com";
|
|
3081
|
+
function openBrowser2(url) {
|
|
3082
|
+
try {
|
|
3083
|
+
if (process.platform === "darwin") {
|
|
3084
|
+
execFile2("open", [url]);
|
|
3085
|
+
} else if (process.platform === "win32") {
|
|
3086
|
+
execFile2("cmd", ["/c", "start", url]);
|
|
3087
|
+
} else {
|
|
3088
|
+
execFile2("xdg-open", [url]);
|
|
3089
|
+
}
|
|
3090
|
+
} catch {
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3047
3093
|
async function setJwtCommand(token) {
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3094
|
+
if (token) {
|
|
3095
|
+
p.intro("\u{1F511} Enable Management Features");
|
|
3096
|
+
try {
|
|
3097
|
+
saveJwt(token.trim());
|
|
3098
|
+
success("Session token saved.");
|
|
3099
|
+
p.outro(
|
|
3100
|
+
"Management commands (list, delete, toggle, assign) are now unlocked."
|
|
3101
|
+
);
|
|
3102
|
+
} catch (err) {
|
|
3103
|
+
error(
|
|
3104
|
+
`Failed to save token: ${err instanceof Error ? err.message : String(err)}`
|
|
3105
|
+
);
|
|
3106
|
+
process.exit(1);
|
|
3107
|
+
}
|
|
3108
|
+
return;
|
|
3109
|
+
}
|
|
3110
|
+
p.intro("\u{1F511} Enable Management Features");
|
|
3111
|
+
p.note(
|
|
3112
|
+
[
|
|
3113
|
+
"Some commands (list, delete, enable/disable abilities) need your",
|
|
3114
|
+
"OpenHome session token to work.",
|
|
3115
|
+
"",
|
|
3116
|
+
"Think of it as a temporary key that proves you're logged in to",
|
|
3117
|
+
"your account. You only need to do this once."
|
|
3118
|
+
].join("\n"),
|
|
3119
|
+
"What's this?"
|
|
3120
|
+
);
|
|
3121
|
+
console.log("");
|
|
3122
|
+
console.log(
|
|
3123
|
+
chalk13.dim(` Opening ${chalk13.bold("app.openhome.com")} in your browser...`)
|
|
3124
|
+
);
|
|
3125
|
+
openBrowser2(OPENHOME_URL);
|
|
3126
|
+
console.log("");
|
|
3127
|
+
p.note(
|
|
3128
|
+
[
|
|
3129
|
+
`${chalk13.bold("Step 1")} Make sure you're logged in to app.openhome.com`,
|
|
3130
|
+
"",
|
|
3131
|
+
`${chalk13.bold("Step 2")} Open the browser console:`,
|
|
3132
|
+
` Mac \u2192 ${chalk13.cyan("Cmd + Option + J")}`,
|
|
3133
|
+
` Windows / Linux \u2192 ${chalk13.cyan("F12")}, then click the Console tab`,
|
|
3134
|
+
"",
|
|
3135
|
+
`${chalk13.bold("Step 3")} Paste this command into the console and press Enter:`,
|
|
3136
|
+
"",
|
|
3137
|
+
` ${chalk13.green("copy(localStorage.getItem('token'))")}`,
|
|
3138
|
+
"",
|
|
3139
|
+
`${chalk13.bold("Step 4")} Your token is now in your clipboard.`,
|
|
3140
|
+
` Come back here and paste it below.`
|
|
3141
|
+
].join("\n"),
|
|
3142
|
+
"How to get your token"
|
|
3143
|
+
);
|
|
3144
|
+
const input = await p.password({
|
|
3145
|
+
message: "Paste your token here",
|
|
3146
|
+
validate: (val) => {
|
|
3147
|
+
if (!val || !val.trim()) return "Token is required";
|
|
3148
|
+
if (val.trim().length < 20)
|
|
3149
|
+
return "That doesn't look right \u2014 the token should be much longer";
|
|
3063
3150
|
}
|
|
3064
|
-
|
|
3151
|
+
});
|
|
3152
|
+
if (typeof input === "symbol") {
|
|
3153
|
+
p.cancel("Cancelled.");
|
|
3154
|
+
return;
|
|
3065
3155
|
}
|
|
3066
3156
|
try {
|
|
3067
|
-
saveJwt(
|
|
3157
|
+
saveJwt(input.trim());
|
|
3068
3158
|
success("Session token saved.");
|
|
3069
3159
|
p.note(
|
|
3070
|
-
|
|
3071
|
-
|
|
3160
|
+
[
|
|
3161
|
+
"These commands are now unlocked:",
|
|
3162
|
+
` ${chalk13.bold("openhome list")} \u2014 see all your deployed abilities`,
|
|
3163
|
+
` ${chalk13.bold("openhome delete")} \u2014 remove an ability`,
|
|
3164
|
+
` ${chalk13.bold("openhome toggle")} \u2014 enable or disable an ability`,
|
|
3165
|
+
` ${chalk13.bold("openhome assign")} \u2014 link abilities to an agent`
|
|
3166
|
+
].join("\n"),
|
|
3167
|
+
"You're all set"
|
|
3072
3168
|
);
|
|
3073
|
-
p.outro("Done
|
|
3169
|
+
p.outro("Done!");
|
|
3074
3170
|
} catch (err) {
|
|
3075
3171
|
error(
|
|
3076
3172
|
`Failed to save token: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -3079,6 +3175,45 @@ async function setJwtCommand(token) {
|
|
|
3079
3175
|
}
|
|
3080
3176
|
}
|
|
3081
3177
|
|
|
3178
|
+
// src/commands/validate.ts
|
|
3179
|
+
import { resolve as resolve5 } from "path";
|
|
3180
|
+
import chalk14 from "chalk";
|
|
3181
|
+
async function validateCommand(pathArg = ".") {
|
|
3182
|
+
const targetDir = resolve5(pathArg);
|
|
3183
|
+
p.intro(`\u{1F50E} Validate ability`);
|
|
3184
|
+
const s = p.spinner();
|
|
3185
|
+
s.start("Running checks...");
|
|
3186
|
+
const result = validateAbility(targetDir);
|
|
3187
|
+
if (result.errors.length === 0 && result.warnings.length === 0) {
|
|
3188
|
+
s.stop("All checks passed.");
|
|
3189
|
+
p.outro("Ability is ready to deploy! \u{1F389}");
|
|
3190
|
+
return;
|
|
3191
|
+
}
|
|
3192
|
+
s.stop("Checks complete.");
|
|
3193
|
+
if (result.errors.length > 0) {
|
|
3194
|
+
p.note(
|
|
3195
|
+
result.errors.map(
|
|
3196
|
+
(issue) => `${chalk14.red("\u2717")} ${issue.file ? chalk14.bold(`[${issue.file}]`) + " " : ""}${issue.message}`
|
|
3197
|
+
).join("\n"),
|
|
3198
|
+
`${result.errors.length} Error(s)`
|
|
3199
|
+
);
|
|
3200
|
+
}
|
|
3201
|
+
if (result.warnings.length > 0) {
|
|
3202
|
+
p.note(
|
|
3203
|
+
result.warnings.map(
|
|
3204
|
+
(w) => `${chalk14.yellow("\u26A0")} ${w.file ? chalk14.bold(`[${w.file}]`) + " " : ""}${w.message}`
|
|
3205
|
+
).join("\n"),
|
|
3206
|
+
`${result.warnings.length} Warning(s)`
|
|
3207
|
+
);
|
|
3208
|
+
}
|
|
3209
|
+
if (result.passed) {
|
|
3210
|
+
p.outro("Validation passed (with warnings).");
|
|
3211
|
+
} else {
|
|
3212
|
+
error("Fix errors before deploying.");
|
|
3213
|
+
process.exit(1);
|
|
3214
|
+
}
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3082
3217
|
// src/cli.ts
|
|
3083
3218
|
var __filename = fileURLToPath(import.meta.url);
|
|
3084
3219
|
var __dirname = dirname(__filename);
|
|
@@ -3111,16 +3246,6 @@ async function interactiveMenu() {
|
|
|
3111
3246
|
label: "\u2728 Create Ability",
|
|
3112
3247
|
hint: "Scaffold and deploy a new ability"
|
|
3113
3248
|
},
|
|
3114
|
-
{
|
|
3115
|
-
value: "chat",
|
|
3116
|
-
label: "\u{1F4AC} Chat",
|
|
3117
|
-
hint: "Talk to your agent"
|
|
3118
|
-
},
|
|
3119
|
-
{
|
|
3120
|
-
value: "trigger",
|
|
3121
|
-
label: "\u26A1 Trigger",
|
|
3122
|
-
hint: "Fire an ability remotely with a phrase"
|
|
3123
|
-
},
|
|
3124
3249
|
{
|
|
3125
3250
|
value: "list",
|
|
3126
3251
|
label: "\u{1F4CB} My Abilities",
|
|
@@ -3147,25 +3272,15 @@ async function interactiveMenu() {
|
|
|
3147
3272
|
hint: "View agents and set default"
|
|
3148
3273
|
},
|
|
3149
3274
|
{
|
|
3150
|
-
value: "
|
|
3151
|
-
label: "\u{
|
|
3152
|
-
hint: "
|
|
3153
|
-
},
|
|
3154
|
-
{
|
|
3155
|
-
value: "config",
|
|
3156
|
-
label: "\u2699\uFE0F Edit Config",
|
|
3157
|
-
hint: "Update trigger words, description, category"
|
|
3275
|
+
value: "chat",
|
|
3276
|
+
label: "\u{1F4AC} Chat",
|
|
3277
|
+
hint: "Talk to your agent"
|
|
3158
3278
|
},
|
|
3159
3279
|
{
|
|
3160
3280
|
value: "logs",
|
|
3161
3281
|
label: "\u{1F4E1} Logs",
|
|
3162
3282
|
hint: "Stream live agent messages"
|
|
3163
3283
|
},
|
|
3164
|
-
{
|
|
3165
|
-
value: "whoami",
|
|
3166
|
-
label: "\u{1F464} Who Am I",
|
|
3167
|
-
hint: "Show auth, default agent, tracked abilities"
|
|
3168
|
-
},
|
|
3169
3284
|
{
|
|
3170
3285
|
value: "logout",
|
|
3171
3286
|
label: "\u{1F513} Log Out",
|
|
@@ -3179,12 +3294,6 @@ async function interactiveMenu() {
|
|
|
3179
3294
|
case "init":
|
|
3180
3295
|
await initCommand();
|
|
3181
3296
|
break;
|
|
3182
|
-
case "chat":
|
|
3183
|
-
await chatCommand();
|
|
3184
|
-
break;
|
|
3185
|
-
case "trigger":
|
|
3186
|
-
await triggerCommand();
|
|
3187
|
-
break;
|
|
3188
3297
|
case "list":
|
|
3189
3298
|
await listCommand();
|
|
3190
3299
|
break;
|
|
@@ -3200,18 +3309,12 @@ async function interactiveMenu() {
|
|
|
3200
3309
|
case "agents":
|
|
3201
3310
|
await agentsCommand();
|
|
3202
3311
|
break;
|
|
3203
|
-
case "
|
|
3204
|
-
await
|
|
3205
|
-
break;
|
|
3206
|
-
case "config":
|
|
3207
|
-
await configEditCommand();
|
|
3312
|
+
case "chat":
|
|
3313
|
+
await chatCommand();
|
|
3208
3314
|
break;
|
|
3209
3315
|
case "logs":
|
|
3210
3316
|
await logsCommand();
|
|
3211
3317
|
break;
|
|
3212
|
-
case "whoami":
|
|
3213
|
-
await whoamiCommand();
|
|
3214
|
-
break;
|
|
3215
3318
|
case "logout":
|
|
3216
3319
|
await logoutCommand();
|
|
3217
3320
|
await ensureLoggedIn();
|
|
@@ -3277,6 +3380,9 @@ program.command("logs").description("Stream live agent messages and logs").optio
|
|
|
3277
3380
|
program.command("whoami").description("Show auth status, default agent, and tracked abilities").action(async () => {
|
|
3278
3381
|
await whoamiCommand();
|
|
3279
3382
|
});
|
|
3383
|
+
program.command("validate [path]").description("Check an ability for errors before deploying").action(async (path) => {
|
|
3384
|
+
await validateCommand(path);
|
|
3385
|
+
});
|
|
3280
3386
|
program.command("set-jwt [token]").description(
|
|
3281
3387
|
"Save a session token to enable management commands (list, delete, toggle, assign)"
|
|
3282
3388
|
).action(async (token) => {
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { whoamiCommand } from "./commands/whoami.js";
|
|
|
19
19
|
import { configEditCommand } from "./commands/config-edit.js";
|
|
20
20
|
import { logsCommand } from "./commands/logs.js";
|
|
21
21
|
import { setJwtCommand } from "./commands/set-jwt.js";
|
|
22
|
+
import { validateCommand } from "./commands/validate.js";
|
|
22
23
|
import { p, handleCancel } from "./ui/format.js";
|
|
23
24
|
|
|
24
25
|
// Read version from package.json
|
|
@@ -61,16 +62,6 @@ async function interactiveMenu(): Promise<void> {
|
|
|
61
62
|
label: "✨ Create Ability",
|
|
62
63
|
hint: "Scaffold and deploy a new ability",
|
|
63
64
|
},
|
|
64
|
-
{
|
|
65
|
-
value: "chat",
|
|
66
|
-
label: "💬 Chat",
|
|
67
|
-
hint: "Talk to your agent",
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
value: "trigger",
|
|
71
|
-
label: "⚡ Trigger",
|
|
72
|
-
hint: "Fire an ability remotely with a phrase",
|
|
73
|
-
},
|
|
74
65
|
{
|
|
75
66
|
value: "list",
|
|
76
67
|
label: "📋 My Abilities",
|
|
@@ -97,25 +88,15 @@ async function interactiveMenu(): Promise<void> {
|
|
|
97
88
|
hint: "View agents and set default",
|
|
98
89
|
},
|
|
99
90
|
{
|
|
100
|
-
value: "
|
|
101
|
-
label: "
|
|
102
|
-
hint: "
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
value: "config",
|
|
106
|
-
label: "⚙️ Edit Config",
|
|
107
|
-
hint: "Update trigger words, description, category",
|
|
91
|
+
value: "chat",
|
|
92
|
+
label: "💬 Chat",
|
|
93
|
+
hint: "Talk to your agent",
|
|
108
94
|
},
|
|
109
95
|
{
|
|
110
96
|
value: "logs",
|
|
111
97
|
label: "📡 Logs",
|
|
112
98
|
hint: "Stream live agent messages",
|
|
113
99
|
},
|
|
114
|
-
{
|
|
115
|
-
value: "whoami",
|
|
116
|
-
label: "👤 Who Am I",
|
|
117
|
-
hint: "Show auth, default agent, tracked abilities",
|
|
118
|
-
},
|
|
119
100
|
{
|
|
120
101
|
value: "logout",
|
|
121
102
|
label: "🔓 Log Out",
|
|
@@ -130,12 +111,6 @@ async function interactiveMenu(): Promise<void> {
|
|
|
130
111
|
case "init":
|
|
131
112
|
await initCommand();
|
|
132
113
|
break;
|
|
133
|
-
case "chat":
|
|
134
|
-
await chatCommand();
|
|
135
|
-
break;
|
|
136
|
-
case "trigger":
|
|
137
|
-
await triggerCommand();
|
|
138
|
-
break;
|
|
139
114
|
case "list":
|
|
140
115
|
await listCommand();
|
|
141
116
|
break;
|
|
@@ -151,18 +126,12 @@ async function interactiveMenu(): Promise<void> {
|
|
|
151
126
|
case "agents":
|
|
152
127
|
await agentsCommand();
|
|
153
128
|
break;
|
|
154
|
-
case "
|
|
155
|
-
await
|
|
156
|
-
break;
|
|
157
|
-
case "config":
|
|
158
|
-
await configEditCommand();
|
|
129
|
+
case "chat":
|
|
130
|
+
await chatCommand();
|
|
159
131
|
break;
|
|
160
132
|
case "logs":
|
|
161
133
|
await logsCommand();
|
|
162
134
|
break;
|
|
163
|
-
case "whoami":
|
|
164
|
-
await whoamiCommand();
|
|
165
|
-
break;
|
|
166
135
|
case "logout":
|
|
167
136
|
await logoutCommand();
|
|
168
137
|
await ensureLoggedIn();
|
|
@@ -317,6 +286,13 @@ program
|
|
|
317
286
|
await whoamiCommand();
|
|
318
287
|
});
|
|
319
288
|
|
|
289
|
+
program
|
|
290
|
+
.command("validate [path]")
|
|
291
|
+
.description("Check an ability for errors before deploying")
|
|
292
|
+
.action(async (path?: string) => {
|
|
293
|
+
await validateCommand(path);
|
|
294
|
+
});
|
|
295
|
+
|
|
320
296
|
program
|
|
321
297
|
.command("set-jwt [token]")
|
|
322
298
|
.description(
|
package/src/commands/login.ts
CHANGED
|
@@ -1,14 +1,46 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
1
2
|
import { ApiClient } from "../api/client.js";
|
|
2
3
|
import type { Personality } from "../api/contracts.js";
|
|
3
|
-
import { saveApiKey
|
|
4
|
+
import { saveApiKey } from "../config/store.js";
|
|
4
5
|
import { success, error, info, p, handleCancel } from "../ui/format.js";
|
|
5
6
|
import chalk from "chalk";
|
|
6
7
|
|
|
8
|
+
const SETTINGS_URL = "https://app.openhome.com/dashboard/settings";
|
|
9
|
+
|
|
10
|
+
function openBrowser(url: string): void {
|
|
11
|
+
try {
|
|
12
|
+
if (process.platform === "darwin") {
|
|
13
|
+
execFile("open", [url]);
|
|
14
|
+
} else if (process.platform === "win32") {
|
|
15
|
+
execFile("cmd", ["/c", "start", url]);
|
|
16
|
+
} else {
|
|
17
|
+
execFile("xdg-open", [url]);
|
|
18
|
+
}
|
|
19
|
+
} catch {
|
|
20
|
+
// best-effort
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
7
24
|
export async function loginCommand(): Promise<void> {
|
|
8
25
|
p.intro("🔑 OpenHome Login");
|
|
9
26
|
|
|
27
|
+
const open = await p.confirm({
|
|
28
|
+
message: `Press Enter to open your browser and navigate to the ${chalk.bold("API Keys")} tab`,
|
|
29
|
+
initialValue: true,
|
|
30
|
+
active: "Open browser",
|
|
31
|
+
inactive: "Skip",
|
|
32
|
+
});
|
|
33
|
+
handleCancel(open);
|
|
34
|
+
|
|
35
|
+
if (open) {
|
|
36
|
+
openBrowser(SETTINGS_URL);
|
|
37
|
+
console.log(
|
|
38
|
+
`\n ${chalk.dim(`Opened ${chalk.bold("app.openhome.com/dashboard/settings")} — click the ${chalk.bold("API Keys")} tab`)}\n`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
10
42
|
const apiKey = await p.password({
|
|
11
|
-
message: "
|
|
43
|
+
message: "Paste your API key here",
|
|
12
44
|
validate: (val) => {
|
|
13
45
|
if (!val || !val.trim()) return "API key is required";
|
|
14
46
|
},
|
|
@@ -38,7 +70,6 @@ export async function loginCommand(): Promise<void> {
|
|
|
38
70
|
saveApiKey(apiKey as string);
|
|
39
71
|
success("API key saved.");
|
|
40
72
|
|
|
41
|
-
// Show agents on this account
|
|
42
73
|
if (agents.length > 0) {
|
|
43
74
|
p.note(
|
|
44
75
|
agents
|
package/src/commands/set-jwt.ts
CHANGED
|
@@ -1,36 +1,110 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
1
2
|
import { saveJwt } from "../config/store.js";
|
|
2
3
|
import { success, error, p } from "../ui/format.js";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
|
|
6
|
+
const OPENHOME_URL = "https://app.openhome.com";
|
|
7
|
+
|
|
8
|
+
function openBrowser(url: string): void {
|
|
9
|
+
try {
|
|
10
|
+
if (process.platform === "darwin") {
|
|
11
|
+
execFile("open", [url]);
|
|
12
|
+
} else if (process.platform === "win32") {
|
|
13
|
+
execFile("cmd", ["/c", "start", url]);
|
|
14
|
+
} else {
|
|
15
|
+
execFile("xdg-open", [url]);
|
|
16
|
+
}
|
|
17
|
+
} catch {
|
|
18
|
+
// best-effort — user can open manually
|
|
19
|
+
}
|
|
20
|
+
}
|
|
3
21
|
|
|
4
22
|
export async function setJwtCommand(token?: string): Promise<void> {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (typeof input === "symbol") {
|
|
20
|
-
p.cancel("Cancelled.");
|
|
21
|
-
return;
|
|
23
|
+
// Direct usage: openhome set-jwt eyJ...
|
|
24
|
+
if (token) {
|
|
25
|
+
p.intro("🔑 Enable Management Features");
|
|
26
|
+
try {
|
|
27
|
+
saveJwt(token.trim());
|
|
28
|
+
success("Session token saved.");
|
|
29
|
+
p.outro(
|
|
30
|
+
"Management commands (list, delete, toggle, assign) are now unlocked.",
|
|
31
|
+
);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
error(
|
|
34
|
+
`Failed to save token: ${err instanceof Error ? err.message : String(err)}`,
|
|
35
|
+
);
|
|
36
|
+
process.exit(1);
|
|
22
37
|
}
|
|
23
|
-
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Guided interactive flow
|
|
42
|
+
p.intro("🔑 Enable Management Features");
|
|
43
|
+
|
|
44
|
+
p.note(
|
|
45
|
+
[
|
|
46
|
+
"Some commands (list, delete, enable/disable abilities) need your",
|
|
47
|
+
"OpenHome session token to work.",
|
|
48
|
+
"",
|
|
49
|
+
"Think of it as a temporary key that proves you're logged in to",
|
|
50
|
+
"your account. You only need to do this once.",
|
|
51
|
+
].join("\n"),
|
|
52
|
+
"What's this?",
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
console.log("");
|
|
56
|
+
console.log(
|
|
57
|
+
chalk.dim(` Opening ${chalk.bold("app.openhome.com")} in your browser...`),
|
|
58
|
+
);
|
|
59
|
+
openBrowser(OPENHOME_URL);
|
|
60
|
+
console.log("");
|
|
61
|
+
|
|
62
|
+
p.note(
|
|
63
|
+
[
|
|
64
|
+
`${chalk.bold("Step 1")} Make sure you're logged in to app.openhome.com`,
|
|
65
|
+
"",
|
|
66
|
+
`${chalk.bold("Step 2")} Open the browser console:`,
|
|
67
|
+
` Mac → ${chalk.cyan("Cmd + Option + J")}`,
|
|
68
|
+
` Windows / Linux → ${chalk.cyan("F12")}, then click the Console tab`,
|
|
69
|
+
"",
|
|
70
|
+
`${chalk.bold("Step 3")} Paste this command into the console and press Enter:`,
|
|
71
|
+
"",
|
|
72
|
+
` ${chalk.green("copy(localStorage.getItem('token'))")}`,
|
|
73
|
+
"",
|
|
74
|
+
`${chalk.bold("Step 4")} Your token is now in your clipboard.`,
|
|
75
|
+
` Come back here and paste it below.`,
|
|
76
|
+
].join("\n"),
|
|
77
|
+
"How to get your token",
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const input = await p.password({
|
|
81
|
+
message: "Paste your token here",
|
|
82
|
+
validate: (val) => {
|
|
83
|
+
if (!val || !val.trim()) return "Token is required";
|
|
84
|
+
if (val.trim().length < 20)
|
|
85
|
+
return "That doesn't look right — the token should be much longer";
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (typeof input === "symbol") {
|
|
90
|
+
p.cancel("Cancelled.");
|
|
91
|
+
return;
|
|
24
92
|
}
|
|
25
93
|
|
|
26
94
|
try {
|
|
27
|
-
saveJwt(
|
|
95
|
+
saveJwt((input as string).trim());
|
|
28
96
|
success("Session token saved.");
|
|
29
97
|
p.note(
|
|
30
|
-
|
|
31
|
-
|
|
98
|
+
[
|
|
99
|
+
"These commands are now unlocked:",
|
|
100
|
+
` ${chalk.bold("openhome list")} — see all your deployed abilities`,
|
|
101
|
+
` ${chalk.bold("openhome delete")} — remove an ability`,
|
|
102
|
+
` ${chalk.bold("openhome toggle")} — enable or disable an ability`,
|
|
103
|
+
` ${chalk.bold("openhome assign")} — link abilities to an agent`,
|
|
104
|
+
].join("\n"),
|
|
105
|
+
"You're all set",
|
|
32
106
|
);
|
|
33
|
-
p.outro("Done
|
|
107
|
+
p.outro("Done!");
|
|
34
108
|
} catch (err) {
|
|
35
109
|
error(
|
|
36
110
|
`Failed to save token: ${err instanceof Error ? err.message : String(err)}`,
|
package/src/commands/status.ts
CHANGED
|
@@ -3,7 +3,12 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { ApiClient, NotImplementedError } from "../api/client.js";
|
|
5
5
|
import { MockApiClient } from "../api/mock-client.js";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
getApiKey,
|
|
8
|
+
getConfig,
|
|
9
|
+
getJwt,
|
|
10
|
+
getTrackedAbilities,
|
|
11
|
+
} from "../config/store.js";
|
|
7
12
|
import { error, warn, info, p, handleCancel } from "../ui/format.js";
|
|
8
13
|
import chalk from "chalk";
|
|
9
14
|
|