UNPKG

eve

Version:

Filesystem-first framework for durable backend AI agents that run anywhere.

20 lines (17 loc) • 5.49 kB
import{appendEnv}from"../../append-env.js";import{select,text}from"../../ask.js";import{writeTextFile}from"../../scaffold/files.js";import{deriveSlackConnectorSlug}from"../../scaffold/update/channels.js";import"../../scaffold/index.js";import{provisionPhotonConnector}from"./connect.js";import{findDedicatedPhotonLine,provisionPhotonProject,usePhotonProject,validatePhotonPhoneNumber}from"./management.js";import{openUrl}from"../../primitives/open-url.js";import{basename,join}from"node:path";const defaultDeps={appendEnv,deriveConnectorSlug:deriveSlackConnectorSlug,findDedicatedLine:findDedicatedPhotonLine,openUrl,provisionConnector:provisionPhotonConnector,provisionProject:provisionPhotonProject,useProject:usePhotonProject,writeTextFile};function connectTemplate(e){return`import { connectPhotonCredentials } from "@vercel/connect/eve"; import { photonIMessageChannel } from "eve/channels/photon"; export default photonIMessageChannel({ credentials: connectPhotonCredentials(${JSON.stringify(e)}), }); `}async function preparePhotonSetup(e,t=defaultDeps){let n=basename(e.appRoot),r=await e.asker.ask(select({key:`photon-credentials`,message:`How would you like to configure Photon?`,options:[{id:`vercel`,value:`vercel-connect`,label:`Set up Vercel Connect`,hint:`Use a linked Vercel project`},{id:`portable`,value:`environment`,label:`Use portable credentials`,hint:`Configure the Photon webhook manually after deployment`}],recommended:e.environment.vercel.kind===`available`?`vercel-connect`:`environment`,required:!0})),i=`eve · ${n||`agent`}`,a=await e.asker.askEditable({key:`photon-project-source`,message:`Photon project`,options:[{id:`create`,value:`create`,label:`Create a new Photon project`,hint:`Name: ${i}`},{id:`existing`,value:`existing`,label:`Use an existing Photon project`,hint:`Enter its project credentials`}],recommended:`create`,required:!0,editable:{key:`photon-project-name`,value:`create`,label:`Name`,recommended:i,validate:e=>e.trim().length===0?`Project name cannot be empty.`:null}}),o=`create`;if(a.value===`existing`){let t=await e.asker.ask(text({key:`photon-project-id`,message:`Photon project ID`,required:!0})),n=await e.asker.ask(text({key:`photon-project-secret`,message:`Photon project secret`,required:!0,sensitive:!0,environment:`IMESSAGE_PROJECT_SECRET`}));o={projectId:t.trim(),projectSecret:n.trim()}}let s=o===`create`?void 0:await t.findDedicatedLine(o),c=s===void 0?await e.asker.ask(text({key:`photon-phone-number`,message:`Your iMessage phone number`,placeholder:`+15551234567`,required:!0,validate:validatePhotonPhoneNumber})):void 0,l=r===`vercel-connect`?await e.resolveVercelProject(`Photon`):void 0,u={agentName:n,credentials:r,photonProject:o,dedicatedLine:s,phoneNumber:c};return a.value===`create`&&(u.photonProjectName=a.text??i),l!==void 0&&(u.vercelProject=l),u}async function resolvePhotonProject(e,t,n){if(e.photonProject!==`create`)return n.useProject({...e.photonProject,dedicatedLine:e.dedicatedLine,phoneNumber:e.phoneNumber});if(e.phoneNumber===void 0)throw Error(`Photon phone number is required.`);let r=t.presenter.log.spinner?.(`Waiting for Photon approval…`,{kind:`external-action`,emphasis:`browser`}),i;try{let r=await n.provisionProject({projectName:e.photonProjectName??`eve · ${e.agentName||`agent`}`,phoneNumber:e.phoneNumber,signal:t.signal,onAuthorization(e){i=t.presenter.beginExternalAction({message:`Authorize Photon`,url:e.verificationUrl,userCode:e.userCode}),n.openUrl(e.verificationUrl)}});return i?.complete(),r}finally{r?.stop()}}async function applyPhotonSetup(e,t,n=defaultDeps){let r=await resolvePhotonProject(e,t,n);try{let i=join(t.appRoot,`agent/channels/photon.ts`);if(e.credentials===`vercel-connect`){let a=await n.deriveConnectorSlug(t.appRoot,e.agentName),o=await n.provisionConnector({credentials:r,log:t.presenter.log,project:e.vercelProject,projectRoot:t.appRoot,slug:a,signal:t.signal});await n.writeTextFile(i,connectTemplate(o.uid),{force:t.force})}else await n.appendEnv(join(t.appRoot,`.env.local`),{IMESSAGE_PROJECT_ID:r.projectId,IMESSAGE_PROJECT_SECRET:r.projectSecret}),await n.writeTextFile(i,`import { photonIMessageChannel } from "eve/channels/photon"; async function photonCredentials() { const projectId = process.env.IMESSAGE_PROJECT_ID; const projectSecret = process.env.IMESSAGE_PROJECT_SECRET; if (!projectId || !projectSecret) throw new Error("Photon project credentials are required."); return { projectId, projectSecret }; } export default photonIMessageChannel({ credentials: photonCredentials, webhookSecret: process.env.IMESSAGE_WEBHOOK_SECRET, }); `,{force:t.force}),t.presenter.nextSteps([`Deploy the agent, then create a Photon webhook pointing to https://<your-host>/eve/v1/photon.`,`Copy the webhook signing secret into IMESSAGE_WEBHOOK_SECRET, alongside IMESSAGE_PROJECT_ID and IMESSAGE_PROJECT_SECRET, in your host's encrypted environment variables.`]);t.presenter.log.success(`Scaffolded channel: photon`),r.assignedPhoneNumber!==void 0&&t.presenter.note(r.assignedPhoneNumber,`Text your agent`,{tone:`success`});let a=`https://app.photon.codes/dashboard/${r.projectId}`;return t.presenter.note(a,`Photon project`,{tone:`success`}),{facts:[{label:`Photon project dashboard`,value:a,kind:`url`},...r.assignedPhoneNumber===void 0?[]:[{label:`Agent phone number`,value:r.assignedPhoneNumber,kind:`phone`}]],deploymentRequired:!0}}catch(e){throw await r.cleanup().catch(()=>{}),e}}export{applyPhotonSetup,preparePhotonSetup};