openhome-cli 0.1.1 → 0.1.3

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 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
- **Status:** v0.1.0 (MVP)
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
- # Clone and link globally
15
- git clone https://github.com/Bradymck/openhome-cli.git
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
- # Now available everywhere
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:** The upload endpoint is not yet live on the server. When it returns "Not Implemented", the CLI saves your zip to `~/.openhome/last-deploy.zip` for manual upload at [app.openhome.com](https://app.openhome.com).
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
- > **Note:** This endpoint is not yet live. Use `--mock` to preview the output format.
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
- # Test with fake data
214
- openhome agents --mock
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
- Shows all agents on your account with names and descriptions. Optionally set or change your default agent (used by `deploy` when `--personality` is not specified).
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
- Shows: name, display name, status, version, timestamps, linked agents, validation errors, and deploy history.
285
+ > **Requires session token** (uses the same list endpoint internally). Run `openhome set-jwt <token>` first.
237
286
 
238
- > **Note:** This endpoint is not yet live. Use `--mock` to preview the output format.
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 + fallback API key
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
- ## Project Structure
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
- | Package | Version | Purpose |
391
- |---------|---------|---------|
392
- | commander | 12.x | CLI argument parsing |
393
- | @clack/prompts | 1.x | Interactive menus, spinners, prompts |
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 | Method | Status |
406
- |----------|--------|--------|
407
- | `/api/sdk/get_personalities` | POST | Live |
408
- | `/websocket/voice-stream/{key}/{agent}` | WebSocket | Live |
409
- | `/api/sdk/abilities` | POST (upload) | Not yet implemented |
410
- | `/api/sdk/abilities` | GET (list) | Not yet implemented |
411
- | `/api/sdk/abilities/:id` | GET (detail) | Not yet implemented |
412
-
413
- The CLI handles "Not Implemented" responses gracefully. When an endpoint is unavailable:
414
- - **Deploy**: Saves zip to `~/.openhome/last-deploy.zip` and shows manual upload instructions
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
- Use `--mock` on any command to test with fake data while endpoints are being built.
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
- ### Needs Server-Side Work
454
+ ## Development
444
455
 
445
- - [ ] Upload endpoint (`POST /api/sdk/abilities`)
446
- - [ ] List endpoint (`GET /api/sdk/abilities`)
447
- - [ ] Detail endpoint (`GET /api/sdk/abilities/:id`)
448
- - [ ] Log streaming endpoint (WebSocket)
449
- - [ ] Delete endpoint (`DELETE /api/sdk/abilities/:id`)
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
@@ -301,24 +301,6 @@ async function loginCommand() {
301
301
  }
302
302
  saveApiKey(apiKey);
303
303
  success("API key saved.");
304
- p.note(
305
- [
306
- "Some features (list, toggle, delete, assign) require a session token.",
307
- "To get it: go to app.openhome.com \u2192 open DevTools Console \u2192 run:",
308
- " localStorage.getItem('access_token')"
309
- ].join("\n"),
310
- "Optional: Session Token"
311
- );
312
- const jwtInput = await p.text({
313
- message: "Paste your session token (or press Enter to skip)",
314
- placeholder: "eyJhbGci..."
315
- });
316
- handleCancel(jwtInput);
317
- const jwt = jwtInput?.trim();
318
- if (jwt) {
319
- saveJwt(jwt);
320
- success("Session token saved.");
321
- }
322
304
  if (agents.length > 0) {
323
305
  p.note(
324
306
  agents.map((a) => `${chalk2.bold(a.name)} ${chalk2.gray(a.id)}`).join("\n"),
@@ -1848,6 +1830,12 @@ async function deleteCommand(abilityArg, opts = {}) {
1848
1830
  error("Not authenticated. Run: openhome login");
1849
1831
  process.exit(1);
1850
1832
  }
1833
+ if (!jwt) {
1834
+ error(
1835
+ "This command requires a session token.\nGet it from app.openhome.com \u2192 DevTools \u2192 Application \u2192 Local Storage \u2192 token\nThen run: openhome set-jwt <token>"
1836
+ );
1837
+ process.exit(1);
1838
+ }
1851
1839
  client = new ApiClient(apiKey, getConfig().api_base_url, jwt);
1852
1840
  }
1853
1841
  const s = p.spinner();
@@ -1931,6 +1919,12 @@ async function toggleCommand(abilityArg, opts = {}) {
1931
1919
  error("Not authenticated. Run: openhome login");
1932
1920
  process.exit(1);
1933
1921
  }
1922
+ if (!jwt) {
1923
+ error(
1924
+ "This command requires a session token.\nGet it from app.openhome.com \u2192 DevTools \u2192 Application \u2192 Local Storage \u2192 token\nThen run: openhome set-jwt <token>"
1925
+ );
1926
+ process.exit(1);
1927
+ }
1934
1928
  client = new ApiClient(apiKey, getConfig().api_base_url, jwt);
1935
1929
  }
1936
1930
  const s = p.spinner();
@@ -2024,6 +2018,12 @@ async function assignCommand(opts = {}) {
2024
2018
  error("Not authenticated. Run: openhome login");
2025
2019
  process.exit(1);
2026
2020
  }
2021
+ if (!jwt) {
2022
+ error(
2023
+ "This command requires a session token.\nGet it from app.openhome.com \u2192 DevTools \u2192 Application \u2192 Local Storage \u2192 token\nThen run: openhome set-jwt <token>"
2024
+ );
2025
+ process.exit(1);
2026
+ }
2027
2027
  client = new ApiClient(apiKey, getConfig().api_base_url, jwt);
2028
2028
  }
