@eng-ai/sdk 2.3.0 → 2.5.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/CHANGELOG.md CHANGED
@@ -2,6 +2,37 @@
2
2
 
3
3
  All notable changes to `@eng-ai/sdk` will be documented in this file.
4
4
 
5
+ ## 2.5.0 - 2026-04-05
6
+
7
+ ### Added
8
+
9
+ - External profile support (API key owner scope):
10
+ - `getProfile()`
11
+ - `updateProfile({ preferredName?, honorific?, crea?, specialty?, avatarUrl? })`
12
+ - CLI `eng-ai` profile commands:
13
+ - `profile get`
14
+ - `profile set --preferred-name ... --honorific ... --crea ... --specialty ... --avatar-url ...`
15
+
16
+ ### Changed
17
+
18
+ - Frontend profile update flow now writes through backend API only (`/api/v1/auth/me/profile`) and no longer depends on direct profile writes in Supabase client.
19
+
20
+ ## 2.4.0 - 2026-04-05
21
+
22
+ ### Added
23
+
24
+ - External user settings support (API key only):
25
+ - `getAISettings()`
26
+ - `setAISettings({ provider, model, projectModel?, apiKey?, googleApiKeyRag?, preferredTimezone? })`
27
+ - `clearAISettings()`
28
+ - `setVoiceLiveEnabled(enabled)`
29
+ - `getGeneralSettings()`
30
+ - `setGeneralSettings(preferredTimezone?)`
31
+ - CLI `eng-ai` commands for external settings:
32
+ - `settings ai get|set|clear`
33
+ - `settings ai voice-live --enabled true|false`
34
+ - `settings general get|set`
35
+
5
36
  ## 2.3.0 - 2026-04-05
6
37
 
7
38
  ### Added
package/README.md CHANGED
@@ -100,6 +100,33 @@ Supported automation plan actions:
100
100
  - `rejectAutomationPlan(projectId, planId, note?, { idempotencyKey? })`
101
101
  - `applyAutomationPlan(projectId, planId, { idempotencyKey? })`
102
102
 
103
+ External user settings (API key owner scope):
104
+
105
+ ```js
106
+ const ai = await client.getAISettings();
107
+ await client.setAISettings({
108
+ provider: "google",
109
+ model: "gemini-3.1-flash-lite-preview",
110
+ projectModel: "gemini-3.1-flash-lite-preview",
111
+ preferredTimezone: "America/Sao_Paulo",
112
+ });
113
+ await client.setVoiceLiveEnabled(true);
114
+ const general = await client.getGeneralSettings();
115
+ await client.setGeneralSettings(general.preferred_timezone || "America/Sao_Paulo");
116
+ ```
117
+
118
+ External profile (API key owner scope):
119
+
120
+ ```js
121
+ const profile = await client.getProfile();
122
+ await client.updateProfile({
123
+ preferredName: "Felipe",
124
+ honorific: "Eng.",
125
+ crea: "123456/D",
126
+ specialty: "Geotecnia",
127
+ });
128
+ ```
129
+
103
130
  CLI (`eng-ai`) included in package:
104
131
 
105
132
  ```bash
@@ -108,6 +135,12 @@ ENG_AI_API_KEY=sk_xxx eng-ai plans actionable list --limit 30
108
135
  ENG_AI_API_KEY=sk_xxx eng-ai plans approve --project-id <id> --plan-id <id> --note "Approved"
109
136
  ENG_AI_API_KEY=sk_xxx eng-ai plans apply --project-id <id> --plan-id <id>
110
137
  ENG_AI_API_KEY=sk_xxx eng-ai plans watch --interval 15 --limit 30
138
+ ENG_AI_API_KEY=sk_xxx eng-ai settings ai get
139
+ ENG_AI_API_KEY=sk_xxx eng-ai settings ai set --provider google --model gemini-3.1-flash-lite-preview --preferred-timezone America/Sao_Paulo
140
+ ENG_AI_API_KEY=sk_xxx eng-ai settings ai voice-live --enabled true
141
+ ENG_AI_API_KEY=sk_xxx eng-ai settings general set --preferred-timezone America/Sao_Paulo
142
+ ENG_AI_API_KEY=sk_xxx eng-ai profile get
143
+ ENG_AI_API_KEY=sk_xxx eng-ai profile set --preferred-name Felipe --honorific "Eng." --crea "123456/D" --specialty Geotecnia
111
144
  ```
112
145
 
113
146
  Default base URL: `https://api.eng-ai.com/api/v2`.
package/bin/eng-ai.js CHANGED
@@ -40,6 +40,14 @@ function toPositiveInt(value, fallback) {
40
40
  return Math.floor(parsed);
41
41
  }
42
42
 
