openhome-cli 0.1.17 → 0.1.19
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 +105 -73
- package/package.json +1 -1
- package/src/api/client.ts +20 -4
- package/src/commands/assign.ts +2 -0
- package/src/commands/delete.ts +2 -0
- package/src/commands/deploy.ts +3 -0
- package/src/commands/handle-session-expired.ts +23 -0
- package/src/commands/list.ts +2 -0
- package/src/commands/toggle.ts +2 -0
package/dist/cli.js
CHANGED
|
@@ -49,6 +49,12 @@ var ApiError = class extends Error {
|
|
|
49
49
|
this.name = "ApiError";
|
|
50
50
|
}
|
|
51
51
|
};
|
|
52
|
+
var SessionExpiredError = class extends Error {
|
|
53
|
+
constructor() {
|
|
54
|
+
super("Session token expired or invalid");
|
|
55
|
+
this.name = "SessionExpiredError";
|
|
56
|
+
}
|
|
57
|
+
};
|
|
52
58
|
var ApiClient = class {
|
|
53
59
|
constructor(apiKey, baseUrl, jwt) {
|
|
54
60
|
this.apiKey = apiKey;
|
|
@@ -82,6 +88,9 @@ var ApiClient = class {
|
|
|
82
88
|
throw new NotImplementedError(path);
|
|
83
89
|
}
|
|
84
90
|
const message = body?.detail ?? body?.error?.message ?? response.statusText;
|
|
91
|
+
if (useJwt && (response.status === 401 || message.toLowerCase().includes("token not valid") || message.toLowerCase().includes("token is invalid") || message.toLowerCase().includes("not valid for any token"))) {
|
|
92
|
+
throw new SessionExpiredError();
|
|
93
|
+
}
|
|
85
94
|
throw new ApiError(String(response.status), message);
|
|
86
95
|
}
|
|
87
96
|
return response.json();
|
|
@@ -101,9 +110,7 @@ var ApiClient = class {
|
|
|
101
110
|
const form = new FormData();
|
|
102
111
|
form.append(
|
|
103
112
|
"zip_file",
|
|
104
|
-
new Blob([zipBuffer], {
|
|
105
|
-
type: "application/zip"
|
|
106
|
-
}),
|
|
113
|
+
new Blob([new Uint8Array(zipBuffer)], { type: "application/zip" }),
|
|
107
114
|
"ability.zip"
|
|
108
115
|
);
|
|
109
116
|
if (imageBuffer && imageName) {
|
|
@@ -111,7 +118,7 @@ var ApiClient = class {
|
|
|
111
118
|
const imageMime = imageExt === "jpg" || imageExt === "jpeg" ? "image/jpeg" : "image/png";
|
|
112
119
|
form.append(
|
|
113
120
|
"image_file",
|
|
114
|
-
new Blob([imageBuffer], { type: imageMime }),
|
|
121
|
+
new Blob([new Uint8Array(imageBuffer)], { type: imageMime }),
|
|
115
122
|
imageName
|
|
116
123
|
);
|
|
117
124
|
}
|
|
@@ -684,6 +691,25 @@ async function createAbilityZip(dirPath) {
|
|
|
684
691
|
});
|
|
685
692
|
}
|
|
686
693
|
|
|
694
|
+
// src/commands/handle-session-expired.ts
|
|
695
|
+
import chalk3 from "chalk";
|
|
696
|
+
async function handleIfSessionExpired(err) {
|
|
697
|
+
if (!(err instanceof SessionExpiredError)) return false;
|
|
698
|
+
console.log("");
|
|
699
|
+
p.note(
|
|
700
|
+
[
|
|
701
|
+
"Your session token has expired or been invalidated.",
|
|
702
|
+
"This happens when you log into the OpenHome website again.",
|
|
703
|
+
"",
|
|
704
|
+
`You need to grab a fresh token \u2014 it only takes 30 seconds.`
|
|
705
|
+
].join("\n"),
|
|
706
|
+
chalk3.yellow("Session expired")
|
|
707
|
+
);
|
|
708
|
+
await setupJwt();
|
|
709
|
+
p.note("Token updated. Run the command again to continue.", "Ready");
|
|
710
|
+
return true;
|
|
711
|
+
}
|
|
712
|
+
|
|
687
713
|
// src/api/mock-client.ts
|
|
688
714
|
var MOCK_PERSONALITIES = [
|
|
689
715
|
{ id: "pers_alice", name: "Alice", description: "Friendly assistant" },
|
|
@@ -1219,6 +1245,7 @@ async function deployCommand(pathArg, opts = {}) {
|
|
|
1219
1245
|
p.outro("Zip ready for manual upload.");
|
|
1220
1246
|
return;
|
|
1221
1247
|
}
|
|
1248
|
+
if (await handleIfSessionExpired(err)) return;
|
|
1222
1249
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1223
1250
|
if (msg.toLowerCase().includes("same name")) {
|
|
1224
1251
|
error(`An ability named "${uniqueName}" already exists.`);
|
|
@@ -1341,6 +1368,7 @@ async function deployZip(zipPath, opts = {}) {
|
|
|
1341
1368
|
p.outro("Deployed successfully! \u{1F389}");
|
|
1342
1369
|
} catch (err) {
|
|
1343
1370
|
s.stop("Upload failed.");
|
|
1371
|
+
if (await handleIfSessionExpired(err)) return;
|
|
1344
1372
|
error(`Deploy failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1345
1373
|
process.exit(1);
|
|
1346
1374
|
}
|
|
@@ -2139,7 +2167,7 @@ async function initCommand(nameArg) {
|
|
|
2139
2167
|
}
|
|
2140
2168
|
|
|
2141
2169
|
// src/commands/delete.ts
|
|
2142
|
-
import
|
|
2170
|
+
import chalk4 from "chalk";
|
|
2143
2171
|
async function deleteCommand(abilityArg, opts = {}) {
|
|
2144
2172
|
p.intro("\u{1F5D1}\uFE0F Delete ability");
|
|
2145
2173
|
let client;
|
|
@@ -2194,7 +2222,7 @@ async function deleteCommand(abilityArg, opts = {}) {
|
|
|
2194
2222
|
options: abilities.map((a) => ({
|
|
2195
2223
|
value: a.ability_id,
|
|
2196
2224
|
label: a.unique_name,
|
|
2197
|
-
hint: `${
|
|
2225
|
+
hint: `${chalk4.gray(a.status)} v${a.version}`
|
|
2198
2226
|
}))
|
|
2199
2227
|
});
|
|
2200
2228
|
handleCancel(selected);
|
|
@@ -2222,13 +2250,14 @@ async function deleteCommand(abilityArg, opts = {}) {
|
|
|
2222
2250
|
p.note("API Not Available Yet", "Delete endpoint not yet implemented.");
|
|
2223
2251
|
return;
|
|
2224
2252
|
}
|
|
2253
|
+
if (await handleIfSessionExpired(err)) return;
|
|
2225
2254
|
error(`Delete failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2226
2255
|
process.exit(1);
|
|
2227
2256
|
}
|
|
2228
2257
|
}
|
|
2229
2258
|
|
|
2230
2259
|
// src/commands/toggle.ts
|
|
2231
|
-
import
|
|
2260
|
+
import chalk5 from "chalk";
|
|
2232
2261
|
async function toggleCommand(abilityArg, opts = {}) {
|
|
2233
2262
|
p.intro("\u26A1 Enable / Disable ability");
|
|
2234
2263
|
let client;
|
|
@@ -2283,7 +2312,7 @@ async function toggleCommand(abilityArg, opts = {}) {
|
|
|
2283
2312
|
options: abilities.map((a) => ({
|
|
2284
2313
|
value: a.ability_id,
|
|
2285
2314
|
label: a.unique_name,
|
|
2286
|
-
hint: `${a.status === "disabled" ?
|
|
2315
|
+
hint: `${a.status === "disabled" ? chalk5.gray("disabled") : chalk5.green("enabled")} v${a.version}`
|
|
2287
2316
|
}))
|
|
2288
2317
|
});
|
|
2289
2318
|
handleCancel(selected);
|
|
@@ -2321,13 +2350,14 @@ async function toggleCommand(abilityArg, opts = {}) {
|
|
|
2321
2350
|
p.note("Toggle endpoint not yet implemented.", "API Not Available Yet");
|
|
2322
2351
|
return;
|
|
2323
2352
|
}
|
|
2353
|
+
if (await handleIfSessionExpired(err)) return;
|
|
2324
2354
|
error(`Toggle failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2325
2355
|
process.exit(1);
|
|
2326
2356
|
}
|
|
2327
2357
|
}
|
|
2328
2358
|
|
|
2329
2359
|
// src/commands/assign.ts
|
|
2330
|
-
import
|
|
2360
|
+
import chalk6 from "chalk";
|
|
2331
2361
|
async function assignCommand(opts = {}) {
|
|
2332
2362
|
p.intro("\u{1F517} Assign abilities to agent");
|
|
2333
2363
|
let client;
|
|
@@ -2378,7 +2408,7 @@ async function assignCommand(opts = {}) {
|
|
|
2378
2408
|
options: personalities.map((pers) => ({
|
|
2379
2409
|
value: pers.id,
|
|
2380
2410
|
label: pers.name,
|
|
2381
|
-
hint:
|
|
2411
|
+
hint: chalk6.gray(pers.id)
|
|
2382
2412
|
}))
|
|
2383
2413
|
});
|
|
2384
2414
|
handleCancel(agentId);
|
|
@@ -2416,23 +2446,24 @@ async function assignCommand(opts = {}) {
|
|
|
2416
2446
|
p.note("Assign endpoint not yet implemented.", "API Not Available Yet");
|
|
2417
2447
|
return;
|
|
2418
2448
|
}
|
|
2449
|
+
if (await handleIfSessionExpired(err)) return;
|
|
2419
2450
|
error(`Assign failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2420
2451
|
process.exit(1);
|
|
2421
2452
|
}
|
|
2422
2453
|
}
|
|
2423
2454
|
|
|
2424
2455
|
// src/commands/list.ts
|
|
2425
|
-
import
|
|
2456
|
+
import chalk7 from "chalk";
|
|
2426
2457
|
function statusColor(status) {
|
|
2427
2458
|
switch (status) {
|
|
2428
2459
|
case "active":
|
|
2429
|
-
return
|
|
2460
|
+
return chalk7.green(status);
|
|
2430
2461
|
case "processing":
|
|
2431
|
-
return
|
|
2462
|
+
return chalk7.yellow(status);
|
|
2432
2463
|
case "failed":
|
|
2433
|
-
return
|
|
2464
|
+
return chalk7.red(status);
|
|
2434
2465
|
case "disabled":
|
|
2435
|
-
return
|
|
2466
|
+
return chalk7.gray(status);
|
|
2436
2467
|
default:
|
|
2437
2468
|
return status;
|
|
2438
2469
|
}
|
|
@@ -2484,6 +2515,7 @@ async function listCommand(opts = {}) {
|
|
|
2484
2515
|
p.outro("List endpoint not yet implemented.");
|
|
2485
2516
|
return;
|
|
2486
2517
|
}
|
|
2518
|
+
if (await handleIfSessionExpired(err)) return;
|
|
2487
2519
|
error(
|
|
2488
2520
|
`Failed to list abilities: ${err instanceof Error ? err.message : String(err)}`
|
|
2489
2521
|
);
|
|
@@ -2495,19 +2527,19 @@ async function listCommand(opts = {}) {
|
|
|
2495
2527
|
import { join as join4, resolve as resolve3 } from "path";
|
|
2496
2528
|
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
2497
2529
|
import { homedir as homedir3 } from "os";
|
|
2498
|
-
import
|
|
2530
|
+
import chalk8 from "chalk";
|
|
2499
2531
|
function statusBadge(status) {
|
|
2500
2532
|
switch (status) {
|
|
2501
2533
|
case "active":
|
|
2502
|
-
return
|
|
2534
|
+
return chalk8.bgGreen.black(` ${status.toUpperCase()} `);
|
|
2503
2535
|
case "processing":
|
|
2504
|
-
return
|
|
2536
|
+
return chalk8.bgYellow.black(` ${status.toUpperCase()} `);
|
|
2505
2537
|
case "failed":
|
|
2506
|
-
return
|
|
2538
|
+
return chalk8.bgRed.white(` ${status.toUpperCase()} `);
|
|
2507
2539
|
case "disabled":
|
|
2508
|
-
return
|
|
2540
|
+
return chalk8.bgGray.white(` ${status.toUpperCase()} `);
|
|
2509
2541
|
default:
|
|
2510
|
-
return
|
|
2542
|
+
return chalk8.bgWhite.black(` ${status.toUpperCase()} `);
|
|
2511
2543
|
}
|
|
2512
2544
|
}
|
|
2513
2545
|
function readAbilityName(dir) {
|
|
@@ -2596,14 +2628,14 @@ async function statusCommand(abilityArg, opts = {}) {
|
|
|
2596
2628
|
);
|
|
2597
2629
|
if (ability.validation_errors.length > 0) {
|
|
2598
2630
|
p.note(
|
|
2599
|
-
ability.validation_errors.map((e) =>
|
|
2631
|
+
ability.validation_errors.map((e) => chalk8.red(`\u2717 ${e}`)).join("\n"),
|
|
2600
2632
|
"Validation Errors"
|
|
2601
2633
|
);
|
|
2602
2634
|
}
|
|
2603
2635
|
if (ability.deploy_history.length > 0) {
|
|
2604
2636
|
const historyLines = ability.deploy_history.map((event) => {
|
|
2605
|
-
const icon = event.status === "success" ?
|
|
2606
|
-
return `${icon} v${event.version} ${event.message} ${
|
|
2637
|
+
const icon = event.status === "success" ? chalk8.green("\u2713") : chalk8.red("\u2717");
|
|
2638
|
+
return `${icon} v${event.version} ${event.message} ${chalk8.gray(new Date(event.timestamp).toLocaleString())}`;
|
|
2607
2639
|
});
|
|
2608
2640
|
p.note(historyLines.join("\n"), "Deploy History");
|
|
2609
2641
|
}
|
|
@@ -2623,7 +2655,7 @@ async function statusCommand(abilityArg, opts = {}) {
|
|
|
2623
2655
|
}
|
|
2624
2656
|
|
|
2625
2657
|
// src/commands/agents.ts
|
|
2626
|
-
import
|
|
2658
|
+
import chalk9 from "chalk";
|
|
2627
2659
|
async function agentsCommand(opts = {}) {
|
|
2628
2660
|
p.intro("\u{1F916} Your Agents");
|
|
2629
2661
|
let client;
|
|
@@ -2648,7 +2680,7 @@ async function agentsCommand(opts = {}) {
|
|
|
2648
2680
|
return;
|
|
2649
2681
|
}
|
|
2650
2682
|
p.note(
|
|
2651
|
-
personalities.map((pers) => `${
|
|
2683
|
+
personalities.map((pers) => `${chalk9.bold(pers.name)} ${chalk9.gray(pers.id)}`).join("\n"),
|
|
2652
2684
|
"Agents"
|
|
2653
2685
|
);
|
|
2654
2686
|
const config = getConfig();
|
|
@@ -2702,7 +2734,7 @@ async function logoutCommand() {
|
|
|
2702
2734
|
|
|
2703
2735
|
// src/commands/chat.ts
|
|
2704
2736
|
import WebSocket from "ws";
|
|
2705
|
-
import
|
|
2737
|
+
import chalk10 from "chalk";
|
|
2706
2738
|
import * as readline from "readline";
|
|
2707
2739
|
var PING_INTERVAL = 3e4;
|
|
2708
2740
|
async function chatCommand(agentArg, opts = {}) {
|
|
@@ -2743,7 +2775,7 @@ async function chatCommand(agentArg, opts = {}) {
|
|
|
2743
2775
|
}
|
|
2744
2776
|
}
|
|
2745
2777
|
const wsUrl = `${WS_BASE}${ENDPOINTS.voiceStream(apiKey, agentId)}`;
|
|
2746
|
-
info(`Connecting to agent ${
|
|
2778
|
+
info(`Connecting to agent ${chalk10.bold(agentId)}...`);
|
|
2747
2779
|
await new Promise((resolve6) => {
|
|
2748
2780
|
const ws = new WebSocket(wsUrl, {
|
|
2749
2781
|
perMessageDeflate: false,
|
|
@@ -2759,7 +2791,7 @@ async function chatCommand(agentArg, opts = {}) {
|
|
|
2759
2791
|
output: process.stdout
|
|
2760
2792
|
});
|
|
2761
2793
|
function promptUser() {
|
|
2762
|
-
rl.question(
|
|
2794
|
+
rl.question(chalk10.green("You: "), (input) => {
|
|
2763
2795
|
const trimmed = input.trim();
|
|
2764
2796
|
if (!trimmed) {
|
|
2765
2797
|
promptUser();
|
|
@@ -2795,7 +2827,7 @@ async function chatCommand(agentArg, opts = {}) {
|
|
|
2795
2827
|
}, PING_INTERVAL);
|
|
2796
2828
|
success("Connected! Type a message and press Enter. Type /quit to exit.");
|
|
2797
2829
|
console.log(
|
|
2798
|
-
|
|
2830
|
+
chalk10.gray(
|
|
2799
2831
|
" Tip: Send trigger words to activate abilities (e.g. 'play aquaprime')"
|
|
2800
2832
|
)
|
|
2801
2833
|
);
|
|
@@ -2810,7 +2842,7 @@ async function chatCommand(agentArg, opts = {}) {
|
|
|
2810
2842
|
const data = msg.data;
|
|
2811
2843
|
if (data.content && data.role === "assistant") {
|
|
2812
2844
|
if (data.live && !data.final) {
|
|
2813
|
-
const prefix = `${
|
|
2845
|
+
const prefix = `${chalk10.cyan("Agent:")} `;
|
|
2814
2846
|
readline.clearLine(process.stdout, 0);
|
|
2815
2847
|
readline.cursorTo(process.stdout, 0);
|
|
2816
2848
|
process.stdout.write(`${prefix}${data.content}`);
|
|
@@ -2819,7 +2851,7 @@ async function chatCommand(agentArg, opts = {}) {
|
|
|
2819
2851
|
if (currentResponse !== "") {
|
|
2820
2852
|
console.log("");
|
|
2821
2853
|
} else {
|
|
2822
|
-
console.log(`${
|
|
2854
|
+
console.log(`${chalk10.cyan("Agent:")} ${data.content}`);
|
|
2823
2855
|
}
|
|
2824
2856
|
currentResponse = "";
|
|
2825
2857
|
console.log("");
|
|
@@ -2835,7 +2867,7 @@ async function chatCommand(agentArg, opts = {}) {
|
|
|
2835
2867
|
ws.send(JSON.stringify({ type: "text", data: "bot-speak-end" }));
|
|
2836
2868
|
if (currentResponse === "") {
|
|
2837
2869
|
console.log(
|
|
2838
|
-
|
|
2870
|
+
chalk10.gray(" (Agent sent audio \u2014 text-only mode)")
|
|
2839
2871
|
);
|
|
2840
2872
|
console.log("");
|
|
2841
2873
|
}
|
|
@@ -2895,7 +2927,7 @@ async function chatCommand(agentArg, opts = {}) {
|
|
|
2895
2927
|
|
|
2896
2928
|
// src/commands/trigger.ts
|
|
2897
2929
|
import WebSocket2 from "ws";
|
|
2898
|
-
import
|
|
2930
|
+
import chalk11 from "chalk";
|
|
2899
2931
|
var PING_INTERVAL2 = 3e4;
|
|
2900
2932
|
var RESPONSE_TIMEOUT = 3e4;
|
|
2901
2933
|
async function triggerCommand(phraseArg, opts = {}) {
|
|
@@ -2947,7 +2979,7 @@ async function triggerCommand(phraseArg, opts = {}) {
|
|
|
2947
2979
|
}
|
|
2948
2980
|
}
|
|
2949
2981
|
const wsUrl = `${WS_BASE}${ENDPOINTS.voiceStream(apiKey, agentId)}`;
|
|
2950
|
-
info(`Sending "${
|
|
2982
|
+
info(`Sending "${chalk11.bold(phrase)}" to agent ${chalk11.bold(agentId)}...`);
|
|
2951
2983
|
const s = p.spinner();
|
|
2952
2984
|
s.start("Waiting for response...");
|
|
2953
2985
|
await new Promise((resolve6) => {
|
|
@@ -2977,7 +3009,7 @@ async function triggerCommand(phraseArg, opts = {}) {
|
|
|
2977
3009
|
s.stop("Timed out waiting for response.");
|
|
2978
3010
|
if (fullResponse) {
|
|
2979
3011
|
console.log(`
|
|
2980
|
-
${
|
|
3012
|
+
${chalk11.cyan("Agent:")} ${fullResponse}`);
|
|
2981
3013
|
}
|
|
2982
3014
|
cleanup();
|
|
2983
3015
|
resolve6();
|
|
@@ -2994,7 +3026,7 @@ ${chalk10.cyan("Agent:")} ${fullResponse}`);
|
|
|
2994
3026
|
if (!data.live || data.final) {
|
|
2995
3027
|
s.stop("Response received.");
|
|
2996
3028
|
console.log(`
|
|
2997
|
-
${
|
|
3029
|
+
${chalk11.cyan("Agent:")} ${fullResponse}
|
|
2998
3030
|
`);
|
|
2999
3031
|
cleanup();
|
|
3000
3032
|
resolve6();
|
|
@@ -3011,7 +3043,7 @@ ${chalk10.cyan("Agent:")} ${fullResponse}
|
|
|
3011
3043
|
if (fullResponse) {
|
|
3012
3044
|
s.stop("Response received.");
|
|
3013
3045
|
console.log(`
|
|
3014
|
-
${
|
|
3046
|
+
${chalk11.cyan("Agent:")} ${fullResponse}
|
|
3015
3047
|
`);
|
|
3016
3048
|
cleanup();
|
|
3017
3049
|
resolve6();
|
|
@@ -3050,7 +3082,7 @@ ${chalk10.cyan("Agent:")} ${fullResponse}
|
|
|
3050
3082
|
}
|
|
3051
3083
|
|
|
3052
3084
|
// src/commands/whoami.ts
|
|
3053
|
-
import
|
|
3085
|
+
import chalk12 from "chalk";
|
|
3054
3086
|
import { homedir as homedir4 } from "os";
|
|
3055
3087
|
async function whoamiCommand() {
|
|
3056
3088
|
p.intro("\u{1F464} OpenHome CLI Status");
|
|
@@ -3060,17 +3092,17 @@ async function whoamiCommand() {
|
|
|
3060
3092
|
const home = homedir4();
|
|
3061
3093
|
if (apiKey) {
|
|
3062
3094
|
const masked = apiKey.slice(0, 6) + "..." + apiKey.slice(-4);
|
|
3063
|
-
info(`Authenticated: ${
|
|
3095
|
+
info(`Authenticated: ${chalk12.green("yes")} (key: ${chalk12.gray(masked)})`);
|
|
3064
3096
|
} else {
|
|
3065
3097
|
info(
|
|
3066
|
-
`Authenticated: ${
|
|
3098
|
+
`Authenticated: ${chalk12.red("no")} \u2014 run ${chalk12.bold("openhome login")}`
|
|
3067
3099
|
);
|
|
3068
3100
|
}
|
|
3069
3101
|
if (config.default_personality_id) {
|
|
3070
|
-
info(`Default agent: ${
|
|
3102
|
+
info(`Default agent: ${chalk12.bold(config.default_personality_id)}`);
|
|
3071
3103
|
} else {
|
|
3072
3104
|
info(
|
|
3073
|
-
`Default agent: ${
|
|
3105
|
+
`Default agent: ${chalk12.gray("not set")} \u2014 run ${chalk12.bold("openhome agents")}`
|
|
3074
3106
|
);
|
|
3075
3107
|
}
|
|
3076
3108
|
if (config.api_base_url) {
|
|
@@ -3079,12 +3111,12 @@ async function whoamiCommand() {
|
|
|
3079
3111
|
if (tracked.length > 0) {
|
|
3080
3112
|
const lines = tracked.map((a) => {
|
|
3081
3113
|
const shortPath = a.path.startsWith(home) ? `~${a.path.slice(home.length)}` : a.path;
|
|
3082
|
-
return ` ${
|
|
3114
|
+
return ` ${chalk12.bold(a.name)} ${chalk12.gray(shortPath)}`;
|
|
3083
3115
|
});
|
|
3084
3116
|
p.note(lines.join("\n"), `${tracked.length} tracked ability(s)`);
|
|
3085
3117
|
} else {
|
|
3086
3118
|
info(
|
|
3087
|
-
`Tracked abilities: ${
|
|
3119
|
+
`Tracked abilities: ${chalk12.gray("none")} \u2014 run ${chalk12.bold("openhome init")}`
|
|
3088
3120
|
);
|
|
3089
3121
|
}
|
|
3090
3122
|
p.outro("Done.");
|
|
@@ -3226,7 +3258,7 @@ async function configEditCommand(pathArg) {
|
|
|
3226
3258
|
|
|
3227
3259
|
// src/commands/logs.ts
|
|
3228
3260
|
import WebSocket3 from "ws";
|
|
3229
|
-
import
|
|
3261
|
+
import chalk13 from "chalk";
|
|
3230
3262
|
var PING_INTERVAL3 = 3e4;
|
|
3231
3263
|
async function logsCommand(opts = {}) {
|
|
3232
3264
|
p.intro("\u{1F4E1} Stream agent logs");
|
|
@@ -3266,8 +3298,8 @@ async function logsCommand(opts = {}) {
|
|
|
3266
3298
|
}
|
|
3267
3299
|
}
|
|
3268
3300
|
const wsUrl = `${WS_BASE}${ENDPOINTS.voiceStream(apiKey, agentId)}`;
|
|
3269
|
-
info(`Streaming logs from agent ${
|
|
3270
|
-
info(`Press ${
|
|
3301
|
+
info(`Streaming logs from agent ${chalk13.bold(agentId)}...`);
|
|
3302
|
+
info(`Press ${chalk13.bold("Ctrl+C")} to stop.
|
|
3271
3303
|
`);
|
|
3272
3304
|
await new Promise((resolve6) => {
|
|
3273
3305
|
const ws = new WebSocket3(wsUrl, {
|
|
@@ -3289,33 +3321,33 @@ async function logsCommand(opts = {}) {
|
|
|
3289
3321
|
ws.on("message", (raw) => {
|
|
3290
3322
|
try {
|
|
3291
3323
|
const msg = JSON.parse(raw.toString());
|
|
3292
|
-
const ts =
|
|
3324
|
+
const ts = chalk13.gray((/* @__PURE__ */ new Date()).toLocaleTimeString());
|
|
3293
3325
|
switch (msg.type) {
|
|
3294
3326
|
case "log":
|
|
3295
3327
|
console.log(
|
|
3296
|
-
`${ts} ${
|
|
3328
|
+
`${ts} ${chalk13.blue("[LOG]")} ${JSON.stringify(msg.data)}`
|
|
3297
3329
|
);
|
|
3298
3330
|
break;
|
|
3299
3331
|
case "action":
|
|
3300
3332
|
console.log(
|
|
3301
|
-
`${ts} ${
|
|
3333
|
+
`${ts} ${chalk13.magenta("[ACTION]")} ${JSON.stringify(msg.data)}`
|
|
3302
3334
|
);
|
|
3303
3335
|
break;
|
|
3304
3336
|
case "progress":
|
|
3305
3337
|
console.log(
|
|
3306
|
-
`${ts} ${
|
|
3338
|
+
`${ts} ${chalk13.yellow("[PROGRESS]")} ${JSON.stringify(msg.data)}`
|
|
3307
3339
|
);
|
|
3308
3340
|
break;
|
|
3309
3341
|
case "question":
|
|
3310
3342
|
console.log(
|
|
3311
|
-
`${ts} ${
|
|
3343
|
+
`${ts} ${chalk13.cyan("[QUESTION]")} ${JSON.stringify(msg.data)}`
|
|
3312
3344
|
);
|
|
3313
3345
|
break;
|
|
3314
3346
|
case "message": {
|
|
3315
3347
|
const data = msg.data;
|
|
3316
3348
|
if (data.content && !data.live) {
|
|
3317
|
-
const role = data.role === "assistant" ?
|
|
3318
|
-
console.log(`${ts} ${
|
|
3349
|
+
const role = data.role === "assistant" ? chalk13.cyan("AGENT") : chalk13.green("USER");
|
|
3350
|
+
console.log(`${ts} ${chalk13.white(`[${role}]`)} ${data.content}`);
|
|
3319
3351
|
}
|
|
3320
3352
|
break;
|
|
3321
3353
|
}
|
|
@@ -3334,13 +3366,13 @@ async function logsCommand(opts = {}) {
|
|
|
3334
3366
|
case "error-event": {
|
|
3335
3367
|
const errData = msg.data;
|
|
3336
3368
|
console.log(
|
|
3337
|
-
`${ts} ${
|
|
3369
|
+
`${ts} ${chalk13.red("[ERROR]")} ${errData?.message || errData?.title || JSON.stringify(msg.data)}`
|
|
3338
3370
|
);
|
|
3339
3371
|
break;
|
|
3340
3372
|
}
|
|
3341
3373
|
default:
|
|
3342
3374
|
console.log(
|
|
3343
|
-
`${ts} ${
|
|
3375
|
+
`${ts} ${chalk13.gray(`[${msg.type}]`)} ${JSON.stringify(msg.data)}`
|
|
3344
3376
|
);
|
|
3345
3377
|
break;
|
|
3346
3378
|
}
|
|
@@ -3366,7 +3398,7 @@ async function logsCommand(opts = {}) {
|
|
|
3366
3398
|
}
|
|
3367
3399
|
|
|
3368
3400
|
// src/commands/set-jwt.ts
|
|
3369
|
-
import
|
|
3401
|
+
import chalk14 from "chalk";
|
|
3370
3402
|
async function setJwtCommand(token) {
|
|
3371
3403
|
p.intro("\u{1F511} Enable Management Features");
|
|
3372
3404
|
if (token) {
|
|
@@ -3388,21 +3420,21 @@ async function setJwtCommand(token) {
|
|
|
3388
3420
|
[
|
|
3389
3421
|
"Here's what you'll do:",
|
|
3390
3422
|
"",
|
|
3391
|
-
`${
|
|
3423
|
+
`${chalk14.bold("1.")} We'll open ${chalk14.bold("app.openhome.com")} \u2014 make sure you're logged in`,
|
|
3392
3424
|
"",
|
|
3393
|
-
`${
|
|
3394
|
-
` Mac \u2192 ${
|
|
3395
|
-
` Windows / Linux \u2192 ${
|
|
3425
|
+
`${chalk14.bold("2.")} Open the browser console:`,
|
|
3426
|
+
` Mac \u2192 ${chalk14.cyan("Cmd + Option + J")}`,
|
|
3427
|
+
` Windows / Linux \u2192 ${chalk14.cyan("F12")} then click ${chalk14.cyan("Console")}`,
|
|
3396
3428
|
"",
|
|
3397
|
-
`${
|
|
3398
|
-
` ${
|
|
3399
|
-
` Type ${
|
|
3429
|
+
`${chalk14.bold("3.")} Chrome may show this warning \u2014 it's expected:`,
|
|
3430
|
+
` ${chalk14.yellow(`"Don't paste code you don't understand..."`)}`,
|
|
3431
|
+
` Type ${chalk14.cyan("allow pasting")} and press Enter to dismiss it.`,
|
|
3400
3432
|
"",
|
|
3401
|
-
`${
|
|
3433
|
+
`${chalk14.bold("4.")} Paste this command and press Enter:`,
|
|
3402
3434
|
"",
|
|
3403
|
-
` ${
|
|
3435
|
+
` ${chalk14.green("copy(localStorage.getItem('access_token')), '\u2713 Token copied to clipboard!'")}`,
|
|
3404
3436
|
"",
|
|
3405
|
-
`${
|
|
3437
|
+
`${chalk14.bold("5.")} Your token is copied to clipboard \u2014 paste it back here.`
|
|
3406
3438
|
].join("\n"),
|
|
3407
3439
|
"Enable management features (one-time setup)"
|
|
3408
3440
|
);
|
|
@@ -3423,7 +3455,7 @@ async function setJwtCommand(token) {
|
|
|
3423
3455
|
|
|
3424
3456
|
// src/commands/validate.ts
|
|
3425
3457
|
import { resolve as resolve5 } from "path";
|
|
3426
|
-
import
|
|
3458
|
+
import chalk15 from "chalk";
|
|
3427
3459
|
async function validateCommand(pathArg = ".") {
|
|
3428
3460
|
const targetDir = resolve5(pathArg);
|
|
3429
3461
|
p.intro(`\u{1F50E} Validate ability`);
|
|
@@ -3439,7 +3471,7 @@ async function validateCommand(pathArg = ".") {
|
|
|
3439
3471
|
if (result.errors.length > 0) {
|
|
3440
3472
|
p.note(
|
|
3441
3473
|
result.errors.map(
|
|
3442
|
-
(issue) => `${
|
|
3474
|
+
(issue) => `${chalk15.red("\u2717")} ${issue.file ? chalk15.bold(`[${issue.file}]`) + " " : ""}${issue.message}`
|
|
3443
3475
|
).join("\n"),
|
|
3444
3476
|
`${result.errors.length} Error(s)`
|
|
3445
3477
|
);
|
|
@@ -3447,7 +3479,7 @@ async function validateCommand(pathArg = ".") {
|
|
|
3447
3479
|
if (result.warnings.length > 0) {
|
|
3448
3480
|
p.note(
|
|
3449
3481
|
result.warnings.map(
|
|
3450
|
-
(w) => `${
|
|
3482
|
+
(w) => `${chalk15.yellow("\u26A0")} ${w.file ? chalk15.bold(`[${w.file}]`) + " " : ""}${w.message}`
|
|
3451
3483
|
).join("\n"),
|
|
3452
3484
|
`${result.warnings.length} Warning(s)`
|
|
3453
3485
|
);
|
|
@@ -3493,9 +3525,9 @@ async function checkForUpdates() {
|
|
|
3493
3525
|
);
|
|
3494
3526
|
process.exit(0);
|
|
3495
3527
|
} else {
|
|
3496
|
-
const { default:
|
|
3528
|
+
const { default: chalk16 } = await import("chalk");
|
|
3497
3529
|
console.log(
|
|
3498
|
-
|
|
3530
|
+
chalk16.yellow(
|
|
3499
3531
|
` Update available: v${version} \u2192 v${latest} Run: npm install -g openhome-cli@latest
|
|
3500
3532
|
`
|
|
3501
3533
|
)
|
package/package.json
CHANGED
package/src/api/client.ts
CHANGED
|
@@ -33,6 +33,13 @@ export class ApiError extends Error {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
export class SessionExpiredError extends Error {
|
|
37
|
+
constructor() {
|
|
38
|
+
super("Session token expired or invalid");
|
|
39
|
+
this.name = "SessionExpiredError";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
36
43
|
export interface IApiClient {
|
|
37
44
|
getPersonalities(): Promise<Personality[]>;
|
|
38
45
|
verifyApiKey(apiKey: string): Promise<VerifyApiKeyResponse>;
|
|
@@ -107,6 +114,17 @@ export class ApiClient implements IApiClient {
|
|
|
107
114
|
(body as ApiErrorResponse | null)?.error?.message ??
|
|
108
115
|
response.statusText;
|
|
109
116
|
|
|
117
|
+
// Detect expired/invalid JWT
|
|
118
|
+
if (
|
|
119
|
+
useJwt &&
|
|
120
|
+
(response.status === 401 ||
|
|
121
|
+
message.toLowerCase().includes("token not valid") ||
|
|
122
|
+
message.toLowerCase().includes("token is invalid") ||
|
|
123
|
+
message.toLowerCase().includes("not valid for any token"))
|
|
124
|
+
) {
|
|
125
|
+
throw new SessionExpiredError();
|
|
126
|
+
}
|
|
127
|
+
|
|
110
128
|
throw new ApiError(String(response.status), message);
|
|
111
129
|
}
|
|
112
130
|
|
|
@@ -134,9 +152,7 @@ export class ApiClient implements IApiClient {
|
|
|
134
152
|
const form = new FormData();
|
|
135
153
|
form.append(
|
|
136
154
|
"zip_file",
|
|
137
|
-
new Blob([zipBuffer
|
|
138
|
-
type: "application/zip",
|
|
139
|
-
}),
|
|
155
|
+
new Blob([new Uint8Array(zipBuffer)], { type: "application/zip" }),
|
|
140
156
|
"ability.zip",
|
|
141
157
|
);
|
|
142
158
|
|
|
@@ -146,7 +162,7 @@ export class ApiClient implements IApiClient {
|
|
|
146
162
|
imageExt === "jpg" || imageExt === "jpeg" ? "image/jpeg" : "image/png";
|
|
147
163
|
form.append(
|
|
148
164
|
"image_file",
|
|
149
|
-
new Blob([imageBuffer
|
|
165
|
+
new Blob([new Uint8Array(imageBuffer)], { type: imageMime }),
|
|
150
166
|
imageName,
|
|
151
167
|
);
|
|
152
168
|
}
|
package/src/commands/assign.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ApiClient, NotImplementedError } from "../api/client.js";
|
|
2
|
+
import { handleIfSessionExpired } from "./handle-session-expired.js";
|
|
2
3
|
import { MockApiClient } from "../api/mock-client.js";
|
|
3
4
|
import { getApiKey, getConfig, getJwt } from "../config/store.js";
|
|
4
5
|
import { error, success, info, p, handleCancel } from "../ui/format.js";
|
|
@@ -124,6 +125,7 @@ export async function assignCommand(
|
|
|
124
125
|
return;
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
if (await handleIfSessionExpired(err)) return;
|
|
127
129
|
error(`Assign failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
128
130
|
process.exit(1);
|
|
129
131
|
}
|
package/src/commands/delete.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ApiClient, NotImplementedError } from "../api/client.js";
|
|
2
|
+
import { handleIfSessionExpired } from "./handle-session-expired.js";
|
|
2
3
|
import { MockApiClient } from "../api/mock-client.js";
|
|
3
4
|
import { getApiKey, getConfig, getJwt } from "../config/store.js";
|
|
4
5
|
import { error, success, p, handleCancel } from "../ui/format.js";
|
|
@@ -108,6 +109,7 @@ export async function deleteCommand(
|
|
|
108
109
|
return;
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
if (await handleIfSessionExpired(err)) return;
|
|
111
113
|
error(`Delete failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
112
114
|
process.exit(1);
|
|
113
115
|
}
|
package/src/commands/deploy.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { homedir } from "node:os";
|
|
|
10
10
|
import { validateAbility } from "../validation/validator.js";
|
|
11
11
|
import { createAbilityZip } from "../util/zip.js";
|
|
12
12
|
import { ApiClient, NotImplementedError } from "../api/client.js";
|
|
13
|
+
import { handleIfSessionExpired } from "./handle-session-expired.js";
|
|
13
14
|
import { MockApiClient } from "../api/mock-client.js";
|
|
14
15
|
import {
|
|
15
16
|
getApiKey,
|
|
@@ -549,6 +550,7 @@ export async function deployCommand(
|
|
|
549
550
|
return;
|
|
550
551
|
}
|
|
551
552
|
|
|
553
|
+
if (await handleIfSessionExpired(err)) return;
|
|
552
554
|
const msg = err instanceof Error ? err.message : String(err);
|
|
553
555
|
if (msg.toLowerCase().includes("same name")) {
|
|
554
556
|
error(`An ability named "${uniqueName}" already exists.`);
|
|
@@ -693,6 +695,7 @@ async function deployZip(
|
|
|
693
695
|
p.outro("Deployed successfully! 🎉");
|
|
694
696
|
} catch (err) {
|
|
695
697
|
s.stop("Upload failed.");
|
|
698
|
+
if (await handleIfSessionExpired(err)) return;
|
|
696
699
|
error(`Deploy failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
697
700
|
process.exit(1);
|
|
698
701
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { SessionExpiredError } from "../api/client.js";
|
|
2
|
+
import { setupJwt } from "./login.js";
|
|
3
|
+
import { error, p } from "../ui/format.js";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
|
|
6
|
+
export async function handleIfSessionExpired(err: unknown): Promise<boolean> {
|
|
7
|
+
if (!(err instanceof SessionExpiredError)) return false;
|
|
8
|
+
|
|
9
|
+
console.log("");
|
|
10
|
+
p.note(
|
|
11
|
+
[
|
|
12
|
+
"Your session token has expired or been invalidated.",
|
|
13
|
+
"This happens when you log into the OpenHome website again.",
|
|
14
|
+
"",
|
|
15
|
+
`You need to grab a fresh token — it only takes 30 seconds.`,
|
|
16
|
+
].join("\n"),
|
|
17
|
+
chalk.yellow("Session expired"),
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
await setupJwt();
|
|
21
|
+
p.note("Token updated. Run the command again to continue.", "Ready");
|
|
22
|
+
return true;
|
|
23
|
+
}
|
package/src/commands/list.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ApiClient, NotImplementedError } from "../api/client.js";
|
|
2
|
+
import { handleIfSessionExpired } from "./handle-session-expired.js";
|
|
2
3
|
import { MockApiClient } from "../api/mock-client.js";
|
|
3
4
|
import { getApiKey, getConfig, getJwt } from "../config/store.js";
|
|
4
5
|
import { error, warn, info, table, p } from "../ui/format.js";
|
|
@@ -77,6 +78,7 @@ export async function listCommand(
|
|
|
77
78
|
p.outro("List endpoint not yet implemented.");
|
|
78
79
|
return;
|
|
79
80
|
}
|
|
81
|
+
if (await handleIfSessionExpired(err)) return;
|
|
80
82
|
error(
|
|
81
83
|
`Failed to list abilities: ${err instanceof Error ? err.message : String(err)}`,
|
|
82
84
|
);
|
package/src/commands/toggle.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ApiClient, NotImplementedError } from "../api/client.js";
|
|
2
|
+
import { handleIfSessionExpired } from "./handle-session-expired.js";
|
|
2
3
|
import { MockApiClient } from "../api/mock-client.js";
|
|
3
4
|
import { getApiKey, getConfig, getJwt } from "../config/store.js";
|
|
4
5
|
import { error, success, p, handleCancel } from "../ui/format.js";
|
|
@@ -119,6 +120,7 @@ export async function toggleCommand(
|
|
|
119
120
|
return;
|
|
120
121
|
}
|
|
121
122
|
|
|
123
|
+
if (await handleIfSessionExpired(err)) return;
|
|
122
124
|
error(`Toggle failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
123
125
|
process.exit(1);
|
|
124
126
|
}
|