iobroker.ai-usage 0.1.0 → 0.2.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.
Files changed (49) hide show
  1. package/README.md +13 -2
  2. package/admin/custom/assets/Components-Bd545388.js +1 -0
  3. package/admin/custom/assets/ConfigPanel-qivVprTj.js +1 -0
  4. package/admin/custom/assets/_virtual_mf-localSharedImportMap___mfe_internal__AiUsageComponentSet__mf_owner__1-CXmnAfNY.js +1 -0
  5. package/admin/custom/assets/_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-DwHNIjSR.js +1243 -0
  6. package/admin/custom/assets/_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_json_mf_2_config__loadShare__.js-qu1s07FY.js +1494 -0
  7. package/admin/custom/assets/_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-C8-GjKo4.js +9 -0
  8. package/admin/custom/assets/_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__prebuild___mf_0_iobroker_mf_1_gui_mf_2_components__prebuild__-CQxWwR2r.js +1 -0
  9. package/admin/custom/assets/hostInit-RHMRa462.js +1 -0
  10. package/admin/custom/assets/index-B0J1NbOp.js +1 -0
  11. package/admin/custom/assets/lib-ByF76TCN.js +3 -0
  12. package/admin/custom/assets/mf-entry-bootstrap-0-e070fa3e.js +35 -0
  13. package/admin/custom/assets/rolldown-runtime-C0FnF6B9.js +1 -0
  14. package/admin/custom/assets/virtualExposes-BZWQ_oUV.js +1 -0
  15. package/admin/custom/assets/virtual_mf-REMOTE_ENTRY_ID___mfe_internal__AiUsageComponentSet__customComponents_js-l7jtElkM.js +1 -0
  16. package/admin/custom/assets/virtual_mf-exposes-ssr___mfe_internal__AiUsageComponentSet__customComponents_js-CRK-nAdE.js +2 -0
  17. package/admin/custom/assets/virtual_mf-exposes___mfe_internal__AiUsageComponentSet__customComponents_js-BZ-CVpVo.js +2 -0
  18. package/admin/custom/assets/vite-preload-helper-B7qeedMF.js +1 -0
  19. package/admin/custom/customComponents.js +1 -0
  20. package/admin/custom/i18n/de.json +23 -0
  21. package/admin/custom/i18n/en.json +23 -0
  22. package/admin/custom/i18n/es.json +23 -0
  23. package/admin/custom/i18n/fr.json +23 -0
  24. package/admin/custom/i18n/it.json +23 -0
  25. package/admin/custom/i18n/nl.json +23 -0
  26. package/admin/custom/i18n/pl.json +23 -0
  27. package/admin/custom/i18n/pt.json +23 -0
  28. package/admin/custom/i18n/ru.json +23 -0
  29. package/admin/custom/i18n/uk.json +23 -0
  30. package/admin/custom/i18n/zh-cn.json +23 -0
  31. package/admin/custom/mf-manifest.json +1 -0
  32. package/admin/i18n/de.json +9 -27
  33. package/admin/i18n/en.json +9 -27
  34. package/admin/i18n/es.json +9 -27
  35. package/admin/i18n/fr.json +9 -27
  36. package/admin/i18n/it.json +9 -27
  37. package/admin/i18n/nl.json +9 -27
  38. package/admin/i18n/pl.json +9 -27
  39. package/admin/i18n/pt.json +9 -27
  40. package/admin/i18n/ru.json +9 -27
  41. package/admin/i18n/uk.json +9 -27
  42. package/admin/i18n/zh-cn.json +9 -27
  43. package/admin/jsonConfig.json +7 -166
  44. package/build/lib/pure-helpers.js +5 -2
  45. package/build/lib/pure-helpers.js.map +2 -2
  46. package/build/main.js +105 -7
  47. package/build/main.js.map +2 -2
  48. package/io-package.json +15 -15
  49. package/package.json +5 -2
