openhome-cli 0.1.0 → 0.1.2
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/{chunk-Q4UKUXDB.js → chunk-OAKGNZQM.js} +11 -1
- package/dist/cli.js +1116 -1005
- package/dist/{store-DR7EKQ5T.js → store-USDMWKXY.js} +7 -3
- package/package.json +1 -1
- package/src/api/client.ts +76 -25
- package/src/api/contracts.ts +21 -0
- package/src/api/endpoints.ts +1 -2
- package/src/cli.ts +11 -9
- package/src/commands/assign.ts +11 -4
- package/src/commands/delete.ts +11 -4
- package/src/commands/init.ts +14 -2
- package/src/commands/list.ts +11 -4
- package/src/commands/login.ts +1 -1
- package/src/commands/set-jwt.ts +40 -0
- package/src/commands/status.ts +4 -3
- package/src/commands/toggle.ts +11 -4
- package/src/config/store.ts +11 -1
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getApiKey,
|
|
3
3
|
getConfig,
|
|
4
|
+
getJwt,
|
|
4
5
|
getTrackedAbilities,
|
|
5
6
|
registerAbility,
|
|
6
7
|
saveApiKey,
|
|
7
|
-
saveConfig
|
|
8
|
-
|
|
8
|
+
saveConfig,
|
|
9
|
+
saveJwt
|
|
10
|
+
} from "./chunk-OAKGNZQM.js";
|
|
9
11
|
export {
|
|
10
12
|
getApiKey,
|
|
11
13
|
getConfig,
|
|
14
|
+
getJwt,
|
|
12
15
|
getTrackedAbilities,
|
|
13
16
|
registerAbility,
|
|
14
17
|
saveApiKey,
|
|
15
|
-
saveConfig
|
|
18
|
+
saveConfig,
|
|
19
|
+
saveJwt
|
|
16
20
|
};
|
package/package.json
CHANGED
package/src/api/client.ts
CHANGED
|
@@ -10,6 +10,8 @@ import type {
|
|
|
10
10
|
DeleteCapabilityResponse,
|
|
11
11
|
ToggleCapabilityResponse,
|
|
12
12
|
AssignCapabilitiesResponse,
|
|
13
|
+
InstalledCapability,
|
|
14
|
+
AbilitySummaryWithExtras,
|
|
13
15
|
} from "./contracts.js";
|
|
14
16
|
import { API_BASE, ENDPOINTS } from "./endpoints.js";
|
|
15
17
|
|
|
@@ -59,6 +61,7 @@ export class ApiClient implements IApiClient {
|
|
|
59
61
|
constructor(
|
|
60
62
|
private readonly apiKey: string,
|
|
61
63
|
baseUrl?: string,
|
|
64
|
+
private readonly jwt?: string,
|
|
62
65
|
) {
|
|
63
66
|
this.baseUrl = baseUrl ?? API_BASE;
|
|
64
67
|
if (!this.baseUrl.startsWith("https://")) {
|
|
@@ -69,33 +72,42 @@ export class ApiClient implements IApiClient {
|
|
|
69
72
|
private async request<T>(
|
|
70
73
|
path: string,
|
|
71
74
|
options: RequestInit = {},
|
|
75
|
+
useJwt = false,
|
|
72
76
|
): Promise<T> {
|
|
77
|
+
const token = useJwt ? this.jwt : this.apiKey;
|
|
73
78
|
const url = `${this.baseUrl}${path}`;
|
|
74
79
|
const response = await fetch(url, {
|
|
75
80
|
...options,
|
|
76
81
|
headers: {
|
|
77
|
-
Authorization: `Bearer ${
|
|
82
|
+
Authorization: `Bearer ${token}`,
|
|
78
83
|
...(options.headers ?? {}),
|
|
79
84
|
},
|
|
80
85
|
});
|
|
81
86
|
|
|
82
87
|
if (!response.ok) {
|
|
83
|
-
|
|
88
|
+
if (response.status === 404) {
|
|
89
|
+
throw new NotImplementedError(path);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let body: Record<string, unknown> | null = null;
|
|
84
93
|
try {
|
|
85
|
-
body = (await response.json()) as
|
|
94
|
+
body = (await response.json()) as Record<string, unknown>;
|
|
86
95
|
} catch {
|
|
87
96
|
// ignore parse errors
|
|
88
97
|
}
|
|
89
98
|
|
|
90
|
-
if (
|
|
99
|
+
if (
|
|
100
|
+
(body as ApiErrorResponse | null)?.error?.code === "NOT_IMPLEMENTED"
|
|
101
|
+
) {
|
|
91
102
|
throw new NotImplementedError(path);
|
|
92
103
|
}
|
|
93
104
|
|
|
94
|
-
|
|
95
|
-
body?.
|
|
96
|
-
body?.error?.message ??
|
|
97
|
-
|
|
98
|
-
|
|
105
|
+
const message =
|
|
106
|
+
(body?.detail as string) ??
|
|
107
|
+
(body as ApiErrorResponse | null)?.error?.message ??
|
|
108
|
+
response.statusText;
|
|
109
|
+
|
|
110
|
+
throw new ApiError(String(response.status), message);
|
|
99
111
|
}
|
|
100
112
|
|
|
101
113
|
return response.json() as Promise<T>;
|
|
@@ -154,15 +166,38 @@ export class ApiClient implements IApiClient {
|
|
|
154
166
|
}
|
|
155
167
|
|
|
156
168
|
async listAbilities(): Promise<ListAbilitiesResponse> {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
169
|
+
const data = await this.request<InstalledCapability[]>(
|
|
170
|
+
ENDPOINTS.listCapabilities,
|
|
171
|
+
{ method: "GET" },
|
|
172
|
+
true, // uses JWT
|
|
173
|
+
);
|
|
174
|
+
// Normalise to AbilitySummary shape
|
|
175
|
+
return {
|
|
176
|
+
abilities: data.map((c) => ({
|
|
177
|
+
ability_id: String(c.id),
|
|
178
|
+
unique_name: c.name,
|
|
179
|
+
display_name: c.name,
|
|
180
|
+
version: 1,
|
|
181
|
+
status: c.enabled ? "active" : "disabled",
|
|
182
|
+
personality_ids: [],
|
|
183
|
+
created_at: c.last_updated ?? new Date().toISOString(),
|
|
184
|
+
updated_at: c.last_updated ?? new Date().toISOString(),
|
|
185
|
+
trigger_words: c.trigger_words,
|
|
186
|
+
category: c.category,
|
|
187
|
+
})),
|
|
188
|
+
};
|
|
160
189
|
}
|
|
161
190
|
|
|
162
191
|
async getAbility(id: string): Promise<GetAbilityResponse> {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
192
|
+
// No single-get endpoint — fetch all and filter
|
|
193
|
+
const { abilities } = await this.listAbilities();
|
|
194
|
+
const found = abilities.find(
|
|
195
|
+
(a) => a.ability_id === id || a.unique_name === id,
|
|
196
|
+
);
|
|
197
|
+
if (!found) {
|
|
198
|
+
throw new ApiError("404", `Ability "${id}" not found.`);
|
|
199
|
+
}
|
|
200
|
+
return { ...found, validation_errors: [], deploy_history: [] };
|
|
166
201
|
}
|
|
167
202
|
|
|
168
203
|
async verifyApiKey(apiKey: string): Promise<VerifyApiKeyResponse> {
|
|
@@ -205,13 +240,26 @@ export class ApiClient implements IApiClient {
|
|
|
205
240
|
id: string,
|
|
206
241
|
enabled: boolean,
|
|
207
242
|
): Promise<ToggleCapabilityResponse> {
|
|
243
|
+
// Fetch current state first so we can PUT back the full object
|
|
244
|
+
const { abilities } = await this.listAbilities();
|
|
245
|
+
const current = abilities.find((a) => a.ability_id === id);
|
|
246
|
+
if (!current) {
|
|
247
|
+
throw new ApiError("404", `Ability "${id}" not found.`);
|
|
248
|
+
}
|
|
208
249
|
return this.request<ToggleCapabilityResponse>(
|
|
209
250
|
ENDPOINTS.editInstalledCapability(id),
|
|
210
251
|
{
|
|
211
|
-
method: "
|
|
252
|
+
method: "PUT",
|
|
212
253
|
headers: { "Content-Type": "application/json" },
|
|
213
|
-
body: JSON.stringify({
|
|
254
|
+
body: JSON.stringify({
|
|
255
|
+
enabled,
|
|
256
|
+
name: current.unique_name,
|
|
257
|
+
category: (current as AbilitySummaryWithExtras).category ?? "skill",
|
|
258
|
+
trigger_words:
|
|
259
|
+
(current as AbilitySummaryWithExtras).trigger_words ?? [],
|
|
260
|
+
}),
|
|
214
261
|
},
|
|
262
|
+
true, // uses JWT
|
|
215
263
|
);
|
|
216
264
|
}
|
|
217
265
|
|
|
@@ -219,13 +267,16 @@ export class ApiClient implements IApiClient {
|
|
|
219
267
|
personalityId: string,
|
|
220
268
|
capabilityIds: number[],
|
|
221
269
|
): Promise<AssignCapabilitiesResponse> {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
270
|
+
// Uses multipart/form-data — JSON is rejected
|
|
271
|
+
const form = new FormData();
|
|
272
|
+
form.append("personality_id", personalityId);
|
|
273
|
+
for (const capId of capabilityIds) {
|
|
274
|
+
form.append("matching_capabilities", String(capId));
|
|
275
|
+
}
|
|
276
|
+
return this.request<AssignCapabilitiesResponse>(
|
|
277
|
+
ENDPOINTS.editPersonality,
|
|
278
|
+
{ method: "PUT", body: form },
|
|
279
|
+
true, // uses JWT
|
|
280
|
+
);
|
|
230
281
|
}
|
|
231
282
|
}
|
package/src/api/contracts.ts
CHANGED
|
@@ -48,6 +48,27 @@ export interface AbilitySummary {
|
|
|
48
48
|
updated_at: string;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
// Extended with fields from the real API
|
|
52
|
+
export interface AbilitySummaryWithExtras extends AbilitySummary {
|
|
53
|
+
trigger_words?: string[];
|
|
54
|
+
category?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Raw shape returned by get-installed-capabilities
|
|
58
|
+
export interface InstalledCapability {
|
|
59
|
+
id: number;
|
|
60
|
+
name: string;
|
|
61
|
+
category: string;
|
|
62
|
+
enabled: boolean;
|
|
63
|
+
trigger_words: string[];
|
|
64
|
+
last_updated?: string;
|
|
65
|
+
image_file?: string;
|
|
66
|
+
default?: boolean;
|
|
67
|
+
system_capability?: boolean;
|
|
68
|
+
agent_capability?: boolean;
|
|
69
|
+
shortcut?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
51
72
|
export interface ListAbilitiesResponse {
|
|
52
73
|
abilities: AbilitySummary[];
|
|
53
74
|
}
|
package/src/api/endpoints.ts
CHANGED
|
@@ -6,8 +6,7 @@ export const ENDPOINTS = {
|
|
|
6
6
|
getPersonalities: "/api/sdk/get_personalities",
|
|
7
7
|
verifyApiKey: "/api/sdk/verify_apikey/",
|
|
8
8
|
uploadCapability: "/api/capabilities/add-capability/",
|
|
9
|
-
listCapabilities: "/api/capabilities/get-
|
|
10
|
-
getCapability: (id: string) => `/api/capabilities/get-capability/${id}/`,
|
|
9
|
+
listCapabilities: "/api/capabilities/get-installed-capabilities/",
|
|
11
10
|
deleteCapability: (id: string) =>
|
|
12
11
|
`/api/capabilities/delete-capability/${id}/`,
|
|
13
12
|
bulkDeleteCapabilities: "/api/capabilities/delete-capability/",
|
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
|
|
@@ -58,12 +59,7 @@ async function interactiveMenu(): Promise<void> {
|
|
|
58
59
|
{
|
|
59
60
|
value: "init",
|
|
60
61
|
label: "✨ Create Ability",
|
|
61
|
-
hint: "Scaffold a new ability
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
value: "deploy",
|
|
65
|
-
label: "🚀 Deploy",
|
|
66
|
-
hint: "Upload ability to OpenHome",
|
|
62
|
+
hint: "Scaffold and deploy a new ability",
|
|
67
63
|
},
|
|
68
64
|
{
|
|
69
65
|
value: "chat",
|
|
@@ -134,9 +130,6 @@ async function interactiveMenu(): Promise<void> {
|
|
|
134
130
|
case "init":
|
|
135
131
|
await initCommand();
|
|
136
132
|
break;
|
|
137
|
-
case "deploy":
|
|
138
|
-
await deployCommand();
|
|
139
|
-
break;
|
|
140
133
|
case "chat":
|
|
141
134
|
await chatCommand();
|
|
142
135
|
break;
|
|
@@ -324,6 +317,15 @@ program
|
|
|
324
317
|
await whoamiCommand();
|
|
325
318
|
});
|
|
326
319
|
|
|
320
|
+
program
|
|
321
|
+
.command("set-jwt [token]")
|
|
322
|
+
.description(
|
|
323
|
+
"Save a session token to enable management commands (list, delete, toggle, assign)",
|
|
324
|
+
)
|
|
325
|
+
.action(async (token?: string) => {
|
|
326
|
+
await setJwtCommand(token);
|
|
327
|
+
});
|
|
328
|
+
|
|
327
329
|
// ── Entry point: menu if no args, subcommand otherwise ───────────
|
|
328
330
|
|
|
329
331
|
if (process.argv.length <= 2) {
|
package/src/commands/assign.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ApiClient, NotImplementedError } from "../api/client.js";
|
|
2
2
|
import { MockApiClient } from "../api/mock-client.js";
|
|
3
|
-
import { getApiKey, getConfig } from "../config/store.js";
|
|
3
|
+
import { getApiKey, getConfig, getJwt } from "../config/store.js";
|
|
4
4
|
import { error, success, info, p, handleCancel } from "../ui/format.js";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
|
|
@@ -14,12 +14,19 @@ export async function assignCommand(
|
|
|
14
14
|
if (opts.mock) {
|
|
15
15
|
client = new MockApiClient();
|
|
16
16
|
} else {
|
|
17
|
-
const apiKey = getApiKey();
|
|
18
|
-
|
|
17
|
+
const apiKey = getApiKey() ?? "";
|
|
18
|
+
const jwt = getJwt() ?? undefined;
|
|
19
|
+
if (!apiKey && !jwt) {
|
|
19
20
|
error("Not authenticated. Run: openhome login");
|
|
20
21
|
process.exit(1);
|
|
21
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
|
+
}
|
|
29
|
+
client = new ApiClient(apiKey, getConfig().api_base_url, jwt);
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
const s = p.spinner();
|
package/src/commands/delete.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ApiClient, NotImplementedError } from "../api/client.js";
|
|
2
2
|
import { MockApiClient } from "../api/mock-client.js";
|
|
3
|
-
import { getApiKey, getConfig } from "../config/store.js";
|
|
3
|
+
import { getApiKey, getConfig, getJwt } from "../config/store.js";
|
|
4
4
|
import { error, success, p, handleCancel } from "../ui/format.js";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
|
|
@@ -15,12 +15,19 @@ export async function deleteCommand(
|
|
|
15
15
|
if (opts.mock) {
|
|
16
16
|
client = new MockApiClient();
|
|
17
17
|
} else {
|
|
18
|
-
const apiKey = getApiKey();
|
|
19
|
-
|
|
18
|
+
const apiKey = getApiKey() ?? "";
|
|
19
|
+
const jwt = getJwt() ?? undefined;
|
|
20
|
+
if (!apiKey && !jwt) {
|
|
20
21
|
error("Not authenticated. Run: openhome login");
|
|
21
22
|
process.exit(1);
|
|
22
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
|
+
}
|
|
30
|
+
client = new ApiClient(apiKey, getConfig().api_base_url, jwt);
|
|
24
31
|
}
|
|
25
32
|
|
|
26
33
|
// Fetch abilities to let user pick
|
package/src/commands/init.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { homedir } from "node:os";
|
|
|
10
10
|
import { validateAbility } from "../validation/validator.js";
|
|
11
11
|
import { registerAbility } from "../config/store.js";
|
|
12
12
|
import { success, error, warn, info, p, handleCancel } from "../ui/format.js";
|
|
13
|
+
import { deployCommand } from "./deploy.js";
|
|
13
14
|
|
|
14
15
|
type TemplateType =
|
|
15
16
|
| "basic"
|
|
@@ -889,7 +890,18 @@ export async function initCommand(nameArg?: string): Promise<void> {
|
|
|
889
890
|
warn(`${w.file ? `[${w.file}] ` : ""}${w.message}`);
|
|
890
891
|
}
|
|
891
892
|
|
|
892
|
-
|
|
893
|
+
if (result.passed) {
|
|
894
|
+
const deployNow = await p.confirm({
|
|
895
|
+
message: "Deploy to OpenHome now?",
|
|
896
|
+
initialValue: true,
|
|
897
|
+
});
|
|
898
|
+
handleCancel(deployNow);
|
|
899
|
+
|
|
900
|
+
if (deployNow) {
|
|
901
|
+
await deployCommand(targetDir);
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
893
905
|
|
|
894
|
-
p.outro(`Ability "${name}" is ready
|
|
906
|
+
p.outro(`Ability "${name}" is ready! Run: openhome deploy`);
|
|
895
907
|
}
|
package/src/commands/list.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ApiClient, NotImplementedError } from "../api/client.js";
|
|
2
2
|
import { MockApiClient } from "../api/mock-client.js";
|
|
3
|
-
import { getApiKey, getConfig } from "../config/store.js";
|
|
3
|
+
import { getApiKey, getConfig, getJwt } from "../config/store.js";
|
|
4
4
|
import { error, warn, info, table, p } from "../ui/format.js";
|
|
5
5
|
import type { TableRow } from "../ui/format.js";
|
|
6
6
|
import chalk from "chalk";
|
|
@@ -30,12 +30,19 @@ export async function listCommand(
|
|
|
30
30
|
if (opts.mock) {
|
|
31
31
|
client = new MockApiClient();
|
|
32
32
|
} else {
|
|
33
|
-
const apiKey = getApiKey();
|
|
34
|
-
|
|
33
|
+
const apiKey = getApiKey() ?? "";
|
|
34
|
+
const jwt = getJwt() ?? undefined;
|
|
35
|
+
if (!apiKey && !jwt) {
|
|
35
36
|
error("Not authenticated. Run: openhome login");
|
|
36
37
|
process.exit(1);
|
|
37
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
|
+
}
|
|
45
|
+
client = new ApiClient(apiKey, getConfig().api_base_url, jwt);
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
const s = p.spinner();
|
package/src/commands/login.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ApiClient } from "../api/client.js";
|
|
2
2
|
import type { Personality } from "../api/contracts.js";
|
|
3
|
-
import { saveApiKey } from "../config/store.js";
|
|
3
|
+
import { saveApiKey, saveJwt } from "../config/store.js";
|
|
4
4
|
import { success, error, info, p, handleCancel } from "../ui/format.js";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { saveJwt } from "../config/store.js";
|
|
2
|
+
import { success, error, p } from "../ui/format.js";
|
|
3
|
+
|
|
4
|
+
export async function setJwtCommand(token?: string): Promise<void> {
|
|
5
|
+
p.intro("🔑 Set Session Token");
|
|
6
|
+
|
|
7
|
+
let jwt = token;
|
|
8
|
+
|
|
9
|
+
if (!jwt) {
|
|
10
|
+
const input = await p.text({
|
|
11
|
+
message: "Paste your OpenHome session token",
|
|
12
|
+
placeholder: "eyJ...",
|
|
13
|
+
validate: (val) => {
|
|
14
|
+
if (!val || !val.trim()) return "Token is required";
|
|
15
|
+
if (!val.trim().startsWith("eyJ"))
|
|
16
|
+
return "Doesn't look like a JWT — should start with eyJ";
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
if (typeof input === "symbol") {
|
|
20
|
+
p.cancel("Cancelled.");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
jwt = input;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
saveJwt(jwt.trim());
|
|
28
|
+
success("Session token saved.");
|
|
29
|
+
p.note(
|
|
30
|
+
"Management commands (list, delete, toggle, assign) are now unlocked.",
|
|
31
|
+
"Token saved",
|
|
32
|
+
);
|
|
33
|
+
p.outro("Done.");
|
|
34
|
+
} catch (err) {
|
|
35
|
+
error(
|
|
36
|
+
`Failed to save token: ${err instanceof Error ? err.message : String(err)}`,
|
|
37
|
+
);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/commands/status.ts
CHANGED
|
@@ -105,12 +105,13 @@ export async function statusCommand(
|
|
|
105
105
|
if (opts.mock) {
|
|
106
106
|
client = new MockApiClient();
|
|
107
107
|
} else {
|
|
108
|
-
const apiKey = getApiKey();
|
|
109
|
-
|
|
108
|
+
const apiKey = getApiKey() ?? "";
|
|
109
|
+
const jwt = getJwt() ?? undefined;
|
|
110
|
+
if (!apiKey && !jwt) {
|
|
110
111
|
error("Not authenticated. Run: openhome login");
|
|
111
112
|
process.exit(1);
|
|
112
113
|
}
|
|
113
|
-
client = new ApiClient(apiKey, getConfig().api_base_url);
|
|
114
|
+
client = new ApiClient(apiKey, getConfig().api_base_url, jwt);
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
const s = p.spinner();
|
package/src/commands/toggle.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ApiClient, NotImplementedError } from "../api/client.js";
|
|
2
2
|
import { MockApiClient } from "../api/mock-client.js";
|
|
3
|
-
import { getApiKey, getConfig } from "../config/store.js";
|
|
3
|
+
import { getApiKey, getConfig, getJwt } from "../config/store.js";
|
|
4
4
|
import { error, success, p, handleCancel } from "../ui/format.js";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
|
|
@@ -15,12 +15,19 @@ export async function toggleCommand(
|
|
|
15
15
|
if (opts.mock) {
|
|
16
16
|
client = new MockApiClient();
|
|
17
17
|
} else {
|
|
18
|
-
const apiKey = getApiKey();
|
|
19
|
-
|
|
18
|
+
const apiKey = getApiKey() ?? "";
|
|
19
|
+
const jwt = getJwt() ?? undefined;
|
|
20
|
+
if (!apiKey && !jwt) {
|
|
20
21
|
error("Not authenticated. Run: openhome login");
|
|
21
22
|
process.exit(1);
|
|
22
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
|
+
}
|
|
30
|
+
client = new ApiClient(apiKey, getConfig().api_base_url, jwt);
|
|
24
31
|
}
|
|
25
32
|
|
|
26
33
|
// Fetch abilities
|
package/src/config/store.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface CliConfig {
|
|
|
19
19
|
api_base_url?: string;
|
|
20
20
|
default_personality_id?: string;
|
|
21
21
|
api_key?: string;
|
|
22
|
+
jwt?: string;
|
|
22
23
|
abilities?: TrackedAbility[];
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -129,9 +130,18 @@ export function getTrackedAbilities(): TrackedAbility[] {
|
|
|
129
130
|
export function saveApiKey(key: string): void {
|
|
130
131
|
const saved = keychainSet(key);
|
|
131
132
|
if (!saved) {
|
|
132
|
-
// Fallback: save in config file (less secure)
|
|
133
133
|
const config = getConfig();
|
|
134
134
|
config.api_key = key;
|
|
135
135
|
saveConfig(config);
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
|
+
|
|
139
|
+
export function getJwt(): string | null {
|
|
140
|
+
return getConfig().jwt ?? null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function saveJwt(jwt: string): void {
|
|
144
|
+
const config = getConfig();
|
|
145
|
+
config.jwt = jwt;
|
|
146
|
+
saveConfig(config);
|
|
147
|
+
}
|