43
+ function toBoolean(value, fallback = undefined) {
44
+ if (value === undefined || value === null) return fallback;
45
+ const normalized = String(value).trim().toLowerCase();
46
+ if (["1", "true", "yes", "y", "on"].includes(normalized)) return true;
47
+ if (["0", "false", "no", "n", "off"].includes(normalized)) return false;
48
+ return fallback;
49
+ }
50
+
43
51
  function maskId(value) {
44
52
  const text = String(value || "");
45
53
  if (text.length <= 8) return text;
@@ -57,6 +65,14 @@ Usage:
57
65
  eng-ai plans reject --project-id <id> --plan-id <id> [--note <text>] [--idempotency-key <key>]
58
66
  eng-ai plans apply --project-id <id> --plan-id <id> [--idempotency-key <key>]
59
67
  eng-ai plans watch [--interval <seconds>] [--limit <n>]
68
+ eng-ai settings ai get
69
+ eng-ai settings ai set --provider <openai|anthropic|google> --model <name> [--project-model <name>] [--api-key <key>] [--google-api-key-rag <key>] [--preferred-timezone <IANA_TZ>]
70
+ eng-ai settings ai clear
71
+ eng-ai settings ai voice-live --enabled <true|false>
72
+ eng-ai settings general get
73
+ eng-ai settings general set [--preferred-timezone <IANA_TZ>]
74
+ eng-ai profile get
75
+ eng-ai profile set [--preferred-name <name>] [--honorific <text>] [--crea <text>] [--specialty <text>] [--avatar-url <url>]
60
76
 
61
77
  Environment:
62
78
  ENG_AI_API_KEY (required)
@@ -192,6 +208,109 @@ async function runPlansCommand(client, subTokens, options) {
192
208
  console.log(JSON.stringify(result, null, 2));
193
209
  }
194
210
 
211
+ async function runSettingsCommand(client, subTokens, options) {
212
+ const section = String(subTokens[0] || "").trim().toLowerCase();
213
+ const action = String(subTokens[1] || "").trim().toLowerCase();
214
+
215
+ if (!section || !action) {
216
+ throw new Error("Unsupported settings command. Use: settings ai|get ...");
217
+ }
218
+
219
+ if (section === "ai") {
220
+ if (action === "get") {
221
+ const result = await client.getAISettings();
222
+ console.log(JSON.stringify(result, null, 2));
223
+ return;
224
+ }
225
+
226
+ if (action === "clear") {
227
+ const result = await client.clearAISettings();
228
+ console.log("AI settings removed.");
229
+ console.log(JSON.stringify(result, null, 2));
230
+ return;
231
+ }
232
+
233
+ if (action === "set") {
234
+ const provider = options.provider;
235
+ const model = options.model;
236
+ if (!provider || !model) {
237
+ throw new Error("Missing required options: --provider and --model");
238
+ }
239
+
240
+ const result = await client.setAISettings({
241
+ provider,
242
+ model,
243
+ projectModel: options["project-model"],
244
+ apiKey: options["api-key"],
245
+ googleApiKeyRag: options["google-api-key-rag"],
246
+ preferredTimezone: options["preferred-timezone"],
247
+ });
248
+ console.log("AI settings updated.");
249
+ console.log(JSON.stringify(result, null, 2));
250
+ return;
251
+ }
252
+
253
+ if (action === "voice-live") {
254
+ const enabled = toBoolean(options.enabled, undefined);
255
+ if (enabled === undefined) {
256
+ throw new Error("Missing or invalid --enabled. Use --enabled true|false");
257
+ }
258
+ const result = await client.setVoiceLiveEnabled(enabled);
259
+ console.log("Voice-live toggle updated.");
260
+ console.log(JSON.stringify(result, null, 2));
261
+ return;
262
+ }
263
+ }
264
+
265
+ if (section === "general") {
266
+ if (action === "get") {
267
+ const result = await client.getGeneralSettings();
268
+ console.log(JSON.stringify(result, null, 2));
269
+ return;
270
+ }
271
+
272
+ if (action === "set") {
273
+ const result = await client.setGeneralSettings(options["preferred-timezone"] || null);
274
+ console.log("General settings updated.");
275
+ console.log(JSON.stringify(result, null, 2));
276
+ return;
277
+ }
278
+ }
279
+
280
+ throw new Error("Unsupported settings command. Use ai|get/set/clear/voice-live or general|get/set");
281
+ }
282
+
283
+ async function runProfileCommand(client, subTokens, options) {
284
+ const action = String(subTokens[0] || "").trim().toLowerCase();
285
+ if (action === "get") {
286
+ const result = await client.getProfile();
287
+ console.log(JSON.stringify(result, null, 2));
288
+ return;
289
+ }
290
+
291
+ if (action === "set") {
292
+ const payload = {
293
+ preferredName: options["preferred-name"],
294
+ honorific: options.honorific,
295
+ crea: options.crea,
296
+ specialty: options.specialty,
297
+ avatarUrl: options["avatar-url"],
298
+ };
299
+ const hasAnyField = Object.values(payload).some((value) => value !== undefined);
300
+ if (!hasAnyField) {
301
+ throw new Error(
302
+ "Missing profile fields. Use at least one: --preferred-name, --honorific, --crea, --specialty, --avatar-url"
303
+ );
304
+ }
305
+ const result = await client.updateProfile(payload);
306
+ console.log("Profile updated.");
307
+ console.log(JSON.stringify(result, null, 2));
308
+ return;
309
+ }
310
+
311
+ throw new Error("Unsupported profile command. Use: profile get|set");
312
+ }
313
+
195
314
  async function main() {
196
315
  const { tokens, options } = parseArgs(process.argv.slice(2));
197
316
  const command = tokens[0];
@@ -209,6 +328,14 @@ async function main() {
209
328
  await runPlansCommand(client, tokens.slice(1), options);
210
329
  return;
211
330
  }
331
+ if (command === "settings") {
332
+ await runSettingsCommand(client, tokens.slice(1), options);
333
+ return;
334
+ }
335
+ if (command === "profile") {
336
+ await runProfileCommand(client, tokens.slice(1), options);
337
+ return;
338
+ }
212
339
 
213
340
  throw new Error(`Unknown command: ${command}`);
214
341
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eng-ai/sdk",
3
- "version": "2.3.0",
3
+ "version": "2.5.0",
4
4
  "description": "Official JavaScript SDK for ENG-AI External API v2",
5
5
  "type": "module",
6
6
  "main": "./src/client.js",
package/src/client.js CHANGED
@@ -378,6 +378,89 @@ export class EngAIClient {
378
378
  });
379
379
  }
