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/README.md
ADDED
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
# OpenHome CLI
|
|
2
|
+
|
|
3
|
+
Command-line tool for managing OpenHome voice AI abilities. Create and deploy abilities without leaving your terminal.
|
|
4
|
+
|
|
5
|
+
**Status:** v0.1.0 (MVP)
|
|
6
|
+
**Node:** 18+
|
|
7
|
+
**Platform:** macOS (primary), Linux/Windows (config-file fallback for keychain)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
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
|
|
20
|
+
|
|
21
|
+
# Now available everywhere
|
|
22
|
+
openhome
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# 1. Log in with your API key
|
|
31
|
+
openhome login
|
|
32
|
+
|
|
33
|
+
# 2. Create a new ability
|
|
34
|
+
openhome init my-ability
|
|
35
|
+
|
|
36
|
+
# 3. Edit main.py in your editor
|
|
37
|
+
|
|
38
|
+
# 4. Deploy
|
|
39
|
+
openhome deploy ./my-ability
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Or just run `openhome` with no arguments for the interactive menu.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Commands
|
|
47
|
+
|
|
48
|
+
### `openhome` (no arguments)
|
|
49
|
+
|
|
50
|
+
Opens an interactive menu. Use arrow keys to navigate, Enter to select. The menu loops after each command — pick another or choose Exit.
|
|
51
|
+
|
|
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
|
+
If you are not logged in, the CLI prompts for login before showing the menu.
|
|
68
|
+
|
|
69
|
+
All commands below also work directly from the terminal.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
### `openhome login`
|
|
74
|
+
|
|
75
|
+
Authenticate with your OpenHome API key.
|
|
76
|
+
|
|
77
|
+
1. Prompts for your API key (masked input)
|
|
78
|
+
2. Verifies the key against the OpenHome API
|
|
79
|
+
3. Stores the key securely (macOS Keychain, or `~/.openhome/config.json` fallback)
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
openhome login
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
### `openhome init [name]`
|
|
88
|
+
|
|
89
|
+
Scaffold a new ability with all required files.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Interactive (prompts for name)
|
|
93
|
+
openhome init
|
|
94
|
+
|
|
95
|
+
# Direct
|
|
96
|
+
openhome init my-weather-bot
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Prompts:**
|
|
100
|
+
1. **Name** — lowercase, numbers, hyphens only (e.g. `my-ability`)
|
|
101
|
+
2. **Ability type** — Skill (user-triggered), Brain Skill (auto-triggered), or Background Daemon
|
|
102
|
+
3. **Template** — Basic (speak + response) or API (external API with secrets)
|
|
103
|
+
4. **Trigger words** — comma-separated phrases that activate the ability
|
|
104
|
+
|
|
105
|
+
**Generated files:**
|
|
106
|
+
|
|
107
|
+
| File | Purpose |
|
|
108
|
+
|------|---------|
|
|
109
|
+
| `main.py` | Your ability code (Python) |
|
|
110
|
+
| `config.json` | Name + trigger words |
|
|
111
|
+
| `__init__.py` | Required by OpenHome (empty) |
|
|
112
|
+
| `README.md` | Description of your ability |
|
|
113
|
+
|
|
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.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
### `openhome deploy [path]`
|
|
151
|
+
|
|
152
|
+
Validate, zip, and upload an ability to OpenHome.
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# Deploy from current directory
|
|
156
|
+
openhome deploy
|
|
157
|
+
|
|
158
|
+
# Deploy specific ability
|
|
159
|
+
openhome deploy ./my-ability
|
|
160
|
+
|
|
161
|
+
# Preview without uploading
|
|
162
|
+
openhome deploy ./my-ability --dry-run
|
|
163
|
+
|
|
164
|
+
# Test with fake API
|
|
165
|
+
openhome deploy ./my-ability --mock
|
|
166
|
+
|
|
167
|
+
# Attach to specific agent
|
|
168
|
+
openhome deploy ./my-ability --personality pers_alice
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
| Flag | What it does |
|
|
172
|
+
|------|-------------|
|
|
173
|
+
| `--dry-run` | Show what would deploy. No zip, no upload. |
|
|
174
|
+
| `--mock` | Use fake API responses for testing |
|
|
175
|
+
| `--personality <id>` | Override default agent for this deploy |
|
|
176
|
+
|
|
177
|
+
**What happens on deploy:**
|
|
178
|
+
1. Validates ability (blocks if errors)
|
|
179
|
+
2. Creates ZIP (excludes `__pycache__`, `.pyc`, `.git`)
|
|
180
|
+
3. Asks for confirmation
|
|
181
|
+
4. Uploads to OpenHome
|
|
182
|
+
|
|
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).
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
### `openhome list`
|
|
188
|
+
|
|
189
|
+
List all your deployed abilities.
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
openhome list
|
|
193
|
+
|
|
194
|
+
# Test with fake data
|
|
195
|
+
openhome list --mock
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Shows a table with name, version, status, and last update date.
|
|
199
|
+
|
|
200
|
+
Status colors: green = active, yellow = processing, red = failed, gray = disabled.
|
|
201
|
+
|
|
202
|
+
> **Note:** This endpoint is not yet live. Use `--mock` to preview the output format.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
### `openhome agents`
|
|
207
|
+
|
|
208
|
+
View your agents and set a default for deploys.
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
openhome agents
|
|
212
|
+
|
|
213
|
+
# Test with fake data
|
|
214
|
+
openhome agents --mock
|
|
215
|
+
```
|
|
216
|
+
|
|
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).
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
### `openhome status [ability]`
|
|
222
|
+
|
|
223
|
+
Show detailed info for one ability.
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
# By name
|
|
227
|
+
openhome status my-weather-bot
|
|
228
|
+
|
|
229
|
+
# Read name from local config.json
|
|
230
|
+
openhome status
|
|
231
|
+
|
|
232
|
+
# Test with fake data
|
|
233
|
+
openhome status my-weather-bot --mock
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Shows: name, display name, status, version, timestamps, linked agents, validation errors, and deploy history.
|
|
237
|
+
|
|
238
|
+
> **Note:** This endpoint is not yet live. Use `--mock` to preview the output format.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Validation Rules
|
|
243
|
+
|
|
244
|
+
Deploy automatically checks these rules before uploading. Errors block deployment. Warnings do not.
|
|
245
|
+
|
|
246
|
+
### Required Files
|
|
247
|
+
|
|
248
|
+
Every ability must have:
|
|
249
|
+
- `main.py`
|
|
250
|
+
- `__init__.py`
|
|
251
|
+
- `config.json`
|
|
252
|
+
- `README.md`
|
|
253
|
+
|
|
254
|
+
### config.json
|
|
255
|
+
|
|
256
|
+
Must contain:
|
|
257
|
+
- `unique_name` — non-empty string
|
|
258
|
+
- `matching_hotwords` — array of strings
|
|
259
|
+
|
|
260
|
+
### main.py Required Patterns
|
|
261
|
+
|
|
262
|
+
Your main Python file must include:
|
|
263
|
+
|
|
264
|
+
| What | Why |
|
|
265
|
+
|------|-----|
|
|
266
|
+
| Class extending `MatchingCapability` | OpenHome ability base class |
|
|
267
|
+
| `call(self, ...)` method | Entry point OpenHome calls |
|
|
268
|
+
| `worker: AgentWorker = None` | Required field declaration |
|
|
269
|
+
| `capability_worker: CapabilityWorker = None` | Required field declaration |
|
|
270
|
+
| `resume_normal_flow()` call | Returns control to user after ability runs |
|
|
271
|
+
| `# {{register_capability}}` comment | Template marker used by OpenHome |
|
|
272
|
+
|
|
273
|
+
### Blocked Patterns (Errors)
|
|
274
|
+
|
|
275
|
+
These are not allowed in any `.py` file:
|
|
276
|
+
|
|
277
|
+
| Pattern | Use Instead |
|
|
278
|
+
|---------|-------------|
|
|
279
|
+
| `print()` | `self.worker.editor_logging_handler` |
|
|
280
|
+
| `asyncio.sleep()` | `self.worker.session_tasks.sleep()` |
|
|
281
|
+
| `asyncio.create_task()` | `self.worker.session_tasks.create()` |
|
|
282
|
+
| `open()` | `capability_worker` file helpers |
|
|
283
|
+
| `exec()` | Not allowed |
|
|
284
|
+
| `eval()` | Not allowed |
|
|
285
|
+
| `pickle` / `dill` / `shelve` / `marshal` | Not allowed (security) |
|
|
286
|
+
| `assert` | Not allowed |
|
|
287
|
+
| `hashlib.md5()` | Not allowed |
|
|
288
|
+
|
|
289
|
+
### Blocked Imports (Errors)
|
|
290
|
+
|
|
291
|
+
| Import | Why |
|
|
292
|
+
|--------|-----|
|
|
293
|
+
| `redis` | Not available in sandbox |
|
|
294
|
+
| `from src.utils.db_handler` | Internal, not for abilities |
|
|
295
|
+
| `connection_manager` | Internal, not for abilities |
|
|
296
|
+
| `user_config` | Internal, not for abilities |
|
|
297
|
+
|
|
298
|
+
### Warnings (Do Not Block)
|
|
299
|
+
|
|
300
|
+
| Check | Message |
|
|
301
|
+
|-------|---------|
|
|
302
|
+
| Hardcoded API keys (`sk_...`, `key_...`) | Use `capability_worker.get_single_key()` instead |
|
|
303
|
+
| Multiple class definitions | Only one `MatchingCapability` class expected per ability |
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Configuration
|
|
308
|
+
|
|
309
|
+
### Storage Location
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
~/.openhome/
|
|
313
|
+
config.json # Settings + fallback API key
|
|
314
|
+
last-deploy.zip # Saved when upload endpoint unavailable
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Config Fields
|
|
318
|
+
|
|
319
|
+
| Field | Purpose | Default |
|
|
320
|
+
|-------|---------|---------|
|
|
321
|
+
| `api_base_url` | Override API endpoint | `https://app.openhome.com` |
|
|
322
|
+
| `default_personality_id` | Default agent for deploys | (none) |
|
|
323
|
+
| `api_key` | Fallback key storage | (none — prefers Keychain) |
|
|
324
|
+
|
|
325
|
+
### API Key Storage
|
|
326
|
+
|
|
327
|
+
On macOS, your API key is stored in the system Keychain (service: `openhome-cli`, account: `api-key`). On other platforms, it falls back to `~/.openhome/config.json`.
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
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
|
|
389
|
+
|
|
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 |
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## API Status
|
|
404
|
+
|
|
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
|
|
416
|
+
|
|
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.
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
## Roadmap
|
|
433
|
+
|
|
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
|
+
- [ ] `openhome watch` — Auto-deploy on file changes
|
|
440
|
+
- [ ] Background Daemon and Brain Skill templates
|
|
441
|
+
- [ ] Cross-platform secure key storage (Windows Credential Manager, Linux Secret Service)
|
|
442
|
+
|
|
443
|
+
### Needs Server-Side Work
|
|
444
|
+
|
|
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`)
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## Terminology
|
|
454
|
+
|
|
455
|
+
| Term | Meaning |
|
|
456
|
+
|------|---------|
|
|
457
|
+
| **Ability** | A Python plugin that adds a feature to an OpenHome agent |
|
|
458
|
+
| **Agent** | A voice AI personality that can have multiple abilities (called "personality" in the API) |
|
|
459
|
+
| **Trigger words** | Spoken phrases that activate an ability (called `matching_hotwords` in config.json) |
|
|
460
|
+
| **Skill** | An ability type that runs when the user triggers it |
|
|
461
|
+
| **Brain Skill** | An ability type that the agent triggers automatically |
|
|
462
|
+
| **Background Daemon** | An ability type that runs continuously from session start |
|
|
463
|
+
| **CapabilityWorker** | The runtime helper object for speaking, listening, file I/O, and secrets |
|
|
464
|
+
| **AgentWorker** | The runtime object for logging and session management |
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## License
|
|
469
|
+
|
|
470
|
+
MIT
|
package/bin/openhome.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// src/config/store.ts
|
|
2
|
+
import {
|
|
3
|
+
readFileSync,
|
|
4
|
+
writeFileSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
existsSync,
|
|
7
|
+
readdirSync
|
|
8
|
+
} from "fs";
|
|
9
|
+
import { homedir } from "os";
|
|
10
|
+
import { join } from "path";
|
|
11
|
+
|
|
12
|
+
// src/config/keychain.ts
|
|
13
|
+
import { execFileSync } from "child_process";
|
|
14
|
+
var SERVICE = "openhome-cli";
|
|
15
|
+
var ACCOUNT = "api-key";
|
|
16
|
+
function keychainGet(service = SERVICE, account = ACCOUNT) {
|
|
17
|
+
try {
|
|
18
|
+
const result = execFileSync(
|
|
19
|
+
"security",
|
|
20
|
+
["find-generic-password", "-a", account, "-s", service, "-w"],
|
|
21
|
+
{ encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }
|
|
22
|
+
);
|
|
23
|
+
return result.trim() || null;
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function keychainSet(password, service = SERVICE, account = ACCOUNT) {
|
|
29
|
+
try {
|
|
30
|
+
execFileSync(
|
|
31
|
+
"security",
|
|
32
|
+
[
|
|
33
|
+
"add-generic-password",
|
|
34
|
+
"-a",
|
|
35
|
+
account,
|
|
36
|
+
"-s",
|
|
37
|
+
service,
|
|
38
|
+
"-w",
|
|
39
|
+
password,
|
|
40
|
+
"-U"
|
|
41
|
+
],
|
|
42
|
+
{ encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }
|
|
43
|
+
);
|
|
44
|
+
return true;
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function keychainDelete(service = SERVICE, account = ACCOUNT) {
|
|
50
|
+
try {
|
|
51
|
+
execFileSync(
|
|
52
|
+
"security",
|
|
53
|
+
["delete-generic-password", "-a", account, "-s", service],
|
|
54
|
+
{ encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }
|
|
55
|
+
);
|
|
56
|
+
return true;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/config/store.ts
|
|
63
|
+
var CONFIG_DIR = join(homedir(), ".openhome");
|
|
64
|
+
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
65
|
+
function ensureConfigDir() {
|
|
66
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
67
|
+
mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function getConfig() {
|
|
71
|
+
ensureConfigDir();
|
|
72
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
73
|
+
return {};
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const raw = readFileSync(CONFIG_FILE, "utf8");
|
|
77
|
+
return JSON.parse(raw);
|
|
78
|
+
} catch {
|
|
79
|
+
return {};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function saveConfig(config) {
|
|
83
|
+
ensureConfigDir();
|
|
84
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), {
|
|
85
|
+
encoding: "utf8",
|
|
86
|
+
mode: 384
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function getApiKey() {
|
|
90
|
+
const fromKeychain = keychainGet();
|
|
91
|
+
if (fromKeychain) return fromKeychain;
|
|
92
|
+
const config = getConfig();
|
|
93
|
+
return config.api_key ?? null;
|
|
94
|
+
}
|
|
95
|
+
function registerAbility(name, absPath) {
|
|
96
|
+
const config = getConfig();
|
|
97
|
+
const abilities = config.abilities ?? [];
|
|
98
|
+
const idx = abilities.findIndex((a) => a.path === absPath);
|
|
99
|
+
if (idx >= 0) {
|
|
100
|
+
abilities[idx].name = name;
|
|
101
|
+
} else {
|
|
102
|
+
abilities.push({
|
|
103
|
+
name,
|
|
104
|
+
path: absPath,
|
|
105
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
config.abilities = abilities;
|
|
109
|
+
saveConfig(config);
|
|
110
|
+
}
|
|
111
|
+
function getTrackedAbilities() {
|
|
112
|
+
const config = getConfig();
|
|
113
|
+
const tracked = (config.abilities ?? []).filter((a) => {
|
|
114
|
+
try {
|
|
115
|
+
return existsSync(join(a.path, "config.json"));
|
|
116
|
+
} catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
const abilitiesDir = join(process.cwd(), "abilities");
|
|
121
|
+
if (existsSync(abilitiesDir)) {
|
|
122
|
+
try {
|
|
123
|
+
const dirs = readdirSync(abilitiesDir, { withFileTypes: true });
|
|
124
|
+
for (const d of dirs) {
|
|
125
|
+
if (!d.isDirectory()) continue;
|
|
126
|
+
const dirPath = join(abilitiesDir, d.name);
|
|
127
|
+
const configPath = join(dirPath, "config.json");
|
|
128
|
+
if (!existsSync(configPath)) continue;
|
|
129
|
+
if (tracked.some((a) => a.path === dirPath)) continue;
|
|
130
|
+
try {
|
|
131
|
+
const abilityConfig = JSON.parse(
|
|
132
|
+
readFileSync(configPath, "utf8")
|
|
133
|
+
);
|
|
134
|
+
tracked.push({
|
|
135
|
+
name: abilityConfig.unique_name ?? d.name,
|
|
136
|
+
path: dirPath,
|
|
137
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
138
|
+
});
|
|
139
|
+
} catch {
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return tracked;
|
|
146
|
+
}
|
|
147
|
+
function saveApiKey(key) {
|
|
148
|
+
const saved = keychainSet(key);
|
|
149
|
+
if (!saved) {
|
|
150
|
+
const config = getConfig();
|
|
151
|
+
config.api_key = key;
|
|
152
|
+
saveConfig(config);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export {
|
|
157
|
+
keychainDelete,
|
|
158
|
+
getConfig,
|
|
159
|
+
saveConfig,
|
|
160
|
+
getApiKey,
|
|
161
|
+
registerAbility,
|
|
162
|
+
getTrackedAbilities,
|
|
163
|
+
saveApiKey
|
|
164
|
+
};
|
package/dist/cli.d.ts
ADDED