2029
2029
  const s = p.spinner();
@@ -2127,6 +2127,12 @@ async function listCommand(opts = {}) {
2127
2127
  error("Not authenticated. Run: openhome login");
2128
2128
  process.exit(1);
2129
2129
  }
2130
+ if (!jwt) {
2131
+ error(
2132
+ "This command requires a session token.\nGet it from app.openhome.com \u2192 DevTools \u2192 Application \u2192 Local Storage \u2192 token\nThen run: openhome set-jwt <token>"
2133
+ );
2134
+ process.exit(1);
2135
+ }
2130
2136
  client = new ApiClient(apiKey, getConfig().api_base_url, jwt);
2131
2137
  }
2132
2138
  const s = p.spinner();
@@ -3037,6 +3043,107 @@ async function logsCommand(opts = {}) {
3037
3043
  });
3038
3044
  }
3039
3045
 
3046
+ // src/commands/set-jwt.ts
3047
+ import { execFile } from "child_process";
3048
+ import chalk13 from "chalk";
3049
+ var OPENHOME_URL = "https://app.openhome.com";
3050
+ function openBrowser(url) {
3051
+ try {
3052
+ if (process.platform === "darwin") {
3053
+ execFile("open", [url]);
3054
+ } else if (process.platform === "win32") {
3055
+ execFile("cmd", ["/c", "start", url]);
3056
+ } else {
3057
+ execFile("xdg-open", [url]);
3058
+ }
3059
+ } catch {
3060
+ }
3061
+ }
3062
+ async function setJwtCommand(token) {
3063
+ if (token) {
3064
+ p.intro("\u{1F511} Enable Management Features");
3065
+ try {
3066
+ saveJwt(token.trim());
3067
+ success("Session token saved.");
3068
+ p.outro(
3069
+ "Management commands (list, delete, toggle, assign) are now unlocked."
3070
+ );
3071
+ } catch (err) {
3072
+ error(
3073
+ `Failed to save token: ${err instanceof Error ? err.message : String(err)}`
3074
+ );
3075
+ process.exit(1);
3076
+ }
3077
+ return;
3078
+ }
3079
+ p.intro("\u{1F511} Enable Management Features");
3080
+ p.note(
3081
+ [
3082
+ "Some commands (list, delete, enable/disable abilities) need your",
3083
+ "OpenHome session token to work.",
3084
+ "",
3085
+ "Think of it as a temporary key that proves you're logged in to",
3086
+ "your account. You only need to do this once."
3087
+ ].join("\n"),
3088
+ "What's this?"
3089
+ );
3090
+ console.log("");
3091
+ console.log(
3092
+ chalk13.dim(` Opening ${chalk13.bold("app.openhome.com")} in your browser...`)
3093
+ );
3094
+ openBrowser(OPENHOME_URL);
3095
+ console.log("");
3096
+ p.note(
3097
+ [
3098
+ `${chalk13.bold("Step 1")} Make sure you're logged in to app.openhome.com`,
3099
+ "",
3100
+ `${chalk13.bold("Step 2")} Open the browser console:`,
3101
+ ` Mac \u2192 ${chalk13.cyan("Cmd + Option + J")}`,
3102
+ ` Windows / Linux \u2192 ${chalk13.cyan("F12")}, then click the Console tab`,
3103
+ "",
3104
+ `${chalk13.bold("Step 3")} Paste this command into the console and press Enter:`,
3105
+ "",
3106
+ ` ${chalk13.green("copy(localStorage.getItem('token'))")}`,
3107
+ "",
3108
+ `${chalk13.bold("Step 4")} Your token is now in your clipboard.`,
3109
+ ` Come back here and paste it below.`
3110
+ ].join("\n"),
3111
+ "How to get your token"
3112
+ );
3113
+ const input = await p.password({
3114
+ message: "Paste your token here",
3115
+ validate: (val) => {
3116
+ if (!val || !val.trim()) return "Token is required";
3117
+ if (val.trim().length < 20)
3118
+ return "That doesn't look right \u2014 the token should be much longer";
3119
+ }
3120
+ });
3121
+ if (typeof input === "symbol") {
3122
+ p.cancel("Cancelled.");
3123
+ return;
3124
+ }
3125
+ try {
3126
+ saveJwt(input.trim());
3127
+ success("Session token saved.");
3128
+ p.note(
3129
+ [
3130
+ "These commands are now unlocked:",
3131
+ ` ${chalk13.bold("openhome list")} \u2014 see all your deployed abilities`,
3132
+ ` ${chalk13.bold("openhome delete")} \u2014 remove an ability`,
3133
+ ` ${chalk13.bold("openhome toggle")} \u2014 enable or disable an ability`,
3134
+ ` ${chalk13.bold("openhome assign")} \u2014 link abilities to an agent`
3135
+ ].join("\n"),
3136
+ "You're all set"
3137
+ );
3138
+ p.outro("Done!");
3139
+ } catch (err) {
3140
+ error(
3141
+ `Failed to save token: ${err instanceof Error ? err.message : String(err)}`
3142
+ );
3143
+ process.exit(1);
3144
+ }
3145
+ }
3146
+
3040
3147
  // src/cli.ts