380
380
 
381
+ async getAISettings() {
382
+ return await this._request("/settings/llm", {
383
+ method: "GET",
384
+ });
385
+ }
386
+
387
+ async setAISettings(payload) {
388
+ const provider = this._requireNonEmptyString(payload?.provider, "provider").toLowerCase();
389
+ const model = this._requireNonEmptyString(payload?.model, "model");
390
+ const body = {
391
+ provider,
392
+ model,
393
+ project_model: payload?.projectModel ?? payload?.project_model,
394
+ api_key: payload?.apiKey ?? payload?.api_key,
395
+ google_api_key_rag: payload?.googleApiKeyRag ?? payload?.google_api_key_rag,
396
+ preferred_timezone: payload?.preferredTimezone ?? payload?.preferred_timezone,
397
+ };
398
+
399
+ return await this._request("/settings/llm", {
400
+ method: "PUT",
401
+ body,
402
+ });
403
+ }
404
+
405
+ async clearAISettings() {
406
+ return await this._request("/settings/llm", {
407
+ method: "DELETE",
408
+ });
409
+ }
410
+
411
+ async setVoiceLiveEnabled(enabled) {
412
+ return await this._request("/settings/voice-live", {
413
+ method: "PUT",
414
+ body: { enabled: !!enabled },
415
+ });
416
+ }
417
+
418
+ async getGeneralSettings() {
419
+ return await this._request("/settings/general", {
420
+ method: "GET",
421
+ });
422
+ }
423
+
424
+ async setGeneralSettings(preferredTimezone = null) {
425
+ return await this._request("/settings/general", {
426
+ method: "PUT",
427
+ body: {
428
+ preferred_timezone: preferredTimezone || null,
429
+ },
430
+ });
431
+ }
432
+
433
+ async getProfile() {
434
+ return await this._request("/profile", {
435
+ method: "GET",
436
+ });
437
+ }
438
+
439
+ async updateProfile(payload = {}) {
440
+ const body = {
441
+ preferred_name: payload?.preferredName ?? payload?.preferred_name,
442
+ honorific: payload?.honorific,
443
+ crea: payload?.crea ?? payload?.crea_cau,
444
+ specialty: payload?.specialty,
445
+ avatar_url: payload?.avatarUrl ?? payload?.avatar_url,
446
+ };
447
+
448
+ const hasAnyField = Object.values(body).some((value) => value !== undefined);
449
+ if (!hasAnyField) {
450
+ throw new EngAIError("at least one profile field is required for updateProfile", {
451
+ status: 400,
452
+ code: "validation_error",
453
+ detail:
454
+ "provide one of: preferred_name, honorific, crea, specialty, avatar_url",
455
+ });
456
+ }
457
+
458
+ return await this._request("/profile", {
459
+ method: "PUT",
460
+ body,
461
+ });
462
+ }
463
+
381
464
  async getProjectContext(projectId) {
382
465
  const normalizedProjectId = this._requireNonEmptyString(projectId, "projectId");
383
466
  return await this._request(`/projects/${encodeURIComponent(normalizedProjectId)}/context`, {