foundingbot 0.1.4 → 0.1.6

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
@@ -1,5 +1,19 @@
1
1
  # @foundingbot/cli
2
2
 
3
+ ## 0.1.6
4
+
5
+ ### Patch Changes
6
+
7
+ - [#71](https://github.com/phuctm97/foundingbot/pull/71) [`44c1812`](https://github.com/phuctm97/foundingbot/commit/44c1812aea1972f71918282e8f608c7c5a11ac34) Thanks [@kittranofficial](https://github.com/kittranofficial)! - Support running the CLI with Node.js or Bun, using shared native credential storage and runtime-neutral environment access.
8
+
9
+ Use a fixed production credential service and namespace-scoped services for non-production servers.
10
+
11
+ ## 0.1.5
12
+
13
+ ### Patch Changes
14
+
15
+ - [#70](https://github.com/phuctm97/foundingbot/pull/70) [`e94af93`](https://github.com/phuctm97/foundingbot/commit/e94af938e8310acafca6036323986b5897f27adc) Thanks [@kittranofficial](https://github.com/kittranofficial)! - Default bot creation to the production Telegram manager and username template when environment overrides are absent.
16
+
3
17
  ## 0.1.4
4
18
 
5
19
  ### Patch Changes
package/dist/main.mjs CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bun
2
- import{defineCittyPlugin as e,defineCommand as t,runMain as n}from"citty";import{cancel as r,confirm as i,group as a,isCancel as o,log as s,note as c,outro as l,password as u,select as d,text as f}from"@clack/prompts";import{createAuthClient as p}from"better-auth/client";import{once as ee}from"node:events";import m from"open";import{z as h}from"zod";import{createORPCClient as g}from"@orpc/client";import{RPCLink as _}from"@orpc/client/fetch";import{randomBytes as v}from"node:crypto";import{contract as y}from"@foundingbot/util";import{OpenAPILink as b}from"@orpc/openapi-client/fetch";var x=`0.1.4`,te=`Founding Bot CLI client`;const S=Bun.env.FOUNDINGBOT_SERVER_URL||`https://server.founding.bot`,C={service:`bot.founding.cli.auth.token`,name:S};async function w(){return await Bun.secrets.get(C)||void 0}function T(e){return Bun.secrets.set({...C,value:e})}function E(){return Bun.secrets.delete(C)}async function D(){let e=await w();return{authorization:e?`Bearer ${e}`:void 0}}async function O(e){throw await E(),Error(`Verify ${e} to continue. Check your inbox and any spam, junk, or other folders, or run the resend-verification command to request another verification email.`)}function k(e){let t=e instanceof Error?e.cause:void 0;return typeof t==`object`&&!!t&&`code`in t&&t.code===`EMAIL_NOT_VERIFIED`}var A=p({baseURL:new URL(`/auth`,S).href,fetchOptions:{throw:!0,auth:{token:w,type:`Bearer`},onSuccess:async({response:e})=>{let t=e.headers.get(`set-auth-token`);t&&await T(t)},customFetchImpl:(e,t)=>globalThis.fetch(e,t)}});const j=e({name:`require-signed-in`,setup:async e=>{let t=await A.getSession();if(!t)throw Error(`Not signed in. Run the sign-in command, then try again.`);t.user.emailVerified||await O(t.user.email),e.data=t}});function M(e){return t({...e,run:e.run,plugins:[j]})}const ne=e({name:`require-signed-out`,setup:async()=>{let e=await A.getSession();if(e)throw Error(`Already signed in as ${e.user.email}. Run the sign-out command, then try again.`)}});function N(e){return t({...e,plugins:[ne]})}function P(e){return h.string().refine(e=>e.trim().length>0,`${e} is required`)}const F=h.email(`Enter a valid email`);function I(e){return P(e).pipe(F)}function L(e){return P(e).min(8,`Password must be at least 8 characters`).max(128,`Password must be at most 128 characters`)}const R=P(`Confirm password`);function z(e){return R.refine(t=>t===e,`Passwords do not match`)}const B=P(`Name`),V=h.union([h.literal(``),h.url({error:`Enter a valid HTTP or HTTPS URL`,protocol:/^https?$/})]),H=I(`Email`),U=I(`New email`),W=P(`Token`),G=P(`Activation code`).regex(/^[a-f0-9]{64}$/,`Enter a valid activation code`),K=P(`Password`),q=P(`Current password`),J=L(`New password`),Y=L(`Password`);function X(e){r(e),process.exit(0)}async function Z(e){try{let t=await m(e);if(process.platform===`darwin`){t.ref();let[e]=await ee(t,`close`);if(e!==0)throw Error(`Browser launch failed`)}}catch{s.warn(`Couldn’t open your browser. Open the link above manually.`)}}const re=new _({url:new URL(`/rpc`,S),headers:D,fetch:(e,t)=>globalThis.fetch(e,t)});var Q=g(re),ie=M({meta:{name:`activate-bot`,description:`Activate your bot`},run:async()=>{let{activationCode:e}=await a({activationCode:()=>u({message:`Activation code`,validate:G,clearOnError:!0})},{onCancel:()=>X(`Bot activation canceled`)}),{url:t}=await Q.activateBot({activationCode:e});s.info(`Complete checkout in Stripe:\n${t}`),await Z(t),l(`Complete checkout to activate your bot.`)}}),ae=M({meta:{name:`change-email`,description:`Change your email`},run:async()=>{let{newEmail:e}=await a({newEmail:()=>f({message:`New email`,validate:U})},{onCancel:()=>X(`Email change canceled`)});await A.changeEmail({newEmail:e,callbackURL:`/cli/email-change`}),l(`Email change requested for ${e}`)}}),oe=M({meta:{name:`change-password`,description:`Change your password`},run:async()=>{let e=await a({currentPassword:()=>u({message:`Current password`,validate:q}),newPassword:()=>u({message:`New password`,validate:J,clearOnError:!0}),confirmPassword:({results:e})=>u({message:`Confirm password`,validate:z(e.newPassword||``),clearOnError:!0}),revokeOtherSessions:()=>i({message:`Revoke other sessions?`})},{onCancel:()=>X(`Password change canceled`)});await A.changePassword({currentPassword:e.currentPassword,newPassword:e.newPassword,revokeOtherSessions:e.revokeOtherSessions}),l(`Password change succeeded`)}}),se=M({meta:{name:`create-bot`,description:`Create your bot`},run:async()=>{let e=Bun.env.TELEGRAM_MANAGER_USERNAME;if(!e)throw Error(`TELEGRAM_MANAGER_USERNAME is not configured`);let t=Bun.env.TELEGRAM_BOT_TEMPLATE;if(!t)throw Error(`TELEGRAM_BOT_TEMPLATE is not configured`);if(t.split(`<random>`).length!==2)throw Error(`TELEGRAM_BOT_TEMPLATE must contain exactly one <random> placeholder`);let n=t.replace(`<random>`,v(8).toString(`hex`));if(n.length>32||/[^A-Za-z0-9_]/.test(n)||!n.toLowerCase().endsWith(`bot`))throw Error(`TELEGRAM_BOT_TEMPLATE must generate a username of at most 32 ASCII letters, digits, or underscores ending in bot`);let r=`https://t.me/newbot/${encodeURIComponent(e)}/${encodeURIComponent(n)}`;s.info(`Create your bot in Telegram:\n${r}`),await Z(r),l(`Finish creating your bot, then press Start or send /start to continue.`)}}),ce=N({meta:{name:`forgot-password`,description:`Request a password reset`},run:async()=>{let{email:e}=await a({email:()=>f({message:`Email`,validate:H})},{onCancel:()=>X(`Password reset request canceled`)});try{await A.requestPasswordReset({email:e,redirectTo:`/cli/password-reset`})}catch(t){throw Error(`Failed to request password reset for ${e}`,{cause:t})}l(`If an account exists for ${e}, password reset instructions have been sent`)}}),le=M({meta:{name:`list-bots`,description:`List your bots`},run:async()=>{let e=await Q.listBots();if(e.length===0){console.log(`No bots`);return}e.forEach((e,t)=>{c(`Name: ${e.name}\nTelegram bot username: @${e.telegramBotUsername}\nTelegram bot ID: ${e.telegramBotId}`,`Bot ${t+1}`)})}}),ue=M({meta:{name:`list-sessions`,description:`List your active sessions`},run:async({data:e})=>{let t=await A.listSessions();if(t.length===0){console.log(`No active sessions`);return}t.forEach((t,n)=>{c([`IP address: ${t.ipAddress||`(None)`}`,`User agent: ${t.userAgent||`(None)`}`,`ID: ${t.id}`,`Created: ${t.createdAt.toISOString()}`,`Updated: ${t.updatedAt.toISOString()}`,`Expires: ${t.expiresAt.toISOString()}`].join(`
3
- `),`Session ${n+1}${t.id===e.session.id?` (Current)`:``}`)})}}),de=M({meta:{name:`manage-billing`,description:`Manage your billing`},run:async()=>{let{url:e}=await Q.manageBilling();s.info(`Manage your billing in Stripe:\n${e}`),await Z(e),l(`Continue in your browser.`)}});const fe=new b(y,{url:new URL(`/api`,S),headers:D,fetch:(e,t)=>globalThis.fetch(e,t)});var pe=g(fe),me=t({meta:{name:`ping`,description:`Run health checks`},run:async()=>{let e=await fetch(new URL(`/health`,S));if(!e.ok)throw Error(`Server health check failed`);console.log(`Server health:`,e.statusText||`OK`);let t=await fetch(new URL(`/auth/ok`,S));if(!t.ok)throw Error(`Auth health check failed`);console.log(`Auth health:`,t.statusText||`OK`);let n=await Q.ping();console.log(`RPC ping:`,n);let r=await pe.ping();console.log(`API ping:`,r)}}),he=M({meta:{name:`rebuild-bot`,description:`Rebuild your bot`},run:async()=>{let e=await Q.listBots();if(e.length===0){console.log(`No bots`);return}let t=()=>X(`Bot rebuild canceled`),{index:n,confirmed:r}=await a({index:()=>d({message:`Bot`,options:e.map((e,t)=>({value:t,label:`${e.name} (@${e.telegramBotUsername})`}))}),confirmed:()=>i({message:`Rebuild this bot and permanently delete its data?`,initialValue:!1})},{onCancel:t});r||t();let o=e[n];if(!o)throw Error(`Bot not found`);await Q.rebuildBot({telegramBotId:o.telegramBotId}),l(`Bot rebuilt`)}}),ge=M({meta:{name:`remove-bot`,description:`Remove your bot`},run:async()=>{let e=await Q.listBots();if(e.length===0){console.log(`No bots`);return}let t=()=>X(`Bot removal canceled`),{index:n,confirmed:r}=await a({index:()=>d({message:`Bot`,options:e.map((e,t)=>({value:t,label:`${e.name} (@${e.telegramBotUsername})`}))}),confirmed:()=>i({message:`Remove this bot and permanently delete its data?`,initialValue:!1})},{onCancel:t});r||t();let c=e[n];if(!c)throw Error(`Bot not found`);await Q.removeBot({telegramBotId:c.telegramBotId}),s.success(`Bot removed`);let u=`${c.name} (@${c.telegramBotUsername})`,f=await i({message:`Also delete ${u} from Telegram?`,initialValue:!0});if(o(f)||!f){l(`${u} remains on Telegram. To reconnect it to your account, send /start to it and follow the instructions.`);return}let p=new URL(`https://t.me/BotFather`);p.searchParams.set(`start`,`deletebot`),s.info(`Continue deleting ${u} from Telegram:\n${p}`),await Z(p.href),l(`Select @${c.telegramBotUsername} in BotFather and follow the instructions.`)}}),_e=M({meta:{name:`repair-bot`,description:`Check and repair your bot`},run:async()=>{let e=await Q.listBots();if(e.length===0){console.log(`No bots`);return}let{index:t}=await a({index:()=>d({message:`Bot`,options:e.map((e,t)=>({value:t,label:`${e.name} (@${e.telegramBotUsername})`}))})},{onCancel:()=>X(`Bot repair canceled`)}),n=e[t];if(!n)throw Error(`Bot not found`);await Q.repairBot({telegramBotId:n.telegramBotId}),l(`Bot repaired`)}}),ve=N({meta:{name:`resend-verification`,description:`Request another verification email`},run:async()=>{let{email:e}=await a({email:()=>f({message:`Email`,validate:H})},{onCancel:()=>X(`Verification email request canceled`)});try{await A.sendVerificationEmail({email:e,callbackURL:`/cli/email-verification`})}catch(t){throw Error(`Failed to request verification email for ${e}`,{cause:t})}l(`Verification email requested for ${e}`)}}),ye=N({meta:{name:`reset-password`,description:`Reset your password`},run:async()=>{let e=await a({token:()=>u({message:`Token`,validate:W,clearOnError:!0}),newPassword:()=>u({message:`New password`,validate:J,clearOnError:!0}),confirmPassword:({results:e})=>u({message:`Confirm password`,validate:z(e.newPassword||``),clearOnError:!0})},{onCancel:()=>X(`Password reset canceled`)});await A.resetPassword({token:e.token,newPassword:e.newPassword}),l(`Password reset succeeded. Run the sign-in command to continue.`)}}),be=M({meta:{name:`reset-usage`,description:`Reset your bot’s usage limits`},run:async()=>{let e=await Q.listBots();if(e.length===0){console.log(`No bots`);return}let t=()=>X(`Usage reset canceled`),{index:n,confirmed:r}=await a({index:()=>d({message:`Bot`,options:e.map((e,t)=>({value:t,label:`${e.name} (@${e.telegramBotUsername})`}))}),confirmed:()=>i({message:`Use an available reset for this bot?`,initialValue:!1})},{onCancel:t});r||t();let o=e[n];if(!o)throw Error(`Bot not found`);await Q.resetUsage({telegramBotId:o.telegramBotId}),l(`Usage limits reset`)}}),$=M({meta:{name:`revoke-session`,description:`Revoke an active session`},run:async({data:e})=>{let t=await A.listSessions();if(t.length===0){console.log(`No active sessions`);return}let{index:n}=await a({index:()=>d({message:`Session`,options:t.map((t,n)=>({value:n,label:`Session ${n+1}${t.id===e.session.id?` (Current)`:``}`,hint:`Created: ${t.createdAt.toISOString()} · User agent: ${t.userAgent||`(None)`} · IP address: ${t.ipAddress||`(None)`}`}))})},{onCancel:()=>X(`Session revocation canceled`)}),r=t[n];if(!r)throw Error(`Session not found`);await A.revokeSession({token:r.token}),r.id===e.session.id&&await E(),l(`Session revoked`)}}),xe=N({meta:{name:`sign-in`,description:`Sign in`},run:async()=>{let e=await a({email:()=>f({message:`Email`,validate:H}),password:()=>u({message:`Password`,validate:K})},{onCancel:()=>X(`Sign-in canceled`)}),t;try{({user:t}=await A.signIn.email(e))}catch(t){throw k(t)&&await O(e.email),t}t.emailVerified||await O(t.email),l(`Signed in as ${t.email}`)}}),Se=t({meta:{name:`sign-out`,description:`Sign out`},run:async()=>{await A.signOut(),await E(),l(`Signed out`)}}),Ce=N({meta:{name:`sign-up`,description:`Create an account`},run:async()=>{let e=await a({name:()=>f({message:`Name`,validate:B}),email:()=>f({message:`Email`,validate:H}),password:()=>u({message:`Password`,validate:Y,clearOnError:!0})},{onCancel:()=>X(`Sign-up canceled`)}),t=await A.signUp.email({...e,callbackURL:`/cli/email-verification`});t.user.emailVerified||await O(t.user.email),l(`Signed up as ${t.user.email}`)}}),we=M({meta:{name:`update-profile`,description:`Update your profile`},run:async({data:e})=>{let{name:t,avatar:n}=await a({name:()=>f({message:`Name`,initialValue:e.user.name,validate:B}),avatar:()=>f({message:`Avatar`,initialValue:e.user.image||void 0,placeholder:`Optional URL`,validate:V})},{onCancel:()=>X(`Profile update canceled`)});await A.updateUser({name:t,image:n||null}),l(`Profile update succeeded`)}}),Te=M({meta:{name:`view-usage`,description:`View your bot’s usage details`},run:async()=>{let e=await Q.listBots();if(e.length===0){console.log(`No bots`);return}let{index:t}=await a({index:()=>d({message:`Bot`,options:e.map((e,t)=>({value:t,label:`${e.name} (@${e.telegramBotUsername})`}))})},{onCancel:()=>X(`Usage check canceled`)}),n=e[t];if(!n)throw Error(`Bot not found`);let r=await Q.viewUsage({telegramBotId:n.telegramBotId}),i=`Subscription: (None)`;if(r.subscription){let{plan:e,price:t,usage:n}=r.subscription,a=new Intl.NumberFormat(`en-US`,{style:`currency`,currency:t.currency}),o=10**(a.resolvedOptions().maximumFractionDigits??2),s=t.duration.count===1?t.duration.unit:`${t.duration.count} ${t.duration.unit}s`;i=`Subscription:\n Plan: ${e}\n Price: ${a.format(t.amount/o)}\n Billing: Every ${s}\n Usage:\n Five-hour used: ${n.fiveHourUsedPercent}%\n Weekly used: ${n.weeklyUsedPercent}%\n Available resets: ${n.availableResets}`}c(`Status: ${r.status||`(None)`}\n${i}`,`${n.name} (@${n.telegramBotUsername})`)}}),Ee=M({meta:{name:`whoami`,description:`Show the current user`},run:({data:e})=>{c([`Name: ${e.user.name}`,`Email: ${e.user.email}`,`Avatar: ${e.user.image||`(None)`}`,`ID: ${e.user.id}`,`Created: ${e.user.createdAt.toISOString()}`,`Updated: ${e.user.updatedAt.toISOString()}`].join(`
4
- `),`Current user`)}});const De=t({meta:{name:`foundingbot`,version:x,description:te},subCommands:{"sign-up":Ce,"resend-verification":ve,"sign-in":xe,"sign-out":Se,"forgot-password":ce,"reset-password":ye,whoami:Ee,"update-profile":we,"change-email":ae,"change-password":oe,"list-sessions":ue,"revoke-session":$,"create-bot":se,"activate-bot":ie,"list-bots":le,"repair-bot":_e,"rebuild-bot":he,"remove-bot":ge,"view-usage":Te,"reset-usage":be,"manage-billing":de,ping:me}});await n(De);export{};
1
+ #!/usr/bin/env node
2
+ import{defineCittyPlugin as e,defineCommand as t,runMain as n}from"citty";import{cancel as r,confirm as i,group as a,isCancel as o,log as s,note as c,outro as l,password as u,select as d,text as f}from"@clack/prompts";import{createAuthClient as p}from"better-auth/client";import{AsyncEntry as m}from"@napi-rs/keyring";import{once as ee}from"node:events";import te from"open";import{z as h}from"zod";import{createORPCClient as g}from"@orpc/client";import{RPCLink as _}from"@orpc/client/fetch";import{randomBytes as ne}from"node:crypto";import{contract as v}from"@foundingbot/util";import{OpenAPILink as y}from"@orpc/openapi-client/fetch";var b=`0.1.6`,x=`Founding Bot CLI client`;const S=process.env.FOUNDINGBOT_SERVER_URL||`https://server.founding.bot`;function re(){if(new URL(S).origin===`https://server.founding.bot`)return`bot.founding.cli.auth`;let e=process.env.FOUNDINGBOT_NAMESPACE?.trim();if(!e)throw Error(`FOUNDINGBOT_NAMESPACE is required for non-production servers`);return`bot.founding.cli.auth:${e}`}const C=new m(re(),`token`);async function w(){return await C.getPassword()||void 0}function ie(e){return C.setPassword(e)}function T(){return C.deleteCredential()}async function E(){let e=await w();return{authorization:e?`Bearer ${e}`:void 0}}async function D(e){throw await T(),Error(`Verify ${e} to continue. Check your inbox and any spam, junk, or other folders, or run the resend-verification command to request another verification email.`)}function O(e){let t=e instanceof Error?e.cause:void 0;return typeof t==`object`&&!!t&&`code`in t&&t.code===`EMAIL_NOT_VERIFIED`}var k=p({baseURL:new URL(`/auth`,S).href,fetchOptions:{throw:!0,auth:{token:w,type:`Bearer`},onSuccess:async({response:e})=>{let t=e.headers.get(`set-auth-token`);t&&await ie(t)},customFetchImpl:(e,t)=>globalThis.fetch(e,t)}});const A=e({name:`require-signed-in`,setup:async e=>{let t=await k.getSession();if(!t)throw Error(`Not signed in. Run the sign-in command, then try again.`);t.user.emailVerified||await D(t.user.email),e.data=t}});function j(e){return t({...e,run:e.run,plugins:[A]})}const M=e({name:`require-signed-out`,setup:async()=>{let e=await k.getSession();if(e)throw Error(`Already signed in as ${e.user.email}. Run the sign-out command, then try again.`)}});function N(e){return t({...e,plugins:[M]})}function P(e){return h.string().refine(e=>e.trim().length>0,`${e} is required`)}const F=h.email(`Enter a valid email`);function I(e){return P(e).pipe(F)}function L(e){return P(e).min(8,`Password must be at least 8 characters`).max(128,`Password must be at most 128 characters`)}const R=P(`Confirm password`);function z(e){return R.refine(t=>t===e,`Passwords do not match`)}const B=P(`Name`),V=h.union([h.literal(``),h.url({error:`Enter a valid HTTP or HTTPS URL`,protocol:/^https?$/})]),H=I(`Email`),U=I(`New email`),W=P(`Token`),G=P(`Activation code`).regex(/^[a-f0-9]{64}$/,`Enter a valid activation code`),K=P(`Password`),q=P(`Current password`),J=L(`New password`),Y=L(`Password`);function X(e){r(e),process.exit(0)}async function Z(e){try{let t=await te(e);if(process.platform===`darwin`){t.ref();let[e]=await ee(t,`close`);if(e!==0)throw Error(`Browser launch failed`)}}catch{s.warn(`Couldn’t open your browser. Open the link above manually.`)}}const ae=new _({url:new URL(`/rpc`,S),headers:E,fetch:(e,t)=>globalThis.fetch(e,t)});var Q=g(ae),oe=j({meta:{name:`activate-bot`,description:`Activate your bot`},run:async()=>{let{activationCode:e}=await a({activationCode:()=>u({message:`Activation code`,validate:G,clearOnError:!0})},{onCancel:()=>X(`Bot activation canceled`)}),{url:t}=await Q.activateBot({activationCode:e});s.info(`Complete checkout in Stripe:\n${t}`),await Z(t),l(`Complete checkout to activate your bot.`)}}),se=j({meta:{name:`change-email`,description:`Change your email`},run:async()=>{let{newEmail:e}=await a({newEmail:()=>f({message:`New email`,validate:U})},{onCancel:()=>X(`Email change canceled`)});await k.changeEmail({newEmail:e,callbackURL:`/cli/email-change`}),l(`Email change requested for ${e}`)}}),ce=j({meta:{name:`change-password`,description:`Change your password`},run:async()=>{let e=await a({currentPassword:()=>u({message:`Current password`,validate:q}),newPassword:()=>u({message:`New password`,validate:J,clearOnError:!0}),confirmPassword:({results:e})=>u({message:`Confirm password`,validate:z(e.newPassword||``),clearOnError:!0}),revokeOtherSessions:()=>i({message:`Revoke other sessions?`})},{onCancel:()=>X(`Password change canceled`)});await k.changePassword({currentPassword:e.currentPassword,newPassword:e.newPassword,revokeOtherSessions:e.revokeOtherSessions}),l(`Password change succeeded`)}}),le=j({meta:{name:`create-bot`,description:`Create your bot`},run:async()=>{let e=process.env.TELEGRAM_MANAGER_USERNAME||`itsfoundingbot`,t=process.env.TELEGRAM_BOT_TEMPLATE||`founding_<random>_bot`;if(t.split(`<random>`).length!==2)throw Error(`TELEGRAM_BOT_TEMPLATE must contain exactly one <random> placeholder`);let n=t.replace(`<random>`,ne(8).toString(`hex`));if(n.length>32||/[^A-Za-z0-9_]/.test(n)||!n.toLowerCase().endsWith(`bot`))throw Error(`TELEGRAM_BOT_TEMPLATE must generate a username of at most 32 ASCII letters, digits, or underscores ending in bot`);let r=`https://t.me/newbot/${encodeURIComponent(e)}/${encodeURIComponent(n)}`;s.info(`Create your bot in Telegram:\n${r}`),await Z(r),l(`Finish creating your bot, then press Start or send /start to continue.`)}}),ue=N({meta:{name:`forgot-password`,description:`Request a password reset`},run:async()=>{let{email:e}=await a({email:()=>f({message:`Email`,validate:H})},{onCancel:()=>X(`Password reset request canceled`)});try{await k.requestPasswordReset({email:e,redirectTo:`/cli/password-reset`})}catch(t){throw Error(`Failed to request password reset for ${e}`,{cause:t})}l(`If an account exists for ${e}, password reset instructions have been sent`)}}),de=j({meta:{name:`list-bots`,description:`List your bots`},run:async()=>{let e=await Q.listBots();if(e.length===0){console.log(`No bots`);return}e.forEach((e,t)=>{c(`Name: ${e.name}\nTelegram bot username: @${e.telegramBotUsername}\nTelegram bot ID: ${e.telegramBotId}`,`Bot ${t+1}`)})}}),fe=j({meta:{name:`list-sessions`,description:`List your active sessions`},run:async({data:e})=>{let t=await k.listSessions();if(t.length===0){console.log(`No active sessions`);return}t.forEach((t,n)=>{c([`IP address: ${t.ipAddress||`(None)`}`,`User agent: ${t.userAgent||`(None)`}`,`ID: ${t.id}`,`Created: ${t.createdAt.toISOString()}`,`Updated: ${t.updatedAt.toISOString()}`,`Expires: ${t.expiresAt.toISOString()}`].join(`
3
+ `),`Session ${n+1}${t.id===e.session.id?` (Current)`:``}`)})}}),pe=j({meta:{name:`manage-billing`,description:`Manage your billing`},run:async()=>{let{url:e}=await Q.manageBilling();s.info(`Manage your billing in Stripe:\n${e}`),await Z(e),l(`Continue in your browser.`)}});const me=new y(v,{url:new URL(`/api`,S),headers:E,fetch:(e,t)=>globalThis.fetch(e,t)});var he=g(me),ge=t({meta:{name:`ping`,description:`Run health checks`},run:async()=>{let e=await fetch(new URL(`/health`,S));if(!e.ok)throw Error(`Server health check failed`);console.log(`Server health:`,e.statusText||`OK`);let t=await fetch(new URL(`/auth/ok`,S));if(!t.ok)throw Error(`Auth health check failed`);console.log(`Auth health:`,t.statusText||`OK`);let n=await Q.ping();console.log(`RPC ping:`,n);let r=await he.ping();console.log(`API ping:`,r)}}),_e=j({meta:{name:`rebuild-bot`,description:`Rebuild your bot`},run:async()=>{let e=await Q.listBots();if(e.length===0){console.log(`No bots`);return}let t=()=>X(`Bot rebuild canceled`),{index:n,confirmed:r}=await a({index:()=>d({message:`Bot`,options:e.map((e,t)=>({value:t,label:`${e.name} (@${e.telegramBotUsername})`}))}),confirmed:()=>i({message:`Rebuild this bot and permanently delete its data?`,initialValue:!1})},{onCancel:t});r||t();let o=e[n];if(!o)throw Error(`Bot not found`);await Q.rebuildBot({telegramBotId:o.telegramBotId}),l(`Bot rebuilt`)}}),ve=j({meta:{name:`remove-bot`,description:`Remove your bot`},run:async()=>{let e=await Q.listBots();if(e.length===0){console.log(`No bots`);return}let t=()=>X(`Bot removal canceled`),{index:n,confirmed:r}=await a({index:()=>d({message:`Bot`,options:e.map((e,t)=>({value:t,label:`${e.name} (@${e.telegramBotUsername})`}))}),confirmed:()=>i({message:`Remove this bot and permanently delete its data?`,initialValue:!1})},{onCancel:t});r||t();let c=e[n];if(!c)throw Error(`Bot not found`);await Q.removeBot({telegramBotId:c.telegramBotId}),s.success(`Bot removed`);let u=`${c.name} (@${c.telegramBotUsername})`,f=await i({message:`Also delete ${u} from Telegram?`,initialValue:!0});if(o(f)||!f){l(`${u} remains on Telegram. To reconnect it to your account, send /start to it and follow the instructions.`);return}let p=new URL(`https://t.me/BotFather`);p.searchParams.set(`start`,`deletebot`),s.info(`Continue deleting ${u} from Telegram:\n${p}`),await Z(p.href),l(`Select @${c.telegramBotUsername} in BotFather and follow the instructions.`)}}),ye=j({meta:{name:`repair-bot`,description:`Check and repair your bot`},run:async()=>{let e=await Q.listBots();if(e.length===0){console.log(`No bots`);return}let{index:t}=await a({index:()=>d({message:`Bot`,options:e.map((e,t)=>({value:t,label:`${e.name} (@${e.telegramBotUsername})`}))})},{onCancel:()=>X(`Bot repair canceled`)}),n=e[t];if(!n)throw Error(`Bot not found`);await Q.repairBot({telegramBotId:n.telegramBotId}),l(`Bot repaired`)}}),be=N({meta:{name:`resend-verification`,description:`Request another verification email`},run:async()=>{let{email:e}=await a({email:()=>f({message:`Email`,validate:H})},{onCancel:()=>X(`Verification email request canceled`)});try{await k.sendVerificationEmail({email:e,callbackURL:`/cli/email-verification`})}catch(t){throw Error(`Failed to request verification email for ${e}`,{cause:t})}l(`Verification email requested for ${e}`)}}),xe=N({meta:{name:`reset-password`,description:`Reset your password`},run:async()=>{let e=await a({token:()=>u({message:`Token`,validate:W,clearOnError:!0}),newPassword:()=>u({message:`New password`,validate:J,clearOnError:!0}),confirmPassword:({results:e})=>u({message:`Confirm password`,validate:z(e.newPassword||``),clearOnError:!0})},{onCancel:()=>X(`Password reset canceled`)});await k.resetPassword({token:e.token,newPassword:e.newPassword}),l(`Password reset succeeded. Run the sign-in command to continue.`)}}),Se=j({meta:{name:`reset-usage`,description:`Reset your bot’s usage limits`},run:async()=>{let e=await Q.listBots();if(e.length===0){console.log(`No bots`);return}let t=()=>X(`Usage reset canceled`),{index:n,confirmed:r}=await a({index:()=>d({message:`Bot`,options:e.map((e,t)=>({value:t,label:`${e.name} (@${e.telegramBotUsername})`}))}),confirmed:()=>i({message:`Use an available reset for this bot?`,initialValue:!1})},{onCancel:t});r||t();let o=e[n];if(!o)throw Error(`Bot not found`);await Q.resetUsage({telegramBotId:o.telegramBotId}),l(`Usage limits reset`)}}),Ce=j({meta:{name:`revoke-session`,description:`Revoke an active session`},run:async({data:e})=>{let t=await k.listSessions();if(t.length===0){console.log(`No active sessions`);return}let{index:n}=await a({index:()=>d({message:`Session`,options:t.map((t,n)=>({value:n,label:`Session ${n+1}${t.id===e.session.id?` (Current)`:``}`,hint:`Created: ${t.createdAt.toISOString()} · User agent: ${t.userAgent||`(None)`} · IP address: ${t.ipAddress||`(None)`}`}))})},{onCancel:()=>X(`Session revocation canceled`)}),r=t[n];if(!r)throw Error(`Session not found`);await k.revokeSession({token:r.token}),r.id===e.session.id&&await T(),l(`Session revoked`)}}),$=N({meta:{name:`sign-in`,description:`Sign in`},run:async()=>{let e=await a({email:()=>f({message:`Email`,validate:H}),password:()=>u({message:`Password`,validate:K})},{onCancel:()=>X(`Sign-in canceled`)}),t;try{({user:t}=await k.signIn.email(e))}catch(t){throw O(t)&&await D(e.email),t}t.emailVerified||await D(t.email),l(`Signed in as ${t.email}`)}}),we=t({meta:{name:`sign-out`,description:`Sign out`},run:async()=>{await k.signOut(),await T(),l(`Signed out`)}}),Te=N({meta:{name:`sign-up`,description:`Create an account`},run:async()=>{let e=await a({name:()=>f({message:`Name`,validate:B}),email:()=>f({message:`Email`,validate:H}),password:()=>u({message:`Password`,validate:Y,clearOnError:!0})},{onCancel:()=>X(`Sign-up canceled`)}),t=await k.signUp.email({...e,callbackURL:`/cli/email-verification`});t.user.emailVerified||await D(t.user.email),l(`Signed up as ${t.user.email}`)}}),Ee=j({meta:{name:`update-profile`,description:`Update your profile`},run:async({data:e})=>{let{name:t,avatar:n}=await a({name:()=>f({message:`Name`,initialValue:e.user.name,validate:B}),avatar:()=>f({message:`Avatar`,initialValue:e.user.image||void 0,placeholder:`Optional URL`,validate:V})},{onCancel:()=>X(`Profile update canceled`)});await k.updateUser({name:t,image:n||null}),l(`Profile update succeeded`)}}),De=j({meta:{name:`view-usage`,description:`View your bot’s usage details`},run:async()=>{let e=await Q.listBots();if(e.length===0){console.log(`No bots`);return}let{index:t}=await a({index:()=>d({message:`Bot`,options:e.map((e,t)=>({value:t,label:`${e.name} (@${e.telegramBotUsername})`}))})},{onCancel:()=>X(`Usage check canceled`)}),n=e[t];if(!n)throw Error(`Bot not found`);let r=await Q.viewUsage({telegramBotId:n.telegramBotId}),i=`Subscription: (None)`;if(r.subscription){let{plan:e,price:t,usage:n}=r.subscription,a=new Intl.NumberFormat(`en-US`,{style:`currency`,currency:t.currency}),o=10**(a.resolvedOptions().maximumFractionDigits??2),s=t.duration.count===1?t.duration.unit:`${t.duration.count} ${t.duration.unit}s`;i=`Subscription:\n Plan: ${e}\n Price: ${a.format(t.amount/o)}\n Billing: Every ${s}\n Usage:\n Five-hour used: ${n.fiveHourUsedPercent}%\n Weekly used: ${n.weeklyUsedPercent}%\n Available resets: ${n.availableResets}`}c(`Status: ${r.status||`(None)`}\n${i}`,`${n.name} (@${n.telegramBotUsername})`)}}),Oe=j({meta:{name:`whoami`,description:`Show the current user`},run:({data:e})=>{c([`Name: ${e.user.name}`,`Email: ${e.user.email}`,`Avatar: ${e.user.image||`(None)`}`,`ID: ${e.user.id}`,`Created: ${e.user.createdAt.toISOString()}`,`Updated: ${e.user.updatedAt.toISOString()}`].join(`
4
+ `),`Current user`)}});const ke=t({meta:{name:`foundingbot`,version:b,description:x},subCommands:{"sign-up":Te,"resend-verification":be,"sign-in":$,"sign-out":we,"forgot-password":ue,"reset-password":xe,whoami:Oe,"update-profile":Ee,"change-email":se,"change-password":ce,"list-sessions":fe,"revoke-session":Ce,"create-bot":le,"activate-bot":oe,"list-bots":de,"repair-bot":ye,"rebuild-bot":_e,"remove-bot":ve,"view-usage":De,"reset-usage":Se,"manage-billing":pe,ping:ge}});await n(ke);export{};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foundingbot",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Founding Bot CLI client",
5
5
  "type": "module",
6
6
  "main": "./dist/main.mjs",
@@ -10,6 +10,7 @@
10
10
  "dependencies": {
11
11
  "@clack/prompts": "^1.8.1",
12
12
  "@foundingbot/util": "^0.1.6",
13
+ "@napi-rs/keyring": "^2.1.0",
13
14
  "@orpc/client": "^1.15.4",
14
15
  "@orpc/openapi-client": "^1.15.4",
15
16
  "better-auth": "^1.7.7",