3041
3148
  var __filename = fileURLToPath(import.meta.url);
3042
3149
  var __dirname = dirname(__filename);
@@ -3124,6 +3231,11 @@ async function interactiveMenu() {
3124
3231
  label: "\u{1F464} Who Am I",
3125
3232
  hint: "Show auth, default agent, tracked abilities"
3126
3233
  },
3234
+ {
3235
+ value: "set-jwt",
3236
+ label: "\u{1F511} Enable Management",
3237
+ hint: "Unlock list, delete, toggle, assign"
3238
+ },
3127
3239
  {
3128
3240
  value: "logout",
3129
3241
  label: "\u{1F513} Log Out",
@@ -3170,6 +3282,9 @@ async function interactiveMenu() {
3170
3282
  case "whoami":
3171
3283
  await whoamiCommand();
3172
3284
  break;
3285
+ case "set-jwt":
3286
+ await setJwtCommand();
3287
+ break;
3173
3288
  case "logout":
3174
3289
  await logoutCommand();
3175
3290
  await ensureLoggedIn();
@@ -3235,6 +3350,11 @@ program.command("logs").description("Stream live agent messages and logs").optio
3235
3350
  program.command("whoami").description("Show auth status, default agent, and tracked abilities").action(async () => {
3236
3351
  await whoamiCommand();
3237
3352
  });
3353
+ program.command("set-jwt [token]").description(
3354
+ "Save a session token to enable management commands (list, delete, toggle, assign)"
3355
+ ).action(async (token) => {
3356
+ await setJwtCommand(token);
3357
+ });
3238
3358
  if (process.argv.length <= 2) {
3239
3359
  interactiveMenu().catch((err) => {
3240
3360
  console.error(err instanceof Error ? err.message : String(err));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openhome-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "CLI for managing OpenHome voice AI abilities",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.ts CHANGED
@@ -18,6 +18,7 @@ import { triggerCommand } from "./commands/trigger.js";
18
18
  import { whoamiCommand } from "./commands/whoami.js";
19
19
  import { configEditCommand } from "./commands/config-edit.js";
20
20
  import { logsCommand } from "./commands/logs.js";
21
+ import { setJwtCommand } from "./commands/set-jwt.js";
21
22
  import { p, handleCancel } from "./ui/format.js";
22
23
 
23
24
  // Read version from package.json
@@ -115,6 +116,11 @@ async function interactiveMenu(): Promise<void> {
115
116
  label: "👤 Who Am I",
116
117
  hint: "Show auth, default agent, tracked abilities",
117
118
  },
119
+ {
120
+ value: "set-jwt",
121
+ label: "🔑 Enable Management",
122
+ hint: "Unlock list, delete, toggle, assign",
123
+ },
118
124
  {
119
125
  value: "logout",
120
126
  label: "🔓 Log Out",
@@ -162,6 +168,9 @@ async function interactiveMenu(): Promise<void> {
162
168
  case "whoami":
163
169
  await whoamiCommand();
164
170
  break;
171
+ case "set-jwt":
172
+ await setJwtCommand();
173
+ break;
165
174
  case "logout":
166
175
  await logoutCommand();
167
176
  await ensureLoggedIn();
@@ -316,6 +325,15 @@ program
316
325
  await whoamiCommand();
317
326
  });
318
327
 
328
+ program
329
+ .command("set-jwt [token]")
330
+ .description(
331
+ "Save a session token to enable management commands (list, delete, toggle, assign)",
332
+ )
333
+ .action(async (token?: string) => {
334
+ await setJwtCommand(token);
335
+ });
336
+
319
337
  // ── Entry point: menu if no args, subcommand otherwise ───────────
320
338
 
321
339
  if (process.argv.length <= 2) {
@@ -20,6 +20,12 @@ export async function assignCommand(
20
20
  error("Not authenticated. Run: openhome login");
21
21
  process.exit(1);
22
22
  }
23
+ if (!jwt) {
24
+ error(
25
+ "This command requires a session token.\nGet it from app.openhome.com → DevTools → Application → Local Storage → token\nThen run: openhome set-jwt <token>",
26
+ );
27
+ process.exit(1);
28
+ }
23
29
  client = new ApiClient(apiKey, getConfig().api_base_url, jwt);
24
30
  }
25
31
 
@@ -21,6 +21,12 @@ export async function deleteCommand(
21
21
  error("Not authenticated. Run: openhome login");
22
22
  process.exit(1);
23
23
  }
24
+ if (!jwt) {
25
+ error(
26
+ "This command requires a session token.\nGet it from app.openhome.com → DevTools → Application → Local Storage → token\nThen run: openhome set-jwt <token>",
27
+ );
28
+ process.exit(1);
29
+ }
24
30
  client = new ApiClient(apiKey, getConfig().api_base_url, jwt);
25
31
  }
26
32
 
@@ -36,6 +36,12 @@ export async function listCommand(
36
36
  error("Not authenticated. Run: openhome login");
37
37
  process.exit(1);
38
38
  }
39
+ if (!jwt) {
40
+ error(
41
+ "This command requires a session token.\nGet it from app.openhome.com → DevTools → Application → Local Storage → token\nThen run: openhome set-jwt <token>",
42
+ );
43
+ process.exit(1);
44
+ }
39
45
  client = new ApiClient(apiKey, getConfig().api_base_url, jwt);
40
46
  }
41
47
 
@@ -38,28 +38,6 @@ export async function loginCommand(): Promise<void> {
38
38
  saveApiKey(apiKey as string);
39
39
  success("API key saved.");
40
40
 
41
- // Ask for JWT for full management access
42
- p.note(
43
- [
44
- "Some features (list, toggle, delete, assign) require a session token.",
45
- "To get it: go to app.openhome.com → open DevTools Console → run:",
46
- " localStorage.getItem('access_token')",
47
- ].join("\n"),
48
- "Optional: Session Token",
49
- );
50
-
51
- const jwtInput = await p.text({
52
- message: "Paste your session token (or press Enter to skip)",
53
- placeholder: "eyJhbGci...",
54
- });
55
- handleCancel(jwtInput);
56
-
57
- const jwt = (jwtInput as string | undefined)?.trim();
58
- if (jwt) {
59
- saveJwt(jwt);
60
- success("Session token saved.");
61
- }
62
-
63
41
  // Show agents on this account
64
42
  if (agents.length > 0) {
65
43
  p.note(
@@ -0,0 +1,114 @@
1
+ import { execFile } from "node:child_process";
2
+ import { saveJwt } from "../config/store.js";
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
+ }
21
+
22
+ export async function setJwtCommand(token?: string): Promise<void> {
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);
37
+ }
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;
92
+ }
93
+
94
+ try {
95
+ saveJwt((input as string).trim());
96
+ success("Session token saved.");
97
+ p.note(
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",
106
+ );
107
+ p.outro("Done!");
108
+ } catch (err) {
109
+ error(
110
+ `Failed to save token: ${err instanceof Error ? err.message : String(err)}`,
111
+ );
112
+ process.exit(1);
113
+ }
114
+ }
@@ -21,6 +21,12 @@ export async function toggleCommand(
21
21
  error("Not authenticated. Run: openhome login");
22
22
  process.exit(1);
23
23
  }
24
+ if (!jwt) {
25
+ error(
26
+ "This command requires a session token.\nGet it from app.openhome.com → DevTools → Application → Local Storage → token\nThen run: openhome set-jwt <token>",
27
+ );
28
+ process.exit(1);
29
+ }
24
30
  client = new ApiClient(apiKey, getConfig().api_base_url, jwt);
25
31
  }
26
32