@madh-io/alfred-ai 0.9.72 → 0.9.73
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/bundle/index.js +1 -1
- package/package.json +1 -1
package/bundle/index.js
CHANGED
|
@@ -624,7 +624,7 @@ ${o}`}}clearCompleted(e,t){let s=e.list,r=this.todoRepo.clearCompleted(Z(t),s);r
|
|
|
624
624
|
`+s).trim();return{success:!0,data:{project:e,output:r},display:[`**Compose down:** \`${e}\``,"","```",r,"```"].join(`
|
|
625
625
|
`)}}}});import{readFile as Rc,writeFile as Cc,mkdir as Lc}from"node:fs/promises";import{homedir as Un}from"node:os";import{join as Fn}from"node:path";import Mc from"node:crypto";function Mu(){let c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~",e=Mc.randomBytes(86);return Array.from(e).map(t=>c[t%c.length]).join("")}function Ou(c){return Mc.createHash("sha256").update(c).digest("base64url")}function ue(c,e){return c[e]?.value??"?"}var Cu,Nc,eo,Lu,Nu,Du,Dc,to,Pu,so,Oc=T(()=>{"use strict";F();Cu="https://customer.bmwgroup.com/gcdm/oauth/device/code",Nc="https://customer.bmwgroup.com/gcdm/oauth/token",eo="https://api-cardata.bmwgroup.com",Lu="v1",Nu="authenticate_user openid cardata:api:read cardata:streaming:read",Du=5*6e4,Dc="Alfred",to=Fn(Un(),".alfred","bmw-tokens.json");u(Mu,"generateCodeVerifier");u(Ou,"generateCodeChallenge");Pu=["vehicle.drivetrain.batteryManagement.header","vehicle.drivetrain.batteryManagement.maxEnergy","vehicle.drivetrain.electricEngine.remainingElectricRange","vehicle.drivetrain.electricEngine.charging.status","vehicle.drivetrain.electricEngine.charging.level","vehicle.drivetrain.electricEngine.charging.timeRemaining","vehicle.drivetrain.electricEngine.charging.hvStatus","vehicle.powertrain.electric.battery.charging.power","vehicle.drivetrain.electricEngine.charging.acVoltage","vehicle.drivetrain.electricEngine.charging.acAmpere","vehicle.powertrain.electric.battery.stateOfCharge.target","vehicle.powertrain.electric.battery.stateOfHealth.displayed","vehicle.powertrain.tractionBattery.charging.port.anyPosition.isPlugged","vehicle.powertrain.tractionBattery.charging.port.anyPosition.flap.isOpen","vehicle.body.chargingPort.lockedStatus"];u(ue,"tv");so=class extends x{static{u(this,"BMWSkill")}metadata={name:"bmw",category:"infrastructure",description:'BMW CarData \u2014 Fahrzeugdaten abrufen. "authorize" startet den Device-Auth-Flow (einmalig). "status" zeigt SoC, Reichweite, Modell, Batterie-Gesundheit. "charging" zeigt Ladestatus, Leistung, Restzeit, Ziel-SoC, Stecker. "charging_sessions" listet Lade-Sessions (from/to Zeitraum).',riskLevel:"read",version:"2.0.0",inputSchema:{type:"object",properties:{action:{type:"string",enum:["authorize","status","charging","charging_sessions"],description:"BMW CarData action"},vin:{type:"string",description:"Vehicle Identification Number (optional \u2014 uses stored VIN if omitted)"},device_code:{type:"string",description:"Device code from authorize step 1 (for polling token in step 2)"},from:{type:"string",description:"ISO date-time start for charging_sessions (required for that action)"},to:{type:"string",description:"ISO date-time end for charging_sessions (required for that action)"}},required:["action"]}};config;tokens=null;cache=new Map;constructor(e){super(),this.config=e}async execute(e,t){let s=e.action;if(!s)return{success:!1,error:'Missing required field "action"'};try{switch(s){case"authorize":return await this.authorize(e.device_code);case"status":return await this.getStatus(e.vin);case"charging":return await this.getCharging(e.vin);case"charging_sessions":return await this.getChargingSessions(e.vin,e.from,e.to);default:return{success:!1,error:`Unknown action "${s}"`}}}catch(r){return{success:!1,error:`BMW API error: ${r instanceof Error?r.message:String(r)}`}}}async authorize(e){if(e)return await this.pollToken(e);let t=Mu(),s=Ou(t),r=await fetch(Cu,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:this.config.clientId,response_type:"device_code",code_challenge:s,code_challenge_method:"S256",scope:Nu}),signal:AbortSignal.timeout(15e3)});if(!r.ok){let i=await r.text().catch(()=>"");throw new Error(`Device code request failed: HTTP ${r.status} \u2014 ${i.slice(0,300)}`)}let o=await r.json(),n={codeVerifier:t,deviceCode:o.device_code};return await this.savePartialTokens(n),{success:!0,data:o,display:["## BMW Autorisierung","",`1. \xD6ffne: **${o.verification_uri_complete??o.verification_uri}**`,`2. Gib diesen Code ein: **${o.user_code}**`,"",`Danach ruf diese Action erneut auf mit \`device_code: "${o.device_code}"\` um den Token abzuholen.`].join(`
|
|
626
626
|
`)}}async pollToken(e){let s=(await this.loadTokens())?.codeVerifier;if(!s)throw new Error('Kein code_verifier gefunden. Bitte zuerst "authorize" ohne device_code aufrufen.');let r=await fetch(Nc,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:this.config.clientId,device_code:e,grant_type:"urn:ietf:params:oauth:grant-type:device_code",code_verifier:s}),signal:AbortSignal.timeout(15e3)});if(!r.ok){let d=await r.text().catch(()=>"");if(d.includes("authorization_pending"))return{success:!0,data:{status:"pending"},display:"Autorisierung noch ausstehend \u2014 bitte zuerst im Browser best\xE4tigen, dann erneut versuchen."};throw new Error(`Token poll failed: HTTP ${r.status} \u2014 ${d.slice(0,300)}`)}let o=await r.json(),n=o.access_token,i=await this.fetchVin(n),a=await this.ensureContainer(n,i),l={accessToken:n,refreshToken:o.refresh_token,idToken:o.id_token,expiresAt:Date.now()+(o.expires_in??3600)*1e3,vin:i,containerId:a};return await this.saveTokens(l),this.tokens=l,{success:!0,data:{vin:i,containerId:a},display:["## BMW Autorisierung erfolgreich","",`**VIN:** ${i}`,`**Container:** ${a}`,"Tokens gespeichert. Du kannst jetzt Fahrzeugdaten abrufen."].join(`
|
|
627
|
-
`)}}async fetchVin(e){let t=await fetch(`${eo}/customers/vehicles/mappings`,{headers:this.apiHeaders(e),signal:AbortSignal.timeout(15e3)});if(!t.ok)throw new Error(`Failed to fetch vehicles: HTTP ${t.status}`);let s=await t.json(),r=s.find(
|
|
627
|
+
`)}}async fetchVin(e){let t=await fetch(`${eo}/customers/vehicles/mappings`,{headers:this.apiHeaders(e),signal:AbortSignal.timeout(15e3)});if(!t.ok)throw new Error(`Failed to fetch vehicles: HTTP ${t.status}`);let s=await t.json(),r=Array.isArray(s)?s:Array.isArray(s.vehicles)?s.vehicles:Array.isArray(s.mappings)?s.mappings:[],o=r.find(n=>n.mappingType==="PRIMARY");if(!o){if(r.length>0)return r[0].vin;throw new Error(`No vehicles found in account (response: ${JSON.stringify(s).slice(0,200)})`)}return o.vin}async ensureContainer(e,t){let s=await fetch(`${eo}/customers/containers`,{headers:this.apiHeaders(e),signal:AbortSignal.timeout(15e3)});if(s.ok){let n=await s.json(),a=(Array.isArray(n)?n:Array.isArray(n.containers)?n.containers:[]).find(l=>l.name===Dc);if(a)return a.containerId}let r=await fetch(`${eo}/customers/containers`,{method:"POST",headers:{...this.apiHeaders(e),"Content-Type":"application/json"},body:JSON.stringify({name:Dc,purpose:"Alfred AI Assistant",technicalDescriptors:Pu.map(n=>({key:n})),vins:[t]}),signal:AbortSignal.timeout(15e3)});if(!r.ok){let n=await r.text().catch(()=>"");throw new Error(`Container creation failed: HTTP ${r.status} \u2014 ${n.slice(0,300)}`)}return(await r.json()).containerId}apiHeaders(e){return{Authorization:`Bearer ${e}`,"x-version":Lu,Accept:"application/json"}}async apiGet(e){let t=e,s=this.cache.get(t);if(s&&Date.now()-s.ts<Du)return s.data;let r=await this.ensureToken(),o=`${eo}${e}`,n=await fetch(o,{headers:this.apiHeaders(r),signal:AbortSignal.timeout(15e3)});if(n.status===401){let a=await this.loadTokens();a&&(r=await this.refreshAccessToken(a),n=await fetch(o,{headers:this.apiHeaders(r),signal:AbortSignal.timeout(15e3)}))}if(!n.ok){let a=await n.text().catch(()=>"");throw new Error(`HTTP ${n.status} \u2014 ${a.slice(0,300)}`)}let i=await n.json();return this.cache.set(t,{data:i,ts:Date.now()}),i}async loadTokens(){if(this.tokens)return this.tokens;try{let e=await Rc(to,"utf-8");return this.tokens=JSON.parse(e),this.tokens}catch{return null}}async saveTokens(e){await Lc(Fn(Un(),".alfred"),{recursive:!0}),await Cc(to,JSON.stringify(e,null,2),"utf-8")}async savePartialTokens(e){await Lc(Fn(Un(),".alfred"),{recursive:!0});let t={};try{let s=await Rc(to,"utf-8");t=JSON.parse(s)}catch{}await Cc(to,JSON.stringify({...t,...e},null,2),"utf-8")}async ensureToken(){let e=await this.loadTokens();if(!e?.accessToken)throw new Error('Nicht autorisiert. Bitte zuerst die "authorize"-Action aufrufen, um den BMW-Account zu verbinden.');return Date.now()>e.expiresAt-6e4?await this.refreshAccessToken(e):e.accessToken}async refreshAccessToken(e){let t=await fetch(Nc,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:this.config.clientId,grant_type:"refresh_token",refresh_token:e.refreshToken}),signal:AbortSignal.timeout(15e3)});if(!t.ok)throw this.tokens=null,new Error('Token-Refresh fehlgeschlagen. Bitte erneut "authorize" aufrufen.');let s=await t.json(),r={...e,accessToken:s.access_token,refreshToken:s.refresh_token??e.refreshToken,idToken:s.id_token??e.idToken,expiresAt:Date.now()+(s.expires_in??3600)*1e3};return delete r.codeVerifier,delete r.deviceCode,await this.saveTokens(r),this.tokens=r,r.accessToken}async resolveVin(e){if(e)return e;let t=await this.loadTokens();if(t?.vin)return t.vin;throw new Error('Keine VIN angegeben und keine gespeicherte VIN gefunden. Bitte zuerst "authorize" aufrufen.')}async resolveContainerId(){let e=await this.loadTokens();if(e?.containerId)return e.containerId;throw new Error('Kein Container gefunden. Bitte zuerst "authorize" aufrufen.')}async getStatus(e){let t=await this.resolveVin(e),s=await this.resolveContainerId(),[r,o]=await Promise.all([this.apiGet(`/customers/vehicles/${t}/telematicData?containerId=${s}`),this.apiGet(`/customers/vehicles/${t}/basicData`)]),n=r.telematicData??{},i=ue(n,"vehicle.drivetrain.batteryManagement.header"),a=ue(n,"vehicle.drivetrain.electricEngine.remainingElectricRange"),l=ue(n,"vehicle.drivetrain.batteryManagement.maxEnergy"),d=ue(n,"vehicle.powertrain.electric.battery.stateOfHealth.displayed"),p=["## BMW Fahrzeugstatus","",`**Modell:** ${o.model??o.modelName??"?"}`,`**VIN:** ${t}`,`**Ladestand (SoC):** ${i} %`,`**Elektrische Reichweite:** ${a} km`,`**Batteriekapazit\xE4t:** ${l} kWh`,`**Batterie-Gesundheit (SoH):** ${d} %`];return{success:!0,data:{telematic:n,basic:o},display:p.join(`
|
|
628
628
|
`)}}async getCharging(e){let t=await this.resolveVin(e),s=await this.resolveContainerId(),o=(await this.apiGet(`/customers/vehicles/${t}/telematicData?containerId=${s}`)).telematicData??{},n=ue(o,"vehicle.drivetrain.electricEngine.charging.status"),i=ue(o,"vehicle.drivetrain.batteryManagement.header"),a=ue(o,"vehicle.drivetrain.electricEngine.charging.level"),l=ue(o,"vehicle.drivetrain.electricEngine.charging.timeRemaining"),d=ue(o,"vehicle.powertrain.electric.battery.charging.power"),m=ue(o,"vehicle.drivetrain.electricEngine.charging.hvStatus"),p=ue(o,"vehicle.powertrain.electric.battery.stateOfCharge.target"),_=ue(o,"vehicle.drivetrain.electricEngine.charging.acVoltage"),y=ue(o,"vehicle.drivetrain.electricEngine.charging.acAmpere"),g=ue(o,"vehicle.powertrain.tractionBattery.charging.port.anyPosition.isPlugged"),b=ue(o,"vehicle.powertrain.tractionBattery.charging.port.anyPosition.flap.isOpen"),S=ue(o,"vehicle.body.chargingPort.lockedStatus"),A=["## BMW Ladestatus","",`**Status:** ${n}`,`**Ladestand:** ${i} %`,`**Ladelevel:** ${a}`,`**Ladeleistung:** ${d} kW`,`**Restzeit:** ${l} min`,`**Ziel-SoC:** ${p} %`,`**HV-Batterie:** ${m}`,`**AC Spannung:** ${_} V`,`**AC Strom:** ${y} A`,`**Stecker eingesteckt:** ${g}`,`**Ladeklappe offen:** ${b}`,`**Ladeport-Schloss:** ${S}`];return{success:!0,data:o,display:A.join(`
|
|
629
629
|
`)}}async getChargingSessions(e,t,s){let r=await this.resolveVin(e),o=new Date,n=s??o.toISOString(),i=t??new Date(o.getTime()-30*24*60*6e4).toISOString(),a=await this.apiGet(`/customers/vehicles/${r}/chargingHistory?from=${encodeURIComponent(i)}&to=${encodeURIComponent(n)}`),l=a.chargingSessions??a.sessions??[],d=[`## BMW Lade-Sessions (${i.slice(0,10)} \u2013 ${n.slice(0,10)})`,"","| Datum | Dauer | Energie | Start-SoC | End-SoC |","|-------|-------|---------|-----------|---------|"];for(let m of l.slice(0,20)){let p=m.date??m.startTime??"-",_=m.duration??m.chargingDuration??"-",y=m.energyCharged??m.energy??"-",g=m.startSoc??"-",b=m.endSoc??"-";d.push(`| ${p} | ${_} min | ${y} kWh | ${g}% | ${b}% |`)}return l.length===0&&d.push("| - | Keine Sessions gefunden | - | - | - |"),{success:!0,data:a,display:d.join(`
|
|
630
630
|
`)}}}});var Uu,ro,Pc=T(()=>{"use strict";F();Uu="https://routes.googleapis.com/directions/v2:computeRoutes",ro=class extends x{static{u(this,"RoutingSkill")}metadata={name:"routing",category:"information",description:'Routenberechnung mit Live-Traffic via Google Routes API. "route" berechnet Route mit Distanz, Dauer und Dauer im aktuellen Verkehr. "departure_time" empfiehlt wann man losfahren soll, um zu einer bestimmten Zeit anzukommen. Orte als Adresse oder "lat,lng" angeben.',riskLevel:"read",version:"1.0.0",inputSchema:{type:"object",properties:{action:{type:"string",enum:["route","departure_time"],description:"Routing action"},origin:{type:"string",description:'Start-Adresse oder "lat,lng"'},destination:{type:"string",description:'Ziel-Adresse oder "lat,lng"'},departure_time:{type:"string",description:"ISO-Zeitpunkt f\xFCr Abfahrt (optional, f\xFCr Traffic-Berechnung)"},arrival_time:{type:"string",description:"ISO-Zeitpunkt gew\xFCnschte Ankunft (f\xFCr departure_time-Action)"},travel_mode:{type:"string",enum:["DRIVE","BICYCLE","WALK","TRANSIT"],description:"Fortbewegungsart (Standard: DRIVE)"}},required:["action","origin","destination"]}};config;constructor(e){super(),this.config=e}async execute(e,t){let s=e.action;if(!s)return{success:!1,error:'Missing required field "action"'};let r=e.origin,o=e.destination;if(!r)return{success:!1,error:'Missing required field "origin"'};if(!o)return{success:!1,error:'Missing required field "destination"'};try{switch(s){case"route":return await this.computeRoute(r,o,e.departure_time,e.travel_mode);case"departure_time":return await this.computeDepartureTime(r,o,e.arrival_time,e.travel_mode);default:return{success:!1,error:`Unknown action "${s}"`}}}catch(n){return{success:!1,error:`Google Routes API error: ${n instanceof Error?n.message:String(n)}`}}}async computeRoute(e,t,s,r){let o=this.buildRequestBody(e,t,r,s),i=(await this.callRoutesApi(o)).routes?.[0];if(!i)return{success:!1,error:"Keine Route gefunden."};let a=(i.distanceMeters/1e3).toFixed(1),l=this.parseDuration(i.duration),d=this.parseDuration(i.staticDuration),m=l-d,p=["## Route","",`**${e}** \u2192 **${t}**`,"",`**Distanz:** ${a} km`,`**Fahrzeit (aktuell):** ${this.formatMinutes(l)}`,`**Fahrzeit (ohne Verkehr):** ${this.formatMinutes(d)}`];if(m>1&&p.push(`**Verkehrsverz\xF6gerung:** +${this.formatMinutes(m)}`),s){let _=new Date(new Date(s).getTime()+l*6e4);p.push(`**Gesch\xE4tzte Ankunft:** ${_.toLocaleString("de-AT")}`)}return{success:!0,data:{distanceKm:parseFloat(a),durationMinutes:l,staticDurationMinutes:d},display:p.join(`
|