package/build/main.js CHANGED
@@ -69,12 +69,11 @@ class AiUsageAdapter extends utils.Adapter {
69
69
  case "claudeAuthStart": {
70
70
  const accountId = this.claudeAccountIdFrom(obj.message);
71
71
  if (!accountId) {
72
- this.respond(obj, "\u2192 Enter the exact name of a Claude subscription row from the table above");
72
+ this.respond(obj, { error: "Save the settings with a Claude subscription account first" });
73
73
  return;
74
74
  }
75
- const pkce = (0, import_claude_auth.generatePkce)();
76
- this.pendingClaudeAuth.set(accountId, pkce);
77
- this.respond(obj, (0, import_claude_auth.buildAuthorizeUrl)(pkce));
75
+ const url = await this.publishClaudeSignInUrl(accountId);
76
+ this.respond(obj, { url });
78
77
  return;
79
78
  }
80
79
  case "claudeAuthCode": {
@@ -82,7 +81,7 @@ class AiUsageAdapter extends utils.Adapter {
82
81
  const code = typeof ((_a = obj.message) == null ? void 0 : _a.code) === "string" ? obj.message.code : "";
83
82
  const pkce = accountId ? this.pendingClaudeAuth.get(accountId) : void 0;
84
83
  if (!accountId || !pkce) {
85
- this.respond(obj, { error: "Generate the sign-in link first (step 1)" });
84
+ this.respond(obj, { error: "Save the settings with a Claude subscription account first" });
86
85
  return;
87
86
  }
88
87
  if (!code.trim()) {
@@ -93,7 +92,8 @@ class AiUsageAdapter extends utils.Adapter {
93
92
  const tokens = await (0, import_claude_auth.exchangeCode)(code, pkce, import_http.postJson, Date.now());
94
93
  await this.claudeTokenStore(accountId).save(tokens);
95
94
  this.pendingClaudeAuth.delete(accountId);
96
- this.respond(obj, { result: "Signed in \u2014 restart the instance (or save the settings) to start polling" });
95
+ await this.setClaudeAuthStates(accountId, "", true);
96
+ this.respond(obj, { result: "ok" });
97
97
  } catch (e) {
98
98
  this.respond(obj, { error: `Sign-in failed: ${e instanceof Error ? e.message : String(e)}` });
99
99
  }
@@ -134,6 +134,103 @@ class AiUsageAdapter extends utils.Adapter {
134
134
  const accounts = (0, import_pure_helpers.parseAccounts)(this.config.accounts);
135
135
  return accounts.some((account) => account.id === id && account.provider === "claude-sub") ? id : void 0;
136
136
  }
137
+ /**
138
+ * Create the `auth.<id>` states of one Claude account and publish a FRESH
139
+ * sign-in link (the adapter owns the secret; the link stays valid until it is
140
+ * regenerated or redeemed — the admin card only displays it).
141
+ *
142
+ * @param accountId the id-safe account id
143
+ * @returns the sign-in URL
144
+ */
145
+ async publishClaudeSignInUrl(accountId) {
146
+ const pkce = (0, import_claude_auth.generatePkce)();
147
+ this.pendingClaudeAuth.set(accountId, pkce);
148
+ const url = (0, import_claude_auth.buildAuthorizeUrl)(pkce);
149
+ await this.setClaudeAuthStates(accountId, url, false);
150
+ return url;
151
+ }
152
+ /**
153
+ * Create (once) and write the two `auth.<id>` states of one Claude account.
154
+ *
155
+ * @param accountId the id-safe account id
156
+ * @param url the current sign-in URL ("" when signed in)
157
+ * @param signedIn whether a usable sign-in exists
158
+ */
159
+ async setClaudeAuthStates(accountId, url, signedIn) {
160
+ await this.extendObject("auth", {
161
+ type: "folder",
162
+ common: { name: { en: "Sign-in", de: "Anmeldung" } },
163
+ native: {}
164
+ });
165
+ await this.extendObject(`auth.${accountId}`, {
166
+ type: "channel",
167
+ common: { name: accountId },
168
+ native: {}
169
+ });
170
+ await this.extendObject(`auth.${accountId}.signInUrl`, {
171
+ type: "state",
172
+ common: {
173
+ name: { en: "Sign-in link", de: "Anmelde-Link" },
174
+ type: "string",
175
+ role: "url",
176
+ read: true,
177
+ write: false,
178
+ def: ""
179
+ },
180
+ native: {}
181
+ });
182
+ await this.extendObject(`auth.${accountId}.signedIn`, {
183
+ type: "state",
184
+ common: {
185
+ name: { en: "Signed in", de: "Angemeldet" },
186
+ type: "boolean",
187
+ role: "indicator",
188
+ read: true,
189
+ write: false,
190
+ def: false
191
+ },
192
+ native: {}
193
+ });
194
+ await this.setState(`auth.${accountId}.signInUrl`, { val: url, ack: true });
195
+ await this.setState(`auth.${accountId}.signedIn`, { val: signedIn, ack: true });
196
+ }
197
+ /**
198
+ * Prepare the guided sign-in of every Claude subscription account: publish the
199
+ * live status, and for accounts without a usable sign-in a stable sign-in link.
200
+ *
201
+ * @param accounts the validated account configs
202
+ */
203
+ async prepareClaudeAuth(accounts) {
204
+ const claudeAccounts = accounts.filter((a) => a.provider === "claude-sub");
205
+ try {
206
+ const wanted = new Set(claudeAccounts.map((a) => a.id));
207
+ const objects = await this.getAdapterObjectsAsync();
208
+ const stale = /* @__PURE__ */ new Set();
209
+ for (const id of Object.keys(objects)) {
210
+ const relative = id.substring(this.namespace.length + 1);
211
+ const [root, child] = relative.split(".");
212
+ if (root === "auth" && child && !wanted.has(child)) {
213
+ stale.add(child);
214
+ }
215
+ }
216
+ for (const child of stale) {
217
+ await this.delObjectAsync(`auth.${child}`, { recursive: true });
218
+ }
219
+ } catch {
220
+ }
221
+ for (const account of claudeAccounts) {
222
+ try {
223
+ const tokens = await this.claudeTokenStore(account.id).load();
224
+ if (tokens) {
225
+ await this.setClaudeAuthStates(account.id, "", true);
226
+ } else {
227
+ await this.publishClaudeSignInUrl(account.id);
228
+ }
229
+ } catch (e) {
230
+ this.log.warn(`${account.name}: cannot prepare sign-in (${e instanceof Error ? e.message : String(e)})`);
231
+ }
232
+ }
233
+ }
137
234
  /**
138
235
  * The persistent token storage for one Claude account: an encrypted JSON file in
139
236
  * the instance data directory (a `native` write would restart the instance).
@@ -173,6 +270,7 @@ class AiUsageAdapter extends utils.Adapter {
173
270
  const accounts = (0, import_pure_helpers.parseAccounts)(this.config.accounts);
174
271
  const interval = (0, import_pure_helpers.clampPollInterval)(this.config.pollInterval);
175
272
  await this.cleanupStaleAccounts();
273
+ await this.prepareClaudeAuth(accounts);
176
274
  if (accounts.length === 0) {
177
275
  this.log.info("No AI accounts configured \u2014 add accounts in the instance settings");
178
276
  await this.setState("info.connection", { val: false, ack: true });
@@ -288,7 +386,7 @@ class AiUsageAdapter extends utils.Adapter {
288
386
  if (keepIds.length === 0) {
289
387
  return;
290
388
  }
291
- const keep = /* @__PURE__ */ new Set([...keepIds, "info", "total"]);
389
+ const keep = /* @__PURE__ */ new Set([...keepIds, "info", "total", "auth"]);
292
390
  try {
293
391
  const objects = await this.getAdapterObjectsAsync();
294
392
  const roots = /* @__PURE__ */ new Set();
package/build/main.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/main.ts"],
4
- "sourcesContent": ["import * as utils from \"@iobroker/adapter-core\";\nimport { Credentials } from \"@iobroker/adapter-core\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { postJson } from \"./lib/http\";\nimport { PollEngine } from \"./lib/poll-engine\";\nimport { clampPollInterval, parseAccounts, sanitizeId, validAccountIds, type AccountConfig } from \"./lib/pure-helpers\";\nimport type { UsageProvider } from \"./lib/provider\";\nimport {\n buildAuthorizeUrl,\n exchangeCode,\n generatePkce,\n type PkcePair,\n type TokenSet,\n} from \"./lib/providers/claude-auth\";\nimport { anthropicApiProvider } from \"./lib/providers/anthropic-api\";\nimport { claudeSubProvider, type TokenStore } from \"./lib/providers/claude-sub\";\nimport { deepSeekProvider } from \"./lib/providers/deepseek\";\nimport { openAiProvider } from \"./lib/providers/openai\";\nimport { openRouterProvider } from \"./lib/providers/openrouter\";\n\n/** A cancellable handle: interval or timeout \u2014 the engine treats them uniformly. */\ntype TimerHandle =\n | { kind: \"interval\"; handle: ioBroker.Interval | undefined }\n | { kind: \"timeout\"; handle: ioBroker.Timeout | undefined };\n\n/**\n * AI Usage adapter \u2014 polls the usage/limit/cost sources of configured AI accounts\n * (Claude subscription, OpenRouter, DeepSeek, OpenAI API, Anthropic API) and\n * mirrors them into read-only states. Orchestration lives in the\n * fully unit-tested {@link PollEngine}; this class only wires ioBroker IO to it.\n */\nexport class AiUsageAdapter extends utils.Adapter {\n private engine: PollEngine | null = null;\n /** Pending Claude sign-in attempts, keyed by account id (PKCE lives only in memory). */\n private readonly pendingClaudeAuth = new Map<string, PkcePair>();\n\n /**\n * @param options the adapter options\n */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({ ...options, name: \"ai-usage\" });\n this.on(\"ready\", this.onReady.bind(this));\n this.on(\"message\", this.onMessage.bind(this));\n this.on(\"unload\", this.onUnload.bind(this));\n }\n\n /**\n * Handle admin messages \u2014 the guided Claude subscription sign-in.\n *\n * @param obj the message\n */\n private async onMessage(obj: ioBroker.Message): Promise<void> {\n try {\n switch (obj.command) {\n case \"claudeAuthStart\": {\n const accountId = this.claudeAccountIdFrom(obj.message);\n if (!accountId) {\n // Plain string \u2014 this feeds a textSendTo display in the admin.\n this.respond(obj, \"\u2192 Enter the exact name of a Claude subscription row from the table above\");\n return;\n }\n const pkce = generatePkce();\n this.pendingClaudeAuth.set(accountId, pkce);\n this.respond(obj, buildAuthorizeUrl(pkce));\n return;\n }\n case \"claudeAuthCode\": {\n const accountId = this.claudeAccountIdFrom(obj.message);\n const code =\n typeof (obj.message as { code?: unknown })?.code === \"string\" ? (obj.message as { code: string }).code : \"\";\n const pkce = accountId ? this.pendingClaudeAuth.get(accountId) : undefined;\n if (!accountId || !pkce) {\n this.respond(obj, { error: \"Generate the sign-in link first (step 1)\" });\n return;\n }\n if (!code.trim()) {\n this.respond(obj, { error: \"Paste the code from the Anthropic page first\" });\n return;\n }\n try {\n const tokens = await exchangeCode(code, pkce, postJson, Date.now());\n await this.claudeTokenStore(accountId).save(tokens);\n this.pendingClaudeAuth.delete(accountId);\n this.respond(obj, { result: \"Signed in \u2014 restart the instance (or save the settings) to start polling\" });\n } catch (e) {\n this.respond(obj, { error: `Sign-in failed: ${e instanceof Error ? e.message : String(e)}` });\n }\n return;\n }\n default:\n // Always answer, or the caller's callback would dangle until timeout.\n this.respond(obj, { error: `Unknown command: ${obj.command}` });\n }\n } catch (e) {\n this.log.error(`onMessage failed: ${e instanceof Error ? e.message : String(e)}`);\n this.respond(obj, { error: \"internal error \u2014 see log\" });\n }\n }\n\n /**\n * Send a message response, when the caller expects one.\n *\n * @param obj the request message\n * @param response the response payload\n */\n private respond(obj: ioBroker.Message, response: unknown): void {\n if (obj.callback) {\n this.sendTo(obj.from, obj.command, response, obj.callback);\n }\n }\n\n /**\n * Resolve the account id for a Claude sign-in message: the given name must match\n * a claude-sub row of the accounts table.\n *\n * @param message the message payload ({ account })\n * @returns the id-safe account id, or undefined\n */\n private claudeAccountIdFrom(message: unknown): string | undefined {\n const name =\n typeof (message as { account?: unknown })?.account === \"string\" ? (message as { account: string }).account : \"\";\n const id = sanitizeId(name);\n if (!id) {\n return undefined;\n }\n const accounts = parseAccounts(this.config.accounts);\n return accounts.some(account => account.id === id && account.provider === \"claude-sub\") ? id : undefined;\n }\n\n /**\n * The persistent token storage for one Claude account: an encrypted JSON file in\n * the instance data directory (a `native` write would restart the instance).\n *\n * @param accountId the id-safe account id\n * @returns the store\n */\n private claudeTokenStore(accountId: string): TokenStore {\n const dir = utils.getAbsoluteInstanceDataDir(this);\n const file = join(dir, `claude-tokens-${accountId}.json`);\n return {\n load: async (): Promise<TokenSet | null> => {\n try {\n const encrypted = await readFile(file, \"utf8\");\n const parsed = JSON.parse(this.decrypt(encrypted)) as Partial<TokenSet>;\n if (typeof parsed.accessToken !== \"string\" || typeof parsed.refreshToken !== \"string\") {\n return null;\n }\n return {\n accessToken: parsed.accessToken,\n refreshToken: parsed.refreshToken,\n expiresAt: Number(parsed.expiresAt) || 0,\n };\n } catch {\n return null; // never signed in (or unreadable) \u2014 the provider reports auth-required\n }\n },\n save: async (tokens: TokenSet): Promise<void> => {\n await mkdir(dir, { recursive: true });\n await writeFile(file, this.encrypt(JSON.stringify(tokens)), \"utf8\");\n },\n };\n }\n\n /** Validate the configuration, clean up stale account trees and start the engine. */\n private async onReady(): Promise<void> {\n try {\n const accounts = parseAccounts(this.config.accounts);\n const interval = clampPollInterval(this.config.pollInterval);\n await this.cleanupStaleAccounts();\n if (accounts.length === 0) {\n this.log.info(\"No AI accounts configured \u2014 add accounts in the instance settings\");\n await this.setState(\"info.connection\", { val: false, ack: true });\n return;\n }\n const providers = new Map<string, UsageProvider>();\n for (const account of accounts) {\n const provider = await this.makeProvider(account);\n if (provider) {\n providers.set(account.id, provider);\n }\n }\n this.engine = new PollEngine(accounts, providers, interval, {\n upsertObject: async def => {\n await this.extendObject(def.id, { type: def.type, common: def.common as ioBroker.ObjectCommon, native: {} });\n },\n setState: (id, value) => {\n void this.setState(id, { val: value, ack: true }).catch(() => {\n /* states DB going down \u2014 never crash the poll loop */\n });\n },\n schedule: (cb, ms): TimerHandle => ({ kind: \"interval\", handle: this.setInterval(cb, ms) }),\n scheduleOnce: (cb, ms): TimerHandle => ({ kind: \"timeout\", handle: this.setTimeout(cb, ms) }),\n cancel: handle => {\n const timer = handle as TimerHandle;\n if (timer.kind === \"interval\") {\n this.clearInterval(timer.handle);\n } else {\n this.clearTimeout(timer.handle);\n }\n },\n now: () => Date.now(),\n log: {\n debug: m => this.log.debug(m),\n info: m => this.log.info(m),\n warn: m => this.log.warn(m),\n error: m => this.log.error(m),\n },\n notify: this.config.notifications\n ? (_account, message) =>\n void this.registerNotification(\"ai-usage\", \"userActionRequired\", message).catch(e =>\n this.log.debug(`Could not raise notification: ${e instanceof Error ? e.message : String(e)}`),\n )\n : undefined,\n });\n await this.engine.start();\n this.log.info(`Monitoring ${providers.size} of ${accounts.length} AI account(s), polling every ${interval} s`);\n } catch (e) {\n this.log.error(`Startup failed: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n\n /**\n * Build the provider for one account, resolving its credential from the central\n * storage. Accounts whose provider is not implemented yet, or whose credential\n * cannot be read, are skipped (the engine logs the skip).\n *\n * @param account the validated account config\n * @returns the provider, or undefined to skip the account\n */\n private async makeProvider(account: AccountConfig): Promise<UsageProvider | undefined> {\n switch (account.provider) {\n case \"claude-sub\":\n return claudeSubProvider(this.claudeTokenStore(account.id), undefined, postJson);\n case \"openrouter\": {\n const key = await this.resolveKey(account);\n return key ? openRouterProvider(key) : undefined;\n }\n case \"deepseek\": {\n const key = await this.resolveKey(account);\n return key ? deepSeekProvider(key) : undefined;\n }\n case \"openai\": {\n const key = await this.resolveKey(account);\n return key ? openAiProvider(key) : undefined;\n }\n case \"anthropic-api\": {\n const key = await this.resolveKey(account);\n return key ? anthropicApiProvider(key) : undefined;\n }\n default:\n return undefined;\n }\n }\n\n /**\n * Read and decrypt a key-form credential from the central credential storage.\n *\n * @param account the account whose credential to resolve\n * @returns the key, or undefined (with a log line) when it cannot be read\n */\n private async resolveKey(account: AccountConfig): Promise<string | undefined> {\n if (!account.credentialId) {\n this.log.warn(`${account.name}: no credential selected \u2014 pick one in the instance settings`);\n return undefined;\n }\n try {\n const credential = await Credentials.getCredentials(this, account.credentialId);\n const values = credential.values as { key?: unknown; password?: unknown };\n const key = typeof values.key === \"string\" && values.key ? values.key : undefined;\n if (!key) {\n this.log.warn(`${account.name}: credential ${account.credentialId} carries no API key`);\n }\n return key;\n } catch (e) {\n this.log.warn(\n `${account.name}: cannot read credential ${account.credentialId} (${e instanceof Error ? e.message : String(e)})`,\n );\n return undefined;\n }\n }\n\n /**\n * Delete the object trees of accounts that are no longer in the table. Disabled\n * rows keep their tree (they are only paused); an EMPTY table deletes nothing \u2014\n * the guard against wiping everything through an accidental clear.\n */\n private async cleanupStaleAccounts(): Promise<void> {\n const keepIds = validAccountIds(this.config.accounts);\n if (keepIds.length === 0) {\n return;\n }\n const keep = new Set([...keepIds, \"info\", \"total\"]);\n try {\n const objects = await this.getAdapterObjectsAsync();\n const roots = new Set<string>();\n for (const id of Object.keys(objects)) {\n const relative = id.substring(this.namespace.length + 1);\n const root = relative.split(\".\")[0];\n if (root && !keep.has(root)) {\n roots.add(root);\n }\n }\n for (const root of roots) {\n this.log.info(`Removing objects of no longer configured account \"${root}\"`);\n await this.delObjectAsync(root, { recursive: true });\n }\n } catch (e) {\n this.log.warn(`Cleanup of stale accounts failed: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n\n /**\n * Tear down synchronously \u2014 no async/await here, else the controller kills the\n * process before cleanup finishes.\n *\n * @param callback invoked when cleanup is done\n */\n private onUnload(callback: () => void): void {\n try {\n this.engine?.stop();\n this.engine = null;\n void this.setState(\"info.connection\", { val: false, ack: true });\n } catch {\n // never block shutdown\n }\n callback();\n }\n}\n\nif (require.main !== module) {\n // Export the constructor in compact mode\n module.exports = (options: Partial<utils.AdapterOptions> | undefined) => new AiUsageAdapter(options);\n} else {\n (() => new AiUsageAdapter())();\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import * as utils from \"@iobroker/adapter-core\";\nimport { Credentials } from \"@iobroker/adapter-core\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { postJson } from \"./lib/http\";\nimport { PollEngine } from \"./lib/poll-engine\";\nimport { clampPollInterval, parseAccounts, sanitizeId, validAccountIds, type AccountConfig } from \"./lib/pure-helpers\";\nimport type { UsageProvider } from \"./lib/provider\";\nimport {\n buildAuthorizeUrl,\n exchangeCode,\n generatePkce,\n type PkcePair,\n type TokenSet,\n} from \"./lib/providers/claude-auth\";\nimport { anthropicApiProvider } from \"./lib/providers/anthropic-api\";\nimport { claudeSubProvider, type TokenStore } from \"./lib/providers/claude-sub\";\nimport { deepSeekProvider } from \"./lib/providers/deepseek\";\nimport { openAiProvider } from \"./lib/providers/openai\";\nimport { openRouterProvider } from \"./lib/providers/openrouter\";\n\n/** A cancellable handle: interval or timeout \u2014 the engine treats them uniformly. */\ntype TimerHandle =\n | { kind: \"interval\"; handle: ioBroker.Interval | undefined }\n | { kind: \"timeout\"; handle: ioBroker.Timeout | undefined };\n\n/**\n * AI Usage adapter \u2014 polls the usage/limit/cost sources of configured AI accounts\n * (Claude subscription, OpenRouter, DeepSeek, OpenAI API, Anthropic API) and\n * mirrors them into read-only states. Orchestration lives in the\n * fully unit-tested {@link PollEngine}; this class only wires ioBroker IO to it.\n */\nexport class AiUsageAdapter extends utils.Adapter {\n private engine: PollEngine | null = null;\n /** Pending Claude sign-in attempts, keyed by account id (PKCE lives only in memory). */\n private readonly pendingClaudeAuth = new Map<string, PkcePair>();\n\n /**\n * @param options the adapter options\n */\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({ ...options, name: \"ai-usage\" });\n this.on(\"ready\", this.onReady.bind(this));\n this.on(\"message\", this.onMessage.bind(this));\n this.on(\"unload\", this.onUnload.bind(this));\n }\n\n /**\n * Handle admin messages \u2014 the guided Claude subscription sign-in.\n *\n * @param obj the message\n */\n private async onMessage(obj: ioBroker.Message): Promise<void> {\n try {\n switch (obj.command) {\n case \"claudeAuthStart\": {\n // Regenerate the sign-in link for one account (invalidates the previous one).\n const accountId = this.claudeAccountIdFrom(obj.message);\n if (!accountId) {\n this.respond(obj, { error: \"Save the settings with a Claude subscription account first\" });\n return;\n }\n const url = await this.publishClaudeSignInUrl(accountId);\n this.respond(obj, { url });\n return;\n }\n case \"claudeAuthCode\": {\n const accountId = this.claudeAccountIdFrom(obj.message);\n const code =\n typeof (obj.message as { code?: unknown })?.code === \"string\" ? (obj.message as { code: string }).code : \"\";\n const pkce = accountId ? this.pendingClaudeAuth.get(accountId) : undefined;\n if (!accountId || !pkce) {\n this.respond(obj, { error: \"Save the settings with a Claude subscription account first\" });\n return;\n }\n if (!code.trim()) {\n this.respond(obj, { error: \"Paste the code from the Anthropic page first\" });\n return;\n }\n try {\n const tokens = await exchangeCode(code, pkce, postJson, Date.now());\n await this.claudeTokenStore(accountId).save(tokens);\n this.pendingClaudeAuth.delete(accountId);\n await this.setClaudeAuthStates(accountId, \"\", true);\n this.respond(obj, { result: \"ok\" });\n } catch (e) {\n this.respond(obj, { error: `Sign-in failed: ${e instanceof Error ? e.message : String(e)}` });\n }\n return;\n }\n default:\n // Always answer, or the caller's callback would dangle until timeout.\n this.respond(obj, { error: `Unknown command: ${obj.command}` });\n }\n } catch (e) {\n this.log.error(`onMessage failed: ${e instanceof Error ? e.message : String(e)}`);\n this.respond(obj, { error: \"internal error \u2014 see log\" });\n }\n }\n\n /**\n * Send a message response, when the caller expects one.\n *\n * @param obj the request message\n * @param response the response payload\n */\n private respond(obj: ioBroker.Message, response: unknown): void {\n if (obj.callback) {\n this.sendTo(obj.from, obj.command, response, obj.callback);\n }\n }\n\n /**\n * Resolve the account id for a Claude sign-in message: the given name must match\n * a claude-sub row of the accounts table.\n *\n * @param message the message payload ({ account })\n * @returns the id-safe account id, or undefined\n */\n private claudeAccountIdFrom(message: unknown): string | undefined {\n const name =\n typeof (message as { account?: unknown })?.account === \"string\" ? (message as { account: string }).account : \"\";\n const id = sanitizeId(name);\n if (!id) {\n return undefined;\n }\n const accounts = parseAccounts(this.config.accounts);\n return accounts.some(account => account.id === id && account.provider === \"claude-sub\") ? id : undefined;\n }\n\n /**\n * Create the `auth.<id>` states of one Claude account and publish a FRESH\n * sign-in link (the adapter owns the secret; the link stays valid until it is\n * regenerated or redeemed \u2014 the admin card only displays it).\n *\n * @param accountId the id-safe account id\n * @returns the sign-in URL\n */\n private async publishClaudeSignInUrl(accountId: string): Promise<string> {\n const pkce = generatePkce();\n this.pendingClaudeAuth.set(accountId, pkce);\n const url = buildAuthorizeUrl(pkce);\n await this.setClaudeAuthStates(accountId, url, false);\n return url;\n }\n\n /**\n * Create (once) and write the two `auth.<id>` states of one Claude account.\n *\n * @param accountId the id-safe account id\n * @param url the current sign-in URL (\"\" when signed in)\n * @param signedIn whether a usable sign-in exists\n */\n private async setClaudeAuthStates(accountId: string, url: string, signedIn: boolean): Promise<void> {\n await this.extendObject(\"auth\", {\n type: \"folder\",\n common: { name: { en: \"Sign-in\", de: \"Anmeldung\" } },\n native: {},\n });\n await this.extendObject(`auth.${accountId}`, {\n type: \"channel\",\n common: { name: accountId },\n native: {},\n });\n await this.extendObject(`auth.${accountId}.signInUrl`, {\n type: \"state\",\n common: {\n name: { en: \"Sign-in link\", de: \"Anmelde-Link\" },\n type: \"string\",\n role: \"url\",\n read: true,\n write: false,\n def: \"\",\n },\n native: {},\n });\n await this.extendObject(`auth.${accountId}.signedIn`, {\n type: \"state\",\n common: {\n name: { en: \"Signed in\", de: \"Angemeldet\" },\n type: \"boolean\",\n role: \"indicator\",\n read: true,\n write: false,\n def: false,\n },\n native: {},\n });\n await this.setState(`auth.${accountId}.signInUrl`, { val: url, ack: true });\n await this.setState(`auth.${accountId}.signedIn`, { val: signedIn, ack: true });\n }\n\n /**\n * Prepare the guided sign-in of every Claude subscription account: publish the\n * live status, and for accounts without a usable sign-in a stable sign-in link.\n *\n * @param accounts the validated account configs\n */\n private async prepareClaudeAuth(accounts: AccountConfig[]): Promise<void> {\n const claudeAccounts = accounts.filter(a => a.provider === \"claude-sub\");\n // Remove sign-in channels of accounts that are gone (the account cleanup only\n // handles top-level account trees, not the shared auth folder).\n try {\n const wanted = new Set(claudeAccounts.map(a => a.id));\n const objects = await this.getAdapterObjectsAsync();\n const stale = new Set<string>();\n for (const id of Object.keys(objects)) {\n const relative = id.substring(this.namespace.length + 1);\n const [root, child] = relative.split(\".\");\n if (root === \"auth\" && child && !wanted.has(child)) {\n stale.add(child);\n }\n }\n for (const child of stale) {\n await this.delObjectAsync(`auth.${child}`, { recursive: true });\n }\n } catch {\n // cleanup only \u2014 never block startup\n }\n for (const account of claudeAccounts) {\n try {\n const tokens = await this.claudeTokenStore(account.id).load();\n if (tokens) {\n await this.setClaudeAuthStates(account.id, \"\", true);\n } else {\n await this.publishClaudeSignInUrl(account.id);\n }\n } catch (e) {\n this.log.warn(`${account.name}: cannot prepare sign-in (${e instanceof Error ? e.message : String(e)})`);\n }\n }\n }\n\n /**\n * The persistent token storage for one Claude account: an encrypted JSON file in\n * the instance data directory (a `native` write would restart the instance).\n *\n * @param accountId the id-safe account id\n * @returns the store\n */\n private claudeTokenStore(accountId: string): TokenStore {\n const dir = utils.getAbsoluteInstanceDataDir(this);\n const file = join(dir, `claude-tokens-${accountId}.json`);\n return {\n load: async (): Promise<TokenSet | null> => {\n try {\n const encrypted = await readFile(file, \"utf8\");\n const parsed = JSON.parse(this.decrypt(encrypted)) as Partial<TokenSet>;\n if (typeof parsed.accessToken !== \"string\" || typeof parsed.refreshToken !== \"string\") {\n return null;\n }\n return {\n accessToken: parsed.accessToken,\n refreshToken: parsed.refreshToken,\n expiresAt: Number(parsed.expiresAt) || 0,\n };\n } catch {\n return null; // never signed in (or unreadable) \u2014 the provider reports auth-required\n }\n },\n save: async (tokens: TokenSet): Promise<void> => {\n await mkdir(dir, { recursive: true });\n await writeFile(file, this.encrypt(JSON.stringify(tokens)), \"utf8\");\n },\n };\n }\n\n /** Validate the configuration, clean up stale account trees and start the engine. */\n private async onReady(): Promise<void> {\n try {\n const accounts = parseAccounts(this.config.accounts);\n const interval = clampPollInterval(this.config.pollInterval);\n await this.cleanupStaleAccounts();\n await this.prepareClaudeAuth(accounts);\n if (accounts.length === 0) {\n this.log.info(\"No AI accounts configured \u2014 add accounts in the instance settings\");\n await this.setState(\"info.connection\", { val: false, ack: true });\n return;\n }\n const providers = new Map<string, UsageProvider>();\n for (const account of accounts) {\n const provider = await this.makeProvider(account);\n if (provider) {\n providers.set(account.id, provider);\n }\n }\n this.engine = new PollEngine(accounts, providers, interval, {\n upsertObject: async def => {\n await this.extendObject(def.id, { type: def.type, common: def.common as ioBroker.ObjectCommon, native: {} });\n },\n setState: (id, value) => {\n void this.setState(id, { val: value, ack: true }).catch(() => {\n /* states DB going down \u2014 never crash the poll loop */\n });\n },\n schedule: (cb, ms): TimerHandle => ({ kind: \"interval\", handle: this.setInterval(cb, ms) }),\n scheduleOnce: (cb, ms): TimerHandle => ({ kind: \"timeout\", handle: this.setTimeout(cb, ms) }),\n cancel: handle => {\n const timer = handle as TimerHandle;\n if (timer.kind === \"interval\") {\n this.clearInterval(timer.handle);\n } else {\n this.clearTimeout(timer.handle);\n }\n },\n now: () => Date.now(),\n log: {\n debug: m => this.log.debug(m),\n info: m => this.log.info(m),\n warn: m => this.log.warn(m),\n error: m => this.log.error(m),\n },\n notify: this.config.notifications\n ? (_account, message) =>\n void this.registerNotification(\"ai-usage\", \"userActionRequired\", message).catch(e =>\n this.log.debug(`Could not raise notification: ${e instanceof Error ? e.message : String(e)}`),\n )\n : undefined,\n });\n await this.engine.start();\n this.log.info(`Monitoring ${providers.size} of ${accounts.length} AI account(s), polling every ${interval} s`);\n } catch (e) {\n this.log.error(`Startup failed: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n\n /**\n * Build the provider for one account, resolving its credential from the central\n * storage. Accounts whose provider is not implemented yet, or whose credential\n * cannot be read, are skipped (the engine logs the skip).\n *\n * @param account the validated account config\n * @returns the provider, or undefined to skip the account\n */\n private async makeProvider(account: AccountConfig): Promise<UsageProvider | undefined> {\n switch (account.provider) {\n case \"claude-sub\":\n return claudeSubProvider(this.claudeTokenStore(account.id), undefined, postJson);\n case \"openrouter\": {\n const key = await this.resolveKey(account);\n return key ? openRouterProvider(key) : undefined;\n }\n case \"deepseek\": {\n const key = await this.resolveKey(account);\n return key ? deepSeekProvider(key) : undefined;\n }\n case \"openai\": {\n const key = await this.resolveKey(account);\n return key ? openAiProvider(key) : undefined;\n }\n case \"anthropic-api\": {\n const key = await this.resolveKey(account);\n return key ? anthropicApiProvider(key) : undefined;\n }\n default:\n return undefined;\n }\n }\n\n /**\n * Read and decrypt a key-form credential from the central credential storage.\n *\n * @param account the account whose credential to resolve\n * @returns the key, or undefined (with a log line) when it cannot be read\n */\n private async resolveKey(account: AccountConfig): Promise<string | undefined> {\n if (!account.credentialId) {\n this.log.warn(`${account.name}: no credential selected \u2014 pick one in the instance settings`);\n return undefined;\n }\n try {\n const credential = await Credentials.getCredentials(this, account.credentialId);\n const values = credential.values as { key?: unknown; password?: unknown };\n const key = typeof values.key === \"string\" && values.key ? values.key : undefined;\n if (!key) {\n this.log.warn(`${account.name}: credential ${account.credentialId} carries no API key`);\n }\n return key;\n } catch (e) {\n this.log.warn(\n `${account.name}: cannot read credential ${account.credentialId} (${e instanceof Error ? e.message : String(e)})`,\n );\n return undefined;\n }\n }\n\n /**\n * Delete the object trees of accounts that are no longer in the table. Disabled\n * rows keep their tree (they are only paused); an EMPTY table deletes nothing \u2014\n * the guard against wiping everything through an accidental clear.\n */\n private async cleanupStaleAccounts(): Promise<void> {\n const keepIds = validAccountIds(this.config.accounts);\n if (keepIds.length === 0) {\n return;\n }\n const keep = new Set([...keepIds, \"info\", \"total\", \"auth\"]);\n try {\n const objects = await this.getAdapterObjectsAsync();\n const roots = new Set<string>();\n for (const id of Object.keys(objects)) {\n const relative = id.substring(this.namespace.length + 1);\n const root = relative.split(\".\")[0];\n if (root && !keep.has(root)) {\n roots.add(root);\n }\n }\n for (const root of roots) {\n this.log.info(`Removing objects of no longer configured account \"${root}\"`);\n await this.delObjectAsync(root, { recursive: true });\n }\n } catch (e) {\n this.log.warn(`Cleanup of stale accounts failed: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n\n /**\n * Tear down synchronously \u2014 no async/await here, else the controller kills the\n * process before cleanup finishes.\n *\n * @param callback invoked when cleanup is done\n */\n private onUnload(callback: () => void): void {\n try {\n this.engine?.stop();\n this.engine = null;\n void this.setState(\"info.connection\", { val: false, ack: true });\n } catch {\n // never block shutdown\n }\n callback();\n }\n}\n\nif (require.main !== module) {\n // Export the constructor in compact mode\n module.exports = (options: Partial<utils.AdapterOptions> | undefined) => new AiUsageAdapter(options);\n} else {\n (() => new AiUsageAdapter())();\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/io-package.json CHANGED
@@ -1,8 +1,21 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "ai-usage",
4
- "version": "0.1.0",
4
+ "version": "0.2.0",
5
5
  "news": {
6
+ "0.2.0": {
7
+ "en": "Completely new settings page: your stored AI credentials as simple on/off switches, and a reliable guided Claude sign-in",
8
+ "de": "Komplett neue Einstellungs-Seite: gespeicherte KI-Zugänge als einfache An/Aus-Schalter und eine zuverlässige geführte Claude-Anmeldung",
9
+ "ru": "Полностью новая страница настроек: сохранённые данные доступа ИИ как простые переключатели и надёжный пошаговый вход Claude",
10
+ "pt": "Página de definições totalmente nova: credenciais de IA guardadas como interruptores simples e um início de sessão Claude guiado e fiável",
11
+ "nl": "Volledig nieuwe instellingenpagina: opgeslagen AI-toegangsgegevens als eenvoudige aan/uit-schakelaars en een betrouwbare begeleide Claude-aanmelding",
12
+ "fr": "Page de réglages entièrement nouvelle : identifiants IA enregistrés comme simples interrupteurs et une connexion Claude guidée et fiable",
13
+ "it": "Pagina delle impostazioni completamente nuova: credenziali IA salvate come semplici interruttori e un accesso Claude guidato e affidabile",
14
+ "es": "Página de ajustes completamente nueva: credenciales de IA guardadas como simples interruptores y un inicio de sesión Claude guiado y fiable",
15
+ "pl": "Całkowicie nowa strona ustawień: zapisane dane dostępu AI jako proste przełączniki i niezawodne prowadzone logowanie Claude",
16
+ "uk": "Повністю нова сторінка налаштувань: збережені дані доступу ШІ як прості перемикачі та надійний покроковий вхід Claude",
17
+ "zh-cn": "全新的设置页面:已保存的 AI 凭据变为简单的开关,并提供可靠的引导式 Claude 登录"
18
+ },
6
19
  "0.1.0": {
7
20
  "en": "First release: usage limits, credits and costs of your Claude, OpenAI, Anthropic, OpenRouter and DeepSeek accounts as datapoints, with a warning at your chosen threshold",
8
21
  "de": "Erste Version: Auslastungs-Limits, Guthaben und Kosten Ihrer Claude-, OpenAI-, Anthropic-, OpenRouter- und DeepSeek-Konten als Datenpunkte, mit Warnung an Ihrer Schwelle",
@@ -15,19 +28,6 @@
15
28
  "pl": "Pierwsze wydanie: limity użycia, środki i koszty kont Claude, OpenAI, Anthropic, OpenRouter i DeepSeek jako punkty danych, z ostrzeżeniem przy wybranym progu",
16
29
  "uk": "Перший випуск: ліміти використання, кредити та витрати акаунтів Claude, OpenAI, Anthropic, OpenRouter і DeepSeek як точки даних, з попередженням на обраному порозі",
17
30
  "zh-cn": "首个版本:将 Claude、OpenAI、Anthropic、OpenRouter 和 DeepSeek 账户的使用限额、余额与费用作为数据点,并在达到所选阈值时发出警告"
18
- },
19
- "0.0.1": {
20
- "en": "Initial development version",
21
- "de": "Erste Entwicklungsversion",
22
- "ru": "Первая версия для разработки",
23
- "pt": "Primeira versão de desenvolvimento",
24
- "nl": "Eerste ontwikkelversie",
25
- "fr": "Première version de développement",
26
- "it": "Prima versione di sviluppo",
27
- "es": "Primera versión de desarrollo",
28
- "pl": "Pierwsza wersja rozwojowa",
29
- "uk": "Перша версія для розробки",
30
- "zh-cn": "初始开发版本"
31
31
  }
32
32
  },
33
33
  "titleLang": {
@@ -100,7 +100,7 @@
100
100
  ],
101
101
  "globalDependencies": [
102
102
  {
103
- "admin": ">=8.0.0"
103
+ "admin": ">=8.0.1"
104
104
  }
105
105
  ]
106
106
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.ai-usage",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "ioBroker adapter monitoring usage, limits and costs of AI accounts (Claude, OpenAI, OpenRouter, DeepSeek)",
5
5
  "author": {
6
6
  "name": "krobi",
@@ -48,6 +48,7 @@
48
48
  "@alcalzone/release-script-plugin-iobroker": "^5.2.0",
49
49
  "@alcalzone/release-script-plugin-license": "^5.2.2",
50
50
  "@iobroker/adapter-dev": "^1.5.0",
51
+ "@iobroker/build-tools": "^3.0.1",
51
52
  "@iobroker/eslint-config": "^2.3.4",
52
53
  "@iobroker/testing": "^5.3.0",
53
54
  "@tsconfig/node22": "^22.0.5",
@@ -76,7 +77,9 @@
76
77
  "format": "prettier --write .",
77
78
  "format:check": "prettier --check .",
78
79
  "translate": "translate-adapter",
79
- "release": "release-script"
80
+ "release": "release-script",
81
+ "build:admin": "node tasks",
82
+ "prepublishOnly": "node tasks"
80
83
  },
81
84
  "bugs": {
82
85
  "url": "https://github.com/krobipd/ioBroker.ai-usage/issues"