openhome-cli 0.1.3 → 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/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getApiKey,
3
3
  getConfig,
4
- getJwt as getJwt2,
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: "Enter your OpenHome API key",
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((resolve5, reject) => {
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
- resolve5(Buffer.concat(chunks));
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 = getJwt2() ?? void 0;
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 = getJwt2() ?? void 0;
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 = getJwt2() ?? void 0;
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 = getJwt2() ?? void 0;
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((resolve5) => {
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
- resolve5();
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
- resolve5();
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((resolve5) => {
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
- resolve5();
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
- resolve5();
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
- resolve5();
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
- resolve5();
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
- resolve5();
2751
+ resolve6();
2721
2752
  });
2722
2753
  ws.on("close", () => {
2723
2754
  if (pingInterval) clearInterval(pingInterval);
2724
2755
  if (responseTimer) clearTimeout(responseTimer);
2725
- resolve5();
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((resolve5) => {
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
- resolve5();
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
- resolve5();
3067
+ resolve6();
3037
3068
  });
3038
3069
  process.on("SIGINT", () => {
3039
3070
  console.log("");
@@ -3044,17 +3075,17 @@ async function logsCommand(opts = {}) {
3044
3075
  }
3045
3076
 
3046
3077
  // src/commands/set-jwt.ts
3047
- import { execFile } from "child_process";
3078
+ import { execFile as execFile2 } from "child_process";
3048
3079
  import chalk13 from "chalk";
3049
3080
  var OPENHOME_URL = "https://app.openhome.com";
3050
- function openBrowser(url) {
3081
+ function openBrowser2(url) {
3051
3082
  try {
3052
3083
  if (process.platform === "darwin") {
3053
- execFile("open", [url]);
3084
+ execFile2("open", [url]);
3054
3085
  } else if (process.platform === "win32") {
3055
- execFile("cmd", ["/c", "start", url]);
3086
+ execFile2("cmd", ["/c", "start", url]);
3056
3087
  } else {
3057
- execFile("xdg-open", [url]);
3088
+ execFile2("xdg-open", [url]);
3058
3089
  }
3059
3090
  } catch {
3060
3091
  }
@@ -3091,7 +3122,7 @@ async function setJwtCommand(token) {
3091
3122
  console.log(
3092
3123
  chalk13.dim(` Opening ${chalk13.bold("app.openhome.com")} in your browser...`)
3093
3124
  );
3094
- openBrowser(OPENHOME_URL);
3125
+ openBrowser2(OPENHOME_URL);
3095
3126
  console.log("");
3096
3127
  p.note(
3097
3128
  [
@@ -3144,6 +3175,45 @@ async function setJwtCommand(token) {
3144
3175
  }
3145
3176
  }
3146
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
+
3147
3217
  // src/cli.ts
3148
3218
  var __filename = fileURLToPath(import.meta.url);
3149
3219
  var __dirname = dirname(__filename);
@@ -3176,16 +3246,6 @@ async function interactiveMenu() {
3176
3246
  label: "\u2728 Create Ability",
3177
3247
  hint: "Scaffold and deploy a new ability"
3178
3248
  },
3179
- {
3180
- value: "chat",
3181
- label: "\u{1F4AC} Chat",
3182
- hint: "Talk to your agent"
3183
- },
3184
- {
3185
- value: "trigger",
3186
- label: "\u26A1 Trigger",
3187
- hint: "Fire an ability remotely with a phrase"
3188
- },
3189
3249
  {
3190
3250
  value: "list",
3191
3251
  label: "\u{1F4CB} My Abilities",
@@ -3212,30 +3272,15 @@ async function interactiveMenu() {
3212
3272
  hint: "View agents and set default"
3213
3273
  },
3214
3274
  {
3215
- value: "status",
3216
- label: "\u{1F50D} Status",
3217
- hint: "Check ability status"
3218
- },
3219
- {
3220
- value: "config",
3221
- label: "\u2699\uFE0F Edit Config",
3222
- hint: "Update trigger words, description, category"
3275
+ value: "chat",
3276
+ label: "\u{1F4AC} Chat",
3277
+ hint: "Talk to your agent"
3223
3278
  },
3224
3279
  {
3225
3280
  value: "logs",
3226
3281
  label: "\u{1F4E1} Logs",
3227
3282
  hint: "Stream live agent messages"
3228
3283
  },
3229
- {
3230
- value: "whoami",
3231
- label: "\u{1F464} Who Am I",
3232
- hint: "Show auth, default agent, tracked abilities"
3233
- },
3234
- {
3235
- value: "set-jwt",
3236
- label: "\u{1F511} Enable Management",
3237
- hint: "Unlock list, delete, toggle, assign"
3238
- },
3239
3284
  {
3240
3285
  value: "logout",
3241
3286
  label: "\u{1F513} Log Out",
@@ -3249,12 +3294,6 @@ async function interactiveMenu() {
3249
3294
  case "init":
3250
3295
  await initCommand();
3251
3296
  break;
3252
- case "chat":
3253
- await chatCommand();
3254
- break;
3255
- case "trigger":
3256
- await triggerCommand();
3257
- break;
3258
3297
  case "list":
3259
3298
  await listCommand();
3260
3299
  break;
@@ -3270,21 +3309,12 @@ async function interactiveMenu() {
3270
3309
  case "agents":
3271
3310
  await agentsCommand();
3272
3311
  break;
3273
- case "status":
3274
- await statusCommand();
3275
- break;
3276
- case "config":
3277
- await configEditCommand();
3312
+ case "chat":
3313
+ await chatCommand();
3278
3314
  break;
3279
3315
  case "logs":
3280
3316
  await logsCommand();
3281
3317
  break;
3282
- case "whoami":
3283
- await whoamiCommand();
3284
- break;
3285
- case "set-jwt":
3286
- await setJwtCommand();
3287
- break;
3288
3318
  case "logout":
3289
3319
  await logoutCommand();
3290
3320
  await ensureLoggedIn();
@@ -3350,6 +3380,9 @@ program.command("logs").description("Stream live agent messages and logs").optio
3350
3380
  program.command("whoami").description("Show auth status, default agent, and tracked abilities").action(async () => {
3351
3381
  await whoamiCommand();
3352
3382
  });
3383
+ program.command("validate [path]").description("Check an ability for errors before deploying").action(async (path) => {
3384
+ await validateCommand(path);
3385
+ });
3353
3386
  program.command("set-jwt [token]").description(
3354
3387
  "Save a session token to enable management commands (list, delete, toggle, assign)"
3355
3388
  ).action(async (token) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openhome-cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "CLI for managing OpenHome voice AI abilities",
5
5
  "type": "module",
6
6
  "bin": {
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,30 +88,15 @@ async function interactiveMenu(): Promise<void> {
97
88
  hint: "View agents and set default",
98
89
  },
99
90
  {
100
- value: "status",
101
- label: "🔍 Status",
102
- hint: "Check ability status",
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
- {
120
- value: "set-jwt",
121
- label: "🔑 Enable Management",
122
- hint: "Unlock list, delete, toggle, assign",
123
- },
124
100
  {
125
101
  value: "logout",
126
102
  label: "🔓 Log Out",
@@ -135,12 +111,6 @@ async function interactiveMenu(): Promise<void> {
135
111
  case "init":
136
112
  await initCommand();
137
113
  break;
138
- case "chat":
139
- await chatCommand();
140
- break;
141
- case "trigger":
142
- await triggerCommand();
143
- break;
144
114
  case "list":
145
115
  await listCommand();
146
116
  break;
@@ -156,21 +126,12 @@ async function interactiveMenu(): Promise<void> {
156
126
  case "agents":
157
127
  await agentsCommand();
158
128
  break;
159
- case "status":
160
- await statusCommand();
161
- break;
162
- case "config":
163
- await configEditCommand();
129
+ case "chat":
130
+ await chatCommand();
164
131
  break;
165
132
  case "logs":
166
133
  await logsCommand();
167
134
  break;
168
- case "whoami":
169
- await whoamiCommand();
170
- break;
171
- case "set-jwt":
172
- await setJwtCommand();
173
- break;
174
135
  case "logout":
175
136
  await logoutCommand();
176
137
  await ensureLoggedIn();
@@ -325,6 +286,13 @@ program
325
286
  await whoamiCommand();
326
287
  });
327
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
+
328
296
  program
329
297
  .command("set-jwt [token]")
330
298
  .description(
@@ -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, saveJwt } from "../config/store.js";
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: "Enter your OpenHome API key",
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
@@ -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 { getApiKey, getConfig, getTrackedAbilities } from "../config/store.js";
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