foundingbot 0.1.13 → 0.1.14
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 +6 -0
- package/dist/main.mjs +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @foundingbot/cli
|
|
2
2
|
|
|
3
|
+
## 0.1.14
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#199](https://github.com/phuctm97/foundingbot/pull/199) [`cb86a15`](https://github.com/phuctm97/foundingbot/commit/cb86a15db81d480734588c139a04d770a3135f53) Thanks [@kittranofficial](https://github.com/kittranofficial)! - Activate a bot from a link: its /start reply now has an "Activate bot" button instead of an activation code. The button opens a new bot activation page, which continues to Stripe Checkout once you're signed in. The CLI's `activate-bot` command now asks for that link instead of a code. The sign-in page's "Forgot your password?" link is now dimmed, so it stands apart from the "Sign up" link.
|
|
8
|
+
|
|
3
9
|
## 0.1.13
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/dist/main.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{defineCittyPlugin as e,defineCommand as t,runCommand as n,runMain as r}from"citty";import{cancel as i,confirm as a,group as o,isCancel as s,log as c,note as l,outro as u,password as d,select as f,spinner as p,text as m}from"@clack/prompts";import{createAuthClient as h}from"better-auth/client";import{deviceAuthorizationClient as ee}from"better-auth/client/plugins";import{AsyncEntry as te}from"@napi-rs/keyring";import{once as g}from"node:events";import{setTimeout as _}from"node:timers/promises";import v from"open";import{z as y}from"zod";import{createORPCClient as b}from"@orpc/client";import{RPCLink as ne}from"@orpc/client/fetch";import{contract as re}from"@foundingbot/util";import{OpenAPILink as ie}from"@orpc/openapi-client/fetch";var x=`foundingbot`,
|
|
3
|
-
`),`Session ${n+1}${t.id===e.session.id?` (Current)`:``}`)})}}),
|
|
4
|
-
`),`Current user`)}}),"update-profile":
|
|
2
|
+
import{defineCittyPlugin as e,defineCommand as t,runCommand as n,runMain as r}from"citty";import{cancel as i,confirm as a,group as o,isCancel as s,log as c,note as l,outro as u,password as d,select as f,spinner as p,text as m}from"@clack/prompts";import{createAuthClient as h}from"better-auth/client";import{deviceAuthorizationClient as ee}from"better-auth/client/plugins";import{AsyncEntry as te}from"@napi-rs/keyring";import{once as g}from"node:events";import{setTimeout as _}from"node:timers/promises";import v from"open";import{z as y}from"zod";import{createORPCClient as b}from"@orpc/client";import{RPCLink as ne}from"@orpc/client/fetch";import{contract as re}from"@foundingbot/util";import{OpenAPILink as ie}from"@orpc/openapi-client/fetch";var x=`foundingbot`,ae=`0.1.14`,S=`Founding Bot CLI client`;const C=new URL(process.env.FOUNDINGBOT_SERVER_URL||`https://server.founding.bot`);function w(){if(C.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 T=new te(w(),`token`);async function E(){return await T.getPassword()||void 0}function D(e){return T.setPassword(e)}function O(){return T.deleteCredential()}async function k(){let e=await E();return{authorization:e?`Bearer ${e}`:void 0,"foundingbot-client":x}}async function A(e){throw await O(),Error(`Verify ${e} to continue. Check your inbox or run the resend-verification command to request another verification email.`)}function oe(e){let t=e instanceof Error?e.cause:void 0;return typeof t==`object`&&!!t&&`code`in t&&t.code===`EMAIL_NOT_VERIFIED`}var j=h({baseURL:new URL(`/auth`,C).href,fetchOptions:{throw:!0,auth:{token:E,type:`Bearer`},headers:{"foundingbot-client":x},onSuccess:async({response:e})=>{let t=e.headers.get(`set-auth-token`);t&&await D(t)},customFetchImpl:(e,t)=>globalThis.fetch(e,t)},plugins:[ee()]});const se=e({name:`require-signed-in`,setup:async e=>{let t=await j.getSession();if(!t)throw Error(`Not signed in. Run the sign-in command, then try again.`);t.user.emailVerified||await A(t.user.email),e.data=t}});function M(e){return t({...e,run:e.run,plugins:[se]})}const ce=e({name:`require-signed-out`,setup:async()=>{let e=await j.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:[ce]})}function P(e){return t({meta:e.meta,args:e.args,run:async({rawArgs:t})=>{try{await n(e,{rawArgs:t})}catch(e){let t=process.env.FOUNDINGBOT_CLI_DEBUG===`1`||process.env.FOUNDINGBOT_CLI_DEBUG===`true`;e instanceof Error&&!t?c.error(e.message||`An error occurred`,{output:process.stderr}):console.error(e),process.exit(1)}}})}function F(e,{trim:t=!0}={}){return y.string().refine(e=>(t?e.trim():e).length>0,`${e} is required`)}const I=y.email(`Enter a valid email address`);function L(e){return F(e).pipe(I)}function R(e){return F(e,{trim:!1}).min(8,`Password must be at least 8 characters`).max(128,`Password must be at most 128 characters`)}const z=F(`Confirm password`,{trim:!1});function B(e){return z.refine(t=>t===e,`Passwords do not match`)}const V=F(`Name`),H=y.union([y.literal(``),y.url({error:`Enter a valid HTTP or HTTPS URL`,protocol:/^https?$/})]),U=L(`Email`),W=L(`New email`),G=F(`Token`);function le(e){let t=URL.parse(e.trim())?.searchParams.get(`code`);return t&&/^[a-f0-9]{64}$/.test(t)?t:void 0}const K=F(`Activation link`).transform(le).pipe(y.string({error:`Enter the link from your bot's "Activate bot" button`})),ue=F(`Password`,{trim:!1}),de=F(`Current password`,{trim:!1}),q=R(`New password`),fe=R(`Password`);function J(e){i(e),process.exit(0)}async function Y(e){if(!process.env.SSH_CONNECTION)try{let t=await v(e);if(process.platform===`darwin`){t.ref();let[e]=await g(t,`close`);if(e!==0)throw Error(`Browser launch failed`)}}catch{c.warn(`Couldn’t open your browser. Open the link above manually.`)}}function pe(e){return _(e)}const me=new ne({url:new URL(`/rpc`,C),headers:k,fetch:(e,t)=>globalThis.fetch(e,t)});var X=b(me),he=M({meta:{name:`activate-bot`,description:`Activate your bot`},run:async()=>{let{activationLink:e}=await o({activationLink:()=>d({message:`Activation link`,validate:K,clearOnError:!0})},{onCancel:()=>J(`Bot activation canceled`)}),t=K.parse(e),{url:n}=await X.activateBot({activationCode:t});c.info(`Complete checkout in Stripe to activate your bot:\n${n}`),await Y(n),u(`Continue in your browser.`)}}),ge=M({meta:{name:`change-email`,description:`Change your email`},run:async()=>{let{newEmail:e}=await o({newEmail:()=>m({message:`New email`,validate:W})},{onCancel:()=>J(`Email change canceled`)});await j.changeEmail({newEmail:e,callbackURL:`/cli/email-change`}),u(`Email change requested for ${e}`)}}),_e=M({meta:{name:`change-password`,description:`Change your password`},run:async()=>{let e=await o({currentPassword:()=>d({message:`Current password`,validate:de}),newPassword:()=>d({message:`New password`,validate:q,clearOnError:!0}),confirmPassword:({results:e})=>d({message:`Confirm password`,validate:B(e.newPassword||``),clearOnError:!0}),revokeOtherSessions:()=>a({message:`Revoke other sessions?`})},{onCancel:()=>J(`Password change canceled`)});await j.changePassword({currentPassword:e.currentPassword,newPassword:e.newPassword,revokeOtherSessions:e.revokeOtherSessions}),u(`Password change succeeded`)}}),ve=N({meta:{name:`forgot-password`,description:`Request a password reset`},run:async()=>{let{email:e}=await o({email:()=>m({message:`Email`,validate:U})},{onCancel:()=>J(`Password reset request canceled`)});try{await j.requestPasswordReset({email:e,redirectTo:`/cli/password-reset`})}catch(t){throw Error(`Failed to request password reset for ${e}`,{cause:t})}u(`If an account exists for ${e}, password reset instructions have been sent`)}}),ye=M({meta:{name:`list-bots`,description:`List your bots`},run:async()=>{let e=await X.listBots();e.length===0?console.log(`No bots`):e.forEach((e,t)=>{l(`Name: ${e.name}\nTelegram bot username: @${e.telegramBotUsername}\nTelegram bot ID: ${e.telegramBotId}`,`Bot ${t+1}`)})}}),be=M({meta:{name:`list-sessions`,description:`List your active sessions`},run:async({data:e})=>{let t=await j.listSessions();t.length===0?console.log(`No active sessions`):t.forEach((t,n)=>{l([`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)`:``}`)})}}),xe=M({meta:{name:`manage-billing`,description:`Manage your billing`},run:async()=>{let{url:e}=await X.manageBilling();c.info(`Manage your billing in Stripe:\n${e}`),await Y(e),u(`Continue in your browser.`)}});const Se=new ie(re,{url:new URL(`/api`,C),headers:k,fetch:(e,t)=>globalThis.fetch(e,t)});var Ce=b(Se),we=t({meta:{name:`ping`,description:`Run health checks`},run:async()=>{let e=await fetch(new URL(`/health`,C));if(!e.ok)throw Error(`Server health check failed`);console.log(`Server health:`,e.statusText||`OK`);let t=await fetch(new URL(`/auth/ok`,C));if(!t.ok)throw Error(`Auth health check failed`);console.log(`Auth health:`,t.statusText||`OK`);let n=await X.ping();console.log(`RPC ping:`,n);let r=await Ce.ping();console.log(`API ping:`,r)}}),Te=M({meta:{name:`rebuild-bot`,description:`Rebuild your bot`},run:async()=>{let e=await X.listBots();if(e.length===0){console.log(`No bots`);return}let t=()=>J(`Bot rebuild canceled`),{index:n,confirmed:r}=await o({index:()=>f({message:`Bot`,options:e.map((e,t)=>({value:t,label:`${e.name} (@${e.telegramBotUsername})`}))}),confirmed:()=>a({message:`Rebuild this bot and permanently delete its data?`,initialValue:!1})},{onCancel:t});r||t();let i=e[n];if(!i)throw Error(`Bot not found`);await X.rebuildBot({telegramBotId:i.telegramBotId}),u(`Bot rebuilt`)}}),Ee=M({meta:{name:`remove-bot`,description:`Remove your bot`},run:async()=>{let e=await X.listBots();if(e.length===0){console.log(`No bots`);return}let t=()=>J(`Bot removal canceled`),{index:n,confirmed:r}=await o({index:()=>f({message:`Bot`,options:e.map((e,t)=>({value:t,label:`${e.name} (@${e.telegramBotUsername})`}))}),confirmed:()=>a({message:`Remove this bot and permanently delete its data?`,initialValue:!1})},{onCancel:t});r||t();let i=e[n];if(!i)throw Error(`Bot not found`);await X.removeBot({telegramBotId:i.telegramBotId}),c.success(`Bot removed`);let l=`${i.name} (@${i.telegramBotUsername})`,d=await a({message:`Also delete ${l} from Telegram?`});if(s(d)||!d){u(`${l} 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`),c.info(`Continue deleting ${l} from Telegram:\n${p}`),await Y(p.href),u(`Select @${i.telegramBotUsername} in BotFather and follow the instructions.`)}}),De=M({meta:{name:`repair-bot`,description:`Check and repair your bot`},run:async()=>{let e=await X.listBots();if(e.length===0){console.log(`No bots`);return}let{index:t}=await o({index:()=>f({message:`Bot`,options:e.map((e,t)=>({value:t,label:`${e.name} (@${e.telegramBotUsername})`}))})},{onCancel:()=>J(`Bot repair canceled`)}),n=e[t];if(!n)throw Error(`Bot not found`);await X.repairBot({telegramBotId:n.telegramBotId}),u(`Bot repaired`)}}),Oe=N({meta:{name:`resend-verification`,description:`Request another verification email`},run:async()=>{let{email:e}=await o({email:()=>m({message:`Email`,validate:U})},{onCancel:()=>J(`Verification email request canceled`)});try{await j.sendVerificationEmail({email:e,callbackURL:`/cli/email-verification`})}catch(t){throw Error(`Failed to request verification email for ${e}`,{cause:t})}u(`Verification email requested for ${e}`)}}),ke=N({meta:{name:`reset-password`,description:`Reset your password`},run:async()=>{let e=await o({token:()=>d({message:`Token`,validate:G,clearOnError:!0}),newPassword:()=>d({message:`New password`,validate:q,clearOnError:!0}),confirmPassword:({results:e})=>d({message:`Confirm password`,validate:B(e.newPassword||``),clearOnError:!0})},{onCancel:()=>J(`Password reset canceled`)});await j.resetPassword({token:e.token,newPassword:e.newPassword}),u(`Password reset succeeded. Run the sign-in command to continue.`)}}),Z=M({meta:{name:`reset-usage`,description:`Reset your bot’s usage limits`},run:async()=>{let e=await X.listBots();if(e.length===0){console.log(`No bots`);return}let t=()=>J(`Usage reset canceled`),{index:n,confirmed:r}=await o({index:()=>f({message:`Bot`,options:e.map((e,t)=>({value:t,label:`${e.name} (@${e.telegramBotUsername})`}))}),confirmed:()=>a({message:`Use an available reset for this bot?`,initialValue:!1})},{onCancel:t});r||t();let i=e[n];if(!i)throw Error(`Bot not found`);await X.resetUsage({telegramBotId:i.telegramBotId}),u(`Usage limits reset`)}}),Ae=M({meta:{name:`revoke-session`,description:`Revoke an active session`},run:async({data:e})=>{let t=await j.listSessions();if(t.length===0){console.log(`No active sessions`);return}let{index:n}=await o({index:()=>f({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:()=>J(`Session revocation canceled`)}),r=t[n];if(!r)throw Error(`Session not found`);await j.revokeSession({token:r.token}),r.id===e.session.id&&await O(),u(`Session revoked`)}});function Q(e){return e instanceof Error?`code`in e&&[`ECONNRESET`,`ECONNREFUSED`,`ECONNABORTED`,`ETIMEDOUT`,`EPIPE`,`EAI_AGAIN`,`ENETUNREACH`,`EHOSTUNREACH`].includes(String(e.code))?!0:Q(e.cause):!1}const $=`The code expired. Run the sign-in command with --device-auth to get a new code.`;function je(e){let t=e instanceof Error?e.cause:void 0;return typeof t==`object`&&t&&`error`in t?t.error:void 0}async function Me(){let e=await o({email:()=>m({message:`Email`,validate:U}),password:()=>d({message:`Password`,validate:ue})},{onCancel:()=>J(`Sign-in canceled`)});try{return(await j.signIn.email(e)).user}catch(t){throw oe(t)&&await A(e.email),t}}async function Ne(){let{device_code:e,user_code:t,verification_uri_complete:n,interval:r,expires_in:i}=await j.device.code({client_id:x}),a=`${t.slice(0,4)}-${t.slice(4)}`,o=Date.now()+i*1e3;c.info(`Confirm the code ${a} in your browser:\n${n}`),await Y(n);let s=p({cancelMessage:`Sign-in canceled`,onCancel:()=>process.exit(0)});s.start(`Waiting for approval in your browser`);let l=r,u;for(;!u;){if(await pe(Math.min(l*1e3,Math.max(0,o-Date.now()))),Date.now()>=o)throw s.error(`Sign-in failed`),Error($);let t=AbortSignal.timeout(Math.max(0,o-Date.now()));try{({access_token:u}=await j.device.token({grant_type:`urn:ietf:params:oauth:grant-type:device_code`,device_code:e,client_id:x,fetchOptions:{signal:t}}))}catch(e){if(t.aborted)throw s.error(`Sign-in failed`),Error($);if(Q(e)){l*=2;continue}let n=je(e);if(n===`authorization_pending`)continue;if(n===`slow_down`){l+=5;continue}throw s.error(`Sign-in failed`),n===`access_denied`?Error(`Sign-in was denied in your browser.`):n===`expired_token`?Error($):e}}await D(u),s.stop(`Approved in your browser`);let d=await j.getSession();if(!d)throw await O(),Error(`Couldn’t load your account after sign-in.`);return d.user}var Pe=N({meta:{name:`sign-in`,description:`Sign in`},args:{"device-auth":{type:`boolean`,description:`Sign in by approving a code in your browser`}},run:async({args:e})=>{let t=e[`device-auth`]?await Ne():await Me();t.emailVerified||await A(t.email),u(`Signed in as ${t.email}`)}}),Fe=t({meta:{name:`sign-out`,description:`Sign out`},run:async()=>{await j.signOut(),await O(),u(`Signed out`)}}),Ie=N({meta:{name:`sign-up`,description:`Create an account`},run:async()=>{let e=await o({name:()=>m({message:`Name`,validate:V}),email:()=>m({message:`Email`,validate:U}),password:()=>d({message:`Password`,validate:fe,clearOnError:!0})},{onCancel:()=>J(`Sign-up canceled`)}),t=await j.signUp.email({...e,callbackURL:`/cli/email-verification`});t.user.emailVerified||await A(t.user.email),u(`Signed up as ${t.user.email}`)}}),Le=M({meta:{name:`update-profile`,description:`Update your profile`},run:async({data:e})=>{let{name:t,avatar:n}=await o({name:()=>m({message:`Name`,initialValue:e.user.name,validate:V}),avatar:()=>m({message:`Avatar`,initialValue:e.user.image||void 0,placeholder:`Optional URL`,validate:H})},{onCancel:()=>J(`Profile update canceled`)});await j.updateUser({name:t,image:n||null}),u(`Profile update succeeded`)}}),Re=M({meta:{name:`view-usage`,description:`View your bot’s usage details`},run:async()=>{let e=await X.listBots();if(e.length===0){console.log(`No bots`);return}let{index:t}=await o({index:()=>f({message:`Bot`,options:e.map((e,t)=>({value:t,label:`${e.name} (@${e.telegramBotUsername})`}))})},{onCancel:()=>J(`Usage check canceled`)}),n=e[t];if(!n)throw Error(`Bot not found`);let r=await X.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}`}l(`Status: ${r.status||`(None)`}\n${i}`,`${n.name} (@${n.telegramBotUsername})`)}}),ze={"sign-up":Ie,"resend-verification":Oe,"sign-in":Pe,"sign-out":Fe,"forgot-password":ve,"reset-password":ke,whoami:M({meta:{name:`whoami`,description:`Show the current user`},run:({data:e})=>{l([`Name: ${e.user.name.trim()||`(None)`}`,`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`)}}),"update-profile":Le,"change-email":ge,"change-password":_e,"list-sessions":be,"revoke-session":Ae,"activate-bot":he,"list-bots":ye,"repair-bot":De,"rebuild-bot":Te,"remove-bot":Ee,"view-usage":Re,"reset-usage":Z,"manage-billing":xe,ping:we};const Be=t({meta:{name:`foundingbot`,version:ae,description:S},subCommands:Object.fromEntries(Object.entries(ze).map(([e,t])=>[e,P(t)]))});await r(Be);export{};
|